ultralytics-actions 0.1.7__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.
- {ultralytics_actions-0.1.7 → ultralytics_actions-0.1.9}/PKG-INFO +1 -1
- {ultralytics_actions-0.1.7 → ultralytics_actions-0.1.9}/actions/__init__.py +1 -1
- {ultralytics_actions-0.1.7 → ultralytics_actions-0.1.9}/actions/review_pr.py +52 -59
- {ultralytics_actions-0.1.7 → ultralytics_actions-0.1.9}/actions/utils/github_utils.py +1 -1
- {ultralytics_actions-0.1.7 → ultralytics_actions-0.1.9}/actions/utils/openai_utils.py +3 -2
- {ultralytics_actions-0.1.7 → ultralytics_actions-0.1.9}/ultralytics_actions.egg-info/PKG-INFO +1 -1
- {ultralytics_actions-0.1.7 → ultralytics_actions-0.1.9}/LICENSE +0 -0
- {ultralytics_actions-0.1.7 → ultralytics_actions-0.1.9}/README.md +0 -0
- {ultralytics_actions-0.1.7 → ultralytics_actions-0.1.9}/actions/dispatch_actions.py +0 -0
- {ultralytics_actions-0.1.7 → ultralytics_actions-0.1.9}/actions/first_interaction.py +0 -0
- {ultralytics_actions-0.1.7 → ultralytics_actions-0.1.9}/actions/summarize_pr.py +0 -0
- {ultralytics_actions-0.1.7 → ultralytics_actions-0.1.9}/actions/summarize_release.py +0 -0
- {ultralytics_actions-0.1.7 → ultralytics_actions-0.1.9}/actions/update_file_headers.py +0 -0
- {ultralytics_actions-0.1.7 → ultralytics_actions-0.1.9}/actions/update_markdown_code_blocks.py +0 -0
- {ultralytics_actions-0.1.7 → ultralytics_actions-0.1.9}/actions/utils/__init__.py +0 -0
- {ultralytics_actions-0.1.7 → ultralytics_actions-0.1.9}/actions/utils/common_utils.py +0 -0
- {ultralytics_actions-0.1.7 → ultralytics_actions-0.1.9}/actions/utils/version_utils.py +0 -0
- {ultralytics_actions-0.1.7 → ultralytics_actions-0.1.9}/pyproject.toml +0 -0
- {ultralytics_actions-0.1.7 → ultralytics_actions-0.1.9}/setup.cfg +0 -0
- {ultralytics_actions-0.1.7 → ultralytics_actions-0.1.9}/tests/test_cli_commands.py +0 -0
- {ultralytics_actions-0.1.7 → ultralytics_actions-0.1.9}/tests/test_common_utils.py +0 -0
- {ultralytics_actions-0.1.7 → ultralytics_actions-0.1.9}/tests/test_dispatch_actions.py +0 -0
- {ultralytics_actions-0.1.7 → ultralytics_actions-0.1.9}/tests/test_file_headers.py +0 -0
- {ultralytics_actions-0.1.7 → ultralytics_actions-0.1.9}/tests/test_first_interaction.py +0 -0
- {ultralytics_actions-0.1.7 → ultralytics_actions-0.1.9}/tests/test_github_utils.py +0 -0
- {ultralytics_actions-0.1.7 → ultralytics_actions-0.1.9}/tests/test_init.py +0 -0
- {ultralytics_actions-0.1.7 → ultralytics_actions-0.1.9}/tests/test_openai_utils.py +0 -0
- {ultralytics_actions-0.1.7 → ultralytics_actions-0.1.9}/tests/test_summarize_pr.py +0 -0
- {ultralytics_actions-0.1.7 → ultralytics_actions-0.1.9}/tests/test_summarize_release.py +0 -0
- {ultralytics_actions-0.1.7 → ultralytics_actions-0.1.9}/tests/test_update_markdown_codeblocks.py +0 -0
- {ultralytics_actions-0.1.7 → ultralytics_actions-0.1.9}/tests/test_urls.py +0 -0
- {ultralytics_actions-0.1.7 → ultralytics_actions-0.1.9}/ultralytics_actions.egg-info/SOURCES.txt +0 -0
- {ultralytics_actions-0.1.7 → ultralytics_actions-0.1.9}/ultralytics_actions.egg-info/dependency_links.txt +0 -0
- {ultralytics_actions-0.1.7 → ultralytics_actions-0.1.9}/ultralytics_actions.egg-info/entry_points.txt +0 -0
- {ultralytics_actions-0.1.7 → ultralytics_actions-0.1.9}/ultralytics_actions.egg-info/requires.txt +0 -0
- {ultralytics_actions-0.1.7 → 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.
|
|
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>
|
|
@@ -30,9 +30,10 @@ SKIP_PATTERNS = [
|
|
|
30
30
|
]
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
def parse_diff_files(diff_text: str) -> dict:
|
|
34
|
-
"""Parse diff
|
|
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:]
|
|
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:]
|
|
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("\\"):
|
|
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(
|
|
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
|
-
"-
|
|
112
|
-
"
|
|
113
|
-
"-
|
|
114
|
-
"
|
|
115
|
-
"-
|
|
116
|
-
"-
|
|
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
|
-
"-
|
|
121
|
-
"- Exact paths (no ./), 'side' field
|
|
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,19 +143,26 @@ 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{
|
|
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="
|
|
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)
|
|
146
161
|
print(json.dumps(review_data, indent=2))
|
|
147
|
-
|
|
162
|
+
|
|
163
|
+
# Count comments BEFORE filtering (for COMMENT vs APPROVE decision)
|
|
164
|
+
comments_before_filtering = len(review_data.get("comments", []))
|
|
165
|
+
print(f"AI generated {comments_before_filtering} comments")
|
|
148
166
|
|
|
149
167
|
# Validate, filter, and deduplicate comments
|
|
150
168
|
unique_comments = {}
|
|
@@ -158,20 +176,14 @@ def generate_pr_review(repository: str, diff_text: str, pr_title: str, pr_descri
|
|
|
158
176
|
print(f"Filtered out {file_path}:{line_num} (file not in diff)")
|
|
159
177
|
continue
|
|
160
178
|
if line_num not in diff_files[file_path].get(side, {}):
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
|
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)
|
|
175
187
|
|
|
176
188
|
# Validate start_line if provided - drop start_line for suggestions (single-line only)
|
|
177
189
|
if start_line:
|
|
@@ -195,6 +207,7 @@ def generate_pr_review(repository: str, diff_text: str, pr_title: str, pr_descri
|
|
|
195
207
|
review_data.update(
|
|
196
208
|
{
|
|
197
209
|
"comments": list(unique_comments.values()),
|
|
210
|
+
"comments_before_filtering": comments_before_filtering,
|
|
198
211
|
"diff_files": diff_files,
|
|
199
212
|
"diff_truncated": diff_truncated,
|
|
200
213
|
"skipped_files": skipped_count,
|
|
@@ -251,27 +264,11 @@ def post_review_summary(event: Action, review_data: dict, review_number: int) ->
|
|
|
251
264
|
comments = review_data.get("comments", [])
|
|
252
265
|
summary = review_data.get("summary") or ""
|
|
253
266
|
|
|
254
|
-
# Don't approve if error occurred
|
|
267
|
+
# Don't approve if error occurred, inline comments exist, or critical/high severity issues
|
|
255
268
|
has_error = not summary or ERROR_MARKER in summary
|
|
269
|
+
has_inline_comments = review_data.get("comments_before_filtering", 0) > 0
|
|
256
270
|
has_issues = any(c.get("severity") not in ["LOW", "SUGGESTION", None] for c in comments)
|
|
257
|
-
|
|
258
|
-
phrase in summary.lower()
|
|
259
|
-
for phrase in [
|
|
260
|
-
"please",
|
|
261
|
-
"should",
|
|
262
|
-
"must",
|
|
263
|
-
"raise",
|
|
264
|
-
"needs",
|
|
265
|
-
"before merging",
|
|
266
|
-
"fix",
|
|
267
|
-
"error",
|
|
268
|
-
"issue",
|
|
269
|
-
"problem",
|
|
270
|
-
"warning",
|
|
271
|
-
"concern",
|
|
272
|
-
]
|
|
273
|
-
)
|
|
274
|
-
event_type = "COMMENT" if (has_error or has_issues or requests_changes) else "APPROVE"
|
|
271
|
+
event_type = "COMMENT" if (has_error or has_inline_comments or has_issues) else "APPROVE"
|
|
275
272
|
|
|
276
273
|
body = (
|
|
277
274
|
f"## {review_title}\n\n"
|
|
@@ -296,10 +293,8 @@ def post_review_summary(event: Action, review_data: dict, review_number: int) ->
|
|
|
296
293
|
continue
|
|
297
294
|
|
|
298
295
|
severity = comment.get("severity") or "SUGGESTION"
|
|
299
|
-
comment_body = f"{EMOJI_MAP.get(severity, '💭')} **{severity}**: {(comment.get('message') or '')[:1000]}"
|
|
300
|
-
|
|
301
|
-
# Get side (LEFT for removed lines, RIGHT for added lines)
|
|
302
296
|
side = comment.get("side", "RIGHT")
|
|
297
|
+
comment_body = f"{EMOJI_MAP.get(severity, '💭')} **{severity}**: {(comment.get('message') or '')[:1000]}"
|
|
303
298
|
|
|
304
299
|
if suggestion := comment.get("suggestion"):
|
|
305
300
|
suggestion = suggestion[:1000] # Clip suggestion length
|
|
@@ -316,15 +311,13 @@ def post_review_summary(event: Action, review_data: dict, review_number: int) ->
|
|
|
316
311
|
if start_line < line:
|
|
317
312
|
review_comment["start_line"] = start_line
|
|
318
313
|
review_comment["start_side"] = side
|
|
319
|
-
print(f"Multi-line comment: {file_path}:{start_line}-{line} ({side})")
|
|
320
314
|
|
|
321
315
|
review_comments.append(review_comment)
|
|
322
316
|
|
|
323
317
|
# Submit review with inline comments
|
|
324
|
-
payload = {"commit_id": commit_sha, "body": body, "event": event_type}
|
|
318
|
+
payload = {"commit_id": commit_sha, "body": body.strip(), "event": event_type}
|
|
325
319
|
if review_comments:
|
|
326
320
|
payload["comments"] = review_comments
|
|
327
|
-
print(f"Posting review with {len(review_comments)} inline comments")
|
|
328
321
|
|
|
329
322
|
event.post(
|
|
330
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
|
-
|
|
165
|
-
|
|
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
|
{ultralytics_actions-0.1.7 → ultralytics_actions-0.1.9}/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.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>
|
|
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.7 → ultralytics_actions-0.1.9}/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
|
{ultralytics_actions-0.1.7 → ultralytics_actions-0.1.9}/tests/test_update_markdown_codeblocks.py
RENAMED
|
File without changes
|
|
File without changes
|
{ultralytics_actions-0.1.7 → ultralytics_actions-0.1.9}/ultralytics_actions.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ultralytics_actions-0.1.7 → ultralytics_actions-0.1.9}/ultralytics_actions.egg-info/requires.txt
RENAMED
|
File without changes
|
{ultralytics_actions-0.1.7 → ultralytics_actions-0.1.9}/ultralytics_actions.egg-info/top_level.txt
RENAMED
|
File without changes
|