본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 24. 14:10

Go로 구축한 로컬 우선(local-first) LLM 코드 리뷰어: 전체 파이프라인 소개

요약

Go 언어로 구축된 로컬 우선(local-first) 방식의 LLM 코드 리뷰 CLI 도구인 CommitBrief를 소개합니다. 보안과 비용 효율성을 위해 비밀 정보 스캔, 캐싱, 비용 사전 점검 등 LLM 호출 전후의 정교한 파이프라인을 갖추고 있습니다.

핵심 포인트

  • 로컬 모델(Ollama 등) 지원으로 데이터 외부 유출 방지
  • 비밀 정보 스캔 및 콘텐츠 주소 지정 캐싱 기능 포함
  • go-git과 git CLI를 혼합 사용한 안정적인 diff 추출
  • 다양한 LLM 제공자(Anthropic, OpenAI, Gemini 등) 지원

CommitBrief는 팀원이 확인하거나 미래의 당신이 확인하기 전에, git diff에 대해 LLM 리뷰를 실행하는 로컬 우선(local-first) CLI입니다. 서버도 없고 텔레메트리(telemetry)도 없습니다. diff는 당신이 선택한 제공자(provider)에게만 전송되며, Ollama와 같은 로컬 모델을 사용하면 아예 외부로 나가지도 않습니다.

흥미로운 엔지니어링 요소는 단순히 "LLM을 호출하는 것"이 아닙니다. 리뷰가 저렴하고, 안전하며, 재현 가능하도록 유지하기 위해 그 호출 "주변"에서 일어나야 하는 모든 과정입니다. commitbrief --staged부터 화면에 결과가 나타나기까지의 전체 경로를 소개합니다.

요약 (TL;DR)

  • 정체 — Claude, GPT, Gemini 또는 완전히 로컬인 Ollama 모델 등 당신이 선택한 제공자를 통해 스테이징된 diff(또는 모든 git diff 범위)를 리뷰하는 CLI입니다.
  • 보이지 않는 부분 — LLM 호출은 14개 단계 중 하나일 뿐입니다. 필터링(Filtering), 전송 전 비밀 정보 스캔(secret scan), 콘텐츠 주소 지정 캐싱(content-addressed caching), 비용 사전 점검(cost preflight)이 대부분의 작업을 수행합니다.
  • 한계 — 이것은 "영(0)번째" 리뷰어이며, 인간 리뷰어를 대체하는 것이 아닙니다.

주요 사실

  • Go 1.25, GPL-3.0-or-later, 호스팅 서비스 없음.
  • 10개의 제공자 + 모의(mock): Anthropic, OpenAI, Gemini, Ollama (네이티브 API); DeepSeek, Mistral, Cohere (OpenAI 호환); claude-cli, gemini-cli, codex-cli (서브프로세스 기반).
  • 리뷰 경로는 **읽기 전용(read-only)**입니다. 유일한 git 쓰기 명령은 commitbrief commit이며, 이마저도 이미 스테이징된 변경 사항에 대해 단 한 번의 git commit을 실행할 뿐, 파일을 절대 수정하지 않습니다.
  • 설치: brew install CommitBrief/tap/commitbrief, scoop install commitbrief, 또는 go install github.com/CommitBrief/commitbrief/cmd/commitbrief@latest.

리뷰의 형태

모든 리뷰는 하나의 선형 파이프라인(linear pipeline)을 따라 진행됩니다. 자세히 살펴보기 전에 전체적인 구조는 다음과 같습니다:

단계수행 내용목적
1. 컨텍스트 해결 (Resolve context).git을 향해 상위로 탐색, 설정 병합 (내장 < 글로벌 < 저장소), 환경 변수 + 플래그 적용실행당 하나의 결정론적(deterministic) 설정 확보
...

이 중 5개 단계가 대부분의 비중을 차지합니다. 순서대로 살펴보겠습니다.

diff 가져오기: git을 진실의 원천(source of truth)으로 사용하는 go-git

diff를 읽는 것이 사소한 일이라고 생각할 수도 있습니다. 맞습니다 — 하지만 스테이징된 상태와 스테이징되지 않은 상태(staged-vs-unstaged)의 비교, 워크트리(worktree) 비교, 그리고 git diff main...feature가 Windows에서도 git과 정확히 동일하게 동작해야 하는 상황이 오기 전까지는 말입니다.

CommitBrief는 하이브리드 방식을 사용합니다. 기본적으로 go-git 구현체를 사용하되, git CLI로 폴백(fallback)하는 방식입니다 (ADR-0002). go-git이 깔끔하게 모델링할 수 있는 범위 연산(Range operations) — 커밋 대 첫 번째 부모(commit-vs-first-parent), 머지 베이스 범위 diff(merge-base range diffs), 브랜치 diff — 등은 프로세스 내부(in-process)에서 처리됩니다. 스테이징된(staged), 스테이징되지 않은(unstaged), 그리고 임의의 git diff <args> 패스스루(passthrough)는 안정적인 파싱을 위해 --no-color --no-ext-diff 옵션과 함께 git 명령어를 셸(shell)로 호출합니다. 인덱스(index)와 워크트리(worktree) 상태에 대한 진실의 원천(source of truth)은 CLI로 유지됩니다. 이러한 내부 구조(plumbing)를 재구현하는 것은 리뷰 도구에서 피해야 할 미묘한 불일치(drift)를 야기하기 때문입니다.

commitbrief --staged                 # 인덱스(index)
commitbrief --unstaged               # 워킹 트리(working tree)
commitbrief diff main...feature      # 인자가 git diff로 그대로 전달됨

필터링(Filtering): 모델이 노이즈를 읽지 않도록 하는 3단계 계층

diff는 대부분 유의미한 신호(signal)인 동시에, 리뷰어가 읽어서는 안 될 쓰레기 더미이기도 합니다. 필터링은 세 개의 결합된 계층으로 구성됩니다 (ADR-0006):

  1. 내장 필터(Built-ins) — 약 65개의 패턴: 락 파일(lock files), vendor/**, node_modules/**, 생성된 코드(generated code), 빌드 결과물(build artifacts), 바이너리(binaries), IDE/OS 노이즈, 그리고 .commitbrief/cache/**.
  2. .commitbriefignore — gitignore 구문을 사용하며, 저장소 루트(repo-root)에 위치하고 팀 단위로 공유됩니다. 이는 내장 필터 _이후_에 적용되며, 마지막 일치 항목이 우선하는(last-match-wins) 방식입니다. 따라서 !pattern 라인을 통해 내장 필터에서 제외된 항목을 다시 포함할 수 있습니다.
  3. 의미론적 산문(Semantic prose)COMMITBRIEF.md에 작성된 자연어 제외 규칙(예: "생성된 모의 객체(generated mocks)는 표시하지 마세요")이며, 모델 자체에 의해 적용됩니다.

제외 계층을 거친 후, --file / --dir 옵션을 통해 더 좁은 허용 목록(allowlist)을 적용합니다. 만약 아무것도 남지 않는다면, 실행은 아무런 자원도 소모하지 않고 종료 코드 0으로 종료됩니다. 즉, 빈 diff는 에러가 아니라 성공입니다.

아무도 실행하지 않는 가드(guard): 모델에 비밀 정보를 보내지 마세요

5단계는 제가 가장 신경 쓰는 단계입니다. 왜냐하면 코드가 기기를 떠나기 직전의 경계에 위치하기 때문입니다.

프로바이더 (provider) 호출 전에 두 가지 검사가 실행됩니다. 첫째, 가드 (guard)가 사용자의 설정 파일이 조용히 전송되는 것을 거부합니다. 만약 diff 내의 어떤 경로라도 .commitbrief/로 시작하면 프롬프트(prompt)를 띄우며, TTY가 없는 환경에서는 자동으로 중단(abort)합니다. 둘째, 시크릿 스캐너 (secret scanner)가 추가된 라인에 대해서만 8가지 내장 패턴(AWS 액세스 키, GitHub/GitLab 토큰, Anthropic/OpenAI 키, JWT, Stripe 라이브 키, PEM 개인 키)을 대상으로 실행됩니다. 또한 guard.secret_patterns를 통해 사용자만의 패턴을 추가할 수 있습니다.

제가 자랑스럽게 생각하는 세부 사항 중 하나는, 매칭 시 {Line, Patterns} 정보만 기록하고 매칭된 서브스트링 (substring)은 절대 기록하지 않는다는 점입니다. 유출을 막기 위해 존재하는 스캐너 자체가 로그 라인, stderr, 또는 캐시 파일을 통해 유출의 원인이 되어서는 안 되기 때문입니다.

우회 정책 (bypass policy) 또한 의도적으로 설계되었습니다. --allow-secrets는 스캔을 건너뛰지만, --yes건너뛰지 않습니다. 파이프라인 내에서 프롬프트를 자동 승인(auto-confirming)한다고 해서, 자격 증명 (credential)이 제3자에게 전송되는 것을 조용히 승인해서는 결코 안 됩니다.

캐싱은 단지 콘텐츠 주소 지정 (content addressing)일 뿐입니다

동일한 diff를 두 번 리뷰하는 것은 비용이 들지 않아야 합니다. 캐시 키 (cache key)는 답변을 변경할 수 있는 정확한 입력값들에 대한 SHA-256 값입니다:

sha256( diff "::" systemPrompt "::" provider ":" model ":" lang ":" schemaVersion [":ctx"] [":mode+"+mode] )

모든 입력값은 제 자리를 가집니다. 완전히 조립된 시스템 프롬프트 (system prompt)가 키에 포함되어 있으므로, COMMITBRIEF.md를 편집하면 오래된 리뷰는 무효화됩니다. 스키마 버전 (schema version)이 키에 포함되어 있으므로, 출력 계약 (output contract)을 올리면 모든 것이 한꺼번에 무효화됩니다. --with-context와 커밋 메시지 (commit-message) 모드는 각각 접미사 (suffix)를 가져 일반적인 리뷰와 충돌하지 않습니다. 항목들은 .commitbrief/cache/<key>.json에 원자적 (atomically)으로 기록됩니다 (임시 파일 생성, 0600 권한 설정, 그 후 이름 변경 방식). 응답당 하나의 파일이 생성되며, 별도의 인덱스는 없습니다.

그 결실은 8단계에서 나타납니다. 비용 사전 점검(Cost preflight)은 보수적인 (len+3)/4 휴리스틱(heuristic)을 사용하여 입력 토큰을 추정하고, 예상 출력 토큰을 [200, 1500] 범위로 제한하며, 모델별 가격표를 곱합니다. 그리고 이 추정치가 사용자의 cost.warn_threshold_usd(기본값 $0.50)를 넘을 때만 프롬프트를 실행합니다. 캐시 히트(Cache hit)가 발생하면 사전 점검을 완전히 건너뜁니다. 변경되지 않은 디프(diff)에 대해 리뷰를 다시 실행하는 데 드는 비용은 디스크 읽기 한 번뿐입니다.

호출, 그리고 모델이 오작동할 때 발생하는 일

API 제공업체의 경우, CommitBrief는 구조화된 결과(structured findings)를 요청하고 이를 severity(심각도), file(파일), line(라인), title(제목), description(설명), suggestion(제안)으로 구성된 고정된 계약(contract)으로 파싱하여 JSON schema v1 형식으로 출력합니다. 만약 모델이 파싱할 수 없는 내용을 반환하면 한 번 재시도합니다. 여전히 문제가 있다면, 시스템은 충돌하는 대신 원문 텍스트를 Markdown으로 렌더링하고 경고를 출력하는 방식으로 성능을 저하시켜(degrade) 대응합니다. CLI 기반 제공업체(claude-cli, gemini-cli, codex-cli)는 읽기 전용 서브프로세스(subprocess)로 실행되며 텍스트를 있는 그대로 스트리밍합니다.

출력은 기본적으로 카드(Cards) 형태이며, 다음과 같이 사용할 수도 있습니다:

commitbrief --json --fail-on=high       # CI 게이트: high 이상의 결과가 발견되면 exit 1
commitbrief diff main...feature --markdown -o review.md

종료 코드(Exit codes)는 단순하게 유지됩니다: 성공 시(깨끗한 리뷰 또는 --fail-on 임계값을 넘지 않은 경우 포함) 0, 오류 발생 또는 임계값 초과 시 1을 반환합니다.

이것이 아닌 것

이것은 '영(0)번째 리뷰어'이지, 인간 리뷰어를 대체하는 것이 아닙니다. 이는 인젝션(injection), 누락된 nil 체크, 삼켜진 에러(swallowed errors), 이제는 도달할 수 없게 된 가드 절(guard clause)과 같이 명백하지만 놓치기 쉬운 부류를 잡아냅니다. 의도 수준의 설계 문제(intent-level design problems)를 잡아내지는 못하며, 해당 기능이 존재해야 하는지 여부도 알려주지 않습니다. 그 논의는 리뷰어와의 몫으로 남습니다.

또한, 저는 품질을 강요하지도 않습니다. 알려진 정답 코퍼스(corpus)를 기준으로 실제 출력을 점수화하는 재현 가능한 평가 하네스(eval harness)가 준비되어 있으니 직접 실행해 보십시오:

COMMITBRIEF_EVAL_PROVIDER=<name> make eval-live

다른 사람이 확인하기 전에, 이미 신뢰하고 있는 제공업체를 사용하여 로컬에서 디프(diff)를 검토할 수 있는 '두 번째 눈'을 원하신다면, 그것이 바로 이 도구의 핵심 목적입니다.

commitbrief setup     # 제공업체 선택, 키 붙여넣기, 연결 테스트
commitbrief init      # 선택 사항: 프로젝트별 규칙 작성
git add .
...

저장소 및 설치 안내: github.com/CommitBrief/commitbrief.

이 글은 Building CommitBrief의 파트 1입니다. 다음 편: 하나의 Go 인터페이스가 세 가지 전송 클래스(native APIs, OpenAI 호환 엔드포인트, subprocess 기반 CLI)를 통해 어떻게 10개의 LLM으로 확장되는지에 대해 다룹니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0