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
| # | Gate | Script | What it enforces | Bypass marker |
|---|---|---|---|---|
| 1 | Format | npx prettier --check | Repo-wide Prettier compliance. | — (fix it: prettier --write) |
| 2 | Migration tracking | npm run check:migrations | Every Prisma migration in */migrations/ has a tracking row in the right schema_migrations table. Guards against the 2026-05-10 dual-tracking-table footgun. | — |
| 3 | Audit coverage lint | (in lint step) | Security-sensitive mutations use auditCritical(); routine ones use auditAction(). | — (per-callsite // audit-coverage: skip <reason> comment) |
| 4 | OpenAPI freshness | npm run check:openapi | Committed apps/project-tracker/openapi.json matches the Zod schemas. Run npx turbo run generate-openapi --filter=@constellation/project-tracker to regenerate. | — |
| 5 | Changeset present | npx tsx scripts/check-changeset.ts | Every PR with user-visible impact adds a .changeset/*.md fragment. | [skip changeset] |
| 6 | PR requirements | npx 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] |
| 7 | AC traceability (INF-110) | npx tsx scripts/check-ac-traceability.ts | Every 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] |
| 8 | Docs-sync (INF-111, warn-mode v1) | npx tsx scripts/check-docs-sync.ts | Behaviour-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] |
| 9 | Scripts unit tests | npm run test:scripts | Vitest run against scripts/**/*.test.ts (the parsers / validators / CI gates). | — |
| 10 | Route wrapping | npm run check:routes | Every route in catalog / directory / wiki is wrapped with authedRoute / authedRouteWithParams. | Per-route // @route-wrap: skip <reason> |
| 11 | Lint + typecheck | npx turbo run lint typecheck | ESLint + TypeScript strict across the monorepo. | — |
| 12 | Build + test | npx turbo run build test | Full build, unit + integration + property tests. | — |
| 13 | E2E smoke | Playwright saved-filters.smoke.spec.ts | Saved-filters POST → GET → DELETE round-trip against a seeded next dev (PT-26 regression gate). | — |
| 14 | Wiki MCP boundary | npx tsx scripts/check-wiki-mcp-boundary.ts | Wiki MCP tools stay within the documented boundary. | — |
| 15 | Verify 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 form | Meaning | Verification |
|---|---|---|
→ path/to/file.ts:symbol | Code symbol is present in the diff | Greps the file (at HEAD) for symbol definitions / exports / property heads. |
→ path/to/file.md (no colon) | Non-code file is present in the diff | Checks 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 behaviour | Gated on Playwright smoke instead. |
→ <!-- deferred: <reason> --> | Explicit waiver | Surfaces 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 pattern | Required docs surface |
|---|---|
apps/catalog/src/app/.../route.ts | apps/docs/docs/modules/catalog/ |
apps/catalog/src/server/services/ | apps/docs/docs/modules/catalog/ |
apps/directory/src/app/.../route.ts | apps/docs/docs/modules/directory/ |
apps/directory/src/server/services/ | apps/docs/docs/modules/directory/ |
apps/wiki/src/app/.../route.ts | apps/docs/docs/modules/wiki/ |
apps/project-tracker/src/app/api/.../route.ts | apps/docs/docs/modules/project-tracker/ OR openapi.json regen |
packages/platform/*/src/.../index.ts | apps/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 notblocked.
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.