repo-mailbox/skills/coord-send/SKILL.md
Kjell Tore Guttormsen 5e5bc4a66e feat(route,board): strike the advisor rule, add board.sh --row <repo>
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>
2026-09-12 23:45:47 +02:00

219 lines
11 KiB
Markdown

---
name: coord-send
description: >-
Send an inter-repo coordination message through the local coord mailbox — a
natural-language front door over the `coord-send` script. Use this whenever the
user wants to notify, tell, message, ping, or coordinate with another repository
(or all repositories) about work happening in the current repo: "send info to all
repos", "tell repo-x the bug is fixed", "let repo Y and Z know",
"broadcast that the spec changed", "reply to that coord message", or a
deferred/conditional notice like "when the build is green, notify repo X". Also
triggers on Norwegian phrasings: "send info til alle repo", "varsle repo X", "gi
beskjed til Y og Z", "kringkast at …", "svar på coord-meldingen", "når Z er ferdig,
varsle X". Trigger even when the user names a repo plus something to convey without
saying "coord" explicitly — routing a message to another repo IS this skill. Also
covers retiring a broadcast that has become wrong or obsolete: "retract that
broadcast", "that announcement is outdated, pull it", "trekk tilbake kringkastingen",
"den broadcasten er utdatert".
version: "0.34.0"
---
# coord-send — natural-language front door for inter-repo messages
This skill turns a plain request ("tell repo X that Y happened") into the right
`coord-send` invocation. The script is the engine; this skill is only the mapping
from intent to arguments. It never edits mailbox files by hand — always go through
the script, because the script owns the filename grammar, frontmatter, and delivery
guarantees.
## Boundary — why messages are thin
The mailbox is **transport, not state**. A coord message is a *notice*, not the
source of truth. The durable record of any decision lives in the owning repo's
state files / docs and its git history. So when you compose a body, write a
self-contained heads-up and point at where the real record lives (a commit
hash, a doc path) — don't try to make the message itself the canonical artifact.
Coordination content is also private: it never belongs on a public surface.
## The engine
CSEND="${CLAUDE_PLUGIN_ROOT}/scripts/coord-send.sh"
There is no deployed copy anywhere else and no fallback path. A Bash tool call
never has `CLAUDE_PLUGIN_ROOT` set as a real shell variable — only this
skill's own rendering resolves the token — so a `${CLAUDE_PLUGIN_ROOT:-$HOME/.claude}`
fallback silently won every time the line above was actually executed,
routing through whatever happened to sit at `~/.claude/scripts/coord-send.sh`
instead of the plugin's own bundled script. That path was never a legitimate
parallel deployment — the operator invokes this skill only through its
natural-language front door, never a personal terminal alias — so the file
that sat there was a pure accident target with no owner keeping it current.
It has been deleted. If this fails to resolve, the fix is this expression —
never a restored fallback.
Interface (body comes from a quoted heredoc so nothing in it is shell-expanded):
# to one repo
"$CSEND" --to <repo> --subject "<subject>" <<'BODY'
<message body>
BODY
# to every repo (accumulates; first-time newcomers see it too)
"$CSEND" --broadcast --subject "<subject>" <<'BODY'
<message body>
BODY
# reply to a message this repo received (routes to the original sender and
# marks the original handled; target + "Re: …" subject are inferred)
"$CSEND" --reply-to <message-filename> <<'BODY'
<message body>
BODY
# retire one of THIS repo's own broadcasts (no subject, no body)
"$CSEND" --retract <broadcast-filename>
# a notice that needs no answer (any of the forms above)
"$CSEND" --to <repo> --subject "<subject>" --fyi <<'BODY'
<message body>
BODY
Sender identity (`--from`) defaults to the basename of the current git toplevel, so
you almost never set it. Outside a git repo there is no default — the send refuses
with exit 2 rather than naming itself after the working directory, so on a global
surface (`~/repos`, `$HOME`) pass `--from <repo>` and make the identity a choice.
Exit 0 = delivered; exit 2 = usage/IO error (read stderr and fix the arguments
rather than retrying blindly).
## Does it need an answer? (`--fyi`)
Every message declares whether its sender expects a reply. **Omitting `--fyi` is
the declaration that one is expected** — that is the default, and it is the safe
one: a forgotten flag over-counts what the recipient owes, which is visible,
while the opposite would create debt nobody ever sees.
Pass `--fyi` when the message is genuinely a notice: "shipped 0.9.0", "the spec
moved to docs/x.md", "your build is green again". Omit it when you are asking a
question, requesting a decision, or handing over work — anything where silence
would leave *you* blocked.
Two things this flag is not:
- **Not a way to lower the bar for the recipient.** Both terminal states stay
open on every message: a `--fyi` message must still be closed with
`coord-done`, and the recipient may still reply. The field says what *you*
expect, and the receiving session is told in as many words that it is a
declaration, not an instruction.
- **Not available on a broadcast.** `--broadcast` always writes
`reply-expected: no`, `--fyi` or not, because there is no reply path to a
broadcast at all (`--reply-to` resolves inside the recipient's own mailbox).
Passing it there is harmless and changes nothing.
A reply is not a special case either: `--reply-to` without `--fyi` expects one
back. When your reply closes the exchange — and it usually does — say so with
`--fyi` rather than leaving the other repo an open item.
## Choosing the target
- **One named repo** → `--to <repo>`. The repo name is its directory basename; use
the name the user gave. No registration exists — sending to a new name just creates
that repo's inbox, so a typo silently creates a dead mailbox. If unsure a name is
real, check `~/repos/` or existing `~/.claude/coord/<repo>/` before sending.
- **Several named repos** ("X and Y") → loop `--to` once per repo with the same
subject and body. There is no multi-target flag; the loop is the mechanism:
for repo in repo-x repo-y; do
"$CSEND" --to "$repo" --subject "<subject>" <<'BODY'
<body>
BODY
done
- **All repos** → `--broadcast`. Prefer this only when the notice genuinely concerns
everyone, because broadcasts accumulate and every future first-time repo sees the
standing backlog. For a bounded, known set, loop `--to` instead so unrelated future
repos don't inherit it. A broadcast that later turns out wrong can be retired with
`--retract` (see below), but only for repos that have not received it yet.
- **A reply to something received** → `--reply-to <filename>`. Use the filename from
the injected inbox/archive; it resolves the sender and closes the original in one
step.
## Retracting a broadcast
When a broadcast has become wrong or obsolete, retire it — don't send a correction
and leave the original standing, because every future first-time repo would receive
both. `--retract <broadcast-filename>` archives it out of the delivery queue.
Three things to be honest about when you report it:
- **It is un-send, not recall.** Repos that already received the broadcast keep it.
Retraction only stops delivery to repos that haven't seen it yet. If the old news
actively misleads someone who already got it, a correcting broadcast is still
needed — retraction alone does not reach them.
- **Only the sender may retract.** The `from:` field must match this repo's identity.
If the announcing repo has since been renamed, its old identity no longer resolves,
so pass `--from <old-sender>` explicitly (read the sender off the file). Say that
you did.
- **Nothing is deleted.** The message moves to `_broadcast/archive/`. Never remove a
mailbox file by hand — the script owns the filename grammar and delivery guarantees.
Find the filename in `~/.claude/coord/_broadcast/inbox/` (or from the injected
`--- broadcast: <filename> ---` line) rather than guessing it.
## Composing the message
Derive a short, specific `--subject` from the intent if the user didn't give one
("ingest bug fixed", not "update"). In the body, state what happened, what the
recipient should know or do, and a pointer to the durable record (commit hash, doc
path). Keep it to the point — this is a notice, not a report. Match the repo's
language convention for message content; keep it sober and factual.
## Deferred and conditional sends
"When Z is done, notify X" means: do **not** send now. Hold the intent — the target,
subject, and body — and continue the work. The moment condition Z is satisfied *in
this session*, fire the send. This works because you carry the intent across turns
within a session.
Be honest about the limit: a skill cannot persist intent across sessions. If the
session is ending and the condition still hasn't been met, surface the pending
message to the user so it isn't silently lost — don't imply it will fire later on
its own.
## After sending
Report what actually happened: the target(s), the subject, and the delivered
filename(s) from the script's stdout. If a send failed (exit 2), say so with the
stderr reason rather than claiming success. Delivery reaches the recipient at *their*
next session start (the receive side is a hook), so tell the user it's queued, not
that the other repo has seen it.
## Examples
**Example 1 — single repo, composed body**
Input: "tell repo-x the ingest bug is fixed, it was commit abc1234"
Action: `--to repo-x --subject "ingest bug fixed"`, body naming the fix and
pointing at commit abc1234 and the owning doc.
**Example 2 — broadcast**
Input: "let all the repos know the shared spec grew a new validation rule"
Action: `--broadcast --subject "spec: new validation rule"`, body summarizing
the change and pointing at the spec's owning repo.
**Example 3 — bounded multi-target**
Input: "gi beskjed til repo-x og repo-y om at gaten er re-pinnet"
Action: loop `--to repo-x` then `--to repo-y`, same subject "shared gate re-pinned".
**Example 4 — deferred**
Input: "når testene er grønne, varsle repo-y"
Action: keep working; once the test run passes this session, send
`--to repo-y` with the green result. If the session ends first, surface the
pending notice to the user.
**Example 5 — reply**
Input: "svar på coord-meldingen fra repo-x at vi tar det"
Action: `--reply-to <that-message-filename>`, body acknowledging and stating the plan.
**Example 6 — retract**
Input: "den gamle kringkastingen om plugin-navnet er utdatert, trekk den"
Action: locate the file in `_broadcast/inbox/`, then `--retract <that-filename>`
(adding `--from <old-sender>` if this repo has been renamed since). Report that
repos which already received it are unaffected.