generated from coulomb/repo-seed
WP-0016 finished: interactive registry maintain with llm-connect automation
Some checks failed
ci / validate-registry (push) Has been cancelled
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>
This commit is contained in:
188
tests/test_maintain.py
Normal file
188
tests/test_maintain.py
Normal file
@@ -0,0 +1,188 @@
|
||||
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
|
||||
Reference in New Issue
Block a user