diff --git a/AGENTS.md b/AGENTS.md index 25d06cb..360f529 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -587,235 +587,14 @@ without a PR is refused with a pointer to **↑ open PR** — there is nothing for origin to merge otherwise. Single-player merges locally, exactly as above. -## Stages +## Phases -The flow is linear: - -``` -backlog → to-do → in-progress → review → done -``` - -### backlog/ -Where new tasks are written and where they wait. A backlog task may be rough, -incomplete, or fully specified — what it has in common with its neighbours is -that nobody is working on it. Most tasks live here for most of their life. - -### to-do/ -Picked up and queued to work on next. Moving a task from `backlog/` to `to-do/` -is a commitment to do it soon, so keep this directory short — a long `to-do/` is -just a second backlog. - -### in-progress/ -Actively being worked on right now. Anything here should have someone (or an -agent session) attached to it. If work stalls, move it back to `to-do/` or -`backlog/` rather than leaving it parked — a stale `in-progress/` makes the board -lie about what is happening. - -Implementation plans (created via Claude Code's plan mode) are stored in `plans/` -and can be referenced from the task file. - -### review/ -The work is built and awaits judgment: tests written and passing, a PR open -(see "Pull requests"), behaviour checked in the running app, edge cases -probed. A task sitting here has code but not yet confidence. If review turns -up problems, move it back to `in-progress/`. - -### done/ -Finished and merged. Completed task files are kept as a record of what was built -and why — they are the closest thing we have to design history, so don't delete -or trim them. - -### reference/ (beside tasks/, not a stage) -Supporting documents that tasks can link to — external specs, API documentation, -research notes, screenshots, competitive analysis, regulatory references, etc. -These don't move through the workflow; they're stable resources. Reference them -from task files using relative links — two levels up from a stage directory -(e.g. `[the payments API spec](../../reference/payments-api.md)`). - -## Moving a task - -When moving a task between stages: -1. Update the **Status** field in the task file header -2. Move the file to the new directory -3. Add any notes about why it's moving (e.g. "approach agreed, starting build") - -Moves are not always forward. Going back a stage is normal and expected — -verification failing, or an approach not surviving contact with the code, should -move the task backwards rather than being worked around in place. - -## Claiming a card - -**Claiming is moving.** Taking a card out of `backlog/` or `to-do/` towards -work is the commitment, so that is where ownership is recorded: the board -writes an `**Assignee:** ` line into the header, taken from this -checkout's `git config user.name` — the identity git history already shows, -no new concept. The first claim sticks: a card that already names an -assignee keeps it when someone else moves it on. Walking a card all the way -back to `backlog/` clears the line — nobody holds it again. - -The assignee is who launches agents on the card and whose judgment the -review waits for. It gates exactly one thing — starting work, which -another board refuses until you take the card over deliberately (see -"State syncs; reactions don't"). Everything else is convention: reading, -reviewing and moving are open to anyone, and git history is the audit. - -Two consequences worth knowing: - -- **Hand-moves bypass the claim.** A plain `mv` between stage directories - is still a first-class move (the watcher narrates it), but nothing writes - the assignee — update the line yourself in the same edit as **Status**. -- **Identity is git's, so it collides like git's.** Two machines both - configured `user.name = ronald` are one person as far as the board is - concerned. Teams that share a git history already share that assumption. - -With `BOARD_COMMIT_MOVES` on, board-made moves also **commit themselves**: -the move and the claim land in one commit touching only that task file, -messaged `board: ()`, staged by pathspec so -unrelated staged work is neither committed nor unstaged (hooks are skipped — -this is bookkeeping, not code). Pushing is not part of it: those commits sit -on your local `main` until you push it (or until `BOARD_SYNC` pushes them — -see "Syncing boards"), which the guard in "Pull requests" will tell you -about if you forget. The setting is off by default: a single-player -board neither writes nor clears the assignee and makes no commits, and -`tasks/` is committed by hand. The gate governs only whether -a *move* writes the line — an **Assignee:** added to a file by hand is still -read and shown on the card whether the gate is on or off. - -## Syncing boards - -`BOARD_SYNC=1` makes `origin/main` the truth and every board a converging -replica. It implies `BOARD_COMMIT_MOVES` — a move that never commits has -nothing to publish — and off (the default) nothing below runs: no fetch, -no push, no thread, no behaviour change at all. - -- **Push is event-driven.** The commit a move makes is pushed as soon as - it lands. A rejected push means another board got there first, so the - board fetches, replays its own commits on top and pushes again. -- **Pull is a beat.** Every `BOARD_SYNC_INTERVAL` (30s by default) and - once at startup: fetch, then fast-forward. The watcher narrates what - arrived, attributed to the commit's author rather than `disk`. -- **Losing a race is a toast, not a mystery.** When replaying collides - with a card someone else already moved, origin wins: the local move is - dropped, the file reverts to origin's version and the board says - `07 claimed by elena — your move was undone`. -- **A human's unpushed commit is never published.** Before any auto-push - every local-ahead commit on `main` must be `board: `-prefixed. One - that isn't stops the push (and the replay), names itself in the ticker - and holds the header's `sync stalled` chip until you push it yourself - or move it off main. -- **Offline is quiet.** An unreachable origin says so once, then works - locally; commits queue on `main` and go out on the next reachable - fetch. -- **Which remote is one answer, not two.** One remote and one branch, by - design — but the remote is the one `BOARD_GIT_REMOTE` names, else the - checkout's first, resolved in the same place PR opening asks, so the two - halves of team mode can never publish to different places. It is used as - named: a `BOARD_GIT_REMOTE` this checkout has no remote for stalls - saying so rather than reaching past it for another one. A checkout with - no remote at all stalls the same way, from startup — team mode syncing - nothing is exactly the state a board must not render as healthy, and it - is the likeliest first state of a fresh installation. Add a remote (or - set the setting) and the chip clears with a line saying sync is - converging again. - -### State syncs; reactions don't - -The board does not only render state, it reacts to it: a card entering -review opens a PR. With N replicas watching one truth, a reaction must -fire on exactly one of them, so **only the board whose user made the move -acts on it**. A move a pull applied renders and narrates — attributed to -its author — and triggers nothing. `watch.py` answers the question, since -that is where the attribution already lives, and every future automation -hung off a stage transition inherits it: am I the actor? - -The file-carried gates stay in place behind that rule, so the rare double -is harmless rather than loud: the `**PR:**` line before `gh pr create` -(and a create that races anyway adopts the open PR), an existing branch -and worktree before a work launch. Both layers, deliberately — the -actor-only rule prevents the duplication, idempotency survives it. - -Two consequences you can see: - -- **A half-done side effect is nobody's to finish automatically.** The - actor's board can die between moving a card and opening its PR; no - other board picks that up, and in team mode the startup catch-up stands - down for the same reason. The card wears **↑ open PR** instead — a - person decides. -- **Ownership gates work launches.** A card someone else holds refuses - **▸ start work**, naming them, and offers **▸ take over** as the - deliberate second path. Shared liveness is not part of this: a - teammate's running agent is a static "in-progress, assigned to them" on - your board, because agent registries stay in each board's own memory. - -Two disciplines make this safe, and team mode assumes both: - -- **Local `main` advances only through the board and origin.** Code work - lives in worktrees and PRs — that is what keeps the main checkout clean - and fast-forwardable. Uncommitted changes to tracked files, a checkout - sitting on another branch, or a divergence the guard won't replay all - stall sync rather than risking your work; each one is narrated once and - shown as a chip in the header until it clears. -- **Sync never merges.** It fast-forwards, or rebases the board's own - bookkeeping commits. Nothing here force-pushes, and nothing reacts to - what it pulled beyond narrating it. - -One board fetches twice a minute at the default interval; N boards make -N times that. Against GitHub this is nothing, but on a rate-limited or -metered remote raise `BOARD_SYNC_INTERVAL`. - -## Task file format - -Each task is a markdown file with a descriptive filename -(e.g. `01-document-handling-review.md`). Numbers are allocated in creation order -and stay with the file for life — they do not renumber when a task moves stage. - -Start new tasks from `tasks/task-template.md` — copy it into `backlog/` and -fill it in. Its sections earn their keep: Context and What-to-build are what -a work agent gets as its brief, Acceptance is what reviews judge against, -and a non-empty Open-questions section makes an agent refuse the task -(`NOT READY`) rather than guess. The template itself is never listed on the -board (only stage directories are read). - -The file should have at minimum: - -```markdown -# Task title - -**Status:** Backlog | To Do | In Progress | Review | Done -**Priority:** High | Medium | Low -``` - -Use those exact status values — nothing else (not "Not started", "WIP", etc.) — -and keep the status in step with the directory the file sits in. Priority may -carry a short justification after the level -(e.g. `Medium — foundational for any real environment`). - -Every header field, and who writes it: - -| Field | Required | Value | Written by | -| --- | --- | --- | --- | -| **Status** | yes | `Backlog` · `To Do` · `In Progress` · `Review` · `Done` (`Archived` for a card in `tasks/archive/`) | you, or the board on a move | -| **Priority** | yes | `High` · `Medium` · `Low`, optionally followed by a short justification | you | -| **Type** | no | `Discovery` · `Bug` · `Feature` · `Refactor` · `Chore` · `Phase` | you | -| **Assignee** | no | a name, taken from `git config user.name` | the board on a claiming move, or you by hand | -| **Depends on** | no | task numbers or external preconditions, comma-separated | you | -| **PR** | no | the pull request url | the board when it opens one | - -**Status** is the field the board holds you to: a header that disagrees -with the directory the file sits in is flagged `status drift`. -**Assignee** and **PR** it writes and reads itself. The rest are for -whoever picks the next card. - -An optional **Type** line can record what kind of work the task is, when that -isn't obvious from the title: - -```markdown -**Type:** Discovery | Bug | Feature | Refactor | Chore | Phase -``` - -Type is orthogonal to status. A discovery task — research, scoping, spiking an -approach — moves through the same five stages as everything else; "discovery" -describes the work, not where it sits on the board. +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 @@ -1149,6 +928,236 @@ halted — on every view, holding until the phase is run again or held, alongside the toast and the ticker line that already fire. And the lane head's title and its `card ↗` chip open the phase card. +## Stages + +The flow is linear: + +``` +backlog → to-do → in-progress → review → done +``` + +### backlog/ +Where new tasks are written and where they wait. A backlog task may be rough, +incomplete, or fully specified — what it has in common with its neighbours is +that nobody is working on it. Most tasks live here for most of their life. + +### to-do/ +Picked up and queued to work on next. Moving a task from `backlog/` to `to-do/` +is a commitment to do it soon, so keep this directory short — a long `to-do/` is +just a second backlog. + +### in-progress/ +Actively being worked on right now. Anything here should have someone (or an +agent session) attached to it. If work stalls, move it back to `to-do/` or +`backlog/` rather than leaving it parked — a stale `in-progress/` makes the board +lie about what is happening. + +Implementation plans (created via Claude Code's plan mode) are stored in `plans/` +and can be referenced from the task file. + +### review/ +The work is built and awaits judgment: tests written and passing, a PR open +(see "Pull requests"), behaviour checked in the running app, edge cases +probed. A task sitting here has code but not yet confidence. If review turns +up problems, move it back to `in-progress/`. + +### done/ +Finished and merged. Completed task files are kept as a record of what was built +and why — they are the closest thing we have to design history, so don't delete +or trim them. + +### reference/ (beside tasks/, not a stage) +Supporting documents that tasks can link to — external specs, API documentation, +research notes, screenshots, competitive analysis, regulatory references, etc. +These don't move through the workflow; they're stable resources. Reference them +from task files using relative links — two levels up from a stage directory +(e.g. `[the payments API spec](../../reference/payments-api.md)`). + +## Moving a task + +When moving a task between stages: +1. Update the **Status** field in the task file header +2. Move the file to the new directory +3. Add any notes about why it's moving (e.g. "approach agreed, starting build") + +Moves are not always forward. Going back a stage is normal and expected — +verification failing, or an approach not surviving contact with the code, should +move the task backwards rather than being worked around in place. + +## Claiming a card + +**Claiming is moving.** Taking a card out of `backlog/` or `to-do/` towards +work is the commitment, so that is where ownership is recorded: the board +writes an `**Assignee:** ` line into the header, taken from this +checkout's `git config user.name` — the identity git history already shows, +no new concept. The first claim sticks: a card that already names an +assignee keeps it when someone else moves it on. Walking a card all the way +back to `backlog/` clears the line — nobody holds it again. + +The assignee is who launches agents on the card and whose judgment the +review waits for. It gates exactly one thing — starting work, which +another board refuses until you take the card over deliberately (see +"State syncs; reactions don't"). Everything else is convention: reading, +reviewing and moving are open to anyone, and git history is the audit. + +Two consequences worth knowing: + +- **Hand-moves bypass the claim.** A plain `mv` between stage directories + is still a first-class move (the watcher narrates it), but nothing writes + the assignee — update the line yourself in the same edit as **Status**. +- **Identity is git's, so it collides like git's.** Two machines both + configured `user.name = ronald` are one person as far as the board is + concerned. Teams that share a git history already share that assumption. + +With `BOARD_COMMIT_MOVES` on, board-made moves also **commit themselves**: +the move and the claim land in one commit touching only that task file, +messaged `board: ()`, staged by pathspec so +unrelated staged work is neither committed nor unstaged (hooks are skipped — +this is bookkeeping, not code). Pushing is not part of it: those commits sit +on your local `main` until you push it (or until `BOARD_SYNC` pushes them — +see "Syncing boards"), which the guard in "Pull requests" will tell you +about if you forget. The setting is off by default: a single-player +board neither writes nor clears the assignee and makes no commits, and +`tasks/` is committed by hand. The gate governs only whether +a *move* writes the line — an **Assignee:** added to a file by hand is still +read and shown on the card whether the gate is on or off. + +## Syncing boards + +`BOARD_SYNC=1` makes `origin/main` the truth and every board a converging +replica. It implies `BOARD_COMMIT_MOVES` — a move that never commits has +nothing to publish — and off (the default) nothing below runs: no fetch, +no push, no thread, no behaviour change at all. + +- **Push is event-driven.** The commit a move makes is pushed as soon as + it lands. A rejected push means another board got there first, so the + board fetches, replays its own commits on top and pushes again. +- **Pull is a beat.** Every `BOARD_SYNC_INTERVAL` (30s by default) and + once at startup: fetch, then fast-forward. The watcher narrates what + arrived, attributed to the commit's author rather than `disk`. +- **Losing a race is a toast, not a mystery.** When replaying collides + with a card someone else already moved, origin wins: the local move is + dropped, the file reverts to origin's version and the board says + `07 claimed by elena — your move was undone`. +- **A human's unpushed commit is never published.** Before any auto-push + every local-ahead commit on `main` must be `board: `-prefixed. One + that isn't stops the push (and the replay), names itself in the ticker + and holds the header's `sync stalled` chip until you push it yourself + or move it off main. +- **Offline is quiet.** An unreachable origin says so once, then works + locally; commits queue on `main` and go out on the next reachable + fetch. +- **Which remote is one answer, not two.** One remote and one branch, by + design — but the remote is the one `BOARD_GIT_REMOTE` names, else the + checkout's first, resolved in the same place PR opening asks, so the two + halves of team mode can never publish to different places. It is used as + named: a `BOARD_GIT_REMOTE` this checkout has no remote for stalls + saying so rather than reaching past it for another one. A checkout with + no remote at all stalls the same way, from startup — team mode syncing + nothing is exactly the state a board must not render as healthy, and it + is the likeliest first state of a fresh installation. Add a remote (or + set the setting) and the chip clears with a line saying sync is + converging again. + +### State syncs; reactions don't + +The board does not only render state, it reacts to it: a card entering +review opens a PR. With N replicas watching one truth, a reaction must +fire on exactly one of them, so **only the board whose user made the move +acts on it**. A move a pull applied renders and narrates — attributed to +its author — and triggers nothing. `watch.py` answers the question, since +that is where the attribution already lives, and every future automation +hung off a stage transition inherits it: am I the actor? + +The file-carried gates stay in place behind that rule, so the rare double +is harmless rather than loud: the `**PR:**` line before `gh pr create` +(and a create that races anyway adopts the open PR), an existing branch +and worktree before a work launch. Both layers, deliberately — the +actor-only rule prevents the duplication, idempotency survives it. + +Two consequences you can see: + +- **A half-done side effect is nobody's to finish automatically.** The + actor's board can die between moving a card and opening its PR; no + other board picks that up, and in team mode the startup catch-up stands + down for the same reason. The card wears **↑ open PR** instead — a + person decides. +- **Ownership gates work launches.** A card someone else holds refuses + **▸ start work**, naming them, and offers **▸ take over** as the + deliberate second path. Shared liveness is not part of this: a + teammate's running agent is a static "in-progress, assigned to them" on + your board, because agent registries stay in each board's own memory. + +Two disciplines make this safe, and team mode assumes both: + +- **Local `main` advances only through the board and origin.** Code work + lives in worktrees and PRs — that is what keeps the main checkout clean + and fast-forwardable. Uncommitted changes to tracked files, a checkout + sitting on another branch, or a divergence the guard won't replay all + stall sync rather than risking your work; each one is narrated once and + shown as a chip in the header until it clears. +- **Sync never merges.** It fast-forwards, or rebases the board's own + bookkeeping commits. Nothing here force-pushes, and nothing reacts to + what it pulled beyond narrating it. + +One board fetches twice a minute at the default interval; N boards make +N times that. Against GitHub this is nothing, but on a rate-limited or +metered remote raise `BOARD_SYNC_INTERVAL`. + +## Task file format + +Each task is a markdown file with a descriptive filename +(e.g. `01-document-handling-review.md`). Numbers are allocated in creation order +and stay with the file for life — they do not renumber when a task moves stage. + +Start new tasks from `tasks/task-template.md` — copy it into `backlog/` and +fill it in. Its sections earn their keep: Context and What-to-build are what +a work agent gets as its brief, Acceptance is what reviews judge against, +and a non-empty Open-questions section makes an agent refuse the task +(`NOT READY`) rather than guess. The template itself is never listed on the +board (only stage directories are read). + +The file should have at minimum: + +```markdown +# Task title + +**Status:** Backlog | To Do | In Progress | Review | Done +**Priority:** High | Medium | Low +``` + +Use those exact status values — nothing else (not "Not started", "WIP", etc.) — +and keep the status in step with the directory the file sits in. Priority may +carry a short justification after the level +(e.g. `Medium — foundational for any real environment`). + +Every header field, and who writes it: + +| Field | Required | Value | Written by | +| --- | --- | --- | --- | +| **Status** | yes | `Backlog` · `To Do` · `In Progress` · `Review` · `Done` (`Archived` for a card in `tasks/archive/`) | you, or the board on a move | +| **Priority** | yes | `High` · `Medium` · `Low`, optionally followed by a short justification | you | +| **Type** | no | `Discovery` · `Bug` · `Feature` · `Refactor` · `Chore` · `Phase` | you | +| **Assignee** | no | a name, taken from `git config user.name` | the board on a claiming move, or you by hand | +| **Depends on** | no | task numbers or external preconditions, comma-separated | you | +| **PR** | no | the pull request url | the board when it opens one | + +**Status** is the field the board holds you to: a header that disagrees +with the directory the file sits in is flagged `status drift`. +**Assignee** and **PR** it writes and reads itself. The rest are for +whoever picks the next card. + +An optional **Type** line can record what kind of work the task is, when that +isn't obvious from the title: + +```markdown +**Type:** Discovery | Bug | Feature | Refactor | Chore | Phase +``` + +Type is orthogonal to status. A discovery task — research, scoping, spiking an +approach — moves through the same five stages as everything else; "discovery" +describes the work, not where it sits on the board. + An optional **Assignee** line records who holds the card: ```markdown diff --git a/README.md b/README.md index 085a41b..3497e79 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,11 @@ A live kanban for coding-agent work: task files in stage directories are the only source of truth; a stdlib-only board narrates everything that happens to them — agents working in git worktrees, PRs opening on review, CI and Copilot state on the cards, drives of the app from a task's own -branch, and an archive that is never a delete. Turn on team mode +branch, and an archive that is never a delete. Work that is a run of +related cards rather than one is a **phase**: a card that lists its +cards, an integration branch of its own, and members branched from it, +run one at a time and merged back on green — ending in a single PR into +`main` for a human. Turn on team mode (`BOARD_SYNC=1`) and the truth is `origin/main`: moves commit and push themselves, every board pulls on a beat, and the person who claimed a card first keeps it. diff --git a/manager/core/VERSION b/manager/core/VERSION index bc872a0..e6d0c43 100644 --- a/manager/core/VERSION +++ b/manager/core/VERSION @@ -1 +1 @@ -0.3-alpha +0.4-alpha diff --git a/site/pages.json b/site/pages.json index eb7abef..5942572 100644 --- a/site/pages.json +++ b/site/pages.json @@ -78,6 +78,39 @@ "description": "A card entering review gets a PR opened for it by the board; then review PR, copilot and act on PR, until it settles.", "source": "AGENTS.md", "from": "## Pull requests", + "to": "## Phases" + }, + { + "path": "/concepts/phases/", + "title": "Phases", + "layout": "article", + "section": "Concepts", + "description": "A phase is a card that lists its cards: Type: Phase, a ## Cards section in run order, and membership that runs one direction only.", + "lede": "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.", + "source": "AGENTS.md", + "from": "## Phases", + "to": "### A phase runs itself, on a branch of its own" + }, + { + "path": "/concepts/running-a-phase/", + "title": "Running a phase", + "layout": "article", + "section": "Concepts", + "description": "Each member branches from the phase's tip, runs headless, and merges back on green. Halt, never skip — and one PR into main at the end.", + "lede": "Everything between starting a phase and the one PR it ends with: where each member's branch is cut, what advances the run and what stops it, how a restarted board picks the phase back up, and what merging it does to the cards it carried.", + "source": "AGENTS.md", + "from": "### A phase runs itself, on a branch of its own", + "to": "### A phase's members leave the Board view" + }, + { + "path": "/concepts/phases-on-the-board/", + "title": "Where a phase is drawn", + "layout": "article", + "section": "Concepts", + "description": "The Board stops listing a phase's members and the phase card stands for them; the Phases view gives every phase a swimlane of its own.", + "lede": "A phase card stands for cards the kanban no longer lists. What the columns say instead, what the phase card owes in return, and the view that gives every phase a lane of its own.", + "source": "AGENTS.md", + "from": "### A phase's members leave the Board view", "to": "## Stages" }, { diff --git a/site/templates/home.html b/site/templates/home.html index 8d613ee..ecd4296 100644 --- a/site/templates/home.html +++ b/site/templates/home.html @@ -157,10 +157,12 @@ Task board for ~/your-repo/.task-manager/tasks the moment they end.
- Parallel work, zero collisions + Parallel when it can be, in order when it must Each agent gets its own git worktree and - branch. Run five at once without them editing the same file out - from under each other. + branch, so five run at once without editing the same file out from + under each other. Work that has to land in order is a phase: one + branch, its cards worked and merged into it one at a time, and one + PR at the end.
Nothing merges without you