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
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@
|
|||
# STATE.md board line - optional machine-readable field (see below)
|
||||
# git status --porcelain - uncommitted risk
|
||||
# git log -1 --format=%ct - when anything last landed (the SISTE column)
|
||||
# <repo>/PLAN.md mtime - the PLAN column: existence (byte-exact name) and
|
||||
# days behind the last commit. Never its content.
|
||||
# DISPLAY only and not in the sort.
|
||||
# ~/.claude/coord/<repo>/orders - PENDING orders (the ORDRE column).
|
||||
# Rendered N:Md - the count AND the age of the
|
||||
# OLDEST order, never the newest and never a mean:
|
||||
|
|
@ -820,13 +823,38 @@ printf '%s\n' "$REPOS" | while IFS= read -r d; do
|
|||
lastd=-1; lastcol="-"
|
||||
fi
|
||||
|
||||
# PLAN: does this repo keep a destination where sessions actually look, and
|
||||
# is anyone touching it? A FILESYSTEM reading only - PLAN.md's content is
|
||||
# never read, so no wording inside it can move this cell. `-` = no file
|
||||
# named exactly PLAN.md in the root; `Nd` = whole days its mtime lies behind
|
||||
# the last commit (0d when it is as new or newer). The name is matched BYTE
|
||||
# FOR BYTE through a glob, never by `[ -f PLAN.md ]` alone: APFS is
|
||||
# case-insensitive by default, so that test is true for a `plan.md` and
|
||||
# would report a plan that is not there. A glob matches the names readdir
|
||||
# returns, as stored. A PLAN.md with no commit to compare against, or an
|
||||
# unreadable mtime, is `?` - never `-`, which means "no plan", and never
|
||||
# `0d`, which would be a measurement that did not happen.
|
||||
# Display only: not in any sort key, not in --plan, not in SUM.
|
||||
plancol="-"
|
||||
for pl_f in "$d"/PLAN.m[d]; do
|
||||
[ "${pl_f##*/}" = "PLAN.md" ] && [ -f "$pl_f" ] || continue
|
||||
pl_m="$(stat -f %m "$pl_f" 2>/dev/null)"
|
||||
if [ -z "$pl_m" ] || [ -z "$lastct" ]; then
|
||||
plancol="?"
|
||||
elif [ "$pl_m" -ge "$lastct" ]; then
|
||||
plancol="0d"
|
||||
else
|
||||
plancol="$(( (lastct - pl_m) / 86400 ))d"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ! -f "$state" ]; then
|
||||
# No plan file, so no plan age: ALDER is blank rather than quietly showing
|
||||
# the commit age under a heading that means something else everywhere else
|
||||
# in the table. The sort key keeps using it - order is unchanged.
|
||||
printf '5|%06d|%s|-|-|%s|%s|-|%s|%s|-|%s|%s|%s|%s|(ingen STATE.md)\n' \
|
||||
printf '5|%06d|%s|-|-|%s|%s|-|%s|%s|-|%s|%s|%s|%s|-|%s|(ingen STATE.md)\n' \
|
||||
"$lastd" "$name" "$inbox" "$dirty" "$lastcol" "$d" \
|
||||
"$orders" "$ordersage" "$claimed" "$claimedage"
|
||||
"$orders" "$ordersage" "$claimed" "$claimedage" "$plancol"
|
||||
continue
|
||||
fi
|
||||
|
||||
|
|
@ -918,10 +946,11 @@ printf '%s\n' "$REPOS" | while IFS= read -r d; do
|
|||
# `voy` sits between claimedage and neste, never after it: `neste` is free
|
||||
# prose lifted out of a STATE.md and may contain a literal '|', so it has to
|
||||
# stay the last field on the line.
|
||||
# `plan` sits between voy and neste for the same reason.
|
||||
voy="$(voyage_cell "$d")"
|
||||
printf '%s|%06d|%s|%s|%s|%s|%s|%sd|%s|%s|%s|%s|%s|%s|%s|%s|%s\n' \
|
||||
printf '%s|%06d|%s|%s|%s|%s|%s|%sd|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s\n' \
|
||||
"$bucket" "$age" "$name" "$disp" "$cost" "$inbox" "$dirty" "$age" "$lastcol" "$d" \
|
||||
"${blockedon:--}" "$orders" "$ordersage" "$claimed" "$claimedage" "$voy" "$neste"
|
||||
"${blockedon:--}" "$orders" "$ordersage" "$claimed" "$claimedage" "$voy" "$plancol" "$neste"
|
||||
done > "${TMPDIR:-/tmp}/board.$$"
|
||||
|
||||
RECORDS="${TMPDIR:-/tmp}/board.$$"
|
||||
|
|
@ -1078,20 +1107,20 @@ awk -F'|' '$4 ~ /^blocked/ && $11 != "-" && $11 != "" {print $11}' "$RECORDS" \
|
|||
|
||||
hdr() {
|
||||
printf '\n%s\n' "$1"
|
||||
printf '%-32s %-34s %-14s %4s %7s %7s %7s %4s %6s %6s %s\n' \
|
||||
"REPO" "STATUS" "KOST" "INN" "ORDRE" "FLY" "VOY" "DRT" "ALDER" "SISTE" "NESTE"
|
||||
printf '%-32s %-34s %-14s %4s %7s %7s %7s %4s %6s %6s %5s %s\n' \
|
||||
"REPO" "STATUS" "KOST" "INN" "ORDRE" "FLY" "VOY" "DRT" "ALDER" "SISTE" "PLAN" "NESTE"
|
||||
}
|
||||
|
||||
rows() {
|
||||
awk -F'|' -v b="$1" '$1==b' "$RECORDS" | sort -t'|' -k2,2n | \
|
||||
while IFS='|' read -r bucket sortkey name status cost inbox dirty age last dir blockedon orders ordersage claimed claimedage voy neste; do
|
||||
while IFS='|' read -r bucket sortkey name status cost inbox dirty age last dir blockedon orders ordersage claimed claimedage voy plan neste; do
|
||||
# `N:age` only when there is something to date. An empty queue prints a
|
||||
# bare 0, never "0:0d" - absence must not borrow the shape of a
|
||||
# measurement.
|
||||
o_cell="$orders"; [ "$ordersage" = "-" ] || o_cell="$orders:$ordersage"
|
||||
c_cell="$claimed"; [ "$claimedage" = "-" ] || c_cell="$claimed:$claimedage"
|
||||
printf '%-32s %-34s %-14s %4s %7s %7s %7s %4s %6s %6s %s\n' \
|
||||
"$name" "$status" "$cost" "$inbox" "$o_cell" "$c_cell" "$voy" "$dirty" "$age" "$last" "$(trunc "$neste" "$NESTE_WIDTH")"
|
||||
printf '%-32s %-34s %-14s %4s %7s %7s %7s %4s %6s %6s %5s %s\n' \
|
||||
"$name" "$status" "$cost" "$inbox" "$o_cell" "$c_cell" "$voy" "$dirty" "$age" "$last" "$plan" "$(trunc "$neste" "$NESTE_WIDTH")"
|
||||
done
|
||||
}
|
||||
|
||||
|
|
@ -1281,7 +1310,7 @@ brief() {
|
|||
FILENAME==OWF { ow[$1] = $2 + 0; next }
|
||||
{ name = $3; owed = (name in ow) ? ow[name] : 0; if (owed > 0) print owed, $0 }
|
||||
' "$OWED" "$RECORDS" | sort -t'|' -k1,1nr | \
|
||||
while IFS='|' read -r owed bucket sortkey name status cost inbox dirty age last dir blockedon orders ordersage claimed claimedage voy neste; do
|
||||
while IFS='|' read -r owed bucket sortkey name status cost inbox dirty age last dir blockedon orders ordersage claimed claimedage voy plan neste; do
|
||||
printf ' %-32s INN %-4s %s\n' "$name" "$owed" "$status"
|
||||
# Wrapped, not cut - the whole line is the point, but a 500-character one
|
||||
# is unreadable in a file nobody watched being written. Locale is set for
|
||||
|
|
@ -1409,7 +1438,7 @@ dispatch() {
|
|||
# `neste` is the LAST record field and is taken as "field N onward". This
|
||||
# index once read 14 and was never moved as fields were inserted before
|
||||
# `neste`, gluing three other columns onto the prose - move it with the record.
|
||||
d_neste="$(printf '%s' "$d_rec" | cut -d'|' -f17-)"
|
||||
d_neste="$(printf '%s' "$d_rec" | cut -d'|' -f18-)"
|
||||
[ -d "$d_dir" ] || { echo "board: the directory for $D_REPO does not exist: $d_dir" >&2; exit 2; }
|
||||
|
||||
# Rejected traits REFUSE. Degrading to a command without them is the worst
|
||||
|
|
@ -1893,7 +1922,7 @@ plan() {
|
|||
echo ""
|
||||
|
||||
pn=0
|
||||
while IFS='|' read -r why bucket sortkey name status cost inbox dirty age last dir blockedon orders ordersage claimed claimedage voy neste; do
|
||||
while IFS='|' read -r why bucket sortkey name status cost inbox dirty age last dir blockedon orders ordersage claimed claimedage voy plan neste; do
|
||||
[ -n "$name" ] || continue
|
||||
pn=$((pn + 1))
|
||||
printf 'tab=%s\n' "$pn"
|
||||
|
|
@ -1953,7 +1982,7 @@ inbox_plan() {
|
|||
FILENAME==RCF {
|
||||
name = $3
|
||||
rc_bucket[name] = $1; rc_status[name] = $4; rc_cost[name] = $5
|
||||
rc_dir[name] = $10; rc_neste[name] = $17
|
||||
rc_dir[name] = $10; rc_neste[name] = $18
|
||||
next
|
||||
}
|
||||
{
|
||||
|
|
@ -2186,10 +2215,11 @@ row_one() {
|
|||
r_fly="$(printf '%s' "$r_rec" | cut -d'|' -f14)"
|
||||
r_flyage="$(printf '%s' "$r_rec" | cut -d'|' -f15)"
|
||||
r_voy="$(printf '%s' "$r_rec" | cut -d'|' -f16)"
|
||||
r_plan="$(printf '%s' "$r_rec" | cut -d'|' -f17)"
|
||||
# `neste` is free prose and may contain a literal '|', which is why it is the
|
||||
# last field of the record - so it is taken as "everything from 17 on", never
|
||||
# as field 17.
|
||||
r_neste="$(printf '%s' "$r_rec" | cut -d'|' -f17-)"
|
||||
# last field of the record - so it is taken as "everything from 18 on", never
|
||||
# as field 18.
|
||||
r_neste="$(printf '%s' "$r_rec" | cut -d'|' -f18-)"
|
||||
|
||||
# The TOKEN, not the display string: the table renders a blocked repo as
|
||||
# `blocked>target` because one column has to carry both, and this rendering
|
||||
|
|
@ -2217,6 +2247,7 @@ row_one() {
|
|||
echo "alder=$r_alder"
|
||||
echo "siste=$r_siste"
|
||||
echo "upushet=$r_unpushed"
|
||||
echo "plan=$r_plan"
|
||||
# LAST, and for the same reason it is last in the record: it is free prose
|
||||
# lifted out of a STATE.md, so anything placed after it would be unreachable
|
||||
# for a consumer reading from the end. Printed WHOLE - the 38-character cut
|
||||
|
|
@ -2259,6 +2290,15 @@ echo " Baerer samme N:Md - alderen paa den ELDSTE claimen. Ingenting frigir
|
|||
echo "VOY = Voyage-prosjekter (brief i omloep). N:Md = antall og alderen paa det"
|
||||
echo " STALESTE prosjektet - dager siden NYESTE artefakt der, dvs. hvor lenge"
|
||||
echo " ingenting har skjedd. Bart 0 = ingen prosjekter. Detaljer: --voyage."
|
||||
# PLAN is a filesystem reading and nothing more. It shows WHERE a destination
|
||||
# is missing or untouched; whether the criterion inside it can be falsified is
|
||||
# a judgement no column can make, and the legend says so rather than letting
|
||||
# `0d` read as "this repo has a good plan".
|
||||
echo "PLAN = rot-PLAN.md: '-' = ingen fil som heter noyaktig PLAN.md. Nd = dager"
|
||||
echo " planens mtime ligger bak siste commit (0d = like ny eller nyere)."
|
||||
echo " '?' = plan uten commit aa sammenligne med. Bedommer IKKE om kriteriet"
|
||||
echo " kan felles, og leser aldri innholdet. touch/checkout nullstiller mtime"
|
||||
echo " (samme forbehold som ALDER). Kun visning: aldri sortert, aldri summert."
|
||||
echo "ALDER = dager siden STATE.md endret. SISTE = dager siden siste commit."
|
||||
|
||||
[ "$(count 1)" -gt 0 ] && { hdr "BLOKKERT (venter paa ekstern avhengighet)"; rows 1; }
|
||||
|
|
|
|||
|
|
@ -1,235 +0,0 @@
|
|||
#!/bin/bash
|
||||
# plan-column-eval.sh - the RED gate for board.sh's PLAN column, written
|
||||
# before the column exists. It is NOT one of the five selftest suites and is
|
||||
# deliberately not wired into `npm test`: a committed red check inside
|
||||
# board-selftest.sh would turn the whole suite red on main and break the
|
||||
# README number check, so "red for the right reason" could no longer be told
|
||||
# apart from "red because something else broke". The session that builds the
|
||||
# column moves these checks into board-selftest.sh (and updates the counts)
|
||||
# once they are green.
|
||||
#
|
||||
# What the column is (operator decision 2026-09-17): a DISPLAY-ONLY reading of
|
||||
# the filesystem, one value per scanned repo:
|
||||
# - no file named exactly PLAN.md in the repo root
|
||||
# Nd PLAN.md exists; N = whole days its mtime lies BEHIND the last commit
|
||||
# (0d when PLAN.md is as new as, or newer than, the last commit)
|
||||
# It never reads PLAN.md's content, never enters a sort, a --plan block or a
|
||||
# sum, and does not judge whether a criterion can be falsified.
|
||||
#
|
||||
# Table cells are located by the header, never by a fixed offset: the column's
|
||||
# position is the builder's choice. The locator splits header and row on runs
|
||||
# of 2+ spaces, which relies on two properties the table already has - every
|
||||
# cell is followed by at least two spaces, and NESTE (free prose) is last.
|
||||
#
|
||||
# ASCII only, bash 3.2 safe, never touches ~/repos or ~/.claude/coord.
|
||||
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; }
|
||||
|
||||
NOW="$(date +%s)"
|
||||
DAY=86400
|
||||
|
||||
mkrepo() { mkdir -p "$1" && git -C "$1" init -q 2>/dev/null; }
|
||||
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"
|
||||
}
|
||||
# commit_at <dir> <epoch> <msg>: commit everything with a fixed committer date.
|
||||
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
|
||||
}
|
||||
touch_at() { touch -t "$(date -r "$2" +%Y%m%d%H%M.%S)" "$1"; }
|
||||
last_commit() { git -C "$1" log -1 --format=%ct 2>/dev/null; }
|
||||
mtime() { stat -f %m "$1"; }
|
||||
|
||||
# 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.
|
||||
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 }'
|
||||
}
|
||||
row_plan() { printf '%s\n' "$1" | sed -n 's/^plan=//p'; }
|
||||
board() { "$BOARD" --roots "$ROOT" "$@" 2>/dev/null; }
|
||||
|
||||
echo "plan-column-eval (root: $ROOT, mailbox: $CLAUDE_COORD_DIR)"
|
||||
|
||||
# --- Fixtures ---------------------------------------------------------------
|
||||
|
||||
# p-none: no PLAN.md anywhere.
|
||||
mkrepo "$ROOT/p-none"; state "$ROOT/p-none" p-none planned
|
||||
commit_at "$ROOT/p-none" $((NOW - 3 * 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 "$ROOT/p-fresh"; state "$ROOT/p-fresh" p-fresh planned
|
||||
commit_at "$ROOT/p-fresh" $((NOW - 2 * DAY)) init
|
||||
: > "$ROOT/p-fresh/PLAN.md"
|
||||
touch_at "$ROOT/p-fresh/PLAN.md" "$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 "$ROOT/p-stale"; state "$ROOT/p-stale" p-stale planned
|
||||
printf '# PLAN\nPLAN=99d\n' > "$ROOT/p-stale/PLAN.md"
|
||||
P_STALE_CT=$((NOW - 3600))
|
||||
commit_at "$ROOT/p-stale" "$P_STALE_CT" init
|
||||
touch_at "$ROOT/p-stale/PLAN.md" $((P_STALE_CT - 5 * DAY - 3 * 3600))
|
||||
|
||||
# p-docs: PLAN.md in docs/ only - the wrong place by convention.
|
||||
mkrepo "$ROOT/p-docs"; state "$ROOT/p-docs" p-docs planned
|
||||
mkdir -p "$ROOT/p-docs/docs"; echo "# PLAN" > "$ROOT/p-docs/docs/PLAN.md"
|
||||
commit_at "$ROOT/p-docs" $((NOW - 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 "$ROOT/p-lower"; state "$ROOT/p-lower" p-lower planned
|
||||
echo "# plan" > "$ROOT/p-lower/plan.md"
|
||||
commit_at "$ROOT/p-lower" $((NOW - DAY)) init
|
||||
|
||||
# --- Ground truth for the fixtures (these must be green today) -------------
|
||||
[ -z "$(git -C "$ROOT/p-stale" status --porcelain)" ]
|
||||
check "fixture: p-stale is committed clean" $?
|
||||
P_STALE_EXP="$(( ($(last_commit "$ROOT/p-stale") - $(mtime "$ROOT/p-stale/PLAN.md")) / DAY ))d"
|
||||
[ "$P_STALE_EXP" != "0d" ]
|
||||
check "fixture: p-stale's expected value is computed from its timestamps and is not 0d ($P_STALE_EXP)" $?
|
||||
[ "$(mtime "$ROOT/p-fresh/PLAN.md")" -ge "$(last_commit "$ROOT/p-fresh")" ]
|
||||
check "fixture: p-fresh's PLAN.md is newer than its last commit" $?
|
||||
if [ -f "$ROOT/p-lower/PLAN.md" ]; then LOWER_ARMED=0; else LOWER_ARMED=1; fi
|
||||
check "fixture: the case trap is armed here - [ -f PLAN.md ] is true for plan.md (case-insensitive fs)" "$LOWER_ARMED"
|
||||
ls -1 "$ROOT/p-lower" | grep -qx 'PLAN.md'; rc=$?
|
||||
[ "$rc" -ne 0 ]; check "fixture: a byte-exact directory listing does NOT see PLAN.md in p-lower" $?
|
||||
|
||||
TABLE="$(board)"
|
||||
N_REPOS="$(printf '%s\n' "$TABLE" | sed -n 's/^BOARD .*(\([0-9][0-9]*\) repo).*/\1/p' | head -1)"
|
||||
[ "${N_REPOS:-0}" -eq 5 ]; check "fixture: board scanned the 5 fixture repos (header says ${N_REPOS:-nothing})" $?
|
||||
|
||||
# --- 1. Three different values from three repos ----------------------------
|
||||
printf '%s\n' "$TABLE" | grep -q '^REPO .* PLAN '
|
||||
check "1: the table header carries a PLAN column" $?
|
||||
[ "$(table_cell "$TABLE" p-none)" = "-" ]; check "1: no root PLAN.md -> '-'" $?
|
||||
[ "$(table_cell "$TABLE" p-fresh)" = "0d" ]; check "1: PLAN.md newer than the last commit -> '0d'" $?
|
||||
[ "$(table_cell "$TABLE" p-stale)" = "$P_STALE_EXP" ]
|
||||
check "1: PLAN.md older than the last commit -> '$P_STALE_EXP' (from the fixture's own timestamps)" $?
|
||||
V3="$(table_cell "$TABLE" p-none) $(table_cell "$TABLE" p-fresh) $(table_cell "$TABLE" p-stale)"
|
||||
[ "$(printf '%s\n' $V3 | sort -u | wc -l | tr -d ' ')" -eq 3 ]
|
||||
check "1: the three fixtures show three DIFFERENT values ($V3)" $?
|
||||
|
||||
# --- 2. Wrong place and wrong case are not a plan ---------------------------
|
||||
[ "$(table_cell "$TABLE" p-docs)" = "-" ]; check "2: docs/PLAN.md only -> '-'" $?
|
||||
[ "$(table_cell "$TABLE" p-lower)" = "-" ]; check "2: lowercase plan.md in the root -> '-' (byte-exact name)" $?
|
||||
|
||||
# --- 3. --row carries plan= with the table's value ---------------------------
|
||||
for r in p-none p-fresh p-stale p-docs p-lower; do
|
||||
RV="$(row_plan "$(board --row "$r")")"
|
||||
TV="$(table_cell "$TABLE" "$r")"
|
||||
[ -n "$RV" ] && [ "$RV" = "$TV" ]
|
||||
check "3: --row $r has plan=${RV:-<missing>} equal to the table cell (${TV:-<missing>})" $?
|
||||
done
|
||||
[ "$(board --row p-none | grep -c '^plan=')" -eq 1 ]
|
||||
check "3: --row emits exactly one plan= line" $?
|
||||
[ "$(board --row p-none | tail -1 | cut -d= -f1)" = "neste" ]
|
||||
check "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.
|
||||
SROOT="$(mktemp -d)"
|
||||
for r in s-a s-b; do
|
||||
mkrepo "$SROOT/$r"; state "$SROOT/$r" "$r" planned
|
||||
echo "<!-- route: path=known; verification=strong; reversibility=cheap; scope=local; rationale=x -->" >> "$SROOT/$r/STATE.md"
|
||||
: > "$SROOT/$r/PLAN.md"
|
||||
commit_at "$SROOT/$r" $((NOW - DAY)) init
|
||||
touch_at "$SROOT/$r/STATE.md" $((NOW - 2 * DAY))
|
||||
done
|
||||
touch_at "$SROOT/s-a/PLAN.md" $((NOW - 9 * DAY)); touch_at "$SROOT/s-b/PLAN.md" "$NOW"
|
||||
T1="$("$BOARD" --roots "$SROOT" 2>/dev/null)"; P1="$("$BOARD" --roots "$SROOT" --plan 2>/dev/null)"
|
||||
touch_at "$SROOT/s-a/PLAN.md" "$NOW"; touch_at "$SROOT/s-b/PLAN.md" $((NOW - 9 * DAY))
|
||||
T2="$("$BOARD" --roots "$SROOT" 2>/dev/null)"; P2="$("$BOARD" --roots "$SROOT" --plan 2>/dev/null)"
|
||||
A1="$(table_cell "$T1" s-a)"; A2="$(table_cell "$T2" s-a)"
|
||||
[ -n "$A1" ] && [ -n "$A2" ] && [ "$A1" != "$A2" ]
|
||||
check "4: precondition - the swap really changed s-a's PLAN value (${A1:-<missing>} -> ${A2:-<missing>})" $?
|
||||
O1="$(printf '%s\n' "$T1" | awk '$1 ~ /^s-/ {print $1}' | tr '\n' ' ')"
|
||||
O2="$(printf '%s\n' "$T2" | awk '$1 ~ /^s-/ {print $1}' | tr '\n' ' ')"
|
||||
[ -n "$A1" ] && [ "$O1" = "$O2" ]; check "4: table order is identical across the swap ($O1| $O2)" $?
|
||||
Q1="$(printf '%s\n' "$P1" | sed -n 's/^repo=//p' | tr '\n' ' ')"
|
||||
Q2="$(printf '%s\n' "$P2" | sed -n 's/^repo=//p' | tr '\n' ' ')"
|
||||
[ -n "$A1" ] && [ -n "$Q1" ] && [ "$Q1" = "$Q2" ]; check "4: --plan order is identical across the swap ($Q1| $Q2)" $?
|
||||
if printf '%s\n' "$P1" | grep -qi '^plan='; then rc=1; else rc=0; fi
|
||||
[ -n "$A1" ] && [ "$rc" -eq 0 ]; check "4: --plan blocks carry no plan= key" $?
|
||||
S1="$(printf '%s\n' "$T1" | grep '^SUM:')"; S2="$(printf '%s\n' "$T2" | grep '^SUM:')"
|
||||
if printf '%s' "$S1" | grep -qi 'plan'; then rc=1; else rc=0; fi
|
||||
[ -n "$A1" ] && [ "$rc" -eq 0 ] && [ "$S1" = "$S2" ]; check "4: the SUM line neither mentions PLAN nor moves with it" $?
|
||||
/bin/rm -rf "$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.
|
||||
LROOT="$(mktemp -d)"
|
||||
L="$LROOT/loop-repo"
|
||||
mkrepo "$L"; state "$L" loop-repo in-progress
|
||||
mkdir -p "$L/docs"; echo "destination lives here" > "$L/docs/destination.local.md"
|
||||
BASE=$((NOW - 60 * DAY)); HITS=0; STEPS=0
|
||||
i=0
|
||||
while [ "$i" -lt 6 ]; do
|
||||
echo "step $i" > "$L/work.txt"
|
||||
commit_at "$L" $((BASE + i * 10 * DAY)) "step $i"
|
||||
[ "$i" -eq 4 ] && git -C "$L" -c tag.gpgSign=false tag v9.9.0 >/dev/null 2>&1
|
||||
LT="$("$BOARD" --roots "$LROOT" 2>/dev/null)"
|
||||
STEPS=$((STEPS + 1))
|
||||
[ "$(table_cell "$LT" loop-repo)" = "-" ] && HITS=$((HITS + 1))
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ -n "$(git -C "$L" tag -l v9.9.0)" ]; check "5: fixture - the release tag exists in the replay" $?
|
||||
[ "$HITS" -eq "$STEPS" ]; check "5: '-' at every step of the replay, before and after the tag ($HITS of $STEPS)" $?
|
||||
/bin/rm -rf "$LROOT" 2>/dev/null
|
||||
|
||||
# --- 6. Denominator: one value per scanned repo ------------------------------
|
||||
WITH=0
|
||||
for r in p-none p-fresh p-stale p-docs p-lower; do
|
||||
[ -n "$(table_cell "$TABLE" "$r")" ] && WITH=$((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.
|
||||
[ "${N_REPOS:-0}" -gt 0 ] && [ "$WITH" -eq "$N_REPOS" ]
|
||||
check "6: every scanned repo has a PLAN value ($WITH of ${N_REPOS:-?})" $?
|
||||
printf '%s\n' "$TABLE" | grep -q '^undersoekt: '
|
||||
check "6: the scan's own denominator line is still printed" $?
|
||||
|
||||
# --- 7. Legend -----------------------------------------------------------------
|
||||
LEG="$(printf '%s\n' "$TABLE" | awk '/^PLAN = /{on=1} on && /^[A-Z]+ = / && !/^PLAN = /{on=0} on')"
|
||||
[ -n "$LEG" ]; check "7: the legend has a 'PLAN = ' entry" $?
|
||||
printf '%s' "$LEG" | grep -q 'IKKE' && printf '%s' "$LEG" | grep -qi 'felle'
|
||||
check "7: the legend says the column does NOT judge whether the criterion can be falsified" $?
|
||||
printf '%s' "$LEG" | grep -q 'touch' && printf '%s' "$LEG" | grep -q 'checkout'
|
||||
check "7: the legend says touch/checkout reset the mtime" $?
|
||||
if printf '%s' "$LEG" | LC_ALL=C grep -q '[^ -~]'; then rc=1; else rc=0; fi
|
||||
[ -n "$LEG" ] && [ "$rc" -eq 0 ]; check "7: the legend is ASCII like the rest of the table" $?
|
||||
|
||||
echo ""
|
||||
echo "plan-column-eval: $PASS passed, $FAIL failed"
|
||||
[ "$FAIL" -eq 0 ] || exit 1
|
||||
exit 0
|
||||
Loading…
Add table
Add a link
Reference in a new issue