How to Compare Two DESIGN.md Files to Detect Changes and Regressions

The design.md CLI provides a dedicated diff command that parses two DESIGN.md files into design-system models and computes a structural diff to detect changes, returning exit code 1 when regressions are found.

The google-labs-code/design.md repository includes a built-in comparison tool that allows you to compare two DESIGN.md files to identify token changes, component modifications, and quality regressions. By leveraging the existing linter infrastructure, the diff command transforms markdown files into structured design-system models and performs granular map-based comparisons. This functionality is essential for design system governance, enabling teams to validate changes before merging pull requests or deploying updates.

How the DESIGN.md Diff Command Works

The diff implementation follows a seven-step pipeline defined in packages/cli/src/commands/diff.ts. Understanding this flow helps you interpret results and debug comparison issues.

1. Input Reading and Validation

The process begins with readInput in packages/cli/src/utils.ts, which loads the before and after files from disk or stdin. If a file cannot be read, the utility throws a FileReadError and halts execution.

2. Design System Parsing

Both files are parsed using the lint function exposed from packages/cli/src/linter/index.js. This generates a DesignSystemState object containing immutable Map instances for colors, typography, rounded corners, spacing, and component definitions.

3. Component Serialization

Because components are stored as Map<string, ComponentDef>, the helper serializeComponents (located in packages/cli/src/commands/diff.ts) converts these complex objects into plain JavaScript objects. This normalization allows components to be compared using the same logic as primitive tokens.

4. Structural Map Diffing

The core comparison logic resides in diffMaps within packages/cli/src/utils.ts. This utility walks the before and after maps simultaneously, returning three arrays:

  • added — keys present only in the after file
  • removed — keys present only in the before file
  • modified — keys present in both but with differing values

5. Report Generation

The command aggregates results into a JSON structure containing:

  • tokens: Per-category diffs for colors, typography, rounded, and spacing
  • findings: Lint summary for each file plus the delta of errors and warnings
  • regression: A boolean flag set to true when the after file contains more errors or warnings than the before file

6. Output Formatting

The formatOutput utility in packages/cli/src/utils.ts renders the report as either JSON (default) or Markdown (--format markdown), making it suitable for both automated parsing and human review.

7. Exit Code Signaling

The CLI exits with code 1 when a regression is detected, otherwise 0. This behavior enables seamless integration with CI pipelines and pre-commit hooks.

CLI Usage Examples

The most common way to compare two DESIGN.md files is via the command line interface registered in packages/cli/src/index.ts.

Generate a JSON Diff Report

design.md diff path/to/before.DESIGN.md path/to/after.DESIGN.md \
  --format json > diff-report.json

Generate a Markdown Report for PR Comments

design.md diff path/to/before.DESIGN.md path/to/after.DESIGN.md \
  --format markdown > diff-report.md

Both commands print the diff to stdout and return exit code 1 if the after file introduces new lint errors or warnings, indicating a regression.

Programmatic API for Custom Workflows

You can replicate the diff logic in Node.js scripts by importing the internal utilities. This approach is useful for custom reporting or integration with existing build tools.

import { readInput } from 'design.md/packages/cli/src/utils.js';
import { lint } from 'design.md/packages/cli/src/linter/index.js';
import { diffMaps } from 'design.md/packages/cli/src/utils.js';
import { serializeComponents } from 'design.md/packages/cli/src/commands/diff.js';

// Load files
const beforeText = await readInput('before.DESIGN.md');
const afterText  = await readInput('after.DESIGN.md');

// Parse into design system models
const beforeReport = lint(beforeText);
const afterReport  = lint(afterText);

// Compare token categories
const tokenDiff = {
  colors:     diffMaps(beforeReport.designSystem.colors,     afterReport.designSystem.colors),
  typography: diffMaps(beforeReport.designSystem.typography, afterReport.designSystem.typography),
  rounded:    diffMaps(beforeReport.designSystem.rounded,    afterReport.designSystem.rounded),
  spacing:    diffMaps(beforeReport.designSystem.spacing,    afterReport.designSystem.spacing),
};

// Compare components (requires serialization)
tokenDiff.components = diffMaps(
  serializeComponents(beforeReport.designSystem.components),
  serializeComponents(afterReport.designSystem.components),
);

console.log(JSON.stringify(tokenDiff, null, 2));

The serializeComponents helper converts the internal Map<string, ComponentDef> structure into plain objects, enabling the shallow comparison used by diffMaps.

Detecting Regressions in CI/CD

The exit code behavior makes the diff command ideal for automated quality gates. The following bash script demonstrates how to fail a build when design regressions are detected:

#!/usr/bin/env bash
set -euo pipefail

design.md diff "$BASE_DESIGN" "$HEAD_DESIGN" --format json > diff.json

if grep -q '"regression":true' diff.json; then
  echo "🚨 Design regression detected!"
  exit 1
fi

echo "✅ No design regressions."

This script captures the JSON output, checks the regression boolean field, and fails with exit code 1 if the after state degrades design system quality.

Key Implementation Details

According to the source code in google-labs-code/design.md, the diff logic is deliberately map-centric. The linter already represents the design system as immutable Map data structures, so diffMaps performs a shallow JSON-string comparison of values. This architectural choice guarantees that nested property changes—such as a component's border radius value—are detected without requiring a computationally expensive full tree-diff algorithm.

Summary

  • The design.md diff command in packages/cli/src/commands/diff.ts provides native support to compare two DESIGN.md files.
  • Input handling uses readInput from packages/cli/src/utils.ts to load files or stdin.
  • Parsing relies on the lint function in packages/cli/src/linter/index.js to generate DesignSystemState models.
  • Comparison utilizes diffMaps in packages/cli/src/utils.ts to identify added, removed, and modified tokens and components.
  • Component handling requires serializeComponents to normalize Map<string, ComponentDef> into comparable plain objects.
  • Regression detection compares lint error/warning counts and sets exit code 1 when quality degrades.
  • Output formats include JSON for automation and Markdown for human-readable reports.

Frequently Asked Questions

What exit code does the diff command return when a regression is detected?

The CLI exits with code 1 when the after file contains more lint errors or warnings than the before file, and 0 otherwise. This behavior is implemented in packages/cli/src/commands/diff.ts and enables straightforward integration with CI pipelines that expect non-zero exit codes on failure.

How does the diff command handle component definitions?

Components are stored internally as Map<string, ComponentDef> objects. The serializeComponents helper function in packages/cli/src/commands/diff.ts converts these maps into plain JavaScript objects before comparison. This allows the generic diffMaps utility to detect changes to component properties using shallow value comparison.

Can I use the diff functionality programmatically without the CLI?

Yes. You can import readInput, lint, diffMaps, and serializeComponents from their respective source files in packages/cli/src/ to build custom comparison workflows. This approach lets you process DESIGN.md diffs within Node.js scripts while maintaining full control over output formatting and error handling.

What format options are available for the diff output?

The formatOutput utility in packages/cli/src/utils.ts supports two formats: JSON (default) for machine-readable structured data, and Markdown (--format markdown) for human-readable reports suitable for pull request comments or documentation. Both formats include the same underlying data: token diffs, lint findings, and the regression boolean.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →