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.
- package/CHANGELOG.md +38 -0
- package/README.md +1 -1
- package/VERSION +1 -1
- package/bin/baldart.js +15 -0
- package/framework/.claude/agents/code-reviewer.md +25 -11
- package/framework/.claude/agents/codebase-architect.md +13 -5
- package/framework/.claude/agents/doc-reviewer.md +26 -12
- package/framework/.claude/agents/ui-expert.md +18 -10
- package/framework/.claude/skills/context-primer/SKILL.md +7 -0
- package/framework/.claude/skills/design-system-init/SKILL.md +164 -156
- package/framework/.claude/skills/design-system-init/scripts/component-spec.template.md +26 -3
- package/framework/.claude/skills/design-system-init/scripts/extract-manifest.mjs +151 -0
- package/framework/.claude/skills/new/references/final-review.md +12 -2
- package/framework/.claude/skills/new/references/team-mode.md +1 -1
- package/framework/.claude/workflows/new-card-review.js +67 -13
- package/framework/.claude/workflows/new-final-review.js +63 -7
- package/framework/agents/component-manifest-schema.md +145 -0
- package/framework/agents/design-system-protocol.md +63 -23
- package/framework/agents/index.md +2 -0
- package/framework/docs/COMPONENT-MANIFEST-LAYER.md +100 -0
- package/framework/routines/ds-drift.routine.yml +19 -12
- package/framework/templates/baldart.config.template.yml +22 -0
- package/package.json +1 -1
- package/src/commands/configure.js +54 -0
- package/src/commands/doctor.js +85 -0
- package/src/commands/tokens.js +80 -0
- package/src/commands/update.js +7 -0
- package/src/utils/token-emitters/README.md +36 -0
- package/src/utils/token-emitters/css.js +18 -0
- package/src/utils/token-emitters/index.js +47 -0
- package/src/utils/token-emitters/ts.js +32 -0
- 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
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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.
|
|
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. **
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
2. **Resolve target path.** Default `docs/design-system/`.
|
|
65
|
-
|
|
66
|
-
3. **Inventory existing primitives.**
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
86
|
-
`paths.design_system: <target
|
|
87
|
-
|
|
88
|
-
unrelated keys).
|
|
89
|
-
|
|
90
|
-
|
|
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. **
|
|
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. **
|
|
123
|
+
2. **Deterministic extraction (no subagent — Codex-portable).** Run the extractor
|
|
124
|
+
over `${paths.components_primitives}` (+ `${paths.components_root}/shared`):
|
|
97
125
|
```
|
|
98
|
-
|
|
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. **
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
-
|
|
110
|
-
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
✓
|
|
134
|
-
✓
|
|
135
|
-
✓
|
|
136
|
-
|
|
137
|
-
Config updated:
|
|
138
|
-
Next: review
|
|
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
|
-
##
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
the
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
|
169
|
-
|
|
170
|
-
|
|
|
171
|
-
|
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
literals that duplicate these tokens
|
|
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
|
-
-
|
|
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}`
|
|
230
|
-
`baldart.config.yml`.
|
|
231
|
-
-
|
|
232
|
-
|
|
233
|
-
- Idempotent: re-running
|
|
234
|
-
|
|
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.
|
|
4
|
-
>
|
|
5
|
-
>
|
|
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. **
|
|
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):
|