baldart 4.64.0 → 4.66.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 (32) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +1 -1
  3. package/VERSION +1 -1
  4. package/bin/baldart.js +15 -0
  5. package/framework/.claude/agents/code-reviewer.md +25 -11
  6. package/framework/.claude/agents/codebase-architect.md +13 -5
  7. package/framework/.claude/agents/doc-reviewer.md +26 -12
  8. package/framework/.claude/agents/ui-expert.md +18 -10
  9. package/framework/.claude/skills/context-primer/SKILL.md +7 -0
  10. package/framework/.claude/skills/design-system-init/SKILL.md +164 -156
  11. package/framework/.claude/skills/design-system-init/scripts/component-spec.template.md +26 -3
  12. package/framework/.claude/skills/design-system-init/scripts/extract-manifest.mjs +151 -0
  13. package/framework/.claude/skills/new/references/final-review.md +12 -2
  14. package/framework/.claude/skills/new/references/team-mode.md +1 -1
  15. package/framework/.claude/workflows/new-card-review.js +67 -13
  16. package/framework/.claude/workflows/new-final-review.js +63 -7
  17. package/framework/agents/component-manifest-schema.md +145 -0
  18. package/framework/agents/design-system-protocol.md +63 -23
  19. package/framework/agents/index.md +2 -0
  20. package/framework/docs/COMPONENT-MANIFEST-LAYER.md +100 -0
  21. package/framework/routines/ds-drift.routine.yml +19 -12
  22. package/framework/templates/baldart.config.template.yml +22 -0
  23. package/package.json +1 -1
  24. package/src/commands/configure.js +54 -0
  25. package/src/commands/doctor.js +85 -0
  26. package/src/commands/tokens.js +80 -0
  27. package/src/commands/update.js +7 -0
  28. package/src/utils/token-emitters/README.md +36 -0
  29. package/src/utils/token-emitters/css.js +18 -0
  30. package/src/utils/token-emitters/index.js +47 -0
  31. package/src/utils/token-emitters/ts.js +32 -0
  32. package/src/utils/tokens-generator.js +165 -0
@@ -2,17 +2,21 @@
2
2
  name: design-system-init
3
3
  effort: medium
4
4
  description: >
5
- Bootstrap a component registry / design system on a project that doesn't have
6
- one yet. Scaffolds ${paths.design_system}/INDEX.md (component index +
7
- Canonical Authority Matrix), tokens-reference.md (extracted from
8
- ${paths.global_styles} / Tailwind config), and a components/<Name>.md spec
9
- per primitive already present under ${paths.components_primitives}. Then
10
- flips features.has_design_system: true in baldart.config.yml. Use when the
11
- user says /design-system-init, "crea design system", "bootstrap design
12
- system", "inizializza registro componenti", or when configure detected no
13
- design-system path and the user wants one. Idempotent refuses to overwrite
14
- an existing registry, supports incremental re-runs to add specs for new
15
- components.
5
+ Bootstrap OR upgrade a component registry / design system. GREENFIELD: scaffolds
6
+ ${paths.design_system}/INDEX.md (a thin generated router), a DTCG
7
+ ${paths.design_tokens} (.tokens.json) token SSOT + generated tokens-reference.md,
8
+ and a components/<Name>.md spec per primitive under ${paths.components_primitives}
9
+ each carrying a machine-readable frontmatter HEAD (deterministic-from-TS +
10
+ agentic fields, schema in agents/component-manifest-schema.md). Then flips
11
+ features.has_design_system: true. UPGRADE: on a project that already has a
12
+ registry but prose-only specs / a monolithic INDEX / no DTCG tokens, regenerates
13
+ the per-component HEAD from source, lifts prose into agentic fields (doc-reviewer),
14
+ rebuilds the thin INDEX router, and bootstraps .tokens.json from the existing
15
+ token file with a round-trip verify. Use when the user says /design-system-init,
16
+ "crea design system", "bootstrap design system", "inizializza registro
17
+ componenti", "aggiorna le spec dei componenti", or when `baldart doctor` nudges
18
+ (specs missing/prose-only). Idempotent — re-runs skip unchanged components
19
+ (source_sha) and never clobber filled agentic fields.
16
20
  ---
17
21
 
18
22
  # Design-System Init
@@ -28,8 +32,18 @@ current state so the discipline can kick in immediately.
28
32
 
29
33
  **Reads from `baldart.config.yml`:** `paths.design_system`,
30
34
  `paths.components_primitives`, `paths.components_root`, `paths.global_styles`,
31
- `paths.ui_guidelines`, `identity.brand_name`, `identity.design_philosophy`,
32
- `stack.design_system_signals`.
35
+ `paths.design_tokens`, `design_tokens.outputs`, `paths.ui_guidelines`,
36
+ `identity.brand_name`, `identity.design_philosophy`, `stack.design_system_signals`.
37
+
38
+ **Writes to `baldart.config.yml`:** `features.has_design_system: true`,
39
+ `paths.design_system`, and (when DTCG tokens are bootstrapped)
40
+ `paths.design_tokens` + `design_tokens.outputs`.
41
+
42
+ **Schema:** the per-component frontmatter HEAD this skill writes is defined in
43
+ `framework/agents/component-manifest-schema.md` (deterministic vs agentic split,
44
+ regeneration + transition-leniency rules). The token SSOT + generator contract is
45
+ in `framework/agents/design-system-protocol.md` § Token Contract; generation runs
46
+ via `baldart tokens build` (never reimplement it here).
33
47
 
34
48
  **Gated by features:** none — this skill is the bootstrap mechanism for
35
49
  `features.has_design_system`. It refuses to run when a registry is already
@@ -54,184 +68,175 @@ precedence caveats: `framework/agents/effort-protocol.md`.
54
68
 
55
69
  ## What This Skill Does
56
70
 
57
- 1. **Refusal check.** Reads `baldart.config.yml`.
58
- - If `features.has_design_system: true` AND `${paths.design_system}/INDEX.md`
59
- exists: stop with "Registry already present at `${paths.design_system}`.
60
- To extend it, edit it directly or invoke `ui-design` / `frontend-design`
61
- which know how to propose registry additions."
62
- - If `has_design_system: true` but the path is missing: warn about the
63
- drift, offer to re-scaffold at the configured path.
64
- 2. **Resolve target path.** Default `docs/design-system/`. Ask the user to
65
- confirm or override. Use `${paths.design_system}` if already set in config.
66
- 3. **Inventory existing primitives.** Launch the `codebase-architect` agent
67
- on `${paths.components_primitives}` (and `${paths.components_root}/shared`
68
- when present) to extract: file path, exported component name, props
69
- signature, variants (if discoverable), accessibility hooks used, tokens
70
- referenced.
71
- 4. **Extract token contract.** Read `${paths.global_styles}` for CSS custom
72
- properties; read `tailwind.config.{ts,js,mjs,cjs}` for theme extensions;
73
- read any `tokens.{json,ts,js}` or Style Dictionary output. Group by
74
- category (color, spacing, shadow, radius, typography, motion).
75
- 5. **Scaffold the files** under the target path:
76
- - `INDEX.md` component index, Canonical Authority Matrix (empty
77
- visual/behavioral/composition columns to be filled by the team),
78
- pointer to `tokens-reference.md` and `components/`.
79
- - `tokens-reference.md` extracted tokens, grouped, with a note that
80
- `tokens-reference.md` is the SSOT and hardcoded literals are forbidden.
81
- - `components/<Name>.md` one file per discovered primitive, generated
82
- from `scripts/component-spec.template.md`. Fill the fields the inventory
83
- could resolve; leave clearly-marked TODO sections for the rest.
71
+ 1. **Detect MODE** (read `baldart.config.yml`). This skill no longer refuses on
72
+ an existing registry it upgrades it.
73
+ - **GREENFIELD** no `${paths.design_system}/INDEX.md`: scaffold from scratch.
74
+ - **UPGRADE** INDEX.md exists but specs are prose-only / absent, the INDEX is
75
+ a monolith, or no DTCG tokens (the `baldart doctor` nudge case): regenerate
76
+ the machine-readable layer over the existing registry. Re-runs are
77
+ incremental (skip unchanged components by `source_sha`).
78
+ 2. **Resolve target path.** Default `docs/design-system/`. Use
79
+ `${paths.design_system}` if already set; confirm/override otherwise.
80
+ 3. **Inventory existing primitives — DETERMINISTIC first.** Run the pure-Node
81
+ extractor (`scripts/extract-manifest.mjs` via Bash) over
82
+ `${paths.components_primitives}` (+ `${paths.components_root}/shared`) to read
83
+ from TS types: exported name, `source`, `source_sha`, `props`
84
+ ({type,required,default}), `variants` (literal union), `composes` (in-registry
85
+ imports), `category` (import-fan-in heuristic). **This half runs with NO
86
+ subagent** it is the Codex-portable path. Empty results on a non-TS stack are
87
+ acceptable (advisory), never a failure.
88
+ 4. **Enrich AGENTIC.** Launch `doc-reviewer` to fill the agentic HEAD fields
89
+ (`purpose`, `a11y`, `token_bindings`, `related`, `must_rules`) in UPGRADE
90
+ mode by lifting them from the existing prose body; in GREENFIELD by reading the
91
+ source. Never clobber a filled agentic field on re-run. Under Codex (no
92
+ subagents) this degrades to leaving those fields empty + a TODO note.
93
+ 5. **Bootstrap the DTCG token SSOT.** See § Token SSOT bootstrap below: derive
94
+ `.tokens.json` from the existing token source, write `paths.design_tokens`,
95
+ configure `design_tokens.outputs`, run `baldart tokens build`, and
96
+ **round-trip verify** (regenerate, diff vs original; surface a non-empty
97
+ semantic diff, never auto-accept).
98
+ 6. **Write the files** under the target path:
99
+ - `INDEX.md` — **thin generated router**: one row per primitive
100
+ (`name | source | one-line purpose | spec link`). No Authority Matrix here
101
+ (it lives in `design-system-protocol.md`). Regenerated from the specs; never
102
+ hand-edited.
103
+ - `tokens-reference.md` — **generated** human view of `.tokens.json` (or pure
104
+ narrative). Not a third source of values.
105
+ - `components/<Name>.md` — one per primitive from
106
+ `scripts/component-spec.template.md`: the frontmatter HEAD (deterministic +
107
+ agentic) + prose body. In UPGRADE mode, prepend/refresh the HEAD on existing
108
+ specs in place, preserving the prose.
84
109
  - `patterns/.gitkeep` — empty dir for future pattern docs.
85
- 6. **Update config.** Set `features.has_design_system: true` and
86
- `paths.design_system: <target>` in `baldart.config.yml`. Use the same
87
- YAML write path as `configure` (preserve user values, never clobber
88
- unrelated keys).
89
- 7. **Print next-steps.** Authority Matrix to fill, specs to review, suggested
90
- first follow-up (`/design-review` on a key route to validate the registry).
110
+ 7. **Update config.** Set `features.has_design_system: true`,
111
+ `paths.design_system: <target>`, and (when tokens bootstrapped)
112
+ `paths.design_tokens` + `design_tokens.outputs`. Use the same YAML write path
113
+ as `configure` (preserve user values, never clobber unrelated keys).
114
+ 8. **Print next-steps.** Specs needing agentic review (empty fields), the
115
+ round-trip token diff if non-empty, suggested follow-up (`/design-review` on a
116
+ key route).
91
117
 
92
118
  ## Workflow
93
119
 
94
- 1. **Refusal check** — see § What This Skill Does step 1.
120
+ 1. **Detect mode + target path** — GREENFIELD vs UPGRADE (§ What This Skill Does
121
+ step 1). Confirm/override `${paths.design_system}` (default `docs/design-system/`).
95
122
 
96
- 2. **Target path prompt:**
123
+ 2. **Deterministic extraction (no subagent — Codex-portable).** Run the extractor
124
+ over `${paths.components_primitives}` (+ `${paths.components_root}/shared`):
97
125
  ```
98
- Scaffold the registry at: docs/design-system/ [Y/n/custom]
126
+ node .framework/framework/.claude/skills/design-system-init/scripts/extract-manifest.mjs \
127
+ --roots <primitives>,<shared> --json
99
128
  ```
129
+ It emits, per component: `name`, `source`, `source_sha`, `props`, `variants`,
130
+ `composes`, `category`. Empty fields on a non-TS stack are advisory, not errors.
100
131
 
101
- 3. **Inventory** launch `codebase-architect` with this prompt skeleton:
102
- > Enumerate the UI primitives under `${paths.components_primitives}`. For
103
- > each: file path, exported name, props type (best-effort from TS/JSDoc),
104
- > variants (e.g. `variant="primary|ghost"`), accessibility attributes
105
- > (aria-*, role, focus management), design tokens referenced (CSS vars,
106
- > Tailwind classes that map to tokens). Report as a JSON-friendly list.
107
-
108
- 4. **Token extraction** — for each detected source:
109
- - CSS custom properties: parse `--*: ...;` declarations and group by prefix.
110
- - Tailwind theme: parse `theme.extend.{colors,spacing,boxShadow,borderRadius,fontFamily,transitionDuration,transitionTimingFunction}`.
111
- - Style Dictionary / tokens file: parse the JSON tree.
112
- - If nothing is found, leave a TODO section in `tokens-reference.md`
113
- pointing the team to populate it before relying on the SSOT contract.
114
-
115
- 5. **Generate files** using the template in
116
- `scripts/component-spec.template.md` for each component, and the inline
117
- templates below for INDEX and tokens-reference.
118
-
119
- 6. **Persist config:**
120
- ```yaml
121
- paths:
122
- design_system: docs/design-system # or user-chosen
123
- features:
124
- has_design_system: true
125
- ```
132
+ 3. **Agentic enrichment (`doc-reviewer`).** Fill `purpose`, `a11y`,
133
+ `token_bindings`, `related`, `must_rules`. UPGRADE: lift from each spec's
134
+ existing prose. Never clobber a filled field. Under Codex: leave empty + TODO.
135
+
136
+ 4. **Token SSOT bootstrap** § Token SSOT bootstrap (derive `.tokens.json`,
137
+ set outputs, `baldart tokens build`, round-trip verify).
138
+
139
+ 5. **Write files** — per-component HEAD+prose from
140
+ `scripts/component-spec.template.md`; thin INDEX router; generated
141
+ tokens-reference; `patterns/.gitkeep`. UPGRADE refreshes HEADs in place.
142
+
143
+ 6. **Persist config** `features.has_design_system: true`, `paths.design_system`,
144
+ and (if tokens bootstrapped) `paths.design_tokens` + `design_tokens.outputs`.
145
+ Same YAML write path as `configure` (preserve user values).
126
146
 
127
147
  7. **Output** — short status block (see Output Contract).
128
148
 
129
149
  ## Output Contract
130
150
 
131
151
  ```
132
- Design-system bootstrap:
133
- Created docs/design-system/INDEX.md
134
- Created docs/design-system/tokens-reference.md (32 tokens extracted)
135
- Generated 17 component specs under docs/design-system/components/
136
- 4 components had no resolvable props — TODO sections marked
137
- Config updated: baldart.config.yml has_design_system=true, paths.design_system=docs/design-system
138
- Next: review INDEX.md Authority Matrix, fill TODO sections, run /design-review on a key route.
152
+ Design-system bootstrap (mode: upgrade):
153
+ Extracted HEAD for 135 components (deterministic) — 4 with no resolvable props (advisory)
154
+ Agentic enrichment via doc-reviewer: 131 enriched, 4 left TODO
155
+ DTCG tokens: src/ui/tokens.tokens.json (42 tokens) → tokens.ts rebuilt; round-trip diff clean
156
+ INDEX.md regenerated as thin router (135 rows)
157
+ Config updated: has_design_system=true, paths.design_tokens=src/ui/tokens.tokens.json
158
+ Next: review the 4 TODO specs, run /design-review on a key route.
139
159
  ```
140
160
 
141
- ## INDEX.md template
142
-
143
- The generated `INDEX.md` has this skeleton (concrete values substituted from
144
- the inventory):
161
+ ## Token SSOT bootstrap
162
+
163
+ When the project has tokens (a `tokens.{ts,js,json}`, CSS custom properties, or
164
+ Tailwind theme) but no DTCG source, bootstrap the SSOT:
165
+
166
+ 1. **Derive `.tokens.json`.** Deterministically extract the flat key/value leaves
167
+ the source exposes; hand the ambiguous leaves (which `$type`? aliases like
168
+ `{color.gray.200}`?) to `doc-reviewer` for `$type` assignment + alias
169
+ reconstruction. Write it to `paths.design_tokens` (default
170
+ `${paths.design_system}/tokens.tokens.json` or `src/ui/tokens.tokens.json`).
171
+ 2. **Configure outputs.** Set `design_tokens.outputs` to regenerate the EXISTING
172
+ token file in place (e.g. `{ format: ts, path: src/ui/tokens.ts }`) so there is
173
+ exactly one token artifact and it becomes generated.
174
+ 3. **Build + round-trip verify.** Run `baldart tokens build`, then diff the
175
+ regenerated output against the original. **A non-empty semantic diff (values
176
+ changed/dropped) is surfaced to the user, never auto-accepted** — it means the
177
+ `.tokens.json` derivation lost something. Only a formatting-only diff is safe.
178
+ 4. **Mark generated.** The output carries `// baldart-generated from .tokens.json`
179
+ — from now on, edit `.tokens.json`, never the output (the DS_TOKENS_DRIFT gate
180
+ enforces this).
181
+
182
+ **Do NOT replace a hand-authored token file in place on the first run** unless the
183
+ round-trip is clean — prefer generating to a new path and let the user flip the
184
+ import site once they trust it (the in-place replacement is the deferred step).
185
+
186
+ ## INDEX.md template (thin generated router)
187
+
188
+ The generated `INDEX.md` is a **router**, not a curated doc — regenerated from the
189
+ specs, never hand-edited. The Authority Matrix + MUST rules live in
190
+ `framework/agents/design-system-protocol.md` (protocol-level), not here.
145
191
 
146
192
  ```markdown
147
- # ${identity.brand_name} Design System
148
-
149
- Single source of truth for visual, behavioral, and compositional decisions on
150
- this product. Read this BEFORE producing or reviewing any UI. The registry-first
151
- protocol is defined in `framework/agents/design-system-protocol.md`.
152
-
153
- ## Token Contract
154
-
155
- Tokens live in [`tokens-reference.md`](tokens-reference.md). Hardcoded color /
156
- shadow / radius / spacing / motion values are FORBIDDEN when these tokens
157
- cover the use case.
158
-
159
- ## Component Index
160
-
161
- | Component | File | Purpose | Spec |
162
- |-----------|------|---------|------|
163
- | Button | ${paths.components_primitives}/Button.tsx | … | [components/Button.md](components/Button.md) |
164
- | … | … | … | … |
193
+ <!-- baldart-generated router — regenerated by /design-system-init from components/*.md HEAD. Do not hand-edit. -->
194
+ # ${identity.brand_name} Design System — Component Router
165
195
 
166
- ## Canonical Authority Matrix
196
+ Discovery entry point. Read a row's spec HEAD (not the source) to reuse a
197
+ primitive. Token SSOT: `${paths.design_tokens}` (DTCG). Authority Matrix + token
198
+ contract: `framework/agents/design-system-protocol.md`.
167
199
 
168
- | Dimension | Authority | Override path |
169
- |--------------------|---------------------------------|---------------|
170
- | Color / shadow / radius / spacing | `tokens-reference.md` | Propose a new token + ADR |
171
- | Props / variants / a11y | per-component `components/<Name>.md` | Update the spec in the same change |
172
- | Composition (patterns) | `patterns/<topic>.md` | New pattern doc + INDEX entry |
173
-
174
- ## Quick MUST Rules
175
-
176
- - Reuse from the Component Index before inventing a new primitive.
177
- - Consume tokens via the project's mechanism (CSS vars / Tailwind theme /
178
- theme provider) — never hardcode literals.
179
- - Any new primitive ships with its `components/<Name>.md` spec in the same
180
- change.
181
- - Authority Matrix violations require a registry update, not a local override.
182
-
183
- ## How to Extend
184
-
185
- - New primitive → create `components/<Name>.md` (the template lives at
186
- `.framework/framework/.claude/skills/design-system-init/scripts/component-spec.template.md`
187
- after `npx baldart add` — copy it, fill it in, drop under
188
- `${paths.design_system}/components/`),
189
- implement under `${paths.components_primitives}`, add the row above.
190
- - New pattern → create `patterns/<topic>.md`, link from this INDEX.
191
- - New token → add to `tokens-reference.md`, propagate to the project's
192
- token mechanism.
200
+ | Component | Source | Purpose | Spec |
201
+ |-----------|--------|---------|------|
202
+ | Button | src/ui/components/Button.tsx | Primary action trigger | [components/Button.md](components/Button.md) |
203
+ | … | | | |
193
204
  ```
194
205
 
195
- ## tokens-reference.md template
206
+ ## tokens-reference.md template (generated human view)
207
+
208
+ `tokens-reference.md` is a **generated** human-readable view of the DTCG
209
+ `.tokens.json` (or pure narrative) — NOT a hand-authored source of values. The
210
+ SSOT is `.tokens.json` at `${paths.design_tokens}`.
196
211
 
197
212
  ```markdown
213
+ <!-- baldart-generated view of ${paths.design_tokens} — edit the .tokens.json, not this file. -->
198
214
  # Token Reference
199
215
 
200
- Single source of truth for design tokens. Values below are extracted from
201
- ${paths.global_styles} and tailwind config (when present). DO NOT hardcode
202
- literals that duplicate these tokens in components.
216
+ Human view of the DTCG token SSOT (`${paths.design_tokens}`). The runtime
217
+ artifacts (tokens.ts / CSS vars) are generated via `baldart tokens build`. DO NOT
218
+ hardcode literals that duplicate these tokens.
203
219
 
204
220
  ## Color
205
- - `--color-primary: <value>`
206
- - …
207
-
208
- ## Spacing
209
- - `--space-1: <value>` — …
210
- - …
211
-
212
- ## Shadow
221
+ - `color.action.primary` → `<resolved value>` (CSS `--color-action-primary`)
213
222
  - …
214
223
 
215
- ## Radius
224
+ ## Spacing / Shadow / Radius / Typography / Motion
216
225
  - …
217
-
218
- ## Typography
219
- - …
220
-
221
- ## Motion
222
- - duration: …
223
- - easing: …
224
226
  ```
225
227
 
226
228
  ## Notes
227
229
 
228
230
  - The skill never modifies source components — its only side effects are
229
- creating files under `${paths.design_system}` and updating
230
- `baldart.config.yml`.
231
- - Generated specs are starting points, not final docs. The expected next move
232
- is human review + filling TODO sections + a `/design-review` pass.
233
- - Idempotent: re-running detects existing specs and only generates files for
234
- new primitives (and updates the Component Index table).
231
+ creating files under `${paths.design_system}`, deriving `.tokens.json` +
232
+ regenerating its outputs, and updating `baldart.config.yml`.
233
+ - Deterministic HEAD fields are starting points; agentic fields + TODO sections
234
+ expect a `doc-reviewer` / human pass. Run `/design-review` on a key route after.
235
+ - Idempotent: re-running skips components whose `source_sha` is unchanged and
236
+ never clobbers filled agentic fields; deterministic fields are refreshed from
237
+ source; the INDEX router + tokens-reference are fully regenerated.
238
+ - Codex-safe: the deterministic extractor + `baldart tokens build` run with no
239
+ subagent; only the agentic enrichment needs Claude (degrades to TODO).
235
240
 
236
241
  ## See Also
237
242
 
@@ -239,4 +244,7 @@ literals that duplicate these tokens in components.
239
244
  - `framework/agents/project-context.md` — config + overlay contract.
240
245
  - `framework/.claude/skills/ui-design/SKILL.md` — primary consumer (reads
241
246
  the registry BLOCKING).
242
- - `scripts/component-spec.template.md` — per-component spec template.
247
+ - `scripts/component-spec.template.md` — per-component spec template (HEAD + prose).
248
+ - `scripts/extract-manifest.mjs` — deterministic, zero-dep HEAD extractor (Codex-portable).
249
+ - `framework/agents/component-manifest-schema.md` — the frontmatter HEAD schema.
250
+ - `framework/docs/COMPONENT-MANIFEST-LAYER.md` — layer design + lifecycle.
@@ -1,8 +1,31 @@
1
+ ---
2
+ # Machine-readable HEAD — schema: framework/agents/component-manifest-schema.md
3
+ # DETERMINISTIC fields (regenerated from source — do not hand-curate):
4
+ name: {{exported_name}}
5
+ source: {{file_path}}
6
+ source_sha: {{source_sha}}
7
+ status: {{status}} # stable | experimental | deprecated (default stable)
8
+ category: {{category}} # primitive | composite | feature
9
+ props: {{props_yaml}} # name → { type, required, default } ({} if unresolved)
10
+ variants: {{variants_yaml}} # [] if none / unresolved
11
+ composes: {{composes_yaml}} # in-registry primitives this imports
12
+ # AGENTIC fields (curated by doc-reviewer — preserved across regeneration):
13
+ purpose: "{{purpose}}"
14
+ token_bindings: {{token_bindings_yaml}} # DTCG token IDs (not values)
15
+ a11y: "{{a11y}}"
16
+ related: {{related_yaml}}
17
+ must_rules: {{must_rules_yaml}}
18
+ last_verified: {{today}}
19
+ owner: doc-reviewer
20
+ ---
21
+
1
22
  # {{ComponentName}}
2
23
 
3
- > Per-component spec. Authoritative for props, variants, accessibility, and
4
- > anti-patterns. Visual authority (color/shadow/radius/spacing) belongs to
5
- > [`../tokens-reference.md`](../tokens-reference.md).
24
+ > Per-component spec. The HEAD above is the machine-readable contract (read it
25
+ > first for discovery); this prose body is the human long-form rationale.
26
+ > Authoritative for props, variants, accessibility, and anti-patterns. Visual
27
+ > authority (color/shadow/radius/spacing) belongs to the DTCG token SSOT
28
+ > (`${paths.design_tokens}`), surfaced via [`../tokens-reference.md`](../tokens-reference.md).
6
29
 
7
30
  ## Source
8
31
 
@@ -0,0 +1,151 @@
1
+ #!/usr/bin/env node
2
+ // Deterministic component-manifest extractor (since v4.65.0).
3
+ //
4
+ // Pure Node, ZERO dependencies — this is the Codex-portable half of the
5
+ // component-manifest layer: it runs WITHOUT any Claude subagent. It reads a
6
+ // best-effort machine-readable HEAD from React+TS sources for /design-system-init
7
+ // to scaffold each `components/<Name>.md` frontmatter. The semantic fields
8
+ // (purpose, a11y, must_rules) are filled separately by doc-reviewer.
9
+ //
10
+ // Reliability contract: name / source / source_sha are always emitted (cheap +
11
+ // exact); props / variants / composes are best-effort regex extraction over the
12
+ // common patterns. Empty fields are ADVISORY ("not yet enriched"), never an
13
+ // error — a non-TS stack simply yields empty props/variants. Schema:
14
+ // framework/agents/component-manifest-schema.md.
15
+ //
16
+ // Usage:
17
+ // node extract-manifest.mjs --roots src/ui/components,src/components/shared [--json]
18
+
19
+ import fs from 'node:fs';
20
+ import path from 'node:path';
21
+ import { execFileSync } from 'node:child_process';
22
+
23
+ function arg(name, def = '') {
24
+ const i = process.argv.indexOf(name);
25
+ return i >= 0 && process.argv[i + 1] ? process.argv[i + 1] : def;
26
+ }
27
+
28
+ const roots = arg('--roots').split(',').map((s) => s.trim()).filter(Boolean);
29
+ const asJson = process.argv.includes('--json');
30
+
31
+ /** Recursively collect .tsx/.ts files (skip tests, stories, node_modules, index barrels). */
32
+ function walk(dir, out = []) {
33
+ let entries;
34
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (_) { return out; }
35
+ for (const e of entries) {
36
+ const full = path.join(dir, e.name);
37
+ if (e.isDirectory()) {
38
+ if (e.name === 'node_modules' || e.name.startsWith('.')) continue;
39
+ walk(full, out);
40
+ } else if (/\.(tsx|ts)$/.test(e.name)
41
+ && !/\.(test|spec|stories)\./.test(e.name)
42
+ && e.name !== 'index.ts' && e.name !== 'index.tsx') {
43
+ out.push(full);
44
+ }
45
+ }
46
+ return out;
47
+ }
48
+
49
+ function gitSha(file) {
50
+ try { return execFileSync('git', ['hash-object', file], { encoding: 'utf8' }).trim().slice(0, 7); }
51
+ catch (_) { return ''; }
52
+ }
53
+
54
+ /** Best-effort exported component name: PascalCase export const/function/default. */
55
+ function exportedName(src, file) {
56
+ const pats = [
57
+ /export\s+default\s+function\s+([A-Z][A-Za-z0-9]*)/,
58
+ /export\s+function\s+([A-Z][A-Za-z0-9]*)/,
59
+ /export\s+const\s+([A-Z][A-Za-z0-9]*)\s*[:=]/,
60
+ ];
61
+ for (const p of pats) { const m = src.match(p); if (m) return m[1]; }
62
+ // fall back to the filename (PascalCase component file convention)
63
+ const base = path.basename(file).replace(/\.(tsx|ts)$/, '');
64
+ return /^[A-Z]/.test(base) ? base : '';
65
+ }
66
+
67
+ /** Best-effort props from a `interface XProps {…}` / `type XProps = {…}` block.
68
+ * Brace-matched so nested object props don't bleed their inner fields out. */
69
+ function extractProps(src, name) {
70
+ const head = new RegExp(`(?:interface|type)\\s+${name}Props\\b[^\\{]*\\{`);
71
+ const hm = src.match(head);
72
+ if (!hm) return {};
73
+ // Walk from the opening brace to its balanced close.
74
+ const start = hm.index + hm[0].length;
75
+ let depth = 1, i = start;
76
+ for (; i < src.length && depth > 0; i++) {
77
+ if (src[i] === '{') depth++;
78
+ else if (src[i] === '}') depth--;
79
+ }
80
+ const body = src.slice(start, i - 1);
81
+ const props = {};
82
+ // Parse TOP-LEVEL `name?: type;` pairs only — skip anything nested in braces.
83
+ let d = 0, line = '';
84
+ const flush = () => {
85
+ const lm = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)(\?)?\s*:\s*([\s\S]+?)\s*;?\s*$/);
86
+ if (lm) {
87
+ const type = lm[3].replace(/\s+/g, ' ').trim();
88
+ // A nested object prop collapses to "object" rather than a stray "{".
89
+ props[lm[1]] = { type: type === '{' || type.endsWith('{') ? 'object' : type, required: !lm[2] };
90
+ }
91
+ line = '';
92
+ };
93
+ for (const ch of body) {
94
+ if (ch === '{' || ch === '(' || ch === '[') { d++; line += ch; continue; }
95
+ if (ch === '}' || ch === ')' || ch === ']') { d = Math.max(0, d - 1); line += ch; continue; }
96
+ if ((ch === ';' || ch === '\n') && d === 0) { flush(); continue; }
97
+ line += ch;
98
+ }
99
+ if (line.trim()) flush();
100
+ return props;
101
+ }
102
+
103
+ /** Variants = literal union of the `variant` prop. */
104
+ function extractVariants(props) {
105
+ const v = props.variant && props.variant.type;
106
+ if (!v) return [];
107
+ const lits = [...v.matchAll(/'([^']+)'|"([^"]+)"/g)].map((m) => m[1] || m[2]);
108
+ return lits;
109
+ }
110
+
111
+ /** composes = PascalCase identifiers imported via relative paths (in-repo primitives). */
112
+ function extractComposes(src) {
113
+ const out = new Set();
114
+ const importRe = /import\s+(?:\{([^}]*)\}|([A-Z][A-Za-z0-9]*))\s+from\s+['"](\.[^'"]+)['"]/g;
115
+ let im;
116
+ while ((im = importRe.exec(src))) {
117
+ if (im[2]) out.add(im[2]);
118
+ if (im[1]) im[1].split(',').map((s) => s.trim().split(/\s+as\s+/)[0]).filter((s) => /^[A-Z]/.test(s)).forEach((s) => out.add(s));
119
+ }
120
+ return [...out];
121
+ }
122
+
123
+ const components = [];
124
+ for (const root of roots) {
125
+ for (const file of walk(root)) {
126
+ let src;
127
+ try { src = fs.readFileSync(file, 'utf8'); } catch (_) { continue; }
128
+ const name = exportedName(src, file);
129
+ if (!name) continue;
130
+ const props = extractProps(src, name);
131
+ components.push({
132
+ name,
133
+ source: path.relative(process.cwd(), file),
134
+ source_sha: gitSha(file),
135
+ status: 'stable',
136
+ category: 'primitive',
137
+ props,
138
+ variants: extractVariants(props),
139
+ composes: extractComposes(src).filter((c) => c !== name),
140
+ });
141
+ }
142
+ }
143
+
144
+ const result = { schema: 'baldart.component-manifest-extract/1', count: components.length, components };
145
+ if (asJson) process.stdout.write(JSON.stringify(result, null, 2) + '\n');
146
+ else {
147
+ for (const c of components) {
148
+ process.stdout.write(`${c.name} (${c.source}) props:${Object.keys(c.props).length} variants:${c.variants.length} composes:${c.composes.length}\n`);
149
+ }
150
+ process.stdout.write(`\n${components.length} components.\n`);
151
+ }
@@ -113,7 +113,17 @@ that is a **gate violation**: log it as
113
113
  The workflow returns `{ codexEngine, findings, noActionFindings, gateTable, summary }` where
114
114
  `findings` are already consolidated and classified (`VERIFIED` /
115
115
  `NEEDS_MANUAL_CONFIRMATION`; `FALSE_POSITIVE` already dropped) and `gateTable`
116
- is the qa-sentinel PASS/FAIL/SKIP table. **Since `applyFixes: true` is passed (since v4.59.0),
116
+ is the qa-sentinel PASS/FAIL/SKIP table. **Server-side reviewer death is non-silent (since v4.66.0):**
117
+ a reviewer killed by a transient API error (529 Overloaded / 429 / 5xx / rate-limit — the org-level
118
+ limit is SHARED across every terminal) is retried in-workflow (`agentSafe`), and if it stays dead it
119
+ is recorded in `summary.degradedReviewers` (the batch review ran with INCOMPLETE coverage — do NOT
120
+ read a clean `findings` as "nothing found"). In particular a dead **qa-sentinel** does NOT yield an
121
+ empty `gateTable`: the workflow injects a synthetic `{ gate: 'mechanical-gates (qa-sentinel)', status:
122
+ 'FAIL' }` so the merge is treated as **non-PASS** (blocked) rather than silently passing on UNKNOWN
123
+ mechanical gates. When `summary.degradedReviewers` is non-empty or the qa gate is the synthetic FAIL,
124
+ surface it terse in F.5 and treat the merge as gated; **AUTONOMOUS** → materialize a follow-up card to
125
+ re-run the final review/gates (never merge blind). This is the per-agent twin of the F.1.5 whole-workflow
126
+ recovery, and mirrors the transient-vs-genuine discipline in `references/team-mode.md` § empty-result. **Since `applyFixes: true` is passed (since v4.59.0),
117
127
  the workflow ALSO ran its Fix phase** and returns the additive fields
118
128
  `{ ...above, fixesApplied:[…1-line strings], docFixesApplied:[…1-line strings], residual:[…slim findings] }`:
119
129
  it already applied every VERIFIED fix by domain owner (security-reviewer → coder → doc-reviewer, all
@@ -266,7 +276,7 @@ that is a **gate violation**: log it as
266
276
  |-------|-----------------|-------|--------|
267
277
  | **doc-reviewer** | `doc-reviewer` | Cross-card doc consistency, ssot-registry completeness, invariants | Findings: `finding_id`, `title`, `severity`, `confidence`, `evidence`, `minimal_fix_direction` |
268
278
  | **api-perf-cost-auditor** | `api-perf-cost-auditor` | API/data/performance/cost defects (skip if no API/data files in scope) | Same findings schema |
269
- | **qa-sentinel** | `qa-sentinel` | **Mechanical gates ONLY** over the batch scope (lint, tsc, full test suite, build, `npm audit`, markdownlint) | A PASS/FAIL gate table — NOT a findings list. qa-sentinel does not read source files, does not emit severities, and does not do edge-case/reproducibility analysis (its system prompt forbids it). A gate FAILURE feeds the fix-loop the same way a VERIFIED finding does. |
279
+ | **qa-sentinel** | `qa-sentinel` | **Mechanical gates ONLY** over the batch scope (lint, tsc, full test suite, build, `npm audit`, markdownlint) | A PASS/FAIL gate table — NOT a findings list. qa-sentinel does not read source files, does not emit severities, and does not do edge-case/reproducibility analysis (its system prompt forbids it). A gate FAILURE feeds the fix-loop the same way a VERIFIED finding does. **If qa-sentinel itself DIES (transient 529/rate-limit exhausted, or a terminal API error) the gate table is UNKNOWN, never empty: the workflow injects a synthetic `status: FAIL` so the merge is blocked rather than passing blind (since v4.66.0).** |
270
280
 
271
281
  **F-041 per-finder slim — N=1 only (coverage-gated; identical rule to the delegated path's
272
282
  `slimDoc`/`slimApi`).** This inline prose is the SSOT the workflow mirrors, so it carries the same
@@ -155,7 +155,7 @@ For each completed agent:
155
155
  - Subagent transcripts live at `<session-subagents-dir>/agent-*.jsonl`, each with a sibling `agent-*.meta.json` whose `agentType` equals the teammate label. Resolve the file and read its tail, e.g.:
156
156
  `D="$(ls -dt ~/.claude/projects/*/${CLAUDE_SESSION_ID:-*}/subagents 2>/dev/null | head -1)"; f="$(grep -l '"<teammate-label>"' "$D"/*.meta.json 2>/dev/null | head -1 | sed 's/\.meta\.json$/.jsonl/')"; tail -c 4000 "$f"`
157
157
  (if `$CLAUDE_SESSION_ID` is unset, the most recent `subagents/` dir under this project is the live session's.) If the transcript truly cannot be located, fall back to whatever the `Agent` tool result surfaced — but do NOT default to "genuine empty-result" without having looked. **State the classification you reached + the evidence in your narration** (e.g. "03 last event = `Rate limited` → transient").
158
- - **Transient infra failure (the common cause of a silent empty rest in a WIDE parallel wave).** The agent's last event is an API error — signatures: `isApiErrorMessage:true`, `Rate limited`, `Server is temporarily limiting requests`, `overload`, `(not your usage limit)` — i.e. the background teammate was **killed mid-flight by API rate-limiting** (it had done real work — reads, a plan — then died), NOT a model fabrication or "decided nothing to do". The runtime does NOT auto-resume a dead teammate, so it rests with no report + no diff. **Re-spawn it, but STAGGERED with backoff — do NOT re-fire several transient-failed agents in the same parallel burst** (that re-saturates the API and kills them again). Space them out (one at a time, or after a brief pause). This still costs a fresh run (the killed agent's reads are lost — the runtime cannot resume it), so **prevention beats recovery**: if a wave repeatedly hits rate limits, NARROW the parallel fan-out width for that wave (spawn fewer coders at once) rather than re-firing the full wave. A transient failure does NOT consume the Step-B genuine-failure budget.
158
+ - **Transient infra failure (the common cause of a silent empty rest in a WIDE parallel wave).** The agent's last event is an API error — signatures: `isApiErrorMessage:true`, `Rate limited`, `Server is temporarily limiting requests`, `overload`, `(not your usage limit)` — i.e. the background teammate was **killed mid-flight by API rate-limiting** (it had done real work — reads, a plan — then died), NOT a model fabrication or "decided nothing to do". The runtime does NOT auto-resume a dead teammate, so it rests with no report + no diff. **Re-spawn it, but STAGGERED with backoff — do NOT re-fire several transient-failed agents in the same parallel burst** (that re-saturates the API and kills them again). Space them out (one at a time, or after a brief pause). This still costs a fresh run (the killed agent's reads are lost — the runtime cannot resume it), so **prevention beats recovery**: if a wave repeatedly hits rate limits, NARROW the parallel fan-out width for that wave (spawn fewer coders at once) rather than re-firing the full wave. A transient failure does NOT consume the Step-B genuine-failure budget. **(Workflow twin, since v4.66.0:** inside the `new-card-review` / `new-final-review` dynamic workflows the same transient-vs-terminal discipline is enforced in code — `agentSafe` retries a transient-killed reviewer in-workflow, and a reviewer that stays dead is recorded in `summary.degradedReviewers` rather than silently dropped by `filter(Boolean)`; a dead **qa-sentinel** specifically injects a synthetic `FAIL` gate so the merge is blocked, never passed on UNKNOWN mechanical gates. Same SSOT, two surfaces: this prose for orchestrator-spawned teammates, the workflow JS for in-workflow fan-out.)**
159
159
  - **Genuine empty-result / fabrication.** The agent rested CLEANLY (no error in its last event), with no completion report and no diff. THIS is the model-fault case: take the one Step-B re-spawn below (same cap), re-briefing the coder with an explicit "write your ownership files and confirm them on disk before reporting done" mandate. A second empty result → `AskUserQuestion` (skip/abandon) — do not re-spawn a third time. **When AUTONOMOUS, apply § AUTONOMOUS RESOLUTION RULE (SKILL.md): category=blocker; recommended=none safe (a card whose coder produced nothing twice has no implementation) → skip the card AND materialize a follow-up card carrying its spec (NEVER mark it DONE / no-op).** (Classify the cause first — a transient rate-limit death is re-spawned staggered, not counted against this budget.)
160
160
 
161
161
  **If an agent fails** (status: failed after 3 retries — the central repair cap):