Initial commit
This commit is contained in:
206
skills/build-assistant/scripts/validate-skill.sh
Executable file
206
skills/build-assistant/scripts/validate-skill.sh
Executable file
@@ -0,0 +1,206 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user