@dzhechkov/harness-cli 0.3.236 → 0.3.238

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,15 +9,15 @@
9
9
  },
10
10
  {
11
11
  "path": "README.md",
12
- "sha256": "1e36d7e9e6022d412c030975ec686b1ab91c61c64c807ef2e9ff3fa75ac8917c"
12
+ "sha256": "ef6603652143fc1517c81506dad9d5e7b79e5d5138aad9f6281ad77492d58b71"
13
13
  },
14
14
  {
15
15
  "path": "coverage/coverage-final.json",
16
- "sha256": "1abb00d07ab57ad413d2747baa6a68cda0876a9e0c6f75db89c205fbf4863530"
16
+ "sha256": "667bf610bf890a7b93480a4b1045e98dc0c765effd80b78fde4e48538f595b3e"
17
17
  },
18
18
  {
19
19
  "path": "coverage/coverage-summary.json",
20
- "sha256": "42d2a98f54c2ec773b49a0a43c959a78d070c8c427724f4affbd5bafef35621d"
20
+ "sha256": "593705e81f851c0e65ffa9feaa9bbd396e7a3295b9b21076cfd953ee5a05b93a"
21
21
  },
22
22
  {
23
23
  "path": "dist/bin.d.ts",
@@ -41,15 +41,15 @@
41
41
  },
42
42
  {
43
43
  "path": "dist/cli.d.ts.map",
44
- "sha256": "8a00bf14c3fdd437b7e482da8c4018366041755714ed1791b1c523f9dee5de6c"
44
+ "sha256": "8f8807920c7ddd901361eb5a602df9c85fa69726e47535a146937038eab6b281"
45
45
  },
46
46
  {
47
47
  "path": "dist/cli.js",
48
- "sha256": "dea4446db1109285c1fc5f5283d7263462cb446f12b0d9b1e1e821374d0ca57d"
48
+ "sha256": "5f5fbcc7c040a272f6af992ef1f9c39482b941d74dbcadec9f3d9008babcb08a"
49
49
  },
50
50
  {
51
51
  "path": "dist/cli.js.map",
52
- "sha256": "a253625c94835ef94bbd141b1dd759578433423bd098314fd977c515f140dafe"
52
+ "sha256": "420d1178a513e54f315095a17e9c5722543ca6cbdbebd9a674d4d50ef61b8aa0"
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": "543319797d47d599a646c7d695f82aa31491ffd97579edd691b6a9cb99af76ef"
76
+ "sha256": "8fbaf10d29b5733cf9445e1ef5804a3e124f733c3390a99937d2d8dc3b4cf02a"
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": "c5d4902f9b0c4133318b15e757d07856872177dc92752d5996440524342b7f88"
84
+ "sha256": "131f23a50187e1be19875d4ccd76d8c354b1fbb8d193c03e73fda16ff64f51dc"
85
85
  },
86
86
  {
87
87
  "path": "src/index.ts",
@@ -89,7 +89,7 @@
89
89
  },
90
90
  {
91
91
  "path": "test/cli.test.ts",
92
- "sha256": "0538f3cfd8eb880305c08fef05a78601263003c54a6f1b44f97cd7ebd7bd37f9"
92
+ "sha256": "3deb99917f3d8b4a5c9a3b0315bd65a3099ce2b508121cfa2bdeed19b7554d48"
93
93
  },
94
94
  {
95
95
  "path": "tsconfig.json",
@@ -101,5 +101,5 @@
101
101
  }
102
102
  ]
103
103
  },
104
- "signature": "0IDcNO+8RqyABrL7YXlDBQLCTiNktCk/Ktd724vSyxsJ0/rti05jjEudABZo3+ILMok7jLgQs/YRalC77RdOCA=="
104
+ "signature": "spl/yr0En3yUAxhYjhIdot8D/Tri4JeoMrweQkA9GF2F9UyzDco3FLpaqCkvwlhGvj4phKb4qB0Nm3e+GAMaDw=="
105
105
  }
package/README.md CHANGED
@@ -372,6 +372,41 @@ the release job gets a perfectly attested malicious package.
372
372
  A ready-to-install workflow is at `features/publish-provenance/07_code_changes/publish.yml`; copy it to
373
373
  `.github/workflows/` and add an `NPM_TOKEN` secret.
374
374
 
375
+ ### Lesson quarantine — a fresh lesson is a hypothesis, not knowledge
376
+
377
+ Self-learning has a poisoning problem: the moment `dz teach` stores a lesson, it ranks alongside
378
+ patterns proven over months and can ride the auto-inject hook into your next task's context — even
379
+ if it is wrong, one-off, or junk. Quarantine (opt-in) closes the gap between COLLECT and RANK:
380
+
381
+ ```jsonc
382
+ // .dz/config.json
383
+ { "memory": { "learning": {
384
+ "quarantine": true, // fresh lessons start as quarantined hypotheses
385
+ "quarantineDamp": 0.5, // rank multiplier for ⚠q hits in recall (0..1]
386
+ "quarantineExpireDays": 30 // unconfirmed after N days ⇒ expiry CANDIDATE (report only)
387
+ } } }
388
+ ```
389
+
390
+ Three surfaces, three strictness levels — and promotion is EARNED, never automatic:
391
+
392
+ ```bash
393
+ dz teach "..." # → "⚠ quarantined: excluded from auto-inject, damped in recall"
394
+ dz recall "topic" # ⚠q hits are VISIBLE but marked + rank-damped (never hidden)
395
+ # the UserPromptSubmit auto-inject hook EXCLUDES ⚠q lessons entirely (logged, never silent)
396
+
397
+ dz teach --reinforce "<exact text>" # confirming a lesson IS its promotion
398
+ dz recall --promote <dzId> --apply # or promote explicitly (dry-run by default)
399
+
400
+ dz consolidate --prune-quarantine # report expired unconfirmed lessons (dry-run)
401
+ dz consolidate --prune-quarantine --apply # remove them (snapshots first) — a SEPARATE gate,
402
+ # never coupled to --prune-noise (unproven ≠ garbage)
403
+ ```
404
+
405
+ Absent config = zero behavior change. Existing lessons are grandfathered as promoted; a corrupt
406
+ quarantine marker reads as promoted (a metadata glitch must never isolate proven knowledge).
407
+ **When to use:** any project where subagents teach lessons unattended — the quarantine is the gate
408
+ between "an agent wrote this down" and "agents now act on it".
409
+
375
410
  ### Target parity — `dz parity` (the honest feature × target map)
376
411
 
377
412
  The harness runs on 10 targets, but not every feature runs everywhere: hooks, MCP (Model Context
@@ -392,13 +427,54 @@ Example (real output, trimmed):
392
427
  dz parity — codex (capabilities: shell, skills, mcp)
393
428
  ✓ dz CLI (all commands) via shell command
394
429
  ◐ feature-adr pipeline via interactive skill (plain /feature-adr)
395
- Step-10 Delivery Gate not available on this target
430
+ Step-10 Delivery Gate via dz delivery-check (CLI 4-plane hand-off protocol)
396
431
  ◐ Integrity claim-check via dz claim-check (CLI) + publish gate
397
432
  ```
398
433
 
399
434
  **When to use:** before promising a workflow to a teammate on Cursor/Codex/Hermes; when choosing
400
435
  a target for a project; as the requirements input for porting a feature to more targets.
401
436
 
437
+ ### Portable delivery gate — `dz delivery-check` (Step-10, on every shell target)
438
+
439
+ The feature-adr Step-10 Delivery Gate reviews a landed feature across four orthogonal planes and
440
+ emits a machine-checkable `ready | blocked` hand-off. On Claude-Code the ultracode workflow can run
441
+ it as the **opt-in** Step-10 (`args.deliveryGate: true` — off by default); `dz delivery-check` is
442
+ the **portable `manual` form** that travels to every shell
443
+ target (Cursor/Codex/Gemini/…). The CLI runs the deterministic parts (artifact probes, fail-closed
444
+ hand-off arithmetic, cross-validation bookkeeping) and **dispatches** the semantic review to the
445
+ target's own agent runtime — the `dz challenge` cartridge shape. It never posts anything anywhere
446
+ (findings-only).
447
+
448
+ ```bash
449
+ dz delivery-check --slug my-feature --context-only # print artifact probes + the 4-plane review brief
450
+ dz delivery-check --slug my-feature --findings review.json # classify a fed-back review → writes 10_delivery_review.md, prints ready|blocked
451
+ dz delivery-check --slug my-feature --findings review.json --strict --json # CI gate: exit 1 on blocked, machine-readable result
452
+ ```
453
+
454
+ Example (trimmed):
455
+
456
+ ```
457
+ dz delivery-check — my-feature: blocked
458
+ ✓ 0 BLOCKER: PASS
459
+ ✗ 0 HIGH: FAIL — 1 confirmed HIGH
460
+ ✓ planes complete: PASS
461
+ ✓ BLOCKER/HIGH cross-validated: PASS
462
+ ✓ required artifacts present: PASS
463
+ ↳ wrote features/my-feature/10_delivery_review.md
464
+ ```
465
+
466
+ `ready` only off complete, cross-validated, clean evidence: a null/partial plane, a failed required
467
+ probe (the `07_code_changes/` manifest), a confirmed BLOCKER/HIGH, or an **un-cross-validated**
468
+ BLOCKER/HIGH (`cross-validation-incomplete`) all yield `blocked`. Classification reads only numeric
469
+ severity counts, so instruction-like text inside a finding cannot move the verdict.
470
+
471
+ **When to use:** as the final quality hand-off for a landed feature on any non-Claude-Code target;
472
+ as a CI gate (`--strict`) that fails a merge on an unresolved BLOCKER/HIGH.
473
+
474
+ For AGENTS.md-class targets, `dz feature-adr-setup --gates [--target <name>] --apply` emits a
475
+ zero-config `architecture/gates/delivery-check.md` whose runnable-here gate list is computed from
476
+ `dz parity` for that target (create-if-absent, never clobbered).
477
+
402
478
  ### Verified release — `dz release` (4 HARD gates in front of `dz publish`)
403
479
 
404
480
  `dz publish` gates on guard / claim-check / signatures / provenance / files-whitelist — none of which
@@ -508,7 +584,7 @@ Get the whole set with `dz init --target claude-code --preset meta`, or pick one
508
584
 
509
585
  > **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.
510
586
 
511
- ## All Commands (56)
587
+ ## All Commands (57)
512
588
 
513
589
  ```
514
590
  dz setup --target <name> [--preset <name>] [--select id,id,...] [--skills-dir <dir>] [--memory agentdb] [--no-memory] [--no-hooks] [--install-driver] [--force]
@@ -563,6 +639,7 @@ dz challenge --plan <plan.md> [--json] [--context-only] [--author <model>] # a
563
639
  dz routing [--stage <s>] [--json] # inspect the learned cost-optimal routing store: what `args.models.<stage>='auto-cost'` believes per (stage, complexity-tier, model) — gated attempts/successes/rate (feeds feature-adr model selection)
564
640
  dz bto-optimize --split|--plan|--select|--scope-check|--diff [--json] # deterministic engine behind /bto-optimize: hold-out split + hard-capped budget + no-regress-on-holdout winner selection (defeats judge-gaming); prose-only, diff-confirmed, never auto-writes
565
641
  dz discrimination-check --test <f[,f]> [--base <ref>] [--name <filter>] [--runner <cmd>] [--json] # §42 test-discrimination gate for feature-adr Step-8: run the ADR's property test in an isolated git worktree at pre-feature base — it MUST go red without the fix; a green is a false green (HIGH finding, advisory, never auto-aborts)
642
+ 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
566
643
  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
567
644
  dz sbom --pack <dir> [--out <file>] # emit the CycloneDX 1.5 SBOM for a pack standalone (file-level bill of materials); print to stdout or write to a file
568
645
  dz guard check --op <publish|teach|consolidate|reindex> [--text <s>] [--json] [--force <reason>] # declarative constraint layer before self-mutating ops: HARD violation → block (exit 1), SOFT → warn; zero-config defaults, .dz/guard.json to customise; dz guard --init | dz guard log (append-only audit). dz publish runs it automatically (--no-guard "<reason>" = logged escape hatch)
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;AAgQH,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;AAs0K9E,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE,KAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAgJ5E"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA8QH,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;AAyiL9E,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE,KAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAkJ5E"}
package/dist/cli.js CHANGED
@@ -9,7 +9,7 @@ import { fileURLToPath } from 'node:url';
9
9
  import { execSync } 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, 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, 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, } from '@dzhechkov/harness-core';
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, 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, } from '@dzhechkov/harness-core';
13
13
  import { getPreset, PRESET_NAMES } from '@dzhechkov/harness-presets';
14
14
  import { scanGitHub, analyzeRepo, generateReport, deepAnalyze, scanAllSources, ScoutMemory } from '@dzhechkov/scout';
15
15
  const USAGE = `dz - DZ cross-platform harness CLI
@@ -34,11 +34,12 @@ Usage:
34
34
  dz publish [--filter <name>] [--bump-only] [--claim-check <off|warn|error>] [--require-signing] [--provenance|--no-provenance] (dry-run by default; pass --yes/--confirm/--no-dry-run to go live; claim-check gate default warn — surfaces README claim findings, never blocks; error fails an offending package)
35
35
  dz release [--filter <name>] [--tag] [--publish] [--json] [--dry-run] [--no-issue] (VERIFIED release: 4 HARD gates in FRONT of dz publish — full package test suites, audit >=high, node --check of every dist/bin file, bin smoke-boot via "node <bin> --help" — any red gate STOPS the release (exit 1) + best-effort gh issue; all green ⇒ re-sign reminder, then prints the ready dz publish command (or chains with --publish); never duplicates publish's own gates)
36
36
  dz parity [--target <name>] [--json] (the honest feature×target map, COMPUTED from the capability model — which harness feature is full / manual / absent on each of the ${TARGET_NAMES.length} targets, and via which form)
37
+ 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)
37
38
  dz setup --target <name> [--preset <name>] [--select id,id,...] [--skills-dir <dir>] [--project <dir>] [--memory agentdb] [--no-memory] [--no-hooks] [--install-driver] [--force] [--enrich]
38
39
  dz teach "<pattern>" [--reward <0-1>] [--domain <name>] [--type rule|success-pattern|lesson-learned] [--project <dir>] [--no-mirror] (--project pins the learned store to <dir>/.dz, not the cwd — pin to a canonical brain)
39
40
  dz teach --from-json <file> [--project <dir>] [--no-mirror] (bulk-import a 'dz recall --all --json' export — share a learned store across machines)
40
- dz consolidate [--sessions-dir <dir>] [--project <dir>] [--no-mirror] [--prune-noise [--apply]] (prune-noise: DRY-RUN by default; --apply snapshots then deletes)
41
- dz recall "<query>" [--limit <N>] [--semantic | --no-semantic] [--books [--book <slug>]] [--project <dir>] | dz recall --all [--json] | dz recall --usage [--json] | dz recall --forget <dzId>[,<dzId>] [--apply] (forget: dry-run default; snapshots before removing)
41
+ dz consolidate [--sessions-dir <dir>] [--project <dir>] [--no-mirror] [--prune-noise [--apply]] [--prune-quarantine [--apply]] (both prunes: DRY-RUN by default; --apply snapshots then deletes; prune-quarantine = expired unproven lessons ONLY, never coupled to noise)
42
+ dz recall "<query>" [--limit <N>] [--semantic | --no-semantic] [--books [--book <slug>]] [--project <dir>] | dz recall --all [--json] | dz recall --usage [--json] | dz recall --forget <dzId>[,<dzId>] [--apply] | dz recall --promote <dzId>[,<dzId>] [--apply] (forget/promote: dry-run default; forget snapshots before removing; promote lifts lesson-quarantine)
42
43
  dz vector status [--project <dir>] [--json] (semantic tier: engine, mirrored vs lexical counts, pending queue)
43
44
  dz vector reindex [--project <dir>] [--json] (snapshot, re-embed learned-pattern vectors, stamp current model)
44
45
  dz vector export <path> [--project <dir>] (portable VECTOR form (.rvf, opt-in RVF engine); patterns ship via recall --all --json)
@@ -1198,6 +1199,18 @@ async function cmdTeach(options, flags, cwd, write) {
1198
1199
  if (receipt.mirrored > 0)
1199
1200
  write(` ↳ mirrored to vector tier (${receipt.engine ?? 'vector'})`);
1200
1201
  };
1202
+ // lesson-quarantine FR-8: the fresh-teach mirror carries the qStatus marker so the hook daemon
1203
+ // (which reads only the mirror's metadata) can exclude unproven lessons from auto-inject.
1204
+ const emitMirrorQ = async (root, records, source, quarantined) => {
1205
+ if (flags.has('no-mirror') || records.length === 0 || !vectorMirrorEnabled(root))
1206
+ return;
1207
+ const entries = records
1208
+ .map((r) => patternVectorEntry(r, source, quarantined ? { quarantined: true } : {}))
1209
+ .filter((e) => e !== undefined);
1210
+ const receipt = await mirrorEntriesToVector(root, entries);
1211
+ if (receipt.mirrored > 0)
1212
+ write(` ↳ mirrored to vector tier (${receipt.engine ?? 'vector'})${quarantined ? ' [quarantined]' : ''}`);
1213
+ };
1201
1214
  // `dz teach --harmonize` — documented ALIAS of `dz vector harmonize`: SEMANTIC dedup of the
1202
1215
  // learned store. ONE implementation (harmonizeVectorStore), two entry points (QR-6). Routed
1203
1216
  // BEFORE the --from-json / single-teach paths. Dry-run by default; --apply after a backup.
@@ -1277,6 +1290,10 @@ async function cmdTeach(options, flags, cwd, write) {
1277
1290
  const trained = await backend.train();
1278
1291
  if (trained.flushed > 0) {
1279
1292
  write(`↳ reinforced ${reinforce}`);
1293
+ // lesson-quarantine: reinforcement IS promotion — keep the hook daemon's mirror in step.
1294
+ const clearedQ = clearAgentdbQuarantine(projectRoot, [reinforce]);
1295
+ if (clearedQ.cleared > 0)
1296
+ write(` ↳ promoted out of quarantine (mirror updated)`);
1280
1297
  return 0;
1281
1298
  }
1282
1299
  // HIGH-fix: a no-match must NOT auto-teach the raw argument — callers pass dzIds or truncated
@@ -1308,6 +1325,9 @@ async function cmdTeach(options, flags, cwd, write) {
1308
1325
  // teach below so the lesson is NEVER silently discarded (the exact silent-drop the ADR forbids).
1309
1326
  if (trained.flushed > 0) {
1310
1327
  write(`↳ reinforced existing pattern ${verdict.dzId} (cos=${verdict.cosine.toFixed(2)}) — not re-added`);
1328
+ const clearedQ = clearAgentdbQuarantine(projectRoot, [verdict.dzId]);
1329
+ if (clearedQ.cleared > 0)
1330
+ write(' ↳ promoted out of quarantine (mirror updated)');
1311
1331
  return 0;
1312
1332
  }
1313
1333
  write(`dz teach --guard: reinforce of ${verdict.dzId} did not flush (backend off or write failure) — teaching the lesson normally instead`);
@@ -1334,18 +1354,45 @@ async function cmdTeach(options, flags, cwd, write) {
1334
1354
  // Tier-2 (ADR-005): persist through the unified @dzhechkov/memory store. recordPattern
1335
1355
  // folds any legacy .dz/patterns.jsonl into the backend (idempotent) and returns the
1336
1356
  // total count. The lossy `npx agentdb add` dual-write was removed in Tier-1 (audit #6).
1337
- const count = await recordPattern(projectRoot, entry);
1357
+ // lesson-quarantine (opt-in): a fresh lesson is a HYPOTHESIS until it earns promotion.
1358
+ const quarantineOn = readMemoryLearningConfig(projectRoot).quarantine;
1359
+ const count = await recordPattern(projectRoot, entry, quarantineOn ? { quarantine: true } : {});
1338
1360
  write(`Learned: "${pattern.slice(0, 60)}${pattern.length > 60 ? '...' : ''}"`);
1339
1361
  write(` Domain: ${domain} Reward: ${reward} Backend: memory (@dzhechkov/memory)`);
1340
1362
  write(` Total patterns: ${count}`);
1363
+ if (quarantineOn) {
1364
+ write(' ⚠ quarantined: excluded from auto-inject, damped in recall — promote by confirming it (dz teach --reinforce "<text>") or dz recall --promote <dzId> --apply');
1365
+ }
1341
1366
  // The lexical write above is durable — the vector mirror is strictly best-effort (I-3).
1342
- await emitMirror(projectRoot, [entry], 'dz-teach');
1367
+ await emitMirrorQ(projectRoot, [entry], 'dz-teach', quarantineOn);
1343
1368
  return 0;
1344
1369
  }
1345
1370
  async function cmdConsolidate(options, flags, cwd, write) {
1346
1371
  const projectRoot = resolve(cwd, options.get('project') ?? '.');
1347
1372
  const sessionsDirOpt = options.get('sessions-dir');
1348
1373
  const pruneNoise = flags.has('prune-noise');
1374
+ // lesson-quarantine FR-7: expiry is a SEPARATE, explicit surface — never coupled to prune-noise
1375
+ // (the recalled decay-vs-noise lesson: valid-but-unproven is not garbage). Dry-run by default.
1376
+ if (flags.has('prune-quarantine')) {
1377
+ const cfg = readMemoryLearningConfig(projectRoot);
1378
+ const res = pruneQuarantinePatterns(projectRoot, { dryRun: !flags.has('apply'), expireDays: cfg.quarantineExpireDays });
1379
+ if (!flags.has('apply')) {
1380
+ write(`dz consolidate --prune-quarantine: DRY RUN — ${res.candidates.length} expired quarantined lesson(s) (> ${cfg.quarantineExpireDays}d, never reinforced)`);
1381
+ for (const c of res.candidates)
1382
+ write(` [${c.ageDays < 0 ? '?' : c.ageDays}d] ${c.dzId} ${c.text.slice(0, 70)}`);
1383
+ if (res.candidates.length > 0)
1384
+ write(' → re-run with --apply to remove (snapshots first); or promote keepers: dz recall --promote <dzId> --apply');
1385
+ return 0;
1386
+ }
1387
+ if (res.error !== undefined) {
1388
+ write(`dz consolidate --prune-quarantine: ${res.error}`);
1389
+ return 1;
1390
+ }
1391
+ write(`dz consolidate --prune-quarantine: removed ${res.removed} expired quarantined lesson(s)`);
1392
+ if (res.snapshot !== undefined)
1393
+ write(` snapshot: ${res.snapshot}`);
1394
+ return 0;
1395
+ }
1349
1396
  // --prune-noise: RETRO-PRUNE legacy noise (tool telemetry + system-wrapper "responses") from
1350
1397
  // the lexical store AND the agentdb vector mirror BEFORE harvesting, so this run's watermark
1351
1398
  // never re-learns from junk. Best-effort — a prune error is reported, never fatal.
@@ -1571,6 +1618,44 @@ async function cmdRecallForget(options, flags, projectRoot, write) {
1571
1618
  write(' the vector mirror still holds them — run `dz vector reindex` to resync');
1572
1619
  return 0;
1573
1620
  }
1621
+ /**
1622
+ * `dz recall --promote <dzId>[,<dzId>…] [--apply]` — lift quarantine from NAMED records
1623
+ * (lesson-quarantine FR-6b). Dry-run by default, the --forget symmetry. Also clears the
1624
+ * agentdb mirror's qStatus (best-effort) so the hook daemon stops excluding promoted lessons.
1625
+ */
1626
+ async function cmdRecallPromote(options, flags, projectRoot, write) {
1627
+ const ids = (options.get('promote') ?? '').split(',').map((s) => s.trim()).filter((s) => s.length > 0);
1628
+ if (ids.length === 0) {
1629
+ write('dz recall --promote: no ids given (comma-separated dzIds; quarantined ones are marked ⚠q in recall)');
1630
+ return 1;
1631
+ }
1632
+ if (!flags.has('apply')) {
1633
+ const records = loadStoreRecords(projectRoot);
1634
+ const found = ids.filter((id) => records.some((r) => r.id === id));
1635
+ write(`dz recall --promote: DRY RUN — ${found.length}/${ids.length} id(s) match the store. Re-run with --apply to promote.`);
1636
+ return 0;
1637
+ }
1638
+ const res = await promotePatterns(projectRoot, ids);
1639
+ // Codex-QE finding 5: even on a mid-batch failure, the ALREADY-promoted records must get their
1640
+ // mirror rows cleared — otherwise the canonical store and the hook-visible mirror split-brain.
1641
+ if (res.promoted.length > 0) {
1642
+ const cleared = clearAgentdbQuarantine(projectRoot, res.promoted);
1643
+ if (cleared.cleared > 0)
1644
+ write(` ↳ mirror updated (${cleared.cleared} row(s) un-quarantined in agentdb)`);
1645
+ else if (cleared.error !== undefined)
1646
+ write(` ⚠ mirror not updated (${cleared.error}) — run dz vector reindex to resync`);
1647
+ }
1648
+ if (!res.ok) {
1649
+ write(`dz recall --promote: failed — ${res.error ?? 'unknown error'} (promoted so far: ${res.promoted.length})`);
1650
+ return 1;
1651
+ }
1652
+ write(`dz recall --promote: promoted ${res.promoted.length} record(s)`);
1653
+ if (res.notQuarantined.length > 0)
1654
+ write(` already promoted (not quarantined): ${res.notQuarantined.join(', ')}`);
1655
+ if (res.notFound.length > 0)
1656
+ write(` not found: ${res.notFound.join(', ')}`);
1657
+ return 0;
1658
+ }
1574
1659
  async function cmdRecall(options, flags, cwd, write) {
1575
1660
  const projectRoot = resolve(cwd, options.get('project') ?? '.');
1576
1661
  const asJson = flags.has('json');
@@ -1579,6 +1664,8 @@ async function cmdRecall(options, flags, cwd, write) {
1579
1664
  return cmdRecallUsage(options, flags, projectRoot, write);
1580
1665
  if (options.has('forget'))
1581
1666
  return cmdRecallForget(options, flags, projectRoot, write);
1667
+ if (options.has('promote'))
1668
+ return cmdRecallPromote(options, flags, projectRoot, write);
1582
1669
  // --all: dump the entire learned store (backend-agnostic, via loadStorePatternsSync).
1583
1670
  // With --json this is the portable export the agentdb-memory MCP bridge consumes.
1584
1671
  if (all) {
@@ -1678,9 +1765,17 @@ async function cmdRecall(options, flags, cwd, write) {
1678
1765
  const lexLabel = result.lexicalBackend === 'sqlite' ? 'SQLite FTS5' : 'keyword (JSON)';
1679
1766
  const ranking = vectorOn ? `${lexLabel} + vector (${result.vectorEngine}) ranking` : `${lexLabel} ranking (lexical)`;
1680
1767
  write(`dz recall "${query}" — ${hits.length} hit(s), ${ranking}`);
1768
+ let sawQuarantined = false;
1681
1769
  for (const h of hits) {
1682
1770
  const backendTag = vectorOn ? ` ⟨${h.backend}⟩` : '';
1683
- write(` [${h.pattern.reward.toFixed(2)}] (${h.pattern.domain})${backendTag} ${h.pattern.pattern.slice(0, 80)}`);
1771
+ const qTag = h.quarantined === true ? ' ⚠q' : '';
1772
+ if (h.quarantined === true)
1773
+ sawQuarantined = true;
1774
+ write(` [${h.pattern.reward.toFixed(2)}] (${h.pattern.domain})${backendTag}${qTag} ${h.pattern.pattern.slice(0, 80)}`);
1775
+ }
1776
+ if (sawQuarantined) {
1777
+ // The loop stays VISIBLE (ADR D2): a quarantined hit is shown, marked, and explained.
1778
+ write(' ⚠q = quarantined (unproven hypothesis, rank damped) — confirm with dz teach --reinforce, or dz recall --promote <dzId> --apply');
1684
1779
  }
1685
1780
  if (result.vectorError !== undefined && mode !== 'lexical') {
1686
1781
  // Engine present but the semantic leg failed/timed out — one honest line, exit 0 (05 §2.3).
@@ -4905,8 +5000,17 @@ function cmdFeatureAdrSetup(options, flags, cwd, write) {
4905
5000
  write('dz feature-adr-setup: --loc-cap must be a positive finite number');
4906
5001
  return 1;
4907
5002
  }
5003
+ // portable-gates (direction b): --gates scaffolds the zero-config architecture/gates/delivery-check.md.
5004
+ // Its "runnable here" list is computed for --target (default agents-md, the AGENTS.md-class target class).
5005
+ const wantGates = flags.has('gates');
5006
+ const targetOpt = options.get('target');
5007
+ if (targetOpt !== undefined && !isTargetName(targetOpt)) {
5008
+ write(`dz feature-adr-setup: --target must be one of: ${TARGET_NAMES.join(', ')}`);
5009
+ return 1;
5010
+ }
5011
+ const gatesTarget = isTargetName(targetOpt ?? '') ? targetOpt : 'agents-md';
4908
5012
  const specPath = options.get('from-spec');
4909
- if (specPath === undefined && !wantGuards) {
5013
+ if (specPath === undefined && !wantGuards && !wantGates) {
4910
5014
  // default + --plan: the read-only "which documents, and where?" answer.
4911
5015
  const plan = buildSetupPlan(scanForSetup(repoRoot));
4912
5016
  if (flags.has('json')) {
@@ -4944,7 +5048,9 @@ function cmdFeatureAdrSetup(options, flags, cwd, write) {
4944
5048
  }
4945
5049
  if (wantGuards)
4946
5050
  spec = { ...spec, guards: locCap !== undefined ? { locCap } : true };
4947
- const result = scaffoldFromSpec(spec, readExistingForScaffold(repoRoot));
5051
+ if (wantGates)
5052
+ spec = { ...spec, gates: true };
5053
+ const result = scaffoldFromSpec(spec, readExistingForScaffold(repoRoot), gatesTarget);
4948
5054
  if (flags.has('json') && !flags.has('apply')) {
4949
5055
  write(JSON.stringify(result, null, 2));
4950
5056
  return 0;
@@ -5182,6 +5288,149 @@ function cmdDiscriminationCheck(options, flags, cwd, write) {
5182
5288
  function nameFor(t, outcome) {
5183
5289
  return t.name !== undefined ? { file: t.file, name: t.name, outcome } : { file: t.file, outcome };
5184
5290
  }
5291
+ /**
5292
+ * `dz delivery-check` — the portable Step-10 Delivery Gate (feature portable-gates). The `manual` form that
5293
+ * travels to every `shell` target: the deterministic parts (artifact probes, hand-off arithmetic,
5294
+ * cross-validation bookkeeping) run IN the CLI; the semantic 4-plane review is DISPATCHED to the caller's own
5295
+ * agent runtime — the `dz challenge` cartridge shape. All logic lives in harness-core's pure engine; this is
5296
+ * the executor. Advisory exit codes (0 by default; `--strict` ⇒ 1 on `blocked`).
5297
+ *
5298
+ * --slug <slug> the feature under features/<slug> (required; featureDir derived internally)
5299
+ * --context-only default mode: print artifact probes + the 4-plane review brief (no verdict)
5300
+ * --findings <f.json> classify a fed-back findings array → write 10_delivery_review.md + print ready/blocked
5301
+ * --strict with --findings: exit 1 iff handoff === 'blocked' (CI gate)
5302
+ * --author <model> cosmetic: the reviewer to dispatch, printed alongside the brief
5303
+ * --json machine contract { planesChecked, planesSkipped, findings, handoff, artifact }
5304
+ */
5305
+ function cmdDeliveryCheck(options, flags, cwd, write) {
5306
+ let repoRoot = cwd;
5307
+ try {
5308
+ repoRoot = execSync('git rev-parse --show-toplevel', { cwd, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }).trim() || cwd;
5309
+ }
5310
+ catch { /* not git */ }
5311
+ const json = flags.has('json');
5312
+ const errOut = (error) => {
5313
+ if (json)
5314
+ write(JSON.stringify({ error, planesChecked: [], planesSkipped: [], findings: [], handoff: null, artifact: 'not-written' }, null, 2));
5315
+ else {
5316
+ write(`dz delivery-check: ${error}`);
5317
+ write(' allowed: --slug <slug>, --findings <f.json>, --author <model>, --context-only, --strict, --json');
5318
+ }
5319
+ return 1;
5320
+ };
5321
+ // Strict allowlist (delivery finding: a typo'd --findings silently downgraded the --strict CI
5322
+ // gate to a vacuous exit-0 pass — a mistyped option must be an ERROR, never a mode change).
5323
+ const allowedFlags = new Set(['context-only', 'strict', 'json', 'help']);
5324
+ const allowedOptions = new Set(['slug', 'findings', 'author']);
5325
+ for (const flag of flags)
5326
+ if (!allowedFlags.has(flag))
5327
+ return errOut(`unknown option --${flag}`);
5328
+ for (const key of options.keys()) {
5329
+ if (key.startsWith('_positional_'))
5330
+ return errOut(`unexpected argument "${options.get(key)}"`);
5331
+ if (!allowedOptions.has(key))
5332
+ return errOut(`unknown option --${key}`);
5333
+ }
5334
+ const slug = options.get('slug');
5335
+ if (slug === undefined || slug.trim() === '')
5336
+ return errOut('pass --slug <slug> (the feature under features/<slug>)');
5337
+ // Slug convention is a SECURITY boundary here (delivery finding, CONFIRMED traversal reproducer:
5338
+ // --slug '../../outside/evil' wrote 10_delivery_review.md outside the repo). kebab-case Latin,
5339
+ // <=40 chars — the repo's own slug rule as a layer-1 three-line check.
5340
+ const slugTrimmed = slug.trim();
5341
+ if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(slugTrimmed) || slugTrimmed.length > 40) {
5342
+ return errOut(`invalid --slug "${slugTrimmed}" — kebab-case Latin, max 40 chars (a path-like slug would redirect the report write)`);
5343
+ }
5344
+ const featureDir = join(repoRoot, 'features', slugTrimmed);
5345
+ // --context-only is a real mode flag, not decoration: combining it with --findings contradicts
5346
+ // itself (delivery finding — the flag was accepted and silently ignored).
5347
+ if (flags.has('context-only') && options.get('findings') !== undefined) {
5348
+ return errOut('--context-only and --findings are mutually exclusive (context-only prints the brief; findings classifies the verdict)');
5349
+ }
5350
+ // git status --porcelain — CLI layer only (the `no child_process in core` contract); a non-git target
5351
+ // degrades to an empty list, which is honest (AM-10: changed-files is informational, never a required gate).
5352
+ let changedFiles = [];
5353
+ try {
5354
+ const out = execSync('git status --porcelain', { cwd: repoRoot, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
5355
+ changedFiles = out.split('\n').map((l) => l.slice(3).trim()).filter((s) => s.length > 0);
5356
+ }
5357
+ catch { /* non-git / unavailable → empty (honest) */ }
5358
+ const facts = collectDeliveryFacts(featureDir, { changedFiles, repoRoot });
5359
+ const plan = planDeliveryCheck(facts);
5360
+ const author = options.get('author');
5361
+ const findingsPath = options.get('findings');
5362
+ if (findingsPath === undefined) {
5363
+ // Default / --context-only: emit the led protocol (probes + brief). No verdict — the CLI has no model.
5364
+ if (json) {
5365
+ write(JSON.stringify({
5366
+ planesChecked: [],
5367
+ planesSkipped: plan.planes.map((p) => p.id),
5368
+ findings: [],
5369
+ handoff: null,
5370
+ artifact: 'not-written',
5371
+ probes: plan.probes,
5372
+ }, null, 2));
5373
+ return 0;
5374
+ }
5375
+ write(`dz delivery-check — ${slug} (portable 4-plane hand-off protocol; you drive the review)`);
5376
+ for (const p of plan.probes) {
5377
+ const mark = p.passed === true ? '✓' : p.passed === false ? '✗' : '○';
5378
+ write(` ${mark} ${p.description}${p.required ? ' [required]' : ' [informational]'}`);
5379
+ }
5380
+ write('');
5381
+ write(renderDeliveryBrief(plan, facts));
5382
+ // The hand-off criterion the verdict will be judged by, shown UP FRONT (this also makes
5383
+ // plan.criterionTemplate a consumed surface, not dead product code — delivery finding).
5384
+ write('\n── hand-off criterion (all must PASS for ready) ──');
5385
+ for (const c of plan.criterionTemplate)
5386
+ write(` ○ ${c.label}`);
5387
+ if (author !== undefined && author.trim() !== '')
5388
+ write(`\n── reviewer to dispatch ──\n${author.trim()}`);
5389
+ write('\n(next: run the four planes, then `dz delivery-check --slug ' + slug + ' --findings findings.json` for the verdict)');
5390
+ return 0;
5391
+ }
5392
+ // --findings mode: parse (malformed/missing ⇒ [], a failed-plane input — fail-closed, never a throw).
5393
+ let reviewResults = [];
5394
+ try {
5395
+ const parsed = JSON.parse(readFileSync(resolve(cwd, findingsPath), 'utf-8'));
5396
+ if (Array.isArray(parsed))
5397
+ reviewResults = parsed;
5398
+ else if (parsed !== null && typeof parsed === 'object' && Array.isArray(parsed.planes))
5399
+ reviewResults = parsed.planes;
5400
+ else
5401
+ reviewResults = [];
5402
+ }
5403
+ catch {
5404
+ reviewResults = [];
5405
+ }
5406
+ const verdict = classifyDelivery(plan, reviewResults);
5407
+ // The SAME predicate as the fail-closed verdict (core-exported) — a local copy was a drift channel.
5408
+ const planesChecked = plan.planes.filter((_, i) => isUsablePlaneResult(reviewResults[i])).map((p) => p.id);
5409
+ const planesSkipped = plan.planes.filter((_, i) => !isUsablePlaneResult(reviewResults[i])).map((p) => p.id);
5410
+ // Write the regenerable report (NOT augment-never-clobber — deliberately overwritten each run).
5411
+ let artifact = 'not-written';
5412
+ try {
5413
+ const reviewPath = join(featureDir, '10_delivery_review.md');
5414
+ mkdirSync(dirname(reviewPath), { recursive: true });
5415
+ writeFileSync(reviewPath, renderDeliveryReview(verdict, facts));
5416
+ artifact = 'written';
5417
+ }
5418
+ catch { /* best-effort — the verdict is still returned */ }
5419
+ if (json) {
5420
+ write(JSON.stringify({ planesChecked, planesSkipped, findings: verdict.findings, handoff: verdict.handoff, artifact }, null, 2));
5421
+ }
5422
+ else {
5423
+ write(`dz delivery-check — ${slug}: ${verdict.handoff}`);
5424
+ for (const c of verdict.criterion) {
5425
+ const mark = c.status === 'PASS' ? '✓' : c.status === 'FAIL' ? '✗' : '○';
5426
+ write(` ${mark} ${c.label}: ${c.status}${c.detail ? ` — ${c.detail}` : ''}`);
5427
+ }
5428
+ write(artifact === 'written' ? `\n↳ wrote features/${slug}/10_delivery_review.md` : '\n↳ 10_delivery_review.md NOT written (verdict above is authoritative)');
5429
+ }
5430
+ if (flags.has('strict') && verdict.handoff === 'blocked')
5431
+ return 1;
5432
+ return 0;
5433
+ }
5185
5434
  /**
5186
5435
  * `dz routing` — inspect the learned cost-optimal routing outcome store (feature learned-cost-routing). Shows
5187
5436
  * what `args.models[stage]='auto-cost'` currently believes per (stage, complexity-tier, model): gated
@@ -5619,6 +5868,8 @@ export async function runCli(argv, io = {}) {
5619
5868
  return cmdChallenge(options, flags, cwd, write);
5620
5869
  case 'discrimination-check':
5621
5870
  return cmdDiscriminationCheck(options, flags, cwd, write);
5871
+ case 'delivery-check':
5872
+ return cmdDeliveryCheck(options, flags, cwd, write);
5622
5873
  case 'routing':
5623
5874
  return cmdRouting(options, flags, cwd, write);
5624
5875
  case 'bto-optimize':