Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion .claude/self/checks.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
24 changes: 24 additions & 0 deletions .github/workflows/gates.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Loading