# 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` — 14 repository interfaces - **Adapters** (implementations): `apps/server/db/repositories/drizzle/*.ts` — 14 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' \| 'pending_review' \| 'completed' \| 'archived', default 'active' | | branch | text nullable | auto-generated initiative branch (e.g., 'cw/user-auth') | | executionMode | text enum | 'yolo' \| 'review_per_phase', default 'review_per_phase' | | qualityReview | integer (boolean) | default false; flags initiative for quality review workflow | | 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' \| 'pending_review' | | mergeBase | text nullable | git merge-base hash stored before phase merge, enables diff reconstruction for completed phases | | 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' | | category | text enum | 'execute' \| 'research' \| 'discuss' \| 'plan' \| 'detail' \| 'refine' \| 'verify' \| 'merge' \| 'review' | | priority | text enum | 'low' \| 'medium' \| 'high' | | status | text enum | 'pending' \| 'in_progress' \| 'quality_review' \| 'completed' \| 'blocked' | | order | integer | default 0 | | summary | text nullable | Agent result summary — propagated to dependent tasks as context | | retryCount | integer NOT NULL | default 0, incremented on agent crash auto-retry, reset on manual retry | | 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 | | | prompt | text nullable | Full assembled prompt passed to agent at spawn; persisted for durability after log cleanup | | 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' | | lastFetchedAt | integer/timestamp | nullable, updated by ProjectSyncManager | | 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)`. ### chat_sessions Persistent chat sessions for iterative refinement of phases/tasks. | Column | Type | Notes | |--------|------|-------| | id | text PK | nanoid | | targetType | text enum | 'phase' \| 'task' | | targetId | text NOT NULL | phase or task ID | | initiativeId | text FK → initiatives (cascade) | | | agentId | text FK → agents (set null) | linked agent | | status | text enum | 'active' \| 'closed', default 'active' | | createdAt, updatedAt | integer/timestamp | | Indexes: `(targetType, targetId)`, `(initiativeId)`. ### chat_messages Messages within a chat session. | Column | Type | Notes | |--------|------|-------| | id | text PK | nanoid | | chatSessionId | text FK → chat_sessions (cascade) | | | role | text enum | 'user' \| 'assistant' \| 'system' | | content | text NOT NULL | | | changeSetId | text FK → change_sets (set null) | links assistant messages to applied changes | | createdAt | integer/timestamp | | Index: `(chatSessionId)`. ### errands Tracks errand work items linked to a project branch, optionally assigned to an agent. | Column | Type | Notes | |--------|------|-------| | id | text PK | caller-supplied | | description | text NOT NULL | human-readable description | | branch | text NOT NULL | working branch name | | baseBranch | text NOT NULL | default 'main' | | agentId | text FK → agents (set null) | assigned agent; null if unassigned | | projectId | text FK → projects (cascade) | owning project | | status | text enum | active, pending_review, conflict, merged, abandoned; default 'active' | | createdAt, updatedAt | integer/timestamp | | ### review_comments Inline review comments on phase diffs, persisted across page reloads. | Column | Type | Notes | |--------|------|-------| | id | text PK | nanoid | | phaseId | text FK → phases (cascade) | scopes comment to phase | | filePath | text NOT NULL | file in diff | | lineNumber | integer NOT NULL | line number (new-side or old-side for deletions) | | lineType | text enum | 'added' \| 'removed' \| 'context' | | body | text NOT NULL | comment text | | author | text NOT NULL | default 'you' | | resolved | integer/boolean | default false | | createdAt, updatedAt | integer/timestamp | | Index: `(phaseId)`. ## Repository Interfaces 14 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, 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, findByAgentIds (batch), deleteByAgentId, getSessionCount | | ConversationRepository | create, findById, findPendingForAgent, answer, countByFromAgentIds (batch), findByFromAgentId | | ChatSessionRepository | createSession, findActiveSession, findActiveSessionByAgentId, updateSession, createMessage, findMessagesBySessionId | | ReviewCommentRepository | create, findByPhaseId, resolve, unresolve, delete | | ErrandRepository | create, findById, findAll (filter by projectId/status), update, delete | ## 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 0035 (36 total).