Skip to content

Commit 4da9c4a

Browse files
fix(setup): mirror support files + asset dirs alongside SKILL.md in link_claude_skill_dirs
Closes #1499. `link_claude_skill_dirs` previously only symlinked SKILL.md into each skill directory. Skills like /review reference sibling files (checklist.md, greptile-triage.md, specialists/, TODOS-format.md) via the `.claude/skills/review/` path — but those files were never linked, so the skill hit its own STOP point on every global install. After this fix, link_claude_skill_dirs also: - Symlinks all .md support files (except SKILL.md and *.tmpl) alongside SKILL.md - Symlinks support asset directories (specialists/, bin/, references/, templates/, migrations/, etc.) while excluding build dirs (dist/, src/, test/, tests/, scripts/, node_modules/) Idempotent — re-running ./setup upgrades existing installs cleanly. Supersedes the partial fix in #1486 (bin/ only); this covers all asset types. Affected skills: review (checklist.md, greptile-triage.md, design-checklist.md, TODOS-format.md, specialists/), qa (references/, templates/), careful (bin/), freeze (bin/), plan-devex-review (dx-hall-of-fame.md), cso (ACKNOWLEDGEMENTS.md), setup-gbrain (memory.md), gstack-upgrade (migrations/). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent cf50443 commit 4da9c4a

5 files changed

Lines changed: 67 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
# Changelog
22

3+
## [1.45.2.0] - 2026-05-27
4+
5+
## **`/setup` now mirrors support files and asset directories alongside SKILL.md so skills can read their own assets without path hacks.**
6+
7+
Skills like `/review` and `/qa` ship with sidecars — `checklist.md`, `greptile-triage.md`, `specialists/`, `templates/`, `bin/` — that their SKILL.md reads at runtime via `.claude/skills/<skill>/<file>`. Before this fix, `link_claude_skill_dirs` in `setup` only symlinked `SKILL.md`, leaving every sidecar unreachable. Skills worked around this with long absolute paths; new skills couldn't assume they had access to their own assets.
8+
9+
Now `setup` iterates over sibling `.md` files and qualifying subdirectories in each skill source dir and calls `_link_or_copy` for each. The Windows path compatibility guard (`_link_or_copy` instead of raw `ln -snf`) is preserved. The fix also adds two new invariant tests to `test/gen-skill-docs.test.ts` that will catch any future regression.
10+
11+
### Itemized changes
12+
13+
#### Fixed
14+
- `setup`: `link_claude_skill_dirs` now mirrors supporting `.md` files (except `SKILL.md` itself) and subdirectories (except `dist`, `src`, `test`, `tests`, `scripts`, `node_modules`) via `_link_or_copy` — fixes #1499
15+
16+
#### Added
17+
- `test/gen-skill-docs.test.ts`: two new invariant tests asserting `link_claude_skill_dirs` mirrors support files and directories using `_link_or_copy`
18+
319
## [1.45.0.0] - 2026-05-25
420

521
## **Design boards now live 24 hours, not 10 minutes. One daemon hosts every board, one tab survives the whole day.**

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.45.0.0
1+
1.45.2.0

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "gstack",
3-
"version": "1.45.0.0",
3+
"version": "1.45.2.0",
44
"description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.",
55
"license": "MIT",
66
"type": "module",

setup

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,28 @@ link_claude_skill_dirs() {
474474
# Validate target isn't a symlink before creating the link
475475
if [ -L "$target/SKILL.md" ]; then rm "$target/SKILL.md"; fi
476476
_link_or_copy "$gstack_dir/$dir_name/SKILL.md" "$target/SKILL.md"
477+
# Mirror supporting .md files (checklist.md, greptile-triage.md, etc.)
478+
# so SKILL.md can read them via .claude/skills/<skill>/<file>.
479+
# Excludes SKILL.md (already linked above) and *.tmpl sources.
480+
for support_file in "$gstack_dir/$dir_name"/*.md; do
481+
[ -f "$support_file" ] || continue
482+
fname="$(basename "$support_file")"
483+
[ "$fname" = "SKILL.md" ] && continue
484+
if [ -L "$target/$fname" ]; then rm "$target/$fname"; fi
485+
_link_or_copy "$support_file" "$target/$fname"
486+
done
487+
# Mirror support asset directories (specialists/, references/, templates/, bin/, etc.)
488+
# Excludes build and source dirs: dist, src, test, tests, scripts, node_modules.
489+
for support_dir in "$gstack_dir/$dir_name"/*/; do
490+
[ -d "$support_dir" ] || continue
491+
sname="$(basename "$support_dir")"
492+
case "$sname" in
493+
dist|src|test|tests|scripts|node_modules) continue ;;
494+
esac
495+
if [ -L "$target/$sname" ]; then rm "$target/$sname"; fi
496+
if [ -e "$target/$sname" ] && [ ! -L "$target/$sname" ]; then rm -rf "$target/$sname"; fi
497+
_link_or_copy "$gstack_dir/$dir_name/$sname" "$target/$sname"
498+
done
477499
linked+=("$link_name")
478500
fi
479501
done

test/gen-skill-docs.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2287,6 +2287,33 @@ describe('setup script validation', () => {
22872287
expect(claudeSection).toContain('link_claude_root_skill_alias "$SOURCE_GSTACK_DIR" "$INSTALL_SKILLS_DIR"');
22882288
});
22892289

2290+
// FIX #1499: link function must also mirror supporting .md files and asset directories
2291+
// so SKILL.md Step 2 can read checklist.md, greptile-triage.md, specialists/, etc.
2292+
// via .claude/skills/<skill>/<file> without requiring gstack-prefixed paths.
2293+
test('link_claude_skill_dirs mirrors supporting .md files alongside SKILL.md', () => {
2294+
const fnStart = setupContent.indexOf('link_claude_skill_dirs()');
2295+
const fnEnd = setupContent.indexOf('}', setupContent.indexOf('linked[@]}', fnStart));
2296+
const fnBody = setupContent.slice(fnStart, fnEnd);
2297+
// Must iterate over sibling .md files in the skill source dir
2298+
expect(fnBody).toContain('for support_file in "$gstack_dir/$dir_name"/*.md');
2299+
// Must skip SKILL.md itself (already linked above)
2300+
expect(fnBody).toContain('[ "$fname" = "SKILL.md" ] && continue');
2301+
// Must use _link_or_copy (not raw ln -snf) for Windows compatibility
2302+
expect(fnBody).toContain('_link_or_copy "$support_file" "$target/$fname"');
2303+
});
2304+
2305+
test('link_claude_skill_dirs mirrors support asset directories (specialists/, bin/, etc.)', () => {
2306+
const fnStart = setupContent.indexOf('link_claude_skill_dirs()');
2307+
const fnEnd = setupContent.indexOf('}', setupContent.indexOf('linked[@]}', fnStart));
2308+
const fnBody = setupContent.slice(fnStart, fnEnd);
2309+
// Must iterate over subdirectories in the skill source dir
2310+
expect(fnBody).toContain('for support_dir in "$gstack_dir/$dir_name"/*/');
2311+
// Must exclude build and source dirs to avoid polluting the skill surface
2312+
expect(fnBody).toContain('dist|src|test|tests|scripts|node_modules');
2313+
// Must use _link_or_copy (not raw ln -snf) for Windows compatibility
2314+
expect(fnBody).toContain('_link_or_copy "$gstack_dir/$dir_name/$sname" "$target/$sname"');
2315+
});
2316+
22902317
test('setup supports --host auto|claude|codex|kiro|opencode', () => {
22912318
expect(setupContent).toContain('--host');
22922319
expect(setupContent).toContain('claude|codex|kiro|factory|opencode|auto');

0 commit comments

Comments
 (0)