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
51 changes: 51 additions & 0 deletions .github/workflows/ci-full.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
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.

on:
schedule:
- cron: '15 4 * * 1'
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ci-full-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
name: test (node ${{ matrix.node }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node: [18, 22]

steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Setup Node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: ${{ matrix.node }}

- name: Install
run: npm install --ignore-scripts

- name: Unit tests
run: npm test

- name: Static check CLI entrypoint
run: node --check bin/multiagent-safety.js

- name: Check scripts/ ↔ templates/scripts/ symlink parity
run: bash scripts/check-script-symlinks.sh

- name: Package dry run
run: npm pack --dry-run
34 changes: 25 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,24 +1,40 @@
name: CI

# Budget-friendly trigger surface. Branch protection on `main` forces
# all changes through a PR, so PR-time CI is sufficient — post-merge
# `push: main` CI was pure duplication and is dropped here. Run a full
# matrix on-demand via the workflow_dispatch trigger, or weekly via
# `ci-full.yml`.
on:
push:
branches:
- main
pull_request:
branches:
- main
types: [opened, reopened, synchronize, ready_for_review]
paths-ignore:
- '**/*.md'
- 'docs/**'
- 'openspec/**'
- '.github/ISSUE_TEMPLATE/**'
- '.github/PULL_REQUEST_TEMPLATE.md'
- '.changeset/**'
workflow_dispatch:

permissions:
contents: read

# One in-flight run per ref. Rapid pushes to an agent PR cancel the
# prior run instead of letting both finish on Actions minutes.
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
name: test (node ${{ matrix.node }})
name: test (node 20)
# Draft PRs skip CI to save minutes during in-flight agent work.
# CI auto-fires on `ready_for_review`.
if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node: [18, 20, 22]

steps:
- name: Checkout
Expand All @@ -27,7 +43,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: ${{ matrix.node }}
node-version: 20

- name: Install
run: npm install --ignore-scripts
Expand Down
16 changes: 10 additions & 6 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
name: CodeQL

# CodeQL is the single most expensive workflow per run on this repo.
# Per-PR / per-push triggers were the biggest line item on the monthly
# Actions bill, so they are dropped here. The weekly schedule keeps
# security coverage; `workflow_dispatch` is the on-demand escape hatch
# for ad-hoc audits.
on:
push:
branches:
- main
pull_request:
branches:
- main
schedule:
- cron: '35 3 * * 1'
workflow_dispatch:

permissions:
contents: read

concurrency:
group: codeql-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
analyze:
name: Analyze (javascript-typescript)
Expand Down
18 changes: 17 additions & 1 deletion .github/workflows/cr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,30 @@ name: Code Review

on:
pull_request:
types: [opened, reopened, synchronize]
types: [opened, reopened, synchronize, ready_for_review]

permissions:
contents: read
pull-requests: write

# Code review runs OpenAI API calls — cancel superseded runs on the
# same PR so rapid pushes don't fan-out the model bill.
concurrency:
group: cr-${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true

jobs:
review:
# Draft PRs and automated agent-lane PRs (head branch `agent/*`)
# 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.
if: >-
github.event.pull_request.draft == false &&
!startsWith(github.event.pull_request.head.ref, 'agent/')
runs-on: ubuntu-latest
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
Expand Down
6 changes: 4 additions & 2 deletions .github/workflows/scorecard.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
name: OpenSSF Scorecard

# Scorecard ran on every push to main, which compounded the per-merge
# Actions bill. The weekly schedule + branch-protection-rule trigger
# keeps the OpenSSF result fresh; `workflow_dispatch` covers ad-hoc
# audits.
on:
branch_protection_rule:
schedule:
- cron: '25 3 * * 1'
push:
branches: [main]
workflow_dispatch:

permissions: read-all
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-05-13
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# agent-claude-budget-friendly-ci-templates-2026-05-14-00-52 (minimal / T1)

Branch: `agent/<your-name>/<branch-slug>`

Describe the change in a sentence or two. Commit message is the spec of record.

## Handoff

- Handoff: change=`agent-claude-budget-friendly-ci-templates-2026-05-14-00-52`; branch=`agent/<your-name>/<branch-slug>`; scope=`TODO`; action=`continue this sandbox or finish cleanup after a usage-limit/manual takeover`.
- Copy prompt: Continue `agent-claude-budget-friendly-ci-templates-2026-05-14-00-52` on branch `agent/<your-name>/<branch-slug>`. Work inside the existing sandbox, review `openspec/changes/agent-claude-budget-friendly-ci-templates-2026-05-14-00-52/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/<your-name>/<branch-slug> --base dev --via-pr --wait-for-merge --cleanup`.

## Cleanup

- [ ] Run: `gx branch finish --branch agent/<your-name>/<branch-slug> --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`).
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
## Why

Gitguardex agent flows (`gx branch start` lanes) land high-volume PRs per month. Every PR currently fans out across CI, CodeQL, Scorecard, and AI Code Review with no skip mechanism, which dominates the GitHub Actions bill — to the point that the org's spending limit has been hit and blocked merges on multiple repos this month. The cost lands on humans too: even non-agent PRs eat duplicate `push:main` builds after merge.

## What Changes

Apply a coordinated budget posture across the gitguardex repo's own workflows AND seed a `templates/github/workflows/` directory that bootstraps the same defaults into every project that uses gitguardex.

Live workflows in this repo:

- `ci.yml` — drop `push: main` trigger (PR-time runs cover correctness under branch protection); drop the Node 18/22 matrix from PR-time (moved to a weekly `ci-full.yml`); add `paths-ignore` for docs/openspec/changeset paths; add `concurrency: cancel-in-progress`; gate the `test` job on `pull_request.draft == false`; add `ready_for_review` to the trigger list.
- `ci-full.yml` *(new)* — weekly schedule + `workflow_dispatch` runs the full Node 18 / 22 matrix that no longer runs per-PR.
- `codeql.yml` — drop `push:main` and `pull_request` triggers; keep weekly schedule + `branch_protection_rule` + `workflow_dispatch`; add concurrency.
- `cr.yml` — add concurrency; add `ready_for_review` trigger; skip on draft PRs and on `agent/*` head branches (the largest single CR-bill cut for agent-heavy repos).
- `scorecard.yml` — drop `push:main`; keep weekly schedule + `branch_protection_rule` + `workflow_dispatch`.

Templates seeded for downstream projects (`templates/github/workflows/`):

- `ci.yml` — same posture as the live `ci.yml` with placeholder steps.
- `ci-full.yml` — same posture as the live `ci-full.yml` with placeholder steps.
- `cr.yml` — mirrors the live `cr.yml` (including the `agent/*` skip pattern).
- `README.md` — documents the four trims, when to keep them, and when to relax.

## Impact

- **Repo CI bill drops materially.** Per-PR runs are now: one CI job on a single Node version, AI review only on human PRs, no CodeQL/Scorecard per PR. Cross-version compat coverage moves to a weekly schedule.
- **Per-PR feedback loop changes**: agents iterating in draft mode get no CI feedback until they promote to ready-for-review. Trade-off accepted: agents already run `pnpm test`/`pnpm typecheck`/`pnpm lint` in their worktrees before opening PRs; the CI gate is a final check, not the inner loop.
- **No AI code review on agent PRs.** Documented as a maintainer opt-in via a `needs-review` label or by stripping the `agent/*` guard from `cr.yml`.
- **Templates make this the default for every gitguardex-managed project**, so future repos don't have to discover the same posture independently.
- **No code changes outside `.github/workflows/` and `templates/github/`.** No runtime behavior change, no test changes, no scripts touched.
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
## ADDED Requirements

### Requirement: Live workflows carry a budget posture
Every GitHub Actions workflow in `.github/workflows/` of this repo SHALL:

- declare `concurrency:` with a per-ref group name and `cancel-in-progress: true`, **OR** be explicitly exempted with an inline comment explaining why superseded runs must not cancel (e.g. release publication on a tag),
- omit the `push: branches: [main]` trigger when the workflow's purpose is "validate before merge" (CI, CodeQL, Scorecard), since branch protection forces all changes through a PR and post-merge re-runs are duplication,
- include `ready_for_review` in `pull_request.types` when the workflow's per-PR job is gated by `pull_request.draft == false`.

#### Scenario: ci.yml gated on draft
- **GIVEN** the live `.github/workflows/ci.yml`
- **WHEN** parsed by a YAML loader
- **THEN** it MUST declare `concurrency: cancel-in-progress: true`
- **AND** its `test` job MUST set `if: github.event_name != 'pull_request' || github.event.pull_request.draft == false`
- **AND** its `on.pull_request.types` MUST contain `ready_for_review`
- **AND** it MUST NOT declare a `push: branches: [main]` trigger.

#### Scenario: codeql.yml runs on schedule, not per-PR
- **GIVEN** the live `.github/workflows/codeql.yml`
- **WHEN** parsed by a YAML loader
- **THEN** its `on` block MUST contain a `schedule:` entry
- **AND** its `on` block MUST contain `workflow_dispatch:`
- **AND** its `on` block MUST NOT contain a `pull_request:` or `push:` trigger.

#### Scenario: cr.yml skips agent/* head branches
- **GIVEN** the live `.github/workflows/cr.yml`
- **WHEN** parsed by a YAML loader
- **THEN** its `review` job's `if:` expression MUST include `!startsWith(github.event.pull_request.head.ref, 'agent/')`.

### Requirement: Templates seed the same posture in downstream projects
The `templates/github/workflows/` directory SHALL carry workflow files that bootstrap the same budget posture into a downstream gitguardex-managed project.

#### Scenario: Templates exist and parse
- **GIVEN** the `templates/github/workflows/` directory in this repo
- **THEN** it MUST contain `ci.yml`, `ci-full.yml`, `cr.yml`, and `README.md`
- **AND** each `.yml` file MUST parse cleanly with a standard YAML loader
- **AND** each `.yml` template MUST carry the same `concurrency:` + `if: draft == false` (or equivalent agent-skip) posture as the live file it mirrors.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
## Definition of Done

This change is complete only when **all** of the following are true:

- Every checkbox below is checked.
- The agent branch reaches `MERGED` state on `origin` and the PR URL + state are recorded in the completion handoff.
- If any step blocks (test failure, conflict, ambiguous result), append a `BLOCKED:` line under section 4 explaining the blocker and **STOP**. Do not tick remaining cleanup boxes; do not silently skip the cleanup pipeline.

## Handoff

- Handoff: change=`agent-claude-budget-friendly-ci-templates-2026-05-14-00-52`; branch=`agent/claude/budget-friendly-ci-templates-2026-05-14-00-52`; scope=`TODO`; action=`continue this sandbox or finish cleanup after a usage-limit/manual takeover`.
- Copy prompt: Continue `agent-claude-budget-friendly-ci-templates-2026-05-14-00-52` on branch `agent/claude/budget-friendly-ci-templates-2026-05-14-00-52`. Work inside the existing sandbox, review `openspec/changes/agent-claude-budget-friendly-ci-templates-2026-05-14-00-52/tasks.md`, continue from the current state instead of creating a new sandbox, and when the work is done run `gx branch finish --branch agent/claude/budget-friendly-ci-templates-2026-05-14-00-52 --base main --via-pr --wait-for-merge --cleanup`.

## 1. Specification

- [x] 1.1 Finalize proposal scope and acceptance criteria — see `proposal.md`.
- [x] 1.2 Define normative requirements in `specs/ci-workflow-budget/spec.md`.

## 2. Implementation

- [x] 2.1 Trim live workflows: `ci.yml`, `ci-full.yml` (new), `codeql.yml`, `cr.yml`, `scorecard.yml`.
- [x] 2.2 Seed `templates/github/workflows/` with `ci.yml`, `ci-full.yml`, `cr.yml`, and `README.md` carrying the same budget posture.

## 3. Verification

- [x] 3.1 `npm test` green; `bash scripts/check-script-symlinks.sh` green.
- [x] 3.2 `js-yaml` parse over all 8 modified/new workflow files.
- [ ] 3.3 Run `openspec validate agent-claude-budget-friendly-ci-templates-2026-05-14-00-52 --type change --strict`.

## 4. Cleanup (mandatory; run before claiming completion)

- [ ] 4.1 Run the cleanup pipeline: `gx branch finish --branch agent/claude/budget-friendly-ci-templates-2026-05-14-00-52 --base main --via-pr --wait-for-merge --cleanup`. This handles commit -> push -> PR create -> merge wait -> worktree prune in one invocation.
- [ ] 4.2 Record the PR URL and final merge state (`MERGED`) in the completion handoff.
- [ ] 4.3 Confirm the sandbox worktree is gone (`git worktree list` no longer shows the agent path; `git branch -a` shows no surviving local/remote refs for the branch).
67 changes: 67 additions & 0 deletions templates/github/workflows/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# `templates/github/workflows/` — budget-friendly CI defaults

Workflow files in this directory are copied into a gitguardex-managed
project's `.github/workflows/` directory when bootstrapping. They are
the **default** budget posture for projects that use `gx branch start`
to drive agent iterations.

Agent flows land a high volume of PRs per month. Without these trims,
every PR + every post-merge push fans out across CI, CodeQL, Scorecard,
and Code Review — which dominates the GitHub Actions bill for any
multi-agent repo. The trims below cut that cost without giving up
correctness coverage.

## What's trimmed and why

1. **`concurrency: cancel-in-progress: true`** scoped per workflow + ref
so rapid pushes to the same agent branch cancel the prior run
instead of letting both finish on Actions minutes.

2. **`if: github.event.pull_request.draft == false`** on every job that
shouldn't run on a draft PR, paired with
`pull_request.types: [..., ready_for_review]` in the trigger list so
CI fires the moment the PR is promoted out of draft.

3. **`if: !startsWith(head.ref, 'agent/')`** on the Code Review job
(`cr.yml`) — skip AI review on automated agent-lane PRs. AI review
on hundreds of agent PRs per month burns both Actions minutes and
OpenAI tokens without adding signal; human-authored PRs (any non-
`agent/*` head branch) still get reviewed.

4. **No `push: main` trigger** in `ci.yml` — branch protection on
`main` forces all changes through a PR, so PR-time CI is sufficient
and post-merge CI on `main` was pure duplication. Use
`workflow_dispatch` for ad-hoc full runs.

5. **`paths-ignore`** for docs / openspec / template-only changes — skip
CI on changes that don't affect runtime behavior.

## Customizing

- Replace `placeholder` steps in `ci.yml` with your build/test/lint
commands.
- Keep the `concurrency:`, `if:`, and `paths-ignore:` patterns. They
are the load-bearing part of the budget posture; removing them undoes
the win.

## When to skip the draft-skip pattern

If your CI is fast (≤ 2 min) and you want continuous validation as
agents iterate, drop the `if: pull_request.draft == false` job guard.
The concurrency cancel alone still prevents minute pile-up.

## When to re-enable AI code review on agent PRs

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.

## What about CodeQL / Scorecard?

The gitguardex repo itself runs CodeQL and Scorecard on the **weekly
schedule + `workflow_dispatch`** only — not on per-PR / per-push
triggers. Those workflows are long-running (5–10 min for CodeQL) and
were the largest single line item on the monthly Actions bill before
this change. If your project needs per-PR CodeQL gating for compliance
reasons, re-add the `pull_request` trigger and accept the cost; for
most repos, weekly + on-demand is the right default.
Loading
Loading