mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-09 14:38:59 +02:00
fix(render): section refs point at the FINAL render dir, never the tmp swap dir (#2692)
gen-skill-docs bakes its --out-dir into rendered CONTENT (rewriteSectionBase), and both swap-in callers (setup, gstack-config gbrain-refresh) render into claude.tmp.<pid> before the #2569 atomic rename — so every rendered skill carried ~9 dead section Read paths that pointed at a directory the swap had just deleted. New --link-root flag names the final serving dir (defaults to --out-dir for direct-render callers: bin/dev-setup, dev-skill.ts, mkdtemp tests — full caller audit in the wave notes); the rewrite now uses a replacement callback so a $-bearing configured path can't expand as $& in a replacement string. The swap logic itself stays byte-identical. Tests pin the generator contract (tmp out-dir files reference the final dir, $-bearing path included) and both callers' wiring. Fixes #2692 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
e2d6cb570f
commit
7ed5e87bba
@@ -77,6 +77,48 @@ describe('gen-skill-docs --out-dir (B2 render isolation)', () => {
|
||||
}
|
||||
});
|
||||
|
||||
// #2692: the swap-in callers (setup, gstack-config gbrain-refresh) render
|
||||
// into claude.tmp.<pid> then RENAME it into place — so section refs must be
|
||||
// rewritten to the FINAL serving dir (--link-root), never the tmp out-dir,
|
||||
// or every rendered Read dies the moment the swap completes.
|
||||
test('--link-root repoints section refs at the FINAL dir, not the tmp out-dir (#2692)', () => {
|
||||
const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-home-'));
|
||||
const base = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-swap-'));
|
||||
// Mirror the real caller shape, including a `$`-bearing path segment so a
|
||||
// replacement-string regression ($& expansion) fails loudly.
|
||||
const finalDir = path.join(base, 'render$live', 'claude');
|
||||
const outDir = `${finalDir}.tmp.12345`;
|
||||
fs.mkdirSync(path.dirname(finalDir), { recursive: true });
|
||||
try {
|
||||
fs.writeFileSync(
|
||||
path.join(tmpHome, 'gbrain-detection.json'),
|
||||
JSON.stringify({ gbrain_local_status: 'ok', gbrain_version: '9.9.9' }),
|
||||
);
|
||||
const res = spawnSync(
|
||||
'bun',
|
||||
['run', 'scripts/gen-skill-docs.ts', '--respect-detection', '--host', 'claude',
|
||||
'--out-dir', outDir, '--link-root', finalDir],
|
||||
{ cwd: ROOT, encoding: 'utf-8', timeout: 120_000, env: { ...process.env, GSTACK_HOME: tmpHome } },
|
||||
);
|
||||
expect(res.status).toBe(0);
|
||||
const skillContent = fs.readFileSync(path.join(outDir, 'ship', 'SKILL.md'), 'utf-8');
|
||||
// Files land in the tmp out-dir; their CONTENT references the final dir.
|
||||
expect(skillContent).toContain(`${finalDir}/ship/sections/`);
|
||||
expect(skillContent).not.toContain(`${outDir}/ship/sections/`);
|
||||
expect(skillContent).not.toContain('~/.claude/skills/gstack/ship/sections/');
|
||||
} finally {
|
||||
fs.rmSync(tmpHome, { recursive: true, force: true });
|
||||
fs.rmSync(base, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('both swap-in callers pass --link-root with the final render dir (#2692 wiring)', () => {
|
||||
const setupSrc = fs.readFileSync(path.join(ROOT, 'setup'), 'utf-8');
|
||||
const configSrc = fs.readFileSync(path.join(ROOT, 'bin', 'gstack-config'), 'utf-8');
|
||||
expect(setupSrc).toContain('--out-dir "$_GSTACK_RENDER_TMP" --link-root "$_GSTACK_RENDER_DIR"');
|
||||
expect(configSrc).toContain('--out-dir "$RENDER_TMP" --link-root "$RENDER_DIR"');
|
||||
});
|
||||
|
||||
test('retired global extras (proactive-suggestions.json) are not written anywhere', () => {
|
||||
const outDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-out-'));
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user