ultralytics-actions 0.2.0__py3-none-any.whl → 0.2.1__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 +4 -1
- actions/first_interaction.py +2 -2
- actions/review_pr.py +17 -1
- actions/scan_prs.py +205 -0
- actions/utils/openai_utils.py +9 -6
- {ultralytics_actions-0.2.0.dist-info → ultralytics_actions-0.2.1.dist-info}/METADATA +114 -66
- {ultralytics_actions-0.2.0.dist-info → ultralytics_actions-0.2.1.dist-info}/RECORD +11 -10
- {ultralytics_actions-0.2.0.dist-info → ultralytics_actions-0.2.1.dist-info}/WHEEL +0 -0
- {ultralytics_actions-0.2.0.dist-info → ultralytics_actions-0.2.1.dist-info}/entry_points.txt +0 -0
- {ultralytics_actions-0.2.0.dist-info → ultralytics_actions-0.2.1.dist-info}/licenses/LICENSE +0 -0
- {ultralytics_actions-0.2.0.dist-info → ultralytics_actions-0.2.1.dist-info}/top_level.txt +0 -0
actions/__init__.py
CHANGED
|
@@ -12,10 +12,13 @@
|
|
|
12
12
|
# │ │ ├── github_utils.py
|
|
13
13
|
# │ │ ├── openai_utils.py
|
|
14
14
|
# │ │ └── common_utils.py
|
|
15
|
+
# │ ├── dispatch_actions.py
|
|
15
16
|
# │ ├── first_interaction.py
|
|
16
17
|
# │ ├── review_pr.py
|
|
18
|
+
# │ ├── scan_prs.py
|
|
17
19
|
# │ ├── summarize_pr.py
|
|
18
20
|
# │ ├── summarize_release.py
|
|
21
|
+
# │ ├── update_file_headers.py
|
|
19
22
|
# │ └── update_markdown_code_blocks.py
|
|
20
23
|
# └── tests/
|
|
21
24
|
# ├── __init__.py
|
|
@@ -23,4 +26,4 @@
|
|
|
23
26
|
# ├── test_summarize_pr.py
|
|
24
27
|
# └── ...
|
|
25
28
|
|
|
26
|
-
__version__ = "0.2.
|
|
29
|
+
__version__ = "0.2.1"
|
actions/first_interaction.py
CHANGED
|
@@ -188,9 +188,9 @@ def main(*args, **kwargs):
|
|
|
188
188
|
if event.should_skip_pr_author():
|
|
189
189
|
return
|
|
190
190
|
|
|
191
|
-
print("Processing PR open with unified API call...")
|
|
191
|
+
print(f"Processing PR open by @{username} with unified API call...")
|
|
192
192
|
diff = event.get_pr_diff()
|
|
193
|
-
response = get_pr_open_response(event.repository, diff, title,
|
|
193
|
+
response = get_pr_open_response(event.repository, diff, title, username, label_descriptions)
|
|
194
194
|
|
|
195
195
|
if summary := response.get("summary"):
|
|
196
196
|
print("Updating PR description with summary...")
|
actions/review_pr.py
CHANGED
|
@@ -152,7 +152,23 @@ def generate_pr_review(repository: str, diff_text: str, pr_title: str, pr_descri
|
|
|
152
152
|
# print(f"\nUser prompt (first 3000 chars):\n{messages[1]['content'][:3000]}...\n")
|
|
153
153
|
|
|
154
154
|
try:
|
|
155
|
-
response = get_completion(
|
|
155
|
+
response = get_completion(
|
|
156
|
+
messages,
|
|
157
|
+
reasoning_effort="low",
|
|
158
|
+
model="gpt-5-codex",
|
|
159
|
+
tools=[
|
|
160
|
+
{
|
|
161
|
+
"type": "web_search",
|
|
162
|
+
"filters": {
|
|
163
|
+
"allowed_domains": [
|
|
164
|
+
"ultralytics.com",
|
|
165
|
+
"github.com",
|
|
166
|
+
"stackoverflow.com",
|
|
167
|
+
]
|
|
168
|
+
},
|
|
169
|
+
}
|
|
170
|
+
],
|
|
171
|
+
)
|
|
156
172
|
|
|
157
173
|
json_str = re.search(r"```(?:json)?\s*(\{.*?\})\s*```", response, re.DOTALL)
|
|
158
174
|
review_data = json.loads(json_str.group(1) if json_str else response)
|
actions/scan_prs.py
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
|
|
2
|
+
"""List and auto-merge open PRs across GitHub organization."""
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import subprocess
|
|
7
|
+
from datetime import datetime, timezone
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_age_days(created_at):
|
|
11
|
+
"""Calculate PR age in days from ISO timestamp."""
|
|
12
|
+
return (datetime.now(timezone.utc) - datetime.fromisoformat(created_at.replace("Z", "+00:00"))).days
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_phase_emoji(age_days):
|
|
16
|
+
"""Return emoji and label for PR age phase."""
|
|
17
|
+
if age_days == 0:
|
|
18
|
+
return "🆕", "NEW"
|
|
19
|
+
elif age_days <= 7:
|
|
20
|
+
return "🟢", f"{age_days} days"
|
|
21
|
+
elif age_days <= 30:
|
|
22
|
+
return "🟡", f"{age_days} days"
|
|
23
|
+
else:
|
|
24
|
+
return "🔴", f"{age_days} days"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def run():
|
|
28
|
+
"""List open PRs across organization and auto-merge eligible Dependabot PRs."""
|
|
29
|
+
# Get and validate settings
|
|
30
|
+
org = os.getenv("ORG", "ultralytics")
|
|
31
|
+
visibility = os.getenv("VISIBILITY", "public").lower()
|
|
32
|
+
repo_visibility = os.getenv("REPO_VISIBILITY", "public").lower()
|
|
33
|
+
valid_visibilities = {"public", "private", "internal", "all"}
|
|
34
|
+
|
|
35
|
+
if visibility not in valid_visibilities:
|
|
36
|
+
print(f"⚠️ Invalid visibility '{visibility}', defaulting to 'public'")
|
|
37
|
+
visibility = "public"
|
|
38
|
+
|
|
39
|
+
# Security: if calling repo is public, restrict to public repos only
|
|
40
|
+
if repo_visibility == "public" and visibility != "public":
|
|
41
|
+
print(f"⚠️ Security: Public repo cannot scan {visibility} repos. Restricting to public only.")
|
|
42
|
+
visibility = "public"
|
|
43
|
+
|
|
44
|
+
print(f"🔍 Scanning {visibility} repositories in {org} organization...")
|
|
45
|
+
|
|
46
|
+
# Get active repos with specified visibility
|
|
47
|
+
cmd = ["gh", "repo", "list", org, "--limit", "1000", "--json", "name,url,isArchived"]
|
|
48
|
+
if visibility != "all":
|
|
49
|
+
cmd.extend(["--visibility", visibility])
|
|
50
|
+
|
|
51
|
+
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
|
52
|
+
repos = {r["name"]: r["url"] for r in json.loads(result.stdout) if not r["isArchived"]}
|
|
53
|
+
|
|
54
|
+
if not repos:
|
|
55
|
+
print("⚠️ No repositories found")
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
# Get all open PRs
|
|
59
|
+
result = subprocess.run(
|
|
60
|
+
[
|
|
61
|
+
"gh",
|
|
62
|
+
"search",
|
|
63
|
+
"prs",
|
|
64
|
+
"--owner",
|
|
65
|
+
org,
|
|
66
|
+
"--state",
|
|
67
|
+
"open",
|
|
68
|
+
"--limit",
|
|
69
|
+
"1000",
|
|
70
|
+
"--json",
|
|
71
|
+
"repository,number,title,url,createdAt",
|
|
72
|
+
"--sort",
|
|
73
|
+
"created",
|
|
74
|
+
"--order",
|
|
75
|
+
"desc",
|
|
76
|
+
],
|
|
77
|
+
capture_output=True,
|
|
78
|
+
text=True,
|
|
79
|
+
check=True,
|
|
80
|
+
)
|
|
81
|
+
all_prs = json.loads(result.stdout)
|
|
82
|
+
|
|
83
|
+
if not all_prs:
|
|
84
|
+
print("✅ No open PRs found")
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
# Count PRs by phase
|
|
88
|
+
phase_counts = {"new": 0, "green": 0, "yellow": 0, "red": 0}
|
|
89
|
+
for pr in all_prs:
|
|
90
|
+
age_days = get_age_days(pr["createdAt"])
|
|
91
|
+
phase_counts[
|
|
92
|
+
"new" if age_days == 0 else "green" if age_days <= 7 else "yellow" if age_days <= 30 else "red"
|
|
93
|
+
] += 1
|
|
94
|
+
|
|
95
|
+
repo_count = len({pr["repository"]["name"] for pr in all_prs if pr["repository"]["name"] in repos})
|
|
96
|
+
summary = [
|
|
97
|
+
f"# 🔍 Open Pull Requests - {org.title()} Organization\n",
|
|
98
|
+
f"**Total:** {len(all_prs)} open PRs across {repo_count} repos",
|
|
99
|
+
f"**By Phase:** 🆕 {phase_counts['new']} New | 🟢 {phase_counts['green']} Green (≤7d) | 🟡 {phase_counts['yellow']} Yellow (≤30d) | 🔴 {phase_counts['red']} Red (>30d)\n",
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
for repo_name in sorted({pr["repository"]["name"] for pr in all_prs}):
|
|
103
|
+
if repo_name not in repos:
|
|
104
|
+
continue
|
|
105
|
+
|
|
106
|
+
repo_prs = [pr for pr in all_prs if pr["repository"]["name"] == repo_name]
|
|
107
|
+
summary.append(
|
|
108
|
+
f"## 📦 [{repo_name}]({repos[repo_name]}) - {len(repo_prs)} open PR{'s' if len(repo_prs) > 1 else ''}"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
for pr in repo_prs[:30]:
|
|
112
|
+
emoji, age_str = get_phase_emoji(get_age_days(pr["createdAt"]))
|
|
113
|
+
summary.append(f"- 🔀 [#{pr['number']}]({pr['url']}) {pr['title']} {emoji} {age_str}")
|
|
114
|
+
|
|
115
|
+
if len(repo_prs) > 30:
|
|
116
|
+
summary.append(f"- ... {len(repo_prs) - 30} more PRs")
|
|
117
|
+
summary.append("")
|
|
118
|
+
|
|
119
|
+
# Auto-merge Dependabot GitHub Actions PRs
|
|
120
|
+
print("\n🤖 Checking for Dependabot PRs to auto-merge...")
|
|
121
|
+
summary.append("\n# 🤖 Auto-Merge Dependabot GitHub Actions PRs\n")
|
|
122
|
+
total_found = total_merged = total_skipped = 0
|
|
123
|
+
|
|
124
|
+
for repo_name in repos:
|
|
125
|
+
result = subprocess.run(
|
|
126
|
+
[
|
|
127
|
+
"gh",
|
|
128
|
+
"pr",
|
|
129
|
+
"list",
|
|
130
|
+
"--repo",
|
|
131
|
+
f"{org}/{repo_name}",
|
|
132
|
+
"--author",
|
|
133
|
+
"app/dependabot",
|
|
134
|
+
"--state",
|
|
135
|
+
"open",
|
|
136
|
+
"--json",
|
|
137
|
+
"number,title,files,mergeable,statusCheckRollup",
|
|
138
|
+
],
|
|
139
|
+
capture_output=True,
|
|
140
|
+
text=True,
|
|
141
|
+
)
|
|
142
|
+
if result.returncode != 0:
|
|
143
|
+
continue
|
|
144
|
+
|
|
145
|
+
merged = 0
|
|
146
|
+
for pr in json.loads(result.stdout):
|
|
147
|
+
if not all(f["path"].startswith(".github/workflows/") for f in pr["files"]):
|
|
148
|
+
continue
|
|
149
|
+
|
|
150
|
+
total_found += 1
|
|
151
|
+
pr_ref = f"{org}/{repo_name}#{pr['number']}"
|
|
152
|
+
print(f" Found: {pr_ref} - {pr['title']}")
|
|
153
|
+
|
|
154
|
+
if merged >= 1:
|
|
155
|
+
print(f" ⏭️ Skipped (already merged 1 PR in {repo_name})")
|
|
156
|
+
total_skipped += 1
|
|
157
|
+
continue
|
|
158
|
+
|
|
159
|
+
if pr["mergeable"] != "MERGEABLE":
|
|
160
|
+
print(f" ❌ Skipped (not mergeable: {pr['mergeable']})")
|
|
161
|
+
total_skipped += 1
|
|
162
|
+
continue
|
|
163
|
+
|
|
164
|
+
# Check if all status checks passed (normalize rollup structure)
|
|
165
|
+
rollup = pr.get("statusCheckRollup")
|
|
166
|
+
if isinstance(rollup, list):
|
|
167
|
+
checks = rollup
|
|
168
|
+
elif isinstance(rollup, dict):
|
|
169
|
+
checks = rollup.get("contexts", [])
|
|
170
|
+
else:
|
|
171
|
+
checks = []
|
|
172
|
+
failed_checks = [c for c in checks if c.get("conclusion") not in ["SUCCESS", "SKIPPED", "NEUTRAL"]]
|
|
173
|
+
|
|
174
|
+
if failed_checks:
|
|
175
|
+
for check in failed_checks:
|
|
176
|
+
print(f" ❌ Failing check: {check.get('name', 'unknown')} = {check.get('conclusion')}")
|
|
177
|
+
total_skipped += 1
|
|
178
|
+
continue
|
|
179
|
+
|
|
180
|
+
print(" ✅ All checks passed, merging...")
|
|
181
|
+
result = subprocess.run(
|
|
182
|
+
["gh", "pr", "merge", str(pr["number"]), "--repo", f"{org}/{repo_name}", "--squash", "--admin"],
|
|
183
|
+
capture_output=True,
|
|
184
|
+
text=True,
|
|
185
|
+
)
|
|
186
|
+
if result.returncode == 0:
|
|
187
|
+
print(f" ✅ Successfully merged {pr_ref}")
|
|
188
|
+
summary.append(f"- ✅ Merged {pr_ref}")
|
|
189
|
+
total_merged += 1
|
|
190
|
+
merged += 1
|
|
191
|
+
else:
|
|
192
|
+
print(f" ❌ Merge failed: {result.stderr.strip()}")
|
|
193
|
+
total_skipped += 1
|
|
194
|
+
|
|
195
|
+
summary.append(f"\n**Summary:** Found {total_found} | Merged {total_merged} | Skipped {total_skipped}")
|
|
196
|
+
print(f"\n📊 Dependabot Summary: Found {total_found} | Merged {total_merged} | Skipped {total_skipped}")
|
|
197
|
+
|
|
198
|
+
# Write to GitHub step summary if available
|
|
199
|
+
if summary_file := os.getenv("GITHUB_STEP_SUMMARY"):
|
|
200
|
+
with open(summary_file, "a") as f:
|
|
201
|
+
f.write("\n".join(summary))
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
if __name__ == "__main__":
|
|
205
|
+
run()
|
actions/utils/openai_utils.py
CHANGED
|
@@ -94,9 +94,9 @@ def get_pr_summary_prompt(repository: str, diff_text: str) -> tuple[str, bool]:
|
|
|
94
94
|
return prompt, len(diff_text) > MAX_PROMPT_CHARS
|
|
95
95
|
|
|
96
96
|
|
|
97
|
-
def get_pr_first_comment_template(repository: str) -> str:
|
|
97
|
+
def get_pr_first_comment_template(repository: str, username: str) -> str:
|
|
98
98
|
"""Returns the PR first comment template with checklist (used only by unified PR open)."""
|
|
99
|
-
return f"""👋 Hello @username, thank you for submitting
|
|
99
|
+
return f"""👋 Hello @{username}, thank you for submitting a `{repository}` 🚀 PR! To ensure a seamless integration of your work, please review the following checklist:
|
|
100
100
|
|
|
101
101
|
- ✅ **Define a Purpose**: Clearly explain the purpose of your fix or feature in your PR description, and link to any [relevant issues](https://github.com/{repository}/issues). Ensure your commit messages are clear, concise, and adhere to the project's conventions.
|
|
102
102
|
- ✅ **Synchronize with Source**: Confirm your PR is synchronized with the `{repository}` `main` branch. If it's behind, update it by clicking the 'Update branch' button or by running `git pull` and `git merge main` locally.
|
|
@@ -117,6 +117,7 @@ def get_completion(
|
|
|
117
117
|
reasoning_effort: str | None = None,
|
|
118
118
|
response_format: dict | None = None,
|
|
119
119
|
model: str = OPENAI_MODEL,
|
|
120
|
+
tools: list[dict] | None = None,
|
|
120
121
|
) -> str | dict:
|
|
121
122
|
"""Generates a completion using OpenAI's Responses API with retry logic."""
|
|
122
123
|
assert OPENAI_API_KEY, "OpenAI API key is required."
|
|
@@ -129,6 +130,8 @@ def get_completion(
|
|
|
129
130
|
data = {"model": model, "input": messages, "store": False, "temperature": temperature}
|
|
130
131
|
if "gpt-5" in model:
|
|
131
132
|
data["reasoning"] = {"effort": reasoning_effort or "low"}
|
|
133
|
+
if tools:
|
|
134
|
+
data["tools"] = tools
|
|
132
135
|
|
|
133
136
|
try:
|
|
134
137
|
r = requests.post(url, json=data, headers=headers, timeout=(30, 900))
|
|
@@ -196,16 +199,16 @@ def get_completion(
|
|
|
196
199
|
return content
|
|
197
200
|
|
|
198
201
|
|
|
199
|
-
def get_pr_open_response(repository: str, diff_text: str, title: str,
|
|
202
|
+
def get_pr_open_response(repository: str, diff_text: str, title: str, username: str, available_labels: dict) -> dict:
|
|
200
203
|
"""Generates unified PR response with summary, labels, and first comment in a single API call."""
|
|
201
204
|
is_large = len(diff_text) > MAX_PROMPT_CHARS
|
|
202
205
|
|
|
203
206
|
filtered_labels = filter_labels(available_labels, is_pr=True)
|
|
204
207
|
labels_str = "\n".join(f"- {name}: {description}" for name, description in filtered_labels.items())
|
|
205
208
|
|
|
206
|
-
prompt = f"""You are processing a new GitHub
|
|
209
|
+
prompt = f"""You are processing a new GitHub PR by @{username} for the {repository} repository.
|
|
207
210
|
|
|
208
|
-
Generate 3 outputs in a single JSON response for the PR titled {title} with the following diff:
|
|
211
|
+
Generate 3 outputs in a single JSON response for the PR titled '{title}' with the following diff:
|
|
209
212
|
{diff_text[:MAX_PROMPT_CHARS]}
|
|
210
213
|
|
|
211
214
|
|
|
@@ -227,7 +230,7 @@ Customized welcome message adapting the template below:
|
|
|
227
230
|
- No spaces between bullet points
|
|
228
231
|
|
|
229
232
|
Example comment template (adapt as needed, keep all links):
|
|
230
|
-
{get_pr_first_comment_template(repository)}
|
|
233
|
+
{get_pr_first_comment_template(repository, username)}
|
|
231
234
|
|
|
232
235
|
Return ONLY valid JSON in this exact format:
|
|
233
236
|
{{"summary": "...", "labels": [...], "first_comment": "..."}}"""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ultralytics-actions
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
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>
|
|
@@ -38,111 +38,159 @@ Dynamic: license-file
|
|
|
38
38
|
|
|
39
39
|
<a href="https://www.ultralytics.com/"><img src="https://raw.githubusercontent.com/ultralytics/assets/main/logo/Ultralytics_Logotype_Original.svg" width="320" alt="Ultralytics logo"></a>
|
|
40
40
|
|
|
41
|
-
# 🚀 Ultralytics Actions
|
|
41
|
+
# 🚀 Ultralytics Actions
|
|
42
42
|
|
|
43
|
-
Welcome to
|
|
43
|
+
Welcome to [Ultralytics Actions](https://github.com/ultralytics/actions) - a collection of GitHub Actions and Python tools for automating code quality, PR management, and CI/CD workflows across Ultralytics projects.
|
|
44
44
|
|
|
45
45
|
[](https://github.com/marketplace/actions/ultralytics-actions)
|
|
46
46
|
|
|
47
47
|
[](https://github.com/ultralytics/actions/actions/workflows/ci.yml)
|
|
48
48
|
[](https://github.com/ultralytics/actions/actions/workflows/format.yml)
|
|
49
|
-
[](https://github.com/ultralytics/actions/actions/workflows/scan-prs.yml)
|
|
50
50
|
[](https://codecov.io/github/ultralytics/actions)
|
|
51
51
|
|
|
52
52
|
[](https://discord.com/invite/ultralytics)
|
|
53
53
|
[](https://community.ultralytics.com/)
|
|
54
54
|
[](https://reddit.com/r/ultralytics)
|
|
55
55
|
|
|
56
|
-
##
|
|
56
|
+
## 📦 Repository Contents
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
This repository provides three main components:
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
- **Swift Code:** Formatted with [`swift-format`](https://github.com/swiftlang/swift-format) to maintain a uniform coding style across Swift projects. _(Note: Requires the `macos-latest` runner.)_
|
|
64
|
-
- **Spell Check:** Common misspellings are caught using [codespell](https://github.com/codespell-project/codespell).
|
|
65
|
-
- **Broken Links Check:** Broken links in documentation and Markdown files are identified using [Lychee](https://github.com/lycheeverse/lychee).
|
|
66
|
-
- **PR Summary:** Concise Pull Request summaries are generated using [OpenAI](https://openai.com/) GPT-5, improving clarity and review efficiency.
|
|
67
|
-
- **PR Review:** AI-powered code reviews identify critical bugs, security issues, and code quality concerns with suggested fixes.
|
|
68
|
-
- **Auto-labeling:** Applies relevant labels to issues and PRs via [OpenAI](https://openai.com/) GPT-5 for intelligent categorization.
|
|
60
|
+
1. **[Ultralytics Actions](#ultralytics-actions-main-action)** - Main GitHub Action for AI-powered code formatting, PR summaries, and auto-labeling
|
|
61
|
+
2. **[Standalone Actions](#standalone-actions)** - Reusable composite actions for common CI/CD tasks
|
|
62
|
+
3. **[Python Package](#python-package)** - `ultralytics-actions` package for programmatic use
|
|
69
63
|
|
|
70
|
-
##
|
|
64
|
+
## Ultralytics Actions (Main Action)
|
|
71
65
|
|
|
72
|
-
|
|
66
|
+
AI-powered formatting, labeling, and PR summaries for Python, Swift, and Markdown files.
|
|
73
67
|
|
|
74
|
-
|
|
75
|
-
- **Pull Requests:**
|
|
76
|
-
- Ensures contributions meet formatting standards before merging.
|
|
77
|
-
- Generates a concise summary of changes using GPT-5.
|
|
78
|
-
- Provides AI-powered inline code reviews with suggested fixes for critical issues.
|
|
79
|
-
- Applies relevant labels using GPT-5 for intelligent categorization.
|
|
80
|
-
- **Issues:** Automatically applies relevant labels using GPT-5 when new issues are created.
|
|
68
|
+
### 📄 Features
|
|
81
69
|
|
|
82
|
-
|
|
70
|
+
- **Python Code:** Formatted using [Ruff](https://github.com/astral-sh/ruff), an extremely fast Python linter and formatter
|
|
71
|
+
- **Markdown Files:** Styled with [Prettier](https://github.com/prettier/prettier) to ensure consistent documentation appearance
|
|
72
|
+
- **Docstrings:** Cleaned and standardized using [docformatter](https://github.com/PyCQA/docformatter)
|
|
73
|
+
- **Swift Code:** Formatted with [`swift-format`](https://github.com/swiftlang/swift-format) _(requires `macos-latest` runner)_
|
|
74
|
+
- **Spell Check:** Common misspellings caught using [codespell](https://github.com/codespell-project/codespell)
|
|
75
|
+
- **Broken Links Check:** Broken links identified using [Lychee](https://github.com/lycheeverse/lychee)
|
|
76
|
+
- **PR Summary:** Concise Pull Request summaries generated using [OpenAI](https://openai.com/) GPT-5
|
|
77
|
+
- **PR Review:** AI-powered code reviews identify critical bugs, security issues, and quality concerns with suggested fixes
|
|
78
|
+
- **Auto-labeling:** Applies relevant labels to issues and PRs via [OpenAI](https://openai.com/) GPT-5
|
|
83
79
|
|
|
84
|
-
|
|
80
|
+
### 🛠️ How It Works
|
|
85
81
|
|
|
86
|
-
|
|
82
|
+
Triggers on GitHub events to streamline workflows:
|
|
87
83
|
|
|
88
|
-
|
|
84
|
+
- **Push Events:** Automatically formats code when changes are pushed to `main`
|
|
85
|
+
- **Pull Requests:** Ensures formatting standards, generates summaries, provides AI reviews, and applies labels
|
|
86
|
+
- **Issues:** Automatically applies relevant labels using GPT-5
|
|
89
87
|
|
|
90
|
-
|
|
88
|
+
### 🔧 Setup
|
|
91
89
|
|
|
92
|
-
|
|
93
|
-
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
|
|
90
|
+
Create `.github/workflows/ultralytics-actions.yml`:
|
|
94
91
|
|
|
95
|
-
|
|
96
|
-
|
|
92
|
+
```yaml
|
|
93
|
+
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
|
|
97
94
|
|
|
98
|
-
|
|
95
|
+
# Ultralytics Actions https://github.com/ultralytics/actions
|
|
96
|
+
# This workflow formats code and documentation in PRs to Ultralytics standards
|
|
99
97
|
|
|
100
|
-
|
|
101
|
-
issues:
|
|
102
|
-
types: [opened]
|
|
103
|
-
pull_request:
|
|
104
|
-
branches: [main]
|
|
105
|
-
types: [opened, closed, synchronize, review_requested]
|
|
98
|
+
name: Ultralytics Actions
|
|
106
99
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
100
|
+
on:
|
|
101
|
+
issues:
|
|
102
|
+
types: [opened]
|
|
103
|
+
pull_request:
|
|
104
|
+
branches: [main]
|
|
105
|
+
types: [opened, closed, synchronize, review_requested]
|
|
111
106
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
- name: Run Ultralytics Actions
|
|
117
|
-
uses: ultralytics/actions@main
|
|
118
|
-
with:
|
|
119
|
-
token: ${{ secrets.GITHUB_TOKEN }} # Auto-generated token
|
|
120
|
-
labels: true # Auto-label issues/PRs using AI
|
|
121
|
-
python: true # Format Python with Ruff and docformatter
|
|
122
|
-
prettier: true # Format YAML, JSON, Markdown, CSS
|
|
123
|
-
swift: false # Format Swift (requires macos-latest)
|
|
124
|
-
dart: false # Format Dart/Flutter
|
|
125
|
-
spelling: true # Check spelling with codespell
|
|
126
|
-
links: true # Check broken links with Lychee
|
|
127
|
-
summary: true # Generate AI-powered PR summaries
|
|
128
|
-
openai_api_key: ${{ secrets.OPENAI_API_KEY }} # Powers PR summaries, labels and comments
|
|
129
|
-
brave_api_key: ${{ secrets.BRAVE_API_KEY }} # Used for broken link resolution
|
|
130
|
-
```
|
|
107
|
+
permissions:
|
|
108
|
+
contents: write # Modify code in PRs
|
|
109
|
+
pull-requests: write # Add comments and labels to PRs
|
|
110
|
+
issues: write # Add comments and labels to issues
|
|
131
111
|
|
|
132
|
-
|
|
112
|
+
jobs:
|
|
113
|
+
actions:
|
|
114
|
+
runs-on: ubuntu-latest
|
|
115
|
+
steps:
|
|
116
|
+
- name: Run Ultralytics Actions
|
|
117
|
+
uses: ultralytics/actions@main
|
|
118
|
+
with:
|
|
119
|
+
token: ${{ secrets.GITHUB_TOKEN }} # Auto-generated token
|
|
120
|
+
labels: true # Auto-label issues/PRs using AI
|
|
121
|
+
python: true # Format Python with Ruff and docformatter
|
|
122
|
+
prettier: true # Format YAML, JSON, Markdown, CSS
|
|
123
|
+
swift: false # Format Swift (requires macos-latest)
|
|
124
|
+
dart: false # Format Dart/Flutter
|
|
125
|
+
spelling: true # Check spelling with codespell
|
|
126
|
+
links: true # Check broken links with Lychee
|
|
127
|
+
summary: true # Generate AI-powered PR summaries
|
|
128
|
+
openai_api_key: ${{ secrets.OPENAI_API_KEY }} # Powers PR summaries, labels and reviews
|
|
129
|
+
brave_api_key: ${{ secrets.BRAVE_API_KEY }} # Used for broken link resolution
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Standalone Actions
|
|
133
|
+
|
|
134
|
+
Reusable composite actions for common CI/CD tasks. Each can be used independently in your workflows.
|
|
135
|
+
|
|
136
|
+
### 1. Retry Action
|
|
137
|
+
|
|
138
|
+
Retry failed commands with exponential backoff.
|
|
139
|
+
|
|
140
|
+
```yaml
|
|
141
|
+
- uses: ultralytics/actions/retry@main
|
|
142
|
+
with:
|
|
143
|
+
command: npm install
|
|
144
|
+
max_attempts: 3
|
|
145
|
+
timeout_minutes: 5
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
[**📖 Full Documentation →**](retry/README.md)
|
|
149
|
+
|
|
150
|
+
### 2. Cleanup Disk Action
|
|
151
|
+
|
|
152
|
+
Free up disk space on GitHub runners by removing unnecessary packages and files.
|
|
153
|
+
|
|
154
|
+
```yaml
|
|
155
|
+
- uses: ultralytics/actions/cleanup-disk@main
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
[**📖 Full Documentation →**](cleanup-disk/README.md)
|
|
159
|
+
|
|
160
|
+
### 3. Scan PRs Action
|
|
161
|
+
|
|
162
|
+
List open PRs across an organization and auto-merge eligible Dependabot PRs.
|
|
163
|
+
|
|
164
|
+
```yaml
|
|
165
|
+
- uses: ultralytics/actions/scan-prs@main
|
|
166
|
+
with:
|
|
167
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
|
168
|
+
org: ultralytics # Optional: defaults to ultralytics
|
|
169
|
+
visibility: public # Optional: public, private, internal, or all
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
[**📖 Full Documentation →**](scan-prs/README.md)
|
|
133
173
|
|
|
134
174
|
## Python Package
|
|
135
175
|
|
|
136
|
-
Install
|
|
176
|
+
Install `ultralytics-actions` for programmatic access to action utilities.
|
|
137
177
|
|
|
138
178
|
[](https://pypi.org/project/ultralytics-actions/)
|
|
139
179
|
[](https://clickpy.clickhouse.com/dashboard/ultralytics-actions)
|
|
140
180
|
[](https://pypi.org/project/ultralytics-actions/)
|
|
141
181
|
|
|
142
|
-
```
|
|
182
|
+
```bash
|
|
143
183
|
pip install ultralytics-actions
|
|
144
184
|
```
|
|
145
185
|
|
|
186
|
+
**Available Modules:**
|
|
187
|
+
|
|
188
|
+
- `actions.review_pr` - AI-powered PR review
|
|
189
|
+
- `actions.summarize_pr` - Generate PR summaries
|
|
190
|
+
- `actions.scan_prs` - Scan and manage organization PRs
|
|
191
|
+
- `actions.first_interaction` - Welcome message for new contributors
|
|
192
|
+
- And more in `actions/` directory
|
|
193
|
+
|
|
146
194
|
## 💡 Contribute
|
|
147
195
|
|
|
148
196
|
Ultralytics thrives on community collaboration, and we deeply value your contributions! Please see our [Contributing Guide](https://docs.ultralytics.com/help/contributing/) for details on how you can get involved. We also encourage you to share your feedback through our [Survey](https://www.ultralytics.com/survey?utm_source=github&utm_medium=social&utm_campaign=Survey). A huge thank you 🙏 to all our contributors!
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
actions/__init__.py,sha256=
|
|
1
|
+
actions/__init__.py,sha256=G-r-dtUaDXDKiTJuqW8GafMpIpOjbCVlG1ETyC4_yqY,881
|
|
2
2
|
actions/dispatch_actions.py,sha256=ljlFR1o8m1qTHbStsJJVMVDdJv7iVqMfdPzKlZyKXl8,6743
|
|
3
|
-
actions/first_interaction.py,sha256=
|
|
4
|
-
actions/review_pr.py,sha256=
|
|
3
|
+
actions/first_interaction.py,sha256=wcKzLEUJmYnHmtwn-sz3N5erwftMT9jn7XxSKATAmXY,9815
|
|
4
|
+
actions/review_pr.py,sha256=QqYmWE37sA4mJ6bPcY5M2dlNc1lRJPwT7XcJJFP1C7c,17466
|
|
5
|
+
actions/scan_prs.py,sha256=9Gu4EHmLjdShIlkoCQfIrcxLpMZeOOnpKEyv_mVc3rU,7407
|
|
5
6
|
actions/summarize_pr.py,sha256=0y4Cl4_ZMMtDWVhxwWasn3mHo_4GCnegJrf29yujUYM,5715
|
|
6
7
|
actions/summarize_release.py,sha256=8D5EOQ36mho1HKtWD2J-IDH_xJJb3q0shgXZSdemmDM,9078
|
|
7
8
|
actions/update_file_headers.py,sha256=E5fKYLdeW16-BHCcuqxohGpGZqgEh-WX4ZmCQJw2R90,6684
|
|
@@ -9,11 +10,11 @@ actions/update_markdown_code_blocks.py,sha256=w3DTRltg2Rmr4-qrNawv_S2vJbheKE0tne
|
|
|
9
10
|
actions/utils/__init__.py,sha256=Uf7S5qYHS59zoAP9uKVIZwhpUbgyI947dD9jAWu50Lg,1115
|
|
10
11
|
actions/utils/common_utils.py,sha256=InBc-bsXcwzQYjuDxtrrm3bj7J-70U54G0s2nQKgCg8,12052
|
|
11
12
|
actions/utils/github_utils.py,sha256=5yzNIiu7-WBmH1-gSi4O31m1Fwd4k8pfbwM2BPVGf88,19989
|
|
12
|
-
actions/utils/openai_utils.py,sha256=
|
|
13
|
+
actions/utils/openai_utils.py,sha256=07g5NsfAfSuJ6CqWWQxsZ0MR4_kh6-Rjmud_iGPm49U,11965
|
|
13
14
|
actions/utils/version_utils.py,sha256=EIbm3iZVNyNl3dh8aNz_9ITeTC93ZxfyUzIRkO3tSXw,3242
|
|
14
|
-
ultralytics_actions-0.2.
|
|
15
|
-
ultralytics_actions-0.2.
|
|
16
|
-
ultralytics_actions-0.2.
|
|
17
|
-
ultralytics_actions-0.2.
|
|
18
|
-
ultralytics_actions-0.2.
|
|
19
|
-
ultralytics_actions-0.2.
|
|
15
|
+
ultralytics_actions-0.2.1.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
|
|
16
|
+
ultralytics_actions-0.2.1.dist-info/METADATA,sha256=1kN57DVDjQZMQGlhfF_3ugsfYGaXCsxFM-5guwrgFT4,12478
|
|
17
|
+
ultralytics_actions-0.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
18
|
+
ultralytics_actions-0.2.1.dist-info/entry_points.txt,sha256=n_VbDs3Xj33daaeN_2D72UTEuyeH8hVc6-CPH55ymkY,496
|
|
19
|
+
ultralytics_actions-0.2.1.dist-info/top_level.txt,sha256=5apM5x80QlJcGbACn1v3fkmIuL1-XQCKcItJre7w7Tw,8
|
|
20
|
+
ultralytics_actions-0.2.1.dist-info/RECORD,,
|
|
File without changes
|
{ultralytics_actions-0.2.0.dist-info → ultralytics_actions-0.2.1.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{ultralytics_actions-0.2.0.dist-info → ultralytics_actions-0.2.1.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|