feat(board): --voyage reports the Voyage briefs in flight

board read STATE lines and knew nothing about a brief, so a programme
running Voyage across several repos had no shared surface: nobody could
answer which briefs were in flight, in what phase, and who was waiting on
whom. Order 20260831T135934Z-696228720 fired the programme-level B1 row
once /trekplan had been delivered in llm-ingestion-okf, so the fields
were chosen against a run that had gone the whole way rather than guessed.

Adds board.sh --voyage (a sixth rendering of the same scan, read-only)
and a VOY column beside ORDRE and FLY, never summed with them and never
in the sort.

Detection is by PROPERTY, never by directory name: a directory holding
brief.md or brief.md.draft under any of the three planning locations the
convention recognises. It walks the FILESYSTEM, never the git index -
llm-ingestion-okf gitignores .claude/projects/ (087be0b), so an
index-based detector would report ZERO briefs in the one repo actually
running one.

The phase ladder measures ARTIFACTS, not sessions, and the legend says
so: /trekexecute leaves a file behind only in its multi-session form, so
a plan executed in one session leaves nothing and `plan` is the last
thing the filesystem can prove. Nothing here claims a session is alive -
the same refusal FLY carries.

Three absences that must not borrow the shape of a measurement:
brief_quality is read from the frontmatter block only and an absent field
reads `-`, never `complete` (only 8 of ~40 briefs on the real tree carry
it); research=- (never started) is distinct from research=0 (a directory
holding nothing); and --voyage always prints its own denominator rather
than rendering as an empty page.

Blocking decisions are counted at DECLARATION SITES, not mentions: the
pattern occurs on four lines of the real brief, of which one declares it,
so a bare grep -c answers 4 where the honest answer is 1.

The age is the NEWEST artifact, inverting the oldest-wins rule ORDRE and
FLY carry - an order queue's problem is the oldest item still waiting, a
project's problem is that its most recent activity is old.

Mutation-verified three ways: newest->oldest turns exactly one check red,
anchored pattern->bare string turns three red (the known-negative control
among them), filesystem->git ls-files turns 23 red. Live-verified against
the completed run: fase=plan, kvalitet=complete, blokkerende=1, gate=S4,
research=5 - the only project of 51 across 14 repos with an open blocking
decision.

Cost measured rather than assumed: 0.31.0 takes 6.3s over the real
52-repo tree, with VOY 7.7-9.2s. The first cut called stat once per
artifact and took 13.4s; the batched form is what makes the column
affordable. CLAUDE.md's old "~3s" claim did not survive the measurement
and is corrected.

Bounded gap, stated rather than closed: whether a detected project is
still "in flight" is not decided here. That needs a threshold, and a
threshold would make the board decide that work is abandoned - the
identical thing the order queue is already forbidden from doing.

board-selftest.sh: 325 -> 360 checks.

Co-Authored-By: Claude <claude-opus-5>
This commit is contained in:
Kjell Tore Guttormsen 2026-08-31 18:11:04 +02:00
commit 85cd628c3e
5 changed files with 701 additions and 21 deletions

View file

@ -207,7 +207,7 @@
# see the dispatch() comment for what happens when it is guessed.
#
# Usage: board.sh [--roots <dir>[,<dir>...]] [--plain]
# [--brief|--plan|--inbox-plan|--dispatch ...]
# [--brief|--plan|--inbox-plan|--dispatch|--voyage ...]
# [--focus "<prose>"]
# Env: CLAUDE_COORD_DIR overrides the mailbox root.
# BOARD_ROOTS overrides the default scan roots.
@ -223,6 +223,7 @@ PLAN=0
INBOX_PLAN=0
FOCUS=""
DISPATCH=0
VOYAGE=0
D_REPO=""; D_PROMPT=""; D_PANE=""; D_ORDER=""
D_PATH=""; D_VER=""; D_REV=""; D_SCOPE=""; D_RAT=""
@ -238,9 +239,9 @@ while [ $# -gt 0 ]; do
--roots) [ $# -ge 2 ] || { echo "board: --roots requires a value" >&2; exit 2; }
ROOTS="$2"; shift 2 ;;
# Four renderings of one scan, so exactly one may be selected: last wins.
--brief) BRIEF=1; PLAN=0; INBOX_PLAN=0; shift ;;
--plan) PLAN=1; BRIEF=0; INBOX_PLAN=0; shift ;;
--inbox-plan) INBOX_PLAN=1; BRIEF=0; PLAN=0; shift ;;
--brief) BRIEF=1; PLAN=0; INBOX_PLAN=0; VOYAGE=0; shift ;;
--plan) PLAN=1; BRIEF=0; INBOX_PLAN=0; VOYAGE=0; shift ;;
--inbox-plan) INBOX_PLAN=1; BRIEF=0; PLAN=0; VOYAGE=0; shift ;;
# Raw operator prose, forwarded verbatim by the driver: it does not
# tokenize, match or normalize, so every bit of that work is here. Same
# `shift 2` guard as --roots, for the same bash 3.2 reason.
@ -248,7 +249,8 @@ while [ $# -gt 0 ]; do
FOCUS="$2"; shift 2 ;;
# Dispatch rendering: one repo, one prompt file, one startup command. Same
# `shift 2` guard as every other value flag, for the same bash 3.2 reason.
--dispatch) DISPATCH=1; BRIEF=0; PLAN=0; INBOX_PLAN=0; shift ;;
--dispatch) DISPATCH=1; BRIEF=0; PLAN=0; INBOX_PLAN=0; VOYAGE=0; shift ;;
--voyage) VOYAGE=1; BRIEF=0; PLAN=0; INBOX_PLAN=0; DISPATCH=0; shift ;;
--repo) [ $# -ge 2 ] || { echo "board: --repo requires a value" >&2; exit 2; }
D_REPO="$2"; shift 2 ;;
--prompt-file) [ $# -ge 2 ] || { echo "board: --prompt-file requires a value" >&2; exit 2; }
@ -403,6 +405,169 @@ oldest_order_age() {
printf '%dd' $(( (NOW - ooa_oldest) / 86400 ))
}
# --- Voyage project detection (ORDRE 20260831T135934Z-696228720) ------------
# board read STATE lines and knew nothing about Voyage briefs, so a programme
# running Voyage across several repos had no shared surface: nobody could
# answer "which briefs are in flight, in what phase, and who waits on whom".
#
# Detection is by PROPERTY, never by directory name. The global CLAUDE.md says
# plans and briefs are defined by properties and recognises three locations
# (docs/, .claude/projects/, features/<n>-<name>/), so a directory counts as a
# Voyage project iff it HOLDS brief.md or brief.md.draft under one of them.
#
# It walks the FILESYSTEM and never `git ls-files`. Measured: llm-ingestion-okf
# gitignores .claude/projects/ (commit 087be0b, "keep .claude/projects/
# local-only"), so an index-based detector would report ZERO briefs in the one
# repo actually running a Voyage - a broken query returning a positive-looking
# null. Selftest section 29 pins it with a fixture whose ignore status is
# asserted as ground truth first.
voyage_dirs() {
for vd_g in "$1"/.claude/projects/*/ "$1"/docs/*/ "$1"/features/*/; do
[ -d "$vd_g" ] || continue
vd_p="${vd_g%/}"
if [ -f "$vd_p/brief.md" ] || [ -f "$vd_p/brief.md.draft" ]; then
printf '%s\n' "$vd_p"
fi
done
}
# The phase ladder, furthest artifact present wins. This is the one field that
# cannot be derived from a STATE.md at all, and it is the whole reason the row
# exists: brief.md.draft (the review gate has not cleared) -> brief.md ->
# research/*.md -> plan.md -> execute -> review.md.
#
# IT MEASURES ARTIFACTS, NOT SESSIONS, and the bound is stated rather than
# hidden: /trekexecute leaves a file behind only in its MULTI-session form
# (.session-state.local.json), so a plan executed in one session leaves nothing
# and `plan` is the last thing the filesystem can prove. Nothing here says a
# session is alive - the same refusal FLY carries, for the same reason.
voyage_phase() {
[ -f "$1/review.md" ] && { printf 'review'; return 0; }
[ -f "$1/.session-state.local.json" ] && { printf 'execute'; return 0; }
[ -f "$1/plan.md" ] && { printf 'plan'; return 0; }
for vp_f in "$1"/research/*.md; do
[ -f "$vp_f" ] && { printf 'research'; return 0; }
done
[ -f "$1/brief.md" ] && { printf 'brief'; return 0; }
[ -f "$1/brief.md.draft" ] && { printf 'brief-draft'; return 0; }
printf '?'
}
# Days since the NEWEST artifact in the project - "nothing has happened here
# for N days". This INVERTS the oldest-wins rule ORDRE and FLY carry, and the
# inversion is deliberate rather than an oversight: an order queue's problem is
# the oldest item still waiting, while a project's problem is that its most
# recent activity is old. Both answer "how long has this been a problem".
# `?` when a mtime could not be read, reported for the WHOLE reading: an
# unmeasured file could be the newest one, which makes "newest" a claim this
# function is no longer entitled to make.
voyage_age() {
# One `find` and one BATCHED `stat` (-exec ... +), never a stat per file: the
# per-file form forked once per artifact and cost 10s over the real tree,
# which would have made the plain table 4x slower for a column beside it.
va_files="$(find "$1" -type f 2>/dev/null)"
va_n="$(printf '%s\n' "$va_files" | grep -c . | tr -d ' ')"
[ "${va_n:-0}" -gt 0 ] || { printf '?'; return 0; }
va_ms="$(find "$1" -type f -exec stat -f %m {} + 2>/dev/null | grep '^[0-9][0-9]*$')"
va_mn="$(printf '%s\n' "$va_ms" | grep -c . | tr -d ' ')"
# A file counted but not dated means the reading is short by an unknown
# amount, and the missing one could be the newest - so "newest" stops being a
# claim this function is entitled to make. Reported for the WHOLE reading.
[ "${va_mn:-0}" -eq "${va_n:-0}" ] || { printf '?'; return 0; }
va_new="$(printf '%s\n' "$va_ms" | sort -n | tail -1)"
case "$va_new" in ''|*[!0-9]*) printf '?'; return 0 ;; esac
printf '%dd' $(( (NOW - va_new) / 86400 ))
}
# brief_quality out of the FRONTMATTER BLOCK ONLY, never the body - a body line
# is untrusted text and must not be able to speak for the brief, the same rule
# coord-count.sh applies to `reply-expected`. Measured across the real tree
# 2026-08-31: only 8 of ~40 briefs carry the field at all, so ABSENT must read
# `-` and never `complete`. `partial` vs `complete` is precisely what three
# presence-greps could not tell apart (the order's field 2).
voyage_quality() {
vq_f="$1/brief.md"
[ -f "$vq_f" ] || vq_f="$1/brief.md.draft"
[ -f "$vq_f" ] || { printf -- '-'; return 0; }
vq_v="$(awk '
NR==1 { if ($0 ~ /^---[[:space:]]*$/) { inf=1; next } else { exit } }
inf && /^---[[:space:]]*$/ { exit }
inf && /^brief_quality:[[:space:]]*/ {
sub(/^brief_quality:[[:space:]]*/, "")
gsub(/^["'"'"']|["'"'"']$/, "")
gsub(/[[:space:]]+$/, "")
print; exit
}' "$vq_f" 2>/dev/null | head -1 | tr -d '\r')"
[ -n "$vq_v" ] || vq_v="-"
printf '%s' "$vq_v"
}
# Open blocking decisions: `[BLOCKING DECISION, before S<n>]` in the brief.
# Counted at DECLARATION SITES ONLY - the bracket must open the line's content
# after list markers, headings and bold. Measured on the real brief: the string
# occurs on four lines, of which ONE declares and three refer to it in prose,
# so a bare `grep -c` reports 4 where the honest answer is 1. Selftest section
# 29 carries the known-negative control (a brief whose only occurrences are
# prose must count 0).
#
# It reports what the brief DECLARES and infers nothing about resolution: board
# reports and refuses the inference, exactly as FLY does for liveness.
# Prints "<count> <gates>" as two space-separated fields.
voyage_blocking() {
vb_f="$1/brief.md"
[ -f "$vb_f" ] || vb_f="$1/brief.md.draft"
[ -f "$vb_f" ] || { printf '? -'; return 0; }
vb_re='^[[:space:]]*(#+[[:space:]]*)?([-*+][[:space:]]*)?(\*\*)?\[BLOCKING DECISION'
vb_n="$(grep -cE "$vb_re" "$vb_f" 2>/dev/null | tr -d ' ')"
case "$vb_n" in ''|*[!0-9]*) vb_n=0 ;; esac
vb_g="$(grep -E "$vb_re" "$vb_f" 2>/dev/null \
| sed -n 's/.*\[BLOCKING DECISION,[[:space:]]*before[[:space:]]*\([A-Za-z0-9._-]*\)\].*/\1/p' \
| sort -u | paste -sd, - | tr -d ' ')"
[ -n "$vb_g" ] || vb_g="-"
printf '%s %s' "$vb_n" "$vb_g"
}
# The research denominator (the order's field 5). A research step with NULL
# output is ansikt 4 at programme level: research/README.md in the completed
# run opened with "this directory held 0 files when the session started", and
# nothing on any board could show that. Three outcomes, deliberately distinct:
# "-" no research directory at all - the step was never started.
# "0" the directory exists and holds nothing - started, produced nothing.
# "N" measured.
voyage_research() {
[ -d "$1/research" ] || { printf -- '-'; return 0; }
vr_n=0
for vr_f in "$1"/research/*.md; do
[ -f "$vr_f" ] && vr_n=$((vr_n+1))
done
printf '%d' "$vr_n"
}
# The repo-level table cell: count, and the age of the STALEST project - the
# one whose most recent activity is oldest, which is the analogue of ORDRE's
# oldest-first rule. A bare `0` means no projects; "0d" would mean a project
# touched today, and those are two different facts (the same reason FLY exists
# beside ORDRE, and the same reason an empty order queue prints a bare 0).
voyage_cell() {
vc_n=0; vc_max=""; vc_unmeas=0
while IFS= read -r vc_d; do
[ -n "$vc_d" ] || continue
vc_n=$((vc_n+1))
vc_a="$(voyage_age "$vc_d")"
case "$vc_a" in
'?') vc_unmeas=1 ;;
*) vc_a="${vc_a%d}"
if [ -z "$vc_max" ] || [ "$vc_a" -gt "$vc_max" ]; then vc_max="$vc_a"; fi ;;
esac
done <<VOYAGE_CELL_EOF
$(voyage_dirs "$1")
VOYAGE_CELL_EOF
[ "$vc_n" -eq 0 ] && { printf '0'; return 0; }
if [ "$vc_unmeas" -eq 1 ] || [ -z "$vc_max" ]; then printf '%d:?' "$vc_n"; return 0; fi
printf '%d:%dd' "$vc_n" "$vc_max"
}
printf '%s\n' "$REPOS" | while IFS= read -r d; do
[ -n "$d" ] || continue
name="$(basename "$d")"
@ -598,9 +763,13 @@ printf '%s\n' "$REPOS" | while IFS= read -r d; do
# `orders` sits between blockedon 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.
printf '%s|%06d|%s|%s|%s|%s|%s|%sd|%s|%s|%s|%s|%s|%s|%s|%s\n' \
# `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.
voy="$(voyage_cell "$d")"
printf '%s|%06d|%s|%s|%s|%s|%s|%sd|%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" "$neste"
"${blockedon:--}" "$orders" "$ordersage" "$claimed" "$claimedage" "$voy" "$neste"
done > "${TMPDIR:-/tmp}/board.$$"
RECORDS="${TMPDIR:-/tmp}/board.$$"
@ -757,20 +926,20 @@ awk -F'|' '$4 ~ /^blocked/ && $11 != "-" && $11 != "" {print $11}' "$RECORDS" \
hdr() {
printf '\n%s\n' "$1"
printf '%-32s %-34s %-14s %4s %7s %7s %4s %6s %6s %s\n' \
"REPO" "STATUS" "KOST" "INN" "ORDRE" "FLY" "DRT" "ALDER" "SISTE" "NESTE"
printf '%-32s %-34s %-14s %4s %7s %7s %7s %4s %6s %6s %s\n' \
"REPO" "STATUS" "KOST" "INN" "ORDRE" "FLY" "VOY" "DRT" "ALDER" "SISTE" "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 neste; do
while IFS='|' read -r bucket sortkey name status cost inbox dirty age last dir blockedon orders ordersage claimed claimedage voy 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 %4s %6s %6s %s\n' \
"$name" "$status" "$cost" "$inbox" "$o_cell" "$c_cell" "$dirty" "$age" "$last" "$(trunc "$neste" "$NESTE_WIDTH")"
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")"
done
}
@ -960,7 +1129,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 neste; do
while IFS='|' read -r owed bucket sortkey name status cost inbox dirty age last dir blockedon orders ordersage claimed claimedage voy 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
@ -1564,7 +1733,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 neste; do
while IFS='|' read -r why bucket sortkey name status cost inbox dirty age last dir blockedon orders ordersage claimed claimedage voy neste; do
[ -n "$name" ] || continue
pn=$((pn + 1))
printf 'tab=%s\n' "$pn"
@ -1624,7 +1793,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] = $16
rc_dir[name] = $10; rc_neste[name] = $17
next
}
{
@ -1691,6 +1860,105 @@ if [ "$INBOX_PLAN" -eq 1 ]; then
exit 0
fi
# --- Voyage rendering (--voyage) -------------------------------------------
# A SIXTH rendering of the same scan, never a second scan, and read-only like
# every other one: it writes to no repo, no STATE.md and no mailbox.
#
# It answers the question no single STATE.md can - which briefs are in flight,
# in what phase, and who is waiting on whom - as key=value blocks, the format
# this file already uses for its machine-readable renderings. Prose would make
# the format an API no test in THIS repo could hold stable for a consumer in
# another.
#
# "Who waits on whom" has exactly two answerable forms here, and neither is
# guessed: an OPEN BLOCKING DECISION means the project waits on the operator
# (measured on the completed run - one decision gated /trekplan for a full day
# and nothing on any board showed it), and the repo's PENDING ORDER IDS are
# carried beside the phase, because that coupling is what lets a reader go from
# "this brief is standing still" to "this order is pending".
voyage() {
vy_repos=0; vy_with=0; vy_total=0
echo "VOYAGE - briefer i omloep"
# The legend is engine behaviour, exactly as FLY's liveness denial is: a
# column or a field whose form nothing pins is one that drifts.
echo "# FASE maaler ARTEFAKTER, ikke sesjoner. En plan utfoert i EN sesjon legger"
echo "# ingen fil igjen, saa 'plan' er det siste filsystemet kan bevise. Ingenting"
echo "# her sier at en sesjon lever - samme nekt som FLY baerer."
echo "# KVALITET er brief_quality fra frontmatter. '-' betyr at feltet MANGLER,"
echo "# aldri at briefen er complete. RESEARCH: '-' = ingen katalog, '0' = katalog"
echo "# uten output (et research-steg med null resultat, ansikt 4 paa programnivaa)."
echo "# ALDER = dager siden NYESTE artefakt: hvor lenge ingenting har skjedd. Det er"
echo "# motsatt av ORDRE/FLY med vilje - en ko sitt problem er det eldste som venter,"
echo "# et prosjekt sitt problem er at siste aktivitet er gammel."
echo ""
while IFS='|' read -r vy_name vy_dir; do
[ -n "$vy_name" ] || continue
vy_repos=$((vy_repos+1))
vy_here=0
# Order coupling: the repo's PENDING orders (claimed ones are in flight and
# are not what a reader is looking for when a brief has stopped moving).
vy_ord=0; vy_ids=""
for vy_of in "$COORD/$vy_name/orders"/*.md; do
[ -f "$vy_of" ] || continue
vy_ord=$((vy_ord+1))
vy_ids="$vy_ids$(basename "$vy_of" .md)
"
done
while IFS= read -r vy_p; do
[ -n "$vy_p" ] || continue
vy_here=1; vy_total=$((vy_total+1))
vy_rel="${vy_p#$vy_dir/}"
vy_bl="$(voyage_blocking "$vy_p")"
vy_bn="${vy_bl%% *}"; vy_bg="${vy_bl##* }"
case "$vy_bn" in
0|'?') vy_wait="-" ;;
*) vy_wait="operatoerbeslutning" ;;
esac
printf 'prosjekt=%s/%s\n' "$vy_name" "$vy_rel"
printf 'repo=%s\n' "$vy_name"
printf 'dir=%s\n' "$vy_p"
printf 'fase=%s\n' "$(voyage_phase "$vy_p")"
printf 'kvalitet=%s\n' "$(voyage_quality "$vy_p")"
printf 'blokkerende=%s\n' "$vy_bn"
printf 'blokkerende_gate=%s\n' "$vy_bg"
printf 'venter=%s\n' "$vy_wait"
printf 'research=%s\n' "$(voyage_research "$vy_p")"
printf 'alder=%s\n' "$(voyage_age "$vy_p")"
printf 'ordre=%s\n' "$vy_ord"
printf '%s' "$vy_ids" | while IFS= read -r vy_i; do
[ -n "$vy_i" ] && printf 'ordre_id=%s\n' "$vy_i"
done
echo ""
done <<VOYAGE_PROJ_EOF
$(voyage_dirs "$vy_dir")
VOYAGE_PROJ_EOF
[ "$vy_here" -eq 1 ] && vy_with=$((vy_with+1))
done <<VOYAGE_REPO_EOF
$(awk -F'|' '{print $3 "|" $10}' "$RECORDS")
VOYAGE_REPO_EOF
# The denominator, always - "nothing found" and "not measured" must never
# render as the same empty page. This is the whole ansikt-4 rule applied to
# this renderer's own output.
if [ "$vy_total" -eq 0 ]; then
printf 'ingen=0 Voyage-prosjekter funnet i %s skannede repo\n' "$vy_repos"
printf '# Det er et MAALT null, ikke en tom side: %s repo ble undersoekt paa de\n' "$vy_repos"
echo "# tre anerkjente plasseringene (.claude/projects/, docs/, features/)."
fi
printf '# nevner: %s repo skannet, %s med minst ett Voyage-prosjekt, %s prosjekter\n' \
"$vy_repos" "$vy_with" "$vy_total"
}
if [ "$VOYAGE" -eq 1 ]; then
voyage
exit 0
fi
count() { awk -F'|' -v b="$1" '$1==b' "$RECORDS" | wc -l | tr -d ' '; }
echo "BOARD - tverr-repo oppmerksomhetstavle ($(awk 'END{print NR}' "$RECORDS") repo)"
@ -1712,6 +1980,13 @@ echo "FLY = ordrer i arbeid (claimed). Beviser aldri at en okt lever eller er ak
echo " Beviser bare at noen TOK ordren; ingenting frigir den om okten dor."
echo " Summeres ALDRI med ORDRE - samme ko, annen tilstand."
echo " Baerer samme N:Md - alderen paa den ELDSTE claimen. Ingenting frigir den."
# VOY is the same class of reading as ORDRE and FLY - a durable filesystem fact
# about work sitting in this repo - and like them it is never summed with them.
# The per-project detail (phase, brief_quality, blocking decisions) lives in
# --voyage: a single cell cannot carry a phase when a repo holds seven briefs.
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."
echo "ALDER = dager siden STATE.md endret. SISTE = dager siden siste commit."
[ "$(count 1)" -gt 0 ] && { hdr "BLOKKERT (venter paa ekstern avhengighet)"; rows 1; }