Replaces the weighted score shipped in 0.19.0 with five lookups: chain-root credit, unhandled inbox, planned, in-progress, undeclared status. Within a group: that group's own quantity, then a Sonnet next-cost, then oldest plan. The score's objection is accepted, not forgotten, and is written into board.sh and CLAUDE.md so a later session reads it as decided rather than as an unfixed defect: a group order cannot express "owes one message AND releases two others" as one quantity. What the score could not do was hold still for the format's second consumer - re-tuning one weight against another silently reorders a parser in another repo, and no test here can catch that. planned now ranks above in-progress, inverted by the same decision: converting a decision into motion is the slow step; live work is already moving. Debt stays uncapped and never excluded. One group below chain-root credit is not the cap declined at 0.19.0 - the debtor keeps its tab, its most-owed-first position, and its why=inbox:N. Pinned by a discriminating fixture the score would fail: a root releasing one repo outranks a repo owing four. board-selftest 134 -> 138. Suite 183 + 138 + 73 = 394. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y6ULuFCPMNYAPNN3pAjsXQ
1101 lines
56 KiB
Bash
Executable file
1101 lines
56 KiB
Bash
Executable file
#!/bin/bash
|
|
# board-selftest.sh - prove board.sh end-to-end against a throwaway repo tree
|
|
# and a throwaway mailbox (never touches ~/repos or ~/.claude/coord). Re-run
|
|
# after any edit to board.sh. ASCII only, bash 3.2 safe.
|
|
#
|
|
# Multibyte STATE.md content (em-dash, the NESTE pointing-hand) is generated
|
|
# with printf octal escapes so this script's own source stays pure ASCII - a
|
|
# literal em-dash in shell source has crashed bash 3.2 under `set -u` before.
|
|
set -u
|
|
export LC_ALL=C
|
|
|
|
DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
BOARD="$DIR/board.sh"
|
|
|
|
ROOT="$(mktemp -d)"
|
|
CLAUDE_COORD_DIR="$(mktemp -d)"
|
|
export CLAUDE_COORD_DIR
|
|
cleanup() { /bin/rm -rf "$ROOT" "$CLAUDE_COORD_DIR" 2>/dev/null; }
|
|
trap cleanup EXIT
|
|
|
|
PASS=0; FAIL=0
|
|
check() { if [ "$2" -eq 0 ]; then PASS=$((PASS+1)); echo " ok - $1"; else FAIL=$((FAIL+1)); echo " FAIL - $1"; fi; }
|
|
|
|
# Multibyte building blocks (octal escapes keep this source ASCII).
|
|
EMDASH="$(printf '\342\200\224')"
|
|
HAND="$(printf '\360\237\221\211')"
|
|
OSLASH="$(printf '\303\270')"
|
|
|
|
mkrepo() { mkdir -p "$1" && git -C "$1" init -q 2>/dev/null; }
|
|
|
|
echo "board-selftest (root: $ROOT, mailbox: $CLAUDE_COORD_DIR)"
|
|
|
|
# --- 0. Empty root: no repos, still exits cleanly. ---
|
|
out0="$("$BOARD" --roots "$ROOT" 2>/dev/null)"; rc=$?
|
|
[ "$rc" -eq 0 ]; check "empty root exits 0" $?
|
|
|
|
# --- Fixture tree ---------------------------------------------------------
|
|
|
|
# repo-a: full board line, in-progress, cheap next step, unhandled inbox.
|
|
mkrepo "$ROOT/repo-a"
|
|
{
|
|
echo "# STATE - repo-a"
|
|
echo ""
|
|
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
|
|
echo "<!-- board: status=in-progress; blocked-on=-; next-cost=sonnet/xhigh -->"
|
|
printf '**Lukk to MAJOR fra reviewen** %s deretter S3.3 concurrent fan-out.\n' "$EMDASH"
|
|
} > "$ROOT/repo-a/STATE.md"
|
|
|
|
# repo-b: STATE + NESTE prose but NO board line (the un-backfilled majority).
|
|
mkrepo "$ROOT/repo-b"
|
|
{
|
|
echo "# STATE - repo-b"
|
|
echo ""
|
|
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
|
|
echo ""
|
|
printf '> Kj%sr forskningstema 1 headless %s hard gate for motorens arkitektur.\n' "$OSLASH" "$EMDASH"
|
|
} > "$ROOT/repo-b/STATE.md"
|
|
|
|
# repo-c: no STATE.md at all (dormant class).
|
|
mkrepo "$ROOT/repo-c"
|
|
|
|
# repo-d: dirty working tree.
|
|
mkrepo "$ROOT/repo-d"
|
|
{
|
|
echo "# STATE - repo-d"
|
|
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
|
|
echo "<!-- board: status=deferred; blocked-on=-; next-cost=sonnet/high -->"
|
|
echo "Parkert med vilje."
|
|
} > "$ROOT/repo-d/STATE.md"
|
|
# Commit STATE.md first, so the dirty count proves board counts UNCOMMITTED
|
|
# files only (2 junk files) rather than every file in a fresh tree.
|
|
git -C "$ROOT/repo-d" add STATE.md >/dev/null 2>&1
|
|
git -C "$ROOT/repo-d" -c user.email=t@t -c user.name=t commit -qm init >/dev/null 2>&1
|
|
: > "$ROOT/repo-d/junk1.txt"
|
|
: > "$ROOT/repo-d/junk2.txt"
|
|
|
|
# repo-e: malformed status token (must be flagged, never silently accepted).
|
|
mkrepo "$ROOT/repo-e"
|
|
{
|
|
echo "# STATE - repo-e"
|
|
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
|
|
echo "<!-- board: status=aktiv; blocked-on=-; next-cost=opus/high -->"
|
|
echo "Ugyldig token."
|
|
} > "$ROOT/repo-e/STATE.md"
|
|
|
|
# repo-g: prose containing a 'board:'-lookalike ABOVE the real board line.
|
|
# An unanchored substring grep would match 'dashboard:' first and mis-parse the
|
|
# whole repo; the board line is defined as living under the NESTE heading.
|
|
mkrepo "$ROOT/repo-g"
|
|
{
|
|
echo "# STATE - repo-g"
|
|
echo "Vi bygde et dashboard: status=done ble diskutert i forrige runde."
|
|
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
|
|
echo "<!-- board: status=planned; blocked-on=-; next-cost=opus/xhigh -->"
|
|
echo "Ekte neste steg her."
|
|
} > "$ROOT/repo-g/STATE.md"
|
|
|
|
# repo-h: next-cost written the way the model rubric actually names the models,
|
|
# with a space and capitals ("Sonnet 5/xhigh"). This is spec-conformant
|
|
# (<modell>/<effort>) and must parse - a lowercase-only pattern silently drops
|
|
# it to "?" and hides the cost column exactly where it is needed.
|
|
mkrepo "$ROOT/repo-h"
|
|
{
|
|
echo "# STATE - repo-h"
|
|
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
|
|
echo "<!-- board: status=in-progress; blocked-on=-; next-cost=Sonnet 5/xhigh -->"
|
|
echo "Neste steg her."
|
|
} > "$ROOT/repo-h/STATE.md"
|
|
|
|
# polyrepo/: NOT a git repo itself, but holds git repos one level down.
|
|
mkdir -p "$ROOT/polyrepo"
|
|
mkrepo "$ROOT/polyrepo/plug-x"
|
|
{
|
|
echo "# STATE - plug-x"
|
|
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
|
|
echo "<!-- board: status=blocked; blocked-on=repo-a; next-cost=opus/high -->"
|
|
printf 'Venter p%s amendment-pakken.\n' "$EMDASH"
|
|
} > "$ROOT/polyrepo/plug-x/STATE.md"
|
|
|
|
# repo-wt + wt-feature: a git WORKTREE (and a submodule) has .git as a FILE,
|
|
# not a directory. A `git worktree add <root>/feature-x` yields a sibling
|
|
# directory that can CARRY its own STATE.md. Testing discovery for a .git
|
|
# DIRECTORY only drops it silently - the same silent-loss class as the V6
|
|
# marker fix.
|
|
mkrepo "$ROOT/repo-wt"
|
|
{
|
|
echo "# STATE - repo-wt"
|
|
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
|
|
echo "<!-- board: status=done; blocked-on=-; next-cost=sonnet/high -->"
|
|
printf 'Hovedtreet %s ferdig.\n' "$EMDASH"
|
|
} > "$ROOT/repo-wt/STATE.md"
|
|
git -C "$ROOT/repo-wt" add STATE.md >/dev/null 2>&1
|
|
git -C "$ROOT/repo-wt" -c user.email=t@t -c user.name=t commit -qm init >/dev/null 2>&1
|
|
git -C "$ROOT/repo-wt" -c user.email=t@t -c user.name=t \
|
|
worktree add -q -b feature-x "$ROOT/wt-feature" >/dev/null 2>&1
|
|
# Distinct board line, so the check proves board read the STATE INSIDE the
|
|
# worktree rather than matching the source repo's row by accident.
|
|
{
|
|
echo "# STATE - wt-feature"
|
|
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
|
|
echo "<!-- board: status=in-progress; blocked-on=-; next-cost=fable/xhigh -->"
|
|
printf 'Arbeid i worktree %s eget neste steg.\n' "$EMDASH"
|
|
} > "$ROOT/wt-feature/STATE.md"
|
|
|
|
# repo-old: fresh STATE.md, last COMMIT 400 days old. This is the case the
|
|
# fixture tree was missing entirely: with age read off STATE.md's mtime, a repo
|
|
# whose work stopped long ago looks exactly as alive as one touched this
|
|
# morning, and no column contradicts it.
|
|
mkrepo "$ROOT/repo-old"
|
|
{
|
|
echo "# STATE - repo-old"
|
|
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
|
|
echo "<!-- board: status=in-progress; blocked-on=-; next-cost=Opus 5/high -->"
|
|
echo "Ser levende ut, men ingen commit paa lenge."
|
|
} > "$ROOT/repo-old/STATE.md"
|
|
OLD_EPOCH=$(( $(date +%s) - 400 * 86400 ))
|
|
git -C "$ROOT/repo-old" add STATE.md >/dev/null 2>&1
|
|
# %ct is the COMMITTER date. `--date=` sets the AUTHOR date only and would
|
|
# leave %ct at now, so the fixture would be backdated in name only.
|
|
GIT_AUTHOR_DATE="@$OLD_EPOCH +0000" GIT_COMMITTER_DATE="@$OLD_EPOCH +0000" \
|
|
git -C "$ROOT/repo-old" -c user.email=t@t -c user.name=t commit -qm init >/dev/null 2>&1
|
|
|
|
# repo-stale: the mirror image - STATE.md untouched since a fixed past date,
|
|
# last commit today. The pair pins that the two ages are independent readings
|
|
# and that neither of them silently became the sort key.
|
|
mkrepo "$ROOT/repo-stale"
|
|
{
|
|
echo "# STATE - repo-stale"
|
|
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
|
|
echo "<!-- board: status=in-progress; blocked-on=-; next-cost=Opus 5/high -->"
|
|
echo "Gammel STATE, fersk commit."
|
|
} > "$ROOT/repo-stale/STATE.md"
|
|
git -C "$ROOT/repo-stale" add STATE.md >/dev/null 2>&1
|
|
git -C "$ROOT/repo-stale" -c user.email=t@t -c user.name=t commit -qm init >/dev/null 2>&1
|
|
touch -t 202601010000 "$ROOT/repo-stale/STATE.md"
|
|
|
|
# plain-dir/: no git repo anywhere under it - must be ignored entirely.
|
|
mkdir -p "$ROOT/plain-dir/sub"
|
|
echo "hei" > "$ROOT/plain-dir/sub/file.txt"
|
|
|
|
# Coord fixture: 3 unhandled for repo-a, 1 archived (must not be counted).
|
|
mkdir -p "$CLAUDE_COORD_DIR/repo-a/inbox" "$CLAUDE_COORD_DIR/repo-a/archive"
|
|
for n in 1 2 3; do echo "msg" > "$CLAUDE_COORD_DIR/repo-a/inbox/2026-msg$n-from-x.md"; done
|
|
echo "old" > "$CLAUDE_COORD_DIR/repo-a/archive/2026-old-from-x.md"
|
|
|
|
# repo-owes: unhandled inbox AND a route line, so the briefing can derive the
|
|
# EXACT startup command - advisor flag included - from the repo's own four
|
|
# traits instead of guessing from next-cost alone. Its NESTE runs deliberately
|
|
# past the table's 38-character column: carrying that line whole is the reason
|
|
# the briefing exists at all.
|
|
mkrepo "$ROOT/repo-owes"
|
|
{
|
|
echo "# STATE - repo-owes"
|
|
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
|
|
echo "<!-- board: status=in-progress; blocked-on=-; next-cost=Sonnet 5/high -->"
|
|
echo "<!-- route: path=known; verification=strong; reversibility=cheap; scope=local; rationale=x -->"
|
|
echo "Svar org-ops om badge-formatet, deretter bump katalogens ref og verifiser check-versions."
|
|
} > "$ROOT/repo-owes/STATE.md"
|
|
mkdir -p "$CLAUDE_COORD_DIR/repo-owes/inbox"
|
|
echo "msg" > "$CLAUDE_COORD_DIR/repo-owes/inbox/2026-msg1-from-y.md"
|
|
|
|
# repo-typo: a route line that EXISTS but carries a value outside route.sh's
|
|
# closed set. This is the case that actually occurs as route lines are written
|
|
# by hand across repos, and it is distinct from having no route line at all:
|
|
# route.sh exits 2, the command comes back empty, and the briefing must say so
|
|
# rather than print a bare "$ " that reads as a runnable command.
|
|
mkrepo "$ROOT/repo-typo"
|
|
{
|
|
echo "# STATE - repo-typo"
|
|
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
|
|
echo "<!-- board: status=planned; blocked-on=-; next-cost=Opus 5/high -->"
|
|
echo "<!-- route: path=knwon; verification=strong; reversibility=cheap; scope=local; rationale=x -->"
|
|
echo "Neste steg for repo-typo."
|
|
} > "$ROOT/repo-typo/STATE.md"
|
|
mkdir -p "$CLAUDE_COORD_DIR/repo-typo/inbox"
|
|
echo "msg" > "$CLAUDE_COORD_DIR/repo-typo/inbox/2026-msg1-from-y.md"
|
|
|
|
# --- Focus fixtures --------------------------------------------------------
|
|
# The topic marker grammar is the register's, not this repo's: a STATE.md line
|
|
# of the form `<slug>: <status-token> <emdash> prose`. board.sh only ever READS
|
|
# it, and reads it from STATE.md alone - it must never start opening
|
|
# register.md to learn the vocabulary, so the slug set is whatever the scanned
|
|
# STATE.md files declare.
|
|
#
|
|
# repo-focus-plain declares the slug in the bare form.
|
|
mkrepo "$ROOT/repo-focus-plain"
|
|
{
|
|
echo "# STATE - repo-focus-plain"
|
|
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
|
|
echo "<!-- board: status=in-progress; blocked-on=-; next-cost=Opus 5/high -->"
|
|
echo "Neste steg for repo-focus-plain."
|
|
echo ""
|
|
printf -- '- demo-topic: in-progress %s wired at the boundary, ingen apen trad\n' "$EMDASH"
|
|
} > "$ROOT/repo-focus-plain/STATE.md"
|
|
|
|
# repo-focus-bold: the SAME declaration wrapped in bold and backticks. This is
|
|
# the class the register's own grep loses, and it was the most consequential
|
|
# find in the measurement that produced the decision - a heavy consumer made
|
|
# invisible by two asterisks. A reader that only accepts the bare form
|
|
# reproduces that defect.
|
|
mkrepo "$ROOT/repo-focus-bold"
|
|
{
|
|
echo "# STATE - repo-focus-bold"
|
|
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
|
|
echo "<!-- board: status=planned; blocked-on=-; next-cost=Sonnet 5/high -->"
|
|
echo "Neste steg for repo-focus-bold."
|
|
echo ""
|
|
printf -- '- **`demo-topic`**: planned %s pin bumpet, ikke startet\n' "$EMDASH"
|
|
} > "$ROOT/repo-focus-bold/STATE.md"
|
|
|
|
# repo-focus-boldstatus: the bold is on the STATUS token, not the slug. Found in
|
|
# the real tree AFTER the first release, which is the point: accepting bold on
|
|
# one half of the line and not the other is not a rule, it is an accident of
|
|
# which example was in front of us. The register's own grep loses this form too,
|
|
# so the repo writing it looks silent while having declared.
|
|
mkrepo "$ROOT/repo-focus-boldstatus"
|
|
{
|
|
echo "# STATE - repo-focus-boldstatus"
|
|
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
|
|
echo "<!-- board: status=planned; blocked-on=-; next-cost=Sonnet 5/high -->"
|
|
echo "Neste steg for repo-focus-boldstatus."
|
|
echo ""
|
|
echo "demo-topic: **done** (1.8.1). akse-B naadd, ingen apen trad"
|
|
} > "$ROOT/repo-focus-boldstatus/STATE.md"
|
|
|
|
# repo-focus-mentions: names the slug in prose and declares NOTHING. This is
|
|
# the held-back class the report exists for. It is in-progress with a live
|
|
# next step, so the cutoff genuinely removes a repo that would otherwise be a
|
|
# tab - which is what makes silence about it a lie rather than a rounding.
|
|
mkrepo "$ROOT/repo-focus-mentions"
|
|
{
|
|
echo "# STATE - repo-focus-mentions"
|
|
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
|
|
echo "<!-- board: status=in-progress; blocked-on=-; next-cost=Opus 5/high -->"
|
|
echo "Vi pinner demo-topic i byggefila, men har aldri skrevet en markorlinje."
|
|
} > "$ROOT/repo-focus-mentions/STATE.md"
|
|
|
|
# repo-focus-other: declares a DIFFERENT slug sharing one hyphen-part, so
|
|
# "topic" resolves to two slugs and "demo" to one. Ambiguity must widen the
|
|
# answer and say so, never silently pick a winner.
|
|
mkrepo "$ROOT/repo-focus-other"
|
|
{
|
|
echo "# STATE - repo-focus-other"
|
|
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
|
|
echo "<!-- board: status=planned; blocked-on=-; next-cost=Sonnet 5/high -->"
|
|
echo "Neste steg for repo-focus-other."
|
|
echo ""
|
|
printf -- '- other-topic: planned %s ikke startet\n' "$EMDASH"
|
|
} > "$ROOT/repo-focus-other/STATE.md"
|
|
|
|
OUT="$("$BOARD" --roots "$ROOT" 2>/dev/null)"
|
|
|
|
# --- 1. Discovery ---------------------------------------------------------
|
|
printf '%s' "$OUT" | grep -q 'repo-a'; check "discovers top-level git repo" $?
|
|
printf '%s' "$OUT" | grep -q 'plug-x'; check "discovers nested polyrepo git repo (depth 2)" $?
|
|
printf '%s' "$OUT" | grep -q 'repo-c'; check "lists git repo without STATE.md" $?
|
|
printf '%s' "$OUT" | grep -q 'plain-dir'; [ $? -ne 0 ]; check "ignores non-git directory tree" $?
|
|
printf '%s' "$OUT" | grep -q 'polyrepo'; [ $? -ne 0 ]; check "polyrepo container itself is not listed as a repo" $?
|
|
# Guard the fixture itself: if git ever stops writing a .git FILE for
|
|
# worktrees, the next two checks would pass for the wrong reason.
|
|
[ -f "$ROOT/wt-feature/.git" ]; check "fixture: worktree .git is a FILE, not a dir" $?
|
|
printf '%s' "$OUT" | grep -q 'wt-feature'; check "discovers git worktree (.git is a file)" $?
|
|
printf '%s' "$OUT" | grep -qE 'wt-feature.*fable/xhigh'
|
|
check "reads STATE.md from inside the worktree, not the source repo" $?
|
|
|
|
# --- 2. Board line parsing (the B field) ----------------------------------
|
|
printf '%s' "$OUT" | grep -q 'in-progress'; check "parses status token from board line" $?
|
|
printf '%s' "$OUT" | grep -q 'sonnet/xhigh'; check "parses next-cost from board line" $?
|
|
printf '%s' "$OUT" | grep -qE 'repo-h.*Sonnet 5/xhigh'
|
|
check "next-cost accepts spaces and capitals (rubric model names)" $?
|
|
printf '%s' "$OUT" | grep -qE 'plug-x.*repo-a'
|
|
check "blocked repo names its blocker on its own row" $?
|
|
printf '%s' "$OUT" | grep -q 'deferred'; check "distinguishes deferred from blocked" $?
|
|
|
|
# --- 3. Malformed input is flagged, not swallowed -------------------------
|
|
printf '%s' "$OUT" | grep -qi 'malformed\|ugyldig\|invalid'; check "malformed status token is flagged" $?
|
|
|
|
# A 'dashboard:' lookalike earlier in the file must not win over the real line.
|
|
printf '%s' "$OUT" | grep -qE 'repo-g.*planned.*opus/xhigh'
|
|
check "board-line parse ignores 'board:' lookalikes in prose" $?
|
|
|
|
# --- 4. Heuristic fallback when board line is absent -----------------------
|
|
printf '%s' "$OUT" | grep -q 'repo-b'; check "repo without board line still listed" $?
|
|
printf '%s' "$OUT" | grep -qi 'forskningstema\|headless'; check "shows NESTE excerpt when board line absent" $?
|
|
# Markdown noise must be stripped: a blockquoted NESTE line renders as prose.
|
|
printf '%s' "$OUT" | grep -qE 'repo-b.*[[:space:]]>'; [ $? -ne 0 ]
|
|
check "blockquote marker stripped from NESTE excerpt" $?
|
|
|
|
# --- 5. Git + coord signals ------------------------------------------------
|
|
printf '%s' "$OUT" | grep -qE 'repo-d.*[^0-9]2([^0-9]|$)'; check "reports dirty file count" $?
|
|
printf '%s' "$OUT" | grep -qE 'repo-a.*[^0-9]3([^0-9]|$)'; check "reports unhandled coord inbox count (archive excluded)" $?
|
|
|
|
# --- 6. Bucketing ----------------------------------------------------------
|
|
printf '%s' "$OUT" | grep -qi 'blocked'; check "blocked repo surfaced" $?
|
|
printf '%s' "$OUT" | grep -qiE 'dormant|sovende|uten STATE'; check "STATE-less repos bucketed separately" $?
|
|
|
|
# --- 7. Robustness ---------------------------------------------------------
|
|
# Multibyte prose must not crash the reader nor emit split-character garbage.
|
|
[ -n "$OUT" ]; check "produces output over multibyte STATE prose" $?
|
|
printf '%s' "$OUT" | iconv -f UTF-8 -t UTF-8 >/dev/null 2>&1
|
|
check "output is valid UTF-8 (no split multibyte truncation)" $?
|
|
|
|
# A STATE.md with a NESTE heading and nothing after it must not hang or crash.
|
|
mkrepo "$ROOT/repo-f"
|
|
{ echo "# STATE - repo-f"; printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"; } > "$ROOT/repo-f/STATE.md"
|
|
"$BOARD" --roots "$ROOT" >/dev/null 2>&1; check "empty NESTE block does not crash" $?
|
|
|
|
# The NESTE marker must be anchored to the heading form, not any line
|
|
# containing the word. A prose line mentioning "NESTE" earlier in the file
|
|
# must not be mistaken for the marker - that would make the excerpt the line
|
|
# AFTER the false match instead of the real next step under the real heading.
|
|
mkrepo "$ROOT/repo-neste-lookalike"
|
|
{
|
|
echo "# STATE - repo-neste-lookalike"
|
|
echo "## Fakta"
|
|
echo "Noe forklaring om NESTE-formen som ble diskutert i gaar."
|
|
echo "Dette er en tilfeldig linje som IKKE er det ekte neste steget."
|
|
echo ""
|
|
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
|
|
echo "<!-- board: status=in-progress; blocked-on=-; next-cost=sonnet/xhigh -->"
|
|
echo "Det ekte neste steget er aa fikse boardskriptet."
|
|
} > "$ROOT/repo-neste-lookalike/STATE.md"
|
|
NESTE_OUT="$("$BOARD" --roots "$ROOT" 2>/dev/null)"
|
|
printf '%s' "$NESTE_OUT" | grep -qE 'repo-neste-lookalike.*ekte neste steget'
|
|
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" $?
|
|
|
|
"$BOARD" --help >/dev/null 2>&1; check "--help exits 0" $?
|
|
|
|
# Unreadable root is a no-op, not a crash.
|
|
"$BOARD" --roots "$ROOT/does-not-exist" >/dev/null 2>&1; check "missing root is a clean no-op" $?
|
|
|
|
# --- 8. The documented next-cost form IS the convention --------------------
|
|
# The cost field is free text on purpose: the parser reads to ';' or '-->' so
|
|
# the rubric's spacing survives, and the fixtures above deliberately vary the
|
|
# spelling to pin that leniency. But leniency on the read side means the ONLY
|
|
# thing holding the field uniform is what --help prescribes, and --help once
|
|
# prescribed a versionless `sonnet/xhigh` while the global rubric spelled
|
|
# `Sonnet 5 / xhigh`. Two specs for one field: measured across the real tree,
|
|
# not one board line used ours, and eight distinct strings were in circulation.
|
|
# Pinned here because cleaning the DATA cannot fix that - a spec that
|
|
# contradicts the rubric regenerates the divergence every session.
|
|
HELP="$("$BOARD" --help 2>/dev/null)"
|
|
printf '%s' "$HELP" | grep -q 'next-cost=Sonnet 5/xhigh'
|
|
check "--help prescribes the canonical next-cost form" $?
|
|
# The versionless form is what the rubric does NOT use; no example may show it.
|
|
if printf '%s' "$HELP" | grep -qE '(sonnet|opus|fable)/(high|xhigh)'; then rc=1; else rc=0; fi
|
|
check "--help shows no versionless model example" "$rc"
|
|
|
|
# --- 9. SISTE: last commit, read for EVERY repo ----------------------------
|
|
# One column, one meaning. ALDER answers "when was the plan last touched" and
|
|
# is blank where there is no plan; SISTE answers "when did anything last land"
|
|
# and is read for every repo. Before this, a single ALDER column carried both
|
|
# readings depending on which branch a repo fell into, so a repo with a STATE.md
|
|
# could go a year without a commit and show nothing at all.
|
|
[ "$(git -C "$ROOT/repo-old" log -1 --format=%ct 2>/dev/null)" = "$OLD_EPOCH" ]
|
|
check "fixture: repo-old's COMMITTER date is actually backdated" $?
|
|
|
|
printf '%s' "$OUT" | grep -q 'SISTE'; check "table has a SISTE column" $?
|
|
|
|
printf '%s\n' "$OUT" | grep -qE '^repo-old .*[[:space:]]0d[[:space:]]+40[01]d[[:space:]]'
|
|
check "fresh STATE.md + old commit: ALDER stays 0d, SISTE shows the 400 days" $?
|
|
|
|
printf '%s\n' "$OUT" | grep -qE '^repo-stale .*[[:space:]][1-9][0-9]*d[[:space:]]+0d[[:space:]]'
|
|
check "old STATE.md + fresh commit: the two ages read independently" $?
|
|
|
|
# A repo with no commits has no last commit to report. A day count there would
|
|
# be a fabricated reading, and the pre-existing -1d was exactly that.
|
|
# The -n guard is load-bearing: an absent row would leave crow empty, and an
|
|
# empty string trivially contains no day count - the check would pass by
|
|
# finding nothing rather than by reading the row.
|
|
crow="$(printf '%s\n' "$OUT" | grep '^repo-c ')"
|
|
if [ -n "$crow" ] && ! printf '%s' "$crow" | grep -qE '[0-9]+d'; then rc=0; else rc=1; fi
|
|
check "repo with no commits reports no day count in either age column" "$rc"
|
|
|
|
# The four ranking rules stay four: SISTE is evidence the operator reads, never
|
|
# an input to the order. If it had become the sort key, repo-old (400d) would
|
|
# fall behind repo-stale (0d) instead of ahead of it.
|
|
n_old="$(printf '%s\n' "$OUT" | grep -n '^repo-old ' | cut -d: -f1)"
|
|
n_stale="$(printf '%s\n' "$OUT" | grep -n '^repo-stale ' | cut -d: -f1)"
|
|
[ -n "$n_old" ] && [ -n "$n_stale" ] && [ "$n_old" -lt "$n_stale" ]
|
|
check "SISTE is evidence, not a ranking input (order still follows ALDER)" $?
|
|
|
|
# --- 8. Briefing rendering (--brief) --------------------------------------
|
|
# A second rendering of the SAME scan, never a second scan: the briefing
|
|
# answers "who is waiting on me and what does answering cost", where the table
|
|
# answers "what is the state of every repo".
|
|
BRIEF="$("$BOARD" --roots "$ROOT" --brief 2>/dev/null)"
|
|
|
|
printf '%s' "$BRIEF" | grep -q 'repo-owes'
|
|
check "brief includes a repo with an unhandled inbox" $?
|
|
|
|
printf '%s' "$BRIEF" | grep -q 'repo-b'; [ $? -ne 0 ]
|
|
check "brief excludes a repo that owes nothing" $?
|
|
|
|
# The table cuts NESTE at 38 characters. The briefing carries the whole line,
|
|
# so the tail past the cut is the thing being proved here.
|
|
printf '%s' "$BRIEF" | grep -q 'check-versions'
|
|
check "brief prints the full NESTE line, not the 38-char table excerpt" $?
|
|
|
|
# Derived by CALLING route.sh with the repo's own four traits - deliberately
|
|
# not spelled out in board.sh - so the rubric keeps exactly one copy. Row 1
|
|
# carries the advisor, which is what makes the quota fallback safe to take.
|
|
printf '%s' "$BRIEF" | grep -q 'claude --model sonnet --effort high --advisor opus'
|
|
check "brief derives the exact startup command from the repo's route line" $?
|
|
|
|
# The command must start its OWN line. `fold` copies its input's missing
|
|
# trailing newline, which ran the command onto the tail of the NESTE prose and
|
|
# produced a briefing whose commands could not be copied - caught by eye
|
|
# against the real tree, which is exactly what a check is for.
|
|
printf '%s' "$BRIEF" | grep -qE '^ \$ claude --model'
|
|
check "startup command starts on its own line, not appended to NESTE prose" $?
|
|
|
|
# A route line that parses to a value route.sh rejects must degrade the same
|
|
# way a missing one does. Silence here would print a bare '$ ' - the shape of a
|
|
# runnable command, carrying nothing - which is worse than an absent command
|
|
# because it looks copied-and-pasteable.
|
|
printf '%s' "$BRIEF" | grep -A4 'repo-typo' | grep -qi 'route'
|
|
check "unparseable route line degrades to a marker, not to an empty command" $?
|
|
|
|
printf '%s' "$BRIEF" | grep -A4 'repo-typo' | grep -qE '^ \$ claude'; [ $? -ne 0 ]
|
|
check "unparseable route line never emits a command line at all" $?
|
|
|
|
# repo-a owes 3 messages and has no route line. A fabricated command would be
|
|
# worse than none: it would read as authoritative while being a guess.
|
|
printf '%s' "$BRIEF" | grep -A4 'repo-a' | grep -qi 'route'
|
|
check "repo owing mail but lacking a route line is marked, never guessed at" $?
|
|
|
|
# Read-only by construction, and the briefing must not erode it. Reading IS
|
|
# delivering in this engine, so a survey that consumed a backlog would be
|
|
# precisely the defect the whole design forbids.
|
|
snap() { find "$1" -type f -exec stat -f '%N %m %z' {} \; 2>/dev/null | sort; }
|
|
b4="$(snap "$CLAUDE_COORD_DIR")"
|
|
"$BOARD" --roots "$ROOT" --brief >/dev/null 2>&1
|
|
[ "$b4" = "$(snap "$CLAUDE_COORD_DIR")" ]
|
|
check "brief writes nothing to the mailbox (reading is delivering)" $?
|
|
|
|
# A mailbox can carry a name the repo scan will NEVER produce: a declared
|
|
# non-git surface (CLAUDE_COORD_REPO, e.g. ~/repos itself) or a checkout
|
|
# outside the scan roots. board.sh discovers git repos, so such a mailbox is
|
|
# invisible to every column it prints - and the briefing exists to answer "who
|
|
# is waiting on you", where dropping one silently is the loss-wearing-the-
|
|
# shape-of-normal class this engine keeps naming. Measured against the real
|
|
# mailbox before this check existed: 11 repos / 21 messages in the briefing
|
|
# against 12 mailboxes / 22 pending in coord-count.
|
|
mkdir -p "$CLAUDE_COORD_DIR/orphan-surface/inbox"
|
|
echo "msg" > "$CLAUDE_COORD_DIR/orphan-surface/inbox/2026-msg1-from-z.md"
|
|
BRIEF2="$("$BOARD" --roots "$ROOT" --brief 2>/dev/null)"
|
|
|
|
printf '%s' "$BRIEF2" | grep -q 'orphan-surface'
|
|
check "brief surfaces a pending mailbox that has no repo in the scan tree" $?
|
|
|
|
# It must be reported as its own class, not silently folded in among repos the
|
|
# scan actually found - there is no STATE.md behind it and so no next step.
|
|
printf '%s' "$BRIEF2" | grep -qi 'utenfor repo-skannen\|uten repo'
|
|
check "orphan mailbox is reported as its own class, not as a scanned repo" $?
|
|
|
|
# --- 9. brief-nightly.sh: the only writer, and it writes atomically -------
|
|
# board.sh stays read-only, so the file write lives in a separate wrapper.
|
|
# Unattended overwrite must never expose a half-written briefing: the wrapper
|
|
# renders to a temp file in the same directory and renames it into place.
|
|
NIGHTLY="$DIR/brief-nightly.sh"
|
|
OUTFILE="$ROOT/briefing.md"
|
|
CLAUDE_BRIEF_FILE="$OUTFILE" BOARD_ROOTS="$ROOT" bash "$NIGHTLY" >/dev/null 2>&1
|
|
check "brief-nightly exits 0" $?
|
|
|
|
[ -f "$OUTFILE" ] && grep -q 'repo-owes' "$OUTFILE"
|
|
check "brief-nightly writes the briefing to CLAUDE_BRIEF_FILE" $?
|
|
|
|
ls "$OUTFILE".tmp.* >/dev/null 2>&1; [ $? -ne 0 ]
|
|
check "brief-nightly leaves no temp file behind" $?
|
|
|
|
# An EMPTY render is a failed render, and must leave the previous briefing
|
|
# standing rather than truncating it to nothing - the failure mode a plain
|
|
# `> file` redirect has, and the reason the temp-then-rename exists. A repo
|
|
# tree where nobody owes anything is NOT this case: that renders a valid,
|
|
# non-empty briefing saying so, and is written normally.
|
|
echo "FORRIGE BRIEFING" > "$OUTFILE"
|
|
CLAUDE_BRIEF_FILE="$OUTFILE" BOARD_ROOTS="$ROOT/does-not-exist" \
|
|
bash "$NIGHTLY" >/dev/null 2>&1
|
|
grep -q 'FORRIGE BRIEFING' "$OUTFILE"
|
|
check "empty render never overwrites the previous briefing" $?
|
|
|
|
# --- 10. Day-plan rendering (--plan) ---------------------------------------
|
|
# A THIRD rendering of the same scan, on exactly the argument --brief was built
|
|
# on. The table answers "what is the state of every repo"; the briefing answers
|
|
# "who is waiting on me"; the plan answers "which repos do I open a tab for
|
|
# today, in what order, with which command". It has TWO consumers - the operator
|
|
# pasting commands, and a separate driver repo consuming the plan - which is why
|
|
# it renders key=value blocks rather than prose: prose would make the FORMAT an
|
|
# API that no test in this repo could hold stable.
|
|
#
|
|
# repo-blocked-owes: blocked AND owing mail. The exclusion of `blocked` is about
|
|
# a repo's OWN next step, which by definition cannot be moved; answering a
|
|
# message is a different axis, and is often precisely what unblocks it. Without
|
|
# this fixture the debt-first rule and the status exclusion never meet, and
|
|
# whichever one was written second would silently win.
|
|
mkrepo "$ROOT/repo-blocked-owes"
|
|
{
|
|
echo "# STATE - repo-blocked-owes"
|
|
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
|
|
echo "<!-- board: status=blocked; blocked-on=repo-a; next-cost=Opus 5/high -->"
|
|
printf 'Venter p%s repo-a, men skylder to svar.\n' "$EMDASH"
|
|
} > "$ROOT/repo-blocked-owes/STATE.md"
|
|
mkdir -p "$CLAUDE_COORD_DIR/repo-blocked-owes/inbox"
|
|
for n in 1 2; do
|
|
echo "msg" > "$CLAUDE_COORD_DIR/repo-blocked-owes/inbox/2026-msg$n-from-w.md"
|
|
done
|
|
|
|
# repo-done-owes: done AND owing mail. Same rule as repo-blocked-owes, but this
|
|
# is the shape that actually occurs - measured on the real tree at 0.16.0, two
|
|
# of the 26 planned repos were `done` with an unhandled inbox, and none were
|
|
# blocked-with-debt. A repo whose own work is finished can still owe an answer.
|
|
mkrepo "$ROOT/repo-done-owes"
|
|
{
|
|
echo "# STATE - repo-done-owes"
|
|
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
|
|
echo "<!-- board: status=done; blocked-on=-; next-cost=Sonnet 5/high -->"
|
|
printf 'Arbeidet er ferdig %s men innboksen er ikke gjort opp.\n' "$EMDASH"
|
|
} > "$ROOT/repo-done-owes/STATE.md"
|
|
mkdir -p "$CLAUDE_COORD_DIR/repo-done-owes/inbox"
|
|
echo "msg" > "$CLAUDE_COORD_DIR/repo-done-owes/inbox/2026-msg1-from-w.md"
|
|
|
|
PLAN="$("$BOARD" --roots "$ROOT" --plan 2>/dev/null)"; rc=$?
|
|
check "--plan exits 0" "$rc"
|
|
|
|
printf '%s' "$PLAN" | grep -q '^# PLAN '
|
|
check "plan carries a header naming what it is" $?
|
|
|
|
# Machine-readable by construction: the driver repo reads these keys.
|
|
printf '%s' "$PLAN" | grep -q '^tab=1$'
|
|
check "plan numbers tabs starting at 1" $?
|
|
|
|
# Debt outranks everything, most-owed first. repo-a owes 3, repo-blocked-owes 2,
|
|
# repo-owes and repo-typo 1 each.
|
|
[ "$(printf '%s\n' "$PLAN" | grep -A1 '^tab=1$' | grep '^repo=' | cut -d= -f2)" = "repo-a" ]
|
|
check "the repo owing the most messages is tab 1" $?
|
|
|
|
n_a="$(printf '%s\n' "$PLAN" | grep -n '^repo=repo-a$' | cut -d: -f1)"
|
|
n_bo="$(printf '%s\n' "$PLAN" | grep -n '^repo=repo-blocked-owes$' | cut -d: -f1)"
|
|
n_ow="$(printf '%s\n' "$PLAN" | grep -n '^repo=repo-owes$' | cut -d: -f1)"
|
|
[ -n "$n_a" ] && [ -n "$n_bo" ] && [ -n "$n_ow" ] \
|
|
&& [ "$n_a" -lt "$n_bo" ] && [ "$n_bo" -lt "$n_ow" ]
|
|
check "debt group is ordered by message count, descending" $?
|
|
|
|
# The status exclusion is about a repo's own next step, never about its debt.
|
|
printf '%s' "$PLAN" | grep -q '^repo=repo-blocked-owes$'
|
|
check "a BLOCKED repo that owes mail is still planned (debt is the other axis)" $?
|
|
|
|
printf '%s\n' "$PLAN" | grep -A4 '^repo=repo-blocked-owes$' | grep -q '^why=inbox:2$'
|
|
check "a blocked repo in the plan says debt, not status, is why it is there" $?
|
|
|
|
# Debt before live work: repo-h is in-progress and owes nothing. Stated as
|
|
# group 2 over group 4, NOT as "before any that owes none" - since 0.20.0 the
|
|
# chain-root group sits above debt, so a repo owing nothing CAN outrank one that
|
|
# does. Section 12 pins that half on the fixture that shows it cleanly.
|
|
n_h="$(printf '%s\n' "$PLAN" | grep -n '^repo=repo-h$' | cut -d: -f1)"
|
|
[ -n "$n_h" ] && [ "$n_ow" -lt "$n_h" ]
|
|
check "a repo owing mail is planned before live work that owes and releases nothing" $?
|
|
|
|
# Group 2 over group 3: repo-g is planned and owes nothing.
|
|
n_g="$(printf '%s\n' "$PLAN" | grep -n '^repo=repo-g$' | cut -d: -f1)"
|
|
[ -n "$n_g" ] && [ "$n_ow" -lt "$n_g" ]
|
|
check "a repo owing mail is planned before a repo that merely has a plan" $?
|
|
|
|
# repo-a is BOTH in-progress and owing. It must appear exactly once - a repo
|
|
# listed twice is two tabs for one repo, which is the plan failing at its job.
|
|
[ "$(printf '%s\n' "$PLAN" | grep -c '^repo=repo-a$')" -eq 1 ]
|
|
check "a repo qualifying under two rules is planned exactly once" $?
|
|
|
|
# repo-a owes 3 messages AND is the blocker plug-x and repo-blocked-owes both
|
|
# name. Under the group form why= names the GROUP that put the repo in the plan,
|
|
# and the chain-root group is the first one tested - so it reports what a session
|
|
# there would release rather than what it owes. Between 0.19.0 and 0.20.0 the
|
|
# same assertion held for a different reason (the dominant term of a score);
|
|
# the reason is written down because the check passes either way and only the
|
|
# reason says which form is running.
|
|
printf '%s\n' "$PLAN" | grep -A3 '^repo=repo-a$' | grep -q '^why=unblocks:2$'
|
|
check "why= names the group that planned the repo, releasing over owing" $?
|
|
|
|
# planned outranks in-progress among the repos that neither owe nor release.
|
|
# INVERTED at 0.20.0 by operator decision: a decided-but-unstarted next step is
|
|
# the step that converts a decision into motion, and live work is already moving.
|
|
# This is a policy call, not a preference - it is recorded here and in board.sh
|
|
# so a later session cannot read it as a sort that drifted.
|
|
[ -n "$n_g" ] && [ "$n_g" -lt "$n_h" ]
|
|
check "planned work is planned before live in-progress work (operator inversion)" $?
|
|
|
|
# Group 4 over group 5: repo-e declares an out-of-set status token and lands in
|
|
# the undeclared group, which is planned LAST and never dropped.
|
|
n_e="$(printf '%s\n' "$PLAN" | grep -n '^repo=repo-e$' | cut -d: -f1)"
|
|
[ -n "$n_e" ] && [ "$n_h" -lt "$n_e" ]
|
|
check "a repo with no declared status is planned after live work, not dropped" $?
|
|
|
|
# Excluded classes. A tab for a done or deferred repo is noise; a tab for a
|
|
# blocked repo that owes nothing cannot be moved at all.
|
|
printf '%s' "$PLAN" | grep -q '^repo=repo-wt$'; [ $? -ne 0 ]
|
|
check "plan excludes a done repo that owes nothing" $?
|
|
|
|
# ...but the exclusion is conditional on owing nothing, and the header says so.
|
|
# Read the other way, the plan's own header would call these entries a bug.
|
|
printf '%s' "$PLAN" | grep -q '^repo=repo-done-owes$'
|
|
check "a DONE repo that owes mail is still planned" $?
|
|
printf '%s\n' "$PLAN" | grep -A4 '^repo=repo-done-owes$' | grep -q '^status=done$'
|
|
check "a done repo in the plan still reports its real status" $?
|
|
printf '%s\n' "$PLAN" | grep -m1 '^# Utelatt' | grep -q 'skylder svar'
|
|
check "the header states the exclusions are conditional on owing nothing" $?
|
|
printf '%s' "$PLAN" | grep -q '^repo=repo-d$'; [ $? -ne 0 ]
|
|
check "plan excludes a deferred repo" $?
|
|
printf '%s' "$PLAN" | grep -q '^repo=plug-x$'; [ $? -ne 0 ]
|
|
check "plan excludes a blocked repo that owes nothing" $?
|
|
printf '%s' "$PLAN" | grep -q '^repo=repo-c$'; [ $? -ne 0 ]
|
|
check "plan excludes a repo with no STATE.md and no debt" $?
|
|
|
|
# A repo with no board line has no declared next step, but dropping it SILENTLY
|
|
# is what the table's own MERK line exists to prevent. It is planned last and
|
|
# labelled, never omitted.
|
|
printf '%s' "$PLAN" | grep -q '^repo=repo-b$'
|
|
check "a repo lacking a board line is planned, not silently dropped" $?
|
|
printf '%s\n' "$PLAN" | grep -A3 '^repo=repo-b$' | grep -q '^why=uavklart$'
|
|
check "a repo with unknown status is labelled uavklart, not guessed into a rule" $?
|
|
n_b="$(printf '%s\n' "$PLAN" | grep -n '^repo=repo-b$' | cut -d: -f1)"
|
|
[ -n "$n_b" ] && [ "$n_g" -lt "$n_b" ]
|
|
check "uavklart repos rank below every repo that declared a status" $?
|
|
|
|
# The command comes from route.sh, same single copy of the rubric the briefing
|
|
# uses - advisor flag included, since that is a property of the ROW.
|
|
printf '%s' "$PLAN" | grep -q '^command=claude --model sonnet --effort high --advisor opus$'
|
|
check "plan derives the exact startup command from the repo's route line" $?
|
|
|
|
# Both no-command causes must degrade to a marker. A bare `command=` would be
|
|
# the shape of a runnable command carrying nothing - worse than none, because a
|
|
# driver reading `^command=` would type an empty line into a live pane.
|
|
printf '%s' "$PLAN" | grep -q '^command=$'; [ $? -ne 0 ]
|
|
check "plan never emits an empty command= line" $?
|
|
|
|
printf '%s\n' "$PLAN" | grep -A6 '^repo=repo-typo$' | grep -q '^command_missing='
|
|
check "unparseable route line degrades to command_missing=, not to a guess" $?
|
|
printf '%s\n' "$PLAN" | grep -A6 '^repo=repo-a$' | grep -q '^command_missing='
|
|
check "repo owing mail but lacking a route line is marked, never guessed at" $?
|
|
|
|
# Same argument as the briefing: the 38-char cut is the TABLE column's property.
|
|
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-)"
|
|
[ -n "$pdir" ] && [ -d "$pdir" ] && [ "${pdir#/}" != "$pdir" ]
|
|
check "dir= is an absolute path that exists" $?
|
|
|
|
# Every block must be complete: a driver that reads a block missing dir= or
|
|
# command= has no safe default, and guessing one types into a live pane.
|
|
n_tab="$(printf '%s\n' "$PLAN" | grep -c '^tab=')"
|
|
n_rep="$(printf '%s\n' "$PLAN" | grep -c '^repo=')"
|
|
n_dir="$(printf '%s\n' "$PLAN" | grep -c '^dir=')"
|
|
n_why="$(printf '%s\n' "$PLAN" | grep -c '^why=')"
|
|
n_sta="$(printf '%s\n' "$PLAN" | grep -c '^status=')"
|
|
n_nes="$(printf '%s\n' "$PLAN" | grep -c '^neste=')"
|
|
n_cmd="$(printf '%s\n' "$PLAN" | grep -c '^command=')"
|
|
n_cms="$(printf '%s\n' "$PLAN" | grep -c '^command_missing=')"
|
|
[ "$n_tab" -gt 0 ] && [ "$n_rep" -eq "$n_tab" ] && [ "$n_dir" -eq "$n_tab" ] \
|
|
&& [ "$n_why" -eq "$n_tab" ] && [ "$n_sta" -eq "$n_tab" ] \
|
|
&& [ "$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"
|
|
# An empty tab list satisfies "every number is its index" vacuously, which is
|
|
# the check passing by finding nothing rather than by reading the numbering.
|
|
[ -s "$ROOT/tabs.txt" ] || seq_ok=1
|
|
i=0; while read -r t; do i=$((i+1)); [ "$t" = "$i" ] || seq_ok=1; done < "$ROOT/tabs.txt"
|
|
check "tab numbers are dense and consecutive" "$seq_ok"
|
|
|
|
# Read-only, same as every other rendering. Reading IS delivering in this
|
|
# engine, so a plan that consumed a backlog would be the core defect.
|
|
p4="$(snap "$CLAUDE_COORD_DIR")"
|
|
"$BOARD" --roots "$ROOT" --plan >/dev/null 2>&1
|
|
[ "$p4" = "$(snap "$CLAUDE_COORD_DIR")" ]
|
|
check "plan writes nothing to the mailbox (reading is delivering)" $?
|
|
|
|
# The scan and the mailbox are two populations. An orphan mailbox has no repo to
|
|
# cd into, so it cannot BE a tab - but dropping it makes the plan claim a
|
|
# completeness it knows it lacks. It is reported as commentary.
|
|
printf '%s' "$PLAN" | grep -q 'orphan-surface'
|
|
check "plan surfaces a pending mailbox with no repo in the scan tree" $?
|
|
printf '%s' "$PLAN" | grep -q '^repo=orphan-surface$'; [ $? -ne 0 ]
|
|
check "orphan mailbox is commentary, never a tab block (there is no dir to cd to)" $?
|
|
|
|
# Comment lines are '#'-prefixed so a consumer can drop them with one rule.
|
|
printf '%s\n' "$PLAN" | grep -vE '^#|^$|^[a-z_]+=' | grep -q .; [ $? -ne 0 ]
|
|
check "every plan line is a comment, a blank, or a key=value pair" $?
|
|
|
|
printf '%s' "$PLAN" | iconv -f UTF-8 -t UTF-8 >/dev/null 2>&1
|
|
check "plan output is valid UTF-8" $?
|
|
|
|
"$BOARD" --help 2>/dev/null | grep -q '\-\-plan'
|
|
check "--help documents --plan" $?
|
|
|
|
# --- 11. Focused day plan (--focus) ---------------------------------------
|
|
# --focus is the first CUTOFF the plan has ever had, and every check here
|
|
# exists because a cutoff is exactly what the plan format promised it was not.
|
|
# The filter alone is half the feature; the other half is the run saying what
|
|
# it held back, and they ship together or not at all.
|
|
FPLAIN="$("$BOARD" --roots "$ROOT" --plan 2>/dev/null)"
|
|
FDEMO="$("$BOARD" --roots "$ROOT" --plan --focus demo 2>/dev/null)"
|
|
|
|
n_all="$(printf '%s\n' "$FPLAIN" | grep -c '^repo=')"
|
|
n_focus="$(printf '%s\n' "$FDEMO" | grep -c '^repo=')"
|
|
|
|
# The operator's instruction, as a test: prove it NARROWS. Every other check
|
|
# in this section passes trivially against a no-op filter, which is precisely
|
|
# the state the argument was in before this feature - swallowed by the unknown
|
|
# argument fallback, full plan returned, no crash. "It did not crash" is not
|
|
# evidence of a filter.
|
|
[ "$n_focus" -lt "$n_all" ] && [ "$n_focus" -gt 0 ]
|
|
check "focused plan is strictly smaller than the full plan, and not empty" $?
|
|
|
|
printf '%s\n' "$FDEMO" | grep -q '^repo=repo-focus-plain$'
|
|
check "a repo declaring the slug survives the focus" $?
|
|
|
|
printf '%s\n' "$FDEMO" | grep -q '^repo=repo-focus-bold$'
|
|
check "a bold/backtick declaration counts as declared (the register's grep loses it)" $?
|
|
|
|
# Bold around the STATUS rather than the slug. Accepting one and not the other
|
|
# is not a rule, just whichever example was in front of us first.
|
|
printf '%s\n' "$FDEMO" | grep -q '^repo=repo-focus-boldstatus$'
|
|
check "bold on the status token counts as declared, same as bold on the slug" $?
|
|
|
|
printf '%s\n' "$FDEMO" | grep '^fokus_treff=' | grep -q 'demo-topic: done'
|
|
check "the evidence reports the status unwrapped, never '**done**'" $?
|
|
|
|
printf '%s\n' "$FPLAIN" | grep -q '^repo=repo-a$'
|
|
printf '%s\n' "$FDEMO" | grep -q '^repo=repo-a$'; [ $? -ne 0 ]
|
|
check "an off-topic repo is in the full plan and gone from the focused one" $?
|
|
|
|
printf '%s\n' "$FDEMO" | grep -q '^repo=repo-focus-other$'; [ $? -ne 0 ]
|
|
check "a repo declaring a DIFFERENT slug is not swept in" $?
|
|
|
|
# The precondition. A repo that mentions the slug without declaring it is
|
|
# neither kept nor dropped silently: it is named. Enumerating this class - not
|
|
# reasoning about it - is what found the heaviest consumer in the real tree.
|
|
printf '%s\n' "$FDEMO" | grep -q '^fokus_utenfor='
|
|
check "the run reports the repos it held back" $?
|
|
|
|
printf '%s\n' "$FDEMO" | grep '^fokus_utenfor=' | grep -q 'repo-focus-mentions'
|
|
check "the held-back report NAMES the repo, not just a count" $?
|
|
|
|
printf '%s\n' "$FDEMO" | grep -q '^repo=repo-focus-mentions$'; [ $? -ne 0 ]
|
|
check "a mention without a marker line does not become a tab" $?
|
|
|
|
# Wording is load-bearing: board.sh reads text, and has no grounds whatever
|
|
# for a claim about relevance. It says what it FOUND.
|
|
printf '%s\n' "$FDEMO" | grep '^fokus_utenfor=' | grep -q 'nevner'
|
|
check "the held-back line says 'nevner' (a fact about text found)" $?
|
|
|
|
printf '%s\n' "$FDEMO" | grep '^fokus_utenfor=' | grep -qi 'dekker'; [ $? -ne 0 ]
|
|
check "the held-back line never claims a repo 'dekker' the topic" $?
|
|
|
|
# It states its own reach: 27 STATE.md, not "the tree". One repo in the real
|
|
# measurement has its strongest evidence in a README the scan never opens.
|
|
printf '%s\n' "$FDEMO" | grep -q '^fokus_rekkevidde=.*STATE.md'
|
|
check "the run states its reach (N STATE.md searched, live-counted)" $?
|
|
|
|
n_state="$(printf '%s\n' "$FDEMO" | sed -n 's/^fokus_rekkevidde=sokt i \([0-9]*\) STATE.md.*/\1/p')"
|
|
# Both depths, because that is what board SCANS. The register's own roll-up
|
|
# greps ~/repos/*/STATE.md and so cannot see a polyrepo child; the reach line
|
|
# must state the population board actually searched, not the register's.
|
|
real_state="$(ls "$ROOT"/*/STATE.md "$ROOT"/*/*/STATE.md 2>/dev/null | wc -l | tr -d ' ')"
|
|
[ -n "$n_state" ] && [ "$n_state" = "$real_state" ]
|
|
check "the reach count is measured, never a constant" $?
|
|
|
|
printf '%s\n' "$FDEMO" | grep -q '^fokus=demo-topic$'
|
|
check "the run names the slug the prose resolved to" $?
|
|
|
|
# Per-block evidence: a focused run must say why each surviving block survived.
|
|
# This is the need that closed topics= - relocated from all 27 blocks every
|
|
# day into the focused run, where it is actually evidence.
|
|
n_blocks="$(printf '%s\n' "$FDEMO" | grep -c '^repo=')"
|
|
n_ev="$(printf '%s\n' "$FDEMO" | grep -c '^fokus_treff=')"
|
|
[ "$n_ev" -eq "$n_blocks" ] && ev_ok=0 || ev_ok=1
|
|
check "every surviving block carries its own evidence (fokus_treff=)" "$ev_ok"
|
|
|
|
printf '%s\n' "$FDEMO" | grep '^fokus_treff=' | grep -q 'demo-topic'
|
|
check "the per-block evidence names the slug and its declared status" $?
|
|
|
|
# Zero held back is a real answer and must LOOK like one. A trailing ": " with
|
|
# nothing after it is the same defect class the format already legislated
|
|
# against for command=: the shape of a value carrying nothing. Reached via a
|
|
# slug that nobody mentions without declaring.
|
|
FOTHER="$("$BOARD" --roots "$ROOT" --plan --focus other 2>/dev/null)"
|
|
printf '%s\n' "$FOTHER" | grep '^fokus_utenfor=' | grep -qE ':[[:space:]]*$'; [ $? -ne 0 ]
|
|
check "an empty held-back set renders a value, never a dangling colon" $?
|
|
|
|
# Ambiguity widens and is named. "topic" is a hyphen-part of two declared
|
|
# slugs; picking one silently would be the cutoff lying about its own size.
|
|
FBOTH="$("$BOARD" --roots "$ROOT" --plan --focus topic 2>/dev/null)"
|
|
printf '%s\n' "$FBOTH" | grep '^fokus=' | grep -q 'demo-topic'
|
|
printf '%s\n' "$FBOTH" | grep '^fokus=' | grep -q 'other-topic'
|
|
check "ambiguous prose resolves to the union, with every slug named" $?
|
|
|
|
printf '%s\n' "$FBOTH" | grep -q '^repo=repo-focus-other$'
|
|
check "the union actually includes the second slug's repo" $?
|
|
|
|
# No match: the full plan, plus a line saying the focus was not applied. An
|
|
# empty plan would be the worst outcome of all - the driver forwards operator
|
|
# prose verbatim, so a typo would silently produce a morning with no tabs.
|
|
FNONE="$("$BOARD" --roots "$ROOT" --plan --focus kaffe 2>/dev/null)"
|
|
n_none="$(printf '%s\n' "$FNONE" | grep -c '^repo=')"
|
|
[ "$n_none" -eq "$n_all" ]
|
|
check "prose matching no declared slug returns the FULL plan, never an empty one" $?
|
|
|
|
printf '%s\n' "$FNONE" | grep -q '^fokus_ikke_brukt='
|
|
check "a focus that was not applied says so" $?
|
|
|
|
# Format invariant: --focus adds fields, it does not change the shape two
|
|
# declared consumers parse.
|
|
printf '%s\n' "$FDEMO" | grep -vE '^#|^$|^[a-z_]+=' | grep -q .; [ $? -ne 0 ]
|
|
check "focused plan is still comments, blanks and key=value only" $?
|
|
|
|
printf '%s' "$FDEMO" | iconv -f UTF-8 -t UTF-8 >/dev/null 2>&1
|
|
check "focused plan output is valid UTF-8" $?
|
|
|
|
# The read-only invariant holds for the new path too: reading is delivering,
|
|
# and a plan that consumed a backlog would be the core defect of the engine.
|
|
before_inbox="$(ls "$CLAUDE_COORD_DIR/repo-a/inbox" 2>/dev/null | wc -l | tr -d ' ')"
|
|
"$BOARD" --roots "$ROOT" --plan --focus demo >/dev/null 2>&1
|
|
after_inbox="$(ls "$CLAUDE_COORD_DIR/repo-a/inbox" 2>/dev/null | wc -l | tr -d ' ')"
|
|
[ "$before_inbox" = "$after_inbox" ]
|
|
check "--focus writes nothing to the mailbox" $?
|
|
|
|
# bash 3.2: `shift 2` past the end loops forever. A flag whose value is missing
|
|
# must not hang the driver that forwards it - and it refuses loudly, exactly as
|
|
# --roots does, rather than degrading into "no focus" and printing a full plan
|
|
# that looks like a focused one.
|
|
"$BOARD" --roots "$ROOT" --plan --focus >/dev/null 2>&1
|
|
[ $? -eq 2 ]
|
|
check "--focus with no value exits 2, like every other flag missing its value" $?
|
|
|
|
"$BOARD" --help 2>/dev/null | grep -q '\-\-focus'
|
|
check "--help documents --focus" $?
|
|
|
|
# --- 12. Chain-root credit in --plan --------------------------------------
|
|
# Ordered by what a repo OWES, the plan could never say what OPENING it would
|
|
# RELEASE. blocked-on was scanned and then used for one display string only.
|
|
#
|
|
# The credit goes to the ROOT of the chain and to nothing else, and that is the
|
|
# whole rule: opening a blocked repo moves nothing, because its own next step is
|
|
# by definition waiting on someone. Opening the root moves everything behind it.
|
|
# So the walk is transitive - crediting only the direct blocker would leave a
|
|
# two-hop chain with its root uncredited, which is the shape the real tree
|
|
# actually had when this was ordered.
|
|
#
|
|
# Three fixtures, one chain: leaf -> mid -> root.
|
|
mkrepo "$ROOT/chain-root"
|
|
{
|
|
echo "# STATE - chain-root"
|
|
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
|
|
# Deliberately `planned`, not `in-progress`: the root must earn its position
|
|
# from the chain-root group alone, never from the status group, so the ranking
|
|
# checks below fail for the right reason instead of passing on fixture order.
|
|
echo "<!-- board: status=planned; blocked-on=-; next-cost=Opus 5/high -->"
|
|
printf 'Roten %s ingen gjeld, men holder to repo oppe.\n' "$EMDASH"
|
|
} > "$ROOT/chain-root/STATE.md"
|
|
|
|
mkrepo "$ROOT/chain-mid"
|
|
{
|
|
echo "# STATE - chain-mid"
|
|
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
|
|
echo "<!-- board: status=blocked; blocked-on=chain-root; next-cost=Opus 5/high -->"
|
|
printf 'Venter p%s chain-root.\n' "$EMDASH"
|
|
} > "$ROOT/chain-mid/STATE.md"
|
|
|
|
mkrepo "$ROOT/chain-leaf"
|
|
{
|
|
echo "# STATE - chain-leaf"
|
|
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
|
|
echo "<!-- board: status=blocked; blocked-on=chain-mid; next-cost=Opus 5/high -->"
|
|
printf 'Venter p%s chain-mid, som venter p%s roten.\n' "$EMDASH" "$EMDASH"
|
|
} > "$ROOT/chain-leaf/STATE.md"
|
|
|
|
# A cycle is not hypothetical: two repos can each declare they wait on the other
|
|
# after a miscommunication, and nobody would notice until the board hung. The
|
|
# walk must terminate and credit NOBODY - a cycle has no root by definition.
|
|
mkrepo "$ROOT/cyc-a"
|
|
{
|
|
echo "# STATE - cyc-a"
|
|
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
|
|
echo "<!-- board: status=blocked; blocked-on=cyc-b; next-cost=Opus 5/high -->"
|
|
echo "Venter paa cyc-b."
|
|
} > "$ROOT/cyc-a/STATE.md"
|
|
mkrepo "$ROOT/cyc-b"
|
|
{
|
|
echo "# STATE - cyc-b"
|
|
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
|
|
echo "<!-- board: status=blocked; blocked-on=cyc-a; next-cost=Opus 5/high -->"
|
|
echo "Venter paa cyc-a."
|
|
} > "$ROOT/cyc-b/STATE.md"
|
|
|
|
# blocked-on names a repo the scan never produced. Nothing validates that field,
|
|
# so a typo is the expected case, not the exotic one - and it must credit
|
|
# nothing rather than credit a name that has no directory to open.
|
|
mkrepo "$ROOT/dangle"
|
|
{
|
|
echo "# STATE - dangle"
|
|
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
|
|
echo "<!-- board: status=blocked; blocked-on=no-such-repo; next-cost=Opus 5/high -->"
|
|
echo "Venter paa noe som ikke finnes."
|
|
} > "$ROOT/dangle/STATE.md"
|
|
|
|
# THE DISCRIMINATING PAIR for the 0.20.0 group form. Every other ordering check
|
|
# in this section passes under BOTH the 0.19.0 score and the group form, because
|
|
# chain-root releases two repos and 40*2 beats any debt in this tree - it would
|
|
# have gone green while the operator's decision quietly failed to ship.
|
|
#
|
|
# These two are built so the forms DISAGREE. solo-root releases exactly one repo
|
|
# and owes nothing (score: 40 + 5 planned = 45). heavy-debtor owes four (score:
|
|
# 60 + 10 in-progress = 70). The score ranks the debtor first; the group form
|
|
# ranks the root first, because releasing is group 1 and debt is group 2.
|
|
mkrepo "$ROOT/solo-root"
|
|
{
|
|
echo "# STATE - solo-root"
|
|
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
|
|
echo "<!-- board: status=planned; blocked-on=-; next-cost=Opus 5/high -->"
|
|
printf 'Losner ett repo, skylder ingenting.\n'
|
|
} > "$ROOT/solo-root/STATE.md"
|
|
|
|
mkrepo "$ROOT/solo-leaf"
|
|
{
|
|
echo "# STATE - solo-leaf"
|
|
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
|
|
echo "<!-- board: status=blocked; blocked-on=solo-root; next-cost=Opus 5/high -->"
|
|
printf 'Venter p%s solo-root.\n' "$EMDASH"
|
|
} > "$ROOT/solo-leaf/STATE.md"
|
|
|
|
mkrepo "$ROOT/heavy-debtor"
|
|
{
|
|
echo "# STATE - heavy-debtor"
|
|
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
|
|
echo "<!-- board: status=in-progress; blocked-on=-; next-cost=Opus 5/high -->"
|
|
printf 'Skylder fire svar, losner ingen.\n'
|
|
} > "$ROOT/heavy-debtor/STATE.md"
|
|
mkdir -p "$CLAUDE_COORD_DIR/heavy-debtor/inbox"
|
|
for n in 1 2 3 4; do
|
|
echo "msg" > "$CLAUDE_COORD_DIR/heavy-debtor/inbox/2026-msg$n-from-v.md"
|
|
done
|
|
|
|
P2="$("$BOARD" --roots "$ROOT" --plan 2>/dev/null)"; rc=$?
|
|
check "--plan still exits 0 with chain, cycle and dangling fixtures present" "$rc"
|
|
|
|
printf '%s' "$P2" | grep -q '^repo=chain-root$'
|
|
check "the root of a blocked chain is planned even though it owes no mail" $?
|
|
|
|
printf '%s\n' "$P2" | grep -A3 '^repo=chain-root$' | grep -q '^why=unblocks:2$'
|
|
check "the root is credited with the WHOLE subtree it holds up, not one edge" $?
|
|
|
|
# The exclusion these fixtures must not break: a blocked repo with no debt still
|
|
# gets no tab. The credit goes to the root precisely so the blocked repos do not
|
|
# need one - crediting them instead would open two tabs that cannot move.
|
|
printf '%s' "$P2" | grep -q '^repo=chain-mid$'; [ $? -ne 0 ]
|
|
check "a blocked middle link with no debt is still not planned" $?
|
|
printf '%s' "$P2" | grep -q '^repo=chain-leaf$'; [ $? -ne 0 ]
|
|
check "a blocked leaf with no debt is still not planned" $?
|
|
|
|
# Unblocking outranks work that neither owes nor releases anything. This is the
|
|
# point of the change: repo-h is in-progress and idle, chain-root is in-progress
|
|
# and holds two repos up.
|
|
n2_cr="$(printf '%s\n' "$P2" | grep -n '^repo=chain-root$' | cut -d: -f1)"
|
|
n2_h="$(printf '%s\n' "$P2" | grep -n '^repo=repo-h$' | cut -d: -f1)"
|
|
[ -n "$n2_cr" ] && [ -n "$n2_h" ] && [ "$n2_cr" -lt "$n2_h" ]
|
|
check "a chain root ranks above live work that releases nothing" $?
|
|
|
|
# THE decisive check for the 0.20.0 group form - the discriminating pair built
|
|
# above. solo-root releases ONE and owes nothing; heavy-debtor owes FOUR. This is
|
|
# the only ordering assertion in the file that the 0.19.0 score would fail, so it
|
|
# is the one that proves which form is actually running. Debt is still neither
|
|
# capped nor excluded here - it is one group lower, not down-weighted.
|
|
n2_sr="$(printf '%s\n' "$P2" | grep -n '^repo=solo-root$' | cut -d: -f1)"
|
|
n2_hd="$(printf '%s\n' "$P2" | grep -n '^repo=heavy-debtor$' | cut -d: -f1)"
|
|
[ -n "$n2_sr" ] && [ -n "$n2_hd" ] && [ "$n2_sr" -lt "$n2_hd" ]
|
|
check "releasing ONE repo outranks owing FOUR (group form, not the score)" $?
|
|
|
|
# The other half of the same rule: the debtor is still IN the plan, and still
|
|
# says debt is why. A group form that ranked debt lower by dropping it would be
|
|
# the excluded-debt defect wearing the operator's decision as a disguise.
|
|
printf '%s\n' "$P2" | grep -A3 '^repo=heavy-debtor$' | grep -q '^why=inbox:4$'
|
|
check "a repo outranked by a chain root still keeps its tab and its why=" $?
|
|
|
|
# A cycle terminates and credits nobody. Both halves matter: hanging is the
|
|
# failure everyone imagines, but silently inventing a root is the one that would
|
|
# ship - it looks like a working plan.
|
|
printf '%s' "$P2" | grep -q '^repo=cyc-a$'; [ $? -ne 0 ]
|
|
check "a cyclic blocked-on chain credits nobody (cyc-a not planned)" $?
|
|
printf '%s' "$P2" | grep -q '^repo=cyc-b$'; [ $? -ne 0 ]
|
|
check "a cyclic blocked-on chain credits nobody (cyc-b not planned)" $?
|
|
printf '%s' "$P2" | grep -q '^why=unblocks:0$'; [ $? -ne 0 ]
|
|
check "no repo is ever credited with an empty unblock count" $?
|
|
|
|
# A dangling blocked-on must not conjure a tab for a name with no directory.
|
|
printf '%s' "$P2" | grep -q '^repo=no-such-repo$'; [ $? -ne 0 ]
|
|
check "a dangling blocked-on target is never planned into existence" $?
|
|
printf '%s' "$P2" | grep -q '^repo=dangle$'; [ $? -ne 0 ]
|
|
check "the repo carrying a dangling blocked-on is itself still excluded" $?
|
|
|
|
# The format is the API. Changing the ranking form must not move, rename or drop
|
|
# a single field - the driver parses these keys positionally-independently, but
|
|
# it parses THESE keys.
|
|
n2_tab="$(printf '%s\n' "$P2" | grep -c '^tab=')"
|
|
n2_rep="$(printf '%s\n' "$P2" | grep -c '^repo=')"
|
|
n2_dir="$(printf '%s\n' "$P2" | grep -c '^dir=')"
|
|
n2_why="$(printf '%s\n' "$P2" | grep -c '^why=')"
|
|
n2_sta="$(printf '%s\n' "$P2" | grep -c '^status=')"
|
|
n2_nes="$(printf '%s\n' "$P2" | grep -c '^neste=')"
|
|
n2_cmd="$(printf '%s\n' "$P2" | grep -c '^command=')"
|
|
n2_cms="$(printf '%s\n' "$P2" | grep -c '^command_missing=')"
|
|
[ "$n2_tab" -gt 0 ] && [ "$n2_rep" -eq "$n2_tab" ] && [ "$n2_dir" -eq "$n2_tab" ] \
|
|
&& [ "$n2_why" -eq "$n2_tab" ] && [ "$n2_sta" -eq "$n2_tab" ] \
|
|
&& [ "$n2_nes" -eq "$n2_tab" ] && [ $(( n2_cmd + n2_cms )) -eq "$n2_tab" ]
|
|
check "ranked plan still carries every documented field exactly once per block" $?
|
|
|
|
printf '%s' "$P2" | grep -q '^command=$'; [ $? -ne 0 ]
|
|
check "ranked plan never emits an empty command= line" $?
|
|
|
|
# Debt still puts a repo in the plan and still names itself when it is the
|
|
# dominant reason. Rejecting the proposed cap on debt was a deliberate call:
|
|
# owing a reply is the other axis, and answering is often what unblocks a chain.
|
|
printf '%s\n' "$P2" | grep -A3 '^repo=repo-blocked-owes$' | grep -q '^why=inbox:2$'
|
|
check "debt still names itself in why= when it is the dominant reason" $?
|
|
|
|
# --brief is a second RENDERING of the same scan and must survive the change.
|
|
"$BOARD" --roots "$ROOT" --brief >/dev/null 2>&1
|
|
check "--brief still exits 0 after the ranking change" $?
|
|
|
|
# The plan must still print the scan whole. There is no cutoff by design, so a
|
|
# consumer's own tab cap is the only thing that ever hides a repo.
|
|
[ "$n2_tab" -ge "$n_tab" ]
|
|
check "the grouped plan hides nothing an earlier ordering showed" $?
|
|
|
|
echo ""
|
|
echo "board-selftest: $PASS passed, $FAIL failed"
|
|
[ "$FAIL" -eq 0 ] || exit 1
|
|
exit 0
|