2026-05-18 15:47:13 -07:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
from typing import Any
|
|
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
refactor: consolidate reviewer modules into agent/review/
Part of the domain-reorg adoption (build plan step C2): fork content,
upstream layout. Nine 1:1 module moves (reviewer_diff/eval_store/
findings/groups/publish/reconcile/trace_context + review_style_
collector/guidance) into agent/review/, with internal relative
imports re-wired to the new package depth. agent/review/__init__.py
mirrors upstream's thin re-export shim (one of the 21 verified "A"
structural adds).
Rewrote the 38 grep hits across importer files (agent/{analyzer,
ci_autofix,reviewer,webapp}.py, agent/dashboard/*, agent/middleware/
settle_review_check.py, agent/tools/*, agent/utils/github_feedback.py,
agent/webhooks/github.py, evals/reviewer/*, and the reviewer test
suite) to point at agent.review.*; 4 of the 38 hits were name
collisions (list_reviewer_findings, reviewer_outcomes,
_reviewer_thread_id, reviewer_thread_id — not the moved modules) and
were left untouched. tests/test_github_checks.py's module-alias
import (`from agent import reviewer_publish`) follows upstream's own
`from agent.review import publish as reviewer_publish` pattern so
downstream `reviewer_publish.*` call sites needed no changes.
agent/reviewer.py and agent/webapp.py stay in place per the hard
rule (fork content, import-only rewire) and are not part of this
package.
Gates: ruff check + ruff format --check, pytest --co -q (1637
collected), full unit suite (1637 passed), and the reviewer/findings
suite in isolation (pytest -k "review or finding", 421 passed).
2026-07-17 13:52:03 -04:00
|
|
|
from agent.review.findings import new_finding
|
2026-05-18 15:47:13 -07:00
|
|
|
from evals.reviewer import target
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_eval_target_marks_runs_as_eval_dry_run(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
|
|
|
monkeypatch.delenv("REVIEWER_EVAL_MODEL_ID", raising=False)
|
|
|
|
|
monkeypatch.delenv("REVIEWER_EVAL_REASONING_EFFORT", raising=False)
|
|
|
|
|
configurable = target._build_configurable(
|
|
|
|
|
{
|
|
|
|
|
"repo": "acme/repo",
|
|
|
|
|
"pr_number": 1,
|
|
|
|
|
"pr_url": "https://github.com/acme/repo/pull/1",
|
|
|
|
|
"base_sha": "base",
|
|
|
|
|
"head_sha": "head",
|
|
|
|
|
"head_ref": "branch",
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert configurable["reviewer_eval"] is True
|
|
|
|
|
assert configurable["eval"] is True
|
|
|
|
|
assert configurable["__is_for_execution__"] is True
|
2026-07-17 12:10:54 -04:00
|
|
|
assert configurable["reviewer_eval_cap"] == 6
|
2026-05-18 15:47:13 -07:00
|
|
|
assert "source" not in configurable
|
|
|
|
|
|
|
|
|
|
|
2026-07-17 12:10:54 -04:00
|
|
|
def test_eval_target_passes_configured_cap(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
|
|
|
monkeypatch.setenv("REVIEWER_EVAL_CAP", "1")
|
|
|
|
|
|
|
|
|
|
configurable = target._build_configurable(
|
|
|
|
|
{
|
|
|
|
|
"repo": "acme/repo",
|
|
|
|
|
"pr_number": 1,
|
|
|
|
|
"pr_url": "https://github.com/acme/repo/pull/1",
|
|
|
|
|
"base_sha": "base",
|
|
|
|
|
"head_sha": "head",
|
|
|
|
|
"head_ref": "branch",
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert configurable["reviewer_eval_cap"] == 1
|
|
|
|
|
|
|
|
|
|
|
2026-05-18 15:47:13 -07:00
|
|
|
def test_eval_target_passes_model_overrides(monkeypatch: pytest.MonkeyPatch) -> None:
|
2026-05-28 10:59:29 -07:00
|
|
|
monkeypatch.setenv("REVIEWER_EVAL_MODEL_ID", "anthropic:claude-opus-4-8")
|
2026-05-18 15:47:13 -07:00
|
|
|
monkeypatch.setenv("REVIEWER_EVAL_REASONING_EFFORT", "high")
|
|
|
|
|
|
|
|
|
|
configurable = target._build_configurable(
|
|
|
|
|
{
|
|
|
|
|
"repo": "acme/repo",
|
|
|
|
|
"pr_number": 1,
|
|
|
|
|
"pr_url": "https://github.com/acme/repo/pull/1",
|
|
|
|
|
"base_sha": "base",
|
|
|
|
|
"head_sha": "head",
|
|
|
|
|
"head_ref": "branch",
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
2026-05-28 10:59:29 -07:00
|
|
|
assert configurable["reviewer_model_id"] == "anthropic:claude-opus-4-8"
|
2026-05-18 15:47:13 -07:00
|
|
|
assert configurable["reviewer_reasoning_effort"] == "high"
|
|
|
|
|
|
|
|
|
|
|
feat: tune reviewer for precision — web/wiki tools + recalibrated prompt (#1312)
* feat: tune reviewer for precision — web/wiki tools + recalibrated prompt
Reviewer agent now has web_search, fetch_url, and http_request alongside the
finding tools, so it can verify library semantics and consult the DeepWiki
auto-generated wiki for public repos (https://deepwiki.com/<owner>/<repo>)
before flagging cross-file or architectural concerns.
Prompt rewritten to push precision over recall:
- explicit severity ladder pushing reviews toward bimodal high/low instead of
defaulting to medium
- ≤200-char description target (gold set averages ~186 chars; we were at ~436)
- mandatory docs / wiki / code lookup before flagging concurrency, security,
or perf — the three categories that dominated false positives
- "do not flag" list covering compiler/linter-catchable nits, speculative
claims without a concrete attacker/interleaving/scale, style preferences
the codebase doesn't share, and test-quality nits on non-test diffs
- smart file-selection guidance for large PRs (deprioritize generated /
vendored / pure-rename hunks)
Eval config switched to openai:gpt-5.5 + high reasoning effort for the next
benchmark run.
* trim prompt
* subagent prompting
* confidence ratings
* added medium
* enforce confidence threshold
* .
* reviewer: precision-tuned prompt + drop confidence gate
Rewrites the reviewer system prompt around a defensibility bar (anchor +
failure mode + maintainer wouldn't say "not a bug"), an explicit do-not-file
list (style nits, speculation, scope-policing, same-bug fan-out), and a
checklist of 10 bug archetypes drawn from a per-PR audit of the eval golden
set. The audit showed 145 FPs in the last eval split ~28% speculative, ~26%
style-nit, ~31% real-but-unscored (mostly same-archetype fan-out); the new
prompt targets each class directly.
Confidence is still recorded on every finding for post-hoc calibration but
no longer gates publication — the audit showed the gate was a no-op (agent
self-rated 65% of findings "high" regardless), and the prompt's defensibility
bar is the actual discipline. Drops CONFIDENCE_ORDER, CONFIDENCE_THRESHOLD,
the confidence_threshold kwarg on filter_findings_for_publish, the
confidence_filtered score_mode, and the min_confidence kwarg on the eval
target's _extract_comments — all dead once the gate is gone.
Also removes the "informational" severity tier from the Severity enum,
SEVERITY_ORDER, and all validators / tests / docstrings. It was reserved for
FYI observations the dataset never rewards.
* benchmax
* adding google provider
* slight steering
* tuning
* more tuning
* fix
* cleanup
* reducing overfitting
* Add per-repo review style profiles and inject them into the reviewer.
Dashboard users can analyze historical PR review feedback per repository,
edit the resulting style guide, and have it loaded from LangGraph Store at
reviewer runtime (including Martian eval runs) keyed by owner/name.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix review style job errors leaking exception details to clients.
Return generic dashboard messages while logging full stack traces server-side.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-20 11:35:00 -07:00
|
|
|
def _result_with_findings(findings: list[dict[str, Any]]) -> dict[str, Any]:
|
|
|
|
|
return {"messages": [{"tool_calls": [{"name": "add_finding", "args": f} for f in findings]}]}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_extract_comments_includes_all_confidences() -> None:
|
|
|
|
|
result = _result_with_findings(
|
|
|
|
|
[
|
|
|
|
|
{
|
|
|
|
|
"file": "a.py",
|
|
|
|
|
"severity": "high",
|
|
|
|
|
"confidence": "low",
|
|
|
|
|
"description": "lo",
|
|
|
|
|
"start_line": 1,
|
|
|
|
|
"end_line": 1,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"file": "b.py",
|
|
|
|
|
"severity": "high",
|
|
|
|
|
"confidence": "high",
|
|
|
|
|
"description": "hi",
|
|
|
|
|
"start_line": 2,
|
|
|
|
|
"end_line": 2,
|
|
|
|
|
},
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
comments = target._extract_comments(result)
|
|
|
|
|
assert {c["file"] for c in comments} == {"a.py", "b.py"}
|
|
|
|
|
|
|
|
|
|
|
2026-05-18 15:47:13 -07:00
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_extract_surfaced_comments_uses_publish_filter(
|
|
|
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
|
|
|
) -> None:
|
|
|
|
|
high = new_finding(
|
|
|
|
|
severity="high",
|
feat: tune reviewer for precision — web/wiki tools + recalibrated prompt (#1312)
* feat: tune reviewer for precision — web/wiki tools + recalibrated prompt
Reviewer agent now has web_search, fetch_url, and http_request alongside the
finding tools, so it can verify library semantics and consult the DeepWiki
auto-generated wiki for public repos (https://deepwiki.com/<owner>/<repo>)
before flagging cross-file or architectural concerns.
Prompt rewritten to push precision over recall:
- explicit severity ladder pushing reviews toward bimodal high/low instead of
defaulting to medium
- ≤200-char description target (gold set averages ~186 chars; we were at ~436)
- mandatory docs / wiki / code lookup before flagging concurrency, security,
or perf — the three categories that dominated false positives
- "do not flag" list covering compiler/linter-catchable nits, speculative
claims without a concrete attacker/interleaving/scale, style preferences
the codebase doesn't share, and test-quality nits on non-test diffs
- smart file-selection guidance for large PRs (deprioritize generated /
vendored / pure-rename hunks)
Eval config switched to openai:gpt-5.5 + high reasoning effort for the next
benchmark run.
* trim prompt
* subagent prompting
* confidence ratings
* added medium
* enforce confidence threshold
* .
* reviewer: precision-tuned prompt + drop confidence gate
Rewrites the reviewer system prompt around a defensibility bar (anchor +
failure mode + maintainer wouldn't say "not a bug"), an explicit do-not-file
list (style nits, speculation, scope-policing, same-bug fan-out), and a
checklist of 10 bug archetypes drawn from a per-PR audit of the eval golden
set. The audit showed 145 FPs in the last eval split ~28% speculative, ~26%
style-nit, ~31% real-but-unscored (mostly same-archetype fan-out); the new
prompt targets each class directly.
Confidence is still recorded on every finding for post-hoc calibration but
no longer gates publication — the audit showed the gate was a no-op (agent
self-rated 65% of findings "high" regardless), and the prompt's defensibility
bar is the actual discipline. Drops CONFIDENCE_ORDER, CONFIDENCE_THRESHOLD,
the confidence_threshold kwarg on filter_findings_for_publish, the
confidence_filtered score_mode, and the min_confidence kwarg on the eval
target's _extract_comments — all dead once the gate is gone.
Also removes the "informational" severity tier from the Severity enum,
SEVERITY_ORDER, and all validators / tests / docstrings. It was reserved for
FYI observations the dataset never rewards.
* benchmax
* adding google provider
* slight steering
* tuning
* more tuning
* fix
* cleanup
* reducing overfitting
* Add per-repo review style profiles and inject them into the reviewer.
Dashboard users can analyze historical PR review feedback per repository,
edit the resulting style guide, and have it loaded from LangGraph Store at
reviewer runtime (including Martian eval runs) keyed by owner/name.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix review style job errors leaking exception details to clients.
Return generic dashboard messages while logging full stack traces server-side.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-20 11:35:00 -07:00
|
|
|
confidence="high",
|
2026-05-18 15:47:13 -07:00
|
|
|
category="correctness",
|
|
|
|
|
file="a.py",
|
|
|
|
|
start_line=10,
|
|
|
|
|
end_line=10,
|
|
|
|
|
description="high signal",
|
|
|
|
|
sha="head",
|
|
|
|
|
finding_id="f_high",
|
|
|
|
|
)
|
|
|
|
|
low = new_finding(
|
|
|
|
|
severity="low",
|
feat: tune reviewer for precision — web/wiki tools + recalibrated prompt (#1312)
* feat: tune reviewer for precision — web/wiki tools + recalibrated prompt
Reviewer agent now has web_search, fetch_url, and http_request alongside the
finding tools, so it can verify library semantics and consult the DeepWiki
auto-generated wiki for public repos (https://deepwiki.com/<owner>/<repo>)
before flagging cross-file or architectural concerns.
Prompt rewritten to push precision over recall:
- explicit severity ladder pushing reviews toward bimodal high/low instead of
defaulting to medium
- ≤200-char description target (gold set averages ~186 chars; we were at ~436)
- mandatory docs / wiki / code lookup before flagging concurrency, security,
or perf — the three categories that dominated false positives
- "do not flag" list covering compiler/linter-catchable nits, speculative
claims without a concrete attacker/interleaving/scale, style preferences
the codebase doesn't share, and test-quality nits on non-test diffs
- smart file-selection guidance for large PRs (deprioritize generated /
vendored / pure-rename hunks)
Eval config switched to openai:gpt-5.5 + high reasoning effort for the next
benchmark run.
* trim prompt
* subagent prompting
* confidence ratings
* added medium
* enforce confidence threshold
* .
* reviewer: precision-tuned prompt + drop confidence gate
Rewrites the reviewer system prompt around a defensibility bar (anchor +
failure mode + maintainer wouldn't say "not a bug"), an explicit do-not-file
list (style nits, speculation, scope-policing, same-bug fan-out), and a
checklist of 10 bug archetypes drawn from a per-PR audit of the eval golden
set. The audit showed 145 FPs in the last eval split ~28% speculative, ~26%
style-nit, ~31% real-but-unscored (mostly same-archetype fan-out); the new
prompt targets each class directly.
Confidence is still recorded on every finding for post-hoc calibration but
no longer gates publication — the audit showed the gate was a no-op (agent
self-rated 65% of findings "high" regardless), and the prompt's defensibility
bar is the actual discipline. Drops CONFIDENCE_ORDER, CONFIDENCE_THRESHOLD,
the confidence_threshold kwarg on filter_findings_for_publish, the
confidence_filtered score_mode, and the min_confidence kwarg on the eval
target's _extract_comments — all dead once the gate is gone.
Also removes the "informational" severity tier from the Severity enum,
SEVERITY_ORDER, and all validators / tests / docstrings. It was reserved for
FYI observations the dataset never rewards.
* benchmax
* adding google provider
* slight steering
* tuning
* more tuning
* fix
* cleanup
* reducing overfitting
* Add per-repo review style profiles and inject them into the reviewer.
Dashboard users can analyze historical PR review feedback per repository,
edit the resulting style guide, and have it loaded from LangGraph Store at
reviewer runtime (including Martian eval runs) keyed by owner/name.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix review style job errors leaking exception details to clients.
Return generic dashboard messages while logging full stack traces server-side.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-20 11:35:00 -07:00
|
|
|
confidence="high",
|
2026-05-18 15:47:13 -07:00
|
|
|
category="style",
|
|
|
|
|
file="b.py",
|
|
|
|
|
start_line=20,
|
|
|
|
|
end_line=20,
|
|
|
|
|
description="low signal",
|
|
|
|
|
sha="head",
|
|
|
|
|
finding_id="f_low",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
class Threads:
|
|
|
|
|
async def get(self, _thread_id: str) -> dict[str, Any]:
|
2026-07-17 12:10:54 -04:00
|
|
|
return {
|
|
|
|
|
"metadata": {
|
|
|
|
|
"findings": [high, low],
|
|
|
|
|
"reviewer_eval_publication": {
|
|
|
|
|
"finding_ids": ["f_high"],
|
|
|
|
|
"severity_threshold": "medium",
|
|
|
|
|
"cap": 6,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-18 15:47:13 -07:00
|
|
|
|
|
|
|
|
class Client:
|
|
|
|
|
threads = Threads()
|
|
|
|
|
|
|
|
|
|
monkeypatch.setenv("REVIEWER_EVAL_SEVERITY_THRESHOLD", "medium")
|
|
|
|
|
monkeypatch.setenv("REVIEWER_EVAL_CAP", "4")
|
|
|
|
|
|
2026-07-17 12:10:54 -04:00
|
|
|
comments, publish_completed = await target._extract_surfaced_comments(Client(), "tid")
|
2026-05-18 15:47:13 -07:00
|
|
|
|
|
|
|
|
assert comments == [
|
|
|
|
|
{
|
|
|
|
|
"file": "a.py",
|
|
|
|
|
"line": 10,
|
|
|
|
|
"body": "high signal",
|
|
|
|
|
"severity": "high",
|
|
|
|
|
}
|
|
|
|
|
]
|
2026-07-17 12:10:54 -04:00
|
|
|
assert publish_completed is True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_extract_surfaced_comments_requires_publication_snapshot() -> None:
|
|
|
|
|
class Threads:
|
|
|
|
|
async def get(self, _thread_id: str) -> dict[str, Any]:
|
|
|
|
|
return {"metadata": {"findings": []}}
|
|
|
|
|
|
|
|
|
|
class Client:
|
|
|
|
|
threads = Threads()
|
|
|
|
|
|
|
|
|
|
comments, publish_completed = await target._extract_surfaced_comments(Client(), "tid")
|
|
|
|
|
|
|
|
|
|
assert comments == []
|
|
|
|
|
assert publish_completed is False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_extract_comments_deduplicates_identical_tool_calls() -> None:
|
|
|
|
|
finding = {
|
|
|
|
|
"file": "a.py",
|
|
|
|
|
"severity": "high",
|
|
|
|
|
"description": "Same issue",
|
|
|
|
|
"start_line": 1,
|
|
|
|
|
"end_line": 1,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
comments = target._extract_comments(_result_with_findings([finding, finding]))
|
|
|
|
|
|
|
|
|
|
assert len(comments) == 1
|
2026-06-16 19:38:36 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_completed_counter_increments() -> None:
|
|
|
|
|
start = target.get_completed_count()
|
|
|
|
|
target._record_completed()
|
|
|
|
|
target._record_completed()
|
|
|
|
|
assert target.get_completed_count() == start + 2
|