feat(skills): declare which surfaces each skill ships to
CI / Check / Test (push) Failing after 1s
CI / Tag release (push) Skipped

The repo distributes three ways — the Claude Code plugin, the symlinked
library for Crush/Pi/OpenCode, and claude.ai, which has no fetch path at all —
and treated every skill as belonging to all of them. claude.ai is not a fourth
host to mirror: gitea-ci, playwright and web-shot have no shell and no cluster
there, so a flat mirror installs skills that can never fire and degrades
selection for the ones that can.

Every SKILL.md now carries `surfaces:`, defaulting to [claude-code, library];
dream, own-the-task and grill-me add `chat`, matching what is actually
installed on claude.ai today. Claude Code accepts the extra key —
`claude plugin validate` passes and the skills still load.

bin/check-surfaces validates values, not just presence, because the failure
mode here is silent: a wrong surface removes a skill from a harness rather than
erroring. install.sh links only skills whose surfaces include `library`, and a
source that declares nothing at all is treated as foreign and linked as-is —
Anthropic's official set has no reason to know this convention. doctor's
missing-link check skips non-library skills for the same reason.

Refs #29
Bump-Type: minor
This commit is contained in:
2026-08-29 10:09:52 +02:00
parent 15737321ac
commit 90a58f0b9e
49 changed files with 214 additions and 1 deletions
+27
View File
@@ -156,5 +156,32 @@ else
no 'configured sources are recorded for doctor' "$out"
fi
# --- 8. surfaces: the library link set is not "every skill" -----------------
# claude.ai has no shell, no cluster and no checkout, so a chat-only skill
# linked into Crush is a skill that can never fire, competing for selection
# with ones that can (#29).
f=$(new_fixture surfaces)
mkdir -p "$f/ck/skills/chatonly"
printf -- '---\nname: chatonly\nsurfaces: [chat]\ndescription: c\n---\n' > "$f/ck/skills/chatonly/SKILL.md"
printf -- '---\nname: alpha\nsurfaces: [claude-code, library]\ndescription: a\n---\n' > "$f/ck/skills/alpha/SKILL.md"
out=$(run_install "$f")
if [ -L "$f/home/.agents/skills/alpha" ] && [ ! -e "$f/home/.agents/skills/chatonly" ]; then
ok 'a skill that does not ship to the library is not linked'
else
no 'a skill that does not ship to the library is not linked' "$out"
fi
# A foreign source has no reason to know this convention, so no declaration
# means "link it" — otherwise adding Anthropic's set would link nothing.
f=$(new_fixture foreign)
mkdir -p "$f/other/xlsx"
printf -- '---\nname: xlsx\ndescription: x\n---\n' > "$f/other/xlsx/SKILL.md"
out=$(run_install "$f" "$f/other")
if [ -L "$f/home/.agents/skills/xlsx" ]; then
ok 'a source with no surfaces convention is linked as-is'
else
no 'a source with no surfaces convention is linked as-is' "$out"
fi
printf '\ninstall: %d passed, %d failed\n' "$pass" "$fail"
[ "$fail" -eq 0 ]