$npx -y skills add strongeron/storybook-workbench --skill sb-healthCheck design-system health for a React+Vite app — raw hex, undefined/unused tokens, scale gaps, DESIGN.md drift, and (opt-in) property→token-family misuse. Use for 'is my design system healthy', 'find raw colors', 'check tokens', or a mixed design system.
| 1 | # sb-health — design-system health gate |
| 2 | |
| 3 | The browser can't shell out, so the script runs the checks and writes JSON the wrapper renders. |
| 4 | `design-system-health.json` is a **rendered** output (Health Docs + DesignSystemHealth/TokenMatrix), so |
| 5 | `refresh-usage.sh` re-runs this check with the other rendered extractors — see `CONTEXT.md` §STORAGE MAP. |
| 6 | |
| 7 | ## Run it |
| 8 | |
| 9 | ```bash |
| 10 | SKILL=${CLAUDE_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT}} |
| 11 | CORE=${CLAUDE_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT}} |
| 12 | "$SKILL/scripts/validate-design-system.sh" # → .storybook/design-system-health.json |
| 13 | # --emit-prompt also outputs an LLM sub-agent prompt for semantic checks |
| 14 | # (naming drift, semantic-vs-presentational, scale clarity) |
| 15 | "$CORE/scripts/scaffold-wrapper.sh" DesignSystemHealth # → .storybook/wrappers/ (+ icons.tsx) |
| 16 | |
| 17 | # Opt-in property→token-family check (off until a designer authors the rules file): |
| 18 | "$SKILL/scripts/check-property-tokens.py" --init # scaffold design-system/lint/colors.json (+ schema) |
| 19 | ``` |
| 20 | |
| 21 | ## What it checks |
| 22 | |
| 23 | - **raw-color** — hex/rgba/hsl literals in components (should be tokens). When the literal's value EQUALS |
| 24 | an existing token's resolved value (e.g. `#005596` on a tile whose siblings use `bg-secondary` → |
| 25 | `--secondary` resolves to `#005596`), the fix is to **adopt that exact token**, not invent a new one — |
| 26 | the finding names the matching token when it can resolve one, so the fix is "replace with `--x`", not "add a token". |
| 27 | - **undefined-token** — components reference `--foo` that's not declared |
| 28 | - **scale-gap** — spacing/type scale has unexpected jumps |
| 29 | - **unused-token** — declared but never referenced (var, Tailwind utility, or custom `@utility`). |
| 30 | Read from sb-inventory's single source (`.storybook/project-inventory.json` → `tokens.map`), |
| 31 | not re-scanned here, so health, inventory, and the token views never disagree. Run `sb-inventory` |
| 32 | first; if the file is absent this check is skipped. |
| 33 | - **design-md** — a `DESIGN.md` (Google Labs YAML-tokens-plus-markdown, an increasingly common way to |
| 34 | brief agents on a visual identity) is a **claim, not ground truth** — like `AGENTS.md` it drifts as |
| 35 | the code changes, or was wrong from the start. The script finds it and cross-checks the colors it |
| 36 | *claims* against the colors the code's CSS tokens actually *declare*, emitting `design-md-drift` for |
| 37 | each claimed color the code doesn't define. Treat a present `DESIGN.md` as untrusted until reconciled. |
| 38 | - **property-token-family** *(opt-in)* — a *valid, declared* token used on the **wrong property family** |
| 39 | (e.g. `color: var(--color-container)` — a container token used as text color). raw-color catches "no |
| 40 | token at all"; undefined-token catches "token doesn't exist"; this catches "real token, wrong slot." |
| 41 | Off until a designer authors `design-system/lint/colors.json` (see below), so the zero-config default |
| 42 | is preserved. Findings are `warning`/`info` only — an opt-in never flips a green CI build red. |
| 43 | - runs `stylelint` if configured |
| 44 | |
| 45 | Report `summary` (errors/warnings/info counts) and the top findings. The `DesignSystemHealth` |
| 46 | wrapper renders severity with the shared icon set (no emoji). |
| 47 | |
| 48 | The JSON it writes (shape — read these fields, don't re-derive). Each finding is |
| 49 | `{kind, severity, message, file?, line?, fix?}`; `summary` rolls up the counts: |
| 50 | |
| 51 | ```jsonc |
| 52 | { |
| 53 | "findings": [ |
| 54 | { "kind": "raw-color", "severity": "error", "file": "Button.tsx", "line": 42, "message": "#3b82f6 literal", "fix": "use --color-primary" }, |
| 55 | { "kind": "undefined-token", "severity": "error", "file": "Card.tsx", "line": 11, "message": "--surface-2 not declared" }, |
| 56 | { "kind": "design-md-drift", "severity": "warning", "message": "DESIGN.md claims #1e90ff; no token declares it" }, |
| 57 | { "kind": "unused-token", "severity": "info", "message": "--legacy-accent declared, never referenced" }, |
| 58 | { "kind": "scale-gap", "severity": "info", "message": "spacing jumps 16px → 48px" }, |
| 59 | { "kind": "property-token-family", "severity": "warning", "file": "Card.css", "line": 3, "message": "background uses --color-content — not allowed for 'background*' |