Verify in CI
Gate the build on unresolved conflicts, token misuse, and stale contracts.
primitiv verify re-runs the build and exits non-zero when the contract is stale, has unresolved conflicts, or has hardcoded literals in source that bypass the contract. Run it in CI to catch drift, conflicts, and token misuse before they ship.
What it checks
- Unresolved conflicts — a source disagrees with the source-of-truth and no rule handles it
- Token misuse — hardcoded Tailwind arbitrary values like
bg-[#ff0000]orp-[8px]in source files that bypass the contract. Smart-matched to a suggested token when the literal matches a token's value. - Stale contract —
primitiv.contract.jsonno longer matches a fresh rebuild from its sources - Missing config or contract — no
primitiv.config.jsor noprimitiv.contract.json
Precedence when multiple issues exist: conflicts > violations > stale. A fresh build with violations is surfaced before a stale-but-clean one.
Exit codes
| Code | Meaning |
|---|---|
0 | Contract is fresh, conflicts resolved, no token misuse |
1 | Stale contract OR token misuse detected (warning level, default) |
2 | Unresolved conflicts, OR --strict escalation of stale / token misuse |
3 | No config or contract found |
Flags
| Flag | Effect |
|---|---|
--strict | Escalate stale contract and token misuse to hard failure (exit 2). Use in CI on main. |
--json | Emit a machine-readable report to stdout instead of human-readable text. |
--fast | Use file mtimes for drift detection instead of rebuilding the contract in memory. Faster, but unreliable in CI / fresh clones (mtimes get reset on checkout). Best for local pre-commit checks. |
GitHub Actions
primitiv init auto-installs .github/workflows/primitiv-verify.yml for GitHub repos, using verify --strict. Re-running primitiv init refreshes the file between its <!-- primitiv --> markers without trampling other content.
For non-GitHub CI (GitLab, Bitbucket) or manual configurations:
# .github/workflows/design-contract.yml
name: Design contract
on:
pull_request:
paths:
- "src/**"
- "primitiv.config.js"
- "primitiv.rationale.yml"
- "tokens/**"
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
- run: npx @ai-by-design/primitiv verify --strictReading the output
A failure with conflicts and violations looks like this:
$ primitiv verify
✗ 2 pending conflicts require resolution:
- token: color.primary
→ set governance.sourceOfTruth, or update one of the sources
- token: spacing.md
→ set governance.sourceOfTruth, or update one of the sources
! 3 token misuses detected:
- src/components/Button.tsx:14 bg-[#ff0000] → use --color-destructive
- src/components/Card.tsx:7 p-[8px] → use --spacing-2
- src/components/Hero.tsx:22 text-[#abcdef] → no matching token
Exit 2Each violation points to the file and line of the literal, the captured utility (e.g. bg-[#ff0000]), and a smart-match suggestion when a contract token shares the literal's value. Call get_violations via the MCP server for the full list — verify only prints the first five.
Resolve conflicts by either:
- Updating the losing source to match the source-of-truth, or
- Adding an explicit rule in
primitiv.config.jsundergovernance, or - Changing
sourceOfTruthif the underlying decision has shifted.
Resolve violations by replacing the literal with the suggested token (or any token from get_design_context), or by suppressing the line if the literal is intentional.
Suppressing false positives
Use // primitiv-ignore-next-line on the line directly preceding the violation:
// primitiv-ignore-next-line
<div className="bg-[#fef3c7]" /> // intentional one-off — promo bannerScope is the next non-blank line. Blank lines between the directive and the code are fine; intervening comments or code are not.
When you want build vs verify
buildwrites the contract. Run locally while authoring; run in CI onmainafter merge.verifyonly exits with a status — it doesn't write the contract, so it won't leave drift behind. Run in CI on every PR.
Treating verify --strict as a required status check is the cleanest way to keep the contract honest.