Primitiv

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] or p-[8px] in source files that bypass the contract. Smart-matched to a suggested token when the literal matches a token's value.
  • Stale contractprimitiv.contract.json no longer matches a fresh rebuild from its sources
  • Missing config or contract — no primitiv.config.js or no primitiv.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

CodeMeaning
0Contract is fresh, conflicts resolved, no token misuse
1Stale contract OR token misuse detected (warning level, default)
2Unresolved conflicts, OR --strict escalation of stale / token misuse
3No config or contract found

Flags

FlagEffect
--strictEscalate stale contract and token misuse to hard failure (exit 2). Use in CI on main.
--jsonEmit a machine-readable report to stdout instead of human-readable text.
--fastUse 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 --strict

Reading 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 2

Each 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.js under governance, or
  • Changing sourceOfTruth if 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 banner

Scope 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

  • build writes the contract. Run locally while authoring; run in CI on main after merge.
  • verify only 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.

On this page