commitgate 0.3.0 → 0.4.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.
@@ -1,8 +1,12 @@
1
+ <!-- commitgate:contract -->
1
2
  # AGENTS.md (AI REQ workflow — Codex/Builder 계약)
2
3
 
3
4
  > 이 파일은 `commitgate`(init)가 대상 repo에 `AGENTS.md`가 없을 때 생성한 **템플릿**이다.
4
5
  > 프로젝트 고유 규칙(SSOT 경로·포트·참조 프로젝트·phase 문서 등)은 여기에 직접 채워 넣어라.
5
6
  > Codex CLI는 repo 루트의 `AGENTS.md`를 리뷰 컨텍스트로 읽는다.
7
+ >
8
+ > ⚠️ 첫 줄의 `<!-- commitgate:contract -->` 마커를 지우지 마라. `.claude/`·`.cursor/` 진입점 파일이
9
+ > "이 `AGENTS.md`가 CommitGate 계약인가"를 그 마커로 판별하고, `npx commitgate`도 부재 시 경고한다.
6
10
 
7
11
  ## 역할
8
12
 
@@ -78,8 +82,13 @@ protected branch에 변경을 넣는 경로는 **두 가지**이고 **둘 다
78
82
  | 명령 | 용도 |
79
83
  |---|---|
80
84
  | `req:new <slug> --run` | REQ 티켓 생성(채번·브랜치·`00/01/02` 스캐폴드) |
85
+ | `req:next <id> [--json]` | **다음 행동 계산**(읽기 전용). `RUN`/`AGENT`/`AWAIT_HUMAN`/`DONE`/`BLOCKED` |
81
86
  | `req:review-codex <id> --kind design\|phase [--phase <p>] --run` | Codex 리뷰 조립·호출·승인 반영 |
82
87
  | `req:doctor <id>` | 일관성 게이트(D-체크) |
83
88
  | `req:commit <id> [--run] [--message-file <f>]` | 승인된 phase 커밋 + evidence-finalize |
84
89
 
90
+ **다음 행동을 추측하지 마라.** `req:next`가 state와 git에서 계산해 준다. `RUN`이면 그 명령을 그대로 실행하고, `AGENT`면 그 작업을 한 뒤 `git add` 하고, `AWAIT_HUMAN`이면 멈춰서 출력된 승인 문장을 그대로 받는다. 이 루프를 끊지 말고 반복한다.
91
+
92
+ **리뷰어 페르소나는 도구가 주입한다.** `req:review-codex`가 `workflow/review-persona.md`를 프롬프트 첫 블록으로 넣는다(`req.config.json`의 `reviewPersonaPath`). 사람이 직접 실행하든 다른 에이전트가 실행하든 동일하다 — 프롬프트에 손으로 붙여넣지 마라. 파일이 없거나 비어 있으면 fail-closed로 멈춘다.
93
+
85
94
  설정은 repo 루트의 `req.config.json`(선택). 자세한 계약은 `README.md` 참조.
package/README.en.md CHANGED
@@ -28,36 +28,29 @@ codex --version
28
28
  codex login status
29
29
  ```
30
30
 
31
- Then paste this prompt into your AI coding agent.
31
+ **You do not paste a long prompt.** Installation lays down agent entrypoints for you.
32
+
33
+ | File | Read by |
34
+ |---|---|
35
+ | `AGENTS.md` | Codex CLI, Cursor — **the contract** |
36
+ | `.claude/skills/commitgate/SKILL.md` | Claude Code (auto-invoked when it matches) |
37
+ | `.claude/commands/req.md` | Claude Code (`/req` explicit call) |
38
+ | `.cursor/rules/commitgate.mdc` | Cursor (`alwaysApply`) |
39
+ | `CLAUDE.md` | Claude Code (always loaded) — created only if absent |
40
+
41
+ Just give the agent a requirement.
32
42
 
33
43
  ```text
34
- Do not handle this as a normal implementation. Use the CommitGate workflow installed in this project.
35
-
36
- Create a new REQ ticket and run this flow end to end:
37
- req:new write design docs Codex design review → implement and test → req:doctor → Codex phase review → req:commit
38
-
39
- Proceed automatically:
40
- - If `req:review-codex` returns NEEDS_FIX/exit 3, fix the findings and rerun review.
41
- - If it returns BLOCKED/exit 2, do not retry the same review; escalate or change the review target. If a stuck thread is suspected, you may retry once with `--fresh-thread`.
42
- - The review target is only what has been staged with git add.
43
- - Do not manually git add state.json or responses/.
44
-
45
- Stop for human confirmation only (each item must be approved by that exact sentence; one approval never carries over to the next step):
46
- - Right before req:commit --run
47
- - [Path A · optional] [I1] Before pushing the feature branch and opening a PR / [I2] Before merging the PR, after confirming the required status checks are green
48
- - [Path B] [B1] Before a direct push to a protected branch — get a separate "branch protection bypass를 사용한 direct push 승인". This push bypasses the required status checks, and CI runs after it
49
- - [R1/R2/R3] tag creation and push / npm publish / GitHub release — each approved separately, after CI is green
50
- - Before destructive actions such as reset, clean, or force push
51
- - When the requested scope must change
52
- - When Codex review returns BLOCKED or remains unclear after bounded retries
53
-
54
- Requirement:
55
- - What:
56
- - Why:
57
- - Constraints:
58
- - Done when:
44
+ /req Add a profile edit API
45
+
46
+ - What: PATCH /profile to change nickname and bio
47
+ - Why: users cannot change their profile after signup
48
+ - Constraints: reuse the existing auth middleware, no schema change
49
+ - Done when: unit tests pass, unauthorized users get 403
59
50
  ```
60
51
 
52
+ Outside Claude Code you can skip the slash command and state the requirement directly — `.cursor/rules` and `AGENTS.md` load the rules. If the four fields are missing, the agent asks first.
53
+
61
54
  The agent's first reply should look roughly like this.
62
55
 
63
56
  ```text
@@ -69,7 +62,38 @@ Phases:
69
62
  Control points: before req:commit --run / [B1] before a direct push to main (or [I1] open PR → [I2] merge)
70
63
  ```
71
64
 
72
- After that, the agent runs design, implementation, tests, and Codex review. You only confirm at control points.
65
+ ### The agent follows whatever `req:next` says
66
+
67
+ The agent never guesses the next action. The tool computes it from `state.json` and git state.
68
+
69
+ ```sh
70
+ npm run req:next -- 2026-002
71
+ ```
72
+
73
+ ```text
74
+ [req:next] RUN REQ-2026-002
75
+ phase `phase-1`의 staged 변경을 리뷰받는다.
76
+
77
+ $ npm run req:review-codex -- 2026-002 --kind phase --phase phase-1 --run
78
+ ```
79
+
80
+ | kind | Meaning | exit |
81
+ |---|---|---|
82
+ | `RUN` | Run the printed command verbatim, then `req:next` again | 0 |
83
+ | `AGENT` | Work the tool cannot do (implement, write docs, `git add`) | 0 |
84
+ | `AWAIT_HUMAN` | **Control point** — do not proceed without the exact approval sentence | 10 |
85
+ | `DONE` | Nothing left for the tool. Integration is a separate control point | 11 |
86
+ | `BLOCKED` | Escalate to a human. Do not retry the same review | 2 |
87
+
88
+ Use `--json` for machine-readable output. It is **read-only** and changes no state.
89
+
90
+ Repeat this loop without stopping and it drives design → Codex review → implementation → re-review → commit. You only confirm at `AWAIT_HUMAN`.
91
+
92
+ ### The reviewer persona is injected by the tool
93
+
94
+ `req:review-codex` puts `workflow/review-persona.md` in as the **first block** of the prompt. It is identical whether a human, Cursor, or Claude runs the command — it does not live where an agent can forget it. If the file is missing or empty, the review stops fail-closed.
95
+
96
+ Edit it for your project, or point `reviewPersonaPath` in `req.config.json` at a different file. Set it to `null` to disable.
73
97
 
74
98
  Both integration paths are valid: **through a PR (optional)** and **direct push**. A PR is not mandatory. But a direct push to a protected branch **bypasses the required status checks**, so it needs a separate "branch protection bypass를 사용한 direct push 승인" — holding bypass permission is not approval. In that case CI runs **after** the push, so its green is post-hoc verification, and the agent must not omit that from its report. tag, npm publish, and GitHub release are control points of their own, requested after CI is green and never bundled with the integration approval. See [AGENTS.template.md](AGENTS.template.md) and [docs/RELEASING.md](docs/RELEASING.md) for the full contract.
75
99
 
@@ -98,12 +122,27 @@ In short: **approved changes pass, ambiguous changes stop.**
98
122
 
99
123
  | Added item | Purpose |
100
124
  |---|---|
101
- | `scripts/req/` | `req:new`, `req:review-codex`, `req:doctor`, `req:commit` scripts |
125
+ | `scripts/req/` | `req:new`, `req:next`, `req:review-codex`, `req:doctor`, `req:commit` scripts |
102
126
  | `workflow/*.schema.json` | Schemas for Codex responses and config |
127
+ | `workflow/review-persona.md` | Reviewer persona injected into the Codex review prompt |
103
128
  | `req.config.json` | Project-level configuration |
104
- | `AGENTS.md` | Template rules for the agent and reviewer |
129
+ | `AGENTS.md` | The contract (created only if absent) |
130
+ | `CLAUDE.md` | Claude Code pointer (created only if absent) |
131
+ | `.claude/skills/commitgate/SKILL.md` | Claude Code skill (pointer) |
132
+ | `.claude/commands/req.md` | `/req` slash command (pointer) |
133
+ | `.cursor/rules/commitgate.mdc` | Cursor rule (pointer) |
105
134
  | `package.json` scripts | `req:*` commands and required devDependencies |
106
135
 
136
+ The entrypoint files are **thin pointers**. The contract itself lives only in `AGENTS.md`.
137
+
138
+ If another tool already owns `.claude/` or `.cursor/`, skip that layer.
139
+
140
+ ```sh
141
+ npx commitgate --no-agent-entrypoints
142
+ ```
143
+
144
+ If an `AGENTS.md` already exists without the CommitGate contract marker (`<!-- commitgate:contract -->`), the contract template is installed alongside it as `AGENTS.commitgate.md` and you are told to merge it. Your existing file is never touched.
145
+
107
146
  Preview without writing files:
108
147
 
109
148
  ```sh
@@ -163,7 +202,7 @@ git checkout HEAD -- package.json
163
202
 
164
203
  Delete only the paths `npx commitgate uninstall` listed. Removing `scripts/req/` or `workflow/` as whole directories would also take your own files and your ticket evidence with them.
165
204
 
166
- > git does not track empty directories. After deleting the files, `git status` can report a clean tree while empty `scripts/` and `workflow/` directories remain on disk.
205
+ > git does not track empty directories. After deleting the files, `git status` can report a clean tree while empty `scripts/`, `workflow/`, `.claude/`, and `.cursor/` directories remain on disk.
167
206
 
168
207
  ### If you already committed the scaffold
169
208
 
@@ -266,12 +305,22 @@ npm run req:commit -- 2026-001 --run --message-file commit-message.txt
266
305
  | `npx commitgate` | Install CommitGate into a project |
267
306
  | `npx commitgate --dry-run` | Preview the install plan without writing files |
268
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` |
269
309
  | `npx commitgate uninstall` | Preview the removal plan (read-only — deletes nothing) |
270
- | `req:new <slug> --run` | Create a REQ ticket, branch, and design docs |
271
- | `req:review-codex <id> --kind design --run` | Review the design |
272
- | `req:review-codex <id> --kind phase --run` | Review the implementation |
273
- | `req:doctor <id>` | Check gate status |
274
- | `req:commit <id> --run -m "message"` | Commit approved changes |
310
+ | `npm run req:new -- <slug> --run` | Create a REQ ticket, branch, and design docs |
311
+ | `npm run req:next -- <id> [--json]` | **Compute the next action** (read-only) |
312
+ | `npm run req:review-codex -- <id> --kind design --run` | Review the design |
313
+ | `npm run req:review-codex -- <id> --kind phase --phase <p> --run` | Review the implementation |
314
+ | `npm run req:doctor -- <id>` | Check gate status |
315
+ | `npm run req:commit -- <id> --run -m "message"` | Commit approved changes |
316
+
317
+ `req:*` are **`package.json` scripts**, not executables on your PATH. npm needs the `--` separator to pass arguments.
318
+
319
+ ```sh
320
+ npm run req:next -- 2026-002 # npm
321
+ pnpm req:next 2026-002 # pnpm
322
+ yarn req:next 2026-002 # yarn
323
+ ```
275
324
 
276
325
  ---
277
326
 
@@ -285,6 +334,7 @@ Defaults are enough for most projects. If needed, edit `req.config.json` in the
285
334
  | `ticketRoot` | `"workflow"` | REQ ticket directory |
286
335
  | `packageManager` | auto-detected | `npm`, `pnpm`, or `yarn` |
287
336
  | `designDocs` | `00/01/02` docs | Design document filenames |
337
+ | `reviewPersonaPath` | `"workflow/review-persona.md"` | First block of the review prompt. `null` disables it |
288
338
 
289
339
  Empty `branchPrefix` values and paths that escape the project root are rejected.
290
340
 
package/README.md CHANGED
@@ -28,37 +28,30 @@ codex --version
28
28
  codex login status
29
29
  ```
30
30
 
31
- 그다음 AI 코딩 에이전트에게 아래 프롬프트를 붙여넣습니다.
31
+ **긴 프롬프트를 붙여넣을 필요가 없습니다.** 설치가 에이전트 진입점을 함께 깝니다.
32
+
33
+ | 파일 | 읽는 도구 |
34
+ |---|---|
35
+ | `AGENTS.md` | Codex CLI, Cursor — **계약 정본** |
36
+ | `.claude/skills/commitgate/SKILL.md` | Claude Code (요청에 맞으면 자동 발동) |
37
+ | `.claude/commands/req.md` | Claude Code (`/req` 명시 호출) |
38
+ | `.cursor/rules/commitgate.mdc` | Cursor (`alwaysApply`) |
39
+ | `CLAUDE.md` | Claude Code (항상 로드) — 부재 시에만 생성 |
40
+
41
+ 에이전트에게 요구사항만 주면 됩니다.
32
42
 
33
43
  ```text
34
- 요청은 일반 구현으로 처리하지 말고, 이 프로젝트에 설치된 CommitGate사용해라.
35
-
36
- REQ 티켓을 만들고 다음 흐름을 끝까지 진행해라:
37
- req:new 설계문서 작성 Codex design 리뷰 → 구현·테스트 → req:doctor → Codex phase 리뷰 → req:commit
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
- - 완료 기준:
44
+ /req 프로필 수정 API추가해줘
45
+
46
+ - 무엇을: PATCH /profile 닉네임·소개글 수정
47
+ - 왜: 지금은 가입 프로필을 바꿀 방법이 없다
48
+ - 제약: 기존 인증 미들웨어 재사용, 스키마 변경 없음
49
+ - 완료 기준: 단위 테스트 통과, 권한 없는 사용자는 403
59
50
  ```
60
51
 
61
- 응답은 보통 이렇게 나와야 합니다.
52
+ Claude Code가 아니면 슬래시 커맨드 없이 요구사항만 주어도 됩니다(`.cursor/rules`·`AGENTS.md`가 규칙을 로드합니다). 네 칸이 비어 있으면 에이전트가 먼저 물어봅니다.
53
+
54
+ 첫 응답은 보통 이렇게 나옵니다.
62
55
 
63
56
  ```text
64
57
  REQ-2026-002 발행
@@ -69,7 +62,38 @@ phase:
69
62
  통제점: req:commit --run 직전 / [B1] main direct push 직전 (또는 [I1] PR 생성 → [I2] merge)
70
63
  ```
71
64
 
72
- 이후에는 에이전트가 설계, 구현, 테스트, Codex 리뷰를 진행합니다. 사용자는 통제점에서만 확인하면 됩니다.
65
+ ### 에이전트는 `req:next`가 시키는 대로 진행합니다
66
+
67
+ 다음 행동을 에이전트가 추측하지 않습니다. 도구가 `state.json`과 git 상태에서 계산합니다.
68
+
69
+ ```sh
70
+ npm run req:next -- 2026-002
71
+ ```
72
+
73
+ ```text
74
+ [req:next] RUN REQ-2026-002
75
+ phase `phase-1`의 staged 변경을 리뷰받는다.
76
+
77
+ $ npm run req:review-codex -- 2026-002 --kind phase --phase phase-1 --run
78
+ ```
79
+
80
+ | kind | 뜻 | exit |
81
+ |---|---|---|
82
+ | `RUN` | 출력된 명령을 그대로 실행하고 다시 `req:next` | 0 |
83
+ | `AGENT` | 도구가 대신 못 하는 작업(구현·문서 작성·`git add`) | 0 |
84
+ | `AWAIT_HUMAN` | **통제점** — 출력된 승인 문장을 그대로 받기 전엔 진행 금지 | 10 |
85
+ | `DONE` | 이 티켓에서 도구가 할 일 없음. 통합은 별도 통제점 | 11 |
86
+ | `BLOCKED` | 사람에게 보고. 같은 리뷰 재시도 금지 | 2 |
87
+
88
+ `--json`으로 기계 판독할 수 있습니다. **읽기 전용**이라 어떤 상태도 바꾸지 않습니다.
89
+
90
+ 이 루프를 끊지 말고 반복하면 설계 → Codex 리뷰 → 구현 → 재리뷰 → 커밋이 진행됩니다. 사용자는 `AWAIT_HUMAN`에서만 확인하면 됩니다.
91
+
92
+ ### 리뷰어 페르소나는 도구가 주입합니다
93
+
94
+ `req:review-codex`는 `workflow/review-persona.md`를 프롬프트 **첫 블록**으로 넣습니다. 사람이 직접 실행하든, Cursor가 실행하든, Claude가 실행하든 동일합니다 — 에이전트가 잊을 수 있는 자리에 두지 않습니다. 파일이 없거나 비어 있으면 리뷰가 fail-closed로 멈춥니다.
95
+
96
+ 내용을 프로젝트에 맞게 고치거나, `req.config.json`의 `reviewPersonaPath`로 다른 파일을 지정할 수 있습니다. `null`로 두면 비활성화됩니다.
73
97
 
74
98
  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
99
 
@@ -98,12 +122,27 @@ CommitGate가 막는 것은 단순한 명령 실수가 아니라 **리뷰받지
98
122
 
99
123
  | 추가 항목 | 설명 |
100
124
  |---|---|
101
- | `scripts/req/` | `req:new`, `req:review-codex`, `req:doctor`, `req:commit` 스크립트 |
125
+ | `scripts/req/` | `req:new`, `req:next`, `req:review-codex`, `req:doctor`, `req:commit` 스크립트 |
102
126
  | `workflow/*.schema.json` | Codex 응답과 설정 검증 스키마 |
127
+ | `workflow/review-persona.md` | Codex 리뷰 프롬프트에 주입되는 리뷰어 페르소나 |
103
128
  | `req.config.json` | 프로젝트별 설정 |
104
- | `AGENTS.md` | 에이전트와 Reviewer가 읽는 규칙 템플릿 |
129
+ | `AGENTS.md` | 계약 정본 (없을 때만 생성) |
130
+ | `CLAUDE.md` | Claude Code 지침 포인터 (없을 때만 생성) |
131
+ | `.claude/skills/commitgate/SKILL.md` | Claude Code 스킬 (포인터) |
132
+ | `.claude/commands/req.md` | `/req` 슬래시 커맨드 (포인터) |
133
+ | `.cursor/rules/commitgate.mdc` | Cursor 규칙 (포인터) |
105
134
  | `package.json` 스크립트 | `req:*` 명령과 필요한 devDependencies |
106
135
 
136
+ 진입점 파일들은 **얇은 포인터**입니다. 계약 본문은 `AGENTS.md` 하나에만 있습니다.
137
+
138
+ `.claude/`·`.cursor/`를 다른 도구가 쓰고 있다면 건너뛸 수 있습니다.
139
+
140
+ ```sh
141
+ npx commitgate --no-agent-entrypoints
142
+ ```
143
+
144
+ 기존 `AGENTS.md`가 있는데 CommitGate 계약 마커(`<!-- commitgate:contract -->`)가 없으면, 계약 템플릿을 `AGENTS.commitgate.md`로 함께 놓고 병합을 안내합니다. 기존 파일은 건드리지 않습니다.
145
+
107
146
  미리보기만 하려면:
108
147
 
109
148
  ```sh
@@ -163,7 +202,7 @@ git checkout HEAD -- package.json
163
202
 
164
203
  파일 삭제는 `npx commitgate uninstall`이 나열해 준 경로만 지우세요. `scripts/req/`나 `workflow/`를 디렉터리째 지우면 그 안의 사용자 파일이나 티켓 증거가 함께 사라집니다.
165
204
 
166
- > git은 빈 디렉터리를 추적하지 않습니다. 파일을 다 지운 뒤 `git status`가 clean이어도 빈 `scripts/`·`workflow/`가 파일시스템에 남을 수 있습니다.
205
+ > git은 빈 디렉터리를 추적하지 않습니다. 파일을 다 지운 뒤 `git status`가 clean이어도 빈 `scripts/`·`workflow/`·`.claude/`·`.cursor/`가 파일시스템에 남을 수 있습니다.
167
206
 
168
207
  ### 이미 커밋했다면
169
208
 
@@ -224,7 +263,7 @@ Windows에서 설치 직후 `codex` 명령을 못 찾으면 새 터미널을 열
224
263
 
225
264
  ## 수동 명령
226
265
 
227
- 대부분의 사용자는 프롬프트 방식으로 충분합니다. 아래는 내부에서 어떤 명령이 실행되는지 이해하거나 직접 디버깅할 때만 보면 됩니다.
266
+ 대부분의 사용자는 `req:next`가 시키는 대로만 하면 됩니다. 아래는 내부에서 어떤 명령이 실행되는지 이해하거나 직접 디버깅할 때만 보면 됩니다.
228
267
 
229
268
  ```sh
230
269
  # 1. 티켓과 브랜치 생성
@@ -266,12 +305,22 @@ npm run req:commit -- 2026-001 --run --message-file commit-message.txt
266
305
  | `npx commitgate` | 프로젝트에 CommitGate 설치 |
267
306
  | `npx commitgate --dry-run` | 파일을 쓰지 않고 설치 계획 확인 |
268
307
  | `npx commitgate --strict` | 낮은 `cross-spawn` 버전 경고를 설치 실패로 처리 |
308
+ | `npx commitgate --no-agent-entrypoints` | `.claude/`·`.cursor/`·`CLAUDE.md` 설치 건너뛰기 |
269
309
  | `npx commitgate uninstall` | 제거 계획 확인 (읽기 전용 — 아무것도 지우지 않음) |
270
- | `req:new <slug> --run` | REQ 티켓, 브랜치, 설계문서 생성 |
271
- | `req:review-codex <id> --kind design --run` | 설계 리뷰 |
272
- | `req:review-codex <id> --kind phase --run` | 구현 리뷰 |
273
- | `req:doctor <id>` | 게이트 상태 확인 |
274
- | `req:commit <id> --run -m "message"` | 승인된 변경 커밋 |
310
+ | `npm run req:new -- <slug> --run` | REQ 티켓, 브랜치, 설계문서 생성 |
311
+ | `npm run req:next -- <id> [--json]` | **다음 행동 계산** (읽기 전용) |
312
+ | `npm run req:review-codex -- <id> --kind design --run` | 설계 리뷰 |
313
+ | `npm run req:review-codex -- <id> --kind phase --phase <p> --run` | 구현 리뷰 |
314
+ | `npm run req:doctor -- <id>` | 게이트 상태 확인 |
315
+ | `npm run req:commit -- <id> --run -m "message"` | 승인된 변경 커밋 |
316
+
317
+ `req:*`는 PATH에 잡히는 실행 파일이 아니라 **`package.json` 스크립트**입니다. npm은 인자 전달에 `--` 구분자가 필요합니다.
318
+
319
+ ```sh
320
+ npm run req:next -- 2026-002 # npm
321
+ pnpm req:next 2026-002 # pnpm
322
+ yarn req:next 2026-002 # yarn
323
+ ```
275
324
 
276
325
  ---
277
326
 
@@ -285,6 +334,7 @@ npm run req:commit -- 2026-001 --run --message-file commit-message.txt
285
334
  | `ticketRoot` | `"workflow"` | REQ 티켓 폴더 |
286
335
  | `packageManager` | 자동 감지 | `npm`, `pnpm`, `yarn` |
287
336
  | `designDocs` | `00/01/02` 문서 | 설계 문서 파일명 |
337
+ | `reviewPersonaPath` | `"workflow/review-persona.md"` | 리뷰 프롬프트 첫 블록. `null`이면 비활성 |
288
338
 
289
339
  빈 `branchPrefix`나 프로젝트 밖으로 나가는 경로는 거부됩니다.
290
340
 
package/bin/init.ts CHANGED
@@ -4,10 +4,11 @@
4
4
  *
5
5
  * 동작(멱등·비파괴):
6
6
  * 1. 대상 repo 감사(git repo·package.json 필수 → 없으면 fail-closed throw)
7
- * 2. `scripts/req/**` + `workflow/{machine,req.config}.schema.json` 복사(기존 파일은 --force 없으면 스킵)
7
+ * 2. `scripts/req/**` + `KIT_COPY_RELPATHS`(스키마 2종 + review-persona.md) 복사(기존 파일은 --force 없으면 스킵)
8
8
  * 3. `req.config.json` 시드(부재 시): 감지한 packageManager + handoffPath:null(프로젝트별 값은 코어 DEFAULTS가 아니라 config에서 흡수)
9
9
  * 4. 대상 `package.json`에 req:* 스크립트·devDeps(ajv/tsx) 주입(기존 키 미덮어씀)
10
10
  * 5. `AGENTS.md` 부재 시 템플릿 생성(있으면 스킵 — Codex 계약 보존)
11
+ * 6. 에이전트 진입점(.claude/skills·.claude/commands·.cursor/rules) 복사 + `CLAUDE.md` 부재 시 생성 (--no-agent-entrypoints로 생략)
11
12
  *
12
13
  * 코어 승인 바인딩·staged tree 검증은 건드리지 않는다(복사만). 프로젝트 차이는 req.config.json에서만 흡수.
13
14
  */
@@ -24,7 +25,7 @@ import {
24
25
  import { execFileSync } from 'node:child_process'
25
26
  import { resolve, join, dirname, relative } from 'node:path'
26
27
  import { fileURLToPath, pathToFileURL } from 'node:url'
27
- import { loadConfig, stripBom, type PackageManager } from '../scripts/req/lib/config'
28
+ import { loadConfig, stripBom, DEFAULT_REVIEW_PERSONA_RELPATH, type PackageManager } from '../scripts/req/lib/config'
28
29
  import { createGitAdapter, type GitRunner } from '../scripts/req/lib/adapters'
29
30
  import * as semver from 'semver'
30
31
 
@@ -41,11 +42,58 @@ export const KIT_SOURCE_DIR_REL = 'scripts/req'
41
42
  */
42
43
  export const KIT_SCHEMA_RELPATHS = ['workflow/machine.schema.json', 'workflow/req.config.schema.json'] as const
43
44
 
45
+ /**
46
+ * init이 `scripts/req/**` 외에 **실제로 복사하는** 파일 목록(패키지-상대 = 대상-상대). 복사기와 uninstall planner의 SSOT.
47
+ *
48
+ * ⚠️ `KIT_SCHEMA_RELPATHS`와 **의미가 다르다** — 그 상수는 "설정된 `schemaPath`가 init이 깐 스키마인가"를
49
+ * 판정하는 **스키마 축**이고(`bin/uninstall.ts`), 이 상수는 **복사 축**이다. 여기 persona를 넣되 저기엔 넣지 않는다.
50
+ *
51
+ * ⚠️ `package.json`의 `files[]`는 또 다른 축(npm tarball)이다. **셋을 혼동하면**
52
+ * tarball엔 실렸는데 대상 repo엔 안 깔리는 파일이 생기고, phase-1b의 persona fail-closed와 만나
53
+ * 신규 설치본의 모든 리뷰가 멈춘다(REQ-2026-010 design R1 P1).
54
+ */
55
+ export const KIT_COPY_RELPATHS = [...KIT_SCHEMA_RELPATHS, DEFAULT_REVIEW_PERSONA_RELPATH] as const
56
+
57
+ /**
58
+ * 에이전트 진입점 (REQ-2026-010 D7·D8). init 복사기와 uninstall planner가 공유하는 SSOT.
59
+ *
60
+ * ⚠️ `KIT_COPY_RELPATHS`와 달리 **`src !== dest`**다. `copyInto`는 `relative(PACKAGE_ROOT, src)`로 레이아웃을
61
+ * 재현하므로 쓸 수 없고, `uninstall`의 `tool` 분류도 `join(PACKAGE_ROOT, rel)`로 원본을 찾을 수 없다.
62
+ * 두 곳 모두 `src`/`dest`를 분리해 다뤄야 한다.
63
+ *
64
+ * 본문 SSOT는 `AGENTS.md`다. 여기 깔리는 파일은 **얇은 포인터**이며 계약 본문을 복제하지 않는다 — 복제하면 drift 부채가 된다.
65
+ */
66
+ export const KIT_AGENT_ENTRYPOINTS = [
67
+ { src: 'templates/claude-skill.md', dest: '.claude/skills/commitgate/SKILL.md' },
68
+ { src: 'templates/claude-command.md', dest: '.claude/commands/req.md' },
69
+ { src: 'templates/cursor-rule.mdc', dest: '.cursor/rules/commitgate.mdc' },
70
+ ] as const
71
+
72
+ /** `CLAUDE.md`는 `AGENTS.md`와 같은 취급 — **부재 시에만** 생성하고, uninstall에서 `ambiguous`(자동 제거 금지). */
73
+ export const KIT_CLAUDE_TEMPLATE_REL = 'templates/CLAUDE.template.md'
74
+ export const KIT_CLAUDE_DEST_REL = 'CLAUDE.md'
75
+
76
+ /** `AGENTS.md`가 CommitGate 계약인지 판별하는 마커. 진입점 포인터들이 이 마커로 SSOT를 확인한다. */
77
+ export const AGENTS_CONTRACT_MARKER = '<!-- commitgate:contract -->'
78
+
79
+ /**
80
+ * 기존 `AGENTS.md`에 계약 마커가 없을 때 **대상 repo에 함께 놓는** 계약 템플릿 사본 (phase-3a R1 P2).
81
+ *
82
+ * ⚠️ 이게 없으면 포인터의 복구 지시가 **막다른 길**이 된다. 진입점 파일들은 "마커가 없으면 계약 템플릿을
83
+ * 참조해 병합하라"고 하는데, `AGENTS.template.md`는 **패키지 안에만** 있고 대상 repo에는 복사되지 않는다.
84
+ * `npx commitgate`는 전역/로컬 설치가 아니라 npm 캐시에서 한 번 실행될 뿐이라 `node_modules/commitgate/`도
85
+ * 남지 않는다. 사용자는 참조할 파일을 찾을 수 없다.
86
+ *
87
+ * 그래서 마커가 없을 때만 이 경로로 사본을 놓는다. 마커가 있으면(정상) 이 파일은 만들지 않는다.
88
+ */
89
+ export const KIT_AGENTS_CONTRACT_COPY_REL = 'AGENTS.commitgate.md'
90
+
44
91
  /** 대상 package.json에 주입할 req:* 스크립트. */
45
92
  export const REQ_SCRIPTS: Record<string, string> = {
46
93
  'req:new': 'tsx scripts/req/req-new.ts',
47
94
  'req:review-codex': 'tsx scripts/req/review-codex.ts',
48
95
  'req:doctor': 'tsx scripts/req/req-doctor.ts',
96
+ 'req:next': 'tsx scripts/req/req-next.ts',
49
97
  'req:commit': 'tsx scripts/req/req-commit.ts',
50
98
  }
51
99
 
@@ -64,6 +112,8 @@ export interface InitOptions {
64
112
  force: boolean
65
113
  dryRun: boolean
66
114
  strict: boolean // cross-spawn 하한 미만이면 WARN 대신 throw(#1)
115
+ /** `.claude/`·`.cursor/`·`CLAUDE.md`를 건너뛴다. 다른 도구가 그 디렉터리를 쓰는 repo를 위한 opt-out(D7). */
116
+ noAgentEntrypoints?: boolean
67
117
  }
68
118
 
69
119
  export interface InitResult {
@@ -77,6 +127,10 @@ export interface InitResult {
77
127
  packageManager: PackageManager
78
128
  crossSpawnFloorWarned: boolean // 기존 cross-spawn이 보안 하한 미만이라 경고(#1)
79
129
  dryRun: boolean
130
+ claudeMdCreated: boolean // CLAUDE.md를 새로 만들었는가(있으면 미덮어씀)
131
+ agentsMarkerMissing: boolean // 기존 AGENTS.md에 commitgate 계약 마커가 없어 경고했는가
132
+ agentsContractCopyCreated: boolean // 마커 부재 시 AGENTS.commitgate.md(계약 템플릿 사본)를 놓았는가
133
+ agentEntrypointsSkipped: boolean // --no-agent-entrypoints
80
134
  }
81
135
 
82
136
  /**
@@ -164,6 +218,54 @@ function copyInto(
164
218
  }
165
219
  }
166
220
 
221
+ /**
222
+ * 진입점 dest 경로들이 **실제로 만들어질 수 있는지** preflight에서 확인한다 (D8).
223
+ *
224
+ * `mkdirSync(recursive)`는 경로 중간 컴포넌트가 **파일**이면 ENOTDIR로 죽는다. apply 단계에서 그러면
225
+ * 앞의 파일들은 이미 복사된 뒤라 **부분 설치**가 된다. 쓰기 전에 걸러서 preflight→apply 계약을 지킨다.
226
+ */
227
+ function assertEntrypointPathsUsable(targetRoot: string): void {
228
+ const dests = [...KIT_AGENT_ENTRYPOINTS.map((e) => e.dest), KIT_CLAUDE_DEST_REL, KIT_AGENTS_CONTRACT_COPY_REL]
229
+ for (const dest of dests) {
230
+ const parts = dest.split('/')
231
+ // 마지막(파일명) 제외한 각 디렉터리 컴포넌트가 파일로 존재하면 mkdir 불가.
232
+ for (let i = 0; i < parts.length - 1; i++) {
233
+ const sub = join(targetRoot, ...parts.slice(0, i + 1))
234
+ if (existsSync(sub) && !statSync(sub).isDirectory())
235
+ throw new Error(
236
+ `진입점 설치 불가: ${parts.slice(0, i + 1).join('/')} 가 디렉터리가 아니라 파일입니다(${dest} 를 만들 수 없음).\n` +
237
+ ` → 해당 파일을 옮기거나, --no-agent-entrypoints 로 이 계층을 건너뛰세요.`,
238
+ )
239
+ }
240
+ const abs = join(targetRoot, dest)
241
+ if (existsSync(abs) && statSync(abs).isDirectory())
242
+ throw new Error(`진입점 설치 불가: ${dest} 가 디렉터리로 존재합니다(파일이어야 함).`)
243
+ }
244
+ }
245
+
246
+ /**
247
+ * `src → dest`(경로가 다름) 복사. 기존 파일은 `--force` 없으면 스킵. 중첩 디렉터리를 만든다.
248
+ * `copyInto`(레이아웃 재현)와 달리 매핑이 명시적이다.
249
+ */
250
+ function copyEntrypoints(
251
+ targetRoot: string,
252
+ opts: InitOptions,
253
+ copied: string[],
254
+ skipped: string[],
255
+ ): void {
256
+ for (const { src, dest } of KIT_AGENT_ENTRYPOINTS) {
257
+ const destAbs = join(targetRoot, dest)
258
+ if (existsSync(destAbs) && !opts.force) {
259
+ skipped.push(dest)
260
+ continue
261
+ }
262
+ copied.push(dest)
263
+ if (opts.dryRun) continue
264
+ mkdirSync(dirname(destAbs), { recursive: true })
265
+ copyFileSync(join(PACKAGE_ROOT, src), destAbs)
266
+ }
267
+ }
268
+
167
269
  /** JSON 파일을 객체로 파싱(fail-closed). 파싱 실패·비-객체(배열/원시)면 throw. */
168
270
  function parseJsonObject(path: string, label: string): Record<string, unknown> {
169
271
  let parsed: unknown
@@ -310,7 +412,7 @@ export function runInit(opts: InitOptions): InitResult {
310
412
  const packageManager = detectPackageManager(targetRoot)
311
413
 
312
414
  // req.config.json 계획(쓰기 없음). handoffPath:null·packageManager를 항상 보장 —
313
- // 코어 DEFAULTS의 palm 고유값(handoffPath) 기존 부분 config에서도 resurface하지 않도록(design R1 P2). 기존 키 보존.
415
+ // handoffPath는 프로젝트별 값이라 코어 기본이 비활성(null)이다 비활성을 config **명시 기록**한다(암묵 < 명시). 기존 키 보존.
314
416
  let configAction: 'created' | 'merged' | 'unchanged' = 'unchanged'
315
417
  const configKeysAdded: string[] = []
316
418
  let configToWrite: Record<string, unknown> | null = null
@@ -354,13 +456,29 @@ export function runInit(opts: InitOptions): InitResult {
354
456
  const agentsPath = join(targetRoot, 'AGENTS.md')
355
457
  const agentsCreated = !existsSync(agentsPath)
356
458
 
459
+ const agentEntrypointsSkipped = opts.noAgentEntrypoints === true
460
+ if (!agentEntrypointsSkipped) assertEntrypointPathsUsable(targetRoot)
461
+
462
+ // 기존 AGENTS.md에 계약 마커가 없으면 진입점 포인터가 **엉뚱한 SSOT**를 가리키게 된다(design R1 observation).
463
+ // 설치는 계속한다(비파괴 원칙) — 사용자가 병합하도록 알릴 뿐.
464
+ const agentsMarkerMissing =
465
+ !agentEntrypointsSkipped && !agentsCreated && !readFileSync(agentsPath, 'utf8').includes(AGENTS_CONTRACT_MARKER)
466
+
467
+ const claudeMdPath = join(targetRoot, KIT_CLAUDE_DEST_REL)
468
+ const claudeMdCreated = !agentEntrypointsSkipped && !existsSync(claudeMdPath)
469
+
470
+ // 마커가 없으면 포인터가 참조할 계약 템플릿을 **대상 repo에** 놓는다 — 그러지 않으면 복구 지시가 막다른 길이다.
471
+ const contractCopyPath = join(targetRoot, KIT_AGENTS_CONTRACT_COPY_REL)
472
+ const agentsContractCopyCreated = agentsMarkerMissing && (!existsSync(contractCopyPath) || opts.force)
473
+
357
474
  // ══ Apply: 여기부터 쓰기(preflight 전부 통과 후에만) ═════════════════
358
475
  const copied: string[] = []
359
476
  const skipped: string[] = []
360
477
  copyInto(walkFiles(join(PACKAGE_ROOT, KIT_SOURCE_DIR_REL)), PACKAGE_ROOT, targetRoot, opts, copied, skipped)
361
- // ⚠️ KIT_SCHEMA_RELPATHS는 패키지-상대 = 대상-상대(리터럴 `workflow/`). ticketRoot/schemaPath 설정과 무관 — uninstall planner와 공유하는 SSOT.
362
- const schemaFiles = KIT_SCHEMA_RELPATHS.map((rel) => join(PACKAGE_ROOT, rel))
363
- copyInto(schemaFiles, PACKAGE_ROOT, targetRoot, opts, copied, skipped)
478
+ // ⚠️ KIT_COPY_RELPATHS는 패키지-상대 = 대상-상대(리터럴 `workflow/`). ticketRoot/schemaPath 설정과 무관 — uninstall planner와 공유하는 SSOT.
479
+ const kitFiles = KIT_COPY_RELPATHS.map((rel) => join(PACKAGE_ROOT, rel))
480
+ copyInto(kitFiles, PACKAGE_ROOT, targetRoot, opts, copied, skipped)
481
+ if (!agentEntrypointsSkipped) copyEntrypoints(targetRoot, opts, copied, skipped)
364
482
 
365
483
  if (!opts.dryRun) {
366
484
  if (configToWrite) writeFileSync(cfgPath, JSON.stringify(configToWrite, null, 2) + '\n', 'utf8')
@@ -370,6 +488,23 @@ export function runInit(opts: InitOptions): InitResult {
370
488
  writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf8')
371
489
  }
372
490
  if (agentsCreated) copyFileSync(join(PACKAGE_ROOT, 'AGENTS.template.md'), agentsPath)
491
+ // CLAUDE.md는 AGENTS.md와 동일 정책: **부재 시에만** 생성(--force로도 덮어쓰지 않는다 — 사용자 파일일 수 있다).
492
+ if (claudeMdCreated) copyFileSync(join(PACKAGE_ROOT, KIT_CLAUDE_TEMPLATE_REL), claudeMdPath)
493
+ if (agentsContractCopyCreated) copyFileSync(join(PACKAGE_ROOT, 'AGENTS.template.md'), contractCopyPath)
494
+ }
495
+
496
+ if (agentsMarkerMissing) {
497
+ // phase-3a R1 observation: 사본을 **새로 놓았을 때**와 **기존 것을 보존했을 때**의 문구가 달라야 한다.
498
+ // "설치했습니다"가 두 경우 모두에 나오면, 편집된 사본을 그대로 둔 사용자가 새 템플릿을 받았다고 오해한다.
499
+ const copyNote = opts.dryRun
500
+ ? `계약 템플릿을 ${KIT_AGENTS_CONTRACT_COPY_REL} 로 설치할 예정입니다.`
501
+ : agentsContractCopyCreated
502
+ ? `계약 템플릿을 ${KIT_AGENTS_CONTRACT_COPY_REL} 로 함께 설치했습니다.`
503
+ : `${KIT_AGENTS_CONTRACT_COPY_REL} 가 이미 있어 보존했습니다(덮어쓰려면 --force).`
504
+ console.warn(
505
+ `⚠️ 기존 AGENTS.md에 ${AGENTS_CONTRACT_MARKER} 마커가 없습니다 — .claude/·.cursor/ 진입점이 가리킬 CommitGate 계약이 그 파일에 없습니다.\n` +
506
+ ` ${copyNote} 그 내용을 AGENTS.md에 병합한 뒤 ${KIT_AGENTS_CONTRACT_COPY_REL} 를 지우세요(설치는 계속됩니다).`,
507
+ )
373
508
  }
374
509
 
375
510
  return {
@@ -383,6 +518,10 @@ export function runInit(opts: InitOptions): InitResult {
383
518
  packageManager,
384
519
  crossSpawnFloorWarned,
385
520
  dryRun: opts.dryRun,
521
+ claudeMdCreated,
522
+ agentsMarkerMissing,
523
+ agentsContractCopyCreated,
524
+ agentEntrypointsSkipped,
386
525
  }
387
526
  }
388
527
 
@@ -391,6 +530,7 @@ export function parseArgs(argv: string[]): InitOptions {
391
530
  let force = false
392
531
  let dryRun = false
393
532
  let strict = false
533
+ let noAgentEntrypoints = false
394
534
  for (let i = 0; i < argv.length; i++) {
395
535
  const a = argv[i]
396
536
  if (a === '--dir') {
@@ -404,6 +544,8 @@ export function parseArgs(argv: string[]): InitOptions {
404
544
  dryRun = true
405
545
  } else if (a === '--strict') {
406
546
  strict = true
547
+ } else if (a === '--no-agent-entrypoints') {
548
+ noAgentEntrypoints = true
407
549
  } else if (a === '-h' || a === '--help') {
408
550
  printHelp()
409
551
  process.exit(0)
@@ -411,7 +553,7 @@ export function parseArgs(argv: string[]): InitOptions {
411
553
  throw new Error(`알 수 없는 인자: ${a}`)
412
554
  }
413
555
  }
414
- return { dir: resolve(dir), force, dryRun, strict }
556
+ return { dir: resolve(dir), force, dryRun, strict, noAgentEntrypoints }
415
557
  }
416
558
 
417
559
  function printHelp(): void {
@@ -426,6 +568,8 @@ function printHelp(): void {
426
568
  --force 기존 kit 파일 덮어쓰기(기본: 스킵)
427
569
  --dry-run 변경 없이 수행 예정 목록만 출력
428
570
  --strict 기존 cross-spawn이 보안 하한(>=7.0.6) 미만이면 경고 대신 중단(fail-closed)
571
+ --no-agent-entrypoints
572
+ .claude/·.cursor/·CLAUDE.md 진입점 설치를 건너뛴다
429
573
  -h, --help 도움말
430
574
 
431
575
  설치 후:
@@ -458,6 +602,11 @@ export function main(argv: string[]): void {
458
602
  `${tag} package.json: ${r.packageJsonAdded.length > 0 ? '추가 ' + r.packageJsonAdded.join(', ') : '변경 없음'}`,
459
603
  )
460
604
  console.log(`${tag} AGENTS.md: ${r.agentsCreated ? '템플릿 생성' : '이미 존재(유지)'}`)
605
+ if (r.agentEntrypointsSkipped) console.log(`${tag} 에이전트 진입점: 건너뜀(--no-agent-entrypoints)`)
606
+ else {
607
+ console.log(`${tag} CLAUDE.md: ${r.claudeMdCreated ? '템플릿 생성' : '이미 존재(유지)'}`)
608
+ if (r.agentsContractCopyCreated) console.log(`${tag} ${KIT_AGENTS_CONTRACT_COPY_REL}: 계약 템플릿 사본 생성(AGENTS.md에 병합 후 삭제)`)
609
+ }
461
610
  if (r.crossSpawnFloorWarned) console.log(`${tag} ⚠️ cross-spawn 버전 하한 경고(위 참조) — 강제 중단은 --strict`)
462
611
  if (!r.dryRun) {
463
612
  console.log(`\n다음:`)