Skip to content

Commit 4ea7c7a

Browse files
chefsalecursoragent
andcommitted
LGTM-with-comments docs and tests; fix order_changed false positive; webhook env validation
Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 1630f1f commit 4ea7c7a

4 files changed

Lines changed: 114 additions & 20 deletions

File tree

src/ai_reviewer/github/formatter.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,9 @@ def get_review_action_with_delta(
410410
) -> str:
411411
"""Determine GitHub review action considering the delta.
412412
413+
LGTM-with-comments: REQUEST_CHANGES only for critical findings. When there are
414+
only warnings, suggestions, or nitpicks, returns COMMENT so the author isn't blocked.
415+
413416
Args:
414417
review: Consolidated review
415418
delta: Review delta
@@ -418,25 +421,25 @@ def get_review_action_with_delta(
418421
Returns:
419422
GitHub review action
420423
"""
421-
# If all issues are resolved, approve (if allowed)
422424
if delta.all_issues_resolved and allow_approve:
423425
return "APPROVE"
424426

425-
# If there are critical issues (new or open), request changes
426-
# But only if allow_approve is True (i.e., not in GitHub Actions)
427-
# GitHub Actions can't approve, and REQUEST_CHANGES blocks merging
427+
# Block merge only when there are critical findings (not warnings/suggestions/nitpicks)
428428
has_critical = any(
429429
f.severity.value == "critical" for f in delta.new_findings + delta.open_findings
430430
)
431431
if has_critical and allow_approve:
432432
return "REQUEST_CHANGES"
433433

434-
# In GitHub Actions or no critical issues, just comment
434+
# No critical: COMMENT (includes only nits/suggestions/warnings — don't block author)
435435
return "COMMENT"
436436

437437
def get_review_action(self, review: ConsolidatedReview, allow_approve: bool = True) -> str:
438438
"""Determine the GitHub review action based on findings.
439439
440+
LGTM-with-comments: REQUEST_CHANGES only for critical findings. When there are
441+
only warnings, suggestions, or nitpicks, returns COMMENT so the author isn't blocked.
442+
440443
Args:
441444
review: Consolidated review
442445
allow_approve: Whether to allow APPROVE/REQUEST_CHANGES actions
@@ -445,14 +448,12 @@ def get_review_action(self, review: ConsolidatedReview, allow_approve: bool = Tr
445448
Returns:
446449
GitHub review action: "APPROVE", "REQUEST_CHANGES", or "COMMENT"
447450
"""
448-
# REQUEST_CHANGES blocks merging - only use when allow_approve is True
449-
# (i.e., running locally with proper permissions)
451+
if not review.findings and allow_approve:
452+
return "APPROVE"
453+
# Block merge only on critical; warnings/suggestions/nitpicks → COMMENT
450454
if review.has_critical_issues and allow_approve:
451455
return "REQUEST_CHANGES"
452-
elif not review.findings and allow_approve:
453-
return "APPROVE"
454-
else:
455-
return "COMMENT"
456+
return "COMMENT"
456457

457458

458459
def format_review_as_json(review: ConsolidatedReview) -> dict:

src/ai_reviewer/github/webhook.py

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -140,10 +140,32 @@ async def default_review_handler(repo: str, pr_number: int) -> None:
140140
logger.error("GITHUB_TOKEN not set")
141141
return
142142

143+
try:
144+
cursor_timeout = int(os.environ.get("CURSOR_TIMEOUT", "300"))
145+
except ValueError:
146+
cursor_timeout = 300
147+
logger.warning("CURSOR_TIMEOUT invalid, using default 300")
148+
149+
try:
150+
num_agents = int(os.environ.get("NUM_AGENTS", "3"))
151+
except ValueError:
152+
num_agents = 3
153+
logger.warning("NUM_AGENTS invalid, using default 3")
154+
155+
try:
156+
min_agreement = float(os.environ.get("MIN_VALIDATION_AGREEMENT", str(2 / 3)))
157+
except ValueError:
158+
min_agreement = 2 / 3
159+
logger.warning("MIN_VALIDATION_AGREEMENT invalid, using default 2/3")
160+
143161
cursor_config = CursorConfig(
144162
api_key=cursor_api_key,
145163
base_url=os.environ.get("CURSOR_BASE_URL", "https://api.cursor.com/v0"),
146-
timeout=int(os.environ.get("CURSOR_TIMEOUT", "300")),
164+
timeout=cursor_timeout,
165+
)
166+
167+
enable_cross_review = (
168+
os.environ.get("ENABLE_CROSS_REVIEW", "true").lower() != "false"
147169
)
148170

149171
try:
@@ -152,12 +174,9 @@ async def default_review_handler(repo: str, pr_number: int) -> None:
152174
pr_number=pr_number,
153175
cursor_config=cursor_config,
154176
github_token=github_token,
155-
num_agents=int(os.environ.get("NUM_AGENTS", "3")),
156-
enable_cross_review=os.environ.get("ENABLE_CROSS_REVIEW", "true").lower()
157-
!= "false",
158-
min_validation_agreement=float(
159-
os.environ.get("MIN_VALIDATION_AGREEMENT", str(2 / 3))
160-
),
177+
num_agents=num_agents,
178+
enable_cross_review=enable_cross_review,
179+
min_validation_agreement=min_agreement,
161180
)
162181

163182
if review.all_agents_failed:

src/ai_reviewer/review.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -333,9 +333,10 @@ def apply_cross_review(
333333

334334
new_findings = [x[0] for x in kept]
335335
n_dropped = len(review.findings) - len(new_findings)
336-
original_ids = [f.id for f in review.findings]
337336
new_ids = [f.id for f in new_findings]
338-
order_changed = original_ids != new_ids
337+
# Compare only relative order of retained findings (avoid false positive when findings dropped)
338+
remaining_original_order = [f.id for f in review.findings if f.id in set(new_ids)]
339+
order_changed = remaining_original_order != new_ids
339340

340341
summary = review.summary
341342
if n_dropped > 0 or order_changed:

tests/test_github.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,79 @@ def test_determines_review_action(self):
304304
# This is intentional - REQUEST_CHANGES blocks merging and Actions can't approve to unblock
305305
assert formatter.get_review_action(critical_review, allow_approve=False) == "COMMENT"
306306

307+
# LGTM-with-comments: only nits/suggestions (no critical/warning) → COMMENT (don't block author)
308+
nits_only_review = ConsolidatedReview(
309+
id="review-nits",
310+
created_at=datetime.now(),
311+
repo="test/repo",
312+
pr_number=42,
313+
findings=[
314+
ConsolidatedFinding(
315+
id="f1",
316+
file_path="test.py",
317+
line_start=1,
318+
line_end=2,
319+
severity=Severity.SUGGESTION,
320+
category=Category.STYLE,
321+
title="Consider renaming",
322+
description="Optional",
323+
suggested_fix=None,
324+
consensus_score=0.5,
325+
agreeing_agents=["a1"],
326+
confidence=0.8,
327+
),
328+
ConsolidatedFinding(
329+
id="f2",
330+
file_path="test.py",
331+
line_start=3,
332+
line_end=3,
333+
severity=Severity.NITPICK,
334+
category=Category.STYLE,
335+
title="Nit: trailing space",
336+
description="Style",
337+
suggested_fix=None,
338+
consensus_score=0.3,
339+
agreeing_agents=["a1"],
340+
confidence=0.7,
341+
),
342+
],
343+
summary="Minor suggestions",
344+
agent_count=2,
345+
review_quality_score=0.9,
346+
total_review_time_ms=1500,
347+
)
348+
assert formatter.get_review_action(nits_only_review) == "COMMENT"
349+
assert formatter.get_review_action(nits_only_review, allow_approve=True) == "COMMENT"
350+
351+
# Only warnings (no critical) → COMMENT (we don't block on warnings)
352+
warnings_only_review = ConsolidatedReview(
353+
id="review-warn",
354+
created_at=datetime.now(),
355+
repo="test/repo",
356+
pr_number=42,
357+
findings=[
358+
ConsolidatedFinding(
359+
id="f1",
360+
file_path="test.py",
361+
line_start=1,
362+
line_end=1,
363+
severity=Severity.WARNING,
364+
category=Category.LOGIC,
365+
title="Edge case",
366+
description="Consider handling",
367+
suggested_fix=None,
368+
consensus_score=1.0,
369+
agreeing_agents=["a1"],
370+
confidence=0.85,
371+
),
372+
],
373+
summary="One warning",
374+
agent_count=1,
375+
review_quality_score=0.85,
376+
total_review_time_ms=1000,
377+
)
378+
assert formatter.get_review_action(warnings_only_review) == "COMMENT"
379+
307380

308381
class TestResolveFixedComments:
309382
"""Tests for duplicate detection and resolved comment handling."""

0 commit comments

Comments
 (0)