Skip to content

Gemini Automated Code Review #44

Gemini Automated Code Review

Gemini Automated Code Review #44

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 (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("라인별 코드 리뷰 제출됨!");