9
9
sys .path .append (str (Path (__file__ ).parent .parent .parent ))
10
10
11
11
# GitHub API credentials
12
- GITHUB_TOKEN = os .getenv ("GITHUB_TOKEN" )
12
+ GITHUB_TOKEN = os .getenv ("GITHUB_TOKEN" ) # Used for commenting and requesting changes
13
+ BOT_GITHUB_TOKEN = os .getenv ("BOT" ) # Used for approvals
13
14
GITHUB_REPOSITORY = os .getenv ("GITHUB_REPOSITORY" )
14
15
PR_NUMBER = os .getenv ("PR_NUMBER" )
15
16
18
19
19
20
# PR Review Rules
20
21
README_RULES = """
21
- 1. Subdomains must be for personal sites, open-source projects, or legitimate services.
22
- 2. JSON must contain 'domain', 'subdomain', 'owner', and 'records'.
23
- 3. Wildcard domains require justification.
24
- 4. Cloudflare (NS), Netlify, and Vercel are not supported.
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.
22
+ ### PR Review Guidelines
23
+ 1. **Subdomains must be valid**: Only for personal sites, open-source projects, or legitimate services.
24
+ 2. **JSON Structure**: Each file must contain `domain`, `subdomain`, `owner`, and `records`.
25
+ 3. **No Wildcard Abuse**: Wildcard domains (`*.example.com`) require proper justification.
26
+ 4. **Disallowed DNS Providers**: Cloudflare (NS), Netlify, and Vercel are **not allowed**.
27
+ 5. **Legal & Appropriate Usage**: Domains must not be used for illegal or inappropriate purposes.
28
+ 6. **Clear Descriptions**: PR descriptions should explain why the domain is needed.
29
+ 7. **Domain Restrictions**: **Only `is-epic.me` and `is-awsm.tech`** are allowed.
28
30
"""
29
31
30
32
def fetch_changed_files (pr ):
@@ -42,50 +44,77 @@ def fetch_file_content(repo, filename):
42
44
def ai_review_pr (pr_body , changed_files , file_contents ):
43
45
"""Uses AI to review the PR based on the rules."""
44
46
review_prompt = f"""
45
- Review the PR based on these rules:
47
+ **Task:** Review this Pull Request based on the following rules:
46
48
47
49
{ README_RULES }
48
50
49
- PR Description: { pr_body }
50
- Changed Files: { ', ' . join ( changed_files ) }
51
+ ** PR Description:**
52
+ { pr_body }
51
53
52
- File Contents:
54
+ **Changed Files:**
55
+ { ', ' .join (changed_files )}
56
+
57
+ **File Contents:**
53
58
{ file_contents }
54
59
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.
60
+ ---
61
+
62
+ **Expected Output Format:**
63
+ - If the PR is correct, respond with:
64
+ ✅ PR Approved. No issues found.
65
+ - If issues exist, respond with:
66
+ - **Structured comments per issue.**
67
+ - **GitHub Actions-style comments**, e.g.:
68
+ - 'Consider handling session failures...'
69
+ - 'Avoid using a generic exception handler...'
70
+ - 'Ensure that the import statement for `BlackboxAPI` aligns with others...'
71
+
72
+ **DO NOT** just say "Request changes"—explain why!
58
73
"""
59
74
60
75
try :
61
76
response = g4f .ChatCompletion .create (
62
77
model = g4f .models .gpt_4 ,
63
78
messages = [{"role" : "user" , "content" : review_prompt }]
64
79
)
65
-
66
- # Ensure response is a string
67
- decision = response .get ("content" , "" ).strip ().lower () if isinstance (response , dict ) else response .strip ().lower ()
68
80
69
- # Log unexpected responses
81
+ decision = response .get ("content" , "" ).strip () if isinstance (response , dict ) else response .strip ()
82
+
83
+ # If AI fails or response is empty, request changes automatically
70
84
if not decision :
71
85
print ("❌ AI response is empty or invalid. Defaulting to 'request changes'." )
72
- return "request changes"
86
+ return "request changes" , [ "AI review failed. Please manually check." ]
73
87
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"
88
+ # If AI finds issues, extract structured comments
89
+ if "consider" in decision .lower () or "avoid" in decision .lower ():
90
+ return "request changes" , decision .split ("\n " )
78
91
79
- return decision
92
+ return "approve" , []
80
93
81
94
except Exception as e :
82
95
print (f"❌ AI review failed: { e } " )
83
- return "request changes"
96
+ return "request changes" , [ "AI review failed. Please manually check." ]
84
97
85
98
def post_comment (pr , message ):
86
99
"""Posts a comment on the PR."""
87
100
pr .create_issue_comment (message )
88
101
102
+ def approve_pr (repo , pr ):
103
+ """Approves the PR using the bot's personal GitHub token."""
104
+ bot_github = Github (BOT_GITHUB_TOKEN )
105
+ bot_repo = bot_github .get_repo (GITHUB_REPOSITORY )
106
+ bot_pr = bot_repo .get_pull (int (PR_NUMBER ))
107
+
108
+ bot_pr .create_review (event = "APPROVE" , body = "✅ AI Code Reviewer (Bot) has approved this PR." )
109
+ print ("✅ PR Approved by AI (Using Bot Token)" )
110
+
111
+ def request_changes (repo , pr , comments ):
112
+ """Requests changes on the PR using the default GitHub Actions token."""
113
+ formatted_comments = "\n \n " .join ([f"⚠️ **{ comment } **" for comment in comments ])
114
+ pr .create_review (event = "REQUEST_CHANGES" , body = f"⚠️ AI Review suggests changes:\n \n { formatted_comments } " )
115
+ post_comment (pr , f"⚠️ AI Review:\n \n { formatted_comments } " )
116
+ print ("⚠️ PR Needs Changes" )
117
+
89
118
def main ():
90
119
github = Github (GITHUB_TOKEN )
91
120
repo = github .get_repo (GITHUB_REPOSITORY )
@@ -94,19 +123,12 @@ def main():
94
123
changed_files = fetch_changed_files (pr )
95
124
file_contents = "\n \n " .join ([f"### { file } \n { fetch_file_content (repo , file )} " for file in changed_files ])
96
125
97
- decision = ai_review_pr (pr .body , changed_files , file_contents )
98
-
99
- if "approve" in decision :
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" )
106
- else :
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" )
126
+ decision , comments = ai_review_pr (pr .body , changed_files , file_contents )
127
+
128
+ if decision == "approve" :
129
+ approve_pr (repo , pr ) # Uses bot token for approval
130
+ elif decision == "request changes" :
131
+ request_changes (repo , pr , comments ) # Uses GitHub Actions token for requests
110
132
111
133
if __name__ == "__main__" :
112
134
main ()
0 commit comments