feat(route): give next-cost a writer, and make the rubric the only copy

The board line's next-cost field had a reader (board.sh) and no writer, so
its value was retyped by hand every session and drifted into several
competing spellings. Cleaning the data could not fix that: the cause was the
missing write path.

route.sh is that writer. Four scored traits of the next task -- path,
verification, reversibility, scope -- plus a required rationale, run through
the operator's model rubric moved here as the single copy. The row table is a
closed set of six values, so a seventh spelling cannot enter circulation, and
route-selftest.sh section 6 runs the round trip (route emits -> board parses)
inside one repo rather than across two.

Two spellings of one decision come out of one table: the rubric name for the
board line, the CLI alias for the command the operator pastes. Effort levels
are the set pinned in this marketplace; model aliases are gated against the
installed claude rather than hardcoded.

Three things worth naming:

- Escalation is asymmetric. Any single trait escalates; the cheapest row needs
  all four at the cheap end. Underkill costs one session, overkill costs quota
  every session -- but a wrong architecture call costs more than either.
- The Fable rows fire only from an explicit judgement flag, never inferred
  from the last-session record. "The session did not finish" also covers
  context exhaustion and operator interrupts, which say nothing about the
  model, and Fable runs without an advisor.
- The trait block is a single-line HTML comment because board.sh's NESTE
  extractor skips only lines that START with '<!--'. Measured first: a YAML
  block or a multi-line comment silently replaces the repo's next step on the
  board with "next_task:". Pinned by section 7.

board.sh is untouched as a program; its header now points at route.sh for the
value set so this does not reopen the two-specs defect 305f168 closed.

Selftests: coord 136, board 30, route 47 (new), node 7.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017peNgsxVt1BR4BTuMwiPoX
This commit is contained in:
Kjell Tore Guttormsen 2026-07-31 10:10:22 +02:00
commit 0d1183800e
8 changed files with 808 additions and 11 deletions

View file

@ -40,10 +40,35 @@ marketplace plugin. Three components, one boundary:
of two repos would not be enforcing it. `~/.claude/scripts/board.sh` is a
deployed copy (the operator's `board()` shell function points at it), exactly
as with the `coord-*` scripts — this repo is the source of truth.
- **Skills (`skills/coord-send/`, `skills/board/`):** natural-language front
- **Route (`scripts/route.sh`):** pure calculator for the next session's model
and effort. Takes four scored traits of the next task plus a required
rationale, and prints one block of `key=value` lines: the rubric row, the rule
that fired, the `next-cost` value, a pasteable startup command, the one-row
cheaper fallback, and the STATE.md comment lines. Pinned by
`route-selftest.sh` (47 checks).
**It is here because it is the WRITER for the field `board.sh` already reads.**
`next-cost` had a reader and no writer, so it was hand-typed every session and
drifted into several competing spellings — cleaning the data
could not fix that, because the cause was the missing write path. The row
table is a closed set of six values, so a seventh cannot enter circulation,
and section 6 of the selftest runs the round trip (route emits → board parses)
*inside* one repo rather than across two. `board.sh` itself is untouched: a
calculator that prints to stdout writes nothing, and the session writes
STATE.md.
**The row table is the operator's global rubric, moved here as the single
copy.** It is not a second spec — `board.sh --help` documents the board line's
*grammar* and points here for the *values*. Scoring the traits is judgement
and belongs to the skill; turning scores into a row is a lookup and takes zero
model calls.
- **Skills (`skills/coord-send/`, `skills/board/`, `skills/route/`):** natural-language front
doors mapping user intent to engine invocations. No mailbox logic lives here
either. `board` additionally owns the *ranking* — which repo wins and why —
since `board.sh` deliberately prints evidence and takes no position.
since `board.sh` deliberately prints evidence and takes no position. `route`
likewise owns the *scoring*: the calculator is deterministic, so all judgement
sits in choosing the four trait values, and the skill must never reason its
way to a model instead.
**Boundary rule:** the mailbox is transport, not state. Durable decisions
live in the owning repo's docs/git history; messages are notices pointing at
@ -77,24 +102,27 @@ halves together for exactly that reason.
- Zero dependencies everywhere: bash + coreutils in the engine, `node:`
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 (30/30).
`bash scripts/coord-selftest.sh` must exit 0 (136/136),
`bash scripts/board-selftest.sh` must exit 0 (30/30) and
`bash scripts/route-selftest.sh` must exit 0 (47/47).
- English for all code, docs, and commit messages (public repo). Norwegian
trigger aliases in the skill description are deliberate.
- Conventional Commits: `type(scope): description`.
## Commands
- Test: `bash scripts/coord-selftest.sh` and `bash scripts/board-selftest.sh`
(or `npm test`, the Node wrapper around both)
- Test: `bash scripts/coord-selftest.sh`, `bash scripts/board-selftest.sh` and
`bash scripts/route-selftest.sh` (or `npm test`, the Node wrapper around all three)
- Hook smoke test: `node hooks/scripts/session-start.mjs` (expects JSON on stdout)
- Board smoke test: `bash scripts/board.sh` (read-only, ~3s over the real tree)
- Route smoke test: `bash scripts/route.sh --path known --verification strong
--reversibility cheap --scope local --rationale x` (writes nothing, instant)
## Release
Version must agree across: `.claude-plugin/plugin.json`, `package.json`,
README version badge, `skills/coord-send/SKILL.md` and `skills/board/SKILL.md`
frontmatter, git tag `vX.Y.Z`, and the catalog `ref` in
README version badge, `skills/coord-send/SKILL.md`, `skills/board/SKILL.md` and
`skills/route/SKILL.md` frontmatter, git tag `vX.Y.Z`, and the catalog `ref` in
`ktg-plugin-marketplace/catalog/.claude-plugin/marketplace.json`. Release via
the catalog's `scripts/release-plugin.mjs repo-mailbox` (tag + ref bump
together);