@holmes-lab/holmes-kit 0.3.1 → 0.3.2

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/CHANGELOG.md CHANGED
@@ -4,6 +4,24 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+ <!-- @implements A-SPEC-209 -->
8
+ ## [0.3.2] - 2026-08-31
9
+
10
+ Windows doctor unbroken: the wiring-handshake checks can now actually run the wiring they judge.
11
+
12
+ ### Fixed
13
+
14
+ - **doctor wiring handshake on Windows** (A-SPEC-499.1): the four wiring checks (claude / codex /
15
+ antigravity handshake + mcp wiring spawn) spawned `npx` directly, which on win32 is `npx.cmd`
16
+ and cannot be executed by a plain spawn — every check died `spawn npx ENOENT` on a correctly
17
+ wired target (field-measured on 0.3.1). A pure platform adapter now reroutes execution through
18
+ `cmd.exe /d /s /c` (whitespace-bearing tokens quoted — space-in-username installs included);
19
+ posix spawn arguments are value-identical to before.
20
+ - **ENOENT prescription**: a command the OS cannot find no longer gets "Re-run `holmes-kit init`"
21
+ — re-initing into the identical failure forever was the measured loop. It now says the command
22
+ could not be resolved (PATH/platform) and to try it in a shell; genuine stale-wiring failures
23
+ keep the re-init wording byte-for-byte.
24
+
7
25
  <!-- @implements A-SPEC-209 -->
8
26
  ## [0.3.1] - 2026-08-31
9
27
 
package/dist/.build-id CHANGED
@@ -1 +1 @@
1
- 9dd2f60-mthaxwnr
1
+ 5010ff8-mthc0xla
@@ -95,6 +95,13 @@ export interface ProbeRunner {
95
95
  };
96
96
  }
97
97
  export declare function runDoctor(packageRoot: string, target?: string, opts?: DoctorOptions, extraChecks?: Check[]): Promise<Check[]>;
98
+ /**
99
+ * Drive a real MCP stdio handshake: initialize -> initialized -> tools/list, with a timeout.
100
+ * Async by necessity — the protocol is a ROUND TRIP, so a spawnSync that writes everything at once
101
+ * and closes stdin makes the server exit before answering (a false FAIL this check produced on its
102
+ * very first run against a healthy server).
103
+ */
104
+ export declare function wiringSpawnCheck(command: string, args: string[], timeoutMs?: number, platform?: NodeJS.Platform): Promise<Check>;
98
105
  export declare function formatChecks(checks: Check[]): string;
99
106
  /**
100
107
  * Prove that EVERY wired harness can actually start a server, not merely that its file parses.
@@ -39,6 +39,7 @@ exports.npmCliEntry = npmCliEntry;
39
39
  exports.prefixVerdict = prefixVerdict;
40
40
  exports.probeEnv = probeEnv;
41
41
  exports.runDoctor = runDoctor;
42
+ exports.wiringSpawnCheck = wiringSpawnCheck;
42
43
  exports.formatChecks = formatChecks;
43
44
  exports.wiringHandshakeChecks = wiringHandshakeChecks;
44
45
  exports.semanticTierVerdict = semanticTierVerdict;
@@ -52,6 +53,7 @@ const path = __importStar(require("node:path"));
52
53
  const role_policy_1 = require("../governance/role-policy");
53
54
  const blind_spots_1 = require("../guardrail/blind-spots");
54
55
  const node_child_process_1 = require("node:child_process");
56
+ const spawn_spec_1 = require("./spawn-spec");
55
57
  const os = __importStar(require("node:os"));
56
58
  const settings_merge_1 = require("./settings-merge");
57
59
  const playbook_skills_1 = require("./playbook-skills");
@@ -1026,8 +1028,20 @@ function cleanupOnSignal(dir) {
1026
1028
  // Spawn the TARGET's wiring verbatim and require initialize inside the deadline. Unlike
1027
1029
  // `mcpHandshakeCheck` below (which proves this INSTALL can serve), this proves the WIRING the
1028
1030
  // harness will actually run reaches a server — the gap the dead npx form lived in.
1029
- function wiringSpawnCheck(command, args, timeoutMs = 30000) {
1031
+ // @implements A-SPEC-499.1 exported for the prescription tests; `platform` is injectable so the
1032
+ // win32 branch is testable off-Windows.
1033
+ function wiringSpawnCheck(command, args, timeoutMs = 30000, platform = process.platform) {
1034
+ // The DISPLAYED command stays the original wiring string even when the win32 adapter rewraps the
1035
+ // execution — the user compares this against their wiring file, not against cmd.exe plumbing.
1030
1036
  const quoted = `${command} ${args.join(' ')}`;
1037
+ // @implements A-SPEC-499.1 — Windows field measurement: four wiring checks on a correctly rewired
1038
+ // 0.3.1 target all died `spawn npx ENOENT`, because `npx` is `npx.cmd` there and a plain spawn
1039
+ // cannot execute a .cmd shim. The adapter reroutes through cmd.exe; posix is value-identical.
1040
+ const spec = (0, spawn_spec_1.spawnSpecFor)(command, args, platform);
1041
+ // The re-init prescription is only honest when the wiring might actually be stale. A command the
1042
+ // OS cannot find (ENOENT) re-inits into the identical failure forever — the measured loop.
1043
+ const enoentFix = `\`${command}\` 명령을 찾지 못했습니다(PATH 또는 플랫폼) — 배선 문자열이 옳다면 재-init은 도움이 되지 않습니다. 같은 명령을 셸에서 직접 실행해 확인하십시오.`;
1044
+ const spawnFailFix = (e) => e?.code === 'ENOENT' ? enoentFix : 'Re-run `holmes-kit init` in the target to rewrite the wiring.';
1031
1045
  return new Promise((resolve) => {
1032
1046
  let done = false;
1033
1047
  let out = '';
@@ -1045,13 +1059,13 @@ function wiringSpawnCheck(command, args, timeoutMs = 30000) {
1045
1059
  };
1046
1060
  const timer = setTimeout(() => finish('FAIL', `wiring did not answer initialize within ${timeoutMs / 1000}s: \`${quoted}\``, 'Re-run `holmes-kit init` in the target to rewrite the wiring, then re-run doctor.'), timeoutMs);
1047
1061
  try {
1048
- child = (0, node_child_process_1.spawn)(command, args, { stdio: ['pipe', 'pipe', 'pipe'] });
1062
+ child = (0, node_child_process_1.spawn)(spec.command, spec.args, { stdio: ['pipe', 'pipe', 'pipe'] });
1049
1063
  }
1050
1064
  catch (e) {
1051
- finish('FAIL', `wiring could not be spawned: \`${quoted}\` — ${e.message}`, 'Re-run `holmes-kit init` in the target to rewrite the wiring.');
1065
+ finish('FAIL', `wiring could not be spawned: \`${quoted}\` — ${e.message}`, spawnFailFix(e));
1052
1066
  return;
1053
1067
  }
1054
- child.on('error', (e) => finish('FAIL', `wiring could not be spawned: \`${quoted}\` — ${e.message}`, 'Re-run `holmes-kit init` in the target to rewrite the wiring.'));
1068
+ child.on('error', (e) => finish('FAIL', `wiring could not be spawned: \`${quoted}\` — ${e.message}`, spawnFailFix(e)));
1055
1069
  child.on('exit', (code) => finish('FAIL', `wiring exited (code ${code}) before answering initialize: \`${quoted}\``, 'Re-run `holmes-kit init` in the target to rewrite the wiring.'));
1056
1070
  child.stdin?.on('error', () => { });
1057
1071
  child.stdout?.on('data', (d) => {
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Platform adapter for spawning a WIRING command verbatim. On win32, `npx` is `npx.cmd`, and a
3
+ * plain child_process.spawn cannot execute a .cmd shim (Node's CVE-2024-27980 hardening) — the
4
+ * Windows field measurement: doctor's four wiring-handshake checks all died `spawn npx ENOENT`
5
+ * on a target whose wiring was provably correct. Routing through cmd.exe is what the check's own
6
+ * purpose demands (it executes what the wiring file says), and no interpolation is added: tokens
7
+ * pass through byte-identical, quoted only when they carry whitespace (the measured
8
+ * space-in-username install path).
9
+ *
10
+ * PURE — platform arrives as an argument so every branch is testable off-Windows.
11
+ */
12
+ export declare function spawnSpecFor(command: string, args: string[], platform: NodeJS.Platform): {
13
+ command: string;
14
+ args: string[];
15
+ };
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.spawnSpecFor = spawnSpecFor;
4
+ // @implements A-SPEC-499.1
5
+ /**
6
+ * Platform adapter for spawning a WIRING command verbatim. On win32, `npx` is `npx.cmd`, and a
7
+ * plain child_process.spawn cannot execute a .cmd shim (Node's CVE-2024-27980 hardening) — the
8
+ * Windows field measurement: doctor's four wiring-handshake checks all died `spawn npx ENOENT`
9
+ * on a target whose wiring was provably correct. Routing through cmd.exe is what the check's own
10
+ * purpose demands (it executes what the wiring file says), and no interpolation is added: tokens
11
+ * pass through byte-identical, quoted only when they carry whitespace (the measured
12
+ * space-in-username install path).
13
+ *
14
+ * PURE — platform arrives as an argument so every branch is testable off-Windows.
15
+ */
16
+ function spawnSpecFor(command, args, platform) {
17
+ if (platform !== 'win32')
18
+ return { command, args: [...args] };
19
+ const joined = [command, ...args].map((t) => (/\s/.test(t) ? `"${t}"` : t)).join(' ');
20
+ return { command: 'cmd.exe', args: ['/d', '/s', '/c', joined] };
21
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "//": "@implements A-SPEC-209",
3
3
  "name": "@holmes-lab/holmes-kit",
4
- "version": "0.3.1",
4
+ "version": "0.3.2",
5
5
  "description": "Holmes-Kit — deterministic Agentic Software Engineering (ASE) harness with causal traceability (spec chain + D-CPG + RTM + phase guardrail)",
6
6
  "main": "dist/holmes/mcp/server.js",
7
7
  "types": "dist/holmes/mcp/server.d.ts",