feat(board): PLAN column - where a destination is missing or untouched
A display-only table column and a `plan=` field in --row, read from the filesystem: `-` = no file named exactly PLAN.md in the repo root; `Nd` = whole days the plan's mtime lies behind the last commit (`0d` when as new or newer); `?` = a plan with no commit to compare against. The content is never read, and the legend says the column does not judge whether a criterion can be falsified. Not a sort key, not in --plan, not in SUM. The name is matched byte for byte through a glob plus a basename compare: APFS is case-insensitive by default, so `[ -f PLAN.md ]` is true for a `plan.md`. The glob needs no subprocess. The checks were written red in a separate eval before the column existed and pass unchanged; they now live in board-selftest section 34 and the eval file is removed (two judges for one column would drift). Mutation-verified: a case-blind test, sorting on the field, a plan= key in --plan and a legend without IKKE each turn exactly one check red. The new record field sits before `neste`, so every "last field onward" index moved 17 -> 18, and F6's right-counted DRT read moved NF-3 -> NF-4. board-selftest 428 -> 461 (badge 961). Suites: coord 257, board 461, route 73, orders 116, guard 54; npm test 12/12, run after git add. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
f079debdee
commit
87fb78334e
7 changed files with 327 additions and 260 deletions
|
|
@ -2379,19 +2379,19 @@ check "F6: fixture ground truth - git really does fail on the broken worktree" $
|
|||
|
||||
F6TBL="$(CLAUDE_COORD_DIR="$F6COORD" "$BOARD" --roots "$F6ROOT" 2>/dev/null)"
|
||||
|
||||
# DRT is counted from the RIGHT (NF-3: DRT ALDER SISTE NESTE), never from the
|
||||
# DRT is counted from the RIGHT (NF-4: DRT ALDER SISTE PLAN NESTE), never from the
|
||||
# left: the KOST field is free text and its realistic spelling ("Sonnet 5/low")
|
||||
# contains a space, so $7 reads FLY instead. Measured, not assumed - the first
|
||||
# cut of this section passed its clean-tree control by accident that way.
|
||||
printf '%s' "$F6TBL" | grep -E '^f6-clean[[:space:]]' | awk '{print $(NF-3)}' | grep -qx '0'
|
||||
printf '%s' "$F6TBL" | grep -E '^f6-clean[[:space:]]' | awk '{print $(NF-4)}' | grep -qx '0'
|
||||
check "F6: control - a healthy CLEAN tree still reads DRT 0" $?
|
||||
printf '%s' "$F6TBL" | grep -E '^f6-dirty[[:space:]]' | awk '{print $(NF-3)}' | grep -qx '1'
|
||||
printf '%s' "$F6TBL" | grep -E '^f6-dirty[[:space:]]' | awk '{print $(NF-4)}' | grep -qx '1'
|
||||
check "F6: control - a healthy DIRTY tree still reads DRT 1" $?
|
||||
|
||||
printf '%s' "$F6TBL" | grep -E '^f6-broken[[:space:]]' | awk '{print $(NF-3)}' | grep -qx '0'
|
||||
printf '%s' "$F6TBL" | grep -E '^f6-broken[[:space:]]' | awk '{print $(NF-4)}' | grep -qx '0'
|
||||
[ $? -ne 0 ]
|
||||
check "F6: a git failure does NOT read as DRT 0 (clean tree)" $?
|
||||
printf '%s' "$F6TBL" | grep -E '^f6-broken[[:space:]]' | awk '{print $(NF-3)}' | grep -qx '?'
|
||||
printf '%s' "$F6TBL" | grep -E '^f6-broken[[:space:]]' | awk '{print $(NF-4)}' | grep -qx '?'
|
||||
check "F6: a git failure reads as ? (not measured), the same token coord-count uses" $?
|
||||
|
||||
# The load-bearing consequence. All three fixtures are status=done with no mail
|
||||
|
|
@ -3455,6 +3455,218 @@ grep -q -- '--row' "$BOARD"; check "row: board.sh actually carries the --row fla
|
|||
/bin/rm -rf "$ROW_ROOT" "$ROW_COORD" 2>/dev/null
|
||||
|
||||
|
||||
# --- 34. PLAN column: a filesystem reading, display only --------------------
|
||||
# Operator decision 2026-09-17. The cell answers one question: does this repo
|
||||
# keep a destination where sessions actually look (a root PLAN.md), and is
|
||||
# anyone touching it? `-` = no file named exactly PLAN.md in the root; `Nd` =
|
||||
# whole days its mtime lies behind the last commit. It never reads the file,
|
||||
# never enters a sort, a --plan block or SUM, and does not judge whether a
|
||||
# criterion inside it can be falsified - the legend says so.
|
||||
#
|
||||
# These checks were written RED in a separate file before the column existed
|
||||
# (24 of 33 failing because the column was missing, fixture ground truth
|
||||
# green), kept out of this suite so main stayed green, and moved here once the
|
||||
# column passed them unchanged. That file is gone: two judges for one column
|
||||
# would drift. Mutation-verified against four broken variants, each turning
|
||||
# exactly one check red: a case-blind `[ -f PLAN.md ]`, sorting rows on the
|
||||
# plan field, a plan= key in the --plan blocks, and a legend without IKKE.
|
||||
#
|
||||
# Table cells are located through the HEADER (split on runs of 2+ spaces),
|
||||
# never a fixed offset, so a later column move does not break this section.
|
||||
PC_ROOT="$(mktemp -d)"
|
||||
PC_COORD="$(mktemp -d)"
|
||||
PC_NOW="$(date +%s)"
|
||||
PC_DAY=86400
|
||||
|
||||
pc_state() {
|
||||
{
|
||||
echo "# STATE - $2"
|
||||
echo "## NESTE - START HER"
|
||||
echo "<!-- board: status=$3; blocked-on=-; next-cost=Sonnet 5/high -->"
|
||||
echo "Next step for $2."
|
||||
} > "$1/STATE.md"
|
||||
}
|
||||
# pc_commit_at <dir> <epoch> <msg>: commit everything with a fixed committer date.
|
||||
pc_commit_at() {
|
||||
git -C "$1" add -A >/dev/null 2>&1
|
||||
GIT_AUTHOR_DATE="@$2 +0000" GIT_COMMITTER_DATE="@$2 +0000" \
|
||||
git -C "$1" -c user.email=t@t -c user.name=t commit -qm "$3" >/dev/null 2>&1
|
||||
}
|
||||
pc_touch_at() { touch -t "$(date -r "$2" +%Y%m%d%H%M.%S)" "$1"; }
|
||||
pc_last_commit() { git -C "$1" log -1 --format=%ct 2>/dev/null; }
|
||||
pc_mtime() { stat -f %m "$1"; }
|
||||
|
||||
# pc_table_cell <table-output> <repo>: the PLAN cell of <repo>'s row, read under
|
||||
# the nearest preceding header line. Empty when the header has no PLAN column.
|
||||
pc_table_cell() {
|
||||
printf '%s\n' "$1" | awk -v repo="$2" '
|
||||
function split2(s, arr, n) { gsub(/ +/, "\t", s); sub(/^\t/, "", s); return split(s, arr, "\t") }
|
||||
/^REPO / { n = split2($0, h); col = 0; for (i = 1; i <= n; i++) if (h[i] == "PLAN") col = i; next }
|
||||
$1 == repo && col > 0 { split2($0, r); print r[col]; exit }'
|
||||
}
|
||||
pc_row_plan() { printf '%s\n' "$1" | sed -n 's/^plan=//p'; }
|
||||
pc_board() { CLAUDE_COORD_DIR="$PC_COORD" "$BOARD" --roots "$PC_ROOT" "$@" 2>/dev/null; }
|
||||
|
||||
|
||||
# --- Fixtures ---------------------------------------------------------------
|
||||
|
||||
# p-none: no PLAN.md anywhere.
|
||||
mkrepo "$PC_ROOT/p-none"; pc_state "$PC_ROOT/p-none" p-none planned
|
||||
pc_commit_at "$PC_ROOT/p-none" $((PC_NOW - 3 * PC_DAY)) init
|
||||
|
||||
# p-fresh: PLAN.md newer than the last commit -> 0d. The file is EMPTY: a value
|
||||
# computed from content could not produce 0d here, so this also shows that
|
||||
# the column is a filesystem reading.
|
||||
mkrepo "$PC_ROOT/p-fresh"; pc_state "$PC_ROOT/p-fresh" p-fresh planned
|
||||
pc_commit_at "$PC_ROOT/p-fresh" $((PC_NOW - 2 * PC_DAY)) init
|
||||
: > "$PC_ROOT/p-fresh/PLAN.md"
|
||||
pc_touch_at "$PC_ROOT/p-fresh/PLAN.md" "$PC_NOW"
|
||||
|
||||
# p-stale: PLAN.md 5 days and 3 hours older than the last commit -> 5d. Its
|
||||
# content looks like a value on purpose ("PLAN=99d"); it must not be read.
|
||||
mkrepo "$PC_ROOT/p-stale"; pc_state "$PC_ROOT/p-stale" p-stale planned
|
||||
printf '# PLAN\nPLAN=99d\n' > "$PC_ROOT/p-stale/PLAN.md"
|
||||
PC_P_STALE_CT=$((PC_NOW - 3600))
|
||||
pc_commit_at "$PC_ROOT/p-stale" "$PC_P_STALE_CT" init
|
||||
pc_touch_at "$PC_ROOT/p-stale/PLAN.md" $((PC_P_STALE_CT - 5 * PC_DAY - 3 * 3600))
|
||||
|
||||
# p-docs: PLAN.md in docs/ only - the wrong place by convention.
|
||||
mkrepo "$PC_ROOT/p-docs"; pc_state "$PC_ROOT/p-docs" p-docs planned
|
||||
mkdir -p "$PC_ROOT/p-docs/docs"; echo "# PLAN" > "$PC_ROOT/p-docs/docs/PLAN.md"
|
||||
pc_commit_at "$PC_ROOT/p-docs" $((PC_NOW - PC_DAY)) init
|
||||
|
||||
# p-lower: lowercase plan.md in the root. On a case-insensitive filesystem
|
||||
# (APFS default) `[ -f PLAN.md ]` is TRUE here, so a board that asks the
|
||||
# filesystem alone would report a plan that is not there. The name has to be
|
||||
# compared byte for byte against what the directory actually stores (e.g. a
|
||||
# directory listing matched exactly against PLAN.md).
|
||||
mkrepo "$PC_ROOT/p-lower"; pc_state "$PC_ROOT/p-lower" p-lower planned
|
||||
echo "# plan" > "$PC_ROOT/p-lower/plan.md"
|
||||
pc_commit_at "$PC_ROOT/p-lower" $((PC_NOW - PC_DAY)) init
|
||||
|
||||
# --- Ground truth for the fixtures (these must be green today) -------------
|
||||
[ -z "$(git -C "$PC_ROOT/p-stale" status --porcelain)" ]
|
||||
check "plan: fixture - p-stale is committed clean" $?
|
||||
PC_P_STALE_EXP="$(( ($(pc_last_commit "$PC_ROOT/p-stale") - $(pc_mtime "$PC_ROOT/p-stale/PLAN.md")) / PC_DAY ))d"
|
||||
[ "$PC_P_STALE_EXP" != "0d" ]
|
||||
check "plan: fixture - p-stale's expected value is computed from its timestamps and is not 0d ($PC_P_STALE_EXP)" $?
|
||||
[ "$(pc_mtime "$PC_ROOT/p-fresh/PLAN.md")" -ge "$(pc_last_commit "$PC_ROOT/p-fresh")" ]
|
||||
check "plan: fixture - p-fresh's PLAN.md is newer than its last commit" $?
|
||||
if [ -f "$PC_ROOT/p-lower/PLAN.md" ]; then PC_LOWER_ARMED=0; else PC_LOWER_ARMED=1; fi
|
||||
check "plan: fixture - the case trap is armed here - [ -f PLAN.md ] is true for plan.md (case-insensitive fs)" "$PC_LOWER_ARMED"
|
||||
ls -1 "$PC_ROOT/p-lower" | grep -qx 'PLAN.md'; PC_rc=$?
|
||||
[ "$PC_rc" -ne 0 ]; check "plan: fixture - a byte-exact directory listing does NOT see PLAN.md in p-lower" $?
|
||||
|
||||
PC_TABLE="$(pc_board)"
|
||||
PC_N_REPOS="$(printf '%s\n' "$PC_TABLE" | sed -n 's/^BOARD .*(\([0-9][0-9]*\) repo).*/\1/p' | head -1)"
|
||||
[ "${PC_N_REPOS:-0}" -eq 5 ]; check "plan: fixture - board scanned the 5 fixture repos (header says ${PC_N_REPOS:-nothing})" $?
|
||||
|
||||
# --- 1. Three different values from three repos ----------------------------
|
||||
printf '%s\n' "$PC_TABLE" | grep -q '^REPO .* PLAN '
|
||||
check "plan 1: the table header carries a PLAN column" $?
|
||||
[ "$(pc_table_cell "$PC_TABLE" p-none)" = "-" ]; check "plan 1: no root PLAN.md -> '-'" $?
|
||||
[ "$(pc_table_cell "$PC_TABLE" p-fresh)" = "0d" ]; check "plan 1: PLAN.md newer than the last commit -> '0d'" $?
|
||||
[ "$(pc_table_cell "$PC_TABLE" p-stale)" = "$PC_P_STALE_EXP" ]
|
||||
check "plan 1: PLAN.md older than the last commit -> '$PC_P_STALE_EXP' (from the fixture's own timestamps)" $?
|
||||
PC_V3="$(pc_table_cell "$PC_TABLE" p-none) $(pc_table_cell "$PC_TABLE" p-fresh) $(pc_table_cell "$PC_TABLE" p-stale)"
|
||||
[ "$(printf '%s\n' $PC_V3 | sort -u | wc -l | tr -d ' ')" -eq 3 ]
|
||||
check "plan 1: the three fixtures show three DIFFERENT values ($PC_V3)" $?
|
||||
|
||||
# --- 2. Wrong place and wrong case are not a plan ---------------------------
|
||||
[ "$(pc_table_cell "$PC_TABLE" p-docs)" = "-" ]; check "plan 2: docs/PLAN.md only -> '-'" $?
|
||||
[ "$(pc_table_cell "$PC_TABLE" p-lower)" = "-" ]; check "plan 2: lowercase plan.md in the root -> '-' (byte-exact name)" $?
|
||||
|
||||
# --- 3. --row carries plan= with the table's value ---------------------------
|
||||
for PC_r in p-none p-fresh p-stale p-docs p-lower; do
|
||||
PC_RV="$(pc_row_plan "$(pc_board --row "$PC_r")")"
|
||||
PC_TV="$(pc_table_cell "$PC_TABLE" "$PC_r")"
|
||||
[ -n "$PC_RV" ] && [ "$PC_RV" = "$PC_TV" ]
|
||||
check "plan 3: --row $PC_r has plan=${PC_RV:-<missing>} equal to the table cell (${PC_TV:-<missing>})" $?
|
||||
done
|
||||
[ "$(pc_board --row p-none | grep -c '^plan=')" -eq 1 ]
|
||||
check "plan 3: --row emits exactly one plan= line" $?
|
||||
[ "$(pc_board --row p-none | tail -1 | cut -d= -f1)" = "neste" ]
|
||||
check "plan 3: neste= is still the last --row line" $?
|
||||
|
||||
# --- 4. Not in the sort, not in --plan blocks, not summed --------------------
|
||||
# Two repos identical in every sorted property; run 1 gives s-a the stale plan
|
||||
# and s-b the fresh one, run 2 swaps them. Any sort key built on PLAN would
|
||||
# reverse them in one of the two runs.
|
||||
PC_SROOT="$(mktemp -d)"
|
||||
for PC_r in s-a s-b; do
|
||||
mkrepo "$PC_SROOT/$PC_r"; pc_state "$PC_SROOT/$PC_r" "$PC_r" planned
|
||||
echo "<!-- route: path=known; verification=strong; reversibility=cheap; scope=local; rationale=x -->" >> "$PC_SROOT/$PC_r/STATE.md"
|
||||
: > "$PC_SROOT/$PC_r/PLAN.md"
|
||||
pc_commit_at "$PC_SROOT/$PC_r" $((PC_NOW - PC_DAY)) init
|
||||
pc_touch_at "$PC_SROOT/$PC_r/STATE.md" $((PC_NOW - 2 * PC_DAY))
|
||||
done
|
||||
pc_touch_at "$PC_SROOT/s-a/PLAN.md" $((PC_NOW - 9 * PC_DAY)); pc_touch_at "$PC_SROOT/s-b/PLAN.md" "$PC_NOW"
|
||||
PC_T1="$(CLAUDE_COORD_DIR="$PC_COORD" "$BOARD" --roots "$PC_SROOT" 2>/dev/null)"; PC_P1="$(CLAUDE_COORD_DIR="$PC_COORD" "$BOARD" --roots "$PC_SROOT" --plan 2>/dev/null)"
|
||||
pc_touch_at "$PC_SROOT/s-a/PLAN.md" "$PC_NOW"; pc_touch_at "$PC_SROOT/s-b/PLAN.md" $((PC_NOW - 9 * PC_DAY))
|
||||
PC_T2="$(CLAUDE_COORD_DIR="$PC_COORD" "$BOARD" --roots "$PC_SROOT" 2>/dev/null)"; PC_P2="$(CLAUDE_COORD_DIR="$PC_COORD" "$BOARD" --roots "$PC_SROOT" --plan 2>/dev/null)"
|
||||
PC_A1="$(pc_table_cell "$PC_T1" s-a)"; PC_A2="$(pc_table_cell "$PC_T2" s-a)"
|
||||
[ -n "$PC_A1" ] && [ -n "$PC_A2" ] && [ "$PC_A1" != "$PC_A2" ]
|
||||
check "plan 4: precondition - the swap really changed s-a's PLAN value (${PC_A1:-<missing>} -> ${PC_A2:-<missing>})" $?
|
||||
PC_O1="$(printf '%s\n' "$PC_T1" | awk '$1 ~ /^s-/ {print $1}' | tr '\n' ' ')"
|
||||
PC_O2="$(printf '%s\n' "$PC_T2" | awk '$1 ~ /^s-/ {print $1}' | tr '\n' ' ')"
|
||||
[ -n "$PC_A1" ] && [ "$PC_O1" = "$PC_O2" ]; check "plan 4: table order is identical across the swap ($PC_O1| $PC_O2)" $?
|
||||
PC_Q1="$(printf '%s\n' "$PC_P1" | sed -n 's/^repo=//p' | tr '\n' ' ')"
|
||||
PC_Q2="$(printf '%s\n' "$PC_P2" | sed -n 's/^repo=//p' | tr '\n' ' ')"
|
||||
[ -n "$PC_A1" ] && [ -n "$PC_Q1" ] && [ "$PC_Q1" = "$PC_Q2" ]; check "plan 4: --plan order is identical across the swap ($PC_Q1| $PC_Q2)" $?
|
||||
if printf '%s\n' "$PC_P1" | grep -qi '^plan='; then PC_rc=1; else PC_rc=0; fi
|
||||
[ -n "$PC_A1" ] && [ "$PC_rc" -eq 0 ]; check "plan 4: --plan blocks carry no plan= key" $?
|
||||
PC_S1="$(printf '%s\n' "$PC_T1" | grep '^SUM:')"; PC_S2="$(printf '%s\n' "$PC_T2" | grep '^SUM:')"
|
||||
if printf '%s' "$PC_S1" | grep -qi 'plan'; then PC_rc=1; else PC_rc=0; fi
|
||||
[ -n "$PC_A1" ] && [ "$PC_rc" -eq 0 ] && [ "$PC_S1" = "$PC_S2" ]; check "plan 4: the SUM line neither mentions PLAN nor moves with it" $?
|
||||
/bin/rm -rf "$PC_SROOT" 2>/dev/null
|
||||
|
||||
# --- 5. Replay: the shape C failed on ----------------------------------------
|
||||
# Many commits, no root PLAN.md, the destination written in docs/*.local.md,
|
||||
# and a release tag late in the window. A heuristic keyed on "activity without
|
||||
# a release" went silent at the tag; this column must read '-' at every step.
|
||||
PC_LROOT="$(mktemp -d)"
|
||||
PC_L="$PC_LROOT/loop-repo"
|
||||
mkrepo "$PC_L"; pc_state "$PC_L" loop-repo in-progress
|
||||
mkdir -p "$PC_L/docs"; echo "destination lives here" > "$PC_L/docs/destination.local.md"
|
||||
PC_BASE=$((PC_NOW - 60 * PC_DAY)); PC_HITS=0; PC_STEPS=0
|
||||
PC_i=0
|
||||
while [ "$PC_i" -lt 6 ]; do
|
||||
echo "step $PC_i" > "$PC_L/work.txt"
|
||||
pc_commit_at "$PC_L" $((PC_BASE + PC_i * 10 * PC_DAY)) "step $PC_i"
|
||||
[ "$PC_i" -eq 4 ] && git -C "$PC_L" -c tag.gpgSign=false tag v9.9.0 >/dev/null 2>&1
|
||||
PC_LT="$(CLAUDE_COORD_DIR="$PC_COORD" "$BOARD" --roots "$PC_LROOT" 2>/dev/null)"
|
||||
PC_STEPS=$((PC_STEPS + 1))
|
||||
[ "$(pc_table_cell "$PC_LT" loop-repo)" = "-" ] && PC_HITS=$((PC_HITS + 1))
|
||||
PC_i=$((PC_i + 1))
|
||||
done
|
||||
[ -n "$(git -C "$PC_L" tag -l v9.9.0)" ]; check "plan 5: fixture - the release tag exists in the replay" $?
|
||||
[ "$PC_HITS" -eq "$PC_STEPS" ]; check "plan 5: '-' at every step of the replay, before and after the tag ($PC_HITS of $PC_STEPS)" $?
|
||||
/bin/rm -rf "$PC_LROOT" 2>/dev/null
|
||||
|
||||
# --- 6. Denominator: one value per scanned repo ------------------------------
|
||||
PC_WITH=0
|
||||
for PC_r in p-none p-fresh p-stale p-docs p-lower; do
|
||||
[ -n "$(pc_table_cell "$PC_TABLE" "$PC_r")" ] && PC_WITH=$((PC_WITH + 1))
|
||||
done
|
||||
# A non-empty, non-zero denominator first: with an empty scan both sides are 0
|
||||
# and the comparison alone would pass on nothing.
|
||||
[ "${PC_N_REPOS:-0}" -gt 0 ] && [ "$PC_WITH" -eq "$PC_N_REPOS" ]
|
||||
check "plan 6: every scanned repo has a PLAN value ($PC_WITH of ${PC_N_REPOS:-?})" $?
|
||||
printf '%s\n' "$PC_TABLE" | grep -q '^undersoekt: '
|
||||
check "plan 6: the scan's own denominator line is still printed" $?
|
||||
|
||||
# --- 7. Legend -----------------------------------------------------------------
|
||||
PC_LEG="$(printf '%s\n' "$PC_TABLE" | awk '/^PLAN = /{on=1} on && /^[A-Z]+ = / && !/^PLAN = /{on=0} on')"
|
||||
[ -n "$PC_LEG" ]; check "plan 7: the legend has a 'PLAN = ' entry" $?
|
||||
printf '%s' "$PC_LEG" | grep -q 'IKKE' && printf '%s' "$PC_LEG" | grep -qi 'felle'
|
||||
check "plan 7: the legend says the column does NOT judge whether the criterion can be falsified" $?
|
||||
printf '%s' "$PC_LEG" | grep -q 'touch' && printf '%s' "$PC_LEG" | grep -q 'checkout'
|
||||
check "plan 7: the legend says touch/checkout reset the mtime" $?
|
||||
if printf '%s' "$PC_LEG" | LC_ALL=C grep -q '[^ -~]'; then PC_rc=1; else PC_rc=0; fi
|
||||
[ -n "$PC_LEG" ] && [ "$PC_rc" -eq 0 ]; check "plan 7: the legend is ASCII like the rest of the table" $?
|
||||
|
||||
/bin/rm -rf "$PC_ROOT" "$PC_COORD" 2>/dev/null
|
||||
|
||||
echo ""
|
||||
echo "board-selftest: $PASS passed, $FAIL failed"
|
||||
[ "$FAIL" -eq 0 ] || exit 1
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue