CI/CD Pipelines for AI-Coded Apps
LLM-generated code ships with predictable failure modes: plausible-but-wrong logic, silently-disabled tests, hallucinated APIs, secrets pasted inline, dependencies pulled from typosquats, and "works on my laptop" defaults. The pipeline is the only honest reviewer — if it doesn't block, it ships.
When to apply
- New repo getting its first CI workflow
- Existing pipeline that "works" but has obvious gaps (no SAST, no SBOM, no coverage gate)
- A bug or incident traced back to "should have been caught in CI"
- Migrating to a reusable / shared workflow across repos
- Standardising AI-coded projects on a baseline before scaling team
Six categories of gates
Every pipeline needs coverage of these. Items marked 🔴 are non-negotiable — they fail the build.
1. Security gates 🔴
| Gate |
Tool |
What it catches |
| Secret scanning on every push |
gitleaks or trufflehog + GitHub push protection |
API keys, tokens, private keys |
| SAST |
semgrep (multi-lang), bandit (Py), eslint-plugin-security, gosec (Go) |
Injection, unsafe APIs, hardcoded crypto |
| SCA on every PR (not just weekly) |
pip-audit, npm audit --audit-level=high, osv-scanner |
New Critical/High CVEs in deps |
| Container image scan |
trivy image or grype (also scan base image) |
Vulns in OS packages and app layers |
| IaC scan |
checkov, tfsec |
Misconfigured Dockerfiles, compose, k8s, terraform |
| Dependency Review action on PRs |
GitHub dependency-review-action |
Diff of newly-added deps + their licences/CVEs |
| SBOM on release |
syft → SPDX/CycloneDX |
Supply-chain audits |
| Build provenance |
actions/attest-build-provenance |
SLSA level 2+ |
Pinned action SHAs (not @v4) |
step-security/harden-runner |
Action supply-chain attacks, blocked egress |
2. Correctness & maintainability 🔴
| Gate |
Tool |
Why |
| Format + lint blocking |
prettier, ruff, gofmt |
AI cheerfully ignores style; the pipeline must not |
| Strict typing |
mypy --strict, tsc --noEmit --strict, go vet |
Most hallucinated APIs die here |
| Unit + integration tests both required |
native runners |
AI-generated unit tests often mirror buggy code; integration hits real contracts |
| Coverage floor + diff-cover |
coverage.py, c8, diff-cover |
E.g. 80% line floor + diff coverage ≥ floor. Kills "tests for new code are optional" drift |
| Mutation testing on critical modules |
mutmut, stryker |
Catches tests that assert nothing |
| Migration safety |
Alembic / Prisma / Atlas up + down dry-run against throwaway Postgres |
AI loves irreversible migrations |
| Contract / schema diff |
oasdiff (OpenAPI), buf breaking (Proto), GraphQL schema diff |
Block breaking changes without explicit label |
| Dead-code / unused-dep |
knip, ts-prune, vulture, depcheck |
Known AI slop signal |
| PR guards |
size limit (≤ ~400 LOC), CODEOWNERS, Conventional Commits title lint, branch protection w/ required checks |
Forces small reviewable PRs |
| Test-skip detector |
grep diff for @skip, .skip, xit(, it.only( → fail |
AI's favourite shortcut |
| Artefact hygiene |
grep diff for TODO / FIXME / "Implementation left as exercise" in non-doc files → fail |
AI placeholder leakage |
3. Scalability & performance
| Gate |
Tool / pattern |
| Build + dependency cache |
actions/cache, setup-node/python cache, Docker BuildKit + registry cache (40–80% wall-clock cut) |
| Matrix builds |
for supported runtimes (node/python versions, OS) |
| Reusable workflows |
workflow_call to kill copy-paste drift across repos |
| Path filters + job fan-out |
dorny/paths-filter so a web-only PR never waits on backend tests |
| Self-hosted runners for heavy jobs |
pair with harden-runner and ephemeral VMs / actions-runner-controller |
| Preview environments on PR |
Dokploy, Fly, Railway, Cloudflare Preview — reviewers test real behaviour |
| Performance budgets |
Lighthouse CI (frontend), k6/locust smoke (backend), size-limit (bundle) |
| Flaky-test quarantine |
retry + detect, open issue, don't silently re-run forever |
4. Release & runtime safety
- Environment protection rules with manual approval on
production.
- Zero-downtime migration policy: expand-migrate-contract enforced via lint rule (no
DROP / ALTER TYPE / non-nullable-without-default in one release).
- Progressive delivery: canary or blue/green; automated rollback on SLO breach.
- Feature flags for AI-heavy paths — kill-switch beats redeploy.
- Post-deploy smoke tests + synthetic monitors as a required status in the deploy workflow.
- Runbook link required in release notes; PR template enforces it.
5. AI-specific gates
- Prompt / model registry versioned in git — treat prompts, system messages, model IDs like code: reviewed, diffed, tagged.
- Eval harness in CI — small deterministic eval suite runs on every PR that touches an AI path (agent, prompt, model, tool). Block regressions, same as tests.
- Cost + latency budgets per endpoint, reported in PR comment. LLM spend is now a CI concern.
- PII / prompt-injection lint on user-input paths; red-team fixtures in the eval set.
- Reproducibility: pin model versions (no
gpt-4o-latest in prod); log model + prompt hash with every response.
- Data contracts for tool-calling: JSON Schema validation on every LLM function-call argument before it hits business logic.
6. Consistency & agent governance 🔴 (for AI-coded work)
LLM agents drift in conventions (layers, names, utils, logging) faster than they drift in security. These controls act on the input side — before CI even runs — so the agent writes code that matches the repo instead of inventing a new shape per feature.
| Gate |
Purpose |
AGENTS.md / CLAUDE.md per repo |
Allowed libs, forbidden patterns, import boundaries, naming, error handling, logging shape. Auto-loaded by Claude / Cursor / Codex. |
| Gold-standard file |
One canonical module exemplifying every rule. Agents told "new code looks like this." Individual rules = unit tests, gold standard = E2E test. |
| Do/don't examples inside rules |
Concrete counter-examples kill ambiguity. Red-team each rule by trying to misinterpret it. |
ADRs under docs/adr/ (MADR format) |
Numbered architectural decisions referenced from prompts so agents don't reinvent patterns per feature. |
| AI review bot as required check |
CodeRabbit / Greptile / Qodo. Catches "duplicate of existing util", "violates module convention". Greptile ~82% catch rate vs Cursor 58% / CodeRabbit 44%. |
| Duplicate-code + complexity budgets |
jscpd (all langs), lizard / radon (cyclomatic), eslint-complexity. Ratchet downward. |
| Cyclic / illegal-import detection |
madge / dependency-cruiser (JS), import-linter (Py), Nx module-boundary rules. |
| Monorepo affected graph |
Nx or Turborepo. Centralizes config, enforces module boundaries, cuts CI 40–70% by running only affected projects. |
pre-commit framework (or lefthook / husky) |
Format, lint, secret-scan, test-skip grep, TODO grep on staged files — same ruleset as CI. Kills ~80% of CI failures before push. |
| Pinned runtimes |
.tool-versions (mise/asdf) + .nvmrc + .python-version. Eliminates "works on Claude's laptop". |
| EditorConfig + single formatter config |
One place for Prettier/Black/gofmt settings. Prevents trivial style churn agents regenerate. |
Conventional Commits + commitlint + semantic-release |
Automatic SemVer, CHANGELOG, GitHub Releases. |
| PR template with AI-disclosure |
% AI-generated, linked session/prompt, runbook link. Makes provenance auditable. |
| Renovate (grouped, auto-merge patch/minor on green) |
Replaces weekly audit issue board with always-fresh PRs. |
| Commit signing required on protected branches |
Branch protection rule. |
| Structured-logging contract + trace-id lint |
Custom ESLint / ruff rule requiring logger.info(event=..., **fields) shape + mandatory trace_id / request_id. |
| Agent sandboxing in CI |
When an agent generates code inside CI, run under step-security/harden-runner + ephemeral VM + egress allowlist. |
| Spec-driven PRs |
Non-trivial PRs commit the plan (e.g. PRD or implementation plan) alongside code; PR template enforces the link. |
Audit wiring: PR-blocking vs scheduled-issue-tracking
Audits run with two distinct wirings. A repo usually needs both. Pick the right one per audit type — the difference is which event triggers the workflow and what "failure" means.
A. PR-blocking gates (fail the build)
- Triggers:
pull_request, push to protected branches.
- Job behaviour: scan only the PR diff (or the new state). If a new Critical/High finding lands, fail the build so the PR can't merge.
- Examples: gitleaks on staged content, semgrep on changed files,
npm audit against the new lockfile, container scan on the PR-built image, schema-diff on the new OpenAPI doc.
- Why: stops new findings from landing. Targeted, actionable, owned by the PR author.
B. Scheduled audits (manage GitHub issues, never fail the build)
- Triggers:
schedule: (e.g. weekly cron) + workflow_dispatch.
- Job behaviour:
- Scan the whole codebase / lockfile / image set.
- If findings exist → open or update a single labelled GitHub issue per audit category. Idempotent: same title + label → update; new run → bump the body with the latest report.
- If findings are gone → close the previously-opened issue.
- The job itself always exits 0. The issue is the signal.
- Why: scheduled-job failures create noise without an actionable PR. Issues are actionable and form a live dashboard. The
/issues view with the audit label becomes the team's standing audit board.
Where to apply pattern B (not pattern A)
Use scheduled-issue-tracking for findings that:
- Already exist in the codebase (you don't want to gate the next unrelated PR on them).
- Need triage/decision rather than immediate fix (e.g. "outdated dep — upgrade across 3 services this sprint").
- Cover concerns that are about the repo as a whole, not the diff.
Specific audits to wire as scheduled issue-trackers:
- Dependency vulnerabilities (
pip-audit, npm audit, osv-scanner) — across the full lockfile, not just diff.
- Outdated dependencies (
npm outdated, pip list --outdated).
- License drift / compliance.
- Container base-image vulns (rebuild + scan weekly even if no app changes).
- Dead-code / unused-dep accumulation reports.
- SBOM diff against the previous release.
- Cyclomatic-complexity / duplicate-code budgets when ratcheting (report drift, don't break builds mid-sprint).
Issue lifecycle (idempotent)
issue title: "Audit: <category> — <project>"
issue labels: ["audit", "automated", "<category>"]
on each scheduled run:
if findings:
if existing open issue with this title+label: update body (replace)
else: create new issue
else:
if existing open issue with this title+label: close with comment "✅ no findings on <date>"
Use a helper such as peter-evans/create-issue-from-file or a small gh issue wrapper. The board ends up with one row per audit category per project: open issues = current debt, closed issues = recently-resolved debt with provenance.
One-line summary
| Trigger |
Finding |
No finding |
| PR / push (gate) |
Fail the build |
Continue |
| Scheduled (dashboard) |
Open/update issue, exit 0 |
Close issue, exit 0 |
Canonical jobs
When implementing the gates, use a fixed set of job: names so workflows are interchangeable across repos and reusable workflow_call templates work without per-repo edits. Branch-protection "required status checks" reference these names — renaming a job silently breaks merge protection.
Always required (PR-gating, every repo)
| Job |
Gates implemented |
Notes |
pr-guards |
size limit, Conventional Commits title, no .skip / it.only / xit(, no TODO / FIXME in non-doc files, AI-disclosure |
Cheap, runs first, fails fast |
secret-scan |
gitleaks/trufflehog on the diff |
Independent runner; never needs: anything |
lint |
format + lint blocking |
One job; format and lint inside |
typecheck |
strict typing (tsc --noEmit --strict / mypy --strict / go vet) |
Separate from lint — fails fast |
test-unit |
unit tests |
Matrix on runtime versions if applicable |
test-integration |
integration tests |
Brings up DB / queue via services: |
coverage-gate |
line floor + diff-cover ≥ floor |
needs: [test-unit, test-integration] |
sast |
semgrep + lang-specific (bandit / eslint-plugin-security / gosec) |
Independent |
sca |
npm audit / pip-audit / osv-scanner on the PR's lockfile |
Diff scope, not repo-wide |
dep-review |
GitHub dependency-review-action |
License + CVE diff on new deps |
dead-code |
knip / ts-prune / vulture / depcheck |
|
build |
production build |
needs: [lint, typecheck] |
Required when applicable
| Job |
Triggered when |
Gates |
migrate-check |
repo has prisma/, migrations/, db/migrate |
up + down dry-run on throwaway Postgres |
schema-diff |
repo has openapi.yaml / *.proto / GraphQL SDL |
oasdiff / buf breaking / GraphQL schema diff |
docker-build-scan |
repo has Dockerfile |
Build image, then trivy image (also scan base) |
iac-scan |
repo has Dockerfile / compose.yml / *.tf / k8s/ |
checkov + tfsec |
bundle-size |
frontend repo |
size-limit against budget |
lighthouse |
frontend repo |
Lighthouse CI score floor |
load-smoke |
backend with perf budget |
k6 / locust against ephemeral env |
mutation-test |
repo opted into mutation testing |
stryker / mutmut on critical modules |
AI-specific (when repo has AI paths)
| Job |
Gates |
ai-eval |
deterministic eval suite; blocks regressions |
ai-cost-budget |
cost + latency per AI endpoint reported in PR comment |
prompt-injection-lint |
PII detection + red-team fixtures on user-input paths |
tool-call-schema |
JSON Schema validation on every LLM tool-call argument |
Release-only (on tag / release event)
| Job |
Gates |
sbom |
syft → SPDX/CycloneDX, attached to the release |
provenance |
actions/attest-build-provenance (SLSA ≥ 2) |
publish |
npm publish / Docker push / etc. — needs: everything green |
Deploy (separate workflow, gated by required status checks)
| Job |
Trigger |
Notes |
deploy-preview |
pull_request |
Per-PR ephemeral environment |
deploy-staging |
push to main |
Auto-deploy on green |
deploy-production |
release tag |
Environment protection rule + manual approval |
smoke-postdeploy |
needs: deploy-* |
Required status; only this marks the deploy "done" |
Scheduled audits (separate workflow, exit 0 — see "Audit wiring" above)
| Job |
Cadence |
What it audits |
audit-deps-weekly |
weekly |
full-lockfile vuln scan; manages a single audit issue |
audit-outdated-weekly |
weekly |
npm outdated / pip list --outdated |
audit-licenses-weekly |
weekly |
license-policy drift |
audit-image-weekly |
weekly |
rebuild + rescan latest base image |
audit-deadcode-weekly |
weekly |
accumulation report |
audit-sbom-diff |
per release |
diff vs previous release SBOM |
audit-complexity-weekly |
weekly |
duplicate-code + cyclomatic complexity drift |
Branch-protection required status checks
The minimum that must be green before merging to a protected branch:
pr-guards
secret-scan
lint
typecheck
test-unit
test-integration
coverage-gate
sast
sca
dead-code
build
Plus, when applicable to the repo:
migrate-check
schema-diff
docker-build-scan
ai-eval
smoke-postdeploy
Naming rules
- Lowercase, hyphenated. Matches GitHub's job-slug convention.
- One concern per job. Don't merge
lint and typecheck into quality — branch protection becomes coarse and a typecheck failure reruns lint.
needs: only what's actually consumed. Coverage genuinely needs tests; lint doesn't need typecheck. Excessive needs: serialises the pipeline for no benefit.
- Never rename a job without auditing branch protection. Required-status-check names are referenced by the GitHub API; a rename either silently skips the check or blocks every merge until the rule is updated.
- Reusable workflow ships these names verbatim. A repo consuming
_ci.yml should not need to remap.
A reference reusable implementation lives in this template at .github/workflows/_ci.yml — downstream repos call it with uses: mechemsi/claude-template/.github/workflows/_ci.yml@<sha>. Language-neutral worked examples for these jobs are in ./examples/ — a parameterised _ci.yml plus standalone secret-scan, scheduled-audit, and PR-template patterns. Same canonical job names, swappable per ecosystem via workflow_call inputs.
Auditing an existing pipeline
Use this as a checklist when reviewing a project's CI. For each row, mark the project's status: ✅ in place, add to introduce, review if uncertain, — if not applicable.
| # |
Gap |
Status |
| 1 |
Secret scanning (gitleaks / trufflehog + GitHub push protection) |
|
| 2 |
SAST per language (semgrep + bandit / eslint-security / gosec) |
|
| 3 |
SCA on every PR (pip-audit, npm audit, osv-scanner) — not just weekly cron |
|
| 4 |
Container image scan on built images (trivy / grype) — also scan base |
|
| 5 |
IaC scan (checkov / tfsec) on Dockerfile, compose, k8s, terraform |
|
| 6 |
Dependency Review action on PRs (license + CVE diff) |
|
| 7 |
SBOM generated on release (syft → SPDX/CycloneDX) |
|
| 8 |
Build provenance (actions/attest-build-provenance, SLSA ≥ 2) |
|
| 9 |
Pinned action SHAs (no @v4) + step-security/harden-runner |
|
| 10 |
Format + lint as blocking check |
|
| 11 |
Strict typing (mypy --strict, tsc --noEmit --strict, go vet) |
|
| 12 |
Unit + integration tests both required |
|
| 13 |
Coverage gate + diff-cover (per-PR diff coverage ≥ floor) |
|
| 14 |
Mutation testing on critical modules (mutmut / stryker) |
|
| 15 |
Migration safety (up + down dry-run against throwaway DB) |
|
| 16 |
Contract / schema diff (oasdiff, buf breaking, GraphQL schema diff) |
|
| 17 |
Dead-code & unused-dep scan (knip, ts-prune, vulture, depcheck) |
|
| 18 |
PR size guard + CODEOWNERS + Conventional Commits title lint |
|
| 19 |
Test-skip detector (grep diff for @skip, .skip, xit(, it.only() |
|
| 20 |
Artefact-hygiene grep (TODO/FIXME/"left as exercise" in non-doc files) |
|
| 21 |
Build + dependency cache (40–80% wall-clock savings) |
|
| 22 |
Matrix builds for supported runtime versions / OS |
|
| 23 |
Reusable workflow_call shared across repos |
|
| 24 |
Path filters + per-surface job fan-out |
|
| 25 |
Self-hosted runners for heavy jobs (with harden-runner) |
|
| 26 |
Preview environments on PR |
|
| 27 |
Performance budgets (Lighthouse CI / k6 / size-limit) |
|
| 28 |
Flaky-test quarantine (retry + open issue, not silent re-run) |
|
| 29 |
Environment protection rules + manual approval on production |
|
| 30 |
Expand-migrate-contract enforced via lint |
|
| 31 |
Progressive delivery (canary / blue-green) + automated rollback |
|
| 32 |
Feature flags for AI-heavy paths |
|
| 33 |
Post-deploy smoke tests + synthetic monitors as required status |
|
| 34 |
Runbook link required in release notes / PR template |
|
| 35 |
Prompt / model registry versioned in git |
|
| 36 |
AI eval harness in CI (deterministic, blocks regressions) |
|
| 37 |
Cost + latency budgets per AI endpoint, reported in PR |
|
| 38 |
PII / prompt-injection lint + red-team fixtures in eval set |
|
| 39 |
Pinned model versions (no *-latest in prod); model + prompt hash logged per response |
|
| 40 |
JSON Schema validation on every LLM tool-call argument |
|
| 41 |
AGENTS.md / CLAUDE.md per repo (allowed libs, naming, layers, logging) |
|
| 42 |
Gold-standard file (one canonical module exemplifying every rule) |
|
| 43 |
ADR directory docs/adr/ (MADR format) |
|
| 44 |
AI review bot as required check (Greptile / CodeRabbit / Qodo) |
|
| 45 |
Duplicate-code + cyclomatic complexity budgets (jscpd, lizard, radon) |
|
| 46 |
Cyclic / illegal-import detection (madge, dependency-cruiser, import-linter) |
|
| 47 |
Monorepo affected graph (Nx / Turborepo) — only build/test what changed |
|
| 48 |
pre-commit framework (or lefthook / husky) — same ruleset as CI |
|
| 49 |
Pinned runtimes (.tool-versions / .nvmrc / .python-version) |
|
| 50 |
EditorConfig + single-source formatter config |
|
| 51 |
Conventional Commits + commitlint + semantic-release |
|
| 52 |
Renovate (grouped, auto-merge patch/minor on green) |
|
| 53 |
PR template with AI-disclosure (% AI-generated, prompt link, runbook) |
|
| 54 |
Commit signing required on protected branches |
|
| 55 |
Structured-logging lint rule (event + fields + trace_id) |
|
| 56 |
Agent sandboxing in CI when agents run inside CI (harden-runner + ephemeral) |
|
| 57 |
Spec-driven PRs (plan/PRD committed alongside code, enforced by template) |
|
Output the table marking each row's status, then prioritise the gaps using the adoption order below — don't try to fix all of them in one PR.
Minimum viable baseline — adoption order
Don't try to add all 60+ gates at once. This order maximises return per hour spent.
- Gitleaks + push protection — one hour, stops the worst leaks.
- Semgrep + language-specific SAST — one PR, pre-written rulesets.
- Per-PR SCA (
pip-audit, npm audit, osv-scanner) — upgrade weekly audit to per-PR.
- Coverage gate + diff-cover + grep-in-diff for test-skip and TODO.
- Trivy on Docker build + SBOM upload.
- Reusable
workflow_call pipeline shared across repos.
- Preview environments + Lighthouse / k6 budgets.
- AI eval harness + prompt/model registry (only if the repo has AI paths).
- Drop
AGENTS.md / CLAUDE.md + ADR seed + gold-standard file + PR template into every new project (or inherit from a template repo).
pre-commit + pinned runtimes — one PR per repo. Immediate CI-failure drop (~80%).
- AI review bot as required check — pick one (Greptile for context, CodeRabbit for UX).
- Nx / Turborepo in monorepos — affected graph + module-boundary lint.
- Conventional Commits + semantic-release + Renovate — via reusable
workflow_call.
- Duplicate-code + complexity budgets — tune to current code, ratchet down.
- Structured-logging lint + AI-disclosure PR template.
- Agent sandboxing (
harden-runner + ephemeral runner) for repos where agents run inside CI.
Everything above is boring, well-trodden, and low-cost. Skipping any of it on AI-coded apps is the same bet as shipping without tests — just slower to notice.
Common mistakes
- Treating SAST/SCA as warnings. Anything not gated is decoration. Security findings must fail the build (with a documented exception path).
- Coverage % without diff-cover. New code can land at 0% while overall % stays comfortable.
- Re-running flaky tests forever. Hides real bugs and trains the team to ignore red.
@v4 action references — supply-chain risk. Pin SHAs.
- One mega
ci.yml. Path-filter and split per surface so a docs-only PR doesn't run the backend test suite.
- Adopting all 60 gates day one — pipeline becomes a 30-minute slog, team starts disabling checks. Use the adoption order.
- No
AGENTS.md / CLAUDE.md — agents drift the repo's conventions every PR; your reviewers spend their time on style, not logic.
- Self-hosted runners without
harden-runner — runner becomes a foothold the moment a malicious dep lands.
Related
security-review — companion skill for code-level security audit (this skill is pipeline-level).
dependency-management rule — what gets installed; this skill is what gets checked.
error-handling, logging-observability — paired with §6 lint rules (structured logs, trace IDs).
12-factor-app — release/runtime context for §4.
testing-architecture — coverage gate and mutation testing depth come from here.
db-migration-safety — pair with a CI gate that flags DROP/RENAME/NOT NULL/large-index DDL in migration diffs.
feature-flags-and-rollout — CI matrix expansion for active rollouts; required-checks-green gate before flag flips.
code-review-discipline — CI gates are the floor; review is the ceiling. Pipeline says "ready for review", not "ready to merge".
performance-optimization — perf regression gates after a hot-path fix live in the pipeline.
Source: mechemsi/claude-template — distributed by TomeVault.
1---2name: ci-cd-pipeline-123description: Use when designing or reviewing CI/CD pipelines or GitHub Actions workflows; when auditing CI for security or quality gaps; when picking tools (gitleaks, semgrep, trivy, osv-scanner, etc.); when deciding what should fail-the-build vs warn; when shipping AI-generated code that needs harder gates than human code; when a PR slipped through with secrets, hallucinated APIs, broken migrations, or uncovered branches.4---56# CI/CD Pipelines for AI-Coded Apps78LLM-generated code ships with predictable failure modes: plausible-but-wrong logic, silently-disabled tests, hallucinated APIs, secrets pasted inline, dependencies pulled from typosquats, and "works on my laptop" defaults. **The pipeline is the only honest reviewer — if it doesn't block, it ships.**910## When to apply1112- New repo getting its first CI workflow13- Existing pipeline that "works" but has obvious gaps (no SAST, no SBOM, no coverage gate)14- A bug or incident traced back to "should have been caught in CI"15- Migrating to a reusable / shared workflow across repos16- Standardising AI-coded projects on a baseline before scaling team1718## Six categories of gates1920Every pipeline needs coverage of these. Items marked 🔴 are non-negotiable — they fail the build.2122---2324### 1. Security gates 🔴2526| Gate | Tool | What it catches |27|------|------|-----------------|28| Secret scanning on every push | `gitleaks` or `trufflehog` + GitHub push protection | API keys, tokens, private keys |29| SAST | `semgrep` (multi-lang), `bandit` (Py), `eslint-plugin-security`, `gosec` (Go) | Injection, unsafe APIs, hardcoded crypto |30| SCA on every PR (not just weekly) | `pip-audit`, `npm audit --audit-level=high`, `osv-scanner` | New Critical/High CVEs in deps |31| Container image scan | `trivy image` or `grype` (also scan base image) | Vulns in OS packages and app layers |32| IaC scan | `checkov`, `tfsec` | Misconfigured Dockerfiles, compose, k8s, terraform |33| Dependency Review action on PRs | GitHub `dependency-review-action` | Diff of newly-added deps + their licences/CVEs |34| SBOM on release | `syft` → SPDX/CycloneDX | Supply-chain audits |35| Build provenance | `actions/attest-build-provenance` | SLSA level 2+ |36| Pinned action SHAs (not `@v4`) | `step-security/harden-runner` | Action supply-chain attacks, blocked egress |3738### 2. Correctness & maintainability 🔴3940| Gate | Tool | Why |41|------|------|-----|42| Format + lint blocking | `prettier`, `ruff`, `gofmt` | AI cheerfully ignores style; the pipeline must not |43| Strict typing | `mypy --strict`, `tsc --noEmit --strict`, `go vet` | Most hallucinated APIs die here |44| Unit + integration tests both required | native runners | AI-generated unit tests often mirror buggy code; integration hits real contracts |45| Coverage floor + diff-cover | `coverage.py`, `c8`, `diff-cover` | E.g. 80% line floor + diff coverage ≥ floor. Kills "tests for new code are optional" drift |46| Mutation testing on critical modules | `mutmut`, `stryker` | Catches tests that assert nothing |47| Migration safety | Alembic / Prisma / Atlas up + down dry-run against throwaway Postgres | AI loves irreversible migrations |48| Contract / schema diff | `oasdiff` (OpenAPI), `buf breaking` (Proto), GraphQL schema diff | Block breaking changes without explicit label |49| Dead-code / unused-dep | `knip`, `ts-prune`, `vulture`, `depcheck` | Known AI slop signal |50| PR guards | size limit (≤ ~400 LOC), `CODEOWNERS`, Conventional Commits title lint, branch protection w/ required checks | Forces small reviewable PRs |51| Test-skip detector | grep diff for `@skip`, `.skip`, `xit(`, `it.only(` → fail | AI's favourite shortcut |52| Artefact hygiene | grep diff for `TODO` / `FIXME` / "Implementation left as exercise" in non-doc files → fail | AI placeholder leakage |5354### 3. Scalability & performance5556| Gate | Tool / pattern |57|------|----------------|58| Build + dependency cache | `actions/cache`, `setup-node/python` cache, Docker BuildKit + registry cache (40–80% wall-clock cut) |59| Matrix builds | for supported runtimes (node/python versions, OS) |60| Reusable workflows | `workflow_call` to kill copy-paste drift across repos |61| Path filters + job fan-out | `dorny/paths-filter` so a web-only PR never waits on backend tests |62| Self-hosted runners for heavy jobs | pair with `harden-runner` and ephemeral VMs / `actions-runner-controller` |63| Preview environments on PR | Dokploy, Fly, Railway, Cloudflare Preview — reviewers test real behaviour |64| Performance budgets | Lighthouse CI (frontend), `k6`/`locust` smoke (backend), `size-limit` (bundle) |65| Flaky-test quarantine | retry + detect, open issue, **don't** silently re-run forever |6667### 4. Release & runtime safety6869- **Environment protection rules** with manual approval on `production`.70- **Zero-downtime migration policy**: expand-migrate-contract enforced via lint rule (no `DROP` / `ALTER TYPE` / non-nullable-without-default in one release).71- **Progressive delivery**: canary or blue/green; automated rollback on SLO breach.72- **Feature flags** for AI-heavy paths — kill-switch beats redeploy.73- **Post-deploy smoke tests** + **synthetic monitors** as a required status in the deploy workflow.74- **Runbook link required** in release notes; PR template enforces it.7576### 5. AI-specific gates7778- **Prompt / model registry versioned in git** — treat prompts, system messages, model IDs like code: reviewed, diffed, tagged.79- **Eval harness in CI** — small deterministic eval suite runs on every PR that touches an AI path (agent, prompt, model, tool). Block regressions, same as tests.80- **Cost + latency budgets** per endpoint, reported in PR comment. LLM spend is now a CI concern.81- **PII / prompt-injection lint** on user-input paths; red-team fixtures in the eval set.82- **Reproducibility**: pin model versions (no `gpt-4o-latest` in prod); log model + prompt hash with every response.83- **Data contracts for tool-calling**: JSON Schema validation on every LLM function-call argument before it hits business logic.8485### 6. Consistency & agent governance 🔴 (for AI-coded work)8687LLM agents drift in conventions (layers, names, utils, logging) faster than they drift in security. These controls act on the **input** side — before CI even runs — so the agent writes code that matches the repo instead of inventing a new shape per feature.8889| Gate | Purpose |90|------|---------|91| `AGENTS.md` / `CLAUDE.md` per repo | Allowed libs, forbidden patterns, import boundaries, naming, error handling, logging shape. Auto-loaded by Claude / Cursor / Codex. |92| Gold-standard file | One canonical module exemplifying *every* rule. Agents told "new code looks like this." Individual rules = unit tests, gold standard = E2E test. |93| Do/don't examples inside rules | Concrete counter-examples kill ambiguity. Red-team each rule by trying to misinterpret it. |94| ADRs under `docs/adr/` (MADR format) | Numbered architectural decisions referenced from prompts so agents don't reinvent patterns per feature. |95| AI review bot as required check | CodeRabbit / Greptile / Qodo. Catches "duplicate of existing util", "violates module convention". Greptile ~82% catch rate vs Cursor 58% / CodeRabbit 44%. |96| Duplicate-code + complexity budgets | `jscpd` (all langs), `lizard` / `radon` (cyclomatic), `eslint-complexity`. Ratchet downward. |97| Cyclic / illegal-import detection | `madge` / `dependency-cruiser` (JS), `import-linter` (Py), Nx module-boundary rules. |98| Monorepo affected graph | Nx or Turborepo. Centralizes config, enforces module boundaries, cuts CI 40–70% by running only affected projects. |99| `pre-commit` framework (or `lefthook` / `husky`) | Format, lint, secret-scan, test-skip grep, TODO grep on staged files — same ruleset as CI. Kills ~80% of CI failures before push. |100| Pinned runtimes | `.tool-versions` (mise/asdf) + `.nvmrc` + `.python-version`. Eliminates "works on Claude's laptop". |101| EditorConfig + single formatter config | One place for Prettier/Black/gofmt settings. Prevents trivial style churn agents regenerate. |102| Conventional Commits + `commitlint` + semantic-release | Automatic SemVer, CHANGELOG, GitHub Releases. |103| PR template with AI-disclosure | `% AI-generated`, linked session/prompt, runbook link. Makes provenance auditable. |104| Renovate (grouped, auto-merge patch/minor on green) | Replaces weekly audit issue board with always-fresh PRs. |105| Commit signing required on protected branches | Branch protection rule. |106| Structured-logging contract + trace-id lint | Custom ESLint / ruff rule requiring `logger.info(event=..., **fields)` shape + mandatory `trace_id` / `request_id`. |107| Agent sandboxing in CI | When an agent generates code *inside* CI, run under `step-security/harden-runner` + ephemeral VM + egress allowlist. |108| Spec-driven PRs | Non-trivial PRs commit the plan (e.g. PRD or implementation plan) alongside code; PR template enforces the link. |109110---111112## Audit wiring: PR-blocking vs scheduled-issue-tracking113114Audits run with **two distinct wirings**. A repo usually needs both. Pick the right one per audit type — the difference is which event triggers the workflow and what "failure" means.115116### A. PR-blocking gates (fail the build)117118- **Triggers:** `pull_request`, `push` to protected branches.119- **Job behaviour:** scan only the PR diff (or the new state). If a new Critical/High finding lands, **fail the build** so the PR can't merge.120- **Examples:** gitleaks on staged content, semgrep on changed files, `npm audit` against the new lockfile, container scan on the PR-built image, schema-diff on the new OpenAPI doc.121- **Why:** stops *new* findings from landing. Targeted, actionable, owned by the PR author.122123### B. Scheduled audits (manage GitHub issues, **never fail the build**)124125- **Triggers:** `schedule:` (e.g. weekly cron) + `workflow_dispatch`.126- **Job behaviour:**127 - Scan the whole codebase / lockfile / image set.128 - If findings exist → **open or update** a single labelled GitHub issue per audit category. Idempotent: same title + label → update; new run → bump the body with the latest report.129 - If findings are gone → **close** the previously-opened issue.130 - The job itself **always exits 0**. The issue is the signal.131- **Why:** scheduled-job failures create noise without an actionable PR. Issues are actionable and form a live dashboard. The `/issues` view with the `audit` label becomes the team's standing audit board.132133### Where to apply pattern B (not pattern A)134135Use scheduled-issue-tracking for findings that:136137- Already exist in the codebase (you don't want to gate the next unrelated PR on them).138- Need triage/decision rather than immediate fix (e.g. "outdated dep — upgrade across 3 services this sprint").139- Cover concerns that are about the repo as a whole, not the diff.140141Specific audits to wire as scheduled issue-trackers:142143- Dependency vulnerabilities (`pip-audit`, `npm audit`, `osv-scanner`) — across the full lockfile, not just diff.144- Outdated dependencies (`npm outdated`, `pip list --outdated`).145- License drift / compliance.146- Container base-image vulns (rebuild + scan weekly even if no app changes).147- Dead-code / unused-dep accumulation reports.148- SBOM diff against the previous release.149- Cyclomatic-complexity / duplicate-code budgets when ratcheting (report drift, don't break builds mid-sprint).150151### Issue lifecycle (idempotent)152153```154issue title: "Audit: <category> — <project>"155issue labels: ["audit", "automated", "<category>"]156157on each scheduled run:158 if findings:159 if existing open issue with this title+label: update body (replace)160 else: create new issue161 else:162 if existing open issue with this title+label: close with comment "✅ no findings on <date>"163```164165Use a helper such as `peter-evans/create-issue-from-file` or a small `gh issue` wrapper. The board ends up with one row per audit category per project: open issues = current debt, closed issues = recently-resolved debt with provenance.166167### One-line summary168169| Trigger | Finding | No finding |170|---------|---------|------------|171| PR / push (gate) | **Fail the build** | Continue |172| Scheduled (dashboard) | Open/update issue, exit 0 | Close issue, exit 0 |173174## Canonical jobs175176When implementing the gates, use a fixed set of `job:` names so workflows are interchangeable across repos and reusable `workflow_call` templates work without per-repo edits. Branch-protection "required status checks" reference these names — renaming a job silently breaks merge protection.177178### Always required (PR-gating, every repo)179180| Job | Gates implemented | Notes |181|-----|-------------------|-------|182| `pr-guards` | size limit, Conventional Commits title, no `.skip` / `it.only` / `xit(`, no `TODO` / `FIXME` in non-doc files, AI-disclosure | Cheap, runs first, fails fast |183| `secret-scan` | gitleaks/trufflehog on the diff | Independent runner; never `needs:` anything |184| `lint` | format + lint blocking | One job; format and lint inside |185| `typecheck` | strict typing (`tsc --noEmit --strict` / `mypy --strict` / `go vet`) | Separate from lint — fails fast |186| `test-unit` | unit tests | Matrix on runtime versions if applicable |187| `test-integration` | integration tests | Brings up DB / queue via `services:` |188| `coverage-gate` | line floor + diff-cover ≥ floor | `needs: [test-unit, test-integration]` |189| `sast` | semgrep + lang-specific (bandit / eslint-plugin-security / gosec) | Independent |190| `sca` | `npm audit` / `pip-audit` / `osv-scanner` on the PR's lockfile | Diff scope, not repo-wide |191| `dep-review` | GitHub `dependency-review-action` | License + CVE diff on new deps |192| `dead-code` | `knip` / `ts-prune` / `vulture` / `depcheck` | |193| `build` | production build | `needs: [lint, typecheck]` |194195### Required when applicable196197| Job | Triggered when | Gates |198|-----|---------------|-------|199| `migrate-check` | repo has `prisma/`, `migrations/`, `db/migrate` | up + down dry-run on throwaway Postgres |200| `schema-diff` | repo has `openapi.yaml` / `*.proto` / GraphQL SDL | `oasdiff` / `buf breaking` / GraphQL schema diff |201| `docker-build-scan` | repo has `Dockerfile` | Build image, then `trivy image` (also scan base) |202| `iac-scan` | repo has `Dockerfile` / `compose.yml` / `*.tf` / `k8s/` | `checkov` + `tfsec` |203| `bundle-size` | frontend repo | `size-limit` against budget |204| `lighthouse` | frontend repo | Lighthouse CI score floor |205| `load-smoke` | backend with perf budget | `k6` / `locust` against ephemeral env |206| `mutation-test` | repo opted into mutation testing | `stryker` / `mutmut` on critical modules |207208### AI-specific (when repo has AI paths)209210| Job | Gates |211|-----|-------|212| `ai-eval` | deterministic eval suite; blocks regressions |213| `ai-cost-budget` | cost + latency per AI endpoint reported in PR comment |214| `prompt-injection-lint` | PII detection + red-team fixtures on user-input paths |215| `tool-call-schema` | JSON Schema validation on every LLM tool-call argument |216217### Release-only (on tag / release event)218219| Job | Gates |220|-----|-------|221| `sbom` | `syft` → SPDX/CycloneDX, attached to the release |222| `provenance` | `actions/attest-build-provenance` (SLSA ≥ 2) |223| `publish` | `npm publish` / Docker push / etc. — `needs:` everything green |224225### Deploy (separate workflow, gated by required status checks)226227| Job | Trigger | Notes |228|-----|---------|-------|229| `deploy-preview` | `pull_request` | Per-PR ephemeral environment |230| `deploy-staging` | push to `main` | Auto-deploy on green |231| `deploy-production` | release tag | Environment protection rule + manual approval |232| `smoke-postdeploy` | `needs: deploy-*` | Required status; only this marks the deploy "done" |233234### Scheduled audits (separate workflow, exit 0 — see "Audit wiring" above)235236| Job | Cadence | What it audits |237|-----|---------|----------------|238| `audit-deps-weekly` | weekly | full-lockfile vuln scan; manages a single audit issue |239| `audit-outdated-weekly` | weekly | `npm outdated` / `pip list --outdated` |240| `audit-licenses-weekly` | weekly | license-policy drift |241| `audit-image-weekly` | weekly | rebuild + rescan latest base image |242| `audit-deadcode-weekly` | weekly | accumulation report |243| `audit-sbom-diff` | per release | diff vs previous release SBOM |244| `audit-complexity-weekly` | weekly | duplicate-code + cyclomatic complexity drift |245246### Branch-protection required status checks247248The minimum that must be green before merging to a protected branch:249250```251pr-guards252secret-scan253lint254typecheck255test-unit256test-integration257coverage-gate258sast259sca260dead-code261build262```263264Plus, when applicable to the repo:265266```267migrate-check268schema-diff269docker-build-scan270ai-eval271smoke-postdeploy272```273274### Naming rules275276- **Lowercase, hyphenated.** Matches GitHub's job-slug convention.277- **One concern per job.** Don't merge `lint` and `typecheck` into `quality` — branch protection becomes coarse and a typecheck failure reruns lint.278- **`needs:` only what's actually consumed.** Coverage genuinely needs tests; lint doesn't need typecheck. Excessive `needs:` serialises the pipeline for no benefit.279- **Never rename a job without auditing branch protection.** Required-status-check names are referenced by the GitHub API; a rename either silently skips the check or blocks every merge until the rule is updated.280- **Reusable workflow ships these names verbatim.** A repo consuming `_ci.yml` should not need to remap.281282A reference reusable implementation lives in this template at `.github/workflows/_ci.yml` — downstream repos call it with `uses: mechemsi/claude-template/.github/workflows/_ci.yml@<sha>`. Language-neutral worked examples for these jobs are in [`./examples/`](./examples/README.md) — a parameterised `_ci.yml` plus standalone secret-scan, scheduled-audit, and PR-template patterns. Same canonical job names, swappable per ecosystem via `workflow_call` inputs.283284## Auditing an existing pipeline285286Use this as a checklist when reviewing a project's CI. For each row, mark the project's status: ✅ in place, **add** to introduce, **review** if uncertain, **—** if not applicable.287288| # | Gap | Status |289|---|-----|--------|290| 1 | Secret scanning (gitleaks / trufflehog + GitHub push protection) | |291| 2 | SAST per language (semgrep + bandit / eslint-security / gosec) | |292| 3 | SCA on every PR (`pip-audit`, `npm audit`, `osv-scanner`) — not just weekly cron | |293| 4 | Container image scan on built images (trivy / grype) — also scan base | |294| 5 | IaC scan (checkov / tfsec) on Dockerfile, compose, k8s, terraform | |295| 6 | Dependency Review action on PRs (license + CVE diff) | |296| 7 | SBOM generated on release (syft → SPDX/CycloneDX) | |297| 8 | Build provenance (`actions/attest-build-provenance`, SLSA ≥ 2) | |298| 9 | Pinned action SHAs (no `@v4`) + `step-security/harden-runner` | |299| 10 | Format + lint as **blocking** check | |300| 11 | Strict typing (`mypy --strict`, `tsc --noEmit --strict`, `go vet`) | |301| 12 | Unit + integration tests both required | |302| 13 | Coverage gate + diff-cover (per-PR diff coverage ≥ floor) | |303| 14 | Mutation testing on critical modules (`mutmut` / `stryker`) | |304| 15 | Migration safety (up + down dry-run against throwaway DB) | |305| 16 | Contract / schema diff (`oasdiff`, `buf breaking`, GraphQL schema diff) | |306| 17 | Dead-code & unused-dep scan (`knip`, `ts-prune`, `vulture`, `depcheck`) | |307| 18 | PR size guard + `CODEOWNERS` + Conventional Commits title lint | |308| 19 | Test-skip detector (grep diff for `@skip`, `.skip`, `xit(`, `it.only(`) | |309| 20 | Artefact-hygiene grep (TODO/FIXME/"left as exercise" in non-doc files) | |310| 21 | Build + dependency cache (40–80% wall-clock savings) | |311| 22 | Matrix builds for supported runtime versions / OS | |312| 23 | Reusable `workflow_call` shared across repos | |313| 24 | Path filters + per-surface job fan-out | |314| 25 | Self-hosted runners for heavy jobs (with harden-runner) | |315| 26 | Preview environments on PR | |316| 27 | Performance budgets (Lighthouse CI / k6 / size-limit) | |317| 28 | Flaky-test quarantine (retry + open issue, not silent re-run) | |318| 29 | Environment protection rules + manual approval on production | |319| 30 | Expand-migrate-contract enforced via lint | |320| 31 | Progressive delivery (canary / blue-green) + automated rollback | |321| 32 | Feature flags for AI-heavy paths | |322| 33 | Post-deploy smoke tests + synthetic monitors as required status | |323| 34 | Runbook link required in release notes / PR template | |324| 35 | Prompt / model registry versioned in git | |325| 36 | AI eval harness in CI (deterministic, blocks regressions) | |326| 37 | Cost + latency budgets per AI endpoint, reported in PR | |327| 38 | PII / prompt-injection lint + red-team fixtures in eval set | |328| 39 | Pinned model versions (no `*-latest` in prod); model + prompt hash logged per response | |329| 40 | JSON Schema validation on every LLM tool-call argument | |330| 41 | `AGENTS.md` / `CLAUDE.md` per repo (allowed libs, naming, layers, logging) | |331| 42 | Gold-standard file (one canonical module exemplifying every rule) | |332| 43 | ADR directory `docs/adr/` (MADR format) | |333| 44 | AI review bot as required check (Greptile / CodeRabbit / Qodo) | |334| 45 | Duplicate-code + cyclomatic complexity budgets (jscpd, lizard, radon) | |335| 46 | Cyclic / illegal-import detection (madge, dependency-cruiser, import-linter) | |336| 47 | Monorepo affected graph (Nx / Turborepo) — only build/test what changed | |337| 48 | `pre-commit` framework (or lefthook / husky) — same ruleset as CI | |338| 49 | Pinned runtimes (`.tool-versions` / `.nvmrc` / `.python-version`) | |339| 50 | EditorConfig + single-source formatter config | |340| 51 | Conventional Commits + commitlint + semantic-release | |341| 52 | Renovate (grouped, auto-merge patch/minor on green) | |342| 53 | PR template with AI-disclosure (% AI-generated, prompt link, runbook) | |343| 54 | Commit signing required on protected branches | |344| 55 | Structured-logging lint rule (event + fields + trace_id) | |345| 56 | Agent sandboxing in CI when agents run inside CI (harden-runner + ephemeral) | |346| 57 | Spec-driven PRs (plan/PRD committed alongside code, enforced by template) | |347348Output the table marking each row's status, then prioritise the gaps using the adoption order below — don't try to fix all of them in one PR.349350## Minimum viable baseline — adoption order351352Don't try to add all 60+ gates at once. This order maximises return per hour spent.3533541. **Gitleaks + push protection** — one hour, stops the worst leaks.3552. **Semgrep + language-specific SAST** — one PR, pre-written rulesets.3563. **Per-PR SCA** (`pip-audit`, `npm audit`, `osv-scanner`) — upgrade weekly audit to per-PR.3574. **Coverage gate + diff-cover** + grep-in-diff for test-skip and TODO.3585. **Trivy on Docker build + SBOM upload.**3596. **Reusable `workflow_call` pipeline** shared across repos.3607. **Preview environments + Lighthouse / k6 budgets.**3618. **AI eval harness + prompt/model registry** (only if the repo has AI paths).3629. **Drop `AGENTS.md` / `CLAUDE.md` + ADR seed + gold-standard file + PR template** into every new project (or inherit from a template repo).36310. **`pre-commit` + pinned runtimes** — one PR per repo. Immediate CI-failure drop (~80%).36411. **AI review bot** as required check — pick one (Greptile for context, CodeRabbit for UX).36512. **Nx / Turborepo** in monorepos — affected graph + module-boundary lint.36613. **Conventional Commits + semantic-release + Renovate** — via reusable `workflow_call`.36714. **Duplicate-code + complexity budgets** — tune to current code, ratchet down.36815. **Structured-logging lint + AI-disclosure PR template.**36916. **Agent sandboxing** (`harden-runner` + ephemeral runner) for repos where agents run inside CI.370371Everything above is boring, well-trodden, and low-cost. Skipping any of it on AI-coded apps is the same bet as shipping without tests — just slower to notice.372373## Common mistakes374375- **Treating SAST/SCA as warnings.** Anything not gated is decoration. Security findings must fail the build (with a documented exception path).376- **Coverage % without diff-cover.** New code can land at 0% while overall % stays comfortable.377- **Re-running flaky tests forever.** Hides real bugs and trains the team to ignore red.378- **`@v4` action references** — supply-chain risk. Pin SHAs.379- **One mega `ci.yml`.** Path-filter and split per surface so a docs-only PR doesn't run the backend test suite.380- **Adopting all 60 gates day one** — pipeline becomes a 30-minute slog, team starts disabling checks. Use the adoption order.381- **No `AGENTS.md` / `CLAUDE.md`** — agents drift the repo's conventions every PR; your reviewers spend their time on style, not logic.382- **Self-hosted runners without `harden-runner`** — runner becomes a foothold the moment a malicious dep lands.383384## Related385386- `security-review` — companion skill for code-level security audit (this skill is pipeline-level).387- `dependency-management` rule — what gets installed; this skill is what gets *checked*.388- `error-handling`, `logging-observability` — paired with §6 lint rules (structured logs, trace IDs).389- `12-factor-app` — release/runtime context for §4.390- `testing-architecture` — coverage gate and mutation testing depth come from here.391- `db-migration-safety` — pair with a CI gate that flags `DROP`/`RENAME`/`NOT NULL`/large-index DDL in migration diffs.392- `feature-flags-and-rollout` — CI matrix expansion for active rollouts; required-checks-green gate before flag flips.393- `code-review-discipline` — CI gates are the floor; review is the ceiling. Pipeline says "ready for review", not "ready to merge".394- `performance-optimization` — perf regression gates after a hot-path fix live in the pipeline.395396---397> Source: [mechemsi/claude-template](https://github.com/mechemsi/claude-template) — distributed by [TomeVault](https://tomevault.io).398<!-- tomevault:4.0:skill_md:2026-05-22 -->