@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 +10 -10
- package/README.md +79 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +259 -8
- package/dist/cli.js.map +1 -1
- package/package.json +2 -2
- package/sbom.json +9 -9
- package/src/cli.ts +250 -7
package/.dz-manifest.json
CHANGED
|
@@ -9,15 +9,15 @@
|
|
|
9
9
|
},
|
|
10
10
|
{
|
|
11
11
|
"path": "README.md",
|
|
12
|
-
"sha256": "
|
|
12
|
+
"sha256": "ef6603652143fc1517c81506dad9d5e7b79e5d5138aad9f6281ad77492d58b71"
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
"path": "coverage/coverage-final.json",
|
|
16
|
-
"sha256": "
|
|
16
|
+
"sha256": "667bf610bf890a7b93480a4b1045e98dc0c765effd80b78fde4e48538f595b3e"
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
"path": "coverage/coverage-summary.json",
|
|
20
|
-
"sha256": "
|
|
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": "
|
|
44
|
+
"sha256": "8f8807920c7ddd901361eb5a602df9c85fa69726e47535a146937038eab6b281"
|
|
45
45
|
},
|
|
46
46
|
{
|
|
47
47
|
"path": "dist/cli.js",
|
|
48
|
-
"sha256": "
|
|
48
|
+
"sha256": "5f5fbcc7c040a272f6af992ef1f9c39482b941d74dbcadec9f3d9008babcb08a"
|
|
49
49
|
},
|
|
50
50
|
{
|
|
51
51
|
"path": "dist/cli.js.map",
|
|
52
|
-
"sha256": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
92
|
+
"sha256": "3deb99917f3d8b4a5c9a3b0315bd65a3099ce2b508121cfa2bdeed19b7554d48"
|
|
93
93
|
},
|
|
94
94
|
{
|
|
95
95
|
"path": "tsconfig.json",
|
|
@@ -101,5 +101,5 @@
|
|
|
101
101
|
}
|
|
102
102
|
]
|
|
103
103
|
},
|
|
104
|
-
"signature": "
|
|
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
|
-
|
|
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 (
|
|
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;
|
|
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]] (
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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':
|