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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,11 @@ breaking changes may land in a minor release.
closes for unlanded code; a new sweep warns while such a run exists (DW-571).
- Pass `--allow-all-urls` in copilot's `bypass_args` so unattended sessions fetch URLs
without a permission prompt; this changes the copilot default config digest (DW-598).
- Build a parent initiative's children one run each when `run` targets a parent (was
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).

### Removed

Expand Down
5 changes: 3 additions & 2 deletions docs/FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ bmad-loop drives a BMAD sprint unattended: a deterministic Python loop launches
- **Properties:**
- A run is pinned to `[core] active_initiative` at start; a changed initiative pauses the run at `ticket-tree` instead of building another initiative's ticket.
- The tree is read in the layout the installed `tickets.py` uses: a `tickets/` folder inside each initiative and epic, or the preview-era flat layout. A parent initiative passes the preflight only under worktree isolation without `gates.retrospective = "auto"`, with the store checks run for each sliced child (`tickets.initiative-parent`), and an epic declared but not sliced yet holds no tickets.
- `run` on a parent initiative builds its children one run each in build order, writing every drained child's initiative `status: done` before each pick (one a stopped chain left open included), under one `--max-stories` budget; a child run that does not finish holds the chain until `resume` or `resolve` drives it to finished, and the chain's end names what each waiting child waits on.
- `run` on a parent initiative builds its children one run each in build order, writing every drained child's initiative `status: done` before each pick (one a stopped chain left open included), under one `--max-stories` budget; a child run that does not finish holds the chain until `resume` or `resolve` drives it to finished, and the chain's end names what each waiting child waits on. Each child's run pauses at `epic-boundary` after every epic it finishes, as a one-initiative run does.
- `run --dry-run` on a parent initiative writes nothing: it lists the children in build order with a tag, the child a run builds first and that child's dry run; the TUI start-run preview lists the children and the first ready child's board, and a chained child run's dashboard board shows that child.
- A hitl ticket pauses at `ticket-hitl` and a ticket that needs refining at `ticket-refine`, both before any session runs for it.
- bmad-loop writes a plan's `status` and `blocked_*` only through `tickets.py mark`, under a lock, read back afterwards and checked against one plan transition table; it never writes `dropped` or an epic file.
- Tickets this run did not start are never dispatched or marked.
Expand All @@ -60,7 +61,7 @@ bmad-loop drives a BMAD sprint unattended: a deterministic Python loop launches
- `run` refuses a tracker store and the multi-repo workspace layout before creating a run directory; resume and resolve refuse a store whose layout changed.
- When a run without `--story` finishes an epic, it runs the retrospective and pauses once at `epic-boundary` for a person to close the epic; a `rejected` verdict pauses every later pick while the epic stays open, and an epic gated on an open one waits for its closure.
- **Config:** `[stories] source = "tickets"`, `[core] active_initiative`, `[tickets] mark_done`, `[tickets] thorough_review`, `[gates] retrospective`; `bmad-loop run --epic ID|SLUG`, `--story REF`, `--dry-run`; `bmad-loop confirm <ref>`
- **Tested by:** `tests/test_tickets_engine.py`, `tests/test_tickets_source.py`, `tests/test_tickettree.py`, `tests/test_ticketmodel.py`, `tests/test_ticketstore.py`, `tests/test_statemachine.py`, `tests/test_initiativechain.py`, `tests/test_cli.py`, `tests/test_tickets_e2e.py`
- **Tested by:** `tests/test_tickets_engine.py`, `tests/test_tickets_source.py`, `tests/test_tickettree.py`, `tests/test_ticketmodel.py`, `tests/test_ticketstore.py`, `tests/test_statemachine.py`, `tests/test_initiativechain.py`, `tests/test_cli.py`, `tests/test_tui_data.py`, `tests/test_tickets_e2e.py`
- **Docs:** [Ticket-tree mode guide](tickets-guide.md), [Setup: BMAD v7](setup-guide.md#installing-on-bmad-v7-bmad-setup), [Configuration: [tickets]](reference/configuration.md#tickets), [Resolve in tickets mode](escalations-guide.md#resolve-in-tickets-mode), [TUI: tickets mode](tui-guide.md#tickets-mode)

### Trust-nothing verification
Expand Down
4 changes: 2 additions & 2 deletions docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,8 @@ 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; `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, `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.
- Prints `run <id> starting (attach: bmad-loop attach)`, then the run summary; exits 0 once the engine has run.

### `sweep`
Expand Down
8 changes: 7 additions & 1 deletion docs/tickets-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,13 @@ Closing a child writes `status: done` into its `<child>.md`. It happens before e

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).

`--epic` and `--story` select within one initiative, so `run` on a parent refuses 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.
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. Close the epic with bmad-agent-po and commit it (see [closure](#epic-retrospective-and-closure)), then `bmad-loop resume <run-id>`. The resume 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.

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.

## Store layouts

Expand Down
16 changes: 14 additions & 2 deletions docs/tui-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,11 @@ ticket: state glyph and label, the ref, the epic folder, a three-slot flags cell
and the title. The state is the plan's status when the ticket has a plan, else
`planned` (or `refine` when it must be refined first).

A run that builds one child of a parent initiative shows that child's tickets,
read as `bmad-loop status` reads them: through the child's folder, with the tree
pinned to the parent that `[core] active_initiative` names. A config that no
longer names the parent shows as the board's fault.

`tickets.py` is the project's own code, so the board reads it on the poll worker,
never the UI thread. It runs again only when a `tickets.toml` or `.md` file in the
initiative tree (its `tickets/` folders and epic folders, in either layout), the
Expand Down Expand Up @@ -393,8 +398,15 @@ To return to the default proportions, delete the `[tui]` size keys from
- **source**: `sprint mode` (walks `sprint-status.yaml`), `stories mode`
(folder-and-id dispatch) or `tickets mode` (the BMAD v7 ticket tree),
prefilled from `[stories]`. Tickets mode previews the active initiative: its
ticket counts and the refs that pause on hitl or a checkpoint. A tickets pick
launches only while `[stories] source = "tickets"`. Under that policy a
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
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.
- **spec folder**: stories mode only. The epic spec folder holding
Expand Down
Loading
Loading