Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,8 @@ breaking changes may land in a minor release.
exit 1). Require worktree isolation without the auto retrospective; pause each child
at its epic boundaries; close drained children (`status: done`) before every pick;
spend one `--max-stories` budget across the chain. Show the chain in `run --dry-run`
and the TUI start-run preview, and a chained run's child on its tickets board (DW-574).
and the TUI start-run preview, both picking as if the drained children were closed,
and a chained run's child on its tickets board (DW-574).

### Removed

Expand Down
1 change: 1 addition & 0 deletions docs/adapter-authoring-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ Rules that span methods:
- `window_alive(session, window_id)`: `True` while the window is listed AND its pane is alive; a window the multiplexer keeps listed after its pane exited (`remain-on-exit on`) is not alive. Membership alone is `list_window_ids`. Raise when liveness cannot be known; never answer `False` for that.
- `kill_window(target)`: tolerate the window already being gone; no-op on a transport failure.
- `select_window(target)`: make the window current; best-effort.
- `rename_window(target, name)` (default: raises `MultiplexerError`): rename the window so a by-name lookup finds it under `name`, which must stick against any automatic rename. Checked: raise `MultiplexerError` when the rename did not land.
- `set_window_option(target, option, value)` / `unset_window_option(target, option)`: best-effort. An unset option must read back as unset, not empty.
- `show_window_option(target, option)`: the value, or `''` when unset or on failure.
- `pipe_pane(window_id, log_file)`: tee the pane to `log_file`; tolerate a dead window.
Expand Down
4 changes: 4 additions & 0 deletions docs/multiplexer-backends.md
Original file line number Diff line number Diff line change
Expand Up @@ -326,3 +326,7 @@ Operational notes for any external backend:
- **The TUI's parked-prompt answer keys need `send_keys`.** The method is optional: a
backend that does not override it refuses every answer, and the operator answers by
attaching instead. See the [backend contract](adapter-authoring-guide.md#the-transport-contract-for-a-backend-author).
- **`rename_window(target, name)` is checked.** Unlike `select_window`, it raises
`MultiplexerError` when the rename did not land, and the new name must stick: a
multiplexer that auto-renames windows must not rename it back. The method is optional:
a backend that does not override it raises `MultiplexerError` for every rename.
5 changes: 3 additions & 2 deletions docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,9 @@ bmad-loop run [--project PROJECT] [--spec FOLDER] [--epic EPIC] [--story STORY]
- In tickets mode `run` resolves the selectors against the active initiative before anything is created. `--epic` must name one epic of the initiative table. `--story` goes through `tickets.py find` and must name one ticket that `next` lists as ready to start or to refine (and that is in the `--epic` epic, when both are given). Anything else exits 1. The run then picks only within the selection, and finishes once it has nothing left to start there. `--dry-run` applies the same selection.
- Refused with exit 1 before anything is created on: git below the supported floor, a dirty worktree, a worktree-isolation conflict, missing base skills, or a failing plugin-trust verdict.
- `[stories] source = "tickets"` runs the active initiative's ticket tree. Before anything is created, `run` executes the `tickets.*` preflight (capability probe included) and refuses with exit 1 on a problem, including a ticket store it cannot classify or drive (`tickets.store-topology` and the refusals below). The classified topology is recorded on the run, and `resume`, `resolve` and the TUI refuse a store that changed since; `branch_per = "run"` prints the `tickets.branch-per-run` warning. Each picked ticket is dispatched as `ticket <ref>` and, once verified at `built`, marked `done` under `[tickets] mark_done = "auto"` (left at `built` under `"never"`): inside its unit's commit for a store tracked in the code checkout (T1), after the code lands for a shared store, with a plan-only commit in a store that is its own repository (T3/T4). Under worktree isolation a unit's branch is `bmad-loop/<run>/<epic-slug>/<ref>`.
- On a parent initiative, `run` builds the children one run each: the first child `next <parent>` lists as ready, skipping one with no folder or nothing left to dispatch, then the next after each run that finishes. Before each pick, its first included, it marks every drained child's initiative `done` and prints `... closed <child> (drained)`, or `... leaves <child> open (<why>: ...)` while a run holds the child, its first pick still finds work, or its store is refused. It prints `initiative chain <parent>: <child> -> <next>` per switch, or `... ends (<why>)` with each waiting child's unmet prerequisites and their status, and stops at a run that does not finish, including each child's pause at `epic-boundary` after every epic it finishes; `resume` or `resolve` driving that run to finished continues the chain. `--epic` and `--story` exit 1 there; `--run-id` names the first child's run; `--max-stories` is one budget for the whole chain, and the chain ends `max-stories` once it is spent.
- `--dry-run` in tickets mode reads the active initiative through its `tickets.py` and marks nothing. It prints the startable tickets in build order (initiative epic table, then entry order), each with its badges, its `ticket <ref>` dispatch (`thorough` when `[tickets] thorough_review` selects it, `Halt after planning.` on an unrefined plan-checkpoint ticket) and its env, then the first pause a run would reach (`predicted:`), the `next` group counts and the tree's problems. A `tickets.py` failure exits 1. On a parent initiative it lists the children in build order (status, closed and total tickets, unmet prerequisites, and a tag: `next`, `ready`, `ready, passed over (<why>)`, `waiting`, `no folder`, `drained (the run closes it first)`, `done` or `dropped`), prints the `next` child's block, and ends `predicted: chain starts with <child>; later children are chosen as each finishes`, or with the chain's end line when no child can start. It closes no drained child; the predicted line names those the run would close first. `--max-stories` caps the first child's block only, `--epic` and `--story` exit 1, and the banner warns when the preflight would refuse the parent.
- On a parent initiative, `run` builds the children one run each: the first child `next <parent>` lists as ready, skipping one with no folder or nothing left to dispatch, then the next after each run that finishes. Before each pick, its first included (after the clean-worktree, skills and plugin-trust checks, and journaled in the first child's run), it marks every drained child's initiative `done` and prints `... closed <child> (drained)`, or `... leaves <child> open (<why>: ...)` while a run holds the child, its first pick still finds work, or its store is refused. It prints `initiative chain <parent>: <child> -> <next>` per switch, or `... ends (<why>)` with each waiting child's unmet prerequisites and their status, and stops at a run that does not finish, including each child's pause at `epic-boundary` after every epic it finishes; `resume` or `resolve` driving that run to finished continues the chain. `--epic` and `--story` exit 1 there; `--run-id` names the first child's run; `--max-stories` is one budget for the whole chain, and the chain ends `max-stories` once it is spent.
- A launch check that refuses the next child's run of a parent initiative (a dirty worktree, the skills, plugin trust, the child's store) ends the chain `launch-refused`, and `run` exits with that check's exit code.
- `--dry-run` in tickets mode reads the active initiative through its `tickets.py` and marks nothing. It prints the startable tickets in build order (initiative epic table, then entry order), each with its badges, its `ticket <ref>` dispatch (`thorough` when `[tickets] thorough_review` selects it, `Halt after planning.` on an unrefined plan-checkpoint ticket) and its env, then the first pause a run would reach (`predicted:`), the `next` group counts and the tree's problems. A `tickets.py` failure exits 1. On a parent initiative it lists the children in build order (status, closed and total tickets, unmet prerequisites, and a tag: `next`, `ready`, `ready, passed over (<why>)`, `waiting`, `no folder`, `drained (the run closes it first)`, `done` or `dropped`), prints the `next` child's block, and ends `predicted: chain starts with <child>; later children are chosen as each finishes`, or with the chain's end line when no child can start. It closes no drained child but picks as if the run had, and the predicted line names them. `--max-stories` caps the first child's block only, `--epic` and `--story` exit 1, and the banner warns when the preflight would refuse the parent.
- Prints `run <id> starting (attach: bmad-loop attach)`, then the run summary; exits 0 once the engine has run.

### `sweep`
Expand Down
4 changes: 3 additions & 1 deletion docs/reference/run-state.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@ A run paused on a usage limit also records `paused_limit_class` (`blocked` for a

A tickets run's `state.json` also records `initiative` and `initiative_folder`, the BMAD v7 initiative the run is pinned to: every ticket-tree read refuses while the live `[core] active_initiative` names another. Each task carries a `ticket` binding resolved when it was picked (ref, id, epic slug and id, title, plan, risk, and the refined, hitl and checkpoint flags). `ticket_epic_filter`, `ticket_story_approvals`, `ticket_epic_closures`, `ticket_epic_closure_resumed` and `ticket_epic_closure_acked` hold the epic selector, the refs whose story file an operator approved (resuming the pick's `plan-checkpoint` pause records the ref), each finished epic's closure (its retrospective `verdict`, or `unknown`), the epics whose closure pause a resume answered (a closure recorded without one pauses again), and the epics whose one closure pause was resumed past. Tickets pauses add the `paused_stage` values `ticket-hitl`, `ticket-refine` and `ticket-tree` (causes below). `run` resolves a tickets run's selectors before it starts: `ticket_epic_filter` holds the `--epic` epic slug, `story_filter` the `--story` ticket ref, and `epic_filter` stays null.

`parent_initiative` names the parent initiative when a tickets run builds one of its children, and is null otherwise. A child run's `max_stories` is what was left of the chain's `--max-stories` when it launched. When `run`, or a resume of a child run, builds a parent's children, each finished child run journals the switch: `initiative-mark` and its store commit for each drained child closed, `initiative-close-skipped` (`parent`, `slug`, `why`: `run-live`, `run-unfinished`, `runs-unread`, `work-left`, `store-refused` or `uncommitted-edit`, `blocking_run`, `detail`) for each one left open, then `initiative-chain-next` (`parent`, `from_child`, `to_child`, and the children `passed_over` with `passed_over_why`) or `initiative-chain-end` (`parent`, `end`, the `waiting` children, one row per unmet prerequisite across `waiting_child`, `waiting_on` and `waiting_on_status`, and the passed-over ones). The next child's run journals `initiative-chain` (`parent`, `previous_run`, `previous_child`). The closes `run` makes before its first child's run are printed, not journaled: no run exists yet.
`parent_initiative` names the parent initiative when a tickets run builds one of its children, and is null otherwise. A child run's `max_stories` is what was left of the chain's `--max-stories` when it launched.

When `run`, or a resume of a child run, builds a parent's children, each finished child run journals the switch: `initiative-mark` and its store commit for each drained child closed, `initiative-close-skipped` (`parent`, `slug`, `why`: `run-live`, `run-unfinished`, `runs-unread`, `work-left`, `store-refused` or `uncommitted-edit`, `blocking_run`, `detail`) for each one left open, then `initiative-chain-next` (`parent`, `from_child`, `to_child`, and the children `passed_over` with `passed_over_why`) or `initiative-chain-end` (`parent`, `end`, the `waiting` children, one row per unmet prerequisite across `waiting_child`, `waiting_on` and `waiting_on_status`, and the passed-over ones). A next child whose launch a check refuses gets `initiative-chain-next` followed by `initiative-chain-end` with `end` `launch-refused` and an `error` naming the child and the exit code. The next child's run journals `initiative-chain` (`parent`, `previous_run`, `previous_child`). The closes `run` makes before its first child's run are printed as they happen and journaled in that child's run, after its `run-start` row; a start that composes no run (no child to pick, a close that cannot be committed, a refused launch) only prints them.

A v7 sweep records `initiative` and `initiative_folder` too, and its resume, `resolve` and the dashboard's re-arm refuse while the live `[core] active_initiative` names another, because its ledger and pre-answers belong to the initiative it started on. The run-end sweep of a run that builds a child initiative works that child's ledger: its `initiative` and `initiative_folder` name the child and its `parent_initiative` the parent, and it is refused while the live `[core] active_initiative` names anything but the parent.

Expand Down
7 changes: 5 additions & 2 deletions docs/tickets-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,19 +47,22 @@ A parent initiative, whose `tickets.toml` lists child initiatives in `[[initiati
- a stop was asked of the run.
- the `--max-stories` budget is spent (`max-stories`).
- it would build the same child again after a run that landed no ticket (`stalled`).
- a launch check refuses the next child's run, such as a dirty worktree or the child's store (`launch-refused`). `run` exits with that check's exit code.
- no child can start: some still wait on a prerequisite (`waiting`), every child left was passed over (`passed-over`), or every child is done or dropped (`all-done`). A child waiting on a dropped prerequisite still counts as waiting. The end line names each waiting child's unmet prerequisites with their status, for example `initiative-billing on initiative-search (dropped), initiative-ledger (no-folder)`; `open` is a prerequisite whose file sets no status.

Closing a child writes `status: done` into its `<child>.md`. It happens before every pick, both when `run` starts and after each child's run finishes, for every child whose tickets are all done or dropped. That includes a child a stopped or interrupted chain left open, and one drained outside the chain. A line names each child closed. A drained child is left open, with a line naming why, while a run of it is live, or while a run that builds it is paused or otherwise unfinished (resume that run). A sweep of it holds it only while live. It is also left open while its first pick still finds work, such as an epic awaiting closure, which a run of the child then does, and while its store is refused.

When `run` starts, it closes only once the launch checks that no child changes have passed (a clean worktree, the skills, plugin trust), so a run they refuse writes and commits nothing. The first child's run journal then records those closes, ahead of its own.

Where the store is versioned, a close is committed as `chore(tickets): <child> done [bmad-loop <run>]` (`[bmad-loop chain <parent>]` for a close before the first child's run), in the code repo (T1) or in the store's own repository (T3/T4). A close that cannot be committed ends the chain (`store-commit-failed`, exit 1).

Each child's run pauses at `epic-boundary` after every epic it finishes, as a one-initiative run does, and no policy key skips that pause. Run `bmad-loop resolve <run-id>` to close the epic with bmad-agent-po and resume, or close and commit it yourself and `bmad-loop resume <run-id>` (see [closure](#epic-retrospective-and-closure)). The resumed run goes on with that child, and once its run finishes the chain goes on to the next child.

`--epic` and `--story` select within one initiative, so `run` on a parent, and its dry run, refuse them with exit 1. `--run-id` names the first child's run; each later child's run gets its own id. `--max-stories` caps the whole chain: each child's run starts with what the runs before it left, counting every ticket they dispatched.

`bmad-loop run --dry-run` on a parent writes nothing. It lists the children in build order, each with its status, closed and total tickets, the prerequisites it waits on and a tag (`next`, `ready`, `ready, passed over (<why>)`, `waiting`, `no folder`, `drained (the run closes it first)`, `done` or `dropped`). It then prints the `next` child's dry run, read through that child's folder, and ends `predicted: chain starts with <child>; later children are chosen as each finishes`, or with the end line `run` prints when no child can start. It closes no drained child; the predicted line names those the run closes before its first pick. Its `--max-stories` caps the first child's listing only, and its banner warns when the preflight would refuse the parent.
`bmad-loop run --dry-run` on a parent writes nothing. It lists the children in build order, each with its status, closed and total tickets, the prerequisites it waits on and a tag (`next`, `ready`, `ready, passed over (<why>)`, `waiting`, `no folder`, `drained (the run closes it first)`, `done` or `dropped`). It then prints the `next` child's dry run, read through that child's folder, and ends `predicted: chain starts with <child>; later children are chosen as each finishes`, or with the end line `run` prints when no child can start. It closes no drained child, but picks as the tree would read once the run has closed them (a drained child with an epic still to close or break down stays open, as `run` leaves it); the predicted line names those the run closes before its first pick. Its `--max-stories` caps the first child's listing only, and its banner warns when the preflight would refuse the parent.

The TUI's start-run preview lists the same children and shows the board of the first ready child that has a folder and is not drained; `run --dry-run` names the exact first pick. A chained child run's [tickets board](tui-guide.md#tickets-board-middle-tickets-mode) shows that child's tickets.
The TUI's start-run preview lists the same children, tags each drained child `closes first`, and shows the board of the first ready child once those are closed; `run --dry-run` names the exact first pick. A chained child run's [tickets board](tui-guide.md#tickets-board-middle-tickets-mode) shows that child's tickets. A chain the TUI launched runs in one control window, which takes the name of each chained run as the chain moves on, so attach and stop on the current child's run reach it.

## Store layouts

Expand Down
10 changes: 5 additions & 5 deletions docs/tui-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -401,11 +401,11 @@ To return to the default proportions, delete the `[tui]` size keys from
ticket counts and the refs that pause on hitl or a checkpoint. On a parent
initiative it lists the children in build order, each with its status, closed
and total tickets, what it waits on and a tag (`first ready`, `ready`,
`waiting`, `no folder`, `drained`, `done` or `dropped`), then the counts and
checkpoint refs of the `first ready` child (the first ready child that has a
folder and is not drained), or why no child can start. The run's pick also
passes over a ready child with nothing to dispatch, so `run --dry-run` names
the child a run builds first. A
`waiting`, `no folder`, `closes first`, `done` or `dropped`), then the counts
and checkpoint refs of the `first ready` child (the first ready child with a
folder once a run closes the drained ones), or why no child can start. A run
passes over a ready child with nothing to dispatch and keeps open a drained
one with an epic to close, so `run --dry-run` names the child it builds first. A
tickets pick launches only while `[stories] source = "tickets"`. Under that policy a
stories pick still launches (its `--spec` overrides the policy source); a
sprint pick does not.
Expand Down
Loading
Loading