ultralytics-actions 0.0.5__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.
actions/__init__.py ADDED
@@ -0,0 +1,31 @@
1
+ # Ultralytics Actions 🚀, AGPL-3.0 license https://ultralytics.com/license
2
+
3
+ # project_root/
4
+ # ├── pyproject.toml
5
+ # ├── README.md
6
+ # ├── LICENSE
7
+ # ├── .gitignore
8
+ # ├── actions/
9
+ # │ ├── __init__.py
10
+ # │ ├── utils/
11
+ # │ │ ├── __init__.py
12
+ # │ │ ├── github_utils.py
13
+ # │ │ ├── openai_utils.py
14
+ # │ │ └── common_utils.py
15
+ # │ ├── first_interaction.py
16
+ # │ ├── summarize_pr.py
17
+ # │ ├── summarize_release.py
18
+ # │ └── update_markdown_code_blocks.py
19
+ # └── tests/
20
+ # ├── __init__.py
21
+ # ├── test_first_interaction.py
22
+ # ├── test_summarize_pr.py
23
+ # └── ...
24
+
25
+ from .first_interaction import main as first_interaction_main
26
+ from .summarize_pr import main as summarize_pr_main
27
+ from .summarize_release import main as summarize_release_main
28
+ from .update_markdown_code_blocks import main as update_markdown_code_blocks_main
29
+
30
+ __all__ = ["first_interaction_main", "summarize_pr_main", "summarize_release_main", "update_markdown_code_blocks_main"]
31
+ __version__ = "0.0.5"
@@ -0,0 +1,401 @@
1
+ # Ultralytics Actions 🚀, AGPL-3.0 license https://ultralytics.com/license
2
+
3
+ import json
4
+ import os
5
+ from typing import Dict, List, Tuple
6
+
7
+ import requests
8
+
9
+ from .utils import (
10
+ GITHUB_API_URL,
11
+ GITHUB_EVENT_NAME,
12
+ GITHUB_EVENT_PATH,
13
+ GITHUB_HEADERS,
14
+ REPO_NAME,
15
+ get_completion,
16
+ get_github_data,
17
+ get_pr_diff,
18
+ graphql_request,
19
+ remove_html_comments,
20
+ )
21
+
22
+ # Environment variables
23
+ BLOCK_USER = os.getenv("BLOCK_USER", "false").lower() == "true"
24
+
25
+
26
+ def get_event_content() -> Tuple[int, str, str, str, str, str, str]:
27
+ """Extracts the number, node_id, title, body, username, and issue_type."""
28
+ with open(GITHUB_EVENT_PATH) as f:
29
+ data = json.load(f)
30
+ action = data["action"] # 'opened', 'closed', 'created' (discussion), etc.
31
+ if GITHUB_EVENT_NAME == "issues":
32
+ item = data["issue"]
33
+ issue_type = "issue"
34
+ elif GITHUB_EVENT_NAME in ["pull_request", "pull_request_target"]:
35
+ pr_number = data["pull_request"]["number"]
36
+ item = get_github_data(f"pulls/{pr_number}")
37
+ issue_type = "pull request"
38
+ elif GITHUB_EVENT_NAME == "discussion":
39
+ item = data["discussion"]
40
+ issue_type = "discussion"
41
+ else:
42
+ raise ValueError(f"Unsupported event type: {GITHUB_EVENT_NAME}")
43
+
44
+ number = item["number"]
45
+ node_id = item.get("node_id") or item.get("id")
46
+ title = item["title"]
47
+ body = remove_html_comments(item.get("body", ""))
48
+ username = item["user"]["login"]
49
+ return number, node_id, title, body, username, issue_type, action
50
+
51
+
52
+ def update_issue_pr_content(number: int, node_id: str, issue_type: str):
53
+ """Updates the title and body of the issue, pull request, or discussion."""
54
+ new_title = "Content Under Review"
55
+ new_body = """This post has been flagged for review by [Ultralytics Actions](https://ultralytics.com/actions) due to possible spam, abuse, or off-topic content. For more information please see our:
56
+
57
+ - [Code of Conduct](https://docs.ultralytics.com/help/code_of_conduct)
58
+ - [Security Policy](https://docs.ultralytics.com/help/security)
59
+
60
+ For questions or bug reports related to this action please visit https://github.com/ultralytics/actions.
61
+
62
+ Thank you 🙏
63
+ """
64
+ if issue_type == "discussion":
65
+ mutation = """
66
+ mutation($discussionId: ID!, $title: String!, $body: String!) {
67
+ updateDiscussion(input: {discussionId: $discussionId, title: $title, body: $body}) {
68
+ discussion {
69
+ id
70
+ }
71
+ }
72
+ }
73
+ """
74
+ graphql_request(mutation, variables={"discussionId": node_id, "title": new_title, "body": new_body})
75
+ else:
76
+ url = f"{GITHUB_API_URL}/repos/{REPO_NAME}/issues/{number}"
77
+ r = requests.patch(url, json={"title": new_title, "body": new_body}, headers=GITHUB_HEADERS)
78
+ print(f"{'Successful' if r.status_code == 200 else 'Fail'} issue/PR #{number} update: {r.status_code}")
79
+
80
+
81
+ def close_issue_pr(number: int, node_id: str, issue_type: str):
82
+ """Closes the issue, pull request, or discussion."""
83
+ if issue_type == "discussion":
84
+ mutation = """
85
+ mutation($discussionId: ID!) {
86
+ closeDiscussion(input: {discussionId: $discussionId}) {
87
+ discussion {
88
+ id
89
+ }
90
+ }
91
+ }
92
+ """
93
+ graphql_request(mutation, variables={"discussionId": node_id})
94
+ else:
95
+ url = f"{GITHUB_API_URL}/repos/{REPO_NAME}/issues/{number}"
96
+ r = requests.patch(url, json={"state": "closed"}, headers=GITHUB_HEADERS)
97
+ print(f"{'Successful' if r.status_code == 200 else 'Fail'} issue/PR #{number} close: {r.status_code}")
98
+
99
+
100
+ def lock_issue_pr(number: int, node_id: str, issue_type: str):
101
+ """Locks the issue, pull request, or discussion."""
102
+ if issue_type == "discussion":
103
+ mutation = """
104
+ mutation($lockableId: ID!, $lockReason: LockReason) {
105
+ lockLockable(input: {lockableId: $lockableId, lockReason: $lockReason}) {
106
+ lockedRecord {
107
+ ... on Discussion {
108
+ id
109
+ }
110
+ }
111
+ }
112
+ }
113
+ """
114
+ graphql_request(mutation, variables={"lockableId": node_id, "lockReason": "OFF_TOPIC"})
115
+ else:
116
+ url = f"{GITHUB_API_URL}/repos/{REPO_NAME}/issues/{number}/lock"
117
+ r = requests.put(url, json={"lock_reason": "off-topic"}, headers=GITHUB_HEADERS)
118
+ print(f"{'Successful' if r.status_code in {200, 204} else 'Fail'} issue/PR #{number} lock: {r.status_code}")
119
+
120
+
121
+ def block_user(username: str):
122
+ """Blocks a user from the organization."""
123
+ url = f"{GITHUB_API_URL}/orgs/{REPO_NAME.split('/')[0]}/blocks/{username}"
124
+ r = requests.put(url, headers=GITHUB_HEADERS)
125
+ print(f"{'Successful' if r.status_code == 204 else 'Fail'} user block for {username}: {r.status_code}")
126
+
127
+
128
+ def get_relevant_labels(
129
+ issue_type: str, title: str, body: str, available_labels: Dict, current_labels: List
130
+ ) -> List[str]:
131
+ """Uses OpenAI to determine the most relevant labels."""
132
+ # Remove mutually exclusive labels like both 'bug' and 'question' or inappropriate labels like 'help wanted'
133
+ for label in ["help wanted", "TODO"]: # normal case
134
+ available_labels.pop(label, None) # remove as should only be manually added
135
+ if "bug" in current_labels:
136
+ available_labels.pop("question", None)
137
+ elif "question" in current_labels:
138
+ available_labels.pop("bug", None)
139
+
140
+ # Add "Alert" to available labels if not present
141
+ if "Alert" not in available_labels:
142
+ available_labels["Alert"] = (
143
+ "Potential spam, abuse, or illegal activity including advertising, unsolicited promotions, malware, phishing, crypto offers, pirated software or media, free movie downloads, cracks, keygens or any other content that violates terms of service or legal standards."
144
+ )
145
+
146
+ labels = "\n".join(f"- {name}: {description}" for name, description in available_labels.items())
147
+
148
+ prompt = f"""Select the top 1-3 most relevant labels for the following GitHub {issue_type}.
149
+
150
+ INSTRUCTIONS:
151
+ 1. Review the {issue_type} title and description.
152
+ 2. Consider the available labels and their descriptions.
153
+ 3. Choose 1-3 labels that best match the {issue_type} content.
154
+ 4. Only use the "Alert" label when you have high confidence that this is an inappropriate {issue_type}.
155
+ 5. Respond ONLY with the chosen label names (no descriptions), separated by commas.
156
+ 6. If no labels are relevant, respond with 'None'.
157
+
158
+ AVAILABLE LABELS:
159
+ {labels}
160
+
161
+ {issue_type.upper()} TITLE:
162
+ {title}
163
+
164
+ {issue_type.upper()} DESCRIPTION:
165
+ {body[:16000]}
166
+
167
+ YOUR RESPONSE (label names only):
168
+ """
169
+ print(prompt) # for short-term debugging
170
+ messages = [
171
+ {
172
+ "role": "system",
173
+ "content": "You are a helpful assistant that labels GitHub issues, pull requests, and discussions.",
174
+ },
175
+ {"role": "user", "content": prompt},
176
+ ]
177
+ suggested_labels = get_completion(messages)
178
+ if "none" in suggested_labels.lower():
179
+ return []
180
+
181
+ available_labels_lower = {name.lower(): name for name in available_labels}
182
+ return [
183
+ available_labels_lower[label.lower().strip()]
184
+ for label in suggested_labels.split(",")
185
+ if label.lower().strip() in available_labels_lower
186
+ ]
187
+
188
+
189
+ def get_label_ids(labels: List[str]) -> List[str]:
190
+ """Retrieves GitHub label IDs for a list of label names using the GraphQL API."""
191
+ query = """
192
+ query($owner: String!, $name: String!) {
193
+ repository(owner: $owner, name: $name) {
194
+ labels(first: 100, query: "") {
195
+ nodes {
196
+ id
197
+ name
198
+ }
199
+ }
200
+ }
201
+ }
202
+ """
203
+ owner, repo = REPO_NAME.split("/")
204
+ result = graphql_request(query, variables={"owner": owner, "name": repo})
205
+ if "data" in result and "repository" in result["data"]:
206
+ all_labels = result["data"]["repository"]["labels"]["nodes"]
207
+ label_map = {label["name"].lower(): label["id"] for label in all_labels}
208
+ return [label_map.get(label.lower()) for label in labels if label.lower() in label_map]
209
+ else:
210
+ print(f"Failed to fetch labels: {result.get('errors', 'Unknown error')}")
211
+ return []
212
+
213
+
214
+ def apply_labels(number: int, node_id: str, labels: List[str], issue_type: str):
215
+ """Applies the given labels to the issue, pull request, or discussion."""
216
+ if "Alert" in labels:
217
+ create_alert_label()
218
+
219
+ if issue_type == "discussion":
220
+ print(f"Using node_id: {node_id}") # Debug print
221
+ label_ids = get_label_ids(labels)
222
+ if not label_ids:
223
+ print("No valid labels to apply.")
224
+ return
225
+
226
+ mutation = """
227
+ mutation($labelableId: ID!, $labelIds: [ID!]!) {
228
+ addLabelsToLabelable(input: {labelableId: $labelableId, labelIds: $labelIds}) {
229
+ labelable {
230
+ ... on Discussion {
231
+ id
232
+ }
233
+ }
234
+ }
235
+ }
236
+ """
237
+ graphql_request(mutation, {"labelableId": node_id, "labelIds": label_ids})
238
+ print(f"Successfully applied labels: {', '.join(labels)}")
239
+ else:
240
+ url = f"{GITHUB_API_URL}/repos/{REPO_NAME}/issues/{number}/labels"
241
+ r = requests.post(url, json={"labels": labels}, headers=GITHUB_HEADERS)
242
+ print(f"{'Successful' if r.status_code == 200 else 'Fail'} apply labels {', '.join(labels)}: {r.status_code}")
243
+
244
+
245
+ def create_alert_label():
246
+ """Creates the 'Alert' label in the repository if it doesn't exist."""
247
+ alert_label = {"name": "Alert", "color": "FF0000", "description": "Potential spam, abuse, or off-topic."}
248
+ requests.post(f"{GITHUB_API_URL}/repos/{REPO_NAME}/labels", json=alert_label, headers=GITHUB_HEADERS)
249
+
250
+
251
+ def is_org_member(username: str) -> bool:
252
+ """Checks if a user is a member of the organization."""
253
+ org_name = REPO_NAME.split("/")[0]
254
+ url = f"{GITHUB_API_URL}/orgs/{org_name}/members/{username}"
255
+ r = requests.get(url, headers=GITHUB_HEADERS)
256
+ return r.status_code == 204 # 204 means the user is a member
257
+
258
+
259
+ def add_comment(number: int, node_id: str, comment: str, issue_type: str):
260
+ """Adds a comment to the issue, pull request, or discussion."""
261
+ if issue_type == "discussion":
262
+ mutation = """
263
+ mutation($discussionId: ID!, $body: String!) {
264
+ addDiscussionComment(input: {discussionId: $discussionId, body: $body}) {
265
+ comment {
266
+ id
267
+ }
268
+ }
269
+ }
270
+ """
271
+ graphql_request(mutation, variables={"discussionId": node_id, "body": comment})
272
+ else:
273
+ url = f"{GITHUB_API_URL}/repos/{REPO_NAME}/issues/{number}/comments"
274
+ r = requests.post(url, json={"body": comment}, headers=GITHUB_HEADERS)
275
+ print(f"{'Successful' if r.status_code in {200, 201} else 'Fail'} issue/PR #{number} comment: {r.status_code}")
276
+
277
+
278
+ def get_first_interaction_response(issue_type: str, title: str, body: str, username: str, number: int) -> str:
279
+ """Generates a custom response using LLM based on the issue/PR content and instructions."""
280
+ issue_discussion_response = f"""
281
+ 👋 Hello @{username}, thank you for submitting a `{REPO_NAME}` 🚀 {issue_type.capitalize()}. To help us address your concern efficiently, please ensure you've provided the following information:
282
+
283
+ 1. For bug reports:
284
+ - A clear and concise description of the bug
285
+ - A minimum reproducible example (MRE)[https://docs.ultralytics.com/help/minimum_reproducible_example/] that demonstrates the issue
286
+ - Your environment details (OS, Python version, package versions)
287
+ - Expected behavior vs. actual behavior
288
+ - Any error messages or logs related to the issue
289
+
290
+ 2. For feature requests:
291
+ - A clear and concise description of the proposed feature
292
+ - The problem this feature would solve
293
+ - Any alternative solutions you've considered
294
+
295
+ 3. For questions:
296
+ - Provide as much context as possible about your question
297
+ - Include any research you've already done on the topic
298
+ - Specify which parts of the [documentation](https://docs.ultralytics.com), if any, you've already consulted
299
+
300
+ Please make sure you've searched existing {issue_type}s to avoid duplicates. If you need to add any additional information, please comment on this {issue_type}.
301
+
302
+ Thank you for your contribution to improving our project!
303
+ """
304
+
305
+ pr_response = f"""
306
+ 👋 Hello @{username}, thank you for submitting an `{REPO_NAME}` 🚀 PR! To ensure a seamless integration of your work, please review the following checklist:
307
+
308
+ - ✅ **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/{REPO_NAME}/issues). Ensure your commit messages are clear, concise, and adhere to the project's conventions.
309
+ - ✅ **Synchronize with Source**: Confirm your PR is synchronized with the `{REPO_NAME}` `main` branch. If it's behind, update it by clicking the 'Update branch' button or by running `git pull` and `git merge main` locally.
310
+ - ✅ **Ensure CI Checks Pass**: Verify all Ultralytics [Continuous Integration (CI)](https://docs.ultralytics.com/help/CI/) checks are passing. If any checks fail, please address the issues.
311
+ - ✅ **Update Documentation**: Update the relevant [documentation](https://docs.ultralytics.com) for any new or modified features.
312
+ - ✅ **Add Tests**: If applicable, include or update tests to cover your changes, and confirm that all tests are passing.
313
+ - ✅ **Sign the CLA**: Please ensure you have signed our [Contributor License Agreement](https://docs.ultralytics.com/help/CLA/) if this is your first Ultralytics PR by writing "I have read the CLA Document and I sign the CLA" in a new message.
314
+ - ✅ **Minimize Changes**: Limit your changes to the **minimum** necessary for your bug fix or feature addition. _"It is not daily increase but daily decrease, hack away the unessential. The closer to the source, the less wastage there is."_ — Bruce Lee
315
+
316
+ For more guidance, please refer to our [Contributing Guide](https://docs.ultralytics.com/help/contributing). Don’t hesitate to leave a comment if you have any questions. Thank you for contributing to Ultralytics! 🚀
317
+ """
318
+
319
+ if issue_type == "pull request":
320
+ example = os.getenv("FIRST_PR_RESPONSE") or pr_response
321
+ else:
322
+ example = os.getenv("FIRST_ISSUE_RESPONSE") or issue_discussion_response
323
+
324
+ org_name, repo_name = REPO_NAME.split("/")
325
+ repo_url = f"https://github.com/{REPO_NAME}"
326
+ diff = get_pr_diff(number)[:32000] if issue_type == "pull request" else ""
327
+
328
+ prompt = f"""Generate a customized response to the new GitHub {issue_type} below:
329
+
330
+ CONTEXT:
331
+ - Repository: {repo_name}
332
+ - Organization: {org_name}
333
+ - Repository URL: {repo_url}
334
+ - User: {username}
335
+
336
+ INSTRUCTIONS:
337
+ - Do not answer the question or resolve the issue directly
338
+ - Adapt the example {issue_type} response below as appropriate, keeping all badges, links and references provided
339
+ - For bug reports, specifically request a minimum reproducible example (MRE) if not provided
340
+ - INCLUDE ALL LINKS AND INSTRUCTIONS IN THE EXAMPLE BELOW, customized as appropriate
341
+ - In your response, mention to the user that this is an automated response and that an Ultralytics engineer will also assist soon
342
+ - Do not add a sign-off or valediction like "best regards" at the end of your response
343
+ - Do not add spaces between bullet points or numbered lists
344
+ - Only link to files or URLs in the example below, do not add external links
345
+ - Use a few emojis to enliven your response
346
+
347
+ EXAMPLE {issue_type.upper()} RESPONSE:
348
+ {example}
349
+
350
+ {issue_type.upper()} TITLE:
351
+ {title}
352
+
353
+ {issue_type.upper()} DESCRIPTION:
354
+ {body[:16000]}
355
+
356
+ {"PULL REQUEST DIFF:" if issue_type == "pull request" else ""}
357
+ {diff if issue_type == "pull request" else ""}
358
+
359
+ YOUR {issue_type.upper()} RESPONSE:
360
+ """
361
+ print(f"\n\n{prompt}\n\n") # for debug
362
+ messages = [
363
+ {
364
+ "role": "system",
365
+ "content": f"You are a helpful assistant responding to GitHub {issue_type}s for the {org_name} organization.",
366
+ },
367
+ {"role": "user", "content": prompt},
368
+ ]
369
+ return get_completion(messages)
370
+
371
+
372
+ def main():
373
+ """Runs autolabel action and adds custom response for new issues/PRs/Discussions."""
374
+ number, node_id, title, body, username, issue_type, action = get_event_content()
375
+ available_labels = get_github_data("labels")
376
+ label_descriptions = {label["name"]: label.get("description", "") for label in available_labels}
377
+ if issue_type == "discussion":
378
+ current_labels = [] # For discussions, labels may need to be fetched differently or adjusted
379
+ else:
380
+ current_labels = [label["name"].lower() for label in get_github_data(f"issues/{number}/labels")]
381
+ relevant_labels = get_relevant_labels(issue_type, title, body, label_descriptions, current_labels)
382
+
383
+ if relevant_labels:
384
+ apply_labels(number, node_id, relevant_labels, issue_type)
385
+ if "Alert" in relevant_labels and not is_org_member(username):
386
+ update_issue_pr_content(number, node_id, issue_type)
387
+ if issue_type != "pull request":
388
+ close_issue_pr(number, node_id, issue_type)
389
+ lock_issue_pr(number, node_id, issue_type)
390
+ if BLOCK_USER:
391
+ block_user(username=username)
392
+ else:
393
+ print("No relevant labels found or applied.")
394
+
395
+ if action in {"opened", "created"}:
396
+ custom_response = get_first_interaction_response(issue_type, title, body, username, number)
397
+ add_comment(number, node_id, custom_response, issue_type)
398
+
399
+
400
+ if __name__ == "__main__":
401
+ main()
@@ -0,0 +1,82 @@
1
+ # Ultralytics Actions 🚀, AGPL-3.0 license https://ultralytics.com/license
2
+
3
+ import requests
4
+
5
+ from .utils import (
6
+ GITHUB_API_URL,
7
+ GITHUB_HEADERS,
8
+ PR_NUMBER,
9
+ REPO_NAME,
10
+ get_completion,
11
+ get_pr_diff,
12
+ )
13
+
14
+ # Action settings
15
+ SUMMARY_START = (
16
+ "## 🛠️ PR Summary\n\n<sub>Made with ❤️ by [Ultralytics Actions](https://github.com/ultralytics/actions)<sub>\n\n"
17
+ )
18
+
19
+
20
+ def generate_pr_summary(repo_name, diff_text):
21
+ """Generates a professionally written yet accessible summary of a PR using OpenAI's API."""
22
+ if not diff_text:
23
+ diff_text = "**ERROR: DIFF IS EMPTY, THERE ARE ZERO CODE CHANGES IN THIS PR."
24
+ ratio = 3.3 # about 3.3 characters per token
25
+ limit = round(128000 * ratio * 0.5) # use up to 50% of the 128k context window for prompt
26
+ messages = [
27
+ {
28
+ "role": "system",
29
+ "content": "You are an Ultralytics AI assistant skilled in software development and technical communication. Your task is to summarize GitHub PRs from Ultralytics in a way that is accurate, concise, and understandable to both expert developers and non-expert users. Focus on highlighting the key changes and their impact in simple, concise terms.",
30
+ },
31
+ {
32
+ "role": "user",
33
+ "content": f"Summarize this '{repo_name}' PR, focusing on major changes, their purpose, and potential impact. Keep the summary clear and concise, suitable for a broad audience. Add emojis to enliven the summary. Reply directly with a summary along these example guidelines, though feel free to adjust as appropriate:\n\n"
34
+ f"### 🌟 Summary (single-line synopsis)\n"
35
+ f"### 📊 Key Changes (bullet points highlighting any major changes)\n"
36
+ f"### 🎯 Purpose & Impact (bullet points explaining any benefits and potential impact to users)\n"
37
+ f"\n\nHere's the PR diff:\n\n{diff_text[:limit]}",
38
+ },
39
+ ]
40
+ reply = get_completion(messages)
41
+ if len(diff_text) > limit:
42
+ return SUMMARY_START + "**WARNING ⚠️** this PR is very large, summary may not cover all changes.\n\n" + reply
43
+ else:
44
+ return SUMMARY_START + reply
45
+
46
+
47
+ def update_pr_description(repo_name, pr_number, new_summary):
48
+ """Updates the original PR description with a new summary, replacing an existing summary if found."""
49
+ # Fetch the current PR description
50
+ pr_url = f"{GITHUB_API_URL}/repos/{repo_name}/pulls/{pr_number}"
51
+ pr_response = requests.get(pr_url, headers=GITHUB_HEADERS)
52
+ pr_data = pr_response.json()
53
+ current_description = pr_data.get("body") or "" # warning, can be None
54
+
55
+ # Check if existing summary is present and update accordingly
56
+ if SUMMARY_START in current_description:
57
+ updated_description = current_description.split(SUMMARY_START)[0] + new_summary
58
+ else:
59
+ updated_description = current_description + "\n\n" + new_summary
60
+
61
+ # Update the PR description
62
+ update_response = requests.patch(pr_url, json={"body": updated_description}, headers=GITHUB_HEADERS)
63
+ return update_response.status_code
64
+
65
+
66
+ def main():
67
+ """Summarize PR."""
68
+ diff = get_pr_diff(PR_NUMBER)
69
+
70
+ # Generate PR summary
71
+ summary = generate_pr_summary(REPO_NAME, diff)
72
+
73
+ # Update PR description
74
+ status_code = update_pr_description(REPO_NAME, PR_NUMBER, summary)
75
+ if status_code == 200:
76
+ print("PR description updated successfully.")
77
+ else:
78
+ print(f"Failed to update PR description. Status code: {status_code}")
79
+
80
+
81
+ if __name__ == "__main__":
82
+ main()