From d8894536861f1c739e073cbf729aa019e65129e4 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Sun, 16 Aug 2026 09:44:10 -0700 Subject: [PATCH] fix(setup-gbrain): invoke gstack-memory-ingest/gstack-gbrain-sync via bun run + .ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /setup-gbrain's transcript-ingest steps told the agent to run bin/gstack-memory-ingest and bin/gstack-gbrain-sync by BARE name. Neither exists — only the .ts files ship (mode 644, no bin alias) — so the agent dutifully reported 'script missing at install root' and the ingest/full- sync steps dead-ended on every host (hit live under Codex; the Claude render carries the same text). All four template sites (probe, silent-bulk, post-answer full sync, the preamble-hook incremental mention) and the four memory.md reference-doc sites now use the repo's established form: 'bun run /gstack-memory- ingest.ts …' / 'bun run /gstack-gbrain-sync.ts …' — matching what sync-gbrain already does. Generated SKILL.md regenerated from the template in the same commit. Re-derived from PR #2409 by @SomSamantray per the wave's screening rule (the PR edited the generated SKILL.md directly; the generated file must come from gen:skill-docs). The contributor's structural test rides along as-is: bare-invocation regexes with negative .ts lookahead and backslash- continuation coverage pin every site, so the drift can't return. The referenced-paths ratchet in test/setup-claude-skill-assets.test.ts drops its two #2250 known-broken entries — the class-2 assertion now guards these paths again. Verified against #2250's site list (template lines 690/735/784-area, all covered) plus a fresh grep: zero bare invocations remain in the template or memory.md; the one prose mention ('gstack-memory-ingest now persists…') is not an invocation and stays. Fixes #2250 Fixes #2393 Co-authored-by: SomSamantray Co-Authored-By: Claude Fable 5 --- setup-gbrain/SKILL.md | 8 +- setup-gbrain/SKILL.md.tmpl | 8 +- setup-gbrain/memory.md | 8 +- test/setup-claude-skill-assets.test.ts | 6 +- .../setup-gbrain-bin-invocation-paths.test.ts | 133 ++++++++++++++++++ 5 files changed, 146 insertions(+), 17 deletions(-) create mode 100644 test/setup-gbrain-bin-invocation-paths.test.ts diff --git a/setup-gbrain/SKILL.md b/setup-gbrain/SKILL.md index 73786e36f..54b3a987a 100644 --- a/setup-gbrain/SKILL.md +++ b/setup-gbrain/SKILL.md @@ -1503,7 +1503,7 @@ curated `~/.gstack/` artifacts into gbrain so the retrieval surface Run the probe to size the operation: ```bash -~/.claude/skills/gstack/bin/gstack-memory-ingest --probe +bun run ~/.claude/skills/gstack/bin/gstack-memory-ingest.ts --probe ``` Read the output. If `Total files in window: 0`, skip — there's nothing @@ -1511,7 +1511,7 @@ to ingest. Set `gstack-config set transcript_ingest_mode incremental` silently and continue to Step 8. If `New (never ingested)` is < 200 AND total bytes are < 100MB: silent -bulk via `gstack-memory-ingest --bulk --quiet`. Set +bulk via `bun run ~/.claude/skills/gstack/bin/gstack-memory-ingest.ts --bulk --quiet`. Set `transcript_ingest_mode=incremental` and continue. Otherwise (the "many transcripts on disk" path): AskUserQuestion with @@ -1548,14 +1548,14 @@ Options: After answer: ```bash ~/.claude/skills/gstack/bin/gstack-config set transcript_ingest_mode -~/.claude/skills/gstack/bin/gstack-gbrain-sync --full --no-brain-sync +bun run ~/.claude/skills/gstack/bin/gstack-gbrain-sync.ts --full --no-brain-sync ``` (`--no-brain-sync` because Step 7 already wired that path; this just runs the code import + memory ingest stages. Brain-sync will run on the next preamble hook.) If A/D/E, ingest is incremental from this point on; preamble-boundary -hook runs `gstack-gbrain-sync --incremental --quiet` on every skill +hook runs `bun run ~/.claude/skills/gstack/bin/gstack-gbrain-sync.ts --incremental --quiet` on every skill start (cheap mtime fast-path). Reference doc for users: `setup-gbrain/memory.md` (linked from CLAUDE.md diff --git a/setup-gbrain/SKILL.md.tmpl b/setup-gbrain/SKILL.md.tmpl index 2eff76403..3636a4f9b 100644 --- a/setup-gbrain/SKILL.md.tmpl +++ b/setup-gbrain/SKILL.md.tmpl @@ -736,7 +736,7 @@ curated `~/.gstack/` artifacts into gbrain so the retrieval surface Run the probe to size the operation: ```bash -~/.claude/skills/gstack/bin/gstack-memory-ingest --probe +bun run ~/.claude/skills/gstack/bin/gstack-memory-ingest.ts --probe ``` Read the output. If `Total files in window: 0`, skip — there's nothing @@ -744,7 +744,7 @@ to ingest. Set `gstack-config set transcript_ingest_mode incremental` silently and continue to Step 8. If `New (never ingested)` is < 200 AND total bytes are < 100MB: silent -bulk via `gstack-memory-ingest --bulk --quiet`. Set +bulk via `bun run ~/.claude/skills/gstack/bin/gstack-memory-ingest.ts --bulk --quiet`. Set `transcript_ingest_mode=incremental` and continue. Otherwise (the "many transcripts on disk" path): AskUserQuestion with @@ -781,14 +781,14 @@ Options: After answer: ```bash ~/.claude/skills/gstack/bin/gstack-config set transcript_ingest_mode -~/.claude/skills/gstack/bin/gstack-gbrain-sync --full --no-brain-sync +bun run ~/.claude/skills/gstack/bin/gstack-gbrain-sync.ts --full --no-brain-sync ``` (`--no-brain-sync` because Step 7 already wired that path; this just runs the code import + memory ingest stages. Brain-sync will run on the next preamble hook.) If A/D/E, ingest is incremental from this point on; preamble-boundary -hook runs `gstack-gbrain-sync --incremental --quiet` on every skill +hook runs `bun run ~/.claude/skills/gstack/bin/gstack-gbrain-sync.ts --incremental --quiet` on every skill start (cheap mtime fast-path). Reference doc for users: `setup-gbrain/memory.md` (linked from CLAUDE.md diff --git a/setup-gbrain/memory.md b/setup-gbrain/memory.md index 7732af4ce..c57744e37 100644 --- a/setup-gbrain/memory.md +++ b/setup-gbrain/memory.md @@ -47,9 +47,9 @@ v1.33.0.0 — off by default. To re-enable it (adds ~4-8 min to cold runs on a large transcript corpus), use either: ```bash -gstack-memory-ingest --bulk --scan-secrets +bun run bin/gstack-memory-ingest.ts --bulk --scan-secrets # or -GSTACK_MEMORY_INGEST_SCAN_SECRETS=1 gstack-memory-ingest --bulk +GSTACK_MEMORY_INGEST_SCAN_SECRETS=1 bun run bin/gstack-memory-ingest.ts --bulk ``` When enabled, gitleaks covers: @@ -155,7 +155,7 @@ verdict block. If a row is RED, the row tells you what to do. Common cases: - **Salience block is empty** — your transcripts may not be ingested - yet. Run `gstack-gbrain-sync --full` to do a full pass. + yet. Run `bun run bin/gstack-gbrain-sync.ts --full` to do a full pass. - **"gbrain CLI missing" in the preamble output** — gbrain isn't on your PATH. Run `/setup-gbrain` to install/wire it. @@ -166,7 +166,7 @@ Common cases: --pglite && gbrain import `. - **A page has stale or wrong content** — `gbrain delete_page `, - then re-run `gstack-gbrain-sync --incremental` to re-ingest from + then re-run `bun run bin/gstack-gbrain-sync.ts --incremental` to re-ingest from source if the source file is still on disk and unchanged. ## Privacy + audit diff --git a/test/setup-claude-skill-assets.test.ts b/test/setup-claude-skill-assets.test.ts index 6e811bdca..8cabfadab 100644 --- a/test/setup-claude-skill-assets.test.ts +++ b/test/setup-claude-skill-assets.test.ts @@ -43,11 +43,7 @@ const BUILT_ARTIFACT_ALLOWLIST = [ * do not add entries without an issue + a scheduled fix. */ const KNOWN_BROKEN_CLASS2: Record = { - // #2250: setup-gbrain's docs call both scripts by bare name; only the .ts - // files exist. Fixed by the wave's c24 (PR #2409 re-derive) — remove these - // entries in that commit. - 'bin/gstack-memory-ingest': '#2250 — fixed by setup-gbrain .ts invocation-path commit', - 'bin/gstack-gbrain-sync': '#2250 — fixed by setup-gbrain .ts invocation-path commit', + // (empty — #2250's bare bin names were the last entries; keep it that way) }; /** Extract a named shell function body (through its closing brace) from setup. */ diff --git a/test/setup-gbrain-bin-invocation-paths.test.ts b/test/setup-gbrain-bin-invocation-paths.test.ts new file mode 100644 index 000000000..259f2542c --- /dev/null +++ b/test/setup-gbrain-bin-invocation-paths.test.ts @@ -0,0 +1,133 @@ +// setup-gbrain bin invocation path lint. +// +// Pins the correct bun-run + .ts invocation form for gstack-memory-ingest +// and gstack-gbrain-sync wherever setup-gbrain's docs instruct the agent +// to run them. Regression coverage for #2393 / #2250: both scripts are +// .ts files with no package.json bin alias stripping the extension, so a +// bare name (no `bun run` prefix, no `.ts` suffix) fails with "No such +// file or directory" the moment an agent follows the doc literally. +// +// Why a structural test instead of a full Agent SDK E2E: +// - The failure is entirely in the prose an agent reads, not in +// runtime behavior a service test could exercise. A grep-based +// regression on the template/reference-doc text is fast (<200ms), +// free, and catches the same drift a full E2E would, without the +// token cost. Same rationale as test/setup-gbrain-path4-structure.test.ts. +// - The correct invocation form and the stale one differ only by +// `bun run ` + `.ts`, right next to each other in the same files — +// exactly the kind of drift a cheap structural check exists to catch, +// matching this repo's convention (e.g. test/memory-ingest-no-put_page.test.ts +// pinning fix #1346). + +import { describe, test, expect } from 'bun:test'; +import * as fs from 'fs'; +import * as path from 'path'; + +const ROOT = path.resolve(import.meta.dir, '..'); +const TMPL = path.join(ROOT, 'setup-gbrain', 'SKILL.md.tmpl'); +const MEMORY_DOC = path.join(ROOT, 'setup-gbrain', 'memory.md'); + +const tmpl = fs.readFileSync(TMPL, 'utf-8'); +const memoryDoc = fs.readFileSync(MEMORY_DOC, 'utf-8'); + +// A "bare invocation" is the tool name immediately followed by a flag/arg +// with no `.ts` in between — the exact stale shape #2393/#2250 reported. +// The negative lookahead means `gstack-memory-ingest.ts --probe` (correct) +// does NOT match, while `gstack-memory-ingest --probe` (stale) does. +// `(?:\s|\\\r?\n)+` also spans a backslash line-continuation between the +// name and its flag (e.g. `gstack-memory-ingest \` newline ` --probe`), +// a style this same template already uses for other commands (see the +// read_secret_to_env invocation a few hundred lines up) — a plain `\s+` +// would miss a stale invocation reintroduced in that form. +const bareMemoryIngest = /\bgstack-memory-ingest\b(?!\.ts)(?:\s|\\\r?\n)+--/; +const bareGbrainSync = /\bgstack-gbrain-sync\b(?!\.ts)(?:\s|\\\r?\n)+--/; + +describe('setup-gbrain/SKILL.md.tmpl — bin invocation paths', () => { + test('no bare gstack-memory-ingest invocation remains', () => { + expect(tmpl).not.toMatch(bareMemoryIngest); + }); + + test('no bare gstack-gbrain-sync invocation remains', () => { + expect(tmpl).not.toMatch(bareGbrainSync); + }); + + test('the probe step uses bun run + .ts (R1)', () => { + expect(tmpl).toContain( + 'bun run ~/.claude/skills/gstack/bin/gstack-memory-ingest.ts --probe' + ); + }); + + test('the silent-bulk mention uses bun run + .ts (R2)', () => { + expect(tmpl).toContain( + 'bun run ~/.claude/skills/gstack/bin/gstack-memory-ingest.ts --bulk --quiet' + ); + }); + + test('the post-answer full-sync step uses bun run + .ts (R3)', () => { + expect(tmpl).toContain( + 'bun run ~/.claude/skills/gstack/bin/gstack-gbrain-sync.ts --full --no-brain-sync' + ); + }); + + test('the preamble-hook incremental-sync mention uses bun run + .ts (R4)', () => { + expect(tmpl).toContain( + 'bun run ~/.claude/skills/gstack/bin/gstack-gbrain-sync.ts --incremental --quiet' + ); + }); + + test('the neighboring gstack-config line in the post-answer block is untouched (bash script, no extension)', () => { + expect(tmpl).toContain( + '~/.claude/skills/gstack/bin/gstack-config set transcript_ingest_mode ' + ); + }); + + test('the prose-only mention naming the tool as a sentence subject is left unchanged (KTD4 — not a literal invocation)', () => { + expect(tmpl).toContain('gstack-memory-ingest now persists staged transcripts to'); + }); +}); + +describe('setup-gbrain/memory.md — bin invocation paths', () => { + test('no bare gstack-memory-ingest invocation remains', () => { + expect(memoryDoc).not.toMatch(bareMemoryIngest); + }); + + test('no bare gstack-gbrain-sync invocation remains', () => { + expect(memoryDoc).not.toMatch(bareGbrainSync); + }); + + test('the secret-scanning example uses bun run + .ts (R5)', () => { + expect(memoryDoc).toContain('bun run bin/gstack-memory-ingest.ts --bulk --scan-secrets'); + expect(memoryDoc).toContain( + 'GSTACK_MEMORY_INGEST_SCAN_SECRETS=1 bun run bin/gstack-memory-ingest.ts --bulk' + ); + }); + + test('the troubleshooting full-pass mention uses bun run + .ts (R5)', () => { + expect(memoryDoc).toContain('Run `bun run bin/gstack-gbrain-sync.ts --full` to do a full pass.'); + }); + + test('the troubleshooting incremental-reingest mention uses bun run + .ts (R5)', () => { + expect(memoryDoc).toContain( + 're-run `bun run bin/gstack-gbrain-sync.ts --incremental` to re-ingest from' + ); + }); + + test('the already-correct reference line at the top of the file is unchanged', () => { + expect(memoryDoc).toContain('bun run bin/gstack-memory-ingest.ts --probe` (which'); + }); +}); + +describe('bare-invocation regex — backslash line-continuation coverage', () => { + // This template writes multi-line commands with a trailing backslash + // continuation elsewhere (e.g. the read_secret_to_env invocation), so a + // stale invocation reintroduced in that same style must still be caught. + test('catches a bare invocation split across a backslash continuation', () => { + const staleContinuation = 'gstack-memory-ingest \\\n --probe'; + expect(staleContinuation).toMatch(bareMemoryIngest); + }); + + test('does not flag a correct invocation split across a backslash continuation', () => { + const fixedContinuation = 'bun run bin/gstack-gbrain-sync.ts \\\n --incremental'; + expect(fixedContinuation).not.toMatch(bareGbrainSync); + }); +});