feat: pulse-dev — dev environment full-stack com hot reload + agentes + taskboard + vault Obsidian

- Stack Swarm 'dev': redis + taskboard + frontend HMR (Vite) + backend HMR (tsx) + 5 agents
- TaskBoard standalone React com Redis pub/sub e BLPOP queue
- Backend API Express: /tasks /agents /health com hot reload
- Agentes: 2 FE (frontend), 2 BE (backend), 1 DevOps (parallel workers)
- Vault Obsidian: /root/Obsidian-Pulse/ com estrutura Inbox/Projetos/Docker/Dev/Codex/Logs/Memorias/Templates
- Skill obsidian-vault-linker instalada e documentada
- Caddy labels: board.octal.tec.br + api.octal.tec.br + frontend.octal.tec.br
- Protocolo task queue Redis documentado em MEMORY.md
This commit is contained in:
Pulse Agent
2026-05-20 19:40:54 -03:00
parent 8aa77a5ac3
commit adeb4dad33
12 changed files with 730 additions and 0 deletions
+22
View File
@@ -0,0 +1,22 @@
{
"name": "pulse-dev-backend",
"private": true,
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "tsx watch --dir ./src server.ts"
},
"dependencies": {
"express": "^5.1.0",
"ioredis": "^5.4.1",
"cors": "^2.8.5",
"helmet": "^8.0.0",
"uuid": "^10.0.0"
},
"devDependencies": {
"tsx": "^4.19.2",
"@types/express": "^5.0.0",
"@types/cors": "^2.8.17",
"@types/uuid": "^10.0.0"
}
}
+98
View File
@@ -0,0 +1,98 @@
import express from 'express'
import cors from 'cors'
import helmet from 'helmet'
import Redis from 'ioredis'
import { v4 as uuidv4 } from 'uuid'
const app = express()
app.use(cors())
app.use(helmet())
app.use(express.json())
// ─── Redis ──────────────────────────────────────────────────────────
const redis = new Redis({ host: 'redis', port: 6379, lazyConnect: true })
redis.on('error', (err) => console.error('[Redis] error:', err.message))
redis.on('ready', () => console.log('[Redis] conectado'))
// ─── Task Queue ─────────────────────────────────────────────────────
const TASK_QUEUE = 'dev-tasks'
const LOG_CHANNEL = 'dev-logs'
function log(level, msg) {
const line = JSON.stringify({ ts: new Date().toISOString(), level, msg })
redis.publish(LOG_CHANNEL, line).catch(() => {})
console.log(`[${level}] ${msg}`)
}
// GET /tasks — lista todas
// POST /tasks — cria tarefa
// PUT /tasks/:id — atualiza status
// GET /agents — lista agentes conectados
// GET /logs/:limit — logs recentes
app.get('/tasks', async (_req, res) => {
const keys = await redis.keys('task:*')
const tasks = []
for (const k of keys) {
const t = await redis.get(k)
if (t) tasks.push(JSON.parse(t))
}
tasks.sort((a, b) => b.updated_at?.localeCompare(a.updated_at) || 0)
res.json(tasks)
})
app.post('/tasks', async (req, res) => {
const id = uuidv4()
const task = {
id, title: req.body.title, type: req.body.type || 'feature',
priority: req.body.priority || 'medium', status: 'todo',
domain: req.body.domain || 'fullstack', assignee: null,
created_at: new Date().toISOString(), updated_at: new Date().toISOString(),
}
await redis.set(`task:${id}`, JSON.stringify(task))
await redis.rpush('task-queue', id)
log('TASK', `Nova tarefa: "${task.title}" [${task.type}]`)
res.status(201).json(task)
})
app.put('/tasks/:id', async (req, res) => {
const key = `task:${req.params.id}`
const raw = await redis.get(key)
if (!raw) return res.status(404).json({ error: 'Não encontrada' })
const task = JSON.parse(raw)
Object.assign(task, req.body, { updated_at: new Date().toISOString() })
await redis.set(key, JSON.stringify(task))
log('INFO', `Tarefa atualizada: "${task.title}" status=${task.status}`)
res.json(task)
})
app.get('/agents', async (_req, res) => {
const keys = await redis.keys('agent:*')
const agents = []
for (const k of keys) {
const a = await redis.get(k)
if (a) agents.push(JSON.parse(a))
}
res.json(agents)
})
app.get('/health', (_req, res) => {
res.json({ status: 'ok', ts: new Date().toISOString() })
})
// Promover tarefa automaticamente ao criar/modificar
app.use('*', (req, res) => { res.status(404).json({ error: 'Not found' }) })
const PORT = 3001
app.listen(PORT, '0.0.0.0', () => {
// Registrar agente backend
;(async () => {
try { await redis.connect() } catch (e) { console.error('Redis connect:', e.message) }
await redis.set('agent:dev-backend', JSON.stringify({
id: 'dev-backend', role: 'backend', status: 'online',
started_at: new Date().toISOString(),
}))
log('AGENT', `Backend API online :${PORT}`)
log('AGENT', `Redis conectado — queue: ${TASK_QUEUE}`)
})()
})