대부분이 큰 소리로 말하지 않는 것에 대해 솔직해지자. 우리는 AI가 생성한 코드를 거의 리뷰하지 않는다.
Diff가 600줄이다. 에이전트가 14개 파일을 건드렸다. Pull request를 열고, 변경 사항을 스크롤하고, 몇몇 함수를 힐끗 보고, 머지한다. 테스트를 먼저 돌릴 수도 있다. 안 돌릴 수도 있다. 코드가 합리적으로 보인다. 에이전트가 완료라고 했다. 배포하자.
이건 게으름이 아니다. 구조적인 문제다. 전통적인 코드 리뷰는 사람이 코드를 작성하고 질문하면 자신의 추론을 설명할 수 있는 세계를 위해 설계됐다. Diff가 50-200줄이었던 건 집중 세션에서 한 사람이 그 정도를 작성하기 때문이다. PR 설명에 "Y 때문에 접근법 X를 선택했다"고 쓰여 있었고, 그 맥락을 신뢰할 수 있었다.
AI 에이전트는 그렇게 작동하지 않는다. Claude Code는 2분 만에 500줄의 작동하는 코드를 생성할 수 있다. PR 설명은 보통 "feature X 구현"이 전부다. Diff는 무엇이 바뀌었는지는 알려주지만 왜에 대해서는 아무것도 알려주지 않는다. 에이전트가 어떤 대안을 고려했는지 기록이 없다. 어떤 트레이드오프를 했는지 설명이 없다. 실제로 무언가를 테스트했다는 증거가 없다. 블랙박스의 출력을 리뷰하고 있는데, 리뷰 도구는 출력만 보여준다.
이 글은 diff 기반 리뷰가 에이전트 출력에 대해 왜 실패하는지, 실제로 빠져 있는 층이 무엇인지, 그리고 리뷰 시간을 3배로 늘리지 않고 AI 생성 코드를 리뷰 가능하게 만드는 구체적인 패턴을 다룬다.
리뷰 갭
개발자들은 사적으로 이 문제에 솔직하다. 커뮤니티 스레드에서 패턴이 반복된다: "에이전트 diff는 대충 훑어본다." "테스트 통과 확인하고 머지한다." "대충 맞아 보이면 승인한다."
제약 조건을 고려하면 이건 합리적인 행동이다. 동료가 PR을 제출하면 맥락이 있다. 무슨 작업을 하고 있었는지 알고, 티켓을 봤고, 스탠드업에서 접근법을 논의했을 수도 있다. Diff는 보충 자료다. 진짜 리뷰는 공유된 맥락을 통해 이루어졌다.
에이전트의 경우, 그런 것이 전혀 없다. 에이전트가 태스크를 가져가고, 3분간 침묵하고, diff를 생성했다. 유일한 맥락은 에이전트가 PR에 남긴 한 줄짜리 설명이다. 의도나 추론에 대한 맥락 없이 그 diff를 처음부터 리뷰하는 것은 같은 크기의 사람 PR을 리뷰하는 것보다 5-10배 더 오래 걸린다. 그래서 사람들은 하지 않는다. 몇 가지 핵심 부분만 점검하고 승인한다.
결과는 예측 가능하다. 맥락이 있었다면 잡았을 버그가 빠져나간다. 에이전트가 작은 결정을 내리면서 아키텍처 드리프트가 누적된다. 코드 품질이 미묘하게 저하된다. 깨지지는 않았지만 완전히 올바르지도 않다. 한 파일에서 일관성 없는 에러 핸들링. 작동하지만 확장성이 떨어지는 데이터베이스 쿼리. 에이전트가 존재를 몰랐기 때문에 기존 것을 복제한 새 유틸리티 함수.
이러한 실패는 diff 리뷰에서 보이지 않는다. 에이전트가 무엇을 하려고 했는지 이해하고 그 의도를 실행과 비교할 수 있을 때만 보인다.
Diff가 충분하지 않은 이유
Diff는 텍스트 변경의 기록이다. 그게 전부다. 사람의 코드 리뷰에서 diff가 작동하는 건 리뷰어가 패턴 인식과 공유된 맥락에서 의도를 추론할 수 있기 때문이다. 동료가 try/catch 블록을 추가하는 걸 보고 지난주 버그 리포트의 에러 케이스를 처리하고 있다는 걸 안다. 함수 이름을 바꾸는 걸 보고 팀이 합의한 명명 규칙을 따르고 있다는 걸 안다.
에이전트 생성 코드에서는 추론 과정에 참여하지 않았기 때문에 의도를 추론할 수 없다. 500줄짜리 에이전트 diff가 실제로 알려주는 것:
- 어떤 파일이 수정됐는지
- 어떤 줄이 추가, 변경, 삭제됐는지
- 새 코드의 구문 구조
알려주지 않는 것:
왜 이 접근법이 선택됐는지. 에이전트가 세 가지 다른 구현을 고려했을 수 있다. 하나를 골랐다. 왜인지 모른다. 선택한 것이 최적일 수도 있다. 처음 시도한 것이 충분히 잘 작동했을 뿐일 수도 있다. Diff에서는 알 수 없다.
어떤 대안이 폐기됐는지. 에이전트가 WebSocket 구독 대신 폴링 전략을 선택했다면, 그것이 의도적인 아키텍처 결정이었는지 우연이었는지. Diff는 말해주지 않는다.
구현이 spec과 일치하는지. Spec을 한 창에, diff를 다른 창에 열고 각 수락 기준을 수동으로 교차 참조해야 한다. 대부분은 하지 않는다.
무엇이 어떻게 테스트됐는지. Diff에 새 테스트 파일이 포함될 수 있다. 하지만 에이전트가 실행했는가? 통과했는가? Spec의 엣지 케이스를 커버하는가? 알려면 브랜치를 체크아웃해서 직접 실행해야 한다.
에이전트가 범위 안에 있었는지. 태스크가 "로그인 버그 수정"이었는데 에이전트가 auth 미들웨어도 리팩토링하고, 유틸리티 함수 두 개를 이름 바꾸고, 설정 스키마도 업데이트했을 수 있다. 이 변경사항들은 개별적으로는 문제없어 보인다. 하지만 요청되지 않았고, 명세되지 않았고, 원래 수락 기준에 대해 테스트되지 않았다.
이건 특정 diff 도구의 문제가 아니다. GitHub의 리뷰 인터페이스, GitLab의 머지 리퀘스트, Gerrit, 터미널의 git diff. 모두 같은 것을 보여준다: 무엇이 바뀌었는지. 에이전트 출력에 대해 "무엇이 바뀌었는지"는 가장 덜 중요한 질문이다. 중요한 질문은: 이 변경이 해야 할 것을 하고, 그 외에는 아무것도 하지 않는가?
빠진 층: 구현 내러티브
리뷰어가 실제로 필요한 건 에이전트의 추론 흔적이다. Diff가 아니다. 구현의 이야기: 에이전트가 무엇을 할 계획이었는지, 실제로 무엇을 했는지, 결과를 어떻게 검증했는지. 이것을 구현 내러티브라고 부르자.
좋은 구현 내러티브는 다섯 가지 질문에 답한다:
- 계획이 뭐였는가? 코드를 작성하기 전에 에이전트가 뭘 하려고 했는가? 어떤 파일, 어떤 접근법, 어떤 순서?
- 구현 중에 무슨 일이 있었는가? 계획이 코드베이스와의 접촉을 견뎠는가? 놀라운 일, 방향 전환, 범위 변경이 있었는가?
- 최종 결과가 뭐였는가? Diff가 아니다. 무엇이 바뀌었고 왜 바뀌었는지의 평문 요약.
- 어떻게 검증됐는가? 구현이 작동하는지 확인하기 위해 에이전트가 취한 구체적인 단계. "테스트 통과"가 아니라 "수락 기준 #3을 X를 수행하여 검증하고 Y를 관찰했다."
- 리뷰어가 뭘 확인해야 하는가? 어떤 것이 사람의 주의를 받을 가치가 있는지에 대한 에이전트 자체의 추천. 어느 방향으로든 갈 수 있는 설계 결정이나 세컨드 오피니언을 받을 가치가 있는 성능 트레이드오프가 있을 수 있다.
이 중 어느 것도 표준 PR 워크플로에 존재하지 않는다. PR 설명 필드는 아무도 강제하지 않는 자유 텍스트다. 에이전트 PR은 기본적으로 최소한의 설명을 갖는다. 에이전트에게 구현하라고 했지, 추론을 문서화하라고 하지 않았기 때문이다.
갭은 도구가 아니다. 프로세스다. 리뷰 인프라는 존재한다. 빠져 있는 건 리뷰어가 diff에 대해 확인할 수 있는 에이전트 의도의 구조화된 기록이다.
큰 오버헤드 없이 갭을 메우는 패턴이 있다. 세 부분으로 구성된다: 에이전트가 코드를 작성하기 전에 plan을 코멘트하고, 구현하고, 완료 시 구조화된 done 리포트를 코멘트한다.
1단계: Plan 코멘트
에이전트가 파일을 열기 전에 무엇을 할 것인지 작성한다. 번호가 매겨진 단계, 건드릴 파일, 취할 접근법.
PLAN: Fix WebSocket reconnection dropping messages during
server restart.
1. Add a message buffer to hooks/use-websocket.ts that queues
outbound messages while the connection is in CONNECTING state
2. On successful reconnection, flush the buffer in order
3. Add a 30-second timeout: if reconnection hasn't succeeded,
surface an error to the user via the toast system
4. Update the existing reconnection test to verify buffer
behavior
Files: hooks/use-websocket.ts, components/connection-status.tsx
Test: Unit test for buffer queueing/flushing, manual test by
killing the WS server mid-operation and verifying no messages
are lost on reconnect.
에이전트에게 약 30초가 걸린다. 리뷰어는 2분 이내에 읽는다. 그 2분 안에 diff 리뷰에서 20분 걸릴 문제를 발견한다:
- 범위가 맞는가? (계획은 2개 파일이라고 한다. Diff가 6개를 건드리면 무언가가 벗어났다.)
- 접근법이 건전한가? (flush-on-reconnect가 있는 메시지 버퍼는 합리적이다. 서버에서 전체 상태를 다시 가져오는 걸 제안했다면, 먼저 논의하고 싶을 것이다.)
- 빠진 단계가 있는가? (타임아웃 윈도우 동안 보낸 메시지는 어떻게 되는가? 버려야 하는가 재시도해야 하는가?)
계획은 가벼운 체크포인트다. 리뷰하고, 승인하면, 에이전트가 의도에 대한 공유된 이해를 가지고 진행한다.
2단계: 구현
에이전트가 코드를 작성한다. 여기서 바뀌는 건 없다. 하지만 계획이 존재하기 때문에 에이전트는 임의의 결정 대신 따를 수 있는 로드맵이 있다.
3단계: Done 코멘트
에이전트가 완료하면 구조화된 완료 리포트를 게시한다. "완료"가 아니다. "기능 구현함"이 아니다. 실제로 무엇을 했는지에 대한 상세한 설명.
DONE: WebSocket reconnection now buffers outbound messages
during CONNECTING state and flushes on successful reconnect.
Changes:
- hooks/use-websocket.ts: Added messageBuffer ref, queue
messages when readyState !== OPEN, flush in FIFO order on
'open' event. Added 30s reconnection timeout.
- components/connection-status.tsx: Shows "Reconnecting..."
badge during buffer state, switches to error toast after
timeout.
QA Verification:
1. Start dev server and WebSocket server
2. Open app, verify connection dot is green
3. Kill WebSocket server process
4. Trigger an action that sends a WS message (e.g., update
a bead status)
5. Restart WebSocket server within 30 seconds
6. Verify: the buffered message is delivered, bead status
updates in the UI
7. Repeat step 3, but wait >30 seconds before restart
8. Verify: error toast appears after timeout
Acceptance criteria:
- [x] Messages sent during reconnection are not lost (step 6)
- [x] Timeout surfaces user-visible error (step 8)
- [x] No behavior change when connection is stable (step 2)
Commit: f4e2a1b
이제 리뷰어가 필요한 모든 것을 갖추었다. 계획을 읽고(의도), done 코멘트를 읽고(실제로 무엇이 구축됐고 어떻게 검증됐는지), 그런 다음 전체 맥락과 함께 diff를 확인한다. Diff 리뷰가 "이게 다 뭐지?"에서 "에이전트가 말한 것과 일치하는지 확인하자"로 바뀐다.
에이전트 출력 리뷰 체크리스트
구현 내러티브가 있어도 체계적인 접근이 필요하다. Claude Code 출력을 리뷰할 때 사용하는 체크리스트다. 리뷰당 5-10분이 걸리고 diff만으로는 놓치는 범주의 버그를 잡는다.
Spec 정렬:
- 구현이 spec의 모든 수락 기준을 다루는가?
- Spec이 요청한 것 이상의 변경이 있는가?
- Done 코멘트가 각 기준을 검증 단계에 매핑하는가?
범위 제한:
- 에이전트가 계획에 나열된 파일만 수정했는가?
- 추가 파일을 건드렸다면, 명시된 이유가 있는가?
- 태스크의 일부가 아닌 "정리" 변경(이름 변경, 재포맷, 리팩토링)이 있는가?
테스트 커버리지:
- 새 동작에 대한 새 테스트가 존재하는가?
- 테스트가 실제로 올바른 것을 테스트하는가? (에이전트는 구현이 아닌 mock을 테스트하여 자명하게 통과하는 테스트를 작성하기도 한다.)
- 에이전트가 테스트를 실행했다고 주장하는가? 증거가 있는가?
아키텍처 일관성:
- 변경이 코드베이스의 기존 패턴을 따르는가?
- 기존 것을 복제하는 새로운 추상화가 있는가?
- 에러 핸들링 전략이 프로젝트의 나머지와 일치하는가?
의존성 인식:
- 에이전트가 의존성을 추가했다면, 정당화되는가?
- 변경이 기존 기능을 깨뜨리는가? (수정된 모듈을 임포트하는 파일 확인.)
- 태스크가 다른 태스크에 의존성이 있다면, 그 의존성이 해결됐는가?
이 체크리스트는 어떤 코드 리뷰 도구에서도 작동한다. 포스트잇에 인쇄하거나, PR 템플릿에 넣거나, CLAUDE.md에 구축하여 에이전트가 측정될 기준을 알게 하라. 핵심은 개별 항목이 아니다. "좋아 보인다" 대신 구조화된 프로토콜을 갖는 것이다.
리뷰 표면으로서의 Beads
Plan-comment-done 패턴은 살 곳이 필요하다. 계획과 done 코멘트가 Slack 메시지, PR 설명, 터미널 출력에 흩어져 있으면, spec, 계획, 구현, 검증 사이의 연결이 끊어진다.
이것이 beads가 해결하는 문제다. Beads는 오픈소스 Git 네이티브 이슈 트래커로, 각 "bead"가 태스크의 전체 라이프사이클을 담는다: 설명으로서의 spec, 코멘트로서의 에이전트 계획, 코멘트로서의 done 리포트, 코멘트로서의 QA 결과. 모두 하나의 엔티티에 첨부되고, 검색 가능하고, 영구적이다.
bd CLI로의 리뷰 워크플로:
Spec으로 태스크 생성:
bd create --title "Fix WebSocket reconnection message loss" \
--description "## Problem
Messages sent during WebSocket reconnection are silently
dropped...
## Acceptance Criteria
1. Messages queued during CONNECTING state are delivered
on reconnect
2. 30-second timeout surfaces error to user
3. No behavior change when connection is stable" \
--type bug --priority p1
에이전트가 작업을 클레임하고 계획을 게시:
bd update bb-f4e2 --claim --actor eng1
bd comments add bb-f4e2 --author eng1 "PLAN: Add message
buffer to WebSocket hook...
1. Queue outbound messages when readyState !== OPEN
2. Flush buffer in FIFO order on 'open' event
3. Add 30s timeout with error toast
4. Update reconnection test
Files: hooks/use-websocket.ts, components/connection-status.tsx"
2분 안에 계획을 리뷰:
bd show bb-f4e2 # Spec + plan 코멘트 읽기
계획이 올바르면 에이전트가 진행한다. 그렇지 않으면 코드가 작성되기 전에 수정 사항을 코멘트한다.
에이전트가 완료하고 done 리포트를 게시:
bd comments add bb-f4e2 --author eng1 "DONE: WebSocket
reconnection now buffers outbound messages...
QA Verification:
1. Kill WS server, trigger action, restart within 30s...
Acceptance criteria:
- [x] Buffered messages delivered on reconnect
- [x] Timeout error visible
- [x] No regression on stable connection
Commit: f4e2a1b"
bd update bb-f4e2 --status ready_for_qa
QA가 독립적으로 검증:
bd show bb-f4e2 # Done 코멘트의 검증 단계 읽기
# 각 단계 실행
bd comments add bb-f4e2 --author qa1 "QA PASS: All 3 criteria
verified. Buffer flushes correctly, timeout fires at 30s,
stable connections unaffected."
전체 리뷰 흔적이 한 곳에 있다. 6개월 후 누군가 "WebSocket이 재연결 중에 왜 메시지를 버퍼링하는가?"라고 물으면, 답은 bead에 있다. Spec이 문제를 설명하고, 계획이 접근법을 설명하고, done 코멘트가 무엇이 구축됐는지 설명하고, QA 코멘트가 작동함을 확인한다.
터미널 리뷰가 한계에 도달할 때
한 태스크에 bd show를 실행하면 모든 것을 얻는다. 하지만 여러 병렬 워크스트림에 걸쳐 여러 에이전트의 출력을 리뷰할 때, CLI 워크플로는 선형적으로 확장된다: 태스크마다 bd show 한 번, 리뷰 준비된 것을 보려면 bd list 한 번, 승인해야 할 계획마다 bd show 한 번.
여기서 Beadbox가 들어맞는다. Beadbox는 실시간 대시보드로, 워크스페이스의 모든 태스크를 현재 상태, 최신 코멘트, 리뷰 파이프라인에서의 위치와 함께 보여준다. 어떤 에이전트가 승인이 필요한 계획을 게시했는지. 어떤 것이 리뷰 준비된 done 리포트를 게시했는지. 어떤 것이 아직 진행 중인지. 에이전트가 bd CLI로 코멘트를 작성하고 상태를 변경함에 따라 모든 것이 실시간으로 업데이트된다.
Plan-comment-done 패턴을 사용하는 데 Beadbox가 필요하지는 않다. CLI가 전체 워크플로를 처리한다. 하지만 5개의 에이전트가 동시에 리뷰 가능한 출력을 생산할 때, 각 태스크를 개별적으로 폴링하는 대신 리뷰 큐를 한눈에 볼 수 있다는 것이 파이프라인을 통과하는 속도를 바꾼다.
Beadbox는 베타 기간 동안 무료이며, 기반이 되는 beads CLI는 오픈소스다.
리뷰 문제는 저절로 해결되지 않는다
AI 생성 코드는 우리의 리뷰 능력보다 빠르게 증가하고 있다. 우리가 가진 도구는 다른 규모와 다른 워크플로를 위해 만들어졌다. GitHub PR, IDE diff, 정교한 정적 분석까지. 근본적인 문제를 해결하는 것은 없다. 저자의 의도를 모르고 코드를 리뷰하는 것이 의도를 알고 리뷰하는 것보다 극적으로 어렵다는 것이다.
해결책은 더 나은 diff 도구가 아니다. 구조화된 의도다. 에이전트가 무엇을 계획했는지, 실제로 무엇을 했는지, 결과를 어떻게 검증했는지의 기록이다. Plan-comment-done 패턴은 큰 오버헤드 없이 그 기록을 제공한다. 에이전트가 계획 작성에 30초. 리뷰에 2분. 에이전트가 done 리포트 작성에 60초. 처음부터가 아닌 전체 맥락과 함께 diff를 리뷰한다.
가져갈 다섯 가지 원칙:
-
코드 전에 계획을 요구하라. 30초 plan 코멘트가 20분 리뷰 세션을 절약한다. 계획이 잘못됐으면 코드가 존재하기 전에 수정하라.
-
구조화된 done 리포트를 요구하라. "완료"는 done 리포트가 아니다. 검증 단계, 수락 기준 매핑, 커밋 해시가 done 리포트다.
-
Diff가 아닌 spec에 대해 리뷰하라. Diff는 무엇이 바뀌었는지 보여준다. Spec은 무엇이 바뀌어야 했는지 말한다. 교차 참조하라.
-
범위 경계를 강제하라. 에이전트가 계획 외의 파일을 건드렸다면 그것은 경고 신호다. 계획되지 않은 변경은 리뷰되지 않은 변경이다.
-
리뷰를 판단이 아닌 프로토콜로 다루라. 체크리스트가 직감보다 더 많은 버그를 잡는다. "좋아 보인다"는 리뷰가 아니다.
에이전트는 계속 빨라질 것이다. Diff는 계속 커질 것이다. 질문은 리뷰 프로세스가 그것에 맞춰 확장되는지, 아니면 여전히 600줄짜리 diff를 힐끗 보며 최선을 바라고 있는지다.
이런 워크플로를 구축하고 있다면, GitHub에서 Beadbox에 스타를 달아주세요.