-
Notifications
You must be signed in to change notification settings - Fork 0
219 lines (177 loc) · 7.31 KB
/
gemini_review.yml
File metadata and controls
219 lines (177 loc) · 7.31 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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
name: Gemini Automated Code Review
on:
pull_request:
types: [opened, reopened, synchronize]
branches:
- main
workflow_dispatch:
inputs:
pr_number:
description: "리뷰할 PR 번호 (기존 PR 수동 리뷰 시 입력)"
required: false
jobs:
code_review:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install GoogleGenerativeAI SDK
run: npm install @google/generative-ai@latest
- name: Determine PR Info
id: pr_info
uses: actions/github-script@v7
with:
script: |
let pr_number;
if (context.eventName === "workflow_dispatch") {
pr_number = context.payload.inputs["pr_number"];
if (!pr_number) {
core.setFailed("❌ 수동 실행 시에는 PR 번호 입력이 필요합니다.");
return;
}
} else {
pr_number = context.payload.pull_request.number;
}
console.log(`🔍 리뷰할 PR 번호: ${pr_number}`);
const { data: pr } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr_number
});
console.log(`📂 base: ${pr.base.ref}, head: ${pr.head.ref}`);
core.setOutput("number", pr_number);
core.setOutput("base", pr.base.ref);
core.setOutput("head", pr.head.ref);
- name: Generate Git Diff for PR
run: |
echo "📂 base: ${{ steps.pr_info.outputs.base }}"
echo "📂 head: ${{ steps.pr_info.outputs.head }}"
git fetch origin "${{ steps.pr_info.outputs.base }}"
git fetch origin "${{ steps.pr_info.outputs.head }}"
git diff "origin/${{ steps.pr_info.outputs.base }}"..."origin/${{ steps.pr_info.outputs.head }}" > diff.txt
- name: Run Gemini Review (Line-by-Line JSON)
id: gemini_review
uses: actions/github-script@v7
env:
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
with:
script: |
const fs = require("fs");
const diff_output = fs.readFileSync("diff.txt", "utf8");
const { GoogleGenerativeAI } = require("@google/generative-ai");
if (!diff_output.trim()) {
console.log("No code changes detected, skipping review.");
fs.writeFileSync("review_result.txt", "변경된 코드가 없어 리뷰를 건너뜁니다.");
return;
}
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
const model = genAI.getGenerativeModel({ model: "gemini-2.5-flash" });
const prompt = `
Explain in Korean.
당신은 **시니어 프론트엔드 개발자**입니다.
아래 git diff를 기반으로 변경된 줄마다 코드리뷰 코멘트를 작성하세요.
반드시 다음 JSON 형식을 **엄격히** 지켜서 출력하십시오:
[
{
"path": "src/components/Example.tsx",
"line": 123,
"text": "라인별 리뷰 코멘트",
"side": "RIGHT"
}
]
규칙:
1. **무조건 src 내부에 있는 .ts 및 .tsx 파일만 리뷰합니다.**
2. **성능 문제**가 있는지 해당 라인 기준으로 평가합니다.
3. **타입스크립트 문법 오류, 타입 미스매치, 타입 개선 포인트**가 있으면 지적합니다.
4. **변수명, 함수명, 주석 품질**이 부족한 경우 개선 의견을 냅니다.
5. **중복 코드 또는 리팩토링 포인트**가 보이면 간단하게 제안합니다.
6. 단, 라인별 리뷰 특성상 **각 코멘트는 1~2줄로 간결하게 작성**합니다.
(전체 파일 기준 5~8개 제안은 불가능하므로 "해당 줄에 필요한 제안"만 작성)
매우 중요:
- diff 의 "@@ -a,b +c,d @@" 정보를 이용해 **정확한 실제 변경 라인 번호(line)** 를 계산해야 합니다.
- JSON 형식이 조금이라도 깨지면 안 됩니다.
- JSON 이외의 설명 텍스트를 절대 출력하지 마세요.
<git diff>
${diff_output}
</git diff>
`;
const result = await model.generateContent(prompt);
const text = result.response.text();
fs.writeFileSync("review.json", text);
- name: Convert JSON to diff positions
id: convert
uses: actions/github-script@v7
with:
script: |
const fs = require("fs");
const review = JSON.parse(fs.readFileSync("review.json", "utf8"));
const diff = fs.readFileSync("diff.txt", "utf8");
const output = [];
const fileBlocks = diff.split("diff --git").slice(1);
for (const block of fileBlocks) {
const pathMatch = block.match(/b\/(.+)\nindex/);
if (!pathMatch) continue;
const path = pathMatch[1];
const fileComments = review.filter(c => c.path === path);
if (fileComments.length === 0) continue;
const lines = block.split("\n");
let position = 0;
let newLine = 0;
let oldLine = 0;
for (const line of lines) {
position++;
if (line.startsWith("@@")) {
const m = line.match(/@@ -(\d+),?\d* \+(\d+),?\d* @@/);
if (m) {
oldLine = parseInt(m[1]);
newLine = parseInt(m[2]);
}
continue;
}
if (line.startsWith("+")) {
newLine++;
const comment = fileComments.find(c => c.line === newLine);
if (comment) {
output.push({
path,
position,
body: comment.text
});
}
continue;
}
if (line.startsWith("-")) {
oldLine++;
continue;
}
oldLine++;
newLine++;
}
}
core.setOutput("comments", JSON.stringify(output));
- name: Submit Code Review
uses: actions/github-script@v7
with:
script: |
const comments = JSON.parse(`${{ steps.convert.outputs.comments }}`);
if (!comments.length) {
console.log("No review comments generated.");
return;
}
await github.rest.pulls.createReview({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: ${{ steps.pr_info.outputs.number }},
event: "COMMENT",
comments
});
console.log("라인별 코드 리뷰 제출됨!");