207 lines
6.7 KiB
Bash
Executable File
207 lines
6.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Script: validate-skill.sh
|
|
# Purpose: Validate Agent Skill directory compliance with Claude Code standards
|
|
# Subsystem: build-system
|
|
# Called by: /build:skill command after generation
|
|
# Outputs: Validation report to stdout
|
|
|
|
set -euo pipefail
|
|
|
|
SKILL_DIR="${1:?Usage: $0 <skill-directory>}"
|
|
EXIT_CODE=0
|
|
|
|
echo "[INFO] Validating skill directory: $SKILL_DIR"
|
|
echo ""
|
|
|
|
# Check directory exists
|
|
if [[ ! -d "$SKILL_DIR" ]]; then
|
|
echo "❌ ERROR: Directory not found: $SKILL_DIR"
|
|
exit 1
|
|
fi
|
|
|
|
# Check SKILL.md exists
|
|
if [[ ! -f "$SKILL_DIR/SKILL.md" ]]; then
|
|
echo "❌ ERROR: Missing SKILL.md file"
|
|
exit 1
|
|
fi
|
|
|
|
# Check frontmatter exists and starts at line 1
|
|
FIRST_LINE=$(head -n 1 "$SKILL_DIR/SKILL.md")
|
|
if [[ "$FIRST_LINE" != "---" ]]; then
|
|
echo "❌ ERROR: YAML frontmatter MUST start at line 1"
|
|
echo " Found: $FIRST_LINE"
|
|
echo " Expected: ---"
|
|
echo " CRITICAL: Nothing can come before the opening --- (no titles, no comments, no blank lines)"
|
|
exit 1
|
|
fi
|
|
|
|
if ! grep -q "^---$" "$SKILL_DIR/SKILL.md"; then
|
|
echo "❌ ERROR: Missing closing frontmatter delimiter in SKILL.md"
|
|
exit 1
|
|
fi
|
|
|
|
# Check required frontmatter fields
|
|
REQUIRED_FIELDS=("name:" "description:")
|
|
for field in "${REQUIRED_FIELDS[@]}"; do
|
|
if ! grep -q "^$field" "$SKILL_DIR/SKILL.md"; then
|
|
echo "❌ ERROR: Missing required field: $field"
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
# Check description includes trigger keywords
|
|
if ! grep -q "Use when" "$SKILL_DIR/SKILL.md"; then
|
|
echo "⚠️ WARNING: Description should include 'Use when' trigger context"
|
|
fi
|
|
|
|
echo ""
|
|
echo "[INFO] Checking minimum requirements (scripts, templates, examples)..."
|
|
echo ""
|
|
|
|
# Minimum requirements per skill
|
|
MIN_SCRIPTS=3
|
|
MIN_TEMPLATES=4
|
|
MIN_EXAMPLES=3
|
|
|
|
# Count scripts
|
|
if [[ -d "$SKILL_DIR/scripts" ]]; then
|
|
SCRIPT_COUNT=$(find "$SKILL_DIR/scripts" -type f -name "*.sh" | wc -l)
|
|
echo "📂 Scripts found: $SCRIPT_COUNT"
|
|
|
|
if ((SCRIPT_COUNT >= MIN_SCRIPTS)); then
|
|
echo " ✅ Meets minimum requirement (>= $MIN_SCRIPTS scripts)"
|
|
else
|
|
echo " ❌ ERROR: Below minimum requirement (need $MIN_SCRIPTS, found $SCRIPT_COUNT)"
|
|
echo " Each skill should have 3-5 helper scripts (setup, validate, generate, etc.)"
|
|
EXIT_CODE=1
|
|
fi
|
|
|
|
# List scripts
|
|
if ((SCRIPT_COUNT > 0)); then
|
|
echo " Scripts:"
|
|
find "$SKILL_DIR/scripts" -type f -name "*.sh" -exec basename {} \; | sed 's/^/ - /'
|
|
fi
|
|
else
|
|
echo "📂 Scripts: directory not found"
|
|
echo " ❌ ERROR: Missing scripts/ directory"
|
|
EXIT_CODE=1
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# Count templates
|
|
if [[ -d "$SKILL_DIR/templates" ]]; then
|
|
TEMPLATE_COUNT=$(find "$SKILL_DIR/templates" -type f | wc -l)
|
|
echo "📂 Templates found: $TEMPLATE_COUNT"
|
|
|
|
if ((TEMPLATE_COUNT >= MIN_TEMPLATES)); then
|
|
echo " ✅ Meets minimum requirement (>= $MIN_TEMPLATES templates)"
|
|
else
|
|
echo " ❌ ERROR: Below minimum requirement (need $MIN_TEMPLATES, found $TEMPLATE_COUNT)"
|
|
echo " Each skill should have 4-6 templates (basic, advanced, TS, Python, etc.)"
|
|
EXIT_CODE=1
|
|
fi
|
|
|
|
# Check for TypeScript and Python coverage
|
|
TS_COUNT=$(find "$SKILL_DIR/templates" -type f -name "*.ts" -o -name "*.tsx" | wc -l)
|
|
PY_COUNT=$(find "$SKILL_DIR/templates" -type f -name "*.py" | wc -l)
|
|
|
|
if ((TS_COUNT > 0)) && ((PY_COUNT > 0)); then
|
|
echo " ✅ Has both TypeScript ($TS_COUNT) and Python ($PY_COUNT) templates"
|
|
elif ((TS_COUNT > 0)); then
|
|
echo " ⚠️ WARNING: Has TypeScript templates but no Python templates"
|
|
elif ((PY_COUNT > 0)); then
|
|
echo " ⚠️ WARNING: Has Python templates but no TypeScript templates"
|
|
fi
|
|
|
|
# List templates by type
|
|
if ((TEMPLATE_COUNT > 0)); then
|
|
echo " Templates:"
|
|
find "$SKILL_DIR/templates" -type f | sed "s|$SKILL_DIR/templates/||" | sed 's/^/ - /'
|
|
fi
|
|
else
|
|
echo "📂 Templates: directory not found"
|
|
echo " ❌ ERROR: Missing templates/ directory"
|
|
EXIT_CODE=1
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# Count examples
|
|
if [[ -d "$SKILL_DIR/examples" ]]; then
|
|
EXAMPLE_COUNT=$(find "$SKILL_DIR/examples" -type f -name "*.md" | wc -l)
|
|
echo "📂 Examples found: $EXAMPLE_COUNT"
|
|
|
|
if ((EXAMPLE_COUNT >= MIN_EXAMPLES)); then
|
|
echo " ✅ Meets minimum requirement (>= $MIN_EXAMPLES examples)"
|
|
else
|
|
echo " ❌ ERROR: Below minimum requirement (need $MIN_EXAMPLES, found $EXAMPLE_COUNT)"
|
|
echo " Each skill should have 3-5 examples (basic, advanced, patterns, edge-cases, integration)"
|
|
EXIT_CODE=1
|
|
fi
|
|
|
|
# List examples
|
|
if ((EXAMPLE_COUNT > 0)); then
|
|
echo " Examples:"
|
|
find "$SKILL_DIR/examples" -type f -name "*.md" -exec basename {} \; | sed 's/^/ - /'
|
|
fi
|
|
else
|
|
echo "📂 Examples: directory not found"
|
|
echo " ❌ ERROR: Missing examples/ directory"
|
|
EXIT_CODE=1
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# Cross-reference: Check that SKILL.md references match actual files
|
|
echo "[INFO] Checking SKILL.md references match actual files..."
|
|
echo ""
|
|
|
|
# Count references in SKILL.md
|
|
SCRIPT_REFS=$(grep -oE 'scripts/[a-zA-Z0-9_-]+\.sh' "$SKILL_DIR/SKILL.md" 2>/dev/null | sort -u | wc -l || echo 0)
|
|
TEMPLATE_REFS=$(grep -oE 'templates/[a-zA-Z0-9_/.-]+\.(ts|py|tsx|js)' "$SKILL_DIR/SKILL.md" 2>/dev/null | sort -u | wc -l || echo 0)
|
|
EXAMPLE_REFS=$(grep -oE 'examples/[a-zA-Z0-9_-]+\.md' "$SKILL_DIR/SKILL.md" 2>/dev/null | sort -u | wc -l || echo 0)
|
|
|
|
echo "📝 SKILL.md references:"
|
|
echo " Scripts referenced: $SCRIPT_REFS"
|
|
echo " Templates referenced: $TEMPLATE_REFS"
|
|
echo " Examples referenced: $EXAMPLE_REFS"
|
|
|
|
echo ""
|
|
|
|
# Compare references to actual files
|
|
if [[ -d "$SKILL_DIR/scripts" ]]; then
|
|
if ((SCRIPT_REFS == SCRIPT_COUNT)); then
|
|
echo " ✅ Script references match actual files ($SCRIPT_REFS = $SCRIPT_COUNT)"
|
|
else
|
|
echo " ⚠️ WARNING: Script count mismatch (referenced: $SCRIPT_REFS, actual: $SCRIPT_COUNT)"
|
|
fi
|
|
fi
|
|
|
|
if [[ -d "$SKILL_DIR/templates" ]]; then
|
|
if ((TEMPLATE_REFS == TEMPLATE_COUNT)); then
|
|
echo " ✅ Template references match actual files ($TEMPLATE_REFS = $TEMPLATE_COUNT)"
|
|
else
|
|
echo " ⚠️ WARNING: Template count mismatch (referenced: $TEMPLATE_REFS, actual: $TEMPLATE_COUNT)"
|
|
fi
|
|
fi
|
|
|
|
if [[ -d "$SKILL_DIR/examples" ]]; then
|
|
if ((EXAMPLE_REFS == EXAMPLE_COUNT)); then
|
|
echo " ✅ Example references match actual files ($EXAMPLE_REFS = $EXAMPLE_COUNT)"
|
|
else
|
|
echo " ⚠️ WARNING: Example count mismatch (referenced: $EXAMPLE_REFS, actual: $EXAMPLE_COUNT)"
|
|
fi
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# Final result
|
|
if [ $EXIT_CODE -eq 0 ]; then
|
|
echo "✅ Skill validation passed - all minimum requirements met!"
|
|
else
|
|
echo "❌ Skill validation failed - does not meet minimum requirements"
|
|
fi
|
|
|
|
exit $EXIT_CODE
|