@dzhechkov/harness-cli 0.3.232 → 0.3.234
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 +137 -0
- package/README.md +43 -4
- package/dist/cli.d.ts +17 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +309 -20
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/package.json +5 -3
- package/sbom.json +333 -0
- package/src/cli.ts +318 -16
- package/src/index.ts +1 -1
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
{
|
|
2
|
+
"manifest": {
|
|
3
|
+
"version": 1,
|
|
4
|
+
"pack": "harness-cli",
|
|
5
|
+
"files": [
|
|
6
|
+
{
|
|
7
|
+
"path": ".agentic-qe/brain.rvf",
|
|
8
|
+
"sha256": "8136cd4cc145d3dbe6ed3ae4489946bbc26e3e44172f0e5241ac45dde38bfead"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"path": ".agentic-qe/brain.rvf.lock",
|
|
12
|
+
"sha256": "96c3f61d34c63a8adf2a87cd10abc73b09946223798a543d2c477f6486ceba2b"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"path": ".agentic-qe/memory.db",
|
|
16
|
+
"sha256": "456372cb9a8c12f98ae6378338b8469d9be6df05cbe03a99b412d83d962b3d11"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"path": ".agentic-qe/memory.db-shm",
|
|
20
|
+
"sha256": "613a87ec0ce8a7cc48d1b6939999ac623c144ff5718f04b40b9d26519f517af2"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"path": ".agentic-qe/memory.db-wal",
|
|
24
|
+
"sha256": "8b037ea49fcdf20ebbe02798aaa6b5cdddd5cadd1f7239c629650093afd5ac2f"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"path": ".agentic-qe/patterns.rvf",
|
|
28
|
+
"sha256": "9090b2e6d339eaa183672ac0cf9f625dbd5f9fc76d64afcdb34a3f5871c31c5a"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"path": ".agentic-qe/patterns.rvf.idmap.json",
|
|
32
|
+
"sha256": "c2693f9a7f47a2d2bffe3004188e70ce08411bf8db47d07c60551d18ff7640c4"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"path": ".agentic-qe/patterns.rvf.lock",
|
|
36
|
+
"sha256": "ad77ee665c75491a8700381fb79205d308653e99a15013564c7732eeb6aa1181"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"path": "CHANGELOG.md",
|
|
40
|
+
"sha256": "9555ead768fef7963e735b0c6af21a64412811531534108be3df61bc65b51021"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"path": "README.md",
|
|
44
|
+
"sha256": "4a11112ac688fbc833439ec9d047da01abf0981189edf611f198dc36ae7c34ec"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"path": "coverage/coverage-final.json",
|
|
48
|
+
"sha256": "1abb00d07ab57ad413d2747baa6a68cda0876a9e0c6f75db89c205fbf4863530"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"path": "coverage/coverage-summary.json",
|
|
52
|
+
"sha256": "42d2a98f54c2ec773b49a0a43c959a78d070c8c427724f4affbd5bafef35621d"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"path": "dist/bin.d.ts",
|
|
56
|
+
"sha256": "58be9f04d2fe63cca2efe3957b9933068e443a0e57f9838ff9ef800cc191e03f"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"path": "dist/bin.d.ts.map",
|
|
60
|
+
"sha256": "b1e41c8c19fa56ae9dec2267740eb4389bc785431478861a8843867f99c1b25b"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"path": "dist/bin.js",
|
|
64
|
+
"sha256": "ad24e06de21b036e2b5a43abc4efd6e7ceb2ae126177b218a3ee13a026641f83"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"path": "dist/bin.js.map",
|
|
68
|
+
"sha256": "c2dd399fec3f26e48e23971b29e27c508c1a1f96376a193e72dc8042015c8e65"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"path": "dist/cli.d.ts",
|
|
72
|
+
"sha256": "628c55bb864dd8a8f337a9cb3fe4271f0df7c0bf9237b1150427171f0577fd57"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"path": "dist/cli.d.ts.map",
|
|
76
|
+
"sha256": "8c3b6f509c494ff446a098239f39453510a385f879d86127e91a528287b32333"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"path": "dist/cli.js",
|
|
80
|
+
"sha256": "0ea78654ed97065078affc088022d25406568345f30aaf87ec30533b1537110a"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"path": "dist/cli.js.map",
|
|
84
|
+
"sha256": "8fc984391e8ff91464f32e73d36253f292d558e2bb2676b2a18e474d8d1861d5"
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"path": "dist/index.d.ts",
|
|
88
|
+
"sha256": "aaac0134a074fd06e9b5e09daa952ef6353510e481885da51a6e4f2e92beba35"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"path": "dist/index.d.ts.map",
|
|
92
|
+
"sha256": "459c3381a9fbcd3378b623f9fe103a8d3337e4025a2f546b6d40e4d9dd4e0f70"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"path": "dist/index.js",
|
|
96
|
+
"sha256": "15ff1e6b84537b91b73b6a7e2d1c45f1120b25af9d1139df8da8ad094e99cf0e"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"path": "dist/index.js.map",
|
|
100
|
+
"sha256": "2f61a3be1af4ab9f7bbcbacb912ccbd89e3b73414f9fd24766598bfbdc1f8655"
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"path": "keys/README.md",
|
|
104
|
+
"sha256": "204deec657a7f147bf3016441082c2f868f6f11051776f4edc13ec6b44745192"
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"path": "package.json",
|
|
108
|
+
"sha256": "68a09fba4c6068dd171738166ad659097bf99d08b8a80ca504ad5109148b4eb5"
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"path": "src/bin.ts",
|
|
112
|
+
"sha256": "a0d4e20fafc5cba35816f1f0c6cd03d9ce7c4434d2ca049384491079d2408fd4"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"path": "src/cli.ts",
|
|
116
|
+
"sha256": "af5541d8bd97570102b8df85b6ed598813b8d53ede9cc71533e40b808d5c033e"
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"path": "src/index.ts",
|
|
120
|
+
"sha256": "395b27b317e575b94a1049cdb284db185e0ec382fbae1e588c78ff2398b2563f"
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"path": "test/cli.test.ts",
|
|
124
|
+
"sha256": "73b554b9f78681d9e1dd168b945bac427bcf027e11125157d8b4ab5e2b25dc22"
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"path": "tsconfig.json",
|
|
128
|
+
"sha256": "e80ef2911c3347a713dc3dca6a5f6034d91e938e58e71345ea920dd9833bcdbb"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"path": "vitest.config.ts",
|
|
132
|
+
"sha256": "a6ffc6172b50a3a59010f8f8c66e2204e7d298b62a7f2bc5de33cbf39c99899d"
|
|
133
|
+
}
|
|
134
|
+
]
|
|
135
|
+
},
|
|
136
|
+
"signature": "XO+n3VwYnhgXfKF8nBdxZ4ALrmUgXX+3OSBzauqpk9UV8/PVbKLHspZQ2Pfu9zO7wIcharU8f+NF/GXMGCyWDw=="
|
|
137
|
+
}
|
package/README.md
CHANGED
|
@@ -241,6 +241,11 @@ dz roam --apply # sync agent state across machin
|
|
|
241
241
|
### Phase 6: Share (publish to the world)
|
|
242
242
|
|
|
243
243
|
```bash
|
|
244
|
+
# Verified release — 4 HARD gates (tests, audit, syntax, smoke-boot) in FRONT of dz publish:
|
|
245
|
+
dz release --dry-run # print the full gate plan, execute nothing
|
|
246
|
+
dz release --filter skills-devops --tag # gates for one release set; on green: git tag + notes
|
|
247
|
+
dz release # gates for the whole workspace; green prints the ready dz publish command
|
|
248
|
+
|
|
244
249
|
# Publish updated packages to npm:
|
|
245
250
|
dz publish --dry-run # preview
|
|
246
251
|
dz publish --filter skills-devops # publish specific package
|
|
@@ -307,7 +312,9 @@ dz sbom --pack packages/@dzhechkov/skills-qe --out sbom.json # write to a file
|
|
|
307
312
|
```
|
|
308
313
|
|
|
309
314
|
A single flipped byte, a deleted file, or an **added** file all fail verification and the offending
|
|
310
|
-
path is named.
|
|
315
|
+
path is named. `node_modules`/`.git` are unsigned territory by design (npm ships neither) — the sign and
|
|
316
|
+
verify walks share those exclusions, while a symlink smuggled anywhere ELSE in the pack still fails loudly.
|
|
317
|
+
Re-sign after ANY pack change (the manifest hashes `package.json` too) — sign is the LAST step before publish. Every degenerate input (no manifest, empty file list, empty signature, no public key)
|
|
311
318
|
fails **closed** — absence never reads as success. `dz doctor` **and** `dz drift-check` run this check
|
|
312
319
|
over the installed packs: a **tampered** pack is fatal (blocks); an **unsigned** pack or a missing trust
|
|
313
320
|
root is reported, not fatal (transitional — the existing packs are not yet signed).
|
|
@@ -327,7 +334,7 @@ every run; `--require-signing` turns that into a refusal today.
|
|
|
327
334
|
verifies what it just installed. A pack that does not match its signed manifest is **fatal**:
|
|
328
335
|
|
|
329
336
|
```bash
|
|
330
|
-
dz doctor # ... signatures:
|
|
337
|
+
dz doctor # ... signatures: 23 verified, 0 unsigned, 0 TAMPERED (the gate is ARMED: keys/dz.pub committed)
|
|
331
338
|
dz doctor --require-signing # an unsigned pack becomes fatal too
|
|
332
339
|
dz upgrade # a TAMPERED pack aborts the upgrade
|
|
333
340
|
dz doctor --pubkey ./my.pub # verify against a key you pinned yourself
|
|
@@ -365,6 +372,37 @@ the release job gets a perfectly attested malicious package.
|
|
|
365
372
|
A ready-to-install workflow is at `features/publish-provenance/07_code_changes/publish.yml`; copy it to
|
|
366
373
|
`.github/workflows/` and add an `NPM_TOKEN` secret.
|
|
367
374
|
|
|
375
|
+
### Verified release — `dz release` (4 HARD gates in front of `dz publish`)
|
|
376
|
+
|
|
377
|
+
`dz publish` gates on guard / claim-check / signatures / provenance / files-whitelist — none of which
|
|
378
|
+
prove the code **works**. `dz release` is the opt-in VERIFY phase in front of it: (1) each package's full
|
|
379
|
+
test suite, (2) `pnpm audit --prod --audit-level high` (production deps by default — a dev-only advisory
|
|
380
|
+
is not a false gate; `--audit-dev` widens; an audit that *cannot run* also blocks, classified
|
|
381
|
+
`AUDIT_ERROR`), (3) `node --check` of every `dist/**/*.js` + bin file (a dist older than `src/` is
|
|
382
|
+
`STALE_DIST`, never checked as-is; a package that declares a `build` script but has **no** dist JS is
|
|
383
|
+
`MISSING_DIST` — an unbuilt package cannot ship), (4) smoke-boot every bin via `node <bin> --help` in a
|
|
384
|
+
throwaway cwd with a timeout. Packages without a `test` script are **named skips** in the report — never
|
|
385
|
+
silent passes; a template-only pack (no build, no artifacts, no bin) is a named `SKIP_NO_ARTIFACTS`.
|
|
386
|
+
|
|
387
|
+
```bash
|
|
388
|
+
dz release --dry-run # full gate plan, zero commands executed
|
|
389
|
+
dz release --filter harness-core # gates for one release set
|
|
390
|
+
dz release --affected # narrow to packages touched by working tree + last commit (git down ⇒ FAIL-OPEN to full set)
|
|
391
|
+
dz release --tag # on green: annotated git tag + short notes from recent commits
|
|
392
|
+
dz release --publish # on green: chain in-process into dz publish (its own dry-run protocol)
|
|
393
|
+
dz release --json # CI-parseable verdict; valid JSON on error paths too (with --publish, publish output rides INSIDE the envelope)
|
|
394
|
+
dz release --audit-dev # widen the audit gate to dev dependencies too
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
Expected flow: gate table (`✓/✗/○` per gate with per-package failures + named skips) → on any red gate:
|
|
398
|
+
release STOPPED, exit 1, best-effort `gh issue create` (no `gh`/network ⇒ loud log, never a block;
|
|
399
|
+
note: the issue body embeds first lines of failing output — pass `--no-issue` on public repos when
|
|
400
|
+
test/audit output may carry paths or environment details) → on
|
|
401
|
+
green: re-sign reminder (`dz sign …` — the publish signature gate is refuse-unsigned), then the ready
|
|
402
|
+
`dz publish` command printed **without** `--yes`: live publish stays your explicit act. **When to use:**
|
|
403
|
+
before a multi-package npm release, or whenever a broken dist/bin must be impossible to ship; keep plain
|
|
404
|
+
`dz publish` for routine pushes. `dz publish` itself is byte-identical whether or not release exists.
|
|
405
|
+
|
|
368
406
|
### Skill Packs (23 packs · 179 skills)
|
|
369
407
|
|
|
370
408
|
Each pack is an npm package — click through for the **full per-skill documentation** (what each skill does + how to trigger it). Install a whole pack with `dz install <pkg>`, or pick skills with `dz init --select` / a `--preset`.
|
|
@@ -443,7 +481,7 @@ Get the whole set with `dz init --target claude-code --preset meta`, or pick one
|
|
|
443
481
|
|
|
444
482
|
> **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.
|
|
445
483
|
|
|
446
|
-
## All Commands (
|
|
484
|
+
## All Commands (55)
|
|
447
485
|
|
|
448
486
|
```
|
|
449
487
|
dz setup --target <name> [--preset <name>] [--select id,id,...] [--skills-dir <dir>] [--memory agentdb] [--no-memory] [--no-hooks] [--install-driver] [--force]
|
|
@@ -503,6 +541,7 @@ dz sbom --pack <dir> [--out <file>] # emit the CycloneDX 1.5 SBOM for a pack s
|
|
|
503
541
|
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)
|
|
504
542
|
dz feature-adr-setup --guards [--loc-cap <n>] [--apply] # P3: scaffold DETERMINISTIC guard tests into the project — guards.config.json + a zero-dependency check.mjs runner (LOC cap, secret scan, frozen-file sha256 pins, waivers-with-reasons); wire `node architecture/guards/check.mjs` into CI
|
|
505
543
|
dz publish [--filter <name>] [--bump-only] [--claim-check <off|warn|error>] (dry-run by default; pass --yes/--confirm to go live; claim-check gate defaults to warn — surfaces README claim findings, never blocks)
|
|
544
|
+
dz release [--filter <name>] [--affected] [--audit-dev] [--tag] [--publish] [--json] [--dry-run] [--no-issue] # VERIFIED release: 4 HARD gates in front of dz publish — package test suites, pnpm audit --prod >=high (--audit-dev widens), node --check of every dist/bin file (unbuilt package with a build script ⇒ MISSING_DIST fail), bin smoke-boot "node <bin> --help" (temp cwd + timeout); --affected narrows to git-touched packages (fail-open); any red gate STOPS the release (exit 1) + best-effort gh issue; green ⇒ re-sign reminder, then the ready dz publish command (never with --yes injected)
|
|
506
545
|
dz auto-canonicalize --source <github-url> --pack <skills-pack>
|
|
507
546
|
dz sync-upstream [--package <dir>] [--list] [--all]
|
|
508
547
|
dz drift-check [--all] [--json] [--project <dir>] # CI gate: exit 1 on NEW shared-skill drift (baseline: .dz/drift-allowlist.json; --all incl .claude dogfood)
|
|
@@ -2059,7 +2098,7 @@ npx @dzhechkov/p-replicator init
|
|
|
2059
2098
|
|
|
2060
2099
|
## Status
|
|
2061
2100
|
|
|
2062
|
-
`v0.3.
|
|
2101
|
+
`v0.3.234` — published on npm. Also available as [Claude Plugin](#claude-plugin). Part of [DZ Harness Hub](https://github.com/djd1m/dz-harness-hub).
|
|
2063
2102
|
|
|
2064
2103
|
## Claude Plugin
|
|
2065
2104
|
|
package/dist/cli.d.ts
CHANGED
|
@@ -13,6 +13,23 @@ export interface CliIo {
|
|
|
13
13
|
* command that needs it (`brain ground`), and never when stdin is a TTY (nothing piped).
|
|
14
14
|
*/
|
|
15
15
|
readonly stdin?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Test seam for `dz release`: overrides subprocess execution for gate steps and the
|
|
18
|
+
* gh/git side channels (production leaves it unset → real `execSync`, stdio piped).
|
|
19
|
+
* A scripted runner makes failing gates, gh outages, and git-tag failures testable
|
|
20
|
+
* without spawning anything.
|
|
21
|
+
*/
|
|
22
|
+
readonly releaseRunner?: ReleaseExecRunner;
|
|
16
23
|
}
|
|
24
|
+
/** Injected subprocess runner used by `dz release` (see {@link CliIo.releaseRunner}). */
|
|
25
|
+
export type ReleaseExecRunner = (cmd: string, opts: {
|
|
26
|
+
readonly cwd: string;
|
|
27
|
+
readonly timeoutMs: number;
|
|
28
|
+
}) => {
|
|
29
|
+
exitCode: number;
|
|
30
|
+
stdout: string;
|
|
31
|
+
stderr: string;
|
|
32
|
+
timedOut?: boolean;
|
|
33
|
+
};
|
|
17
34
|
export declare function runCli(argv: string[], io?: CliIo): Promise<number>;
|
|
18
35
|
//# sourceMappingURL=cli.d.ts.map
|
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;AA4PH,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;AAguK9E,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE,KAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA8I5E"}
|
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, 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, assertKeyOutsideTree, decidePublishGate, 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, 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';
|
|
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
|
|
@@ -32,6 +32,7 @@ Usage:
|
|
|
32
32
|
dz sign --pack <dir> --key <path-outside-repo> (Ed25519 manifest + CycloneDX SBOM for a pack)
|
|
33
33
|
dz verify-pack --pack <dir> [--pubkey <path>] (signature check; fail-closed; key from the repo, never the pack)
|
|
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
|
+
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)
|
|
35
36
|
dz setup --target <name> [--preset <name>] [--select id,id,...] [--skills-dir <dir>] [--project <dir>] [--memory agentdb] [--no-memory] [--no-hooks] [--install-driver] [--force] [--enrich]
|
|
36
37
|
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)
|
|
37
38
|
dz teach --from-json <file> [--project <dir>] [--no-mirror] (bulk-import a 'dz recall --all --json' export — share a learned store across machines)
|
|
@@ -2784,25 +2785,9 @@ async function cmdAutoCanonicalize(options, cwd, write) {
|
|
|
2784
2785
|
}
|
|
2785
2786
|
/** The pinned trust root. A key inside the artifact under verification is data, not a key (ADR-001). */
|
|
2786
2787
|
const TRUST_ROOT_REL = 'keys/dz.pub';
|
|
2787
|
-
function
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
for (const e of readdirSync(d, { withFileTypes: true })) {
|
|
2791
|
-
if (e.name === 'node_modules' || e.name === '.git')
|
|
2792
|
-
continue;
|
|
2793
|
-
if (e.name === MANIFEST_NAME || e.name === SBOM_NAME)
|
|
2794
|
-
continue;
|
|
2795
|
-
const abs = join(d, e.name);
|
|
2796
|
-
const r = rel ? rel + '/' + e.name : e.name;
|
|
2797
|
-
if (e.isDirectory())
|
|
2798
|
-
walk(abs, r);
|
|
2799
|
-
else if (e.isFile())
|
|
2800
|
-
out.push(r);
|
|
2801
|
-
}
|
|
2802
|
-
};
|
|
2803
|
-
walk(dir, '');
|
|
2804
|
-
return out.sort();
|
|
2805
|
-
}
|
|
2788
|
+
// One walk to rule both: the SIGN file list is core's listPackFiles — the SAME function verify uses for
|
|
2789
|
+
// its added-file sweep, so the two can never drift apart again (the 10-false-TAMPERED lesson, task #36).
|
|
2790
|
+
const packFiles = (dir) => listSignablePackFiles(dir);
|
|
2806
2791
|
/**
|
|
2807
2792
|
* The consumer-side apply-leg (ADR-001, verify-apply-leg). All security content lives in the two pure
|
|
2808
2793
|
* functions in harness-core; this only resolves paths, reads bytes, and reports.
|
|
@@ -3276,6 +3261,308 @@ function cmdPublish(options, flags, cwd, write) {
|
|
|
3276
3261
|
}
|
|
3277
3262
|
return report.errors > 0 ? 1 : 0;
|
|
3278
3263
|
}
|
|
3264
|
+
/* ------------------------------------------------------------------ */
|
|
3265
|
+
/* dz release — verified-release conveyor (feature release-verified, */
|
|
3266
|
+
/* ADR-001): 4 HARD gates in FRONT of the untouched dz publish. */
|
|
3267
|
+
/* ------------------------------------------------------------------ */
|
|
3268
|
+
/**
|
|
3269
|
+
* The single executor of the pure engine's {@link GateStep} plan. ALL subprocess side effects
|
|
3270
|
+
* of the release live here (engine purity, NFR-1). Contract highlights:
|
|
3271
|
+
*
|
|
3272
|
+
* - ALL four gates execute even after an earlier gate fails (AM-9/FR-6 — no fail-fast hiding);
|
|
3273
|
+
* one red gate still reddens the release.
|
|
3274
|
+
* - Smoke steps run in a THROWAWAY cwd (AM-4): skills bins are installers that mutate
|
|
3275
|
+
* `.claude/` on default action.
|
|
3276
|
+
* - gh issue + git tag are best-effort periphery: loud on failure, NEVER verdict- or
|
|
3277
|
+
* exit-code-affecting (FR-7/FR-8, AM-6).
|
|
3278
|
+
* - On green it never injects `--yes/--confirm/--no-dry-run` into the printed/chained publish
|
|
3279
|
+
* (AM-5): live publish stays the operator's explicit act, and `--publish` chains into
|
|
3280
|
+
* publish's own default (dry-run) protocol with ALL its gates untouched.
|
|
3281
|
+
*/
|
|
3282
|
+
function cmdRelease(options, flags, cwd, write, runner) {
|
|
3283
|
+
const json = flags.has('json');
|
|
3284
|
+
const warnings = [];
|
|
3285
|
+
const say = (line) => {
|
|
3286
|
+
if (!json)
|
|
3287
|
+
write(line);
|
|
3288
|
+
};
|
|
3289
|
+
const loud = (line) => {
|
|
3290
|
+
warnings.push(line);
|
|
3291
|
+
if (!json)
|
|
3292
|
+
write(line);
|
|
3293
|
+
};
|
|
3294
|
+
const emitJson = (obj) => {
|
|
3295
|
+
if (json)
|
|
3296
|
+
write(JSON.stringify({ ...obj, warnings }, null, 2));
|
|
3297
|
+
};
|
|
3298
|
+
// G9 reuse-never-copy: the engine's firstOutputLine, not a byte-parallel local copy.
|
|
3299
|
+
const oneLine = firstOutputLine;
|
|
3300
|
+
// Strict allowlist (cmdPublish's typo-rejection pattern) — a mistyped flag is NEVER
|
|
3301
|
+
// swallowed, and the rejection honors the --json contract too (AC-7).
|
|
3302
|
+
// `--verified` is accepted but not read: verified is the DEFAULT and only mode of
|
|
3303
|
+
// `dz release` in this slice — the flag exists so the branded invocation is not a typo error.
|
|
3304
|
+
const allowedFlags = new Set(['verified', 'tag', 'publish', 'json', 'dry-run', 'no-issue', 'affected', 'audit-dev', 'help']);
|
|
3305
|
+
const allowedOptions = new Set(['filter']);
|
|
3306
|
+
const allowedHelp = ' allowed: --verified, --filter <substr>, --affected, --audit-dev, --tag, --publish, --json, --dry-run, --no-issue';
|
|
3307
|
+
const rejectUnknown = (key) => {
|
|
3308
|
+
if (json)
|
|
3309
|
+
write(JSON.stringify({ error: `unknown option --${key}`, allowed: allowedHelp.trim(), publishAction: 'blocked', exitCode: 1 }, null, 2));
|
|
3310
|
+
else {
|
|
3311
|
+
write(`dz release: unknown option --${key}`);
|
|
3312
|
+
write(allowedHelp);
|
|
3313
|
+
}
|
|
3314
|
+
return 1;
|
|
3315
|
+
};
|
|
3316
|
+
for (const flag of flags)
|
|
3317
|
+
if (!allowedFlags.has(flag))
|
|
3318
|
+
return rejectUnknown(flag);
|
|
3319
|
+
for (const key of options.keys()) {
|
|
3320
|
+
if (key.startsWith('_positional_'))
|
|
3321
|
+
continue;
|
|
3322
|
+
if (!allowedOptions.has(key))
|
|
3323
|
+
return rejectUnknown(key);
|
|
3324
|
+
}
|
|
3325
|
+
// --filter mirrors publish semantics: trim + drop empties; empty result is an ERROR,
|
|
3326
|
+
// never "match all" (the publish P0 regression this deliberately copies).
|
|
3327
|
+
const filterStr = options.get('filter');
|
|
3328
|
+
let filter;
|
|
3329
|
+
if (filterStr !== undefined) {
|
|
3330
|
+
filter = filterStr.split(',').map((s) => s.trim()).filter((s) => s.length > 0);
|
|
3331
|
+
if (filter.length === 0) {
|
|
3332
|
+
if (json)
|
|
3333
|
+
write(JSON.stringify({ error: '--filter requires a non-empty comma-separated list of package-name substrings', publishAction: 'blocked', exitCode: 1 }, null, 2));
|
|
3334
|
+
else
|
|
3335
|
+
write('dz release: --filter requires a non-empty comma-separated list of package-name substrings');
|
|
3336
|
+
return 1;
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
// DETECT — real facts via the engine's one fs seam (discoverPackages underneath).
|
|
3340
|
+
let factsList;
|
|
3341
|
+
try {
|
|
3342
|
+
factsList = collectPackageFacts(cwd, filter);
|
|
3343
|
+
}
|
|
3344
|
+
catch (err) {
|
|
3345
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3346
|
+
if (json)
|
|
3347
|
+
write(JSON.stringify({ error: msg, publishAction: 'blocked', exitCode: 1 }, null, 2));
|
|
3348
|
+
else
|
|
3349
|
+
write(`dz release: ${msg}`);
|
|
3350
|
+
return 1;
|
|
3351
|
+
}
|
|
3352
|
+
if (factsList.length === 0) {
|
|
3353
|
+
// C-6: outside a workspace (or an over-narrow filter) — explain and exit non-zero, never throw.
|
|
3354
|
+
const msg = 'no publishable packages found under packages/@dzhechkov — run from the monorepo root (or widen --filter)';
|
|
3355
|
+
if (json)
|
|
3356
|
+
write(JSON.stringify({ error: msg, packages: [], publishAction: 'blocked', exitCode: 1 }, null, 2));
|
|
3357
|
+
else
|
|
3358
|
+
write(`dz release: ${msg}`);
|
|
3359
|
+
return 1;
|
|
3360
|
+
}
|
|
3361
|
+
// Executor: default = real execSync with piped stdio + timeout; injectable for tests.
|
|
3362
|
+
// Defined BEFORE planning because --affected needs read-only git diffs at DETECT time.
|
|
3363
|
+
const run = runner ??
|
|
3364
|
+
((cmd, opts) => {
|
|
3365
|
+
try {
|
|
3366
|
+
const stdout = execSync(cmd, { cwd: opts.cwd, stdio: 'pipe', encoding: 'utf-8', timeout: opts.timeoutMs });
|
|
3367
|
+
return { exitCode: 0, stdout: stdout == null ? '' : String(stdout), stderr: '' };
|
|
3368
|
+
}
|
|
3369
|
+
catch (err) {
|
|
3370
|
+
const e = err;
|
|
3371
|
+
// execSync's timeout kills via signal and leaves status null — that IS the timeout shape.
|
|
3372
|
+
const timedOut = (e.status === null || e.status === undefined) && (e.signal != null || e.killed === true);
|
|
3373
|
+
return {
|
|
3374
|
+
exitCode: typeof e.status === 'number' ? e.status : 1,
|
|
3375
|
+
stdout: e.stdout == null ? '' : String(e.stdout),
|
|
3376
|
+
stderr: e.stderr == null || String(e.stderr).trim() === '' ? formatPublishError(e) : String(e.stderr),
|
|
3377
|
+
timedOut,
|
|
3378
|
+
};
|
|
3379
|
+
}
|
|
3380
|
+
});
|
|
3381
|
+
// AM-8/--affected: narrow the release set to packages touched by the working tree + last
|
|
3382
|
+
// commit. Selection is the PURE selectAffectedPackages; the two read-only git diffs are the
|
|
3383
|
+
// injected fact source. Any git failure ⇒ null ⇒ FAIL-OPEN to the full set (never zero).
|
|
3384
|
+
if (flags.has('affected')) {
|
|
3385
|
+
const dWork = run('git diff --name-only HEAD', { cwd, timeoutMs: 10_000 });
|
|
3386
|
+
const dLast = run('git diff --name-only HEAD~1..HEAD', { cwd, timeoutMs: 10_000 });
|
|
3387
|
+
const anyOk = dWork.exitCode === 0 || dLast.exitCode === 0;
|
|
3388
|
+
const changed = anyOk
|
|
3389
|
+
? [dWork, dLast]
|
|
3390
|
+
.filter((r) => r.exitCode === 0)
|
|
3391
|
+
.flatMap((r) => r.stdout.split('\n'))
|
|
3392
|
+
.map((s) => s.trim())
|
|
3393
|
+
.filter((s) => s.length > 0)
|
|
3394
|
+
: null;
|
|
3395
|
+
const selected = selectAffectedPackages(changed, factsList);
|
|
3396
|
+
if (changed === null)
|
|
3397
|
+
loud('dz release --affected: ⚠ git diff unavailable — FAIL-OPEN to the full package set');
|
|
3398
|
+
else
|
|
3399
|
+
say(`dz release --affected: ${selected.length}/${factsList.length} package(s) selected from ${changed.length} changed file(s)${selected.length === factsList.length ? ' (no narrowing — full set)' : ''}`);
|
|
3400
|
+
factsList = selected;
|
|
3401
|
+
}
|
|
3402
|
+
const plan = planReleaseGates(factsList, {
|
|
3403
|
+
monorepoRoot: cwd,
|
|
3404
|
+
pnpmLockPresent: existsSync(join(cwd, 'pnpm-lock.yaml')),
|
|
3405
|
+
includeDevDeps: flags.has('audit-dev'),
|
|
3406
|
+
});
|
|
3407
|
+
// --dry-run: print the full plan, execute NOTHING (deterministic, byte-testable preview).
|
|
3408
|
+
if (flags.has('dry-run')) {
|
|
3409
|
+
if (json) {
|
|
3410
|
+
write(JSON.stringify({ dryRun: true, packages: plan.packages, steps: plan.steps, skips: plan.skips, warnings }, null, 2));
|
|
3411
|
+
return 0;
|
|
3412
|
+
}
|
|
3413
|
+
write(`\ndz release --dry-run — plan only, zero commands executed (${plan.packages.length} package(s))`);
|
|
3414
|
+
for (const gate of ['tests', 'audit', 'syntax', 'smoke']) {
|
|
3415
|
+
const steps = plan.steps.filter((s) => s.gate === gate);
|
|
3416
|
+
write(` ${gate} (${steps.length} step(s)):`);
|
|
3417
|
+
for (const s of steps) {
|
|
3418
|
+
if (s.kind === 'synthetic-fail')
|
|
3419
|
+
write(` ✗ [planned ${s.failClass ?? 'FAIL'}] ${s.pkg ?? ''}: ${s.reason}`);
|
|
3420
|
+
else
|
|
3421
|
+
write(` · ${s.cmd} (cwd=${s.tempCwd === true ? '<temp>' : s.cwd}, timeout=${s.timeoutMs}ms)`);
|
|
3422
|
+
}
|
|
3423
|
+
if (gate === 'tests')
|
|
3424
|
+
for (const sk of plan.skips)
|
|
3425
|
+
write(` ○ [${sk.class}] ${sk.pkg}: ${sk.reason}`);
|
|
3426
|
+
}
|
|
3427
|
+
write(' → run without --dry-run to execute the gates');
|
|
3428
|
+
return 0;
|
|
3429
|
+
}
|
|
3430
|
+
// VERIFY — execute EVERY exec step (AM-9: all 4 gates run and report even after a failure).
|
|
3431
|
+
const executions = [];
|
|
3432
|
+
let smokeTmp;
|
|
3433
|
+
const execSteps = plan.steps.filter((s) => s.kind !== 'synthetic-fail');
|
|
3434
|
+
say(`\ndz release — executing ${execSteps.length} gate step(s) across ${plan.packages.length} package(s)…`);
|
|
3435
|
+
for (const step of execSteps) {
|
|
3436
|
+
let stepCwd = step.cwd;
|
|
3437
|
+
if (step.tempCwd === true) {
|
|
3438
|
+
// AM-4: boot bins in a throwaway cwd so an installer-style bin cannot mutate the workspace.
|
|
3439
|
+
if (smokeTmp === undefined)
|
|
3440
|
+
smokeTmp = mkdtempSync(join(tmpdir(), 'dz-release-smoke-'));
|
|
3441
|
+
stepCwd = smokeTmp;
|
|
3442
|
+
}
|
|
3443
|
+
const started = Date.now();
|
|
3444
|
+
const r = run(step.cmd, { cwd: stepCwd, timeoutMs: step.timeoutMs });
|
|
3445
|
+
executions.push({
|
|
3446
|
+
stepId: step.id,
|
|
3447
|
+
exitCode: r.exitCode,
|
|
3448
|
+
stdout: r.stdout,
|
|
3449
|
+
stderr: r.stderr,
|
|
3450
|
+
durationMs: Date.now() - started,
|
|
3451
|
+
timedOut: r.timedOut,
|
|
3452
|
+
});
|
|
3453
|
+
}
|
|
3454
|
+
if (smokeTmp !== undefined) {
|
|
3455
|
+
try {
|
|
3456
|
+
rmSync(smokeTmp, { recursive: true, force: true });
|
|
3457
|
+
}
|
|
3458
|
+
catch { /* best-effort cleanup */ }
|
|
3459
|
+
}
|
|
3460
|
+
const verdict = classifyGateExecutions(plan, executions);
|
|
3461
|
+
// Report: per-gate ✓/✗/○ with package granularity + timestamp (NFR-5). Skips are named,
|
|
3462
|
+
// never folded into pass wording (AM-2: "N passed, M skipped", not "all tests passed").
|
|
3463
|
+
for (const g of verdict.gates) {
|
|
3464
|
+
const icon = g.status === 'pass' ? '✓' : g.status === 'fail' ? '✗' : '○';
|
|
3465
|
+
say(` ${icon} ${g.gate.padEnd(7)} ${g.status.toUpperCase().padEnd(4)} ${g.passed} passed, ${g.failures.length} failed, ${g.skips.length} skipped`);
|
|
3466
|
+
for (const f of g.failures)
|
|
3467
|
+
say(` [${f.class}] ${f.pkg !== undefined ? `${f.pkg}: ` : ''}${f.reason}`);
|
|
3468
|
+
for (const sk of g.skips)
|
|
3469
|
+
say(` [${sk.class}] ${sk.pkg}: ${sk.reason}`);
|
|
3470
|
+
}
|
|
3471
|
+
say(` verdict at ${verdict.timestamp}: ${verdict.publishAction === 'proceed' ? '✓ all gates green' : '✗ RELEASE BLOCKED'}`);
|
|
3472
|
+
const invocation = `dz release${filterStr !== undefined ? ` --filter ${filterStr}` : ''}`;
|
|
3473
|
+
const shq = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
|
|
3474
|
+
if (verdict.publishAction === 'blocked') {
|
|
3475
|
+
say(`dz release: ✗ release STOPPED — ${verdict.blockedBy.join('; ')}`);
|
|
3476
|
+
// FR-7 / AM-6: best-effort issue — a courier, never a judge. Loud on any failure;
|
|
3477
|
+
// the verdict and exit code are ALREADY decided and cannot change here.
|
|
3478
|
+
let issueUrl;
|
|
3479
|
+
if (!flags.has('no-issue')) {
|
|
3480
|
+
const issue = buildFailureIssue(verdict, { invocation });
|
|
3481
|
+
const probe = run('command -v gh', { cwd, timeoutMs: 10_000 });
|
|
3482
|
+
if (probe.exitCode !== 0) {
|
|
3483
|
+
loud(`dz release: ⚠ gh unavailable — file the issue manually: ${issue.title}`);
|
|
3484
|
+
}
|
|
3485
|
+
else {
|
|
3486
|
+
const res = run(`gh issue create --title ${shq(issue.title)} --body ${shq(issue.body)}`, { cwd, timeoutMs: 30_000 });
|
|
3487
|
+
if (res.exitCode === 0) {
|
|
3488
|
+
issueUrl = oneLine(res.stdout) || undefined;
|
|
3489
|
+
say(`dz release: gh issue created${issueUrl !== undefined ? `: ${issueUrl}` : ''}`);
|
|
3490
|
+
}
|
|
3491
|
+
else {
|
|
3492
|
+
loud(`dz release: ⚠ gh issue creation failed (${oneLine(res.stderr, res.stdout) || 'unknown error'}) — file the issue manually: ${issue.title}`);
|
|
3493
|
+
}
|
|
3494
|
+
}
|
|
3495
|
+
}
|
|
3496
|
+
emitJson({
|
|
3497
|
+
gates: verdict.gates,
|
|
3498
|
+
ok: verdict.ok,
|
|
3499
|
+
blockedBy: verdict.blockedBy,
|
|
3500
|
+
skipped: verdict.skipped,
|
|
3501
|
+
publishAction: verdict.publishAction,
|
|
3502
|
+
timestamp: verdict.timestamp,
|
|
3503
|
+
...(issueUrl !== undefined ? { issueUrl } : {}),
|
|
3504
|
+
exitCode: 1,
|
|
3505
|
+
});
|
|
3506
|
+
return 1;
|
|
3507
|
+
}
|
|
3508
|
+
// Success path. FR-8: --tag is best-effort trimming — loud on failure, never gate-affecting.
|
|
3509
|
+
let tagName;
|
|
3510
|
+
if (flags.has('tag')) {
|
|
3511
|
+
const sha = run('git rev-parse --short HEAD', { cwd, timeoutMs: 10_000 });
|
|
3512
|
+
const log = run('git log --oneline -n 15 --no-decorate', { cwd, timeoutMs: 10_000 });
|
|
3513
|
+
const notes = buildReleaseNotes(log.exitCode === 0 ? log.stdout.split('\n') : []);
|
|
3514
|
+
tagName = releaseTagName(new Date(), sha.exitCode === 0 ? sha.stdout.trim() : '');
|
|
3515
|
+
const tag = run(`git tag -a ${tagName} -m ${shq(notes)}`, { cwd, timeoutMs: 10_000 });
|
|
3516
|
+
if (tag.exitCode === 0) {
|
|
3517
|
+
say(`dz release: tagged ${tagName} (annotated with short release notes from recent commits)`);
|
|
3518
|
+
say('dz release: note — the tag attests a VERIFIED tree, not a completed publish');
|
|
3519
|
+
}
|
|
3520
|
+
else {
|
|
3521
|
+
loud(`dz release: ⚠ git tag failed (${oneLine(tag.stderr, tag.stdout) || 'unknown error'}) — non-blocking; tag manually: git tag -a ${tagName}`);
|
|
3522
|
+
}
|
|
3523
|
+
}
|
|
3524
|
+
// FR-10 / AC-8: re-sign reminder BEFORE the handoff — publish's signature gate is
|
|
3525
|
+
// refuse-unsigned, and gates 3–4 often follow a rebuild.
|
|
3526
|
+
say('dz release: reminder — re-sign any rebuilt packs BEFORE publishing (dz sign --pack <dir> --key <path-outside-repo>); the publish signature gate is refuse-unsigned');
|
|
3527
|
+
// FR-9 / AM-5: the handoff NEVER injects --yes/--confirm/--no-dry-run — live publish stays
|
|
3528
|
+
// the operator's explicit, loud-bannered act inside dz publish itself.
|
|
3529
|
+
const publishCommand = `dz publish${filterStr !== undefined ? ` --filter ${filterStr}` : ''}`;
|
|
3530
|
+
let publishExit;
|
|
3531
|
+
let publishOutput;
|
|
3532
|
+
if (flags.has('publish')) {
|
|
3533
|
+
say(`dz release: ✓ gates green — chaining into ${publishCommand} in-process (ALL publish gates + its dry-run-by-default protocol run untouched)`);
|
|
3534
|
+
const pubOptions = new Map(filterStr !== undefined ? [['filter', filterStr]] : []);
|
|
3535
|
+
// --json contract: the chained publish must NOT print prose before the JSON envelope —
|
|
3536
|
+
// capture its lines and carry them INSIDE the envelope instead (stdout stays one JSON doc).
|
|
3537
|
+
if (json) {
|
|
3538
|
+
publishOutput = [];
|
|
3539
|
+
const captured = publishOutput;
|
|
3540
|
+
publishExit = cmdPublish(pubOptions, new Set(), cwd, (line) => {
|
|
3541
|
+
captured.push(line);
|
|
3542
|
+
});
|
|
3543
|
+
}
|
|
3544
|
+
else {
|
|
3545
|
+
publishExit = cmdPublish(pubOptions, new Set(), cwd, write);
|
|
3546
|
+
}
|
|
3547
|
+
}
|
|
3548
|
+
else {
|
|
3549
|
+
say(`dz release: ✓ all gates green — publish when ready: ${publishCommand}`);
|
|
3550
|
+
}
|
|
3551
|
+
emitJson({
|
|
3552
|
+
gates: verdict.gates,
|
|
3553
|
+
ok: verdict.ok,
|
|
3554
|
+
blockedBy: verdict.blockedBy,
|
|
3555
|
+
skipped: verdict.skipped,
|
|
3556
|
+
publishAction: verdict.publishAction,
|
|
3557
|
+
timestamp: verdict.timestamp,
|
|
3558
|
+
...(tagName !== undefined ? { tag: tagName } : {}),
|
|
3559
|
+
publishCommand,
|
|
3560
|
+
...(publishExit !== undefined ? { publishExit } : {}),
|
|
3561
|
+
...(publishOutput !== undefined ? { publishOutput } : {}),
|
|
3562
|
+
exitCode: publishExit ?? 0,
|
|
3563
|
+
});
|
|
3564
|
+
return publishExit ?? 0;
|
|
3565
|
+
}
|
|
3279
3566
|
function cmdRegistry(options, cwd, write) {
|
|
3280
3567
|
const registry = buildRegistry(cwd);
|
|
3281
3568
|
if (registry.totalSkills === 0) {
|
|
@@ -5203,6 +5490,8 @@ export async function runCli(argv, io = {}) {
|
|
|
5203
5490
|
return await cmdAutoCanonicalize(options, cwd, write);
|
|
5204
5491
|
case 'publish':
|
|
5205
5492
|
return cmdPublish(options, flags, cwd, write);
|
|
5493
|
+
case 'release':
|
|
5494
|
+
return cmdRelease(options, flags, cwd, write, io.releaseRunner);
|
|
5206
5495
|
case 'registry':
|
|
5207
5496
|
return cmdRegistry(options, cwd, write);
|
|
5208
5497
|
case 'benchmark':
|