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:
daymade
2025-12-01 20:33:15 +08:00
parent adc4072f02
commit 31a535b409
3 changed files with 124 additions and 12 deletions

View File

@@ -194,6 +194,8 @@ To begin implementation, start with the reusable resources identified above: `sc
Also, delete any example files and directories not needed for the skill. The initialization script creates example files in `scripts/`, `references/`, and `assets/` to demonstrate structure, but most skills won't need all of them.
**When updating an existing skill**: Scan all existing reference files to check if they need corresponding updates. New features often require updates to architecture, workflow, or other existing documentation to maintain consistency.
#### Reference File Naming
Filenames must be self-explanatory without reading contents.
@@ -276,13 +278,41 @@ The packaging script will:
- YAML frontmatter format and required fields
- Skill naming conventions and directory structure
- Description completeness and quality
- File organization and resource references
- **Path reference integrity** - all `scripts/`, `references/`, and `assets/` paths mentioned in SKILL.md must exist
2. **Package** the skill if validation passes, creating a zip file named after the skill (e.g., `my-skill.zip`) that includes all files and maintains the proper directory structure for distribution.
**Common validation failure:** If SKILL.md references `scripts/my_script.py` but the file doesn't exist, validation will fail with "Missing referenced files: scripts/my_script.py". Ensure all bundled resources exist before packaging.
If validation fails, the script will report the errors and exit without creating a package. Fix any validation errors and run the packaging command again.
### Step 7: Iterate
### Step 7: Update Marketplace
After packaging, update the marketplace registry to include the new or updated skill.
**For new skills**, add an entry to `.claude-plugin/marketplace.json`:
```json
{
"name": "skill-name",
"description": "Copy from SKILL.md frontmatter description",
"source": "./",
"strict": false,
"version": "1.0.0",
"category": "developer-tools",
"keywords": ["relevant", "keywords"],
"skills": ["./skill-name"]
}
```
**For updated skills**, bump the version in `plugins[].version` following semver:
- Patch (1.0.x): Bug fixes, typo corrections
- Minor (1.x.0): New features, additional references
- Major (x.0.0): Breaking changes, restructured workflows
**Also update** `metadata.version` and `metadata.description` if the overall plugin collection changed significantly.
### Step 8: Iterate
After testing the skill, users may request improvements. Often this happens right after using the skill, with fresh context of how the skill performed.

View File

@@ -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__":