test(memory-ingest): pin put_page regression + scrub stale name from --help and comments (#1346)

#1346 reported that gstack-memory-ingest still called the renamed
gbrain put_page subcommand on gbrain v0.18+. The actual code migrated
to `gbrain put` and later to batch `gbrain import <dir>` before this
report landed — only documentation lag remained.

This commit:
- Updates the --help string ("Skip gbrain put calls (still updates
  state file)") so user-facing docs match the shipped subcommand
- Updates two inline comments that still referenced the old name
- Adds test/memory-ingest-no-put_page.test.ts: a regression pin that
  strips comments from bin/gstack-memory-ingest.ts and fails the build
  if "put_page" appears in any active code or string literal, plus a
  sanity check that the file still calls a supported gbrain page-write
  verb (put or import)

Closes #1346. Reporter @kylma-code surfaced the doc lag; the original
code migration credit is on the v1.27.x wave.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Garry Tan 2026-05-18 20:38:03 -07:00
parent 8c956fa2a8
commit 765e0d3195
No known key found for this signature in database
GPG Key ID: C1F69E85C74EFE1D
2 changed files with 57 additions and 3 deletions

View File

@ -194,7 +194,7 @@ Options:
--all-history Walk transcripts older than 90 days too.
--sources <list> Comma-separated subset: ${ALL_TYPES.join(",")}
--limit <N> Stop after N pages written (smoke testing).
--no-write Skip gbrain put_page calls (still updates state file).
--no-write Skip gbrain put calls (still updates state file).
Used by tests + dry runs without actual ingest.
--scan-secrets Opt-in per-file gitleaks scan during prepare. Off by
default; gstack-brain-sync already gates the git-push
@ -1061,7 +1061,7 @@ async function probeMode(args: CliArgs): Promise<ProbeReport> {
}
// Per ED2: ~25-35 min for ~11.7K transcripts = ~150ms/page synchronous
// (gitleaks + render + put_page + embedding). Scale linearly.
// (gitleaks + render + put + embedding). Scale linearly.
const estimateMinutes = Math.max(1, Math.round((newCount + updatedCount) * 0.15 / 60));
return {
@ -1374,7 +1374,7 @@ async function ingestPass(args: CliArgs): Promise<BulkResult> {
if (args.noWrite) {
// --no-write: skip the gbrain import call but still record state for
// prepared pages (treat them as ingested for dedup purposes). Matches
// the prior contract from --help: "Skip gbrain put_page calls (still
// the prior contract from --help: "Skip gbrain put calls (still
// updates state file)".
const nowIso = new Date().toISOString();
for (const p of prep.prepared) {

View File

@ -0,0 +1,54 @@
/**
* Regression pin for #1346: gstack-memory-ingest must never call the
* `gbrain put_page` subcommand (renamed to `put` in gbrain v0.18+).
*
* The original bug shipped a literal `"put_page"` in execFileSync args,
* crashing every transcript ingest against modern gbrain. The fix migrated
* the per-file path to `gbrain put <slug>` and later to the batch
* `gbrain import <dir>` runner. This test pins both surfaces: source code
* must not contain `put_page` outside comments, and any future contributor
* adding it back trips the build.
*/
import { describe, it, expect } from "bun:test";
import { readFileSync } from "fs";
import { join } from "path";
const SOURCE_PATH = join(import.meta.dir, "..", "bin", "gstack-memory-ingest.ts");
/**
* Strip line comments (`// ...`) and block comments (`/* ... */`) from TS
* source so the regression check only inspects executable code. Naive but
* sufficient we don't need full TS parsing, just to ignore the
* documentation/changelog mentions of the old subcommand name.
*
* Order matters: strip block comments first (they may span multiple lines
* and contain `//`), then line comments. String-literal awareness is
* intentionally skipped if anyone writes "put_page" inside an active
* string they want the test to fail.
*/
function stripComments(src: string): string {
// Block comments — non-greedy across newlines.
const noBlock = src.replace(/\/\*[\s\S]*?\*\//g, "");
// Line comments — strip from `//` to end of line.
return noBlock.replace(/\/\/[^\n]*/g, "");
}
describe("gstack-memory-ingest — no put_page in active code (regression for #1346)", () => {
it("source file does not call the renamed gbrain put_page subcommand", () => {
const src = readFileSync(SOURCE_PATH, "utf-8");
const stripped = stripComments(src);
expect(stripped).not.toContain("put_page");
});
it("source file does call the canonical gbrain put subcommand or gbrain import", () => {
// Sanity check that the file actually uses one of the supported page-write
// verbs — guards against accidentally removing all gbrain calls and having
// the negative test above pass for the wrong reason.
const src = readFileSync(SOURCE_PATH, "utf-8");
const stripped = stripComments(src);
const callsPut = /\bgbrain\s+put\b/.test(stripped) || /["']put["']/.test(stripped);
const callsImport = /\bimport\b/.test(stripped); // `gbrain import` runner
expect(callsPut || callsImport).toBe(true);
});
});