handleChat()에서 도구 선택 로직(145줄)을 순수 모듈로 추출. 이 로직은 2,959줄
함수 안의 클로저라 테스트가 불가능했고, 게이트를 확인하려면 게이트웨이를 띄우고
실제 채팅을 보내 SSE의 토큰 수를 읽는 수밖에 없었음.
- src/gateway/chat/tool-scope.ts (신규) — I/O 없는 순수 함수.
buildSkillToolFilter()는 스키마 객체가 아니라 도구 '이름'을 받게 해서
테스트에서 문자열만으로 구동 가능하도록 함
- tests/tool-scope.test.ts — 17개. 비용(불필요한 도구 미노출)과
도달성(요청한 도구는 반드시 노출) 양쪽을 모두 고정
- handle-chat.ts 3,131 → 2,992줄
추출과 동시에 테스트가 버그 3종을 찾음:
1. 한글 뒤 \b 는 절대 매칭되지 않음 (죽은 패턴 5개)
\b는 ASCII 단어경계라 한글 음절 뒤에서는 성립하지 않음. `메일\b`가 그 예로,
"메일 확인해줘"가 게이트를 통과하지 못해 이메일 도구가 아예 노출되지 않았음
("이메일"이라고 써야만 동작). 같은 죽은 패턴이 hasCodeKeyword(함수/클래스/
버그), hasNewsKeyword(기사), hasPptxKeyword(덱)에도 있었음. 배포 후 확인:
"메일 확인해줘"가 email_list를 정상 호출함
2. pptx 게이트 조건 반전
`presenterEnabled && !hasPptxKeyword`라 스킬이 '꺼진' 경우 배제가 아예
동작하지 않았음. 스킬이 꺼졌으면 더 제한해야 하는데 반대로 열려 있었던 것.
실제 영향 범위(skills_state.json 대조): papa·cherry는 presenter=true라 무관,
jasmine만 매 턴 ~1,600토큰(create_presentation 833은 최대 스키마)을 지고
있었음. weather/legal과 같은 형태로 정정
3. (테스트 작성 중) 원문을 줄여 쓴 케이스가 실패 → 이전 커밋에서 수정한
SPEC_CONTEXT_KEYWORD 구멍과 동일 계열임을 재확인
전체 47개 테스트 통과. 실측 오버헤드는 papa 기준 3,574로 동일(위 2번이
papa에겐 원래 정상 동작했으므로 예상된 결과).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tests/ 디렉터리가 비어 있었고 package.json의 두 스크립트가 삭제된 파일을
가리킨 채 방치돼 있었음:
- test → tests/test-v2.ts (존재하지 않음)
- gateway → src/gateway/server-v2.ts (v4.3.5에서 삭제됨)
즉 npm test가 몇 달간 실행 자체가 불가능한 상태였고, 그 대가로 프롬프트 게이트
정규식을 고칠 때마다 node -e 임시 스크립트를 손으로 짜고 버리는 일이 반복됐음
(2026-07-29 하루에만 5회). 회귀 방지는 하나도 남지 않았음.
- Node 22 내장 러너(node:test) + tsx 사용, 테스트 프레임워크 의존성 없음
- tests/prompt-gates.test.ts (25) — 모델 동작을 강제/억제하는 정규식 게이트 전반
- tests/usage-log.test.ts (5) — Ollama 외 provider의 토큰 집계 단일 지점
- tests/README.md — 무엇을 왜 테스트하는지, 케이스 작성 규칙, 다음 확장 대상
- 전체 30개 통과, 0.6초
부수 성과 — 테스트가 실제 버그를 찾음:
케이스를 쓰면서 프로덕션 원문을 한 단어(GPU) 줄여 썼더니 실패했고, 그게 진짜
구멍이었음. "토큰 처리 속도 손실이 약 15%~25%"처럼 하드웨어 명사가 없는 처리량
날조는 SPEC_CONTEXT_KEYWORD에 걸리지 않아 그대로 통과하고 있었음 — 원문에
우연히 들어있던 "GPU" 덕에 잡히던 것. 토큰/대역폭/추론/처리속도를 키워드에
추가해 막고, 그 케이스를 테스트로 고정함(강수확률·할인율 오탐 없음 확인).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>