Skip to main content

CI integration

Integrate preset AI into your CI/CD pipeline to enforce design system consistency automatically.

Overview

preset AI provides three ways to integrate with your CI pipeline:

  1. GitHub Action: one-line setup for GitHub repos
  2. CLI commands: direct CLI usage in any CI system
  3. SARIF output: GitHub Code Scanning integration

GitHub Action

The easiest way to add preset AI to your workflow.

Quick setup

Create .github/workflows/preset.yml:

name: Design System Check

on:
  pull_request:
    branches: [main]

permissions:
  contents: read
  pull-requests: write   # post the results comment
  id-token: write        # mint the per-run OIDC token (recommended auth)

jobs:
  preset:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Check design system
        uses: preset-ai/compliance-check@v1
        with:
          design-system-id: ${{ secrets.PRESET_DESIGN_SYSTEM_ID }}

What it does

On every pull request, the action validates the changed files against your design system and:

  1. Flags violations: native elements where a preset exists, hardcoded colors, arbitrary spacing, forbidden primitives, and token misuse
  2. Annotates the diff and posts a PR comment: results land inline and in a single updating comment
  3. Fails on errors: blocks merge when errors are found (configurable; warnings do not fail by default)

It checks changed .tsx, .jsx, .ts, .vue, and .svelte files by default. There is no command to choose: the action always runs design-system validation. (The drift, scan, and audit-copy workflows below are CLI commands, for non-GitHub CI.)

Authentication

The recommended path is keyless OIDC: install the Preset GitHub App (binding it to your design system during install) and set permissions: id-token: write. The action mints a fresh OIDC token per run, scoped to your repo, so there is no secret to rotate. If your runner cannot grant id-token: write, fall back to an API key: preset-api-key: ${{ secrets.PRESET_API_KEY }}.

Configuration options

- uses: preset-ai/compliance-check@v1
  with:
    design-system-id: ${{ secrets.PRESET_DESIGN_SYSTEM_ID }}

    # Full DB-backed validation (optional; scopes the design system)
    supabase-url: ${{ secrets.PRESET_SUPABASE_URL }}
    supabase-key: ${{ secrets.PRESET_SUPABASE_ANON_KEY }}

    # Which files to check (defaults shown)
    include: '**/*.tsx,**/*.jsx,**/*.ts,**/*.vue,**/*.svelte'
    exclude: '**/node_modules/**'

    # Gating
    fail-on-errors: 'true'        # default
    fail-on-warnings: 'false'     # default
    score-threshold: '0'          # min health score to pass (0 = off)
    regression-threshold: '0'     # max score drop vs the last snapshot (0 = off)

    # Surfaces
    comment-on-pr: 'true'         # default
    upload-sarif: 'false'         # GitHub Code Scanning

    # API-key fallback (only if not using the OIDC App)
    preset-api-key: ${{ secrets.PRESET_API_KEY }}

CLI integration

For non-GitHub CI systems or custom pipelines.

Installation

curl -fsSL https://presetai.dev/install.sh | sh

Commands

Drift detection

# Table output (human-readable)
preset drift src

# JSON output (for parsing)
preset drift src --format json --output drift.json

# SARIF output (for security tools)
preset drift src --format sarif --output drift.sarif

# With custom presets
preset drift src --presets ./my-presets.json

# Strict mode (exit 1 on any drift)
preset drift src --strict

Pattern scanning

# Find patterns in codebase
preset scan src

# JSON output with compliance score
preset scan src --format json --output scan.json

# Adjust threshold
preset scan src --threshold 5

Copy audit

# Check terminology
preset audit-copy src --config terminology.json

# SARIF output
preset audit-copy src --format sarif --output copy.sarif

# Strict mode
preset audit-copy src --strict

Exit codes

CodeMeaning
0Success, no violations
1Violations found (with --strict) or error

JSON output structure

Drift report

{
  "driftScore": 25,
  "hasDrift": true,
  "summary": {
    "totalComponents": 156,
    "usingPresets": 142,
    "withDrift": 8,
    "unknown": 6
  },
  "instances": [
    {
      "file": "src/components/Button.tsx",
      "line": 15,
      "column": 5,
      "component": "Button",
      "severity": "warning",
      "driftType": "prop_override",
      "actualProps": { "variant": "primary" },
      "expectedPreset": "primary-action",
      "expectedProps": { "variant": "default" },
      "suggestedFix": "Use preset 'primary-action' instead"
    }
  ]
}

Scan report

{
  "meta": {
    "fileCount": 42,
    "duration": 1234
  },
  "summary": {
    "filesScanned": 42,
    "totalUsages": 156,
    "patternsDetected": 12,
    "proposalsGenerated": 8
  },
  "score": 85,
  "proposals": [
    {
      "name": "primary-button",
      "component": "Button",
      "props": { "variant": "default" },
      "usageCount": 24,
      "confidence": 0.92
    }
  ]
}

SARIF integration

SARIF (Static Analysis Results Interchange Format) enables integration with GitHub Code Scanning and other security tools.

GitHub Code Scanning

Upload SARIF results to see violations in the Security tab:

- name: Run preset AI drift
  run: preset drift src --format sarif --output results.sarif

- name: Upload SARIF
  uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: results.sarif
    category: preset-drift

SARIF structure

preset AI generates SARIF 2.1.0 compliant output:

{
  "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
  "version": "2.1.0",
  "runs": [{
    "tool": {
      "driver": {
        "name": "preset-drift",
        "rules": [
          {
            "id": "drift/prop-override",
            "name": "PropOverride",
            "shortDescription": { "text": "Component props override preset" }
          }
        ]
      }
    },
    "results": [
      {
        "ruleId": "drift/prop-override",
        "level": "warning",
        "message": { "text": "Use preset 'primary-action' instead" },
        "locations": [{
          "physicalLocation": {
            "artifactLocation": { "uri": "src/Button.tsx" },
            "region": { "startLine": 15, "startColumn": 5 }
          }
        }]
      }
    ]
  }]
}

Example CI configurations

GitHub Actions (full)

name: Design System

on:
  pull_request:
    branches: [main]
  push:
    branches: [main]

permissions:
  contents: read
  pull-requests: write   # post the results comment
  id-token: write        # mint the per-run OIDC token (recommended auth)
  security-events: write # upload SARIF to Code Scanning

jobs:
  preset:
    name: Design system check
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: preset-ai/compliance-check@v1
        id: preset
        with:
          design-system-id: ${{ secrets.PRESET_DESIGN_SYSTEM_ID }}
          upload-sarif: 'true'
      - name: Report score
        if: always()
        run: |
          echo "Design system score: ${{ steps.preset.outputs.health-score }}/100 (${{ steps.preset.outputs.health-grade }})"

To also run drift detection, terminology, or pattern analysis in CI, add CLI steps (see CLI integration above): preset drift src --strict and preset audit-copy src.

GitLab CI

preset-drift:
  stage: test
  image: node:20
  script:
    - curl -fsSL https://presetai.dev/install.sh | sh
    - preset drift src --format json --output drift.json --strict
  artifacts:
    reports:
      dotenv: drift.json
    when: always

preset-copy:
  stage: test
  image: node:20
  script:
    - curl -fsSL https://presetai.dev/install.sh | sh
    - preset audit-copy src --config terminology.json --strict

CircleCI

version: 2.1

jobs:
  preset-check:
    docker:
      - image: cimg/node:20.0
    steps:
      - checkout
      - run:
          name: Install preset AI CLI
          command: curl -fsSL https://presetai.dev/install.sh | sh
      - run:
          name: Run drift check
          command: preset drift src --format sarif --output results.sarif --strict
      - store_artifacts:
          path: results.sarif

workflows:
  main:
    jobs:
      - preset-check

Jenkins

pipeline {
    agent { docker { image 'node:20' } }

    stages {
        stage('Design system check') {
            steps {
                sh 'curl -fsSL https://presetai.dev/install.sh | sh'
                sh 'preset drift src --format json --output drift.json'
            }
        }
    }

    post {
        always {
            archiveArtifacts artifacts: 'drift.json'
        }
    }
}

Best practices

Start with warnings, graduate to errors

Begin with fail-on-errors: false to see the scope of issues without blocking merges, then tighten:

# Phase 1: visibility (the comment posts; nothing fails the build)
fail-on-errors: 'false'
comment-on-pr: 'true'

# Phase 2: enforcement
fail-on-errors: 'true'

Use SARIF for tracking

Upload SARIF to track trends over time in GitHub's Security tab.

Run on push to main

Run the check on push to main (not just PRs) so each merge records a health snapshot. That snapshot is the baseline regression-threshold and fail-on-new-only compare against on later PRs:

on:
  push:
    branches: [main]

jobs:
  preset:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write
    steps:
      - uses: actions/checkout@v4
      - uses: preset-ai/compliance-check@v1
        with:
          design-system-id: ${{ secrets.PRESET_DESIGN_SYSTEM_ID }}
          fail-on-errors: 'false'   # observe on main; do not block the push

Version-control your presets and terminology

The CLI drift and audit-copy commands read local config you keep in the repo:

project/
├── src/
├── design-system/
│   ├── presets.json      # passed to `preset drift --presets`
│   └── terminology.json  # passed to `preset audit-copy --config`
└── .github/
    └── workflows/
        └── preset.yml

Staged file checks

For pre-commit hooks, use the --staged flag:

preset scan --staged --format json

Troubleshooting

"No presets found"

The drift command uses default presets if none are provided. To point it at your own:

preset drift src --presets ./path/to/presets.json

"SARIF upload failed"

Ensure your workflow has the required permission:

permissions:
  security-events: write

"PR comment not appearing"

Check that:

  1. Event is pull_request (not push)
  2. Token has pull-requests: write permission
  3. comment-on-pr is 'true' (string, not boolean)

"CLI command not found"

The preset binary is not on your PATH. Install it:

curl -fsSL https://presetai.dev/install.sh | sh

If the installer placed it in ~/.local/bin, add that directory to your PATH.

Was this page helpful?

Last reviewed by @jschuyler
All systems operationalDocsDevelopersPlatformPricing
PrivacyTerms© 2026 fndd, LLC