Skip to content

Commit 96dc1c8

Browse files
Merge branch 'main' into main
2 parents 681304f + c89894d commit 96dc1c8

File tree

1 file changed

+53
-123
lines changed

1 file changed

+53
-123
lines changed

etc/tool/copilot.py

+53-123
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,86 @@
11
import sys
2-
from pathlib import Path
3-
4-
sys.path.append(str(Path(__file__).parent.parent.parent))
5-
6-
import g4f
72
import os
3+
import time
84
import requests
5+
from pathlib import Path
96
from github import Github
7+
import g4f
108

11-
g4f.debug.logging = True
12-
g4f.debug.version_check = False
9+
sys.path.append(str(Path(__file__).parent.parent.parent))
1310

14-
GITHUB_TOKEN = os.getenv('GITHUB_TOKEN')
15-
GITHUB_REPOSITORY = os.getenv('GITHUB_REPOSITORY')
16-
PR_NUMBER = os.getenv('PR_NUMBER')
11+
# GitHub API credentials
12+
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")
13+
GITHUB_REPOSITORY = os.getenv("GITHUB_REPOSITORY")
14+
PR_NUMBER = os.getenv("PR_NUMBER")
1715

1816
# Allowed domains after migration
1917
ALLOWED_DOMAINS = {"is-epic.me", "is-awsm.tech"}
2018

21-
# README rules for validation
19+
# PR Review Rules
2220
README_RULES = """
2321
1. Subdomains must be for personal sites, open-source projects, or legitimate services.
24-
2. JSON structure must be valid and must contain 'domain', 'subdomain', 'owner', and 'records' fields.
25-
3. Wildcard domains (e.g., *.example.is-epic.me) require a detailed reason.
22+
2. JSON must contain 'domain', 'subdomain', 'owner', and 'records'.
23+
3. Wildcard domains require justification.
2624
4. Cloudflare (NS), Netlify, and Vercel are not supported.
27-
5. Illegal or inappropriate domain use is strictly prohibited.
28-
6. PR descriptions must be clear, and all required fields must be properly filled.
29-
7. Only the new domains (is-epic.me and is-awsm.tech) are allowed. Old domains will be rejected.
25+
5. Illegal or inappropriate domain use is prohibited.
26+
6. PR descriptions must be clear.
27+
7. Only new domains (is-epic.me and is-awsm.tech) are allowed.
3028
"""
3129

3230
def fetch_changed_files(pr):
3331
"""Fetches the list of files changed in the PR."""
3432
return [file.filename for file in pr.get_files()]
3533

3634
def fetch_file_content(repo, filename):
37-
"""Fetches the content of a given file in the PR."""
35+
"""Fetches file content from a PR."""
3836
try:
3937
file_content = repo.get_contents(filename, ref=pr.head.ref)
4038
return file_content.decoded_content.decode()
4139
except Exception as e:
4240
return f"Error fetching file content: {e}"
4341

4442
def ai_review_pr(pr_body, changed_files, file_contents):
45-
"""Uses AI to review the PR according to README rules."""
43+
"""Uses AI to review the PR based on the rules."""
4644
review_prompt = f"""
47-
Review the following pull request based on these rules:
45+
Review the PR based on these rules:
4846
4947
{README_RULES}
5048
5149
PR Description: {pr_body}
5250
Changed Files: {', '.join(changed_files)}
53-
51+
5452
File Contents:
5553
{file_contents}
5654
57-
Check if the PR follows the rules. Approve if everything is correct, or request changes if there are issues.
58-
Also, ensure that only the new domains (is-epic.me and is-awsm.tech) are used.
59-
If old domains (is-cool.me, is-app.tech) are present, reject the PR and request a domain update.
55+
- Approve if all rules are met.
56+
- Reject if old domains (`is-cool.me`, `is-app.tech`) are used.
57+
- If the JSON structure is missing or incorrect, request changes.
6058
"""
6159

62-
response = g4f.ChatCompletion.create(model=g4f.models.gpt_4, messages=[{"role": "user", "content": review_prompt}])
63-
64-
if isinstance(response, dict):
65-
decision = response.get("content", "").strip().lower()
66-
else:
67-
print(f"Unexpected response format: {response}")
68-
decision = response.strip().lower()
60+
try:
61+
response = g4f.ChatCompletion.create(
62+
model=g4f.models.gpt_4,
63+
messages=[{"role": "user", "content": review_prompt}]
64+
)
65+
66+
# Ensure response is a string
67+
decision = response.get("content", "").strip().lower() if isinstance(response, dict) else response.strip().lower()
6968

70-
return decision
69+
# Log unexpected responses
70+
if not decision:
71+
print("❌ AI response is empty or invalid. Defaulting to 'request changes'.")
72+
return "request changes"
73+
74+
# Force rejection if old domains are detected
75+
if "is-cool.me" in file_contents or "is-app.tech" in file_contents:
76+
print("❌ PR rejected due to use of old domains.")
77+
return "reject"
78+
79+
return decision
80+
81+
except Exception as e:
82+
print(f"❌ AI review failed: {e}")
83+
return "request changes"
7184

7285
def post_comment(pr, message):
7386
"""Posts a comment on the PR."""
@@ -84,99 +97,16 @@ def main():
8497
decision = ai_review_pr(pr.body, changed_files, file_contents)
8598

8699
if "approve" in decision:
87-
pr.create_review(event="APPROVE", body="AI Code Reviewer has approved this PR.")
88-
print("PR Approved by AI")
100+
pr.create_review(event="APPROVE", body="✅ AI Code Reviewer has approved this PR.")
101+
print("✅ PR Approved by AI")
102+
elif "reject" in decision:
103+
pr.create_review(event="REQUEST_CHANGES", body="❌ PR rejected due to rule violations.")
104+
post_comment(pr, "❌ Your PR violates the domain rules (e.g., using `is-cool.me`). Please update and resubmit.")
105+
print("❌ PR Rejected")
89106
else:
90-
pr.create_review(event="REQUEST_CHANGES", body="AI Code Reviewer suggests changes based on README rules.")
91-
post_comment(pr, f"AI Code Reviewer suggests changes:\n\n{decision}")
92-
print("PR Needs Changes & Commented")
107+
pr.create_review(event="REQUEST_CHANGES", body="⚠️ AI Code Reviewer suggests changes.")
108+
post_comment(pr, "⚠️ AI Reviewer suggests changes:\n\n" + decision)
109+
print("⚠️ PR Needs Changes")
93110

94111
if __name__ == "__main__":
95112
main()
96-
97-
98-
def wait_for_workflows(repo, pr_number, max_wait=1800, interval=30):
99-
"""Waits for specified GitHub workflows to complete."""
100-
workflows = ["auto-merge.yml", "save-pr-number.yml", "validation.yml"]
101-
start_time = time.time()
102-
103-
while time.time() - start_time < max_wait:
104-
runs = repo.get_workflow_runs(branch=f"refs/pull/{pr_number}/merge", status="in_progress")
105-
if all(run.name not in workflows for run in runs):
106-
return True # All workflows completed
107-
108-
time.sleep(interval)
109-
110-
print("❌ Workflows did not complete within the time limit.")
111-
return False
112-
113-
import os
114-
import requests
115-
116-
def approve_pull_request(repo, pr_number):
117-
"""Approves the pull request using GitHub token if available, otherwise fallback to standard API."""
118-
pr = repo.get_pull(pr_number)
119-
120-
# Dismiss previous approvals first
121-
dismiss_previous_approvals(repo, pr_number)
122-
123-
# Validate PR files and domains, collect feedback comments
124-
comments = []
125-
126-
if not validate_pr_files(pr):
127-
comments.append({"file": "unknown", "line": 0, "comment": "❌ PR contains invalid files. Please review file requirements."})
128-
129-
if not validate_domains(pr):
130-
comments.append({"file": "unknown", "line": 0, "comment": "❌ PR includes an invalid domain. Ensure compliance with domain policies."})
131-
132-
# Wait for workflows to complete
133-
if not wait_for_workflows(repo, pr_number):
134-
comments.append({"file": "unknown", "line": 0, "comment": "❌ Workflows did not pass successfully. Please check GitHub Actions."})
135-
136-
# Post review comments if any issues are found
137-
if comments:
138-
post_review_comment(repo, pr_number, comments)
139-
return
140-
141-
# Attempt to approve PR using GitHub Actions secret token
142-
github_token = os.getenv("GITHUB_TOKEN")
143-
if github_token:
144-
headers = {"Authorization": f"token {github_token}"}
145-
approval_url = f"https://api.github.com/repos/{repo.owner.login}/{repo.name}/pulls/{pr_number}/reviews"
146-
data = {"event": "APPROVE", "body": "✅ PR approved automatically as it complies with rules and all workflow checks passed."}
147-
148-
response = requests.post(approval_url, json=data, headers=headers)
149-
if response.status_code in [200, 201]:
150-
print("✅ PR approved successfully using GitHub token.")
151-
return
152-
else:
153-
print(f"⚠️ GitHub token approval failed: {response.json()}")
154-
155-
# Fallback: Approve using repository API
156-
try:
157-
pr.create_review(event="APPROVE", body="✅ PR approved automatically as it complies with rules and all workflow checks passed.")
158-
print("✅ PR approved successfully using repository API.")
159-
except Exception as e:
160-
print(f"❌ Failed to approve PR using repository API: {e}")
161-
162-
def dismiss_previous_approvals(repo, pr_number):
163-
"""Dismisses previous approvals for the PR."""
164-
pr = repo.get_pull(pr_number)
165-
reviews = pr.get_reviews()
166-
167-
for review in reviews:
168-
if review.state == "APPROVED":
169-
repo._requester.requestJson("PUT", f"{pr.url}/reviews/{review.id}/dismissals",
170-
input={"message": "Approval dismissed due to new commit."})
171-
print(f"⚠️ Previous approval by {review.user.login} dismissed.")
172-
173-
def post_review_comment(repo, pr_number, comments):
174-
"""Posts review comments on the PR instead of rejecting it outright."""
175-
pr = repo.get_pull(pr_number)
176-
177-
review_comments = [{"path": c["file"], "position": c["line"], "body": c["comment"]} for c in comments]
178-
if review_comments:
179-
pr.create_review(event="REQUEST_CHANGES", comments=review_comments)
180-
print("⚠️ Review comments posted for required changes.")
181-
else:
182-
print("✅ No issues found.")

0 commit comments

Comments
 (0)