Execution agents were spawning blind — no input files, no knowledge of what predecessor tasks accomplished. This adds three capabilities: 1. summary column on tasks table — completeTask() reads the finishing agent's result.message and stores it on the task record 2. dispatchNext() gathers full initiative context (initiative, phase, sibling tasks, pages) and passes it as inputContext so agents get .cw/input/task.md, initiative.md, phase.md, and context directories 3. context/tasks/*.md files now include the summary field in frontmatter so dependent agents can see what prior agents accomplished
196 lines
7.8 KiB
Markdown
196 lines
7.8 KiB
Markdown
# Database Module
|
|
|
|
`apps/server/db/` — SQLite database via better-sqlite3 + Drizzle ORM with hexagonal architecture.
|
|
|
|
## Architecture
|
|
|
|
- **Schema**: `apps/server/db/schema.ts` — all tables, columns, relations
|
|
- **Ports** (interfaces): `apps/server/db/repositories/*.ts` — 10 repository interfaces
|
|
- **Adapters** (implementations): `apps/server/db/repositories/drizzle/*.ts` — 10 Drizzle adapters
|
|
- **Barrel exports**: `apps/server/db/index.ts` re-exports everything
|
|
|
|
All adapters use nanoid() for IDs, auto-manage timestamps, and use Drizzle's `.returning()` for atomic reads after writes.
|
|
|
|
## Tables
|
|
|
|
### initiatives
|
|
| Column | Type | Notes |
|
|
|--------|------|-------|
|
|
| id | text PK | nanoid |
|
|
| name | text NOT NULL | |
|
|
| status | text enum | 'active' \| 'completed' \| 'archived', default 'active' |
|
|
| mergeRequiresApproval | integer/boolean | default true |
|
|
| branch | text nullable | auto-generated initiative branch (e.g., 'cw/user-auth') |
|
|
| createdAt, updatedAt | integer/timestamp | |
|
|
|
|
### phases
|
|
| Column | Type | Notes |
|
|
|--------|------|-------|
|
|
| id | text PK | |
|
|
| initiativeId | text FK → initiatives (cascade) | |
|
|
| name | text NOT NULL | |
|
|
| content | text nullable | Tiptap JSON |
|
|
| status | text enum | 'pending' \| 'approved' \| 'in_progress' \| 'completed' \| 'blocked' |
|
|
| createdAt, updatedAt | integer/timestamp | |
|
|
|
|
### phase_dependencies
|
|
`phaseId` FK → phases, `dependsOnPhaseId` FK → phases. Both cascade delete.
|
|
|
|
### tasks
|
|
| Column | Type | Notes |
|
|
|--------|------|-------|
|
|
| id | text PK | |
|
|
| phaseId | text nullable FK → phases (cascade) | |
|
|
| initiativeId | text nullable FK → initiatives (cascade) | |
|
|
| parentTaskId | text nullable self-ref FK (cascade) | decomposition hierarchy |
|
|
| name | text NOT NULL | |
|
|
| description | text nullable | |
|
|
| type | text enum | 'auto' \| 'checkpoint:human-verify' \| 'checkpoint:decision' \| 'checkpoint:human-action' |
|
|
| category | text enum | 'execute' \| 'research' \| 'discuss' \| 'plan' \| 'detail' \| 'refine' \| 'verify' \| 'merge' \| 'review' |
|
|
| priority | text enum | 'low' \| 'medium' \| 'high' |
|
|
| status | text enum | 'pending_approval' \| 'pending' \| 'in_progress' \| 'completed' \| 'blocked' |
|
|
| requiresApproval | integer/boolean nullable | null = inherit from initiative |
|
|
| order | integer | default 0 |
|
|
| summary | text nullable | Agent result summary — propagated to dependent tasks as context |
|
|
| createdAt, updatedAt | integer/timestamp | |
|
|
|
|
### task_dependencies
|
|
`taskId` FK → tasks, `dependsOnTaskId` FK → tasks. Both cascade delete.
|
|
|
|
### agents
|
|
| Column | Type | Notes |
|
|
|--------|------|-------|
|
|
| id | text PK | |
|
|
| name | text NOT NULL UNIQUE | human-readable alias (adjective-animal) |
|
|
| taskId | text nullable FK → tasks (set null) | |
|
|
| initiativeId | text nullable FK → initiatives (set null) | |
|
|
| sessionId | text nullable | CLI session ID for resume |
|
|
| worktreeId | text NOT NULL | path to agent's git worktree |
|
|
| provider | text NOT NULL | default 'claude' |
|
|
| accountId | text nullable FK → accounts (set null) | |
|
|
| status | text enum | 'idle' \| 'running' \| 'waiting_for_input' \| 'stopped' \| 'crashed' |
|
|
| mode | text enum | 'execute' \| 'discuss' \| 'plan' \| 'detail' \| 'refine' |
|
|
| pid | integer nullable | OS process ID |
|
|
| exitCode | integer nullable | |
|
|
| outputFilePath | text nullable | |
|
|
| result | text nullable | JSON |
|
|
| pendingQuestions | text nullable | JSON |
|
|
| userDismissedAt | integer/timestamp nullable | |
|
|
| createdAt, updatedAt | integer/timestamp | |
|
|
|
|
### accounts
|
|
| Column | Type | Notes |
|
|
|--------|------|-------|
|
|
| id | text PK | |
|
|
| email | text NOT NULL | |
|
|
| provider | text NOT NULL | default 'claude' |
|
|
| configJson | text nullable | serialized .claude.json |
|
|
| credentials | text nullable | serialized .credentials.json |
|
|
| isExhausted | integer/boolean | default false |
|
|
| exhaustedUntil | integer/timestamp nullable | |
|
|
| lastUsedAt | integer/timestamp nullable | round-robin scheduling |
|
|
| sortOrder | integer | |
|
|
| createdAt, updatedAt | integer/timestamp | |
|
|
|
|
### proposals
|
|
| Column | Type | Notes |
|
|
|--------|------|-------|
|
|
| id | text PK | |
|
|
| agentId | text FK → agents (cascade) | |
|
|
| initiativeId | text FK → initiatives (cascade) | |
|
|
| targetType | text enum | 'page' \| 'phase' \| 'task' |
|
|
| targetId | text nullable | existing entity ID, null for creates |
|
|
| title, summary, content | text | markdown body |
|
|
| metadata | text nullable | JSON |
|
|
| status | text enum | 'pending' \| 'accepted' \| 'dismissed' |
|
|
| sortOrder | integer | |
|
|
| createdAt, updatedAt | integer/timestamp | |
|
|
|
|
### pages
|
|
| Column | Type | Notes |
|
|
|--------|------|-------|
|
|
| id | text PK | |
|
|
| initiativeId | text FK → initiatives (cascade) | |
|
|
| parentPageId | text nullable self-ref FK (cascade) | root page has NULL |
|
|
| title | text NOT NULL | |
|
|
| content | text nullable | Tiptap JSON |
|
|
| sortOrder | integer | |
|
|
| createdAt, updatedAt | integer/timestamp | |
|
|
|
|
### projects
|
|
| Column | Type | Notes |
|
|
|--------|------|-------|
|
|
| id | text PK | |
|
|
| name | text NOT NULL UNIQUE | |
|
|
| url | text NOT NULL UNIQUE | git repo URL |
|
|
| defaultBranch | text NOT NULL | default 'main' |
|
|
| createdAt, updatedAt | integer/timestamp | |
|
|
|
|
### initiative_projects (junction)
|
|
`initiativeId` + `projectId` with unique index. Both FK cascade.
|
|
|
|
### messages
|
|
Self-referencing (parentMessageId) for threading. Sender/recipient types: 'agent' | 'user'.
|
|
|
|
### agent_log_chunks
|
|
| Column | Type | Notes |
|
|
|--------|------|-------|
|
|
| id | text PK | |
|
|
| agentId | text NOT NULL | **NO FK** — survives agent deletion |
|
|
| agentName | text NOT NULL | snapshot for display |
|
|
| sessionNumber | integer | spawn=1, resume=prev+1 |
|
|
| content | text NOT NULL | raw JSONL chunk |
|
|
| createdAt | integer/timestamp | |
|
|
|
|
Index on `agentId` for fast queries.
|
|
|
|
### conversations
|
|
|
|
Inter-agent communication records.
|
|
|
|
| Column | Type | Notes |
|
|
|--------|------|-------|
|
|
| id | text PK | |
|
|
| fromAgentId | text NOT NULL | FK→agents ON DELETE CASCADE |
|
|
| toAgentId | text NOT NULL | FK→agents ON DELETE CASCADE |
|
|
| initiativeId | text | FK→initiatives ON DELETE SET NULL |
|
|
| phaseId | text | FK→phases ON DELETE SET NULL |
|
|
| taskId | text | FK→tasks ON DELETE SET NULL |
|
|
| question | text NOT NULL | |
|
|
| answer | text | nullable until answered |
|
|
| status | text enum | pending, answered |
|
|
| createdAt | integer/timestamp | |
|
|
| updatedAt | integer/timestamp | |
|
|
|
|
Indexes: `(toAgentId, status)` for listen polling, `(fromAgentId)`.
|
|
|
|
## Repository Interfaces
|
|
|
|
11 repositories, each with standard CRUD plus domain-specific methods:
|
|
|
|
| Repository | Key Methods |
|
|
|-----------|-------------|
|
|
| InitiativeRepository | create, findById, findAll, findByStatus, update, delete |
|
|
| PhaseRepository | + createDependency, getDependencies, getDependents, findByInitiativeId |
|
|
| TaskRepository | + findByParentTaskId, findByPhaseId, findPendingApproval, createDependency |
|
|
| AgentRepository | + findByName, findByTaskId, findBySessionId, findByStatus |
|
|
| MessageRepository | + findPendingForUser, findRequiringResponse, findReplies |
|
|
| PageRepository | + findRootPage, getOrCreateRootPage, findByParentPageId |
|
|
| ProjectRepository | + junction ops: setInitiativeProjects (diff-based), findProjectsByInitiativeId |
|
|
| AccountRepository | + findNextAvailable (round-robin), markExhausted, clearExpiredExhaustion |
|
|
| ProposalRepository | + findByAgentIdAndStatus, updateManyByAgentId, countByAgentIdAndStatus |
|
|
| LogChunkRepository | insertChunk, findByAgentId, deleteByAgentId, getSessionCount |
|
|
| ConversationRepository | create, findById, findPendingForAgent, answer |
|
|
|
|
## Migrations
|
|
|
|
Located in `drizzle/`. Applied via `ensureSchema()` on startup using Drizzle's `migrate()`.
|
|
|
|
Key rules:
|
|
- **Never use raw SQL** for schema initialization
|
|
- Run `npx drizzle-kit generate` to create migrations
|
|
- See [database-migrations.md](database-migrations.md) for full workflow
|
|
- Snapshots stale after 0008; migrations 0008+ are hand-written
|
|
|
|
Current migrations: 0000 through 0026 (27 total).
|