perf(gbrain): memoize gbrain resolution + use --fast doctor in detect

Cuts detect's wall time substantially by sharing fork-exec results
between the helper that walks the JSON output and the localEngineStatus
classifier from lib/gbrain-local-status.ts.

Before: detect made 2x `command -v gbrain` calls (one in detect's
detectGbrain, one in the classifier's resolveGbrainBin) and 2x
`gbrain --version` calls. With memoization keyed on PATH, both
collapse to one fork each (~400ms saved per skill preamble).

Also adds `--fast` to the `gbrain doctor --json` call in detect so a
broken-db config (Garry's repro) doesn't burn a full 5s timeout on the
doctor's DB-connection check. The classifier still probes the DB
directly via `gbrain sources list --json` for engine reachability —
that's `gbrain_local_status`, separate from the coarse
`gbrain_doctor_ok` summary flag.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-05-13 14:37:29 -07:00
parent e1b2b7fae9
commit f95d5c54c3
2 changed files with 40 additions and 14 deletions
+25 -9
View File
@@ -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<string, string | null>();
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<string, string>();
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 } {