run.sh 5.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. #!/usr/bin/env bash
  2. # Self-test for the typescript-ops skill.
  3. #
  4. # Offline-deterministic (no network, no TypeScript compiler required). Asserts
  5. # structural integrity (frontmatter, references present + linked) and — the
  6. # load-bearing check — the staleness verifier contract (SKILL-RESOURCE-PROTOCOL
  7. # §7, §10): the catalogued version-bearing facts (TypeScript major, zod,
  8. # valibot) stay named in the prose and the dated currency note stays present.
  9. # Resolves paths relative to itself so it works in the repo and once installed
  10. # to ~/.claude/skills/typescript-ops/.
  11. #
  12. # Usage: bash tests/run.sh
  13. # Exit: 0 all pass, 1 one or more failures
  14. set -uo pipefail
  15. HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  16. SKILL="$(dirname "$HERE")"
  17. DOC="$SKILL/SKILL.md"
  18. REF="$SKILL/references"
  19. PASS=0; FAIL=0
  20. ok() { PASS=$((PASS+1)); printf ' PASS %s\n' "$1"; }
  21. no() { FAIL=$((FAIL+1)); printf ' FAIL %s\n' "$1"; }
  22. has() { case "$2" in *"$1"*) ok "$3";; *) no "$3 (missing '$1')";; esac; }
  23. echo "=== typescript-ops self-test ==="
  24. # ── SKILL.md frontmatter ───────────────────────────────────────────────────
  25. echo "-- frontmatter --"
  26. [[ -f "$DOC" ]] && ok "SKILL.md present" || { no "SKILL.md missing"; echo "=== $PASS passed, $FAIL failed ==="; exit 1; }
  27. [[ "$(sed -n '1p' "$DOC")" == "---" ]] && ok "frontmatter fence opens at line 1" || no "no opening frontmatter fence"
  28. doc="$(cat "$DOC")"
  29. has 'name: typescript-ops' "$doc" "frontmatter declares name: typescript-ops"
  30. has 'description:' "$doc" "frontmatter has description"
  31. has 'license: MIT' "$doc" "frontmatter declares license"
  32. has 'author: claude-mods' "$doc" "frontmatter declares metadata.author"
  33. # ── references: the 5 documented files exist ───────────────────────────────
  34. echo "-- references present --"
  35. EXPECT=(type-system utility-types generics-patterns config-strict ecosystem)
  36. for r in "${EXPECT[@]}"; do
  37. f="$REF/$r.md"
  38. [[ -f "$f" ]] && ok "$r.md present" || no "$r.md missing"
  39. done
  40. # ── every references/ citation in SKILL.md resolves (no ghost refs) ────────
  41. # typescript-ops cites its references as inline code spans (`./references/x.md`),
  42. # not markdown links — extract those spans and confirm each file exists.
  43. echo "-- cited references resolve --"
  44. linked=0; broken=0
  45. while IFS= read -r rel; do
  46. [[ -z "$rel" ]] && continue
  47. linked=$((linked+1))
  48. [[ -f "$SKILL/$rel" ]] || { no "SKILL.md cites missing file: $rel"; broken=$((broken+1)); }
  49. done < <(grep -oE '`(\./)?references/[^`#]+\.md`' "$DOC" | sed -E 's/^`//; s/`$//; s/^\.\///' | sort -u)
  50. [[ "$linked" -gt 0 ]] && ok "SKILL.md cites its references ($linked unique)" || no "SKILL.md cites no references"
  51. [[ "$broken" -eq 0 ]] && ok "all cited reference files resolve" || no "$broken reference citation(s) broken"
  52. # ── dated currency note present (verifier depends on it) ───────────────────
  53. echo "-- currency note --"
  54. grep -qE 'as of 20[0-9]{2}' "$DOC" && ok "dated 'as of 20XX' currency note present" || no "no dated currency note"
  55. # ── staleness verifier: offline contract (SKILL-RESOURCE-PROTOCOL §7) ───────
  56. echo "-- check-typescript-facts.py (offline) --"
  57. VERIFIER="$SKILL/scripts/check-typescript-facts.py"
  58. CATALOG="$SKILL/assets/typescript-facts.json"
  59. ec() { local want="$1" lbl="$2"; shift 2; "$@" >/dev/null 2>&1; local got=$?
  60. [[ "$got" == "$want" ]] && ok "$lbl (exit $got)" || no "$lbl (want $want got $got)"; }
  61. # Pick a python that actually executes — skips the Windows Store python3 stub.
  62. PY=""
  63. for c in python python3 py; do
  64. if command -v "$c" >/dev/null 2>&1 && "$c" -c "" >/dev/null 2>&1; then PY="$c"; break; fi
  65. done
  66. [[ -f "$VERIFIER" ]] && ok "verifier present" || no "verifier missing"
  67. [[ -f "$CATALOG" ]] && ok "facts catalog present" || no "catalog missing"
  68. has "scripts/check-typescript-facts.py" "$doc" "verifier cited from SKILL.md"
  69. if [[ -n "$PY" ]]; then
  70. TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
  71. ec 0 "py_compile" "$PY" -m py_compile "$VERIFIER"
  72. ec 0 "--help" "$PY" "$VERIFIER" --help
  73. ec 0 "--offline consistent" "$PY" "$VERIFIER" --offline
  74. ec 2 "bad flag -> 2" "$PY" "$VERIFIER" --bogus
  75. ec 2 "conflicting modes -> 2" "$PY" "$VERIFIER" --offline --live
  76. jout="$("$PY" "$VERIFIER" --offline --json 2>/dev/null)"
  77. has 'claude-mods.typescript-ops.facts/v1' "$jout" "--json envelope schema"
  78. ec 3 "missing catalog -> 3" "$PY" "$VERIFIER" --offline --catalog "$TMP/nope.json"
  79. printf '{"packages":"x"}' > "$TMP/bad.json"
  80. ec 4 "malformed catalog -> 4" "$PY" "$VERIFIER" --offline --catalog "$TMP/bad.json"
  81. printf '{"packages":[{"name":"zzznotreal","documented_major":3}]}' > "$TMP/drift.json"
  82. ec 10 "uncited package -> 10" "$PY" "$VERIFIER" --offline --catalog "$TMP/drift.json"
  83. else
  84. no "no working python to exercise the verifier"
  85. fi
  86. # ── summary ────────────────────────────────────────────────────────────────
  87. echo "=== $PASS passed, $FAIL failed ==="
  88. [[ "$FAIL" -eq 0 ]] || exit 1