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

> Easily compare two DESIGN.md files using the design-system CLI diff command. Detect changes and regressions with a structural diff and get an exit code of 1 for errors.

- Repository: [Google Labs Code/design.md](https://github.com/google-labs-code/design.md)
- Tags: how-to-guide
- Published: 2026-07-04

---

**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`](https://github.com/google-labs-code/design.md/blob/main/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`](https://github.com/google-labs-code/design.md/blob/main/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`](https://github.com/google-labs-code/design.md/blob/main/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`](https://github.com/google-labs-code/design.md/blob/main/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`](https://github.com/google-labs-code/design.md/blob/main/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`](https://github.com/google-labs-code/design.md/blob/main/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`](https://github.com/google-labs-code/design.md/blob/main/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`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/index.ts).

### Generate a JSON Diff Report

```bash
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

```bash
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.

```typescript
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:

```bash
#!/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`](https://github.com/google-labs-code/design.md/blob/main/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`](https://github.com/google-labs-code/design.md/blob/main/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`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/utils.ts) to load files or stdin.
- **Parsing** relies on the `lint` function in [`packages/cli/src/linter/index.js`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/index.js) to generate `DesignSystemState` models.
- **Comparison** utilizes `diffMaps` in [`packages/cli/src/utils.ts`](https://github.com/google-labs-code/design.md/blob/main/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`](https://github.com/google-labs-code/design.md/blob/main/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`](https://github.com/google-labs-code/design.md/blob/main/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`](https://github.com/google-labs-code/design.md/blob/main/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.