ultralytics-actions 0.1.2__tar.gz → 0.1.4__tar.gz

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.

Files changed (36) hide show
  1. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.4}/PKG-INFO +3 -3
  2. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.4}/README.md +2 -2
  3. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.4}/actions/__init__.py +1 -1
  4. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.4}/actions/first_interaction.py +15 -0
  5. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.4}/actions/review_pr.py +23 -36
  6. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.4}/actions/utils/github_utils.py +19 -5
  7. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.4}/actions/utils/openai_utils.py +3 -2
  8. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.4}/ultralytics_actions.egg-info/PKG-INFO +3 -3
  9. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.4}/LICENSE +0 -0
  10. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.4}/actions/dispatch_actions.py +0 -0
  11. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.4}/actions/summarize_pr.py +0 -0
  12. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.4}/actions/summarize_release.py +0 -0
  13. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.4}/actions/update_file_headers.py +0 -0
  14. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.4}/actions/update_markdown_code_blocks.py +0 -0
  15. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.4}/actions/utils/__init__.py +0 -0
  16. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.4}/actions/utils/common_utils.py +0 -0
  17. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.4}/actions/utils/version_utils.py +0 -0
  18. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.4}/pyproject.toml +0 -0
  19. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.4}/setup.cfg +0 -0
  20. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.4}/tests/test_cli_commands.py +0 -0
  21. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.4}/tests/test_common_utils.py +0 -0
  22. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.4}/tests/test_dispatch_actions.py +0 -0
  23. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.4}/tests/test_file_headers.py +0 -0
  24. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.4}/tests/test_first_interaction.py +0 -0
  25. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.4}/tests/test_github_utils.py +0 -0
  26. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.4}/tests/test_init.py +0 -0
  27. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.4}/tests/test_openai_utils.py +0 -0
  28. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.4}/tests/test_summarize_pr.py +0 -0
  29. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.4}/tests/test_summarize_release.py +0 -0
  30. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.4}/tests/test_update_markdown_codeblocks.py +0 -0
  31. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.4}/tests/test_urls.py +0 -0
  32. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.4}/ultralytics_actions.egg-info/SOURCES.txt +0 -0
  33. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.4}/ultralytics_actions.egg-info/dependency_links.txt +0 -0
  34. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.4}/ultralytics_actions.egg-info/entry_points.txt +0 -0
  35. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.4}/ultralytics_actions.egg-info/requires.txt +0 -0
  36. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.4}/ultralytics_actions.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ultralytics-actions
3
- Version: 0.1.2
3
+ Version: 0.1.4
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,8 +64,8 @@ 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.
68
- - **Auto-labeling:** Relevant labels are applied to issues and pull requests via [OpenAI](https://openai.com/) GPT-5 for intelligent categorization.
67
+ - **PR Review:** AI-powered code reviews identify critical bugs, security issues, and code quality concerns with suggested fixes.
68
+ - **Auto-labeling:** Applies relevant labels to issues and PRs via [OpenAI](https://openai.com/) GPT-5 for intelligent categorization.
69
69
 
70
70
  ## 🛠️ How It Works
71
71
 
@@ -26,8 +26,8 @@ Ultralytics Actions automatically applies formats, updates, and enhancements usi
26
26
  - **Spell Check:** Common misspellings are caught using [codespell](https://github.com/codespell-project/codespell).
27
27
  - **Broken Links Check:** Broken links in documentation and Markdown files are identified using [Lychee](https://github.com/lycheeverse/lychee).
28
28
  - **PR Summary:** Concise Pull Request summaries are generated using [OpenAI](https://openai.com/) GPT-5, improving clarity and review efficiency.
29
- - **PR Review:** AI-powered inline code reviews identify critical bugs, security issues, and code quality concerns with suggested fixes.
30
- - **Auto-labeling:** Relevant labels are applied to issues and pull requests via [OpenAI](https://openai.com/) GPT-5 for intelligent categorization.
29
+ - **PR Review:** AI-powered code reviews identify critical bugs, security issues, and code quality concerns with suggested fixes.
30
+ - **Auto-labeling:** Applies relevant labels to issues and PRs via [OpenAI](https://openai.com/) GPT-5 for intelligent categorization.
31
31
 
32
32
  ## 🛠️ How It Works
33
33
 
@@ -23,4 +23,4 @@
23
23
  # ├── test_summarize_pr.py
24
24
  # └── ...
25
25
 
26
- __version__ = "0.1.2"
26
+ __version__ = "0.1.4"
@@ -5,12 +5,14 @@ from __future__ import annotations
5
5
  import os
6
6
  import time
7
7
 
8
+ from . import review_pr
8
9
  from .utils import Action, filter_labels, get_completion, get_pr_open_response, remove_html_comments
9
10
 
10
11
  SUMMARY_START = (
11
12
  "## 🛠️ PR Summary\n\n<sub>Made with ❤️ by [Ultralytics Actions](https://github.com/ultralytics/actions)<sub>\n\n"
12
13
  )
13
14
  BLOCK_USER = os.getenv("BLOCK_USER", "false").lower() == "true"
15
+ AUTO_PR_REVIEW = os.getenv("REVIEW", "true").lower() == "true"
14
16
 
15
17
 
16
18
  def apply_and_check_labels(event, number, node_id, issue_type, username, labels, label_descriptions):
@@ -185,6 +187,9 @@ def main(*args, **kwargs):
185
187
 
186
188
  # Use unified PR open response for new PRs (summary + labels + first comment in 1 API call)
187
189
  if issue_type == "pull request" and action == "opened":
190
+ if event.should_skip_pr_author():
191
+ return
192
+
188
193
  print("Processing PR open with unified API call...")
189
194
  diff = event.get_pr_diff()
190
195
  response = get_pr_open_response(event.repository, diff, title, body, label_descriptions)
@@ -192,6 +197,8 @@ def main(*args, **kwargs):
192
197
  if summary := response.get("summary"):
193
198
  print("Updating PR description with summary...")
194
199
  event.update_pr_description(number, SUMMARY_START + summary)
200
+ else:
201
+ summary = body
195
202
 
196
203
  if relevant_labels := response.get("labels", []):
197
204
  apply_and_check_labels(event, number, node_id, issue_type, username, relevant_labels, label_descriptions)
@@ -200,6 +207,14 @@ def main(*args, **kwargs):
200
207
  print("Adding first interaction comment...")
201
208
  time.sleep(1) # sleep to ensure label added first
202
209
  event.add_comment(number, node_id, first_comment, issue_type)
210
+
211
+ # Automatic PR review after first interaction
212
+ if AUTO_PR_REVIEW:
213
+ print("Starting automatic PR review...")
214
+ review_number = review_pr.dismiss_previous_reviews(event)
215
+ review_data = review_pr.generate_pr_review(event.repository, diff, title, summary)
216
+ review_pr.post_review_summary(event, review_data, review_number)
217
+ print("PR review completed")
203
218
  return
204
219
 
205
220
  # Handle issues and discussions (NOT PRs)
@@ -79,34 +79,20 @@ def generate_pr_review(repository: str, diff_text: str, pr_title: str, pr_descri
79
79
  diff_truncated = len(diff_text) > limit
80
80
  lines_changed = sum(len(lines) for lines in diff_files.values())
81
81
 
82
- comment_guidance = (
83
- "Provide up to 1-3 comments only if critical issues exist"
84
- if lines_changed < 50
85
- else "Provide up to 3-5 comments only if high-impact issues exist"
86
- if lines_changed < 200
87
- else "Provide up to 5-10 comments only for the most critical issues"
88
- )
89
-
90
82
  content = (
91
83
  "You are an expert code reviewer for Ultralytics. Provide detailed inline comments on specific code changes.\n\n"
92
- "Focus on: Code quality, style, best practices, bugs, edge cases, error handling, performance, security, documentation, test coverage\n\n"
93
- "FORMATTING: Use backticks for all summary and suggestion code, files, branches, functions, variables, packages, e.g. `x=3`\n\n"
84
+ "Focus on: Bugs, security, performance, best practices, edge cases, error handling\n\n"
85
+ "FORMATTING: Use backticks for code: `x=3`, `file.py`, `function()`\n\n"
94
86
  "CRITICAL RULES:\n"
95
- "1. Quality over quantity: Zero comments is fine for clean code - only flag truly important issues\n"
96
- f"2. {comment_guidance} - these are maximums, not targets\n"
97
- "3. CRITICAL: Do not post separate comments on adjacent/nearby lines (within 10 lines). Combine all related issues into ONE comment\n"
98
- "4. When combining issues from multiple lines, use 'start_line' (first line) and 'line' (last line) to highlight the entire range\n"
99
- "5. Each comment must reference separate areas - no overlapping line ranges\n"
100
- "6. Prioritize: CRITICAL bugs/security > HIGH impact issues > code quality\n"
101
- "7. Keep comments concise, friendly, and easy to understand - avoid jargon when possible\n"
102
- "8. DO not comment on routine changes: adding imports, adding dependencies, updating version numbers, standard refactoring\n"
103
- "9. Trust the developer - only flag issues with clear evidence of problems, not hypothetical concerns\n\n"
104
- "SUMMARY GUIDELINES:\n"
105
- "- Keep summary brief, clear, and actionable - avoid overly detailed explanations\n"
106
- "- Highlight only the most important findings\n"
107
- "- Do NOT include file names or line numbers in the summary - inline comments already show exact locations\n"
108
- "- Focus on what needs to be fixed, not where\n\n"
109
- "CODE SUGGESTIONS:\n"
87
+ "1. Quality over quantity - zero comments is fine for clean code, only flag truly important issues\n"
88
+ "2. Combine issues that are directly related to the same problem\n"
89
+ "3. Use 'start_line' and 'line' to highlight multi-line ranges when issues span multiple lines\n"
90
+ "4. Prioritize: CRITICAL bugs/security > HIGH impact > code quality improvements\n"
91
+ "5. Keep comments concise and friendly - avoid jargon\n"
92
+ "6. Skip routine changes: imports, version updates, standard refactoring\n\n"
93
+ "SUMMARY:\n"
94
+ "- Brief and actionable - what needs fixing, not where (locations shown in inline comments)\n\n"
95
+ "SUGGESTIONS:\n"
110
96
  "- ONLY provide 'suggestion' field when you have high certainty the code is problematic AND sufficient context for a confident fix\n"
111
97
  "- If uncertain about the correct fix, omit 'suggestion' field and explain the concern in 'message' only\n"
112
98
  "- Suggestions must be ready-to-merge code with NO comments, placeholders, or explanations\n"
@@ -118,10 +104,7 @@ def generate_pr_review(repository: str, diff_text: str, pr_title: str, pr_descri
118
104
  "Return JSON: "
119
105
  '{"comments": [{"file": "exact/path", "line": N, "severity": "HIGH", "message": "...", "suggestion": "..."}], "summary": "..."}\n\n'
120
106
  "Rules:\n"
121
- "- Only comment on NEW lines (starting with + in diff)\n"
122
- "- Use exact file paths from diff (no ./ prefix)\n"
123
- "- Line numbers must match NEW file line numbers from @@ hunks\n"
124
- "- When '- old' then '+ new', new line keeps SAME line number\n"
107
+ "- Only NEW lines (+ in diff), exact paths (no ./), correct line numbers from @@ hunks\n"
125
108
  "- Severity: CRITICAL, HIGH, MEDIUM, LOW, SUGGESTION\n"
126
109
  f"- Files changed: {len(file_list)} ({', '.join(file_list[:10])}{'...' if len(file_list) > 10 else ''})\n"
127
110
  f"- Lines changed: {lines_changed}\n"
@@ -142,7 +125,7 @@ def generate_pr_review(repository: str, diff_text: str, pr_title: str, pr_descri
142
125
  ]
143
126
 
144
127
  try:
145
- response = get_completion(messages, reasoning_effort="medium")
128
+ response = get_completion(messages, reasoning_effort="medium", model="gpt-5-codex")
146
129
  print("\n" + "=" * 80 + f"\nFULL AI RESPONSE:\n{response}\n" + "=" * 80 + "\n")
147
130
 
148
131
  json_str = re.search(r"```(?:json)?\s*(\{.*?\})\s*```", response, re.DOTALL)
@@ -241,12 +224,12 @@ def post_review_summary(event: Action, review_data: dict, review_number: int) ->
241
224
  body = (
242
225
  f"## {review_title}\n\n"
243
226
  "<sub>Made with ❤️ by [Ultralytics Actions](https://github.com/ultralytics/actions)</sub>\n\n"
244
- f"{review_data.get('summary', 'Review completed')}\n\n"
227
+ f"{review_data.get('summary', 'Review completed')[:1000]}\n\n" # Clip summary length
245
228
  )
246
229
 
247
230
  if comments:
248
231
  shown = min(len(comments), 10)
249
- body += f"💬 Posted {shown} inline comment{'s' if shown != 1 else ''}{' (10 shown, more available)' if len(comments) > 10 else ''}\n"
232
+ body += f"💬 Posted {shown} inline comment{'s' if shown != 1 else ''}\n"
250
233
 
251
234
  if review_data.get("diff_truncated"):
252
235
  body += "\n⚠️ **Large PR**: Review focused on critical issues. Some details may not be covered.\n"
@@ -256,14 +239,15 @@ def post_review_summary(event: Action, review_data: dict, review_number: int) ->
256
239
 
257
240
  # Build inline comments for the review
258
241
  review_comments = []
259
- for comment in comments[:10]:
242
+ for comment in comments[:10]: # Limit to 10 comments
260
243
  if not (file_path := comment.get("file")) or not (line := comment.get("line", 0)):
261
244
  continue
262
245
 
263
246
  severity = comment.get("severity", "SUGGESTION")
264
- comment_body = f"{EMOJI_MAP.get(severity, '💭')} **{severity}**: {comment.get('message', '')}"
247
+ comment_body = f"{EMOJI_MAP.get(severity, '💭')} **{severity}**: {comment.get('message', '')[:1000]}"
265
248
 
266
249
  if suggestion := comment.get("suggestion"):
250
+ suggestion = suggestion[:1000] # Clip suggestion length
267
251
  if "```" not in suggestion:
268
252
  # Extract original line indentation and apply to suggestion
269
253
  if original_line := review_data.get("diff_files", {}).get(file_path, {}).get(line):
@@ -307,12 +291,15 @@ def main(*args, **kwargs):
307
291
  print(f"Skipping: PR state is {event.pr.get('state') if event.pr else 'None'}")
308
292
  return
309
293
 
294
+ # Skip self-authored or bot PRs unless manually review_requested
295
+ if event.event_data.get("action") != "review_requested" and event.should_skip_pr_author():
296
+ return
297
+
310
298
  print(f"Starting PR review for #{event.pr['number']}")
311
299
  review_number = dismiss_previous_reviews(event)
312
300
 
313
301
  diff = event.get_pr_diff()
314
- pr_description = event._pr_summary_cache or event.pr.get("body", "")
315
- review = generate_pr_review(event.repository, diff, event.pr.get("title", ""), pr_description)
302
+ review = generate_pr_review(event.repository, diff, event.pr.get("title", ""), event.pr.get("body", ""))
316
303
 
317
304
  post_review_summary(event, review, review_number)
318
305
  print("PR review completed")
@@ -188,6 +188,20 @@ class Action:
188
188
  """Checks if a user is a member of the organization."""
189
189
  return self.get(f"{GITHUB_API_URL}/orgs/{self.repository.split('/')[0]}/members/{username}").status_code == 204
190
190
 
191
+ def should_skip_pr_author(self) -> bool:
192
+ """Checks if PR should be skipped based on author (self-authored or bot PRs)."""
193
+ if not self.pr:
194
+ return False
195
+ if pr_author := self.pr.get("user", {}).get("login"):
196
+ if pr_author == self.get_username():
197
+ print(f"Skipping: PR author ({pr_author}) is the same as bot")
198
+ return True
199
+ # Check both user.type and [bot] suffix for robust bot detection
200
+ if self.pr.get("user", {}).get("type") == "Bot" or pr_author.endswith("[bot]"):
201
+ print(f"Skipping: PR author ({pr_author}) is a bot")
202
+ return True
203
+ return False
204
+
191
205
  def is_fork_pr(self) -> bool:
192
206
  """Checks if PR is from a fork (different repo than base)."""
193
207
  if not self.pr:
@@ -267,10 +281,10 @@ class Action:
267
281
  start = "## 🛠️ PR Summary"
268
282
  if start in description:
269
283
  print("Existing PR Summary found, replacing.")
270
- updated_description = description.split(start)[0] + new_summary
284
+ updated_description = description.split(start)[0].rstrip() + "\n\n" + new_summary
271
285
  else:
272
286
  print("PR Summary not found, appending.")
273
- updated_description = description + "\n\n" + new_summary
287
+ updated_description = (description.rstrip() + "\n\n" + new_summary) if description.strip() else new_summary
274
288
 
275
289
  self.patch(url, json={"body": updated_description})
276
290
  self._pr_summary_cache = new_summary
@@ -386,7 +400,7 @@ Thank you 🙏
386
400
  try:
387
401
  data = response.json()["data"]["repository"]["pullRequest"]
388
402
  comments = data["reviews"]["nodes"] + data["comments"]["nodes"]
389
- token_username = self.get_username()
403
+ username = self.get_username()
390
404
  author = data["author"]["login"] if data["author"]["__typename"] != "Bot" else None
391
405
 
392
406
  contributors = {x["author"]["login"] for x in comments if x["author"]["__typename"] != "Bot"}
@@ -399,10 +413,10 @@ Thank you 🙏
399
413
  contributors.add(login)
400
414
 
401
415
  contributors.discard(author)
402
- contributors.discard(token_username)
416
+ contributors.discard(username)
403
417
 
404
418
  pr_credit = ""
405
- if author and author != token_username:
419
+ if author and author != username:
406
420
  pr_credit += f"@{author}"
407
421
  if contributors:
408
422
  pr_credit += (" with contributions from " if pr_credit else "") + ", ".join(
@@ -112,6 +112,7 @@ def get_completion(
112
112
  temperature: float = 1.0,
113
113
  reasoning_effort: str = None,
114
114
  response_format: dict = None,
115
+ model: str = OPENAI_MODEL,
115
116
  ) -> str | dict:
116
117
  """Generates a completion using OpenAI's Responses API based on input messages."""
117
118
  assert OPENAI_API_KEY, "OpenAI API key is required."
@@ -122,8 +123,8 @@ def get_completion(
122
123
 
123
124
  max_retries = 2
124
125
  for attempt in range(max_retries + 2):
125
- data = {"model": OPENAI_MODEL, "input": messages, "store": False, "temperature": temperature}
126
- if "gpt-5" in OPENAI_MODEL:
126
+ data = {"model": model, "input": messages, "store": False, "temperature": temperature}
127
+ if "gpt-5" in model:
127
128
  data["reasoning"] = {"effort": reasoning_effort or "low"}
128
129
  # GPT-5 Responses API handles JSON via prompting, not format parameter
129
130
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ultralytics-actions
3
- Version: 0.1.2
3
+ Version: 0.1.4
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,8 +64,8 @@ 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.
68
- - **Auto-labeling:** Relevant labels are applied to issues and pull requests via [OpenAI](https://openai.com/) GPT-5 for intelligent categorization.
67
+ - **PR Review:** AI-powered code reviews identify critical bugs, security issues, and code quality concerns with suggested fixes.
68
+ - **Auto-labeling:** Applies relevant labels to issues and PRs via [OpenAI](https://openai.com/) GPT-5 for intelligent categorization.
69
69
 
70
70
  ## 🛠️ How It Works
71
71