DESIGN.md CLI Linting Rules: Complete Configuration Guide
The DESIGN.md CLI executes ten fixed linting rules—including checks for broken references, contrast ratios, and orphaned tokens—that are defined in packages/cli/src/linter/rules/index.ts and can only be customized programmatically via the lint(content, options?) function, not through CLI flags.
The google-labs-code/design.md repository provides a TypeScript-based CLI tool for validating DESIGN.md files against design system specifications. While the command-line interface runs a hardcoded set of DESIGN.md CLI linting rules, the underlying API exposes configuration hooks that allow you to override the default rule set or implement custom validation logic.
Default Linting Rules in DESIGN.md CLI
The linter processes ten rules in a specific order determined by the DEFAULT_RULE_DESCRIPTORS constant in packages/cli/src/linter/rules/index.ts. These descriptors are combined into the runnable DEFAULT_RULES array at line 54, which the CLI invokes when you run design-md lint.
Each rule implementation resides in packages/cli/src/linter/linter/rules/ and performs a specific validation:
| Rule | Description | Implementation |
|---|---|---|
| brokenRefRule | Detects broken references to tokens, colors, and other design system elements | broken-ref.ts |
| missingPrimaryRule | Ensures a primary color token is defined | missing-primary.ts |
| contrastCheckRule | Validates contrast ratios for accessibility compliance | contrast-ratio.ts |
| orphanedTokensRule | Flags tokens that are defined but never used | orphaned-tokens.ts |
| tokenSummaryRule | Generates token usage summaries and warns on unexpected token types | token-summary.ts |
| missingSectionsRule | Validates presence of mandatory sections (Overview, Colors, Typography, etc.) | missing-sections.ts |
| missingTypographyRule | Guarantees every referenced typography token exists | missing-typography.ts |
| sectionOrderRule | Enforces canonical ordering of sections and allowed aliases | section-order.ts |
| unknownKeyRule | Flags unknown top-level keys in the DESIGN.md file | unknown-key.ts |
| tokenLikeIgnoredRule | Ignores intentionally placeholder tokens without values | token-like-ignored.ts |
How to Configure DESIGN.md CLI Linting Rules
The CLI command defined in packages/cli/src/commands/lint.ts calls the lint() function without passing an options object, meaning it always runs the full DEFAULT_RULES array. To customize which rules execute, you must use the programmatic API.
Programmatic Configuration via the Lint API
The lint() function exported from packages/cli/src/linter/lint.ts accepts an optional LintOptions object containing a rules array. Pass a filtered subset of DEFAULT_RULES or a custom array to override the default behavior.
import { lint, DEFAULT_RULES } from './packages/cli/src/linter/index.js';
import { missingPrimary } from './packages/cli/src/linter/linter/rules/missing-primary.js';
// Run with the full default set
const report1 = lint(designMdContent);
// Run with a custom subset (e.g., drop the contrast check)
const customRules = DEFAULT_RULES.filter(r => r !== missingPrimary);
const report2 = lint(designMdContent, { rules: customRules });
Creating Custom CLI Wrappers
If you require a bespoke rule set for regular command-line use, create a wrapper script that imports the lint function and passes your own rules array:
#!/usr/bin/env node
import { lint, DEFAULT_RULES } from './packages/cli/src/linter/index.js';
import { unknownKey } from './packages/cli/src/linter/linter/rules/unknown-key.js';
// Only run the "unknown key" rule
const myRules = [unknownKey];
const report = lint(process.argv[2], { rules: myRules });
console.log(JSON.stringify(report, null, 2));
Implementing Custom Lint Rules
Custom rules must implement the LintRule type, receiving a DesignSystemState object and returning an array of Finding objects:
// my-rule.ts
import type { LintRule, DesignSystemState } from '../model/spec.js';
import type { Finding } from '../../linter/spec.js';
export const myRule: LintRule = (state: DesignSystemState): Finding[] => {
// Example: flag any token whose name contains a space
return Object.entries(state.tokens)
.filter(([k]) => k.includes(' '))
.map(([k]) => ({
severity: 'warning',
path: `tokens.${k}`,
message: `Token names should not contain spaces (found "${k}")`,
}));
};
// Usage in your script
import { myRule } from './my-rule.js';
const report = lint(content, { rules: [...DEFAULT_RULES, myRule] });
Running the Built-in CLI
For standard validation using the complete default rule set, invoke the CLI directly:
design-md lint examples/totality-festival/DESIGN.md --format json
This outputs a JSON object containing findings and a summary field with counts of errors, warnings, and infos.
Key Source Files for Linting
packages/cli/src/linter/rules/index.ts– ContainsDEFAULT_RULE_DESCRIPTORSandDEFAULT_RULES(line 54), the master list of default rules.packages/cli/src/linter/linter/rules/– Directory containing individual rule implementations (broken-ref.ts,missing-primary.ts, etc.).packages/cli/src/linter/lint.ts– Exposes the publiclint(content, options?)API.packages/cli/src/commands/lint.ts– CLI entry point that callslint()with no options.packages/cli/src/linter/spec-config.yaml– Defines the DESIGN.md specification schema (not a lint configuration file).
Summary
- The DESIGN.md CLI runs ten fixed linting rules defined in
packages/cli/src/linter/rules/index.tsasDEFAULT_RULE_DESCRIPTORS. - No CLI flags exist for disabling or configuring individual rules; the CLI always runs the full
DEFAULT_RULESarray. - Programmatic customization requires importing the
lint()function frompackages/cli/src/linter/lint.tsand passing arulesarray viaLintOptions. - Custom rules can be implemented by conforming to the
LintRuletype and processing theDesignSystemStateobject. - Rule implementations are located in
packages/cli/src/linter/linter/rules/with filenames matching their functionality (e.g.,contrast-ratio.ts).
Frequently Asked Questions
Can I disable specific rules via CLI flags?
No. The CLI command in packages/cli/src/commands/lint.ts does not expose flags for rule configuration. It always invokes lint(content) with the default options. To run a subset of rules, you must create a custom script that imports the lint function and passes a filtered rules array in the LintOptions object.
How do I add a custom rule to the DESIGN.md linter?
Create a TypeScript file that exports a function conforming to the LintRule type, which accepts a DesignSystemState and returns a Finding[] array. Import this rule into your custom script and pass it to the lint() function via the options.rules array alongside or instead of the default rules.
What is the difference between DEFAULT_RULE_DESCRIPTORS and DEFAULT_RULES?
DEFAULT_RULE_DESCRIPTORS in packages/cli/src/linter/rules/index.ts contains metadata about each rule, while DEFAULT_RULES (constructed at line 54) is the runnable array of rule functions that the linter actually executes. The CLI uses DEFAULT_RULES when you run the lint command.
Where does the lint command output its results?
By default, the CLI outputs validation results to stdout. You can specify the output format using the --format flag (e.g., --format json), which returns a structured JSON object containing a findings array and a summary object with error, warning, and information counts.
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 →