Skip to content
Closed
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
54 changes: 54 additions & 0 deletions .github/actions/setup/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: Adapter setup
description: >
Toolchain setup + dependency install, driven entirely by .claude/gates.json so the
same workflow works on any downstream repo without hand-edited YAML. Reads
project.packageManager / project.language / gates.coverage_threshold, sets up the
matching toolchain, runs the `install` gate, and exports COVERAGE_THRESHOLD.

runs:
using: composite
steps:
# Read the adapter. `node` is preinstalled on GitHub runners, so this works even
# before any toolchain setup — same dependency gate.sh already relies on.
- id: adapter
shell: bash
run: |
gates=".claude/gates.json"
read_json() { node -e "try{const g=require('./$gates');process.stdout.write(String($1||''))}catch(e){process.stdout.write('')}"; }
pm="$(read_json "g.project&&g.project.packageManager")"
lang="$(read_json "g.project&&g.project.language")"
thr="$(read_json "g.gates&&g.gates.coverage_threshold")"
echo "package-manager=$pm" >> "$GITHUB_OUTPUT"
echo "language=$lang" >> "$GITHUB_OUTPUT"
# Export the threshold so coverage gate commands can read $COVERAGE_THRESHOLD.
[ -n "$thr" ] && echo "COVERAGE_THRESHOLD=$thr" >> "$GITHUB_ENV"
echo "adapter: packageManager='$pm' language='$lang' coverage_threshold='$thr'"

# --- JavaScript/TypeScript toolchain (the common case) -------------------------
- if: contains(fromJSON('["pnpm","npm","yarn"]'), steps.adapter.outputs.package-manager)
uses: actions/setup-node@v4
with:
node-version: 20

- if: steps.adapter.outputs.package-manager == 'pnpm'
shell: bash
run: corepack enable

# --- Extension point: add stack-specific toolchain setup here ------------------
# The template ships the JS path above. Downstream repos on other stacks add their
# setup here (it's a config edit, like gates.json). Examples:
#
# - if: contains(steps.adapter.outputs.language, 'olidity') # Solidity / Foundry
# uses: foundry-rs/foundry-toolchain@v1
#
# - if: contains(steps.adapter.outputs.language, 'ython') # Python
# uses: actions/setup-python@v5
# with: { python-version: '3.12' }
#
# See issue #9 (per-worktree lifecycle) — the same bootstrap that lets isolated
# worktree workers run a gate is what CI needs here.

# Install dependencies via the adapter's `install` gate (skips cleanly if empty).
- name: Install dependencies
shell: bash
run: bash .claude/scripts/gate.sh install
44 changes: 44 additions & 0 deletions .github/workflows/gates.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Server-side gate enforcement. Mirrors the local gates (settings.json hooks +
# gate.sh) at the GitHub PR level, so a merge is gated by CI — not only by the
# orchestrator remembering to run the gates before it opens a PR.
#
# Adapter-driven: each job just runs `gate.sh <name>`, which reads the command from
# .claude/gates.json (and skips cleanly when a gate is left ""). You should not need
# to edit this file per project — configure gates.json instead. Toolchain setup lives
# in .github/actions/setup (also adapter-driven). See docs/PROMPTS.md to (re)generate.
#
# Enforcement teeth: turn these checks into REQUIRED status checks via branch
# protection on the base branch — see docs/GETTING_STARTED.md.
name: gates

on:
pull_request:
workflow_dispatch:

permissions:
contents: read

# A newer push to the same PR cancels the in-flight run.
concurrency:
group: gates-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
gates:
name: ${{ matrix.gate }}
runs-on: ubuntu-latest
strategy:
fail-fast: false # one red gate shouldn't hide the others
matrix:
# One job per gate → each surfaces as its own PR check (what visual
# front-ends like emdash monitor). `install` is not here — it runs once in
# the setup action as a prerequisite for every gate.
gate: [build, lint, typecheck, test, coverage, security]
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
- name: gate.sh ${{ matrix.gate }}
# An unconfigured gate ("") exits 0 (skip), so the check is green-but-trivial
# 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 }}
26 changes: 26 additions & 0 deletions docs/GETTING_STARTED.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,35 @@ Scope it, show me the plan, and wait for my approval before writing code.
Approve the plan, let one implementer run in its worktree, watch the reviewers gate it, review the PR. Then
read [`USAGE.md`](USAGE.md) to scale up, and [`TOKEN_BUDGET.md`](TOKEN_BUDGET.md) before you go parallel.

## Step 7 — Enforce gates in CI (server-side)
The hooks and `gate.sh` enforce gates *locally*, and the orchestrator runs them before opening a PR — but
nothing stops a human (or a bot) merging a PR whose gates never ran. The template ships
[`.github/workflows/gates.yml`](../.github/workflows/gates.yml) + a `.github/actions/setup` composite action
that run **the same `gate.sh` gates** on every `pull_request`, reading commands from `gates.json`. It's
adapter-driven — you configure `gates.json`, not the YAML.

1. **It works out of the box for JS/TS.** For other stacks, add the toolchain at the *Extension point* comment
in `.github/actions/setup/action.yml` (e.g. `foundry-rs/foundry-toolchain` for Solidity,
`actions/setup-python`), keyed off `project.language`. Empty gates skip, so unconfigured checks stay green.
2. **Make the checks required** — this is the actual enforcement. On the base branch (`merge.baseBranch`):
**Settings → Branches → Add branch protection rule** → *Require status checks to pass before merging*, then
select the gate checks (`build`, `lint`, `typecheck`, `test`, `coverage`, `security`). Or via CLI:
```bash
gh api -X PUT repos/<owner>/<repo>/branches/<base>/protection \
-f 'required_status_checks[strict]=true' \
-f 'required_status_checks[checks][][context]=build' \
-f 'required_status_checks[checks][][context]=lint' \
-f 'required_status_checks[checks][][context]=typecheck' \
-f 'required_status_checks[checks][][context]=test' \
-f 'required_status_checks[checks][][context]=coverage' \
-f 'enforce_admins=true' -F 'required_pull_request_reviews=null' -F 'restrictions=null'
```
Without this step the workflow only *reports* pass/fail; required checks are what block the merge button.

## Verification checklist
- [ ] `CLAUDE.md` describes the project and lists modules.
- [ ] `.claude/gates.json` has real commands; `gate.sh build|lint|test` behave correctly.
- [ ] `/agents` lists orchestrator, implementer, reviewer, test-runner.
- [ ] A pilot task produced a branch/PR that passed gates + review.
- [ ] CI gates run on PRs and are set as **required** status checks on the base branch (Step 7).
- [ ] You've checked spend with `/cost` or `npx ccusage`.
12 changes: 12 additions & 0 deletions docs/PROMPTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,15 @@ Run `npx ccusage` and summarize token/cost by model for this session. Given the
changes to: max_parallel_workers, model routing, the module map, and any agent prompt that caused rework or
overlap. Propose concrete edits to gates.json.
```

## 10. Wire up CI gate enforcement (server-side)
```
The repo ships .github/workflows/gates.yml + .github/actions/setup — an adapter-driven CI that runs each
gate.sh gate on pull_request, reading commands from gates.json. Don't hand-edit the workflow. Instead:
1. Confirm gates.json has working gate commands (run `bash .claude/scripts/gate.sh <name>` for each).
2. If this repo isn't pure JS/TS, add the toolchain to .github/actions/setup at the "Extension point" comment
(e.g. foundry-toolchain for Solidity, setup-python for Python), keyed off project.language. Keep it a
config edit — don't fork the workflow.
3. Make the matrix checks REQUIRED via branch protection (see GETTING_STARTED "Step 7 — Enforce gates in CI"),
so a red gate actually blocks merge. Show me the branch-protection settings to apply.
```
Loading