baldart 5.0.1 → 5.2.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 (58) hide show
  1. package/CHANGELOG.md +157 -0
  2. package/README.md +6 -6
  3. package/VERSION +1 -1
  4. package/framework/.claude/agents/CHANGELOG.md +90 -0
  5. package/framework/.claude/agents/REGISTRY.md +5 -5
  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/senior-researcher.md +166 -151
  9. package/framework/.claude/agents/ui-expert.md +4 -10
  10. package/framework/.claude/agents/ui-quality-critic.md +27 -11
  11. package/framework/.claude/skills/design-system-init/CHANGELOG.md +5 -0
  12. package/framework/.claude/skills/design-system-init/SKILL.md +2 -2
  13. package/framework/.claude/skills/e2e-review/CHANGELOG.md +5 -0
  14. package/framework/.claude/skills/e2e-review/SKILL.md +2 -2
  15. package/framework/.claude/skills/frontend-design/CHANGELOG.md +20 -0
  16. package/framework/.claude/skills/frontend-design/SKILL.md +39 -216
  17. package/framework/.claude/skills/gamification-design/CHANGELOG.md +5 -0
  18. package/framework/.claude/skills/gamification-design/SKILL.md +2 -2
  19. package/framework/.claude/skills/prd/CHANGELOG.md +16 -0
  20. package/framework/.claude/skills/prd/SKILL.md +1 -1
  21. package/framework/.claude/skills/prd/references/discovery-phase.md +5 -1
  22. package/framework/.claude/skills/prd/references/research-phase.md +32 -12
  23. package/framework/.claude/skills/research/CHANGELOG.md +9 -0
  24. package/framework/.claude/skills/research/SKILL.md +94 -0
  25. package/framework/.claude/skills/research/assets/report-compare.template.md +50 -0
  26. package/framework/.claude/skills/research/assets/report-decision.template.md +42 -0
  27. package/framework/.claude/skills/research/assets/report-deep.template.md +61 -0
  28. package/framework/.claude/skills/research/assets/report-regulatory.template.md +44 -0
  29. package/framework/.claude/skills/research/references/playbook.md +112 -0
  30. package/framework/.claude/skills/research/scripts/rebuild-research-index.mjs +77 -0
  31. package/framework/.claude/skills/ui-design/CHANGELOG.md +60 -1
  32. package/framework/.claude/skills/ui-design/SKILL.md +127 -75
  33. package/framework/.claude/skills/ui-design/references/anti-slop.md +106 -0
  34. package/framework/.claude/skills/ui-design/references/craft-standards.md +259 -0
  35. package/framework/.claude/skills/ui-design/references/design-brief.md +92 -0
  36. package/framework/.claude/skills/ui-design/references/design-directions.md +188 -0
  37. package/framework/.claude/skills/ui-design/references/evaluation.md +125 -165
  38. package/framework/.claude/skills/ui-design/references/generation.md +125 -92
  39. package/framework/.claude/skills/ui-design/references/inventory.md +9 -2
  40. package/framework/.claude/skills/ui-design/scripts/craft-check.mjs +248 -0
  41. package/framework/agents/component-manifest-schema.md +1 -1
  42. package/framework/agents/design-system-protocol.md +6 -6
  43. package/framework/agents/index.md +3 -0
  44. package/framework/agents/research-protocol.md +228 -0
  45. package/framework/agents/skills-mapping.md +42 -23
  46. package/framework/docs/PROJECT-CONFIGURATION.md +28 -5
  47. package/framework/templates/baldart.config.template.yml +6 -0
  48. package/framework/templates/research-index.template.md +13 -0
  49. package/framework/templates/research-sources.CHANGELOG.md +14 -0
  50. package/framework/templates/research-sources.template.md +31 -0
  51. package/package.json +1 -1
  52. package/src/commands/configure.js +27 -1
  53. package/src/commands/doctor.js +82 -0
  54. package/src/commands/update.js +48 -1
  55. package/src/utils/__tests__/classify-divergence.test.js +42 -0
  56. package/src/utils/git.js +45 -9
  57. package/src/utils/research-library.js +92 -0
  58. package/src/utils/symlinks.js +3 -0
@@ -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);
@@ -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.
@@ -43,6 +44,7 @@ Route agents to the right module with minimal reading.
43
44
  - If authoring/auditing a subagent whose return would flood the orchestrator context (free-form analysis/audit/research) -> read `agents/return-contract-protocol.md` and bound its final message: COMPACT headline + `path:line` findings + disk pointer, persist the long form. The input-side twin of the effort protocol.
44
45
  - If authoring/editing an agent definition -> the shared operating procedures (injection guard, retrieval, memory, tool budget) live in `agents/agent-operating-protocol.md` and the reviewer verification passes (challenge/simulation/CoVe/risk) in `agents/review-protocol.md` — cite their sections, never re-inline them; keep only the 1-line binding versions in the agent body.
45
46
  - If a skill performs runtime-mechanical operations (spawn an agent, gate on the user, track state, accelerate a fan-out, run an adversarial review) and must work on BOTH Claude Code and Codex -> read `agents/runtime-portability-protocol.md`: bind each abstract operation to the runtime (Claude `Task`/`Workflow`/`AskUserQuestion`/task-spine ↔ Codex named-agent spawn / inline / plain prompt / file-backed queue), detect the runtime once at kickoff, never probe Claude-only primitives on Codex. Shared policy (delegation, no-silent-fallback, worktree isolation) stays in `AGENTS.md` — cited, not restated.
47
+ - If conducting research (technology evaluation, best practices, compliance survey), spawning `senior-researcher`, or managing the research library (`${paths.research_dir}/`) -> read `agents/research-protocol.md` (the matching `SECTION=` only): pass a `PROFILE=<decision|deep|compare|regulatory>` token, run the library reuse pre-flight BEFORE searching, and route sources by nature via the source matrix. Interactive channel: the `/research` skill.
46
48
  - If building or maintaining a derived LLM wiki overlay (`${paths.wiki_dir}/`) -> read `agents/llm-wiki-methodology.md` and invoke `wiki-curator` / `/capture`.
47
49
  - For day-to-day status -> read and update `${paths.references_dir}/project-status.md`.
48
50
  - For legacy context -> read `archive/project_full_legacy.md` if it exists.
@@ -85,6 +87,7 @@ When adding or updating agents, update REGISTRY.md — not this file.
85
87
  - `agents/agent-operating-protocol.md` — Shared operating procedures for the core agents (injection guard, doc-retrieval consumption, persistent-memory hygiene, tool-budget discipline), `SECTION=` dispatch, read only the matching section; agents keep 1-line binding versions inline (since v5.0.0)
86
88
  - `agents/review-protocol.md` — The shared verification engine for reviewers: Challenge + Actionability, Simulation (diff-walk / plan-walk), Chain-of-Verification, quantified risk scoring + absolute severity calibration, specialist-spawn discipline incl. orchestrated-mode `specialist_dispatch` suppression; `SECTION=` dispatch (since v5.0.0)
87
89
  - `agents/doc-audit-protocol.md` — doc-reviewer's audit-mode procedures (drift validator suite, topological generation, epistemic metadata, SCIP anchors, schema/registry drift, coverage gauges); read ONLY when invoked without card context; `SECTION=` dispatch (since v5.0.0)
90
+ - `agents/research-protocol.md` — Research discipline: `PROFILE=<decision|deep|compare|regulatory>` output contracts (+ `DEPTH=` iterative loop), the research library (`paths.research_dir` — layout, report frontmatter, INDEX, archive-not-delete), the reuse pre-flight (`FULL_REUSE`/`DELTA`/`NEW` + per-category TTLs), and the versioned source matrix with its growth loop (`SOURCE_MATRIX_CANDIDATE`); `SECTION=` dispatch. The research-side sibling of `analysis-profiles.md` (since v5.1.0)
88
91
 
89
92
  ## Where to Document (Decision Tree)
90
93
 
@@ -0,0 +1,228 @@
1
+ # Research Protocol — profiles, library, reuse, and the source matrix
2
+
3
+ **Purpose**: the single SSOT for how BALDART conducts research. Consumed by the
4
+ `senior-researcher` agent (worker) and the `/research` skill (interactive
5
+ orchestrator); `/prd` Step 2.5 spawns the worker directly with the tokens
6
+ defined here. One research contract used to fit every request — a
7
+ publication-grade literature review — which conflicted with what orchestrators
8
+ actually need (fast, stack-aware, decision-ready answers) and buried finished
9
+ research where it was never reused. This module fixes both: **profiles** shape
10
+ the output to the purpose, the **library** makes every report a reusable,
11
+ indexed asset, and the **source matrix** routes each research nature to the
12
+ right sources and grows over time.
13
+
14
+ This is the research-side sibling of `analysis-profiles.md` (same mechanism:
15
+ one agent + a `PROFILE=` prompt token + one SSOT module — never a twin agent).
16
+ Prompt-level, zero new config keys beyond `paths.research_dir`, zero runtime
17
+ dependency.
18
+
19
+ ## Contract
20
+
21
+ - **Dispatch**: consumers cite a section as
22
+ `agents/research-protocol.md SECTION=<profiles|library|reuse|sources>`.
23
+ When you (an agent or skill) need the full procedure, Grep this file for the
24
+ heading `### SECTION: <name>` and Read ONLY that section — never this module
25
+ end-to-end.
26
+ - **Degrade-safe**: everything here degrades, never aborts. No
27
+ `paths.research_dir` → deliver to the prompt-supplied path (or
28
+ `${paths.docs_dir}/` as last resort) and skip the library steps. No
29
+ `SOURCES.md` → fall back to the framework default matrix. No matrix at all →
30
+ proceed with judgment and say so in the report.
31
+ - The agent body keeps 1-line BINDING versions of these rules inline; if the
32
+ inline line and this module diverge, **this module wins** — fix the agent.
33
+
34
+ ---
35
+
36
+ ### SECTION: profiles
37
+
38
+ The `PROFILE=<decision|deep|compare|regulatory>` token in the spawn prompt
39
+ selects the research contract. **Token absent → `deep`** (preserves the legacy
40
+ full-report semantics for direct invocation and for existing spawners that
41
+ pass no token).
42
+
43
+ | Profile | Typical consumer | Output contract | Depth |
44
+ |---|---|---|---|
45
+ | `decision` | `/prd` Step 2.5, orchestrators mid-pipeline | Per-topic blocks, **recommendation first**: Recommendation (for THIS stack) → Best practices → Gotchas / anti-patterns → **Contradictions & Unknowns (mandatory section)** → Sources. Matches the `/prd` "Processing Findings" shape byte-for-byte. | Time-boxed; 1–3 questions; no ceremony sections |
46
+ | `deep` | direct user invocation, `/research` | The full §0–§11 report (Retrieval Index, TOC, Executive Summary, Taxonomy, Comparative Analysis, Key Findings, Recommendation, Risks, Evidence Map, Annotated Bibliography, Appendix with Search Log) | Full survey; see the DEPTH token below |
47
+ | `compare` | technology / vendor evaluations | Comparison matrix on fixed axes — performance, cost, complexity, risk, maturity, adoption — then Recommendation + explicit "when NOT to use" per option | Medium; one matrix, no unbounded exploration |
48
+ | `regulatory` | `/prd` S2 signal, compliance questions | Obligation checklist (requirement → source → deadline/threshold → implementation note), jurisdiction-aware; normative sources only for the obligations themselves | High rigor, tightly scoped to the named jurisdiction(s) |
49
+
50
+ Rules that apply to EVERY profile:
51
+
52
+ - **AI-readable minimum**: every report — not just `deep` — is consumed by an
53
+ AI reader with limited context: short paragraphs, dense factual bullets,
54
+ modular self-contained sections, consistent terminology. The full ceremony
55
+ (Retrieval Index, Evidence Map, Search Log) belongs to `deep` only; the
56
+ lighter profiles carry traceability through their per-topic **Sources**
57
+ lists (dated + evidence-labeled).
58
+ - **Recommendation is stack-bound**: evaluate against the stack signature and
59
+ the internal-repo findings supplied in the prompt — "best for THIS project",
60
+ never "best in the abstract".
61
+ - **Recency bias**: for fast-moving topics (frameworks, APIs, tooling) prefer
62
+ sources < 18 months old; date-stamp every source in the bibliography.
63
+ - **Contradictions surface early**: when evidence is split or absent, say so
64
+ near the top (in `decision` it is a mandatory section) — a hidden
65
+ disagreement is the failure mode that sends a project in the wrong
66
+ direction.
67
+
68
+ **`DEPTH=<1..3>` (deep profile only, default 1)** — the iterative
69
+ breadth/depth loop, opt-in so legacy spawns cost what they cost today:
70
+
71
+ - `DEPTH=1` (default): single research pass — today's behavior.
72
+ - `DEPTH=2..3`: before searching, generate 3–5 expert *perspectives* on the
73
+ topic (architect, operator, security, cost-owner, end-user — pick what fits)
74
+ and derive each perspective's questions. Then per round: search → extract
75
+ *learnings* + *follow-up directions* → recurse into the most promising
76
+ directions until DEPTH is exhausted. Each round is bounded by the tool
77
+ budget (`agents/agent-operating-protocol.md SECTION=tool-budget`).
78
+ - At `DEPTH>=2`, before finalizing run a citation spot-check (CoVe-lite):
79
+ WebFetch the sources behind the decision-driving claims and verify they
80
+ actually support them; demote or drop any that don't.
81
+
82
+ ### SECTION: library
83
+
84
+ The research library lives at `${paths.research_dir}` (key in
85
+ `baldart.config.yml`; seeded by the BALDART CLI):
86
+
87
+ ```
88
+ ${paths.research_dir}/
89
+ INDEX.md <- THE map. Agents do lookup HERE first, never by walking dirs.
90
+ SOURCES.md <- live source matrix (consumer-owned; see SECTION: sources)
91
+ architecture/ integrations/ regulatory/ ux-patterns/ algorithms/ tooling/
92
+ _archive/ <- superseded reports: banner added, row removed from INDEX
93
+ ```
94
+
95
+ - **Category**: pick the closest of the six; a report that genuinely fits none
96
+ goes in the closest match with a distinguishing tag (do NOT invent new
97
+ top-level directories — the closed set keeps lookup deterministic).
98
+ - **Report frontmatter** (every report, mandatory — this is the source of
99
+ truth; INDEX.md is a regenerable view over it):
100
+
101
+ ```yaml
102
+ ---
103
+ id: RES-<YYYYMMDD>-<slug> # date + slug, NEVER max+1 (see Concurrency)
104
+ title: <one line>
105
+ category: architecture|integrations|regulatory|ux-patterns|algorithms|tooling
106
+ tags: [<free-form, lowercase, for findability — be generous>]
107
+ profile: decision|deep|compare|regulatory
108
+ nature: scientific|dev|regulatory|ux|market
109
+ stack_signature: "<framework + database + auth, from baldart.config.yml>"
110
+ date: <YYYY-MM-DD>
111
+ valid_until: <YYYY-MM-DD> # date + category TTL (see SECTION: reuse)
112
+ status: current # current | superseded
113
+ questions:
114
+ - <the research questions answered>
115
+ sources_count: <n>
116
+ related: [] # ids of related reports
117
+ ---
118
+ ```
119
+
120
+ - **INDEX.md row format** (append-only; one row per report):
121
+
122
+ ```
123
+ | <id> | <title> | <category> | <profile> | <tags csv> | <date> | <valid_until> | <path> |
124
+ ```
125
+
126
+ - **Archive, never delete**: a superseded report moves to `_archive/`, gets a
127
+ first-line banner `> [!SUPERSEDED] Replaced by <id>. Non-authoritative.`,
128
+ `status: superseded` in frontmatter, and its INDEX row is removed (out of
129
+ the index = invisible to lookup, history preserved in git).
130
+ - **Concurrency**: IDs are `RES-<YYYYMMDD>-<slug>` (add a short unique suffix
131
+ only on a real collision) — never "max existing + 1", which races under
132
+ parallel spawns. When an orchestrator (the `/research` skill, `/prd`) runs
133
+ the show, **the orchestrator pre-allocates id + full path in each spawn
134
+ prompt and serializes the INDEX appends itself** in its filing step; a
135
+ worker spawned solo appends its own row. Reconciliation is deterministic:
136
+ `rebuild-research-index.mjs` (shipped with the `/research` skill)
137
+ regenerates INDEX.md from report frontmatter.
138
+ - **Delivery-path resolution** (total, in order — never undefined, never
139
+ abort): (1) an explicit output path in the spawn prompt is AUTHORITATIVE;
140
+ (2) else `${paths.research_dir}/<category>/<id>-<slug>.md`;
141
+ (3) else (no path, no key) `${paths.docs_dir}/`.
142
+
143
+ ### SECTION: reuse
144
+
145
+ Research repeats across features and projects. Before ANY new research, run
146
+ this pre-flight (skip it only when `paths.research_dir` is missing/empty —
147
+ then use the prompt-supplied path and proceed as a plain one-shot run):
148
+
149
+ 1. **Lookup**: Read `INDEX.md` (only the index — not the reports). Match the
150
+ topic against `title`, `tags`, `category`.
151
+ 2. **On hit, judge fitness**:
152
+ - **Freshness**: compare today against `valid_until`. Category TTLs (used
153
+ to SET `valid_until` at write time): `regulatory` 6 months ·
154
+ `integrations`/`tooling` 6–12 · `ux-patterns` 12 ·
155
+ `architecture`/`algorithms` 24.
156
+ - **Stack match**: compare the report's `stack_signature` with the current
157
+ one.
158
+ 3. **Decide and DECLARE the decision in your report/return** (`REUSE:` line):
159
+ - `FULL_REUSE` — fresh + same stack + questions covered → return the
160
+ existing report; do not search again.
161
+ - `DELTA` — partially stale, stack drifted, or questions partially covered
162
+ → update ONLY what expired, publish as a new report that `related:`-links
163
+ the old one; the old report is archived per SECTION: library.
164
+ - `NEW` — no usable hit.
165
+ - **Anti-false-reuse guard**: doubtful staleness or a different stack →
166
+ `DELTA`, never `FULL_REUSE`. Reuse must never silently serve a stale
167
+ answer to a decision-making consumer.
168
+ 4. **Orchestrator polling contract**: when the spawn prompt pre-allocates an
169
+ output path, ALWAYS write a file there — the new report (`NEW`/`DELTA`), or
170
+ a stub for `FULL_REUSE`:
171
+
172
+ ```
173
+ RESEARCH_OUTPUT_PATH: <this file's own path>
174
+ REUSE: FULL_REUSE -> <path of the existing report>
175
+ ```
176
+
177
+ so a file-exists poll never times out and never matches a stale artifact.
178
+
179
+ ### SECTION: sources
180
+
181
+ The source matrix routes each research **nature** to the right sources. Schema
182
+ (rows = natures; the columns are fixed):
183
+
184
+ | Column | Meaning |
185
+ |---|---|
186
+ | `nature` | scientific · dev · regulatory · ux · market |
187
+ | `primary` | search these first; sufficient alone for [STRONG]/[MODERATE] claims |
188
+ | `secondary` | corroboration and leads; never sole support for a decision-driving claim |
189
+ | `avoid` | known-poor signal for this nature (content farms, marketing, SEO spam) |
190
+ | `notes` | nature-specific heuristics (recency windows, venue quality cues) |
191
+
192
+ **Resolution order** (no dual-SSOT — the default matrix lives in ONE file):
193
+
194
+ 1. `${paths.research_dir}/SOURCES.md` — the live, consumer-owned matrix
195
+ (framework defaults + the project's `## Local additions`).
196
+ 2. `.framework/framework/templates/research-sources.template.md` — the
197
+ framework default matrix (readable inside every consumer install).
198
+ 3. Neither readable → proceed with judgment and state in the report that no
199
+ source matrix was available.
200
+
201
+ **Growth loop** (how the matrix improves project after project): when a run
202
+ hits a matrix gap — an unmapped nature, a newly discovered source that proved
203
+ high-quality in THIS run, or a mapped source that proved poor — append to the
204
+ END of the report:
205
+
206
+ ```markdown
207
+ ## SOURCE_MATRIX_CANDIDATE
208
+
209
+ - nature: <matrix row>
210
+ change: add-source | demote-source | new-nature
211
+ source: <name + URL>
212
+ tier: primary | secondary | avoid
213
+ evidence: <what happened in this run that proves it, 1-3 lines>
214
+
215
+ ### Prompt for BALDART (copy-paste)
216
+
217
+ Aggiorna la matrice fonti in `framework/templates/research-sources.template.md`:
218
+ <change> per nature `<nature>`: `<source>` come `<tier>` — evidenza: <evidence>.
219
+ Bump `matrix_version` (minor per add, patch per demote/note) e aggiungi la voce
220
+ a `framework/templates/research-sources.CHANGELOG.md`.
221
+ ```
222
+
223
+ (the research twin of skill-improver's `## Upstream Candidates`). Also mention
224
+ the candidate in your COMPACT return so the orchestrator can act on it. The
225
+ `/research` skill then offers two applications: (a) immediately to the local
226
+ `SOURCES.md` `## Local additions`, and/or (b) the copy-paste prompt upstream to
227
+ the BALDART maintainer session. Emit a candidate ONLY with run-local evidence —
228
+ never from general knowledge.