From 0cbb690201d0a683cff39d5407eda1b2199f4db5 Mon Sep 17 00:00:00 2001 From: Lukas May Date: Fri, 30 Jan 2026 14:14:09 +0100 Subject: [PATCH] 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 --- .planning/phases/02-data-layer/02-01-PLAN.md | 162 +++++++++++++++++ .planning/phases/02-data-layer/02-02-PLAN.md | 181 +++++++++++++++++++ 2 files changed, 343 insertions(+) create mode 100644 .planning/phases/02-data-layer/02-01-PLAN.md create mode 100644 .planning/phases/02-data-layer/02-02-PLAN.md diff --git a/.planning/phases/02-data-layer/02-01-PLAN.md b/.planning/phases/02-data-layer/02-01-PLAN.md new file mode 100644 index 0000000..0e2be97 --- /dev/null +++ b/.planning/phases/02-data-layer/02-01-PLAN.md @@ -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 +--- + + +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. + + + +@~/.claude/get-shit-done/workflows/execute-plan.md +@~/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@src/events/types.ts +@src/events/bus.ts + + + + + + Task 1: Install Drizzle ORM and SQLite dependencies + package.json + + 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. + + npm ls drizzle-orm better-sqlite3 drizzle-kit shows installed versions + All Drizzle and SQLite dependencies installed + + + + Task 2: Create database connection factory + src/db/config.ts, src/db/index.ts, drizzle.config.ts + + 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. + + + Create minimal test: import { createDatabase } from './src/db/index.js'; const db = createDatabase(':memory:'); + Should not throw. + + Database connection factory works with in-memory and file-based SQLite + + + + Task 3: Define task hierarchy schema + src/db/schema.ts + + 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. + + npm run build succeeds with no TypeScript errors in schema.ts + Schema defines all 4 tables with proper foreign key relationships and exported types + + + + + +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) + + + +- 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 + + + +After completion, create `.planning/phases/02-data-layer/02-01-SUMMARY.md` + diff --git a/.planning/phases/02-data-layer/02-02-PLAN.md b/.planning/phases/02-data-layer/02-02-PLAN.md new file mode 100644 index 0000000..7f1442b --- /dev/null +++ b/.planning/phases/02-data-layer/02-02-PLAN.md @@ -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 +--- + + +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. + + + +@~/.claude/get-shit-done/workflows/execute-plan.md +@~/.claude/get-shit-done/templates/summary.md + + + +@.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 + + + + + + Task 1: Create TaskHierarchyRepository port interface + src/db/repository.ts + + 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. + + npm run build succeeds - interface compiles correctly + TaskHierarchyRepository interface exported with all CRUD operations + + + + Task 2: Create DrizzleTaskHierarchyRepository adapter + src/db/drizzle-repository.ts + + 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 + + npm run build succeeds - no type errors in implementation + DrizzleTaskHierarchyRepository implements all interface methods + + + + Task 3: Write repository tests + src/db/repository.test.ts + + 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. + + npm test -- src/db/repository.test.ts passes all tests + All repository operations have test coverage proving CRUD works correctly + + + + + +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) + + + +- 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 + + + +After completion, create `.planning/phases/02-data-layer/02-02-SUMMARY.md` +