summaryrefslogtreecommitdiff
path: root/internal/storage/db.go
diff options
context:
space:
mode:
authorClaude Sonnet 5 <noreply@anthropic.com>2026-07-03 23:46:26 +0000
committerClaude Sonnet 5 <noreply@anthropic.com>2026-07-03 23:46:26 +0000
commit392c7c1ada310b2f928dca89b75ba628478f7694 (patch)
treec2b32420ce5df0da79d6d4b014fe2c784f2bd4be /internal/storage/db.go
parent997cd8b56bc086a02b9c7c006dd62b07b9fcd2f3 (diff)
feat(story): add Epic/Story data model + REST CRUD (Phase 7a)
Pure data model + CRUD -- no orchestration behavior yet (that's Phase 7b). Revives ADR-007's stories concept (adapted; ADR-007 itself was deleted as "more machinery than the usage pattern needed") while staying lean: reuses the existing events/projects/task-tree machinery rather than building a parallel hierarchy. - internal/story: Epic/Story types. Epic is a loosely-scoped initiative (OPEN/CLOSED, no execution semantics). Story is a shippable slice of work realized by a task tree rooted at root_task_id; epic_id/project_id/ root_task_id are loose references (no FK enforcement, matching the codebase's existing tolerance for unmatched reference strings like tasks.project). - storage: new epics/stories tables (additive migrations) + CRUD methods. Story status is intentionally unvalidated pass-through in this phase -- no task.ValidTransition-style state machine, since there's no orchestrator yet to make transitions meaningful. - event: 9 new Kind constants for the ceremony this layer will eventually drive (epic_proposed, discovery_proposed, framing_decided, groomed, prioritized, eval_verdict, arbitration_decided, retro_captured, human_accepted) -- defined but nothing emits them yet. - api: GET/POST /api/epics, GET/PUT /api/epics/{id}, GET /api/epics/{id}/stories, GET/POST /api/stories, GET/PUT /api/stories/{id}, and GET /api/stories/{id}/task-tree -- BFS walk from root_task_id following both parent_task_id children and depends_on-edge dependents (visited-set guarded), returning a flat node list for a later UI phase to render as a graph. Unauthenticated, matching the existing projects/tasks endpoints' posture. go build/vet/test -race -count=1 all pass, full suite. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V1moSNCJRcP6kykA4tyUSs
Diffstat (limited to 'internal/storage/db.go')
-rw-r--r--internal/storage/db.go30
1 files changed, 30 insertions, 0 deletions
diff --git a/internal/storage/db.go b/internal/storage/db.go
index d571c2e..8b563a6 100644
--- a/internal/storage/db.go
+++ b/internal/storage/db.go
@@ -154,6 +154,36 @@ func (s *DB) migrate() error {
UNIQUE(role, version)
)`,
`CREATE INDEX IF NOT EXISTS idx_role_configs_role_status ON role_configs(role, status)`,
+ `CREATE TABLE IF NOT EXISTS epics (
+ id TEXT PRIMARY KEY,
+ name TEXT NOT NULL,
+ description TEXT,
+ status TEXT NOT NULL DEFAULT 'OPEN',
+ discovery_source TEXT,
+ created_at DATETIME NOT NULL,
+ updated_at DATETIME NOT NULL
+ )`,
+ `CREATE TABLE IF NOT EXISTS stories (
+ id TEXT PRIMARY KEY,
+ epic_id TEXT,
+ project_id TEXT,
+ name TEXT NOT NULL,
+ branch_name TEXT,
+ status TEXT NOT NULL,
+ discovery_source TEXT,
+ spec TEXT,
+ acceptance_criteria_json TEXT NOT NULL DEFAULT '[]',
+ validation_json TEXT,
+ deploy_config TEXT,
+ priority TEXT,
+ root_task_id TEXT,
+ created_at DATETIME NOT NULL,
+ updated_at DATETIME NOT NULL
+ )`,
+ `CREATE INDEX IF NOT EXISTS idx_epics_status ON epics(status)`,
+ `CREATE INDEX IF NOT EXISTS idx_stories_status ON stories(status)`,
+ `CREATE INDEX IF NOT EXISTS idx_stories_epic_id ON stories(epic_id)`,
+ `CREATE INDEX IF NOT EXISTS idx_stories_root_task_id ON stories(root_task_id)`,
}
for _, m := range migrations {
if _, err := s.db.Exec(m); err != nil {