Repository navigation
Search Mathlib and the pool before asking whether a PR is new - #306
Conversation
The audited rules ask whether a contribution's headline is already proved. The reviewer had no way to find out — rules, PR description, and diff, nothing else — so its first production run answered `already_formalized` with "unverifiable from the supplied diff". Honest, and useless: #275 was approved with zero findings and then closed by hand for duplicating `SimpleGraph.nonempty_hom_of_forall_finite_subgraph_hom`, and #225 was closed as subsumed by the pool's own `Incompleteness`. Two searches now run before the model is asked, and their results go into the prompt as evidence. **Mathlib.** Every headline the PR adds — the `(declaration, informal)` pairs already in its card — goes to LeanExplore's hybrid name-and-meaning search. Put through it, #275's claim returns the exact declaration it duplicated, fifth of six; #285's returns Lagrange four-squares and weighted sums of squares, i.e. nothing, which is the right answer for a genuinely new result. The evidence reads in both directions, so an empty result is usable as evidence of novelty rather than a shrug. **The pool.** All 141 projects as `slug: title [tags]`, about 4k tokens. No similarity heuristic beats handing over the complete list, and #225 shows pooled prior art counts the same as Mathlib's. Pre-computed rather than offered as a tool the model may or may not think to call: deterministic, testable, one round trip. Only project and challenge PRs pay for it — a refactor re-opens a question settled when the thing it touches merged — and only for headlines the PR actually adds. The results are our own output, so they sit outside the fenced contributor span, but the rules say plainly that a hit is a candidate and that shared vocabulary is not duplication. A search that cannot run is never fatal: no key, no backend, or bad YAML each degrade to a stated "not searched", which the rules distinguish from a clean miss so silence is never read as an absence of prior art. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Greptile SummaryThis PR introduces pre-computed Mathlib and pool prior-art search for
Confidence Score: 5/5Safe to merge. The new prior-art path degrades gracefully at every failure point — missing key, backend error, unreadable registry, or per-claim timeout each produce a clearly-labelled 'not searched' note rather than a crash or a false clean-miss. Both previously flagged defects are fixed in this head: fetch_file_at no longer pairs the raw Accept header with --jq, and per-claim async failures map to None in the hits dict rather than being dropped. The render function distinguishes None (search errored) from [] (search ran, nothing found) from absent key entirely. The 316-test suite, including regression coverage for the #275 duplicate case and the unreadable-registry edge case, exercises every meaningful failure mode. Files Needing Attention: No files require special attention.
|
| Filename | Overview |
|---|---|
| python/lean_pool/prior_art.py | New module implementing pre-computed Mathlib + pool prior-art search; correctly maps per-claim async failures to None (distinct from an empty hit list), degrades gracefully on missing API key or backend failure, and renders search results as candidates not verdicts. |
| python/lean_pool/review.py | Adds fetch_file_at (using raw Accept header, no --jq) and gather_prior_art; wires the prior-art section into request_review; both previously flagged issues (incompatible gh-API flags, per-claim failures masking as novelty) are resolved in this head. |
| .github/workflows/llm-review.yml | Adds LEANEXPLORE_API_KEY env-var injection to the review step; without this the secret would never reach search_mathlib even if set as a repo secret. |
| python/tests/test_prior_art.py | Comprehensive tests covering new-claims scoping, per-claim failure mapping to None, clean-miss vs. failure distinction, unreadable registry, and the #275 regression case; all important failure modes are exercised. |
| python/tests/conftest.py | Adds a lean_explore stub alongside the existing openai stub; uses setdefault so developers with the review group installed exercise the real package, while CI without it uses the stub. |
| .github/REVIEW_RULES.md | Check 3 rewritten to use the now-supplied Prior Art section; unverifiable is reserved for searches that could not run, and hits are explicitly framed as candidates not verdicts. |
| python/pyproject.toml | Adds lean-explore>=0.4 to the review dependency group; lock resolves to 1.2.1. |
| python/tests/test_review.py | Existing test suite unchanged structurally; prior-art integration tests for gather_prior_art / fetch_file_at are covered in test_prior_art.py. |
Sequence Diagram
sequenceDiagram
participant W as Workflow (llm-review.yml)
participant M as main()
participant G as gather_prior_art()
participant F as fetch_file_at()
participant GH as GitHub API
participant S as search_mathlib()
participant LE as LeanExplore API
participant R as render()
participant LLM as OpenAI Model
W->>M: uv run python -m lean_pool.review
M->>M: classify_pr() → kind
Note over M: kind in {project, challenge, refactor, solution, infra}
M->>G: gather_prior_art(kind, head_sha, repo)
alt kind not in (project, challenge)
G-->>M: None
else
G->>F: fetch_file_at(registry, head_sha)
F->>GH: "GET repos/{repo}/contents/{registry}?ref={sha} Accept: raw+json"
GH-->>F: raw YAML bytes
F-->>G: head_text (or empty string on error)
G->>G: read base_text from disk (checked-out base branch)
G->>G: new_claims(head_text, base_text, kind)
G->>S: search_mathlib(claims)
alt LEANEXPLORE_API_KEY not set
S-->>G: "({}, reason)"
else per-claim async search
S->>LE: asyncio.gather([client.search(claim.query) ...])
LE-->>S: hits per claim (or exception mapped to None)
S-->>G: (hits_by_declaration, None)
end
G->>G: read projects.yml for pool index
G->>R: render(claims, hits, projects_text, unavailable)
R-->>G: prior_art_section (Markdown)
G-->>M: prior_art_section
end
M->>LLM: request_review(rules, diff, prior_art_section, ...)
LLM-->>M: structured JSON verdict
M->>GH: post_comment(render_comment(...))
Reviews (4): Last reviewed commit: "Stub lean_explore for the test environme..." | Re-trigger Greptile
|
LLM review skipped: PR touches no |
`--jq ".content"` was paired with `Accept: application/vnd.github.raw+json`. The raw media type returns file bytes, so gh tried to parse YAML as JSON, printed "invalid character 'p' looking for beginning of value", and exited 1. That was caught and turned into "", which `new_claims` read as an empty registry — so every prior-art section would have said "this PR adds no new headline", and no Mathlib search would ever have run. My dry run missed it by fabricating the head registry instead of fetching one. Verified against PR #285's head now: 248k characters, three headlines. Two guards so the failure cannot be silent again. `gather_prior_art` distinguishes an unreadable registry from a PR that genuinely adds nothing — both give zero claims, but only one means prior art went unchecked — and a test pins the gh argument shape, since the broken and working forms differ only by a flag. Caught by Greptile on #306. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Verified and fixed — that was a real, total failure of the feature, not a lint nit.
My own dry run missed it because it fabricated the head registry instead of fetching one. Against PR #285's real head the fixed call returns 248,015 characters and three headlines. Added two guards so it cannot fail silently again: |
|
LLM review skipped: PR touches no |
`asyncio.gather(return_exceptions=True)` hands back exceptions inline, and `_search_all` was filtering them out — so a headline whose query timed out vanished from the mapping, and `render` fell through to "No Mathlib declaration matched this statement." One flaky query would have been reported as evidence of novelty, which is the exact confusion this module exists to prevent, one level down from the fetch bug. A failed claim now maps to None and renders as unchecked. Both seams are tested: the gather, and what the section says about each outcome. Caught by Greptile on #306. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Fixed in 8faf8f5 — verified, and it's the same bug class as the fetch one, a level down.
A failed claim now maps to |
The suite installs only the test group, so `prior_art._search_all`'s import of `lean_explore.api` failed in CI while passing locally, where I had run `uv sync --group review`. conftest already stubs `openai` for exactly this reason; `lean_explore` belongs beside it. Both use setdefault, so a machine with the real packages still exercises them. Verified the way I should have the first time — `uv sync --locked --group test` and `uv run pytest`, which is what CI runs, with lean_explore genuinely absent. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
LLM review skipped: PR touches no |
1 similar comment
|
LLM review skipped: PR touches no |
…313) Adopts Tau Ceti's single-angle review structure for project PRs. The 148-review audit showed one narrative call letting the overall impression swamp specific checks: the two worst misses (#275, #278) were long approving summaries with zero findings, and `fit` was ~96% collinear with the verdict it sat next to. Five independent calls per project PR, each on the shared core plus one rubric under .github/review-rubrics/: faithfulness, novelty (carrying the #306 prior-art evidence), significance, and sources blocking; quality advisory — in 148 reviews that dimension never justified a veto, so its block records as discuss. The overall verdict is computed from the rubric verdicts, never asked of a model: any blocking block → request_changes, any discuss → needs_discussion — and an elided-diff review cannot approve, now enforced in code rather than requested in prose. An unreadable rubric verdict aggregates as discuss, never as a silent pass. One sticky comment: rubric table with bottom lines, combined facts table, findings grouped by rubric with evidence, usage footer summing tokens with each call priced at its own tier. ~$0.50-0.70 per project review at flex/xhigh vs ~$0.13 for the monolithic call. Refactor, challenge, and solution modes unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Closes the one gap the audit left open.
REVIEW_RULES.mdasks whether a headline is already proved; the reviewer had no way to find out. Its first production run under the new rules answeredalready_formalizedwith "unverifiable from the supplied diff" — honest, and useless.That question has already cost two merges: #275 was approved with zero findings and closed by hand for duplicating
SimpleGraph.nonempty_hom_of_forall_finite_subgraph_hom, and #225 was closed as subsumed by the pool's ownIncompleteness.It finds the real duplicate, and doesn't invent one
I put both PRs' actual headlines through the live index before building anything:
SimpleGraph.Colorable,SimpleGraph.chromaticNumber, …SimpleGraph.nonempty_hom_of_forall_finite_subgraph_hom— "The De Bruijn–Erdős Theorem for Graph Homomorphisms" (5th of 6)Set.Finite.induction_on_subset,Nat.sum_four_squares,UniqueSums— nothing relevantThe miss is recoverable and a genuinely new result stays clean, so an empty result is usable as evidence of novelty rather than a shrug. Note the real hit ranked fifth — hence six hits per claim, not one.
What gets searched
(declaration, informal)pairs already in the card — precise, and typically 1–5 queries per PR.slug: title [tags]form, ~4k tokens. No similarity heuristic beats handing over the complete list, and Import godel-loeb formalization #225 shows pooled prior art counts the same as Mathlib's.Design notes
Pre-computed, not a tool call. Deterministic, unit-testable, one round trip whether or not the model would have thought to look. It also sidesteps the reported
reasoning_effort+ function-tools incompatibility ongpt-5.6-sol, which would have forced a choice between searching andxhigh.Scoped. Only
projectandchallengePRs, and only headlines the PR adds — a refactor or a bump would otherwise re-litigate prior art settled when the thing it touches merged.Trust. These are our results, so they sit outside the contributor fence from #293 — but the section and check 3 both say a hit is a candidate, that shared vocabulary is not duplication, and that the declaration must be read before anything is called a duplicate.
Never fatal. Missing key, backend down, or malformed YAML each degrade to a stated "not searched", which the rules distinguish from a clean miss so silence is never mistaken for absence of prior art.
LEANEXPLORE_API_KEYwas already a repo secret; it's now passed to the review step.Check 3 is rewritten to use the evidence: if the search ran and found nothing, say so and leave
already_formalizedempty;unverifiableis only for a search that could not run, and belongs in the prose rather than in a field that holds a declaration name.Verified the
lean-exploreAPI against the installed package rather than assuming —SearchResulthas nodescriptionfield, so the prose comes frominformalizationwithdocstringas fallback. 316 tests, including a regression test built from the #275 case.🤖 Generated with Claude Code