fix(setup): install every skill runtime asset for the Claude host

On a fresh Claude install, link_claude_skill_dirs installed only SKILL.md
(+ sections/) per skill. Every skill that reads a sibling runtime file at
.claude/skills/<name>/<file> was broken out of the box: /review stopped at
'Read .claude/skills/review/checklist.md' (file never installed), and qa's
templates/references, plan-devex-review's dx-hall-of-fame.md,
gstack-upgrade's migrations/, and careful/freeze's bin/ hooks were all
silently missing. Codex/Factory/OpenCode/Kiro installers already copied
these; the primary host never did.

Fix: a shared _link_skill_runtime_assets helper installs EVERYTHING a skill
ships next to its SKILL.md, with an explicit exclusion list (F7):
node_modules, dist, test, *.tmpl, hidden files. Exclusion-list polarity
means a newly added asset installs by default instead of being silently
dropped. Assets refresh unconditionally on re-run (rm + relink/copy), so
Windows real-dir copies pick up changes after git pull.

New free test runs the real installer functions against the live repo into
a temp skills dir with a TWO-CLASS referenced-paths assertion (ENG-OV7):
alias-relative refs (.claude/skills/<name>/<path>) must exist under the
install; repo-anchored refs (~/.claude/skills/gstack/<path>) must exist in
the tree modulo an explicit built-artifact allowlist (browse/design/
make-pdf dist + the compiled gstack-global-discover). Known-broken class-2
refs (#2250 bare bin names) are ratcheted: the test fails if they quietly
start existing without the entry being removed.

Fixes #2317
Fixes #2454

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-08-16 09:49:30 -07:00
co-authored by Claude Fable 5
parent 1a1dab2c5a
commit 0f38feee78
2 changed files with 272 additions and 8 deletions
+43 -8
View File
@@ -694,6 +694,41 @@ fi
# 3. Ensure ~/.gstack global state directory exists
mkdir -p "$HOME/.gstack/projects"
# ─── Helper: link a skill's runtime assets into its installed dir ────────────
# Installs EVERY runtime asset a skill ships next to its SKILL.md (#2317,
# #2454): review/checklist.md + specialists/, qa/templates + references,
# gstack-upgrade/migrations, careful/bin, freeze/bin, sections/, etc.
# Exclusion list rather than inclusion list (F7) so a new asset file is
# installed by default instead of silently dropped:
# - SKILL.md linked separately by the caller (name-aware)
# - node_modules dependency trees, never a runtime read
# - dist compiled binaries; skills reference them repo-anchored
# (~/.claude/skills/gstack/browse/dist/...), never
# alias-relative, and fresh clones haven't built them
# - test test fixtures
# - *.tmpl generator sources; the generated file is the asset
# - hidden files excluded by the glob (no dotglob)
# Shared so any flattened-skill installer can reuse it (the Claude path is
# the first consumer; codex/factory/opencode install from generated trees).
_link_skill_runtime_assets() {
local src_dir="$1"
local dst_dir="$2"
local asset asset_name
for asset in "$src_dir"/*; do
[ -e "$asset" ] || continue # empty-glob guard
asset_name="$(basename "$asset")"
case "$asset_name" in
SKILL.md|node_modules|dist|test|*.tmpl) continue ;;
esac
# Refresh unconditionally: rm the old entry (symlink OR real copy — the
# Windows install pattern) so re-runs after `git pull` pick up changes.
if [ -e "$dst_dir/$asset_name" ] || [ -L "$dst_dir/$asset_name" ]; then
rm -rf "$dst_dir/$asset_name"
fi
_link_or_copy "$asset" "$dst_dir/$asset_name"
done
}
# ─── Helper: link Claude skill subdirectories into a skills parent directory ──
# Creates real directories (not symlinks) at the top level with a SKILL.md symlink
# inside. This ensures Claude discovers them as top-level skills, not nested under
@@ -732,14 +767,14 @@ 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"
# Link the sections/ subdir for carved skills (v2 plan T9). The prefixed
# Claude skill dir otherwise holds only SKILL.md, so a runtime
# "Read sections/<name>.md" 404s. Route through _link_or_copy so Windows
# gets a fresh copy (and re-copies on every ./setup, refreshing staleness).
if [ -d "$gstack_dir/$dir_name/sections" ]; then
if [ -e "$target/sections" ] || [ -L "$target/sections" ]; then rm -rf "$target/sections"; fi
_link_or_copy "$gstack_dir/$dir_name/sections" "$target/sections"
fi
# Link every runtime asset the skill ships next to its SKILL.md (#2317,
# #2454): sections/ for carved skills, review's checklist.md +
# specialists/, qa's templates/ + references/, gstack-upgrade's
# migrations/, careful/freeze's bin/, ... Without this, only SKILL.md
# landed and /review 404'd at "Read .claude/skills/review/checklist.md"
# on every fresh Claude install. Routes through _link_or_copy so Windows
# gets real copies refreshed on every ./setup.
_link_skill_runtime_assets "$gstack_dir/$dir_name" "$target"
linked+=("$link_name")
fi
done