코드리뷰 서비스를 만들었다
Codex 리뷰가 좋았는데 횟수가 모자랐다. 그래서 직접 만들었다.
Codex 코드리뷰 서비스를 잘 쓰고 있었는데 Plus 플랜의 제한이 빡빡했다. (물론 현재 claude code max 5x 와 codex pro 두개를 사용중이다.) OAuth로 직접 호출하면 제한 없이 쓸 수 있지 않을까? 이 생각 하나로 시작한 프로젝트가 사내 Git 플랫폼이 바뀌면서 본격적인 서비스가 됐다.
처음엔 프롬프트도 대충이었다. 일단 되는지가 목표였고 프롬프트는 차근차근 개선하면 된다고 생각했다. 실제로 그렇게 됐다.
Codex 리뷰 서비스가 좋았다
사내에서는 AI 관련 지원이 없었다. AI 코드리뷰를 쓰고 싶으면 개인적으로 알아서 해야 하는 상황이었다. CodeRabbit 같은 서비스를 팀에 도입하려고 알아봤는데 비용이 만만치 않아서 무산됐다. 최근에는 Claude도 코드리뷰 서비스를 출시했지만 역시 비싸다고 한다. GitHub Actions에 AI 리뷰를 붙이는 방법도 있긴 한데 팀에서 쉽게 적용하고 관리할 수 있는 형태는 아니었다.
그런 상황에서 Codex 코드리뷰 서비스를 개인적으로 쓰고 있었다. 사내 개발자 몇 명만 각자 구독해서 쓰는 정도. 근데 이게 정말 좋았다. PR을 올리면 AI가 자동으로 인라인 코멘트를 달아주는데 내가 미처 놓친 부분을 짚어주는 경험이 신선했다.
코드를 푸시하기 전에 Claude Code 같은 로컬 agent로 리뷰를 돌려볼 수도 있다. 근데 PR에 달리는 리뷰는 느낌이 다르다. PR에 달리면 나만 보는 게 아니라 팀원도 본다. 기록이 남고 공식적인 리뷰 프로세스의 일부가 된다. 로컬 agent는 내가 질문해야 답하는데 PR 리뷰는 AI가 먼저 전체를 스캔해준다. 물어보지 않은 것도 잡아준다. PR 열면 자동으로 달리니까 별도 작업도 없다.
문제는 횟수였다. 당시 Plus 플랜으로는 하루에 쓸 수 있는 리뷰가 너무 적었다.
OAuth로 직접 호출하면 되지 않나
Codex가 OAuth를 지원하는 걸 봤다. 이걸 직접 호출하면 플랜 제한 없이 쓸 수 있겠다.
그래서 만들기 시작했다. PR webhook을 받아서 diff를 추출하고 Codex OAuth로 AI 리뷰를 호출해서 결과를 PR 인라인 코멘트로 게시하는 것. 프롬프트는 나중에, 아키텍처도 일단 돌아가게. 거창한 설계보다는 PR 올리면 AI 리뷰가 자동으로 달리는 그 경험을 재현하는 게 1순위였다.
처음 버전은 프롬프트도 단순했고 에러 처리도 빈약했다. 근데 PR을 올리고 몇 초 뒤에 AI 코멘트가 달리는 걸 봤을 때 이건 된다고 느꼈다.
동작하는 걸 보니 나만 쓰기엔 아까웠다. 우리 사내 상황이 그랬다. 인원은 있지만 모두 바쁘고 각자 다른 프로젝트를 하다 보니 서로 코드리뷰를 해주기 어려웠다. PR이 며칠째 리뷰 없이 방치되는 일이 잦았다. 대시보드를 만들고 AI 모델을 선택할 수 있게 하고 리포별 설정을 지원하면서 점점 팀에서 쓸 수 있는 형태로 발전시켰다.
1 | PR Webhook → API (Fastify) → BullMQ → Worker → AI Gateway → PR 인라인 코멘트 |
API는 webhook을 빠르게 받고 Worker는 비동기로 AI 리뷰를 생성하고 Web은 설정과 히스토리를 관리한다. 세 개로 나눈 이유는 단순하다. webhook은 즉시 응답해야 하는데 AI 리뷰는 수십 초 걸린다. 큐를 사이에 두면 재시도도 된다.
사내 Git이 Gitea로 바뀌었다
그러던 중 사내에서 GitHub을 Gitea로 전환하게 됐다.
Codex 코드리뷰 서비스는 GitHub만 지원한다. 내가 만든 서비스도 GitHub 전용이었다. 둘 다 못 쓰게 됐다.
이게 오히려 프로젝트의 전환점이 됐다. 어떤 Git 플랫폼이든 AI 코드리뷰를 받을 수 있게 만들자.
Gitea 지원은 생각보다 많은 것을 바꿔야 했다. 가장 큰 차이는 GitHub App에 해당하는 게 Gitea에는 없다는 것이다.
GitHub에서는 App을 설치하면 webhook이 자동으로 등록되고 설치 기반으로 리포 접근 권한이 관리된다. App JWT로 Installation Token을 발급받아 API를 호출하는 2단계 인증 구조다. App이 설치된 리포만 자동으로 동기화되니까 접근 제어도 깔끔하다.
Gitea에는 이 구조가 통째로 없다. 대안으로 PAT(Personal Access Token)을 썼다. 토큰 하나로 webhook 생성도 하고 API 호출도 하는 구조다. 단순하긴 한데 GitHub App이 해주던 것들을 전부 직접 만들어야 했다.
webhook부터 그렇다. GitHub App은 설치만 하면 webhook이 알아서 붙는데 Gitea는 리포 활성화 시 API로 webhook을 직접 생성하고 비활성화 시 삭제하는 로직을 짰다. hook ID를 DB에 저장해서 생명주기를 관리한다. 접근 제어도 마찬가지다. GitHub App은 설치된 리포만 보이니까 자연스럽게 스코프가 정해지는데 Gitea PAT는 권한 범위 안의 모든 리포가 보인다. DB에 등록된 리포만 리뷰를 실행하도록 필터링을 넣었고 Gitea 리포는 등록 시 비활성 상태로 시작해서 대시보드에서 명시적으로 활성화해야 webhook이 생성되게 했다.
자잘한 차이도 많았다. webhook 서명 포맷이 다르다. GitHub은 sha256= 프리픽스가 붙은 HMAC인데 Gitea는 프리픽스 없이 hex 문자열만 온다. PR 업데이트 이벤트 이름도 다르다. Gitea는 synchronized이고 GitHub은 synchronize. diff를 가져오는 것도 달랐다. Gitea는 structured API patch를 지원하지 않아서 .diff URL로 fallback했다.
Worker의 기존 githubProvider 참조 14곳을 전수 교체하고 GitProvider 인터페이스를 추출해서 GitHub과 Gitea가 각각 구현하는 구조로 전환했다. 가장 큰 변경이었지만 덕분에 provider-agnostic 파이프라인이 만들어졌다. 이제 GitLab이든 Bitbucket이든 GitProvider 구현체 하나만 추가하면 된다.
프롬프트는 진짜 나중에 고쳤다
프롬프트는 나중에 하겠다고 했던 게 진짜 나중에 됐다. 근데 결국 고칠 수밖에 없는 순간이 온다.
처음에는 좋은 코드란 무엇인가를 길게 설명하는 프롬프트였다. AI는 성실하게 답하긴 하는데 취향 수준의 지적이 많았다. 이 변수명을 바꾸면 좋겠다, 이 함수가 좀 길다. 이런 코멘트가 PR마다 5~6개씩 달리면 결국 무시하게 된다.
V2에서는 방향을 바꿨다. 좋은 것을 설명하는 대신 절대 안 되는 것을 먼저 고정했다.
1 | Must-NOT 규칙: |
이렇게 하니까 AI 출력이 취향 지적에서 리스크 지적으로 바뀌었다. Decision 필드(APPROVE/COMMENT/REQUEST_CHANGES)도 도입해서 AI가 판정까지 내리게 했다.
프롬프트를 아무리 다듬어도 한계는 있었다. 단일 AI에게 보안, 로직, 품질을 동시에 분석하라고 하면 앞쪽에 집중하다 뒤쪽은 대충 넘긴다. 그래서 3개 전문 페르소나(Security, Logic, Quality)를 병렬로 실행하고 메타 리뷰어가 결과를 병합하는 구조를 만들었다. PR당 4번의 AI 호출이지만 사용자는 하나의 리뷰만 본다.
1 | ┌─ Security 페르소나 ──┐ |
돌아보면
계획해서 여기까지 온 게 아니다. Codex 횟수가 모자라서 시작했고 팀에 공유하고 싶어서 대시보드를 붙였고 Gitea로 전환되면서 멀티 프로바이더를 만들었고 리뷰 품질이 아쉬워서 프롬프트를 고쳤다. 매번 불편함이 다음 단계를 만들었다.
프롬프트를 완벽하게 만들고 시작하겠다는 건 함정이다. 일단 파이프라인을 만들어서 AI 코멘트가 PR에 달리는 경험을 먼저 해보는 게 낫다. 프롬프트는 실제로 달린 코멘트를 보면서 이건 쓸모없네, 이건 더 잡아줬으면 하며 고치는 게 훨씬 효과적이다.
GitHub에서 Gitea로 전환된 것도 다시 생각하면 괜찮았다. provider 추상화를 강제당해서 더 나은 구조가 됐고 Codex에 의존하지 않는 독립적인 서비스가 됐다.
멀티 페르소나가 정말 단일 프롬프트보다 나은지는 아직 정량적으로 모른다. A/B 테스트 체계를 만들어야 한다. PR당 4번 호출의 비용도 고민이다. diff에 따라 필요한 페르소나만 골라서 실행하는 것도 해보고 싶다.
근데 가장 중요한 건 지금 이 순간에도 PR을 올리면 AI 리뷰가 자동으로 달린다는 것이다. 그게 처음에 원했던 전부였고 그건 이미 되고 있다.