ds-context

A model misses the design system when the context is wrong: nothing, or the entire Storybook. ds-context turns a local folder into a short pack and shows what each packing strategy costs in tokens.

Why

An agent drifts off the system for two different reasons. Sometimes it receives no spec. Sometimes it receives every token and every example at once. In the first case it invents a Button. In the second it drowns.

ds-context sits in front of the model. It builds the pack you can hand to Claude or Codex and then measure with ds-eval.

Input

The MVP reads a folder. It does not crawl Storybook or call the Figma API. A Figma file arrives as markdown, for example from figma-to-design-md. The Acme fixture is five files:

fixtures/acme/
  tokens.css
  components.json
  patterns.md
  anti-patterns.md
  examples.md
  dist/
    design-system.md
    components.md
    patterns.md
    anti-patterns.md
    examples.md
    manifest.json

tokens.css is the scale a later repair checks against.

:root {
  --color-accent: #8B5CF6;
  --color-text: #212223;
  --color-surface: #ffffff;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --radius-2: 8px;
  --font-size-body: 14px;
}

components.json holds the name, props, example, and what to avoid. The example is copied into the pack only for the full strategy.

[
  {
    "name": "Button",
    "props": ["variant", "size", "children"],
    "example": "<Button variant=\"primary\">Save</Button>",
    "avoid": "A raw <button> with an inline background."
  },
  {
    "name": "Input",
    "props": ["label", "hint", "error"],
    "example": "<Input label=\"Email\" />",
    "avoid": "An unlabeled <input>."
  },
  {
    "name": "Dialog",
    "props": ["open", "title", "onClose"],
    "example": "<Dialog open title=\"Delete account\" />",
    "avoid": "A <div className=\"modal\"> with a hand-rolled overlay."
  },
  {
    "name": "Select",
    "props": ["label", "options", "value"],
    "example": "<Select label=\"Country\" options={countries} />",
    "avoid": "A native <select> when the system Select exists."
  }
]

Three markdown files set the patterns, the bans, and short screen examples.

# Patterns

## Destructive confirm
Ask for confirmation before a delete. Use Dialog, name the object, and make the confirm button the danger variant.

## Empty state
Show one next action. Do not invent a second illustration style.

## Settings
Group fields by task. Use Input with a label. Actions sit in the page footer, not inside the form card.
# Anti-patterns

- Do not hardcode #8B5CF6. Use var(--color-accent).
- Do not build a modal from a positioned div.
- Do not add a button with no accessible name.
- Do not pick 13px of padding. Use the space scale.
# Examples

A settings page uses Input for the name and email, Select for the country, and Button for save.
A delete flow opens Dialog. The confirm action is variant="danger". The close control has an accessible name.
Empty lists use the empty-state pattern: one sentence and one Button.

Run

python3 ds-context/ds_context.py build ds-context/fixtures/acme

The command writes the pack to ds-context/fixtures/acme/dist. Offline. No keys and no model call. On this fixture it prints:

source  ds-context/fixtures/acme
out     ds-context/fixtures/acme/dist
pack    files
        design-system.md   ~ 292 tokens
        components.md      ~ 160 tokens
        patterns.md        ~  87 tokens
        anti-patterns.md   ~  54 tokens
        examples.md        ~  72 tokens
strategies (estimate, ~4 chars/token, manifest excluded)
        full         ~ 665
        compact      ~ 375
        components   ~ 180

Output

design-system.md is the file you can put in context whole. The full strategy inlines patterns, anti-patterns, and examples. The separate files in dist/ repeat the source markdown.

# Design system

Components: Button, Input, Dialog, Select.

## Tokens

| Token | Value |
| --- | --- |
| `--color-accent` | #8B5CF6 |
| `--color-text` | #212223 |
| `--color-surface` | #ffffff |
| `--space-2` | 8px |
| `--space-3` | 12px |
| `--space-4` | 16px |
| `--radius-2` | 8px |
| `--font-size-body` | 14px |

# Patterns

## Destructive confirm
Ask for confirmation before a delete. Use Dialog, name the object, and make the confirm button the danger variant.

## Empty state
Show one next action. Do not invent a second illustration style.

## Settings
Group fields by task. Use Input with a label. Actions sit in the page footer, not inside the form card.

# Anti-patterns

- Do not hardcode #8B5CF6. Use var(--color-accent).
- Do not build a modal from a positioned div.
- Do not add a button with no accessible name.
- Do not pick 13px of padding. Use the space scale.

# Examples

A settings page uses Input for the name and email, Select for the country, and Button for save.
A delete flow opens Dialog. The confirm action is variant="danger". The close control has an accessible name.
Empty lists use the empty-state pattern: one sentence and one Button.

components.md is the API. The example and the Avoid line come from the JSON.

# Component API

### Button

Props: `variant`, `size`, `children`

Example:

```jsx
<Button variant="primary">Save</Button>
```

Avoid: A raw <button> with an inline background.

### Input

Props: `label`, `hint`, `error`

Example:

```jsx
<Input label="Email" />
```

Avoid: An unlabeled <input>.

### Dialog

Props: `open`, `title`, `onClose`

Example:

```jsx
<Dialog open title="Delete account" />
```

Avoid: A <div className="modal"> with a hand-rolled overlay.

### Select

Props: `label`, `options`, `value`

Example:

```jsx
<Select label="Country" options={countries} />
```

Avoid: A native <select> when the system Select exists.

Strategies

The size estimate is about 4 characters per token. It is not the Claude tokenizer. The manifest is excluded from the total. On the Acme fixture:

StrategyEstimateContents
full~665Tokens, API with examples, patterns, anti-patterns, and screen examples
compact~375Tokens, API without examples, and anti-patterns. No separate examples.md
components~180Overview with tokens and the API only. No patterns or examples

Full pack by file:

FileEstimate
design-system.md~292
components.md~160
patterns.md~87
anti-patterns.md~54
examples.md~72

Manifest

The manifest is for the next step in the loop: pick a strategy under a token budget and pass only those files to the model. Component and token names are listed so ui-repair and ds-eval can check against the same source.

{
  "strategy": "full",
  "source": "ds-context/fixtures/acme",
  "components": [
    "Button",
    "Input",
    "Dialog",
    "Select"
  ],
  "tokens": [
    "--color-accent",
    "--color-text",
    "--color-surface",
    "--space-2",
    "--space-3",
    "--space-4",
    "--radius-2",
    "--font-size-body"
  ],
  "files": {
    "design-system.md": 292,
    "components.md": 160,
    "patterns.md": 87,
    "anti-patterns.md": 54,
    "examples.md": 72
  },
  "tokens_total": 665
}

Where it sits

Figma / local spec
→ ds-context
→ model
→ ui-repair
→ ds-eval
→ prompt-regress

ds-context prepares the context. ds-eval measures whether it helped. prompt-regress shows whether a new prompt broke cases that used to pass.

Limit

The tool does not open Storybook or Figma. It does not choose a slice of the pack for one screen: the strategies are global. The token estimate is for comparing packs, not for an API bill.