chore(release): bump to 0.30.0

Two commits landed after v0.29.0 and neither is reachable: every entry point
on this machine runs the version-pinned plugin cache, not the tree.

Minor rather than patch because the behaviour is EXTENDED, not merely
repaired - coord-count.sh and board.sh gained a new exit status (3, "the
world you named is not there"), and pre-state-line-guard.mjs gained
CLAUDE_STATE_MAX_LINES. Exit codes are a contract, and the installed cache
still runs the old one.

Version synced across all seven version-bearing files - plugin.json,
package.json, the README badge, and the four SKILL.md frontmatters - so
release-plugin.mjs's plugin.json == README-badge == target check passes.
The three remaining 0.29.0 mentions (CLAUDE.md:915, CLAUDE.md:960,
board.sh:1388) are historical prose about when things shipped and must keep
saying 0.29.0.

CHANGELOG covers BOTH unreleased commits, not just the newest: d8fdeaa's
five defects were one class - a failed measurement rendering as a
reassuring value - and the entry carries what that session found about
itself, that --plan's free-capacity test had to move from "$7 + 0" to a
string comparison against "0", because "?" coerces to 0 in arithmetic and
would have certified an UNMEASURED tree as free capacity. The defect
reappearing one layer down, wearing the fix as a disguise.

No tag and no catalog change: that is the catalog session's order, which
runs release-plugin.mjs to do the tag and the ref bump atomically. Doing
both here would take a decision on the catalog's behalf without its
context.

Suites verified under system bash 3.2, not Homebrew 5.3: coord 242, board
300, route 69, orders 110, guard 54; npm test 11/11.

Order 20260826T115026Z-998059640-from-.claude
This commit is contained in:
Kjell Tore Guttormsen 2026-08-26 14:09:37 +02:00
commit 3e4a3bc62a
8 changed files with 112 additions and 7 deletions

View file

@ -1,6 +1,6 @@
{
"name": "repo-mailbox",
"version": "0.29.0",
"version": "0.30.0",
"description": "Local mailbox for coordination between Claude Code sessions in different repositories. Directed messages and broadcasts as plain Markdown files on your own disk, injected as context at session start. Local, private, no network.",
"author": {
"name": "Kjell Tore Guttormsen"

View file

@ -5,6 +5,111 @@ 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.30.0] - 2026-08-26
Two commits landed after `v0.29.0` and neither is reachable: every entry point
on this machine runs the version-pinned plugin cache, not the tree. Minor
rather than patch because the behaviour is EXTENDED, not merely repaired -
`coord-count.sh` and `board.sh` gained a new exit status and the state-line
guard gained an environment variable. Exit codes are a contract, and the
installed cache still runs the old one.
All five defects here are the same class: **a failed measurement rendering as
a reassuring value** - a broken or uninstrumented query returning a
positive-looking null, consumed as a fact about the world.
### Fixed
- **`coord-count.sh`: a mailbox root that does not exist was byte-identical to
one where nobody has pending mail** - zero lines on stdout, exit 0, nothing
on stderr. `board.sh` consumes that TSV. The contract is now three statuses:
0 = counted, 2 = usage error, **3 = mailbox root absent**. 3 rather than 2 on
purpose: 2 already means "you called me wrong" and this means "the world you
named is not there" - two different repairs, and a consumer that only ever
sees one integer cannot tell them apart. Both nonzero paths print NOTHING on
stdout, so neither can be mistaken for a count of zero. What the old
"always 0" claim protected is kept and made precise: no state OF THE MAILBOX
can produce a nonzero exit, so a SessionStart still cannot be failed by mail.
An existing-but-empty root stays a silent, clean 0, and the selftest pins
that silence as hard as it pins the failure.
- **`coord-count.sh`'s header promised exit 0 unconditionally, and that was
already false** - `--exclude` with no value exited 2 - so the documented
contract contradicted code a reader could run. The header text is now pinned
by a check of its own: a doc line nothing tests is a doc line that drifts,
which is how this one drifted.
- **`board.sh`: `git status --porcelain | wc -l` yields zero lines whether the
tree is CLEAN or git refused to answer**, so DRT printed **0** and every
reader took it as "nothing uncommitted here". Reachable, not theoretical:
discovery tests `.git` with `-e` so worktrees are found, and a worktree whose
parent checkout was deleted exits 128. The exit status is now the
discriminator and an unmeasured tree reads **`?`** - the same token
`coord-count.sh` already uses for an age it could not compute.
**The fix was worthless without its two awk consumers, and one of them is
the point:** `--plan`'s free-capacity test compared `$7 + 0`, and `"?"`
coerces to **0** in arithmetic - so an UNMEASURED tree would have been
certified as free capacity. That is the defect reappearing one layer down,
wearing the fix as a disguise. It now compares the field as a STRING against
exactly `"0"`; only a tree measured clean is clean. The SUM roll-up, where a
`?` would silently add 0, now NAMES the repos it could not add.
- **`board.sh`: a scan root that does not exist was skipped in silence**, so a
typo in `--roots`, a moved home directory and a genuinely empty tree were one
single output with the denominator never reported. Every bad root is now
named on stderr; the exit status changes only when NO root was scanned at all
(**3**, for the same reason as above). A mix still exits 0 and still prints
the board - some repos really were scanned. This REPLACES an older assertion
reading `check "missing root is a clean no-op"`: the defect encoded as a
passing test, which is why nothing ever caught it. `brief-nightly.sh` needs
no change and gains from this - it already treats a nonzero `--brief` as
failure and keeps yesterday's briefing, so a mistyped root now preserves the
file instead of overwriting it with an empty render.
### Added
- **`CLAUDE_STATE_MAX_LINES` overrides the state-line guard's limit**, the same
kind of knob `CLAUDE_COORD_DIR` is for the mailbox root. As a bare constant
the limit forced every boundary fixture to hardcode the number the code
carried - two copies of one policy, both rewritten by hand the last time the
operator moved it (60 -> 120). It is **not** a bypass claim; this guard has
always been escapable by writing the file another way. The one thing it must
never do is silently fail to take effect, so an UNUSABLE value (`""`, `"0"`,
negatives, anything not made of digits) **denies with a message naming the
variable** instead of falling back to 120 - a caller who set it and got the
default anyway would be reading a limit that was never in force.
### Changed
- **The README's Install block is now fenced (` ```bash `)** while the rest of
the README keeps its indented blocks. The org-side D-census reported this
repo as the only plugin of 12 with no start command; the premise was
imprecise - the lines were present, indented - but the finding held, because
the machine-readable entry point keys on a fenced block and 10 of 11 sibling
plugin READMEs use one. Measured before and after: 11 of 11 now match. Only
the Install block was converted; the other 39 indented blocks were not
ordered changed and converting them would bury a two-line fix in a
whole-file diff.
### Known gaps, stated rather than closed
- `coord-inbox.sh:57` and `coord-order-inbox.sh:60/64` carry the identical
`[ -d "$COORD" ] || exit 0` shape that was fixed in `coord-count.sh`. The
denominator was measured, not estimated, and they were deliberately left
alone: they are the INJECTION path, their output is prose a session reads
rather than a TSV a program parses, and their contract really is "never fail
a SessionStart".
- `board.sh` does not yet CONSUME `coord-count.sh`'s new exit 3. The signal
exists; the `HAVE_COUNT` machinery does not read it. Tracked as separate
work rather than widened into this release in silence.
### Testing
Suites at this release, verified under system bash 3.2 (not Homebrew 5.3):
coord 242, board 300, route 69, orders 110, state-line-guard 54; `npm test`
11/11. Every design choice above was mutation-tested, and every negative check
carries a known-positive control - one selftest section's first cut was
VACUOUSLY green (wrong basename, plus a fixture path that was never exported)
and is recorded in `CLAUDE.md` rather than quietly fixed.
## [0.29.0] - 2026-08-23
Answers a question the board could not answer before: which repos are finished

View file

@ -10,7 +10,7 @@ Session A in repo X leaves a message for repo Y; the next session in repo Y gets
*AI-generated: all code produced by Claude Code through dialog-driven development.*
![Version](https://img.shields.io/badge/version-0.29.0-blue)
![Version](https://img.shields.io/badge/version-0.30.0-blue)
![Hooks](https://img.shields.io/badge/hooks-1-green)
![Skills](https://img.shields.io/badge/skills-4-orange)
![CLI scripts](https://img.shields.io/badge/CLI_scripts-8-blue)

View file

@ -1,6 +1,6 @@
{
"name": "repo-mailbox",
"version": "0.29.0",
"version": "0.30.0",
"private": true,
"type": "module",
"engines": {

View file

@ -20,7 +20,7 @@ description: >-
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.29.0"
version: "0.30.0"
---
# board — which repo deserves the next session

View file

@ -15,7 +15,7 @@ description: >-
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.29.0"
version: "0.30.0"
---
# coord-send — natural-language front door for inter-repo messages

View file

@ -17,7 +17,7 @@ description: >-
skill. Not for choosing WHICH repo deserves the next session (that is `board`),
not for scoring model and effort alone (that is `route`), and not for sending a
message to another repo (that is `coord-send`).
version: "0.29.0"
version: "0.30.0"
---
# dispatch — hand a session a task it can actually start on

View file

@ -14,7 +14,7 @@ description: >-
the operator names no model and no tool — choosing the model for the next
session IS this skill. Not for choosing which REPO gets the next session:
that is the `board` skill.
version: "0.29.0"
version: "0.30.0"
---
# route — what the next session should run with