run.sh 3.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
  1. #!/usr/bin/env bash
  2. # Offline self-test for the react-ops skill — structure, frontmatter, and the
  3. # staleness-verifier contract (SKILL-RESOURCE-PROTOCOL.md §7, §10).
  4. #
  5. # Offline-deterministic (no network, no React install). Resolves paths relative
  6. # to itself so it works in the repo and once installed to ~/.claude/skills/.
  7. #
  8. # Usage: bash tests/run.sh
  9. # Input: none (self-contained; no network)
  10. # Output: TAP-ish progress on stderr; final PASS/FAIL line.
  11. # Exit: 0 all pass, 1 any failure
  12. set -uo pipefail
  13. HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  14. SKILL="$(dirname "$HERE")"
  15. DOC="$SKILL/SKILL.md"
  16. PASS=0; FAIL=0
  17. ok() { PASS=$((PASS+1)); printf ' PASS %s\n' "$1" >&2; }
  18. no() { FAIL=$((FAIL+1)); printf ' FAIL %s\n' "$1" >&2; }
  19. # Resolve a *working* python (python3, else python). The bare `command -v` is
  20. # not enough on Windows, where `python3` is a Microsoft Store stub that exits
  21. # nonzero. Skip the whole verifier block if none works.
  22. PY=""
  23. for c in python3 python py; do
  24. if command -v "$c" >/dev/null 2>&1 && "$c" -c "" >/dev/null 2>&1; then PY="$c"; break; fi
  25. done
  26. echo "=== react-ops self-test ===" >&2
  27. # ── SKILL.md frontmatter ───────────────────────────────────────────────────
  28. [[ -f "$DOC" ]] && ok "SKILL.md present" || { no "SKILL.md missing"; echo "=== $PASS passed, $FAIL failed ===" >&2; exit 1; }
  29. doc="$(cat "$DOC")"
  30. case "$doc" in *"name: react-ops"*) ok "frontmatter declares name: react-ops";; *) no "frontmatter name != react-ops";; esac
  31. case "$doc" in *"license: MIT"*) ok "frontmatter declares license: MIT";; *) no "missing license: MIT";; esac
  32. case "$doc" in *"as of 20"*) ok "currency note carries a year";; *) no "no dated 'as of <year>' currency note";; esac
  33. # ── resources present + cited ──────────────────────────────────────────────
  34. for res in assets/react-facts.json scripts/check-react-facts.py; do
  35. [[ -f "$SKILL/$res" ]] && ok "resource present: $res" || no "missing resource: $res"
  36. done
  37. case "$doc" in *"scripts/check-react-facts.py"*) ok "verifier cited from SKILL.md";; *) no "verifier uncited";; esac
  38. # ── staleness verifier: offline contract (§7) ───────────────────────────────
  39. if [[ -n "$PY" ]]; then
  40. V="$SKILL/scripts/check-react-facts.py"
  41. F="$SKILL/assets/react-facts.json"
  42. ec() { local want="$1" lbl="$2"; shift 2; "$@" >/dev/null 2>&1; local got=$?
  43. [[ "$got" == "$want" ]] && ok "$lbl (exit $got)" || no "$lbl (want $want got $got)"; }
  44. TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
  45. ec 0 "py_compile" "$PY" -m py_compile "$V"
  46. ec 0 "--help" "$PY" "$V" --help
  47. ec 0 "--offline consistent" "$PY" "$V" --offline
  48. ec 2 "bad flag -> 2" "$PY" "$V" --bogus
  49. ec 2 "conflicting modes -> 2" "$PY" "$V" --offline --live
  50. jout="$("$PY" "$V" --offline --json 2>/dev/null)"
  51. case "$jout" in *"claude-mods.react-ops.facts/v1"*) ok "--json envelope schema";; *) no "--json envelope schema missing";; esac
  52. ec 3 "missing facts -> 3" "$PY" "$V" --offline --facts "$TMP/nope.json"
  53. printf '{"schema":"claude-mods.react-ops.facts/v1","packages":{"zzz":{"documented_major":1,"prose":["zzznotreal"]}}}' > "$TMP/drift.json"
  54. ec 10 "uncited package -> 10" "$PY" "$V" --offline --facts "$TMP/drift.json"
  55. else
  56. no "no working python to exercise the verifier"
  57. fi
  58. echo "=== $PASS passed, $FAIL failed ===" >&2
  59. [[ "$FAIL" -eq 0 ]] || exit 1