Skip to content
Open
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
35 changes: 35 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,24 @@ breaking changes may land in a minor release.

### Changed

- **psmux sessions now live in a per-project registry** (#537). bmad-loop points
`PSMUX_DATA_DIR` at `<state root>/<project>/_mux`, so a prune in one project cannot address
another's servers at all. A bare `psmux ls` no longer shows them — `bmad-loop mux` prints the
root and the export that does. The `bmad-loop-ctl` control session becomes one per project —
named `bmad-loop-ctl-<registry digest>` on psmux, whose duplicate-server mutex is keyed on the
session name across every registry in a login session (a fixed name would fail every project's
launch but the first);
unchanged on tmux —
and `cleanup` also sweeps the old default registry for tagged pre-upgrade sessions, naming
whatever it still holds afterwards. The root is always derived: an ambient `PSMUX_DATA_DIR` is
overridden — reported once on stderr, and left alone for your own psmux sessions — because
honouring it would make the registry a function of the shell a command started in. Coding-CLI
windows are told the state root through their env; other windows inherit it as before, and
psmux's `PSMUX_BARE_ENV=1` — which breaks that inheritance — is declared unsupported and warned
about once per process. Cleanup demands the project tag
whenever the registry it addresses is not one bmad-loop derived, a shared default registry
included. The multiplexer seam signatures are unchanged; a backend built against them keeps
working.
- **A published run archive now lands at mode `0600`** instead of a umask-derived mode (#591).
It is staged through a file the orchestrator creates itself rather than one `tarfile` opens
by name, so it inherits the private mode the rest of the `.bmad-loop` write path uses.
Expand Down Expand Up @@ -85,6 +103,23 @@ breaking changes may land in a minor release.
`$HOME` one there was wrong in both directions — an empty seed that let global ignores leak
into `git add -A`, or patterns git is not applying that made session files go missing. Gated
on the reported version's own `.windows.` fork string, not on the platform.
- **`--run-id` refuses the reserved control-session shape — `ctl` and every `ctl-<anything>`, in
any letter case**.
Such an id mints the control session's own name as the run's agent session (`bmad-loop-ctl`,
or a per-registry `bmad-loop-ctl-<digest>` on psmux), so the adapter adopts the live control
session and the run's teardown kills it — every parked window with it; on Windows the
multiplexer resolves session names case-insensitively, so the case variants alias it too. The
two session namespaces are now disjoint at the mint — and guarded at the read paths for runs
an older release already persisted under such an id: `resume` and `resolve` refuse (at entry,
before any side effect) with the recovery steps, while `stop`, `delete`, `archive` and `clean`
work on the run without ever addressing a session that can be a control session's name (`ctl`,
`ctl-<16 hex>` — the narrower shape a control session's name can actually take). A historical
run under any other `ctl-*` id keeps its genuine agent session
reachable in the registry the process addresses: `stop` kills it there by its exact name, and
`cleanup` sweeps it like any other run's — including, for a tagged pre-upgrade session left in
a legacy registry, through the legacy pass (`stop` reaches no registry but the one this
process exported; the stop itself is registry-independent, only its backstop session kill is
scoped).
- **TUI: a graceful-stop request that cannot be written is reported, not fatal.** The `S`
worker caught only the helper's own refusals; an `OSError` from the write itself escaped,
and Textual's default `exit_on_error` took the dashboard down with it. It now surfaces
Expand Down
4 changes: 4 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ trunk check # lint + format verification on changed files, as CI does
trunk check --all # the whole repo — catches files your change didn't touch
```

The checkout uses CRLF while Prettier is configured for LF. A direct local
`prettier --check` can therefore disagree with CI; CI checks LF-normalized
content, so use the trunk commands above for the authoritative result.

### CHANGELOG

**Every user-visible change needs a CHANGELOG entry.** Add it under the `## [Unreleased]` heading in [CHANGELOG.md](CHANGELOG.md), and only under one of the six [Keep a Changelog](https://keepachangelog.com) subsections — `Added`, `Changed`, `Deprecated`, `Removed`, `Fixed`, `Security`. Keep entries terse, scannable, and imperative.
Expand Down
16 changes: 8 additions & 8 deletions README.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions docs/FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ See [README.md](../README.md) for the narrative overview and [setup-guide.md](se
- Read-only observer + launcher (`bmad-loop tui`): runs table, expandable sprint tree (epics → stories/retro), severity-colored deferred-work ledger, per-story phase table (phase · agent · dev attempts · review cycles · tokens · commit/defer), a run header naming the live-or-configured active agent, tabs tailing journal / pane log / `ATTENTION`.
- Launch & manage from keys: start run/sweep (`r`/`s`), resume (`e`), resolve escalation (`R`), answer missed decisions (`d`), attach (`a`), cleanup (`c`), validate (`v`), settings editor (`g`), theme/mode toggle (`M`), quit (`q`).
- Resizable panes: every boundary is drag-adjustable by mouse (the divider bars double as the Sprint / Deferred Work section headings) or a `ctrl+w` keyboard resize mode; sizes persist per-project to `[tui]` in `policy.toml` and re-apply on the next launch.
- Survives TUI exit/crash: runs launched from the TUI are detached `bmad-loop` processes in a dedicated `bmad-loop-ctl` tmux session; the dashboard watches purely via run-dir artifacts, so shell-started runs appear identically.
- Survives TUI exit/crash: runs launched from the TUI are detached `bmad-loop` processes in a dedicated `bmad-loop-ctl` tmux session (per-project name suffix on psmux); the dashboard watches purely via run-dir artifacts, so shell-started runs appear identically.
- Comment-preserving policy editor (`g`): grouped form, sections collapsed by default with one-line descriptions (`ctrl+e` toggles all), validated with the engine's own parser, unset keys show defaults as placeholders.

### tmux session management
Expand Down Expand Up @@ -212,7 +212,7 @@ See [README.md](../README.md) for the narrative overview and [setup-guide.md](se

- `bmad-loop init` — install skills, hooks, policy, gitignore.
- `bmad-loop validate` — preflight all prerequisites. `--json` instead emits a stable machine-readable document (schema-versioned; the `ok` verdict, the queue `mode`/`spec_folder`, per-severity `counts`, and every check as a flat emission-ordered finding with a stable `check` id, `severity`, human `message` and structured `detail`) per the [contract below](#machine-readable-output---json); a failing check still emits the whole document, at exit 1 — the nonzero code is the verdict, not a failure to produce one.
- `bmad-loop mux` — list registered terminal-multiplexer backends (platform · availability · version · which is selected and why; a backend whose binary is present but crashed the version probe gets a `warning:` on stderr carrying the probe's own failure, since the `-` in the VERSION column cannot tell that apart from a binary that reports no version); `mux set <name>` persists a machine-scoped choice into policy.toml (`--clear` reverts to auto, `--force` allows a name only registered on the target machine). Bundled backend: `tmux`; external backends (e.g. the herdr adapter) register via the `bmad_loop.mux_backends` entry-point group — see [Terminal multiplexer backends](multiplexer-backends.md).
- `bmad-loop mux` — list registered terminal-multiplexer backends (platform · availability · version · which is selected and why; a backend whose binary is present but crashed the version probe gets a `warning:` on stderr carrying the probe's own failure, since the `-` in the VERSION column cannot tell that apart from a binary that reports no version); on a backend with a registry namespace it also prints the **registry root** this project's sessions live in plus the export that reaches them from a bare client — on psmux that is `<state root>/<project>/_mux`, so a plain `psmux ls` shows none of them and says so rather than erroring ([#537](https://github.com/bmad-code-org/bmad-loop/issues/537)); `mux set <name>` persists a machine-scoped choice into policy.toml (`--clear` reverts to auto, `--force` allows a name only registered on the target machine). Bundled backend: `tmux`; external backends (e.g. the herdr adapter) register via the `bmad_loop.mux_backends` entry-point group — see [Terminal multiplexer backends](multiplexer-backends.md).
- `bmad-loop adapters` — list registered coding-CLI adapter **kinds** (name · builtin/external · whether the family drives a multiplexer · which profiles select it), the CLI axis's counterpart to `mux`. Unlike `mux` there is no global choice to persist: a kind is selected per profile by its `adapter` field. A profile referencing an unregistered kind, and any out-of-tree adapter/profile package that failed to load, get a `warning:` on stderr; `validate` reports the same as `adapter.kind` / `adapter.external` / `adapter.external-profile`.
- `bmad-loop run` — drive the dev → review → verify → commit loop.
- `bmad-loop sweep` — triage + execute open deferred-work entries.
Expand Down
49 changes: 41 additions & 8 deletions docs/adapter-authoring-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,30 @@ fragments) — or implement
seams of a full OS port are in
[Porting bmad-loop to a new OS](porting-to-a-new-os.md). The contract groups into:

- **Sessions** — `has_session`, `new_session` (geometry is optional: agent
sessions pin a fixed pane size because they are observed while detached; the
control session omits it), `kill_session`, `list_sessions`, `session_options`
(read a user option across all sessions), `set_session_option`.
- **Sessions** — `has_session`, `new_session` (geometry is
optional: agent sessions pin a fixed pane size because they are observed
while detached; the control session omits it),
`kill_session`, `list_sessions`, `session_options` (read a user option
across all sessions), `set_session_option`.

One session method carries a default the seam cannot verify for your
transport: `session_name_key(name)`, the canonical comparison key — two
names denote the same live session exactly when their keys are equal. The
default is identity (exact comparison — correct for tmux, whose session
names are case-sensitive). **If your transport resolves session names
case-insensitively — or folds them any other way — you MUST override**
(psmux does: its NTFS port-file store opens names case-insensitively).
With the inherited identity key, a case-variant of the control session's
name is not discounted by the removal guard, so the documented recovery
`bmad-loop delete ctl` wedges behind a false live-session refusal on a
persisted `CTL` run.

- **Windows** — `new_window` (run a command in a fresh window), `new_parked_window`
(run a command, then _park_ on a keypress so the exit status stays inspectable,
then return any attached client to its origin — the POSIX `sh -c` recipe is
composed from the base's overridable shell-dialect hooks, so a non-POSIX
backend swaps the dialect fragments, not the method body), `list_window_ids`
(run a command, then _park_ on a keypress so the exit
status stays inspectable, then return any attached client to its origin — the
POSIX `sh -c` recipe is composed from the base's overridable shell-dialect
hooks, so a non-POSIX backend swaps the dialect fragments, not the method
body), `list_window_ids`
(which MUST emit the same id form your `new_window` returns — `window_alive` is
a membership test over it, so qualifying one side and not the other reads every
live window as dead), `list_windows` (selected fields per window),
Expand All @@ -106,6 +121,24 @@ seams of a full OS port are in
`available` (is this backend usable on the current host), `version` (the
binary's version string or `None` — **one bounded line**, folded with
`fold_version()`; see [the porting guide](porting-to-a-new-os.md)).
- **Registry namespace** (optional; all three default to "no namespace", so a
transport without one writes nothing) — `has_registry_namespace` (a property
of the transport: does it namespace sessions by registry at all? Answer
`True` on every instance if it does, even when no root is currently in
force: cleanup uses it to tell "no namespace exists" from "running on the
transport's shared default registry", and only the first lets an untagged
session be claimed on run-directory evidence — a namespaced backend that
leaves this at the inherited `False` keeps the pre-namespace historical
reach, which on a shared registry can kill another project's session),
`registry_root` (the root your verbs
currently resolve targets through, for `bmad-loop mux` to disclose; `None`
from a namespaced backend means "no root in force" — the shared default —
not "no namespace") and
`legacy_registries` (instances bound to roots your sessions may predate, for
the cleanup sweep). Only for a transport that addresses sessions through a
directory of per-session files, as psmux does through `PSMUX_DATA_DIR`; the
rules are in [the porting
guide](porting-to-a-new-os.md#registry-namespaces-only-if-your-transport-has-one).

**Both client verbs report effect, not dispatch.** They answer what the
parked-window return path trusts — a bool from `detach_client`, a tri-state
Expand Down
Loading