@dzhechkov/harness-cli 0.5.4 → 0.6.1

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
@@ -5,11 +5,11 @@
5
5
  "files": [
6
6
  {
7
7
  "path": "CHANGELOG.md",
8
- "sha256": "9555ead768fef7963e735b0c6af21a64412811531534108be3df61bc65b51021"
8
+ "sha256": "b99db6be2d9e6d46f0a9d3ff0877c445efac7b9e90d5fe1db9f5bfa2cb6dc107"
9
9
  },
10
10
  {
11
11
  "path": "README.md",
12
- "sha256": "0d475969dbc98308bf0e23a15700c537ed4947b3cf910b334e647e3972fc78fe"
12
+ "sha256": "696aae63a95e8f8726eecc1aa0e68f20d7980e99bb4008b22f2e1058ca67c6c0"
13
13
  },
14
14
  {
15
15
  "path": "coverage/coverage-final.json",
@@ -41,19 +41,19 @@
41
41
  },
42
42
  {
43
43
  "path": "dist/cli.d.ts.map",
44
- "sha256": "7c64ed0d976d79fd8f3970ced13e664db1e97c43f96a7b153d7b993948cc5f9f"
44
+ "sha256": "8d15372b27f7d79f54df09671b01d5944e8f3f3d87075ba4ece7f0131fc1103e"
45
45
  },
46
46
  {
47
47
  "path": "dist/cli.js",
48
- "sha256": "8052367d84a5a1c4a9f7fb3397a6297edf707031bbfa59b945d240d17e221f04"
48
+ "sha256": "be3648deb68dca8a48cc323f34f54c77c05f0ddf0d86982ea9d6f2cc8e073349"
49
49
  },
50
50
  {
51
51
  "path": "dist/cli.js.map",
52
- "sha256": "f759949bcfaa04c3f4eaebbccde688192d44f129b61115313533f8828f7376f8"
52
+ "sha256": "47bcecfe2c9f1daeb73056b3a2c1e10e6b77c465c1bb0d6169690f2276a9cc77"
53
53
  },
54
54
  {
55
55
  "path": "dist/core-compat.d.ts",
56
- "sha256": "f89dbd282c83fa64f23c8fff3c83bc85c08c8deef451fd4d525e33784c2a0e13"
56
+ "sha256": "b735a6c18bfbe5adf17bbcfe6e2eeae71477b877e958c36e0a5e73e59f100d6a"
57
57
  },
58
58
  {
59
59
  "path": "dist/core-compat.d.ts.map",
@@ -61,7 +61,7 @@
61
61
  },
62
62
  {
63
63
  "path": "dist/core-compat.js",
64
- "sha256": "66fd34797f007aa0607698a159fb367a772f32f5be1c2a90748c20cbf4b973cf"
64
+ "sha256": "6d052e0a7352e9824bc1d4a319d03b42320f6551210f7dfd360afbdca4e556f0"
65
65
  },
66
66
  {
67
67
  "path": "dist/core-compat.js.map",
@@ -85,11 +85,15 @@
85
85
  },
86
86
  {
87
87
  "path": "keys/README.md",
88
- "sha256": "204deec657a7f147bf3016441082c2f868f6f11051776f4edc13ec6b44745192"
88
+ "sha256": "142fc5d0d59003476b8e7367d6fc21fe40ce9e0bf70159318a05abdb10502b8a"
89
+ },
90
+ {
91
+ "path": "keys/dz.pub",
92
+ "sha256": "353885160582f8e0cfb2a97ef9e51f77997f0120841f92a1ce50d202ebf5dd73"
89
93
  },
90
94
  {
91
95
  "path": "package.json",
92
- "sha256": "797bfd035a68dc039ca33393f02f4ed1fe97e1021559e01906a0fbbc9b28c1dc"
96
+ "sha256": "a43608842dab18914486c96c1989b294031f35118bb91c1e8bd0807041a10ddd"
93
97
  },
94
98
  {
95
99
  "path": "src/bin.ts",
@@ -97,11 +101,11 @@
97
101
  },
98
102
  {
99
103
  "path": "src/cli.ts",
100
- "sha256": "9338e5f5dd5cbb1f3692cb7db53531f103bc28450c109765bf161664a7598a5b"
104
+ "sha256": "d39fa1f437401419889965674889df3e5091d4ed76242e36f8e70f18683d4e4a"
101
105
  },
102
106
  {
103
107
  "path": "src/core-compat.ts",
104
- "sha256": "ba96d37f80c98618a1d8ca61cf071c9d26c74dab8d277106e9079068428a5b71"
108
+ "sha256": "5fef10b3d968d299eb7463157e8b164413872f7cc8bf5e9ca1c605e63569a9f7"
105
109
  },
106
110
  {
107
111
  "path": "src/index.ts",
@@ -111,13 +115,17 @@
111
115
  "path": "test/agents-sync.test.ts",
112
116
  "sha256": "32606890dc57d58257e6b4a38246acb343eea88b6e4b2f643f061325a34d066a"
113
117
  },
118
+ {
119
+ "path": "test/amendment-check-cli.test.ts",
120
+ "sha256": "3dd543fd3032f1fca5c063af98a7501b4cccb9d7e42f6c307074b67f2d55d231"
121
+ },
114
122
  {
115
123
  "path": "test/cli.test.ts",
116
124
  "sha256": "d9adcce84b509e00213bda59a0948eca90d83bdd2fdbbc86aee5ce11d9176940"
117
125
  },
118
126
  {
119
127
  "path": "test/command-count.test.ts",
120
- "sha256": "f744cfe02805f567312a196805ebc49486c3e2a8e3f64de1700be276481f0cc1"
128
+ "sha256": "d9677688116c0a35591cb7e53ab1502566e44336959ac0ea449f5e8a1c5e31e3"
121
129
  },
122
130
  {
123
131
  "path": "test/core-compat-guard.test.ts",
@@ -139,6 +147,10 @@
139
147
  "path": "test/epoch-replay-cli.test.ts",
140
148
  "sha256": "a5a48585b565288ca1ea96030b02b2ccb37a621ef7b3cfe54cd7aaa7164d6904"
141
149
  },
150
+ {
151
+ "path": "test/feature-adr-record-cli.test.ts",
152
+ "sha256": "e1a2687a1ae2bc2d32cbcc852487d30f9451ce1ea4a132e188186dd4251c49d1"
153
+ },
142
154
  {
143
155
  "path": "test/fixtures/discrimination-check/fake-runner.sh",
144
156
  "sha256": "126bbd1ba9c08ef9de826cfa21df3981720e4bc36cd993dff28e0de435cbebae"
@@ -255,13 +267,17 @@
255
267
  "path": "test/mutation-registry.json",
256
268
  "sha256": "3e6f9bb105154121988da41568ff85573099235857ba25e76c4264c166b414ae"
257
269
  },
270
+ {
271
+ "path": "test/packaged-trust-root.test.ts",
272
+ "sha256": "46c0af5494ad717eb4d936489f4bde2199b40cd756ab6c8adb0cd1714a971304"
273
+ },
258
274
  {
259
275
  "path": "test/parallel-worktree-smoke.test.ts",
260
276
  "sha256": "0522b12ccdf3db79215c4c42162ef970f4cba0c0af96208426ca0323c367d0ee"
261
277
  },
262
278
  {
263
279
  "path": "test/plan-completeness-gate.test.ts",
264
- "sha256": "be010af797aeae754a3dd3e9f6f25d6f01066d4bf2037a01322554453d943ee3"
280
+ "sha256": "fafa2d1ff56bd41a6496b7ce569be671f2f617deac07425f47ad6e87b3ec00ce"
265
281
  },
266
282
  {
267
283
  "path": "test/qe-bridge-cli.test.ts",
@@ -333,5 +349,5 @@
333
349
  }
334
350
  ]
335
351
  },
336
- "signature": "8n7bGx5j0Y+txpJ/aT6tprAsMiId19Wna+BUKC+G1/Vu3xOUAGWitkU4dvRjnmNgfyMmvC5VWjBYpzjS/dPaAQ=="
352
+ "signature": "yL9vp8bfrRx+/nOqnZuEZbW2ccdXFsSI5KEjED+FYQZxqCfx3aQ5KD6htiBIADjoOrKOUDVq7sg6ZcxxBsMCBg=="
337
353
  }
package/README.md CHANGED
@@ -281,7 +281,9 @@ dz help # see all commands
281
281
  dz pretrain # analyze project files → recommend by tech stack
282
282
  dz recommend "build API and deploy to K8s" # keyword match → skills + toolkits
283
283
  dz recommend "work on this project" # generic? → auto-runs pretrain → recommends by stack
284
- dz stats # 46 packages, 179 skills, 10 targets, 14 presets
284
+ dz stats # 54 packages, 200 skills, 10 targets, 11 presets
285
+ # NB: the preset figure is stats' own, and it UNDER-reports — the registry has 14
286
+ # (reasoning, news and pm are missing from stats). Backlog 6e6ae88d7f7660f6.
285
287
  dz dashboard # visual panel — packages, adapters, skill packs
286
288
  dz registry # browse all 179 skills by category
287
289
  dz registry search kubernetes # find specific skills
@@ -1735,9 +1737,35 @@ only at their documented scopes (plan + step), and `fanouts[].registry` items mu
1735
1737
  ItemKey domain the trace plane enforces — `trace.emit` can never decide whether a valid plan runs.
1736
1738
  Deferred options are on the loop-designer roadmap.
1737
1739
 
1738
- ## All Commands (69)
1740
+ ### `dz feature-adr-checkpoint` — record a pipeline stage only after WITNESSING it
1739
1741
 
1740
- *(69 MEASURED — reproducer: `grep -c "^ case '" src/cli.ts`, the dispatch cases.)*
1742
+ ```bash
1743
+ # refuses: the stage claims an artifact that is not on disk
1744
+ dz feature-adr-checkpoint --feature-dir ~/proj/features/add-auth \
1745
+ --stage plan --input-hash 8f21c0 --result '{"wrote":["06_implementation_plan.md"]}' \
1746
+ --artifact 06_implementation_plan.md
1747
+ # → dz feature-adr checkpoint: REFUSED — stage plan is missing its artifact(s): 06_implementation_plan.md (exit 1)
1748
+
1749
+ # records: the same call once the plan actually exists
1750
+ # → dz feature-adr checkpoint: recorded plan (witnessed 1 artifact(s)) (exit 0)
1751
+ ```
1752
+
1753
+ **When to use it:** you do not call this by hand in normal work — `/feature-adr` in workflow mode calls
1754
+ it after every expensive stage so a crashed run resumes instead of restarting. Reach for it directly
1755
+ only when repairing a run's `.fa-state/checkpoints.jsonl`.
1756
+
1757
+ **Why it is a command and not a line of shell.** The workflow script is sandboxed with no filesystem,
1758
+ so it used to hand a subagent a finished JSON line and say *"append this"*. The subagent verified
1759
+ nothing, and a safety classifier read that shape as one party instructing another to declare a
1760
+ verification gate complete — MEASURED 2026-08-21: nine consecutive writes blocked in one six-hour run,
1761
+ `.fa-state/` never created, resume silently dead, and the run still reported success. The subagent now
1762
+ runs a command that MEASURES the declared artifacts itself. It refuses a null result (a dead stage is
1763
+ never recorded as done), an absent or partially-present artifact set, and a stage that declares nothing
1764
+ to witness — so a stage that did not happen can no longer be recorded, which the old mechanism allowed.
1765
+
1766
+ ## All Commands (72)
1767
+
1768
+ *(72 MEASURED — reproducer: `grep -c "^ case '" src/cli.ts`, the dispatch cases.)*
1741
1769
 
1742
1770
  ```
1743
1771
  dz setup --target <name> [--preset <name>] [--select id,id,...] [--skills-dir <dir>] [--memory agentdb] [--no-memory] [--no-hooks] [--install-driver] [--force]
@@ -1793,6 +1821,8 @@ dz challenge --plan <plan.md> [--json] [--context-only] [--author <model>] # a
1793
1821
  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)
1794
1822
  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
1795
1823
  dz discrimination-check --test <f[,f]> [--base <ref>] [--name <filter>] [--runner <cmd>] [--timeout <ms>] [--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. SEVEN verdicts, each gated on EXECUTION evidence: DISCRIMINATES · DISCRIMINATES_VIA_ERROR · NON_DISCRIMINATING (false green) · TEST_FILE_ABSENT · LOAD_ERROR_AT_BOTH_REVS · FAILS_AT_TIP · CANNOT_ISOLATE (+ typed reason). Advisory, never auto-aborts
1824
+ dz amendment-check --slug <slug> | --feature-dir <dir> | --all [--json] (every AM-N amendment row must resolve to a test found INSIDE the file the row names; the PLAN is authoritative when it carries rows, and an ideation amendment the plan drops is a failure; --all is a census that always exits 0; exit 0 pass/skip, 1 fail, 3 NOT-ESTABLISHED — a section that parsed ZERO rows is never a pass)
1825
+ dz feature-adr-record --kind ledger|training-pair --stage <s> [--slug <s>] [--row|--pair <json>] [--mark <n>] [--once] [--json] (the witnessed writer: payload as an ARGUMENT never as shell, refused before any write, timestamp stamped before serialising, append verified by re-reading the tail; exit 0 written|duplicate|skipped, 2 refused, 3 not-verified, never blocking)
1796
1826
  dz mutation-gate [--package <dir>] [--registry <file>] [--test-cmd "<cmd>"] [--only <id[,id]>] [--timeout <ms>] [--rebaseline per-entry|final] [--keep-scratch] [--json] # the mutation gate: for each NAMED protection in a declarative registry, copy the package to a scratch dir (shadow-repo layout, node_modules symlinked, git-initialized), verify the baseline is green, apply the entry's exact {find, replace} mutation, run the suite, REQUIRE red, restore — and require the red to be ATTRIBUTABLE to the protection: a mutated file that no longer parses is MUTATION_UNPARSEABLE, a failing count far above the entry's bound (maxFailing, default from observed) is OVER_FAILING, and a restored tree that does not reproduce green makes the entry INCONCLUSIVE (flaky suite). A mutation that does not apply, a green suite, or an inconclusive run is a FAILURE — never a skip. exit 0 all proven / 1 gate failed / 2 setup error
1797
1827
  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
1798
1828
  dz skills-verify [--dir <project>] [--expect a,b] [--static] [--strict] [--timeout <s>] [--json] # does .claude/skills/ actually REGISTER? --static = instant layout scan (CI-safe, no session): flags dirs that can never register; default also starts a real session and reads the authoritative system/init listing. exit 0 pass / 1 fail / 2 inconclusive — an unobservable registration is NEVER a pass
@@ -1802,6 +1832,7 @@ dz epoch-replay --emit [--project <dir>] [--limit <N>] [--seed <N>] [--margin <
1802
1832
  dz epoch-replay --judge <filled-work-order.json> [--out <file>] [--json] # blind judge prompts from the filled plans — the file carries {id, prompt} ONLY (no assignment, no arm names); refuses an order whose digest or seed-derived assignment does not check out; half-pairs skipped WITH A REASON, on stdout
1803
1833
  dz epoch-replay --score <judgments.json> --work-order <file> [--slice <name>] [--json] # un-blind against the VERIFIED pre-registered assignment; ONE paired binomial over DECISIVE pairs (ties excluded, reported) → SUPPORTED only when the lift interval (2p−1) lies entirely above zero; FALSIFIED only on harm or a passed non-superiority test (lift upper bound below the margin PRE-REGISTERED in the work order, default 0.05, at 10+ decisive pairs); else INCONCLUSIVE (min 5 decisive pairs). Refuses a forged work order, a --margin flag, or duplicate judgement ids; the verdict is data, not an exit code
1804
1834
  dz score --slug <feature> [--project <dir>] [--json] # process scorecard for ONE feature-adr run, from its artifacts: ADR confirmation, discrimination proof, cross-model QE grade, live verification, README-first, learning loop, amendments — DESCRIPTIVE-ONLY (a low score exits 0); evidence lines are shown so the reader judges the heuristics
1835
+ dz feature-adr-checkpoint (--slug <feature> | --feature-dir <abs>) --stage <s> --input-hash <h> --result <json> [--artifact a,b] [--json]
1805
1836
  dz reqe [--slug <feature> [--done --report <f>]] [--project <dir>] [--json] # the re-QE debt ledger: a usage-switched feature-adr run whose Step-8 QE ran on the coder's OWN family (cross-model guard suspended, FR-2.9) records a debt; list debts (also surfaced by dz usage), print the cross-family review brief, settle FAIL-CLOSED against an existing GRADED report (the run's own 08_qe_report.md — even hard-linked — can never settle its own debt); settlement lands in 08_qe_report.md, evidence rotates to reqe-settled.json
1806
1837
  dz qe-bridge --family claude --slug <feature> [--coder-family codex|claude] [--model <id>] [--files a,b] [--out <f>] [--timeout <s>] [--allow-same-family] [--json] # the REVERSE QE bridge (Codex-hosted → Claude reviewer): the reviewer runs ISOLATED (an empty temp cwd + --safe-mode --strict-mcp-config --tools '' --no-session-persistence, so no CLAUDE.md/skills/plugins/hooks/MCP load) and its verdict is read from the --output-format json RESULT ENVELOPE, so text a customization printed onto the same stdout can never become a signoff. Probes the model first; sends SCOPED extracts under a loud 200k-char ceiling; the grade must agree across three LAST-anchored channels AND the marker must be the final content — empty/gradeless/mismatched/miscounted output is a named failure with an audit record under features/<slug>/.fa-state/qe-bridge/, never a clean review. exit 0 signoff parsed (ANY grade — it reports, it does not gate) / 1 named failure / 2 usage. DZ_QE_BRIDGE_CLAUDE_BIN is a TEST SEAM (recorded as binOverride:true)
1807
1838
  dz backlog <sub> add "<idea>" | list | show <id> | goals [--validate] | roulette [--seed n] [--commit <id>] | ship <id…> | drop <id…> | reopen <id…> | enrich <id> | jira <id> | harmonize [--apply] # brain-backed idea backlog: capture an idea → semantic dedup against past ideas/features via the REUSED agentdb vector engine (two-signal: bounded-excerpt cosine DUPLICATE≥0.92 corroborated by shared subject vocabulary — a register-only 0.94 is demoted to RELATED, a length-only re-capture is caught as a subset duplicate; absorbed texts kept in absorbed.jsonl) + GoalMap alignment ("map+compass") → weighted seeded roulette picks one to work on → enrich STAGES an idea2prd hand-off → jira writes an auditable outbox via a configurable MCP adapter seam (jira-mcp|copilot-mcp|none). No 2nd vector store; without agentdb it degrades to exact-text dedup (honest)
@@ -3992,7 +4023,24 @@ npx @dzhechkov/p-replicator init
3992
4023
 
3993
4024
  ## Status
3994
4025
 
3995
- `v0.5.4` — **published 2026-08-20** (0.5.3 is deprecated the core it pinned could not be installed). No CLI behavior change of its own: released in lockstep with
4026
+ `v0.6.1` — two commands that move checks off the model's judgement and onto the disk.
4027
+ `dz amendment-check` resolves every `AM-N` amendment row to a test found INSIDE the file the row names
4028
+ (the plan is authoritative; an amendment it drops or rewords under the same id is a failure; `--all` is
4029
+ a census that never blocks). `dz feature-adr-record` is the witnessed writer for the run-cost ledger
4030
+ and training pairs: the payload arrives as an ARGUMENT rather than baked into a shell pipeline, a
4031
+ malformed or wrong-kind payload is refused before any write, the timestamp is stamped before
4032
+ serialising, and the append is verified by re-reading the tail. `MIN_CORE` moves to `0.6.1`.
4033
+
4034
+ **v0.6.0** — adds `dz feature-adr-checkpoint`, which writes the pipeline's durable checkpoint line as a
4035
+ COMMAND instead of leaving a subagent to hand-write state into a file: a security classifier blocked
4036
+ NINE consecutive hand-written checkpoint writes in one run, `.fa-state/checkpoints.jsonl` was never
4037
+ created, resume was silently dead, and the run still reported success. The command measures every
4038
+ declared artifact and refuses to persist a null result, absent or partial artifacts, or a stage that
4039
+ declared no artifact at all. `MIN_CORE` moves to `0.6.0` and the dependency range with it — under 0.x
4040
+ semver a caret pins the minor, so a range left at `^0.5.4` would install a core this build then
4041
+ refuses at startup.
4042
+
4043
+ **v0.5.4** — **published 2026-08-20** (0.5.3 is deprecated — the core it pinned could not be installed). No CLI behavior change of its own: released in lockstep with
3996
4044
  `@dzhechkov/harness-core@0.5.3` and pinned to it, so a fresh install gets the per-sibling design
3997
4045
  checkpoint in `/feature-adr` (one dead design agent no longer discards three finished siblings) and
3998
4046
  its refusal gate (an incomplete design fan stops at the Step-5/6 boundary instead of planning off a
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
package/dist/cli.js CHANGED
@@ -22,7 +22,7 @@ readBacklogConfig, readIdeas, writeIdeas, ideaId, dedupIdea, readGoalMap, readGo
22
22
  // qe-bridge (feature qe-bridge-claude, ADR-001): the pure half of the reverse QE bridge.
23
23
  KNOWN_CLAUDE, isSafeClaudeId, claudeProbeArgs, claudeReviewArgs, interpretClaudeProbe, modelFamily, buildBridgePrompt, parseBridgeOutput, buildBridgeFailureRecord, buildBridgeSignoffRecord, renderBridgeReport, isSafeSlug, hasUnsafePathChars, hasDotDotSegment, buildReqeBrief, settleReqeDebt, renderReqeList, REQE_SCOPE,
24
24
  // Mutation gate (feature ha-mutation-gate) — break each named protection, run the suite, require red.
25
- parseMutationRegistry, applyMutationToText, countFailingTests, classifyBaseline, classifyRunFailure, classifyMutationOutcome, mutationGateExitCode, summarizeMutationResults, renderMutationReport, TRACE_BUNDLE_LEDGER_PATH, TRACE_BUNDLE_SCHEMA, TRACE_BUNDLE_RUN_META_FILE, buildBundle, serializeBundle, parseBundle, planImport, } from '@dzhechkov/harness-core';
25
+ parseMutationRegistry, applyMutationToText, countFailingTests, classifyBaseline, classifyRunFailure, classifyMutationOutcome, mutationGateExitCode, summarizeMutationResults, renderMutationReport, TRACE_BUNDLE_LEDGER_PATH, TRACE_BUNDLE_SCHEMA, TRACE_BUNDLE_RUN_META_FILE, buildBundle, serializeBundle, parseBundle, planImport, decideCheckpointWrite, amendmentSection, planSaysNoAmendments, parseAmendments, resolveAmendments, decideAmendmentOutcome, amendmentVerdictLine, amendmentsMissingFromPlan, AMENDMENT_VACUITY_NOTE, decideRecordWrite, decideReadBack, recordVerdictLine, } from '@dzhechkov/harness-core';
26
26
  import { getPreset, PRESET_NAMES } from '@dzhechkov/harness-presets';
27
27
  import { scanGitHub, analyzeRepo, generateReport, deepAnalyze, scanAllSources, ScoutMemory } from '@dzhechkov/scout';
28
28
  const USAGE = `dz - DZ cross-platform harness CLI
@@ -62,6 +62,9 @@ Usage:
62
62
  dz epoch-replay --judge <filled-work-order.json> [--out <file>] (blind judge prompts from the filled plans)
63
63
  dz epoch-replay --score <judgments.json> --work-order <file> [--slice <name>] [--json] (un-blind against the pre-registered assignment → SUPPORTED only when the two 95% Wilson CIs are DISJOINT, else FALSIFIED / INCONCLUSIVE)
64
64
  dz score --slug <feature> [--project <dir>] [--json] (process scorecard for ONE feature-adr run, from its artifacts: ADR confirmation, discrimination, cross-model QE grade, live verification, README-first, learning loop, amendments — descriptive-only, a low score exits 0)
65
+ dz amendment-check --slug <slug> | --feature-dir <dir> | --all [--json] (the deterministic Step-8 amendment gate: every AM-N row must resolve to a test found INSIDE the file the row names; the PLAN is authoritative when it carries rows, and an ideation amendment the plan drops is a failure. exit 0 pass/skip, 1 fail, 3 NOT-ESTABLISHED — a section that parsed ZERO rows is never a pass. --all is a CENSUS and always exits 0. Does NOT prove non-vacuity — that is dz discrimination-check)
66
+ dz feature-adr-record --kind ledger|training-pair --stage <s> [--slug <s>] [--row|--pair <json>] [--mark <n>] [--once] [--json] (the witnessed writer for the run-cost ledger and training pairs: the payload arrives as an ARGUMENT, never as shell; a malformed or wrong-kind payload is REFUSED before any write; the timestamp is stamped before serialising; the append is verified by re-reading the tail. exit 0 written|duplicate|skipped, 2 refused, 3 not-verified — a record failure is never blocking)
67
+ dz feature-adr-checkpoint (--slug <feature> | --feature-dir <abs>) --stage <s> --input-hash <h> --result <json> [--artifact a,b] [--json] (record a pipeline stage ONLY after measuring its artifacts on disk; refuses a null result, an absent artifact, or a stage that declares none — the subagent runs a COMMAND instead of hand-writing durable state)
65
68
  dz reqe [--slug <feature> [--done --report <f>]] [--json] (the re-QE debt ledger: a usage-switched run whose Step-8 QE ran on the coder's OWN family records a debt; list debts, print the cross-family review brief, settle FAIL-CLOSED against a graded report — the settlement lands in 08_qe_report.md)
66
69
  dz qe-bridge --family claude --slug <feature> [--coder-family codex|claude] [--model <id>] [--files a,b] [--out <f>] [--timeout <s>] [--allow-same-family] [--json] (the REVERSE QE bridge: run an INDEPENDENT Claude reviewer over a feature's Step-8 artifacts from ANY host — a Codex session included, plain shell, no Claude agent plane needed — and land a PARSED signoff. The reviewer runs ISOLATED: an EMPTY temp cwd plus --safe-mode --strict-mcp-config --tools '' --no-session-persistence, so no CLAUDE.md/skills/plugins/hooks/MCP load, and the verdict is read from the --output-format json RESULT ENVELOPE — text a session customization printed onto the same stdout can never become a signoff. Probes the model before trusting it; sends SCOPED extracts with a loud 200k-char ceiling (never silent truncation); the grade must AGREE across three LAST-anchored channels (terminal marker line, fenced qe-bridge-signoff JSON, the report's own GRADE line) AND the marker must be the FINAL content — empty, gradeless, self-contradicting or miscounted output is one of 18 NAMED failures with an audit record under features/<slug>/.fa-state/qe-bridge/ (runId, resolved executable + binOverride, prompt sha256, channel offsets, requestedOut, reportWritten, retained raw stdout; 0600 files in a 0700 dir), never a clean review. A --coder-family that contradicts the recorded reqe debt is refused. Writes features/<slug>/08b_reqe_report.md, which dz reqe --done settles unchanged. DISCLOSURE: the extracts you scope are sent to the Claude runtime; the bridge cannot classify secrets. DZ_QE_BRIDGE_CLAUDE_BIN is a TEST SEAM, not a flag. exit 0 signoff parsed (ANY grade — it reports, it does not gate) / 1 named failure / 2 usage)
67
70
  dz mutation-gate [--package <dir>] [--registry <file>] [--test-cmd "<cmd>"] [--only <id[,id]>] [--timeout <ms>] [--rebaseline per-entry|final] [--keep-scratch] [--json] (prove each NAMED protection has a test that DISCRIMINATES: copy the package to a scratch dir, verify the baseline suite is green, apply each registry mutation, run the suite, REQUIRE red, restore. The red must be BEHAVIOURAL: a mutation that no longer parses is MUTATION_UNPARSEABLE; a red run whose OWN output reports a test FILE failing to load (node --test file-level not-ok with exitCode, vitest Failed Suites) is MUTATION_LOAD_FATAL — the signal comes from the same run as the failing count, never from a separate isolated import; red output whose shape matches no known runner is INCONCLUSIVE (a runner-coverage gap, loud, never PROVEN); a count far above the entry's bound is OVER_FAILING; a restored tree that does not reproduce green makes the entry INCONCLUSIVE (flaky). Mutation writes are realpath-contained to the scratch copy: a symlink escape or a node_modules/ target is refused (exit 2), the real tree is never written. A mutation that does not apply, a green suite, or an inconclusive run is a FAILURE — never a skip. exit 0 all proven / 1 gate failed / 2 setup error)
@@ -4307,7 +4310,7 @@ function cmdPublish(options, flags, cwd, write) {
4307
4310
  // Reject unknown flags/options so a typo (e.g. `--dry-rum`) can NEVER be
4308
4311
  // silently swallowed and flip the command into live-publish mode.
4309
4312
  const allowedFlags = new Set(['dry-run', 'no-dry-run', 'yes', 'confirm', 'bump-only', 'help', 'require-signing', 'provenance', 'no-provenance']);
4310
- const allowedOptions = new Set(['filter', 'claim-check', 'no-guard']);
4313
+ const allowedOptions = new Set(['filter', 'claim-check', 'no-guard', 'sign-key']);
4311
4314
  const allowedHelp = ' allowed: --dry-run (default), --yes/--confirm/--no-dry-run (go live), --bump-only, --filter <substr>, --claim-check <off|warn|error>, --no-guard "<reason>" (skip the guard pre-flight; logged)';
4312
4315
  for (const flag of flags) {
4313
4316
  if (!allowedFlags.has(flag)) {
@@ -4458,7 +4461,30 @@ function cmdPublish(options, flags, cwd, write) {
4458
4461
  return 1;
4459
4462
  }
4460
4463
  }
4461
- const report = publishPackages(cwd, { provenance, dryRun, filter, bumpOnly, claimGate: claimCheckOpt });
4464
+ // A signed pack must be RE-SIGNED after publish's own bump and README sync (feature
4465
+ // `sign-after-bump`): publish mutates the pack, so any earlier signature describes files that no
4466
+ // longer exist. Default to the same path `dz sign --init` writes, so the ordinary operator needs no
4467
+ // new flag; `--sign-key` overrides it.
4468
+ const signKey = (options.get('sign-key') ?? join(homedir(), '.dz', 'keys', 'dz.key')).trim();
4469
+ const report = publishPackages(cwd, {
4470
+ provenance,
4471
+ dryRun,
4472
+ filter,
4473
+ bumpOnly,
4474
+ claimGate: claimCheckOpt,
4475
+ signKey: signKey === '' ? undefined : resolve(cwd, signKey),
4476
+ reSign: (packDir, keyPath) => {
4477
+ // The same three steps `dz sign` performs, including the SBOM — a manifest refreshed without
4478
+ // its SBOM would leave the two describing different trees.
4479
+ const files = packFiles(packDir);
4480
+ if (files.length === 0)
4481
+ throw new Error(`refusing to sign an empty pack: ${packDir}`);
4482
+ const manifest = buildManifest(packDir, basename(packDir), files);
4483
+ const signed = signManifest(manifest, readFileSync(keyPath, 'utf-8'));
4484
+ writeFileSync(join(packDir, MANIFEST_NAME), `${JSON.stringify(signed, null, 2)}\n`);
4485
+ writeFileSync(join(packDir, SBOM_NAME), `${JSON.stringify(buildSbom(manifest), null, 2)}\n`);
4486
+ },
4487
+ });
4462
4488
  write(`\ndz publish${dryRun ? ' --dry-run' : ''}${bumpOnly ? ' --bump-only' : ''}${claimCheckOpt !== 'warn' ? ` --claim-check ${claimCheckOpt}` : ''}`);
4463
4489
  write(` Published: ${report.published} Skipped: ${report.skipped} Errors: ${report.errors}\n`);
4464
4490
  for (const pkg of report.packages) {
@@ -8614,6 +8640,293 @@ function scanOneReqeRoot(featuresDir, keyMatchesDir, out, addMalformed) {
8614
8640
  * `dz reqe` — the re-QE debt ledger (backlog 6b40e667): list usage-switched same-family QE debts,
8615
8641
  * print the cross-family review brief, settle FAIL-CLOSED against a graded report.
8616
8642
  */
8643
+ /**
8644
+ * `dz feature-adr checkpoint` — record a pipeline stage, but only after WITNESSING its artifacts.
8645
+ *
8646
+ * Why this is a command and not a line of shell (2026-08-21). The workflow script is sandboxed with
8647
+ * no filesystem, so it delegated checkpoint writes to a subagent by handing it a finished JSON line
8648
+ * and saying "append this". The subagent verified nothing. A safety classifier read that shape as one
8649
+ * party instructing another to declare a verification gate complete, and blocked NINE consecutive
8650
+ * writes in one run — router, four design substages, plan, code, qe, and the cost-ledger row. The
8651
+ * measured consequence: `.fa-state/checkpoints.jsonl` was never created, resume was silently dead,
8652
+ * and the six-hour run still reported success.
8653
+ *
8654
+ * The classifier's premise was wrong for those particular writes — every stage had run. Its instinct
8655
+ * was not: the old mechanism could not tell a real completion from a fabricated one, which is exactly
8656
+ * what a cross-family reviewer had already filed against the `fleet` stage. So the subagent now runs
8657
+ * THIS, and the verification lives in code under test instead of in the wording of a prompt.
8658
+ *
8659
+ * It measures `--artifact` paths on disk itself. A caller cannot assert presence; it can only name
8660
+ * what must be there.
8661
+ */
8662
+ /**
8663
+ * `dz amendment-check` — the deterministic half of the Step-8 amendment gate (ADR-001).
8664
+ *
8665
+ * The gate used to be prompt text asking the QE agent to confirm every `AM-N` row names a real test.
8666
+ * That is layer 4 on the cost-of-detection ladder, and `features/qe-scoped-review` shipped with five
8667
+ * dangling ids and a plan recording `## Amendments: None`. This command owns I/O and the exit code;
8668
+ * `harness-core/src/amendment-trace.ts` owns the grammar and the rules.
8669
+ */
8670
+ /**
8671
+ * `dz feature-adr-record` — the witnessed writer for the run-cost ledger and training pairs
8672
+ * (ADR-001 … ADR-003). The subagent stops being a COURIER handed a shell pipeline and becomes a
8673
+ * CALLER handed arguments: this command owns the paths, the refusal, the append, the READ-BACK and
8674
+ * the exit code. A courier can neither refuse nor verify, which is how four workflow runs finished
8675
+ * with no cost row at all.
8676
+ */
8677
+ function cmdFeatureAdrRecord(options, flags, cwd, write) {
8678
+ const json = flags.has('json');
8679
+ const kind = (options.get('kind') ?? '').trim();
8680
+ const stage = (options.get('stage') ?? '').trim();
8681
+ const slug = (options.get('slug') ?? '').trim();
8682
+ const payloadRaw = options.get('row') ?? options.get('pair') ?? '';
8683
+ const emit = (d, extra = {}) => {
8684
+ if (json) {
8685
+ write(JSON.stringify({ ok: d.exit === 0, kind, stage, verdict: d.verdict, exit: d.exit, reason: d.reason, blocking: d.blocking, ...extra }));
8686
+ }
8687
+ else {
8688
+ write(recordVerdictLine(kind === 'ledger' || kind === 'training-pair' ? kind : 'ledger', stage, d));
8689
+ }
8690
+ return d.exit;
8691
+ };
8692
+ if (kind !== 'ledger' && kind !== 'training-pair') {
8693
+ write('dz feature-adr-record: --kind must be ledger or training-pair');
8694
+ return 2;
8695
+ }
8696
+ if (payloadRaw === '') {
8697
+ write('dz feature-adr-record: --row (ledger) or --pair (training-pair) is required');
8698
+ return 2;
8699
+ }
8700
+ const repo = (options.get('project') ?? cwd).trim() || cwd;
8701
+ const target = kind === 'ledger'
8702
+ ? join(repo, '.dz', 'feature-adr', 'run-cost-ledger.jsonl')
8703
+ : join(repo, '.dz', 'fa-training', slug === '' ? 'unknown' : slug, `${stage.replace(/[^\w.-]/g, '_')}.jsonl`);
8704
+ const markDir = join(repo, '.dz', 'fa-training', '.backfill-marks');
8705
+ const markName = (options.get('mark') ?? '').trim();
8706
+ const markPath = markName === '' ? null : join(markDir, markName.replace(/[^\w.-]/g, '_'));
8707
+ const decision = decideRecordWrite({
8708
+ kind,
8709
+ payloadRaw,
8710
+ stage,
8711
+ stageProducedResult: flags.has('no-result') ? false : true,
8712
+ markExists: markPath !== null && existsSync(markPath),
8713
+ targetExists: existsSync(target),
8714
+ targetHasPair: flags.has('once') && existsSync(target),
8715
+ timestamp: new Date().toISOString(),
8716
+ });
8717
+ if (decision.line === null)
8718
+ return emit(decision);
8719
+ // MEASURE, never assume: an unwritable target is a LOUD refusal, because a swallowed mkdir failure
8720
+ // is exactly how a write becomes a silent no-op (acid case A4).
8721
+ try {
8722
+ mkdirSync(dirname(target), { recursive: true });
8723
+ if (markPath !== null) {
8724
+ mkdirSync(markDir, { recursive: true });
8725
+ mkdirSync(markPath);
8726
+ }
8727
+ }
8728
+ catch (err) {
8729
+ const code = err.code;
8730
+ if (markPath !== null && code === 'EEXIST') {
8731
+ // A stale mark (target absent) was already decided as writable above; re-taking it is a no-op,
8732
+ // not a duplicate. Only a mark WITH its target means another run got here first.
8733
+ if (decision.staleMark !== true) {
8734
+ return emit({ verdict: 'duplicate', exit: 0, reason: 'a mark for this record already exists — another run captured it first', blocking: false, line: null });
8735
+ }
8736
+ }
8737
+ return emit({ verdict: 'refused', exit: 2, reason: `the target could not be prepared: ${err.message}`, blocking: false, line: null });
8738
+ }
8739
+ try {
8740
+ appendFileSync(target, `${decision.line}\n`, 'utf-8');
8741
+ }
8742
+ catch (err) {
8743
+ return emit({ verdict: 'refused', exit: 2, reason: `the append failed: ${err.message}`, blocking: false, line: null });
8744
+ }
8745
+ // ADR-002: the write is verified against the disk, never inferred from the absence of an error.
8746
+ let lastLine = null;
8747
+ try {
8748
+ const body = readFileSync(target, 'utf-8');
8749
+ const lines = body.split('\n').filter((l) => l !== '');
8750
+ lastLine = lines.length > 0 ? lines[lines.length - 1] : null;
8751
+ }
8752
+ catch {
8753
+ lastLine = null;
8754
+ }
8755
+ return emit(decideReadBack(decision.line, lastLine), { target });
8756
+ }
8757
+ function cmdAmendmentCheck(options, flags, cwd, write) {
8758
+ const json = flags.has('json');
8759
+ const readOr = (abs) => {
8760
+ try {
8761
+ return readFileSync(abs, 'utf-8');
8762
+ }
8763
+ catch {
8764
+ return null;
8765
+ }
8766
+ };
8767
+ const checkOne = (featureDir) => {
8768
+ const slug = basename(featureDir);
8769
+ const ideation = readOr(join(featureDir, '03.5_ideation_report.md'));
8770
+ const plan = readOr(join(featureDir, '06_implementation_plan.md'));
8771
+ if (ideation === null) {
8772
+ return {
8773
+ slug,
8774
+ resolutions: [],
8775
+ decision: decideAmendmentOutcome({ sectionPresent: false, rows: [], resolutions: [], planSaysNone: false }),
8776
+ };
8777
+ }
8778
+ const sectionPresent = amendmentSection(ideation) !== null;
8779
+ const ideationRows = parseAmendments(ideation);
8780
+ const planRows = plan === null ? [] : parseAmendments(plan);
8781
+ // The PLAN is authoritative when it carries rows: Step 6 owes "carry AM-N into the plan
8782
+ // verbatim", and the ideation report is the historical record — rewriting its rows to match
8783
+ // tests named later would close the trail by falsifying it. Coverage keeps that honest below.
8784
+ const rows = planRows.length > 0 ? planRows : ideationRows;
8785
+ const missingFromPlan = planRows.length > 0 ? amendmentsMissingFromPlan(ideationRows, planRows) : [];
8786
+ // Paths in an amendment row are repo-relative, so they resolve against the repo root — not
8787
+ // against the feature directory, and not against wherever the caller happened to stand.
8788
+ const resolutions = resolveAmendments(rows, { readFile: (rel) => readOr(resolve(cwd, rel)) });
8789
+ const decision = decideAmendmentOutcome({
8790
+ sectionPresent,
8791
+ rows,
8792
+ resolutions,
8793
+ planSaysNone: plan !== null && planSaysNoAmendments(plan),
8794
+ missingFromPlan,
8795
+ });
8796
+ return { slug, decision, resolutions };
8797
+ };
8798
+ // --all is a CENSUS, not a gate (ADR-003): a gate that is red on arrival over 21 historical
8799
+ // features gets disabled, and then the mechanism is gone along with the debt it was to surface.
8800
+ if (flags.has('all')) {
8801
+ const featuresDir = join(cwd, 'features');
8802
+ let slugs = [];
8803
+ try {
8804
+ slugs = readdirSync(featuresDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort();
8805
+ }
8806
+ catch {
8807
+ if (json)
8808
+ write(JSON.stringify({ ok: true, mode: 'all', features: [], note: 'no features/ directory' }));
8809
+ else
8810
+ write('amendment traceability census: no features/ directory');
8811
+ return 0;
8812
+ }
8813
+ const rowsOut = [];
8814
+ for (const slug of slugs) {
8815
+ try {
8816
+ const r = checkOne(join(featuresDir, slug));
8817
+ rowsOut.push({ slug, outcome: r.decision.outcome, counts: r.decision.counts, reasons: r.decision.reasons.length });
8818
+ }
8819
+ catch (err) {
8820
+ // AM-6: one unreadable feature becomes its own row. Dropping it silently would make the
8821
+ // census read like coverage it does not have.
8822
+ rowsOut.push({ slug, outcome: 'read-error', error: err.message });
8823
+ }
8824
+ }
8825
+ if (json) {
8826
+ write(JSON.stringify({ ok: true, mode: 'all', note: AMENDMENT_VACUITY_NOTE, features: rowsOut }));
8827
+ }
8828
+ else {
8829
+ const tally = new Map();
8830
+ for (const r of rowsOut)
8831
+ tally.set(String(r['outcome']), (tally.get(String(r['outcome'])) ?? 0) + 1);
8832
+ for (const r of rowsOut) {
8833
+ if (r['outcome'] !== 'pass' && r['outcome'] !== 'skip')
8834
+ write(` [${String(r['outcome'])}] ${String(r['slug'])}`);
8835
+ }
8836
+ write(`amendment traceability census over ${rowsOut.length} feature(s): ${[...tally].map(([k, v]) => `${v} ${k}`).join(', ')}`);
8837
+ write(AMENDMENT_VACUITY_NOTE);
8838
+ }
8839
+ return 0;
8840
+ }
8841
+ const explicitDir = (options.get('feature-dir') ?? '').trim();
8842
+ const slug = (options.get('slug') ?? '').trim();
8843
+ if (explicitDir === '' && slug === '') {
8844
+ write('dz amendment-check: one of --slug / --feature-dir is required (or --all for a census)');
8845
+ return 2;
8846
+ }
8847
+ const featureDir = explicitDir !== '' ? resolve(cwd, explicitDir) : join(cwd, 'features', slug);
8848
+ const { decision, resolutions } = checkOne(featureDir);
8849
+ if (json) {
8850
+ write(JSON.stringify({
8851
+ ok: decision.outcome === 'pass' || decision.outcome === 'skip',
8852
+ slug: basename(featureDir),
8853
+ outcome: decision.outcome,
8854
+ exit: decision.exit,
8855
+ counts: decision.counts,
8856
+ reasons: decision.reasons,
8857
+ rows: resolutions,
8858
+ note: AMENDMENT_VACUITY_NOTE,
8859
+ }));
8860
+ }
8861
+ else {
8862
+ for (const r of resolutions) {
8863
+ if (r.verdict !== 'resolved')
8864
+ write(` [${r.verdict}] ${r.id}${r.testId === null ? '' : ` \`${r.testId}\``} — ${r.detail}`);
8865
+ }
8866
+ write(AMENDMENT_VACUITY_NOTE);
8867
+ // The verdict is the LAST line, in the K2 gate's own shape, so a caller that reads the tail of
8868
+ // the output reads a verdict rather than a finding.
8869
+ write(amendmentVerdictLine(decision));
8870
+ }
8871
+ return decision.exit;
8872
+ }
8873
+ function cmdFeatureAdrCheckpoint(options, flags, cwd, write) {
8874
+ const json = flags.has('json');
8875
+ const emit = (payload, human, code) => {
8876
+ if (json)
8877
+ write(JSON.stringify(payload));
8878
+ else
8879
+ write(human);
8880
+ return code;
8881
+ };
8882
+ const slug = (options.get('slug') ?? '').trim();
8883
+ const stage = (options.get('stage') ?? '').trim();
8884
+ const inputHash = (options.get('input-hash') ?? '').trim();
8885
+ const resultRaw = options.get('result') ?? '';
8886
+ const artifacts = (options.get('artifact') ?? '')
8887
+ .split(',')
8888
+ .map((a) => a.trim())
8889
+ .filter((a) => a !== '');
8890
+ if ((slug === '' && (options.get('feature-dir') ?? '').trim() === '') || stage === '' || inputHash === '') {
8891
+ return emit({ ok: false, reason: 'usage' }, 'dz feature-adr checkpoint: --stage, --input-hash and one of --slug / --feature-dir are required', 2);
8892
+ }
8893
+ let result;
8894
+ try {
8895
+ result = JSON.parse(resultRaw);
8896
+ }
8897
+ catch {
8898
+ return emit({ ok: false, reason: 'result is not valid JSON' }, 'dz feature-adr checkpoint: --result must be valid JSON', 2);
8899
+ }
8900
+ // --feature-dir wins when given: the workflow knows the absolute path already, and depending on
8901
+ // cwd resolution there would make the command's behaviour depend on where the subagent happened to
8902
+ // stand. --slug stays for humans running this by hand from a repo root.
8903
+ const explicitDir = (options.get('feature-dir') ?? '').trim();
8904
+ const featureDir = explicitDir !== '' ? explicitDir : join(cwd, 'features', slug);
8905
+ // MEASURE, never trust: presence is established here, by this process, on this disk.
8906
+ const present = artifacts.filter((rel) => {
8907
+ const abs = join(featureDir, rel);
8908
+ try {
8909
+ return statSync(abs).isFile();
8910
+ }
8911
+ catch {
8912
+ return false;
8913
+ }
8914
+ });
8915
+ const verdict = decideCheckpointWrite({ stage, inputHash, result, artifacts, present });
8916
+ if (!verdict.ok) {
8917
+ return emit({ ok: false, stage, reason: verdict.reason }, 'dz feature-adr checkpoint: REFUSED — ' + verdict.reason, 1);
8918
+ }
8919
+ const stateDir = join(featureDir, '.fa-state');
8920
+ try {
8921
+ mkdirSync(stateDir, { recursive: true });
8922
+ appendFileSync(join(stateDir, 'checkpoints.jsonl'), verdict.line + '\n', 'utf-8');
8923
+ }
8924
+ catch (err) {
8925
+ const reason = err instanceof Error ? err.message : String(err);
8926
+ return emit({ ok: false, stage, reason }, 'dz feature-adr checkpoint: write failed — ' + reason, 1);
8927
+ }
8928
+ return emit({ ok: true, stage, witnessed: verdict.witnessed }, 'dz feature-adr checkpoint: recorded ' + stage + ' (witnessed ' + verdict.witnessed.length + ' artifact(s))', 0);
8929
+ }
8617
8930
  function cmdReqe(options, flags, cwd, write) {
8618
8931
  const json = flags.has('json');
8619
8932
  if (flags.has('help')) {
@@ -11436,6 +11749,12 @@ export async function runCli(argv, io = {}) {
11436
11749
  return cmdEpochReplay(options, flags, cwd, write);
11437
11750
  case 'score':
11438
11751
  return cmdScore(options, flags, cwd, write);
11752
+ case 'feature-adr-record':
11753
+ return cmdFeatureAdrRecord(options, flags, cwd, write);
11754
+ case 'amendment-check':
11755
+ return cmdAmendmentCheck(options, flags, cwd, write);
11756
+ case 'feature-adr-checkpoint':
11757
+ return cmdFeatureAdrCheckpoint(options, flags, cwd, write);
11439
11758
  case 'reqe':
11440
11759
  return cmdReqe(options, flags, cwd, write);
11441
11760
  case 'qe-bridge':