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>
210 lines
11 KiB
Markdown
210 lines
11 KiB
Markdown
---
|
||
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.
|