Files
ai-proj-helper/scripts/generate-catalog.py
johnQ d43b414fc9 feat(skill): 技能治理 v0.2 —— 分桶/调用模式/校验/安全/清单/安装矩阵
- 新增 SCOPE.md 收录门槛 + .out-of-scope/ 拒绝档案
- 生命周期分桶 promoted/beta/deprecated,marketplace 只发布 promoted
- 调用模式 model-invoked/user-invoked(user-invoked 不占模型目录)
- 新增脚本 validate-skills.py / generate-catalog.py / scan-skill.sh(+allowlist)
- 新增 beta 技能 skill-creator、ai-proj-helper-entry、verification-before-completion(各带 evals)
- install-skills.sh: --agent dsh / --link / --audit / --include-beta / --no-validate
- 融合:dev-review 接收评审意见、req-design 依赖边、agent-swarm 并行编排、dev-test TDD
- catalog.json 61 个技能(58 promoted / 3 beta);validate 0 错误;scan 0 高危;现有测试通过
2026-10-31 22:03:13 +08:00

107 lines
3.5 KiB
Python
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Generate catalog.json — the reviewable list of every skill in this repo.
Usage: python3 scripts/generate-catalog.py [--check]
(default) write catalog.json
--check fail if catalog.json is out of date (for CI)
"""
from __future__ import annotations
import hashlib
import json
import re
import sys
from datetime import datetime, timezone
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
CATALOG = ROOT / "catalog.json"
def frontmatter(text: str) -> dict:
if not text.startswith("---"):
return {}
end = text.find("\n---", 3)
if end == -1:
return {}
data: dict = {}
metadata: dict = {}
key = None
for line in text[3:end].splitlines():
if not line.strip():
continue
m = re.match(r"^([A-Za-z0-9_-]+):\s*(.*)$", line)
if m:
key = m.group(1)
data[key] = m.group(2).strip().strip('"').strip("'")
continue
m = re.match(r"^\s+([A-Za-z0-9_-]+):\s*(.*)$", line)
if m and key == "metadata":
metadata[m.group(1)] = m.group(2).strip().strip('"').strip("'")
if metadata:
data["metadata"] = metadata
return data
def build() -> dict:
skills = []
for plugin_json in sorted(ROOT.glob("skills-*/*-plugin/.claude-plugin/plugin.json")):
plugin_dir = plugin_json.parent.parent
meta = json.loads(plugin_json.read_text(encoding="utf-8"))
skill_files = sorted((plugin_dir / "skills").rglob("SKILL.md"))
if not skill_files:
continue
skill_md = skill_files[0]
fm = frontmatter(skill_md.read_text(encoding="utf-8", errors="replace"))
desc = fm.get("description", "")
body = skill_md.read_text(encoding="utf-8", errors="replace")
skills.append(
{
"name": meta.get("install_name") or fm.get("name"),
"category": meta.get("dir_category"),
"version": meta.get("version"),
"status": meta.get("status", "promoted"),
"invocation": (fm.get("metadata") or {}).get("invocation", "model-invoked"),
"description": desc,
"description_len": len(desc),
"path": str(skill_md.relative_to(ROOT)),
"digest": hashlib.sha256(body.encode("utf-8")).hexdigest()[:16],
}
)
counts: dict = {}
for skill in skills:
key = f"{skill['status']}/{skill['invocation']}"
counts[key] = counts.get(key, 0) + 1
return {
"repo": "ai-proj-helper",
"generated_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
"total": len(skills),
"counts": counts,
"skills": skills,
}
def main() -> int:
catalog = build()
text = json.dumps(catalog, ensure_ascii=False, indent=2) + "\n"
if "--check" in sys.argv:
if not CATALOG.is_file():
print("catalog.json 不存在,请运行 python3 scripts/generate-catalog.py")
return 1
current = json.loads(CATALOG.read_text(encoding="utf-8"))
current.pop("generated_at", None)
fresh = dict(catalog)
fresh.pop("generated_at", None)
if current != fresh:
print("catalog.json 已过期,请重新生成")
return 1
print("catalog.json 是最新的")
return 0
CATALOG.write_text(text, encoding="utf-8")
print(f"写入 {CATALOG.relative_to(ROOT)}:{catalog['total']} 个技能 {catalog['counts']}")
return 0
if __name__ == "__main__":
raise SystemExit(main())