From ae32a72e65d0d1dd5d34711487dd5145b34d4b00 Mon Sep 17 00:00:00 2001 From: Bryan Slatner Date: Fri, 10 Jul 2026 10:01:37 -0400 Subject: [PATCH] fix(scripts): make add-lang bench scripts work on Windows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit verify-extraction.mjs hard-coded execFileSync('codegraph', ...), which is ENOENT on Windows: CreateProcess does no PATHEXT resolution, and npm ships CLIs only as .cmd / sh / .ps1 shims (no .exe), none of which Node can spawn without a shell. That forced FORCE_AB=1 workarounds to get past a phantom extraction failure. - verify-extraction.mjs: resolve the binary — CODEGRAPH_BIN env var first (.js/.mjs/.cjs run via process.execPath), else on win32 scan PATH for the .cmd/.exe/.bat shim and run batch shims through the shell with self-quoted args (Node does not escape when shell is enabled). POSIX default path is unchanged. Failure now exits 2 with a CODEGRAPH_BIN hint instead of a raw ENOENT. - bench.sh: honor CODEGRAPH_BIN via a codegraph() function shadowing the PATH lookup, exported so the verify-extraction.mjs child resolves the same binary. Unset-env behavior is unchanged. Validated on Windows 11 (Git Bash + PowerShell): env-var override, .cmd shim resolution with spaces in both shim dir and repo path, graceful no-binary failure, and a full bench.sh clone->index->verify->skip-A/B run. Co-Authored-By: Claude Fable 5 --- scripts/add-lang/bench.sh | 22 ++++++++++++-- scripts/add-lang/verify-extraction.mjs | 40 ++++++++++++++++++++++++-- 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/scripts/add-lang/bench.sh b/scripts/add-lang/bench.sh index 172fe4064..98ba30caf 100755 --- a/scripts/add-lang/bench.sh +++ b/scripts/add-lang/bench.sh @@ -9,7 +9,10 @@ # broken extractor); set FORCE_AB=1 to run it anyway. # # Usage: bench.sh "" [headless|tmux|all] -# Env: CORPUS corpus dir (default /tmp/codegraph-corpus, shared with agent-eval) +# Env: CORPUS corpus dir (default /tmp/codegraph-corpus, shared with agent-eval) +# CODEGRAPH_BIN path to the codegraph binary or dist/bin/codegraph.js — +# bypasses the PATH lookup (needed on Windows, where the +# npm shim isn't directly exec-able from Node children) set -uo pipefail LANG_TOKEN="${1:?usage: bench.sh \"\" [mode]}" @@ -23,10 +26,23 @@ AGENT_EVAL="$(cd "$HARNESS/../agent-eval" && pwd)" CORPUS="${CORPUS:-/tmp/codegraph-corpus}" REPO="$CORPUS/$NAME" -command -v codegraph >/dev/null || { echo "no codegraph on PATH (build + ./scripts/local-install.sh first)"; exit 1; } +# CODEGRAPH_BIN overrides the PATH lookup; the function shadows the `codegraph` +# command for the rest of the script, and the export lets verify-extraction.mjs +# (a Node child) resolve the same binary. +if [ -n "${CODEGRAPH_BIN:-}" ]; then + export CODEGRAPH_BIN + case "$CODEGRAPH_BIN" in + *.js|*.mjs|*.cjs) codegraph() { node "$CODEGRAPH_BIN" "$@"; } ;; + *) codegraph() { "$CODEGRAPH_BIN" "$@"; } ;; + esac + CODEGRAPH_DESC="$CODEGRAPH_BIN" +else + command -v codegraph >/dev/null || { echo "no codegraph on PATH (build + ./scripts/local-install.sh first, or set CODEGRAPH_BIN)"; exit 1; } + CODEGRAPH_DESC="$(command -v codegraph)" +fi echo "==================== add-lang bench: $NAME ($LANG_TOKEN) ====================" -echo "codegraph: $(command -v codegraph) -> $(codegraph --version 2>/dev/null || echo '?')" +echo "codegraph: $CODEGRAPH_DESC -> $(codegraph --version 2>/dev/null || echo '?')" # 1. Ensure the repo (shallow clone, reuse if present). mkdir -p "$CORPUS" diff --git a/scripts/add-lang/verify-extraction.mjs b/scripts/add-lang/verify-extraction.mjs index bdb443e25..b57957ce6 100755 --- a/scripts/add-lang/verify-extraction.mjs +++ b/scripts/add-lang/verify-extraction.mjs @@ -5,11 +5,16 @@ // // Usage: node scripts/add-lang/verify-extraction.mjs // Reads `codegraph status --json` using whatever codegraph is on PATH, -// so it reflects the binary that built the index. +// so it reflects the binary that built the index. Set CODEGRAPH_BIN to a +// specific binary (or to dist/bin/codegraph.js) to bypass the PATH lookup — +// on Windows, npm installs codegraph as a .cmd shim that execFileSync can't +// launch by bare name, so the lookup below resolves the shim itself. // // Exit codes: 0 = pass or soft-warn, 1 = critical fail, 2 = could not run. import { execFileSync } from 'node:child_process'; +import { existsSync } from 'node:fs'; +import path from 'node:path'; const [repo, lang] = process.argv.slice(2); if (!repo || !lang) { @@ -17,12 +22,43 @@ if (!repo || !lang) { process.exit(2); } +function resolveCodegraphBin() { + if (process.env.CODEGRAPH_BIN) return process.env.CODEGRAPH_BIN; + if (process.platform !== 'win32') return 'codegraph'; + // Windows: find the npm shim on PATH ourselves. The extensionless shim is a + // sh script (Git Bash only); .cmd/.exe are what a Node child can run. + for (const dir of (process.env.PATH || '').split(path.delimiter)) { + if (!dir) continue; + for (const ext of ['.cmd', '.exe', '.bat']) { + const candidate = path.join(dir, `codegraph${ext}`); + if (existsSync(candidate)) return candidate; + } + } + return 'codegraph'; +} + +function runCodegraph(args) { + const bin = resolveCodegraphBin(); + const ext = path.extname(bin).toLowerCase(); + if (ext === '.js' || ext === '.mjs' || ext === '.cjs') { + return execFileSync(process.execPath, [bin, ...args], { encoding: 'utf8' }); + } + if (ext === '.cmd' || ext === '.bat') { + // Batch shims only run through cmd.exe (shell:true). Node doesn't escape + // when shell is enabled, so quote every token ourselves. + const q = (s) => (/[\s"^&|<>()]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s); + return execFileSync(q(bin), args.map(q), { encoding: 'utf8', shell: true }); + } + return execFileSync(bin, args, { encoding: 'utf8' }); +} + let status; try { - const out = execFileSync('codegraph', ['status', repo, '--json'], { encoding: 'utf8' }); + const out = runCodegraph(['status', repo, '--json']); status = JSON.parse(out); } catch (e) { console.error(`[verify] could not read codegraph status for ${repo}: ${e.message}`); + console.error('[verify] hint: set CODEGRAPH_BIN to the codegraph binary or dist/bin/codegraph.js'); process.exit(2); }