fix(board): one spec for next-cost, and it is the rubric's

The board line's cost field had two specifications and one of them was this
script. `--help` prescribed a versionless `sonnet/xhigh`; the global model
rubric it points at spells the model `Sonnet 5 / xhigh`. A session following
either was correct by its own source, so the field drifted with nobody making
a mistake. Measured across a real 44-repo tree: 14 board lines carrying eight
distinct strings, and not one of them the form documented here - the only
spelling nobody used was ours.

The help now prescribes the rubric's, which is also what most lines already
carry. The parser is untouched and stays lenient: it reads to `;` or `-->` so
the rubric's spacing survives, and the fixtures still vary the spelling on
purpose to pin that. Read-side leniency was never the defect; it is what makes
a documentation-only fix sufficient.

No normalization at read, deliberately. Board lines are rewritten every session
by convention, so an unambiguous spec self-heals the existing lines in about one
session per active repo - mapping variants would be permanent code migrating
data that migrates itself. Nothing compares the field mechanically today either:
KOST is a display column and the board sorts on a numeric bucket key, so the
defect was real but latent. Two checks (28 -> 30) pin the documented form,
red first, because cleaning the data cannot fix a spec that regenerates the
divergence every session. Validation at write has no write path to attach to:
nothing in this repo emits a board line outside the fixtures.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U6EixQo6hpoRCVtiAXdnFs
This commit is contained in:
Kjell Tore Guttormsen 2026-07-28 22:06:45 +02:00
commit 305f1687b7
5 changed files with 54 additions and 5 deletions

View file

@ -5,6 +5,34 @@ 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).
## [Unreleased]
### Fixed
- **The board line's `next-cost` field had two specs, and one of them was
ours.** `board.sh --help` prescribed a versionless `sonnet/xhigh` while the
global model rubric it points at spells the model `Sonnet 5 / xhigh`. Neither
a session following the help nor one following the rubric was doing anything
wrong, so the field drifted: measured across a real 44-repo tree, the 14
existing board lines carried **eight** distinct strings — and not one used the
form documented here. The help now prescribes the rubric's spelling
(`Sonnet 5/xhigh`), which is also the one most lines already use.
**The parser is unchanged and stays lenient.** It still reads to `;` or
`-->` rather than stopping at the first non-lowercase character, precisely so
the rubric's spacing survives; the selftest fixtures still vary the spelling
on purpose to pin that. Leniency on the read side was never the defect — it
is what makes a documentation-only fix sufficient.
**No normalization was added, deliberately.** Board lines are rewritten every
session by convention, so an unambiguous spec self-heals the existing lines
within about one session per active repo. Mapping variants at read time would
be permanent code migrating data that migrates itself, and nothing compares
the field mechanically today: `KOST` is a display column, and the board sorts
on a numeric bucket key. Two new `board-selftest.sh` checks (30 total) pin the
documented form, because a spec that contradicts the rubric regenerates the
divergence no matter how often the data is cleaned up.
## [0.9.0] - 2026-07-28
### Added

View file

@ -31,7 +31,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` (28 checks).
STATE.md and no mailbox. Pinned by `board-selftest.sh` (30 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
@ -78,7 +78,7 @@ halves together for exactly that reason.
builtins only in hook and tests.
- TDD: no behavior change without a failing selftest check first.
`bash scripts/coord-selftest.sh` must exit 0 (136/136) and
`bash scripts/board-selftest.sh` must exit 0 (28/28).
`bash scripts/board-selftest.sh` must exit 0 (30/30).
- English for all code, docs, and commit messages (public repo). Norwegian
trigger aliases in the skill description are deliberate.
- Conventional Commits: `type(scope): description`.

View file

@ -124,7 +124,7 @@ Note that raising the inbox's priority (Rule 7) deliberately does **not** widen
## Development
bash scripts/coord-selftest.sh # 136 checks against a throwaway mailbox
bash scripts/board-selftest.sh # 28 checks against a throwaway repo tree
bash scripts/board-selftest.sh # 30 checks against a throwaway repo tree
npm test # both selftests via node --test
TDD is the house rule: every behavior change lands with a failing selftest check first.

View file

@ -213,6 +213,23 @@ mkrepo "$ROOT/repo-f"
# Unreadable root is a no-op, not a crash.
"$BOARD" --roots "$ROOT/does-not-exist" >/dev/null 2>&1; check "missing root is a clean no-op" $?
# --- 8. The documented next-cost form IS the convention --------------------
# The cost field is free text on purpose: the parser reads to ';' or '-->' so
# the rubric's spacing survives, and the fixtures above deliberately vary the
# spelling to pin that leniency. But leniency on the read side means the ONLY
# thing holding the field uniform is what --help prescribes, and --help once
# prescribed a versionless `sonnet/xhigh` while the global rubric spelled
# `Sonnet 5 / xhigh`. Two specs for one field: measured across the real tree,
# not one board line used ours, and eight distinct strings were in circulation.
# Pinned here because cleaning the DATA cannot fix that - a spec that
# contradicts the rubric regenerates the divergence every session.
HELP="$("$BOARD" --help 2>/dev/null)"
printf '%s' "$HELP" | grep -q 'next-cost=Sonnet 5/xhigh'
check "--help prescribes the canonical next-cost form" $?
# The versionless form is what the rubric does NOT use; no example may show it.
if printf '%s' "$HELP" | grep -qE '(sonnet|opus|fable)/(high|xhigh)'; then rc=1; else rc=0; fi
check "--help shows no versionless model example" "$rc"
echo ""
echo "board-selftest: $PASS passed, $FAIL failed"
[ "$FAIL" -eq 0 ] || exit 1

View file

@ -18,11 +18,15 @@
#
# Board line (optional, one per STATE.md, directly under the NESTE heading):
#
# <!-- board: status=in-progress; blocked-on=-; next-cost=sonnet/xhigh -->
# <!-- board: status=in-progress; blocked-on=-; next-cost=Sonnet 5/xhigh -->
#
# status planned | in-progress | blocked | deferred | done
# blocked-on <repo-name> or - (only meaningful with status=blocked)
# next-cost <model>/<effort> from the global model rubric, e.g. opus/xhigh
# next-cost <model>/<effort>, the model spelled EXACTLY as the global rubric
# spells it: Sonnet 5/xhigh, Opus 5/high. The parser below accepts
# any spelling, but this field is compared across repos by eye, so
# one form is the whole point - and a second spelling documented
# here is how a field with no write path drifts.
#
# ATTENTION AXIS, NOT A TOPIC AXIS. This status vocabulary is deliberately NOT
# the vocabulary a cross-repo TOPIC register uses. A topic register answers