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.
- {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/PKG-INFO +1 -1
- {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/actions/__init__.py +1 -1
- {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/actions/review_pr.py +62 -30
- {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/ultralytics_actions.egg-info/PKG-INFO +1 -1
- {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/LICENSE +0 -0
- {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/README.md +0 -0
- {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/actions/dispatch_actions.py +0 -0
- {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/actions/first_interaction.py +0 -0
- {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/actions/summarize_pr.py +0 -0
- {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/actions/summarize_release.py +0 -0
- {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/actions/update_file_headers.py +0 -0
- {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/actions/update_markdown_code_blocks.py +0 -0
- {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/actions/utils/__init__.py +0 -0
- {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/actions/utils/common_utils.py +0 -0
- {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/actions/utils/github_utils.py +0 -0
- {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/actions/utils/openai_utils.py +0 -0
- {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/actions/utils/version_utils.py +0 -0
- {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/pyproject.toml +0 -0
- {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/setup.cfg +0 -0
- {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/tests/test_cli_commands.py +0 -0
- {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/tests/test_common_utils.py +0 -0
- {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/tests/test_dispatch_actions.py +0 -0
- {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/tests/test_file_headers.py +0 -0
- {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/tests/test_first_interaction.py +0 -0
- {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/tests/test_github_utils.py +0 -0
- {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/tests/test_init.py +0 -0
- {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/tests/test_openai_utils.py +0 -0
- {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/tests/test_summarize_pr.py +0 -0
- {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/tests/test_summarize_release.py +0 -0
- {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/tests/test_update_markdown_codeblocks.py +0 -0
- {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/tests/test_urls.py +0 -0
- {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/ultralytics_actions.egg-info/SOURCES.txt +0 -0
- {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/ultralytics_actions.egg-info/dependency_links.txt +0 -0
- {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/ultralytics_actions.egg-info/entry_points.txt +0 -0
- {ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/ultralytics_actions.egg-info/requires.txt +0 -0
- {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.
|
|
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>
|
|
@@ -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,
|
|
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
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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][
|
|
50
|
-
|
|
51
|
-
elif not line.startswith("
|
|
52
|
-
|
|
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:
|
|
69
|
-
for path,
|
|
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(
|
|
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, "
|
|
118
|
+
'{"comments": [{"file": "exact/path", "line": N, "side": "RIGHT", "severity": "HIGH", "message": "..."}], "summary": "..."}\n\n'
|
|
106
119
|
"Rules:\n"
|
|
107
|
-
"-
|
|
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
|
|
144
|
-
print(f"Filtered out {file_path}:{line_num} (
|
|
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
|
-
"
|
|
235
|
-
"needs
|
|
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":
|
|
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"] =
|
|
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
|
|
{ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/ultralytics_actions.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ultralytics-actions
|
|
3
|
-
Version: 0.1.
|
|
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>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/actions/update_markdown_code_blocks.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/tests/test_update_markdown_codeblocks.py
RENAMED
|
File without changes
|
|
File without changes
|
{ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/ultralytics_actions.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/ultralytics_actions.egg-info/requires.txt
RENAMED
|
File without changes
|
{ultralytics_actions-0.1.6 → ultralytics_actions-0.1.7}/ultralytics_actions.egg-info/top_level.txt
RENAMED
|
File without changes
|