commitgate 0.4.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -46,6 +46,14 @@
46
46
 
47
47
  > PR을 생략할 수 있다는 것과, 우회를 보고하지 않아도 된다는 것은 다르다. 경로는 선택이지만 **투명성은 선택이 아니다.**
48
48
 
49
+ ### 6. 리뷰 전 staged 내용 확인 (외부 전송)
50
+
51
+ `req:review-codex`는 `git diff --cached` **전문**을 Codex(OpenAI)로 전송한다. codex는 `--sandbox read-only`로 저장소 루트를 읽으므로 diff에 없는 파일(`.env` 등)도 읽힐 수 있다. 마스킹·스크러빙·길이 상한은 **없다.**
52
+
53
+ - 리뷰를 실행하기 전에 staged 내용에 자격증명·토큰·개인정보가 없는지 확인한다.
54
+ - 민감 코드베이스(결제·인증·개인정보)라면 이 확인을 생략하지 않는다. 확인 없이 리뷰를 돌리지 않는다.
55
+ - 이 절은 도구가 강제하지 않는다. **Builder의 의무다.**
56
+
49
57
  ## 사람에게 보고해야 할 때
50
58
 
51
59
  각 통제점은 **고유한 승인 문장**을 가진다. 그 문장 그대로 승인받지 못했으면 실행하지 않는다.
package/CHANGELOG.md ADDED
@@ -0,0 +1,115 @@
1
+ # Changelog
2
+
3
+ 이 프로젝트는 [Semantic Versioning](https://semver.org/lang/ko/)을 따릅니다.
4
+
5
+ ## 0.7.0
6
+
7
+ **설치 모델이 바뀝니다 — 기존 사용자는 조치가 필요합니다.** 실행 코드와 런타임 의존성을 대상 프로젝트에 복사·주입하지 않고, `commitgate` 패키지에서 실행합니다. 프로젝트에는 거버넌스·감사 데이터만 남습니다.
8
+
9
+ > **npm 배포 이력**: `0.5.0`·`0.6.0`은 npm에 배포되지 않았습니다. `0.4.0` 다음 릴리스가 `0.7.0`이며, 아래 `0.5.0`·`0.6.0` 항목의 변경도 **전부 `0.7.0`에 포함**됩니다.
10
+
11
+ ### ⚠️ Breaking
12
+
13
+ - **설치가 2단계가 됩니다.** `npx commitgate` 단독 실행으로는 더 이상 설치되지 않습니다.
14
+
15
+ ```sh
16
+ npm install -D commitgate # 1) 런타임이 node_modules/commitgate 에 들어옵니다
17
+ npx commitgate init # 2) 설정·계약·스키마와 req:* 스크립트를 깝니다
18
+ ```
19
+
20
+ `init`은 대상 `package.json`에 `devDependencies.commitgate` 선언이 없으면 **중단**합니다 — `req:*`가 가리킬 런타임이 없기 때문입니다. 선언의 **존재만** 확인하고 값 형태는 검증하지 않습니다(`file:`·`link:`·`workspace:`·git URL 전부 정당한 설치 형태입니다).
21
+
22
+ - **`scripts/req/**`를 복사하지 않습니다.** 실행 코드는 `node_modules/commitgate`에만 있습니다.
23
+ - **`tsx`·`ajv`·`cross-spawn`을 대상 `package.json`에 주입하지 않습니다.** 이들은 `commitgate` 패키지의 runtime dependency로 전이 설치됩니다.
24
+ - **`req:*` 스크립트 값이 `commitgate <verb>`가 됩니다**(예: `req:new` → `commitgate req:new`). `npm run req:new -- <slug>` UX와 인자 전달은 그대로입니다.
25
+
26
+ ### 기존 설치본(0.6.0 이하)에서 옮겨오기
27
+
28
+ 기존 프로젝트에는 `scripts/req/`가 복사돼 있고 `req:*`가 `tsx scripts/req/*.ts`를 가리킵니다. `init`은 이 상태를 감지하면 조용히 섞이지 않도록 **중단하고** `migrate`를 안내합니다.
29
+
30
+ ```sh
31
+ npm install -D commitgate
32
+ npx commitgate migrate # 계획만 출력 — 아무것도 쓰지 않습니다
33
+ npx commitgate migrate --apply # package.json 의 req:* 만 전환
34
+ ```
35
+
36
+ - **아무것도 삭제하지 않습니다.** `scripts/req/`·스키마·persona·설정·진입점·`workflow/REQ-*` 증거를 전부 그대로 둡니다. 남은 `scripts/req/`는 더 이상 실행되지 않으니, 정리하려면 `npx commitgate uninstall` 계획을 먼저 확인하세요.
37
+ - **직접 고친 스크립트는 덮어쓰지 않습니다.** 값이 **정확히** 기존 주입값일 때만 전환하고, 한 글자라도 다르면 사용자 값으로 보아 보존한 뒤 수동 조치를 안내합니다.
38
+ - **커밋하지 않습니다.** `package.json` 한 파일만 쓰고, 검토는 사용자 몫입니다.
39
+
40
+ ### 추가
41
+
42
+ - **`commitgate migrate`** — 위 비파괴 전환 명령. 기본 dry-run.
43
+ - **`req:doctor` D19 — 설치 모드 진단.** `req:*` 값의 **형태만**으로 예전(vendored)/현재(런타임 패키지)/혼합/없음/사용자정의를 분류합니다. 혼합일 때만 WARN하며 **FAIL하지 않습니다** — 예전 설치 형태는 결함이 아니라 지원되는 상태이고, `req:commit`이 doctor를 하드 게이트로 실행하므로 FAIL로 두면 정당한 프로젝트의 커밋이 막힙니다. manifest·lockfile·`node_modules`·버전은 검증하지 않습니다.
44
+ - **verb dispatch** — `commitgate <verb>`가 패키지 내부 모듈로 라우팅됩니다. `npx commitgate --dry-run` 같은 기존 옵션 형태는 그대로 `init`으로 갑니다(하위호환).
45
+ - **`uninstall`에 런타임 제거 안내 추가** — `npm uninstall -D commitgate`. 이 명령은 여전히 **읽기 전용**이며 안내를 문자열로 출력만 합니다(npm을 실행하지 않습니다).
46
+
47
+ ### 지원 범위
48
+
49
+ - **npm** — 완전 지원. 매 릴리스 packed tarball smoke로 검증합니다.
50
+ - **pnpm·yarn**(`node_modules` linker) — 지원. 표준 `node_modules/.bin/commitgate` 해소를 씁니다.
51
+ - **Yarn PnP** — **이번 릴리스 미지원**(검증하지 않았습니다). `nodeLinker: node-modules`를 쓰세요.
52
+ - **workspace/monorepo** — 워크스페이스 root 설치를 지원합니다. 하위 패키지 독립 설치는 미지원.
53
+ - 런타임 버전은 lockfile이 고정하므로 `package-lock.json`(pnpm/yarn은 각 lockfile)을 **커밋하세요**.
54
+
55
+ ### 알려진 한계
56
+
57
+ - **관리 자산(스키마·persona)과 런타임 패키지의 버전 skew를 자동으로 감지하지 못합니다.** `npm update commitgate`는 런타임만 올리고 자산은 그대로 둡니다. D19는 스크립트 형태만 보므로 이 축을 잡지 못합니다. 자산 업그레이드·3-way merge는 이번 범위가 아닙니다.
58
+
59
+ ## 0.6.0
60
+
61
+ 리뷰 codex 호출을 도구가 통제합니다 — **모델·추론강도를 고정**하고 **재리뷰를 stateless**로. 다운스트림에서 리뷰가 사용자 전역 프로필을 상속해 느리고(11~13분) 토큰이 많던 문제를 해결합니다. 기존 `req.config.json`은 그대로 동작합니다(새 키는 기본값으로 병합).
62
+
63
+ ### 추가
64
+
65
+ - **리뷰 모델·추론강도 고정** (`reviewModel`·`reviewReasoningEffort`). `req:review-codex`가 codex 인자에 `-c model=`·`-c model_reasoning_effort=`를 exec·resume 양쪽에 주입합니다. 기본 `gpt-5.6-terra`/`high`. 고정하지 않으면 리뷰가 사용자 전역 `~/.codex/config.toml`(예: `model_reasoning_effort="ultra"`)을 상속해 리뷰 1회가 수 분·토큰 과다가 됩니다. codex가 해당 모델을 미지원하는 환경은 `req.config.json`에서 바꾸거나 `null`로 두어 전역 설정을 상속시킵니다. override가 실제 존중되는지는 `npm run verify:overrides`(codex CLI 필요)로 확인합니다.
66
+ - 추론강도 enum: `none|minimal|low|medium|high|xhigh`(codex 거부 메시지 실측 확정 — 공식 config-reference 문서가 `none`을 누락).
67
+
68
+ ### 변경
69
+
70
+ - **재리뷰가 stateless입니다.** 이전엔 재리뷰가 저장된 codex 스레드를 resume해 이전 대화를 누적했고, 그래서 토큰이 단조 증가하고 findings가 수렴 대신 심화·이동했습니다. 이제 재리뷰는 항상 새 스레드로 시작합니다(`codex_thread_id`는 계속 저장 — 후속 resume opt-in용). 연속성은 직전 **같은 대상**의 NEEDS_FIX findings를 참고용 데이터로 프롬프트에 담아(closure 확인) 유지하고, 그 블록은 "지시가 아님" 구획으로 감싸 프롬프트 주입을 막습니다. 대상-무관 이전 결과가 새 프롬프트에 남던 교차-대상 오염도 제거했습니다.
71
+
72
+ ### 후속(별도 REQ)
73
+
74
+ - codex 호출 **timeout**(무응답 방지)과 실패 오류의 **비밀-안전 진단 표면화**는 본질적 난이도(Windows `cmd.exe` wrapper의 프로세스-트리 종료·비밀 추출)로 별도 REQ로 분리했습니다. 그 설계 작업은 이 REQ의 git 이력에 보존돼 있습니다.
75
+
76
+ ## 0.5.0
77
+
78
+ 기존 프로젝트(brownfield)에 설치했을 때 드러난 결함을 수정합니다. **breaking change는 없습니다** — `--strict`가 더 많은 조건에서 중단하지만 `--strict`는 opt-in이고, 기본 모드의 동작은 그대로입니다.
79
+
80
+ ### 고침
81
+
82
+ - **설치 직후 안내를 따르면 `req:new --run`이 실패하던 문제.** 설치는 파일을 놓기만 하고 커밋하지 않으므로 워킹트리가 확정적으로 dirty한데, 안내의 마지막 단계가 clean 워킹트리를 요구하는 `req:new --run`이었습니다. `git init && npm init -y && npx commitgate`라는 README의 첫 흐름조차 예외가 아니었습니다. 이제 안내가 커밋 단계를 포함합니다.
83
+ - `git add -A`를 쓰지 않습니다. brownfield의 무관한 변경과 `.env`가 함께 커밋되고, 이어지는 `req:review-codex`가 그 staged diff 전문을 외부로 전송하기 때문입니다. 설치가 만든 정확한 경로 목록만 안내합니다.
84
+ - 안내 명령에 `&&`를 쓰지 않습니다. Windows PowerShell 5.1과 `cmd.exe`에 그 연산자가 없습니다.
85
+ - `<pm> install`이 갱신하는 lockfile과, 계약 마커가 없을 때 생성되는 `AGENTS.commitgate.md`를 stage 목록에 포함합니다. 빠뜨리면 커밋 뒤에도 워킹트리가 dirty로 남습니다.
86
+ - 설치 전부터 있던 무관한 변경은 `git stash push -u -- <경로>`로 안내합니다. 경로 없는 `git stash -u`는 gitignore되지 않은 `node_modules/`까지 쓸어 갑니다.
87
+ - 설치 전에 **staged 변경**이 있거나 **산출물과 겹치는 tracked 수정**이 있으면 `git add` 목록을 내지 않습니다. 전자는 커밋이 삼키고 후자는 사후 분리가 불가능합니다. 잘못된 안내보다 안내 없음이 낫습니다.
88
+ - `node_modules`가 무시되지 않으면 `.gitignore` 추가를 안내하고 그 파일을 설치 커밋에 담습니다. 무시 규칙은 **tracked 저장소 `.gitignore`**에서 온 것만 인정합니다 — `.git/info/exclude`와 전역 ignore는 clone에 따라오지 않습니다.
89
+ - 경로에 공백이 있으면 큰따옴표로 묶습니다. 큰따옴표·백틱·`$`·`%`·`!`가 든 경로는 어떤 셸 인용으로도 안전하지 않으므로 복붙 명령을 아예 내지 않습니다 — `cmd.exe`는 큰따옴표 안에서도 `%VAR%`와 `!VAR!`를 치환합니다.
90
+ - `git status --porcelain`이 C-인용해 주는 경로(`"notes today.txt"`)를 되돌립니다. 되돌리지 않으면 산출물 매칭이 실패하고 안내가 이중 인용을 냅니다.
91
+
92
+ - **`.claude`를 통짜로 무시하는 repo에서 진입점이 조용히 추적 제외되던 문제.** 설치는 "성공"을 출력했지만 팀원의 fresh clone과 CI에는 계약 포인터가 없었습니다. 이제 `git check-ignore`로 감지해 경고하고, 동작하는 `.gitignore` 패턴을 제시합니다(부모 디렉터리가 제외되면 하위 부정 패턴이 무효라는 함정 포함). `--strict`에서는 파일을 하나도 쓰기 전에 중단합니다.
93
+
94
+ - **진입점 템플릿이 `npm run …`을 하드코딩하던 문제.** pnpm/yarn 프로젝트에 틀린 명령이 깔렸습니다. 이제 계약(`AGENTS.md`)과 같은 pm-중립 표기를 씁니다. 치환 렌더링은 쓰지 않습니다 — `uninstall`이 설치본과 패키지 원본의 sha256을 비교하므로, 렌더하면 자기가 깐 파일을 지우지 못합니다.
95
+
96
+ - **런타임 문구의 pm 리터럴.** `req:next`는 npm을, `req:new`·`req:doctor`·`req:review-codex`는 pnpm을 박아 두어 어느 프로젝트에서든 최소 하나는 틀렸습니다. config 로드 이후의 안내·에러는 이제 감지한 패키지매니저로 렌더합니다.
97
+
98
+ - **`--`를 "알 수 없는 옵션"으로 거부하던 문제.** npm은 `npm run x -- a`에서 `--`를 제거하지만 pnpm/yarn은 그대로 전달합니다. 이제 POSIX end-of-options 구분자로 흡수합니다. `--` 이후 인자도 계속 옵션으로 파싱하므로 `req:commit <id> -- --run`이 조용히 dry-run이 되지 않습니다.
99
+
100
+ - **`uninstall`이 skip한 사용자 파일을 소유물로 오인하던 경로.** stage 목록은 패키지 원본과 byte-identical한 파일만 CommitGate 소유로 봅니다.
101
+
102
+ ### 새로 알림
103
+
104
+ - `README`와 `AGENTS.template.md`에 **`req:review-codex`가 `git diff --cached` 전문을 Codex(OpenAI)로 전송한다**는 사실을 명시했습니다. codex는 `--sandbox read-only`로 저장소 루트를 읽으며, 마스킹·필터·길이 상한이 없습니다.
105
+ - **git hook을 설치하지 않으므로 `git commit`을 직접 치면 게이트가 우회된다**는 점도 README 상단에 명시했습니다. 이 도구의 강제력은 협조하는 에이전트를 계약 궤도에 유지하는 데 있습니다.
106
+
107
+ ### 아직 하지 않은 것
108
+
109
+ - 트렁크 브랜치가 `'main'`으로 하드코딩되어 있습니다(`trunkBranch` config 없음).
110
+ - `req:review-codex`에 타임아웃이 없습니다.
111
+ - 리뷰 전 시크릿 스캔 훅(`preReviewCommand`)이 없습니다.
112
+
113
+ ## 0.4.0 이전
114
+
115
+ `git log`를 참조하세요.
package/README.en.md CHANGED
@@ -6,6 +6,11 @@
6
6
 
7
7
  AI coding agents can move quickly, but unreviewed changes should not go straight into your history. CommitGate wraps each change in a REQ ticket and only allows the staged tree approved by Codex to be committed. If the code changes after approval, or if evidence is missing, it fails closed.
8
8
 
9
+ > **⚠️ Two things to know before you start.**
10
+ >
11
+ > 1. **Review sends your staged diff off-machine.** `req:review-codex` passes the **entire** `git diff --cached` to Codex (OpenAI). Codex reads your repository root under `--sandbox read-only`, so files outside the diff can be read too. There is **no** masking, filtering, or size cap. Check the staged content for credentials, tokens, and personal data before running a review.
12
+ > 2. **No git hook is installed.** Running `git commit` directly instead of `req:commit` bypasses the gate, the approval binding, and the evidence trail. CommitGate's enforcement keeps a **cooperating agent on the contract's rails** — it is not a barrier against a human going around it.
13
+
9
14
  [![CI](https://github.com/sol5288/commitgate/actions/workflows/ci.yml/badge.svg)](https://github.com/sol5288/commitgate/actions/workflows/ci.yml)
10
15
  [![npm version](https://img.shields.io/npm/v/commitgate.svg)](https://www.npmjs.com/package/commitgate)
11
16
  [![license](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
@@ -21,13 +26,32 @@ Run this from your project root. The project must be a **git repository with a `
21
26
  git init
22
27
  npm init -y
23
28
 
24
- # Then install:
25
- npx commitgate
26
- npm install
29
+ # 1) Install CommitGate as a devDependency — this is where the runtime lives:
30
+ npm install -D commitgate
31
+
32
+ # 2) Add config, contract, schemas, and the req:* scripts to your project:
33
+ npx commitgate init
34
+
27
35
  codex --version
28
36
  codex login status
29
37
  ```
30
38
 
39
+ > **Why two steps?** CommitGate does **not** copy its runtime code into your project. Step 1 puts the runtime in `node_modules/commitgate`; step 2 adds only **governance assets** (config, contract, schemas, persona) plus `req:* = commitgate <verb>` scripts.
40
+ > That means updating is a single `npm update commitgate`, and removing the runtime is `npm uninstall -D commitgate`.
41
+ > `init` **stops** if `devDependencies.commitgate` is not declared — there would be no runtime for `req:*` to point at.
42
+
43
+ Installation writes files but never commits them. `req:new` **requires a clean working tree**, so commit the scaffold first. The installer's `다음:` (next steps) output prints the exact paths to stage.
44
+
45
+ ```sh
46
+ git add -- <the paths the installer printed>
47
+ git status # confirm only what you intended is staged
48
+ git commit -m "chore: install commitgate"
49
+ ```
50
+
51
+ > **Do not stage everything (`-A` / `.`).** Unrelated changes and untracked files such as `.env` would be swept into the commit, and the next `req:review-codex` transmits that staged diff in full to an external service.
52
+ > Park any changes that predate the install **by pathspec** after the install commit: `git stash push -u -- <paths>`.
53
+ > Without `-u`, untracked files remain and `req:new` stays blocked; without the pathspec, a bare `git stash -u` also sweeps up directories that are not ignored, such as `node_modules/`. The installer prints that path list too.
54
+
31
55
  **You do not paste a long prompt.** Installation lays down agent entrypoints for you.
32
56
 
33
57
  | File | Read by |
@@ -114,24 +138,42 @@ CommitGate is designed to block **unreviewed changes from being committed**, not
114
138
 
115
139
  In short: **approved changes pass, ambiguous changes stop.**
116
140
 
141
+ ### What It Does *Not* Enforce
142
+
143
+ So that you do not miscalculate where your real defenses are:
144
+
145
+ - **This is not hard enforcement.** No git hook is installed, so running `git commit` directly instead of `req:commit` bypasses doctor, the approval binding, and the evidence trail. Your real defense for production is still CI and the deployment pipeline.
146
+ - **It does not keep your staged content secret.** `req:review-codex` transmits the full `git diff --cached` to Codex (OpenAI), and codex reads the repository root under `--sandbox read-only`. There is no masking, scrubbing, or size cap. For payment or credential-bearing codebases, write a "inspect the staged diff before review" step into your contract (`AGENTS.md`).
147
+ - **It does not guarantee anything after the commit.** Approval binds the staged tree at commit time; merge, tag, and publish are each separate control points.
148
+
117
149
  ---
118
150
 
119
151
  ## What Installation Adds
120
152
 
121
- `npx commitgate` adds the following to the target project. Existing files are not overwritten by default.
153
+ `npx commitgate init` adds the following to the target project. Existing files are not overwritten by default.
122
154
 
123
155
  | Added item | Purpose |
124
156
  |---|---|
125
- | `scripts/req/` | `req:new`, `req:next`, `req:review-codex`, `req:doctor`, `req:commit` scripts |
126
157
  | `workflow/*.schema.json` | Schemas for Codex responses and config |
127
- | `workflow/review-persona.md` | Reviewer persona injected into the Codex review prompt |
158
+ | `workflow/review-persona.md` | Reviewer persona injected into the Codex review prompt (created only if absent) |
128
159
  | `req.config.json` | Project-level configuration |
129
160
  | `AGENTS.md` | The contract (created only if absent) |
130
161
  | `CLAUDE.md` | Claude Code pointer (created only if absent) |
131
162
  | `.claude/skills/commitgate/SKILL.md` | Claude Code skill (pointer) |
132
163
  | `.claude/commands/req.md` | `/req` slash command (pointer) |
133
164
  | `.cursor/rules/commitgate.mdc` | Cursor rule (pointer) |
134
- | `package.json` scripts | `req:*` commands and required devDependencies |
165
+ | `package.json` scripts | `req:new`·`req:next`·`req:review-codex`·`req:doctor`·`req:commit` = `commitgate <verb>` (missing keys only) |
166
+
167
+ ### What it does **not** install
168
+
169
+ | Item | Where it lives instead |
170
+ |---|---|
171
+ | `scripts/req/**` runtime code | `node_modules/commitgate` — never copied into your project |
172
+ | `tsx` · `ajv` · `cross-spawn` | runtime dependencies of the `commitgate` package — never injected into your `package.json` |
173
+
174
+ What stays in your project is **governance and audit data** only: config, contract, schemas, persona, and `workflow/REQ-*` evidence. Because the runtime lives in the package, `npm update commitgate` updates it in one step and vendored copies cannot drift.
175
+
176
+ The `req:*` scripts call the installed package bin — `npm run req:new -- <slug>` → `commitgate req:new <slug>` → `node_modules/.bin/commitgate`.
135
177
 
136
178
  The entrypoint files are **thin pointers**. The contract itself lives only in `AGENTS.md`.
137
179
 
@@ -149,21 +191,64 @@ Preview without writing files:
149
191
  npx commitgate --dry-run
150
192
  ```
151
193
 
152
- Treat the security floor warning as an install failure:
194
+ Treat integrity warnings as an install failure:
153
195
 
154
196
  ```sh
155
197
  npx commitgate --strict
156
198
  ```
157
199
 
158
- If an existing `cross-spawn` is below the verified floor, CommitGate stops before copying files.
200
+ CommitGate stops **before writing any file** if a contract pointer would be swallowed by `.gitignore`, if the `workflow/.gitignore` policy file would not reach a fresh clone, if the working tree makes a safe install commit impossible, or if an existing `cross-spawn` is below the verified floor.
201
+
202
+ > `--strict` also treats the `package.json`/lockfile changes left by the required `npm install -D commitgate` as pre-existing dirt. Recommended order: `npm i -D commitgate` → **commit** → `npx commitgate init --strict` → commit the scaffold.
159
203
 
160
204
  > `workflow/machine.schema.json` and `workflow/req.config.schema.json` are always copied under `workflow/`, regardless of the `ticketRoot` setting in `req.config.json`.
161
205
 
162
206
  ---
163
207
 
208
+ ## Migrating from an older install (`migrate`)
209
+
210
+ If `scripts/req/` is copied into your project and `req:*` points at `tsx scripts/req/*.ts`, you have an **older (vendored) install**. `init` detects this and **stops** rather than creating a silent mix, pointing you here.
211
+
212
+ ```sh
213
+ npm install -D commitgate # first, if it is not a devDependency yet
214
+ npx commitgate migrate # plan only — writes nothing
215
+ npx commitgate migrate --apply # rewrites only the req:* scripts in package.json
216
+ ```
217
+
218
+ `migrate` does exactly **one** thing: it rewrites the `req:*` keys **whose current value is byte-for-byte the old injected value** to `commitgate <verb>`.
219
+
220
+ - **It deletes nothing.** `scripts/req/`, schemas, persona, config, entrypoints, and `workflow/REQ-*` evidence are all left in place. The leftover `scripts/req/` is no longer executed; run `npx commitgate uninstall` to see a cleanup plan first.
221
+ - **It never overwrites scripts you edited.** Any value that differs — even by one character — is treated as yours, preserved, and reported for manual action.
222
+ - **It does not commit.** It writes `package.json` only; reviewing is up to you.
223
+
224
+ `req:doctor` also reports the install mode (old / current / mixed).
225
+
226
+ ---
227
+
228
+ ## Support scope
229
+
230
+ | Environment | Status |
231
+ |---|---|
232
+ | **npm** | Fully supported — verified on every release by a packed-tarball smoke test |
233
+ | **pnpm · yarn** (`node_modules` linker) | Supported — uses the standard `node_modules/.bin/commitgate` resolution |
234
+ | **Yarn PnP** | **Not supported in this release** (untested). Use `nodeLinker: node-modules` |
235
+ | **workspaces / monorepo** | **Workspace-root installs** are supported (`req.config.json` and `workflow/` at the root). Installing independently in a sub-package is not supported |
236
+
237
+ **Reproducibility**: the review model/effort pins in `req.config.json`, plus the schemas and persona, stay in your project, so past review inputs are reproducible from git history. Runtime versions are pinned by your lockfile — **commit `package-lock.json`** (or the pnpm/yarn equivalent).
238
+
239
+ ---
240
+
164
241
  ## Removing CommitGate
165
242
 
166
- First, the important part: **`npx commitgate` is not a global install.** npx downloads the package into the npm cache (`_npx/<hash>/`) and runs it once; it leaves nothing in your global `node_modules` and nothing on your PATH. The real "installation" is the set of files added to your repo, plus the `package.json` changes listed above.
243
+ CommitGate lives in two places: the **runtime** (`node_modules/commitgate`) and the **governance files installed into your project**.
244
+
245
+ The package manager removes the runtime:
246
+
247
+ ```sh
248
+ npm uninstall -D commitgate # pnpm remove -D commitgate · yarn remove commitgate
249
+ ```
250
+
251
+ For the project files, review the plan below and clean up yourself. First, the important part: **`npx commitgate` is not a global install.** npx downloads the package into the npm cache (`_npx/<hash>/`) and runs it once; it leaves nothing in your global `node_modules` and nothing on your PATH.
167
252
 
168
253
  Start by previewing the removal plan. This command **deletes nothing**:
169
254
 
@@ -302,11 +387,14 @@ npm run req:commit -- 2026-001 --run --message-file commit-message.txt
302
387
 
303
388
  | Command | Purpose |
304
389
  |---|---|
305
- | `npx commitgate` | Install CommitGate into a project |
306
- | `npx commitgate --dry-run` | Preview the install plan without writing files |
307
- | `npx commitgate --strict` | Treat low `cross-spawn` version warnings as install failures |
308
- | `npx commitgate --no-agent-entrypoints` | Skip `.claude/`, `.cursor/`, and `CLAUDE.md` |
390
+ | `npm install -D commitgate` | **Install the runtime (required first)** — the executable code lives in `node_modules/commitgate` |
391
+ | `npx commitgate init` | Install config, contract, schemas, and the `req:*` scripts into a project |
392
+ | `npx commitgate init --dry-run` | Preview the install plan without writing files |
393
+ | `npx commitgate init --strict` | Treat integrity warnings as install failures — stops before writing any file |
394
+ | `npx commitgate init --no-agent-entrypoints` | Skip `.claude/`, `.cursor/`, and `CLAUDE.md` |
395
+ | `npx commitgate migrate [--apply]` | Move an older vendored install to the runtime package (plan-only by default, non-destructive) |
309
396
  | `npx commitgate uninstall` | Preview the removal plan (read-only — deletes nothing) |
397
+ | `npm uninstall -D commitgate` | Remove the runtime |
310
398
  | `npm run req:new -- <slug> --run` | Create a REQ ticket, branch, and design docs |
311
399
  | `npm run req:next -- <id> [--json]` | **Compute the next action** (read-only) |
312
400
  | `npm run req:review-codex -- <id> --kind design --run` | Review the design |
@@ -335,9 +423,15 @@ Defaults are enough for most projects. If needed, edit `req.config.json` in the
335
423
  | `packageManager` | auto-detected | `npm`, `pnpm`, or `yarn` |
336
424
  | `designDocs` | `00/01/02` docs | Design document filenames |
337
425
  | `reviewPersonaPath` | `"workflow/review-persona.md"` | First block of the review prompt. `null` disables it |
426
+ | `reviewModel` | `"gpt-5.6-terra"` | codex review model (pinned via `-c model=`). `null` inherits your global codex config |
427
+ | `reviewReasoningEffort` | `"high"` | codex review reasoning effort. One of `none`, `minimal`, `low`, `medium`, `high`, `xhigh`. `null` inherits the global setting |
338
428
 
339
429
  Empty `branchPrefix` values and paths that escape the project root are rejected.
340
430
 
431
+ **Pinned review model & effort**: `req:review-codex` injects `-c model=` and `-c model_reasoning_effort=` into the codex arguments to **pin the model and reasoning effort**. Without pinning, a review inherits your global `~/.codex/config.toml` (e.g. `model_reasoning_effort="ultra"`), making a single review take minutes and burn tokens. The defaults are `gpt-5.6-terra`/`high`; if your codex doesn't support that model, change it in `req.config.json` or set it to `null` to inherit the global config. Whether the overrides are actually honored can be checked with `npm run verify:overrides` (requires the codex CLI).
432
+
433
+ **Stateless re-reviews**: each re-review starts a **fresh codex thread** (it does not resume/accumulate the prior conversation — which drove token growth and goalpost drift). Only the previous same-target NEEDS_FIX findings are carried into the prompt as reference data, to confirm closure.
434
+
341
435
  ---
342
436
 
343
437
  ## FAQ
@@ -361,17 +455,18 @@ No. Existing files are skipped. Use `--force` if you intentionally want to refre
361
455
 
362
456
  ## Current Scope
363
457
 
364
- The current release is **Stage A: vendored scaffold model**. `npx commitgate` copies workflow files into the target project.
458
+ The current release is a **runtime package model**. Executable code and runtime dependencies live only in `node_modules/commitgate`; your project keeps governance/audit data and the `req:* = commitgate <verb>` scripts. (Older vendored installs move over with [`migrate`](#migrating-from-an-older-install-migrate).)
365
459
 
366
460
  Current verification:
367
461
 
368
462
  - GitHub Actions runs a `ubuntu-latest`, `macos-latest`, `windows-latest` × Node 18/20/22 matrix.
369
- - `npm run smoke` installs the packed tarball and runs the installed `commitgate` bin.
463
+ - `npm run smoke` installs the packed tarball into a throwaway project and asserts that the target has **no** `scripts/req/`, that `tsx`/`ajv`/`cross-spawn` are **not** injected, that all five `req:*` scripts point at the package bin, and that `npm run req:doctor` actually dispatches into the module inside the package. It verifies `migrate`'s non-destructiveness the same way.
370
464
  - A Windows `.cmd` wrapper injection regression test protects package-manager and Codex wrapper paths.
371
465
 
372
466
  Future scope:
373
467
 
374
- - Running directly from `node_modules` as a library-style model
468
+ - Yarn PnP support; independent installs in workspace sub-packages
469
+ - Asset↔runtime version drift detection
375
470
  - Non-git VCS support
376
471
  - More design document templates
377
472
 
package/README.md CHANGED
@@ -6,6 +6,11 @@
6
6
 
7
7
  AI 에이전트가 코드를 빠르게 만들더라도, 리뷰 없이 바로 커밋되면 위험합니다. CommitGate는 변경을 티켓 단위로 묶고, Codex가 승인한 staged tree만 커밋되게 합니다. 승인 후 코드가 바뀌거나 증거가 부족하면 기본적으로 막습니다.
8
8
 
9
+ > **⚠️ 시작하기 전에 두 가지를 알아 두세요.**
10
+ >
11
+ > 1. **리뷰는 staged diff를 외부로 전송합니다.** `req:review-codex`는 `git diff --cached` **전문**을 Codex(OpenAI)로 보냅니다. codex는 `--sandbox read-only`로 저장소 루트를 읽으므로 diff에 없는 파일도 읽힐 수 있습니다. 마스킹·필터·길이 상한은 **없습니다.** 리뷰 전에 staged 내용에 자격증명·토큰·개인정보가 없는지 확인하세요.
12
+ > 2. **git hook을 설치하지 않습니다.** `req:commit` 대신 `git commit`을 직접 치면 게이트·승인 바인딩·증거 기록이 전부 우회됩니다. CommitGate의 강제력은 **협조하는 에이전트를 계약 궤도에 유지하는 것**에 있지, 사람의 우회를 막는 데 있지 않습니다.
13
+
9
14
  [![CI](https://github.com/sol5288/commitgate/actions/workflows/ci.yml/badge.svg)](https://github.com/sol5288/commitgate/actions/workflows/ci.yml)
10
15
  [![npm version](https://img.shields.io/npm/v/commitgate.svg)](https://www.npmjs.com/package/commitgate)
11
16
  [![license](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
@@ -21,13 +26,32 @@ AI 에이전트가 코드를 빠르게 만들더라도, 리뷰 없이 바로 커
21
26
  git init
22
27
  npm init -y
23
28
 
24
- # 그다음 설치:
25
- npx commitgate
26
- npm install
29
+ # 1) CommitGate를 devDependency로 설치합니다 — 실행 코드가 여기 들어옵니다:
30
+ npm install -D commitgate
31
+
32
+ # 2) 프로젝트에 설정·계약·스키마와 req:* 스크립트를 깝니다:
33
+ npx commitgate init
34
+
27
35
  codex --version
28
36
  codex login status
29
37
  ```
30
38
 
39
+ > **왜 두 단계인가요?** CommitGate는 실행 코드를 프로젝트에 **복사하지 않습니다**. 1단계가 런타임을 `node_modules/commitgate`에 넣고, 2단계는 프로젝트에 **거버넌스 자산**(설정·계약·스키마·persona)과 `req:* = commitgate <verb>` 스크립트만 깝니다.
40
+ > 그래서 업데이트는 `npm update commitgate` 한 번이고, 런타임 제거는 `npm uninstall -D commitgate`입니다.
41
+ > `init`은 `devDependencies.commitgate` 선언이 없으면 **중단**합니다 — `req:*`가 가리킬 런타임이 없기 때문입니다.
42
+
43
+ 설치는 파일을 놓기만 하고 커밋하지 않습니다. `req:new`는 **clean 워킹트리를 요구**하므로, 설치분을 먼저 커밋하세요. 설치 출력의 `다음:` 안내가 stage할 정확한 경로 목록을 알려 줍니다.
44
+
45
+ ```sh
46
+ git add -- <설치 출력이 알려 준 경로들>
47
+ git status # 의도한 것만 staged 인지 눈으로 확인
48
+ git commit -m "chore: install commitgate"
49
+ ```
50
+
51
+ > **전체를 담는 stage(`-A` / `.`)를 쓰지 마세요.** 기존 프로젝트의 무관한 변경과 `.env` 같은 미추적 파일이 함께 커밋되고, 이어지는 `req:review-codex`가 그 staged diff 전문을 외부로 전송합니다.
52
+ > 설치 전부터 있던 무관한 변경은 설치 커밋 뒤에 **경로를 명시해** 치우세요: `git stash push -u -- <경로들>`.
53
+ > `-u` 없이는 untracked가 남아 `req:new`가 막히고, 경로 없이 `git stash -u`만 쓰면 `node_modules/`처럼 무시되지 않은 디렉터리까지 딸려 갑니다. 설치 출력이 그 경로 목록도 알려 줍니다.
54
+
31
55
  **긴 프롬프트를 붙여넣을 필요가 없습니다.** 설치가 에이전트 진입점을 함께 깝니다.
32
56
 
33
57
  | 파일 | 읽는 도구 |
@@ -114,24 +138,42 @@ CommitGate가 막는 것은 단순한 명령 실수가 아니라 **리뷰받지
114
138
 
115
139
  한 줄로 말하면, **확실히 승인된 변경만 통과하고 애매하면 멈추는 방식**입니다.
116
140
 
141
+ ### 보장하지 않는 것
142
+
143
+ 방어선을 잘못 계산하지 않도록, 이 도구가 **하지 않는 일**을 분명히 해 둡니다.
144
+
145
+ - **하드 강제가 아닙니다.** git hook을 설치하지 않으므로 `req:commit` 대신 `git commit`을 직접 치면 doctor·승인 바인딩·증거 기록이 전부 우회됩니다. 운영 반영의 실제 방어선은 여전히 CI와 배포 파이프라인입니다.
146
+ - **staged 내용의 비밀을 지켜 주지 않습니다.** `req:review-codex`는 `git diff --cached` 전문을 Codex(OpenAI)로 전송하고, codex는 `--sandbox read-only`로 저장소 루트를 읽습니다. 마스킹·스크러빙·길이 상한이 없습니다. 결제·자격증명처럼 민감한 코드베이스라면 리뷰 전 staged diff를 육안으로 확인하는 절차를 계약(`AGENTS.md`)에 명문화하세요.
147
+ - **커밋 이후를 보장하지 않습니다.** 승인은 커밋 시점의 staged tree에 대한 것이고, 머지·태그·publish는 각각 별도 통제점입니다.
148
+
117
149
  ---
118
150
 
119
151
  ## 설치가 하는 일
120
152
 
121
- `npx commitgate`는 대상 프로젝트에 아래 파일과 설정을 추가합니다. 기존 파일은 기본적으로 덮어쓰지 않습니다.
153
+ `npx commitgate init`은 대상 프로젝트에 아래 파일과 설정을 추가합니다. 기존 파일은 기본적으로 덮어쓰지 않습니다.
122
154
 
123
155
  | 추가 항목 | 설명 |
124
156
  |---|---|
125
- | `scripts/req/` | `req:new`, `req:next`, `req:review-codex`, `req:doctor`, `req:commit` 스크립트 |
126
157
  | `workflow/*.schema.json` | Codex 응답과 설정 검증 스키마 |
127
- | `workflow/review-persona.md` | Codex 리뷰 프롬프트에 주입되는 리뷰어 페르소나 |
158
+ | `workflow/review-persona.md` | Codex 리뷰 프롬프트에 주입되는 리뷰어 페르소나 (없을 때만 생성) |
128
159
  | `req.config.json` | 프로젝트별 설정 |
129
160
  | `AGENTS.md` | 계약 정본 (없을 때만 생성) |
130
161
  | `CLAUDE.md` | Claude Code 지침 포인터 (없을 때만 생성) |
131
162
  | `.claude/skills/commitgate/SKILL.md` | Claude Code 스킬 (포인터) |
132
163
  | `.claude/commands/req.md` | `/req` 슬래시 커맨드 (포인터) |
133
164
  | `.cursor/rules/commitgate.mdc` | Cursor 규칙 (포인터) |
134
- | `package.json` 스크립트 | `req:*` 명령과 필요한 devDependencies |
165
+ | `package.json` 스크립트 | `req:new`·`req:next`·`req:review-codex`·`req:doctor`·`req:commit` = `commitgate <verb>` (없는 키만) |
166
+
167
+ ### 설치하지 **않는** 것
168
+
169
+ | 항목 | 어디에 있나 |
170
+ |---|---|
171
+ | `scripts/req/**` 실행 코드 | `node_modules/commitgate` — 프로젝트에 복사하지 않습니다 |
172
+ | `tsx` · `ajv` · `cross-spawn` | `commitgate` 패키지의 runtime dependency — 대상 `package.json`에 주입하지 않습니다 |
173
+
174
+ 프로젝트에 남는 것은 **거버넌스·감사 데이터**(설정·계약·스키마·persona·`workflow/REQ-*` 증거)뿐입니다. 실행 코드는 패키지에만 있으므로 `npm update commitgate` 한 번으로 갱신되고, 복사본 버전이 갈라지지 않습니다.
175
+
176
+ `req:*` 스크립트는 설치된 패키지 bin을 호출합니다 — `npm run req:new -- <slug>` → `commitgate req:new <slug>` → `node_modules/.bin/commitgate`.
135
177
 
136
178
  진입점 파일들은 **얇은 포인터**입니다. 계약 본문은 `AGENTS.md` 하나에만 있습니다.
137
179
 
@@ -149,21 +191,69 @@ npx commitgate --no-agent-entrypoints
149
191
  npx commitgate --dry-run
150
192
  ```
151
193
 
152
- 보안 하한 경고를 설치 실패로 취급하려면:
194
+ 정합성 경고를 설치 실패로 취급하려면:
153
195
 
154
196
  ```sh
155
197
  npx commitgate --strict
156
198
  ```
157
199
 
158
- 기존 `cross-spawn`이 검증 하한보다 낮으면 파일을 복사하기 전에 중단합니다.
200
+ **파일을 하나도 쓰기 전에** 중단합니다. 대상은 다음과 같습니다.
201
+
202
+ - 계약 포인터(`.claude/`·`.cursor/`·`AGENTS.md`·`CLAUDE.md`)가 `.gitignore`에 걸려 팀·CI에 공유되지 않을 때
203
+ - `workflow/.gitignore` 정책 파일이 무시돼 fresh clone·CI에 scratch 규칙이 전달되지 않을 때
204
+ - 설치 전 워킹트리에 staged 변경이 있거나 설치 산출물과 겹치는 수정이 있어, 설치분만 담은 커밋을 만들 수 없을 때
205
+ - 기존 `cross-spawn`이 검증 하한보다 낮을 때(프로젝트가 그 패키지를 이미 쓰는 경우)
206
+
207
+ > `--strict`는 **선행 `npm install -D commitgate`가 남긴 `package.json`·lockfile 변경도** preexisting-dirty로 봅니다. 권장 순서: `npm i -D commitgate` → **커밋** → `npx commitgate init --strict` → 설치분 커밋.
159
208
 
160
209
  > `workflow/machine.schema.json`과 `workflow/req.config.schema.json`은 `req.config.json`의 `ticketRoot` 설정과 무관하게 **항상 `workflow/` 아래**에 복사됩니다.
161
210
 
162
211
  ---
163
212
 
213
+ ## 예전 설치본에서 옮겨오기 (`migrate`)
214
+
215
+ `scripts/req/`가 프로젝트에 복사돼 있고 `req:*`가 `tsx scripts/req/*.ts`를 가리킨다면 **예전(vendored) 설치본**입니다. `init`은 이 상태를 감지하면 조용히 섞이지 않도록 **중단하고** 이 명령을 안내합니다.
216
+
217
+ ```sh
218
+ npm install -D commitgate # 아직 devDependency가 아니라면 먼저
219
+ npx commitgate migrate # 계획만 출력 — 아무것도 쓰지 않습니다
220
+ npx commitgate migrate --apply # package.json 의 req:* 만 전환
221
+ ```
222
+
223
+ `migrate`가 하는 일은 **하나**입니다: `req:*` 중 **현재 값이 정확히 예전 주입값인 키만** `commitgate <verb>`로 바꿉니다.
224
+
225
+ - **아무것도 삭제하지 않습니다.** `scripts/req/`·스키마·persona·설정·진입점·`workflow/REQ-*` 증거를 전부 그대로 둡니다. 남은 `scripts/req/`는 더 이상 실행되지 않으니, 정리하려면 `npx commitgate uninstall` 계획을 먼저 확인하세요.
226
+ - **직접 고친 스크립트는 덮어쓰지 않습니다.** 값이 한 글자라도 다르면 사용자 값으로 보고 보존한 뒤 수동 조치를 안내합니다.
227
+ - **커밋하지 않습니다.** `package.json` 한 파일만 쓰고, 검토는 사용자 몫입니다.
228
+
229
+ `req:doctor`도 설치 모드(예전/현재/혼합)를 진단해 알려 줍니다.
230
+
231
+ ---
232
+
233
+ ## 지원 범위
234
+
235
+ | 환경 | 상태 |
236
+ |---|---|
237
+ | **npm** | 완전 지원 — 매 릴리스 packed tarball smoke로 검증합니다 |
238
+ | **pnpm · yarn** (`node_modules` linker) | 지원 — `node_modules/.bin/commitgate`로 해소되는 표준 경로를 씁니다 |
239
+ | **Yarn PnP** | **이번 릴리스 미지원**(검증하지 않았습니다). `nodeLinker: node-modules`를 쓰세요 |
240
+ | **workspace/monorepo** | **워크스페이스 root 설치**를 지원합니다(root에 `req.config.json`·`workflow/`). 하위 패키지에 독립 설치하는 배치는 미지원 |
241
+
242
+ **재현성**: `req.config.json`의 리뷰 모델·추론강도 핀과 스키마·persona가 프로젝트에 남아 과거 리뷰 입력이 git 이력으로 재현됩니다. 런타임 버전은 **lockfile이 고정**하므로 `package-lock.json`(pnpm/yarn은 각 lockfile)을 **커밋하세요**.
243
+
244
+ ---
245
+
164
246
  ## 제거하려면
165
247
 
166
- 먼저 알아둘 것: **`npx commitgate`는 전역 설치가 아닙니다.** npx는 패키지를 npm 캐시(`_npx/<hash>/`) 받아 실행할 뿐이고, 전역 `node_modules`에도 PATH에도 아무것도 남기지 않습니다. 실제 "설치물"은 위 표대로 **대상 repo에 추가된 파일과 `package.json` 변경**입니다.
248
+ CommitGate는 곳에 있습니다. **런타임**(`node_modules/commitgate`) **프로젝트에 깔린 거버넌스 파일**입니다.
249
+
250
+ 런타임은 package manager가 지웁니다:
251
+
252
+ ```sh
253
+ npm uninstall -D commitgate # pnpm remove -D commitgate · yarn remove commitgate
254
+ ```
255
+
256
+ 프로젝트 파일은 아래 계획을 보고 직접 정리하세요. 먼저 알아둘 것: **`npx commitgate`는 전역 설치가 아닙니다.** npx는 패키지를 npm 캐시(`_npx/<hash>/`)에 받아 한 번 실행할 뿐이고, 전역 `node_modules`에도 PATH에도 아무것도 남기지 않습니다.
167
257
 
168
258
  제거 계획을 먼저 확인하세요. 이 명령은 **아무것도 지우지 않고** 계획만 출력합니다:
169
259
 
@@ -302,11 +392,14 @@ npm run req:commit -- 2026-001 --run --message-file commit-message.txt
302
392
 
303
393
  | 명령 | 용도 |
304
394
  |---|---|
305
- | `npx commitgate` | 프로젝트에 CommitGate 설치 |
306
- | `npx commitgate --dry-run` | 파일을 쓰지 않고 설치 계획 확인 |
307
- | `npx commitgate --strict` | 낮은 `cross-spawn` 버전 경고를 설치 실패로 처리 |
308
- | `npx commitgate --no-agent-entrypoints` | `.claude/`·`.cursor/`·`CLAUDE.md` 설치 건너뛰기 |
395
+ | `npm install -D commitgate` | **런타임 설치 (선행 필수)** — 실행 코드가 `node_modules/commitgate`에 들어옵니다 |
396
+ | `npx commitgate init` | 프로젝트에 설정·계약·스키마와 `req:*` 스크립트 설치 |
397
+ | `npx commitgate init --dry-run` | 파일을 쓰지 않고 설치 계획 확인 |
398
+ | `npx commitgate init --strict` | 정합성 경고를 설치 실패로 처리 (gitignore된 계약 포인터, 설치 커밋을 안전하게 만들 수 없는 워킹트리 등) — 파일을 하나도 쓰기 전에 중단 |
399
+ | `npx commitgate init --no-agent-entrypoints` | `.claude/`·`.cursor/`·`CLAUDE.md` 설치 건너뛰기 |
400
+ | `npx commitgate migrate [--apply]` | 예전 vendored 설치본 → 런타임 패키지 전환 (기본: 계획만, 비파괴) |
309
401
  | `npx commitgate uninstall` | 제거 계획 확인 (읽기 전용 — 아무것도 지우지 않음) |
402
+ | `npm uninstall -D commitgate` | 런타임 제거 |
310
403
  | `npm run req:new -- <slug> --run` | REQ 티켓, 브랜치, 설계문서 생성 |
311
404
  | `npm run req:next -- <id> [--json]` | **다음 행동 계산** (읽기 전용) |
312
405
  | `npm run req:review-codex -- <id> --kind design --run` | 설계 리뷰 |
@@ -335,9 +428,15 @@ yarn req:next 2026-002 # yarn
335
428
  | `packageManager` | 자동 감지 | `npm`, `pnpm`, `yarn` |
336
429
  | `designDocs` | `00/01/02` 문서 | 설계 문서 파일명 |
337
430
  | `reviewPersonaPath` | `"workflow/review-persona.md"` | 리뷰 프롬프트 첫 블록. `null`이면 비활성 |
431
+ | `reviewModel` | `"gpt-5.6-terra"` | codex 리뷰 모델(`-c model=`로 고정). `null`이면 codex 전역 설정을 상속 |
432
+ | `reviewReasoningEffort` | `"high"` | codex 리뷰 추론강도. `none`·`minimal`·`low`·`medium`·`high`·`xhigh` 중 하나. `null`이면 전역 상속 |
338
433
 
339
434
  빈 `branchPrefix`나 프로젝트 밖으로 나가는 경로는 거부됩니다.
340
435
 
436
+ **리뷰 모델·추론강도 고정**: `req:review-codex`는 codex 인자에 `-c model=`·`-c model_reasoning_effort=`를 주입해 **모델과 추론강도를 고정**합니다. 고정하지 않으면 리뷰가 사용자 전역 `~/.codex/config.toml`(예: `model_reasoning_effort="ultra"`)을 상속해 리뷰 1회가 수 분·토큰 과다가 됩니다. 기본값은 `gpt-5.6-terra`/`high`이고, 프로젝트의 codex가 그 모델을 지원하지 않으면 `req.config.json`에서 바꾸거나 `null`로 두어 전역 설정을 상속시킵니다. override가 실제로 존중되는지는 `npm run verify:overrides`(codex CLI 필요)로 확인할 수 있습니다.
437
+
438
+ **재리뷰는 stateless**: 재리뷰는 매번 **새 codex 스레드**로 시작합니다(이전 대화를 resume해 누적하지 않음 — 토큰 증가와 findings 심화·이동을 막습니다). 직전 같은 대상의 NEEDS_FIX findings만 참고용으로 프롬프트에 담겨 해소 여부(closure)를 확인합니다.
439
+
341
440
  ---
342
441
 
343
442
  ## FAQ
@@ -361,17 +460,18 @@ yarn req:next 2026-002 # yarn
361
460
 
362
461
  ## 현재 범위
363
462
 
364
- 현재 버전은 **Stage A: vendored scaffold 모델**입니다. `npx commitgate`가 대상 프로젝트에 워크플로 파일을 복사합니다.
463
+ 현재 버전은 **런타임 패키지 모델**입니다. 실행 코드와 런타임 의존성은 `node_modules/commitgate`에만 있고, 프로젝트에는 거버넌스·감사 데이터와 `req:* = commitgate <verb>` 스크립트만 남습니다. (예전 vendored scaffold 설치본은 [`migrate`](#예전-설치본에서-옮겨오기-migrate)로 전환합니다.)
365
464
 
366
465
  현재 운영 중인 검증입니다.
367
466
 
368
467
  - GitHub Actions에서 `ubuntu-latest`, `macos-latest`, `windows-latest` × Node 18/20/22 매트릭스를 실행합니다.
369
- - `npm run smoke`는 pack tarball 설치본의 `commitgate` bin을 실행합니다.
468
+ - `npm run smoke`는 pack tarball 임시 프로젝트에 실제로 설치해, 대상에 `scripts/req/`가 **없고** `tsx`·`ajv`·`cross-spawn`이 **주입되지 않으며** 다섯 `req:*`가 패키지 bin을 가리키는지, 그리고 `npm run req:doctor`가 실제로 패키지 안의 모듈까지 dispatch되는지 확인합니다. `migrate` 비파괴성도 같은 방식으로 검증합니다.
370
469
  - Windows `.cmd` 래퍼 주입 회귀 테스트가 패키지 매니저와 Codex wrapper 경로를 보호합니다.
371
470
 
372
471
  아래는 후속 범위입니다.
373
472
 
374
- - `node_modules`에서 직접 실행하는 라이브러리 모델
473
+ - Yarn PnP 지원, 워크스페이스 하위 패키지 독립 설치
474
+ - 자산↔런타임 버전 드리프트 탐지
375
475
  - 비-git VCS 지원
376
476
  - 더 다양한 설계문서 템플릿
377
477
 
@@ -10,20 +10,29 @@
10
10
  * (node:module의 register('tsx/esm')는 tsx v4가 deprecated --loader로 간주해 거부 → tsx 자체 API 사용.)
11
11
  * (Stage B에서 init.ts를 JS로 빌드하면 이 런처는 제거 가능.)
12
12
  *
13
- * verb dispatch(REQ-2026-007): `uninstall`만 별도 모듈로 보낸다. verb 없는 호출은 **현행 그대로 init**(하위호환).
14
- * dispatch를 여기(단일 bin 진입점) 두면 uninstall.ts init.ts 단방향 import가 되어 순환이 생기지 않는다.
13
+ * verb dispatch(REQ-2026-014 Stage B, 설계 D3): 로컬 패키지 bin이 `req:*`를 dispatch한다.
14
+ * - 알려진 verb(`req:new`/`req:next`/`req:review-codex`/`req:doctor`/`req:commit`/`uninstall`/`init`) → 해당 모듈(verb 토큰 소비).
15
+ * - argv 없음 **또는 첫 인자가 `-` 옵션**(`--dry-run`·`--dir`·`--strict`·`--force`·`--no-agent-entrypoints`·`-h`) → **init에 argv 전체 전달**(하위호환).
16
+ * - 그 외 비-옵션 미지 토큰 → fail-closed(오타를 조용히 init으로 보내지 않는다). `migrate`는 Phase 3에서 등록.
17
+ * 각 대상 모듈은 `runCli(argv)`(예외→친절한 1줄+exit1 경계)를 export한다. import되면 대상의 `if (isMain)` 가드는 발화하지 않으므로 중복 실행 없음.
15
18
  */
16
19
  import { register } from 'tsx/esm/api'
17
20
  import { fileURLToPath, pathToFileURL } from 'node:url'
18
21
  import { dirname, join } from 'node:path'
22
+ import { resolveDispatch } from './dispatch.mjs'
19
23
 
20
24
  register()
21
25
 
22
26
  const binDir = dirname(fileURLToPath(import.meta.url))
23
27
  const argv = process.argv.slice(2)
24
- const entry = argv[0] === 'uninstall' ? 'uninstall.ts' : 'init.ts'
25
- const rest = argv[0] === 'uninstall' ? argv.slice(1) : argv
26
28
 
27
- const mod = await import(pathToFileURL(join(binDir, entry)).href)
29
+ const decision = resolveDispatch(argv)
30
+ if ('unknown' in decision) {
31
+ // 비-옵션 미지 토큰: fail-closed(스택트레이스 없이 한 줄).
32
+ console.error(`commitgate: 알 수 없는 명령: ${decision.unknown}`)
33
+ process.exit(1)
34
+ }
35
+
36
+ const mod = await import(pathToFileURL(join(binDir, decision.entry)).href)
28
37
  // runCli = 예외를 친절한 한 줄 메시지 + exit 1로 변환하는 CLI 경계(스택트레이스 노출 방지).
29
- mod.runCli(rest)
38
+ mod.runCli(decision.rest)