Sb health
AI agent skill bundle for Storybook on React + Vite — audit real-vs-slop components, capture flows, write CSF3 stories, ship. Claude Code / Codex / Cursor.
npx -y skills add strongeron/storybook-workbench --skill sb-healthAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 18 stars18 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Check 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.
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
9.7 KB, as published. Nobody here has run it
sb-health — design-system health gate
The browser can't shell out, so the script runs the checks and writes JSON the wrapper renders.
design-system-health.json is a rendered output (Health Docs + DesignSystemHealth/TokenMatrix), so
refresh-usage.sh re-runs this check with the other rendered extractors — see CONTEXT.md §STORAGE MAP.
Run it
SKILL=${CLAUDE_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT}}
CORE=${CLAUDE_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT}}
"$SKILL/scripts/validate-design-system.sh" # → .storybook/design-system-health.json
# --emit-prompt also outputs an LLM sub-agent prompt for semantic checks
# (naming drift, semantic-vs-presentational, scale clarity)
"$CORE/scripts/scaffold-wrapper.sh" DesignSystemHealth # → .storybook/wrappers/ (+ icons.tsx)
# Opt-in property→token-family check (off until a designer authors the rules file):
"$SKILL/scripts/check-property-tokens.py" --init # scaffold design-system/lint/colors.json (+ schema)
What it checks
- raw-color — hex/rgba/hsl literals in components (should be tokens). When the literal's value EQUALS
an existing token's resolved value (e.g.
#005596on a tile whose siblings usebg-secondary→--secondaryresolves to#005596), the fix is to adopt that exact token, not invent a new one — the finding names the matching token when it can resolve one, so the fix is "replace with--x", not "add a token". - undefined-token — components reference
--foothat's not declared - scale-gap — spacing/type scale has unexpected jumps
- unused-token — declared but never referenced (var, Tailwind utility, or custom
@utility). Read from sb-inventory's single source (.storybook/project-inventory.json→tokens.map), not re-scanned here, so health, inventory, and the token views never disagree. Runsb-inventoryfirst; if the file is absent this check is skipped. - design-md — a
DESIGN.md(Google Labs YAML-tokens-plus-markdown, an increasingly common way to brief agents on a visual identity) is a claim, not ground truth — likeAGENTS.mdit drifts as the code changes, or was wrong from the start. The script finds it and cross-checks the colors it claims against the colors the code's CSS tokens actually declare, emittingdesign-md-driftfor each claimed color the code doesn't define. Treat a presentDESIGN.mdas untrusted until reconciled. - property-token-family (opt-in) — a valid, declared token used on the wrong property family
(e.g.
color: var(--color-container)— a container token used as text color). raw-color catches "no token at all"; undefined-token catches "token doesn't exist"; this catches "real token, wrong slot." Off until a designer authorsdesign-system/lint/colors.json(see below), so the zero-config default is preserved. Findings arewarning/infoonly — an opt-in never flips a green CI build red. - runs
stylelintif configured
Report summary (errors/warnings/info counts) and the top findings. The DesignSystemHealth
wrapper renders severity with the shared icon set (no emoji).
The JSON it writes (shape — read these fields, don't re-derive). Each finding is
{kind, severity, message, file?, line?, fix?}; summary rolls up the counts:
{
"findings": [
{ "kind": "raw-color", "severity": "error", "file": "Button.tsx", "line": 42, "message": "#3b82f6 literal", "fix": "use --color-primary" },
{ "kind": "undefined-token", "severity": "error", "file": "Card.tsx", "line": 11, "message": "--surface-2 not declared" },
{ "kind": "design-md-drift", "severity": "warning", "message": "DESIGN.md claims #1e90ff; no token declares it" },
{ "kind": "unused-token", "severity": "info", "message": "--legacy-accent declared, never referenced" },
{ "kind": "scale-gap", "severity": "info", "message": "spacing jumps 16px → 48px" },
{ "kind": "property-token-family", "severity": "warning", "file": "Card.css", "line": 3, "message": "background uses --color-content — not allowed for 'background*' (expects --color-container / --color-canvas)", "fix": "Use one of --color-container, --color-canvas, or remap with /* color-lint-alias: … */" }
],
"summary": { "total": 6, "errors": 2, "warnings": 2, "info": 2, "checksRun": ["raw-color", "undefined-token", "..."] }
}
Property→token-family rules (opt-in, designer-owned)
The which-token-family-belongs-on-which-property logic is design intent, so it lives in a designer-editable rules file — not in the script. The designer owns the rules; the dev owns the linter. Scaffold it once (writes the file and a JSON Schema so it's self-documenting in any editor):
"$SKILL/scripts/check-property-tokens.py" --init # → design-system/lint/colors.json (+ colors.schema.json)
// design-system/lint/colors.json — owned by the designer
{
"$schema": "./colors.schema.json",
"propertyTokens": {
"color": ["--color-content"], // exact property
"background*": ["--color-container", "--color-canvas", "--color-backdrop"], // family (trailing *)
"box-shadow": ["--color-border", "--color-shadow"],
"border*": ["--color-border"] // values are token PREFIXES
}
}
Only properties listed are governed (opt in one property at a time). Escape hatches, in order of preference — both are read from your source files:
/* color-lint-alias: --color-theme --color-container */ /* file-level prefix remap: a token is
semantically right but lives in the "wrong"
family — treat this prefix as that one */
color: var(--color-theme-fg); /* color-lint-ignore */ /* per-line suppression — counted, and the
report nudges a refactor past 10. The number
growing is the signal: mute fast, fix slowly. */
The rules file is itself a claim, not ground truth (same stance as DESIGN.md): if a rule allows a
token-prefix that no declared token matches, the script emits property-rules-drift so a stale config
can't silently pass — or silently mis-flag.
Triage order — severity is the verdict, not the count: fix error first (raw-color → map to an
existing token; undefined-token → declare it or fix the typo), reconcile warning (design-md-drift
→ hand to design-md; property-token-family → swap to an allowed token, or if the token is genuinely
right add a color-lint-alias; property-rules-drift → fix the rules file), and report info as-is
(unused-token / scale-gap / property-lint-suppressions are signals to verify, never auto-fixes — see
Never below). A repo with 0 errors and 20 info findings is healthy.
Never (and the non-obvious why)
- NEVER delete an
unused-tokenon sight — it isinfo, noterror. A static scan can't see a token consumed by a Tailwind utility (bg-[--brand]), a runtimevar()built by string concat, or a sibling app in the monorepo. "Unused" means "I didn't find a reference," not "safe to remove." - NEVER treat
scale-gapas a failure to fix — it'sinfo. Real scales have intentional jumps (a display size far above body); "smoothing" them invents tokens nobody asked for and breaks the rhythm a designer chose. - NEVER trust
DESIGN.mdas ground truth — it's a claim, likeAGENTS.md/CLAUDE.md, and drifts. Reconcile againstdesign-md-driftfirst; an un-reconciled DESIGN.md will confidently lie about colors the code doesn't define. - NEVER "fix" a
raw-colorby inventing a new token — map it to an existing token. A fresh token per literal just relocates the mess into the token layer (more undefined-token noise next run). - NEVER hand-recompute this JSON by re-grepping — the browser can't shell out, so the script is the single source; cite its fields. Re-deriving by eye is how the counts drift from what the wrapper renders.
When to defer
For extraction (turning a messy codebase into tokens + components), this skill only measures.
To actually extract, run /ds-runbook → /ds-audit → /ds-token-extract → /ds-component-extract,
then come back here to verify health and to sb-stories for capture.
For a DESIGN.md that drifted (or to author/regenerate one from the code's real tokens), hand
off to the design-md skill — it composes, extracts, and deep-audits the Google Labs DESIGN.md
format. sb-health only flags the drift; design-md reconciles or rewrites the brief.
Next
If health is poor and the project is in transition, fix tokens (or route to ds-* skills) before
authoring stories. Otherwise append a finding and continue to sb-stories.