Skip to content

feat(epic-store): file-mode completeness — review-resume, recommender, browse cache#204

Merged
thejustinwalsh merged 7 commits into
mainfrom
middle-issue-200
Jun 3, 2026
Merged

feat(epic-store): file-mode completeness — review-resume, recommender, browse cache#204
thejustinwalsh merged 7 commits into
mainfrom
middle-issue-200

Conversation

@thejustinwalsh

@thejustinwalsh thejustinwalsh commented Jun 3, 2026

Copy link
Copy Markdown
Owner

Summary

Closes #200

Completes the three documented Phase-1/2 file-mode gaps from Epic #190 (PR #198) so a file-backed Epic reaches parity with a GitHub-backed one: PR-review resume, recommender + auto-dispatch, and browse-cache/dashboard visibility. Each gap routes an already-mode-aware seam through to a path that was still GitHub-hardcoded, reusing the existing makeRouting*Gateway / readEpicStoreConfig routing pattern.

What changed

  • packages/dispatcher/src/epic-store/file-poll-gateway.ts — a file Epic's PR resolves from the Epic file's durable meta.pr stamp (via new by-PR-number prSnapshot/prLifecycle), so review-resume + merged/closed reconcile work in file mode. File-vs-github is discriminated by epicFileExists, not a numeric heuristic.
  • packages/state-issue/InFlightItem.issue widened number → string (numeric Epic number or file-mode slug); parser reads #([^*\s]+); stays schema v1 (additive, byte-identical round-trip holds).
  • packages/dispatcher/src/epic-store/index.tsmakeRoutingStateGateway (mirrors the epic/poll routers).
  • packages/dispatcher/src/auto-dispatch.ts + main.ts — auto-dispatch reads state via the routing gateway and enqueues file Epics by slug (parseEpicRef); file-mode repos run with a sentinel state-issue 0 the file gateway ignores.
  • packages/dispatcher/src/workflows/recommender.ts + recommender-run.ts + packages/core/src/config.ts — the recommender's state I/O is routed; its run is reachable for file mode ([epic_store] parsed into config; resolveRecommenderOptions sentinel-0 gate; the prompt reframes for the file store).
  • packages/dispatcher/src/db/migrations/010_epics_ref_key.sql + epics-cache.ts — the Epic browse cache is re-keyed (repo, number) → (repo, ref) so file Epics are cached; refreshEpics routes per mode.
  • packages/dashboard/EpicCard carries ref + nullable number; the card renders via the existing <EpicRef> (#N label or file:// slug link); the workflow/decision joins key on epic_ref.

Why these changes

PRs/reviews are GitHub-native in both modes — file mode just can't resolve the PR via Closes #<n>, so it resolves via the Epic file's meta.pr (the same key findEpicPr already uses). The recommender/auto-dispatch loop and the browse cache were GitHub-hardcoded; routing them through the existing per-repo gateway resolvers (and re-keying the cache on the canonical ref, mirroring migration 009's workflows.epic_ref) is what surfaces file Epics end to end. The InFlightItem widening is additive — numeric refs render identically, so the state-issue round-trip invariant is preserved. Full rationale in planning/issues/200/decisions.md.

Acceptance criteria

  • File-mode PR-review resume — a parked file-mode Epic resumes on a CHANGES_REQUESTED review (and reconciles on merge/close), resolving its PR from meta.pr. Evidenced by the integration test packages/dispatcher/test/epic-store/file-review-resume-integration.test.ts (drives the real runPoller path) + file-poll-gateway.test.ts.
  • File-mode recommender + auto-dispatch — auto-dispatch reads the repo's state_file and dispatches file Epics by slug; the recommender run is reachable and frames its prompt for the file store. Evidenced by the integration test packages/dispatcher/test/epic-store/file-auto-dispatch-integration.test.ts (drives the real readState → autoDispatch path) + auto-dispatch.test.ts, recommender-run.test.ts, recommender-workflow.test.ts. (Live recommender ranking quality over file Epics is operator-smoke, per feat(epic-store): file-backed Epic store (opt-in hybrid) #190's file-mode precedent — see Out of scope.)
  • File-mode Epic browse cache + dashboard — file Epics are cached (ref-keyed) and surface in the dashboard as file:// slug links. Evidenced by packages/dispatcher/test/epics-cache.test.ts, packages/dashboard/test/epics-deps.test.ts, and packages/dashboard/test/epics.test.tsx.
  • No regression to github-mode — every existing path (numeric refs, github browse cache, github recommender) is unchanged. Evidenced by the full suite (1286 pass) and the github-mode assertions in epics-deps.test.ts / file-auto-dispatch-integration.test.ts.

Verification

  • bun test1286 pass, 0 fail (122 files). bun run typecheck clean. bun run lint + bun run format clean.
  • Phase 1: bun test packages/dispatcher/test/epic-store/file-review-resume-integration.test.ts — real poller resume of a file Epic.
  • Phase 2: bun test packages/dispatcher/test/epic-store/file-auto-dispatch-integration.test.ts — real state_file → auto-dispatch by slug.
  • Phase 3: bun test packages/dispatcher/test/epics-cache.test.ts packages/dashboard/test/epics-deps.test.ts — file Epics cached + surfaced by ref.
  • Migration 010 applies cleanly; schema version 10 (packages/dispatcher/test/db.test.ts).

Stumbling points

  • The branch was initially based on the wrong parent (an unrelated open PR's branch) and lacked the feat(epic-store): file-backed Epic store (opt-in hybrid) #190 foundation; reset onto origin/main before starting.
  • The recommender is deeply coupled to a numeric stateIssue (prompt, run-options, surfacer). Rather than thread a number | fileTarget union through ~8 sites, file mode uses a sentinel 0 the routing state gateway ignores; the prompt branches on the explicit epicStore mode. The standalone dispatchRecommender helper was dropping epicStore (the daemon path forwarded it) — caught in self-review and fixed with a prompt-capture test.
  • Slugs are unconstrained file stems, so the #<ref> regexes were widened from [\w-] to the real delimiters (space / **) to keep the round-trip invariant intact for a dotted slug.

Suggested CLAUDE.md updates

  • The state-issue CLAUDE.md could note that #<ref> in the In-flight section may now be a file-mode slug (not just a number), and that the round-trip invariant depends on the ref containing no space/*.

Out of scope

Summary by CodeRabbit

  • New Features

    • File-backed epics supported alongside GitHub epics across UI, dispatch, recommender, and auto-dispatch.
  • Improvements

    • Dashboard shows file-mode epics with proper links and disabled dispatch where appropriate.
    • State and recommender workflows accept string epic refs (numeric IDs or slugs).
    • Polling/resume now resolves PRs from epic file metadata when present.
  • Database Changes

    • Schema bumped to v10; epics keyed by canonical ref to support mixed-mode tracking.

@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 564db5f6-7c34-421c-af3f-5ea9cfaa214a

📥 Commits

Reviewing files that changed from the base of the PR and between b67c910 and 2701b51.

📒 Files selected for processing (6)
  • packages/dispatcher/src/poller-gateway.ts
  • packages/dispatcher/test/poller-gateway.test.ts
  • packages/state-issue/test/fuzz.test.ts
  • packages/state-issue/test/validate.test.ts
  • planning/middle-management-build-spec.md
  • schemas/state-issue.v1.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/state-issue/test/fuzz.test.ts
  • packages/dispatcher/src/poller-gateway.ts

📝 Walkthrough

Walkthrough

Introduces file-mode epic store support: adds EpicStore config and EpicStoreSettings, migrates epics to canonical string refs, widens state in-flight refs to strings, routes epic/state access per-repo mode, updates auto-dispatch/recommender/poller/gateways, and updates dashboard/UI and tests.

Changes

File-mode Epic Store Implementation

Layer / File(s) Summary
Epic store configuration & core type exports
packages/core/src/config.ts, packages/core/src/index.ts
Adds EpicStoreSettings type representing [epic_store] TOML config section with mode and optional file paths; extends MiddleConfig with optional epicStore field; re-exports type from core module.
Epic table schema migration to ref-based keying
packages/dispatcher/src/db/migrations/010_epics_ref_key.sql, packages/cli/test/db-scripts.test.ts, packages/dispatcher/test/db.test.ts
Migration 010 rebuilds epics table with primary key (repo, ref), makes number nullable for file-mode, backfills existing rows with ref = CAST(number AS TEXT), updates schema version to 10.
Epic identifier refactoring: epicNumber → epicRef
packages/dispatcher/src/auto-dispatch.ts, packages/dispatcher/src/workflow-record.ts, packages/dispatcher/test/auto-dispatch.test.ts
Transitions dispatch unit from numeric epicNumber to string epicRef; replaces parseEpicNumber with parseEpicRef supporting numeric and slug refs; adds epicRef field to workflow records; updates test assertions across auto-dispatch scenarios.
Epic cache migration to ref-based model
packages/dispatcher/src/epics-cache.ts, packages/dispatcher/test/epics-cache.test.ts
Updates EpicRow to include canonical ref: string and nullable number; reworks refreshEpics to upsert on (repo, ref); updates stale-epic closing to track open refs; modifies readEpics ordering to place file-epics (null number) after GitHub epics.
File-mode PR resolution via meta.pr stamp
packages/dispatcher/src/epic-store/file-poll-gateway.ts, packages/dispatcher/src/poller.ts, packages/dispatcher/test/epic-store/file-poll-gateway.test.ts, packages/dispatcher/test/epic-store/file-review-resume-integration.test.ts
Resolves file-mode PRs by reading meta.pr from Epic file when it exists; delegates numeric refs to GitHub PR finders; extends PollGateway with prSnapshot/prLifecycle methods for direct PR lookups.
Mode-aware routing gateways for epic and state access
packages/dispatcher/src/epic-store/index.ts
Extends makeRoutingPollGateway to route prSnapshot/prLifecycle calls per-repo; introduces makeRoutingStateGateway that routes state reads/writes based on mode, using sentinel issue number 0 for file-mode repos.
In-flight state schema: numeric issue → string ref
packages/state-issue/src/schema.v1.ts, packages/state-issue/src/parser.ts, packages/state-issue/test/parser.test.ts, packages/state-issue/test/fuzz.test.ts, schemas/state-issue.v1.md
Changes InFlightItem.issue from number to string supporting both GitHub numeric refs and file-mode slugs; updates regex and parser to capture non-numeric epic refs; extends parser/renderer round-trip tests.
Recommender workflow file-mode support
packages/dispatcher/src/workflows/recommender.ts, packages/dispatcher/test/recommender-workflow.test.ts
Extends RecommenderInput with optional epicStore; changes InFlightSummary.issue to string; updates prompt assembly to reframe for file-backed store (epics dir/state file); routes state gateway per mode; populates in-flight issue from epicRef instead of epicNumber.
Main dispatcher routing and auto-dispatch integration
packages/dispatcher/src/main.ts
Wires routing gateways throughout dispatcher loop; introduces sentinel stateIssueNumber = 0 for file-mode; updates auto-dispatch to use routed state gateway and epicRef enqueue; disables state-issue commenting in file mode; switches epic refresh calls to routed gateway.
Recommender dispatch options: file-mode gating
packages/dispatcher/src/recommender-run.ts, packages/dispatcher/test/recommender-run.test.ts
Updates DispatchRecommenderOptions with optional epicStore; derives file-mode flag and sets stateIssue = 0 for file repos; forwards epicStore through to recommender workflow input.
Dashboard epic card: ref-based identity and file-mode rendering
packages/dashboard/src/wire.ts, packages/dashboard/src/app/components/Epics.tsx, packages/dashboard/src/db-deps.ts, packages/dashboard/test/epics-deps.test.ts, packages/dashboard/test/epics.test.tsx
Adds canonical ref to EpicCard type and makes number nullable; refactors Epics component to render via EpicRef component; gates dispatch button for file-mode epics; updates listEpics to populate ref and resolve workflows by epic_ref; adds file-mode epic test cases.
Integration tests: file-mode auto-dispatch, PR-review resume, poller routing
packages/dispatcher/test/epic-store/file-auto-dispatch-integration.test.ts, packages/dispatcher/test/epic-store/file-review-resume-integration.test.ts
Adds comprehensive integration test suites validating file-mode auto-dispatch through routing gateway, PR-review resume via meta.pr stamping, and correct gateway routing for both file and GitHub modes.
Test updates: schema, fixtures, stubs, assertions
packages/dashboard/test/app.test.tsx, packages/dashboard/test/epics.test.tsx, packages/dispatcher/test/epic-store/file-watcher-integration.test.ts, packages/dispatcher/test/epic-store/selector.test.ts, packages/dispatcher/test/poller.test.ts, packages/dispatcher/test/state-issue.test.ts
Updates database seeding to include ref column, extends test helpers with seedFileEpic, adds file-mode epic test cases, updates test fixture issue values to strings, and extends PollGateway test stubs with new PR-related methods.
Issue #200 planning and decisions documentation
planning/issues/200/plan.md, planning/issues/200/decisions.md
Documents the three Phase-1/2 file-mode gaps being addressed and records decisions across schema/routing/prompt/cache/UI changes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 69.64% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title directly and specifically identifies the main feature: adding file-mode completeness to the epic-store with three key components (review-resume, recommender, browse cache).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Resolve a file-mode Epic's PR from the Epic file's durable meta.pr stamp so
review-changes/CHANGES_REQUESTED (and the merged/closed reconcile) resume work
in file mode — previously the file poll gateway returned null for any slug.

- Widen PollGateway with by-PR-number prSnapshot/prLifecycle; ghPollGateway's
  Closes-#N finder now reuses the extracted fetchPrSnapshot builder.
- file-poll-gateway resolves a slug via meta.pr then fetches by number; numeric
  refs still delegate to gh's finders. A stale/absent meta.pr degrades to null.
- Route the two methods through makeRoutingPollGateway.
- Integration test drives the real runPoller path end to end for a file Epic.
Route the auto-dispatch readState and the recommender state I/O through a new
makeRoutingStateGateway (file <-> github per repo), so a file-mode repo's ranked
plan in its state_file drives dispatch and the recommender reads/writes it.

- state-issue: InFlightItem.issue widened number -> string (numeric Epic number
  or file-mode slug); parser reads #([\w-]+); stays schema v1 (additive, round
  -trip holds; fuzz + samples seeded with slugs).
- auto-dispatch: enqueue by epicRef (string); parseEpicRef extracts #<ref>; a
  file Epic dispatches by slug. main.ts runs auto-dispatch for file-mode repos
  (sentinel state issue 0; the file gateway ignores it).
- recommender: in-flight rows source epicRef (added to ActiveImplementationWorkflow
  via the migration-009 epic_ref column); state I/O routed; resolveRecommenderOptions
  accepts file mode; the prompt reframes for the file store. surface() skips the
  gh comment for sentinel 0. Live ranking quality is operator-smoke (per #190).
- core: parse [epic_store] into MiddleConfig.epicStore.
- Integration test drives the real file-mode auto-dispatch readState path.
Re-key the epics browse cache from (repo, number) to (repo, ref) [migration 010]
so a file-mode Epic — a slug with no GitHub number — is cached and surfaces in
the dashboard. refreshEpics routes through the routing Epic gateway (per-mode
listing); readEpics orders github Epics newest-first, file Epics after.

- epics-cache: ref-keyed upsert/close, EpicRow gains ref + nullable number, the
  file-Epic skip is removed.
- dashboard: EpicCard carries ref + nullable number; the card renders via the
  existing <EpicRef> (#N label or file:// slug link); workflowForEpic keys on
  epic_ref (both modes); the ready-row join matches by ref. In-dashboard force
  -dispatch is disabled for file Epics (numeric route only) with a title pointing
  at 'mm dispatch <slug>' — browsable, not falsely dispatchable.
- Tests: file-mode cache (cache/close/order), dashboard listEpics by ref, the
  Epics component file:// render. Schema-version assertions bumped 9 -> 10.
Adversarial self-review pass before marking ready — resolve the ref-shape class:

- recommender-run: forward opts.epicStore into RecommenderInput (was dropped on
  the standalone-helper path; the daemon path already forwarded it). Test captures
  the on-disk prompt of a file-mode run.
- file-poll-gateway: discriminate file vs github by epicFileExists (the check
  listIssueComments already uses), not a ^\d+$ heuristic — a numeric-named file
  Epic (42.md) resolves via meta.pr, not github issue #42.
- ref regexes: parseEpicRef #(\S+), IN_FLIGHT_RE #([^*\s]+) — match the real ref
  grammar (space / ** delimited), so a non-kebab file stem (v1.2-rollout) doesn't
  truncate and break the byte-identical round-trip invariant.

Each fix carries a failing-first test.

@thejustinwalsh thejustinwalsh left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Decision-log highlights distilled inline (full rationale in planning/issues/200/decisions.md). These mark the load-bearing design choices a reviewer should sanity-check.

// github number → gh's `Closes #<n>` finder. Keying on the file (not a
// `^\d+$` shape) means a numeric-named file Epic (`123.md`) still resolves
// via meta.pr rather than being mistaken for github issue #123.
if (!epicFileExists(epicsDir, epicRef)) return gh.findPrForEpic(repo, epicRef);

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why epicFileExists, not a numeric check. A file Epic's PR is resolved from the file's meta.pr stamp — the same key file-epic-gateway.findEpicPr already uses. The discriminator is the Epic file on disk (the same check listIssueComments uses), not a ^\d+$ shape, so a numeric-named file Epic (42.md) still resolves via meta.pr instead of being mistaken for github issue #42. prSnapshot/prLifecycle (new by-PR-number PollGateway methods) do the fetch; a stale meta.pr degrades to null, not a thrown pass.

if (!m) fail(`malformed "In-flight" item: "${line}"`);
return {
issue: Number(m[1]),
issue: m[1]!,

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

InFlightItem.issue is now a string (numeric ref or slug), still schema v1. Numeric refs are a subset of string refs and render identically (**#200**), so the byte-identical round-trip invariant holds — no v1→v2 fork. The regex is #([^*\s]+) (not [\w-]) because a file stem isn't constrained to kebab; matching up to the ** delimiter keeps the round-trip exact for any slug the file store produced (a dotted v1.2-rollout would otherwise truncate). Scoped to In-flight per the issue; the other sections stay numeric.

* auto-dispatch `readState` always read the GitHub state issue, so a file-mode
* repo's ranked plan (in its `state_file`) is never read or rewritten (#200).
*/
export function makeRoutingStateGateway(deps: {

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Routing state gateway + sentinel-0. The StateGateway interface is (repo, issueNumber)-keyed. Rather than thread a number | fileTarget union through the recommender's ~8 call sites, a file-mode repo passes a sentinel 0 that the file gateway ignores (it reads state_file); github repos pass the real number. This router resolves file-vs-github per repo, mirroring makeRoutingEpicGateway/makeRoutingPollGateway. Every path that could hit gh on issue #0 is guarded (the recommender surface, the auto-dispatch parse-surfacer).

PRIMARY KEY (repo, ref)
);

INSERT INTO epics_new

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-key (repo, number)(repo, ref). A file Epic has no GitHub number, so the numeric PK couldn't represent it — it was explicitly skipped, invisible in the dashboard. ref is canonical (numeric string | slug), backfilled CAST(number AS TEXT), exactly mirroring migration 009's workflows.epic_ref. SQLite can't change a PK in place; the runner disables FK enforcement around the rebuild loop for precisely this. readEpics orders number DESC, ref ASC so github Epics come first (newest), file Epics (NULL number → last under DESC) after.

// routed state gateway reads/writes it and ignores the sentinel `0`. Github mode
// still requires a configured issue number.
const fileMode = config.epicStore?.mode === "file";
const stateIssue = fileMode ? 0 : config.stateIssue?.number;

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommender run reachable for file mode. [epic_store] is parsed into MiddleConfig.epicStore; a file-mode repo uses sentinel stateIssue = 0 instead of erroring on the missing [state_issue] number, and the prompt reframes for the file store (rank Epic files, rewrite state_file, refs are slugs). The wiring is unit/integration-tested; the live ranking quality over file Epics is operator-smoke (per #190's file-mode precedent), since it's a live agent run that can't be CI-gated.

@thejustinwalsh thejustinwalsh marked this pull request as ready for review June 3, 2026 15:49
@thejustinwalsh thejustinwalsh added the ready-for-review All phases done and verified — PR ready for final human review and merge label Jun 3, 2026
@thejustinwalsh

Copy link
Copy Markdown
Owner Author

Reviewer's brief — #200 file-mode completeness (PR #204)

What this is. Closes the three Phase-1/2 file-mode gaps from Epic #190 so a file-backed Epic reaches parity with a GitHub-backed one: PR-review resume, recommender + auto-dispatch, and browse-cache/dashboard visibility. One branch, one PR; MERGEABLE.

How to run it

bun install
bun run typecheck          # clean
bun run lint               # clean
bun test                   # 1286 pass, 0 fail
# The three real-path integration tests, one per gap:
bun test packages/dispatcher/test/epic-store/file-review-resume-integration.test.ts
bun test packages/dispatcher/test/epic-store/file-auto-dispatch-integration.test.ts
bun test packages/dispatcher/test/epics-cache.test.ts packages/dashboard/test/epics-deps.test.ts

What to verify (and what "correct" looks like)

  1. PR-review resume (file-poll-gateway.ts). A file Epic's PR is resolved from the Epic file's meta.pr stamp, then fetched by number via the new PollGateway.prSnapshot/prLifecycle. Correct = a parked file-mode Epic resumes on CHANGES_REQUESTED and reconciles on merge/close, identical to github mode. The discriminator is epicFileExists (an Epic file on disk), not a ^\d+$ heuristic — confirm a numeric-named file Epic (42.md) resolves via meta.pr, not github issue Recommender workflow #42.
  2. Recommender + auto-dispatch. Auto-dispatch reads the repo's state_file through makeRoutingStateGateway and enqueues file Epics by slug (parseEpicRef). The recommender run is reachable for file mode (sentinel stateIssue = 0; [epic_store] parsed into config; prompt reframed). Correct = file-auto-dispatch-integration.test.ts dispatches the slug; the recommender prompt says "file-backed" and names epics_dir/state_file, never "state issue #0".
  3. Browse cache + dashboard. Migration 010 re-keys epics (repo, number) → (repo, ref); refreshEpics routes per mode; the dashboard renders file Epics as file:// slug links via <EpicRef>. Correct = file Epics appear in listEpics with number: null, the runner resolves by epic_ref, and the in-dashboard dispatch button is disabled for them (CLI-only) with a pointer to mm dispatch <slug>.

How to review

  • State-issue round-trip is the load-bearing invariant. InFlightItem.issue went number → string (slug support) but stays schema v1 — numeric refs render identically. The fuzz + sample round-trip tests now include slugs; the #([^*\s]+) regex matches up to the ** delimiter so a non-kebab slug (v1.2-rollout) round-trips byte-identically. If you change either the renderer or the regex, re-run packages/state-issue/.
  • Migration 010 rebuilds the epics table (SQLite can't change a PK in place). Verify the column copy is faithful and the (repo, ref) PK + nullable number are correct.
  • The decision log is planning/issues/200/decisions.md; the five load-bearing choices are also inline review comments on the diff.

Fragile / extra eyes

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
schemas/state-issue.v1.md (1)

79-79: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Scope validation rule #4 so it doesn’t imply in-flight #<ref> must be numeric

  • schemas/state-issue.v1.md says “Validation rule #4: All #N references match /#\d+/”, but packages/state-issue/src/validate.ts applies the numeric check only to readyToDispatch epics and blocked issue blockers—state.inFlight refs are not constrained there.
  • To keep the schema doc authoritative, update rule #4 wording/scope to exclude in-flight #<ref> (the in-flight ref may be a file-mode slug).
  • Also align the in-flight slug description: the doc says “kebab-case”, but the parser allows dotted stems (e.g. v1.2-rollout).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schemas/state-issue.v1.md` at line 79, Update the schema doc rule `#4` to match
actual validator behavior by scoping the numeric-only constraint to
readyToDispatch epics and blocked issue refs (as enforced in
packages/state-issue/src/validate.ts by the readyToDispatch and blocked checks)
and explicitly exclude state.inFlight references from the /#\d+/ requirement;
also change the in-flight slug description from “kebab-case” to allow dotted
stems (e.g., note that state.inFlight may be a file-mode slug like v1.2-rollout)
so the markdown aligns with the parser/validator behavior.
🧹 Nitpick comments (1)
schemas/state-issue.v1.md (1)

47-51: 💤 Low value

Doc says "kebab-case" but the parser accepts broader slugs.

The slug is documented as kebab-case here, yet parser.ts intentionally captures any non-space/non-* ref (e.g. dotted v1.2-rollout) to keep the round-trip exact. As this doc is authoritative for parser conformance, consider widening the wording (e.g. "a file-stem slug, e.g. rollout-epic-store") so the spec matches the implemented capture surface.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schemas/state-issue.v1.md` around lines 47 - 51, The spec incorrectly
restricts `<ref>` to "kebab-case" while parser.ts accepts any non-space/non-`*`
ref (e.g. dotted or mixed tokens like `v1.2-rollout`); update the documentation
in state-issue.v1.md to describe `<ref>` as a file-stem slug (or "file-mode Epic
slug") that may include dots, hyphens and other non-space/non-`*` characters,
give an example such as `v1.2-rollout` alongside `rollout-epic-store`, and note
that this wording matches the parser.ts capture behavior so the spec and
implementation remain consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/dispatcher/src/poller-gateway.ts`:
- Around line 107-112: The call that assigns reviewsOut using gh([...
`repos/${repo}/pulls/${prNumber}/reviews`]) is not protected by try/catch, so
transient failures can throw and break the poller pass; update the code to
mirror the viewOut handling by wrapping the reviews fetch in a try/catch block
around the gh call (referencing reviewsOut, gh, repo, prNumber) and return null
(or otherwise handle the error consistently) on failure to preserve per-workflow
failure isolation as implemented in poller.ts.

---

Outside diff comments:
In `@schemas/state-issue.v1.md`:
- Line 79: Update the schema doc rule `#4` to match actual validator behavior by
scoping the numeric-only constraint to readyToDispatch epics and blocked issue
refs (as enforced in packages/state-issue/src/validate.ts by the readyToDispatch
and blocked checks) and explicitly exclude state.inFlight references from the
/#\d+/ requirement; also change the in-flight slug description from “kebab-case”
to allow dotted stems (e.g., note that state.inFlight may be a file-mode slug
like v1.2-rollout) so the markdown aligns with the parser/validator behavior.

---

Nitpick comments:
In `@schemas/state-issue.v1.md`:
- Around line 47-51: The spec incorrectly restricts `<ref>` to "kebab-case"
while parser.ts accepts any non-space/non-`*` ref (e.g. dotted or mixed tokens
like `v1.2-rollout`); update the documentation in state-issue.v1.md to describe
`<ref>` as a file-stem slug (or "file-mode Epic slug") that may include dots,
hyphens and other non-space/non-`*` characters, give an example such as
`v1.2-rollout` alongside `rollout-epic-store`, and note that this wording
matches the parser.ts capture behavior so the spec and implementation remain
consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 03d50858-88bc-431f-9bda-6fce98b92a30

📥 Commits

Reviewing files that changed from the base of the PR and between 8fbd926 and b67c910.

📒 Files selected for processing (41)
  • packages/cli/test/db-scripts.test.ts
  • packages/core/src/config.ts
  • packages/core/src/index.ts
  • packages/dashboard/src/app/components/Epics.tsx
  • packages/dashboard/src/db-deps.ts
  • packages/dashboard/src/wire.ts
  • packages/dashboard/test/app.test.tsx
  • packages/dashboard/test/epics-deps.test.ts
  • packages/dashboard/test/epics.test.tsx
  • packages/dispatcher/src/auto-dispatch.ts
  • packages/dispatcher/src/db/migrations/010_epics_ref_key.sql
  • packages/dispatcher/src/epic-store/file-poll-gateway.ts
  • packages/dispatcher/src/epic-store/index.ts
  • packages/dispatcher/src/epics-cache.ts
  • packages/dispatcher/src/main.ts
  • packages/dispatcher/src/poller-gateway.ts
  • packages/dispatcher/src/poller.ts
  • packages/dispatcher/src/recommender-run.ts
  • packages/dispatcher/src/workflow-record.ts
  • packages/dispatcher/src/workflows/recommender.ts
  • packages/dispatcher/test/auto-dispatch.test.ts
  • packages/dispatcher/test/db.test.ts
  • packages/dispatcher/test/epic-store/file-auto-dispatch-integration.test.ts
  • packages/dispatcher/test/epic-store/file-poll-gateway.test.ts
  • packages/dispatcher/test/epic-store/file-review-resume-integration.test.ts
  • packages/dispatcher/test/epic-store/file-watcher-integration.test.ts
  • packages/dispatcher/test/epic-store/selector.test.ts
  • packages/dispatcher/test/epics-cache.test.ts
  • packages/dispatcher/test/poller.test.ts
  • packages/dispatcher/test/recommender-run.test.ts
  • packages/dispatcher/test/recommender-workflow.test.ts
  • packages/dispatcher/test/state-issue.test.ts
  • packages/state-issue/src/parser.ts
  • packages/state-issue/src/schema.v1.ts
  • packages/state-issue/test/fuzz.test.ts
  • packages/state-issue/test/parser.test.ts
  • packages/state-issue/test/sample-states.ts
  • packages/state-issue/test/validate.test.ts
  • planning/issues/200/decisions.md
  • planning/issues/200/plan.md
  • schemas/state-issue.v1.md

Comment thread packages/dispatcher/src/poller-gateway.ts Outdated
A transient failure of the per-PR reviews API call (rate limit, network)
threw out of fetchPrSnapshot, unlike the sibling `pr view` fetch which
already degrades to null. Wrap it to match — a stale/unreachable PR snapshot
degrades to "no PR" instead of propagating. Pass `env` to the `gh` helper
so argv[0] resolves against the current PATH (behavior-identical in prod),
which lets a fake gh on PATH drive both failure branches under test.
…slug wording

The schema doc claimed in-flight `<ref>` is kebab-case and rule 4 numeric-checks
all #N refs, but validate.ts only constrains Ready epics and Blocked blockers and
parser.ts captures any non-space/non-\* stem for in-flight refs (file-mode Epic
slugs, incl. dotted `v1.2-rollout`). Correct the schema doc and the authoritative
build spec to match. Pin the behavior: a validate test for a slug in-flight ref,
and a dotted-slug arm in the round-trip fuzz generator.
@thejustinwalsh

Copy link
Copy Markdown
Owner Author

Addressed the two schemas/state-issue.v1.md findings (the outside-diff rule #4 note and the lines 47–51 kebab-case nitpick) in 2701b51 — both were the same class: the doc overstated the ref constraint relative to the parser/validator.

  • Rule Add round-trip fuzz test for state-issue parser/renderer #4 is now scoped to Ready row epics and Blocked issue blockers (the only refs validate.ts numeric-checks via EPIC_REF_RE/ISSUE_REF_RE); In-flight <ref> is called out as exempt — it may be a file-mode Epic slug.
  • In-flight slug wording no longer says "kebab-case"; it now describes a file-stem token (any non-space/non-* run, e.g. rollout-epic-store or v1.2-rollout), matching parser.ts's #([^*\s]+) capture.
  • Also propagated the same correction to planning/middle-management-build-spec.md (the authoritative design doc carried the identical pre-fix rule Add round-trip fuzz test for state-issue parser/renderer #4).

Pinned the behavior: a validate test asserting a slug In-flight ref validates OK, and a dotted-slug arm added to the round-trip fuzz generator so byte-identity is exercised against the broadened capture.

@thejustinwalsh thejustinwalsh merged commit e63de69 into main Jun 3, 2026
1 check passed
@thejustinwalsh thejustinwalsh deleted the middle-issue-200 branch June 3, 2026 17:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-review All phases done and verified — PR ready for final human review and merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(epic-store): file-mode completeness beyond Phase 1/2 (review-resume, recommender, browse cache)

1 participant