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

@ -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` (134 checks).
STATE.md and no mailbox. Pinned by `board-selftest.sh` (138 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
@ -86,10 +86,24 @@ marketplace plugin. Three components, one boundary:
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 term is a lookup over
fields the scan already read. Since 0.19.0 it is ONE score rather than four
buckets, because four groups cannot express "this repo owes one message and
releases two others" - which is how a chain's root ended up ranked below the
repos waiting on it.
fields the scan already read. Since 0.20.0 it is FIVE ORDERED GROUPS -
chain-root, debt, planned, in-progress, undeclared - ranked within a group by
that group's own quantity, then a Sonnet next-cost, then oldest plan first.
**0.19.0 shipped a weighted score here and 0.20.0 replaced it, and the
objection the score answered is ACCEPTED, not forgotten.** A group order
genuinely cannot express "this repo owes one message and releases two others"
as one quantity; a score could, and that was its point. What a score could not
do was hold still for the second consumer - re-tuning 40 against 15 silently
reorders a parser living in another repo, and no test in THIS repo can catch
that. The operator weighed both and chose the lookup (2026-08-03). Write that
down every time this paragraph is edited: a later session that reads the
objection as an unfixed defect will "restore" the score, and the round trip is
the loop this file exists to stop.
**`planned` ranks ABOVE `in-progress`, inverted at 0.20.0 by operator
decision.** Turning a decision into motion is the slow step; live work is
already moving. Flipping it back is a policy change, not a sort fix.
**Debt is never excluded and never capped, and that is the rule most likely to
be "fixed" into a defect.** Excluding `blocked` or `done` is a claim about a
@ -97,11 +111,12 @@ marketplace plugin. Three components, one boundary:
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. A cap on debt was proposed with the chain credit at 0.19.0 and DECLINED
by the operator for the same reason. The weight carries that decision: 15 per
message is above the sum of every other bonus (13), so one message more still
outranks any combination of status and cost. Lowering it to 12 - the value
originally proposed - lets a tiebreak flip the debt order, which is the cap
arriving through the back door.
by the operator. Sitting one group below chain-root credit is NOT that cap:
the debtor keeps its tab, its most-owed-first position among the other
debtors, and its `why=inbox:N`. A change that DROPPED a debtor from the plan
would be the declined cap wearing the group order as a disguise, and selftest
section 12 pins both halves - the root outranking four owed messages, and the
debtor keeping everything it had.
**Chain-root credit lands on the ROOT and nowhere else.** For every `blocked`
repo the `blocked-on` edge is followed transitively to the first repo that is
@ -272,7 +287,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 (134/134) and
`bash scripts/board-selftest.sh` must exit 0 (138/138) 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.