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