ultralytics-actions 0.1.8__py3-none-any.whl → 0.1.9__py3-none-any.whl
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.
- actions/__init__.py +1 -1
- actions/review_pr.py +44 -39
- actions/utils/github_utils.py +1 -1
- actions/utils/openai_utils.py +3 -2
- {ultralytics_actions-0.1.8.dist-info → ultralytics_actions-0.1.9.dist-info}/METADATA +1 -1
- {ultralytics_actions-0.1.8.dist-info → ultralytics_actions-0.1.9.dist-info}/RECORD +10 -10
- {ultralytics_actions-0.1.8.dist-info → ultralytics_actions-0.1.9.dist-info}/WHEEL +0 -0
- {ultralytics_actions-0.1.8.dist-info → ultralytics_actions-0.1.9.dist-info}/entry_points.txt +0 -0
- {ultralytics_actions-0.1.8.dist-info → ultralytics_actions-0.1.9.dist-info}/licenses/LICENSE +0 -0
- {ultralytics_actions-0.1.8.dist-info → ultralytics_actions-0.1.9.dist-info}/top_level.txt +0 -0
actions/__init__.py
CHANGED
actions/review_pr.py
CHANGED
|
@@ -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,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{
|
|
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)
|
|
@@ -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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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",
|
actions/utils/github_utils.py
CHANGED
|
@@ -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')}")
|
actions/utils/openai_utils.py
CHANGED
|
@@ -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
|
|
@@ -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>
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
actions/__init__.py,sha256=
|
|
1
|
+
actions/__init__.py,sha256=j5AWc7zYYZL-B2DuboeBC58Yx3gjYg4eHLe1Np7bQxQ,772
|
|
2
2
|
actions/dispatch_actions.py,sha256=i81UeHrYudAsOUFUfN71u6X-1cmZaZaiiTj6p2rvz8A,4217
|
|
3
3
|
actions/first_interaction.py,sha256=QxPsLjd-m2G-QYOcQb2hQfIB_alupzeZzSHTk-jw0bg,9856
|
|
4
|
-
actions/review_pr.py,sha256=
|
|
4
|
+
actions/review_pr.py,sha256=tZztKjHmoGU3XBXy4dsxCWTHQGQIUpjmOGE8sNtxYfg,17329
|
|
5
5
|
actions/summarize_pr.py,sha256=3nFotiZX42dz-mzDQ9wcoUILJKkcaxrC5EeyxvuvY60,5775
|
|
6
6
|
actions/summarize_release.py,sha256=iCXa9a1DcOrDVe8pMWEsYKgDxuIOhIgMsYymElOLK6o,9083
|
|
7
7
|
actions/update_file_headers.py,sha256=E5fKYLdeW16-BHCcuqxohGpGZqgEh-WX4ZmCQJw2R90,6684
|
|
8
8
|
actions/update_markdown_code_blocks.py,sha256=w3DTRltg2Rmr4-qrNawv_S2vJbheKE0tne1iz79FzXg,8692
|
|
9
9
|
actions/utils/__init__.py,sha256=unjXYIFNFeHrdC8LooDFVWlj6fAdGhssUgASo5229zY,1073
|
|
10
10
|
actions/utils/common_utils.py,sha256=2DRvcyCgmn507w3T4FJcQSZNI9KC1gVUb8CnJqPapD0,11943
|
|
11
|
-
actions/utils/github_utils.py,sha256=
|
|
12
|
-
actions/utils/openai_utils.py,sha256=
|
|
11
|
+
actions/utils/github_utils.py,sha256=OKbUOjqOdu7rTLWZdFsB2uMggEtcwrjW98ecBT8lFMg,19714
|
|
12
|
+
actions/utils/openai_utils.py,sha256=WPRiLJYOMEsmmWcQ-IirnQp1N37EQhO9OvgQaK9JIV0,10706
|
|
13
13
|
actions/utils/version_utils.py,sha256=EIbm3iZVNyNl3dh8aNz_9ITeTC93ZxfyUzIRkO3tSXw,3242
|
|
14
|
-
ultralytics_actions-0.1.
|
|
15
|
-
ultralytics_actions-0.1.
|
|
16
|
-
ultralytics_actions-0.1.
|
|
17
|
-
ultralytics_actions-0.1.
|
|
18
|
-
ultralytics_actions-0.1.
|
|
19
|
-
ultralytics_actions-0.1.
|
|
14
|
+
ultralytics_actions-0.1.9.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
|
|
15
|
+
ultralytics_actions-0.1.9.dist-info/METADATA,sha256=E0FUdK1lP_igrfWdDrUYyx30-vS6BtOgrj0kyEsmemk,12368
|
|
16
|
+
ultralytics_actions-0.1.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
17
|
+
ultralytics_actions-0.1.9.dist-info/entry_points.txt,sha256=n_VbDs3Xj33daaeN_2D72UTEuyeH8hVc6-CPH55ymkY,496
|
|
18
|
+
ultralytics_actions-0.1.9.dist-info/top_level.txt,sha256=5apM5x80QlJcGbACn1v3fkmIuL1-XQCKcItJre7w7Tw,8
|
|
19
|
+
ultralytics_actions-0.1.9.dist-info/RECORD,,
|
|
File without changes
|
{ultralytics_actions-0.1.8.dist-info → ultralytics_actions-0.1.9.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{ultralytics_actions-0.1.8.dist-info → ultralytics_actions-0.1.9.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|