muse-agent-example/.claude/skills/db/scripts/sync_agent_registry.py
zizi 6728815174 框架(数据库): 落库 102 智能体/技能登记表 + 登记脚本(看板智能体空间数据源)
- example_agent_role(5 角色)+ example_skill(24 技能),可变表带公共列
- 登记表是 Git 侧配置权威的库内影子(类比 meta_function_chain),Git 仍是权威
- sync_agent_registry.py:从 .claude/agents/*.md、.claude/skills/*/SKILL.md 抽 frontmatter
  + 尽力抽取涉及表名,upsert 入库,幂等可重跑;已登记 5 角色 + 24 技能
2026-07-31 01:03:22 +08:00

98 lines
4.1 KiB
Python
Raw 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
"""智能体/技能登记脚本——把 Git 侧的 agent/skill 元数据影子进库,供看板只读。
落库设计 §2.10 的 A 方案:
- Git 侧(.claude/agents/*.md、.claude/skills/*/SKILL.md)仍是配置权威;
- 本脚本抽取 frontmatter(name/description/model/tools)+ 尽力抽取涉及的表名,
upsert 进 example_agent_role / example_skill;
- 看板只读登记表、不读 Git。幂等可重跑(upsert),配置变更后重跑即同步。
跑法(仓库根目录):.venv/bin/python .claude/skills/db/scripts/sync_agent_registry.py
"""
import json
import re
from pathlib import Path
from db import connect # 复用锁死的 DSN(与 db.py 同目录,脚本目录自动在 sys.path)
ROOT = Path(__file__).resolve().parents[4] # .claude/skills/db/scripts → 仓库根
AGENTS_DIR = ROOT / ".claude" / "agents"
SKILLS_DIR = ROOT / ".claude" / "skills"
TABLE_RE = re.compile(r"\b(muse_[a-z_]+|example_[a-z_]+)\b")
def parse_frontmatter(text: str) -> dict:
"""解析开头 --- 围栏内的 frontmatter 为 key->字符串(每行按第一个冒号切,值可含冒号/逗号)。"""
lines = text.splitlines()
if not lines or lines[0].strip() != "---":
return {}
fm = {}
for line in lines[1:]:
if line.strip() == "---":
break
if ":" in line:
k, _, v = line.partition(":")
fm[k.strip()] = v.strip()
return fm
def sync_roles(conn) -> int:
n = 0
for md in sorted(AGENTS_DIR.glob("*.md")):
fm = parse_frontmatter(md.read_text(encoding="utf-8"))
role = fm.get("name") or md.stem
desc = fm.get("description", "")
display = desc.split("——", 1)[0].strip() or None # "网文写手——…" 取破折号前作显示名
tools_raw = fm.get("tools")
tools = json.dumps([t.strip() for t in tools_raw.split(",") if t.strip()],
ensure_ascii=False) if tools_raw else None
conn.execute(
"""INSERT INTO example_agent_role
(role, display_name, model, tools, responsibility, source_ref, synced_at, creator, updater)
VALUES (%s,%s,%s,%s::jsonb,%s,%s,CURRENT_TIMESTAMP,'sync_agent_registry','sync_agent_registry')
ON CONFLICT (tenant_id, role) DO UPDATE SET
display_name=EXCLUDED.display_name, model=EXCLUDED.model, tools=EXCLUDED.tools,
responsibility=EXCLUDED.responsibility, source_ref=EXCLUDED.source_ref,
synced_at=CURRENT_TIMESTAMP, updater='sync_agent_registry'""",
(role, display, fm.get("model"), tools, desc or None, str(md.relative_to(ROOT))))
n += 1
return n
def sync_skills(conn) -> int:
n = 0
for sk in sorted(SKILLS_DIR.glob("*/SKILL.md")):
text = sk.read_text(encoding="utf-8")
fm = parse_frontmatter(text)
name = fm.get("name") or sk.parent.name
tables = sorted(set(TABLE_RE.findall(text))) # 尽力抽取涉及的表名(不区分读写,待人工核)
model_used = None
if "MiniMax" in text:
model_used = "MiniMax-M3"
elif re.search(r"\bopus\b", text):
model_used = "opus"
conn.execute(
"""INSERT INTO example_skill
(skill_name, purpose, reads, model_used, source_ref, synced_at, creator, updater)
VALUES (%s,%s,%s::jsonb,%s,%s,CURRENT_TIMESTAMP,'sync_agent_registry','sync_agent_registry')
ON CONFLICT (tenant_id, skill_name) DO UPDATE SET
purpose=EXCLUDED.purpose, reads=EXCLUDED.reads, model_used=EXCLUDED.model_used,
source_ref=EXCLUDED.source_ref, synced_at=CURRENT_TIMESTAMP, updater='sync_agent_registry'""",
(name, fm.get("description"),
json.dumps(tables, ensure_ascii=False) if tables else None,
model_used, str(sk.relative_to(ROOT))))
n += 1
return n
def main():
with connect() as conn:
nr = sync_roles(conn)
ns = sync_skills(conn)
conn.commit()
print(f"已登记 {nr} 个角色、{ns} 个技能 → example_agent_role / example_skill")
if __name__ == "__main__":
main()