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

11 KiB

name description version
coord-send 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". 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.