@dzhechkov/harness-cli 0.3.235 → 0.3.237
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 -42
- package/README.md +72 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +242 -3
- package/dist/cli.js.map +1 -1
- package/package.json +2 -2
- package/sbom.json +9 -89
- package/src/cli.ts +241 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dzhechkov/harness-cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.237",
|
|
4
4
|
"description": "The dz CLI — install AI skills for Claude Code, Codex, OpenCode, Hermes, OpenClaude, GitHub Copilot. 35 commands, 13 presets, 6 platform targets.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"@dzhechkov/skills-reverse-engineering": "^0.1.0",
|
|
58
58
|
"@dzhechkov/skills-presentation-storyteller": "^0.1.0",
|
|
59
59
|
"@dzhechkov/skills-website-cloner": "^0.1.0",
|
|
60
|
-
"@dzhechkov/harness-core": "0.3.
|
|
60
|
+
"@dzhechkov/harness-core": "0.3.128"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@types/node": "^25.6.0",
|
package/sbom.json
CHANGED
|
@@ -9,86 +9,6 @@
|
|
|
9
9
|
}
|
|
10
10
|
},
|
|
11
11
|
"components": [
|
|
12
|
-
{
|
|
13
|
-
"type": "file",
|
|
14
|
-
"name": ".agentic-qe/brain.rvf",
|
|
15
|
-
"hashes": [
|
|
16
|
-
{
|
|
17
|
-
"alg": "SHA-256",
|
|
18
|
-
"content": "8136cd4cc145d3dbe6ed3ae4489946bbc26e3e44172f0e5241ac45dde38bfead"
|
|
19
|
-
}
|
|
20
|
-
]
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
"type": "file",
|
|
24
|
-
"name": ".agentic-qe/brain.rvf.lock",
|
|
25
|
-
"hashes": [
|
|
26
|
-
{
|
|
27
|
-
"alg": "SHA-256",
|
|
28
|
-
"content": "99fa5178a48b6953b8f3a6f1690204a3b5449004b7dd105e7a3155ad3658266c"
|
|
29
|
-
}
|
|
30
|
-
]
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
"type": "file",
|
|
34
|
-
"name": ".agentic-qe/memory.db",
|
|
35
|
-
"hashes": [
|
|
36
|
-
{
|
|
37
|
-
"alg": "SHA-256",
|
|
38
|
-
"content": "456372cb9a8c12f98ae6378338b8469d9be6df05cbe03a99b412d83d962b3d11"
|
|
39
|
-
}
|
|
40
|
-
]
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
"type": "file",
|
|
44
|
-
"name": ".agentic-qe/memory.db-shm",
|
|
45
|
-
"hashes": [
|
|
46
|
-
{
|
|
47
|
-
"alg": "SHA-256",
|
|
48
|
-
"content": "cab99dd5d4068013a0c5f4f1c2d29dbec07729dc47c1d77adba6a53b773d0026"
|
|
49
|
-
}
|
|
50
|
-
]
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
"type": "file",
|
|
54
|
-
"name": ".agentic-qe/memory.db-wal",
|
|
55
|
-
"hashes": [
|
|
56
|
-
{
|
|
57
|
-
"alg": "SHA-256",
|
|
58
|
-
"content": "2cc80ec378c1f9cd2588676588ee1b73bfe4f2f7ef3b96879c295eb86dccd2c2"
|
|
59
|
-
}
|
|
60
|
-
]
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
"type": "file",
|
|
64
|
-
"name": ".agentic-qe/patterns.rvf",
|
|
65
|
-
"hashes": [
|
|
66
|
-
{
|
|
67
|
-
"alg": "SHA-256",
|
|
68
|
-
"content": "9090b2e6d339eaa183672ac0cf9f625dbd5f9fc76d64afcdb34a3f5871c31c5a"
|
|
69
|
-
}
|
|
70
|
-
]
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
"type": "file",
|
|
74
|
-
"name": ".agentic-qe/patterns.rvf.idmap.json",
|
|
75
|
-
"hashes": [
|
|
76
|
-
{
|
|
77
|
-
"alg": "SHA-256",
|
|
78
|
-
"content": "c2693f9a7f47a2d2bffe3004188e70ce08411bf8db47d07c60551d18ff7640c4"
|
|
79
|
-
}
|
|
80
|
-
]
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
"type": "file",
|
|
84
|
-
"name": ".agentic-qe/patterns.rvf.lock",
|
|
85
|
-
"hashes": [
|
|
86
|
-
{
|
|
87
|
-
"alg": "SHA-256",
|
|
88
|
-
"content": "84842f3916c558b1f4f2da2a5b0236587f146dda1f6ab4ba4c31ed7b8b17f943"
|
|
89
|
-
}
|
|
90
|
-
]
|
|
91
|
-
},
|
|
92
12
|
{
|
|
93
13
|
"type": "file",
|
|
94
14
|
"name": "CHANGELOG.md",
|
|
@@ -105,7 +25,7 @@
|
|
|
105
25
|
"hashes": [
|
|
106
26
|
{
|
|
107
27
|
"alg": "SHA-256",
|
|
108
|
-
"content": "
|
|
28
|
+
"content": "7e8b772a6fa9ca0f36364cafd5ea996abeb62dc894c2f67f50ea7704a7a8824d"
|
|
109
29
|
}
|
|
110
30
|
]
|
|
111
31
|
},
|
|
@@ -115,7 +35,7 @@
|
|
|
115
35
|
"hashes": [
|
|
116
36
|
{
|
|
117
37
|
"alg": "SHA-256",
|
|
118
|
-
"content": "
|
|
38
|
+
"content": "667bf610bf890a7b93480a4b1045e98dc0c765effd80b78fde4e48538f595b3e"
|
|
119
39
|
}
|
|
120
40
|
]
|
|
121
41
|
},
|
|
@@ -125,7 +45,7 @@
|
|
|
125
45
|
"hashes": [
|
|
126
46
|
{
|
|
127
47
|
"alg": "SHA-256",
|
|
128
|
-
"content": "
|
|
48
|
+
"content": "593705e81f851c0e65ffa9feaa9bbd396e7a3295b9b21076cfd953ee5a05b93a"
|
|
129
49
|
}
|
|
130
50
|
]
|
|
131
51
|
},
|
|
@@ -185,7 +105,7 @@
|
|
|
185
105
|
"hashes": [
|
|
186
106
|
{
|
|
187
107
|
"alg": "SHA-256",
|
|
188
|
-
"content": "
|
|
108
|
+
"content": "cf6a3265890a8856ef31e3e7ac58026c15232c3c118ba0fcd1ce0d7590b84dd8"
|
|
189
109
|
}
|
|
190
110
|
]
|
|
191
111
|
},
|
|
@@ -195,7 +115,7 @@
|
|
|
195
115
|
"hashes": [
|
|
196
116
|
{
|
|
197
117
|
"alg": "SHA-256",
|
|
198
|
-
"content": "
|
|
118
|
+
"content": "b9af9be1bd31acd60e76275ff1055dd8e9b39b531b0c375467648f142b74d2c4"
|
|
199
119
|
}
|
|
200
120
|
]
|
|
201
121
|
},
|
|
@@ -205,7 +125,7 @@
|
|
|
205
125
|
"hashes": [
|
|
206
126
|
{
|
|
207
127
|
"alg": "SHA-256",
|
|
208
|
-
"content": "
|
|
128
|
+
"content": "6a51963ede51129e43524d62f3d757255a002dfec3df2c1dfa519af6f235a667"
|
|
209
129
|
}
|
|
210
130
|
]
|
|
211
131
|
},
|
|
@@ -265,7 +185,7 @@
|
|
|
265
185
|
"hashes": [
|
|
266
186
|
{
|
|
267
187
|
"alg": "SHA-256",
|
|
268
|
-
"content": "
|
|
188
|
+
"content": "1e7ef1d6e0796144999ad64d3650aeb4a2ccc7dfc66c5c5a8b705d783a9d0801"
|
|
269
189
|
}
|
|
270
190
|
]
|
|
271
191
|
},
|
|
@@ -285,7 +205,7 @@
|
|
|
285
205
|
"hashes": [
|
|
286
206
|
{
|
|
287
207
|
"alg": "SHA-256",
|
|
288
|
-
"content": "
|
|
208
|
+
"content": "5180330ac6612542c80a74ea8e0983f5cd231e95bb3962eccdd65fbdbf6611a8"
|
|
289
209
|
}
|
|
290
210
|
]
|
|
291
211
|
},
|
|
@@ -305,7 +225,7 @@
|
|
|
305
225
|
"hashes": [
|
|
306
226
|
{
|
|
307
227
|
"alg": "SHA-256",
|
|
308
|
-
"content": "
|
|
228
|
+
"content": "461beed08ad6cfde7bbf559701fcbc6978f622c3951c80463c4e4943c538eff5"
|
|
309
229
|
}
|
|
310
230
|
]
|
|
311
231
|
},
|
package/src/cli.ts
CHANGED
|
@@ -48,6 +48,9 @@ import {
|
|
|
48
48
|
runInitAgentsMd,
|
|
49
49
|
runInitGeminiMd,
|
|
50
50
|
TARGET_NAMES,
|
|
51
|
+
buildParityMatrix,
|
|
52
|
+
TARGET_CAPABILITIES,
|
|
53
|
+
TARGET_SHORT_LABELS,
|
|
51
54
|
WORKFLOW_NAMES,
|
|
52
55
|
importEcc,
|
|
53
56
|
recordPattern,
|
|
@@ -176,6 +179,12 @@ import {
|
|
|
176
179
|
renderProseDiff,
|
|
177
180
|
readScenarioIds,
|
|
178
181
|
DEFAULT_MAX_JUDGE_RUNS,
|
|
182
|
+
collectDeliveryFacts,
|
|
183
|
+
planDeliveryCheck,
|
|
184
|
+
renderDeliveryBrief,
|
|
185
|
+
classifyDelivery,
|
|
186
|
+
isUsablePlaneResult,
|
|
187
|
+
renderDeliveryReview,
|
|
179
188
|
} from '@dzhechkov/harness-core';
|
|
180
189
|
import type { Family, ModelRung, Candidate as BtoCandidate, DimScores } from '@dzhechkov/harness-core';
|
|
181
190
|
import type { SetupSpec } from '@dzhechkov/harness-core';
|
|
@@ -204,6 +213,8 @@ Usage:
|
|
|
204
213
|
dz verify-pack --pack <dir> [--pubkey <path>] (signature check; fail-closed; key from the repo, never the pack)
|
|
205
214
|
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)
|
|
206
215
|
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)
|
|
216
|
+
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)
|
|
217
|
+
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)
|
|
207
218
|
dz setup --target <name> [--preset <name>] [--select id,id,...] [--skills-dir <dir>] [--project <dir>] [--memory agentdb] [--no-memory] [--no-hooks] [--install-driver] [--force] [--enrich]
|
|
208
219
|
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)
|
|
209
220
|
dz teach --from-json <file> [--project <dir>] [--no-mirror] (bulk-import a 'dz recall --all --json' export — share a learned store across machines)
|
|
@@ -3571,6 +3582,88 @@ function cmdPublish(options: Map<string, string>, flags: Set<string>, cwd: strin
|
|
|
3571
3582
|
return report.errors > 0 ? 1 : 0;
|
|
3572
3583
|
}
|
|
3573
3584
|
|
|
3585
|
+
/* ------------------------------------------------------------------ */
|
|
3586
|
+
/* dz parity — the honest feature×target map (target-parity-matrix, */
|
|
3587
|
+
/* ADR-001): computed from the declarative model, never hand-written */
|
|
3588
|
+
/* ------------------------------------------------------------------ */
|
|
3589
|
+
|
|
3590
|
+
function cmdParity(options: Map<string, string>, flags: Set<string>, write: Write): number {
|
|
3591
|
+
const json = flags.has('json');
|
|
3592
|
+
if (flags.has('help')) {
|
|
3593
|
+
write('dz parity [--target <name>] [--json] — the computed feature×target map (never hand-written)');
|
|
3594
|
+
write(` targets: ${TARGET_NAMES.join(', ')}`);
|
|
3595
|
+
return 0;
|
|
3596
|
+
}
|
|
3597
|
+
const allowedFlags = new Set(['json', 'help']);
|
|
3598
|
+
const allowedOptions = new Set(['target']);
|
|
3599
|
+
// A stray positional is a typo'd invocation, not something to silently ignore (Codex QE gap 7).
|
|
3600
|
+
if (options.has('_positional_0')) {
|
|
3601
|
+
write(json ? JSON.stringify({ error: `unexpected argument "${options.get('_positional_0')}"`, exitCode: 1 }) : `dz parity: unexpected argument "${options.get('_positional_0')}"\n allowed: --target <name>, --json`);
|
|
3602
|
+
return 1;
|
|
3603
|
+
}
|
|
3604
|
+
for (const flag of flags) {
|
|
3605
|
+
if (!allowedFlags.has(flag)) {
|
|
3606
|
+
write(json ? JSON.stringify({ error: `unknown option --${flag}`, exitCode: 1 }) : `dz parity: unknown option --${flag}\n allowed: --target <name>, --json`);
|
|
3607
|
+
return 1;
|
|
3608
|
+
}
|
|
3609
|
+
}
|
|
3610
|
+
for (const key of options.keys()) {
|
|
3611
|
+
if (key.startsWith('_positional_')) continue;
|
|
3612
|
+
if (!allowedOptions.has(key)) {
|
|
3613
|
+
write(json ? JSON.stringify({ error: `unknown option --${key}`, exitCode: 1 }) : `dz parity: unknown option --${key}\n allowed: --target <name>, --json`);
|
|
3614
|
+
return 1;
|
|
3615
|
+
}
|
|
3616
|
+
}
|
|
3617
|
+
|
|
3618
|
+
const matrix = buildParityMatrix();
|
|
3619
|
+
const target = options.get('target');
|
|
3620
|
+
if (target !== undefined && !TARGET_NAMES.includes(target as (typeof TARGET_NAMES)[number])) {
|
|
3621
|
+
if (json) write(JSON.stringify({ error: `unknown target "${target}"`, targets: TARGET_NAMES, exitCode: 1 }, null, 2));
|
|
3622
|
+
else write(`dz parity: unknown target "${target}" — one of: ${TARGET_NAMES.join(', ')}`);
|
|
3623
|
+
return 1;
|
|
3624
|
+
}
|
|
3625
|
+
|
|
3626
|
+
if (json) {
|
|
3627
|
+
const rows = matrix.map((r) => ({
|
|
3628
|
+
id: r.feature.id,
|
|
3629
|
+
title: r.feature.title,
|
|
3630
|
+
cells: target !== undefined ? { [target]: r.cells[target as (typeof TARGET_NAMES)[number]] } : r.cells,
|
|
3631
|
+
}));
|
|
3632
|
+
// A filtered response stays internally consistent: capabilities are filtered too (Codex QE gap 9).
|
|
3633
|
+
const caps = target !== undefined ? { [target]: TARGET_CAPABILITIES[target as (typeof TARGET_NAMES)[number]] } : TARGET_CAPABILITIES;
|
|
3634
|
+
write(JSON.stringify({ targets: target !== undefined ? [target] : TARGET_NAMES, capabilities: caps, features: rows }, null, 2));
|
|
3635
|
+
return 0;
|
|
3636
|
+
}
|
|
3637
|
+
|
|
3638
|
+
if (target !== undefined) {
|
|
3639
|
+
const t = target as (typeof TARGET_NAMES)[number];
|
|
3640
|
+
write(`\ndz parity — ${t} (capabilities: ${TARGET_CAPABILITIES[t].join(', ')})\n`);
|
|
3641
|
+
for (const r of matrix) {
|
|
3642
|
+
const c = r.cells[t];
|
|
3643
|
+
const icon = c.level === 'full' ? '✓' : c.level === 'manual' ? '◐' : '—';
|
|
3644
|
+
write(` ${icon} ${r.feature.title.padEnd(58)} ${c.level === 'none' ? 'not available on this target' : `via ${c.via ?? ''}`}`);
|
|
3645
|
+
}
|
|
3646
|
+
write('\n ✓ full (the complete experience) ◐ manual (works, you drive it by hand) — not available');
|
|
3647
|
+
return 0;
|
|
3648
|
+
}
|
|
3649
|
+
|
|
3650
|
+
// Grid: one column per target, short header, one row per feature. Computed, never hand-written;
|
|
3651
|
+
// the labels live NEXT TO the model (a local map here would be a second, unchecked registry).
|
|
3652
|
+
const short: Record<string, string> = TARGET_SHORT_LABELS;
|
|
3653
|
+
write('\ndz parity — feature × target (computed from the model; dz parity --target <name> for detail)\n');
|
|
3654
|
+
write(` ${'feature'.padEnd(52)} ${TARGET_NAMES.map((t) => (short[t] ?? t).padStart(4)).join('')}`);
|
|
3655
|
+
for (const r of matrix) {
|
|
3656
|
+
const cells = TARGET_NAMES.map((t) => {
|
|
3657
|
+
const c = r.cells[t];
|
|
3658
|
+
return (c.level === 'full' ? '✓' : c.level === 'manual' ? '◐' : '—').padStart(4);
|
|
3659
|
+
}).join('');
|
|
3660
|
+
write(` ${r.feature.title.slice(0, 52).padEnd(52)} ${cells}`);
|
|
3661
|
+
}
|
|
3662
|
+
write('\n ✓ full ◐ manual (available, driven by hand) — not available');
|
|
3663
|
+
write(` columns: ${TARGET_NAMES.map((t) => `${short[t] ?? t}=${t}`).join(' ')}`);
|
|
3664
|
+
return 0;
|
|
3665
|
+
}
|
|
3666
|
+
|
|
3574
3667
|
/* ------------------------------------------------------------------ */
|
|
3575
3668
|
/* dz release — verified-release conveyor (feature release-verified, */
|
|
3576
3669
|
/* ADR-001): 4 HARD gates in FRONT of the untouched dz publish. */
|
|
@@ -5115,8 +5208,18 @@ function cmdFeatureAdrSetup(options: Map<string, string>, flags: Set<string>, cw
|
|
|
5115
5208
|
return 1;
|
|
5116
5209
|
}
|
|
5117
5210
|
|
|
5211
|
+
// portable-gates (direction b): --gates scaffolds the zero-config architecture/gates/delivery-check.md.
|
|
5212
|
+
// Its "runnable here" list is computed for --target (default agents-md, the AGENTS.md-class target class).
|
|
5213
|
+
const wantGates = flags.has('gates');
|
|
5214
|
+
const targetOpt = options.get('target');
|
|
5215
|
+
if (targetOpt !== undefined && !isTargetName(targetOpt)) {
|
|
5216
|
+
write(`dz feature-adr-setup: --target must be one of: ${TARGET_NAMES.join(', ')}`);
|
|
5217
|
+
return 1;
|
|
5218
|
+
}
|
|
5219
|
+
const gatesTarget: TargetName = isTargetName(targetOpt ?? '') ? (targetOpt as TargetName) : 'agents-md';
|
|
5220
|
+
|
|
5118
5221
|
const specPath = options.get('from-spec');
|
|
5119
|
-
if (specPath === undefined && !wantGuards) {
|
|
5222
|
+
if (specPath === undefined && !wantGuards && !wantGates) {
|
|
5120
5223
|
// default + --plan: the read-only "which documents, and where?" answer.
|
|
5121
5224
|
const plan = buildSetupPlan(scanForSetup(repoRoot));
|
|
5122
5225
|
if (flags.has('json')) { write(JSON.stringify(plan, null, 2)); return 0; }
|
|
@@ -5138,8 +5241,9 @@ function cmdFeatureAdrSetup(options: Map<string, string>, flags: Set<string>, cw
|
|
|
5138
5241
|
spec = {}; // --guards standalone: scaffold just the guard set
|
|
5139
5242
|
}
|
|
5140
5243
|
if (wantGuards) spec = { ...spec, guards: locCap !== undefined ? { locCap } : true };
|
|
5244
|
+
if (wantGates) spec = { ...spec, gates: true };
|
|
5141
5245
|
|
|
5142
|
-
const result = scaffoldFromSpec(spec, readExistingForScaffold(repoRoot));
|
|
5246
|
+
const result = scaffoldFromSpec(spec, readExistingForScaffold(repoRoot), gatesTarget);
|
|
5143
5247
|
if (flags.has('json') && !flags.has('apply')) { write(JSON.stringify(result, null, 2)); return 0; }
|
|
5144
5248
|
write(renderScaffoldPreview(result));
|
|
5145
5249
|
|
|
@@ -5342,6 +5446,137 @@ function nameFor(t: { file: string; name?: string }, outcome: 'pass' | 'fail' |
|
|
|
5342
5446
|
return t.name !== undefined ? { file: t.file, name: t.name, outcome } : { file: t.file, outcome };
|
|
5343
5447
|
}
|
|
5344
5448
|
|
|
5449
|
+
/**
|
|
5450
|
+
* `dz delivery-check` — the portable Step-10 Delivery Gate (feature portable-gates). The `manual` form that
|
|
5451
|
+
* travels to every `shell` target: the deterministic parts (artifact probes, hand-off arithmetic,
|
|
5452
|
+
* cross-validation bookkeeping) run IN the CLI; the semantic 4-plane review is DISPATCHED to the caller's own
|
|
5453
|
+
* agent runtime — the `dz challenge` cartridge shape. All logic lives in harness-core's pure engine; this is
|
|
5454
|
+
* the executor. Advisory exit codes (0 by default; `--strict` ⇒ 1 on `blocked`).
|
|
5455
|
+
*
|
|
5456
|
+
* --slug <slug> the feature under features/<slug> (required; featureDir derived internally)
|
|
5457
|
+
* --context-only default mode: print artifact probes + the 4-plane review brief (no verdict)
|
|
5458
|
+
* --findings <f.json> classify a fed-back findings array → write 10_delivery_review.md + print ready/blocked
|
|
5459
|
+
* --strict with --findings: exit 1 iff handoff === 'blocked' (CI gate)
|
|
5460
|
+
* --author <model> cosmetic: the reviewer to dispatch, printed alongside the brief
|
|
5461
|
+
* --json machine contract { planesChecked, planesSkipped, findings, handoff, artifact }
|
|
5462
|
+
*/
|
|
5463
|
+
function cmdDeliveryCheck(options: Map<string, string>, flags: Set<string>, cwd: string, write: Write): number {
|
|
5464
|
+
let repoRoot = cwd;
|
|
5465
|
+
try { repoRoot = execSync('git rev-parse --show-toplevel', { cwd, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }).trim() || cwd; } catch { /* not git */ }
|
|
5466
|
+
|
|
5467
|
+
const json = flags.has('json');
|
|
5468
|
+
const errOut = (error: string): number => {
|
|
5469
|
+
if (json) write(JSON.stringify({ error, planesChecked: [], planesSkipped: [], findings: [], handoff: null, artifact: 'not-written' }, null, 2));
|
|
5470
|
+
else { write(`dz delivery-check: ${error}`); write(' allowed: --slug <slug>, --findings <f.json>, --author <model>, --context-only, --strict, --json'); }
|
|
5471
|
+
return 1;
|
|
5472
|
+
};
|
|
5473
|
+
// Strict allowlist (delivery finding: a typo'd --findings silently downgraded the --strict CI
|
|
5474
|
+
// gate to a vacuous exit-0 pass — a mistyped option must be an ERROR, never a mode change).
|
|
5475
|
+
const allowedFlags = new Set(['context-only', 'strict', 'json', 'help']);
|
|
5476
|
+
const allowedOptions = new Set(['slug', 'findings', 'author']);
|
|
5477
|
+
for (const flag of flags) if (!allowedFlags.has(flag)) return errOut(`unknown option --${flag}`);
|
|
5478
|
+
for (const key of options.keys()) {
|
|
5479
|
+
if (key.startsWith('_positional_')) return errOut(`unexpected argument "${options.get(key)}"`);
|
|
5480
|
+
if (!allowedOptions.has(key)) return errOut(`unknown option --${key}`);
|
|
5481
|
+
}
|
|
5482
|
+
|
|
5483
|
+
const slug = options.get('slug');
|
|
5484
|
+
if (slug === undefined || slug.trim() === '') return errOut('pass --slug <slug> (the feature under features/<slug>)');
|
|
5485
|
+
// Slug convention is a SECURITY boundary here (delivery finding, CONFIRMED traversal reproducer:
|
|
5486
|
+
// --slug '../../outside/evil' wrote 10_delivery_review.md outside the repo). kebab-case Latin,
|
|
5487
|
+
// <=40 chars — the repo's own slug rule as a layer-1 three-line check.
|
|
5488
|
+
const slugTrimmed = slug.trim();
|
|
5489
|
+
if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(slugTrimmed) || slugTrimmed.length > 40) {
|
|
5490
|
+
return errOut(`invalid --slug "${slugTrimmed}" — kebab-case Latin, max 40 chars (a path-like slug would redirect the report write)`);
|
|
5491
|
+
}
|
|
5492
|
+
const featureDir = join(repoRoot, 'features', slugTrimmed);
|
|
5493
|
+
|
|
5494
|
+
// --context-only is a real mode flag, not decoration: combining it with --findings contradicts
|
|
5495
|
+
// itself (delivery finding — the flag was accepted and silently ignored).
|
|
5496
|
+
if (flags.has('context-only') && options.get('findings') !== undefined) {
|
|
5497
|
+
return errOut('--context-only and --findings are mutually exclusive (context-only prints the brief; findings classifies the verdict)');
|
|
5498
|
+
}
|
|
5499
|
+
|
|
5500
|
+
// git status --porcelain — CLI layer only (the `no child_process in core` contract); a non-git target
|
|
5501
|
+
// degrades to an empty list, which is honest (AM-10: changed-files is informational, never a required gate).
|
|
5502
|
+
let changedFiles: string[] = [];
|
|
5503
|
+
try {
|
|
5504
|
+
const out = execSync('git status --porcelain', { cwd: repoRoot, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
|
|
5505
|
+
changedFiles = out.split('\n').map((l) => l.slice(3).trim()).filter((s) => s.length > 0);
|
|
5506
|
+
} catch { /* non-git / unavailable → empty (honest) */ }
|
|
5507
|
+
|
|
5508
|
+
const facts = collectDeliveryFacts(featureDir, { changedFiles, repoRoot });
|
|
5509
|
+
const plan = planDeliveryCheck(facts);
|
|
5510
|
+
const author = options.get('author');
|
|
5511
|
+
|
|
5512
|
+
const findingsPath = options.get('findings');
|
|
5513
|
+
if (findingsPath === undefined) {
|
|
5514
|
+
// Default / --context-only: emit the led protocol (probes + brief). No verdict — the CLI has no model.
|
|
5515
|
+
if (json) {
|
|
5516
|
+
write(JSON.stringify({
|
|
5517
|
+
planesChecked: [],
|
|
5518
|
+
planesSkipped: plan.planes.map((p) => p.id),
|
|
5519
|
+
findings: [],
|
|
5520
|
+
handoff: null,
|
|
5521
|
+
artifact: 'not-written',
|
|
5522
|
+
probes: plan.probes,
|
|
5523
|
+
}, null, 2));
|
|
5524
|
+
return 0;
|
|
5525
|
+
}
|
|
5526
|
+
write(`dz delivery-check — ${slug} (portable 4-plane hand-off protocol; you drive the review)`);
|
|
5527
|
+
for (const p of plan.probes) {
|
|
5528
|
+
const mark = p.passed === true ? '✓' : p.passed === false ? '✗' : '○';
|
|
5529
|
+
write(` ${mark} ${p.description}${p.required ? ' [required]' : ' [informational]'}`);
|
|
5530
|
+
}
|
|
5531
|
+
write('');
|
|
5532
|
+
write(renderDeliveryBrief(plan, facts));
|
|
5533
|
+
// The hand-off criterion the verdict will be judged by, shown UP FRONT (this also makes
|
|
5534
|
+
// plan.criterionTemplate a consumed surface, not dead product code — delivery finding).
|
|
5535
|
+
write('\n── hand-off criterion (all must PASS for ready) ──');
|
|
5536
|
+
for (const c of plan.criterionTemplate) write(` ○ ${c.label}`);
|
|
5537
|
+
if (author !== undefined && author.trim() !== '') write(`\n── reviewer to dispatch ──\n${author.trim()}`);
|
|
5538
|
+
write('\n(next: run the four planes, then `dz delivery-check --slug ' + slug + ' --findings findings.json` for the verdict)');
|
|
5539
|
+
return 0;
|
|
5540
|
+
}
|
|
5541
|
+
|
|
5542
|
+
// --findings mode: parse (malformed/missing ⇒ [], a failed-plane input — fail-closed, never a throw).
|
|
5543
|
+
let reviewResults: unknown[] = [];
|
|
5544
|
+
try {
|
|
5545
|
+
const parsed: unknown = JSON.parse(readFileSync(resolve(cwd, findingsPath), 'utf-8'));
|
|
5546
|
+
if (Array.isArray(parsed)) reviewResults = parsed;
|
|
5547
|
+
else if (parsed !== null && typeof parsed === 'object' && Array.isArray((parsed as { planes?: unknown }).planes)) reviewResults = (parsed as { planes: unknown[] }).planes;
|
|
5548
|
+
else reviewResults = [];
|
|
5549
|
+
} catch { reviewResults = []; }
|
|
5550
|
+
|
|
5551
|
+
const verdict = classifyDelivery(plan, reviewResults as ({ findings?: readonly unknown[] } | null | undefined)[]);
|
|
5552
|
+
// The SAME predicate as the fail-closed verdict (core-exported) — a local copy was a drift channel.
|
|
5553
|
+
const planesChecked = plan.planes.filter((_, i) => isUsablePlaneResult(reviewResults[i])).map((p) => p.id);
|
|
5554
|
+
const planesSkipped = plan.planes.filter((_, i) => !isUsablePlaneResult(reviewResults[i])).map((p) => p.id);
|
|
5555
|
+
|
|
5556
|
+
// Write the regenerable report (NOT augment-never-clobber — deliberately overwritten each run).
|
|
5557
|
+
let artifact: 'written' | 'not-written' = 'not-written';
|
|
5558
|
+
try {
|
|
5559
|
+
const reviewPath = join(featureDir, '10_delivery_review.md');
|
|
5560
|
+
mkdirSync(dirname(reviewPath), { recursive: true });
|
|
5561
|
+
writeFileSync(reviewPath, renderDeliveryReview(verdict, facts));
|
|
5562
|
+
artifact = 'written';
|
|
5563
|
+
} catch { /* best-effort — the verdict is still returned */ }
|
|
5564
|
+
|
|
5565
|
+
if (json) {
|
|
5566
|
+
write(JSON.stringify({ planesChecked, planesSkipped, findings: verdict.findings, handoff: verdict.handoff, artifact }, null, 2));
|
|
5567
|
+
} else {
|
|
5568
|
+
write(`dz delivery-check — ${slug}: ${verdict.handoff}`);
|
|
5569
|
+
for (const c of verdict.criterion) {
|
|
5570
|
+
const mark = c.status === 'PASS' ? '✓' : c.status === 'FAIL' ? '✗' : '○';
|
|
5571
|
+
write(` ${mark} ${c.label}: ${c.status}${c.detail ? ` — ${c.detail}` : ''}`);
|
|
5572
|
+
}
|
|
5573
|
+
write(artifact === 'written' ? `\n↳ wrote features/${slug}/10_delivery_review.md` : '\n↳ 10_delivery_review.md NOT written (verdict above is authoritative)');
|
|
5574
|
+
}
|
|
5575
|
+
|
|
5576
|
+
if (flags.has('strict') && verdict.handoff === 'blocked') return 1;
|
|
5577
|
+
return 0;
|
|
5578
|
+
}
|
|
5579
|
+
|
|
5345
5580
|
/**
|
|
5346
5581
|
* `dz routing` — inspect the learned cost-optimal routing outcome store (feature learned-cost-routing). Shows
|
|
5347
5582
|
* what `args.models[stage]='auto-cost'` currently believes per (stage, complexity-tier, model): gated
|
|
@@ -5732,6 +5967,8 @@ export async function runCli(argv: string[], io: CliIo = {}): Promise<number> {
|
|
|
5732
5967
|
return cmdPublish(options, flags, cwd, write);
|
|
5733
5968
|
case 'release':
|
|
5734
5969
|
return cmdRelease(options, flags, cwd, write, io.releaseRunner);
|
|
5970
|
+
case 'parity':
|
|
5971
|
+
return cmdParity(options, flags, write);
|
|
5735
5972
|
case 'registry':
|
|
5736
5973
|
return cmdRegistry(options, cwd, write);
|
|
5737
5974
|
case 'benchmark':
|
|
@@ -5764,6 +6001,8 @@ export async function runCli(argv: string[], io: CliIo = {}): Promise<number> {
|
|
|
5764
6001
|
return cmdChallenge(options, flags, cwd, write);
|
|
5765
6002
|
case 'discrimination-check':
|
|
5766
6003
|
return cmdDiscriminationCheck(options, flags, cwd, write);
|
|
6004
|
+
case 'delivery-check':
|
|
6005
|
+
return cmdDeliveryCheck(options, flags, cwd, write);
|
|
5767
6006
|
case 'routing':
|
|
5768
6007
|
return cmdRouting(options, flags, cwd, write);
|
|
5769
6008
|
case 'bto-optimize':
|