@dzhechkov/harness-cli 0.3.224 → 0.3.226

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dzhechkov/harness-cli",
3
- "version": "0.3.224",
3
+ "version": "0.3.226",
4
4
  "description": "The dz CLI — install AI skills for Claude Code, Codex, OpenCode, Hermes, OpenClaude, GitHub Copilot. 35 commands, 13 presets, 6 platform targets.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -55,7 +55,7 @@
55
55
  "@dzhechkov/skills-reverse-engineering": "^0.1.0",
56
56
  "@dzhechkov/skills-presentation-storyteller": "^0.1.0",
57
57
  "@dzhechkov/skills-website-cloner": "^0.1.0",
58
- "@dzhechkov/harness-core": "0.3.118"
58
+ "@dzhechkov/harness-core": "0.3.119"
59
59
  },
60
60
  "devDependencies": {
61
61
  "@types/node": "^25.6.0",
package/src/cli.ts CHANGED
@@ -151,8 +151,15 @@ import {
151
151
  recordProvisional,
152
152
  finalizeOutcome,
153
153
  COST_LADDER,
154
+ splitScenarios,
155
+ budgetPlan,
156
+ selectWinner,
157
+ proseScopeOk,
158
+ renderProseDiff,
159
+ readScenarioIds,
160
+ DEFAULT_MAX_JUDGE_RUNS,
154
161
  } from '@dzhechkov/harness-core';
155
- import type { Family, ModelRung } from '@dzhechkov/harness-core';
162
+ import type { Family, ModelRung, Candidate as BtoCandidate, DimScores } from '@dzhechkov/harness-core';
156
163
  import type { SetupSpec } from '@dzhechkov/harness-core';
157
164
  import type { ProvenanceMode, PackVerdict, ClaudeUsageModel, PatternRecord, TargetName, BookKU, HarmonizeReport, UsageCalibrationPlan, ClaimFinding, RecallUsagePatternRow } from '@dzhechkov/harness-core';
158
165
  import { getPreset, PRESET_NAMES } from '@dzhechkov/harness-presets';
@@ -4623,6 +4630,87 @@ function cmdRouting(options: Map<string, string>, flags: Set<string>, cwd: strin
4623
4630
  return 0;
4624
4631
  }
4625
4632
 
4633
+ /**
4634
+ * `dz bto-optimize` — the deterministic engine BEHIND the `/bto-optimize` skill (feature bto-optimize-holdout).
4635
+ * Adds dspy-MIPROv2 rigor the current evolutionary loop lacks: a hold-out split, a hard budget cap, and a
4636
+ * no-regress-on-holdout winner selector. NOT a rival command — the skill delegates these steps; candidate-prose
4637
+ * generation + judge scoring stay skill-side. `--json` on every subcommand.
4638
+ * --split --scenarios <csv|@file> [--holdout <r>] deterministic tune/holdout split
4639
+ * --plan --candidates K --rounds R --tune N --holdout M [--max C] budget plan (trims to the cap)
4640
+ * --select --baseline <@json> --candidates <@json> [--tolerance t] accept only on holdout no-regress
4641
+ * --scope-check --original <f> --candidate <f> prose-only guard
4642
+ * --diff --original <f> --candidate <f> the prose diff to confirm
4643
+ */
4644
+ function cmdBtoOptimize(options: Map<string, string>, flags: Set<string>, cwd: string, write: Write): number {
4645
+ const json = flags.has('json');
4646
+ const emit = (o: unknown): number => { write(JSON.stringify(o, null, json ? 2 : 0)); return 0; };
4647
+ const readContained = (rel: string): string => {
4648
+ // Containment (QE #traversal): an @file must stay under cwd — `@../../etc/passwd` must not read outside.
4649
+ const rootAbs = resolve(cwd);
4650
+ const abs = resolve(rootAbs, rel);
4651
+ if (abs !== rootAbs && !abs.startsWith(rootAbs + sep)) throw new Error(`path escapes the working directory: ${rel}`);
4652
+ return readFileSync(abs, 'utf-8');
4653
+ };
4654
+ const readJson = (spec: string | undefined): unknown => {
4655
+ if (spec === undefined) return undefined;
4656
+ const raw = spec.startsWith('@') ? readContained(spec.slice(1)) : spec;
4657
+ return JSON.parse(raw);
4658
+ };
4659
+ try {
4660
+ if (flags.has('split')) {
4661
+ const sc = options.get('scenarios');
4662
+ if (sc === undefined) { write('dz bto-optimize --split needs --scenarios <csv|@file>'); return 1; }
4663
+ const ids = sc.startsWith('@') ? readScenarioIds(resolve(cwd, sc.slice(1))) : sc.split(',').map((s) => s.trim()).filter(Boolean);
4664
+ const ratio = Number.parseFloat(options.get('holdout') ?? '');
4665
+ const split = splitScenarios(ids, Number.isFinite(ratio) ? ratio : undefined);
4666
+ if (json) return emit(split);
4667
+ write(`tune (${split.tune.length}): ${split.tune.join(', ')}`);
4668
+ write(`holdout (${split.holdout.length}): ${split.holdout.join(', ')}`);
4669
+ return 0;
4670
+ }
4671
+ if (flags.has('plan')) {
4672
+ const n = (k: string, d: number): number => { const v = Number.parseInt(options.get(k) ?? '', 10); return Number.isFinite(v) ? v : d; };
4673
+ const max = Number.parseInt(options.get('max') ?? '', 10);
4674
+ const plan = budgetPlan({ candidates: n('candidates', 5), rounds: n('rounds', 1), tuneCount: n('tune', 3), holdoutCount: n('holdout', 2) }, Number.isFinite(max) ? max : DEFAULT_MAX_JUDGE_RUNS);
4675
+ if (json) return emit(plan);
4676
+ write(`budget plan: ${plan.candidates} candidates × ${plan.rounds} round(s) → ${plan.tuneRuns} tune + ${plan.holdoutRuns} holdout = ${plan.totalRuns} judge run(s) (cap ${plan.cap}, ${plan.withinCap ? 'within cap' : 'OVER CAP'})`);
4677
+ if (plan.trimmed) write(` trimmed to fit: ${plan.trimmed}`);
4678
+ return 0;
4679
+ }
4680
+ if (flags.has('select')) {
4681
+ const baseline = readJson(options.get('baseline')) as { holdout: DimScores } | undefined;
4682
+ const candidates = readJson(options.get('candidates')) as BtoCandidate[] | undefined;
4683
+ if (!baseline || !Array.isArray(candidates)) { write('dz bto-optimize --select needs --baseline <@json {holdout}> and --candidates <@json [..]>'); return 1; }
4684
+ const tol = Number.parseFloat(options.get('tolerance') ?? '');
4685
+ const result = selectWinner(baseline, candidates, Number.isFinite(tol) ? { tolerance: tol } : {});
4686
+ if (json) return emit(result);
4687
+ write(result.winner ? `✓ winner: ${result.winner} — ${result.reason}` : `✗ no winner — ${result.reason}`);
4688
+ return 0;
4689
+ }
4690
+ if (flags.has('scope-check') || flags.has('diff')) {
4691
+ const o = options.get('original'); const c = options.get('candidate');
4692
+ if (o === undefined || c === undefined) { write('needs --original <file> and --candidate <file>'); return 1; }
4693
+ const origText = readContained(o);
4694
+ const candText = readContained(c);
4695
+ if (flags.has('scope-check')) {
4696
+ const r = proseScopeOk(origText, candText);
4697
+ if (json) return emit(r);
4698
+ write(r.ok ? `✓ prose-only: ${r.reason}` : `✗ out of scope: ${r.reason}`);
4699
+ return r.ok ? 0 : 1;
4700
+ }
4701
+ write(renderProseDiff(origText, candText));
4702
+ return 0;
4703
+ }
4704
+ write('dz bto-optimize: pass --split | --plan | --select | --scope-check | --diff (see docs)');
4705
+ return 1;
4706
+ } catch (e) {
4707
+ const msg = e instanceof Error ? e.message : String(e);
4708
+ if (json) { write(JSON.stringify({ error: msg })); return 1; }
4709
+ write(`dz bto-optimize: ${msg}`);
4710
+ return 1;
4711
+ }
4712
+ }
4713
+
4626
4714
  function cmdStats(cwd: string, write: Write): number {
4627
4715
  const baseDir = join(cwd, 'packages', '@dzhechkov');
4628
4716
  if (!existsSync(baseDir)) {
@@ -4901,6 +4989,8 @@ export async function runCli(argv: string[], io: CliIo = {}): Promise<number> {
4901
4989
  return cmdChallenge(options, flags, cwd, write);
4902
4990
  case 'routing':
4903
4991
  return cmdRouting(options, flags, cwd, write);
4992
+ case 'bto-optimize':
4993
+ return cmdBtoOptimize(options, flags, cwd, write);
4904
4994
  case 'dashboard':
4905
4995
  return cmdDashboard(cwd, write);
4906
4996
  case 'roam':