ultralytics-actions 0.1.8__tar.gz → 0.1.9__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.8 → ultralytics_actions-0.1.9}/PKG-INFO +1 -1
  2. {ultralytics_actions-0.1.8 → ultralytics_actions-0.1.9}/actions/__init__.py +1 -1
  3. {ultralytics_actions-0.1.8 → ultralytics_actions-0.1.9}/actions/review_pr.py +44 -39
  4. {ultralytics_actions-0.1.8 → ultralytics_actions-0.1.9}/actions/utils/github_utils.py +1 -1
  5. {ultralytics_actions-0.1.8 → ultralytics_actions-0.1.9}/actions/utils/openai_utils.py +3 -2
  6. {ultralytics_actions-0.1.8 → ultralytics_actions-0.1.9}/ultralytics_actions.egg-info/PKG-INFO +1 -1
  7. {ultralytics_actions-0.1.8 → ultralytics_actions-0.1.9}/LICENSE +0 -0
  8. {ultralytics_actions-0.1.8 → ultralytics_actions-0.1.9}/README.md +0 -0
  9. {ultralytics_actions-0.1.8 → ultralytics_actions-0.1.9}/actions/dispatch_actions.py +0 -0
  10. {ultralytics_actions-0.1.8 → ultralytics_actions-0.1.9}/actions/first_interaction.py +0 -0
  11. {ultralytics_actions-0.1.8 → ultralytics_actions-0.1.9}/actions/summarize_pr.py +0 -0
  12. {ultralytics_actions-0.1.8 → ultralytics_actions-0.1.9}/actions/summarize_release.py +0 -0
  13. {ultralytics_actions-0.1.8 → ultralytics_actions-0.1.9}/actions/update_file_headers.py +0 -0
  14. {ultralytics_actions-0.1.8 → ultralytics_actions-0.1.9}/actions/update_markdown_code_blocks.py +0 -0
  15. {ultralytics_actions-0.1.8 → ultralytics_actions-0.1.9}/actions/utils/__init__.py +0 -0
  16. {ultralytics_actions-0.1.8 → ultralytics_actions-0.1.9}/actions/utils/common_utils.py +0 -0
  17. {ultralytics_actions-0.1.8 → ultralytics_actions-0.1.9}/actions/utils/version_utils.py +0 -0
  18. {ultralytics_actions-0.1.8 → ultralytics_actions-0.1.9}/pyproject.toml +0 -0
  19. {ultralytics_actions-0.1.8 → ultralytics_actions-0.1.9}/setup.cfg +0 -0
  20. {ultralytics_actions-0.1.8 → ultralytics_actions-0.1.9}/tests/test_cli_commands.py +0 -0
  21. {ultralytics_actions-0.1.8 → ultralytics_actions-0.1.9}/tests/test_common_utils.py +0 -0
  22. {ultralytics_actions-0.1.8 → ultralytics_actions-0.1.9}/tests/test_dispatch_actions.py +0 -0
  23. {ultralytics_actions-0.1.8 → ultralytics_actions-0.1.9}/tests/test_file_headers.py +0 -0
  24. {ultralytics_actions-0.1.8 → ultralytics_actions-0.1.9}/tests/test_first_interaction.py +0 -0
  25. {ultralytics_actions-0.1.8 → ultralytics_actions-0.1.9}/tests/test_github_utils.py +0 -0
  26. {ultralytics_actions-0.1.8 → ultralytics_actions-0.1.9}/tests/test_init.py +0 -0
  27. {ultralytics_actions-0.1.8 → ultralytics_actions-0.1.9}/tests/test_openai_utils.py +0 -0
  28. {ultralytics_actions-0.1.8 → ultralytics_actions-0.1.9}/tests/test_summarize_pr.py +0 -0
  29. {ultralytics_actions-0.1.8 → ultralytics_actions-0.1.9}/tests/test_summarize_release.py +0 -0
  30. {ultralytics_actions-0.1.8 → ultralytics_actions-0.1.9}/tests/test_update_markdown_codeblocks.py +0 -0
  31. {ultralytics_actions-0.1.8 → ultralytics_actions-0.1.9}/tests/test_urls.py +0 -0
  32. {ultralytics_actions-0.1.8 → ultralytics_actions-0.1.9}/ultralytics_actions.egg-info/SOURCES.txt +0 -0
  33. {ultralytics_actions-0.1.8 → ultralytics_actions-0.1.9}/ultralytics_actions.egg-info/dependency_links.txt +0 -0
  34. {ultralytics_actions-0.1.8 → ultralytics_actions-0.1.9}/ultralytics_actions.egg-info/entry_points.txt +0 -0
  35. {ultralytics_actions-0.1.8 → ultralytics_actions-0.1.9}/ultralytics_actions.egg-info/requires.txt +0 -0
  36. {ultralytics_actions-0.1.8 → ultralytics_actions-0.1.9}/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.8
3
+ Version: 0.1.9
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.8"
26
+ __version__ = "0.1.9"
@@ -30,9 +30,10 @@ SKIP_PATTERNS = [
30
30
  ]
31
31
 
32
32
 
33
- def parse_diff_files(diff_text: str) -> dict:
34
- """Parse diff to extract file paths, valid line numbers, and line content for comments (both sides)."""
33
+ def parse_diff_files(diff_text: str) -> tuple[dict, str]:
34
+ """Parse diff and return file mapping with line numbers AND augmented diff with explicit line numbers."""
35
35
  files, current_file, new_line, old_line = {}, None, 0, 0
36
+ augmented_lines = []
36
37
 
37
38
  for line in diff_text.split("\n"):
38
39
  if line.startswith("diff --git"):
@@ -41,23 +42,31 @@ def parse_diff_files(diff_text: str) -> dict:
41
42
  new_line, old_line = 0, 0
42
43
  if current_file:
43
44
  files[current_file] = {"RIGHT": {}, "LEFT": {}}
45
+ augmented_lines.append(line)
44
46
  elif line.startswith("@@") and current_file:
45
- # Extract both old and new line numbers
46
47
  match = re.search(r"@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)?", line)
47
48
  if match:
48
49
  old_line, new_line = int(match.group(1)), int(match.group(2))
50
+ augmented_lines.append(line)
49
51
  elif current_file and (new_line > 0 or old_line > 0):
50
52
  if line.startswith("+") and not line.startswith("+++"):
51
- files[current_file]["RIGHT"][new_line] = line[1:] # Added line (right/new side)
53
+ files[current_file]["RIGHT"][new_line] = line[1:]
54
+ augmented_lines.append(f"R{new_line:>5} {line}") # Prefix with RIGHT line number
52
55
  new_line += 1
53
56
  elif line.startswith("-") and not line.startswith("---"):
54
- files[current_file]["LEFT"][old_line] = line[1:] # Removed line (left/old side)
57
+ files[current_file]["LEFT"][old_line] = line[1:]
58
+ augmented_lines.append(f"L{old_line:>5} {line}") # Prefix with LEFT line number
55
59
  old_line += 1
56
- elif not line.startswith("\\"): # Context line (ignore "No newline" markers)
60
+ elif not line.startswith("\\"):
61
+ augmented_lines.append(f" {line}") # Context line, no number
57
62
  new_line += 1
58
63
  old_line += 1
64
+ else:
65
+ augmented_lines.append(line)
66
+ else:
67
+ augmented_lines.append(line)
59
68
 
60
- return files
69
+ return files, "\n".join(augmented_lines)
61
70
 
62
71
 
63
72
  def generate_pr_review(repository: str, diff_text: str, pr_title: str, pr_description: str) -> dict:
@@ -65,7 +74,7 @@ def generate_pr_review(repository: str, diff_text: str, pr_title: str, pr_descri
65
74
  if not diff_text:
66
75
  return {"comments": [], "summary": "No changes detected in diff"}
67
76
 
68
- diff_files = parse_diff_files(diff_text)
77
+ diff_files, augmented_diff = parse_diff_files(diff_text)
69
78
  if not diff_files:
70
79
  return {"comments": [], "summary": "No files with changes detected in diff"}
71
80
 
@@ -82,7 +91,7 @@ def generate_pr_review(repository: str, diff_text: str, pr_title: str, pr_descri
82
91
  return {"comments": [], "summary": f"All {skipped_count} changed files are generated/vendored (skipped review)"}
83
92
 
84
93
  file_list = list(diff_files.keys())
85
- diff_truncated = len(diff_text) > MAX_PROMPT_CHARS
94
+ diff_truncated = len(augmented_diff) > MAX_PROMPT_CHARS
86
95
  lines_changed = sum(len(sides["RIGHT"]) + len(sides["LEFT"]) for sides in diff_files.values())
87
96
 
88
97
  content = (
@@ -108,17 +117,19 @@ def generate_pr_review(repository: str, diff_text: str, pr_title: str, pr_descri
108
117
  "- Avoid triple backticks (```) in suggestions as they break markdown formatting\n"
109
118
  "- It's better to flag an issue without a suggestion than provide a wrong or uncertain fix\n\n"
110
119
  "LINE NUMBERS:\n"
111
- "- You MUST extract line numbers directly from the @@ hunk headers in the diff below\n"
112
- "- RIGHT (added +): Find @@ lines, use numbers after +N (e.g., @@ -10,5 +20,7 @@ means RIGHT starts at line 20)\n"
113
- "- LEFT (removed -): Find @@ lines, use numbers after -N (e.g., @@ -10,5 +20,7 @@ means LEFT starts at line 10)\n"
114
- "- Count forward from hunk start: + lines increment RIGHT, - lines increment LEFT, context lines increment both\n"
115
- "- CRITICAL: Using line numbers not in the diff will cause your comment to be rejected\n"
116
- "- Suggestions only work on RIGHT (added) lines, never on LEFT (removed) lines\n\n"
120
+ "- Each line in the diff is prefixed with its line number for clarity:\n"
121
+ " R 123 +added code <- RIGHT side (new file), line 123\n"
122
+ " L 45 -removed code <- LEFT side (old file), line 45\n"
123
+ " context line <- context (no number needed)\n"
124
+ "- Extract the number after R or L prefix to get the exact line number\n"
125
+ "- Use 'side': 'RIGHT' for R-prefixed lines, 'side': 'LEFT' for L-prefixed lines\n"
126
+ "- Suggestions only work on RIGHT lines, never on LEFT lines\n"
127
+ "- CRITICAL: Only use line numbers that you see explicitly prefixed in the diff\n\n"
117
128
  "Return JSON: "
118
129
  '{"comments": [{"file": "exact/path", "line": N, "side": "RIGHT", "severity": "HIGH", "message": "..."}], "summary": "..."}\n\n'
119
130
  "Rules:\n"
120
- "- Verify line numbers from @@ hunks: +N for RIGHT (added), -N for LEFT (removed)\n"
121
- "- Exact paths (no ./), 'side' field defaults to RIGHT if omitted\n"
131
+ "- Extract line numbers from R#### or L#### prefixes in the diff\n"
132
+ "- Exact paths (no ./), 'side' field must match R (RIGHT) or L (LEFT) prefix\n"
122
133
  "- Severity: CRITICAL, HIGH, MEDIUM, LOW, SUGGESTION\n"
123
134
  f"- Files changed: {len(file_list)} ({', '.join(file_list[:10])}{'...' if len(file_list) > 10 else ''})\n"
124
135
  f"- Lines changed: {lines_changed}\n"
@@ -132,14 +143,18 @@ def generate_pr_review(repository: str, diff_text: str, pr_title: str, pr_descri
132
143
  f"Review this PR in https://github.com/{repository}:\n"
133
144
  f"Title: {pr_title}\n"
134
145
  f"Description: {remove_html_comments(pr_description or '')[:1000]}\n\n"
135
- f"Diff:\n{diff_text[:MAX_PROMPT_CHARS]}\n\n"
146
+ f"Diff:\n{augmented_diff[:MAX_PROMPT_CHARS]}\n\n"
136
147
  "Now review this diff according to the rules above. Return JSON with comments array and summary."
137
148
  ),
138
149
  },
139
150
  ]
140
151
 
152
+ # Debug: print prompts sent to AI
153
+ # print(f"\nSystem prompt (first 1000 chars):\n{messages[0]['content'][:2000]}...\n")
154
+ # print(f"\nUser prompt (first 1000 chars):\n{messages[1]['content'][:2000]}...\n")
155
+
141
156
  try:
142
- response = get_completion(messages, reasoning_effort="medium", model="gpt-5-codex")
157
+ response = get_completion(messages, reasoning_effort="low", model="gpt-5-codex")
143
158
 
144
159
  json_str = re.search(r"```(?:json)?\s*(\{.*?\})\s*```", response, re.DOTALL)
145
160
  review_data = json.loads(json_str.group(1) if json_str else response)
@@ -161,20 +176,14 @@ def generate_pr_review(repository: str, diff_text: str, pr_title: str, pr_descri
161
176
  print(f"Filtered out {file_path}:{line_num} (file not in diff)")
162
177
  continue
163
178
  if line_num not in diff_files[file_path].get(side, {}):
164
- # Try other side if not found
165
- other_side = "LEFT" if side == "RIGHT" else "RIGHT"
166
- if line_num in diff_files[file_path].get(other_side, {}):
167
- print(f"Switching {file_path}:{line_num} from {side} to {other_side}")
168
- c["side"] = other_side
169
- side = other_side
170
- # GitHub rejects suggestions on removed lines
171
- if side == "LEFT" and c.get("suggestion"):
172
- print(f"Dropping suggestion for {file_path}:{line_num} - LEFT side doesn't support suggestions")
173
- c.pop("suggestion", None)
174
- else:
175
- available = {s: list(diff_files[file_path][s].keys())[:10] for s in ["RIGHT", "LEFT"]}
176
- print(f"Filtered out {file_path}:{line_num} (available: {available})")
177
- continue
179
+ available = {s: list(diff_files[file_path][s].keys())[:10] for s in ["RIGHT", "LEFT"]}
180
+ print(f"Filtered out {file_path}:{line_num} (side={side}, available: {available})")
181
+ continue
182
+
183
+ # GitHub rejects suggestions on removed lines
184
+ if side == "LEFT" and c.get("suggestion"):
185
+ print(f"Dropping suggestion for {file_path}:{line_num} - LEFT side doesn't support suggestions")
186
+ c.pop("suggestion", None)
178
187
 
179
188
  # Validate start_line if provided - drop start_line for suggestions (single-line only)
180
189
  if start_line:
@@ -284,10 +293,8 @@ def post_review_summary(event: Action, review_data: dict, review_number: int) ->
284
293
  continue
285
294
 
286
295
  severity = comment.get("severity") or "SUGGESTION"
287
- comment_body = f"{EMOJI_MAP.get(severity, '💭')} **{severity}**: {(comment.get('message') or '')[:1000]}"
288
-
289
- # Get side (LEFT for removed lines, RIGHT for added lines)
290
296
  side = comment.get("side", "RIGHT")
297
+ comment_body = f"{EMOJI_MAP.get(severity, '💭')} **{severity}**: {(comment.get('message') or '')[:1000]}"
291
298
 
292
299
  if suggestion := comment.get("suggestion"):
293
300
  suggestion = suggestion[:1000] # Clip suggestion length
@@ -304,15 +311,13 @@ def post_review_summary(event: Action, review_data: dict, review_number: int) ->
304
311
  if start_line < line:
305
312
  review_comment["start_line"] = start_line
306
313
  review_comment["start_side"] = side
307
- print(f"Multi-line comment: {file_path}:{start_line}-{line} ({side})")
308
314
 
309
315
  review_comments.append(review_comment)
310
316
 
311
317
  # Submit review with inline comments
312
- payload = {"commit_id": commit_sha, "body": body, "event": event_type}
318
+ payload = {"commit_id": commit_sha, "body": body.strip(), "event": event_type}
313
319
  if review_comments:
314
320
  payload["comments"] = review_comments
315
- print(f"Posting review with {len(review_comments)} inline comments")
316
321
 
317
322
  event.post(
318
323
  f"{GITHUB_API_URL}/repos/{event.repository}/pulls/{pr_number}/reviews",
@@ -131,7 +131,7 @@ class Action:
131
131
 
132
132
  if self.verbose:
133
133
  elapsed = r.elapsed.total_seconds()
134
- print(f"{'✓' if success else '✗'} {method.upper()} {url} → {r.status_code} ({elapsed:.1f}s)")
134
+ print(f"{'✓' if success else '✗'} {method.upper()} {url} → {r.status_code} ({elapsed:.1f}s)", flush=True)
135
135
  if not success:
136
136
  try:
137
137
  print(f" ❌ Error: {r.json().get('message', 'Unknown error')}")
@@ -161,8 +161,9 @@ def get_completion(
161
161
  continue
162
162
  raise
163
163
  except requests.exceptions.HTTPError as e:
164
- if attempt < 2 and e.response and e.response.status_code >= 500:
165
- print(f"Server error {e.response.status_code}, retrying in {2**attempt}s")
164
+ status_code = getattr(e.response, "status_code", 0) if e.response else 0
165
+ if attempt < 2 and status_code >= 500:
166
+ print(f"Server error {status_code}, retrying in {2**attempt}s")
166
167
  time.sleep(2**attempt)
167
168
  continue
168
169
  raise
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ultralytics-actions
3
- Version: 0.1.8
3
+ Version: 0.1.9
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>