Zero Zone Of Pain

에이전트가 못 읽는 코드베이스를,
zzop 이 읽는다.

AI 코딩 에이전트는 저장소 전체를 컨텍스트에 못 담는다. 안 읽은 것은 추측한다. zzop 은 저장소를 읽어 한 장의 JSON 지도로 답한다 — 어떤 호출이 어떤 라우트에 닿고, 어떤 호출은 아무 데도 안 닿는지. 그리고 같은 입력이면 매번 같은 답을 준다.

코드는 쓰지 않는다. 에이전트가 딛고 일하는 이해를 정확하게 만든다.

문제

라우트 하나를 고쳤다. 아무도 눈치채지 못했다.

독립적으로 만들어진 두 앱 — React 프론트엔드와 Express 백엔드. 코드도 타입도 공유하지 않는다. 백엔드 라우트 이름을 하나 정리한다.

백엔드 정리
- router.put('/user',      auth.required, …)
+ router.put('/users/me',  auth.required, …)

프론트엔드 빌드는 깨끗하다. 라우트가 문자열 리터럴이라 대조할 타입이 없다. 자체 목킹 테스트도 초록이다. 계약은 이미 깨졌는데, 한 저장소만 보는 린터·타입체커·테스트는 구조적으로 못 본다 — 증거가 두 저장소에 나뉘어 있고 컴파일러 경계를 넘지 않기 때문이다.

zzop 이 답하는 것
=== unprovided consumes ===
  "PUT /api/user"       @ fe-vite     src/pages/Settings.jsx:19   
     호출은 살아 있는데 받는 라우트가 없다

=== unconsumed provides ===
  "PUT /api/users/me"   @ be-express  auth.controller.ts:61       
     라우트는 있는데 부르는 쪽이 없다

깨진 양쪽을 파일과 줄까지. 디스크에서 아무것도 공유하지 않는 두 저장소를 가로질러서.

무엇을 보나

엔진 하나, 렌즈 셋

교차 계층

프론트 호출과 백엔드 라우트를 잇는다. 아무도 안 부르는 엔드포인트, 메서드 불일치, 경로 드리프트 — 저장소를 넘어서도.

보안

SQL 인젝션, 약한 해시, SSRF, 하드코딩된 시크릿. DSL 룰과 네이티브 분석이 언어를 가로질러 본다.

구조

순환 의존, 죽은 코드, 리팩터 우선순위. 구조적 부채를 파일 단위로 셈한다.

네이티브로 읽는 언어는 여덟이다 — TypeScript · Python · Java · C# · Rust · Go · Prisma · SQL. 그 밖의 언어는 어댑터로 주입한다.

왜 믿나

못 본 것을 스스로 말한다.

정적 분석의 진짜 위험은 틀린 답이 아니라 침묵이다. 아무것도 안 나온 것과 못 본 것을 구별할 수 없으면, 초록은 아무 뜻도 없다.

결정적
같은 입력이면 바이트까지 같은 출력. 타임스탬프도, 흔들리는 정렬도 없다.
빈 배열의 뜻
언제나 “봤고, 없었다”. 못 본 것은 빈 배열이 아니라 부재로 보고된다.
못 한 일
이번 실행이 제공하지 못한 능력은 warnings 에 스스로 적힌다. 대충 채워 넣지 않는다.
발견마다
그것을 끄는 정확한 설정을 같이 준다. 룰 발견은 고치는 법과 억제 주석까지.

그래서 zzop 은 목록을 주는 도구가 아니라 방향을 주는 도구다. 결과는 리팩터 ROI 로 정렬되고, 두 저장소가 조용히 어긋난 것은 일급 발견이 된다.

실전에서

X 가 공개한 코드 전부를 걸어 봤다.

X(구 트위터)와 xAI 가 공개한 저장소 12개 — For You 피드부터 Grok 의 빌드 시스템까지 — 를 한 번에 걸었다. 아래 수는 2026-08-15 에 zzop 0.31.0 으로 잰 것이고, 수마다 무엇을 센 것인지가 다르다: walked 는 트리에서 걸은 파일, 파서 수신은 그중 네이티브 파서 8종이 실제로 받은 것, 심볼은 그 파서들이 추출한 선언이다.

x-algorithm
X 의 For You 피드(Rust + Python). 215파일 중 207을 파서가 받았다 — 96%. 이 세트에서 가장 높고, 이유도 정직하다: Rust 와 Python 은 정면 커버리지다. 한계도 같은 런이 말했다 — 실서비스 경로는 gRPC 인데 zzop 에 gRPC 인식기가 없어서, axum 을 임포트하고도 라우트 0 이 맞는 답이다.
grok-build
Grok 의 빌드 시스템. 단일 트리에서 심볼 71,142 — zzop 자기 저장소(13,259)의 5.4배를 한 트리가 낸다.
12트리 전체
walked 12,078 파일, 그중 파서 수신 4,480(37%) — 나머지는 대부분 지원 밖 언어(Scala 등)이고, 그 파일들도 버려지지 않고 줄 수와 텍스트 룰은 받는다. 심볼 합 94,281. 전량 facts 한 번이 콜드 73초, 캐시 뒤 30초.

두 비율을 섞지 않는 것이 이 표의 요점이다: 96% 는 한 트리(x-algorithm)의 수이고 37% 가 세트 전체다. 낮은 쪽을 숨기면 높은 쪽도 못 믿게 된다. 재는 법: 저장소들을 클론하고 zzop facts --config 한 번 — 트리마다 coverage 블록이 자기 파일·수신·심볼 수를 내고, 열두 블록을 더하면 위 합계가 나온다. 초 단위는 그 실행의 벽시계 시간이지 출력 필드가 아니다.

그 런이 무엇을 봤나

결함 171건 — 그리고 zzop 이 그걸 어떻게 말하는지.

같은 저장소 12개에 룰을 걸었다(2026-08-15, zzop 0.32.0). 결함이 나온 곳은 4개뿐이고 8개는 0건이다 — 0건도 결과라, 숨기지 않고 공시한다. 합 171건: 심각도로 나누면 critical 5 · warning 122 · info 44. 여기서 핵심은 심각도가 취약점 판정이 아니라 렉시컬 판정이라는 것이다.

critical 5건 — 전부 테스트 안
다섯 건 모두 conn-string-credentials(scheme://user:pass@host 를 소스에 박은 URL)이고, 다섯 건 모두 #[test] 함수 안이다 — 자격증명을 지우는 코드의 테스트 입력(strip_url_credentials_removes_token)이다. zzop 은 이걸 취약점이라 부르지 않는다. 렉시컬로 보이는 것을 보고하고, 잠재우는 config 키를 문장에 담고, 사람이 5초 만에 픽스처임을 읽게 둔다.
진짜 신호는 warning 에
가장 많이 뜬 룰: command-and-interpolation 33 · reqwest-no-timeout 24 · hardcoded-secret 19 · high-entropy-secret 13 · fs-check-then-use 9. 크로스-레인은 별도로, 소비되지 않는 엔드포인트·제공자 없는 호출 같은 계약 틈 20건을 냈다 — 파일 하나를 보는 룰이 못 보는 층이다.
8개는 0건 — 그것도 공시
communitynotes·grok-1·xai-proto·xai-sdk-python·x-algorithm 등 8개는 결함 0. 단 zzop 은 "0 = 안전"이라고 말하지 않는다: 같은 런이 트리마다 무엇을 못 봤는지(지원 밖 언어, 미해석 핸들러, gRPC 처럼 인식기 없는 경로)를 커버리지 블록에 담아, 0건이 깨끗인지 범위 밖인지 읽게 한다.

이게 제품의 논지다: zzop 은 결함을 자랑하지 않는다 — 자기가 무엇을 봤고 무엇을 못 봤는지를 정직하게 말한다. critical 다섯이 전부 테스트 픽스처인 것이 약점이 아니라, 그걸 취약점이라 우기지 않은 것이 강점이다. 재현: 저장소 12개를 클론하고 zzop cross --config 한 번 — 트리마다 심각도·룰별 카운트가 나오고, each finding 은 rule id·file:line·잠재우는 config 키를 함께 낸다. 이 코퍼스의 그래프 다섯 장 — dep 전량 4,457파일을 한 캔버스에 그린 것 포함 — 은 영어판 쇼케이스 페이지에 모여 있다(상단 EN 토글).

시작

세 줄이면 된다.

01 zzop init 설정 파일을 쓴다 · 트리당 한 번
02 zzop analyze . 이 트리를 분석 · JSON 출력
03 zzop cross ./web ./api 두 저장소를 잇는다

Node.js 도, npm 도, 컴파일할 것도 없다. GitHub Releases 에서 바이너리를 받으면 끝이다.

zzop
터미널과 CI 용 CLI. JSON 을 stdout 으로 낸다.
zzop-mcp
에이전트용 MCP 서버. 플러그인을 깔면 당신이 커맨드를 칠 일이 없다 — 에이전트가 직접 묻는다.

설정은 선택이 아니라 필수다. zzop 이 당신의 프로젝트에 대해 추측했을 이름들이 거기 산다 — 선언하지 않은 키는 zzop 이 판정하지 않는다.