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>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-12 23:45:47 +02:00
commit 5e5bc4a66e
13 changed files with 689 additions and 271 deletions

View file

@ -80,8 +80,9 @@
# the exact command to start a session there. The 38-char cut is the table
# column's property, not the record's, so the briefing prints NESTE uncut. Each
# command is derived by CALLING route.sh with that repo's own four traits -
# next-cost alone cannot yield it, since the advisor flag is a property of the
# ROW. A repo with no route line is told so rather than handed a guess.
# next-cost alone cannot yield it, since it names the row rather than spelling
# the command, and there must be exactly one spelling in circulation. A repo
# with no route line is told so rather than handed a guess.
#
# --brief is still read-only: it writes nothing. The file write lives in
# brief-nightly.sh, which renders to a temp file and renames it into place, and
@ -200,14 +201,24 @@
# --path P --verification V --reversibility R --scope S
# --rationale "why"
#
# Model, effort and --advisor come from route.sh's row table, never from a
# --model/--effort pair: the advisor flag is a property of the ROW, and a
# dispatch that took the model directly would have no honest source for it.
# Model and effort come from route.sh's row table, never from a --model/--effort
# pair: the rubric has exactly one copy, and a dispatch that took the model
# directly would be a second, unscored way to reach the same decision.
# --target-pane is a MEASUREMENT (morning --probe-panes) and has no default -
# see the dispatch() comment for what happens when it is guessed.
#
# --row <repo> is a SEVENTH rendering: the table's row for ONE repo, as
# `field=value` lines a program can grep instead of columns a human counts
# spaces in. It exists because the columns WERE misread - FLY read as ORDRE on
# 2026-09-11 - and because every other rendering a program consumes is already
# key=value. It takes no position: every field is a value the scan already
# holds, under the name its column carries, with `neste` last and uncut.
# One exception, stated rather than blended in: `upushet` is not a column, and
# is measured once for the named repo only. See row_one() for what it can and
# cannot claim.
#
# Usage: board.sh [--roots <dir>[,<dir>...]] [--plain]
# [--brief|--plan|--inbox-plan|--dispatch|--voyage ...]
# [--brief|--plan|--inbox-plan|--dispatch|--voyage|--row <repo>]
# [--focus "<prose>"]
# Env: CLAUDE_COORD_DIR overrides the mailbox root.
# BOARD_ROOTS overrides the default scan roots.
@ -224,6 +235,7 @@ INBOX_PLAN=0
FOCUS=""
DISPATCH=0
VOYAGE=0
ROW=0; ROW_REPO=""
D_REPO=""; D_PROMPT=""; D_PANE=""; D_ORDER=""
D_PATH=""; D_VER=""; D_REV=""; D_SCOPE=""; D_RAT=""
@ -238,10 +250,16 @@ while [ $# -gt 0 ]; do
# bash 3.2: `shift 2` past the end of $# is a no-op -> would loop forever.
--roots) [ $# -ge 2 ] || { echo "board: --roots requires a value" >&2; exit 2; }
ROOTS="$2"; shift 2 ;;
# Four renderings of one scan, so exactly one may be selected: last wins.
--brief) BRIEF=1; PLAN=0; INBOX_PLAN=0; VOYAGE=0; shift ;;
--plan) PLAN=1; BRIEF=0; INBOX_PLAN=0; VOYAGE=0; shift ;;
--inbox-plan) INBOX_PLAN=1; BRIEF=0; PLAN=0; VOYAGE=0; shift ;;
# Renderings of one scan, so exactly one may be selected: last wins.
--brief) BRIEF=1; PLAN=0; INBOX_PLAN=0; VOYAGE=0; ROW=0; shift ;;
--plan) PLAN=1; BRIEF=0; INBOX_PLAN=0; VOYAGE=0; ROW=0; shift ;;
--inbox-plan) INBOX_PLAN=1; BRIEF=0; PLAN=0; VOYAGE=0; ROW=0; shift ;;
# One repo, one field=value line per column. Same `shift 2` guard as every
# other value flag, for the same bash 3.2 reason - and a MISSING value is a
# usage error rather than an empty row, since an empty row reads exactly
# like a repo whose every column is blank.
--row) [ $# -ge 2 ] || { echo "board: --row requires a repo name" >&2; exit 2; }
ROW=1; ROW_REPO="$2"; BRIEF=0; PLAN=0; INBOX_PLAN=0; VOYAGE=0; DISPATCH=0; shift 2 ;;
# Raw operator prose, forwarded verbatim by the driver: it does not
# tokenize, match or normalize, so every bit of that work is here. Same
# `shift 2` guard as --roots, for the same bash 3.2 reason.
@ -249,8 +267,8 @@ while [ $# -gt 0 ]; do
FOCUS="$2"; shift 2 ;;
# Dispatch rendering: one repo, one prompt file, one startup command. Same
# `shift 2` guard as every other value flag, for the same bash 3.2 reason.
--dispatch) DISPATCH=1; BRIEF=0; PLAN=0; INBOX_PLAN=0; VOYAGE=0; shift ;;
--voyage) VOYAGE=1; BRIEF=0; PLAN=0; INBOX_PLAN=0; DISPATCH=0; shift ;;
--dispatch) DISPATCH=1; BRIEF=0; PLAN=0; INBOX_PLAN=0; VOYAGE=0; ROW=0; shift ;;
--voyage) VOYAGE=1; BRIEF=0; PLAN=0; INBOX_PLAN=0; DISPATCH=0; ROW=0; shift ;;
--repo) [ $# -ge 2 ] || { echo "board: --repo requires a value" >&2; exit 2; }
D_REPO="$2"; shift 2 ;;
--prompt-file) [ $# -ge 2 ] || { echo "board: --prompt-file requires a value" >&2; exit 2; }
@ -1079,9 +1097,9 @@ rows() {
# --- Briefing rendering (--brief) ------------------------------------------
# The startup command for one repo, derived from that repo's OWN route line by
# calling route.sh. Deriving it from next-cost instead would not work even in
# principle: the advisor flag is a property of the ROW, and two rows can share
# a model/effort pair while differing on it. A repo with no route line gets its
# calling route.sh. Deriving it from next-cost instead would mean a second
# writer for the same command string, spelled from a rubric NAME rather than
# from the row table itself. A repo with no route line gets its
# next-cost printed and is told where the command comes from - a guessed
# command would read as authoritative while being a guess, which is worse than
# no command at all.
@ -1303,13 +1321,18 @@ fi
# brief-nightly.sh already owns the briefing's write.
#
# THE COST COMES FROM route.sh's ROW TABLE, never from a --model/--effort pair
# handed in. That is not tidiness: `--advisor opus` is a property of the ROW,
# two rows share a model/effort pair while differing on it, and the CLI accepts
# a wrong advisor silently (it drops it for a Fable main model without failing).
# A dispatch that took the model directly would have no source for that flag,
# and both available guesses are wrong in the same direction - a session that
# looks peer-reviewed without being. Scoring the four traits is the caller's
# judgement; turning them into a command is this lookup.
# handed in. That is not tidiness: the rubric is a policy with exactly one copy,
# and a dispatch accepting the model directly would be a second way to reach the
# same decision - one that records no traits, no rationale and no next-cost, so
# nothing afterwards can say whether the ROUTING was wrong or the SCORING was.
# Scoring the four traits is the caller's judgement; turning them into a command
# is this lookup.
#
# Until 2026-09-12 this paragraph rested on `--advisor opus` instead: the flag
# was a property of the ROW, so a model/effort pair could not yield it. That
# rule is struck (see route.sh), and the argument is rewritten rather than
# quietly left standing on a premise the code no longer has - a comment
# defending a mechanism that was removed is how the next session "restores" it.
dispatch() {
# --target-pane is REQUIRED and has no default, the same rule route.sh's
# --last-effort carries: it is a MEASUREMENT of the world (does the target
@ -1387,8 +1410,8 @@ dispatch() {
[ -d "$d_dir" ] || { echo "board: the directory for $D_REPO does not exist: $d_dir" >&2; exit 2; }
# Rejected traits REFUSE. Degrading to a command without them is the worst
# available outcome: a command missing --advisor is indistinguishable from a
# row that legitimately has none.
# available outcome: a command built on three scored traits and one silently
# dropped one is indistinguishable from a fully scored decision.
d_route="$(bash "$ROUTE" --path "$D_PATH" --verification "$D_VER" \
--reversibility "$D_REV" --scope "$D_SCOPE" --rationale "$D_RAT" 2>&1)" || {
echo "board: route.sh rejected the traits, so there is no command to give:" >&2
@ -2097,6 +2120,114 @@ if [ "$VOYAGE" -eq 1 ]; then
exit 0
fi
# --- Row rendering (--row <repo>) -------------------------------------------
# A SEVENTH rendering of the same scan, never a second scan, and read-only like
# every other one. (Order 20260912T202210Z-7588027378 calls it the sixth; by
# this file's own numbering --inbox-plan is the fourth and --dispatch the fifth,
# so it is the seventh. The count is corrected here rather than carried wrong,
# for the same reason every other number in this file is measured.)
#
# WHAT IT ANSWERS: the table's row for ONE repo, as `field=value` lines a
# program can grep. The table is fixed-width and is for a human; reading a
# column off it by eye is how FLY was misread as ORDRE on 2026-09-11, and a
# 34-character repo key already shifts a row two characters right - a stated,
# unclosed gap that makes counting spaces strictly worse than it looks. Every
# other rendering a program consumes (--plan, --inbox-plan, --dispatch) is
# key=value for exactly this reason; the default view was the one left out.
#
# It takes NO position and adds NO judgement: every field is a value $RECORDS
# already holds, printed under the name its column carries. The three mailbox
# counts are three separate fields because they are three separate facts - INN
# is "others wait on you", ORDRE is "work waits on this repo", FLY is "someone
# took an order" - and this rendering exists because they were confused.
#
# ONE FIELD IS NOT A RENDERING, and it is named rather than blended in:
# `upushet` is not a column the scan computes. It is measured here, once, for
# the named repo only, because the order asks for it and because "nothing is
# waiting to be pushed" and "nobody looked" are exactly the pair this repo
# refuses to render alike. One reading for one repo in a one-repo view is not a
# scan; it never enters the table, the plan or the briefing.
#
# It reads the remote-TRACKING ref, not the remote. That is a real bound, not a
# hedge: a stale ref under-reports after someone else pushed, and over-reports
# after a push this checkout has not fetched. The honest reading of `upushet=N`
# is "the local ref says N", and `git ls-remote` is still what a claim ABOUT
# THE WORLD costs.
row_unpushed() {
ru_up="$(git -C "$1" rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null)" || return 1
[ -n "$ru_up" ] || return 1
ru_n="$(git -C "$1" rev-list --count "$ru_up..HEAD" 2>/dev/null)" || return 1
case "$ru_n" in ''|*[!0-9]*) return 1 ;; esac
printf '%s' "$ru_n"
}
row_one() {
r_rec="$(awk -F'|' -v n="$ROW_REPO" '$3==n {print; exit}' "$RECORDS")"
# REFUSE rather than emit an empty block. A consumer reading ^status= off an
# empty output gets nothing back and reads it as a repo with no board line -
# which is a real, different state this very rendering spells as `?`.
if [ -z "$r_rec" ]; then
echo "board: no repo named '$ROW_REPO' in the scanned roots" >&2
return 2
fi
r_disp="$(printf '%s' "$r_rec" | cut -d'|' -f4)"
r_cost="$(printf '%s' "$r_rec" | cut -d'|' -f5)"
r_inn="$(printf '%s' "$r_rec" | cut -d'|' -f6)"
r_drt="$(printf '%s' "$r_rec" | cut -d'|' -f7)"
r_alder="$(printf '%s' "$r_rec" | cut -d'|' -f8)"
r_siste="$(printf '%s' "$r_rec" | cut -d'|' -f9)"
r_dir="$(printf '%s' "$r_rec" | cut -d'|' -f10)"
r_blocked="$(printf '%s' "$r_rec" | cut -d'|' -f11)"
r_ordre="$(printf '%s' "$r_rec" | cut -d'|' -f12)"
r_ordreage="$(printf '%s' "$r_rec" | cut -d'|' -f13)"
r_fly="$(printf '%s' "$r_rec" | cut -d'|' -f14)"
r_flyage="$(printf '%s' "$r_rec" | cut -d'|' -f15)"
r_voy="$(printf '%s' "$r_rec" | cut -d'|' -f16)"
# `neste` is free prose and may contain a literal '|', which is why it is the
# last field of the record - so it is taken as "everything from 17 on", never
# as field 17.
r_neste="$(printf '%s' "$r_rec" | cut -d'|' -f17-)"
# The TOKEN, not the display string: the table renders a blocked repo as
# `blocked>target` because one column has to carry both, and this rendering
# has two fields and no such constraint. A consumer matching status=blocked
# must not have to know about the arrow.
case "$r_disp" in
blocked\>*) r_status=blocked ;;
*) r_status="$r_disp" ;;
esac
r_unpushed="$(row_unpushed "$r_dir")" || r_unpushed="?"
echo "repo=$ROW_REPO"
echo "dir=$r_dir"
echo "status=$r_status"
echo "blocked-on=$r_blocked"
echo "next-cost=$r_cost"
echo "inn=$r_inn"
echo "ordre=$r_ordre"
echo "ordre-alder=$r_ordreage"
echo "fly=$r_fly"
echo "fly-alder=$r_flyage"
echo "voy=$r_voy"
echo "drt=$r_drt"
echo "alder=$r_alder"
echo "siste=$r_siste"
echo "upushet=$r_unpushed"
# LAST, and for the same reason it is last in the record: it is free prose
# lifted out of a STATE.md, so anything placed after it would be unreachable
# for a consumer reading from the end. Printed WHOLE - the 38-character cut
# belongs to the table column, and cutting here would make the cut string the
# only copy, which is the defect --brief was built to stop.
echo "neste=$r_neste"
return 0
}
if [ "$ROW" -eq 1 ]; then
row_one
exit $?
fi
count() { awk -F'|' -v b="$1" '$1==b' "$RECORDS" | wc -l | tr -d ' '; }
echo "BOARD - tverr-repo oppmerksomhetstavle ($(awk 'END{print NR}' "$RECORDS") repo)"