mirror of
https://github.com/garrytan/gstack.git
synced 2026-08-07 14:58:37 +02:00
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:
co-authored by
Claude Opus 4.7
parent
e1b2b7fae9
commit
f95d5c54c3
@@ -37,7 +37,11 @@ import { existsSync, readFileSync } from "fs";
|
|||||||
import { homedir } from "os";
|
import { homedir } from "os";
|
||||||
import { join } from "path";
|
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 STATE_DIR = process.env.GSTACK_HOME || join(userHome(), ".gstack");
|
||||||
const SCRIPT_DIR = __dirname;
|
const SCRIPT_DIR = __dirname;
|
||||||
@@ -71,10 +75,12 @@ function tryReadJSON(path: string): unknown | null {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- gbrain binary presence + version ---
|
// --- 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 } {
|
function detectGbrain(): { onPath: boolean; version: string | null } {
|
||||||
const out = tryExec("sh", ["-c", "command -v gbrain"], 2_000);
|
const bin = resolveGbrainBin();
|
||||||
if (!out) return { onPath: false, version: null };
|
if (!bin) return { onPath: false, version: null };
|
||||||
const verRaw = tryExec("gbrain", ["--version"], 2_000);
|
const verRaw = readGbrainVersion();
|
||||||
if (!verRaw) return { onPath: true, version: null };
|
if (!verRaw) return { onPath: true, version: null };
|
||||||
// Match bash behavior: head -1 | tr -d '[:space:]'
|
// Match bash behavior: head -1 | tr -d '[:space:]'
|
||||||
const version = verRaw.split("\n")[0].replace(/\s+/g, "") || null;
|
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) ---
|
// --- 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 {
|
function detectDoctor(onPath: boolean): boolean {
|
||||||
if (!onPath) return false;
|
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;
|
if (!out) return false;
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(out) as { status?: string };
|
const parsed = JSON.parse(out) as { status?: string };
|
||||||
|
|||||||
@@ -91,34 +91,50 @@ function hashPath(p: string): string {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve the absolute path of `gbrain` on PATH. Returns null when missing.
|
* 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 {
|
try {
|
||||||
const out = execFileSync("sh", ["-c", "command -v gbrain"], {
|
const out = execFileSync("sh", ["-c", "command -v gbrain"], {
|
||||||
encoding: "utf-8",
|
encoding: "utf-8",
|
||||||
timeout: 2_000,
|
timeout: 2_000,
|
||||||
stdio: ["ignore", "pipe", "ignore"],
|
stdio: ["ignore", "pipe", "ignore"],
|
||||||
env: env ?? process.env,
|
env: e,
|
||||||
});
|
});
|
||||||
return out.trim() || null;
|
result = out.trim() || null;
|
||||||
} catch {
|
} 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 {
|
try {
|
||||||
const out = execFileSync("gbrain", ["--version"], {
|
const out = execFileSync("gbrain", ["--version"], {
|
||||||
encoding: "utf-8",
|
encoding: "utf-8",
|
||||||
timeout: 2_000,
|
timeout: 2_000,
|
||||||
stdio: ["ignore", "pipe", "ignore"],
|
stdio: ["ignore", "pipe", "ignore"],
|
||||||
env: env ?? process.env,
|
env: e,
|
||||||
});
|
});
|
||||||
return out.trim().split("\n")[0] || "";
|
result = out.trim().split("\n")[0] || "";
|
||||||
} catch {
|
} catch {
|
||||||
return "";
|
result = "";
|
||||||
}
|
}
|
||||||
|
_gbrainVersionCache.set(key, result);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function configFingerprint(): { mtime: number; size: number } {
|
function configFingerprint(): { mtime: number; size: number } {
|
||||||
|
|||||||
Reference in New Issue
Block a user