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:
parent
671e275a97
commit
5e5bc4a66e
13 changed files with 689 additions and 271 deletions
179
scripts/board.sh
179
scripts/board.sh
|
|
@ -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)"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue