[CI/CD] PR을 올리면 AI가 코드 리뷰를 해주는 워크플로우 도입

Melcoding·2일 전

계기

PR로 머지 기록들을 남기는데 혼자 작업을 하기에 개인 프로젝트라 내가 올리고 머지 승인하다보니 의미가 없다고 생각이 들었음.
그러다가 누군가 PR을 올릴 때, PR에 대한 반응을 자동적으로 만들었다는 글을 보고 그렇다면 AI를 붙여서 리뷰를 받을 수 있지 않을까 생각했음.

설계

1. 어떤 AI를 붙일까?

결론은 openAI를 연결하기로 했음

그 이유는?

코드 작업시 claude와 같이 작업을 진행하고 있는데, 리뷰도 같은 모델로 하는 경우 긍정적 편향이 있을 수 있다고 하는 내용을 어디선가 보았음.
그래서 '아! 그렇다면 다른 AI로 리뷰를 받는게 낫겠다'고 판단!

OpenAI의 여러 모델 중 어떤 것을 붙일까?

아래의 이유로 'gpt-5.1-codex-mini' 결정

  1. 코드 리뷰이기 때문에 OpenAI에서 제공하는 모델 중 코드를 잘 사용하는 모델란에 있는 모델을 선택하기로 했음. 제공 모델
  2. 코드 리뷰라 이미 작성된 코드를 검토하기에 성능이 엄청나게 필요하지 않을 것이라고 판단했고 그렇다면 경제적으로 생각해서 비용이 가장 적게드는 모델로 선택하지로 결론 내림.

그 결과가 'gpt-5.1-codex-mini' 였음.

2. 흐름

흐름은 아래와 같이 PR이 등록되거나 추가 커밋으로 수정되는 경우 시작하게 됨.

PR 오픈 or 추가 커밋으로 수정
  → git diff로 변경 코드 추출 (pr_diff.txt)
  → OpenAI API에 diff + 프롬프트 전송
  → 리뷰 텍스트 반환
  → GitHub PR 코멘트로 자동 등록

본격 GitHub Actions 워크플로우 추가

사전 준비

  • OpenAI API키를 받아서 GitHub 레포 Settings > SecretsOPENAI_API_KEY 등록

파일 구조

.github/
├── workflows/
│   └── pr-review.yml
└── scripts/
    └── ai_review.js

pr-review.yml

GitHub Action 워크플로우를 설정하는 파일로 아래와 같이 작성되어 있음.

# pr-review.yml

name: AI Code Review

# PR 이벤트 트리거
on:
  pull_request:
    types: [opened, synchronize, reopened] # 생성·커밋 추가·재오픈 시 실행

# GitHub 권한 설정
permissions:
  contents: read          # 코드 읽기
  pull-requests: write    # PR에 리뷰 코멘트 작성

jobs:
  ai-review:
    runs-on: ubuntu-latest
    steps:
      # 전체 커밋 히스토리 포함해서 체크아웃 (diff 계산에 필요)
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      # base 브랜치와 현재 브랜치의 diff를 파일로 저장
      - name: Get PR diff
        run: |
          git fetch origin ${{ github.base_ref }}
          git diff origin/${{ github.base_ref }}...HEAD > pr_diff.txt

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      # AI 리뷰 스크립트 실행에 필요한 패키지 설치
      - name: Install dependencies
        run: npm install openai

      # OpenAI API로 diff 분석 후 PR에 리뷰 코멘트 게시
      - name: Run AI review
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          GITHUB_REPOSITORY: ${{ github.repository }}
          PR_NUMBER: ${{ github.event.number }}
        run: node .github/scripts/ai_review.js

ai_review.js

워크플로우 설정 파일인 pr-review.yml에서 실행시키는 파일로 api를 통해 프롬프트를 보내고 받는 동작을 실행하는 파일임.
아래의 내용을 고려하여 작성되었음.

1. 토큰 효율성을 위해 프롬프트 질문은 영어로 질문하고 답변은 한글로 받음

참고
영어: 1토큰 ≈ 4글자
한글: 1토큰 ≈ 1~2글자

2. MAX_CHARS 설정

AI가 MAX_CHARS 설정을 제안했는데, 아래의 이유들로 수락함

  • OpenAI API 토큰 한도 초과 방지
  • 설정 안 하면 모델이 최대 128,000 토큰까지 응답 가능 → 비용 폭탄 위험
  • 대신 초과 시 앞부분만 리뷰하고 경고 문구 추가
  • 80,000자의 기준은 코드를 더 많이 봤을 AI를 믿기로 함

80,000자 기준 근거
명확한 계산 기준 없이 경험적 수치. 대략 80,000 ÷ 4 = 약 20,000 토큰

// ai_review.js

import { readFileSync } from "fs";
import OpenAI from "openai";

// diff 텍스트가 너무 길면 잘라내기 위한 최대 글자 수
const MAX_CHARS = 80_000;

// GitHub API를 호출해 PR에 코멘트를 게시하는 함수
async function postComment(body) {
    const { GITHUB_TOKEN, GITHUB_REPOSITORY, PR_NUMBER } = process.env;
    const url = `https://api.github.com/repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments`;

    const response = await fetch(url, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${GITHUB_TOKEN}`,
        Accept: "application/vnd.github+json",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ body }),
    });

    if (!response.ok) throw new Error(`GitHub API error: ${response.status}`);
  }

  async function main() {
    // 워크플로우에서 저장한 diff 파일 읽기
    let diff = readFileSync("pr_diff.txt", "utf-8");

    // 변경사항이 없으면 리뷰 생략
    if (!diff.trim()) {
      console.log("변경사항 없음, 리뷰 스킵");
      return;
    }

    // diff가 너무 길면 앞부분만 사용 (토큰 초과 방지)
    const truncated = diff.length > MAX_CHARS;
    if (truncated) diff = diff.slice(0, MAX_CHARS);

    const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

    // 리뷰 기준과 출력 포맷을 지정한 프롬프트
    const prompt = `You are a senior React/TypeScript developer. Please review the PR diff below and respond in Korean.
Project: React 19 + TypeScript + Firebase + Tailwind CSS advent calendar app

Review criteria:
1. TypeScript type safety (overuse of any, missing types, etc.)
2. React patterns (hooks rules, unnecessary re-renders, component structure)
3. Firebase usage (Timestamp conversion, error handling, security rule violations)
4. Security (XSS, exposed sensitive info, auth bypass possibilities)
5. Bug risks (runtime errors, missing edge cases)
6. Code quality (readability, DRY, duplicate logic)
7. Naming conventions (variables, functions, components — clarity, consistency, and intent)
8. Performance (unnecessary useEffect, missing optimization, memoization needs)
9. Error boundaries (missing try/catch, insufficient async error handling)
10. Component size (oversized components, need for separation)

Format:
- 🔴 **Critical** — must fix
- 🟡 **Warning** — recommended fix
- 🟢 **Suggestion** — improvement ideas
- ✅ **Good** — well done

If overall looks good, a short compliment is fine. Keep feedback concise and actionable.

  \`\`\`diff
  ${diff}
  \`\`\`
  ${truncated ? "\n> ⚠️  diff가 너무 커서 앞부분만 리뷰되었습니다." : ""}`;

    const model = "gpt-5.1-codex-mini"
    const context = [
      { role: 'user', content: prompt }
    ];

    // OpenAI Responses API로 리뷰 생성
    const response = await client.responses.create({
      model: model,
      input: context,
      max_output_tokens: 2000,
    });

    // 리뷰 결과를 PR 코멘트로 게시
    const review = response.output_text;
    await postComment(`## AI 코드 리뷰\n\n${review}\n\n---\n*Powered by OpenAI ${model}*`);
    console.log("리뷰 코멘트 등록 완료");
  }

  main().catch((error) => {
    console.error(error);
    process.exit(1);
  });

결과

프로젝트 도입 결과 리뷰를 보고 수정을 한 경우도 있고 추후 수정 계획인 것도 있는 등 보완해야 할 것들을 짚어줘서 코드의 퀄리티를 높일 수 있겠구나 느꼈음.
그리고 생각하지 못한 부분을 짚어줘서 배우는 것도 많음.
현재는 아래의 플로우로 작업 진행중!

PR 올림
→ 코멘트 달림
→ 코멘트 내용 판단
	 → 코멘트가 지금 바로 수정해야 할 크리티컬한 경우 바로 수정 진행
	 → 크리티컬하지 않거나 생각해야 할 부분이 있다면 보류
→ 판단 내용을 코멘트 남기고 추가 작업이 필요한 경우에는 작업
→ 머지하여 작업 완료

다른 프로젝트 도입

프로젝트에 도입하니 동작을 잘하고 생각보다 좋은 리뷰를 많이 주어서 PR의 의미가 생겨나는 것 같았음.
그래서 다른 저장소들에도 도입해야겠다고 생각하고 학습하고 실습하는 저장소에 이를 붙이기로 함.
다만 그곳은 기능을 따로 브랜치로 만들지 않아 PR을 올리지 않고 메인으로 바로 올리기에 지금 현재의 형태가 아닌 푸시할 경우에 코멘트로 작성하는 것으로 작업을 생각하였음.
관련 내용은 추후 2탄으로 가져오겠음.

수정해야 할 부분

전체 코드를 보지 않아서 이미 설정되어 있는 경우도 짚어주는 경우가 있는데, 설정 파일은 함께 프롬프트로 전달할지 고민 중.
사용하면서 수정할 부분은 아예 코멘트가 없는 경우도 있어서 답변이 없는 경우 예외처리를 추가 필요.

profile
https://github.com/meldyssey

0개의 댓글