1+ #! /bin/bash
2+
3+ # Colors for output
4+ RED=' \033[0;31m'
5+ GREEN=' \033[0;32m'
6+ YELLOW=' \033[1;33m'
7+ BLUE=' \033[0;34m'
8+ NC=' \033[0m' # No Color
9+
10+ SKILLS_DIR=" skills"
11+ ISSUES=0
12+ WARNINGS=0
13+ PASSED=0
14+
15+ echo " 🔍 Auditing Skills Against Agent Skills Specification"
16+ echo " ======================================================"
17+ echo " "
18+ echo " Reference: https://agentskills.io/specification.md"
19+ echo " "
20+
21+ # Validation rules from CLAUDE.md
22+ # REQUIRED: name, description
23+ # OPTIONAL: license, metadata
24+ # name: 1-64 chars, lowercase a-z, numbers, hyphens only, must match directory
25+ # description: 1-1024 chars with trigger phrases
26+ # SKILL.md: under 500 lines
27+ # Optional dirs: references/, scripts/, assets/
28+
29+ for skill_dir in " $SKILLS_DIR " /* /; do
30+ skill_name=$( basename " $skill_dir " )
31+ skill_file=" $skill_dir /SKILL.md"
32+ skill_errors=()
33+ skill_warnings=()
34+
35+ # Check if SKILL.md exists
36+ if [[ ! -f " $skill_file " ]]; then
37+ echo -e " ${RED} ❌ $skill_name ${NC} "
38+ echo " Missing SKILL.md"
39+ (( ISSUES++ ))
40+ continue
41+ fi
42+
43+ # Extract frontmatter (between the first two `---` markers, exclusive)
44+ frontmatter=$( awk ' /^---$/{count++; next} count==1' " $skill_file " )
45+
46+ # Validate frontmatter exists
47+ if [[ -z " $frontmatter " ]]; then
48+ echo -e " ${RED} ❌ $skill_name ${NC} "
49+ echo " Missing YAML frontmatter (---)"
50+ (( ISSUES++ ))
51+ continue
52+ fi
53+
54+ # ===== NAME VALIDATION =====
55+ name_in_file=$( echo " $frontmatter " | grep " ^name:" | sed ' s/^name: //' | tr -d ' ' )
56+
57+ if [[ -z " $name_in_file " ]]; then
58+ skill_errors+=(" Missing 'name' field in frontmatter" )
59+ elif [[ " $name_in_file " != " $skill_name " ]]; then
60+ skill_errors+=(" Name mismatch: directory='$skill_name ' but frontmatter='$name_in_file '" )
61+ elif ! [[ " $name_in_file " =~ ^[a-z0-9]([a-z0-9-]{0,62}[a-z0-9])? $ ]]; then
62+ skill_errors+=(" Invalid name format: '$name_in_file ' (must be lowercase, alphanumeric + hyphens only)" )
63+ elif [[ ${# name_in_file} -lt 1 || ${# name_in_file} -gt 64 ]]; then
64+ skill_errors+=(" Name length invalid: ${# name_in_file} chars (must be 1-64)" )
65+ fi
66+
67+ # ===== DESCRIPTION VALIDATION =====
68+ # Extract full description: same-line values, quoted strings, and YAML
69+ # block/folded scalars (description: > or | with indented continuation lines).
70+ description=$( echo " $frontmatter " | awk '
71+ /^description:/ {
72+ sub(/^description:[[:space:]]*/, "")
73+ line = $0
74+ # Double-quoted single-line
75+ if (line ~ /^"/) {
76+ gsub(/^"|"$/, "", line)
77+ print line
78+ exit
79+ }
80+ # Folded (>) or literal (|) block — indicator may include - (strip chomping)
81+ if (line ~ /^(>|>-|[|]|[|]-)[[:space:]]*$/) {
82+ desc = ""
83+ while (getline > 0) {
84+ # Next top-level frontmatter key (unindented name:)
85+ if ($0 ~ /^[a-zA-Z][a-zA-Z0-9_-]*:[[:space:]]/) break
86+ s = $0
87+ sub(/^[[:space:]]+/, "", s)
88+ desc = (desc == "" ? s : desc " " s)
89+ }
90+ print desc
91+ exit
92+ }
93+ print line
94+ exit
95+ }
96+ ' )
97+
98+ if [[ -z " $description " ]]; then
99+ skill_errors+=(" Missing 'description' field in frontmatter" )
100+ else
101+ desc_len=${# description}
102+ if [[ $desc_len -lt 1 || $desc_len -gt 1024 ]]; then
103+ skill_errors+=(" Description length invalid: $desc_len chars (must be 1-1024)" )
104+ fi
105+
106+ # Check for trigger phrases (case-insensitive; match "Use", "when", etc.)
107+ if ! echo " $description " | grep -Eiq ' when|mention|use' ; then
108+ skill_warnings+=(" Description lacks clear trigger phrases ('when', 'mention', 'use')" )
109+ fi
110+ fi
111+
112+ # ===== OPTIONAL FIELDS VALIDATION =====
113+ license=$( echo " $frontmatter " | grep " ^license:" | sed ' s/^license: //' | tr -d ' ' )
114+ if [[ -n " $license " && " $license " != " MIT" && " $license " != " Apache-2.0" && " $license " != " ISC" ]]; then
115+ skill_warnings+=(" License '$license ' is non-standard (default: MIT)" )
116+ fi
117+
118+ # Check metadata structure
119+ metadata=$( echo " $frontmatter " | grep -A 10 " ^metadata:" )
120+ if [[ -n " $metadata " ]]; then
121+ # If metadata exists, check for version placement
122+ if echo " $frontmatter " | grep -q " ^version:" ; then
123+ skill_errors+=(" 'version' is top-level (should be under 'metadata:')" )
124+ fi
125+ # Could add more metadata validation here
126+ fi
127+
128+ # ===== FILE STRUCTURE VALIDATION =====
129+ line_count=$( wc -l < " $skill_file " )
130+ if [[ $line_count -gt 500 ]]; then
131+ skill_warnings+=(" SKILL.md is $line_count lines (should be <500, move details to references/)" )
132+ fi
133+
134+ # Check for optional directories
135+ for optdir in references scripts assets; do
136+ if [[ -d " $skill_dir /$optdir " ]]; then
137+ # Just note its presence - no validation required
138+ :
139+ fi
140+ done
141+
142+ # ===== REPORT RESULTS =====
143+ if [[ ${# skill_errors[@]} -gt 0 ]]; then
144+ echo -e " ${RED} ❌ $skill_name ${NC} "
145+ for error in " ${skill_errors[@]} " ; do
146+ echo -e " ${RED} Error:${NC} $error "
147+ done
148+ if [[ ${# skill_warnings[@]} -gt 0 ]]; then
149+ for warning in " ${skill_warnings[@]} " ; do
150+ echo -e " ${YELLOW} Warning:${NC} $warning "
151+ done
152+ fi
153+ (( ISSUES++ ))
154+ elif [[ ${# skill_warnings[@]} -gt 0 ]]; then
155+ echo -e " ${YELLOW} ⚠️ $skill_name ${NC} "
156+ for warning in " ${skill_warnings[@]} " ; do
157+ echo -e " ${YELLOW} Warning:${NC} $warning "
158+ done
159+ (( WARNINGS++ ))
160+ else
161+ echo -e " ${GREEN} ✓ $skill_name ${NC} "
162+ (( PASSED++ ))
163+ fi
164+ done
165+
166+ echo " "
167+ echo " ======================================================"
168+ echo " Summary:"
169+ echo -e " ${GREEN} ✓ Passed: $PASSED ${NC} "
170+ if [[ $WARNINGS -gt 0 ]]; then
171+ echo -e " ${YELLOW} ⚠️ Warnings: $WARNINGS ${NC} "
172+ fi
173+ if [[ $ISSUES -gt 0 ]]; then
174+ echo -e " ${RED} ❌ Issues: $ISSUES ${NC} "
175+ fi
176+ echo " "
177+
178+ if [[ $ISSUES -eq 0 ]]; then
179+ echo -e " ${GREEN} All skills are valid! ✓${NC} "
180+ exit 0
181+ else
182+ echo -e " ${RED} Found $ISSUES issue(s) that need fixing.${NC} "
183+ exit 1
184+ fi
0 commit comments