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:
162
.planning/phases/02-data-layer/02-01-PLAN.md
Normal file
162
.planning/phases/02-data-layer/02-01-PLAN.md
Normal 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>
|
||||
181
.planning/phases/02-data-layer/02-02-PLAN.md
Normal file
181
.planning/phases/02-data-layer/02-02-PLAN.md
Normal 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<Initiative>
|
||||
- getInitiative(id: string): Promise<Initiative | null>
|
||||
- listInitiatives(): Promise<Initiative[]>
|
||||
- updateInitiative(id: string, data: Partial<NewInitiative>): Promise<Initiative>
|
||||
- deleteInitiative(id: string): Promise<void>
|
||||
|
||||
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>
|
||||
|
||||
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>
|
||||
|
||||
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>
|
||||
|
||||
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>
|
||||
Reference in New Issue
Block a user