@ngockhoale/ukit 2.2.6 → 2.2.7

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
@@ -2,6 +2,31 @@
2
2
 
3
3
  All notable changes to UKit are documented here.
4
4
 
5
+ ## 2.2.7 - 2026-08-28
6
+
7
+ Follow-up to 2.2.6: that release only made the wrong-runtime failure message *explain* the
8
+ `UKIT_NODE_PATH` workaround better, but a user still had to diagnose the error and export the
9
+ variable by hand. This release removes the manual step for the common case.
10
+
11
+ ### Fixed
12
+
13
+ - **The omp bridge now auto-detects a working Node.js binary instead of requiring
14
+ `UKIT_NODE_PATH` to be set by hand.** [`ukit-bridge.js`](.omp/hooks/pre/ukit-bridge.js) probes
15
+ `process.execPath` with `--version` before trusting it; if that fails (e.g. a Bun-hosted or
16
+ self-compiled omp reporting its own binary as `process.execPath`), it falls back to the bare
17
+ `node` command name and lets normal OS `PATH` resolution find a real Node.js binary — this
18
+ works the same way on macOS, Linux, and Windows, since Node's `child_process` spawn does `PATH`
19
+ lookup for bare executable names on all three. An explicit `UKIT_NODE_PATH` still always wins
20
+ and skips the probe. The probe result is cached for the life of the omp process, so it only
21
+ runs once, not on every tool call. The transport-failure message's `UKIT_NODE_PATH` hint now
22
+ only appears when both the probe and the `node` PATH fallback have failed.
23
+
24
+ ### Added
25
+
26
+ - **1 regression test** in `tests/hooks/ompHookBridge.test.js` — with `UKIT_NODE_PATH` unset and
27
+ `process.execPath` mocked to fail the `--version` probe, the bridge falls back to invoking the
28
+ bare `node` command instead of blocking the tool call.
29
+
5
30
  ## 2.2.6 - 2026-08-27
6
31
 
7
32
  Follow-up to a real-world report: a user on a Bun-hosted/self-compiled omp build kept getting
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ngockhoale/ukit",
3
- "version": "2.2.6",
3
+ "version": "2.2.7",
4
4
  "description": "Install/update an index-first AI workspace for Claude Code, OpenAI Codex, OpenCode, and omp (Oh My Pi).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -8,6 +8,7 @@
8
8
  import fs from 'node:fs';
9
9
  import path from 'node:path';
10
10
  import { fileURLToPath } from 'node:url';
11
+ import { spawnSync } from 'node:child_process';
11
12
  import {
12
13
  evaluateCompletion,
13
14
  incrementContinuation,
@@ -181,6 +182,30 @@ function recordHookErrorDiagnostic(projectRoot, sessionId, diagnostic) {
181
182
  }
182
183
  }
183
184
 
185
+ // Cached across the omp process lifetime: resolution below spawns a probe process, and every
186
+ // tool call would otherwise pay that cost again.
187
+ let cachedNodeExecutable = null;
188
+
189
+ function isRealNodeBinary(candidate) {
190
+ try {
191
+ const probe = spawnSync(candidate, ['--version'], { timeout: 2000, encoding: 'utf8' });
192
+ return !probe.error && typeof probe.stdout === 'string' && /^v\d+\.\d+\.\d+/.test(probe.stdout.trim());
193
+ } catch {
194
+ return false;
195
+ }
196
+ }
197
+
198
+ // Do not blindly trust process.execPath: under a Bun-hosted or self-compiled omp, it can report
199
+ // omp's own binary path instead of a real Node.js one. Probe it first; if it fails, fall back to
200
+ // the bare "node" command name so PATH resolution (cross-platform: macOS/Linux/Windows) finds a
201
+ // real Node.js binary automatically, without requiring a manual UKIT_NODE_PATH export.
202
+ function resolveNodeExecutable() {
203
+ if (process.env.UKIT_NODE_PATH) return process.env.UKIT_NODE_PATH;
204
+ if (cachedNodeExecutable) return cachedNodeExecutable;
205
+ cachedNodeExecutable = isRealNodeBinary(process.execPath) ? process.execPath : 'node';
206
+ return cachedNodeExecutable;
207
+ }
208
+
184
209
  export async function runScriptChain(
185
210
  pi,
186
211
  scripts,
@@ -195,8 +220,7 @@ export async function runScriptChain(
195
220
 
196
221
  const runnerPath = path.join(projectRoot, '.claude', 'ukit', 'runtime', 'hook-chain-runner.mjs');
197
222
  const scriptPaths = scripts.map((scriptName) => path.join(projectRoot, '.claude', 'hooks', scriptName));
198
- // Do not blindly trust process.execPath: under a Bun-hosted omp, it points at bun, not node.
199
- const nodeExecutable = process.env.UKIT_NODE_PATH || process.execPath;
223
+ const nodeExecutable = resolveNodeExecutable();
200
224
  const startedAt = Date.now();
201
225
  let execResult;
202
226
  try {
@@ -243,9 +267,9 @@ export async function runScriptChain(
243
267
  recordHookErrorDiagnostic(projectRoot, payload.session_id, diagnostic);
244
268
  const nodePathHint = process.env.UKIT_NODE_PATH
245
269
  ? ''
246
- : ` "${diagnostic.nodeExecutable}" may not be a real Node.js binary (e.g. a Bun-hosted or `
247
- + `self-compiled omp reports its own path as process.execPath). If so, set UKIT_NODE_PATH `
248
- + `to one, e.g.: export UKIT_NODE_PATH="$(command -v node)".`;
270
+ : ` UKit already tried process.execPath and a "node" PATH lookup; neither resolved to a `
271
+ + `working Node.js binary (runtime="${diagnostic.nodeExecutable}"). Set UKIT_NODE_PATH to `
272
+ + `an explicit Node.js binary path to override, e.g.: export UKIT_NODE_PATH="$(command -v node)".`;
249
273
  const reason = `UKit OMP hook runner failed before producing a valid result `
250
274
  + `(killed=${diagnostic.killed}, code=${diagnostic.code}, elapsedMs=${diagnostic.elapsedMs}, `
251
275
  + `runtime=${diagnostic.nodeExecutable}). No safety-gate verdict was available for [${scripts.join(', ')}]. `