@dzhechkov/harness-cli 0.3.233 → 0.3.235

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.dz-manifest.json CHANGED
@@ -9,7 +9,7 @@
9
9
  },
10
10
  {
11
11
  "path": ".agentic-qe/brain.rvf.lock",
12
- "sha256": "028658845c4fc1e9bd9487b575be0aee727d477fa9ad1b08c26299ce61f622c3"
12
+ "sha256": "99fa5178a48b6953b8f3a6f1690204a3b5449004b7dd105e7a3155ad3658266c"
13
13
  },
14
14
  {
15
15
  "path": ".agentic-qe/memory.db",
@@ -17,11 +17,11 @@
17
17
  },
18
18
  {
19
19
  "path": ".agentic-qe/memory.db-shm",
20
- "sha256": "23ae1f330a8fad23eabe444e89dc9d4588297cc1abca5929356eb6fec9f5079d"
20
+ "sha256": "cab99dd5d4068013a0c5f4f1c2d29dbec07729dc47c1d77adba6a53b773d0026"
21
21
  },
22
22
  {
23
23
  "path": ".agentic-qe/memory.db-wal",
24
- "sha256": "2eade3dbce1a9fd33a0775269af13fe97c62fdf1dbf44a8569ba0e7341315433"
24
+ "sha256": "2cc80ec378c1f9cd2588676588ee1b73bfe4f2f7ef3b96879c295eb86dccd2c2"
25
25
  },
26
26
  {
27
27
  "path": ".agentic-qe/patterns.rvf",
@@ -33,7 +33,7 @@
33
33
  },
34
34
  {
35
35
  "path": ".agentic-qe/patterns.rvf.lock",
36
- "sha256": "b28a956544f4358d57df98fc6c8df0b8f5d5cd8e8b102cee80ed3aabdf0a112b"
36
+ "sha256": "84842f3916c558b1f4f2da2a5b0236587f146dda1f6ab4ba4c31ed7b8b17f943"
37
37
  },
38
38
  {
39
39
  "path": "CHANGELOG.md",
@@ -41,7 +41,7 @@
41
41
  },
42
42
  {
43
43
  "path": "README.md",
44
- "sha256": "406be5cb9294367793daed191b2418bb99669186c8ead8a3a3f994f0db36e1b6"
44
+ "sha256": "378ef9c8fcde4e77594b29a5f043b57f880f5df2b40fce679addfae756670154"
45
45
  },
46
46
  {
47
47
  "path": "coverage/coverage-final.json",
@@ -69,27 +69,27 @@
69
69
  },
70
70
  {
71
71
  "path": "dist/cli.d.ts",
72
- "sha256": "d304769099f8a7f79ba6c295e0348c2aa211527b050dbfff597f7e87c2b80505"
72
+ "sha256": "628c55bb864dd8a8f337a9cb3fe4271f0df7c0bf9237b1150427171f0577fd57"
73
73
  },
74
74
  {
75
75
  "path": "dist/cli.d.ts.map",
76
- "sha256": "680727bef753625ed99fd9d79ab846c9024608ef0f88d19ee2c8aec25eccf130"
76
+ "sha256": "45c0c1328829a56cfe0ea0ebc74c15477a00bb683b496404688ab4b7b7a88ef8"
77
77
  },
78
78
  {
79
79
  "path": "dist/cli.js",
80
- "sha256": "672bc2fd640d9a92b5af7bfe2880843a770bfedd610f679ae022892fa7c8e66c"
80
+ "sha256": "20974a159438ac73a4c308cdb3710154dd9fe0450ed5184f6f3938792380fece"
81
81
  },
82
82
  {
83
83
  "path": "dist/cli.js.map",
84
- "sha256": "08ec7cf51a49ec818b789cbb6f32b8fc71756e498c99d8756212d16329a8b147"
84
+ "sha256": "bfb9ecebeb1c3cc631f2c8e7f46af4318e3698789c9dda49aaebbe26fe1c7fa7"
85
85
  },
86
86
  {
87
87
  "path": "dist/index.d.ts",
88
- "sha256": "c1ddc3aa44ca13264d54a72ad8376d8321de06ab48262eb2a6c8d5023c011bb6"
88
+ "sha256": "aaac0134a074fd06e9b5e09daa952ef6353510e481885da51a6e4f2e92beba35"
89
89
  },
90
90
  {
91
91
  "path": "dist/index.d.ts.map",
92
- "sha256": "a8697ddfabf35947e17a255fce26dfee141f2184f6789c32ba1de75dcd80e36c"
92
+ "sha256": "459c3381a9fbcd3378b623f9fe103a8d3337e4025a2f546b6d40e4d9dd4e0f70"
93
93
  },
94
94
  {
95
95
  "path": "dist/index.js",
@@ -105,7 +105,7 @@
105
105
  },
106
106
  {
107
107
  "path": "package.json",
108
- "sha256": "b48c494a4d4e2126e033c2d0019e463b9ad6cd3bae7a2aa0da296eba1ab66fcf"
108
+ "sha256": "c03aafaeaf663bb47b756608049bf77f6460615f1e806054644ec830c524cf72"
109
109
  },
110
110
  {
111
111
  "path": "src/bin.ts",
@@ -113,15 +113,15 @@
113
113
  },
114
114
  {
115
115
  "path": "src/cli.ts",
116
- "sha256": "14ca947c27f366c1377c129b3def86cdd4577536249e44f0009f31884fff9018"
116
+ "sha256": "6b8d21416576a2744b5d9bf973d7b878c255495829fcf3679b183a977abc55a2"
117
117
  },
118
118
  {
119
119
  "path": "src/index.ts",
120
- "sha256": "f0aea91d07867603d177da6029f97851ced0b91b62e9db5d0bab5b464f794a91"
120
+ "sha256": "395b27b317e575b94a1049cdb284db185e0ec382fbae1e588c78ff2398b2563f"
121
121
  },
122
122
  {
123
123
  "path": "test/cli.test.ts",
124
- "sha256": "d9374e1cc23923c2d5c4cca1e228f31e2c3465f187d1c84cf3f50ad947401537"
124
+ "sha256": "09472c195254c359f14256c39e8c7afd08c0b71aadb57cd077534f0d7c482005"
125
125
  },
126
126
  {
127
127
  "path": "tsconfig.json",
@@ -133,5 +133,5 @@
133
133
  }
134
134
  ]
135
135
  },
136
- "signature": "Vt+64ve0S3VUr7RQQ1sKTxBPCcNyOcMW/F1auDLC8k9ljNGhORaNolB0WZ9xdXN2ZsGz8jQLH0ttajx6eHLxCA=="
136
+ "signature": "lQbeQtlApDnUL/w9FcwEmMPS6jGGZ5eTBN+VVlDt0+Hh3QKOsk9G3SK9kOpiCAqTzt/2/54p8lFSiP0FwNuzCQ=="
137
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
@@ -367,6 +372,37 @@ the release job gets a perfectly attested malicious package.
367
372
  A ready-to-install workflow is at `features/publish-provenance/07_code_changes/publish.yml`; copy it to
368
373
  `.github/workflows/` and add an `NPM_TOKEN` secret.
369
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
+
370
406
  ### Skill Packs (23 packs · 179 skills)
371
407
 
372
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`.
@@ -445,7 +481,7 @@ Get the whole set with `dz init --target claude-code --preset meta`, or pick one
445
481
 
446
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.
447
483
 
448
- ## All Commands (54)
484
+ ## All Commands (55)
449
485
 
450
486
  ```
451
487
  dz setup --target <name> [--preset <name>] [--select id,id,...] [--skills-dir <dir>] [--memory agentdb] [--no-memory] [--no-hooks] [--install-driver] [--force]
@@ -505,6 +541,7 @@ dz sbom --pack <dir> [--out <file>] # emit the CycloneDX 1.5 SBOM for a pack s
505
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)
506
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
507
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)
508
545
  dz auto-canonicalize --source <github-url> --pack <skills-pack>
509
546
  dz sync-upstream [--package <dir>] [--list] [--all]
510
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)
@@ -1620,7 +1657,10 @@ dz guard (teach): ✗ BLOCK [checked: no-secrets, store-bloat-cap]
1620
1657
  hatch `--no-guard "<reason>"` requires a reason and is logged to `.dz/guard-audit.jsonl` — an override is
1621
1658
  visible, never silent. Calibrated against false gates: in a pnpm workspace, `workspace:*` in source is
1622
1659
  *correct* (pnpm rewrites it at publish), so the rule resolves each workspace dep to the version it would
1623
- ship as and blocks only a dep that would ship raw.
1660
+ ship as and blocks only a dep that would ship raw. The `readme-consistency` rule also holds the **docs
1661
+ site** in the contour: the command count on the Starlight CLI overview and the front-page tagline must
1662
+ match the cli README (the site once drifted 35→55 silently; now a mismatch is a warning here and a red
1663
+ test in CI). In a repo without the docs site those pairs simply aren't gathered — no false gate.
1624
1664
 
1625
1665
  ### `dz feature-adr-setup --guards` — when a project rule should be a TEST, not a reviewer's memory
1626
1666
 
@@ -2061,7 +2101,7 @@ npx @dzhechkov/p-replicator init
2061
2101
 
2062
2102
  ## Status
2063
2103
 
2064
- `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).
2104
+ `v0.3.235` — published on npm. Also available as [Claude Plugin](#claude-plugin). Part of [DZ Harness Hub](https://github.com/djd1m/dz-harness-hub).
2065
2105
 
2066
2106
  ## Claude Plugin
2067
2107
 
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;AAkPH,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;AAg8JD,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;AAovK9E,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, listSignablePackFiles, 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)
@@ -3260,6 +3261,308 @@ function cmdPublish(options, flags, cwd, write) {
3260
3261
  }
3261
3262
  return report.errors > 0 ? 1 : 0;
3262
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
+ }
3263
3566
  function cmdRegistry(options, cwd, write) {
3264
3567
  const registry = buildRegistry(cwd);
3265
3568
  if (registry.totalSkills === 0) {
@@ -3937,6 +4240,19 @@ function gatherReadmeCounts(root) {
3937
4240
  pairs.push({ label: 'commands (root CJM header vs cli All Commands)', a: cjm, b: cliAll });
3938
4241
  if (rootAll !== null && cliAll !== null)
3939
4242
  pairs.push({ label: 'All Commands (root vs cli)', a: rootAll, b: cliAll });
4243
+ // sitedoc in the readme-first contour (feature sitedoc-readme-first, ADR-001 + Codex QE AM-3/AM-4):
4244
+ // the docs SITE carries the same command count as the cli README. A MISSING file skips its pair
4245
+ // (target repo without sitedoc — missing-evidence contract). But a file that EXISTS and no longer
4246
+ // matches its anchored pattern emits a MISMATCH pair (a: -1) — silent non-extraction is the exact
4247
+ // disease this contour cures (AM-1 applies to the guard path too, not only the CI test).
4248
+ const sitePair = (rel, re, label) => {
4249
+ if (cliAll === null || !existsSync(join(root, rel)))
4250
+ return;
4251
+ const found = num(read(rel), re);
4252
+ pairs.push({ label, a: found ?? -1, b: cliAll });
4253
+ };
4254
+ sitePair('packages/@dzhechkov/sitedoc/src/content/docs/cli/overview.md', /^description: All (\d+) dz commands/m, 'commands (docs-site CLI overview description vs cli All Commands)');
4255
+ sitePair('packages/@dzhechkov/sitedoc/src/content/docs/index.mdx', /^\s*tagline: "[^"]*?(\d+) commands/m, 'commands (docs-site index hero tagline vs cli All Commands)');
3940
4256
  return pairs;
3941
4257
  }
3942
4258
  /** Gather the facts one op needs. All I/O is best-effort — a missing signal skips its rule, never crashes. */
@@ -5187,6 +5503,8 @@ export async function runCli(argv, io = {}) {
5187
5503
  return await cmdAutoCanonicalize(options, cwd, write);
5188
5504
  case 'publish':
5189
5505
  return cmdPublish(options, flags, cwd, write);
5506
+ case 'release':
5507
+ return cmdRelease(options, flags, cwd, write, io.releaseRunner);
5190
5508
  case 'registry':
5191
5509
  return cmdRegistry(options, cwd, write);
5192
5510
  case 'benchmark':