@dzhechkov/harness-cli 0.4.5 → 0.4.6

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/README.md CHANGED
@@ -1027,7 +1027,7 @@ Each pack is an npm package — click through for the **full per-skill documenta
1027
1027
  | [@dzhechkov/skills-qe](https://www.npmjs.com/package/@dzhechkov/skills-qe) | 20 | Quality engineering — test-gen, coverage, chaos, defect intelligence, QCSD swarms |
1028
1028
  | [@dzhechkov/skills-reasoning](https://www.npmjs.com/package/@dzhechkov/skills-reasoning) | 4 | Generic reasoning & code-quality — investigate (root-cause), solid (SOLID/TDD), karpathy-guidelines, agents-md-creator |
1029
1029
  | [@dzhechkov/skills-ecc](https://www.npmjs.com/package/@dzhechkov/skills-ecc) | 20 | Claude-Code engineering craft — agent architecture, autonomous loops, framework patterns |
1030
- | [@dzhechkov/skills-meta](https://www.npmjs.com/package/@dzhechkov/skills-meta) | 18 | Dev-process meta skills — explore, feature-adr, design-thinking, audit, skill-advisor |
1030
+ | [@dzhechkov/skills-meta](https://www.npmjs.com/package/@dzhechkov/skills-meta) | 19 | Dev-process meta skills — explore, feature-adr, design-thinking, audit, skill-advisor, loop-plan-author |
1031
1031
  | [@dzhechkov/skills-academic](https://www.npmjs.com/package/@dzhechkov/skills-academic) | 5 | Thesis-defense toolkit — dissertation review, questions, doc-check, defense eval |
1032
1032
  | [@dzhechkov/skills-news](https://www.npmjs.com/package/@dzhechkov/skills-news) | 3 | *dz-original* — news digests (`news-digest`) + delta watches (`news-monitor`) + bundled `goap-research-ed25519` verified-research backend (mandatory) |
1033
1033
  | [@dzhechkov/skills-idea2prd](https://www.npmjs.com/package/@dzhechkov/skills-idea2prd) | 1 | *dz-original* — `idea2prd-manual`: idea/problem → PRD+ADR+DDD+C4+Pseudocode+Tests+Completion (9 checkpoints); bundles the analyst trio as a sources.json-tracked vendor ([ADR-0001](https://github.com/djd1m/dz-harness-hub/blob/main/docs/adr/0001-skill-canonicalization-and-dependency-model.md)) |
@@ -1097,7 +1097,7 @@ Get the whole set with `dz init --target claude-code --preset meta`, or pick one
1097
1097
 
1098
1098
  Custom loops used to be born by copy-pasting a 1470-line workflow script; nothing deterministic
1099
1099
  checked the copy. The loop-designer meta-factory replaces that: a versioned typed plan
1100
- (`loop-plan/1`), a schema-driven generator, a 17-rule lint gate, and a local trace plane.
1100
+ (`loop-plan/1`), a schema-driven generator, an 18-rule lint gate, and a local trace plane.
1101
1101
 
1102
1102
  ```bash
1103
1103
  # 1. Scaffold a plan (pipeline | barrier | fanout | gate), edit the TODO prompts:
@@ -1116,7 +1116,7 @@ dz workflow render triage-loop.plan.json --o triage-loop.js
1116
1116
 
1117
1117
  # 4. Gate it (generated-loop CI mode; exit 0/1/3 — inconclusive is NEVER a pass):
1118
1118
  dz workflow-lint triage-loop.js --plan triage-loop.plan.json --require-plan
1119
- # → dz workflow-lint: PASS (mode=require-plan; 0 fail, 0 warn, 0 inconclusive over 17 rules)
1119
+ # → dz workflow-lint: PASS (mode=require-plan; 0 fail, 0 warn, 0 inconclusive over 18 rules)
1120
1120
 
1121
1121
  # 5. Run it via Workflow({scriptPath:'triage-loop.js', args:{traceDir:'/abs/run-dir'}}), then read
1122
1122
  # the run's own trace (seq-ordered; wallTime is diagnostic only):
@@ -1207,12 +1207,46 @@ dz workflow render audit.plan.json --o audit.loop.js
1207
1207
  # → wrote audit.loop.plan.json then audit.loop.js (exec-fp sha256:49feb8a2c1e17038…, blobs: trace)
1208
1208
 
1209
1209
  dz workflow-lint audit.loop.js --plan audit.loop.plan.json --require-plan
1210
- # → dz workflow-lint: PASS (mode=require-plan; 0 fail, 1 warn, 0 inconclusive over 17 rules)
1210
+ # → dz workflow-lint: PASS (mode=require-plan; 0 fail, 1 warn, 0 inconclusive over 18 rules)
1211
1211
  ```
1212
1212
 
1213
1213
  `inconclusive` is never a pass, and the rendered script keeps your hand edits across re-renders
1214
1214
  (USER regions are preserved).
1215
1215
 
1216
+ #### Declare each step's tool perimeter — `LoopStep.tools`
1217
+
1218
+ **When to use it:** any loop whose steps reach external systems (a ticket tracker, a wiki, a repo
1219
+ host) through MCP, and you want the intended perimeter written down, well-formed, and gated rather
1220
+ than living in a prompt someone will edit.
1221
+
1222
+ ```jsonc
1223
+ // in your plan.json — one array per DISPATCHING step (agent | gate)
1224
+ {"stepId":"discovery","kind":"agent","phase":"Discovery",
1225
+ "tools":["gitlab:read","jira:read","wiki:read"], "budget":{"maxAgents":1}},
1226
+ {"stepId":"verdict-gate","kind":"gate","phase":"Discovery","deps":["discovery"],
1227
+ "tools":[], "budget":{"maxAgents":1}}
1228
+ ```
1229
+
1230
+ `tools: []` is the **meaningful** value for a step that touches no external tool — absence is a lint
1231
+ finding, because silence is never permission:
1232
+
1233
+ ```bash
1234
+ dz workflow-lint my-loop.js --plan my-loop.plan.json --require-plan
1235
+ # → FAIL tool-perimeter-declared: dispatching step sa declares no `tools` perimeter — absence
1236
+ # FLAGS: silence is never permission (declare `tools: []` if the step touches no external tool)
1237
+ ```
1238
+
1239
+ A non-empty array is **enacted**, not decorative: it renders a fixed contract line into that step's
1240
+ prompt, and the lint rule cross-checks the script against the plan (so a plan edit you forgot to
1241
+ re-render fails too). Severity is staged — **WARN by default, FAIL only under `--require-plan`.**
1242
+
1243
+ **It is a DECLARATION, not enforcement.** `agent()` exposes no tool restriction; real enforcement
1244
+ lives at the MCP server. Do not read this field as a sandbox.
1245
+
1246
+ Worked consumer in this repo: `.claude/workflows/cfr-pipeline.js` — a 12-step, 6-gate Customer
1247
+ Feature Request pipeline with seven typed terminal exits, whose five source-touching stages each
1248
+ declare their perimeter (`features/cfr-pipeline/`).
1249
+
1216
1250
  #### Step 4 — run it (Claude Code), read it back (anywhere)
1217
1251
 
1218
1252
  In **Claude Code**, paste exactly this shape:
@@ -1425,7 +1459,7 @@ dz drift-check [--all] [--json] [--project <dir>] # CI gate: exit 1 on N
1425
1459
  dz sync-canonical <skill> [--check] [--from <dir>] [--auto] [--project <dir>] # heal every copy from skills-meta/<skill> or --from; no canonical + --check = compare copies to each other (exit 1 on drift); no canonical + write = refuse unless --auto (LOUD, picks most-complete copy); --check writes nothing
1426
1460
  dz scout [--topics <list>] [--since <date>] [--deep] [--output <file>] [--diff] [--report]
1427
1461
  dz workflow init --name <n> [--pattern pipeline|barrier|fanout|gate] [--o <plan.json>] | validate <plan.json> [--json] | render <plan.json> --o <script.js> [--check] [--force] | blobs [--check] # loop-plan/1 authoring (the ADR-005 templates are retired)
1428
- dz workflow-lint <script.js> [--plan <plan.json>] [--require-plan|--legacy] [--json] # 17-rule deterministic gate; exit 0/1/3 — inconclusive is never a pass
1462
+ dz workflow-lint <script.js> [--plan <plan.json>] [--require-plan|--legacy] [--json] # 18-rule deterministic gate; exit 0/1/3 — inconclusive is never a pass
1429
1463
  dz workflow-trace <runDir|--slug <s>|--run <id>> [--invariants <plan.json>] [--html <out.html>] [--json] # timeline + SEQ invariant runner over the loop's own trace.jsonl
1430
1464
  dz workflow-trace export <run> --o <file> [--include-pairs --yes] [--strict] # one run's telemetry as ONE movable file
1431
1465
  dz workflow-trace import <bundle> --into <root> [--force] [--with-pairs] # reconstruct that run; fail-closed against clobbering
@@ -1440,6 +1474,44 @@ dz import-ecc [--local-path <dir>] [--select id,id,...] [--limit N] [--out
1440
1474
  dz help
1441
1475
  ```
1442
1476
 
1477
+ ## Global: `dz --version` / `-v` / `dz version`
1478
+
1479
+ ```bash
1480
+ dz --version # 0.4.6
1481
+ dz --version --json # {"name":"dz","version":"0.4.6","node":"v22.22.0","schemas":{"loopPlan":"loop-plan/1"}}
1482
+ ```
1483
+
1484
+ One line, exit 0. Unresolvable ⇒ the literal `unknown` and exit **1** — never a fabricated number,
1485
+ and never a zero exit for "I could not tell you".
1486
+
1487
+ This is a pre-dispatch GLOBAL FLAG, not a 67th command: the count above is unchanged, and a test
1488
+ derives it from the rendered `dz --help` command list so a reformat cannot move it silently.
1489
+
1490
+ **Why it exists.** `dz --version` used to print the entire USAGE manual and exit 0 (MEASURED
1491
+ 2026-08-17 on 0.4.5 — reproducer: `node dist/bin.js --version` before this change). Any wrapper
1492
+ guarding a version range — `@dzhechkov/loop-designer-plugin` is the first — would read exit 0 as "it
1493
+ answered me" while finding no version to parse, and then call a possibly-stale binary. Recognised
1494
+ only as the FIRST argument, so a later positional `-v` still belongs to its subcommand.
1495
+
1496
+ ## `dz skills-verify` also sees SLASH COMMANDS now
1497
+
1498
+ ```bash
1499
+ # does a plugin's skill AND its five commands actually register?
1500
+ dz skills-verify --dir /tmp/probe --plugin-dir packages/@dzhechkov/loop-designer-plugin \
1501
+ --expect loop-designer:loop-plan-author \
1502
+ --expect-commands loop-designer:init,loop-designer:validate,loop-designer:render,loop-designer:lint,loop-designer:trace
1503
+ ```
1504
+
1505
+ - `--plugin-dir <dir>` loads a plugin into the probe session (session-scoped, no marketplace). With
1506
+ no `--expect-commands`, the expectation DEFAULTS to the manifest's own `commands[]`; an unreadable
1507
+ manifest is refused rather than defaulted to an empty (vacuously passing) expectation.
1508
+ - `--expect-commands a,b` names the slash commands that must appear in the authoritative listing.
1509
+ - An **absent** `slash_commands` key is `inconclusive`, never an empty list — schema drift and "your
1510
+ commands did not load" are different facts and must not be collapsed.
1511
+ - `--static` now also PRINTS its advisories. A `.claude-plugin/plugin.json` under `.claude/skills/`
1512
+ previously produced `no layout problems found` and nothing else; the shape most likely to be a
1513
+ silent non-registration was invisible in the mode CI runs. It is reported, and still never fatal.
1514
+
1443
1515
  ### Grounding: three tiers + the token trade-off (`dz brain ground` / `dz brain expand`)
1444
1516
 
1445
1517
  The `dz brain ground` hook fires on **every turn**, so what it injects is a token trade-off. There are
@@ -3434,7 +3506,9 @@ npx @dzhechkov/p-replicator init
3434
3506
 
3435
3507
  ## Status
3436
3508
 
3437
- `v0.4.5` — published on npm. Also available as [Claude Plugin](#claude-plugin). Part of [DZ Harness Hub](https://github.com/djd1m/dz-harness-hub).
3509
+ `v0.4.6` — published on npm. Also available as [Claude Plugin](#claude-plugin). Part of [DZ Harness Hub](https://github.com/djd1m/dz-harness-hub).
3510
+
3511
+ New in this change: the global `dz --version` / `-v` / `dz version` surface (one parseable line), and `dz skills-verify --plugin-dir` / `--expect-commands` so slash-command registration is gate-visible. Both exist for `@dzhechkov/loop-designer-plugin`, which requires `dz` in `^0.4`, verifies it at run time, and falls back to `npx -y @dzhechkov/harness-cli@^0.4` when the `dz` on PATH is stale, unparseable or missing.
3438
3512
 
3439
3513
  ## Claude Plugin
3440
3514
 
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;AAocH,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;IAC3C;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CACjE;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;AA0lR9E,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE,KAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAoK5E"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAucH,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;IAC3C;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CACjE;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;AA4oR9E,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE,KAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAwL5E"}
package/dist/cli.js CHANGED
@@ -12,7 +12,7 @@ import { homedir, tmpdir } from 'node:os';
12
12
  import { createRequire } from 'node:module';
13
13
  import { createSkill, getSkillInfo, isTargetName, listSkills, runDoctor, runInit, resolvePackageSkillRoots, PACKAGE_SKILL_LAYOUTS, 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, buildParityMatrix, TARGET_CAPABILITIES, TARGET_SHORT_LABELS, WORKFLOW_TEMPLATES_RETIRED_MESSAGE, parsePlan, isParseErrors, validatePlan, normalizePlan, planDigest, toTraceProjection, renderPlan, mergeRender, lint, lintExitCode, LOOP_BLOBS, parseTrace, assembleTimeline, runInvariants, renderTimelineHtml, importEcc, recordPattern, resolveLearningBackend, storeStats, consolidateSessions, pruneNoisePatterns, lessonDeltaReport, removePatternsByIds, snapshotStore, recallHybrid, teachGuard, mirrorPatternsToVector, mirrorEntriesToVector, patternVectorEntry, readMemoryLearningConfig, promotePatterns, quarantineExpiryCandidates, pruneQuarantinePatterns, clearAgentdbQuarantine, vectorMirrorEnabled, vectorTierStatus, resolveVectorEngine, reindexVectorStore, harmonizeVectorStore, importRvfCheckpoint, statuslineData, writeFeatureAdrState, computeUsage, deriveCostLedger, renderCostLedger, verifyCostLedgerReport, writeCostLedgerJsonl, COST_LEDGER_SCOPE, deriveUsageCalibration, normalizeClaudeUsageModelKey, readUsageLimits, parseWeeklyResetAnchor, 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, EVENT_CHAIN_TAIL_BYTES, EMPTY_LOG_TAIL, readTailInfo, appendChainedLines, verifyEventChainText, buildManifest, buildSbom, resolveTrustRoot, decideVerifyPolicy, generateSigningKeypair, evaluateGuard, resolveRules, auditRecord, guardExitCode, DEFAULT_RULES, parsePnpmLockImporters, scannableStubPath,
14
14
  // guard-promotion (feature guard-promotion, scout idea #1)
15
- assembleCandidates, renderPromotionReport, renderPromotionAdr, normalizePromotionState, nextPromotionState, globMatch, promotionAdrRelPath, DEFAULT_WINDOW_DAYS, DEFAULT_PERIODS, MAX_CONTENT_FETCHES, BUILTIN_COVERAGE, 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, harvestStageOutcomes, recommendModels, planFeed, GRADE_SUCCESS_FLOOR, COST_LADDER, splitScenarios, budgetPlan, selectWinner, proseScopeOk, renderProseDiff, readScenarioIds, DEFAULT_MAX_JUDGE_RUNS, collectDeliveryFacts, planDeliveryCheck, renderDeliveryBrief, classifyDelivery, isUsablePlaneResult, renderDeliveryReview, scanSkillsLayout, parseInitFacts, verifyRegistration, buildContentProbePrompt, classifyContentProbe, renderContentProbe, findNonRegistrableSkillDirs, assembleCompoundingReport,
15
+ assembleCandidates, renderPromotionReport, renderPromotionAdr, normalizePromotionState, nextPromotionState, globMatch, promotionAdrRelPath, DEFAULT_WINDOW_DAYS, DEFAULT_PERIODS, MAX_CONTENT_FETCHES, BUILTIN_COVERAGE, 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, harvestStageOutcomes, recommendModels, planFeed, GRADE_SUCCESS_FLOOR, COST_LADDER, splitScenarios, budgetPlan, selectWinner, proseScopeOk, renderProseDiff, readScenarioIds, DEFAULT_MAX_JUDGE_RUNS, collectDeliveryFacts, planDeliveryCheck, renderDeliveryBrief, classifyDelivery, isUsablePlaneResult, renderDeliveryReview, scanSkillsLayout, declaredPluginSurface, parseInitFacts, verifyRegistration, buildContentProbePrompt, classifyContentProbe, renderContentProbe, findNonRegistrableSkillDirs, assembleCompoundingReport,
16
16
  // Cold-vs-warm EPOCH RUNNER (feature epoch-replay) — orchestrates + scores, never calls a model.
17
17
  replayableInstances, buildWorkOrder, buildJudgePrompts, unblindJudgments, verifyWorkOrder, isValidMargin, DIGEST_HONEST_SCOPE, scoreEpochReplay, generateMockOutcomes, renderEpochReplayResult, renderWorkOrderSummary, renderJudgePromptsSummary, WORK_ORDER_KIND, DEFAULT_MOCK_N, DEFAULT_MOCK_SEED, scoreRun, renderScorecard, renderCompoundingReport, readReinforcementState, readQuarantineState, registrationExitCode, renderRegistrationReport,
18
18
  // Smart Backlog (feature smart-backlog) — goal-directed idea pipeline over the Brain vector engine.
@@ -119,6 +119,8 @@ Usage:
119
119
  dz import-ecc [--local-path <dir>] [--select id,id,...] [--limit N] [--output <dir>] [--force]
120
120
  dz help
121
121
 
122
+ Global: --version | -v [--json] (prints this CLI's own semver on one line, exit 0; "unknown" + exit 1 when unresolvable)
123
+
122
124
  Workflows: author loop-plan/1 plans with dz workflow init/validate/render; gate them with dz workflow-lint; read runs with dz workflow-trace (the ADR-005 templates are retired)
123
125
 
124
126
  Targets: ${TARGET_NAMES.join(', ')}
@@ -529,8 +531,8 @@ function workflowInitPlan(name, pattern) {
529
531
  ...base,
530
532
  steps: [
531
533
  { stepId: 'fan', kind: 'fanout', phase: 'Work', concurrency: 'pipeline', budget: { maxAgents: 8 } },
532
- { stepId: 'a', kind: 'agent', phase: 'Work', prompt: 'TODO: stage A per item', budget: { maxAgents: 4 } }, // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
533
- { stepId: 'b', kind: 'agent', phase: 'Work', prompt: 'TODO: stage B per item', budget: { maxAgents: 4 } }, // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
534
+ { stepId: 'a', kind: 'agent', phase: 'Work', prompt: 'TODO: stage A per item', budget: { maxAgents: 4 }, tools: [] }, // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
535
+ { stepId: 'b', kind: 'agent', phase: 'Work', prompt: 'TODO: stage B per item', budget: { maxAgents: 4 }, tools: [] }, // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
534
536
  { stepId: 'jn', kind: 'join', phase: 'Work', deps: ['fan'] },
535
537
  ],
536
538
  fanouts: [{ stage: 'fan', registry: ['item1', 'item2', 'item3'], maxFanout: 3, chain: ['a', 'b'] }],
@@ -542,10 +544,10 @@ function workflowInitPlan(name, pattern) {
542
544
  ...base,
543
545
  steps: [
544
546
  { stepId: 'fan', kind: 'fanout', phase: 'Lanes', concurrency: 'barrier', budget: { maxAgents: 6 } },
545
- { stepId: 'lane', kind: 'agent', phase: 'Lanes', prompt: 'TODO: one lane', budget: { maxAgents: 6 } }, // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
547
+ { stepId: 'lane', kind: 'agent', phase: 'Lanes', prompt: 'TODO: one lane', budget: { maxAgents: 6 }, tools: [] }, // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
546
548
  { stepId: 'jn', kind: 'join', phase: 'Lanes', deps: ['fan'] },
547
549
  // the consumer hangs off the BARRIER (jn), never the fork — barrier-postdominates teaches this
548
- { stepId: 'synthesize', kind: 'agent', phase: 'Synthesize', deps: ['jn'], prompt: 'TODO: synthesize across lanes', budget: { maxAgents: 1 } }, // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
550
+ { stepId: 'synthesize', kind: 'agent', phase: 'Synthesize', deps: ['jn'], prompt: 'TODO: synthesize across lanes', budget: { maxAgents: 1 }, tools: [] }, // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
549
551
  ],
550
552
  fanouts: [{ stage: 'fan', registry: ['lane1', 'lane2', 'lane3'], maxFanout: 3, chain: ['lane'] }],
551
553
  joins: [{ stage: 'jn', forStage: 'fan', joinPolicy: 'all-activated', onInvalid: 'named-failure' }],
@@ -555,8 +557,8 @@ function workflowInitPlan(name, pattern) {
555
557
  return {
556
558
  ...base,
557
559
  steps: [
558
- { stepId: 'work', kind: 'agent', phase: 'Work', prompt: 'TODO: produce the artifact', budget: { maxAgents: 2 } }, // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
559
- { stepId: 'gate', kind: 'gate', phase: 'Gate', deps: ['work'], prompt: 'TODO: gate check (parse the verdict, never synthesize one)', budget: { maxAgents: 1 } }, // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
560
+ { stepId: 'work', kind: 'agent', phase: 'Work', prompt: 'TODO: produce the artifact', budget: { maxAgents: 2 }, tools: [] }, // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
561
+ { stepId: 'gate', kind: 'gate', phase: 'Gate', deps: ['work'], prompt: 'TODO: gate check (parse the verdict, never synthesize one)', budget: { maxAgents: 1 }, tools: [] }, // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
560
562
  ],
561
563
  gates: [{ stepId: 'gate', kind: 'parse-verdict', failRoute: 'work', maxRedos: 1 }],
562
564
  };
@@ -564,7 +566,7 @@ function workflowInitPlan(name, pattern) {
564
566
  // minimal default: one agent step
565
567
  return {
566
568
  ...base,
567
- steps: [{ stepId: 'main', kind: 'agent', phase: 'Work', prompt: 'TODO: the one step', budget: { maxAgents: 1 } }], // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
569
+ steps: [{ stepId: 'main', kind: 'agent', phase: 'Work', prompt: 'TODO: the one step', budget: { maxAgents: 1 }, tools: [] }], // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
568
570
  };
569
571
  }
570
572
  /**
@@ -762,8 +764,12 @@ function cmdWorkflowLint(options, flags, cwd, write) {
762
764
  /** `dz workflow-trace` — timeline + invariant runner over a run's trace.jsonl. Scope is CAPPED
763
765
  * (AM-8): <runDir|--slug|--run>, --invariants, --html, --json. NO watch/filter/compare/search/
764
766
  * retention/access-control — adding one needs an ADR amendment (the surface test pins this). */
765
- /** The tool version stamped into a bundle's provenance. Unknown is honest; a throw is not. */
766
- function traceBundleToolVersion() {
767
+ /**
768
+ * This CLI's OWN semver, read from its package.json. `unknown` is honest; a throw is not, and an
769
+ * invented number is worse than either — a downstream version guard that is handed a fabricated
770
+ * version happily calls a binary it should have refused.
771
+ */
772
+ function dzOwnVersion() {
767
773
  try {
768
774
  const req = createRequire(import.meta.url);
769
775
  const pkg = req('../package.json');
@@ -773,6 +779,10 @@ function traceBundleToolVersion() {
773
779
  return 'unknown';
774
780
  }
775
781
  }
782
+ /** The tool version stamped into a bundle's provenance. Unknown is honest; a throw is not. */
783
+ function traceBundleToolVersion() {
784
+ return dzOwnVersion();
785
+ }
776
786
  /** Resolve a run the SAME three ways the timeline reader does — positional, --slug, --run — so a
777
787
  * bundle can never address a run by a scheme the rest of the command does not understand. */
778
788
  function resolveTraceRun(options, cwd, positional) {
@@ -7926,7 +7936,7 @@ const PROBE_SCRUB_ENV = [
7926
7936
  * lands, then kill it — the model never answers, so the probe costs ~no tokens. Never throws:
7927
7937
  * every failure becomes an `error` string, which the pure classifier turns into `inconclusive`.
7928
7938
  */
7929
- function probeInitStream(projectDir, timeoutMs) {
7939
+ function probeInitStream(projectDir, timeoutMs, pluginDir = null) {
7930
7940
  return new Promise((resolveProbe) => {
7931
7941
  const env = { ...process.env };
7932
7942
  for (const key of PROBE_SCRUB_ENV)
@@ -7952,7 +7962,13 @@ function probeInitStream(projectDir, timeoutMs) {
7952
7962
  const timer = setTimeout(() => finish(`no init event within ${Math.round(timeoutMs / 1000)}s (is \`claude\` logged in?)`), timeoutMs);
7953
7963
  try {
7954
7964
  // NOT `--bare`: that mode skips plugin credentials and fails with "Not logged in".
7955
- child = spawn('claude', ['-p', 'ok', '--output-format', 'stream-json', '--verbose'], {
7965
+ const args = ['-p', 'ok', '--output-format', 'stream-json', '--verbose'];
7966
+ // Session-scoped plugin load — the marketplace-free vehicle (ADR-003 D-3). Without this the
7967
+ // probe reads a session in which the plugin was never loaded, and reports its commands
7968
+ // missing for a reason that has nothing to do with the package under test.
7969
+ if (pluginDir !== null)
7970
+ args.push('--plugin-dir', pluginDir);
7971
+ child = spawn('claude', args, {
7956
7972
  cwd: projectDir,
7957
7973
  env,
7958
7974
  stdio: ['ignore', 'pipe', 'pipe'],
@@ -8026,8 +8042,11 @@ function probeContent(projectDir, prompt, timeoutMs) {
8026
8042
  async function cmdSkillsVerify(options, flags, cwd, write) {
8027
8043
  const json = flags.has('json');
8028
8044
  if (flags.has('help')) {
8029
- write('dz skills-verify [--dir <project>] [--expect a,b] [--static] [--strict] [--timeout <s>] [--json]');
8045
+ write('dz skills-verify [--dir <project>] [--expect a,b] [--expect-commands a,b] [--plugin-dir <dir>] [--static] [--strict] [--timeout <s>] [--json]');
8030
8046
  write(' Verifies that a project\'s .claude/skills/ actually register in Claude Code.');
8047
+ write(' --plugin-dir <dir> load a plugin into the probe session (session-scoped, no marketplace);');
8048
+ write(' with no --expect-commands, the expectation defaults to the manifest\'s own commands[]');
8049
+ write(' --expect-commands slash commands that MUST appear in the session listing, e.g. loop-designer:init');
8031
8050
  write(' --static layout scan only (no Claude session, CI-safe): flags dirs that can never register');
8032
8051
  write(' --live-content ADVISORY extra turn: ask a live model to name the skills and quote one, proving');
8033
8052
  write(' the CONTENT is usable — registration is not usability. Never changes the exit code.');
@@ -8036,8 +8055,8 @@ async function cmdSkillsVerify(options, flags, cwd, write) {
8036
8055
  return 0;
8037
8056
  }
8038
8057
  const allowedFlags = new Set(['json', 'help', 'static', 'strict', 'live-content']);
8039
- const allowedOptions = new Set(['dir', 'expect', 'timeout']);
8040
- const usage = ' allowed: --dir <project>, --expect a,b, --timeout <s>, --static, --strict, --live-content, --json';
8058
+ const allowedOptions = new Set(['dir', 'expect', 'expect-commands', 'plugin-dir', 'timeout']);
8059
+ const usage = ' allowed: --dir <project>, --expect a,b, --expect-commands a,b, --plugin-dir <dir>, --timeout <s>, --static, --strict, --live-content, --json';
8041
8060
  if (options.has('_positional_0')) {
8042
8061
  const message = `unexpected argument "${options.get('_positional_0')}"`;
8043
8062
  write(json ? JSON.stringify({ error: message, exitCode: 1 }) : `dz skills-verify: ${message}\n${usage}`);
@@ -8065,6 +8084,27 @@ async function cmdSkillsVerify(options, flags, cwd, write) {
8065
8084
  .map((s) => s.trim())
8066
8085
  .filter(Boolean)
8067
8086
  : scan.registrable;
8087
+ // A plugin loaded with `--plugin-dir` is session-scoped, so its surface is NOT on the project's
8088
+ // disk and `scan.registrable` cannot describe it. The manifest can — and defaulting to the
8089
+ // manifest's own `commands[]` keeps the gate honest without a hand-typed list that silently
8090
+ // drifts from the manifest it is supposed to be checking. An UNREADABLE manifest is refused
8091
+ // rather than defaulted to an empty expectation: an empty expectation passes without checking.
8092
+ const pluginDir = options.has('plugin-dir') ? resolve(cwd, options.get('plugin-dir') ?? '') : null;
8093
+ let expectedCommands = options.has('expect-commands')
8094
+ ? (options.get('expect-commands') ?? '')
8095
+ .split(',')
8096
+ .map((s) => s.trim())
8097
+ .filter(Boolean)
8098
+ : [];
8099
+ if (pluginDir !== null && !options.has('expect-commands')) {
8100
+ const surface = declaredPluginSurface(pluginDir);
8101
+ if (surface === null) {
8102
+ const message = `cannot read ${join(pluginDir, '.claude-plugin', 'plugin.json')} (or it declares no name) — pass --expect-commands explicitly`;
8103
+ write(json ? JSON.stringify({ error: message, exitCode: 1 }) : `dz skills-verify: ${message}`);
8104
+ return 1;
8105
+ }
8106
+ expectedCommands = surface.commands;
8107
+ }
8068
8108
  // ── L1 only: deterministic, no session, safe for CI ──
8069
8109
  if (flags.has('static')) {
8070
8110
  const exitCode = scan.findings.length > 0 ? 1 : 0;
@@ -8078,6 +8118,12 @@ async function cmdSkillsVerify(options, flags, cwd, write) {
8078
8118
  for (const f of scan.findings)
8079
8119
  write(` [${f.kind}] ${f.detail}`);
8080
8120
  write(scan.findings.length ? ` ${scan.findings.length} layout problem(s) — these can never register` : ' no layout problems found');
8121
+ // Advisories were collected but never PRINTED in static mode: a `.claude-plugin/plugin.json`
8122
+ // under `.claude/skills` produced "no layout problems found" and nothing else, so the one
8123
+ // shape most likely to be a silent non-registration was invisible in exactly the mode CI and
8124
+ // humans run most. Reported, still never fatal (that distinction is the whole point).
8125
+ for (const a of scan.advisories)
8126
+ write(` [${a.kind}] ADVISORY: ${a.detail}`);
8081
8127
  write(' (static is a PROXY — run without --static to read the real registration listing)');
8082
8128
  }
8083
8129
  return exitCode;
@@ -8087,7 +8133,7 @@ async function cmdSkillsVerify(options, flags, cwd, write) {
8087
8133
  const timeoutMs = Number.isFinite(timeoutSec) && timeoutSec > 0 ? timeoutSec * 1000 : 180_000;
8088
8134
  if (!json)
8089
8135
  write(`dz skills-verify: starting a session in ${projectDir} to read the real registration listing…`);
8090
- const { stream, error } = await probeInitStream(projectDir, timeoutMs);
8136
+ const { stream, error } = await probeInitStream(projectDir, timeoutMs, pluginDir);
8091
8137
  // `init.skills` carries names, not provenance: a USER-level skill of the same name would satisfy
8092
8138
  // the expectation while the project's own copy stays broken. Collect the collisions so the
8093
8139
  // classifier can refuse to attribute registration to this project (Codex QE #2).
@@ -8109,6 +8155,7 @@ async function cmdSkillsVerify(options, flags, cwd, write) {
8109
8155
  // The provenance check RAN (that is what `checked: true` asserts) — see `ambiguous` above.
8110
8156
  provenance: { checked: true, ambiguous },
8111
8157
  ...(options.has('expect') ? { expected } : {}),
8158
+ ...(expectedCommands.length > 0 ? { expectedCommands } : {}),
8112
8159
  }, { resolvePath: canonical });
8113
8160
  const exitCode = registrationExitCode(result.verdict, flags.has('strict'));
8114
8161
  // ADVISORY layer: registration is not usability. Costs a real model turn, so it is opt-in, and it
@@ -9164,6 +9211,26 @@ export async function runCli(argv, io = {}) {
9164
9211
  }
9165
9212
  };
9166
9213
  const { command, options, optionLists, flags } = parseArgs(argv);
9214
+ // ── `dz --version` / `dz -v` / `dz version` — PRE-DISPATCH, before the help branch ──
9215
+ //
9216
+ // Until now `dz --version` printed the whole USAGE manual and exited 0 (MEASURED 2026-08-17,
9217
+ // reproducer `node dist/bin.js --version`). Exit 0 plus prose is the worst possible answer for a
9218
+ // caller that must decide whether a `dz` it found on PATH is safe to invoke: the status code says
9219
+ // "fine" and there is no number to parse. Any wrapper guarding a version range needs exactly one
9220
+ // parseable line. Recognised only as the FIRST token (or the `version` subcommand) so that a
9221
+ // later positional `-v` belonging to a subcommand keeps its own meaning.
9222
+ if (argv[0] === '--version' || argv[0] === '-v' || command === 'version') {
9223
+ const version = dzOwnVersion();
9224
+ if (flags.has('json')) {
9225
+ write(JSON.stringify({ name: 'dz', version, node: process.version, schemas: { loopPlan: 'loop-plan/1' } }));
9226
+ }
9227
+ else {
9228
+ write(version);
9229
+ }
9230
+ // An unresolvable version is a FAILURE, not a value: exiting 0 with the literal `unknown` would
9231
+ // let a guard treat "I could not tell you" as "I answered you".
9232
+ return version === 'unknown' ? 1 : 0;
9233
+ }
9167
9234
  if (command === '' || command === 'help' || flags.has('help')) {
9168
9235
  write(USAGE);
9169
9236
  return 0;