--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
225 lines
12 KiB
Markdown
225 lines
12 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". 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.
|
|
version: "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.
|
|
|
|
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: 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 `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.
|
|
|
|
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.
|