baldart 4.66.2 → 4.67.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 +15 -0
- package/VERSION +1 -1
- package/framework/.claude/agents/code-reviewer.md +5 -1
- package/framework/.claude/agents/doc-reviewer.md +14 -1
- package/framework/.claude/skills/design-system-init/SKILL.md +19 -8
- package/framework/.claude/skills/design-system-init/scripts/serialize-spec.mjs +61 -2
- package/framework/agents/component-manifest-schema.md +16 -3
- package/framework/agents/design-system-protocol.md +7 -2
- package/framework/routines/ds-drift.routine.yml +5 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,21 @@ 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.67.0] - 2026-06-23
|
|
9
|
+
|
|
10
|
+
**Token-binding CORRECTNESS check + PRD-Inventory-sourced enrichment — the survivor of an adversarial pass that killed a design-time "intent seed".** Goal: capture design intent ("when/how/which-tokens") so the implementing agent doesn't reverse-engineer the manifest's agentic fields from code. The proposed `component-intent.yml` seed (emitted by `/prd`) was refuted 3/3 — it duplicates the PRD's existing UI Element Inventory + the manifest (twin), goes stale via `/prd-add` REDO, survives-but-wrong (the v4.66.2 guard checks token EXISTENCE, not correctness-for-this-component), and as an on-disk prose file would be skipped/unowned/Codex-inert. This ships the deterministic survivors instead.
|
|
11
|
+
|
|
12
|
+
**MINOR** — adds a capability (usage check + intent-source wiring); no new config key, no install change. Schema-change propagation rule does NOT apply.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **Token-binding correctness check (`serialize-spec.mjs --verify-usage`)** — closes the half v4.66.2 left open (existence ≠ correctness): a binding can exist in the DTCG yet be wrong for this component. `verifyBindingUsage(components, cwd)` (exported) reads each component's effective styling surface — its own `source` (+ sibling `.module.css`/`.css`) **plus the sources of the primitives it directly `composes`** (a wrapper presenting an overlay via a composed Popover legitimately binds `zIndex.overlay`) — and reports any `token_bindings` id absent from that surface as `unverifiedBindings`. **ADVISORY, never a drop/block** (a binding can be legitimately indirect — Tailwind classes, a non-sibling stylesheet — so a hard block would false-fire). Resolves source from the `source` field, never a guessed path. Validated on mayo: 96% of 603 bindings verified; the residual surfaces for review, exit 0.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- **Intent-sourced enrichment (no new artifact)** — `doc-reviewer` + `/design-system-init` step 4 now fill the agentic HEAD fields (`purpose`/`a11y`/`must_rules`) from the PRD's existing **UI Element Inventory** (`Purpose`/`function_ref`/`Behavior`/`States`) when the component traces to a PRD (card `links.prd`/`links.design`), reconciled against the implemented source — capturing design-time intent instead of reverse-engineering it. Clean fallback to source-only when no PRD exists. `token_bindings` are now picked preferring ids the source actually uses.
|
|
21
|
+
- **Advisory propagated to the three Post-Intervention sites** — `design-system-protocol.md` (check 3), `code-reviewer.md` (Rule 8), and the `ds-drift` routine now flag a resolves-but-unused binding as a `DS_TOKENS_DRIFT` **advisory** (review-required: fix or justify as indirect), distinct from the hard existence drift. SSOT'd in `component-manifest-schema.md` (existence = HARD/drop; usage = ADVISORY/report). `/design-system-init` passes `--verify-usage` on every generation.
|
|
22
|
+
|
|
8
23
|
## [4.66.2] - 2026-06-23
|
|
9
24
|
|
|
10
25
|
**`token_bindings` are now validated against the real DTCG vocabulary — fixing dangling bindings found on a real upgrade.** After the v4.66.1 HEAD-serialization fix, a real `/design-system-init` upgrade still produced 26/586 `token_bindings` that didn't resolve in `.tokens.json` (`font.weight.medium` instead of `typography.fontWeight.medium`, `z.overlay` vs `zIndex.overlay`, `space.2` vs `space.sm`, …). Root cause: the agentic enricher (`doc-reviewer`) writes `token_bindings` as free text and guesses plausible-but-wrong names. A dangling binding id is `DS_TOKENS_DRIFT`. This closes the gap so it cannot be written, and instructs the enricher to pick from the actual token vocabulary.
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
4.
|
|
1
|
+
4.67.0
|
|
@@ -58,7 +58,11 @@ Concrete enforcement contract for the review pass:
|
|
|
58
58
|
2. For any component in the diff, cross-check against its
|
|
59
59
|
`components/<Name>.md` HEAD — variants, props, `token_bindings`, a11y,
|
|
60
60
|
`must_rules`. A `token_bindings` ID that does not resolve in the DTCG source
|
|
61
|
-
(`paths.design_tokens`) is `DS_TOKENS_DRIFT
|
|
61
|
+
(`paths.design_tokens`) is `DS_TOKENS_DRIFT` (hard). A `token_bindings` ID that
|
|
62
|
+
resolves but is NOT used in the component's effective styling surface (own
|
|
63
|
+
source + directly-composed children) is a `DS_TOKENS_DRIFT` **advisory** (flag
|
|
64
|
+
for review — the binding is likely over-attributed or wrong; not a merge-block,
|
|
65
|
+
since it can be legitimately indirect).
|
|
62
66
|
3. Enforce design-system MUST rules (the spec HEAD's `must_rules` + project INDEX)
|
|
63
67
|
as HIGH-confidence findings. Typical rules include:
|
|
64
68
|
- No hardcoded hex, shadow, radius, spacing literals — only tokens. When
|
|
@@ -71,7 +71,20 @@ Never clobber a filled agentic field on a refresh. **Pick `token_bindings` from
|
|
|
71
71
|
the REAL DTCG vocabulary** — read the id list from `${paths.design_tokens}` and
|
|
72
72
|
use exact ids (`typography.fontWeight.medium`, `zIndex.overlay`, `space.sm`),
|
|
73
73
|
never invented names (`font.weight.medium`, `z.overlay`, `space.2`); the
|
|
74
|
-
serializer drops unresolved ids
|
|
74
|
+
serializer drops unresolved ids (existence) and **flags ids not used in the
|
|
75
|
+
component's source** (correctness, advisory) — so prefer the ids the source
|
|
76
|
+
actually consumes.
|
|
77
|
+
|
|
78
|
+
**Intent source order (when the component traces to a PRD/design).** Do NOT
|
|
79
|
+
reverse-engineer intent from code alone when a richer source exists. If the work
|
|
80
|
+
traces to a PRD (the card's `links.prd` / `links.design`), read that PRD's **UI
|
|
81
|
+
Element Inventory** row for this component (its `Purpose`, `function_ref`,
|
|
82
|
+
`Behavior`, `States`) and use it as the **primary source** for `purpose` /
|
|
83
|
+
`a11y` / `must_rules` — it captures the design-time intent ("when/how to use")
|
|
84
|
+
that the implemented code does not encode. Then **reconcile against the
|
|
85
|
+
implemented source** (the deterministic fields + actual token usage are ground
|
|
86
|
+
truth; the Inventory is intent, not API). Fallback: when there is no PRD/Inventory
|
|
87
|
+
for the component, infer from the source as before. (This is the design-system twin
|
|
75
88
|
of your i18n-registry ownership: you curate context, tooling fills the mechanical part.)
|
|
76
89
|
|
|
77
90
|
Standard drift checks against this corpus:
|
|
@@ -95,8 +95,15 @@ precedence caveats: `framework/agents/effort-protocol.md`.
|
|
|
95
95
|
id list from `${paths.design_tokens}` (the DTCG `.tokens.json`) and pick exact
|
|
96
96
|
ids (`typography.fontWeight.medium`, `zIndex.overlay`, `space.sm`), never
|
|
97
97
|
invent plausible-but-wrong names (`font.weight.medium`, `z.overlay`, `space.2`).
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
Prefer the ids the component's **source actually uses** — the serializer's
|
|
99
|
+
`--tokens` guard (step 6) drops unknown ids and `--verify-usage` flags ids not
|
|
100
|
+
used in the source as advisory drift; picking from real usage avoids both.
|
|
101
|
+
**Intent source order:** if the component traces to a PRD (a card's
|
|
102
|
+
`links.prd` / `links.design`), read that PRD's **UI Element Inventory** row
|
|
103
|
+
(`Purpose`/`function_ref`/`Behavior`/`States`) as the PRIMARY source for
|
|
104
|
+
`purpose`/`a11y`/`must_rules` (design-time intent the code does not encode),
|
|
105
|
+
then reconcile against the implemented source. Fallback to source-only when no
|
|
106
|
+
PRD/Inventory exists.
|
|
100
107
|
5. **Bootstrap the DTCG token SSOT.** See § Token SSOT bootstrap below.
|
|
101
108
|
6. **Write the files — DETERMINISTICALLY (the HEAD is NEVER model-authored).**
|
|
102
109
|
Merge the deterministic JSON (step 3) with the agentic JSON (step 4) per
|
|
@@ -105,7 +112,7 @@ precedence caveats: `framework/agents/effort-protocol.md`.
|
|
|
105
112
|
node .framework/framework/.claude/skills/design-system-init/scripts/serialize-spec.mjs \
|
|
106
113
|
--bundle <bundle.json> --out-dir ${paths.design_system}/components \
|
|
107
114
|
--index ${paths.design_system}/INDEX.md --brand "${identity.brand_name}" \
|
|
108
|
-
--tokens ${paths.design_tokens}
|
|
115
|
+
--tokens ${paths.design_tokens} --verify-usage
|
|
109
116
|
```
|
|
110
117
|
It emits every `components/<Name>.md` (valid-YAML HEAD via deterministic
|
|
111
118
|
serialization + preserved prose) **and** the thin INDEX router, then
|
|
@@ -115,11 +122,15 @@ precedence caveats: `framework/agents/effort-protocol.md`.
|
|
|
115
122
|
`purpose`/`a11y`): the model supplies field *data*, the script owns the YAML.
|
|
116
123
|
Do NOT write the HEAD by hand or via the prose template.
|
|
117
124
|
`--tokens` (when `paths.design_tokens` is set) activates the **binding
|
|
118
|
-
vocabulary guard
|
|
119
|
-
`.tokens.json` is DROPPED and REPORTED (
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
125
|
+
vocabulary guard** (existence): any `token_bindings` id that does not resolve
|
|
126
|
+
in the DTCG `.tokens.json` is DROPPED and REPORTED (`droppedBindings`) — so a
|
|
127
|
+
`DS_TOKENS_DRIFT` can never be written. `--verify-usage` activates the
|
|
128
|
+
**correctness check**: ids that resolve but are NOT used in the component's
|
|
129
|
+
styling surface (own source + composed children) are REPORTED (not dropped) as
|
|
130
|
+
`unverifiedBindings` — an advisory review list (the binding is over-attributed
|
|
131
|
+
or wrong, OR legitimately indirect via Tailwind/non-sibling CSS). Review both
|
|
132
|
+
lists per step 4: a drop = a guessed-wrong name; an unverified id = pick the id
|
|
133
|
+
the source actually uses, or justify it.
|
|
123
134
|
- `tokens-reference.md` — **generated** human view of `.tokens.json` (or pure
|
|
124
135
|
narrative). Not a third source of values.
|
|
125
136
|
- `patterns/.gitkeep` — empty dir for future pattern docs.
|
|
@@ -169,6 +169,51 @@ export function validateBindings(components, idSet) {
|
|
|
169
169
|
return dropped;
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
+
/**
|
|
173
|
+
* Correctness check (since v4.67.0): a `token_bindings` id should actually be
|
|
174
|
+
* USED in the component's OWN source. The v4.66.2 guard only checks the id
|
|
175
|
+
* EXISTS in the DTCG — a plausible-but-wrong binding (exists globally, not used
|
|
176
|
+
* here) survives it. This reads each component's real source (`source` field +
|
|
177
|
+
* sibling `.module.css`/`.css`) and marks a binding "found" if its CSS-var form
|
|
178
|
+
* (`--<dot-as-dash>`) or the dotted id appears.
|
|
179
|
+
*
|
|
180
|
+
* ADVISORY, never a drop/block: a binding can be legitimately indirect
|
|
181
|
+
* (Tailwind-class-based with no `--var`, or in a non-sibling stylesheet) — so
|
|
182
|
+
* unverified ids are REPORTED for review (`DS_TOKENS_DRIFT` advisory), not
|
|
183
|
+
* removed. The component's effective styling surface = its OWN source + the
|
|
184
|
+
* sources of the primitives it directly `composes` (a wrapper that presents an
|
|
185
|
+
* overlay via a composed Popover legitimately binds `zIndex.overlay`); only a
|
|
186
|
+
* binding absent from that whole surface is a genuine phantom. Resolve from the
|
|
187
|
+
* `source` field (NEVER a guessed `components/<Name>.tsx` path — that
|
|
188
|
+
* mis-resolves nested components). Returns [{component, id}] for the NOT-found.
|
|
189
|
+
*/
|
|
190
|
+
export function verifyBindingUsage(components, cwd) {
|
|
191
|
+
const byName = new Map(components.map((c) => [c.name, c]));
|
|
192
|
+
const readSurface = (rel) => {
|
|
193
|
+
let s = '';
|
|
194
|
+
for (const p of [rel, rel.replace(/\.tsx?$/, '.module.css'), rel.replace(/\.tsx?$/, '.css')]) {
|
|
195
|
+
try { s += fs.readFileSync(path.join(cwd, p), 'utf8'); } catch (_) { /* may not exist */ }
|
|
196
|
+
}
|
|
197
|
+
return s;
|
|
198
|
+
};
|
|
199
|
+
const unverified = [];
|
|
200
|
+
for (const c of components) {
|
|
201
|
+
const b = c.token_bindings || [];
|
|
202
|
+
if (!b.length || !c.source) continue;
|
|
203
|
+
let src = readSurface(c.source);
|
|
204
|
+
for (const childName of c.composes || []) {
|
|
205
|
+
const child = byName.get(childName);
|
|
206
|
+
if (child && child.source) src += readSurface(child.source);
|
|
207
|
+
}
|
|
208
|
+
if (!src) continue; // cannot read any source → cannot judge; stay silent (no false flag)
|
|
209
|
+
for (const id of b) {
|
|
210
|
+
const cssVar = `--${id.replace(/\./g, '-')}`;
|
|
211
|
+
if (!src.includes(cssVar) && !src.includes(id)) unverified.push({ component: c.name, id });
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return unverified;
|
|
215
|
+
}
|
|
216
|
+
|
|
172
217
|
// CLI
|
|
173
218
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
174
219
|
const bundlePath = arg('--bundle');
|
|
@@ -176,8 +221,10 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
|
|
176
221
|
const indexPath = arg('--index');
|
|
177
222
|
const brandArg = arg('--brand');
|
|
178
223
|
const tokensPath = arg('--tokens'); // optional DTCG .tokens.json → binding vocab guard
|
|
224
|
+
const verifyUsage = process.argv.includes('--verify-usage'); // advisory source-usage check
|
|
225
|
+
const cwd = arg('--cwd', process.cwd());
|
|
179
226
|
if (!bundlePath || !outDir) {
|
|
180
|
-
console.error('usage: serialize-spec.mjs --bundle bundle.json --out-dir <dir> [--index <path>] [--brand <name>] [--tokens <.tokens.json>]');
|
|
227
|
+
console.error('usage: serialize-spec.mjs --bundle bundle.json --out-dir <dir> [--index <path>] [--brand <name>] [--tokens <.tokens.json>] [--verify-usage] [--cwd <dir>]');
|
|
181
228
|
process.exit(2);
|
|
182
229
|
}
|
|
183
230
|
const bundle = JSON.parse(fs.readFileSync(bundlePath, 'utf8'));
|
|
@@ -200,6 +247,18 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
|
|
200
247
|
}
|
|
201
248
|
}
|
|
202
249
|
|
|
250
|
+
// Binding correctness check (since v4.67.0): ADVISORY — report bindings not
|
|
251
|
+
// used in the component's own source (never drop; can be legitimately indirect).
|
|
252
|
+
let unverified = [];
|
|
253
|
+
if (verifyUsage) {
|
|
254
|
+
unverified = verifyBindingUsage(components, cwd);
|
|
255
|
+
if (unverified.length) {
|
|
256
|
+
console.error(`token_bindings: ${unverified.length} id(s) not found in their component source (DS_TOKENS_DRIFT advisory — review: fix or justify as indirect):`);
|
|
257
|
+
for (const u of unverified.slice(0, 20)) console.error(` - ${u.component}: ${u.id}`);
|
|
258
|
+
if (unverified.length > 20) console.error(` … +${unverified.length - 20} more`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
203
262
|
fs.mkdirSync(outDir, { recursive: true });
|
|
204
263
|
let invalid = 0;
|
|
205
264
|
for (const c of components) {
|
|
@@ -210,7 +269,7 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
|
|
210
269
|
if (v.checked && !v.ok) { invalid++; console.error(`INVALID HEAD ${c.name}.md: ${v.error}`); }
|
|
211
270
|
}
|
|
212
271
|
if (indexPath) fs.writeFileSync(indexPath, emitIndex(components, brand), 'utf8');
|
|
213
|
-
const report = { schema: 'baldart.serialize-spec/1', written: components.length, index: !!indexPath, invalid, droppedBindings: dropped.length };
|
|
272
|
+
const report = { schema: 'baldart.serialize-spec/1', written: components.length, index: !!indexPath, invalid, droppedBindings: dropped.length, unverifiedBindings: unverified.length };
|
|
214
273
|
console.log(JSON.stringify(report));
|
|
215
274
|
if (invalid > 0) process.exit(1); // fail-closed: never leave invalid HEADs
|
|
216
275
|
}
|
|
@@ -115,15 +115,28 @@ and `baldart doctor` treat an unparseable HEAD as drift to repair (re-run
|
|
|
115
115
|
**salvage** the agentic values leniently, then re-serialize — never strict-parse
|
|
116
116
|
and lose them.
|
|
117
117
|
|
|
118
|
-
**Token-binding vocabulary invariant:** every `token_bindings`
|
|
119
|
-
a real node in the DTCG `.tokens.json` (`paths.design_tokens`).
|
|
120
|
-
enricher picks ids from that file's actual vocabulary — never invents
|
|
118
|
+
**Token-binding vocabulary invariant (existence — HARD):** every `token_bindings`
|
|
119
|
+
id MUST resolve to a real node in the DTCG `.tokens.json` (`paths.design_tokens`).
|
|
120
|
+
The agentic enricher picks ids from that file's actual vocabulary — never invents
|
|
121
121
|
plausible-but-wrong names (`font.weight.medium`/`z.overlay`/`space.2` instead of
|
|
122
122
|
`typography.fontWeight.medium`/`zIndex.overlay`/`space.sm`). The serializer's
|
|
123
123
|
`--tokens` guard DROPS + REPORTS any unresolved id (so a `DS_TOKENS_DRIFT` is
|
|
124
124
|
never written); a dropped id means the enricher guessed wrong (fix it or add the
|
|
125
125
|
token).
|
|
126
126
|
|
|
127
|
+
**Token-binding correctness invariant (usage — ADVISORY):** existence is not
|
|
128
|
+
correctness — a token can exist globally yet be wrong for this component. A
|
|
129
|
+
`token_bindings` id SHOULD actually be USED in the component's effective styling
|
|
130
|
+
surface: its own source (`source` + sibling `.module.css`/`.css`) or the source
|
|
131
|
+
of a primitive it directly `composes` (a wrapper presenting an overlay via a
|
|
132
|
+
composed Popover legitimately binds `zIndex.overlay`). The serializer's
|
|
133
|
+
`--verify-usage` check REPORTS — never drops — any id absent from that surface as
|
|
134
|
+
a `DS_TOKENS_DRIFT` **advisory** (review-required), because a binding can be
|
|
135
|
+
legitimately indirect (Tailwind classes, a non-sibling stylesheet). The reviewer
|
|
136
|
+
either corrects the binding or justifies it; it is NOT a hard merge-block (a hard
|
|
137
|
+
block would false-fire). Pick bindings the source actually uses to keep this
|
|
138
|
+
clean.
|
|
139
|
+
|
|
127
140
|
## Regeneration contract
|
|
128
141
|
|
|
129
142
|
- **Anchor**: `source_sha`. A regeneration run skips a component whose source is
|
|
@@ -482,8 +482,13 @@ For each item in the diff that touched a visual surface, run these 4 checks:
|
|
|
482
482
|
set, the change MUST be made in `.tokens.json` and the stack-native output
|
|
483
483
|
regenerated via `baldart tokens build` (a hand-edit to the generated output is
|
|
484
484
|
the drift). Otherwise update the project's token source + reference. Either
|
|
485
|
-
way, a dangling `token_bindings` ID is drift. Stale =
|
|
486
|
-
task blocked.
|
|
485
|
+
way, a dangling `token_bindings` ID (not in the DTCG) is drift. Stale =
|
|
486
|
+
`DS_TOKENS_DRIFT`, task blocked. **Binding correctness (advisory):** a
|
|
487
|
+
`token_bindings` id that exists in the DTCG but is NOT used in the component's
|
|
488
|
+
effective styling surface (own source + directly-composed children) is a
|
|
489
|
+
`DS_TOKENS_DRIFT` **advisory** finding (review-required, NOT a block) — the
|
|
490
|
+
`serialize-spec.mjs --verify-usage` check reports these; the reviewer corrects
|
|
491
|
+
the binding or justifies it as legitimately indirect.
|
|
487
492
|
4. **Surfaced silent primitive ⇒ added to INDEX**: during the work, did you
|
|
488
493
|
reuse a primitive that exists in the source tree but is **not** in
|
|
489
494
|
`INDEX.md`? If yes, add it to the INDEX in the same change (or open a
|
|
@@ -34,7 +34,11 @@ prompt: |
|
|
|
34
34
|
verify the generated outputs (`design_tokens.outputs`) + `tokens-reference.md`
|
|
35
35
|
equal `baldart tokens build` — a hand-edited generated file or a dangling
|
|
36
36
|
`token_bindings` ID is `DS_TOKENS_DRIFT`. When unset, fall back to verifying
|
|
37
|
-
the project token source vs `tokens-reference.md`.
|
|
37
|
+
the project token source vs `tokens-reference.md`. Also run the binding
|
|
38
|
+
CORRECTNESS check (`serialize-spec.mjs --verify-usage`): a `token_bindings`
|
|
39
|
+
id not used in its component's styling surface is a `DS_TOKENS_DRIFT`
|
|
40
|
+
**advisory** — list it for review (fix the binding or justify as indirect),
|
|
41
|
+
do not auto-remove.
|
|
38
42
|
5. Emit a consolidated report under
|
|
39
43
|
`docs/reports/{{YYYYMMDD}}-ds-drift.md`.
|
|
40
44
|
|