baldart 4.68.1 → 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,35 @@ 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
+
25
+ ## [4.68.2] - 2026-06-24
26
+
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.
28
+
29
+ **PATCH** — tightens the deferral classification of an existing gate; no new config key, no new agent/skill. Deliberately NOT a blanket "all docs block merge" (that regresses the follow-up mechanism by coupling ready code to genuinely-separate design decisions) — only *own-docs* block; adjacent/cross-cutting docs still defer to a tracked follow-up.
30
+
31
+ ### Changed
32
+
33
+ - **New SSOT § "Documentation Closure"** in `agents/workflows.md` (twin of the AC Scope Closure Discipline, STRICTER) — a change's OWN docs must be SYNCED in the same PR before merge, no follow-up escape, a stub is not synced. "Own-docs" is **deterministic** (not the agent's discretion to relabel as adjacent): (1) the spec of any modified primitive, (2) the screen/route doc for a touched route, (3) the card's declared `documentation_impact`/`canonical_docs`. Anything outside those three is adjacent and still deferrable.
34
+ - **`design-system-protocol.md` Decision Matrix tightened** — a spec update that DOCUMENTS the behaviour THIS change introduced (a new/changed `must_rule`/prop/variant/slot/a11y hook) is **inline-BLOCKING, not deferrable**; only a genuinely SEPARATE design decision (a new variant's full semantics, breaking-change migration design, an unresolved a11y trade-off) remains the deferrable case.
35
+ - **`doc-reviewer` Own-Documentation Closure gate** — own-doc obligations are `synced` or a **BLOCKER** (`DOC_OWN_STUB` for a stub-only own-route doc, `DS_COMPONENT_STALE` for a modified-primitive spec), emitted as `BLOCKER` (never a `LOW`/`MED` note, never a silent "deferred"). Flows through the existing pre-merge severity gate (an unresolved BLOCKER blocks the merge — `final-review.md`) and the `doc` domain-override (never inline-bypassed). Shared agent + shared `new-final-review.js` → enforced in BOTH `/new` and `new2` with no separate wiring.
36
+
8
37
  ## [4.68.1] - 2026-06-24
9
38
 
10
39
  **`/design-system-init` now actually ACTIVATES the closed-set gate it just populated — found on the first real `mayo` upgrade run.** The v4.68.0 upgrade correctly derived + confirmed the closed-set data (the two-header `page-header` family + a `range-slider` pair landed in the component HEADs with `canonical_for`/`selection_closed: true`), but `baldart ds-gate` stayed `no-policy` and never enforced. Root cause: step 6's serializer bundle merged "deterministic (step 3) + agentic (step 4)" but NOT the step-4b/4c selection-policy fields, so `emitSelectionPolicy` ran over components without `canonical_for` and generated an EMPTY `INDEX.md § "Selection Policy"` block (the HEADs got the fields via a side path; the gate reads the INDEX block). Data present, gate dormant.
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.1
1
+ 4.69.0
@@ -246,7 +246,33 @@ Run this before any general audit:
246
246
  7. Write missing docs directly. Prefer extending existing reference docs over creating new files unless the feature is large enough to justify a standalone document. For type-specific checklists, read `.claude/skills/doc-reviewer-support/references/doc-checklists.md` **if it exists**; otherwise apply the doc-type rules and invariant table above and the `doc-writing-for-rag` skill's templates.
247
247
  8. Update indexes when new docs are added.
248
248
  9. Assess Obsidian/wiki sync need (section H) — **OPTIONAL, only when `features.has_wiki_overlay: true` in `baldart.config.yml`**. If the flag is absent or false, skip section H with a one-line "no wiki overlay" note. When the flag is true, read `.claude/skills/doc-reviewer-support/references/obsidian-integration.md` for trigger conditions **if it exists**; otherwise assess on first principles (new cross-cutting concept, renamed canonical, deprecated doc) and note the need without dispatching any sync agent here.
249
- 10. Report what was checked, added, fixed, and deferred.
249
+ 10. Report what was checked, added, fixed, and deferred — but see the Own-Documentation Closure gate below: an own-doc may NOT appear in the "deferred" bucket.
250
+
251
+ ### Own-Documentation Closure (BLOCKING — own-docs close in this PR, no defer)
252
+
253
+ Per `agents/workflows.md § Documentation Closure`, the documentation of **what
254
+ this change itself touched** must be SYNCED in this diff before merge — it is NOT
255
+ deferrable to a follow-up, and a STUB is not synced. Build the own-doc obligation
256
+ list deterministically from the diff + card (do not relabel an own-doc as
257
+ "adjacent" to defer it):
258
+
259
+ 1. **Modified-primitive spec** — every component whose source changed → its
260
+ `components/<Name>.md` reflects the shipped behaviour (the `must_rules` / props /
261
+ slots the change introduced). A stale/stub spec is `DS_COMPONENT_STALE`.
262
+ 2. **Touched route/screen doc** — the diff changed a route/page → the screen/route
263
+ doc for THAT route is synced (not just stubbed). A stub-only doc for a route you
264
+ changed is `DOC_OWN_STUB` (the screens/README "stub but full-sync deferred"
265
+ pattern — not allowed for an own-route).
266
+ 3. **Declared `documentation_impact` / `canonical_docs`** — every doc the card named
267
+ as in-scope is synced.
268
+
269
+ Each obligation is `synced` or it is a **BLOCKER** finding (`DOC_OWN_STUB` /
270
+ `DS_COMPONENT_STALE`) — emit it as `BLOCKER` in section F/G, never as a `LOW`/`MED`
271
+ note and never silently in a "deferred" list. In WRITE mode you fix it yourself
272
+ (you are fully responsible for feature docs — `## Constraints`); you do NOT defer
273
+ an own-doc to another agent or a later card. Only **adjacent** docs (a broader
274
+ rewrite, a genuinely separate design decision, areas the change only grazed) may be
275
+ reported as a tracked follow-up.
250
276
 
251
277
  ## Audit Process (general documentation review)
252
278
  1. **Map**: Build tree of all docs with purpose annotations.
@@ -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
@@ -600,7 +603,8 @@ For each item in the diff that touched a visual surface, run these 4 checks:
600
603
  | Situation | Action |
601
604
  |---|---|
602
605
  | Spec / token update is trivial (variant added, prop renamed, token value tweaked) | Apply inline — same commit as the code change |
603
- | Spec / token update requires design judgement (new variant semantics, breaking change, accessibility decision) | Open a follow-up card; **document the gap explicitly** in the change description; flag for `code-reviewer` |
606
+ | Spec update DOCUMENTS the behaviour THIS change introduced (a new/changed `must_rule`, prop, variant, slot, a11y hook the diff added) | **Apply inline — BLOCKING, not deferrable.** This is an *own-doc* (`agents/workflows.md § Documentation Closure`): documenting what you just shipped is part of the change, never a follow-up. A stub is not synced. |
607
+ | Spec / token update requires a genuinely SEPARATE design decision (a new variant's full semantics needing its own design pass, a breaking change needing migration design, an unresolved accessibility trade-off) — beyond documenting this change | Open a follow-up card; **document the gap explicitly** in the change description; flag for `code-reviewer`. This is the ONLY deferrable case. |
604
608
  | Reused undocumented primitive that is broadly useful | Add INDEX entry inline; if a full spec needs design work, scaffold a stub spec marked `status: needs-review` |
605
609
  | Reused undocumented primitive that is surface-specific | Add INDEX entry with `scope: local` and a 2-line note explaining why no full spec is needed |
606
610
  | Introduced a primitive that **duplicates** an existing registry entry | **Stop**. This is the failure mode the cascade exists to prevent. Revert and reuse the existing primitive, or escalate to refactor the existing primitive if the new requirement is legitimately broader |
@@ -652,7 +656,8 @@ The following files reference this module instead of duplicating its rules:
652
656
  intrinsic-quality critic consumed by `/e2e-review` Phase 4b)
653
657
  - `framework/.claude/skills/ui-design/SKILL.md`
654
658
  - `framework/.claude/skills/frontend-design/SKILL.md`
655
- - `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)
656
661
  - `framework/.claude/commands/design-review.md`
657
662
  - `framework/agents/design-review.md`
658
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`.
@@ -61,6 +61,38 @@ When a card's `acceptance_criteria` cannot be fully implemented as written, the
61
61
 
62
62
  Any deferral travels through an explicit `AskUserQuestion` with the AC text quoted verbatim, one question per AC. For orchestrator skills (`/new`, `/prd`) this discipline is enforced in the per-card AC-closure gate before commit. Auto Mode's "bias toward proceeding" applies to routine decisions, NOT to scope reduction — silent deferral of a numbered AC is a violation even in Auto Mode.
63
63
 
64
+ ### Documentation Closure (own-docs close before merge — no defer)
65
+
66
+ The same discipline applies to the **documentation of what the change itself
67
+ touched** — and is STRICTER than ACs: a change's OWN docs must be reconciled
68
+ **in the same PR, before merge**. They may NOT be deferred to a post-merge
69
+ follow-up, and a STUB is not "synced". Leaving the doc of the thing you just
70
+ changed stale at merge is the drift entry door the Post-Intervention philosophy
71
+ exists to close ("reconcile at source, not next Monday").
72
+
73
+ **"Own docs" is deterministic** (derivable from the diff + card — not the agent's
74
+ discretion to relabel as "adjacent"):
75
+
76
+ 1. **Modified-primitive spec** — any component whose source changed in the diff →
77
+ its `${paths.design_system}/components/<Name>.md` (incl. the `must_rules` /
78
+ behaviour the change introduced). This is `DS_COMPONENT_STALE` and is already
79
+ blocking; it is **not** deferrable as "needs design judgement" when it merely
80
+ documents what this change did (see `agents/design-system-protocol.md`
81
+ Decision Matrix).
82
+ 2. **Touched route/screen doc** — if the diff changes a route/page, the screen/route
83
+ doc for THAT route (`${paths.references_dir}/ui/…` / `docs/design/screens/…`).
84
+ 3. **Declared `documentation_impact` / `canonical_docs`** — the docs the card itself
85
+ named as in-scope.
86
+
87
+ Each own-doc obligation is `synced` (the doc reflects the shipped change, in this
88
+ diff) or it BLOCKS the merge. There is no `deferred` state for an own-doc and no
89
+ follow-up-card escape — the only resolutions are *sync it now* or an explicit
90
+ `[USER-APPROVED DEFERRAL]` recorded via `AskUserQuestion` (interactive) / surfaced
91
+ as a blocker (autonomous). **Adjacent / cross-cutting docs** — a broader rewrite, a
92
+ genuinely separate design decision, docs for areas the change only grazed — are
93
+ NOT own-docs: those legitimately defer to a tracked follow-up card. The boundary is
94
+ the three deterministic categories above; anything outside them is adjacent.
95
+
64
96
  ### Coder authority (no defer)
65
97
 
66
98
  Coder agents report `done | failed` at card level. Per-requirement signals (`done | not_implemented`) are inputs to the orchestrator's AC-Closure Gate, never terminal verdicts. Coders have NO authority to declare an AC `deferred`, `out of scope`, `covered by X`, `partial`, `blocked`, or any equivalent. Only the user, via the gate's `AskUserQuestion`, can approve a deferral. Inventing constraints — `time budget`, `context budget`, `context pressure as override`, or any phrase not enumerated in a phase's documented Gate table — to skip phases or defer ACs is a protocol violation. The same rule applies to orchestrator agents (sequential and team-mode L0/L1/L2/L3): they relay coder signals into the gate; they do not aggregate, summarize, or pre-filter deferrals.
@@ -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.1",
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