File size: 4,274 Bytes
18b2059
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#!/usr/bin/env python3
"""Traverse a candidate Space only through its evaluator-visible entrypoints."""

from __future__ import annotations

import argparse
import hashlib
import json
import re
from pathlib import Path


MARKDOWN_LINK = re.compile(r"\[[^\]]+\]\(([^)]+)\)")
REQUIRED_CLAIM_TERMS = (
    "source",
    "assumption",
    "raw",
    "checker",
    "control",
    "command",
    "runtime",
    "limitation",
)


def _sha256(path: Path) -> str:
    return hashlib.sha256(path.read_bytes()).hexdigest()


def _load_navigation(root: Path) -> tuple[list[Path], dict[str, Path]]:
    payload = json.loads((root / "logbook.json").read_text(encoding="utf-8"))
    pages: list[Path] = []
    slugs: dict[str, Path] = {}

    def walk(node: dict[str, object]) -> None:
        path = Path(str(node["file"]))
        pages.append(path)
        slugs[str(node["slug"])] = path
        for child in node.get("children", []):
            walk(child)

    walk(payload["root"])
    return pages, slugs


def review(root: Path) -> dict[str, object]:
    root = root.resolve()
    entrypoints = [Path("README.md"), Path("logbook.json"), Path("pages/index.md")]
    nav_pages, slugs = _load_navigation(root)
    queue = list(dict.fromkeys(entrypoints + nav_pages))
    opened: list[dict[str, str]] = []
    missing: list[str] = []
    claim_checks: dict[str, dict[str, object]] = {}
    seen: set[Path] = set()

    while queue:
        relative = queue.pop(0)
        if relative in seen:
            continue
        seen.add(relative)
        path = root / relative
        if not path.is_file():
            missing.append(relative.as_posix())
            continue
        opened.append({"path": relative.as_posix(), "sha256": _sha256(path)})
        if path.suffix.lower() != ".md":
            continue
        text = path.read_text(encoding="utf-8")
        if relative.parts[:2] == ("pages", "claims"):
            lowered = text.lower()
            claim_checks[relative.stem.upper()] = {
                term: term in lowered for term in REQUIRED_CLAIM_TERMS
            }
        for target in MARKDOWN_LINK.findall(text):
            target = target.split("#", 1)[0] if not target.startswith("#/") else target
            if target.startswith("#/"):
                slug = target[2:]
                if slug in slugs:
                    queue.append(slugs[slug])
                else:
                    missing.append(f"unknown slug {target} from {relative}")
            elif target.startswith("/"):
                queue.append(Path(target[1:]))
            elif target and "://" not in target and not target.startswith("#"):
                queue.append(
                    (root / relative.parent / target).resolve().relative_to(root)
                )

    incomplete_claims = {
        claim: [term for term, present in checks.items() if not present]
        for claim, checks in claim_checks.items()
        if not all(checks.values())
    }
    if len(claim_checks) != 6:
        missing.append(f"expected 6 canonical claim pages, found {len(claim_checks)}")
    if incomplete_claims:
        missing.append(f"incomplete claim pages: {incomplete_claims}")

    index = (root / "pages/index.md").read_text(encoding="utf-8")
    if "Historical rejected baseline" not in index:
        missing.append("historical navigation is not explicitly rejected")
    if "Evaluator-visible evidence matrix" not in index:
        missing.append("canonical visibility matrix is absent")

    return {
        "entrypoints": [path.as_posix() for path in entrypoints],
        "files_opened": opened,
        "claim_checks": claim_checks,
        "missing_or_incomplete": missing,
        "passed": not missing,
    }


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--root", type=Path, required=True)
    parser.add_argument("--output", type=Path)
    args = parser.parse_args()
    result = review(args.root)
    rendered = json.dumps(result, indent=2, sort_keys=True)
    if args.output:
        args.output.parent.mkdir(parents=True, exist_ok=True)
        args.output.write_text(rendered + "\n", encoding="utf-8")
    print(rendered)
    return 0 if result["passed"] else 1


if __name__ == "__main__":
    raise SystemExit(main())