#!/bin/bash # tools/qa.sh (Lane F) — the gate. Run before you finish a session; leave it GREEN. # Dependency-free: bash + node + python3. Gates accumulate each round. set -u cd "$(dirname "$0")/.." FAIL=0 red() { printf '\033[31m✗ %s\033[0m\n' "$1"; FAIL=1; } green() { printf '\033[32m✓ %s\033[0m\n' "$1"; } # 1. every JS module parses JSBAD=0 while IFS= read -r -d '' f; do node --check "$f" 2>/dev/null || { node --check "$f"; JSBAD=1; } done < <(find web/js -name '*.js' -print0) [ "$JSBAD" = 0 ] && green "node --check: all web/js modules parse" || red "JS syntax errors" # 2. determinism law: Math.random only in core/rng.js RAND=$(grep -rn 'Math\.random' web/js --include='*.js' | grep -v 'core/rng.js' || true) if [ -n "$RAND" ]; then red "Math.random outside core/rng.js:"; echo "$RAND"; else green "determinism: no stray Math.random"; fi # 3. level JSONs parse + minimal keys LVLBAD=0 for f in web/js/levels/*.json; do [ -e "$f" ] || continue python3 - "$f" <<'PY' || LVLBAD=1 import json, sys d = json.load(open(sys.argv[1])) assert d.get("id") and isinstance(d.get("seed"), int) and d.get("segments"), "missing id/seed/segments" PY done [ "$LVLBAD" = 0 ] && green "level JSONs valid" || red "level JSON errors" # 4. Lane C selfcheck (when it exists) if [ -f web/js/levels/index.js ]; then node web/js/levels/index.js --selfcheck && green "levels selfcheck" || red "levels selfcheck failed" fi # 5. Lane D manifest validation (when it exists) if [ -f pipeline/validate_manifest.py ]; then python3 pipeline/validate_manifest.py && green "manifest valid" || red "manifest errors" fi # 6. vendor present (the game must run offline) [ -f web/vendor/three.module.js ] && green "vendor: three r175 present" || red "web/vendor/three.module.js missing" [ "$FAIL" = 0 ] && printf '\033[32mQA GREEN\033[0m\n' || printf '\033[31mQA RED\033[0m\n' exit $FAIL