feat(setup-gbrain): carve the branch-exclusive install paths into sections

Brain-init (Paths 1/2/3/4 bodies), engine remediation, transcript gate, and
CLAUDE.md persist load on demand — at most one install route ever runs.
Skeleton 75.3KB -> 57.0KB; the Step 1 detect and Step 2 path dispatch stay
always-loaded. New buildSetupGbrainFixture helper gives the periodic E2Es
extract-don't-copy fixtures with a non-empty guard; the voyage-code-3 gate
counts scan the tmpl union (the third init site lives in engine-remediation).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-08-25 17:03:12 +00:00
co-authored by Claude Fable 5
parent 6bb1996004
commit cdfe2d9074
18 changed files with 1336 additions and 1021 deletions
+14 -3
View File
@@ -83,7 +83,7 @@ exit 0
/**
* Verbatim reimplementation of the skill template's voyage-code-3
* conditional. The template (setup-gbrain/SKILL.md.tmpl Path 3, Step 1.5
* conditional. The template (setup-gbrain/sections/brain-init.md.tmpl Path 3, Step 1.5
* inside the rollback wrapper, Step 4.5 Path 4 Yes branch) instructs the
* model to execute this bash; we execute the same bash here and assert the
* argv passed to gbrain matches the contract.
@@ -202,9 +202,18 @@ gbrain init --pglite --json $GBRAIN_EMBED_FLAGS
});
it("template uses the positional-params shape, not an unquoted flags var", () => {
// Carved (token-reduction Phase 4): count across the tmpl UNION — one
// PGLite init site stays in the skeleton, the Path-3/4 sites live in the
// brain-init section.
const tmpl = readFileSync(
join(import.meta.dir, "..", "setup-gbrain", "SKILL.md.tmpl"),
"utf-8",
) + readFileSync(
join(import.meta.dir, "..", "setup-gbrain", "sections", "brain-init.md.tmpl"),
"utf-8",
) + readFileSync(
join(import.meta.dir, "..", "setup-gbrain", "sections", "engine-remediation.md.tmpl"),
"utf-8",
);
expect(tmpl).not.toContain("$GBRAIN_EMBED_FLAGS");
const sites = tmpl.match(/gbrain init --pglite --json "\$@"/g) || [];
@@ -229,8 +238,10 @@ describe("template alignment: the .tmpl actually contains the voyage gate", () =
// Belt-and-suspenders: if someone edits the template and drops the
// VOYAGE_API_KEY conditional without updating the test above, this catches
// it. The shell snippet under test must literally appear in the .tmpl.
const TEMPLATE_PATH = join(import.meta.dir, "..", "setup-gbrain", "SKILL.md.tmpl");
const tmpl = readFileSync(TEMPLATE_PATH, "utf-8");
// Carved union — see comment above.
const tmpl = readFileSync(join(import.meta.dir, "..", "setup-gbrain", "SKILL.md.tmpl"), "utf-8")
+ readFileSync(join(import.meta.dir, "..", "setup-gbrain", "sections", "brain-init.md.tmpl"), "utf-8")
+ readFileSync(join(import.meta.dir, "..", "setup-gbrain", "sections", "engine-remediation.md.tmpl"), "utf-8");
it("setup-gbrain template gates the embedding-model flag on VOYAGE_API_KEY", () => {
// Should appear at least once (currently 3 init sites use the same gate).
+107
View File
@@ -0,0 +1,107 @@
/**
* setup-gbrain E2E fixture builder — carve-aware (token-reduction Phase 4).
*
* setup-gbrain is carved: the generated SKILL.md is a decision-tree skeleton
* whose STOP-Read pointers reference install paths
* (`~/.claude/skills/gstack/setup-gbrain/sections/*.md`) that do not exist in
* a hermetic E2E sandbox. Pointing an agent at the raw skeleton would burn
* turns on failed Reads and never reach the per-path init procedures under
* test. This builder reconstructs a runnable single-file fixture, wave-1
* style (see the codex fixture in test/skill-e2e-workflow.test.ts):
*
* 1. slice the skeleton from the skill title (dropping the shared preamble —
* CLAUDE.md rule: "E2E test fixtures: extract, don't copy"),
* 2. cut the Section index table (its sections/ paths don't resolve here),
* 3. replace each STOP pointer with the section body the test needs, or an
* explicit "not needed" stub for the rest, and
* 4. run a non-empty guard: every needed section's distinctive anchor must
* be present in the result, so a renamed/emptied section fails loudly
* instead of shipping a silently hollow fixture.
*
* Monolith-tolerant: if the generated SKILL.md has no STOP pointers (pre-carve
* checkout, or a regen that un-carves), the bodies are still inline and the
* anchor guard passes — the builder works on both shapes.
*/
import * as fs from 'fs';
import * as path from 'path';
const ROOT = path.resolve(import.meta.dir, '..', '..');
const SKILL_MD = path.join(ROOT, 'setup-gbrain', 'SKILL.md');
const SECTIONS_DIR = path.join(ROOT, 'setup-gbrain', 'sections');
const TITLE = '# /setup-gbrain — Coding-Agent Onboarding for gbrain';
/** Matches one generated STOP-Read pointer (two lines) and captures the section file name. */
const STOP_POINTER =
/^> \*\*STOP\.\*\* Before [^\n]*sections\/([a-z0-9-]+\.md)[^\n]*\n> in full\.[^\n]*/gm;
/** Distinctive per-section anchors — the non-empty guard for inlined content. */
export const SECTION_ANCHORS: Record<string, string> = {
'brain-init.md': '### Path 4 (Remote gbrain MCP',
'claude-md-persist.md': 'Mode: remote-http',
'engine-remediation.md': "Your local gbrain engine isn't responding",
'transcript-gate.md': 'gstack-memory-ingest.ts --probe',
};
/**
* Build the fixture text: skeleton (preamble dropped, Section index cut) with
* `neededSections` inlined at their STOP pointers and every other pointer
* replaced by an explicit not-needed stub. Throws on any missing anchor.
*/
export function buildSetupGbrainFixture(neededSections: string[]): string {
for (const file of neededSections) {
if (!(file in SECTION_ANCHORS)) {
throw new Error(
`setup-gbrain fixture: unknown section "${file}" — known: ${Object.keys(SECTION_ANCHORS).join(', ')}`,
);
}
}
let full = fs.readFileSync(SKILL_MD, 'utf-8');
const titleIdx = full.indexOf(TITLE);
if (titleIdx < 0) throw new Error(`setup-gbrain fixture: title heading not found: "${TITLE}"`);
full = full.slice(titleIdx);
// Cut the Section index table (heading through its closing --- separator).
const idxStart = full.indexOf('## Section index');
if (idxStart >= 0) {
const idxEnd = full.indexOf('\n---\n', idxStart);
if (idxEnd < 0) throw new Error('setup-gbrain fixture: Section index has no closing ---');
full = full.slice(0, idxStart) + full.slice(idxEnd + '\n---\n'.length);
}
full = full.replace(STOP_POINTER, (_m, file: string) => {
if (!neededSections.includes(file)) {
return '_(Section not included in this fixture — not needed for this run. Continue with the next step.)_';
}
const secPath = path.join(SECTIONS_DIR, file);
if (!fs.existsSync(secPath)) {
throw new Error(
`setup-gbrain fixture: sections/${file} not generated — run bun run gen:skill-docs`,
);
}
const body = fs
.readFileSync(secPath, 'utf-8')
.replace(/^<!--[^\n]*-->\n/gm, '') // strip AUTO-GENERATED header comments
.trim();
if (body.length < 500) {
throw new Error(`setup-gbrain fixture: sections/${file} is unexpectedly small/empty`);
}
return body;
});
// Non-empty guard on the RESULT — holds for both the carved shape (section
// inlined above) and the monolith shape (body was never carved out).
for (const file of neededSections) {
if (!full.includes(SECTION_ANCHORS[file])) {
throw new Error(
`setup-gbrain fixture: needed section "${file}" content missing from fixture ` +
`(anchor not found: "${SECTION_ANCHORS[file]}")`,
);
}
}
return full;
}
+34 -15
View File
@@ -25,9 +25,28 @@ import * as path from 'path';
const ROOT = path.resolve(import.meta.dir, '..');
const TMPL = path.join(ROOT, 'setup-gbrain', 'SKILL.md.tmpl');
const SECTIONS_DIR = path.join(ROOT, 'setup-gbrain', 'sections');
const MEMORY_DOC = path.join(ROOT, 'setup-gbrain', 'memory.md');
// Carve-aware (token-reduction Phase 4): setup-gbrain is carved. The Step 7.5
// ingest-gate body (which owns the R1-R4 invocations) lives in
// sections/transcript-gate.md.tmpl; the skeleton keeps dispatch + the Step 10
// verdict prose. Negative (no-bare-invocation) checks run over the UNION so a
// stale form can't hide in any template file.
const tmpl = fs.readFileSync(TMPL, 'utf-8');
const transcriptGate = fs.readFileSync(
path.join(SECTIONS_DIR, 'transcript-gate.md.tmpl'),
'utf-8',
);
const tmplUnion = [tmpl]
.concat(
fs
.readdirSync(SECTIONS_DIR)
.filter((f) => f.endsWith('.md.tmpl'))
.sort()
.map((f) => fs.readFileSync(path.join(SECTIONS_DIR, f), 'utf-8')),
)
.join('\n');
const memoryDoc = fs.readFileSync(MEMORY_DOC, 'utf-8');
// A "bare invocation" is the tool name immediately followed by a flag/arg
@@ -42,46 +61,46 @@ const memoryDoc = fs.readFileSync(MEMORY_DOC, 'utf-8');
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);
describe('setup-gbrain templates (skeleton + sections) — bin invocation paths', () => {
test('no bare gstack-memory-ingest invocation remains anywhere in the union', () => {
expect(tmplUnion).not.toMatch(bareMemoryIngest);
});
test('no bare gstack-gbrain-sync invocation remains', () => {
expect(tmpl).not.toMatch(bareGbrainSync);
test('no bare gstack-gbrain-sync invocation remains anywhere in the union', () => {
expect(tmplUnion).not.toMatch(bareGbrainSync);
});
test('the probe step uses bun run + .ts (R1)', () => {
expect(tmpl).toContain(
test('the probe step uses bun run + .ts (R1, transcript-gate section)', () => {
expect(transcriptGate).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(
test('the silent-bulk mention uses bun run + .ts (R2, transcript-gate section)', () => {
expect(transcriptGate).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(
test('the post-answer full-sync step uses bun run + .ts (R3, transcript-gate section)', () => {
expect(transcriptGate).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(
test('the preamble-hook incremental-sync mention uses bun run + .ts (R4, transcript-gate section)', () => {
expect(transcriptGate).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(
expect(transcriptGate).toContain(
'~/.claude/skills/gstack/bin/gstack-config set transcript_ingest_mode <choice>'
);
});
test('the prose-only mention naming the tool as a sentence subject is left unchanged (KTD4 — not a literal invocation)', () => {
test('the prose-only mention naming the tool as a sentence subject is left unchanged (KTD4 — not a literal invocation; Step 10 verdict, skeleton)', () => {
expect(tmpl).toContain('gstack-memory-ingest now persists staged transcripts to');
});
});
+100 -44
View File
@@ -1,8 +1,17 @@
// setup-gbrain Path 4 structural lint.
//
// Verifies the SKILL.md.tmpl has the prose contract that Path 4 (Remote MCP)
// depends on: STOP gates after verify failures, never-write-token rules,
// mode-aware CLAUDE.md block, idempotent re-run path.
// Verifies the skill's templates carry the prose contract that Path 4
// (Remote MCP) depends on: STOP gates after verify failures, never-write-token
// rules, mode-aware CLAUDE.md block, idempotent re-run path.
//
// Carve-aware (token-reduction Phase 4): setup-gbrain is carved — the
// SKILL.md.tmpl is a decision-tree skeleton (detect, path dispatch, verify,
// MCP registration, verdict) and the branch-exclusive install bodies live in
// setup-gbrain/sections/*.md.tmpl. Each pin below targets the file that OWNS
// the content: dispatch/verdict pins hit the skeleton, per-path init pins hit
// sections/brain-init.md.tmpl, the CLAUDE.md block pins hit
// sections/claude-md-persist.md.tmpl, and the token-security regressions run
// over the union so a marker can't silently vanish during a re-carve.
//
// Why a structural test instead of a full Agent SDK E2E:
// - Side effects (claude.json mutation, MCP registration) are covered
@@ -21,110 +30,157 @@ 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 SKILL_DIR = path.join(ROOT, 'setup-gbrain');
const SECTIONS_DIR = path.join(SKILL_DIR, 'sections');
const tmpl = fs.readFileSync(TMPL, 'utf-8');
// Skeleton template — always loaded; owns detect, path dispatch, Steps 5/5a/6/7/9/10.
const skeleton = fs.readFileSync(path.join(SKILL_DIR, 'SKILL.md.tmpl'), 'utf-8');
// Per-path init procedures (Paths 1/2a/2b/3/4 + Switch) — Step 4 body.
const brainInit = fs.readFileSync(path.join(SECTIONS_DIR, 'brain-init.md.tmpl'), 'utf-8');
// Step 8 CLAUDE.md persist body (both mode blocks + the gated guidance write).
const claudeMdPersist = fs.readFileSync(
path.join(SECTIONS_DIR, 'claude-md-persist.md.tmpl'),
'utf-8',
);
// Skeleton + every section template — total behavior, order-stable.
const union = [skeleton]
.concat(
fs
.readdirSync(SECTIONS_DIR)
.filter((f) => f.endsWith('.md.tmpl'))
.sort()
.map((f) => fs.readFileSync(path.join(SECTIONS_DIR, f), 'utf-8')),
)
.join('\n');
describe('setup-gbrain Path 4 (Remote MCP) — structural contract', () => {
test('Step 2 lists Path 4 as one of the path options', () => {
// "4 — Remote gbrain MCP" with em-dash (—, U+2014 — one codepoint).
expect(tmpl).toMatch(/\*\*4 . Remote gbrain MCP/);
describe('setup-gbrain carve — dispatch stays in the skeleton', () => {
test('the path-dispatch step (Step 2 picker) stays always-loaded', () => {
expect(skeleton).toContain('## Step 2: Pick a path (AskUserQuestion)');
});
test('Step 4 has a Path 4 sub-section', () => {
expect(tmpl).toMatch(/### Path 4 \(Remote gbrain MCP/);
test('the skeleton routes to all four sections and renders the index', () => {
expect(skeleton).toContain('{{SECTION_INDEX:setup-gbrain}}');
for (const id of ['engine-remediation', 'brain-init', 'transcript-gate', 'claude-md-persist']) {
expect(skeleton).toContain(`{{SECTION:${id}}}`);
}
});
test('the carved bodies moved OUT of the skeleton (no leak-back)', () => {
// Step 4 per-path init:
expect(skeleton).not.toContain('### Path 1 (Supabase, existing URL)');
expect(skeleton).not.toContain('read_secret_to_env GBRAIN_MCP_TOKEN');
// Step 1.5 remediation AUQ:
expect(skeleton).not.toContain("Your local gbrain engine isn't responding");
// Step 7.5 ingest gate body:
expect(skeleton).not.toContain('gstack-memory-ingest.ts --probe');
// Step 8 block formats:
expect(skeleton).not.toContain('Mode: remote-http');
});
});
describe('setup-gbrain Path 4 (Remote MCP) — structural contract', () => {
test('Step 2 lists Path 4 as one of the path options (skeleton)', () => {
// "4 — Remote gbrain MCP" with em-dash (—, U+2014 — one codepoint).
expect(skeleton).toMatch(/\*\*4 . Remote gbrain MCP/);
});
test('Step 4 has a Path 4 sub-section (brain-init section)', () => {
expect(brainInit).toMatch(/### Path 4 \(Remote gbrain MCP/);
});
test('Step 4 collects the bearer via read_secret_to_env, never argv', () => {
// The secret-read helper is the canonical token-capture pattern.
// Without it, tokens land in shell history.
expect(tmpl).toContain('read_secret_to_env GBRAIN_MCP_TOKEN');
expect(brainInit).toContain('read_secret_to_env GBRAIN_MCP_TOKEN');
});
test('Step 4c invokes gstack-gbrain-mcp-verify and STOPs on failure', () => {
expect(tmpl).toContain('gstack-gbrain-mcp-verify');
expect(brainInit).toContain('gstack-gbrain-mcp-verify');
// The STOP rule is what prevents partial registration after auth fail.
const path4Section = tmpl.split('### Path 4')[1] || '';
const path4Section = brainInit.split('### Path 4')[1] || '';
expect(path4Section).toMatch(/STOP/);
});
test('Step 4d explicitly skips Steps 3, 4 (other paths), 5, 7.5 in remote mode', () => {
expect(tmpl).toMatch(/4d.*[Ss]kip Steps? 3, 4.*5.*7\.5/s);
expect(brainInit).toMatch(/4d.*[Ss]kip Steps? 3, 4.*5.*7\.5/s);
});
test('Step 5a has a Path 4 branch with claude mcp add --transport http', () => {
expect(tmpl).toMatch(/Path 4 \(Remote MCP/);
expect(tmpl).toMatch(/claude mcp add --scope user --transport http gbrain/);
expect(tmpl).toContain('Authorization: Bearer $GBRAIN_MCP_TOKEN');
test('Step 5a has a Path 4 branch with claude mcp add --transport http (skeleton)', () => {
expect(skeleton).toMatch(/Path 4 \(Remote MCP/);
expect(skeleton).toMatch(/claude mcp add --scope user --transport http gbrain/);
expect(skeleton).toContain('Authorization: Bearer $GBRAIN_MCP_TOKEN');
// Token must be unset after registration so it doesn't linger in env.
expect(tmpl).toMatch(/unset GBRAIN_MCP_TOKEN/);
expect(skeleton).toMatch(/unset GBRAIN_MCP_TOKEN/);
});
test('Step 5a removes any prior gbrain registration before adding the new one', () => {
// Otherwise local-stdio + remote-http coexist, which breaks routing.
expect(tmpl).toMatch(/claude mcp remove gbrain/);
expect(skeleton).toMatch(/claude mcp remove gbrain/);
});
test('Step 7 calls gstack-artifacts-init with --url-form-supported flag', () => {
expect(tmpl).toMatch(/gstack-artifacts-init.*--url-form-supported/);
test('Step 7 calls gstack-artifacts-init with --url-form-supported flag (skeleton)', () => {
expect(skeleton).toMatch(/gstack-artifacts-init.*--url-form-supported/);
});
test('Step 8 CLAUDE.md block branches on mode', () => {
test('Step 8 CLAUDE.md block branches on mode (claude-md-persist section)', () => {
// The remote-http block has Mode: remote-http; local-stdio block has Engine:.
expect(tmpl).toMatch(/### Path 4 \(Remote MCP\)/);
expect(tmpl).toMatch(/Mode: remote-http/);
expect(tmpl).toMatch(/Mode: local-stdio/);
expect(claudeMdPersist).toMatch(/### Path 4 \(Remote MCP\)/);
expect(claudeMdPersist).toMatch(/Mode: remote-http/);
expect(claudeMdPersist).toMatch(/Mode: local-stdio/);
});
test('Step 8 explicitly says the bearer is never written to CLAUDE.md', () => {
// Token-leak regression guard. CLAUDE.md is committed in many projects.
expect(tmpl).toMatch(/bearer token is \*\*never\*\* written to CLAUDE\.md/);
expect(claudeMdPersist).toMatch(/bearer token is \*\*never\*\* written to CLAUDE\.md/);
});
test('Step 9 smoke test on Path 4 prints a placeholder, never the real token', () => {
// Don't paste the token into the curl example the user might share.
expect(tmpl).toMatch(/<YOUR_TOKEN>/);
expect(skeleton).toMatch(/<YOUR_TOKEN>/);
});
test('Step 10 verdict block has a remote-http variant separate from local-stdio', () => {
expect(tmpl).toMatch(/### Path 4 \(Remote MCP\)/);
expect(tmpl).toMatch(/mode: remote-http/);
expect(tmpl).toMatch(/N\/A.*remote mode/);
expect(skeleton).toMatch(/### Path 4 \(Remote MCP\)/);
expect(skeleton).toMatch(/mode: remote-http/);
expect(skeleton).toMatch(/N\/A.*remote mode/);
});
test('idempotency: re-running with gbrain_mcp_mode=remote-http skips Step 2', () => {
// Re-run path stays graceful; no double-registration.
expect(tmpl).toMatch(/gbrain_mcp_mode=remote-http/);
expect(skeleton).toMatch(/gbrain_mcp_mode=remote-http/);
});
test('Step 5 (local doctor) explicitly skips on Path 4', () => {
expect(tmpl).toMatch(/SKIP entirely on Path 4 \(Remote MCP\)/);
test('Step 5 (local doctor) explicitly skips on Path 4 (skeleton)', () => {
expect(skeleton).toMatch(/SKIP entirely on Path 4 \(Remote MCP\)/);
});
test('Step 7.5 (transcript ingest) explicitly skips on Path 4', () => {
test('Step 7.5 (transcript ingest) explicitly skips on Path 4 (skeleton)', () => {
// Transcript ingest needs local gbrain CLI which Path 4 doesn't install.
const matches = tmpl.match(/SKIP entirely on Path 4 \(Remote MCP\)/g);
// The skip notes are DISPATCH — they must stay in the always-loaded
// skeleton (Steps 3, 5, and 7.5 each carry one).
const matches = skeleton.match(/SKIP entirely on Path 4 \(Remote MCP\)/g);
expect(matches?.length).toBeGreaterThanOrEqual(2);
});
});
describe('setup-gbrain Path 4 — token security regressions', () => {
test('the template never inlines a real-shaped bearer string', () => {
test('no template (skeleton or section) inlines a real-shaped bearer string', () => {
// We never want a literal "gbrain_<hex>" token to appear in the
// template — placeholders only. This catches the failure mode where
// someone copies a real token into the template by accident.
// templates — placeholders only. This catches the failure mode where
// someone copies a real token into a template by accident.
const realTokenShape = /gbrain_[a-f0-9]{40,}/;
expect(tmpl).not.toMatch(realTokenShape);
expect(union).not.toMatch(realTokenShape);
});
test('Path 4 always uses env-var $GBRAIN_MCP_TOKEN, never inline strings', () => {
// Find every reference to the bearer header in Path 4 and verify it's
// either an env-var expansion or an explicit placeholder. Allow:
// Find every reference to the bearer header in Path 4 (across the
// skeleton AND sections) and verify it's either an env-var expansion
// or an explicit placeholder. Allow:
// - $GBRAIN_MCP_TOKEN (env-var expansion)
// - <bearer>, <YOUR_TOKEN>, <TOKEN> (placeholder)
// - "..." (rest-of-doc-text continuation; a doc note showing how
// `claude mcp add --header` shapes its argv).
const path4Section = tmpl.match(/### Path 4 \(Remote MCP[\s\S]*?(?=###|## )/g)?.join('') || '';
const path4Section = union.match(/### Path 4 \(Remote MCP[\s\S]*?(?=###|## )/g)?.join('') || '';
const bearerLines = path4Section.match(/Bearer\s+\S+/g) || [];
for (const line of bearerLines) {
expect(line).toMatch(/Bearer (\$GBRAIN_MCP_TOKEN|<bearer>|<YOUR_TOKEN>|<TOKEN>|\.\.\."?)/);
+10 -1
View File
@@ -7,6 +7,11 @@
// regression guard for the "verify failed → STOP" gate.
//
// Cost: ~$0.30-$0.50 per run. Gate-tier (EVALS=1 EVALS_TIER=gate).
//
// Carve-aware: the Step 4 Path 4 body (collect URL/token, verify, STOP rule)
// lives in setup-gbrain/sections/brain-init.md, so the fixture inlines that
// section into the skeleton via buildSetupGbrainFixture. Step 8 is not needed:
// on a failed verify the skill STOPs before any CLAUDE.md write.
import { test, expect } from 'bun:test';
import { describeE2ETier } from './helpers/e2e-gate';
@@ -15,6 +20,7 @@ import * as os from 'os';
import * as path from 'path';
import * as http from 'http';
import { runAgentSdkTest, passThroughNonAskUserQuestion, resolveClaudeBinary } from './helpers/agent-sdk-runner';
import { buildSetupGbrainFixture } from './helpers/setup-gbrain-fixture';
// Periodic-tier (companion to skill-e2e-setup-gbrain-remote.test.ts).
// Deterministic gate coverage lives in setup-gbrain-path4-structure.test.ts.
@@ -86,7 +92,10 @@ describeE2E('/setup-gbrain Path 4 — bad token STOPs cleanly', () => {
let modelTextOutput = '';
try {
const skillPath = path.resolve(import.meta.dir, '..', 'setup-gbrain', 'SKILL.md');
// Carve-aware fixture: skeleton + brain-init inlined (non-empty guard
// inside the builder). The test drives Steps 4a-4c to the STOP.
const skillPath = path.join(gstackHome, 'setup-gbrain-SKILL.md');
fs.writeFileSync(skillPath, buildSetupGbrainFixture(['brain-init.md']));
const result = await runAgentSdkTest({
systemPrompt: { type: 'preset', preset: 'claude_code' },
userPrompt:
@@ -29,6 +29,7 @@ import {
passThroughNonAskUserQuestion,
resolveClaudeBinary,
} from './helpers/agent-sdk-runner';
import { buildSetupGbrainFixture } from './helpers/setup-gbrain-fixture';
const describeE2E = describeE2ETier('periodic');
@@ -166,11 +167,14 @@ describeE2E('/setup-gbrain Path 4 + Step 4.5 Yes → local PGLite for code', ()
process.env.GBRAIN_MCP_TOKEN = 'gbrain_fake_token_for_test';
try {
const skillPath = path.resolve(
import.meta.dir,
'..',
'setup-gbrain',
'SKILL.md',
// Carve-aware fixture (see test/helpers/setup-gbrain-fixture.ts):
// skeleton + brain-init (Step 4 Path 4 body incl. the Step 4d local
// PGLite offer this test says Yes to) + claude-md-persist (Step 8 sits
// on the walked path to Step 10). Non-empty guard inside the builder.
const skillPath = path.join(sandboxHome, 'setup-gbrain-SKILL.md');
fs.writeFileSync(
skillPath,
buildSetupGbrainFixture(['brain-init.md', 'claude-md-persist.md']),
);
const result = await runAgentSdkTest({
systemPrompt: { type: 'preset', preset: 'claude_code' },
+10 -1
View File
@@ -10,6 +10,10 @@
// Cost: ~$0.30-$0.50 per run. Gate-tier (EVALS=1 EVALS_TIER=gate).
//
// See setup-gbrain/SKILL.md.tmpl Step 4 (Path 4) for the contract under test.
// The Step 4 body lives in setup-gbrain/sections/brain-init.md (carved), so
// the fixture is built via buildSetupGbrainFixture: skeleton + the brain-init
// and claude-md-persist sections inlined (Step 8 writes the Mode: remote-http
// block this test asserts on).
import { test, expect } from 'bun:test';
import { describeE2ETier } from './helpers/e2e-gate';
@@ -18,6 +22,7 @@ import * as os from 'os';
import * as path from 'path';
import * as http from 'http';
import { runAgentSdkTest, passThroughNonAskUserQuestion, resolveClaudeBinary } from './helpers/agent-sdk-runner';
import { buildSetupGbrainFixture } from './helpers/setup-gbrain-fixture';
// Periodic-tier: the model's interpretation of "follow Path 4 only" is
// non-deterministic (it sometimes skips Step 8 CLAUDE.md write, sometimes
@@ -144,7 +149,11 @@ describeE2E('/setup-gbrain Path 4 (Remote MCP) — happy path', () => {
let modelTextOutput = '';
try {
const skillPath = path.resolve(import.meta.dir, '..', 'setup-gbrain', 'SKILL.md');
// Carve-aware fixture: skeleton + brain-init (Step 4 Path 4 body) +
// claude-md-persist (Step 8 block formats), STOP pointers resolved
// inline so no Read escapes the sandbox. Non-empty guard inside.
const skillPath = path.join(gstackHome, 'setup-gbrain-SKILL.md');
fs.writeFileSync(skillPath, buildSetupGbrainFixture(['brain-init.md', 'claude-md-persist.md']));
const result = await runAgentSdkTest({
systemPrompt: { type: 'preset', preset: 'claude_code' },
env: childEnv,