feat(skills): declare which surfaces each skill ships to
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:
@@ -0,0 +1,65 @@
|
||||
#!/bin/sh
|
||||
# Tests for bin/check-surfaces.
|
||||
#
|
||||
# The repo ships to three surfaces — the Claude Code plugin, the symlinked
|
||||
# library (Crush/Pi/OpenCode) and claude.ai, which has no fetch path at all —
|
||||
# and used to treat every skill as belonging to all of them. gitea-ci,
|
||||
# playwright and web-shot cannot fire in a chat surface; a flat mirror installs
|
||||
# skills that can never trigger and degrades selection for the ones that can
|
||||
# (#29).
|
||||
#
|
||||
# The risk this gate exists for: a wrong `surfaces:` value silently removes a
|
||||
# skill from a harness rather than erroring. So presence is not enough — values
|
||||
# are validated against the allowed set.
|
||||
|
||||
set -u
|
||||
|
||||
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||
SUT="$ROOT/bin/check-surfaces"
|
||||
|
||||
pass=0
|
||||
fail=0
|
||||
WORK=${TMPDIR:-/tmp}/check_surfaces_test.$$
|
||||
|
||||
if [ ! -f "$SUT" ]; then
|
||||
printf 'FAIL: %s does not exist\n' "$SUT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cleanup() { rm -rf "$WORK"; }
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
# fixture <name> <surfaces-line-or-empty> — a checkout holding one skill.
|
||||
fixture() {
|
||||
_f="$WORK/$1"
|
||||
mkdir -p "$_f/skills/alpha"
|
||||
if [ -n "$2" ]; then
|
||||
printf -- '---\nname: alpha\n%s\ndescription: a\n---\n' "$2" > "$_f/skills/alpha/SKILL.md"
|
||||
else
|
||||
printf -- '---\nname: alpha\ndescription: a\n---\n' > "$_f/skills/alpha/SKILL.md"
|
||||
fi
|
||||
printf '%s' "$_f"
|
||||
}
|
||||
|
||||
# expect <rc> <label> <fixture> [needle]
|
||||
expect() {
|
||||
_want=$1; _label=$2; _fx=$3; _needle=${4:-}
|
||||
_out=$(sh "$SUT" "$_fx" 2>&1); _rc=$?
|
||||
if [ "$_rc" -ne "$_want" ]; then
|
||||
fail=$((fail + 1)); printf ' FAIL %s (want rc=%s got %s)\n%s\n' "$_label" "$_want" "$_rc" "$_out"; return
|
||||
fi
|
||||
if [ -n "$_needle" ] && ! printf '%s' "$_out" | grep -q "$_needle"; then
|
||||
fail=$((fail + 1)); printf ' FAIL %s (rc ok, report never says %s)\n%s\n' "$_label" "$_needle" "$_out"; return
|
||||
fi
|
||||
pass=$((pass + 1)); printf ' ok %s\n' "$_label"
|
||||
}
|
||||
|
||||
expect 0 'the default pair is accepted' "$(fixture ok 'surfaces: [claude-code, library]')"
|
||||
expect 0 'all three surfaces are accepted' "$(fixture three 'surfaces: [claude-code, library, chat]')"
|
||||
expect 0 'a single surface is accepted' "$(fixture one 'surfaces: [chat]')"
|
||||
expect 1 'a missing surfaces field fails' "$(fixture missing '')" 'alpha'
|
||||
expect 1 'an unknown surface value fails' "$(fixture bogus 'surfaces: [claude-code, desktop]')" 'desktop'
|
||||
expect 1 'an empty surfaces list fails' "$(fixture empty 'surfaces: []')" 'alpha'
|
||||
|
||||
printf '\ncheck_surfaces: %d passed, %d failed\n' "$pass" "$fail"
|
||||
[ "$fail" -eq 0 ]
|
||||
@@ -127,6 +127,13 @@ f=$(new_fixture missing); wire_clean "$f"
|
||||
rm -f "$f/home/.agents/skills/beta"
|
||||
assert_fails "$f" 'beta' 'a checkout skill with no link is reported'
|
||||
|
||||
# ...but only for skills that ship to the library. A chat-only skill has no
|
||||
# business in a Crush directory, so its absence is correct, not drift (#29).
|
||||
f=$(new_fixture chatonly); wire_clean "$f"
|
||||
mkdir -p "$f/ck/skills/gamma"
|
||||
printf -- '---\nname: gamma\nsurfaces: [chat]\ndescription: g\n---\n' > "$f/ck/skills/gamma/SKILL.md"
|
||||
assert_clean "$f" 'a chat-only skill needs no library link'
|
||||
|
||||
# --- 5. stray entry (the loose secret-hygiene.md that loaded nowhere) --------
|
||||
f=$(new_fixture stray); wire_clean "$f"
|
||||
printf 'orphan\n' > "$f/ck/skills/loose-note.md"
|
||||
|
||||
@@ -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 ]
|
||||
|
||||
Reference in New Issue
Block a user