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

### Added

- **Mistral Vibe adapter profile (`mistral-vibe`, alias `vibe`), experimental.** Drives
Mistral's `vibe` CLI (≥ 2.23) through the generic tmux + hook-signal transport. Adds the
first non-JSON hook dialect, `vibe-hooks-toml`: `.vibe/hooks.toml` is a flat
`hooks = [...]` array of tables whose entries name their own event in a `type` field
rather than being keyed by it. Hook-config read/write now goes through a dialect-aware
`load_hook_config` / `dump_hook_config` seam in `install.py`; every JSON dialect emits
byte-identical output as before. `post_agent` is vibe's only turn-end event (no
SessionStart/SessionEnd/PreCompact) and fires per response turn, so the profile ships
`stop_without_result_nudges = 5`. Launches with `--trust`, which is mandatory — vibe
reads project hooks only in a trusted directory — and, being per-invocation rather than
an exact-path store, leaves `isolation = "worktree"` working. vibe exposes no `--model`
flag: leave `[adapter] model` empty and select the model via `VIBE_ACTIVE_MODEL`.
`usage_parser = "none"` pending a probe of `~/.vibe/logs/session/*/messages.jsonl`.

- **A park travels with its story's commit, so `bmad-loop confirm` works from any clone (#356).**
Each parked story now writes one committed JSON record to `.bmad-loop/operator/<key>.json`, inside
the story's own commit window, so the record rides the park's commit — through the worktree
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,7 @@ One generic driver (`adapters/generic.py`) runs any coding CLI that fits the inj
| `gemini` | supported, E2E-verified | Gemini CLI ≥ 0.46 (hooks on by default since then). Launches with `-i` to stay interactive; `AfterAgent` maps to canonical Stop. Usage parser validated against real chat logs. |
| `copilot` | supported, E2E-verified | GitHub Copilot **CLI** (the `copilot` binary, GA ≥ 2026-02) — _not_ the VS Code extension. Launches with `-i` to stay interactive; turn-end is `agentStop` (per response turn); `--allow-all-tools` for unattended runs. `copilot-events` usage parser reads token totals from the trailing `session.shutdown` line, so the profile waits a short grace (`usage_grace_s = 8`) before tallying. **Pin a capable model** (see below). |
| `antigravity` | experimental — `isolation = "none"` only | Google **Antigravity CLI** (`agy` ≥ 1.1.3). Launches with `-i` to stay interactive; `Stop` is the turn-end event (agy has no SessionStart/SessionEnd hook). Skills and hooks live in `.agents/` (flat `Stop` handler in `.agents/hooks.json`, keyed by hook-group name). Hook payloads are protojson/camelCase. **Trust is exact-path**: `agy` blocks on a "trust this folder" dialog for any workspace not listed verbatim in `settings.json` `trustedWorkspaces`, and `--dangerously-skip-permissions` does not bypass it — so `isolation = "worktree"` hangs ([#169](https://github.com/bmad-code-org/bmad-loop/issues/169)). `usage_parser = "none"` is permanent, not pending: agy's transcript carries no usage data (tokens live only in an internal SQLite/protobuf store), so runs work but token columns stay empty. Verify against your build with `probe-adapter antigravity`. |
| `mistral-vibe` | experimental (alias `vibe`) | **Mistral Vibe** (`vibe` ≥ 2.23). Hooks and skills live in `.vibe/` and `.agents/skills/`; `post_agent` is the only turn-end event (no SessionStart/SessionEnd/PreCompact) and it fires per response turn, so the profile ships `stop_without_result_nudges = 5`. The **only non-JSON hook dialect**: `.vibe/hooks.toml` is a flat `hooks = [...]` array (`vibe-hooks-toml`). **Trust is per-invocation**: vibe reads project hooks only in a trusted directory, so the profile launches with `--trust` — which, unlike `antigravity`, means `isolation = "worktree"` works. vibe has **no `--model` flag**: leave `[adapter] model` empty and select the model via `VIBE_ACTIVE_MODEL` or vibe's own config. `usage_parser = "none"` pending a probe — runs work, token columns stay empty. Verify against your build with `probe-adapter mistral-vibe`. |
| `opencode` | supported, E2E-verified | **OpenCode** ≥ 1.18 (profile `opencode-http`), driven over HTTP/SSE — one headless `opencode serve` per session, **no tmux window**. Needs the extra: `pip install 'bmad-loop[opencode]'`. Auth once globally with `opencode auth login`; skills live in `.claude/skills/`; set `model` as `provider/model` (e.g. `anthropic/claude-haiku-4-5`). Watch sessions via `run_dir/logs/<task_id>.log` or the TUI Log tab; `resolve` is `--no-interactive` only; the Unity plugin's window guards are unsupported here. |

**Copilot — pin a capable model:** Copilot's free default (GPT-5 mini) is unreliable for the multi-step dev/review skills — it silently skips steps mid-workflow and fails the story. Set a capable model in policy, e.g. `[adapter] model = "claude-sonnet-4-6"` (passed through as `--model`), for end-to-end reliability. Because Copilot fires `agentStop` per response turn, a thorough multi-turn review needs more than one nudge to finish; the profile ships `stop_without_result_nudges = 5`, and you can tune it per stage (e.g. `[adapter.review] stop_without_result_nudges = …`). Both knobs are editable in the settings TUI under `[adapter]`.
Expand All @@ -599,7 +600,7 @@ One generic driver (`adapters/generic.py`) runs any coding CLI that fits the inj

**Shared prerequisites:** the `bmad-loop-*` skills must be present in `.agents/skills/` (codex and gemini read it; Claude Code reads `.claude/skills/`), and each CLI must have been run once interactively in the project for auth/trust — `bmad-loop init --cli codex --cli gemini` installs the skills into `.agents/skills/`, registers the hook relay, and prints the per-CLI first-run steps.

**Adding a CLI without touching Python:** drop a TOML file in `<project>/.bmad-loop/profiles/<name>.toml` with at minimum a binary, `prompt_template`, bypass flags, and a `[hooks]` block picking one of the config dialects (`claude-settings-json` / `codex-hooks-json` / `gemini-settings-json` / `copilot-settings-json` / `antigravity-hooks-json`) plus a native→canonical event map. The full profile schema — every `CLIProfile` / `HookSpec` field and its default — lives in the **[Profile field reference](docs/adapter-authoring-guide.md#profile-field-reference)** of the adapter authoring guide, the single canonical home for it. The hook relay script and orchestrator are CLI-agnostic — each registration passes the canonical event name as the script argument. A CLI whose hook config clones one of the existing dialects (the ecosystem trend) needs nothing else; a genuinely different transport gets its own adapter class instead (see [Writing a new adapter class](docs/adapter-authoring-guide.md#writing-a-new-adapter-class) — the shipped opencode HTTP+SSE adapter in `adapters/opencode_http.py` is its worked example).
**Adding a CLI without touching Python:** drop a TOML file in `<project>/.bmad-loop/profiles/<name>.toml` with at minimum a binary, `prompt_template`, bypass flags, and a `[hooks]` block picking one of the config dialects (`claude-settings-json` / `codex-hooks-json` / `gemini-settings-json` / `copilot-settings-json` / `antigravity-hooks-json` / `vibe-hooks-toml`) plus a native→canonical event map. The full profile schema — every `CLIProfile` / `HookSpec` field and its default — lives in the **[Profile field reference](docs/adapter-authoring-guide.md#profile-field-reference)** of the adapter authoring guide, the single canonical home for it. The hook relay script and orchestrator are CLI-agnostic — each registration passes the canonical event name as the script argument. A CLI whose hook config clones one of the existing dialects (the ecosystem trend) needs nothing else; a genuinely different transport gets its own adapter class instead (see [Writing a new adapter class](docs/adapter-authoring-guide.md#writing-a-new-adapter-class) — the shipped opencode HTTP+SSE adapter in `adapters/opencode_http.py` is its worked example).

**Finalizing a profile:** the facts a profile needs that live in no doc — the CLI's exact hook payload shape, its transcript location/format, and the token schema a `usage_parser` reads — are collected and sanitized by `bmad-loop probe-adapter <cli>` (a zero-launch scan by default, or `--probe` for a live capture). The [adapter authoring guide](docs/adapter-authoring-guide.md) walks through using it end to end.

Expand Down
1 change: 1 addition & 0 deletions docs/FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ See [README.md](../README.md) for the narrative overview and [setup-guide.md](se
- 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`.
- Experimental: `mistral-vibe` (Mistral **Vibe** ≥ 2.23, alias `vibe`) — `post_agent` is the sole turn-end hook (no SessionStart/SessionEnd/PreCompact) and fires per response turn, so the profile ships `stop_without_result_nudges = 5`; skills read from `.agents/skills/`. Its `.vibe/hooks.toml` is the only **non-JSON** hook dialect (`vibe-hooks-toml`): a flat `hooks = [...]` array whose entries name their own event in a `type` field. vibe loads project hooks only in a **trusted** directory, so the profile launches with `--trust` — trust is per-invocation rather than an exact-path store, so `isolation = "worktree"` works (unlike `antigravity`). vibe exposes no `--model` flag: leave `[adapter] model` empty and pick the model via `VIBE_ACTIVE_MODEL` or vibe's config. `usage_parser = "none"` pending a probe of `~/.vibe/logs/session/*/messages.jsonl`. Verify against your build with `probe-adapter mistral-vibe`.
- 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).
- `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).
Expand Down
5 changes: 3 additions & 2 deletions docs/adapter-authoring-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,8 @@ Drop a TOML file in `<project>/.bmad-loop/profiles/<name>.toml` with the fields
from the [Profile field reference](#profile-field-reference) below. The minimum is
a `binary`, a `prompt_template`, bypass flags, a `[hooks]` block picking one of the
config dialects (`claude-settings-json` / `codex-hooks-json` /
`gemini-settings-json` / `copilot-settings-json` / `antigravity-hooks-json`) and
`gemini-settings-json` / `copilot-settings-json` / `antigravity-hooks-json` /
`vibe-hooks-toml`) and
a native→canonical event map, and a `usage_parser` (start with `"none"` until
you've written one).

Expand Down Expand Up @@ -425,7 +426,7 @@ resolves to `claude`.

| Field | Required | Meaning |
| ------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `dialect` | ✅ | The CLI's hook-config format — one of `claude-settings-json`, `codex-hooks-json`, `gemini-settings-json`, `copilot-settings-json`, `antigravity-hooks-json`. |
| `dialect` | ✅ | The CLI's hook-config format — one of `claude-settings-json`, `codex-hooks-json`, `gemini-settings-json`, `copilot-settings-json`, `antigravity-hooks-json`, `vibe-hooks-toml`. Every dialect is JSON except `vibe-hooks-toml`, whose `.vibe/hooks.toml` is a flat `hooks = [...]` array of tables — entries name their own event in a `type` field rather than being keyed by it. |
| `config_path` | ✅ | Project-relative path the hook config is written to (e.g. `.claude/settings.json`). Absolute paths are rejected. |
| `events` | ✅ | Map of **native** event name → **canonical** event name. The canonical side must be one of `SessionStart`, `Stop`, `SessionEnd`, `PreCompact`; the native side is whatever the CLI emits (e.g. `agentStop = "Stop"`). At least one entry. |

Expand Down
24 changes: 21 additions & 3 deletions docs/setup-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,8 @@ would not carry.
## Choosing which CLIs to drive

The supported adapters are `claude` (the default), `codex`, `gemini`, `copilot`,
`antigravity` (Google's `agy`, experimental — `isolation = "none"` only), and `opencode`
`antigravity` (Google's `agy`, experimental — `isolation = "none"` only),
`mistral-vibe` (Mistral's `vibe`, experimental), and `opencode`
(OpenCode ≥ 1.18 over HTTP/SSE, profile `opencode-http` — no tmux window; needs the
`bmad-loop[opencode]` extra and `model` set as `provider/model`). You can pick more
than one — register every CLI you intend to use for dev, review, or sweep triage.
Expand Down Expand Up @@ -234,7 +235,8 @@ bmad-loop init --project <project-root> --cli claude --cli codex --cli gemini

Run with no `--cli` and `init` registers hooks for every CLI the `policy.toml` references,
so a dual-client setup that's already configured in policy needs no extra flags. Names must
be exactly `claude`, `codex`, `gemini`, `copilot`, `antigravity`, or `opencode-http` (alias
be exactly `claude`, `codex`, `gemini`, `copilot`, `antigravity`, `mistral-vibe` (alias
`vibe`), or `opencode-http` (alias
`opencode`) — `init` errors on an unknown profile and lists the valid ones. A hookless
profile like `opencode-http` installs its skills but registers no hooks (it signals over
HTTP/SSE).
Expand Down Expand Up @@ -269,6 +271,22 @@ them to whoever owns the machine:
- **Token usage is not recorded** (`usage_parser = "none"`) — and this is permanent,
not a gap: agy's transcript carries no usage data at all (it counts tokens only in
an internal SQLite/protobuf store). Runs work; the token columns stay empty.
- **mistral-vibe** — run `vibe --setup` once and authenticate (Mistral API key) before
`bmad-loop run`; spawned sessions can't answer the setup prompt, and a pending one
reads as a session timeout. Requires Mistral Vibe ≥ 2.23. Three things to know,
verified against 2.23.2:
- **Trust gates the hooks.** vibe reads `<project>/.vibe/hooks.toml` only when the
working directory is trusted, so the profile launches with `--trust` (trusted for
that invocation only, never persisted). Without it no `Stop` ever arrives and every
session reads as a timeout. Because trust is per-invocation rather than an
exact-path store, `isolation = "worktree"` works fine here.
- **No `--model` flag.** Leave `[adapter] model` empty and select the model with the
`VIBE_ACTIVE_MODEL` env var or vibe's own `active_model` config; setting it in
policy passes an argument vibe's parser rejects.
- **Token usage is not yet recorded** (`usage_parser = "none"`) — unlike antigravity
this is pending rather than permanent: vibe writes a `messages.jsonl` per session
under `~/.vibe/logs/session/`, which a parser may be able to read. Runs work; the
token columns stay empty. Confirm with `bmad-loop probe-adapter mistral-vibe`.
- **opencode** — install the HTTP client extra (`pip install 'bmad-loop[opencode]'`) and
authenticate once, **globally**, with `opencode auth login` (not per-project — there is no
workspace-trust dialog to answer). Requires OpenCode ≥ 1.18. Set the model as
Expand All @@ -281,7 +299,7 @@ them to whoever owns the machine:

### Skill location

`claude` reads skills from `.claude/skills/`; `codex`, `gemini`, `copilot`, and `antigravity`
`claude` reads skills from `.claude/skills/`; `codex`, `gemini`, `copilot`, `antigravity`, and `mistral-vibe`
read from `.agents/skills/`. `init` installs the bundled `bmad-loop-*` skills into the right tree
for each CLI you pass via `--cli`, so selecting any of the `.agents/skills/` CLIs populates it automatically. It skips skill
dirs that already exist — pass `--force-skills` to overwrite a stale copy, or `--no-skills` to
Expand Down
Loading