feat(epic-store): foundation — spec + plan + parser/renderer/migrations#188
Conversation
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (41)
📝 WalkthroughWalkthroughThis PR establishes the foundation for a file-backed Epic store mode, enabling per-repo opt-in storage of Epic data and Q&A conversations in Markdown files instead of GitHub issues. It introduces comprehensive design documentation, renames gateway interfaces for clarity ( ChangesFile-backed Epic store foundation
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Comment |
…hybrid)
Per-repo opt-in `epic_store = "file"` mode: one Markdown file per Epic
under `planning/epics/`, recommender state in `.middle/state.md`. PRs and
CI stay GitHub-native in both modes ("hybrid"). Workflow bodies, gates,
watchdog, hook server, poller — all unchanged: the three existing single-
seam interfaces (`GitHubGateway`, `StateIssueGateway`, `GitHubPollGateway`)
get renamed (`EpicGateway`, `StateGateway`, `PollGateway`) and gain
parallel file implementations behind the same contracts. Bootstrap picks
the implementation per-repo from `repo_config.epic_store`.
The agent's `blocked.json` flow plugs in at one DI seam (`postQuestion`).
Round-trip-pure parser/renderer absorbs #178's class (structurally
distinct `question`/`answer` markers) and #180's class (renderer is the
sole writer for strict-marker content). Phase split: file-Epic dispatch
ships first (~2 wk) with a `mm resume` escape hatch; file-watcher Q&A
resume (~1 wk) rides the existing 120s poller cron.
See: docs/superpowers/specs/2026-05-29-file-backed-epic-store-design.md
…se 2 Bite-sized TDD steps, exact files + commands + expected output for each. Phase 1 (~2 wk): rename gateways → schema migrations → parser/renderer → three file gateways → bootstrap selector → postQuestion wiring → mm init/dispatch/doctor/resume → skill refactor → parity test → smoke. Phase 2 (~1 wk): mtime poll helper → file-signal poll on poller cron → parity test for file-edit Q&A resume → smoke. Spec: docs/superpowers/specs/2026-05-29-file-backed-epic-store-design.md
…/Poll) Preparation for the file-backed Epic store: the three existing single- seam DI'd interfaces are renamed to reflect what they abstract (an Epic store, not GitHub specifically). Implementations (ghGitHub, ghStateIssueGateway, ghPollGateway) keep their gh* names — they're the GitHub implementation of the renamed interfaces. Behavior unchanged. - GitHubGateway → EpicGateway - StateIssueGateway → StateGateway - GitHubPollGateway → PollGateway Mechanical codemod across 23 files. 1068 tests pass, typecheck clean.
Additive migration: adds epic_store ('github' default), epics_dir, and
state_file columns. All existing rows default to github mode — zero
behavior change for existing repos. File-mode opt-in is a single config
edit (epic_store.mode = "file" in .middle/<slug>.toml).
The bootstrap selector reads this column to pick the gateway trio at
buildImplementationDeps time. epics_dir / state_file are nullable —
only populated when mode = 'file'.
… (008) Additive migration with backfill: epic_ref TEXT (nullable), populated from CAST(epic_number AS TEXT) for every existing row whose epic_number is non-null. Recommender / documentation workflows have null epic_number and stay null epic_ref. github mode writes both columns; file mode writes only epic_ref (slug). Application-level enforcement in createWorkflowRecord ensures every implementation workflow has it set. epic_number was already nullable — no table rebuild needed.
HTML-comment markers are the structural contract for the round-trip parser/renderer that follows. Type model describes the fully-parsed Epic shape: meta, acceptance, sub-issues, conversation. Every field a renderer needs is on the model so the parser preserves it — required for the byte-identical round-trip invariant. Marker convention mirrors state-issue v1 (<!-- AGENT-QUEUE-STATE v1 -->): the marker IS the structural contract; the renderer is the sole writer of strict attribute lines (closes #180's class of writer/parser drift).
…rsation)
Strict on markers + attributes (the structural contract), lenient on
prose. Throws with a named-marker error when a structural element is
malformed — operators diagnose without log-tailing.
Conversation parser distinguishes dispatch-event vs question, threads
an answer block under its question, and treats an answer's html-comment-
only body as 'placeholder empty' (no resume) vs human-written text
('replied' — the file-watcher trigger).
12 tests covering: empty epic, missing markers, every meta key, checked
+ unchecked sub-issues, provenance suffix, conversation with empty
answer, conversation with non-empty answer.
…erty test renderEpicFile(parseEpicFile(body)) === body for every fixture (empty- epic, codex-adapter, mid-question, all-closed) — byte-identical, first try. Round-trip purity replaces a lock: dispatcher and human can both edit the file (dispatcher patches conversation entries via the renderer; human edits between markers or inside their answer block) without corrupting each other's writes. The renderer is the sole writer of strict-marker attribute lines. Agents/humans write between markers but never inside the attributes — that single-writer rule closes #180's class for the file path.
- migration filenames: 008→009 (workflows.epic_ref) to clear collision with #181's 007_retention.sql - db.test.ts + db-scripts.test.ts: bump schema-version assertions to 9 (final post-008+009 state) - re-apply rename codemod to references introduced by #185/#186 after the original rename codemod landed
8a25b90 to
2db1447
Compare
Summary
Brainstormed → spec'd → planned → implemented the foundation of the file-backed Epic store (per-repo opt-in, hybrid: file Epics + GitHub PRs).
This PR ships: the design spec, the implementation plan, the gateway-interface rename, the two schema migrations, the Epic file format with parser/renderer, and a byte-identical round-trip property test.
🚧 Draft — foundation only. The actual file gateways (
fileEpicGateway/fileStateGateway/filePollGateway) and bootstrap selector are deferred to the next session pending a design call (see "Open design fork" below).What's in this PR
8a25b90chore081fe7dfeat6b30389featc657e07feat34f9a85featworkflows.epic_refwith backfill8f5dc2ffeatrepo_config.epic_store+epics_dir+state_file114f632refactorGitHubGateway/StateIssueGateway/GitHubPollGateway→EpicGateway/StateGateway/PollGatewaya1cab30docs996011fdocsAll commits are back-compat — every existing repo defaults to
epic_store = 'github'and behaves identically. Zero workflow body changes; zero behavior change in github mode.Verification
bun run typecheck— cleanbun run lint— cleanbun run format— cleanbun test— 1090 pass, 0 fail (+16 new tests: 12 parser + 4 round-trip)Open design fork (you call it at the start of the next session)
The plan/spec said "rename
epicNumber: number→epicRef: stringeverywhere." Reading the actualEpicGatewaysurface (packages/dispatcher/src/github.ts:98), that's ~30 callsites across workflows + recommender + poller + main.ts + tests, plus the workflow input shape (ImplementationInput.epicNumber: number), plus the dispatcher's HTTP handler.Two ways forward:
A. Full
epicRef: stringrefactor (the spec's intent)B. Synthetic numeric ID per Epic file (pragmatic shortcut)
slug ↔ numberinternally (counter file.middle/epics/.idsor stable hash).number. Workflow code unchanged.I stopped here rather than ramming through one path because it's a real architectural call, not mechanical work.
Follow-ups already filed
feat(dashboard): file-mode Epic display (epic_ref + file:// links)— Task 4 deferred per YAGNI; dashboard works unchanged in github mode.What's NOT yet in this PR (next-session work)
postQuestionwiring (Tasks 11-12)mm init --epic-store=file,mm dispatch --epic <slug>,mm doctormode-aware,mm resume(Tasks 13-16)Spec + plan
docs/superpowers/specs/2026-05-29-file-backed-epic-store-design.mddocs/superpowers/plans/2026-05-29-file-backed-epic-store.mdRead the spec first; the plan is the operational decomposition.
Summary by CodeRabbit
New Features
Documentation