feat(shell): deliver project skills through .agents

This commit is contained in:
2026-07-29 11:54:36 -04:00
parent c6beafc53e
commit 02f2017b40
3 changed files with 54 additions and 32 deletions

View File

@@ -53,68 +53,75 @@ pkgs.runCommandLocal "skills-shell-hook-check"
# The fixture project carries three things the hook must not disturb: a
# hand-authored skill under its own name, a hand-authored skill whose name
# collides with a selected one, and a symlink pointing outside the store.
mkdir -p proj/.claude/skills/handmade
printf '%s\n' "a hand-authored skill" > proj/.claude/skills/handmade/SKILL.md
mkdir -p proj/.claude/skills/sample-gamma
printf '%s\n' "a hand-authored gamma" > proj/.claude/skills/sample-gamma/SKILL.md
ln -s /nonexistent/external-target proj/.claude/skills/external
mkdir -p proj/.agents/skills/handmade
printf '%s\n' "a hand-authored skill" > proj/.agents/skills/handmade/SKILL.md
mkdir -p proj/.agents/skills/sample-gamma
printf '%s\n' "a hand-authored gamma" > proj/.agents/skills/sample-gamma/SKILL.md
ln -s /nonexistent/external-target proj/.agents/skills/external
cd proj
echo "first entry: the two free skills link as store symlinks that resolve"
printf '%s\n' "$hookBoth" > ../hook-both.sh
. ../hook-both.sh
for name in sample-alpha sample-beta; do
[ -L ".claude/skills/$name" ] || fail "$name was not linked as a symlink"
case "$(readlink ".claude/skills/$name")" in
[ -L ".agents/skills/$name" ] || fail "$name was not linked as a symlink"
case "$(readlink ".agents/skills/$name")" in
${builtins.storeDir}/*) : ;;
*) fail "$name's symlink does not point into the store" ;;
esac
test -f ".claude/skills/$name/SKILL.md" \
test -f ".agents/skills/$name/SKILL.md" \
|| fail "$name's link does not resolve to its SKILL.md"
done
echo "a selected skill colliding with a hand-authored one leaves the real directory"
{ [ -d .claude/skills/sample-gamma ] && [ ! -L .claude/skills/sample-gamma ]; } \
{ [ -d .agents/skills/sample-gamma ] && [ ! -L .agents/skills/sample-gamma ]; } \
|| fail "the colliding hand-authored skill was replaced by a symlink"
grep -qx "a hand-authored gamma" .claude/skills/sample-gamma/SKILL.md \
grep -qx "a hand-authored gamma" .agents/skills/sample-gamma/SKILL.md \
|| fail "the colliding hand-authored skill's content was overwritten"
echo "the non-colliding hand-authored skill is an untouched real directory"
{ [ -d .claude/skills/handmade ] && [ ! -L .claude/skills/handmade ]; } \
{ [ -d .agents/skills/handmade ] && [ ! -L .agents/skills/handmade ]; } \
|| fail "the hand-authored skill was replaced or removed"
test -f .claude/skills/handmade/SKILL.md || fail "the hand-authored skill lost its content"
test -f .agents/skills/handmade/SKILL.md || fail "the hand-authored skill lost its content"
echo "a symlink pointing outside the store is not ours, so it is left alone"
[ -L .claude/skills/external ] || fail "the non-store symlink was removed"
[ -L .agents/skills/external ] || fail "the non-store symlink was removed"
echo "Claude Code compatibility points at the canonical skill directory"
[ -L .claude/skills ] || fail ".claude/skills is not a compatibility symlink"
[ "$(readlink .claude/skills)" = "../.agents/skills" ] \
|| fail ".claude/skills points somewhere other than ../.agents/skills"
test -f .claude/skills/sample-alpha/SKILL.md \
|| fail "Claude Code compatibility link does not resolve selected skills"
echo "the .gitignore lists only the skills the hook actually linked, plus itself"
grep -qx '/.gitignore' .claude/skills/.gitignore || fail ".gitignore does not ignore itself"
grep -qx '/sample-alpha' .claude/skills/.gitignore || fail ".gitignore omits sample-alpha"
grep -qx '/sample-beta' .claude/skills/.gitignore || fail ".gitignore omits sample-beta"
grep -qx '/.gitignore' .agents/skills/.gitignore || fail ".gitignore does not ignore itself"
grep -qx '/sample-alpha' .agents/skills/.gitignore || fail ".gitignore omits sample-alpha"
grep -qx '/sample-beta' .agents/skills/.gitignore || fail ".gitignore omits sample-beta"
for tracked in /handmade /sample-gamma /external; do
grep -qx "$tracked" .claude/skills/.gitignore \
grep -qx "$tracked" .agents/skills/.gitignore \
&& fail ".gitignore lists $tracked, which would untrack a real path"
done
echo "no manifest or state file is written: only the fixtures and links exist"
entries=$(ls -A .claude/skills | sort | tr '\n' ' ')
entries=$(ls -A .agents/skills | sort | tr '\n' ' ')
[ "$entries" = ".gitignore external handmade sample-alpha sample-beta sample-gamma " ] \
|| fail "unexpected entries under .claude/skills: [$entries]"
|| fail "unexpected entries under .agents/skills: [$entries]"
echo "second entry with sample-beta deselected: its symlink is removed, the rest stay"
printf '%s\n' "$hookAlpha" > ../hook-alpha.sh
. ../hook-alpha.sh
test ! -e .claude/skills/sample-beta || fail "the deselected skill's symlink was not removed"
[ -L .claude/skills/sample-alpha ] || fail "the still-selected skill was dropped"
{ [ -d .claude/skills/handmade ] && [ ! -L .claude/skills/handmade ]; } \
test ! -e .agents/skills/sample-beta || fail "the deselected skill's symlink was not removed"
[ -L .agents/skills/sample-alpha ] || fail "the still-selected skill was dropped"
{ [ -d .agents/skills/handmade ] && [ ! -L .agents/skills/handmade ]; } \
|| fail "the hand-authored skill was disturbed on re-entry"
{ [ -d .claude/skills/sample-gamma ] && [ ! -L .claude/skills/sample-gamma ]; } \
{ [ -d .agents/skills/sample-gamma ] && [ ! -L .agents/skills/sample-gamma ]; } \
|| fail "the colliding hand-authored skill was disturbed on re-entry"
[ -L .claude/skills/external ] || fail "the non-store symlink was disturbed on re-entry"
[ -L .agents/skills/external ] || fail "the non-store symlink was disturbed on re-entry"
echo "the .gitignore tracks the new selection"
grep -qx '/sample-alpha' .claude/skills/.gitignore || fail ".gitignore lost sample-alpha"
grep -qx '/sample-beta' .claude/skills/.gitignore \
grep -qx '/sample-alpha' .agents/skills/.gitignore || fail ".gitignore lost sample-alpha"
grep -qx '/sample-beta' .agents/skills/.gitignore \
&& fail ".gitignore still lists the deselected sample-beta"
touch "$out"