baldart 5.1.0 → 5.2.1

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 (39) hide show
  1. package/CHANGELOG.md +115 -0
  2. package/README.md +3 -3
  3. package/VERSION +1 -1
  4. package/framework/.claude/agents/CHANGELOG.md +45 -0
  5. package/framework/.claude/agents/REGISTRY.md +2 -2
  6. package/framework/.claude/agents/coder.md +3 -3
  7. package/framework/.claude/agents/remotion-animator-orchestrator.md +4 -3
  8. package/framework/.claude/agents/ui-expert.md +4 -10
  9. package/framework/.claude/agents/ui-quality-critic.md +27 -11
  10. package/framework/.claude/skills/design-system-init/CHANGELOG.md +5 -0
  11. package/framework/.claude/skills/design-system-init/SKILL.md +2 -2
  12. package/framework/.claude/skills/e2e-review/CHANGELOG.md +5 -0
  13. package/framework/.claude/skills/e2e-review/SKILL.md +2 -2
  14. package/framework/.claude/skills/frontend-design/CHANGELOG.md +20 -0
  15. package/framework/.claude/skills/frontend-design/SKILL.md +39 -216
  16. package/framework/.claude/skills/gamification-design/CHANGELOG.md +5 -0
  17. package/framework/.claude/skills/gamification-design/SKILL.md +2 -2
  18. package/framework/.claude/skills/ui-design/CHANGELOG.md +60 -1
  19. package/framework/.claude/skills/ui-design/SKILL.md +127 -75
  20. package/framework/.claude/skills/ui-design/references/anti-slop.md +106 -0
  21. package/framework/.claude/skills/ui-design/references/craft-standards.md +259 -0
  22. package/framework/.claude/skills/ui-design/references/design-brief.md +92 -0
  23. package/framework/.claude/skills/ui-design/references/design-directions.md +188 -0
  24. package/framework/.claude/skills/ui-design/references/evaluation.md +125 -165
  25. package/framework/.claude/skills/ui-design/references/generation.md +125 -92
  26. package/framework/.claude/skills/ui-design/references/inventory.md +9 -2
  27. package/framework/.claude/skills/ui-design/scripts/craft-check.mjs +248 -0
  28. package/framework/.claude/skills/worktree-manager/CHANGELOG.md +19 -0
  29. package/framework/.claude/skills/worktree-manager/SKILL.md +29 -8
  30. package/framework/.claude/skills/worktree-manager/scripts/merge-worktree.sh +25 -3
  31. package/framework/agents/component-manifest-schema.md +1 -1
  32. package/framework/agents/design-system-protocol.md +6 -6
  33. package/framework/agents/index.md +1 -0
  34. package/framework/agents/skills-mapping.md +25 -23
  35. package/framework/docs/PROJECT-CONFIGURATION.md +5 -5
  36. package/package.json +1 -1
  37. package/src/commands/configure.js +1 -1
  38. package/src/utils/__tests__/classify-divergence.test.js +42 -0
  39. package/src/utils/git.js +45 -9
@@ -0,0 +1,248 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * craft-check.mjs — deterministic anti-slop / craft gate for ui-design mockups.
4
+ *
5
+ * Scans self-contained mockup HTML for the mechanically-checkable subset of
6
+ * references/anti-slop.md + references/craft-standards.md. Runs identically on
7
+ * Claude Code and Codex (zero deps, Node >= 18) — it is Gate 0 of Step D.
8
+ *
9
+ * Usage:
10
+ * node craft-check.mjs <file.html> [more.html ...] [--json] [--strict-palette]
11
+ *
12
+ * --strict-palette escalate palette-reflex findings (AI-indigo, beige
13
+ * battery, trust gradient) from warn to block. Use in
14
+ * exploration regime; leave off in-context (the project's
15
+ * own brand may legitimately use these families).
16
+ *
17
+ * Exit codes: 0 = PASS (no blockers), 1 = FAIL (>=1 blocker), 2 = usage error.
18
+ *
19
+ * Rule provenance: distilled from impeccable (Apache-2.0, © Paul Bakaus),
20
+ * taste-skill (MIT, © Leonxlnx), open-design (Apache-2.0, nexu-io).
21
+ * Heuristics are conservative: binary rules block, judgment-adjacent rules warn.
22
+ */
23
+
24
+ import { readFileSync } from 'node:fs';
25
+
26
+ const args = process.argv.slice(2);
27
+ const asJson = args.includes('--json');
28
+ const strictPalette = args.includes('--strict-palette');
29
+ const files = args.filter((a) => !a.startsWith('--'));
30
+
31
+ if (files.length === 0) {
32
+ console.error('usage: node craft-check.mjs <file.html> [...] [--json] [--strict-palette]');
33
+ process.exit(2);
34
+ }
35
+
36
+ const INDIGO_HEXES = ['#6366f1', '#4f46e5', '#4338ca', '#3730a3', '#8b5cf6', '#7c3aed', '#a855f7'];
37
+ const BEIGE_BG_HEXES = ['#f5f1ea', '#f7f5f1', '#fbf8f1', '#efeae0', '#ece6db', '#faf7f1', '#e8dfcb'];
38
+ const BEIGE_ACCENT_HEXES = ['#b08947', '#b6553a', '#9a2436', '#9c6e2a', '#bc7c3a', '#7d5621'];
39
+ const EMOJI_RE = /[\u{1F300}-\u{1FAFF}\u{2600}-\u{27BF}\u{2B00}-\u{2BFF}\u{FE0F}]/u;
40
+
41
+ function lineOf(src, index) {
42
+ return src.slice(0, index).split('\n').length;
43
+ }
44
+
45
+ function excerptAt(src, index, len = 80) {
46
+ const start = Math.max(0, src.lastIndexOf('\n', index) + 1);
47
+ const end = src.indexOf('\n', index);
48
+ return src.slice(start, end === -1 ? start + len : Math.min(end, start + len)).trim();
49
+ }
50
+
51
+ /** Collect matches of a regex as {line, excerpt} evidence (capped). */
52
+ function findAll(src, re, cap = 5) {
53
+ const out = [];
54
+ let m;
55
+ const rex = new RegExp(re.source, re.flags.includes('g') ? re.flags : re.flags + 'g');
56
+ while ((m = rex.exec(src)) !== null && out.length < cap) {
57
+ out.push({ line: lineOf(src, m.index), excerpt: excerptAt(src, m.index) });
58
+ if (m.index === rex.lastIndex) rex.lastIndex++;
59
+ }
60
+ return out;
61
+ }
62
+
63
+ /** Strip script/style/comments, then tags → visible text (index-preserving via spaces). */
64
+ function visibleText(src) {
65
+ const blank = (s) => s.replace(/[^\n]/g, ' ');
66
+ return src
67
+ .replace(/<script[\s\S]*?<\/script>/gi, blank)
68
+ .replace(/<style[\s\S]*?<\/style>/gi, blank)
69
+ .replace(/<!--[\s\S]*?-->/g, blank)
70
+ .replace(/<[^>]+>/g, blank);
71
+ }
72
+
73
+ function checkFile(path) {
74
+ const src = readFileSync(path, 'utf8');
75
+ const text = visibleText(src);
76
+ const findings = [];
77
+ const add = (rule, severity, message, evidence) => {
78
+ if (evidence.length > 0) findings.push({ rule, severity, message, count: evidence.length, evidence });
79
+ };
80
+ const palSev = strictPalette ? 'block' : 'warn';
81
+
82
+ // ---- Absolute bans (binary) ------------------------------------------
83
+ add('em-dash', 'block',
84
+ 'Em-dash (or en-dash separator) in visible copy — zero tolerance (anti-slop.md).',
85
+ findAll(text, /—|\s–\s/));
86
+
87
+ add('filler-copy', 'block',
88
+ 'Filler copy (lorem ipsum / "Feature One") — an empty section is a composition problem.',
89
+ findAll(text, /lorem ipsum|\bfeature (one|two|three)\b/i));
90
+
91
+ add('gradient-text', 'block',
92
+ 'Gradient text (background-clip: text) — decorative, never meaningful.',
93
+ findAll(src, /background-clip:\s*text|bg-clip-text/i));
94
+
95
+ add('scroll-cue', 'block',
96
+ '"Scroll to explore" cue — they know what scroll is.',
97
+ findAll(text, /scroll (down )?to (explore|discover|see)/i));
98
+
99
+ add('scroll-listener', 'block',
100
+ 'window scroll listener in script — use IntersectionObserver / animation-library scroll values.',
101
+ findAll(src, /addEventListener\(\s*['"]scroll['"]/));
102
+
103
+ if (!/<meta[^>]+name=["']viewport["']/i.test(src)) {
104
+ findings.push({ rule: 'missing-viewport', severity: 'block', message: 'Missing <meta name="viewport"> — the mockup cannot be judged responsively.', count: 1, evidence: [{ line: 1, excerpt: '<head>' }] });
105
+ }
106
+ if (!/<html[^>]+lang=/i.test(src)) {
107
+ findings.push({ rule: 'missing-lang', severity: 'block', message: 'Missing lang attribute on <html> (identity.language).', count: 1, evidence: [{ line: 1, excerpt: '<html>' }] });
108
+ }
109
+
110
+ // Emoji as icons inside headings/buttons
111
+ {
112
+ const ev = [];
113
+ const rex = /<(h[1-6]|button)\b[^>]*>([\s\S]*?)<\/\1>/gi;
114
+ let m;
115
+ while ((m = rex.exec(src)) !== null && ev.length < 5) {
116
+ const inner = m[2].replace(/<[^>]+>/g, '');
117
+ if (EMOJI_RE.test(inner)) ev.push({ line: lineOf(src, m.index), excerpt: excerptAt(src, m.index) });
118
+ }
119
+ add('emoji-icons', 'block',
120
+ 'Emoji as feature icons in headings/buttons — use monoline SVG with currentColor.', ev);
121
+ }
122
+
123
+ // ---- Rationed patterns (count-bounded) --------------------------------
124
+ {
125
+ const sections = (src.match(/<section\b/gi) || []).length || 1;
126
+ const eyebrows = findAll(src, /class=["'][^"']*\buppercase\b[^"']*\btracking-/g, 50);
127
+ const cap = Math.ceil(sections / 3);
128
+ if (eyebrows.length > cap) {
129
+ findings.push({
130
+ rule: 'eyebrow-overuse', severity: 'block',
131
+ message: `Eyebrow kickers: ${eyebrows.length} for ${sections} section(s) — cap is ceil(sections/3) = ${cap}. Drop them; the headline alone is enough.`,
132
+ count: eyebrows.length, evidence: eyebrows.slice(0, 5),
133
+ });
134
+ }
135
+ }
136
+
137
+ add('numbered-markers', 'warn',
138
+ 'Numbered section markers (01 / 02 · Label) — the eyebrow trope one tier deeper.',
139
+ findAll(text, /\b0\d\s*[\/·]\s*\S/));
140
+
141
+ add('status-dots', 'warn',
142
+ 'Pulsing decorative status dots — zero by default.',
143
+ findAll(src, /class=["'][^"']*animate-pulse[^"']*["']/));
144
+
145
+ add('marquee-overuse', 'warn',
146
+ 'More than one marquee/auto-scroll strip per page.',
147
+ findAll(src, /marquee|animate-\[?scroll/gi).slice(1));
148
+
149
+ // ---- Palette reflexes (warn; block with --strict-palette) -------------
150
+ add('ai-indigo', palSev,
151
+ 'Default-Tailwind indigo/violet accent — the textbook AI tell (LILA rule).',
152
+ findAll(src, new RegExp(INDIGO_HEXES.join('|'), 'i')));
153
+
154
+ add('beige-battery', palSev,
155
+ 'Premium-consumer beige/brass battery — banned as a default; rotate palette families (design-directions.md).',
156
+ findAll(src, new RegExp([...BEIGE_BG_HEXES, ...BEIGE_ACCENT_HEXES].join('|'), 'i')));
157
+
158
+ add('trust-gradient', palSev,
159
+ 'Two-stop purple→blue/cyan/pink "trust" gradient — a flat surface + intentional type beats it.',
160
+ findAll(src, /from-(purple|indigo|violet)-\d+[^"']*to-(blue|cyan|pink|fuchsia)-\d+/i));
161
+
162
+ // ---- Craft warnings ----------------------------------------------------
163
+ add('side-stripe', 'warn',
164
+ 'Colored side-stripe accent (border-left/right > 1px) — the canonical AI dashboard tile.',
165
+ findAll(src, /border-l-[2-8]\b|border-r-[2-8]\b|border-left:\s*[2-9]px\s+solid\s+(?!transparent)/i));
166
+
167
+ add('h-screen', 'warn',
168
+ 'h-screen used — prefer min-h-[100dvh] (iOS Safari).',
169
+ findAll(src, /\bh-screen\b/));
170
+
171
+ add('transition-all', 'warn',
172
+ '`transition: all` — animate transform/opacity explicitly.',
173
+ findAll(src, /transition:\s*all|\btransition-all\b/i));
174
+
175
+ add('br-in-heading', 'warn',
176
+ '<br> inside a heading — plan font scale with the copy instead of breaking lines manually.',
177
+ findAll(src, /<h[1-3][^>]*>(?:(?!<\/h[1-3]>)[\s\S])*?<br\s*\/?>/gi));
178
+
179
+ add('caps-no-tracking', 'warn',
180
+ 'ALL-CAPS element without letter-spacing — uppercase requires +0.06–0.1em tracking.',
181
+ findAll(src, /class=["'](?![^"']*tracking-)[^"']*\buppercase\b[^"']*["']/g));
182
+
183
+ add('justify-text', 'warn',
184
+ 'Justified text — never text-align: justify in UI.',
185
+ findAll(src, /text-align:\s*justify|\btext-justify\b/i));
186
+
187
+ add('fake-metrics', 'warn',
188
+ 'Fake-perfect metric (99.9%, 10×) — real source, labelled mock, or absent (Jane Doe rule).',
189
+ findAll(text, /\b99\.9+\s*%|\b10×|\b100\s*%\s+(faster|better)/i));
190
+
191
+ // First <img> lazy / dimensionless images (CLS)
192
+ {
193
+ const imgs = [...src.matchAll(/<img\b[^>]*>/gi)];
194
+ if (imgs.length > 0 && /loading=["']lazy["']/i.test(imgs[0][0])) {
195
+ findings.push({ rule: 'lcp-lazy', severity: 'warn', message: 'First image is loading="lazy" — never lazy-load the LCP candidate.', count: 1, evidence: [{ line: lineOf(src, imgs[0].index), excerpt: excerptAt(src, imgs[0].index) }] });
196
+ }
197
+ const dimless = imgs.filter((m) => !/\bwidth=|\baspect-ratio|aspect-\[/i.test(m[0]));
198
+ add('img-no-dimensions', 'warn',
199
+ '<img> without width/height or aspect-ratio — reserves no space (CLS).',
200
+ dimless.slice(0, 5).map((m) => ({ line: lineOf(src, m.index), excerpt: excerptAt(src, m.index) })));
201
+ }
202
+
203
+ // Token discipline: distinct raw hexes + off-scale arbitrary values
204
+ {
205
+ const hexes = new Set((src.match(/#[0-9a-fA-F]{6}\b/g) || []).map((h) => h.toLowerCase()));
206
+ if (hexes.size > 12) {
207
+ findings.push({ rule: 'hex-sprawl', severity: 'warn', message: `${hexes.size} distinct raw hex colors — an incomplete palette; consolidate to CSS custom properties.`, count: hexes.size, evidence: [] });
208
+ }
209
+ const offScale = [...src.matchAll(/\b(?:[mp][trblxy]?|gap|space-[xy])-\[(\d+)px\]/g)]
210
+ .filter((m) => Number(m[1]) % 4 !== 0);
211
+ add('off-scale-spacing', 'warn',
212
+ 'Arbitrary spacing off the base-4 scale (e.g. p-[13px]) — token violation.',
213
+ offScale.slice(0, 5).map((m) => ({ line: lineOf(src, m.index), excerpt: m[0] })));
214
+ const sizes = new Set([
215
+ ...[...src.matchAll(/text-\[(\d+)px\]/g)].map((m) => m[1]),
216
+ ...[...src.matchAll(/font-size:\s*(\d+)px/gi)].map((m) => m[1]),
217
+ ]);
218
+ if (sizes.size > 9) {
219
+ findings.push({ rule: 'type-scale-sprawl', severity: 'warn', message: `${sizes.size} distinct px font sizes — one scale, 6–9 deliberate steps.`, count: sizes.size, evidence: [] });
220
+ }
221
+ }
222
+
223
+ const blockers = findings.filter((f) => f.severity === 'block');
224
+ return { file: path, verdict: blockers.length === 0 ? 'PASS' : 'FAIL', blockers: blockers.length, warnings: findings.length - blockers.length, findings };
225
+ }
226
+
227
+ const results = files.map((f) => {
228
+ try {
229
+ return checkFile(f);
230
+ } catch (e) {
231
+ return { file: f, verdict: 'ERROR', error: e.message, findings: [] };
232
+ }
233
+ });
234
+
235
+ if (asJson) {
236
+ console.log(JSON.stringify({ strictPalette, results }, null, 2));
237
+ } else {
238
+ for (const r of results) {
239
+ console.log(`\n${r.verdict === 'PASS' ? '✓' : '✗'} ${r.file} — ${r.verdict}` + (r.error ? ` (${r.error})` : ''));
240
+ for (const f of r.findings || []) {
241
+ console.log(` [${f.severity.toUpperCase()}] ${f.rule} ×${f.count}: ${f.message}`);
242
+ for (const ev of f.evidence.slice(0, 3)) console.log(` L${ev.line}: ${ev.excerpt}`);
243
+ }
244
+ }
245
+ console.log('');
246
+ }
247
+
248
+ process.exit(results.some((r) => r.verdict === 'FAIL' || r.verdict === 'ERROR') ? 1 : 0);
@@ -2,6 +2,25 @@
2
2
 
3
3
  Formato: [Keep a Changelog](https://keepachangelog.com/) · [SemVer](https://semver.org/).
4
4
 
5
+ ## 1.1.0 — 2026-07-03
6
+
7
+ ### Fixed — local-trunk sync robustness (step 4c "Common", both strategies)
8
+
9
+ Il sync del trunk locale falliva l'ff nella maggior parte dei checkout reali,
10
+ lasciando `SYNC-DEFERRED` / `SYNC-NEEDS-DECISION` inutilmente. Due fix mirati,
11
+ **senza `git stash`** (vietato — `refs/stash` è condiviso tra worktree, incidente
12
+ FEAT-0522). SSOT script `scripts/merge-worktree.sh` + prose SKILL.md tenuti in sync.
13
+
14
+ - **Fix 1** — quando il main è su un branch ≠ `$TRUNK`, avanza il *ref* locale
15
+ `refs/heads/$TRUNK` con un fetch ref-only ff-only (`git fetch origin $TRUNK:$TRUNK`),
16
+ **senza toccare il working tree** e senza checkout. Converte una grossa fetta di
17
+ `SYNC-DEFERRED` in sync pulito (`sync_marker: none`). Se `$TRUNK` è checked out in
18
+ un altro worktree git rifiuta → fallback al fetch-only + defer.
19
+ - **Fix 2** — quando l'ff fallisce con tree tracked-clean e nessun file owned/foreign,
20
+ disambigua via `merge-base --is-ancestor` tra **divergenza reale** del branch (serve
21
+ rebase/merge) e **collisione con file untracked** (elencati) — prima riportava sempre
22
+ il fuorviante "clean (diverged?)" (lo scan usa `--untracked-files=no`).
23
+
5
24
  ## 1.0.0 — 2026-07-01
6
25
 
7
26
  - Baseline: versioning per-skill introdotto al framework v4.82.0.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: worktree-manager
3
3
  effort: medium
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  description: >
6
6
  Manage fully independent git worktrees for parallel coding agents.
7
7
  Commands: /nw (new worktree) creates an isolated workspace with its own
@@ -1168,16 +1168,37 @@ if [ "$CURRENT_BRANCH" = "$TRUNK" ]; then
1168
1168
  echo "[SYNC-NEEDS-DECISION] framework-managed reconcile rebase conflict on $TRUNK — orchestrator MUST raise ONE explicit user gate."
1169
1169
  fi
1170
1170
  else
1171
- echo "[SYNC-NEEDS-DECISION] main repo $TRUNK cannot fast-forward and the working tree is clean (diverged?) orchestrator MUST raise ONE explicit user gate."
1171
+ # Fix 2 tracked tree clean, nothing owned/foreign dirty, yet ff failed.
1172
+ # Disambiguate a genuine branch divergence from an untracked-file collision the
1173
+ # incoming ff would overwrite (origin/$TRUNK is current — the pull fetched it).
1174
+ # NEVER stash to clear it (refs/stash is shared across worktrees — FEAT-0522).
1175
+ if git -C "$MAIN" merge-base --is-ancestor "$TRUNK" "origin/$TRUNK" 2>/dev/null; then
1176
+ COLLIDE=""
1177
+ for f in $(git -C "$MAIN" ls-files --others --exclude-standard); do
1178
+ git -C "$MAIN" cat-file -e "origin/$TRUNK:$f" 2>/dev/null && COLLIDE="$COLLIDE $f"
1179
+ done
1180
+ echo "[SYNC-NEEDS-DECISION] main repo $TRUNK is fast-forwardable but untracked file(s) block the merge:${COLLIDE:- (unknown)} — orchestrator MUST raise ONE explicit user gate."
1181
+ else
1182
+ echo "[SYNC-NEEDS-DECISION] main repo $TRUNK has diverged from origin/$TRUNK (local commits not on origin) — needs a rebase/merge decision; orchestrator MUST raise ONE explicit user gate."
1183
+ fi
1172
1184
  fi
1173
1185
  fi
1174
1186
  else
1175
- echo "ℹ️ Main repo HEAD is '$CURRENT_BRANCH' not switching. Next manual \`git pull\` on $TRUNK will sync."
1176
- git -C "$MAIN" fetch origin "$TRUNK" --quiet
1177
- # Structured marker for upstream orchestrators (e.g. /new Phase 6c).
1178
- # `/mw` standalone keeps fetch-only behavior (terminal isolation); orchestrators
1179
- # parse this marker and route reconciliation through an explicit user gate.
1180
- echo "[SYNC-DEFERRED] main repo HEAD=${CURRENT_BRANCH}, local $TRUNK not fast-forwarded — orchestrator workspace-hygiene phase must reconcile."
1187
+ # Fix 1 main is on another branch: do NOT checkout $TRUNK. Fast-forward the local
1188
+ # $TRUNK *ref* without touching any working tree via a ref-only, ff-only fetch.
1189
+ # `git fetch origin $TRUNK:$TRUNK` advances refs/heads/$TRUNK only on a fast-forward
1190
+ # and fails cleanly otherwise (never a checkout, never a stash). If $TRUNK is checked
1191
+ # out in another worktree git refuses we fall back to fetch-only + defer.
1192
+ if git -C "$MAIN" fetch origin "$TRUNK:$TRUNK" --quiet 2>/dev/null; then
1193
+ echo "✅ Local $TRUNK ref fast-forwarded to origin/$TRUNK (main HEAD='$CURRENT_BRANCH' untouched)."
1194
+ else
1195
+ echo "ℹ️ Main repo HEAD is '$CURRENT_BRANCH' — not switching. Next manual \`git pull\` on $TRUNK will sync."
1196
+ git -C "$MAIN" fetch origin "$TRUNK" --quiet
1197
+ # Structured marker for upstream orchestrators (e.g. /new Phase 6c).
1198
+ # `/mw` standalone keeps fetch-only behavior (terminal isolation); orchestrators
1199
+ # parse this marker and route reconciliation through an explicit user gate.
1200
+ echo "[SYNC-DEFERRED] main repo HEAD=${CURRENT_BRANCH}, local $TRUNK not fast-forwarded (diverged or checked out elsewhere) — orchestrator workspace-hygiene phase must reconcile."
1201
+ fi
1181
1202
  fi
1182
1203
  ```
1183
1204
 
@@ -492,12 +492,34 @@ if [ "$CURRENT_BRANCH" = "$TRUNK" ]; then
492
492
  M_SYNCMARK="SYNC-NEEDS-DECISION"; M_SYNCDETAIL="framework-managed reconcile rebase conflict on $TRUNK"
493
493
  fi
494
494
  else
495
- M_SYNCMARK="SYNC-NEEDS-DECISION"; M_SYNCDETAIL="main repo $TRUNK cannot fast-forward and the working tree is clean (diverged?)"
495
+ # Fix 2 tracked tree clean, nothing owned/foreign dirty, yet ff failed.
496
+ # Disambiguate a genuine branch divergence from an untracked-file collision
497
+ # the incoming ff would overwrite (origin/$TRUNK is current — pull fetched it).
498
+ # NEVER stash to clear it (refs/stash is shared across worktrees — FEAT-0522).
499
+ if git -C "$MAIN" merge-base --is-ancestor "$TRUNK" "origin/$TRUNK" 2>/dev/null; then
500
+ COLLIDE=""
501
+ for f in $(git -C "$MAIN" ls-files --others --exclude-standard 2>/dev/null); do
502
+ git -C "$MAIN" cat-file -e "origin/$TRUNK:$f" 2>/dev/null && COLLIDE="$COLLIDE $f"
503
+ done
504
+ M_SYNCMARK="SYNC-NEEDS-DECISION"; M_SYNCDETAIL="main repo $TRUNK is fast-forwardable but untracked file(s) block the merge:${COLLIDE:- (unknown)} — orchestrator MUST raise ONE explicit user gate"
505
+ else
506
+ M_SYNCMARK="SYNC-NEEDS-DECISION"; M_SYNCDETAIL="main repo $TRUNK has diverged from origin/$TRUNK (local commits not on origin) — needs a rebase/merge decision, orchestrator MUST raise ONE explicit user gate"
507
+ fi
496
508
  fi
497
509
  fi
498
510
  else
499
- git -C "$MAIN" fetch origin "$TRUNK" --quiet 2>>"$LOG" || true
500
- M_SYNCMARK="SYNC-DEFERRED"; M_SYNCDETAIL="main repo HEAD=${CURRENT_BRANCH}, local $TRUNK not fast-forwarded"
511
+ # Fix 1 main is on another branch: do NOT checkout $TRUNK. Fast-forward the local
512
+ # $TRUNK *ref* (refs/heads/$TRUNK) WITHOUT touching any working tree via a ref-only,
513
+ # ff-only fetch. `git fetch origin $TRUNK:$TRUNK` advances the local branch ref only
514
+ # on a fast-forward and fails cleanly (non-destructive) otherwise — never a checkout,
515
+ # never a stash. If $TRUNK is checked out in another worktree git refuses and we fall
516
+ # back to the deferred fetch below.
517
+ if git -C "$MAIN" fetch origin "$TRUNK:$TRUNK" --quiet 2>>"$LOG"; then
518
+ M_SYNCMARK="none"; M_SYNCDETAIL="local $TRUNK ref fast-forwarded to origin (main HEAD=${CURRENT_BRANCH}, working tree untouched)"
519
+ else
520
+ git -C "$MAIN" fetch origin "$TRUNK" --quiet 2>>"$LOG" || true
521
+ M_SYNCMARK="SYNC-DEFERRED"; M_SYNCDETAIL="main repo HEAD=${CURRENT_BRANCH}, local $TRUNK not fast-forwarded (diverged or checked out elsewhere)"
522
+ fi
501
523
  fi
502
524
 
503
525
  # 7. Cleanup worktree + registry (only after a verified land). Individual cmds.
@@ -220,7 +220,7 @@ This keeps consumers that have not yet upgraded (and non-TS stacks) from becomin
220
220
  - **Single-element edit**: `/ds-edit` (update one existing component/token —
221
221
  resync / extend / breaking / re-govern; regenerates the HEAD from the new source
222
222
  via the same scripts, **preserving non-empty agentic fields + prose**).
223
- - **Per-task write**: `ui-expert` / `ui-design` / `frontend-design` in the
223
+ - **Per-task write**: `ui-expert` / `ui-design` / `ui-implement` in the
224
224
  Post-Intervention Coherence Check — a new/modified primitive regenerates its
225
225
  HEAD in the same change.
226
226
  - **Curation owner**: `doc-reviewer` (agentic fields).
@@ -12,7 +12,7 @@ twofold:
12
12
  implementation, so existing primitives are reused instead of duplicated.
13
13
 
14
14
  This is the textual SSOT for the registry-first protocol. Skills (`ui-design`,
15
- `frontend-design`, `design-system-init`), agents (`ui-expert`, `code-reviewer`),
15
+ `ui-implement`, `design-system-init`), agents (`ui-expert`, `code-reviewer`),
16
16
  and the `/design-review` command all reference this module instead of
17
17
  duplicating the BLOCKING-read cascade.
18
18
 
@@ -468,7 +468,7 @@ its own — it **proposes, a human confirms**:
468
468
  ## Functional Traceability Gate (BLOCKING — unconditional)
469
469
 
470
470
  A mockup is a fidelity target, **not** a requirements oracle. Design tools
471
- (Claude Design, Figma, the `ui-design`/`frontend-design` generators) routinely
471
+ (Claude Design, Figma, the `ui-design` generator) routinely
472
472
  add interactive-looking chrome — buttons, icons, menu entries, toggles, badges —
473
473
  that no requirement asked for. Implementing a mockup **1:1 for fidelity**
474
474
  materialises that chrome into real markup + dead handlers + unused icon imports,
@@ -524,7 +524,7 @@ For every **orphan**, never decide silently (consistent with the framework's
524
524
 
525
525
  ### Where it runs
526
526
 
527
- 1. **Design / generation time** (`ui-design`, `frontend-design`, `ui-expert`
527
+ 1. **Design / generation time** (`ui-design`, `ui-expert`
528
528
  designing) — when producing a mockup, do not invent interactive chrome with
529
529
  no backing requirement; mark genuinely decorative elements as such in the
530
530
  inventory so the downstream gate is deterministic, not a guess.
@@ -698,7 +698,7 @@ complete** when `features.has_design_system: true` until the registry has
698
698
  been verified against the change that was just made.
699
699
 
700
700
  The check runs at end-of-task by the agent that performed the work (`ui-expert`,
701
- `ui-design`, `frontend-design`, `code-reviewer`). It is **not** delegated to
701
+ `ui-design`, `code-reviewer`). It is **not** delegated to
702
702
  the weekly `ds-drift` routine — that routine is a safety net, not the primary
703
703
  mechanism. Drift introduced today must be reconciled today, not next Monday.
704
704
 
@@ -820,9 +820,9 @@ The following files reference this module instead of duplicating its rules:
820
820
  - `framework/.claude/agents/ui-expert.md`
821
821
  - `framework/.claude/agents/code-reviewer.md`
822
822
  - `framework/.claude/agents/ui-quality-critic.md` (Design Quality Rubric — the
823
- intrinsic-quality critic consumed by `/e2e-review` Phase 4b)
823
+ intrinsic-quality critic consumed by `/e2e-review` Phase 4b and `/ui-design`
824
+ Step D)
824
825
  - `framework/.claude/skills/ui-design/SKILL.md`
825
- - `framework/.claude/skills/frontend-design/SKILL.md`
826
826
  - `framework/.claude/skills/design-system-init/SKILL.md` (bulk registry bootstrap/upgrade)
827
827
  - `framework/.claude/skills/ds-new/SKILL.md` (single-element guided creation)
828
828
  - `framework/.claude/skills/ds-edit/SKILL.md` (single-element guided edit — resync / extend / breaking / re-govern)
@@ -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 the task is a DESIGN DECISION (new page/screen/component to design, redesign/restyle, design options to explore, a local prototype/mockup) -> route to the `/ui-design` skill (the local design studio: Design Read & direction lock, deterministic craft gate, dual-lens evaluation). An ALREADY-APPROVED mockup to implement routes to `/ui-implement`; a direct in-code UI change with no design decision delegates to the `ui-expert` agent (`AGENTS.md` § delegation). NEVER route design work to `frontend-design` (a router since v5.2.0) or `ui-ux-pro-max` (superseded by `ui-design/references/`).
23
24
  - 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
25
  - 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`.
25
26
  - If EDITING one existing design-system element (add a variant/prop, breaking change, re-govern, or resync the spec) and `features.has_design_system: true` -> use the `/ds-edit` skill (classify the change → apply source via `ui-expert` → regenerate the spec via the serializer, PRESERVING agentic fields + prose, same canonical template as `/ds-new` → verify). The pure mechanical doc-resync after a code change is already automatic (serializer + `DS_COMPONENT_STALE` gate); `/ds-edit` is for the deliberate change.
@@ -191,38 +191,38 @@ competing methodology — it detects the task type and passes the matching
191
191
 
192
192
  ## UI/UX Skills
193
193
 
194
- ### frontend-design
195
-
196
- **When to use**:
197
-
198
- - Building UI components
199
- - Creating pages
200
- - Styling work
201
-
202
- **Triggers**:
203
-
204
- - "Create [page]"
205
- - "Build [component]"
206
- - "Design [interface]"
194
+ > **UI routing rule (since v5.2.0).** ALL design-decision work routes to
195
+ > `ui-design` (the local design studio). `frontend-design` is a RETIRED
196
+ > generator kept as a router: its broad triggers still land there, but its
197
+ > body immediately reroutes (design → `/ui-design`, approved mockup → code →
198
+ > `/ui-implement`, direct in-code UI → `ui-expert` agent per `AGENTS.md`
199
+ > § delegation). Never follow pre-v5.2.0 `frontend-design` design guidance.
200
+ > `ui-ux-pro-max` is superseded by `ui-design/references/`
201
+ > (design-directions / craft-standards / anti-slop) — never route to it.
207
202
 
208
203
  ### ui-design
209
204
 
210
205
  **When to use**:
211
206
 
212
- - Designing new pages or components (context-aware mockups)
213
- - The `/prd` skill reaches its UI design phase
214
- - Redesigning existing pages / creating design options for review
207
+ - Designing, prototyping or restyling ANY page/screen/component locally —
208
+ the internal twin of the Claude Design handoff
209
+ - The `/prd` skill reaches its UI design phase (Step 3)
210
+ - Redesigning existing pages / exploring design options or aesthetic
211
+ directions for review
215
212
 
216
213
  **Triggers**:
217
214
 
218
215
  - "/ui-design"
219
- - "Design UI" / "mockup" / "opzioni di design"
220
- - Design-heavy features, visual refinement, design-system work
216
+ - "Design UI" / "mockup" / "prototipo" / "opzioni di design" / "restyle" /
217
+ "rifai la UI" / "migliora il design" / "progetta interfaccia"
218
+ - Design-heavy features, visual refinement, greenfield landing/dashboard work
219
+
220
+ ### frontend-design (router — retired generator)
221
221
 
222
- > `ui-ux-pro-max` is a **tool used inside** the `ui-expert` agent / `ui-design`
223
- > skill (see REGISTRY.md listed as a Key Tool of `ui-expert`), not a
224
- > standalone invocable BALDART skill. Route UI work through `ui-design` /
225
- > `ui-expert`, not to `ui-ux-pro-max` directly.
222
+ **When to use**: never follow it as a workflow. If a trigger lands here
223
+ ("create/build/style [page/component/UI]"), apply its routing table:
224
+ design work `ui-design` · approved mockup `ui-implement` · in-code UI
225
+ change `ui-expert` agent · motion → `motion-design`.
226
226
 
227
227
  ### ui-implement
228
228
 
@@ -674,7 +674,9 @@ Is it a bug fix?
674
674
  |
675
675
  v
676
676
  Is it UI/visual work?
677
- |-- YES --> /ui-design (mockups/options) or frontend-design;
677
+ |-- YES --> design decision (new/redesign/restyle/options) /ui-design;
678
+ | approved mockup → code → /ui-implement;
679
+ | direct in-code UI change → ui-expert agent (AGENTS.md § delegation);
678
680
  | motion → /motion-design
679
681
  |
680
682
  v
@@ -94,8 +94,8 @@ Empty string `""` means the concept doesn't exist in your project. Skills gated
94
94
 
95
95
  | Key | Typical value | Used by |
96
96
  |---|---|---|
97
- | `design_system` | `docs/design-system` | ui-design, frontend-design, prd, bug, kie-ai, simplify, playwright-skill, webapp-testing, gamification-design |
98
- | `ui_guidelines` | `docs/references/ui-guidelines.md` | ui-design, frontend-design, copywriting, gamification-design |
97
+ | `design_system` | `docs/design-system` | ui-design, ui-implement, prd, bug, kie-ai, simplify, playwright-skill, webapp-testing, gamification-design |
98
+ | `ui_guidelines` | `docs/references/ui-guidelines.md` | ui-design, copywriting, gamification-design |
99
99
  | `api_index` | `docs/references/api/index.md` | doc-writing-for-rag |
100
100
  | `api_schemas` | `docs/references/api/schemas.md` | doc-writing-for-rag |
101
101
  | `api_errors` | `docs/references/errors.md` | doc-writing-for-rag |
@@ -157,7 +157,7 @@ Lifecycle:
157
157
  | Key | Type | Notes |
158
158
  |---|---|---|
159
159
  | `brand_name` | string | Public product name |
160
- | `design_philosophy` | string | Free-form, one line. Examples: `"Neo-Brutalism"`, `"Glassmorphism"`, `"Minimalist"`, `"Material 3"`, `""` (no opinion). Drives aesthetic decisions in `ui-design`, `frontend-design`, `kie-ai`, `motion-design`. |
160
+ | `design_philosophy` | string | Free-form, one line. Examples: `"Neo-Brutalism"`, `"Glassmorphism"`, `"Minimalist"`, `"Material 3"`, `""` (no opinion). Drives aesthetic decisions in `ui-design`, `kie-ai`, `motion-design`. |
161
161
  | `language` | BCP-47 tag | `"en"`, `"it"`, etc. Drives copy register in `copywriting`, error-message language in `ui-design`. |
162
162
  | `audience_segments` | array of strings | Domain split, e.g. `["merchant","customer"]` or `["b2b","b2c"]`. Empty `[]` = no segmentation. Drives prompts in `copywriting`, evaluation in `ui-design`. |
163
163
 
@@ -257,7 +257,7 @@ Every flag MUST be present as `true` or `false` once `baldart configure` has run
257
257
 
258
258
  | Flag | Meaning |
259
259
  |---|---|
260
- | `has_design_system` | The project has docs rooted at `paths.design_system`. When `true`, `ui-design`, `bug`, `frontend-design`, `simplify`, `playwright-skill` etc. treat the INDEX as BLOCKING. |
260
+ | `has_design_system` | The project has docs rooted at `paths.design_system`. When `true`, `ui-design`, `ui-implement`, `bug`, `simplify`, `playwright-skill` etc. treat the INDEX as BLOCKING. |
261
261
  | `multi_tenant_theming` | The project supports per-tenant theme overrides. Activates themed-surface text/bg pairing rules in `ui-design`, `playwright-skill`, `webapp-testing`. |
262
262
  | `has_api_docs` | Canonical API docs at `paths.api_index`. Gates `doc-writing-for-rag` schemas/errors workflow. |
263
263
  | `has_backlog` | Card-driven workflow (`paths.backlog_dir` + AGENTS.md card protocol). `new` skill refuses to run without it. |
@@ -337,7 +337,7 @@ These skills had heavy hard-coding before v3.0.0; if your project has opinions i
337
337
  |---|---|
338
338
  | `ui-design` | Design philosophy mandates, theming pairing rules, charting wrappers, browser open command, evaluation thresholds |
339
339
  | `copywriting` | Brand voice pillars, audience register matrix, forbidden vocabulary, language-only rules |
340
- | `frontend-design` | Aesthetic mandates, illustration-motion contract |
340
+ | `frontend-design` | (retired to a router since v5.2.0 — port aesthetic mandates / illustration-motion contract to the `ui-design` overlay) |
341
341
  | `prd` / `prd-add` | Project terminology, mandatory review gates, design-system reads for UI PRDs |
342
342
  | `kie-ai` | Illustration character library, video-prompt motion vocabulary, forbidden aesthetics |
343
343
  | `bug` | Project debug entry points (env summary helper, cache debug switch, error-code module) |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "baldart",
3
- "version": "5.1.0",
3
+ "version": "5.2.1",
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"
@@ -790,7 +790,7 @@ async function interactivePrompts(merged, detected) {
790
790
  ' /design-system-init (or invoke the skill)\n' +
791
791
  'to scaffold INDEX.md + tokens-reference.md + per-component specs at\n' +
792
792
  'docs/design-system/. The registry-first protocol (ui-expert, ui-design,\n' +
793
- 'frontend-design, /design-review) needs these files to enforce coherence.'
793
+ 'ui-implement, /design-review) needs these files to enforce coherence.'
794
794
  );
795
795
  } else if (merged.features.has_design_system === false) {
796
796
  UI.info(
@@ -290,5 +290,47 @@ console.log('\n─── overlayCoversTouched (fs-backed) ───');
290
290
  fsMod.rmSync(tmp, { recursive: true, force: true });
291
291
  }
292
292
 
293
+ // absorbablePayloadSubset(touched) — pure. The subset whose byte-content must
294
+ // match upstream for a commit to be "absorbed". Consumer files (outside
295
+ // `.framework/`) and subtree bookkeeping (VERSION/CHANGELOG/README) are
296
+ // EXCLUDED. Fixtures reproduce the exact mayo v5.0.1→v5.1.0 divergence that
297
+ // falsely blocked the update pre-v5.1.1 (mixed FEAT commits + add-then-revert
298
+ // pairs touching VERSION/CHANGELOG).
299
+ console.log('\n─── absorbablePayloadSubset (pure) ───');
300
+ {
301
+ const cases = [
302
+ { name: 'mixed FEAT commit: framework doc + app files → only the doc survives',
303
+ touched: ['.framework/framework/agents/design-system-protocol.md',
304
+ 'backlog/FEAT-0054-03-enforcement.yml',
305
+ 'docs/references/ssot-registry.md'],
306
+ expected: ['.framework/framework/agents/design-system-protocol.md'] },
307
+ { name: 'mixed FEAT commit: framework doc + CLAUDE.md + eslint config → only the doc',
308
+ touched: ['.framework/framework/agents/design-system-protocol.md',
309
+ 'CLAUDE.md', 'eslint.responsive.config.mjs'],
310
+ expected: ['.framework/framework/agents/design-system-protocol.md'] },
311
+ { name: 'bookkeeping-only touch (VERSION/CHANGELOG/README) → empty subset',
312
+ touched: ['.framework/VERSION', '.framework/CHANGELOG.md', '.framework/README.md'],
313
+ expected: [] },
314
+ { name: 'revert commit: bookkeeping + payload scripts → only the scripts survive',
315
+ touched: ['.framework/CHANGELOG.md', '.framework/README.md', '.framework/VERSION',
316
+ '.framework/framework/.claude/skills/design-system-init/scripts/extract-ts.mjs',
317
+ '.framework/framework/agents/component-manifest-schema.md'],
318
+ expected: ['.framework/framework/.claude/skills/design-system-init/scripts/extract-ts.mjs',
319
+ '.framework/framework/agents/component-manifest-schema.md'] },
320
+ { name: 'consumer-only files → empty subset',
321
+ touched: ['src/app/page.tsx', 'README.md'], expected: [] },
322
+ { name: 'empty touched → empty subset',
323
+ touched: [], expected: [] },
324
+ { name: 'null touched → empty subset (no throw)',
325
+ touched: null, expected: [] },
326
+ ];
327
+ for (const c of cases) {
328
+ const actual = GitUtils.absorbablePayloadSubset(c.touched);
329
+ const ok = JSON.stringify(actual) === JSON.stringify(c.expected);
330
+ if (ok) { pass++; console.log(` ✓ ${c.name}`); }
331
+ else { fail++; console.log(` ✗ ${c.name} → ${JSON.stringify(actual)} (expected ${JSON.stringify(c.expected)})`); }
332
+ }
333
+ }
334
+
293
335
  console.log(`\nResult: ${pass} pass, ${fail} fail`);
294
336
  process.exit(fail > 0 ? 1 : 0);