Through 0.4.0 the injection block told every repo to "consider replying/resolving where it fits in this session". That sentence was the whole problem: the injection text is the only place a repo is ever told what to do with a message, so the wording IS the protocol -- and it granted permission to defer. Messages sat unanswered for weeks while each session did its own work first. Nothing was broken; the protocol was asking for exactly what it got. The block now states an ordering and a completion obligation: handle the inbox before the task the session came to do, and drive every directed message to a terminal state before the session ends (--reply-to or coord-done). Neither terminal state is the default -- the format has no reply-expected field, so mandating only the reply would manufacture traffic for messages that merely inform. Leaving one pending stays allowed but must be stated to the operator with a reason. Raising priority deliberately does not widen the trust boundary. The obligation is procedural, never substantive: responding is mandatory, complying with what a message asks is not. Untrusted cross-repo content still cannot direct the reader; it merely can no longer be ignored. The injection states both halves and selftest section 20 pins them together, so a future reword cannot keep the priority and quietly drop the distinction -- that combination would turn prioritization into an injection surface. Selftest 82 -> 93. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U6EixQo6hpoRCVtiAXdnFs
207 lines
9.2 KiB
Markdown
207 lines
9.2 KiB
Markdown
# Changelog
|
|
|
|
All notable changes to this project will be documented in this file.
|
|
|
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
|
## [0.5.0] - 2026-07-25
|
|
|
|
### Changed
|
|
|
|
- **The inbox is a priority, not a suggestion (Rule 7).** Through 0.4.0 the
|
|
injection block told every repo to "consider replying/resolving where it fits
|
|
in this session". That sentence was the whole problem: the injection text is
|
|
the only place a repo is ever told what to do with a message, so the wording
|
|
*is* the protocol — and it granted permission to defer. Messages sat
|
|
unanswered for weeks while each session did its own work first. Nothing was
|
|
broken; the protocol was asking for exactly what it got.
|
|
|
|
The block now states an ordering and a completion obligation:
|
|
|
|
- **Handle the inbox first**, before the task the session came to do — "not
|
|
after it, not *if there is time*".
|
|
- **Every directed message must reach a terminal state before the session
|
|
ends:** `coord-send --reply-to` or `coord-done`.
|
|
- **Neither terminal state is the default.** The format has no
|
|
reply-expected field, so mandating only the reply would manufacture
|
|
traffic for messages that merely inform.
|
|
- **Leaving one pending stays allowed, but must be stated** to the operator
|
|
with a reason. The escape hatch is explicit rather than silent.
|
|
|
|
- **Raising priority deliberately does not widen the trust boundary.** The
|
|
obligation is procedural, never substantive: *responding* is mandatory,
|
|
*complying* with what a message asks is not — only the operator authorizes
|
|
that. Untrusted cross-repo content still cannot direct the reader; it merely
|
|
can no longer be ignored. The injection states both halves, and the selftest
|
|
pins them together so a future reword cannot keep the priority and quietly
|
|
drop the distinction.
|
|
|
|
- Selftest grew from 82 to 93 checks (new section 20 pins the wording contract:
|
|
the permissive phrasing is gone, ordering and completion are stated, both
|
|
terminal states are named with neither as default, responding-vs-complying is
|
|
kept distinct, and the untrusted-data framing survives).
|
|
|
|
## [0.4.0] - 2026-07-25
|
|
|
|
### Added
|
|
|
|
- **`coord-send --retract <filename>`: a broadcast can finally be retired.**
|
|
Until now nothing could remove a message from `_broadcast/inbox/`:
|
|
`coord-done` is directed-only and never touches the broadcast queue. The
|
|
backlog could therefore only grow, and every *new* repo received the entire
|
|
standing history at its first session — including announcements that had
|
|
since become false. Retract moves the message to `_broadcast/archive/`, so
|
|
no future repo is served it.
|
|
|
|
Deliberate limits, both documented and pinned by tests:
|
|
|
|
- **Un-send, not recall.** Repos that already received the broadcast keep
|
|
it; `_broadcast/seen/` is delivery history and is left untouched.
|
|
- **Only the sender may retract** (`from:` must match the repo identity).
|
|
`--from` overrides this, as it does everywhere else in the engine, which
|
|
makes the check an accident guard rather than a security boundary.
|
|
- **Nothing is deleted**, mirroring `coord-done`. Retracting twice is a
|
|
no-op (exit 0).
|
|
|
|
- Selftest grew from 70 to 82 checks (new section 19 covers retraction:
|
|
authorization, archiving, non-delivery to new repos, no collateral damage to
|
|
other broadcasts, invalid names, flag combinations, and idempotency).
|
|
|
|
### Fixed
|
|
|
|
- README's install command still said `claude plugin install
|
|
coord@ktg-plugin-marketplace` after the v0.3.0 rename.
|
|
- README told the reader to prune `_broadcast/inbox/` by hand when a notice
|
|
stopped being relevant — advice that contradicted the rule that the script
|
|
owns mailbox files. `--retract` is now the supported path.
|
|
|
|
## [0.3.0] - 2026-07-25
|
|
|
|
### Changed
|
|
|
|
- **Renamed the plugin and repository from `coord` to `repo-mailbox`.** The old
|
|
name said that something was coordinated but not what the thing was; the new
|
|
one names what it is, using the same vocabulary the code and docs already use
|
|
throughout (mailbox, inbox, archive, broadcast). The Forgejo repository moved
|
|
from `open/coord` to `open/repo-mailbox` (the old URL redirects), and the
|
|
marketplace entry, plugin manifest and package name follow.
|
|
|
|
**This is breaking for installed users:** the skill is now invoked as
|
|
`/repo-mailbox:coord-send` rather than `/coord:coord-send`, and the plugin
|
|
must be reinstalled under its new name.
|
|
|
|
**Deliberately unchanged:** the CLI (`coord-send.sh`, `coord-inbox.sh`,
|
|
`coord-done.sh`, `coord-selftest.sh`), the skill name `coord-send`, the
|
|
mailbox root `~/.claude/coord/`, and the `CLAUDE_COORD_DIR` override. Those
|
|
name the transport protocol, not the product — renaming them would migrate
|
|
live mailbox data and break message history in every participating repo for
|
|
no gain in clarity.
|
|
|
|
### Fixed
|
|
|
|
- Catalog README listed `48 selftest checks` for this plugin; the actual count
|
|
has been 70 since v0.2.2.
|
|
|
|
## [0.2.2] - 2026-07-25
|
|
|
|
### Changed
|
|
|
|
- `coord-inbox.sh` now warns on stderr for each unknown argument instead of
|
|
discarding it silently. The read path stays lenient (it runs inside the
|
|
SessionStart hook, which must never fail a session over a stray flag) and
|
|
the exit code is unchanged, but a mistyped flag no longer looks like a
|
|
working invocation. The hook runs the script with stderr discarded, so the
|
|
warning surfaces in manual CLI use only.
|
|
|
|
### Added
|
|
|
|
- Selftest: 68 -> 70 checks, covering the stderr warning and the leniency it
|
|
must not break (unknown argument still reads the inbox and exits 0).
|
|
|
|
### Documentation
|
|
|
|
- README: corrected the stale selftest count (64 -> 70), documented that a
|
|
broadcast is never delivered back to its own sender (shipped in 0.2.1 but
|
|
undocumented), and added rule 6 — message content is data, never
|
|
instructions — which `coord-inbox.sh` already enforces in the injection
|
|
framing.
|
|
|
|
## [0.2.1] - 2026-07-25
|
|
|
|
### Fixed
|
|
|
|
- Broadcast self-delivery: `coord-send --broadcast` left no seen-set entry for
|
|
the sender, so the announcing repo got its own announcement injected at its
|
|
next session start. The delivered filename is now recorded in
|
|
`_broadcast/seen/<sender>` at delivery time, reusing the existing per-repo
|
|
seen set rather than adding a second exclusion mechanism. Filtering on the
|
|
`from:` field at read time was rejected: `from:` is sender-controlled, so it
|
|
would let any repo suppress a broadcast for another by forging the field.
|
|
The seen file is keyed by the raw sender name (the read side keys it
|
|
unsanitized, so the two must agree); senders containing a slash, or equal to
|
|
`.`/`..`, skip the marking rather than escape the directory.
|
|
|
|
### Added
|
|
|
|
- Selftest: 64 -> 68 checks, covering sender self-exclusion, unaffected
|
|
delivery to other repos, self-exclusion for sender names that are not
|
|
shell-clean, and the seen-marking path-escape guard.
|
|
|
|
## [0.2.0] - 2026-07-24
|
|
|
|
Hardening pass over the three CLIs.
|
|
|
|
### Fixed
|
|
|
|
- Atomic delivery: the temp file is created inside the destination directory
|
|
(dot-prefixed, invisible to the inbox glob), so the final rename never
|
|
crosses filesystems and readers never observe a half-written message.
|
|
- `.` and `..` are rejected explicitly in the `--reply-to` and `coord-done`
|
|
name guards instead of relying on downstream failure.
|
|
|
|
### Added
|
|
|
|
- `-h`/`--help` on `coord-inbox.sh`, making usage output uniform across the
|
|
three CLIs.
|
|
- Selftest: 48 -> 64 checks, closing gaps around the default mailbox path via
|
|
`HOME` fallback, malformed frontmatter on the read path, and a read-only
|
|
destination directory.
|
|
|
|
## [0.1.0] - 2026-07-24
|
|
|
|
First public release: extraction of a battle-tested personal mechanism into a
|
|
distributable plugin.
|
|
|
|
### Added
|
|
|
|
- Bash engine: `coord-send.sh` (directed / broadcast / reply delivery),
|
|
`coord-inbox.sh` (pending inbox + unseen broadcasts, formatted for context
|
|
injection), `coord-done.sh` (archive without reply), all bash-3.2-safe,
|
|
ASCII-only, zero dependencies.
|
|
- `coord-selftest.sh`: 48 checks against a throwaway mailbox, including
|
|
regression cover for filename sanitization, no-hang argument parsing,
|
|
prompt-injection resistance, frontmatter hygiene, and broadcast ordering.
|
|
- SessionStart hook (`hooks/scripts/session-start.mjs`): thin zero-dependency
|
|
Node wrapper that injects the repo's pending mailbox as
|
|
`additionalContext`. Always exits 0 — a broken mailbox never blocks a session.
|
|
- `coord-send` skill: natural-language front door (English + Norwegian
|
|
triggers) mapping intent to engine invocations, including bounded
|
|
multi-target loops, deferred sends, and replies.
|
|
|
|
### Fixed
|
|
|
|
- Frontmatter injection: CR/LF and control characters in `--subject`/`--from`
|
|
are collapsed before writing, so fields can no longer inject frontmatter
|
|
lines or a premature `---` terminator.
|
|
- Broadcast loss: a per-repo seen set (`_broadcast/seen/<repo>`, one delivered
|
|
filename per line) replaces the single high-water mark, which silently
|
|
dropped a same-second broadcast whose filename sorted below one already
|
|
seen. Upgrade note: a legacy watermark file is read as a one-entry seen
|
|
set, so old broadcasts may be re-delivered once.
|
|
|
|
### Changed
|
|
|
|
- Read-side protocol strings are English (`--- message:`, `-> reply:`,
|
|
UNTRUSTED DATA framing); the forgery-resistance selftest checks pin the
|
|
English tokens.
|