---
name: write-gate
description: >-
  Pre-write and review gates for code changes. Pre-write: before
  creating any new function, helper, type, or file — 만들어줘, 추가해줘,
  구현해줘, 새 파일, 리팩토링, new helper/component. Post-write: after
  finishing a change — 다 했어, 리뷰해줘, 검토, review my change.
  Prevents duplicate reimplementation and enforces the review checklist
  including run-verification (scaled to change size/runtime surface — no
  full verification for small mechanical changes) and doc sync.
user-invocable: false   # 사용자 표면은 /hi-vibe:* 명령 10개다. 스킬까지 슬래시 메뉴에 나오면 16개가 되어 "외울 게 적다"는 약속이 깨진다. Claude의 자동 호출은 그대로 유지된다.
---

# write-gate

> **답변 언어**: 이 지침은 한국어로 쓰였지만, **출력은 항상 사용자가 대화에서 쓰는 언어**를 따른다 (한국어→한국어, 영어→영어). 기존 문서·코드에 언어가 있으면 그쪽을 우선한다.

## 먼저: 안전벨트가 매여 있는지 (두 모드 공통, 세션당 한 번)

**훅은 조용히 실패한다**(fail-open) — 망가져도 에러조차 안 뜬다. 그래서
"훅이 죽었나"를 훅으로는 알 수 없다(자기가 안 도니까). 확인할 수 있는 건
**훅과 무관하게 도는 이 스킬 층뿐**이다. 이 스킬은 사용자 말("만들어줘",
"다 했어")에 걸리므로 훅이 전멸해도 살아 있다.

이 스킬이 이번 세션에서 처음 걸릴 때 한 번만:

`python3 "${CLAUDE_PLUGIN_ROOT}/scripts/doctor.py" --root <repo> --quick`

JSON `state` 하나로 갈린다. **`alive`면 아무 말도 하지 마라** — 정상을
보고하는 건 잔소리다.

- **`alive`** → 침묵. 그대로 아래 작업을 진행한다.
- **`stale` / `never-ran`** → 훅이 이 세션에서 안 돌았다. 한 줄로 알린다:
  "hi-vibe 훅이 이 세션에서 안 돌고 있습니다(마지막 기록 N시간 전). 방금
  플러그인을 업데이트했다면 Claude Code를 다시 시작하면 되고, 아니면
  `/hi-vibe:doctor`로 확인하세요." **막지는 마라** — 훅 없이도 이 스킬은
  동작하므로 하던 일은 그대로 진행한다.
- **`not-initialized`** → 이 폴더는 hi-vibe가 꺼져 있다. 한 번만 알린다:
  "이 폴더는 hi-vibe가 꺼져 있습니다(자동 감지·기록 없음). 쓰시려면
  `/hi-vibe:init`, 안 쓰실 거면 그렇다고 말씀만 주세요." 사용자가 "안
  쓴다"고 하면 `.hi-vibe/optout` 파일을 만들어라(`mkdir -p .hi-vibe &&
  touch .hi-vibe/optout`) — 그러면 훅은 계속 꺼진 채로, 다시 묻지 않는다.
  **여기서 마음대로 init하지 마라.** opt-in은 사용자가 정한다.
- **`optout`** → 침묵. 이 폴더에서 다시는 묻지 않는다.

**`tracked_env`가 비어 있지 않으면** `state`와 별개로 **반드시 알린다**(세션당
한 번). 목록에 있는 `.env` 파일이 Git에 올라가 있다는 뜻이고, **비밀키 검사는
`.env`를 검사 대상에서 빼므로 이건 훅도 `check`도 못 잡는다.**

> `.env`가 Git에 올라가 있습니다(`<파일들>`). 비밀키 검사는 `.env`를 안 보기
> 때문에 이건 아무 검사에도 안 걸립니다. `git rm --cached <파일>` 후
> `.gitignore`에 넣으세요. **이미 push했다면 히스토리에 남아 있으니 그 키는
> 폐기(rotate)해야 합니다.**

파일을 열어 보지 마라 — 내용을 읽는 순간 대화창이 유출 통로가 된다. 지워주지도
마라(사용자가 결정한다). 알리고 하던 일을 계속한다.

조회 자체가 실패하면(스크립트 없음·python3 없음) 조용히 넘어간다 — 확인을
못 한 것이 작업을 막을 이유는 아니다.

## Mode: find (코드 작성 전)

새 function/helper/type/파일을 만들기 전에, 순서대로:

1. **존재 확인**: repo-xray로 그럴듯한 이름 2~3개를 검색한다.
   `python3 "${CLAUDE_PLUGIN_ROOT}/skills/repo-xray/scripts/audit.py" find <name> --root <repo>`
   결과의 스캔 범위를 인용해 판정한다 (repo-xray 계약).
2. **외부 API 근거 확인**: 새 코드가 외부 라이브러리/프레임워크의
   API를 사용하거나 버전에 민감한 부분(파라미터명, 옵션, 반환 형태,
   설정 키)을 다루면, 기억으로 쓰지 않는다. context7 MCP가 연결되어
   있으면 `resolve-library-id` → `query-docs`로 최신 문서를 확인하고,
   없으면 공식 문서를 WebFetch로 확인한다. 둘 다 불가하면 추정임을
   밝히고 쓴다 (grounded-answers 계약). 프로젝트 내부 코드만 다루는
   경우 이 단계는 건너뛴다.
3. **위치 확인**: 대상 폴더의 `MODULE.md`를 읽는다 — 이 코드가 그
   폴더의 책임에 맞는가? 안 맞으면 맞는 폴더를 찾거나 사용자에게 묻는다.
4. **SSOT**: 공용 유틸/타입/shape은 지정된 공용 위치에 한 번만.
   로컬 복사본을 만들지 않는다.
5. **배치**: 새 파일은 위계(도메인/레이어)에 맞게. 루트에 평탄하게
   쌓지 않는다. 처음부터 파일을 나눈다 — 한 파일에 몰지 않는다.
6. **경계 선언**: 이 코드가 import해도 되는 것 / 이 코드를 import해도
   되는 곳을 한 줄로 선언하고 시작한다.

판정은 셋 중 하나: **재사용** (기존 것 그대로) / **확장** (기존 것
수정) / **신규** (근거: 스캔 범위 내 없음).

### 요청 범위 밖을 발견하면 — 고치지 말고 물어라

코드를 보다 보면 요청과 무관한 문제가 눈에 띈다. 그건 좋은 발견이지만
**거기서 손대기 시작하면 "한 줄 고쳐줘"가 기능 추가가 된다.** 실제 사례:
"사이트에 적어줘"라는 요청으로 시작해 봇 기능 신설까지 번졌고, 그 새 기능의
버그를 잡느라 작업이 배로 늘었다.

- **요청한 것만 한다.** 밖에서 발견한 건 **한 줄로 보고하고 사용자가 정한다**:
  "사진이 담겼는지 아무도 모르는 상태예요. 지금 고칠까요, 따로 볼까요?"
- 사용자가 "같이 해줘"라고 하면 그때 한다. **묻는 데 드는 건 한 줄이고,
  안 물으면 몇 배가 든다.**
- 예외는 **요청한 것을 하려면 반드시 통과해야 하는 것**뿐이다(그게 막혀서
  요청 자체가 불가능한 경우). 그땐 왜 필요한지 밝히고 진행한다.

이건 "발견하지 마라"가 아니다. 발견은 하되 **범위를 넓히는 결정은 사용자
것**이라는 뜻이다.

## Mode: review (코드 작성 후)

**플래그가 없다.** 범위·깊이·병렬 여부는 아래 순서대로 **기계가 준 숫자를
보고 여기서 판단한다** — 사용자에게 고르라고 묻지 않는다. 사용자는 무엇을
켜야 좋은지 알 수 없고, 물어보는 순간 제일 좋은 기능이 안 켜진 채로 끝난다.

### 1. 범위 (헬퍼가 코드로 정확히 정한다 — AI가 해시를 세지 않는다)

`python3 "${CLAUDE_PLUGIN_ROOT}/skills/write-gate/scripts/review_scope.py" list --root <repo>`
→ `scope`·`scope_label`(어느 계단에서 잡았나), `to_review`(아직/다시 봐야 할
파일), `skipped`(이미 봤고 그 뒤로 안 바뀐 것), `sizes`·`total_changed_lines`·
`file_count`(규모), `fingerprint`.

- **`deleted`가 비어 있지 않으면** 그 파일들은 열어볼 수 없다. 대신
  **남은 호출부**를 찾아라 — `import`·호출·문자열 경로. AI가 파일을 통째로
  지우고 부르던 곳을 안 고친 경우가 실제 위험이고, 지운 코드는 리뷰 대상에서
  빠지기 쉬워 아무도 안 본다. 남은 참조가 있으면 그 자리에서 고친다.
- **`to_review`가 비었으면**(그리고 `deleted`도 비었으면) "새로 리뷰할 변경이
  없습니다 (이미 본 것 N개는 그대로)"만 알리고 끝낸다 — 억지로 훑지 않는다.
- **`scope`가 `uncommitted`가 아니면** 무엇을 보고 있는지 한 줄로 밝힌다:
  "안 커밋한 변경이 없어서 <scope_label>을 봅니다." 커밋·푸시했다고 리뷰가
  죽지 않게 계단으로 내려가지만, 사용자는 지금 뭘 보는지 알아야 한다.
- **사용자가 범위를 말했으면**(예 "로그인 쪽만", "3일치", "이 파일만") 그쪽을
  우선한다. 헬퍼 범위보다 사용자 말이 위다.
- **git 저장소가 아니면** 범위를 못 구한다 — 그 사실을 알리고, 이번 세션에
  만진 변경을 사용자와 확인해 아래 체크리스트로 처리한다.

### 2. 규모가 크면 병렬 (묻지 말고 알리고 진행)

**임계값을 코드에 박지 않는다** — 기계는 `total_changed_lines`·`file_count`만
주고, 판단은 여기서 한다. 어림잡아 **파일이 여러 개이면서 수백 줄 이상**이면
병렬로 간다(정확한 컷은 상황 판단). 순차는 뒤로 갈수록 얕아지는데, 그걸
감수하고 순차를 고를 사용자는 없다.

병렬로 갈 때는 **고르라고 묻지 말고, 무엇을 왜 하는지와 대가를 한 줄로 알리고
그대로 진행한다**:

> 변경이 큽니다 (12파일 1,430줄). 얕아지지 않게 병렬로 봅니다. 토큰을 더 씁니다.

`python3 "${CLAUDE_PLUGIN_ROOT}/skills/write-gate/scripts/review_scope.py" chunk <N> --root <repo>`
로 줄 수 균형이 맞은 `buckets`(파일 그룹) N개를 받아, **각 버킷마다 Agent
도구로 리뷰어를 하나씩 병렬 소환**한다. 각 리뷰어에게 "이 파일 묶음에 아래
체크리스트를 적용하고 ✅/⚠️ 요약을 돌려달라"고 시키고, 결과를 모아 통합
보고한다. N은 규모에 맞게(대개 3~6), 버킷 수를 넘기지 않는다. 규모가 작으면
이 단계를 건너뛰고 그냥 순차로 간다.

사용자가 "가볍게 봐줘"(영어로는 "keep it light" / "light review")라고 했으면
병렬도 fresh-eyes도 생략하고 체크리스트만 돌린다 — 끄는 건 플래그가 아니라
말로 한다. 영문 랜딩이 이 말을 escape hatch로 안내하므로 영어 표현도 받는다.

### 3. 체크리스트

`to_review`의 각 파일(=기능 덩어리)에 아래를 적용한다(병렬이면 각 버킷
안에서). 각 항목을 ✅/⚠️로 보고한다. ⚠️는 이유와 수정 방법을 붙인다.
비례 원칙: 이번 변경과 무관한 항목은 "해당 없음" 한 마디로 통과시켜라
— 형식적 도배도, 통째 생략도 금지. 단 9·10(필수)은 항상 답한다. 파일이
많으면 파일별 한두 줄 요약(✅/⚠️)으로 압축하고 ⚠️만 펼치되, 실행 검증·문서
동기화(9·10)는 전체에 대해 마지막에 한 번 종합해 답한다.

**자기 점검 루프 (핵심)**: ⚠️는 보고하고 끝내는 게 아니다. 그 자리에서
고친 뒤 해당 항목을 다시 검사해 ✅로 만든다 — 통과할 때까지 반복.
바로 못 고치는 ⚠️만 남기되, "왜 지금 못 고치는지 + 언제/어떻게 고칠지"를
사용자에게 명시한다. 체크리스트는 흠집 목록이 아니라 흠집을 없애는
루프다.

**단, 고치기 전에 어느 쪽인지 가른다 (필수):**

- **원래 요구사항을 어긴 결함** → 그 자리에서 고친다. 예: "일부만 저장됐는데
  전부 성공이라고 표시한다" — 사용자가 요청한 적 없는 거짓 보고이므로 물어볼
  게 없다.
- **새 동작·알림·정책이 필요한 것** → **고치지 말고, 발견 사실과 대안을
  보고하고 확인받는다.** 예: "권한이 없으면 회의당 한 번 안내 메시지를
  보내자" — 없던 사용자 경험을 새로 만드는 결정이다.

가르는 기준 한 줄: **이걸 고치면 사용자가 여태 못 보던 화면·메시지·규칙이
새로 생기는가?** 생기면 묻는다.

이 구분이 없으면 리뷰의 자동 수정 루프가 **기능 확장으로 번진다**. 그건 이
플러그인이 막으려는 증상("헷갈리는 결정을 사용자에게 묻지 않고 임의 진행")을
리뷰가 직접 저지르는 것이고, `fresh-eyes`가 잡으라고 있는 스코프 크립을
같은 리뷰가 만들어내는 자기모순이다.

1. 에러 삼킴을 새로 추가하지 않았는가 (root-cause-first 금지 패턴)
2. 중첩 ≤ 3단계 (넘으면 early return / guard clause로 평탄화)
3. 크기 상한: 함수 ~50줄, 파일 ~400줄. 파일은 `list`의 `oversized`를 그대로
   쓴다(눈으로 세지 마라). **`lines - growth`가 400 이하면 이번에 처음 넘긴
   것 — 그것만 ⚠️로 펼쳐 분리를 제안한다.** 원래 넘어 있던 파일은 한 줄로
   축약하고(`kis_client.py 686줄, 이번 +26`), `growth`가 음수면(줄인 것)
   아예 짚지 마라. 같은 경고를 매번 똑같이 펼치면 쌓여서 신호가 아니게
   된다. **기준 400은 그대로다 — 무르게 하는 게 아니라 반복을 줄이는 것이다.**
4. 중복 생성 vs 교체: 기존 코드를 대체했다면 옛 버전을 지웠는가,
   호출부를 옮겼는가, 죽은 코드/주석 처리 코드가 남지 않았는가
5. 안 쓰는 re-export/import 잔재가 없는가
6. 순환 의존을 새로 만들지 않았는가 (의심되면 repo-xray scan)
7. 숨은 결합을 새로 만들지 않았는가 — 전역 상태 공유, 초기화 순서
   의존, import 부수효과, 암묵 계약(호출 순서/형태를 문서 없이
   가정), 한 모듈로의 과도한 fan-in/fan-out 쏠림
8. 테스트: 문자열 비교가 아니라 동작을 검증하는가, mock은 외부
   경계에서만인가, 엣지케이스(빈 값/None/0/경계값/실패 경로)를
   먼저 다뤘는가, 테스트 통과용 방어코드를 프로덕션에 넣지 않았는가
9. **실행 검증 (변경 크기·런타임 표면에 비례)**: 검증 강도는 이번
   변경이 실제로 만든 위험에 맞춘다 — **작은 변경에 풀 검증을 요구하지
   않는다.** 세 등급으로 판단한다:
   - **런타임 표면 없음** (문서·주석·순수 포맷, 동작이 그대로인 설정/
     이름 변경) → 실행 검증 불필요. "런타임 표면 없음"으로 통과하고,
     앱을 억지로 띄우지 않는다.
   - **작은·국소 변경** (기존에 검증된 패턴을 그대로 복제, script 태그·
     설정 한 줄 추가, 좁은 범위 수정) → **가벼운 검증으로 충분하다**:
     구문/임포트 체크(`node --check`·import), 파일이 실제로 서빙/로드
     되는지 확인 정도. 풀 브라우저 E2E나 여러 조건 렌더는 불필요.
   - **동작을 바꾸는 변경** (로직·API·스키마·새 기능·버그 수정) →
     실제로 실행해 동작을 관찰한다 — 테스트 실행, 앱 구동, 버그면 원래
     재현 케이스 재실행. 작성자가 눈으로 읽는 것은 검증이 아니다.
   **UI·CSS·레이아웃·스타일 변경 → 사용자가 검증 루프다 (특례)**:
   생김새·간격·정렬·색·반응형 같은 시각적 변경은 위 등급과 별개로,
   **에이전트가 브라우저(Playwright 등)를 띄워 자가 검증하지 않는다.**
   사용자가 앱을 직접 띄워 보고 있으면 화면을 보는 것 자체가 검증이고,
   에이전트가 딴 브라우저를 또 띄우는 건 이중일이다(레이아웃이 어긋나면
   사용자가 즉시 본다). 대신: ①변경하고 ②캐시버스팅(`?v=N`)했으면 그
   사실과 함께 ③"새로고침(⌘⇧R) 후 무엇을 확인하면 되는지"를 한 줄로
   알려주고 사용자에게 넘긴다. 같은 화면을 여러 상태(모드·화면폭·로딩중)로
   반복 렌더해 자가 확인하는 것은 금지. CSS 값은 브라우저로 픽셀을 실측해
   상수로 박지 말고(예: `min-height:529px`는 행 높이가 바뀌면 깨지는 땜빵)
   콘텐츠 기반(행 수·`rem`·내용에 맞는 예약)으로 견고하게 잡는다. **예외**:
   사용자가 "브라우저로 확인해줘"라고 명시했거나, 특정 화면폭에서만 깨지는
   회귀처럼 눈으로 못 잡고 렌더로만 재현되는 경우 → 그때만 최소 렌더.
   **최소 충분 원칙 (과잉검증 금지)**: 한 번의 관찰로 확인되면 멈춘다.
   같은 것을 여러 조건(화면 폭·브라우저·반복 실행)으로 다시 확인하거나,
   요청 범위 밖 코드까지 파고들어 검증하지 않는다 — 그건 꼼꼼함이 아니라
   시간 낭비다(사용자가 답변 하나에 12분을 기다리게 만든다). 해당 등급에
   필요한 실행을 못 했다면 "실행 검증 안 됨"과 이유를 명시한다 —
   "될 겁니다"는 금지 (grounded-answers 계약).
10. **문서 동기화 (필수, 자동)**: 폴더 책임이 바뀌었으면 → 해당
    `MODULE.md`를 같은 턴에 갱신. `CLAUDE.md`는 **코드만 봐서는 모를 것**이
    바뀌었을 때만 건드린다 (새 제약·새 함정·기록할 결정·새로 만든
    MODULE.md 포인터). 파일을 옮긴 것은 여기 해당하지 않는다 — 구조 변경
    때마다 CLAUDE.md를 고치게 만들면 결국 거짓말하는 문서가 된다. **실질 변경이면 → 사용자가 `/hi-vibe:log`를 치기를
    기다리지 말고, 지금 이 자리에서 `CHANGELOG.md`의 `[Unreleased]`에
    직접 항목을 추가한다** (docs-keeper의 log 모드 규칙을 따른다:
    `date '+%Y-%m-%d %H:%M'`로 실제 시각, Added/Changed/Fixed/Removed로
    분류, 한 변경당 한 줄). **단 "실질 변경"만** — 동작·API·스키마·의존성·
    설정 변화. 오타·포맷·주석·동작 동일한 순수 리팩토링은 적지 않는다
    (CHANGELOG를 노이즈로 채우지 않는다). `init`이 CHANGELOG를 만들므로
    보통은 이미 있다 — 없으면 `docs-keeper`의 템플릿으로 만들고 적는다
    (기록할 게 생겼는데 파일이 없다고 그냥 넘기면 그 변경은 영영 안 남는다).

### 4. fresh-eyes (기본 켬 — 작을 때만 생략)

체크리스트를 마치면 **fresh-eyes 에이전트를 Agent 도구로 소환한다.** 이건
기본값이지 옵션이 아니다 — 체크리스트가 이 파일 안의 "빠뜨림"을 잡는다면
fresh-eyes는 **"절반만 끝난 것"**(고친 파일을 가리키는 다른 파일이 안 따라감)
과 "판단 착오"(과잉 설계·스코프 크립·더 단순한 길)를 잡고, 그건 작성자가
자기 의도를 알고 있어서 혼자서는 못 보는 것이다.

- 전달할 것: ①사용자의 원래 요구사항 한 줄 ②이번에 바꾼 파일 목록.
- 전달하지 말 것: 설계 이유나 변명 — 에이전트는 깨끗한 눈이어야 한다.
  작성자의 착각을 공유하지 않는 것이 이 리뷰의 존재 이유다.
- 사용자에게 말할 땐 **이름을 `fresh-eyes`로 쓴다.** 별명이 여러 개면
  무엇이 돌았는지 세션 기록에서 못 찾는다 ("남의 눈"으로 바꿔 부르지 마라).
- 판정이 돌아오면: 요약해 사용자에게 전하되, "재고 권장" 항목마다
  동의하는지/반박하는지 근거와 함께 명시한다. 기계적으로 전부
  수용하지도, 전부 방어하지도 않는다.

**"사용자가 요청 안 했으니 안 부른다"는 오해다.** `사용자가 요청하지 않으면
서브에이전트를 부르지 마라` 같은 세션 지시가 있어도, **이 호출은 그 요청
안에 이미 들어 있다** — 사용자가 `init`으로 hi-vibe를 켰고, Stop 훅이 매번
`fresh-eyes까지 수행하라`고 지시하고 있다. 그게 요청이다. 이 판단이
세션마다 갈려 **어떤 세션은 하루 종일 체크리스트만 돌았다**(2026-08-07).

**생략하는 경우**: ①변경이 작을 때(오타·주석·포맷·한 줄 설정처럼 판단할
설계가 없는 것) ②Agent 호출이 실제로 실패할 때. 둘 다 **조용히 넘기지 말고
한 줄로 밝힌다** — "변경이 작아 설계 리뷰는 건너뜀" / "Agent 호출 실패로
설계 리뷰 못 함". 무엇이 안 돌았는지 사용자가 알아야 한다. **"막혀 있어서"로
뭉뚱그리지 마라** — 안 부른 것과 못 부른 것은 다르고, 사용자가 고칠 수
있는지가 갈린다.

### 5. 완료 기록 (필수)

실제로 리뷰를 마친 파일들을
`python3 "${CLAUDE_PLUGIN_ROOT}/skills/write-gate/scripts/review_scope.py" mark <파일들> --root <repo>`
로 표시한다 → 다음 리뷰에서 안 바뀐 건 자동으로 건너뛴다. Stop 훅도 이
표시를 보고 조용해지므로, **mark를 빠뜨리면 같은 변경으로 리뷰가 다시
걸린다.** (⚠️를 못 고치고 남긴 파일은 mark 하지 마라 — 다음에 또 걸려야 한다.)

`skipped`가 있었으면 "이미 리뷰됨(변경 없음): N개" 한 줄로 밝힌다.

### 보고는 짧게 — 한 번 말한 것을 또 말하지 마라

리뷰가 값을 하려면 사용자가 **읽어야** 한다. 길면 안 읽는다.

- **같은 결함을 두 번 설명하지 마라.** 체크리스트 ⚠️와 마무리 요약에
  같은 내용을 반복해 적는 일이 실제로 일어난다. **가장 구체적인 자리 한
  곳에만** 쓰고 나머지는 짧게 가리킨다. **단 `👋` 줄은 여기서 뺀다** —
  그건 설명이 아니라 **세는 표시**라, 내용이 체크리스트에 이미 있어도
  아래 세 조건이 맞으면 붙인다.
- **✅는 나열하지 마라.** "그 외 항목 통과" 한 줄로 묶고 ⚠️만 펼친다.
  파일이 많아도 마찬가지다.
- **시간은 묻기 전엔 말하지 말고, 물으면 재서 답하라.** 안 물었는데 붙이는
  소요 시간 해명은 노이즈다. 하지만 **"왜 이렇게 오래 걸렸냐"고 물으면
  추측하지 말고 실제 시각을 재서 구간별로 답한다**(git 커밋 시각·명령 로그).
  "리뷰 때문인지 기능 때문인지"는 사용자가 알 자격이 있고, 추측으로 답하는
  건 grounded-answers 위반이다. **일을 키운 쪽이 자신이면 그렇게 말하라** —
  리뷰 탓으로 돌리지 않는다.
- 끝에 **"빠르게 끝내고 싶으면 '가볍게 봐줘'라고 하시면 됩니다"**를 한 번만
  덧붙인다(세션에 이미 말했으면 생략) — 끄는 법을 모르면 마찰이 불만이 된다.
  **다만 이건 "브레이크가 있다"는 안내지, 깊이를 사용자에게 넘기는 게 아니다.**
  위 "고르라고 묻지 않는다"는 그대로다 — 이번 리뷰를 어디까지 할지는 여전히
  네가 정하고, **"다음엔 어떻게 할까요?"로 되묻지 마라.** 실제로 사용자가
  "리뷰가 값어치 있었냐"고 물었을 때 이 문장을 **답 대신** 내놓은 적이 있다.
  그건 판단을 떠넘긴 것이다 — 값어치 판단은 재서 답하고, 이 안내는 그 뒤에
  한 줄로만 붙인다.

평소 리뷰 한 건이면 이 정도가 목표다:

```text
일부 저장을 전체 성공으로 표시하던 문제와, 권한 없는 서버에서 성공 신호가
나오지 않는 문제를 발견해 고쳤습니다. 그 외 항목 통과.
👋 hi-vibe가 방금 …을 잡아서 고쳤어요 — 체크리스트.
실행 검증: 테스트 75개 통과 · 문서 갱신함: CHANGELOG
```

⚠️가 많거나 fresh-eyes가 재고를 권했으면 당연히 길어진다 — **줄이라는 건
중복과 변명이지 내용이 아니다.**

### 마지막 두 줄

반드시 끝낸다 — 첫 줄은 9번 등급에 맞는 하나로:
"실행 검증: <무엇을 어떻게 실행해 확인했나>" / "가벼운 검증: <구문·서빙 확인>" / "화면 확인 요청: <⌘⇧R 후 무엇을 볼지>" / "런타임 표면 없음 — <이유>" / "실행 검증 안 됨 — <이유>"
"문서 갱신함: <목록 (CHANGELOG 자동 기록 포함)>" 또는 "문서 영향 없음 — <이유>"

## hi-vibe catch — 반사실적 발견에만 공을 밝힌다

hi-vibe는 티 안 나게 뒤에서 잡아주는 게 설계라, 잘 작동할수록 자기 공을
못 드러낸다. 그래서 이 리뷰가 **사용자가 그냥 지나쳤을 것을 붙잡았을 때만**,
보고 맨 끝에 한 줄을 더한다. 세 조건을 **모두** 만족할 때:
① hi-vibe(이 스킬/에이전트)가 찾았다 — 사용자·메인 흐름이 자발적으로 짚은 게 아니다
② 진짜 결함·판단이다 — 스타일 트집·취향·이미 ✅로 통과한 항목이 아니다
③ 이 스킬이 돌기 전엔 레이더 밖이었다 — 안 돌렸으면 놓쳤을 것이다

형식(문구는 사용자 언어로, `👋 hi-vibe` 접두사는 고정 — 나중에 세션에서 grep 가능하게):
`👋 hi-vibe가 방금 <무엇>을 잡았어요 — <이 스킬/에이전트>.` (고쳐줬으면 "…을 잡아서 고쳤어요.")

여기서 세는 것은 체크리스트 ⚠️ 중 **사용자가 그대로 배포했을** 항목이다.
fresh-eyes 항목은 그 출력이 자체 catch 줄을 이미 달고 오므로, 그건 그대로
전하고 여기서 또 달지 않는다(중복 금지).

**과장 금지(핵심):** 셋 중 하나라도 아니면 이 줄을 넣지 마라. 통과·스타일
지적·사용자가 이미 알던 것에 공을 붙이는 순간, 조용히 일하는 플러그인의
신뢰가 깨진다. 이 표기는 자화자찬이 아니라, 티 안 나게 작동하는 hi-vibe가
*실제로 무엇을 살렸는지*를 저자·사용자가 볼 수 있게 하는 창이다.
