@dzhechkov/skills-bto 1.3.2 → 1.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/cli.js CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dzhechkov/skills-bto",
3
- "version": "1.3.2",
3
+ "version": "1.3.4",
4
4
  "description": "Build-Benchmark-Test-Optimize skill pack for Claude Code — deterministic benchmarking, quality gates, witness chain, judge attestation, and optimization",
5
5
  "main": "src/cli.js",
6
6
  "bin": {
@@ -50,4 +50,4 @@
50
50
  "publishConfig": {
51
51
  "access": "public"
52
52
  }
53
- }
53
+ }
package/src/cli.js CHANGED
@@ -84,8 +84,14 @@ function parseArgs(argv) {
84
84
  command = 'version';
85
85
  break;
86
86
  default:
87
- if (!arg.startsWith('-') && command === null) {
87
+ if (arg.startsWith('-')) {
88
+ error(`Unknown option: ${arg}`);
89
+ process.exit(1);
90
+ } else if (command === null) {
88
91
  command = arg;
92
+ } else {
93
+ error(`Unexpected argument: ${arg}`);
94
+ process.exit(1);
89
95
  }
90
96
  break;
91
97
  }
@@ -64,7 +64,7 @@ function installComponent(key, comp, templatesDir, targetDir) {
64
64
  if (filterFn) {
65
65
  // Filtered component — only copy matching files from shared directory
66
66
  copyDirFiltered(src, dest, filterFn);
67
- return getRelativePathsFiltered(dest, filterFn).map((rel) => path.join(comp.src, rel));
67
+ return getRelativePathsFiltered(src, filterFn).map((rel) => path.join(comp.src, rel));
68
68
  }
69
69
 
70
70
  // Non-filtered component — copy entire directory or file
@@ -75,7 +75,7 @@ function installComponent(key, comp, templatesDir, targetDir) {
75
75
  }
76
76
 
77
77
  copyDirRecursive(src, dest);
78
- return getRelativePaths(dest).map((rel) => path.join(comp.src, rel));
78
+ return getRelativePaths(src).map((rel) => path.join(comp.src, rel));
79
79
  }
80
80
 
81
81
  // ---------------------------------------------------------------------------
@@ -162,16 +162,19 @@ async function run(options) {
162
162
  if (!comp) continue;
163
163
 
164
164
  const destPath = path.join(targetDir, comp.src);
165
+ const srcPath = path.join(templatesDir, comp.src);
165
166
  const filterFn = getComponentFilter(comp);
167
+ const scanBase = fileExists(srcPath) ? srcPath : destPath;
166
168
 
167
169
  if (comp.isFile) {
168
170
  if (fileExists(destPath)) {
169
171
  allFiles.push(comp.src);
170
172
  }
171
173
  } else if (fileExists(destPath)) {
174
+ // Scan the TEMPLATE source, not the destination, so user files aren't adopted.
172
175
  const paths = filterFn
173
- ? getRelativePathsFiltered(destPath, filterFn)
174
- : getRelativePaths(destPath);
176
+ ? getRelativePathsFiltered(scanBase, filterFn)
177
+ : getRelativePaths(scanBase);
175
178
  allFiles.push(...paths.map((rel) => path.join(comp.src, rel)));
176
179
  }
177
180
  }
package/src/utils.js CHANGED
@@ -304,15 +304,17 @@ const COMPONENTS = {
304
304
  },
305
305
  commands: {
306
306
  src: '.claude/commands',
307
- label: 'BTO Commands (5 commands)',
307
+ label: 'BTO Commands (verify-chain + bto*)',
308
308
  group: 'core',
309
309
  filter: 'bto',
310
+ extra: ['verify-chain.md'],
310
311
  },
311
312
  rules: {
312
313
  src: '.claude/rules',
313
- label: 'BTO Quality Gate Rules',
314
+ label: 'BTO Quality Gate Rules (+ witness-chain)',
314
315
  group: 'core',
315
316
  filter: 'bto',
317
+ extra: ['witness-chain.md'],
316
318
  },
317
319
  agents: {
318
320
  src: '.claude/agents',
@@ -326,6 +328,13 @@ const COMPONENTS = {
326
328
  group: 'core',
327
329
  filter: 'bto',
328
330
  },
331
+ lib: {
332
+ // verify-chain.md reads lib/witness-chain.md + lib/judge-attestation.md at runtime;
333
+ // without this component those reads fail (finding #26).
334
+ src: 'lib',
335
+ label: 'BTO verification protocols (witness-chain, judge-attestation)',
336
+ group: 'core',
337
+ },
329
338
  };
330
339
 
331
340
  // ===========================================================================
@@ -348,9 +357,10 @@ function getComponentFilter(comp) {
348
357
  // rules: bto-*.md (e.g., bto-quality-gate.md)
349
358
  // agents: bto-*.md (e.g., bto-builder.md)
350
359
  const prefix = comp.filter; // 'bto'
360
+ const extra = comp.extra || []; // explicit allowlist of non-prefixed files to include
351
361
 
352
362
  return (filename) => {
353
- return filename.startsWith(prefix);
363
+ return filename.startsWith(prefix) || extra.includes(filename);
354
364
  };
355
365
  }
356
366
 
@@ -179,3 +179,10 @@ Each worker writes a log regardless of success:
179
179
  ## Reusability Note
180
180
  This template is artifact-type agnostic. Replace BASE_ARTIFACT_PATH and RUBRIC_PATH
181
181
  to optimize any text artifact: prompts, skills, presentations, research sections, code docstrings.
182
+
183
+ ## Hold-out validation (delegate to `dz bto-optimize`)
184
+ When `dz` is available, do NOT accept a variant on its tuning-set score alone. Split scenarios
185
+ (`dz bto-optimize --split`), score candidates on the tune set, validate the top candidate on the held-out set,
186
+ and call `dz bto-optimize --select` — it accepts a winner only if the weakest dimension improves on the HOLDOUT
187
+ with no regression elsewhere. This is the anti-Goodhart guard the heuristic loop lacks. See
188
+ `skills/bto/modules/optimize.md` → "Rigorous validation".
@@ -60,13 +60,13 @@ For each required section in the golden sample, check if the artifact contains a
60
60
 
61
61
  **Axis 2 — Ordering Score:**
62
62
  Compare the ordering of matched sections against the golden sample ordering.
63
- - Use normalized Kendall tau distance: `ordering_score = 1 - (inversions / max_inversions)`
64
- - If fewer than 3 matched sections → ordering_score = 1.0 (too few to meaningfully compare)
63
+ - Use normalized inversion distance: `ordering_score = 1 - (inversion_count / max_inversions)` where `max_inversions = n * (n - 1) / 2` (n = number of matched sections)
64
+ - If only 0 or 1 sections are matched → ordering_score defaults to 0.0 (too few to compare)
65
65
 
66
66
  **Axis 3 — Proportion Score:**
67
- For each matched section, compute the ratio of artifact section length to golden sample section length.
68
- - `proportion_score = 1 - mean(|log(artifact_len / golden_len)|)` clamped to [0, 1]
69
- - Measures whether sections are proportionally sized (not too bloated, not too thin)
67
+ For each matched section, compute its proportion of total content (by character count) and compare against the golden sample's expected proportion.
68
+ - `proportion_score = 1 - mean(|actual_proportion[i] - golden_proportion[i]|)` clamped to [0, 1]
69
+ - Measures whether sections are proportionally sized (not too bloated, not too thin). Unmapped sections are excluded.
70
70
 
71
71
  **Display per-section MATCH/MISS table:**
72
72
 
@@ -82,36 +82,36 @@ Golden Section Status Artifact Section Size Ratio
82
82
 
83
83
  **Golden Similarity Score:**
84
84
  ```
85
- B0 = section_coverage * 0.50 + ordering_score * 0.25 + proportion_score * 0.25
85
+ B0 = mean(section_coverage, ordering_score, proportion_score)
86
86
  ```
87
87
 
88
- **Gate:** If B0 < 0.30 BLOCK immediately. The artifact's structure is too far from the expected form to benchmark meaningfully.
88
+ B0 is not gated on its own — it feeds the aggregate BENCHMARK_SCORE (Step 8), where a low B0 (< 0.40) is surfaced in the escalation fix list. Do not BLOCK at B0 in isolation.
89
89
 
90
90
  ### Step 5: Layer B1 — Deterministic Test Suite
91
91
 
92
92
  **Purpose:** Run all applicable deterministic tests for the detected artifact type. Zero LLM cost.
93
93
 
94
- Execute all applicable checks from `benchmark.md` for the detected artifact type. These overlap with but extend the Layer 0 checks from `/bto-test`:
94
+ Execute all applicable checks from `references/quality-checklist.md` for the detected artifact type. (The `benchmark.md` module documents these same checks as its TEST-* suite; `references/quality-checklist.md` is the authoritative ID index.) These overlap with but extend the Layer 0 checks from `/bto-test`:
95
95
 
96
- **Universal Checks (U1-U12):**
96
+ **Universal Checks (U-01 through U-12):**
97
97
  - U-01: File exists and is non-empty
98
98
  - U-02: UTF-8 encoding valid
99
99
  - U-03: Starts with level-1 heading
100
- - U-04: No placeholder text (`[TODO]`, `[TBD]`, `<INSERT>`, `[PLACEHOLDER]`)
100
+ - U-04: No placeholder text (`TODO`, `FIXME`, `[INSERT`, `<YOUR_`, `...`)
101
101
  - U-05: No empty sections (heading with no content before next heading)
102
- - U-06: Consistent heading hierarchy (no h1→h3 jumps)
102
+ - U-06: Consistent heading hierarchy (no `##`→`####` jumps)
103
103
  - U-07: No broken internal cross-references
104
104
  - U-08: File size within bounds (200B – 100KB per file)
105
105
  - U-09: No trailing whitespace on lines
106
- - U-10: Standard Markdown only (no HTML unless in code blocks)
106
+ - U-10: Standard Markdown only (no raw HTML)
107
107
  - U-11: All code blocks properly closed
108
108
  - U-12: No duplicate top-level sections
109
109
 
110
- **Type-Specific Checks:**
110
+ **Type-Specific Checks (see `references/quality-checklist.md` for full definitions):**
111
111
  - Skill: SK-01 through SK-16 (SKILL.md exists, required sections, modules/, references/, examples/, etc.)
112
- - Command: CM-01 through CM-08 (Usage section, Parameters, Protocol steps, Checkpoint, Critical Rules, etc.)
113
- - Rule: RL-01 through RL-06 (Table format, detection signals, fix actions, no contradictions, etc.)
114
- - Agent: AG-01 through AG-04 (Model specified, isolation rules, naming convention, output format)
112
+ - Command: CM-01 through CM-11 (location, `$ARGUMENTS`, checkpoint, skill loading, usage line, examples, empty-argument handling, etc.)
113
+ - Rule: RL-01 through RL-09 (table format, detection signals, fix actions, severity designation, no vague patterns, etc.)
114
+ - Agent: AT-01 through AT-10 (purpose, model specified + justified, isolation scope, output format, failure protocol, naming convention, etc.)
115
115
 
116
116
  **Display per-test PASS/FAIL table:**
117
117
 
@@ -132,7 +132,7 @@ SK-03 Has ## Anti-Patterns section FAIL Section missing
132
132
  B1 = passed_tests / total_applicable_tests
133
133
  ```
134
134
 
135
- **Gate:** If B1 < 0.60 BLOCK. Too many structural failures to proceed to consistency probing.
135
+ B1 is not gated on its own — it feeds the aggregate BENCHMARK_SCORE (Step 8), where a low B1 (< 0.40) is surfaced in the escalation fix list. Do not BLOCK at B1 in isolation.
136
136
 
137
137
  ### Step 6: Layer B2 — Consistency Probe
138
138
 
@@ -142,29 +142,44 @@ B1 = passed_tests / total_applicable_tests
142
142
 
143
143
  **Spawn 3 parallel haiku agents with the identical evaluation prompt:**
144
144
 
145
- Each agent receives the exact same prompt:
145
+ Each agent independently answers the same 4 probe questions about the artifact:
146
146
  ```
147
- You are evaluating a Claude Code artifact for structural quality.
148
- Rate these 4 dimensions (0.0 to 1.0 each, two decimal places):
149
- 1. STRUCTURE — Does the artifact follow a clear, logical organization?
150
- 2. CLARITY Are instructions unambiguous and actionable?
151
- 3. COVERAGE Does it address all aspects implied by its title/scope?
152
- 4. CONSISTENCY Is the internal terminology and style uniform?
153
- Output as JSON: {"structure": X.XX, "clarity": X.XX, "coverage": X.XX, "consistency": X.XX}
147
+ You are analyzing a Claude Code {artifact_type} for consistency.
148
+ Read the artifact carefully, then answer these 4 questions. Be specific and concise.
149
+
150
+ Q1: What is the PRIMARY purpose of this artifact? (one sentence)
151
+ Q2: List the TOP 3 most important sections/components. (ordered list)
152
+ Q3: What is the MAIN anti-pattern or risk this artifact addresses? (one sentence)
153
+ Q4: If an agent followed this artifact, what would the OUTPUT look like? (2-3 sentences)
154
+
155
+ ## Required Output Format
156
+ PURPOSE: [answer to Q1]
157
+ TOP_SECTIONS:
158
+ 1. [section]
159
+ 2. [section]
160
+ 3. [section]
161
+ MAIN_RISK: [answer to Q3]
162
+ EXPECTED_OUTPUT: [answer to Q4]
154
163
  ```
155
164
 
156
165
  **Isolation:** Each agent evaluates independently. No cross-communication.
157
166
 
158
- **After all 3 return, compute structural agreement:**
167
+ **After all 3 return, the orchestrator (not haiku) measures agreement per question:**
159
168
 
160
- For each dimension:
161
- ```
162
- agreement[dim] = 1 - (max(scores[dim]) - min(scores[dim]))
163
- ```
169
+ | Agreement Level | Score | Criteria |
170
+ |----------------|-------|----------|
171
+ | Full agreement (3/3 match) | 1.0 | All 3 responses convey the same meaning (semantic match) |
172
+ | Majority agreement (2/3 match) | 0.67 | 2 of 3 agree, 1 diverges |
173
+ | No agreement (all different) | 0.0 | All 3 responses are substantively different |
174
+
175
+ - Q1 (PURPOSE): all 3 identify the same core purpose (semantic, not lexical)
176
+ - Q2 (TOP_SECTIONS): overlap score = |intersection of all 3| / 3
177
+ - Q3 (MAIN_RISK): all 3 identify the same risk category
178
+ - Q4 (EXPECTED_OUTPUT): all 3 describe structurally similar output
164
179
 
165
180
  **Consistency Score:**
166
181
  ```
167
- B2 = mean(agreement across all 4 dimensions)
182
+ B2 = mean(q1_agreement, q2_agreement, q3_agreement, q4_agreement)
168
183
  ```
169
184
 
170
185
  **Interpretation:**
@@ -172,16 +187,16 @@ B2 = mean(agreement across all 4 dimensions)
172
187
  - B2 0.60-0.85 → MODERATE consistency — some sections are open to interpretation
173
188
  - B2 < 0.60 → LOW consistency — the artifact is structurally ambiguous
174
189
 
175
- **Display per-probe comparison:**
190
+ **Display per-question comparison:**
176
191
 
177
192
  ```
178
- Dimension Probe 1 Probe 2 Probe 3 Range Agreement
179
- ──────────── ────────── ────────── ────────── ──────── ──────────
180
- STRUCTURE 0.80 0.85 0.82 0.05 0.95
181
- CLARITY 0.70 0.75 0.65 0.10 0.90
182
- COVERAGE 0.60 0.70 0.55 0.15 0.85
183
- CONSISTENCY 0.75 0.78 0.73 0.05 0.95
184
- Mean: 0.91
193
+ Question Agreement Detail
194
+ ──────────────────────────── ─────────── ────────────────────────────────
195
+ Q1 PURPOSE 1.00 3/3 agree
196
+ Q2 TOP_SECTIONS 0.67 2/3 sections overlap
197
+ Q3 MAIN_RISK 1.00 3/3 agree
198
+ Q4 EXPECTED_OUTPUT 0.67 2/3 agree
199
+ Mean: 0.84
185
200
  ```
186
201
 
187
202
  No gate on B2 — consistency is informational and feeds into the aggregate score.
@@ -192,43 +207,46 @@ No gate on B2 — consistency is informational and feeds into the aggregate scor
192
207
 
193
208
  **Metric 1 — Token Efficiency:**
194
209
  ```
195
- token_efficiency = meaningful_content_tokens / total_tokens
210
+ token_efficiency = content_chars / (content_chars + formatting_chars)
196
211
  ```
197
- Where `meaningful_content_tokens` excludes: blank lines, decorative separators, repeated boilerplate headers, and excessive whitespace. Target: > 0.70.
212
+ Where `content_chars` = characters in prose and code blocks, and `formatting_chars` = characters in markdown formatting (headings, dividers, table pipes, bullets). Healthy range: 0.60–0.85.
198
213
 
199
214
  **Metric 2 — Information Density:**
200
215
  ```
201
- information_density = unique_concepts / total_sections
216
+ density_score = min(unique_concepts / (total_sections * 3), 1.0)
202
217
  ```
203
- Where `unique_concepts` = count of distinct topics, terms, or instructions introduced. Measures whether each section contributes new information vs. repeating prior content. Target: > 2.0 concepts per section.
218
+ Where `unique_concepts` = count of distinct key terms/concepts (unique multi-word phrases in headings + bold text + table headers) and `total_sections` = count of `##` headings. Each section should introduce ~3 unique concepts.
204
219
 
205
220
  **Metric 3 — Bloat Detection:**
206
- Identify sections where:
207
- - Section length > 3x the golden sample equivalent → BLOATED
208
- - Section length < 0.2x the golden sample equivalent → THIN
209
- - Section repeats > 30% of content from another section → REDUNDANT
221
+ ```
222
+ avg_section_size = mean(section_sizes)
223
+ bloated_sections = sections where size > 3 * avg_section_size
224
+ bloat_ratio = count(bloated_sections) / total_sections
225
+ ```
226
+ `bloat_ratio` of 0 is ideal; above 0.3 is problematic.
210
227
 
228
+ **Metric 4 — Redundancy Detection:**
211
229
  ```
212
- bloat_score = 1 - (bloated_sections + thin_sections + redundant_sections) / total_sections
230
+ repeated_phrases = phrases of 5+ words appearing 3+ times (excluding code blocks and table formatting)
231
+ redundancy_score = min(count(repeated_phrases) / total_sections, 1.0)
213
232
  ```
214
- Clamped to [0, 1].
233
+ A score of 0 means no redundancy detected.
215
234
 
216
235
  **Performance Score:**
217
236
  ```
218
- B3 = token_efficiency * 0.35 + normalize(information_density, 0, 4) * 0.35 + bloat_score * 0.30
237
+ B3 = mean(token_efficiency, density_score, 1 - bloat_ratio, 1 - redundancy_score)
219
238
  ```
220
- Where `normalize(x, min, max)` clamps and scales x to [0, 1].
221
239
 
222
240
  **Display per-section metrics:**
223
241
 
224
242
  ```
225
- Section Tokens Concepts Golden Ratio Flag
226
- ───────────────────────── ──────── ────────── ────────────── ──────────
227
- ## Overview 320 4 1.2x OK
228
- ## Protocol 1840 12 0.8x OK
229
- ## Anti-Patterns 580 6 2.8x BLOATED
230
- ## Quick Start 95 2 0.3x THIN
231
- ## Dependencies 210 3 1.0x OK
243
+ Section Tokens Concepts vs Avg Size Flag
244
+ ───────────────────────── ──────── ────────── ───────────── ──────────
245
+ ## Overview 320 4 0.4x OK
246
+ ## Protocol 1840 12 2.3x OK
247
+ ## Anti-Patterns 580 6 0.7x OK
248
+ ## Examples 2900 4 3.6x BLOATED
249
+ ## Dependencies 210 3 0.3x OK
232
250
  ```
233
251
 
234
252
  ### Step 8: Aggregate & Gate
@@ -245,8 +263,8 @@ BENCHMARK_SCORE = B0 * 0.30 + B1 * 0.35 + B2 * 0.15 + B3 * 0.20
245
263
  - B3 (Performance) at 0.20 — efficiency matters for production artifacts
246
264
 
247
265
  **Gate:**
248
- - BENCHMARK_SCORE < 0.50 → **BLOCK** — artifact requires significant rework before evaluation
249
- - BENCHMARK_SCORE 0.50-0.70 → **WARN** — artifact has structural issues; proceed to TEST with caution
266
+ - BENCHMARK_SCORE < 0.50 → **BLOCK** — do NOT proceed to TEST; return to BUILD with a prioritized fix list (maximum 2 automatic retries, then escalate to human). On retry, re-run only the deterministic layers (B0 + B1).
267
+ - BENCHMARK_SCORE 0.50-0.70 → **WARN** — proceed to TEST with an advisory flag (per-layer scores + top failures passed to the judges)
250
268
  - BENCHMARK_SCORE > 0.70 → **PASS** — artifact meets benchmark standards; ready for TEST
251
269
 
252
270
  Record `BENCHMARK_SCORE` for downstream consumption by `/bto-test` and `/bto`.
@@ -295,8 +313,8 @@ The `BENCHMARK_SCORE` is available to downstream commands:
295
313
  - Layer B2 uses haiku only — NEVER use sonnet or opus for consistency probes
296
314
  - Agent tool is REQUIRED for Layer B2 — do not run probes sequentially
297
315
  - Golden samples from `references/golden-samples.md` are the authoritative structural reference
298
- - If golden sample does not exist for the detected type → skip B0, set B0 = 0.50 (neutral), and log a warning
316
+ - If golden sample does not exist for the detected type → skip B0 entirely and renormalize the remaining weights (B1/B2/B3) per the module's Partial Evaluation rule; log a warning
299
317
  - Report `BENCHMARK_SCORE` explicitly so downstream commands can consume it
300
- - BLOCK verdict at B0 or B1 halts the benchmarkdo NOT proceed to subsequent layers
301
- - Layer B3 bloat detection uses golden sample proportions without a golden sample, use absolute thresholds only
318
+ - The gate is on the aggregate `BENCHMARK_SCORE` only (Step 8) there are no per-layer BLOCK gates at B0 or B1
319
+ - Layer B3 bloat detection is based on each section's size relative to the artifact's own average section size (no golden sample required)
302
320
  - If "verbose" is in $ARGUMENTS, show expanded per-section diagnostics for all layers without prompting
@@ -206,3 +206,11 @@ This command is also invoked internally by `/bto` as the final step (OPTIMIZE ph
206
206
  - Always save a `.pre-optimize.bak` backup before overwriting original
207
207
  - Round 3 evaluation must use Layer 2 (sonnet judges), not Layer 1 (haiku)
208
208
  - Preserve original artifact intent — optimization changes HOW, not WHAT
209
+
210
+ ## Rigorous validation (recommended when `dz` ≥ 0.3.119)
211
+
212
+ For a reproducible, budget-capped run that resists judge-gaming, follow the **"Rigorous validation (hold-out +
213
+ no-regress)"** protocol in `skills/bto/modules/optimize.md`: it splits scenarios into a tuning set and a held-out
214
+ set, tunes candidates on the tune set, and accepts a winner ONLY if it lifts the weakest dimension on the UNSEEN
215
+ holdout without regressing the others (`dz bto-optimize --split/--plan/--select`). Prose-only, diff-confirmed,
216
+ never auto-written. Absent `dz`, the heuristic evolutionary loop runs unchanged.
@@ -199,3 +199,46 @@ Stop optimization immediately if:
199
199
  2. Critical structural checks (Layer 0) fail on any variant
200
200
  3. Artifact semantics change fundamentally
201
201
  4. User requests stop
202
+
203
+ ---
204
+
205
+ ## Rigorous validation (hold-out + no-regress) — `dz bto-optimize` (when `dz` ≥ 0.3.119)
206
+
207
+ The evolutionary loop above SELECTS the highest score on the **same** eval it tuned on — which lets a variant
208
+ that flatters the judge panel (verbosity, buzzwords) win even if it doesn't help real users (**Goodhart**). When
209
+ the `dz` CLI is available, delegate the deterministic validation steps to it so acceptance is gated on **unseen**
210
+ scenarios with a **no-regress** guard and a **hard budget cap**. This strengthens the loop; it does not replace it.
211
+
212
+ **Scope (Phase-1):** only the **directive prose** of `SKILL.md` is mutated — the "when to activate" block + the
213
+ core instruction. Frontmatter, section headings, and examples are OFF-LIMITS (`--scope-check` rejects a candidate
214
+ that touches them). Never auto-write; the human confirms the diff.
215
+
216
+ Protocol (folds into the steps above):
217
+
218
+ 1. **Split** the BTO scenario ids into a tuning set and a held-out set (deterministic):
219
+ ```bash
220
+ dz bto-optimize --split --scenarios @scenarios.json --holdout 0.34 # → { tune:[...], holdout:[...] }
221
+ ```
222
+ 2. **Plan the budget** and respect the hard cap (the engine trims candidates/rounds to fit and reports it):
223
+ ```bash
224
+ dz bto-optimize --plan --candidates 5 --rounds 1 --tune <#tune> --holdout <#holdout> --max 24
225
+ ```
226
+ Never run more judge passes than the printed plan.
227
+ 3. **Tune** — generate the K prose candidates (existing strategies) and score each via the judge panel on the
228
+ **tune** scenarios ONLY. Record per-candidate per-dimension scores.
229
+ 4. **Validate** — score the top tune candidate(s) on the **holdout** scenarios (unseen).
230
+ 5. **Select** — the engine accepts a candidate ONLY if, on the holdout, the weakest dimension improves AND no
231
+ other dimension / the aggregate regresses (beyond `--tolerance`, default 0):
232
+ ```bash
233
+ dz bto-optimize --select --baseline @baseline.json --candidates @candidates.json # → { winner|null, reason }
234
+ ```
235
+ `baseline.json` = `{ "holdout": {<DimScores>} }`; each candidate = `{ id, prose, tune:{DimScores}, holdout:{DimScores} }`.
236
+ A tune-winner that regresses on the holdout is **rejected** — this is the anti-gaming guarantee.
237
+ 6. **Confirm gate** — show the prose diff + the tune/holdout deltas and let the human accept before any write:
238
+ ```bash
239
+ dz bto-optimize --scope-check --original SKILL.md --candidate candidate.md # prose-only guard
240
+ dz bto-optimize --diff --original SKILL.md --candidate candidate.md # the diff to confirm
241
+ ```
242
+
243
+ Grounded in dspy.ts MIPROv2 (propose → minibatch-tune → **validate on held-out** → best). Absent `dz` ⇒ fall
244
+ back to the heuristic loop above (unchanged).