فهرست منبع

fix(skills): Route panel separators through term.sh so TERM_ASCII=1 works

`fleet status` emitted a literal U+00B7 MIDDLE DOT (0xC2 0xB7) even under
TERM_ASCII=1, mojibaking on non-UTF-8 Windows consoles and making the
FLEET_ASCII=1 remedy that SKILL.md advertises a no-op for the summary line
and footer.

term.sh was not the gap: TERM_DOT has had a registered ASCII proxy ("|")
all along, and a sweep of every public helper and registry key confirms
the library is already 7-bit clean under TERM_ASCII=1. The defect was in
the callers, which authored the character inline so it never reached the
registry.

Fixed at the call sites:
  - fleet.sh   summary line (both views), footer hotkeys, verbose title
  - mac-ops    panel.sh + quickrun.sh summary/title/health rows
  - github-ops repo-scorecard sweep line (U+2026, same class)

Also registers TERM_ELLIPSIS alongside TERM_DOT/TERM_ARROW, so the
continuation ellipsis has a name to interpolate instead of a literal to
type; term_truncate now reads it (ASCII marker stays "..", unchanged).

docs/TERMINAL-DESIGN.md carried the root cause: its own section 6.3 ASCII
fallback example rendered the separator as a middle dot, documenting the
leak as correct. Corrected, and the three text-level glyphs are now in the
registry tables.

Gates added, both negative-tested:
  - tests/check-resources.sh exercises every term.sh helper and registry
    key under TERM_ASCII=1, plus a static check that consumers never
    hardcode a glyph the registry owns (comments and heredoc bodies are
    stripped; prose punctuation is deliberately out of scope)
  - fleet-ops/tests/run.sh asserts the WHOLE status panel is ASCII-pure
    across default/verbose/empty/FLEET_ASCII/icons=ascii, and that UTF-8
    mode still renders the Unicode separator

The pre-existing icons=ascii assertion only checked for a single glyph
(no "|"), which is why the sibling separators went unnoticed; it is now
a full-panel check.

just check: 45/45 skill suites, validation, doc-drift, resources clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
0xDarkMatter 1 ماه پیش
والد
کامیت
6addb4eef3

+ 18 - 2
docs/TERMINAL-DESIGN.md

@@ -132,6 +132,17 @@ Default width: **10 pips** = clean 10% increments. Override only when the data h
 | ---- | ------- | ----- |
 | tip  | `💡`     | `(i)` |
 
+#### Text separators
+
+Authored strings mix glyphs with prose, which is exactly where an unregistered
+literal hides. Interpolate the variable; never type the character.
+
+| Role      | Unicode | ASCII | Variable         | Use for                                  |
+| --------- | ------- | ----- | ---------------- | ---------------------------------------- |
+| separator | `·`     | `\|`   | `$TERM_DOT`      | joining hotkeys/labels, `N lanes · N active` |
+| pointer   | `→`     | `->`  | `$TERM_ARROW`    | `problem → remedy` leads                     |
+| ellipsis  | `…`     | `...` | `$TERM_ELLIPSIS` | `sweeping org …` progress, truncation marker |
+
 #### Spinners (live mode only)
 
 Three families, each with a different role:
@@ -615,7 +626,7 @@ Every glyph has a registered ASCII proxy:
 ```
 +-- [F] fleet -------------------------------- (b) main ---*
 |
-+-- 4 lanes · 3 active
++-- 4 lanes | 3 active
 |
 +-- RUNNING (2)
 |   +-- feat/oauth-pkce       *-*-*-@      M4 ?1      12m
@@ -628,7 +639,7 @@ Every glyph has a registered ASCII proxy:
 +-- CONFLICT (1)
 |   `-- feat/audit-log        *-*-X        blocked    17m
 |
-`-- R refresh · L land · ? help ---- (+) daemon  (.) 17m ---*
+`-- R refresh | L land | ? help ---- (+) daemon  (.) 17m ---*
 ```
 
 Same skeleton. Rounded corners (`╭ ╰`) collapse to `+`. Rail dots (`● ◉ ⊗`) become `* @ X`. Health bullets (`•`) become `(+) (.) (!)`. The grid survives.
@@ -765,6 +776,11 @@ term_spinner_frame family tick           # working / heartbeat → glyph
 # Edge cases
 term_truncate    "text" max_cols         # ellipsis-truncate
 term_term_width                          # current cols
+
+# Text-level glyphs — interpolate, never type the literal (TERM_ASCII=1 swaps these)
+$TERM_DOT                                # · / |    separator
+$TERM_ARROW                              # → / ->   pointer
+$TERM_ELLIPSIS                           # … / ...  continuation
 ```
 
 ### Registries

+ 11 - 1
skills/_lib/term.sh

@@ -70,6 +70,11 @@ TERM_ARROW=""
 # ASCII-safe so authored footer/summary strings stay pure under TERM_ASCII=1.
 TERM_DOT=""
 
+# Continuation ellipsis (… / ...) — for "sweeping org …" progress lines and the
+# truncation marker in term_truncate. Registered rather than authored inline for
+# the same reason as TERM_DOT: a literal U+2026 survives TERM_ASCII=1.
+TERM_ELLIPSIS=""
+
 # Spinner frame banks (set by term_init; arrays keep order).
 TERM_SPIN_WORKING=()
 TERM_SPIN_HEARTBEAT=()
@@ -125,6 +130,7 @@ term_init() {
     TERM_GLYPH_TIP="(i)"
     TERM_ARROW="->"
     TERM_DOT="|"
+    TERM_ELLIPSIS="..."
     TERM_SPIN_WORKING=('|' '/' '-' '\')
     TERM_SPIN_HEARTBEAT=('.' ':' '*' ':')
   else
@@ -146,6 +152,7 @@ term_init() {
     TERM_GLYPH_TIP="💡"
     TERM_ARROW="→"
     TERM_DOT="·"
+    TERM_ELLIPSIS="…"
     TERM_SPIN_WORKING=('⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏')
     TERM_SPIN_HEARTBEAT=('·' '∙' '•' '●' '•' '∙')
   fi
@@ -290,7 +297,10 @@ term_truncate() {
   local text=$1 max=$2
   local len=${#text}
   if [[ $len -le $max ]]; then printf '%s' "$text"; return; fi
-  local ell="…"
+  # Registered glyph, not a literal — TERM_ASCII=1 must swap it. The ASCII marker
+  # stays two dots (not $TERM_ELLIPSIS's three): it is the truncation cue, and a
+  # third dot would eat another column of the name it is already shortening.
+  local ell="$TERM_ELLIPSIS"
   [[ "$TERM_ASCII_MODE" -eq 1 ]] && ell=".."
   local elllen=${#ell}
   printf '%s%s' "${text:0:$((max - elllen))}" "$ell"

+ 8 - 5
skills/fleet-ops/scripts/fleet.sh

@@ -395,7 +395,10 @@ __fleet_daemon_state() {
 __fleet_footer() {
   local active=$1 daemon_state=$2
   local hotkeys
-  hotkeys="$(term_hotkey R refresh) · $(term_hotkey L land) · $(term_hotkey '?' help)"
+  # Separators come from term.sh ($TERM_DOT), never an authored U+00B7. A literal
+  # middle dot bypasses the ASCII-fallback registry, so it survives TERM_ASCII=1
+  # and mojibakes on non-UTF-8 consoles. tests/check-resources.sh gates this.
+  hotkeys="$(term_hotkey R refresh) ${TERM_DOT} $(term_hotkey L land) ${TERM_DOT} $(term_hotkey '?' help)"
   local healths
   healths="$(term_health "$daemon_state" "daemon")"
   [[ "$active" -gt 0 ]] && healths="$healths  $(term_health pending "$active active")"
@@ -435,7 +438,7 @@ fleet_view_panel() {
   fi
 
   term_panel_vert
-  term_summary_line "$total $([ "$total" -eq 1 ] && echo lane || echo lanes) · $active active"
+  term_summary_line "$total $([ "$total" -eq 1 ] && echo lane || echo lanes) ${TERM_DOT} $active active"
   term_panel_vert
 
   local i
@@ -484,7 +487,7 @@ fleet_view_verbose() {
   local now=$(date +%s)
 
   echo ""
-  term_panel_open fleet "fleet · verbose" "$TERM_GLYPH_BRANCH $BASE_BRANCH"
+  term_panel_open fleet "fleet ${TERM_DOT} verbose" "$TERM_GLYPH_BRANCH $BASE_BRANCH"
 
   if [[ $total -eq 0 ]]; then
     term_panel_vert
@@ -496,7 +499,7 @@ fleet_view_verbose() {
   fi
 
   term_panel_vert
-  term_summary_line "$total $([ "$total" -eq 1 ] && echo lane || echo lanes) · $active active"
+  term_summary_line "$total $([ "$total" -eq 1 ] && echo lane || echo lanes) ${TERM_DOT} $active active"
   term_panel_vert
 
   for f in "$LANES_DIR"/*; do
@@ -633,7 +636,7 @@ land_one() {
     if [[ -n "$TEST_CMD" ]]; then
       log "running test_cmd: $TEST_CMD"
       if eval "$TEST_CMD" >>"$LOG" 2>&1; then
-        log "PASS: $branch landed"
+        log "PASS: $branch landed"
       else
         log "FAIL: tests failed — reverting $branch"
         git reset --hard HEAD^

+ 38 - 2
skills/fleet-ops/tests/run.sh

@@ -96,6 +96,41 @@ case "$main_log" in *"merge: feat/batch-b"*) ok "merge: feat/batch-b on main";;
 case "$(head -n1 "$REPO/.claude/fleet/lanes/feat%2Ffoo" 2>/dev/null)" in
   RUNNING) ok "land --all left RUNNING feat/foo untouched";; *) no "land --all wrongly touched RUNNING lane";; esac
 
+echo "-- TERM_ASCII=1 renders the WHOLE panel in ASCII, not just the tree rail --"
+# Regression: `fleet status` leaked a literal U+00B7 (0xC2 0xB7) from the summary
+# line and the footer hotkeys under TERM_ASCII=1 — those separators were authored
+# inline instead of coming from term.sh's $TERM_DOT, so the ASCII registry never
+# saw them and non-UTF-8 Windows consoles mojibaked. Assert the ENTIRE emission,
+# every view: a single-glyph check (the old "no │" test) cannot catch a sibling.
+# FORCE_COLOR=1 keeps the ANSI path live so color codes can't hide a glyph.
+# Lanes here span RUNNING + LANDED, so sections, rails, leaf rows, the summary
+# line and the footer are all exercised in one shot.
+ascii_pure() { # label, output
+  local dirty
+  dirty="$(printf '%s' "$2" | LC_ALL=C grep -o '[^[:print:][:cntrl:]]' | LC_ALL=C sort -u | tr -d '\n')"
+  if [ -n "$dirty" ]; then
+    no "$1 emits non-ASCII under TERM_ASCII=1 ($(printf '%s' "$dirty" | od -An -c | tr -s ' '))"
+  else ok "$1 is pure ASCII under TERM_ASCII=1"; fi
+}
+ascii_pure "status panel"   "$(TERM_ASCII=1 FORCE_COLOR=1 bash "$FLEET" status 2>&1)"
+ascii_pure "verbose panel"  "$(TERM_ASCII=1 FORCE_COLOR=1 bash "$FLEET" status --verbose 2>&1)"
+# FLEET_ASCII is the legacy alias SKILL.md advertises as the mojibake fix — it
+# must reach exactly the same registry, or the documented remedy is a lie.
+ascii_pure "status (FLEET_ASCII=1)" "$(env -u TERM_ASCII FLEET_ASCII=1 FORCE_COLOR=1 bash "$FLEET" status 2>&1)"
+# Empty state renders a different branch of the panel (tip glyph, no sections).
+EREPO="$SB/empty"; mkdir -p "$EREPO"
+git -C "$EREPO" init -q -b main
+git -C "$EREPO" config user.email t@t; git -C "$EREPO" config user.name t
+git -C "$EREPO" config core.autocrlf false
+echo e > "$EREPO/f"; git -C "$EREPO" add -A
+git -C "$EREPO" -c user.email=t@t -c user.name=t commit -qm init
+ascii_pure "empty-state panel" "$(cd "$EREPO" && TERM_ASCII=1 FORCE_COLOR=1 bash "$FLEET" status 2>&1)"
+# Guard the other direction too: without TERM_ASCII the panel must still use the
+# Unicode glyphs, or "pure ASCII" would pass trivially by rendering nothing.
+uni_out="$(cd "$REPO" && env -u TERM_ASCII -u FLEET_ASCII LC_ALL=en_US.UTF-8 bash "$FLEET" status 2>&1)"
+case "$uni_out" in *"$(printf '\xc2\xb7')"*) ok "unicode mode still renders the U+00B7 separator";;
+  *) no "unicode mode lost its separator (ASCII fallback leaked into UTF-8 output)";; esac
+
 echo "-- scrub gate still works on a slashed branch --"
 wt="$SB/wt-feat_foo"
 # Marker built via printf so this source file never contains the contiguous
@@ -249,8 +284,9 @@ eq "CRLF config parsed without trailing CR" "echo hi" "$(cfg_get test_cmd)"
 printf 'icons=ascii\n' > "$CFG"
 eq "icons key parsed" "ascii" "$(cfg_get icons)"
 bash "$FLEET" track main >/dev/null 2>&1 || true
-icon_out="$(env -u TERM_ASCII -u FLEET_ASCII bash "$FLEET" status 2>&1)"
-case "$icon_out" in *"│"*) no "icons=ascii still emitted unicode tree glyphs";; *) ok "icons=ascii reaches term_init (no unicode glyphs)";; esac
+# Asserts the whole panel, not just the tree rail: `icons=ascii` is the config
+# route into term_init, so it must deliver the same ASCII purity TERM_ASCII=1 does.
+ascii_pure "icons=ascii panel" "$(env -u TERM_ASCII -u FLEET_ASCII FORCE_COLOR=1 bash "$FLEET" status 2>&1)"
 rm -f "$CREPO/.claude/fleet/lanes/main"
 
 echo "-- test gate: test_cmd actually runs and actually blocks the merge --"

+ 1 - 1
skills/github-ops/scripts/repo-scorecard.sh

@@ -401,7 +401,7 @@ fi
 # ---- Fleet sweep ----------------------------------------------------------
 if [ -n "$ORG" ]; then
   valid_owner "$ORG" || { echo "repo-scorecard: invalid owner '$ORG'" >&2; exit "$EX_USAGE"; }
-  echo "$(term_color dim "repo-scorecard: sweeping $ORG ")" >&2
+  echo "$(term_color dim "repo-scorecard: sweeping $ORG ${TERM_ELLIPSIS}")" >&2
   list="$(runner gh repo list "$ORG" --no-archived --limit 200 --json nameWithOwner,visibility 2>/dev/null)" \
     || skip "gh repo list failed for $ORG (not authed / offline / rate-limited?)"
   [ -n "$list" ] || skip "no repos returned for $ORG"

+ 3 - 3
skills/mac-ops/scripts/_lib/panel.sh

@@ -141,9 +141,9 @@ panel_render() {
     local indicator="${2:-}"
 
     echo ""
-    term_panel_open mac-ops "mac-ops · $tag" "$indicator"
+    term_panel_open mac-ops "mac-ops ${TERM_DOT} $tag" "$indicator"
     term_panel_vert
-    term_summary_line "$((PASS_COUNT+FAIL_COUNT+WARN_COUNT+INFO_COUNT)) checks · $FAIL_COUNT fail · $WARN_COUNT warn"
+    term_summary_line "$((PASS_COUNT+FAIL_COUNT+WARN_COUNT+INFO_COUNT)) checks ${TERM_DOT} $FAIL_COUNT fail ${TERM_DOT} $WARN_COUNT warn"
     term_panel_vert
 
     # Render in order: fail, warn, pass (count only), info (count only)
@@ -202,7 +202,7 @@ panel_render() {
     [[ "$WARN_COUNT" -gt 0 ]] && health_state="warning"
     [[ "$FAIL_COUNT" -gt 0 ]] && health_state="critical"
     local right_health
-    right_health="$(term_health "$health_state" "$FAIL_COUNT fail · $WARN_COUNT warn")"
+    right_health="$(term_health "$health_state" "$FAIL_COUNT fail ${TERM_DOT} $WARN_COUNT warn")"
     term_panel_close "" "$right_health"
     echo ""
 

+ 3 - 3
skills/mac-ops/scripts/quickrun.sh

@@ -115,9 +115,9 @@ if [[ "$JSON_MODE" -eq 1 ]]; then
 elif panel_enabled; then
     hostname_short=$(scutil --get LocalHostName 2>/dev/null | head -c 30 || hostname -s | head -c 30)
     echo ""
-    term_panel_open mac-ops "mac-ops · quickrun" "$hostname_short"
+    term_panel_open mac-ops "mac-ops ${TERM_DOT} quickrun" "$hostname_short"
     term_panel_vert
-    term_summary_line "$((TOTAL_PASS+TOTAL_FAIL+TOTAL_WARN+TOTAL_INFO)) checks across 5 audits · $TOTAL_FAIL fail · $TOTAL_WARN warn"
+    term_summary_line "$((TOTAL_PASS+TOTAL_FAIL+TOTAL_WARN+TOTAL_INFO)) checks across 5 audits ${TERM_DOT} $TOTAL_FAIL fail ${TERM_DOT} $TOTAL_WARN warn"
     term_panel_vert
 
     if [[ "${#ALL_FAILS[@]}" -gt 0 ]]; then
@@ -165,7 +165,7 @@ elif panel_enabled; then
     health_state="healthy"
     [[ "$TOTAL_WARN" -gt 0 ]] && health_state="warning"
     [[ "$TOTAL_FAIL" -gt 0 ]] && health_state="critical"
-    right_health="$(term_health "$health_state" "$TOTAL_FAIL fail · $TOTAL_WARN warn")"
+    right_health="$(term_health "$health_state" "$TOTAL_FAIL fail ${TERM_DOT} $TOTAL_WARN warn")"
     term_panel_close "" "$right_health"
     echo ""
 

+ 100 - 0
tests/check-resources.sh

@@ -135,6 +135,106 @@ for s in skills/claude-api-ops/scripts/check-model-table.py \
         || bad "$(basename "$s") missing inline Term"
 done
 
+echo "== terminal design: term.sh itself + its consumers are ASCII-pure"
+# term.sh is shared infrastructure — a glyph that skips the ASCII registry leaks
+# into every panel in the repo at once. Exercise EVERY public helper (and every
+# registry key) under TERM_ASCII=1 and assert the whole emission is 7-bit.
+__probe="$(mktemp)"
+cat > "$__probe" <<'PROBE'
+. skills/_lib/term.sh
+TERM_ASCII=1 term_init
+for v in TERM_TREE_BRANCH TERM_TREE_LAST TERM_TREE_VERT TERM_PANEL_TL TERM_PANEL_BL \
+         TERM_PANEL_HRULE TERM_PANEL_TERM TERM_ICON_PENDING TERM_ICON_READY \
+         TERM_ICON_DONE TERM_ICON_FAILED TERM_ICON_WARN TERM_ICON_HINT \
+         TERM_GLYPH_BRANCH TERM_GLYPH_ALERT TERM_GLYPH_TIP TERM_ARROW TERM_DOT \
+         TERM_ELLIPSIS; do
+    printf '%s=%s\n' "$v" "${!v}"
+done
+printf '%s\n' "${TERM_SPIN_WORKING[@]}" "${TERM_SPIN_HEARTBEAT[@]}"
+# Registry keys, plus one miss per registry to cover the not-found branch.
+for k in fleet forge psql watch deploy git windows-ops mac-ops github-ops audit \
+         supply-chain net-ops adr loop terraform claude play __miss__; do
+    term_brand_glyph "$k"; echo
+done
+for k in healthy pending warning critical alarm busted unknown __miss__; do
+    term_health_glyph "$k"; echo; term_health "$k" text; echo
+done
+for k in user web mobile auth database cache queue storage service api search \
+         timer build hook log __miss__; do term_diagram_icon "$k"; echo; done
+for k in ok bad gap warn skip na unknown __miss__; do term_mark "$k"; echo; done
+for s in RUNNING PENDING READY LANDED DONE OK FAILED ERROR CONFLICT WARN HINT \
+         INFO __miss__; do term_state_icon "$s"; echo; term_section "$s" label 1; done
+term_truncate "$(printf 'x%.0s' $(seq 40))" 10; echo
+term_panel_open fleet name indicator; term_panel_open fleet name
+term_panel_close hotkeys healths; term_panel_close
+term_panel_vert; term_panel_line body; term_summary_line meta
+term_leaf_line "$TERM_TREE_BRANCH" name leaf meta age
+term_toast fleet message
+term_status_row ok label value; term_status_row warn label
+term_alert warning msg; term_alert critical msg
+for n in 0 1 2 3; do for h in HEAD CONFLICT EMPTY __miss__; do term_rail "$n" "$h"; echo; done; done
+for k in progress score capacity; do
+    for f in 0 30 70 100; do term_pip_bar "$k" "$f" 100; echo; done
+    term_pip_bar "$k" 3 5; echo
+done
+term_hotkey R refresh; echo
+for f in working heartbeat __miss__; do
+    for t in 0 1 2 3 4 5 6 7 8 9; do term_spinner_frame "$f" "$t"; echo; done
+done
+term_header title meta; term_header title; term_divider 20
+term_tree_item icon label meta; term_tree_item icon label
+term_tree_connector 1 1; echo; term_tree_connector 1 2; echo
+term_tree_indent 1; echo; term_tree_indent 2; echo
+term_tree_node prefix conn label meta; term_tree_node prefix conn label
+term_table_row a b c d; term_empty nothing; term_color green text; echo
+PROBE
+__probe_out="$(FORCE_COLOR=1 bash "$__probe" 2>&1)"
+if printf '%s' "$__probe_out" | LC_ALL=C grep -q '[^[:print:][:cntrl:]]'; then
+    bad "term.sh emits non-ASCII under TERM_ASCII=1: $(printf '%s' "$__probe_out" \
+        | LC_ALL=C grep -o '[^[:print:][:cntrl:]]' | LC_ALL=C sort -u | tr -d '\n' | od -An -c | tr -s ' ')"
+else pass "term.sh: every helper + registry key ASCII-pure under TERM_ASCII=1"; fi
+rm -f "$__probe"
+
+# Source-level guard for the consumers. A hardcoded DECORATION glyph in an
+# authored string never reaches the registry, so TERM_ASCII=1 can't swap it —
+# the exact defect that leaked U+00B7 out of `fleet status`. Scoped to the
+# glyphs term.sh already registers a proxy for, so every hit has a named fix:
+#   U+00B7 -> $TERM_DOT      U+2192 -> $TERM_ARROW    U+2026 -> term_truncate
+#   tree/panel chrome        -> $TERM_TREE_* / $TERM_PANEL_*
+#   rail + pip + mark glyphs -> term_rail / term_pip_bar / term_mark
+# Prose punctuation (em dash etc.) is deliberately NOT matched — that is a
+# separate, wider class and term.sh registers no proxy for it.
+#
+# Two kinds of line are stripped before matching, because neither reaches a
+# terminal: shell comments (so a guard comment naming the character is fine),
+# and heredoc bodies (adr-init.sh writes a markdown ADR template to disk, where
+# a real arrow is correct and TERM_ASCII has no say).
+#
+# mac-ops is macOS-only and net-ops is per-platform, so output-level purity
+# checks can't reach them on CI — this static check is their only gate.
+__glyphs='·|→|…|│|├|└|─|╭|╰|●|◉|⊗|▰|▱|▲|✓|✗|⎇|⬤'
+__strip_heredocs='
+    /<<-?[[:space:]]*['"'"'"]?[A-Za-z_][A-Za-z0-9_]*['"'"'"]?/ && !inhd {
+        line = $0
+        sub(/.*<<-?[[:space:]]*/, "", line)
+        gsub(/['"'"'"]/, "", line)
+        sub(/[^A-Za-z0-9_].*/, "", line)
+        if (line != "") { inhd = 1; tag = line; next }
+    }
+    inhd { t = $0; sub(/^[[:space:]]+/, "", t); if (t == tag) inhd = 0; next }
+    { print }
+'
+__dirty=""
+for s in $(grep -rl '_lib/term\.sh\|__MACOPS_TERM_LIB' --include='*.sh' skills/ 2>/dev/null); do
+    case "$s" in */tests/*) continue ;; esac
+    if sed 's/#.*$//' "$s" | awk "$__strip_heredocs" | grep -qE "$__glyphs"; then
+        __dirty="$__dirty $s"
+    fi
+done
+if [ -n "$__dirty" ]; then
+    bad "term.sh consumers hardcode registry glyphs (use \$TERM_DOT/\$TERM_ARROW/term_*):$__dirty"
+else pass "term.sh consumers route every registry glyph through term.sh"; fi
+
 echo
 if [ "$fail" -eq 0 ]; then echo "resource checks: clean"; exit 0; fi
 echo "resource checks: failures above"; exit 1