feat(robomp): added incoming PR review feature with classify and submit

- Added `review_pr` task that checks out PR head in a detached worktree, classifies rank/type/area, and posts a batched GitHub review as `event=COMMENT`.
- Added four new host tools: `fetch_pr`, `classify_pr`, `pr_review_comment`, and `submit_pr_review`; review tools self-gate on `review_mode`, push/open-PR tools refuse when `review_mode` is set.
- Added sqlite staging table `pr_review_comments` with `stage_review_comment`, `list_staged_review_comments`, and `clear_staged_review_comments` DAOs.
- Routed `pull_request.opened/reopened/ready_for_review` to `review_pr` and extended `pull_request.closed` cleanup to any tracked PR regardless of author.
This commit is contained in:
can1357
2026-06-02 08:23:09 +02:00
parent 04cc014c16
commit b503f7d86b
30 changed files with 2392 additions and 112 deletions
+34 -1
View File
@@ -22,6 +22,7 @@ from robomp.github_client import (
GitHubError,
IssueInfo,
IssueSummary,
PullRequestFileInfo,
PullRequestInfo,
PullRequestReviewInfo,
ReactionInfo,
@@ -276,7 +277,7 @@ def round_trip_app(proxy_settings: Settings):
}
],
)
if path == "/repos/octo/widget/pulls/2/reviews":
if path == "/repos/octo/widget/pulls/2/reviews" and req.method == "GET":
return httpx.Response(
200,
json=[
@@ -289,6 +290,25 @@ def round_trip_app(proxy_settings: Settings):
}
],
)
if path == "/repos/octo/widget/pulls/2/files":
return httpx.Response(
200,
json=[{"filename": "src/app.py", "status": "modified", "additions": 2, "deletions": 1}],
)
if path == "/repos/octo/widget/pulls/2/reviews" and req.method == "POST":
body = json.loads(req.content)
assert body["event"] == "COMMENT"
assert body["comments"] == [{"path": "src/app.py", "line": 12, "side": "RIGHT", "body": "finding"}]
return httpx.Response(
200,
json={
"id": 55,
"user": {"login": "robomp-bot"},
"body": body["body"],
"state": "COMMENTED",
"submitted_at": "2026-01-01T00:00:00Z",
},
)
if path == "/user":
return httpx.Response(200, json={"login": "robomp-bot"})
if path == "/repos/octo/widget/pulls/4" and req.method == "GET":
@@ -353,6 +373,19 @@ async def test_round_trip_all_endpoints(round_trip_app) -> None:
prs = await client.list_pr_reviews("octo/widget", 2)
assert len(prs) == 1 and isinstance(prs[0], PullRequestReviewInfo)
files = await client.list_pr_files("octo/widget", 2)
assert len(files) == 1 and isinstance(files[0], PullRequestFileInfo)
assert files[0].path == "src/app.py"
submitted = await client.submit_pr_review(
repo="octo/widget",
pr_number=2,
body="summary",
event="COMMENT",
comments=[{"path": "src/app.py", "line": 12, "side": "RIGHT", "body": "finding"}],
)
assert submitted.id == 55
assert await client.get_authenticated_login() == "robomp-bot"
existing_pr = await client.get_pull_request("octo/widget", 4)