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>
346 lines
19 KiB
Markdown
346 lines
19 KiB
Markdown
---
|
|
name: board
|
|
description: >-
|
|
Answer "which repository deserves the next session" across every repo at once,
|
|
using the local cross-repo attention board (`board.sh`) as the evidence. Use
|
|
whenever the user asks what to work on next, who is waiting on them, what
|
|
unblocks the most other work, what is cheapest to move under quota pressure, or
|
|
for a cross-repo status read: "what should I work on", "who is waiting on me",
|
|
"what unblocks the most", "show the board", "what is cheapest to move",
|
|
"cross-repo status", "where is the leverage", "which repo is blocked". Also
|
|
covers planning a whole day rather than picking one repo: "plan my day", "which
|
|
repos should I open tabs for", "day plan" — including a day with a subject:
|
|
"focus day on X", "today I am only working on X", "which repos does X touch".
|
|
Also triggers on Norwegian phrasings:
|
|
"hva skal jeg jobbe med", "hvem venter på meg", "hva løsner mest", "vis tavlen",
|
|
"hva er billigst å flytte", "hvor bør jeg begynne", "status på tvers av repo",
|
|
"hva er blokkert", "lag en dagsplan", "planlegg dagen", "hvilke repo skal jeg
|
|
åpne i dag", "fokusdag på X", "i dag jobber jeg bare med X", "hvilke repo
|
|
gjelder X". Also covers Voyage briefs in flight across repos: "which briefs are
|
|
running", "what phase is that brief in", "which brief is blocked on a decision",
|
|
"show the voyage board", "hvilke briefer er i gang", "hvilken fase ligger den i",
|
|
"hvilke briefer venter paa en beslutning", "vis Voyage-oversikten". Trigger even when the
|
|
user names no repo and no tool — choosing *between* repos is this skill. Not for
|
|
"where were we" inside the current repo: that is this repo's own STATE.md,
|
|
already injected at session start.
|
|
version: "0.34.0"
|
|
---
|
|
|
|
# board — which repo deserves the next session
|
|
|
|
This skill turns "what should I work on" into a decision backed by measurement.
|
|
`board.sh` is the engine; this skill is the ranking and the answer. The script is
|
|
read-only by construction — it never writes to a repo, a STATE.md, or the mailbox
|
|
— so running it is always safe and needs no confirmation.
|
|
|
|
The failure mode this skill exists to prevent is dumping the table. The board
|
|
prints one line per repo across every repo on the machine; pasting that back is
|
|
not an answer, it is the question again in table form. Run it, read it, name one
|
|
repo.
|
|
|
|
## The engine
|
|
|
|
BOARD="${CLAUDE_PLUGIN_ROOT}/scripts/board.sh"
|
|
|
|
There is no deployed copy anywhere else and no fallback path. One entry point is
|
|
the whole point: two entries pointed at two copies and made it impossible to know
|
|
which one actually ran. If this fails to resolve, the fix is this expression —
|
|
never restoring a copy under `~/.claude/scripts/`.
|
|
|
|
"$BOARD" # every repo under the default roots
|
|
"$BOARD" --roots <dir>[,<dir>...] # scan somewhere else
|
|
|
|
`BOARD_ROOTS` overrides the default scan roots and `CLAUDE_COORD_DIR` the mailbox
|
|
root. Exit 0 is normal (0 discovered repos also exits 0, silently); exit 2 means a
|
|
malformed argument — read stderr and fix it rather than retrying.
|
|
|
|
**Re-run it every single invocation.** Never answer from a board you ran earlier
|
|
in the session, from a number quoted in a document, or from memory. Inbox counts,
|
|
uncommitted files and board lines all change between turns, and a recommendation
|
|
built on a stale count is the exact defect the operator's premise-verification
|
|
rule exists to stop. The run costs about eight seconds over ~50 repos (measured 2026-08-31; the
|
|
older "about three seconds" figure predates both the current tree size and
|
|
the `VOY` column).
|
|
|
|
## What the columns mean
|
|
|
|
| Column | Meaning |
|
|
|---|---|
|
|
| `STATUS` | `planned` / `in-progress` / `blocked` / `deferred` / `done`, or `blocked>X` naming the repo it waits on. `?` means the STATE.md has no board line. |
|
|
| `KOST` | Model/effort for the next step, from the rubric row table in `route.sh` (the `route` skill writes it; this one only reads it). |
|
|
| `INN` | Unhandled inbox: **other repos are waiting on THIS one**. An obligation it owes outward. |
|
|
| `ORDRE` | Pending orders: **authorized work is waiting on this repo**, unclaimed and pickable. |
|
|
| `FLY` | Orders in flight (claimed). Someone TOOK the order — never proof a session is still alive. |
|
|
| `VOY` | Voyage projects (briefs in flight). `N:Md` = how many, and how long since the **stalest** one's newest artifact. A bare `0` means none. Per-project detail lives in `--voyage`. |
|
|
| `DRT` | Uncommitted files. |
|
|
| `ALDER` | Days since STATE.md last changed — the age of the *plan*. `-` where the repo has none. |
|
|
| `SISTE` | Days since the last commit — the age of the *work*. `-` where the repo has no commits yet. |
|
|
| `NESTE` | First line of the STATE.md next-step block, truncated. |
|
|
|
|
## Briefs in flight — `--voyage`
|
|
|
|
"$BOARD" --voyage
|
|
|
|
Answers what no single `STATE.md` can: **which Voyage briefs are running, in what
|
|
phase, and who is waiting on whom.** One `key=value` block per project.
|
|
|
|
`fase` is the only field here that cannot be derived from a STATE.md at all, and
|
|
it is why the view exists: `brief-draft` (the `/trekbrief` review gate has not
|
|
cleared) → `brief` → `research` → `plan` → `execute` → `review`.
|
|
|
|
Read these three the way the engine means them, and never soften them:
|
|
|
|
- **`fase` measures ARTIFACTS, not sessions.** A plan executed in one session
|
|
leaves no file, so `plan` is the last thing the filesystem can prove. Nothing
|
|
here says a session is alive — the same refusal `FLY` carries.
|
|
- **`kvalitet=-` means the `brief_quality` field is ABSENT, never that the brief
|
|
is complete.** Only 8 of ~40 briefs on the real tree carry it. `partial` vs
|
|
`complete` is exactly what three presence-greps cannot tell apart.
|
|
- **`research=-` and `research=0` are different facts.** `-` is no research
|
|
directory (never started); `0` is a directory that exists and holds nothing —
|
|
a research step with null output.
|
|
|
|
`venter=operatoerbeslutning` means the brief declares an open
|
|
`[BLOCKING DECISION, before S<n>]`, and `blokkerende_gate` names the step it
|
|
gates. That is the one form of "who waits on whom" the files can prove; the
|
|
`ordre_id=` lines beside it carry the repo's pending orders, which is what lets a
|
|
reader go from "this brief is standing still" to "this order is pending".
|
|
|
|
Report what the blocks say. Do **not** infer that a brief is abandoned, that a
|
|
decision has since been resolved, or that a session is running — the view
|
|
reports and refuses the inference, and so should you.
|
|
|
|
**`ORDRE` and `FLY` are never summed, and `FLY` is never read as "busy."** They
|
|
are the same queue in two states. Before `FLY` existed, a repo with one order in
|
|
flight and a repo with no orders at all both printed `ORDRE 0` — the same digit
|
|
for two opposite facts, which is how two tabs sat idle for 45 hours holding
|
|
finished orders with nothing on the board reporting it. What `FLY` still cannot
|
|
tell you is whether a session is *running*: nothing un-claims an order when the
|
|
session that claimed it dies (one order on the live mailbox had been claimed for
|
|
117 hours). Say "an order is claimed here", never "a session is working here".
|
|
The board inspects no processes and will not start.
|
|
|
|
**`INN` never means "this repo is waiting on someone."** It means the opposite:
|
|
messages arrived and were not handled. The mailbox format carries no reply-to or
|
|
thread field, so outbound waiting is not derivable from it at all — `blocked>X` is
|
|
the only source for who a repo waits *on*. Conflating the two axes is a known
|
|
defect class here; keep them apart in every sentence you write.
|
|
|
|
## How to rank
|
|
|
|
Apply in order and stop at the first that discriminates. Always say which rule
|
|
fired — the rule is the justification, and it is what lets the operator disagree.
|
|
|
|
1. **Unblocks the most.** A repo that others are `blocked>` on. Moving it converts
|
|
several blocked repos to active: the highest leverage per token spent. Follow
|
|
the chain — if A is blocked on B and B is blocked on C, the answer is C.
|
|
2. **Cheap and blocking.** Among unblockers, prefer the lowest `KOST`. This is the
|
|
standing rule under quota pressure: take the cheap blocking one first.
|
|
3. **Owes the most outward.** High `INN` means other sessions are stalled waiting
|
|
for a reply. Answering is procedurally mandatory anyway, so this work is owed
|
|
regardless of what else is on the board.
|
|
4. **Uncommitted risk.** High `DRT` is not urgent work, but it is exposure worth
|
|
naming: the config backup mirrors commits, not working trees, so uncommitted
|
|
files exist in exactly one place.
|
|
|
|
**Neither `ALDER` nor `SISTE` is a ranking input.** An old STATE.md often means a
|
|
finished repo, not a neglected one, and an old last commit can mean the same.
|
|
Report either only if the user asks about staleness directly — the four rules
|
|
above stay four.
|
|
|
|
Read them as a **pair**, though: they answer different questions, and the gap
|
|
between them is the reading neither gives alone. A fresh `ALDER` beside a long
|
|
`SISTE` is a repo that keeps being re-planned without anything landing, which no
|
|
other column on the board reports.
|
|
|
|
`deferred` is a deliberate choice, not neglect — do not surface a deferred repo as
|
|
a candidate unless nothing else qualifies, and say that it was deferred if you do.
|
|
|
|
## Read the winner's STATE.md before answering
|
|
|
|
The `NESTE` column is truncated to fit the table, so it is a pointer, not the
|
|
action. Once ranking picks a repo, open that repo's `STATE.md` and read the actual
|
|
next-step block. Report the real first action from that block. A truncated
|
|
fragment quoted as if it were the next step is a wrong answer that looks right.
|
|
|
|
## The answer
|
|
|
|
Short prose, roughly five to ten lines. No table, no per-repo rundown, no top-five
|
|
list — "top five with their columns" is still a dump.
|
|
|
|
- **The repo**, named once.
|
|
- **Why it won**, as the rule that fired.
|
|
- **The concrete first action**, from that repo's STATE.md next-step block.
|
|
- **What it costs** — the `KOST` value, so the operator can set model and effort
|
|
before starting.
|
|
- **Runner-up in one clause**, only if it is genuinely close.
|
|
|
|
If the user asked a narrower question ("who is waiting on me", "what is cheapest"),
|
|
answer that question directly from the same run instead of forcing the full
|
|
ranking onto them.
|
|
|
|
## When the ask is a day, not a repo
|
|
|
|
"Hvilke repo skal jeg åpne tabber for i dag", "lag en dagsplan", "planlegg dagen",
|
|
"hva står på programmet" — that is a different question from "which repo wins",
|
|
and it has its own rendering:
|
|
|
|
"$BOARD" --plan
|
|
|
|
This is the one case where a list *is* the answer and the no-dumping rule does not
|
|
apply — the user asked for the day, and a day has more than one repo in it. Pass
|
|
the plan through with a short framing line; do not re-rank it, re-order it, or trim
|
|
it. The order is the engine's position and it is deterministic — five ordered
|
|
groups, each a lookup, no weights:
|
|
|
|
1. chain-root credit (repos released transitively, most first)
|
|
2. unhandled inbox (most owed first, whatever the status)
|
|
3. planned
|
|
4. in-progress
|
|
5. no declared status (last, and labelled)
|
|
|
|
Within a group: that group's own quantity, then a Sonnet `next-cost`, then oldest
|
|
plan first. `planned` sits above `in-progress` by operator decision at 0.20.0 —
|
|
turning a decision into motion is the slow step; live work is already moving.
|
|
|
|
**Chain-root credit is the term worth understanding before you explain an order
|
|
to the operator.** For every `blocked` repo the engine follows `blocked-on` to
|
|
the ROOT of the chain and credits only the root, transitively. Opening a blocked
|
|
repo releases nobody — its next step is by definition waiting; opening the root
|
|
releases everything behind it. A cycle or a `blocked-on` naming an unscanned repo
|
|
credits nobody. This is engine rule 1 above, now computed rather than eyeballed.
|
|
|
|
Debt is **uncapped and never excluded** by decision: owing a reply is the other
|
|
axis from a repo's own next step, and answering is often what unblocks a chain.
|
|
Sitting one group below chain-root credit is not a cap — a debtor keeps its tab,
|
|
its most-owed-first position among the other debtors, and its `why=inbox:N`.
|
|
|
|
`why=` names the **group** that put the repo in the plan, so a block can read
|
|
`why=unblocks:2` even though the repo also owes mail. Read it as "what opening
|
|
this releases", not as the only reason it qualified. Substituting your own judgement for the order makes
|
|
the plan unreproducible and costs the property that makes it trustworthy.
|
|
|
|
Two things to say out loud when you hand it over:
|
|
|
|
- **A `done` or `blocked` repo in the plan is not a bug.** It is there because it
|
|
owes mail, and owing an answer is a different axis from whether its own next step
|
|
can move. `why=inbox:N` says exactly that.
|
|
- **`command_missing=` means that repo has no usable route line**, so there is no
|
|
command to paste. Name those repos rather than letting the operator discover it
|
|
per tab. Fixing them is the `route` skill's job, in *that* repo — never a side
|
|
quest here.
|
|
- **`ledig_antall=N` and the `ledig=` lines are free capacity, not tabs.** They
|
|
name the repos that can take NEW work: nothing owed, nothing queued, nothing in
|
|
flight, clean tree, at `done` or `deferred`. They carry no `tab=` and no
|
|
command on purpose — there is no next step to start, so the operator decides
|
|
what to send there. Read them out when the ask is about capacity ("hvem kan ta
|
|
mer arbeid", "hvor har jeg ledig kapasitet") and whenever the plan is short.
|
|
`status=done` alone is **not** the same set: on the real tree 4 of 17
|
|
done/deferred repos were not free. Never derive this list yourself from the
|
|
table — the engine joins four fields you would have to join by hand.
|
|
- **`fly=N` on a tab block means that repo already holds a claimed order.** Say
|
|
so before the operator opens the pane. It is not proof a session is live, and
|
|
it is not a reason to drop the tab — it is a reason to look first.
|
|
|
|
### When the day has a subject ("fokusdag")
|
|
|
|
"I dag jobber jeg bare med X", "fokusdag på X", "hvilke repo gjelder X" — pass the
|
|
user's own words through, untouched:
|
|
|
|
"$BOARD" --plan --focus "<the user's phrasing, verbatim>"
|
|
|
|
**Do not tokenize, normalize or translate the phrase into a slug yourself.** The
|
|
engine resolves prose against the topic markers the scanned `STATE.md` files
|
|
actually declare, and it reports what it resolved to. Guessing a slug here would
|
|
put a judgement call in front of a lookup that is already deterministic — and
|
|
`fokus=` in the output tells the user what the phrase resolved to, which your
|
|
guess would silently replace.
|
|
|
|
This is the only rendering that *hides* repos, so three lines carry weight and
|
|
must be relayed, never trimmed as noise:
|
|
|
|
- **`fokus_utenfor=`** — repos that *mention* the subject with no marker line.
|
|
They are named, not counted, because that is where the misses live: a repo can
|
|
be a heavy participant and still never have written a marker. Say "nevner",
|
|
never "dekker" — this reports text found in a file, not relevance.
|
|
- **`fokus_droppet=`** — how many blocks the cutoff removed.
|
|
- **`fokus_rekkevidde=`** — how many `STATE.md` were searched. The scan opens no
|
|
other file, so a repo whose evidence lives in a `README` is invisible to it.
|
|
|
|
If `fokus_ikke_brukt=` appears, the phrase matched no declared subject and the
|
|
**full** plan came back. Say so plainly — the user asked for a narrow day and got
|
|
a wide one, and presenting it as focused would be the one failure this whole
|
|
rendering is built to avoid.
|
|
|
|
Blocks are `key=value` and comment lines start with `#`, because a separate driver
|
|
repo consumes this output. That format is a contract with a consumer outside this
|
|
repo: never reformat it, and never hand-edit a block before passing it on.
|
|
|
|
**Point the operator at `paste=`, not at `dir=` + `command=`.** Those two exist
|
|
for a driver, which cd's the pane itself and then types the command; `paste=` is
|
|
the same thing already joined, and it is the only line a human should ever copy.
|
|
Telling someone to "cd to `dir=` and then paste `command=`" asks them to assemble
|
|
two fields by hand, which is exactly how a session gets started in the wrong repo.
|
|
A block with `command_missing=` has no `paste=` at all — say so for those repos
|
|
rather than letting the operator hunt for a line that is not there.
|
|
|
|
## Coverage gaps you must name
|
|
|
|
A ranking is only as honest as its inputs, and two gaps are invisible in the answer
|
|
unless you state them:
|
|
|
|
- **Repos with no board line** render `?` in status and cost, so they drop out of
|
|
exactly the sort the board exists for. The script counts them in its closing
|
|
note. Pass that count through as one clause ("N repos have no board line and are
|
|
outside this ranking"), and never let it grow into a side quest — adding board
|
|
lines to other repos is that repo's work, not this session's.
|
|
- **`MALFORMED` status tokens** mean a board line outside the closed vocabulary.
|
|
The script warns; repeat the warning and name the repo, because a malformed line
|
|
silently misplaces a repo in the wrong bucket.
|
|
|
|
Repos with no STATE.md at all appear in their own group. They are not candidates —
|
|
there is no next step to read — but a large uncommitted count in one is worth a
|
|
sentence under rule 4.
|
|
|
|
## Boundaries
|
|
|
|
- **Read-only, both ways.** The script writes nothing, and neither should the
|
|
skill. Do not fix a missing board line, edit another repo's STATE.md, or send a
|
|
coord message as a side effect of ranking. Recommend; the operator decides.
|
|
- **Not a session-start hook.** Injecting the board into every session costs real
|
|
context about repos the session is not in. It runs when asked, never on a timer.
|
|
- **Coordination metadata is private.** Repo names, blocking chains and inbox
|
|
counts are local operational data and never belong on a public surface.
|
|
|
|
## Examples
|
|
|
|
Placeholder names throughout — resolve real ones from the run, never from a
|
|
document.
|
|
|
|
**Example 1 — the ordinary ask**
|
|
Input: "hva skal jeg jobbe med nå?"
|
|
Action: run the board. `repo-a` is `blocked>repo-b`, and `repo-b` is active at a
|
|
low cost. Answer: `repo-b`, rule 1 + 2 (cheap and unblocks `repo-a`), the first
|
|
action read from `repo-b/STATE.md`, and its `KOST`.
|
|
|
|
**Example 2 — quota pressure**
|
|
Input: "jeg har lite kvote igjen, hva er billigst å flytte?"
|
|
Action: run the board, restrict to candidates whose `KOST` is at the low end of the
|
|
rubric, and prefer one that also unblocks something. Name the cost explicitly.
|
|
|
|
**Example 3 — the obligation question**
|
|
Input: "hvem venter på meg?"
|
|
Action: read `INN` only. List the repos with unhandled mail and say that this is
|
|
mail owed outward, not repos this one is waiting on. Do not rank; the question was
|
|
narrower than the ranking.
|
|
|
|
**Example 4 — chain following**
|
|
Input: "what unblocks the most?"
|
|
Action: build the `blocked>` chain from the run, and name the repo at its root,
|
|
plus how many repos come free when it moves.
|
|
|
|
**Example 5 — nothing qualifies**
|
|
Input: "vis tavlen"
|
|
Action: if every repo is `done` or `deferred`, say so plainly and name the largest
|
|
uncommitted exposure instead of inventing a candidate. An empty board is a real
|
|
answer.
|