@radicool/throughline 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +1 -1
  2. package/adapters/codex/AGENTS.md +11 -10
  3. package/adapters/codex/prompts/component-builder.md +107 -0
  4. package/adapters/codex/prompts/design-system-audit.md +20 -0
  5. package/adapters/codex/prompts/document-component.md +58 -0
  6. package/adapters/codex/prompts/repository-builder.md +14 -0
  7. package/adapters/codex/prompts/retrofit-planner.md +21 -1
  8. package/adapters/codex/prompts/storybook-chromatic-builder.md +84 -2
  9. package/adapters/cursor/.cursor/commands/document-component.md +58 -0
  10. package/adapters/cursor/.cursor/rules/component-builder.mdc +108 -1
  11. package/adapters/cursor/.cursor/rules/component-pipeline.mdc +1 -1
  12. package/adapters/cursor/.cursor/rules/design-system-audit.mdc +21 -1
  13. package/adapters/cursor/.cursor/rules/figma-environment-setup.mdc +1 -1
  14. package/adapters/cursor/.cursor/rules/icon-system-builder.mdc +1 -1
  15. package/adapters/cursor/.cursor/rules/repository-builder.mdc +15 -1
  16. package/adapters/cursor/.cursor/rules/retrofit-planner.mdc +22 -2
  17. package/adapters/cursor/.cursor/rules/storybook-chromatic-builder.mdc +85 -3
  18. package/adapters/cursor/.cursor/rules/token-builder.mdc +1 -1
  19. package/adapters/cursor/.cursor/rules/token-crosswalk-builder.mdc +1 -1
  20. package/adapters/cursor/.cursor/rules/token-sheet-builder.mdc +1 -1
  21. package/adapters/cursor/.cursor/rules/token-sync-layer.mdc +1 -1
  22. package/adapters/generic/AGENTS.md +11 -10
  23. package/adapters/generic/commands/document-component.md +58 -0
  24. package/adapters/generic/skills/component-builder/SKILL.md +107 -0
  25. package/adapters/generic/skills/design-system-audit/SKILL.md +20 -0
  26. package/adapters/generic/skills/repository-builder/SKILL.md +14 -0
  27. package/adapters/generic/skills/retrofit-planner/SKILL.md +21 -1
  28. package/adapters/generic/skills/storybook-chromatic-builder/SKILL.md +84 -2
  29. package/package.json +1 -1
  30. package/references/component-doc-archetypes.md +90 -0
  31. package/references/component-doc-schema.md +154 -0
  32. package/references/doc-card-builder.md +565 -0
  33. package/references/doc-writing-standard.md +144 -0
  34. package/references/figma-component-standards.md +63 -16
  35. package/references/guide-voice.md +96 -0
  36. package/references/manifest-schema.md +46 -6
  37. package/scripts/README.md +34 -0
  38. package/scripts/build-doc-card-builder.mjs +143 -0
  39. package/scripts/build-docs-digest.mjs +74 -0
  40. package/scripts/docs-check.mjs +117 -0
  41. package/scripts/docs-lint.mjs +163 -0
  42. package/scripts/install.mjs +13 -1
  43. package/scripts/lib/doc-card-plan.mjs +101 -0
  44. package/scripts/lib/doc-card-render.figma.js +371 -0
  45. package/scripts/lib/doc-record.mjs +54 -0
@@ -1,6 +1,7 @@
1
1
  # Retrofit planner (orchestrator)
2
2
 
3
- Sequences a brownfield retrofit through the safe 7-phase order, gating each phase on a
3
+ Sequences a brownfield retrofit through the safe seven-phase order (with `docs` inserted
4
+ as a gated Phase 6.5), gating each phase on a
4
5
  human confirmation. Like `component-pipeline`, this skill holds **zero domain logic of
5
6
  its own** — it is a sequencer that invokes the real skills and the phase work, and only
6
7
  updates the manifest fields it owns (`retrofit.*`, `completedSkills`). All the
@@ -102,6 +103,25 @@ triad as you go — `check-types`, `build-storybook` + Chromatic, **and run the
102
103
  + spot-check 5–7 routes (the build alone is blind to story-unreachable SCSS). **Gate:**
103
104
  confirm the triad passes before continuing.
104
105
 
106
+ ### Phase 6.5 — `docs` (adopt existing documentation, then fill gaps)
107
+
108
+ Set `retrofit.phase = "docs"`. Bring the documentation layer onto the system's
109
+ components **adopt-first**, so no existing human-written doc is lost:
110
+
111
+ 1. **Adopt.** For each component, run the doc-authoring ingest (Step 4.5 of
112
+ `component-builder`): read existing code JSDoc/MDX/README and Figma
113
+ `description`, seed the canonical `.doc.json` marked `provenance: imported`, and
114
+ stamp fingerprints. This first pass **claims** existing content — it is not a
115
+ re-render and must not overwrite it.
116
+ 2. **Fill gaps.** Run the remaining generation layers (infer → enrich → specialize
117
+ → interview) only for blocks the adoption did not populate; the user approves.
118
+ 3. **Project + gate.** Render the code surfaces (Step 5.5 of
119
+ `storybook-chromatic-builder`), run `docs:digest`, and run `docs:check` — it
120
+ should pass (surfaces just rendered) with Figma surfaces `edit-unverified`.
121
+
122
+ Confirm with the user before writing, consistent with every other phase. On a large
123
+ system, size the batch from `audit.docSurface` and adopt in reviewable chunks.
124
+
105
125
  ### Phase 7 — `cleanup`
106
126
 
107
127
  Remove the old token outputs **only after** the repo-wide token-removal guard returns
@@ -27,6 +27,17 @@ system). Wire it to consume `packages/tokens` output so stories render with the
27
27
  real design tokens (import the generated CSS/theme). Checkpoint: confirm
28
28
  Storybook runs and shows the token-themed canvas.
29
29
 
30
+ Install the documentation scripts alongside the token scripts: copy the five
31
+ files and register the three npm scripts listed under **Documentation scripts —
32
+ install as a set** in `.throughline/scripts/README.md`. That table is
33
+ the single source of truth for what a consuming repo gets; do not restate the
34
+ list here.
35
+
36
+ These are the documentation analog of `tokens:validate`; see
37
+ `.throughline/scripts/README.md`. This copy is setup, not a forever-fork:
38
+ `/document-component` re-checks these files' freshness on every run and refreshes
39
+ them from the plugin when it has moved on.
40
+
30
41
  **pnpm build-script allowlist (first-run gotcha).** On pnpm workspaces, the
31
42
  `@storybook/react-vite` install pulls `esbuild`, whose postinstall is blocked by
32
43
  pnpm's default `onlyBuiltDependencies` policy — Storybook then fails to start with a
@@ -149,6 +160,43 @@ Configure Chromatic to snapshot everything (do **not** set `onlyChanged`), and
149
160
  verify a token-only PR re-snapshots all stories — they should flip orange against
150
161
  the green baseline.
151
162
 
163
+ ### Usage & cost guardrails
164
+
165
+ The full-snapshot default above is the right call for catching regressions, and it
166
+ is also the **maximum-usage** choice: every story, every run. Chromatic bills per
167
+ snapshot, so name this cost shape to the user when you set Chromatic up, and put the
168
+ guardrails in *before* the first big token PR, not after the bill.
169
+
170
+ What Chromatic actually offers (re-verify the live numbers at
171
+ `chromatic.com/pricing` — they drift):
172
+
173
+ - **Free plan (~5,000 snapshots/month):** testing **auto-pauses** when the ceiling
174
+ is hit. No surprise bill, but visual-regression coverage silently *stops* until
175
+ the monthly reset or an upgrade. For a full-suite design system that ceiling
176
+ arrives fast — treat a *paused* build as a red flag, not a passing one.
177
+ - **Paid plans: no hard spending cap.** Overage snapshots auto-bill at month-end.
178
+ The only native guardrail is **usage alerts** — an email when consumption crosses
179
+ a threshold you set (e.g. 90%).
180
+
181
+ The math, so the user sizes the plan honestly: **snapshots ≈ stories × modes ×
182
+ builds.** One `/sync-figma-tokens` PR re-snapshots the *entire* suite × every mode
183
+ in a single build — e.g. 40 components × 2 modes = 80 snapshots per build, and a
184
+ handful of token PRs plus daily `main` builds clears a free tier in a week.
185
+
186
+ So the guardrails, all of them user-controlled (Chromatic will not cap you):
187
+
188
+ 1. **Set usage alerts** at ~80% on a paid plan so the bill cannot sneak up. On the
189
+ free plan, make sure the user knows testing *pauses* at the ceiling.
190
+ 2. **Scope the CI trigger.** Run Chromatic on **pull requests and `main` only** —
191
+ never on every branch push — path-filter out docs-only changes, and keep it to
192
+ one Chromatic build per commit (no duplicate runs).
193
+ 3. **Size the plan to the math** before the first token PR. A large story count is
194
+ also the *only* reason to revisit TurboSnap (see above), and even then treat
195
+ every token change as a full run.
196
+
197
+ Do not silently pick a plan or leave the trigger wide open. Name the tradeoff and
198
+ let the user choose with the numbers in front of them.
199
+
152
200
  ## Step 5 — Code Connect (plan-gated, skip gracefully)
153
201
 
154
202
  Code Connect ties Figma components to their code counterparts so Figma's dev
@@ -170,6 +218,32 @@ mode shows the real code. It's plan-gated (Figma Organization/Enterprise).
170
218
  run after the user publishes. This does **not** block the code side: implement
171
219
  each slot prop from the recorded slot contract regardless of the Figma dropdown.
172
220
 
221
+ ## Step 5.5 — Render documentation to code
222
+
223
+ For each component that has a canonical record
224
+ (`design-system/docs/components/<Name>.doc.json`), render the code-side surfaces
225
+ from it (read `.throughline/references/component-doc-schema.md` for the
226
+ projection contract):
227
+
228
+ - **Storybook autodocs (MDX).** Generate `<Name>.mdx` next to the component (e.g.
229
+ `packages/ui/src/<Name>/<Name>.mdx`) rendering summary, description,
230
+ when-to-use/not, do's/don'ts, accessibility, and a variant/state table. Put the
231
+ record's fingerprint in MDX frontmatter as `docFingerprint: <fp>`.
232
+ - **JSDoc.** Add a doc comment to the code component from `summary` + `description`
233
+ and per-prop descriptions from `variants`/`states` meanings, so `argTypes`
234
+ descriptions surface in the Storybook controls table.
235
+ - **AI digest.** Run `docs:digest` to (re)generate `design-system/docs/index.json`
236
+ + `design-system/docs/llms.txt` from all records.
237
+
238
+ **Update the manifest:** add the `storybookMdx` surface to
239
+ `components.meta[<Name>].doc.surfaces` as `{ src: <fp>, render: <hash of the MDX
240
+ file>, file: "<repo-relative MDX path>" }`.
241
+
242
+ **Wire the gate.** Ensure `docs:check` is part of the repo's verification (a CI
243
+ step and/or a Turbo task). It compares every surface against its record and exits
244
+ non-zero on drift; Figma surfaces report `edit-unverified` (checked live in a Figma
245
+ session). Run `docs:check` once here and confirm it passes before handing off.
246
+
173
247
  ## Step 6 — Finalize component status (Figma write-back)
174
248
 
175
249
  A component built and storied here is now **done** — but its Figma doc card was
@@ -193,10 +267,15 @@ the `figma_execute` scripting gotchas — `getNodeByIdAsync` and an explicit
193
267
 
194
268
  - Set `components.meta[name].status` = `"stable"` and refresh
195
269
  `components.meta[name].updatedAt` to today.
270
+ - When promoting status (e.g. draft → stable), also set `status` + `updatedAt` in
271
+ the component's `.doc.json`, recompute its fingerprint, re-run `docs:digest`, and
272
+ re-render the affected surfaces so `docs:check` stays green.
196
273
  - If Figma is connected (per `figma.mechanism`), open the component's doc card and
197
274
  update the `Status Label` text to `stable`, re-bind the `Status` chip fill to
198
- the **success** semantic color variable (mode-aware, not a hardcoded hex), and
199
- set `Last Updated` to today's date then screenshot to confirm the chip
275
+ the **success** semantic color variable (mode-aware, not a hardcoded hex), then
276
+ re-run the canonical doc-card builder against the same card to refresh the
277
+ header date from the `record.updatedAt` already set above (it locates the date
278
+ node under either header shape) — then screenshot to confirm the chip
200
279
  recolored and the date changed.
201
280
  - If Figma isn't connected, still update the manifest and tell the user the card
202
281
  will reconcile next Figma session (or offer to reconnect and fix it now).
@@ -257,6 +336,9 @@ that may not exist.
257
336
  - Never rely on TurboSnap (`onlyChanged: true`) for a token-driven design system
258
337
  — its incremental model keeps missing global token changes. Default to full
259
338
  snapshots (every story, every run); revisit only at large story counts.
339
+ - Never leave Chromatic's cost shape unspoken or the CI trigger wide open — there
340
+ is no hard spend cap on paid plans, so set usage alerts, run it on PRs + `main`
341
+ only, and size the plan to stories × modes × builds before the first token PR.
260
342
  - Never use the sequential model for story-gen — parallelize via subagents.
261
343
  - Never capture the Chromatic baseline *after* a code retrofit — baseline before, so
262
344
  intended drift-fixes are distinguishable from regressions.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@radicool/throughline",
3
- "version": "0.13.0",
3
+ "version": "0.15.0",
4
4
  "description": "Build a complete design system end to end — author in Figma, sync tokens to code, generate Storybook. Usable from Claude Code, Cursor, Codex, or any AGENTS.md agent.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,90 @@
1
+ # Component documentation archetypes
2
+
3
+ The best-practice knowledge layer for the documentation generation pipeline
4
+ (`component-builder` Step: *Author the documentation record*). When authoring a
5
+ component's `.doc.json`, match the component to the nearest **archetype** below and
6
+ seed its `dos`, `donts`, `accessibility`, `whenToUse`, and `whenNotToUse` from that
7
+ entry, then specialize to the target framework and confirm with the user. Stamp
8
+ `provenance` as `best-practice` (or `w3c-apg` for the accessibility block) for
9
+ anything sourced here.
10
+
11
+ These are **seeds, not gospel** — the user's approval and the actual built
12
+ component override them. Sources: W3C ARIA Authoring Practices Guide (roles +
13
+ keyboard), Material 3, Shopify Polaris (content/usage), IBM Carbon (usage).
14
+
15
+ Seeds are clause-style shorthand; the authoring pipeline expands them into full sentences per references/doc-writing-standard.md before they enter a record.
16
+
17
+ ## Button
18
+
19
+ - **whenToUse:** trigger an action or event (submit, confirm, open a dialog).
20
+ - **whenNotToUse:** navigation between pages/URLs (use a Link).
21
+ - **dos:** lead the label with a verb; keep one primary (highest-emphasis) button
22
+ per view; keep labels short (≤ ~3 words).
23
+ - **donts:** don't use a button for navigation (use a Link); don't stack multiple
24
+ primary buttons (keep one per view); don't disable without telling the user why.
25
+ - **accessibility (w3c-apg):** role `button`; Enter and Space activate; an
26
+ icon-only button needs an `aria-label`; disabled buttons are not focusable.
27
+
28
+ ## Input / text field
29
+
30
+ - **whenToUse:** collect a single line of free-form text.
31
+ - **whenNotToUse:** choosing from a fixed set (use Select/Radio); long multi-line
32
+ text (use Textarea).
33
+ - **dos:** always pair with a visible label; show format hints as helper text;
34
+ reserve space for error text to avoid layout shift.
35
+ - **donts:** don't use placeholder text as the only label (pair with a visible
36
+ label); don't validate on every keystroke before first blur.
37
+ - **accessibility (w3c-apg):** every input has a programmatically associated
38
+ `<label>`; error state sets `aria-invalid` and links the message via
39
+ `aria-describedby`.
40
+
41
+ ## Checkbox / radio / toggle
42
+
43
+ - **whenToUse:** checkbox/toggle for independent on/off; radio for one-of-many.
44
+ - **whenNotToUse:** a single either/or action that takes effect immediately with no
45
+ save (prefer a toggle) vs. a form choice (prefer radio/checkbox).
46
+ - **dos:** label the control, not just the group; make the label clickable.
47
+ - **donts:** don't use a radio group for multi-select (use checkboxes); don't use
48
+ a toggle for choices needing a separate Save (use radio/checkbox).
49
+ - **accessibility (w3c-apg):** roles `checkbox` / `radio` / `switch`; Space
50
+ toggles; radio groups navigate with arrow keys; state exposed via
51
+ `aria-checked`.
52
+
53
+ ## Card
54
+
55
+ - **whenToUse:** group related content and actions about a single subject.
56
+ - **whenNotToUse:** primary page layout scaffolding (use a layout/grid component); a
57
+ bare list of text (use a List).
58
+ - **dos:** make the primary action obvious; keep one main call-to-action per card.
59
+ - **donts:** don't nest cards more than one level; don't make the whole card AND an
60
+ inner button separately clickable in conflicting ways.
61
+ - **accessibility:** if the whole card is a link/button, it needs an accessible
62
+ name; don't bury interactive controls that keyboard users can't reach in order.
63
+
64
+ ## Modal / dialog
65
+
66
+ - **whenToUse:** interrupt for a focused task or a decision that blocks the flow.
67
+ - **whenNotToUse:** non-critical messages (use an inline banner or toast).
68
+ - **dos:** trap focus while open; return focus to the trigger on close; provide an
69
+ explicit close affordance.
70
+ - **donts:** don't stack modals; don't put long scrolling forms in a small modal.
71
+ - **accessibility (w3c-apg):** role `dialog` with `aria-modal="true"`; labelled by
72
+ its title (`aria-labelledby`); Escape closes; focus is trapped within.
73
+
74
+ ## Badge / chip / tag
75
+
76
+ - **whenToUse:** short status, count, or category label (badge); a removable/
77
+ selectable item (chip).
78
+ - **whenNotToUse:** interactive primary actions (use a Button).
79
+ - **dos:** keep text to a word or two; match badge color to its semantic meaning
80
+ (success, warning, error).
81
+ - **donts:** don't rely on color alone to convey status (include text or an icon).
82
+ - **accessibility:** a removable chip's remove control needs an accessible name
83
+ (e.g. "Remove <label>"); status conveyed with text, not color only (WCAG 1.4.1).
84
+
85
+ ## Fallback (unlisted archetype)
86
+
87
+ For a component without an entry above: derive `dos`/`donts` from its role and
88
+ built structure, source the `accessibility` block from the matching W3C APG
89
+ pattern, and mark everything for user confirmation. Add a new archetype section
90
+ here once the component's guidance stabilizes.
@@ -0,0 +1,154 @@
1
+ # Component documentation record
2
+
3
+ The canonical, folder-resident source of truth for a component's usage
4
+ documentation. One JSON file per component at
5
+ `design-system/docs/components/<ComponentName>.doc.json`. Every other
6
+ documentation surface (Figma component description, the doc card, Storybook
7
+ autodocs/MDX, the AI digest) is a **projection** rendered from this file — never
8
+ authored independently.
9
+
10
+ It lives in the working folder from the moment a component is built (exactly like
11
+ `design-system.json`), so it exists during the Figma-only *folder* stage, before
12
+ any repo. The path stays `design-system/docs/` across folder→repo; files never
13
+ move.
14
+
15
+ ## Why JSON (not YAML)
16
+
17
+ The plugin's scripts are zero-dependency and there is no YAML parser available, so
18
+ the record is JSON to keep `docs:check` and `docs:digest` able to parse it
19
+ deterministically. JSON is equally machine-legible for AI consumers.
20
+
21
+ ## Schema
22
+
23
+ ```json
24
+ {
25
+ "name": "Button",
26
+ "summary": "Triggers an action or event.",
27
+ "description": "A clickable control that initiates an action…",
28
+ "whenToUse": ["Submitting a form", "Confirming a decision"],
29
+ "whenNotToUse": ["Navigating to a new page — use a Link"],
30
+ "variants": {
31
+ "type": { "primary": "Highest-emphasis action…", "secondary": "…", "ghost": "…" },
32
+ "size": { "sm": "…", "md": "…", "lg": "…" }
33
+ },
34
+ "states": { "hover": "…", "focus": "…", "disabled": "…", "loading": "…" },
35
+ "dos": ["Lead with a verb", "One primary button per view"],
36
+ "donts": ["Don't use for navigation", "Don't stack >2 primaries"],
37
+ "accessibility": {
38
+ "role": "button",
39
+ "keyboard": ["Enter / Space activates"],
40
+ "notes": ["Icon-only buttons need an aria-label"]
41
+ },
42
+ "tokensUsed": ["color.bg.primary", "spacing.sm", "radius.md"],
43
+ "status": "stable",
44
+ "updatedAt": "2026-07-14",
45
+ "provenance": {
46
+ "description": "ai-inferred",
47
+ "dos": "best-practice+user",
48
+ "accessibility": "w3c-apg"
49
+ }
50
+ }
51
+ ```
52
+
53
+ ### Fields (v1 core)
54
+
55
+ - **Required:** `name`, `summary`, `description`.
56
+ - **Optional content:** `whenToUse`, `whenNotToUse`, `variants`, `states`, `dos`,
57
+ `donts`, `accessibility`, `tokensUsed`.
58
+ - **Lifecycle:** `status` (`draft`|`beta`|`stable`|`deprecated`), `updatedAt` (ISO date).
59
+ - **`provenance`** — per-block author source, one of `imported`, `ai-inferred`,
60
+ `best-practice`, `w3c-apg`, `framework`, `user`, or a `+`-joined combination
61
+ (e.g. `best-practice+user`). Regeneration **re-infers** a block whose
62
+ provenance includes `ai-inferred`, `framework`, `best-practice`, or `w3c-apg`,
63
+ and **never overwrites** one whose provenance includes `user` or `imported`.
64
+ Every value is assigned to exactly one of those two tiers: generated content is
65
+ re-inferred, human input (`user`) and pre-existing external content
66
+ (`imported`) are protected. Protection takes precedence: a combination that
67
+ contains both — `best-practice+user`, say — is protected. A block is
68
+ re-inferred only when it carries no `user` or `imported` marker at all. A
69
+ protected block may still be rewritten when the user approves the rewrite at
70
+ the record-approval gate; the result is stamped `imported+user`, which is
71
+ protected from then on and never re-proposed.
72
+
73
+ Deferred to a later version (do not emit in v1): `anatomy`, `content` (writing
74
+ guidelines), `examples`.
75
+
76
+ ## Fingerprint algorithm
77
+
78
+ `fingerprint = sha256(stableStringify(record_without_provenance)).slice(0, 16)`
79
+
80
+ - `provenance` is **excluded** — it is authoring metadata, not projected content.
81
+ - `stableStringify` sorts object keys recursively so formatting/key-order never
82
+ affects the hash.
83
+ - The 16-hex-char result is the stamp recorded per surface and per canonical file.
84
+
85
+ Implemented once in `scripts/lib/doc-record.mjs` (`canonicalFingerprint`) and
86
+ reused by `docs:check`, `docs:digest`, and — for the Figma surfaces — by the
87
+ Figma-connected skill computing the identical hash over the description content.
88
+
89
+ ## Projection mapping
90
+
91
+ | Block(s) | Figma component description | Doc card | Storybook autodocs (MDX) + JSDoc | AI digest |
92
+ |---|---|---|---|---|
93
+ | summary, description | ✔ | ✔ | ✔ | ✔ |
94
+ | whenToUse / whenNotToUse | ✔ | ✔ | ✔ | ✔ |
95
+ | variants, states (meanings) | — | ✔ legend | ✔ argTypes | ✔ |
96
+ | dos / donts | ✔ | ✔ | ✔ | ✔ |
97
+ | accessibility | ✔ | ✔ | ✔ | ✔ |
98
+ | tokensUsed | — | — | ✔ | ✔ |
99
+
100
+ Each surface carries a fingerprint stamp of the record it was rendered from:
101
+ - **Figma component description** — a trailing marker line `<!-- tl:doc <fp> -->`.
102
+ - **Doc card** — a named metadata node `Doc Fingerprint` holding `<fp>`.
103
+ - **Storybook MDX** — a frontmatter field `docFingerprint: <fp>`.
104
+
105
+ ## Manifest pointer (`components.meta[name].doc`)
106
+
107
+ The manifest stores pointers + per-surface fingerprints, never content:
108
+
109
+ ```json
110
+ {
111
+ "doc": {
112
+ "path": "design-system/docs/components/Button.doc.json",
113
+ "fingerprint": "<canonical fingerprint at last render>",
114
+ "surfaces": {
115
+ "figmaDescription": { "src": "<fp>", "render": "<hash of description text>" },
116
+ "docCard": { "src": "<fp>", "render": "<hash of card content>", "renderer": "4" },
117
+ "storybookMdx": { "src": "<fp>", "render": "<hash of mdx file>", "file": "packages/ui/src/Button/Button.mdx" }
118
+ }
119
+ }
120
+ }
121
+ ```
122
+
123
+ - `src` — the canonical fingerprint the surface was rendered from (detects **stale**).
124
+ - `render` — a hash of the surface's rendered content at render time (detects
125
+ **edited**, for surfaces the tooling can re-read).
126
+ - `file` — repo-relative path for code surfaces so `docs:check` can re-read them.
127
+ - `renderer` — (docCard only) the layout version of the builder that last
128
+ rendered the card: `DOC_CARD_RENDERER_VERSION` in `scripts/lib/doc-card-plan.mjs`,
129
+ currently `"4"`. Additive and optional — absence means the card predates the
130
+ versioned builder. Stamped from the builder's returned summary, never by
131
+ re-reading the card.
132
+
133
+ ## Drift + reconciliation contract
134
+
135
+ `docs:check` classifies each surface:
136
+ - **canonical-changed** — the `.doc.json` fingerprint ≠ `doc.fingerprint`.
137
+ - **stale** — `surface.src` ≠ current canonical fingerprint.
138
+ - **edited** — a re-readable surface's current content hash ≠ `surface.render`.
139
+ - **layout-upgrade-available** — informational, never failing, docCard only:
140
+ `surfaces.docCard.renderer` is missing or lower than the current
141
+ `DOC_CARD_RENDERER_VERSION`. The card's content is not in drift — its layout
142
+ predates the current builder. Re-render on next touch (no unprompted Figma
143
+ writes; untouched brownfield cards must not generate a standing warning wall).
144
+ - **missing-surface** — a repo surface that declares a `file` which is now gone.
145
+ Failing, and distinct from `edit-unverified`: the surface *was* re-readable and
146
+ its rendered output has been deleted, not merely unreadable this run.
147
+ - **edit-unverified** — a surface the CLI can't read (Figma); checked live by the
148
+ Figma-connected skill instead.
149
+
150
+ Reconciliation is **per item, reviewable**: for each drift the user chooses
151
+ **re-render** (canonical wins) or **pull-back** (fold the surface edit into the
152
+ record), landed as a PR. **Brownfield first run is an adoption**, not a re-render:
153
+ existing surface content is claimed into the record as `provenance: imported` and
154
+ fingerprints are stamped, rather than treated as `edited` drift.