Releases: bmad-code-org/bmad-loop
Releases · bmad-code-org/bmad-loop
Release list
v0.8.0
Changed
- BREAKING: the project is renamed
bmad-auto→bmad-loop. The distribution, console script,
and CLI are nowbmad-loop; the Python package isbmad_loop(wasautomator); the BMAD module
code and marketplace plugin arebmad-loop(wasbauto); per-project state moves from
.automator/to.bmad-loop/. The GitHub repo is now
bmad-code-org/bmad-loop — old web and git URLs
redirect. Clean break: no compatibility shims. - BREAKING: renamed public identifiers. Env vars
BMAD_AUTO_*→BMAD_LOOP_*; plugin
entry-point groupbmad_auto.plugins→bmad_loop.plugins; hook relaysbmad_auto_hook.py/
bmad_auto_probe_hook.py→bmad_loop_hook.py/bmad_loop_probe_hook.py; skills
/bmad-auto-{setup,sweep,resolve}→/bmad-loop-{setup,sweep,resolve}; tmux session/window
prefixesbmad-auto-*→bmad-loop-*; worktree branchesautomator/<run-id>→
bmad-loop/<run-id>; TUI classBmadAutoApp→BmadLoopApp. Custom plugins, CLI profiles, and
policy files that reference any of these must be updated.
Added
- New
pre_commit_gateplugin workflow-injection stage. Gate workflows can bind to
pre_commit_gate, which fires unconditionally just before every commit — on the review-converged,
review-skipped, and review-budget-rescue paths alike — while the phase can still legally defer.
TEA's trace/nfr/review gates rebind to it, fixing blocking gates that were previously inert
whenever a dev session recommended no review follow-up (soon_pre_commitfail-opened on the
missing artifacts).
Fixed
- A workflow session that finishes its work but never writes a completion marker no longer
livelocks the run. Each result-less Stop used to refill the stall-nudge budget, re-nudging a
responsive-but-signal-less session untilsession_timeout_min. The engine now appends an explicit
completion contract (absolute marker path + frontmatter shape) to every workflow-session prompt,
and a newlimits.workflow_stall_nudges_cap(default 3) caps the total nudges a workflow session
may receive — degrading a still-missing marker to "stalled" in ~30 min instead of hours. Dev/review
session nudging is unchanged. - On Windows, a live engine whose process identity can't be read now shows
UNKNOWNinstead of a
falseINTERRUPTED. psutil raisesERROR_ACCESS_DENIEDfor a process in another session or
elevation, which the identity-aware liveness read had surfaced as dead — mislabeling running runs
and weakening the resume/delete guards. Liveness is now tri-state (alive/dead/unknown) and
biased away from false-dead;resume,resolve,delete,archive, and cleanup all surface and
warn onunknownwithout ever letting an unverifiable pid block cleanup forever, andresolve
gains--forceto override a squatted-pid block. - A review session that appends to the deferred-work ledger no longer leaves a sweep bundle
unclosed. The bundle ledger is reclosed after review (journaled distinctly as
sweep-bundle-reclosed), and the review prompt now states the ledger is append-only for sessions. - Worktree git-exclude patterns now anchor correctly on native Windows.
install.pynormalizes
backslashes in the per-worktree exclude paths so the ignore rules match (a no-op on POSIX).
Migration
- Reinstall the tool under its new name — uv can't rename a package in place:
uv tool uninstall bmad-auto, then
uv tool install "bmad-loop[tui] @ git+https://github.com/bmad-code-org/bmad-loop.git". - Re-run
/bmad-loop-setup(orbmad-loop initdirectly).initmigrates a project in place:
it strips the old.automator/Stop hook from each CLI's settings, removes thebmad-auto-*
skill dirs, and carries.automator/policy.tomlover to.bmad-loop/policy.toml. Setup folds the
oldbautoconfig intobmad-loopand clears the leftoverbautoconfig section, stale
BMAD Automator Skillshelp rows, and the_bmad/bauto/installer dir. - Legacy
.automator/is left in place (runs, archives, profiles, plugins) and can be deleted
or hand-moved once the migration is confirmed; stale.automator/*gitignore lines are left
untouched.
v0.7.12
Added
- The TUI dashboard now shows the cost-proportional weighted token total, with the raw total in a new
column. Thetokenscolumn and the run-header summary discount cache-read tokens by the run's
cache_read_weight— the same weighting the per-story budget enforces — so the headline number
tracks spend rather than context re-reads; the previous unweighted total moves to a newrawcolumn.
Fixed
- The dashboard no longer crashes when a background poll lands as the screen is torn down or
switched away. A poll worker delivers its refresh on the UI thread; if that arrived just as the
app quit or another screen opened, the query for the run table raisedNoMatches. The apply now
drops stale refreshes for a screen that is no longer running (while still updating one merely
backgrounded under a modal). - A failed attempt's work is preserved before an auto-rollback hard reset instead of being silently
discarded. Withscm.rollback_on_failureon (or on a resolved re-drive), a deferred or stopped
attempt's commits above baseline are now parked under anattempt-preserve/<run_id>-<head8>branch,
and its uncommitted working-tree diff — tracked edits and run-created untracked files alike — under
refs/attempt-preserve-dirty/; both are recoverable by name and survive gc. A plain rollback that
cannot create the ref refuses to reset and pauses for manual recovery rather than destroying work.
The uncommitted snapshot is scoped to this run's own changes (never a pre-existing untracked file),
commits under a synthetic identity so it works with no git user configured, and is keyed per retry
so repeated rollbacks against the same baseline no longer overwrite each other's recovery ref. - Process liveness is now identity-aware, so a reused PID no longer reads as a live run. A recycled
pid (common on Windows) used to register as a false "alive" — blocking resume of a dead run,
stranding worktree reclaim, leaking sessions, and showing dead runs as RUNNING. The pid file now
carries a process-identity token that resume, stop, and the TUI verify against; on win32 the engine
also ignores console SIGINT/SIGBREAK during a run so a ConPTY Ctrl+C broadcast can't kill it. - A story from a resolved escalation that still can't finish now re-escalates instead of being
silently deferred. When a human-resolved CRITICALblockedescalation was re-driven and the
re-drive couldn't reachstatus: done(e.g. the environment was still broken), the story used to
exhaust its dev/review budget and plateau-defer — filing an unresolved blocker as deferred work and
rolling back the implemented code. Whileresolved_redriveis latched, budget exhaustion now
re-escalates (pauses for the human) instead of deferring, and the attempt's tree is preserved. - A resumed
--epic Nrun stays scoped to its epic and no longer declares the epic "done" while
stories remain.resumerebuilt the engine without the run's--epic/--story/--max-stories,
so a scoped run silently widened to every epic; with strict file-order story selection, deferring or
finishing a story in an epic placed out of numeric order in the sprint board (e.g. one appended last)
bounced selection to an earlier-in-file epic and fired a spurious "epic N complete" boundary,
stranding the epic's remaining stories. The selector and cap are now persisted and restored on
resume, and story selection exhausts the current epic before advancing — so an epic boundary fires
only when that epic has no actionable stories left. Document-order epic execution is unchanged.
v0.7.11
Fixed
- A finalized story the review just won't stop recommending a follow-up for is now committed, not
rolled back, when the review budget runs out. Exhaustinglimits.max_review_cyclespreviously
always deferred + reverted — discarding completed, review-passing work (frontmatterstatus: done,
verify green) whose only "failure" was a never-clearingfollowup_review_recommended. The
orchestrator now commits that work and re-files the lingering recommendation as a fresh open
deferred-work entry; a story that itself came from such an entry is committed without re-filing, so
a second non-convergence reaches a human instead of looping across sweeps. Worktree-isolation runs
were unaffected — a deferred unit already keeps its worktree.
v0.7.10
Fixed
- Completed work left at the transient
in-reviewfrontmatter is no longer falsely deferred and
rolled back. Abmad-dev-autosession that dies in its step-04 Finalize tail can leave the spec
frontmatter at the transientin-reviewmarker while the## Auto Run Resultprose already says
Status: done— the same stale-frontmatter gate bug as 0.7.8 with a different value. The 0.7.8
reconcile skippedin-reviewto protect the legacybmad-auto-devreview-handoff, but that fork
is retired andin-reviewis now only ever a transient marker, so it is reconciled todone
before the gates run. Every deterministic gate still runs afterward, and afollowup_review_recommended: true
spec still triggers the follow-up review pass. Closes a re-sweep loop that re-ran and discarded the
same completed bundles (~47M tokens/cycle).
v0.7.9
Fixed
- The multiplexer seam no longer leaks raw subprocess timeouts. Every contract method now raises
MultiplexerError/TmuxErroror returns its documented sentinel instead of letting a 30 s tmux
hang escape as a rawsubprocess.TimeoutExpired. - A transient tmux hang no longer crashes a run or mis-reads a working session as dead. The
wait-loop tolerates an unknowable liveness probe — a persistent hang degrades to an honest
timeout, never a falsecrashed. - An unexpected engine exception is now recorded instead of being lost to the parked control
window. The orchestrator writes arun-crashjournal line + acrash.txttraceback, sets a
CRASHEDrun status, and tears down the orphaned agent session — rather than dying with a
traceback printed only to the pruned control pane.
v0.7.8
Fixed
- Completed
bmad-dev-autowork is no longer falsely deferred when the skill leaves the spec
frontmatterstatusstale. The skill can finalize a run in its## Auto Run Resultprose
(Status: done) yet leave the YAML frontmatter at the template defaultdraft; since every gate
reads frontmatter, the sprint/ledger sync no-op'd and the story or sweep bundle deferred — losing
tested work on rollback. The orchestrator now reconciles the frontmatter to the success status
from the terminal prose before the gates run (journaledspec-status-reconciled). It reconciles
only adoneoutcome from a non-terminal status, and every deterministic gate (worktree diff,
dw-ids, verify commands, ledger) still runs — so the bookkeeping is repaired without trusting prose
to pass a gate.
v0.7.7
Fixed
- Spec-status gates are now case- and whitespace-insensitive. A hand-edited spec whose
frontmatter carried a stray-casedDone/In-Reviewsilently failed the dev/review gate and the
story never advanced; every spec-frontmatter status read now normalizes through a single
verify.status_ofhelper. The well-formed lowercase path is unchanged. Also fixes the
manual-rollback notice, which rendered an invalidgit reset --hard the run's baseline commit
when no baseline was recorded — it now shows a<baseline_commit>placeholder. - Project-relative path guards reject
..traversal and OS-foreign absolute paths. A CLI
profile or plugin manifest could declare aconfig_path/skill_tree/seed_files/module path
that climbed out of the project with../— or, on Windows, a POSIX-absolute/etc/...that
Path.is_absolute()failed to flag — and slip past the "must be project-relative" check. The
guards now reject both, on every platform. - Persisted relative paths serialize with forward slashes. A worktree run's
spec_fileand the
resolve context'sresolution_pathwere written with the host separator, so astate.jsonor
context file produced on Windows read back with backslashes. Both now persist viaas_posix()for
a single cross-OS contract (a no-op on POSIX). - The TUI no longer shows a stale run after a same-size state rewrite. The dashboard's
stat-gated cache keyed on(mtime_ns, size), so an atomicstate.jsonrewrite of identical size
within one coarse mtime tick (e.g. WSL2 drvfs) could be served stale. The engine rewrites
atomically onto a fresh inode, so the cache signature now includesst_ino. - A dev session is no longer mis-stalled while it is actively working or legitimately waiting.
Building ondev_stall_grace_s(0.7.5), the idle-grace window now measures genuine inactivity
rather than time-since-last-Stop: any growth of the session's pane log (a long productive turn, a
streaming subagent) re-arms it, so a session that has finished implementation and is mid-review is
no longer killed and rolled back. And because bmad-auto cannot re-invoke a turn that ended to await
a background process, the grace window no longer dead-ends in a stall — on real silence the
orchestrator wakes the session with up tolimits.dev_stall_nudges(new, default 2) nudges before
giving up; a genuine Stop restores the budget, so a slow-but-cooperative session waits up to
session_timeout_minwhile a truly unresponsive one still stalls.0restores stall-on-grace-expiry.
v0.7.6
Changed
- The OS is now abstracted behind a registry of seams, so a non-tmux/native-Windows port is new
files plus one registration line each — no core edits. The terminal multiplexer is selected
through a registry (register_multiplexer, bysys.platformwith aBMAD_AUTO_MUX_BACKEND
override) rather than hardcoded inget_multiplexer(), and the tmux backend split into a reusable
BaseTmuxBackendextension point — every tmux invocation funnels through one overridable_run()
primitive — withTmuxMultiplexeras a thin POSIX leaf, so a tmux-family backend (an eventual
"psmux") overrides only the spawn primitive and the few divergent methods. - Process-lifecycle primitives moved behind a
ProcessHostseam. Politely-stop / force-kill /
liveness / PID-reuse-identity now route throughget_process_host()(registered like the
multiplexer, with aBMAD_AUTO_PROCESS_HOSToverride); aWindowsProcessHostships ready to
register. Hook registration no longer hardcodespython3— it takes the interpreter prefix from
ProcessHost.hook_interpreter()(POSIXpython3; Windowsuv run --no-project python), and
bmad-auto validateruns a platform preflight that reports the selected backend's readiness and
names the process host. Behavior on Linux/macOS/WSL is unchanged. - The bundled Unity plugin's teardown delegates pid lifecycle to
ProcessHostand its helper
scripts run under the orchestrator's own interpreter. Plugin helper scripts are spawned via
sys.executable(not a PATH-resolvedpython3), so a bundled script may importautomatorseams;
unity_teardown.pynow reaps leaked Editor/MCP processes throughget_process_host()instead of
re-implementing kill/liveness, gaining Windows behavior for free.
Added
- A consolidated porting guide maps the four OS seams (terminal
multiplexer, process lifecycle, hook interpreter, validate preflight), their registries and
test-override env vars, and exactly what a native-Windows port costs end to end. The
adapter-/plugin-authoring guides, ROADMAP, README, and FEATURES are updated to the post-registry
world.
v0.7.5
Added
- Select and copy text from the TUI Log and Attention panes. Click-drag highlights and
ctrl+c
copies (those panes are now selectableRichLogs with a workingget_selection);ycopies the
whole active pane in one keystroke. The other panes are interactive widgets — hold your terminal's
bypass modifier (Shift on most Linux terminals, Option on iTerm) to use its native selection. Copy
rides OSC 52, so under tmux it needsset -g set-clipboard onto reach the system clipboard. bmad-auto diagnoseemits a sanitized diagnostic dump of a run/sweep so a user can hand
maintainers what's needed to debug a run without shipping any code, spec/story content, prompts,
transcripts, file paths, or PII. It derives the diagnostic shape — phase/token/session
histograms, escalation counts, adapter/model, env, run-dir file sizes — and routes every
content-bearing value through the auditedsanitizechokepoint: identifiers (story keys,
branches, SHAs) are pseudonymized to stable per-dump aliases so events still correlate, free text
collapses to presence booleans, and the rendered output is re-scanned by a fail-closed leak
self-check before writing. Defaults to the latest run;--all,--out,--json, and a
local-only--legendare supported.
Changed
- The
sanitizechokepoint now redacts credential-shaped strings. Identifier-shaped secrets
(ghp_/sk-/AKIA/xoxb-/JWTs and long high-entropy blobs) previously passed the slug gate
verbatim; they are now<redacted:secret>, closing the same hole forprobe-adapter.
Fixed
- A dev session that ends its turn to await a long-running background process is no longer
mis-stalled. Abmad-dev-autosession that yields to wait on a slow job (a Unity PlayMode run, a
long test) and expects to be re-invoked on completion fired a result-less Stop — and since the
generic dev adapter runs zero nudges, that was ruled stalled after only the 15s result grace,
driving a retry that (withscm.rollback_on_failureoff) paused the whole sweep for a manual
reset. A newlimits.dev_stall_grace_s(default 600s) opens an idle-grace window on a result-less
dev Stop and re-arms it on each re-invocation, so only a genuinely idle gap with no terminal spec —
or the session timeout — counts as a stall. Non-dev adapters keep zero grace and are unchanged.
v0.7.4
Fixed
- Deferred-work sweep no longer defers every bundle it just finished. After the migration to
the generic upstreambmad-dev-autoprimitive, bundle dev sessions completed the work but were
rejected byverify_dev_bundlewithresult.json dw_ids [] do not match the bundle's […],
retried to budget, deferred, and rolled the work back — so a sweep could never close a bundled
entry. The retired dev fork used to echo the dw ids; the generic skill doesn't. The orchestrator
already owns the bundle→dw-id binding, so the cross-check now passes when the session claims no
ids, and the run exportsBMAD_AUTO_DW_IDSso the synthesized result still carries them and the
check stays live.