claude-dev-env 8.19.1 → 8.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -24,8 +24,44 @@ MERGEABLE_STATE_KEY = "mergeable_state"
24
24
  NUMBER_KEY = "number"
25
25
  TITLE_KEY = "title"
26
26
  HEAD_KEY = "head"
27
+ BASE_KEY = "base"
28
+ REF_KEY = "ref"
27
29
  SHA_KEY = "sha"
28
30
 
31
+ BRANCH_RULES_ENDPOINT_TEMPLATE = "{api_root}/repos/{slug}/rules/branches/{branch}"
32
+ CHECK_RUNS_ENDPOINT_TEMPLATE = (
33
+ "{api_root}/repos/{slug}/commits/{sha}/check-runs?{query}"
34
+ )
35
+ COMBINED_STATUS_ENDPOINT_TEMPLATE = (
36
+ "{api_root}/repos/{slug}/commits/{sha}/status?{query}"
37
+ )
38
+ COMPARE_ENDPOINT_TEMPLATE = "{api_root}/repos/{slug}/compare/{base}...{head}"
39
+ CHECK_NAME_PARAMETER = "check_name"
40
+ PER_PAGE_PARAMETER = "per_page"
41
+ CHECK_PAGE_SIZE = 100
42
+
43
+ RULE_TYPE_KEY = "type"
44
+ RULE_PARAMETERS_KEY = "parameters"
45
+ REQUIRED_STATUS_CHECKS_RULE_TYPE = "required_status_checks"
46
+ MERGE_QUEUE_RULE_TYPE = "merge_queue"
47
+ REQUIRED_STATUS_CHECKS_KEY = "required_status_checks"
48
+ CONTEXT_KEY = "context"
49
+ INTEGRATION_ID_KEY = "integration_id"
50
+ CHECK_RUNS_KEY = "check_runs"
51
+ CHECK_RUN_NAME_KEY = "name"
52
+ CHECK_RUN_ID_KEY = "id"
53
+ CHECK_RUN_APP_KEY = "app"
54
+ CHECK_RUN_APP_ID_KEY = "id"
55
+ CHECK_RUN_STATUS_KEY = "status"
56
+ CHECK_RUN_CONCLUSION_KEY = "conclusion"
57
+ CHECK_RUN_COMPLETED_STATUS = "completed"
58
+ ALL_PASSING_CHECK_CONCLUSIONS = frozenset({"success", "neutral", "skipped"})
59
+ STATUSES_KEY = "statuses"
60
+ STATUS_STATE_KEY = "state"
61
+ SUCCESS_STATUS_STATE = "success"
62
+ PENDING_STATUS_STATE = "pending"
63
+ BEHIND_BY_KEY = "behind_by"
64
+
29
65
  MERGEABLE_STATE_CLEAN = "clean"
30
66
  MERGEABLE_STATE_BEHIND = "behind"
31
67
  MERGEABLE_STATE_DIRTY = "dirty"
@@ -61,6 +97,19 @@ ALL_HOLD_REASONS_BY_STATE = {
61
97
  MERGEABLE_STATE_BLOCKED: BLOCKED_HOLD_REASON,
62
98
  MERGEABLE_STATE_UNSTABLE: UNSTABLE_HOLD_REASON,
63
99
  }
100
+ FAILING_REQUIRED_CHECKS_HOLD_TEMPLATE = (
101
+ "A required check is not passing on this head: {checks}. Read the "
102
+ "failing check, fix it, and push."
103
+ )
104
+ REQUIRED_CHECK_STATE_TEMPLATE = "{context} ({state})"
105
+ REQUIRED_CHECK_SEPARATOR = ", "
106
+ MISSING_CHECK_STATE = "missing"
107
+ PENDING_CHECK_STATE = "pending"
108
+ BEHIND_MERGE_QUEUE_HOLD_TEMPLATE = (
109
+ "Every required check passes, but the head is {count} commit(s) behind "
110
+ "the base branch and the merge queue will not take it. Merge the base "
111
+ "branch into this one and push."
112
+ )
64
113
  UNKNOWN_STATE_HOLD_TEMPLATE = (
65
114
  "GitHub reports the merge state as {state}, which this check does not "
66
115
  "treat as ready. Read the pull request page."
@@ -68,6 +117,13 @@ UNKNOWN_STATE_HOLD_TEMPLATE = (
68
117
  UNRESOLVED_THREADS_HOLD_TEMPLATE = (
69
118
  "{count} review thread(s) are open. Answer each one and resolve it."
70
119
  )
120
+ MERGE_QUEUE_EJECTION_HOLD_TEMPLATE = (
121
+ "The merge queue ejected this head for failed checks, and no commit has "
122
+ "landed since. Read the merge_group run for the "
123
+ "gh-readonly-queue/{base}/pr-{number}-<sha> branch, fix the failure, and "
124
+ "push before you enqueue it again."
125
+ )
126
+ FAILED_CHECKS_REMOVAL_REASON = "failed_checks"
71
127
 
72
128
  MERGE_VERDICT_LABEL = "MERGE"
73
129
  HOLD_VERDICT_LABEL = "HOLD"
@@ -99,6 +155,19 @@ query($owner: String!, $name: String!, $number: Int!, $pageSize: Int!) {
99
155
  }
100
156
  }
101
157
  """
158
+ MERGE_QUEUE_REMOVAL_PAGE_SIZE = 100
159
+ MERGE_QUEUE_REMOVAL_QUERY = """
160
+ query($owner: String!, $name: String!, $number: Int!, $pageSize: Int!) {
161
+ repository(owner: $owner, name: $name) {
162
+ pullRequest(number: $number) {
163
+ commits(last: 1) { nodes { commit { committedDate } } }
164
+ timelineItems(last: $pageSize, itemTypes: [REMOVED_FROM_MERGE_QUEUE_EVENT]) {
165
+ nodes { ... on RemovedFromMergeQueueEvent { createdAt reason } }
166
+ }
167
+ }
168
+ }
169
+ }
170
+ """
102
171
  QUERY_KEY = "query"
103
172
  VARIABLES_KEY = "variables"
104
173
  OWNER_VARIABLE = "owner"
@@ -117,5 +186,25 @@ ALL_THREAD_NODE_KEYS = (
117
186
  REVIEW_THREADS_KEY,
118
187
  NODES_KEY,
119
188
  )
189
+ COMMITS_KEY = "commits"
190
+ COMMIT_KEY = "commit"
191
+ COMMITTED_DATE_KEY = "committedDate"
192
+ TIMELINE_ITEMS_KEY = "timelineItems"
193
+ CREATED_AT_KEY = "createdAt"
194
+ REMOVAL_REASON_KEY = "reason"
195
+ ALL_MERGE_QUEUE_REMOVAL_NODE_KEYS = (
196
+ DATA_KEY,
197
+ REPOSITORY_KEY,
198
+ PULL_REQUEST_KEY,
199
+ TIMELINE_ITEMS_KEY,
200
+ NODES_KEY,
201
+ )
202
+ ALL_HEAD_COMMIT_NODE_KEYS = (
203
+ DATA_KEY,
204
+ REPOSITORY_KEY,
205
+ PULL_REQUEST_KEY,
206
+ COMMITS_KEY,
207
+ NODES_KEY,
208
+ )
120
209
  ALL_RESOLVED_KEYS = ("isResolved", "is_resolved", "resolved")
121
210
  ALL_OUTDATED_KEYS = ("isOutdated", "is_outdated", "outdated")
@@ -131,6 +131,12 @@ NUMBER_KEY: str = "number"
131
131
  USER_KEY: str = "user"
132
132
  """Field carrying the account that opened a pull request."""
133
133
 
134
+ USER_TYPE_KEY: str = "type"
135
+ """Field on a REST user naming whether the account is a person or a bot."""
136
+
137
+ BOT_USER_TYPE: str = "Bot"
138
+ """The user type GitHub reports for an app account such as a review bot."""
139
+
134
140
  SHORT_SHA_LENGTH: int = 7
135
141
  """How much of a commit identifier the verdict line prints."""
136
142
 
@@ -13,10 +13,14 @@ and the approvals conclusion, and name what waits.
13
13
  thread: a red circle, resolved in silence -> open
14
14
  top-level: a bot comment, no agent comment -> open
15
15
  top-level: a bot comment, then an agent one -> closed
16
- top-level: the bot edits after that reply -> open
16
+ top-level: the bot edits after that reply -> closed
17
+ top-level: a person edits after that reply -> open
17
18
 
18
19
  A red circle marks a finding a review states as blocking, so resolution alone
19
20
  leaves it open. The reply says what changed, or why the finding stands.
21
+ A review bot rewrites its summary comment on each pass, and any new finding it
22
+ has arrives as a review thread or a new comment, so its edit alone reopens
23
+ nothing.
20
24
  """
21
25
 
22
26
  from __future__ import annotations
@@ -34,6 +38,7 @@ from dev_env_scripts_constants.review_closure_constants import (
34
38
  APPROVALS_CHECK_NAME,
35
39
  APPROVALS_OPEN_REASON,
36
40
  AUTHOR_LOGIN_KEY,
41
+ BOT_USER_TYPE,
37
42
  CHECK_RUN_CONCLUSION_KEY,
38
43
  CHECK_RUN_NAME_KEY,
39
44
  CLOSED_DETAIL,
@@ -58,6 +63,7 @@ from dev_env_scripts_constants.review_closure_constants import (
58
63
  UNNAMED_THREAD_SUBJECT,
59
64
  UPDATED_AT_KEY,
60
65
  USER_KEY,
66
+ USER_TYPE_KEY,
61
67
  VERDICT_LINE_TEMPLATE,
62
68
  )
63
69
 
@@ -86,6 +92,7 @@ class TopLevelComment:
86
92
 
87
93
  identifier: object
88
94
  author_login: str
95
+ is_bot: bool
89
96
  created_at: datetime
90
97
  updated_at: datetime
91
98
  url: str
@@ -212,7 +219,8 @@ def top_level_finding(
212
219
  """
213
220
  if comment.author_login in all_driver_logins:
214
221
  return None
215
- if latest_driver_time is not None and comment.updated_at <= latest_driver_time:
222
+ last_counted_at = comment.created_at if comment.is_bot else comment.updated_at
223
+ if latest_driver_time is not None and last_counted_at <= latest_driver_time:
216
224
  return None
217
225
  return OpenFinding(
218
226
  subject=comment.url,
@@ -228,15 +236,18 @@ def top_level_findings(
228
236
 
229
237
  The driving agent answers a top-level comment by posting a top-level
230
238
  comment of its own. Its latest one answers every comment last touched
231
- before it, and a comment edited after it waits again.
239
+ before it. A person's comment edited after it waits again. A bot's edit
240
+ leaves its comment answered, since a bot posts each new finding as a
241
+ review thread or a new comment.
232
242
 
233
243
  Args:
234
244
  all_comments: The top-level comments on the pull request.
235
245
  all_driver_logins: The logins whose comments count as the answer.
236
246
 
237
247
  Returns:
238
- One finding per comment from another account that was posted or
239
- edited after the driving agent's latest top-level comment.
248
+ One finding per comment from another account posted after the
249
+ driving agent's latest top-level comment, and one per person's
250
+ comment edited after it.
240
251
  """
241
252
  all_listed_comments = tuple(all_comments)
242
253
  latest_driver_time = latest_driver_comment_time(
@@ -424,6 +435,7 @@ def parse_top_level_comment(
424
435
  return TopLevelComment(
425
436
  identifier=all_comment_fields.get(COMMENT_IDENTIFIER_KEY),
426
437
  author_login=_comment_author_login(all_comment_fields),
438
+ is_bot=_is_bot_author(all_comment_fields),
427
439
  created_at=datetime.fromisoformat(str(all_comment_fields.get(CREATED_AT_KEY))),
428
440
  updated_at=datetime.fromisoformat(str(all_comment_fields.get(UPDATED_AT_KEY))),
429
441
  url=str(all_comment_fields.get(HTML_URL_KEY) or ""),
@@ -477,6 +489,14 @@ def _parse_comment(all_comment_fields: Mapping[str, object]) -> ReviewComment:
477
489
  )
478
490
 
479
491
 
492
+ def _is_bot_author(all_comment_fields: Mapping[str, object]) -> bool:
493
+ all_author_fields = all_comment_fields.get(USER_KEY)
494
+ return (
495
+ isinstance(all_author_fields, Mapping)
496
+ and all_author_fields.get(USER_TYPE_KEY) == BOT_USER_TYPE
497
+ )
498
+
499
+
480
500
  def _comment_author_login(all_comment_fields: Mapping[str, object]) -> str:
481
501
  for each_key in ALL_COMMENT_AUTHOR_KEYS:
482
502
  all_author_fields = all_comment_fields.get(each_key)