From bdea1fb6da72452fc6ef0ca4bab7d343c98e052e Mon Sep 17 00:00:00 2001 From: Johannes du Plessis Date: Thu, 7 May 2026 20:26:59 -0700 Subject: [PATCH] feat(open-swe): always anchor findings to a single line (#1264) * feat(open-swe): always anchor findings to a single line GitHub renders multi-line review comment ranges as walls of context above the comment, which buries the point. Match Devin Review's behavior and always collapse `end_line` to `start_line` so each finding is anchored to the single most relevant line. Removes the (now unused) `MAX_FINDING_RANGE_LINES` cap and `clip_finding_range` helper. * fix(open-swe): collapse end_line before diff-range check --- agent/reviewer.py | 11 +++++----- agent/reviewer_findings.py | 19 ----------------- agent/tools/add_finding.py | 32 ++++++++++------------------- tests/test_reviewer_tools.py | 40 ++++++------------------------------ 4 files changed, 22 insertions(+), 80 deletions(-) diff --git a/agent/reviewer.py b/agent/reviewer.py index f419e319..cfdcb018 100644 --- a/agent/reviewer.py +++ b/agent/reviewer.py @@ -109,12 +109,11 @@ Cloning is optional — for most PRs the diff alone is enough. issue worth surfacing; `low` = small nit; `informational` = FYI / context, not a flaw. Inflated severities erode trust — be honest. - `category`: e.g. `correctness`, `security`, `perf`, `style`, `flag`. - - `file`, `start_line`, `end_line`: anchor inside the PR diff. Keep - ranges tight — anchor to the few lines that actually matter (the call - site, the signature line, the conditional). **Do not range-select an - entire function**; GitHub renders the full range as context above the - comment and buries the point. Ranges over ~10 lines are auto-collapsed - to the start line. + - `file`, `start_line`: anchor the comment to a single line inside the + PR diff — the call site, the signature line, the conditional that's + actually wrong. The tool always anchors to one line because GitHub + renders multi-line ranges as walls of context that bury the comment. + `end_line` is accepted for API compatibility but ignored. - `description`: what's wrong, in 1–4 sentences. Markdown is fine. - `suggestion`: **only** include for small, obvious fixes that fit in 4 lines or fewer — a one-liner rename, a missing guard, a typo, a flipped diff --git a/agent/reviewer_findings.py b/agent/reviewer_findings.py index e7c134d7..19214c87 100644 --- a/agent/reviewer_findings.py +++ b/agent/reviewer_findings.py @@ -29,13 +29,6 @@ REVIEWER_THREAD_KIND = "reviewer" # longer suggestions; the description still gets posted on its own. MAX_SUGGESTION_LINES = 4 -# Anchoring a finding to a giant range (e.g. an entire function) makes the -# GitHub comment dump dozens of lines of context above the actual review -# text, which buries the point. When the range exceeds this, collapse it to -# the first line — the comment still anchors near the issue without showing -# the whole block. ~5-line ranges are still useful, so the cap is generous. -MAX_FINDING_RANGE_LINES = 10 - def clip_suggestion(suggestion: str | None) -> tuple[str | None, bool]: """Return (suggestion_or_none, was_dropped). Drops if over the line cap.""" @@ -46,18 +39,6 @@ def clip_suggestion(suggestion: str | None) -> tuple[str | None, bool]: return suggestion, False -def clip_finding_range( - start_line: int | None, - end_line: int | None, -) -> tuple[int | None, int | None, bool]: - """Return (start, end, was_collapsed). Collapses end→start if over cap.""" - if start_line is None or end_line is None: - return start_line, end_line, False - if end_line - start_line + 1 > MAX_FINDING_RANGE_LINES: - return start_line, start_line, True - return start_line, end_line, False - - Severity = Literal["informational", "low", "medium", "high", "critical"] FindingStatus = Literal["open", "resolved", "dismissed"] DiffSide = Literal["LEFT", "RIGHT"] diff --git a/agent/tools/add_finding.py b/agent/tools/add_finding.py index 7ae5c3d7..1f59fb0e 100644 --- a/agent/tools/add_finding.py +++ b/agent/tools/add_finding.py @@ -9,13 +9,11 @@ from langgraph.config import get_config from ..reviewer_diff import is_range_in_diff from ..reviewer_findings import ( - MAX_FINDING_RANGE_LINES, MAX_SUGGESTION_LINES, DiffSide, Finding, Severity, append_finding, - clip_finding_range, clip_suggestion, get_thread_id_from_runtime, new_finding, @@ -54,15 +52,14 @@ def add_finding( ``style``, ``flag``, etc.). Free-form; used for grouping in the UI. file: Repo-relative path of the file the finding refers to. description: Markdown body the user sees. - start_line: 1-based start line in the new (post-PR) file. Equal to - ``end_line`` for single-line findings; less than ``end_line`` for - ranges. Omit (with ``end_line``) for file-level findings. - end_line: 1-based end line, inclusive. Defaults to ``start_line``. - Keep ranges tight — anchor to the few lines that actually matter - for the comment. Ranges over 10 lines are auto-collapsed to a - single line because GitHub renders the full range as context - above the comment, which buries the point. Don't anchor to an - entire function; pick the call site or signature line instead. + start_line: 1-based line in the new (post-PR) file to anchor the + comment to. Pick the single most relevant line — the call site, + the signature, or the line the issue is actually about. Omit + (with ``end_line``) for file-level findings. + end_line: Accepted for API compatibility, but findings are always + anchored to a single line (``start_line``). GitHub renders + multi-line ranges as walls of context that bury the comment, so + we collapse to one line for a cleaner comment. suggestion: Replacement text for ``start_line..end_line``. When set, the published GitHub comment includes a ```suggestion``` block so the user can click "Commit suggestion". **Only set this for small, @@ -89,6 +86,9 @@ def add_finding( if start_line is not None and end_line is not None and end_line < start_line: return {"success": False, "error": "end_line must be >= start_line"} + if start_line is not None: + end_line = start_line + config = get_config() configurable = config.get("configurable", {}) if isinstance(config, dict) else {} diff_line_set = configurable.get("diff_line_set") if isinstance(configurable, dict) else None @@ -106,8 +106,6 @@ def add_finding( ), } - start_line, end_line, range_collapsed = clip_finding_range(start_line, end_line) - diff_hunk: str | None = None if isinstance(diff_text, str) and diff_text: from ..reviewer_diff import extract_diff_hunk @@ -139,14 +137,6 @@ def add_finding( "dropped — the finding was recorded with description only. Only " "include `suggestion` for small, obvious fixes." ) - if range_collapsed: - result["range_collapsed"] = True - result["range_warning"] = ( - f"Range exceeded the {MAX_FINDING_RANGE_LINES}-line cap and was " - f"collapsed to a single line ({start_line}). Anchor findings to " - "the most relevant line — GitHub renders large ranges as walls " - "of context that bury the comment." - ) return result diff --git a/tests/test_reviewer_tools.py b/tests/test_reviewer_tools.py index 4cc0c6a1..d923cd82 100644 --- a/tests/test_reviewer_tools.py +++ b/tests/test_reviewer_tools.py @@ -80,7 +80,7 @@ def test_add_finding_persists_to_thread_metadata() -> None: assert persisted_thread == "tid-1" assert persisted["file"] == "foo.py" assert persisted["start_line"] == 11 - assert persisted["end_line"] == 12 + assert persisted["end_line"] == 11 assert persisted["suggestion"] == "renamed = 1" assert persisted["status"] == "open" assert persisted["first_seen_sha"] == "sha-head" @@ -175,35 +175,8 @@ def test_add_finding_keeps_short_suggestion() -> None: assert captured[0]["suggestion"] == short_suggestion -def test_add_finding_collapses_oversized_range() -> None: - captured: list[Any] = [] - - async def fake_append(thread_id: str, finding: Any) -> Any: - captured.append(finding) - return finding - - with ( - patch("agent.tools.add_finding.get_config", return_value=_config()), - patch("agent.tools.add_finding.get_thread_id_from_runtime", return_value="tid-1"), - patch("agent.tools.add_finding.append_finding", side_effect=fake_append), - ): - result = add_finding( - severity="medium", - category="correctness", - file="foo.py", - description="big function", - start_line=15, - end_line=40, # 26 lines — well over the cap - ) - - assert result["success"] is True - assert result.get("range_collapsed") is True - assert "range_warning" in result - assert captured[0]["start_line"] == 15 - assert captured[0]["end_line"] == 15 - - -def test_add_finding_keeps_small_range() -> None: +def test_add_finding_always_collapses_to_single_line() -> None: + """Multi-line ranges are always collapsed to ``start_line``.""" captured: list[Any] = [] async def fake_append(thread_id: str, finding: Any) -> Any: @@ -219,15 +192,14 @@ def test_add_finding_keeps_small_range() -> None: severity="low", category="style", file="foo.py", - description="five lines", + description="anchor on start_line", start_line=15, - end_line=19, # 5 lines — within the cap + end_line=19, ) assert result["success"] is True - assert "range_collapsed" not in result assert captured[0]["start_line"] == 15 - assert captured[0]["end_line"] == 19 + assert captured[0]["end_line"] == 15 def test_update_finding_rejects_long_suggestion_without_clobbering() -> None: