Commands
dna start
Extract a design identity from the current codebase and write .dna/identity.yaml.
dna start # extract identity
dna start --force # overwrite an existing identity.yaml
What it extracts:
- CSS custom properties (the palette, with tiers inferred from the token resolution graph)
- Tailwind color tokens and the configured spacing scale — a real
theme.spacing/extend.spacingin yourtailwind.config.*is recorded asscales.spacingScale - shadcn/ui-style HSL-channel tokens (
--primary: 142 71% 29%) — recognized as colors only when real usage corroborates them viahsl(var(--x))somewhere in live source; a bare number triple is never fabricated into a color - Font sizes in use (the type scale), with observed-in-usage values annotated for review — the extractor surfaces, never silently drops
- The spacing grid unit
- Radius scale and idioms
What it writes:
.dna/identity.yaml— the identity with per-value provenanceds-lint.config.json— a starter lint config (if none exists)- An instruction block in
CLAUDE.md/AGENTS.md(if present) telling AI IDEs to call the MCP tools on their own initiative — inventory/similar before building, resolve before writing raw values, check after design-relevant edits
An existing
identity.yamlis never overwritten without--force. It is a record, not derived output — re-extraction without--forceleaves the recorded history intact.
dna check
The conformance gate. Runs lint, cn-merge guard, rendered conformance, and structural manifests.
dna check # diff-default: only changed files
dna check --audit # whole-repo scan
dna check --only=lint # specific layers only
dna check --only=lint,render
Layers:
| Layer | Flag | Requires |
|---|---|---|
| Lint (source) | --only=lint | identity.yaml |
| cn-merge guard | --only=cn | identity.yaml |
| Rendered | --only=render | puppeteer-core peer + Vite |
| Manifests | --only=manifest | .dna/frames/ |
Exit 1 on violations; 0 on clean or nothing-to-check. Tri-state output — a run with nothing to check never renders as a green pass.
dna resolve
Reverse-lookup a raw value to its identity token — the proactive counterpart to dna check. Author in the token vocabulary on the first pass, instead of writing a literal and getting it flagged after the fact.
dna resolve "#2E7D32" # color → token name
dna resolve "rgb(46,125,50)" # same color, same answer
dna resolve 16px # length → token(s)
dna resolve 1rem
Given a color (hex or rgb()/rgba() — equivalent inputs resolve identically) or a length, it prints the matching identity token name(s) labeled by axis. A value that sits on multiple scales (e.g. both a spacing step and a radius step) reports all of them, never just one.
Tri-state and honest:
- A well-formed value that isn't in your identity prints "not a token" — no green tick
- No identity yet → prints the
dna startprompt - Malformed input → a clean error
Detector only — reads the identity, writes nothing.
dna allow
Adopt a deliberate arbitrary value directly into identity.yaml's enforced section — an exception with a reason is a decision; one without is drift.
dna allow spacing 18px --why "hero optical adjustment"
dna allow radius 10px --as chip --why "marketing cards"
dna allow color "#1DA1F2" --as twitter --why "brand-mandated embed"
dna allow type 15px --why "legacy article body"
- Takes effect on the next
dna checkimmediately — nodna startre-run, no hand-editing YAML --whyis required;--as <name>names the value (required forradiusandcolor)- Stamps provenance and appends the decision to
.dna/decisions.jsonl - Survival across re-extraction:
typeandspacingadoptions survivedna start --force;radiusandcolordo not unless the value is also present in source —dna allowtells you which applies when you run it
dna inventory
List every exported component in the configured inventory.dirs — parsed fresh from source on every run.
dna inventory # human-readable output
dna inventory --json # machine-readable JSON
Output:
inventory — 8 component(s). Check here BEFORE building anything new.
Button {variant, size, disabled}
Primary action. Use for all form submissions. · src/components/Button.tsx
Card {title, children, footer}
Container for grouped content. · src/components/Card.tsx
For components, the failure mode is ignorance, not defiance. An AI writes a new Card because it never looked — not because it was told to.
dna inventoryis designed to be read at session start.
Requires the optional typescript peer to parse JSX/TSX components.
dna similar
Two advisory duplication checks over the same structural scorer (role-normalized shape + class shape, asymmetric containment). Diff-default; --audit for the whole tree.
dna similar # changed/untracked files only
dna similar --audit # whole repo
Check 1 — rebuild of an existing component. Changed JSX is scored against the component library: "you rebuilt <Metric> by hand." A rebuild is roughly the component's size and explains most of the candidate's features — caught even when the tag names differ.
Check 2 — clone of a net-new shape. Near-identical subtrees clustered across the scanned files themselves: a shape repeated ≥2× with no backing component — "extract one and reuse it." This is the case rebuild-matching is structurally blind to, since neither copy has a library entry to match against.
similar — 2 findings
rebuild src/screens/Dashboard.tsx:42
→ Card (src/components/Card.tsx) score: 0.91
clone src/screens/Pricing.tsx:18 · src/screens/Landing.tsx:74
→ same net-new shape ×2, no backing component
Both checks are advisory — exit 0, never gating. A finding is information for a human, not a broken build.
Configure in ds-lint.config.json:
{
"similar": {
"threshold": 0.8,
"minEvidence": 8,
"exclude": ["SpecialCard"],
"enabled": true
}
}
dna shapes
Derive a candidate component vocabulary from committed Figma frame dumps, ranked by frequency (rule of three).
dna shapes
Reads .dna/frames/ and extracts recurring structural patterns. Useful for bootstrapping a component inventory from design before writing code.
dna gen-manifest
Generate structural screen manifests from Figma frame dumps for use by dna check --only=manifest.
dna gen-manifest
Reads .dna/frames/ and writes .dna/manifests/. Each manifest describes the expected major sections and their vertical order for a screen.
dna figma diff
Advisory, optional, hermetic structural change detector — answers "what changed structurally in the frames since I last looked?" without touching the network.
dna figma diff --fresh=./scratch-dump # diff every configured screen
dna figma diff --fresh=./scratch-dump home # diff one screen
- Baseline vs fresh. The baseline is the committed dump
gen-manifestalready reads (<cacheDir>/<screen>.figma.json— the last frame state you accepted into git). The fresh dump is one you just produced, passed via--fresh=<dir>. - Hermetic. Pure local dump-vs-dump. No fetching, no credentials, no network-capable import — the same isolation guarantee as
dna check. - Structural only. Reports the
Sec-*section delta per screen — added, removed, and reordered (same set, new y-order). Token-value and shape deltas are deliberately out of scope. - Tri-state, middle never green.
pass(✓ structurally identical) ·no-op(• no baseline or no fresh dump, naming the reason) ·delta(Δ a real change). - Advisory exit convention. A real delta reports and exits
0. The only non-zero exit is a hard error — a malformed dump or missing--fresh.
dna is code-first: a frame change is information, not automatically a code violation. This ships as a reporting command, never a gate.
dna hook install
Install a git pre-commit hook that runs dna check before every commit.
dna hook install
dna hook install --force # replace a pre-commit hook dna didn't write
Writes .git/hooks/pre-commit. A failing check blocks the commit.
It refuses to overwrite a pre-commit hook it didn't write itself. Pass
--forceto replace a foreign hook — its content is lost once you do, so back it up first. Re-running over dna's own prior install always proceeds without--force.
dna mcp
Start the standalone MCP server. See MCP Overview.
dna mcp
dna mcp --cwd=/path/to/project