from __future__ import annotations import json import subprocess import textwrap from pathlib import Path from typing import Any import yaml from jsonschema import Draft202012Validator from reuse_surface.llm_bridge import execute_prompt, extract_json_object from reuse_surface.registry import ROOT, load_index_at, parse_front_matter, registry_paths PATCH_SCHEMA_PATH = ROOT / "schemas" / "registry-patch.schema.json" MATURITY_SUMMARY = """ | Dimension | Levels | Question | |---|---|---| | discovery | D0–D7 | Planning/orientation reuse strength | | availability | A0–A7 | Consumption mode and delivery artifacts | | completeness | C0–C6 | Scope vs intent and expectations | | reliability | R0–R6 | Consumer quality signals | Promotion rules: - Cite repo-relative evidence paths for every maturity_promote patch. - Prefer single-step promotions (one level per dimension). - Do not invent files; only cite paths visible in git diff or context. """ def load_patch_schema() -> dict[str, Any]: return json.loads(PATCH_SCHEMA_PATH.read_text(encoding="utf-8")) def _git_diff(repo_root: Path, git_since: str | None) -> str: if not git_since: return "" proc = subprocess.run( [ "git", "-C", str(repo_root), "diff", git_since, "HEAD", "--", "registry/", "reuse_surface/", "tests/", "docs/", ".gitea/", "pyproject.toml", ], capture_output=True, text=True, check=False, ) return proc.stdout[:12000] def build_maintain_prompt( repo_root: Path, capability_id: str, *, git_since: str | None = None, context_files: list[str] | None = None, ) -> str: paths = registry_paths(repo_root) index = load_index_at(paths["index"]) row = next((item for item in index["capabilities"] if item["id"] == capability_id), None) if not row: raise ValueError(f"capability not in index: {capability_id}") entry = parse_front_matter(repo_root / row["path"]) diff = _git_diff(repo_root, git_since) context_chunks: list[str] = [] for rel in context_files or []: path = repo_root / rel if path.is_file(): context_chunks.append(f"### {rel}\n{path.read_text(encoding='utf-8')[:4000]}") schema_hint = json.dumps( { "patches": [ { "capability_id": capability_id, "kind": "maturity_promote", "confidence": "medium", "rationale": "CI gate added", "dimension": "reliability", "from_level": "R2", "to_level": "R3", "evidence_citations": ["tests/test_example.py"], "promotion_history_entry": { "date": "2026-06-16", "dimension": "reliability", "from": "R2", "to": "R3", "rationale": "pytest coverage for consumption path", }, } ], "notes": ["optional human review items"], }, indent=2, ) return textwrap.dedent( f""" Propose structured registry maintenance patches for `{capability_id}`. Return ONLY JSON matching this shape (no markdown fences): {schema_hint} Allowed patch kinds: vector_sync, evidence_append, artifact_append, maturity_promote, consumer_feedback, relation_add, index_row_add, index_updated_bump. Maturity reference: {MATURITY_SUMMARY} Current entry YAML: {yaml.safe_dump(entry, sort_keys=False)} Git diff since {git_since or 'N/A'}: {diff or '(none)'} Context files: {chr(10).join(context_chunks) if context_chunks else '(none)'} """ ).strip() def request_maintain_patches( repo_root: Path, capability_id: str, *, git_since: str | None = None, context_files: list[str] | None = None, llm_url: str | None = None, ) -> dict[str, Any]: prompt = build_maintain_prompt( repo_root, capability_id, git_since=git_since, context_files=context_files, ) content = execute_prompt( prompt, base_url=llm_url, config={"temperature": 0.2, "max_tokens": 3000}, ) payload = extract_json_object(content) validator = Draft202012Validator(load_patch_schema()) errors = sorted(validator.iter_errors(payload), key=lambda err: list(err.path)) if errors: messages = "; ".join(error.message for error in errors[:3]) raise ValueError(f"patch schema validation failed: {messages}") return payload