ultralytics-actions 0.1.2__tar.gz → 0.1.3__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.3}/PKG-INFO +1 -1
  2. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.3}/actions/__init__.py +1 -1
  3. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.3}/actions/first_interaction.py +16 -1
  4. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.3}/actions/review_pr.py +22 -35
  5. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.3}/actions/utils/github_utils.py +17 -3
  6. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.3}/ultralytics_actions.egg-info/PKG-INFO +1 -1
  7. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.3}/LICENSE +0 -0
  8. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.3}/README.md +0 -0
  9. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.3}/actions/dispatch_actions.py +0 -0
  10. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.3}/actions/summarize_pr.py +0 -0
  11. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.3}/actions/summarize_release.py +0 -0
  12. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.3}/actions/update_file_headers.py +0 -0
  13. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.3}/actions/update_markdown_code_blocks.py +0 -0
  14. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.3}/actions/utils/__init__.py +0 -0
  15. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.3}/actions/utils/common_utils.py +0 -0
  16. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.3}/actions/utils/openai_utils.py +0 -0
  17. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.3}/actions/utils/version_utils.py +0 -0
  18. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.3}/pyproject.toml +0 -0
  19. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.3}/setup.cfg +0 -0
  20. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.3}/tests/test_cli_commands.py +0 -0
  21. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.3}/tests/test_common_utils.py +0 -0
  22. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.3}/tests/test_dispatch_actions.py +0 -0
  23. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.3}/tests/test_file_headers.py +0 -0
  24. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.3}/tests/test_first_interaction.py +0 -0
  25. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.3}/tests/test_github_utils.py +0 -0
  26. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.3}/tests/test_init.py +0 -0
  27. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.3}/tests/test_openai_utils.py +0 -0
  28. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.3}/tests/test_summarize_pr.py +0 -0
  29. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.3}/tests/test_summarize_release.py +0 -0
  30. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.3}/tests/test_update_markdown_codeblocks.py +0 -0
  31. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.3}/tests/test_urls.py +0 -0
  32. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.3}/ultralytics_actions.egg-info/SOURCES.txt +0 -0
  33. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.3}/ultralytics_actions.egg-info/dependency_links.txt +0 -0
  34. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.3}/ultralytics_actions.egg-info/entry_points.txt +0 -0
  35. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.3}/ultralytics_actions.egg-info/requires.txt +0 -0
  36. {ultralytics_actions-0.1.2 → ultralytics_actions-0.1.3}/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.3
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>
@@ -23,4 +23,4 @@
23
23
  # ├── test_summarize_pr.py
24
24
  # └── ...
25
25
 
26
- __version__ = "0.1.2"
26
+ __version__ = "0.1.3"
@@ -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,13 +187,18 @@ 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)
191
196
 
192
197
  if summary := response.get("summary"):
193
198
  print("Updating PR description with summary...")
194
- event.update_pr_description(number, SUMMARY_START + summary)
199
+ event.update_pr_description(number, SUMMARY_START + summary + "\n\n" + body)
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"
@@ -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:
@@ -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(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ultralytics-actions
3
- Version: 0.1.2
3
+ Version: 0.1.3
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>