51 lines
1.9 KiB
Python
51 lines
1.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Validate dsh-skills catalog: each skills/<name>/SKILL.md frontmatter must
|
|
carry required name and description, with kebab-case names (the contract
|
|
enforced by @deepseek-ai/dsh-skill-filesystem). Exits non-zero on any issue."""
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
def main(root: str) -> int:
|
|
base = Path(root) / "skills"
|
|
if not base.is_dir():
|
|
print(f"no skills dir at {base}", file=sys.stderr)
|
|
return 1
|
|
errors = 0
|
|
examined = 0
|
|
for d in sorted(p for p in base.iterdir() if p.is_dir()):
|
|
md = d / "SKILL.md"
|
|
if not md.is_file():
|
|
print(f"[warn] {d.name}: missing SKILL.md", file=sys.stderr)
|
|
errors += 1
|
|
continue
|
|
examined += 1
|
|
text = md.read_text(encoding="utf-8")
|
|
m = re.match(r"^---\n([\s\S]*?)\n---", text)
|
|
if not m:
|
|
print(f"[error] {d.name}: no frontmatter", file=sys.stderr)
|
|
errors += 1
|
|
continue
|
|
fm = m.group(1)
|
|
name = re.search(r"^name:\s*(\S.*)$", fm, re.M)
|
|
desc = re.search(r"^description:\s*(\S.*)$", fm, re.M)
|
|
if not name:
|
|
print(f"[error] {d.name}: missing 'name'", file=sys.stderr)
|
|
errors += 1
|
|
else:
|
|
n = name.group(1).strip()
|
|
if n != d.name:
|
|
print(f"[error] {d.name}: frontmatter name '{n}' != dir name", file=sys.stderr)
|
|
errors += 1
|
|
if not re.fullmatch(r"[a-z0-9]+(-[a-z0-9]+)*", n):
|
|
print(f"[error] {d.name}: name not kebab-case: '{n}'", file=sys.stderr)
|
|
errors += 1
|
|
if not desc:
|
|
print(f"[error] {d.name}: missing 'description'", file=sys.stderr)
|
|
errors += 1
|
|
print(f"checked {examined} skills, {errors} problems")
|
|
return 1 if errors else 0
|
|
|
|
if __name__ == "__main__":
|
|
root = sys.argv[1] if len(sys.argv) > 1 else "."
|
|
sys.exit(main(root)) |