baldart 4.63.0 → 4.65.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.
Files changed (34) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +3 -2
  3. package/VERSION +1 -1
  4. package/bin/baldart.js +15 -0
  5. package/framework/.claude/agents/REGISTRY.md +3 -0
  6. package/framework/.claude/agents/code-reviewer.md +25 -11
  7. package/framework/.claude/agents/codebase-architect.md +13 -5
  8. package/framework/.claude/agents/doc-reviewer.md +26 -12
  9. package/framework/.claude/agents/merge-conflict-resolver.md +58 -0
  10. package/framework/.claude/agents/ui-expert.md +18 -10
  11. package/framework/.claude/skills/context-primer/SKILL.md +7 -0
  12. package/framework/.claude/skills/design-system-init/SKILL.md +164 -156
  13. package/framework/.claude/skills/design-system-init/scripts/component-spec.template.md +26 -3
  14. package/framework/.claude/skills/design-system-init/scripts/extract-manifest.mjs +151 -0
  15. package/framework/.claude/skills/new/references/merge-cleanup.md +23 -18
  16. package/framework/.claude/skills/worktree-manager/SKILL.md +19 -0
  17. package/framework/.claude/skills/worktree-manager/scripts/merge-worktree.sh +518 -0
  18. package/framework/.claude/workflows/new2.js +14 -3
  19. package/framework/agents/component-manifest-schema.md +145 -0
  20. package/framework/agents/design-system-protocol.md +63 -23
  21. package/framework/agents/index.md +2 -0
  22. package/framework/docs/COMPONENT-MANIFEST-LAYER.md +100 -0
  23. package/framework/routines/ds-drift.routine.yml +19 -12
  24. package/framework/templates/baldart.config.template.yml +22 -0
  25. package/package.json +1 -1
  26. package/src/commands/configure.js +54 -0
  27. package/src/commands/doctor.js +85 -0
  28. package/src/commands/tokens.js +80 -0
  29. package/src/commands/update.js +7 -0
  30. package/src/utils/token-emitters/README.md +36 -0
  31. package/src/utils/token-emitters/css.js +18 -0
  32. package/src/utils/token-emitters/index.js +47 -0
  33. package/src/utils/token-emitters/ts.js +32 -0
  34. package/src/utils/tokens-generator.js +165 -0
@@ -225,6 +225,8 @@ const MERGE_SCHEMA = {
225
225
  deferredLeftOpen: { type: 'array', items: { type: 'string' }, description: 'F-040 — committed cards left NON-DONE (open owner-gated AC); the skill marks them DONE post-run' },
226
226
  epicsClosed: { type: 'array', items: { type: 'string' }, description: 'Epic/parent cards marked DONE by Phase 6b step 5e (all children DONE) — NOT a forcedDone violation' },
227
227
  uncommittedLeft: { type: 'boolean', description: 'true if dirty code was left (NOT committed) + reported (F-030)' },
228
+ status: { type: 'string', description: 'merge-worktree.sh terminal status: success|code_conflict|build_fail|sync_needs_decision|error' },
229
+ conflictFiles: { type: 'array', items: { type: 'string' }, description: 'on status:code_conflict — the UNRESOLVED code/test files the script paused on (left for a human / the resolver; new2 OPS agent does NOT hand-resolve)' },
228
230
  note: { type: 'string' },
229
231
  },
230
232
  }
@@ -1083,16 +1085,24 @@ if (!committed.length) {
1083
1085
  } else {
1084
1086
  try {
1085
1087
  mergeResult = await agentSafe(
1086
- `Auto-merge the batch worktree to ${TRUNK} per ${REF}/merge-cleanup.md (Phase 6 via /mw programmatic checksAlreadyPassed:true, Phase 6b status reconciliation, Phase 6c hygiene). Run git yourself.\n\n${projectBrief}\nWorktree: ${sharedCtx.worktreePath}\nBranch: ${sharedCtx.branch}\nmerge_strategy: ${mergeStrategy}\nCommitted cards: ${committed.map((r) => r.card).join(' ')}\nPhase-0 stash to restore (if any): see /tmp/batch-tracker-${firstCard}.md.\n\n` +
1088
+ `Auto-merge the batch worktree to ${TRUNK} per ${REF}/merge-cleanup.md: Phase 6 via the DETERMINISTIC script merge-worktree.sh, then Phase 6b status reconciliation + Phase 6c hygiene. Run git yourself.\n\n${projectBrief}\nWorktree: ${sharedCtx.worktreePath}\nBranch: ${sharedCtx.branch}\nmerge_strategy: ${mergeStrategy}\nCommitted cards: ${committed.map((r) => r.card).join(' ')}\nPhase-0 stash to restore (if any): see /tmp/batch-tracker-${firstCard}.md.\n\n` +
1089
+ `PHASE 6 — run the SSOT script (the merge analogue of setup-worktree.sh; NOT a hand-rolled merge, NOT /mw inline):\n` +
1090
+ ` MERGE_SH="$(ls .framework/framework/.claude/skills/worktree-manager/scripts/merge-worktree.sh .claude/skills/worktree-manager/scripts/merge-worktree.sh 2>/dev/null | head -1)"\n` +
1091
+ ` bash "$MERGE_SH" --worktree ${sharedCtx.worktreePath} --manifest /tmp/new2-merge-status-${firstCard}.txt --skip-checks\n` +
1092
+ ` Read /tmp/new2-merge-status-${firstCard}.txt and set status:<its status:>. \n` +
1093
+ ` • status:success → set merged:true + mergeCommit/mergeTs from the file, then do Phase 6b/6c below. Read sync_marker:/sync_detail: from the FILE (not stdout) for the G19-23 hygiene gate.\n` +
1094
+ ` • status:code_conflict → set merged:false, status:'code_conflict', conflictFiles:[the file's conflict_files split on comma]. The script ALREADY resolved every additive doc/registry/JSONL conflict and PAUSED the rebase on the code/test files. Per your ROLE BOUNDARY you do NOT hand-resolve code — leave the worktree paused (do NOT abort), SKIP 6b/6c, and return (the workflow tracks it as a merge blocker → follow-up).\n` +
1095
+ ` • status:build_fail/error/sync_needs_decision → merged:false, note:<the file's error:>, leave the worktree intact, return.\n` +
1096
+ ` • $MERGE_SH empty (older subtree) → fall back to /mw programmatic checksAlreadyPassed:true.\n\n` +
1087
1097
  `DETERMINISTIC POLICIES (NO prompts):\n` +
1088
- `• ROLE BOUNDARY: you are the OPS/GIT agent — you NEVER edit source or doc files. Reconciliation touches ONLY card YAML status fields + registry rows. A merge conflict on content is leave+report, never hand-resolved here.\n` +
1098
+ `• ROLE BOUNDARY: you are the OPS/GIT agent — you NEVER edit source or doc files. Reconciliation touches ONLY card YAML status fields + registry rows. A merge conflict on CODE is leave+report (status:code_conflict), never hand-resolved here.\n` +
1089
1099
  `• G24 → auto-merge via merge_strategy.\n` +
1090
1100
  `• F-030 HARD RULE: NEVER \`git add\`/commit code that did not pass the per-card gates. If the worktree is dirty with uncommitted code → DO NOT commit it; leave it, set uncommittedLeft:true, and report. NO "safety commit". Security/migration code is NEVER swept in.\n` +
1091
1101
  `• F-029 HARD RULE: Phase 6b reconciliation marks a card DONE ONLY if it has a real commit in ${TRUNK}..HEAD AND its gates are green. NEVER force a non-implemented card to DONE. Return forcedDone:[] (must be empty).\n` +
1092
1102
  `• F-040 DEFERRED CARDS — leave NON-DONE (do NOT force to DONE in Phase 6b): ${deferredCards.length ? deferredCards.join(' ') : '(none)'}. These committed their code but carry an OPEN owner-gated/policy-deferred AC (e.g. a pending remote db:push). Their YAML is INTENTIONALLY IN_PROGRESS; the new2 skill marks them DONE post-run after materialising the deferral's follow-up. They ARE part of the merge — just skip them in the DONE-reconciliation. Return deferredLeftOpen:[the ones you left non-DONE].\n` +
1093
1103
  `• EPIC CLOSURE (Phase 6b step 5e): the epic/parent card (group.is_epic:true) is NOT in the batch and stays TODO unless closed here. For each distinct group.parent of the batch cards (and any epic card in the batch itself): if EVERY child of that epic — \`grep -l "parent: <EPIC-ID>" ${paths.backlog_dir || 'backlog'}/*.yml | xargs grep -L "status: DONE"\` prints nothing — set the epic card status:DONE + completed_date + note "epic-closure gate — all children DONE" and fold into the reconciliation commit. If any child is still open → leave the epic untouched. This is NOT a forcedDone violation (the epic is a tracker, gated on all-children-DONE, not on its own commit). Return epicsClosed:[<EPIC-IDs marked DONE>].\n` +
1094
1104
  `• G19 sync-deferred → HEAD==${TRUNK} ff-pull, else leave+report. G20 → leave+report. G21 post-batch dirty → partition-ignore framework artifacts; leave the rest + report (do NOT commit). G22 divergence → behind: ff-pull; ahead/both: leave+report; NEVER reset --hard/force-push. G23 stash restore conflict → leave intact + report.\n\n` +
1095
- `Return: { merged, mergeCommit, mergeTs, reconciliation, forcedDone:[], deferredLeftOpen:[], uncommittedLeft, note }`,
1105
+ `Return: { merged, mergeCommit, mergeTs, status, conflictFiles, reconciliation, forcedDone:[], deferredLeftOpen:[], uncommittedLeft, note }`,
1096
1106
  // Sonnet, not the inherited opus: this is a deterministic OPS/GIT executor (git merge +
1097
1107
  // YAML status reconciliation + grep-based epic closure + leave-and-report hygiene gates), and
1098
1108
  // the correctness-critical checks (F-029 forcedDone guard, F-040 deferred guard) are enforced
@@ -1112,6 +1122,7 @@ if (!committed.length) {
1112
1122
  if (mergeResult && wronglyDone.length) { noteDegraded('false_done'); ledger(firstCard, 'F040-guard', 'VIOLATION', `deferred cards force-DONE by merge: ${wronglyDone.join(' ')}`) }
1113
1123
  }
1114
1124
  ledger(firstCard, 'G24-merge', (mergeResult && mergeResult.merged) ? 'MERGED' : 'INCOMPLETE', (mergeResult && (mergeResult.mergeCommit || mergeResult.note)) || '')
1125
+ if (mergeResult && mergeResult.status === 'code_conflict') ledger(firstCard, 'merge-code-conflict', 'BLOCKED', `merge-worktree.sh paused on code/test conflicts: ${(mergeResult.conflictFiles || []).join(' ')} — worktree intact, follow-up tracked (new2 OPS boundary does not hand-resolve code)`)
1115
1126
  if (mergeResult && mergeResult.reconciliation) ledger(firstCard, 'G19-23-reconcile', 'AUTO', mergeResult.reconciliation)
1116
1127
  }
1117
1128
 
@@ -0,0 +1,145 @@
1
+ # Component Manifest Schema
2
+
3
+ ## Purpose
4
+
5
+ Define the **machine-readable HEAD** every per-component spec
6
+ (`${paths.design_system}/components/<Name>.md`) carries so agents discover and
7
+ reuse primitives by reading a small, structured frontmatter **on demand** — not
8
+ by parsing a monolithic `INDEX.md` or the component source. This module is the
9
+ **single source of truth** for *which fields a component spec needs* and *in what
10
+ state*; every writer and every consumer cites it.
11
+
12
+ It is the design-system analogue of `agents/card-schema.md`: a schema that ≥6
13
+ surfaces produce or consume (`design-system-init`, `doc-reviewer`, `ui-expert`,
14
+ `code-reviewer`, `codebase-architect`, `context-primer`, `ds-drift`) and must
15
+ therefore live in one place that none of them re-describe.
16
+
17
+ ## Scope
18
+
19
+ **In**: the frontmatter field table, the deterministic-vs-agentic split, the
20
+ regeneration contract, the on-demand read contract, the thin-INDEX router shape,
21
+ and the transition-leniency rule.
22
+
23
+ **Out**: the *enforcement* (when a missing/stale spec blocks) and the drift codes
24
+ (`DS_INDEX_DRIFT` / `DS_COMPONENT_STALE` / `DS_TOKENS_DRIFT`) — those stay in
25
+ `agents/design-system-protocol.md` (§ Post-Intervention Coherence Check). This
26
+ module defines the *fields those codes are checked against*; it **never copies**
27
+ the codes or the gate (that would re-introduce the drift the SSOT split prevents).
28
+ Token *values* and the DTCG source live in the token layer
29
+ (`${paths.design_tokens}` → `.tokens.json`); this module references token **IDs**,
30
+ never values.
31
+
32
+ ## Gating
33
+
34
+ Same as the design-system protocol: meaningful only when
35
+ `features.has_design_system: true`. There is **no separate feature flag** — the
36
+ manifest rides on the design-system layer. When the flag is `false`, component
37
+ specs (and this schema) do not apply.
38
+
39
+ ## The frontmatter HEAD
40
+
41
+ Additive: a YAML frontmatter block is prepended to the EXISTING prose spec. The
42
+ prose body (Purpose, Props table, Accessibility, Anti-patterns, …) is preserved
43
+ verbatim — the HEAD makes the *mechanical* half machine-addressable; the prose
44
+ stays the human-readable long form.
45
+
46
+ ```yaml
47
+ ---
48
+ # === DETERMINISTIC — extracted from source (TS types) + git; regenerable, never hand-curated ===
49
+ name: Button # exported component name
50
+ source: components/ui/Button.tsx # path relative to repo root
51
+ source_sha: a1b2c3d # git blob sha of source at last generation (drift anchor)
52
+ status: stable # stable | experimental | deprecated (default stable; `deprecated` is a hand override)
53
+ category: primitive # primitive | composite | feature (heuristic on import fan-in; hand-overridable)
54
+ props: # from the TS prop type / interface
55
+ variant: { type: "'primary'|'ghost'|'destructive'", required: false, default: "'primary'" }
56
+ size: { type: "'sm'|'md'|'lg'", required: false, default: "'md'" }
57
+ disabled: { type: boolean, required: false, default: false }
58
+ variants: [primary, ghost, destructive] # enumerated literal union of the `variant` prop
59
+ composes: [Slot, Spinner] # in-registry primitives imported by this component
60
+ # === AGENTIC — curated by doc-reviewer; survives regeneration, never clobbered when non-empty ===
61
+ purpose: "Primary action trigger; one primary per surface."
62
+ token_bindings: [color.action.primary, radius.md, space.2, motion.fast] # DTCG token IDs (not values)
63
+ a11y: "Icon-only requires aria-label; ≥44px target; respects prefers-reduced-motion."
64
+ related: [IconButton, Link] # sibling primitives (not composition)
65
+ must_rules: ["MUST consume color.action.primary", "MUST set aria-label when icon-only"]
66
+ last_verified: 2026-06-23 # set by doc-reviewer / ds-drift on semantic re-verify
67
+ owner: doc-reviewer # curation owner (constant; documents the SSOT)
68
+ ---
69
+ ```
70
+
71
+ ### Field origin
72
+
73
+ | Origin | Fields | Rule |
74
+ |---|---|---|
75
+ | **Deterministic** (TS/git) | `name`, `source`, `source_sha`, `status` (default), `category` (heuristic), `props`, `variants`, `composes` | Always safe to **overwrite** on regeneration — they are derived. Extracted by a pure Node script (no agent), so the path is **portable to Codex**. |
76
+ | **Agentic** (doc-reviewer) | `purpose`, `token_bindings`, `a11y`, `related`, `must_rules`, `last_verified`; plus `status: deprecated` as a judgment | **Never clobbered** when non-empty. Empty agentic fields = "not yet enriched", filled later by `doc-reviewer` / `ds-drift`, never auto-invented. |
77
+
78
+ ## Read contract (the context win)
79
+
80
+ 1. An agent doing reuse-discovery reads the thin `INDEX.md` (router) → then **only
81
+ the frontmatter** of the N specs in scope (front-load the first `---…---`
82
+ block), NOT the prose body and NOT the source.
83
+ 2. The prose body is read only when the agent is about to **modify** that
84
+ component.
85
+ 3. `token_bindings` are IDs into `.tokens.json`, so one already-read tokens file
86
+ answers every component's bindings — no per-component token re-read, and the
87
+ binding stays stable when a token *value* changes.
88
+
89
+ This is the whole point: replace "read a 33KB monolith + fall to source" with
90
+ "read a small router + N small frontmatters".
91
+
92
+ ## Thin INDEX router
93
+
94
+ `${paths.design_system}/INDEX.md` is **generated** and holds only routing data —
95
+ one row per primitive: `name | source | one-line purpose | spec link`. It carries
96
+ NO human-curated judgment (the Authority Matrix moved to
97
+ `design-system-protocol.md`; per-component detail lives in each spec). Because it
98
+ is generated, it is always safe to regenerate from the specs — never hand-edit it.
99
+
100
+ ## Regeneration contract
101
+
102
+ - **Anchor**: `source_sha`. A regeneration run skips a component whose source is
103
+ unchanged AND whose spec already has a HEAD (no-op); regenerates only
104
+ stale/missing ones.
105
+ - **Deterministic fields**: overwritten from current source every run.
106
+ - **Agentic fields**: preserved unless empty; an empty field is a candidate for
107
+ enrichment, never a reason to clobber a filled one.
108
+ - **Idempotent + resumable**: a kill mid-run leaves a mix of HEAD-bearing and
109
+ prose-only specs — all valid (see leniency below). No half-written state.
110
+
111
+ ## Transition leniency (do not raise the floor)
112
+
113
+ The HEAD is an **upgrade target, not a new floor**. During and after adoption:
114
+
115
+ - A **prose-only spec** (no HEAD) is still a valid spec — it does NOT trip
116
+ `DS_COMPONENT_STALE` merely for lacking a HEAD.
117
+ - **Empty deterministic fields** (e.g. a JS/Vue/Svelte consumer with no TS types,
118
+ or an unresolved prop) are **advisory** — "not yet enriched", never a blocking
119
+ finding.
120
+ - A **missing** spec for a new primitive remains exactly the `DS_INDEX_DRIFT` it
121
+ is today — the manifest adds no new failure mode, it only makes the existing
122
+ spec richer where the data is available.
123
+
124
+ This keeps consumers that have not yet upgraded (and non-TS stacks) from becoming
125
+ *more* broken than before the manifest existed.
126
+
127
+ ## Producers & consumers
128
+
129
+ - **Bootstrap / upgrade**: `/design-system-init` (deterministic extract + agentic
130
+ enrich; greenfield and upgrade modes).
131
+ - **Per-task write**: `ui-expert` / `ui-design` / `frontend-design` in the
132
+ Post-Intervention Coherence Check — a new/modified primitive regenerates its
133
+ HEAD in the same change.
134
+ - **Curation owner**: `doc-reviewer` (agentic fields).
135
+ - **Per-merge gate**: `code-reviewer` (reads HEAD-first; enforces the drift codes
136
+ per the protocol).
137
+ - **Read-first discovery**: `codebase-architect`, `context-primer`.
138
+ - **Weekly net**: `ds-drift` (compares `source_sha`/HEAD against source).
139
+
140
+ ## See also
141
+
142
+ - `agents/design-system-protocol.md` — the enforcement gate + drift codes + read
143
+ cascade this schema feeds.
144
+ - `agents/card-schema.md` — the sibling schema module this one mirrors.
145
+ - `agents/project-context.md` — `baldart.config.yml` + overlay resolution.
@@ -47,13 +47,21 @@ To bootstrap a registry on a project that does not yet have one, invoke the
47
47
 
48
48
  ## Canonical sources (resolve via `baldart.config.yml`)
49
49
 
50
- - `${paths.design_system}/INDEX.md` — component index + Canonical Authority
51
- Matrix + quick MUST rules. Entry point for every UI task.
52
- - `${paths.design_system}/tokens-reference.md` human-readable token contract
53
- (colors, spacing, shadows, radii, motion). Single source of truth for any
54
- value that would otherwise be hardcoded.
55
- - `${paths.design_system}/components/<Name>.md` per-component spec (props,
56
- variants, accessibility, anti-patterns). One file per primitive.
50
+ - `${paths.design_system}/INDEX.md` — **generated** thin router: one row per
51
+ primitive (`name | source | one-line purpose | spec link`). Entry point for
52
+ every UI task. Human-curated judgement does NOT live here (it moved to the
53
+ Authority Matrix below + each spec's frontmatter) never hand-edit the INDEX.
54
+ - `${paths.design_tokens}` `.tokens.json` **the token SSOT**, W3C DTCG
55
+ format (`$type`/`$value`). The stack-native artifact (`tokens.ts` / CSS custom
56
+ properties) is **generated** from it via `baldart tokens build`; editing that
57
+ generated output by hand is drift (see Token Contract).
58
+ - `${paths.design_system}/tokens-reference.md` — **generated/narrative** human
59
+ view of the tokens. NOT a third source of values: either generated from
60
+ `.tokens.json` or pure narrative ("how to use tokens"), never hand-authored
61
+ values that duplicate the DTCG source.
62
+ - `${paths.design_system}/components/<Name>.md` — per-component spec. Carries a
63
+ machine-readable frontmatter HEAD (see `agents/component-manifest-schema.md`)
64
+ + prose body (props, variants, accessibility, anti-patterns). One per primitive.
57
65
  - `${paths.design_system}/patterns/*.md` — opt-in pattern docs (theming,
58
66
  overlays, motion choreography, multi-tenant pairing rules).
59
67
  - `${paths.ui_guidelines}` — brand voice, typography, philosophy. Always read.
@@ -64,9 +72,12 @@ To bootstrap a registry on a project that does not yet have one, invoke the
64
72
 
65
73
  Before producing any new UI code, mockup, or design review:
66
74
 
67
- 1. **BLOCKING** — read `${paths.design_system}/INDEX.md`. Extract: the
68
- component index, the Authority Matrix for the surface you are about to
69
- touch, any pattern docs the index points to.
75
+ 1. **BLOCKING** — read `${paths.design_system}/INDEX.md` (the thin router) to
76
+ locate the primitives in scope, then read **only the frontmatter HEAD** of
77
+ each in-scope `components/<Name>.md` (per `agents/component-manifest-schema.md`)
78
+ — not the prose body, not the source. Read the prose body only when you are
79
+ about to modify that primitive. Also read the Authority Matrix (below) + any
80
+ pattern docs the router points to.
70
81
  2. **BLOCKING** — read `${paths.ui_guidelines}` for visual language, typography,
71
82
  accessibility, and brand voice. The `identity.design_philosophy` (when set)
72
83
  is the lens through which these guidelines apply.
@@ -120,10 +131,26 @@ any code or mockup produced under this protocol:
120
131
  the `motion-design` skill).
121
132
 
122
133
  The exact token consumption mechanism (CSS custom properties, Tailwind theme
123
- extend, theme provider) is project-specific — defer to
124
- `${paths.design_system}/tokens-reference.md` and the project's chosen
134
+ extend, theme provider) is project-specific — defer to the project's chosen
125
135
  mechanism.
126
136
 
137
+ **Token SSOT inversion (when `paths.design_tokens` is set).** The canonical
138
+ token source is the DTCG `.tokens.json` at `${paths.design_tokens}`. The
139
+ stack-native artifact agents/components consume (`tokens.ts`, a CSS custom-property
140
+ sheet, …) is **generated** from it by `baldart tokens build` and carries a
141
+ `// baldart-generated from .tokens.json — do not edit` marker. Consequences:
142
+
143
+ - To add/change a token, edit `.tokens.json` and regenerate — never hand-edit the
144
+ generated output.
145
+ - `DS_TOKENS_DRIFT` is now mechanically checkable: the generated output must equal
146
+ `baldart tokens build` from the current `.tokens.json`. A hand-edit to the
147
+ generated file (output ≠ rebuild) is the violation — port it to `.tokens.json`.
148
+ - A `token_bindings` ID in a component HEAD must resolve to a real node in
149
+ `.tokens.json`; a dangling ID is `DS_TOKENS_DRIFT`.
150
+
151
+ When `paths.design_tokens` is unset, the project has not adopted the DTCG SSOT —
152
+ fall back to the project's existing token file as the source (no inversion).
153
+
127
154
  ## Reference Tables (embed)
128
155
 
129
156
  These tables are the SSOT for the *numeric ranges* every agent/skill should
@@ -306,13 +333,16 @@ visually uneven steps because HSL lightness is not perceptual.
306
333
 
307
334
  ## Authority Matrix
308
335
 
309
- `${paths.design_system}/INDEX.md` declares, for each primitive, who is
310
- authoritative over which dimension:
336
+ This matrix is **canonical here** (protocol-level), not in `INDEX.md` the INDEX
337
+ is a generated router and cannot hold curated judgement. For each primitive, it
338
+ declares who is authoritative over which dimension:
311
339
 
312
- - **Visual authority** — colors, spacing, shadows, radii: the registry +
313
- tokens-reference. Agents/skills may not deviate.
340
+ - **Visual authority** — colors, spacing, shadows, radii, motion: the DTCG token
341
+ SSOT (`${paths.design_tokens}` → `.tokens.json`, surfaced via the generated
342
+ token reference). Agents/skills may not deviate.
314
343
  - **Behavioral authority** — props, variants, accessibility hooks: the
315
- per-component spec.
344
+ per-component spec (its frontmatter HEAD for the machine-readable contract, its
345
+ prose body for rationale).
316
346
  - **Composition authority** — how primitives are combined into patterns: the
317
347
  pattern docs.
318
348
 
@@ -441,12 +471,19 @@ For each item in the diff that touched a visual surface, run these 4 checks:
441
471
  in the same change. Missing spec = `DS_INDEX_DRIFT` finding, task blocked.
442
472
  2. **Modified primitive ⇒ updated spec**: did the change alter a primitive's
443
473
  props / variants / accessibility behavior? If yes, the corresponding
444
- `components/<Name>.md` MUST be updated to match. Stale spec =
445
- `DS_COMPONENT_STALE` finding, task blocked.
446
- 3. **New / changed token updated reference**: did the change introduce or
447
- modify a token (color, spacing, shadow, radius, motion)? If yes,
448
- `${paths.design_system}/tokens-reference.md` MUST reflect it. Stale
449
- reference = `DS_TOKENS_DRIFT` finding, task blocked.
474
+ `components/<Name>.md` MUST be updated to match including its **frontmatter
475
+ HEAD** (deterministic fields `props`/`variants`/`composes`/`source_sha`
476
+ regenerated from source; agentic fields re-verified). Stale spec =
477
+ `DS_COMPONENT_STALE` finding, task blocked. (A prose-only spec lacking a HEAD
478
+ is NOT stale merely for that — see `agents/component-manifest-schema.md`
479
+ § Transition leniency.)
480
+ 3. **New / changed token ⇒ updated SSOT**: did the change introduce or modify a
481
+ token (color, spacing, shadow, radius, motion)? If `paths.design_tokens` is
482
+ set, the change MUST be made in `.tokens.json` and the stack-native output
483
+ regenerated via `baldart tokens build` (a hand-edit to the generated output is
484
+ the drift). Otherwise update the project's token source + reference. Either
485
+ way, a dangling `token_bindings` ID is drift. Stale = `DS_TOKENS_DRIFT`,
486
+ task blocked.
450
487
  4. **Surfaced silent primitive ⇒ added to INDEX**: during the work, did you
451
488
  reuse a primitive that exists in the source tree but is **not** in
452
489
  `INDEX.md`? If yes, add it to the INDEX in the same change (or open a
@@ -529,6 +566,9 @@ When changing the BLOCKING cascade, change it here — not in the consumers.
529
566
 
530
567
  ## See also
531
568
 
569
+ - `framework/agents/component-manifest-schema.md` — the machine-readable
570
+ frontmatter HEAD on each `components/<Name>.md`, the on-demand read contract,
571
+ and the regeneration + transition-leniency rules this gate is checked against.
532
572
  - `framework/agents/project-context.md` — always-ask / never-assume contract
533
573
  and the `baldart.config.yml` resolution flow.
534
574
  - `framework/agents/design-review.md` — design-review workflow and report
@@ -20,6 +20,7 @@ Route agents to the right module with minimal reading.
20
20
  - If touching database schema or fields -> read `agents/data-model.md` and `${paths.references_dir}/data-model.md`.
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
+ - 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`.
23
24
  - If touching design-review workflows or UI guidelines -> read `agents/design-review.md` and project-specific UI guidelines.
24
25
  - If touching architecture, auth, or tech stack -> read `agents/architecture.md`.
25
26
  - If touching workflow/process/commits/backlog -> read `agents/workflows.md`.
@@ -72,6 +73,7 @@ When adding or updating agents, update REGISTRY.md — not this file.
72
73
  - `agents/i18n-protocol.md` — Internationalization discipline: no hardcoded user-facing strings, context registry (`paths.i18n_registry`) + ICU + context-aware translation; BLOCKING anti-hardcoded lint gate (since v4.52.0, gated on `features.has_i18n`)
73
74
  - `agents/design-system-protocol.md` — Registry-first discipline for UI work: BLOCKING cascade on `INDEX.md` + `tokens-reference.md` + `components/<Name>.md` (since v3.11.0, gated on `features.has_design_system`)
74
75
  - `agents/card-schema.md` — Atomic Card Baseline Schema: the universal, profile-aware (epic/child/standalone) field contract every backlog card satisfies, plus the consumer HALT/BACK-FILL/WARN contract (since v4.35.0)
76
+ - `agents/component-manifest-schema.md` — Component Manifest Schema: the machine-readable frontmatter HEAD on each `components/<Name>.md` (deterministic-from-TS + agentic fields), on-demand read contract, regeneration + transition-leniency rules (since v4.65.0, gated on `features.has_design_system`)
75
77
  - `agents/return-contract-protocol.md` — Subagent return-message economy: COMPACT (bounded headline + `path:line` findings + disk pointer) vs FULL, persist-then-summarize — the input-side twin of `effort-protocol.md` (since v4.59.0)
76
78
 
77
79
  ## Where to Document (Decision Tree)
@@ -0,0 +1,100 @@
1
+ # Component Manifest + DTCG Token Layer
2
+
3
+ Authoritative design, lifecycle, and invariants for the machine-readable
4
+ component-manifest layer and the DTCG token SSOT (since v4.65.0). Companion to
5
+ the textual SSOTs: `framework/agents/component-manifest-schema.md` (the HEAD
6
+ schema) and `framework/agents/design-system-protocol.md` (the gate + cascade).
7
+
8
+ ## 1. Why this exists
9
+
10
+ On a real consumer, UI component discovery was slow: the registry's `INDEX.md`
11
+ had grown to a **33KB monolith** read in full on every UI task, there was no
12
+ `tokens-reference.md` (tokens parsed from a large hand-authored token file), and
13
+ **0 of ~135 components** had a per-component spec — so every "what does this
14
+ primitive do / can I reuse it" lookup degraded to reading component **source**.
15
+ Graphify is
16
+ the wrong lever here: component lookup is a single-symbol query, which the
17
+ code-graph protocol routes to LSP/Grep, not the graph.
18
+
19
+ The fix is not a new layer — the framework already prescribes per-component specs
20
+ (`components/<Name>.md`, owned by `doc-reviewer`, gated by `code-reviewer`,
21
+ audited by `ds-drift`). It is **(a)** making those specs machine-readable and
22
+ read **on demand**, and **(b)** giving tokens a real, interoperable SSOT.
23
+
24
+ ## 2. What BALDART adds vs what the stack already ships
25
+
26
+ | Concern | Stack / status quo | What BALDART adds |
27
+ |---|---|---|
28
+ | Component contract | prose `components/<Name>.md` (often absent) | a YAML **frontmatter HEAD** — deterministic (from TS) + agentic (curated) — read on demand |
29
+ | Discovery entry | monolithic INDEX read in full | **thin generated INDEX router** → frontmatter-first reads of only the N specs in scope |
30
+ | Token source | ad-hoc `tokens.ts` / CSS / Tailwind | **W3C DTCG `.tokens.json`** as SSOT |
31
+ | Token output | hand-authored | **generated** via `baldart tokens build` (own thin emitter registry — no Style Dictionary) |
32
+ | Generation | hand-written specs that don't scale | **hybrid**: deterministic Node extractor + agentic enrichment |
33
+
34
+ BALDART wraps the **standard** (DTCG) and owns only a thin generator — the same
35
+ "wrap best-in-class, own the adapter" ethos as the i18n and graph layers.
36
+
37
+ ## 3. The moving parts
38
+
39
+ | Piece | Path | Role |
40
+ |---|---|---|
41
+ | HEAD schema | `framework/agents/component-manifest-schema.md` | SSOT for the frontmatter fields + read/regeneration/leniency contract |
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 |
45
+ | Bootstrap/upgrade | `framework/.claude/skills/design-system-init/SKILL.md` | greenfield + upgrade modes |
46
+ | Token emitters | `src/utils/token-emitters/{index,ts,css}.js` | DTCG → stack-native output (REGISTRY pattern) |
47
+ | Token generator | `src/utils/tokens-generator.js` | parse `.tokens.json`, resolve `{aliases}`, build/isStale |
48
+ | CLI verb | `src/commands/tokens.js` (`baldart tokens build`) | the operation skills/doctor CALL |
49
+ | Config | `paths.design_tokens` + `design_tokens.outputs` | propagated template→configure→update→doctor |
50
+
51
+ ## 4. Install / adoption lifecycle
52
+
53
+ ```
54
+ has_design_system: true (no separate flag — the manifest rides on it)
55
+
56
+ ├─ configure: autodetect .tokens.json → paths.design_tokens; propose ts/css outputs; baldart tokens build
57
+
58
+ ├─ /design-system-init (greenfield OR upgrade):
59
+ │ deterministic extract (no subagent) → agentic enrich (doc-reviewer)
60
+ │ → .tokens.json bootstrap + round-trip verify → thin INDEX router
61
+
62
+ ├─ per task: ui-expert/ui-design write the HEAD in the Post-Intervention Check
63
+ │ coder/architect/context-primer READ frontmatter-first
64
+
65
+ ├─ per merge: code-reviewer Rule 8 gates (HEAD-first; transition-lenient)
66
+
67
+ ├─ weekly: ds-drift (source_sha + token-output-vs-DTCG)
68
+
69
+ └─ doctor backfill: tokens-build (stale output) + ds-manifest-nudge (specs missing/prose-only)
70
+ ```
71
+
72
+ ## 5. Invariants (do not break)
73
+
74
+ - **Codex portability**: the READ path and the deterministic half (extractor,
75
+ `baldart tokens build`) run with **no subagent**. Only agentic enrichment needs
76
+ Claude; under Codex it degrades to empty/TODO fields — never a hard failure.
77
+ - **No twin**: ONE artifact per component (the spec, evolved) and ONE token SSOT
78
+ (`.tokens.json`); `tokens-reference.md` and `tokens.ts`/CSS are **generated
79
+ views/outputs**, never hand-authored sources. The INDEX is a generated router.
80
+ - **Token SSOT inversion**: when `paths.design_tokens` is set, edit `.tokens.json`
81
+ and regenerate; a hand-edit to a generated output (output ≠ `baldart tokens
82
+ build`) is `DS_TOKENS_DRIFT`. Generated files carry a `baldart-generated` banner.
83
+ - **Transition leniency (no new floor)**: a prose-only spec lacking a HEAD is
84
+ valid (not `DS_COMPONENT_STALE`); empty deterministic fields (non-TS stack) are
85
+ advisory; a missing spec for a new primitive is the same `DS_INDEX_DRIFT` as
86
+ before. The manifest is an upgrade target, not a stricter gate.
87
+ - **Ownership**: `doc-reviewer` owns the agentic HEAD fields (never clobbered on
88
+ regeneration); the deterministic fields are regenerated from source; never
89
+ hand-curate them.
90
+ - **Authority Matrix** lives in `design-system-protocol.md`, not the (generated)
91
+ INDEX.
92
+
93
+ ## 6. Adding capability later (deferred, documented)
94
+
95
+ - Extra emitters (`scss`, `tailwind`, `json`) — drop a file in `token-emitters/`.
96
+ - A git pre-commit hook to regenerate tokens on `.tokens.json` change (opt-in).
97
+ - In-place replacement of a hand-authored `tokens.ts` (after the round-trip is
98
+ trusted on real consumers).
99
+ - `framework-edit-gate` hard-deny on editing a generated token output (after one
100
+ release of the advisory banner).
@@ -14,24 +14,31 @@ prompt: |
14
14
  Run the weekly design-system drift check (only if `${paths.design_system}/`
15
15
  exists in the project — exit cleanly if absent):
16
16
 
17
- 1. **INDEX coverage**: compare `${paths.design_system}/INDEX.md` component
18
- index against the actual shared-component inventory. Flag
19
- `DS_INDEX_DRIFT` for any mismatch.
20
- 2. **Per-component accuracy**: for each `${paths.design_system}/components/<Name>.md`,
21
- verify it lines up with the source file (variants, props, exported
22
- constants). Flag `DS_COMPONENT_STALE` for docs not re-verified in >7
23
- days where the source has changed in git.
17
+ 1. **INDEX coverage**: the `${paths.design_system}/INDEX.md` router must list
18
+ every primitive that has a spec. Flag `DS_INDEX_DRIFT` for any mismatch
19
+ (fix by regenerating the router, never hand-editing it).
20
+ 2. **Per-component HEAD accuracy**: for each
21
+ `${paths.design_system}/components/<Name>.md`, compare its frontmatter HEAD
22
+ against the source — `source_sha` vs the current git blob, `props`/`variants`
23
+ vs the TS types (schema: `framework/agents/component-manifest-schema.md`).
24
+ Flag `DS_COMPONENT_STALE` when `source_sha` is stale (or >7 days unverified
25
+ with a source change). A prose-only spec with no HEAD is an upgrade candidate
26
+ (recommend `/design-system-init`), NOT itself stale.
24
27
  3. **Animations reconciliation**: `${paths.design_system}/patterns/animations.md`
25
28
  must match keyframes in the project's global styles. Flag
26
29
  `DS_ANIMATIONS_DRIFT`.
27
- 4. **Tokens reconciliation**: when tokens source files changed, verify
28
- `${paths.design_system}/tokens-reference.md` is in sync and the relevant
29
- CSS custom properties still exist. Flag `DS_TOKENS_DRIFT`.
30
+ 4. **Tokens reconciliation**: when `paths.design_tokens` is set (DTCG SSOT),
31
+ verify the generated outputs (`design_tokens.outputs`) + `tokens-reference.md`
32
+ equal `baldart tokens build` a hand-edited generated file or a dangling
33
+ `token_bindings` ID is `DS_TOKENS_DRIFT`. When unset, fall back to verifying
34
+ the project token source vs `tokens-reference.md`.
30
35
  5. Emit a consolidated report under
31
36
  `docs/reports/{{YYYYMMDD}}-ds-drift.md`.
32
37
 
33
- Apply trivial fixes inline (e.g. update an INDEX row to match the current
34
- source). Anything that requires design judgement is left as a finding.
38
+ Apply trivial fixes inline (regenerate the INDEX router; `baldart tokens build`
39
+ for token-output drift; refresh a stale deterministic HEAD via the extractor).
40
+ Anything that requires design judgement (agentic HEAD fields, new variant
41
+ semantics) is left as a finding.
35
42
 
36
43
  output:
37
44
  path: docs/reports/{{YYYYMMDD}}-ds-drift.md
@@ -35,6 +35,13 @@ paths:
35
35
  components_root: "" # e.g. src/components
36
36
  global_styles: "" # e.g. src/app/globals.css
37
37
 
38
+ # Design-token SSOT (since v4.65.0). The W3C DTCG `.tokens.json` file that is
39
+ # the SINGLE source of truth for design tokens; the stack-native artifact
40
+ # (tokens.ts / CSS vars) is GENERATED from it via `baldart tokens build` (see
41
+ # the design_tokens: block below). Only meaningful when has_design_system: true.
42
+ # Leave empty if the project has not adopted the DTCG SSOT (no inversion).
43
+ design_tokens: "" # e.g. src/ui/tokens.tokens.json
44
+
38
45
  # Workflow infrastructure (set the matching `features.*` flag).
39
46
  backlog_dir: "" # e.g. backlog
40
47
  adrs_dir: "" # e.g. docs/decisions
@@ -312,6 +319,21 @@ graph:
312
319
  # without it, so this is an enhancement, not a requirement.
313
320
  register_mcp: false
314
321
 
322
+ # ─── DESIGN TOKENS (DTCG) ──────────────────────────────────────────────────
323
+ # Generation targets for the DTCG token SSOT (since v4.65.0). The source is
324
+ # `paths.design_tokens` (a W3C DTCG `.tokens.json`). `baldart tokens build`
325
+ # reads it, resolves `{aliases}`, and regenerates each output below. The
326
+ # generated files carry a `baldart-generated` banner — edit `.tokens.json`, never
327
+ # the output. Only meaningful when has_design_system: true AND
328
+ # paths.design_tokens is set; otherwise `tokens build` is a no-op.
329
+ design_tokens:
330
+ # Each output is { format, path }. format ∈ token-emitters registry
331
+ # (src/utils/token-emitters/: ts | css — extend by dropping a new emitter).
332
+ outputs: []
333
+ # Example:
334
+ # - { format: ts, path: src/ui/tokens.ts }
335
+ # - { format: css, path: src/ui/tokens.css }
336
+
315
337
  # ─── TOOLCHAIN ─────────────────────────────────────────────────────────────
316
338
  # State of the curated dev toolchain. Populated by `baldart configure` / the
317
339
  # `/toolchain-bootstrap` skill. Only meaningful when `features.has_toolchain:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "baldart",
3
- "version": "4.63.0",
3
+ "version": "4.65.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"
@@ -308,6 +308,23 @@ function detect(cwd = process.cwd()) {
308
308
  )
309
309
  );
310
310
 
311
+ // ---- Design-token SSOT (DTCG `.tokens.json`, since v4.65.0) -------------
312
+ // Detection-only: a `.tokens.json` / `.tokens` file at a conventional spot
313
+ // means the project already speaks DTCG. If absent we leave design_tokens
314
+ // empty (no inversion) — adoption is an explicit /design-system-init step,
315
+ // never auto-forced (the project's existing tokens.ts stays the source).
316
+ const designTokensPath = findFirst(
317
+ ...expandCandidates(
318
+ 'tokens.tokens.json', 'design/tokens.tokens.json',
319
+ 'src/ui/tokens.tokens.json', 'src/design-system/tokens.tokens.json',
320
+ 'tokens.json', 'design-tokens.json'
321
+ ),
322
+ ...(designSystemPath ? [
323
+ path.join(designSystemPath, 'tokens.tokens.json'),
324
+ path.join(designSystemPath, 'tokens.json'),
325
+ ] : [])
326
+ ) || '';
327
+
311
328
  // ---- API docs (widened — OpenAPI, GraphQL, populated dir) --------------
312
329
  const apiIndex = findFirst('docs/references/api/index.md', 'docs/api/index.md');
313
330
  const apiSchemas = findFirst('docs/references/api/schemas.md', 'docs/api/schemas.md');
@@ -425,6 +442,7 @@ function detect(cwd = process.cwd()) {
425
442
  components_primitives: componentsPrimitives,
426
443
  components_root: componentsRoot,
427
444
  global_styles: globalStyles,
445
+ design_tokens: designTokensPath,
428
446
  backlog_dir: exists('backlog') ? 'backlog' : '',
429
447
  adrs_dir: countMatches('docs/decisions', /^ADR-.*\.md$/) > 0 ? 'docs/decisions' : '',
430
448
  prd_dir: exists('docs/prd') ? 'docs/prd' : '',
@@ -732,6 +750,7 @@ async function interactivePrompts(merged, detected) {
732
750
  ['components_primitives', 'UI primitives directory (e.g. src/components/ui)', () => true],
733
751
  ['components_root', 'Components root directory', () => true],
734
752
  ['global_styles', 'Global stylesheet path', () => true],
753
+ ['design_tokens', 'DTCG token SSOT (.tokens.json) — leave empty if not adopted', () => merged.features.has_design_system],
735
754
  ['backlog_dir', 'Backlog directory', () => merged.features.has_backlog],
736
755
  ['adrs_dir', 'ADR directory', () => merged.features.has_adrs],
737
756
  ['prd_dir', 'PRD directory', () => merged.features.has_prd_workflow],
@@ -750,6 +769,41 @@ async function interactivePrompts(merged, detected) {
750
769
  merged.paths[key] = await promptForKey(`${label}`, proposed);
751
770
  }
752
771
 
772
+ // ---- Design tokens (DTCG SSOT, since v4.65.0) -------------------------
773
+ // Only when the project adopted the DTCG source (paths.design_tokens set).
774
+ // The block holds the GENERATION targets; `baldart tokens build` (+ doctor
775
+ // backfill) regenerate them. We never invert an existing hand-authored
776
+ // tokens.ts here — that migration is an explicit /design-system-init step.
777
+ merged.design_tokens = merged.design_tokens || { outputs: [] };
778
+ if (merged.features.has_design_system === true && merged.paths.design_tokens) {
779
+ UI.section('Design tokens (DTCG SSOT → generated artifacts)');
780
+ const TokensGenerator = require('../utils/tokens-generator');
781
+ const gen = new TokensGenerator(process.cwd());
782
+ if (!gen.detect(merged.paths.design_tokens)) {
783
+ UI.info(`DTCG source \`${merged.paths.design_tokens}\` not found yet — run /design-system-init to scaffold it. Leaving design_tokens.outputs empty.`);
784
+ } else if (!(merged.design_tokens.outputs || []).length) {
785
+ // Propose the conventional outputs next to the existing token consumers.
786
+ const proposed = [];
787
+ const tsGuess = merged.paths.global_styles ? '' : 'src/ui/tokens.ts';
788
+ if (await UI.confirm('Generate a TypeScript token object (tokens.ts) from the DTCG source?', true)) {
789
+ const p = await promptForKey(' tokens.ts output path', tsGuess || 'src/ui/tokens.ts');
790
+ if (p) proposed.push({ format: 'ts', path: p });
791
+ }
792
+ if (await UI.confirm('Also generate a CSS custom-property sheet?', false)) {
793
+ const p = await promptForKey(' tokens.css output path', 'src/ui/tokens.css');
794
+ if (p) proposed.push({ format: 'css', path: p });
795
+ }
796
+ merged.design_tokens.outputs = proposed;
797
+ if (proposed.length) {
798
+ const res = gen.build({ source: merged.paths.design_tokens, outputs: proposed });
799
+ if (res.ok) UI.success(`Tokens built (${res.tokenCount}) → ${res.written.join(', ')}`);
800
+ else UI.warning(`Token build failed: ${res.error} (fix the .tokens.json then run \`baldart tokens build\`).`);
801
+ }
802
+ }
803
+ } else {
804
+ merged.design_tokens.outputs = merged.design_tokens.outputs || [];
805
+ }
806
+
753
807
  // ---- E2E review tuning (since v3.18.0) --------------------------------
754
808
  // Only runs when the user opted in above. Populates the e2e_review.*
755
809
  // sub-section with the severity floor, self-heal cap, pixel pre-filter