"""The bundled skills declare the moments they are for (milestone 458 step 5). Loading the reporting procedure is a report; loading the planning procedure is planning. Each SKILL.md says which moment it is for in its frontmatter (`metadata: moments:`), where an author editing the skill sees it — but the server that resolves a skill load to its moments never sees the plugin's files, so the same declaration ships as defaults in `moment_actions.BUNDLED_SKILL_MOMENTS`. Two copies drift unless something holds them together; this does, in both directions, and every skill has to make the choice rather than inherit silence. """ from __future__ import annotations import json import re from pathlib import Path from scribe.services import moment_actions as ma from scribe.services import moments ROOT = Path(__file__).resolve().parents[1] SKILLS = ROOT / "plugin" / "skills" MANIFEST = ROOT / "plugin" / ".claude-plugin" / "plugin.json" _FRONT = re.compile(r"\A---\n(.*?)\n---\n", re.S) _METADATA = re.compile(r"^metadata:[ \t]*\n((?:[ \t]+\S.*(?:\n|\Z))*)", re.M) _MOMENTS = re.compile(r"^[ \t]+moments:[ \t]*(.*)$", re.M) def declared(text: str) -> tuple[str, ...]: """The moments a SKILL.md's frontmatter declares; () when it declares none. Agent Skills metadata maps strings to strings, so several moments are one comma-separated value. """ front = _FRONT.match(text) meta = _METADATA.search(front.group(1)) if front else None line = _MOMENTS.search(meta.group(1)) if meta else None if not line: return () return tuple(m.strip() for m in line.group(1).split(",") if m.strip()) def _on_disk() -> dict[str, tuple[str, ...]]: return {p.parent.name: declared(p.read_text()) for p in sorted(SKILLS.glob("*/SKILL.md"))} def test_the_reader_finds_a_declaration_and_only_in_the_frontmatter(): """The guard below is only as good as this reader, so it must be able to say no: a declaration in the body, or none at all, reads as nothing.""" assert declared( "---\nname: x\ndescription: y\nmetadata:\n moments: work.plan, reply.report\n---\nbody\n" ) == ("work.plan", "reply.report") assert declared("---\nname: x\ndescription: y\n---\nmetadata:\n moments: work.plan\n") == () assert declared("---\nname: x\nmetadata:\n author: someone\n---\n") == () assert declared("no frontmatter\n") == () def test_every_bundled_skill_declares_the_moments_it_is_for(): silent = [name for name, found in _on_disk().items() if not found] assert not silent, ( f"{silent} declare no moment. Add `metadata:` / ` moments: ` to " f"the frontmatter (list_moments has the catalog) and the same entry to " f"moment_actions.BUNDLED_SKILL_MOMENTS" ) def test_the_frontmatter_and_the_shipped_defaults_agree(): assert _on_disk() == ma.BUNDLED_SKILL_MOMENTS, ( "a SKILL.md's `metadata: moments:` and moment_actions.BUNDLED_SKILL_MOMENTS " "disagree — the server reads only the latter, so change both together" ) def test_every_declared_moment_is_in_the_catalog(): bad = {name: found for name, found in _on_disk().items() if any(m not in moments.MOMENTS for m in found)} assert not bad, bad def test_the_defaults_qualify_skills_by_the_plugins_own_name(): """The harness names a plugin's skill `:`, so the defaults match only while this agrees with the manifest.""" assert json.loads(MANIFEST.read_text())["name"] == ma.BUNDLED_SKILL_PLUGIN