@radicool/throughline 0.13.0 → 0.14.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 (29) hide show
  1. package/README.md +1 -1
  2. package/adapters/codex/AGENTS.md +2 -1
  3. package/adapters/codex/prompts/component-builder.md +63 -0
  4. package/adapters/codex/prompts/design-system-audit.md +20 -0
  5. package/adapters/codex/prompts/document-component.md +26 -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 +39 -0
  9. package/adapters/cursor/.cursor/commands/document-component.md +26 -0
  10. package/adapters/cursor/.cursor/rules/component-builder.mdc +63 -0
  11. package/adapters/cursor/.cursor/rules/design-system-audit.mdc +20 -0
  12. package/adapters/cursor/.cursor/rules/repository-builder.mdc +14 -0
  13. package/adapters/cursor/.cursor/rules/retrofit-planner.mdc +22 -2
  14. package/adapters/cursor/.cursor/rules/storybook-chromatic-builder.mdc +39 -0
  15. package/adapters/generic/AGENTS.md +2 -1
  16. package/adapters/generic/commands/document-component.md +26 -0
  17. package/adapters/generic/skills/component-builder/SKILL.md +63 -0
  18. package/adapters/generic/skills/design-system-audit/SKILL.md +20 -0
  19. package/adapters/generic/skills/repository-builder/SKILL.md +14 -0
  20. package/adapters/generic/skills/retrofit-planner/SKILL.md +21 -1
  21. package/adapters/generic/skills/storybook-chromatic-builder/SKILL.md +39 -0
  22. package/package.json +1 -1
  23. package/references/component-doc-archetypes.md +86 -0
  24. package/references/component-doc-schema.md +136 -0
  25. package/references/manifest-schema.md +27 -5
  26. package/scripts/README.md +11 -0
  27. package/scripts/build-docs-digest.mjs +74 -0
  28. package/scripts/docs-check.mjs +103 -0
  29. package/scripts/lib/doc-record.mjs +54 -0
@@ -227,6 +227,66 @@ Two rules for every slot:
227
227
  Record each component's slots, variant matrix, and token bindings in the
228
228
  component spec (for Code Connect when available, else the repo component spec).
229
229
 
230
+ ## Step 4.5 — Author the documentation record (and project it)
231
+
232
+ Every component gets a canonical documentation record — the source of truth for
233
+ its usage docs — written to the working folder next to `design-system.json`
234
+ (**folder-resident from day one**, exactly like the manifest; no repo required).
235
+ Read `.throughline/references/component-doc-schema.md` for the exact JSON
236
+ schema, the fingerprint algorithm, and the projection contract.
237
+
238
+ **Run the generation pipeline (each layer only fills what it legitimately knows;
239
+ stamp `provenance` per block):**
240
+
241
+ 0. **Ingest existing docs — brownfield only, runs first.** If docs already exist
242
+ for this component (code JSDoc/MDX/README, or a populated Figma component
243
+ `description`), read them and seed the record marked `provenance: imported`.
244
+ **Never silently overwrite existing human-written docs** — this is the
245
+ read-before-you-assert rule. Skip on greenfield.
246
+ 1. **Infer from the built artifact.** From the component you just built — its
247
+ variants, states, slots, and bound tokens — author `description`, `variants`,
248
+ `states`, and `tokensUsed` (`tokensUsed` comes from the real variable bindings,
249
+ not a guess). Provenance `ai-inferred`.
250
+ 2. **Enrich from the archetype knowledge base.** Match the component to the nearest
251
+ archetype in `.throughline/references/component-doc-archetypes.md` and
252
+ seed `dos`, `donts`, `accessibility`, `whenToUse`, `whenNotToUse`. Provenance
253
+ `best-practice` (or `w3c-apg` for the accessibility block).
254
+ 3. **Specialize to `project.uiFramework`.** Align variant-meaning wording and the
255
+ accessibility idiom to the target framework (the same field you read for variant
256
+ vocabulary). Provenance `framework`.
257
+ 4. **Interview for the non-inferable.** Ask the user for brand/product-specific
258
+ do's & don'ts and intent. Provenance `user`. **Show the whole drafted record and
259
+ get explicit approval before writing anything** — layers 1–4 only fill blocks the
260
+ ingest step did not, and an `imported`/`user` block is never overwritten.
261
+
262
+ **Write the record and project it:**
263
+
264
+ - Write `design-system/docs/components/<Name>.doc.json` (JSON; required fields
265
+ `name`, `summary`, `description`).
266
+ - **Figma component description.** Set the component's native `description` field
267
+ (via `figma_set_description`) to a compact markdown rendering — summary,
268
+ when-to-use/not, do's/don'ts, and the a11y summary — and append a fingerprint
269
+ marker line `<!-- tl:doc <fp> -->` (this is the surface Dev Mode and Code Connect
270
+ read).
271
+ - **Doc card body.** Extend the existing doc card (name/short-desc/status/date, per
272
+ `.throughline/references/figma-component-standards.md`) with a usage body:
273
+ when-to-use, do's/don'ts, an a11y line, and a variant/state legend — all
274
+ token-bound (no hardcoded hex/px). Add a metadata text node named
275
+ `Doc Fingerprint` holding `<fp>`.
276
+ - Compute `<fp>` as the canonical fingerprint defined in the schema reference
277
+ (sha256 of the projected record without `provenance`, first 16 hex chars).
278
+
279
+ **Update the manifest (fields this skill owns):** set
280
+ `components.meta[<Name>].doc` to `{ path, fingerprint: <fp>, surfaces: {
281
+ figmaDescription: { src: <fp>, render: <hash of the description text> }, docCard: {
282
+ src: <fp>, render: <hash of the card body content> } } }`. The code surfaces
283
+ (`storybookMdx`) are added later by `storybook-chromatic-builder`.
284
+
285
+ Run the standard doc-card visual-validation + post-build audit
286
+ (`.throughline/references/figma-component-standards.md`) after enriching
287
+ the card. `docs:check` runs at the code stage; at folder stage the record + Figma
288
+ surfaces are the fallback.
289
+
230
290
  ## Step 5 — Naming as contract
231
291
 
232
292
  Name components deterministically so Figma↔code mapping is automatic: `Button` ↔
@@ -284,4 +344,7 @@ Offer next steps: build the code counterparts and stories
284
344
  internal architecture) **detaches every instance** that referenced its variants (the
285
345
  Card's footer buttons, etc.). Record which components consume which, warn before an
286
346
  architectural rebuild, and re-instance the affected consumers afterward.
347
+ - Never overwrite an existing component `description` or imported doc content
348
+ without reading it first and marking it `provenance: imported` — brownfield docs
349
+ are seeds, not blank slates.
287
350
 
@@ -56,6 +56,23 @@ counts:
56
56
  repo with no Tailwind simply scores `0` there. Detect what the repo actually uses (is
57
57
  there a `tailwind.config`? SCSS? CSS-in-JS?) and explain the counts in those terms.
58
58
 
59
+ ## Step 1.5 — Size the documentation surface
60
+
61
+ Inventory existing documentation the same way the code surface is sized — from
62
+ **verified reads, never assumptions**. Per component (or per code component when no
63
+ Figma component exists yet), record whether usage docs already exist and where:
64
+
65
+ - **Code:** JSDoc/TSDoc on the component, an `.mdx` doc page, a per-component
66
+ README.
67
+ - **Figma:** a populated component `description` field.
68
+
69
+ Write the totals to `audit.docSurface` in the manifest, e.g. `{ "documented": 12,
70
+ "undocumented": 34, "sources": { "codeJsdoc": 8, "mdx": 4, "figmaDescription": 6,
71
+ "readme": 3 } }`. This right-sizes the documentation retrofit (how much exists to
72
+ adopt vs. author from scratch) so `retrofit-planner`'s `docs` phase can be planned
73
+ against real numbers. See `.throughline/references/component-doc-schema.md`
74
+ for what a full record contains.
75
+
59
76
  ## Step 2 — Inventory the Figma file (verified per-class reads)
60
77
 
61
78
  Variables, text styles, and effect/paint styles are **different surfaces** — read each
@@ -103,6 +120,9 @@ Set `tokens.intakeMode: "retrofit"` (this skill establishes the brownfield path
103
120
  owns this transition). Append `design-system-audit` to `completedSkills`.
104
121
 
105
122
  Then recommend the next step:
123
+ - Report the documentation debt from `audit.docSurface` (documented vs.
124
+ undocumented) and note that the retrofit's `docs` phase will adopt existing docs
125
+ before authoring the gaps.
106
126
  - If the user wants the guided, gated end-to-end retrofit → **`retrofit-planner`**
107
127
  (the orchestrator; recommended for multi-session retrofits).
108
128
  - If they only want the crosswalk backbone next → **`token-crosswalk-builder`** (it
@@ -106,6 +106,20 @@ in it" — they already have a working local repo.
106
106
  On success, set `workspace.stage` and `repo.stage` to `github` and record
107
107
  `repo.remote`. Append `repository-builder` to `completedSkills`.
108
108
 
109
+ ### Adopt the documentation store
110
+
111
+ The folder-resident documentation store at `design-system/docs/` (canonical
112
+ `*.doc.json` records plus the generated `index.json` + `llms.txt`) already exists
113
+ from the Figma phase. Bring it under version control as-is — **do not relocate it**
114
+ (the path is stable across folder→repo, and every manifest `doc.path` points at it).
115
+ Ensure it is committed (not git-ignored).
116
+
117
+ Wire the documentation drift gate into the repo's verification so it runs in CI
118
+ alongside `tokens:validate`: add a `docs:check` step (the `docs-check.mjs` script is
119
+ installed by `storybook-chromatic-builder`; if code hasn't been set up yet, note
120
+ that the gate comes online with the Storybook step). `docs:check` exits non-zero on
121
+ drift; Figma surfaces report `edit-unverified` and are checked in a Figma session.
122
+
109
123
  ## Secrets: the part most people have never done
110
124
 
111
125
  This is where users with low coding experience get stuck — many have never made
@@ -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,16 @@ 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 from the
31
+ plugin's `scripts/` — `build-docs-digest.mjs`, `docs-check.mjs`, and
32
+ `lib/doc-record.mjs` — into the repo and register npm scripts):
33
+
34
+ - `"docs:digest": "node scripts/build-docs-digest.mjs"`
35
+ - `"docs:check": "node scripts/docs-check.mjs"`
36
+
37
+ These are the documentation analog of `tokens:validate`; see
38
+ `.throughline/scripts/README.md`.
39
+
30
40
  **pnpm build-script allowlist (first-run gotcha).** On pnpm workspaces, the
31
41
  `@storybook/react-vite` install pulls `esbuild`, whose postinstall is blocked by
32
42
  pnpm's default `onlyBuiltDependencies` policy — Storybook then fails to start with a
@@ -170,6 +180,32 @@ mode shows the real code. It's plan-gated (Figma Organization/Enterprise).
170
180
  run after the user publishes. This does **not** block the code side: implement
171
181
  each slot prop from the recorded slot contract regardless of the Figma dropdown.
172
182
 
183
+ ## Step 5.5 — Render documentation to code
184
+
185
+ For each component that has a canonical record
186
+ (`design-system/docs/components/<Name>.doc.json`), render the code-side surfaces
187
+ from it (read `.throughline/references/component-doc-schema.md` for the
188
+ projection contract):
189
+
190
+ - **Storybook autodocs (MDX).** Generate `<Name>.mdx` next to the component (e.g.
191
+ `packages/ui/src/<Name>/<Name>.mdx`) rendering summary, description,
192
+ when-to-use/not, do's/don'ts, accessibility, and a variant/state table. Put the
193
+ record's fingerprint in MDX frontmatter as `docFingerprint: <fp>`.
194
+ - **JSDoc.** Add a doc comment to the code component from `summary` + `description`
195
+ and per-prop descriptions from `variants`/`states` meanings, so `argTypes`
196
+ descriptions surface in the Storybook controls table.
197
+ - **AI digest.** Run `docs:digest` to (re)generate `design-system/docs/index.json`
198
+ + `design-system/docs/llms.txt` from all records.
199
+
200
+ **Update the manifest:** add the `storybookMdx` surface to
201
+ `components.meta[<Name>].doc.surfaces` as `{ src: <fp>, render: <hash of the MDX
202
+ file>, file: "<repo-relative MDX path>" }`.
203
+
204
+ **Wire the gate.** Ensure `docs:check` is part of the repo's verification (a CI
205
+ step and/or a Turbo task). It compares every surface against its record and exits
206
+ non-zero on drift; Figma surfaces report `edit-unverified` (checked live in a Figma
207
+ session). Run `docs:check` once here and confirm it passes before handing off.
208
+
173
209
  ## Step 6 — Finalize component status (Figma write-back)
174
210
 
175
211
  A component built and storied here is now **done** — but its Figma doc card was
@@ -193,6 +229,9 @@ the `figma_execute` scripting gotchas — `getNodeByIdAsync` and an explicit
193
229
 
194
230
  - Set `components.meta[name].status` = `"stable"` and refresh
195
231
  `components.meta[name].updatedAt` to today.
232
+ - When promoting status (e.g. draft → stable), also set `status` + `updatedAt` in
233
+ the component's `.doc.json`, recompute its fingerprint, re-run `docs:digest`, and
234
+ re-render the affected surfaces so `docs:check` stays green.
196
235
  - If Figma is connected (per `figma.mechanism`), open the component's doc card and
197
236
  update the `Status Label` text to `stable`, re-bind the `Status` chip fill to
198
237
  the **success** semantic color variable (mode-aware, not a hardcoded hex), and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@radicool/throughline",
3
- "version": "0.13.0",
3
+ "version": "0.14.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,86 @@
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
+ ## Button
16
+
17
+ - **whenToUse:** trigger an action or event (submit, confirm, open a dialog).
18
+ - **whenNotToUse:** navigation between pages/URLs — use a Link.
19
+ - **dos:** lead the label with a verb; keep one primary (highest-emphasis) button
20
+ per view; keep labels short (≤ ~3 words).
21
+ - **donts:** don't use a button for navigation; don't stack multiple primary
22
+ buttons; don't disable without telling the user why.
23
+ - **accessibility (w3c-apg):** role `button`; Enter and Space activate; an
24
+ icon-only button needs an `aria-label`; disabled buttons are not focusable.
25
+
26
+ ## Input / text field
27
+
28
+ - **whenToUse:** collect a single line of free-form text.
29
+ - **whenNotToUse:** choosing from a fixed set (use Select/Radio); long multi-line
30
+ text (use Textarea).
31
+ - **dos:** always pair with a visible label; show format hints as helper text;
32
+ reserve space for error text to avoid layout shift.
33
+ - **donts:** don't use placeholder text as the only label; don't validate on every
34
+ keystroke before first blur.
35
+ - **accessibility (w3c-apg):** every input has a programmatically associated
36
+ `<label>`; error state sets `aria-invalid` and links the message via
37
+ `aria-describedby`.
38
+
39
+ ## Checkbox / radio / toggle
40
+
41
+ - **whenToUse:** checkbox/toggle for independent on/off; radio for one-of-many.
42
+ - **whenNotToUse:** a single either/or action that takes effect immediately with no
43
+ save (prefer a toggle) vs. a form choice (prefer radio/checkbox).
44
+ - **dos:** label the control, not just the group; make the label clickable.
45
+ - **donts:** don't use a radio group for multi-select; don't use a toggle for
46
+ choices that only apply after a separate Save.
47
+ - **accessibility (w3c-apg):** roles `checkbox` / `radio` / `switch`; Space
48
+ toggles; radio groups navigate with arrow keys; state exposed via
49
+ `aria-checked`.
50
+
51
+ ## Card
52
+
53
+ - **whenToUse:** group related content and actions about a single subject.
54
+ - **whenNotToUse:** primary page layout scaffolding; a bare list of text.
55
+ - **dos:** make the primary action obvious; keep one main call-to-action per card.
56
+ - **donts:** don't nest cards more than one level; don't make the whole card AND an
57
+ inner button separately clickable in conflicting ways.
58
+ - **accessibility:** if the whole card is a link/button, it needs an accessible
59
+ name; don't bury interactive controls that keyboard users can't reach in order.
60
+
61
+ ## Modal / dialog
62
+
63
+ - **whenToUse:** interrupt for a focused task or a decision that blocks the flow.
64
+ - **whenNotToUse:** non-critical messages (use an inline banner or toast).
65
+ - **dos:** trap focus while open; return focus to the trigger on close; provide an
66
+ explicit close affordance.
67
+ - **donts:** don't stack modals; don't put long scrolling forms in a small modal.
68
+ - **accessibility (w3c-apg):** role `dialog` with `aria-modal="true"`; labelled by
69
+ its title (`aria-labelledby`); Escape closes; focus is trapped within.
70
+
71
+ ## Badge / chip / tag
72
+
73
+ - **whenToUse:** short status, count, or category label (badge); a removable/
74
+ selectable token (chip).
75
+ - **whenNotToUse:** interactive primary actions (use a Button).
76
+ - **dos:** keep text to a word or two; bind color to a semantic tone token.
77
+ - **donts:** don't rely on color alone to convey status — include text/icon.
78
+ - **accessibility:** a removable chip's remove control needs an accessible name
79
+ (e.g. "Remove <label>"); status conveyed with text, not color only (WCAG 1.4.1).
80
+
81
+ ## Fallback (unlisted archetype)
82
+
83
+ For a component without an entry above: derive `dos`/`donts` from its role and
84
+ built structure, source the `accessibility` block from the matching W3C APG
85
+ pattern, and mark everything for user confirmation. Add a new archetype section
86
+ here once the component's guidance stabilizes.
@@ -0,0 +1,136 @@
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 `ai-inferred`/`framework`
62
+ blocks and **never overwrites** a block whose provenance includes `user` or
63
+ `imported`.
64
+
65
+ Deferred to a later version (do not emit in v1): `anatomy`, `content` (writing
66
+ guidelines), `examples`.
67
+
68
+ ## Fingerprint algorithm
69
+
70
+ `fingerprint = sha256(stableStringify(record_without_provenance)).slice(0, 16)`
71
+
72
+ - `provenance` is **excluded** — it is authoring metadata, not projected content.
73
+ - `stableStringify` sorts object keys recursively so formatting/key-order never
74
+ affects the hash.
75
+ - The 16-hex-char result is the stamp recorded per surface and per canonical file.
76
+
77
+ Implemented once in `scripts/lib/doc-record.mjs` (`canonicalFingerprint`) and
78
+ reused by `docs:check`, `docs:digest`, and — for the Figma surfaces — by the
79
+ Figma-connected skill computing the identical hash over the description content.
80
+
81
+ ## Projection mapping
82
+
83
+ | Block(s) | Figma component description | Doc card | Storybook autodocs (MDX) + JSDoc | AI digest |
84
+ |---|---|---|---|---|
85
+ | summary, description | ✔ | ✔ | ✔ | ✔ |
86
+ | whenToUse / whenNotToUse | ✔ | ✔ | ✔ | ✔ |
87
+ | variants, states (meanings) | ✔ compact | ✔ legend | ✔ argTypes | ✔ |
88
+ | dos / donts | ✔ | ✔ | ✔ | ✔ |
89
+ | accessibility | ✔ | ✔ | ✔ | ✔ |
90
+ | tokensUsed | — | — | ✔ | ✔ |
91
+
92
+ Each surface carries a fingerprint stamp of the record it was rendered from:
93
+ - **Figma component description** — a trailing marker line `<!-- tl:doc <fp> -->`.
94
+ - **Doc card** — a named metadata node `Doc Fingerprint` holding `<fp>`.
95
+ - **Storybook MDX** — a frontmatter field `docFingerprint: <fp>`.
96
+
97
+ ## Manifest pointer (`components.meta[name].doc`)
98
+
99
+ The manifest stores pointers + per-surface fingerprints, never content:
100
+
101
+ ```json
102
+ {
103
+ "doc": {
104
+ "path": "design-system/docs/components/Button.doc.json",
105
+ "fingerprint": "<canonical fingerprint at last render>",
106
+ "surfaces": {
107
+ "figmaDescription": { "src": "<fp>", "render": "<hash of description text>" },
108
+ "docCard": { "src": "<fp>", "render": "<hash of card content>" },
109
+ "storybookMdx": { "src": "<fp>", "render": "<hash of mdx file>", "file": "packages/ui/src/Button/Button.mdx" }
110
+ }
111
+ }
112
+ }
113
+ ```
114
+
115
+ - `src` — the canonical fingerprint the surface was rendered from (detects **stale**).
116
+ - `render` — a hash of the surface's rendered content at render time (detects
117
+ **edited**, for surfaces the tooling can re-read).
118
+ - `file` — repo-relative path for code surfaces so `docs:check` can re-read them.
119
+
120
+ ## Drift + reconciliation contract
121
+
122
+ `docs:check` classifies each surface:
123
+ - **canonical-changed** — the `.doc.json` fingerprint ≠ `doc.fingerprint`.
124
+ - **stale** — `surface.src` ≠ current canonical fingerprint.
125
+ - **edited** — a re-readable surface's current content hash ≠ `surface.render`.
126
+ - **missing-surface** — a repo surface that declares a `file` which is now gone.
127
+ Failing, and distinct from `edit-unverified`: the surface *was* re-readable and
128
+ its rendered output has been deleted, not merely unreadable this run.
129
+ - **edit-unverified** — a surface the CLI can't read (Figma); checked live by the
130
+ Figma-connected skill instead.
131
+
132
+ Reconciliation is **per item, reviewable**: for each drift the user chooses
133
+ **re-render** (canonical wins) or **pull-back** (fold the surface edit into the
134
+ record), landed as a PR. **Brownfield first run is an adoption**, not a re-render:
135
+ existing surface content is claimed into the record as `provenance: imported` and
136
+ fingerprints are stamped, rather than treated as `edited` drift.
@@ -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 5)
15
15
 
16
16
  ```json
17
17
  {
18
- "schemaVersion": 4,
18
+ "schemaVersion": 5,
19
19
  "user": {
20
20
  "codingLevel": "new"
21
21
  },
@@ -87,7 +87,8 @@ bailing or running silently.
87
87
  "ranAt": null,
88
88
  "codeSurface": null,
89
89
  "figmaInventory": null,
90
- "percentSemantic": null
90
+ "percentSemantic": null,
91
+ "docSurface": null
91
92
  },
92
93
  "tokenCrosswalk": {
93
94
  "path": null,
@@ -249,6 +250,17 @@ bailing or running silently.
249
250
  the manifest and the artboard never disagree. Re-running a component refreshes
250
251
  its `updatedAt`. Keep `built` (names) as the source of truth for "exists";
251
252
  `meta` is supplementary doc metadata.
253
+ - `meta[name].doc` — documentation pointer + per-surface fingerprints for the
254
+ component (v1: components only). **Pointers and hashes, never content** — the
255
+ content lives in `design-system/docs/components/<name>.doc.json`. Shape:
256
+ `{ path, fingerprint, surfaces: { <surfaceName>: { src, render, file? } } }`,
257
+ where `fingerprint` is the canonical fingerprint at last render, `src` is the
258
+ canonical fingerprint a surface was rendered from (detects stale), `render` is a
259
+ hash of the surface's rendered content (detects edits, for re-readable surfaces),
260
+ and `file` is the repo-relative path of a code surface. Written by
261
+ `component-builder` (Figma + card surfaces) and `storybook-chromatic-builder`
262
+ (code surfaces); read by the `docs:check` gate. See
263
+ `${CLAUDE_PLUGIN_ROOT}/references/component-doc-schema.md`.
252
264
  - `instanceSwapUpgradePending` — array of component names whose icon/component
253
265
  slots were built with the **toggle + manual-swap fallback** because the
254
266
  library wasn't published yet, so the typed `INSTANCE_SWAP` dropdown is still
@@ -292,6 +304,11 @@ system so the retrofit can be right-sized.
292
304
  semantic. The single number that decides rename+cleanup vs. rewrite — see
293
305
  `${CLAUDE_PLUGIN_ROOT}/references/brownfield-retrofit.md` (safe sequence, `audit`
294
306
  phase) for how it's used.
307
+ - `docSurface` — object sizing the documentation debt for a brownfield retrofit,
308
+ from verified per-component reads: e.g. `{ "documented": 12, "undocumented": 34,
309
+ "sources": { "codeJsdoc": 8, "mdx": 4, "figmaDescription": 6, "readme": 3 } }`.
310
+ `null` until the audit's documentation-sizing pass runs. Counts come from real
311
+ reads, never assumptions (same discipline as `codeSurface` / `figmaInventory`).
295
312
 
296
313
  ### `tokenCrosswalk`
297
314
  Populated by the `token-crosswalk-builder` skill. Points at the backbone artifact
@@ -307,8 +324,9 @@ that maps new token ↔ old Figma token ↔ code identifier.
307
324
  Populated by the `retrofit-planner` orchestrator. Tracks where a multi-phase
308
325
  retrofit stands so a later session can resume.
309
326
  - `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
327
+ `"code"`, `"docs"`, `"cleanup"`, `"done"`, or `null` (no retrofit in progress).
328
+ Phases run in that order; the `docs` phase adopts existing documentation (see
329
+ `retrofit-planner`). See the safe sequence in
312
330
  `${CLAUDE_PLUGIN_ROOT}/references/brownfield-retrofit.md`.
313
331
  - `startedAt` / `completedAt` — ISO timestamps bounding the retrofit.
314
332
  - `journalScaffolded` — whether the `docs/design-system/` decision journal has been
@@ -339,3 +357,7 @@ retrofit stands so a later session can resume.
339
357
  6. **`workspace.origin` is immutable after intake.** Written once by
340
358
  `figma-environment-setup` Step 0 and must not be overwritten by any downstream
341
359
  skill. Skills read it to adapt behavior — they do not modify it.
360
+
361
+ **v4 → v5 migration:** add `audit.docSurface` (default `null`) and the `docs`
362
+ retrofit phase; `components.meta[name].doc` is added lazily per component as docs
363
+ are authored. Bump `schemaVersion` to `5`. No existing field changes.
package/scripts/README.md CHANGED
@@ -12,6 +12,8 @@ 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` |
15
17
 
16
18
  The crosswalk contract is documented in
17
19
  `${CLAUDE_PLUGIN_ROOT}/references/crosswalk-schema.md`.
@@ -76,3 +78,12 @@ rewriting `${CLAUDE_PLUGIN_ROOT}` → `.throughline`:
76
78
  npx @radicool/throughline init --target=cursor|codex|generic
77
79
 
78
80
  See `scripts/install.mjs` (pure core + CLI + `install.test.mjs`).
81
+
82
+ ## Documentation scripts
83
+
84
+ `docs:digest` and `docs:check` operate on the folder-resident documentation store
85
+ at `design-system/docs/`. Both share `lib/doc-record.mjs` (record loading +
86
+ fingerprinting). `docs:check` re-reads repo surfaces (Storybook MDX); Figma
87
+ surfaces are marked `edit-unverified` and are checked live by the Figma-connected
88
+ skills. See `${CLAUDE_PLUGIN_ROOT}/references/component-doc-schema.md` for the
89
+ record schema and fingerprint contract.
@@ -0,0 +1,74 @@
1
+ // docs:digest — aggregates every component doc record into two AI-facing
2
+ // artifacts: index.json (machine map) and llms.txt (narrative index).
3
+ // Zero dependencies.
4
+ //
5
+ // Usage: node build-docs-digest.mjs [--root <dir>]
6
+ import { readdirSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import { parseArgs } from 'node:util';
9
+ import { pathToFileURL } from 'node:url';
10
+ import { loadRecord } from './lib/doc-record.mjs';
11
+
12
+ const DOCS_DIR = join('design-system', 'docs');
13
+ const COMPONENTS_DIR = join(DOCS_DIR, 'components');
14
+
15
+ export function buildIndex(records) {
16
+ return {
17
+ generatedFrom: 'design-system/docs/components/*.doc.json',
18
+ components: records.map((r) => ({
19
+ name: r.name,
20
+ summary: r.summary ?? '',
21
+ description: r.description ?? '',
22
+ whenToUse: r.whenToUse ?? [],
23
+ whenNotToUse: r.whenNotToUse ?? [],
24
+ variants: r.variants ?? {},
25
+ states: r.states ?? {},
26
+ dos: r.dos ?? [],
27
+ donts: r.donts ?? [],
28
+ accessibility: r.accessibility ?? {},
29
+ tokensUsed: r.tokensUsed ?? [],
30
+ status: r.status ?? 'draft',
31
+ })),
32
+ };
33
+ }
34
+
35
+ export function buildLlmsTxt(records) {
36
+ const lines = ['# Design system — component usage guide', ''];
37
+ lines.push('Generated documentation for AI and human consumers. One section per component.', '');
38
+ for (const r of records) {
39
+ lines.push(`## ${r.name}`, '');
40
+ if (r.summary) lines.push(r.summary, '');
41
+ if (r.description) lines.push(r.description, '');
42
+ if ((r.whenToUse ?? []).length) lines.push('**When to use:** ' + r.whenToUse.join('; '));
43
+ if ((r.whenNotToUse ?? []).length) lines.push('**When not to use:** ' + r.whenNotToUse.join('; '));
44
+ if ((r.dos ?? []).length) lines.push('**Do:** ' + r.dos.join('; '));
45
+ if ((r.donts ?? []).length) lines.push("**Don't:** " + r.donts.join('; '));
46
+ if ((r.tokensUsed ?? []).length) lines.push('**Tokens:** ' + r.tokensUsed.join(', '));
47
+ lines.push('');
48
+ }
49
+ return lines.join('\n');
50
+ }
51
+
52
+ export function loadAllRecords(root) {
53
+ const dir = join(root, COMPONENTS_DIR);
54
+ if (!existsSync(dir)) return [];
55
+ return readdirSync(dir)
56
+ .filter((f) => f.endsWith('.doc.json'))
57
+ .sort()
58
+ .map((f) => loadRecord(join(dir, f)));
59
+ }
60
+
61
+ function main() {
62
+ const { values } = parseArgs({ options: { root: { type: 'string', default: '.' } } });
63
+ const root = values.root;
64
+ const records = loadAllRecords(root);
65
+ const outDir = join(root, DOCS_DIR);
66
+ if (!existsSync(outDir)) mkdirSync(outDir, { recursive: true });
67
+ writeFileSync(join(outDir, 'index.json'), JSON.stringify(buildIndex(records), null, 2) + '\n');
68
+ writeFileSync(join(outDir, 'llms.txt'), buildLlmsTxt(records));
69
+ console.log(`✓ docs:digest — ${records.length} component(s) → design-system/docs/{index.json,llms.txt}`);
70
+ }
71
+
72
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
73
+ main();
74
+ }