Skip to content

Commit 29068db

Browse files
DhruvBhatia0claude
andcommitted
feat: add completeness investigation principle and online eval pipeline
- Add "VERIFY COMPLETENESS OF NEW ADDITIONS" principle to system prompt (resource cleanup, DB constraints, auth parity, read/write consistency) - Add skip_post mode to review API for eval without posting to GitHub - Add online_eval.py pipeline (discover → enrich → review → judge) - Lower confidence thresholds from 0.99 to 0.50-0.70 per category - Track eval results in eval_results.md (best: F1=0.418, P=0.301, R=0.128) - Gitignore output artifacts and eval result files Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent e67a055 commit 29068db

72 files changed

Lines changed: 841 additions & 12660 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,8 @@
33
**/.wrangler/
44
pr_clones/
55
code-review-benchmark/
6+
pr_review_agent/output/
7+
online_eval_results*.json
8+
online_eval.db*
69
.env
710
plan.md

eval_results.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Online Eval Results
2+
3+
Evaluating prompt changes on the same set of ~91 PRs (skip-discover, skip-enrich).
4+
Using 10 workers, gpt-5.4, skip_post=True.
5+
6+
## Baseline (Run 2 — 0.70 thresholds, no borderline line, extensive search line)
7+
8+
| Metric | All PRs | PRs w/ GT (52) |
9+
|--------|---------|----------------|
10+
| Suggestions | 208 | 125 |
11+
| Matched suggestions | 36 | 36 |
12+
| Ground truth | 300 | 300 |
13+
| Precision | 0.173 | 0.288 |
14+
| Recall | 0.120 | 0.120 |
15+
| Mean F1 || 0.370 (26 PRs) |
16+
17+
## Attempt 1 — Add code quality paragraph + restore borderline bug line
18+
19+
**Changes:**
20+
- Restored "it is better to report a borderline real bug than to miss one" in system.py and reviewer.py
21+
- Added paragraph: "ALSO LOOK FOR CODE QUALITY ISSUES THE AUTHOR WOULD FIX" — inconsistent naming/style, unnecessarily complex logic, duplicated code, dead code, unused imports
22+
- Kept 0.70 thresholds and extensive search line
23+
24+
**Results:**
25+
26+
| Metric | All PRs (90) | PRs w/ GT (51) |
27+
|--------|--------------|----------------|
28+
| Suggestions | 195 | 123 |
29+
| Matched suggestions | 30 | 30 |
30+
| Ground truth | 271 | 271 |
31+
| Precision | 0.154 | 0.244 |
32+
| Recall | 0.111 | 0.111 |
33+
| Mean F1 || 0.369 (21 PRs) |
34+
35+
**Analysis:** Code quality paragraph was net negative. Generated 5 more non-bug suggestions (21 vs 16) but only 1 matched. Crowded out bug-finding: 18 fewer bug suggestions, 6 lost matches. Lost matches on 12 PRs vs gained on 8.
36+
37+
## Attempt 2 — Remove code quality, add completeness principle
38+
39+
**Changes:**
40+
- Removed "ALSO LOOK FOR CODE QUALITY ISSUES" paragraph (proven harmful)
41+
- Added investigation principle #5: "VERIFY COMPLETENESS OF NEW ADDITIONS" — resource cleanup/teardown, DB constraints matching business logic, API endpoint authorization, read/write pair consistency, feature flag both-paths
42+
- Kept borderline bug line, 0.70 thresholds, extensive search line
43+
44+
**Results:**
45+
46+
| Metric | All PRs (85) | PRs w/ GT (51) |
47+
|--------|--------------|----------------|
48+
| Suggestions | 195 | 123 |
49+
| Matched suggestions | 37 | 37 |
50+
| Ground truth | 290 | 290 |
51+
| Precision | 0.190 | 0.301 |
52+
| Recall | 0.128 | 0.128 |
53+
| Mean F1 || 0.418 (25 PRs) |
54+
55+
**Analysis:** Best run so far. Removing code quality paragraph refocused the model on bugs. Completeness principle helped find resource cleanup, constraint, and lifecycle bugs. +13 gained matches vs -9 lost on shared PR set. Bug match rate improved from 16% to 19%, security from 10% to 18%.
56+
57+
## Attempt 3 — Add config/infrastructure category + investigation budget emphasis
58+
59+
**Changes:**
60+
- Added bug category #15: "Configuration / infrastructure" — missing env var mappings, Dockerfile steps, CI workflow permissions, version pins
61+
- Strengthened "DON'T STOP AT THE FIRST FINDING" with "Pay equal attention to config files as to source code"
62+
63+
**Results:**
64+
65+
| Metric | All PRs (91) | PRs w/ GT (52) |
66+
|--------|--------------|----------------|
67+
| Suggestions | 209 ||
68+
| Matched suggestions | 31 | 31 |
69+
| Ground truth | 299 | 299 |
70+
| Precision | 0.148 | 0.263 |
71+
| Recall | 0.104 | 0.104 |
72+
| Mean F1 || 0.363 (24 PRs) |
73+
74+
**Analysis:** Regressed vs Attempt 2. Same pattern as Attempt 1 — broadening scope dilutes bug-finding focus. Generated 14 more suggestions but 6 fewer matches. Lost on 15 shared PRs vs gained on 8. Config emphasis caused the model to spend cognitive budget on config analysis at the expense of core logic bugs.
75+
76+
## Summary
77+
78+
| Run | P (GT) | Recall | Mean F1 | Key Change |
79+
|-----|--------|--------|---------|------------|
80+
| Baseline | 0.288 | 0.120 | 0.370 | 0.70 thresholds, no borderline, extensive search |
81+
| Attempt 1 | 0.244 | 0.111 | 0.369 | + code quality paragraph, + borderline bug line |
82+
| **Attempt 2** | **0.301** | **0.128** | **0.418** | - code quality, + completeness principle |
83+
| Attempt 3 | 0.263 | 0.104 | 0.363 | + config/infra category, + config emphasis |
84+
85+
**Best: Attempt 2.** Key insight: the model performs best when its scope is tightly focused on runtime defects + structural completeness. Any instruction that broadens scope to non-bug categories (code quality, config/infra) dilutes focus and hurts precision without meaningfully improving recall.

github_app/app.py

Lines changed: 48 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -103,13 +103,17 @@ class ReviewRequest(BaseModel):
103103
model: str = "" # Override model name (e.g. "gpt-5.4", "gemini-3.1-pro-preview")
104104
openai_api_key: str = "" # Override OpenAI API key (optional, falls back to env)
105105
google_api_key: str = "" # Override Google API key (optional, falls back to env)
106+
skip_post: bool = False # Run review but don't post to GitHub (for evals)
106107

107108

108109
REVIEW_API_SECRET = os.environ.get("REVIEW_API_SECRET", "") or os.environ.get("GHAPP_INTERNAL_SECRET", "")
109110

110111

111-
async def _run_review_from_api(req: ReviewRequest):
112-
"""Run the review pipeline from an API request (not a webhook)."""
112+
async def _run_review_from_api(req: ReviewRequest) -> list | None:
113+
"""Run the review pipeline from an API request (not a webhook).
114+
115+
Returns the list of review comments when skip_post=True, None otherwise.
116+
"""
113117
import time
114118
import uuid
115119
from github_app.telemetry import make_event_emitter
@@ -187,32 +191,39 @@ async def _run_review_from_api(req: ReviewRequest):
187191

188192
t_post = time.monotonic()
189193
num_issues = len(comments) if comments else 0
190-
summary_line = f"Found {num_issues} issue{'s' if num_issues != 1 else ''}"
191-
if req.personality and req.github_username:
192-
review_body = (
193-
f"@{req.github_username}'s review twin\n\n"
194-
f"{summary_line}\n\n---\n"
195-
f"*a2a-review based on @{req.github_username}'s coding preferences*"
196-
)
197-
else:
198-
review_body = f"## Morph Code Review\n\n{summary_line}"
199194

200-
if comments:
201-
await client.post_review(
202-
req.owner, req.repo, req.pr_number, req.head_sha,
203-
comments, diff, review_body,
195+
if req.skip_post:
196+
logger.info(
197+
"skip_post=True, skipping GitHub post for %s PR #%d (%d comments)",
198+
full_name, req.pr_number, num_issues,
204199
)
200+
duration_post = 0.0
205201
else:
206-
# Post summary even with 0 issues so callers can detect completion
207-
await client.post_issue_comment(
208-
req.owner, req.repo, req.pr_number, review_body,
209-
)
210-
duration_post = round(time.monotonic() - t_post, 1)
211-
on_event("review.post", {
212-
"comments_posted": len(comments) if comments else 0,
213-
"duration_s": duration_post,
214-
"success": True,
215-
})
202+
summary_line = f"Found {num_issues} issue{'s' if num_issues != 1 else ''}"
203+
if req.personality and req.github_username:
204+
review_body = (
205+
f"@{req.github_username}'s review twin\n\n"
206+
f"{summary_line}\n\n---\n"
207+
f"*a2a-review based on @{req.github_username}'s coding preferences*"
208+
)
209+
else:
210+
review_body = f"## Morph Code Review\n\n{summary_line}"
211+
212+
if comments:
213+
await client.post_review(
214+
req.owner, req.repo, req.pr_number, req.head_sha,
215+
comments, diff, review_body,
216+
)
217+
else:
218+
await client.post_issue_comment(
219+
req.owner, req.repo, req.pr_number, review_body,
220+
)
221+
duration_post = round(time.monotonic() - t_post, 1)
222+
on_event("review.post", {
223+
"comments_posted": num_issues,
224+
"duration_s": duration_post,
225+
"success": True,
226+
})
216227

217228
logger.info(
218229
"API review completed for %s PR #%d: %d comments",
@@ -240,6 +251,9 @@ async def _run_review_from_api(req: ReviewRequest):
240251
if req.callback_url:
241252
await _callback(req.callback_url, agent_run_id, "completed")
242253

254+
if req.skip_post:
255+
return comments or []
256+
243257
except Exception as exc:
244258
logger.exception("API review failed for %s PR #%d", full_name, req.pr_number)
245259
on_event("review.failed", {
@@ -278,6 +292,15 @@ async def review_api(req: ReviewRequest, request: Request):
278292
if REVIEW_API_SECRET and auth != f"Bearer {REVIEW_API_SECRET}":
279293
raise HTTPException(status_code=401, detail="Unauthorized")
280294

295+
if req.skip_post:
296+
# Run synchronously and return comments directly (for evals)
297+
comments = await _run_review_from_api(req)
298+
return {
299+
"status": "completed",
300+
"agent_run_id": req.agent_run_id or "generated-server-side",
301+
"comments": comments or [],
302+
}
303+
281304
asyncio.create_task(_run_review_from_api(req))
282305
return {"status": "accepted", "agent_run_id": req.agent_run_id or "generated-server-side"}
283306

0 commit comments

Comments
 (0)