baldart 4.68.2 → 4.69.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 CHANGED
@@ -5,6 +5,23 @@ All notable changes to BALDART will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [4.69.0] - 2026-06-24
9
+
10
+ **`/ds-new` — the fifth corner of the design-system discipline: guided creation of ONE canonical element + a canonical, ENFORCED doc template.** Discovery/reuse, prevention (`/prd`), enforcement (`ds-gate`), and bulk bootstrap (`/design-system-init`) all existed — but there was no user-invocable entry point to create a single new component or token *correctly* on-the-fly (research → create → document → register → govern → verify); `ui-expert` does it inside tasks, `/design-system-init` is bulk-only. Second gap the user named: the docs were **not standardized** — the rich prose template existed but was buried in `scripts/` and (critically) **not wired into the serializer** (`emitSpec` stubbed a one-liner on empty prose), so freshly-created specs were untemplated.
11
+
12
+ **MINOR** — adds a skill + a thin script wrapper; no new config key (rides on `features.has_design_system`). A pure orchestrator: every rule stays in its existing SSOT.
13
+
14
+ ### Added
15
+
16
+ - **`/ds-new` skill** (`framework/.claude/skills/ds-new/SKILL.md`) — single-element guided creation, the on-the-fly twin of `/design-system-init`, also callable by `/prd` at its reconciliation gate's "NEW (governed)" branch. THIN orchestrator: reuse-first (Component Discovery Cascade + Selection-Policy-by-role + `signatureDuplicates`) → optional scaffold (delegated to `ui-expert`, opt-in; default documents an existing source) → extract HEAD (`extract-one.mjs`) → enrich agentic fields (`doc-reviewer`) → govern closures → document+register (serializer) → verify (`ds-gate`) → coherence report. Token path: DTCG node → `baldart tokens build` → bind. Gated on `features.has_design_system` (refuse + suggest `/design-system-init` when false). Codex-portable (deterministic spine zero-subagent; agentic steps degrade to TODO). No rule re-described — cross-pointers to `design-system-protocol.md` + `component-manifest-schema.md`.
17
+ - **`extract-one.mjs`** — single-component wrapper around `extract-manifest.mjs`'s now-exported `extractFile` (the bulk extractor was `--roots <dir>` only). One extraction implementation, zero-dep, Codex-portable; verified to produce byte-identical output to the bulk path.
18
+
19
+ ### Changed
20
+
21
+ - **Canonical doc template promoted + ENFORCED.** Moved `component-spec.template.md` from `design-system-init/scripts/` to the visible **`framework/templates/component-spec.template.md`** (single SSOT, framework-internal, excluded from the consumer-copy loop) and enhanced it to explicitly encode the structural-retrieval contract (HEAD-first discovery, token IDs not values, tables>prose, cross-ref-over-repetition, one-owner-per-fact — cross-pointing `doc-writing-for-rag` + `component-manifest-schema.md` § Read contract). **Wired into the serializer**: `serialize-spec.mjs`'s `emitSpec()` now FILLS the template on empty prose (placeholders from `props`/`variants`/`token_bindings`, unfillable ones keep `<!-- TODO -->`), via a new `--prose-template` flag (default resolves to the promoted path). Non-empty prose is still preserved verbatim. One change → three beneficiaries: `/ds-new`, `/design-system-init` greenfield, `ui-expert` Post-Intervention regen — every newly-created spec now follows the standardized shape.
22
+ - **`extract-manifest.mjs`** — per-file extraction factored into exported `extractFile`; CLI guarded with `import.meta` so importing has no side effects.
23
+ - **`/prd` reconciliation** (`ui-design-phase.md`) gains an OPT-IN "create now" arm at the NEW-governed branch (invoke `/ds-new`); default stays bind-and-defer. `/ds-new` registered as the single-element create path in `design-system-protocol.md` (§ When a primitive is missing + Consumers), `component-manifest-schema.md` (Producers & consumers), and `agents/index.md` routing.
24
+
8
25
  ## [4.68.2] - 2026-06-24
9
26
 
10
27
  **A change's OWN documentation now closes before merge — no more "post-merge, non-blocking" doc follow-ups for what the change itself touched.** Observed on a real `mayo` FEAT-0055 run that merged with two deferred doc items: a full-sync of the `/products` screen doc (the exact route it changed, beyond a stub) and a `must_rule` clarification on `PageLayout.md` (the component it modified). Both are *own-docs* — documenting what you just shipped — yet `/new` permitted deferring them (the DS Decision Matrix routed "needs judgement" to a follow-up, and Phase 6b force-marks cards DONE regardless). Leaving the doc of the thing you just changed stale at merge is the drift entry door the Post-Intervention philosophy exists to close.
package/README.md CHANGED
@@ -211,14 +211,14 @@ Skills always-ask when required keys are missing — never silently default.
211
211
  never overwrites your file. Full guide:
212
212
  [`framework/docs/PROJECT-CONFIGURATION.md`](framework/docs/PROJECT-CONFIGURATION.md).
213
213
 
214
- ### Skills (35 portable skills)
214
+ ### Skills (36 portable skills)
215
215
 
216
216
  Skills live under `.claude/skills/` and are auto-discovered by Claude Code.
217
217
  Bundled skills:
218
218
 
219
219
  - **Workflow**: `new`, `new2` (v4.16.0 — EXPERIMENTAL workflow-hosted `/new`, Claude-only, for A/B testing context economy), `prd`, `prd-add`, `bug`, `simplify`, `worktree-manager`, `issue-review`, `context-primer`
220
220
  - **Code quality**: `skill-creator`, `find-skills`, `webapp-testing`, `playwright-skill`, `lsp-bootstrap` (v3.10.0), `graphify-bootstrap` (v4.21.0 — code knowledge graph), `graph-align` (v4.21.0 — doc↔graph alignment), `toolchain-bootstrap` (v4.41.0 — curated dev toolchain), `e2e-review` (v3.18.0)
221
- - **Design**: `frontend-design`, `ui-design`, `motion-design`, `gamification-design`, `design-system-init` (v3.11.0)
221
+ - **Design**: `frontend-design`, `ui-design`, `motion-design`, `gamification-design`, `design-system-init` (v3.11.0 — bulk registry bootstrap/upgrade), `ds-new` (v4.69.0 — single-element guided creation: one component or token, reuse-first → optional scaffold → document + register + govern + verify; the on-the-fly twin of `design-system-init`)
222
222
  - **Product**: `seo-audit`, `copywriting`, `api-design-principles`
223
223
  - **Knowledge**: `doc-writing-for-rag`, `capture` (LLM wiki overlay)
224
224
  - **Internationalization**: `i18n` (v4.52.0 — audit the context registry + context-aware translation into native locale files), `i18n-adopt` (v4.52.0 — one-shot migration that externalizes all hardcoded strings on an existing codebase); both gated on `features.has_i18n`
package/VERSION CHANGED
@@ -1 +1 @@
1
- 4.68.2
1
+ 4.69.0
@@ -371,8 +371,9 @@ hardcode literals that duplicate these tokens.
371
371
  - `framework/agents/project-context.md` — config + overlay contract.
372
372
  - `framework/.claude/skills/ui-design/SKILL.md` — primary consumer (reads
373
373
  the registry BLOCKING).
374
- - `scripts/component-spec.template.md` — prose-body reference (HEAD is serializer-owned).
375
- - `scripts/extract-manifest.mjs` — deterministic, zero-dep HEAD extractor (Codex-portable).
376
- - `scripts/serialize-spec.mjs` — deterministic HEAD serializer (valid-YAML, fail-closed) + INDEX router emitter.
374
+ - `framework/templates/component-spec.template.md` — the CANONICAL prose-body template (SSOT for the spec shape; the serializer fills it on creation, HEAD is serializer-owned). The `--prose-template` default resolves here; no flag needed.
375
+ - `scripts/extract-manifest.mjs` — deterministic, zero-dep HEAD extractor (`extractFile` shared with `extract-one.mjs`; Codex-portable).
376
+ - `scripts/extract-one.mjs` — single-component wrapper around `extractFile` (used by `/ds-new`).
377
+ - `scripts/serialize-spec.mjs` — deterministic HEAD serializer (valid-YAML, fail-closed) + INDEX router + Selection Policy + canonical prose-body fill.
377
378
  - `framework/agents/component-manifest-schema.md` — the frontmatter HEAD schema.
378
379
  - `framework/docs/COMPONENT-MANIFEST-LAYER.md` — layer design + lifecycle.
@@ -25,9 +25,6 @@ function arg(name, def = '') {
25
25
  return i >= 0 && process.argv[i + 1] ? process.argv[i + 1] : def;
26
26
  }
27
27
 
28
- const roots = arg('--roots').split(',').map((s) => s.trim()).filter(Boolean);
29
- const asJson = process.argv.includes('--json');
30
-
31
28
  /** Recursively collect .tsx/.ts files (skip tests, stories, node_modules, index barrels). */
32
29
  function walk(dir, out = []) {
33
30
  let entries;
@@ -120,32 +117,48 @@ function extractComposes(src) {
120
117
  return [...out];
121
118
  }
122
119
 
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
- }
120
+ /**
121
+ * Extract ONE component's deterministic manifest fields from a source file.
122
+ * Returns null when the file is unreadable or exports no PascalCase component.
123
+ * This is the SINGLE extraction implementation — shared by the bulk extractor
124
+ * (CLI below) and `extract-one.mjs` (the single-component wrapper /ds-new uses),
125
+ * so the two never fork.
126
+ */
127
+ export function extractFile(file) {
128
+ let src;
129
+ try { src = fs.readFileSync(file, 'utf8'); } catch (_) { return null; }
130
+ const name = exportedName(src, file);
131
+ if (!name) return null;
132
+ const props = extractProps(src, name);
133
+ return {
134
+ name,
135
+ source: path.relative(process.cwd(), file),
136
+ source_sha: gitSha(file),
137
+ status: 'stable',
138
+ category: 'primitive',
139
+ props,
140
+ variants: extractVariants(props),
141
+ composes: extractComposes(src).filter((c) => c !== name),
142
+ };
142
143
  }
143
144
 
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`);
145
+ // CLI guarded so importing `extractFile` (from extract-one.mjs) has NO side effects.
146
+ if (import.meta.url === `file://${process.argv[1]}`) {
147
+ const roots = arg('--roots').split(',').map((s) => s.trim()).filter(Boolean);
148
+ const asJson = process.argv.includes('--json');
149
+ const components = [];
150
+ for (const root of roots) {
151
+ for (const file of walk(root)) {
152
+ const c = extractFile(file);
153
+ if (c) components.push(c);
154
+ }
155
+ }
156
+ const result = { schema: 'baldart.component-manifest-extract/1', count: components.length, components };
157
+ if (asJson) process.stdout.write(JSON.stringify(result, null, 2) + '\n');
158
+ else {
159
+ for (const c of components) {
160
+ process.stdout.write(`${c.name} (${c.source}) props:${Object.keys(c.props).length} variants:${c.variants.length} composes:${c.composes.length}\n`);
161
+ }
162
+ process.stdout.write(`\n${components.length} components.\n`);
149
163
  }
150
- process.stdout.write(`\n${components.length} components.\n`);
151
164
  }
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+ // Single-component deterministic manifest extractor (since v4.69.0).
3
+ //
4
+ // The thin single-element wrapper around `extract-manifest.mjs`'s `extractFile`
5
+ // (the bulk extractor is `--roots <dir>` only). Used by `/ds-new` to extract one
6
+ // component's deterministic HEAD fields (name, source, source_sha, props,
7
+ // variants, composes) WITHOUT a subagent — the Codex-portable spine. ZERO
8
+ // dependencies; imports the ONE shared extraction implementation so it can never
9
+ // drift from the bulk path.
10
+ //
11
+ // Usage:
12
+ // node extract-one.mjs --source <path.tsx> [--json]
13
+
14
+ import { extractFile } from './extract-manifest.mjs';
15
+
16
+ function arg(name, def = '') {
17
+ const i = process.argv.indexOf(name);
18
+ return i >= 0 && process.argv[i + 1] ? process.argv[i + 1] : def;
19
+ }
20
+
21
+ const source = arg('--source');
22
+ const asJson = process.argv.includes('--json');
23
+
24
+ if (!source) {
25
+ console.error('usage: extract-one.mjs --source <path.tsx> [--json]');
26
+ process.exit(2);
27
+ }
28
+
29
+ const component = extractFile(source);
30
+ if (!component) {
31
+ console.error(`extract-one: no exported PascalCase component found in ${source} (or file unreadable)`);
32
+ process.exit(1);
33
+ }
34
+
35
+ if (asJson) {
36
+ process.stdout.write(JSON.stringify({ schema: 'baldart.component-manifest-extract-one/1', component }, null, 2) + '\n');
37
+ } else {
38
+ process.stdout.write(`${component.name} (${component.source}) props:${Object.keys(component.props).length} variants:${component.variants.length} composes:${component.composes.length}\n`);
39
+ }
@@ -91,9 +91,44 @@ export function emitHead(c) {
91
91
  // Date is fine in a CLI script (this is not a resumable workflow).
92
92
  const TODAY = new Date().toISOString().slice(0, 10);
93
93
 
94
- /** Full spec file = HEAD fences + preserved prose body. */
95
- export function emitSpec(c) {
96
- const prose = (c.prose && c.prose.trim()) ? c.prose.replace(/^\s+/, '') : `# ${c.name}\n\n> Per-component spec. The HEAD above is the machine-readable contract.\n`;
94
+ /**
95
+ * Fill the canonical prose-body template (framework/templates/component-spec.template.md)
96
+ * for a freshly-created component. Takes the template TEXT, strips its leading
97
+ * comment + documented `---…---` HEAD block (the serializer owns the real HEAD),
98
+ * and substitutes the deterministically-fillable `{{…}}` placeholders. Unfillable
99
+ * ones keep the template's `<!-- TODO -->` so a human/doc-reviewer finishes them.
100
+ * Zero-dep, pure string ops. This is the wiring that makes EVERY newly-created
101
+ * spec follow the standardized shape (the gap: emitSpec used to stub a one-liner).
102
+ */
103
+ export function fillProseTemplate(c, templateText) {
104
+ // Body = everything after the second `---` fence (drop the leading comment + HEAD doc block).
105
+ const body = templateText.replace(/^[\s\S]*?\n---\n[\s\S]*?\n---\n/, '');
106
+ const propsTable = (c.props && Object.keys(c.props).length)
107
+ ? Object.entries(c.props).map(([k, p]) => `| ${k} | ${String((p && p.type) || '').replace(/\|/g, '\\|')} | ${(p && p.default !== undefined && p.default !== '') ? p.default : '—'} | ${(p && p.required) ? 'required' : '—'} |`).join('\n')
108
+ : '';
109
+ const variants = (c.variants && c.variants.length) ? c.variants.map((v) => `- \`${v}\``).join('\n') : '';
110
+ const tokens = (c.token_bindings && c.token_bindings.length) ? c.token_bindings.map((t) => `- \`${t}\``).join('\n') : '';
111
+ return body
112
+ .replace(/\{\{ComponentName\}\}/g, c.name || '')
113
+ .replace(/\{\{exported_name\}\}/g, c.name || '')
114
+ .replace(/\{\{file_path\}\}/g, c.source || '')
115
+ .replace(/\{\{today\}\}/g, c.last_verified || TODAY)
116
+ .replace(/\{\{props_table\}\}/g, propsTable)
117
+ .replace(/\{\{variants_section\}\}/g, variants)
118
+ .replace(/\{\{tokens_section\}\}/g, tokens);
119
+ }
120
+
121
+ /**
122
+ * Full spec file = HEAD fences + prose body. Prose resolution:
123
+ * 1. existing `c.prose` → preserved verbatim (never clobbered on regeneration);
124
+ * 2. else, when `proseTemplate` text is provided → the filled canonical template;
125
+ * 3. else → a minimal one-line stub (fallback when no template is resolvable).
126
+ */
127
+ export function emitSpec(c, proseTemplate) {
128
+ let prose;
129
+ if (c.prose && c.prose.trim()) prose = c.prose.replace(/^\s+/, '');
130
+ else if (proseTemplate) prose = fillProseTemplate(c, proseTemplate).replace(/^\s+/, '');
131
+ else prose = `# ${c.name}\n\n> Per-component spec. The HEAD above is the machine-readable contract.\n`;
97
132
  return `---\n${emitHead(c)}\n---\n\n${prose.replace(/\s*$/, '')}\n`;
98
133
  }
99
134
 
@@ -286,8 +321,14 @@ if (import.meta.url === `file://${process.argv[1]}`) {
286
321
  const tokensPath = arg('--tokens'); // optional DTCG .tokens.json → binding vocab guard
287
322
  const verifyUsage = process.argv.includes('--verify-usage'); // advisory source-usage check
288
323
  const cwd = arg('--cwd', process.cwd());
324
+ // Canonical prose-body template (framework/templates/component-spec.template.md).
325
+ // Default resolves relative to this script; callers may override with --prose-template.
326
+ const proseTemplatePath = arg('--prose-template', new URL('../../../../templates/component-spec.template.md', import.meta.url).pathname);
327
+ let proseTemplate = '';
328
+ try { proseTemplate = fs.readFileSync(proseTemplatePath, 'utf8'); }
329
+ catch (_) { /* template unresolvable → emitSpec falls back to the one-line stub */ }
289
330
  if (!bundlePath || !outDir) {
290
- console.error('usage: serialize-spec.mjs --bundle bundle.json --out-dir <dir> [--index <path>] [--brand <name>] [--tokens <.tokens.json>] [--verify-usage] [--cwd <dir>]');
331
+ console.error('usage: serialize-spec.mjs --bundle bundle.json --out-dir <dir> [--index <path>] [--brand <name>] [--tokens <.tokens.json>] [--verify-usage] [--prose-template <path>] [--cwd <dir>]');
291
332
  process.exit(2);
292
333
  }
293
334
  const bundle = JSON.parse(fs.readFileSync(bundlePath, 'utf8'));
@@ -325,7 +366,7 @@ if (import.meta.url === `file://${process.argv[1]}`) {
325
366
  fs.mkdirSync(outDir, { recursive: true });
326
367
  let invalid = 0;
327
368
  for (const c of components) {
328
- const spec = emitSpec(c);
369
+ const spec = emitSpec(c, proseTemplate);
329
370
  fs.writeFileSync(path.join(outDir, `${c.name}.md`), spec, 'utf8');
330
371
  const head = spec.match(/^---\n([\s\S]*?)\n---/)[1];
331
372
  const v = tryValidate(head);
@@ -0,0 +1,199 @@
1
+ ---
2
+ name: ds-new
3
+ effort: medium
4
+ description: >
5
+ Create ONE canonical design-system element — a component OR a token — end to
6
+ end with the full discipline: reuse-first research, (optional) scaffold, then
7
+ document + register + govern + verify. The single-element, on-the-fly twin of
8
+ the bulk `/design-system-init`: usable standalone by you, and callable by `/prd`
9
+ when its reconciliation gate confirms a NEW component. A THIN orchestrator — it
10
+ delegates every rule to the existing SSOTs (Component Discovery Cascade, the
11
+ manifest HEAD schema, the closed-set Selection Policy, the token cascade) and
12
+ produces STANDARDIZED docs by filling the canonical template
13
+ (framework/templates/component-spec.template.md) via the deterministic
14
+ serializer — it never hand-writes a spec HEAD. Use when the user says /ds-new,
15
+ "nuovo componente design system", "aggiungi un token", "crea un componente
16
+ canonico", "rendi canonico questo componente", "registra una primitive",
17
+ "documenta un componente nel design system". Gated on features.has_design_system.
18
+ ---
19
+
20
+ # DS New — create one canonical design-system element
21
+
22
+ The fifth corner of the design-system discipline: discovery/reuse, prevention
23
+ (`/prd`), enforcement (`baldart ds-gate`), and bulk bootstrap
24
+ (`/design-system-init`) already exist — this is the **guided creation of ONE
25
+ element** done correctly. It is a conductor: each step DELEGATES to an existing
26
+ module and cross-pointers to it; **no cascade/schema/closed-set/token rule is
27
+ re-described here** (re-describing it is how it drifts from the SSOT).
28
+
29
+ ## Project Context
30
+
31
+ **Reads from `baldart.config.yml`:** `paths.design_system`,
32
+ `paths.components_primitives`, `paths.components_root`, `paths.design_tokens`,
33
+ `design_tokens.outputs`, `paths.ui_guidelines`, `paths.global_styles`,
34
+ `identity.brand_name`, `git.trunk_branch`.
35
+
36
+ **Writes:** files under `${paths.design_system}` (the spec, the regenerated INDEX
37
+ + Selection Policy), the component HEAD fields, and — for tokens — the DTCG
38
+ `${paths.design_tokens}` + the regenerated outputs. Like `/design-system-init`,
39
+ it is **source-read-only by default** (it does not author component source);
40
+ scaffolding a `.tsx` is opt-in and delegated to `ui-expert`.
41
+
42
+ **Schema:** the frontmatter HEAD is defined in
43
+ `framework/agents/component-manifest-schema.md` and is **emitted by the
44
+ serializer, never hand-written**. The standardized prose body is the canonical
45
+ template `framework/templates/component-spec.template.md` (the serializer fills
46
+ it). The creation/governance rules are the SSOT in
47
+ `framework/agents/design-system-protocol.md` (Component Discovery Cascade, §
48
+ Closed-Set Selection Policy, § Token Cascade, Post-Intervention Coherence Check).
49
+
50
+ **Gated by features:** `features.has_design_system` (BLOCKING). When `false`,
51
+ REFUSE and recommend `/design-system-init` (you need a registry to add to). See
52
+ `design-system-protocol.md` § Gating.
53
+
54
+ **Overlay:** loads `.baldart/overlays/ds-new.md` if present (e.g. mandated stack
55
+ for scaffolds, brand-voice for the prose, a project token-naming convention).
56
+
57
+ **On missing keys:** ask the user; do not assume defaults. See
58
+ `framework/agents/project-context.md` § 3.
59
+
60
+ ## Effort
61
+
62
+ **Baseline:** `effort: medium` (frontmatter). **Inline override:** pass
63
+ `effort=<low|medium|high|xhigh|max>` anywhere in the invocation to scale
64
+ reasoning depth for this run — detect it once at kickoff and strip the token
65
+ before consuming user input. Mapping + parsing contract:
66
+ `framework/agents/effort-protocol.md`.
67
+
68
+ ## Invocation
69
+
70
+ `/ds-new <Name>` (component) · `/ds-new token <id>` (token) · `/ds-new` (ask).
71
+ Optional: `scaffold` (also generate the source), a mockup/figma ref, a one-line
72
+ intent. When called by `/prd` (the reconciliation gate's "NEW (governed)"
73
+ branch), the role/closure are pre-resolved — skip the reuse step (P1/step 1).
74
+
75
+ ## Pre-flight
76
+
77
+ - **P0 — gate.** `features.has_design_system: false` → REFUSE + suggest
78
+ `/design-system-init`. (DELEGATE `design-system-protocol.md` § Gating.)
79
+ - **P1 — kind.** component | token (from the arg, else ask one question).
80
+ - **P2 — source mode (component only).** Does the source already exist, or should
81
+ the skill scaffold it? Ask once; **default = document an existing file.**
82
+
83
+ ---
84
+
85
+ ## Component path
86
+
87
+ Each step is a delegation. Read the cited SSOT; do not paraphrase it.
88
+
89
+ 1. **Reuse-first (BLOCKING).** Walk the Component Discovery Cascade AND query the
90
+ Selection Policy **by role** (`INDEX.md` § Selection Policy), per
91
+ `design-system-protocol.md`. Surface duplicates with the gate's own detector —
92
+ `node -e` over `src/utils/ds-reuse-gate.js → signatureDuplicates` (or `baldart
93
+ ds-gate` once a spec exists). Ask the user **only** on a real fork (a
94
+ suspected-duplicate, or a closed-family hit) — one consolidated
95
+ `AskUserQuestion`. On a clean match → **STOP and reuse the existing component**
96
+ (creating it anyway is the exact failure this skill prevents).
97
+ 2. **Create (conditional).** *Scaffold mode* → DELEGATE `ui-expert` (Task tool):
98
+ it runs its own BLOCKING Functional Traceability + Token Contract pre-work and
99
+ writes a tokens-compliant source under `${paths.components_primitives}/<Name>`.
100
+ *Document mode* → take the existing path. Under Codex (no subagents), scaffold
101
+ degrades to a tokens-compliant-skeleton TODO and requires you to supply the
102
+ path. The skill itself NEVER writes component source.
103
+ 3. **Extract the deterministic HEAD.**
104
+ `node .framework/framework/.claude/skills/design-system-init/scripts/extract-one.mjs --source <path.tsx> --json`
105
+ → `name`/`source`/`source_sha`/`props`/`variants`/`composes` (zero-dep,
106
+ Codex-portable, no subagent).
107
+ 4. **Enrich the agentic fields AS DATA.** DELEGATE `doc-reviewer` (Task tool) to
108
+ produce `purpose` / `token_bindings` (picked from the REAL ids in
109
+ `${paths.design_tokens}`) / `a11y` / `related` / `must_rules` as JSON values —
110
+ NEVER typed into YAML. Codex → leave empty + TODO. (Schema:
111
+ `component-manifest-schema.md`.)
112
+ 5. **Govern (conditional).** Set `canonical_for` / `use_when` / `selection_closed`
113
+ ONLY when a closure is confirmed by the user (or pre-resolved by `/prd`);
114
+ propose-only when unattended. DELEGATE `design-system-protocol.md` § Closed-Set
115
+ Selection Policy + § Governance. An agent never flips closure on its own.
116
+ 6. **Document + register (the standardized doc).** Build a 1-component bundle
117
+ (`{ brand, components: [<the merged deterministic+agentic+policy fields>] }`)
118
+ **merged with the HEADs of ALL existing components** (read each
119
+ `${paths.design_system}/components/*.md` HEAD) so the regenerated `--index`
120
+ router + Selection Policy block stay complete (the same caveat as
121
+ `/design-system-init` step 6 — an INDEX from a partial bundle drops rows). Run:
122
+ ```
123
+ node .framework/framework/.claude/skills/design-system-init/scripts/serialize-spec.mjs \
124
+ --bundle <bundle.json> --out-dir ${paths.design_system}/components \
125
+ --index ${paths.design_system}/INDEX.md --brand "${identity.brand_name}" \
126
+ --tokens ${paths.design_tokens} --verify-usage
127
+ ```
128
+ It emits the valid-YAML HEAD + the **standardized prose body** (it fills
129
+ `framework/templates/component-spec.template.md` — the `--prose-template`
130
+ default; you never hand-write the spec) + the regenerated thin INDEX, then
131
+ self-validates and **fails closed**. Review `droppedBindings` /
132
+ `unverifiedBindings` per step 4.
133
+ 7. **Verify.** Serializer exit 0. If a closure was written: confirm
134
+ `grep -q '## Selection Policy' ${paths.design_system}/INDEX.md` AND
135
+ `npx baldart ds-gate --json` does NOT report `"reason":"no-policy"`. The gate
136
+ reads the **committed** baseline → the INDEX (with the policy block) must be
137
+ committed for enforcement; surface that so the user commits it. (This is
138
+ `/design-system-init` step 6b reused.)
139
+ 8. **Coherence report (never silent).** Emit the protocol's "Registry coherence
140
+ check: N findings" block (per § Post-Intervention Coherence Check): new spec
141
+ shipped, INDEX entry added, tokens reconciled, closed-set respected.
142
+
143
+ ---
144
+
145
+ ## Token path
146
+
147
+ 1. **Reuse-first.** Check the token already exists (in `${paths.design_tokens}` /
148
+ `tokens-reference.md`); ask **primitive vs semantic vs component** layer.
149
+ DELEGATE `design-system-protocol.md` § Token Cascade (components consume
150
+ semantic, never primitive; prefer OKLCH for palette generation).
151
+ 2. **Edit the DTCG SSOT.** Add the node to `${paths.design_tokens}` (`.tokens.json`,
152
+ `$type` + `$value`, hierarchical id). NEVER hand-edit the generated output.
153
+ 3. **Regenerate.** `npx baldart tokens build` (reads `.tokens.json`, regenerates
154
+ `design_tokens.outputs[]` + `tokens-reference.md`). DELEGATE — never reimplement.
155
+ 4. **Round-trip verify.** A non-empty semantic diff between the generated output
156
+ and the prior is surfaced, **never auto-accepted** — the user ratifies.
157
+ 5. **Bind (optional).** When a component should consume the new token, add its
158
+ **id** (not value) to that component's `token_bindings` and re-serialize (the
159
+ component path's step 6). The `--tokens` guard confirms the id resolves.
160
+ 6. **Coherence report.** Per § Post-Intervention Coherence Check (check 3): a
161
+ dangling `token_bindings` id is `DS_TOKENS_DRIFT`.
162
+
163
+ **Token-doc standard (NOT a per-token file).** A token's one-owner home is its
164
+ DTCG node + its row in the generated `tokens-reference.md` + its id in component
165
+ `token_bindings`. Do NOT create a per-token `.md` (it would be a 4th source of
166
+ truth — violates `doc-writing-for-rag` one-owner-per-fact). Token *rationale*, if
167
+ any, goes in an existing `${paths.design_system}/patterns/*.md`.
168
+
169
+ ---
170
+
171
+ ## Inline fallbacks (portability + fail-safe)
172
+
173
+ - **No `Workflow` tool needed** — this skill is prose-orchestrated.
174
+ - **Codex (no subagents):** the deterministic spine (`extract-one` → `serialize-spec`
175
+ → `tokens build` → `ds-gate`) is zero-subagent and runs unchanged; the agentic
176
+ enrichment (step 4) and `ui-expert` scaffold degrade to TODO + a clear note.
177
+ - **Older CLI:** `baldart ds-gate` / `baldart tokens build` absent →
178
+ SKIP-with-reason, never block (every BALDART gate's fail-safe contract). Surface
179
+ the SKIP; do not claim a gate ran when it did not.
180
+
181
+ ## /prd integration
182
+
183
+ `/prd`'s reconciliation gate (`prd/references/ui-design-phase.md`, the "NEW
184
+ (governed) — confirmed with user" branch) OWNS the decision *whether* a new
185
+ element is needed; `/ds-new` EXECUTES the creation. When `/prd` invokes it with a
186
+ resolved `region → NewComponent — role: family@context — standard:y/n — closed:y/n`,
187
+ skip P1/P2/step 1 (already reconciled) and run from step 2. No rule is duplicated
188
+ across the two.
189
+
190
+ ## See Also
191
+
192
+ - `framework/agents/design-system-protocol.md` — the cascade, closed-set, token
193
+ cascade, governance, and Post-Intervention rules this skill delegates to.
194
+ - `framework/agents/component-manifest-schema.md` — the HEAD schema.
195
+ - `framework/templates/component-spec.template.md` — the canonical prose template
196
+ the serializer fills (the standardized doc shape).
197
+ - `framework/.claude/skills/design-system-init/SKILL.md` — the bulk twin; shares
198
+ the same scripts (`extract-manifest`/`extract-one`/`serialize-spec`).
199
+ - `framework/docs/COMPONENT-MANIFEST-LAYER.md` — the layer overview.
@@ -186,6 +186,16 @@ Before invoking `ui-design`:
186
186
  it from then on. A new standard for *all* views also opens a **migration
187
187
  follow-up card** for the existing views (never migrate them in this PRD).
188
188
 
189
+ 7. **Create now (OPT-IN) vs bind-and-defer (default).** For each `NEW (governed)`
190
+ element, the default is **bind-and-defer**: persist the binding (step 6) and let
191
+ `ui-expert` create the component at `/new` implement time. When the user wants it
192
+ materialized **now** (the canonical element + its standardized spec + INDEX entry
193
+ + governance, before any card runs), invoke **`/ds-new`** with the resolved tuple
194
+ (`NewComponent — role: family@context — standard:y/n — closed:y/n`); it skips its
195
+ own reuse step (already reconciled here) and runs from its create step. `/prd`
196
+ owns the *decision*; `/ds-new` executes the *creation* — no rule is duplicated.
197
+ This is additive: omit it and the default bind-and-defer path is unchanged.
198
+
189
199
  Pass the resolved inventory to `ui-design`:
190
200
 
191
201
  ```
@@ -192,7 +192,10 @@ This keeps consumers that have not yet upgraded (and non-TS stacks) from becomin
192
192
  ## Producers & consumers
193
193
 
194
194
  - **Bootstrap / upgrade**: `/design-system-init` (deterministic extract + agentic
195
- enrich; greenfield and upgrade modes).
195
+ enrich; greenfield and upgrade modes; bulk).
196
+ - **Single-element create**: `/ds-new` (one component or token, on-the-fly or
197
+ called by `/prd`; same scripts — `extract-one`/`serialize-spec` — as the bulk
198
+ skill, so the HEAD it writes is identical).
196
199
  - **Per-task write**: `ui-expert` / `ui-design` / `frontend-design` in the
197
200
  Post-Intervention Coherence Check — a new/modified primitive regenerates its
198
201
  HEAD in the same change.
@@ -360,7 +360,10 @@ agent has two choices:
360
360
  - **Add to the registry** — preferred when the primitive is broadly useful.
361
361
  Create the per-component spec, the implementation under
362
362
  `${paths.components_primitives}/<Name>`, and an INDEX.md entry in the same
363
- change.
363
+ change. The guided single-element path is the **`/ds-new`** skill (reuse-first →
364
+ optional scaffold → document + register + govern + verify); it produces the
365
+ standardized spec by filling the canonical template via the serializer. The bulk
366
+ analogue (whole registry at once) is `/design-system-init`.
364
367
  - **Local one-off** — only when the use case is genuinely surface-specific
365
368
  (e.g. a marketing-page hero illustration). Even then, prefer composing
366
369
  existing primitives over hand-rolling a new one. Justify the local choice
@@ -653,7 +656,8 @@ The following files reference this module instead of duplicating its rules:
653
656
  intrinsic-quality critic consumed by `/e2e-review` Phase 4b)
654
657
  - `framework/.claude/skills/ui-design/SKILL.md`
655
658
  - `framework/.claude/skills/frontend-design/SKILL.md`
656
- - `framework/.claude/skills/design-system-init/SKILL.md`
659
+ - `framework/.claude/skills/design-system-init/SKILL.md` (bulk registry bootstrap/upgrade)
660
+ - `framework/.claude/skills/ds-new/SKILL.md` (single-element guided creation)
657
661
  - `framework/.claude/commands/design-review.md`
658
662
  - `framework/agents/design-review.md`
659
663
 
@@ -21,6 +21,7 @@ Route agents to the right module with minimal reading.
21
21
  - If touching UI pages/routes or flows -> read `${paths.references_dir}/ui/index.md` (then specific UI module for your domain).
22
22
  - If touching ANY visual surface (component, page, mockup, design review) and `features.has_design_system: true` -> read `agents/design-system-protocol.md` and treat the BLOCKING cascade (`${paths.design_system}/INDEX.md` + `tokens-reference.md` + `components/<Name>.md`) as mandatory pre-work. When the flag is `false`, recommend `/design-system-init` to scaffold a registry.
23
23
  - If a UI change might INTRODUCE a component (designing from a mockup, building a new primitive) and `features.has_design_system: true` -> read `agents/design-system-protocol.md` § "Closed-Set Selection Policy": query the registry by ROLE (`INDEX.md` § Selection Policy), reuse the canonical member of any `closed` family, honor the card's `component_bindings`, and run `baldart ds-gate` (a `DS_CLOSED_SET_VIOLATION` — a new canonical in a closed family, the "third header" failure mode — BLOCKS). A new member of a closed family is a governance decision (`/prd` reconciliation + ADR), never an implementation act.
24
+ - If creating ONE new canonical design-system element on-the-fly (a component or a token) and `features.has_design_system: true` -> use the `/ds-new` skill (reuse-first → optional scaffold via `ui-expert` → document + register + govern + verify); it produces the standardized spec by filling `framework/templates/component-spec.template.md` via the serializer, never hand-writing a HEAD. The bulk analogue (whole registry) is `/design-system-init`.
24
25
  - If CREATING, UPDATING, or READING a per-component spec (`${paths.design_system}/components/<Name>.md`) -> read `agents/component-manifest-schema.md` for the machine-readable frontmatter HEAD (deterministic-from-TS + agentic fields) and the on-demand read contract (INDEX router → frontmatter-first, prose only when modifying). The drift codes + gate stay in `design-system-protocol.md`.
25
26
  - If touching design-review workflows or UI guidelines -> read `agents/design-review.md` and project-specific UI guidelines.
26
27
  - If touching architecture, auth, or tech stack -> read `agents/architecture.md`.
@@ -40,8 +40,8 @@ BALDART wraps the **standard** (DTCG) and owns only a thin generator — the sam
40
40
  |---|---|---|
41
41
  | HEAD schema | `framework/agents/component-manifest-schema.md` | SSOT for the frontmatter fields + read/regeneration/leniency contract |
42
42
  | Gate + cascade | `framework/agents/design-system-protocol.md` | BLOCKING reads, drift codes, token SSOT inversion, Authority Matrix |
43
- | Deterministic extractor | `framework/.claude/skills/design-system-init/scripts/extract-manifest.mjs` | zero-dep Node; name/source/source_sha/props/variants/composes (Codex-portable) |
44
- | Spec template | `…/design-system-init/scripts/component-spec.template.md` | HEAD + prose scaffold |
43
+ | Deterministic extractor | `framework/.claude/skills/design-system-init/scripts/extract-manifest.mjs` (`extractFile`) + `extract-one.mjs` (single-component wrapper) | zero-dep Node; name/source/source_sha/props/variants/composes (Codex-portable) |
44
+ | Spec template | `framework/templates/component-spec.template.md` | CANONICAL prose-body template (SSOT); serializer fills it on creation via `--prose-template` (default) |
45
45
  | Bootstrap/upgrade | `framework/.claude/skills/design-system-init/SKILL.md` | greenfield + upgrade modes |
46
46
  | Token emitters | `src/utils/token-emitters/{index,ts,css}.js` | DTCG → stack-native output (REGISTRY pattern) |
47
47
  | Token generator | `src/utils/tokens-generator.js` | parse `.tokens.json`, resolve `{aliases}`, build/isStale |
@@ -1,8 +1,21 @@
1
- <!-- NOTE: the frontmatter HEAD below is EMITTED DETERMINISTICALLY by
2
- scripts/serialize-spec.mjs (field data in, valid YAML out). Do NOT hand-write
3
- or hand-fill it invalid YAML (e.g. `props:{}` without a space, unquoted
4
- multiline strings) is exactly the bug the serializer prevents. This template
5
- documents the SHAPE + is the reference for the PROSE BODY below the fences. -->
1
+ <!-- CANONICAL per-component spec template (since v4.69.0). SSOT for the SHAPE of
2
+ every `${paths.design_system}/components/<Name>.md`. Consumed by
3
+ scripts/serialize-spec.mjs (it fills the PROSE BODY below the fences on
4
+ creation) and cited by /ds-new, /design-system-init, ui-expert, doc-reviewer.
5
+ This is framework-internal (machinery), not a hand-edit copy.
6
+
7
+ The frontmatter HEAD is EMITTED DETERMINISTICALLY by serialize-spec.mjs
8
+ (field data in, valid YAML out) — NEVER hand-write or hand-fill it (invalid
9
+ YAML, e.g. `props:{}` without a space, is exactly the bug the serializer
10
+ prevents). The block below documents the HEAD shape; the serializer owns it.
11
+
12
+ RETRIEVAL CONTRACT (why this shape — see `doc-writing-for-rag` SKILL.md +
13
+ `agents/component-manifest-schema.md` § "Read contract"): agents discover a
14
+ component by reading the thin INDEX router → then ONLY this HEAD frontmatter
15
+ (never this prose body, never the source) — the prose is read solely when
16
+ MODIFYING the component. So: keep the body scan-able (tables > prose), one
17
+ fact per owner (visual values live in tokens-reference.md, not here),
18
+ cross-ref instead of repeating, and store token IDs (not values). -->
6
19
  ---
7
20
  # Machine-readable HEAD — schema: framework/agents/component-manifest-schema.md
8
21
  # DETERMINISTIC fields (regenerated from source — do not hand-curate):
@@ -20,6 +33,10 @@ token_bindings: {{token_bindings_yaml}} # DTCG token IDs (not values)
20
33
  a11y: "{{a11y}}"
21
34
  related: {{related_yaml}}
22
35
  must_rules: {{must_rules_yaml}}
36
+ # Selection policy (closed-set — see design-system-protocol.md § Closed-Set Selection Policy):
37
+ canonical_for: {{canonical_for_yaml}} # ["<family>@<context>"] roles this is THE canonical answer for ([] = none)
38
+ use_when: "{{use_when}}" # one-line predicate: pick this over its siblings when…
39
+ selection_closed: {{selection_closed}} # true ⇒ the role family is a CLOSED set (new member = governance, not code)
23
40
  last_verified: {{today}}
24
41
  owner: doc-reviewer
25
42
  ---
@@ -27,10 +44,12 @@ owner: doc-reviewer
27
44
  # {{ComponentName}}
28
45
 
29
46
  > Per-component spec. The HEAD above is the machine-readable contract (read it
30
- > first for discovery); this prose body is the human long-form rationale.
31
- > Authoritative for props, variants, accessibility, and anti-patterns. Visual
32
- > authority (color/shadow/radius/spacing) belongs to the DTCG token SSOT
33
- > (`${paths.design_tokens}`), surfaced via [`../tokens-reference.md`](../tokens-reference.md).
47
+ > first for discovery); this prose body is the human long-form rationale, read
48
+ > only when MODIFYING this component. Authoritative for props, variants,
49
+ > accessibility, and anti-patterns. Visual authority (color/shadow/radius/spacing)
50
+ > belongs to the DTCG token SSOT (`${paths.design_tokens}`), surfaced via
51
+ > [`../tokens-reference.md`](../tokens-reference.md) — never duplicate token
52
+ > values here.
34
53
 
35
54
  ## Source
36
55
 
@@ -67,9 +86,10 @@ when to use it. Example:
67
86
 
68
87
  {{tokens_section}}
69
88
 
70
- <!-- TODO: list the tokens this component reads from tokens-reference.md.
71
- Color, spacing, shadow, radius, motion. If you find hardcoded literals in
72
- the source, that's a registry violation — fix at the source. -->
89
+ <!-- TODO: these are DTCG token IDs (not values) — one read of tokens-reference.md
90
+ resolves every value. List the color / spacing / shadow / radius / motion tokens
91
+ this component reads. A hardcoded literal in the source is a registry violation —
92
+ fix it at the source, add a token if one is missing. -->
73
93
 
74
94
  ## Accessibility
75
95
 
@@ -104,4 +124,4 @@ Cross-link to patterns/ docs when applicable. -->
104
124
  <!-- Track breaking changes to the spec. Bump version when removing a variant
105
125
  or changing a default. -->
106
126
 
107
- - {{today}} — Spec scaffolded by `/design-system-init`.
127
+ - {{today}} — Spec scaffolded by BALDART (`/ds-new` / `/design-system-init`).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "baldart",
3
- "version": "4.68.2",
3
+ "version": "4.69.0",
4
4
  "description": "Claude Agent Framework - Reusable framework for coordinating AI agents and humans in software projects",
5
5
  "bin": {
6
6
  "baldart": "./bin/baldart.js"
@@ -785,6 +785,7 @@ class SymlinkUtils {
785
785
  const FRAMEWORK_INTERNAL = new Set([
786
786
  'baldart.config.template.yml',
787
787
  'skill-project-context.snippet.md',
788
+ 'component-spec.template.md', // canonical prose-body template — consumed by serialize-spec.mjs, not hand-edited
788
789
  'overlays', // example overlays directory — consumers reference, don't copy wholesale
789
790
  ]);
790
791