"""Skill 注册表 (Anthropic 标准格式)。 每个 skill 是 skills// 目录,内含 SKILL.md(带 frontmatter)+ 可选的 references/、scripts/、assets/。启动时只读 frontmatter 做 discovery,完整 SKILL.md 和 references 由 agent 按需加载(渐进披露)。 """ from __future__ import annotations import re from dataclasses import dataclass from pathlib import Path from typing import Dict, Optional, Tuple import yaml _FRONTMATTER_RE = re.compile(r"^---\n(.*?)\n---\n?", re.DOTALL) def parse_frontmatter(text: str) -> Tuple[dict, str]: """解析 markdown 顶部的 YAML frontmatter。返回 (meta, body)。""" m = _FRONTMATTER_RE.match(text) if not m: return {}, text meta = yaml.safe_load(m.group(1)) or {} if not isinstance(meta, dict): meta = {} return meta, text[m.end():] @dataclass class Skill: name: str description: str skill_dir: Path @property def skill_md(self) -> Path: return self.skill_dir / "SKILL.md" def full_content(self) -> str: return self.skill_md.read_text(encoding="utf-8") @classmethod def from_dir(cls, skill_dir: Path) -> Optional["Skill"]: md = skill_dir / "SKILL.md" if not md.exists(): return None meta, _ = parse_frontmatter(md.read_text(encoding="utf-8")) name = meta.get("name") or skill_dir.name desc = meta.get("description") or "" if not desc: return None # description 是 discovery 的关键,缺了不收 return cls(name=name, description=desc, skill_dir=skill_dir) class SkillRegistry: def __init__(self, skills_dir: Path) -> None: self.skills_dir = Path(skills_dir) self.skills: Dict[str, Skill] = {} self._scan() def _scan(self) -> None: if not self.skills_dir.exists(): return for child in sorted(self.skills_dir.iterdir()): if not child.is_dir(): continue skill = Skill.from_dir(child) if skill is not None: self.skills[skill.name] = skill def discovery_block(self) -> str: """启动时注入 system prompt 的 skill 列表(name + description)。""" if not self.skills: return "" lines = [f"- **{s.name}**: {s.description}" for s in self.skills.values()] return "\n".join(lines) def get(self, name: str) -> Optional[Skill]: return self.skills.get(name)