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.
- package/AGENTS.template.md +8 -0
- package/CHANGELOG.md +115 -0
- package/README.en.md +112 -17
- package/README.md +117 -17
- package/bin/commitgate.mjs +15 -6
- package/bin/dispatch.d.mts +6 -0
- package/bin/dispatch.mjs +38 -0
- package/bin/init.ts +788 -78
- package/bin/migrate.ts +244 -0
- package/bin/uninstall.ts +43 -1
- package/package.json +6 -3
- package/req.config.json.sample +2 -0
- package/scripts/req/lib/adapters.ts +56 -8
- package/scripts/req/lib/config.ts +25 -0
- 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 +33 -17
- package/scripts/req/review-codex.ts +198 -68
- package/scripts/verify-review-overrides.mjs +96 -0
- package/templates/CLAUDE.template.md +2 -1
- package/templates/claude-command.md +5 -2
- package/templates/claude-skill.md +4 -1
- package/templates/cursor-rule.mdc +5 -2
- package/templates/workflow.gitignore +8 -0
- package/workflow/machine.schema.json +5 -1
- package/workflow/req.config.schema.json +5 -0
- package/workflow/review-persona.md +22 -1
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* REQ-2026-013 P1 — 리뷰 모델·추론강도 override **실효성** live 검증(수동/smoke).
|
|
4
|
+
*
|
|
5
|
+
* arg-캡처 단위 테스트(tests/unit/req-adapters.test.ts)는 도구가 `-c` 를 **넘기는지**만 본다.
|
|
6
|
+
* codex가 그 override를 **존중하는지**(무시하고 전역 상속하지 않는지)는 live로만 확인된다 —
|
|
7
|
+
* 자기-리뷰 성공은 "적용"과 "무시하고 ultra 상속"을 구분 못 하기 때문(설계 D7).
|
|
8
|
+
*
|
|
9
|
+
* 그래서 **bogus 값**을 주고 codex가 **거부**하면 override가 codex에 도달·해석됐다는 증거다:
|
|
10
|
+
* - bogus model → `... model is not supported` / `Model metadata for ... not found`
|
|
11
|
+
* - bogus effort → `[reasoning.effort] [invalid_enum_value]`
|
|
12
|
+
* exec·resume 두 경로 각각에 대해 확인한다(어댑터가 `-c` 를 양쪽에 주입하므로).
|
|
13
|
+
*
|
|
14
|
+
* ⚠️ 실제 codex CLI + 인증이 필요하다(CI 게이트 아님 — 로컬/수동 실행). exit 0 = 4/4 통과.
|
|
15
|
+
* 사용: `node scripts/verify-review-overrides.mjs`
|
|
16
|
+
*/
|
|
17
|
+
import spawn from 'cross-spawn'
|
|
18
|
+
|
|
19
|
+
const BOGUS_MODEL = '__bogus_model_xyz__'
|
|
20
|
+
const BOGUS_EFFORT = '__bogus_effort_xyz__'
|
|
21
|
+
const VALID_MODEL = 'gpt-5.6-terra'
|
|
22
|
+
const VALID_EFFORT = 'high'
|
|
23
|
+
|
|
24
|
+
/** 어댑터(adapters.ts:review)와 동일한 `-c` 오버라이드 조립. */
|
|
25
|
+
function overrideArgs(model, effort) {
|
|
26
|
+
const a = []
|
|
27
|
+
if (model) a.push('-c', `model="${model}"`)
|
|
28
|
+
if (effort) a.push('-c', `model_reasoning_effort="${effort}"`)
|
|
29
|
+
return a
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** codex 한 번 실행(exec 또는 resume) → { text: 합쳐진 오류 메시지, code }. cross-spawn(어댑터와 동일 spawn). */
|
|
33
|
+
function runCodex({ resumeThreadId, model, effort }) {
|
|
34
|
+
const base = resumeThreadId
|
|
35
|
+
? ['exec', 'resume', resumeThreadId, '-c', 'sandbox_mode="read-only"', ...overrideArgs(model, effort), '--json', '-']
|
|
36
|
+
: ['exec', ...overrideArgs(model, effort), '--json', '--sandbox', 'read-only', '-']
|
|
37
|
+
const r = spawn.sync('codex', base, { input: 'reply with the single word ok', encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 })
|
|
38
|
+
const out = (r.stdout || '') + '\n' + (r.stderr || '')
|
|
39
|
+
// JSONL에서 turn.failed/error/item.completed(error)의 message를 모은다(실측 계약).
|
|
40
|
+
let msgs = ''
|
|
41
|
+
let threadId = null
|
|
42
|
+
for (const line of out.split('\n')) {
|
|
43
|
+
const t = line.trim()
|
|
44
|
+
if (!t) continue
|
|
45
|
+
try {
|
|
46
|
+
const ev = JSON.parse(t)
|
|
47
|
+
if (ev.type === 'thread.started' && typeof ev.thread_id === 'string') threadId = ev.thread_id
|
|
48
|
+
if (ev.type === 'error' && typeof ev.message === 'string') msgs += ev.message + '\n'
|
|
49
|
+
if (ev.type === 'turn.failed' && ev.error?.message) msgs += ev.error.message + '\n'
|
|
50
|
+
if (ev.type === 'item.completed' && ev.item?.type === 'error' && ev.item?.message) msgs += ev.item.message + '\n'
|
|
51
|
+
} catch {
|
|
52
|
+
msgs += t + '\n' // 비-JSONL(에러 텍스트)도 포함
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return { text: msgs + out, code: r.status, threadId }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let pass = 0
|
|
59
|
+
let fail = 0
|
|
60
|
+
function check(label, cond, detail) {
|
|
61
|
+
if (cond) {
|
|
62
|
+
pass++
|
|
63
|
+
console.log(`PASS ${label}`)
|
|
64
|
+
} else {
|
|
65
|
+
fail++
|
|
66
|
+
console.log(`FAIL ${label}\n ${detail}`)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const modelRejected = (t) => /not supported|not found|invalid.*model|unknown model/i.test(t)
|
|
71
|
+
const effortRejected = (t) => /reasoning.?effort|invalid_enum_value/i.test(t)
|
|
72
|
+
|
|
73
|
+
console.log('REQ-2026-013 P1 override 실효성 live 검증 (codex CLI 필요)\n')
|
|
74
|
+
|
|
75
|
+
// 1) 유효 override로 throwaway 스레드 확보(resume 검증용).
|
|
76
|
+
const seed = runCodex({ resumeThreadId: null, model: VALID_MODEL, effort: VALID_EFFORT })
|
|
77
|
+
if (!seed.threadId) {
|
|
78
|
+
console.log(`FAIL seed exec가 thread_id를 반환하지 못함 — 유효 model/effort(${VALID_MODEL}/${VALID_EFFORT})로 실행 실패?\n ${seed.text.slice(0, 400)}`)
|
|
79
|
+
process.exit(1)
|
|
80
|
+
}
|
|
81
|
+
console.log(`(seed thread = ${seed.threadId})\n`)
|
|
82
|
+
|
|
83
|
+
// 2) exec — bogus model / bogus effort
|
|
84
|
+
const em = runCodex({ resumeThreadId: null, model: BOGUS_MODEL, effort: VALID_EFFORT })
|
|
85
|
+
check('exec + bogus model → codex 거부', modelRejected(em.text), em.text.slice(0, 300))
|
|
86
|
+
const ee = runCodex({ resumeThreadId: null, model: VALID_MODEL, effort: BOGUS_EFFORT })
|
|
87
|
+
check('exec + bogus effort → codex 거부', effortRejected(ee.text), ee.text.slice(0, 300))
|
|
88
|
+
|
|
89
|
+
// 3) resume — bogus model / bogus effort (override가 resume에서도 재적용됨을 확인)
|
|
90
|
+
const rm = runCodex({ resumeThreadId: seed.threadId, model: BOGUS_MODEL, effort: VALID_EFFORT })
|
|
91
|
+
check('resume + bogus model → codex 거부', modelRejected(rm.text), rm.text.slice(0, 300))
|
|
92
|
+
const re = runCodex({ resumeThreadId: seed.threadId, model: VALID_MODEL, effort: BOGUS_EFFORT })
|
|
93
|
+
check('resume + bogus effort → codex 거부', effortRejected(re.text), re.text.slice(0, 300))
|
|
94
|
+
|
|
95
|
+
console.log(`\n${pass}/${pass + fail} 통과`)
|
|
96
|
+
process.exit(fail === 0 ? 0 : 1)
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
- **계약 정본**: 저장소 루트의 [`AGENTS.md`](./AGENTS.md). 절대 규칙·통제점·승인 문장이 거기 있다.
|
|
12
12
|
(`<!-- commitgate:contract -->` 마커가 없으면 CommitGate 계약이 아니다 — init이 함께 설치한 루트의 `AGENTS.commitgate.md`를 계약으로 읽고, 사용자에게 `AGENTS.md`로의 병합을 요청하라.)
|
|
13
|
-
- **다음 행동은 추측하지 않는다**: `
|
|
13
|
+
- **다음 행동은 추측하지 않는다**: `req:next <REQ-id>`가 알려 준다.
|
|
14
14
|
`RUN`은 그대로 실행, `AGENT`는 그 작업 수행 후 `git add`, `AWAIT_HUMAN`은 **멈추고 승인 문장을 그대로** 받는다.
|
|
15
|
+
워크플로 명령은 이 저장소의 **패키지매니저 실행 형식**으로 돌린다. `req:next`의 `RUN` 출력이 정확한 형태를 그대로 보여 준다.
|
|
15
16
|
- 자세한 진입 절차는 `/req` 슬래시 커맨드 또는 `.claude/skills/commitgate/SKILL.md`에 있다.
|
|
@@ -25,8 +25,11 @@ $ARGUMENTS
|
|
|
25
25
|
|
|
26
26
|
## 절차
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
> 아래 명령은 **저장소의 패키지매니저 실행 형식**으로 돌린다(npm은 `run`과 `--` 구분자가 필요하고, pnpm·yarn은 스크립트 이름을 바로 받는다).
|
|
29
|
+
> `npx commitgate` 설치 출력과 `req:next`의 `RUN` 출력이 언제나 이 저장소에 맞는 정확한 형태를 보여 준다 — 그걸 그대로 쓰면 된다.
|
|
30
|
+
|
|
31
|
+
1. `req:new <slug> --run` — 티켓과 브랜치를 만든다.
|
|
32
|
+
2. 그다음부터는 **`req:next <REQ-id>`가 시키는 대로** 한다.
|
|
30
33
|
- `RUN` → 출력된 명령을 그대로 실행 → 다시 `req:next`
|
|
31
34
|
- `AGENT` → 그 작업을 하고 `git add` → 다시 `req:next`
|
|
32
35
|
- `AWAIT_HUMAN` → **멈추고** 출력된 승인 문장을 그대로 받는다
|
|
@@ -35,9 +35,12 @@ description: 이 저장소의 REQ 워크플로(CommitGate)로 요구사항을
|
|
|
35
35
|
**다음 행동을 스스로 추측하지 마라.** 도구가 상태에서 계산해 준다.
|
|
36
36
|
|
|
37
37
|
```sh
|
|
38
|
-
|
|
38
|
+
req:next <REQ-id>
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
+
> 워크플로 명령은 이 저장소의 **패키지매니저 실행 형식**으로 돌린다(npm은 `run`과 `--` 구분자가 필요하고, pnpm·yarn은 스크립트 이름을 바로 받는다).
|
|
42
|
+
> `req:next`의 `RUN` 출력이 언제나 정확한 형태를 그대로 보여 주므로, 그 줄을 복사해 쓰면 된다.
|
|
43
|
+
|
|
41
44
|
출력의 `kind`가 정본이다(`--json`으로 기계 판독 가능).
|
|
42
45
|
|
|
43
46
|
| kind | 할 일 |
|
|
@@ -33,10 +33,13 @@ alwaysApply: true
|
|
|
33
33
|
다음 행동을 스스로 추측하지 마라. 도구가 상태에서 계산한다.
|
|
34
34
|
|
|
35
35
|
```sh
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
req:new <slug> --run # 티켓·브랜치 생성
|
|
37
|
+
req:next <REQ-id> # 그다음은 항상 이것
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
+
> 이 명령들은 저장소의 **패키지매니저 실행 형식**으로 돌린다(npm은 `run`과 `--` 구분자가 필요하고, pnpm·yarn은 스크립트 이름을 바로 받는다).
|
|
41
|
+
> `req:next`의 `RUN` 출력이 정확한 형태를 그대로 보여 준다.
|
|
42
|
+
|
|
40
43
|
| kind | 할 일 |
|
|
41
44
|
|---|---|
|
|
42
45
|
| `RUN` | 출력된 명령을 그대로 실행하고 다시 `req:next` |
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# CommitGate 워크플로 스크래치 산출물(티켓 내부) — commitgate init이 대상 repo의 workflow/.gitignore로 설치.
|
|
2
|
+
#
|
|
3
|
+
# ⚠️ 이 패턴은 **이 .gitignore 파일이 있는 디렉터리(= workflow/) 기준 상대경로**다(중첩 .gitignore, gitignore(5)).
|
|
4
|
+
# 루트 .gitignore가 쓰는 `workflow/**/…` 형태를 여기 그대로 복사하면 `workflow/workflow/…`를 찾아 무효가 된다.
|
|
5
|
+
# `/REQ-*/…`로 **앵커드**해 티켓 직계만 무시한다 — 티켓 밖에 흘러든 동명 파일까지 숨기지 않는다(fail-closed 정합).
|
|
6
|
+
/REQ-*/codex-response.json
|
|
7
|
+
/REQ-*/.review-preview.txt
|
|
8
|
+
/REQ-*/.codex-*.tmp
|
|
@@ -31,7 +31,11 @@
|
|
|
31
31
|
"additionalProperties": false,
|
|
32
32
|
"required": ["severity", "detail", "file"],
|
|
33
33
|
"properties": {
|
|
34
|
-
"severity": {
|
|
34
|
+
"severity": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"enum": ["P1", "P2", "P3"],
|
|
37
|
+
"description": "Blocking severity. ONLY P1 belongs in findings, and the output schema you are given permits P1 only — P2/P3 remain in this enum solely so previously archived reviews still validate, and you MUST NOT emit them. A defect is P1 only when ALL THREE hold. (1) CATEGORY: it is a requirement violation, data loss or corruption, a security hole, a monetary error, or a fail-closed bypass. (2) NORMAL PATH: it reproduces on the normal supported usage path — this project supports a single active worktree and a cooperative worker, so rare recovery races, multi-worktree divergence and full distributed consistency are outside the supported model. (3) EVIDENCE: you state a concrete reproduction path or failure scenario ('with this input/state, this wrong result occurs'). EXCLUSION RULE: if it is not in the CATEGORY list it is NOT P1 — even if it reproduces on the normal path, and even if it is genuinely worth fixing. Portability, structure, maintainability, readability, naming, future extensibility, scope-adjacent improvements and follow-up debt all belong in observations, never in findings. If you are guessing or it 'might' be a problem, that is observations too. Putting non-P1 remarks in findings is the failure mode this field exists to prevent: it blocks approval indefinitely and the review never converges."
|
|
38
|
+
},
|
|
35
39
|
"detail": { "type": "string" },
|
|
36
40
|
"file": { "type": ["string", "null"] }
|
|
37
41
|
}
|
|
@@ -9,6 +9,11 @@
|
|
|
9
9
|
"branchPrefix": { "type": "string", "minLength": 1 },
|
|
10
10
|
"packageManager": { "type": "string", "enum": ["pnpm", "npm", "yarn"] },
|
|
11
11
|
"granularityMaxFiles": { "type": "integer", "minimum": 1 },
|
|
12
|
+
"reviewModel": { "type": ["string", "null"], "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*$" },
|
|
13
|
+
"reviewReasoningEffort": {
|
|
14
|
+
"type": ["string", "null"],
|
|
15
|
+
"enum": ["none", "minimal", "low", "medium", "high", "xhigh", null]
|
|
16
|
+
},
|
|
12
17
|
"designDocs": {
|
|
13
18
|
"type": "object",
|
|
14
19
|
"additionalProperties": false,
|
|
@@ -4,7 +4,28 @@
|
|
|
4
4
|
|
|
5
5
|
- Builder가 작성한 리뷰 요청서(`codex-request.md`)의 "리뷰 포인트"는 심사 범위의 **하한**이지 상한이 아니다. 요청서가 묻지 않은 결함도 스스로 분석해 지적하라.
|
|
6
6
|
- Builder가 짜 놓은 리뷰 프레임에 갇히지 마라. 무엇을 봐야 하는지는 네가 판단한다.
|
|
7
|
-
- 개발
|
|
7
|
+
- 개발 부채를 식별하되 **`observations`에 기록해 다음 티켓의 입력으로 만들어라**. 지금 넘어가도 되는 부채까지 차단하는 것은 리뷰의 실패다.
|
|
8
|
+
|
|
9
|
+
## 보장 범위 경계 (이 경계 밖은 결함이 아니다)
|
|
10
|
+
|
|
11
|
+
**넓게 보되, 이 프로젝트가 약속한 범위 안에서 판정하라.** 탐색 범위와 차단 범위는 다르다.
|
|
12
|
+
|
|
13
|
+
- 이 프로젝트는 **하나의 활성 worktree와 협조적 작업자**만 지원한다. CommitGate는 정상적인 반복 호출에서 실수를 막는 도구이지, 비협조적·분산 동시 실행을 합의 없이 해결하는 시스템이 아니다.
|
|
14
|
+
- 따라서 **다중 worktree state 발산·드문 recovery 경합·완전한 분산 정합성**은 지원 범위 밖이다. 이것들은 결함이 아니라 **명시된 경계**다.
|
|
15
|
+
- 지원하지 않는 운영 모델을 근거로 차단하지 마라. 그 경계를 푸는 **새 서브시스템을 요구하지 마라**.
|
|
16
|
+
- **정상 사용 경로를 우선하라.** 정상 경로에서 재현되지 않는 이론적 조합은 `observations`다.
|
|
17
|
+
|
|
18
|
+
## P1 정의 (차단의 유일한 기준)
|
|
19
|
+
|
|
20
|
+
`findings`에는 **P1만** 넣는다. P1은 아래 셋을 **모두** 만족할 때만 성립한다.
|
|
21
|
+
|
|
22
|
+
1. **카테고리**: 요구 위반 · 데이터 손상 · 보안 구멍 · 금전 오류 · fail-closed 우회 중 하나다.
|
|
23
|
+
2. **정상 경로**: 정상 사용 경로에서 재현된다.
|
|
24
|
+
3. **증거**: 재현 경로나 실패 시나리오를 명시했다.
|
|
25
|
+
|
|
26
|
+
**배제 규칙**: 카테고리에 없으면 **정상 경로에서 재현되더라도, 고칠 가치가 있더라도 P1이 아니다.** 포터빌리티·구조·유지보수·가독성·이름·장래 확장성·범위 인접 개선·후속 부채는 전부 `observations`다. 추측이면 `observations`다.
|
|
27
|
+
|
|
28
|
+
**절대 표현 금지**: "모든 경우"·"우회 불가"·"어떤 worktree에서도"는 transactional backend가 있을 때만 쓸 수 있다. 설계가 그런 절대 보장을 약속하지 않았다는 이유로 차단하지 마라.
|
|
8
29
|
|
|
9
30
|
## 판정은 구조화 응답 필드로만 낸다
|
|
10
31
|
|