Repository navigation
Expand file tree
/
Copy pathgithub.py
More file actions
170 lines (139 loc) · 5.32 KB
/
Copy pathgithub.py
File metadata and controls
170 lines (139 loc) · 5.32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
"""
github.py — all GitHub CLI interactions with retry hardening
"""
import os, subprocess, json, re, time
from config import REPO, PR_STATE, PR_LIMIT, CONTEXT_FILE
GH_RETRIES = 3
GH_RETRY_SLEEP = 2 # seconds
def _gh_run(cmd, retries=GH_RETRIES, quiet=False):
"""Run a gh command with retry logic. Returns (returncode, stdout, stderr)."""
code = -1
stdout = ""
stderr = ""
last_err = None
for attempt in range(1, retries + 1):
try:
result = subprocess.run(
cmd,
capture_output=True, encoding="utf-8", errors="replace", timeout=60
)
if result.returncode == 0:
return result.returncode, result.stdout, result.stderr
code = result.returncode
stdout = result.stdout
stderr = result.stderr
last_err = result.stderr.strip()
except subprocess.TimeoutExpired:
code = -1
stdout = ""
stderr = "gh command timed out (60s)"
last_err = stderr
except Exception as e:
code = -1
stdout = ""
stderr = str(e)
last_err = stderr
if attempt < retries:
print(f" gh retry {attempt}/{retries} — {last_err[:80]}")
time.sleep(GH_RETRY_SLEEP)
if not quiet:
print(f" gh failed after {retries} retries: {last_err[:120] if last_err else 'unknown'}")
return code, stdout, stderr
def gh(endpoint):
code, stdout, _ = _gh_run(["gh", "api", f"https://api.github.com/{endpoint}"])
if code != 0:
return []
try:
return json.loads(stdout)
except Exception:
return []
def gh_paginate(endpoint):
code, stdout, _ = _gh_run(
["gh", "api", "--paginate", f"https://api.github.com/{endpoint}"]
)
if code != 0:
return []
text = stdout.strip()
if not text:
return []
try:
return json.loads(text)
except json.JSONDecodeError:
try:
text = "[" + text.replace("][", ",") + "]"
flat = json.loads(text)
return [i for sub in flat for i in sub] if flat and isinstance(flat[0], list) else flat
except Exception:
return []
def fetch_prs():
limit = max(1, min(PR_LIMIT, 100))
prs = gh(f"repos/{REPO}/pulls?state={PR_STATE}&per_page={limit}&sort=updated&direction=desc")
return prs if isinstance(prs, list) else []
def fetch_pr_files(n):
files = gh(f"repos/{REPO}/pulls/{n}/files?per_page=100")
return [f["filename"] for f in files] if isinstance(files, list) else []
def fetch_coderabbit_sections(n):
"""Fetch ONLY the Walkthrough and Changes sections from CodeRabbit comment."""
raw = None
comments = gh(f"repos/{REPO}/issues/{n}/comments?per_page=50")
if isinstance(comments, list):
for c in comments:
if "coderabbit" in c.get("user", {}).get("login", "").lower() and c.get("body"):
raw = c["body"]
break
if not raw:
reviews = gh(f"repos/{REPO}/pulls/{n}/reviews?per_page=50")
if isinstance(reviews, list):
for r in reviews:
if "coderabbit" in r.get("user", {}).get("login", "").lower() and r.get("body"):
raw = r["body"]
break
if not raw:
return None
return extract_walkthrough_and_changes(raw)
def extract_walkthrough_and_changes(text):
"""Pull only the Walkthrough and Changes table from a CodeRabbit comment."""
result = {}
wt_match = re.search(
r"##\s*Walkthrough\s*\n(.*?)(?=\n##\s|\Z)",
text, re.DOTALL | re.IGNORECASE
)
if wt_match:
result["walkthrough"] = wt_match.group(1).strip()
ch_match = re.search(
r"##\s*Changes\s*\n(.*?)(?=\n##\s|\Z)",
text, re.DOTALL | re.IGNORECASE
)
if ch_match:
result["changes"] = ch_match.group(1).strip()
if not result:
result["walkthrough"] = text[:800]
result["changes"] = ""
return result
def fetch_repo_context():
"""Project background for the prompts: a local context.md if present, else the target
repository's AGENTS.md, else its README.md. Returns (text, source)."""
if os.path.exists(CONTEXT_FILE):
with open(CONTEXT_FILE, "r", encoding="utf-8") as f:
return f.read().strip(), "context.md"
for path in ("AGENTS.md", "README.md"):
cmd = ["gh", "api", "-H", "Accept: application/vnd.github.raw", f"repos/{REPO}/contents/{path}"]
code, stdout, stderr = _gh_run(cmd, retries=1, quiet=True)
if code != 0 and "HTTP 404" not in stderr:
code, stdout, stderr = _gh_run(cmd) # not a missing file: retry like other gh calls
if code != 0 and "HTTP 404" not in stderr:
print(f" WARNING: could not read {path} from {REPO}; continuing without it")
if code == 0 and stdout.strip():
return stdout.strip(), f"{REPO}/{path}"
return "", None
def extract_linked_issue(body):
if not body:
return None
m = re.search(r"(?:fixes|closes|resolves)\s+#(\d+)", body, re.IGNORECASE)
return int(m.group(1)) if m else None
def check_gh_auth():
result = subprocess.run(
["gh", "auth", "status"],
capture_output=True, encoding="utf-8", errors="replace"
)
return result.returncode == 0