init: llm-wiki-haness 하네스 설계
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
#!/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())
|
||||
Reference in New Issue
Block a user