mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-10 18:07:24 +01:00
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
103 lines
3.1 KiB
Python
103 lines
3.1 KiB
Python
"""Run the contract check over every git repo under ~/src and ~ and write SWEEP.md."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from collections import Counter
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
|
|
if __package__ in (None, ""): # pragma: no cover - plain-script import path
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
|
|
from repo_contract.check import Unreadable, violations
|
|
|
|
OUT = Path.home() / "data" / "claude-scratch" / "repo-contract" / "SWEEP.md"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Result:
|
|
"""Outcome for one repo."""
|
|
|
|
name: str
|
|
path: Path
|
|
stacks: list[str]
|
|
problems: list[str]
|
|
error: str | None = None
|
|
|
|
@property
|
|
def passed(self) -> bool:
|
|
"""True when there were no problems and no read error."""
|
|
return not self.problems and self.error is None
|
|
|
|
|
|
def find_repos(roots: list[Path]) -> list[Path]:
|
|
"""Direct children of each root that are git repos (`.git` is a directory)."""
|
|
found = [
|
|
d
|
|
for r in roots
|
|
if r.is_dir()
|
|
for d in sorted(r.iterdir())
|
|
if not d.name.startswith(".") and (d / ".git").is_dir()
|
|
]
|
|
return sorted(found, key=lambda p: str(p))
|
|
|
|
|
|
def run_one(repo: Path) -> Result:
|
|
"""Check one repo, folding read errors into the result."""
|
|
try:
|
|
problems, stacks = violations(repo)
|
|
except Unreadable as exc:
|
|
return Result(repo.name, repo, [], [], f"unreadable: {exc}")
|
|
return Result(repo.name, repo, stacks, problems)
|
|
|
|
|
|
def render(results: list[Result]) -> str:
|
|
"""Markdown report: summary, by-stack counts, per-repo table."""
|
|
passed = sum(r.passed for r in results)
|
|
by_stack: Counter[str] = Counter()
|
|
fails: Counter[str] = Counter()
|
|
for r in results:
|
|
key = r.stacks[0] if r.stacks else "none"
|
|
by_stack[key] += 1
|
|
fails[key] += not r.passed
|
|
out = [
|
|
"# Repo contract sweep",
|
|
"",
|
|
f"{len(results)} repos: {passed} pass, {len(results) - passed} fail.",
|
|
"",
|
|
"| primary stack | repos | fail |",
|
|
"|---|---|---|",
|
|
*[f"| {s} | {n} | {fails[s]} |" for s, n in sorted(by_stack.items())],
|
|
"",
|
|
"| repo | result | stacks | missing |",
|
|
"|---|---|---|---|",
|
|
]
|
|
for r in results:
|
|
verdict = "PASS" if r.passed else "FAIL"
|
|
detail = r.error or "; ".join(r.problems)
|
|
out.append(
|
|
f"| {r.name} | {verdict} | {', '.join(r.stacks) or '-'} | {detail.replace('|', '/')} |"
|
|
)
|
|
return "\n".join(out) + "\n"
|
|
|
|
|
|
def main(
|
|
argv: list[str] | None = None, roots: list[Path] | None = None, out: Path = OUT
|
|
) -> int:
|
|
"""Sweep, write the report, print the summary line. Always exit 0."""
|
|
del argv
|
|
home = Path.home()
|
|
results = [run_one(r) for r in find_repos(roots or [home / "src", home])]
|
|
out.parent.mkdir(parents=True, exist_ok=True)
|
|
out.write_text(render(results), encoding="utf-8")
|
|
passed = sum(r.passed for r in results)
|
|
print(
|
|
f"repo-contract sweep: {len(results)} repos, {passed} pass, {len(results) - passed} fail -> {out}"
|
|
)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__": # pragma: no cover
|
|
sys.exit(main())
|