@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
@@ -0,0 +1,144 @@
1
+ # Doc-writing standard
2
+
3
+ The writing standard for all doc-record (`.doc.json`) content. Applied by the
4
+ authoring pipeline in `component-builder` and `/document-component`. Every
5
+ projection — Figma component description, doc card, Storybook MDX, AI digest —
6
+ inherits this text unchanged; none of them are written separately.
7
+
8
+ **Governing rule: describe the thing and how to use it, never how it was
9
+ made.**
10
+
11
+ ## Register
12
+
13
+ Plain reference: neutral and declarative for descriptions, imperative for
14
+ guidance — the register Polaris and Carbon use.
15
+
16
+ This is **not** the conversational guide voice of `references/guide-voice.md`.
17
+ Guide voice is for skill conversation — a guide talking to the person building
18
+ the system. This standard is for doc-record content — what gets written about
19
+ the component itself, read by whoever uses it later. Different audience,
20
+ different register; do not blend them.
21
+
22
+ ## Per-block rules
23
+
24
+ Before/after pairs are the real Button record
25
+ (`throughline-sample/design-system/docs/components/Button.doc.json`).
26
+
27
+ ### `description`
28
+
29
+ 2–3 sentences: what it is, what it's for, and the one thing that most changes
30
+ how you use it. Never variant/size counts (the specimen and legend already show
31
+ them), never token binding, never a slot inventory.
32
+
33
+ > **Before:** "A clickable control that triggers an action — submitting a
34
+ > form, confirming a decision, or opening a dialog. It comes in six emphasis
35
+ > variants across three sizes, supports optional leading and trailing icons
36
+ > and a loading state, and binds every color, spacing, radius, and type value
37
+ > to the system's semantic tokens."
38
+ >
39
+ > **After:** "A clickable control that starts an action: saving a form,
40
+ > confirming a choice, opening a dialog. Its emphasis levels signal how
41
+ > important an action is."
42
+
43
+ ### `whenToUse` / `whenNotToUse`
44
+
45
+ Situations, never an echo of `summary`. `whenNotToUse` always names the
46
+ alternative.
47
+
48
+ > **Before:** summary "Triggers an action or event." → whenToUse[0] "Trigger
49
+ > an action or event — submit, confirm, open a dialog"
50
+ >
51
+ > **After:** "Something happens on the current page — save, confirm, open a
52
+ > dialog"
53
+
54
+ ### `variants` / `states`
55
+
56
+ Lead with meaning; visual treatment is optional and never the whole entry.
57
+
58
+ > **Before:** "Highest-emphasis, solid brand fill — the one primary action in
59
+ > a view." → **After:** "The one main action in a view."
60
+ >
61
+ > **Before:** "Non-interactive and not focusable; reduced opacity." →
62
+ > **After:** "Can't be clicked or tabbed to."
63
+
64
+ ### `dos` / `donts`
65
+
66
+ Imperative, one action per entry, ≤ 14 words, full stop. Don'ts open with
67
+ *Don't / Never / Avoid* and name the alternative.
68
+
69
+ > **Before:** "Don't use a button for navigation — use a Link" →
70
+ > **After:** "Don't use a button to navigate. Use a Link."
71
+
72
+ ### `accessibility.notes`
73
+
74
+ What the reader must do, not what the framework emits.
75
+
76
+ > **Cut:** "Renders a native `<button>`, so `role=\"button\"` is implicit."
77
+ >
78
+ > **Before:** "An icon-only button needs an aria-label" → **After:** "An
79
+ > icon-only button needs an aria-label so screen readers can announce it."
80
+
81
+ ## Vocabulary
82
+
83
+ Technical terms that are the real names of things stay — `aria-label`, `role`,
84
+ `Enter`, `Space` are what a reader would search for. What is banned from
85
+ user-facing prose is the system's own machinery vocabulary: tokens,
86
+ variables, bindings, fingerprints, provenance, projections, surfaces (in the
87
+ machinery sense). `tokensUsed` keeps its token names — it is a structured
88
+ field, machine-useful, and never rendered as prose.
89
+
90
+ **No inline-code markup.** Write `aria-label`, `Enter`, `role` as plain words.
91
+ One record string is projected to three surfaces that render backticks three
92
+ different ways — MDX styles them, Figma's plain-text `description` strips them,
93
+ and the doc card shows the literal character — so the record carries no
94
+ presentation markup at all. Where formatting is wanted, the projection template
95
+ supplies it: the Storybook MDX wraps variant and state keys in backticks itself.
96
+
97
+ ## Global rules
98
+
99
+ - Full sentences. No em-dash label-fragments bolting a clause onto a fragment.
100
+ - One set of strings for humans and AI — no dual copy. The digest (`llms.txt`
101
+ / `index.json`) inherits the same text.
102
+
103
+ ## The lint — `scripts/docs-lint.mjs`
104
+
105
+ Checks the mechanically reliable subset of this standard. Zero-dependency,
106
+ **warnings only, always exits 0**.
107
+
108
+ | Rule | Checks | Threshold |
109
+ |---|---|---|
110
+ | `machinery-vocabulary` | banned-word list in user-facing prose (all prose fields; `tokensUsed`, `name`, `status`, `provenance` exempt) | — |
111
+ | `summary-echo` | `whenToUse[0]` reusing the summary's content words (stopwords removed, naive plural/verb-s stemming) | > 60% overlap |
112
+ | `run-on-sentence` | sentence length | > 35 words |
113
+ | `summary-length` | `summary` length | > 12 words |
114
+ | `description-length` | `description` length | outside 15–70 words |
115
+ | `guidance-length` | `dos` / `donts` entry length | > 14 words |
116
+ | `dont-shape` | a `donts` entry not opening with *Don't / Never / Avoid* | — |
117
+ | `terminal-stop` | a `dos` / `donts` entry not ending with a full stop | — |
118
+ | `treatment-lead` | a variant/state meaning opening with a visual-treatment word ({fill, filled, solid, stroke, border, bordered, outline, shadow, opacity, elevation}) | first 4 words |
119
+ | `empty-meaning` | a variant or state meaning that's too short to say anything | < 3 words |
120
+ | `no-inline-code` | backticks in user-facing prose (the card renders them literally; Figma strips them from `description`) | — |
121
+
122
+ **Output contract:** always exits 0; one warning per line as
123
+ `<file>: <block-path>: <rule>: <message>`; `--json` emits
124
+ `{"warnings": [{path, rule, message}]}`.
125
+
126
+ **Deliberately not linted: verb-presence.** Not reliably detectable in plain
127
+ JS without an NLP dependency, and a rule that fires wrongly is worse than no
128
+ rule. It stays a prose rule, caught by the authoring pipeline and the user
129
+ approval step.
130
+
131
+ ## Sequencing
132
+
133
+ 1. Draft the record.
134
+ 2. Write the file to disk.
135
+ 3. `node ${CLAUDE_PLUGIN_ROOT}/scripts/docs-lint.mjs <file>`
136
+ 4. Fix warnings.
137
+ 5. Show the user.
138
+
139
+ The lint shapes the draft before approval — it does not nag afterward.
140
+
141
+ `imported` / `user` provenance blocks are never silently rewritten. The lint
142
+ still warns on them; the proposed rewrite is carried into the single
143
+ record-approval gate (shown as before/after, labelled with provenance).
144
+ Blocks the user clears are stamped `imported+user`.
@@ -281,9 +281,28 @@ frames. Applies to `component-builder`, `icon-system-builder`, and
281
281
 
282
282
  ### Every component sits on its own documentation card
283
283
 
284
- Wrap each generated component in a "doc card" — a frame that holds the component
285
- plus a small header. Never leave components floating on bare canvas. The card
286
- shows:
284
+ Wrap each generated component in a "doc card" — a **vertical, three-band
285
+ auto-layout frame**: a **header** band, a **specimen** band (holding the
286
+ component set — the builder's specimen contract is the card's `COMPONENT_SET`,
287
+ not a band name), and a **`Usage`** band holding the documentation body. Never
288
+ leave components floating on bare canvas.
289
+
290
+ **The `Usage` band is never hand-built.** It is rendered by the canonical
291
+ builder snippet in `${CLAUDE_PLUGIN_ROOT}/references/doc-card-builder.md`
292
+ (generated — that file carries the full call contract: the record/fingerprint
293
+ slots, the nine required semantic variables, the `Body/Default` text style, and
294
+ the returned summary you verify and stamp the manifest from). The builder
295
+ computes the card's width from the body type — a column-unit grid whose text
296
+ fills its block, not the card. The column count comes from the record's
297
+ content — the widest row's block count, with a floor of three. It rebuilds
298
+ the `Usage` frame and rewrites the header's short description and date from
299
+ the record; it reads the specimen and never writes it, and the status chip
300
+ keeps its own owner (the finalize write-back below). The header's
301
+ short-description text node is clamped to one column unit wide
302
+ (`summary.columnUnit` from the builder's return), so it never stretches
303
+ across a wide matrix.
304
+
305
+ The header shows:
287
306
 
288
307
  - **Component name** (the deterministic name, matching code).
289
308
  - **Short description** (what it is / when to use it).
@@ -293,9 +312,11 @@ shows:
293
312
  `components.meta[name].status`. **Name the chip frame `Status` and its label
294
313
  text node `Status Label`** so the finalize write-back (below) can find and
295
314
  update them later — a chip with no deterministic name can't be promoted.
296
- - **Last updated** — a date, from `components.meta[name].updatedAt`, refreshed
297
- whenever the component is rebuilt. **Name this text node `Last Updated`** for
298
- the same reason.
315
+ - **Last updated** — a date, from the doc record's `updatedAt` field
316
+ (`record.updatedAt` the single source for the header date; not
317
+ `components.meta[name].updatedAt`, which is separate manifest bookkeeping),
318
+ refreshed whenever the component is rebuilt. **Name this text node
319
+ `Last Updated`** for the same reason.
299
320
 
300
321
  **Always separate the header from the component area with a division element.** The
301
322
  header block (name, description, status, date) and the component/variant area below
@@ -341,8 +362,11 @@ re-describing it:
341
362
  Figma: flip the status chips amber → green and set Last Updated to today.
342
363
  Confirm?"* One confirmation covers the whole batch; don't ask per card.
343
364
  1. Set `components.meta[name].status` to the new status (`stable` on finalize) and
344
- `components.meta[name].updatedAt` to today (ISO date). The manifest is the
345
- source of truth.
365
+ `components.meta[name].updatedAt` to today (ISO date) this is manifest
366
+ bookkeeping, separate from the header date. Also set `updatedAt` to today in
367
+ the component's `.doc.json` record: `record.updatedAt` is the single source
368
+ the doc card's header renders its date from, so this is the write that
369
+ actually moves the date the user sees.
346
370
  2. If Figma is connected (use `figma.mechanism`), locate the component's doc card
347
371
  by its deterministic name (script the write-back per
348
372
  `${CLAUDE_PLUGIN_ROOT}/references/figma-scripting.md` — `getNodeByIdAsync`, and
@@ -351,14 +375,20 @@ re-describing it:
351
375
  - set the `Status Label` text to the new status (e.g. `stable`);
352
376
  - re-bind the `Status` chip fill to the matching semantic color variable
353
377
  (`stable` → success, `draft`/`beta` → warning, `deprecated` → neutral/danger)
354
- — re-bind the variable, don't hardcode a hex, so it stays mode-aware;
355
- - set the `Last Updated` text to today's date.
356
- Then run the visual-validation loop (screenshot → confirm the chip recolored
357
- and the date changed re-screenshot).
378
+ — re-bind the variable, don't hardcode a hex, so it stays mode-aware.
379
+ Then re-run the canonical doc-card builder
380
+ (`${CLAUDE_PLUGIN_ROOT}/references/doc-card-builder.md`) against the same card
381
+ to refresh the header date from the `record.updatedAt` just set in step 1 — the
382
+ builder locates the date node under either header shape (legacy label/value
383
+ frame, or the to-spec `Last Updated` text node) and writes it there, so this
384
+ step never hand-stamps today's date onto a guessed node. Then run the
385
+ visual-validation loop (screenshot → confirm the chip recolored and the date
386
+ changed → re-screenshot).
358
387
  3. If Figma is **not** connected, still do step 1, and tell the user the card will
359
- reconcile to the manifest the next time a Figma session runs (the doc card
360
- always renders from `components.meta[name]`). Offer to reconnect and update it
361
- now if they want it reflected immediately.
388
+ reconcile the next time a Figma session runs its status chip renders from
389
+ `components.meta[name].status` and its header date from `record.updatedAt`,
390
+ both already updated by step 1. Offer to reconnect and update it now if they
391
+ want it reflected immediately.
362
392
 
363
393
  **Icons are the one exception:** the whole icon set lives on a *single* doc card
364
394
  holding the icon grid — one card for all icons, not one card per icon.
@@ -410,6 +440,10 @@ The doc card is a **vertical, top-to-bottom auto-layout** frame
410
440
  overlapping text is almost always absolutely-positioned or mis-sized nodes, and
411
441
  proper auto layout eliminates it.
412
442
 
443
+ The `Usage` band's internal layout (rows, wrapping, block widths) is entirely
444
+ the builder's job — these auto-layout rules apply to the header band and any
445
+ hand-built chrome, not to nodes inside `Usage`.
446
+
413
447
  ### Arrange cards in a parent container (fixes overlapping artboards)
414
448
 
415
449
  Never drop cards onto blank canvas at coordinates that can collide. Place all doc
@@ -486,6 +520,11 @@ For each generated artboard / doc card / icon grid, read the nodes back (via
486
520
  3. **Variables bound** — every fill, stroke, text color, corner radius,
487
521
  `itemSpacing`, and padding resolves to a **bound variable** (`boundVariables`
488
522
  present), not a raw hex/px. No hardcoded values anywhere in the doc-card chrome.
523
+ **Two documented exceptions inside the `Usage` band** (layout chrome, not
524
+ design values, both produced by the builder): the computed column-unit width
525
+ on blocks, and the derived eyebrow type (size/case/tracking/weight). All
526
+ other `Usage` properties — padding, gaps, text colors, dividers — must still
527
+ resolve to bound variables.
489
528
  **Check container and component-set background fills specifically** — a paint bind
490
529
  that didn't stick renders the placeholder color instead (a pure-black placeholder
491
530
  reads as accidental dark mode), so read back `fills[0].boundVariables.color` on
@@ -522,9 +561,17 @@ For each generated artboard / doc card / icon grid, read the nodes back (via
522
561
  be retrofitted to the current recipe (see "State handling").
523
562
  10. **Visual** — the screenshot (from the validation loop) shows no overlaps,
524
563
  misalignment, lopsided hug/fill sizing, or clipped strokes/focus rings.
564
+ 11. **Usage band rendered by the builder** — the card's `Usage` frame was
565
+ created by `renderDocCard`
566
+ (`${CLAUDE_PLUGIN_ROOT}/references/doc-card-builder.md`) in this session, and
567
+ the returned summary matches the record: `rowsRendered` and `blocksCreated`
568
+ line up with the record's populated blocks, `cardWidth` is a whole multiple
569
+ of `columnUnit` (minimum 3), and the frame contains the deterministic names
570
+ (`Usage`, `Usage Row 1..3`, `Block: …`, `Doc Fingerprint`). Verify from the
571
+ summary, not a screenshot. A hand-assembled usage body is a fail.
525
572
 
526
573
  If any item fails, **fix and re-audit** — don't hand off a partial pass. Iterate
527
- with the same ~3-pass budget as the visual loop. Only when all ten pass is the
574
+ with the same ~3-pass budget as the visual loop. Only when all eleven pass is the
528
575
  build done.
529
576
 
530
577
  ## Naming
@@ -0,0 +1,96 @@
1
+ # Guide Voice
2
+
3
+ ThroughLine walks someone through building a design system. That framing is the
4
+ whole product. Every skill should sound like a guide standing next to you, not a
5
+ tool printing status. This file is the source of truth for that voice, and
6
+ specifically for how a flow **closes** — the moment a skill finishes a unit of
7
+ work and hands back to the user.
8
+
9
+ Skills that end a flow point here. Do not restate the rules inline; reference this
10
+ file and follow it.
11
+
12
+ ## The principle
13
+
14
+ A guide has a point of view and shares it. A config wizard lays out options and
15
+ makes you choose. The difference the user feels is whether they leave a step with
16
+ a clear next move or with homework.
17
+
18
+ The failure this corrects: closing a flow with a flat menu of unlike options, each
19
+ ranked for the user to adjudicate, with no recommendation. It reads as cold and it
20
+ pushes the thinking back onto the person you were supposed to be guiding.
21
+
22
+ ## The flow-close pattern
23
+
24
+ When a skill finishes, close in four beats, in prose. Not a table. Not an option
25
+ card. Prose.
26
+
27
+ **1. Outcome.** One line. What got done, and whether it is in sync.
28
+
29
+ > "Button is documented. The canonical record is written, and the Figma
30
+ > description and doc card are both projected and in sync."
31
+
32
+ **2. What I set aside, and why.** Name any gap you deferred, in a clause, with the
33
+ reason. Read existing state to know what is actually outstanding (for the docs
34
+ flow, a component at `status: "draft"` with no code surface in
35
+ `meta[name].doc.surfaces` means the code side is not built yet). Give the reason
36
+ so the deferral reads as a decision, not an omission.
37
+
38
+ > "I left the code-side render for later on purpose. It is a one-time,
39
+ > system-wide scaffold that every future component rides, so it is worth doing
40
+ > deliberately as its own step, not half-built mid-flow."
41
+
42
+ **3. My recommendation.** A short, sequenced, reasoned plan. State it as the
43
+ default. End on one clear next action the user can say yes to.
44
+
45
+ > "My take: document Input and Card next to pressure-test the model on
46
+ > higher-surface-area components, then stand up the code side once, against all
47
+ > three at once. Want me to start on Input?"
48
+
49
+ **4. Or.** At most one genuinely comparable alternative, demoted to a light aside.
50
+ If there is not a real alternative, leave this out entirely. Do not manufacture
51
+ one for symmetry.
52
+
53
+ > "(If you would rather lay the code rails first so everything after Button rides
54
+ > them, say the word and I will do that instead.)"
55
+
56
+ ## The hard rule
57
+
58
+ **Never present a decision point as a grid of co-equal options.** At the close of
59
+ a flow, an `AskUserQuestion`-style option menu is the anti-pattern itself. The
60
+ whole point is that you have a recommendation. Lead with it.
61
+
62
+ Use a real option list only when the choices are genuinely comparable, mutually
63
+ exclusive, and you honestly have no basis to recommend one. That is rare at a flow
64
+ close. It is common mid-flow (which component to build, which framework), and
65
+ there an option list is fine.
66
+
67
+ ## What good looks like, versus what to avoid
68
+
69
+ Avoid:
70
+
71
+ > "Two honest notes. The JSDoc surface is untouched. Nothing is committed. Want me
72
+ > to open a PR, or keep going and document the next batch?"
73
+
74
+ Two unlike options, no recommendation, a respected boundary dressed up as a
75
+ decision. That is the config wizard.
76
+
77
+ Better:
78
+
79
+ > "The JSDoc call was right. Leaving hand-written imported docs untouched is the
80
+ > record model working as designed, not a gap. My take: open the PR now, before
81
+ > the next batch. It is a clean, self-contained unit, and opening it is what
82
+ > actually exercises the docs:check CI gate end-to-end. So: branch, PR, let CI
83
+ > prove the gate, then document the next batch on landed foundations. (If you want
84
+ > to keep momentum, batching the next few first is defensible, you are just
85
+ > trading review clarity for it.)"
86
+
87
+ Same facts. One affirms the boundary instead of dressing it as a choice, collapses
88
+ the fork to one recommended path with the reason, and demotes the alternative to
89
+ an aside.
90
+
91
+ ## Adoption
92
+
93
+ `/document-component` is the first flow to adopt this. Every other flow picks it up
94
+ when it is next touched, not in a speculative sweep. Persisting *why* a stage was
95
+ allowed to advance as structured state is a separate, larger piece (the
96
+ verification proof bundle) and is out of scope here.
@@ -11,11 +11,11 @@ what changed. Gating decisions are made by reading this file: if a prerequisite
11
11
  field is unset, the skill **offers** to run the prerequisite skill rather than
12
12
  bailing or running silently.
13
13
 
14
- ## Schema (schemaVersion 4)
14
+ ## Schema (schemaVersion 6)
15
15
 
16
16
  ```json
17
17
  {
18
- "schemaVersion": 4,
18
+ "schemaVersion": 6,
19
19
  "user": {
20
20
  "codingLevel": "new"
21
21
  },
@@ -42,7 +42,8 @@ bailing or running silently.
42
42
  "coverPageBuilt": false,
43
43
  "canPublish": null,
44
44
  "libraryPublished": false,
45
- "publishedAt": null
45
+ "publishedAt": null,
46
+ "docCardVariables": null
46
47
  },
47
48
  "tokens": {
48
49
  "intakeMode": null,
@@ -87,7 +88,8 @@ bailing or running silently.
87
88
  "ranAt": null,
88
89
  "codeSurface": null,
89
90
  "figmaInventory": null,
90
- "percentSemantic": null
91
+ "percentSemantic": null,
92
+ "docSurface": null
91
93
  },
92
94
  "tokenCrosswalk": {
93
95
  "path": null,
@@ -188,6 +190,16 @@ bailing or running silently.
188
190
  discipline in `${CLAUDE_PLUGIN_ROOT}/references/brownfield-retrofit.md`.**
189
191
  - `publishedAt` — ISO timestamp the user last confirmed a publish, for "you may
190
192
  need to re-publish after adding components" messaging.
193
+ - `docCardVariables` — object mapping the doc-card builder's nine semantic
194
+ variable roles to the variable name each resolves to in this project's file:
195
+ `{ textDefault, textMuted, tonePositive, toneNegative, border, spacePadding,
196
+ spaceRowGap, spaceBlockGap, spaceItemGap }`. `null` until set. Written once,
197
+ on the first doc-card render in a project, and read by every render after
198
+ (see `${CLAUDE_PLUGIN_ROOT}/references/doc-card-builder.md`). Exists because
199
+ the roles are project-agnostic but the variable names are not — an
200
+ unrecorded mapping drifts between renders (different roles resolved to
201
+ different variables each time) and leaves cards visually inconsistent. E.g.
202
+ `{ "textDefault": "color/text/primary", "textMuted": "color/text/secondary", "tonePositive": "color/success/default", "toneNegative": "color/danger/default", "border": "color/border/default", "spacePadding": "space/inset/lg", "spaceRowGap": "space/gap/xl", "spaceBlockGap": "space/gap/lg", "spaceItemGap": "space/gap/sm" }`.
191
203
 
192
204
  ### `tokens`
193
205
  - `intakeMode` — how the user started: `"generative"` (seed expanded by AI),
@@ -249,6 +261,20 @@ bailing or running silently.
249
261
  the manifest and the artboard never disagree. Re-running a component refreshes
250
262
  its `updatedAt`. Keep `built` (names) as the source of truth for "exists";
251
263
  `meta` is supplementary doc metadata.
264
+ - `meta[name].doc` — documentation pointer + per-surface fingerprints for the
265
+ component (v1: components only). **Pointers and hashes, never content** — the
266
+ content lives in `design-system/docs/components/<name>.doc.json`. Shape:
267
+ `{ path, fingerprint, surfaces: { <surfaceName>: { src, render, file?, renderer? } } }`,
268
+ where `fingerprint` is the canonical fingerprint at last render, `src` is the
269
+ canonical fingerprint a surface was rendered from (detects stale), `render` is a
270
+ hash of the surface's rendered content (detects edits, for re-readable surfaces),
271
+ `renderer` (docCard only) is the layout version of the builder that last
272
+ rendered the card (`DOC_CARD_RENDERER_VERSION`); missing/lower is reported by
273
+ `docs:check` as the informational `layout-upgrade-available`, never as drift,
274
+ and `file` is the repo-relative path of a code surface. Written by
275
+ `component-builder` (Figma + card surfaces) and `storybook-chromatic-builder`
276
+ (code surfaces); read by the `docs:check` gate. See
277
+ `${CLAUDE_PLUGIN_ROOT}/references/component-doc-schema.md`.
252
278
  - `instanceSwapUpgradePending` — array of component names whose icon/component
253
279
  slots were built with the **toggle + manual-swap fallback** because the
254
280
  library wasn't published yet, so the typed `INSTANCE_SWAP` dropdown is still
@@ -292,6 +318,11 @@ system so the retrofit can be right-sized.
292
318
  semantic. The single number that decides rename+cleanup vs. rewrite — see
293
319
  `${CLAUDE_PLUGIN_ROOT}/references/brownfield-retrofit.md` (safe sequence, `audit`
294
320
  phase) for how it's used.
321
+ - `docSurface` — object sizing the documentation debt for a brownfield retrofit,
322
+ from verified per-component reads: e.g. `{ "documented": 12, "undocumented": 34,
323
+ "sources": { "codeJsdoc": 8, "mdx": 4, "figmaDescription": 6, "readme": 3 } }`.
324
+ `null` until the audit's documentation-sizing pass runs. Counts come from real
325
+ reads, never assumptions (same discipline as `codeSurface` / `figmaInventory`).
295
326
 
296
327
  ### `tokenCrosswalk`
297
328
  Populated by the `token-crosswalk-builder` skill. Points at the backbone artifact
@@ -307,8 +338,9 @@ that maps new token ↔ old Figma token ↔ code identifier.
307
338
  Populated by the `retrofit-planner` orchestrator. Tracks where a multi-phase
308
339
  retrofit stands so a later session can resume.
309
340
  - `phase` — one of `"audit"`, `"refine"`, `"rebind"`, `"sync"`, `"baseline"`,
310
- `"code"`, `"cleanup"`, `"done"`, or `null` (no retrofit in progress). Phases run
311
- in that order; see the safe sequence in
341
+ `"code"`, `"docs"`, `"cleanup"`, `"done"`, or `null` (no retrofit in progress).
342
+ Phases run in that order; the `docs` phase adopts existing documentation (see
343
+ `retrofit-planner`). See the safe sequence in
312
344
  `${CLAUDE_PLUGIN_ROOT}/references/brownfield-retrofit.md`.
313
345
  - `startedAt` / `completedAt` — ISO timestamps bounding the retrofit.
314
346
  - `journalScaffolded` — whether the `docs/design-system/` decision journal has been
@@ -339,3 +371,11 @@ retrofit stands so a later session can resume.
339
371
  6. **`workspace.origin` is immutable after intake.** Written once by
340
372
  `figma-environment-setup` Step 0 and must not be overwritten by any downstream
341
373
  skill. Skills read it to adapt behavior — they do not modify it.
374
+
375
+ **v4 → v5 migration:** add `audit.docSurface` (default `null`) and the `docs`
376
+ retrofit phase; `components.meta[name].doc` is added lazily per component as docs
377
+ are authored. Bump `schemaVersion` to `5`. No existing field changes.
378
+
379
+ **v5 → v6 migration:** add `figma.docCardVariables` (default `null`), populated
380
+ on the first doc-card render. Bump `schemaVersion` to `6`. No existing field
381
+ changes.
package/scripts/README.md CHANGED
@@ -12,6 +12,31 @@ tested here; copied verbatim by `token-crosswalk-builder` into the user's
12
12
  | `guard-token-removal.mjs` | Grep `.ts/.tsx` (minus generated + tests) for about-to-be-deleted symbols; blocks cleanup until zero references remain. | run during the cleanup phase |
13
13
  | `lib/crosswalk.mjs` | Shared loader + structural validation for `crosswalk.json` (used by the validator and reverse-index). | copied alongside |
14
14
  | `crosswalk.schema.json` | The finalized JSON Schema for `crosswalk.json` (contract + editor support). | copied beside `crosswalk.json` |
15
+ | `build-docs-digest.mjs` | Aggregate every `design-system/docs/components/*.doc.json` into `design-system/docs/index.json` + `llms.txt` for AI/human consumers. | `docs:digest` |
16
+ | `docs-check.mjs` | Drift gate — verifies each component's doc surfaces still match its canonical record (via `lib/doc-record.mjs` fingerprints). Exits 1 on drift. | `docs:check` |
17
+ | `docs-lint.mjs` | Copy lint for .doc.json records — warnings only, always exits 0 on a parseable record; the mechanical subset of `references/doc-writing-standard.md`. | `docs:lint` |
18
+ | `lib/doc-record.mjs` | Canonical record load + `canonicalFingerprint` (sha256 over the record minus `provenance`). The fingerprint every surface is stamped with. | copied alongside docs-check.mjs |
19
+ | `lib/doc-card-render.figma.js` | Figma renderer template for the doc card's `Usage` band and header. Inlined into `references/doc-card-builder.md`; never executed as a module. | plugin-internal (not installed) |
20
+ | `lib/doc-card-plan.mjs` | Pure layout planner for the doc card's `Usage` band + `DOC_CARD_RENDERER_VERSION` (single source of the layout version). Inlined into `references/doc-card-builder.md`; imported by `docs-check.mjs`. | copied alongside docs-check.mjs; also inlined into the generated builder |
21
+ | `build-doc-card-builder.mjs` | Generate `references/doc-card-builder.md` from the planner + the Figma renderer template (`lib/doc-card-render.figma.js`). `--check` gates CI. | plugin-internal (not installed) |
22
+
23
+ **Documentation scripts — install as a set.** Copying these files without
24
+ registering them leaves a repo with a script on disk and no entry point, which
25
+ is how a stale `docs:check` went unnoticed for a full release. Both
26
+ `storybook-chromatic-builder` (first-time setup) and `/document-component`
27
+ (freshness refresh) install the same five files and register the same three
28
+ scripts:
29
+
30
+ | File | npm script |
31
+ | --- | --- |
32
+ | `build-docs-digest.mjs` | `"docs:digest": "node scripts/build-docs-digest.mjs"` |
33
+ | `docs-check.mjs` | `"docs:check": "node scripts/docs-check.mjs"` |
34
+ | `docs-lint.mjs` | `"docs:lint": "node scripts/docs-lint.mjs"` |
35
+ | `lib/doc-record.mjs` | — (imported by the above) |
36
+ | `lib/doc-card-plan.mjs` | — (imported by the above) |
37
+
38
+ A refresh that adds a file must also add its npm script; check `package.json`
39
+ for all three every time, not just the file that changed.
15
40
 
16
41
  The crosswalk contract is documented in
17
42
  `${CLAUDE_PLUGIN_ROOT}/references/crosswalk-schema.md`.
@@ -76,3 +101,12 @@ rewriting `${CLAUDE_PLUGIN_ROOT}` → `.throughline`:
76
101
  npx @radicool/throughline init --target=cursor|codex|generic
77
102
 
78
103
  See `scripts/install.mjs` (pure core + CLI + `install.test.mjs`).
104
+
105
+ ## Documentation scripts
106
+
107
+ `docs:digest` and `docs:check` operate on the folder-resident documentation store
108
+ at `design-system/docs/`. Both share `lib/doc-record.mjs` (record loading +
109
+ fingerprinting). `docs:check` re-reads repo surfaces (Storybook MDX); Figma
110
+ surfaces are marked `edit-unverified` and are checked live by the Figma-connected
111
+ skills. See `${CLAUDE_PLUGIN_ROOT}/references/component-doc-schema.md` for the
112
+ record schema and fingerprint contract.