Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bp7V99BXv1v5pWHUwY63it
283 lines
15 KiB
Markdown
283 lines
15 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". 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.20.2"
|
|
---
|
|
|
|
# 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 — 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.
|
|
|
|
### 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.
|