From cc7553af9c08048d34d4aecf64ac8ea3ede8dad3 Mon Sep 17 00:00:00 2001 From: NagyVikt Date: Thu, 14 May 2026 01:30:55 +0200 Subject: [PATCH] ci: label-based opt-in for AI review + full matrix on agent PRs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds escape hatches for the budget-friendly defaults. Both apply identically in the live workflows AND the templates ship to downstream projects. - cr.yml: agent/* PRs still skip AI review by default, but the `needs-review` label overrides the skip for that one PR. - ci-full.yml: weekly schedule + workflow_dispatch unchanged, plus per-PR opt-in via the `needs-ci-full` label. Both workflows add `labeled` to their pull_request.types so applying the label fires the run immediately — no re-push required. README documents the labels and points at `gh label create` for setup. Pre-existing test/metadata.test.js failures (4) are stale assertions from PR #571 and remain out of scope here. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci-full.yml | 13 ++++++++++-- .github/workflows/cr.yml | 13 +++++++----- .../.openspec.yaml | 2 ++ .../notes.md | 16 +++++++++++++++ templates/github/workflows/README.md | 20 +++++++++++++++++++ templates/github/workflows/ci-full.yml | 11 +++++++++- templates/github/workflows/cr.yml | 17 ++++++++++------ 7 files changed, 78 insertions(+), 14 deletions(-) create mode 100644 openspec/changes/agent-claude-ci-label-based-opt-in-2026-05-14-01-28/.openspec.yaml create mode 100644 openspec/changes/agent-claude-ci-label-based-opt-in-2026-05-14-01-28/notes.md diff --git a/.github/workflows/ci-full.yml b/.github/workflows/ci-full.yml index b94c012..951d005 100644 --- a/.github/workflows/ci-full.yml +++ b/.github/workflows/ci-full.yml @@ -2,13 +2,16 @@ name: CI (full matrix) # Weekly compatibility run across the supported Node versions # (engine `>=18`). PR-time CI only runs Node 20 for budget reasons; -# this catches Node 18 / 22 regressions within seven days, and can be -# triggered on-demand via workflow_dispatch before a release. +# this catches Node 18 / 22 regressions within seven days, can be +# triggered on-demand via workflow_dispatch before a release, and can +# be opted-into per-PR by applying the `needs-ci-full` label. on: schedule: - cron: '15 4 * * 1' workflow_dispatch: + pull_request: + types: [labeled, synchronize] permissions: contents: read @@ -20,6 +23,12 @@ concurrency: jobs: test: name: test (node ${{ matrix.node }}) + # PR runs only fire when the `needs-ci-full` label is present. + # Schedule and workflow_dispatch always run. + if: >- + github.event_name != 'pull_request' || + contains(github.event.pull_request.labels.*.name, 'needs-ci-full') || + (github.event.action == 'labeled' && github.event.label.name == 'needs-ci-full') runs-on: ubuntu-latest strategy: fail-fast: false diff --git a/.github/workflows/cr.yml b/.github/workflows/cr.yml index e810f50..5c43e01 100644 --- a/.github/workflows/cr.yml +++ b/.github/workflows/cr.yml @@ -2,7 +2,7 @@ name: Code Review on: pull_request: - types: [opened, reopened, synchronize, ready_for_review] + types: [opened, reopened, synchronize, ready_for_review, labeled] permissions: contents: read @@ -20,12 +20,15 @@ jobs: # skip review. The agent flow lands hundreds of PRs per month; AI # review on each one burns both Actions minutes and OpenAI tokens # without adding signal. Human-authored PRs (any non-`agent/*` - # head branch) still get reviewed; agent PRs can opt-in by adding - # a `needs-review` label, which a maintainer can re-run via - # `workflow_dispatch`-style label trigger if desired. + # head branch) still get reviewed; agent PRs opt-in by applying + # the `needs-review` label. if: >- github.event.pull_request.draft == false && - !startsWith(github.event.pull_request.head.ref, 'agent/') + ( + !startsWith(github.event.pull_request.head.ref, 'agent/') || + contains(github.event.pull_request.labels.*.name, 'needs-review') || + (github.event.action == 'labeled' && github.event.label.name == 'needs-review') + ) runs-on: ubuntu-latest env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} diff --git a/openspec/changes/agent-claude-ci-label-based-opt-in-2026-05-14-01-28/.openspec.yaml b/openspec/changes/agent-claude-ci-label-based-opt-in-2026-05-14-01-28/.openspec.yaml new file mode 100644 index 0000000..93831bd --- /dev/null +++ b/openspec/changes/agent-claude-ci-label-based-opt-in-2026-05-14-01-28/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-05-13 diff --git a/openspec/changes/agent-claude-ci-label-based-opt-in-2026-05-14-01-28/notes.md b/openspec/changes/agent-claude-ci-label-based-opt-in-2026-05-14-01-28/notes.md new file mode 100644 index 0000000..a44033d --- /dev/null +++ b/openspec/changes/agent-claude-ci-label-based-opt-in-2026-05-14-01-28/notes.md @@ -0,0 +1,16 @@ +# agent-claude-ci-label-based-opt-in-2026-05-14-01-28 (minimal / T1) + +Branch: `agent//` + +Describe the change in a sentence or two. Commit message is the spec of record. + +## Handoff + +- Handoff: change=`agent-claude-ci-label-based-opt-in-2026-05-14-01-28`; branch=`agent//`; scope=`TODO`; action=`continue this sandbox or finish cleanup after a usage-limit/manual takeover`. +- Copy prompt: Continue `agent-claude-ci-label-based-opt-in-2026-05-14-01-28` on branch `agent//`. Work inside the existing sandbox, review `openspec/changes/agent-claude-ci-label-based-opt-in-2026-05-14-01-28/notes.md`, continue from the current state instead of creating a new sandbox, and when the work is done run `gx branch finish --branch agent// --base dev --via-pr --wait-for-merge --cleanup`. + +## Cleanup + +- [ ] Run: `gx branch finish --branch agent// --base dev --via-pr --wait-for-merge --cleanup` +- [ ] Record PR URL + `MERGED` state in the completion handoff. +- [ ] Confirm sandbox worktree is gone (`git worktree list`, `git branch -a`). diff --git a/templates/github/workflows/README.md b/templates/github/workflows/README.md index a132f6e..432f8ea 100644 --- a/templates/github/workflows/README.md +++ b/templates/github/workflows/README.md @@ -56,6 +56,26 @@ If your team relies on AI review as a true gating signal (not just advisory), remove the `!startsWith(head.ref, 'agent/')` guard in `cr.yml`. Expect the OpenAI bill to scale linearly with merge volume. +## Per-PR label opt-in + +Both `cr.yml` and `ci-full.yml` honor PR labels so the occasional +agent PR that actually needs the heavier check can opt in without +flipping a global toggle: + +| Label | Effect | +| --- | --- | +| `needs-review` | Run AI code review on this PR even though it's `agent/*`. Useful for security-sensitive changes or public-API redesigns. | +| `needs-ci-full` | Run the full cross-runtime matrix from `ci-full.yml` on this PR instead of waiting for the weekly schedule. Useful before a release branch lands. | + +To enable: open the PR, then `gh pr edit --add-label needs-review` +(or click the labels picker in the GitHub UI). The label-trigger fires +the workflow immediately; you don't need to re-push. + +Add label definitions to your repo with `gh label create needs-review +--description "Run AI code review on this PR"` and similar for +`needs-ci-full`, or define them in `.github/labels.yml` if you use a +label-sync workflow. + ## What about CodeQL / Scorecard? The gitguardex repo itself runs CodeQL and Scorecard on the **weekly diff --git a/templates/github/workflows/ci-full.yml b/templates/github/workflows/ci-full.yml index 2b52042..9e74b3c 100644 --- a/templates/github/workflows/ci-full.yml +++ b/templates/github/workflows/ci-full.yml @@ -4,7 +4,8 @@ # # Strategy: PR-time `ci.yml` runs the primary runtime only (cheap). # This workflow runs the full matrix on the weekly schedule, and -# on-demand via `workflow_dispatch` before a release. +# on-demand via `workflow_dispatch` before a release. Per-PR opt-in +# is available by applying the `needs-ci-full` label to a PR. # # Customize the matrix rows below to match your supported runtimes. @@ -14,6 +15,8 @@ on: schedule: - cron: '15 4 * * 1' workflow_dispatch: + pull_request: + types: [labeled, synchronize] permissions: contents: read @@ -25,6 +28,12 @@ concurrency: jobs: test: name: test (node ${{ matrix.node }}) + # PR runs only fire when the `needs-ci-full` label is present. + # Schedule and workflow_dispatch always run. + if: >- + github.event_name != 'pull_request' || + contains(github.event.pull_request.labels.*.name, 'needs-ci-full') || + (github.event.action == 'labeled' && github.event.label.name == 'needs-ci-full') runs-on: ubuntu-latest strategy: fail-fast: false diff --git a/templates/github/workflows/cr.yml b/templates/github/workflows/cr.yml index 4aa6c81..2520a40 100644 --- a/templates/github/workflows/cr.yml +++ b/templates/github/workflows/cr.yml @@ -2,7 +2,7 @@ name: Code Review on: pull_request: - types: [opened, reopened, synchronize, ready_for_review] + types: [opened, reopened, synchronize, ready_for_review, labeled] permissions: contents: read @@ -17,13 +17,18 @@ concurrency: jobs: review: - # Skip on draft PRs and on `agent/*` head branches. Gitguardex - # agent flows land high-volume PRs that don't benefit from AI - # review; skipping them is the single largest CR-bill cut. - # Review fires on `ready_for_review` automatically for human PRs. + # Skip on draft PRs and on `agent/*` head branches by default. + # Agent PRs can opt-in by applying the `needs-review` label — + # useful for the occasional agent PR that genuinely needs AI + # eyes (security-sensitive change, public-API redesign, etc.). + # Human-authored PRs (any non-`agent/*` head branch) always run. if: >- github.event.pull_request.draft == false && - !startsWith(github.event.pull_request.head.ref, 'agent/') + ( + !startsWith(github.event.pull_request.head.ref, 'agent/') || + contains(github.event.pull_request.labels.*.name, 'needs-review') || + (github.event.action == 'labeled' && github.event.label.name == 'needs-review') + ) runs-on: ubuntu-latest env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}