Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
A comprehensive collection of Agent Skills for context engineering, multi-agent architectures, and production agent systems.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
researcher/scripts/skill_health.py
1#!/usr/bin/env python32"""Deterministic per-skill health metrics.34Produces a quality score per skill plus a corpus aggregate. Catches drift before5users notice: missing sections, stale claims, dead internal links, weak gotchas.67Run with no arguments for the default report. Use --json for machine output.8Output is written to researcher/reports/skill-health.json by default and can be9piped into the daily snapshot for longitudinal trend tracking.1011This script is intentionally deterministic. It does not call any LLM and does12not make outbound HTTP requests unless --check-urls is set (off by default).13"""1415from __future__ import annotations1617import argparse18import json19import re20import sys21import urllib.error22import urllib.request23from dataclasses import asdict, dataclass, field24from pathlib import Path25from typing import Any262728ROOT = Path(__file__).resolve().parents[2]29SKILLS_DIR = ROOT / "skills"30CLAIMS_FILE = ROOT / "researcher" / "claims" / "index.jsonl"31MECHANISMS_FILE = ROOT / "researcher" / "mechanisms" / "registry.jsonl"32ACTIVATION_FILE = ROOT / "researcher" / "fixtures" / "activation-cases.jsonl"33DEFAULT_OUTPUT = ROOT / "researcher" / "reports" / "skill-health.json"34HISTORY_FILE = ROOT / "researcher" / "reports" / "skill-health-history.jsonl"3536REQUIRED_SECTIONS = [37"## When to Activate",38"## Core Concepts",39"## Practical Guidance",40"## Gotchas",41"## Integration",42"## References",43]4445NUMERIC_CLAIM_PATTERNS = [46re.compile(r"\b\d+(?:\.\d+)?\s*%"),47re.compile(r"\b\d+(?:\.\d+)?\s*x\b"),48re.compile(r"\b\d+(?:\.\d+)?\s*ms\b"),49re.compile(r"\b\d+(?:\.\d+)?\s*s(?:ec|econds)?\b"),50re.compile(r"\b\d+(?:\.\d+)?\s*tokens?\b", re.IGNORECASE),51re.compile(r"\b\d+(?:k|K|M|B|x)\b"),52]5354BENCHMARK_NAMES = {55"LoCoMo",56"LongMemEval",57"BrowseComp",58"SWE-bench",59"RULER",60"DMR",61"HotPotQA",62"MMLU",63"GSM8K",64"HumanEval",65}6667CLAIM_ID_PATTERN = re.compile(r"\bclaim-[a-z0-9][a-z0-9-]*[a-z0-9]\b")68INTERNAL_SKILL_LINK = re.compile(r"\[([^\]]+)\]\((?:\.\./)?skills/([a-z0-9-]+)/(?:[^)]*)\)")69EXTERNAL_LINK = re.compile(r"\[(?:[^\]]+)\]\((https?://[^)]+)\)")70FENCE_LINE = re.compile(r"^\s*```")717273@dataclass74class SkillHealth:75name: str76path: str77line_count: int = 078line_count_ok: bool = True79frontmatter_valid: bool = True80frontmatter_issues: list[str] = field(default_factory=list)81missing_sections: list[str] = field(default_factory=list)82gotcha_count: int = 083code_example_count: int = 084internal_links_total: int = 085internal_links_resolved: int = 086external_link_count: int = 087external_link_results: list[dict[str, Any]] = field(default_factory=list)88numeric_claims_total: int = 089numeric_claims_with_id: int = 090claim_ids_referenced: list[str] = field(default_factory=list)91claim_ids_unknown: list[str] = field(default_factory=list)92mechanism_count: int = 093activation_case_count: int = 094score: float = 0.095flagged: bool = False969798def load_jsonl(path: Path) -> list[dict[str, Any]]:99if not path.exists():100return []101records: list[dict[str, Any]] = []102for line in path.read_text(encoding="utf-8").splitlines():103if not line.strip():104continue105try:106records.append(json.loads(line))107except json.JSONDecodeError:108continue109return records110111112def parse_frontmatter(text: str) -> tuple[dict[str, str], list[str]]:113issues: list[str] = []114if not text.startswith("---\n"):115return {}, ["missing opening frontmatter delimiter"]116end = text.find("\n---", 4)117if end == -1:118return {}, ["missing closing frontmatter delimiter"]119data: dict[str, str] = {}120for raw in text[4:end].splitlines():121if not raw.strip() or raw.startswith(" "):122continue123if ":" not in raw:124continue125key, value = raw.split(":", 1)126data[key.strip()] = value.strip().strip('"').strip("'")127return data, issues128129130def count_gotchas(text: str) -> int:131match = re.search(r"^## Gotchas\s*\n(.+?)(?=\n## |\Z)", text, flags=re.DOTALL | re.MULTILINE)132if not match:133return 0134body = match.group(1)135return len(re.findall(r"^\s*\d+\.\s+", body, flags=re.MULTILINE))136137138def count_code_examples(text: str) -> int:139fence_open = 0140for line in text.splitlines():141if FENCE_LINE.match(line):142fence_open += 1143return fence_open // 2144145146def count_numeric_claims(text: str) -> int:147total = 0148for pattern in NUMERIC_CLAIM_PATTERNS:149total += len(pattern.findall(text))150for name in BENCHMARK_NAMES:151total += text.count(name)152return total153154155def collect_internal_links(text: str) -> tuple[int, int]:156matches = INTERNAL_SKILL_LINK.findall(text)157total = len(matches)158resolved = 0159for _, target in matches:160if (SKILLS_DIR / target / "SKILL.md").exists():161resolved += 1162return total, resolved163164165def collect_external_links(text: str, check_urls: bool, timeout: float) -> tuple[int, list[dict[str, Any]]]:166urls = EXTERNAL_LINK.findall(text)167results: list[dict[str, Any]] = []168if not check_urls:169return len(urls), results170for url in urls:171try:172request = urllib.request.Request(url, method="HEAD", headers={"User-Agent": "skill-health/0.1"})173with urllib.request.urlopen(request, timeout=timeout) as response:174results.append({"url": url, "status": response.status})175except urllib.error.HTTPError as exc:176results.append({"url": url, "status": exc.code, "error": str(exc)})177except (urllib.error.URLError, TimeoutError) as exc:178results.append({"url": url, "status": "unreachable", "error": str(exc)})179return len(urls), results180181182def collect_claim_ids(text: str, known_claim_ids: set[str]) -> tuple[list[str], list[str], int]:183found = list(set(CLAIM_ID_PATTERN.findall(text)))184unknown = [cid for cid in found if cid not in known_claim_ids]185return sorted(found), sorted(unknown), len(found)186187188def normalize(value: int, target: int) -> float:189if target <= 0:190return 0.0191return min(1.0, value / target)192193194def compute_score(record: SkillHealth) -> float:195required_section_score = (len(REQUIRED_SECTIONS) - len(record.missing_sections)) / len(REQUIRED_SECTIONS)196gotcha_score = normalize(record.gotcha_count, target=3)197code_score = normalize(record.code_example_count, target=2)198if record.internal_links_total:199internal_score = record.internal_links_resolved / record.internal_links_total200else:201internal_score = 1.0202if record.numeric_claims_total:203claim_score = record.numeric_claims_with_id / record.numeric_claims_total204else:205claim_score = 1.0206activation_score = 1.0 if record.activation_case_count >= 1 else 0.5207mechanism_score = 1.0 if record.mechanism_count >= 1 else 0.5208frontmatter_score = 1.0 if record.frontmatter_valid else 0.0209210return round(2110.20 * required_section_score212+ 0.15 * gotcha_score213+ 0.10 * code_score214+ 0.15 * internal_score215+ 0.10 * activation_score216+ 0.15 * claim_score217+ 0.10 * mechanism_score218+ 0.05 * frontmatter_score,2194,220)221222223def evaluate_skill(224skill_dir: Path,225known_claim_ids: set[str],226mechanism_owners: dict[str, int],227activation_owners: dict[str, int],228check_urls: bool,229url_timeout: float,230) -> SkillHealth:231skill_file = skill_dir / "SKILL.md"232record = SkillHealth(233name=skill_dir.name,234path=str(skill_file.relative_to(ROOT)),235)236if not skill_file.exists():237record.frontmatter_valid = False238record.frontmatter_issues.append("SKILL.md missing")239record.flagged = True240return record241242text = skill_file.read_text(encoding="utf-8")243lines = text.splitlines()244record.line_count = len(lines)245record.line_count_ok = record.line_count <= 500246247frontmatter, issues = parse_frontmatter(text)248record.frontmatter_issues = issues249name = frontmatter.get("name", "")250description = frontmatter.get("description", "")251if not name:252issues.append("missing name")253elif name != skill_dir.name:254issues.append(f"name '{name}' does not match directory '{skill_dir.name}'")255if not description:256issues.append("missing description")257elif len(description) > 1024:258issues.append("description exceeds 1024 characters")259if re.search(r"\b(I can|Use me|You can use this)\b", description):260issues.append("description may not be third person")261record.frontmatter_valid = not issues and bool(name) and bool(description)262263for section in REQUIRED_SECTIONS:264if section not in text:265record.missing_sections.append(section)266267record.gotcha_count = count_gotchas(text)268record.code_example_count = count_code_examples(text)269record.internal_links_total, record.internal_links_resolved = collect_internal_links(text)270record.external_link_count, record.external_link_results = collect_external_links(text, check_urls, url_timeout)271272record.numeric_claims_total = count_numeric_claims(text)273found, unknown, with_id = collect_claim_ids(text, known_claim_ids)274record.claim_ids_referenced = found275record.claim_ids_unknown = unknown276record.numeric_claims_with_id = with_id277278record.mechanism_count = mechanism_owners.get(skill_dir.name, 0)279record.activation_case_count = activation_owners.get(skill_dir.name, 0)280281record.score = compute_score(record)282record.flagged = record.score < 0.75 or not record.line_count_ok or bool(record.missing_sections) or not record.frontmatter_valid283return record284285286def build_report(check_urls: bool, url_timeout: float) -> dict[str, Any]:287if not SKILLS_DIR.exists():288return {289"ok": False,290"error": f"skills directory missing at {SKILLS_DIR}",291"skills": [],292}293294claims = load_jsonl(CLAIMS_FILE)295known_claim_ids = {record.get("claim_id") for record in claims if record.get("claim_id")}296297mechanisms = load_jsonl(MECHANISMS_FILE)298mechanism_owners: dict[str, int] = {}299for entry in mechanisms:300owner = entry.get("owning_skill")301if isinstance(owner, str):302mechanism_owners[owner] = mechanism_owners.get(owner, 0) + 1303304activation_owners: dict[str, int] = {}305for entry in load_jsonl(ACTIVATION_FILE):306owner = entry.get("expected_primary_skill")307if isinstance(owner, str):308activation_owners[owner] = activation_owners.get(owner, 0) + 1309310skills: list[SkillHealth] = []311for skill_dir in sorted(p for p in SKILLS_DIR.iterdir() if p.is_dir()):312skills.append(313evaluate_skill(314skill_dir,315known_claim_ids,316mechanism_owners,317activation_owners,318check_urls,319url_timeout,320)321)322323scores = [record.score for record in skills]324corpus_score = round(sum(scores) / len(scores), 4) if scores else 0.0325flagged = sum(1 for record in skills if record.flagged)326327return {328"ok": flagged == 0,329"summary": {330"skill_count": len(skills),331"corpus_score": corpus_score,332"min_score": min(scores) if scores else 0.0,333"max_score": max(scores) if scores else 0.0,334"flagged": flagged,335"known_claim_ids": len(known_claim_ids),336"mechanism_owners": mechanism_owners,337"activation_owners": activation_owners,338},339"skills": [asdict(record) for record in skills],340}341342343def main() -> int:344parser = argparse.ArgumentParser(description="Deterministic skill health report")345parser.add_argument("--json", action="store_true", help="print machine-readable JSON to stdout")346parser.add_argument("--output", type=Path, default=DEFAULT_OUTPUT, help="path to write the JSON report")347parser.add_argument("--check-urls", action="store_true", help="perform HEAD requests on external URLs")348parser.add_argument("--url-timeout", type=float, default=10.0, help="seconds per external HEAD request")349parser.add_argument("--strict", action="store_true", help="exit non-zero if any skill is flagged")350parser.add_argument("--no-history", action="store_true", help="do not append a one-line summary to the history file")351args = parser.parse_args()352353report = build_report(args.check_urls, args.url_timeout)354args.output.parent.mkdir(parents=True, exist_ok=True)355args.output.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8")356357if not args.no_history:358HISTORY_FILE.parent.mkdir(parents=True, exist_ok=True)359history_entry = {360"ok": report.get("ok"),361"summary": report.get("summary"),362}363with HISTORY_FILE.open("a", encoding="utf-8") as handle:364handle.write(json.dumps(history_entry, sort_keys=True) + "\n")365366if args.json:367print(json.dumps(report, indent=2))368else:369summary = report.get("summary", {})370print(371f"Skill health: corpus={summary.get('corpus_score', 0.0)} "372f"min={summary.get('min_score', 0.0)} "373f"flagged={summary.get('flagged', 0)} "374f"skills={summary.get('skill_count', 0)}"375)376for record in report.get("skills", []):377marker = "FLAG" if record["flagged"] else "ok "378print(f" {marker} {record['name']:<28} score={record['score']:.3f} lines={record['line_count']}")379print(f"Wrote report to {args.output.relative_to(ROOT)}")380381if args.strict and not report.get("ok"):382return 1383return 0384385386if __name__ == "__main__":387sys.exit(main())388