Layered architecture with explicit dependency injection (no global state):
src/
domain/ tasks (stages, dependencies, artifacts, claiming), events, validation
storage/ SQLite (better-sqlite3, WAL mode)
transport/ REST (node:http), WebSocket (ws), MCP (stdio)
ui/ Vanilla JS kanban dashboard (no build step for UI)
- No frameworks — no React, Vue, Express. Pure Node.js + TypeScript.
context.tsis the DI root — wires all services together.- UI files (
index.html,app.js,styles.css) are plain files copied todist/ui/on build. - Soft deps:
AgentBridge→ agent-comm (notifications),KnowledgeBridge→ agent-knowledge (push learnings/decisions on task completion). Both HTTP-only, fail-open.
- Layout: Kanban board with columns per pipeline stage
- Icons: Material Symbols Outlined (via Google Fonts CSS). No emojis.
- Fonts: Inter (UI text), JetBrains Mono (code/data)
- Theme: Light/dark toggle via
data-theme="dark"attribute on<html> - Design tokens: CSS custom properties (
--bg,--accent,--border,--shadow-*, etc.) - Accent color:
#5d8da8 - Radii: 8px cards/columns, 4px tags
- Column headers: Uppercase, 12px, weight 600, letter-spacing 0.5px
- Tags: Color-coded by type (project=accent, assignee=purple, priority=orange, artifacts=blue, blocked=red)
- No inline comments — only file-level section headers (
// === ... ===or// --- ... ---) - No Co-Authored-By or Claude branding in commits
- ESLint + Prettier enforced via lint-staged (husky pre-commit)
- Version lives in
package.jsonand is read at runtime (REST/health, WS state payload, UI header) - Never hardcode version strings
- Every commit must bump the patch version minimum
- Commit message format:
v1.0.x: short description
npm run build # tsc + copy UI files to dist/
npm test # vitest (unit + integration)
npm run check # typecheck + lint + format + test
npm run dev # watch mode (tsc + nodemon)
Default: backlog → spec → plan → implement → test → review → done
Configurable per project via task_pipeline_config. Tasks advance through stages sequentially; dependencies block advancement until resolved.
- REST:
GET /health,GET/POST /api/tasks,PUT /api/tasks/:id/stage,GET /api/tasks/:id/artifacts,GET/POST /api/tasks/:id/comments,GET /api/search?q=,GET /api/agents - WebSocket: Full state on connect, incremental events streamed, DB polling for cross-process updates (2s interval)
- MCP (8 tools):
task_create,task_get(include subtasks/artifacts/comments),task_list(search viaquery, pick next vianext: true),task_update(metadata + dependencies),task_delete,task_stage(claim/advance/regress/complete/fail/cancel),task_artifact(general/decision/learning/comment),task_config
The dashboard server polls the SQLite DB every 2 seconds to detect changes made by other processes (MCP stdio servers). This ensures the kanban board stays in sync even when tasks are created/modified via MCP tools in separate Claude Code sessions.
- Task: id, title, description, status (pending/in_progress/completed/failed/cancelled), stage, priority, project, assigned_to, parent_id, tags, result, created_by, created_at, updated_at
- Status transitions: pending -> in_progress (claim), in_progress -> completed/failed, any non-terminal -> cancelled
- Stage transitions: Sequential through pipeline; regress allowed to any earlier stage; dependencies block advancement
- Artifact: name + stage + task_id = identity; version auto-incremented, previous_id links history
- Comment: threaded via parent_comment_id; agent_id tracks author
- Collaborator: task_id + agent_id + role (collaborator/reviewer/watcher)
- Approval: task_id + stage + status (pending/approved/rejected); blocks advancement when required
- Dependency: task_id -> depends_on_id (DAG with cycle detection)
Custom error hierarchy in src/types.ts:
TasksError(base, 400) ->NotFoundError(404),ConflictError(409),ValidationError(422)- Always use these instead of plain
Error— the MCP transport formats them for tool responses
- Framework: vitest with global test functions
- Test files:
tests/*.test.ts(13 files, 355+ tests) - In-memory SQLite via
createTestContext()fromtests/helpers.ts - Each test gets a fresh context in
beforeEach, closed inafterEach - Pattern: Arrange-Act-Assert with descriptive
describe/itblocks
- SQLite with WAL mode, better-sqlite3
- Schema versioning via
_metatable (current: V5) - Migrations in
src/storage/database.ts:migrateV1(),migrateV2(),migrateV3(),migrateV4(),migrateV5() - All tables use FK constraints with
ON DELETE CASCADE - FTS5 virtual table for full-text search with triggers for sync