1919from . import live_review_support , reviewer_review_helpers
2020
2121_UNSET = object ()
22+ _ASSIGNMENT_GUIDANCE_AUTHORS = {"github-actions" , "github-actions[bot]" , "guidelines-bot" }
2223
2324
2425def _record_timestamp (record : dict | None , * , parse_timestamp ) -> datetime | None :
@@ -63,7 +64,43 @@ def _initial_cycle_boundary(review_data: dict) -> tuple[str | None, str | None]:
6364 return None , None
6465
6566
66- def _alternate_current_head_cycle_boundary (review_data : dict , issue_snapshot : dict | None ) -> str | None :
67+ def _claim_assignment_guidance_timestamp (bot , issue_number : int , current_reviewer : str | None ) -> str | None :
68+ if not isinstance (current_reviewer , str ) or not current_reviewer .strip ():
69+ return None
70+ expected_first_line = f"👋 Hey @{ current_reviewer } ! You've been assigned to review this coding guideline PR."
71+ first_match : tuple [datetime , str ] | None = None
72+ page = 1
73+ while True :
74+ response = bot .github .list_issue_comments_result (issue_number , page = page )
75+ if not response .ok or not isinstance (response .payload , list ):
76+ return None
77+ for comment in response .payload :
78+ if not isinstance (comment , dict ):
79+ continue
80+ user = comment .get ("user" )
81+ login = user .get ("login" ) if isinstance (user , dict ) else None
82+ created_at = comment .get ("created_at" )
83+ body = comment .get ("body" )
84+ if not isinstance (login , str ) or not isinstance (created_at , str ) or not isinstance (body , str ):
85+ continue
86+ if login .lower () not in _ASSIGNMENT_GUIDANCE_AUTHORS :
87+ continue
88+ lines = body .splitlines ()
89+ first_line = lines [0 ].strip () if lines else ""
90+ if first_line != expected_first_line :
91+ continue
92+ created_dt = live_review_support .parse_github_timestamp (created_at )
93+ if created_dt is None :
94+ continue
95+ if first_match is None or created_dt < first_match [0 ]:
96+ first_match = (created_dt , created_at )
97+ if len (response .payload ) < 100 :
98+ break
99+ page += 1
100+ return first_match [1 ] if first_match is not None else None
101+
102+
103+ def _alternate_current_head_cycle_boundary (bot , issue_number : int , review_data : dict , issue_snapshot : dict | None ) -> str | None :
67104 if not isinstance (issue_snapshot , dict ) or not isinstance (issue_snapshot .get ("pull_request" ), dict ):
68105 return None
69106 if review_data .get ("assignment_method" ) != "claim" :
@@ -72,8 +109,7 @@ def _alternate_current_head_cycle_boundary(review_data: dict, issue_snapshot: di
72109 value = review_data .get (field )
73110 if isinstance (value , str ) and value :
74111 return None
75- created_at = issue_snapshot .get ("created_at" )
76- return created_at if isinstance (created_at , str ) and created_at else None
112+ return _claim_assignment_guidance_timestamp (bot , issue_number , review_data .get ("current_reviewer" ))
77113
78114
79115def _scope_basis_and_anchor (review_data : dict , contributor_handoff : dict | None ) -> tuple [str | None , str | None ]:
@@ -591,7 +627,12 @@ def compute_reviewer_response_state(
591627 reviews ,
592628 parse_timestamp = bot .parse_iso8601_timestamp ,
593629 )
594- alternate_current_head_cycle_boundary = _alternate_current_head_cycle_boundary (review_data , issue_snapshot )
630+ alternate_current_head_cycle_boundary = _alternate_current_head_cycle_boundary (
631+ bot ,
632+ issue_number ,
633+ review_data ,
634+ issue_snapshot ,
635+ )
595636
596637 return derive_reviewer_response_state (
597638 review_data ,
0 commit comments