fix(validate): accept SKILL.md as skill definition
Claude Code-style skills use SKILL.md with YAML frontmatter instead of skill.toml. Validator now accepts either form, unblocking the 60 bundled skills restored in #42. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
fc650e4ed3
commit
adf2323330
1 file changed
+42
-1
+42
-1
@@ -238,6 +238,44 @@ def validate_skill_file(filepath: Path) -> list[str]:
|
|||||||
return errors
|
return errors
|
||||||
|
|
||||||
|
|
||||||
|
def validate_skill_md_file(filepath: Path) -> list[str]:
|
||||||
|
"""Validate a Claude Code-style SKILL.md file with YAML frontmatter."""
|
||||||
|
errors = []
|
||||||
|
rel = filepath.relative_to(filepath.parent.parent)
|
||||||
|
|
||||||
|
try:
|
||||||
|
text = filepath.read_text(encoding="utf-8")
|
||||||
|
except OSError as e:
|
||||||
|
return [f"{rel}: Failed to read file: {e}"]
|
||||||
|
|
||||||
|
if not text.startswith("---"):
|
||||||
|
errors.append(f"{rel}: Missing YAML frontmatter (must start with '---')")
|
||||||
|
return errors
|
||||||
|
|
||||||
|
end = text.find("\n---", 3)
|
||||||
|
if end == -1:
|
||||||
|
errors.append(f"{rel}: Unterminated YAML frontmatter (missing closing '---')")
|
||||||
|
return errors
|
||||||
|
|
||||||
|
fm = text[3:end].strip()
|
||||||
|
meta: dict[str, str] = {}
|
||||||
|
for line in fm.splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
if not line or line.startswith("#"):
|
||||||
|
continue
|
||||||
|
if ":" not in line:
|
||||||
|
continue
|
||||||
|
k, _, v = line.partition(":")
|
||||||
|
meta[k.strip()] = v.strip().strip('"').strip("'")
|
||||||
|
|
||||||
|
if not meta.get("name"):
|
||||||
|
errors.append(f"{rel}: Missing frontmatter field 'name'")
|
||||||
|
if not meta.get("description"):
|
||||||
|
errors.append(f"{rel}: Missing frontmatter field 'description'")
|
||||||
|
|
||||||
|
return errors
|
||||||
|
|
||||||
|
|
||||||
def validate_aliases_file(filepath: Path) -> list[str]:
|
def validate_aliases_file(filepath: Path) -> list[str]:
|
||||||
"""Validate aliases.toml."""
|
"""Validate aliases.toml."""
|
||||||
if not filepath.exists():
|
if not filepath.exists():
|
||||||
@@ -370,10 +408,13 @@ def main():
|
|||||||
skill_dirs = sorted([d for d in skills_dir.iterdir() if d.is_dir() and not d.name.startswith(".")])
|
skill_dirs = sorted([d for d in skills_dir.iterdir() if d.is_dir() and not d.name.startswith(".")])
|
||||||
for d in skill_dirs:
|
for d in skill_dirs:
|
||||||
skill_toml = d / "skill.toml"
|
skill_toml = d / "skill.toml"
|
||||||
|
skill_md = d / "SKILL.md"
|
||||||
if skill_toml.exists():
|
if skill_toml.exists():
|
||||||
all_errors.extend(validate_skill_file(skill_toml))
|
all_errors.extend(validate_skill_file(skill_toml))
|
||||||
|
elif skill_md.exists():
|
||||||
|
all_errors.extend(validate_skill_md_file(skill_md))
|
||||||
else:
|
else:
|
||||||
all_errors.append(f"skills/{d.name}: Missing skill.toml")
|
all_errors.append(f"skills/{d.name}: Missing skill.toml or SKILL.md")
|
||||||
stats["skills"] = len(skill_dirs)
|
stats["skills"] = len(skill_dirs)
|
||||||
|
|
||||||
# --- Plugins ---
|
# --- Plugins ---
|
||||||
|
|||||||
Reference in new issue
Block a user