Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions setup
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,28 @@ link_claude_skill_dirs() {
# Validate target isn't a symlink before creating the link
if [ -L "$target/SKILL.md" ]; then rm "$target/SKILL.md"; fi
ln -snf "$gstack_dir/$dir_name/SKILL.md" "$target/SKILL.md"
# Symlink supporting .md files so SKILL.md can read them via .claude/skills/<skill>/
# path (checklist.md, greptile-triage.md, design-checklist.md, TODOS-format.md, etc.)
# Excludes SKILL.md (already linked above) and *.tmpl template sources.
for support_file in "$gstack_dir/$dir_name"/*.md; do
[ -f "$support_file" ] || continue
fname="$(basename "$support_file")"
[ "$fname" = "SKILL.md" ] && continue
if [ -L "$target/$fname" ]; then rm "$target/$fname"; fi
ln -snf "$support_file" "$target/$fname"
done
# Symlink support asset directories (specialists/, references/, templates/, bin/, etc.)
# Excludes build and source dirs: dist, src, test, tests, scripts, node_modules.
for support_dir in "$gstack_dir/$dir_name"/*/; do
[ -d "$support_dir" ] || continue
sname="$(basename "$support_dir")"
case "$sname" in
dist|src|test|tests|scripts|node_modules) continue ;;
esac
if [ -L "$target/$sname" ]; then rm "$target/$sname"; fi
if [ -e "$target/$sname" ] && [ ! -L "$target/$sname" ]; then rm -rf "$target/$sname"; fi
ln -snf "$gstack_dir/$dir_name/$sname" "$target/$sname"
done
linked+=("$link_name")
fi
done
Expand Down
27 changes: 27 additions & 0 deletions test/gen-skill-docs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2270,6 +2270,33 @@ describe('setup script validation', () => {
expect(fnBody).toContain('rm -f "$target"');
});

// FIX #1499: link function must also mirror supporting .md files and asset directories
// so SKILL.md Step 2 can read checklist.md, greptile-triage.md, specialists/, etc.
// via .claude/skills/<skill>/<file> without requiring gstack-prefixed paths.
test('link_claude_skill_dirs symlinks supporting .md files alongside SKILL.md', () => {
const fnStart = setupContent.indexOf('link_claude_skill_dirs()');
const fnEnd = setupContent.indexOf('}', setupContent.indexOf('linked[@]}', fnStart));
const fnBody = setupContent.slice(fnStart, fnEnd);
// Must iterate over sibling .md files in the skill source dir
expect(fnBody).toContain('for support_file in "$gstack_dir/$dir_name"/*.md');
// Must skip SKILL.md itself (already linked above)
expect(fnBody).toContain('[ "$fname" = "SKILL.md" ] && continue');
// Must create symlinks for each support file
expect(fnBody).toContain('ln -snf "$support_file" "$target/$fname"');
});

test('link_claude_skill_dirs symlinks support asset directories (specialists/, bin/, etc.)', () => {
const fnStart = setupContent.indexOf('link_claude_skill_dirs()');
const fnEnd = setupContent.indexOf('}', setupContent.indexOf('linked[@]}', fnStart));
const fnBody = setupContent.slice(fnStart, fnEnd);
// Must iterate over subdirectories in the skill source dir
expect(fnBody).toContain('for support_dir in "$gstack_dir/$dir_name"/*/');
// Must exclude build and source dirs to avoid polluting the skill surface
expect(fnBody).toContain('dist|src|test|tests|scripts|node_modules');
// Must create symlinks for qualifying support dirs
expect(fnBody).toContain('ln -snf "$gstack_dir/$dir_name/$sname" "$target/$sname"');
});

test('setup supports --host auto|claude|codex|kiro|opencode', () => {
expect(setupContent).toContain('--host');
expect(setupContent).toContain('claude|codex|kiro|factory|opencode|auto');
Expand Down