@dzhechkov/harness-cli 0.3.242 → 0.3.243

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dzhechkov/harness-cli",
3
- "version": "0.3.242",
3
+ "version": "0.3.243",
4
4
  "description": "The dz CLI — install AI skills for Claude Code, Codex, OpenCode, Hermes, OpenClaude, GitHub Copilot. 35 commands, 13 presets, 6 platform targets.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -57,7 +57,7 @@
57
57
  "@dzhechkov/skills-reverse-engineering": "^0.1.0",
58
58
  "@dzhechkov/skills-presentation-storyteller": "^0.1.0",
59
59
  "@dzhechkov/skills-website-cloner": "^0.1.0",
60
- "@dzhechkov/harness-core": "0.3.133"
60
+ "@dzhechkov/harness-core": "0.3.134"
61
61
  },
62
62
  "devDependencies": {
63
63
  "@types/node": "^25.6.0",
package/sbom.json CHANGED
@@ -25,7 +25,7 @@
25
25
  "hashes": [
26
26
  {
27
27
  "alg": "SHA-256",
28
- "content": "f10f682ab0a6a472d1bb52fb8c861d8b22669d579af8ddbbd392e2fb115cce67"
28
+ "content": "5f4e31653970ef6970b6d87c862a1c6ac1f2da92c3f4f913b4be4155284db1f4"
29
29
  }
30
30
  ]
31
31
  },
@@ -105,7 +105,7 @@
105
105
  "hashes": [
106
106
  {
107
107
  "alg": "SHA-256",
108
- "content": "2efde3e98ca7843ac6260881eef51bfd3743e736425ed60fc73bdbebacdb54c6"
108
+ "content": "2bb00d2f42334250ee57b4d0f88d92968cbe3f010ec8d75bb748c0ee6f8de0b8"
109
109
  }
110
110
  ]
111
111
  },
@@ -115,7 +115,7 @@
115
115
  "hashes": [
116
116
  {
117
117
  "alg": "SHA-256",
118
- "content": "88282ec4122a1e414dfb4c74e8583a49893b7ea0eba18d0cabe9ff50e72b0dec"
118
+ "content": "d959d7623b68286e440e04f90ee3ae7af0166c1fe506f2cf7e6e9e317439409d"
119
119
  }
120
120
  ]
121
121
  },
@@ -125,7 +125,7 @@
125
125
  "hashes": [
126
126
  {
127
127
  "alg": "SHA-256",
128
- "content": "da8e7d578e7929880f0a87e240dac4a3a25afaf5c4da96442fbcb159f1eb0e07"
128
+ "content": "68e0bbf621169f7bae350a5b723f9425969ab12b6f7dd98ce26bb06d32c77c92"
129
129
  }
130
130
  ]
131
131
  },
@@ -185,7 +185,7 @@
185
185
  "hashes": [
186
186
  {
187
187
  "alg": "SHA-256",
188
- "content": "7c10b138fb5cbb7c8ccba350863c100aea77a5fd2b2bd30fa08a3ea4260a0e4a"
188
+ "content": "e26250419d70ec922def52f25f63000b658eec8203b29a2135edad1be98f8d5d"
189
189
  }
190
190
  ]
191
191
  },
@@ -205,7 +205,7 @@
205
205
  "hashes": [
206
206
  {
207
207
  "alg": "SHA-256",
208
- "content": "79d8ade45869b7a264d54da7fafda72a39953cab0d48f5ad504dd26657a54a1b"
208
+ "content": "14a3db7155fcc858272439a55bc7a43a09c7f6fd7caa302e5a13c153fe1a6d16"
209
209
  }
210
210
  ]
211
211
  },
package/src/cli.ts CHANGED
@@ -195,6 +195,9 @@ import {
195
195
  scanSkillsLayout,
196
196
  parseInitFacts,
197
197
  verifyRegistration,
198
+ buildContentProbePrompt,
199
+ classifyContentProbe,
200
+ renderContentProbe,
198
201
  findNonRegistrableSkillDirs,
199
202
  registrationExitCode,
200
203
  renderRegistrationReport,
@@ -5693,6 +5696,41 @@ function probeInitStream(projectDir: string, timeoutMs: number): Promise<{ strea
5693
5696
  });
5694
5697
  }
5695
5698
 
5699
+ /** Run a full `claude -p` turn and return its text. Unlike the init probe this NEEDS the answer. */
5700
+ function probeContent(projectDir: string, prompt: string, timeoutMs: number): Promise<{ text: string; error: string | null }> {
5701
+ return new Promise((resolveProbe) => {
5702
+ const env: NodeJS.ProcessEnv = { ...process.env };
5703
+ for (const key of PROBE_SCRUB_ENV) delete env[key];
5704
+ env.CLAUDE_PROJECT_DIR = projectDir;
5705
+ let out = '';
5706
+ let err = '';
5707
+ let settled = false;
5708
+ let child: ReturnType<typeof spawn>;
5709
+ const finish = (error: string | null): void => {
5710
+ if (settled) return;
5711
+ settled = true;
5712
+ clearTimeout(timer);
5713
+ try {
5714
+ child?.kill('SIGTERM');
5715
+ } catch {
5716
+ /* already gone */
5717
+ }
5718
+ resolveProbe({ text: out, error });
5719
+ };
5720
+ const timer = setTimeout(() => finish(`content probe timed out after ${Math.round(timeoutMs / 1000)}s`), timeoutMs);
5721
+ try {
5722
+ child = spawn('claude', ['-p', prompt, '--output-format', 'text'], { cwd: projectDir, env, stdio: ['ignore', 'pipe', 'pipe'] });
5723
+ } catch (error) {
5724
+ finish(`cannot run \`claude\`: ${error instanceof Error ? error.message : String(error)}`);
5725
+ return;
5726
+ }
5727
+ child.stdout?.on('data', (c: Buffer) => { out += c.toString(); });
5728
+ child.stderr?.on('data', (c: Buffer) => { err += c.toString(); });
5729
+ child.on('error', (error: Error) => finish(`cannot run \`claude\`: ${error.message}`));
5730
+ child.on('close', () => finish(out.trim() === '' ? err.trim() || 'the content probe produced no output' : null));
5731
+ });
5732
+ }
5733
+
5696
5734
  /**
5697
5735
  * `dz skills-verify` — does this project's `.claude/skills/` actually REGISTER? (feature
5698
5736
  * skills-verify, ADR-001.) L1 static scan is instant and CI-safe; the live layer reads the
@@ -5709,14 +5747,16 @@ async function cmdSkillsVerify(
5709
5747
  write('dz skills-verify [--dir <project>] [--expect a,b] [--static] [--strict] [--timeout <s>] [--json]');
5710
5748
  write(' Verifies that a project\'s .claude/skills/ actually register in Claude Code.');
5711
5749
  write(' --static layout scan only (no Claude session, CI-safe): flags dirs that can never register');
5750
+ write(' --live-content ADVISORY extra turn: ask a live model to name the skills and quote one, proving');
5751
+ write(' the CONTENT is usable — registration is not usability. Never changes the exit code.');
5712
5752
  write(' default also starts a real session and reads the authoritative system/init listing');
5713
5753
  write(' exit: 0 pass · 1 fail · 2 inconclusive (--strict makes inconclusive exit 1)');
5714
5754
  return 0;
5715
5755
  }
5716
5756
 
5717
- const allowedFlags = new Set(['json', 'help', 'static', 'strict']);
5757
+ const allowedFlags = new Set(['json', 'help', 'static', 'strict', 'live-content']);
5718
5758
  const allowedOptions = new Set(['dir', 'expect', 'timeout']);
5719
- const usage = ' allowed: --dir <project>, --expect a,b, --timeout <s>, --static, --strict, --json';
5759
+ const usage = ' allowed: --dir <project>, --expect a,b, --timeout <s>, --static, --strict, --live-content, --json';
5720
5760
  if (options.has('_positional_0')) {
5721
5761
  const message = `unexpected argument "${options.get('_positional_0')}"`;
5722
5762
  write(json ? JSON.stringify({ error: message, exitCode: 1 }) : `dz skills-verify: ${message}\n${usage}`);
@@ -5796,8 +5836,19 @@ async function cmdSkillsVerify(
5796
5836
  );
5797
5837
  const exitCode = registrationExitCode(result.verdict, flags.has('strict'));
5798
5838
 
5799
- if (json) write(JSON.stringify({ mode: 'live', ...result, skillsRoot: scan.skillsRoot, exitCode }, null, 2));
5800
- else write(renderRegistrationReport(result, scan));
5839
+ // ADVISORY layer: registration is not usability. Costs a real model turn, so it is opt-in, and it
5840
+ // can never change the exit code — a model-mediated signal must not gate anything.
5841
+ let content: ReturnType<typeof classifyContentProbe> | null = null;
5842
+ if (flags.has('live-content') && result.verdict === 'pass' && expected.length > 0) {
5843
+ const probe = await probeContent(projectDir, buildContentProbePrompt(expected), timeoutMs);
5844
+ content = classifyContentProbe(probe.error === null ? probe.text : '', expected.slice(0, 6));
5845
+ }
5846
+
5847
+ if (json) write(JSON.stringify({ mode: 'live', ...result, ...(content ? { contentProbe: content } : {}), skillsRoot: scan.skillsRoot, exitCode }, null, 2));
5848
+ else {
5849
+ write(renderRegistrationReport(result, scan));
5850
+ if (content) write(renderContentProbe(content));
5851
+ }
5801
5852
  return exitCode;
5802
5853
  }
5803
5854