From 2d14a90b38ec68dcc699b293d29e44382ef03247 Mon Sep 17 00:00:00 2001 From: VaporFlow Date: Mon, 13 Jul 2026 03:38:59 -0300 Subject: [PATCH 1/2] Add Cursor Agent CLI profile for unattended bmad-loop runs. Ship a built-in `cursor` profile using headless `--print`/`--trust`, the Copilot hooks dialect at `.cursor/hooks.json`, and SKILL.md-by-path prompts. Document SessionEnd-based completion, probe transcript discovery, and E2E coverage for review, stop/resume, worktrees, and sweeps. Co-authored-by: Cursor --- CHANGELOG.md | 12 +++++++ README.md | 13 ++++--- docs/FEATURES.md | 4 +-- docs/setup-guide.md | 11 ++++-- src/bmad_loop/cli.py | 4 +-- src/bmad_loop/data/profiles/cursor.toml | 36 +++++++++++++++++++ .../data/skills/bmad-loop-setup/SKILL.md | 10 +++--- .../bmad-loop-setup/scripts/cleanup-legacy.py | 2 +- src/bmad_loop/policy.py | 2 +- src/bmad_loop/probe.py | 3 ++ tests/test_probe.py | 2 +- tests/test_profile.py | 22 +++++++++++- 12 files changed, 98 insertions(+), 23 deletions(-) create mode 100644 src/bmad_loop/data/profiles/cursor.toml diff --git a/CHANGELOG.md b/CHANGELOG.md index 86f1121d..bb4e245f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,18 @@ breaking changes may land in a minor release. ### Added +- **Cursor Agent CLI profile (`cursor`).** Built-in TOML profile for the Cursor + Agent CLI (`cursor-agent` / `agent`): unattended `--print` + `--force` + + `--trust` + `--approve-mcps` (interactive `--trust` is rejected), skills in + `.agents/skills/`, hooks in `.cursor/hooks.json` via the Copilot settings + dialect, SKILL.md-by-path prompt template (no slash expansion), and + `usage_parser = "none"` until transcripts expose tokens. Completion is + SessionEnd + window-death β€” `stop` is registered but was not observed in + E2E. Probe discovery glob: + `~/.cursor/projects/*/agent-transcripts/**/*.jsonl`. E2E-verified for sprint + runs, forced follow-up review, stop/resume, worktree isolation, and deferred + sweeps. + - **Unity modal-dialog guards (`[plugins.unity]`).** A chronically-dirty Unity scene raises modal Editor dialogs ("scene changed on disk", "save changes before closing") that freeze the MCP dispatch loop and stall the whole run. The bundled Unity plugin now defends in depth: it seeds an diff --git a/README.md b/README.md index ff8c1c3f..625a8426 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Plain Python drives the loop β€” **pick story β†’ implement β†’ adversarially re [![CI](https://github.com/bmad-code-org/bmad-loop/actions/workflows/ci.yml/badge.svg)](https://github.com/bmad-code-org/bmad-loop/actions/workflows/ci.yml) ![Python](https://img.shields.io/badge/python-3.11%E2%80%933.14-blue) -![CLIs](https://img.shields.io/badge/agents-claude%20%C2%B7%20codex%20%C2%B7%20gemini%20%C2%B7%20copilot%20%C2%B7%20antigravity-8a2be2) +![CLIs](https://img.shields.io/badge/agents-claude%20%C2%B7%20codex%20%C2%B7%20gemini%20%C2%B7%20copilot%20%C2%B7%20antigravity%20%C2%B7%20cursor-8a2be2) ![No LLM in the loop](https://img.shields.io/badge/control%20loop-deterministic-success) ![License: MIT](https://img.shields.io/badge/license-MIT-green) @@ -33,12 +33,12 @@ Inspired by the original [bmad-automator](https://github.com/bmad-code-org/bmad- - πŸ” **Trust nothing, verify everything.** After each session the orchestrator checks artifacts on disk: spec frontmatter status, baseline-commit match (recorded independently β€” a cheap LLM-lie detector), non-empty diff, sprint-status sync, and _your_ test/lint commands before any commit. - πŸ“’ **One source of truth.** `sprint-status.yaml` is owned by the BMAD skills; the orchestrator only ever reads it. - πŸͺŸ **Fresh context per step.** Dev and review are separate sessions β€” review never inherits the implementer's context, so there's no anchoring bias. -- ♻️ **Resumable & multi-agent.** Every run is a resumable state machine on disk, and a generic tmux adapter drives `claude`, `codex`, `gemini`, `copilot`, or `antigravity` (mix per stage). +- ♻️ **Resumable & multi-agent.** Every run is a resumable state machine on disk, and a generic tmux adapter drives `claude`, `codex`, `gemini`, `copilot`, `antigravity`, or `cursor` (mix per stage). - 🌿 **Optional worktree isolation.** Opt in (`[scm] isolation = "worktree"`) and each story runs in its own git worktree/branch and merges back locally β€” your main checkout stays free while a run is in flight. ## Requirements -- **Python 3.11+**, **tmux**, and a supported coding CLI β€” `claude` by default; `codex`, `gemini`, `copilot`, and `antigravity` (`agy`) via [profiles](#other-coding-clis). +- **Python 3.11+**, **tmux**, and a supported coding CLI β€” `claude` by default; `codex`, `gemini`, `copilot`, `antigravity` (`agy`), and `cursor` (`cursor-agent`) via [profiles](#other-coding-clis). - **Linux or macOS** (or **Windows via WSL**, which _is_ Linux β€” it runs as-is). tmux is the one bundled terminal-multiplexer backend today, but it sits behind a pluggable **registry** of OS seams (transport, process lifecycle, hook interpreter) with availability-aware selection β€” env var β†’ persisted `[mux] backend` choice (`bmad-loop mux set `) β†’ platform default (`psmux` on Windows, `tmux` elsewhere) β†’ first available platform match β€” so a native-Windows backend slots in as new files + a registration line each, with no engine edits β€” see [Porting bmad-loop to a new OS](docs/porting-to-a-new-os.md). Native Windows is not yet shipped. - A **BMAD v6 project** (`_bmad/bmm/config.yaml`, a `sprint-status.yaml` from `bmad-sprint-planning`) with the upstream `bmad-dev-auto` skill (and the three review-hunter skills its step-04 invokes inline: `bmad-review-adversarial-general`, `bmad-review-edge-case-hunter`, `bmad-review-verification-gap`) and the bmad-loop skill module from this repo installed (`bmad-loop-resolve`, `bmad-loop-sweep` β€” see [Installing the skill module](#installing-the-skill-module)). Standard BMAD skills stay untouched. @@ -345,7 +345,7 @@ trigger = "recommended" # when enabled: "recommended" runs the separate revie # (the loop is bounded by limits.max_review_cycles either way) [adapter] -name = "claude" # CLI profile: claude | codex | gemini | copilot | antigravity | custom +name = "claude" # CLI profile: claude | codex | gemini | copilot | antigravity | cursor | custom model = "" # empty = CLI default cleanup_session_on_finish = true # kill the run's tmux session when it finishes (false keeps it for inspection) # extra_args replaces the profile's default bypass flags when set: @@ -502,19 +502,18 @@ 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 β€” probe-required | Google **Antigravity CLI** (`agy` β‰₯ 1.0.16). 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`). `usage_parser = "none"` until a token parser is written. Verify against your build with `probe-adapter antigravity`. | +| `cursor` | supported, E2E-verified | Cursor **Agent CLI** (`cursor-agent` / `agent`). Unattended path is `--print` + `--force` + `--trust` + `--approve-mcps` (`--trust` is rejected without `--print`). Skills in `.agents/skills/`; hooks in `.cursor/hooks.json` via the Copilot settings dialect. `sessionStart`/`sessionEnd` fire; `stop` is registered but was not observed in E2E β€” completion uses SessionEnd + window-death. No slash expansion of the initial prompt β€” SKILL.md is loaded by path (Copilot-style). `usage_parser = "none"` (transcripts under `~/.cursor/projects/*/agent-transcripts/` have no token fields yet). | **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]`. **On budgets:** agentic sessions are dominated by cache reads (80–90%+ of raw tokens), which every supported vendor bills at ~0.1x base input. The `max_tokens_per_story` check therefore uses a cost-weighted total β€” cache reads count at `limits.cache_read_weight` (default 0.1) β€” while displayed totals stay raw. Set the weight to 1.0 to budget raw tokens. -**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. +**Shared prerequisites:** the `bmad-loop-*` skills must be present in `.agents/skills/` (codex, gemini, copilot, and cursor 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 `/.bmad-loop/profiles/.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) and the opencode HTTP+SSE design stub in `adapters/opencode_http.py`). **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 ` (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. -Cursor CLI is currently blocked on two gaps, for whoever picks it up: token usage is not exposed anywhere (hooks, JSON output, or on-disk chats), and slash-command expansion of the initial prompt argument is unverified β€” its `sessionStart`/`stop` hooks do fire in the CLI, so a profile using the window-death fallback plus `usage_parser = "none"` is feasible. - ## Development ```bash diff --git a/docs/FEATURES.md b/docs/FEATURES.md index 12f642bc..53a1b7df 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -20,7 +20,7 @@ See [README.md](../README.md) for the narrative overview and [setup-guide.md](se | Plateau-defer | Stuck stories are skipped, stashed, and the run continues | One unconvergeable story blocking a whole sprint | | Typed escalations + resolve workflow | CRITICAL pauses + notifies; interactive resolve agent re-arms the story | Ambiguous specs silently producing wrong code | | Deferred-work sweeps | Triages an append-only ledger against real code, bundles + executes | Split-off goals and review findings get lost | -| Multi-CLI adapter + profiles | Generic driver runs claude/codex/gemini/copilot/antigravity; per-stage overrides; TOML profiles; transport + process-lifecycle + hook-interpreter behind a pluggable OS-seam registry (tmux/POSIX today) | Vendor lock-in; no way to mix models per stage; future non-tmux/Windows transport | +| Multi-CLI adapter + profiles | Generic driver runs claude/codex/gemini/copilot/antigravity/cursor; per-stage overrides; TOML profiles; transport + process-lifecycle + hook-interpreter behind a pluggable OS-seam registry (tmux/POSIX today) | Vendor lock-in; no way to mix models per stage; future non-tmux/Windows transport | | Cost-weighted token budgets | Per-story budget counts cache reads at ~0.1x; raw totals displayed | Naive token caps misjudge real cost (cache reads dominate) | | Non-invasive skill forks | Drives its own `bmad-loop-*` skill forks; reads `sprint-status.yaml` only | Modifying a user's standard BMAD install | | Read-only TUI + launcher | Live dashboard over run-dir artifacts; launches detached runs | No visibility into what an unattended run is doing | @@ -127,7 +127,7 @@ See [README.md](../README.md) for the narrative overview and [setup-guide.md](se - 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 is the only backend today, behind a pluggable seam so a native-Windows backend can be added without touching the engine (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: `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), `cursor` (Cursor Agent CLI `cursor-agent`; unattended `--print` + `--trust`; SessionEnd + window-death; `usage_parser = "none"` until transcripts expose tokens). - Experimental, probe-required: `antigravity` (Google's `agy` β‰₯ 1.0.16) β€” `-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"` until a transcript parser is written. 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/.toml` (binary, prompt template, bypass flags, hook dialect, nativeβ†’canonical event map). diff --git a/docs/setup-guide.md b/docs/setup-guide.md index b5d014dd..9ac5afad 100644 --- a/docs/setup-guide.md +++ b/docs/setup-guide.md @@ -93,7 +93,7 @@ invokes inline β€” are present before a run starts. ## Choosing which CLIs to drive -The supported adapters are `claude` (the default), `codex`, `gemini`, `copilot`, and +The supported adapters are `claude` (the default), `codex`, `gemini`, `copilot`, `cursor`, and `antigravity` (Google's `agy`, experimental β€” probe before unattended use). You can pick more than one β€” register every CLI you intend to use for dev, review, or sweep triage. @@ -172,7 +172,7 @@ bmad-loop init --project --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`, or `antigravity` β€” `init` errors on an unknown profile and +be exactly `claude`, `codex`, `gemini`, `copilot`, `antigravity`, or `cursor` β€” `init` errors on an unknown profile and lists the valid ones. ### First-run notes @@ -198,10 +198,15 @@ them to whoever owns the machine: (`usage_parser = "none"`), and worktree runs use a different path than the trusted one, so verify during the probe whether that re-triggers a trust prompt. Requires Antigravity CLI (`agy` β‰₯ 1.0.16). +- **cursor** β€” run `cursor-agent login` once. The profile launches headless with `--print` + + `--trust` (interactive `--trust` is rejected). Skills live in `.agents/skills/`; hooks + in `.cursor/hooks.json`. `sessionStart`/`sessionEnd` fire; do not rely on `stop` for + completion. Token usage is not captured yet (`usage_parser = "none"`). Requires the + Cursor Agent CLI (`cursor-agent` / `agent`). ### Skill location -`claude` reads skills from `.claude/skills/`; `codex`, `gemini`, `copilot`, and `antigravity` +`claude` reads skills from `.claude/skills/`; `codex`, `gemini`, `copilot`, `antigravity`, and `cursor` 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 diff --git a/src/bmad_loop/cli.py b/src/bmad_loop/cli.py index 15a800b9..12905825 100644 --- a/src/bmad_loop/cli.py +++ b/src/bmad_loop/cli.py @@ -1728,7 +1728,7 @@ def add(name: str, func, help: str, *, aliases=()) -> argparse.ArgumentParser: "--cli", action="append", metavar="PROFILE", - help="CLI profile(s) to register hooks for (claude | codex | gemini | copilot | antigravity | custom; " + help="CLI profile(s) to register hooks for (claude | codex | gemini | copilot | antigravity | cursor | custom; " "repeatable; default: profiles referenced by .bmad-loop/policy.toml, or claude)", ) init_p.add_argument( @@ -1781,7 +1781,7 @@ def add(name: str, func, help: str, *, aliases=()) -> argparse.ArgumentParser: aliases=["collect-adapter-data"], ) probe_p.add_argument( - "cli", help="CLI profile name (claude | codex | gemini | copilot | antigravity | custom)" + "cli", help="CLI profile name (claude | codex | gemini | copilot | antigravity | cursor | custom)" ) probe_p.add_argument( "--probe", diff --git a/src/bmad_loop/data/profiles/cursor.toml b/src/bmad_loop/data/profiles/cursor.toml new file mode 100644 index 00000000..8c23c2cb --- /dev/null +++ b/src/bmad_loop/data/profiles/cursor.toml @@ -0,0 +1,36 @@ +# Cursor Agent CLI (`cursor-agent` / `agent`). Unattended runs use `--print` +# (headless): `--trust` is rejected without it, and interactive sessions stall +# on the Workspace Trust dialog in fresh dirs / worktrees. With `--print` the +# process exits when the turn finishes β€” SessionEnd + window-death cover +# completion. Skills live in `.agents/skills/` (shared with codex/gemini/ +# copilot). Hook config reuses the Copilot dialect (versioned JSON with flat +# `{command:…}` handlers) at `.cursor/hooks.json`. +# +# Native events: sessionStart / sessionEnd fire reliably. `stop` is registered +# but was not observed on single-turn print or interactive smokes, nor on +# multi-subagent review passes during E2E bring-up β€” keep it mapped in case a +# future build fires it; completion must not depend on it. +# +# Prompt strategy mirrors Copilot: do not rely on slash-command expansion of +# the initial prompt argument β€” load SKILL.md by path instead. usage_parser is +# "none": Cursor Agent transcripts under +# ~/.cursor/projects/*/agent-transcripts/**/*.jsonl carry no token fields yet. +# +# E2E-verified: sprint-mode run, forced follow-up review, stop/resume, +# worktree isolation + merge-back, and deferred-work sweep triage+bundle +# (Cursor Agent CLI 2026.07.09). +name = "cursor" +binary = "cursor-agent" +skill_tree = ".agents/skills" +prompt_template = 'LOAD the FULL .agents/skills/{skill}/SKILL.md, read its entire contents and follow its directions exactly, using subagents as needed: {args}' +launch_args = ["--print"] +bypass_args = ["--force", "--trust", "--approve-mcps"] +model_flag = "--model" +usage_parser = "none" +first_run_note = "run `cursor-agent login` once before `bmad-loop run`; profile uses --print/--trust for unattended runs (interactive --trust is rejected). Requires Cursor Agent CLI (`cursor-agent` / `agent`)" +seed_files = [".cursor/hooks.json", ".cursor/mcp.json"] + +[hooks] +dialect = "copilot-settings-json" +config_path = ".cursor/hooks.json" +events = { sessionStart = "SessionStart", stop = "Stop", sessionEnd = "SessionEnd" } diff --git a/src/bmad_loop/data/skills/bmad-loop-setup/SKILL.md b/src/bmad_loop/data/skills/bmad-loop-setup/SKILL.md index 1bc29476..fba53c8a 100644 --- a/src/bmad_loop/data/skills/bmad-loop-setup/SKILL.md +++ b/src/bmad_loop/data/skills/bmad-loop-setup/SKILL.md @@ -132,9 +132,9 @@ Unless the user explicitly asked to skip it (e.g. `skills only` / `--no-tool`), 3. **Bootstrap the project** β€” install the coding-CLI hooks, the bundled `bmad-loop-*` skills, the `.bmad-loop/policy.toml` template, and the gitignore entry (idempotent). - First decide **which coding CLI(s)** the orchestrator should drive. The supported adapters are `claude` (default), `codex`, `gemini`, `copilot`, and `antigravity` (Google's `agy`). Hooks are registered per CLI, so the choice matters β€” register every CLI you intend to use for dev/review/triage. Ask the user (unless they already specified it in their setup args, e.g. `cli: claude, codex`, or accepted defaults β€” then default to `claude` only): + First decide **which coding CLI(s)** the orchestrator should drive. The supported adapters are `claude` (default), `codex`, `gemini`, `copilot`, `antigravity` (Google's `agy`), and `cursor` (`cursor-agent`). Hooks are registered per CLI, so the choice matters β€” register every CLI you intend to use for dev/review/triage. Ask the user (unless they already specified it in their setup args, e.g. `cli: claude, codex`, or accepted defaults β€” then default to `claude` only): - > "Which coding CLI(s) should the orchestrator drive β€” `claude`, `codex`, `gemini`, `copilot`, and/or `antigravity`? You can pick more than one. [claude]" + > "Which coding CLI(s) should the orchestrator drive β€” `claude`, `codex`, `gemini`, `copilot`, `antigravity`, and/or `cursor`? You can pick more than one. [claude]" Build the command with one `--cli ` per selected CLI (the flag is repeatable). **On an upgrade, append `--force-skills`** so the per-project skill copies are actually refreshed β€” without it `init` skips every existing skill dir and the project keeps stale skills against the upgraded tool. On a fresh install, omit it. @@ -149,9 +149,9 @@ Unless the user explicitly asked to skip it (e.g. `skills only` / `--no-tool`), bmad-loop init --project "{project-root}" --cli claude --force-skills ``` - Names must be exactly `claude`, `codex`, `gemini`, `copilot`, or `antigravity` β€” `init` errors on an unknown profile and lists the valid ones. `init` prints any one-time first-run notes per CLI (e.g. start `claude` once in the project and accept the workspace-trust + hooks-approval dialogs before `bmad-loop run` β€” spawned sessions can't answer first-run dialogs). Relay those notes to the user. + Names must be exactly `claude`, `codex`, `gemini`, `copilot`, `antigravity`, or `cursor` β€” `init` errors on an unknown profile and lists the valid ones. `init` prints any one-time first-run notes per CLI (e.g. start `claude` once in the project and accept the workspace-trust + hooks-approval dialogs before `bmad-loop run` β€” spawned sessions can't answer first-run dialogs). Relay those notes to the user. - **Skills are installed automatically:** `init` lays the bundled `bmad-loop-*` skills into the right tree for each selected CLI β€” `.claude/skills/` for `claude`, `.agents/skills/` for `codex`/`gemini`/`copilot`/`antigravity`. On a fresh install, existing skill dirs are left untouched; on an upgrade, `--force-skills` overwrites them with the bundled copies from the upgraded tool (use `--no-skills` to skip the step and manage skills yourself). + **Skills are installed automatically:** `init` lays the bundled `bmad-loop-*` skills into the right tree for each selected CLI β€” `.claude/skills/` for `claude`, `.agents/skills/` for `codex`/`gemini`/`copilot`/`antigravity`/`cursor`. On a fresh install, existing skill dirs are left untouched; on an upgrade, `--force-skills` overwrites them with the bundled copies from the upgraded tool (use `--no-skills` to skip the step and manage skills yourself). > **Note:** `--force-skills` also overwrites `bmad-loop-setup` itself (it ships in the same bundle). That's expected and safe β€” the freshly laid-down setup skill takes effect on the **next** invocation, and your `_bmad/custom/*.toml` overrides (keyed by skill directory name) are untouched. @@ -179,7 +179,7 @@ Unless the user explicitly asked to skip it (e.g. `skills only` / `--no-tool`), After both merge scripts complete successfully, remove any **redundant skill-payload** directories an older installer may have staged under `_bmad/` (past layouts duplicated skills that already live in the CLI's skill tree, e.g. `.claude/skills/`). On a modern BMAD v6 install nothing redundant is staged, so this step is a **safe no-op** β€” it never touches `_bmad/core/`, per-module config, or the `_bmad/_config/` manifest. -As with the merge scripts, replace `{project-root}` in the `--bmad-dir` and `--skills-dir` path arguments with the actual project root before running. For `--skills-dir`, use the skill tree of a CLI registered in step 3: `.claude/skills` for `claude`, `.agents/skills` for `codex`/`gemini`/`copilot`/`antigravity`. `init` lays the bundled skills into **every** selected CLI's tree, so any registered CLI's tree verifies correctly β€” the command below shows the `claude` tree; substitute `.agents/skills` if only non-Claude CLIs were registered. Do **not** pass `--also-remove _config` (or any shared config/manifest dir): `_config/` is live BMAD infrastructure and the script refuses to remove it regardless. +As with the merge scripts, replace `{project-root}` in the `--bmad-dir` and `--skills-dir` path arguments with the actual project root before running. For `--skills-dir`, use the skill tree of a CLI registered in step 3: `.claude/skills` for `claude`, `.agents/skills` for `codex`/`gemini`/`copilot`/`antigravity`/`cursor`. `init` lays the bundled skills into **every** selected CLI's tree, so any registered CLI's tree verifies correctly β€” the command below shows the `claude` tree; substitute `.agents/skills` if only non-Claude CLIs were registered. Do **not** pass `--also-remove _config` (or any shared config/manifest dir): `_config/` is live BMAD infrastructure and the script refuses to remove it regardless. ```bash python3 ./scripts/cleanup-legacy.py --bmad-dir "{project-root}/_bmad" --module-code bmad-loop --skills-dir "{project-root}/.claude/skills" diff --git a/src/bmad_loop/data/skills/bmad-loop-setup/scripts/cleanup-legacy.py b/src/bmad_loop/data/skills/bmad-loop-setup/scripts/cleanup-legacy.py index df109bce..f9fd0b41 100755 --- a/src/bmad_loop/data/skills/bmad-loop-setup/scripts/cleanup-legacy.py +++ b/src/bmad_loop/data/skills/bmad-loop-setup/scripts/cleanup-legacy.py @@ -57,7 +57,7 @@ def parse_args(): parser.add_argument( "--skills-dir", help="Path to the CLI's installed-skills tree (.claude/skills/ for claude, " - ".agents/skills/ for codex/gemini/copilot/antigravity) β€” enables safety " + ".agents/skills/ for codex/gemini/copilot/antigravity/cursor) β€” enables safety " "verification that skills are installed before removing legacy copies", ) parser.add_argument( diff --git a/src/bmad_loop/policy.py b/src/bmad_loop/policy.py index 6b3418af..0d8c1011 100644 --- a/src/bmad_loop/policy.py +++ b/src/bmad_loop/policy.py @@ -855,7 +855,7 @@ def _fold_deprecated_engine( spec_folder = "" [adapter] -name = "claude" # claude | codex | gemini | +name = "claude" # claude | codex | gemini | copilot | antigravity | cursor | model = "" # empty = CLI default model cleanup_session_on_finish = true # kill the run's tmux session when it finishes (false keeps it for inspection) # extra_args replaces the profile's default permission-bypass flags when set: diff --git a/src/bmad_loop/probe.py b/src/bmad_loop/probe.py index ec0c9d3a..05cb486e 100644 --- a/src/bmad_loop/probe.py +++ b/src/bmad_loop/probe.py @@ -61,6 +61,9 @@ # is community-doc-sourced (agy 1.0.x) β€” confirm against your build with # `probe-adapter antigravity --probe` before trusting auto-discovery. "antigravity": "~/.gemini/antigravity-cli/brain/*/.system_generated/logs/transcript.jsonl", + # Cursor Agent CLI: sessionEnd.transcript_path points at a jsonl under this + # tree. No token fields yet (usage_parser = "none"); glob is for discovery. + "cursor": "~/.cursor/projects/*/agent-transcripts/**/*.jsonl", } _TOKEN_KEY_RE = re.compile( diff --git a/tests/test_probe.py b/tests/test_probe.py index 50e3b4e0..5f5fd0c7 100644 --- a/tests/test_probe.py +++ b/tests/test_probe.py @@ -150,7 +150,7 @@ def test_discover_location_redacts_username(tmp_path, monkeypatch): # ----------------------------------------------------------- registration -@pytest.mark.parametrize("dialect_cli", ["claude", "codex", "gemini", "copilot", "antigravity"]) +@pytest.mark.parametrize("dialect_cli", ["claude", "codex", "gemini", "copilot", "antigravity", "cursor"]) def test_probe_hook_registers_under_native_events(dialect_cli): from bmad_loop.install import ANTIGRAVITY_HOOK_GROUP, merge_hooks diff --git a/tests/test_profile.py b/tests/test_profile.py index 7f5981a6..47d4874a 100644 --- a/tests/test_profile.py +++ b/tests/test_profile.py @@ -50,9 +50,24 @@ def test_builtin_profiles_load(): # copilot also fires agentStop for subagent turns (empty transcriptPath) β€” those # are ignored so the main session's turn-end drives completion assert profiles["copilot"].subagent_stop_without_transcript is True + # cursor: headless --print/--trust path; Copilot hook dialect; SessionEnd is + # the reliable completion signal (stop may not fire); no token parser yet + assert profiles["cursor"].binary == "cursor-agent" + assert profiles["cursor"].launch_args == ("--print",) + assert profiles["cursor"].bypass_args == ("--force", "--trust", "--approve-mcps") + assert profiles["cursor"].skill_tree == ".agents/skills" + assert profiles["cursor"].usage_parser == "none" + assert profiles["cursor"].hooks.dialect == "copilot-settings-json" + assert profiles["cursor"].hooks.config_path == ".cursor/hooks.json" + assert profiles["cursor"].hooks.events == { + "sessionStart": "SessionStart", + "stop": "Stop", + "sessionEnd": "SessionEnd", + } + assert ".cursor/hooks.json" in profiles["cursor"].seed_files # other built-ins keep the defaults: read usage once, inherit the global nudge # limit, and treat every Stop as the main turn-end (no subagent filtering) - for name in ("claude", "codex", "gemini"): + for name in ("claude", "codex", "gemini", "cursor"): assert profiles[name].usage_grace_s == 0.0 assert profiles[name].stop_without_result_nudges is None assert profiles[name].subagent_stop_without_transcript is False @@ -108,6 +123,11 @@ def test_render_prompt_passthrough_and_template(): assert codex.render_prompt("/bmad-dev-auto 1-1-a") == ( "Use the $bmad-dev-auto skill now, and use subagents as needed: 1-1-a" ) + cursor = get_profile("cursor") + assert cursor.render_prompt("/bmad-dev-auto 1-1-a") == ( + "LOAD the FULL .agents/skills/bmad-dev-auto/SKILL.md, read its entire contents " + "and follow its directions exactly, using subagents as needed: 1-1-a" + ) # non-slash prompts pass through {prompt}; {skill}/{args} degrade gracefully assert claude.render_prompt("just do it") == "just do it" From 3fd257b1e529e23e3433e7c913e4149aa427763f Mon Sep 17 00:00:00 2001 From: VaporFlow Date: Mon, 13 Jul 2026 19:50:01 -0300 Subject: [PATCH 2/2] Improve Cursor profile streaming and document Linux/WSL preference. Stream NDJSON to the TUI Log via pipe-pane, and note that native Windows attach/Log stay limited under --print. Co-authored-by: Cursor --- CHANGELOG.md | 17 ++++++++++------- README.md | 2 +- docs/FEATURES.md | 2 +- docs/setup-guide.md | 10 ++++++---- src/bmad_loop/data/profiles/cursor.toml | 15 +++++++++++++-- tests/test_profile.py | 12 +++++++++--- 6 files changed, 40 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb4e245f..d68b8394 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,13 +10,16 @@ breaking changes may land in a minor release. ### Added - **Cursor Agent CLI profile (`cursor`).** Built-in TOML profile for the Cursor - Agent CLI (`cursor-agent` / `agent`): unattended `--print` + `--force` + - `--trust` + `--approve-mcps` (interactive `--trust` is rejected), skills in - `.agents/skills/`, hooks in `.cursor/hooks.json` via the Copilot settings - dialect, SKILL.md-by-path prompt template (no slash expansion), and - `usage_parser = "none"` until transcripts expose tokens. Completion is - SessionEnd + window-death β€” `stop` is registered but was not observed in - E2E. Probe discovery glob: + Agent CLI (`cursor-agent` / `agent`): unattended `--print` + + `--output-format stream-json` + `--stream-partial-output` (feeds the TUI Log + tab via pipe-pane) + `--force` + `--trust` + `--approve-mcps` (interactive + `--trust` is rejected), skills in `.agents/skills/`, hooks in + `.cursor/hooks.json` via the Copilot settings dialect, SKILL.md-by-path + prompt template (no slash expansion), and `usage_parser = "none"` until + transcripts expose tokens. Completion is SessionEnd + window-death β€” `stop` + is registered but was not observed in E2E. Prefer Linux/WSL2 with real tmux + (native Windows pipe-pane often leaves the Log empty; attach stays sparse + under `--print`). Probe discovery glob: `~/.cursor/projects/*/agent-transcripts/**/*.jsonl`. E2E-verified for sprint runs, forced follow-up review, stop/resume, worktree isolation, and deferred sweeps. diff --git a/README.md b/README.md index 625a8426..c66af3db 100644 --- a/README.md +++ b/README.md @@ -502,7 +502,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 β€” probe-required | Google **Antigravity CLI** (`agy` β‰₯ 1.0.16). 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`). `usage_parser = "none"` until a token parser is written. Verify against your build with `probe-adapter antigravity`. | -| `cursor` | supported, E2E-verified | Cursor **Agent CLI** (`cursor-agent` / `agent`). Unattended path is `--print` + `--force` + `--trust` + `--approve-mcps` (`--trust` is rejected without `--print`). Skills in `.agents/skills/`; hooks in `.cursor/hooks.json` via the Copilot settings dialect. `sessionStart`/`sessionEnd` fire; `stop` is registered but was not observed in E2E β€” completion uses SessionEnd + window-death. No slash expansion of the initial prompt β€” SKILL.md is loaded by path (Copilot-style). `usage_parser = "none"` (transcripts under `~/.cursor/projects/*/agent-transcripts/` have no token fields yet). | +| `cursor` | supported, E2E-verified | Cursor **Agent CLI** (`cursor-agent` / `agent`). Unattended path is `--print` + `--output-format stream-json` + `--stream-partial-output` + `--force` + `--trust` + `--approve-mcps` (`--trust` is rejected without `--print`; stream-json feeds the TUI Log via pipe-pane). Prefer Linux/WSL2 + real tmux β€” native Windows often leaves Log empty; attach (`a`) stays sparse under `--print`. Skills in `.agents/skills/`; hooks in `.cursor/hooks.json` via the Copilot settings dialect. `sessionStart`/`sessionEnd` fire; `stop` is registered but was not observed in E2E β€” completion uses SessionEnd + window-death. No slash expansion of the initial prompt β€” SKILL.md is loaded by path (Copilot-style). `usage_parser = "none"` (transcripts under `~/.cursor/projects/*/agent-transcripts/` have no token fields yet). | **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]`. diff --git a/docs/FEATURES.md b/docs/FEATURES.md index 53a1b7df..d51b66db 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -127,7 +127,7 @@ See [README.md](../README.md) for the narrative overview and [setup-guide.md](se - 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 is the only backend today, behind a pluggable seam so a native-Windows backend can be added without touching the engine (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), `cursor` (Cursor Agent CLI `cursor-agent`; unattended `--print` + `--trust`; SessionEnd + window-death; `usage_parser = "none"` until transcripts expose tokens). +- 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), `cursor` (Cursor Agent CLI `cursor-agent`; unattended `--print` + `stream-json` + `--trust`; SessionEnd + window-death; prefer Linux/WSL2 for TUI Log; `usage_parser = "none"` until transcripts expose tokens). - Experimental, probe-required: `antigravity` (Google's `agy` β‰₯ 1.0.16) β€” `-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"` until a transcript parser is written. 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/.toml` (binary, prompt template, bypass flags, hook dialect, nativeβ†’canonical event map). diff --git a/docs/setup-guide.md b/docs/setup-guide.md index 9ac5afad..fa4c0bac 100644 --- a/docs/setup-guide.md +++ b/docs/setup-guide.md @@ -199,10 +199,12 @@ them to whoever owns the machine: so verify during the probe whether that re-triggers a trust prompt. Requires Antigravity CLI (`agy` β‰₯ 1.0.16). - **cursor** β€” run `cursor-agent login` once. The profile launches headless with `--print` - + `--trust` (interactive `--trust` is rejected). Skills live in `.agents/skills/`; hooks - in `.cursor/hooks.json`. `sessionStart`/`sessionEnd` fire; do not rely on `stop` for - completion. Token usage is not captured yet (`usage_parser = "none"`). Requires the - Cursor Agent CLI (`cursor-agent` / `agent`). + + `--output-format stream-json` + `--stream-partial-output` + `--trust` (interactive + `--trust` is rejected). Skills live in `.agents/skills/`; hooks in `.cursor/hooks.json`. + `sessionStart`/`sessionEnd` fire; do not rely on `stop` for completion. Prefer + Linux/WSL2 + tmux so the TUI Log tab receives the stream (native Windows pipe-pane is + unreliable; attach stays sparse under `--print`). Token usage is not captured yet + (`usage_parser = "none"`). Requires the Cursor Agent CLI (`cursor-agent` / `agent`). ### Skill location diff --git a/src/bmad_loop/data/profiles/cursor.toml b/src/bmad_loop/data/profiles/cursor.toml index 8c23c2cb..29308da6 100644 --- a/src/bmad_loop/data/profiles/cursor.toml +++ b/src/bmad_loop/data/profiles/cursor.toml @@ -16,6 +16,12 @@ # "none": Cursor Agent transcripts under # ~/.cursor/projects/*/agent-transcripts/**/*.jsonl carry no token fields yet. # +# TUI Log: `--output-format stream-json --stream-partial-output` streams NDJSON +# to the pane so pipe-pane can tee into *.log (closest parity to Claude's +# scrollback Log tab). Attach (`a`) stays sparse by design β€” `--print` is not +# an interactive TTY. Prefer Linux / WSL2 with real tmux; native Windows +# pipe-pane/psmux often leaves the Log tab empty. +# # E2E-verified: sprint-mode run, forced follow-up review, stop/resume, # worktree isolation + merge-back, and deferred-work sweep triage+bundle # (Cursor Agent CLI 2026.07.09). @@ -23,11 +29,16 @@ name = "cursor" binary = "cursor-agent" skill_tree = ".agents/skills" prompt_template = 'LOAD the FULL .agents/skills/{skill}/SKILL.md, read its entire contents and follow its directions exactly, using subagents as needed: {args}' -launch_args = ["--print"] +launch_args = [ + "--print", + "--output-format", + "stream-json", + "--stream-partial-output", +] bypass_args = ["--force", "--trust", "--approve-mcps"] model_flag = "--model" usage_parser = "none" -first_run_note = "run `cursor-agent login` once before `bmad-loop run`; profile uses --print/--trust for unattended runs (interactive --trust is rejected). Requires Cursor Agent CLI (`cursor-agent` / `agent`)" +first_run_note = "run `cursor-agent login` once before `bmad-loop run`; profile uses --print/--trust (interactive --trust is rejected) and stream-json for the TUI Log tab. Prefer Linux/WSL2 + tmux. Requires Cursor Agent CLI (`cursor-agent` / `agent`)" seed_files = [".cursor/hooks.json", ".cursor/mcp.json"] [hooks] diff --git a/tests/test_profile.py b/tests/test_profile.py index 47d4874a..22f4298e 100644 --- a/tests/test_profile.py +++ b/tests/test_profile.py @@ -50,10 +50,16 @@ def test_builtin_profiles_load(): # copilot also fires agentStop for subagent turns (empty transcriptPath) β€” those # are ignored so the main session's turn-end drives completion assert profiles["copilot"].subagent_stop_without_transcript is True - # cursor: headless --print/--trust path; Copilot hook dialect; SessionEnd is - # the reliable completion signal (stop may not fire); no token parser yet + # cursor: headless --print/--trust path + stream-json for TUI Log; Copilot + # hook dialect; SessionEnd is the reliable completion signal (stop may not + # fire); no token parser yet assert profiles["cursor"].binary == "cursor-agent" - assert profiles["cursor"].launch_args == ("--print",) + assert profiles["cursor"].launch_args == ( + "--print", + "--output-format", + "stream-json", + "--stream-partial-output", + ) assert profiles["cursor"].bypass_args == ("--force", "--trust", "--approve-mcps") assert profiles["cursor"].skill_tree == ".agents/skills" assert profiles["cursor"].usage_parser == "none"