commitgate 0.1.2 → 0.2.1
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/README.en.md +22 -1
- package/README.md +22 -1
- package/bin/init.ts +115 -5
- package/package.json +66 -63
package/README.en.md
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
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
|
+
[](https://github.com/sol5288/commitgate/actions/workflows/ci.yml)
|
|
10
|
+
[](https://www.npmjs.com/package/commitgate)
|
|
9
11
|
[](./LICENSE)
|
|
10
12
|
|
|
11
13
|
---
|
|
@@ -71,6 +73,7 @@ CommitGate is designed to block **unreviewed changes from being committed**, not
|
|
|
71
73
|
- If the approved staged tree differs from the current staged tree, the commit is blocked.
|
|
72
74
|
- Workflow files such as `state.json` and `responses/` cannot be mixed into the source commit.
|
|
73
75
|
- If Codex CLI is missing or fails, the workflow fails instead of silently passing.
|
|
76
|
+
- During install, existing `cross-spawn` versions below the verified floor warn by default and fail with `--strict`.
|
|
74
77
|
- Approval responses and evidence are kept under `workflow/REQ-.../responses/`.
|
|
75
78
|
|
|
76
79
|
In short: **approved changes pass, ambiguous changes stop.**
|
|
@@ -95,6 +98,14 @@ Preview without writing files:
|
|
|
95
98
|
npx commitgate --dry-run
|
|
96
99
|
```
|
|
97
100
|
|
|
101
|
+
Treat the security floor warning as an install failure:
|
|
102
|
+
|
|
103
|
+
```sh
|
|
104
|
+
npx commitgate --strict
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
If an existing `cross-spawn` is below the verified floor, CommitGate stops before copying files.
|
|
108
|
+
|
|
98
109
|
---
|
|
99
110
|
|
|
100
111
|
## Prerequisites
|
|
@@ -160,6 +171,8 @@ npm run req:commit -- 2026-001 --run --message-file commit-message.txt
|
|
|
160
171
|
| Command | Purpose |
|
|
161
172
|
|---|---|
|
|
162
173
|
| `npx commitgate` | Install CommitGate into a project |
|
|
174
|
+
| `npx commitgate --dry-run` | Preview the install plan without writing files |
|
|
175
|
+
| `npx commitgate --strict` | Treat low `cross-spawn` version warnings as install failures |
|
|
163
176
|
| `req:new <slug> --run` | Create a REQ ticket, branch, and design docs |
|
|
164
177
|
| `req:review-codex <id> --kind design --run` | Review the design |
|
|
165
178
|
| `req:review-codex <id> --kind phase --run` | Review the implementation |
|
|
@@ -194,6 +207,9 @@ No. If the staged tree changes after approval, CommitGate treats the approval as
|
|
|
194
207
|
**Why should I not stage `state.json` or `responses/`?**
|
|
195
208
|
They are workflow state and evidence files. Mixing them into the source commit weakens the approval binding, so `req:commit` blocks it.
|
|
196
209
|
|
|
210
|
+
**What should I do if I see a cross-spawn version warning?**
|
|
211
|
+
It means the target project may already have a `cross-spawn` version below CommitGate's verified floor. Upgrade it with `npm i -D cross-spawn@^7.0.6`. In CI or security-sensitive installs, use `npx commitgate --strict` to treat the warning as a failure.
|
|
212
|
+
|
|
197
213
|
**Does running install twice overwrite files?**
|
|
198
214
|
No. Existing files are skipped. Use `--force` if you intentionally want to refresh them.
|
|
199
215
|
|
|
@@ -203,12 +219,17 @@ No. Existing files are skipped. Use `--force` if you intentionally want to refre
|
|
|
203
219
|
|
|
204
220
|
The current release is **Stage A: vendored scaffold model**. `npx commitgate` copies workflow files into the target project.
|
|
205
221
|
|
|
222
|
+
Current verification:
|
|
223
|
+
|
|
224
|
+
- GitHub Actions runs a `ubuntu-latest`, `macos-latest`, `windows-latest` × Node 18/20/22 matrix.
|
|
225
|
+
- `npm run smoke` installs the packed tarball and runs the installed `commitgate` bin.
|
|
226
|
+
- A Windows `.cmd` wrapper injection regression test protects package-manager and Codex wrapper paths.
|
|
227
|
+
|
|
206
228
|
Future scope:
|
|
207
229
|
|
|
208
230
|
- Running directly from `node_modules` as a library-style model
|
|
209
231
|
- Non-git VCS support
|
|
210
232
|
- More design document templates
|
|
211
|
-
- Broader Linux/macOS CI smoke coverage
|
|
212
233
|
|
|
213
234
|
---
|
|
214
235
|
|
package/README.md
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
AI 에이전트가 코드를 빠르게 만들더라도, 리뷰 없이 바로 커밋되면 위험합니다. CommitGate는 변경을 티켓 단위로 묶고, Codex가 승인한 staged tree만 커밋되게 합니다. 승인 후 코드가 바뀌거나 증거가 부족하면 기본적으로 막습니다.
|
|
8
8
|
|
|
9
|
+
[](https://github.com/sol5288/commitgate/actions/workflows/ci.yml)
|
|
10
|
+
[](https://www.npmjs.com/package/commitgate)
|
|
9
11
|
[](./LICENSE)
|
|
10
12
|
|
|
11
13
|
---
|
|
@@ -71,6 +73,7 @@ CommitGate가 막는 것은 단순한 명령 실수가 아니라 **리뷰받지
|
|
|
71
73
|
- 승인된 staged tree와 지금 커밋하려는 staged tree가 다르면 막습니다.
|
|
72
74
|
- `state.json`, `responses/` 같은 워크플로 내부 파일을 source 커밋에 섞으면 막습니다.
|
|
73
75
|
- Codex CLI가 없거나 실행에 실패하면 조용히 통과하지 않고 실패합니다.
|
|
76
|
+
- 설치 시 기존 `cross-spawn`이 검증 하한보다 낮으면 경고하고, `--strict`에서는 중단합니다.
|
|
74
77
|
- 승인 응답과 증거 파일은 `workflow/REQ-.../responses/`에 남습니다.
|
|
75
78
|
|
|
76
79
|
한 줄로 말하면, **확실히 승인된 변경만 통과하고 애매하면 멈추는 방식**입니다.
|
|
@@ -95,6 +98,14 @@ CommitGate가 막는 것은 단순한 명령 실수가 아니라 **리뷰받지
|
|
|
95
98
|
npx commitgate --dry-run
|
|
96
99
|
```
|
|
97
100
|
|
|
101
|
+
보안 하한 경고를 설치 실패로 취급하려면:
|
|
102
|
+
|
|
103
|
+
```sh
|
|
104
|
+
npx commitgate --strict
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
기존 `cross-spawn`이 검증 하한보다 낮으면 파일을 복사하기 전에 중단합니다.
|
|
108
|
+
|
|
98
109
|
---
|
|
99
110
|
|
|
100
111
|
## 준비물
|
|
@@ -160,6 +171,8 @@ npm run req:commit -- 2026-001 --run --message-file commit-message.txt
|
|
|
160
171
|
| 명령 | 용도 |
|
|
161
172
|
|---|---|
|
|
162
173
|
| `npx commitgate` | 프로젝트에 CommitGate 설치 |
|
|
174
|
+
| `npx commitgate --dry-run` | 파일을 쓰지 않고 설치 계획 확인 |
|
|
175
|
+
| `npx commitgate --strict` | 낮은 `cross-spawn` 버전 경고를 설치 실패로 처리 |
|
|
163
176
|
| `req:new <slug> --run` | REQ 티켓, 브랜치, 설계문서 생성 |
|
|
164
177
|
| `req:review-codex <id> --kind design --run` | 설계 리뷰 |
|
|
165
178
|
| `req:review-codex <id> --kind phase --run` | 구현 리뷰 |
|
|
@@ -194,6 +207,9 @@ npm run req:commit -- 2026-001 --run --message-file commit-message.txt
|
|
|
194
207
|
**`state.json`이나 `responses/`는 왜 stage하면 안 되나요?**
|
|
195
208
|
워크플로 증거와 상태 파일입니다. source 커밋에 섞이면 승인 바인딩이 흐려지므로 `req:commit`이 막습니다.
|
|
196
209
|
|
|
210
|
+
**cross-spawn 버전 경고가 나오면 어떻게 하나요?**
|
|
211
|
+
대상 프로젝트의 기존 `cross-spawn`이 CommitGate가 검증한 하한보다 낮을 수 있다는 뜻입니다. `npm i -D cross-spawn@^7.0.6`으로 올리세요. CI나 보안 민감 환경에서는 `npx commitgate --strict`를 사용해 경고를 실패로 다루세요.
|
|
212
|
+
|
|
197
213
|
**두 번 설치하면 덮어쓰나요?**
|
|
198
214
|
아니요. 기존 파일은 건너뜁니다. 강제로 갱신하려면 `--force`를 사용하세요.
|
|
199
215
|
|
|
@@ -203,12 +219,17 @@ npm run req:commit -- 2026-001 --run --message-file commit-message.txt
|
|
|
203
219
|
|
|
204
220
|
현재 버전은 **Stage A: vendored scaffold 모델**입니다. 즉 `npx commitgate`가 대상 프로젝트에 워크플로 파일을 복사합니다.
|
|
205
221
|
|
|
222
|
+
현재 운영 중인 검증입니다.
|
|
223
|
+
|
|
224
|
+
- GitHub Actions에서 `ubuntu-latest`, `macos-latest`, `windows-latest` × Node 18/20/22 매트릭스를 실행합니다.
|
|
225
|
+
- `npm run smoke`는 pack tarball 설치본의 `commitgate` bin을 실행합니다.
|
|
226
|
+
- Windows `.cmd` 래퍼 주입 회귀 테스트가 패키지 매니저와 Codex wrapper 경로를 보호합니다.
|
|
227
|
+
|
|
206
228
|
아래는 후속 범위입니다.
|
|
207
229
|
|
|
208
230
|
- `node_modules`에서 직접 실행하는 라이브러리 모델
|
|
209
231
|
- 비-git VCS 지원
|
|
210
232
|
- 더 다양한 설계문서 템플릿
|
|
211
|
-
- Linux/macOS CI smoke 확대
|
|
212
233
|
|
|
213
234
|
---
|
|
214
235
|
|
package/bin/init.ts
CHANGED
|
@@ -25,6 +25,7 @@ import { resolve, join, dirname, relative } from 'node:path'
|
|
|
25
25
|
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
26
26
|
import { loadConfig, stripBom, type PackageManager } from '../scripts/req/lib/config'
|
|
27
27
|
import { createGitAdapter } from '../scripts/req/lib/adapters'
|
|
28
|
+
import * as semver from 'semver'
|
|
28
29
|
|
|
29
30
|
/** 이 패키지 루트(bin/ 기준 1단계 위). 복사 원본. */
|
|
30
31
|
const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
@@ -37,10 +38,13 @@ const REQ_SCRIPTS: Record<string, string> = {
|
|
|
37
38
|
'req:commit': 'tsx scripts/req/req-commit.ts',
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
/** cross-spawn 주입 spec(= 보안 하한 SSOT). 진단(#1)과 주입이 이 값을 공유. */
|
|
42
|
+
const CROSS_SPAWN_SPEC = '^7.0.6'
|
|
43
|
+
|
|
40
44
|
/** 대상 package.json에 주입할 devDeps(워크플로 실행 전제). cross-spawn = 복사된 adapters.ts의 안전 spawn(P1) 런타임 의존. */
|
|
41
45
|
const REQ_DEV_DEPS: Record<string, string> = {
|
|
42
46
|
ajv: '^8.20.0',
|
|
43
|
-
'cross-spawn':
|
|
47
|
+
'cross-spawn': CROSS_SPAWN_SPEC,
|
|
44
48
|
tsx: '^4.19.1',
|
|
45
49
|
}
|
|
46
50
|
|
|
@@ -48,6 +52,7 @@ export interface InitOptions {
|
|
|
48
52
|
dir: string
|
|
49
53
|
force: boolean
|
|
50
54
|
dryRun: boolean
|
|
55
|
+
strict: boolean // cross-spawn 하한 미만이면 WARN 대신 throw(#1)
|
|
51
56
|
}
|
|
52
57
|
|
|
53
58
|
export interface InitResult {
|
|
@@ -59,6 +64,7 @@ export interface InitResult {
|
|
|
59
64
|
packageJsonAdded: string[] // 추가된 script/devDep 키
|
|
60
65
|
agentsCreated: boolean
|
|
61
66
|
packageManager: PackageManager
|
|
67
|
+
crossSpawnFloorWarned: boolean // 기존 cross-spawn이 보안 하한 미만이라 경고(#1)
|
|
62
68
|
dryRun: boolean
|
|
63
69
|
}
|
|
64
70
|
|
|
@@ -142,6 +148,92 @@ function parseJsonObject(path: string, label: string): Record<string, unknown> {
|
|
|
142
148
|
return parsed as Record<string, unknown>
|
|
143
149
|
}
|
|
144
150
|
|
|
151
|
+
// ─────────────────────────────── cross-spawn 버전 하한 진단 (#1) ──
|
|
152
|
+
|
|
153
|
+
/** 보안 하한 = 주입 spec의 최소버전(SSOT — 하드코딩 이중화 금지). '^7.0.6' → 7.0.6. */
|
|
154
|
+
const CROSS_SPAWN_FLOOR = semver.minVersion(CROSS_SPAWN_SPEC)
|
|
155
|
+
|
|
156
|
+
/** obj가 plain object일 때 obj[key](문자열). 아니면 undefined. */
|
|
157
|
+
function stringField(obj: unknown, key: string): string | undefined {
|
|
158
|
+
if (obj && typeof obj === 'object' && !Array.isArray(obj)) {
|
|
159
|
+
const v = (obj as Record<string, unknown>)[key]
|
|
160
|
+
if (typeof v === 'string') return v
|
|
161
|
+
}
|
|
162
|
+
return undefined
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** 대상의 기존 cross-spawn spec(devDeps 우선, 없으면 deps). 없으면 null. */
|
|
166
|
+
function existingCrossSpawnSpec(pkg: Record<string, unknown>): string | null {
|
|
167
|
+
return stringField(pkg.devDependencies, 'cross-spawn') ?? stringField(pkg.dependencies, 'cross-spawn') ?? null
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** node_modules에 실제 설치된 cross-spawn 버전(valid semver). 없으면 null. */
|
|
171
|
+
function installedCrossSpawnVersion(targetRoot: string): string | null {
|
|
172
|
+
const p = join(targetRoot, 'node_modules', 'cross-spawn', 'package.json')
|
|
173
|
+
if (!existsSync(p)) return null
|
|
174
|
+
try {
|
|
175
|
+
const v = (JSON.parse(stripBom(readFileSync(p, 'utf8'))) as { version?: unknown }).version
|
|
176
|
+
return typeof v === 'string' && semver.valid(v) ? v : null
|
|
177
|
+
} catch {
|
|
178
|
+
return null
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** lockfile 해소 cross-spawn 버전(package-lock v2/v3 JSON 우선, pnpm/yarn best-effort). 없으면 null. */
|
|
183
|
+
function lockedCrossSpawnVersion(targetRoot: string): string | null {
|
|
184
|
+
const pl = join(targetRoot, 'package-lock.json')
|
|
185
|
+
if (existsSync(pl)) {
|
|
186
|
+
try {
|
|
187
|
+
const j = JSON.parse(stripBom(readFileSync(pl, 'utf8'))) as {
|
|
188
|
+
packages?: Record<string, { version?: unknown }>
|
|
189
|
+
dependencies?: Record<string, { version?: unknown }>
|
|
190
|
+
}
|
|
191
|
+
const v = j.packages?.['node_modules/cross-spawn']?.version ?? j.dependencies?.['cross-spawn']?.version
|
|
192
|
+
if (typeof v === 'string' && semver.valid(v)) return v
|
|
193
|
+
} catch {
|
|
194
|
+
/* best-effort */
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
for (const [file, re] of [
|
|
198
|
+
['pnpm-lock.yaml', /cross-spawn@(\d+\.\d+\.\d+)/],
|
|
199
|
+
['yarn.lock', /(?:^|\n)"?cross-spawn@[^\n]*:[\s\S]*?\n\s+version:?\s+"?(\d+\.\d+\.\d+)"?/],
|
|
200
|
+
] as const) {
|
|
201
|
+
const fp = join(targetRoot, file)
|
|
202
|
+
if (!existsSync(fp)) continue
|
|
203
|
+
try {
|
|
204
|
+
const m = readFileSync(fp, 'utf8').match(re)
|
|
205
|
+
if (m?.[1] && semver.valid(m[1])) return m[1]
|
|
206
|
+
} catch {
|
|
207
|
+
/* best-effort */
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return null
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* 기존 cross-spawn이 보안 하한 미만인지 판정(#1). 우선순위: **설치버전 → lockfile 해소버전 → range**.
|
|
215
|
+
* range fallback은 `>=floor`로 절대 해소 불가한 spec만 below로 본다(‘^7.0.0’·‘~7.0.1’ 오탐 방지 — R1 P2).
|
|
216
|
+
* 기존 cross-spawn 없으면 null(우리가 `^7.0.6` 주입 → 진단 불필요).
|
|
217
|
+
*/
|
|
218
|
+
export function crossSpawnBelowFloor(
|
|
219
|
+
targetRoot: string,
|
|
220
|
+
pkg: Record<string, unknown>,
|
|
221
|
+
): { below: boolean; detail: string } | null {
|
|
222
|
+
if (!CROSS_SPAWN_FLOOR) return null // 이론상 도달 불가(REQ_DEV_DEPS 고정값)
|
|
223
|
+
const floor = CROSS_SPAWN_FLOOR.version
|
|
224
|
+
const spec = existingCrossSpawnSpec(pkg)
|
|
225
|
+
if (!spec) return null
|
|
226
|
+
|
|
227
|
+
const installed = installedCrossSpawnVersion(targetRoot)
|
|
228
|
+
if (installed) return { below: semver.lt(installed, floor), detail: `설치버전 ${installed}` }
|
|
229
|
+
|
|
230
|
+
const locked = lockedCrossSpawnVersion(targetRoot)
|
|
231
|
+
if (locked) return { below: semver.lt(locked, floor), detail: `lockfile ${locked}` }
|
|
232
|
+
|
|
233
|
+
if (semver.validRange(spec)) return { below: !semver.intersects(spec, `>=${floor}`), detail: `범위 ${spec}` }
|
|
234
|
+
return { below: false, detail: `범위 ${spec}(파싱 불가 — 무경고)` }
|
|
235
|
+
}
|
|
236
|
+
|
|
145
237
|
/**
|
|
146
238
|
* 설치 코어. IO는 여기서만(테스트가 임시 repo로 직접 호출).
|
|
147
239
|
* **Preflight(전 검증·파싱) → Apply(쓰기) 2단계** — malformed 입력에 대해 어떤 파일도 복사·수정하기 전에 실패한다(부분 설치 방지, design R2 P2).
|
|
@@ -161,11 +253,23 @@ export function runInit(opts: InitOptions): InitResult {
|
|
|
161
253
|
scripts?: Record<string, string>
|
|
162
254
|
devDependencies?: Record<string, string>
|
|
163
255
|
}
|
|
164
|
-
// scripts·devDependencies가 존재하면 반드시 plain object — 배열/원시면 patch
|
|
165
|
-
|
|
256
|
+
// scripts·devDependencies·dependencies가 존재하면 반드시 plain object — 배열/원시면 patch 유실(scripts/devDeps, phase R1 P2)
|
|
257
|
+
// 또는 cross-spawn 진단(dependencies, design R1 P3) 오동작. 읽기 전에 shape 검증(fail-closed).
|
|
258
|
+
for (const field of ['scripts', 'devDependencies', 'dependencies'] as const) {
|
|
166
259
|
const v = (pkg as Record<string, unknown>)[field]
|
|
167
260
|
if (v !== undefined && (typeof v !== 'object' || v === null || Array.isArray(v)))
|
|
168
|
-
throw new Error(`package.json의 ${field} 필드가 객체가 아님(${pkgPath}) —
|
|
261
|
+
throw new Error(`package.json의 ${field} 필드가 객체가 아님(${pkgPath}) — 배열/원시 미지원.`)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// cross-spawn 보안 하한 진단(#1): 기존 cross-spawn이 하한 미만이면 WARN(기본)/throw(--strict). preflight라 strict throw 시 부분 설치 없음.
|
|
265
|
+
let crossSpawnFloorWarned = false
|
|
266
|
+
const floorCheck = crossSpawnBelowFloor(targetRoot, pkg as Record<string, unknown>)
|
|
267
|
+
if (floorCheck?.below) {
|
|
268
|
+
const spec = CROSS_SPAWN_SPEC
|
|
269
|
+
const msg = `기존 cross-spawn(${floorCheck.detail})이 보안 하한 >=${CROSS_SPAWN_FLOOR?.version} 미만 — CommitGate 안전 경계(safeSpawnSync)는 ${spec} 검증분입니다. 'npm i -D cross-spawn@${spec}' 권장.`
|
|
270
|
+
if (opts.strict) throw new Error(`[--strict] ${msg}`)
|
|
271
|
+
console.warn(`⚠️ ${msg} (설치는 계속 — 강제 중단하려면 --strict)`)
|
|
272
|
+
crossSpawnFloorWarned = true
|
|
169
273
|
}
|
|
170
274
|
|
|
171
275
|
const cfgPath = join(targetRoot, 'req.config.json')
|
|
@@ -247,6 +351,7 @@ export function runInit(opts: InitOptions): InitResult {
|
|
|
247
351
|
packageJsonAdded,
|
|
248
352
|
agentsCreated,
|
|
249
353
|
packageManager,
|
|
354
|
+
crossSpawnFloorWarned,
|
|
250
355
|
dryRun: opts.dryRun,
|
|
251
356
|
}
|
|
252
357
|
}
|
|
@@ -255,6 +360,7 @@ export function parseArgs(argv: string[]): InitOptions {
|
|
|
255
360
|
let dir = process.cwd()
|
|
256
361
|
let force = false
|
|
257
362
|
let dryRun = false
|
|
363
|
+
let strict = false
|
|
258
364
|
for (let i = 0; i < argv.length; i++) {
|
|
259
365
|
const a = argv[i]
|
|
260
366
|
if (a === '--dir') {
|
|
@@ -266,6 +372,8 @@ export function parseArgs(argv: string[]): InitOptions {
|
|
|
266
372
|
force = true
|
|
267
373
|
} else if (a === '--dry-run') {
|
|
268
374
|
dryRun = true
|
|
375
|
+
} else if (a === '--strict') {
|
|
376
|
+
strict = true
|
|
269
377
|
} else if (a === '-h' || a === '--help') {
|
|
270
378
|
printHelp()
|
|
271
379
|
process.exit(0)
|
|
@@ -273,7 +381,7 @@ export function parseArgs(argv: string[]): InitOptions {
|
|
|
273
381
|
throw new Error(`알 수 없는 인자: ${a}`)
|
|
274
382
|
}
|
|
275
383
|
}
|
|
276
|
-
return { dir: resolve(dir), force, dryRun }
|
|
384
|
+
return { dir: resolve(dir), force, dryRun, strict }
|
|
277
385
|
}
|
|
278
386
|
|
|
279
387
|
function printHelp(): void {
|
|
@@ -286,6 +394,7 @@ function printHelp(): void {
|
|
|
286
394
|
--dir <path> 대상 repo 루트(기본: 현재 디렉터리)
|
|
287
395
|
--force 기존 kit 파일 덮어쓰기(기본: 스킵)
|
|
288
396
|
--dry-run 변경 없이 수행 예정 목록만 출력
|
|
397
|
+
--strict 기존 cross-spawn이 보안 하한(>=7.0.6) 미만이면 경고 대신 중단(fail-closed)
|
|
289
398
|
-h, --help 도움말
|
|
290
399
|
|
|
291
400
|
설치 후:
|
|
@@ -315,6 +424,7 @@ export function main(argv: string[]): void {
|
|
|
315
424
|
`${tag} package.json: ${r.packageJsonAdded.length > 0 ? '추가 ' + r.packageJsonAdded.join(', ') : '변경 없음'}`,
|
|
316
425
|
)
|
|
317
426
|
console.log(`${tag} AGENTS.md: ${r.agentsCreated ? '템플릿 생성' : '이미 존재(유지)'}`)
|
|
427
|
+
if (r.crossSpawnFloorWarned) console.log(`${tag} ⚠️ cross-spawn 버전 하한 경고(위 참조) — 강제 중단은 --strict`)
|
|
318
428
|
if (!r.dryRun) {
|
|
319
429
|
console.log(`\n다음:`)
|
|
320
430
|
console.log(` 1. cd ${r.targetRoot} && ${r.packageManager} install`)
|
package/package.json
CHANGED
|
@@ -1,63 +1,66 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "commitgate",
|
|
3
|
-
"version": "0.1
|
|
4
|
-
"description": "CommitGate — Builder↔Reviewer(Claude↔Codex) fail-closed 커밋 게이트: 리뷰·승인·증거 없인 커밋을 통과시키지 않는 AI REQ 워크플로 kit (req:new/review-codex/doctor/commit)",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"license": "MIT",
|
|
7
|
-
"author": "sol5288",
|
|
8
|
-
"repository": {
|
|
9
|
-
"type": "git",
|
|
10
|
-
"url": "git+https://github.com/sol5288/commitgate.git"
|
|
11
|
-
},
|
|
12
|
-
"homepage": "https://github.com/sol5288/commitgate#readme",
|
|
13
|
-
"bugs": {
|
|
14
|
-
"url": "https://github.com/sol5288/commitgate/issues"
|
|
15
|
-
},
|
|
16
|
-
"keywords": [
|
|
17
|
-
"ai",
|
|
18
|
-
"code-review",
|
|
19
|
-
"workflow",
|
|
20
|
-
"codex",
|
|
21
|
-
"claude",
|
|
22
|
-
"git",
|
|
23
|
-
"commit-gate",
|
|
24
|
-
"fail-closed",
|
|
25
|
-
"review-gate",
|
|
26
|
-
"cli"
|
|
27
|
-
],
|
|
28
|
-
"bin": {
|
|
29
|
-
"commitgate": "bin/commitgate.mjs"
|
|
30
|
-
},
|
|
31
|
-
"scripts": {
|
|
32
|
-
"req:new": "tsx scripts/req/req-new.ts",
|
|
33
|
-
"req:review-codex": "tsx scripts/req/review-codex.ts",
|
|
34
|
-
"req:doctor": "tsx scripts/req/req-doctor.ts",
|
|
35
|
-
"req:commit": "tsx scripts/req/req-commit.ts",
|
|
36
|
-
"test": "vitest run",
|
|
37
|
-
"typecheck": "tsc --noEmit"
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
"
|
|
42
|
-
"workflow/
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"README.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
|
|
63
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "commitgate",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "CommitGate — Builder↔Reviewer(Claude↔Codex) fail-closed 커밋 게이트: 리뷰·승인·증거 없인 커밋을 통과시키지 않는 AI REQ 워크플로 kit (req:new/review-codex/doctor/commit)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "sol5288",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/sol5288/commitgate.git"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/sol5288/commitgate#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/sol5288/commitgate/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"ai",
|
|
18
|
+
"code-review",
|
|
19
|
+
"workflow",
|
|
20
|
+
"codex",
|
|
21
|
+
"claude",
|
|
22
|
+
"git",
|
|
23
|
+
"commit-gate",
|
|
24
|
+
"fail-closed",
|
|
25
|
+
"review-gate",
|
|
26
|
+
"cli"
|
|
27
|
+
],
|
|
28
|
+
"bin": {
|
|
29
|
+
"commitgate": "bin/commitgate.mjs"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"req:new": "tsx scripts/req/req-new.ts",
|
|
33
|
+
"req:review-codex": "tsx scripts/req/review-codex.ts",
|
|
34
|
+
"req:doctor": "tsx scripts/req/req-doctor.ts",
|
|
35
|
+
"req:commit": "tsx scripts/req/req-commit.ts",
|
|
36
|
+
"test": "vitest run",
|
|
37
|
+
"typecheck": "tsc --noEmit",
|
|
38
|
+
"smoke": "node scripts/smoke.mjs"
|
|
39
|
+
},
|
|
40
|
+
"files": [
|
|
41
|
+
"scripts/req",
|
|
42
|
+
"workflow/machine.schema.json",
|
|
43
|
+
"workflow/req.config.schema.json",
|
|
44
|
+
"bin",
|
|
45
|
+
"AGENTS.template.md",
|
|
46
|
+
"req.config.json.sample",
|
|
47
|
+
"README.md",
|
|
48
|
+
"README.en.md"
|
|
49
|
+
],
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=18.17"
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"ajv": "^8.20.0",
|
|
55
|
+
"cross-spawn": "^7.0.6",
|
|
56
|
+
"semver": "^7.6.3",
|
|
57
|
+
"tsx": "^4.19.1"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@types/cross-spawn": "^6.0.6",
|
|
61
|
+
"@types/node": "^22.7.4",
|
|
62
|
+
"@types/semver": "^7.5.8",
|
|
63
|
+
"typescript": "^5.6.2",
|
|
64
|
+
"vitest": "^2.1.2"
|
|
65
|
+
}
|
|
66
|
+
}
|