feat(skill-creator): Add path reference validation
- Add find_path_references() to scan SKILL.md for bundled resource paths - Add validate_path_references() to verify referenced files exist - Smart filtering for example/documentation contexts - Update SKILL.md Step 6 with validation details - Bump version to 1.2.0 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -8,33 +8,90 @@ import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def find_path_references(content: str) -> list[str]:
|
||||
"""
|
||||
Extract path references from SKILL.md content.
|
||||
Looks for patterns like scripts/xxx, references/xxx, assets/xxx
|
||||
|
||||
Filters out:
|
||||
- Placeholder paths (xxx, example, etc.)
|
||||
- Paths in example contexts (lines containing "Example:", "e.g.", etc.)
|
||||
- Generic documentation examples
|
||||
"""
|
||||
# Pattern to match bundled resource paths (scripts/, references/, assets/)
|
||||
pattern = r'(?:scripts|references|assets)/[\w./-]+'
|
||||
|
||||
# Find all matches with their line context
|
||||
unique_paths = set()
|
||||
for line in content.split('\n'):
|
||||
# Skip lines that are clearly examples or documentation
|
||||
line_lower = line.lower()
|
||||
if any(x in line_lower for x in [
|
||||
'example:', 'examples:', 'e.g.', 'for example',
|
||||
'- **example', '- example:', 'such as',
|
||||
'pattern:', 'usage:', '❌', '✅',
|
||||
'- **allowed', '- **best practice', 'would be helpful',
|
||||
'like `scripts/', 'like `references/', 'like `assets/',
|
||||
]):
|
||||
continue
|
||||
|
||||
# Find paths in this line
|
||||
matches = re.findall(pattern, line)
|
||||
for path in matches:
|
||||
# Skip obvious placeholders
|
||||
if any(x in path.lower() for x in ['example', 'xxx', '<', '>', 'my-', 'my_']):
|
||||
continue
|
||||
unique_paths.add(path)
|
||||
|
||||
return list(unique_paths)
|
||||
|
||||
|
||||
def validate_path_references(skill_path: Path, content: str) -> tuple[bool, list[str]]:
|
||||
"""
|
||||
Verify all path references in SKILL.md actually exist.
|
||||
|
||||
Returns:
|
||||
(all_exist, missing_paths)
|
||||
"""
|
||||
referenced_paths = find_path_references(content)
|
||||
missing = []
|
||||
|
||||
for ref_path in referenced_paths:
|
||||
full_path = skill_path / ref_path
|
||||
if not full_path.exists():
|
||||
missing.append(ref_path)
|
||||
|
||||
return len(missing) == 0, missing
|
||||
|
||||
|
||||
def validate_skill(skill_path):
|
||||
"""Basic validation of a skill"""
|
||||
skill_path = Path(skill_path)
|
||||
|
||||
|
||||
# Check SKILL.md exists
|
||||
skill_md = skill_path / 'SKILL.md'
|
||||
if not skill_md.exists():
|
||||
return False, "SKILL.md not found"
|
||||
|
||||
|
||||
# Read and validate frontmatter
|
||||
content = skill_md.read_text()
|
||||
if not content.startswith('---'):
|
||||
return False, "No YAML frontmatter found"
|
||||
|
||||
|
||||
# Extract frontmatter
|
||||
match = re.match(r'^---\n(.*?)\n---', content, re.DOTALL)
|
||||
if not match:
|
||||
return False, "Invalid frontmatter format"
|
||||
|
||||
|
||||
frontmatter = match.group(1)
|
||||
|
||||
|
||||
# Check required fields
|
||||
if 'name:' not in frontmatter:
|
||||
return False, "Missing 'name' in frontmatter"
|
||||
if 'description:' not in frontmatter:
|
||||
return False, "Missing 'description' in frontmatter"
|
||||
|
||||
|
||||
# Extract name for validation
|
||||
name_match = re.search(r'name:\s*(.+)', frontmatter)
|
||||
if name_match:
|
||||
@@ -53,6 +110,11 @@ def validate_skill(skill_path):
|
||||
if '<' in description or '>' in description:
|
||||
return False, "Description cannot contain angle brackets (< or >)"
|
||||
|
||||
# Validate path references exist
|
||||
paths_valid, missing_paths = validate_path_references(skill_path, content)
|
||||
if not paths_valid:
|
||||
return False, f"Missing referenced files: {', '.join(missing_paths)}"
|
||||
|
||||
return True, "Skill is valid!"
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user