Files
codex-py/tests/synthesis/test_gaps.py
Tarik Moussa c4106b0f51 fix(synthesis): make coverage-map gaps opt-in (audit R-8)
find_gaps defaulted its probe topics to paper titles, so a title — which
retrieves mostly its own single bibkey — fell below synthesis_gap_min_coverage
and flagged almost every paper as a 'gap'. Coverage-map gaps now run only for
explicitly-requested topics; the CLI gains a repeatable --topic option. The
code-vs-corpus gap path (precise) is unchanged and still default.

Tests: the two cases that relied on default-title coverage gaps now pass explicit
topics; added a regression that no coverage gaps appear without --topic.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-15 16:32:01 +02:00

162 lines
5.7 KiB
Python

"""Tests for find_gaps — coverage map + code-vs-corpus signals."""
from __future__ import annotations
from pathlib import Path
from typing import Any
import pytest
from codex.synthesis import find_gaps
from tests.synthesis.conftest import StubLLM
def test_find_gaps_code_cites_missing_bibkey(
monkeypatch: pytest.MonkeyPatch,
mock_paper_titles: None,
mock_settings: Path,
fixture_chunks: list[dict[str, object]],
tmp_path: Path,
) -> None:
"""A bibkey referenced in code but absent from corpus → direct gap lead."""
# Build a small C++ source tree with an @cite pointing at a missing bibkey
src_dir = tmp_path / "src"
src_dir.mkdir()
(src_dir / "main.cpp").write_text(
"// @cite UnknownPaper2099\nvoid foo() {}\n",
encoding="utf-8",
)
# All probes return the fixture chunks (so coverage map yields nothing)
monkeypatch.setattr(
"codex.synthesis._retrieve_chunks",
lambda queries, top_k: [dict(c) for c in fixture_chunks],
)
leads = find_gaps(lib_path=str(src_dir), llm=StubLLM(""))
bibkeys_in_gaps = {p.bibkey for lead in leads for p in lead.provenance}
# The missing bibkey shows up as a gap
assert any("UnknownPaper2099" in lead.title for lead in leads)
assert "UnknownPaper2099" in bibkeys_in_gaps
def test_find_gaps_code_known_bibkey_not_reported(
monkeypatch: pytest.MonkeyPatch,
mock_paper_titles: None,
mock_settings: Path,
fixture_chunks: list[dict[str, object]],
tmp_path: Path,
) -> None:
"""A bibkey already in the corpus is NOT flagged by the code-vs-corpus path."""
src_dir = tmp_path / "src"
src_dir.mkdir()
(src_dir / "main.cpp").write_text(
"// @cite Springborn2008\nvoid foo() {}\n",
encoding="utf-8",
)
monkeypatch.setattr(
"codex.synthesis._retrieve_chunks",
lambda queries, top_k: [dict(c) for c in fixture_chunks],
)
leads = find_gaps(lib_path=str(src_dir), llm=StubLLM(""))
# Springborn2008 IS in the corpus → not a gap
assert not any("Springborn2008" in lead.title for lead in leads)
def test_find_gaps_missing_lib_path_ok(
monkeypatch: pytest.MonkeyPatch,
mock_paper_titles: None,
mock_settings: Path,
fixture_chunks: list[dict[str, object]],
) -> None:
"""Without lib_path the code-vs-corpus path is skipped (no crash)."""
monkeypatch.setattr(
"codex.synthesis._retrieve_chunks",
lambda queries, top_k: [dict(c) for c in fixture_chunks],
)
leads = find_gaps(lib_path=None, llm=StubLLM(""))
# No crash; result list may be empty depending on coverage map.
assert isinstance(leads, list)
def test_find_gaps_coverage_map_low_coverage_triggers_llm(
monkeypatch: pytest.MonkeyPatch,
mock_paper_titles: None,
mock_settings: Path,
) -> None:
"""A topic covered by only 1 bibkey (< 2) triggers the LLM gap prompt.
The grounded body must reference Springborn2008 [...] in a content-5-gram
that exists in the chunk, otherwise the lead is dropped.
"""
sparse_chunks: list[dict[str, Any]] = [
{
"id": 10,
"paper_id": "springborn-2008",
"ord": 16,
"content": (
"For an ideal tetrahedron with dihedral angles the hyperbolic volume is "
"V = L(gamma1) + L(gamma2) + L(gamma3). "
"The volume function V0 is strictly concave on the angle domain."
),
"bibkey": "Springborn2008",
},
]
monkeypatch.setattr(
"codex.synthesis._retrieve_chunks",
lambda queries, top_k: [dict(c) for c in sparse_chunks],
)
grounded_body = (
"Only Springborn2008 covers the volume formula at depth, while comparison "
"papers are missing.\n"
"For an ideal tetrahedron with dihedral angles the hyperbolic volume is "
"V = L(gamma1) + L(gamma2) + L(gamma3). [Springborn2008 #chunk 16]\n"
)
# Coverage gaps are opt-in (audit R-8): pass the topic explicitly.
leads = find_gaps(llm=StubLLM(grounded_body), topics=["hyperbolic volume formula"])
assert any(lead.kind == "gap" for lead in leads)
def test_find_gaps_no_coverage_gaps_without_explicit_topics(
monkeypatch: pytest.MonkeyPatch,
mock_paper_titles: None,
mock_settings: Path,
) -> None:
"""Without explicit topics, paper titles do NOT auto-generate coverage gaps (R-8)."""
sparse_chunks: list[dict[str, Any]] = [
{
"id": 10,
"paper_id": "springborn-2008",
"ord": 16,
"content": "the hyperbolic volume is V = L(gamma1) + L(gamma2) + L(gamma3)",
"bibkey": "Springborn2008",
},
]
monkeypatch.setattr(
"codex.synthesis._retrieve_chunks",
lambda queries, top_k: [dict(c) for c in sparse_chunks],
)
# No lib_path, no topics → the coverage map must not run despite sparse coverage.
leads = find_gaps(llm=StubLLM("a gap. [Springborn2008 #chunk 16]"))
assert leads == []
def test_find_gaps_explicit_topics_override_default(
monkeypatch: pytest.MonkeyPatch,
mock_paper_titles: None,
mock_settings: Path,
) -> None:
"""Passing topics= explicitly overrides the default (paper titles)."""
captured_queries: list[list[str]] = []
def fake_retrieve(queries: list[str], top_k: int) -> list[dict[str, Any]]:
captured_queries.append(queries)
return [] # forces direct-gap-no-chunks path
monkeypatch.setattr("codex.synthesis._retrieve_chunks", fake_retrieve)
find_gaps(llm=StubLLM(""), topics=["custom topic A", "custom topic B"])
# Both custom topics should have been probed
flat = [q for qs in captured_queries for q in qs]
assert "custom topic A" in flat
assert "custom topic B" in flat