Detecting Token Regressions Between DESIGN.md Versions Using the diff Command
The design CLI provides a diff sub-command that compares two DESIGN.md files, reports token-level changes, and exits with a non-zero code when lint errors or warnings increase.
The design.md repository ships a specialized command-line tool for tracking design system evolution. By comparing token maps and component definitions across file versions, the diff command surfaces exactly what changed—and whether those changes represent a regression in design system quality.
How the diff Command Works
The diff command follows a predictable pipeline that transforms raw DESIGN.md files into a structured regression report. Understanding this flow helps you interpret results and extend the tool for custom workflows.
Reading and Validating Inputs
The process begins with readInput, a safe file wrapper located in src/utils.ts that accepts either file paths or stdin streams. When you pass - as an argument, the utility streams process.stdin into a string, while invalid paths trigger a JSON error response and graceful exit. This helper ensures the subsequent linting stage always receives valid markdown content.
Linting DESIGN.md Files
Both input files are processed by the shared lint function from src/linter/index.ts. This produces a design-system report containing typed Map objects for colors, typography, spacing, rounded corners, and component definitions. These maps serve as the canonical data structures for comparison, as seen in src/commands/diff.ts where the linter is invoked for both the "before" and "after" states.
Normalizing Component Data
Component definitions require special handling before comparison. The serializeComponents function (defined in src/commands/diff.ts) converts component maps into plain objects by extracting properties entries. This normalization ensures that complex component structures become comparable key-value pairs that the generic diff algorithm can process.
Computing Token Differences
The core comparison logic lives in diffMaps within src/utils.ts. This utility walks two Map instances and produces three arrays: added, removed, and modified. The command invokes this helper for each token category—colors, typography, spacing, and rounded corners—generating a comprehensive delta of the design system.
Regression Detection Logic
After computing token differences, the command compares lint summaries. If the after report contains more errors or warnings than the before report, the regression flag is set to true and the process exits with code 1. This behavior, implemented in src/commands/diff.ts, makes the command suitable for automated CI gates that must block pull requests introducing new design violations.
CLI Usage Examples
Basic File Comparison
Compare two DESIGN.md files and receive structured JSON output:
design diff path/to/before/DESIGN.md path/to/after/DESIGN.md
The default JSON format includes the complete token delta and regression status.
Processing Stdin Streams
Use the - shorthand to pipe content directly into the comparison:
cat before.md | design diff - path/to/after/DESIGN.md
This leverages the readInput utility's stdin handling without creating temporary files.
Human-Readable Markdown Output
Generate a formatted markdown report for code reviews:
design diff path/to/before/DESIGN.md path/to/after/DESIGN.md --format markdown
The --format flag routes through formatOutput in src/utils.ts, which supports both JSON and markdown serializers.
Programmatic Integration
Embed the diff logic directly in Node.js scripts by importing the underlying utilities:
import { readInput, diffMaps } from './packages/cli/src/utils.js';
import { lint } from './packages/cli/src/linter/index.js';
// Load design files
const before = await readInput('before/DESIGN.md');
const after = await readInput('after/DESIGN.md');
// Generate lint reports with token maps
const beforeReport = lint(before);
const afterReport = lint(after);
// Normalize components for comparison
function serializeComponents(components) {
const out = new Map();
for (const [name, comp] of components) {
out.set(name, Object.fromEntries(comp.properties));
}
return out;
}
// Compute specific token diffs
const colorDiff = diffMaps(
beforeReport.designSystem.colors,
afterReport.designSystem.colors
);
console.log(JSON.stringify(colorDiff, null, 2));
This approach gives you granular control over which token categories to compare while using the same battle-tested diffMaps implementation that powers the CLI.
CI/CD Integration
The diff command's exit code behavior makes it ideal for GitHub Actions workflows that prevent design regressions:
name: Design Token Regression Check
on: [pull_request]
jobs:
diff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install dependencies
run: bun install
- name: Check for token regressions
run: |
bun run design diff ${{ github.event.pull_request.base.sha }}:DESIGN.md \
${{ github.event.pull_request.head.sha }}:DESIGN.md \
--format json
When new lint errors or warnings appear in the pull request's DESIGN.md, the command exits with code 1, automatically failing the check and blocking the merge.
Summary
- The
design diffcommand compares two DESIGN.md files and reports token-level changes across colors, typography, spacing, and components. - Regression detection triggers when the after-state contains more lint errors or warnings, causing a non-zero exit code suitable for CI pipelines.
- The architecture relies on isolated utilities—
readInputfor file handling,lintfor parsing,serializeComponentsfor normalization, anddiffMapsfor comparison—making the codebase extensible. - Output formats include machine-readable JSON (default) and human-readable markdown via the
--formatflag. - Source files live in
src/commands/diff.ts(orchestration),src/utils.ts(generic helpers), andsrc/linter/index.ts(parsing logic).
Frequently Asked Questions
How does the diff command determine if a change is a regression?
The command considers a regression to have occurred when the after DESIGN.md file produces more lint errors or warnings than the before file during the linting phase. This comparison happens in src/commands/diff.ts and sets the regression flag to true while exiting with code 1, distinguishing statistical token changes from actual quality degradation.
Can I compare DESIGN.md files from different git commits without checking them out?
Yes. When using the CLI in a git repository, you can pass commit references with the filename prefix syntax (e.g., abc123:DESIGN.md). The readInput utility handles these paths, allowing you to compare the base branch against the head branch directly in CI environments without intermediate file operations.
What token categories does the diff command analyze?
The command inspects five categories: colors, typography, spacing, rounded corners, and component definitions. Each category is compared using the shared diffMaps utility, which identifies added, removed, and modified entries within the respective Map structures.
Why does the diff command use Map objects instead of plain objects for comparison?
The design.md linter parses DESIGN.md into typed Map instances to preserve insertion order and support complex key types. The diffMaps function in src/utils.ts specifically handles Map iteration, while serializeComponents normalizes component data into plain objects only where necessary for deep equality checks of nested properties.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →