[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M70FW7GSY648HPJD78GTFQ10":3,"skill-same:01M70FW7GSY648HPJD78GTFQ10":55},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":29,"linkOnly":30,"markdownContent":31,"markdownBody":32,"frontMatter":33,"refName":34,"commitSha":35,"skillDirectory":36,"forksCount":37,"repositoryHtmlUrl":38,"repositoryHomepageUrl":6,"ownerHtmlUrl":39,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":40,"install":41,"resources":54},"01M70FW7GSY648HPJD78GTFQ10","codex-review",null,"Codex CLI 코드 리뷰 모드. PASS 시 v2 code_review 증거 발급, severity escalation, Sonnet fallback, 재리뷰는 후속 회차 요청서 양식. rein 의 코드리뷰 게이트 통과 증거를 발급하는 유일한 스킬.","JayJihyunKim\u002Frein","JayJihyunKim","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F9338574?v=4","01M70FV3CW5XEZGG6GS341V6J6","flyingcat4","01M70FV3DSX4YES0CN14AFJT2T",1,"MIT","https:\u002F\u002Fgithub.com\u002FJayJihyunKim\u002Frein\u002Fblob\u002Fmain\u002Fplugins\u002Frein-core\u002Fskills\u002Fcodex-review\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28],"review","codex","codex review","cli","codex cli","code","escalation","severity","fallback","sonnet","2026-11-10 10:12:30",false,"---\nname: codex-review\ndescription: \"Codex CLI 코드 리뷰 모드. PASS 시 v2 code_review 증거 발급, severity escalation, Sonnet fallback, 재리뷰는 후속 회차 요청서 양식. rein 의 코드리뷰 게이트 통과 증거를 발급하는 유일한 스킬.\"\n---\n\n# Codex Review Skill (Mode A)\n\n## 1. 목적\n\n코드 리뷰 전용 — rein 의 리뷰 게이트 생성 스킬. `\u002Fcodex-review` 슬래시 명령으로 호출한다.\n\n이 스킬은 **리뷰 gate** 다. PASS 회차에서 `bin\u002Frein issue-evidence code_review --verdict PASS --reviewed-digest \u003CD>` 로 v2 code_review 증거를 발급하며, 커밋 시점에 `pre-bash-commit-review-gate.sh`(Phase 7 웨이브 3 ③-c 신설 — 구 `pre-bash-test-commit-gate.sh`, ③-c 삭제 완료, 의 코드 리뷰 축 후속)가 이 v2 증거만으로 `git commit` 허용 여부를 판정한다. **Phase 7 웨이브 3 ③-d (2026-08-24)** 로 legacy stamp(`trail\u002Fdod\u002F.codex-reviewed`\u002F`.review-pending`) 의 write·read 경로가 전부 제거됐다 — 위 v2 발급이 이 스킬의 유일한 기록 절차이며, dual-read 전환기 서술은 더 이상 유효하지 않다. (테스트 실행 자체는 GUARD-1 이후 이 리뷰 게이트 대상이 아니다 — pre-bash-commit-discipline-gate.sh 의 동일 지점 주석 참조.)\n\nSecond opinion (brainstorm 반박, spec sanity, refactor tradeoff 이중 검증) 이 필요하면 `\u002Fcodex-review` 가 아니라 `\u002Fcodex-ask` 를 사용한다 — v2 증거를 발급하지 않는 별도 스킬.\n\n### Mode 대비\n\n| 항목         | Mode A (`\u002Fcodex-review`)                                                | Mode B (`\u002Fcodex-ask`)                                                                  |\n| ------------ | ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |\n| 용도         | 리뷰 게이트                                                             | Second opinion                                                                         |\n| 리뷰 결과 기록 | **필수** (v2 code_review 증거 발급)                                    | **절대 금지**                                                                          |\n| Resume --last | 래퍼는 매번 새 세션 — 맥락은 후속 회차 요청서 양식으로 (§2, §7)           | **금지** — 매번 새 세션                                                                |\n| Sandbox      | 래퍼가 `read-only` 고정 (§6.6)                                          | 항상 `read-only`                                                                       |\n| **Fallback** | codex 실행 **실패** 시에만 Sonnet\u002Fcode-reviewer\u002Fhuman fallback 허용 (§4) | **same-session Claude fallback 금지**. 독립 reviewer 부재 시 degraded \u002F no-second-opinion 으로 명시 |\n\nFallback 정책 비대칭 이유: Mode A 는 \"리뷰가 어떤 형태로든 수행되어 v2 증거가 발급\" 되는 것이 게이트 통과의 전제이므로 codex 실행 실패 시 차순위 reviewer 로 대체할 수 있다. Mode B 는 \"독립 관점\" 자체가 가치이므로 same-session Claude 가 대신하면 본질이 깨진다 — 호출자는 결과 없이 작업을 계속하거나, 별도 시점에 Mode B 를 다시 시도한다.\n\n---\n\n## 2. Running a review\n\n`\u002Fcodex-review` 는 `scripts\u002Frein-codex-review.sh` wrapper 를 호출한다.\nWrapper 가 context assembly, envelope 4 slots, codex exec, PASS 시 v2 code_review 증거 발급을 담당한다.\n\n### Usage\n\n- Interactive (code review): `bash \"${CLAUDE_PLUGIN_ROOT:-$PWD}\u002Fscripts\u002Frein-codex-review.sh\"`\n- Automated (agent, code review, **file redirect 필수**):\n\n  ```bash\n  # 1. prompt 를 임시 파일로 작성 (Write 도구 또는 echo > file)\n  # 2. file redirect 으로 wrapper 호출 — pipe (`|`) 사용 금지 (pre-bash-safety-guard 차단)\n  bash \"${CLAUDE_PLUGIN_ROOT:-$PWD}\u002Fscripts\u002Frein-codex-review.sh\" --non-interactive \u003C \u002Ftmp\u002Fcodex-prompt.txt\n  ```\n\n- Spec review (plan review): prompt 파일 맨 앞에 `[NON_INTERACTIVE] spec review for plan: \u003Cpath>` marker. wrapper 가 spec-review mode 로 분기.\n- Spec review (design review): prompt 파일 맨 앞에 `[NON_INTERACTIVE] spec review for design: \u003Cpath>` marker.\n\n> **Pipe vs file redirect**: `printf '...' | bash wrapper.sh` 패턴은 pre-bash-safety-guard 가\n> 차단한다 (이유: stdin 의 명령 source 를 hook 가 검증할 수 없음). 항상 `\u003C \u002Ftmp\u002F\u003Cfile>`\n> 형태로 호출하라.\n\n### 요청서 작성 규약 — review-readiness 사전검사\n\n정량(개수\u002F비율\u002F백분율)·테스트 통과 주장을 쓰려면 각 주장을 `[EVIDENCE]` 블록으로 선언한다. 블록 없이 주장을 쓰면 래퍼가 review-readiness 사전검사에서 **exit 4 로 거부**한다 (codex 미호출 — 비용 0). 주장 없는 요청서는 종전대로 블록 불필요 (완전 하위호환 — 기존 요청서 무변경). output 발췌에 `[EFFORT:...]` 리터럴을 포함하지 마라 — 래퍼의 기존 전역 strip 단계가 제거한다 (사전검사가 보는 \"원문\" 은 strip 이후의 PROMPT_BODY). **블록을 갖췄다고 모든 잔존 정량 서술이 통과하는 것은 아니다** — 블록 밖에 남은 정량\u002FPASS 서술은 형태에 따라 두 tier 로 갈린다: 검증명사+통과어 공존(예: \"테스트 통과\"), 계약 문맥어 없이 또는 결과 서술어와 함께 쓰인 비율·백분율(예: \"커버리지 85%\"), 그리고 결과 서술어가 있고 계약 문맥어가 없는 수량+단위(예: \"실패 3건 발견\")는 **reject-tier** 로 블록이 있어도 exit 4 거부; 고정 계약 형태(예: \"함수 50줄 이내\", \"오차 상한 ±20%\")와 결과 서술어 없는 단독 수량(예: \"파일 5개\")은 **advisory-tier** 로 비차단 경고에 그친다 (문법·집합식: §4.1).\n\n- 증거 블록 문법 전문: §4.1\n- exit 4 판별 계약 + 호출자 행동: §4.2 (Sonnet 폴백 비대상)\n- spec-review 모드는 사전검사 비대상: §6.7\n- **수치 주장의 구성**: 합계·개수를 주장하면 그 숫자가 무엇의 합인지 한 줄로 적는다. 최소 형식은 `\u003C합계> = \u003C항목1 값>(\u003C출처>) + \u003C항목2 값>(\u003C출처>) + …` 이고, 출처는 파일 경로(필요하면 절)·명령 중 하나다. 예: `변경 테스트 파일 7 = 신규 3(git status --short tests\u002F) + 갱신 4(git diff --name-only tests\u002F)`. 리뷰어(Claim Audit)는 각 항목을 출처에서 확인하고, 모든 항목이 확인되고 합이 맞으면 그 숫자를 1:1 대응을 갖춘 것으로 본다. 구성이 없으면 리뷰어가 숫자마다 대응을 직접 찾아야 하고, 찾지 못하면 수정 필요로 판정한다. 사전검사(형식 검사)는 구성 줄을 따로 검사하지 않는다 — 다만 구성 줄도 블록 밖에 쓰면 정량 서술로 스캔되므로(§4.1 두 tier) \"통과·발견·실패\" 같은 결과 서술어를 넣지 말고 사실만 나열한다.\n\n### 자가검증 증거 — 두 축 계약\n\n코드 리뷰 모드에서 리뷰 대상에 변경(추적 파일 변경 또는 미추적 파일)이 있으면, 래퍼는 codex 를 부르기 전에 요청서에 아래 세 가지가 있는지 **형식만** 확인한다. 명령을 다시 실행하지 않고, 출력의 진위도 확인하지 않는다. 하나라도 없으면 exit 4 로 거부한다(§4.2 — codex 미호출, 회차 비소모). spec-review 모드와, 변경이 문서·trail 뿐이라고 판정된 경우는 확인하지 않는다.\n\n1. `[axis:typecheck]` 블록 — `claim:` 에 `[axis:typecheck]` 토큰을 정확히 하나 담고 `exit_code: 0` 인 `[EVIDENCE]` 블록(§4.1). 타입 검사·린트·구문 검사 같은 정적 검증이다.\n2. `[axis:test]` 블록 — `claim:` 에 `[axis:test]` 토큰을 정확히 하나 담고 `exit_code: 0` 인 `[EVIDENCE]` 블록. 1 과 **다른 블록**이어야 한다. 한 `claim:` 에 두 토큰을 같이 넣으면 어느 축으로도 세지 않는다.\n3. `diff_self_review:` 단독 라인 — 작성자가 diff 를 직접 읽고 확인한 내용 한 줄(비어 있으면 안 된다).\n\n**테스트 블록의 범위는 정해져 있지 않다.** 래퍼는 `[axis:test]` 블록이 전체 스위트를 실행했는지 보지 않는다. 초회에는 전체 스위트를 권장하고, 2회차부터는 아래 \"델타 증거\" 형식으로 대상 테스트만 실행해도 된다.\n\n예외 선언 — 블록·코드 펜스 밖의 단독 라인으로 쓴다:\n\n- `verification_commands: none` — 이 저장소에 실행할 검증 명령이 없을 때. 두 블록 요구를 면제하고 `diff_self_review:` 만 요구한다.\n- `verification_state: tests-intentionally-red` + `expected_failure: \u003C무엇이 왜 실패해야 하는가>` — TDD 의 red 단계. `[axis:typecheck]` exit0 블록 + `[axis:test]` 블록(exit 1~123·125·126 허용, 0·124·127·128 이상은 거부) + `diff_self_review:` 를 요구한다.\n\n### 대상 테스트 선택 도구 — `rein-affected-tests.py`\n\n후속 회차 델타 증거의 대상 테스트(아래 \"델타 증거\" 3번 칸)를 눈으로 고르지 않고 기계로 고른다. 요청서 작성자가 직접 실행한다 — 래퍼는 이 도구를 부르지도, 테스트를 실행하지도 않는다.\n\n```bash\npython3 \"${CLAUDE_PLUGIN_ROOT:-$PWD\u002Fplugins\u002Frein-core}\u002Fscripts\u002Frein-affected-tests.py\" \\\n  --base \u003Cfull_run_tree> --untracked-scope '\u003Cuntracked_scope 의 glob>' \\\n  --untracked-snapshot \u003Cuntracked_at_full_run 줄을 담은 파일> --format json\n```\n\n`--base` 를 생략하면 `HEAD` 대비 작업 트리 변경을 본다. `--untracked-scope` 는 델타 증거의 `untracked_scope:` 와 같은 glob 을 준다(여러 번 지정 가능, 생략하면 전체). `--untracked-snapshot` 은 `untracked_at_full_run:` 의 줄들(`\u003C경로> \u003Csha256> \u003C줄 수>`, 없으면 `none`)을 담은 파일이다 — 도구가 미추적 항목을 델타 증거 2번 칸과 같은 규칙으로 계산한다. 외부 입력이 바뀌었거나 알 수 없으면 `--external-inputs-changed` 를 붙인다. `--format text` 는 사람용 요약, `--no-install` 은 아래 자동 설치를 건너뛴다. 종료 코드 0 이면 출력이 있다(전체 실행 판정 포함). 0 이 아니면 도구를 쓰지 못한 것이다 — 전체 스위트를 돌리거나 직접 골라 `selection: manual` 로 적는다. 도구 전체 실행 시간은 최대 600초다.\n\n**판정 순서**:\n\n1. 먼저 아래 \"전체 테스트 재실행으로 돌아가는 조건\" 의 의존성·빌드 설정, 테스트 공용 설정·픽스처, 소스 5개·200줄 초과(줄 수는 같은 산식 — 문서 포함 델타 전체), 소스 삭제·이름 변경, 외부 입력 변경, 변경 없음을 본다. 하나라도 걸리면 어떤 방법도 쓰지 않고 `전체 실행 필요: 예` 다 — 프로젝트 스크립트도 이를 뒤집지 못한다.\n2. `project-script` — 프로젝트가 둔 `.rein\u002Faffected-tests.sh`. 계약: stdin 으로 바뀐 경로를 한 줄에 하나 받고, stdout 으로 `{\"tests\": [...], \"command\": \"...\"}` JSON 을 낸다(최대 1 MiB). 종료 코드 0 = 결과 유효, 3 = 전체 실행 필요, 그 밖·120초 초과·형식 오류 = 다음 방법으로 내려간다. **고른 테스트가 빠짐없는지는 프로젝트가 책임진다 — rein 은 검증하지 않는다.** 도구는 `--base` 에 커밋된 판을 꺼내 실행한다 — 기준 이후 바뀐 내용은 실행하지 않고, 기준에 없는 새 파일이면 건너뛴다. 스크립트는 그 판의 트리 전체를 임시 디렉터리로 꺼낸 곳을 작업 디렉터리로 실행되므로 같은 판의 보조 파일(source 하는 helper 등)도 그 판이 쓰인다. 현재(검토 대상) 저장소는 환경 변수 `REIN_AFFECTED_TARGET`(저장소 절대 경로)로만 전달되니, 선택 대상 파일은 이 경로 아래에서 읽어야 한다. 그 밖에 `REIN_AFFECTED_BASE`·`REIN_AFFECTED_ROOT` 를 받는다. 실행 환경에서는 `BASH_ENV`·`ENV`·`CDPATH`·내보낸 셸 함수를 제거하고 `bash --noprofile --norc` 로 실행한다.\n3. 두 스택이 함께 바뀌었거나 지원 스택(파이썬 pytest, JS\u002FTS jest·vitest·`npm test`) 밖이면 `전체 실행 필요: 예` 다. 이후 방법 중 jest·vitest·import-search 는 저장소 전체에서 첫 인자가 고정 문자열 리터럴이 아닌 동적 import(`importlib.import_module(name)`·`__import__(name)`·`require(name)`·`import(name)`·인터폴레이션이 있는 템플릿 문자열, 따옴표 문자열에 다른 식을 이어 붙인 `import(\".\u002Fplugins\u002F\" + name)` 같은 형태 — 따옴표 문자열 하나만 인자로 오는 경우만 고정 경로로 본다)를 쓰는 파일을 찾아, 그런 테스트 파일과 그런 소스 파일에 import 로 (중간 모듈을 거쳐서라도) 닿는 테스트를 바뀐 파일과 무관하게 항상 대상에 더한다 — 동적 import 는 무엇을 불러오는지 정적으로 알 수 없기 때문이다. 테스트 파일 규칙을 바꾸는 설정(pytest 의 `python_files`·`testpaths`, jest 의 `testMatch`·`testRegex`·`roots`, vitest 의 `include`·`includeSource`)이 있거나, 저장소에 구문 오류가 있는 파이썬 파일·읽을 수 없거나 너무 큰 파일이 있거나, 저장소를 끝까지 훑지 못하면, 또는 더한 결과가 전체 테스트 파일의 절반을 넘으면 `전체 실행 필요: 예` 다(jest·vitest 의 이 절반 계산은 jest·vitest 의 실제 대상이 아니라 선택 도구 자신의 import 그래프로 센 근사다). testmon 은 실제 실행을 추적하므로 이 규칙에서 빠진다. 도구가 따라가지 못하는 경우가 남는다: JS 경로 별칭·번들러 설정 기반 모듈 해석, 함수 별칭으로 감싼 호출·`exec`·`eval`·설정 문자열 기반 플러그인 로딩 같은 패턴 밖의 동적 로딩, 그리고 JS 파일의 구문 오류(감지하지 않는다)다.\n4. `jest` \u002F `vitest` \u002F `testmon` — 정밀 도구. jest·vitest 는 `package.json` 의 의존성에 있고 `node_modules\u002F.bin\u002F` 에 실행 파일이 있을 때, 명령은 `jest --findRelatedTests \u003C파일>` \u002F `vitest related --run \u003C파일>` 이고, 3 의 추가 대상이 있으면 `&& jest --runTestsByPath \u003C추가 경로>` \u002F `&& vitest run \u003C추가 경로>` 가 붙는다. testmon 은 파이썬 프로젝트에 pytest-testmon 과 기록(`.testmondata`)이 있을 때, 명령은 `pytest --testmon` 이다. 세 도구 모두 대상은 도구가 실행 시 정한다. testmon 의 기록이 없으면 전체 실행이고 명령은 `pytest --testmon-noselect`(선택 없이 전체를 돌리며 기록을 만든다)다.\n5. `import-search` — rein 내장 검색. 바뀐 모듈을 import 하는 테스트, 그리고 바뀐 모듈을 import 하는 다른 모듈을 import 하는 테스트(2단계)를 고르고, 3 의 추가 대상을 더한다. 파이썬은 import 문, JS\u002FTS 는 상대 경로 import·require 만 본다. 다음이면 전체 실행: 바뀐 모듈을 직접 import 하는 테스트가 둘 이상(공용 모듈 — 이 방법은 완화 대상이 아니다) \u002F 어떤 테스트에도 매핑되지 않는 소스 \u002F 대상이 전체 테스트 파일의 절반 초과.\n6. `full` — 위 어느 것으로도 고르지 못했다. 사유가 함께 나온다.\n\n도구 방법(`jest`·`vitest`·`testmon`)이 실행 시 \"관련 테스트 없음\" 으로 실패하면 전체 실행으로 간다. testmon 이 있는 프로젝트의 전체 실행(기준 실행 포함)은 `pytest --testmon-noselect` 로 돌린다 — `selection: testmon` 의 기준 실행 블록은 이 옵션을 포함해야 한다(아래 델타 증거 4번 칸).\n\n**정밀 도구 자동 설치** — 4단계에서 파이썬 프로젝트에 pytest-testmon 이 없으면 한 번 설치를 시도한다(1~3단계에서 결론이 나면 시도하지 않는다): `poetry.lock` 이 있으면 `poetry add --group dev \"pytest-testmon>=2,\u003C3\"`, `uv.lock` 이 있으면 `uv add --dev \"pytest-testmon>=2,\u003C3\"`, 그 외에는 활성 venv(`VIRTUAL_ENV`)가 있을 때만 그 venv 의 `python -I -m pip install \"pytest-testmon>=2,\u003C3\"`(작업 폴더의 같은 이름 모듈을 집어오지 않도록 격리 실행)(+ `requirements-dev.txt` 가 있으면 한 줄 추가). venv 없는 시스템 파이썬에는 설치하지 않는다. 결과는 `.rein\u002Fstate\u002Ftest-tools.json` 에 기록하고, 기록이 있으면 다시 시도하지 않는다 — 다시 시도하려면 그 파일을 지운다. 설치에 성공한 회차는 의존성이 바뀌었으므로 전체 실행(`pytest --testmon-noselect`)이다. 실패하면 `import-search` 로 내려간다. 끄려면:\n\n```yaml\n# .rein\u002Fpolicy\u002Ftest-selection.yaml\nauto_install_test_tools: false\n```\n\n파일이 없으면 켜짐이다. 파일이 있는데 읽을 수 없으면(형식 오류·PyYAML 없음·true\u002Ffalse 가 아닌 값) 꺼짐으로 보고 경고한다.\n\n### 리뷰 전 프로젝트 사전 검사 — `.rein\u002Freview-precheck.sh`\n\n프로젝트가 저장소 루트에 `.rein\u002Freview-precheck.sh` 를 커밋해 두면, 래퍼가 코드 리뷰 모드에서 codex 를 부르기 전에 이를 실행한다. 린트·정적 규칙처럼 외부 리뷰 전에 기계로 잡을 수 있는 문제를 여기서 걸러 회차를 아낀다. 파일이 없으면 아무 일도 일어나지 않는다. 검사 항목은 프로젝트가 정한다 — rein 은 연결만 한다.\n\n- **시점**: 위 \"자가검증 증거 — 두 축 계약\" 확인을 통과한 뒤, 회차 예산 판정 전. spec-review 모드에서는 실행하지 않는다. 자가검증이 요구되지 않는 리뷰(문서·trail 뿐인 변경)에서도 코드 리뷰 모드면 실행한다.\n- **검토 대상 이전 판만 실행**: 작업 트리의 파일은 직접 실행하지 않는다. 작업 트리 리뷰에서는 HEAD 에 커밋된 판, 커밋 범위 리뷰에서는 검토 대상 커밋들 이전(리뷰 비교 기준)의 판을 꺼내 실행한다 — 리뷰 중인 변경이 이 스크립트를 추가·수정·삭제해도 그 내용은 이번 리뷰에서 실행되지 않는다. 그 기준에 파일이 없으면 실행하지 않고 경고(`WARNING: [codex-review][readiness-advisory]`) 후 리뷰를 진행한다.\n- **실행**: 검토 이전 판의 **트리 전체**를 먼저 검사(절대 경로·`..`·외부 심볼릭 링크 거부)한 뒤 저장소 밖 임시 디렉터리에 꺼내, 그 디렉터리를 작업 디렉터리로 `bash --noprofile --norc` 로 실행한다. 같은 판의 보조 파일(source 하는 helper)도 그 판이 쓰인다. 현재(검토 대상) 저장소는 환경 변수 `REIN_PRECHECK_TARGET`(저장소 절대 경로)으로만 전달되니, 검사 대상 파일은 이 경로 아래에서 읽어야 한다. stdin 은 비어 있다. 실행 환경에서는 `BASH_ENV`·`ENV`·`CDPATH`·내보낸 셸 함수를 제거한다. 기준 판이 일반 파일이 아니거나 트리 검사에 걸리면 거부한다.\n- **환경 변수**: `REIN_PRECHECK_TARGET`(검토 대상 저장소 절대 경로), `REIN_PRECHECK_PROJECT_DIR`(저장소 루트), `REIN_PRECHECK_CHANGED_FILES`(리뷰 대상 파일 목록을 한 줄에 하나 담은 파일의 경로), `REIN_PRECHECK_CHANGED_FILES_RC`(0 이 아니면 목록을 얻지 못한 것 — 전체 검사로 가라), `REIN_PRECHECK_DIFF_BASE`(리뷰 비교 기준), `REIN_PRECHECK_REVIEW_SUBJECT`(`working_tree` 또는 `commit_range`).\n- **시간 제한**: 기본 120초, `REIN_REVIEW_PRECHECK_TIMEOUT` 으로 조정(1~9999 정수). 래퍼가 스크립트를 자기 프로세스 그룹으로 띄워 직접 시간을 재므로 `timeout` 명령이 없는 환경(macOS 기본)에서도 지켜지고, 기한을 넘기면 그룹 전체(스크립트가 띄운 프로세스 포함)를 끝낸다. 이를 위해 `setsid` 또는 `perl` 이 필요하다 — 둘 다 없으면 실행하지 않고 거부한다.\n- **출력**: 1 MiB 까지만 보관하고 나머지는 버린다. 거부 시 마지막 20줄을 보여 준다.\n- **결과**: 종료 코드 0 = 통과(출력 없이 리뷰 진행). 그 밖은 exit 4 거부 — codex 를 부르지 않고 회차를 쓰지 않는다. 거부 진단행은 `ERROR: [codex-review][readiness-reject] 프로젝트 사전 검사` 로 시작하고 유형(실패 rc \u002F 시간 초과 \u002F 실행 불가 \u002F 거부)을 밝힌다. 스크립트가 직접 124·137 로 끝나면 시간 초과로 보고된다.\n- **규약**: 작업 트리를 바꾸지 않는다(래퍼는 확인하지 않는다 — 바꾸면 리뷰 대상이 바뀐다). 같은 입력에 같은 결과를 낸다. 지적은 파일 경로와 줄을 포함해 stdout·stderr 에 쓴다.\n\n### 후속 회차 요청서 양식 (2회차 이상)\n\n래퍼는 재리뷰마다 **새 codex 세션**을 띄운다 — 이전 회차의 검토 내용은 자동으로 전달되지 않는다(§7). 그래서 2회차부터는 호출자가 아래 다섯 칸을 요청서에 직접 싣는다. 목적은 리뷰어의 주의를 \"이전 지적 + 수정분 + 그것이 닿는 계약\" 에 모으는 것이다.\n\n1. **직전 검토 근거** — 같은 사이클의 몇 번째 확인인지, 직전 판정과 차단급 지적 유무.\n2. **열린 지적과 처리** — 지적별로 셋 중 하나: 고침(어디를) \u002F 위험 수용(근거·기록 위치) \u002F 반박(근거).\n3. **종결된 지적** — 이미 배제·수용해 다시 다루지 않을 항목. 새 세션은 이를 모르므로 적지 않으면 같은 지적이 회차마다 되돌아온다.\n4. **직전 검토 이후 바뀐 것** — 항목으로 나열(수정분의 범위).\n5. **영향받는 계약과 증거** — 4 가 닿는 계약과 그 검증 명령(`[EVIDENCE]` 블록, §4.1). 테스트 증거는 아래 \"델타 증거\" 형식으로 대상 테스트만 실행해 갖출 수 있다.\n\n요청 문장은 다음 취지로 쓴다: \"후속 확인이다 — 필수 검사 축은 그대로 수행하되 4·5 를 우선 확인해 달라. 중대한 결함은 범위와 무관하게 보고해 달라.\" **\"그 밖의 새 결함도 함께 봐 달라\", \"수정이 새 충돌을 만들지 않았는지 전체를 확인해 달라\" 같은 문장은 쓰지 않는다** — 매 회차 전체 재탐색을 요청하는 것과 같다.\n\n**델타 증거 — 후속 회차의 테스트 증거**\n\n2회차부터는 매 회차 전체 테스트 스위트를 다시 돌리지 않는다. 대신 \"전체 스위트를 돌린 시점의 트리 식별자 + 그 뒤 바뀐 파일(델타) + 델타가 닿는 테스트만 실행한 기록\" 을 싣는다. 자가검증 두 축 계약의 `[axis:test]` 블록은 이 대상 테스트 실행으로 채운다(래퍼는 테스트 범위를 보지 않는다 — 위 \"자가검증 증거 — 두 축 계약\"). 코드 리뷰 봉투의 Claim Audit 은 이 형식을 갖춘 요청서에 전체 스위트 재실행을 요구하지 않는다.\n\n1. **기준 트리 식별자** — 전체 스위트를 돌린 **직후**에 `git stash create` 를 실행해 출력된 커밋 id 를 적는다. 이 명령은 작업 트리와 stash 목록을 바꾸지 않고 추적 파일의 현재 상태(스테이지 포함)를 커밋 객체로 고정한다. 출력이 비어 있으면(추적 변경 없음) `git rev-parse HEAD` 의 값을 쓴다. 이 커밋 객체는 어느 ref 에도 걸리지 않는다 — 가비지 수집 유예(기본 2주)는 보장이 아니므로, 요청서를 쓰기 직전에 `git cat-file -e \u003Cid>` 로 아직 있는지 확인하고 없으면 전체 스위트를 다시 돌려 새 식별자를 만든다. 전체 스위트 직후에 `git ls-files --others --exclude-standard` 의 결과를 `untracked_scope:` 에 적은 범위(glob 목록, 예: `src\u002F** tests\u002F**` — 생략하면 전체)로 거르고, 남은 파일마다 `\u003C경로> \u003Csha256> \u003C줄 수>` 한 줄(`sha256sum`·`wc -l`)을 `untracked_at_full_run:` 에 적는다. 기준과 현재 비교는 항상 같은 `untracked_scope` 를 쓴다 — 집합이 다르면 문서·trail 파일이 added 로 오판된다 — `git stash create` 는 미추적 파일 내용을 고정하지 않으므로 이 지문·줄 수 스냅샷이 미추적 파일의 기준이다(없으면 `none`).\n2. **델타** — `git diff --name-status \u003C기준 트리 식별자>` 의 출력을 그대로 싣고, `git diff --numstat \u003C기준 트리 식별자>` 의 추가·삭제 합을 `delta_lines:` 에, 소스 파일(테스트·문서 제외) 수를 `delta_source_files:` 에 적는다. `--name-status` 에는 미추적 파일이 나오지 않으므로 `untracked_at_full_run` 스냅샷과 지금 `git ls-files --others --exclude-standard` 를 **같은 `untracked_scope`** 로 거른 목록을 비교해 세 경우를 `?? \u003C경로> \u003Cadded|modified|deleted>` 로 직접 추가한다: added(지금 줄 수) — 스냅샷에 없던 파일, modified(스냅샷·지금 중 큰 줄 수) — sha256 이 다른 파일, deleted(스냅샷 줄 수) — 스냅샷에 있었는데 지금 미추적 목록에 없는 파일. **추적 전환 규칙**: 스냅샷 파일이 지금 추적 파일이 됐으면(`git ls-files --error-unmatch \u003C경로>` 성공, `--name-status` 에 `A` 로 나타남) deleted 가 아니다 — `--name-status` 항목으로만 한 번 센다. 세 경우의 줄 수를 `delta_lines` 에 더하고, 소스 파일이면 `delta_source_files` 에도 센다(산식: `numstat 합 + ?? 줄 수 = delta_lines`, `--name-status 소스 + ?? 소스 = delta_source_files` — 리뷰어가 같은 산식으로 재계산해 대조한다). 판단이 어려우면 범위 안 미추적 파일을 전부 modified 로 올린다.\n3. **대상 테스트** — 델타 파일마다, 그 파일을 import 하거나 그 동작을 실행하는 테스트 파일을 전부 나열한다(산정 책임은 요청서 작성자). 위 \"대상 테스트 선택 도구\" 로 골랐으면 `selection:` 에 출력의 선택 방법을 적고, 도구 실행을 axis 토큰 없는 `[EVIDENCE]` 블록으로 싣는다 — `command:` 는 도구 호출 그대로(`--base \u003Cfull_run_tree>`, `--untracked-scope`, `--untracked-snapshot`, `--format json` 포함), output 은 JSON 출력 전체다. 리뷰어는 그 JSON 의 `base`·`untracked_scope`·`changed`·`delta_lines`·`delta_source_files` 를 이 델타 증거의 값과 대조한다 — 도구를 실행한 뒤 파일이 더 바뀌었으면 대조가 어긋나므로 도구를 다시 실행한다. JSON 이 증거 블록 상한(60줄·8000바이트)을 넘으면 도구 방법을 쓸 수 없다 — `selection: manual` 로 쓰거나 전체 실행으로 간다. 도구가 `전체 실행 필요: 예`(`full_run_required: true`)를 내면 델타 증거를 쓰지 않는다. 눈으로 골랐으면 `selection: manual`. 나열한 테스트를 실행한 명령을 `[axis:test]` 블록의 `command:` 로 싣는다 — 도구로 골랐으면 JSON 의 `command` 와 같아야 한다. `jest`·`vitest`·`testmon` 은 도구가 실행 시 대상을 정하므로 `targeted_tests:` 에 `* -> \u003C선택 방법> 선택` 한 줄로 적는다.\n4. **기준 실행 기록** — 기준 시점의 전체 스위트 실행을 axis 토큰 없는 `[EVIDENCE]` 블록으로 싣는다(`claim:` 예: `기준 트리 \u003Cid> 에서 전체 스위트 실행`, output 은 요약 몇 줄). 이 블록은 `exit_code: 0` 이어야 한다 — 실패한 실행은 기준이 아니다. 새 세션의 리뷰어는 이전 요청서를 볼 수 없으므로 매 회차 다시 싣는다. `selection: testmon` 이면 기준 실행 블록의 `command:` 에 `--testmon-noselect` 가 있어야 한다(선택 없이 전체를 돌리며 testmon 기록을 만든 실행 — 그 기록이 이후 회차의 선택 근거다).\n5. **외부 입력** — 테스트가 읽는 저장소 밖 입력과 gitignore 대상 입력(실데이터 디렉터리, 환경 변수, 인터프리터·의존성 설치 상태)이 기준 실행 이후 바뀌지 않았음을 `external_inputs: unchanged` 로 선언한다. 바뀌었거나 알 수 없으면 델타 증거를 쓰지 않고 전체 재실행으로 돌아간다.\n\n형식:\n\n```\ndelta_evidence:\n  full_run_tree: \u003Cgit stash create 출력 — 비어 있으면 git rev-parse HEAD>\n  untracked_scope: \u003C미추적 비교 범위 glob 목록 — 예: src\u002F** tests\u002F**, 생략하면 전체>\n  untracked_at_full_run:\n    \u003C경로> \u003Csha256> \u003C줄 수>   # 전체 스위트 직후, untracked_scope 로 거른 미추적 파일마다 한 줄, 없으면 none\n  delta:\n    \u003Cgit diff --name-status \u003Cfull_run_tree> 출력 그대로>\n    ?? \u003C경로> \u003Cadded|modified|deleted>   # 스냅샷과 지금 상태의 비교\n  delta_lines: \u003Cgit diff --numstat \u003Cfull_run_tree> 추가+삭제 합 + 미추적 added\u002Fmodified\u002Fdeleted 줄 수>\n  delta_source_files: \u003C델타 중 소스 파일 수 — 테스트·문서 제외>\n  targeted_tests:\n    \u003C델타 파일> -> \u003C테스트 파일>, \u003C테스트 파일>\n  selection: \u003Cproject-script|jest|vitest|testmon|import-search|manual>   # 생략하면 manual\n  external_inputs: unchanged\n```\n\n칸이 하나라도 비거나 산식이 맞지 않으면 델타 증거가 아니다 — 리뷰어는 Medium \"델타 증거 불완전 — 전체 실행 후 재요청\" 으로 돌려주므로 전체 재실행으로 돌아간다. 이어서 `[EVIDENCE]` 블록 두 개 — 기준 실행 기록(axis 토큰 없음, `exit_code: 0`) 1개, 대상 테스트 실행(`claim:` 에 `[axis:test]`) 1개 — 와, 선택 도구를 썼으면 도구 실행 블록(axis 토큰 없음, output 은 JSON) 1개, 그리고 `[axis:typecheck]` 블록과 `diff_self_review:` 라인을 싣는다. \"대상 테스트 통과\" 같은 문장을 블록 밖에 쓰지 않는다 — 검증명사와 통과어가 함께 있는 라인은 블록이 있어도 사전검사가 거부한다(§4.1 reject-tier). 통과 사실은 블록의 `claim:` 에만 쓴다.\n\n**전체 테스트 재실행으로 돌아가는 조건** — 델타가 다음 중 하나라도 포함하면 델타 증거를 쓰지 말고 전체 스위트를 다시 실행해 그 결과를 `[axis:test]` 블록으로 싣는다(새 기준 트리 식별자도 이때 만든다):\n\n- 의존성·빌드 설정 파일 변경 — lockfile, `pyproject.toml`, `package.json`, `setup.*` 류. 작성자가 아는 의존성 설치 상태·런타임 버전 변경도 여기에 든다.\n- 테스트 공용 설정·픽스처 변경 — `conftest.py`, `tests\u002Ffixtures\u002F**`, `tests\u002Fhelpers\u002F**` 류.\n- 둘 이상의 테스트 파일이 import 하는 공용 모듈 변경 — `selection:` 이 `import-search` 이거나 `manual`(칸 없음 포함)일 때 적용한다. 도구 보증 방법(`project-script`·`jest`·`vitest`·`testmon`)으로 골랐으면 적용하지 않는다 — 대상의 완전성은 프로젝트 스크립트(프로젝트가 책임, rein 은 검증하지 않는다) 또는 도구(jest·vitest 의 import 그래프, testmon 의 실행 기록)가 보증하며, jest·vitest 를 쓸 때는 선택 도구가 저장소의 동적 import 흔적 파일과 그 파일에 import 로 닿는 테스트를 실행 명령에 더한다.\n- 소스 파일(테스트·문서 제외) 5개 초과 변경(`delta_source_files` > 5), 또는 델타 전체의 변경 200줄 초과(`delta_lines` > 200).\n- 소스 파일 삭제 또는 이름 변경(`--name-status` 의 `D`·`R`).\n- 테스트가 읽는 외부 입력(저장소 밖·gitignore 대상 데이터, 환경 변수, 인터프리터·의존성 설치 상태)이 기준 이후 바뀌었거나 알 수 없음(`external_inputs` 가 `unchanged` 가 아님).\n- 델타 증거의 칸이 하나라도 비었거나, 기준 실행 블록이 `exit_code: 0` 이 아니거나, 기준 커밋이 더 이상 없음(`git cat-file -e` 실패).\n- 대상 테스트 선택 도구가 `전체 실행 필요: 예` 를 냈음.\n\n이 숫자(5개·200줄)는 측정으로 정한 값이 아니라 운영 가설이다 — 놓친 결함이 드러나면 이 절에서 조정한다. \"전체 검토로 복귀하는 조건\"(아래)은 리뷰 범위의 조건이고, 이 절은 테스트 실행 범위의 조건이다 — 둘은 따로 판정한다.\n\n**전체 스위트 실행 횟수** — 리뷰 회차마다가 아니다. 기준 실행 1회, 그리고 최종 트리가 기준 트리와 다르면(기준 이후 코드가 바뀌었으면) 커밋 직전에 최종 트리에서 1회 더 — 복귀 조건이 한 번도 걸리지 않으면 사이클당 1~2회다. 복귀 조건이 걸릴 때마다 전체 실행 1회와 새 기준 트리가 추가되므로 상한은 \"1~2회 + 복귀 횟수\" 이지 고정된 숫자가 아니다. 최종 트리가 기준과 같으면 기준 실행이 곧 커밋 전 실행이라 추가 실행이 없다. 커밋 전 실행에서 실패해 코드를 고치면 그 수정은 리뷰를 받지 않은 변경이므로 종전대로 재리뷰 대상이다.\n\n**전체 검토로 복귀하는 조건** — 하나라도 해당하면 이 양식을 쓰지 말고 초회와 같은 전체 검토를 요청한다: 새 인터페이스(호출 규약·출력 형식·파일 위치) 추가, 보안 경계(아래 (b) 의 범주 — 한정 열거 아님) 변경, 작업 범위(DoD·Scope 항목) 변경, 직전 검토 이후 변경이 검토 대상의 대부분을 차지.\n\n**함께 지킬 운영 규율**:\n\n- 작업 기준서(DoD)의 범위를 **검토 단위에 맞춘다**. 기준서가 아직 구현하지 않은 범위 항목까지 가리키면 매 회차 그 항목들이 다시 평가된다.\n- 문서 리뷰에서 \"구현 단계 이관\" 참고사항과 오류 경로·상태 수명 지적을 **문서 본문의 계약으로 편입하지 않는다**(§6.7). 편입하면 다음 회차에 \"문서 내부 충돌\" 로 되돌아오고, 그 지적은 판정에 반영된다.\n- 후속 회차의 강도는 **조건부 기본값**이다 — 아래 셋이 모두 맞으면 `[EFFORT:medium]` 으로 부른다(§6.1): (a) 직전 독립 검토 보고에 High 가 없고 필수 검사 축이 수행됐다, (b) 변경이 직전 지적 처리에 한정되고 새 인터페이스·보안 경계·범위 변경이 없다 — 보안 경계는 보안 규칙이 다루는 범주 전부다(입력 검증·경로·권한·비밀 취급·인증·세션·TLS·인증서·암호화·역직렬화·코드 실행·외부 신뢰 경계 등, 한정 열거가 아니다) 또는 §6.3 floor 경로, (c) 새 판단이 필요한 변경이 없다. \"High 를 고쳤다\" 는 (a) 충족이 아니다 — 직전 보고에 High 가 있었으면 그 회차는 대상 밖이다. 셋 중 하나라도 어긋나면 medium 을 쓰지 않는다: 직전 보고에 High 가 있었거나(그 High 를 고쳤더라도 — 그 수정은 아직 독립 검토를 받지 않았다) (b)·(c) 에 어긋나면 §6.3 재승급 규칙대로 `[EFFORT:high]` 를 넣고, 직전 보고가 필수 축을 수행하지 않았으면 그 검토는 완결이 아니므로 마커 없이 전체 검토와 산출 강도(§6.3, 위험 경로 floor 포함)로 복귀한다. 이 기본값은 medium 이 놓치는 결함 폭을 재지 않은 운영 가설이며, 이후 리뷰·CI·버그에서 드러난 누락과 대조한다.\n- 회차 상한(§10)과 별개로, 누적 대기가 길어지면 재호출 대신 사람에게 **남은 위험 수용 \u002F 범위 축소 \u002F 중단** 중 결정을 요청한다. 시간이 많이 들었다는 사실은 통과 사유가 아니다.\n\n**한계**: 래퍼는 후속 회차에도 검사 축 전부를 응답에 요구한다. 이 양식은 범위 축소를 **보장하지 않는다**. 효과를 볼 때는 소요 시간만이 아니라 놓친 결함도 함께 본다 — `trail\u002Freview-events\u002F` 의 소요는 판정으로 끝난 호출만 담으므로 총 대기 지표가 아니다.\n\n### Mode 별 기록 규칙 (CRITICAL)\n\n- **Code review mode**: PASS 시 `bin\u002Frein issue-evidence code_review --verdict PASS --reviewed-digest \u003CD>` 로 v2 증거 발급 (commit gate 통과용). digest 는 리뷰 대상 diff 범위(`HEAD~1` 체인 기반)에서 산정한다 (Phase 7 웨이브 3 ③-d — 이전에는 stamp 의 `diff_base:` 필드에서 읽었지만, stamp 자체가 제거되면서 매 실행 시 직접 재산정으로 전환됐다, GI-codex-review-diff-base 후속).\n- **Spec review mode (plan 또는 design)**: code_review v2 증거를 **절대 발급 안 함**. legacy marker 는 이미 존재하지 않으므로 건드릴 대상도 없음. verdict 만 stdout 으로 방출. caller 가 `bash \"${CLAUDE_PLUGIN_ROOT:-$PWD}\u002Fscripts\u002Frein-mark-spec-reviewed.sh\" \u003Cpath> \u003Creviewer>` 를 별도로 호출해 `trail\u002Fdod\u002F.spec-reviews\u002F*.reviewed` 를 생성할 책임 (spec-review 축은 이번 웨이브의 존속 예외).\n- Rationale: code_review v2 증거는 code commit gate 전용. spec review 가 이를 발급하면 코드 변경 없이도 gate 통과 → rein 규율 붕괴.\n\n### Legacy interactive model selection\n\n아래는 wrapper 가 없던 시기의 manual invocation 절차로, 현재는 wrapper 가 담당한다. 참고용으로 남긴다.\n\n1. 모델은 단일 출처(`plugins\u002Frein-core\u002Fconfig\u002Fcodex-models.sh` 의 `CODE_GATE_MODEL` — 게이트 고정 모델, 프로필 구조)가 결정한다. 래퍼가 이 값을 `-m` 으로 전달하므로 수동 모델 선택은 불필요하다 — 모델 rename 시 그 파일만 수정한다. (legacy alias `CODE_MODEL` 은 신값을 노출한다.)\n2. Ask the user (via `AskUserQuestion`) which reasoning effort to use: `low`, `medium`, or `high`.\n3. Select the sandbox mode required for the task; default to `--sandbox read-only` unless edits or network access are necessary.\n4. Assemble the command with the appropriate options:\n   - `-m, --model \u003CMODEL>`\n   - `--config model_reasoning_effort=\"\u003Clow|medium|high>\"`\n   - `--sandbox \u003Cread-only|workspace-write|danger-full-access>`\n   - `--full-auto`\n   - `-C, --cd \u003CDIR>`\n   - `--skip-git-repo-check`\n5. Run the command, capture stdout\u002Fstderr, and summarize the outcome for the user.\n6. After every `codex` command, immediately use `AskUserQuestion` to confirm next steps, collect clarifications, or decide whether to resume the same review cycle (§7).\n\n### Quick Reference\n\n| Use case                               | Sandbox mode      | Key flags                                |\n| -------------------------------------- | ----------------- | ---------------------------------------- |\n| 코드 리뷰 (초회)                       | `read-only`       | 래퍼가 `--sandbox read-only` 를 항상 전달 (§6.6) |\n| 코드 리뷰 (fix 후 재리뷰, 같은 사이클) | `read-only`       | 래퍼 재호출(새 세션) + 후속 회차 요청서 양식 (§2) |\n| 로컬 편집 (수동 직접 호출, 래퍼 아님)  | `workspace-write` | `--sandbox workspace-write --full-auto`  |\n| 다른 디렉토리에서 실행                 | task needs        | `-C \u003CDIR>` 추가                          |\n\n### 실행 모드 — Bash 도구로 호출 시 foreground 전용\n\n`plugins\u002Frein-core\u002Frules\u002Fbackground-jobs.md` 예외 절에 따라, 어떤 경로로 codex 를 호출하든 Bash 도구에서는 foreground 실행이 강제된다. Bash 도구의 auto-background 전환이 stdin 을 unix socket 으로 붙이면 codex sandbox\u002Fauth 초기화가 hang 한다 (증상: 네트워크 연결 0개, CPU 0, `~\u002F.codex\u002Fsessions\u002F` 미갱신).\n\n**공통 규칙** (wrapper \u002F direct 모두):\n\n- Bash 도구 호출 시 `run_in_background: false` 필수\n- **시간상한은 래퍼가 코드로 강제한다** (문서 규약 아님): 1차 상한 `low 120s` \u002F `medium 180s` \u002F `high 300s` (spec-review 동일). 1차 상한 도달 후 30초 창 단위로 생존 검진하며, 신호는 **3축**이다 (2026-07-27~):\n  - **edge**(창 사이 실제 변화: 결합 스풀 성장 **또는** codex 자손 PID 집합 변동) → **무기한 유예** (절대 상한 없음)\n  - **level**(상태 유지·변화 없음: 미완료 자식 명령 표식 **또는** 자손 존재) → **유한 lease**(기본 20창) 안에서만 유예\n  - 위 어느 것도 아닌 창이 **연속 6창(180s)** 이면 TERM → 10s grace → KILL → reap 후 **exit 5** 로 종료 (판별 계약 + 호출자 행동: §4.2, 하니스 상호작용: §4.3)\n  - 배경: codex 는 자식 명령 실행 **중 출력을 내지 않아**, 리뷰어가 테스트를 돌리는 정상 구간이 과거 '무성장 단일 축' 판정으로 오살됐다 (2026-07-27 실측·수리)\n- 워치독 override env **5종** `REIN_WATCHDOG_{CAP,INTERVAL,GRACE,STALL_WINDOWS,LEVEL_LEASE}_OVERRIDE` 는 **테스트 전용**이다 — 운영 호출에서 설정 금지\n- 600s harness limit 을 넘어가는 리뷰는 기존대로 prompt 쪼개기 (§4.3)\n\n**Wrapper 경로** — `bash \"${CLAUDE_PLUGIN_ROOT:-$PWD}\u002Fscripts\u002Frein-codex-review.sh\" [--non-interactive] \u003C \u002Ftmp\u002F\u003Cprompt>.txt`:\n\n- wrapper 는 stdin 으로 prompt 를 받는다. **항상 file redirect (`\u003C \u002Ftmp\u002F\u003Cfile>`)** 사용 — pipe (`printf '...' | bash wrapper.sh`) 는 pre-bash-safety-guard 가 차단 (stdin 명령 source 검증 불가)\n- prompt 작성: Write 도구로 `\u002Ftmp\u002Fcodex-prompt.txt` 등에 작성 후 redirect\n- 출력은 wrapper 가 PASS 시 v2 발급 + stdout 요약을 동기 반환. `> \u002Ftmp\u002F\u003Coutput>.log 2>&1` 형태로 직접 파일에 쓰고 Read 로 확인. `| tail -N` 추가 pipe 는 불필요\n- v2 발급은 **foreground 동기 실행** 가정 위에서 동작. background 전환 시 발급이 누락된 채 프로세스만 죽을 수 있음 — legacy stamp 안전망이 없으므로(③-d), 이 경우 \"리뷰는 됐는데 기록이 없다\"가 곧 \"리뷰가 안 된 것\"과 동일하게 취급된다 — 재실행 필요\n\n**Direct codex exec 경로** (§2 \"Legacy interactive model selection\" 의 수동 조립):\n\n- stdin `\u003C \u002Fdev\u002Fnull` 로 명시 close\n- 출력은 `> \u003Cfile> 2>&1` 로 직접 파일에 쓰고 `Read` 로 읽기. `| tail -N` 금지 (EOF 까지 버퍼링)\n- codex 실행 후 v2 발급은 수동으로 caller 가 `bin\u002Frein issue-evidence code_review --verdict \u003CV> --reviewed-digest \u003CD>` 를 직접 호출한다 (`review_round` 는 §10 카운터가 담당하며 발급 인자에 포함되지 않는다)\n\nHang 감지 + 복구: §8 Error Handling 참고.\n\n---\n\n## 3. Escalation 규칙\n\n리뷰 결과 severity + 수정 규모에 따라 다음 단계를 결정한다:\n\n| 리뷰 결과      | 수정 규모 | 다음 행동                                                          |\n| -------------- | --------- | ------------------------------------------------------------------ |\n| High 이슈 있음 | 무관      | 수정 후 **codex 재리뷰** (Round 증가)                              |\n| Medium만 있음  | > 3줄     | 수정 후 **codex 재리뷰** (Round 증가)                              |\n| Medium만 있음  | ≤ 3줄     | 수정 후 **sonnet 셀프리뷰**                                        |\n| Low만 있음     | 무관      | 수정 후 **sonnet 셀프리뷰**                                        |\n| 이슈 없음      | —         | **통과** — v2 code_review 증거 발급 (§5)                            |\n| 3회차에도 High | —         | **사람에게 에스컬레이션** — 기록 없음 (PASS 가 아니므로 발급 대상 아님) |\n\n**문서 지적의 차단 기준** (래퍼가 두 봉투 — 문서 봉투의 판정 규율 (iv), 코드 봉투의 Claim Audit — 에 같은 문장으로 방출한다): 수정 필요(NEEDS-FIX)로 이어지는 지적은 결정·범위·사실의 오류여야 하고, 무엇을 잘못 전달하는지와 그 근거가 지적에 있어야 한다. 같은 뜻의 표현 선택·배치·명칭 취향은 \"참고사항\" 으로 분리되며 verdict 를 승격시키지 않는다 — 참고사항은 위 표의 다음 행동을 결정하지 않는다. 실질 결함(잘못된 사용법, 영향 범위 과장, 필수 보안·안전 제약의 누락 — 인증·TLS 검증·안전한 역직렬화 등은 범위의 오류이며 \"구현 단계 이관\" 참고사항으로 돌리지 않는다 —, 완료 주장, 수치 불일치, 누락된 추적 연결)은 계속 차단이다. Scope ID 의 측정 가능성도 문자열 형식이 아니라 Scope Items 표의 설명·검증 방법까지 읽어 판정한다 — 세 요소(대상·방향·상황)가 설명에 있으면 문자열만으로 플래그하지 않고, 설명에도 없을 때만 Medium 이다.\n\n**sonnet 셀프리뷰**: 변경 diff 를 직접 확인하고, PASS 판정이면 §5 발급을 그대로 수행한다 (발급 자체에는 reviewer 구분 필드가 없다 — 사람 보고 문구에서만 \"self-review\" 로 구분한다).\n\nRound 관리: 회차는 래퍼가 사이클 단위로 계수하며 상한을 **코드로 강제**한다 (§10). 같은 사이클 내 재리뷰도 래퍼는 새 세션으로 실행한다 — 이전 검토의 맥락은 호출자가 후속 회차 요청서 양식(§2)으로 전달한다 (§7).\n\n> 2026-08-04 정정: 이전 판은 \"stamp 의 `review_round` 필드에 회차를 기록한다\" 고 서술했으나 **그 필드는 존재한 적이 없다**. 래퍼에 회차 개념 자체가 없었고, 상한은 산문 지시로만 존재해 두 번 무력화됐다 (13회차 무보고 연속 실행 \u002F 종결 지시 후 5회차 폭주). §10 이 이를 코드 계약으로 대체한다.\n\n---\n\n## 4. Sonnet Fallback\n\nCodex 실패 (에러\u002F타임아웃) 시 Sonnet 기반 대체 리뷰 경로:\n\n1. **Codex 리뷰 실행** — §2 절차로 `codex exec`\n2. **성공 시** — §5 v2 발급 후 종료\n3. **실패 시 (에러\u002F타임아웃)** — sonnet 폴백 리뷰 실행\n   - `code-reviewer` 스킬 호출, 또는\n   - `general-purpose` 에이전트 (model: sonnet) 로 이관\n4. **폴백 성공 시** — §5 v2 발급을 그대로 수행하고, 사람 보고 문구에서 \"sonnet 폴백, 사유: codex_timeout\" (또는 해당 에러 코드) 처럼 밝힌 뒤 종료 (발급 인자 자체에는 fallback 사유 필드가 없다)\n5. **폴백도 실패 시** — 작업 중단, 사용자에게 보고\n\n**중요**: \"codex 리뷰 결과가 부족해서\" 등은 폴백 사유가 아니다. 에러\u002F타임아웃 같은 **실행 실패** 만 폴백을 허용한다.\n\n**모델 설정 에러(종료 코드 3)는 폴백 대상이 아니다.** 래퍼가 exit 3 으로 종료하면 codex 가 모델명을 거부한 것(upstream rename 등)이다. sonnet 폴백으로 넘기면 잘못된 모델 설정이 가려지므로, 폴백하지 말고 `plugins\u002Frein-core\u002Fconfig\u002Fcodex-models.sh` 의 `CODE_GATE_MODEL` 을 codex 의 최신 모델명으로 갱신하도록 사용자에게 안내한다. 래퍼가 경로와 현재 값을 stderr 로 출력한다.\n\n**review-readiness 거부(종료 코드 4 + 거부 진단행)도 폴백 대상이 아니다.** codex 실행 실패가 아니라 요청서 결함이므로, sonnet 폴백으로 새면 요청서 결함이 가려진다 (exit 3 과 동일한 비대상 원리). 준비도 거부는 codex spawn **이전**에만 발생하며 (비용 0), v2 발급을 시도하지 않고 존속하는 `.spec-reviews\u002F*` 도 건드리지 않는다. 호출자는 stderr 안내대로 요청서를 수정한 뒤 재호출한다 — 문법은 §4.1, 판별 계약과 호출자 행동은 §4.2.\n\n### 4.1 증거 블록 문법 (`[EVIDENCE]`)\n\n리뷰 요청서(래퍼 stdin 의 PROMPT_BODY)에서 정량\u002FPASS 주장의 재현 증거는 다음 고정 형식으로 선언한다. 래퍼의 review-readiness 사전검사가 codex 호출 전에 형식을 결정론적으로 검증한다 (code-review 모드 한정 — spec-review 모드는 비대상, §6.7):\n\n```\n[EVIDENCE]\nclaim: 테스트 21건 GREEN\ncommand: bash tests\u002Fskills\u002Frun-all.sh\nexit_code: 0\noutput:\n...(생략)...\nok 21 - test-codex-model-profile-routing\n21 passed, 0 failed\n[\u002FEVIDENCE]\n```\n\n**문법 규칙 (결정론적 — 위반 = exit 4)**:\n\n0. **fence 상태 전이 (단일 좌→우 스캔)**: fence 토글(``` ```)은 **증거 블록 밖에서만** 유효하다 — 블록 밖에서 fence 열림 상태의 `[EVIDENCE]`\u002F`[\u002FEVIDENCE]` 단독 라인은 블록 경계로도 형식 위반으로도 취급하지 않는다 (문법 예시·diff 인용 보호). 반대로 **증거 블록 안(output 영역 포함)의 ``` 는 일반 출력 텍스트**이며 fence 상태를 토글하지 않는다 — output 에 미폐쇄 ``` 가 있어도 뒤따르는 실제 `[\u002FEVIDENCE]` 가 정상 폐쇄로 인식된다. 정량\u002FPASS 패턴 스캐너는 이 스캔이 남긴 같은 마스킹 결과를 공유한다 (마스킹 1회). 인라인 백틱은 라인 단독 마커가 될 수 없으므로 블록 파서에는 무관 (패턴 스캐너에만 적용).\n1. `[EVIDENCE]` \u002F `[\u002FEVIDENCE]` 는 각각 **한 줄에 단독**으로 위치한다 (선행 공백 허용).\n2. 블록 내부 필드는 **고정 순서** `claim:` → `command:` → `exit_code:` → `output:` 4종, 각 정확히 1회. 순서 위반·누락·중복 = 형식 위반.\n3. `claim:` = 이 블록이 뒷받침하는 자연어 주장 한 줄 (비어있으면 위반). `command:` = 재현 명령 한 줄 (비어있으면 위반).\n4. `exit_code:` = 0–255 정수. 그 외 값 = 위반.\n5. `output:` 라인 **다음 줄부터** `[\u002FEVIDENCE]` 직전까지가 출력 원문이다 — 어떤 escaping 도 불필요 (여러 줄·특수문자 그대로). 단 출력 영역 안에 `[EVIDENCE]` 단독 라인이 나타나면 중첩 금지 위반. 출력 0줄 허용 (명령이 무출력인 경우) — `output:` 라인 자체는 필수.\n6. **출력 상한**: 블록당 output 60줄 이하 **그리고** 8000바이트 이하. **바이트 정의**: `output:` 다음 줄부터 `[\u002FEVIDENCE]` 직전 줄까지 output 영역의 **UTF-8 원시 바이트 수** — 각 줄의 LF 개행 바이트를 포함하고, 문자 수가 아니라 바이트 수다 (한글 등 다중바이트 문자는 인코딩된 바이트만큼 계수). **경계 포함**: 정확히 60줄·정확히 8000바이트는 통과, 61줄 또는 8001바이트부터 위반. 초과 = 형식 위반 (래퍼는 절단·변조하지 않는다 — 작성자가 관련 발췌로 줄인다. stderr 안내: `tail`\u002F`grep` 발췌 권장).\n7. **블록 수 상한**: 요청서당 유효 블록 16개 이하. 초과 = 형식 위반 (envelope 크기 보호).\n8. 다중 주장 = 다중 블록 (주장 1 : 블록 1). 하나의 명령 출력이 여러 주장을 뒷받침하면 블록을 주장별로 나누고 output 발췌를 각각 최소화한다 (규약 — 래퍼는 주장:블록 대응의 의미를 검증하지 않는다).\n\n**검증 깊이 (확정)**: 형식만. 래퍼는 `command:` 를 **재실행하지 않고**, output 과 exit_code 의 진위를 확인하지 않는다. 형식은 갖췄지만 거짓인 증거는 기존대로 codex 가 잡는다 — 유효 블록은 PROMPT_BODY 원문 위치에 그대로 보존되고, envelope 의 `evidence_manifest:` 슬롯(블록별 claim\u002Fcommand\u002Fexit_code 요약)과 Claim Audit sub-item 7 로 codex 에 구조화 전달된다.\n\n코드 리뷰 모드에서 요구되는 자가검증 두 축(`[axis:typecheck]`·`[axis:test]` 블록 + `diff_self_review:`)과 예외 선언은 §2 \"자가검증 증거 — 두 축 계약\", 후속 회차의 테스트 증거 범위는 §2 \"델타 증거\" 를 본다.\n\n**블록 밖 잔존 정량 매칭 — 두 tier 처분 (reject-tier \u002F advisory-tier)**: 형태 3종(Q1 수량+단위 \u002F Q2 비율·백분율 \u002F Q3 검증명사+통과어 공존)과 문맥어 2종(C = 계약 문맥어: 최소\u002F최대\u002F상한\u002F하한\u002F임계\u002F목표\u002F허용\u002F오차\u002F이내\u002F이하\u002F이상\u002Fthreshold\u002Flimit\u002Ftolerance\u002Ftarget\u002Fquorum\u002Fbudget\u002F±, R = 결과 서술어: 발견\u002F실패\u002F오류\u002F누락\u002F남음\u002F잔존\u002F감지\u002F검출\u002F재현\u002Ffail·found·detected·remaining·flagged·missing·reproduced)를 각각 독립 변수로 평가한다 — 한 라인이 여러 형태에 동시에 매칭돼도 어느 하나가 reject 조건이면 reject-tier 다.\n\n- **reject-tier** (증거 블록이 있어도 `[readiness-reject]` 로 exit 4 거부): Q3(예: \"테스트 통과 확인\", \"build passed\") · Q2 중 C 가 없거나 R 이 있는 것(예: \"커버리지 85%\", \"상한 20% 초과로 3\u002F5 실패\") · Q1 중 R 이 있고 C 가 없는 것(예: \"실패 3건 발견\", \"2 files flagged\").\n- **advisory-tier** (`WARNING: [codex-review][readiness-advisory]` 비차단 경고 + envelope `unbacked_quant_flags:` 슬롯): Q1 중 R 이 없거나 C 가 있는 것(예: \"함수 50줄 이내\", \"최대 재시도 3회\", \"실패 상한 3회\") · Q2 중 C 가 있고 R 이 없는 것(예: \"오차 상한 ±20%\", \"완화 목표 2%\", \"quorum 2\u002F3 이상\").\n- **동률 규칙**: C 와 R 이 한 라인에 같이 있으면 형태의 기본 처분을 유지한다 — Q2(비율·백분율)는 reject 기본, Q1(수량+단위)은 advisory 기본. 문맥어는 신호가 한쪽뿐일 때만 기본 처분을 뒤집는다.\n\n### 4.2 exit code 4 \u002F 5 \u002F 6 — 판별 계약 + 호출자 행동 (준비도 거부 \u002F 래퍼 timeout \u002F 회차 예산 소진)\n\n**호출자 행동 표**:\n\n| exit | 의미 | 호출자 행동 |\n|---|---|---|\n| 4 (+거부 진단행 — `ERROR: [codex-review][readiness-reject]` 로 시작하는 stderr 라인) | review-readiness 거부 — 블록 0개 + 정량\u002FPASS 매칭, **또는 블록 ≥1 이어도 블록 밖 reject-tier 매칭**(§4.1) — codex 미호출 (비용 0) 프로젝트 사전 검사(`.rein\u002Freview-precheck.sh`, §2 \"리뷰 전 프로젝트 사전 검사\")의 실패·시간 초과·실행 불가·거부도 같은 접두로 이 행에 속한다. | stderr 안내대로 요청서를 수정(증거 블록 추가, 계약 형태로 고쳐 쓰기, 또는 형식 교정)해 **재호출**. Sonnet fallback 비대상 — codex 실행 실패가 아니라 요청서 결함이므로 fallback 으로 새면 결함이 가려진다 (exit 3 과 동일한 비대상 원리). 재리뷰 카운트(§3 escalation)에 포함하지 않는다 — 리뷰가 수행되지 않았다. |\n| 4 (거부 진단행 0 — advisory 경고·발췌 내용 무관) | codex 실행 실패 passthrough (드묾 — `CODEX_RC` passthrough 와의 이론적 겹침) | 기존 실행 실패 경로와 동일 — Sonnet fallback 후보 (§4 본문). |\n| 5 (+진단행 — `ERROR: [codex-review][review-timeout]` 로 **시작하는** stderr 라인 ≥1) | 래퍼 소유 timeout (정지 판정 종료 — verdict 없음, v2 발급 미시도) | **즉시 대체 리뷰** (Sonnet fallback, 사유 `codex_timeout` 재사용). 재시도·재호출 없음. 재리뷰 카운트(§3 escalation) 비포함 — 리뷰가 완료되지 않았다. |\n| 5 (진단행 0) | codex 자체 exit 5 passthrough | 기존 실행 실패 처리 (Sonnet fallback 후보) 그대로. |\n| 6 (+진단행 — `ERROR: [codex-review][round-budget-exceeded]`) | 이 사이클의 리뷰 회차 예산 소진 — codex 미호출 (비용 0), v2 발급 미시도 (§10) | **재호출 금지.** 지금까지의 지적과 남은 쟁점을 요약해 **사람에게 넘긴다**. Sonnet fallback 비대상 — codex 실행 실패가 아니라 예산 소진이며, 폴백으로 새면 상한이 무의미해진다. 예산이 더 필요하다고 판단하면 사람의 승인을 받은 뒤에만 `[MAX_ROUNDS:\u003Cn>]` 을 선언해 재호출한다 (선언은 기록에 남는다). |\n\n**호출자 판별 계약 (기계 계약 — 사람 판독 아님)**:\n\n```\n거부 진단행    := 정확히 \"ERROR: [codex-review][readiness-reject]\" 로 시작하는 stderr 라인 (라인 시작 anchored — substring 검색 금지)\n준비도 거부    := exit == 4 AND 거부 진단행 ≥ 1\ncodex 실행실패 := exit == 4 AND 거부 진단행 0  → 기존 실행 실패 처리 (Sonnet fallback 후보) 그대로\n\n앵커행        := 정확히 \"ERROR: [codex-review][review-timeout]\" 로 시작하는 stderr 라인 (라인 시작 anchored — substring 검색 금지)\n래퍼 timeout  := exit == 5 AND 앵커행 ≥ 1  → 즉시 대체 리뷰 (Sonnet fallback, fallback_reason: codex_timeout). 재시도·재호출 없음. 재리뷰 카운트(§3 escalation) 비포함 — 리뷰가 완료되지 않았다\ncodex passthrough := exit == 5 AND 앵커행 0 → 기존 실행 실패 처리 (Sonnet fallback 후보) 그대로\n\n예산 진단행    := 정확히 \"ERROR: [codex-review][round-budget-exceeded]\" 로 시작하는 stderr 라인 (라인 시작 anchored — substring 검색 금지)\n예산 소진      := exit == 6 AND 예산 진단행 ≥ 1  → 재호출 금지, 사람에게 핸드오프. Sonnet fallback 비대상. 리뷰가 수행되지 않았으므로 회차도 소모되지 않는다\ncodex passthrough := exit == 6 AND 예산 진단행 0 → 기존 실행 실패 처리 (Sonnet fallback 후보) 그대로\n\n카운터 진단행  := 정확히 \"ERROR: [codex-review][round-budget-unavailable]\" 로 시작하는 stderr 라인 (라인 시작 anchored)\n카운터 불가    := exit == 7 AND 카운터 진단행 ≥ 1  → **예산 소진이 아니다.** 카운터 디렉토리 쓰기 불가 또는 count 손상으로 예산을 강제할 수 없는 상태다. 사람 핸드오프가 아니라 **인프라 복구**(권한 수정 \u002F 손상 파일 제거) 후 재호출. Sonnet fallback 비대상 — 예산을 강제할 수 없는 채로 리뷰를 통과시키면 안 된다\n```\n\nsubstring 이 아니라 **라인 시작 anchored 접두사**로 판별하는 이유: 발췌·기존 진단 등 다른 stderr 내용에 예약 문자열이 섞여도(래퍼의 발췌 소독 — 발췌 내 예약 태그를 `[readiness-…]` 로 치환 — 이 1차 차단, 접두사 anchoring 이 2차 방어) 판별이 오염되지 않는다. advisory 는 별도 접두사 `WARNING: [codex-review][readiness-advisory]` (비차단) 이므로, advisory 경고가 방출된 뒤 codex 자신이 exit 4 를 반환하는 조합에서도 stderr 에 거부 진단행이 없어 호출자가 passthrough 로 정확히 분류한다.\n\nexit 5 앵커도 동일 원리로 **라인 시작 anchored — substring 검색 금지**: 래퍼가 스풀 내용을 방출하는 모든 경로에서 방출 전에 라인 시작의 예약 접두사 `ERROR: [codex-review][review-timeout]` 을 `ERROR: [codex-review][review-…]` 로 소독하므로, 결합 로그(`> log 2>&1`)에서 라인 앵커 매치는 래퍼 소유 stderr 라인뿐이다 (codex 자체 출력의 리터럴 오염으로 인한 오분류 배제).\n\n### 4.3 하니스 600s 상호작용 (워치독 생존 계약)\n\n- 워치독은 **래퍼 프로세스 내부** 메커니즘 — 하니스(Claude Code Bash)가 래퍼 프로세스를 살려두는 동안만 유효하다. 하니스가 Bash timeout 으로 래퍼를 죽이거나 process group 을 정리하면 워치독 생존은 **비보장** — 이 경우 exit 5 계약은 발화하지 않고, 호출자는 하니스 timeout 을 기존 실행 실패로 처리한다 (§4 본문).\n- **권장 호출 패턴**: `run_in_background: false` + Bash `timeout: 600000`(max) + 출력 직접 파일 리다이렉트 (`> \u002Ftmp\u002F\u003Coutput>.log 2>&1` — background-jobs.md 형식) 유지.\n- 절대 상한이 없으므로 정당한 장기 리뷰는 600s 를 넘을 수 있다. 600s 를 상회하는 리뷰가 하니스에 잘리는 것은 기존 \"600s 한계 넘으면 prompt 쪼개기\" 규약의 영역 — 워치독이 해소하는 것은 \"행 방치\" 이지 \"하니스 상한\" 이 아니다.\n\n---\n\n## 5. 리뷰 결과 기록 — v2 발급 (필수, PASS 회차 한정)\n\n**Phase 7 웨이브 3 ③-d (2026-08-24) 갱신.** 이전에는 이 절이 `trail\u002Fdod\u002F.codex-reviewed` legacy stamp 를 생성하는 절차였다. ③-d 로 legacy 리뷰 표식 3종(`.codex-reviewed`\u002F`.review-pending`\u002F`.security-reviewed`)의 write·read 경로가 전부 제거되면서, PASS 판정 회차에서 `bin\u002Frein issue-evidence code_review --verdict PASS --reviewed-digest \u003CD>` 를 호출하는 것이 **유일한 기록 절차**가 됐다. 이 호출이 없으면(또는 exit 0 이 아니면) `pre-bash-commit-review-gate.sh`(구 `pre-bash-test-commit-gate.sh` 후속, ③-c 삭제 완료)가 커밋을 차단한다 — 더 이상 대신 참조할 legacy stamp 가 없다.\n\n**Plan A Phase 6 이후 \u002F ③-a~③-d 계승**: `scripts\u002Frein-codex-review.sh` wrapper 가 code-review mode PASS 시 이 발급을 자동 호출한다. Sonnet 셀프리뷰 \u002F 수동 경로에서도 caller 가 동일한 발급 호출을 직접 수행한다.\n\n### 5.1 발급 인자\n\nv2 발급은 `--verdict`와 `--reviewed-digest` 두 인자만 받는다 (`plugins\u002Frein-core\u002Frein\u002Fcli\u002Fissue_evidence.py::issue()`). 과거 legacy stamp 가 담던 부가 정보(`reviewer`\u002F`cycle`\u002F`scope`\u002F`files_reviewed`\u002F`fallback_reason`\u002F`resolution`\u002F`remaining_issues`\u002F실행 증빙 5필드 `model`\u002F`effort`\u002F`effort_source`\u002F`policy_version`\u002F`codex_version`)는 **더 이상 durable 하게 저장되지 않는다** — 이 정보가 필요하면 §9 \"사용자 안내\" 문구에 그대로 담아 그 회차의 채팅 응답에만 남긴다. `reviewed-digest` 는 §2 \"Mode 별 기록 규칙\"이 서술한 대로 diff 범위(`HEAD~1` 체인 기반)로 매 실행 직접 산정한다.\n\n- `reviewer` 구분(`codex` \u002F `sonnet-fallback` \u002F `self-review`)은 §9 사용자 안내 문구로만 남는다.\n- `review_round`(같은 사이클 내 N번째 리뷰)는 발급 인자가 아니다 — 회차 계수·상한 강제는 언제나 §10 의 카운터가 전담한다 (legacy stamp 시절에도 래퍼 발급 stamp 에는 이 필드가 없었다).\n\n### 5.2 정상 통과 (codex)\n\n```bash\n\"${CLAUDE_PLUGIN_ROOT:-$PWD\u002Fplugins\u002Frein-core}\u002Fbin\u002Frein\" issue-evidence code_review \\\n  --verdict PASS --reviewed-digest \"$REIN_REVIEWED_DIGEST\"\n```\n\n성공(exit 0) 시 사람 보고에는 \"codex 리뷰 통과, 기록됨\"을 담는다 (§9).\n\n### 5.3 Sonnet Fallback\n\n발급 호출 자체는 5.2 와 동일하다 — `reviewer`\u002F`fallback_reason` 을 구분할 인자가 없으므로, §9 사용자 안내 문구에서 \"codex 실행 실패로 sonnet 폴백 리뷰로 통과, 사유: codex_timeout(또는 해당 코드)\" 를 명시한다.\n\n### 5.4 Sonnet 셀프리뷰 (Low\u002F경미한 Medium)\n\n발급 호출은 5.2 와 동일하다. §9 사용자 안내 문구에서 \"직전 codex 리뷰의 Low\u002FMedium(≤3줄) 지적을 수정한 뒤 self-review 로 통과\" 를 명시한다.\n\n### 5.5 사람 에스컬레이션 (기록 없음)\n\n3라운드 후에도 High 가 남아 있으면 **발급을 시도하지 않는다** — PASS 가 아니므로 대상이 아니다. 잔존 이슈는 §9 사용자 안내 형식으로 사람에게 직접 보고한다.\n\n### 5.6 발급 실패\u002F거부 처리\n\n발급이 exit 0 이 아니면(digest-mismatch 거부, 인프라 실패 등) 이 회차는 **기록되지 않는다** — legacy stamp 안전망이 없으므로, code-reviewer SKILL(`plugins\u002Frein-core\u002Fskills\u002Fcode-reviewer\u002FSKILL.md`)의 \"PASS 시 v2 발급\" 절과 동일한 fail-closed 판정을 따른다: digest-mismatch(또는 응답을 긍정적으로 그 외 사유로 파싱할 수 없는 경우)는 리뷰 대상 트리가 회차 도중 바뀌었다는 뜻이므로 digest 를 재캡처해 재리뷰해야 하고, 그 외 실패는 인프라 복구 후 재발급을 시도한다.\n\n---\n\n## 6. Non-interactive mode\n\n자동 호출 (예: plan-writer agent 가 spec review 트리거) 시 사용. Prompt 에 marker 가 있으면 사용자 질문(AskUserQuestion) 을 skip 하고 default 로 codex exec 실행.\n\n### 6.1 Invocation contract\n\nprompt 본문 어디든 다음 marker 가 있으면 non-interactive mode 활성:\n\n- `[NON_INTERACTIVE]` — AskUserQuestion 호출 skip\n- `[MODEL:\u003Cname>]` — model override (기본값은 단일 출처 `plugins\u002Frein-core\u002Fconfig\u002Fcodex-models.sh` 의 `CODE_GATE_MODEL` — 게이트 고정 모델)\n  - 허용 값: codex 가 지원하는 임의 모델명. 하드코딩 화이트리스트를 두지 않는다 (모델 rename 시 문서 수정 불필요).\n- `[EFFORT:\u003Clevel>]` — reasoning effort override (마커 부재 시 래퍼가 변경 규모로 산출 + 위험도 floor 적용; 측정 실패 시 fail-closed 페어의 `CODE_FAIL_CLOSED_EFFORT`(=`high`))\n  - 허용 값: `low`, `medium`, `high`\n  - **거부 값 (각자 사유 + 산출 진입)**:\n    - `ultra` — 게이트에서 **명시 거부**. ultra 는 자동 하위작업 위임(auto subagents)으로 동작해 게이트 판정이 비결정적이 되고 quota\u002Ftimeout 충돌을 일으킨다. stderr 에 ultra 전용 사유 메시지 출력.\n    - `max` \u002F `xhigh` — timeout 실측 전 **미지원** (후속 재검토 대상). stderr 에 각자 사유 메시지 출력.\n    - 셋 다 거부 후에는 마커 없음과 동일하게 **변경 규모 산출 경로로 진입**하며(floor 포함), 마커는 strip 되어 codex 프롬프트\u002F인자 어디로도 전달되지 않는다. 기타 무효값(`low2` 등)은 기존 generic warning 유지.\n- `[SANDBOX:\u003Cmode>]` — 래퍼는 이 마커를 읽지 않는다. 샌드박스는 항상 `read-only` 다 (§6.6).\n\n### 6.2 Marker 처리 절차\n\n1. prompt 에서 marker 패턴 (`\\[NON_INTERACTIVE\\]`, `\\[MODEL:[^\\]]+\\]` 등) 추출\n2. 추출 후 prompt 본문에서 marker 제거 (codex exec 로 전달 안 함)\n3. 추출된 값 또는 default 로 codex exec 명령 조립\n4. 실행 후 결과 캡처\n\n**구현 상태** (v1.1.2~):\n\n- `[EFFORT:\u003Clevel>]` — ✅ wrapper (`scripts\u002Frein-codex-review.sh`) 에 구현됨. 유효 값은 `low|medium|high`. `ultra`\u002F`max`\u002F`xhigh` 는 각자 전용 사유의 stderr 메시지(ultra=자동위임 게이트 금지, max\u002Fxhigh=timeout 실측 전 미지원) 후 산출 진입, 기타 무효값은 기존 generic warning + **변경 규모 기반 산출 진입**(`_compute_effort`). marker 는 유효성과 무관하게 codex 로 전달되는 prompt 에서 제거.\n- `[NON_INTERACTIVE]` — ✅ wrapper 의 `--non-interactive` CLI 플래그로 처리. marker 자체는 prompt 에 보존 (spec-review 모드 감지에 사용).\n- `[MODEL:\u003Cname>]` — ⏳ marker override 자체는 wrapper 미구현(legacy — prompt-controlled downgrade 차단 목적의 의도된 미구현). 단 **기본 모델은 단일 출처 `plugins\u002Frein-core\u002Fconfig\u002Fcodex-models.sh` 의 `CODE_GATE_MODEL`** 을 wrapper 가 `-m` 으로 항상 전달한다 (config 전 후보 부재 시 래퍼 내장 canonical `gpt-5.6-sol`+`high` 로 명시 폴백 — 무모델 호출 없음, §6.3). `[SANDBOX:\u003Cmode>]` — 의도된 미구현. prompt 에 있어도 래퍼는 무시하고 `--sandbox read-only` 를 전달한다 (§6.6).\n\n### 6.3 래퍼 자동 effort 산출 기준\n\n`\u002Fcodex-review` 호출 시 **래퍼(`rein-codex-review.sh`)가 변경 규모로 effort 를 결정론적으로 산출**한다(마커 부재 시). caller 가 `[EFFORT:\u003Clevel>]` marker 를 직접 prepend 하면 그 값이 산출을 **오버라이드**하고, 그 외에는 래퍼 산출이 적용된다. `~\u002F.codex\u002Fconfig.toml` 은 어느 경로에서도 건드리지 않는다 (사용자 환경 보존). 측정 실패\u002F불가 시 fail-closed **모델+effort 페어**(`CODE_FAIL_CLOSED_MODEL`+`CODE_FAIL_CLOSED_EFFORT` = `gpt-5.6-sol`+`high`)로 폴백 (`effort_source: fail_closed`).\n\n**코드 산출이 권위**다. 아래 표는 산출 휴리스틱의 의도를 설명하며, Claude 본체\u002F사용자의 수동 `[EFFORT:]` 는 산출을 **오버라이드**(상향\u002F하향)하는 용도다.\n\n| 조건                                                                                    | Effort   | 근거                                                        |\n| --------------------------------------------------------------------------------------- | -------- | ----------------------------------------------------------- |\n| docs-only, 3-10줄 diff, 기존 패턴 반복, markdown\u002Fcomment 수정                           | `low`    | reasoning 최소. 2분 내 완료 → harness auto-background 방지. |\n| 단일 모듈 코드 변경 \u003C100줄, hook 수정 1-2개, 아키텍처 변화 없음, 로컬 로직 추가         | `medium` | 표준 리뷰. 대부분 2-3분 내 완료.                            |\n| 다중 파일 + 보안 표면 \u002F 새 데이터 흐름 \u002F 아키텍처 변경 \u002F 복잡한 상태 관리 \u002F 동시성 이슈 | `high`   | 심층 reasoning 필요. 3분 초과로 background 전환 수용.       |\n\n**판단 경계**:\n\n- 경계 케이스는 한 단계 **낮춰서** 시작 → 리뷰 결과가 \"더 깊은 분석 필요\" 면 재리뷰 round 에서 `high` 로 승급.\n- security-reviewer 와 codex-review 는 **별개 agent**. codex-review 의 effort 는 보안 review 와 무관.\n- 사용자가 prompt 에 명시적 `[EFFORT:...]` 를 붙이면 Claude 의 자동 판단을 **오버라이드**. Claude 는 이를 존중.\n- **재승급 불변식 (E5)**: 재리뷰의 high 승급은 회차 숫자가 아니라 **남은 위험**으로 정한다 — 직전 회차 보고에 High 가 있었거나(그 High 를 고쳤더라도 — 수정은 아직 독립 검토 전이다) 후속 회차가 §2 (b)·(c) 에 어긋나면(지적 처리를 넘는 새 인터페이스·보안 경계·범위 변경, 새 판단) caller 가 `[EFFORT:high]` 마커를 주입한다. §2 후속 회차 조건 (a)~(c) 를 모두 만족하면 조건부 기본값 `[EFFORT:medium]`, 직전 보고가 필수 축을 수행하지 않았으면(직전 검토가 완결이 아님) 마커 없이 산출 강도(floor 포함)로 전체 검토한다. 마커는 산출보다 우선이므로 어떤 산출 결과도 재승급된 high 를 약화시키지 않는다.\n\n**위험도 floor (경로 기반 하한선)**:\n\n변경 크기 ≠ 위험도(3줄 인증 우회 = 고위험, 500줄 생성 문서 = 저위험)의 보완으로, **산출된 effort 에만** path 기반 하한선을 적용한다:\n\n- 위험 경로 패턴 5종: `hooks\u002F**` · `security\u002F**` · `config\u002F**` (임의 깊이 디렉토리 세그먼트 매칭), `.github\u002Fworkflows\u002F**` (repo 루트 앵커), `scripts\u002Frein-*.sh` (`scripts\u002F` 세그먼트 하위).\n- 변경 파일 중 한 줄이라도 매칭 + 산출 effort 가 `low` 면 `medium` 으로 승격 (도장에 `effort_source: computed+floor` 기록).\n- **low→medium 단방향만**: `medium`\u002F`high` 산출은 무변경 — floor 는 하한선이지 가산기가 아니다.\n- **마커 > floor**: 유효 `[EFFORT:]` 마커가 있으면 산출·floor 블록 자체에 진입하지 않는다 — `[EFFORT:low]` + 위험 경로 = 최종 `low`. `[EFFORT:high]` 재승급도 마커 경로라 산출\u002Ffloor 가 관찰하지 못한다 (E5 불변식 보존).\n- **spec-review 모드 미적용**: 문서 리뷰는 코드 경로가 아니므로 위험 경로 매칭 자체를 건너뛴다.\n- 측정 실패 폴백(`fail_closed`)은 이미 `high` 라 floor 와 구조적으로 상호 배타.\n\n**Canonical fallback (config 부재 시)**:\n\nconfig(`codex-models.sh`) 의 전 후보가 부재해도 \"빈 모델 → `-m` 생략 → codex 기본 모델 degrade\" 경로는 없다(폐지됨). 래퍼 내장 canonical 상수 **`gpt-5.6-sol` + `high`** 로 명시 폴백하고 stderr 경고를 정확히 1회 출력한다(config 정상 로드 시 무발화). codex 호출에 `-m` 은 **항상** 전달된다(무모델 호출 0건 — 게이트가 \"codex 기본 모델\" 이라는 외부 가변값에 좌우되지 않도록). 이 경로로 실행되면 도장의 `policy_version` 이 `0` 으로 기록된다.\n\n**재리뷰 일관성 (같은 사이클 = 같은 모델)**:\n\n같은 리뷰 사이클 내 재리뷰는 모델을 바꾸지 않는다 — effort 는 §2 의 후속 회차 규칙과 이 절의 재승급 규칙을 따른다. 게이트 모델이 `CODE_GATE_MODEL` 단일 고정이므로 이 일관성은 **구조적으로 보장**된다 — 규모\u002F회차별 모델 혼용은 결함 탐지 성향·severity 보정·PASS 기준이 달라져 게이트 판정 비일관을 만들므로 하지 않는다.\n\n### 6.4 사용 예 (agent 내부 자동 호출)\n\n입력 prompt:\n\n```\n[NON_INTERACTIVE] spec review for plan: docs\u002Fplans\u002F2026-04-20-v011-workflow-hardening.md\nValidate scope coverage and implementation feasibility.\n```\n\n→ skill 처리 후 실제 codex exec:\n\n```\n# 모델은 래퍼가 단일 출처(codex-models.sh)의 CODE_GATE_MODEL 을 -m 으로 전달.\ncodex exec -m \"$CODE_GATE_MODEL\" --sandbox read-only --config model_reasoning_effort=\"high\" \\\n  --skip-git-repo-check \"spec review for plan: ...\"\n```\n\n### 6.5 사용자 직접 호출과의 차이\n\n`[NON_INTERACTIVE]` marker 없이 `\u002Fcodex-review` 호출 시 기존 interactive 경로 (§2 \"Running a Task\" 의 AskUserQuestion 절차) 따름. 자동 mode 는 agent 내부 호출 전용.\n\n### 6.6 보안 노트\n\n래퍼는 codex 에 `--sandbox read-only` 를 **항상** 넘긴다(코드·문서 리뷰 공통). 생략하면 codex 가 사용자 `~\u002F.codex\u002Fconfig.toml` 의 `sandbox_mode` 를 상속하는데, 그 값이 `danger-full-access` 이면 리뷰어 명령이 저장소 밖 파일을 쓰고 네트워크에 접근할 수 있게 된다. 프롬프트 마커(`[SANDBOX:…]`)·환경 변수로 바꾸는 경로는 없다 — 자동 호출 경로의 권한 상승을 막기 위해서다.\n\n- **`workspace-write` 를 쓰지 않는 이유**: 리뷰어 명령이 저장소 안의 게이트 제어 파일 — 통과 기록 저장소, 훅 정책, 리뷰 회차 카운터, 문서 리뷰 표식, `.claude` 설정 — 을 쓸 수 있게 된다. 이 경로들은 리뷰 대상 지문에 들어가지 않으므로, 리뷰 대상 내용에 심은 지시로 리뷰어가 게이트를 약화해도 감지되지 않는다.\n- **대가**: 리뷰어는 임시 파일을 쓰는 테스트를 다시 돌리지 못한다(`\u002Ftmp` 도 쓸 수 없어 `mktemp` 가 실패한다). 리뷰어는 요청서의 증거 블록(§4.1)을 1차 증거로 대조한다 — 테스트 결과는 증거 블록으로 싣는다.\n- 리뷰어 셸 명령에는 네트워크가 없다. 다만 사용자 codex 설정에 등록된 MCP 서버는 이 샌드박스 밖에서 돌며 네트워크를 쓸 수 있다 — 샌드박스는 셸 명령만 묶는다.\n\n### 6.7 기록 분리 — spec review vs code review (CRITICAL)\n\nnon-interactive mode 는 **두 경로** 로 쓰인다:\n\n- **Code review 자동화** (향후) — agent 가 구현 완료 후 codex-review 를 호출하는 경우. 기존 §5 발급 규정 따름 → PASS 시 v2 code_review 증거 발급.\n- **Spec review 자동화** (v1.0.0 plan-writer 경로) — agent 가 plan\u002Fdesign 을 검증받는 경우. code_review v2 증거를 **절대 발급하지 않음**. legacy marker 는 이미 존재하지 않으므로 건드릴 대상도 없음. 코드리뷰 gate 는 별개 절차.\n\n**Spec review 모드 감지**: prompt 첫 줄이 `[NON_INTERACTIVE] spec review for plan:` 또는 `[NON_INTERACTIVE] spec review for design:` 형식으로 시작하면 spec-review 서브플로우로 분기.\n\n**spec-review 모드는 review-readiness 사전검사 대상이 아니다** — 사전검사·정량\u002FPASS 휴리스틱·`evidence_manifest:` 슬롯 전부 skip (사전검사는 code-review 모드 한정, §2 요청서 작성 규약 \u002F §4.1 참조). spec-review 지시문의 \"PASS\"·\"coverage\" 류 관행 토큰이 false-positive 로 자동 경로를 차단하지 않도록 하기 위함이다.\n\n**문서 리뷰의 판정 축 (2026-08-04)**: spec-review 모드는 코드 리뷰와 **다른 슬롯 세트**를 받는다. 래퍼가 결정 건전성 \u002F 범위·추적성 \u002F 주장 감사 3개 슬롯과 판정 규율 4종을 방출하며, 코드 결함·테스트 정합 슬롯은 방출하지 않는다.\n\n- 판정 축은 **결정·범위·추적성** 이다. 구현 수준 결함(오류 경로·자원 수명주기·버퍼링 방식·자료구조 선택)은 verdict 에 반영하지 않고 \"구현 단계 이관\" 참고사항으로 분류한다 — 문서는 실행할 수 없어 그런 주장을 검증할 수단이 없고, 코드 리뷰 게이트가 실물을 놓고 판정한다.\n- 같은 성격의 지적은 적용 대상 전체를 **한 회차에 일괄 열거**한다 (입력·대상별 분할 지적 금지).\n- 저장소에 없는 일반 규칙을 근거로 반려하지 않는다 — \"저장소 계약 위반\" 과 \"일반론 참고사항\" 을 분리 표기하며 후자는 verdict 를 승격시키지 않는다.\n- 수정 필요로 이어지는 지적은 **결정·범위·사실의 오류**여야 하고, 무엇을 잘못 전달하는지와 근거가 지적에 있어야 한다. 같은 뜻의 표현 선택·배치·명칭 취향은 \"참고사항\" 으로 분리해 verdict 를 승격시키지 않는다 — 실질 결함(잘못된 사용법·영향 범위·필수 보안·안전 제약의 누락(범위의 오류 — \"구현 단계 이관\" 참고사항이 아니다)·완료 주장·수치 불일치·누락된 추적 연결)은 계속 차단한다. 코드 봉투의 Claim Audit 도 문서 주장에 같은 문장을 쓴다 (§3).\n\n> 배경: 이전 판에는 모드 분기가 없어 문서 리뷰에도 코드 리뷰 슬롯이 그대로 나갔다. 즉 계획서를 놓고 \"resource leak 을 찾아라\" 라고 지시한 셈이고, 리뷰어가 파일 핸들 수명주기를 소송한 것은 지시대로 수행한 결과였다. 그 지적에 대응하려면 구현 세부를 문서 본문에 쌓아야 해서 리뷰 표면이 자가증식했다 (실측: 설계 리뷰 41회·계획 리뷰 26회, 구현 코드 0줄).\n\nSpec review 서브플로우 동작:\n\n1. codex exec 실행 → verdict 캡처 (PASS \u002F NEEDS-FIX \u002F REJECT)\n2. **code_review v2 증거를 발급하지 않음** (코드리뷰 게이트 오염 방지)\n3. legacy marker 는 이미 존재하지 않으므로 건드릴 대상도 없음\n4. PASS 시 caller (plan-writer 등) 가 책임지고 `bash \"${CLAUDE_PLUGIN_ROOT:-$PWD}\u002Fscripts\u002Frein-mark-spec-reviewed.sh\" \u003Cpath> \u003Creviewer>` 호출 — 이는 spec-review 전용 stamp (`trail\u002Fdod\u002F.spec-reviews\u002F\u003Chash>.reviewed`) 생성 (spec-review 축은 이번 웨이브의 존속 예외)\n5. NEEDS-FIX\u002FREJECT 시 caller 가 handoff (spec-review stamp 생성 안 함)\n\n**왜 분리?**: code_review v2 증거는 `pre-bash-commit-review-gate.sh`(구 `pre-bash-test-commit-gate.sh` 후속, ③-c 삭제 완료) 의 코드 commit gate 축 전용. spec review 에서 이 증거가 발급되면 코드 변경 없이도 gate 통과 가능해져 rein 규율이 깨짐.\n\n**Sonnet fallback 분기도 동일**: spec review 모드에서 codex 실패로 code-reviewer fallback 이 호출되더라도 code_review v2 증거 발급 금지. code-reviewer skill 이 spec-review context 를 인식할 수 있도록 prompt 전달 경로에서 `[NON_INTERACTIVE] spec review` prefix 보존.\n\n---\n\n## 7. Resume 정책\n\n**래퍼는 세션을 이어가지 않는다.** `scripts\u002Frein-codex-review.sh` 는 초회든 재리뷰든 매번 새 `codex exec` 세션을 띄운다 — 이전 회차의 대화는 다음 회차에 전달되지 않는다. 같은 사이클 재리뷰에서 이전 검토의 맥락을 살리는 수단은 세션 재사용이 아니라 **후속 회차 요청서 양식(§2)** 이다.\n\n수동으로 `codex exec resume --last` 를 쓰는 것은 권하지 않는다 — 래퍼를 거치지 않으면 회차 계수(§10)·이벤트 기록·증거 발급(§5)이 모두 빠진다.\n\n**사이클 경계**: 새 DoD, 새 PR, 새 기능 개발로 전환되면 후속 회차 양식을 쓰지 않고 초회 전체 검토로 시작한다. Second opinion 용도로는 절대 이 스킬이 아니라 `\u002Fcodex-ask` 를 사용한다 (v2 증거 미발급 + resume 금지).\n\n---\n\n## 8. Error Handling\n\n- `codex --version` 또는 `codex exec` 가 non-zero exit → 즉시 중단 + 보고. 재시도 전 사용자 지시 요청.\n- 고위험 플래그 (`--full-auto`, `--sandbox danger-full-access`, `--skip-git-repo-check`) 사용 전 `AskUserQuestion` 으로 허가 획득 (이미 허가되지 않은 경우).\n- 경고\u002F부분 결과가 포함된 출력은 요약 후 `AskUserQuestion` 으로 조정 방향을 확인.\n- Codex 실행 실패 → §4 Sonnet fallback 경로. 실패 사유는 §9 사용자 안내 문구에 담는다 (더 이상 저장할 stamp 필드가 없다).\n- **Hang 증상 탐지** (§2 실행 모드 절 참고):\n  - codex 프로세스가 수 분 이상 진행 없음 + CPU 0 + 네트워크 연결 0개 → Bash 도구 auto-background 전환으로 stdin 이 unix socket 에 붙어 sandbox\u002Fauth 초기화가 멈춤\n  - 체크: `lsof -p \u003Cpid> -i` 결과 비어있으면 API 요청 미도달. `ps -o stat,%cpu,etime -p \u003Cpid>` 로 Sleep + 0% CPU 확인\n  - 대응: kill 후 **foreground + stdin close + 직접 파일 출력** 형태로 재호출 (wrapper 경로 권장)\n  - 기록 상태 정리 (Phase 7 웨이브 3 ③-d 갱신): legacy stamp 가 없으므로 \"hang 이 발생한 사이클이 절반만 기록된 상태로 남는\" staleness 자체가 구조적으로 사라졌다 — v2 발급은 원자적 all-or-nothing(호출 자체가 성공\u002F실패)이다. 재호출 전에는 그 회차가 v2 발급까지 도달했는지(사람 보고 문구 또는 §9 결과)만 확인하면 된다.\n\n---\n\n## 9. 사용자 안내\n\n이 SKILL 의 결과를 사용자에게 보고할 때 (호출자 = Claude 가 wrapper output 을 user-facing chat 으로 emit 할 때) 다음 짧은 형식을 **먼저** 출력한다. wrapper 자체는 raw codex output 을 그대로 emit 하며, 안내 prepend 는 호출자 책임 (호출 환경이 다양하므로 wrapper 강제 안 함). 형식은 한 문장 또는 두 문장 — 결과 1줄 + 다음 액션 1줄.\n\n**Round PASS (verdict=PASS)**:\n> 코드 리뷰 통과. [차단급 결함 없음 또는 Low advisory N건]. 다음은 [보안 리뷰 또는 다음 단계].\n\n**Round NEEDS-FIX (verdict=NEEDS-FIX)**:\n> 리뷰에서 N건 수정이 필요해요 — [Severity 요약, 예: \"Medium printf 형식 + Low 테스트 stderr 미검증\"]. [고치고 재리뷰 또는 sonnet 셀프리뷰 — escalation 규칙 §3 따라].\n\n**Sonnet fallback 통과** (§4 — codex 실행 실패 한정. §3 의 sonnet self-review 와 다른 path):\n> codex 가 떠지지 않아 sonnet 폴백 리뷰로 통과시켰습니다 (사유: \u003C코드>). 통과 증거가 기록됐어요.\n\n**3회차에도 High 잔존 → 사람 에스컬레이션**:\n> 3회차 리뷰에도 High N건이 남아 사람 에스컬레이션이 필요해요. 이 회차는 기록되지 않았습니다 — 잔존 이슈를 직접 처리한 뒤 다시 리뷰해 주세요.\n\n**PASS 이지만 기록 실패** (v2 발급이 exit 0 이 아님 — §5.6):\n> 리뷰 자체는 통과했지만 이번 회차는 기록되지 않았어요 — [사유]. digest 재캡처 후 재리뷰가 필요합니다.\n\n이 짧은 안내 후에 기존 codex 본문 (Code Defects \u002F Design Alignment \u002F Test Alignment \u002F Claim Audit \u002F `FINAL_VERDICT: \u003CX>`) 를 그대로 emit 한다.\n\n---\n\n## 10. 회차 예산 (round budget)\n\n리뷰 회차 상한은 **래퍼가 강제**한다. 산문 지시가 아니라 코드 계약이다.\n\n| 항목 | 계약 |\n|---|---|\n| 사이클 키 | 문서 리뷰 = 리뷰 대상 문서의 **정규화된 절대 경로** \u002F 코드 리뷰 = 활성 작업 기준서 slug. 키가 다르면 예산은 독립. 정규화하지 않으면 같은 문서를 `docs\u002Fx.md` \u002F `.\u002Fdocs\u002Fx.md` \u002F 절대 경로로 번갈아 불러 각각 새 예산을 받는 우회가 생긴다 |\n| 기본 상한 | **5회차** (§3 의 \"3회차 High 잔존 → 사람 에스컬레이션\" 에 2회 여유) |\n| 계수 대상 | **미통과 회차만.** 통과(PASS)는 사이클 종료이므로 카운터를 제거한다 |\n| 계수 시점 | **verdict 가 확정된 뒤** 1회. 상한 판정만 spawn 이전에 한다. 리뷰가 수행되지 않은 종료 — 준비도 거부(4)·모델 오류(3)·워치독 정지(5) — 는 예산을 소모하지 않는다 (§4.2 의 \"재리뷰 카운트 비포함\" 과 정합) |\n| 초과 시 | codex 를 실행하지 않고 **exit 6** + 앵커 진단. v2 발급 미시도 |\n| 연장 | **프롬프트 첫 줄이 `[MAX_ROUNDS:5]` 하나뿐일 때만** 선언으로 인정한다 (정수 1~999). 기본 상한을 넘기는 선언은 stderr 경고 + 카운터 이력에 기록된다 — **조용한 연장 경로는 없다** |\n| 마커 오인 방지 | 본문 어디에 있는 마커 문자열도 선언으로 해석하지 않고 **원문 그대로 보존한다**. 위치 제한이 없던 이전 판에서는 이전 리뷰 출력이나 문서 예시를 인용하기만 해도, \"사용자는 `[MAX_ROUNDS:8]` 을 승인하지 않았다\" 같은 문장으로도 상한이 올라갔다 — 사람 승인 없이 예산이 늘어나는 경로였다. 첫 줄 단독이지만 정수가 아닌 표기도 선언이 아니며 본문이 유지된다 |\n| 상태 파일 | `trail\u002Fdod\u002F.review-rounds\u002F\u003C키 해시>` (key \u002F count \u002F limit \u002F updated \u002F extensions). 기록은 키별 락 안에서 파일을 다시 읽어 증가시키고 임시파일+교체로 원자화한다 |\n| 카운터 불가 | 디렉토리 쓰기 불가·count 손상은 예산 소진이 **아니다** — 별도 앵커 + exit 7. 손상 시 0 으로 되돌리지 않는다 (되돌리면 카운터를 훼손하는 것만으로 예산이 무한 리셋된다) |\n| 동시성 한계 | 판정과 기록이 codex 실행 시간만큼 떨어져 있어, 상한 직전 **동시 진입 k 건은 모두 통과**할 수 있다 (최대 k-1 회 초과, k 에 상한 없음). 초과분이 기록된 뒤의 호출은 정상 거부된다. 엄격히 막으려면 만료 가능한 진행 중 예약이 필요한데, 만료를 짧게 잡으면 정상 리뷰를 죽이고 길게 잡으면 강제 종료된 리뷰가 회차를 문다 — 유실 방지를 택하고 한계를 남긴 사용자 결정 (2026-08-04) |\n| 연장 이력의 수명 | 연장 선언은 선언 즉시 stderr 경고로 드러나고, 카운터가 살아있는 동안 그 파일의 이력에 남는다. **사이클이 통과로 끝나면 카운터와 함께 이력도 정리된다** — 세션을 넘는 영구 감사 기록이 아니다 |\n| 위협 모델 | 이 예산은 **정직한 에이전트의 규율 장치**다. 카운터 파일을 지우면 0회차로 돌아가고, 그 디렉토리를 지키는 훅은 없다. 대체 리뷰어 경로가 예산을 확인하지 않는 것도 문서 규정으로만 막혀 있다. 적대적 우회 하드닝은 범위 밖이며, 막는 대상은 \"고치다 보니 무한히 재리뷰하게 되는\" 발산이다. 다만 **의도치 않은 파일 파괴**(카운터 파일 또는 그 디렉토리가 심볼릭 링크인 경우)는 거부한다 — 판정 초입에서 검사해 mkdir·읽기 이전에 exit 7 로 중단하고, 미통과 기록은 락 안에서, 통과 정리는 rm 직전에 각각 재검사한다 (통과 정리 실패도 표식 없이 exit 7) |\n\n**호출자(에이전트) 규약**: exit 6 을 받으면 **재호출하지 않는다.** 지금까지의 지적과 남은 쟁점을 요약해 사람에게 넘긴다. 연장이 필요하다는 판단은 사람의 승인 사항이며, 에이전트가 스스로 `[MAX_ROUNDS:]` 를 올려 재시도하는 것은 이 계약의 위반이다.\n\n> 배경: 회차 상한이 산문 지시로만 존재하던 동안 두 번 무력화됐다 — 보고 없이 13회차 연속 실행, 그리고 사람의 종결 지시 이후 5회차 추가 실행(설계 문서 도장이 무효화되며 병렬 작업자 전원이 편집 차단됨). 산문으로 한 줄 더 쓰는 처방은 이미 두 번 반증됐으므로 채택하지 않았다.\n","# Codex Review Skill (Mode A)\n\n## 1. 목적\n\n코드 리뷰 전용 — rein 의 리뷰 게이트 생성 스킬. `\u002Fcodex-review` 슬래시 명령으로 호출한다.\n\n이 스킬은 **리뷰 gate** 다. PASS 회차에서 `bin\u002Frein issue-evidence code_review --verdict PASS --reviewed-digest \u003CD>` 로 v2 code_review 증거를 발급하며, 커밋 시점에 `pre-bash-commit-review-gate.sh`(Phase 7 웨이브 3 ③-c 신설 — 구 `pre-bash-test-commit-gate.sh`, ③-c 삭제 완료, 의 코드 리뷰 축 후속)가 이 v2 증거만으로 `git commit` 허용 여부를 판정한다. **Phase 7 웨이브 3 ③-d (2026-08-24)** 로 legacy stamp(`trail\u002Fdod\u002F.codex-reviewed`\u002F`.review-pending`) 의 write·read 경로가 전부 제거됐다 — 위 v2 발급이 이 스킬의 유일한 기록 절차이며, dual-read 전환기 서술은 더 이상 유효하지 않다. (테스트 실행 자체는 GUARD-1 이후 이 리뷰 게이트 대상이 아니다 — pre-bash-commit-discipline-gate.sh 의 동일 지점 주석 참조.)\n\nSecond opinion (brainstorm 반박, spec sanity, refactor tradeoff 이중 검증) 이 필요하면 `\u002Fcodex-review` 가 아니라 `\u002Fcodex-ask` 를 사용한다 — v2 증거를 발급하지 않는 별도 스킬.\n\n### Mode 대비\n\n| 항목         | Mode A (`\u002Fcodex-review`)                                                | Mode B (`\u002Fcodex-ask`)                                                                  |\n| ------------ | ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |\n| 용도         | 리뷰 게이트                                                             | Second opinion                                                                         |\n| 리뷰 결과 기록 | **필수** (v2 code_review 증거 발급)                                    | **절대 금지**                                                                          |\n| Resume --last | 래퍼는 매번 새 세션 — 맥락은 후속 회차 요청서 양식으로 (§2, §7)           | **금지** — 매번 새 세션                                                                |\n| Sandbox      | 래퍼가 `read-only` 고정 (§6.6)                                          | 항상 `read-only`                                                                       |\n| **Fallback** | codex 실행 **실패** 시에만 Sonnet\u002Fcode-reviewer\u002Fhuman fallback 허용 (§4) | **same-session Claude fallback 금지**. 독립 reviewer 부재 시 degraded \u002F no-second-opinion 으로 명시 |\n\nFallback 정책 비대칭 이유: Mode A 는 \"리뷰가 어떤 형태로든 수행되어 v2 증거가 발급\" 되는 것이 게이트 통과의 전제이므로 codex 실행 실패 시 차순위 reviewer 로 대체할 수 있다. Mode B 는 \"독립 관점\" 자체가 가치이므로 same-session Claude 가 대신하면 본질이 깨진다 — 호출자는 결과 없이 작업을 계속하거나, 별도 시점에 Mode B 를 다시 시도한다.\n\n---\n\n## 2. Running a review\n\n`\u002Fcodex-review` 는 `scripts\u002Frein-codex-review.sh` wrapper 를 호출한다.\nWrapper 가 context assembly, envelope 4 slots, codex exec, PASS 시 v2 code_review 증거 발급을 담당한다.\n\n### Usage\n\n- Interactive (code review): `bash \"${CLAUDE_PLUGIN_ROOT:-$PWD}\u002Fscripts\u002Frein-codex-review.sh\"`\n- Automated (agent, code review, **file redirect 필수**):\n\n  ```bash\n  # 1. prompt 를 임시 파일로 작성 (Write 도구 또는 echo > file)\n  # 2. file redirect 으로 wrapper 호출 — pipe (`|`) 사용 금지 (pre-bash-safety-guard 차단)\n  bash \"${CLAUDE_PLUGIN_ROOT:-$PWD}\u002Fscripts\u002Frein-codex-review.sh\" --non-interactive \u003C \u002Ftmp\u002Fcodex-prompt.txt\n  ```\n\n- Spec review (plan review): prompt 파일 맨 앞에 `[NON_INTERACTIVE] spec review for plan: \u003Cpath>` marker. wrapper 가 spec-review mode 로 분기.\n- Spec review (design review): prompt 파일 맨 앞에 `[NON_INTERACTIVE] spec review for design: \u003Cpath>` marker.\n\n> **Pipe vs file redirect**: `printf '...' | bash wrapper.sh` 패턴은 pre-bash-safety-guard 가\n> 차단한다 (이유: stdin 의 명령 source 를 hook 가 검증할 수 없음). 항상 `\u003C \u002Ftmp\u002F\u003Cfile>`\n> 형태로 호출하라.\n\n### 요청서 작성 규약 — review-readiness 사전검사\n\n정량(개수\u002F비율\u002F백분율)·테스트 통과 주장을 쓰려면 각 주장을 `[EVIDENCE]` 블록으로 선언한다. 블록 없이 주장을 쓰면 래퍼가 review-readiness 사전검사에서 **exit 4 로 거부**한다 (codex 미호출 — 비용 0). 주장 없는 요청서는 종전대로 블록 불필요 (완전 하위호환 — 기존 요청서 무변경). output 발췌에 `[EFFORT:...]` 리터럴을 포함하지 마라 — 래퍼의 기존 전역 strip 단계가 제거한다 (사전검사가 보는 \"원문\" 은 strip 이후의 PROMPT_BODY). **블록을 갖췄다고 모든 잔존 정량 서술이 통과하는 것은 아니다** — 블록 밖에 남은 정량\u002FPASS 서술은 형태에 따라 두 tier 로 갈린다: 검증명사+통과어 공존(예: \"테스트 통과\"), 계약 문맥어 없이 또는 결과 서술어와 함께 쓰인 비율·백분율(예: \"커버리지 85%\"), 그리고 결과 서술어가 있고 계약 문맥어가 없는 수량+단위(예: \"실패 3건 발견\")는 **reject-tier** 로 블록이 있어도 exit 4 거부; 고정 계약 형태(예: \"함수 50줄 이내\", \"오차 상한 ±20%\")와 결과 서술어 없는 단독 수량(예: \"파일 5개\")은 **advisory-tier** 로 비차단 경고에 그친다 (문법·집합식: §4.1).\n\n- 증거 블록 문법 전문: §4.1\n- exit 4 판별 계약 + 호출자 행동: §4.2 (Sonnet 폴백 비대상)\n- spec-review 모드는 사전검사 비대상: §6.7\n- **수치 주장의 구성**: 합계·개수를 주장하면 그 숫자가 무엇의 합인지 한 줄로 적는다. 최소 형식은 `\u003C합계> = \u003C항목1 값>(\u003C출처>) + \u003C항목2 값>(\u003C출처>) + …` 이고, 출처는 파일 경로(필요하면 절)·명령 중 하나다. 예: `변경 테스트 파일 7 = 신규 3(git status --short tests\u002F) + 갱신 4(git diff --name-only tests\u002F)`. 리뷰어(Claim Audit)는 각 항목을 출처에서 확인하고, 모든 항목이 확인되고 합이 맞으면 그 숫자를 1:1 대응을 갖춘 것으로 본다. 구성이 없으면 리뷰어가 숫자마다 대응을 직접 찾아야 하고, 찾지 못하면 수정 필요로 판정한다. 사전검사(형식 검사)는 구성 줄을 따로 검사하지 않는다 — 다만 구성 줄도 블록 밖에 쓰면 정량 서술로 스캔되므로(§4.1 두 tier) \"통과·발견·실패\" 같은 결과 서술어를 넣지 말고 사실만 나열한다.\n\n### 자가검증 증거 — 두 축 계약\n\n코드 리뷰 모드에서 리뷰 대상에 변경(추적 파일 변경 또는 미추적 파일)이 있으면, 래퍼는 codex 를 부르기 전에 요청서에 아래 세 가지가 있는지 **형식만** 확인한다. 명령을 다시 실행하지 않고, 출력의 진위도 확인하지 않는다. 하나라도 없으면 exit 4 로 거부한다(§4.2 — codex 미호출, 회차 비소모). spec-review 모드와, 변경이 문서·trail 뿐이라고 판정된 경우는 확인하지 않는다.\n\n1. `[axis:typecheck]` 블록 — `claim:` 에 `[axis:typecheck]` 토큰을 정확히 하나 담고 `exit_code: 0` 인 `[EVIDENCE]` 블록(§4.1). 타입 검사·린트·구문 검사 같은 정적 검증이다.\n2. `[axis:test]` 블록 — `claim:` 에 `[axis:test]` 토큰을 정확히 하나 담고 `exit_code: 0` 인 `[EVIDENCE]` 블록. 1 과 **다른 블록**이어야 한다. 한 `claim:` 에 두 토큰을 같이 넣으면 어느 축으로도 세지 않는다.\n3. `diff_self_review:` 단독 라인 — 작성자가 diff 를 직접 읽고 확인한 내용 한 줄(비어 있으면 안 된다).\n\n**테스트 블록의 범위는 정해져 있지 않다.** 래퍼는 `[axis:test]` 블록이 전체 스위트를 실행했는지 보지 않는다. 초회에는 전체 스위트를 권장하고, 2회차부터는 아래 \"델타 증거\" 형식으로 대상 테스트만 실행해도 된다.\n\n예외 선언 — 블록·코드 펜스 밖의 단독 라인으로 쓴다:\n\n- `verification_commands: none` — 이 저장소에 실행할 검증 명령이 없을 때. 두 블록 요구를 면제하고 `diff_self_review:` 만 요구한다.\n- `verification_state: tests-intentionally-red` + `expected_failure: \u003C무엇이 왜 실패해야 하는가>` — TDD 의 red 단계. `[axis:typecheck]` exit0 블록 + `[axis:test]` 블록(exit 1~123·125·126 허용, 0·124·127·128 이상은 거부) + `diff_self_review:` 를 요구한다.\n\n### 대상 테스트 선택 도구 — `rein-affected-tests.py`\n\n후속 회차 델타 증거의 대상 테스트(아래 \"델타 증거\" 3번 칸)를 눈으로 고르지 않고 기계로 고른다. 요청서 작성자가 직접 실행한다 — 래퍼는 이 도구를 부르지도, 테스트를 실행하지도 않는다.\n\n```bash\npython3 \"${CLAUDE_PLUGIN_ROOT:-$PWD\u002Fplugins\u002Frein-core}\u002Fscripts\u002Frein-affected-tests.py\" \\\n  --base \u003Cfull_run_tree> --untracked-scope '\u003Cuntracked_scope 의 glob>' \\\n  --untracked-snapshot \u003Cuntracked_at_full_run 줄을 담은 파일> --format json\n```\n\n`--base` 를 생략하면 `HEAD` 대비 작업 트리 변경을 본다. `--untracked-scope` 는 델타 증거의 `untracked_scope:` 와 같은 glob 을 준다(여러 번 지정 가능, 생략하면 전체). `--untracked-snapshot` 은 `untracked_at_full_run:` 의 줄들(`\u003C경로> \u003Csha256> \u003C줄 수>`, 없으면 `none`)을 담은 파일이다 — 도구가 미추적 항목을 델타 증거 2번 칸과 같은 규칙으로 계산한다. 외부 입력이 바뀌었거나 알 수 없으면 `--external-inputs-changed` 를 붙인다. `--format text` 는 사람용 요약, `--no-install` 은 아래 자동 설치를 건너뛴다. 종료 코드 0 이면 출력이 있다(전체 실행 판정 포함). 0 이 아니면 도구를 쓰지 못한 것이다 — 전체 스위트를 돌리거나 직접 골라 `selection: manual` 로 적는다. 도구 전체 실행 시간은 최대 600초다.\n\n**판정 순서**:\n\n1. 먼저 아래 \"전체 테스트 재실행으로 돌아가는 조건\" 의 의존성·빌드 설정, 테스트 공용 설정·픽스처, 소스 5개·200줄 초과(줄 수는 같은 산식 — 문서 포함 델타 전체), 소스 삭제·이름 변경, 외부 입력 변경, 변경 없음을 본다. 하나라도 걸리면 어떤 방법도 쓰지 않고 `전체 실행 필요: 예` 다 — 프로젝트 스크립트도 이를 뒤집지 못한다.\n2. `project-script` — 프로젝트가 둔 `.rein\u002Faffected-tests.sh`. 계약: stdin 으로 바뀐 경로를 한 줄에 하나 받고, stdout 으로 `{\"tests\": [...], \"command\": \"...\"}` JSON 을 낸다(최대 1 MiB). 종료 코드 0 = 결과 유효, 3 = 전체 실행 필요, 그 밖·120초 초과·형식 오류 = 다음 방법으로 내려간다. **고른 테스트가 빠짐없는지는 프로젝트가 책임진다 — rein 은 검증하지 않는다.** 도구는 `--base` 에 커밋된 판을 꺼내 실행한다 — 기준 이후 바뀐 내용은 실행하지 않고, 기준에 없는 새 파일이면 건너뛴다. 스크립트는 그 판의 트리 전체를 임시 디렉터리로 꺼낸 곳을 작업 디렉터리로 실행되므로 같은 판의 보조 파일(source 하는 helper 등)도 그 판이 쓰인다. 현재(검토 대상) 저장소는 환경 변수 `REIN_AFFECTED_TARGET`(저장소 절대 경로)로만 전달되니, 선택 대상 파일은 이 경로 아래에서 읽어야 한다. 그 밖에 `REIN_AFFECTED_BASE`·`REIN_AFFECTED_ROOT` 를 받는다. 실행 환경에서는 `BASH_ENV`·`ENV`·`CDPATH`·내보낸 셸 함수를 제거하고 `bash --noprofile --norc` 로 실행한다.\n3. 두 스택이 함께 바뀌었거나 지원 스택(파이썬 pytest, JS\u002FTS jest·vitest·`npm test`) 밖이면 `전체 실행 필요: 예` 다. 이후 방법 중 jest·vitest·import-search 는 저장소 전체에서 첫 인자가 고정 문자열 리터럴이 아닌 동적 import(`importlib.import_module(name)`·`__import__(name)`·`require(name)`·`import(name)`·인터폴레이션이 있는 템플릿 문자열, 따옴표 문자열에 다른 식을 이어 붙인 `import(\".\u002Fplugins\u002F\" + name)` 같은 형태 — 따옴표 문자열 하나만 인자로 오는 경우만 고정 경로로 본다)를 쓰는 파일을 찾아, 그런 테스트 파일과 그런 소스 파일에 import 로 (중간 모듈을 거쳐서라도) 닿는 테스트를 바뀐 파일과 무관하게 항상 대상에 더한다 — 동적 import 는 무엇을 불러오는지 정적으로 알 수 없기 때문이다. 테스트 파일 규칙을 바꾸는 설정(pytest 의 `python_files`·`testpaths`, jest 의 `testMatch`·`testRegex`·`roots`, vitest 의 `include`·`includeSource`)이 있거나, 저장소에 구문 오류가 있는 파이썬 파일·읽을 수 없거나 너무 큰 파일이 있거나, 저장소를 끝까지 훑지 못하면, 또는 더한 결과가 전체 테스트 파일의 절반을 넘으면 `전체 실행 필요: 예` 다(jest·vitest 의 이 절반 계산은 jest·vitest 의 실제 대상이 아니라 선택 도구 자신의 import 그래프로 센 근사다). testmon 은 실제 실행을 추적하므로 이 규칙에서 빠진다. 도구가 따라가지 못하는 경우가 남는다: JS 경로 별칭·번들러 설정 기반 모듈 해석, 함수 별칭으로 감싼 호출·`exec`·`eval`·설정 문자열 기반 플러그인 로딩 같은 패턴 밖의 동적 로딩, 그리고 JS 파일의 구문 오류(감지하지 않는다)다.\n4. `jest` \u002F `vitest` \u002F `testmon` — 정밀 도구. jest·vitest 는 `package.json` 의 의존성에 있고 `node_modules\u002F.bin\u002F` 에 실행 파일이 있을 때, 명령은 `jest --findRelatedTests \u003C파일>` \u002F `vitest related --run \u003C파일>` 이고, 3 의 추가 대상이 있으면 `&& jest --runTestsByPath \u003C추가 경로>` \u002F `&& vitest run \u003C추가 경로>` 가 붙는다. testmon 은 파이썬 프로젝트에 pytest-testmon 과 기록(`.testmondata`)이 있을 때, 명령은 `pytest --testmon` 이다. 세 도구 모두 대상은 도구가 실행 시 정한다. testmon 의 기록이 없으면 전체 실행이고 명령은 `pytest --testmon-noselect`(선택 없이 전체를 돌리며 기록을 만든다)다.\n5. `import-search` — rein 내장 검색. 바뀐 모듈을 import 하는 테스트, 그리고 바뀐 모듈을 import 하는 다른 모듈을 import 하는 테스트(2단계)를 고르고, 3 의 추가 대상을 더한다. 파이썬은 import 문, JS\u002FTS 는 상대 경로 import·require 만 본다. 다음이면 전체 실행: 바뀐 모듈을 직접 import 하는 테스트가 둘 이상(공용 모듈 — 이 방법은 완화 대상이 아니다) \u002F 어떤 테스트에도 매핑되지 않는 소스 \u002F 대상이 전체 테스트 파일의 절반 초과.\n6. `full` — 위 어느 것으로도 고르지 못했다. 사유가 함께 나온다.\n\n도구 방법(`jest`·`vitest`·`testmon`)이 실행 시 \"관련 테스트 없음\" 으로 실패하면 전체 실행으로 간다. testmon 이 있는 프로젝트의 전체 실행(기준 실행 포함)은 `pytest --testmon-noselect` 로 돌린다 — `selection: testmon` 의 기준 실행 블록은 이 옵션을 포함해야 한다(아래 델타 증거 4번 칸).\n\n**정밀 도구 자동 설치** — 4단계에서 파이썬 프로젝트에 pytest-testmon 이 없으면 한 번 설치를 시도한다(1~3단계에서 결론이 나면 시도하지 않는다): `poetry.lock` 이 있으면 `poetry add --group dev \"pytest-testmon>=2,\u003C3\"`, `uv.lock` 이 있으면 `uv add --dev \"pytest-testmon>=2,\u003C3\"`, 그 외에는 활성 venv(`VIRTUAL_ENV`)가 있을 때만 그 venv 의 `python -I -m pip install \"pytest-testmon>=2,\u003C3\"`(작업 폴더의 같은 이름 모듈을 집어오지 않도록 격리 실행)(+ `requirements-dev.txt` 가 있으면 한 줄 추가). venv 없는 시스템 파이썬에는 설치하지 않는다. 결과는 `.rein\u002Fstate\u002Ftest-tools.json` 에 기록하고, 기록이 있으면 다시 시도하지 않는다 — 다시 시도하려면 그 파일을 지운다. 설치에 성공한 회차는 의존성이 바뀌었으므로 전체 실행(`pytest --testmon-noselect`)이다. 실패하면 `import-search` 로 내려간다. 끄려면:\n\n```yaml\n# .rein\u002Fpolicy\u002Ftest-selection.yaml\nauto_install_test_tools: false\n```\n\n파일이 없으면 켜짐이다. 파일이 있는데 읽을 수 없으면(형식 오류·PyYAML 없음·true\u002Ffalse 가 아닌 값) 꺼짐으로 보고 경고한다.\n\n### 리뷰 전 프로젝트 사전 검사 — `.rein\u002Freview-precheck.sh`\n\n프로젝트가 저장소 루트에 `.rein\u002Freview-precheck.sh` 를 커밋해 두면, 래퍼가 코드 리뷰 모드에서 codex 를 부르기 전에 이를 실행한다. 린트·정적 규칙처럼 외부 리뷰 전에 기계로 잡을 수 있는 문제를 여기서 걸러 회차를 아낀다. 파일이 없으면 아무 일도 일어나지 않는다. 검사 항목은 프로젝트가 정한다 — rein 은 연결만 한다.\n\n- **시점**: 위 \"자가검증 증거 — 두 축 계약\" 확인을 통과한 뒤, 회차 예산 판정 전. spec-review 모드에서는 실행하지 않는다. 자가검증이 요구되지 않는 리뷰(문서·trail 뿐인 변경)에서도 코드 리뷰 모드면 실행한다.\n- **검토 대상 이전 판만 실행**: 작업 트리의 파일은 직접 실행하지 않는다. 작업 트리 리뷰에서는 HEAD 에 커밋된 판, 커밋 범위 리뷰에서는 검토 대상 커밋들 이전(리뷰 비교 기준)의 판을 꺼내 실행한다 — 리뷰 중인 변경이 이 스크립트를 추가·수정·삭제해도 그 내용은 이번 리뷰에서 실행되지 않는다. 그 기준에 파일이 없으면 실행하지 않고 경고(`WARNING: [codex-review][readiness-advisory]`) 후 리뷰를 진행한다.\n- **실행**: 검토 이전 판의 **트리 전체**를 먼저 검사(절대 경로·`..`·외부 심볼릭 링크 거부)한 뒤 저장소 밖 임시 디렉터리에 꺼내, 그 디렉터리를 작업 디렉터리로 `bash --noprofile --norc` 로 실행한다. 같은 판의 보조 파일(source 하는 helper)도 그 판이 쓰인다. 현재(검토 대상) 저장소는 환경 변수 `REIN_PRECHECK_TARGET`(저장소 절대 경로)으로만 전달되니, 검사 대상 파일은 이 경로 아래에서 읽어야 한다. stdin 은 비어 있다. 실행 환경에서는 `BASH_ENV`·`ENV`·`CDPATH`·내보낸 셸 함수를 제거한다. 기준 판이 일반 파일이 아니거나 트리 검사에 걸리면 거부한다.\n- **환경 변수**: `REIN_PRECHECK_TARGET`(검토 대상 저장소 절대 경로), `REIN_PRECHECK_PROJECT_DIR`(저장소 루트), `REIN_PRECHECK_CHANGED_FILES`(리뷰 대상 파일 목록을 한 줄에 하나 담은 파일의 경로), `REIN_PRECHECK_CHANGED_FILES_RC`(0 이 아니면 목록을 얻지 못한 것 — 전체 검사로 가라), `REIN_PRECHECK_DIFF_BASE`(리뷰 비교 기준), `REIN_PRECHECK_REVIEW_SUBJECT`(`working_tree` 또는 `commit_range`).\n- **시간 제한**: 기본 120초, `REIN_REVIEW_PRECHECK_TIMEOUT` 으로 조정(1~9999 정수). 래퍼가 스크립트를 자기 프로세스 그룹으로 띄워 직접 시간을 재므로 `timeout` 명령이 없는 환경(macOS 기본)에서도 지켜지고, 기한을 넘기면 그룹 전체(스크립트가 띄운 프로세스 포함)를 끝낸다. 이를 위해 `setsid` 또는 `perl` 이 필요하다 — 둘 다 없으면 실행하지 않고 거부한다.\n- **출력**: 1 MiB 까지만 보관하고 나머지는 버린다. 거부 시 마지막 20줄을 보여 준다.\n- **결과**: 종료 코드 0 = 통과(출력 없이 리뷰 진행). 그 밖은 exit 4 거부 — codex 를 부르지 않고 회차를 쓰지 않는다. 거부 진단행은 `ERROR: [codex-review][readiness-reject] 프로젝트 사전 검사` 로 시작하고 유형(실패 rc \u002F 시간 초과 \u002F 실행 불가 \u002F 거부)을 밝힌다. 스크립트가 직접 124·137 로 끝나면 시간 초과로 보고된다.\n- **규약**: 작업 트리를 바꾸지 않는다(래퍼는 확인하지 않는다 — 바꾸면 리뷰 대상이 바뀐다). 같은 입력에 같은 결과를 낸다. 지적은 파일 경로와 줄을 포함해 stdout·stderr 에 쓴다.\n\n### 후속 회차 요청서 양식 (2회차 이상)\n\n래퍼는 재리뷰마다 **새 codex 세션**을 띄운다 — 이전 회차의 검토 내용은 자동으로 전달되지 않는다(§7). 그래서 2회차부터는 호출자가 아래 다섯 칸을 요청서에 직접 싣는다. 목적은 리뷰어의 주의를 \"이전 지적 + 수정분 + 그것이 닿는 계약\" 에 모으는 것이다.\n\n1. **직전 검토 근거** — 같은 사이클의 몇 번째 확인인지, 직전 판정과 차단급 지적 유무.\n2. **열린 지적과 처리** — 지적별로 셋 중 하나: 고침(어디를) \u002F 위험 수용(근거·기록 위치) \u002F 반박(근거).\n3. **종결된 지적** — 이미 배제·수용해 다시 다루지 않을 항목. 새 세션은 이를 모르므로 적지 않으면 같은 지적이 회차마다 되돌아온다.\n4. **직전 검토 이후 바뀐 것** — 항목으로 나열(수정분의 범위).\n5. **영향받는 계약과 증거** — 4 가 닿는 계약과 그 검증 명령(`[EVIDENCE]` 블록, §4.1). 테스트 증거는 아래 \"델타 증거\" 형식으로 대상 테스트만 실행해 갖출 수 있다.\n\n요청 문장은 다음 취지로 쓴다: \"후속 확인이다 — 필수 검사 축은 그대로 수행하되 4·5 를 우선 확인해 달라. 중대한 결함은 범위와 무관하게 보고해 달라.\" **\"그 밖의 새 결함도 함께 봐 달라\", \"수정이 새 충돌을 만들지 않았는지 전체를 확인해 달라\" 같은 문장은 쓰지 않는다** — 매 회차 전체 재탐색을 요청하는 것과 같다.\n\n**델타 증거 — 후속 회차의 테스트 증거**\n\n2회차부터는 매 회차 전체 테스트 스위트를 다시 돌리지 않는다. 대신 \"전체 스위트를 돌린 시점의 트리 식별자 + 그 뒤 바뀐 파일(델타) + 델타가 닿는 테스트만 실행한 기록\" 을 싣는다. 자가검증 두 축 계약의 `[axis:test]` 블록은 이 대상 테스트 실행으로 채운다(래퍼는 테스트 범위를 보지 않는다 — 위 \"자가검증 증거 — 두 축 계약\"). 코드 리뷰 봉투의 Claim Audit 은 이 형식을 갖춘 요청서에 전체 스위트 재실행을 요구하지 않는다.\n\n1. **기준 트리 식별자** — 전체 스위트를 돌린 **직후**에 `git stash create` 를 실행해 출력된 커밋 id 를 적는다. 이 명령은 작업 트리와 stash 목록을 바꾸지 않고 추적 파일의 현재 상태(스테이지 포함)를 커밋 객체로 고정한다. 출력이 비어 있으면(추적 변경 없음) `git rev-parse HEAD` 의 값을 쓴다. 이 커밋 객체는 어느 ref 에도 걸리지 않는다 — 가비지 수집 유예(기본 2주)는 보장이 아니므로, 요청서를 쓰기 직전에 `git cat-file -e \u003Cid>` 로 아직 있는지 확인하고 없으면 전체 스위트를 다시 돌려 새 식별자를 만든다. 전체 스위트 직후에 `git ls-files --others --exclude-standard` 의 결과를 `untracked_scope:` 에 적은 범위(glob 목록, 예: `src\u002F** tests\u002F**` — 생략하면 전체)로 거르고, 남은 파일마다 `\u003C경로> \u003Csha256> \u003C줄 수>` 한 줄(`sha256sum`·`wc -l`)을 `untracked_at_full_run:` 에 적는다. 기준과 현재 비교는 항상 같은 `untracked_scope` 를 쓴다 — 집합이 다르면 문서·trail 파일이 added 로 오판된다 — `git stash create` 는 미추적 파일 내용을 고정하지 않으므로 이 지문·줄 수 스냅샷이 미추적 파일의 기준이다(없으면 `none`).\n2. **델타** — `git diff --name-status \u003C기준 트리 식별자>` 의 출력을 그대로 싣고, `git diff --numstat \u003C기준 트리 식별자>` 의 추가·삭제 합을 `delta_lines:` 에, 소스 파일(테스트·문서 제외) 수를 `delta_source_files:` 에 적는다. `--name-status` 에는 미추적 파일이 나오지 않으므로 `untracked_at_full_run` 스냅샷과 지금 `git ls-files --others --exclude-standard` 를 **같은 `untracked_scope`** 로 거른 목록을 비교해 세 경우를 `?? \u003C경로> \u003Cadded|modified|deleted>` 로 직접 추가한다: added(지금 줄 수) — 스냅샷에 없던 파일, modified(스냅샷·지금 중 큰 줄 수) — sha256 이 다른 파일, deleted(스냅샷 줄 수) — 스냅샷에 있었는데 지금 미추적 목록에 없는 파일. **추적 전환 규칙**: 스냅샷 파일이 지금 추적 파일이 됐으면(`git ls-files --error-unmatch \u003C경로>` 성공, `--name-status` 에 `A` 로 나타남) deleted 가 아니다 — `--name-status` 항목으로만 한 번 센다. 세 경우의 줄 수를 `delta_lines` 에 더하고, 소스 파일이면 `delta_source_files` 에도 센다(산식: `numstat 합 + ?? 줄 수 = delta_lines`, `--name-status 소스 + ?? 소스 = delta_source_files` — 리뷰어가 같은 산식으로 재계산해 대조한다). 판단이 어려우면 범위 안 미추적 파일을 전부 modified 로 올린다.\n3. **대상 테스트** — 델타 파일마다, 그 파일을 import 하거나 그 동작을 실행하는 테스트 파일을 전부 나열한다(산정 책임은 요청서 작성자). 위 \"대상 테스트 선택 도구\" 로 골랐으면 `selection:` 에 출력의 선택 방법을 적고, 도구 실행을 axis 토큰 없는 `[EVIDENCE]` 블록으로 싣는다 — `command:` 는 도구 호출 그대로(`--base \u003Cfull_run_tree>`, `--untracked-scope`, `--untracked-snapshot`, `--format json` 포함), output 은 JSON 출력 전체다. 리뷰어는 그 JSON 의 `base`·`untracked_scope`·`changed`·`delta_lines`·`delta_source_files` 를 이 델타 증거의 값과 대조한다 — 도구를 실행한 뒤 파일이 더 바뀌었으면 대조가 어긋나므로 도구를 다시 실행한다. JSON 이 증거 블록 상한(60줄·8000바이트)을 넘으면 도구 방법을 쓸 수 없다 — `selection: manual` 로 쓰거나 전체 실행으로 간다. 도구가 `전체 실행 필요: 예`(`full_run_required: true`)를 내면 델타 증거를 쓰지 않는다. 눈으로 골랐으면 `selection: manual`. 나열한 테스트를 실행한 명령을 `[axis:test]` 블록의 `command:` 로 싣는다 — 도구로 골랐으면 JSON 의 `command` 와 같아야 한다. `jest`·`vitest`·`testmon` 은 도구가 실행 시 대상을 정하므로 `targeted_tests:` 에 `* -> \u003C선택 방법> 선택` 한 줄로 적는다.\n4. **기준 실행 기록** — 기준 시점의 전체 스위트 실행을 axis 토큰 없는 `[EVIDENCE]` 블록으로 싣는다(`claim:` 예: `기준 트리 \u003Cid> 에서 전체 스위트 실행`, output 은 요약 몇 줄). 이 블록은 `exit_code: 0` 이어야 한다 — 실패한 실행은 기준이 아니다. 새 세션의 리뷰어는 이전 요청서를 볼 수 없으므로 매 회차 다시 싣는다. `selection: testmon` 이면 기준 실행 블록의 `command:` 에 `--testmon-noselect` 가 있어야 한다(선택 없이 전체를 돌리며 testmon 기록을 만든 실행 — 그 기록이 이후 회차의 선택 근거다).\n5. **외부 입력** — 테스트가 읽는 저장소 밖 입력과 gitignore 대상 입력(실데이터 디렉터리, 환경 변수, 인터프리터·의존성 설치 상태)이 기준 실행 이후 바뀌지 않았음을 `external_inputs: unchanged` 로 선언한다. 바뀌었거나 알 수 없으면 델타 증거를 쓰지 않고 전체 재실행으로 돌아간다.\n\n형식:\n\n```\ndelta_evidence:\n  full_run_tree: \u003Cgit stash create 출력 — 비어 있으면 git rev-parse HEAD>\n  untracked_scope: \u003C미추적 비교 범위 glob 목록 — 예: src\u002F** tests\u002F**, 생략하면 전체>\n  untracked_at_full_run:\n    \u003C경로> \u003Csha256> \u003C줄 수>   # 전체 스위트 직후, untracked_scope 로 거른 미추적 파일마다 한 줄, 없으면 none\n  delta:\n    \u003Cgit diff --name-status \u003Cfull_run_tree> 출력 그대로>\n    ?? \u003C경로> \u003Cadded|modified|deleted>   # 스냅샷과 지금 상태의 비교\n  delta_lines: \u003Cgit diff --numstat \u003Cfull_run_tree> 추가+삭제 합 + 미추적 added\u002Fmodified\u002Fdeleted 줄 수>\n  delta_source_files: \u003C델타 중 소스 파일 수 — 테스트·문서 제외>\n  targeted_tests:\n    \u003C델타 파일> -> \u003C테스트 파일>, \u003C테스트 파일>\n  selection: \u003Cproject-script|jest|vitest|testmon|import-search|manual>   # 생략하면 manual\n  external_inputs: unchanged\n```\n\n칸이 하나라도 비거나 산식이 맞지 않으면 델타 증거가 아니다 — 리뷰어는 Medium \"델타 증거 불완전 — 전체 실행 후 재요청\" 으로 돌려주므로 전체 재실행으로 돌아간다. 이어서 `[EVIDENCE]` 블록 두 개 — 기준 실행 기록(axis 토큰 없음, `exit_code: 0`) 1개, 대상 테스트 실행(`claim:` 에 `[axis:test]`) 1개 — 와, 선택 도구를 썼으면 도구 실행 블록(axis 토큰 없음, output 은 JSON) 1개, 그리고 `[axis:typecheck]` 블록과 `diff_self_review:` 라인을 싣는다. \"대상 테스트 통과\" 같은 문장을 블록 밖에 쓰지 않는다 — 검증명사와 통과어가 함께 있는 라인은 블록이 있어도 사전검사가 거부한다(§4.1 reject-tier). 통과 사실은 블록의 `claim:` 에만 쓴다.\n\n**전체 테스트 재실행으로 돌아가는 조건** — 델타가 다음 중 하나라도 포함하면 델타 증거를 쓰지 말고 전체 스위트를 다시 실행해 그 결과를 `[axis:test]` 블록으로 싣는다(새 기준 트리 식별자도 이때 만든다):\n\n- 의존성·빌드 설정 파일 변경 — lockfile, `pyproject.toml`, `package.json`, `setup.*` 류. 작성자가 아는 의존성 설치 상태·런타임 버전 변경도 여기에 든다.\n- 테스트 공용 설정·픽스처 변경 — `conftest.py`, `tests\u002Ffixtures\u002F**`, `tests\u002Fhelpers\u002F**` 류.\n- 둘 이상의 테스트 파일이 import 하는 공용 모듈 변경 — `selection:` 이 `import-search` 이거나 `manual`(칸 없음 포함)일 때 적용한다. 도구 보증 방법(`project-script`·`jest`·`vitest`·`testmon`)으로 골랐으면 적용하지 않는다 — 대상의 완전성은 프로젝트 스크립트(프로젝트가 책임, rein 은 검증하지 않는다) 또는 도구(jest·vitest 의 import 그래프, testmon 의 실행 기록)가 보증하며, jest·vitest 를 쓸 때는 선택 도구가 저장소의 동적 import 흔적 파일과 그 파일에 import 로 닿는 테스트를 실행 명령에 더한다.\n- 소스 파일(테스트·문서 제외) 5개 초과 변경(`delta_source_files` > 5), 또는 델타 전체의 변경 200줄 초과(`delta_lines` > 200).\n- 소스 파일 삭제 또는 이름 변경(`--name-status` 의 `D`·`R`).\n- 테스트가 읽는 외부 입력(저장소 밖·gitignore 대상 데이터, 환경 변수, 인터프리터·의존성 설치 상태)이 기준 이후 바뀌었거나 알 수 없음(`external_inputs` 가 `unchanged` 가 아님).\n- 델타 증거의 칸이 하나라도 비었거나, 기준 실행 블록이 `exit_code: 0` 이 아니거나, 기준 커밋이 더 이상 없음(`git cat-file -e` 실패).\n- 대상 테스트 선택 도구가 `전체 실행 필요: 예` 를 냈음.\n\n이 숫자(5개·200줄)는 측정으로 정한 값이 아니라 운영 가설이다 — 놓친 결함이 드러나면 이 절에서 조정한다. \"전체 검토로 복귀하는 조건\"(아래)은 리뷰 범위의 조건이고, 이 절은 테스트 실행 범위의 조건이다 — 둘은 따로 판정한다.\n\n**전체 스위트 실행 횟수** — 리뷰 회차마다가 아니다. 기준 실행 1회, 그리고 최종 트리가 기준 트리와 다르면(기준 이후 코드가 바뀌었으면) 커밋 직전에 최종 트리에서 1회 더 — 복귀 조건이 한 번도 걸리지 않으면 사이클당 1~2회다. 복귀 조건이 걸릴 때마다 전체 실행 1회와 새 기준 트리가 추가되므로 상한은 \"1~2회 + 복귀 횟수\" 이지 고정된 숫자가 아니다. 최종 트리가 기준과 같으면 기준 실행이 곧 커밋 전 실행이라 추가 실행이 없다. 커밋 전 실행에서 실패해 코드를 고치면 그 수정은 리뷰를 받지 않은 변경이므로 종전대로 재리뷰 대상이다.\n\n**전체 검토로 복귀하는 조건** — 하나라도 해당하면 이 양식을 쓰지 말고 초회와 같은 전체 검토를 요청한다: 새 인터페이스(호출 규약·출력 형식·파일 위치) 추가, 보안 경계(아래 (b) 의 범주 — 한정 열거 아님) 변경, 작업 범위(DoD·Scope 항목) 변경, 직전 검토 이후 변경이 검토 대상의 대부분을 차지.\n\n**함께 지킬 운영 규율**:\n\n- 작업 기준서(DoD)의 범위를 **검토 단위에 맞춘다**. 기준서가 아직 구현하지 않은 범위 항목까지 가리키면 매 회차 그 항목들이 다시 평가된다.\n- 문서 리뷰에서 \"구현 단계 이관\" 참고사항과 오류 경로·상태 수명 지적을 **문서 본문의 계약으로 편입하지 않는다**(§6.7). 편입하면 다음 회차에 \"문서 내부 충돌\" 로 되돌아오고, 그 지적은 판정에 반영된다.\n- 후속 회차의 강도는 **조건부 기본값**이다 — 아래 셋이 모두 맞으면 `[EFFORT:medium]` 으로 부른다(§6.1): (a) 직전 독립 검토 보고에 High 가 없고 필수 검사 축이 수행됐다, (b) 변경이 직전 지적 처리에 한정되고 새 인터페이스·보안 경계·범위 변경이 없다 — 보안 경계는 보안 규칙이 다루는 범주 전부다(입력 검증·경로·권한·비밀 취급·인증·세션·TLS·인증서·암호화·역직렬화·코드 실행·외부 신뢰 경계 등, 한정 열거가 아니다) 또는 §6.3 floor 경로, (c) 새 판단이 필요한 변경이 없다. \"High 를 고쳤다\" 는 (a) 충족이 아니다 — 직전 보고에 High 가 있었으면 그 회차는 대상 밖이다. 셋 중 하나라도 어긋나면 medium 을 쓰지 않는다: 직전 보고에 High 가 있었거나(그 High 를 고쳤더라도 — 그 수정은 아직 독립 검토를 받지 않았다) (b)·(c) 에 어긋나면 §6.3 재승급 규칙대로 `[EFFORT:high]` 를 넣고, 직전 보고가 필수 축을 수행하지 않았으면 그 검토는 완결이 아니므로 마커 없이 전체 검토와 산출 강도(§6.3, 위험 경로 floor 포함)로 복귀한다. 이 기본값은 medium 이 놓치는 결함 폭을 재지 않은 운영 가설이며, 이후 리뷰·CI·버그에서 드러난 누락과 대조한다.\n- 회차 상한(§10)과 별개로, 누적 대기가 길어지면 재호출 대신 사람에게 **남은 위험 수용 \u002F 범위 축소 \u002F 중단** 중 결정을 요청한다. 시간이 많이 들었다는 사실은 통과 사유가 아니다.\n\n**한계**: 래퍼는 후속 회차에도 검사 축 전부를 응답에 요구한다. 이 양식은 범위 축소를 **보장하지 않는다**. 효과를 볼 때는 소요 시간만이 아니라 놓친 결함도 함께 본다 — `trail\u002Freview-events\u002F` 의 소요는 판정으로 끝난 호출만 담으므로 총 대기 지표가 아니다.\n\n### Mode 별 기록 규칙 (CRITICAL)\n\n- **Code review mode**: PASS 시 `bin\u002Frein issue-evidence code_review --verdict PASS --reviewed-digest \u003CD>` 로 v2 증거 발급 (commit gate 통과용). digest 는 리뷰 대상 diff 범위(`HEAD~1` 체인 기반)에서 산정한다 (Phase 7 웨이브 3 ③-d — 이전에는 stamp 의 `diff_base:` 필드에서 읽었지만, stamp 자체가 제거되면서 매 실행 시 직접 재산정으로 전환됐다, GI-codex-review-diff-base 후속).\n- **Spec review mode (plan 또는 design)**: code_review v2 증거를 **절대 발급 안 함**. legacy marker 는 이미 존재하지 않으므로 건드릴 대상도 없음. verdict 만 stdout 으로 방출. caller 가 `bash \"${CLAUDE_PLUGIN_ROOT:-$PWD}\u002Fscripts\u002Frein-mark-spec-reviewed.sh\" \u003Cpath> \u003Creviewer>` 를 별도로 호출해 `trail\u002Fdod\u002F.spec-reviews\u002F*.reviewed` 를 생성할 책임 (spec-review 축은 이번 웨이브의 존속 예외).\n- Rationale: code_review v2 증거는 code commit gate 전용. spec review 가 이를 발급하면 코드 변경 없이도 gate 통과 → rein 규율 붕괴.\n\n### Legacy interactive model selection\n\n아래는 wrapper 가 없던 시기의 manual invocation 절차로, 현재는 wrapper 가 담당한다. 참고용으로 남긴다.\n\n1. 모델은 단일 출처(`plugins\u002Frein-core\u002Fconfig\u002Fcodex-models.sh` 의 `CODE_GATE_MODEL` — 게이트 고정 모델, 프로필 구조)가 결정한다. 래퍼가 이 값을 `-m` 으로 전달하므로 수동 모델 선택은 불필요하다 — 모델 rename 시 그 파일만 수정한다. (legacy alias `CODE_MODEL` 은 신값을 노출한다.)\n2. Ask the user (via `AskUserQuestion`) which reasoning effort to use: `low`, `medium`, or `high`.\n3. Select the sandbox mode required for the task; default to `--sandbox read-only` unless edits or network access are necessary.\n4. Assemble the command with the appropriate options:\n   - `-m, --model \u003CMODEL>`\n   - `--config model_reasoning_effort=\"\u003Clow|medium|high>\"`\n   - `--sandbox \u003Cread-only|workspace-write|danger-full-access>`\n   - `--full-auto`\n   - `-C, --cd \u003CDIR>`\n   - `--skip-git-repo-check`\n5. Run the command, capture stdout\u002Fstderr, and summarize the outcome for the user.\n6. After every `codex` command, immediately use `AskUserQuestion` to confirm next steps, collect clarifications, or decide whether to resume the same review cycle (§7).\n\n### Quick Reference\n\n| Use case                               | Sandbox mode      | Key flags                                |\n| -------------------------------------- | ----------------- | ---------------------------------------- |\n| 코드 리뷰 (초회)                       | `read-only`       | 래퍼가 `--sandbox read-only` 를 항상 전달 (§6.6) |\n| 코드 리뷰 (fix 후 재리뷰, 같은 사이클) | `read-only`       | 래퍼 재호출(새 세션) + 후속 회차 요청서 양식 (§2) |\n| 로컬 편집 (수동 직접 호출, 래퍼 아님)  | `workspace-write` | `--sandbox workspace-write --full-auto`  |\n| 다른 디렉토리에서 실행                 | task needs        | `-C \u003CDIR>` 추가                          |\n\n### 실행 모드 — Bash 도구로 호출 시 foreground 전용\n\n`plugins\u002Frein-core\u002Frules\u002Fbackground-jobs.md` 예외 절에 따라, 어떤 경로로 codex 를 호출하든 Bash 도구에서는 foreground 실행이 강제된다. Bash 도구의 auto-background 전환이 stdin 을 unix socket 으로 붙이면 codex sandbox\u002Fauth 초기화가 hang 한다 (증상: 네트워크 연결 0개, CPU 0, `~\u002F.codex\u002Fsessions\u002F` 미갱신).\n\n**공통 규칙** (wrapper \u002F direct 모두):\n\n- Bash 도구 호출 시 `run_in_background: false` 필수\n- **시간상한은 래퍼가 코드로 강제한다** (문서 규약 아님): 1차 상한 `low 120s` \u002F `medium 180s` \u002F `high 300s` (spec-review 동일). 1차 상한 도달 후 30초 창 단위로 생존 검진하며, 신호는 **3축**이다 (2026-07-27~):\n  - **edge**(창 사이 실제 변화: 결합 스풀 성장 **또는** codex 자손 PID 집합 변동) → **무기한 유예** (절대 상한 없음)\n  - **level**(상태 유지·변화 없음: 미완료 자식 명령 표식 **또는** 자손 존재) → **유한 lease**(기본 20창) 안에서만 유예\n  - 위 어느 것도 아닌 창이 **연속 6창(180s)** 이면 TERM → 10s grace → KILL → reap 후 **exit 5** 로 종료 (판별 계약 + 호출자 행동: §4.2, 하니스 상호작용: §4.3)\n  - 배경: codex 는 자식 명령 실행 **중 출력을 내지 않아**, 리뷰어가 테스트를 돌리는 정상 구간이 과거 '무성장 단일 축' 판정으로 오살됐다 (2026-07-27 실측·수리)\n- 워치독 override env **5종** `REIN_WATCHDOG_{CAP,INTERVAL,GRACE,STALL_WINDOWS,LEVEL_LEASE}_OVERRIDE` 는 **테스트 전용**이다 — 운영 호출에서 설정 금지\n- 600s harness limit 을 넘어가는 리뷰는 기존대로 prompt 쪼개기 (§4.3)\n\n**Wrapper 경로** — `bash \"${CLAUDE_PLUGIN_ROOT:-$PWD}\u002Fscripts\u002Frein-codex-review.sh\" [--non-interactive] \u003C \u002Ftmp\u002F\u003Cprompt>.txt`:\n\n- wrapper 는 stdin 으로 prompt 를 받는다. **항상 file redirect (`\u003C \u002Ftmp\u002F\u003Cfile>`)** 사용 — pipe (`printf '...' | bash wrapper.sh`) 는 pre-bash-safety-guard 가 차단 (stdin 명령 source 검증 불가)\n- prompt 작성: Write 도구로 `\u002Ftmp\u002Fcodex-prompt.txt` 등에 작성 후 redirect\n- 출력은 wrapper 가 PASS 시 v2 발급 + stdout 요약을 동기 반환. `> \u002Ftmp\u002F\u003Coutput>.log 2>&1` 형태로 직접 파일에 쓰고 Read 로 확인. `| tail -N` 추가 pipe 는 불필요\n- v2 발급은 **foreground 동기 실행** 가정 위에서 동작. background 전환 시 발급이 누락된 채 프로세스만 죽을 수 있음 — legacy stamp 안전망이 없으므로(③-d), 이 경우 \"리뷰는 됐는데 기록이 없다\"가 곧 \"리뷰가 안 된 것\"과 동일하게 취급된다 — 재실행 필요\n\n**Direct codex exec 경로** (§2 \"Legacy interactive model selection\" 의 수동 조립):\n\n- stdin `\u003C \u002Fdev\u002Fnull` 로 명시 close\n- 출력은 `> \u003Cfile> 2>&1` 로 직접 파일에 쓰고 `Read` 로 읽기. `| tail -N` 금지 (EOF 까지 버퍼링)\n- codex 실행 후 v2 발급은 수동으로 caller 가 `bin\u002Frein issue-evidence code_review --verdict \u003CV> --reviewed-digest \u003CD>` 를 직접 호출한다 (`review_round` 는 §10 카운터가 담당하며 발급 인자에 포함되지 않는다)\n\nHang 감지 + 복구: §8 Error Handling 참고.\n\n---\n\n## 3. Escalation 규칙\n\n리뷰 결과 severity + 수정 규모에 따라 다음 단계를 결정한다:\n\n| 리뷰 결과      | 수정 규모 | 다음 행동                                                          |\n| -------------- | --------- | ------------------------------------------------------------------ |\n| High 이슈 있음 | 무관      | 수정 후 **codex 재리뷰** (Round 증가)                              |\n| Medium만 있음  | > 3줄     | 수정 후 **codex 재리뷰** (Round 증가)                              |\n| Medium만 있음  | ≤ 3줄     | 수정 후 **sonnet 셀프리뷰**                                        |\n| Low만 있음     | 무관      | 수정 후 **sonnet 셀프리뷰**                                        |\n| 이슈 없음      | —         | **통과** — v2 code_review 증거 발급 (§5)                            |\n| 3회차에도 High | —         | **사람에게 에스컬레이션** — 기록 없음 (PASS 가 아니므로 발급 대상 아님) |\n\n**문서 지적의 차단 기준** (래퍼가 두 봉투 — 문서 봉투의 판정 규율 (iv), 코드 봉투의 Claim Audit — 에 같은 문장으로 방출한다): 수정 필요(NEEDS-FIX)로 이어지는 지적은 결정·범위·사실의 오류여야 하고, 무엇을 잘못 전달하는지와 그 근거가 지적에 있어야 한다. 같은 뜻의 표현 선택·배치·명칭 취향은 \"참고사항\" 으로 분리되며 verdict 를 승격시키지 않는다 — 참고사항은 위 표의 다음 행동을 결정하지 않는다. 실질 결함(잘못된 사용법, 영향 범위 과장, 필수 보안·안전 제약의 누락 — 인증·TLS 검증·안전한 역직렬화 등은 범위의 오류이며 \"구현 단계 이관\" 참고사항으로 돌리지 않는다 —, 완료 주장, 수치 불일치, 누락된 추적 연결)은 계속 차단이다. Scope ID 의 측정 가능성도 문자열 형식이 아니라 Scope Items 표의 설명·검증 방법까지 읽어 판정한다 — 세 요소(대상·방향·상황)가 설명에 있으면 문자열만으로 플래그하지 않고, 설명에도 없을 때만 Medium 이다.\n\n**sonnet 셀프리뷰**: 변경 diff 를 직접 확인하고, PASS 판정이면 §5 발급을 그대로 수행한다 (발급 자체에는 reviewer 구분 필드가 없다 — 사람 보고 문구에서만 \"self-review\" 로 구분한다).\n\nRound 관리: 회차는 래퍼가 사이클 단위로 계수하며 상한을 **코드로 강제**한다 (§10). 같은 사이클 내 재리뷰도 래퍼는 새 세션으로 실행한다 — 이전 검토의 맥락은 호출자가 후속 회차 요청서 양식(§2)으로 전달한다 (§7).\n\n> 2026-08-04 정정: 이전 판은 \"stamp 의 `review_round` 필드에 회차를 기록한다\" 고 서술했으나 **그 필드는 존재한 적이 없다**. 래퍼에 회차 개념 자체가 없었고, 상한은 산문 지시로만 존재해 두 번 무력화됐다 (13회차 무보고 연속 실행 \u002F 종결 지시 후 5회차 폭주). §10 이 이를 코드 계약으로 대체한다.\n\n---\n\n## 4. Sonnet Fallback\n\nCodex 실패 (에러\u002F타임아웃) 시 Sonnet 기반 대체 리뷰 경로:\n\n1. **Codex 리뷰 실행** — §2 절차로 `codex exec`\n2. **성공 시** — §5 v2 발급 후 종료\n3. **실패 시 (에러\u002F타임아웃)** — sonnet 폴백 리뷰 실행\n   - `code-reviewer` 스킬 호출, 또는\n   - `general-purpose` 에이전트 (model: sonnet) 로 이관\n4. **폴백 성공 시** — §5 v2 발급을 그대로 수행하고, 사람 보고 문구에서 \"sonnet 폴백, 사유: codex_timeout\" (또는 해당 에러 코드) 처럼 밝힌 뒤 종료 (발급 인자 자체에는 fallback 사유 필드가 없다)\n5. **폴백도 실패 시** — 작업 중단, 사용자에게 보고\n\n**중요**: \"codex 리뷰 결과가 부족해서\" 등은 폴백 사유가 아니다. 에러\u002F타임아웃 같은 **실행 실패** 만 폴백을 허용한다.\n\n**모델 설정 에러(종료 코드 3)는 폴백 대상이 아니다.** 래퍼가 exit 3 으로 종료하면 codex 가 모델명을 거부한 것(upstream rename 등)이다. sonnet 폴백으로 넘기면 잘못된 모델 설정이 가려지므로, 폴백하지 말고 `plugins\u002Frein-core\u002Fconfig\u002Fcodex-models.sh` 의 `CODE_GATE_MODEL` 을 codex 의 최신 모델명으로 갱신하도록 사용자에게 안내한다. 래퍼가 경로와 현재 값을 stderr 로 출력한다.\n\n**review-readiness 거부(종료 코드 4 + 거부 진단행)도 폴백 대상이 아니다.** codex 실행 실패가 아니라 요청서 결함이므로, sonnet 폴백으로 새면 요청서 결함이 가려진다 (exit 3 과 동일한 비대상 원리). 준비도 거부는 codex spawn **이전**에만 발생하며 (비용 0), v2 발급을 시도하지 않고 존속하는 `.spec-reviews\u002F*` 도 건드리지 않는다. 호출자는 stderr 안내대로 요청서를 수정한 뒤 재호출한다 — 문법은 §4.1, 판별 계약과 호출자 행동은 §4.2.\n\n### 4.1 증거 블록 문법 (`[EVIDENCE]`)\n\n리뷰 요청서(래퍼 stdin 의 PROMPT_BODY)에서 정량\u002FPASS 주장의 재현 증거는 다음 고정 형식으로 선언한다. 래퍼의 review-readiness 사전검사가 codex 호출 전에 형식을 결정론적으로 검증한다 (code-review 모드 한정 — spec-review 모드는 비대상, §6.7):\n\n```\n[EVIDENCE]\nclaim: 테스트 21건 GREEN\ncommand: bash tests\u002Fskills\u002Frun-all.sh\nexit_code: 0\noutput:\n...(생략)...\nok 21 - test-codex-model-profile-routing\n21 passed, 0 failed\n[\u002FEVIDENCE]\n```\n\n**문법 규칙 (결정론적 — 위반 = exit 4)**:\n\n0. **fence 상태 전이 (단일 좌→우 스캔)**: fence 토글(``` ```)은 **증거 블록 밖에서만** 유효하다 — 블록 밖에서 fence 열림 상태의 `[EVIDENCE]`\u002F`[\u002FEVIDENCE]` 단독 라인은 블록 경계로도 형식 위반으로도 취급하지 않는다 (문법 예시·diff 인용 보호). 반대로 **증거 블록 안(output 영역 포함)의 ``` 는 일반 출력 텍스트**이며 fence 상태를 토글하지 않는다 — output 에 미폐쇄 ``` 가 있어도 뒤따르는 실제 `[\u002FEVIDENCE]` 가 정상 폐쇄로 인식된다. 정량\u002FPASS 패턴 스캐너는 이 스캔이 남긴 같은 마스킹 결과를 공유한다 (마스킹 1회). 인라인 백틱은 라인 단독 마커가 될 수 없으므로 블록 파서에는 무관 (패턴 스캐너에만 적용).\n1. `[EVIDENCE]` \u002F `[\u002FEVIDENCE]` 는 각각 **한 줄에 단독**으로 위치한다 (선행 공백 허용).\n2. 블록 내부 필드는 **고정 순서** `claim:` → `command:` → `exit_code:` → `output:` 4종, 각 정확히 1회. 순서 위반·누락·중복 = 형식 위반.\n3. `claim:` = 이 블록이 뒷받침하는 자연어 주장 한 줄 (비어있으면 위반). `command:` = 재현 명령 한 줄 (비어있으면 위반).\n4. `exit_code:` = 0–255 정수. 그 외 값 = 위반.\n5. `output:` 라인 **다음 줄부터** `[\u002FEVIDENCE]` 직전까지가 출력 원문이다 — 어떤 escaping 도 불필요 (여러 줄·특수문자 그대로). 단 출력 영역 안에 `[EVIDENCE]` 단독 라인이 나타나면 중첩 금지 위반. 출력 0줄 허용 (명령이 무출력인 경우) — `output:` 라인 자체는 필수.\n6. **출력 상한**: 블록당 output 60줄 이하 **그리고** 8000바이트 이하. **바이트 정의**: `output:` 다음 줄부터 `[\u002FEVIDENCE]` 직전 줄까지 output 영역의 **UTF-8 원시 바이트 수** — 각 줄의 LF 개행 바이트를 포함하고, 문자 수가 아니라 바이트 수다 (한글 등 다중바이트 문자는 인코딩된 바이트만큼 계수). **경계 포함**: 정확히 60줄·정확히 8000바이트는 통과, 61줄 또는 8001바이트부터 위반. 초과 = 형식 위반 (래퍼는 절단·변조하지 않는다 — 작성자가 관련 발췌로 줄인다. stderr 안내: `tail`\u002F`grep` 발췌 권장).\n7. **블록 수 상한**: 요청서당 유효 블록 16개 이하. 초과 = 형식 위반 (envelope 크기 보호).\n8. 다중 주장 = 다중 블록 (주장 1 : 블록 1). 하나의 명령 출력이 여러 주장을 뒷받침하면 블록을 주장별로 나누고 output 발췌를 각각 최소화한다 (규약 — 래퍼는 주장:블록 대응의 의미를 검증하지 않는다).\n\n**검증 깊이 (확정)**: 형식만. 래퍼는 `command:` 를 **재실행하지 않고**, output 과 exit_code 의 진위를 확인하지 않는다. 형식은 갖췄지만 거짓인 증거는 기존대로 codex 가 잡는다 — 유효 블록은 PROMPT_BODY 원문 위치에 그대로 보존되고, envelope 의 `evidence_manifest:` 슬롯(블록별 claim\u002Fcommand\u002Fexit_code 요약)과 Claim Audit sub-item 7 로 codex 에 구조화 전달된다.\n\n코드 리뷰 모드에서 요구되는 자가검증 두 축(`[axis:typecheck]`·`[axis:test]` 블록 + `diff_self_review:`)과 예외 선언은 §2 \"자가검증 증거 — 두 축 계약\", 후속 회차의 테스트 증거 범위는 §2 \"델타 증거\" 를 본다.\n\n**블록 밖 잔존 정량 매칭 — 두 tier 처분 (reject-tier \u002F advisory-tier)**: 형태 3종(Q1 수량+단위 \u002F Q2 비율·백분율 \u002F Q3 검증명사+통과어 공존)과 문맥어 2종(C = 계약 문맥어: 최소\u002F최대\u002F상한\u002F하한\u002F임계\u002F목표\u002F허용\u002F오차\u002F이내\u002F이하\u002F이상\u002Fthreshold\u002Flimit\u002Ftolerance\u002Ftarget\u002Fquorum\u002Fbudget\u002F±, R = 결과 서술어: 발견\u002F실패\u002F오류\u002F누락\u002F남음\u002F잔존\u002F감지\u002F검출\u002F재현\u002Ffail·found·detected·remaining·flagged·missing·reproduced)를 각각 독립 변수로 평가한다 — 한 라인이 여러 형태에 동시에 매칭돼도 어느 하나가 reject 조건이면 reject-tier 다.\n\n- **reject-tier** (증거 블록이 있어도 `[readiness-reject]` 로 exit 4 거부): Q3(예: \"테스트 통과 확인\", \"build passed\") · Q2 중 C 가 없거나 R 이 있는 것(예: \"커버리지 85%\", \"상한 20% 초과로 3\u002F5 실패\") · Q1 중 R 이 있고 C 가 없는 것(예: \"실패 3건 발견\", \"2 files flagged\").\n- **advisory-tier** (`WARNING: [codex-review][readiness-advisory]` 비차단 경고 + envelope `unbacked_quant_flags:` 슬롯): Q1 중 R 이 없거나 C 가 있는 것(예: \"함수 50줄 이내\", \"최대 재시도 3회\", \"실패 상한 3회\") · Q2 중 C 가 있고 R 이 없는 것(예: \"오차 상한 ±20%\", \"완화 목표 2%\", \"quorum 2\u002F3 이상\").\n- **동률 규칙**: C 와 R 이 한 라인에 같이 있으면 형태의 기본 처분을 유지한다 — Q2(비율·백분율)는 reject 기본, Q1(수량+단위)은 advisory 기본. 문맥어는 신호가 한쪽뿐일 때만 기본 처분을 뒤집는다.\n\n### 4.2 exit code 4 \u002F 5 \u002F 6 — 판별 계약 + 호출자 행동 (준비도 거부 \u002F 래퍼 timeout \u002F 회차 예산 소진)\n\n**호출자 행동 표**:\n\n| exit | 의미 | 호출자 행동 |\n|---|---|---|\n| 4 (+거부 진단행 — `ERROR: [codex-review][readiness-reject]` 로 시작하는 stderr 라인) | review-readiness 거부 — 블록 0개 + 정량\u002FPASS 매칭, **또는 블록 ≥1 이어도 블록 밖 reject-tier 매칭**(§4.1) — codex 미호출 (비용 0) 프로젝트 사전 검사(`.rein\u002Freview-precheck.sh`, §2 \"리뷰 전 프로젝트 사전 검사\")의 실패·시간 초과·실행 불가·거부도 같은 접두로 이 행에 속한다. | stderr 안내대로 요청서를 수정(증거 블록 추가, 계약 형태로 고쳐 쓰기, 또는 형식 교정)해 **재호출**. Sonnet fallback 비대상 — codex 실행 실패가 아니라 요청서 결함이므로 fallback 으로 새면 결함이 가려진다 (exit 3 과 동일한 비대상 원리). 재리뷰 카운트(§3 escalation)에 포함하지 않는다 — 리뷰가 수행되지 않았다. |\n| 4 (거부 진단행 0 — advisory 경고·발췌 내용 무관) | codex 실행 실패 passthrough (드묾 — `CODEX_RC` passthrough 와의 이론적 겹침) | 기존 실행 실패 경로와 동일 — Sonnet fallback 후보 (§4 본문). |\n| 5 (+진단행 — `ERROR: [codex-review][review-timeout]` 로 **시작하는** stderr 라인 ≥1) | 래퍼 소유 timeout (정지 판정 종료 — verdict 없음, v2 발급 미시도) | **즉시 대체 리뷰** (Sonnet fallback, 사유 `codex_timeout` 재사용). 재시도·재호출 없음. 재리뷰 카운트(§3 escalation) 비포함 — 리뷰가 완료되지 않았다. |\n| 5 (진단행 0) | codex 자체 exit 5 passthrough | 기존 실행 실패 처리 (Sonnet fallback 후보) 그대로. |\n| 6 (+진단행 — `ERROR: [codex-review][round-budget-exceeded]`) | 이 사이클의 리뷰 회차 예산 소진 — codex 미호출 (비용 0), v2 발급 미시도 (§10) | **재호출 금지.** 지금까지의 지적과 남은 쟁점을 요약해 **사람에게 넘긴다**. Sonnet fallback 비대상 — codex 실행 실패가 아니라 예산 소진이며, 폴백으로 새면 상한이 무의미해진다. 예산이 더 필요하다고 판단하면 사람의 승인을 받은 뒤에만 `[MAX_ROUNDS:\u003Cn>]` 을 선언해 재호출한다 (선언은 기록에 남는다). |\n\n**호출자 판별 계약 (기계 계약 — 사람 판독 아님)**:\n\n```\n거부 진단행    := 정확히 \"ERROR: [codex-review][readiness-reject]\" 로 시작하는 stderr 라인 (라인 시작 anchored — substring 검색 금지)\n준비도 거부    := exit == 4 AND 거부 진단행 ≥ 1\ncodex 실행실패 := exit == 4 AND 거부 진단행 0  → 기존 실행 실패 처리 (Sonnet fallback 후보) 그대로\n\n앵커행        := 정확히 \"ERROR: [codex-review][review-timeout]\" 로 시작하는 stderr 라인 (라인 시작 anchored — substring 검색 금지)\n래퍼 timeout  := exit == 5 AND 앵커행 ≥ 1  → 즉시 대체 리뷰 (Sonnet fallback, fallback_reason: codex_timeout). 재시도·재호출 없음. 재리뷰 카운트(§3 escalation) 비포함 — 리뷰가 완료되지 않았다\ncodex passthrough := exit == 5 AND 앵커행 0 → 기존 실행 실패 처리 (Sonnet fallback 후보) 그대로\n\n예산 진단행    := 정확히 \"ERROR: [codex-review][round-budget-exceeded]\" 로 시작하는 stderr 라인 (라인 시작 anchored — substring 검색 금지)\n예산 소진      := exit == 6 AND 예산 진단행 ≥ 1  → 재호출 금지, 사람에게 핸드오프. Sonnet fallback 비대상. 리뷰가 수행되지 않았으므로 회차도 소모되지 않는다\ncodex passthrough := exit == 6 AND 예산 진단행 0 → 기존 실행 실패 처리 (Sonnet fallback 후보) 그대로\n\n카운터 진단행  := 정확히 \"ERROR: [codex-review][round-budget-unavailable]\" 로 시작하는 stderr 라인 (라인 시작 anchored)\n카운터 불가    := exit == 7 AND 카운터 진단행 ≥ 1  → **예산 소진이 아니다.** 카운터 디렉토리 쓰기 불가 또는 count 손상으로 예산을 강제할 수 없는 상태다. 사람 핸드오프가 아니라 **인프라 복구**(권한 수정 \u002F 손상 파일 제거) 후 재호출. Sonnet fallback 비대상 — 예산을 강제할 수 없는 채로 리뷰를 통과시키면 안 된다\n```\n\nsubstring 이 아니라 **라인 시작 anchored 접두사**로 판별하는 이유: 발췌·기존 진단 등 다른 stderr 내용에 예약 문자열이 섞여도(래퍼의 발췌 소독 — 발췌 내 예약 태그를 `[readiness-…]` 로 치환 — 이 1차 차단, 접두사 anchoring 이 2차 방어) 판별이 오염되지 않는다. advisory 는 별도 접두사 `WARNING: [codex-review][readiness-advisory]` (비차단) 이므로, advisory 경고가 방출된 뒤 codex 자신이 exit 4 를 반환하는 조합에서도 stderr 에 거부 진단행이 없어 호출자가 passthrough 로 정확히 분류한다.\n\nexit 5 앵커도 동일 원리로 **라인 시작 anchored — substring 검색 금지**: 래퍼가 스풀 내용을 방출하는 모든 경로에서 방출 전에 라인 시작의 예약 접두사 `ERROR: [codex-review][review-timeout]` 을 `ERROR: [codex-review][review-…]` 로 소독하므로, 결합 로그(`> log 2>&1`)에서 라인 앵커 매치는 래퍼 소유 stderr 라인뿐이다 (codex 자체 출력의 리터럴 오염으로 인한 오분류 배제).\n\n### 4.3 하니스 600s 상호작용 (워치독 생존 계약)\n\n- 워치독은 **래퍼 프로세스 내부** 메커니즘 — 하니스(Claude Code Bash)가 래퍼 프로세스를 살려두는 동안만 유효하다. 하니스가 Bash timeout 으로 래퍼를 죽이거나 process group 을 정리하면 워치독 생존은 **비보장** — 이 경우 exit 5 계약은 발화하지 않고, 호출자는 하니스 timeout 을 기존 실행 실패로 처리한다 (§4 본문).\n- **권장 호출 패턴**: `run_in_background: false` + Bash `timeout: 600000`(max) + 출력 직접 파일 리다이렉트 (`> \u002Ftmp\u002F\u003Coutput>.log 2>&1` — background-jobs.md 형식) 유지.\n- 절대 상한이 없으므로 정당한 장기 리뷰는 600s 를 넘을 수 있다. 600s 를 상회하는 리뷰가 하니스에 잘리는 것은 기존 \"600s 한계 넘으면 prompt 쪼개기\" 규약의 영역 — 워치독이 해소하는 것은 \"행 방치\" 이지 \"하니스 상한\" 이 아니다.\n\n---\n\n## 5. 리뷰 결과 기록 — v2 발급 (필수, PASS 회차 한정)\n\n**Phase 7 웨이브 3 ③-d (2026-08-24) 갱신.** 이전에는 이 절이 `trail\u002Fdod\u002F.codex-reviewed` legacy stamp 를 생성하는 절차였다. ③-d 로 legacy 리뷰 표식 3종(`.codex-reviewed`\u002F`.review-pending`\u002F`.security-reviewed`)의 write·read 경로가 전부 제거되면서, PASS 판정 회차에서 `bin\u002Frein issue-evidence code_review --verdict PASS --reviewed-digest \u003CD>` 를 호출하는 것이 **유일한 기록 절차**가 됐다. 이 호출이 없으면(또는 exit 0 이 아니면) `pre-bash-commit-review-gate.sh`(구 `pre-bash-test-commit-gate.sh` 후속, ③-c 삭제 완료)가 커밋을 차단한다 — 더 이상 대신 참조할 legacy stamp 가 없다.\n\n**Plan A Phase 6 이후 \u002F ③-a~③-d 계승**: `scripts\u002Frein-codex-review.sh` wrapper 가 code-review mode PASS 시 이 발급을 자동 호출한다. Sonnet 셀프리뷰 \u002F 수동 경로에서도 caller 가 동일한 발급 호출을 직접 수행한다.\n\n### 5.1 발급 인자\n\nv2 발급은 `--verdict`와 `--reviewed-digest` 두 인자만 받는다 (`plugins\u002Frein-core\u002Frein\u002Fcli\u002Fissue_evidence.py::issue()`). 과거 legacy stamp 가 담던 부가 정보(`reviewer`\u002F`cycle`\u002F`scope`\u002F`files_reviewed`\u002F`fallback_reason`\u002F`resolution`\u002F`remaining_issues`\u002F실행 증빙 5필드 `model`\u002F`effort`\u002F`effort_source`\u002F`policy_version`\u002F`codex_version`)는 **더 이상 durable 하게 저장되지 않는다** — 이 정보가 필요하면 §9 \"사용자 안내\" 문구에 그대로 담아 그 회차의 채팅 응답에만 남긴다. `reviewed-digest` 는 §2 \"Mode 별 기록 규칙\"이 서술한 대로 diff 범위(`HEAD~1` 체인 기반)로 매 실행 직접 산정한다.\n\n- `reviewer` 구분(`codex` \u002F `sonnet-fallback` \u002F `self-review`)은 §9 사용자 안내 문구로만 남는다.\n- `review_round`(같은 사이클 내 N번째 리뷰)는 발급 인자가 아니다 — 회차 계수·상한 강제는 언제나 §10 의 카운터가 전담한다 (legacy stamp 시절에도 래퍼 발급 stamp 에는 이 필드가 없었다).\n\n### 5.2 정상 통과 (codex)\n\n```bash\n\"${CLAUDE_PLUGIN_ROOT:-$PWD\u002Fplugins\u002Frein-core}\u002Fbin\u002Frein\" issue-evidence code_review \\\n  --verdict PASS --reviewed-digest \"$REIN_REVIEWED_DIGEST\"\n```\n\n성공(exit 0) 시 사람 보고에는 \"codex 리뷰 통과, 기록됨\"을 담는다 (§9).\n\n### 5.3 Sonnet Fallback\n\n발급 호출 자체는 5.2 와 동일하다 — `reviewer`\u002F`fallback_reason` 을 구분할 인자가 없으므로, §9 사용자 안내 문구에서 \"codex 실행 실패로 sonnet 폴백 리뷰로 통과, 사유: codex_timeout(또는 해당 코드)\" 를 명시한다.\n\n### 5.4 Sonnet 셀프리뷰 (Low\u002F경미한 Medium)\n\n발급 호출은 5.2 와 동일하다. §9 사용자 안내 문구에서 \"직전 codex 리뷰의 Low\u002FMedium(≤3줄) 지적을 수정한 뒤 self-review 로 통과\" 를 명시한다.\n\n### 5.5 사람 에스컬레이션 (기록 없음)\n\n3라운드 후에도 High 가 남아 있으면 **발급을 시도하지 않는다** — PASS 가 아니므로 대상이 아니다. 잔존 이슈는 §9 사용자 안내 형식으로 사람에게 직접 보고한다.\n\n### 5.6 발급 실패\u002F거부 처리\n\n발급이 exit 0 이 아니면(digest-mismatch 거부, 인프라 실패 등) 이 회차는 **기록되지 않는다** — legacy stamp 안전망이 없으므로, code-reviewer SKILL(`plugins\u002Frein-core\u002Fskills\u002Fcode-reviewer\u002FSKILL.md`)의 \"PASS 시 v2 발급\" 절과 동일한 fail-closed 판정을 따른다: digest-mismatch(또는 응답을 긍정적으로 그 외 사유로 파싱할 수 없는 경우)는 리뷰 대상 트리가 회차 도중 바뀌었다는 뜻이므로 digest 를 재캡처해 재리뷰해야 하고, 그 외 실패는 인프라 복구 후 재발급을 시도한다.\n\n---\n\n## 6. Non-interactive mode\n\n자동 호출 (예: plan-writer agent 가 spec review 트리거) 시 사용. Prompt 에 marker 가 있으면 사용자 질문(AskUserQuestion) 을 skip 하고 default 로 codex exec 실행.\n\n### 6.1 Invocation contract\n\nprompt 본문 어디든 다음 marker 가 있으면 non-interactive mode 활성:\n\n- `[NON_INTERACTIVE]` — AskUserQuestion 호출 skip\n- `[MODEL:\u003Cname>]` — model override (기본값은 단일 출처 `plugins\u002Frein-core\u002Fconfig\u002Fcodex-models.sh` 의 `CODE_GATE_MODEL` — 게이트 고정 모델)\n  - 허용 값: codex 가 지원하는 임의 모델명. 하드코딩 화이트리스트를 두지 않는다 (모델 rename 시 문서 수정 불필요).\n- `[EFFORT:\u003Clevel>]` — reasoning effort override (마커 부재 시 래퍼가 변경 규모로 산출 + 위험도 floor 적용; 측정 실패 시 fail-closed 페어의 `CODE_FAIL_CLOSED_EFFORT`(=`high`))\n  - 허용 값: `low`, `medium`, `high`\n  - **거부 값 (각자 사유 + 산출 진입)**:\n    - `ultra` — 게이트에서 **명시 거부**. ultra 는 자동 하위작업 위임(auto subagents)으로 동작해 게이트 판정이 비결정적이 되고 quota\u002Ftimeout 충돌을 일으킨다. stderr 에 ultra 전용 사유 메시지 출력.\n    - `max` \u002F `xhigh` — timeout 실측 전 **미지원** (후속 재검토 대상). stderr 에 각자 사유 메시지 출력.\n    - 셋 다 거부 후에는 마커 없음과 동일하게 **변경 규모 산출 경로로 진입**하며(floor 포함), 마커는 strip 되어 codex 프롬프트\u002F인자 어디로도 전달되지 않는다. 기타 무효값(`low2` 등)은 기존 generic warning 유지.\n- `[SANDBOX:\u003Cmode>]` — 래퍼는 이 마커를 읽지 않는다. 샌드박스는 항상 `read-only` 다 (§6.6).\n\n### 6.2 Marker 처리 절차\n\n1. prompt 에서 marker 패턴 (`\\[NON_INTERACTIVE\\]`, `\\[MODEL:[^\\]]+\\]` 등) 추출\n2. 추출 후 prompt 본문에서 marker 제거 (codex exec 로 전달 안 함)\n3. 추출된 값 또는 default 로 codex exec 명령 조립\n4. 실행 후 결과 캡처\n\n**구현 상태** (v1.1.2~):\n\n- `[EFFORT:\u003Clevel>]` — ✅ wrapper (`scripts\u002Frein-codex-review.sh`) 에 구현됨. 유효 값은 `low|medium|high`. `ultra`\u002F`max`\u002F`xhigh` 는 각자 전용 사유의 stderr 메시지(ultra=자동위임 게이트 금지, max\u002Fxhigh=timeout 실측 전 미지원) 후 산출 진입, 기타 무효값은 기존 generic warning + **변경 규모 기반 산출 진입**(`_compute_effort`). marker 는 유효성과 무관하게 codex 로 전달되는 prompt 에서 제거.\n- `[NON_INTERACTIVE]` — ✅ wrapper 의 `--non-interactive` CLI 플래그로 처리. marker 자체는 prompt 에 보존 (spec-review 모드 감지에 사용).\n- `[MODEL:\u003Cname>]` — ⏳ marker override 자체는 wrapper 미구현(legacy — prompt-controlled downgrade 차단 목적의 의도된 미구현). 단 **기본 모델은 단일 출처 `plugins\u002Frein-core\u002Fconfig\u002Fcodex-models.sh` 의 `CODE_GATE_MODEL`** 을 wrapper 가 `-m` 으로 항상 전달한다 (config 전 후보 부재 시 래퍼 내장 canonical `gpt-5.6-sol`+`high` 로 명시 폴백 — 무모델 호출 없음, §6.3). `[SANDBOX:\u003Cmode>]` — 의도된 미구현. prompt 에 있어도 래퍼는 무시하고 `--sandbox read-only` 를 전달한다 (§6.6).\n\n### 6.3 래퍼 자동 effort 산출 기준\n\n`\u002Fcodex-review` 호출 시 **래퍼(`rein-codex-review.sh`)가 변경 규모로 effort 를 결정론적으로 산출**한다(마커 부재 시). caller 가 `[EFFORT:\u003Clevel>]` marker 를 직접 prepend 하면 그 값이 산출을 **오버라이드**하고, 그 외에는 래퍼 산출이 적용된다. `~\u002F.codex\u002Fconfig.toml` 은 어느 경로에서도 건드리지 않는다 (사용자 환경 보존). 측정 실패\u002F불가 시 fail-closed **모델+effort 페어**(`CODE_FAIL_CLOSED_MODEL`+`CODE_FAIL_CLOSED_EFFORT` = `gpt-5.6-sol`+`high`)로 폴백 (`effort_source: fail_closed`).\n\n**코드 산출이 권위**다. 아래 표는 산출 휴리스틱의 의도를 설명하며, Claude 본체\u002F사용자의 수동 `[EFFORT:]` 는 산출을 **오버라이드**(상향\u002F하향)하는 용도다.\n\n| 조건                                                                                    | Effort   | 근거                                                        |\n| --------------------------------------------------------------------------------------- | -------- | ----------------------------------------------------------- |\n| docs-only, 3-10줄 diff, 기존 패턴 반복, markdown\u002Fcomment 수정                           | `low`    | reasoning 최소. 2분 내 완료 → harness auto-background 방지. |\n| 단일 모듈 코드 변경 \u003C100줄, hook 수정 1-2개, 아키텍처 변화 없음, 로컬 로직 추가         | `medium` | 표준 리뷰. 대부분 2-3분 내 완료.                            |\n| 다중 파일 + 보안 표면 \u002F 새 데이터 흐름 \u002F 아키텍처 변경 \u002F 복잡한 상태 관리 \u002F 동시성 이슈 | `high`   | 심층 reasoning 필요. 3분 초과로 background 전환 수용.       |\n\n**판단 경계**:\n\n- 경계 케이스는 한 단계 **낮춰서** 시작 → 리뷰 결과가 \"더 깊은 분석 필요\" 면 재리뷰 round 에서 `high` 로 승급.\n- security-reviewer 와 codex-review 는 **별개 agent**. codex-review 의 effort 는 보안 review 와 무관.\n- 사용자가 prompt 에 명시적 `[EFFORT:...]` 를 붙이면 Claude 의 자동 판단을 **오버라이드**. Claude 는 이를 존중.\n- **재승급 불변식 (E5)**: 재리뷰의 high 승급은 회차 숫자가 아니라 **남은 위험**으로 정한다 — 직전 회차 보고에 High 가 있었거나(그 High 를 고쳤더라도 — 수정은 아직 독립 검토 전이다) 후속 회차가 §2 (b)·(c) 에 어긋나면(지적 처리를 넘는 새 인터페이스·보안 경계·범위 변경, 새 판단) caller 가 `[EFFORT:high]` 마커를 주입한다. §2 후속 회차 조건 (a)~(c) 를 모두 만족하면 조건부 기본값 `[EFFORT:medium]`, 직전 보고가 필수 축을 수행하지 않았으면(직전 검토가 완결이 아님) 마커 없이 산출 강도(floor 포함)로 전체 검토한다. 마커는 산출보다 우선이므로 어떤 산출 결과도 재승급된 high 를 약화시키지 않는다.\n\n**위험도 floor (경로 기반 하한선)**:\n\n변경 크기 ≠ 위험도(3줄 인증 우회 = 고위험, 500줄 생성 문서 = 저위험)의 보완으로, **산출된 effort 에만** path 기반 하한선을 적용한다:\n\n- 위험 경로 패턴 5종: `hooks\u002F**` · `security\u002F**` · `config\u002F**` (임의 깊이 디렉토리 세그먼트 매칭), `.github\u002Fworkflows\u002F**` (repo 루트 앵커), `scripts\u002Frein-*.sh` (`scripts\u002F` 세그먼트 하위).\n- 변경 파일 중 한 줄이라도 매칭 + 산출 effort 가 `low` 면 `medium` 으로 승격 (도장에 `effort_source: computed+floor` 기록).\n- **low→medium 단방향만**: `medium`\u002F`high` 산출은 무변경 — floor 는 하한선이지 가산기가 아니다.\n- **마커 > floor**: 유효 `[EFFORT:]` 마커가 있으면 산출·floor 블록 자체에 진입하지 않는다 — `[EFFORT:low]` + 위험 경로 = 최종 `low`. `[EFFORT:high]` 재승급도 마커 경로라 산출\u002Ffloor 가 관찰하지 못한다 (E5 불변식 보존).\n- **spec-review 모드 미적용**: 문서 리뷰는 코드 경로가 아니므로 위험 경로 매칭 자체를 건너뛴다.\n- 측정 실패 폴백(`fail_closed`)은 이미 `high` 라 floor 와 구조적으로 상호 배타.\n\n**Canonical fallback (config 부재 시)**:\n\nconfig(`codex-models.sh`) 의 전 후보가 부재해도 \"빈 모델 → `-m` 생략 → codex 기본 모델 degrade\" 경로는 없다(폐지됨). 래퍼 내장 canonical 상수 **`gpt-5.6-sol` + `high`** 로 명시 폴백하고 stderr 경고를 정확히 1회 출력한다(config 정상 로드 시 무발화). codex 호출에 `-m` 은 **항상** 전달된다(무모델 호출 0건 — 게이트가 \"codex 기본 모델\" 이라는 외부 가변값에 좌우되지 않도록). 이 경로로 실행되면 도장의 `policy_version` 이 `0` 으로 기록된다.\n\n**재리뷰 일관성 (같은 사이클 = 같은 모델)**:\n\n같은 리뷰 사이클 내 재리뷰는 모델을 바꾸지 않는다 — effort 는 §2 의 후속 회차 규칙과 이 절의 재승급 규칙을 따른다. 게이트 모델이 `CODE_GATE_MODEL` 단일 고정이므로 이 일관성은 **구조적으로 보장**된다 — 규모\u002F회차별 모델 혼용은 결함 탐지 성향·severity 보정·PASS 기준이 달라져 게이트 판정 비일관을 만들므로 하지 않는다.\n\n### 6.4 사용 예 (agent 내부 자동 호출)\n\n입력 prompt:\n\n```\n[NON_INTERACTIVE] spec review for plan: docs\u002Fplans\u002F2026-04-20-v011-workflow-hardening.md\nValidate scope coverage and implementation feasibility.\n```\n\n→ skill 처리 후 실제 codex exec:\n\n```\n# 모델은 래퍼가 단일 출처(codex-models.sh)의 CODE_GATE_MODEL 을 -m 으로 전달.\ncodex exec -m \"$CODE_GATE_MODEL\" --sandbox read-only --config model_reasoning_effort=\"high\" \\\n  --skip-git-repo-check \"spec review for plan: ...\"\n```\n\n### 6.5 사용자 직접 호출과의 차이\n\n`[NON_INTERACTIVE]` marker 없이 `\u002Fcodex-review` 호출 시 기존 interactive 경로 (§2 \"Running a Task\" 의 AskUserQuestion 절차) 따름. 자동 mode 는 agent 내부 호출 전용.\n\n### 6.6 보안 노트\n\n래퍼는 codex 에 `--sandbox read-only` 를 **항상** 넘긴다(코드·문서 리뷰 공통). 생략하면 codex 가 사용자 `~\u002F.codex\u002Fconfig.toml` 의 `sandbox_mode` 를 상속하는데, 그 값이 `danger-full-access` 이면 리뷰어 명령이 저장소 밖 파일을 쓰고 네트워크에 접근할 수 있게 된다. 프롬프트 마커(`[SANDBOX:…]`)·환경 변수로 바꾸는 경로는 없다 — 자동 호출 경로의 권한 상승을 막기 위해서다.\n\n- **`workspace-write` 를 쓰지 않는 이유**: 리뷰어 명령이 저장소 안의 게이트 제어 파일 — 통과 기록 저장소, 훅 정책, 리뷰 회차 카운터, 문서 리뷰 표식, `.claude` 설정 — 을 쓸 수 있게 된다. 이 경로들은 리뷰 대상 지문에 들어가지 않으므로, 리뷰 대상 내용에 심은 지시로 리뷰어가 게이트를 약화해도 감지되지 않는다.\n- **대가**: 리뷰어는 임시 파일을 쓰는 테스트를 다시 돌리지 못한다(`\u002Ftmp` 도 쓸 수 없어 `mktemp` 가 실패한다). 리뷰어는 요청서의 증거 블록(§4.1)을 1차 증거로 대조한다 — 테스트 결과는 증거 블록으로 싣는다.\n- 리뷰어 셸 명령에는 네트워크가 없다. 다만 사용자 codex 설정에 등록된 MCP 서버는 이 샌드박스 밖에서 돌며 네트워크를 쓸 수 있다 — 샌드박스는 셸 명령만 묶는다.\n\n### 6.7 기록 분리 — spec review vs code review (CRITICAL)\n\nnon-interactive mode 는 **두 경로** 로 쓰인다:\n\n- **Code review 자동화** (향후) — agent 가 구현 완료 후 codex-review 를 호출하는 경우. 기존 §5 발급 규정 따름 → PASS 시 v2 code_review 증거 발급.\n- **Spec review 자동화** (v1.0.0 plan-writer 경로) — agent 가 plan\u002Fdesign 을 검증받는 경우. code_review v2 증거를 **절대 발급하지 않음**. legacy marker 는 이미 존재하지 않으므로 건드릴 대상도 없음. 코드리뷰 gate 는 별개 절차.\n\n**Spec review 모드 감지**: prompt 첫 줄이 `[NON_INTERACTIVE] spec review for plan:` 또는 `[NON_INTERACTIVE] spec review for design:` 형식으로 시작하면 spec-review 서브플로우로 분기.\n\n**spec-review 모드는 review-readiness 사전검사 대상이 아니다** — 사전검사·정량\u002FPASS 휴리스틱·`evidence_manifest:` 슬롯 전부 skip (사전검사는 code-review 모드 한정, §2 요청서 작성 규약 \u002F §4.1 참조). spec-review 지시문의 \"PASS\"·\"coverage\" 류 관행 토큰이 false-positive 로 자동 경로를 차단하지 않도록 하기 위함이다.\n\n**문서 리뷰의 판정 축 (2026-08-04)**: spec-review 모드는 코드 리뷰와 **다른 슬롯 세트**를 받는다. 래퍼가 결정 건전성 \u002F 범위·추적성 \u002F 주장 감사 3개 슬롯과 판정 규율 4종을 방출하며, 코드 결함·테스트 정합 슬롯은 방출하지 않는다.\n\n- 판정 축은 **결정·범위·추적성** 이다. 구현 수준 결함(오류 경로·자원 수명주기·버퍼링 방식·자료구조 선택)은 verdict 에 반영하지 않고 \"구현 단계 이관\" 참고사항으로 분류한다 — 문서는 실행할 수 없어 그런 주장을 검증할 수단이 없고, 코드 리뷰 게이트가 실물을 놓고 판정한다.\n- 같은 성격의 지적은 적용 대상 전체를 **한 회차에 일괄 열거**한다 (입력·대상별 분할 지적 금지).\n- 저장소에 없는 일반 규칙을 근거로 반려하지 않는다 — \"저장소 계약 위반\" 과 \"일반론 참고사항\" 을 분리 표기하며 후자는 verdict 를 승격시키지 않는다.\n- 수정 필요로 이어지는 지적은 **결정·범위·사실의 오류**여야 하고, 무엇을 잘못 전달하는지와 근거가 지적에 있어야 한다. 같은 뜻의 표현 선택·배치·명칭 취향은 \"참고사항\" 으로 분리해 verdict 를 승격시키지 않는다 — 실질 결함(잘못된 사용법·영향 범위·필수 보안·안전 제약의 누락(범위의 오류 — \"구현 단계 이관\" 참고사항이 아니다)·완료 주장·수치 불일치·누락된 추적 연결)은 계속 차단한다. 코드 봉투의 Claim Audit 도 문서 주장에 같은 문장을 쓴다 (§3).\n\n> 배경: 이전 판에는 모드 분기가 없어 문서 리뷰에도 코드 리뷰 슬롯이 그대로 나갔다. 즉 계획서를 놓고 \"resource leak 을 찾아라\" 라고 지시한 셈이고, 리뷰어가 파일 핸들 수명주기를 소송한 것은 지시대로 수행한 결과였다. 그 지적에 대응하려면 구현 세부를 문서 본문에 쌓아야 해서 리뷰 표면이 자가증식했다 (실측: 설계 리뷰 41회·계획 리뷰 26회, 구현 코드 0줄).\n\nSpec review 서브플로우 동작:\n\n1. codex exec 실행 → verdict 캡처 (PASS \u002F NEEDS-FIX \u002F REJECT)\n2. **code_review v2 증거를 발급하지 않음** (코드리뷰 게이트 오염 방지)\n3. legacy marker 는 이미 존재하지 않으므로 건드릴 대상도 없음\n4. PASS 시 caller (plan-writer 등) 가 책임지고 `bash \"${CLAUDE_PLUGIN_ROOT:-$PWD}\u002Fscripts\u002Frein-mark-spec-reviewed.sh\" \u003Cpath> \u003Creviewer>` 호출 — 이는 spec-review 전용 stamp (`trail\u002Fdod\u002F.spec-reviews\u002F\u003Chash>.reviewed`) 생성 (spec-review 축은 이번 웨이브의 존속 예외)\n5. NEEDS-FIX\u002FREJECT 시 caller 가 handoff (spec-review stamp 생성 안 함)\n\n**왜 분리?**: code_review v2 증거는 `pre-bash-commit-review-gate.sh`(구 `pre-bash-test-commit-gate.sh` 후속, ③-c 삭제 완료) 의 코드 commit gate 축 전용. spec review 에서 이 증거가 발급되면 코드 변경 없이도 gate 통과 가능해져 rein 규율이 깨짐.\n\n**Sonnet fallback 분기도 동일**: spec review 모드에서 codex 실패로 code-reviewer fallback 이 호출되더라도 code_review v2 증거 발급 금지. code-reviewer skill 이 spec-review context 를 인식할 수 있도록 prompt 전달 경로에서 `[NON_INTERACTIVE] spec review` prefix 보존.\n\n---\n\n## 7. Resume 정책\n\n**래퍼는 세션을 이어가지 않는다.** `scripts\u002Frein-codex-review.sh` 는 초회든 재리뷰든 매번 새 `codex exec` 세션을 띄운다 — 이전 회차의 대화는 다음 회차에 전달되지 않는다. 같은 사이클 재리뷰에서 이전 검토의 맥락을 살리는 수단은 세션 재사용이 아니라 **후속 회차 요청서 양식(§2)** 이다.\n\n수동으로 `codex exec resume --last` 를 쓰는 것은 권하지 않는다 — 래퍼를 거치지 않으면 회차 계수(§10)·이벤트 기록·증거 발급(§5)이 모두 빠진다.\n\n**사이클 경계**: 새 DoD, 새 PR, 새 기능 개발로 전환되면 후속 회차 양식을 쓰지 않고 초회 전체 검토로 시작한다. Second opinion 용도로는 절대 이 스킬이 아니라 `\u002Fcodex-ask` 를 사용한다 (v2 증거 미발급 + resume 금지).\n\n---\n\n## 8. Error Handling\n\n- `codex --version` 또는 `codex exec` 가 non-zero exit → 즉시 중단 + 보고. 재시도 전 사용자 지시 요청.\n- 고위험 플래그 (`--full-auto`, `--sandbox danger-full-access`, `--skip-git-repo-check`) 사용 전 `AskUserQuestion` 으로 허가 획득 (이미 허가되지 않은 경우).\n- 경고\u002F부분 결과가 포함된 출력은 요약 후 `AskUserQuestion` 으로 조정 방향을 확인.\n- Codex 실행 실패 → §4 Sonnet fallback 경로. 실패 사유는 §9 사용자 안내 문구에 담는다 (더 이상 저장할 stamp 필드가 없다).\n- **Hang 증상 탐지** (§2 실행 모드 절 참고):\n  - codex 프로세스가 수 분 이상 진행 없음 + CPU 0 + 네트워크 연결 0개 → Bash 도구 auto-background 전환으로 stdin 이 unix socket 에 붙어 sandbox\u002Fauth 초기화가 멈춤\n  - 체크: `lsof -p \u003Cpid> -i` 결과 비어있으면 API 요청 미도달. `ps -o stat,%cpu,etime -p \u003Cpid>` 로 Sleep + 0% CPU 확인\n  - 대응: kill 후 **foreground + stdin close + 직접 파일 출력** 형태로 재호출 (wrapper 경로 권장)\n  - 기록 상태 정리 (Phase 7 웨이브 3 ③-d 갱신): legacy stamp 가 없으므로 \"hang 이 발생한 사이클이 절반만 기록된 상태로 남는\" staleness 자체가 구조적으로 사라졌다 — v2 발급은 원자적 all-or-nothing(호출 자체가 성공\u002F실패)이다. 재호출 전에는 그 회차가 v2 발급까지 도달했는지(사람 보고 문구 또는 §9 결과)만 확인하면 된다.\n\n---\n\n## 9. 사용자 안내\n\n이 SKILL 의 결과를 사용자에게 보고할 때 (호출자 = Claude 가 wrapper output 을 user-facing chat 으로 emit 할 때) 다음 짧은 형식을 **먼저** 출력한다. wrapper 자체는 raw codex output 을 그대로 emit 하며, 안내 prepend 는 호출자 책임 (호출 환경이 다양하므로 wrapper 강제 안 함). 형식은 한 문장 또는 두 문장 — 결과 1줄 + 다음 액션 1줄.\n\n**Round PASS (verdict=PASS)**:\n> 코드 리뷰 통과. [차단급 결함 없음 또는 Low advisory N건]. 다음은 [보안 리뷰 또는 다음 단계].\n\n**Round NEEDS-FIX (verdict=NEEDS-FIX)**:\n> 리뷰에서 N건 수정이 필요해요 — [Severity 요약, 예: \"Medium printf 형식 + Low 테스트 stderr 미검증\"]. [고치고 재리뷰 또는 sonnet 셀프리뷰 — escalation 규칙 §3 따라].\n\n**Sonnet fallback 통과** (§4 — codex 실행 실패 한정. §3 의 sonnet self-review 와 다른 path):\n> codex 가 떠지지 않아 sonnet 폴백 리뷰로 통과시켰습니다 (사유: \u003C코드>). 통과 증거가 기록됐어요.\n\n**3회차에도 High 잔존 → 사람 에스컬레이션**:\n> 3회차 리뷰에도 High N건이 남아 사람 에스컬레이션이 필요해요. 이 회차는 기록되지 않았습니다 — 잔존 이슈를 직접 처리한 뒤 다시 리뷰해 주세요.\n\n**PASS 이지만 기록 실패** (v2 발급이 exit 0 이 아님 — §5.6):\n> 리뷰 자체는 통과했지만 이번 회차는 기록되지 않았어요 — [사유]. digest 재캡처 후 재리뷰가 필요합니다.\n\n이 짧은 안내 후에 기존 codex 본문 (Code Defects \u002F Design Alignment \u002F Test Alignment \u002F Claim Audit \u002F `FINAL_VERDICT: \u003CX>`) 를 그대로 emit 한다.\n\n---\n\n## 10. 회차 예산 (round budget)\n\n리뷰 회차 상한은 **래퍼가 강제**한다. 산문 지시가 아니라 코드 계약이다.\n\n| 항목 | 계약 |\n|---|---|\n| 사이클 키 | 문서 리뷰 = 리뷰 대상 문서의 **정규화된 절대 경로** \u002F 코드 리뷰 = 활성 작업 기준서 slug. 키가 다르면 예산은 독립. 정규화하지 않으면 같은 문서를 `docs\u002Fx.md` \u002F `.\u002Fdocs\u002Fx.md` \u002F 절대 경로로 번갈아 불러 각각 새 예산을 받는 우회가 생긴다 |\n| 기본 상한 | **5회차** (§3 의 \"3회차 High 잔존 → 사람 에스컬레이션\" 에 2회 여유) |\n| 계수 대상 | **미통과 회차만.** 통과(PASS)는 사이클 종료이므로 카운터를 제거한다 |\n| 계수 시점 | **verdict 가 확정된 뒤** 1회. 상한 판정만 spawn 이전에 한다. 리뷰가 수행되지 않은 종료 — 준비도 거부(4)·모델 오류(3)·워치독 정지(5) — 는 예산을 소모하지 않는다 (§4.2 의 \"재리뷰 카운트 비포함\" 과 정합) |\n| 초과 시 | codex 를 실행하지 않고 **exit 6** + 앵커 진단. v2 발급 미시도 |\n| 연장 | **프롬프트 첫 줄이 `[MAX_ROUNDS:5]` 하나뿐일 때만** 선언으로 인정한다 (정수 1~999). 기본 상한을 넘기는 선언은 stderr 경고 + 카운터 이력에 기록된다 — **조용한 연장 경로는 없다** |\n| 마커 오인 방지 | 본문 어디에 있는 마커 문자열도 선언으로 해석하지 않고 **원문 그대로 보존한다**. 위치 제한이 없던 이전 판에서는 이전 리뷰 출력이나 문서 예시를 인용하기만 해도, \"사용자는 `[MAX_ROUNDS:8]` 을 승인하지 않았다\" 같은 문장으로도 상한이 올라갔다 — 사람 승인 없이 예산이 늘어나는 경로였다. 첫 줄 단독이지만 정수가 아닌 표기도 선언이 아니며 본문이 유지된다 |\n| 상태 파일 | `trail\u002Fdod\u002F.review-rounds\u002F\u003C키 해시>` (key \u002F count \u002F limit \u002F updated \u002F extensions). 기록은 키별 락 안에서 파일을 다시 읽어 증가시키고 임시파일+교체로 원자화한다 |\n| 카운터 불가 | 디렉토리 쓰기 불가·count 손상은 예산 소진이 **아니다** — 별도 앵커 + exit 7. 손상 시 0 으로 되돌리지 않는다 (되돌리면 카운터를 훼손하는 것만으로 예산이 무한 리셋된다) |\n| 동시성 한계 | 판정과 기록이 codex 실행 시간만큼 떨어져 있어, 상한 직전 **동시 진입 k 건은 모두 통과**할 수 있다 (최대 k-1 회 초과, k 에 상한 없음). 초과분이 기록된 뒤의 호출은 정상 거부된다. 엄격히 막으려면 만료 가능한 진행 중 예약이 필요한데, 만료를 짧게 잡으면 정상 리뷰를 죽이고 길게 잡으면 강제 종료된 리뷰가 회차를 문다 — 유실 방지를 택하고 한계를 남긴 사용자 결정 (2026-08-04) |\n| 연장 이력의 수명 | 연장 선언은 선언 즉시 stderr 경고로 드러나고, 카운터가 살아있는 동안 그 파일의 이력에 남는다. **사이클이 통과로 끝나면 카운터와 함께 이력도 정리된다** — 세션을 넘는 영구 감사 기록이 아니다 |\n| 위협 모델 | 이 예산은 **정직한 에이전트의 규율 장치**다. 카운터 파일을 지우면 0회차로 돌아가고, 그 디렉토리를 지키는 훅은 없다. 대체 리뷰어 경로가 예산을 확인하지 않는 것도 문서 규정으로만 막혀 있다. 적대적 우회 하드닝은 범위 밖이며, 막는 대상은 \"고치다 보니 무한히 재리뷰하게 되는\" 발산이다. 다만 **의도치 않은 파일 파괴**(카운터 파일 또는 그 디렉토리가 심볼릭 링크인 경우)는 거부한다 — 판정 초입에서 검사해 mkdir·읽기 이전에 exit 7 로 중단하고, 미통과 기록은 락 안에서, 통과 정리는 rm 직전에 각각 재검사한다 (통과 정리 실패도 표식 없이 exit 7) |\n\n**호출자(에이전트) 규약**: exit 6 을 받으면 **재호출하지 않는다.** 지금까지의 지적과 남은 쟁점을 요약해 사람에게 넘긴다. 연장이 필요하다는 판단은 사람의 승인 사항이며, 에이전트가 스스로 `[MAX_ROUNDS:]` 를 올려 재시도하는 것은 이 계약의 위반이다.\n\n> 배경: 회차 상한이 산문 지시로만 존재하던 동안 두 번 무력화됐다 — 보고 없이 13회차 연속 실행, 그리고 사람의 종결 지시 이후 5회차 추가 실행(설계 문서 도장이 무효화되며 병렬 작업자 전원이 편집 차단됨). 산문으로 한 줄 더 쓰는 처방은 이미 두 번 반증됐으므로 채택하지 않았다.\n",{"name":5,"description":7},"main","8e6e9ee16c5c7343c6f0db55f6c8b37d5ef6f1d0","plugins\u002Frein-core\u002Fskills\u002Fcodex-review",0,"https:\u002F\u002Fgithub.com\u002FJayJihyunKim\u002Frein","https:\u002F\u002Fgithub.com\u002FJayJihyunKim","2026-10-15 08:31:34",{"source":8,"skillName":5,"commands":42,"prompt":52,"zipAvailable":53},[43,46,49],{"tool":44,"command":45},"npx","npx skills add JayJihyunKim\u002Frein --skill codex-review",{"tool":47,"command":48},"bunx","bunx skills add JayJihyunKim\u002Frein --skill codex-review",{"tool":50,"command":51},"pnpm","pnpm dlx skills add JayJihyunKim\u002Frein --skill codex-review","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FJayJihyunKim\u002Frein\u002Fblob\u002Fmain\u002Fplugins\u002Frein-core\u002Fskills\u002Fcodex-review\u002FSKILL.md",true,[],[56,69,80,91,102,109],{"skillNo":57,"name":58,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":59,"categories":60,"keywords":66,"publishedAt":29},"01M70FX2WFS12FG002RNTCSXC7","sample-skill","https:\u002F\u002Fgithub.com\u002FJayJihyunKim\u002Frein\u002Fblob\u002Fmain\u002Ftests\u002Fscripts\u002Ffixtures\u002Frein-template-min\u002F.claude\u002Fskills\u002Fsample-skill\u002FSKILL.md",[61],{"categoryNo":62,"categoryName":63,"taxonomyType":64,"taxonomyTypeDesc":65},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[67,68],"sample","sample skill",{"skillNo":70,"name":71,"nameZh":6,"description":72,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":73,"categories":74,"keywords":75,"publishedAt":29},"01M70FX0BT6WSNZKFF60KTZ5H9","repo-audit","저장소 전체 상태 점검. 오래된 규칙, 누락 테스트, 비활성 에이전트, trail 일관성 확인.","https:\u002F\u002Fgithub.com\u002FJayJihyunKim\u002Frein\u002Fblob\u002Fmain\u002Fplugins\u002Frein-core\u002Fskills\u002Frepo-audit\u002FSKILL.md",[],[76,77,78,79],"repo","repo audit","audit","trail",{"skillNo":81,"name":82,"nameZh":6,"description":83,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":84,"categories":85,"keywords":87,"publishedAt":29},"01M70FWXCWRZEJSDQGTNDEKF2G","promote-agent","에이전트 후보를 정식 에이전트로 승격. 사람 승인 이후에만 실행.","https:\u002F\u002Fgithub.com\u002FJayJihyunKim\u002Frein\u002Fblob\u002Fmain\u002Fplugins\u002Frein-core\u002Fskills\u002Fpromote-agent\u002FSKILL.md",[86],{"categoryNo":62,"categoryName":63,"taxonomyType":64,"taxonomyTypeDesc":65},[88,89,90],"promote agent","promote","incidents",{"skillNo":92,"name":93,"nameZh":6,"description":94,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":95,"categories":96,"keywords":97,"publishedAt":29},"01M70FWTP0QPQN09VJR8DNKWZS","pr-review-fixer","PR 리뷰 코멘트를 읽고 수정 사항을 자동으로 적용한다","https:\u002F\u002Fgithub.com\u002FJayJihyunKim\u002Frein\u002Fblob\u002Fmain\u002Fplugins\u002Frein-core\u002Fskills\u002Fpr-review-fixer\u002FSKILL.md",[],[98,19,99,100,101],"pr","pr review","pr review fixer","fixer",{"skillNo":103,"name":104,"nameZh":6,"description":105,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":106,"categories":107,"keywords":108,"publishedAt":29},"01M70FWR0TC5NDF12D9E5ETBAW","persona","페르소나 프리셋 선택·해제 + 질문-답변 커스텀 프리셋 생성. \"페르소나 골라줘\u002F만들어줘\u002F바꿔줘\u002F꺼줘\" 류 요청에서 호출.","https:\u002F\u002Fgithub.com\u002FJayJihyunKim\u002Frein\u002Fblob\u002Fmain\u002Fplugins\u002Frein-core\u002Fskills\u002Fpersona\u002FSKILL.md",[],[104],{"skillNo":110,"name":111,"nameZh":6,"description":112,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":113,"categories":114,"keywords":115,"publishedAt":29},"01M70FWKFPNVC4M5ECZE1F0WS6","parallel-execute","활성 plan 의 `## 실행 전략`(depends_on\u002Fmode\u002Fscope v2) 을 읽어 의존 위상정렬로 웨이브를 유도하고, 각 웨이브의 독립 edit_only 태스크를 같은 작업 트리에서 서브에이전트로 병렬 실행, mutating·의존 태스크는 순차 실행한다. 부모(메인 세션)가 웨이브 단위로 검증·테스트·커밋. 호스트가 병렬 dispatch 불가 시 순차 fallback 으로 degrade.","https:\u002F\u002Fgithub.com\u002FJayJihyunKim\u002Frein\u002Fblob\u002Fmain\u002Fplugins\u002Frein-core\u002Fskills\u002Fparallel-execute\u002FSKILL.md",[],[116,117,118,119,27,120,121,122,123,124],"parallel","execute","parallel execute","edit","mode","dispatch","scope","plan","only"]