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

@ -54,7 +54,7 @@ marketplace plugin. Three components, one boundary:
- **Board (`scripts/board.sh`):** cross-repo attention board. Reads STATE.md
next-step blocks + board lines, `git status`, and mailbox pending counts, and
prints one line per repo. Read-only by construction: it writes to no repo, no
STATE.md and no mailbox. Pinned by `board-selftest.sh` (51 checks).
STATE.md and no mailbox. Pinned by `board-selftest.sh` (86 checks).
**It lives here because the mailbox is one of its three inputs, and it carries
the same axis distinction the mailbox does.** A pending count means *others
@ -82,6 +82,38 @@ marketplace plugin. Three components, one boundary:
differing on it. A repo with no route line is told so rather than handed a
guess, because a guessed command reads as authoritative.
**`board.sh --plan` is the THIRD rendering, and the only one that takes a
position.** It answers which repos to open a tab for today, in what order,
with which command. The position it takes is the ORDER and nothing else -
there is no cutoff, so the plan hides nothing, and every rule is a lookup over
fields the scan already read. Debt ranks first regardless of status, and that
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, while owing a reply is the other axis entirely - answering is often
what unblocks it. Measured on the real tree at 0.16.0, two of 26 planned repos
were `done` with an unhandled inbox. Repos with no board line rank last and
are LABELLED rather than dropped, because the table already prints a MERK line
about them and a plan that omitted them silently would repeat that defect.
It renders `key=value` blocks, not prose, because it has two consumers: the
operator, and a driver repo consuming the plan. Prose would make the rendered
format an API no test in THIS repo could hold stable for a consumer in
another. `command_missing=` carries both no-command causes (no route line, and
a route line route.sh rejects) because a bare `command=` is the shape of a
runnable command carrying nothing - a driver reading `^command=` would type an
empty line into a live pane. `route_cmd_for()` is the single reader of the
route-line grammar, shared with `--brief`, and distinguishes the two causes by
exit code rather than by an empty string.
**Driving a terminal from the plan does NOT belong here, and the measurement
in `docs/ghostty-orchestration-measurement.md` is the argument, not taste.**
It is 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.
None of that is mailbox transport, and none of it may be able to break
`coord-inbox` or `board`. The dependency runs one way: the driver consumes the
plan, the plan never knows a terminal exists.
It also cross-checks itself against `coord-count.sh`, and that is not
belt-and-braces. The repo scan and the mailbox are two different populations:
a mailbox can carry a name no scan will ever produce — a declared non-git
@ -209,7 +241,7 @@ obligations in another repo.
builtins only in hook and tests.
- TDD: no behavior change without a failing selftest check first.
`bash scripts/coord-selftest.sh` must exit 0 (183/183),
`bash scripts/board-selftest.sh` must exit 0 (51/51) and
`bash scripts/board-selftest.sh` must exit 0 (86/86) and
`bash scripts/route-selftest.sh` must exit 0 (73/73).
- English for all code, docs, and commit messages (public repo). Norwegian
trigger aliases in the skill description are deliberate.