Skip to content

Releases: bmad-code-org/bmad-loop

v0.9.0

Choose a tag to compare

@github-actions github-actions released this 21 Jul 22:15
7255174

Added

  • Active agent indicator (#153). The TUI run header and tasks table now name who is driving —
    the resolved adapter·model for the live stage, or the run's configured adapters when no session is
    open. Session-start journal entries and session records stamp the resolved adapter identity, and
    bmad-loop status --json gains an additive run-level adapters (the snapshot-resolved
    dev/review/triage identity, null on a run predating stamping) and per-story adapters_used (the
    identity actually recorded per role) — both are a projection of the run's persisted policy snapshot
    (re-stamped from current config on resume) and its recorded sessions, not live policy read at status
    time; a run whose snapshot predates adapter stamping reports adapters: null rather than a
    fabricated default. Schema version unchanged.

  • Graceful stop (stop --graceful, TUI S). Ask a live run to finish its in-flight item —
    a story or sweep bundle through commit, or an in-progress sweep triage — then finalize cleanly
    and stop as a resumable stopped run, instead of the hard SIGTERM stop that kills mid-item.
    Delivered through a stop-request.json control file consumed at the next item boundary, so it
    needs no signal and works on every platform and multiplexer backend. Pending auto-sweeps are
    suppressed; --cancel-graceful withdraws a request, and a hard stop still wins over a pending one.
    status --json gains an additive graceful_stop_pending field (schema version unchanged).

  • bmad-loop validate --json (#205). A stable, schema-versioned JSON document of the
    preflight: the ok verdict, the queue mode/spec_folder, per-severity counts, and every
    check as a flat emission-ordered finding. Each finding carries a stable check id
    hooks.registered, adapter.binary, skills.base-incomplete, … — so CI can branch on a
    particular failing gate instead of matching remediation prose, which is the part most likely
    to be reworded. detail keeps what each check knew before it flattened itself into a
    sentence: mux.backends-detected keeps every detected backend row rather than the text's
    tmux*, psmux (unavailable) soup, whose trailing * a consumer had to parse to learn which
    backend was selected, and skills.base-incomplete keeps missing_markers as a list rather
    than a ", "-joined string. A failing check emits the whole document and still exits 1
    the nonzero code is the verdict being reported, not a failure to produce one, so the existing
    bmad-loop validate || exit 1 in CI is untouched. machine.py gains the clause that makes
    this well-defined: parse non-empty stdout whatever the exit code, and read the verdict from
    the document's own ok, which unlike rc separates "the checks failed" from "the command
    broke". The text output is byte-for-byte unchanged.

  • bmad-loop clean --json / bmad-loop cleanup --json (#204). Stable, schema-versioned
    JSON documents (one per command — they are separate contracts) reporting what a reclaim
    removed, or under --dry-run would remove: for clean the worktree paths, trimmed,
    archived, deleted and protected run ids, the effective retention policy, and freed_bytes
    as a raw integer — the text's ~1.2MB is a rendering of that number, and formatting is the
    renderer's job; for cleanup the run ids whose sessions went, the live ids left alone, and
    the ctl windows closed. Plan and outcome share one schema — same fields, same meanings, with
    dry_run saying which one you are holding — so a script can pre-flight and then compare
    against what actually happened. The values are each invocation's own sample rather than a
    promise the two agree: freed_bytes is re-measured, and the world can move between the
    preview and the commit. The real paths are now scriptable at all — they previously discarded
    the per-item data and printed only a summary line, and protected was a bare count. Both
    commands printed progress as they mutated, and both warned mid-loop about an unverifiable
    engine pid; under --json that warning becomes a document field, so stderr stays empty and
    stdout stays one pure document.

  • bmad-loop decisions --json (#203). A stable, schema-versioned JSON document of the
    pending deferred-work decisions, so a script can select an option by policy and pre-answer
    it rather than scraping the numbered text. It is strictly richer than that text, which drops
    each decision's context and shows only key/label/effect per option — hiding the intent,
    resolution and bundle_name that decide what the next sweep actually builds or writes.
    The recommendation, a (recommended) suffix on a free-text line in the text form, becomes a
    derived boolean on the option it names. --json implies the listing and never prompts (the
    interactive prompter reads stdin and cannot coexist with a pure document), and nothing
    pending is a valid empty document with exit 0.

  • bmad-loop list --json (#192). A stable, schema-versioned JSON document — one entry
    per run, oldest first (short ref, run id, type, started-at, liveness-aware status, paused
    stage) — replaces the text table when passed. Unparseable runs are included as status
    unknown, and an empty runs dir yields a valid empty document with exit 0.

  • bmad-loop status --json (#190). A stable, schema-versioned JSON document (run
    id/type/source, derived status + pause fields, snapshot cache_read_weight, raw +
    weighted token totals, and per-story phase/attempt/review-cycle/tokens/commit/defer
    reason) replaces the text output when passed. This is the supported machine-readable
    surface — the human text, whose layout #129 already changed once with no warning
    path, is now explicitly best-effort.

  • session-end journal entries carry tokens_weighted beside tokens (#129). Only the
    raw scalar was persisted, and the weight cannot be backed out of it, so a session's
    cost-weighted spend was unreconstructible after the fact — the weighted figure existed
    only for sessions that tripped the budget guard. Every entry whose usage was read now
    records both. null (never 0) when the usage read failed, since untracked is not free;
    both fields stay absent on an aborted end, where no read happened. Distinct from a
    tripped session's budget_weighted, which is the guard's mid-session sample at trip time
    rather than the end-of-session total. The cache_read_weight knob also gained a
    description in the TUI settings screen, where it had none.

  • Mid-session token-budget guard (#158). Both adapter wait loops now sample cumulative
    weighted usage every ~30s and act on crossing the new per-session cap
    (limits.max_tokens_per_session, default 4M weighted) per limits.session_budget_mode:
    warn = one ATTENTION + lifecycle breadcrumb; enforce = wrap-up nudge +
    limits.session_budget_grace_s (default 240s) to finish, then termination with the new
    over_budget session status, which rides the ordinary retry→defer routing. Defaults to
    warn: on upgrade, existing installs gain visibility (one ATTENTION line per over-cap
    session) but no terminations — set session_budget_mode = "enforce" to opt into the
    hard bound, or "off" to silence the guard entirely. Session-end
    journal entries carry budget_weighted/budget/budget_mode for tripped sessions.
    Live-verified on claude; other transcript-reading profiles sample best-effort, and
    adapters with no mid-session usage signal (usage_parser = "none", Copilot) stay inert.

  • OpenCode adapter (opencode-http profile, alias opencode). Drives
    OpenCode ≥ 1.18 entirely over HTTP/SSE — one headless
    opencode serve per session (no tmux window), SSE session.idle as the completion
    signal with an HTTP poll fallback, per-session server password, hermetic skills, and
    token usage read back over the API. Full dev/review synthesis parity via the new
    _ResultFileMixin/_DevSynthesisMixin seams in generic.py; profiles gained a
    hookless [hooks] dialect = "none" mode (no hook registration anywhere). Install the
    HTTP client with pip install 'bmad-loop[opencode]'; set model as provider/model.
    The pinned 1.18.2 API contract is recorded in the adapter's module docstring, guarded
    by a zero-token real-binary smoke test (tests/test_opencode_live.py, skipped when
    the binary is absent).

  • Native-Windows psmux multiplexer backend (experimental). A bundled builtin that
    drives runs on native Windows through psmux — a ConPTY tmux re-implementation that speaks
    the tmux CLI via its own psmux binary — so tmux's session/window model and the
    bmad-loop-<run-id>/bmad-loop-ctl session names carry over unchanged. It registers for
    win32 and is the platform default there, selected automatically when the psmux and
    pwsh binaries are on PATH and psmux reports newer than 3.3.6 (older releases can
    force-kill a recycled PID during teardown, so they read as unavailable and selection falls
    through). Native Windows stays experimental — window hosting, attach/detach mapping, and
    Unity cache-path correctness are tracked in the roadmap — but the dev→review→verify→commit
    loop and TUI observation run. WSL is unaffected (it is Linux and uses tmux). (#58)

  • Out-of-tree multiplexer backends (bmad_loop.mux_backends entry points). A backend
    package installed next to bmad-loop (e.g. uv tool install bmad-loop --with <adapter>) now
    registers itself with no config step: before every selection, core imports each module
    advertised under the bmad_loop.mux_backends entry-point group, whose import-time
    register_multiplexer(...) call makes the backend selectable exactly like a bundled one
    (builtins load first, so default selection is unchanged by installing an adapter). A package
    that fails to import can never break selection — the failure is r...

Read more

v0.8.1

Choose a tag to compare

@github-actions github-actions released this 06 Jul 06:18
9aefd40

Fixed

  • A session that finished its work but crashed before the run recorded it no longer loses that work
    to a restart-and-rollback.
    The engine now records a completed dev/review session behind a
    durability barrier before running post-session hooks (usage is folded in afterward as best-effort
    metadata); on resume it consumes that durably-recorded result straight into verify/decide instead of
    restarting the attempt from baseline. A raw KeyboardInterrupt now records a controlled stop, and
    replay preserves the attempt/cycle/baseline. (#62, closes #57)
  • Several narrower resume-replay edges opened by that durability work are closed. A host death in
    the post-session window of the last allowed review cycle no longer drops a recorded clean review to
    a defer; a reconcile early-return no longer persists a pre-reconcile dict that could silently skip a
    recommended follow-up review; and triage/sweep and labeled plugin-workflow sessions no longer persist
    large, never-consumed result payloads into state.json. (#63)
  • A dev session is now completed only on a real Stop or window death, never on a terminal artifact
    glimpsed while the agent window is still alive.
    The idle-tick and grace-expiry shortcuts could
    return "completed" mid-turn and let the run's cleanup kill a working agent, handing the engine
    half-written state. Both shortcuts are removed, liveness is re-probed immediately before the
    grace-expiry stall verdict, and a re-driven spec has its stale ## Auto Run Result section stripped
    on both re-arm paths so a resolved re-drive can't be misread as its own terminal result. Result scans
    are now fence-aware, so an ## Auto Run Result heading quoted inside a fenced code block is never
    mistaken for a real section (nor destructively stripped). (#53, closes #48 #52)
  • Long journal fields in the TUI now wrap with a hanging indent instead of spilling back under the
    timestamp and kind columns.
    Each row renders as a fixed-width grid whose fields cell folds within
    its own column.

Added

  • Attempt-preserve recovery refs are now bounded. With scm.rollback_on_failure on, auto-rollback
    parks a failed attempt's committed work under attempt-preserve/* and its dirty worktree snapshot
    under attempt-preserve-dirty/*; nothing pruned them, so they grew unbounded. Run start now keeps
    only the newest scm.preserve_keep (default 20, 0 = never prune) per family by committer date and
    best-effort-deletes the tail — a stuck ref never wedges the ones behind it, and prune failures are
    journaled but never block the run. (#50 #54, closes #32 #49)

Changed

  • The adapter shell-dialect seam is now an explicit, documented contract. new_window /
    new_parked_window factor their shell-dialect fragments into overridable hooks (POSIX output stays
    byte-identical), and the command parameter's semantics are pinned in the ABC docstring
    (shlex-joined argv; operator handling is backend-defined). Relevant only to authors porting the
    adapter to a non-POSIX backend. (#47 #60)

v0.8.0

Choose a tag to compare

@github-actions github-actions released this 03 Jul 18:54
648b407

Changed

  • BREAKING: the project is renamed bmad-autobmad-loop. The distribution, console script,
    and CLI are now bmad-loop; the Python package is bmad_loop (was automator); the BMAD module
    code and marketplace plugin are bmad-loop (was bauto); 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 group bmad_auto.pluginsbmad_loop.plugins; hook relays bmad_auto_hook.py /
    bmad_auto_probe_hook.pybmad_loop_hook.py / bmad_loop_probe_hook.py; skills
    /bmad-auto-{setup,sweep,resolve}/bmad-loop-{setup,sweep,resolve}; tmux session/window
    prefixes bmad-auto-*bmad-loop-*; worktree branches automator/<run-id>
    bmad-loop/<run-id>; TUI class BmadAutoAppBmadLoopApp. Custom plugins, CLI profiles, and
    policy files that reference any of these must be updated.

Added

  • New pre_commit_gate plugin 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 (so on_pre_commit fail-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 until session_timeout_min. The engine now appends an explicit
    completion contract (absolute marker path + frontmatter shape) to every workflow-session prompt,
    and a new limits.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 UNKNOWN instead of a
    false INTERRUPTED.
    psutil raises ERROR_ACCESS_DENIED for 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 on unknown without ever letting an unverifiable pid block cleanup forever, and resolve
    gains --force to 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.py normalizes
    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 (or bmad-loop init directly). init migrates a project in place:
    it strips the old .automator/ Stop hook from each CLI's settings, removes the bmad-auto-*
    skill dirs, and carries .automator/policy.toml over to .bmad-loop/policy.toml. Setup folds the
    old bauto config into bmad-loop and clears the leftover bauto config section, stale
    BMAD Automator Skills help 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

Choose a tag to compare

@github-actions github-actions released this 02 Jul 18:12
06ec378

Added

  • The TUI dashboard now shows the cost-proportional weighted token total, with the raw total in a new
    column.
    The tokens column 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 new raw column.

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 raised NoMatches. 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.
    With scm.rollback_on_failure on (or on a resolved re-drive), a deferred or stopped
    attempt's commits above baseline are now parked under an attempt-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 CRITICAL blocked escalation was re-driven and the
    re-drive couldn't reach status: 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. While resolved_redrive is latched, budget exhaustion now
    re-escalates (pauses for the human) instead of deferring, and the attempt's tree is preserved.
  • A resumed --epic N run stays scoped to its epic and no longer declares the epic "done" while
    stories remain.
    resume rebuilt 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

Choose a tag to compare

@github-actions github-actions released this 30 Jun 23:01
f79bd12

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.
    Exhausting limits.max_review_cycles previously
    always deferred + reverted — discarding completed, review-passing work (frontmatter status: done,
    verify green) whose only "failure" was a never-clearing followup_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

Choose a tag to compare

@github-actions github-actions released this 30 Jun 05:14
f32d922

Fixed

  • Completed work left at the transient in-review frontmatter is no longer falsely deferred and
    rolled back.
    A bmad-dev-auto session that dies in its step-04 Finalize tail can leave the spec
    frontmatter at the transient in-review marker while the ## Auto Run Result prose already says
    Status: done — the same stale-frontmatter gate bug as 0.7.8 with a different value. The 0.7.8
    reconcile skipped in-review to protect the legacy bmad-auto-dev review-handoff, but that fork
    is retired and in-review is now only ever a transient marker, so it is reconciled to done
    before the gates run. Every deterministic gate still runs afterward, and a followup_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

Choose a tag to compare

@github-actions github-actions released this 30 Jun 04:19
bfe7562

Fixed

  • The multiplexer seam no longer leaks raw subprocess timeouts. Every contract method now raises
    MultiplexerError/TmuxError or returns its documented sentinel instead of letting a 30 s tmux
    hang escape as a raw subprocess.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 false crashed.
  • An unexpected engine exception is now recorded instead of being lost to the parked control
    window.
    The orchestrator writes a run-crash journal line + a crash.txt traceback, sets a
    CRASHED run status, and tears down the orphaned agent session — rather than dying with a
    traceback printed only to the pruned control pane.

v0.7.8

Choose a tag to compare

@github-actions github-actions released this 30 Jun 00:49
ef7f77b

Fixed

  • Completed bmad-dev-auto work is no longer falsely deferred when the skill leaves the spec
    frontmatter status stale.
    The skill can finalize a run in its ## Auto Run Result prose
    (Status: done) yet leave the YAML frontmatter at the template default draft; 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 (journaled spec-status-reconciled). It reconciles
    only a done outcome 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

Choose a tag to compare

@github-actions github-actions released this 29 Jun 05:58
ec715d4

Fixed

  • Spec-status gates are now case- and whitespace-insensitive. A hand-edited spec whose
    frontmatter carried a stray-cased Done/In-Review silently failed the dev/review gate and the
    story never advanced; every spec-frontmatter status read now normalizes through a single
    verify.status_of helper. The well-formed lowercase path is unchanged. Also fixes the
    manual-rollback notice, which rendered an invalid git 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 a config_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_file and the
    resolve context's resolution_path were written with the host separator, so a state.json or
    context file produced on Windows read back with backslashes. Both now persist via as_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 atomic state.json rewrite 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 includes st_ino.
  • A dev session is no longer mis-stalled while it is actively working or legitimately waiting.
    Building on dev_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 to limits.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_min while a truly unresponsive one still stalls. 0 restores stall-on-grace-expiry.

v0.7.6

Choose a tag to compare

@github-actions github-actions released this 29 Jun 00:18
15c7b80

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, by sys.platform with a BMAD_AUTO_MUX_BACKEND
    override) rather than hardcoded in get_multiplexer(), and the tmux backend split into a reusable
    BaseTmuxBackend extension point — every tmux invocation funnels through one overridable _run()
    primitive — with TmuxMultiplexer as 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 ProcessHost seam. Politely-stop / force-kill /
    liveness / PID-reuse-identity now route through get_process_host() (registered like the
    multiplexer, with a BMAD_AUTO_PROCESS_HOST override); a WindowsProcessHost ships ready to
    register. Hook registration no longer hardcodes python3 — it takes the interpreter prefix from
    ProcessHost.hook_interpreter() (POSIX python3; Windows uv run --no-project python), and
    bmad-auto validate runs 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 ProcessHost and its helper
    scripts run under the orchestrator's own interpreter.
    Plugin helper scripts are spawned via
    sys.executable (not a PATH-resolved python3), so a bundled script may import automator seams;
    unity_teardown.py now reaps leaked Editor/MCP processes through get_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.