ultralytics-actions 0.1.0__py3-none-any.whl → 0.1.2__py3-none-any.whl

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.

Potentially problematic release.


This version of ultralytics-actions might be problematic. Click here for more details.

@@ -13,6 +13,91 @@ from actions import __version__
13
13
  GITHUB_API_URL = "https://api.github.com"
14
14
  GITHUB_GRAPHQL_URL = "https://api.github.com/graphql"
15
15
 
16
+ # GraphQL Queries
17
+ GRAPHQL_REPO_LABELS = """
18
+ query($owner: String!, $name: String!) {
19
+ repository(owner: $owner, name: $name) {
20
+ labels(first: 100, query: "") {
21
+ nodes {
22
+ id
23
+ name
24
+ }
25
+ }
26
+ }
27
+ }
28
+ """
29
+
30
+ GRAPHQL_PR_CONTRIBUTORS = """
31
+ query($owner: String!, $repo: String!, $pr_number: Int!) {
32
+ repository(owner: $owner, name: $repo) {
33
+ pullRequest(number: $pr_number) {
34
+ closingIssuesReferences(first: 50) { nodes { number } }
35
+ url
36
+ title
37
+ body
38
+ author { login, __typename }
39
+ reviews(first: 50) { nodes { author { login, __typename } } }
40
+ comments(first: 50) { nodes { author { login, __typename } } }
41
+ commits(first: 100) { nodes { commit { author { user { login } }, committer { user { login } } } } }
42
+ }
43
+ }
44
+ }
45
+ """
46
+
47
+ GRAPHQL_UPDATE_DISCUSSION = """
48
+ mutation($discussionId: ID!, $title: String!, $body: String!) {
49
+ updateDiscussion(input: {discussionId: $discussionId, title: $title, body: $body}) {
50
+ discussion {
51
+ id
52
+ }
53
+ }
54
+ }
55
+ """
56
+
57
+ GRAPHQL_CLOSE_DISCUSSION = """
58
+ mutation($discussionId: ID!) {
59
+ closeDiscussion(input: {discussionId: $discussionId}) {
60
+ discussion {
61
+ id
62
+ }
63
+ }
64
+ }
65
+ """
66
+
67
+ GRAPHQL_LOCK_DISCUSSION = """
68
+ mutation($lockableId: ID!, $lockReason: LockReason) {
69
+ lockLockable(input: {lockableId: $lockableId, lockReason: $lockReason}) {
70
+ lockedRecord {
71
+ ... on Discussion {
72
+ id
73
+ }
74
+ }
75
+ }
76
+ }
77
+ """
78
+
79
+ GRAPHQL_ADD_DISCUSSION_COMMENT = """
80
+ mutation($discussionId: ID!, $body: String!) {
81
+ addDiscussionComment(input: {discussionId: $discussionId, body: $body}) {
82
+ comment {
83
+ id
84
+ }
85
+ }
86
+ }
87
+ """
88
+
89
+ GRAPHQL_ADD_LABELS_TO_DISCUSSION = """
90
+ mutation($labelableId: ID!, $labelIds: [ID!]!) {
91
+ addLabelsToLabelable(input: {labelableId: $labelableId, labelIds: $labelIds}) {
92
+ labelable {
93
+ ... on Discussion {
94
+ id
95
+ }
96
+ }
97
+ }
98
+ }
99
+ """
100
+
16
101
 
17
102
  class Action:
18
103
  """Handles GitHub Actions API interactions and event processing."""
@@ -29,6 +114,7 @@ class Action:
29
114
  self.verbose = verbose
30
115
  self.eyes_reaction_id = None
31
116
  self._pr_diff_cache = None
117
+ self._pr_summary_cache = None
32
118
  self._username_cache = None
33
119
  self._default_status = {
34
120
  "get": [200],
@@ -102,6 +188,22 @@ class Action:
102
188
  """Checks if a user is a member of the organization."""
103
189
  return self.get(f"{GITHUB_API_URL}/orgs/{self.repository.split('/')[0]}/members/{username}").status_code == 204
104
190
 
191
+ def is_fork_pr(self) -> bool:
192
+ """Checks if PR is from a fork (different repo than base)."""
193
+ if not self.pr:
194
+ return False
195
+ head_repo = self.pr.get("head", {}).get("repo", {}).get("full_name")
196
+ return bool(head_repo) and head_repo != self.repository
197
+
198
+ def should_skip_openai(self) -> bool:
199
+ """Check if OpenAI operations should be skipped."""
200
+ from actions.utils.openai_utils import OPENAI_API_KEY
201
+
202
+ if not OPENAI_API_KEY:
203
+ print("⚠️ Skipping LLM operations (OPENAI_API_KEY not found)")
204
+ return True
205
+ return False
206
+
105
207
  def get_pr_diff(self) -> str:
106
208
  """Retrieves the diff content for a specified pull request with caching."""
107
209
  if self._pr_diff_cache:
@@ -110,7 +212,7 @@ class Action:
110
212
  url = f"{GITHUB_API_URL}/repos/{self.repository}/pulls/{self.pr.get('number')}"
111
213
  response = self.get(url, headers=self.headers_diff)
112
214
  if response.status_code == 200:
113
- self._pr_diff_cache = response.text
215
+ self._pr_diff_cache = response.text or "ERROR: EMPTY DIFF, NO CODE CHANGES IN THIS PR."
114
216
  elif response.status_code == 406:
115
217
  self._pr_diff_cache = "ERROR: PR diff exceeds GitHub's 20,000 line limit, unable to retrieve diff."
116
218
  else:
@@ -148,6 +250,170 @@ class Action:
148
250
  print(result.get("errors"))
149
251
  return result
150
252
 
253
+ def update_pr_description(self, number: int, new_summary: str, max_retries: int = 2):
254
+ """Updates PR description with summary, retrying if description is None."""
255
+ import time
256
+
257
+ url = f"{GITHUB_API_URL}/repos/{self.repository}/pulls/{number}"
258
+ description = ""
259
+ for i in range(max_retries + 1):
260
+ description = self.get(url).json().get("body") or ""
261
+ if description:
262
+ break
263
+ if i < max_retries:
264
+ print("No current PR description found, retrying...")
265
+ time.sleep(1)
266
+
267
+ start = "## 🛠️ PR Summary"
268
+ if start in description:
269
+ print("Existing PR Summary found, replacing.")
270
+ updated_description = description.split(start)[0] + new_summary
271
+ else:
272
+ print("PR Summary not found, appending.")
273
+ updated_description = description + "\n\n" + new_summary
274
+
275
+ self.patch(url, json={"body": updated_description})
276
+ self._pr_summary_cache = new_summary
277
+
278
+ def get_label_ids(self, labels: list[str]) -> list[str]:
279
+ """Retrieves GitHub label IDs for a list of label names using the GraphQL API."""
280
+ owner, repo = self.repository.split("/")
281
+ result = self.graphql_request(GRAPHQL_REPO_LABELS, variables={"owner": owner, "name": repo})
282
+ if "data" in result and "repository" in result["data"]:
283
+ all_labels = result["data"]["repository"]["labels"]["nodes"]
284
+ label_map = {label["name"].lower(): label["id"] for label in all_labels}
285
+ return [label_map.get(label.lower()) for label in labels if label.lower() in label_map]
286
+ return []
287
+
288
+ def apply_labels(self, number: int, node_id: str, labels: list[str], issue_type: str):
289
+ """Applies specified labels to a GitHub issue, pull request, or discussion."""
290
+ if "Alert" in labels:
291
+ self.create_alert_label()
292
+
293
+ if issue_type == "discussion":
294
+ label_ids = self.get_label_ids(labels)
295
+ if not label_ids:
296
+ print("No valid labels to apply.")
297
+ return
298
+ self.graphql_request(GRAPHQL_ADD_LABELS_TO_DISCUSSION, {"labelableId": node_id, "labelIds": label_ids})
299
+ else:
300
+ url = f"{GITHUB_API_URL}/repos/{self.repository}/issues/{number}/labels"
301
+ self.post(url, json={"labels": labels})
302
+
303
+ def create_alert_label(self):
304
+ """Creates the 'Alert' label in the repository if it doesn't exist."""
305
+ alert_label = {"name": "Alert", "color": "FF0000", "description": "Potential spam, abuse, or off-topic."}
306
+ self.post(f"{GITHUB_API_URL}/repos/{self.repository}/labels", json=alert_label)
307
+
308
+ def remove_labels(self, number: int, labels: tuple[str, ...]):
309
+ """Removes specified labels from an issue or PR."""
310
+ for label in labels:
311
+ self.delete(f"{GITHUB_API_URL}/repos/{self.repository}/issues/{number}/labels/{label}")
312
+
313
+ def add_comment(self, number: int, node_id: str, comment: str, issue_type: str):
314
+ """Adds a comment to an issue, pull request, or discussion."""
315
+ if issue_type == "discussion":
316
+ self.graphql_request(GRAPHQL_ADD_DISCUSSION_COMMENT, variables={"discussionId": node_id, "body": comment})
317
+ else:
318
+ self.post(f"{GITHUB_API_URL}/repos/{self.repository}/issues/{number}/comments", json={"body": comment})
319
+
320
+ def update_content(self, number: int, node_id: str, issue_type: str, title: str = None, body: str = None):
321
+ """Updates the title and/or body of an issue, pull request, or discussion."""
322
+ if issue_type == "discussion":
323
+ variables = {"discussionId": node_id}
324
+ if title:
325
+ variables["title"] = title
326
+ if body:
327
+ variables["body"] = body
328
+ self.graphql_request(GRAPHQL_UPDATE_DISCUSSION, variables=variables)
329
+ else:
330
+ url = f"{GITHUB_API_URL}/repos/{self.repository}/issues/{number}"
331
+ data = {}
332
+ if title:
333
+ data["title"] = title
334
+ if body:
335
+ data["body"] = body
336
+ self.patch(url, json=data)
337
+
338
+ def close_item(self, number: int, node_id: str, issue_type: str):
339
+ """Closes an issue, pull request, or discussion."""
340
+ if issue_type == "discussion":
341
+ self.graphql_request(GRAPHQL_CLOSE_DISCUSSION, variables={"discussionId": node_id})
342
+ else:
343
+ url = f"{GITHUB_API_URL}/repos/{self.repository}/issues/{number}"
344
+ self.patch(url, json={"state": "closed"})
345
+
346
+ def lock_item(self, number: int, node_id: str, issue_type: str):
347
+ """Locks an issue, pull request, or discussion to prevent further interactions."""
348
+ if issue_type == "discussion":
349
+ self.graphql_request(GRAPHQL_LOCK_DISCUSSION, variables={"lockableId": node_id, "lockReason": "OFF_TOPIC"})
350
+ else:
351
+ url = f"{GITHUB_API_URL}/repos/{self.repository}/issues/{number}/lock"
352
+ self.put(url, json={"lock_reason": "off-topic"})
353
+
354
+ def block_user(self, username: str):
355
+ """Blocks a user from the organization."""
356
+ url = f"{GITHUB_API_URL}/orgs/{self.repository.split('/')[0]}/blocks/{username}"
357
+ self.put(url)
358
+
359
+ def handle_alert(self, number: int, node_id: str, issue_type: str, username: str, block: bool = False):
360
+ """Handles content flagged as alert: updates content, locks, optionally closes and blocks user."""
361
+ new_title = "Content Under Review"
362
+ new_body = """This post has been flagged for review by [Ultralytics Actions](https://ultralytics.com/actions) due to possible spam, abuse, or off-topic content. For more information please see our:
363
+
364
+ - [Code of Conduct](https://docs.ultralytics.com/help/code-of-conduct/)
365
+ - [Security Policy](https://docs.ultralytics.com/help/security/)
366
+
367
+ For questions or bug reports related to this action please visit https://github.com/ultralytics/actions.
368
+
369
+ Thank you 🙏
370
+ """
371
+ self.update_content(number, node_id, issue_type, title=new_title, body=new_body)
372
+ if issue_type != "pull request":
373
+ self.close_item(number, node_id, issue_type)
374
+ self.lock_item(number, node_id, issue_type)
375
+ if block:
376
+ self.block_user(username)
377
+
378
+ def get_pr_contributors(self) -> tuple[str | None, dict]:
379
+ """Gets PR contributors and closing issues, returns (pr_credit_string, pr_data)."""
380
+ owner, repo = self.repository.split("/")
381
+ variables = {"owner": owner, "repo": repo, "pr_number": self.pr["number"]}
382
+ response = self.post(GITHUB_GRAPHQL_URL, json={"query": GRAPHQL_PR_CONTRIBUTORS, "variables": variables})
383
+ if response.status_code != 200:
384
+ return None, {}
385
+
386
+ try:
387
+ data = response.json()["data"]["repository"]["pullRequest"]
388
+ comments = data["reviews"]["nodes"] + data["comments"]["nodes"]
389
+ token_username = self.get_username()
390
+ author = data["author"]["login"] if data["author"]["__typename"] != "Bot" else None
391
+
392
+ contributors = {x["author"]["login"] for x in comments if x["author"]["__typename"] != "Bot"}
393
+
394
+ for commit in data["commits"]["nodes"]:
395
+ commit_data = commit["commit"]
396
+ for user_type in ["author", "committer"]:
397
+ if user := commit_data[user_type].get("user"):
398
+ if login := user.get("login"):
399
+ contributors.add(login)
400
+
401
+ contributors.discard(author)
402
+ contributors.discard(token_username)
403
+
404
+ pr_credit = ""
405
+ if author and author != token_username:
406
+ pr_credit += f"@{author}"
407
+ if contributors:
408
+ pr_credit += (" with contributions from " if pr_credit else "") + ", ".join(
409
+ f"@{c}" for c in contributors
410
+ )
411
+
412
+ return pr_credit, data
413
+ except KeyError as e:
414
+ print(f"Error parsing GraphQL response: {e}")
415
+ return None, {}
416
+
151
417
  def print_info(self):
152
418
  """Print GitHub Actions information including event details and repository information."""
153
419
  info = {
@@ -28,18 +28,91 @@ def remove_outer_codeblocks(string):
28
28
  """Removes outer code block markers and language identifiers from a string while preserving inner content."""
29
29
  string = string.strip()
30
30
  if string.startswith("```") and string.endswith("```"):
31
- # Get everything after first ``` and newline, up to the last ```
32
31
  string = string[string.find("\n") + 1 : string.rfind("```")].strip()
33
32
  return string
34
33
 
35
34
 
35
+ def filter_labels(available_labels: dict, current_labels: list = None, is_pr: bool = False) -> dict:
36
+ """Filters labels by removing manually-assigned and mutually exclusive labels."""
37
+ current_labels = current_labels or []
38
+ filtered = available_labels.copy()
39
+
40
+ for label in {
41
+ "help wanted",
42
+ "TODO",
43
+ "research",
44
+ "non-reproducible",
45
+ "popular",
46
+ "invalid",
47
+ "Stale",
48
+ "wontfix",
49
+ "duplicate",
50
+ }:
51
+ filtered.pop(label, None)
52
+
53
+ if "bug" in current_labels:
54
+ filtered.pop("question", None)
55
+ elif "question" in current_labels:
56
+ filtered.pop("bug", None)
57
+
58
+ if "Alert" not in filtered:
59
+ filtered["Alert"] = (
60
+ "Potential spam, abuse, or illegal activity including advertising, unsolicited promotions, malware, "
61
+ "phishing, crypto offers, pirated software or media, free movie downloads, cracks, keygens or any other "
62
+ "content that violates terms of service or legal standards."
63
+ )
64
+
65
+ return filtered
66
+
67
+
68
+ def get_pr_summary_guidelines() -> str:
69
+ """Returns PR summary formatting guidelines (used by both unified PR open and PR update/merge)."""
70
+ return """Summarize this PR, focusing on major changes, their purpose, and potential impact. Keep the summary clear and concise, suitable for a broad audience. Add emojis to enliven the summary. Your response must include all 3 sections below with their markdown headers:
71
+
72
+ ### 🌟 Summary
73
+ (single-line synopsis)
74
+
75
+ ### 📊 Key Changes
76
+ - (bullet points highlighting major changes)
77
+
78
+ ### 🎯 Purpose & Impact
79
+ - (bullet points explaining benefits and potential impact to users)"""
80
+
81
+
82
+ def get_pr_summary_prompt(repository: str, diff_text: str) -> tuple[str, bool]:
83
+ """Returns the complete PR summary generation prompt with diff (used by PR update/merge)."""
84
+ ratio = 3.3 # about 3.3 characters per token
85
+ limit = round(128000 * ratio * 0.5) # use up to 50% of the 128k context window for prompt
86
+
87
+ prompt = (
88
+ f"{get_pr_summary_guidelines()}\n\nRepository: '{repository}'\n\nHere's the PR diff:\n\n{diff_text[:limit]}"
89
+ )
90
+ return prompt, len(diff_text) > limit
91
+
92
+
93
+ def get_pr_first_comment_template(repository: str) -> str:
94
+ """Returns the PR first comment template with checklist (used only by unified PR open)."""
95
+ return f"""👋 Hello @username, thank you for submitting an `{repository}` 🚀 PR! To ensure a seamless integration of your work, please review the following checklist:
96
+
97
+ - ✅ **Define a Purpose**: Clearly explain the purpose of your fix or feature in your PR description, and link to any [relevant issues](https://github.com/{repository}/issues). Ensure your commit messages are clear, concise, and adhere to the project's conventions.
98
+ - ✅ **Synchronize with Source**: Confirm your PR is synchronized with the `{repository}` `main` branch. If it's behind, update it by clicking the 'Update branch' button or by running `git pull` and `git merge main` locally.
99
+ - ✅ **Ensure CI Checks Pass**: Verify all Ultralytics [Continuous Integration (CI)](https://docs.ultralytics.com/help/CI/) checks are passing. If any checks fail, please address the issues.
100
+ - ✅ **Update Documentation**: Update the relevant [documentation](https://docs.ultralytics.com/) for any new or modified features.
101
+ - ✅ **Add Tests**: If applicable, include or update tests to cover your changes, and confirm that all tests are passing.
102
+ - ✅ **Sign the CLA**: Please ensure you have signed our [Contributor License Agreement](https://docs.ultralytics.com/help/CLA/) if this is your first Ultralytics PR by writing "I have read the CLA Document and I sign the CLA" in a new message.
103
+ - ✅ **Minimize Changes**: Limit your changes to the **minimum** necessary for your bug fix or feature addition. _"It is not daily increase but daily decrease, hack away the unessential. The closer to the source, the less wastage there is."_ — Bruce Lee
104
+
105
+ For more guidance, please refer to our [Contributing Guide](https://docs.ultralytics.com/help/contributing/). Don't hesitate to leave a comment if you have any questions. Thank you for contributing to Ultralytics! 🚀"""
106
+
107
+
36
108
  def get_completion(
37
109
  messages: list[dict[str, str]],
38
110
  check_links: bool = True,
39
- remove: list[str] = (" @giscus[bot]",), # strings to remove from response
40
- temperature: float = 1.0, # note GPT-5 requires temperature=1.0
41
- reasoning_effort: str = None, # reasoning effort for GPT-5 models: minimal, low, medium, high
42
- ) -> str:
111
+ remove: list[str] = (" @giscus[bot]",),
112
+ temperature: float = 1.0,
113
+ reasoning_effort: str = None,
114
+ response_format: dict = None,
115
+ ) -> str | dict:
43
116
  """Generates a completion using OpenAI's Responses API based on input messages."""
44
117
  assert OPENAI_API_KEY, "OpenAI API key is required."
45
118
  url = "https://api.openai.com/v1/responses"
@@ -47,14 +120,12 @@ def get_completion(
47
120
  if messages and messages[0].get("role") == "system":
48
121
  messages[0]["content"] += "\n\n" + SYSTEM_PROMPT_ADDITION
49
122
 
50
- content = ""
51
123
  max_retries = 2
52
- for attempt in range(max_retries + 2): # attempt = [0, 1, 2, 3], 2 random retries before asking for no links
124
+ for attempt in range(max_retries + 2):
53
125
  data = {"model": OPENAI_MODEL, "input": messages, "store": False, "temperature": temperature}
54
-
55
- # Add reasoning for GPT-5 models
56
126
  if "gpt-5" in OPENAI_MODEL:
57
- data["reasoning"] = {"effort": reasoning_effort or "low"} # Default to low for GPT-5
127
+ data["reasoning"] = {"effort": reasoning_effort or "low"}
128
+ # GPT-5 Responses API handles JSON via prompting, not format parameter
58
129
 
59
130
  r = requests.post(url, json=data, headers=headers)
60
131
  if r.status_code != 200:
@@ -62,7 +133,6 @@ def get_completion(
62
133
  r.raise_for_status()
63
134
  response_data = r.json()
64
135
 
65
- # Extract text from output array
66
136
  content = ""
67
137
  for item in response_data.get("output", []):
68
138
  if item.get("type") == "message":
@@ -71,10 +141,16 @@ def get_completion(
71
141
  content += content_item.get("text", "")
72
142
 
73
143
  content = content.strip()
144
+ if response_format and response_format.get("type") == "json_object":
145
+ import json
146
+
147
+ return json.loads(content)
148
+
74
149
  content = remove_outer_codeblocks(content)
75
150
  for x in remove:
76
151
  content = content.replace(x, "")
77
- if not check_links or check_links_in_string(content): # if no checks or checks are passing return response
152
+
153
+ if not check_links or check_links_in_string(content):
78
154
  return content
79
155
 
80
156
  if attempt < max_retries:
@@ -82,11 +158,61 @@ def get_completion(
82
158
  else:
83
159
  print("Max retries reached. Updating prompt to exclude links.")
84
160
  messages.append({"role": "user", "content": "Please provide a response without any URLs or links in it."})
85
- check_links = False # automatically accept the last message
161
+ check_links = False
86
162
 
87
163
  return content
88
164
 
89
165
 
166
+ def get_pr_open_response(repository: str, diff_text: str, title: str, body: str, available_labels: dict) -> dict:
167
+ """Generates unified PR response with summary, labels, and first comment in a single API call."""
168
+ ratio = 3.3 # about 3.3 characters per token
169
+ limit = round(128000 * ratio * 0.5) # use up to 50% of the 128k context window for prompt
170
+ is_large = len(diff_text) > limit
171
+
172
+ filtered_labels = filter_labels(available_labels, is_pr=True)
173
+ labels_str = "\n".join(f"- {name}: {description}" for name, description in filtered_labels.items())
174
+
175
+ prompt = f"""You are processing a new GitHub pull request for the {repository} repository.
176
+
177
+ Generate 3 outputs in a single JSON response for the PR titled {title} with the following diff:
178
+ {diff_text[:limit]}
179
+
180
+
181
+ --- FIRST JSON OUTPUT (PR SUMMARY) ---
182
+ {get_pr_summary_guidelines()}
183
+
184
+ --- SECOND JSON OUTPUT (PR LABELS) ---
185
+ Array of 1-3 most relevant label names. Only use "Alert" with high confidence for inappropriate PRs. Return empty array if no labels relevant. Available labels:
186
+ {labels_str}
187
+
188
+ --- THIRD OUTPUT (PR FIRST COMMENT) ---
189
+ Customized welcome message adapting the template below:
190
+ - INCLUDE ALL LINKS AND INSTRUCTIONS from the template below, customized as appropriate
191
+ - Keep all checklist items and links from template
192
+ - Only link to files or URLs in the template below, do not add external links
193
+ - Mention this is automated and an engineer will assist
194
+ - Use a few emojis
195
+ - No sign-off or "best regards"
196
+ - No spaces between bullet points
197
+
198
+ Example comment template (adapt as needed, keep all links):
199
+ {get_pr_first_comment_template(repository)}
200
+
201
+ Return ONLY valid JSON in this exact format:
202
+ {{"summary": "...", "labels": [...], "first_comment": "..."}}"""
203
+
204
+ messages = [
205
+ {"role": "system", "content": "You are an Ultralytics AI assistant processing GitHub PRs."},
206
+ {"role": "user", "content": prompt},
207
+ ]
208
+ result = get_completion(messages, temperature=1.0, response_format={"type": "json_object"})
209
+ if is_large and "summary" in result:
210
+ result["summary"] = (
211
+ "**WARNING ⚠️** this PR is very large, summary may not cover all changes.\n\n" + result["summary"]
212
+ )
213
+ return result
214
+
215
+
90
216
  if __name__ == "__main__":
91
217
  messages = [
92
218
  {"role": "system", "content": "You are a helpful AI assistant."},
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ultralytics-actions
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: Ultralytics Actions for GitHub automation and PR management.
5
5
  Author-email: Glenn Jocher <glenn.jocher@ultralytics.com>
6
6
  Maintainer-email: Ultralytics <hello@ultralytics.com>
@@ -64,6 +64,7 @@ Ultralytics Actions automatically applies formats, updates, and enhancements usi
64
64
  - **Spell Check:** Common misspellings are caught using [codespell](https://github.com/codespell-project/codespell).
65
65
  - **Broken Links Check:** Broken links in documentation and Markdown files are identified using [Lychee](https://github.com/lycheeverse/lychee).
66
66
  - **PR Summary:** Concise Pull Request summaries are generated using [OpenAI](https://openai.com/) GPT-5, improving clarity and review efficiency.
67
+ - **PR Review:** AI-powered inline code reviews identify critical bugs, security issues, and code quality concerns with suggested fixes.
67
68
  - **Auto-labeling:** Relevant labels are applied to issues and pull requests via [OpenAI](https://openai.com/) GPT-5 for intelligent categorization.
68
69
 
69
70
  ## 🛠️ How It Works
@@ -74,6 +75,7 @@ Ultralytics Actions triggers on various GitHub events to streamline workflows:
74
75
  - **Pull Requests:**
75
76
  - Ensures contributions meet formatting standards before merging.
76
77
  - Generates a concise summary of changes using GPT-5.
78
+ - Provides AI-powered inline code reviews with suggested fixes for critical issues.
77
79
  - Applies relevant labels using GPT-5 for intelligent categorization.
78
80
  - **Issues:** Automatically applies relevant labels using GPT-5 when new issues are created.
79
81
 
@@ -0,0 +1,19 @@
1
+ actions/__init__.py,sha256=TJd-NrNbVN3UJfNKfPjHUjsNhC7dbGDea80Ig0A1IVk,772
2
+ actions/dispatch_actions.py,sha256=8jaaVkA_LSlpUQ4tuzmQtf2kw3G09uVRD_LmJyXYKNE,4215
3
+ actions/first_interaction.py,sha256=X0eUAov3HcdNPyNa6tpl3ttbfiBhJZndlyy_LGDmsqU,9244
4
+ actions/review_pr.py,sha256=P9ONfDxIE3w7PcTcgBhatTfxb2L_ghqxQjk6UEUipHs,15485
5
+ actions/summarize_pr.py,sha256=XLYsNTf4J6VPyyecZcuiJaSBDgjDSWFj37v5vb1ATCA,5773
6
+ actions/summarize_release.py,sha256=_067Q5AP-Zdnt_qzhHaCuGCr7T4MXSB5_N-M5GX6qgQ,9081
7
+ actions/update_file_headers.py,sha256=E5fKYLdeW16-BHCcuqxohGpGZqgEh-WX4ZmCQJw2R90,6684
8
+ actions/update_markdown_code_blocks.py,sha256=w3DTRltg2Rmr4-qrNawv_S2vJbheKE0tne1iz79FzXg,8692
9
+ actions/utils/__init__.py,sha256=sKNx6o5jcAraEdGFph0o-YC7dMMY-dg_FprIBa6Jydw,1027
10
+ actions/utils/common_utils.py,sha256=8ZmgaXZU3J2sg-HSaldp3hHYq7bI3akcJHdIXPmcNAo,11908
11
+ actions/utils/github_utils.py,sha256=voWQbn6eeqvhDceVfokHypSydlmmNPsUOuisAqGJ1CQ,18963
12
+ actions/utils/openai_utils.py,sha256=91ohkSlldVZt-icYcK7ZLfgQfnMbf4mTWT3nRnQ1QG4,10486
13
+ actions/utils/version_utils.py,sha256=EIbm3iZVNyNl3dh8aNz_9ITeTC93ZxfyUzIRkO3tSXw,3242
14
+ ultralytics_actions-0.1.2.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
15
+ ultralytics_actions-0.1.2.dist-info/METADATA,sha256=uZQWWi4EX-5Cpu_H8VdWxyzFzG7MmearTFZsVTVQjz4,12389
16
+ ultralytics_actions-0.1.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
17
+ ultralytics_actions-0.1.2.dist-info/entry_points.txt,sha256=n_VbDs3Xj33daaeN_2D72UTEuyeH8hVc6-CPH55ymkY,496
18
+ ultralytics_actions-0.1.2.dist-info/top_level.txt,sha256=5apM5x80QlJcGbACn1v3fkmIuL1-XQCKcItJre7w7Tw,8
19
+ ultralytics_actions-0.1.2.dist-info/RECORD,,
@@ -1,19 +0,0 @@
1
- actions/__init__.py,sha256=0aWgNpfTBBLWZ0b0Ij8EQ1K5fKag5uAN4U1gNL7NdJY,772
2
- actions/dispatch_actions.py,sha256=RPqjsnH_8932oLaPp9wWRCWKTaMp8M9dn6CIAMtNjN0,4230
3
- actions/first_interaction.py,sha256=bPG_BMp_k-iJbeQ-fI1bTRHxnfo86ZdIhb0wytBGXVo,16511
4
- actions/review_pr.py,sha256=OR8qF9Eo1JDdjHyau5pnVq2-9c4buru0IoyNgOrsE8E,11979
5
- actions/summarize_pr.py,sha256=2qDkSlonQGytJXcthsggPE-AKu2xhtcsWwIaBgkSmAk,10499
6
- actions/summarize_release.py,sha256=1MQ7Cefv4GTRxr10LwX4b6CFyYZNLNrbCzUKlokUoKE,8671
7
- actions/update_file_headers.py,sha256=E5fKYLdeW16-BHCcuqxohGpGZqgEh-WX4ZmCQJw2R90,6684
8
- actions/update_markdown_code_blocks.py,sha256=XOW0k5f84yYzoXE-zneX0QJeM4dseusPJha8d6kV9hE,8747
9
- actions/utils/__init__.py,sha256=7k4cmFX0Td99Uzgsd8Mm-E0xq5kQ5ZJoPM_oGCVD4CU,804
10
- actions/utils/common_utils.py,sha256=8ZmgaXZU3J2sg-HSaldp3hHYq7bI3akcJHdIXPmcNAo,11908
11
- actions/utils/github_utils.py,sha256=GvU_GSwEr_nA59CgyrurjHSCyKpDSL3LDm69OnuAdJI,8117
12
- actions/utils/openai_utils.py,sha256=wo1VBsDwcnkl9uyKt3D8ZY0OjcO_voc6tVbbcfcRl88,4315
13
- actions/utils/version_utils.py,sha256=EIbm3iZVNyNl3dh8aNz_9ITeTC93ZxfyUzIRkO3tSXw,3242
14
- ultralytics_actions-0.1.0.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
15
- ultralytics_actions-0.1.0.dist-info/METADATA,sha256=uPDfpMtIiZvuCxbeIXzQ1gFua0W0m5JjvSY3qmhL41Q,12166
16
- ultralytics_actions-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
17
- ultralytics_actions-0.1.0.dist-info/entry_points.txt,sha256=n_VbDs3Xj33daaeN_2D72UTEuyeH8hVc6-CPH55ymkY,496
18
- ultralytics_actions-0.1.0.dist-info/top_level.txt,sha256=5apM5x80QlJcGbACn1v3fkmIuL1-XQCKcItJre7w7Tw,8
19
- ultralytics_actions-0.1.0.dist-info/RECORD,,