mirror of https://github.com/garrytan/gstack.git
81 lines
3.6 KiB
TypeScript
81 lines
3.6 KiB
TypeScript
/**
|
|
* Static-source invariant: every gbrain CLI invocation in the hot-path
|
|
* sync code MUST route through `lib/gbrain-exec.ts` (or accept env via
|
|
* the existing `lib/gbrain-sources.ts` opts surface). A future contributor
|
|
* who adds a `spawnSync("gbrain", ...)` call directly in
|
|
* `bin/gstack-gbrain-sync.ts` or `bin/gstack-memory-ingest.ts` silently
|
|
* regresses the DATABASE_URL fix from #1508 + codex review #7 — gbrain's
|
|
* dotenv autoload pulls a host project's `.env.local` value instead of
|
|
* gbrain's own config.
|
|
*
|
|
* This test reads each source file directly and asserts zero direct
|
|
* `spawnSync("gbrain"`, `spawn("gbrain"`, `execFileSync("gbrain"`, or
|
|
* `execSync(...gbrain` matches. Bun runs TS directly so there is no
|
|
* compiled artifact to grep — the .ts source is the truth.
|
|
*
|
|
* The check is intentionally narrow: only the two files where the bug
|
|
* actually hurts users are guarded. Other gbrain spawn sites
|
|
* (`lib/gbrain-sources.ts`, `lib/gbrain-local-status.ts`,
|
|
* `lib/gstack-memory-helpers.ts`, `bin/gstack-brain-context-load.ts`)
|
|
* either already accept env from callers or run probes that don't need
|
|
* DATABASE_URL. Expanding the invariant to those files is a follow-up.
|
|
*/
|
|
|
|
import { describe, it, expect } from "bun:test";
|
|
import { readFileSync } from "fs";
|
|
import { join } from "path";
|
|
|
|
const ROOT = join(import.meta.dir, "..");
|
|
|
|
const GUARDED_FILES = [
|
|
"bin/gstack-gbrain-sync.ts",
|
|
"bin/gstack-memory-ingest.ts",
|
|
];
|
|
|
|
// Patterns that would bypass lib/gbrain-exec.ts. Match the literal `"gbrain"`
|
|
// as the first argument since these helpers are the failure mode.
|
|
const BANNED_PATTERNS: Array<{ name: string; regex: RegExp }> = [
|
|
{ name: 'spawnSync("gbrain", ...)', regex: /spawnSync\s*\(\s*["']gbrain["']/g },
|
|
{ name: 'spawn("gbrain", ...)', regex: /\bspawn\s*\(\s*["']gbrain["']/g },
|
|
{ name: 'execFileSync("gbrain", ...)', regex: /execFileSync\s*\(\s*["']gbrain["']/g },
|
|
{ name: 'execSync("...gbrain...")', regex: /execSync\s*\(\s*["'`][^"'`]*\bgbrain\b/g },
|
|
];
|
|
|
|
describe("gbrain-exec invariant", () => {
|
|
for (const relpath of GUARDED_FILES) {
|
|
it(`${relpath} routes every gbrain spawn through lib/gbrain-exec.ts`, () => {
|
|
const source = readFileSync(join(ROOT, relpath), "utf-8");
|
|
// Strip block comments and line comments before scanning — a
|
|
// documentation reference like `// spawnSync("gbrain", ...)` in a
|
|
// comment shouldn't trip the invariant. The strip is approximate
|
|
// (sufficient for the patterns we care about); production code
|
|
// should match cleanly.
|
|
const stripped = source
|
|
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
.replace(/\/\/.*$/gm, "");
|
|
|
|
for (const { name, regex } of BANNED_PATTERNS) {
|
|
const matches = stripped.match(regex) || [];
|
|
if (matches.length > 0) {
|
|
// Find the line numbers to make the failure actionable.
|
|
const lines = stripped.split("\n");
|
|
const hits: string[] = [];
|
|
for (let i = 0; i < lines.length; i++) {
|
|
if (new RegExp(regex.source).test(lines[i])) {
|
|
hits.push(` ${relpath}:${i + 1}: ${lines[i].trim()}`);
|
|
}
|
|
}
|
|
throw new Error(
|
|
`Found ${matches.length} direct gbrain invocation(s) in ${relpath} matching \`${name}\`:\n${hits.join("\n")}\n\n`
|
|
+ `Route every gbrain spawn through \`spawnGbrain\`/\`execGbrainJson\`/\`execGbrainText\` `
|
|
+ `in lib/gbrain-exec.ts so DATABASE_URL is seeded from gbrain's config.`,
|
|
);
|
|
}
|
|
}
|
|
|
|
// Positive assertion: the file should import from lib/gbrain-exec.
|
|
expect(source).toMatch(/from\s+["']\.\.\/lib\/gbrain-exec["']/);
|
|
});
|
|
}
|
|
});
|