diff --git a/bin/gstack-gbrain-detect b/bin/gstack-gbrain-detect index b0e9931eb..66503905e 100755 --- a/bin/gstack-gbrain-detect +++ b/bin/gstack-gbrain-detect @@ -37,7 +37,11 @@ import { existsSync, readFileSync } from "fs"; import { homedir } from "os"; import { join } from "path"; -import { localEngineStatus } from "../lib/gbrain-local-status"; +import { + localEngineStatus, + resolveGbrainBin, + readGbrainVersion, +} from "../lib/gbrain-local-status"; const STATE_DIR = process.env.GSTACK_HOME || join(userHome(), ".gstack"); const SCRIPT_DIR = __dirname; @@ -71,10 +75,12 @@ function tryReadJSON(path: string): unknown | null { } // --- gbrain binary presence + version --- +// Uses the shared memoized resolvers from lib/gbrain-local-status.ts so +// detect and the classifier share probe results within one process. function detectGbrain(): { onPath: boolean; version: string | null } { - const out = tryExec("sh", ["-c", "command -v gbrain"], 2_000); - if (!out) return { onPath: false, version: null }; - const verRaw = tryExec("gbrain", ["--version"], 2_000); + const bin = resolveGbrainBin(); + if (!bin) return { onPath: false, version: null }; + const verRaw = readGbrainVersion(); if (!verRaw) return { onPath: true, version: null }; // Match bash behavior: head -1 | tr -d '[:space:]' const version = verRaw.split("\n")[0].replace(/\s+/g, "") || null; @@ -93,9 +99,13 @@ function detectConfig(): { exists: boolean; engine: "pglite" | "postgres" | null } // --- gbrain doctor health (any nonzero exit or non-"ok"/"warnings" status → false) --- +// +// Uses --fast to avoid hanging on a dead DB. Per the local-status classifier +// (which probes DB directly via `gbrain sources list`), gbrain_doctor_ok is a +// coarse health summary, not engine-reachability — that's gbrain_local_status. function detectDoctor(onPath: boolean): boolean { if (!onPath) return false; - const out = tryExec("gbrain", ["doctor", "--json"], 5_000); + const out = tryExec("gbrain", ["doctor", "--json", "--fast"], 3_000); if (!out) return false; try { const parsed = JSON.parse(out) as { status?: string }; diff --git a/lib/gbrain-local-status.ts b/lib/gbrain-local-status.ts index 3fd22919b..e646abd61 100644 --- a/lib/gbrain-local-status.ts +++ b/lib/gbrain-local-status.ts @@ -91,34 +91,50 @@ function hashPath(p: string): string { /** * Resolve the absolute path of `gbrain` on PATH. Returns null when missing. - * Uses `command -v` semantics via execFileSync. + * Memoized per-process keyed on PATH so detect's call and the classifier's + * call share one fork-exec (~200ms saved per skill preamble). */ -function resolveGbrainBin(env?: NodeJS.ProcessEnv): string | null { +const _gbrainBinCache = new Map(); +export function resolveGbrainBin(env?: NodeJS.ProcessEnv): string | null { + const e = env ?? process.env; + const key = e.PATH || ""; + if (_gbrainBinCache.has(key)) return _gbrainBinCache.get(key)!; + let result: string | null = null; try { const out = execFileSync("sh", ["-c", "command -v gbrain"], { encoding: "utf-8", timeout: 2_000, stdio: ["ignore", "pipe", "ignore"], - env: env ?? process.env, + env: e, }); - return out.trim() || null; + result = out.trim() || null; } catch { - return null; + result = null; } + _gbrainBinCache.set(key, result); + return result; } -function readGbrainVersion(env?: NodeJS.ProcessEnv): string { +/** Memoized per-process. */ +const _gbrainVersionCache = new Map(); +export function readGbrainVersion(env?: NodeJS.ProcessEnv): string { + const e = env ?? process.env; + const key = `${e.PATH || ""}|${resolveGbrainBin(e) || ""}`; + if (_gbrainVersionCache.has(key)) return _gbrainVersionCache.get(key)!; + let result = ""; try { const out = execFileSync("gbrain", ["--version"], { encoding: "utf-8", timeout: 2_000, stdio: ["ignore", "pipe", "ignore"], - env: env ?? process.env, + env: e, }); - return out.trim().split("\n")[0] || ""; + result = out.trim().split("\n")[0] || ""; } catch { - return ""; + result = ""; } + _gbrainVersionCache.set(key, result); + return result; } function configFingerprint(): { mtime: number; size: number } {