REUSE-WP-0018-T03: LLM semantic rerank for plan-check
Some checks failed
ci / validate-registry (push) Has been cancelled

llm-connect came up locally (mock provider, 127.0.0.1:8080), unblocking
this task.

reuse_surface/plan_check.py: build_rerank_prompt/request_rerank/apply_rerank,
reusing llm_bridge.execute_prompt/extract_json_object (same pattern as
maintain_llm.py's request_maintain_patches). New schema
schemas/plan-check-rerank.schema.json rejects malformed responses (missing
fields, non-JSON, invented candidate ids outside the input set) rather than
guessing.

Per design principle 3 (deterministic matches always rank first),
apply_rerank appends LLM-scored entries after the deterministic list
(kind: 'llm') instead of reordering it -- the trusted base result is
identical with or without the rerank pass. Graceful skip via a 'notes'
field when LLM_CONNECT_URL is unset or the response is malformed, mirroring
maintain.py's no_llm/skip pattern. New --llm-url/--no-llm CLI flags.

Also extended plan-check-result.schema.json for 'notes' and the
(pre-existing, previously unschema'd) 'filed_capability_request' field.

9 new pytest cases; 89 total pass. Live-verified against the running
llm-connect instance: it correctly rejected the mock provider's non-JSON
response and surfaced the skip note, with the deterministic verdict and
match order completely unaffected.

REUSE-WP-0018 is now fully done (T01-T06). Updated
docs/IntentScopeGapAnalysis.md priority 29 to Closed and the workplan's
own frontmatter status to finished.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-07 17:27:39 +02:00
parent a1b3425c84
commit 0c76ccb28b
9 changed files with 335 additions and 17 deletions

View File

@@ -3,12 +3,15 @@ from __future__ import annotations
import json
from reuse_surface.plan_check import (
Match,
MatchQuery,
apply_rerank,
load_query_from_intent,
load_query_from_workplan,
maybe_file_capability_request,
match_query,
record_outcome,
request_rerank,
run_plan_check,
verdict_for_score,
)
@@ -196,3 +199,153 @@ def test_cmd_plan_check_rejects_both_inputs(tmp_path):
workplan.write_text("---\nid: X\n---\nbody")
exit_code = main(["plan-check", str(workplan), "--intent", "also this"])
assert exit_code == 1
# --- T03: LLM rerank ---
SAMPLE_MATCHES = [
Match(
id="capability.infotech.issue-tracking",
score=0.36,
kind="deterministic",
vector="D4 / A2 / C2 / R1",
owner="issue-core",
summary="Unified interface for issue tracking.",
),
Match(
id="capability.audit.event-retain",
score=0.12,
kind="deterministic",
vector="D4 / A2 / C2 / R1",
owner="audit-core",
summary="Collect and retain audit events.",
),
]
def test_request_rerank_valid_response(monkeypatch):
monkeypatch.setattr(
"reuse_surface.plan_check.execute_prompt",
lambda prompt, **kwargs: json.dumps(
{
"candidates": [
{"id": "capability.infotech.issue-tracking", "confidence": 0.9, "rationale": "strong match"},
{"id": "capability.audit.event-retain", "confidence": 0.1, "rationale": "unrelated"},
]
}
),
)
query = load_query_from_intent("issue tracking across platforms")
result = request_rerank(query, SAMPLE_MATCHES)
assert len(result) == 2
assert result[0]["confidence"] == 0.9
def test_request_rerank_rejects_malformed_response(monkeypatch):
monkeypatch.setattr(
"reuse_surface.plan_check.execute_prompt",
lambda prompt, **kwargs: json.dumps({"candidates": [{"id": "capability.infotech.issue-tracking"}]}),
)
query = load_query_from_intent("issue tracking across platforms")
try:
request_rerank(query, SAMPLE_MATCHES)
assert False, "expected ValueError for missing confidence field"
except ValueError as exc:
assert "schema validation failed" in str(exc)
def test_request_rerank_rejects_non_json_response(monkeypatch):
monkeypatch.setattr(
"reuse_surface.plan_check.execute_prompt",
lambda prompt, **kwargs: "not json at all",
)
query = load_query_from_intent("issue tracking across platforms")
try:
request_rerank(query, SAMPLE_MATCHES)
assert False, "expected ValueError for non-JSON response"
except ValueError:
pass
def test_request_rerank_ignores_invented_ids(monkeypatch):
monkeypatch.setattr(
"reuse_surface.plan_check.execute_prompt",
lambda prompt, **kwargs: json.dumps(
{
"candidates": [
{"id": "capability.infotech.issue-tracking", "confidence": 0.8},
{"id": "capability.madeup.not-real", "confidence": 0.99},
]
}
),
)
query = load_query_from_intent("issue tracking across platforms")
result = request_rerank(query, SAMPLE_MATCHES)
assert [c["id"] for c in result] == ["capability.infotech.issue-tracking"]
def test_apply_rerank_appends_after_deterministic_matches():
rerank_result = [
{"id": "capability.audit.event-retain", "confidence": 0.95, "rationale": "actually a great fit"},
]
combined = apply_rerank(SAMPLE_MATCHES, rerank_result)
# Deterministic matches keep their original order and scores untouched.
assert combined[0].id == "capability.infotech.issue-tracking"
assert combined[0].score == 0.36
assert combined[0].kind == "deterministic"
assert combined[1].id == "capability.audit.event-retain"
assert combined[1].score == 0.12
assert combined[1].kind == "deterministic"
# LLM entry is appended after, separately labeled.
assert combined[2].id == "capability.audit.event-retain"
assert combined[2].score == 0.95
assert combined[2].kind == "llm"
def test_run_plan_check_skips_llm_gracefully_when_unset(monkeypatch):
monkeypatch.setattr(
"reuse_surface.plan_check.load_federated_capabilities",
lambda: (SAMPLE_CAPABILITIES, "2026-07-07"),
)
monkeypatch.delenv("LLM_CONNECT_URL", raising=False)
query = load_query_from_intent("issue tracking gitea github gitlab")
result = run_plan_check(query)
assert result["verdict"] in {"reuse", "extend"}
assert any("LLM_CONNECT_URL not set" in n for n in result.get("notes", []))
assert all(m["kind"] == "deterministic" for m in result["matches"])
def test_run_plan_check_no_llm_flag_skips_without_attempting(monkeypatch):
called = []
monkeypatch.setattr(
"reuse_surface.plan_check.load_federated_capabilities",
lambda: (SAMPLE_CAPABILITIES, "2026-07-07"),
)
monkeypatch.setattr(
"reuse_surface.plan_check.request_rerank",
lambda *a, **k: called.append(1) or [],
)
query = load_query_from_intent("issue tracking gitea github gitlab")
run_plan_check(query, use_llm=False)
assert called == []
def test_run_plan_check_integrates_successful_rerank(monkeypatch):
monkeypatch.setattr(
"reuse_surface.plan_check.load_federated_capabilities",
lambda: (SAMPLE_CAPABILITIES, "2026-07-07"),
)
monkeypatch.setattr(
"reuse_surface.plan_check.execute_prompt",
lambda prompt, **kwargs: json.dumps(
{"candidates": [{"id": "capability.infotech.issue-tracking", "confidence": 0.77}]}
),
)
query = load_query_from_intent("issue tracking gitea github gitlab")
result = run_plan_check(query)
assert "notes" not in result or not result["notes"]
llm_entries = [m for m in result["matches"] if m["kind"] == "llm"]
assert len(llm_entries) == 1
assert llm_entries[0]["score"] == 0.77
# deterministic top match is still first and unaffected
assert result["matches"][0]["kind"] == "deterministic"