Risk, security, and validation profiles #65
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| pull_request: | |
| push: | |
| branches: [main] | |
| concurrency: | |
| # PR events share a per-PR group so new pushes supersede stale runs; non-PR | |
| # events (push to main) get a unique per-run group so no pending main run is | |
| # ever replaced or canceled. | |
| group: ci-${{ github.event_name == 'pull_request' && github.event.pull_request.number || github.run_id }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| permissions: | |
| contents: read | |
| jobs: | |
| quality: | |
| # Actions pinned to commit SHAs, like every other tool here (invariant 5): a | |
| # major-only `@v4` moves under us, which is the irreproducibility the pinned | |
| # shellcheck image and claude CLI avoid. The runner is pinned to an OS release | |
| # rather than `ubuntu-latest` — GitHub still refreshes that image's contents | |
| # weekly, so this bounds the drift, it does not eliminate it. Full image | |
| # reproducibility would need a container, which this check battery | |
| # doesn't warrant. | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| # checkout writes the job token into .git/config by default, where any | |
| # later step — or anything a step runs — can read it. Nothing here needs | |
| # authenticated git after the clone, so don't leave it lying around. | |
| persist-credentials: false | |
| # The version-bump check diffs against the merge-base with the PR's base | |
| # branch, and the default depth-1 clone has neither the base branch nor a | |
| # merge-base. ~50 commits, so the full history costs nothing here. | |
| fetch-depth: 0 | |
| # The hook, the two checkers and their three suites are the only executables here, | |
| # so lint plus those suites are the only mechanical gates we have. Everything else | |
| # here is a prompt, and prompts have no typechecker (see README, "Contributing"). | |
| # POSIX sh, for two different reasons that must not be blurred: the HOOK is bound | |
| # by invariant 4, because it runs under whatever /bin/sh a user has, so a bash-ism | |
| # there is a portability bug rather than a style opinion; the two repo-local | |
| # checkers are bound by this workflow invoking them with `sh`, which is their own | |
| # requirement and not invariant 4's reach. | |
| # Pinned to an exact image tag — a moving linter makes the gate irreproducible. | |
| - name: Lint shell scripts (POSIX sh) | |
| run: | | |
| docker run --rm -v "$PWD:/mnt" -w /mnt koalaman/shellcheck:v0.11.0 \ | |
| --shell=sh plugins/dev-workflow/hooks/codex-gate.sh | |
| # The test file's only findings are SC2015 on `[ c ] && pass "x" || fail "x"`. | |
| # `pass` is a bare printf whose sole failure mode is a broken stdout, which | |
| # runs `fail` too — a spurious FAIL, never a false pass. Excluded by code, so | |
| # every other check still applies to this file. | |
| docker run --rm -v "$PWD:/mnt" -w /mnt koalaman/shellcheck:v0.11.0 \ | |
| --shell=sh --exclude=SC2015 plugins/dev-workflow/hooks/codex-gate.test.sh | |
| # The two repo-local checkers are CI gates themselves, so they get the same | |
| # POSIX lint as the hook. | |
| docker run --rm -v "$PWD:/mnt" -w /mnt koalaman/shellcheck:v0.11.0 \ | |
| --shell=sh scripts/check-invariants.sh | |
| docker run --rm -v "$PWD:/mnt" -w /mnt koalaman/shellcheck:v0.11.0 \ | |
| --shell=sh --exclude=SC2015 scripts/check-invariants.test.sh | |
| # No exclusion on this pair: unlike the two suites above, it has no | |
| # `[ c ] && pass || fail` lines, so every rule applies as-is. | |
| docker run --rm -v "$PWD:/mnt" -w /mnt koalaman/shellcheck:v0.11.0 \ | |
| --shell=sh scripts/check-version-bump.sh | |
| docker run --rm -v "$PWD:/mnt" -w /mnt koalaman/shellcheck:v0.11.0 \ | |
| --shell=sh scripts/check-version-bump.test.sh | |
| - name: Hook state-machine tests | |
| run: sh plugins/dev-workflow/hooks/codex-gate.test.sh | |
| # Invariants 5 and 6 plus two prompt-conformance checks, mechanically, and BOTH | |
| # checkers' regression suites. The | |
| # invariant-12 checker itself is not here — it needs a PR base and runs in the | |
| # step below, so naming this step "version bump" would show a green version-bump | |
| # label on a push that was never version-bump checked. | |
| # Each rule was prose first and each was violated anyway — a floating action ref | |
| # shipped in this very workflow, a duplicate hooks manifest key stopped the plugin | |
| # loading in 0.2.1, and the plugin shipped changes without a bump twice. | |
| - name: Invariant checks (pinning, manifest, prompt conformance) + both checker suites | |
| run: | | |
| sh scripts/check-invariants.test.sh | |
| sh scripts/check-invariants.sh | |
| sh scripts/check-version-bump.test.sh | |
| # Invariant 12 itself needs a base to diff against, so it runs only on pull | |
| # requests — a push to main has no PR base, and diffing the push range would just | |
| # re-check what the PR run already gated. Consequence, stated rather than implied: | |
| # a commit pushed straight to main bypasses this check entirely. | |
| # | |
| # base_ref goes through env, never into the run script via ${{ }}: git ref names | |
| # may contain shell metacharacters, and direct interpolation would splice a branch | |
| # name in as code rather than pass it as data. | |
| - name: Version bump required for plugin changes (invariant 12) | |
| if: github.event_name == 'pull_request' | |
| env: | |
| BASE_REF: ${{ github.base_ref }} | |
| run: sh scripts/check-version-bump.sh "origin/$BASE_REF" | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: 22.23.1 | |
| # `plugin validate` needs no auth and no API key — verified against a clean | |
| # HOME — so it runs in CI unchanged: manifest schema, skill/command | |
| # frontmatter, and hooks.json syntax. | |
| - name: Validate marketplace + plugin manifests | |
| run: | | |
| # Pinned exactly, never floating: a moving validator makes this check | |
| # irreproducible. Bump deliberately (repo's own `dependency-unpinned` class). | |
| npm install -g @anthropic-ai/claude-code@2.1.207 | |
| claude plugin validate . --strict |