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
|
import Anthropic from "@anthropic-ai/sdk";
import { config } from "./config";
interface ReviewComment {
filePath: string;
lineNumber: number | null;
body: string;
}
interface ReviewResult {
summary: string;
comments: ReviewComment[];
approved: boolean;
}
let _client: Anthropic | null = null;
function getClient(): Anthropic {
if (!_client) {
if (!config.anthropicApiKey) {
throw new Error("ANTHROPIC_API_KEY is not set");
}
_client = new Anthropic({ apiKey: config.anthropicApiKey });
}
return _client;
}
export async function reviewDiff(
repoFullName: string,
prTitle: string,
prBody: string | null,
baseBranch: string,
headBranch: string,
diffText: string
): Promise<ReviewResult> {
const client = getClient();
const message = await client.messages.create({
model: "claude-sonnet-4-20250514",
max_tokens: 4096,
messages: [
{
role: "user",
content: `You are reviewing a pull request on the repository "${repoFullName}".
**PR Title:** ${prTitle}
**PR Description:** ${prBody || "(none)"}
**Base branch:** ${baseBranch}
**Head branch:** ${headBranch}
Review the following diff. Look for:
- Bugs, logic errors, or potential runtime failures
- Security vulnerabilities (injection, XSS, auth bypasses, secrets in code)
- Performance issues (N+1 queries, unnecessary allocations, blocking I/O)
- Missing error handling at system boundaries
- Breaking changes or API contract violations
Do NOT comment on style, formatting, naming, missing docs, or minor nitpicks. Only flag issues that could cause real problems.
Respond in JSON format:
{
"summary": "1-3 sentence overall assessment",
"approved": true/false,
"comments": [
{
"filePath": "path/to/file.ts",
"lineNumber": 42,
"body": "Explain the issue and suggest a fix"
}
]
}
If the diff looks clean, return approved: true with an empty comments array.
\`\`\`diff
${diffText.slice(0, 100000)}
\`\`\``,
},
],
});
const text =
message.content[0].type === "text" ? message.content[0].text : "";
try {
const jsonMatch = text.match(/\{[\s\S]*\}/);
if (!jsonMatch) {
return {
summary: "AI review completed but could not parse structured output.",
comments: [],
approved: true,
};
}
const parsed = JSON.parse(jsonMatch[0]);
return {
summary: parsed.summary || "Review complete.",
comments: Array.isArray(parsed.comments) ? parsed.comments : [],
approved: parsed.approved !== false,
};
} catch {
return {
summary: text.slice(0, 500),
comments: [],
approved: true,
};
}
}
export function isAiReviewEnabled(): boolean {
return !!config.anthropicApiKey;
}
|