@dzhechkov/harness-cli 0.5.4 → 0.6.0

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.
@@ -39,7 +39,7 @@
39
39
  * standing test (`test/core-import-floor.test.ts`, F1) fails if the two drift apart,
40
40
  * because a guard that says 0.4.7 while npm may install 0.4.2 is worse than no guard.
41
41
  */
42
- export declare const MIN_CORE = "0.5.4";
42
+ export declare const MIN_CORE = "0.6.0";
43
43
  /** The npm name of the guarded package — one literal, used by every leg below. */
44
44
  export declare const CORE_PACKAGE_NAME = "@dzhechkov/harness-core";
45
45
  /**
@@ -42,7 +42,7 @@ import { fileURLToPath } from 'node:url';
42
42
  * standing test (`test/core-import-floor.test.ts`, F1) fails if the two drift apart,
43
43
  * because a guard that says 0.4.7 while npm may install 0.4.2 is worse than no guard.
44
44
  */
45
- export const MIN_CORE = '0.5.4';
45
+ export const MIN_CORE = '0.6.0';
46
46
  /** The npm name of the guarded package — one literal, used by every leg below. */
47
47
  export const CORE_PACKAGE_NAME = '@dzhechkov/harness-core';
48
48
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dzhechkov/harness-cli",
3
- "version": "0.5.4",
3
+ "version": "0.6.0",
4
4
  "description": "The dz CLI — install AI skills for Claude Code, Codex, OpenCode, Hermes, OpenClaude, GitHub Copilot. 67 commands, 14 presets, 10 platform targets.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -41,7 +41,7 @@
41
41
  "sbom.json"
42
42
  ],
43
43
  "dependencies": {
44
- "@dzhechkov/harness-core": "^0.5.4",
44
+ "@dzhechkov/harness-core": "^0.6.0",
45
45
  "@dzhechkov/harness-presets": "^0.5.0",
46
46
  "@dzhechkov/scout": "^0.8.0",
47
47
  "@dzhechkov/skills-devops": "^0.3.0",
package/sbom.json CHANGED
@@ -15,7 +15,7 @@
15
15
  "hashes": [
16
16
  {
17
17
  "alg": "SHA-256",
18
- "content": "9555ead768fef7963e735b0c6af21a64412811531534108be3df61bc65b51021"
18
+ "content": "fb63e4a05e205a1a2ec6806b61898a4aa5ea19148e4bb0e553e2c9b19e382a5f"
19
19
  }
20
20
  ]
21
21
  },
@@ -25,7 +25,7 @@
25
25
  "hashes": [
26
26
  {
27
27
  "alg": "SHA-256",
28
- "content": "0d475969dbc98308bf0e23a15700c537ed4947b3cf910b334e647e3972fc78fe"
28
+ "content": "e4d839c8ae59d86f2150bb632e0f8794688ccfa117974eb5f2605676912e7176"
29
29
  }
30
30
  ]
31
31
  },
@@ -105,7 +105,7 @@
105
105
  "hashes": [
106
106
  {
107
107
  "alg": "SHA-256",
108
- "content": "7c64ed0d976d79fd8f3970ced13e664db1e97c43f96a7b153d7b993948cc5f9f"
108
+ "content": "bc71c42fc8804e71454efde34beee1e4aed2d074ee029edf08fbee3e76cc369a"
109
109
  }
110
110
  ]
111
111
  },
@@ -115,7 +115,7 @@
115
115
  "hashes": [
116
116
  {
117
117
  "alg": "SHA-256",
118
- "content": "8052367d84a5a1c4a9f7fb3397a6297edf707031bbfa59b945d240d17e221f04"
118
+ "content": "b269e0a6c84ff4069655ee9cd8d264c3be16a12f7de2b849e8d9065d0b6c44b3"
119
119
  }
120
120
  ]
121
121
  },
@@ -125,7 +125,7 @@
125
125
  "hashes": [
126
126
  {
127
127
  "alg": "SHA-256",
128
- "content": "f759949bcfaa04c3f4eaebbccde688192d44f129b61115313533f8828f7376f8"
128
+ "content": "8ba4b1bc4d3cc767a686f54a63e38d247fe399ccdbc959951bc788cee52f1eef"
129
129
  }
130
130
  ]
131
131
  },
@@ -135,7 +135,7 @@
135
135
  "hashes": [
136
136
  {
137
137
  "alg": "SHA-256",
138
- "content": "f89dbd282c83fa64f23c8fff3c83bc85c08c8deef451fd4d525e33784c2a0e13"
138
+ "content": "a8a7f048200d1cde46e1c4b9a175734624f86b432e45db13ff69343ef41adee3"
139
139
  }
140
140
  ]
141
141
  },
@@ -155,7 +155,7 @@
155
155
  "hashes": [
156
156
  {
157
157
  "alg": "SHA-256",
158
- "content": "66fd34797f007aa0607698a159fb367a772f32f5be1c2a90748c20cbf4b973cf"
158
+ "content": "817a16aafa21f9d592e1b74fbc7b393208aa6cbe7a1ce44059b3d57e4897ca48"
159
159
  }
160
160
  ]
161
161
  },
@@ -225,7 +225,7 @@
225
225
  "hashes": [
226
226
  {
227
227
  "alg": "SHA-256",
228
- "content": "797bfd035a68dc039ca33393f02f4ed1fe97e1021559e01906a0fbbc9b28c1dc"
228
+ "content": "82ac146f7d849e1315dafc8ca4536e031d5c8104317da46ab8b52e543a187b17"
229
229
  }
230
230
  ]
231
231
  },
@@ -245,7 +245,7 @@
245
245
  "hashes": [
246
246
  {
247
247
  "alg": "SHA-256",
248
- "content": "9338e5f5dd5cbb1f3692cb7db53531f103bc28450c109765bf161664a7598a5b"
248
+ "content": "44be85b04467075bf671bc58572bf294f402b43abc76e23354724a74ea2a6c27"
249
249
  }
250
250
  ]
251
251
  },
@@ -255,7 +255,7 @@
255
255
  "hashes": [
256
256
  {
257
257
  "alg": "SHA-256",
258
- "content": "ba96d37f80c98618a1d8ca61cf071c9d26c74dab8d277106e9079068428a5b71"
258
+ "content": "80b02871db06aa78df70cdc166e46a67fa9335da2f4956d6f937149b7ea69698"
259
259
  }
260
260
  ]
261
261
  },
@@ -295,7 +295,7 @@
295
295
  "hashes": [
296
296
  {
297
297
  "alg": "SHA-256",
298
- "content": "f744cfe02805f567312a196805ebc49486c3e2a8e3f64de1700be276481f0cc1"
298
+ "content": "a637ef30d058fcd95dc18ef6eca45d6ac06e0f2bf54341a0aae68b610180b44d"
299
299
  }
300
300
  ]
301
301
  },
@@ -655,7 +655,7 @@
655
655
  "hashes": [
656
656
  {
657
657
  "alg": "SHA-256",
658
- "content": "be010af797aeae754a3dd3e9f6f25d6f01066d4bf2037a01322554453d943ee3"
658
+ "content": "fafa2d1ff56bd41a6496b7ce569be671f2f617deac07425f47ad6e87b3ec00ce"
659
659
  }
660
660
  ]
661
661
  },
package/src/cli.ts CHANGED
@@ -399,6 +399,7 @@ import {
399
399
  serializeBundle,
400
400
  parseBundle,
401
401
  planImport,
402
+ decideCheckpointWrite,
402
403
  } from '@dzhechkov/harness-core';
403
404
  import type { MutationEntryResult, MutationObservation, MutationRegistryEntry } from '@dzhechkov/harness-core';
404
405
  import type { SkillApplyFailure, SkillLoadFailure } from '@dzhechkov/harness-core';
@@ -449,6 +450,7 @@ Usage:
449
450
  dz epoch-replay --judge <filled-work-order.json> [--out <file>] (blind judge prompts from the filled plans)
450
451
  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)
451
452
  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)
453
+ 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)
452
454
  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)
453
455
  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)
454
456
  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)
@@ -8937,6 +8939,82 @@ function scanOneReqeRoot(
8937
8939
  * `dz reqe` — the re-QE debt ledger (backlog 6b40e667): list usage-switched same-family QE debts,
8938
8940
  * print the cross-family review brief, settle FAIL-CLOSED against a graded report.
8939
8941
  */
8942
+ /**
8943
+ * `dz feature-adr checkpoint` — record a pipeline stage, but only after WITNESSING its artifacts.
8944
+ *
8945
+ * Why this is a command and not a line of shell (2026-08-21). The workflow script is sandboxed with
8946
+ * no filesystem, so it delegated checkpoint writes to a subagent by handing it a finished JSON line
8947
+ * and saying "append this". The subagent verified nothing. A safety classifier read that shape as one
8948
+ * party instructing another to declare a verification gate complete, and blocked NINE consecutive
8949
+ * writes in one run — router, four design substages, plan, code, qe, and the cost-ledger row. The
8950
+ * measured consequence: `.fa-state/checkpoints.jsonl` was never created, resume was silently dead,
8951
+ * and the six-hour run still reported success.
8952
+ *
8953
+ * The classifier's premise was wrong for those particular writes — every stage had run. Its instinct
8954
+ * was not: the old mechanism could not tell a real completion from a fabricated one, which is exactly
8955
+ * what a cross-family reviewer had already filed against the `fleet` stage. So the subagent now runs
8956
+ * THIS, and the verification lives in code under test instead of in the wording of a prompt.
8957
+ *
8958
+ * It measures `--artifact` paths on disk itself. A caller cannot assert presence; it can only name
8959
+ * what must be there.
8960
+ */
8961
+ function cmdFeatureAdrCheckpoint(options: Map<string, string>, flags: Set<string>, cwd: string, write: Write): number {
8962
+ const json = flags.has('json');
8963
+ const emit = (payload: Record<string, unknown>, human: string, code: number): number => {
8964
+ if (json) write(JSON.stringify(payload));
8965
+ else write(human);
8966
+ return code;
8967
+ };
8968
+ const slug = (options.get('slug') ?? '').trim();
8969
+ const stage = (options.get('stage') ?? '').trim();
8970
+ const inputHash = (options.get('input-hash') ?? '').trim();
8971
+ const resultRaw = options.get('result') ?? '';
8972
+ const artifacts = (options.get('artifact') ?? '')
8973
+ .split(',')
8974
+ .map((a) => a.trim())
8975
+ .filter((a) => a !== '');
8976
+ if ((slug === '' && (options.get('feature-dir') ?? '').trim() === '') || stage === '' || inputHash === '') {
8977
+ return emit({ ok: false, reason: 'usage' }, 'dz feature-adr checkpoint: --stage, --input-hash and one of --slug / --feature-dir are required', 2);
8978
+ }
8979
+ let result: unknown;
8980
+ try {
8981
+ result = JSON.parse(resultRaw);
8982
+ } catch {
8983
+ return emit({ ok: false, reason: 'result is not valid JSON' }, 'dz feature-adr checkpoint: --result must be valid JSON', 2);
8984
+ }
8985
+ // --feature-dir wins when given: the workflow knows the absolute path already, and depending on
8986
+ // cwd resolution there would make the command's behaviour depend on where the subagent happened to
8987
+ // stand. --slug stays for humans running this by hand from a repo root.
8988
+ const explicitDir = (options.get('feature-dir') ?? '').trim();
8989
+ const featureDir = explicitDir !== '' ? explicitDir : join(cwd, 'features', slug);
8990
+ // MEASURE, never trust: presence is established here, by this process, on this disk.
8991
+ const present = artifacts.filter((rel) => {
8992
+ const abs = join(featureDir, rel);
8993
+ try {
8994
+ return statSync(abs).isFile();
8995
+ } catch {
8996
+ return false;
8997
+ }
8998
+ });
8999
+ const verdict = decideCheckpointWrite({ stage, inputHash, result, artifacts, present });
9000
+ if (!verdict.ok) {
9001
+ return emit({ ok: false, stage, reason: verdict.reason }, 'dz feature-adr checkpoint: REFUSED — ' + verdict.reason, 1);
9002
+ }
9003
+ const stateDir = join(featureDir, '.fa-state');
9004
+ try {
9005
+ mkdirSync(stateDir, { recursive: true });
9006
+ appendFileSync(join(stateDir, 'checkpoints.jsonl'), verdict.line + '\n', 'utf-8');
9007
+ } catch (err) {
9008
+ const reason = err instanceof Error ? err.message : String(err);
9009
+ return emit({ ok: false, stage, reason }, 'dz feature-adr checkpoint: write failed — ' + reason, 1);
9010
+ }
9011
+ return emit(
9012
+ { ok: true, stage, witnessed: verdict.witnessed },
9013
+ 'dz feature-adr checkpoint: recorded ' + stage + ' (witnessed ' + verdict.witnessed.length + ' artifact(s))',
9014
+ 0,
9015
+ );
9016
+ }
9017
+
8940
9018
  function cmdReqe(options: Map<string, string>, flags: Set<string>, cwd: string, write: Write): number {
8941
9019
  const json = flags.has('json');
8942
9020
  if (flags.has('help')) {
@@ -11697,6 +11775,8 @@ export async function runCli(argv: string[], io: CliIo = {}): Promise<number> {
11697
11775
  return cmdEpochReplay(options, flags, cwd, write);
11698
11776
  case 'score':
11699
11777
  return cmdScore(options, flags, cwd, write);
11778
+ case 'feature-adr-checkpoint':
11779
+ return cmdFeatureAdrCheckpoint(options, flags, cwd, write);
11700
11780
  case 'reqe':
11701
11781
  return cmdReqe(options, flags, cwd, write);
11702
11782
  case 'qe-bridge':
@@ -44,7 +44,7 @@ import { fileURLToPath } from 'node:url';
44
44
  * standing test (`test/core-import-floor.test.ts`, F1) fails if the two drift apart,
45
45
  * because a guard that says 0.4.7 while npm may install 0.4.2 is worse than no guard.
46
46
  */
47
- export const MIN_CORE = '0.5.4';
47
+ export const MIN_CORE = '0.6.0';
48
48
 
49
49
  /** The npm name of the guarded package — one literal, used by every leg below. */
50
50
  export const CORE_PACKAGE_NAME = '@dzhechkov/harness-core';