Fourteen more test files: docstrings state the failure each guard defends as a present-tense shape instead of the incident that found it, and issue, milestone and rule numbers leave comments and assertion messages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
109 lines
4.2 KiB
Python
109 lines
4.2 KiB
Python
"""Every bundled skill keeps the shape Agent Skills can load well.
|
|
|
|
WHY THIS EXISTS
|
|
|
|
Anthropic's skill authoring guidance
|
|
(platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices):
|
|
keep the SKILL.md body under 500 lines, split into reference files as it
|
|
nears that, and link each reference file ONE level deep from SKILL.md —
|
|
Claude may only preview a file reached through another reference file.
|
|
|
|
using-scribe grows because every new practice lands there. What matters on
|
|
most turns stays in SKILL.md; what matters at one moment (placing a rule, a
|
|
missed retrieval, starting a project) lives in files SKILL.md names with the
|
|
moment to open them. These tests keep that shape from quietly undoing
|
|
itself.
|
|
|
|
WHAT IT PINS
|
|
|
|
1. A BUDGET BELOW THE GUIDELINE. 500 is where loading degrades; the budget
|
|
sits under it so the next addition is a choice about what moves out, not
|
|
a squeeze past the line.
|
|
2. EVERY REFERENCE FILE IS LINKED FROM SKILL.md. A file nothing links is
|
|
never read — it states guidance that reaches nobody.
|
|
3. NO REFERENCE FILE LINKS ANOTHER. One level deep, per the guidance.
|
|
4. A LONG REFERENCE FILE OPENS WITH ITS CONTENTS, so a partial read still
|
|
shows what the file covers.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import pathlib
|
|
import re
|
|
|
|
SKILLS = pathlib.Path(__file__).resolve().parents[1] / "plugin" / "skills"
|
|
|
|
SKILL_LINE_BUDGET = 350
|
|
TOC_AFTER_LINES = 100
|
|
_MD_LINK = re.compile(r"\]\(([^)#\s]+\.md)\)")
|
|
|
|
|
|
def _skill_dirs() -> list[pathlib.Path]:
|
|
return sorted(p.parent for p in SKILLS.glob("*/SKILL.md"))
|
|
|
|
|
|
def _refs(folder: pathlib.Path) -> list[pathlib.Path]:
|
|
return sorted(p for p in folder.glob("*.md") if p.name != "SKILL.md")
|
|
|
|
|
|
def over_budget(texts: dict[str, str], budget: int) -> list[str]:
|
|
return [f"{k}: {len(t.splitlines())} lines" for k, t in texts.items()
|
|
if len(t.splitlines()) > budget]
|
|
|
|
|
|
def unlinked(skill_md: str, ref_names: list[str]) -> list[str]:
|
|
linked = set(_MD_LINK.findall(skill_md))
|
|
return [n for n in ref_names if n not in linked]
|
|
|
|
|
|
def nested(refs: dict[str, str]) -> list[str]:
|
|
return [f"{name} → {target}" for name, text in refs.items()
|
|
for target in _MD_LINK.findall(text)]
|
|
|
|
|
|
def missing_contents(refs: dict[str, str], after: int) -> list[str]:
|
|
return [name for name, text in refs.items()
|
|
if len(text.splitlines()) > after and "## Contents" not in text]
|
|
|
|
|
|
def test_every_skill_md_fits_its_budget():
|
|
found = over_budget({d.name: (d / "SKILL.md").read_text() for d in _skill_dirs()},
|
|
SKILL_LINE_BUDGET)
|
|
assert not found, (
|
|
f"SKILL.md over {SKILL_LINE_BUDGET} lines: {found}. Move what matters at "
|
|
f"one moment into a reference file SKILL.md links with that moment, "
|
|
f"rather than raising the budget toward 500."
|
|
)
|
|
|
|
|
|
def test_every_reference_file_is_linked_from_skill_md():
|
|
found = [f"{d.name}/{n}" for d in _skill_dirs()
|
|
for n in unlinked((d / "SKILL.md").read_text(), [p.name for p in _refs(d)])]
|
|
assert not found, (
|
|
f"reference files no SKILL.md links: {found}. Nothing reads a file "
|
|
f"nothing names — link it from SKILL.md with the moment to open it."
|
|
)
|
|
|
|
|
|
def test_reference_files_are_one_level_deep():
|
|
found = [f"{d.name}/{x}" for d in _skill_dirs()
|
|
for x in nested({p.name: p.read_text() for p in _refs(d)})]
|
|
assert not found, (
|
|
f"reference files linking other files: {found}. Link each from SKILL.md "
|
|
f"directly; a file reached through another may only be previewed."
|
|
)
|
|
|
|
|
|
def test_long_reference_files_open_with_contents():
|
|
found = [f"{d.name}/{n}" for d in _skill_dirs()
|
|
for n in missing_contents({p.name: p.read_text() for p in _refs(d)},
|
|
TOC_AFTER_LINES)]
|
|
assert not found, f"reference files over {TOC_AFTER_LINES} lines with no '## Contents': {found}"
|
|
|
|
|
|
def test_the_guards_can_fail():
|
|
"""Each guard bites on the failure it exists for."""
|
|
assert over_budget({"s": "x\n" * 400}, SKILL_LINE_BUDGET) == ["s: 400 lines"]
|
|
assert unlinked("see [a](a.md)", ["a.md", "b.md"]) == ["b.md"]
|
|
assert nested({"a.md": "then [b](b.md)"}) == ["a.md → b.md"]
|
|
assert missing_contents({"a.md": "x\n" * 150}, TOC_AFTER_LINES) == ["a.md"]
|