66"""
77
88import json
9- import re
109import subprocess
1110from dataclasses import dataclass , field
1211from pathlib import Path
@@ -54,8 +53,14 @@ def has_new_files(self) -> bool:
5453
5554
5655_TRUSTED_ASSOCIATIONS = {"MEMBER" , "OWNER" , "COLLABORATOR" }
57- _REVIEWHOG_BOT_LOGIN = "posthog[bot]"
58- _REVIEWHOG_STATUS_RE = re .compile (r"<!--\s*reviewhog:status:[0-9a-f-]+\s*-->" )
56+ TRUSTED_REACTOR_BOTS = {
57+ "chatgpt-codex-connector[bot]" ,
58+ "copilot-pull-request-reviewer[bot]" ,
59+ "greptile-apps[bot]" ,
60+ "hex-security-app[bot]" ,
61+ "posthog[bot]" ,
62+ "veria-ai[bot]" ,
63+ }
5964
6065
6166def _normalize_reviews_for_prompt (reviews_raw : list [dict ], head_sha : str ) -> list [dict ]:
@@ -89,24 +94,20 @@ def _normalize_reviews_for_prompt(reviews_raw: list[dict], head_sha: str) -> lis
8994 return normalized_reviews
9095
9196
92- def _reviewhog_clean_reactions (comments_raw : list [dict ]) -> list [dict ]:
93- for comment in reversed (comments_raw ):
94- user = comment .get ("user" ) or {}
95- if user .get ("login" , "" ).lower () != _REVIEWHOG_BOT_LOGIN or user .get ("type" ) != "Bot" :
96- continue
97- body = comment .get ("body" ) or ""
98- if "Found no issues worth raising, so no review was posted." not in body :
97+ def _normalize_pr_reactions (reactions_raw : list [dict ], author : str ) -> list [dict ]:
98+ normalized = []
99+ for reaction in reactions_raw :
100+ login = (reaction .get ("user" ) or {}).get ("login" , "" )
101+ if login == author or login .lower () not in TRUSTED_REACTOR_BOTS :
99102 continue
100- if _REVIEWHOG_STATUS_RE .search (body ) is None :
101- continue
102- return [
103+ normalized .append (
103104 {
104- "user" : "reviewhog[bot]" ,
105- "emoji" : "👍" ,
106- "created_at" : comment . get ( "updated_at" ) or comment .get ("created_at" ),
105+ "user" : login ,
106+ "emoji" : { "+1" : "👍" , "-1" : "👎" , "eyes" : "👀" }. get ( reaction . get ( "content" ), reaction . get ( "content" )) ,
107+ "created_at" : reaction .get ("created_at" ),
107108 }
108- ]
109- return []
109+ )
110+ return normalized
110111
111112
112113def _gh_api (endpoint : str , * , paginate : bool = False ) -> dict | list :
@@ -282,12 +283,12 @@ def fetch_pr(pr_number: int, repo: str, repo_root: Path | None = None) -> PRData
282283 base_sha = pr ["base" ]["sha" ]
283284 head_sha = pr ["head" ]["sha" ]
284285 check_runs_resp = _gh_api (f"repos/{ repo } /commits/{ head_sha } /check-runs" )
285- reviewhog_reactions : list [dict ] = []
286+ pr_reactions : list [dict ] = []
286287 try :
287- discussion_raw = _gh_api (f"repos/{ repo } /issues/{ pr_number } /comments " , paginate = True )
288- reviewhog_reactions = _reviewhog_clean_reactions ( discussion_raw )
288+ reactions_raw = _gh_api (f"repos/{ repo } /issues/{ pr_number } /reactions " , paginate = True )
289+ pr_reactions = _normalize_pr_reactions ( reactions_raw , pr [ "user" ][ "login" ] )
289290 except Exception as exc :
290- print (f"warning: discussion fetch failed ({ exc } ); continuing without ReviewHog assurance " )
291+ print (f"warning: reaction fetch failed ({ exc } ); continuing without reaction context " )
291292
292293 git_root = repo_root or Path .cwd ()
293294 ensure_commits (pr_number , head_sha , git_root )
@@ -310,7 +311,7 @@ def fetch_pr(pr_number: int, repo: str, repo_root: Path | None = None) -> PRData
310311 reviews = _normalize_reviews_for_prompt (reviews_raw , head_sha ),
311312 review_comments = review_comments ,
312313 check_runs = check_runs_resp .get ("check_runs" , []),
313- pr_reactions = reviewhog_reactions ,
314+ pr_reactions = pr_reactions ,
314315 )
315316
316317
0 commit comments