@dzhechkov/harness-cli 0.3.238 → 0.3.240

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": "README.md",
12
- "sha256": "ef6603652143fc1517c81506dad9d5e7b79e5d5138aad9f6281ad77492d58b71"
12
+ "sha256": "1356d1ebf22352d0ab57ebe17fb91bb8c4df17da8b6968863a92b92402921bb7"
13
13
  },
14
14
  {
15
15
  "path": "coverage/coverage-final.json",
@@ -41,15 +41,15 @@
41
41
  },
42
42
  {
43
43
  "path": "dist/cli.d.ts.map",
44
- "sha256": "8f8807920c7ddd901361eb5a602df9c85fa69726e47535a146937038eab6b281"
44
+ "sha256": "eb431a9aaed2c853b19a797cd49668bed9fe1e700880a8d18d9a1a4cfe243f55"
45
45
  },
46
46
  {
47
47
  "path": "dist/cli.js",
48
- "sha256": "5f5fbcc7c040a272f6af992ef1f9c39482b941d74dbcadec9f3d9008babcb08a"
48
+ "sha256": "9a46ece09ee1ac72ca3d7715ae86c1b5dc3747b18287c6f558a3c5d6569de7da"
49
49
  },
50
50
  {
51
51
  "path": "dist/cli.js.map",
52
- "sha256": "420d1178a513e54f315095a17e9c5722543ca6cbdbebd9a674d4d50ef61b8aa0"
52
+ "sha256": "02dcf19b1e616487f3840482e9af6f9028749b5a8c9d8da143126be5d7ee5779"
53
53
  },
54
54
  {
55
55
  "path": "dist/index.d.ts",
@@ -73,7 +73,7 @@
73
73
  },
74
74
  {
75
75
  "path": "package.json",
76
- "sha256": "8fbaf10d29b5733cf9445e1ef5804a3e124f733c3390a99937d2d8dc3b4cf02a"
76
+ "sha256": "b729864fd3fb9fc4cf2fcab96c6aa36f13ed181f3aa2bb37c6b3ca0c893afd76"
77
77
  },
78
78
  {
79
79
  "path": "src/bin.ts",
@@ -81,7 +81,7 @@
81
81
  },
82
82
  {
83
83
  "path": "src/cli.ts",
84
- "sha256": "131f23a50187e1be19875d4ccd76d8c354b1fbb8d193c03e73fda16ff64f51dc"
84
+ "sha256": "8477517b028f8258d5f11a2197167210de2db0acefa901f0f393579dc14bd409"
85
85
  },
86
86
  {
87
87
  "path": "src/index.ts",
@@ -101,5 +101,5 @@
101
101
  }
102
102
  ]
103
103
  },
104
- "signature": "spl/yr0En3yUAxhYjhIdot8D/Tri4JeoMrweQkA9GF2F9UyzDco3FLpaqCkvwlhGvj4phKb4qB0Nm3e+GAMaDw=="
104
+ "signature": "qMYFndIEe8HqlomfBmJrqsujcr/krjDvnlUuzoSIuw5v6muHu/4VQxha9DnqUpyfyz1MNAHgfo6N841osW0UCg=="
105
105
  }
package/README.md CHANGED
@@ -434,6 +434,64 @@ dz parity — codex (capabilities: shell, skills, mcp)
434
434
  **When to use:** before promising a workflow to a teammate on Cursor/Codex/Hermes; when choosing
435
435
  a target for a project; as the requirements input for porting a feature to more targets.
436
436
 
437
+ ### Do your skills actually register? — `dz skills-verify`
438
+
439
+ Shipping a skill pack is not the same as a skill **registering**. A layout test that asserts
440
+ `SKILL.md` exists on disk verifies a **proxy**; the property that matters is whether Claude Code
441
+ loads it. `@dzhechkov/health-advisor` 1.2.0 shipped to npm with a green layout test and **zero**
442
+ skills registering — the gap was only found by hand, after publish. `dz skills-verify` closes it.
443
+
444
+ Two layers:
445
+
446
+ | layer | needs a session? | what it proves |
447
+ |---|---|---|
448
+ | `--static` | no — instant, CI-safe | which dirs *can* register, and flags the three shapes that never can |
449
+ | default | yes (~3 s) | the **authoritative** listing, read from the session's `system/init` event — no model prose |
450
+
451
+ ```bash
452
+ dz skills-verify --static # CI gate: layout only, exits 1 on a problem
453
+ dz skills-verify # full check in the current project
454
+ dz skills-verify --dir ../my-app --expect my-skill,my-other-skill
455
+ dz skills-verify --json --strict # machine-readable; --strict makes inconclusive exit 1
456
+ ```
457
+
458
+ A healthy project, and one with the exact defect that shipped:
459
+
460
+ ```
461
+ $ dz skills-verify
462
+ dz skills-verify: PASS — all 72 expected skill(s) are registered
463
+ layout: 72 registrable skill dir(s) under /path/.claude/skills
464
+ session: 106 skill(s) registered · client 2.1.220 · 5 plugin(s) loaded
465
+
466
+ $ dz skills-verify --dir /tmp/broken-install
467
+ dz skills-verify: FAIL — nothing can register: 24 layout problem(s) and no registrable skill directory
468
+ layout problems (these can never register):
469
+ [no-skill-md] no SKILL.md at health-advisor/SKILL.md — this directory cannot register
470
+ [plugin-manifest-trap] health-advisor/.claude-plugin/plugin.json does not auto-register — plugins load from the marketplace, not from .claude/skills
471
+ [buried-skill-md] health-advisor/skills/clinical-decision-support/SKILL.md is 2+ levels deep …
472
+ ```
473
+
474
+ A `.claude-plugin/plugin.json` under `.claude/skills/` is reported as an **advisory**, not a failure:
475
+ that layout may load under workspace trust, so the verdict asks the session whether the plugin actually
476
+ loaded (`init.plugins`) instead of judging the layout. A container with skills inside and no matching
477
+ loaded plugin fails, and says exactly that.
478
+
479
+ **Exit codes: `0` pass · `1` fail · `2` inconclusive.** The third is the point — a missing `claude`
480
+ binary, a login prompt, a timeout, or a session that read a *different* project all yield
481
+ `inconclusive`, **never** a pass. (A session that read another project cannot testify that a skill is
482
+ missing, so its listing is refused rather than believed.) A layout so broken that nothing is
483
+ registrable fails too, instead of passing vacuously on an empty expectation.
484
+
485
+ **Use it when:** publishing a package that installs skills, after `dz setup` in a new project, or in
486
+ CI (`--static` needs no Claude session at all).
487
+
488
+ **It also runs itself.** `dz publish` pre-flight (`dz guard --op publish`) now carries a
489
+ `skills-registrable` rule: for every skill pack, a directory whose `SKILL.md` sits below depth 1 would
490
+ ship registering nowhere, and the guard says so by name. It is SOFT (it informs, never blocks) because
491
+ the discriminator is a heuristic — a package counts as a skill pack only if it already has one
492
+ registrable skill, and a directory is flagged only when a `SKILL.md` really exists inside it but in the
493
+ wrong place. Ordinary `scripts/`, `docs/` and pure npx toolkits stay silent.
494
+
437
495
  ### Portable delivery gate — `dz delivery-check` (Step-10, on every shell target)
438
496
 
439
497
  The feature-adr Step-10 Delivery Gate reviews a landed feature across four orthogonal planes and
@@ -584,7 +642,7 @@ Get the whole set with `dz init --target claude-code --preset meta`, or pick one
584
642
 
585
643
  > **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.
586
644
 
587
- ## All Commands (57)
645
+ ## All Commands (58)
588
646
 
589
647
  ```
590
648
  dz setup --target <name> [--preset <name>] [--select id,id,...] [--skills-dir <dir>] [--memory agentdb] [--no-memory] [--no-hooks] [--install-driver] [--force]
@@ -640,6 +698,7 @@ dz routing [--stage <s>] [--json] # inspect the learned cost-optimal routing s
640
698
  dz bto-optimize --split|--plan|--select|--scope-check|--diff [--json] # deterministic engine behind /bto-optimize: hold-out split + hard-capped budget + no-regress-on-holdout winner selection (defeats judge-gaming); prose-only, diff-confirmed, never auto-writes
641
699
  dz discrimination-check --test <f[,f]> [--base <ref>] [--name <filter>] [--runner <cmd>] [--json] # §42 test-discrimination gate for feature-adr Step-8: run the ADR's property test in an isolated git worktree at pre-feature base — it MUST go red without the fix; a green is a false green (HIGH finding, advisory, never auto-aborts)
642
700
  dz delivery-check --slug <slug> [--context-only] [--findings <f.json>] [--strict] [--author <model>] [--json] # portable Step-10 Delivery Gate: the `manual` form that travels to every shell target — prints the 4-plane review brief (regressions ‖ security ‖ code-quality ‖ product-honesty) + artifact probes; --findings classifies a fed-back review into a fail-closed ready|blocked hand-off (only cross-validated BLOCKER/HIGH count) and writes features/<slug>/10_delivery_review.md; --strict exits 1 on blocked
701
+ dz skills-verify [--dir <project>] [--expect a,b] [--static] [--strict] [--timeout <s>] [--json] # does .claude/skills/ actually REGISTER? --static = instant layout scan (CI-safe, no session): flags dirs that can never register; default also starts a real session and reads the authoritative system/init listing. exit 0 pass / 1 fail / 2 inconclusive — an unobservable registration is NEVER a pass
643
702
  dz sign --init --out <path> | --pack <dir> --key <path> # --init: generate the Ed25519 keypair (private OUTSIDE the repo, prints the public key for keys/dz.pub); else sign a pack's manifest + CycloneDX SBOM
644
703
  dz sbom --pack <dir> [--out <file>] # emit the CycloneDX 1.5 SBOM for a pack standalone (file-level bill of materials); print to stdout or write to a file
645
704
  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)
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;AA8QH,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;AAyiL9E,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE,KAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAkJ5E"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAqRH,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;AAgvL9E,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE,KAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAoJ5E"}
package/dist/cli.js CHANGED
@@ -6,10 +6,10 @@
6
6
  import { chmodSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, rmdirSync, rmSync, statSync, symlinkSync, writeFileSync } from 'node:fs';
7
7
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
8
8
  import { fileURLToPath } from 'node:url';
9
- import { execSync } from 'node:child_process';
9
+ import { execSync, spawn } 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, buildParityMatrix, TARGET_CAPABILITIES, TARGET_SHORT_LABELS, WORKFLOW_NAMES, 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, 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, collectDeliveryFacts, planDeliveryCheck, renderDeliveryBrief, classifyDelivery, isUsablePlaneResult, renderDeliveryReview, } 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, buildParityMatrix, TARGET_CAPABILITIES, TARGET_SHORT_LABELS, WORKFLOW_NAMES, 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, 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, collectDeliveryFacts, planDeliveryCheck, renderDeliveryBrief, classifyDelivery, isUsablePlaneResult, renderDeliveryReview, scanSkillsLayout, parseInitFacts, verifyRegistration, findNonRegistrableSkillDirs, registrationExitCode, renderRegistrationReport, } 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
@@ -35,6 +35,7 @@ Usage:
35
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)
36
36
  dz parity [--target <name>] [--json] (the honest feature×target map, COMPUTED from the capability model — which harness feature is full / manual / absent on each of the ${TARGET_NAMES.length} targets, and via which form)
37
37
  dz delivery-check --slug <slug> [--context-only] [--findings <f.json>] [--strict] [--author <model>] [--json] (portable Step-10 Delivery Gate: prints the 4-plane review brief + artifact probes; --findings classifies a fed-back review into a fail-closed ready|blocked hand-off and writes features/<slug>/10_delivery_review.md; --strict exits 1 on blocked)
38
+ dz skills-verify [--dir <project>] [--expect a,b] [--static] [--strict] [--json] (does .claude/skills/ actually REGISTER? --static = instant layout scan for CI; default reads the authoritative system/init listing from a real session. exit 0 pass / 1 fail / 2 inconclusive — never a false pass)
38
39
  dz setup --target <name> [--preset <name>] [--select id,id,...] [--skills-dir <dir>] [--project <dir>] [--memory agentdb] [--no-memory] [--no-hooks] [--install-driver] [--force] [--enrich]
39
40
  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)
40
41
  dz teach --from-json <file> [--project <dir>] [--no-mirror] (bulk-import a 'dz recall --all --json' export — share a learned store across machines)
@@ -4493,6 +4494,22 @@ function gatherGuardFacts(op, root, text, storeCap) {
4493
4494
  facts['readmeFirst'] = [...perPack.entries()].map(([name, e]) => ({ name: '@dzhechkov/' + name, versionBumped: e.pkgJson, readmeChanged: e.readme }));
4494
4495
  }
4495
4496
  catch { /* not a git repo — rule skips */ }
4497
+ // skills-registrable: every skill dir in a skill pack must carry a depth-1 SKILL.md, or it ships
4498
+ // registering nowhere (the health-advisor 1.2.0 class). Pure-toolkit packages yield nothing.
4499
+ try {
4500
+ const packsDir = join(root, 'packages', '@dzhechkov');
4501
+ const packs = readdirSync(packsDir, { withFileTypes: true })
4502
+ .filter((e) => e.isDirectory())
4503
+ .map((e) => e.name);
4504
+ const skillPacks = [];
4505
+ for (const pack of packs) {
4506
+ const nonRegistrable = findNonRegistrableSkillDirs(join(packsDir, pack));
4507
+ if (nonRegistrable.length > 0)
4508
+ skillPacks.push({ name: '@dzhechkov/' + pack, nonRegistrable });
4509
+ }
4510
+ facts['skillPacks'] = skillPacks;
4511
+ }
4512
+ catch { /* unreadable packages dir — rule reports nothing rather than inventing a violation */ }
4496
4513
  }
4497
4514
  if (op === 'consolidate') {
4498
4515
  try {
@@ -5302,6 +5319,176 @@ function nameFor(t, outcome) {
5302
5319
  * --author <model> cosmetic: the reviewer to dispatch, printed alongside the brief
5303
5320
  * --json machine contract { planesChecked, planesSkipped, findings, handoff, artifact }
5304
5321
  */
5322
+ /**
5323
+ * Env vars an INHERITED Claude session leaks into a child. Left in place, the probe can silently
5324
+ * read the parent's project instead of the target — the exact confound that made a hand-rolled
5325
+ * 2026-07-23 probe untrustworthy until controls were added (feature skills-verify, ADR-001).
5326
+ */
5327
+ const PROBE_SCRUB_ENV = [
5328
+ 'CLAUDE_CODE_CHILD_SESSION',
5329
+ 'CLAUDE_CODE_SESSION_ID',
5330
+ 'CLAUDECODE',
5331
+ 'CLAUDE_CODE_ENTRYPOINT',
5332
+ 'CLAUDE_CODE_EXECPATH',
5333
+ 'CLAUDE_PLUGIN_DATA',
5334
+ 'AI_AGENT',
5335
+ ];
5336
+ /**
5337
+ * Start a real Claude session in `projectDir` and capture its stream until the `system/init` event
5338
+ * lands, then kill it — the model never answers, so the probe costs ~no tokens. Never throws:
5339
+ * every failure becomes an `error` string, which the pure classifier turns into `inconclusive`.
5340
+ */
5341
+ function probeInitStream(projectDir, timeoutMs) {
5342
+ return new Promise((resolveProbe) => {
5343
+ const env = { ...process.env };
5344
+ for (const key of PROBE_SCRUB_ENV)
5345
+ delete env[key];
5346
+ env.CLAUDE_PROJECT_DIR = projectDir;
5347
+ let out = '';
5348
+ let err = '';
5349
+ let settled = false;
5350
+ let child;
5351
+ const finish = (error) => {
5352
+ if (settled)
5353
+ return;
5354
+ settled = true;
5355
+ clearTimeout(timer);
5356
+ try {
5357
+ child?.kill('SIGTERM');
5358
+ }
5359
+ catch {
5360
+ /* the child may already be gone */
5361
+ }
5362
+ resolveProbe({ stream: out, error });
5363
+ };
5364
+ const timer = setTimeout(() => finish(`no init event within ${Math.round(timeoutMs / 1000)}s (is \`claude\` logged in?)`), timeoutMs);
5365
+ try {
5366
+ // NOT `--bare`: that mode skips plugin credentials and fails with "Not logged in".
5367
+ child = spawn('claude', ['-p', 'ok', '--output-format', 'stream-json', '--verbose'], {
5368
+ cwd: projectDir,
5369
+ env,
5370
+ stdio: ['ignore', 'pipe', 'pipe'],
5371
+ });
5372
+ }
5373
+ catch (error) {
5374
+ finish(`cannot run \`claude\`: ${error instanceof Error ? error.message : String(error)}`);
5375
+ return;
5376
+ }
5377
+ child.stdout?.on('data', (chunk) => {
5378
+ out += chunk.toString();
5379
+ // Reuse the pure parser so a partially-flushed line simply waits for more data.
5380
+ if (parseInitFacts(out))
5381
+ finish(null);
5382
+ });
5383
+ child.stderr?.on('data', (chunk) => {
5384
+ err += chunk.toString();
5385
+ });
5386
+ child.on('error', (error) => finish(`cannot run \`claude\`: ${error.message}`));
5387
+ child.on('close', (code) => {
5388
+ if (parseInitFacts(out))
5389
+ finish(null);
5390
+ else
5391
+ finish(err.trim() || `\`claude\` exited ${code} without an init event`);
5392
+ });
5393
+ });
5394
+ }
5395
+ /**
5396
+ * `dz skills-verify` — does this project's `.claude/skills/` actually REGISTER? (feature
5397
+ * skills-verify, ADR-001.) L1 static scan is instant and CI-safe; the live layer reads the
5398
+ * authoritative `system/init` listing. Fail-closed: unobservable ⇒ inconclusive, never pass.
5399
+ */
5400
+ async function cmdSkillsVerify(options, flags, cwd, write) {
5401
+ const json = flags.has('json');
5402
+ if (flags.has('help')) {
5403
+ write('dz skills-verify [--dir <project>] [--expect a,b] [--static] [--strict] [--timeout <s>] [--json]');
5404
+ write(' Verifies that a project\'s .claude/skills/ actually register in Claude Code.');
5405
+ write(' --static layout scan only (no Claude session, CI-safe): flags dirs that can never register');
5406
+ write(' default also starts a real session and reads the authoritative system/init listing');
5407
+ write(' exit: 0 pass · 1 fail · 2 inconclusive (--strict makes inconclusive exit 1)');
5408
+ return 0;
5409
+ }
5410
+ const allowedFlags = new Set(['json', 'help', 'static', 'strict']);
5411
+ const allowedOptions = new Set(['dir', 'expect', 'timeout']);
5412
+ const usage = ' allowed: --dir <project>, --expect a,b, --timeout <s>, --static, --strict, --json';
5413
+ if (options.has('_positional_0')) {
5414
+ const message = `unexpected argument "${options.get('_positional_0')}"`;
5415
+ write(json ? JSON.stringify({ error: message, exitCode: 1 }) : `dz skills-verify: ${message}\n${usage}`);
5416
+ return 1;
5417
+ }
5418
+ for (const flag of flags) {
5419
+ if (!allowedFlags.has(flag)) {
5420
+ write(json ? JSON.stringify({ error: `unknown option --${flag}`, exitCode: 1 }) : `dz skills-verify: unknown option --${flag}\n${usage}`);
5421
+ return 1;
5422
+ }
5423
+ }
5424
+ for (const key of options.keys()) {
5425
+ if (key.startsWith('_positional_'))
5426
+ continue;
5427
+ if (!allowedOptions.has(key)) {
5428
+ write(json ? JSON.stringify({ error: `unknown option --${key}`, exitCode: 1 }) : `dz skills-verify: unknown option --${key}\n${usage}`);
5429
+ return 1;
5430
+ }
5431
+ }
5432
+ const projectDir = resolve(cwd, options.get('dir') ?? '.');
5433
+ const scan = scanSkillsLayout(projectDir);
5434
+ const expected = options.has('expect')
5435
+ ? (options.get('expect') ?? '')
5436
+ .split(',')
5437
+ .map((s) => s.trim())
5438
+ .filter(Boolean)
5439
+ : scan.registrable;
5440
+ // ── L1 only: deterministic, no session, safe for CI ──
5441
+ if (flags.has('static')) {
5442
+ const exitCode = scan.findings.length > 0 ? 1 : 0;
5443
+ if (json) {
5444
+ write(JSON.stringify({ mode: 'static', ...scan, expected, exitCode }, null, 2));
5445
+ }
5446
+ else {
5447
+ write(`dz skills-verify (static): ${scan.registrable.length} registrable skill dir(s) under ${scan.skillsRoot}`);
5448
+ if (!scan.exists)
5449
+ write(' no .claude/skills/ directory here');
5450
+ for (const f of scan.findings)
5451
+ write(` [${f.kind}] ${f.detail}`);
5452
+ write(scan.findings.length ? ` ${scan.findings.length} layout problem(s) — these can never register` : ' no layout problems found');
5453
+ write(' (static is a PROXY — run without --static to read the real registration listing)');
5454
+ }
5455
+ return exitCode;
5456
+ }
5457
+ // ── L2: the authoritative listing ──
5458
+ const timeoutSec = Number(options.get('timeout') ?? '180');
5459
+ const timeoutMs = Number.isFinite(timeoutSec) && timeoutSec > 0 ? timeoutSec * 1000 : 180_000;
5460
+ if (!json)
5461
+ write(`dz skills-verify: starting a session in ${projectDir} to read the real registration listing…`);
5462
+ const { stream, error } = await probeInitStream(projectDir, timeoutMs);
5463
+ // `init.skills` carries names, not provenance: a USER-level skill of the same name would satisfy
5464
+ // the expectation while the project's own copy stays broken. Collect the collisions so the
5465
+ // classifier can refuse to attribute registration to this project (Codex QE #2).
5466
+ const userSkillsDir = join(homedir(), '.claude', 'skills');
5467
+ const ambiguous = expected.filter((name) => existsSync(join(userSkillsDir, name, 'SKILL.md')));
5468
+ // realpath so a symlinked project still matches its canonical cwd (Codex QE #6).
5469
+ const canonical = (p) => {
5470
+ try {
5471
+ return realpathSync(p);
5472
+ }
5473
+ catch {
5474
+ return resolve(p);
5475
+ }
5476
+ };
5477
+ const result = verifyRegistration({
5478
+ projectDir,
5479
+ scan,
5480
+ probe: error === null ? { ok: true, stream } : { ok: false, error },
5481
+ // The provenance check RAN (that is what `checked: true` asserts) — see `ambiguous` above.
5482
+ provenance: { checked: true, ambiguous },
5483
+ ...(options.has('expect') ? { expected } : {}),
5484
+ }, { resolvePath: canonical });
5485
+ const exitCode = registrationExitCode(result.verdict, flags.has('strict'));
5486
+ if (json)
5487
+ write(JSON.stringify({ mode: 'live', ...result, skillsRoot: scan.skillsRoot, exitCode }, null, 2));
5488
+ else
5489
+ write(renderRegistrationReport(result, scan));
5490
+ return exitCode;
5491
+ }
5305
5492
  function cmdDeliveryCheck(options, flags, cwd, write) {
5306
5493
  let repoRoot = cwd;
5307
5494
  try {
@@ -5870,6 +6057,8 @@ export async function runCli(argv, io = {}) {
5870
6057
  return cmdDiscriminationCheck(options, flags, cwd, write);
5871
6058
  case 'delivery-check':
5872
6059
  return cmdDeliveryCheck(options, flags, cwd, write);
6060
+ case 'skills-verify':
6061
+ return cmdSkillsVerify(options, flags, cwd, write);
5873
6062
  case 'routing':
5874
6063
  return cmdRouting(options, flags, cwd, write);
5875
6064
  case 'bto-optimize':