--plan shipped with dir= and command= as separate fields. That is right for a driver, which moves the pane itself and then types the command, and unusable for a person: the first operator to read a block could not act on it at all. Joining an absolute path from one field with a command from another is not a saved line of output. It is the step where a session gets started in the wrong repository - the same failure the absolute dir= exists to prevent, moved from the machine to the human. paste= is the two already joined, cd <dir> && <command>, one thing to select. Both forms stay: the two consumers want the same fact shaped differently, and neither shape serves both. Emitted only alongside command=, never once per block. A `paste=cd X && ` with nothing after it would run the cd and then a bare newline, which fails SILENTLY - the operator ends up in the right directory with no session started and no error to notice. board-selftest: 86 -> 89 checks. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011eKRzhD1RqY2MxvDEkrJAM
12 KiB
| name | description | version |
|---|---|---|
| board | 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". 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". 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. | 0.17.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 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 — 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. |
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.
- 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. - Cheap and blocking. Among unblockers, prefer the lowest
KOST. This is the standing rule under quota pressure: take the cheap blocking one first. - Owes the most outward. High
INNmeans 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. - Uncommitted risk. High
DRTis 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
KOSTvalue, 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: debt first
(most-owed first, whatever the status), then in-progress, then planned, then
repos with no board line. Substituting your own judgement for that 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
doneorblockedrepo 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:Nsays 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 therouteskill's job, in that repo — never a side quest here.
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. MALFORMEDstatus 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.