Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ breaking changes may land in a minor release.
failed record write never blocks the story — it parks recordless, journaled and reported by
`validate` — and a failed commit restores the record as found. Confirming commits the record's
deletion together with the spec+board flip, and a legacy-only park still gets that commit.
`validate` reports a board parked with no record under its own id, `operator.park-record-missing`:
no longer the fresh-clone norm, but evidence of a failed record write, a pre-upgrade park, a
checkout that lacks the branch carrying the park commit (pull it), or a deleted record.

- **`bmad-loop confirm` completes a parked story (#335, part 3 of 4).** Once you have carried out the
external actions a story owed, `bmad-loop confirm <story-key>` walks you through them one at a time
Expand All @@ -35,7 +38,7 @@ breaking changes may land in a minor release.
the spec is read BACK from disk rather than trusted, so a story is never declared done over a
write that did not land. A confirmation interrupted between its spec writes and its board write
(the board file in a shape its line writer cannot rewrite, or an IO failure) leaves a signed-off
spec at `done` with the index entry still pointing at it — re-running `confirm` now **finishes**
spec at `done` with the park entry still pointing at it — re-running `confirm` now **finishes**
that instead of refusing it as stale drift: it advances the board, drops the entry and commits,
with no second prompt and no second audit section. It resumes equally from a board a human already
fixed by hand, since that is what the failure message asks them to do. `--list`, `--json`
Expand Down Expand Up @@ -64,7 +67,7 @@ breaking changes may land in a minor release.
committed. Under worktree isolation the unit merges like a `done` one. `[operator] enabled =
false` restores the old two-outcome behavior.

`bmad-loop confirm` and the project-level index it reads arrived in part 3, above.
`bmad-loop confirm` and the park entries it reads arrived in part 3, above.

- **`awaiting-operator` vocabulary, no writer yet (#335, part 1 of 4).** Names, at every layer, the
state a story reaches when its agent-doable work is finished and committed but its acceptance
Expand Down
6 changes: 3 additions & 3 deletions docs/FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ See [README.md](../README.md) for the narrative overview and [setup-guide.md](se
- An auto-rollback parks the attempt before it resets — commits above baseline on an `attempt-preserve/*` branch, the uncommitted tree (tracked edits + run-created untracked files) on a `refs/attempt-preserve-dirty/*` snapshot — and **refuses the reset if it could not** (#340): the run pauses with rescue instructions naming the tree, rather than discarding work the safety net failed to capture. A resolved re-drive is pause-free by contract, so there it journals and proceeds. `scm.preserve_keep` (default 20) bounds retention of both ref families.
- Plateau-defer: when review won't converge, the story is skipped, the spec stashed into the run dir, deferred-work preserved, the run continues. The defer notification names where the attempt survives — in place, the recovery ref the rollback parked it on plus the `git merge --ff-only` line that restores it, flagged as commits-only when the uncommitted snapshot could not be captured (only reachable on a re-drive since #340 — a plain rollback refuses the reset instead); isolated, the kept-failed unit branch plus any earlier rolled-back attempt's ref (named, not offered as a merge — it is not fast-forwardable there). That recovery ref is projected as `preserve_ref` in `status` and `--json`; the unit branch never is (#333). When the recovery itself pauses the run (rollback OFF, or a preserve failure refusing the reset), the defer record still lands before the pause — its notice then points at the ACTION REQUIRED manual-recovery notice instead of naming a ref (#342).
- Stories owing human-only external actions park at `awaiting-operator` instead of lying (#335). A story whose acceptance criteria include something no agent can do — buy a domain, publish a DNS record, grant an API key — finishes and **commits** everything an agent can, records what is owed in its spec's `operator_actions:` frontmatter, and parks. The board advances to `awaiting-operator` (a forward move; nothing regresses), the run continues to the next story, and nothing is rolled back — a park is a success that commits, so there is no stash and no recovery ref. A park clears every deterministic gate a `done` story clears (the spec/board pair, the project's verify commands, a non-empty action list) and skips only the review loop, which has nothing in the diff to converge on; a park declaring nothing readable is refused and repaired rather than committed. Parking is notify-only — non-blocking by design, so unlike an escalation it never halts the run. `[operator] enabled = false` restores the old two-outcome behavior, where such a story could only be `done` (a green board hiding outstanding work) or `blocked` (halting the whole run).
- Completing a park: `bmad-loop confirm <story-key>` walks you through the outstanding actions one at a time, then writes the spec's `## Operator Confirmation` audit section, advances the spec and board to `done`, and commits the pair. Nothing is re-driven — the agent-doable work was committed at park time, and re-running a session would redo finished work while the actions stayed outside the repo; `--reverify` re-runs the project's `[verify]` commands first (for the case that matters: the external action may have changed what the tests see) and a failure blocks the confirmation. The parked stories are indexed in `.bmad-loop/operator-actions.json`, which is **machine-local and never committed** — it is written from inside a story's commit window, where committing it would either shift `HEAD` past the commit the park just stamped or, under worktree isolation, advance the target branch and break an `scm.merge_strategy = "ff"` merge-back. The committed truth is the spec and the board; the index is how `confirm` finds them, so a park is confirmed on the machine that ran it. `validate` warns on drift in both directions (`operator.registry-stale`, `operator.actions-malformed`) and `confirm` refuses a drifted entry rather than flipping a board on the index's word alone.
- A confirmation is resumable. Every write is checked — the spec is read back from disk rather than trusted, so a story is never declared done over a write that did not land — and the index entry is dropped last, so a failure part-way leaves the story findable. Interrupted between the spec writes and the board write, what survives is a signed-off spec at `done` with the entry still pointing at it; re-running `confirm` **finishes** that rather than refusing it as stale, advancing the board, dropping the entry and committing, with no second prompt and no second audit section (the section on disk _is_ the acknowledgment, and it is fence-aware, so an example quoted inside a code block never counts as one). It resumes equally from a board a human already fixed by hand, since that is what the failure message asks for. `--list`, `--json` (`resumable`, `confirmation_recorded`) and `validate` (`operator.confirm-interrupted`) all name the state rather than calling it stale — the remedy inverts, so the check id does too.
- Completing a park: `bmad-loop confirm <story-key>` walks you through the outstanding actions one at a time, then writes the spec's `## Operator Confirmation` audit section, advances the spec and board to `done`, and commits the pair together with the park record's deletion. Nothing is re-driven — the agent-doable work was committed at park time, and re-running a session would redo finished work while the actions stayed outside the repo; `--reverify` re-runs the project's `[verify]` commands first (for the case that matters: the external action may have changed what the tests see) and a failure blocks the confirmation. Each park is recorded in a **committed per-story file** under `.bmad-loop/operator/`, written inside the story's commit window so it rides the park's own commit — through the worktree merge-back, under every `scm.merge_strategy` — to every clone the commit reaches: a teammate, a fresh clone, or CI can confirm a story parked elsewhere. (The pre-#356 machine-local index `.bmad-loop/operator-actions.json` is still read and pruned on confirm, never written, so an in-flight park from an older version stays confirmable where it was written.) The committed truth is the spec and the board; the record is how `confirm` finds them. `validate` warns on drift in every direction — `operator.registry-stale`, `operator.actions-malformed`, and `operator.park-record-missing` for a board parked with no record, which is no longer the fresh-clone norm but evidence of a failed record write, a pre-upgrade park, a checkout that lacks the branch carrying the park commit (pull it), or a deleted record — and `confirm` refuses a drifted record rather than flipping a board on the record's word alone.
- A confirmation is resumable. Every write is checked — the spec is read back from disk rather than trusted, so a story is never declared done over a write that did not land — and the park record is dropped last, so a failure part-way leaves the story findable. Interrupted between the spec writes and the board write, what survives is a signed-off spec at `done` with the entry still pointing at it; re-running `confirm` **finishes** that rather than refusing it as stale, advancing the board, dropping the entry and committing, with no second prompt and no second audit section (the section on disk _is_ the acknowledgment, and it is fence-aware, so an example quoted inside a code block never counts as one). It resumes equally from a board a human already fixed by hand, since that is what the failure message asks for. `--list`, `--json` (`resumable`, `confirmation_recorded`) and `validate` (`operator.confirm-interrupted`) all name the state rather than calling it stale — the remedy inverts, so the check id does too.
- Typed escalations: `CRITICAL` pauses the run + notifies (desktop + `ATTENTION` file); `PREFERENCE` is journaled and continues.
- Environment faults pause without burning budget (#194): a session whose coding CLI never reached the API — a deterministic verify command whose _environment_ is broken (`sh` reports `rc 126/127`; `cmd` has no such convention, so on Windows a missing tool is caught by its `is not recognized` message or by resolving the command's leading token, and a command naming a file `cmd` cannot execute — a `.sh`, anything outside `PATHEXT` — is a fault rather than the silent `rc 0` pass it used to be, #302), **or** a dev/review/fix/workflow/sweep session whose pane log matches the profile's `env_fault_patterns` (an `API Error … Connection refused`-class transport failure that idled out the session clock) — pauses the run with the matched evidence instead of charging the attempt/cycle and deferring the story as if its code were broken. Re-arm (or a sweep's escalation-resume) restores the budget, so a resume after the outage clears re-drives from a clean slate. Patterns are per-profile (seeded only for `claude`; extend/disable via a project profile overlay).
- CRITICAL resolution: `bmad-loop resolve <run-id>` opens an interactive resolve agent seeded with the escalation + frozen spec; you disambiguate, it re-arms the story (`escalated → pending`, spec reset to `ready-for-dev`) and resumes. `--no-interactive` skips to re-arm if you fixed the spec yourself.
Expand Down Expand Up @@ -194,7 +194,7 @@ See [README.md](../README.md) for the narrative overview and [setup-guide.md](se
- `bmad-loop resume <run-id>` — continue a paused/interrupted run.
- `bmad-loop resolve <run-id>` — resolve a CRITICAL escalation, then re-arm + resume (`--story`, `--no-interactive`, `--restore-patch <path>` for intent-gap patch-restore, `--resume`/`--no-resume`).
- `bmad-loop decisions` — answer deferred-work decisions past sweeps left unanswered (`--list` to just show them). `--json` instead emits a stable machine-readable document (schema-versioned; per decision the id, question, context, recommendation and every option's key/label/effect/intent/resolution/bundle-name plus a derived `recommended` flag) per the [contract below](#machine-readable-output---json); it implies the listing and never prompts, and nothing pending yields a valid empty document.
- `bmad-loop confirm <story-key>` — complete a story parked at `awaiting-operator` once you have carried out the external actions it owes: acknowledges each action in turn (`--yes` skips the prompts), writes the spec's `## Operator Confirmation` audit section, advances spec and board to `done`, and commits the pair. `--list` shows every parked story and what each owes without confirming any (marking one whose confirmation was interrupted as already signed off, not as unconfirmable); `--reverify` re-runs the project's `[verify]` commands first and blocks the confirmation if they fail. Re-running it on an interrupted confirmation finishes that confirmation instead of refusing it. `--json` instead emits a stable machine-readable document (schema-versioned; per parked story the key, actions, spec file, spec/board status, parking commit and run, plus the derived `confirmable` and `resumable` flags, the `confirmation_recorded` reading behind the latter, and a human `drift` reason) per the [contract below](#machine-readable-output---json); it implies the listing and never prompts, and nothing parked yields a valid empty document.
- `bmad-loop confirm <story-key>` — complete a story parked at `awaiting-operator` once you have carried out the external actions it owes: acknowledges each action in turn (`--yes` skips the prompts), writes the spec's `## Operator Confirmation` audit section, advances spec and board to `done`, and commits the pair. `--list` shows every parked story and what each owes without confirming any (marking one whose confirmation was interrupted as already signed off, not as unconfirmable); `--reverify` re-runs the project's `[verify]` commands first and blocks the confirmation if they fail. Re-running it on an interrupted confirmation finishes that confirmation instead of refusing it. `--json` instead emits a stable machine-readable document (schema-versioned; per parked story the key, actions, spec file, spec/board status, the parking run and the commit that carries the park — derived from the record's own git history, empty when the record is not yet in any commit — plus the derived `confirmable` and `resumable` flags, the `confirmation_recorded` reading behind the latter, and a human `drift` reason) per the [contract below](#machine-readable-output---json); it implies the listing and never prompts, and nothing parked yields a valid empty document.
- `bmad-loop list` (`ls`) — list every run/sweep with its short ref, type, and status. `--json` instead emits a stable machine-readable document (schema-versioned; one entry per run, oldest first: short ref, run id, type, started-at, liveness-aware status, paused stage) per the [contract below](#machine-readable-output---json); an empty runs dir yields a valid empty document.
- `bmad-loop status [<run-id>]` — run + sprint summary with per-story token totals, cost-weighted with the raw count alongside. `--json` instead emits a stable machine-readable document (schema-versioned; run state, snapshot `cache_read_weight`, per-story phase/attempt/review-cycle/tokens/commit/defer-reason, plus the additively-added run-level `adapters` — the dev/review/triage adapter the policy snapshot resolves to, `null` on a run predating adapter stamping — and per-story `adapters_used`, the adapter identity actually recorded per role) per the [contract below](#machine-readable-output---json) — the supported surface for scripts; the text output is best-effort.
- `bmad-loop diagnose [<run-id>]` (`diag`) — emit a sanitized diagnostic dump of a run/sweep to hand maintainers (histograms, counts, env, file sizes — no code/spec/prompts/paths/PII); a stray pseudonymized identifier is auto-substituted with its alias (disclosed in the report), while PII/secret hits still refuse to emit; defaults to the latest run (`--all`, `--out`, `--max-journal-entries`). `--json` emits the same dump as a stable machine-readable document per the [contract below](#machine-readable-output---json) instead of the markdown report.
Expand Down
1 change: 1 addition & 0 deletions src/bmad_loop/checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"operator.registry-stale",
"operator.actions-malformed",
"operator.confirm-interrupted",
"operator.park-record-missing",
"deferred.closes-unknown",
"deferred.closes-malformed",
"deferred.closes-entry-unreadable",
Expand Down
Loading