feat(board)!: rank --plan on five ordered groups, planned above in-progress

Replaces the weighted score shipped in 0.19.0 with five lookups: chain-root
credit, unhandled inbox, planned, in-progress, undeclared status. Within a
group: that group's own quantity, then a Sonnet next-cost, then oldest plan.

The score's objection is accepted, not forgotten, and is written into board.sh
and CLAUDE.md so a later session reads it as decided rather than as an unfixed
defect: a group order cannot express "owes one message AND releases two others"
as one quantity. What the score could not do was hold still for the format's
second consumer - re-tuning one weight against another silently reorders a
parser in another repo, and no test here can catch that.

planned now ranks above in-progress, inverted by the same decision: converting a
decision into motion is the slow step; live work is already moving.

Debt stays uncapped and never excluded. One group below chain-root credit is not
the cap declined at 0.19.0 - the debtor keeps its tab, its most-owed-first
position, and its why=inbox:N. Pinned by a discriminating fixture the score
would fail: a root releasing one repo outranks a repo owing four.

board-selftest 134 -> 138. Suite 183 + 138 + 73 = 394.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y6ULuFCPMNYAPNN3pAjsXQ
This commit is contained in:
Kjell Tore Guttormsen 2026-08-03 06:55:21 +02:00
commit d0a5ffe515
10 changed files with 287 additions and 118 deletions

View file

@ -20,7 +20,7 @@ description: >-
user names no repo and no tool — choosing *between* repos is this skill. Not for
"where were we" inside the current repo: that is this repo's own STATE.md,
already injected at session start.
version: "0.19.0"
version: "0.20.0"
---
# board — which repo deserves the next session
@ -139,13 +139,18 @@ and it has its own rendering:
This is the one case where a list *is* the answer and the no-dumping rule does not
apply — the user asked for the day, and a day has more than one repo in it. Pass
the plan through with a short framing line; do not re-rank it, re-order it, or trim
it. The order is the engine's position and it is deterministic — one score, not
four groups:
it. The order is the engine's position and it is deterministic — five ordered
groups, each a lookup, no weights:
40 x repos released transitively (chain-root credit)
15 x unhandled inbox messages
+10 in-progress / +5 planned / +2 no declared status
+3 when next-cost names a Sonnet row
1. chain-root credit (repos released transitively, most first)
2. unhandled inbox (most owed first, whatever the status)
3. planned
4. in-progress
5. no declared status (last, and labelled)
Within a group: that group's own quantity, then a Sonnet `next-cost`, then oldest
plan first. `planned` sits above `in-progress` by operator decision at 0.20.0 —
turning a decision into motion is the slow step; live work is already moving.
**Chain-root credit is the term worth understanding before you explain an order
to the operator.** For every `blocked` repo the engine follows `blocked-on` to
@ -154,13 +159,14 @@ repo releases nobody — its next step is by definition waiting; opening the roo
releases everything behind it. A cycle or a `blocked-on` naming an unscanned repo
credits nobody. This is engine rule 1 above, now computed rather than eyeballed.
Debt is **uncapped** by decision: owing a reply is the other axis from a repo's
own next step, and answering is often what unblocks a chain. At 15 per message a
repo owing one more still outranks any combination of status and cost bonuses.
Debt is **uncapped and never excluded** by decision: owing a reply is the other
axis from a repo's own next step, and answering is often what unblocks a chain.
Sitting one group below chain-root credit is not a cap — a debtor keeps its tab,
its most-owed-first position among the other debtors, and its `why=inbox:N`.
`why=` names the **dominant** term, so a block can read `why=unblocks:2` even
though the repo also owes mail. Read it as "what opening this releases", not as
the only reason it qualified. Substituting your own judgement for the order makes
`why=` names the **group** that put the repo in the plan, so a block can read
`why=unblocks:2` even though the repo also owes mail. Read it as "what opening
this releases", not as the only reason it qualified. Substituting your own judgement for the order makes
the plan unreproducible and costs the property that makes it trustworthy.
Two things to say out loud when you hand it over:

View file

@ -15,7 +15,7 @@ description: >-
covers retiring a broadcast that has become wrong or obsolete: "retract that
broadcast", "that announcement is outdated, pull it", "trekk tilbake kringkastingen",
"den broadcasten er utdatert".
version: "0.19.0"
version: "0.20.0"
---
# coord-send — natural-language front door for inter-repo messages

View file

@ -14,7 +14,7 @@ description: >-
the operator names no model and no tool — choosing the model for the next
session IS this skill. Not for choosing which REPO gets the next session:
that is the `board` skill.
version: "0.19.0"
version: "0.20.0"
---
# route — what the next session should run with