ultralytics-actions 0.1.6__tar.gz → 0.1.7__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.6 → ultralytics_actions-0.1.7}/PKG-INFO +1 -1
  2. {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/actions/__init__.py +1 -1
  3. {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/actions/review_pr.py +62 -30
  4. {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/ultralytics_actions.egg-info/PKG-INFO +1 -1
  5. {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/LICENSE +0 -0
  6. {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/README.md +0 -0
  7. {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/actions/dispatch_actions.py +0 -0
  8. {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/actions/first_interaction.py +0 -0
  9. {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/actions/summarize_pr.py +0 -0
  10. {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/actions/summarize_release.py +0 -0
  11. {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/actions/update_file_headers.py +0 -0
  12. {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/actions/update_markdown_code_blocks.py +0 -0
  13. {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/actions/utils/__init__.py +0 -0
  14. {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/actions/utils/common_utils.py +0 -0
  15. {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/actions/utils/github_utils.py +0 -0
  16. {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/actions/utils/openai_utils.py +0 -0
  17. {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/actions/utils/version_utils.py +0 -0
  18. {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/pyproject.toml +0 -0
  19. {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/setup.cfg +0 -0
  20. {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/tests/test_cli_commands.py +0 -0
  21. {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/tests/test_common_utils.py +0 -0
  22. {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/tests/test_dispatch_actions.py +0 -0
  23. {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/tests/test_file_headers.py +0 -0
  24. {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/tests/test_first_interaction.py +0 -0
  25. {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/tests/test_github_utils.py +0 -0
  26. {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/tests/test_init.py +0 -0
  27. {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/tests/test_openai_utils.py +0 -0
  28. {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/tests/test_summarize_pr.py +0 -0
  29. {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/tests/test_summarize_release.py +0 -0
  30. {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/tests/test_update_markdown_codeblocks.py +0 -0
  31. {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/tests/test_urls.py +0 -0
  32. {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/ultralytics_actions.egg-info/SOURCES.txt +0 -0
  33. {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/ultralytics_actions.egg-info/dependency_links.txt +0 -0
  34. {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/ultralytics_actions.egg-info/entry_points.txt +0 -0
  35. {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/ultralytics_actions.egg-info/requires.txt +0 -0
  36. {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/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.6
3
+ Version: 0.1.7
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.6"
26
+ __version__ = "0.1.7"
@@ -31,25 +31,31 @@ SKIP_PATTERNS = [
31
31
 
32
32
 
33
33
  def parse_diff_files(diff_text: str) -> dict:
34
- """Parse diff to extract file paths, valid line numbers, and line content for comments."""
35
- files, current_file, current_line = {}, None, 0
34
+ """Parse diff to extract file paths, valid line numbers, and line content for comments (both sides)."""
35
+ files, current_file, new_line, old_line = {}, None, 0, 0
36
36
 
37
37
  for line in diff_text.split("\n"):
38
38
  if line.startswith("diff --git"):
39
39
  match = re.search(r" b/(.+)$", line)
40
40
  current_file = match.group(1) if match else None
41
- current_line = 0
41
+ new_line, old_line = 0, 0
42
42
  if current_file:
43
- files[current_file] = {}
43
+ files[current_file] = {"RIGHT": {}, "LEFT": {}}
44
44
  elif line.startswith("@@") and current_file:
45
- match = re.search(r"@@.*\+(\d+)(?:,\d+)?", line)
46
- current_line = int(match.group(1)) if match else 0
47
- elif current_file and current_line > 0:
45
+ # Extract both old and new line numbers
46
+ match = re.search(r"@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)?", line)
47
+ if match:
48
+ old_line, new_line = int(match.group(1)), int(match.group(2))
49
+ elif current_file and (new_line > 0 or old_line > 0):
48
50
  if line.startswith("+") and not line.startswith("+++"):
49
- files[current_file][current_line] = line[1:]
50
- current_line += 1
51
- elif not line.startswith("-"):
52
- current_line += 1
51
+ files[current_file]["RIGHT"][new_line] = line[1:] # Added line (right/new side)
52
+ new_line += 1
53
+ elif line.startswith("-") and not line.startswith("---"):
54
+ files[current_file]["LEFT"][old_line] = line[1:] # Removed line (left/old side)
55
+ old_line += 1
56
+ elif not line.startswith("\\"): # Context line (ignore "No newline" markers)
57
+ new_line += 1
58
+ old_line += 1
53
59
 
54
60
  return files
55
61
 
@@ -65,8 +71,8 @@ def generate_pr_review(repository: str, diff_text: str, pr_title: str, pr_descri
65
71
 
66
72
  # Filter out generated/vendored files
67
73
  filtered_files = {
68
- path: lines
69
- for path, lines in diff_files.items()
74
+ path: sides
75
+ for path, sides in diff_files.items()
70
76
  if not any(re.search(pattern, path) for pattern in SKIP_PATTERNS)
71
77
  }
72
78
  skipped_count = len(diff_files) - len(filtered_files)
@@ -77,7 +83,7 @@ def generate_pr_review(repository: str, diff_text: str, pr_title: str, pr_descri
77
83
 
78
84
  file_list = list(diff_files.keys())
79
85
  diff_truncated = len(diff_text) > MAX_PROMPT_CHARS
80
- lines_changed = sum(len(lines) for lines in diff_files.values())
86
+ lines_changed = sum(len(sides["RIGHT"]) + len(sides["LEFT"]) for sides in diff_files.values())
81
87
 
82
88
  content = (
83
89
  "You are an expert code reviewer for Ultralytics. Provide detailed inline comments on specific code changes.\n\n"
@@ -101,10 +107,18 @@ def generate_pr_review(repository: str, diff_text: str, pr_title: str, pr_descri
101
107
  "- Suggestion content must match the exact indentation of the original line\n"
102
108
  "- Avoid triple backticks (```) in suggestions as they break markdown formatting\n"
103
109
  "- It's better to flag an issue without a suggestion than provide a wrong or uncertain fix\n\n"
110
+ "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"
104
117
  "Return JSON: "
105
- '{"comments": [{"file": "exact/path", "line": N, "severity": "HIGH", "message": "...", "suggestion": "..."}], "summary": "..."}\n\n'
118
+ '{"comments": [{"file": "exact/path", "line": N, "side": "RIGHT", "severity": "HIGH", "message": "..."}], "summary": "..."}\n\n'
106
119
  "Rules:\n"
107
- "- Only NEW lines (+ in diff), exact paths (no ./), correct line numbers from @@ hunks\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"
108
122
  "- Severity: CRITICAL, HIGH, MEDIUM, LOW, SUGGESTION\n"
109
123
  f"- Files changed: {len(file_list)} ({', '.join(file_list[:10])}{'...' if len(file_list) > 10 else ''})\n"
110
124
  f"- Lines changed: {lines_changed}\n"
@@ -126,11 +140,10 @@ def generate_pr_review(repository: str, diff_text: str, pr_title: str, pr_descri
126
140
 
127
141
  try:
128
142
  response = get_completion(messages, reasoning_effort="medium", model="gpt-5-codex")
129
- print("\n" + "=" * 80 + f"\nFULL AI RESPONSE:\n{response}\n" + "=" * 80 + "\n")
130
143
 
131
144
  json_str = re.search(r"```(?:json)?\s*(\{.*?\})\s*```", response, re.DOTALL)
132
145
  review_data = json.loads(json_str.group(1) if json_str else response)
133
-
146
+ print(json.dumps(review_data, indent=2))
134
147
  print(f"AI generated {len(review_data.get('comments', []))} comments")
135
148
 
136
149
  # Validate, filter, and deduplicate comments
@@ -138,11 +151,27 @@ def generate_pr_review(repository: str, diff_text: str, pr_title: str, pr_descri
138
151
  for c in review_data.get("comments", []):
139
152
  file_path, line_num = c.get("file"), c.get("line", 0)
140
153
  start_line = c.get("start_line")
154
+ side = (c.get("side") or "RIGHT").upper() # Default to RIGHT (added lines)
141
155
 
142
- # Validate line numbers are in diff
143
- if file_path not in diff_files or line_num not in diff_files[file_path]:
144
- print(f"Filtered out {file_path}:{line_num} (available: {list(diff_files.get(file_path, {}))[:10]}...)")
156
+ # Validate line numbers are in diff (check appropriate side)
157
+ if file_path not in diff_files:
158
+ print(f"Filtered out {file_path}:{line_num} (file not in diff)")
145
159
  continue
160
+ if line_num not in diff_files[file_path].get(side, {}):
161
+ # Try other side if not found
162
+ other_side = "LEFT" if side == "RIGHT" else "RIGHT"
163
+ if line_num in diff_files[file_path].get(other_side, {}):
164
+ print(f"Switching {file_path}:{line_num} from {side} to {other_side}")
165
+ c["side"] = other_side
166
+ side = other_side
167
+ # GitHub rejects suggestions on removed lines
168
+ if side == "LEFT" and c.get("suggestion"):
169
+ print(f"Dropping suggestion for {file_path}:{line_num} - LEFT side doesn't support suggestions")
170
+ c.pop("suggestion", None)
171
+ else:
172
+ available = {s: list(diff_files[file_path][s].keys())[:10] for s in ["RIGHT", "LEFT"]}
173
+ print(f"Filtered out {file_path}:{line_num} (available: {available})")
174
+ continue
146
175
 
147
176
  # Validate start_line if provided - drop start_line for suggestions (single-line only)
148
177
  if start_line:
@@ -152,12 +181,12 @@ def generate_pr_review(repository: str, diff_text: str, pr_title: str, pr_descri
152
181
  elif start_line >= line_num:
153
182
  print(f"Invalid start_line {start_line} >= line {line_num} for {file_path}, dropping start_line")
154
183
  c.pop("start_line", None)
155
- elif start_line not in diff_files[file_path]:
184
+ elif start_line not in diff_files[file_path].get(side, {}):
156
185
  print(f"start_line {start_line} not in diff for {file_path}, dropping start_line")
157
186
  c.pop("start_line", None)
158
187
 
159
- # Deduplicate by line number
160
- key = f"{file_path}:{line_num}"
188
+ # Deduplicate by line number and side
189
+ key = f"{file_path}:{side}:{line_num}"
161
190
  if key not in unique_comments:
162
191
  unique_comments[key] = c
163
192
  else:
@@ -231,8 +260,8 @@ def post_review_summary(event: Action, review_data: dict, review_number: int) ->
231
260
  "please",
232
261
  "should",
233
262
  "must",
234
- "need to",
235
- "needs to",
263
+ "raise",
264
+ "needs",
236
265
  "before merging",
237
266
  "fix",
238
267
  "error",
@@ -269,22 +298,25 @@ def post_review_summary(event: Action, review_data: dict, review_number: int) ->
269
298
  severity = comment.get("severity") or "SUGGESTION"
270
299
  comment_body = f"{EMOJI_MAP.get(severity, '💭')} **{severity}**: {(comment.get('message') or '')[:1000]}"
271
300
 
301
+ # Get side (LEFT for removed lines, RIGHT for added lines)
302
+ side = comment.get("side", "RIGHT")
303
+
272
304
  if suggestion := comment.get("suggestion"):
273
305
  suggestion = suggestion[:1000] # Clip suggestion length
274
306
  if "```" not in suggestion:
275
307
  # Extract original line indentation and apply to suggestion
276
- if original_line := review_data.get("diff_files", {}).get(file_path, {}).get(line):
308
+ if original_line := review_data.get("diff_files", {}).get(file_path, {}).get(side, {}).get(line):
277
309
  indent = len(original_line) - len(original_line.lstrip())
278
310
  suggestion = " " * indent + suggestion.strip()
279
311
  comment_body += f"\n\n**Suggested change:**\n```suggestion\n{suggestion}\n```"
280
312
 
281
313
  # Build comment with optional start_line for multi-line context
282
- review_comment = {"path": file_path, "line": line, "body": comment_body, "side": "RIGHT"}
314
+ review_comment = {"path": file_path, "line": line, "body": comment_body, "side": side}
283
315
  if start_line := comment.get("start_line"):
284
316
  if start_line < line:
285
317
  review_comment["start_line"] = start_line
286
- review_comment["start_side"] = "RIGHT"
287
- print(f"Multi-line comment: {file_path}:{start_line}-{line}")
318
+ review_comment["start_side"] = side
319
+ print(f"Multi-line comment: {file_path}:{start_line}-{line} ({side})")
288
320
 
289
321
  review_comments.append(review_comment)
290
322
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ultralytics-actions
3
- Version: 0.1.6
3
+ Version: 0.1.7
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>