#!/usr/bin/env python3 """ Rebuild index.json from SKILL.md frontmatter in this repository. Usage: python3 scripts/rebuild_index.py python3 scripts/rebuild_index.py --check # exit 1 if index.json is out of date The index is the canonical machine-readable manifest of all skills in this repository. Regenerate it whenever a SKILL.md is added, removed, or has its frontmatter changed. Commit index.json together with the SKILL.md change. """ from __future__ import annotations import argparse import json import re import sys from pathlib import Path REPO_ROOT = Path(__file__).resolve().parent.parent SKILLS_ROOT = REPO_ROOT / "skills" INDEX_PATH = REPO_ROOT / "index.json" FRONTMATTER_RE = re.compile(r"^---\n(.*?)\n---\n", re.DOTALL) KEY_RE = re.compile(r"^([A-Za-z_][A-Za-z0-9_]*):\s*(.+?)(?=\n[A-Za-z_]|\Z)", re.MULTILINE | re.DOTALL) def _grab(frontmatter: str, key: str) -> str: m = re.search(rf"^{key}:\s*(.+?)(?=\n[A-Za-z_]|\Z)", frontmatter, re.MULTILINE | re.DOTALL) if not m: return "" val = m.group(1).strip() # strip YAML block indicators and quotes val = val.strip(">").strip("|").strip() if (val.startswith('"') and val.endswith('"')) or (val.startswith("'") and val.endswith("'")): val = val[1:-1] return val.strip() def _grab_tags(frontmatter: str) -> list[str]: m = re.search(r"tags:\s*\[(.*?)\]", frontmatter, re.DOTALL) if not m: return [] return [t.strip().strip('"').strip("'") for t in m.group(1).split(",") if t.strip()] def build_index() -> dict: skills: list[dict] = [] for skill_md in sorted(SKILLS_ROOT.rglob("SKILL.md")): rel = skill_md.relative_to(SKILLS_ROOT) parts = rel.parts category = parts[0] if len(parts) > 1 else "root" name = parts[-2] if len(parts) > 1 else "root" text = skill_md.read_text(encoding="utf-8") fm_match = FRONTMATTER_RE.match(text) frontmatter = fm_match.group(1) if fm_match else "" desc = _grab(frontmatter, "description").replace("\n", " ").strip() version = _grab(frontmatter, "version") author = _grab(frontmatter, "author") tags = _grab_tags(frontmatter) all_files = sorted( str(p.relative_to(SKILLS_ROOT)) for p in skill_md.parent.rglob("*") if p.is_file() ) skills.append( { "category": category, "name": name, "path": str(rel), "description": desc, "version": version, "author": author, "tags": tags, "files": all_files, } ) return { "repository": "Radix-Skills", "version": 1, "description": ( "Delt AI-skill-bibliotek for Radix — bruges af alle medarbejderes " "AI-agenter (Hermes, Codex m.fl.) til dansk regnskab/ERP/data/devops." ), "categories": sorted({s["category"] for s in skills}), "skill_count": len(skills), "skills": skills, } def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--check", action="store_true", help="Exit 1 if index.json is stale") args = parser.parse_args() new_index = build_index() new_json = json.dumps(new_index, ensure_ascii=False, indent=2) + "\n" if args.check: if not INDEX_PATH.exists(): print("index.json does not exist", file=sys.stderr) return 1 current = INDEX_PATH.read_text(encoding="utf-8") if current != new_json: print("index.json is out of date — run scripts/rebuild_index.py", file=sys.stderr) return 1 return 0 INDEX_PATH.write_text(new_json, encoding="utf-8") print( f"Wrote {INDEX_PATH.relative_to(REPO_ROOT)} with {new_index['skill_count']} skills " f"across {len(new_index['categories'])} categories: {', '.join(new_index['categories'])}" ) return 0 if __name__ == "__main__": sys.exit(main())