Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions src/vouch/dual_solve.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
16 changes: 15 additions & 1 deletion tests/test_dual_solve.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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")
Expand Down
Loading