@dzhechkov/harness-cli 0.3.238 → 0.3.239

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": "6cdf43e972df9144b6eb93beed515bb216c1545bdd69b696b773a3dbe2d5b679"
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": "dfb260e21d46dfc8020eef0bf5ba570f9c335964181cf90446c1c5d761626415"
45
45
  },
46
46
  {
47
47
  "path": "dist/cli.js",
48
- "sha256": "5f5fbcc7c040a272f6af992ef1f9c39482b941d74dbcadec9f3d9008babcb08a"
48
+ "sha256": "a5b7d2fc4f582c507dfab54f1cc4754ca6700f7e686a3c72871afa807cceac25"
49
49
  },
50
50
  {
51
51
  "path": "dist/cli.js.map",
52
- "sha256": "420d1178a513e54f315095a17e9c5722543ca6cbdbebd9a674d4d50ef61b8aa0"
52
+ "sha256": "8fb0c46e092ac7240af9d254f272da649d911b3307b39dc60ac1ef43d349834f"
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": "82a481991b03522d4da62fe49c032c03445928a46a226e8cbb05498eb9321950"
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": "ccb1af3966a258b17eed99ce8b830639166ad8a8a5075ca027d8f30077025d19"
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": "mJYQHwfoPEK5x7cn8B0AbYnqRpUO9iy3qHhWrIHQ/4xqgsTOb/DMSDScPDY2GxfvZsk2pUVuPQS8xjWMKXH9BA=="
105
105
  }
package/README.md CHANGED
@@ -434,6 +434,57 @@ 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
+
437
488
  ### Portable delivery gate — `dz delivery-check` (Step-10, on every shell target)
438
489
 
439
490
  The feature-adr Step-10 Delivery Gate reviews a landed feature across four orthogonal planes and
@@ -584,7 +635,7 @@ Get the whole set with `dz init --target claude-code --preset meta`, or pick one
584
635
 
585
636
  > **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
637
 
587
- ## All Commands (57)
638
+ ## All Commands (58)
588
639
 
589
640
  ```
590
641
  dz setup --target <name> [--preset <name>] [--select id,id,...] [--skills-dir <dir>] [--memory agentdb] [--no-memory] [--no-hooks] [--install-driver] [--force]
@@ -640,6 +691,7 @@ dz routing [--stage <s>] [--json] # inspect the learned cost-optimal routing s
640
691
  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
692
  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
693
  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
694
+ 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
695
  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
696
  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
697
  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;AAoRH,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;AAiuL9E,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, 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)
@@ -5302,6 +5303,176 @@ function nameFor(t, outcome) {
5302
5303
  * --author <model> cosmetic: the reviewer to dispatch, printed alongside the brief
5303
5304
  * --json machine contract { planesChecked, planesSkipped, findings, handoff, artifact }
5304
5305
  */
5306
+ /**
5307
+ * Env vars an INHERITED Claude session leaks into a child. Left in place, the probe can silently
5308
+ * read the parent's project instead of the target — the exact confound that made a hand-rolled
5309
+ * 2026-07-23 probe untrustworthy until controls were added (feature skills-verify, ADR-001).
5310
+ */
5311
+ const PROBE_SCRUB_ENV = [
5312
+ 'CLAUDE_CODE_CHILD_SESSION',
5313
+ 'CLAUDE_CODE_SESSION_ID',
5314
+ 'CLAUDECODE',
5315
+ 'CLAUDE_CODE_ENTRYPOINT',
5316
+ 'CLAUDE_CODE_EXECPATH',
5317
+ 'CLAUDE_PLUGIN_DATA',
5318
+ 'AI_AGENT',
5319
+ ];
5320
+ /**
5321
+ * Start a real Claude session in `projectDir` and capture its stream until the `system/init` event
5322
+ * lands, then kill it — the model never answers, so the probe costs ~no tokens. Never throws:
5323
+ * every failure becomes an `error` string, which the pure classifier turns into `inconclusive`.
5324
+ */
5325
+ function probeInitStream(projectDir, timeoutMs) {
5326
+ return new Promise((resolveProbe) => {
5327
+ const env = { ...process.env };
5328
+ for (const key of PROBE_SCRUB_ENV)
5329
+ delete env[key];
5330
+ env.CLAUDE_PROJECT_DIR = projectDir;
5331
+ let out = '';
5332
+ let err = '';
5333
+ let settled = false;
5334
+ let child;
5335
+ const finish = (error) => {
5336
+ if (settled)
5337
+ return;
5338
+ settled = true;
5339
+ clearTimeout(timer);
5340
+ try {
5341
+ child?.kill('SIGTERM');
5342
+ }
5343
+ catch {
5344
+ /* the child may already be gone */
5345
+ }
5346
+ resolveProbe({ stream: out, error });
5347
+ };
5348
+ const timer = setTimeout(() => finish(`no init event within ${Math.round(timeoutMs / 1000)}s (is \`claude\` logged in?)`), timeoutMs);
5349
+ try {
5350
+ // NOT `--bare`: that mode skips plugin credentials and fails with "Not logged in".
5351
+ child = spawn('claude', ['-p', 'ok', '--output-format', 'stream-json', '--verbose'], {
5352
+ cwd: projectDir,
5353
+ env,
5354
+ stdio: ['ignore', 'pipe', 'pipe'],
5355
+ });
5356
+ }
5357
+ catch (error) {
5358
+ finish(`cannot run \`claude\`: ${error instanceof Error ? error.message : String(error)}`);
5359
+ return;
5360
+ }
5361
+ child.stdout?.on('data', (chunk) => {
5362
+ out += chunk.toString();
5363
+ // Reuse the pure parser so a partially-flushed line simply waits for more data.
5364
+ if (parseInitFacts(out))
5365
+ finish(null);
5366
+ });
5367
+ child.stderr?.on('data', (chunk) => {
5368
+ err += chunk.toString();
5369
+ });
5370
+ child.on('error', (error) => finish(`cannot run \`claude\`: ${error.message}`));
5371
+ child.on('close', (code) => {
5372
+ if (parseInitFacts(out))
5373
+ finish(null);
5374
+ else
5375
+ finish(err.trim() || `\`claude\` exited ${code} without an init event`);
5376
+ });
5377
+ });
5378
+ }
5379
+ /**
5380
+ * `dz skills-verify` — does this project's `.claude/skills/` actually REGISTER? (feature
5381
+ * skills-verify, ADR-001.) L1 static scan is instant and CI-safe; the live layer reads the
5382
+ * authoritative `system/init` listing. Fail-closed: unobservable ⇒ inconclusive, never pass.
5383
+ */
5384
+ async function cmdSkillsVerify(options, flags, cwd, write) {
5385
+ const json = flags.has('json');
5386
+ if (flags.has('help')) {
5387
+ write('dz skills-verify [--dir <project>] [--expect a,b] [--static] [--strict] [--timeout <s>] [--json]');
5388
+ write(' Verifies that a project\'s .claude/skills/ actually register in Claude Code.');
5389
+ write(' --static layout scan only (no Claude session, CI-safe): flags dirs that can never register');
5390
+ write(' default also starts a real session and reads the authoritative system/init listing');
5391
+ write(' exit: 0 pass · 1 fail · 2 inconclusive (--strict makes inconclusive exit 1)');
5392
+ return 0;
5393
+ }
5394
+ const allowedFlags = new Set(['json', 'help', 'static', 'strict']);
5395
+ const allowedOptions = new Set(['dir', 'expect', 'timeout']);
5396
+ const usage = ' allowed: --dir <project>, --expect a,b, --timeout <s>, --static, --strict, --json';
5397
+ if (options.has('_positional_0')) {
5398
+ const message = `unexpected argument "${options.get('_positional_0')}"`;
5399
+ write(json ? JSON.stringify({ error: message, exitCode: 1 }) : `dz skills-verify: ${message}\n${usage}`);
5400
+ return 1;
5401
+ }
5402
+ for (const flag of flags) {
5403
+ if (!allowedFlags.has(flag)) {
5404
+ write(json ? JSON.stringify({ error: `unknown option --${flag}`, exitCode: 1 }) : `dz skills-verify: unknown option --${flag}\n${usage}`);
5405
+ return 1;
5406
+ }
5407
+ }
5408
+ for (const key of options.keys()) {
5409
+ if (key.startsWith('_positional_'))
5410
+ continue;
5411
+ if (!allowedOptions.has(key)) {
5412
+ write(json ? JSON.stringify({ error: `unknown option --${key}`, exitCode: 1 }) : `dz skills-verify: unknown option --${key}\n${usage}`);
5413
+ return 1;
5414
+ }
5415
+ }
5416
+ const projectDir = resolve(cwd, options.get('dir') ?? '.');
5417
+ const scan = scanSkillsLayout(projectDir);
5418
+ const expected = options.has('expect')
5419
+ ? (options.get('expect') ?? '')
5420
+ .split(',')
5421
+ .map((s) => s.trim())
5422
+ .filter(Boolean)
5423
+ : scan.registrable;
5424
+ // ── L1 only: deterministic, no session, safe for CI ──
5425
+ if (flags.has('static')) {
5426
+ const exitCode = scan.findings.length > 0 ? 1 : 0;
5427
+ if (json) {
5428
+ write(JSON.stringify({ mode: 'static', ...scan, expected, exitCode }, null, 2));
5429
+ }
5430
+ else {
5431
+ write(`dz skills-verify (static): ${scan.registrable.length} registrable skill dir(s) under ${scan.skillsRoot}`);
5432
+ if (!scan.exists)
5433
+ write(' no .claude/skills/ directory here');
5434
+ for (const f of scan.findings)
5435
+ write(` [${f.kind}] ${f.detail}`);
5436
+ write(scan.findings.length ? ` ${scan.findings.length} layout problem(s) — these can never register` : ' no layout problems found');
5437
+ write(' (static is a PROXY — run without --static to read the real registration listing)');
5438
+ }
5439
+ return exitCode;
5440
+ }
5441
+ // ── L2: the authoritative listing ──
5442
+ const timeoutSec = Number(options.get('timeout') ?? '180');
5443
+ const timeoutMs = Number.isFinite(timeoutSec) && timeoutSec > 0 ? timeoutSec * 1000 : 180_000;
5444
+ if (!json)
5445
+ write(`dz skills-verify: starting a session in ${projectDir} to read the real registration listing…`);
5446
+ const { stream, error } = await probeInitStream(projectDir, timeoutMs);
5447
+ // `init.skills` carries names, not provenance: a USER-level skill of the same name would satisfy
5448
+ // the expectation while the project's own copy stays broken. Collect the collisions so the
5449
+ // classifier can refuse to attribute registration to this project (Codex QE #2).
5450
+ const userSkillsDir = join(homedir(), '.claude', 'skills');
5451
+ const ambiguous = expected.filter((name) => existsSync(join(userSkillsDir, name, 'SKILL.md')));
5452
+ // realpath so a symlinked project still matches its canonical cwd (Codex QE #6).
5453
+ const canonical = (p) => {
5454
+ try {
5455
+ return realpathSync(p);
5456
+ }
5457
+ catch {
5458
+ return resolve(p);
5459
+ }
5460
+ };
5461
+ const result = verifyRegistration({
5462
+ projectDir,
5463
+ scan,
5464
+ probe: error === null ? { ok: true, stream } : { ok: false, error },
5465
+ // The provenance check RAN (that is what `checked: true` asserts) — see `ambiguous` above.
5466
+ provenance: { checked: true, ambiguous },
5467
+ ...(options.has('expect') ? { expected } : {}),
5468
+ }, { resolvePath: canonical });
5469
+ const exitCode = registrationExitCode(result.verdict, flags.has('strict'));
5470
+ if (json)
5471
+ write(JSON.stringify({ mode: 'live', ...result, skillsRoot: scan.skillsRoot, exitCode }, null, 2));
5472
+ else
5473
+ write(renderRegistrationReport(result, scan));
5474
+ return exitCode;
5475
+ }
5305
5476
  function cmdDeliveryCheck(options, flags, cwd, write) {
5306
5477
  let repoRoot = cwd;
5307
5478
  try {
@@ -5870,6 +6041,8 @@ export async function runCli(argv, io = {}) {
5870
6041
  return cmdDiscriminationCheck(options, flags, cwd, write);
5871
6042
  case 'delivery-check':
5872
6043
  return cmdDeliveryCheck(options, flags, cwd, write);
6044
+ case 'skills-verify':
6045
+ return cmdSkillsVerify(options, flags, cwd, write);
5873
6046
  case 'routing':
5874
6047
  return cmdRouting(options, flags, cwd, write);
5875
6048
  case 'bto-optimize':