Skip to content

refactor: 라인별 코드리뷰로 변경 #47

refactor: 라인별 코드리뷰로 변경

refactor: 라인별 코드리뷰로 변경 #47

Workflow file for this run

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 (Chunked)
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");
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
const model = genAI.getGenerativeModel({ model: "gemini-2.5-flash" });
if (!diff_output.trim()) {
fs.writeFileSync("review.json", "[]");
return;
}
const CHUNK_SIZE = 30000;
let chunks = [];
for (let i = 0; i < diff_output.length; i += CHUNK_SIZE) {
chunks.push(diff_output.slice(i, i + CHUNK_SIZE));
}
console.log(`📦 청크 개수: ${chunks.length}`);
// --- 2) Request Gemini for each chunk ---
async function askGemini(prompt) {
const res = await model.generateContent(prompt);
return res.response.text();
}
let merged = [];
for (let idx = 0; idx < chunks.length; idx++) {
const chunk = chunks[idx];
const prompt = `
Explain in Korean.
당신은 **시니어 프론트엔드 개발자**입니다.
아래 git diff 조각(chunk ${idx+1}/${chunks.length})을 기반으로
변경된 코드의 '변경된 줄'마다 리뷰 코멘트를 생성하세요.
출력은 반드시 **JSON 배열만**:
[
{
"path": "src/components/Example.tsx",
"line": 123,
"text": "리뷰 코멘트",
"side": "RIGHT"
}
]
규칙:
- src 내부의 .ts / .tsx 파일만 리뷰
- 성능 문제 판단
- 타입스크립트 문법/타입 개선 지적
- 변수명/함수명/주석 품질 평가
- 중복 코드/리팩토링 포인트 제안
- 각 코멘트는 1~2줄만 작성
매우 중요:
- "@@ -a,b +c,d @@" 를 사용해 정확한 새 라인 번호 계산
- JSON 코드블록 금지 (```json 포함 금지)
- JSON 외 다른 텍스트를 절대 출력하지 말 것
<git diff>
${chunk}
</git diff>
`;
console.log(`🤖 Gemini 요청: chunk ${idx+1}/${chunks.length}`);
let raw = await askGemini(prompt);
// 코드블록 제거
raw = raw.replace(/```json/g, "").replace(/```/g, "").trim();
try {
const parsed = JSON.parse(raw);
merged.push(...parsed);
} catch (e) {
console.log("❌ JSON 파싱 실패. 출력:");
console.log(raw);
throw e;
}
}
fs.writeFileSync("review.json", JSON.stringify(merged, null, 2));
console.log("🎉 모든 청크 JSON 병합 완료");
- name: Convert JSON to diff positions
id: convert
uses: actions/github-script@v7
with:
script: |
const fs = require("fs");
let raw = fs.readFileSync("review.json", "utf8")
.replace(/```json/g, "")
.replace(/```/g, "")
.trim();
let review = JSON.parse(raw);
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 match = fileComments.find(c => c.line === newLine);
if (match) {
output.push({
path,
position,
body: match.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("코멘트 없음 — 리뷰 생략");
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("라인별 코드 리뷰 제출 완료!");