- Add EvaluationHook for post-execution agent evaluation - Add SkillAdaptationHook for dynamic skill adaptation - Add team/ directory with team coordination logic - Add TEAM_PIPELINE.yaml for smoke_fullstack pipeline config - Update RuntimeView, TraderView and RuntimeSettingsPanel UI - Add runtimeApi and websocket services - Add runtime_state.json to smoke_fullstack state Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
84 lines
2.5 KiB
Python
84 lines
2.5 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Skill metadata parsing helpers for SKILL.md files."""
|
|
|
|
from dataclasses import dataclass, field
|
|
from pathlib import Path
|
|
from typing import List
|
|
|
|
import yaml
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class SkillMetadata:
|
|
"""Parsed metadata for a skill package."""
|
|
|
|
skill_name: str
|
|
path: Path
|
|
source: str
|
|
name: str
|
|
description: str
|
|
version: str = ""
|
|
tools: List[str] = field(default_factory=list)
|
|
allowed_tools: List[str] = field(default_factory=list)
|
|
denied_tools: List[str] = field(default_factory=list)
|
|
|
|
|
|
def parse_skill_metadata(skill_dir: Path, source: str) -> SkillMetadata:
|
|
"""Parse SKILL.md frontmatter with a forgiving schema."""
|
|
skill_name = skill_dir.name
|
|
skill_file = skill_dir / "SKILL.md"
|
|
if not skill_file.exists():
|
|
return SkillMetadata(
|
|
skill_name=skill_name,
|
|
path=skill_dir,
|
|
source=source,
|
|
name=skill_name,
|
|
description="",
|
|
)
|
|
|
|
raw = skill_file.read_text(encoding="utf-8").strip()
|
|
frontmatter = {}
|
|
body = raw
|
|
if raw.startswith("---"):
|
|
parts = raw.split("---", 2)
|
|
if len(parts) >= 3:
|
|
try:
|
|
frontmatter = yaml.safe_load(parts[1].strip()) or {}
|
|
except yaml.YAMLError:
|
|
frontmatter = {}
|
|
body = parts[2].strip()
|
|
if not isinstance(frontmatter, dict):
|
|
frontmatter = {}
|
|
|
|
description = str(frontmatter.get("description") or "").strip()
|
|
if not description and body:
|
|
description = body.splitlines()[0].strip().lstrip("#").strip()
|
|
|
|
return SkillMetadata(
|
|
skill_name=skill_name,
|
|
path=skill_dir,
|
|
source=source,
|
|
name=str(frontmatter.get("name") or skill_name).strip() or skill_name,
|
|
description=description,
|
|
version=str(frontmatter.get("version") or "").strip(),
|
|
tools=_string_list(frontmatter.get("tools")),
|
|
allowed_tools=_string_list(frontmatter.get("allowed_tools")),
|
|
denied_tools=_string_list(frontmatter.get("denied_tools")),
|
|
)
|
|
|
|
|
|
def _string_list(value) -> List[str]:
|
|
if isinstance(value, str):
|
|
item = value.strip()
|
|
return [item] if item else []
|
|
if not isinstance(value, list):
|
|
return []
|
|
seen: List[str] = []
|
|
for item in value:
|
|
if not isinstance(item, str):
|
|
continue
|
|
normalized = item.strip()
|
|
if normalized and normalized not in seen:
|
|
seen.append(normalized)
|
|
return seen
|