fix(setup): render the gbrain :user variant to an out-dir — global installs stay git-clean

On a global-git install with gbrain, ./setup and 'gstack-config
gbrain-refresh' ran gen:skill-docs:user IN PLACE inside the install
checkout, rewriting ~16 TRACKED SKILL.md files. The checkout stayed
permanently dirty, every /gstack-upgrade 'git stash' saved a redundant
snapshot of generated content, and the growing stash list invited a 'git
stash pop' that would lay stale instruction markdown from an older gstack
over the current version — a quiet wrong-rules failure mode.

Fix, wired through machinery that already existed (gen-skill-docs
--out-dir + the symlink install layer): brain-aware SKILL.md now renders
into the untracked ~/.gstack/render/claude, and both Claude installers
serve the render when present — setup's link_claude_skill_dirs prefers
$GSTACK_HOME/render/claude/<skill>/SKILL.md, and bin/gstack-relink does
the same so a later config change can't silently flip skills back to the
blockless canonical source. setup wipes and rebuilds the render each run,
repoints installed skills after a successful render, and removes a stale
render (re-linking canonical) when gbrain is gone. gbrain-refresh renders
to the out-dir and repoints via relink; its 'this dirties the install's
git tree' caveat is retired because it no longer does.

A one-time upgrade migration (gstack-upgrade/migrations/v1.67.0.0.sh, F12)
restores the legacy dirt: unstaged modifications to SKILL.md / sections/
*.md files in the install checkout are git-checkout'd back to canonical;
anything outside that footprint (user edits, untracked files, staged work)
is left alone and reported. Idempotent, non-fatal, symlinked installs
skipped.

Tests: render-preference behavior for both installers, static pins that
every executable :user invocation carries --out-dir and the caveat text is
gone, migration fixture (restore/leave/idempotent/no-op matrix), and the
existing out-dir render test now asserts 'git status --porcelain' gains
zero new entries across a full :user render.

Fixes #2569

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-08-16 09:49:31 -07:00
co-authored by Claude Fable 5
parent 1dbed2c01f
commit 9af589bb73
8 changed files with 399 additions and 33 deletions
+8 -2
View File
@@ -2350,10 +2350,13 @@ describe('setup script validation', () => {
test('Codex install uses link_codex_skill_dirs', () => {
// The Codex install section (section 5) should use the Codex function
// End marker: the next numbered section header (a marker that doesn't
// exist slices to EOF and the assertion reads unrelated sections).
const codexSection = setupContent.slice(
setupContent.indexOf('# 5. Install for Codex'),
setupContent.indexOf('# 6. Create')
setupContent.indexOf('# 6. Install for Kiro')
);
expect(setupContent.indexOf('# 6. Install for Kiro')).toBeGreaterThan(-1);
expect(codexSection).toContain('create_codex_runtime_root');
expect(codexSection).toContain('link_codex_skill_dirs');
expect(codexSection).not.toContain('link_claude_skill_dirs');
@@ -2398,7 +2401,10 @@ describe('setup script validation', () => {
const fnBody = setupContent.slice(fnStart, fnEnd);
expect(fnBody).toContain('mkdir -p "$target"');
// v1.36.0.0: routes through _link_or_copy helper for Windows fallback (cp on MSYS2/Git Bash).
expect(fnBody).toContain('_link_or_copy "$gstack_dir/$dir_name/SKILL.md" "$target/SKILL.md"');
// v1.67 (#2569): the source is render-aware — canonical SKILL.md, or the
// rendered :user variant from ${GSTACK_HOME}/render/claude when present.
expect(fnBody).toContain('_skill_md_src="$gstack_dir/$dir_name/SKILL.md"');
expect(fnBody).toContain('_link_or_copy "$_skill_md_src" "$target/SKILL.md"');
});
// REGRESSION: cleanup functions must handle both old symlinks AND new real-directory pattern