105 lines
3.2 KiB
Python
105 lines
3.2 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Validate all skills in the repository.
|
|
Checks for required frontmatter fields and proper structure.
|
|
"""
|
|
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
def validate_skills(skills_dir):
|
|
print(f"validating skills in: {skills_dir}\n")
|
|
errors = []
|
|
warnings = []
|
|
skill_count = 0
|
|
|
|
for entry in sorted(os.listdir(skills_dir)):
|
|
skill_path = os.path.join(skills_dir, entry)
|
|
skill_file = os.path.join(skill_path, "SKILL.md")
|
|
|
|
if not os.path.isdir(skill_path):
|
|
continue
|
|
|
|
if not os.path.exists(skill_file):
|
|
errors.append(f"{entry}: missing SKILL.md")
|
|
continue
|
|
|
|
skill_count += 1
|
|
|
|
with open(skill_file, 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
|
|
# Check for frontmatter
|
|
if not content.strip().startswith("---"):
|
|
errors.append(f"{entry}: missing frontmatter (must start with ---)")
|
|
continue
|
|
|
|
# Extract frontmatter
|
|
fm_match = re.search(r'^---\s*\n(.*?)\n---', content, re.DOTALL)
|
|
if not fm_match:
|
|
errors.append(f"{entry}: malformed frontmatter")
|
|
continue
|
|
|
|
fm_content = fm_match.group(1)
|
|
|
|
# Check required fields
|
|
name_match = re.search(r'^name:\s*(.+)$', fm_content, re.MULTILINE)
|
|
desc_match = re.search(r'^description:\s*(.+)$', fm_content, re.MULTILINE)
|
|
|
|
if not name_match:
|
|
errors.append(f"{entry}: frontmatter missing 'name' field")
|
|
else:
|
|
name = name_match.group(1).strip()
|
|
# Validate name format
|
|
if not re.match(r'^[a-z0-9]+(-[a-z0-9]+)*$', name):
|
|
errors.append(f"{entry}: name must be lowercase alphanumeric with hyphens")
|
|
if name != entry:
|
|
warnings.append(f"{entry}: name '{name}' doesn't match directory name")
|
|
|
|
if not desc_match:
|
|
errors.append(f"{entry}: frontmatter missing 'description' field")
|
|
else:
|
|
desc = desc_match.group(1).strip()
|
|
if len(desc) < 10:
|
|
warnings.append(f"{entry}: description seems too short")
|
|
if len(desc) > 1024:
|
|
errors.append(f"{entry}: description exceeds 1024 characters")
|
|
|
|
# Check for content after frontmatter
|
|
body = content[fm_match.end():].strip()
|
|
if len(body) < 50:
|
|
warnings.append(f"{entry}: skill content seems too short")
|
|
|
|
# Print results
|
|
print(f"checked {skill_count} skills\n")
|
|
|
|
if warnings:
|
|
print("warnings:")
|
|
for w in warnings:
|
|
print(f" {w}")
|
|
print()
|
|
|
|
if errors:
|
|
print("errors:")
|
|
for e in errors:
|
|
print(f" {e}")
|
|
print()
|
|
print(f"validation failed with {len(errors)} errors")
|
|
return False
|
|
|
|
print("all skills passed validation!")
|
|
return True
|
|
|
|
|
|
if __name__ == "__main__":
|
|
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
skills_path = os.path.join(base_dir, "skills")
|
|
|
|
if not os.path.exists(skills_path):
|
|
print(f"error: skills directory not found at {skills_path}")
|
|
sys.exit(1)
|
|
|
|
success = validate_skills(skills_path)
|
|
sys.exit(0 if success else 1)
|