From 3df6e421130af653a355b1db845d8259c0306c24 Mon Sep 17 00:00:00 2001 From: Roberto Cano <3525807+robercano@users.noreply.github.com> Date: Fri, 3 Jul 2026 15:52:36 +0200 Subject: [PATCH 1/2] fix(harness): parse-only syntax check for ESM/DSL workflow files (#25) .claude/workflows/*.js 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. Strip the `export` keywords, wrap the body in an async IIFE, and parse (not execute) it with vm.Script so real syntax errors still surface without needing the harness globals (agent, phase, log, ...) to be defined. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01WNHtY86twXHEKsLEucSJyY --- .claude/self/checks.sh | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) 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" From 78091d149fed4f3d70242b923b3fdb6febabebe0 Mon Sep 17 00:00:00 2001 From: Roberto Cano <3525807+robercano@users.noreply.github.com> Date: Fri, 3 Jul 2026 15:54:44 +0200 Subject: [PATCH 2/2] feat(ci): run self-adapter gates on PRs (#25) The `gates` job reads the default, placeholder adapter (.claude/gates.json), whose commands are empty for this repo and so every check skips green. That leaves harness (.claude/**) and docs (docs/**) changes with no real server-side gate. Add a second `self-gates` job that runs build/lint/test through the self adapter (.claude/self/gates.json), whose checks are actually implemented, without touching the existing placeholder job. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01WNHtY86twXHEKsLEucSJyY --- .github/workflows/gates.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) 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 }}