commitgate 0.3.1 → 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.
- package/AGENTS.template.md +17 -0
- package/CHANGELOG.md +115 -0
- package/README.en.md +194 -49
- package/README.md +201 -51
- package/bin/commitgate.mjs +15 -6
- package/bin/dispatch.d.mts +6 -0
- package/bin/dispatch.mjs +38 -0
- package/bin/init.ts +916 -57
- package/bin/migrate.ts +244 -0
- package/bin/uninstall.ts +97 -13
- package/package.json +10 -4
- package/req.config.json.sample +16 -13
- package/scripts/req/lib/adapters.ts +56 -8
- package/scripts/req/lib/config.ts +57 -1
- package/scripts/req/lib/porcelain.ts +104 -0
- package/scripts/req/lib/scratch.ts +104 -0
- package/scripts/req/req-commit.ts +16 -3
- package/scripts/req/req-doctor.ts +135 -31
- package/scripts/req/req-new.ts +60 -10
- package/scripts/req/req-next.ts +654 -0
- package/scripts/req/review-codex.ts +349 -73
- package/scripts/verify-review-overrides.mjs +96 -0
- package/templates/CLAUDE.template.md +16 -0
- package/templates/claude-command.md +39 -0
- package/templates/claude-skill.md +62 -0
- package/templates/cursor-rule.mdc +58 -0
- package/templates/workflow.gitignore +8 -0
- package/workflow/machine.schema.json +5 -1
- package/workflow/req.config.schema.json +6 -0
- package/workflow/review-persona.md +66 -0
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
|
[](https://github.com/sol5288/commitgate/actions/workflows/ci.yml)
|
|
10
15
|
[](https://www.npmjs.com/package/commitgate)
|
|
11
16
|
[](./LICENSE)
|
|
@@ -21,44 +26,56 @@ AI 에이전트가 코드를 빠르게 만들더라도, 리뷰 없이 바로 커
|
|
|
21
26
|
git init
|
|
22
27
|
npm init -y
|
|
23
28
|
|
|
24
|
-
#
|
|
25
|
-
|
|
26
|
-
|
|
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
|
|
|
31
|
-
|
|
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
|
+
|
|
55
|
+
**긴 프롬프트를 붙여넣을 필요가 없습니다.** 설치가 에이전트 진입점을 함께 깝니다.
|
|
56
|
+
|
|
57
|
+
| 파일 | 읽는 도구 |
|
|
58
|
+
|---|---|
|
|
59
|
+
| `AGENTS.md` | Codex CLI, Cursor — **계약 정본** |
|
|
60
|
+
| `.claude/skills/commitgate/SKILL.md` | Claude Code (요청에 맞으면 자동 발동) |
|
|
61
|
+
| `.claude/commands/req.md` | Claude Code (`/req` 명시 호출) |
|
|
62
|
+
| `.cursor/rules/commitgate.mdc` | Cursor (`alwaysApply`) |
|
|
63
|
+
| `CLAUDE.md` | Claude Code (항상 로드) — 부재 시에만 생성 |
|
|
64
|
+
|
|
65
|
+
에이전트에게 요구사항만 주면 됩니다.
|
|
32
66
|
|
|
33
67
|
```text
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
- `req:review-codex`가 NEEDS_FIX/exit 3을 반환하면 findings를 수정하고 재리뷰한다.
|
|
41
|
-
- BLOCKED/exit 2를 반환하면 같은 리뷰를 재시도하지 말고 사람에게 보고하거나 리뷰 대상을 바꾼다. 스레드 고착이 의심되면 `--fresh-thread`로 한 번만 회복을 시도할 수 있다.
|
|
42
|
-
- 리뷰 대상은 git add 한 파일만이다.
|
|
43
|
-
- `state.json`과 `responses/`는 직접 `git add`하지 않는다.
|
|
44
|
-
|
|
45
|
-
멈춰서 확인받을 때(각 항목은 그 문장 그대로 승인받아야 하며, 한 승인은 다음 단계로 이월되지 않는다):
|
|
46
|
-
- req:commit --run 직전
|
|
47
|
-
- [경로 A · 선택] [I1] feature branch push + PR 생성 직전 / [I2] required checks green 확인 후 PR merge 직전
|
|
48
|
-
- [경로 B] [B1] protected branch에 direct push 직전 — "branch protection bypass를 사용한 direct push 승인"을 따로 받는다. 이 push는 required checks를 우회하고, CI는 사후에 돈다
|
|
49
|
-
- [R1/R2/R3] tag 생성·push / npm publish / GitHub release — CI green 확인 후 각각 별도 승인
|
|
50
|
-
- reset, clean, force push 같은 destructive 작업 전
|
|
51
|
-
- 요구사항 범위를 바꿔야 할 때
|
|
52
|
-
- Codex 리뷰가 BLOCKED를 반환하거나 제한된 재시도 후에도 판단이 불명확할 때
|
|
53
|
-
|
|
54
|
-
요구사항:
|
|
55
|
-
- 무엇을:
|
|
56
|
-
- 왜:
|
|
57
|
-
- 제약:
|
|
58
|
-
- 완료 기준:
|
|
68
|
+
/req 프로필 수정 API를 추가해줘
|
|
69
|
+
|
|
70
|
+
- 무엇을: PATCH /profile 로 닉네임·소개글 수정
|
|
71
|
+
- 왜: 지금은 가입 후 프로필을 바꿀 방법이 없다
|
|
72
|
+
- 제약: 기존 인증 미들웨어 재사용, 스키마 변경 없음
|
|
73
|
+
- 완료 기준: 단위 테스트 통과, 권한 없는 사용자는 403
|
|
59
74
|
```
|
|
60
75
|
|
|
61
|
-
|
|
76
|
+
Claude Code가 아니면 슬래시 커맨드 없이 요구사항만 주어도 됩니다(`.cursor/rules`·`AGENTS.md`가 규칙을 로드합니다). 네 칸이 비어 있으면 에이전트가 먼저 물어봅니다.
|
|
77
|
+
|
|
78
|
+
첫 응답은 보통 이렇게 나옵니다.
|
|
62
79
|
|
|
63
80
|
```text
|
|
64
81
|
REQ-2026-002 발행
|
|
@@ -69,7 +86,38 @@ phase:
|
|
|
69
86
|
통제점: req:commit --run 직전 / [B1] main direct push 직전 (또는 [I1] PR 생성 → [I2] merge)
|
|
70
87
|
```
|
|
71
88
|
|
|
72
|
-
|
|
89
|
+
### 에이전트는 `req:next`가 시키는 대로 진행합니다
|
|
90
|
+
|
|
91
|
+
다음 행동을 에이전트가 추측하지 않습니다. 도구가 `state.json`과 git 상태에서 계산합니다.
|
|
92
|
+
|
|
93
|
+
```sh
|
|
94
|
+
npm run req:next -- 2026-002
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
```text
|
|
98
|
+
[req:next] RUN REQ-2026-002
|
|
99
|
+
phase `phase-1`의 staged 변경을 리뷰받는다.
|
|
100
|
+
|
|
101
|
+
$ npm run req:review-codex -- 2026-002 --kind phase --phase phase-1 --run
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
| kind | 뜻 | exit |
|
|
105
|
+
|---|---|---|
|
|
106
|
+
| `RUN` | 출력된 명령을 그대로 실행하고 다시 `req:next` | 0 |
|
|
107
|
+
| `AGENT` | 도구가 대신 못 하는 작업(구현·문서 작성·`git add`) | 0 |
|
|
108
|
+
| `AWAIT_HUMAN` | **통제점** — 출력된 승인 문장을 그대로 받기 전엔 진행 금지 | 10 |
|
|
109
|
+
| `DONE` | 이 티켓에서 도구가 할 일 없음. 통합은 별도 통제점 | 11 |
|
|
110
|
+
| `BLOCKED` | 사람에게 보고. 같은 리뷰 재시도 금지 | 2 |
|
|
111
|
+
|
|
112
|
+
`--json`으로 기계 판독할 수 있습니다. **읽기 전용**이라 어떤 상태도 바꾸지 않습니다.
|
|
113
|
+
|
|
114
|
+
이 루프를 끊지 말고 반복하면 설계 → Codex 리뷰 → 구현 → 재리뷰 → 커밋이 진행됩니다. 사용자는 `AWAIT_HUMAN`에서만 확인하면 됩니다.
|
|
115
|
+
|
|
116
|
+
### 리뷰어 페르소나는 도구가 주입합니다
|
|
117
|
+
|
|
118
|
+
`req:review-codex`는 `workflow/review-persona.md`를 프롬프트 **첫 블록**으로 넣습니다. 사람이 직접 실행하든, Cursor가 실행하든, Claude가 실행하든 동일합니다 — 에이전트가 잊을 수 있는 자리에 두지 않습니다. 파일이 없거나 비어 있으면 리뷰가 fail-closed로 멈춥니다.
|
|
119
|
+
|
|
120
|
+
내용을 프로젝트에 맞게 고치거나, `req.config.json`의 `reviewPersonaPath`로 다른 파일을 지정할 수 있습니다. `null`로 두면 비활성화됩니다.
|
|
73
121
|
|
|
74
122
|
main에 반영하는 경로는 **PR 경유(선택)**와 **direct push** 둘 다 유효합니다. PR은 의무가 아닙니다. 다만 protected branch로 직접 push하면 required checks를 **우회**하므로 "branch protection bypass를 사용한 direct push 승인"을 따로 받아야 합니다 — bypass 권한이 있다는 사실은 승인이 아닙니다. 그리고 이때 CI는 push **이후에** 도는 **사후 검증**이라, 그 사실을 보고에서 생략하지 않습니다. tag, npm publish, GitHub release는 반영과 묶이지 않는 별도 통제점이고 CI green 이후에 요청합니다. 자세한 계약은 [AGENTS.template.md](AGENTS.template.md)와 [docs/RELEASING.md](docs/RELEASING.md)를 참고하세요.
|
|
75
123
|
|
|
@@ -90,19 +138,52 @@ CommitGate가 막는 것은 단순한 명령 실수가 아니라 **리뷰받지
|
|
|
90
138
|
|
|
91
139
|
한 줄로 말하면, **확실히 승인된 변경만 통과하고 애매하면 멈추는 방식**입니다.
|
|
92
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
|
+
|
|
93
149
|
---
|
|
94
150
|
|
|
95
151
|
## 설치가 하는 일
|
|
96
152
|
|
|
97
|
-
`npx commitgate
|
|
153
|
+
`npx commitgate init`은 대상 프로젝트에 아래 파일과 설정을 추가합니다. 기존 파일은 기본적으로 덮어쓰지 않습니다.
|
|
98
154
|
|
|
99
155
|
| 추가 항목 | 설명 |
|
|
100
156
|
|---|---|
|
|
101
|
-
| `scripts/req/` | `req:new`, `req:review-codex`, `req:doctor`, `req:commit` 스크립트 |
|
|
102
157
|
| `workflow/*.schema.json` | Codex 응답과 설정 검증 스키마 |
|
|
158
|
+
| `workflow/review-persona.md` | Codex 리뷰 프롬프트에 주입되는 리뷰어 페르소나 (없을 때만 생성) |
|
|
103
159
|
| `req.config.json` | 프로젝트별 설정 |
|
|
104
|
-
| `AGENTS.md` |
|
|
105
|
-
| `
|
|
160
|
+
| `AGENTS.md` | 계약 정본 (없을 때만 생성) |
|
|
161
|
+
| `CLAUDE.md` | Claude Code 지침 포인터 (없을 때만 생성) |
|
|
162
|
+
| `.claude/skills/commitgate/SKILL.md` | Claude Code 스킬 (포인터) |
|
|
163
|
+
| `.claude/commands/req.md` | `/req` 슬래시 커맨드 (포인터) |
|
|
164
|
+
| `.cursor/rules/commitgate.mdc` | Cursor 규칙 (포인터) |
|
|
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`.
|
|
177
|
+
|
|
178
|
+
진입점 파일들은 **얇은 포인터**입니다. 계약 본문은 `AGENTS.md` 하나에만 있습니다.
|
|
179
|
+
|
|
180
|
+
`.claude/`·`.cursor/`를 다른 도구가 쓰고 있다면 건너뛸 수 있습니다.
|
|
181
|
+
|
|
182
|
+
```sh
|
|
183
|
+
npx commitgate --no-agent-entrypoints
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
기존 `AGENTS.md`가 있는데 CommitGate 계약 마커(`<!-- commitgate:contract -->`)가 없으면, 계약 템플릿을 `AGENTS.commitgate.md`로 함께 놓고 병합을 안내합니다. 기존 파일은 건드리지 않습니다.
|
|
106
187
|
|
|
107
188
|
미리보기만 하려면:
|
|
108
189
|
|
|
@@ -110,21 +191,69 @@ CommitGate가 막는 것은 단순한 명령 실수가 아니라 **리뷰받지
|
|
|
110
191
|
npx commitgate --dry-run
|
|
111
192
|
```
|
|
112
193
|
|
|
113
|
-
|
|
194
|
+
정합성 경고를 설치 실패로 취급하려면:
|
|
114
195
|
|
|
115
196
|
```sh
|
|
116
197
|
npx commitgate --strict
|
|
117
198
|
```
|
|
118
199
|
|
|
119
|
-
|
|
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` → 설치분 커밋.
|
|
120
208
|
|
|
121
209
|
> `workflow/machine.schema.json`과 `workflow/req.config.schema.json`은 `req.config.json`의 `ticketRoot` 설정과 무관하게 **항상 `workflow/` 아래**에 복사됩니다.
|
|
122
210
|
|
|
123
211
|
---
|
|
124
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
|
+
|
|
125
246
|
## 제거하려면
|
|
126
247
|
|
|
127
|
-
|
|
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에도 아무것도 남기지 않습니다.
|
|
128
257
|
|
|
129
258
|
제거 계획을 먼저 확인하세요. 이 명령은 **아무것도 지우지 않고** 계획만 출력합니다:
|
|
130
259
|
|
|
@@ -163,7 +292,7 @@ git checkout HEAD -- package.json
|
|
|
163
292
|
|
|
164
293
|
파일 삭제는 `npx commitgate uninstall`이 나열해 준 경로만 지우세요. `scripts/req/`나 `workflow/`를 디렉터리째 지우면 그 안의 사용자 파일이나 티켓 증거가 함께 사라집니다.
|
|
165
294
|
|
|
166
|
-
> git은 빈 디렉터리를 추적하지 않습니다. 파일을 다 지운 뒤 `git status`가 clean이어도 빈 `scripts/`·`workflow/`가 파일시스템에 남을 수 있습니다.
|
|
295
|
+
> git은 빈 디렉터리를 추적하지 않습니다. 파일을 다 지운 뒤 `git status`가 clean이어도 빈 `scripts/`·`workflow/`·`.claude/`·`.cursor/`가 파일시스템에 남을 수 있습니다.
|
|
167
296
|
|
|
168
297
|
### 이미 커밋했다면
|
|
169
298
|
|
|
@@ -224,7 +353,7 @@ Windows에서 설치 직후 `codex` 명령을 못 찾으면 새 터미널을 열
|
|
|
224
353
|
|
|
225
354
|
## 수동 명령
|
|
226
355
|
|
|
227
|
-
대부분의 사용자는
|
|
356
|
+
대부분의 사용자는 `req:next`가 시키는 대로만 하면 됩니다. 아래는 내부에서 어떤 명령이 실행되는지 이해하거나 직접 디버깅할 때만 보면 됩니다.
|
|
228
357
|
|
|
229
358
|
```sh
|
|
230
359
|
# 1. 티켓과 브랜치 생성
|
|
@@ -263,15 +392,28 @@ npm run req:commit -- 2026-001 --run --message-file commit-message.txt
|
|
|
263
392
|
|
|
264
393
|
| 명령 | 용도 |
|
|
265
394
|
|---|---|
|
|
266
|
-
| `
|
|
267
|
-
| `npx commitgate
|
|
268
|
-
| `npx commitgate --
|
|
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 설치본 → 런타임 패키지 전환 (기본: 계획만, 비파괴) |
|
|
269
401
|
| `npx commitgate uninstall` | 제거 계획 확인 (읽기 전용 — 아무것도 지우지 않음) |
|
|
270
|
-
| `
|
|
271
|
-
| `req:
|
|
272
|
-
| `req:
|
|
273
|
-
| `req:
|
|
274
|
-
| `req:
|
|
402
|
+
| `npm uninstall -D commitgate` | 런타임 제거 |
|
|
403
|
+
| `npm run req:new -- <slug> --run` | REQ 티켓, 브랜치, 설계문서 생성 |
|
|
404
|
+
| `npm run req:next -- <id> [--json]` | **다음 행동 계산** (읽기 전용) |
|
|
405
|
+
| `npm run req:review-codex -- <id> --kind design --run` | 설계 리뷰 |
|
|
406
|
+
| `npm run req:review-codex -- <id> --kind phase --phase <p> --run` | 구현 리뷰 |
|
|
407
|
+
| `npm run req:doctor -- <id>` | 게이트 상태 확인 |
|
|
408
|
+
| `npm run req:commit -- <id> --run -m "message"` | 승인된 변경 커밋 |
|
|
409
|
+
|
|
410
|
+
`req:*`는 PATH에 잡히는 실행 파일이 아니라 **`package.json` 스크립트**입니다. npm은 인자 전달에 `--` 구분자가 필요합니다.
|
|
411
|
+
|
|
412
|
+
```sh
|
|
413
|
+
npm run req:next -- 2026-002 # npm
|
|
414
|
+
pnpm req:next 2026-002 # pnpm
|
|
415
|
+
yarn req:next 2026-002 # yarn
|
|
416
|
+
```
|
|
275
417
|
|
|
276
418
|
---
|
|
277
419
|
|
|
@@ -285,9 +427,16 @@ npm run req:commit -- 2026-001 --run --message-file commit-message.txt
|
|
|
285
427
|
| `ticketRoot` | `"workflow"` | REQ 티켓 폴더 |
|
|
286
428
|
| `packageManager` | 자동 감지 | `npm`, `pnpm`, `yarn` |
|
|
287
429
|
| `designDocs` | `00/01/02` 문서 | 설계 문서 파일명 |
|
|
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`이면 전역 상속 |
|
|
288
433
|
|
|
289
434
|
빈 `branchPrefix`나 프로젝트 밖으로 나가는 경로는 거부됩니다.
|
|
290
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
|
+
|
|
291
440
|
---
|
|
292
441
|
|
|
293
442
|
## FAQ
|
|
@@ -311,17 +460,18 @@ npm run req:commit -- 2026-001 --run --message-file commit-message.txt
|
|
|
311
460
|
|
|
312
461
|
## 현재 범위
|
|
313
462
|
|
|
314
|
-
현재 버전은
|
|
463
|
+
현재 버전은 **런타임 패키지 모델**입니다. 실행 코드와 런타임 의존성은 `node_modules/commitgate`에만 있고, 프로젝트에는 거버넌스·감사 데이터와 `req:* = commitgate <verb>` 스크립트만 남습니다. (예전 vendored scaffold 설치본은 [`migrate`](#예전-설치본에서-옮겨오기-migrate)로 전환합니다.)
|
|
315
464
|
|
|
316
465
|
현재 운영 중인 검증입니다.
|
|
317
466
|
|
|
318
467
|
- GitHub Actions에서 `ubuntu-latest`, `macos-latest`, `windows-latest` × Node 18/20/22 매트릭스를 실행합니다.
|
|
319
|
-
- `npm run smoke`는 pack tarball
|
|
468
|
+
- `npm run smoke`는 pack tarball을 임시 프로젝트에 실제로 설치해, 대상에 `scripts/req/`가 **없고** `tsx`·`ajv`·`cross-spawn`이 **주입되지 않으며** 다섯 `req:*`가 패키지 bin을 가리키는지, 그리고 `npm run req:doctor`가 실제로 패키지 안의 모듈까지 dispatch되는지 확인합니다. `migrate` 비파괴성도 같은 방식으로 검증합니다.
|
|
320
469
|
- Windows `.cmd` 래퍼 주입 회귀 테스트가 패키지 매니저와 Codex wrapper 경로를 보호합니다.
|
|
321
470
|
|
|
322
471
|
아래는 후속 범위입니다.
|
|
323
472
|
|
|
324
|
-
-
|
|
473
|
+
- Yarn PnP 지원, 워크스페이스 하위 패키지 독립 설치
|
|
474
|
+
- 자산↔런타임 버전 드리프트 탐지
|
|
325
475
|
- 비-git VCS 지원
|
|
326
476
|
- 더 다양한 설계문서 템플릿
|
|
327
477
|
|
package/bin/commitgate.mjs
CHANGED
|
@@ -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-
|
|
14
|
-
*
|
|
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
|
|
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)
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** 타입 선언 — `bin/dispatch.mjs`(런타임은 tsx 없이 로드돼야 하므로 순수 .mjs로 유지)의 타입. */
|
|
2
|
+
export declare const VERB_MODULES: Record<string, string>
|
|
3
|
+
|
|
4
|
+
export type DispatchDecision = { entry: string; rest: string[] } | { unknown: string }
|
|
5
|
+
|
|
6
|
+
export declare function resolveDispatch(argv: string[]): DispatchDecision
|
package/bin/dispatch.mjs
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* commitgate bin verb dispatch — 순수 결정 로직(부작용 없음, 테스트 가능).
|
|
3
|
+
*
|
|
4
|
+
* `bin/commitgate.mjs`(register tsx·동적 import 부작용 포함)와 `tests/unit/dispatch.test.ts`가 공유한다.
|
|
5
|
+
* 설계 D3(REQ-2026-014 Stage B):
|
|
6
|
+
* - 알려진 verb → 해당 모듈(verb 토큰 소비).
|
|
7
|
+
* - argv 없음 **또는 첫 인자가 `-` 옵션** → init에 argv 전체 전달(하위호환: `npx commitgate --dry-run` 등).
|
|
8
|
+
* - 그 외 비-옵션 미지 토큰 → `unknown`(호출부가 fail-closed).
|
|
9
|
+
*
|
|
10
|
+
* `migrate`는 **파일 생성과 동시에**(Phase 3) 등록했다. Phase 1이 미리 등록하지 않은 이유는, 등록만 하고 모듈이 없으면
|
|
11
|
+
* 깨진 동적 import가 raw unhandled rejection으로 터지기 때문이다(`unknown` 분기의 친절한 1줄 오류가 낫다).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** verb → 대상 모듈(binDir 기준 상대). req:* 는 패키지의 scripts/req/*.ts 에서 실행(Stage B: 대상 프로젝트에 복사하지 않음). */
|
|
15
|
+
export const VERB_MODULES = {
|
|
16
|
+
'req:new': '../scripts/req/req-new.ts',
|
|
17
|
+
'req:next': '../scripts/req/req-next.ts',
|
|
18
|
+
'req:review-codex': '../scripts/req/review-codex.ts',
|
|
19
|
+
'req:doctor': '../scripts/req/req-doctor.ts',
|
|
20
|
+
'req:commit': '../scripts/req/req-commit.ts',
|
|
21
|
+
uninstall: 'uninstall.ts',
|
|
22
|
+
migrate: 'migrate.ts',
|
|
23
|
+
init: 'init.ts',
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* argv → { entry, rest } 또는 { unknown }.
|
|
28
|
+
* @param {string[]} argv process.argv.slice(2)
|
|
29
|
+
* @returns {{ entry: string, rest: string[] } | { unknown: string }}
|
|
30
|
+
*/
|
|
31
|
+
export function resolveDispatch(argv) {
|
|
32
|
+
const verb = argv[0]
|
|
33
|
+
// verb 없음 / init 옵션(`-`로 시작) → init에 argv 전체 전달(D3).
|
|
34
|
+
if (verb === undefined || verb.startsWith('-')) return { entry: 'init.ts', rest: argv }
|
|
35
|
+
if (Object.prototype.hasOwnProperty.call(VERB_MODULES, verb)) return { entry: VERB_MODULES[verb], rest: argv.slice(1) }
|
|
36
|
+
// 비-옵션 미지 토큰: 호출부가 fail-closed 처리(오타를 조용히 init으로 보내지 않는다).
|
|
37
|
+
return { unknown: verb }
|
|
38
|
+
}
|