Files
ai-workflow-course/tools/check.sh
T
claude a2aa953761
CI / check (pull_request) Successful in 5s
feat(ci): add tools/check.sh test suite + PR CI (build=render, test=check)
Prereqs for autopilot full-auto-merge eligibility (and useful CI on their own):
- tools/check.sh: dependency-light test suite (lab py-compile, sh/json/yaml parse,
  no-em-dash slop guard, per-module template-section structure check). Exits non-zero
  on any failure. The autopilot review gate runs this as its `test` command.
- .gitea/.github workflows/ci.yml: run on every PR + push on the docker runners;
  build = render the wiki from the tree, test = tools/check.sh. The PR commit status
  is what the autopilot gate's CI precondition reads.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TfzV5QvtPDz8LJS3Pu5VLT
2026-06-23 09:47:36 -04:00

62 lines
2.6 KiB
Bash
Executable File

#!/usr/bin/env bash
# Test suite for ai-workflow-course. Exits non-zero on any failure.
#
# Used two ways:
# 1. CI (.gitea/workflows/ci.yml, .github/workflows/ci.yml) on every PR + push.
# 2. The claude-deck autopilot review gate's `test` command (runs on the
# merged-throwaway tree before an auto-merge).
#
# Dependency-light: python3 + grep + bash. The YAML check is skipped (not failed)
# if PyYAML is unavailable, so the suite still runs on a bare runner.
set -uo pipefail
cd "$(dirname "$0")/.." || exit 2
fail=0
note() { printf '%s\n' "$*"; }
# 1) Lab Python compiles (skip the intentional paste-in fragment in Module 12).
while IFS= read -r f; do
python3 -m py_compile "$f" 2>/dev/null || { note "FAIL py-compile: $f"; fail=1; }
done < <(find modules capstone -name '*.py' ! -name 'bad-clear-snippet.py')
# 2) Shell scripts parse.
while IFS= read -r f; do
bash -n "$f" 2>/dev/null || { note "FAIL sh-parse: $f"; fail=1; }
done < <(find modules capstone tools -name '*.sh')
# 3) JSON parses.
while IFS= read -r f; do
python3 -c "import json,sys; json.load(open(sys.argv[1]))" "$f" 2>/dev/null || { note "FAIL json: $f"; fail=1; }
done < <(find modules capstone -name '*.json')
# 4) YAML parses (only if PyYAML is present; otherwise skipped, not failed).
if python3 -c "import yaml" 2>/dev/null; then
while IFS= read -r f; do
python3 -c "import yaml,sys; list(yaml.safe_load_all(open(sys.argv[1])))" "$f" 2>/dev/null || { note "FAIL yaml: $f"; fail=1; }
done < <(find modules capstone .gitea .github -name '*.yml' -o -name '*.yaml' 2>/dev/null)
else
note "skip yaml (PyYAML not installed)"
fi
# 5) No-slop guard: no em-dash in prose. The character is built from its codepoint
# so this script holds no literal em-dash. Binaries and __pycache__ are skipped;
# the only allowed em-dash is the regex character class in tools/build_wiki.py.
emd=$(printf '\u2014')
emdash=$(grep -rlI --exclude-dir=__pycache__ --exclude='*.pyc' "$emd" \
README.md AGENTS.md _TEMPLATE.md handoff.md the-workflow-syllabus.md \
modules capstone blog tools 2>/dev/null | grep -v 'tools/build_wiki.py' || true)
if [ -n "$emdash" ]; then
note "FAIL em-dash present in:"; printf ' %s\n' $emdash; fail=1
fi
# 6) Structure: every module + capstone README has the core template sections.
for d in modules/*/ capstone/; do
f="${d}README.md"
[ -f "$f" ] || { note "FAIL missing README: $d"; fail=1; continue; }
for h in "## Prerequisites" "## Hands-on lab" "## Where it breaks"; do
grep -qF "$h" "$f" || { note "FAIL missing section '$h': $f"; fail=1; }
done
done
if [ "$fail" = 0 ]; then note "check.sh: PASS"; else note "check.sh: FAIL"; fi
exit "$fail"