diff --git a/setup b/setup index f812511e4..1dc085fc7 100755 --- a/setup +++ b/setup @@ -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// + # 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 diff --git a/test/gen-skill-docs.test.ts b/test/gen-skill-docs.test.ts index 4bf8abeee..789e38a05 100644 --- a/test/gen-skill-docs.test.ts +++ b/test/gen-skill-docs.test.ts @@ -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// 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');