The board line's next-cost field had a reader (board.sh) and no writer, so
its value was retyped by hand every session and drifted into several
competing spellings. Cleaning the data could not fix that: the cause was the
missing write path.
route.sh is that writer. Four scored traits of the next task -- path,
verification, reversibility, scope -- plus a required rationale, run through
the operator's model rubric moved here as the single copy. The row table is a
closed set of six values, so a seventh spelling cannot enter circulation, and
route-selftest.sh section 6 runs the round trip (route emits -> board parses)
inside one repo rather than across two.
Two spellings of one decision come out of one table: the rubric name for the
board line, the CLI alias for the command the operator pastes. Effort levels
are the set pinned in this marketplace; model aliases are gated against the
installed claude rather than hardcoded.
Three things worth naming:
- Escalation is asymmetric. Any single trait escalates; the cheapest row needs
all four at the cheap end. Underkill costs one session, overkill costs quota
every session -- but a wrong architecture call costs more than either.
- The Fable rows fire only from an explicit judgement flag, never inferred
from the last-session record. "The session did not finish" also covers
context exhaustion and operator interrupts, which say nothing about the
model, and Fable runs without an advisor.
- The trait block is a single-line HTML comment because board.sh's NESTE
extractor skips only lines that START with '<!--'. Measured first: a YAML
block or a multi-line comment silently replaces the repo's next step on the
board with "next_task:". Pinned by section 7.
board.sh is untouched as a program; its header now points at route.sh for the
value set so this does not reopen the two-specs defect 305f168 closed.
Selftests: coord 136, board 30, route 47 (new), node 7.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017peNgsxVt1BR4BTuMwiPoX
175 lines
8.6 KiB
Markdown
175 lines
8.6 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
|
|
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". 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.9.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
|
|
|
|
Resolve the script path portably — this expression is correct both when the skill
|
|
runs bundled inside the plugin and when the scripts are installed as personal
|
|
scripts under `~/.claude/scripts/`:
|
|
|
|
BOARD="${CLAUDE_PLUGIN_ROOT:-$HOME/.claude}/scripts/board.sh"
|
|
|
|
"$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 three seconds.
|
|
|
|
## 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. |
|
|
| `DRT` | Uncommitted files. |
|
|
| `ALDER` | Days since STATE.md last changed. |
|
|
| `NESTE` | First line of the STATE.md next-step block, truncated. |
|
|
|
|
**`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.
|
|
|
|
**`ALDER` is never a ranking input.** An old STATE.md often means a finished repo,
|
|
not a neglected one. Report it only if the user asks about staleness directly.
|
|
|
|
`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.
|
|
|
|
## 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.
|