feat(board): give the plan a paste= line the operator can actually use

--plan shipped with dir= and command= as separate fields. That is right for a
driver, which moves the pane itself and then types the command, and unusable
for a person: the first operator to read a block could not act on it at all.

Joining an absolute path from one field with a command from another is not a
saved line of output. It is the step where a session gets started in the wrong
repository - the same failure the absolute dir= exists to prevent, moved from
the machine to the human.

paste= is the two already joined, cd <dir> && <command>, one thing to select.
Both forms stay: the two consumers want the same fact shaped differently, and
neither shape serves both.

Emitted only alongside command=, never once per block. A `paste=cd X && ` with
nothing after it would run the cd and then a bare newline, which fails
SILENTLY - the operator ends up in the right directory with no session started
and no error to notice.

board-selftest: 86 -> 89 checks.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011eKRzhD1RqY2MxvDEkrJAM
This commit is contained in:
Kjell Tore Guttormsen 2026-08-02 07:34:30 +02:00
commit 5bea1f8651
10 changed files with 76 additions and 9 deletions

View file

@ -1,6 +1,6 @@
{
"name": "repo-mailbox",
"version": "0.16.0",
"version": "0.17.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,28 @@ 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.17.0] - 2026-08-02
### Added
- **`paste=` in every `--plan` block — the operator's line.** `--plan` shipped
with `dir=` and `command=` as separate fields, which is correct for a driver
(it moves the pane itself, then types the command) and unusable for a person.
The first operator to read a block could not act on it: joining an absolute
path from one field with a command from another is not a saved line of
output, it is the step where a session gets started in the wrong repository.
`paste=` is the two already joined — `cd <dir> && <command>` — one thing to
select and paste. Both forms stay, because the two consumers want the same
fact shaped differently and neither shape serves both.
It is emitted **only alongside `command=`**, never once per block: a
`paste=cd X && ` with nothing after it would run the `cd` and then a bare
newline, which fails silently by leaving the operator in the right directory
with no session started and no error to notice.
- `board-selftest.sh`: 86 -> 89 checks.
## [0.16.0] - 2026-08-02
### Added

View file

@ -54,7 +54,7 @@ marketplace plugin. Three components, one boundary:
- **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` (86 checks).
STATE.md and no mailbox. Pinned by `board-selftest.sh` (89 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
@ -105,6 +105,16 @@ marketplace plugin. Three components, one boundary:
route-line grammar, shared with `--brief`, and distinguishes the two causes by
exit code rather than by an empty string.
**`paste=` and `dir=`/`command=` are the same fact for the two consumers, and
neither is redundant.** A driver moves the pane itself and then types the
command, so it needs them apart; a human needs ONE thing to select. Handing
the operator two fields to join by hand is not a saved output line, it is the
step where a session starts in the wrong repo - and it was measured the moment
the feature met its first user, who could not act on the block at all. `paste=`
is emitted only alongside `command=`: `paste=cd X && ` with nothing after it
would run the cd and then a bare newline, which fails SILENTLY by leaving the
operator in the right directory with no session started.
**Driving a terminal from the plan does NOT belong here, and the measurement
in `docs/ghostty-orchestration-measurement.md` is the argument, not taste.**
It is a version-pinned undocumented composition over a preview API whose
@ -241,7 +251,7 @@ obligations in another repo.
builtins only in hook and tests.
- TDD: no behavior change without a failing selftest check first.
`bash scripts/coord-selftest.sh` must exit 0 (183/183),
`bash scripts/board-selftest.sh` must exit 0 (86/86) and
`bash scripts/board-selftest.sh` must exit 0 (89/89) and
`bash scripts/route-selftest.sh` must exit 0 (73/73).
- English for all code, docs, and commit messages (public repo). Norwegian
trigger aliases in the skill description are deliberate.

View file

@ -8,7 +8,7 @@
*AI-generated: all code produced by Claude Code through dialog-driven development.*
![Version](https://img.shields.io/badge/version-0.16.0-blue)
![Version](https://img.shields.io/badge/version-0.17.0-blue)
![Platform](https://img.shields.io/badge/platform-Claude_Code_Plugin-purple)
![Hooks](https://img.shields.io/badge/hooks-1-green)
![Skills](https://img.shields.io/badge/skills-3-orange)
@ -111,6 +111,8 @@ It makes **zero model calls**, which is the point rather than a detail. Under su
It prints `key=value` blocks rather than prose because it has **two consumers**: the operator pasting a command, and a separate repo driving a terminal from it. Prose would make the rendered format an API that no test here could hold stable for a consumer living in another repo. Each block carries `dir=` as an absolute path — a new terminal pane inherits its anchor's working directory, so a plan that omitted it would look right and point at the wrong repo. A repo whose route line is missing or unparseable gets `command_missing=` with the reason, never an empty `command=`: a driver reading `^command=` would type a blank line into a live pane.
The two consumers want the same information shaped differently, so each block carries both. `dir=` and `command=` stay separate for a driver, which moves the pane itself and then types the command. **`paste=` is the operator's line** — the two already joined into one `cd … && claude …` to select and paste. Asking a person to assemble a directory from one field and a command from another is not a saved line of output; it is the step where a session gets started in the wrong repository. `paste=` is emitted only where `command=` is, since `paste=cd X && ` with nothing after it would run the `cd` and then a bare newline, leaving you in the right directory with no session and no error.
Driving a terminal from this plan deliberately lives **outside this repo**. That work is a version-pinned, undocumented composition on top of a preview API whose documented path is already broken upstream, and its blast radius reaches into other repos' running sessions. The dependency runs one way — the driver consumes the plan, the plan never knows a terminal exists — so if the terminal API breaks, the plan still prints and the operator still pastes.
## Security Model
@ -147,7 +149,7 @@ Note that raising the inbox's priority (Rule 7) deliberately does **not** widen
## Development
bash scripts/coord-selftest.sh # 183 checks against a throwaway mailbox
bash scripts/board-selftest.sh # 86 checks against a throwaway repo tree
bash scripts/board-selftest.sh # 89 checks against a throwaway repo tree
bash scripts/route-selftest.sh # 73 checks, incl. the route->board round trip
npm test # all three selftests via node --test

View file

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

View file

@ -566,6 +566,16 @@ check "repo owing mail but lacking a route line is marked, never guessed at" $?
printf '%s' "$PLAN" | grep -q '^neste=.*check-versions'
check "plan prints the full NESTE line, not the 38-char table excerpt" $?
# paste= is the operator's line and command= is the driver's. Both are kept
# because they answer different questions: a driver cd's the pane itself and
# would choke on a compound line, while the operator needs ONE thing to copy.
# Assembling it by hand from two fields is where a tab lands in the wrong repo.
printf '%s' "$PLAN" | grep -q "^paste=cd $ROOT/repo-owes && claude --model sonnet --effort high --advisor opus\$"
check "paste= is the whole line: cd into the repo, then the startup command" $?
printf '%s\n' "$PLAN" | grep -A7 '^repo=repo-typo$' | grep -q '^paste='; [ $? -ne 0 ]
check "a repo with no usable route line gets no paste line either" $?
# The driver cds into this, so a relative or missing path lands a session in the
# wrong repo - constraint 2 of the Ghostty measurement, in field form.
pdir="$(printf '%s\n' "$PLAN" | grep -m1 '^dir=' | cut -d= -f2-)"
@ -587,6 +597,13 @@ n_cms="$(printf '%s\n' "$PLAN" | grep -c '^command_missing=')"
&& [ "$n_nes" -eq "$n_tab" ] && [ $(( n_cmd + n_cms )) -eq "$n_tab" ]
check "every planned block carries every field exactly once" $?
# paste= is one per command=, never one per block: a repo with no command has
# nothing to paste, and `paste=cd X && ` would run the cd and then a bare
# newline - putting the operator in the right repo with no session started.
n_pst="$(printf '%s\n' "$PLAN" | grep -c '^paste=')"
[ "$n_pst" -eq "$n_cmd" ]
check "paste= appears exactly where command= does, never beside command_missing" $?
# Numbering must be dense: a driver opening tab 4 of 3 is a real failure mode.
seq_ok=0
printf '%s\n' "$PLAN" | grep '^tab=' | cut -d= -f2 > "$ROOT/tabs.txt"

View file

@ -409,10 +409,18 @@ fi
# that dropped them silently would repeat exactly that defect.
# Excluded: done, deferred, blocked-without-debt, and repos with no STATE.md and
# no debt - a tab that cannot be moved is not a plan entry.
# Two lines for two consumers, and they are not redundant. A driver cd's the
# pane itself and then types the command, so it needs them apart; the operator
# needs ONE thing to select and paste, because assembling `cd <dir>` from one
# field and the command from another is precisely where a tab ends up started
# in the wrong repo. paste= is emitted only alongside command= - `paste=cd X && `
# with nothing after it would run the cd and then a bare newline, leaving the
# operator in the right directory with no session and no error.
plan_cmd() {
if pc_cmd="$(route_cmd_for "$1")"; then
if [ -n "$pc_cmd" ]; then
printf 'command=%s\n' "$pc_cmd"
printf 'paste=cd %s && %s\n' "$1" "$pc_cmd"
else
printf 'command_missing=route-linjen kunne ikke tolkes (kost: %s)\n' "$2"
fi

View file

@ -17,7 +17,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.16.0"
version: "0.17.0"
---
# board — which repo deserves the next session
@ -155,6 +155,14 @@ Blocks are `key=value` and comment lines start with `#`, because a separate driv
repo consumes this output. That format is a contract with a consumer outside this
repo: never reformat it, and never hand-edit a block before passing it on.
**Point the operator at `paste=`, not at `dir=` + `command=`.** Those two exist
for a driver, which cd's the pane itself and then types the command; `paste=` is
the same thing already joined, and it is the only line a human should ever copy.
Telling someone to "cd to `dir=` and then paste `command=`" asks them to assemble
two fields by hand, which is exactly how a session gets started in the wrong repo.
A block with `command_missing=` has no `paste=` at all — say so for those repos
rather than letting the operator hunt for a line that is not there.
## Coverage gaps you must name
A ranking is only as honest as its inputs, and two gaps are invisible in the answer

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.16.0"
version: "0.17.0"
---
# coord-send — natural-language front door for inter-repo messages

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.16.0"
version: "0.17.0"
---
# route — what the next session should run with