From debb239db419b91bac87f5e9731f6b7634b03a7a Mon Sep 17 00:00:00 2001 From: Yaroslav98214 Date: Thu, 16 Jul 2026 03:03:43 +0000 Subject: [PATCH] fix(dual-solve): read grounding items from the context-pack dict MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ground_prompt gated its items on isinstance(pack, ContextPack), but build_context_pack returns a model_dump'd dict in production, so the guard was always false and grounding always returned "the knowledge base has nothing on this topic yet." — even when the kb had matching claims. dual-solve therefore never fed the code engines any kb context. read items from the dict (tolerating a ContextPack object too), mirroring synthesize's grounding. the existing test monkeypatched build_context_pack to return a ContextPack, so it exercised the dead branch and masked the bug; add a regression that drives the real, unmocked pipeline. --- src/vouch/dual_solve.py | 14 +++++++++++--- tests/test_dual_solve.py | 16 +++++++++++++++- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/vouch/dual_solve.py b/src/vouch/dual_solve.py index dedbb82d..2d4b4042 100644 --- a/src/vouch/dual_solve.py +++ b/src/vouch/dual_solve.py @@ -20,7 +20,7 @@ from .auto_pr import Engine, Runner, slugify from .context import build_context_pack -from .models import ContextPack, SourceType +from .models import SourceType from .proposals import propose_claim from .sandbox import DEFAULT_SANDBOX_IMAGE, require_docker_sandbox from .storage import KBStore @@ -223,10 +223,18 @@ def ground_prompt(store: KBStore, query: str, *, limit: int = 8) -> str: If the KB has nothing for this query, returns an explicit message saying so. """ pack = build_context_pack(store, query=query, limit=limit) - items = pack.items if isinstance(pack, ContextPack) else [] + # build_context_pack returns a model_dump'd dict in production; the old guard + # only accepted a ContextPack object, so items was always empty and grounding + # always said "nothing" even when the kb matched — dual-solve never saw any + # context. tolerate both shapes, mirroring synthesize's grounding. + items = pack["items"] if isinstance(pack, dict) else pack.items if not items: return "(the knowledge base has nothing on this topic yet.)" - return "\n".join(f"- [{it.id}] {it.summary}" for it in items) + return "\n".join( + f"- [{it['id'] if isinstance(it, dict) else it.id}] " + f"{it['summary'] if isinstance(it, dict) else it.summary}" + for it in items + ) def build_prompt(issue: Issue, grounding: str) -> str: diff --git a/tests/test_dual_solve.py b/tests/test_dual_solve.py index 0f0979fe..0e3ace41 100644 --- a/tests/test_dual_solve.py +++ b/tests/test_dual_solve.py @@ -12,7 +12,7 @@ from vouch import auto_pr as ap from vouch import dual_solve as ds -from vouch.models import ContextItem, ContextPack, ProposalStatus +from vouch.models import Claim, ContextItem, ContextPack, ProposalStatus from vouch.storage import KBStore @@ -142,6 +142,20 @@ def test_ground_prompt_empty_is_explicit(tmp_path, monkeypatch): assert "nothing" in ds.ground_prompt(store, "x").lower() +def test_ground_prompt_renders_real_context_pack(tmp_path): + # regression: build_context_pack returns a model_dump'd dict in production, so + # ground_prompt must read items out of that dict. the old isinstance(pack, + # ContextPack) guard was always false for the real (unmocked) path, so + # dual-solve grounding was silently empty even when the kb matched. seed a + # real claim and assert the unmocked pipeline surfaces it. + store = KBStore.init(tmp_path) + src = store.put_source(b"auth doc") + store.put_claim(Claim(id="c1", text="auth uses jwt tokens", evidence=[src.id])) + out = ds.ground_prompt(store, "auth") + assert "[c1]" in out and "auth uses jwt tokens" in out + assert "nothing" not in out.lower() + + def test_build_prompt_includes_issue_and_grounding(): issue = ds.Issue(title="Fix the lexer", body="it crashes", number=5) p = ds.build_prompt(issue, "- [c1] relevant claim")