Initial import: 10 Radix skills across 3 categories
Kategoriserer danske regnskabs-/ERP-skills, ERP-data-afstemnings-
skill og to devops-workflows (gitea-issue-agent, kanban-workflows)
som Radix-medlemmers AI-agenter kan dele.
Struktur:
skills/<kategori>/<skill-navn>/SKILL.md
skills/<kategori>/<skill-navn>/{references,templates,scripts}/
Indekseret af index.json (genereret af scripts/rebuild_index.py).
Kør `python3 scripts/rebuild_index.py --check` i CI for at fange
synkroniseringsfejl.
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
#!/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())
|
||||
Reference in New Issue
Block a user