docs(02): create data layer phase plans

Phase 02: Data Layer
- 2 plans in 2 waves
- 02-01: Database foundation (Drizzle + SQLite setup, schema)
- 02-02: Repository layer (port interface, adapter, tests)
- Ready for execution
This commit is contained in:
Lukas May
2026-01-30 14:14:09 +01:00
parent 7c2664cfb7
commit 0cbb690201
2 changed files with 343 additions and 0 deletions

View File

@@ -0,0 +1,162 @@
---
phase: 02-data-layer
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- package.json
- src/db/config.ts
- src/db/schema.ts
- src/db/index.ts
- drizzle.config.ts
autonomous: true
---
<objective>
Set up SQLite database with Drizzle ORM and define the task hierarchy schema.
Purpose: Establish the persistence layer that all later phases (Agent Lifecycle, Task Dispatch, Coordination) depend on.
Output: Working database connection factory and schema for initiative → phase → plan → task hierarchy.
</objective>
<execution_context>
@~/.claude/get-shit-done/workflows/execute-plan.md
@~/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@src/events/types.ts
@src/events/bus.ts
</context>
<tasks>
<task type="auto">
<name>Task 1: Install Drizzle ORM and SQLite dependencies</name>
<files>package.json</files>
<action>
Install required dependencies:
- drizzle-orm: The ORM for type-safe database operations
- better-sqlite3: Synchronous SQLite driver for Node.js (preferred over async for local-first apps)
- @types/better-sqlite3: TypeScript types for better-sqlite3
- drizzle-kit: CLI tools for migrations (dev dependency)
Use pnpm if lockfile exists, otherwise npm.
</action>
<verify>npm ls drizzle-orm better-sqlite3 drizzle-kit shows installed versions</verify>
<done>All Drizzle and SQLite dependencies installed</done>
</task>
<task type="auto">
<name>Task 2: Create database connection factory</name>
<files>src/db/config.ts, src/db/index.ts, drizzle.config.ts</files>
<action>
Create database connection module following hexagonal patterns:
1. src/db/config.ts:
- Export function getDbPath() that returns ~/.cw/data/cw.db
- Ensure parent directories exist (use mkdir -p equivalent)
- Support optional path override via CW_DB_PATH env var for testing
2. src/db/index.ts:
- Import better-sqlite3 and drizzle
- Export function createDatabase(path?: string): DrizzleDatabase
- Default to getDbPath() if no path provided
- Enable WAL mode for better concurrent read performance
3. drizzle.config.ts:
- Configure drizzle-kit for migrations
- Point to schema file and db path
- Use "better-sqlite" driver
Pattern: Factory function, not singleton - allows multiple instances for testing.
</action>
<verify>
Create minimal test: import { createDatabase } from './src/db/index.js'; const db = createDatabase(':memory:');
Should not throw.
</verify>
<done>Database connection factory works with in-memory and file-based SQLite</done>
</task>
<task type="auto">
<name>Task 3: Define task hierarchy schema</name>
<files>src/db/schema.ts</files>
<action>
Define the four-level task hierarchy schema using Drizzle:
1. initiatives table:
- id: text (primary key, nanoid)
- name: text (not null)
- description: text (nullable)
- status: text ('active' | 'completed' | 'archived')
- createdAt: integer (Unix timestamp)
- updatedAt: integer (Unix timestamp)
2. phases table:
- id: text (primary key)
- initiativeId: text (foreign key -> initiatives.id, cascade delete)
- number: integer (for ordering, e.g., 1, 2, 3)
- name: text (not null)
- description: text (nullable)
- status: text ('pending' | 'in_progress' | 'completed')
- createdAt: integer
- updatedAt: integer
3. plans table:
- id: text (primary key)
- phaseId: text (foreign key -> phases.id, cascade delete)
- number: integer (for ordering)
- name: text (not null)
- description: text (nullable)
- status: text ('pending' | 'in_progress' | 'completed')
- wave: integer (for parallel execution grouping)
- createdAt: integer
- updatedAt: integer
4. tasks table:
- id: text (primary key)
- planId: text (foreign key -> plans.id, cascade delete)
- name: text (not null)
- description: text (nullable)
- type: text ('auto' | 'checkpoint:human-verify' | 'checkpoint:decision' | 'checkpoint:human-action')
- status: text ('pending' | 'in_progress' | 'completed' | 'blocked')
- order: integer (for sequencing within plan)
- createdAt: integer
- updatedAt: integer
Export:
- Table definitions (initiatives, phases, plans, tasks)
- Inferred types (Initiative, Phase, Plan, Task, NewInitiative, etc.)
- Relations for Drizzle relational queries
Use Unix timestamps (integers) not Date objects for SQLite compatibility.
</action>
<verify>npm run build succeeds with no TypeScript errors in schema.ts</verify>
<done>Schema defines all 4 tables with proper foreign key relationships and exported types</done>
</task>
</tasks>
<verification>
Before declaring plan complete:
- [ ] `npm run build` succeeds without errors
- [ ] Database can be created in memory: `createDatabase(':memory:')`
- [ ] Schema types are exported and usable
- [ ] drizzle.config.ts is valid (drizzle-kit validates it)
</verification>
<success_criteria>
- All dependencies installed (drizzle-orm, better-sqlite3, drizzle-kit)
- Database connection factory creates working SQLite connections
- Schema defines initiative → phase → plan → task hierarchy
- Foreign keys cascade deletes correctly
- Types are properly exported for use in repository layer
</success_criteria>
<output>
After completion, create `.planning/phases/02-data-layer/02-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,181 @@
---
phase: 02-data-layer
plan: 02
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/index.ts
autonomous: true
---
<objective>
Create repository layer following hexagonal architecture (port + adapter pattern).
Purpose: Provide clean data access abstraction that decouples business logic from Drizzle/SQLite implementation.
Output: TaskHierarchyRepository port interface and DrizzleTaskHierarchyRepository adapter with tests.
</objective>
<execution_context>
@~/.claude/get-shit-done/workflows/execute-plan.md
@~/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/02-data-layer/02-01-SUMMARY.md
@src/events/types.ts
@src/events/bus.ts
@src/db/schema.ts
</context>
<tasks>
<task type="auto">
<name>Task 1: Create TaskHierarchyRepository port interface</name>
<files>src/db/repository.ts</files>
<action>
Create the repository PORT interface following hexagonal architecture patterns established in Phase 1.1.
Define TaskHierarchyRepository interface with:
Initiative operations:
- createInitiative(data: NewInitiative): Promise&lt;Initiative&gt;
- getInitiative(id: string): Promise&lt;Initiative | null&gt;
- listInitiatives(): Promise&lt;Initiative[]&gt;
- updateInitiative(id: string, data: Partial&lt;NewInitiative&gt;): Promise&lt;Initiative&gt;
- deleteInitiative(id: string): Promise&lt;void&gt;
Phase operations:
- createPhase(data: NewPhase): Promise&lt;Phase&gt;
- getPhase(id: string): Promise&lt;Phase | null&gt;
- listPhasesByInitiative(initiativeId: string): Promise&lt;Phase[]&gt;
- updatePhase(id: string, data: Partial&lt;NewPhase&gt;): Promise&lt;Phase&gt;
- deletePhase(id: string): Promise&lt;void&gt;
Plan operations:
- createPlan(data: NewPlan): Promise&lt;Plan&gt;
- getPlan(id: string): Promise&lt;Plan | null&gt;
- listPlansByPhase(phaseId: string): Promise&lt;Plan[]&gt;
- updatePlan(id: string, data: Partial&lt;NewPlan&gt;): Promise&lt;Plan&gt;
- deletePlan(id: string): Promise&lt;void&gt;
Task operations:
- createTask(data: NewTask): Promise&lt;Task&gt;
- getTask(id: string): Promise&lt;Task | null&gt;
- listTasksByPlan(planId: string): Promise&lt;Task[]&gt;
- updateTask(id: string, data: Partial&lt;NewTask&gt;): Promise&lt;Task&gt;
- deleteTask(id: string): Promise&lt;void&gt;
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.
</action>
<verify>npm run build succeeds - interface compiles correctly</verify>
<done>TaskHierarchyRepository interface exported with all CRUD operations</done>
</task>
<task type="auto">
<name>Task 2: Create DrizzleTaskHierarchyRepository adapter</name>
<files>src/db/drizzle-repository.ts</files>
<action>
Implement the TaskHierarchyRepository interface using Drizzle ORM.
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)
- Sets createdAt/updatedAt timestamps automatically
- Orders results by appropriate fields (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)
Error handling:
- Throw descriptive errors for constraint violations
- Don't swallow SQLite errors - let them propagate
Export the class and re-export from src/db/index.ts
</action>
<verify>npm run build succeeds - no type errors in implementation</verify>
<done>DrizzleTaskHierarchyRepository implements all interface methods</done>
</task>
<task type="auto">
<name>Task 3: Write repository tests</name>
<files>src/db/repository.test.ts</files>
<action>
Write comprehensive tests for DrizzleTaskHierarchyRepository 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 cases:
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 tests:
- createPhase with valid initiativeId
- createPhase with invalid initiativeId throws (foreign key)
- listPhasesByInitiative returns only matching phases
- deletePhase cascades to plans and tasks
Plan tests:
- createPlan with valid phaseId
- listPlansByPhase orders by number
- deletePlan cascades to tasks
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.
</action>
<verify>npm test -- src/db/repository.test.ts passes all tests</verify>
<done>All repository operations have test coverage proving CRUD works correctly</done>
</task>
</tasks>
<verification>
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)
</verification>
<success_criteria>
- TaskHierarchyRepository port interface defines all CRUD operations
- DrizzleTaskHierarchyRepository adapter implements the interface
- Tests prove all operations work correctly
- Cascade deletes work through the hierarchy
- No TypeScript errors
- All tests pass
</success_criteria>
<output>
After completion, create `.planning/phases/02-data-layer/02-02-SUMMARY.md`
</output>