Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/autopilot_loop/orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -602,8 +602,8 @@ def _detect_bouncing_comments(self, current_comments, current_iteration):
Returns a formatted string warning about bouncing comments, or
empty string if none detected.
"""
if current_iteration < 3:
# Need at least 2 previous iterations to detect a bounce
if current_iteration < 2:
# Need at least 1 previous iteration to detect a bounce
return ""

# Load all previous fix summaries
Expand Down Expand Up @@ -674,7 +674,7 @@ def _detect_bouncing_comments(self, current_comments, current_iteration):
if short_prev in c_body or short_curr in prev_snippet:
bounce_count += 1

if bounce_count >= 2:
if bounce_count >= 1:
bouncing.append({
"path": c_path,
"line": comment.get("line", "?"),
Expand All @@ -687,7 +687,7 @@ def _detect_bouncing_comments(self, current_comments, current_iteration):
return ""

lines = [
"The following comments have bounced back %d+ times after being " % 2
"The following comments have bounced back after being "
+ "'fixed'. This indicates a CIRCULAR REVIEW LOOP where CCR keeps "
+ "reversing your changes.",
"",
Expand Down
111 changes: 54 additions & 57 deletions tests/test_orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -1178,37 +1178,36 @@ def _setup_orchestrator(self, config, iteration=3):
orch.task = persistence.get_task(task_id)
return orch

def test_no_bounce_on_first_iterations(self, config):
"""No bouncing detected on iterations 1-2 (not enough history)."""
orch = self._setup_orchestrator(config, iteration=2)
def test_no_bounce_on_first_iteration(self, config):
"""No bouncing detected on iteration 1 (no history yet)."""
orch = self._setup_orchestrator(config, iteration=1)
comments = [{"id": 1, "path": "a.rb", "line": 5, "body": "fix this"}]
result = orch._detect_bouncing_comments(comments, 2)
result = orch._detect_bouncing_comments(comments, 1)
assert result == ""

def test_bounce_detected_after_two_fixes(self, config, tmp_path):
"""Comment bouncing back after being fixed twice is detected."""
def test_bounce_detected_after_one_fix(self, config, tmp_path):
"""Comment bouncing back after being fixed once is detected."""
import json as _json

orch = self._setup_orchestrator(config, iteration=3)

# Create fix summaries for iterations 1 and 2 β€” both marked "fixed"
for prev_iter in [1, 2]:
summary_path = os.path.join(orch.sessions_dir, "fix-summary-%d.json" % prev_iter)
_json.dump(
[{"comment_id": 1, "status": "fixed", "message": "fixed it"}],
open(summary_path, "w"),
)
review_path = os.path.join(orch.sessions_dir, "review-%d.json" % prev_iter)
_json.dump(
{"body": "", "comments": [
{"id": 1, "path": "a.rb", "line": 5, "body": "make this field optional"},
]},
open(review_path, "w"),
)

# Current iteration 3: same comment reappears
orch = self._setup_orchestrator(config, iteration=2)

# Create fix summary for iteration 1 β€” marked "fixed"
summary_path = os.path.join(orch.sessions_dir, "fix-summary-1.json")
_json.dump(
[{"comment_id": 1, "status": "fixed", "message": "fixed it"}],
open(summary_path, "w"),
)
review_path = os.path.join(orch.sessions_dir, "review-1.json")
_json.dump(
{"body": "", "comments": [
{"id": 1, "path": "a.rb", "line": 5, "body": "make this field optional"},
]},
open(review_path, "w"),
)

# Current iteration 2: same comment reappears
comments = [{"id": 99, "path": "a.rb", "line": 5, "body": "make this field optional please"}]
result = orch._detect_bouncing_comments(comments, 3)
result = orch._detect_bouncing_comments(comments, 2)
assert "CIRCULAR REVIEW LOOP" in result
assert "a.rb" in result
assert "DO NOT fix" in result
Expand All @@ -1217,49 +1216,47 @@ def test_no_bounce_for_different_files(self, config, tmp_path):
"""Comments on different files do not trigger bounce detection."""
import json as _json

orch = self._setup_orchestrator(config, iteration=3)

for prev_iter in [1, 2]:
summary_path = os.path.join(orch.sessions_dir, "fix-summary-%d.json" % prev_iter)
_json.dump(
[{"comment_id": 1, "status": "fixed", "message": "fixed it"}],
open(summary_path, "w"),
)
review_path = os.path.join(orch.sessions_dir, "review-%d.json" % prev_iter)
_json.dump(
{"body": "", "comments": [
{"id": 1, "path": "a.rb", "line": 5, "body": "fix this thing"},
]},
open(review_path, "w"),
)
orch = self._setup_orchestrator(config, iteration=2)

summary_path = os.path.join(orch.sessions_dir, "fix-summary-1.json")
_json.dump(
[{"comment_id": 1, "status": "fixed", "message": "fixed it"}],
open(summary_path, "w"),
)
review_path = os.path.join(orch.sessions_dir, "review-1.json")
_json.dump(
{"body": "", "comments": [
{"id": 1, "path": "a.rb", "line": 5, "body": "fix this thing"},
]},
open(review_path, "w"),
)

# Current comment is on a DIFFERENT file
comments = [{"id": 99, "path": "b.rb", "line": 5, "body": "fix this thing"}]
result = orch._detect_bouncing_comments(comments, 3)
result = orch._detect_bouncing_comments(comments, 2)
assert result == ""

def test_no_bounce_for_skipped_comments(self, config, tmp_path):
"""Skipped comments (not fixed) do not count toward bounce detection."""
import json as _json

orch = self._setup_orchestrator(config, iteration=3)

for prev_iter in [1, 2]:
summary_path = os.path.join(orch.sessions_dir, "fix-summary-%d.json" % prev_iter)
_json.dump(
[{"comment_id": 1, "status": "skipped", "message": "not worth it"}],
open(summary_path, "w"),
)
review_path = os.path.join(orch.sessions_dir, "review-%d.json" % prev_iter)
_json.dump(
{"body": "", "comments": [
{"id": 1, "path": "a.rb", "line": 5, "body": "make optional"},
]},
open(review_path, "w"),
)
orch = self._setup_orchestrator(config, iteration=2)

summary_path = os.path.join(orch.sessions_dir, "fix-summary-1.json")
_json.dump(
[{"comment_id": 1, "status": "skipped", "message": "not worth it"}],
open(summary_path, "w"),
)
review_path = os.path.join(orch.sessions_dir, "review-1.json")
_json.dump(
{"body": "", "comments": [
{"id": 1, "path": "a.rb", "line": 5, "body": "make optional"},
]},
open(review_path, "w"),
)

comments = [{"id": 99, "path": "a.rb", "line": 5, "body": "make optional"}]
result = orch._detect_bouncing_comments(comments, 3)
result = orch._detect_bouncing_comments(comments, 2)
assert result == ""


Expand Down
Loading