103 lines
3.0 KiB
Python
103 lines
3.0 KiB
Python
"""Contract checks for the optional agent-merge-conflict-arbiter skill asset.
|
|
|
|
Reads only the SKILL.md markdown asset — no .py source reads, no network.
|
|
"""
|
|
import re
|
|
from pathlib import Path
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
SKILL_PATH = (
|
|
REPO_ROOT
|
|
/ "optional-skills"
|
|
/ "autonomous-ai-agents"
|
|
/ "agent-merge-conflict-arbiter"
|
|
/ "SKILL.md"
|
|
)
|
|
|
|
REQUIRED_SECTIONS = [
|
|
"## When to Use",
|
|
"## Prerequisites",
|
|
"## How to Run",
|
|
"## Quick Reference",
|
|
"## Procedure",
|
|
"## Pitfalls",
|
|
"## Verification",
|
|
]
|
|
|
|
|
|
def _frontmatter_and_body():
|
|
content = SKILL_PATH.read_text(encoding="utf-8")
|
|
assert content.startswith("---"), "SKILL.md must open with frontmatter"
|
|
m = re.search(r"\n---\s*\n", content[3:])
|
|
assert m, "frontmatter must close with ---"
|
|
fm_text = content[3 : m.start() + 3]
|
|
body = content[m.end() + 3 :]
|
|
fm = {}
|
|
for line in fm_text.splitlines():
|
|
km = re.match(r"^(\w[\w-]*):\s*(.*)$", line)
|
|
if km:
|
|
fm[km.group(1)] = km.group(2).strip().strip('"')
|
|
return fm, body
|
|
|
|
|
|
def test_skill_file_exists():
|
|
assert SKILL_PATH.is_file()
|
|
|
|
|
|
def test_frontmatter_required_fields():
|
|
fm, _ = _frontmatter_and_body()
|
|
for field in ("name", "description", "version", "author", "license", "platforms"):
|
|
assert field in fm, f"missing frontmatter field: {field}"
|
|
assert fm["name"] == "agent-merge-conflict-arbiter"
|
|
|
|
|
|
def test_description_hardline():
|
|
fm, _ = _frontmatter_and_body()
|
|
desc = fm["description"]
|
|
assert len(desc) <= 60, f"description is {len(desc)} chars; hardline is 60"
|
|
assert desc.endswith("."), "description must end with a period"
|
|
assert desc.count(".") == 1, "description must be one sentence"
|
|
|
|
|
|
def test_required_sections_present_in_order():
|
|
_, body = _frontmatter_and_body()
|
|
positions = []
|
|
for section in REQUIRED_SECTIONS:
|
|
idx = body.find(section)
|
|
assert idx != -1, f"missing section: {section}"
|
|
positions.append(idx)
|
|
assert positions == sorted(positions), "sections out of required order"
|
|
|
|
|
|
def test_body_size_within_bundled_norms():
|
|
content = SKILL_PATH.read_text(encoding="utf-8")
|
|
lines = content.count("\n") + 1
|
|
assert 80 <= lines <= 260, f"SKILL.md is {lines} lines; expected ~100-200"
|
|
|
|
|
|
def test_references_native_hermes_tools():
|
|
_, body = _frontmatter_and_body()
|
|
for tool in ("`terminal`", "`read_file`", "`patch`", "`delegate_task`"):
|
|
assert tool in body, f"body must reference native tool {tool}"
|
|
|
|
|
|
def test_classification_taxonomy_present():
|
|
_, body = _frontmatter_and_body()
|
|
for cls in (
|
|
"disjoint-intent",
|
|
"same-question-different-answer",
|
|
"superseded",
|
|
):
|
|
assert cls in body, f"missing hunk class: {cls}"
|
|
|
|
|
|
def test_impartiality_contract_stated():
|
|
_, body = _frontmatter_and_body()
|
|
assert "never favor" in body.lower()
|
|
assert "drive-by" in body.lower()
|
|
|
|
|
|
def test_no_machine_local_paths():
|
|
content = SKILL_PATH.read_text(encoding="utf-8")
|
|
assert "/home/" not in content
|