feat(board): render the day plan as a third rendering of the same scan

The table says what the state of every repo is; the briefing says who is
waiting. Neither says which repos to open a tab for today, in what order,
and with which command - so a day plan was assembled by hand every morning
from a table that deliberately takes no position.

--plan is built on exactly the argument --brief was: a lookup over data the
scan already holds, zero model calls, and route.sh already derives the
per-repo command. The order IS the position it takes, and the only one -
there is no cutoff, so nothing is hidden.

The rule most likely to be "fixed" into a defect: repos owing mail rank
first regardless of status. Excluding blocked or done is a claim about a
repo's OWN next step, which by definition cannot be moved, while owing an
answer is the other axis entirely - and answering is often what unblocks
it. Measured on the real tree, two of 26 planned repos were done with an
unhandled inbox, so the fixture tree pins done-with-debt and
blocked-with-debt rather than the general rule alone. Repos with no board
line rank last and are LABELLED, because the table already prints a note
about them and dropping them silently would repeat that defect.

key=value rather than prose, because the plan has two consumers: the
operator, and a separate repo driving a terminal from it. Prose would make
the rendered format an API no test here could hold stable for a consumer
living in another repo. dir= is absolute because a new pane inherits its
anchor's directory; command_missing= carries both no-command causes,
because a driver reading ^command= would type a blank line into a live pane.

route_cmd_for() becomes the single reader of the route-line grammar, shared
with --brief, distinguishing the two failure causes by exit code rather
than by an empty string. --brief output is unchanged.

Driving a terminal from the plan stays OUT of this repo, on the measurement
in docs/ghostty-orchestration-measurement.md rather than on taste.

board-selftest: 51 -> 86 checks.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011eKRzhD1RqY2MxvDEkrJAM
This commit is contained in:
Kjell Tore Guttormsen 2026-08-02 06:40:22 +02:00
commit 6d0fe14d0f
10 changed files with 485 additions and 31 deletions

View file

@ -5,6 +5,71 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.16.0] - 2026-08-02
### Added
- **`board.sh --plan` — the day plan, a THIRD rendering of the same scan.** The
table says what the state of every repo is; the briefing says who is waiting;
the plan says which repos to open a tab for today, in what order, with which
command to start each. Built on exactly the argument `--brief` was: it is a
lookup over data the scan already holds, it costs zero model calls, and
`route.sh` already derives the per-repo command.
**The order is the position it takes, and the only one.** There is no cutoff,
so nothing is hidden, and four deterministic rules decide the ranking:
1. **Repos owing mail come first, most-owed first — whatever their status.**
This is the rule most likely to be "fixed" into a defect. Excluding
`blocked` or `done` is a claim about a repo's *own next step*, which by
definition cannot be moved; owing an answer is a different axis entirely,
and answering is frequently what unblocks it. Measured on the real tree at
this version, two of 26 planned repos were `done` with an unhandled inbox,
which is why the fixture tree now pins both `done`-with-debt and
`blocked`-with-debt rather than the general rule alone.
2. `in-progress` — live work, oldest plan first.
3. `planned`.
4. Repos with no board line, ranked last and **labelled** `why=uavklart`. The
table already prints a note about these; a plan that dropped them silently
would repeat exactly that defect.
Excluded, and only when the repo owes nothing: `done`, `deferred`, `blocked`,
and repos with no STATE.md. A tab that cannot be moved is not a plan entry.
- **`key=value` blocks rather than prose, because the plan has two consumers:**
the operator pasting a command, and a separate repo driving a terminal from
it. Prose would make the rendered format an API that no test in this repo
could hold stable for a consumer living in another one. Comment lines all
start with `#`, so a consumer drops them with one rule.
Two fields carry measured constraints rather than preferences. `dir=` is
absolute because a new terminal pane inherits its anchor's working directory,
so a plan that omitted it would look right and point at the wrong repo. And a
repo whose route line is missing or unparseable gets `command_missing=` with
the reason, never an empty `command=`: a driver reading `^command=` would type
a blank line into whatever that pane is running.
### Changed
- `route_cmd_for()` is now the single reader of the route-line grammar, shared
by `--brief` and `--plan`. It distinguishes "no route line" from "a route line
`route.sh` rejects" by **exit code** rather than by an empty string, because
both must degrade to a marker and neither may become a guess. `--brief` output
is unchanged.
- `board-selftest.sh`: 51 -> 86 checks.
### Notes
- **Driving a terminal from this plan deliberately does not live here.** The
measurement in `docs/ghostty-orchestration-measurement.md` is the argument:
it would be a version-pinned, undocumented composition over a preview API
whose documented path is already broken upstream and whose regression was
closed as *not planned*, with a blast radius reaching into other repos' live
sessions. The dependency runs one way — the driver consumes the plan, the plan
never knows a terminal exists — so if that API breaks, the plan still prints.
- The `0.15.0` entry is missing from this file; the release itself shipped and is
tagged. This is a gap in the log, not in the code.
## [0.14.0] - 2026-08-01
### Added