baldart 4.78.2 → 4.80.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.
@@ -0,0 +1,111 @@
1
+ # Codex-native subagents
2
+
3
+ > **Status:** shipped in v4.80.0. Claude + Codex. No new `baldart.config.yml` key —
4
+ > rides on `tools.enabled` containing `codex`.
5
+
6
+ ## The problem this fixes
7
+
8
+ BALDART's skills delegate code-understanding, review, and planning to named
9
+ **subagents** (`codebase-architect`, `code-reviewer`, `plan-auditor`, …). For a
10
+ long time those agents were treated as a Claude-only construct: the Codex tool
11
+ adapter declared `supportsSubagents() === false` and `agentsDir() === null`, so
12
+ the 32 agent definitions under `framework/.claude/agents/` were **never installed
13
+ into a Codex tree**.
14
+
15
+ The result, on Codex: a skill said *"invoke the `codebase-architect` agent"*, the
16
+ agent did not exist, there was no obvious mechanism to spawn it, and the model
17
+ **improvised** — e.g. falling back to a generic "explorer" sub-agent. Not a
18
+ comprehension failure; a missing portability contract.
19
+
20
+ Meanwhile Codex shipped **custom agents** (subagents). So the fix is not "degrade
21
+ on Codex" — it is "install the agents on Codex, in Codex's own format".
22
+
23
+ ## Why a transpiler, not a symlink
24
+
25
+ Skills are portable by **symlink** because the on-disk format is identical for
26
+ both tools (`SKILL.md` + frontmatter). One source, two symlinks, zero
27
+ duplication.
28
+
29
+ A subagent is **not** identical across tools:
30
+
31
+ | | Claude Code | Codex |
32
+ |---|---|---|
33
+ | Location | `.claude/agents/<name>.md` | `.codex/agents/<name>.toml` |
34
+ | Format | Markdown + YAML frontmatter | TOML |
35
+ | Required fields | `name`, `description` + body | `name`, `description`, `developer_instructions` |
36
+ | Effort knob | `effort: low\|…\|max` | `model_reasoning_effort: low\|medium\|high` |
37
+ | Invocation | Task/Agent tool, `subagent_type` | spawned **by `name`** |
38
+ | Nesting | (orchestrator spawns) | `agents.max_depth: 1` (no deeper nesting) |
39
+
40
+ Because the format differs, the same `framework/.claude/agents/<name>.md` stays
41
+ the **single source of truth** and BALDART **generates** the `.toml` from it on
42
+ install/update. The generated file is a real file (you cannot symlink a `.md`
43
+ source onto a `.toml` target), carrying a `# baldart-generated:` marker so:
44
+
45
+ - a later `update` regenerates it safely (idempotent — unchanged source → no rewrite),
46
+ - a user fork (a `.toml` without the marker) is **never clobbered** (recorded as a conflict).
47
+
48
+ ## Field mapping (`.md` → `.toml`)
49
+
50
+ Implemented in [`src/utils/codex-agent-transpiler.js`](../../src/utils/codex-agent-transpiler.js):
51
+
52
+ | Claude frontmatter / body | Codex TOML | Notes |
53
+ |---|---|---|
54
+ | `name` | `name` | spawn identity; source of truth |
55
+ | `description` | `description` | collapsed to a single line |
56
+ | markdown body | `developer_instructions` | the agent's system prompt; emitted as a TOML multi-line **literal** (`'''…'''`) so backslashes/quotes survive byte-for-byte (falls back to escaped `"""…"""` only if the body itself contains `'''`) |
57
+ | `effort: low\|medium\|high\|xhigh\|max` | `model_reasoning_effort: low\|medium\|high` | `xhigh`/`max` clamp to `high` |
58
+ | `model: opus\|sonnet\|haiku` | *(omitted)* | no 1:1 Codex model → inherit the parent session model (the safe default) |
59
+
60
+ Overlays apply **first**: when `.baldart/overlays/agents/<name>.md` exists the
61
+ markdown overlay is merged (the normal `## [OVERRIDE]/[APPEND]/[PREPEND]`
62
+ machinery), then the merged markdown is transpiled — so Codex agents honour
63
+ overlays exactly like Claude agents. The overlay's HTML-comment marker is
64
+ stripped before it can leak into `developer_instructions`.
65
+
66
+ ## How it is wired
67
+
68
+ The transpile rides on the **existing** per-item merge machinery
69
+ (`SymlinkUtils._mergeBulkDir`) through one new adapter hook:
70
+
71
+ ```js
72
+ // codex.js
73
+ agentsDir() { return '.codex/agents'; } // was null
74
+ supportsSubagents() { return true; } // was false
75
+ transpilerFor(kind) { // NEW
76
+ if (kind !== 'agent') return null;
77
+ const t = require('../codex-agent-transpiler');
78
+ return { outExt: '.toml', transform: t.mdAgentToToml, isGenerated: t.isGeneratedToml };
79
+ }
80
+ ```
81
+
82
+ When `_mergeBulkDir` sees a non-null `transpilerFor(kind)` it routes that kind
83
+ through `_installTranspiledFile` (generate `.toml`) instead of the symlink /
84
+ overlay path. `claude.transpilerFor()` returns null, so Claude is unchanged
85
+ (agents still symlinked `.md`). `commandsDir`/`workflowsDir`/`outputStylesDir`
86
+ stay null for Codex — slash commands, dynamic workflows, and output styles have
87
+ no Codex equivalent.
88
+
89
+ Existing Codex consumers pick this up automatically on the next
90
+ `npx baldart update` (which calls `mergeAgents`), and `baldart doctor`'s
91
+ `mergeAgents` backfill covers any drift.
92
+
93
+ ## The authoring contract (for every new agent, from now on)
94
+
95
+ Any new agent is **auto-portable** — just drop a `.md` under
96
+ `framework/.claude/agents/`. To keep it clean across both tools:
97
+
98
+ 1. **Keep `name` + `description` in the frontmatter.** `name` is the spawn
99
+ identity on both tools; `description` is how Codex chooses which agent to spawn.
100
+ 2. **Put the agent's behaviour in the body** — it becomes `developer_instructions`.
101
+ 3. **Set `effort:`** if the agent needs a non-default reasoning tier; it maps to
102
+ `model_reasoning_effort`.
103
+ 4. **Do not rely on a subagent spawning a further subagent.** Codex caps
104
+ `agents.max_depth` at 1 — consistent with BALDART's own `## Role boundary`
105
+ ("no Task/Agent tool") convention and the "nested subagents not available"
106
+ reality on Claude. Orchestration belongs to the skill, not the agent.
107
+ 5. **Avoid Claude-only tool references in the body that have no Codex analogue.**
108
+ Prefer the portable retrieval cascade (code-graph → LSP → Grep → reads).
109
+
110
+ Nothing else is required — no per-agent Codex file, no registry entry beyond the
111
+ usual `REGISTRY.md` row.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "baldart",
3
- "version": "4.78.2",
3
+ "version": "4.80.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"
@@ -0,0 +1,139 @@
1
+ const yaml = require('js-yaml');
2
+
3
+ /**
4
+ * Codex agent transpiler — turns a BALDART Claude subagent definition
5
+ * (`.claude/agents/<name>.md`, YAML frontmatter + markdown body) into a Codex
6
+ * custom-agent definition (`.codex/agents/<name>.toml`).
7
+ *
8
+ * Why a transpiler and not a symlink (the model used for skills): a skill has
9
+ * the SAME on-disk format for Claude and Codex, so one source file is linked
10
+ * into two places. A subagent does NOT — Codex custom agents are TOML files
11
+ * with a different field set, living under a different root (`.codex/agents/`,
12
+ * not `.agents/skills/`). So the SAME source `.md` is the SSOT and we *generate*
13
+ * the `.toml` from it on install/update. Edit the `.md`; never hand-edit the
14
+ * generated `.toml`.
15
+ *
16
+ * Field mapping (Claude frontmatter → Codex TOML):
17
+ * name → name (source of truth for spawn identity)
18
+ * description → description (single-lined)
19
+ * <body> → developer_instructions (the agent's system prompt)
20
+ * effort → model_reasoning_effort (low|medium|high — see mapEffort)
21
+ * model → (OMITTED on purpose) opus/sonnet/haiku have no 1:1 Codex
22
+ * model; inheriting the parent session
23
+ * model is the safe, correct default.
24
+ *
25
+ * Codex docs: developers.openai.com/codex/subagents — required fields are
26
+ * `name`, `description`, `developer_instructions`; optional `model`,
27
+ * `model_reasoning_effort`, `sandbox_mode`, `nickname_candidates`,
28
+ * `mcp_servers`, `skills.config`. Agents are spawned by their `name`.
29
+ */
30
+
31
+ const TOML_MARKER_PREFIX = '# baldart-generated:';
32
+
33
+ /**
34
+ * Map a BALDART effort tier (low|medium|high|xhigh|max — see
35
+ * framework/agents/effort-protocol.md) onto Codex's `model_reasoning_effort`,
36
+ * which only accepts low|medium|high. xhigh/max clamp to high (the ceiling).
37
+ * Returns null when there's nothing to map (omit the key → inherit parent).
38
+ */
39
+ function mapEffort(effort) {
40
+ if (!effort) return null;
41
+ const e = String(effort).trim().toLowerCase();
42
+ if (e === 'low') return 'low';
43
+ if (e === 'medium') return 'medium';
44
+ if (e === 'high' || e === 'xhigh' || e === 'max') return 'high';
45
+ return null;
46
+ }
47
+
48
+ /** Split leading YAML frontmatter from the markdown body. */
49
+ function splitFrontmatter(md) {
50
+ let rest = String(md).replace(/^/, '');
51
+ const m = rest.match(/^---\n([\s\S]*?)\n---\n?/);
52
+ if (m) return { frontmatter: m[1], body: rest.slice(m[0].length) };
53
+ return { frontmatter: '', body: rest };
54
+ }
55
+
56
+ /**
57
+ * Strip a leading `<!-- baldart-generated: … -->` HTML-comment marker. Present
58
+ * only when the source markdown was overlay-merged (the marker sits right after
59
+ * the frontmatter); it must not leak into developer_instructions.
60
+ */
61
+ function stripHtmlMarker(body) {
62
+ return String(body).replace(/^\s*<!--\s*baldart-generated:[\s\S]*?-->\s*\n/, '');
63
+ }
64
+
65
+ /** Emit a TOML basic (single-line) string with the required escapes. */
66
+ function tomlBasicString(s) {
67
+ return '"' + String(s)
68
+ .replace(/\\/g, '\\\\')
69
+ .replace(/"/g, '\\"')
70
+ .replace(/[\r\n\t]+/g, ' ')
71
+ .trim() + '"';
72
+ }
73
+
74
+ /**
75
+ * Emit a TOML multi-line string for an arbitrary markdown body.
76
+ *
77
+ * Prefer a multi-line LITERAL string (`'''…'''`): it processes no escapes, so
78
+ * markdown full of backslashes and double-quotes survives byte-for-byte. The
79
+ * only thing it cannot contain is `'''`, so fall back to a multi-line BASIC
80
+ * string (`"""…"""`) with escaping when the body has a triple-single-quote.
81
+ * A leading newline (trimmed by the TOML spec) keeps the body starting exactly
82
+ * as authored; a trailing newline keeps a body-final quote from abutting the
83
+ * closing delimiter.
84
+ */
85
+ function tomlMultiline(s) {
86
+ const body = String(s).replace(/\r\n/g, '\n').replace(/\r/g, '\n');
87
+ if (!body.includes("'''")) {
88
+ return "'''\n" + body + "\n'''";
89
+ }
90
+ const esc = body.replace(/\\/g, '\\\\').replace(/"""/g, '\\"\\"\\"');
91
+ return '"""\n' + esc + '\n"""';
92
+ }
93
+
94
+ /**
95
+ * Transpile one Claude agent `.md` into Codex `.toml` text.
96
+ * @param {string} markdown - raw `.md` content (frontmatter + body)
97
+ * @param {{name?: string, frameworkVersion?: string}} [opts]
98
+ * @returns {string} TOML content (ends with a trailing newline)
99
+ */
100
+ function mdAgentToToml(markdown, opts = {}) {
101
+ const { frontmatter, body } = splitFrontmatter(markdown);
102
+ let meta = {};
103
+ if (frontmatter) {
104
+ try { meta = yaml.load(frontmatter) || {}; }
105
+ catch (_) { meta = {}; }
106
+ }
107
+
108
+ const name = String(meta.name || opts.name || '').trim();
109
+ if (!name) {
110
+ throw new Error('codex-agent-transpiler: agent has no `name` (neither frontmatter nor filename provided one)');
111
+ }
112
+ const description = String(meta.description || '').replace(/\s+/g, ' ').trim();
113
+ const instructions = stripHtmlMarker(body).replace(/^\n+/, '').replace(/\n+$/, '\n');
114
+ const effort = mapEffort(meta.effort);
115
+
116
+ const lines = [];
117
+ lines.push(`${TOML_MARKER_PREFIX} kind=agent name=${name} v=${opts.frameworkVersion || 'unknown'}`);
118
+ lines.push(`# Transpiled from .claude/agents/${name}.md by BALDART — edit the source, not this file.`);
119
+ lines.push(`name = ${tomlBasicString(name)}`);
120
+ if (description) lines.push(`description = ${tomlBasicString(description)}`);
121
+ if (effort) lines.push(`model_reasoning_effort = ${tomlBasicString(effort)}`);
122
+ lines.push(`developer_instructions = ${tomlMultiline(instructions)}`);
123
+
124
+ return lines.join('\n') + '\n';
125
+ }
126
+
127
+ /** True when a TOML file was generated by BALDART (safe to overwrite/regenerate). */
128
+ function isGeneratedToml(content) {
129
+ return typeof content === 'string' && content.includes(TOML_MARKER_PREFIX);
130
+ }
131
+
132
+ module.exports = {
133
+ mdAgentToToml,
134
+ isGeneratedToml,
135
+ mapEffort,
136
+ splitFrontmatter,
137
+ tomlMultiline,
138
+ TOML_MARKER_PREFIX
139
+ };
@@ -425,6 +425,18 @@ class SymlinkUtils {
425
425
  const overlayAbs = path.join(this.cwd, overlayRel);
426
426
  const hasOverlay = cfg.overlay && fs.existsSync(overlayAbs);
427
427
 
428
+ // Transpile path: when the adapter declares a transpiler for this kind
429
+ // (e.g. Codex agents → TOML), do NOT symlink the `.md` source — generate
430
+ // the tool-native file from it (overlay-merged first when present).
431
+ const transpiler = (typeof adapter.transpilerFor === 'function') ? adapter.transpilerFor(kind) : null;
432
+ if (transpiler) {
433
+ this._installTranspiledFile({
434
+ kind, baseName, fwAbsolute, overlayAbs, hasOverlay,
435
+ targetRel, transpiler, adapter, frameworkVersion, aggregate
436
+ });
437
+ continue;
438
+ }
439
+
428
440
  // Inspect what currently lives at the link path (if anything).
429
441
  let lstat = null;
430
442
  try { lstat = fs.lstatSync(linkPath); } catch (_) { /* absent */ }
@@ -525,6 +537,77 @@ class SymlinkUtils {
525
537
  });
526
538
  }
527
539
 
540
+ /**
541
+ * Install a transpiled payload for a tool whose on-disk format differs from
542
+ * the framework source (currently: Codex agents, `.md` → `.toml`).
543
+ *
544
+ * The framework `.md` stays the single SSOT; the `.toml` is *generated* (a
545
+ * real file, never a symlink — you can't symlink a `.md` source to a `.toml`
546
+ * target). When an overlay exists it is merged FIRST (reusing the markdown
547
+ * overlay machinery), so Codex agents honour overlays exactly like Claude
548
+ * agents do, then the merged markdown is transpiled. The output carries a
549
+ * `# baldart-generated:` marker so a later update can safely regenerate it
550
+ * and a user fork (no marker) is never clobbered.
551
+ */
552
+ _installTranspiledFile({ kind, baseName, fwAbsolute, overlayAbs, hasOverlay, targetRel, transpiler, adapter, frameworkVersion, aggregate }) {
553
+ const outName = `${baseName}${transpiler.outExt}`;
554
+ const outPath = path.join(this.cwd, targetRel, outName);
555
+ const relOut = path.join(targetRel, outName);
556
+
557
+ // Effective markdown = overlay-merged when an overlay exists, else the base.
558
+ const baseContent = fs.readFileSync(fwAbsolute, 'utf8');
559
+ let effectiveMd = baseContent;
560
+ if (hasOverlay) {
561
+ try {
562
+ const { mergeOverlay } = require('./overlay-merger');
563
+ const overlayContent = fs.readFileSync(overlayAbs, 'utf8');
564
+ const merged = mergeOverlay({ kind, name: baseName, baseContent, overlayContent, frameworkVersion });
565
+ effectiveMd = merged.generated;
566
+ (merged.warnings || []).forEach((w) => {
567
+ UI.warning(`[${adapter.label}] ${kind} ${baseName}: ${w}`);
568
+ aggregate.warnings.push({ tool: adapter.name, kind, name: baseName, warning: w });
569
+ });
570
+ } catch (_) { effectiveMd = baseContent; /* fail-safe: transpile the base */ }
571
+ }
572
+
573
+ let outContent;
574
+ try {
575
+ outContent = transpiler.transform(effectiveMd, { name: baseName, frameworkVersion });
576
+ } catch (err) {
577
+ UI.warning(`[${adapter.label}] ${kind} ${baseName}: transpile failed (${err.message}) — skipped.`);
578
+ aggregate.warnings.push({ tool: adapter.name, kind, name: baseName, warning: `transpile-failed: ${err.message}` });
579
+ return;
580
+ }
581
+
582
+ let lstat = null;
583
+ try { lstat = fs.lstatSync(outPath); } catch (_) { /* absent */ }
584
+ if (lstat) {
585
+ if (lstat.isSymbolicLink()) {
586
+ // Stale symlink from an earlier layout — replace with the generated file.
587
+ fs.unlinkSync(outPath);
588
+ } else {
589
+ const existing = fs.readFileSync(outPath, 'utf8');
590
+ if (!transpiler.isGenerated(existing)) {
591
+ UI.warning(`[${adapter.label}] ${kind} ${baseName}: ${relOut} exists without a baldart-generated marker — NOT overwriting (user fork?).`);
592
+ aggregate.conflicts.push({
593
+ tool: adapter.name, kind, name: baseName,
594
+ local_path: relOut, reason: 'manual-edit-no-marker',
595
+ detected_at: new Date().toISOString()
596
+ });
597
+ return;
598
+ }
599
+ if (existing === outContent) {
600
+ aggregate.skipped.push({ tool: adapter.name, kind, name: baseName, reason: 'already-generated' });
601
+ return;
602
+ }
603
+ }
604
+ }
605
+
606
+ fs.writeFileSync(outPath, outContent);
607
+ UI.success(`[${adapter.label}] ${kind} transpiled: ${relOut}`);
608
+ aggregate.generated.push({ tool: adapter.name, kind, name: baseName });
609
+ }
610
+
528
611
  _generateOverlayedFile({ kind, name, fwAbsolute, overlayAbs, linkPath, lstat, frameworkVersion, aggregate, adapter, targetRel }) {
529
612
  const { mergeOverlay, isGeneratedFile, computeBaseFileSha, ensureBaseFileShaInOverlay, refreshOverlayVersionPin } = require('./overlay-merger');
530
613
  const baseContent = fs.readFileSync(fwAbsolute, 'utf8');
@@ -706,12 +789,16 @@ class SymlinkUtils {
706
789
  allValid = false;
707
790
  } else {
708
791
  const { isGeneratedFile } = require('./overlay-merger');
792
+ const { isGeneratedToml } = require('./codex-agent-transpiler');
709
793
  const entries = fs.readdirSync(full);
710
794
  const frameworkOwned = entries.filter((n) => {
711
795
  const p = path.join(full, n);
712
796
  try {
713
797
  if (fs.lstatSync(p).isSymbolicLink()) return true;
714
- return isGeneratedFile(fs.readFileSync(p, 'utf8'));
798
+ // Symlinked (Claude .md) OR transpiled-generated (Codex .toml) OR
799
+ // overlay-generated (Claude .md with HTML marker).
800
+ const c = fs.readFileSync(p, 'utf8');
801
+ return isGeneratedFile(c) || isGeneratedToml(c);
715
802
  } catch { return false; }
716
803
  });
717
804
  if (frameworkOwned.length === 0) {
@@ -35,6 +35,13 @@ class ClaudeAdapter {
35
35
  /** Where this tool reads output-style definitions from (null if unsupported). */
36
36
  outputStylesDir() { return '.claude/output-styles'; }
37
37
 
38
+ /**
39
+ * Per-kind transpiler hook. Claude consumes every payload in its native
40
+ * on-disk format, so nothing is transpiled — the merge machinery symlinks
41
+ * the source verbatim. (Codex returns a transpiler for `agent`.)
42
+ */
43
+ transpilerFor(/* kind */) { return null; }
44
+
38
45
  /** Whether this tool consumes subagent definitions (`.claude/agents/`). */
39
46
  supportsSubagents() { return true; }
40
47
 
@@ -19,9 +19,16 @@ const os = require('os');
19
19
  * Codex reads from `.agents/skills/<x>`, and both follow the symlink to
20
20
  * the single source under `.framework/framework/.claude/skills/<x>/`.
21
21
  *
22
- * Codex does NOT have an equivalent of Claude subagents (`.claude/agents/`)
23
- * or slash commands (custom prompts are deprecated in favor of skills).
24
- * AGENTS.md at the repo root IS read by Codex — same convention as Claude.
22
+ * Codex DOES have custom agents (since the subagents feature shipped), but in
23
+ * a different shape than Claude: TOML files under `.codex/agents/` (project) or
24
+ * `~/.codex/agents/` (personal), with fields name / description /
25
+ * developer_instructions (+ optional model_reasoning_effort, sandbox_mode, …),
26
+ * spawned by `name`. Because the format differs from Claude's markdown
27
+ * subagents, BALDART does not symlink them — it *transpiles* the same `.md`
28
+ * source into `.toml` via `codex-agent-transpiler` (see `transpilerFor`).
29
+ * Slash commands have no Codex equivalent (custom prompts are deprecated in
30
+ * favor of skills). AGENTS.md at the repo root IS read by Codex — same
31
+ * convention as Claude.
25
32
  */
26
33
  class CodexAdapter {
27
34
  constructor(cwd = process.cwd()) {
@@ -33,13 +40,27 @@ class CodexAdapter {
33
40
 
34
41
  skillsDir() { return '.agents/skills'; }
35
42
 
36
- // Codex has no equivalent of Claude subagents or slash commands (custom
37
- // prompts are deprecated in favor of skills). Returning null disables the
38
- // per-item merge for those kinds adapters that DO support them return
39
- // the target directory string.
40
- agentsDir() { return null; }
43
+ // Codex custom agents live under `.codex/agents/` as TOML files. They are
44
+ // NOT symlinked from the `.md` source (the format differs) the merge
45
+ // machinery routes this kind through `transpilerFor('agent')` to *generate*
46
+ // a `.toml` from each source `.md`. Slash commands have no Codex equivalent
47
+ // (custom prompts are deprecated in favor of skills) null disables that merge.
48
+ agentsDir() { return '.codex/agents'; }
41
49
  commandsDir() { return null; }
42
50
 
51
+ /**
52
+ * Per-kind transpiler. The merge machinery (`SymlinkUtils._mergeBulkDir`)
53
+ * calls this for every merge kind; a non-null return means "do NOT symlink
54
+ * the `.md` source — generate `outExt` content from it via `transform`".
55
+ * Only `agent` is transpiled (markdown subagent → Codex TOML); every other
56
+ * kind returns null (and Codex already disables them via a null dirGetter).
57
+ */
58
+ transpilerFor(kind) {
59
+ if (kind !== 'agent') return null;
60
+ const t = require('../codex-agent-transpiler');
61
+ return { outExt: '.toml', transform: t.mdAgentToToml, isGenerated: t.isGeneratedToml };
62
+ }
63
+
43
64
  // Codex has no equivalent of Claude Code dynamic workflows (the JS
44
65
  // orchestration runtime is Claude-only). Returning null disables the
45
66
  // per-item workflow merge for Codex — the same source workflow scripts are
@@ -51,7 +72,7 @@ class CodexAdapter {
51
72
  // Codex — the source style files are simply never linked into a Codex tree.
52
73
  outputStylesDir() { return null; }
53
74
 
54
- supportsSubagents() { return false; }
75
+ supportsSubagents() { return true; }
55
76
  supportsSlashCommands() { return false; }
56
77
  supportsHooks() { return false; }
57
78
 
@@ -1,137 +0,0 @@
1
- # Claude Design Handoff Prompt — Template
2
-
3
- > Template populated at runtime by **Step 3.0 (Mockup Source Decision)** in
4
- > [../references/ui-design-phase.md](../references/ui-design-phase.md) when the
5
- > user picks the **handoff** branch. All `{{placeholders}}` are resolved from the
6
- > PRD state file (`${paths.prd_dir}/sessions/YYYY-MM-DD-<slug>-state.md`),
7
- > `baldart.config.yml`, and optional overlays.
8
- >
9
- > Render rules:
10
- > - Conditional sections (marked `{{#if …}} … {{/if}}`) are emitted only when the
11
- > condition is true. Skip the entire section (including its heading) when false.
12
- > - Lists (marked `{{#each …}}`) render one bullet per item; emit "—" if empty.
13
- > - The rendered output is presented to the user inside a fenced markdown
14
- > code-block so they can copy-paste verbatim into Claude Design.
15
-
16
- ---
17
-
18
- # Feature: {{feature_title}}
19
-
20
- ## 1. Obiettivo
21
-
22
- {{feature_objective_one_sentence}}
23
-
24
- ## 2. User flow
25
-
26
- {{user_flow_summary}}
27
-
28
- {{#if primary_personas}}
29
- **Personas target**:
30
- {{#each primary_personas}}
31
- - {{this.name}} — {{this.context}}
32
- {{/each}}
33
- {{/if}}
34
-
35
- ## 3. Schermate in scope
36
-
37
- > **Vincolo di tracciabilità funzionale.** Inserisci SOLO controlli (bottoni,
38
- > icone-azione, voci di menu, tab, toggle) legati a una funzione elencata qui
39
- > sotto. Non aggiungere chrome interattivo "per completezza visiva": ogni
40
- > affordance senza funzione diventa codice morto in implementazione. Se proponi
41
- > un elemento interattivo non presente nelle azioni primarie, segnalalo in
42
- > consegna come *proposta da validare*, non come requisito. Gli elementi
43
- > puramente decorativi (icone di sezione, marchi) marcali esplicitamente come
44
- > **decorativi**.
45
-
46
- {{#each screens_in_scope}}
47
- ### {{this.name}}
48
-
49
- - **Scopo**: {{this.purpose}}
50
- - **Stati richiesti**: {{this.states_required}} <!-- empty, loading, populated, error, success, ecc. -->
51
- - **Azioni primarie**: {{this.primary_actions}}
52
- - **Dati visualizzati**: {{this.data_shown}}
53
- {{#if this.notes}}
54
- - **Note**: {{this.notes}}
55
- {{/if}}
56
-
57
- {{/each}}
58
- > **Nota per l'export.** Dai al canvas di questa feature un titolo che contenga il
59
- > nome feature («{{feature_title}}»), e numera gli artboard in modo coerente con gli
60
- > ID schermata qui sopra (es. schermata 3.1 → artboard `s31`). Scaricherò il bundle
61
- > completo del workspace: questo mi permette di ritrovare esattamente le TUE schermate
62
- > ed evitare ambiguità con le altre feature.
63
-
64
- {{#if brand_voice}}
65
- ## 4. Brand & voice
66
-
67
- - **Tono**: {{brand_voice.tone}}
68
- - **Personalità**: {{brand_voice.personality}}
69
- - **Do**: {{brand_voice.do}}
70
- - **Don't**: {{brand_voice.dont}}
71
- {{#if brand_voice.references}}
72
- - **Riferimenti visivi**: {{brand_voice.references}}
73
- {{/if}}
74
- {{/if}}
75
-
76
- {{#if has_design_system}}
77
- ## 5. Design system constraints
78
-
79
- > Riusa SOLO i token e le primitive elencate. Se devi proporre qualcosa di nuovo,
80
- > segnalalo esplicitamente nella consegna così posso aggiornare il registro.
81
-
82
- ### Token chiave
83
-
84
- ```
85
- {{design_tokens_excerpt}}
86
- ```
87
-
88
- ### Primitive da riusare
89
-
90
- {{#each registered_primitives}}
91
- - **{{this.name}}** — {{this.purpose}} ({{this.path}})
92
- {{/each}}
93
-
94
- ### Vincoli numerici (da `design-system-protocol.md`)
95
-
96
- - Type scale: {{tokens.type_scale}}
97
- - Spacing scale: {{tokens.spacing_scale}}
98
- - Radius: {{tokens.radius_scale}}
99
- - Contrast minimo: {{tokens.contrast_target}} (WCAG AA + APCA Lc ≥ {{tokens.apca_target}})
100
- - Motion durations: {{tokens.motion_durations}}
101
- {{/if}}
102
-
103
- ## 6. Stack target
104
-
105
- - **Framework**: {{stack.framework}}
106
- - **Output preferito**: {{output_format_preference}} <!-- "HTML/CSS standalone" | "React component (Tailwind)" | "Figma frame" -->
107
- {{#if stack.charting}}
108
- - **Charting library**: {{stack.charting}}
109
- {{/if}}
110
- {{#if stack.animation}}
111
- - **Animation library**: {{stack.animation}}
112
- {{/if}}
113
-
114
- ## 7. Cosa restituirmi
115
-
116
- Per ogni schermata elencata in § 3:
117
-
118
- 1. **PNG export** ad alta risoluzione (≥ 2x) di OGNI stato richiesto (es. `lista-ordini__empty.png`, `lista-ordini__populated.png`).
119
- 2. {{output_format_preference_block}} <!-- "Codice HTML/CSS in un file unico" | "Codice React (.tsx) con import Tailwind" | "Link Figma alla frame" -->
120
- 3. **Mappa di tracciabilità** — per OGNI elemento interattivo o iconografico
121
- della schermata, una riga: `elemento → funzione che lo motiva` (l'azione
122
- primaria / il dato di § 3 che serve), oppure marcalo **decorativo** se non ha
123
- comportamento. Qualunque elemento che non riesci a tracciare a una funzione,
124
- elencalo separatamente come *orphan da decidere* — così posso scegliere se
125
- dargli una funzione, renderlo decorativo o rimuoverlo, invece di
126
- implementarlo a vuoto.
127
- 4. **Note inline** su:
128
- - Componenti nuovi (non presenti nel registry sopra) che hai introdotto e perché.
129
- - Token deviation: qualunque colore/spacing/radius fuori dalla palette sopra, con motivazione.
130
- - Decisioni di layout non ovvie (es. priority sort, hierarchy visiva, density).
131
-
132
- Quando hai pronto, dimmi i **path locali** dei file (PNG/HTML/TSX) — li importerò in questa sessione PRD per analisi e validazione contro il design system.
133
-
134
- ---
135
-
136
- <!-- baldart-handoff: do-not-edit-manually -->
137
- <!-- Generated from claude-design-handoff-prompt.template.md by /prd Step 3.0 -->