"""Issue #42 stages 1-2: the SKILL.md code-block inventory is data, and the docs follow it. tests/data/skill_code_blocks.json lists every fenced block of the eight SKILL.md files with its class - (b) a procedure no script offers yet, (c) an example, call or diagram - and the (a) blocks removed because the entry script already does them. These tests fail when a SKILL.md block changes without the inventory, when an (a) block comes back, or when an exit-code table drifts from the entry script. """ import hashlib import json import re from pathlib import Path import pytest REPO = Path(__file__).resolve().parent.parent SKILLS = REPO / ".agents" / "skills" DATA = json.loads((Path(__file__).parent / "data" / "skill_code_blocks.json").read_text(encoding="utf-8")) SKILL_NAMES = sorted(p.parent.name for p in SKILLS.glob("multi-agent-mux-*/SKILL.md")) KINDS = {"invocation", "example", "diagram", "procedure", "verification"} _OPEN = re.compile(r"^(\s*)(`{3,}|~{3,})\s*([^`\s]*)") def parse_blocks(text): """Fenced blocks as (start, end, lang, sha, body); 1-based fence lines. Indented fences (inside list items) count: they render as code blocks too. A fence closes on a line of the same character, at least as long.""" lines = text.split("\n") out, i = [], 0 while i < len(lines): m = _OPEN.match(lines[i]) if not m: i += 1 continue fence = m.group(2) close = re.compile(r"^\s*" + re.escape(fence[0]) + "{%d,}\\s*$" % len(fence)) j = i + 1 while j < len(lines) and not close.match(lines[j]): j += 1 body = "\n".join(lines[i + 1:j]) out.append((i + 1, j + 1, m.group(3) or "-", hashlib.sha256(body.encode()).hexdigest()[:12], body)) i = j + 1 return out def skill_text(skill): return (SKILLS / skill / "SKILL.md").read_text(encoding="utf-8") _HEREDOC = re.compile(r"(?