Files
llm-wiki/harness/runtime/active_structure_check.py
T

105 lines
3.4 KiB
Python

#!/usr/bin/env python3
"""Efficiently lint active project and branch documents with one shared index."""
from __future__ import annotations
import argparse
import importlib.util
import json
from pathlib import Path
import sys
from typing import Any
DEFAULT_ROOT = Path(__file__).resolve().parents[2]
RESULT_SCHEMA = "active-structure-result/v1"
class ActiveStructureError(RuntimeError):
pass
def _module(path: Path) -> Any:
spec = importlib.util.spec_from_file_location("wiki_structure_lint_active", path)
if spec is None or spec.loader is None:
raise ActiveStructureError(f"cannot load structure lint: {path}")
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def check(root: Path) -> dict[str, Any]:
root = root.resolve(strict=True)
lint = _module(root / ".claude/hooks/wiki_structure_lint.py")
authority = lint.authority_mapping(root)
by_st, by_file = lint.build_template_index(root)
vault_paths, vault_bases = lint.build_vault_index(root)
cache: dict[Path, str] = {}
if authority["mode"] == "canonical":
paths = sorted(
root / canonical
for legacy, canonical in authority["legacy_to_canonical"].items()
if (
legacy.startswith("raw/branch-notes/")
or legacy.startswith("raw/project-notes/")
)
and legacy.endswith(".md")
)
else:
paths = sorted((root / "raw/branch-notes").glob("*.md")) + sorted(
(root / "raw/project-notes").glob("*.md")
)
paths = [path for path in paths if path.is_file()]
if not paths:
raise ActiveStructureError("no active project/branch documents found")
findings: list[dict[str, Any]] = []
for path in paths:
relative = path.relative_to(root).as_posix()
mode = lint.classify(relative, root)
lint_findings, _source_type = lint.lint_file(
path,
root,
by_st,
by_file,
vault_paths,
vault_bases,
cache,
mode=mode,
)
findings.extend(
{"code": code, "path": relative, "line": line, "message": message}
for code, line, message in lint_findings
)
findings.sort(key=lambda item: (item["path"], item["line"], item["code"]))
return {
"schema_version": RESULT_SCHEMA,
"status": "PASS" if not findings else "FAIL",
"mode": authority["mode"],
"namespace": "canonical" if authority["mode"] == "canonical" else "legacy",
"checked": len(paths),
"findings": findings,
}
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--root", type=Path, default=DEFAULT_ROOT)
args = parser.parse_args(argv)
try:
result = check(args.root)
exit_code = 0 if result["status"] == "PASS" else 1
except (ActiveStructureError, OSError, UnicodeError, ValueError, ImportError) as exc:
result = {
"schema_version": RESULT_SCHEMA,
"status": "ERROR",
"errors": [{"code": "ACTIVE_STRUCTURE_ERROR", "message": str(exc)}],
}
exit_code = 2
json.dump(result, sys.stdout, ensure_ascii=False, indent=2, sort_keys=True)
sys.stdout.write("\n")
return exit_code
if __name__ == "__main__":
raise SystemExit(main())