122 lines
3.6 KiB
Bash
Executable file
122 lines
3.6 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# test-skill-triggers.sh — Verifies skill description quality
|
|
#
|
|
# Honest limit: this only verifies strings are present in SKILL.md description.
|
|
# It cannot prove Claude Code's orchestrator fires the skill on those prompts.
|
|
# Runtime auto-fire validation is the operator's dogfood step (SC1).
|
|
#
|
|
# Checks:
|
|
# - SKILL.md frontmatter has 'description:' field
|
|
# - description block (from `description: |` to the closing `---`) is >=400 chars
|
|
# - if .triggers.txt exists, every phrase in it appears in SKILL.md description
|
|
#
|
|
# Usage: bash tests/test-skill-triggers.sh
|
|
# Exit codes: 0 = pass; 1 = at least one FAIL
|
|
|
|
set -euo pipefail
|
|
LC_ALL=en_US.UTF-8
|
|
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
NC='\033[0m'
|
|
|
|
PLUGIN_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
PASS=0
|
|
FAIL=0
|
|
WARN=0
|
|
|
|
pass() { printf "${GREEN} ✓ %s${NC}\n" "$1"; PASS=$((PASS + 1)); }
|
|
fail() { printf "${RED} ✗ %s${NC}\n" "$1"; FAIL=$((FAIL + 1)); }
|
|
warn() { printf "${YELLOW} ⚠ %s${NC}\n" "$1"; WARN=$((WARN + 1)); }
|
|
|
|
echo "=== test-skill-triggers ==="
|
|
echo "Plugin root: $PLUGIN_ROOT"
|
|
echo ""
|
|
|
|
# -------------------------------------------------------
|
|
# Iterate over every SKILL.md
|
|
# -------------------------------------------------------
|
|
SKILL_COUNT=0
|
|
for skill_file in "$PLUGIN_ROOT"/skills/*/SKILL.md; do
|
|
[ -f "$skill_file" ] || continue
|
|
SKILL_COUNT=$((SKILL_COUNT + 1))
|
|
|
|
skill_dir="$(dirname "$skill_file")"
|
|
skill_name="$(basename "$skill_dir")"
|
|
|
|
echo "--- $skill_name ---"
|
|
|
|
# ------------------------
|
|
# Frontmatter check
|
|
# ------------------------
|
|
first_line="$(head -n 1 "$skill_file")"
|
|
if [ "$first_line" != "---" ]; then
|
|
fail "$skill_name/SKILL.md: missing frontmatter delimiter on line 1"
|
|
echo ""
|
|
continue
|
|
fi
|
|
|
|
# ------------------------
|
|
# Description >=400 chars (Triggers on: enumeration counts)
|
|
# ------------------------
|
|
desc_block_chars="$(awk '/^description: \|/,/^---$/' "$skill_file" | wc -c | tr -d '[:space:]')"
|
|
if [ -z "$desc_block_chars" ]; then desc_block_chars=0; fi
|
|
|
|
if [ "$desc_block_chars" -ge 400 ]; then
|
|
pass "$skill_name: description block is $desc_block_chars chars (>=400)"
|
|
else
|
|
fail "$skill_name: description block is $desc_block_chars chars (<400 required)"
|
|
fi
|
|
|
|
# ------------------------
|
|
# Trigger-phrase coverage (.triggers.txt)
|
|
# ------------------------
|
|
triggers_file="$skill_dir/.triggers.txt"
|
|
|
|
if [ ! -f "$triggers_file" ]; then
|
|
warn "$skill_name: .triggers.txt missing (advisory — operator may want one)"
|
|
echo ""
|
|
continue
|
|
fi
|
|
|
|
trigger_count="$(grep -cE '.' "$triggers_file" || true)"
|
|
if [ -z "$trigger_count" ] || [ "$trigger_count" -lt 8 ]; then
|
|
fail "$skill_name/.triggers.txt: only $trigger_count phrase(s) (>=8 required)"
|
|
else
|
|
pass "$skill_name/.triggers.txt: $trigger_count phrase(s)"
|
|
fi
|
|
|
|
# check each phrase appears in SKILL.md description block
|
|
MISSING=0
|
|
while IFS= read -r phrase; do
|
|
[ -z "$phrase" ] && continue
|
|
if ! grep -qF "$phrase" "$skill_file"; then
|
|
fail "$skill_name: trigger phrase missing from SKILL.md description: '$phrase'"
|
|
MISSING=$((MISSING + 1))
|
|
fi
|
|
done < "$triggers_file"
|
|
|
|
if [ "$MISSING" -eq 0 ]; then
|
|
pass "$skill_name: all $trigger_count trigger phrase(s) appear in SKILL.md"
|
|
fi
|
|
|
|
echo ""
|
|
done
|
|
|
|
if [ "$SKILL_COUNT" -eq 0 ]; then
|
|
fail "no SKILL.md found under skills/*/"
|
|
fi
|
|
|
|
# -------------------------------------------------------
|
|
# Summary
|
|
# -------------------------------------------------------
|
|
echo "=== Summary ==="
|
|
printf "Pass: %d Fail: %d Warn: %d\n" "$PASS" "$FAIL" "$WARN"
|
|
|
|
if [ "$FAIL" -gt 0 ]; then
|
|
exit 1
|
|
fi
|
|
exit 0
|
|
|
|
# Triggers on: documented in each skill's .triggers.txt sibling file.
|