You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Follow-up from #355 (awaiting-operator part 3), where the trade-off was taken deliberately and documented rather than papered over.
The gap
bmad-loop confirm reads .bmad-loop/operator-actions.json, which is machine-local and never committed (it is registered in the repository's local git exclude). So a story parked on the machine that ran the orchestrator can only be confirmed on that machine. A teammate, a fresh clone, or CI sees the parked spec and the parked board but has no way to complete the story with the command.
That is a real product gap, not just an aesthetic one: the human who performs the external action (buy the domain, publish the DNS record, grant the API key) is not necessarily sitting at the box that ran the unattended loop.
Why it was not simply committed
The index is written from inside a story's commit window, in _finalize_commit_phase. Every way of committing it there is wrong:
on its own path (verify.commit_paths, as decisions.apply_pre_answer does) — shifts HEAD past the commit_sha the park just stamped; and under scm.isolation = "worktree" it advances the target branch while the unit branch is still out, so an scm.merge_strategy = "ff" merge-back fails outright and the unit is kept + escalated.
folded into the story's own commit (written before finalize_commit's git add -A, as _close_declared_deferred annotations are) — only reachable inside the worktree, but the human reads the index at the project root. It would also invite a merge conflict between two parked stories whose worktrees were created before the first merged.
left merely untracked — dirties the tree the epic-boundary auto-sweep refuses to run on (_maybe_auto_sweep), and the next story's git add -A sweeps one story's bookkeeping into another story's commit.
Why the spec alone is not enough
confirm cannot fall back to reading the spec when the index has no entry: spec_file is reported by the dev session in its result JSON (engine.py / verify.py set task.spec_file from rj["spec_file"]) and is not derivable from a story key — there is no by-key spec discovery anywhere in the codebase. So the path to a parked story's spec survives only in the index. #355 refuses and explains rather than guessing with a glob.
What a fix needs
A committed index plus the commit sequencing to make it safe. Sketch, not a design:
write the index at a point where committing it cannot collide with the story's own commit or the worktree merge-back — most likely afterintegrate_unit, once the unit is merged and the target branch is the only thing moving;
or make the entry reconstructible: have the park record spec_file somewhere already committed (the story-awaiting-operator journal entry does not carry it today), so a machine without the index can rebuild one.
The second is probably cheaper and composes with the existing operator.registry-stale warning, which already reports "the board parks X but the index has no entry" — that warning is exactly the fresh-clone case, and today it can only tell you to go find another machine.
Acceptance
bmad-loop confirm <story-key> works on a fresh clone of a repo whose board parks that story.
Follow-up from #355 (awaiting-operator part 3), where the trade-off was taken deliberately and documented rather than papered over.
The gap
bmad-loop confirmreads.bmad-loop/operator-actions.json, which is machine-local and never committed (it is registered in the repository's local git exclude). So a story parked on the machine that ran the orchestrator can only be confirmed on that machine. A teammate, a fresh clone, or CI sees the parked spec and the parked board but has no way to complete the story with the command.That is a real product gap, not just an aesthetic one: the human who performs the external action (buy the domain, publish the DNS record, grant the API key) is not necessarily sitting at the box that ran the unattended loop.
Why it was not simply committed
The index is written from inside a story's commit window, in
_finalize_commit_phase. Every way of committing it there is wrong:verify.commit_paths, asdecisions.apply_pre_answerdoes) — shiftsHEADpast thecommit_shathe park just stamped; and underscm.isolation = "worktree"it advances the target branch while the unit branch is still out, so anscm.merge_strategy = "ff"merge-back fails outright and the unit is kept + escalated.finalize_commit'sgit add -A, as_close_declared_deferredannotations are) — only reachable inside the worktree, but the human reads the index at the project root. It would also invite a merge conflict between two parked stories whose worktrees were created before the first merged._maybe_auto_sweep), and the next story'sgit add -Asweeps one story's bookkeeping into another story's commit.Why the spec alone is not enough
confirmcannot fall back to reading the spec when the index has no entry:spec_fileis reported by the dev session in its result JSON (engine.py/verify.pysettask.spec_filefromrj["spec_file"]) and is not derivable from a story key — there is no by-key spec discovery anywhere in the codebase. So the path to a parked story's spec survives only in the index. #355 refuses and explains rather than guessing with a glob.What a fix needs
A committed index plus the commit sequencing to make it safe. Sketch, not a design:
integrate_unit, once the unit is merged and the target branch is the only thing moving;spec_filesomewhere already committed (thestory-awaiting-operatorjournal entry does not carry it today), so a machine without the index can rebuild one.The second is probably cheaper and composes with the existing
operator.registry-stalewarning, which already reports "the board parks X but the index has no entry" — that warning is exactly the fresh-clone case, and today it can only tell you to go find another machine.Acceptance
bmad-loop confirm <story-key>works on a fresh clone of a repo whose board parks that story.scm.merge_strategy = "ff"underscm.isolation = "worktree", and no regression to the clean-tree precondition the epic-boundary auto-sweep depends on (both covered by tests added in feat(cli): bmad-loop confirm completes awaiting-operator stories #355).