@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.
@@ -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. Every degenerate input (no manifest, empty file list, empty signature, no public key)
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: 3 verified, 0 unsigned, 0 TAMPERED, 0 unverifiable
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 (54)
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.179` — published on npm. Also available as [Claude Plugin](#claude-plugin). Part of [DZ Harness Hub](https://github.com/djd1m/dz-harness-hub).
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;AAiPH,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;CACzB;AA48JD,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE,KAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA4I5E"}
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 packFiles(dir) {
2788
- const out = [];
2789
- const walk = (d, rel) => {
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':