npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

geobuke-code

v0.14.0

Published

거북이코드 — 계획↔구현↔검증 게이트. Claude Code PreToolUse hook으로 구현 전 계획 케이스 누락·시나리오 미지정을 차단하고, 응답 종료(Stop) 시 scope 사후판정(축A 파급반경·축B 최소구현 사다리)으로 품질을 권고하며, gbc verify로 구현 후 케이스를 증거(JUnit·코드 독해)와 대조한다(verified>reviewed>unverifiable).

Readme

거북이코드 (geobuke-code)

계획 ↔ 구현 ↔ 검증을 잇는 게이트. 코드를 쓰기 전에 계획 케이스의 침묵 누락·시나리오 미지정을 PreToolUse hook으로 차단하고, 응답이 끝나는 직후 scope 사후 판정으로 파급반경·과잉구현을 권고하며, 구현 gbc verify로 케이스가 실제 충족됐는지 증거(테스트 결과·코드 독해)와 대조한다.

gbc는 기존 코딩 에이전트(Claude Code) 위에 얹는 얇은 게이트·검증 층이다. 모델 계층을 소유하지 않는다 — 판단용 작은 호출(haiku)만 직접 하고, 코드 생성은 그대로 Claude Code가 한다. 테스트를 실행하지도 않는다 — 표준 결과(JUnit)를 읽을 뿐이다.

무엇을 푸는가

구현 전후로 강제되지 않는 것들이 반복 통증을 만든다:

  1. 선행 케이스를 "추후작업"으로 미루다 누락 → 설계 공백 → 큰 결함
  2. 시나리오 미지정으로 임의 구현 → 의도와 다른 동작
  3. 고친 자리만 보고 끝 → 같은 원인이 인접 경로에 잔존(파급 재발) · 요청 밖 과잉구현 방치
  4. "됐다"는데 케이스가 실제 충족됐는지 대조 안 됨 → 통과 후 누락 발견

① 구현 전 게이트 — 코드 변경(Edit/Write/MultiEdit) 직전에 끼어들어:

  • 계획 명세에 있는 케이스가 침묵 누락(언급도 등록도 없이 빠짐)되면 차단
  • 의도·동작 시나리오가 미지정인 채 구현되면 차단
  • 미루기는 명시 등록(gbc defer add)만 허용 — 침묵 누락 차단의 forcing function

게이트는 완전 구현을 요구하지 않는다. 케이스가 다뤄지기 시작했거나 명시 defer되면 통과한다.

② 구현 직후 scope 사후 판정 — 응답 종료(Stop) 시점에 그 턴의 편집들을 모아 코드 품질 두 축을 판정한다. 차단이 아니라 권고:

  • 축A 파급반경 — 같은 원인의 문제가 직접 인접 경계(호출부·계약·공유 상태) 너머에서 재발하는지, gbc가 직접 grep으로 탐색해 찾은 지점만 제시
  • 축B 최소구현 사다리 — 새 코드가 YAGNI(rung1) → 기존 재사용(rung2) → 표준 라이브러리(rung3)에 걸리는지, 먼저 걸리는 하나만 (scope 사후 판정)

③ 구현 후 결과검증(gbc verify) — 구현이 끝난 뒤 케이스가 실제 충족됐는지 증거와 대조하는 판정 사다리:

  • verified — 테스트 실행 결과(JUnit XML)가 케이스를 통과/실패로 증명(::test 바인딩)
  • reviewed — 러너가 없으면 LLM이 최종 코드를 독해해 경량 판정(::file 바인딩, 동작 증명 아님)
  • unverifiable — 증거가 없으면 정직하게 미검증(거짓 통과·거짓 경보 안 함)

gbc는 spec-유래 명령을 실행하지 않고 표준 결과를 읽는다 — 러너가 어떤 것이든(jest·vitest·pytest·go…) JUnit만 내면 이식된다. 실행이 필요하면 사용자가 직접 고정한 명령의 --run만(0.6.0) (gbc verify).

그 위에 운영을 돕는 얇은 층이 붙는다:

  • 누락 케이스 일괄 분류 — 차단이 도출한 형제 케이스를 번호 체크리스트로 받아 한 번에 승인(spec)/미룸(defer) (gbc gate review)
  • 크로스-repo 가시성 — 등록한 다른 repo의 미해결 defer 요약 + 게이트 hook 건강성 롤업("회사 repo에서 게이트가 조용히 안 먹는다"를 한 명령으로 진단) (크로스-repo)
  • 판정 드리프트 회귀락 — 실제 판정을 캡처해두고 모델/프롬프트/SDK 변화 후 재판정해 pass↔block 뒤집힘을 잡는 로컬 pre-flight (gbc gate snapshot)
  • 관측 계측(M1~M3) — 게이트 적중·재호출·통과 후 churn. 여러 repo는 gbc metrics --all로 병합 (계측)
  • 운영 현황 관측(/gbc-monitor 스킬) — 위 게이트 상태·계측·repo 건강성·드리프트락을 묶어 조회·해석하는 읽기전용 표면(상태 변경은 /gate) (/gbc-monitor)

설치

요구사항: Node.js >=22(0.9.0부터, Breaking — gbc tui가 요구하는 ink 7/React 19 최소 버전).

# 1) 전역 설치
npm install -g geobuke-code

# 2) 대상 프로젝트에 게이트 설치
cd <your-project>
gbc init                          # .claude/settings.json에 hook(PreToolUse+Stop+SessionStart) + /gate · /gbc-mute · /gbc-monitor skill 머지 (동의·백업)

기본 게이트(B-모드)는 @anthropic-ai/claude-agent-sdk·ink·react쓰지 않는다 — 셋 다 optionalDependenciesgbc run(A-모드)·gbc tui(풀스크린 TUI)를 안 쓸 거면 가볍게 건너뛸 수 있다:

npm install -g geobuke-code --omit=optional

나중에 gbc tui/gbc run이 필요해지면 그때 npm i -g [email protected] [email protected] @anthropic-ai/[email protected]로 추가하면 된다(미설치 상태로 실행하면 gbc가 정확한 버전까지 포함해 설치 명령을 안내한다). 버전을 반드시 명시하라 — 캐럿(^)이나 미지정으로 설치하면 사내 프록시 레지스트리 캐시 상태에 따라 다른 버전이 깔려 충돌할 수 있다(아래 사내 프록시 레지스트리 환경 참조).

# 1) 클론 + 빌드 (dist/ 생성)
git clone https://github.com/cubha/geobuke-code.git
cd geobuke-code
npm install && npm run build

# 2) gbc 명령 연결 — 택1
npm link                          # 권한 OK면 전역 gbc 생성
# ↑ EACCES(전역 node_modules가 root 소유)면 sudo 없이 PATH의 ~/.local/bin에 wrapper:
printf '#!/bin/sh\nexec node "%s/dist/cli.js" "$@"\n' "$PWD" > ~/.local/bin/gbc && chmod +x ~/.local/bin/gbc

# 3) 대상 프로젝트에 게이트 설치
cd <your-project>
gbc init

소스를 수정하면 npm run build만 다시 하면 된다 — wrapper/link는 같은 dist/cli.js를 가리키므로 재연결 불필요.

gbc init프로젝트 로컬 .claude/settings.json 머지한다(append·멱등·백업). 전역 ~/.claude는 건드리지 않는다. hook 명령은 셸 무관 순수 형태(node "<path>" hook pre-tool-use)라 Windows(cmd.exe)/bash/zsh/Mac에서 동일하게 동작한다.

빠른 게이트 활성화 (API 키 — 선택)

키가 없으면 claude -p 폴백(~13–20s)으로 무설정 동작한다. **haiku 직접 API(~1–3s)**를 쓰려면 키 파일만 만들면 된다 — gbc가 실행 시 직접 읽으므로 settings.json 수정이나 셸 주입은 불필요하다.

⚠️ native Windows에선 API 키를 권장한다. 키 없는 claude -p 폴백도 지원하지만(win32에선 claude.cmd 실행을 위해 셸 경유, 프롬프트는 인젝션 회피로 stdin 전달, 무응답 방지 kill-timeout), 폴백은 느리므로(~13–20s) 위 키 파일로 API 경로(~1–3s)를 쓰는 편이 빠르고 확실하다.

🔒 회사·보안통제(EDR/그룹정책) 환경에선 API 키가 사실상 필수다. native Windows에서 claude.exe가 EDR·정책에 막히면(증상: claude exited 1: 액세스가 거부되었습니다), 키 없는 claude -p 폴백 호출이 매번 실패해 게이트가 조용히 fail-open된다 — 게이트가 느려지는 게 아니라 꺼진다(.gbc/failopen.log에 누적). 이때 ANTHROPIC_API_KEY(또는 키 파일)를 설정하면 CLI spawn을 건너뛰고 직접 API로 판정하므로 막힌 환경에서도 게이트가 살아 있다. 즉 이 경우 키는 속도가 아니라 게이트 작동 자체의 문제다. (깨끗한 native Windows에선 keyless 폴백도 정상 동작함이 검증됨 — 실패는 환경의 CLI 차단에서 비롯된다.)

키 해석 순서: ANTHROPIC_API_KEY 환경변수 > ~/.gbc/api-key 파일.

# bash / zsh / WSL / Mac
mkdir -p ~/.gbc && printf '%s' 'sk-ant-...' > ~/.gbc/api-key && chmod 600 ~/.gbc/api-key
# native Windows (PowerShell) — -NoNewline 필수(끝에 개행 붙으면 키 오염)
New-Item -ItemType Directory -Force -Path "$HOME\.gbc" | Out-Null
Set-Content -Path "$HOME\.gbc\api-key" -Value "sk-ant-..." -NoNewline

⚠️ export ANTHROPIC_API_KEY=… 전역 설정(또는 settings.json top-level env) 금지. Claude Code 본체가 그 키로 과금 전환된다(구독 대신 키 과금). 키 파일 방식은 gbc 판정 호출에만 키가 쓰이므로 이 함정을 구조적으로 피한다.

gbc status는 키 파일/환경변수를 반영해 트랜스포트: api로 표시한다.

동작 원리

phase-protocol/계획 → /plan(SubTask) → 【게이트: 구현 직전 케이스확정】 → 구현(Claude Code) → 【scope: 응답 종료 시 파급반경·최소구현 권고】 → 【gbc verify: 구현 후 케이스↔증거 대조】

게이트는 계획 명세를 .gbc/spec.md(단일 정본)에서 읽는다. 다른 파일을 명세로 쓰려면 $GBC_SPEC_FILE 환경변수로 그 경로를 명시 지정한다(우선순위 $GBC_SPEC_FILE > .gbc/spec.md). gbc가 소유하지 않은 파일을 자동 폴백하지 않으므로, 진행추적 파일 등이 명세로 오인되지 않는다.

코드 변경 직전 PreToolUse hook이 명세 ↔ 변경 ↔ 미룬 항목을 대조해 통과/차단을 판정한다.

동작 시점

gbc init이 프로젝트 .claude/settings.json에 아래 hook을 멱등 등록한다. gbc는 .gbc/만 읽으므로(다른 하네스의 메모리·진행추적 파일 미접근) 어떤 환경에서든 동일하게 동작한다.

| 시점 | hook (matcher) | 동작 | |---|---|---| | 세션 시작·재개 | SessionStart (startup\|resume) | .gbc/defers.json의 미해결 항목을 "진행중 N · 미착수 M"로 구분 표면화(이전 작업 잔여 환기). 잔여 없으면 무출력. compact엔 발화 안 함(노이즈 방지) | | 코드 변경 직전 | PreToolUse (Edit\|Write\|MultiEdit) | 명세 ↔ 변경 ↔ defer 대조 → 통과(침묵)/차단(시나리오 도출 지시)/fail-open | | 작업단위당 1회 | (PreToolUse 캐시) | 같은 명세 해시 내에선 첫 편집만 판정, 이후 통과 → 매 편집 지연 회피 | | 편집 적용 직후(0.12.3) | PostToolUse (Edit\|Write\|MultiEdit) | 사용자가 승인해 실제로 적용된 편집을 작업단위 원장(.gbc/applied.json)에 기록 → 이후 판정이 "이미 적용된 편집"을 참고해 같은 형제 케이스를 재차단하지 않는다(아래 작업단위 구현이력) | | 응답 종료 | Stop | 계측 flush(events.jsonl) + scope 사후 판정(축A 파급반경·축B 최소구현 사다리 — 아래) + 미해결 defer가 있으면 리마인드(매 대화 종료마다). 거슬리면 gbc defer mute(또는 /gbc-mute 스킬)로 끈다 — SessionStart 진입 알림은 유지 | | 업데이트 필요 시 | (PreToolUse·SessionStart) | hook 구버전(②) 또는 신버전 출시(①)면 갱신 안내. PreToolUse는 세션당 1회(systemMessage 비차단) — 통과된 작업단위(cached-skip) 편집에도 표시(0.3.0: 평상 작업 대부분이 cached-skip이라, 여기서 빠지면 배너가 거의 안 떴음). SessionStart는 진입 시 표시(모델 컨텍스트). gbc status는 캐시만 갱신하고 안내는 표시하지 않는다(명시 진단 명령). 게이트 통과/차단 동작은 불변 |

세션 진입 알림만 끄려면 GBC_NO_SESSION_HINT=1. 매 대화 종료(Stop) defer 리마인드만 끄려면 gbc defer mute(영속, 해제 unmute · 스킬 /gbc-mute) — 진입 알림은 남는다. 업데이트 안내만 끄려면 GBC_NO_UPDATE_NOTICE=1. scope 사후 판정을 끄려면 GBC_NO_SCOPE=1. 프로젝트 hook이 구식이거나(SessionStart 누락·옛 명령) 새 버전이 나오면 gbc가 감지해 gbc update(전역 최신 + 재init 한방, --all이면 등록된 repo 전체) 또는 수동 npm i -g geobuke-code@latest → gbc init --yes를 안내한다. 단 안내는 이미 hook이 등록된 프로젝트(=한 번이라도 gbc init을 한 코호트)에만 도달한다 — 전혀 init하지 않은 프로젝트엔 실행할 hook이 없어 구조적으로 알릴 수 없다(gbc는 전역 hook을 깔지 않는다). 업데이트 안내(①)는 네트워크를 게이트 핫패스에 들이지 않는다: ~/.gbc/version-check.json 캐시만 비교하고, 갱신 fetch는 안전한 비-핫패스에서만 짧은 타임아웃(1.5s)으로. ⓐSessionStart는 캐시가 stale이면 표시 전에 갱신해 신버전이 그 세션에 바로 뜬다(1세션 지연 없음). ⓑPreToolUse는 judge를 도는 편집(cache-miss)에서 캐시가 stale이면 refresh를 judge와 병렬로 건다(0.3.0) — judge가 ≥1.5s라 지연 0이고, 사용자가 gbc status를 직접 치지 않아도 캐시가 최신이 된다. cached-skip 핫패스에는 네트워크를 절대 넣지 않는다. 조회 실패는 조용히 무시(fail-silent)되어 게이트 결정에 영향이 없다. 캐시 TTL 12h.

작업단위 구현이력 (PostToolUse, 0.12.3 + 원장 재검증 0.12.4)

같은 작업단위(계획 명세) 안에서 형제 케이스를 순차로 구현하면(예: 케이스 A를 먼저 끝내고 다음 편집에서 케이스 B를 다룸), 이전엔 매 편집이 명세를 백지 상태로 재평가해 "케이스 A가 아직도 안 보인다"며 반복 차단되는 경우가 있었다 — 실사용 리포트: 같은 침묵-누락 케이스가 15분 동안 4회 재차단됐는데, 그 사이 사용자는 그 케이스를 실제로 구현하고 있었다. 원인은 gbc에 PostToolUse hook이 없어 "방금 그 편집이 실제로 승인·적용됐다"는 사실 자체를 알 방법이 없었던 것이다.

PostToolUse hook이 승인된 편집(Edit/Write/MultiEdit)마다 .gbc/applied.json에 새 내용만(요약·시크릿 마스킹 후) 기록한다. 이후 판정은 [이 작업단위에서 이미 적용된 편집] 섹션으로 이 원장을 참고해, 형제 케이스가 다른 위치(다른 파일 포함)에서 이미 구현됐으면 다시 누락으로 잡지 않는다 — 단 심볼 존재만으로는 안 뒤집힌다(P2b [관련 코드 근거(grep)]와 동일 원칙, 근거 텍스트를 실제 판단 자료로 쓰지 심볼 유무만 보지 않는다). 원장은 작업단위(명세 해시)가 바뀌면 자동 무효화되고, gbc done(작업단위 명시 종료)에서도 함께 비워진다. Write(전체 덮어쓰기)는 자기 파일의 과거 엔트리를 참고하지 않는다(덮어쓰기로 사라지는 회귀는 여전히 누락으로 취급 — GATE_SYSTEM ★★ 규칙).

원장 생존 재검증(0.12.4) — 원장은 기록 시점 스냅샷이라, 그 코드가 이후(같은 파일이든 다른 파일이든) 삭제돼도 예전엔 "완료"로 계속 남았다. 이제 판정에 실리기 전 각 엔트리를 현재 디스크 상태와 대조해 생존(alive)·소실(stale)·판정불가(unverifiable) 3상태로 재검증하고, stale 엔트리는 제외한다(불확정은 버리지 않는다 — 읽기 실패·매칭 불가는 그대로 유지). stale 엔트리가 걷어지면 침묵 누락이 자동으로 다시 드러나 근거주입(grep) 재판정도 다시 열린다. gbc gate reset --hard로 원장까지 포함해 재현 실험을 완전히 초기화할 수 있다.

⚠️ PostToolUse hook 신설은 breaking 계약 변경이다 — 0.12.2 이하에서 이미 gbc init한 프로젝트는 gbc init --yes 재실행이 필요하다(머지·멱등·자동 백업, 게이트 판정 자체는 재init 없이도 계속 정상 동작하지만 이 기능만 무동작 상태로 남는다). 미등록 상태는 gbc status·gbc repos --healthPostToolUse hook 미등록으로 안내한다.

시나리오 도출 루프 (수기 입력 불필요)

명세가 비어 시나리오 미지정으로 차단되면, 사용자가 파일을 직접 쓰지 않는다. 차단 메시지가 코딩 에이전트에게 다음을 지시한다:

요청에서 시나리오 도출 → 사용자에게 제시·검증 → gbc spec add로 등록 → 재시도
  • 도출은 코딩 에이전트 본체(Opus, 대화 맥락 보유)가, 게이트 판정은 haiku가 한다 — 두 작업/두 모델 분리. gbc는 모델 계층을 소유하지 않는다(판단용 작은 호출만).
  • 사용자 검증은 양보 불가다 — 같은 에이전트가 도출+구현까지 자동으로 하면 자기 시나리오만 통과시키는 고무도장이 된다. 승인 없는 자동 등록을 금지한다.

누락 케이스 일괄 분류 (gbc gate review)

명세가 있는데 형제 케이스를 침묵 누락해 차단되면, 판정이 도출한 누락 케이스들이 .gbc/pending-review.json에 기록된다. 케이스가 여러 개일 때 하나씩 gbc spec add/gbc defer add를 반복하지 않고 체크리스트로 한 번에 분류한다:

gbc gate review                              # 누락 케이스를 번호 목록으로 (사용자에게 제시·검증)
gbc gate review --spec 1 3 --defer 2         # 1,3은 승인→spec / 2는 미룸→defer (한 번에)
  • 승인(--spec)은 .gbc/spec.md에, 미룸(--defer)은 defer 레지스트리에 등록하고 펜딩을 비운다. 한 케이스가 양쪽에 걸리면 spec 우선(이중 등록 방지). ref는 번호|텍스트|all(defer 명령과 동일).
  • 분류 후 같은 편집을 재시도하면 등록된 케이스 기준으로 재판정된다. 여기서도 사용자 검증이 분류 전제 — 도출된 케이스를 사용자에게 보여주고 승인받은 뒤 등록한다.
  • 펜딩은 "가장 최근 차단의 도출"이라 다음 차단이 덮어쓴다. 단건이면 종전대로 지금 변경에서 직접 다루거나 gbc defer add로 미뤄도 된다.

같은 누락의 반복 차단 억제 (block-repeat, 0.9.3 + 근사매칭 0.13.0)

같은 작업단위(명세 해시) 안에서 같은 침묵-누락 케이스가 편집마다 재차단되면(형제 케이스를 아직 안 다뤘을 뿐인데 매번 같은 사유로 물어봄) 노이즈가 된다. 직전 차단에서 안내된 누락 셋과 (정규화 후) 동일하면 두 번째부터는 사람 승인 없이 조용한 안내로 강등한다(🐢 같은 누락 케이스가 이미 안내됐습니다) — gbc gate review로 여전히 분류 가능하다.

0.13.0부터 완전일치 외에 근사매칭도 본다. LLM이 매 judge 호출마다 같은 잔여 작업을 문구만 다르게(병합·분리·축약) 재서술하면 완전일치 판별이 실패해 재차단이 재발했다(실사용 로그로 확인된 결함). 이제 같은 작업단위 안에서 안내된 누락 문구를 누적해두고, 새 누락이 그 누적분을 바이그램 단위로 충분히 재진술하고 있으면(신규 절이 섞이지 않은 재서술) 강등한다. 완전히 새로운 위반(예: 공지된 적 없는 형제 케이스)은 토큰이 일부 겹치더라도 강등되지 않는다. 이전 동작(완전일치만)으로 되돌리려면 GBC_REPEAT_MATCH=exact.

scope 사후 판정 — 축A 파급반경 · 축B 최소구현 사다리

게이트(PreToolUse)가 "이 편집이 계획 케이스를 침묵 누락했나"를 본다면, scope는 응답 종료(Stop) 시점에 그 턴의 편집들을 모아 코드 품질 두 축을 사후 판정한다. 차단이 아니라 권고다 — 사용자에게 "다른 데도 확인해봐"라고 떠넘기지 않고, gbc가 직접 grep으로 코드베이스를 탐색해 판정한 결과만 제시한다.

  • 축A — 파급반경: 이 편집과 같은 원인의 문제가 직접 인접 경계(①직접 호출부 ②API/데이터 계약 ③공유 상태) 너머에서 재발하는가. (예: formatUserName에 null 폴백을 넣었는데, 그 함수를 안 쓰고 user.name을 직접 렌더하는 다른 파일이 남아 있음.)
  • 축B — 최소구현 사다리(Ponytail): 새 코드가 rung1(YAGNI: 요청 안 한 기능 선구현) → rung2(기존 코드 재사용 가능) → rung3(표준 라이브러리로 대체 가능) 중 걸리는 게 있는가. 먼저 걸리는 하나만. 가드레일(사다리 대상 아님): trust-boundary 검증·보안·접근성·데이터손실 처리, TDD RED 실패 테스트.

동작 흐름 — 큐잉(PreToolUse)과 판정(Stop)의 시점 분리

 대화 턴 진행 중 (핫패스 ~1.7s 예산)          응답 종료 (Stop — 핫패스 아님)
┌─────────────────────────────────┐    ┌─────────────────────────────────────┐
│ Edit/Write 직전 PreToolUse hook │    │ 1. 큐 읽기 → 편집된 심볼 추출         │
│  ├─ 게이트 판정 (기존과 동일)     │    │ 2. 실제 grep 탐색 (직접 호출부·      │
│  └─ scope 큐잉만 (API 호출 0회)  │───▶│    유사 유틸 · 자기 파일 제외 · 10s)  │
│      └▶ .gbc/scope-queue.json  │    │ 3. haiku 1회 배치 판정 (축A + rung)  │
│ (편집 2, 편집 3 … 계속 큐잉)      │    │ 4. actionable만 표면화 → 큐 비움     │
└─────────────────────────────────┘    └─────────────────────────────────────┘

시나리오로 보기

축A — "null 폴백을 넣었는데, 정말 다 고쳐졌나?"

 이번 턴 수정: format.ts의 formatUserName()에 null 폴백 추가
   ├─ profile.tsx   formatUserName(user) 호출   → 폴백 적용 ✅
   └─ header.tsx    {user.name} 직접 렌더        → 폴백 우회, 같은 원인 재발 ⚠️
                     └▶ [scope] 축A broken: header.tsx의 직접 렌더 경로 미적용

"다른 데도 확인해보라"는 떠넘김이 아니라, grep으로 실제 찾은 지점만 제시한다.

축B — "요청은 '날짜 표시'였는데 코드가 커졌다?" (아래 단부터, 먼저 걸리는 하나만)

 rung1  YAGNI      요청 안 한 타임존 변환·캐싱을 선구현        → 🔔 여기서 정지
 rung2  재사용      같은 유틸이 기존 코드에 이미 있음 (grep 필요)
 rung3  표준 lib    Intl.DateTimeFormat으로 대체 가능
 🛡️ 가드레일(사다리 제외): 보안·trust-boundary 검증·접근성·데이터손실 처리·TDD RED 테스트

왜 Stop 시점인가 / 왜 별도 호출인가 (설계 근거):

  • 게이트 프롬프트에 축을 섞으면 검증된 침묵-누락 판정이 실측으로 골든 8/8→6/8 퇴행했다. 그래서 축A/B는 게이트와 완전히 별도 호출로 분리(게이트는 무변경).
  • 축A·축B-rung2는 코드베이스 탐색이 있어야 정확 판정된다. PreToolUse 핫패스(~1.7s 예산)에 grep+판정을 넣으면 지연이 깨지므로, PreToolUse는 큐잉만 하고(API 0회) Stop에서 실제 grep→배치 판정한다(Stop은 핫패스가 아님 — CC가 완료까지 대기).
  • 하드가드(코드): grep이 인접 호출부·유사 유틸을 못 찾으면 모델에게 묻지 않고 unknown(미평가)으로 정직 처리하고, 표면화 시 "탐색 컨텍스트 부족으로 파급반경 판정 생략"을 명시한다(근거 없는 확신 차단).

모델: 기본 haiku. GBC_SCOPE_MODEL=claude-sonnet-4-6로 opt-in(게이트 GBC_MODEL과 물리 분리 — 공유 시 비용 배증). 끄려면 GBC_NO_SCOPE=1.

판정 결과는 gbc metrics[scope] 롤업으로 관측한다(파급반경 broken·사다리 걸림·탐색불가 미평가 건수). events.jsonl엔 열거형 태그만 기록하고 코드 본문·사유는 저장하지 않는다(프라이버시 불변식).

사후 결과검증 (gbc verify)

게이트(PreToolUse)가 구현 "케이스를 다루는가"를 본다면, gbc verify는 구현 "결과물이 케이스를 실제 충족했는가"를 증거와 대조한다. gbc는 spec-유래 명령을 절대 실행하지 않는다 — 표준 결과(JUnit XML)를 읽거나, 러너가 없으면 LLM이 최종 코드를 독해한다. 실행은 환경(사용자 러너·CI) 몫이고 gbc는 결과 포맷만 소비한다(provider 패턴 — spec의 명령을 돌리면 공급망 RCE·환경마다 깨짐; 읽으면 러너 불문 이식). 유일한 예외는 사용자가 직접 고정한 명령의 --run(0.6.0, 아래)이며 그 명령 문자열에 spec 유래 데이터는 절대 섞이지 않는다.

판정 사다리 (강→약):

| 강도 | 판정 | 증거 | 바인딩 | |---|---|---|---| | 강 | verified | 테스트 실행 통과/실패(JUnit XML) | ::test <테스트명> | | 중 | reviewed | LLM이 최종 코드 독해(주소화 판정·동작 증명 아님) | ::file <경로> | | 약 | unverifiable | 증거 없음(결과파일·파일·바인딩 부재) — 정직 바닥 | (없음) |

케이스↔증거 바인딩 — spec 케이스 줄 끝에 접미사로 붙인다(줄 끝 단일 토큰, 공백 포함 이름은 따옴표):

gbc spec add "빈 자격증명 거부 ::test login_empty_creds"     # 러너 결과로 verified
gbc spec add "로그인 검증 로직 ::file src/auth.ts"           # 코드 독해로 reviewed
gbc spec add '경계조건 처리 ::test "should handle empty"'     # 공백 포함 테스트명은 따옴표
gbc verify                                                   # 사다리 리포트
  • verified 쓰려면 러너가 JUnit XML을 .gbc/verify-results.xml로 떨구게 한다 — vitest run --reporter=junit --outputFile=.gbc/verify-results.xml, pytest --junit-xml=.gbc/verify-results.xml, node --test --test-reporter=junit --test-reporter-destination=.gbc/verify-results.xml 등. 판독 경로는 이 파일을 읽기만 한다.
  • 배선이 막막하면 gbc verify --init (0.6.0) — 러너를 감지해(vitest/jest/mocha/node:test) 위 배선 명령을 안내하고, 러너가 없으면 node:test 제로설치 리포터 템플릿(.gbc/junit-reporter.mjs, 의존성 0)을 생성한다. 사용자 파일(package.json 등)은 절대 수정하지 않고, 어떤 명령도 실행하지 않는다.
  • 실행까지 한 번에: gbc verify --run (0.6.0) — 신뢰 소스가 고정한 러너 명령을 실행한 직후 판독한다. 명령 소스는 정확히 2개: CLI 인자(gbc verify --run "npm test", 1회성) 또는 홈 pin(--save~/.gbc/verify-run.json에 저장, repo 이라 PR이 심을 수 없다). spec.md 유래 명령은 구조적으로 배제(spec은 PR 기여 파일 → spec-derived 실행=공급망 RCE). 러너가 결과파일을 갱신하지 않으면 옛 결과를 verified로 치지 않고 강등한다(run-start 검사).
    • ⚠️ allowlist 주의(confused deputy): Claude Code 등에서 Bash(gbc *)처럼 gbc를 와일드카드 allowlist하면 --run "<임의명령>" 인자형이 에이전트의 무프롬프트 임의 실행권이 된다 — spec.md 인젝션이 LLM을 경유해 "신뢰 명령"으로 세탁될 수 있다. --save로 저장된 pin도 이후 인자 없는 --run그대로 재실행하므로 동일 주의가 지속된다. gbc를 allowlist하려면 --run 인자형을 제외하라.
  • reviewed는 러너 없이도 동작 — ::file 케이스의 코드를 LLM이 독해해 충족 여부를 refute-first로 판정한다. 단 독해는 동작 증명이 아니다(미묘한 버그·런타임 오류는 못 잡는다). 모델은 기본 haiku(A/B 실측서 sonnet과 정확도 동률·지연 절반), GBC_VERIFY_MODEL로 opt-in 상향(게이트 GBC_MODEL·scope GBC_SCOPE_MODEL과 물리 분리).
  • fail-open은 unverifiable 떨어진다 — 검토 호출이 실패해도 절대 pass로 뭉개지 않는다(거짓 확신 차단, 게이트 fail-open 철학의 미러).
  • failed·unverifiable 케이스gbc verifygbc defer add "..." 형태로 후보 제안만 한다 — 자동 등록하지 않는다(사람이 분류, defer 원칙과 동일).
  • 보안: ::file ref가 프로젝트(cwd) 밖을 가리키거나 심링크면 읽지 않고 거부한다 — spec.md는 커밋/PR 기여 파일이라, 임의 경로 파일을 LLM에 전송하는 유출을 막는다.
  • 신선도(provenance, 0.6.0): 결과파일이 마지막 관측 편집(hook 이벤트 기준)보다 오래됐으면 verified를 unverifiable로 강등한다 — 옛 결과의 거짓 verified(pass)·거짓 경보(fail)를 대칭 차단. 편집 신호가 없으면(hook 미설치 등) 강등하지 않고 "신선도 미평가"로 정직 고지한다. 관측 기준이라 절대 보증은 아니다(ask-모드로 승인된 block 편집은 미포함).

지연(latency)과 트랜스포트

판정은 작은 LLM 호출이다. 두 트랜스포트:

| 조건 | 트랜스포트 | 지연 | |---|---|---| | 키 있음 (ANTHROPIC_API_KEY env 또는 ~/.gbc/api-key 파일) | Anthropic API 직접 (haiku, 최소 시스템프롬프트) | ~1–3s (목표) | | 키 없음 | claude -p 폴백 (CC 인증 재사용, 무설정 · native Windows 포함) | ~13–20s |

작업단위 1회: 게이트는 작업단위(계획 명세 해시)당 한 번만 발동한다. 명세가 바뀌거나 명세 밖 파일을 편집할 때만 재발동 → 매 편집 지연을 피한다.

빠른 게이트를 원하면 ~/.gbc/api-key 키 파일을 만들어라(설정법·과금 주의: 위 「빠른 게이트 활성화」). 없으면 claude -p 폴백으로 무설정 동작하되 작업단위당 한 번 느리다.

명령

| 명령 | 설명 | |---|---| | gbc init | hook + /gate · /gbc-mute · /gbc-monitor 스킬 설치 + 크로스-repo 레지스트리 자동등록(opt-out: --no-register) | | gbc update | 전역 최신 설치(npm i -g …@latest) + 재init 한방. --all이면 gbc repos 등록 repo 전체로 재init 전파(전역 설치는 1회). --dry-run으로 실행 명령만 미리보기 | | gbc status | 게이트 상태 + 로드된 명세 + Stop 리마인드 음소거 여부 | | gbc defer add "<케이스>" | 케이스를 명시적으로 미루기 (→ open) | | gbc defer list | 미룬 항목 목록 (상태: 미해결/진행중/해결/철회) | | gbc defer start <번호\|텍스트\|all> | 착수 표시 (open → 진행중) | | gbc defer resolve <번호\|텍스트\|all> | 종결 표시 (→ 해결) | | gbc defer withdraw <번호\|텍스트\|all> | 철회 — 오등록·기각 등 완료 아닌 정리 (→ 철회, 완료로 기록되지 않음) | | gbc defer reopen <번호\|텍스트\|all> | 백로그로 되돌리기 (→ open, 잘못된 해결/철회 취소 포함) | | gbc defer mute / unmute | 대화 종료(Stop)마다 뜨는 defer 리마인드 끄기/켜기 (영속) · 스킬: /gbc-mute | | gbc spec add "<케이스>" | 승인된 시나리오를 .gbc/spec.md에 등록 | | gbc spec show | 등록된 케이스 목록 | | gbc spec clear | 명세 비우기(아카이브 없이) | | gbc done | 작업단위 명시 종료(명세 아카이브→비움 + 게이트 리셋). 아카이브는 최신 20개만 보존(GBC_ARCHIVE_KEEP 조정) | | gbc verify | 사후 결과검증 — 케이스↔증거 대조(verified>reviewed>unverifiable). 바인딩: <케이스> ::test <테스트명> / ::file <경로> | | gbc verify --init | 러너 감지→JUnit 배선 스캐폴딩(러너 없으면 node:test 제로설치 템플릿, 비파괴·무실행) | | gbc verify --run ["<명령>"] [--save] | 신뢰 소스(인자·홈 pin) 고정 명령 실행 후 즉시 판독 — spec 유래 명령 금지(RCE). ⚠️allowlist 주의 | | gbc run "<프롬프트>" [--yes] [--model <m>] [--max-turns <N>] | (실험적 A-모드) agent-sdk를 in-process로 구동해 게이트를 SDK 콜백으로 발화 + canUseTool 사람-pause(--yes=자동허용). agent-sdk 별도 설치 필요 (A-모드 미리보기) | | gbc tui [--model <m>] | 풀스크린 TUI(단일-repo, 0.9.0 A3a) — 승인 프롬프트·게이트 줄·계측/repo 토글 패널. ink/react/agent-sdk 별도 설치 필요 (풀스크린 TUI) | | gbc gate reset | 작업단위 게이트 리셋 | | gbc gate reset --hard | state·pendingReview·적용이력 원장까지 전부 초기화(재현 실험용, 0.12.4) | | gbc gate review | 차단이 도출한 누락 케이스 체크리스트 보기 | | gbc gate review --spec <ref> --defer <ref> | 누락 케이스 일괄 분류(승인→spec / 미룸→defer) | | gbc gate snapshot <on\|off\|status\|list\|clear> | 골든셋 캡처 토글·조회(판정 드리프트 회귀락) | | gbc gate snapshot replay [--samples N] | 골든 케이스 재판정(temp 0)·드리프트 시 exit 1. 근거주입이 걸렸던 케이스는 2단계(<id>#p2b)까지 재현 | | gbc metrics [--all] [--json] [--since <시각>] | 계측 리포트(M1~M3 + 진짜 M1 사후대조 + [P2b] 근거주입 롤업). --all=등록 repo들의 events.jsonl 병합 집계(진짜 M1은 단일 repo 전용). --since=시간창 제한(2026-08-09T00:00:00Z 또는 7d/24h/30m) | | gbc score [--json] | (A-모드) extraction⨝events 사후대조 채점 — 세션 편집이 통과 당시 명세를 커버했는지 모델 판정(후보당 1호출). 결과는 .gbc/scores.jsongbc metrics 위반율에 반영 | | gbc repos add [경로] | 크로스-repo 레지스트리에 추가(생략 시 현재 폴더) | | gbc repos list | 등록된 repo + 각 repo의 미해결 defer 수 + 게이트 건강성(hook 부재/구식 코호트) | | gbc repos remove [경로] | 레지스트리에서 제거 | | gbc doctor [--fix] | 하위 디렉토리에 남은 stray(화석) .gbc 마커 탐지·격리(0.12.2, 프로젝트 루트 해석 참조). --fix 없으면 보고만, 있으면 삭제 아닌 격리 | | /gbc-monitor 스킬 | 위 관측 명령(status·metrics --all·repos list·snapshot status)을 묶어 조회·해석하는 읽기전용 표면. 상태 변경은 /gate |

우회: GBC_NO_GATE=1 (계측됨 — 우회 자체가 게이트 가치 측정 데이터). scope 사후 판정 우회: GBC_NO_SCOPE=1. scope 모델 상향: GBC_SCOPE_MODEL=claude-sonnet-4-6(기본 haiku).

A-모드 미리보기 (gbc run) — 실험적

실험적이다(0.7.0 스파이크 → 0.8.0 사후대조 착지). 기본 제품은 위의 B-모드(Claude Code에 얹는 hook 게이트)이며, A-모드는 그 게이트 루프가 in-process로 구동되는 에이전트 위에서도 성립하는지 검증하는 실행 가능한 절개다. 0.8.0부터 A-모드 세션은 gbc score로 **통과 후 시나리오 위반율(진짜 M1)**을 사후 채점할 수 있다. 인터페이스·범위는 후속 버전에서 바뀔 수 있다.

B-모드는 게이트를 외부 stdin hook(Claude Code가 도구 실행 전 gbc hook pre-tool-use를 부름)으로 건다. A-모드는 같은 판정 코어(evaluateGate)를 @anthropic-ai/claude-agent-sdkin-process PreToolUse 콜백으로 걸어, gbc가 에이전트를 직접 구동한다.

# agent-sdk는 optionalDependency — A-모드 쓸 때만 설치. 버전 명시 필수
# (사내 프록시 레지스트리 환경 — [트러블슈팅](#사내-프록시레지스트리-nexusartifactory-환경))
npm i @anthropic-ai/[email protected]

gbc run "add.js에 두 수를 더하는 add(a,b)를 만들어줘" --yes
  • 게이트 = SDK 콜백 — 도구 실행 직전 evaluateGate가 발화한다(B-모드 stdin hook과 동일 판정·동일 .gbc/ 계측). 빈 명세면 차단→사유가 에이전트에 전달돼 시나리오 도출·gbc spec add→재시도→통과 사이클이 그대로 돈다.
  • 사람-pause(canUseTool) — 도구 실행 전 사람에게 허용을 묻는 최소 blocking(고무도장 방지). 미응답·빈 입력은 deny(안전측). ⚠️ 게이트(evaluateGate)는 코드 편집(Edit/Write/MultiEdit)만 판정하고, Bash 등 그 외 도구의 실행 승인은 오로지 이 pause에 달렸다. --yes는 비대화형(CI·자동)용 자동 허용이며 모든 도구를 무관문 자동승인(Bash 임의 명령 포함)하므로 신뢰할 수 있는 프롬프트에만 쓴다.
  • 관측 sink → 사후대조 채점 — SDK 스트림(도구 사용·판정·결과)을 .gbc/extraction.jsonl로 기록한다(session_id 조인키, 시크릿 redaction). 이 1차 자산을 gbc scoreevents.jsonl과 조인해 **통과 후 시나리오 위반율(진짜 M1)**을 채점한다(0.8.0) — 계측 참조.
  • 격리 규율 — agent-sdk에 API 키를 주입하지 않고(SDK 자체 인증 우선순위 사용), settingSources: []로 프로젝트 설정을 로드하지 않는다(gbc 자신의 stdin hook이 겹쳐 이중발화·재귀하는 것을 차단). B-모드 hook 핫패스는 agent-sdk를 절대 로드하지 않는다(런타임 격리).
    • 끊는 것: settings.json·CLAUDE.md·skills·hooks(settingSources:[]) + auto-memory(~/.claude/projects/<repo>/memory/settingSources:[]와 별개 레이어라 명시적으로 settings:{autoMemoryEnabled:false}를 추가 배선한다, 0.10.0). auto-memory를 안 끄면 spawn된 세션이 그 repo의 과거 작업 메모를 읽고(+쓰기 역류로 사용자의 Claude Code 프로젝트 메모리를 조용히 갱신할 수도 있고), keyless 환경의 게이트 판정(claude -p 폴백)도 "이미 완료됐다"는 메모리 서사에 편향돼 미완 작업을 통과시킬 위험이 있다(실측 확인 — CHANGELOG 0.10.0 참조).
    • 공유하는 것: 계정에 연결된 번들 스킬·MCP 커넥터 등 claude CLI 자체의 세션 부속 기능 — gbc가 별도로 끊지 않는다(끄려면 사용자가 자기 claude 설정에서 직접 조정).

풀스크린 TUI (gbc tui) — 0.9.0 A3a / 0.10.0 A3b(다중탭)

0.10.0부터 좌측 상시 사이드바에서 gbc repos에 등록된 repo를 opt-in 탭(Alt+1..9)으로 전환·병행할 수 있다. 0.10.1부터 대화는 사이드바 옆 박스형 대화창에 상주하며(시각행 기준 윈도잉+PgUp/PgDn 스크롤), 0.10.4부터 대화 스크롤백이 repo별로 격리 보존된다 — 탭을 오가도 각 repo의 대화가 사라지지 않고, 백그라운드 탭에 도착한 메시지도 그 탭에 쌓여 복귀 시 확인할 수 있다.

gbc run(A-모드 CLI 실행)과 같은 in-process 엔진 위에, 승인 프롬프트·실시간 게이트 줄·계측 패널을 갖춘 풀스크린 화면을 얹는다.

npm i [email protected] [email protected] @anthropic-ai/[email protected]   # optionalDependency — TUI 쓸 때만 설치, 버전 명시 필수(아래 참조)
gbc tui [--model <모델>]
  • 승인 프롬프트(y/n/e/d) — 게이트가 BLOCK하면 에이전트가 스스로 gbc spec add "..."를 도출해 재시도하는 사이클(시나리오 도출 루프)을, 화면에서 y(승인)/n(거부)/e(수정 후 승인)/d(defer로 보류) 4지선다로 사람이 pause한다. e/d는 그 호출이 실제로 gbc spec add를 도출한 경우(spec-add)에만 유효 — 그 외 일반 도구 승인(generic)에선 e/d 모두 n과 동일하게 거부로 처리된다(고무도장 방지 기본값, 화면 라벨에도 반영).
  • Alt+M / Alt+R / Alt+S 토글 패널 — 대화 흐름을 가리지 않고 진짜 M1(위반율)·M2/M3 계측(Alt+M), 등록 repo 게이트 상태(Alt+R), 설치 스킬 목록(Alt+S)을 겹쳐 확인한다. repos 패널은 ↑/↓·Enter로 해당 repo 탭 전환/opt-in까지 된다(0.10.4). Esc로 닫는다.
  • / 스킬 드롭다운 · ? 도움말 — 입력창에서 /를 치면 설치 스킬 드롭다운(전방일치 필터·↑/↓·Enter/Tab 완성)이 뜨고, 선택한 스킬은 SKILL.md 본문 주입 방식으로 발동한다(격리 규율상 SDK 자체 스킬 로드는 차단 유지). 입력창이 비어있을 때 ?는 전체 단축키 도움말 패널을 토글한다(0.10.4).
  • statusline 2줄 — 디렉터리·브랜치·dirty 여부·모델·비용·컨텍스트 토큰 사용량(12.3k/200k, 0.11.0부터)을 turn마다 갱신한다.
  • 프롬프트 히스토리(↑/↓) 로컬 영속(0.11.0) — 입력창에서 이전에 보낸 프롬프트를 repo별로 다시 탐색할 수 있다. ~/.gbc/prompt-history.json에 repo당 최근 100건까지 저장되며(파일 권한 0600, 저장 전 알려진 시크릿 패턴을 best-effort로 마스킹하지만 완전한 시크릿 스캐닝은 아니다 — 민감정보를 직접 타이핑하지 않도록 주의), GBC_NO_PROMPT_HISTORY=1로 끄면 저장·조회 둘 다 완전히 비활성화된다(이미 저장된 파일은 직접 삭제해야 한다).
  • 격리 규율은 A-모드와 동일gbc tui를 실행하지 않는 한 ink/react/agent-sdk는 로드되지 않는다(다른 모든 gbc 커맨드·B-모드 hook 핫패스 무영향, 회귀락: test/tui-isolation.test.mjs).
  • !<command> 셸 직접 실행(0.12.3) — 입력창에서 !로 시작하면 셸 명령을 직접 실행한다(Claude Code CLI ! 프리픽스와 동일 계열). 사람이 직접 타이핑하는 입력이라 모델 도구 경로가 아니고 게이트 판정을 전혀 경유하지 않는다 — 애초 게이트 범위 밖이라 무충돌. argv 배열로 실행하므로(셸 문자열 통짜 실행 아님) 파이프·리다이렉트·$() 명령치환·변수확장은 지원하지 않는다 — 따옴표('…'/"…")는 공백 포함 인자를 묶는 용도일 뿐 안에서도 확장되지 않는다. 대화형 명령(!vim 등)은 stdin이 없어 즉시 종료되거나 실패한다. 30초 타임아웃·64KB 출력 상한을 넘으면 강제 종료. 스트리밍 응답 중에는 실행할 수 없다(끝난 뒤 다시 시도). 끄려면 GBC_NO_BANG=1.

사내 프록시(레지스트리 Nexus/Artifactory) 환경

회사 npm 레지스트리가 registry.npmjs.org가 아니라 사내 프록시(npm config get registry로 확인 가능)면, gbc tui/gbc run 설치가 아래 순서로 조용히 꼬일 수 있다(실사용 재현 사례):

  1. 캐럿(^)이나 버전 미지정 설치는 하지 않는다. npm i -g ink처럼 버전을 안 박으면, 사내 프록시가 캐싱한 "latest" 메타데이터가 실제 npmjs.org보다 오래됐을 수 있어(미러 동기화 지연) 의도한 버전과 다른 게 깔린다. 패키지 세 개(ink·react·@anthropic-ai/claude-agent-sdk)는 항상 정확한 버전으로, 한 번의 명령에 같이 설치한다(위 코드 블록 그대로 복붙 — 개별로 나눠 설치하면 npm이 peer dependency를 따로 해석해 React가 중복 설치될 수 있다):
    npm uninstall -g ink react @anthropic-ai/claude-agent-sdk
    npm install -g [email protected] [email protected] @anthropic-ai/[email protected]
  2. optionalDependencies는 실패해도 npm이 조용히 넘어간다(exit 0). npm install -g geobuke-code가 "성공"으로 끝나도 사내 레지스트리가 이 세 패키지(특히 스코프 @anthropic-ai/*)를 미러링 안 하거나 차단하면 실제론 하나도 안 깔려있을 수 있다. gbc tui 실행 시 뜨는 안내 메시지가 "설치 안 됨"/"버전불일치"/"React 인스턴스 중복" 중 무엇인지에 따라 원인이 다르다(0.9.1부터 진단 메시지가 구체적으로 알려준다 — 안 뜨고 원시 스택트레이스가 그대로 보이면 이슈로 알려달라).
  3. 직접 진단:
    npm ls -g ink react @anthropic-ai/claude-agent-sdk
    invalid나 버전 불일치가 보이면 위 1번 명령으로 다시 잡는다. 그래도 설치 자체가 안 되면(npm install -g [email protected] ...이 에러) 사내 레지스트리가 해당 패키지를 프록시하지 않는 것 — 레지스트리 관리자에게 @anthropic-ai/* 스코프·ink·react 미러링을 요청한다.

실행 환경의 보안정책이 claude 실행파일을 차단하는 경우 (spawn EPERM/EACCES)

gbc run/gbc tui@anthropic-ai/claude-agent-sdk가 내려받은 번들 claude.exe를 자식 프로세스로 spawn해 구동한다. 실행 환경의 보안정책이 이 실행파일 경로를 차단하면 프롬프트 제출 시 spawn EPERM(또는 EACCES)이 나며, gbc가 자동으로 원인·우회 안내를 낸다.

# bash / zsh / WSL / Mac
claude --version   # 성공하면 그 claude가 이미 허용된 설치본
GBC_CLAUDE_PATH=/path/to/allowed/cli.js gbc tui   # JS 설치본의 cli.js 경로를 SDK에 지정해 우회
# native Windows (PowerShell)
claude --version
$env:GBC_CLAUDE_PATH="C:\path\to\node_modules\@anthropic-ai\claude-code\cli.js"; gbc tui
  • claude --version이 실패하면(예: Access denied) claude 실행파일 자체가 이 환경에서 어디서도 허용되지 않는 것 — 환경 관리자(보안 담당)에게 예외를 요청해야 한다(GBC_CLAUDE_PATH로 우회 불가).
  • 실측 확인된 우회는 번들 claude.exe가 아니라 **npm(JS) 설치본의 cli.js**를 가리키는 것이다(예: node_modules/@anthropic-ai/claude-code/cli.js) — GBC_CLAUDE_PATH 환경변수로 지정하면 gbc run·gbc tui 둘 다 읽는다(SDK Options.pathToClaudeCodeExecutable에 그대로 전달).
  • 지정한 claude 버전이 SDK가 기대하는 프로토콜과 안 맞으면 같은 허용 경로에 해당 버전을 추가 설치해야 할 수 있다. SDK가 어느 claude와 짝인지는 번들 바이너리에 직접 물어보면 된다(추측하지 말 것) — @anthropic-ai/claude-agent-sdk-<플랫폼>/claude --version. 이 저장소가 고정한 SDK 0.3.202는 실측 2.1.202 (Claude Code)였다.

크로스-repo 가시성

여러 repo를 오가며 작업할 때, 다른 repo에 걸린 미완 작업을 그 repo를 열지 않고도 인지하기 위한 기능이다. 세션 진입(SessionStart) 시 현재 repo의 미해결 defer 상세에 더해, 등록된 다른 repo들의 미해결 defer 요약을 한 줄로 환기한다.

gbc init을 하면 그 repo는 레지스트리에 자동 등록된다(opt-out: gbc init --no-register). 따라서 보통은 아래를 따로 칠 필요 없이, init한 repo들이 서로의 미완 작업을 자동으로 환기한다. 수동 관리가 필요할 때만:

# 감시할 repo를 글로벌 레지스트리(~/.gbc/repos.json)에 등록 (각 repo에서 1회, 또는 경로 지정)
gbc repos add                 # 현재 폴더
gbc repos add /path/to/other-repo
gbc repos list                # 등록 현황 + 각 repo 미해결 defer 수

이후 등록된 repo에서 세션을 열면 진입 시 이렇게 뜬다:

🐢 거북이 게이트 — 미해결 defer 1건 (진행중 0 · 미착수 1, 이전 작업 잔여):
1. [미착수] dist 재빌드 자동화                     ← 현재 repo: 번호 매긴 상세
필요하면 사용자에게 이어서 처리할지 확인하세요. 규약 — …

🌐 타 repo 미해결: dev-note 진행중1·미착수1 · fa-support 미착수1   ← 등록된 타 repo: 카운트만
  • 카운트만 표시한다 — 번호 매긴 상세 리스트는 현재 repo에만. 번호는 gbc defer <N> 인덱스 ref와 현재 cwd 기준으로 묶이므로, 타 repo에 번호를 주면 "어느 repo의 N"인지 ref가 깨진다. 타 repo 항목을 다루려면 그 repo로 들어가서 번호로 조작한다.
  • SessionStart에서만 환기한다(매 대화 종료 Stop 리마인드엔 미첨부 — 노이즈 방지). 현재 repo와 미해결 0건 repo는 요약에서 제외(전부 깨끗하면 🌐 줄 자체가 없다).
  • gbc가 설치되지 않은(.gbc/ 없는) repo는 등록돼 있어도 조용히 건너뛴다(fail-silent) — 레지스트리는 넉넉히 등록해도 안전하다.
  • 끄려면 GBC_NO_CROSS_REPO=1(이 줄만) 또는 GBC_NO_SESSION_HINT=1(세션 진입 힌트 전체).

CLI에서 repo별 alias(alias cc-x='cd <dir> && claude')를 쓴다면, alias에 gbc repos add . 2>/dev/null;를 끼워 여는 repo를 자동 등록할 수 있다(등록은 멱등).

게이트 건강성 롤업 (gbc repos list)

"회사 repo에서 게이트가 조용히 안 먹는다"를 한 명령으로 진단한다. gbc repos list는 각 등록 repo의 .claude/settings.json을 읽어 hook 등록 상태를 표시한다:

📁 등록된 repo 3개:
  [○깨끗] /path/to/healthy
  [○깨끗] /path/to/old        ⚠️SessionStart누락        ← 0.2.1 이하 init 코호트
  [●미해결2] /path/to/broken  ⚠️게이트hook부재          ← PreToolUse 게이트가 아예 없음(조용히 죽음)

⚠️ 게이트 hook 부재/SessionStart 누락 repo는 해당 repo에서 'gbc init --yes' 재실행으로 복구하세요.
   (크로스-repo는 hook *등록 여부*만 검사 — 명령 freshness[설치경로 의존]는 각 repo에서 'gbc status'로 확인)
  • 검사 대상: 게이트 hook(PreToolUse)·SessionStart hook의 등록 여부. 둘 다 cliPath 없이 결정론적으로 판정된다.
  • 검사 안 하는 것: hook 명령의 freshness(구버전 prefix 등). gbc는 단일 전역 설치지만 각 repo의 hook 명령엔 설치 시점의 절대경로가 구워져 있어, 현재 런타임 경로로 타 repo를 stale 판정하면 false-positive가 난다. 명령이 구식인지는 그 repo에서 gbc status로 확인한다(진짜 사후대조 freshness는 A-mode 과제).

프로젝트 루트 해석 — stray .gbc 마커

hook(PreToolUse/Stop/SessionStart)이나 CLI가 받는 cwd가 프로젝트 루트의 하위 디렉토리일 수 있다(에이전트 세션이 서브패키지 안에서 작업 중이거나, 서브에이전트가 cwd를 옮긴 경우 등). gbc는 git이 .git을 찾듯 조상 디렉토리로 올라가며 .gbc를 찾는다(resolveProjectRoot).

이때 .gbc가 하나가 아니라 여러 겹일 수 있다 — 과거 버전(0.12.2 이전)에서 하위 디렉토리를 한 번이라도 읽으면 그 자리에 빈 .gbc가 생기던 결함(gbcDir()의 mkdir-on-read)이 있었다. 이 화석이 하위에 남아 있으면, 조상 walk-up이 진짜 루트보다 먼저 화석을 만나 거기서 멈춰버려 진짜 루트의 spec.md·.claude/settings.json을 영원히 못 보게 된다(모든 판정이 "명세 소스: (없음)"으로 조용히 오판됨).

0.12.2부터 gbc는 .gbc를 찾을 때마다 형제 .claude/skills/gate/(gbc init만 만드는 설치 마커) 존재 여부로 진짜/화석을 구분한다 — 형제가 없으면 화석으로 보고 계속 조상으로 올라간다. 이 판별은 내용(spec.md 비어있음 등)이 아니라 구조만 보므로, 방금 gbc init한 빈 프로젝트나 gbc done 직후(spec.md가 의도적으로 비워짐)에도 오탐하지 않는다.

과거에 이미 생긴 화석은 근본수정만으로는 사라지지 않는다(더는 판정을 가로채지 않을 뿐, 디스크엔 남아 있다). gbc doctor로 찾아 정리한다:

gbc doctor          # 등록 repo(gbc repos)를 순회해 화석 .gbc 보고만(read-only)
gbc doctor --fix    # 삭제가 아니라 <repo>/.gbc-quarantine-<시각>/ 로 격리(포렌식 보존)

계측 (M1~M3)

게이트는 모든 결정을 .gbc/events.jsonl(append-only, 메타데이터만 — 코드 본문 미기록)에 기록한다. gbc metrics로 집계를 본다. 끄려면 GBC_NO_METRICS=1.

여러 repo의 게이트 ROI를 한 번에 보려면 gbc metrics --all — 등록된 repo들의 events.jsonl을 병합 집계한다. 병합 시 각 이벤트의 specHash를 repo 경로로 태깅해 repo간 boilerplate 명세 해시 충돌을 막는다(태깅 없이 합치면 한 repo의 통과 뒤 다른 repo의 변이가 M1 churn으로 오집계된다; M2/M3는 세션 UUID 키라 원래 안전). symlink로 등록된 경로는 거부한다(등록 경로 밖 임의 디렉터리 읽기 차단).

근거주입(P2b) 롤업

차단 판정은 곧바로 확정되지 않는다 — 누락으로 지목된 케이스의 심볼을 저장소에서 grep해 실측 근거를 찾고, 매치가 있으면 그 근거를 함께 실어 한 번 더 판정한다(절단선 밖·다른 파일에 이미 구현된 형제 케이스를 판정 모델이 못 보던 것이 오탐의 실측 주지분이었다). gbc metrics[P2b] 블록이 그 발화·전환율·제약을 보여준다.

무엇이 외부로 나가는가 — 근거는 judge(Anthropic haiku) 프롬프트에 실린다. 즉 차단 판정마다 여러 파일의 코드 조각이 전송된다(기존에도 편집 본문과 편집 대상 파일 상태는 전송됐다). 조립 단계에서 시크릿 패턴을 마스킹하고 --include allowlist·--exclude-dir.env·node_modules 등을 애초에 스캔 대상에서 뺀다. 게이트 자체를 끄려면 GBC_NO_GATE=1. ⚠️ 근거는 저장소 파일에서 오므로, 저장소에 쓸 수 있는 주체는 그 텍스트로 판정에 영향을 줄 수 있다(모델 지시 심볼 존재 ≠ 구현 증거가 완화 장치이지 구조적 통제는 아니다).

적용 범위(과대주장 금지) — grep 대상 확장자(.ts/.js/.tsx/.jsx)에서만 동작하고, Write(전체 덮어쓰기)와 케이스 구현을 삭제하는 Edit에서는 억제된다(그 코드는 곧 사라지므로 구현 증거가 아니다). 근거가 "헬퍼 함수가 존재한다" 수준이면 판정은 뒤집히지 않는다 — 호출·배선이 근거에 없으면 여전히 누락으로 본다. 수집은 grep 8회·8초 예산 안에서만 돌고, 초과분은 "못 봤음"으로 정직 표기된다(매치 없음과 구분).

[현재 파일 상태] 절단(0.12.1 P3) — 편집 대상 파일이 8000자를 넘으면 head 고정분(4000자)에 더해 **이번 편집 위치 주변(±1500자)**을 윈도우로 함께 보여준다(이전엔 head-only라 절단면 뒤쪽 정당 기구현 형제가 항상 안 보였다). 전송 총량은 그대로다(8000자 예산 불변, 지연·비용 부담 없음) — 다만 위 "무엇이 외부로 나가는가"의 노출 조건이 "head 안"에서 "head 또는 편집 위치 근처"로 넓어졌다는 뜻이다(security-auditor 지적, 2026-08-10). 공격자가 편집 위치를 직접 통제하긴 어려워 실익은 제한적이지만, 저장소에 쓸 수 있는 주체가 판정에 영향을 줄 수 있다는 구조적 한계 자체는 P2b와 동일하다.

| 지표 | 관측 | B-모드 신뢰도 | |---|---|---| | M2 게이트 적중 vs 도중발견 | 차단이 잡은 누락 케이스 수 vs defer add로 도중 등록된 수 | (defer-registry와 1:1) | | M3 재호출/iteration | 작업단위당 편집 반복 횟수 | proxy | | M1 post-gate 재작업 | 통과 후 churn(spec 변경·gate reset·defer) | (churn proxy) — A-모드 세션은 아래 진짜 M1로 승격 |

진짜 M1 — 사후대조 채점 (0.8.0, A-모드 세션)

진짜 M1(통과 후 시나리오 위반율)은 게이트가 "모델이 실제로 무엇을 했나"를 채점하는 사후 대조가 필요하다 — B-커널(hook)은 엔진 출력을 못 봐 churn 약신호까지다. A-모드(gbc run)는 .gbc/extraction.jsonl에 엔진 출력을 남기므로, 이걸 events.jsonlsession_id로 조인해 두 지표를 산출한다:

gbc run "…" --yes      # A-모드 세션 → extraction.jsonl 축적
gbc score              # 사후대조 채점(후보당 모델 1호출) → .gbc/scores.json
gbc metrics            # [진짜 M1] 섹션에 위반율·오탐율 반영
  • 위반율(LLM 사후대조) — 통과 당시 명세(specHash로 현행 spec.md 또는 spec.archive에서 resolve)와 세션의 실제 편집 묶음을 대조해 violated/compliant/unscored 판정. 분모는 채점 완료분만(unscored를 넣으면 위반율이 과소평가된다), 명세를 resolve 못 하면 다른 명세로 채점하지 않고 unscored(오염 금지). 채점은 비용이 드는 명시 명령(gbc score)이다 — 게이트 핫패스·metrics 순수 집계에 절대 실리지 않는다.
  • 오탐율(행동신호) — 차단의 정당성을 LLM 재판정이 아니라 차단 이후 실제로 일어난 일로 분류한다(게이트 자체가 LLM이라 재판정은 일치도지 truth가 아니다): block→spec 보강→통과=정상작동 / block→편집 수정만으로 통과=자가수정(모호) / block→gate-reset·bypass=무시, block→포기=오탐 후보 / block→fail-open 통과=판정불능(분자·분모 모두 제외). events만으로 산출돼 B-모드 차단에도 적용된다.
  • 정직 규율 — 표본 0이면 rate를 0%가 아니라 —(표본 0)으로 표시하고, extraction 없는 B-모드 세션은 채점가능 분모에서 정직하게 제외된다. 통계적 유의미성은 코퍼스 축적(도그푸딩)에 달렸다 — 지금 숫자는 기전 실증이지 대표 통계가 아니다.

판정 드리프트 회귀락 (gbc gate snapshot)

게이트 판정은 LLM(haiku)이라 모델 업그레이드·프롬프트 수정·SDK 변경이 같은 편집에 대한 통과/차단을 조용히 바꿀 수 있다. 골든셋은 실제 판정을 캡처해두고 나중에 재판정해 그 드리프트를 잡는다.

gbc gate snapshot on          # 캡처 모드 ON (opt-in)
# … 평소처럼 작업 — judge가 평가하는 편집이 .gbc/golden.json에 기록된다 …
gbc gate snapshot list        # 캡처된 케이스 확인 (판정·도구·편집 머리말)
gbc gate snapshot off         # 캡처 종료

# 나중에(모델/gbc 업그레이드 후 등) 드리프트 점검:
gbc gate snapshot replay              # 각 케이스를 temp 0으로 재판정, 캡처 시점과 비교
gbc gate snapshot replay --samples 5  # 케이스당 5회 모달 판정(잔여 비결정 흡수)
  • 하드 신호 = 판정 뒤집힘(pass↔block)만. 하나라도 뒤집히면 replayexit 1(로컬 pre-flight 게이트로 사용 가능). missing[] 변화는 LLM 자유서술이라 정보용으로만 표시하고 절대 실패시키지 않는다.
  • 결정성: replay는 judge를 temperature 0으로 재실행한다(핫패스 게이트는 불변). claude -p 폴백 트랜스포트는 temperature 핀을 지원하지 않아 best-effort다 — 직접 API 키가 있을 때 가장 안정적. temp 0도 bit-stable은 아니므로 --samples N으로 다수결 모달을 쓸 수 있다.
  • 캡처 시점: judge가 실제로 평가한 cache-miss 편집만 기록된다(cached-skip·fail-open 제외). 특정 편집을 캡처하려면 gbc gate reset 후 그 편집을 수행한다.
  • ⚠️ 로컬 전용(privacy): golden.json은 정규화된 편집 본문을 담는다 — events.jsonl이 불변식으로 절대 저장하지 않는 내용이다. .gbc/는 gitignore이므로 이 골든셋은 로컬 드리프트 점검이지 커밋되는 CI 스위트가 아니다. 공유 CI로 쓰려면 편집 본문을 커밋하는 privacy 트레이드오프를 명시적으로 감수해야 한다.

운영 현황 관측 (/gbc-monitor)

위 운영층 명령들(gbc status·gbc metrics --all·gbc repos list·gbc gate snapshot status)은 강력하지만, 세션 안에서 발견가능성이 없었다 — 명령·플래그를 외워 직접 치거나 매번 요청해야 했다. /gbc-monitor 스킬은 이들을 묶어 조회하고 해석하는 읽기전용 표면이다.

/gbc-monitor                  # 게이트 현황 종합(4개 묶음 + 해석)
게이트 현황 보여줘 / 계측 어때 / repo 건강성  # 자연어로도 트리거(특정 항목만도 가능)

핵심은 단순 별칭이 아니라 해석이다 — 숫자가 정상/주의/행동필요 중 무엇인지 판정해준다:

  • repos ✗부재 → 게이트가 조용히 죽은 상태 → 그 repo gbc init 재실행 안내(단 /tmp/* 잔재는 gbc repos remove 청소 후보)
  • M1 churn약신호 proxy — "통과 후 결함 수"로 과대해석 금지. 진짜 M1은 A-모드 세션 한정으로 측정된다(gbc score 사후대조, 0.8.0) — B-모드 churn은 여전히 proxy다
  • 명세 비어있음 → 차단 → 버그가 아니라 게이트가 의도대로 켜진 정상 동작
  • M2 도중발견 비율 낮음 → 누락이 늦게 새기보다 게이트가 사전 차단을 잘 한다는 긍정 신호

경계 — 관측하고, 액션은 가리킨다

/gbc-monitor/gate를 가르는 단 하나의 선: "그 명령이 상태를 바꾸거나(mutate) API를 쓰는가?" 그렇다면 /gate의 영역이다(미루기·리셋·snapshot on/off/clear·replay). /gbc-monitor읽기전용 조회만 하고, 변경이 필요하면 직접 실행하지 않고 /gate가리킨다. 이 분리 덕에 모니터링은 부작용 걱정 없이 언제든 안전하게 부를 수 있다. gbc init이 이 스킬을 /gate·/gbc-mute와 함께 설치한다.

정직한 한계

  • 자동 in-loop 게이트는 구현 전까지다 — "도중 탈선"은 못 잡는다(후속 C 영역). 응답 종료 시 scope 사후 판정은 자동이지만 차단이 아니라 권고라 강제력이 없고, 구현 gbc verify는 자동 루프 게이트가 아니라 명시 호출이고 바인딩·증거에 의존한다.
  • gbc verify의 강도는 사다리다: verified(테스트 실행 증명) > reviewed(LLM 코드 독해, 동작 보증 아님) > unverifiable(증거 없음). reviewed/unverifiable를 "통과"로 뭉치지 않는다 — 거짓 확신(reviewed→pass)·거짓 경보(unverifiable→fail) 둘 다 harm이다. 강한 검증을 원하면 ::test 바인딩 + 러너 JUnit 출력을 배선한다.
  • 판정은 LLM이라 100% 아니다. 사람이 변이 전 케이스를 리뷰/편집하는 pause가 진짜 가치다.
  • 제품 범위 = B-커널(CC-native hook + defer-registry + /gate) + 그 위 운영층(누락 케이스 일괄 분류·scope 사후 판정·크로스-repo 가시성/건강성·판정 드리프트 회귀락·관측 계측 M1~M3) + 사후 결과검증(gbc verify). standalone TUI는 후속 A(public) 영역이다. A-모드 엔진 래핑은 0.7.0 스파이크(gbc run)로 절개됐고, 0.8.0이 그 위에 진짜 M1 사후대조(gbc score)를 착지시켰다 — 단, 이것은 루프 자동 채점이 아니라 명시 호출이고(세션이 끝난 뒤 사람이 부른다), A-모드 세션(extraction 존재)만 채점할 수 있으며, 지금 숫자는 기전 실증 단계라 통계적 대표성이 없다. gbc verify는 그 사전 단계로, 명시 호출 시 케이스↔증거를 사다리로 대조한다.
  • 검증 상태: 게이트 판정 품질은 양 트랜스포트 모두 회귀 8/8(FP0 FN0). 직접 API(haiku) 경로 실측 평균 1.7s(1.1–2.5s), claude -p 폴백 ~18s. 직접 API용 게이트 프롬프트는 최소화하면서 정확도를 유지하도록 "동작 편집 vs 비-동작 편집" 2단계 분류로 튜닝했다(ANTHROPIC_API_KEY=… node dist/eval/regression.js로 재현).
  • fail-open: 판정 호출이 실패하면(키 오류·네트워크 등) 게이트는 안전하게 통과시킨다(개발 차단 방지). 단 fail-open 통과는 작업단위 캐시에서 제외되고(다음 편집 재판정), systemMessage 경고 + .gbc/failopen.log 계측으로 드러난다(조용한 무력화 방지).

라이선스

MIT