mirror of
https://github.com/garrytan/gstack.git
synced 2026-07-09 17:38:40 +02:00
fix(setup): register root gstack slash alias
This commit is contained in:
@@ -46,6 +46,17 @@ _cleanup_skill_entry() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_link_root_skill_alias() {
|
||||||
|
local target="$SKILLS_DIR/_gstack-command"
|
||||||
|
|
||||||
|
[ -f "$INSTALL_DIR/SKILL.md" ] || return 0
|
||||||
|
[ -L "$target" ] && rm -f "$target"
|
||||||
|
mkdir -p "$target"
|
||||||
|
ln -snf "$INSTALL_DIR/SKILL.md" "$target/SKILL.md"
|
||||||
|
}
|
||||||
|
|
||||||
|
_link_root_skill_alias
|
||||||
|
|
||||||
# Discover skills (directories with SKILL.md, excluding meta dirs)
|
# Discover skills (directories with SKILL.md, excluding meta dirs)
|
||||||
SKILL_COUNT=0
|
SKILL_COUNT=0
|
||||||
for skill_dir in "$INSTALL_DIR"/*/; do
|
for skill_dir in "$INSTALL_DIR"/*/; do
|
||||||
|
|||||||
@@ -483,6 +483,26 @@ link_claude_skill_dirs() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Claude Code skips the repo-shaped ~/.claude/skills/gstack directory when
|
||||||
|
# building the user-facing slash-command list. Keep the repo path for runtime
|
||||||
|
# assets, and add a separate thin wrapper whose frontmatter name remains
|
||||||
|
# `gstack` so `/gstack` can autocomplete.
|
||||||
|
link_claude_root_skill_alias() {
|
||||||
|
local gstack_dir="$1"
|
||||||
|
local skills_dir="$2"
|
||||||
|
local target="$skills_dir/_gstack-command"
|
||||||
|
|
||||||
|
[ -f "$gstack_dir/SKILL.md" ] || return 0
|
||||||
|
if [ -L "$target" ]; then
|
||||||
|
rm -f "$target"
|
||||||
|
fi
|
||||||
|
mkdir -p "$target"
|
||||||
|
if [ -L "$target/SKILL.md" ]; then rm "$target/SKILL.md"; fi
|
||||||
|
_link_or_copy "$gstack_dir/SKILL.md" "$target/SKILL.md"
|
||||||
|
echo " linked root skill alias: gstack"
|
||||||
|
_print_windows_copy_note_once
|
||||||
|
}
|
||||||
|
|
||||||
# ─── Helper: remove old unprefixed Claude skill entries ───────────────────────
|
# ─── Helper: remove old unprefixed Claude skill entries ───────────────────────
|
||||||
# Migration: when switching from flat names to gstack- prefixed names,
|
# Migration: when switching from flat names to gstack- prefixed names,
|
||||||
# clean up stale symlinks or directories that point into the gstack directory.
|
# clean up stale symlinks or directories that point into the gstack directory.
|
||||||
@@ -869,6 +889,7 @@ if [ "$INSTALL_CLAUDE" -eq 1 ]; then
|
|||||||
# reads the correct (patched) name: values for symlink naming
|
# reads the correct (patched) name: values for symlink naming
|
||||||
"$SOURCE_GSTACK_DIR/bin/gstack-patch-names" "$SOURCE_GSTACK_DIR" "$SKILL_PREFIX"
|
"$SOURCE_GSTACK_DIR/bin/gstack-patch-names" "$SOURCE_GSTACK_DIR" "$SKILL_PREFIX"
|
||||||
link_claude_skill_dirs "$SOURCE_GSTACK_DIR" "$INSTALL_SKILLS_DIR"
|
link_claude_skill_dirs "$SOURCE_GSTACK_DIR" "$INSTALL_SKILLS_DIR"
|
||||||
|
link_claude_root_skill_alias "$SOURCE_GSTACK_DIR" "$INSTALL_SKILLS_DIR"
|
||||||
# Self-healing: re-run gstack-relink to ensure name: fields and directory
|
# Self-healing: re-run gstack-relink to ensure name: fields and directory
|
||||||
# names are consistent with the config. This catches cases where an interrupted
|
# names are consistent with the config. This catches cases where an interrupted
|
||||||
# setup, stale git state, or gen:skill-docs left name: fields out of sync.
|
# setup, stale git state, or gen:skill-docs left name: fields out of sync.
|
||||||
@@ -940,6 +961,7 @@ if [ "$INSTALL_CLAUDE" -eq 1 ]; then
|
|||||||
fi
|
fi
|
||||||
"$SOURCE_GSTACK_DIR/bin/gstack-patch-names" "$SOURCE_GSTACK_DIR" "$SKILL_PREFIX"
|
"$SOURCE_GSTACK_DIR/bin/gstack-patch-names" "$SOURCE_GSTACK_DIR" "$SKILL_PREFIX"
|
||||||
link_claude_skill_dirs "$SOURCE_GSTACK_DIR" "$INSTALL_SKILLS_DIR"
|
link_claude_skill_dirs "$SOURCE_GSTACK_DIR" "$INSTALL_SKILLS_DIR"
|
||||||
|
link_claude_root_skill_alias "$SOURCE_GSTACK_DIR" "$INSTALL_SKILLS_DIR"
|
||||||
GSTACK_RELINK="$SOURCE_GSTACK_DIR/bin/gstack-relink"
|
GSTACK_RELINK="$SOURCE_GSTACK_DIR/bin/gstack-relink"
|
||||||
if [ -x "$GSTACK_RELINK" ]; then
|
if [ -x "$GSTACK_RELINK" ]; then
|
||||||
GSTACK_SKILLS_DIR="$INSTALL_SKILLS_DIR" GSTACK_INSTALL_DIR="$SOURCE_GSTACK_DIR" "$GSTACK_RELINK" >/dev/null 2>&1 || true
|
GSTACK_SKILLS_DIR="$INSTALL_SKILLS_DIR" GSTACK_INSTALL_DIR="$SOURCE_GSTACK_DIR" "$GSTACK_RELINK" >/dev/null 2>&1 || true
|
||||||
|
|||||||
@@ -2273,6 +2273,20 @@ describe('setup script validation', () => {
|
|||||||
expect(fnBody).toContain('rm -f "$target"');
|
expect(fnBody).toContain('rm -f "$target"');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('setup links root gstack skill through a thin Claude wrapper alias', () => {
|
||||||
|
const fnStart = setupContent.indexOf('link_claude_root_skill_alias()');
|
||||||
|
const fnEnd = setupContent.indexOf('# ─── Helper: remove old unprefixed Claude skill entries', fnStart);
|
||||||
|
const fnBody = setupContent.slice(fnStart, fnEnd);
|
||||||
|
expect(fnBody).toContain('_gstack-command');
|
||||||
|
expect(fnBody).toContain('_link_or_copy "$gstack_dir/SKILL.md" "$target/SKILL.md"');
|
||||||
|
|
||||||
|
const claudeSection = setupContent.slice(
|
||||||
|
setupContent.indexOf('# 4. Install for Claude'),
|
||||||
|
setupContent.indexOf('# 5. Install for Codex')
|
||||||
|
);
|
||||||
|
expect(claudeSection).toContain('link_claude_root_skill_alias "$SOURCE_GSTACK_DIR" "$INSTALL_SKILLS_DIR"');
|
||||||
|
});
|
||||||
|
|
||||||
test('setup supports --host auto|claude|codex|kiro|opencode', () => {
|
test('setup supports --host auto|claude|codex|kiro|opencode', () => {
|
||||||
expect(setupContent).toContain('--host');
|
expect(setupContent).toContain('--host');
|
||||||
expect(setupContent).toContain('claude|codex|kiro|factory|opencode|auto');
|
expect(setupContent).toContain('claude|codex|kiro|factory|opencode|auto');
|
||||||
|
|||||||
@@ -187,6 +187,37 @@ describe('gstack-relink (#578)', () => {
|
|||||||
expect(fs.lstatSync(path.join(skillsDir, 'qa', 'SKILL.md')).isSymbolicLink()).toBe(true);
|
expect(fs.lstatSync(path.join(skillsDir, 'qa', 'SKILL.md')).isSymbolicLink()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('creates a thin root alias wrapper for the /gstack slash command', () => {
|
||||||
|
setupMockInstall(['qa']);
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(installDir, 'SKILL.md'),
|
||||||
|
'---\nname: gstack\ndescription: root\n---\n# gstack',
|
||||||
|
);
|
||||||
|
|
||||||
|
run(`${path.join(installDir, 'bin', 'gstack-config')} set skill_prefix false`, {
|
||||||
|
GSTACK_INSTALL_DIR: installDir,
|
||||||
|
GSTACK_SKILLS_DIR: skillsDir,
|
||||||
|
});
|
||||||
|
run(`${path.join(installDir, 'bin', 'gstack-relink')}`, {
|
||||||
|
GSTACK_INSTALL_DIR: installDir,
|
||||||
|
GSTACK_SKILLS_DIR: skillsDir,
|
||||||
|
});
|
||||||
|
|
||||||
|
const aliasDir = path.join(skillsDir, '_gstack-command');
|
||||||
|
const aliasSkill = path.join(aliasDir, 'SKILL.md');
|
||||||
|
expect(fs.lstatSync(aliasDir).isDirectory()).toBe(true);
|
||||||
|
expect(fs.lstatSync(aliasDir).isSymbolicLink()).toBe(false);
|
||||||
|
expect(fs.lstatSync(aliasSkill).isSymbolicLink()).toBe(true);
|
||||||
|
expect(fs.readlinkSync(aliasSkill)).toBe(path.join(installDir, 'SKILL.md'));
|
||||||
|
expect(fs.readFileSync(aliasSkill, 'utf-8')).toContain('name: gstack');
|
||||||
|
|
||||||
|
run(`${path.join(installDir, 'bin', 'gstack-config')} set skill_prefix true`, {
|
||||||
|
GSTACK_INSTALL_DIR: installDir,
|
||||||
|
GSTACK_SKILLS_DIR: skillsDir,
|
||||||
|
});
|
||||||
|
expect(fs.existsSync(aliasSkill)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
// FIRST INSTALL: --no-prefix must create ONLY flat names, zero gstack-* pollution
|
// FIRST INSTALL: --no-prefix must create ONLY flat names, zero gstack-* pollution
|
||||||
test('first install --no-prefix: only flat names exist, zero gstack-* entries', () => {
|
test('first install --no-prefix: only flat names exist, zero gstack-* entries', () => {
|
||||||
setupMockInstall(['qa', 'ship', 'review', 'plan-ceo-review', 'gstack-upgrade']);
|
setupMockInstall(['qa', 'ship', 'review', 'plan-ceo-review', 'gstack-upgrade']);
|
||||||
|
|||||||
Reference in New Issue
Block a user