diff --git a/.claude/self/checks.sh b/.claude/self/checks.sh index f1bb3d2..0a1bade 100644 --- a/.claude/self/checks.sh +++ b/.claude/self/checks.sh @@ -39,9 +39,32 @@ do_lint() { [ -e "$f" ] || continue bash -n "$f" || { echo "lint: shell syntax error — $f"; rc=1; } done + # Workflow files are a workflow-DSL: they mix ESM-only `export` syntax with + # top-level `return`/`await`, so they're valid as neither plain CommonJS nor + # plain ESM and `node --check` can't validate them directly. Instead, strip + # the `export` keywords and wrap the body in an async IIFE (which makes + # top-level `return`/`await` legal), then parse it with vm.Script — parsing + # never executes the code, so undefined harness globals (agent, phase, log, + # ...) don't matter, but real syntax errors still surface as SyntaxError. for f in .claude/workflows/*.js; do [ -e "$f" ] || continue - node --check "$f" || { echo "lint: JS syntax error — $f"; rc=1; } + node -e ' + const fs = require("fs"); + const vm = require("vm"); + const f = process.argv[1]; + let src = fs.readFileSync(f, "utf8"); + src = src.replace(/^\s*export\s+default\s+/gm, "").replace(/^\s*export\s+/gm, ""); + const wrapped = "(async () => {\n" + src + "\n})"; + try { + new vm.Script(wrapped, { filename: f }); + } catch (e) { + if (e instanceof SyntaxError) { + console.error("lint: JS syntax error — " + f + ": " + e.message); + process.exit(1); + } + throw e; + } + ' "$f" || rc=1 done [ "$rc" -eq 0 ] && echo "lint: shell + workflow syntax OK" return "$rc" diff --git a/.github/workflows/gates.yml b/.github/workflows/gates.yml index a66d73c..a0ef7ef 100644 --- a/.github/workflows/gates.yml +++ b/.github/workflows/gates.yml @@ -42,3 +42,27 @@ jobs: # rather than red — same semantics as the local hooks. The coverage gate # command can read $COVERAGE_THRESHOLD (exported by the setup action). run: bash .claude/scripts/gate.sh ${{ matrix.gate }} + + # This repo self-hosts on the orchestrator template: harness changes under + # `.claude/**` and `docs/**` are exercised via the SELF adapter + # (.claude/self/gates.json), whose gates are actually implemented (node+bash + # checks in .claude/self/checks.sh). The `gates` job above reads the default, + # placeholder adapter (.claude/gates.json), which has empty gate commands for + # this repo and so skips cleanly — meaning harness/docs PRs would otherwise get + # no real server-side check. This job closes that gap without touching the + # placeholder job above. + self-gates: + name: self / ${{ matrix.gate }} + runs-on: ubuntu-latest + strategy: + fail-fast: false # one red gate shouldn't hide the others + matrix: + gate: [build, lint, test] + steps: + - uses: actions/checkout@v4 + - name: gate.sh ${{ matrix.gate }} (self adapter) + # Self-adapter gates are node+bash only, so the default toolchain + # shipped on ubuntu-latest runners is sufficient — no setup action needed. + env: + GATES_FILE: .claude/self/gates.json + run: bash .claude/scripts/gate.sh ${{ matrix.gate }}