From 2968d3283c84ece6ef79e271e984d0e211aa176b Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Sun, 29 Mar 2026 13:40:21 -0700 Subject: [PATCH] fix: prevent gstack-relink from double-prefixing gstack-upgrade gstack-relink now checks if a skill directory is already named gstack-* before prepending the prefix. Previously, setting skill_prefix=true would create gstack-gstack-upgrade, breaking the /gstack-upgrade command. Matches setup script behavior (setup:260) which already has this guard. Co-Authored-By: Claude Opus 4.6 (1M context) --- bin/gstack-relink | 17 +++++++++++++---- test/relink.test.ts | 15 +++++++++++++++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/bin/gstack-relink b/bin/gstack-relink index bfd7bd29..49d0ccac 100755 --- a/bin/gstack-relink +++ b/bin/gstack-relink @@ -46,13 +46,22 @@ for skill_dir in "$INSTALL_DIR"/*/; do [ -f "$skill_dir/SKILL.md" ] || continue if [ "$PREFIX" = "true" ]; then - # Create gstack-* symlink, remove flat if exists - ln -sfn "$INSTALL_DIR/$skill" "$SKILLS_DIR/gstack-$skill" - [ -L "$SKILLS_DIR/$skill" ] && rm -f "$SKILLS_DIR/$skill" + # Don't double-prefix directories already named gstack-* + case "$skill" in + gstack-*) link_name="$skill" ;; + *) link_name="gstack-$skill" ;; + esac + ln -sfn "$INSTALL_DIR/$skill" "$SKILLS_DIR/$link_name" + # Remove old flat symlink if it exists (and isn't the same as the new link) + [ "$link_name" != "$skill" ] && [ -L "$SKILLS_DIR/$skill" ] && rm -f "$SKILLS_DIR/$skill" else # Create flat symlink, remove gstack-* if exists ln -sfn "$INSTALL_DIR/$skill" "$SKILLS_DIR/$skill" - [ -L "$SKILLS_DIR/gstack-$skill" ] && rm -f "$SKILLS_DIR/gstack-$skill" + # Don't remove gstack-* dirs that are their real name (e.g., gstack-upgrade) + case "$skill" in + gstack-*) ;; # Already the real name, no old prefixed link to clean + *) [ -L "$SKILLS_DIR/gstack-$skill" ] && rm -f "$SKILLS_DIR/gstack-$skill" ;; + esac fi SKILL_COUNT=$((SKILL_COUNT + 1)) done diff --git a/test/relink.test.ts b/test/relink.test.ts index 7a951a90..39af8891 100644 --- a/test/relink.test.ts +++ b/test/relink.test.ts @@ -122,6 +122,21 @@ describe('gstack-relink (#578)', () => { expect(output).toContain('setup'); }); + // Test: gstack-upgrade does NOT get double-prefixed + test('does not double-prefix gstack-upgrade directory', () => { + setupMockInstall(['qa', 'ship', 'gstack-upgrade']); + run(`${path.join(installDir, 'bin', 'gstack-config')} set skill_prefix true`); + run(`${path.join(installDir, 'bin', 'gstack-relink')}`, { + GSTACK_INSTALL_DIR: installDir, + GSTACK_SKILLS_DIR: skillsDir, + }); + // gstack-upgrade should keep its name, NOT become gstack-gstack-upgrade + expect(fs.existsSync(path.join(skillsDir, 'gstack-upgrade'))).toBe(true); + expect(fs.existsSync(path.join(skillsDir, 'gstack-gstack-upgrade'))).toBe(false); + // Regular skills still get prefixed + expect(fs.existsSync(path.join(skillsDir, 'gstack-qa'))).toBe(true); + }); + // Test 15: gstack-config set skill_prefix triggers relink test('gstack-config set skill_prefix triggers relink', () => { setupMockInstall(['qa', 'ship']);