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
24 changes: 12 additions & 12 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Things that break silently. Never violate; when in doubt, read the named module'

## Architecture

Two orthogonal seams: **which CLI** (adapter axis: `adapters/base.py` `CodingCLIAdapter`, TOML profiles in `src/bmad_loop/data/profiles/`, user overlay `.bmad-loop/profiles/*.toml` — a new coding CLI is a TOML profile plus `bmad-loop probe-adapter`, not Python) and **which transport** (mux axis: `adapters/multiplexer.py` `TerminalMultiplexer` registry; selection: `BMAD_LOOP_MUX_BACKEND` env > policy `[mux] backend` > platform default > first available match > fallback — full 5-step precedence in [docs/multiplexer-backends.md](docs/multiplexer-backends.md)).
Two orthogonal seams: **which CLI** (adapter axis: `adapters/base.py` `CodingCLIAdapter`, TOML profiles in `src/bmad_loop/data/profiles/`, user overlay `.bmad-loop/profiles/*.toml` — a new coding CLI is a TOML profile plus `bmad-loop probe-adapter`, not Python; a CLI needing its own adapter **class** registers one in `adapters/registry.py` (`register_adapter`, `bmad_loop.adapters` entry point), selected by the profile's `adapter` field) and **which transport** (mux axis: `adapters/multiplexer.py` `TerminalMultiplexer` registry; selection: `BMAD_LOOP_MUX_BACKEND` env > policy `[mux] backend` > platform default > first available match > fallback — full 5-step precedence in [docs/multiplexer-backends.md](docs/multiplexer-backends.md)).

| Module | Role |
| ------------------------------------------ | ------------------------------------------------------------------------------------------- |
Expand Down Expand Up @@ -79,16 +79,16 @@ These rules apply to code you are already touching — do not initiate refactors

## Docs index

| Doc | Read when |
| ------------------------------------------------------------------ | -------------------------------------------- |
| [docs/setup-guide.md](docs/setup-guide.md) | installing/initializing a target project |
| [docs/FEATURES.md](docs/FEATURES.md) | any behavior or policy question |
| [docs/tui-guide.md](docs/tui-guide.md) | TUI work |
| [docs/adapter-authoring-guide.md](docs/adapter-authoring-guide.md) | adding/finalizing a coding-CLI profile |
| [docs/multiplexer-backends.md](docs/multiplexer-backends.md) | mux backend selection/porting |
| [docs/plugin-authoring-guide.md](docs/plugin-authoring-guide.md) | plugin work (incl. game-engine + TEA guides) |
| [docs/porting-to-a-new-os.md](docs/porting-to-a-new-os.md) | OS seams |
| [docs/testing.md](docs/testing.md) | writing/placing tests, guards, flake policy |
| [docs/ROADMAP.md](docs/ROADMAP.md) | planned vs deliberately-deferred work |
| Doc | Read when |
| ------------------------------------------------------------------ | ------------------------------------------------------- |
| [docs/setup-guide.md](docs/setup-guide.md) | installing/initializing a target project |
| [docs/FEATURES.md](docs/FEATURES.md) | any behavior or policy question |
| [docs/tui-guide.md](docs/tui-guide.md) | TUI work |
| [docs/adapter-authoring-guide.md](docs/adapter-authoring-guide.md) | adding/finalizing a coding-CLI profile or adapter class |
| [docs/multiplexer-backends.md](docs/multiplexer-backends.md) | mux backend selection/porting |
| [docs/plugin-authoring-guide.md](docs/plugin-authoring-guide.md) | plugin work (incl. game-engine + TEA guides) |
| [docs/porting-to-a-new-os.md](docs/porting-to-a-new-os.md) | OS seams |
| [docs/testing.md](docs/testing.md) | writing/placing tests, guards, flake policy |
| [docs/ROADMAP.md](docs/ROADMAP.md) | planned vs deliberately-deferred work |

Full list: [docs/README.md](docs/README.md).
47 changes: 47 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,17 @@ whose seams had diverged enough that several ports needed a different fix, and t

### Added

- **Coding-CLI adapter registry: a new adapter class ships out-of-tree (#226).** The transport axis
has long been extensible out-of-tree; the CLI axis had no equivalent, so a CLI needing its own
adapter _class_ forced a name-branch in the run bootstrap. A profile's new `adapter` field names a
kind resolved against `adapters/registry.py`, and a co-installed package registers its own kind
via the `bmad_loop.adapters` entry point and the profile that selects it via `bmad_loop.profiles`
— with no core edit. `bmad-loop adapters` lists the registered kinds and which profiles select
them; `validate` gains `adapter.kind` (checked against the live registry, never a hardcoded set)
plus `adapter.external` / `adapter.external-profile` warnings. A broken third-party package
degrades to a recorded, surfaced reason and can never break selection. `opencode-http` is migrated
to a registered builtin behind a dispatch-unchanged regression pin.

- **docs/testing.md: the formal testing strategy.** Layer taxonomy and placement rules, fixture
and ablation doctrine, the quality-guard inventory, zero-token and flake policy, and a tracked
gap register (#545–#549); AGENTS.md, docs/README.md and CONTRIBUTING.md link here.
Expand Down Expand Up @@ -123,6 +134,42 @@ whose seams had diverged enough that several ports needed a different fix, and t

### Changed

- **The mid-run config pin covers the adapter kind (#461).** `adapter` selects which argv builder
runs at all, so it joins the `config_digest` launch payload — a driven session rewriting it now
moves the pin the auto-triggered child sweep gates on, instead of swapping the whole launch shape
underneath it. The digest resolves the kind from the profile bytes it was handed, not a second
read.

- **`validate`'s httpx and model-format checks key on the adapter kind, not hooklessness.** `httpx`
is the `opencode-http` family's optional extra and `provider/model` is its server's config-file
spelling; both are facts about one adapter class, not about whether a profile registers hooks.
With the transport and driving class now separate axes, a hookless profile driven by another kind
no longer FAILs with a remedy that installs the wrong package, nor draws a `policy.model-qualified`
warning naming a convention it does not use — and an `opencode-http` profile carrying a hook
dialect now gets the model warning it always needed.

- **A profile written before the `adapter` field keeps its old dispatch.** `hooks.dialect = "none"`
used to be the class selector, so a project overlay copied from the packaged opencode profile
carries no `adapter` key; it now resolves to `opencode-http` rather than defaulting onto the tmux
generic adapter, where it would have waited out `session_timeout_min` for a hook a hookless profile
never registers. An explicit `adapter` is always honored, including hookless driven by another kind.

- **A hookless profile can no longer select the `generic` adapter.** `generic` completes on a Stop
hook and `dialect = "none"` means none is ever registered, so the pair described a session that
could only wait out `session_timeout_min` against a CLI that never exits — with `validate` green.
Both routes into the profile map now refuse it: a TOML file naming the pair outright, and an
entry-point provider that builds a hookless profile while leaving `adapter` at its default.
Hookless on any other kind stays legal — that decoupling is what the registry is for.

- **Profiles from a `bmad_loop.profiles` entry point are validated like TOML ones.** Both routes into
the profile map now share one invariant set (hook dialect, path containment, `env_fault_patterns`
compilation, …), so a package can no longer install a profile state the parser would refuse — an
invalid env-fault regex used to trade a load-time error for a silent never-match at classification
time. A malformed `adapter` value funnels into `ProfileError` rather than being `str()`-coerced,
and `name`/`binary`/`adapter` must arrive already canonical — the TOML route strips them, so a
provider handing over `" acme "` would otherwise install a profile filed under a key no `--cli`
resolves, with the provider itself recorded as fine.

- **Lint the workflows, and smoke-test the built package.** `trunk check` now runs `actionlint` and
`zizmor` over `.github/workflows/`, and a `build` CI job builds the sdist + wheel, runs
`bmad-loop --version` from the installed wheel, and checks that wheel carries every data file
Expand Down
5 changes: 3 additions & 2 deletions docs/FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,13 +139,13 @@ See [README.md](../README.md) for the narrative overview and [setup-guide.md](se

### Multi-CLI / multi-agent support

- Generic adapter drives any CLI fitting the injection + hook-signal transport; CLI specifics live in declarative TOML profiles. Two independent axes: the **CLI** (`CodingCLIAdapter` + profile) and the **terminal transport** (`TerminalMultiplexer`) — tmux ships bundled (with an experimental native-Windows `psmux` backend alongside it), and external backends (e.g. the [herdr adapter](https://github.com/pbean/bmad-loop-adapter-herdr)) co-install as packages that self-register ([how](multiplexer-backends.md)), behind a pluggable seam that lets a new backend slot in without touching the engine (see the [adapter authoring guide](adapter-authoring-guide.md#two-axes-cli-vs-transport)).
- Generic adapter drives any CLI fitting the injection + hook-signal transport; CLI specifics live in declarative TOML profiles. Two independent axes: the **CLI** (`CodingCLIAdapter` + profile) and the **terminal transport** (`TerminalMultiplexer`) — tmux ships bundled (with an experimental native-Windows `psmux` backend alongside it), and external backends (e.g. the [herdr adapter](https://github.com/pbean/bmad-loop-adapter-herdr)) co-install as packages that self-register ([how](multiplexer-backends.md)), behind a pluggable seam that lets a new backend slot in without touching the engine. The CLI axis has the same seam: a new adapter **class** registers via `register_adapter` and arrives through the `bmad_loop.adapters` entry-point group, with its selecting profile through `bmad_loop.profiles` — so an out-of-tree adapter family needs no core edit either (see the [adapter authoring guide](adapter-authoring-guide.md#two-axes-cli-vs-transport)).
- The OS is abstracted by a **registry of seams**, each selecting an implementation by platform (with a test-override env var) and extended by a single registration line: the terminal multiplexer (`register_multiplexer`, with availability-aware selection: env var → persisted `[mux] backend` via `bmad-loop mux set` → platform default → first available platform match), the process-lifecycle `ProcessHost` (`register_process_host` — `terminate`/`force_kill`/`is_alive`/`identity`), and the hook interpreter (`ProcessHost.hook_interpreter()`); `bmad-loop validate` runs a platform preflight over them. Porting to a new OS is new files + registrations, no core edits — see [Porting bmad-loop to a new OS](porting-to-a-new-os.md).
- Supported, E2E-verified: `claude` (reference), `codex` (≥ 0.139), `gemini` (≥ 0.46), `copilot` (GitHub Copilot CLI ≥ 2026-02 — the `copilot` binary, not the VS Code extension; `agentStop` turn-end, `-i` interactive launch, `--allow-all-tools`; pin a capable model — the free GPT-5 mini default is unreliable for multi-step skills).
- Supported, E2E-verified over HTTP/SSE (no tmux window): `opencode` (OpenCode ≥ 1.18, profile `opencode-http`, alias `opencode`) — one headless `opencode serve` per session, SSE `session.idle` completion with an HTTP poll fallback, per-session server password, token usage read back over the API. Hookless (`[hooks] dialect = "none"`, no hook registration). With no pane to replay, the run logs split three ways: a curated readable transcript in `logs/<task-id>.log` (agent/user prose, tool calls, slash commands, file edits, permission asks/replies, errors), the server's own stdout in `<task-id>.server.out`, and a structured SSE trace in `<task-id>.sse.jsonl`. Install the extra (`pip install 'bmad-loop[opencode]'`), auth once globally (`opencode auth login`), and set `model` as `provider/model`; the Unity plugin's window guards don't apply (there is no window).
- Experimental, `isolation = "none"` only: `antigravity` (Google's `agy` ≥ 1.1.3) — `-i` interactive launch, `Stop` turn-end hook (flat handler in `.agents/hooks.json`, no SessionStart/SessionEnd), `--dangerously-skip-permissions` for unattended runs; `usage_parser = "none"` permanently — agy's transcript exposes no usage data (tokens live only in an internal SQLite/protobuf store). `agy` gates each workspace on an exact-path `trustedWorkspaces` entry and blocks on an interactive trust dialog, which `--dangerously-skip-permissions` does not bypass — so worktree isolation hangs ([#169](https://github.com/bmad-code-org/bmad-loop/issues/169)). Verify against your `agy` build with `probe-adapter antigravity`.
- Per-stage CLI/model overrides: run dev on one CLI/model, review on another (`[adapter.dev]`, `[adapter.review]`, `[adapter.triage]`).
- Add a CLI without touching Python: drop a TOML profile in `.bmad-loop/profiles/<name>.toml` (binary, prompt template, bypass flags, hook dialect, native→canonical event map).
- Add a CLI without touching Python: drop a TOML profile in `.bmad-loop/profiles/<name>.toml` (binary, prompt template, bypass flags, hook dialect, native→canonical event map). A CLI that needs its own adapter _class_ still needs Python — but not a core edit: the profile's `adapter` field names a kind resolved against the registry, which a co-installed package extends.
- `bmad-loop probe-adapter` collects + sanitizes the data needed to finalize/add a profile (hook payload shape, transcript location/format, token schema): a zero-launch scan by default, opt-in `--probe` for live capture. See the [adapter authoring guide](adapter-authoring-guide.md).

### Budgeting & cost tracking
Expand Down Expand Up @@ -194,6 +194,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 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.
- `bmad-loop resume <run-id>` — continue a paused/interrupted run.
Expand Down
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ guides below go deeper, roughly in the order you'll need them.

## Extending bmad-loop

- **[Finalizing a CLI adapter profile](adapter-authoring-guide.md)** — using `bmad-loop probe-adapter` to collect + sanitize the hook payload shape, transcript location, and token schema a new CLI profile needs.
- **[Authoring CLI adapters & profiles](adapter-authoring-guide.md)** — using `bmad-loop probe-adapter` to collect + sanitize the hook payload shape, transcript location, and token schema a new CLI profile needs, plus the `CodingCLIAdapter` ABC and how an adapter class (and the profile selecting it) ships out-of-tree.
- **[Writing a bmad-loop plugin](plugin-authoring-guide.md)** — the plugin system: `plugin.toml` manifest, hooks, lifecycle stages, settings, the trust model, and workflow injection, with a worked walkthrough.
- **[Writing a Game Engine plugin](game-engine-plugin-guide.md)** — the game-engine layer (built on the plugin system): driving a live engine Editor, the `editor_mode` ↔ `[scm] isolation` coupling, a minimal Godot example.
- **[Writing a plugin for a specific Editor MCP](game-engine-mcp-guide.md)** — Editor-MCP specifics for the bundled Unity plugin: IvanMurzak vs CoplayDev, readiness probes, `per_worktree` isolation, and the full `BMAD_LOOP_*` env-var reference.
Expand Down
Loading