Skip to content

feat(stories): engine, adapter + HITL checkpoints for folder+id dispatch (Phase 2)#77

Open
pbean wants to merge 1 commit into
feat/spec-to-loop-stories-phase1from
feat/spec-to-loop-stories-phase2
Open

feat(stories): engine, adapter + HITL checkpoints for folder+id dispatch (Phase 2)#77
pbean wants to merge 1 commit into
feat/spec-to-loop-stories-phase1from
feat/spec-to-loop-stories-phase2

Conversation

@pbean

@pbean pbean commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Phase 2 of the spec-to-loop contract (BMAD-METHOD #2549, merged), now also carrying the Phase 3 human-in-the-loop checkpoints (folded into this branch). Stacked on #65 (Phase 1) — base is feat/spec-to-loop-stories-phase1, not main, because this builds directly on Phase 1's stories.py / verify_dev_stories / devcontract plan-halt seam. Review/merge #65 first.

Re-pinned to the merged #2549 text before coding: field names, the seven blocking-condition strings, and the Halt after planning. phrasing all match verbatim — no drift.

Engine + adapter

  • StoriesEngine(Engine) — a thin override layer, same pattern SweepEngine proves:
    • _pick_next: linear schedule from stories.py, re-validated fresh every pick (id-stability rule F), a within-run skip set mirroring the sprint engine's base_skip, and a blocked/sentinel/ambiguous story → pause for resolve (PAUSE_ESCALATION, cannot leapfrog).
    • _dev_prompt: folder+id dispatch — /bmad-dev-auto Spec folder: <rel>. Story id: <id>. + verbatim invoke_dev_with, folder always project-relative.
    • _post_dev_state_sync no-op (no sprint board); _verify_dev_artifactsverify_dev_stories; _verify_reviewverify_review_stories (drops the sprint-status gate).
  • Deterministic adapter read-backGenericDevAdapter resolves the id-keyed story spec via stories.resolve_story_spec when BMAD_LOOP_SPEC_FOLDER is set (new env seam on Engine._run_session), skipping the mtime scan; a relative folder is rebased against spec.cwd for worktree isolation.
  • [stories] policy (source = sprint-status|stories, spec_folder; no continue_independent) mirroring [review] — dataclass + validation + core.toml schema section + template. RunState pins source + spec_folder so resume/resolve rebuild the right engine without re-reading policy.
  • Preflight + CLIinstall.missing_stories_support content-probes bmad-dev-auto for folder+id dispatch (fail loud, not at dispatch time); run --spec <folder> forces stories mode; --dry-run prints the linear schedule (checkpoint badges, live on-disk state); --story <id> filters.
  • Commit hygieneverify_dev_stories proof-of-work also excludes the spec folder's stories/ + stories.yaml, so a spec-only story never reads as implementation work.

HITL checkpoints

Per-story and independent — a story may set both flags and pause twice.

  • spec_checkpoint (two-leg plan-halt)_plan_halt_leg reads on-disk state: leg 1 dispatches Halt after planning. + BMAD_LOOP_PLAN_HALT (the adapter synthesizes ready-for-dev as a plan_halt terminal), verify_dev_stories(plan_halt=True) gates the plan (ready-for-dev, no proof-of-work / build-test via the _run_verify_commands_after_dev seam), then the run pauses at PAUSE_PLAN_CHECKPOINT. Resume re-drives leg 2 (a plain folder+id → implement dispatch) via _resume_after_dev_verify, keyed off StoryTask.plan_checkpoint_pending. Reading on-disk status (not a flag) keeps the prompt's Halt after planning. leg and the env in lock-step.
  • done_checkpoint — after a story commits, _after_story pauses at PAUSE_STORY_CHECKPOINT, skipped when the story was the last to dispatch. Fires from both _loop and _finish_inflight, always after worktree integration, so a committed unit is merged before the run stops.
  • Blocked/sentinel/ambiguous wedge_pause_wedged records an ESCALATED task (spec path attached), so resolve / rearm_escalation and the resolved re-drive flow through the same machinery as an in-run escalation. No defer-and-continue.
  • Sentinel recovery (runs.rearm_escalation) — a fixed-slug <id>-unresolved.md / <id>-ambiguous.md is preserved under {run_dir}/sentinels/, journaled sentinel-cleared with its blocking condition, then deleted so the re-dispatch starts clean (PENDING → re-plan).
  • New pause consts PAUSE_PLAN_CHECKPOINT / PAUSE_STORY_CHECKPOINT + StoryTask.plan_checkpoint_pending (serialized). Journal events: plan-halt, checkpoint-pause, sentinel-cleared, stories-validated. The base-engine seams are no-ops for sprint/sweep; the TUI needs no changes — it already shells out to the CLI resume/resolve paths.

Tests

StoriesEngine happy path / scheduling (skip-done, blocked-pause, sentinel-pause, selector) / prompt seams / crash-resume round-trip; plan-checkpoint pause/resume round-trip, story-checkpoint pause incl. skip-if-last, additive spec+done double-pause, sentinel re-arm (preserved copy + journal + clean re-dispatch); adapter id-keyed + plan_halt read-back; [stories] policy matrix; install probe; CLI dry-run/validate/mode-detection; RunState + plan_checkpoint_pending round-trip; verify_dev_stories plan_halt gate; verify_review_stories. Full suite green; trunk check clean.

The full sandbox E2E matrix (two-story happy path, blocked→resolve→re-dispatch, worktree isolation, sprint-mode regression) + TUI polish + docs/CHANGELOG are Phase 4.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 96103771-3736-483d-bfdc-8ee8fee40f02

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/spec-to-loop-stories-phase2

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@augmentcode

augmentcode Bot commented Jul 6, 2026

Copy link
Copy Markdown
🤖 Augment PR Summary

Summary: Adds “stories mode” (folder+id dispatch) to bmad-loop so runs can be driven from a typed stories.yaml manifest (sibling of SPEC.md) instead of sprint-status.yaml.

Changes:

  • Introduces StoriesEngine to pick stories in strict list order, skip already-touched items within a run, and pause on blocked/sentinel/ambiguous states.
  • Extends GenericDevAdapter with deterministic id-keyed read-back when BMAD_LOOP_SPEC_FOLDER is set (avoids mtime scanning).
  • Adds [stories] policy section (+ schema/template) and persists source/spec_folder on RunState so resume/resolve rebuilds the same engine mode.
  • Adds CLI support (run --spec, --dry-run linear schedule output) plus preflight probes for newer bmad-dev-auto folder+id dispatch support.
  • Updates stories-mode verification to exclude spec-folder stories/ and stories.yaml from proof-of-work detection.

Tests: Adds coverage for StoriesEngine scheduling/pause/resume seams, adapter deterministic read-back, CLI/policy/state round-trips, install probing, and verify gates for stories mode.

🤖 Was this summary useful? React with 👍 or 👎

@augmentcode augmentcode 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.

Review completed. 4 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

base = folder if folder.is_absolute() else Path(spec.cwd) / folder
deadline = time.monotonic() + RESULT_GRACE_S
while True:
state = stories.resolve_story_spec(base, story_key)

@augmentcode augmentcode Bot Jul 6, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

src/bmad_loop/adapters/generic.py:510: _stories_result_json() no longer uses handle.launched_ns (unlike the mtime-scan path), so an already-terminal story spec from a previous step (notably the pre-existing done spec when starting a follow-up review session) could be misread as this session’s completion even if the session produced no new output.

Severity: high

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

result = devcontract.synthesize_result(state.path, story_key=story_key or None)
if result.result_json is not None:
return result.result_json
if not wait or time.monotonic() >= deadline:

@augmentcode augmentcode Bot Jul 6, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

src/bmad_loop/adapters/generic.py:515: If resolve_story_spec() yields KIND_AMBIGUOUS (multiple <id>-*.md matches), this currently just waits and then returns None, which will look like a generic stall rather than an actionable “ambiguous story file” error requiring operator intervention.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Comment thread src/bmad_loop/cli.py
print(e, file=sys.stderr)
return 1
if stories_on:
problem = _validate_stories_folder(paths, spec_folder)

@augmentcode augmentcode Bot Jul 6, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

src/bmad_loop/cli.py:323: In stories mode, a bad --story id isn’t validated here (only in _dry_run_stories), so the run can start, create state, and then crash later when StoriesEngine calls stories.schedule(..., selector=...) and gets StoriesError for an unknown id.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Comment thread src/bmad_loop/cli.py
help="force stories mode: dispatch the epic spec folder's stories.yaml by "
"folder+id (overrides [stories].source)",
)
run_p.add_argument("--epic", type=int, help="only stories from this epic (sprint mode)")

@augmentcode augmentcode Bot Jul 6, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

src/bmad_loop/cli.py:1384: When --spec forces stories mode, --epic is silently ignored (StoriesEngine disables epic_filter), which can surprise callers who pass both flags expecting filtering to apply.

Severity: low

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

…tch (Phase 2)

Wire "stories mode" (BMAD-METHOD #2549) into the loop as an opt-in engine
variant alongside the default sprint-status flow — building on the Phase 1
contract layer. Covers the folder+id engine/adapter plus the human-in-the-loop
plan/story checkpoints and sentinel recovery.

Engine + adapter:
- StoriesEngine(Engine): a thin override layer like SweepEngine. `_pick_next`
  runs the linear schedule from stories.py (re-validated fresh every pick,
  within-run skip set mirroring sprint's base_skip, blocked/sentinel/ambiguous
  → pause for resolve). `_dev_prompt` emits the folder+id dispatch
  (`/bmad-dev-auto Spec folder: <rel>. Story id: <id>.` + verbatim
  `invoke_dev_with`). `_post_dev_state_sync` is a no-op (no sprint board),
  `_verify_dev_artifacts`→verify_dev_stories, and `_verify_review`→
  verify_review_stories (drops the sprint-status gate).
- Deterministic adapter read-back: GenericDevAdapter resolves the id-keyed story
  spec via stories.resolve_story_spec when BMAD_LOOP_SPEC_FOLDER is set (new env
  seam on Engine._run_session), skipping the mtime scan; a relative folder is
  rebased against spec.cwd for worktree isolation.
- [stories] policy (source = sprint-status|stories, spec_folder; no
  continue_independent) mirroring [review] — dataclass, validation, core.toml
  schema section, template. RunState pins source + spec_folder so resume/resolve
  rebuild the right engine without re-reading policy.
- Preflight content-probe (install.missing_stories_support): stories mode needs
  a bmad-dev-auto whose step-01 carries folder+id dispatch; fail loud, not at
  dispatch time. `run --spec <folder>` forces stories mode; `--dry-run` prints
  the linear schedule (checkpoints, live on-disk state); `--story <id>` filters.
- verify_dev_stories proof-of-work also excludes the spec folder's stories/ +
  stories.yaml so a spec-only story never reads as implementation work.

HITL checkpoints (per-story, independent — a story may set both and pause twice):
- spec_checkpoint (two-leg plan-halt): `_plan_halt_leg` reads on-disk state —
  leg 1 dispatches `Halt after planning.` + BMAD_LOOP_PLAN_HALT (adapter
  synthesizes ready-for-dev as a `plan_halt` terminal), verify_dev_stories(
  plan_halt=True) gates the plan (ready-for-dev, no proof-of-work, no build/test
  via the `_run_verify_commands_after_dev` seam), then StoriesEngine pauses at
  PAUSE_PLAN_CHECKPOINT. Resume re-drives leg 2 (plain folder+id → implement) via
  `_resume_after_dev_verify`, keyed off StoryTask.plan_checkpoint_pending; the
  on-disk status (not a flag) keeps prompt + env in lock-step.
- done_checkpoint: after a story commits, `_after_story` pauses at
  PAUSE_STORY_CHECKPOINT — skipped when the story was the last to dispatch. Fires
  from both _loop and _finish_inflight, always after worktree integration, so a
  committed unit is merged before the run stops.
- Blocked/sentinel/ambiguous wedge (_pause_wedged) records an ESCALATED task
  (spec path attached), so `resolve`/rearm_escalation and the resolved re-drive
  flow through the same machinery as an in-run escalation — no defer-and-continue.
- Sentinel recovery in runs.rearm_escalation: a fixed-slug <id>-unresolved.md /
  <id>-ambiguous.md is preserved under {run_dir}/sentinels/, journaled
  `sentinel-cleared` with its blocking condition, then deleted so the re-dispatch
  starts clean (PENDING → re-plan).
- New pause consts PAUSE_PLAN_CHECKPOINT / PAUSE_STORY_CHECKPOINT + StoryTask
  .plan_checkpoint_pending (serialized). Journal events: plan-halt,
  checkpoint-pause, sentinel-cleared, stories-validated. Base engine seams are
  no-ops for sprint/sweep; the TUI keeps reusing the CLI resume/resolve paths.

Tests: StoriesEngine happy path / scheduling / prompt seams / resume round-trip,
plan-checkpoint pause/resume round-trip, story-checkpoint pause incl. skip-if-last,
additive spec+done double-pause, sentinel re-arm (preserved copy + journal + clean
re-dispatch), adapter id-keyed + plan_halt read-back, [stories] policy matrix,
install probe, CLI dry-run/validate, RunState + plan_checkpoint_pending round-trip,
verify_dev_stories plan_halt gate, verify_review_stories. Full sandbox E2E matrix
is deferred to Phase 4.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@pbean pbean force-pushed the feat/spec-to-loop-stories-phase2 branch from e94b265 to 3171264 Compare July 7, 2026 00:40
@pbean pbean changed the title feat(stories): engine + adapter for folder+id dispatch (Phase 2) feat(stories): engine, adapter + HITL checkpoints for folder+id dispatch (Phase 2) Jul 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant