Skip to main content

CI gates

Convention without enforcement rots. Every rule in AGENTS.md that an agent could plausibly violate has a paired CI gate that fails the PR if it slips. This page is the single source of truth for what those gates are, where they live, and how to bypass them safely.

All gates live in the Quality Gates job of .github/workflows/ci.yml unless otherwise noted, and run on every PR targeting develop (release / hotfix branches are exempt).

The gates

#GateScriptWhat it enforcesBypass marker
1Formatnpx prettier --checkRepo-wide Prettier compliance.— (fix it: prettier --write)
2Migration trackingnpm run check:migrationsEvery Prisma migration in */migrations/ has a tracking row in the right schema_migrations table. Guards against the 2026-05-10 dual-tracking-table footgun.
3Audit coverage lint(in lint step)Security-sensitive mutations use auditCritical(); routine ones use auditAction().— (per-callsite // audit-coverage: skip <reason> comment)
4OpenAPI freshnessnpm run check:openapiCommitted apps/project-tracker/openapi.json matches the Zod schemas. Run npx turbo run generate-openapi --filter=@constellation/project-tracker to regenerate.
5Changeset presentnpx tsx scripts/check-changeset.tsEvery PR with user-visible impact adds a .changeset/*.md fragment.[skip changeset]
6PR requirementsnpx tsx scripts/check-pr-requirements.ts(a) Title/body cites a ticket; (b) feat: PRs add/modify a spec; (c) added/modified .ai/specs/*.md matches the spec format (INF-107).[skip ticket], [skip spec], [skip spec-format]
7AC traceability (INF-110)npx tsx scripts/check-ac-traceability.tsEvery numbered AC in a changed .ai/specs/*.md resolves to a code anchor (→ file:symbol), file anchor (→ path/to/file.md), UI marker (→ <!-- ui: ... -->), or explicit waiver (→ <!-- deferred: ... -->).[skip ac-traceability]
8Docs-sync (INF-111, warn-mode v1)npx tsx scripts/check-docs-sync.tsBehaviour-changing source files (routes, services, schema, event contracts) have a paired apps/docs/docs/** edit. Warn-only in v1 — flip to hard-fail with DOCS_SYNC_ENFORCE=1 after a tuning window.[skip docs]
9Scripts unit testsnpm run test:scriptsVitest run against scripts/**/*.test.ts (the parsers / validators / CI gates).
10Route wrappingnpm run check:routesEvery route in catalog / directory / wiki is wrapped with authedRoute / authedRouteWithParams.Per-route // @route-wrap: skip <reason>
11Lint + typechecknpx turbo run lint typecheckESLint + TypeScript strict across the monorepo.
12Build + testnpx turbo run build testFull build, unit + integration + property tests.
13E2E smokePlaywright saved-filters.smoke.spec.tsSaved-filters POST → GET → DELETE round-trip against a seeded next dev (PT-26 regression gate).
14Wiki MCP boundarynpx tsx scripts/check-wiki-mcp-boundary.tsWiki MCP tools stay within the documented boundary.
15Verify release migrations(release PRs only)Both staging + production DBs have every migration applied before the release PR opens. See .claude/skills/release-and-migrations.— (rerun after applying)

Bypass-marker matching

Skip markers are matched strictly so a PR description that documents a marker doesn't accidentally trigger the bypass:

  • In the title — any substring match counts (titles are short and intentional).
  • In the body — the marker must appear on its own line, trimmed. Inline mentions, backticked formatting (`[skip X]`), and prose like "use [skip X] to bypass" do NOT trigger the bypass.

Use markers sparingly and only when the rule genuinely does not apply. Every bypass should carry a written reason next to it so reviewers can judge intent.

The two newest gates in detail

AC traceability — check-ac-traceability.ts (INF-110)

Reads every .ai/specs/*.md in the PR diff, parses the ## Acceptance criteria section, and verifies each numbered AC ends with one of four anchor forms:

Anchor formMeaningVerification
→ path/to/file.ts:symbolCode symbol is present in the diffGreps the file (at HEAD) for symbol definitions / exports / property heads.
→ path/to/file.md (no colon)Non-code file is present in the diffChecks the file appears in the PR's changed-file set. Use for AGENTS.md edits, agent / skill / workflow files, docs pages.
→ <!-- ui: <route> -->UI-only behaviourGated on Playwright smoke instead.
→ <!-- deferred: <reason> -->Explicit waiverSurfaces in PR body as a risk.

The script runs locally too: npx tsx scripts/check-ac-traceability.ts <spec-path> .... Exit code is 0 (clean) or 1 (failures).

Why the gate exists. The post-impl-reviewer subagent does AC mapping manually; this gate makes the mapping mechanical so even a PR opened without invoking the reviewer cannot silently miss an AC. INF-110.

Docs-sync — check-docs-sync.ts (INF-111)

Maps behaviour-changing source-file patterns to required docs surfaces:

Source patternRequired docs surface
apps/catalog/src/app/.../route.tsapps/docs/docs/modules/catalog/
apps/catalog/src/server/services/apps/docs/docs/modules/catalog/
apps/directory/src/app/.../route.tsapps/docs/docs/modules/directory/
apps/directory/src/server/services/apps/docs/docs/modules/directory/
apps/wiki/src/app/.../route.tsapps/docs/docs/modules/wiki/
apps/project-tracker/src/app/api/.../route.tsapps/docs/docs/modules/project-tracker/ OR openapi.json regen
packages/platform/*/src/.../index.tsapps/docs/docs/packages/ or apps/docs/docs/architecture/
packages/contracts/src/events/apps/docs/docs/reference/events.md
prisma/schema.prisma or */migrations/apps/docs/docs/modules/ or apps/docs/docs/architecture/

If a PR touches a mapped source pattern without also touching a matching docs surface, the gate flags it. Warn-mode in v1 — exits 0 with warnings so we can tune the mapping before forcing hard-fails. Flip to hard-fail by setting DOCS_SYNC_ENFORCE=1 (per-PR via env, or globally once the false-positive rate is acceptable).

Why warn-mode. The mapping is intentionally narrow on the source side and broad on the docs side. Tuning needs real PR data — better to ship the gate as a warning, gather feedback, and harden than to ship hard-fails and have everyone start using [skip docs] reflexively.

The pre-PR self-review hook (INF-114) — not a CI gate, but enforced

.claude/hooks/pre-pr-self-review.sh runs on the agent's local machine, not in CI. It intercepts gh pr create Bash calls and refuses to let the call through unless:

  • The PR body contains a <details><summary>Self-review</summary>...</details> block, AND
  • The **Verdict:** line inside is not blocked.

Bypass: [skip self-review] (with the standard title-substring / body-own-line semantics).

The hook is documented in detail on the Subagents page. The pairing matters: developer subagents invoke post-impl-reviewer, embed its verdict bar, and the hook then validates that they actually did before letting the PR open.

Running the full pipeline locally

# The 3-line pre-push check (AGENTS.md § Pre-Push Checklist):
npx prettier --check "**/*.{ts,tsx,js,jsx,json,md}"
npx turbo run lint typecheck build test
npm run check:routes

Plus the two new gates:

npm run test:scripts # all scripts/**/*.test.ts
npx tsx scripts/check-ac-traceability.ts # CI mode — needs GITHUB_BASE_REF=develop
npx tsx scripts/check-ac-traceability.ts .ai/specs/SPEC-foo.md # CLI mode
npx tsx scripts/check-docs-sync.ts # warn-mode
DOCS_SYNC_ENFORCE=1 npx tsx scripts/check-docs-sync.ts # hard-fail mode

See also

  • Subagents — the reviewing subagents that emit the verdict bar these gates consume.
  • Workflow — the end-to-end flow that runs these gates.
  • Architecture rules — the underlying hard rules each gate enforces.
  • Constitution — the invariants the rules derive from.