feat(memory): optional gbrain --semantic recall for decision search

Adds gstack-decision-search --semantic (with --query): appends a 'Related from
memory' block from gbrain semantic search, scoped to the curated-memory source.
Pure enhancement, reliability-first: a new lib/gstack-decision-semantic.ts is the
ONLY decision module that touches gbrain and is imported lazily only on --semantic,
so the reliable file path never loads gbrain code. Every path degrades to the
reliable file results when gbrain is off, unconfigured, empty, or errors (never
throws, 10s timeout).

Built against the verified gbrain 0.42.x surface (text output [score] slug --
snippet, NOT JSON; curated-memory source resolved by worktree path, not a
gstack-brain-<user> id). Deterministic-contract tests only: parser units,
degrade-to-null when gbrain absent, and a fake-gbrain shim proving scope+search
end-to-end. find-contradictions deferred (no verifiable CLI surface yet + curated
memory not indexed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-06-07 17:59:34 -07:00
parent e7325cdeea
commit fa250db27b
5 changed files with 314 additions and 2 deletions
+57
View File
@@ -102,3 +102,60 @@ describe("gstack-decision-search", () => {
expect(search()).toBe("");
});
});
describe("gstack-decision-search --semantic (optional gbrain enhancement)", () => {
function shimDir(gbrainBody: string): string {
const d = fs.mkdtempSync(path.join(os.tmpdir(), "gbrain-shim-"));
const p = path.join(d, "gbrain");
fs.writeFileSync(p, gbrainBody, { mode: 0o755 });
fs.chmodSync(p, 0o755);
return d;
}
function searchWithPath(args: string, pathPrefix?: string): string {
const env = { ...process.env, GSTACK_HOME: tmpDir } as NodeJS.ProcessEnv;
if (pathPrefix) env.PATH = `${pathPrefix}:${process.env.PATH}`;
try {
return execSync(`${SEARCH} ${args}`, { cwd: ROOT, env, encoding: "utf-8", timeout: 20000 }).trim();
} catch {
return "";
}
}
test("--semantic without --query behaves like a normal search (no gbrain spawn)", () => {
log('{"decision":"reliable-alpha","scope":"repo","source":"user"}');
const out = searchWithPath("--semantic");
expect(out).toContain("reliable-alpha");
expect(out).not.toContain("Related from memory");
});
test("--semantic --query appends a related-memory block when gbrain returns hits", () => {
log('{"decision":"reliable-alpha","scope":"repo","source":"user"}');
const dir = shimDir(
`#!/usr/bin/env bash
if [ "$1" = "sources" ]; then echo '{"sources":[{"id":"default","local_path":"/u/.gstack-brain-worktree"}]}'; exit 0; fi
if [ "$1" = "search" ]; then echo "[0.88] decisions/related -- a semantically related past call"; exit 0; fi
exit 1
`,
);
try {
const out = searchWithPath("--query alpha --semantic", dir);
expect(out).toContain("reliable-alpha"); // reliable results still shown
expect(out).toContain("Related from memory");
expect(out).toContain("decisions/related");
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test("--semantic degrades silently when gbrain errors (reliable results stand)", () => {
log('{"decision":"reliable-alpha","scope":"repo","source":"user"}');
const dir = shimDir(`#!/usr/bin/env bash\nexit 1\n`);
try {
const out = searchWithPath("--query alpha --semantic", dir);
expect(out).toContain("reliable-alpha");
expect(out).not.toContain("Related from memory");
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
});