From a2aa953761adebfc05b1c84dc654a5fe86e8e9b3 Mon Sep 17 00:00:00 2001 From: claude Date: Tue, 23 Jun 2026 09:47:36 -0400 Subject: [PATCH] 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) Claude-Session: https://claude.ai/code/session_01TfzV5QvtPDz8LJS3Pu5VLT --- .gitea/workflows/ci.yml | 25 ++++++++++++++++ .github/workflows/ci.yml | 25 ++++++++++++++++ tools/check.sh | 61 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+) create mode 100644 .gitea/workflows/ci.yml create mode 100644 .github/workflows/ci.yml create mode 100755 tools/check.sh diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..fae45ed --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,25 @@ +# PR + push CI for the course. Reports a commit status the claude-deck autopilot +# review gate reads, and runs the same build/test the gate runs on the merged tree: +# build = render the wiki from this tree (proves the generator works) +# test = tools/check.sh (lab compile + parse + no-slop guard + structure) +name: CI +on: + pull_request: {} + push: + branches: [main] + workflow_dispatch: {} + +jobs: + check: + runs-on: docker + steps: + - uses: actions/checkout@v4 + - name: build (render wiki) + test (check.sh) + shell: bash + run: | + set -euo pipefail + command -v python3 >/dev/null || { apt-get update && apt-get install -y --no-install-recommends python3 python3-pip; } + python3 -c "import yaml" 2>/dev/null || python3 -m pip install --quiet pyyaml 2>/dev/null || true + python3 tools/build_wiki.py --repo-root . --out /tmp/awc-wiki-build \ + --web-base https://git.jpaul.io/justin/ai-workflow-course --branch main --host gitea + bash tools/check.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..753d7a6 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,25 @@ +# PR + push CI for the GitHub mirror. Mirrors .gitea/workflows/ci.yml: +# build = render the wiki from this tree; test = tools/check.sh. +name: CI +on: + pull_request: {} + push: + branches: [main] + workflow_dispatch: {} + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.x' + - name: build (render wiki) + test (check.sh) + shell: bash + run: | + set -euo pipefail + python3 -m pip install --quiet pyyaml || true + python3 tools/build_wiki.py --repo-root . --out /tmp/awc-wiki-build \ + --web-base https://github.com/justin/ai-workflow-course --branch main --host github + bash tools/check.sh diff --git a/tools/check.sh b/tools/check.sh new file mode 100755 index 0000000..567525c --- /dev/null +++ b/tools/check.sh @@ -0,0 +1,61 @@ +#!/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" -- 2.52.0