from __future__ import annotations from pathlib import Path import yaml from app.agents.skills.types import Skill, SkillMetadata, SkillSource class SkillLoader: """SKILL.md 파일 파싱 및 로딩.""" @staticmethod def parse_skill_file(content: str, path: str, source: SkillSource) -> Skill: """YAML frontmatter + markdown body를 파싱하여 Skill 객체 반환.""" frontmatter, instructions = SkillLoader._split_frontmatter(content) meta = yaml.safe_load(frontmatter) or {} return Skill( name=meta.get("name", ""), description=meta.get("description", ""), path=path, source=source, instructions=instructions.strip(), ) @staticmethod def load_from_path(path: str | Path, source: SkillSource) -> Skill: """파일 경로에서 Skill을 로드.""" p = Path(path) content = p.read_text(encoding="utf-8") return SkillLoader.parse_skill_file(content, str(p), source) @staticmethod def extract_metadata(path: str | Path, source: SkillSource) -> SkillMetadata: """frontmatter만 파싱하여 경량 메타데이터 반환.""" p = Path(path) content = p.read_text(encoding="utf-8") frontmatter, _ = SkillLoader._split_frontmatter(content) meta = yaml.safe_load(frontmatter) or {} return SkillMetadata( name=meta.get("name", ""), description=meta.get("description", ""), path=str(p), source=source, ) @staticmethod def _split_frontmatter(content: str) -> tuple[str, str]: """--- 마커 사이의 YAML frontmatter와 나머지 body를 분리.""" stripped = content.strip() if not stripped.startswith("---"): return "", content # 두 번째 --- 찾기 end_idx = stripped.find("---", 3) if end_idx == -1: return "", content frontmatter = stripped[3:end_idx].strip() body = stripped[end_idx + 3 :] return frontmatter, body