diff --git a/CHANGELOG.md b/CHANGELOG.md index 7dbc82f998..4f19dd9456 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## [1.48.1.0] - 2026-05-28 + +**`/setup` now mirrors support files and asset directories alongside SKILL.md so skills can read their own assets without path hacks.** + +Skills like `/review` and `/qa` ship with sidecars — `checklist.md`, `specialists/`, `bin/` — that their SKILL.md reads at runtime via `.claude/skills//`. Before this fix, `link_claude_skill_dirs` only symlinked `SKILL.md`, leaving every sidecar unreachable. Now `setup` iterates over sibling `.md` files and qualifying subdirectories and calls `_link_or_copy` for each, preserving Windows compatibility. + +### Itemized changes + +#### Fixed +- `setup`: `link_claude_skill_dirs` now mirrors supporting `.md` files and subdirectories via `_link_or_copy` — fixes #1499 + +#### Added +- `test/gen-skill-docs.test.ts`: two new invariant tests asserting support file and directory mirroring + ## [1.48.0.0] - 2026-05-26 ## **Agents stop dropping AskUserQuestion options when there are 5+.** A new canonical preamble rule + runtime gate makes Conductor's 4-option cap a split-or-batch decision, not a silent trim. diff --git a/VERSION b/VERSION index 01934fdf4c..1a61024e40 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.48.0.0 +1.48.1.0 diff --git a/package.json b/package.json index eb77faa516..66bb14f963 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gstack", - "version": "1.48.0.0", + "version": "1.48.1.0", "description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.", "license": "MIT", "type": "module", diff --git a/setup b/setup index 163865731d..97383c6533 100755 --- a/setup +++ b/setup @@ -474,6 +474,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 _link_or_copy "$gstack_dir/$dir_name/SKILL.md" "$target/SKILL.md" + # Mirror supporting .md files (checklist.md, greptile-triage.md, etc.) + # so SKILL.md can read them via .claude/skills//. + # Excludes SKILL.md (already linked above) and *.tmpl 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 + _link_or_copy "$support_file" "$target/$fname" + done + # Mirror 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 + _link_or_copy "$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 0a0c9741ba..9f6e7b2764 100644 --- a/test/gen-skill-docs.test.ts +++ b/test/gen-skill-docs.test.ts @@ -2287,6 +2287,33 @@ describe('setup script validation', () => { expect(claudeSection).toContain('link_claude_root_skill_alias "$SOURCE_GSTACK_DIR" "$INSTALL_SKILLS_DIR"'); }); + // 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 mirrors 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 use _link_or_copy (not raw ln -snf) for Windows compatibility + expect(fnBody).toContain('_link_or_copy "$support_file" "$target/$fname"'); + }); + + test('link_claude_skill_dirs mirrors 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 use _link_or_copy (not raw ln -snf) for Windows compatibility + expect(fnBody).toContain('_link_or_copy "$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');