repo-mailbox/skills/route/SKILL.md
Kjell Tore Guttormsen 5e5bc4a66e feat(route,board): strike the advisor rule, add board.sh --row <repo>
Order 20260912T202210Z-7588027378-from-.claude, operator decision
2026-09-12 (helhetlig vurdering av arbeidssystemet, cut row 3 and the
board.sh --row improvement row). One order, two parts, one version bump.

THE ADVISOR RULE IS STRUCK. route.sh and board.sh --dispatch emit no
--advisor at all. The rule fired per ROW on a need - always on the Sonnet
rows (a capability lift, which is what made the quota fallback safe to
take), and on the Opus rows at reversibility=costly|one-way - and it read
well. It was killed by a MEASUREMENT, not by taste: of 54 dispatches the
PM issued 08.-12.09, ZERO carried the flag, because sessions are started
by hand from the model and effort rather than from the whole emitted
line. A rule nothing honours is not a policy, and an emitted value nobody
acts on is decoration in a field whose only job is to be evidence. The
advisor is now what it already was in practice: an operator decision per
session, said in one sentence in route.sh --help.

The comments that rested on the rule were REWRITTEN, not left standing.
board.sh --dispatch still refuses a --model/--effort pair, but the reason
is no longer "the advisor is a property of the ROW": it is that the rubric
has exactly one copy, and a dispatch taking the model directly would be a
second, unscored way to reach the same decision - recording no traits, no
rationale and no next-cost, so nothing afterwards could say whether the
routing or the scoring was wrong. A comment defending a removed mechanism
is how the next session restores it. Both skills carry the correction.

Pinned as an ABSENCE over the whole trait space - 81 combinations, every
line of output, with a known-positive control proving the sweep's grep
can find a planted advisor - rather than on four sampled rows, because
the claim is that no path emits it. board.sh --dispatch at
reversibility=costly is pinned separately: that is the exact input a
reintroduced rule would fire on. The literal string is absent from
route.sh entirely, including the paragraph recording what was struck (it
says "an opus advisor flag" in words), because a blunt grep cannot tell a
description from a specification. Backward compatibility is pinned rather
than assumed: a route line carrying a legacy advisor= field still parses
and still yields a command - measured, 0 of 48 route lines in ~/repos
carry one, but a reader that broke on an unknown field would turn last
month's STATE.md into "that repo has no route line". The three CLI gates
section 14 carried went with the rule; the suite no longer depends on the
installed claude at all.

board.sh --row <repo> IS THE SEVENTH RENDERING of the same scan, never a
second scan, read-only like every other one. (The order calls it the
sixth; by this file's own numbering --inbox-plan is the fourth and
--dispatch the fifth. Corrected rather than carried wrong.) It exists
because the columns WERE misread: on 11.09 the PM read FLY off the table
by eye and got it wrong, while every other rendering a program consumes
is already key=value. inn, ordre and fly are three separate fields
because they are three separate facts; status is the bare token, never
the table's blocked>target display, with blocked-on beside it; neste is
last and uncut. An unknown repo exits 2 and writes NOTHING to stdout - an
empty block would read as a repo whose every column is blank, which is a
real and different state.

upushet is the ONE field that is not a rendering of the scan, and it is
named rather than blended in: nothing in the scan measures it, so it is
read once, for the named repo only, and never enters the table, the plan
or the briefing. It reads the remote-TRACKING ref, not the remote, so
upushet=N honestly means "the local ref says N"; a repo with no upstream
reports ?, never 0.

The row fixture's three counts are three DIFFERENT integers (3/2/1), and
that is the finding worth recording. Built first with 2/1/1, it was
mutation-tested by making fly read the ORDRE field - the exact 11.09
misreading - and the check stayed GREEN, because the two fields held the
same digit. A fixture that cannot tell two columns apart is the defect
wearing a passing test, inside the section written to prevent it.

Suites under /bin/bash 3.2, before -> after: coord 257 -> 257, board
393 -> 427, route 73 -> 73 (13 advisor checks and 3 CLI gates out, 15
absence/legacy checks in, and it no longer varies with claude being on
PATH), orders 116 -> 116, state-line-guard 54 -> 54. Sum 893 -> 927,
README badge updated to the measured sum. npm test 12/12, fail 0.

Verified live against the real tree, not only fixtures: --row
repo-mailbox reports fly=1 beside ordre=0 (the distinction that was
misread), --row on the nested key from-ai-to-chitta/content-sadhguru
resolves, and an unknown repo exits 2.

No tag, no push, no catalog change - that is the operator's
release-plugin.mjs run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 23:45:47 +02:00

210 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
name: route
description: >-
Decide which model and reasoning effort the NEXT session should run with, by
scoring four traits of the next task and running them through the rubric row
table (`route.sh`). Use at session end, whenever STATE.md's next step is
written or rewritten, and whenever the operator asks what to launch the next
session with: "what model should I use next", "which effort level", "close
the session", "wrap up", "update STATE", "what should the next session run
with", "is opus overkill here", "give me the startup command". Also triggers
on Norwegian phrasings: "hvilken modell neste økt", "hvilken effort", "avslutt
sesjonen", "oppdater STATE", "hva skal neste økt kjøre med", "er opus
overkill", "gi meg oppstartskommandoen", "modell og effort". Trigger even when
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.34.0"
---
# route — what the next session should run with
The operator's closing line requires two things every single session: the model
and effort for the next session, and a pasteable startup command. This skill
produces both from evidence instead of from a hunch, and leaves the scoring
behind in STATE.md so a wrong call can be found later.
**The division of labour is the whole design.** Scoring the four traits is
judgement and belongs to you. Turning scores into a model is a lookup, and
`route.sh` does it deterministically — same scores, same answer, every time,
at zero token cost. Never "reason your way" to a model. If you find yourself
weighing whether the task feels hard enough for Opus, you have skipped the
scoring step and are doing the thing this skill replaces.
## The engine
ROUTE="${CLAUDE_PLUGIN_ROOT}/scripts/route.sh"
"$ROUTE" --path <known|partial|undetermined> \
--verification <strong|weak|none> \
--reversibility <cheap|costly|one-way> \
--scope <local|multi-file|cross-cutting> \
--rationale "why these four scores" \
[--last-model <name> --last-effort <level> \
--last-completed <yes|no> --last-corrections <n>]
There is no deployed copy anywhere else and no fallback path — one entry point
is deliberate. Exit 2 means a bad or missing argument; read stderr and fix the
call rather than dropping the flag. `route.sh --help` carries the full row table
and the reasoning behind it.
The script writes nothing. It prints `row`, `rule`, `next-cost`, `command`,
`fallback`, `fallback-command`, `route-line` and (when the record is given)
`route-last`. **You** paste those into STATE.md.
## Scoring the four traits
Score the task the NEXT session will do — the one in the `👉 NESTE` block —
not the one that just finished. Read the next step as written before scoring;
if you cannot score it from what is written, that is a finding (see below).
| Trait | Score it by asking |
|---|---|
| `path` | Is the solution route already described? `known` = the steps are written down or the pattern exists in this repo. `partial` = the shape is clear, one real decision is open. `undetermined` = it has to be found first. |
| `verification` | Will a machine catch the error? `strong` = tests, types or a compiler fail on it. `weak` = a smoke test or manual run would probably surface it. `none` = prose, API shape, a security judgement — a wrong answer just sits there. |
| `reversibility` | `cheap` = a commit away. `costly` = touches published state, needs a migration or a follow-up release. `one-way` = a pushed tag, a public interface, a deletion. |
| `scope` | `local` = one file or one function. `multi-file` = several files, one repo. `cross-cutting` = many subsystems, or more than one repo. |
`verification` carries the most signal and is the trait most often skipped.
Strong verification means a cheap model's mistakes get caught and corrected —
cheap model plus tight feedback beats an expensive model without it. When
nothing verifies the output, model quality is the only defence left.
Three rules that keep the scoring honest:
- **Score the task, never the feeling.** "Hard", "complex" and "important" are
not traits here. They are unfalsifiable, and they always resolve upward.
- **`--rationale` is required and is the point.** It is where a misscore is
caught weeks later, when the recommendation turns out to have been wrong. One
sentence naming the evidence: "the pattern exists in handlers/, but the error
handling is undecided" — not "medium difficulty".
- **When torn between two scores, take the more expensive one.** Escalation is
asymmetric by design: any one trait escalates, and row 1 needs all four at
the cheap end.
## The specification check you get for free
If the next step is scored `path=undetermined` and no design phase is planned,
**the task description is underspecified — the model is not too small.** Say so,
and rewrite the next step until it can be scored. Upgrading the model to
compensate for a vague specification is the most expensive form of
procrastination available, and it hides the real defect.
This check is worth more than the tokens the routing saves. Do not skip it by
scoring `partial` to keep things moving.
## Fable rows are never this calculator's output
`route.sh` only ever emits rows 1-4. Rows 5-6 (Fable) fired from an explicit
`--opus-xhigh-failed` flag until 2026-08-06, when the operator removed that
policy; nothing replaced it as a trait-derived outcome. Choosing Fable is now
always a deliberate deviation from the rubric — CLAUDE.md is explicit that the
rubric stays the only deterministic lookup and a departure from it is recorded
in STATE as an **override**, never as something this skill produces. If Fable
is the right call for the next step's *form* (big-picture, review, planning),
write the board line and the `rule` by hand — `board.sh` still parses
"Fable 5/high" and "Fable 5/xhigh" — and say so plainly in the rationale rather
than scoring the four traits to land there.
## The last-session record
Write it whenever all four fields are actually known, from what happened in
the session that is ending — never from what STATE.md prescribed:
- `--last-model` / `--last-effort` — what this session actually ran with. Both
are closed sets (`Sonnet 5|Opus 5|Fable 5`, and the verified effort levels),
because the next session compares these values rather than just displaying
them.
- `--last-completed yes|no` — did this session finish the next step the previous
STATE.md set out? Answer about that step, not about the session in general.
- `--last-corrections <n>` — how many rounds of rework it took. This is the
cheap proxy for whether the routing was right.
**Measure the effort, never infer it.** Read what this session actually
resolved:
```bash
echo "$CLAUDE_EFFORT"
```
`CLAUDE_EFFORT` is Claude Code's own *current* effort level, exported into every
tool-use context — which is why a Bash call can read it. Pass it verbatim as
`--last-effort`. The model is not in the environment (there is no
`CLAUDE_MODEL`); take it from what this session knows itself to be running as.
Two sources are **wrong on purpose**, and both fail the same way. The previous
board line holds what was *prescribed*, not what was *run* — the two come apart
exactly when the record would be most interesting. Asking the operator launders
that same prescription through a human, who is reading it off the startup
command they typed rather than off the running process. Confirming a measured
value with them is fine; sourcing it from them is not.
So the record no longer waits on anyone: all four fields are knowable from
inside the session that is ending. Still omit it entirely — all four or none —
if any one of them is genuinely unknown. A guessed value is worse than a missing
one, because it reads back later as a measurement.
**This file must never declare an `effort:` frontmatter field.** Skill
frontmatter overrides the session effort while the skill is active, so the
reading above would report *this skill's* effort instead of the session's — a
measurement measuring itself, with nothing in the output to show it happened.
Pinned by `route-selftest.sh` section 13.
One honest limit: `CLAUDE_EFFORT` is the *current* level, so if the operator
changed it mid-session with `/effort`, "the effort this session ran with" is not
a single value. Record the level the work was actually done at and say so.
## Writing it into STATE.md
Three single-line HTML comments sit directly under the `👉 NESTE` heading, in
this order:
<!-- board: status=in-progress; blocked-on=-; next-cost=Opus 5/high -->
<!-- route: path=partial; verification=strong; ...; rationale=... -->
<!-- route-last: model=Opus 5; effort=xhigh; completed=yes; corrections=1 -->
Splice the emitted `next-cost` value into the existing board line — leave
`status` and `blocked-on` alone, they answer a different question and this skill
knows nothing about them.
**They must stay single-line comments.** `board.sh` reads the first line under
the heading that is not blank, not a heading, and does not *start* with `<!--`,
and shows it as that repo's next step across every repo. A YAML block or a
comment broken across lines therefore replaces the repo's next step on the board
with `next_task:`. Measured, not assumed — `route-selftest.sh` section 7 pins it.
## Reporting it
Give the operator the two closing-line items and nothing more:
- **Modell neste økt:** the `next-cost` value, plus the `fallback` one row
cheaper for quota pressure. Name the `rule` that fired — "path=partial" — so
the call is auditable rather than asserted.
- **Oppstartskommando:** the `command` string, in its own code block, with
`/exit` named. **Never prefix it with `cd`:** one repo per terminal tab, so
the working directory is already right. If the next step belongs in a
different repo, say so in plain words — that is a different tab, not a `cd`.
**Paste `command` verbatim.** It is the row's whole spelling: model and effort,
and nothing else. Do not add flags the calculator did not emit.
**The advisor is not this skill's to decide.** Until 2026-09-12 the rubric
appended an opus advisor on some rows; that rule is struck, on a measurement —
of 54 dispatches issued 08.12.09 not one carried it, because sessions are
started by hand from the model and effort. The advisor is now an operator
decision per session. Never add `--advisor` to an emitted command on your own
initiative; if the operator asks for one, that is their call and it belongs in
the handover as what it is — an operator choice, not a rubric outcome.
If `command` and `fallback-command` are the same as the current session's model,
say `/clear` is enough instead — but only if no newly installed plugin or skill
needs a fresh process to be picked up.
**An advisor the operator asked for is part of that comparison.** It is a
launch flag, so `/clear` reuses the process and keeps whatever advisor the
session started with — a session that is to gain or lose one needs `/exit` and
a full command, never `/clear`. The rubric never puts it there; the operator
does.
Do not paste the whole output block. One row, the rule that produced it, the
command.