from __future__ import annotations import json from pathlib import Path from unittest.mock import patch import yaml from reuse_surface.establish import scaffold_registry from reuse_surface.maintain import run_maintain from reuse_surface.maintain_llm import build_maintain_prompt, load_patch_schema from reuse_surface.patches import ( apply_patches, evidence_gate, filter_auto_patches, patches_from_suggestions, promotion_delta_gate, suggestion_to_patch, ) from reuse_surface.registry import load_index_at, registry_paths from reuse_surface.registry_update import collect_deterministic_suggestions def _seed_repo(tmp_path: Path) -> str: scaffold_registry(tmp_path) cap_id = "capability.demo.sample" rel = "registry/capabilities/capability-demo-sample.md" front_matter = { "id": cap_id, "name": "Sample", "summary": "Sample", "owner": "demo", "status": "draft", "domain": "helix_forge", "tags": ["demo"], "maturity": { "discovery": {"current": "D2", "target": "D5", "confidence": "low"}, "availability": {"current": "A0", "target": "A3", "confidence": "low"}, }, "external_evidence": { "completeness": {"level": "C0", "confidence": "low"}, "reliability": {"level": "R0", "confidence": "low"}, }, "discovery": {"intent": "demo", "includes": [], "excludes": []}, "availability": { "current_level": "A0", "target_level": "A3", "current_artifacts": [], "consumption_modes": ["informational"], }, "relations": {"depends_on": [], "supports": [], "related_to": []}, "evidence": {"documentation": [], "tests": []}, "consumer_guidance": { "recommended_for": [], "not_recommended_for": [], "known_limitations": [], }, } entry = tmp_path / rel entry.parent.mkdir(parents=True, exist_ok=True) entry.write_text("---\n" + yaml.safe_dump(front_matter, sort_keys=False) + "---\n") index_path = registry_paths(tmp_path)["index"] index = load_index_at(index_path) index["capabilities"] = [ { "id": cap_id, "name": "Sample", "summary": "Sample", "vector": "D3 / A0 / C0 / R0", "domain": "helix_forge", "status": "draft", "owner": "demo", "path": rel, "tags": ["demo"], "consumption_modes": ["informational"], } ] index_path.write_text(yaml.safe_dump(index, sort_keys=False), encoding="utf-8") return cap_id def test_patch_schema_loads(): schema = load_patch_schema() assert "patches" in schema["properties"] def test_build_maintain_prompt(tmp_path: Path): cap_id = _seed_repo(tmp_path) prompt = build_maintain_prompt(tmp_path, cap_id, git_since=None) assert cap_id in prompt assert "Return ONLY JSON" in prompt def test_suggestion_to_patch_vector_sync(): patch = suggestion_to_patch( { "capability_id": "capability.demo.sample", "kind": "vector_drift", "detail": "drift", "apply_patch": {"field": "index.vector", "value": "D2 / A0 / C0 / R0"}, } ) assert patch is not None assert patch["kind"] == "vector_sync" def test_evidence_gate_requires_files(tmp_path: Path): evidence = (tmp_path / "tests" / "test_x.py") evidence.parent.mkdir(parents=True) evidence.write_text("def test_x(): pass\n") patch = { "kind": "maturity_promote", "evidence_citations": ["tests/test_x.py"], } assert evidence_gate(tmp_path, patch) patch["evidence_citations"] = ["tests/missing.py"] assert not evidence_gate(tmp_path, patch) def test_promotion_delta_gate(): patch = { "kind": "maturity_promote", "dimension": "availability", "from_level": "A2", "to_level": "A3", } assert promotion_delta_gate(patch, 1) patch["to_level"] = "A5" assert not promotion_delta_gate(patch, 1) def test_apply_patches_vector_sync(tmp_path: Path): cap_id = _seed_repo(tmp_path) suggestions = collect_deterministic_suggestions(tmp_path, capability_id=cap_id) patches = patches_from_suggestions(suggestions) changed = apply_patches(tmp_path, patches) assert changed index = load_index_at(registry_paths(tmp_path)["index"]) assert index["capabilities"][0]["vector"] == "D2 / A0 / C0 / R0" def test_filter_auto_patches(tmp_path: Path): cap_id = _seed_repo(tmp_path) suggestions = collect_deterministic_suggestions(tmp_path, capability_id=cap_id) patches = patches_from_suggestions(suggestions) selected = filter_auto_patches(patches, tmp_path) assert selected def test_run_maintain_auto_no_llm(tmp_path: Path): _seed_repo(tmp_path) def _validate() -> tuple[int, list[str], list[str]]: return 0, [], [] result = run_maintain( tmp_path, all_capabilities=True, auto=True, no_llm=True, validate_fn=_validate, ) assert result.exit_code == 0 assert result.selected_count >= 1 def test_request_maintain_patches_mock(tmp_path: Path): cap_id = _seed_repo(tmp_path) payload = { "patches": [ { "capability_id": cap_id, "kind": "consumer_feedback", "confidence": "medium", "rationale": "note", "append": "helpful", } ], "notes": [], } with patch( "reuse_surface.maintain_llm.execute_prompt", return_value=json.dumps(payload), ): from reuse_surface.maintain_llm import request_maintain_patches result = request_maintain_patches(tmp_path, cap_id, llm_url="http://example") assert len(result["patches"]) == 1