Maddox Engine
Docs

What Maddox Engine actually checks, and how to run it

Everything below also lives in the npm package README. This page exists to give you the mental model before you install anything.

Getting started

No account needed to try it. Point the CLI at your source and the CSS file holding your @theme block:

npx maddox-engine ./src ./src/app/globals.css

That's the whole install — no SDK, no build-step changes, nothing to configure beyond the path to your own tokens. It prints a text report to the terminal. To upload results to a dashboard account (scan history, drift trends over time), set MADDOX_API_KEY from your dashboard first — that step is optional, the scan itself never requires an account.

No tokens handy yet? Try the public quick-scan first — paste a URL and it'll detect your real tokens from the page's own deployed CSS, no local checkout required.

How it works

Your design system has a rulebook: a real color is #2563EB, a real body size is 16px. Nothing stops a hardcoded #2563ec from creeping into a file six months later — it renders fine, nobody notices in review. Maddox scans your source code (or a live URL) and diffs every hardcoded color, font-size, and motion value against that rulebook, then tells you exactly which ones drifted and by how much.

Every checked value lands in exactly one of three buckets:

Match

Exact value, correct as-is.

Near-miss

Close but not exact — within 25 RGB units for a color, 2px for a font-size, 0.05s for a motion duration.

Unrecognized

Not close to anything in the rulebook.

This is a source-code and rendered-output scanner, not a pixel/DOM visual-regression tool. It checks the values a page actually ships against the tokens that are supposed to govern them, so it catches drift that renders identically to a real token (zero visual diff) but was never written as one.

Ground truth sources

Maddox reads your real tokens from one or more of these. When more than one is given, precedence runs Figma Variables > Tokens Studio > @theme CSS — Figma sits earliest in a real design-to-code pipeline, so a later step is more likely to be the stale one.

@theme CSS (always available)

A Tailwind v4 @theme { ... } block. No flags needed — just point the CLI at the CSS file that holds it.

Tokens Studio — --tokens-studio <path>

A Tokens Studio / W3C Design Tokens JSON export. Single-set and multi-set exports are both supported (multi-set: later sets override earlier ones by path). {alias} references resolve automatically. Only types that reduce to one comparable value (color, spacing, sizing, fontSizes, borderRadius, dimension) are used — composite types like typography or boxShadow are skipped rather than misclassified.

Figma Variables — --figma-file <file-key>

Pulled directly from Figma's Variables REST API. Requires an Enterprise Figma plan — any other plan gets a 403, regardless of the token's own permissions. The token is read from MADDOX_FIGMA_TOKEN or MADDOX_FIGMA_OAUTH_TOKEN — never a CLI flag, which would leak it into shell history. Only COLOR and FLOAT variables are used; FLOAT has no unit in Figma's API and is treated as pixels.

Running a scan

Source scan — checks your local files:

npx maddox-engine ./src ./src/app/globals.css --project my-app --fail-below 80

Live URL scan — checks what's actually deployed, not what's in the repo:

npx maddox-engine - ./src/app/globals.css --url https://example.com

--url fetches the page's rendered HTML plus every same-origin <link rel="stylesheet">. A stale CDN cache, a build step that silently drops a token, or a config typo can all make a deployed page diverge from what the source says — only a live-URL scan catches that. Third-party stylesheets (a different origin) are never fetched, so findings only ever point at code the project actually owns.

Two things don't carry over to --url mode:

  • Motion — real motion libraries animate via the JS runtime, not static HTML/CSS literals, so motion findings are dropped from every URL scan rather than surfaced as noise.
  • Component states — state-completeness checks whether an identifier is referenced in source; a fetched, already-rendered page has no source to check.

Suggested fixes

Every near-miss finding with a resolved nearest token gets a suggestion — the concrete replacement text (var(--token-name) for a CSS value, or motionTokens.path.to.value for a motion token). An unrecognized value with no close match, and a missing-state finding, never get a suggestion — there's no safe mechanical fix for either.

By default this is advisory text for you to apply yourself. Pass --apply-fixes to write it straight into the file:

npx maddox-engine ./src ./src/app/globals.css --apply-fixes

Scoped deliberately narrow:

  • Only color/font-size/spacing suggestions are ever applied. Motion suggestions assume a motionTokens import exists in that file's scope — that can't be verified per-file, so applying it blind could silently break the build. Motion stays advisory-only.
  • If a finding's line no longer contains its reported value, it's skipped rather than guessed at.
  • Not available with --url — a fetched page has no local file to write to.
  • This is a real edit to your working tree — review the diff before committing.

CI / GitHub Action

omrdev1/maddox-cli runs a scan, posts the markdown report as a PR comment (updating the same comment on later pushes rather than piling up new ones), and optionally fails the build via fail-below.

- uses: actions/checkout@v4
- uses: omrdev1/maddox-cli@main
  with:
    target-dir: src
    theme-css: src/app/globals.css
    github-token: ${{ secrets.GITHUB_TOKEN }}
    fail-below: "80"
target-dir

Directory to scan. Required unless url is set.

theme-css

Path to the CSS file containing the @theme block. Required.

motion-tokens

Path to a motion-tokens JSON file.

states

Path to a state-contract JSON file. No effect when url is set.

tokens-studio

Path to a Tokens Studio / W3C Design Tokens JSON export.

figma-file / figma-token / figma-token-type

Pull tokens from a Figma file's Variables. figma-token should be a secret, never a literal value.

url

A deployed page URL to scan instead of local source.

github-token

For posting the PR comment, usually ${{ secrets.GITHUB_TOKEN }}. Required.

fail-below

Fail the build below this health score. Omit for comment-only.

api-key / api-url

Optional — upload results to a Maddox Engine dashboard account for scan history and drift trends. Not required to use the Action itself.

Pin @main to a specific commit SHA if you want reproducible CI runs immune to changes on this branch.

CLI reference

npx maddox-engine <target-source-dir> <path-to-globals.css-with-@theme-block> [options]
--project <name>

Project name. Defaults to the target directory's basename, or the URL's hostname in --url mode.

--motion <path>

Path to a JSON file of motion tokens (e.g. a vendored copy of your motion-tokens export).

--states <path>

Path to a state-contract JSON file. No effect in --url mode.

--tokens-studio <path>

Path to a Tokens Studio / W3C Design Tokens JSON export. Merged with, and taking precedence over, @theme on any path both define.

--figma-file <file-key>

Pull color/spacing tokens from a Figma file's Variables. Requires an Enterprise Figma plan.

--url <page-url>

Scan a deployed page instead of local source. Pass a placeholder like - for <target-source-dir> when using this alone.

--exclude <glob>

Skip matching files/directories. Repeatable. A pattern with no / matches at any depth.

--apply-fixes

Write near-miss color/font-size/spacing suggestions back into source files. Not available with --url.

--format text|json|markdown

Output format. Default: text.

--fail-below <0-100>

Exit non-zero if the drift health score falls below this threshold. Omit to never fail.

Health score

One 0-100 number per scan. Match counts fully, near-miss counts half (it drifted, but is still recognizably close to a real token), unrecognized and a missing required state count for nothing. A scan with zero checks scores 100.

score = round((matches × 1 + near-misses × 0.5) / total × 100)

Example: 80 matches, 15 near-misses, 5 unrecognized out of 100 checked values = (80 + 7.5) / 100 × 100 = 87.

Use --fail-below <0-100> to fail a build when a scan's score drops under a threshold you choose — worth setting after you've seen what a real scan of your own codebase actually scores, rather than guessing a number up front.

Ready to try it? Run a quick scan with no install, or view the source on GitHub.