왜 시작했나: 사람이 아닌, 로봇이 먼저 보자
코드리뷰는 중요하지만 항상 시간과 에너지가 남아돌 때만 잘 된다. 내가 속한 팀도 예외가 아니었다. 스프린트 막바지에는 리뷰가 밀리고, 급한 기능부터 합치다 보면 “나중에 손보자”가 쌓였다. 그래서 발상을 바꿨다. 반복적이고 귀찮은 1차 검수는 AI에게 맡기고, 사람은 진짜 어려운 판단과 합의에 집중해 보자는 것. MR이 열리는 순간, AI가 먼저 변경 요약을 만들고 보안·성능·테스트 관점에서 핵심만 골라 코멘트를 남기면, 우리는 그걸 토대로 곧장 본질적인 토론으로 들어갈 수 있다. 그렇게 하면 컨텍스트가 사라지기 전에 더 빨리 결론에 닿는다.
사용 도구 및 오류 해결 과정
도구는 단순하게 가져갔다. CI 환경에서 프롬프트만으로 동작하는 Gemini CLI, 그리고 GitLab의 변경 사항을 읽고 논의를 달 수 있는 MCP 서버 조합이다. 구현 자체는 어렵지 않았다. 하지만 첫 시도에서는 기대했던 것보다 훨씬 많은 시간을 소모했다. 로그에 401 Unauthorized가 연달아 찍히며 아무 것도 진행되지 않았기 때문이다. 더 곤란했던 건 토큰 자체는 살아 있고 권한도 충분했는데, 요청이 자꾸 우리의 사내 GitLab이 아닌 gitlab.com으로 날아간다는 사실이었다. 사전 점검용 curl로는 내부 GitLab API가 멀쩡히 200을 주는데, MCP를 통하면 401이 나온다. 토큰이나 권한 문제가 아니라, 애초에 엉뚱한 곳을 두드리고 있었던 셈이다.
원인은 금방 명확해졌다. 내가 사용한 MCP 구현은 기본 GitLab API 엔드포인트를 https://gitlab.com/api/v4로 가정하고 있었다. 우리 환경은 https://mgt-gitlab.xxx.com/api/v4다. 엔드포인트를 명시적으로 지정하지 않는 한, 어떤 토큰을 넣든 결과는 같았다. 해결은 간단했다. MCP가 읽는 환경변수 이름에 맞춰 GITLAB_URL을 우리 도메인의 v4 엔드포인트로 지정하고, 토큰은 GITLAB_TOKEN으로 전달했다. 그리고 경로 파싱으로 프로젝트를 식별하게 두지 않고, 숫자 project_id만 쓰도록 모델에게 강제했다. OAuth를 꺼서(명시적으로 --oauth=false) PAT만 사용하게 한 것도 안정화에 도움이 됐다. 이렇게 바꾸고 나니, 요청이 정확히 https://mgt-gitlab.xxx.com/api/v4로 향했고, 디스커션과 코멘트가 정상적으로 생성되기 시작했다.
gitlab-ci.yml 파일에 review 관련 stages 적용하기
최종 설정은 아래 한 파일로 정리했다. 핵심은 세 가지다. 사전 curl로 토큰과 권한을 점검하고, GITLAB_URL을 내부 엔드포인트로 못 박고, 프롬프트에서 project_id와 MR IID만 사용하도록 분명히 지시하는 것. 그 외에는 도구를 최소 권한으로 열어두고, 스팸성 코멘트를 만들지 않도록 프롬프트를 절제하는 정도다.
stages:
- gemini_code_review
code_review:
stage: gemini_code_review
image: us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.1.19
variables:
GIT_DEPTH: "0"
before_script:
- echo "Starting Gemini code review…"
# 키 존재 확인
- |
[ -n "$GEMINI_API_KEY" ] || { echo "Error: GEMINI_API_KEY is not set."; exit 1; }
[ -n "$GITLAB_REVIEW_PAT" ] || { echo "Error: GITLAB_REVIEW_PAT is not set."; exit 1; }
# fforster MCP 바이너리
- |
GITLAB_MCP_VERSION="1.31.0"
curl -sSL -o gitlab-mcp.tgz "https://gitlab.com/fforster/gitlab-mcp/-/releases/v${GITLAB_MCP_VERSION}/downloads/gitlab-mcp_${GITLAB_MCP_VERSION}_Linux_x86_64.tar.gz"
tar -xzf gitlab-mcp.tgz && chmod +x gitlab-mcp
# 사전 토큰 체크(이건 항상 너 내부 GitLab URL로 호출됨)
- |
CODE=$(curl -s -o /dev/null -w "%{http_code}" \
-H "PRIVATE-TOKEN: $GITLAB_REVIEW_PAT" \
"$CI_API_V4_URL/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID")
if [ "$CODE" != "200" ]; then
echo "GitLab token precheck failed (HTTP $CODE) for MR $CI_MERGE_REQUEST_IID."; exit 1
fi
# 👇 MCP가 참조할 환경변수들 (fforster는 GITLAB_URL / GITLAB_TOKEN을 사용)
- export GITLAB_TOKEN="$GITLAB_REVIEW_PAT"
- export GITLAB_URL="$CI_API_V4_URL" # https://mgt-gitlab.xxx.com/api/v4 로 설정됨
- export CI_PROJECT_ID="$CI_PROJECT_ID" # 프롬프트에서 사용
# Gemini 설정 (args에 --oauth=false 권장)
- |
mkdir -p "$HOME/.gemini"
cat > "$HOME/.gemini/settings.json" <<EOF
{
"coreTools": ["LSTool","ReadFileTool","GrepTool","GlobTool","ReadManyFilesTool"],
"mcpServers": {
"gitlab": {
"command": "${CI_PROJECT_DIR}/gitlab-mcp",
"args": ["--oauth=false"],
"env": {
"GITLAB_TOKEN": "${GITLAB_REVIEW_PAT}",
"GITLAB_URL": "${CI_API_V4_URL}"
},
"timeout": 5000,
"includeTools": [
"discussion_add_note",
"discussion_list",
"get_merge_request",
"get_merge_request_changes",
"get_merge_request_commits",
"list_merge_request_diffs",
"get_merge_request_participants",
"get_merge_request_reviewers",
"get_repository_file_contents"
]
}
}
}
EOF
script:
# 5) 실제 리뷰 수행
- |
gemini --yolo <<EOF
당신은 숙련된 코드리뷰어다. 아래 GitLab MR에 대해 간결한 요약과 개선 제안을 남겨라.
- 프로젝트 경로: ${CI_PROJECT_PATH}
- 반드시 이 MR IID만 사용: ${CI_MERGE_REQUEST_IID}
원칙:
1) 스팸 금지, 실제 품질에 의미 있는 지적만.
2) Java/Spring, PHP/Laravel, Node/Nest/Express, SQL/MySQL, JPA, Redis/RabbitMQ, K8s/Helm, GitOps/ArgoCD 관점의 베스트프랙티스 반영.
3) 보안(입력검증/시크릿/SQLi), 성능(N+1/인덱스/Filesort/캐싱), 트랜잭션/동시성, 테스트(단위/통합), 로그/모니터링 관점 포함.
4) 가능하면 라인 인용과 간단한 패치 예시(코드블록)를 제시.
5) 본인이 이전에 남긴 코멘트 중복 금지.
작업:
- 변경 요약: 필요 시 단일 코멘트로 핵심 변경 요약.
- 개선 제안: 우선순위 높은 3~7개 항목을 개별 코멘트로 작성.
EOF
rules:
# MR 이벤트에서만 실행
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: on_success
동작은 단순하다. MR이 열리면 파이프라인이 기동하고, Gemini CLI가 프롬프트에 따라 MCP를 호출한다. MCP는 우리 GitLab의 v4 API로 MR 개요와 변경 내용을 읽고, 필요한 경우 디스커션과 코멘트를 생성한다. 나는 그 결과를 확인하면서 정말 중요한 논점만 빠르게 파고들면 된다. 터무니없는 잔소리나 과장된 포맷을 만들지 않도록 프롬프트를 계속 다듬었고, 팀의 스택에 맞춰 관점을 좁혔다. JPA의 N+1, MySQL 인덱싱과 Filesort, 캐싱과 동시성, RabbitMQ 재처리 같은 항목은 우리 상황에서 반복적으로 마주치는 지점이라 우선순위를 높였다. 덕분에 AI가 남기는 코멘트가 잡다한 스타일 이슈에 매달리지 않고 진짜 위험도를 먼저 건드리기 시작했다.
이 방식의 장점은 명확하다. 누군가가 항상 바쁘다는 사실을 인정하고, 그 상태에서도 품질을 일정 수준으로 끌어올리는 길을 만든다. AI가 남긴 코멘트는 초벌 분류에 가깝지만, 그 초벌이 있는 것과 없는 것의 차이는 크다. 리뷰를 시작할 때의 진입 장벽이 낮아지고, 사람이 판단해야 할 이슈로 더 빨리 닿는다. 무엇보다도 “귀찮음”을 줄이는 것에서 큰 만족을 느낀다. 테스트 누락이나 사소한 누수처럼 반복적으로 발견되는 지점은 AI가 먼저 긁어 주고, 나는 배경지식과 도메인 맥락이 필요한 논점을 다듬는다. 결과적으로 리뷰의 밀도가 높아지고, 팀 전체의 응답 속도가 빨라졌다.
여기서 끝인가?
물론 여기서 끝은 아니다. 줄 단위 포지션을 정교하게 찍는 초안 코멘트 흐름으로 바꿔 보면, 더 밀도 높은 리뷰가 가능하다. Draft Notes API를 쓰면 여러 코멘트를 한 번에 공개할 수도 있어 운영이 깔끔해진다. 프롬프트도 팀별로 계속 튜닝할 수 있다. 우리처럼 JPA를 많이 쓰는 팀이라면 엔티티 그래프나 배치 사이즈 같은 키워드를 아예 프롬프트에 박아 두는 것도 방법이다. 계정도 봇을 분리하면, 기록과 추적성이 좋아진다. 포크 MR에서 변수가 주입되지 않는 문제는 정책으로 풀 수 있고, 필요하면 허용 범위를 신중히 열면 된다.
이번 작업을 하면서 가장 크게 배운 건, “기본값”을 절대 믿지 말자는 점이다. 특히 엔드포인트 같은 건 항상 명시해야 한다. GITLAB_URL 한 줄이 빠졌을 뿐인데, 토큰도 권한도 무의미해졌다. 반대로 그 한 줄을 정확히 넣었더니 모든 게 제자리를 찾았다. 자동화는 결국 사람의 시간을 되찾아 주기 위한 장치다. MR을 열면 AI가 먼저 보고, 사람은 중요한 일에 더 오래 머문다. 내가 하고 싶었던 건 그 단순한 분업이었고, 지금은 그 방향으로 잘 굴러가고 있다.
'Infrastructure > Git' 카테고리의 다른 글
[GitLab] Spring Multi Module Project gitlab-ci.yml 작성방법 (0) | 2025.03.15 |
---|---|
알기 쉽게 정의한 DevOps와 GitOps (0) | 2025.03.06 |
Git 사용중에 .gitignore 적용 안되는 현상 해결 (0) | 2022.07.08 |
댓글