-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathgithub_utils.py
138 lines (118 loc) · 4.44 KB
/
github_utils.py
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
from github import Github
from unidiff import PatchSet
from typing import List, Dict
def create_check_run(repo, sha):
"""Create a check run using the modern PyGithub API"""
return repo.create_check_run(
name="AI Code Review",
head_sha=sha,
status="queued", # Initial status should be 'queued'
output={
"title": "Analyzing Changes",
"summary": "🔍 Scanning code changes with AI...",
"text": "This may take 20-30 seconds"
}
)
def update_check_run(check_run, results):
"""Update check run with proper status transitions"""
# First update to in_progress
check_run.edit(
status="in_progress",
output={
"title": "Processing...",
"summary": "Analyzing code patterns"
}
)
# Then update with final results
annotations = []
for result in results:
# Extract line numbers from your analysis results
annotation = {
"path": result['fileName'],
"start_line": result['start_line'], # REQUIRED
"end_line": result['end_line'], # REQUIRED
"annotation_level": map_severity(result['severity']),
"message": result['comment'],
"raw_details": f"Suggestion: {result['suggestion']}\n\n{result.get('suggestedCode', '')}"
}
annotations.append(annotation)
check_run.edit(
status="completed",
# conclusion="success" if len(annotations) == 0 else "action_required",
conclusion="success",
output={
"title": f"Found {len(annotations)} items",
"summary": "AI Code Review Results",
"annotations": annotations[:50] # GitHub limits to 50 annotations per update
}
)
def map_severity(level: str) -> str:
"""Map custom severity levels to GitHub annotation levels"""
return {
"error": "failure",
"warning": "warning",
"info": "notice"
}.get(level.lower(), "notice")
def parse_diff_file_line_numbers(diff_content: str) -> List[Dict]:
"""
Parse a unified diff string and return a structured list of changes using
actual file line numbers.
Returns a list of dicts, each representing a file change:
{
"file_name": str,
"changes": [
{
"type": "added" | "removed" | "context",
"line_number": int, # For added or context lines, this is target_line_no.
# For removed lines, use source_line_no.
"content": str
},
...
]
}
"""
patch = PatchSet(diff_content)
parsed_files = []
for patched_file in patch:
file_info = {
"file_name": patched_file.path,
"changes": []
}
for hunk in patched_file:
for line in hunk:
# Decide which line number to use based on change type.
if line.is_added or not line.is_removed:
line_num = line.target_line_no
else:
line_num = line.source_line_no
if line_num is None:
continue # Skip lines without a valid number
# Append each changed line along with its file-based line number.
file_info["changes"].append({
"type": "added" if line.is_added else "removed" if line.is_removed else "context",
"line_number": line_num,
"content": line.value.rstrip("\n")
})
parsed_files.append(file_info)
return parsed_files
def build_review_prompt_with_file_line_numbers(parsed_files: List[Dict]) -> str:
"""
Create a prompt that includes the diff using actual file line numbers.
"""
prompt_lines = []
for file_data in parsed_files:
file_name = file_data["file_name"]
prompt_lines.append(f"File: {file_name}\n")
prompt_lines.append("Changed lines:")
for change in file_data["changes"]:
# Mark added lines with +, removed with -, context with a space
sign = (
"+" if change["type"] == "added" else
"-" if change["type"] == "removed" else
" "
)
prompt_lines.append(
f"[Line {change['line_number']}] {sign} {change['content']}"
)
prompt_lines.append("\n")
return "\n".join(prompt_lines)