12vectors / bench docs for 0.4-alpha
bench docs GitHub ↗
ConceptsPhases

Phases

The model, before anything runs: what a phase card is made of, how membership is written down, why it runs one direction only, and what the board flags when the list does not resolve.

Some work is a run of related cards rather than one card: three that have to land in order, each building on the one before it. A phase is how bench holds that — a card that lists its cards, an integration branch of its own, a beat that works the list one member at a time, and a single PR into main at the end. Everything else on the board reaches it by inheritance, because a phase card is a card.

A phase is a card that lists its cards

A phase is a group of related tasks meant to run one after another. It is not a directory, a stage or a registry — it is a task card like any other, marked **Type:** Phase, with a ## Cards section naming its members in the order they run:

## Cards

- 31 — Stand up site/ and its build
- 32 — Serve it from a Cloudflare Worker
- 33 — The landing page

The name of the phase is the card itself, its number and title, so nothing is named twice. Document order is run order. The number is what is parsed — 31, #31 and 031 are the same card — and whatever follows it is for the reader, never matched against anything.

Membership runs one direction only: the phase card lists its members, and a member card says nothing about phases. So membership cannot disagree with itself, there is exactly one place to edit when it changes, and a member's phase and position are derived — a ⟶ <phase> 3/5 chip in the card's footer row, beside CI and PR ↗, opening the phase card.

What the list does not resolve is flagged on the card, in the same spirit as status drift: a number no card has, a card two phase cards both list (both are flagged), a card one phase lists twice, a line naming no number, and a phase listed by a phase — phases do not nest. Each is an authoring mistake that would otherwise surface later as a runner behaving oddly.

**Depends on:** is the other half, and it guards rather than orders: the list says what runs next, a member's dependencies say whether it may. The board parses the numbers out of the line and shows them; acting on them belongs to the runner below.

A phase is normally written whole — members listed and each member's dependencies filled in — before anything reaches the board, which is what makes it a thing you can read in a diff. For the card you decide belongs after all there is ⟶ phase, on backlog/ and to-do/ cards that are not already in a phase and are not phase cards themselves. It opens a sheet naming the phase cards in to-do/ and what each already holds, and picking one appends - <n> — <title> — the way a person writes it — to the end of that phase's ## Cards. Nothing else moves: the card stays in its stage, because joining a phase is not a commitment to start it. The append goes through the board's own write path, so it commits itself under BOARD_COMMIT_MOVES and reaches the other boards; an addition that never left one working tree is not an addition the phase would run. Only phases in to-do/ are offered — one in in-progress/ is running, its members being worked in the order the list had when it started — and with no phase waiting there the action is absent rather than present and empty.