diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index 9ed57a7..03615c3 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -16,7 +16,7 @@ None
- [x] **Phase 1: Core Infrastructure** - CLI binary, server mode, process lifecycle, graceful shutdown
- [x] **Phase 1.1: Hexagonal Architecture** (INSERTED) - Tests, events, ports/adapters, tRPC
-- [ ] **Phase 2: Data Layer** - SQLite database with task hierarchy schema
+- [x] **Phase 2: Data Layer** - SQLite database with task hierarchy schema
- [ ] **Phase 3: Git Integration** - Worktree isolation per agent with proper lifecycle
- [ ] **Phase 4: Agent Lifecycle** - Spawn, stop, list agents with session persistence
- [ ] **Phase 5: Task Dispatch** - Task visibility, dependency dispatch, work queue
@@ -67,8 +67,8 @@ Plans:
**Plans**: 2 plans
Plans:
-- [x] 02-01: SQLite Database Setup
-- [ ] 02-02: Repository Layer
+- [x] 02-01: SQLite Database Setup (Wave 1)
+- [x] 02-02: Repository Layer (Wave 2)
### Phase 3: Git Integration
**Goal**: Git worktree management — create isolated worktrees per agent, preview diffs, integrate changes, cleanup
@@ -136,7 +136,7 @@ Phases execute in numeric order: 1 → 1.1 → 2 → 3 → 4 → 5 → 6 → 7
|-------|----------------|--------|-----------|
| 1. Core Infrastructure | 5/5 | Complete | 2026-01-30 |
| 1.1. Hexagonal Architecture | 6/6 | Complete | 2026-01-30 |
-| 2. Data Layer | 1/2 | In progress | - |
+| 2. Data Layer | 2/2 | Complete | 2026-01-30 |
| 3. Git Integration | 0/? | Not started | - |
| 4. Agent Lifecycle | 0/? | Not started | - |
| 5. Task Dispatch | 0/? | Not started | - |
diff --git a/.planning/STATE.md b/.planning/STATE.md
index 3db2f05..076dfbc 100644
--- a/.planning/STATE.md
+++ b/.planning/STATE.md
@@ -10,11 +10,11 @@ See: .planning/PROJECT.md (updated 2026-01-30)
## Current Position
Phase: 2 of 8 (Data Layer)
-Plan: 2 of ? in current phase
-Status: In progress
-Last activity: 2026-01-30 — Completed 02-02-PLAN.md
+Plan: 2 of 2 in current phase
+Status: Phase complete
+Last activity: 2026-01-30 — Completed Phase 2 (Data Layer)
-Progress: ██████████████░░ 92%
+Progress: ██████████████████ 100%
## Performance Metrics
@@ -29,7 +29,7 @@ Progress: ██████████████░░ 92%
|-------|-------|-------|----------|
| 1 | 5/5 | 15 min | 3 min |
| 1.1 | 6/6 | 15 min | 3 min |
-| 2 | 2/? | 8 min | 4 min |
+| 2 | 2/2 | 8 min | 4 min |
**Recent Trend:**
- Last 5 plans: 01.1-05 (2 min), 01.1-06 (4 min), 02-01 (3 min), 02-02 (5 min)
@@ -76,5 +76,5 @@ None yet.
## Session Continuity
Last session: 2026-01-30
-Stopped at: Completed 02-02-PLAN.md (Repository Layer)
+Stopped at: Completed Phase 2 (Data Layer) — all plans finished
Resume file: None
diff --git a/.planning/phases/02-data-layer/02-02-PLAN.md b/.planning/phases/02-data-layer/02-02-PLAN.md
index 7f1442b..2b13b17 100644
--- a/.planning/phases/02-data-layer/02-02-PLAN.md
+++ b/.planning/phases/02-data-layer/02-02-PLAN.md
@@ -5,18 +5,26 @@ type: execute
wave: 2
depends_on: ["02-01"]
files_modified:
- - src/db/repository.ts
- - src/db/drizzle-repository.ts
- - src/db/repository.test.ts
+ - src/db/repositories/initiative-repository.ts
+ - src/db/repositories/phase-repository.ts
+ - src/db/repositories/plan-repository.ts
+ - src/db/repositories/task-repository.ts
+ - src/db/repositories/drizzle/initiative.ts
+ - src/db/repositories/drizzle/phase.ts
+ - src/db/repositories/drizzle/plan.ts
+ - src/db/repositories/drizzle/task.ts
+ - src/db/repositories/drizzle/index.ts
+ - src/db/repositories/index.ts
- src/db/index.ts
+ - src/db/repositories/drizzle/*.test.ts
autonomous: true
---
-Create repository layer following hexagonal architecture (port + adapter pattern).
+Create repository layer following hexagonal architecture with separate repositories per aggregate.
-Purpose: Provide clean data access abstraction that decouples business logic from Drizzle/SQLite implementation.
-Output: TaskHierarchyRepository port interface and DrizzleTaskHierarchyRepository adapter with tests.
+Purpose: Each aggregate (Initiative, Phase, Plan, Task) gets its own repository port + adapter. Clean boundaries, single responsibility, proper DDD.
+Output: 4 repository interfaces, 4 Drizzle adapters, tests for each.
@@ -37,123 +45,170 @@ Output: TaskHierarchyRepository port interface and DrizzleTaskHierarchyRepositor
- Task 1: Create TaskHierarchyRepository port interface
- src/db/repository.ts
+ Task 1: Create repository port interfaces
+ src/db/repositories/initiative-repository.ts, src/db/repositories/phase-repository.ts, src/db/repositories/plan-repository.ts, src/db/repositories/task-repository.ts, src/db/repositories/index.ts
- Create the repository PORT interface following hexagonal architecture patterns established in Phase 1.1.
+ Create separate repository PORT interfaces for each aggregate. Each repository only knows about its own aggregate.
- Define TaskHierarchyRepository interface with:
+ src/db/repositories/initiative-repository.ts:
+ ```typescript
+ export interface InitiativeRepository {
+ create(data: Omit): Promise;
+ findById(id: string): Promise;
+ findAll(): Promise;
+ update(id: string, data: Partial<...>): Promise;
+ delete(id: string): Promise;
+ }
+ ```
- Initiative operations:
- - createInitiative(data: NewInitiative): Promise<Initiative>
- - getInitiative(id: string): Promise<Initiative | null>
- - listInitiatives(): Promise<Initiative[]>
- - updateInitiative(id: string, data: Partial<NewInitiative>): Promise<Initiative>
- - deleteInitiative(id: string): Promise<void>
+ src/db/repositories/phase-repository.ts:
+ ```typescript
+ export interface PhaseRepository {
+ create(data: Omit): Promise;
+ findById(id: string): Promise;
+ findByInitiativeId(initiativeId: string): Promise;
+ update(id: string, data: Partial<...>): Promise;
+ delete(id: string): Promise;
+ }
+ ```
- Phase operations:
- - createPhase(data: NewPhase): Promise<Phase>
- - getPhase(id: string): Promise<Phase | null>
- - listPhasesByInitiative(initiativeId: string): Promise<Phase[]>
- - updatePhase(id: string, data: Partial<NewPhase>): Promise<Phase>
- - deletePhase(id: string): Promise<void>
+ src/db/repositories/plan-repository.ts:
+ ```typescript
+ export interface PlanRepository {
+ create(data: Omit): Promise;
+ findById(id: string): Promise;
+ findByPhaseId(phaseId: string): Promise;
+ update(id: string, data: Partial<...>): Promise;
+ delete(id: string): Promise;
+ }
+ ```
- Plan operations:
- - createPlan(data: NewPlan): Promise<Plan>
- - getPlan(id: string): Promise<Plan | null>
- - listPlansByPhase(phaseId: string): Promise<Plan[]>
- - updatePlan(id: string, data: Partial<NewPlan>): Promise<Plan>
- - deletePlan(id: string): Promise<void>
+ src/db/repositories/task-repository.ts:
+ ```typescript
+ export interface TaskRepository {
+ create(data: Omit): Promise;
+ findById(id: string): Promise;
+ findByPlanId(planId: string): Promise;
+ update(id: string, data: Partial<...>): Promise;
+ delete(id: string): Promise;
+ }
+ ```
- Task operations:
- - createTask(data: NewTask): Promise<Task>
- - getTask(id: string): Promise<Task | null>
- - listTasksByPlan(planId: string): Promise<Task[]>
- - updateTask(id: string, data: Partial<NewTask>): Promise<Task>
- - deleteTask(id: string): Promise<void>
+ src/db/repositories/index.ts - re-export all interfaces.
- Import types from schema.ts. Use Promise for all operations (even though better-sqlite3 is sync) to allow future async adapters.
-
- Pattern: This is the PORT. Implementations are ADAPTERS. Just like EventBus in Phase 1.1.
+ Pattern: These are PORTS. Implementations are ADAPTERS. Same as EventBus in Phase 1.1.
+ Import types from ../schema.js. Use Promise for all operations.
- npm run build succeeds - interface compiles correctly
- TaskHierarchyRepository interface exported with all CRUD operations
+ npm run build succeeds - interfaces compile correctly
+ 4 separate repository interfaces exported, one per aggregate
- Task 2: Create DrizzleTaskHierarchyRepository adapter
- src/db/drizzle-repository.ts
+ Task 2: Create Drizzle repository adapters
+ src/db/repositories/drizzle/initiative.ts, src/db/repositories/drizzle/phase.ts, src/db/repositories/drizzle/plan.ts, src/db/repositories/drizzle/task.ts, src/db/repositories/drizzle/index.ts
- Implement the TaskHierarchyRepository interface using Drizzle ORM.
+ Implement each repository interface with a Drizzle adapter.
- DrizzleTaskHierarchyRepository class:
- - Constructor takes DrizzleDatabase instance (injected, not created internally)
- - Implements all methods from TaskHierarchyRepository interface
- - Uses nanoid for generating IDs (install nanoid if not present)
+ Each adapter class:
+ - Constructor takes DrizzleDatabase instance (dependency injection)
+ - Implements its repository interface
+ - Uses nanoid for generating IDs
- Sets createdAt/updatedAt timestamps automatically
- - Orders results by appropriate fields (phases by number, tasks by order)
+ - Orders results appropriately (phases by number, tasks by order)
- Implementation notes:
- - Use Drizzle's eq() for where clauses
- - Use returning() to get inserted/updated rows
- - For updates, set updatedAt to current timestamp
- - Throw if not found on update/delete (or silently succeed on delete - pick one, document it)
+ src/db/repositories/drizzle/initiative.ts:
+ - DrizzleInitiativeRepository implements InitiativeRepository
+ - CRUD operations on initiatives table only
- Error handling:
- - Throw descriptive errors for constraint violations
- - Don't swallow SQLite errors - let them propagate
+ src/db/repositories/drizzle/phase.ts:
+ - DrizzlePhaseRepository implements PhaseRepository
+ - findByInitiativeId orders by number
+ - Foreign key to initiative enforced by SQLite
- Export the class and re-export from src/db/index.ts
+ src/db/repositories/drizzle/plan.ts:
+ - DrizzlePlanRepository implements PlanRepository
+ - findByPhaseId orders by number
+ - Foreign key to phase enforced by SQLite
+
+ src/db/repositories/drizzle/task.ts:
+ - DrizzleTaskRepository implements TaskRepository
+ - findByPlanId orders by order field
+ - Foreign key to plan enforced by SQLite
+
+ src/db/repositories/drizzle/index.ts - re-export all adapters.
+
+ Install nanoid if not present.
+ Throw on update/delete if not found.
- npm run build succeeds - no type errors in implementation
- DrizzleTaskHierarchyRepository implements all interface methods
+ npm run build succeeds - no type errors in implementations
+ 4 Drizzle adapters implement their respective interfaces
Task 3: Write repository tests
- src/db/repository.test.ts
+ src/db/repositories/drizzle/initiative.test.ts, src/db/repositories/drizzle/phase.test.ts, src/db/repositories/drizzle/plan.test.ts, src/db/repositories/drizzle/task.test.ts
- Write comprehensive tests for DrizzleTaskHierarchyRepository using Vitest.
+ Write tests for each Drizzle repository adapter using Vitest.
- Test setup:
- - Use in-memory database (':memory:') for isolation
- - Create fresh database and apply schema before each test
- - Use Drizzle's migrate or direct schema push for test setup
+ Test setup (shared helper or per-file):
+ - Use in-memory database (':memory:')
+ - Push schema before each test (drizzle-kit push or direct SQL)
+ - Fresh repository instance per test
- Test cases:
+ initiative.test.ts:
+ - create generates id, sets timestamps
+ - findById returns null for non-existent
+ - findAll returns empty array initially
+ - update changes fields and updatedAt
+ - delete removes initiative (cascade tested separately)
- Initiative tests:
- - createInitiative creates with generated id
- - getInitiative returns null for non-existent
- - listInitiatives returns empty array initially
- - updateInitiative updates fields and updatedAt
- - deleteInitiative removes and cascades to children
+ phase.test.ts:
+ - create with valid initiativeId
+ - create with invalid initiativeId throws (FK constraint)
+ - findByInitiativeId returns only matching, ordered by number
+ - update/delete work correctly
- Phase tests:
- - createPhase with valid initiativeId
- - createPhase with invalid initiativeId throws (foreign key)
- - listPhasesByInitiative returns only matching phases
- - deletePhase cascades to plans and tasks
+ plan.test.ts:
+ - create with valid phaseId
+ - create with invalid phaseId throws
+ - findByPhaseId returns only matching, ordered by number
+ - update/delete work correctly
- Plan tests:
- - createPlan with valid phaseId
- - listPlansByPhase orders by number
- - deletePlan cascades to tasks
+ task.test.ts:
+ - create with valid planId
+ - create with invalid planId throws
+ - findByPlanId returns only matching, ordered by order
+ - update status changes work
+ - delete works correctly
- Task tests:
- - createTask with valid planId
- - listTasksByPlan orders by order field
- - updateTask status changes work
-
- Hierarchy tests:
- - Full hierarchy creation (initiative -> phase -> plan -> task)
- - Cascade delete removes entire subtree
- - getTask on deleted plan's task returns null
-
- Use describe/it pattern. Test real behavior, not implementation details.
+ Use describe/it pattern. Test behavior, not implementation.
- npm test -- src/db/repository.test.ts passes all tests
- All repository operations have test coverage proving CRUD works correctly
+ npm test -- src/db/repositories/drizzle passes all tests
+ All repository adapters have test coverage
+
+
+
+ Task 4: Update exports and test cascade deletes
+ src/db/index.ts, src/db/repositories/drizzle/cascade.test.ts
+
+ Update main db index to export repositories and add cascade delete test.
+
+ src/db/index.ts:
+ - Keep existing createDatabase export
+ - Add: export * from './repositories/index.js'
+ - Add: export * from './repositories/drizzle/index.js'
+
+ src/db/repositories/drizzle/cascade.test.ts:
+ - Test full hierarchy creation (initiative -> phase -> plan -> task)
+ - Delete initiative, verify all children removed
+ - Delete phase, verify plans and tasks removed
+ - Delete plan, verify tasks removed
+ - This tests SQLite foreign key cascade behavior through the adapters
+
+ These tests ensure the cascade delete configured in schema.ts works correctly when using the repository layer.
+
+ npm test -- src/db/repositories passes all tests including cascade
+ Exports updated, cascade deletes proven working through repository layer
@@ -161,17 +216,18 @@ Output: TaskHierarchyRepository port interface and DrizzleTaskHierarchyRepositor
Before declaring plan complete:
- [ ] `npm run build` succeeds without errors
-- [ ] `npm test -- src/db/repository.test.ts` passes all tests
-- [ ] Repository interface is importable from src/db/index.ts
-- [ ] Adapter is importable from src/db/index.ts
-- [ ] Foreign key cascades work (delete initiative removes all children)
+- [ ] `npm test -- src/db/repositories` passes all tests
+- [ ] All 4 repository interfaces importable from src/db/index.ts
+- [ ] All 4 adapters importable from src/db/index.ts
+- [ ] Cascade deletes work through hierarchy
-- TaskHierarchyRepository port interface defines all CRUD operations
-- DrizzleTaskHierarchyRepository adapter implements the interface
-- Tests prove all operations work correctly
-- Cascade deletes work through the hierarchy
+- 4 separate repository port interfaces (one per aggregate)
+- 4 Drizzle adapter implementations
+- Each aggregate's repository only knows about its own table
+- Tests prove CRUD operations work
+- Cascade deletes verified
- No TypeScript errors
- All tests pass