check-resources.sh 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. #!/usr/bin/env bash
  2. # Offline resource checks — runs in PR CI, may block.
  3. #
  4. # Exercises the skill verifier/scanner scripts in their OFFLINE/structural mode
  5. # (no network) and asserts basic protocol compliance (SKILL-RESOURCE-PROTOCOL.md):
  6. # every shipped verifier responds to --help with exit 0 and passes its own
  7. # offline self-check against the skill's current content.
  8. #
  9. # The network-dependent --live drift checks run in the scheduled freshness
  10. # workflow, never here — a rate-limit must never block an unrelated PR (§7).
  11. #
  12. # Exit: 0 all checks pass, 1 a check failed.
  13. set -uo pipefail
  14. ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
  15. cd "$ROOT" || exit 1
  16. # Pick a working python (Windows Store python3 stub exits 49 on --version).
  17. PY="python3"
  18. if ! "$PY" --version >/dev/null 2>&1; then PY="python"; fi
  19. fail=0
  20. pass() { echo " ok $*"; }
  21. bad() { echo " FAIL $*"; fail=1; }
  22. run() { # description, expected-exit, command...
  23. local desc="$1" want="$2"; shift 2
  24. "$@" >/dev/null 2>&1; local got=$?
  25. if [ "$got" -eq "$want" ]; then pass "$desc (exit $got)"; else bad "$desc (want $want, got $got)"; fi
  26. }
  27. echo "== claude-api-ops: model-table verifier"
  28. run "model-table --offline consistent" 0 "$PY" skills/claude-api-ops/scripts/check-model-table.py --offline
  29. run "model-table --help" 0 "$PY" skills/claude-api-ops/scripts/check-model-table.py --help
  30. echo "== terraform-ops: action-ref verifier"
  31. run "action-refs --offline well-formed" 0 bash skills/terraform-ops/scripts/check-action-refs.sh --offline
  32. run "action-refs --help" 0 bash skills/terraform-ops/scripts/check-action-refs.sh --help
  33. echo "== claude-code-ops: hooks.json validator"
  34. run "hooks-lint clean on repo hooks.json" 0 "$PY" skills/claude-code-ops/scripts/validate-hooks-json.py hooks/hooks.json
  35. run "hooks-lint --help" 0 "$PY" skills/claude-code-ops/scripts/validate-hooks-json.py --help
  36. echo "== playwright-ops: flake-triage"
  37. run "flake-triage --help" 0 "$PY" skills/playwright-ops/scripts/triage-flakes.py --help
  38. echo "== ffmpeg-ops: command/resource verifier"
  39. run "ffmpeg-ops --offline consistent" 0 bash skills/ffmpeg-ops/scripts/verify-commands.sh --offline
  40. run "ffmpeg-ops --help" 0 bash skills/ffmpeg-ops/scripts/verify-commands.sh --help
  41. echo "== ytdlp-ops: version/staleness verifier"
  42. run "ytdlp-ops --offline consistent" 0 bash skills/ytdlp-ops/scripts/check-ytdlp-version.sh --offline
  43. run "ytdlp-ops --help" 0 bash skills/ytdlp-ops/scripts/check-ytdlp-version.sh --help
  44. echo "== mapbox-ops: fact/staleness verifier"
  45. run "mapbox-ops --offline consistent" 0 "$PY" skills/mapbox-ops/scripts/check-mapbox-facts.py --offline
  46. run "mapbox-ops --help" 0 "$PY" skills/mapbox-ops/scripts/check-mapbox-facts.py --help
  47. echo "== typescript-ops: fact/staleness verifier"
  48. run "typescript-ops --offline consistent" 0 "$PY" skills/typescript-ops/scripts/check-typescript-facts.py --offline
  49. run "typescript-ops --help" 0 "$PY" skills/typescript-ops/scripts/check-typescript-facts.py --help
  50. echo "== fleet-worker: doctor (preflight + staleness) verifier"
  51. run "fleet-doctor --offline consistent" 0 bash skills/fleet-worker/scripts/fleet-doctor.sh --offline
  52. run "fleet-doctor --help" 0 bash skills/fleet-worker/scripts/fleet-doctor.sh --help
  53. echo "== loop-ops: pricing-sync verifier"
  54. run "pricing-sync --offline in sync" 0 "$PY" skills/loop-ops/scripts/check-pricing-sync.py --offline
  55. run "pricing-sync --help" 0 "$PY" skills/loop-ops/scripts/check-pricing-sync.py --help
  56. echo "== loop-ops: worked example is gate-clean (dogfood)"
  57. LOOP_EX="skills/loop-ops/assets/examples/pr-watch/loop.config.yaml"
  58. run "example audits clean" 0 bash skills/loop-ops/scripts/loop-check.sh "$LOOP_EX"
  59. run "example doctors clean (offline)" 0 bash skills/loop-ops/scripts/loop-doctor.sh --offline "$LOOP_EX"
  60. echo "== r-ops: R-stack staleness verifier"
  61. run "r-facts --offline consistent" 0 "$PY" skills/r-ops/scripts/check-r-facts.py --offline
  62. run "r-facts --help" 0 "$PY" skills/r-ops/scripts/check-r-facts.py --help
  63. echo "== threejs-ops: three.js fact/staleness verifier"
  64. run "three-facts --offline consistent" 0 "$PY" skills/threejs-ops/scripts/check-three-facts.py --offline
  65. run "three-facts --help" 0 "$PY" skills/threejs-ops/scripts/check-three-facts.py --help
  66. echo "== isometric-ops: projection-constant/staleness verifier"
  67. run "iso-facts --offline consistent" 0 "$PY" skills/isometric-ops/scripts/check-iso-facts.py --offline
  68. run "iso-facts --help" 0 "$PY" skills/isometric-ops/scripts/check-iso-facts.py --help
  69. echo "== hono-ops: Hono fact/staleness verifier + route-inventory contract"
  70. run "hono-facts --offline consistent" 0 "$PY" skills/hono-ops/scripts/check-hono-facts.py --offline
  71. run "hono-facts --help" 0 "$PY" skills/hono-ops/scripts/check-hono-facts.py --help
  72. run "route-inventory --help" 0 "$PY" skills/hono-ops/scripts/route-inventory.py --help
  73. run "route-inventory fixture scan" 0 "$PY" skills/hono-ops/scripts/route-inventory.py skills/hono-ops/tests/fixtures/sample-app.ts
  74. echo "== protocol: every new verifier is executable + compiles"
  75. for s in skills/claude-api-ops/scripts/check-model-table.py \
  76. skills/claude-code-ops/scripts/validate-hooks-json.py \
  77. skills/playwright-ops/scripts/triage-flakes.py \
  78. skills/mapbox-ops/scripts/check-mapbox-facts.py \
  79. skills/loop-ops/scripts/check-pricing-sync.py \
  80. skills/r-ops/scripts/check-r-facts.py \
  81. skills/threejs-ops/scripts/check-three-facts.py \
  82. skills/isometric-ops/scripts/check-iso-facts.py \
  83. skills/hono-ops/scripts/check-hono-facts.py \
  84. skills/hono-ops/scripts/route-inventory.py; do
  85. "$PY" -m py_compile "$s" 2>/dev/null && pass "py_compile $(basename "$s")" || bad "py_compile $(basename "$s")"
  86. done
  87. bash -n skills/terraform-ops/scripts/check-action-refs.sh 2>/dev/null \
  88. && pass "bash -n check-action-refs.sh" || bad "bash -n check-action-refs.sh"
  89. bash -n skills/ffmpeg-ops/scripts/verify-commands.sh 2>/dev/null \
  90. && pass "bash -n verify-commands.sh" || bad "bash -n verify-commands.sh"
  91. bash -n skills/ytdlp-ops/scripts/check-ytdlp-version.sh 2>/dev/null \
  92. && pass "bash -n check-ytdlp-version.sh" || bad "bash -n check-ytdlp-version.sh"
  93. bash -n skills/fleet-worker/scripts/fleet-doctor.sh 2>/dev/null \
  94. && pass "bash -n fleet-doctor.sh" || bad "bash -n fleet-doctor.sh"
  95. echo "== terminal design: verifier framing adopts term.sh and is ASCII-pure"
  96. # Each verifier renders its human framing on stderr; under TERM_ASCII=1 every
  97. # glyph must fall back to its registered ASCII proxy (design principle #3).
  98. purity() { # desc, cmd...
  99. local desc="$1"; shift
  100. local errout
  101. errout="$(TERM_ASCII=1 FORCE_COLOR=1 "$@" 2>&1 1>/dev/null)"
  102. if printf '%s' "$errout" | LC_ALL=C grep -q '[^[:print:][:cntrl:]]'; then
  103. bad "$desc framing emits non-ASCII under TERM_ASCII=1"
  104. else pass "$desc framing pure ASCII under TERM_ASCII=1"; fi
  105. }
  106. purity "action-refs" bash skills/terraform-ops/scripts/check-action-refs.sh --offline
  107. purity "model-table" "$PY" skills/claude-api-ops/scripts/check-model-table.py --offline
  108. purity "hooks-lint" "$PY" skills/claude-code-ops/scripts/validate-hooks-json.py hooks/hooks.json
  109. __tf="$(mktemp)"; printf '{"suites":[]}' > "$__tf"
  110. purity "flake-triage" "$PY" skills/playwright-ops/scripts/triage-flakes.py "$__tf"
  111. rm -f "$__tf"
  112. purity "fleet-doctor" bash skills/fleet-worker/scripts/fleet-doctor.sh --offline
  113. purity "pricing-sync" "$PY" skills/loop-ops/scripts/check-pricing-sync.py --offline
  114. purity "r-facts" "$PY" skills/r-ops/scripts/check-r-facts.py --offline
  115. grep -q '_lib/term.sh' skills/terraform-ops/scripts/check-action-refs.sh \
  116. && pass "check-action-refs sources term.sh" || bad "check-action-refs missing term.sh"
  117. grep -q '_lib/term.sh' skills/fleet-worker/scripts/fleet-doctor.sh \
  118. && pass "fleet-doctor sources term.sh" || bad "fleet-doctor missing term.sh"
  119. for s in skills/claude-api-ops/scripts/check-model-table.py \
  120. skills/claude-code-ops/scripts/validate-hooks-json.py \
  121. skills/playwright-ops/scripts/triage-flakes.py \
  122. skills/loop-ops/scripts/check-pricing-sync.py \
  123. skills/r-ops/scripts/check-r-facts.py; do
  124. grep -q 'class Term' "$s" && pass "$(basename "$s") carries inline Term" \
  125. || bad "$(basename "$s") missing inline Term"
  126. done
  127. echo "== terminal design: term.sh itself + its consumers are ASCII-pure"
  128. # term.sh is shared infrastructure — a glyph that skips the ASCII registry leaks
  129. # into every panel in the repo at once. Exercise EVERY public helper (and every
  130. # registry key) under TERM_ASCII=1 and assert the whole emission is 7-bit.
  131. __probe="$(mktemp)"
  132. cat > "$__probe" <<'PROBE'
  133. . skills/_lib/term.sh
  134. TERM_ASCII=1 term_init
  135. for v in TERM_TREE_BRANCH TERM_TREE_LAST TERM_TREE_VERT TERM_PANEL_TL TERM_PANEL_BL \
  136. TERM_PANEL_HRULE TERM_PANEL_TERM TERM_ICON_PENDING TERM_ICON_READY \
  137. TERM_ICON_DONE TERM_ICON_FAILED TERM_ICON_WARN TERM_ICON_HINT \
  138. TERM_GLYPH_BRANCH TERM_GLYPH_ALERT TERM_GLYPH_TIP TERM_ARROW TERM_DOT \
  139. TERM_ELLIPSIS; do
  140. printf '%s=%s\n' "$v" "${!v}"
  141. done
  142. printf '%s\n' "${TERM_SPIN_WORKING[@]}" "${TERM_SPIN_HEARTBEAT[@]}"
  143. # Registry keys, plus one miss per registry to cover the not-found branch.
  144. for k in fleet forge psql watch deploy git windows-ops mac-ops github-ops audit \
  145. supply-chain net-ops adr loop terraform claude play __miss__; do
  146. term_brand_glyph "$k"; echo
  147. done
  148. for k in healthy pending warning critical alarm busted unknown __miss__; do
  149. term_health_glyph "$k"; echo; term_health "$k" text; echo
  150. done
  151. for k in user web mobile auth database cache queue storage service api search \
  152. timer build hook log __miss__; do term_diagram_icon "$k"; echo; done
  153. for k in ok bad gap warn skip na unknown __miss__; do term_mark "$k"; echo; done
  154. for s in RUNNING PENDING READY LANDED DONE OK FAILED ERROR CONFLICT WARN HINT \
  155. INFO __miss__; do term_state_icon "$s"; echo; term_section "$s" label 1; done
  156. term_truncate "$(printf 'x%.0s' $(seq 40))" 10; echo
  157. term_panel_open fleet name indicator; term_panel_open fleet name
  158. term_panel_close hotkeys healths; term_panel_close
  159. term_panel_vert; term_panel_line body; term_summary_line meta
  160. term_leaf_line "$TERM_TREE_BRANCH" name leaf meta age
  161. term_toast fleet message
  162. term_status_row ok label value; term_status_row warn label
  163. term_alert warning msg; term_alert critical msg
  164. for n in 0 1 2 3; do for h in HEAD CONFLICT EMPTY __miss__; do term_rail "$n" "$h"; echo; done; done
  165. for k in progress score capacity; do
  166. for f in 0 30 70 100; do term_pip_bar "$k" "$f" 100; echo; done
  167. term_pip_bar "$k" 3 5; echo
  168. done
  169. term_hotkey R refresh; echo
  170. for f in working heartbeat __miss__; do
  171. for t in 0 1 2 3 4 5 6 7 8 9; do term_spinner_frame "$f" "$t"; echo; done
  172. done
  173. term_header title meta; term_header title; term_divider 20
  174. term_tree_item icon label meta; term_tree_item icon label
  175. term_tree_connector 1 1; echo; term_tree_connector 1 2; echo
  176. term_tree_indent 1; echo; term_tree_indent 2; echo
  177. term_tree_node prefix conn label meta; term_tree_node prefix conn label
  178. term_table_row a b c d; term_empty nothing; term_color green text; echo
  179. PROBE
  180. __probe_out="$(FORCE_COLOR=1 bash "$__probe" 2>&1)"
  181. if printf '%s' "$__probe_out" | LC_ALL=C grep -q '[^[:print:][:cntrl:]]'; then
  182. bad "term.sh emits non-ASCII under TERM_ASCII=1: $(printf '%s' "$__probe_out" \
  183. | LC_ALL=C grep -o '[^[:print:][:cntrl:]]' | LC_ALL=C sort -u | tr -d '\n' | od -An -c | tr -s ' ')"
  184. else pass "term.sh: every helper + registry key ASCII-pure under TERM_ASCII=1"; fi
  185. rm -f "$__probe"
  186. # Source-level guard for the consumers. A hardcoded DECORATION glyph in an
  187. # authored string never reaches the registry, so TERM_ASCII=1 can't swap it —
  188. # the exact defect that leaked U+00B7 out of `fleet status`. Scoped to the
  189. # glyphs term.sh already registers a proxy for, so every hit has a named fix:
  190. # U+00B7 -> $TERM_DOT U+2192 -> $TERM_ARROW U+2026 -> term_truncate
  191. # tree/panel chrome -> $TERM_TREE_* / $TERM_PANEL_*
  192. # rail + pip + mark glyphs -> term_rail / term_pip_bar / term_mark
  193. # Prose punctuation (em dash etc.) is deliberately NOT matched — that is a
  194. # separate, wider class and term.sh registers no proxy for it.
  195. #
  196. # Two kinds of line are stripped before matching, because neither reaches a
  197. # terminal: shell comments (so a guard comment naming the character is fine),
  198. # and heredoc bodies (adr-init.sh writes a markdown ADR template to disk, where
  199. # a real arrow is correct and TERM_ASCII has no say).
  200. #
  201. # mac-ops is macOS-only and net-ops is per-platform, so output-level purity
  202. # checks can't reach them on CI — this static check is their only gate.
  203. __glyphs='·|→|…|│|├|└|─|╭|╰|●|◉|⊗|▰|▱|▲|✓|✗|⎇|⬤'
  204. __strip_heredocs='
  205. /<<-?[[:space:]]*['"'"'"]?[A-Za-z_][A-Za-z0-9_]*['"'"'"]?/ && !inhd {
  206. line = $0
  207. sub(/.*<<-?[[:space:]]*/, "", line)
  208. gsub(/['"'"'"]/, "", line)
  209. sub(/[^A-Za-z0-9_].*/, "", line)
  210. if (line != "") { inhd = 1; tag = line; next }
  211. }
  212. inhd { t = $0; sub(/^[[:space:]]+/, "", t); if (t == tag) inhd = 0; next }
  213. { print }
  214. '
  215. __dirty=""
  216. for s in $(grep -rl '_lib/term\.sh\|__MACOPS_TERM_LIB' --include='*.sh' skills/ 2>/dev/null); do
  217. case "$s" in */tests/*) continue ;; esac
  218. if sed 's/#.*$//' "$s" | awk "$__strip_heredocs" | grep -qE "$__glyphs"; then
  219. __dirty="$__dirty $s"
  220. fi
  221. done
  222. if [ -n "$__dirty" ]; then
  223. bad "term.sh consumers hardcode registry glyphs (use \$TERM_DOT/\$TERM_ARROW/term_*):$__dirty"
  224. else pass "term.sh consumers route every registry glyph through term.sh"; fi
  225. echo
  226. if [ "$fail" -eq 0 ]; then echo "resource checks: clean"; exit 0; fi
  227. echo "resource checks: failures above"; exit 1