generated from coulomb/repo-seed
Some checks failed
ci / validate-registry (push) Has been cancelled
Closes the registry maintenance loop from inside each domain repo: interactive prompting for judgment calls, full automation for safe and high-confidence changes, both backed by the llm-connect HTTP bridge. - New modules: maintain.py, maintain_llm.py, patches.py, interactive.py - Schema: schemas/registry-patch.schema.json - CLI: reuse-surface maintain; establish --scaffold --hook - Sibling templates: Makefile fragment, pre-commit hook - Deterministic signal collectors extended; validate cwd auto-detect - Docs, gap priority 28, SCOPE update - Tests: test_maintain.py, test_interactive.py (59 pytest total) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
99 lines
3.2 KiB
Python
99 lines
3.2 KiB
Python
from __future__ import annotations
|
|
|
|
import re
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import yaml
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
CAPABILITIES_DIR = ROOT / "registry" / "capabilities"
|
|
INDEX_PATH = ROOT / "registry" / "indexes" / "capabilities.yaml"
|
|
SCHEMA_PATH = ROOT / "schemas" / "capability.schema.yaml"
|
|
|
|
LEVEL_ORDERS = {
|
|
"discovery": ["D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7"],
|
|
"availability": ["A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7"],
|
|
"completeness": ["C0", "C1", "C2", "C3", "C4", "C5", "C6"],
|
|
"reliability": ["R0", "R1", "R2", "R3", "R4", "R5", "R6"],
|
|
}
|
|
|
|
|
|
def load_schema() -> dict[str, Any]:
|
|
with SCHEMA_PATH.open(encoding="utf-8") as handle:
|
|
return yaml.safe_load(handle)
|
|
|
|
|
|
def parse_front_matter(path: Path) -> dict[str, Any]:
|
|
text = path.read_text(encoding="utf-8")
|
|
match = re.match(r"^---\n(.*?)\n---", text, re.DOTALL)
|
|
if not match:
|
|
raise ValueError(f"{path}: missing YAML front matter")
|
|
data = yaml.safe_load(match.group(1))
|
|
if not isinstance(data, dict):
|
|
raise ValueError(f"{path}: front matter must be a mapping")
|
|
return data
|
|
|
|
|
|
def capability_paths(target: Path | None = None) -> list[Path]:
|
|
if target is not None:
|
|
return [target]
|
|
return sorted(CAPABILITIES_DIR.glob("*.md"))
|
|
|
|
|
|
def load_index() -> dict[str, Any]:
|
|
with INDEX_PATH.open(encoding="utf-8") as handle:
|
|
return yaml.safe_load(handle)
|
|
|
|
|
|
def parse_vector(vector: str) -> dict[str, str]:
|
|
parts = [part.strip() for part in vector.split("/")]
|
|
if len(parts) != 4:
|
|
raise ValueError(f"invalid vector: {vector}")
|
|
return {
|
|
"discovery": parts[0],
|
|
"availability": parts[1],
|
|
"completeness": parts[2],
|
|
"reliability": parts[3],
|
|
}
|
|
|
|
|
|
def level_at_least(dimension: str, current: str, minimum: str) -> bool:
|
|
order = LEVEL_ORDERS[dimension]
|
|
return order.index(current) >= order.index(minimum)
|
|
|
|
|
|
def registry_paths(repo_root: Path) -> dict[str, Path]:
|
|
registry = repo_root / "registry"
|
|
return {
|
|
"registry": registry,
|
|
"capabilities": registry / "capabilities",
|
|
"index": registry / "indexes" / "capabilities.yaml",
|
|
"sources": registry / "federation" / "sources.yaml",
|
|
}
|
|
|
|
|
|
def load_index_at(path: Path) -> dict[str, Any]:
|
|
with path.open(encoding="utf-8") as handle:
|
|
return yaml.safe_load(handle)
|
|
|
|
|
|
def entry_vector(front_matter: dict[str, Any]) -> str:
|
|
discovery = front_matter["maturity"]["discovery"]["current"]
|
|
availability = front_matter["maturity"]["availability"]["current"]
|
|
completeness = front_matter["external_evidence"]["completeness"]["level"]
|
|
reliability = front_matter["external_evidence"]["reliability"]["level"]
|
|
return f"{discovery} / {availability} / {completeness} / {reliability}"
|
|
|
|
|
|
def vectors_match(index_vector: str, front_matter: dict[str, Any]) -> bool:
|
|
return index_vector.replace(" ", "") == entry_vector(front_matter).replace(" ", "")
|
|
|
|
|
|
def resolve_repo_root(explicit: str | Path | None = None) -> Path:
|
|
if explicit:
|
|
return Path(explicit).resolve()
|
|
cwd = Path.cwd()
|
|
if (cwd / "registry" / "indexes" / "capabilities.yaml").is_file():
|
|
return cwd.resolve()
|
|
return ROOT |