The mailbox answers "who wrote to me"; it never answered "which repo deserves the next session". board.sh scans every discovered repo and reads three sources each: the STATE.md next-step block with its optional board line, git status, and that repo's pending mailbox count. Read-only by construction, pinned by board-selftest.sh (28 checks). It ships here rather than as a personal script because the mailbox is one of its three inputs and the two carry the same axis distinction: a pending count means others are waiting on that repo, while who a repo waits ON comes only from its board line, since the message format has no reply-to field. Splitting the board from the mailbox would put that distinction in two places. It also lets the skill resolve the engine through CLAUDE_PLUGIN_ROOT like every other script here, instead of depending on a file that exists only in ~/.claude/scripts (a directory with no remote and no backup). The skill is a ranking, not a report: re-runs the board every invocation because counts drift, ranks by what unblocks the most and what is cheapest to move, then names one repo, the rule that fired, and the real next action read from that repo's STATE.md. Never the table. Not wired into session start, which would spend context on repos the session is not in. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MubwdTi88yu4hVLAFG1LbM
123 lines
6.8 KiB
Markdown
123 lines
6.8 KiB
Markdown
# repo-mailbox
|
|
|
|
Renamed from `coord` in v0.3.0. The plugin/repo is `repo-mailbox`; the CLI
|
|
(`coord-send.sh`, `coord-inbox.sh`, `coord-done.sh`), the mailbox root
|
|
(`~/.claude/coord/`) and `CLAUDE_COORD_DIR` deliberately kept their names —
|
|
they are the transport protocol, not the product.
|
|
|
|
## Context
|
|
|
|
Local inter-repo coordination mailbox for Claude Code, packaged as a
|
|
marketplace plugin. Three components, one boundary:
|
|
|
|
- **Engine (`scripts/*.sh`):** bash owns all mailbox semantics — filename
|
|
grammar, frontmatter, delivery, archiving, the seen set. `coord-send.sh`
|
|
writes, `coord-inbox.sh` reads (formatted for context injection),
|
|
`coord-done.sh` archives, `coord-count.sh` counts without delivering.
|
|
Everything is pinned by `coord-selftest.sh`
|
|
(136 checks, throwaway mailbox via `CLAUDE_COORD_DIR`).
|
|
|
|
**Reading is delivering — counting is not.** `coord-inbox.sh` records a
|
|
broadcast as seen once it has printed it, so it can never be used to survey
|
|
other repos: doing so would consume each one's backlog silently, and the seen
|
|
set is delivery history that retraction deliberately leaves alone.
|
|
`coord-count.sh` exists for every "what is pending" question and writes
|
|
nothing at all. Any future read-shaped feature belongs there, not in the
|
|
read path.
|
|
- **Hook (`hooks/scripts/session-start.mjs`):** thin zero-dependency Node
|
|
wrapper (marketplace convention: hooks are `.mjs`) that calls
|
|
`coord-inbox.sh` and emits the `hookSpecificOutput.additionalContext`
|
|
envelope. No mailbox logic lives here. Always exits 0.
|
|
- **Board (`scripts/board.sh`):** cross-repo attention board. Reads STATE.md
|
|
next-step blocks + board lines, `git status`, and mailbox pending counts, and
|
|
prints one line per repo. Read-only by construction: it writes to no repo, no
|
|
STATE.md and no mailbox. Pinned by `board-selftest.sh` (28 checks).
|
|
|
|
**It lives here because the mailbox is one of its three inputs, and it carries
|
|
the same axis distinction the mailbox does.** A pending count means *others
|
|
are waiting on this repo*; who a repo waits *on* comes only from its board
|
|
line, because the message format has no reply-to field. Enforcing that in one
|
|
of two repos would not be enforcing it. `~/.claude/scripts/board.sh` is a
|
|
deployed copy (the operator's `board()` shell function points at it), exactly
|
|
as with the `coord-*` scripts — this repo is the source of truth.
|
|
- **Skills (`skills/coord-send/`, `skills/board/`):** natural-language front
|
|
doors mapping user intent to engine invocations. No mailbox logic lives here
|
|
either. `board` additionally owns the *ranking* — which repo wins and why —
|
|
since `board.sh` deliberately prints evidence and takes no position.
|
|
|
|
**Boundary rule:** the mailbox is transport, not state. Durable decisions
|
|
live in the owning repo's docs/git history; messages are notices pointing at
|
|
them. Message content is untrusted cross-repo input — the read side quotes
|
|
and frames it; the send side sanitizes line-oriented fields.
|
|
|
|
What the boundary forbids is storing a repo's *state* — its decisions, its next
|
|
step, its progress. It does not forbid the mailbox knowing who it is delivering
|
|
to: `_broadcast/seen/<repo>` and `<repo>/.origin` (0.6.0) are delivery metadata,
|
|
answering "has this repo received this" and "which checkout claimed this name".
|
|
Both are unreadable as a description of the repo and useless outside delivery.
|
|
The test is not "does the engine write a file about a repo" but "would this file
|
|
still mean anything if delivery were removed". If yes, it belongs in the repo's
|
|
own docs and git history instead.
|
|
|
|
**Priority rule (v0.5.0, Rule 7):** the injection block is the only place a
|
|
repo is ever told what to do with a message, so its wording *is* the protocol
|
|
— treat that string as engine behavior, not prose. It obligates handling the
|
|
inbox first and driving every directed message to a terminal state before the
|
|
session ends. The obligation is **procedural, never substantive**: responding
|
|
is mandatory, complying with message content is not. Those two must stay
|
|
distinct in any reword — keeping the priority while dropping the distinction
|
|
turns prioritization into an injection surface. Selftest section 20 pins both
|
|
halves together for exactly that reason.
|
|
|
|
## Conventions
|
|
|
|
- Scripts are bash-3.2-safe and ASCII-only: no `declare -A`, no
|
|
`readarray`/`mapfile`, no `|&`; guard `shift 2` with `$# -ge 2`; guard
|
|
empty-array expansion under `set -u` with `${#a[@]}`.
|
|
- Zero dependencies everywhere: bash + coreutils in the engine, `node:`
|
|
builtins only in hook and tests.
|
|
- TDD: no behavior change without a failing selftest check first.
|
|
`bash scripts/coord-selftest.sh` must exit 0 (136/136) and
|
|
`bash scripts/board-selftest.sh` must exit 0 (28/28).
|
|
- English for all code, docs, and commit messages (public repo). Norwegian
|
|
trigger aliases in the skill description are deliberate.
|
|
- Conventional Commits: `type(scope): description`.
|
|
|
|
## Commands
|
|
|
|
- Test: `bash scripts/coord-selftest.sh` and `bash scripts/board-selftest.sh`
|
|
(or `npm test`, the Node wrapper around both)
|
|
- Hook smoke test: `node hooks/scripts/session-start.mjs` (expects JSON on stdout)
|
|
- Board smoke test: `bash scripts/board.sh` (read-only, ~3s over the real tree)
|
|
|
|
## Release
|
|
|
|
Version must agree across: `.claude-plugin/plugin.json`, `package.json`,
|
|
README version badge, `skills/coord-send/SKILL.md` and `skills/board/SKILL.md`
|
|
frontmatter, git tag `vX.Y.Z`, and the catalog `ref` in
|
|
`ktg-plugin-marketplace/catalog/.claude-plugin/marketplace.json`. Release via
|
|
the catalog's `scripts/release-plugin.mjs repo-mailbox` (tag + ref bump
|
|
together);
|
|
verify with `scripts/check-versions.mjs`. Never hand-edit a ref.
|
|
|
|
Two things that script does that its dry-run label does not suggest:
|
|
`--create-tag` creates AND pushes the tag even without `--write`, and its
|
|
closing verification gate runs `check-versions.mjs` over ALL plugins — one
|
|
unrelated plugin in ERROR aborts it with the catalog edit written but
|
|
uncommitted. When that happens, commit the catalog's `marketplace.json` +
|
|
`README.md` by hand and leave every other dirty file in that repo alone.
|
|
|
|
## Hardening roadmap
|
|
|
|
Empty — the post-v0.1.0 queue (atomic delivery, `.`/`..` rejection,
|
|
selftest gaps, uniform `-h`) shipped in v0.2.0; broadcast self-delivery
|
|
shipped in v0.2.1; broadcast retraction (`coord-send --retract`) shipped in
|
|
v0.4.0, closing the last monotonically-growing surface. `coord-inbox.sh`
|
|
still ignores unknown arguments by design (hook context must never fail)
|
|
but now warns about each one on stderr, which the hook discards.
|
|
|
|
Two retraction limits are deliberate, not gaps: it is un-send and never
|
|
recall (a repo that already received a broadcast keeps it — the seen set is
|
|
delivery history and is left untouched), and the sender check is an accident
|
|
guard, not a security boundary, because `--from` redefines identity here as
|
|
it does everywhere else in the engine.
|