fix(shell): keep generated ignores visible to Pi

This commit is contained in:
2026-07-29 17:01:22 -04:00
parent 02f2017b40
commit 40b37281d5
2 changed files with 62 additions and 20 deletions

View File

@@ -55,6 +55,7 @@ pkgs.runCommandLocal "skills-shell-hook-check"
# collides with a selected one, and a symlink pointing outside the store. # collides with a selected one, and a symlink pointing outside the store.
mkdir -p proj/.agents/skills/handmade mkdir -p proj/.agents/skills/handmade
printf '%s\n' "a hand-authored skill" > proj/.agents/skills/handmade/SKILL.md printf '%s\n' "a hand-authored skill" > proj/.agents/skills/handmade/SKILL.md
printf '%s\n' "# fixture ignore" "result" > proj/.gitignore
mkdir -p proj/.agents/skills/sample-gamma mkdir -p proj/.agents/skills/sample-gamma
printf '%s\n' "a hand-authored gamma" > proj/.agents/skills/sample-gamma/SKILL.md printf '%s\n' "a hand-authored gamma" > proj/.agents/skills/sample-gamma/SKILL.md
ln -s /nonexistent/external-target proj/.agents/skills/external ln -s /nonexistent/external-target proj/.agents/skills/external
@@ -94,18 +95,23 @@ pkgs.runCommandLocal "skills-shell-hook-check"
test -f .claude/skills/sample-alpha/SKILL.md \ test -f .claude/skills/sample-alpha/SKILL.md \
|| fail "Claude Code compatibility link does not resolve selected skills" || fail "Claude Code compatibility link does not resolve selected skills"
echo "the .gitignore lists only the skills the hook actually linked, plus itself" echo "the project .gitignore lists only generated links and keeps skill scanners clear"
grep -qx '/.gitignore' .agents/skills/.gitignore || fail ".gitignore does not ignore itself" test ! -e .agents/skills/.gitignore || fail "the hook wrote an ignore file where skill scanners read it"
grep -qx '/sample-alpha' .agents/skills/.gitignore || fail ".gitignore omits sample-alpha" grep -qx '# fixture ignore' .gitignore || fail "the existing .gitignore content was lost"
grep -qx '/sample-beta' .agents/skills/.gitignore || fail ".gitignore omits sample-beta" grep -qx 'result' .gitignore || fail "the existing .gitignore pattern was lost"
for tracked in /handmade /sample-gamma /external; do grep -qx '# BEGIN mkSkillsShellHook' .gitignore || fail ".gitignore lacks the managed block start"
grep -qx "$tracked" .agents/skills/.gitignore \ grep -qx '# END mkSkillsShellHook' .gitignore || fail ".gitignore lacks the managed block end"
grep -qx '.claude/skills' .gitignore || fail ".gitignore omits the compatibility symlink"
grep -qx '.agents/skills/sample-alpha' .gitignore || fail ".gitignore omits sample-alpha"
grep -qx '.agents/skills/sample-beta' .gitignore || fail ".gitignore omits sample-beta"
for tracked in .agents/skills/handmade .agents/skills/sample-gamma .agents/skills/external; do
grep -qx "$tracked" .gitignore \
&& fail ".gitignore lists $tracked, which would untrack a real path" && fail ".gitignore lists $tracked, which would untrack a real path"
done done
echo "no manifest or state file is written: only the fixtures and links exist" echo "no manifest or state file is written: only the fixtures and links exist"
entries=$(ls -A .agents/skills | sort | tr '\n' ' ') entries=$(ls -A .agents/skills | sort | tr '\n' ' ')
[ "$entries" = ".gitignore external handmade sample-alpha sample-beta sample-gamma " ] \ [ "$entries" = "external handmade sample-alpha sample-beta sample-gamma " ] \
|| fail "unexpected entries under .agents/skills: [$entries]" || fail "unexpected entries under .agents/skills: [$entries]"
echo "second entry with sample-beta deselected: its symlink is removed, the rest stay" echo "second entry with sample-beta deselected: its symlink is removed, the rest stay"
@@ -119,10 +125,12 @@ pkgs.runCommandLocal "skills-shell-hook-check"
|| fail "the colliding hand-authored skill was disturbed on re-entry" || fail "the colliding hand-authored skill was disturbed on re-entry"
[ -L .agents/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" echo "the .gitignore tracks the new selection without duplicating the managed block"
grep -qx '/sample-alpha' .agents/skills/.gitignore || fail ".gitignore lost sample-alpha" grep -qx '.agents/skills/sample-alpha' .gitignore || fail ".gitignore lost sample-alpha"
grep -qx '/sample-beta' .agents/skills/.gitignore \ grep -qx '.agents/skills/sample-beta' .gitignore \
&& fail ".gitignore still lists the deselected sample-beta" && fail ".gitignore still lists the deselected sample-beta"
[ "$(grep -xc '# BEGIN mkSkillsShellHook' .gitignore)" = 1 ] \
|| fail ".gitignore has duplicate managed blocks"
touch "$out" touch "$out"
'' ''

View File

@@ -1,6 +1,8 @@
# Builds a dev-shell shellHook that links a project's selected skills into its # Builds a dev-shell shellHook that links a project's selected skills into its
# own `.agents/skills/` as direct-child symlinks into the Nix store, with # own `.agents/skills/` as direct-child symlinks into the Nix store, with
# `.claude/skills` as a compatibility symlink when that path is free. # `.claude/skills` as a compatibility symlink when that path is free.
# Generated ignores live in the project root so agent skill scanners still see
# the linked skills.
skills: skills:
let let
storeDir = builtins.storeDir; storeDir = builtins.storeDir;
@@ -25,13 +27,15 @@ in
_mkskills_dir=".agents/skills" _mkskills_dir=".agents/skills"
mkdir -p "$_mkskills_dir" mkdir -p "$_mkskills_dir"
_mkskills_claude_skills_managed=0
mkdir -p .claude mkdir -p .claude
if [ ! -e .claude/skills ] && [ ! -L .claude/skills ]; then if [ ! -e .claude/skills ] && [ ! -L .claude/skills ]; then
ln -s ../.agents/skills .claude/skills ln -s ../.agents/skills .claude/skills
_mkskills_claude_skills_managed=1
elif [ -L .claude/skills ]; then elif [ -L .claude/skills ]; then
case "$(readlink .claude/skills)" in case "$(readlink .claude/skills)" in
../.agents/skills) : ;; ../.agents/skills) _mkskills_claude_skills_managed=1 ;;
${storeDir}/*) rm -f .claude/skills && ln -s ../.agents/skills .claude/skills ;; ${storeDir}/*) rm -f .claude/skills && ln -s ../.agents/skills .claude/skills && _mkskills_claude_skills_managed=1 ;;
esac esac
else else
printf 'mkSkillsShellHook: leaving existing non-symlink .claude/skills in place; Claude Code compatibility link not installed\n' >&2 printf 'mkSkillsShellHook: leaving existing non-symlink .claude/skills in place; Claude Code compatibility link not installed\n' >&2
@@ -45,8 +49,9 @@ in
done done
# A real path at a selected skill's name is a hand-authored skill. # A real path at a selected skill's name is a hand-authored skill.
# It is left in place and left out of the managed set, so the .gitignore # It is left in place and left out of the managed set, so the generated
# below keeps tracking it rather than the skill that would have been linked. # ignore block keeps tracking it rather than the skill that would have been
# linked.
_mkskills_managed="" _mkskills_managed=""
_mkskills_link() { _mkskills_link() {
_mkskills_dest="$_mkskills_dir/$1" _mkskills_dest="$_mkskills_dir/$1"
@@ -62,17 +67,46 @@ in
} }
${links} ${links}
# Regenerate the self-ignoring .gitignore from the current selection, so the if [ -f "$_mkskills_dir/.gitignore" ] \
# store symlinks stay out of git while hand-authored skills, absent from this && grep -qx '# Generated by mkSkillsShellHook. Nix-delivered skill symlinks, kept out of git.' "$_mkskills_dir/.gitignore"; then
# list, remain tracked. rm -f "$_mkskills_dir/.gitignore"
fi
# Regenerate the project-root ignore block from the current selection.
# Skill scanners read ignore files under .agents/skills, so the block must
# live above the scanned tree.
_mkskills_gitignore=".gitignore"
_mkskills_gitignore_tmp=".gitignore.mkskills.$$"
if [ -f "$_mkskills_gitignore" ]; then
awk '
$0 == "# BEGIN mkSkillsShellHook" { skip = 1; next }
$0 == "# END mkSkillsShellHook" { skip = 0; next }
!skip { lines[++n] = $0 }
END {
while (n > 0 && lines[n] == "") n--
for (i = 1; i <= n; i++) print lines[i]
}
' "$_mkskills_gitignore" > "$_mkskills_gitignore_tmp"
else
: > "$_mkskills_gitignore_tmp"
fi
{ {
if [ -s "$_mkskills_gitignore_tmp" ]; then
cat "$_mkskills_gitignore_tmp"
printf '\n'
fi
printf '%s\n' "# BEGIN mkSkillsShellHook"
printf '%s\n' "# Generated by mkSkillsShellHook. Nix-delivered skill symlinks, kept out of git." printf '%s\n' "# Generated by mkSkillsShellHook. Nix-delivered skill symlinks, kept out of git."
printf '%s\n' "/.gitignore" if [ "$_mkskills_claude_skills_managed" = 1 ]; then
printf '%s\n' ".claude/skills"
fi
# Disable pathname expansion so a managed name is never glob-expanded. # Disable pathname expansion so a managed name is never glob-expanded.
set -f set -f
for _mkskills_name in $_mkskills_managed; do for _mkskills_name in $_mkskills_managed; do
printf '/%s\n' "$_mkskills_name" printf '.agents/skills/%s\n' "$_mkskills_name"
done done
} > "$_mkskills_dir/.gitignore" printf '%s\n' "# END mkSkillsShellHook"
} > "$_mkskills_gitignore"
rm -f "$_mkskills_gitignore_tmp"
) )
'' ''