grok v0.2.17 — 게이트를 지키던 것이 아무것도 없던 자리
v0.2.17 — 게이트를 지키던 것이 아무것도 없던 자리
2026-09-04. 세 번째 스윕이 남긴 새 기능 4건(E1~E4) 을 오너 승인 후 전부 구현했다. 넷 다
"기능이 없다"가 아니라 "검사가 없다" 였고, 착수 전에 각 항목을 다시 실측하고 Grok으로
반증까지 시킨 뒤 done 정의를 먼저 썼다.
동작은 하나도 바뀌지 않았다. 이 릴리스가 범프인 이유는 index.ts가 쪼개져 번들 바이트가
달라졌기 때문이다 — 캐시는 버전 키다.
E1 — MCP 툴 핸들러를 아무 테스트도 실행하지 않았다
실측(착수 전). isError: result.status !== 'completed'를 delegate·plan·verify 세 반환
지점에서 isError: false로 바꿨다. tsc --noEmit 통과, 352개 테스트 전부 녹색.
isError는 MCP 클라이언트가 "이 위임이 실패했는가"를 아는 유일한 신호다. 뒤집힌 계약은
grok_error로 끝난 실행을 Claude에게 성공이라고 말한다.
원인은 핸들러가 main() 안의 익명 클로저라 호출할 방법 자체가 없었다는 것이다. Grok도
같은 결론을 냈다 — 테스트 트리에 ../src/index import도, MCP 클라이언트도, callTool도 없고,
tool-surface.test.ts는 번들을 텍스트로 스캔할 뿐이다.
한 일. 등록과 핸들러를 src/server.ts로 옮기고 index.ts는 stdio 진입점만 남겼다 —
hook-entry.ts/hook.ts가 이미 쓰던 분리다. 핸들러가 손대는 모든 부작용은 ServerDeps를
지나며 기본값은 실제 구현이다. 그래서 테스트가 인메모리 전송으로 진짜 등록된 tool을
클라이언트처럼 호출한다 — 등록·스키마·핸들러 본문·반환 봉투가 한 번에 덮인다. grok 프로세스도
홈 디렉터리도 건드리지 않는다.
test/server-tools.test.ts가 고정하는 것:
- 9개 tool이 그대로 노출되는지 (
listTools) - delegate/plan/verify:
completed→isError: false, 그 외 →true - auth 사전 검사 실패 시 grok을 부르지 않고 즉시 중단
- 세 tool 모두
recordDelegation을 타이밍과 함께 호출 - plan은
plan: true, verify는check: true를 입력에 실어 보냄 grok_auth_check·grok_build_status는auth.ok를,grok_cli는error/timeout을,
worktree 5개 액션은 각각result.ok를 반영worktree_path없는 diff/apply/remove는 아무 작업도 실행하지 않고 fail closed
done 검증. 같은 뮤테이션을 다시 넣으니 이번엔 3건 실패한다. 원복 후 전부 녹색.
이 항목만으로 테스트 수 352 → 376.
E2 — 배포되는 skills/agents 프론트매터를 읽지 못하던 검사
plugin-surface.test.ts는 내부용 .claude/ 스킬 두 개에는 name 일치와 description 길이
40자 초과를 요구하면서, 실제로 배포되는 skills/grok-first-mile·skills/grok-routing·
agents/grok-worker에는 existsSync 세 줄만 걸어두고 있었다.
원인이 흥미롭다. 세 파일 모두 description: > 폴드 스칼라를 쓰는데, 기존 헬퍼의
/^([a-zA-Z0-9_-]+):\s*(.*)$/는 그 줄에서 ">" 하나만 잡고 들여쓴 본문은 버린다. 즉 기존
헬퍼로 길이 검사를 걸었어도 ">"의 길이를 재고 통과했을 것이다. Grok이 실제로 값을
측정해 description === ">" 임을 확인했다.
폴드 스칼라(>/|/>-/|-)를 이해하는 파서를 더하고, 세 파일의 name·description을
검증한다. 파서 자체가 접힌 값을 펴는지도 픽스처로 고정했다 — ">"를 검사하는 가드는
아무것도 지키지 못하기 때문이다.
done 검증. grok-routing의 description을 짧게 만들면 RED.
E3 — 선언된 버전에 태그·릴리스가 있는지 아무도 보지 않았다
v0.2.12는 매니페스트 두 곳에 선언된 채 태그도 릴리스도 없이 main에 있었다. 마켓플레이스
소스가 ./이고 캐시가 버전 키라, 그 사이 설치자는 그 번호로 옛 번들을 캐시한다. 그래서
번호를 재사용하지 못하고 버렸다.
.github/workflows/ci.yml은 push/pull_request에만 걸려 있고, 태그나 릴리스를 보는 검사는
저장소 어디에도 없었다(Grok 확인).
한 일. mcp-server/scripts/check-release-tag.mjs — 선언 버전의 git 태그와 GitHub 릴리스를
모두 확인하고, 실패 시 캐시 규칙을 인용해 무엇을 하라고 말한다. release-tag-check.yml이
이를 실행한다.
(CONTRIBUTING: "머지 직후 바로 태그"). PR 시점에 검사하면 모든 릴리스 PR이 오탐으로 빨개진다.
schedule(매일) + workflow_dispatch에서는 "선언된 지 하루가 지났는데 아직 태그가 없다"가
모호하지 않다. 체크아웃은 fetch-depth: 0 — 얕은 클론에는 태그가 없어 잘못된 이유로
통과하거나 실패한다.
done 검증. 스크립트를 로컬에서 세 번 돌렸다: 0.2.16(태그+릴리스) → 통과 /
0.2.12(소급 태그는 있으나 릴리스 없음) → 실패, 정확히 역사적 사고를 잡는다 /
9.9.9 → 둘 다 없다고 실패.
E4 — marketplace.json을 파싱하는 것이 하나도 없었다
모든 설치가 이 파일을 통해 플러그인을 찾는데, 테스트도 CI도 이 파일을 열지 않았다. JSON이
깨지거나 엔트리 이름이 plugin.json과 어긋나도 전부 녹색으로 배포된다.
유효한 JSON인지, name이 grok-marketplace인지, 엔트리가 정확히 하나이고 그 이름이
plugin.json의 이름과 같은지, source가 ./인지(버전 키 캐시 규칙이 여기에 의존한다)를
고정했다.
done 검증. 마켓플레이스 이름 변경 → RED. 엔트리 이름 드리프트 → RED.
검증
npm ci→ SDK 1.29.0 = lockfile 일치npm test379 passed / 1 skipped (v0.2.16 시점 352 — E1이 +24, E2·E4가 +3)npm run typecheck,npm run build— 번들 2개 재생성- 뮤테이션 3종을 실제로 넣었다 뺐다 하며 RED→GREEN 확인 (E1·E2·E4)
check-release-tag.mjs3케이스 실측 (E3)- Grok 적대적 교차검증 4건 — E1·E2·E3·E4 주장 전부 CONFIRMED, 파일 무변경
남은 것
이 레포에 열린 코드 항목은 없다. 남은 것은 사람 손이 필요한 것뿐이다 — 설치본 갱신,
GUI 수동 수락 1회, 만료 세션 실측(실계정+시간), SCAManager 토큰의 발급처 revoke 확인.
분류는 docs/09-scope-and-residuals.md.