@dzhechkov/harness-cli 0.3.253 → 0.3.254

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/.dz-manifest.json CHANGED
@@ -9,7 +9,7 @@
9
9
  },
10
10
  {
11
11
  "path": "README.md",
12
- "sha256": "d4275f137eadedaba625b8e05918b343db87288811f37d7b31ef6d5688935906"
12
+ "sha256": "fc57c105e5031d2d6751ef08b64769dc463cd79c5cf7637b48bc8cb7190e5b3a"
13
13
  },
14
14
  {
15
15
  "path": "coverage/coverage-final.json",
@@ -41,15 +41,15 @@
41
41
  },
42
42
  {
43
43
  "path": "dist/cli.d.ts.map",
44
- "sha256": "c9b415287eda0a7c2ad32dbb7cfc3ecce19aa3b90c96d3fbeedd62d20fe34bc7"
44
+ "sha256": "4268f56e38f72625c2c490add4b17ee12d7ff7db991a2090f426afbb026b98a9"
45
45
  },
46
46
  {
47
47
  "path": "dist/cli.js",
48
- "sha256": "56b4da2639c1721180387f28166d84d065718a4002f556307a6598570d335809"
48
+ "sha256": "fe24568930cc226e0bad8b4ccba559e2f837bb4cb068c2cc37750b5a10c27a70"
49
49
  },
50
50
  {
51
51
  "path": "dist/cli.js.map",
52
- "sha256": "4a172ba5384c9716fa48570979b7ffba969599ed5c639d7e7268a829d2b9b4e1"
52
+ "sha256": "4a0b554fab78cd50fc46cff653411b2003b7dd9a4df1b16af6e9f5a0e24379ff"
53
53
  },
54
54
  {
55
55
  "path": "dist/index.d.ts",
@@ -73,7 +73,7 @@
73
73
  },
74
74
  {
75
75
  "path": "package.json",
76
- "sha256": "6c0a974541d458977c4606f21fa230f12c300a999bf7ef2cd5b5dae3de83b617"
76
+ "sha256": "c6469d0f7539ce6c0a8176d0b795952a667df2b975c9f93c66e7cd6eba843383"
77
77
  },
78
78
  {
79
79
  "path": "src/bin.ts",
@@ -81,7 +81,7 @@
81
81
  },
82
82
  {
83
83
  "path": "src/cli.ts",
84
- "sha256": "1aceceef391cac55d00f7872632d28c4116a82025b9235343b81398fc515f944"
84
+ "sha256": "13d3946992186e019310c1c8666fd724ce74880b8fda19850eeb8b6e0399e3d7"
85
85
  },
86
86
  {
87
87
  "path": "src/index.ts",
@@ -91,6 +91,10 @@
91
91
  "path": "test/cli.test.ts",
92
92
  "sha256": "7d6ba26ec1188db3b3ac89f50b015ba1f0c6117b5c30d99b22da41da10c96c12"
93
93
  },
94
+ {
95
+ "path": "test/epoch-replay-cli.test.ts",
96
+ "sha256": "a5a48585b565288ca1ea96030b02b2ccb37a621ef7b3cfe54cd7aaa7164d6904"
97
+ },
94
98
  {
95
99
  "path": "tsconfig.json",
96
100
  "sha256": "e80ef2911c3347a713dc3dca6a5f6034d91e938e58e71345ea920dd9833bcdbb"
@@ -101,5 +105,5 @@
101
105
  }
102
106
  ]
103
107
  },
104
- "signature": "Z1iFk6oNIUD/wUDtCYcGGumn9HbL/wmzeljhrrYhWV7p5sXFpMXImxLq2p1s8F04rbV1nCNeIKq7sZ68YARTAg=="
108
+ "signature": "lRzsYJCin4HX25rutyyh0Veo7d29P+AOTtJyKdgPcBrzIY44kdm7AHYRYCKcTrH5esXg8nVfhxd1Uw74PjtZAg=="
105
109
  }
package/README.md CHANGED
@@ -132,7 +132,7 @@ point any tool at (a skill is just Markdown). Dedicated targets are on the [road
132
132
 
133
133
  ## User Journey — from install to mastery
134
134
 
135
- All 61 commands mapped to a real workflow:
135
+ All 62 commands mapped to a real workflow:
136
136
 
137
137
  ```
138
138
  DISCOVER → INSTALL → USE → CREATE → MAINTAIN → SHARE
@@ -236,6 +236,11 @@ dz workflow --task security-audit # adversarial security scan
236
236
 
237
237
  # Cross-host state sync:
238
238
  dz roam --apply # sync agent state across machines
239
+
240
+ # Is the self-learning loop actually paying off?
241
+ dz compounding # readiness + payoff report (INSUFFICIENT_DATA is a real answer)
242
+ dz epoch-replay --mock --n 24 --effect 0.9 # $0 dry run of the cold-vs-warm verdict math
243
+ dz epoch-replay --emit # ready? emit the real cold-vs-warm work order
239
244
  ```
240
245
 
241
246
  ### Phase 6: Share (publish to the world)
@@ -464,6 +469,98 @@ The prompt queries that make replay possible stay on your machine: `.dz/recall-u
464
469
  git-ignored, entries are truncated at 200 chars and flagged when truncated (a prefix is not a prompt,
465
470
  so flagged rows never count as replayable).
466
471
 
472
+ ### Did the lessons actually change outcomes? — `dz epoch-replay`
473
+
474
+ `dz compounding` says a cold-vs-warm replay **can** be run. `dz epoch-replay` **runs** it and reports
475
+ what it found: Epoch 0 (cold — the prompt alone) vs Epoch 1 (warm — the same prompt plus exactly the
476
+ lessons the apply leg injected), on the same instances, scored into a three-valued verdict.
477
+
478
+ **The test is PAIRED.** Each instance is one judgment on one prompt, so the statistic is a single
479
+ binomial over *decisive* pairs: `p̂ = warm wins / (warm wins + cold wins)`, with a `95%` Wilson
480
+ interval reported on the **lift** scale `2p̂ − 1`. Ties carry no direction — they are excluded from
481
+ the denominator and reported separately.
482
+
483
+ **`SUPPORTED` only when the lift interval lies entirely above zero.** `FALSIFIED` only on **harm**
484
+ (entirely below zero) or on a passed **non-superiority** test — the lift upper bound below a
485
+ pre-registered margin, default `0.05`, at 10+ decisive pairs. Everything else is `INCONCLUSIVE`, a
486
+ first-class honest outcome; below 5 decisive pairs there is no verdict at all.
487
+
488
+ **A tie is under-powered, not refuted.** `6/12` gives a lift interval of `[-0.492, 0.492]` — that
489
+ excludes nothing, so it reads `INCONCLUSIVE`. So does a *perfectly* even 500/500 over 1000 pairs
490
+ (lift upper `0.0619`): the most uninformative result the protocol can produce must not be reported
491
+ as a refutation. A refutation has to clear a bar too.
492
+
493
+ **Try it at $0 first — `--mock` generates seeded synthetic outcomes at a TRUE effect you choose, and
494
+ runs them through the real verdict math:**
495
+
496
+ ```
497
+ $ dz epoch-replay --mock --n 24 --effect 0.9 --seed 20260729
498
+ COLD (epoch 0, no injected lessons): 1/24 CI95 [0.007, 0.202]
499
+ WARM (epoch 1, apply-leg lessons): 23/24 CI95 [0.798, 0.993]
500
+ VERDICT: SUPPORTED
501
+ warm 23/24 CI [0.798, 0.993] is DISJOINT above cold 1/24 CI [0.007, 0.202]
502
+
503
+ $ dz epoch-replay --mock --n 24 --effect -0.9 --seed 20260729 # → FALSIFIED (harm)
504
+ $ dz epoch-replay --mock --n 24 --effect 0.4 --seed 20260729 # → INCONCLUSIVE (neither separates nor excludes)
505
+ $ dz epoch-replay --mock --n 12 --effect 0 --seed 7 # → INCONCLUSIVE (6/6 tie: under-powered, NOT refuted)
506
+ ```
507
+
508
+ ```
509
+ $ dz epoch-replay --mock --n 24 --effect 0.9 --seed 20260729
510
+ SLICE: all · 24 scored instance(s) · 24 DECISIVE pair(s)
511
+ COLD (epoch 0, no injected lessons): 1/24 decisive CI95 [0.007, 0.202]
512
+ WARM (epoch 1, apply-leg lessons): 23/24 decisive CI95 [0.798, 0.993]
513
+ LIFT (paired, 2p−1 over decisive pairs): +0.917 [0.595, 0.985]
514
+ VERDICT: SUPPORTED — the lift interval lies ENTIRELY above zero
515
+ ```
516
+
517
+ Every mock run prints `SYNTHETIC … it is NOT evidence about the learning loop`, and the same seed
518
+ gives byte-identical output — a demo is a reproducer.
519
+
520
+ **Real mode: the runner ORCHESTRATES and SCORES — it never calls a model.** Generation and judging
521
+ happen out of band, so the core stays pure, offline and reproducible:
522
+
523
+ ```
524
+ $ dz epoch-replay --emit # → .dz/epoch-replay/work-order.json (git-ignored: raw prompts)
525
+ 25 instance(s) · seed 20260729 · blind A/B assignment PRE-REGISTERED
526
+ # 1. have an agent fill coldPlan / warmPlan for each item (symmetric length, same task)
527
+ $ dz epoch-replay --judge .dz/epoch-replay/work-order.json # → blind judge prompts
528
+ # 2. have an EXTERNAL, cross-model judge answer them → [{ "id": …, "winner": "A|B|TIE" }]
529
+ $ dz epoch-replay --score judgments.json --work-order .dz/epoch-replay/work-order.json --slice task
530
+ ```
531
+
532
+ Three things make that blind real rather than decorative:
533
+
534
+ - **The judge sees `{id, prompt}` and nothing else.** No assignment, no arm names, no slice label,
535
+ not even a path back to the work order. The judge file is byte-identical whichever way the
536
+ assignment fell, so it cannot be decoded. (Skipped items are reported on stdout, not in the file —
537
+ their reasons name arms.)
538
+ - **The work order is integrity-checked.** It carries a sha256 digest over its pre-registered core
539
+ (version, seed, margin, corpus fingerprint, every `[id, warmIsA]`); `--judge` and `--score`
540
+ recompute it *and* re-derive every assignment from the stated seed, refusing on any mismatch. A
541
+ hand-written order does not buy a verdict.
542
+ > **Honest scope:** this is an integrity check against accidental corruption and mismatch — **not
543
+ > a cryptographic commitment.** The hash is self-contained, so a determined operator can re-forge
544
+ > it (seed-searching a matching assignment at n=12 takes a few thousand tries). The threat model
545
+ > is *you making a mistake*: a hand-edited file, a stale order paired with fresh judgments. The
546
+ > honest-use contract is procedural — emit once, then judge, and keep the emitted file. `--emit`
547
+ > records `emittedAt` + a corpus fingerprint and `--score` prints the digest, seed and margin, so
548
+ > a reviewer can ask for the original and compare three numbers.
549
+ - **The non-superiority margin is pre-registered.** `--margin` is accepted only at `--emit`, is
550
+ stored in the work order and covered by the digest, and `--score` **refuses** a `--margin` flag —
551
+ a margin chosen once you can see the counts is not a pre-registration, and `--margin 99` at
552
+ scoring time would simply buy `FALSIFIED`. Out of range (outside `(0, 0.5]`) is refused, not
553
+ clamped.
554
+ - **Corrupt input is refused, not measured.** Duplicate judgement ids exit 1 rather than counting
555
+ one opinion N times. Unknown ids and unparseable winners are skipped **with a reason**, never
556
+ guessed.
557
+
558
+ The warm arm's only delta is the injected lessons: no gold answers, no verdicts, no outcome labels
559
+ ever enter a work order.
560
+
561
+ **When to use:** after `dz compounding` reports the replay as READY; before claiming that recall
562
+ "works"; and any time you want the claim re-checked as the corpus grows.
563
+
467
564
  ### Usage estimates you can act on — `dz usage`
468
565
 
469
566
  `dz usage` estimates how much of your Claude session (5h block) and week you have spent, from local
@@ -726,7 +823,7 @@ Get the whole set with `dz init --target claude-code --preset meta`, or pick one
726
823
 
727
824
  > **A skill and its npx toolkit are not duplicates — they're a graduation.** Several skills (e.g. `feature-adr`, `design-thinking`) exist BOTH as a skill inside a `dz` preset AND as a standalone `npx` package. The preset's SKILL.md is **fully functional on its own** (the whole methodology — modules + references — travels with it, and it auto-activates by description), and it's the only way to compile that capability to the **non-Claude platforms** (Codex/OpenCode/Hermes/OpenClaude) via `dz`. The npx package adds **project-level runtime governance** around the same skill: a slash command, governance rules, a context shard, and (for feature-adr) reward-learning + `/harvest`. So: pick the **skill/preset** for a working capability across platforms; pick the **npx toolkit** when you want it as a governed, command-driven fixture of one project.
728
825
 
729
- ## All Commands (61)
826
+ ## All Commands (62)
730
827
 
731
828
  ```
732
829
  dz setup --target <name> [--preset <name>] [--select id,id,...] [--skills-dir <dir>] [--memory agentdb] [--no-memory] [--no-hooks] [--install-driver] [--force]
@@ -784,6 +881,10 @@ dz discrimination-check --test <f[,f]> [--base <ref>] [--name <filter>] [--runne
784
881
  dz delivery-check --slug <slug> [--context-only] [--findings <f.json>] [--strict] [--author <model>] [--json] # portable Step-10 Delivery Gate: the `manual` form that travels to every shell target — prints the 4-plane review brief (regressions ‖ security ‖ code-quality ‖ product-honesty) + artifact probes; --findings classifies a fed-back review into a fail-closed ready|blocked hand-off (only cross-validated BLOCKER/HIGH count) and writes features/<slug>/10_delivery_review.md; --strict exits 1 on blocked
785
882
  dz skills-verify [--dir <project>] [--expect a,b] [--static] [--strict] [--timeout <s>] [--json] # does .claude/skills/ actually REGISTER? --static = instant layout scan (CI-safe, no session): flags dirs that can never register; default also starts a real session and reads the authoritative system/init listing. exit 0 pass / 1 fail / 2 inconclusive — an unobservable registration is NEVER a pass
786
883
  dz compounding [--project <dir>] [--json] # honest learning-loop payoff report: pool write-only ratio, guard repeat-violation trajectory (rate per audit-half), cold-vs-warm replay readiness over unique untruncated prompt events, apply-leg staleness — a gate without enough data says INSUFFICIENT_DATA, never a verdict
884
+ dz epoch-replay --mock [--n <N>] [--effect <-1..1>] [--tie-rate <0..1>] [--seed <N>] [--slice <name>] [--margin <0..0.5>] [--json] # $0 synthetic run through the REAL verdict math; labelled SYNTHETIC, same seed = byte-identical
885
+ dz epoch-replay --emit [--project <dir>] [--limit <N>] [--seed <N>] [--margin <0..0.5>] [--out <file>] [--json] # cold-vs-warm work order: replayable instances + PRE-REGISTERED blind A/B assignment + the PRE-REGISTERED non-superiority margin + a sha256 integrity digest + emittedAt/corpus fingerprint (raw prompts — defaults into the git-ignored .dz/epoch-replay/)
886
+ dz epoch-replay --judge <filled-work-order.json> [--out <file>] [--json] # blind judge prompts from the filled plans — the file carries {id, prompt} ONLY (no assignment, no arm names); refuses an order whose digest or seed-derived assignment does not check out; half-pairs skipped WITH A REASON, on stdout
887
+ dz epoch-replay --score <judgments.json> --work-order <file> [--slice <name>] [--json] # un-blind against the VERIFIED pre-registered assignment; ONE paired binomial over DECISIVE pairs (ties excluded, reported) → SUPPORTED only when the lift interval (2p−1) lies entirely above zero; FALSIFIED only on harm or a passed non-superiority test (lift upper bound below the margin PRE-REGISTERED in the work order, default 0.05, at 10+ decisive pairs); else INCONCLUSIVE (min 5 decisive pairs). Refuses a forged work order, a --margin flag, or duplicate judgement ids; the verdict is data, not an exit code
787
888
  dz score --slug <feature> [--project <dir>] [--json] # process scorecard for ONE feature-adr run, from its artifacts: ADR confirmation, discrimination proof, cross-model QE grade, live verification, README-first, learning loop, amendments — DESCRIPTIVE-ONLY (a low score exits 0); evidence lines are shown so the reader judges the heuristics
788
889
  dz backlog <sub> add "<idea>" | list | show <id> | goals [--validate] | roulette [--seed n] [--commit <id>] | enrich <id> | jira <id> | harmonize [--apply] # brain-backed idea backlog: capture an idea → semantic dedup against past ideas/features via the REUSED agentdb vector engine (DUPLICATE≥0.92 / RELATED / NEW) + GoalMap alignment ("map+compass") → weighted seeded roulette picks one to work on → enrich STAGES an idea2prd hand-off → jira writes an auditable outbox via a configurable MCP adapter seam (jira-mcp|copilot-mcp|none). No 2nd vector store; without agentdb it degrades to exact-text dedup (honest)
789
890
  dz sign --init --out <path> | --pack <dir> --key <path> # --init: generate the Ed25519 keypair (private OUTSIDE the repo, prints the public key for keys/dz.pub); else sign a pack's manifest + CycloneDX SBOM
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAkUH,2EAA2E;AAC3E,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,iBAAiB,CAAC;CAC5C;AAED,yFAAyF;AACzF,MAAM,MAAM,iBAAiB,GAAG,CAC9B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAAE,KACvD;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAg2M9E,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE,KAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA0J5E"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAwVH,2EAA2E;AAC3E,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,iBAAiB,CAAC;CAC5C;AAED,yFAAyF;AACzF,MAAM,MAAM,iBAAiB,GAAG,CAC9B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAAE,KACvD;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAopN9E,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE,KAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA4J5E"}
package/dist/cli.js CHANGED
@@ -9,7 +9,9 @@ import { fileURLToPath } from 'node:url';
9
9
  import { execSync, spawn } from 'node:child_process';
10
10
  import { homedir, tmpdir } from 'node:os';
11
11
  import { createRequire } from 'node:module';
12
- import { createSkill, getSkillInfo, getWorkflow, isTargetName, listSkills, runDoctor, runInit, benchmarkSkill, benchmarkSkills, scanMcp, reconcileCapabilities, RECONCILE_BANNER, buildRegistry, discoverSkillPackDirs, checkUpstream, compareSkills, checkAllUpstream, sweepSkillDrift, syncCanonicalSkill, checkUpgrades, discoverPackages, discoverSourcePackages, fetchAllDownloads, filterByCategory, pretrain, recommend, generatePlugin, publishPackages, runSetup, runMigrate, searchRegistry, runSync, runVerify, runInitAgentsMd, runInitGeminiMd, TARGET_NAMES, buildParityMatrix, TARGET_CAPABILITIES, TARGET_SHORT_LABELS, WORKFLOW_NAMES, importEcc, recordPattern, resolveLearningBackend, storeStats, consolidateSessions, pruneNoisePatterns, lessonDeltaReport, removePatternsByIds, snapshotStore, recallHybrid, teachGuard, mirrorPatternsToVector, mirrorEntriesToVector, patternVectorEntry, readMemoryLearningConfig, promotePatterns, quarantineExpiryCandidates, pruneQuarantinePatterns, clearAgentdbQuarantine, vectorMirrorEnabled, vectorTierStatus, resolveVectorEngine, reindexVectorStore, harmonizeVectorStore, importRvfCheckpoint, statuslineData, writeFeatureAdrState, computeUsage, deriveUsageCalibration, normalizeClaudeUsageModelKey, readUsageLimits, claimCheck, summarize, queryBookKnowledge, loadStorePatternsSync, patternRecordId, loadStoreRecords, recordToPattern, bundleSkills, brainHome, listBrain, promoteProjectToBrain, updateBrainSource, queryBrain, groundPrompt, expandKu, reindexBrainVectors, buildPrimer, exportBrainSlice, importBrainSlice, registerKusToBrain, RECALL_USAGE_LOG_RELATIVE, RECALL_USAGE_LOG_MAX_BYTES, parseRecallUsageLog, buildRecallUsageReport, buildManifest, buildSbom, resolveTrustRoot, decideVerifyPolicy, generateSigningKeypair, evaluateGuard, resolveRules, auditRecord, guardExitCode, DEFAULT_RULES, parsePnpmLockImporters, decideProvenance, isInsideTree, signManifest, verifyManifest, listSignablePackFiles, assertKeyOutsideTree, decidePublishGate, collectPackageFacts, planReleaseGates, selectAffectedPackages, classifyGateExecutions, buildFailureIssue, buildReleaseNotes, releaseTagName, firstOutputLine, formatPublishError, MANIFEST_NAME, SBOM_NAME, buildArchitectureMap, renderMapHuman, findArchitectureDrift, renderDriftReport, scanWorkspacePackages, loadSubsystemManifest, loadProductVision, checkFeatureAgainstArchitecture, renderArchCheck, planProjectSkills, guidanceForStage, renderInjectionReport, analyzeCorpus, renderRakeReport, renderCriticSection, rakeAsLesson, rakeReward, DEFAULT_RAKE_THRESHOLDS, streamSessionEvents, findLatestTranscript, detectProcessRakes, buildRetro, renderRetro, retroLessonText, PROCESS_SIGNATURES, RETRO_DOMAIN, scanForSetup, buildSetupPlan, scaffoldFromSpec, renderScaffoldPreview, readExistingForScaffold, assembleChallengeContext, buildChallengeBrief, planDiscriminationCheck, classifyDiscrimination, pickAdversaryModel, CHALLENGE_QUESTIONS, loadOutcomes, renderOutcomes, statsForKey, selectAutoCost, recordProvisional, finalizeOutcome, COST_LADDER, splitScenarios, budgetPlan, selectWinner, proseScopeOk, renderProseDiff, readScenarioIds, DEFAULT_MAX_JUDGE_RUNS, collectDeliveryFacts, planDeliveryCheck, renderDeliveryBrief, classifyDelivery, isUsablePlaneResult, renderDeliveryReview, scanSkillsLayout, parseInitFacts, verifyRegistration, buildContentProbePrompt, classifyContentProbe, renderContentProbe, findNonRegistrableSkillDirs, assembleCompoundingReport, scoreRun, renderScorecard, renderCompoundingReport, readReinforcementState, readQuarantineState, registrationExitCode, renderRegistrationReport,
12
+ import { createSkill, getSkillInfo, getWorkflow, isTargetName, listSkills, runDoctor, runInit, benchmarkSkill, benchmarkSkills, scanMcp, reconcileCapabilities, RECONCILE_BANNER, buildRegistry, discoverSkillPackDirs, checkUpstream, compareSkills, checkAllUpstream, sweepSkillDrift, syncCanonicalSkill, checkUpgrades, discoverPackages, discoverSourcePackages, fetchAllDownloads, filterByCategory, pretrain, recommend, generatePlugin, publishPackages, runSetup, runMigrate, searchRegistry, runSync, runVerify, runInitAgentsMd, runInitGeminiMd, TARGET_NAMES, buildParityMatrix, TARGET_CAPABILITIES, TARGET_SHORT_LABELS, WORKFLOW_NAMES, importEcc, recordPattern, resolveLearningBackend, storeStats, consolidateSessions, pruneNoisePatterns, lessonDeltaReport, removePatternsByIds, snapshotStore, recallHybrid, teachGuard, mirrorPatternsToVector, mirrorEntriesToVector, patternVectorEntry, readMemoryLearningConfig, promotePatterns, quarantineExpiryCandidates, pruneQuarantinePatterns, clearAgentdbQuarantine, vectorMirrorEnabled, vectorTierStatus, resolveVectorEngine, reindexVectorStore, harmonizeVectorStore, importRvfCheckpoint, statuslineData, writeFeatureAdrState, computeUsage, deriveUsageCalibration, normalizeClaudeUsageModelKey, readUsageLimits, claimCheck, summarize, queryBookKnowledge, loadStorePatternsSync, patternRecordId, loadStoreRecords, recordToPattern, bundleSkills, brainHome, listBrain, promoteProjectToBrain, updateBrainSource, queryBrain, groundPrompt, expandKu, reindexBrainVectors, buildPrimer, exportBrainSlice, importBrainSlice, registerKusToBrain, RECALL_USAGE_LOG_RELATIVE, RECALL_USAGE_LOG_MAX_BYTES, parseRecallUsageLog, buildRecallUsageReport, buildManifest, buildSbom, resolveTrustRoot, decideVerifyPolicy, generateSigningKeypair, evaluateGuard, resolveRules, auditRecord, guardExitCode, DEFAULT_RULES, parsePnpmLockImporters, decideProvenance, isInsideTree, signManifest, verifyManifest, listSignablePackFiles, assertKeyOutsideTree, decidePublishGate, collectPackageFacts, planReleaseGates, selectAffectedPackages, classifyGateExecutions, buildFailureIssue, buildReleaseNotes, releaseTagName, firstOutputLine, formatPublishError, MANIFEST_NAME, SBOM_NAME, buildArchitectureMap, renderMapHuman, findArchitectureDrift, renderDriftReport, scanWorkspacePackages, loadSubsystemManifest, loadProductVision, checkFeatureAgainstArchitecture, renderArchCheck, planProjectSkills, guidanceForStage, renderInjectionReport, analyzeCorpus, renderRakeReport, renderCriticSection, rakeAsLesson, rakeReward, DEFAULT_RAKE_THRESHOLDS, streamSessionEvents, findLatestTranscript, detectProcessRakes, buildRetro, renderRetro, retroLessonText, PROCESS_SIGNATURES, RETRO_DOMAIN, scanForSetup, buildSetupPlan, scaffoldFromSpec, renderScaffoldPreview, readExistingForScaffold, assembleChallengeContext, buildChallengeBrief, planDiscriminationCheck, classifyDiscrimination, pickAdversaryModel, CHALLENGE_QUESTIONS, loadOutcomes, renderOutcomes, statsForKey, selectAutoCost, recordProvisional, finalizeOutcome, COST_LADDER, splitScenarios, budgetPlan, selectWinner, proseScopeOk, renderProseDiff, readScenarioIds, DEFAULT_MAX_JUDGE_RUNS, collectDeliveryFacts, planDeliveryCheck, renderDeliveryBrief, classifyDelivery, isUsablePlaneResult, renderDeliveryReview, scanSkillsLayout, parseInitFacts, verifyRegistration, buildContentProbePrompt, classifyContentProbe, renderContentProbe, findNonRegistrableSkillDirs, assembleCompoundingReport,
13
+ // Cold-vs-warm EPOCH RUNNER (feature epoch-replay) — orchestrates + scores, never calls a model.
14
+ replayableInstances, buildWorkOrder, buildJudgePrompts, unblindJudgments, verifyWorkOrder, isValidMargin, DIGEST_HONEST_SCOPE, scoreEpochReplay, generateMockOutcomes, renderEpochReplayResult, renderWorkOrderSummary, renderJudgePromptsSummary, WORK_ORDER_KIND, DEFAULT_MOCK_N, DEFAULT_MOCK_SEED, scoreRun, renderScorecard, renderCompoundingReport, readReinforcementState, readQuarantineState, registrationExitCode, renderRegistrationReport,
13
15
  // Smart Backlog (feature smart-backlog) — goal-directed idea pipeline over the Brain vector engine.
14
16
  readBacklogConfig, readIdeas, writeIdeas, ideaId, dedupIdea, readGoalMap, readGoalMapDetailed, parseEffort, ensureBacklogGitignored, isSafeId, alignIdea, mirrorIdeaVector, snapshotIdeas, spinRoulette, rankRoulette, seededRng, eligibleIdeas, stageEnrichment, buildJiraDraft, resolveJiraAdapter, makeBacklogIO, harmonizeBacklog, BACKLOG_BACKENDS, } from '@dzhechkov/harness-core';
15
17
  import { getPreset, PRESET_NAMES } from '@dzhechkov/harness-presets';
@@ -39,6 +41,10 @@ Usage:
39
41
  dz delivery-check --slug <slug> [--context-only] [--findings <f.json>] [--strict] [--author <model>] [--json] (portable Step-10 Delivery Gate: prints the 4-plane review brief + artifact probes; --findings classifies a fed-back review into a fail-closed ready|blocked hand-off and writes features/<slug>/10_delivery_review.md; --strict exits 1 on blocked)
40
42
  dz skills-verify [--dir <project>] [--expect a,b] [--static] [--strict] [--json] (does .claude/skills/ actually REGISTER? --static = instant layout scan for CI; default reads the authoritative system/init listing from a real session. exit 0 pass / 1 fail / 2 inconclusive — never a false pass)
41
43
  dz compounding [--project <dir>] [--json] (honest learning-loop payoff report: pool write-only ratio, guard repeat-violation trajectory, cold-vs-warm replay readiness, instrumentation health — a gate without enough data says INSUFFICIENT_DATA, never a fake verdict)
44
+ dz epoch-replay --mock [--n <N>] [--effect <-1..1>] [--tie-rate <0..1>] [--seed <N>] [--slice <name>] [--json] ($0 synthetic run — exercises the verdict math, NOT evidence)
45
+ dz epoch-replay --emit [--project <dir>] [--limit <N>] [--seed <N>] [--out <file>] (cold-vs-warm work order: instances + PRE-REGISTERED blind A/B assignment; the runner never calls a model)
46
+ dz epoch-replay --judge <filled-work-order.json> [--out <file>] (blind judge prompts from the filled plans)
47
+ dz epoch-replay --score <judgments.json> --work-order <file> [--slice <name>] [--json] (un-blind against the pre-registered assignment → SUPPORTED only when the two 95% Wilson CIs are DISJOINT, else FALSIFIED / INCONCLUSIVE)
42
48
  dz score --slug <feature> [--project <dir>] [--json] (process scorecard for ONE feature-adr run, from its artifacts: ADR confirmation, discrimination, cross-model QE grade, live verification, README-first, learning loop, amendments — descriptive-only, a low score exits 0)
43
49
  dz backlog add "<idea>" [--effort 1-5] [--proposal <text>] [--dry-run] [--project <dir>] [--json] (capture an idea: semantic dedup against existing ideas via the Brain vector engine (DUPLICATE>=0.92 merges, RELATED links, NEW creates) + GoalMap alignment; --dry-run classifies without writing)
44
50
  dz backlog list [--status <s>] [--goal <id>] [--project <dir>] [--json] (list captured ideas, filterable by status/goal)
@@ -5495,6 +5501,46 @@ function cmdScore(options, flags, cwd, write) {
5495
5501
  write(renderScorecard(card));
5496
5502
  return 0;
5497
5503
  }
5504
+ /**
5505
+ * Read the apply-leg usage log into the shape `compounding.ts` / `epoch-replay.ts` expect.
5506
+ *
5507
+ * ONE reader, because there were two and they disagreed: the `dz compounding` fact-gatherer used
5508
+ * to drop `eventId` and `queryTruncated`, so the readiness gate counted 48 "replayable pairs" over
5509
+ * a log whose honest count is 25 (MEASURED on this repo, 2026-07-29). Both defences documented in
5510
+ * `assembleCompoundingReport` — "one prompt = one pair" (Codex #1) and "a truncated query is a
5511
+ * prefix, not the prompt" (Codex #3) — were correct in the pure function and DEAD at its only
5512
+ * caller, because the caller never passed the fields they read.
5513
+ */
5514
+ function readRecallUsageEvents(root) {
5515
+ const usage = [];
5516
+ try {
5517
+ const text = readFileSync(join(root, '.dz', 'recall-usage.jsonl'), 'utf-8');
5518
+ for (const line of text.split('\n')) {
5519
+ if (line.trim() === '')
5520
+ continue;
5521
+ try {
5522
+ const o = JSON.parse(line);
5523
+ if (typeof o.dzId === 'string' && typeof o.ts === 'string' && o.kind !== 'aggregate') {
5524
+ usage.push({
5525
+ dzId: o.dzId,
5526
+ ts: o.ts,
5527
+ ...(typeof o.query === 'string' ? { query: o.query } : {}),
5528
+ ...(typeof o.runId === 'string' ? { runId: o.runId } : {}),
5529
+ ...(typeof o.eventId === 'string' ? { eventId: o.eventId } : {}),
5530
+ ...(o.queryTruncated === true ? { queryTruncated: true } : {}),
5531
+ });
5532
+ }
5533
+ }
5534
+ catch {
5535
+ /* one bad line must not kill the read */
5536
+ }
5537
+ }
5538
+ }
5539
+ catch {
5540
+ /* no log yet — callers report the absence */
5541
+ }
5542
+ return usage;
5543
+ }
5498
5544
  /**
5499
5545
  * `dz compounding` — does the learning loop actually PAY? (feature compounding, scout C2.)
5500
5546
  * Gathers the facts (store rows, apply-leg usage log, guard audit) and hands them to the PURE
@@ -5532,31 +5578,7 @@ function cmdCompounding(options, flags, cwd, write) {
5532
5578
  reward: typeof r.score === 'number' ? r.score : null,
5533
5579
  }));
5534
5580
  // apply-leg usage events (read records only; aggregate rows carry no query by construction)
5535
- const usage = [];
5536
- try {
5537
- const text = readFileSync(join(root, '.dz', 'recall-usage.jsonl'), 'utf-8');
5538
- for (const line of text.split('\n')) {
5539
- if (line.trim() === '')
5540
- continue;
5541
- try {
5542
- const o = JSON.parse(line);
5543
- if (typeof o.dzId === 'string' && typeof o.ts === 'string' && o.kind !== 'aggregate') {
5544
- usage.push({
5545
- dzId: o.dzId,
5546
- ts: o.ts,
5547
- ...(typeof o.query === 'string' ? { query: o.query } : {}),
5548
- ...(typeof o.runId === 'string' ? { runId: o.runId } : {}),
5549
- });
5550
- }
5551
- }
5552
- catch {
5553
- /* one bad line must not kill the report */
5554
- }
5555
- }
5556
- }
5557
- catch {
5558
- /* no log yet — the report says so */
5559
- }
5581
+ const usage = readRecallUsageEvents(root);
5560
5582
  // guard audit events
5561
5583
  const guard = [];
5562
5584
  try {
@@ -5589,6 +5611,307 @@ function cmdCompounding(options, flags, cwd, write) {
5589
5611
  write(renderCompoundingReport(report));
5590
5612
  return 0;
5591
5613
  }
5614
+ // ── `dz epoch-replay` (feature epoch-replay) ────────────────────────────────────────────────────
5615
+ const EPOCH_REPLAY_DIR = join('.dz', 'epoch-replay');
5616
+ const EPOCH_REPLAY_USAGE = [
5617
+ 'dz epoch-replay --mock [--n <N>] [--effect <-1..1>] [--tie-rate <0..1>] [--seed <N>] [--slice <name>] [--margin <0..1>] [--json]',
5618
+ 'dz epoch-replay --emit [--project <dir>] [--limit <N>] [--seed <N>] [--margin <0..0.5>] [--out <file>] [--json]',
5619
+ 'dz epoch-replay --judge <filled-work-order.json> [--out <file>] [--json]',
5620
+ 'dz epoch-replay --score <judgments.json> --work-order <file> [--slice <name>] [--json] (margin comes from the work order)',
5621
+ ].join('\n ');
5622
+ /** Read a JSON file into an object, or return a parse/IO error string. */
5623
+ function readJsonFile(path) {
5624
+ try {
5625
+ return { value: JSON.parse(readFileSync(path, 'utf-8')) };
5626
+ }
5627
+ catch (e) {
5628
+ return { error: `cannot read ${path}: ${e instanceof Error ? e.message : String(e)}` };
5629
+ }
5630
+ }
5631
+ /**
5632
+ * Integrity-check a parsed work order. Checking `kind` + `Array.isArray(items)` was VACUOUS: a
5633
+ * hand-written file with those two fields and a fabricated `warmIsA` bought whatever verdict its
5634
+ * author wanted (Codex QE HIGH-2). `verifyWorkOrder` recomputes the digest AND re-derives every
5635
+ * assignment from the stated seed.
5636
+ */
5637
+ function asVerifiedWorkOrder(value) {
5638
+ const v = verifyWorkOrder(value);
5639
+ if (!v.ok)
5640
+ return { problems: v.problems };
5641
+ return { order: value };
5642
+ }
5643
+ function writeJsonOut(path, value) {
5644
+ mkdirSync(dirname(path), { recursive: true });
5645
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, 'utf-8');
5646
+ }
5647
+ /** Numeric option parsing that never silently accepts garbage. */
5648
+ function numOpt(options, key) {
5649
+ const raw = options.get(key);
5650
+ if (raw === undefined)
5651
+ return null;
5652
+ const n = Number(raw);
5653
+ if (!Number.isFinite(n))
5654
+ return { error: `--${key} expects a finite number, got ${JSON.stringify(raw)}` };
5655
+ return { value: n };
5656
+ }
5657
+ /**
5658
+ * `dz epoch-replay` — the executable cold-vs-warm epoch runner (feature epoch-replay, scout #4).
5659
+ *
5660
+ * `dz compounding` says whether a replay CAN be run; this says what it FOUND. The runner never
5661
+ * calls a model: real mode emits a work order, renders blind judge prompts, and scores filled
5662
+ * judgments; `--mock` exercises the same verdict math on seeded synthetic outcomes at $0.
5663
+ */
5664
+ function cmdEpochReplay(options, flags, cwd, write) {
5665
+ const json = flags.has('json');
5666
+ const fail = (msg) => {
5667
+ write(json ? JSON.stringify({ error: msg, exitCode: 1 }) : `dz epoch-replay: ${msg}\n usage:\n ${EPOCH_REPLAY_USAGE}`);
5668
+ return 1;
5669
+ };
5670
+ if (flags.has('help')) {
5671
+ write(`dz epoch-replay — cold (epoch 0) vs warm (epoch 1), Wilson-CI three-valued verdict\n ${EPOCH_REPLAY_USAGE}`);
5672
+ write('');
5673
+ write(' ONE binomial over DECISIVE pairs (ties carry no direction and are excluded from the test).');
5674
+ write(' SUPPORTED only when the paired lift interval lies ENTIRELY above zero.');
5675
+ write(' FALSIFIED only on HARM (entirely below zero), or on a passed NON-SUPERIORITY test (the');
5676
+ write(' lift upper bound below the PRE-REGISTERED margin, default 0.05). Otherwise INCONCLUSIVE —');
5677
+ write(' a first-class honest outcome; a tie is UNDER-POWERED, never "refuted".');
5678
+ write(' The margin is pre-registered at --emit and stored in the work order; --score reads it there.');
5679
+ write(' This runner ORCHESTRATES and SCORES; it never calls a model. The judge-facing file holds');
5680
+ write(' {id, prompt} and nothing else; --score refuses any work order whose digest or seed-derived');
5681
+ write(' assignment does not check out, and refuses duplicate judgement ids.');
5682
+ return 0;
5683
+ }
5684
+ const allowedFlags = new Set(['mock', 'emit', 'json', 'help']);
5685
+ for (const flag of flags) {
5686
+ if (!allowedFlags.has(flag))
5687
+ return fail(`unknown option --${flag}`);
5688
+ }
5689
+ const allowedOptions = new Set(['judge', 'score', 'work-order', 'project', 'out', 'n', 'effect', 'tie-rate', 'seed', 'slice', 'limit', 'margin']);
5690
+ for (const key of options.keys()) {
5691
+ if (key.startsWith('_positional_'))
5692
+ return fail(`unexpected argument "${options.get(key)}"`);
5693
+ if (!allowedOptions.has(key))
5694
+ return fail(`unknown option --${key}`);
5695
+ }
5696
+ // Exactly ONE mode. A command whose default mode is a report can silently swallow a typo'd mode
5697
+ // flag and print something that reads like a result — so there is NO default mode here.
5698
+ const modes = [
5699
+ flags.has('mock') ? 'mock' : null,
5700
+ flags.has('emit') ? 'emit' : null,
5701
+ options.has('judge') ? 'judge' : null,
5702
+ options.has('score') ? 'score' : null,
5703
+ ].filter((m) => m !== null);
5704
+ if (modes.length === 0)
5705
+ return fail('pick exactly one mode: --mock | --emit | --judge <file> | --score <file>');
5706
+ if (modes.length > 1)
5707
+ return fail(`modes are exclusive, got: ${modes.join(', ')}`);
5708
+ const mode = modes[0];
5709
+ const num = (key) => {
5710
+ const r = numOpt(options, key);
5711
+ if (r === null)
5712
+ return undefined;
5713
+ if ('error' in r)
5714
+ return r;
5715
+ return r.value;
5716
+ };
5717
+ // ── --mock: seeded synthetic outcomes, $0, exercises the real verdict math ──
5718
+ if (mode === 'mock') {
5719
+ for (const key of ['judge', 'score', 'work-order', 'project', 'out', 'limit']) {
5720
+ if (options.has(key))
5721
+ return fail(`--${key} is not valid with --mock`);
5722
+ }
5723
+ const parsed = {};
5724
+ for (const key of ['n', 'effect', 'tie-rate', 'seed']) {
5725
+ const v = num(key);
5726
+ if (typeof v === 'object' && v !== null)
5727
+ return fail(v.error);
5728
+ parsed[key] = v;
5729
+ }
5730
+ const outcomes = generateMockOutcomes({
5731
+ n: parsed.n ?? DEFAULT_MOCK_N,
5732
+ effect: parsed.effect ?? 0,
5733
+ tieRate: parsed['tie-rate'] ?? 0,
5734
+ seed: parsed.seed ?? DEFAULT_MOCK_SEED,
5735
+ });
5736
+ const marginOpt = numOpt(options, 'margin');
5737
+ if (marginOpt !== null && 'error' in marginOpt)
5738
+ return fail(marginOpt.error);
5739
+ const result = scoreEpochReplay(outcomes, {
5740
+ slice: options.get('slice') ?? 'all',
5741
+ ...(marginOpt !== null ? { margin: marginOpt.value } : {}),
5742
+ });
5743
+ if (result.refusal !== null)
5744
+ return fail(result.refusal);
5745
+ if (json) {
5746
+ write(JSON.stringify({ mode: 'mock', synthetic: true, ...result, exitCode: 0 }, null, 2));
5747
+ }
5748
+ else {
5749
+ write(renderEpochReplayResult(result));
5750
+ write('');
5751
+ write(` SYNTHETIC (--mock): outcomes generated with seed ${parsed.seed ?? DEFAULT_MOCK_SEED} at a TRUE effect of ${parsed.effect ?? 0}. ` +
5752
+ 'This exercises the protocol, it is NOT evidence about the learning loop.');
5753
+ }
5754
+ return 0;
5755
+ }
5756
+ // ── --emit: the generation work order (real mode, stage 1) ──
5757
+ if (mode === 'emit') {
5758
+ for (const key of ['judge', 'score', 'work-order', 'n', 'effect', 'tie-rate', 'slice']) {
5759
+ if (options.has(key))
5760
+ return fail(`--${key} is not valid with --emit`);
5761
+ }
5762
+ const root = resolve(cwd, options.get('project') ?? '.');
5763
+ const seed = num('seed');
5764
+ if (typeof seed === 'object' && seed !== null)
5765
+ return fail(seed.error);
5766
+ const limit = num('limit');
5767
+ if (typeof limit === 'object' && limit !== null)
5768
+ return fail(limit.error);
5769
+ // HIGH-B: the non-superiority margin is PRE-REGISTERED here, digest-covered, and read back by
5770
+ // --score. Out of range is refused, never clamped — `--margin 99` must not buy FALSIFIED.
5771
+ const emitMargin = num('margin');
5772
+ if (typeof emitMargin === 'object' && emitMargin !== null)
5773
+ return fail(emitMargin.error);
5774
+ if (typeof emitMargin === 'number' && !isValidMargin(emitMargin)) {
5775
+ return fail(`--margin ${emitMargin} must be in (0, 0.5] — refused, not clamped: an oversized margin buys FALSIFIED`);
5776
+ }
5777
+ const lessonText = new Map();
5778
+ for (const r of loadStoreRecords(root)) {
5779
+ if (typeof r.text === 'string' && r.text.trim() !== '')
5780
+ lessonText.set(r.id, r.text);
5781
+ }
5782
+ // The SAME reader `dz compounding` uses — readiness and the runner must see one corpus.
5783
+ const instances = replayableInstances(readRecallUsageEvents(root), lessonText);
5784
+ const order = buildWorkOrder(instances, {
5785
+ ...(typeof seed === 'number' ? { seed } : {}),
5786
+ ...(typeof limit === 'number' ? { limit } : {}),
5787
+ ...(typeof emitMargin === 'number' ? { margin: emitMargin } : {}),
5788
+ });
5789
+ const outPath = resolve(cwd, options.get('out') ?? join(root, EPOCH_REPLAY_DIR, 'work-order.json'));
5790
+ try {
5791
+ writeJsonOut(outPath, order);
5792
+ }
5793
+ catch (e) {
5794
+ return fail(`cannot write ${outPath}: ${e instanceof Error ? e.message : String(e)}`);
5795
+ }
5796
+ if (json) {
5797
+ write(JSON.stringify({ mode: 'emit', out: outPath, instances: order.items.length, seed: order.seed, margin: order.margin, digest: order.digest, corpusFingerprint: order.corpusFingerprint, emittedAt: order.emittedAt, exitCode: 0 }, null, 2));
5798
+ }
5799
+ else
5800
+ write(renderWorkOrderSummary(order, outPath));
5801
+ return 0;
5802
+ }
5803
+ // ── --judge: blind judge prompts from a FILLED work order (real mode, stage 2) ──
5804
+ if (mode === 'judge') {
5805
+ for (const key of ['score', 'work-order', 'n', 'effect', 'tie-rate', 'slice', 'limit', 'seed', 'margin']) {
5806
+ if (options.has(key))
5807
+ return fail(`--${key} is not valid with --judge`);
5808
+ }
5809
+ const inPath = resolve(cwd, options.get('judge'));
5810
+ const read = readJsonFile(inPath);
5811
+ if ('error' in read)
5812
+ return fail(read.error);
5813
+ const verified = asVerifiedWorkOrder(read.value);
5814
+ if ('problems' in verified) {
5815
+ return fail(`${inPath} is not a verifiable ${WORK_ORDER_KIND}: ${verified.problems.join('; ')} (emit one with \`dz epoch-replay --emit\`)`);
5816
+ }
5817
+ const result = buildJudgePrompts(verified.order);
5818
+ const outPath = resolve(cwd, options.get('out') ?? join(dirname(inPath), 'judge-prompts.json'));
5819
+ try {
5820
+ // The JUDGE-FACING artifact. Its whole content is {id, prompt} per item — no `warmIsA`, no
5821
+ // arm names, no path back to the work order, and NOT the `skipped` list (whose reasons name
5822
+ // arms). Anything else here hands the judge the answer key (Codex QE CRITICAL-1).
5823
+ writeJsonOut(outPath, {
5824
+ kind: 'dz-epoch-replay-judge-prompts',
5825
+ version: 1,
5826
+ prompts: result.prompts.map((p) => ({ id: p.id, prompt: p.prompt })),
5827
+ });
5828
+ }
5829
+ catch (e) {
5830
+ return fail(`cannot write ${outPath}: ${e instanceof Error ? e.message : String(e)}`);
5831
+ }
5832
+ // `skipped` is OPERATOR-facing only — stdout / --json, never the file.
5833
+ if (json)
5834
+ write(JSON.stringify({ mode: 'judge', out: outPath, prompts: result.prompts.length, skipped: result.skipped, exitCode: 0 }, null, 2));
5835
+ else
5836
+ write(renderJudgePromptsSummary(result, outPath));
5837
+ return 0;
5838
+ }
5839
+ // ── --score: un-blind + verdict (real mode, stage 3) ──
5840
+ for (const key of ['n', 'effect', 'tie-rate', 'limit', 'seed', 'out', 'project']) {
5841
+ if (options.has(key))
5842
+ return fail(`--${key} is not valid with --score`);
5843
+ }
5844
+ // HIGH-B: a margin chosen once the counts are visible is not a pre-registration — and `--margin 99`
5845
+ // at scoring time would simply buy FALSIFIED. Real mode reads it from the work order, full stop.
5846
+ if (options.has('margin')) {
5847
+ return fail('--margin is not valid with --score: the non-superiority margin is PRE-REGISTERED at --emit and stored in the work order (re-emit to change it)');
5848
+ }
5849
+ const orderPath = options.get('work-order');
5850
+ if (orderPath === undefined) {
5851
+ return fail('--score requires --work-order <file>: un-blinding must use the PRE-REGISTERED assignment, not a label in the judgments file');
5852
+ }
5853
+ const orderRead = readJsonFile(resolve(cwd, orderPath));
5854
+ if ('error' in orderRead)
5855
+ return fail(orderRead.error);
5856
+ const verifiedOrder = asVerifiedWorkOrder(orderRead.value);
5857
+ if ('problems' in verifiedOrder) {
5858
+ return fail(`${resolve(cwd, orderPath)} is not a verifiable ${WORK_ORDER_KIND} — refusing to un-blind against it: ${verifiedOrder.problems.join('; ')}`);
5859
+ }
5860
+ const order = verifiedOrder.order;
5861
+ const judgePath = resolve(cwd, options.get('score'));
5862
+ const judgeRead = readJsonFile(judgePath);
5863
+ if ('error' in judgeRead)
5864
+ return fail(judgeRead.error);
5865
+ const rawRows = Array.isArray(judgeRead.value)
5866
+ ? judgeRead.value
5867
+ : typeof judgeRead.value === 'object' && judgeRead.value !== null && Array.isArray(judgeRead.value.judgments)
5868
+ ? judgeRead.value.judgments
5869
+ : null;
5870
+ if (rawRows === null)
5871
+ return fail(`${judgePath} must be an array of {id, winner} rows (or {"judgments": [...]})`);
5872
+ const unblind = unblindJudgments(order, rawRows.map((r) => {
5873
+ const o = (typeof r === 'object' && r !== null ? r : {});
5874
+ return { id: typeof o.id === 'string' ? o.id : '', winner: typeof o.winner === 'string' ? o.winner : '' };
5875
+ }));
5876
+ // A duplicated judgement id is corrupt input, not a skippable row — refuse loudly.
5877
+ if (!unblind.ok)
5878
+ return fail(unblind.error ?? 'judgments refused');
5879
+ const { outcomes, skipped } = unblind;
5880
+ const result = scoreEpochReplay(outcomes, {
5881
+ slice: options.get('slice') ?? 'all',
5882
+ margin: order.margin, // PRE-REGISTERED in the work order, verified by the digest
5883
+ });
5884
+ if (result.refusal !== null)
5885
+ return fail(result.refusal);
5886
+ if (json) {
5887
+ write(JSON.stringify({
5888
+ mode: 'score',
5889
+ workOrder: resolve(cwd, orderPath),
5890
+ judgments: judgePath,
5891
+ scored: outcomes.length,
5892
+ skipped,
5893
+ provenance: { seed: order.seed, margin: order.margin, emittedAt: order.emittedAt, digest: order.digest, corpusFingerprint: order.corpusFingerprint, digestScope: DIGEST_HONEST_SCOPE },
5894
+ ...result,
5895
+ exitCode: 0,
5896
+ }, null, 2));
5897
+ }
5898
+ else {
5899
+ write(renderEpochReplayResult(result));
5900
+ if (skipped.length > 0) {
5901
+ write('');
5902
+ write(` ${skipped.length} judgment(s) SKIPPED (never guessed):`);
5903
+ for (const s of skipped)
5904
+ write(` · ${s.id}: ${s.reason}`);
5905
+ }
5906
+ // Provenance, so a reviewer can ask for the original emitted file and compare.
5907
+ write('');
5908
+ write(` WORK ORDER: seed ${order.seed} · margin ${order.margin} (pre-registered) · emitted ${order.emittedAt}`);
5909
+ write(` digest ${order.digest}`);
5910
+ write(` corpus ${order.corpusFingerprint}`);
5911
+ write(` ${DIGEST_HONEST_SCOPE}`);
5912
+ }
5913
+ return 0;
5914
+ }
5592
5915
  /**
5593
5916
  * Env vars an INHERITED Claude session leaks into a child. Left in place, the probe can silently
5594
5917
  * read the parent's project instead of the target — the exact confound that made a hand-rolled
@@ -6756,6 +7079,8 @@ export async function runCli(argv, io = {}) {
6756
7079
  return cmdSkillsVerify(options, flags, cwd, write);
6757
7080
  case 'compounding':
6758
7081
  return cmdCompounding(options, flags, cwd, write);
7082
+ case 'epoch-replay':
7083
+ return cmdEpochReplay(options, flags, cwd, write);
6759
7084
  case 'score':
6760
7085
  return cmdScore(options, flags, cwd, write);
6761
7086
  case 'backlog':