reverse-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate index.json from the SKILL.md files under skills/.
|
||||
|
||||
Previously this logic lived as an inline heredoc inside
|
||||
.github/workflows/update-index.yml with a hand-rolled regex YAML parser that
|
||||
truncated 604 of 817 descriptions. It lives here now so it is testable outside
|
||||
CI and shares one PyYAML-backed parser with every other tool.
|
||||
|
||||
Usage:
|
||||
python tools/generate-index.py # write index.json
|
||||
python tools/generate-index.py --check # verify index.json is current (CI)
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from skill_frontmatter import description_of, iter_skill_dirs, load_frontmatter, FrontmatterError
|
||||
|
||||
INDEX_VERSION = "1.1.0"
|
||||
REPOSITORY = "https://github.com/mukul975/Anthropic-Cybersecurity-Skills"
|
||||
DEFAULT_DOMAIN = "cybersecurity"
|
||||
|
||||
|
||||
def build_index(skills_dir: str = "skills") -> tuple[dict, list[str]]:
|
||||
"""Build the index payload. Returns (index, errors)."""
|
||||
skills = []
|
||||
errors = []
|
||||
|
||||
for slug, skill_dir in iter_skill_dirs(skills_dir):
|
||||
try:
|
||||
frontmatter = load_frontmatter(os.path.join(skill_dir, "SKILL.md"))
|
||||
except FrontmatterError as exc:
|
||||
errors.append(f"{slug}: {exc}")
|
||||
continue
|
||||
|
||||
description = description_of(frontmatter)
|
||||
if not description:
|
||||
errors.append(f"{slug}: empty description")
|
||||
|
||||
skills.append({
|
||||
"name": slug,
|
||||
"description": description,
|
||||
"domain": frontmatter.get("domain") or DEFAULT_DOMAIN,
|
||||
"path": f"{skills_dir}/{slug}",
|
||||
})
|
||||
|
||||
index = {
|
||||
"version": INDEX_VERSION,
|
||||
"generated_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
||||
"repository": REPOSITORY,
|
||||
"domain": DEFAULT_DOMAIN,
|
||||
"total_skills": len(skills),
|
||||
"skills": skills,
|
||||
}
|
||||
return index, errors
|
||||
|
||||
|
||||
def _comparable(index: dict) -> str:
|
||||
"""Serialize an index ignoring generated_at, so --check tolerates a re-run."""
|
||||
return json.dumps({k: v for k, v in index.items() if k != "generated_at"}, sort_keys=True)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--check", action="store_true",
|
||||
help="verify index.json matches the skills tree; do not write")
|
||||
parser.add_argument("--skills-dir", default="skills")
|
||||
parser.add_argument("--out", default="index.json")
|
||||
args = parser.parse_args()
|
||||
|
||||
if not os.path.isdir(args.skills_dir):
|
||||
print(f"ERROR: '{args.skills_dir}' not found. Run from the repository root.")
|
||||
return 1
|
||||
|
||||
index, errors = build_index(args.skills_dir)
|
||||
|
||||
for error in errors:
|
||||
print(f"ERROR {error}")
|
||||
if errors:
|
||||
print(f"\n{len(errors)} skill(s) could not be indexed.")
|
||||
return 1
|
||||
|
||||
if args.check:
|
||||
if not os.path.isfile(args.out):
|
||||
print(f"ERROR: {args.out} is missing. Run: python tools/generate-index.py")
|
||||
return 1
|
||||
with open(args.out, encoding="utf-8") as handle:
|
||||
current = json.load(handle)
|
||||
if _comparable(current) != _comparable(index):
|
||||
print(f"ERROR: {args.out} is out of date. Run: python tools/generate-index.py")
|
||||
return 1
|
||||
print(f"OK: {args.out} is up to date ({index['total_skills']} skills)")
|
||||
return 0
|
||||
|
||||
with open(args.out, "w", encoding="utf-8") as handle:
|
||||
json.dump(index, handle, separators=(",", ":"))
|
||||
|
||||
print(f"Updated {args.out}: {index['total_skills']} skills")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user