fix(board): track an open HTML comment through to its closer

Skipping lines that START with the comment opener is not the same as
skipping a comment. Every continuation line of a wrapped comment under
the NESTE heading still looked like prose, so the excerpt became comment
internals instead of the next step. Both the board line and the route
line wrap easily; measured, 1 of 28 repos was affected, with the symptom
held down only by the convention of keeping those comments one-line.

An open comment is now tracked to its closer, and a NESTE block that is
nothing but a comment reports an empty block rather than promoting the
comment's own text.

Two limits are the format, not leftovers: an HTML comment body may not
contain the closer at all, so a rationale quoting it still ends its own
comment early.

The fixture is the real shape - one-line, wrapped, one-line, prose -
because a lone wrapped comment passes even with a flag that never
resets, and the continuation marker sits at the start of its line
because the table's 38-char cut would otherwise truncate it away and
the check would pass against the broken code too.

board-selftest 138 -> 142.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014K262DRWBJzEpWoMsBjB8J
This commit is contained in:
Kjell Tore Guttormsen 2026-08-03 09:53:25 +02:00
commit 123d40e4b4
10 changed files with 96 additions and 10 deletions

View file

@ -1,6 +1,6 @@
{
"name": "repo-mailbox",
"version": "0.20.0",
"version": "0.20.1",
"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,33 @@ 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.20.1] - 2026-08-03
### Fixed
- **A multi-line HTML comment under the NESTE heading no longer leaks into the
next-step excerpt.** `board.sh` skipped lines that *started* with the comment
opener, which is not the same thing as skipping a comment: every continuation
line of a wrapped comment still looked like prose, so the excerpt became
comment internals — `scope=local; rationale=...` where the next step should
have been. An open comment is now tracked through to its closer. Both the
board line and the route line wrap easily, and the affected repo was this one
(1 of 28 measured), where the symptom was held down only by the convention of
writing those comments on a single line.
Two limits are the format, not leftovers. An HTML comment body may not contain
`-->` at all, so a route rationale that quotes the closer still terminates its
own comment early — no fix here can change that. And a NESTE block consisting
of nothing but a comment now reports an empty block rather than promoting the
comment's own text, which is what it always should have said.
`board-selftest.sh` 138 → 142 checks. The fixture is the real shape on
purpose — one-line comment, wrapped comment, one-line comment, prose — because
a lone wrapped comment would pass even with an open-comment flag that never
resets. The continuation marker sits at the start of its line for the same
reason: further in, the table's 38-character cut would truncate it away and
the check would pass against the broken code too.
## [0.20.0] - 2026-08-03
### Changed

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` (138 checks).
STATE.md and no mailbox. Pinned by `board-selftest.sh` (142 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
@ -287,7 +287,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 (138/138) and
`bash scripts/board-selftest.sh` must exit 0 (142/142) 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.20.0-blue)
![Version](https://img.shields.io/badge/version-0.20.1-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)
@ -151,7 +151,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 # 138 checks against a throwaway repo tree
bash scripts/board-selftest.sh # 142 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.20.0",
"version": "0.20.1",
"private": true,
"type": "module",
"engines": {

View file

@ -366,6 +366,55 @@ check "NESTE marker anchored to heading, not any content line mentioning it" $?
printf '%s' "$NESTE_OUT" | grep -qE 'repo-neste-lookalike.*tilfeldig linje'
[ $? -ne 0 ]; check "content line mentioning NESTE is not mistaken for the marker" $?
# A MULTI-LINE HTML comment under NESTE must be skipped THROUGH TO ITS CLOSER,
# not just on its opening line. Skipping only lines that START with the opener
# leaves every continuation line looking like prose, so the excerpt becomes
# comment internals - measured on the real tree, this repo was 1 of 28 affected
# and held the symptom down by keeping its comments one-line.
# The fixture is the REAL shape deliberately: one-line, multi-line, one-line,
# prose. A lone multi-line comment would prove enter-and-exit but not that the
# open-comment flag RESETS - a flag stuck on would still pass that weaker
# fixture by falling through to the same prose.
# NOTE: an HTML comment body cannot contain '-->' at all (that is the format,
# not a board.sh limit), so a rationale that quotes the closer still ends the
# comment early. That constraint survives this fix by construction.
mkrepo "$ROOT/repo-multiline-comment"
{
echo "# STATE - repo-multiline-comment"
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
echo "<!-- board: status=planned; blocked-on=-; next-cost=Opus 5/high -->"
# The marker sits at the START of the continuation line on purpose: the table
# cuts the excerpt at 38 characters, so a marker further in would be truncated
# away and the check would pass against the BROKEN code too.
echo "<!-- route: path=known; verification=strong; reversibility=cheap;"
echo " FORTSETTELSESLINJE som ikke er noe neste steg;"
echo " scope=local; rationale=brer seg over flere linjer. -->"
echo "<!-- route-last: model=Opus 5; effort=high; completed=yes -->"
echo "Det ekte neste steget staar under kommentarblokken."
} > "$ROOT/repo-multiline-comment/STATE.md"
ML_OUT="$("$BOARD" --roots "$ROOT" 2>/dev/null)"
printf '%s' "$ML_OUT" | grep -qE 'repo-multiline-comment.*ekte neste steget'
check "multi-line HTML comment skipped through to its closer" $?
printf '%s' "$ML_OUT" | grep -q 'FORTSETTELSESLINJE'
[ $? -ne 0 ]; check "comment continuation line never becomes the NESTE excerpt" $?
# The board line still parses: it is read by its own anchored grep, so the
# excerpt fix must not disturb it.
printf '%s' "$ML_OUT" | grep -qE 'repo-multiline-comment.*planned'
check "board line still parsed alongside a wrapped route comment" $?
# A NESTE block that is NOTHING BUT a multi-line comment has no next step, and
# must say so rather than promote comment internals to the excerpt.
mkrepo "$ROOT/repo-comment-only"
{
echo "# STATE - repo-comment-only"
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
echo "<!-- route: path=known; verification=strong;"
echo " scope=local; rationale=BARE EN KOMMENTAR her. -->"
} > "$ROOT/repo-comment-only/STATE.md"
CO_OUT="$("$BOARD" --roots "$ROOT" 2>/dev/null)"
printf '%s' "$CO_OUT" | grep -qE 'repo-comment-only.*tom NESTE-blokk'
check "NESTE block of only a multi-line comment reports an empty block" $?
"$BOARD" --help >/dev/null 2>&1; check "--help exits 0" $?
# Unreadable root is a no-op, not a crash.

View file

@ -274,11 +274,21 @@ printf '%s\n' "$REPOS" | while IFS= read -r d; do
# the heading itself; strip markdown bold/bullet noise. Anchored to the
# heading form (measured 26/27 real repos, decided 2026-08-02) so a prose
# line that merely mentions the word is never mistaken for the marker.
#
# A comment is tracked to its CLOSER, not just recognised on its opening line.
# Skipping only lines that start with the opener left every continuation line
# of a wrapped comment looking like prose, so the excerpt became comment
# internals - the board line and the route line both wrap easily, and this
# repo was the one of 28 that hit it, held down only by writing them on one
# line. What this does NOT do, and cannot: an HTML comment body may not
# contain the closer at all, so a route rationale that quotes it still ends
# its own comment early. That is the format, not a defect left here.
neste="$(awk '
/^#+[[:space:]].*NESTE/ { flag=1; next }
flag {
if (incomment) { if ($0 ~ /-->/) incomment=0; next }
if ($0 ~ /^[[:space:]]*$/) next
if ($0 ~ /^[[:space:]]*<!--/) next
if ($0 ~ /^[[:space:]]*<!--/) { if ($0 !~ /-->/) incomment=1; next }
if ($0 ~ /^#/) next
print; exit
}' "$state" 2>/dev/null \

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