Files
gstack/test/helpers/sync-gbrain-readiness-fixture.ts
T
garrytan 39fc663894 fix(sync-gbrain): define Step 4 helper args and one atomic write path
Both census read-ready attempts spent turns reading the helper source to
resolve <user-args>, inspecting fixture internals kept inside the repo,
and reconciling 'Read + Edit' with the tmp+mv atomic write, then hit
max turns before the verdict.
2026-09-29 16:19:39 +00:00

64 lines
3.8 KiB
TypeScript

import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import { spawnSync } from 'node:child_process';
const root = path.resolve(import.meta.dir, '../..');
export function createReadinessFixture(kind: 'ready' | 'unknown') {
const base = fs.mkdtempSync(path.join(os.tmpdir(), 'gbrain-ready-'));
const workDir = path.join(base, 'repo');
const home = path.join(base, 'home');
const bin = path.join(base, 'bin');
fs.mkdirSync(workDir); fs.mkdirSync(home); fs.mkdirSync(bin);
const init = spawnSync('git', ['init', '--quiet'], { cwd: workDir, timeout: 10_000 });
if (init.status !== 0) throw new Error('readiness fixture git init failed');
fs.writeFileSync(path.join(workDir, '.gbrain-source'), 'client-fixture\n');
const stateDir = path.join(home, '.gstack');
fs.mkdirSync(stateDir);
fs.writeFileSync(path.join(stateDir, '.gbrain-sync-state.json'), JSON.stringify({
schema_version: 1, last_writer: 'gstack-gbrain-sync', last_stages: [{
name: 'code', ran: true, ok: true,
detail: { status: 'ok', source_id: 'client-fixture', source_path: workDir },
}],
}, null, 2));
const guidance = '<!-- gstack-gbrain-search-guidance:start -->\nExisting search guidance\n<!-- gstack-gbrain-search-guidance:end -->';
fs.writeFileSync(path.join(workDir, 'CLAUDE.md'), kind === 'unknown' ? `# Fixture\n${guidance}\n` : '# Fixture\n');
const skill = fs.readFileSync(path.join(root, 'sync-gbrain/SKILL.md'), 'utf8');
const start = skill.indexOf('## Step 4: Refresh');
const end = skill.indexOf('## Concurrency note', start);
if (start < 0 || end < 0) throw new Error('sync-gbrain Step 4/5 fixture anchors missing');
fs.writeFileSync(path.join(workDir, 'readiness.md'), skill.slice(start, end)
.replaceAll('~/.claude/skills/gstack/bin/gstack-gbrain-read-capability.ts', path.join(root, 'bin/gstack-gbrain-read-capability.ts')));
const log = path.join(home, 'gbrain-calls');
fs.writeFileSync(path.join(bin, 'gbrain'), `#!/usr/bin/env bun
import { appendFileSync } from 'node:fs';
const args = process.argv.slice(2).join(' ');
appendFileSync(${JSON.stringify(log)}, args + '\\n');
if (args === 'sources list --json') console.log(${JSON.stringify(JSON.stringify({ sources: [{ id: 'client-fixture', local_path: workDir }] }))});
else if (args === 'list --source client-fixture --limit 1') console.log('code/fixture/readme\\tcode\\t2026-09-24\\tReadme');
else if (args === 'get code/fixture/readme --source client-fixture --json') ${kind === 'ready'
? `console.log(${JSON.stringify(JSON.stringify({ source_id: 'client-fixture', slug: 'code/fixture/readme', content: '# Readme' }))});`
: "{ console.error('temporary read failure'); process.exit(2); }"}
else { console.error('unsupported operation'); process.exit(3); }
`);
fs.chmodSync(path.join(bin, 'gbrain'), 0o755);
if (process.platform === 'win32') {
fs.writeFileSync(path.join(bin, 'gbrain.cmd'), `@echo off\r\n"${process.execPath}" "%~dp0gbrain" %*\r\n`);
}
const pathKey = Object.keys(process.env).find(key => key.toLowerCase() === 'path') ?? 'PATH';
const pin = fs.readFileSync(path.join(workDir, '.gbrain-source'), 'utf8');
const state = fs.readFileSync(path.join(stateDir, '.gbrain-sync-state.json'), 'utf8');
return {
workDir,
env: { HOME: home, GSTACK_HOME: stateDir, [pathKey]: `${bin}${path.delimiter}${process.env[pathKey] ?? ''}` },
guidance,
calls: () => fs.existsSync(log) ? fs.readFileSync(log, 'utf8').trim().split('\n') : [],
content: () => fs.readFileSync(path.join(workDir, 'CLAUDE.md'), 'utf8'),
sourceIntact: () => fs.readFileSync(path.join(workDir, '.gbrain-source'), 'utf8') === pin
&& fs.readFileSync(path.join(stateDir, '.gbrain-sync-state.json'), 'utf8') === state
&& !fs.existsSync(path.join(workDir, 'code')),
cleanup: () => fs.rmSync(base, { recursive: true, force: true }),
};
}