An audit kit for teams whose interface has drifted from its design system. It reads what the browser actually rendered and which CSS rule won, so every finding names the element, the value, the token it should use and the line to change.
In a design review, one person says the spacing looks off, another says it looks fine, and neither can prove it.
Checking by eye does not scale. One review round on KorCRM ran to 127 screenshots in 19 folders. The drifts that matter are the ones eyes miss: a card gap of 18 px among gaps of 16, or a button in #6d28d9 where the brand token says #7c3aed. Two purples that close cannot be told apart across two screenshots, so the argument ends without a record and opens again next sprint.
Automating the check fails the other way. The first full automated run on KorCRM returned 4,306 findings, most of them repeats of a few dozen shared rules across hundreds of elements. A list that long tells nobody which few lines to change, so it goes unread.
Either way, review time goes on opinion and the drift ships.
127 in 19 folders, checked by eye
“The spacing looks off.”
“Looks fine to me.”
Opinion against opinion, with nothing to measure
Next sprint: new screenshots, the same argument
No record of what drifted, so the decision waits.
The audit gives every step of a review a record a person can cite: what the browser rendered, set against what the design tokens say it should be.
| Step | Before | With the system |
|---|---|---|
| Find the drift | Scrub screenshots by eye, 127 of them for one round | A headless browser opens every route and records what each element actually rendered |
| Check the tokens | Hold two purples side by side and judge | Each value is checked against the design tokens, and a drift names the token it should use |
| Find the cause | Search the stylesheets for the rule behind it | The browser reports which CSS rule won, with the file and line it came from |
| Decide what to fix | A report per element: 4,306 findings on KorCRM | Findings are grouped by the rule that caused them, so one change closes every element it touches |
| Follow up | New screenshots and the same argument next sprint | Each finding keeps its ID on every run, so a fix is confirmed and a regression shows as new |
| Gate the release | Drift ships unless someone spots it | A critical or high-severity finding fails the build with exit code 1 |
/pricing · .cta-button · padding-block
I’d start with one screen your reviewers already argue about, and your design tokens.
One run shows how far that screen has drifted, which few CSS rules cause most of it and what a clean result looks like. The first version runs in the repository and CI your engineers already use, with the tokens and components your designers already keep.
Discuss a system like thisaudit-kit is fifteen single-purpose command-line tools in strict TypeScript, and every one writes the same JSON report. One parser reads them all, and the same report goes to reviewers, agents and CI.
dom-probe drives a headless Chromium through Playwright. For each tagged element it records the computed styles and the bounding box and, through the Chrome DevTools Protocol, which declaration won each property; local source maps walk that answer back to the authored file. token-audit, spacing-audit and tokens-lint read that one metrics file, and a11y-audit, lighthouse-audit and semantic-diff open the same routes from the same config.
The report, called the envelope, carries a summary with a 0 to 100 score and a pass flag, the findings and any artifacts. JSON goes to stdout and logs go to stderr, so every stdout line parses. An MCP server exposes the tool registry to agents, and mark draws numbered boxes on screenshots so a multimodal model can point at a finding by its number.
Opens each route in a headless browser; records computed styles, boxes and the winning declaration
Playwright, CDP, metrics.jsonCompare every value with the tokens, the spacing grid and the lint rules
token-audit, spacing-audit, tokens-lintFindings that share a source declaration become one, with a count of the elements it touches
--group-by cause-fingerprintOne JSON shape from every tool: summary, score, findings and artifacts
schemaVersion 1.0.0Markdown for reviewers, JSON or MCP for agents, an exit code for CI
exit 0–6, mcp-serverA random ID per finding demos well and makes “is yesterday’s drift still open?” impossible to answer.
--verify <id>. ADR-005The envelope sits upstream of every tool and every agent that reads them. One renamed field would break fifteen tools and a parser somewhere out of sight.
schemaVersion has stayed 1.0.0, because the first broken downstream agent costs more than any cleanup a version 2 would buy. Cost: no field is renamed or removed before a major version, and two tools keep their own copy of one selector list to stay sealed. Gained: anything written against 1.0.0 still parses every tool’s output. ADR-003, ADR-007tokens-lint bans raw hex, px and shadow values in critical properties, and agents are expected to write its rules.
The DevTools Protocol can fail to attach on a slow first load.
Follow the first full run on KorCRM through the kit, from the browser to the agent. The left edge of the drawing shows which part of the kit does each step.
A headless browser opens each route and records what every tagged element actually rendered: its computed styles and its box. It also asks the Chrome DevTools Protocol which declaration won each property, and walks that answer back through local source maps to the file that was written. On a 50-element route this adds 16 ms, 3% over a run without it; if CDP will not attach, that route logs a diagnostic and the run goes on.
token-audit, spacing-audit and tokens-lint read the same metrics.json and write the same envelope. A drift finding names the route, selector, property, observed value and the nearest legal token, in one of sixteen categories and five severities.
On KorCRM, the first full run returned 4,306 findings: 782 spacing, 2,714 lint, 656 token and 154 others. Each was a real occurrence, and together they were unreadable. At four thousand findings nobody acts, person or agent.
Muting findings would have hidden real drift, so each finding is traced to the source declaration that caused it and grouped by that cause. The 12 px gap from one stock Tailwind class, .gap-3, was flagged 272 times; grouped, it is one finding with affectedNodeCount: 272. The 4,306 came to about 250 grouped findings, pointing at a few dozen declarations.
Every tool exits the same way: 0 clean, 1 drift found, and 2 to 6 for a usage error, a missing input, a missing dependency, an invalid report and a timeout. By default a critical or high finding fails the run, so CI branches on the code with no text to parse.
The MCP server exposes every registered tool to agents, and mark draws numbered boxes on screenshots so a model can point at finding 7 and mean it. Each ID is a content hash, so --verify <id> re-checks one finding with the same answer on any machine. The dashed arc is the product: every finding traced back to the declaration that caused it.
Each row is a problem the build found, in design review or on a real interface, and the change that settled it at its cause.
Most became contracts the tools enforce: grouping by cause, IDs that hold across runs, rules that cannot run code, a stdout that always parses and sign-in paths that never reach a report. Each row traces to a logged decision.
4px 295 times and 12px 272 times, from the stock Tailwind classes .gap-1 and .gap-3.affectedNodeCount and per-instance detail. 4,306 → ≈250.tokens-lint would give every agent-written ruleset a way to run code inside a tool that gates CI.storageState paths, where sign-in sessions live on disk, could reach any report or run manifest the kit writes.The source is 17,606 lines of strict TypeScript with 13,538 lines of tests, and it names no client project: the project’s rules treat a project name in the source as a bug, which keeps the kit reusable.
The kit turns a design review into evidence: each finding names its element, its value, the token it should use and the rule that caused it, with an ID that holds from run to run.
On KorCRM it grouped 4,306 raw findings into about 250. A version for your team starts from the same method, evidence from the rendered page traced to its cause, and adds what your design system and your release process need.
If your reviews keep arguing about spacing and colour, the fix starts with one screen and your design tokens. Bring them.