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:
Evan HuandClaude Opus 4.6 committed 2026-04-10 20:18:37 +09:00
1 parent fc650e4ed3
commit adf2323330
1 file changed
+42 -1
+42 -1
View File
@@ -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 ---