Add machine-readable review contracts (#182)

Generate a deterministic action-skill index from frontmatter, publish structural schemas for orchestration and findings, and validate flat review composition in CI.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Co-authored-by: dayland <dayland@microsoft.com>
Copilot-Session: 76eb42c4-2acd-4f9c-a898-f4a44f9d46f7
This commit is contained in:
dayland 2026-09-15 10:27:40 +02:00 • committed by GitHub
parent 852a676285
commit b74967bc5b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 803 additions and 3 deletions

View file

@ -381,6 +381,20 @@ def validate_action_skill(path: Path, parsed: Parsed, report: Report) -> None:
bad = [x for x in ss if not x.endswith(".md")]
if bad:
report.error(path, "R20", f"sub-skills entries must end in '.md': {bad}", 1)
non_canonical = [
x for x in ss
if "\\" in x or x.startswith("/") or ".." in Path(x).parts or x.startswith("./")
]
if non_canonical:
report.error(
path,
"R20",
f"sub-skills entries must be canonical repo-relative paths: {non_canonical}",
1,
)
duplicates = sorted({x for x in ss if ss.count(x) > 1})
if duplicates:
report.error(path, "R20", f"sub-skills contains duplicate paths: {duplicates}", 1)
# R21 five required sections, in order, each exactly once
heads = [h for h, _ in headings_in_order(parsed.body)]
@ -565,7 +579,13 @@ class SkillRecord:
skill_id: str | None
def validate_sub_skills_registry(path: Path, fm: dict[str, Any], root: Path, report: Report) -> None:
def validate_sub_skills_registry(
path: Path,
fm: dict[str, Any],
root: Path,
action_skills_by_path: dict[str, dict[str, Any]],
report: Report,
) -> None:
"""R26: a super-skill's declared `sub-skills` must exactly match the
`al-*-review.md` leaf files present in the same directory (set equality,
ordering-agnostic). This keeps the registered leaf list the single source
@ -598,6 +618,17 @@ def validate_sub_skills_registry(path: Path, fm: dict[str, Any], root: Path, rep
f"sub-skills entry is not a sibling 'al-*-review.md' leaf: {entry}", 1,
)
for entry in ss:
leaf = action_skills_by_path.get(entry)
if leaf is None:
if (root / entry).exists():
report.error(path, "R26", f"sub-skills entry is not an action skill: {entry}", 1)
continue
if is_non_empty_list_of_str(leaf.get("sub-skills")):
report.error(path, "R26", f"nested super-skill is not permitted in v1 composition: {entry}", 1)
if leaf.get("outputs") != ["findings-report"]:
report.error(path, "R26", f"sub-skill must produce findings-report: {entry}", 1)
# Sibling leaves on disk that were never registered ('forgot to wire it up').
for leaf in sorted(leaves - declared):
report.error(path, "R26", f"leaf not registered in sub-skills: {leaf}", 1)
@ -670,9 +701,13 @@ def run(root: Path) -> Report:
others = [q.relative_to(root).as_posix() for q in paths if q != p]
report.error(p, "R24", f"skill id '{sid}' ({kind}) is not unique; also defined in: {others}")
# Fourth pass: R26 sub-skills registry matches leaf files on disk
# Fourth pass: R26 sub-skills registry matches compatible leaf files on disk
action_skills_by_path = {
path.relative_to(root).as_posix(): fm
for path, fm in action_skill_fms
}
for path, fm in action_skill_fms:
validate_sub_skills_registry(path, fm, root, report)
validate_sub_skills_registry(path, fm, root, action_skills_by_path, report)
return report