115 lines
4.0 KiB
Python
115 lines
4.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Validate that deep harness SKILL.md files are mirrored in repo-root skills/."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import difflib
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
|
|
|
|
def _mirror_only_lines(expected: str, actual: str) -> list[str]:
|
|
"""Lines present in the root mirror but absent from the regenerated source.
|
|
|
|
These are exactly what `sync_root_skills.py` would overwrite and destroy,
|
|
because it regenerates the mirror from the harness source unconditionally.
|
|
"""
|
|
diff = difflib.unified_diff(
|
|
expected.splitlines(), actual.splitlines(), lineterm="", n=0
|
|
)
|
|
return [
|
|
line[1:].strip()
|
|
for line in diff
|
|
if line.startswith("+") and not line.startswith("+++") and line[1:].strip()
|
|
]
|
|
|
|
|
|
def _load_sync_helpers():
|
|
namespace: dict[str, object] = {"__file__": str(REPO_ROOT / ".github" / "scripts" / "sync_root_skills.py")}
|
|
sync_script = REPO_ROOT / ".github" / "scripts" / "sync_root_skills.py"
|
|
exec(sync_script.read_text(encoding="utf-8"), namespace)
|
|
return namespace
|
|
|
|
|
|
def main() -> int:
|
|
sync = _load_sync_helpers()
|
|
discover_sources = sync["_discover_sources"]
|
|
canonical_skill_id = sync["_canonical_skill_id"]
|
|
rewrite_name_frontmatter = sync["_rewrite_name_frontmatter"]
|
|
root_skills_dir = sync["ROOT_SKILLS_DIR"]
|
|
|
|
# Drift where the mirror holds content the source lacks. Regenerating would
|
|
# delete it, so these must be resolved by editing the source instead.
|
|
clobber: list[tuple[Path, Path, list[str]]] = []
|
|
# Drift the sync script can safely repair: the source moved ahead.
|
|
stale: list[str] = []
|
|
|
|
for source in discover_sources():
|
|
skill_id = canonical_skill_id(source)
|
|
target = root_skills_dir / skill_id / "SKILL.md"
|
|
if not target.is_file():
|
|
stale.append(
|
|
f"Missing root skill for {source.relative_to(REPO_ROOT)}: expected {target.relative_to(REPO_ROOT)}"
|
|
)
|
|
continue
|
|
|
|
source_content = source.read_text(encoding="utf-8")
|
|
expected = rewrite_name_frontmatter(source_content, skill_id)
|
|
actual = target.read_text(encoding="utf-8")
|
|
if actual == expected:
|
|
continue
|
|
|
|
mirror_only = _mirror_only_lines(expected, actual)
|
|
if mirror_only:
|
|
clobber.append((source, target, mirror_only))
|
|
else:
|
|
stale.append(
|
|
f"Out-of-sync root skill for {source.relative_to(REPO_ROOT)}: {target.relative_to(REPO_ROOT)}"
|
|
)
|
|
|
|
if not clobber and not stale:
|
|
print("Root skills validation passed.")
|
|
return 0
|
|
|
|
print("Root skills validation failed:", file=sys.stderr)
|
|
|
|
if clobber:
|
|
print(
|
|
"\nYou edited a GENERATED file. `skills/` is produced from the harness\n"
|
|
"SKILL.md sources; running the sync script would DELETE these edits.",
|
|
file=sys.stderr,
|
|
)
|
|
for source, target, mirror_only in clobber:
|
|
print(f"\n- {target.relative_to(REPO_ROOT)}", file=sys.stderr)
|
|
print(
|
|
f" contains content not present in {source.relative_to(REPO_ROOT)}:",
|
|
file=sys.stderr,
|
|
)
|
|
for line in mirror_only[:10]:
|
|
print(f" + {line}", file=sys.stderr)
|
|
if len(mirror_only) > 10:
|
|
print(f" … and {len(mirror_only) - 10} more line(s)", file=sys.stderr)
|
|
print(
|
|
f" Fix: move the change into {source.relative_to(REPO_ROOT)},\n"
|
|
f" then run `python3 .github/scripts/sync_root_skills.py`.",
|
|
file=sys.stderr,
|
|
)
|
|
|
|
if stale:
|
|
print("\nThe following root skills are stale and can be regenerated:", file=sys.stderr)
|
|
for error in stale:
|
|
print(f"- {error}", file=sys.stderr)
|
|
print(
|
|
"Run `python3 .github/scripts/sync_root_skills.py` and commit the updated root skills.",
|
|
file=sys.stderr,
|
|
)
|
|
|
|
return 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|