fix(brain-context): cold-start probe latency permanently disabled gbrain context

gbrainAvailable() spawned gbrain --version under a 500ms budget; a cold CLI
start on a loaded machine blew the timeout, misclassified gbrain as missing,
and every skill session silently ran brainless — plus the per-query re-probe
burned 3x the budget before any real work. Replaced with a memoized
stat-based PATH scan (PATHEXT-aware on Windows) and made the query timeout
overridable via GSTACK_BRAIN_TIMEOUT_MS for loaded CI environments.

Also picks up the fork's manifest-filter coverage (#1687 shape) against the
fake-gbrain harness — passes against our existing filter support.

Ported from time-attack/gstack (GStack 2).

Co-authored-by: Sina Matian <sina@time-attack.dev>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-08-14 11:55:23 -07:00
co-authored by Sina Matian Claude Fable 5
parent 225e6e4ccd
commit 329d8d6921
2 changed files with 71 additions and 14 deletions
+44 -1
View File
@@ -55,7 +55,12 @@ fi
function prependPath(binDir: string): Record<string, string> {
const pathKey = Object.keys(process.env).find((key) => key.toLowerCase() === "path") || "PATH";
const currentPath = process.env[pathKey] || "";
return { [pathKey]: `${binDir}${delimiter}${currentPath}` };
return {
[pathKey]: `${binDir}${delimiter}${currentPath}`,
// Cold process spawns on a loaded machine can exceed the 500ms default
// budget; the fake gbrain is instant once spawned, so give it headroom.
GSTACK_BRAIN_TIMEOUT_MS: "10000",
};
}
describe("gstack-brain-context-load CLI", () => {
@@ -252,6 +257,44 @@ describe("gstack-brain-context-load — graceful gbrain absence", () => {
}
});
it("manifest filter: blocks reach gbrain as --filter args with template vars resolved (#1687)", () => {
const dir = mkdtempSync(join(tmpdir(), "gstack-bcl-"));
const binDir = join(dir, "bin");
mkdirSync(binDir);
writeFakeGbrain(binDir);
const skillFile = join(dir, "SKILL.md");
writeFileSync(
skillFile,
`---
name: x
gbrain:
schema: 1
context_queries:
- id: prior-sessions
kind: list
filter:
type: ceo-plan
tags_contains: "repo:{repo_slug}"
sort: updated_at_desc
limit: 5
render_as: "## Prior sessions"
---
`,
"utf-8"
);
try {
const r = runScript(["--skill-file", skillFile, "--repo", "my-test-repo"], prependPath(binDir));
expect(r.exitCode).toBe(0);
expect(r.stdout).toContain("fake gbrain list_pages");
expect(r.stdout).toContain("--filter type=ceo-plan");
expect(r.stdout).toContain("--filter tags_contains=repo:my-test-repo");
expect(r.stdout).toContain("--sort updated_at_desc");
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
it("vector + list queries still complete (with SKIP) when gbrain CLI is missing", () => {
// We can't easily un-install gbrain; rely on the helper's own missing-binary
// detection. The default manifest uses kind: list which calls gbrain. If