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 +29 -0
- package/README.md +2 -2
- package/VERSION +1 -1
- package/framework/.claude/agents/doc-reviewer.md +27 -1
- package/framework/.claude/skills/design-system-init/SKILL.md +4 -3
- package/framework/.claude/skills/design-system-init/scripts/extract-manifest.mjs +41 -28
- package/framework/.claude/skills/design-system-init/scripts/extract-one.mjs +39 -0
- package/framework/.claude/skills/design-system-init/scripts/serialize-spec.mjs +46 -5
- package/framework/.claude/skills/ds-new/SKILL.md +199 -0
- package/framework/.claude/skills/prd/references/ui-design-phase.md +10 -0
- package/framework/agents/component-manifest-schema.md +4 -1
- package/framework/agents/design-system-protocol.md +8 -3
- package/framework/agents/index.md +1 -0
- package/framework/agents/workflows.md +32 -0
- package/framework/docs/COMPONENT-MANIFEST-LAYER.md +2 -2
- package/framework/{.claude/skills/design-system-init/scripts → templates}/component-spec.template.md +33 -13
- package/package.json +1 -1
- package/src/utils/symlinks.js +1 -0
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 (
|
|
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.
|
|
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
|
-
- `
|
|
375
|
-
- `scripts/extract-manifest.mjs` — deterministic, zero-dep HEAD extractor (Codex-portable).
|
|
376
|
-
- `scripts/
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
145
|
-
if (
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
/**
|
|
95
|
-
|
|
96
|
-
|
|
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
|
|
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 |
|
|
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 |
|
package/framework/{.claude/skills/design-system-init/scripts → templates}/component-spec.template.md
RENAMED
|
@@ -1,8 +1,21 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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,
|
|
32
|
-
> authority (color/shadow/radius/spacing)
|
|
33
|
-
> (`${paths.design_tokens}`), surfaced via
|
|
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:
|
|
71
|
-
|
|
72
|
-
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
package/src/utils/symlinks.js
CHANGED
|
@@ -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
|
|