diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 7fb272d..0123f12 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -12,7 +12,7 @@ "name": "bauto", "source": "./src/automator/data/skills", "description": "Automation-mode skills driven by the bmad-auto orchestrator: unattended dev (bmad-auto-dev), adversarial review (bmad-auto-review), and deferred-work sweep triage (bmad-auto-sweep)", - "version": "0.6.3", + "version": "0.6.4", "author": { "name": "pinkyd" }, diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c8a9d8..ceb1403 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,30 @@ All notable changes to `bmad-auto` are documented here. The format is based on [Semantic Versioning](https://semver.org/spec/v2.0.0.html). While the project is pre-1.0, breaking changes may land in a minor release. +## [0.6.4] — 2026-06-21 + +### Fixed + +- **Copilot token usage now records (was always 0).** Copilot writes its token totals only in + the trailing `session.shutdown` events line, ~1s after `agentStop` — usage was sampled before + it landed. `read_usage` now polls the transcript for a short grace, driven by a new per-profile + `usage_grace_s` (8s for `copilot`, 0 elsewhere = read once). +- **Copilot multi-turn reviews no longer stall.** `agentStop` fires per response turn, so a + parallel-subagent review ends several turns and tripped the global `stop_without_result_nudges` + default of 1. New per-adapter floor (5 for `copilot`), overridable per stage via `[adapter.review]`. + +### Added + +- **`[adapter] usage_grace_s` / `stop_without_result_nudges`** (base + per-stage + `[adapter.dev|review|triage]`), editable in the settings TUI. Unset = inherit the CLI profile's + shipped default. + +### Changed + +- **Copilot docs.** Pin a capable model — the free GPT-5 mini default silently skips steps in + multi-step dev/review — and it's the Copilot **CLI** binary that's supported, not the VS Code + extension. + ## [0.6.3] — 2026-06-21 ### Fixed @@ -467,6 +491,7 @@ enforced in CI. implementation phase, driven by a Python control loop with hook-based session transport and resumable on-disk run state. +[0.6.4]: https://github.com/bmad-code-org/bmad-auto/releases/tag/v0.6.4 [0.6.3]: https://github.com/bmad-code-org/bmad-auto/releases/tag/v0.6.3 [0.6.2]: https://github.com/bmad-code-org/bmad-auto/releases/tag/v0.6.2 [0.6.1]: https://github.com/bmad-code-org/bmad-auto/releases/tag/v0.6.1 diff --git a/README.md b/README.md index a8b8853..7067a9a 100644 --- a/README.md +++ b/README.md @@ -439,12 +439,14 @@ Each run drives its agents inside a dedicated tmux session, `bmad-auto-` One generic driver (`adapters/generic_tmux.py`) runs any coding CLI that fits the tmux-injection + hook-signal transport; everything CLI-specific lives in a declarative **profile** (`adapters/profile.py`). Built-in profiles ship as TOML in `automator/data/profiles/`: -| Profile | Status | Notes | -| --------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `claude` | supported | reference implementation | -| `codex` | supported, E2E-verified | Codex ≥ 0.139. No slash expansion in the initial prompt — the profile renders `$skill-name` mentions (plus a "use subagents as needed" nudge) instead. No SessionEnd hook; window-death fallback covers crashes. | -| `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` | bundled, pending live E2E | GitHub Copilot CLI ≥ 2026-02. Launches with `-i` to stay interactive; VS Code-compatible PascalCase `Stop` hook (snake_case payloads); `--allow-all-tools` for unattended runs. No `usage_parser` yet — run `probe-adapter` to capture its token schema (see below). | +| Profile | Status | Notes | +| --------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `claude` | supported | reference implementation | +| `codex` | supported, E2E-verified | Codex ≥ 0.139. No slash expansion in the initial prompt — the profile renders `$skill-name` mentions (plus a "use subagents as needed" nudge) instead. No SessionEnd hook; window-death fallback covers crashes. | +| `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). | + +**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. diff --git a/docs/FEATURES.md b/docs/FEATURES.md index 663a87a..4d24bea 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -112,8 +112,7 @@ See [README.md](../README.md) for the narrative overview and [setup-guide.md](se ### Multi-CLI / multi-agent support - Generic tmux adapter drives any CLI fitting the tmux-injection + hook-signal transport; CLI specifics live in declarative TOML profiles. -- Supported, E2E-verified: `claude` (reference), `codex` (≥ 0.139), `gemini` (≥ 0.46). -- Bundled but pending live E2E verification: `copilot` (GitHub Copilot CLI ≥ 2026-02; VS Code-compatible `Stop` hook, `-i` interactive launch, `--allow-all-tools`). +- 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). - 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 `.automator/profiles/.toml` (binary, prompt template, bypass flags, hook dialect, native→canonical event map). - `bmad-auto 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). diff --git a/docs/images/dashboard.png b/docs/images/dashboard.png index b6fb47d..cc501ac 100644 Binary files a/docs/images/dashboard.png and b/docs/images/dashboard.png differ diff --git a/docs/images/dashboard.svg b/docs/images/dashboard.svg index 20ea9ca..aa986bd 100644 --- a/docs/images/dashboard.svg +++ b/docs/images/dashboard.svg @@ -19,226 +19,226 @@ font-weight: 700; } - .terminal-145783149-matrix { + .terminal-569538889-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-145783149-title { + .terminal-569538889-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-145783149-r1 { fill: #c5c8c6 } -.terminal-145783149-r2 { fill: #e0e0e0 } -.terminal-145783149-r3 { fill: #a0a3a6 } -.terminal-145783149-r4 { fill: #0053aa } -.terminal-145783149-r5 { fill: #e2e2e2;font-weight: bold } -.terminal-145783149-r6 { fill: #e0e0e0;font-weight: bold } -.terminal-145783149-r7 { fill: #98e024 } -.terminal-145783149-r8 { fill: #9d9d9d } -.terminal-145783149-r9 { fill: #fd971f } -.terminal-145783149-r10 { fill: #a1a1a1 } -.terminal-145783149-r11 { fill: #ddedf9;font-weight: bold } -.terminal-145783149-r12 { fill: #797979 } -.terminal-145783149-r13 { fill: #262626 } -.terminal-145783149-r14 { fill: #0178d4 } -.terminal-145783149-r15 { fill: #e1e1e1;font-weight: bold } -.terminal-145783149-r16 { fill: #121212 } -.terminal-145783149-r17 { fill: #191919 } -.terminal-145783149-r18 { fill: #9e9e9e } -.terminal-145783149-r19 { fill: #58d1eb } -.terminal-145783149-r20 { fill: #3e3e3e } -.terminal-145783149-r21 { fill: #f4005f } -.terminal-145783149-r22 { fill: #f4005f;font-weight: bold } -.terminal-145783149-r23 { fill: #ffa62b;font-weight: bold } -.terminal-145783149-r24 { fill: #495259 } + .terminal-569538889-r1 { fill: #c5c8c6 } +.terminal-569538889-r2 { fill: #e0e0e0 } +.terminal-569538889-r3 { fill: #a0a3a6 } +.terminal-569538889-r4 { fill: #0053aa } +.terminal-569538889-r5 { fill: #e2e2e2;font-weight: bold } +.terminal-569538889-r6 { fill: #e0e0e0;font-weight: bold } +.terminal-569538889-r7 { fill: #98e024 } +.terminal-569538889-r8 { fill: #9d9d9d } +.terminal-569538889-r9 { fill: #fd971f } +.terminal-569538889-r10 { fill: #a1a1a1 } +.terminal-569538889-r11 { fill: #ddedf9;font-weight: bold } +.terminal-569538889-r12 { fill: #797979 } +.terminal-569538889-r13 { fill: #262626 } +.terminal-569538889-r14 { fill: #0178d4 } +.terminal-569538889-r15 { fill: #e1e1e1;font-weight: bold } +.terminal-569538889-r16 { fill: #121212 } +.terminal-569538889-r17 { fill: #191919 } +.terminal-569538889-r18 { fill: #9e9e9e } +.terminal-569538889-r19 { fill: #58d1eb } +.terminal-569538889-r20 { fill: #3e3e3e } +.terminal-569538889-r21 { fill: #f4005f } +.terminal-569538889-r22 { fill: #f4005f;font-weight: bold } +.terminal-569538889-r23 { fill: #ffa62b;font-weight: bold } +.terminal-569538889-r24 { fill: #495259 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - bmad-auto + bmad-auto - + - - bmad-auto — ~/code/acme-search -Runs────────────────────────────20260612-141000-e5f6▶ running  started 2026-06-12T14:10:00  epic 2 - st  run                   type  tasks 5  done 2  deferred 1  escalated 0  2,473,900 tokens - 20260610-090000-a1b2  story ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── - 20260611-101500-c3d4  sweep  story                           phase             dev    review  tokens        info                               - ▶   20260612-141000-e5f6  story  2-1-search-index                done              ×1     ×1      824,400       a1b2c3d4e5f6                       - 2-2-typeahead                   done              ×1     ×2      699,900       b2c3d4e5f607                       - 2-3-search-ranking              review-running    ×1     ×1      504,200       - 2-4-saved-searches              dev-running       ×1     ×0      168,300       - 2-5-search-analytics            deferred          ×2     ×1      277,100       dev budget exhausted (2 attempts)  -JournalLogAttention -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -Sprint──────────────────────────▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -▼ Epic 1 · 3/3 ✓22:49:18 session-start            task_id=2-1-search-index  role=dev  story_key=2-1-search-index -├ ✓ 1-auth22:49:18 story-done               story_key=2-1-search-index  commit=a1b2c3d4e5f6 -├ ✓ 2-session-tokens22:49:18 session-start            task_id=2-2-typeahead  role=dev  story_key=2-2-typeahead -├ ✓ 3-password-reset22:49:18 review-cycle             story_key=2-2-typeahead  cycle=2  findings=3 -└ ✓ retrospective22:49:18 story-done               story_key=2-2-typeahead  commit=b2c3d4e5f607 -▼ Epic 2 · 2/522:49:18 story-start              story_key=2-3-search-ranking  epic=2 -├ ✓ 1-search-index22:49:18 spec-approved            story_key=2-3-search-ranking  tokens=1834 -├ ✓ 2-typeahead22:49:18 dev-done                 story_key=2-3-search-ranking  tasks_done=3  tasks_total=3 -├ ▶ 3-search-ranking22:49:18 verify-ok                story_key=2-3-search-ranking  commands=pytest -q, ruff check . -├ ◆ 4-saved-searches22:49:18 review-start             story_key=2-3-search-ranking  role=review -└ ○ 5-search-analytics22:49:18 escalation-preference    story_key=2-3-search-ranking  detail=reviewer used codex for the scoring math -▼ Epic 3 · 0/222:49:18 story-deferred           story_key=2-5-search-analytics  reason=dev budget exhausted -├ · 1-billing-portal -└ · 2-invoices - - -Deferred Work — 2 to answer (d) -DW-1 Harden the OAuth token refr… -DW-2 Add pagination to the searc… -DW-3 Replace the ad-hoc ranking … -DW-4 Flaky retry in the indexer … -DW-5 ✓ Polish the empty-state co… - - - - - -▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - q quit  r run  s sweep  e resume  R resolve  d decisions  a attach  x stop  D delete  A archive  c cleanup  v validate  g settings  M mod^p palette + + bmad-auto — ~/code/acme-search +Runs────────────────────────────20260612-141000-e5f6▶ running  started 2026-06-12T14:10:00  epic 2 + st  run                   type  tasks 5  done 2  deferred 1  escalated 0  2,473,900 tokens + 20260610-090000-a1b2  story ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + 20260611-101500-c3d4  sweep  story                           phase             dev    review  tokens        info                               + ▶   20260612-141000-e5f6  story  2-1-search-index                done              ×1     ×1      824,400       a1b2c3d4e5f6                       + 2-2-typeahead                   done              ×1     ×2      699,900       b2c3d4e5f607                       + 2-3-search-ranking              review-running    ×1     ×1      504,200       + 2-4-saved-searches              dev-running       ×1     ×0      168,300       + 2-5-search-analytics            deferred          ×2     ×1      277,100       dev budget exhausted (2 attempts)  +JournalLogAttention +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +Sprint──────────────────────────▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +▼ Epic 1 · 3/3 ✓15:09:35 session-start            task_id=2-1-search-index  role=dev  story_key=2-1-search-index +├ ✓ 1-auth15:09:35 story-done               story_key=2-1-search-index  commit=a1b2c3d4e5f6 +├ ✓ 2-session-tokens15:09:35 session-start            task_id=2-2-typeahead  role=dev  story_key=2-2-typeahead +├ ✓ 3-password-reset15:09:35 review-cycle             story_key=2-2-typeahead  cycle=2  findings=3 +└ ✓ retrospective15:09:35 story-done               story_key=2-2-typeahead  commit=b2c3d4e5f607 +▼ Epic 2 · 2/515:09:35 story-start              story_key=2-3-search-ranking  epic=2 +├ ✓ 1-search-index15:09:35 spec-approved            story_key=2-3-search-ranking  tokens=1834 +├ ✓ 2-typeahead15:09:35 dev-done                 story_key=2-3-search-ranking  tasks_done=3  tasks_total=3 +├ ▶ 3-search-ranking15:09:35 verify-ok                story_key=2-3-search-ranking  commands=pytest -q, ruff check . +├ ◆ 4-saved-searches15:09:35 review-start             story_key=2-3-search-ranking  role=review +└ ○ 5-search-analytics15:09:35 escalation-preference    story_key=2-3-search-ranking  detail=reviewer used codex for the scoring math +▼ Epic 3 · 0/215:09:35 story-deferred           story_key=2-5-search-analytics  reason=dev budget exhausted +├ · 1-billing-portal +└ · 2-invoices + + +Deferred Work — 2 to answer (d) +DW-1 Harden the OAuth token refr… +DW-2 Add pagination to the searc… +DW-3 Replace the ad-hoc ranking … +DW-4 Flaky retry in the indexer … +DW-5 ✓ Polish the empty-state co… + + + + + +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + q quit  r run  s sweep  e resume  R resolve  d decisions  a attach  x stop  D delete  A archive  c cleanup  v validate  g settings  M mod^p palette diff --git a/docs/images/demo.gif b/docs/images/demo.gif index 4f9ba0c..0956e78 100644 Binary files a/docs/images/demo.gif and b/docs/images/demo.gif differ diff --git a/docs/images/settings-scm.png b/docs/images/settings-scm.png index adf4b11..7c41fe1 100644 Binary files a/docs/images/settings-scm.png and b/docs/images/settings-scm.png differ diff --git a/docs/images/settings-scm.svg b/docs/images/settings-scm.svg index 19013ba..afb691c 100644 --- a/docs/images/settings-scm.svg +++ b/docs/images/settings-scm.svg @@ -19,216 +19,216 @@ font-weight: 700; } - .terminal-505119418-matrix { + .terminal-2360967870-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-505119418-title { + .terminal-2360967870-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-505119418-r1 { fill: #c5c8c6 } -.terminal-505119418-r2 { fill: #e0e0e0 } -.terminal-505119418-r3 { fill: #a0a3a6 } -.terminal-505119418-r4 { fill: #1e1e1e } -.terminal-505119418-r5 { fill: #191919 } -.terminal-505119418-r6 { fill: #a5a5a5 } -.terminal-505119418-r7 { fill: #737373 } -.terminal-505119418-r8 { fill: #a5a5a5;font-style: italic; } -.terminal-505119418-r9 { fill: #7f7f7f } -.terminal-505119418-r10 { fill: #003054 } -.terminal-505119418-r11 { fill: #121212 } -.terminal-505119418-r12 { fill: #000000 } -.terminal-505119418-r13 { fill: #ffa62b;font-weight: bold } -.terminal-505119418-r14 { fill: #495259 } + .terminal-2360967870-r1 { fill: #c5c8c6 } +.terminal-2360967870-r2 { fill: #e0e0e0 } +.terminal-2360967870-r3 { fill: #a0a3a6 } +.terminal-2360967870-r4 { fill: #1e1e1e } +.terminal-2360967870-r5 { fill: #191919 } +.terminal-2360967870-r6 { fill: #a5a5a5 } +.terminal-2360967870-r7 { fill: #737373 } +.terminal-2360967870-r8 { fill: #a5a5a5;font-style: italic; } +.terminal-2360967870-r9 { fill: #7f7f7f } +.terminal-2360967870-r10 { fill: #003054 } +.terminal-2360967870-r11 { fill: #121212 } +.terminal-2360967870-r12 { fill: #000000 } +.terminal-2360967870-r13 { fill: #ffa62b;font-weight: bold } +.terminal-2360967870-r14 { fill: #495259 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - bmad-auto + bmad-auto - + - - bmad-auto — ~/code/acme-search - -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -target_branchdefault: the branch checked out at run start -▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ -worktree mode: branch all units merge back into (created if missing) - -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -merge_strategydefault: merge -▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ -worktree mode: how a unit branch lands on the target — ff, merge, or squash - -▔▔▔▔▔▔▔▔ -delete_branch -▁▁▁▁▁▁▁▁ -worktree mode: delete the unit branch after a successful merge -▆▆ -▔▔▔▔▔▔▔▔ -keep_failed -▁▁▁▁▁▁▁▁ -worktree mode: keep a failed unit's worktree + branch mounted for inspection - -▔▔▔▔▔▔▔▔ -auto-rollback failed -attempts▁▁▁▁▁▁▁▁ - -⚠ in-place mode (isolation=none): when ON, a failed attempt's tracked changes are auto-reverted and the -untracked files this run created are deleted (its uncommitted work is lost). When OFF (default), the -orchestrator never touches your tree — it pauses with manual recovery steps. Prefer isolation=worktree to -keep failures off your main checkout.▅▅ - -▔▔▔▔▔▔▔▔ -seed adapter configs -▁▁▁▁▁▁▁▁ -worktree mode: copy each loaded adapter's gitignored MCP/CLI configs (.mcp.json, .claude/settings.json, -.codex/config.toml …) into the worktree so isolated sessions can reach their MCP server - -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -extra worktree seed files - - - esc back  ^s save  ^e expand/collapse all  q quit  r run  s sweep  e resume  R resolve  d decisions  a attach  x stop  D delete  A archiv^p palette + + bmad-auto — ~/code/acme-search + +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +target_branchdefault: the branch checked out at run start +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +worktree mode: branch all units merge back into (created if missing) + +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +merge_strategydefault: merge +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +worktree mode: how a unit branch lands on the target — ff, merge, or squash + +▔▔▔▔▔▔▔▔ +delete_branch +▁▁▁▁▁▁▁▁ +worktree mode: delete the unit branch after a successful merge +▇▇ +▔▔▔▔▔▔▔▔ +keep_failed +▁▁▁▁▁▁▁▁ +worktree mode: keep a failed unit's worktree + branch mounted for inspection + +▔▔▔▔▔▔▔▔ +auto-rollback failed +attempts▁▁▁▁▁▁▁▁ + +⚠ in-place mode (isolation=none): when ON, a failed attempt's tracked changes are auto-reverted and the +untracked files this run created are deleted (its uncommitted work is lost). When OFF (default), the +orchestrator never touches your tree — it pauses with manual recovery steps. Prefer isolation=worktree to +keep failures off your main checkout.▆▆ + +▔▔▔▔▔▔▔▔ +seed adapter configs +▁▁▁▁▁▁▁▁ +worktree mode: copy each loaded adapter's gitignored MCP/CLI configs (.mcp.json, .claude/settings.json, +.codex/config.toml …) into the worktree so isolated sessions can reach their MCP server + +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +extra worktree seed files + + + esc back  ^s save  ^e expand/collapse all  q quit  r run  s sweep  e resume  R resolve  d decisions  a attach  x stop  D delete  A archiv^p palette diff --git a/docs/images/settings.svg b/docs/images/settings.svg index dab23ad..5575199 100644 --- a/docs/images/settings.svg +++ b/docs/images/settings.svg @@ -19,209 +19,209 @@ font-weight: 700; } - .terminal-1072437773-matrix { + .terminal-3686799903-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1072437773-title { + .terminal-3686799903-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1072437773-r1 { fill: #c5c8c6 } -.terminal-1072437773-r2 { fill: #e0e0e0 } -.terminal-1072437773-r3 { fill: #a0a3a6 } -.terminal-1072437773-r4 { fill: #121212 } -.terminal-1072437773-r5 { fill: #6f6f6f } -.terminal-1072437773-r6 { fill: #ffa62b;font-weight: bold } -.terminal-1072437773-r7 { fill: #495259 } + .terminal-3686799903-r1 { fill: #c5c8c6 } +.terminal-3686799903-r2 { fill: #e0e0e0 } +.terminal-3686799903-r3 { fill: #a0a3a6 } +.terminal-3686799903-r4 { fill: #121212 } +.terminal-3686799903-r5 { fill: #6f6f6f } +.terminal-3686799903-r6 { fill: #ffa62b;font-weight: bold } +.terminal-3686799903-r7 { fill: #495259 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - bmad-auto + bmad-auto - + - - bmad-auto — ~/code/acme-search - -~/code/acme-search/.automator/policy.toml -running engines snapshot policy at start — changes apply to new runs and resumes -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -▶ gates — approval gates, escalation & retrospective behavior - -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -▶ review — separate adversarial review session toggle - -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -▶ limits — cycle/attempt caps, timeout & token budget - -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -▶ verify — post-implementation verification commands - -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -▶ notify — desktop & file notifications - -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -▶ adapter — CLI client, model & bypass flags (base for all stages) - -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -▶ adapter.dev — dev-stage adapter overrides - -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -▶ adapter.review — review-stage adapter overrides - -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -▶ adapter.triage — triage-stage adapter overrides - -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -▶ sweep — deferred-work sweep automation - -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -▶ scm — git isolation, branching & merge-back - -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -▶ cleanup — disk reclamation for .automator/runs (terminal runs only) - -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - esc back  ^s save  ^e expand/collapse all  q quit  r run  s sweep  e resume  R resolve  d decisions  a attach  x stop  D delete  A archiv^p palette + + bmad-auto — ~/code/acme-search + +~/code/acme-search/.automator/policy.toml +running engines snapshot policy at start — changes apply to new runs and resumes +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +▶ gates — approval gates, escalation & retrospective behavior + +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +▶ review — separate adversarial review session toggle + +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +▶ limits — cycle/attempt caps, timeout & token budget + +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +▶ verify — post-implementation verification commands + +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +▶ notify — desktop & file notifications + +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +▶ adapter — CLI client, model & bypass flags (base for all stages) + +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +▶ adapter.dev — dev-stage adapter overrides + +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +▶ adapter.review — review-stage adapter overrides + +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +▶ adapter.triage — triage-stage adapter overrides + +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +▶ sweep — deferred-work sweep automation + +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +▶ scm — git isolation, branching & merge-back + +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +▶ cleanup — disk reclamation for .automator/runs (terminal runs only) + +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + esc back  ^s save  ^e expand/collapse all  q quit  r run  s sweep  e resume  R resolve  d decisions  a attach  x stop  D delete  A archiv^p palette diff --git a/docs/images/start-run-modal.png b/docs/images/start-run-modal.png index 10da48e..b43be17 100644 Binary files a/docs/images/start-run-modal.png and b/docs/images/start-run-modal.png differ diff --git a/docs/images/start-run-modal.svg b/docs/images/start-run-modal.svg index 49d8c20..b714562 100644 --- a/docs/images/start-run-modal.svg +++ b/docs/images/start-run-modal.svg @@ -19,241 +19,241 @@ font-weight: 700; } - .terminal-3222002520-matrix { + .terminal-3221216088-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3222002520-title { + .terminal-3221216088-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3222002520-r1 { fill: #e0e0e0 } -.terminal-3222002520-r2 { fill: #646464 } -.terminal-3222002520-r3 { fill: #4a4c4d } -.terminal-3222002520-r4 { fill: #c5c8c6 } -.terminal-3222002520-r5 { fill: #0a2c4e } -.terminal-3222002520-r6 { fill: #646464;font-weight: bold } -.terminal-3222002520-r7 { fill: #476419 } -.terminal-3222002520-r8 { fill: #494949 } -.terminal-3222002520-r9 { fill: #704717 } -.terminal-3222002520-r10 { fill: #4a4a4a } -.terminal-3222002520-r11 { fill: #704717;font-weight: bold } -.terminal-3222002520-r12 { fill: #3b3b3b } -.terminal-3222002520-r13 { fill: #1a1a1a } -.terminal-3222002520-r14 { fill: #0b3a5f } -.terminal-3222002520-r15 { fill: #121212 } -.terminal-3222002520-r16 { fill: #141414 } -.terminal-3222002520-r17 { fill: #0053aa } -.terminal-3222002520-r18 { fill: #e0e0e0;font-weight: bold } -.terminal-3222002520-r19 { fill: #232323 } -.terminal-3222002520-r20 { fill: #1e1e1e } -.terminal-3222002520-r21 { fill: #0178d4 } -.terminal-3222002520-r22 { fill: #797979 } -.terminal-3222002520-r23 { fill: #191919 } -.terminal-3222002520-r24 { fill: #737373 } -.terminal-3222002520-r25 { fill: #2e5e68 } -.terminal-3222002520-r26 { fill: #6c0a30 } -.terminal-3222002520-r27 { fill: #242f38 } -.terminal-3222002520-r28 { fill: #000f18 } -.terminal-3222002520-r29 { fill: #6db2ff } -.terminal-3222002520-r30 { fill: #2d2d2d } -.terminal-3222002520-r31 { fill: #ddedf9;font-weight: bold } -.terminal-3222002520-r32 { fill: #656565;font-weight: bold } -.terminal-3222002520-r33 { fill: #004295 } -.terminal-3222002520-r34 { fill: #0d0d0d } -.terminal-3222002520-r35 { fill: #455969;font-weight: bold } -.terminal-3222002520-r36 { fill: #6c0a30;font-weight: bold } -.terminal-3222002520-r37 { fill: #4b4b4b } -.terminal-3222002520-r38 { fill: #704d1c;font-weight: bold } -.terminal-3222002520-r39 { fill: #282b2e } + .terminal-3221216088-r1 { fill: #e0e0e0 } +.terminal-3221216088-r2 { fill: #646464 } +.terminal-3221216088-r3 { fill: #4a4c4d } +.terminal-3221216088-r4 { fill: #c5c8c6 } +.terminal-3221216088-r5 { fill: #0a2c4e } +.terminal-3221216088-r6 { fill: #646464;font-weight: bold } +.terminal-3221216088-r7 { fill: #476419 } +.terminal-3221216088-r8 { fill: #494949 } +.terminal-3221216088-r9 { fill: #704717 } +.terminal-3221216088-r10 { fill: #4a4a4a } +.terminal-3221216088-r11 { fill: #704717;font-weight: bold } +.terminal-3221216088-r12 { fill: #3b3b3b } +.terminal-3221216088-r13 { fill: #1a1a1a } +.terminal-3221216088-r14 { fill: #0b3a5f } +.terminal-3221216088-r15 { fill: #121212 } +.terminal-3221216088-r16 { fill: #141414 } +.terminal-3221216088-r17 { fill: #0053aa } +.terminal-3221216088-r18 { fill: #e0e0e0;font-weight: bold } +.terminal-3221216088-r19 { fill: #232323 } +.terminal-3221216088-r20 { fill: #1e1e1e } +.terminal-3221216088-r21 { fill: #0178d4 } +.terminal-3221216088-r22 { fill: #797979 } +.terminal-3221216088-r23 { fill: #191919 } +.terminal-3221216088-r24 { fill: #737373 } +.terminal-3221216088-r25 { fill: #2e5e68 } +.terminal-3221216088-r26 { fill: #6c0a30 } +.terminal-3221216088-r27 { fill: #242f38 } +.terminal-3221216088-r28 { fill: #000f18 } +.terminal-3221216088-r29 { fill: #6db2ff } +.terminal-3221216088-r30 { fill: #2d2d2d } +.terminal-3221216088-r31 { fill: #ddedf9;font-weight: bold } +.terminal-3221216088-r32 { fill: #656565;font-weight: bold } +.terminal-3221216088-r33 { fill: #004295 } +.terminal-3221216088-r34 { fill: #0d0d0d } +.terminal-3221216088-r35 { fill: #455969;font-weight: bold } +.terminal-3221216088-r36 { fill: #6c0a30;font-weight: bold } +.terminal-3221216088-r37 { fill: #4b4b4b } +.terminal-3221216088-r38 { fill: #704d1c;font-weight: bold } +.terminal-3221216088-r39 { fill: #282b2e } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - bmad-auto + bmad-auto - + - - bmad-auto — ~/code/acme-search -Runs────────────────────────────20260611-101500-c3d4 [sweep]  ▶ running  started 2026-06-11T10:15:00  epic 2 - st  run                   type  tasks 1  done 1  deferred 0  escalated 0  369,300 tokens - 20260610-090000-a1b2  story ⚑ decision needed: DW-1 — Reopen the OAuth refresh race now, or hold it for the auth hardening epic? - ▶   20260611-101500-c3d4  sweep   press a to attach and answer - 20260612-141000-e5f6  story ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── - story                           phase             dev    review  tokens        info                               - dw-search-pagination            done              ×1     ×1      369,300       2b7f0a91c440                       -JournalLogAttention -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -▔▔▔▔▔▔▔▔▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -22:49:1 -Sprint──────────────────────────22:49:1start runons=2 -▼ Epic 1 · 3/3 ✓22:49:1 -├ ✓ 1-auth22:49:1▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ refresh race now, or hold it for the -├ ✓ 2-session-tokensauth …epic — blank for all -├ ✓ 3-password-reset▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ -└ ✓ retrospective▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -▼ Epic 2 · 2/5story — 3-1, 3.1, slug, or full key (blank for all) -├ ✓ 1-search-index▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ -├ ✓ 2-typeahead▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -├ ▶ 3-search-rankingmax stories — blank for no limit -├ ◆ 4-saved-searches▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ -└ ○ 5-search-analytics▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -▼ Epic 3 · 0/2X dry run (print the plan, spawn nothing)  -├ · 1-billing-portal▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ -└ · 2-invoices -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - start  cancel  -Deferred Work — 2 to answer (d)▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ -DW-1 Harden the OAuth token refr… -DW-2 Add pagination to the searc…▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ -DW-3 Replace the ad-hoc ranking … -DW-4 Flaky retry in the indexer … -DW-5 ✓ Polish the empty-state co… - - - - - -▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - q quit  r run  s sweep  e resume  R resolve  d decisions  a attach  x stop  D delete  A archive  c cleanup  v validate  g settings  M mod^p palette + + bmad-auto — ~/code/acme-search +Runs────────────────────────────20260611-101500-c3d4 [sweep]  ▶ running  started 2026-06-11T10:15:00  epic 2 + st  run                   type  tasks 1  done 1  deferred 0  escalated 0  369,300 tokens + 20260610-090000-a1b2  story ⚑ decision needed: DW-1 — Reopen the OAuth refresh race now, or hold it for the auth hardening epic? + ▶   20260611-101500-c3d4  sweep   press a to attach and answer + 20260612-141000-e5f6  story ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + story                           phase             dev    review  tokens        info                               + dw-search-pagination            done              ×1     ×1      369,300       2b7f0a91c440                       +JournalLogAttention +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +▔▔▔▔▔▔▔▔▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +15:09:3 +Sprint──────────────────────────15:09:3start runons=2 +▼ Epic 1 · 3/3 ✓15:09:3 +├ ✓ 1-auth15:09:3▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ refresh race now, or hold it for the +├ ✓ 2-session-tokensauth …epic — blank for all +├ ✓ 3-password-reset▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +└ ✓ retrospective▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +▼ Epic 2 · 2/5story — 3-1, 3.1, slug, or full key (blank for all) +├ ✓ 1-search-index▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +├ ✓ 2-typeahead▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +├ ▶ 3-search-rankingmax stories — blank for no limit +├ ◆ 4-saved-searches▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +└ ○ 5-search-analytics▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +▼ Epic 3 · 0/2X dry run (print the plan, spawn nothing)  +├ · 1-billing-portal▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +└ · 2-invoices +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + start  cancel  +Deferred Work — 2 to answer (d)▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +DW-1 Harden the OAuth token refr… +DW-2 Add pagination to the searc…▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ +DW-3 Replace the ad-hoc ranking … +DW-4 Flaky retry in the indexer … +DW-5 ✓ Polish the empty-state co… + + + + + +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + q quit  r run  s sweep  e resume  R resolve  d decisions  a attach  x stop  D delete  A archive  c cleanup  v validate  g settings  M mod^p palette diff --git a/docs/images/sweep-decision.png b/docs/images/sweep-decision.png index c06f23a..2493e6b 100644 Binary files a/docs/images/sweep-decision.png and b/docs/images/sweep-decision.png differ diff --git a/docs/images/sweep-decision.svg b/docs/images/sweep-decision.svg index bbfc9a4..f5decf5 100644 --- a/docs/images/sweep-decision.svg +++ b/docs/images/sweep-decision.svg @@ -19,227 +19,227 @@ font-weight: 700; } - .terminal-3122064392-matrix { + .terminal-3147164668-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3122064392-title { + .terminal-3147164668-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3122064392-r1 { fill: #c5c8c6 } -.terminal-3122064392-r2 { fill: #e0e0e0 } -.terminal-3122064392-r3 { fill: #a0a3a6 } -.terminal-3122064392-r4 { fill: #0053aa } -.terminal-3122064392-r5 { fill: #e2e2e2;font-weight: bold } -.terminal-3122064392-r6 { fill: #e0e0e0;font-weight: bold } -.terminal-3122064392-r7 { fill: #98e024 } -.terminal-3122064392-r8 { fill: #9d9d9d } -.terminal-3122064392-r9 { fill: #fd971f } -.terminal-3122064392-r10 { fill: #a1a1a1 } -.terminal-3122064392-r11 { fill: #fd971f;font-weight: bold } -.terminal-3122064392-r12 { fill: #ddedf9;font-weight: bold } -.terminal-3122064392-r13 { fill: #797979 } -.terminal-3122064392-r14 { fill: #262626 } -.terminal-3122064392-r15 { fill: #0178d4 } -.terminal-3122064392-r16 { fill: #121212 } -.terminal-3122064392-r17 { fill: #191919 } -.terminal-3122064392-r18 { fill: #9e9e9e } -.terminal-3122064392-r19 { fill: #58d1eb } -.terminal-3122064392-r20 { fill: #e1e1e1;font-weight: bold } -.terminal-3122064392-r21 { fill: #3e3e3e } -.terminal-3122064392-r22 { fill: #f4005f } -.terminal-3122064392-r23 { fill: #f4005f;font-weight: bold } -.terminal-3122064392-r24 { fill: #ffa62b;font-weight: bold } -.terminal-3122064392-r25 { fill: #495259 } + .terminal-3147164668-r1 { fill: #c5c8c6 } +.terminal-3147164668-r2 { fill: #e0e0e0 } +.terminal-3147164668-r3 { fill: #a0a3a6 } +.terminal-3147164668-r4 { fill: #0053aa } +.terminal-3147164668-r5 { fill: #e2e2e2;font-weight: bold } +.terminal-3147164668-r6 { fill: #e0e0e0;font-weight: bold } +.terminal-3147164668-r7 { fill: #98e024 } +.terminal-3147164668-r8 { fill: #9d9d9d } +.terminal-3147164668-r9 { fill: #fd971f } +.terminal-3147164668-r10 { fill: #a1a1a1 } +.terminal-3147164668-r11 { fill: #fd971f;font-weight: bold } +.terminal-3147164668-r12 { fill: #ddedf9;font-weight: bold } +.terminal-3147164668-r13 { fill: #797979 } +.terminal-3147164668-r14 { fill: #262626 } +.terminal-3147164668-r15 { fill: #0178d4 } +.terminal-3147164668-r16 { fill: #121212 } +.terminal-3147164668-r17 { fill: #191919 } +.terminal-3147164668-r18 { fill: #9e9e9e } +.terminal-3147164668-r19 { fill: #58d1eb } +.terminal-3147164668-r20 { fill: #e1e1e1;font-weight: bold } +.terminal-3147164668-r21 { fill: #3e3e3e } +.terminal-3147164668-r22 { fill: #f4005f } +.terminal-3147164668-r23 { fill: #f4005f;font-weight: bold } +.terminal-3147164668-r24 { fill: #ffa62b;font-weight: bold } +.terminal-3147164668-r25 { fill: #495259 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - bmad-auto + bmad-auto - + - - bmad-auto — ~/code/acme-search -Runs────────────────────────────20260611-101500-c3d4 [sweep]  ▶ running  started 2026-06-11T10:15:00  epic 2 - st  run                   type  tasks 1  done 1  deferred 0  escalated 0  369,300 tokens - 20260610-090000-a1b2  story ⚑ decision needed: DW-1 — Reopen the OAuth refresh race now, or hold it for the auth hardening epic? - ▶   20260611-101500-c3d4  sweep   press a to attach and answer - 20260612-141000-e5f6  story ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── - story                           phase             dev    review  tokens        info                               - dw-search-pagination            done              ×1     ×1      369,300       2b7f0a91c440                       -JournalLogAttention -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -22:49:18 sweep-start              cycle=1 -Sprint──────────────────────────22:49:18 triage-done              bundles=1  already_resolved=1  decisions=2 -▼ Epic 1 · 3/3 ✓22:49:18 bundle-done              bundle=search-pagination  dw_ids=DW-2 -├ ✓ 1-auth22:49:18 decision-pending         dw_id=DW-1  question=Reopen the OAuth refresh race now, or hold it for the -├ ✓ 2-session-tokensauth … -├ ✓ 3-password-reset -└ ✓ retrospective -▼ Epic 2 · 2/5 -├ ✓ 1-search-index -├ ✓ 2-typeahead -├ ▶ 3-search-ranking -├ ◆ 4-saved-searches -└ ○ 5-search-analytics -▼ Epic 3 · 0/2 -├ · 1-billing-portal -└ · 2-invoices - - -Deferred Work — 2 to answer (d) -DW-1 Harden the OAuth token refr… -DW-2 Add pagination to the searc… -DW-3 Replace the ad-hoc ranking … -DW-4 Flaky retry in the indexer … -DW-5 ✓ Polish the empty-state co… - - - - - -▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - q quit  r run  s sweep  e resume  R resolve  d decisions  a attach  x stop  D delete  A archive  c cleanup  v validate  g settings  M mod^p palette + + bmad-auto — ~/code/acme-search +Runs────────────────────────────20260611-101500-c3d4 [sweep]  ▶ running  started 2026-06-11T10:15:00  epic 2 + st  run                   type  tasks 1  done 1  deferred 0  escalated 0  369,300 tokens + 20260610-090000-a1b2  story ⚑ decision needed: DW-1 — Reopen the OAuth refresh race now, or hold it for the auth hardening epic? + ▶   20260611-101500-c3d4  sweep   press a to attach and answer + 20260612-141000-e5f6  story ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + story                           phase             dev    review  tokens        info                               + dw-search-pagination            done              ×1     ×1      369,300       2b7f0a91c440                       +JournalLogAttention +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +15:09:35 sweep-start              cycle=1 +Sprint──────────────────────────15:09:35 triage-done              bundles=1  already_resolved=1  decisions=2 +▼ Epic 1 · 3/3 ✓15:09:35 bundle-done              bundle=search-pagination  dw_ids=DW-2 +├ ✓ 1-auth15:09:35 decision-pending         dw_id=DW-1  question=Reopen the OAuth refresh race now, or hold it for the +├ ✓ 2-session-tokensauth … +├ ✓ 3-password-reset +└ ✓ retrospective +▼ Epic 2 · 2/5 +├ ✓ 1-search-index +├ ✓ 2-typeahead +├ ▶ 3-search-ranking +├ ◆ 4-saved-searches +└ ○ 5-search-analytics +▼ Epic 3 · 0/2 +├ · 1-billing-portal +└ · 2-invoices + + +Deferred Work — 2 to answer (d) +DW-1 Harden the OAuth token refr… +DW-2 Add pagination to the searc… +DW-3 Replace the ad-hoc ranking … +DW-4 Flaky retry in the indexer … +DW-5 ✓ Polish the empty-state co… + + + + + +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + q quit  r run  s sweep  e resume  R resolve  d decisions  a attach  x stop  D delete  A archive  c cleanup  v validate  g settings  M mod^p palette diff --git a/docs/setup-guide.md b/docs/setup-guide.md index a9b5d6f..fc8495d 100644 --- a/docs/setup-guide.md +++ b/docs/setup-guide.md @@ -148,8 +148,8 @@ bmad-auto 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`, or `gemini` — `init` errors on an unknown profile and lists the -valid ones. +be exactly `claude`, `codex`, `gemini`, or `copilot` — `init` errors on an unknown profile and +lists the valid ones. ### First-run notes @@ -164,6 +164,10 @@ them to whoever owns the machine: Requires Codex ≥ 0.139. - **gemini** — authenticate once (browser OAuth or `GEMINI_API_KEY`). Requires Gemini CLI ≥ 0.46. +- **copilot** — run `copilot` once in the project and authenticate (`gh` / a Copilot + subscription). Requires the Copilot **CLI** GA (≥ 2026-02) — _not_ the VS Code extension. + **Pin a capable model**: the free default (GPT-5 mini) silently skips steps in the + multi-step dev/review skills; set `[adapter] model = "claude-sonnet-4-6"` (→ `--model`). ### Skill location diff --git a/module.yaml b/module.yaml index 3089c88..818e636 100644 --- a/module.yaml +++ b/module.yaml @@ -1,7 +1,7 @@ code: bauto name: BMAD Auto Skills description: "Automation-mode skills driven by the bmad-auto orchestrator: unattended dev (bmad-auto-dev), adversarial review (bmad-auto-review), and deferred-work sweep triage (bmad-auto-sweep)" -module_version: 0.6.3 +module_version: 0.6.4 default_selected: false module_greeting: > BMAD Auto installed — both the four automation skills and the diff --git a/pyproject.toml b/pyproject.toml index 315e1e9..8339b64 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "bmad-auto" -version = "0.6.3" +version = "0.6.4" description = "Deterministic ralph-loop orchestrator for the BMAD implementation phase" readme = "README.md" license = "MIT" diff --git a/src/automator/__init__.py b/src/automator/__init__.py index 7dee1bb..7c2c4ce 100644 --- a/src/automator/__init__.py +++ b/src/automator/__init__.py @@ -6,4 +6,4 @@ spec files, and the per-run directory under .automator/runs/. """ -__version__ = "0.6.3" +__version__ = "0.6.4" diff --git a/src/automator/adapters/generic_tmux.py b/src/automator/adapters/generic_tmux.py index 1ce0adf..01180f9 100644 --- a/src/automator/adapters/generic_tmux.py +++ b/src/automator/adapters/generic_tmux.py @@ -61,12 +61,26 @@ def __init__( profile: CLIProfile, binary: str | None = None, extra_args: tuple[str, ...] | None = None, + usage_grace_s: float | None = None, + stop_without_result_nudges: int | None = None, ): self.run_dir = run_dir self.policy = policy self.profile = profile # None = use the profile's default bypass flags; a tuple replaces them self.extra_args = extra_args + # Effective timing knobs: an explicit [adapter]/[adapter.] override + # wins, else the CLI profile's shipped default, else the global fallback. + self._usage_grace_s = usage_grace_s if usage_grace_s is not None else profile.usage_grace_s + self._stop_nudges = ( + stop_without_result_nudges + if stop_without_result_nudges is not None + else ( + profile.stop_without_result_nudges + if profile.stop_without_result_nudges is not None + else policy.limits.stop_without_result_nudges + ) + ) self.name = f"{profile.name}-tmux" self.binary = binary or profile.binary self.session_name = f"bmad-auto-{run_dir.name}" @@ -192,7 +206,7 @@ def wait_for_completion(self, handle: SessionHandle, spec: SessionSpec) -> Sessi deadline = time.monotonic() + spec.timeout_s session_id: str | None = None transcript_path: str | None = None - nudges_left = self.policy.limits.stop_without_result_nudges + nudges_left = self._stop_nudges while True: remaining = deadline - time.monotonic() @@ -309,4 +323,14 @@ def kill(self, handle: SessionHandle) -> None: def read_usage(self, result: SessionResult) -> TokenUsage | None: if not result.transcript_path: return None - return tally_usage(self.profile.usage_parser, Path(result.transcript_path)) + path = Path(result.transcript_path) + # Some CLIs flush their token totals only on shutdown (Copilot writes + # modelMetrics in the trailing session.shutdown line, ~1s after the + # turn-end hook). Poll up to the effective grace so we don't sample the + # transcript before the totals land. grace 0 = read once (today's path). + deadline = time.monotonic() + self._usage_grace_s + while True: + usage = tally_usage(self.profile.usage_parser, path) + if usage is not None or time.monotonic() >= deadline: + return usage + time.sleep(RESULT_POLL_S) diff --git a/src/automator/adapters/profile.py b/src/automator/adapters/profile.py index 23bc33f..225ab0c 100644 --- a/src/automator/adapters/profile.py +++ b/src/automator/adapters/profile.py @@ -58,6 +58,17 @@ class CLIProfile: model_flag: str = "--model" env: dict[str, str] = field(default_factory=dict) usage_parser: str = "none" + # seconds to keep polling the transcript for token usage after the session + # ends. 0 = read once (the totals are already there). CLIs that flush their + # token totals only on shutdown (Copilot writes modelMetrics in the trailing + # session.shutdown line, ~1s after the turn-end hook) need a small grace so + # read_usage doesn't sample the transcript before the totals land. + usage_grace_s: float = 0.0 + # per-adapter floor for Stop-without-result nudges; None = use the global + # limits.stop_without_result_nudges. CLIs that fire a turn-end hook PER + # response turn (Copilot's agentStop) end a parallel-subagent phase across + # several turns, so the global default of 1 declares them stalled too early. + stop_without_result_nudges: int | None = None first_run_note: str = "" # project-relative gitignored configs (MCP/CLI settings) this CLI needs but # that a `git worktree add` checkout omits; provision_worktree copies them in @@ -107,6 +118,15 @@ def fail(msg: str) -> ProfileError: if usage_parser not in USAGE_PARSERS: raise fail(f"usage_parser must be one of {sorted(USAGE_PARSERS)}: got {usage_parser!r}") + usage_grace_s = float(doc.get("usage_grace_s", 0.0)) + if usage_grace_s < 0: + raise fail(f"usage_grace_s must be >= 0: got {usage_grace_s}") + + raw_nudges = doc.get("stop_without_result_nudges") + stop_nudges = None if raw_nudges is None else int(raw_nudges) + if stop_nudges is not None and stop_nudges < 0: + raise fail(f"stop_without_result_nudges must be >= 0: got {stop_nudges}") + skill_tree = str(doc.get("skill_tree", ".claude/skills")) if not skill_tree or Path(skill_tree).is_absolute(): raise fail("skill_tree must be a project-relative path") @@ -127,6 +147,8 @@ def fail(msg: str) -> ProfileError: model_flag=str(doc.get("model_flag", "--model")), env={str(k): str(v) for k, v in doc.get("env", {}).items()}, usage_parser=usage_parser, + usage_grace_s=usage_grace_s, + stop_without_result_nudges=stop_nudges, first_run_note=str(doc.get("first_run_note", "")), seed_files=seed_files, ) diff --git a/src/automator/cli.py b/src/automator/cli.py index 0a5c8df..3909464 100644 --- a/src/automator/cli.py +++ b/src/automator/cli.py @@ -75,6 +75,8 @@ def _make_adapters(project: Path, run_dir: Path, policy) -> dict[str, CodingCLIA policy=policy, profile=profile, extra_args=cfg.extra_args, + usage_grace_s=cfg.usage_grace_s, + stop_without_result_nudges=cfg.stop_without_result_nudges, ) adapters[role] = by_cfg[cfg] return adapters diff --git a/src/automator/data/profiles/copilot.toml b/src/automator/data/profiles/copilot.toml index cbbba41..1503c36 100644 --- a/src/automator/data/profiles/copilot.toml +++ b/src/automator/data/profiles/copilot.toml @@ -24,6 +24,15 @@ launch_args = ["-i"] bypass_args = ["--allow-all-tools", "--allow-all-paths"] model_flag = "--model" usage_parser = "copilot-events" +# Copilot writes its token totals (modelMetrics) only in the trailing +# `session.shutdown` events.jsonl line, ~1s AFTER the agentStop hook fires — so +# read usage on a short grace poll, not the instant the turn ends, or it samples +# the transcript before the totals land (reads as 0/None). +usage_grace_s = 8.0 +# agentStop fires PER response turn, so a parallel-subagent review (e.g. the +# bmad-auto-review layers) ends several turns; the global default of 1 nudge +# would declare it stalled on the 2nd Stop. 5 lets a multi-turn review finish. +stop_without_result_nudges = 5 first_run_note = "run `copilot` once and authenticate (gh / Copilot subscription); requires Copilot CLI GA (>= 2026-02)" skill_tree = ".agents/skills" # .github/copilot/settings.json is the inline hook config (and can also hold MCP diff --git a/src/automator/data/settings/core.toml b/src/automator/data/settings/core.toml index bf3be56..fb73fc7 100644 --- a/src/automator/data/settings/core.toml +++ b/src/automator/data/settings/core.toml @@ -111,6 +111,20 @@ kind = "args" key = "cleanup_session_on_finish" kind = "switch" default_ref = "AdapterPolicy.cleanup_session_on_finish" +[[section.field]] +key = "usage_grace_s" +kind = "float" +minimum = 0 +default_ref = "AdapterPolicy.usage_grace_s" +placeholder = "inherit from CLI profile (copilot ships 8s)" +description = "seconds to poll the transcript for token usage after a session ends; CLIs that flush totals on shutdown (copilot) need a grace" +[[section.field]] +key = "stop_without_result_nudges" +kind = "int" +minimum = 0 +default_ref = "AdapterPolicy.stop_without_result_nudges" +placeholder = "inherit (copilot profile ships 5)" +description = "result-less Stop signals tolerated before a session is called stalled; overrides the global limits value for this CLI" # Per-stage adapter overrides. One section per [adapter.dev|review|triage] table, # expanded from this single template by the STAGES loop so they never drift. @@ -129,6 +143,18 @@ placeholder = "inherit / client default" [[section.field]] key = "extra_args" kind = "args" +[[section.field]] +key = "usage_grace_s" +kind = "float" +minimum = 0 +default_ref = "StageAdapterPolicy.usage_grace_s" +placeholder = "inherit from [adapter]" +[[section.field]] +key = "stop_without_result_nudges" +kind = "int" +minimum = 0 +default_ref = "StageAdapterPolicy.stop_without_result_nudges" +placeholder = "inherit from [adapter]" [[section]] name = "sweep" diff --git a/src/automator/data/skills/bmad-auto-setup/assets/module.yaml b/src/automator/data/skills/bmad-auto-setup/assets/module.yaml index 3089c88..818e636 100644 --- a/src/automator/data/skills/bmad-auto-setup/assets/module.yaml +++ b/src/automator/data/skills/bmad-auto-setup/assets/module.yaml @@ -1,7 +1,7 @@ code: bauto name: BMAD Auto Skills description: "Automation-mode skills driven by the bmad-auto orchestrator: unattended dev (bmad-auto-dev), adversarial review (bmad-auto-review), and deferred-work sweep triage (bmad-auto-sweep)" -module_version: 0.6.3 +module_version: 0.6.4 default_selected: false module_greeting: > BMAD Auto installed — both the four automation skills and the diff --git a/src/automator/policy.py b/src/automator/policy.py index 58459f7..ecff2ee 100644 --- a/src/automator/policy.py +++ b/src/automator/policy.py @@ -104,6 +104,9 @@ class StageAdapterPolicy: name: str | None = None model: str | None = None extra_args: tuple[str, ...] | None = None + # None = inherit from [adapter] (which itself falls back to the CLI profile) + usage_grace_s: float | None = None + stop_without_result_nudges: int | None = None @dataclass(frozen=True) @@ -112,6 +115,10 @@ class ResolvedAdapter: model: str # None = use the profile's default bypass flags; a list replaces them extra_args: tuple[str, ...] | None + # None = fall back to the CLI profile's default (usage_grace_s) / the global + # limits.stop_without_result_nudges respectively + usage_grace_s: float | None = None + stop_without_result_nudges: int | None = None @dataclass(frozen=True) @@ -123,6 +130,10 @@ class AdapterPolicy: # kill the run's bmad-auto- tmux session when it finishes (False keeps # it around for post-run inspection) cleanup_session_on_finish: bool = True + # None = inherit from the selected CLI profile / global limits (see + # ResolvedAdapter); a value overrides the profile's shipped default. + usage_grace_s: float | None = None + stop_without_result_nudges: int | None = None dev: StageAdapterPolicy = field(default_factory=StageAdapterPolicy) review: StageAdapterPolicy = field(default_factory=StageAdapterPolicy) triage: StageAdapterPolicy = field(default_factory=StageAdapterPolicy) @@ -130,12 +141,21 @@ class AdapterPolicy: def resolved(self, role: str) -> ResolvedAdapter: stage = {"dev": self.dev, "review": self.review, "triage": self.triage}.get(role) if stage is None: - return ResolvedAdapter(self.name, self.model, self.extra_args) + return ResolvedAdapter( + self.name, + self.model, + self.extra_args, + self.usage_grace_s, + self.stop_without_result_nudges, + ) name = stage.name if stage.name is not None else self.name # model and extra_args are client-specific: inherit from the base only # when the stage runs the same client; a client switch falls back to # that profile's defaults (CLI default model, profile bypass flags). same_client = name == self.name + # usage_grace_s / stop_without_result_nudges are benign timing knobs that + # mean "fall back to the profile default" when None, so plain stage ?? + # base inheritance is safe regardless of a client switch. return ResolvedAdapter( name=name, model=(stage.model if stage.model is not None else (self.model if same_client else "")), @@ -144,6 +164,14 @@ def resolved(self, role: str) -> ResolvedAdapter: if stage.extra_args is not None else (self.extra_args if same_client else None) ), + usage_grace_s=( + stage.usage_grace_s if stage.usage_grace_s is not None else self.usage_grace_s + ), + stop_without_result_nudges=( + stage.stop_without_result_nudges + if stage.stop_without_result_nudges is not None + else self.stop_without_result_nudges + ), ) @@ -253,6 +281,26 @@ def _section(doc: dict[str, Any], name: str) -> dict[str, Any]: return value +def _opt_grace(d: dict[str, Any], where: str) -> float | None: + raw = d.get("usage_grace_s") + if raw is None: + return None + value = float(raw) + if value < 0: + raise PolicyError(f"{where}.usage_grace_s must be >= 0: got {value}") + return value + + +def _opt_nudges(d: dict[str, Any], where: str) -> int | None: + raw = d.get("stop_without_result_nudges") + if raw is None: + return None + value = int(raw) + if value < 0: + raise PolicyError(f"{where}.stop_without_result_nudges must be >= 0: got {value}") + return value + + def _stage_adapter(adapter_d: dict[str, Any], key: str) -> StageAdapterPolicy: raw = adapter_d.get(key, {}) if not isinstance(raw, dict): @@ -262,6 +310,8 @@ def _stage_adapter(adapter_d: dict[str, Any], key: str) -> StageAdapterPolicy: name=None if raw.get("name") is None else str(raw["name"]), model=None if raw.get("model") is None else str(raw["model"]), extra_args=None if raw_extra is None else tuple(str(a) for a in raw_extra), + usage_grace_s=_opt_grace(raw, f"adapter.{key}"), + stop_without_result_nudges=_opt_nudges(raw, f"adapter.{key}"), ) @@ -382,6 +432,8 @@ def loads(text: str, plugin_schemas: dict[str, Any] | None = None) -> Policy: cleanup_session_on_finish=bool( adapter_d.get("cleanup_session_on_finish", AdapterPolicy.cleanup_session_on_finish) ), + usage_grace_s=_opt_grace(adapter_d, "adapter"), + stop_without_result_nudges=_opt_nudges(adapter_d, "adapter"), dev=_stage_adapter(adapter_d, "dev"), review=_stage_adapter(adapter_d, "review"), triage=_stage_adapter(adapter_d, "triage"), @@ -568,6 +620,11 @@ def _fold_deprecated_engine( 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: # extra_args = ["--permission-mode", "bypassPermissions"] +# Optional overrides of the CLI profile's own defaults (unset = inherit the +# profile's shipped value; copilot ships usage_grace_s = 8 and +# stop_without_result_nudges = 5): +# usage_grace_s = 8.0 # seconds to poll the transcript for token usage after a session ends +# stop_without_result_nudges = 5 # result-less Stop signals tolerated before a session is called stalled # Per-stage overrides for the dev, review and sweep-triage passes. Unset keys # inherit from [adapter] when the stage runs the same client; a stage that @@ -579,6 +636,7 @@ def _fold_deprecated_engine( # [adapter.review] # name = "codex" # model = "gpt-5-codex" +# stop_without_result_nudges = 5 # e.g. a multi-turn review needs more nudges than dev # [adapter.triage] # model = "opus" diff --git a/src/automator/tui/screens/settings_screen.py b/src/automator/tui/screens/settings_screen.py index 51b2e86..e053b78 100644 --- a/src/automator/tui/screens/settings_screen.py +++ b/src/automator/tui/screens/settings_screen.py @@ -193,7 +193,7 @@ def _compose_field(self, spec: SettingSpec) -> ComposeResult: elif spec.kind in ("int", "float"): yield Input( value=None if raw is None else str(raw), - placeholder=f"default: {spec.default}", + placeholder=spec.placeholder or f"default: {spec.default}", type="integer" if spec.kind == "int" else "number", validators=[Number(minimum=spec.minimum, maximum=spec.maximum)], valid_empty=True, diff --git a/tests/test_generic_tmux.py b/tests/test_generic_tmux.py index 9cca0c9..f61b775 100644 --- a/tests/test_generic_tmux.py +++ b/tests/test_generic_tmux.py @@ -13,9 +13,11 @@ import pytest -from automator.adapters.base import SessionSpec +from automator.adapters import generic_tmux +from automator.adapters.base import SessionResult, SessionSpec from automator.adapters.generic_tmux import GenericTmuxAdapter from automator.adapters.profile import get_profile +from automator.model import TokenUsage from automator.policy import LimitsPolicy, Policy HAVE_TMUX = shutil.which("tmux") is not None @@ -149,6 +151,88 @@ def test_await_result_grace_expires_fast(tmp_path): assert time.monotonic() - start < 5 +def _usage_adapter(tmp_path, profile_name, **kw) -> GenericTmuxAdapter: + return GenericTmuxAdapter( + run_dir=tmp_path / "run", + policy=Policy(limits=LimitsPolicy()), + profile=get_profile(profile_name), + **kw, + ) + + +def test_effective_timing_knobs_precedence(tmp_path): + # copilot ships grace 8 / nudges 5; with no override the profile value wins + cop = _usage_adapter(tmp_path, "copilot") + assert cop._usage_grace_s == 8.0 + assert cop._stop_nudges == 5 + # claude ships neither -> grace 0, nudges from the global limits default (1) + cla = _usage_adapter(tmp_path, "claude") + assert cla._usage_grace_s == 0.0 + assert cla._stop_nudges == 1 + # an explicit [adapter]/[adapter.] override beats the profile default + over = _usage_adapter(tmp_path, "copilot", usage_grace_s=2.0, stop_without_result_nudges=9) + assert over._usage_grace_s == 2.0 + assert over._stop_nudges == 9 + + +def test_effective_nudges_fall_back_to_global_limits(tmp_path): + # claude carries no profile nudge value, so the global limits value flows through + cla = GenericTmuxAdapter( + run_dir=tmp_path / "run", + policy=Policy(limits=LimitsPolicy(stop_without_result_nudges=4)), + profile=get_profile("claude"), + ) + assert cla._stop_nudges == 4 + # the copilot profile floor still wins over a lower global default + cop = GenericTmuxAdapter( + run_dir=tmp_path / "run2", + policy=Policy(limits=LimitsPolicy(stop_without_result_nudges=2)), + profile=get_profile("copilot"), + ) + assert cop._stop_nudges == 5 + + +def test_read_usage_polls_for_late_metrics(tmp_path, monkeypatch): + # copilot ships usage_grace_s = 8.0, so read_usage retries until metrics land + adapter = _usage_adapter(tmp_path, "copilot") + usage = TokenUsage(input_tokens=10) + calls: list[str] = [] + + def fake_tally(parser, path): + calls.append(parser) + return None if len(calls) < 3 else usage + + monkeypatch.setattr(generic_tmux, "tally_usage", fake_tally) + monkeypatch.setattr(generic_tmux.time, "sleep", lambda *_: None) + result = SessionResult(status="completed", transcript_path=str(tmp_path / "events.jsonl")) + assert adapter.read_usage(result) is usage + assert len(calls) == 3 # polled past the early None reads + + +def test_read_usage_single_read_when_no_grace(tmp_path, monkeypatch): + # claude has usage_grace_s = 0.0 -> read exactly once, never sleeps + adapter = _usage_adapter(tmp_path, "claude") + calls: list[str] = [] + + def fake_tally(parser, path): + calls.append(parser) + return None + + def no_sleep(*_): + raise AssertionError("read_usage must not sleep when the grace is 0") + + monkeypatch.setattr(generic_tmux, "tally_usage", fake_tally) + monkeypatch.setattr(generic_tmux.time, "sleep", no_sleep) + result = SessionResult(status="completed", transcript_path=str(tmp_path / "x.jsonl")) + assert adapter.read_usage(result) is None + assert len(calls) == 1 + + +def test_read_usage_none_without_transcript(tmp_path): + adapter = _usage_adapter(tmp_path, "copilot") + assert adapter.read_usage(SessionResult(status="completed")) is None + + def _write_fake_cli(tmp_path): fake = tmp_path / "fake-cli" fake.write_text(FAKE_CLI) diff --git a/tests/test_policy.py b/tests/test_policy.py index 1db5c79..5858e76 100644 --- a/tests/test_policy.py +++ b/tests/test_policy.py @@ -104,6 +104,56 @@ def test_unknown_role_resolves_to_base(tmp_path): assert pol.adapter.resolved("retro") == policy.ResolvedAdapter("claude", "", None) +def test_adapter_timing_knobs_base_and_per_stage(tmp_path): + p = tmp_path / "policy.toml" + p.write_text(""" +[adapter] +name = "copilot" +usage_grace_s = 3.5 +[adapter.review] +stop_without_result_nudges = 7 +""") + pol = policy.load(p) + assert pol.adapter.usage_grace_s == 3.5 + assert pol.adapter.stop_without_result_nudges is None + # base usage_grace_s inherits into every stage; review adds a nudge override + review = pol.adapter.resolved("review") + assert review.usage_grace_s == 3.5 + assert review.stop_without_result_nudges == 7 + # dev inherits the base grace and leaves nudges unset (= fall back to profile/global) + dev = pol.adapter.resolved("dev") + assert dev.usage_grace_s == 3.5 + assert dev.stop_without_result_nudges is None + + +def test_adapter_timing_knobs_default_none(tmp_path): + # unset = None on both base and stages, so the adapter falls back to the profile + pol = policy.load(None) + assert pol.adapter.usage_grace_s is None + assert pol.adapter.stop_without_result_nudges is None + assert pol.adapter.resolved("dev").usage_grace_s is None + assert pol.adapter.resolved("dev").stop_without_result_nudges is None + + +@pytest.mark.parametrize( + ("body", "match"), + [ + ("[adapter]\nusage_grace_s = -1\n", r"adapter\.usage_grace_s"), + ("[adapter]\nstop_without_result_nudges = -1\n", r"adapter\.stop_without_result_nudges"), + ("[adapter.review]\nusage_grace_s = -1\n", r"adapter\.review\.usage_grace_s"), + ( + "[adapter.review]\nstop_without_result_nudges = -1\n", + r"adapter\.review\.stop_without_result_nudges", + ), + ], +) +def test_adapter_timing_knobs_reject_negatives(tmp_path, body, match): + p = tmp_path / "policy.toml" + p.write_text(body) + with pytest.raises(policy.PolicyError, match=match): + policy.load(p) + + def test_legacy_model_keys_rejected(tmp_path): p = tmp_path / "policy.toml" p.write_text('[adapter]\nmodel_dev = "haiku"\n') diff --git a/tests/test_profile.py b/tests/test_profile.py index bf160f4..67d881b 100644 --- a/tests/test_profile.py +++ b/tests/test_profile.py @@ -43,6 +43,24 @@ def test_builtin_profiles_load(): "sessionEnd": "SessionEnd", } assert profiles["copilot"].usage_parser == "copilot-events" + # copilot writes token totals only on shutdown (poll grace) and fires + # agentStop per turn (multi-turn reviews need more nudges) + assert profiles["copilot"].usage_grace_s == 8.0 + assert profiles["copilot"].stop_without_result_nudges == 5 + # other built-ins keep the defaults: read usage once, inherit the global nudge limit + for name in ("claude", "codex", "gemini"): + assert profiles[name].usage_grace_s == 0.0 + assert profiles[name].stop_without_result_nudges is None + + +def test_usage_grace_and_nudges_default_when_unset(tmp_path): + # MINIMAL_PROFILE omits both -> 0.0 / None + profiles_dir = tmp_path / ".automator" / "profiles" + profiles_dir.mkdir(parents=True) + (profiles_dir / "mycli.toml").write_text(MINIMAL_PROFILE) + prof = load_profiles(tmp_path)["mycli"] + assert prof.usage_grace_s == 0.0 + assert prof.stop_without_result_nudges is None def test_seed_files_default_empty_when_unset(tmp_path): @@ -127,6 +145,14 @@ def test_user_profile_overlay(tmp_path): ), "seed_files", ), + ( + MINIMAL_PROFILE.replace("[hooks]", "usage_grace_s = -1\n[hooks]"), + "usage_grace_s", + ), + ( + MINIMAL_PROFILE.replace("[hooks]", "stop_without_result_nudges = -2\n[hooks]"), + "stop_without_result_nudges", + ), ], ) def test_invalid_profiles_rejected(tmp_path, mutation, match): diff --git a/tests/test_tui_settings.py b/tests/test_tui_settings.py index 8b6d15b..686c463 100644 --- a/tests/test_tui_settings.py +++ b/tests/test_tui_settings.py @@ -93,6 +93,25 @@ def test_scalar_added_next_to_existing_stage_table_parses(tmp_path): assert pol.adapter.dev.model == "opus" +def test_adapter_timing_knobs_round_trip(tmp_path): + # unset -> inherit (None on base and stages); set a number -> persisted; + # clear -> back to inherit. Mirrors how the TUI int/float collect maps an + # empty box to None (delete key) and a value to the parsed number. + doc = fresh_doc(tmp_path) + base = policy_mod.loads(doc.dumps()).adapter + assert base.usage_grace_s is None and base.stop_without_result_nudges is None + + doc.set("adapter", "usage_grace_s", 6.0) + doc.set("adapter.review", "stop_without_result_nudges", 5) + pol = policy_mod.loads(doc.dumps()) + assert pol.adapter.usage_grace_s == 6.0 + assert pol.adapter.resolved("review").stop_without_result_nudges == 5 + assert doc.validate() is None + + doc.set("adapter", "usage_grace_s", None) + assert policy_mod.loads(doc.dumps()).adapter.usage_grace_s is None + + def test_validate_surfaces_policy_error(tmp_path): doc = fresh_doc(tmp_path) doc.set("gates", "mode", "bogus") diff --git a/uv.lock b/uv.lock index 02ce071..86ecba6 100644 --- a/uv.lock +++ b/uv.lock @@ -4,7 +4,7 @@ requires-python = ">=3.11" [[package]] name = "bmad-auto" -version = "0.6.3" +version = "0.6.4" source = { editable = "." } dependencies = [ { name = "pyyaml" },