diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 00b416d..8b3e93e 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.0", + "version": "0.6.1", "author": { "name": "pinkyd" }, diff --git a/.gitignore b/.gitignore index fe193a2..cf586a7 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ build/ dist/ _bmad/ dev/skills/bmad-release/ +RELEASING.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 03317d7..5534d89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,20 @@ 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.1] — 2026-06-20 + +### Added + +- **Short run refs (Docker-style).** Every command that takes a run id (`status`, `attach`, + `resume`, `resolve`, `stop`, `delete`, `archive`) now accepts a partial — the tail after the + last `-` (e.g. `a1b2`, or as few chars as stay unique). Full ids still work; an ambiguous ref + fails listing the candidates. New `bmad-auto list` (alias `ls`) prints each run/sweep with its + short ref, type, and status. +- **Flexible `--story` selection.** `bmad-auto run --story` now takes more than the exact full + key: an epic+number (`--epic 3 --story 1`, `--story 3-1`, or `--story 3.1`) or a slug fragment + (`--story user-auth`). Full keys still work. Mismatches are caught before the run launches with a + targeted error — no match, ambiguous slug, or matched-but-not-actionable. + ## [0.6.0] — 2026-06-20 ### Fixed @@ -415,6 +429,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.1]: https://github.com/bmad-code-org/bmad-auto/releases/tag/v0.6.1 [0.6.0]: https://github.com/bmad-code-org/bmad-auto/releases/tag/v0.6.0 [0.5.0]: https://github.com/bmad-code-org/bmad-auto/releases/tag/v0.5.0 [0.4.4]: https://github.com/bmad-code-org/bmad-auto/releases/tag/v0.4.4 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 10ed7ef..5cc170e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -90,7 +90,7 @@ trunk fmt # auto-format changed files trunk check # lint + format verification (what CI runs) ``` -Releases are cut by maintainers — see [RELEASING.md](RELEASING.md) for the version-bump and changelog workflow. The version field is validated in CI; if you touch it, run `uv run --no-project python scripts/sync_version.py --check`. +Releases are cut by maintainers. The version field is validated in CI; if you touch it, run `uv run --no-project python scripts/sync_version.py --check`. --- @@ -98,7 +98,7 @@ Releases are cut by maintainers — see [RELEASING.md](RELEASING.md) for the ver ### Target Branch -Submit PRs to the `main` branch. We use trunk-based development. Releases are cut from `main` (see [RELEASING.md](RELEASING.md)). +Submit PRs to the `main` branch. We use trunk-based development. Releases are cut from `main`. ### PR Size diff --git a/README.md b/README.md index 636e8a9..afef13b 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ bmad-auto tui # …or drive everything from the dashboard | `bmad-auto resume ` | Continue a run paused at a gate, escalation, or interruption. | | `bmad-auto resolve ` | Resolve a CRITICAL escalation: open an interactive resolve agent to fix the frozen spec, then re-arm the story and resume. `--story KEY`, `--no-interactive`, `--resume` / `--no-resume`. | | `bmad-auto decisions` | Answer deferred-work decisions earlier sweeps left unanswered (skipped by `--no-prompt`, or an abandoned interactive sweep). Recorded so the next sweep acts on them without re-asking. `--list` shows them without answering. | +| `bmad-auto list` (`ls`) | List every run/sweep with its short ref, type, and status — the handle you pass to the commands below. | | `bmad-auto status []` | Run + sprint summary with per-story token totals (plus a count of decisions awaiting an answer). | | `bmad-auto attach []` | tmux-attach to a run's live agent session. | | `bmad-auto stop ` | Stop a live run — the engine and its agent tmux session. | @@ -76,7 +77,9 @@ bmad-auto tui # …or drive everything from the dashboard | `bmad-auto clean` | Reclaim **disk** from concluded runs per `[cleanup]`: tear down git worktrees a mid-flight stop orphaned (freeing their Unity `Library/` + MCP-server builds), trim the heavy `worktrees/` tree from runs kept for history (they stay viewable in the TUI), and archive/delete runs past the retention window. Only finished/stopped runs are touched; `--dry-run` previews, `--keep ` protects, `--retain N` overrides the window, `--hard` deletes instead of archiving. | | `bmad-auto tui` | The interactive dashboard (needs the `[tui]` extra). `--low-frame-rate` caps it to 15fps + disables animations (fixes repaint tearing over slow/SSH links; also `[tui] low_frame_rate`). | -Every command takes `--project ` (default: the current directory). +Every command takes `--project ` (default: the current directory). Any `` may be a +partial — the tail after the last `-` (e.g. `a1b2`), shortened to any prefix that stays unique; +`bmad-auto list` shows each run's short ref. ## The TUI diff --git a/RELEASING.md b/RELEASING.md deleted file mode 100644 index c0278d6..0000000 --- a/RELEASING.md +++ /dev/null @@ -1,99 +0,0 @@ -# Releasing `bmad-auto` - -One standardized flow. Cutting a release is: **pick a version → curate the -CHANGELOG → run `prepare` → open a PR → merge.** Everything mechanical (version -stamping, asset regeneration, tagging, the GitHub release) is automated, and the -tag + release are created automatically once the PR merges to `main`. - -## TL;DR - -```bash -git switch -c release/0.5.0 # a release/feature branch (never main) -$EDITOR CHANGELOG.md # add a curated `## [0.5.0] — ` section -python scripts/release.py prepare 0.5.0 # stamp + regen assets (if TUI changed) + commit -git push -u origin release/0.5.0 -gh pr create # then: wait for green CI, merge -# → .github/workflows/release.yml tags v0.5.0 and publishes the GitHub release -``` - -## The flow in detail - -### 1. Branch - -Work on a `release/X.Y.Z` (or feature) branch. `prepare` refuses to run on `main`. - -### 2. Curate the CHANGELOG - -Add a section for the new version **before** running `prepare` — the engine -validates it exists and is non-empty. To see what shipped since the last tag: - -```bash -python scripts/release.py commits # commits since the last vX.Y.Z, grouped by type -``` - -Write the entry in the house style (see [CHANGELOG style](#changelog-style)). -Use today's date: `## [0.5.0] — YYYY-MM-DD`. You don't need to add the -`[0.5.0]: …/tag/v0.5.0` link reference at the bottom — `prepare` inserts it. - -### 3. Prepare - -```bash -python scripts/release.py prepare 0.5.0 -``` - -This validates preconditions (clean-ish tree, branch ≠ main, tag absent, version -strictly greater than the current one, CHANGELOG section present), then: - -1. inserts the CHANGELOG link reference if missing; -2. runs `scripts/sync_version.py 0.5.0` — stamps `__init__.py`, `pyproject.toml`, - both `module.yaml` copies, `marketplace.json`, and re-locks `uv.lock`; -3. regenerates `docs/images/` screenshots + `demo.gif` **only if `src/automator/tui` - changed since the last tag** (override with `--force-assets` / `--no-assets`); -4. runs `trunk fmt` if available (keeps the commit lint-clean); -5. commits everything as `chore(release): 0.5.0 — ` (explicit paths only — - it never `git add -A`). - -Useful flags: `--dry-run` (report, mutate nothing), `--force-assets`, `--no-assets`, -`--allow-dirty` (proceed despite unrelated dirty files; still commits only known paths). - -### 4. PR → merge → auto-publish - -Push the branch, open a PR, wait for CI (`test`, `version-sync`, `lint`) to go green, -and merge. On the merge to `main`, **`.github/workflows/release.yml`** runs -`release.py publish`, which: - -- no-ops if `vX.Y.Z` already exists (idempotent — non-release merges do nothing); -- otherwise creates the `vX.Y.Z` tag at the merge commit and a GitHub release whose - notes are the CHANGELOG section for that version. - -Nothing to do by hand after merge. If you ever need to publish manually (e.g. the -workflow was disabled), run `python scripts/release.py publish` on `main` yourself. - -## CHANGELOG style - -Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), `### Added` / -`### Fixed` / `### Changed`. Entries must be **curated prose, never pasted commit -messages or raw info dumps**: - -- Lead each entry with a **bolded subject**, then 1–2 tight sentences on the - user-facing impact and the why — not the implementation diary. -- One entry per meaningful change; fold incidental commits into the relevant entry. -- Prefer concision. If an entry runs past a few sentences, it's probably a dump — - trim it to the change and its consequence. - -```markdown -## [0.5.0] — 2026-07-01 - -### Fixed - -- **Cleanup no longer stops other projects' runs.** tmux sessions are global; a run - id not found under the current project was treated as a prunable orphan and could - match another project's live session. Sessions are now stamped with their project - and cleanup only prunes its own. -``` - -## Local pre-flight - -`python scripts/release.py check` mirrors the CI guards (version-sync + CHANGELOG -section present). Run a full `trunk check` (no path filter) before pushing — CI -lints prettier/markdownlint/yaml beyond ruff/black. diff --git a/docs/FEATURES.md b/docs/FEATURES.md index 6ddc609..447f595 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -161,6 +161,7 @@ See [README.md](../README.md) for the narrative overview and [setup-guide.md](se - `bmad-auto resume ` — continue a paused/interrupted run. - `bmad-auto resolve ` — resolve a CRITICAL escalation, then re-arm + resume. - `bmad-auto decisions` — answer deferred-work decisions past sweeps left unanswered (`--list` to just show them). +- `bmad-auto list` (`ls`) — list every run/sweep with its short ref, type, and status. - `bmad-auto status []` — run + sprint summary with per-story token totals. - `bmad-auto attach []` — tmux-attach to a run's live agent session. - `bmad-auto stop ` — stop a live run (engine + agent session). @@ -169,4 +170,4 @@ See [README.md](../README.md) for the narrative overview and [setup-guide.md](se - `bmad-auto cleanup` — remove leftover tmux artifacts for finished/stopped runs. - `bmad-auto clean` — reclaim disk from concluded runs per `[cleanup]`: tear down worktrees a mid-flight stop orphaned, trim heavy `worktrees/` from runs kept for history, archive/delete past the retention window (`--dry-run`, `--keep`, `--retain N`, `--hard`). - `bmad-auto tui` — the interactive dashboard (`--low-frame-rate` for slow/SSH links). -- Every command takes `--project ` (default: current directory). +- Every command takes `--project ` (default: current directory). Any `` accepts a partial — the tail after the last `-`, shortened to any unique prefix. diff --git a/docs/images/dashboard.png b/docs/images/dashboard.png index 6a5a59b..b6fb47d 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 e05c7f3..20ea9ca 100644 --- a/docs/images/dashboard.svg +++ b/docs/images/dashboard.svg @@ -19,226 +19,226 @@ font-weight: 700; } - .terminal-1370716297-matrix { + .terminal-145783149-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1370716297-title { + .terminal-145783149-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1370716297-r1 { fill: #c5c8c6 } -.terminal-1370716297-r2 { fill: #e0e0e0 } -.terminal-1370716297-r3 { fill: #a0a3a6 } -.terminal-1370716297-r4 { fill: #0053aa } -.terminal-1370716297-r5 { fill: #e2e2e2;font-weight: bold } -.terminal-1370716297-r6 { fill: #e0e0e0;font-weight: bold } -.terminal-1370716297-r7 { fill: #98e024 } -.terminal-1370716297-r8 { fill: #9d9d9d } -.terminal-1370716297-r9 { fill: #fd971f } -.terminal-1370716297-r10 { fill: #a1a1a1 } -.terminal-1370716297-r11 { fill: #ddedf9;font-weight: bold } -.terminal-1370716297-r12 { fill: #797979 } -.terminal-1370716297-r13 { fill: #262626 } -.terminal-1370716297-r14 { fill: #0178d4 } -.terminal-1370716297-r15 { fill: #e1e1e1;font-weight: bold } -.terminal-1370716297-r16 { fill: #121212 } -.terminal-1370716297-r17 { fill: #191919 } -.terminal-1370716297-r18 { fill: #9e9e9e } -.terminal-1370716297-r19 { fill: #58d1eb } -.terminal-1370716297-r20 { fill: #3e3e3e } -.terminal-1370716297-r21 { fill: #f4005f } -.terminal-1370716297-r22 { fill: #f4005f;font-weight: bold } -.terminal-1370716297-r23 { fill: #ffa62b;font-weight: bold } -.terminal-1370716297-r24 { fill: #495259 } + .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 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - 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 ✓00:30:40 session-start            task_id=2-1-search-index  role=dev  story_key=2-1-search-index -├ ✓ 1-auth00:30:40 story-done               story_key=2-1-search-index  commit=a1b2c3d4e5f6 -├ ✓ 2-session-tokens00:30:40 session-start            task_id=2-2-typeahead  role=dev  story_key=2-2-typeahead -├ ✓ 3-password-reset00:30:40 review-cycle             story_key=2-2-typeahead  cycle=2  findings=3 -└ ✓ retrospective00:30:40 story-done               story_key=2-2-typeahead  commit=b2c3d4e5f607 -▼ Epic 2 · 2/500:30:40 story-start              story_key=2-3-search-ranking  epic=2 -├ ✓ 1-search-index00:30:40 spec-approved            story_key=2-3-search-ranking  tokens=1834 -├ ✓ 2-typeahead00:30:40 dev-done                 story_key=2-3-search-ranking  tasks_done=3  tasks_total=3 -├ ▶ 3-search-ranking00:30:40 verify-ok                story_key=2-3-search-ranking  commands=pytest -q, ruff check . -├ ◆ 4-saved-searches00:30:40 review-start             story_key=2-3-search-ranking  role=review -└ ○ 5-search-analytics00:30:40 escalation-preference    story_key=2-3-search-ranking  detail=reviewer used codex for the scoring math -▼ Epic 3 · 0/200:30:40 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 ✓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 diff --git a/docs/images/demo.gif b/docs/images/demo.gif index 7c73453..4f9ba0c 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 486af28..adf4b11 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 e201f1f..19013ba 100644 --- a/docs/images/settings-scm.svg +++ b/docs/images/settings-scm.svg @@ -19,215 +19,216 @@ font-weight: 700; } - .terminal-3126485668-matrix { + .terminal-505119418-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3126485668-title { + .terminal-505119418-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3126485668-r1 { fill: #c5c8c6 } -.terminal-3126485668-r2 { fill: #e0e0e0 } -.terminal-3126485668-r3 { fill: #a0a3a6 } -.terminal-3126485668-r4 { fill: #1e1e1e } -.terminal-3126485668-r5 { fill: #191919 } -.terminal-3126485668-r6 { fill: #a5a5a5;font-style: italic; } -.terminal-3126485668-r7 { fill: #a5a5a5 } -.terminal-3126485668-r8 { fill: #7f7f7f } -.terminal-3126485668-r9 { fill: #737373 } -.terminal-3126485668-r10 { fill: #121212 } -.terminal-3126485668-r11 { fill: #000000 } -.terminal-3126485668-r12 { fill: #ffa62b;font-weight: bold } -.terminal-3126485668-r13 { fill: #495259 } + .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 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - bmad-auto + bmad-auto - - - - bmad-auto — ~/code/acme-search -▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ -none: work in place on the checked-out branch (default) · worktree: run each story in its own git worktree, -merge back to the target branch - -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -branch_perdefault: story -▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ -worktree mode: one branch per story, or one shared branch per run (run forces delete-branch off so the -shared branch survives) - -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -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▁▁ - -▔▔▔▔▔▔▔▔ -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.png b/docs/images/settings.png index f0f7e6e..01a13b8 100644 Binary files a/docs/images/settings.png and b/docs/images/settings.png differ diff --git a/docs/images/settings.svg b/docs/images/settings.svg index fc54706..dab23ad 100644 --- a/docs/images/settings.svg +++ b/docs/images/settings.svg @@ -19,210 +19,209 @@ font-weight: 700; } - .terminal-209972382-matrix { + .terminal-1072437773-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-209972382-title { + .terminal-1072437773-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-209972382-r1 { fill: #c5c8c6 } -.terminal-209972382-r2 { fill: #e0e0e0 } -.terminal-209972382-r3 { fill: #a0a3a6 } -.terminal-209972382-r4 { fill: #121212 } -.terminal-209972382-r5 { fill: #6f6f6f } -.terminal-209972382-r6 { fill: #000000 } -.terminal-209972382-r7 { fill: #ffa62b;font-weight: bold } -.terminal-209972382-r8 { fill: #495259 } + .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 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - 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 - -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▅▅ -▶ tui — dashboard rendering (slow-link / SSH tuning) - - - 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 0b702ab..10da48e 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 03b01c4..49d8c20 100644 --- a/docs/images/start-run-modal.svg +++ b/docs/images/start-run-modal.svg @@ -19,241 +19,241 @@ font-weight: 700; } - .terminal-83744645-matrix { + .terminal-3222002520-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-83744645-title { + .terminal-3222002520-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-83744645-r1 { fill: #e0e0e0 } -.terminal-83744645-r2 { fill: #646464 } -.terminal-83744645-r3 { fill: #4a4c4d } -.terminal-83744645-r4 { fill: #c5c8c6 } -.terminal-83744645-r5 { fill: #0a2c4e } -.terminal-83744645-r6 { fill: #646464;font-weight: bold } -.terminal-83744645-r7 { fill: #476419 } -.terminal-83744645-r8 { fill: #494949 } -.terminal-83744645-r9 { fill: #704717 } -.terminal-83744645-r10 { fill: #4a4a4a } -.terminal-83744645-r11 { fill: #704717;font-weight: bold } -.terminal-83744645-r12 { fill: #3b3b3b } -.terminal-83744645-r13 { fill: #1a1a1a } -.terminal-83744645-r14 { fill: #0b3a5f } -.terminal-83744645-r15 { fill: #121212 } -.terminal-83744645-r16 { fill: #141414 } -.terminal-83744645-r17 { fill: #0053aa } -.terminal-83744645-r18 { fill: #e0e0e0;font-weight: bold } -.terminal-83744645-r19 { fill: #232323 } -.terminal-83744645-r20 { fill: #1e1e1e } -.terminal-83744645-r21 { fill: #0178d4 } -.terminal-83744645-r22 { fill: #797979 } -.terminal-83744645-r23 { fill: #191919 } -.terminal-83744645-r24 { fill: #737373 } -.terminal-83744645-r25 { fill: #2e5e68 } -.terminal-83744645-r26 { fill: #6c0a30 } -.terminal-83744645-r27 { fill: #242f38 } -.terminal-83744645-r28 { fill: #000f18 } -.terminal-83744645-r29 { fill: #6db2ff } -.terminal-83744645-r30 { fill: #2d2d2d } -.terminal-83744645-r31 { fill: #ddedf9;font-weight: bold } -.terminal-83744645-r32 { fill: #656565;font-weight: bold } -.terminal-83744645-r33 { fill: #004295 } -.terminal-83744645-r34 { fill: #0d0d0d } -.terminal-83744645-r35 { fill: #455969;font-weight: bold } -.terminal-83744645-r36 { fill: #6c0a30;font-weight: bold } -.terminal-83744645-r37 { fill: #4b4b4b } -.terminal-83744645-r38 { fill: #704d1c;font-weight: bold } -.terminal-83744645-r39 { fill: #282b2e } + .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 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - 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 -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -▔▔▔▔▔▔▔▔▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -00:30:4 -Sprint──────────────────────────00:30:4start runons=2 -▼ Epic 1 · 3/3 ✓00:30:4 -├ ✓ 1-auth00:30:4▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ refresh race now, or hold it for the -├ ✓ 2-session-tokensauth …epic — blank for all -├ ✓ 3-password-reset▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ -└ ✓ retrospective▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -▼ Epic 2 · 2/5story 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 +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +▔▔▔▔▔▔▔▔▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +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 diff --git a/docs/images/sweep-decision.png b/docs/images/sweep-decision.png index 76aa8b0..c06f23a 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 e06e0c2..bbfc9a4 100644 --- a/docs/images/sweep-decision.svg +++ b/docs/images/sweep-decision.svg @@ -19,227 +19,227 @@ font-weight: 700; } - .terminal-409201596-matrix { + .terminal-3122064392-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-409201596-title { + .terminal-3122064392-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-409201596-r1 { fill: #c5c8c6 } -.terminal-409201596-r2 { fill: #e0e0e0 } -.terminal-409201596-r3 { fill: #a0a3a6 } -.terminal-409201596-r4 { fill: #0053aa } -.terminal-409201596-r5 { fill: #e2e2e2;font-weight: bold } -.terminal-409201596-r6 { fill: #e0e0e0;font-weight: bold } -.terminal-409201596-r7 { fill: #98e024 } -.terminal-409201596-r8 { fill: #9d9d9d } -.terminal-409201596-r9 { fill: #fd971f } -.terminal-409201596-r10 { fill: #a1a1a1 } -.terminal-409201596-r11 { fill: #fd971f;font-weight: bold } -.terminal-409201596-r12 { fill: #ddedf9;font-weight: bold } -.terminal-409201596-r13 { fill: #797979 } -.terminal-409201596-r14 { fill: #262626 } -.terminal-409201596-r15 { fill: #0178d4 } -.terminal-409201596-r16 { fill: #121212 } -.terminal-409201596-r17 { fill: #191919 } -.terminal-409201596-r18 { fill: #9e9e9e } -.terminal-409201596-r19 { fill: #58d1eb } -.terminal-409201596-r20 { fill: #e1e1e1;font-weight: bold } -.terminal-409201596-r21 { fill: #3e3e3e } -.terminal-409201596-r22 { fill: #f4005f } -.terminal-409201596-r23 { fill: #f4005f;font-weight: bold } -.terminal-409201596-r24 { fill: #ffa62b;font-weight: bold } -.terminal-409201596-r25 { fill: #495259 } + .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 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - 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 -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -00:30:40 sweep-start              cycle=1 -Sprint──────────────────────────00:30:40 triage-done              bundles=1  already_resolved=1  decisions=2 -▼ Epic 1 · 3/3 ✓00:30:40 bundle-done              bundle=search-pagination  dw_ids=DW-2 -├ ✓ 1-auth00:30:40 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 +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +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 diff --git a/module.yaml b/module.yaml index e9bb0fe..eba5ce0 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.0 +module_version: 0.6.1 default_selected: false module_greeting: > BMAD Auto installed — both the four automation skills and the diff --git a/pyproject.toml b/pyproject.toml index 539f7ce..3e230ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "bmad-auto" -version = "0.6.0" +version = "0.6.1" 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 884f30d..a25ea40 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.0" +__version__ = "0.6.1" diff --git a/src/automator/cli.py b/src/automator/cli.py index 5e00650..a6808fa 100644 --- a/src/automator/cli.py +++ b/src/automator/cli.py @@ -177,6 +177,14 @@ def cmd_run(args: argparse.Namespace) -> int: if args.dry_run: return _dry_run(paths, pol, args) + try: + sprintstatus.select_actionable( + sprintstatus.load(paths.sprint_status), args.epic, args.story + ) + except sprintstatus.SprintStatusError as e: + print(e, file=sys.stderr) + return 1 + if not verify.worktree_clean(paths.repo_root): print("git worktree is not clean — commit or stash first", file=sys.stderr) return 1 @@ -243,13 +251,11 @@ def render(role: str, prompt: str) -> str: return _render_invocation(pol, paths.project, role, prompt) ss = sprintstatus.load(paths.sprint_status) - queue = [ - s - for s in ss.stories - if s.status in sprintstatus.ACTIONABLE_STATUSES - and (args.epic is None or s.epic == args.epic) - and (args.story is None or s.key == args.story) - ] + try: + queue = sprintstatus.select_actionable(ss, args.epic, args.story) + except sprintstatus.SprintStatusError as e: + print(e, file=sys.stderr) + return 1 if args.max_stories is not None: queue = queue[: args.max_stories] if not queue: @@ -448,9 +454,10 @@ def _resume_paused_run(project: Path, run_dir: Path) -> int: def cmd_resume(args: argparse.Namespace) -> int: project = _project(args) - run_dir = project / RUNS_DIR / args.run_id - if not runs.is_run(run_dir): - print(f"no such run: {args.run_id}", file=sys.stderr) + try: + run_dir = runs.resolve_run_dir(project, args.run_id) + except runs.RunRefError as e: + print(str(e), file=sys.stderr) return 1 return _resume_paused_run(project, run_dir) @@ -467,10 +474,12 @@ def cmd_resolve(args: argparse.Namespace) -> int: from .model import PAUSE_ESCALATION, Phase project = _project(args) - run_dir = runs.run_dir_for(project, args.run_id) - if not runs.is_run(run_dir): - print(f"no such run: {args.run_id}", file=sys.stderr) + try: + run_dir = runs.resolve_run_dir(project, args.run_id) + except runs.RunRefError as e: + print(str(e), file=sys.stderr) return 1 + args.run_id = run_dir.name # normalize so echoed hints show the full id state = load_state(run_dir) if state.paused_stage != PAUSE_ESCALATION: print( @@ -583,7 +592,11 @@ def cmd_decisions(args: argparse.Namespace) -> int: def cmd_status(args: argparse.Namespace) -> int: project = _project(args) if args.run_id: - run_dir = project / RUNS_DIR / args.run_id + try: + run_dir = runs.resolve_run_dir(project, args.run_id) + except runs.RunRefError as e: + print(str(e), file=sys.stderr) + return 1 else: run_dir = runs.latest_run_dir(project) if run_dir is None or not (run_dir / "state.json").is_file(): @@ -623,11 +636,32 @@ def cmd_status(args: argparse.Namespace) -> int: return 0 +def cmd_list(args: argparse.Namespace) -> int: + from .tui.data import discover_runs # import-safe: data.py has no textual imports + + project = _project(args) + infos = discover_runs(project) # oldest first + if not infos: + print("no runs found") + return 0 + print(f"{'REF':6} {'TYPE':6} {'STATUS':10} RUN ID") + for ri in infos: + print(f"{runs.short_ref(ri.run_id):6} {ri.run_type:6} {ri.status:10} {ri.run_id}") + return 0 + + def cmd_attach(args: argparse.Namespace) -> int: from .tui import launch # import-safe: launch.py has no textual imports project = _project(args) - run_dir = project / RUNS_DIR / args.run_id if args.run_id else runs.latest_run_dir(project) + if args.run_id: + try: + run_dir = runs.resolve_run_dir(project, args.run_id) + except runs.RunRefError as e: + print(str(e), file=sys.stderr) + return 1 + else: + run_dir = runs.latest_run_dir(project) if run_dir is None: print("no runs found", file=sys.stderr) return 1 @@ -651,10 +685,12 @@ def cmd_attach(args: argparse.Namespace) -> int: def cmd_stop(args: argparse.Namespace) -> int: project = _project(args) - run_dir = runs.run_dir_for(project, args.run_id) - if not runs.is_run(run_dir): - print(f"no such run: {args.run_id}", file=sys.stderr) + try: + run_dir = runs.resolve_run_dir(project, args.run_id) + except runs.RunRefError as e: + print(str(e), file=sys.stderr) return 1 + args.run_id = run_dir.name if not runs.stop_run(run_dir): print(f"run {args.run_id} already finished", file=sys.stderr) return 1 @@ -664,10 +700,12 @@ def cmd_stop(args: argparse.Namespace) -> int: def cmd_delete(args: argparse.Namespace) -> int: project = _project(args) - run_dir = runs.run_dir_for(project, args.run_id) - if not runs.is_run(run_dir): - print(f"no such run: {args.run_id}", file=sys.stderr) + try: + run_dir = runs.resolve_run_dir(project, args.run_id) + except runs.RunRefError as e: + print(str(e), file=sys.stderr) return 1 + args.run_id = run_dir.name if runs.engine_alive(run_dir): if not args.force: print( @@ -683,10 +721,12 @@ def cmd_delete(args: argparse.Namespace) -> int: def cmd_archive(args: argparse.Namespace) -> int: project = _project(args) - run_dir = runs.run_dir_for(project, args.run_id) - if not runs.is_run(run_dir): - print(f"no such run: {args.run_id}", file=sys.stderr) + try: + run_dir = runs.resolve_run_dir(project, args.run_id) + except runs.RunRefError as e: + print(str(e), file=sys.stderr) return 1 + args.run_id = run_dir.name if runs.engine_alive(run_dir): if not args.force: print( @@ -866,8 +906,8 @@ def main(argv: list[str] | None = None) -> int: parser.add_argument("--version", action="version", version=f"bmad-auto {__version__}") sub = parser.add_subparsers(dest="command", required=True) - def add(name: str, func, help: str) -> argparse.ArgumentParser: - p = sub.add_parser(name, help=help) + def add(name: str, func, help: str, *, aliases=()) -> argparse.ArgumentParser: + p = sub.add_parser(name, help=help, aliases=aliases) p.add_argument("--project", default=".", help="target project root (default: cwd)") p.set_defaults(func=func) return p @@ -897,7 +937,7 @@ def add(name: str, func, help: str) -> argparse.ArgumentParser: run_p = add("run", cmd_run, "run the orchestration loop") run_p.add_argument("--epic", type=int, help="only stories from this epic") - run_p.add_argument("--story", help="only this story key") + run_p.add_argument("--story", help="story: E-S / E.S, a slug fragment, or full key") run_p.add_argument("--max-stories", type=int, help="stop after N stories") run_p.add_argument("--dry-run", action="store_true", help="print the plan, spawn nothing") run_p.add_argument("--run-id", help=argparse.SUPPRESS) # pre-assigned id (used by the TUI) @@ -960,6 +1000,8 @@ def add(name: str, func, help: str) -> argparse.ArgumentParser: help="list the pending decisions without answering them", ) + add("list", cmd_list, "list runs/sweeps with their short ref", aliases=["ls"]) + status_p = add("status", cmd_status, "show run + sprint state") status_p.add_argument("run_id", nargs="?") 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 e9bb0fe..eba5ce0 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.0 +module_version: 0.6.1 default_selected: false module_greeting: > BMAD Auto installed — both the four automation skills and the diff --git a/src/automator/engine.py b/src/automator/engine.py index 7dfc42c..f06f277 100644 --- a/src/automator/engine.py +++ b/src/automator/engine.py @@ -39,7 +39,7 @@ from .policy import Policy from .runs import kill_session from .sprintstatus import load as load_sprint_status -from .sprintstatus import next_actionable +from .sprintstatus import next_actionable, parse_selector from .statemachine import advance from .workspace import ( UnitWorkspace, @@ -131,6 +131,9 @@ def __init__( self.max_stories = max_stories self.epic_filter = epic_filter self.story_filter = story_filter + # widen --story interpretation: full key, short ref (3-1/3.1), bare + # number (+ --epic), or slug fragment. See sprintstatus.StorySelector. + self._selector = parse_selector(epic_filter, story_filter) # spawns a child deferred-work sweep run (injected by the CLI to # avoid an engine -> sweep import cycle); see _maybe_auto_sweep self.sweep_factory = sweep_factory @@ -628,10 +631,7 @@ def _pick_next(self): story = next_actionable(ss, skip) if story is None: return None - if self.epic_filter is not None and story.epic != self.epic_filter: - skip.add(story.key) - continue - if self.story_filter is not None and story.key != self.story_filter: + if not self._selector.matches(story): skip.add(story.key) continue return story diff --git a/src/automator/runs.py b/src/automator/runs.py index 1b75fe0..d57903e 100644 --- a/src/automator/runs.py +++ b/src/automator/runs.py @@ -78,6 +78,36 @@ def is_run(run_dir: Path) -> bool: return (run_dir / STATE_FILE).is_file() +class RunRefError(Exception): + """A run ref matched no run, or was ambiguous.""" + + +def short_ref(run_id: str) -> str: + """The trailing hex segment — the minimal handle users type.""" + return run_id.rsplit("-", 1)[-1] + + +def resolve_run_dir(project: Path, ref: str) -> Path: + """Full or partial run id -> its run dir. An exact id wins outright; + otherwise a partial matches when the trailing segment starts with `ref` or + the full id ends with `ref` (run ids are date-prefixed, so the tail is what + distinguishes them). Raises RunRefError on no match / ambiguity.""" + exact = run_dir_for(project, ref) + if is_run(exact): + return exact + matches = [ + d + for d in list_run_dirs(project) + if short_ref(d.name).startswith(ref) or d.name.endswith(ref) + ] + if not matches: + raise RunRefError(f"no such run: {ref}") + if len(matches) > 1: + listing = "\n".join(f" {d.name}" for d in matches) + raise RunRefError(f"ambiguous run ref {ref!r} matches {len(matches)} runs:\n{listing}") + return matches[0] + + def read_pid(run_dir: Path) -> int | None: """The recorded engine pid, or None when missing/unparseable.""" try: diff --git a/src/automator/sprintstatus.py b/src/automator/sprintstatus.py index fe50a67..85ad75f 100644 --- a/src/automator/sprintstatus.py +++ b/src/automator/sprintstatus.py @@ -17,6 +17,8 @@ RETRO_RE = re.compile(r"^epic-(\d+)-retrospective$") RETRO_ITEM_RE = re.compile(r"^epic-(\d+)-retro-item-(\d+)-(.+)$") STORY_RE = re.compile(r"^(\d+)-(\d+)-(.+)$") +SHORT_REF_RE = re.compile(r"^(\d+)[-.](\d+)$") # short story ref: 3-1 or 3.1 +BARE_NUM_RE = re.compile(r"^(\d+)$") # a lone story number, needs --epic STORY_STATUSES = {"backlog", "ready-for-dev", "in-progress", "review", "done"} LEGACY_STORY_STATUSES = {"drafted": "ready-for-dev"} @@ -139,3 +141,95 @@ def story_status(path: Path, key: str) -> str | None: if story.key == key: return story.status return None + + +@dataclass(frozen=True) +class StorySelector: + """Resolves a human story reference (``--epic``/``--story``) to the + stories it selects. Forms accepted by :func:`parse_selector`: + + * full key ``3-1-user-auth`` — exact match + * short ref ``3-1`` / ``3.1`` — epic 3, story 1 (any slug) + * bare number ``1`` with ``--epic 3`` — epic 3, story 1 + * slug fragment ``user-auth`` / ``auth`` — substring of the slug (must be unique) + * epic only (``--epic 3``, blank story) — every story in the epic + """ + + epic: int | None = None + num: int | None = None + key: str | None = None # exact full key + slug: str | None = None # slug substring + + @property + def is_targeted(self) -> bool: + """True when the selector names one intended story rather than just + an epic-wide (or empty) filter.""" + return any(v is not None for v in (self.key, self.num, self.slug)) + + def matches(self, story: Story) -> bool: + if self.key is not None: + return story.key == self.key + if self.epic is not None and story.epic != self.epic: + return False + if self.num is not None and story.num != self.num: + return False + if self.slug is not None and self.slug not in story.slug: + return False + return True + + +def parse_selector(epic: int | None, story: str | None) -> StorySelector: + """Translate the ``--epic``/``--story`` pair into a :class:`StorySelector`. + + Raises :class:`SprintStatusError` on bad or ambiguous input. + """ + text = (story or "").strip() + if not text: + return StorySelector(epic=epic) + + def _check_epic(parsed_epic: int) -> None: + if epic is not None and epic != parsed_epic: + raise SprintStatusError( + f"--epic {epic} conflicts with story '{text}' (epic {parsed_epic})" + ) + + if m := STORY_RE.match(text): # full key 3-1-slug + e, n = int(m.group(1)), int(m.group(2)) + _check_epic(e) + return StorySelector(epic=e, num=n, key=text) + if m := SHORT_REF_RE.match(text): # 3-1 / 3.1 + e, n = int(m.group(1)), int(m.group(2)) + _check_epic(e) + return StorySelector(epic=e, num=n) + if m := BARE_NUM_RE.match(text): # bare story number, needs --epic + if epic is None: + raise SprintStatusError( + f"ambiguous story '{text}': use --epic E --story {text}, or E-{text}" + ) + return StorySelector(epic=epic, num=int(m.group(1))) + return StorySelector(epic=epic, slug=text) # slug fragment + + +def select_actionable(ss: SprintStatus, epic: int | None, story: str | None) -> list[Story]: + """Stories selected by ``--epic``/``--story`` that are ready to start, in + file order. Raises :class:`SprintStatusError` with a targeted message when a + named story is missing, ambiguous, or exists but is not actionable. + """ + sel = parse_selector(epic, story) + matches = [s for s in ss.stories if sel.matches(s)] + if sel.is_targeted: + if not matches: + raise SprintStatusError(f"no story matches '{story}'") + if sel.slug is not None: + keys = sorted({s.key for s in matches}) + if len(keys) > 1: + raise SprintStatusError( + f"story '{sel.slug}' is ambiguous — matches: {', '.join(keys)}" + ) + actionable = [s for s in matches if s.status in ACTIONABLE_STATUSES] + if sel.is_targeted and matches and not actionable: + s = matches[0] + raise SprintStatusError( + f"story {story} matched {s.key} but its status is " f"'{s.status}' (not actionable)" + ) + return actionable diff --git a/src/automator/tui/screens/modals.py b/src/automator/tui/screens/modals.py index d905c20..781b196 100644 --- a/src/automator/tui/screens/modals.py +++ b/src/automator/tui/screens/modals.py @@ -73,7 +73,10 @@ def compose(self) -> ComposeResult: valid_empty=True, id="epic", ) - yield Input(placeholder="story key — blank for all", id="story") + yield Input( + placeholder="story — 3-1, 3.1, slug, or full key (blank for all)", + id="story", + ) yield Input( placeholder="max stories — blank for no limit", type="integer", diff --git a/tests/test_cli.py b/tests/test_cli.py index 7c36a41..384730b 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -3,6 +3,7 @@ import argparse import json +import pytest from conftest import install_bmad_config, write_sprint from automator import cli @@ -68,6 +69,36 @@ def test_dry_run_renders_per_stage_commands(project, capsys): assert "--model gpt-5-codex" in review_line +@pytest.mark.parametrize( + "epic,story", + [(None, "3-1"), (None, "3.1"), (3, "1"), (None, "user-auth"), (None, "3-1-user-auth")], +) +def test_dry_run_selects_story_by_short_ref(project, capsys, epic, story): + write_sprint( + project, + {"3-1-user-auth": "ready-for-dev", "3-2-foo": "backlog", "4-1-bar": "backlog"}, + ) + _write_policy(project.project) + pol = policy_mod.load(project.project / ".automator" / "policy.toml") + args = argparse.Namespace(epic=epic, story=story, max_stories=None) + + assert cli._dry_run(project, pol, args) == 0 + out = capsys.readouterr().out + assert "3-1-user-auth" in out + assert "3-2-foo" not in out and "4-1-bar" not in out + + +def test_dry_run_reports_targeted_not_actionable(project, capsys): + write_sprint(project, {"3-1-user-auth": "ready-for-dev", "3-2-foo": "done"}) + _write_policy(project.project) + pol = policy_mod.load(project.project / ".automator" / "policy.toml") + args = argparse.Namespace(epic=None, story="3-2", max_stories=None) + + assert cli._dry_run(project, pol, args) == 1 + err = capsys.readouterr().err + assert "3-2 matched 3-2-foo" in err and "not actionable" in err + + def _make_run_with_decision(project, run_id="20260101-000000-aaaa", dw_id="DW-1"): run_dir = project.project / ".automator" / "runs" / run_id run_dir.mkdir(parents=True, exist_ok=True) @@ -167,6 +198,41 @@ def test_status_surfaces_missed_decision_count(project, capsys): assert "decisions awaiting an answer: 1" in capsys.readouterr().out +def test_status_resolves_partial_ref(project, capsys): + _make_run_with_decision(project, run_id="20260101-000000-aaaa") + # the trailing segment alone resolves to the full run + assert cli.main(["status", "--project", str(project.project), "aaaa"]) == 0 + assert "run 20260101-000000-aaaa" in capsys.readouterr().out + + +def test_status_unknown_ref_errors(project, capsys): + _make_run_with_decision(project, run_id="20260101-000000-aaaa") + assert cli.main(["status", "--project", str(project.project), "zzzz"]) == 1 + assert "no such run: zzzz" in capsys.readouterr().err + + +def test_status_ambiguous_ref_errors(project, capsys): + _make_run_with_decision(project, run_id="20260101-000000-aa11") + _make_run_with_decision(project, run_id="20260102-000000-aa22") + assert cli.main(["status", "--project", str(project.project), "aa"]) == 1 + assert "ambiguous run ref 'aa' matches 2 runs" in capsys.readouterr().err + + +def test_list_shows_short_refs(project, capsys): + _make_run_with_decision(project, run_id="20260101-000000-aaaa") + _make_run_with_decision(project, run_id="20260102-000000-bbbb") + assert cli.main(["list", "--project", str(project.project)]) == 0 + out = capsys.readouterr().out + assert "REF" in out + assert "aaaa" in out and "bbbb" in out + assert "20260101-000000-aaaa" in out + + +def test_list_no_runs(project, capsys): + assert cli.main(["list", "--project", str(project.project)]) == 0 + assert "no runs found" in capsys.readouterr().out + + def test_attach_records_return_pane_inside_tmux(project, monkeypatch): from automator.tui import launch diff --git a/tests/test_runs.py b/tests/test_runs.py index 241a714..0297f33 100644 --- a/tests/test_runs.py +++ b/tests/test_runs.py @@ -5,6 +5,8 @@ import subprocess import tarfile +import pytest + from automator import runs from automator.journal import load_state, save_state from automator.model import RunState @@ -86,6 +88,44 @@ def test_run_dir_for_and_is_run(tmp_path): assert not runs.is_run(tmp_path / ".automator" / "runs" / "nope") +def test_short_ref(): + assert runs.short_ref("20260620-143025-a1b2") == "a1b2" + + +def test_resolve_run_dir_exact_and_partial(tmp_path): + target = _make_run(tmp_path, "20260620-143025-a1b2") + _make_run(tmp_path, "20260619-101010-c3d4") + # exact full id + assert runs.resolve_run_dir(tmp_path, "20260620-143025-a1b2") == target + # full trailing segment + assert runs.resolve_run_dir(tmp_path, "a1b2") == target + # prefix of the trailing segment + assert runs.resolve_run_dir(tmp_path, "a1") == target + # a longer tail of the id (endswith) + assert runs.resolve_run_dir(tmp_path, "025-a1b2") == target + + +def test_resolve_run_dir_no_match(tmp_path): + _make_run(tmp_path, "20260620-143025-a1b2") + with pytest.raises(runs.RunRefError, match="no such run: zzzz"): + runs.resolve_run_dir(tmp_path, "zzzz") + + +def test_resolve_run_dir_ambiguous(tmp_path): + _make_run(tmp_path, "20260620-143025-a1b2") + _make_run(tmp_path, "20260619-101010-a1c9") + with pytest.raises(runs.RunRefError, match="ambiguous run ref 'a1' matches 2 runs"): + runs.resolve_run_dir(tmp_path, "a1") + + +def test_resolve_run_dir_exact_wins_over_ambiguity(tmp_path): + # An exact id resolves even when another run's id ends with it (which would + # otherwise be an ambiguous partial match). + exact = _make_run(tmp_path, "20260620-143025-a1b2") + _make_run(tmp_path, "20260101-000000-20260620-143025-a1b2") # ends with the exact id + assert runs.resolve_run_dir(tmp_path, "20260620-143025-a1b2") == exact + + def test_read_pid_missing_and_garbage(tmp_path): run_dir = _make_run(tmp_path, "r1") assert runs.read_pid(run_dir) is None diff --git a/tests/test_sprintstatus.py b/tests/test_sprintstatus.py index d401e10..01b390e 100644 --- a/tests/test_sprintstatus.py +++ b/tests/test_sprintstatus.py @@ -88,6 +88,77 @@ def test_story_status_reread(project): assert sprintstatus.story_status(project.sprint_status, "9-9-z") is None +def test_parse_selector_forms(): + # full key — exact match intent + sel = sprintstatus.parse_selector(None, "3-1-user-auth") + assert (sel.epic, sel.num, sel.key, sel.slug) == (3, 1, "3-1-user-auth", None) + # short refs: hyphen and dot are equivalent + for ref in ("3-1", "3.1"): + sel = sprintstatus.parse_selector(None, ref) + assert (sel.epic, sel.num, sel.key, sel.slug) == (3, 1, None, None) + # bare number resolves against --epic + sel = sprintstatus.parse_selector(3, "1") + assert (sel.epic, sel.num, sel.slug) == (3, 1, None) + # slug fragment + sel = sprintstatus.parse_selector(None, "user-auth") + assert (sel.epic, sel.num, sel.slug) == (None, None, "user-auth") + # epic only — not targeted + sel = sprintstatus.parse_selector(3, None) + assert sel.epic == 3 and not sel.is_targeted + + +def test_parse_selector_bare_number_needs_epic(): + with pytest.raises(sprintstatus.SprintStatusError, match="ambiguous story '1'"): + sprintstatus.parse_selector(None, "1") + + +def test_parse_selector_epic_conflict(): + with pytest.raises(sprintstatus.SprintStatusError, match="conflicts"): + sprintstatus.parse_selector(2, "3-1") + with pytest.raises(sprintstatus.SprintStatusError, match="conflicts"): + sprintstatus.parse_selector(2, "3-1-user-auth") + + +def test_select_actionable_short_ref_and_epic_story(project): + write_sprint( + project, + {"3-1-user-auth": "ready-for-dev", "3-2-foo": "backlog", "4-1-bar": "backlog"}, + ) + ss = sprintstatus.load(project.sprint_status) + for epic, story in [(None, "3-1"), (None, "3.1"), (3, "1"), (None, "user-auth")]: + got = sprintstatus.select_actionable(ss, epic, story) + assert [s.key for s in got] == ["3-1-user-auth"] + # epic only selects every actionable story in the epic + assert [s.key for s in sprintstatus.select_actionable(ss, 3, None)] == [ + "3-1-user-auth", + "3-2-foo", + ] + + +def test_select_actionable_targeted_not_actionable(project): + write_sprint(project, {"3-1-user-auth": "ready-for-dev", "3-2-foo": "done"}) + ss = sprintstatus.load(project.sprint_status) + with pytest.raises( + sprintstatus.SprintStatusError, + match=r"story 3-2 matched 3-2-foo but its status is 'done'", + ): + sprintstatus.select_actionable(ss, None, "3-2") + + +def test_select_actionable_ambiguous_slug(project): + write_sprint(project, {"3-1-user-auth": "backlog", "4-2-admin-auth": "backlog"}) + ss = sprintstatus.load(project.sprint_status) + with pytest.raises(sprintstatus.SprintStatusError, match="ambiguous"): + sprintstatus.select_actionable(ss, None, "auth") + + +def test_select_actionable_no_match(project): + write_sprint(project, {"3-1-user-auth": "backlog"}) + ss = sprintstatus.load(project.sprint_status) + with pytest.raises(sprintstatus.SprintStatusError, match="no story matches"): + sprintstatus.select_actionable(ss, None, "9-9") + + def test_missing_file_raises(project): with pytest.raises(sprintstatus.SprintStatusError, match="not found"): sprintstatus.load(project.sprint_status) diff --git a/uv.lock b/uv.lock index e816306..ee442cf 100644 --- a/uv.lock +++ b/uv.lock @@ -4,7 +4,7 @@ requires-python = ">=3.11" [[package]] name = "bmad-auto" -version = "0.6.0" +version = "0.6.1" source = { editable = "." } dependencies = [ { name = "pyyaml" },