@@ -72,134 +72,121 @@ jobs:
7272 git fetch origin "${{ steps.pr_info.outputs.head }}"
7373 git diff "origin/${{ steps.pr_info.outputs.base }}"..."origin/${{ steps.pr_info.outputs.head }}" > diff.txt
7474
75- - name : Run Gemini Review (Chunked)
75+ - name : Run Gemini Review with Chunking
7676 id : gemini_review
7777 uses : actions/github-script@v7
7878 env :
7979 GEMINI_API_KEY : ${{ secrets.GEMINI_API_KEY }}
8080 with :
8181 script : |
8282 const fs = require("fs");
83+
8384 const diff_output = fs.readFileSync("diff.txt", "utf8");
85+ if (!diff_output.trim()) {
86+ console.log("No code changes detected, skipping review.");
87+ fs.writeFileSync("review.json", "[]");
88+ return;
89+ }
8490
8591 const { GoogleGenerativeAI } = require("@google/generative-ai");
8692 const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
8793 const model = genAI.getGenerativeModel({ model: "gemini-2.5-flash" });
8894
89- if (!diff_output.trim()) {
90- fs.writeFileSync("review.json", "[]");
91- return;
92- }
95+ const CHUNK_SIZE = 90000;
96+ const chunks = [];
9397
94- const CHUNK_SIZE = 30000;
95- let chunks = [];
9698 for (let i = 0; i < diff_output.length; i += CHUNK_SIZE) {
97- chunks.push(diff_output.slice (i, i + CHUNK_SIZE));
99+ chunks.push(diff_output.substring (i, i + CHUNK_SIZE));
98100 }
99- console.log(`📦 청크 개수: ${chunks.length}`);
100-
101101
102- async function askGemini(prompt) {
103- const res = await model.generateContent(prompt);
104- return res.response.text();
105- }
102+ console.log(`📦 총 ${chunks.length}개의 청크로 분할됨`);
106103
107- let merged = [];
104+ const finalReviews = [];
108105
109106 for (let idx = 0; idx < chunks.length; idx++) {
110107 const chunk = chunks[idx];
111108
109+ console.log(`▶ Gemini 요청 중... 청크 ${idx + 1}/${chunks.length}`);
110+
112111 const prompt = `
113112 Explain in Korean.
114113
115114 당신은 **시니어 프론트엔드 개발자**입니다.
116- 아래 git diff 조각(chunk ${idx+1}/${chunks.length})을 기반으로
117- 변경된 코드의 '변경된 줄'마다 리뷰 코멘트를 생성하세요.
115+ 아래 git diff만을 기반으로 변경된 줄에 대한 코드 리뷰 코멘트를 JSON 배열로 작성하세요.
118116
119- 출력은 반드시 **JSON 배열만** :
117+ 반드시 아래 형식으로만 출력하세요 :
120118
121119 [
122120 {
123121 "path": "src/components/Example.tsx",
124122 "line": 123,
125- "text": "리뷰 코멘트",
123+ "text": "코멘트",
126124 "side": "RIGHT"
127125 }
128126 ]
129127
130128 규칙:
131- - src 내부의 .ts / .tsx 파일만 리뷰
132- - 성능 문제 판단
133- - 타입스크립트 문법/타입 개선 지적
134- - 변수명/함수명/주석 품질 평가
135- - 중복 코드/리팩토링 포인트 제안
136- - 각 코멘트는 1~2줄만 작성
137-
138- 매우 중요:
139- - "@@ -a,b +c,d @@" 를 사용해 정확한 새 라인 번호 계산
140- - JSON 코드블록 금지 (```json 포함 금지)
141- - JSON 외 다른 텍스트를 절대 출력하지 말 것
142-
143- <git diff>
129+ 1. src 내부 .ts / .tsx 파일만 포함
130+ 2. 청크에 없는 파일/라인 정보는 절대 작성 금지
131+ 3. JSON 이외의 설명 텍스트 절대 금지
132+
133+ <git diff chunk>
144134 ${chunk}
145- </git diff>
135+ </git diff chunk >
146136 `;
147137
148- console.log(`🤖 Gemini 요청: chunk ${idx+1}/${chunks.length}`);
149- let raw = await askGemini(prompt);
138+ try {
139+ const result = await model.generateContent(prompt);
140+ let text = result.response.text().trim();
141+
142+ text = text.replace(/```json|```/g, "").trim();
143+
144+ let parsed = [];
145+ try {
146+ parsed = JSON.parse(text);
147+ if (!Array.isArray(parsed)) parsed = [];
148+ } catch (err) {
149+ console.log("⚠ JSON parse 실패 → 해당 청크는 건너뜀");
150+ continue;
151+ }
150152
151- // 코드블록 제거
152- raw = raw.replace(/```json/g, "").replace(/```/g, "").trim();
153+ finalReviews.push(...parsed);
153154
154- try {
155- const parsed = JSON.parse(raw);
156- merged.push(...parsed);
157- } catch (e) {
158- console.log("❌ JSON 파싱 실패. 출력:");
159- console.log(raw);
160- throw e;
155+ } catch (error) {
156+ console.log("❌ Gemini 요청 실패:", error.message);
161157 }
162158 }
163159
164- fs.writeFileSync("review.json", JSON.stringify(merged , null, 2));
165- console.log("🎉 모든 청크 JSON 병합 완료 ");
160+ fs.writeFileSync("review.json", JSON.stringify(finalReviews , null, 2));
161+ console.log("🎉 모든 청크 처리 완료 → review.json 생성됨 ");
166162
167163 - name : Convert JSON to diff positions
168164 id : convert
169165 uses : actions/github-script@v7
170166 with :
171167 script : |
172168 const fs = require("fs");
173-
174169 let raw = fs.readFileSync("review.json", "utf8")
175- raw = raw
170+ .trim()
176171 .replace(/```json/g, "")
177172 .replace(/```/g, "")
178- .trim( );
173+ .replace(/^\n+|\n+$/g, "" );
179174
180- const jsonMatch = raw.match(/\[[\s\S]*\]/) ;
175+ let review ;
181176
182- if (!jsonMatch) {
183- console.log("❌ JSON 배열을 찾을 수 없음!");
177+ try {
178+ review = JSON.parse(raw);
179+ } catch (err) {
180+ console.log("Gemini JSON parse error");
184181 console.log("Raw output:");
185182 console.log(raw);
186- throw new Error("Gemini JSON extraction failed") ;
183+ throw err ;
187184 }
188185
189- let jsonString = jsonMatch[0];
190-
191- let review;
192- try {
193- review = JSON.parse(jsonString);
194- } catch (e) {
195- console.log("❌ JSON.parse 실패");
196- console.log("원본 JSON 문자열:");
197- console.log(jsonString);
198- throw e;
199- }
200186 const diff = fs.readFileSync("diff.txt", "utf8");
201187
202188 const output = [];
189+
203190 const fileBlocks = diff.split("diff --git").slice(1);
204191
205192 for (const block of fileBlocks) {
@@ -229,12 +216,12 @@ jobs:
229216
230217 if (line.startsWith("+")) {
231218 newLine++;
232- const match = fileComments.find(c => c.line === newLine);
233- if (match ) {
219+ const comment = fileComments.find(c => c.line === newLine);
220+ if (comment ) {
234221 output.push({
235222 path,
236223 position,
237- body: match .text
224+ body: comment .text
238225 });
239226 }
240227 continue;
@@ -245,7 +232,8 @@ jobs:
245232 continue;
246233 }
247234
248- oldLine++; newLine++;
235+ oldLine++;
236+ newLine++;
249237 }
250238 }
251239
@@ -258,7 +246,7 @@ jobs:
258246 const comments = JSON.parse(`${{ steps.convert.outputs.comments }}`);
259247
260248 if (!comments.length) {
261- console.log("코멘트 없음 — 리뷰 생략 ");
249+ console.log("No review comments generated. ");
262250 return;
263251 }
264252
@@ -270,4 +258,4 @@ jobs:
270258 comments
271259 });
272260
273- console.log("라인별 코드 리뷰 제출 완료 !");
261+ console.log("라인별 코드 리뷰 제출됨 !");
0 commit comments