Back to all posts

130만 줄, 주당 630개 PR: 바이브 코딩된 코드베이스를 관리하는 법

130만 줄, 주당 630개 PR: 바이브 코딩된 코드베이스를 관리하는 법

2026년 7월, Robert C. Martin은 자신의 코딩 에이전트가 만들어낸 코드를 더 이상 읽지 않는다고 썼다.

이 발언은 많은 주목을 받았다. 하지만 그가 일하는 방식을 이해하려면 글의 나머지 부분이 더 중요하다. Martin은 에이전트 주위를 단위 테스트, Gherkin 인수 테스트, QA 절차, 뮤테이션 테스트, 커버리지 검사, 품질 지표로 둘러싼다. 코드는 그 체계를 통과함으로써 그의 신뢰를 얻는다.

몇 달 전에도 그는 비슷한 이야기를 했다. 구현을 들여다보는 대신 테스트 커버리지, 의존성 구조, 순환 복잡도, 모듈 크기, 뮤테이션 테스트 결과를 본다는 것이다. 이를 리뷰 자체를 포기한 것으로 해석하는 사람들이 나오자 그는 이렇게 덧붙였다. "리뷰는 아주 많이 한다. 다만 코드를 읽지 않을 뿐이다."

그의 언급은 AI 비중이 큰 소프트웨어 개발의 실질적인 문제를 짚는다. 코딩 에이전트는 사람이 읽을 수 있는 속도보다 빠르게 변경을 만들어낸다. 생성되는 코드의 양이 팀의 리뷰 역량을 넘어서는 순간, 한 줄씩 확인하는 방식만으로 신뢰를 지탱할 수는 없다.

vm0에서도 지난 여덟 달 동안 같은 문제와 씨름해왔다. 저장소 구현의 대부분은 에이전트가 작성하며 — 흔히 부르는 말로는 바이브 코딩입니다 — 그 코드가 프로덕션에서 하는 일에 대해서는 여섯 명의 엔지니어가 계속 책임을 집니다.

짧은 요약

vm0는 바이브 코딩된 코드베이스입니다. 구현의 대부분은 에이전트가 쓰고, 프로덕션에서의 동작은 여섯 명의 엔지니어가 책임집니다. 여덟 달 시점에 저장소는 1,329,170줄이고, 한 주 동안 630개의 풀 리퀘스트가 병합됐습니다. 줄 단위 리뷰만으로는 감당할 수 없는 품질을 다섯 가지 장치가 떠받칩니다.

  • 수렴된 환경. 엔지니어와 에이전트가 같은 dev container에서 작업하고, 풀 리퀘스트마다 전용 데이터베이스 브랜치를 가질 수 있습니다. 에이전트에게 통한 명령은 사람에게도 통합니다.
  • 실행 가능한 제약. 엄격한 TypeScript, 약 135개의 타입이 붙은 API 계약 모듈, Oxlint, ESLint, 아키텍처 규칙, 그리고 경고를 하나도 허용하지 않는 CI. 기계로 확인할 수 있는 규칙은 리뷰에 맡기지 않습니다.
  • 경계에서의 테스트. 피라미드가 아니라 테스팅 트로피입니다. 모듈의 공개 경계를 통한 통합 테스트, 실제 PostgreSQL과 실제 마이그레이션, 모의 객체는 외부 시스템에만. 테스트 코드는 저장소의 약 36퍼센트입니다.
  • 완결된 피드백 루프. 에이전트가 애플리케이션과 데이터베이스를 띄우고 마이그레이션을 돌리고 실제 브라우저를 조작한 뒤, 실행한 명령과 검증한 경로와 스크린샷을 보고합니다. 풀 리퀘스트 병합 시간의 중앙값은 약 53분입니다.
  • 지속적인 정리. Knip이 죽은 코드를 결정적으로 걷어내고, 매일 도는 워크플로가 린터로는 이름 붙일 수 없는 AI 슬롭을 치우며, 충분히 자주 재발하는 패턴은 린트 규칙이나 타입 제약으로 올라갑니다.

바이브 코딩과 에이전틱 코딩의 실제 의미

바이브 코딩은 LLM에 코드를 요청하고, 나온 것을 실행하고, 수정을 요청하되, 생성된 코드 자체에는 주의를 두지 않는 방식입니다. Martin Fowler의 정의는 의도적으로 좁고, "코드가 존재한다는 사실조차 잊는다"는 표현을 강조합니다. 프로토타입, 일회성 소프트웨어, 영향이 작은 도구에 어울립니다.

에이전틱 코딩은 Fowler가 에이전틱 프로그래밍이라 부르는 것으로, 결과를 유지보수할 팀이 택하는 방식입니다. 에이전트가 저장소를 읽고 파일을 고치고 테스트를 돌리며 오랜 시간 스스로 일하는 동안, 사람은 구조와 동작을 계속 책임지고 실행이 남긴 증거를 검토합니다. 테스트 결과, 품질 신호, 프리뷰, 프로덕션 동작입니다.

차이는 모델이 얼마나 많이 썼는지가 아니라, 사람의 주의가 어디로 가는지입니다.

바이브 코딩에이전틱 코딩
에이전트 자율성프롬프트, 실행, 다시 프롬프트저장소를 읽고 고치고 테스트하고 반복
코드를 읽는 주체없음위험을 지닌 부분은 사람이 읽음
검증 대상결과가 맞아 보이는지타입, 계약, 테스트, 프리뷰, 프로덕션 신호
적합한 상황프로토타입과 일회성 도구유지보수 기간이 있는 소프트웨어
전형적 실패아무도 이해하지 못하는 코드물량에 비해 너무 약한 검증

저장소를 편집하는 에이전트는 더 큰 변화의 한 갈래일 뿐입니다. 비개발자 쪽 이야기는 따로 정리했습니다.

vm0를 바이브 코딩된 프로젝트라고 부르는 이유는 그 표현이 "대부분 AI가 쓴 소프트웨어"의 통칭이 됐기 때문입니다. Fowler의 용어로는 에이전틱 코딩에 가깝습니다. 엔지니어가 직접 타이핑하는 구현은 예전보다 줄었지만, 아키텍처와 유지보수 비용과 프로덕션 동작은 여전히 우리 몫입니다.

코드 생성이 빨라질수록 그 책임의 상당 부분은 개발 환경, 타입 시스템, 테스트 스위트, 자동화된 유지보수 워크플로로 옮겨갔습니다.

바이브 코딩된 코드베이스의 여덟 달

vm0 저장소는 2025년 11월에 만들어졌다. 가장 최근 주간 엔지니어링 규모 리포트 시점에 대략 여덟 달 반이 지난 상태였다.

저장소의 내용은 다음과 같았다.

지표수치
비어 있지 않은 논리 줄1,329,170
프로덕션 줄850,913
테스트 줄478,257
소스 파일5,549
테스트 파일1,327
패키지44
main 브랜치 커밋14,005

이 수치는 2026년 7월 20~26일 주간에 대해 모노레포에서 직접 측정한 주간 엔지니어링 스케일 리포트에서 가져왔습니다.

테스트 코드는 측정된 코드베이스의 약 36퍼센트를 차지했다. 이는 코드 분량의 비중이지 테스트 커버리지 수치는 아니지만, 검증 주변에 얼마나 많은 구현이 쌓였는지는 가늠하게 해준다.

최근 만 3주 동안 엔지니어 6명이 각각 541, 640, 631건의 커밋을 남겼다. 7월 20일부터 26일까지의 주간에 GitHub은 630건의 병합된 풀 리퀘스트를 기록했다. 그중 556건은 엔지니어 6명이 작성했고, 나머지 74건은 릴리스 자동화가 작성했다.

풀 리퀘스트를 연 뒤 병합되기까지의 중앙값은 약 53분이었다. 90번째 백분위수는 약 7.4시간이었다.

이 정도의 변경 속도에서 사람의 리뷰는 여전히 유용하지만 품질 체계 전체를 떠받칠 수는 없다. 변경의 생애 주기 여러 지점에서 독립적인 신호가 필요하다.

지금 우리의 접근에는 다섯 가지 반복되는 주제가 있다. 환경의 수렴, 실행 가능한 제약, 경계 중심 테스트, 완결된 피드백 루프, 그리고 지속적인 정리다.

dev container로 개발 환경을 수렴시키기

재현하기 어려운 장애의 상당수는 풀 리퀘스트에 결코 드러나지 않는 상태에서 비롯된다.

개발자에게는 문서화되지 않은 환경 변수, 전역으로 설치된 도구, 오래된 설정 파일, 몇 달째 돌고 있는 데이터베이스가 있을 수 있다. 사람은 이런 세부에 익숙해진다. 에이전트는 대개 그것들을 볼 수 없다.

우리는 호스트 머신을 표준 개발 환경으로 취급하는 것을 점차 그만두었다. 엔지니어와 에이전트는 dev container 안에서 작업한다. 개발 이미지에는 프로젝트 툴체인, PostgreSQL, pgvector, Chromium, 브라우저 자동화 유틸리티가 들어 있다.

CI는 동일한 멀티스테이지 Dockerfile 계열에서 빌드된, 버전이 고정된 툴체인 이미지 위에서 돈다. 개발 이미지와 CI 이미지는 목적이 다르고, 프로덕션은 또 다른 배포 형태를 가진다. 유용한 성질은 수렴이다. 도구 버전, 의존성, 런타임 전제가 명시적이고 버전 관리된다.

여기서 단순한 기대가 나온다. 에이전트가 실행하는 명령은 같은 개발 컨테이너 안에서 엔지니어가 재현할 수 있어야 한다. 개발 중에 통과한 검사는 CI에서도 아주 유사한 툴체인 아래에서 돌아야 한다.

데이터에도 비슷한 발상을 적용한다. 풀 리퀘스트 프리뷰마다 자체 데이터베이스 브랜치를 두고 실제 마이그레이션과 시드 단계를 실행할 수 있다. 에이전트는 데이터를 만들고 바꾸고 파괴적인 테스트를 반복할 수 있다. 개발자의 기존 데이터베이스를 빌리지도, 다른 풀 리퀘스트의 상태를 물려받지도 않는다.

환경 변경은 저장소를 통해 전달된다. 도구 업그레이드, 브라우저 버전, 데이터베이스 확장은 애플리케이션 코드와 같은 방식으로 리뷰되고 배포된다.

타입과 린트 규칙으로 엔지니어링 기준을 실행 가능하게 만들기

글로 쓰인 기준은 사람이 설계 결정을 이해하는 데 도움이 된다. 하지만 모든 변경이 그 결정을 따르도록 보장하는 힘은 훨씬 약하다. 긴 에이전트 세션에서는 특히 그렇다.

안정적인 규칙은 신뢰할 수 있게 표현할 수 있는 한 타입 시스템, 린터, CI에 넣는다.

vm0은 strictnoUncheckedIndexedAccess를 포함한 엄격한 TypeScript 설정을 쓴다. 일부 패키지는 사용되지 않은 값과 암시적 반환에 대한 추가 검사도 켠다.

API에는 tRPC의 타입 기계와 Zod 스키마로 만든, 스키마 우선의 타입 지정 REST 계약 계층을 쓴다. Drizzle이 데이터베이스 접근과 TypeScript 타입을 연결한다. 이 검토 시점에 저장소에는 약 135개의 계약 모듈이 있었다.

이런 선택이 잘못된 제품 결정을 잡아주지는 않는다. 다만 인터페이스 어긋남을 일찍 드러낸다. 필드나 응답이 바뀌면 관련 호출부는 대개 정적 분석 단계에서 실패한다. 그때 나오는 오류는 보통 에이전트가 다음 반복에 쓸 수 있을 만큼 구체적이다.

린팅은 두 번째 규칙 묶음을 담당한다. 플랫폼은 Oxlint, 타입 인식 검사, ESLint에 더해 프로젝트 고유의 아키텍처 규칙을 실행한다. CI는 경고를 하나도 허용하지 않는다. 무기한 남아 있어도 되는 경고는 배경 소음이 되기 쉽고, 배경 소음은 사람에게도 에이전트에게도 무시하기 쉽다.

문제가 반복되면 그 검사가 어디에 속하는지 따져본다.

  1. 타입 시스템이 표현할 수 있는가?
  2. 린터나 구조 분석 도구가 정확히 탐지할 수 있는가?
  3. 저장소 전체에 걸친 의미적 판단이 필요한가?

앞의 둘은 모든 변경에 대해 빠르고 결정적인 피드백을 준다. 셋째 묶음은 이 글 뒷부분에서 설명할 반복 워크플로로 다룬다.

AI 생성 코드에서 오류를 부르기 쉬운 패턴 좁히기

일부 언어 기능은 정당한 쓰임새를 가지면서도, 취약한 에이전트 생성 코드에 자주 등장한다.

try/catch는 오류 경계를 흐릴 수 있다. 에이전트가 실패를 만났을 때 catch 블록과 폴백을 덧붙이는 것은, 원래 오류를 잃어버리면서 현재 경로를 계속 살려두는 손쉬운 방법이 된다. Promise의 .then().catch()async/await와 섞으면 제어 흐름이 여러 스타일에 흩어진다.

React의 useEffect는 상태 관리에서 비슷한 문제를 만든다. 상태를 복사하거나, 두 개의 진실 원본을 동기화하거나, 데이터 모델에서 알아보기 어려운 순서 의존성을 인코딩하는 데 자주 쓰인다.

핵심 웹 플랫폼은 이런 패턴을 기본적으로 제한한다. 정당한 예외는 그 이유를 옆에 명시한 채로 남길 수 있다.

핵심 웹 플랫폼의 프로덕션 코드에는 현재 useEffect가 하나도 없다. 의존성과 부수 효과를 더 명시적으로 모델링하기 위해 ccstate를 비롯한 무효과 패턴을 쓴다. 모노레포의 다른 곳, 주로 공용 UI와 데스크톱 코드에는 소수의 프로덕션 useEffect 호출이 여전히 남아 있으므로 주장의 범위가 중요하다.

이 규칙들은 이 저장소에서 반복된 실패에서 자랐다. 어떤 패턴이 같은 유지보수 문제를 거듭 만들어내면, 우리는 그것을 리뷰 지침에서 실행 가능한 제약으로 옮긴다.

모듈 경계에서 테스트하기: 피라미드가 아니라 테스팅 트로피

vm0은 통상적인 테스트 피라미드를 따르지 않는다. 우리 테스트 가이드는 테스팅 트로피를 설명한다. 바닥에 정적 분석, 지배적인 층에 통합 테스트, 그리고 중요한 사용자 여정을 위한 소수의 엔드투엔드 테스트다.

단위 테스트는 상대적으로 드물다. 보안에 민감한 로직, 알고리즘, 상태 기계에 선별적으로 쓴다. 대부분의 비즈니스 동작은 모듈의 공개 경계를 통해 테스트한다.

API 테스트는 계약을 통해 실제 애플리케이션을 호출한다. 테스트 데이터는 실용적인 범위에서 프로덕션 엔드포인트로 준비하고, 단언은 공개 동작에 대해 수행한다. 테스트는 내부 서비스에 직접 손을 뻗거나 데이터베이스 테이블을 수정하지 않는다. 그렇게 하면 현재 구현에 결합되기 때문이다.

내부 인프라는 비용이 합리적인 범위에서 실제 그대로 둔다.

  • PostgreSQL과 pgvector
  • 데이터베이스 마이그레이션
  • 파일 시스템
  • 내부 서비스
  • 외부 시스템 경계에서의 목

이런 통합 테스트는 고도로 격리된 단위 테스트보다 느리게 돌고 더 완전한 환경을 요구한다. 대신 "테스트가 통과했다"와 "애플리케이션이 실제 데이터베이스로 이 작업을 수행할 수 있다" 사이의 간극을 줄여준다.

경계 테스트는 대규모 내부 리팩터링의 여지를 남긴다. 에이전트는 모듈을 재구성하고, 서비스를 쪼개고, 데이터 접근 계층을 바꿀 수 있다. 그동안에도 공개 동작은 보호된다.

Uncle Bob은 단위 테스트, Gherkin, 뮤테이션 테스트를 무겁게 쓰는 다른 배합을 선호한다. 공유되는 원칙은 독립적인 검증이다. 코드를 생성한 과정이 그 코드가 동작한다고 주장하는 유일한 근거여서는 안 된다.

코딩 에이전트에게 완결된 피드백 루프 주기

초기 코딩 에이전트 실행은 익숙한 보고로 끝나는 일이 많았다. 코드를 바꿨고 TypeScript는 통과했습니다. 애플리케이션을 띄워 페이지를 확인해 주세요.

그러면 개발 루프의 절반이 엔지니어에게 남는다. 누군가는 여전히 데이터베이스를 준비하고, 서비스를 띄우고, 브라우저를 열고, 데이터를 만들고, 실패를 관찰하고, 그것을 에이전트에게 다시 설명해야 한다.

이제 에이전트에게는 그 작업의 상당 부분을 직접 수행할 만큼의 개발 환경이 있다. 애플리케이션과 데이터베이스를 띄우고, 마이그레이션을 실행하고, 테스트 데이터를 만들고, 풀 리퀘스트 프리뷰를 방문하고, 실제 브라우저 조작을 수행할 수 있다.

브라우저 검증은 타입 검사와 API 테스트가 잘 다루지 못하는 부류의 문제를 잡아낸다. 깨진 내비게이션, 끝나지 않는 로딩 상태, 완전한 흐름에서만 나타나는 권한 오류, 그리고 시각적 회귀다.

실행이 끝나면 에이전트는 실행한 명령, 테스트한 경로, 관찰한 내용을 보고한다. UI 변경이라면 스크린샷도 넣을 수 있다. 엔지니어는 그 증거를 살펴본 뒤 직접 프리뷰를 열지 판단할 수 있다.

스크린샷은 테스트가 아니며 다른 결함이 없다는 증명도 아니다. 다만 에이전트의 맥락을 재구성하는 비용을 낮춘다. 작은 UI 변경이라면 재현 가능한 절차와 마지막 스크린샷이 "고쳐졌을 것"이라는 메시지보다 훨씬 유용하다.

에이전트가 만들어내는 결과의 품질은 사람을 기다리지 않고 얻을 수 있는 피드백에 크게 좌우된다.

트렁크 기반 개발로 브랜치를 짧게 유지하기

빠른 코드 생성은 많은 브랜치 재고를 만들 수 있다.

오래 사는 기능 브랜치는 병합 충돌, 중복 작업, 낡은 맥락을 쌓는다. 풀 리퀘스트가 커질수록 리뷰는 어렵고 느려진다. 우리는 트렁크 기반 개발로 브랜치를 짧게 유지하고 main을 중심으로 계속 통합한다.

메인 브랜치 규칙은 다음을 요구한다.

  • 변경에는 풀 리퀘스트를 사용한다
  • 선형 히스토리와 스쿼시 병합
  • 머지 큐
  • Turbo, Rust, 보안 검사
  • 필수 게이트의 관행적 우회 금지

자동화도 같은 경로를 따른다. 에이전트가 풀 리퀘스트를 만들 수 있고, 위험이 낮은 일부 유지보수 작업은 자동 병합을 켤 수 있지만, 변경은 여전히 CI와 머지 큐를 통과한다.

풀 리퀘스트의 크기와 수명은 한 주에 630건이 병합될 수 있었던 이유를 어느 정도 설명해준다. 작은 변경은 맥락이 적고, 검증하기 쉬우며, 제품 작업이나 다른 자동 수리와 충돌할 가능성이 낮다.

저장소의 가비지 컬렉션으로서의 Knip: 죽은 코드 걷어내기

코드 생성은 자연스럽게 파일과 추상을 늘린다. 삭제에는 별도의 프롬프트가 필요한 경우가 많다.

리팩터링 뒤에는 오래된 파일이 저장소에 남을 수 있다. 기능을 제거해도 익스포트, 의존성, 엔트리 포인트가 남을 수 있다. 이런 잔여물은 테스트를 깨뜨리는 일이 드물지만, 시간이 지나면 코드베이스를 탐색하기 어렵게 만든다.

우리는 Knip으로 사용되지 않는 파일, 익스포트, 의존성, 엔트리 포인트를 찾는다. TypeScript는 코드가 유효하다고 확인해줄 수 있다. Knip은 그것이 여전히 시스템에 참여하고 있는지를 묻는다.

이 잔여물은 코딩 에이전트에게 추가 비용이 된다. 저장소는 그들에게 가장 중요한 맥락 원천 중 하나다. 낡은 헬퍼나 버려진 구현은 다음에 그것을 읽는 에이전트에게 승인된 패턴처럼 보일 수 있다.

죽은 코드를 제거하면 앞으로의 실행에 주어지는 입력도 좋아진다. Knip은 이 작업의 결정적인 부분을 일상적인 품질 검사로 삼을 만큼 빠르게 처리한다.

AI 슬롭을 정리하는 반복 워크플로

Knip과 ESLint에는 분명한 한계가 있다. 많은 형태의 퇴화는 프로젝트 맥락과 의미적 판단을 필요로 한다.

우리는 이 잔여물을 가리키는 실무적 이름으로 "AI 슬롭"을 쓴다. 불필요한 폴백, 중복된 추상, 공개 경계를 우회하는 테스트, 불가능한 상태를 위한 방어적 분기 같은 것들이다. 개별 사례는 무해해 보일 수 있다. 하지만 모이면 저장소를 이해하기 어렵게 만들고, 미래의 에이전트에게 나쁜 본보기를 준다.

vm0의 여러 반복 워크플로가 이런 패턴을 훑는다.

일일 AI 슬롭 정리는 새 잔여물을 찾아 확신도가 높고 위험이 낮은 수정을 소수만 고른다. 다른 워크플로들은 내부 서비스로 손을 뻗는 API 테스트, React와 ccstate 안티패턴, 안전하게 처리할 수 있는 기술 부채를 점검한다.

각 워크플로는 변경 범위를 좁게 유지한다. 풀 리퀘스트를 열고, 그다음은 평소의 타입 검사, 린트 규칙, 테스트, 머지 큐에 맡긴다. 자동 병합이 설정된 풀 리퀘스트도 같은 게이트를 통과해야 한다.

스캔에서 CI와 병합까지 이어지는 자동 저장소 유지보수 워크플로. 실패 시 수리가 동작한다

이 워크플로들은 한 번에 모든 기술 부채를 없애려 하지 않는다. 매일의 작은 묶음이 몇 달에 한 번의 대청소보다 검증하기 쉽고 덜 어수선하다.

반복 워크플로가 같은 패턴을 충분히 자주 발견하면, 그 검사를 ESLint나 Knip, 혹은 타입 시스템으로 옮기는 것을 검토한다. 의미적 워크플로는 더 저렴한 결정적 검사로 바꾸기 전에 규칙을 관찰하고 다듬는 자리 역할을 한다.

이런 반복 vm0 워크플로는 일정에 따라 실행됩니다.

불안정한 테스트 실패를 자동 수리로 바꾸기

또 다른 워크플로 묶음은 GitHub Actions의 실패에서 출발한다.

메인 브랜치나 머지 큐에서 테스트가 실패하면, 워크플로가 로그를 읽고 플레이키함의 증거를 찾으며 재시도, 타이밍, 환경 요인을 살핀다. 증거가 특정한 수리를 뒷받침하면 테스트나 구현을 갱신하고, 풀 리퀘스트를 열고, 전체 CI 경로를 다시 돌린다.

재시도에서 통과했다고 원래의 실패가 무해해지지는 않는다. 재시도 버튼에 의존하는 팀은 빨간 빌드에 대한 신뢰를 조금씩 잃는다. 그렇게 되면 실패한 검사도 또 하나의 배경 소음이 된다.

자동 수리 워크플로는 간헐적 실패를 추적 가능한 코드 변경으로 바꾼다. 진단과 패치, 검증이 풀 리퀘스트에 그대로 보인다. 위험이 큰 변경은 엔지니어가 살펴볼 수 있고, 증거가 탄탄한 좁은 수정은 머지 큐를 통과할 수 있다.

현재 우리의 품질 체계는 크게 세 개 층으로 이뤄져 있다.

단계메커니즘주요 관심사
작성할 때TypeScript, 계약, Drizzle, ESLint타입 오류, 인터페이스 어긋남, 알려진 코드 패턴
병합 전Knip, 통합 테스트, 실제 데이터베이스, 프리뷰, 머지 큐죽은 코드, 모듈 동작, 완전한 런타임 결과
병합 후예약형·이벤트 구동형 vm0 워크플로AI 슬롭, 의미적 안티패턴, 플레이키 테스트, 아키텍처 어긋남

층들은 서로를 먹인다. 워크플로가 찾은 문제는 정적 규칙이 될 수 있다. CI나 프로덕션에서 발견된 실패는 테스트와 새로운 엔지니어링 지침이 될 수 있다.

에이전트를 계속 돌리는 데에는 비용이 듭니다. 비용을 줄이는 방법은 별도로 썼습니다.

AI 생성 코드를 리뷰할 때 사람의 주의는 어디로 가는가

vm0 엔지니어들은 여전히 코드를 읽는다. 특히 아키텍처 변경, 보안에 민감한 작업, 결제, 데이터 마이그레이션에서 그렇다. "절대 코드를 읽지 말라"를 팀 규칙으로 삼지는 않았다.

달라진 것은 주의의 배분이다. 코드를 읽는 일은 계약, 테스트 경계, 프리뷰 동작, 스크린샷, 품질 지표, 워크플로 진단과 나란히 놓인 하나의 신호가 되었다.

여러 판단은 여전히 숙련된 안목을 요구한다.

  • 요구사항이 완전한지
  • 모듈 경계를 어디에 두어야 하는지
  • 어떤 실패가 복구 가능한지
  • 결함이 얼마만큼의 사업 영향을 만들 수 있는지
  • 보안 모델이 적절한지
  • 지금의 규칙이 다루지 못하는 새로운 실패 패턴은 무엇인지

엔지니어는 에이전트를 둘러싼 환경도 관리한다. 문제가 반복되면 타입 제약, 린트 규칙, 테스트, 반복 워크플로 중 무엇을 더할지 결정한다. 개발 시스템 자체가 중요한 엔지니어링 산출물이 되었다.

읽기 좋은 코드는 여전히 중요하다. 다음 독자는 엔지니어일 수도, 또 다른 에이전트일 수도 있다. 엉킨 코드는 더 많은 맥락을 소모하고, 이후 변경의 범위를 넓히며, 검증의 신뢰도를 떨어뜨린다.

디자인 쪽에서도 비슷한 변화가 있었습니다. design-as-code를 통해 시각적 결정이 같은 저장소와 같은 리뷰 경로로 들어왔습니다.

보안, 그리고 속도가 붙지 않는 변경들

속도는 고르게 분배되지 않습니다. vm0에는 자기 속도로 가는 변경 목록이 짧게 있습니다. 보안에 민감한 작업, 결제, 데이터 마이그레이션, 아키텍처 결정입니다. 누가 혹은 무엇이 썼든, 이런 변경은 엔지니어가 한 줄씩 읽습니다.

AI 생성 코드의 보안 위험은 우리 경험상 특이한 취약점보다는 그럴듯하지만 아무도 책임지지 않는 코드에 가깝습니다. 결국 효과를 내는 통제는 평범한 것들이며, 다만 일관되게 적용될 때 그렇습니다.

  • 평소에는 아껴 쓰는 단위 테스트를 보안에 민감한 로직, 알고리즘, 상태 머신에는 의도적으로 사용합니다.
  • 테스트는 외부 시스템 경계에서만 모의합니다. 내부 인프라는 실제로 두어, 내부 계약을 깨는 변경이 프로덕션이 아니라 CI에서 실패하게 합니다.
  • 에이전트는 dev container 안에서 풀 리퀘스트마다 만들어지는 데이터베이스 브랜치를 상대로 작업합니다. 개발자 머신도, 공유 데이터베이스도 아닙니다.
  • 머지 큐와 필수 체크에는 관행적인 우회로가 없습니다. 에이전트가 열고 자동 병합으로 표시한 풀 리퀘스트도 마찬가지입니다.
  • try/catch는 기본적으로 제한합니다. 당장의 경로를 살리려고 에이전트가 덧붙인 폴백에 실패가 삼켜지지 않도록 하기 위해서입니다.

자격 증명 처리는 별도의 설계 문제이고 답도 따로 있습니다. 토큰을 에이전트의 손이 닿지 않는 곳에 두는 브로커 패턴은 별도의 글에서 설명했습니다.

이 중 어떤 것도 생성된 코드를 그 자체로 안전하게 만들지는 않습니다. 사람의 판단이 유일한 통제인 변경의 범위를 좁히고, 그 범위를 분명히 드러낼 뿐입니다.

이 수치들이 보여주지 않는 것

주간 리포트는 저장소의 규모와 전달 속도를 보여준다. 그 자체로 프로덕션 신뢰성을 증명하지는 않는다.

런타임 품질에 대한 영향을 평가하려면 가용성, 프로덕션 오류율, 인시던트 건수, 변경 실패율, 롤백 빈도, 평균 복구 시간이 필요하다. CI가 초록인 것은 전달 과정의 한 부분을 말해줄 뿐이다.

우리는 그런 결과 지표를 계속 갖춰가고 있다. 엔지니어링 관행은 시스템이 위험을 어떻게 다루는지 설명하고, 프로덕션 데이터는 그 관리가 얼마나 잘 작동하는지 보여준다.

자주 묻는 질문

바이브 코딩이란 무엇인가요 바이브 코딩은 LLM에 코드를 요청하고, 돌아온 것을 실행하고, 수정을 요청하되 생성된 코드를 읽지 않는 방식입니다. Fowler의 정의는 일부러 좁아서, "코드가 존재한다는 사실조차 잊는" 상태를 전제합니다. 프로토타입, 일회성 소프트웨어, 영향이 작은 도구에 맞습니다.

에이전틱 코딩이란 무엇인가요 에이전틱 코딩은 더 오래 실행되는 형태의 AI 지원 개발입니다. 에이전트가 저장소를 읽고 파일을 고치고 테스트를 돌리며 긴 시간 스스로 반복합니다. 사람은 아키텍처와 동작을 계속 책임지고, 쓰인 모든 줄이 아니라 실행이 남긴 증거를 검토합니다.

바이브 코딩과 에이전틱 코딩은 어떻게 다른가요 저작자가 아니라 주의의 문제입니다. 바이브 코딩에서는 코드를 검사하지 않습니다. 에이전틱 코딩에서는 에이전트가 스스로 일하는 동안 엔지니어가 주변 증거를 확인합니다. 테스트 결과, 품질 신호, 프리뷰, 프로덕션 동작입니다. vm0는 보통 바이브 코딩으로 불리지만 Fowler의 용어로는 에이전틱 코딩입니다.

AI 슬롭이란 무엇인가요 AI 슬롭은 AI 생성 코드가 남기는 잔여물입니다. 불필요한 폴백, 중복된 추상화, 공개 경계를 우회하는 테스트, 불가능한 상태를 위한 방어 분기 같은 것들입니다. 하나씩 보면 무해합니다. 쌓이면 저장소를 이해하기 어렵게 만들고 다음 에이전트에게 나쁜 예시를 남깁니다.

주당 630개의 풀 리퀘스트에서 AI 생성 코드를 어떻게 리뷰하나요 한 줄씩 읽지 않습니다. vm0에서 사람의 리뷰는 판단이 필요한 곳으로 갑니다. 아키텍처, 보안에 민감한 작업, 결제, 데이터 마이그레이션입니다. 나머지는 장치가 떠받칩니다. 엄격한 타입과 계약, 모듈 경계의 통합 테스트, 풀 리퀘스트마다 붙는 실제 데이터베이스 프리뷰, 브라우저 검증, Knip, 관행적 우회가 없는 머지 큐입니다.

대규모 바이브 코딩의 모범 사례는 무엇인가요 vm0에서 여덟 달 동안 유지된 다섯 가지입니다. 에이전트와 사람이 같은 명령을 실행하도록 개발 환경을 수렴시킬 것. 기준을 문서가 아니라 타입과 린터와 CI에서 실행 가능하게 만들 것. 실제 인프라를 상대로 모듈 경계에서 테스트할 것. 브라우저를 포함한 완결된 피드백 루프를 에이전트에게 줄 것. 그리고 가끔 크게 치우는 대신 계속 정리할 것.

AI 생성 코드의 보안 위험은 무엇인가요 흔한 위험은 특이한 취약점이 아니라 그럴듯하지만 아무도 책임지지 않는 코드입니다. vm0는 보안에 민감한 작업과 결제와 데이터 마이그레이션을 사람의 리뷰에 남기고, 해당 로직에는 단위 테스트를 의도적으로 쓰며, 테스트에서 내부 인프라를 실제로 유지하고, 실패를 삼키는 try/catch 같은 패턴을 제한하며, 필수 체크의 관행적 우회를 허용하지 않습니다.

AI 생성 코드는 기술 부채를 만드나요 특정한 종류를 만듭니다. 여전히 컴파일되고 테스트도 통과하지만 더 이상 시스템에 참여하지 않는 코드, 그리고 어떤 린터도 이름 붙일 수 없는 의미적 잔여물입니다. 결정적으로 판별되는 부분은 Knip이 제거합니다. 나머지는 반복 워크플로가 매일 작은 배치로 처리하고, 충분히 자주 재발하는 패턴은 린트 규칙이나 타입 제약이 됩니다.

vm0 코드베이스의 얼마나 많은 부분을 AI가 쓰나요 구현의 대부분입니다. 2026년 7월 20~26일 주간에 병합된 630개의 풀 리퀘스트 중 556개는 여섯 명의 엔지니어 명의였고 나머지는 릴리스 자동화였으며, 그 안의 코드는 대부분 에이전트가 썼습니다. 엔지니어가 유지하는 것은 아키텍처와 제약과 프로덕션 동작입니다.

테스팅 트로피란 무엇이고 왜 테스트 피라미드가 아닌가요 테스팅 트로피는 정적 분석을 바닥에, 통합 테스트를 주된 층으로, 소수의 엔드투엔드 테스트를 위에 둡니다. vm0가 이를 쓰는 이유는 모듈의 공개 경계를 통해 쓴 테스트라야 에이전트가 아래쪽 구현을 재조립하는 동안에도 동작을 계속 보호하기 때문입니다. 두꺼운 단위 테스트 층은 그러지 못합니다.

AI가 쓴 저장소에서 죽은 코드를 어떻게 찾나요 코드 생성은 파일과 추상화를 늘리지만, 삭제는 보통 별도의 지시가 필요합니다. vm0는 Knip을 정기 점검으로 돌려 쓰이지 않는 파일, 익스포트, 의존성, 엔트리 포인트를 찾습니다. TypeScript는 코드가 유효한지 확인하고, Knip은 그것이 아직 시스템에 참여하는지를 묻습니다. 죽은 코드에서 중요한 것은 후자입니다.

바이브 코딩된 코드를 프로덕션에서 써도 안전한가요 누가 입력했는지가 아니라 무엇이 그것을 검증하는지에 달려 있습니다. 우리가 요구하는 신호는 달라지지 않았습니다. 타입과 계약, 공개 경계를 통한 테스트, 실제 인프라, 반드시 녹색이어야 하는 파이프라인입니다. 이 글의 수치는 저장소 규모와 배포 속도를 말합니다. 프로덕션 질문에 답하는 것은 가용성과 오류율과 변경 실패율이며 그 지표는 아직 모으는 중입니다.

여덟 달을 지나며

여덟 달은 최종적인 방법론을 선언하기에 이르다. 모델과 에이전트 도구, 저장소는 계속 바뀌고, 우리의 규칙과 워크플로도 그에 맞춰 바뀐다.

한 가지 변화는 이미 분명하다. 코드 생성이 빨라지면서 환경, 제약, 테스트, 피드백 체계가 품질 부담을 더 많이 떠안게 되었다. 엔지니어는 구현을 타이핑하는 시간을 줄이고, 동작을 정의하고 경계를 설계하고 검증을 개선하는 데 더 많은 시간을 쓴다.

vm0 코드베이스는 앞으로도 커질 것이다. Knip이 결정적인 잔여물을 걷어낸다. 정적 규칙이 이미 이해하고 있는 실패 패턴을 막는다. 통합 테스트가 모듈 동작을 지킨다. 반복 워크플로가 아직 기계적으로 표현하지 못하는 퇴화를 다룬다.

코드는 여전히 중요하다. 다만 우리는 어떤 변경이 메인 브랜치에 속하는지, 그리고 그 구현이 저장소에 남아야 하는지를 판단하기 위해 기계가 실행할 수 있는 증거를 더 많이 쓰게 되었다.

Stay in the loop

// Get the latest insights on AI teammates and collaboration.

SubscribeJoin Discord