AI 코드 리뷰가 필요한 이유
수동 코드 리뷰는 리뷰어의 컨디션, 도메인 지식, 가용 시간에 따라 품질이 달라집니다. AI는 지치지 않고 모든 PR에 일관된 기준을 적용합니다.
flowchart LR PR[PR 생성] --> GH[GitHub Actions 트리거] GH --> Diff[git diff 추출] Diff --> LLM[LLM 분석] LLM --> Comment[PR 코멘트 게시] LLM --> Labels[라벨 자동 부착] style LLM fill:#7c3aed,color:#fff style Comment fill:#16a34a,color:#fff
기본 설정: GitHub Actions + Claude
# .github/workflows/ai-code-review.yml
name: AI Code Review
on:
pull_request:
types: [opened, synchronize]
jobs:
ai-review:
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: read
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get PR diff
id: diff
run: |
git diff origin/${{ github.base_ref }}...HEAD > pr_diff.txt
echo "diff_size=$(wc -c < pr_diff.txt)" >> $GITHUB_OUTPUT
- name: AI Code Review
if: steps.diff.outputs.diff_size < '50000' # 50KB 제한
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: python .github/scripts/ai_review.py
Python 리뷰 스크립트
# .github/scripts/ai_review.py
import os
import anthropic
from github import Github
client = anthropic.Anthropic()
gh = Github(os.environ["GITHUB_TOKEN"])
def get_pr_diff() -> str:
with open("pr_diff.txt") as f:
return f.read()[:30000] # 토큰 절약
def review_code(diff: str) -> str:
response = client.messages.create(
model="claude-sonnet-4-6-20251001",
max_tokens=4096,
system='''당신은 시니어 소프트웨어 엔지니어입니다.
PR diff를 검토해서 다음 항목을 체크하세요:
1. **보안 취약점**: SQL injection, XSS, 인증 누락, 시크릿 하드코딩
2. **버그**: null 참조, 경계값 오류, 레이스 컨디션
3. **성능**: N+1 쿼리, 불필요한 루프, 메모리 누수
4. **코드 품질**: 중복 코드, 복잡도, 명명 규칙
5. **테스트 누락**: 중요 로직에 테스트가 없는 경우
각 이슈는 심각도(🔴 CRITICAL / 🟡 WARNING / 🔵 INFO)와 함께 파일명:줄번호 형식으로 표시.
이슈가 없으면 ✅ LGTM 이라고만 작성.''',
messages=[{
"role": "user",
"content": f"다음 PR diff를 리뷰해주세요:
```diff
{diff}
```"
}]
)
return response.content[0].text
def post_review(review: str):
repo_name = os.environ["GITHUB_REPOSITORY"]
pr_number = int(os.environ["PR_NUMBER"])
repo = gh.get_repo(repo_name)
pr = repo.get_pull(pr_number)
# 기존 봇 코멘트 삭제 (중복 방지)
for comment in pr.get_issue_comments():
if comment.user.login == "github-actions[bot]":
comment.delete()
# 새 리뷰 코멘트 게시
body = f"
## 🤖 AI 코드 리뷰{review}
---
*Powered by Claude Sonnet 4.6*"
pr.create_issue_comment(body)
if __name__ == "__main__":
diff = get_pr_diff()
review = review_code(diff)
post_review(review)
print("✅ AI 코드 리뷰 완료")
특화 리뷰: 보안 집중 검사
SECURITY_REVIEW_PROMPT = '''당신은 보안 전문 코드 리뷰어입니다.
OWASP Top 10 기준으로 다음을 반드시 검사하세요:
A01 - Broken Access Control: 권한 확인 누락, IDOR 취약점
A02 - Cryptographic Failures: 약한 암호화, 키 하드코딩
A03 - Injection: SQL/NoSQL/OS 인젝션, XSS
A04 - Insecure Design: 비즈니스 로직 결함
A05 - Security Misconfiguration: 기본 설정, 불필요한 기능 활성화
A06 - Vulnerable Components: 취약한 의존성
A07 - Auth Failures: 약한 인증, 세션 관리
A08 - Integrity Failures: 검증되지 않은 데이터
A09 - Logging Failures: 로깅 부족, 민감정보 로그
A10 - SSRF: 서버 사이드 요청 위조
발견된 취약점은 CVE 또는 CWE 번호와 함께 수정 방법 제시.'''
자동 라벨링 및 승인
def auto_label(review: str, pr) -> None:
labels_to_add = []
if "🔴 CRITICAL" in review:
labels_to_add.append("needs-security-review")
# 보안팀 멘션
pr.create_issue_comment("@security-team 보안 리뷰가 필요합니다!")
elif "🟡 WARNING" in review:
labels_to_add.append("needs-revision")
else:
labels_to_add.append("ai-approved")
# 이슈 없으면 자동 approve
pr.create_review(event="APPROVE", body="AI 리뷰 통과: 이슈 없음")
for label in labels_to_add:
try:
pr.add_to_labels(label)
except Exception:
pass # 라벨이 없으면 무시
리뷰 품질 측정
| 지표 | 수동 리뷰 | AI 리뷰 |
|---|---|---|
| 응답 시간 | 수 시간 ~ 수 일 | 2-3분 |
| 보안 이슈 탐지율 | 60-70% | 85-90% |
| 일관성 | 리뷰어마다 다름 | 항상 동일 |
| 커버리지 | 일부 파일만 | 모든 변경사항 |
| 비용 | 엔지니어 시간 | ~$0.02/PR |
고급: 파일별 인라인 코멘트
def post_inline_comments(diff: str, pr):
# 파일별로 분리하여 각각 분석
files = parse_diff_by_file(diff)
for filename, file_diff in files.items():
if not is_code_file(filename):
continue
response = client.messages.create(
model="claude-haiku-4-5-20251001", # 저렴한 모델로 파일별 처리
max_tokens=512,
messages=[{
"role": "user",
"content": f"이 파일 diff에서 가장 심각한 이슈 하나만 알려줘:
{file_diff}"
}]
)
# GitHub Review API로 라인 코멘트
commit = pr.get_commits().reversed[0]
pr.create_review_comment(
body=response.content[0].text,
commit=commit,
path=filename,
line=get_changed_line(file_diff)
)
GitHub Actions에서 PR_NUMBER 환경변수는 ${{ github.event.number }}로 전달하세요.





