Adopting dspack for an existing design system

August 10, 2026 ยท View on GitHub

You have a design system โ€” components in code, tokens, house rules, opinions earned the hard way. This guide walks the complete path from that starting point to a locally validated dspack contract that AI agents can query (ds-mcp), generate under (dspack-gen), and render from (dspack-emit).

Every step is labeled with one of three modes, and the labels are the point:

LabelMeaning
๐Ÿค– AutomatedA tool does this. Run the command.
๐Ÿ‘€ Human reviewA machine produced output; a person confirms it is true.
โš–๏ธ Design decisionNobody can extract this. It encodes a choice your team made. No tool in this ecosystem will pretend otherwise.

The honest summary up front: the machine-extractable half of a contract (tokens, component props, bindings, themes, layout) is steps 1โ€“3 and takes minutes. The valuable half โ€” the governance that makes an agent behave like someone who has read your guidelines โ€” is steps 4โ€“9, and it is authored, not extracted. That is by design: rules carry rationales, and rationales are decisions. (DESIGN.md explains why the format draws the line there.)

The journey at a glance

#StepMode
1Extract a snapshot from your codebase๐Ÿค– Automated
2Set the version to the current spec โ€” retired; snapshots declare it๐Ÿค– Automated
3Review the snapshot against reality๐Ÿ‘€ Human review
4Author component guidanceโš–๏ธ Design decision
5Define categories and tag componentsโš–๏ธ Design decision
6Record patterns and anti-patternsโš–๏ธ Design decision
7Name your intentsโš–๏ธ Design decision
8Write rulesโš–๏ธ Design decision
9Write worked examples and cross-link themโš–๏ธ Design decision (machine-validated)
10Validate the contract๐Ÿค– Automated
11Serve it to agents๐Ÿค– Automated
12Generate, lint, and render๐Ÿค– Automated (rendering profile: โš–๏ธ)

Step 1 assumes a React + Tailwind/shadcn or Vue 3 + Vuetify 3 codebase (what dspack-export currently supports). For any other stack, skip to Writing the snapshot by hand โ€” a valid document needs only dspack and name, and the shadcn reference contract was written entirely by hand.


Phase 1 โ€” the snapshot (minutes)

Step 1 ยท Extract a snapshot โ€” ๐Ÿค– Automated

npm install -g @aestheticfunction/dspack-export

cd /path/to/your/design-system
dspack-export init        # detects conventions, writes dspack-export.config.json
dspack-export generate --config dspack-export.config.json

(Installing from a source clone โ€” npm install && npm run build && npm link in a checkout โ€” works identically.)

You get a <name>.dspack.json (named from your package.json name, or a placeholder if there isn't one) containing everything observable from source: components with props (cva variant enums and defaults on React; defineProps/emits/slots on Vue), tokens from CSS custom properties or an imported DTCG token file, themes (dark-mode overrides), layout (breakpoints), frameworkBindings (import paths), and metadata recording what generated it. If your tokens live in Figma or Tokens Studio, export them to a DTCG file and point the config's tokens field at it โ€” the import is file-based; no tool integration or network involved.

Three properties of the output matter later:

  • It is deterministic โ€” same input, same bytes โ€” so you can commit it and diff regenerations.
  • It carries a non-semantic metadata["x-bootstrap"] ledger recording which sections the tool generated (with content hashes) and which governance surfaces await your authorship. It affects nothing downstream; once your contract is fully yours, delete it โ€” after which the tool refuses to touch the file at all.
  • Regeneration never destroys your work โ€” guaranteed, not advised. The workflow is: commit the snapshot, then enrich the committed copy. If you run generate against a file containing anything you authored (or any file without the ledger), it refuses, says why, and points you at --out for writing a fresh snapshot elsewhere to compare by hand.

Step 2 ยท Set the version โ€” retired

Nothing to do: snapshots declare the current spec version ("dspack": "0.4") directly. (This step existed when the tool emitted the older v0.2 shape; the number is kept so later steps keep their names.)

Step 3 ยท Review the snapshot โ€” ๐Ÿ‘€ Human review

Extraction is heuristic; you are the ground truth. Read the generated file once, end to end, checking:

  • Props: are enum values and defaults right? cva and docgen catch most cases; wrappers and re-exports can confuse them.
  • Descriptions: they came from your JSDoc. A doc comment written for developers is often wrong as agent guidance ("renders a button" tells an agent nothing about when).
  • Tokens: tier and naming as your team understands them, not just as CSS declared them.
  • What's missing: components the extractor didn't find, deliberately or not. Note them; you can add entries by hand.

Fix what's wrong directly in the file. From here on, the file is yours.


Phase 2 โ€” the governance layer (the real work)

Everything in this phase is a โš–๏ธ design decision. The tooling validates what you write; it never writes it for you. If a step feels like work only your team could do โ€” that's the feature. This is the institutional knowledge ("the old card is deprecated", "modals are for destructive confirmations only") that no artifact records and every new hire absorbs slowly.

A worked example runs through this phase: the shadcn reference contract was authored by hand, incrementally, in this repository's git history โ€” and its arc is the pattern to copy:

  1. c918aa6 โ€” the contract is born as vocabulary only (v0.1: components, tokens, bindings). Useful to agents already, zero governance.
  2. 44f02e2 โ€” a component (table) arrives with a pattern: data-table-with-row-actions, prose guidance about how tables carry actions.
  3. 805732c โ€” "record-collection intent โ€” the data-table pattern's prose becomes law": the prose pattern is promoted into an intent (record-collection), typed rules enforcing what the prose said, and a worked example the rules point at.
  4. f405fce โ€” a fix commit linking an example to the rule that referenced it. (Cross-linking is easy to forget; step 9 covers it and the validator now catches dangling references.)

Prose first, law second. You do not need to write rules on day one โ€” a contract whose governance is three sentences of whenToUse is already ahead of a README nobody pastes into a prompt.

Step 4 ยท Author component guidance โ€” โš–๏ธ

For each component that matters, fill in what the snapshot cannot know:

  • description โ€” rewritten as guidance, not implementation notes.
  • whenToUse / whenNotToUse โ€” the sentences your design reviews repeat.
  • accessibility โ€” role, required attributes, keyboard interactions, label requirements.
  • composition โ€” sub-components, what must/may nest where.
  • constraints โ€” contextual must/should notes with severity.

Field shapes: spec v0.2; working examples of every field: the shadcn contract.

Step 5 ยท Define categories โ€” โš–๏ธ

v0.4 lets the contract define its own taxonomy โ€” dspack bakes none in. Add a top-level categories registry (e.g. interactive, overlay) and tag components with them; rules can then select by category ("no interactive descendants inside a button") instead of enumerating components. While here, tag props with propRole (flag/dimension/choice/slot/handler/content/state) โ€” it tells generation tooling what a prop is for. Spec: v0.4.

Step 6 ยท Patterns and anti-patterns โ€” โš–๏ธ

patterns are preferred combinations ("form fields stack vertically with labels above"); antiPatterns are things your team has deliberately ruled out, each with a reason. Anti-patterns punch above their weight: an agent told what not to do and why stops reproducing the mistake your team retired two years ago. (Id convention: pattern, anti-pattern, component, and category ids are plain kebab-case โ€” bare-input-collection, no prefix; the schema rejects dots. The rule. and ex. prefixes belong to rules and examples only.) These are prose โ€” they are also the raw material rules get promoted from (see the worked example above).

Step 7 ยท Name your intents โ€” โš–๏ธ

An intent is a named kind of surface your system knows how to govern: destructive-action, record-collection, data-collection. Intents scope everything downstream โ€” rules apply to intents; examples belong to intents; callers declare an intent when they ask for generation. Start with the one or two situations where your team has the strongest opinions.

Step 8 ยท Write rules โ€” โš–๏ธ

Rules are typed, deterministic predicates โ€” four types (spec v0.3 ยง5, v0.4 ยง4):

TypeEnforces
component-choicewhich components must / must not appear for an intent
required-compositionstructure every instance of a component must contain
forbidden-compositionstructure and values no instance may contain
required-props (v0.4)named props a component must carry directly

Every rule carries a rationale โ€” shown verbatim to whoever (or whatever) violates it โ€” and a severity (must/should). Write the rationale first; if you cannot say why, it is not yet a rule, it is a habit. Note for required-composition: the required ids must be declared in the contract (as components or sub-components), and a declared id satisfies nothing by itself โ€” the rule checks actual descendants at lint time.

Step 9 ยท Worked examples โ€” โš–๏ธ (machine-validated)

An examples[] entry embeds a complete dspack surface โ€” a small component tree in your contract's vocabulary (surface schema) โ€” showing what good looks like for an intent. They serve double duty: few-shot exemplars for generation, and living proof your rules are satisfiable.

The minimal skeleton โ€” the surface's four top-level fields are required and system must equal your contract's name:

{
  "id": "ex.minimal-signup",
  "intent": "signup-form",
  "description": "The smallest correct signup: framed fields, one primary action.",
  "surface": {
    "dspackSurface": "0.1",
    "system": "your-system-name",
    "intent": "signup-form",
    "root": {
      "component": "card", "id": "signup-card",
      "children": [
        { "component": "input",  "id": "email" },
        { "component": "button", "id": "submit", "text": "Sign up",
          "props": { "variant": "default" } }
      ]
    }
  }
}

Two mechanical requirements the validator enforces:

  • every example surface must pass S1 (surface schema) and S2 (uses only your contract's vocabulary โ€” component ids, props, enum values);
  • cross-link rules and examples (rule.examples: ["ex.your-example"]), and every reference must resolve.

Writing the snapshot by hand

No supported extractor for your stack? Start from { "dspack": "0.4", "name": "your-system" } and add components and tokens as you go โ€” every other block is optional, and the phases above apply unchanged. The shadcn contract is the reference for what each block looks like fully populated.


Phase 3 โ€” validate and use

Step 10 ยท Validate โ€” ๐Ÿค– Automated

npx -p @aestheticfunction/dspack-spec dspack-validate --file your-system.dspack.json

Or, from a checkout of this repository:

git clone https://github.com/aestheticfunction/dspack
cd dspack && npm ci
npm run validate -- --file /path/to/your-system.dspack.json

Both are the same validator โ€” the published bin is a front-end over this repository's harness, never a fork. You get, per document: schema validation for its declared version, back-compat (the additive guarantee), governance consistency (unique ids; intent, component, and example references resolve; every example surface passes S1 + S2), and categories consistency. Fix what it reports; re-run until green.

Step 11 ยท Serve it to agents โ€” ๐Ÿค– Automated

npm install -g @aestheticfunction/ds-mcp
ds-mcp --dspack ./your-system.dspack.json

Connect Claude Code, Cursor, Claude Desktop, or Copilot (client setup) and the agent can query all eleven tools against your contract โ€” components, tokens, patterns, anti-patterns โ€” and lint its own output with validate-ui. The generation tools need the v0.3+ governance blocks from Phase 2.

Step 12 ยท Generate, lint, render โ€” ๐Ÿค– (rendering profile: โš–๏ธ)

Every dspack-gen command takes your contract directly โ€” no registration, no fixtures:

npm install @aestheticfunction/dspack-gen
npx dspack-gen context --dspack ./your-system.dspack.json --intent your-intent
npx dspack-gen lint    --dspack ./your-system.dspack.json --surface ./some.dsurface.json
npx dspack-gen run     --dspack ./your-system.dspack.json --intent your-intent \
    --prompt "..." --model ollama:your-model

context prints the compiled { system, schema, fewshot }; lint runs the S1โ€“S3 gates (exit code 2 = a governance rule fired โ€” your Phase 2 work, executing); run is the full generate โ†’ lint โ†’ repair โ†’ emit pipeline.

To render governed surfaces through dspack-emit (A2UI or json-render), your contract needs a mapping profile โ€” pure data describing how your components correspond to the target protocol's. That is a design-decision-bearing artifact of its own, with its own guide: Writing an emit profile. You author it in your own codebase against the published package โ€” the studio's Astryx profile is the reference for exactly that pattern. To see the whole chain assembled, dspack-studio runs it end to end (hosted replay).


Contributing a contract to the ecosystem (optional)

Everything above serves your use of dspack and touches no ecosystem repository. Contributing a contract as a canonical example โ€” maintained in this repo, byte-synced to consumers, exercised by the eval harness โ€” is a different, heavier path: the contract lands in examples/ here first (upstream-first), consumers re-sync their byte copies, dspack-gen grows pinned fixtures and golden context/violating files, and dspack-emit needs an in-repo profile with regenerated goldens. Open a discussion first if you want that; it is maintainer-coordinated and not required for anything in this guide.

Where this guide ends

This covers the upstream contract lifecycle: code โ†’ snapshot โ†’ governed, validated contract โ†’ served/generated/linted. Emit-profile authoring lives in dspack-emit's guide. Keeping a contract continuously reconciled with design tools is deliberately out of scope (see aesthetic-function, currently paused). Questions the guide doesn't answer are adoption feedback the project wants: open an issue or discussion in this repository.