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.
- package/AGENTS.template.md +9 -0
- package/README.en.md +85 -35
- package/README.md +87 -37
- package/bin/init.ts +156 -7
- package/bin/uninstall.ts +54 -12
- package/package.json +5 -2
- package/req.config.json.sample +14 -13
- package/scripts/req/lib/config.ts +47 -6
- package/scripts/req/req-commit.ts +1 -1
- package/scripts/req/req-doctor.ts +0 -1
- package/scripts/req/req-new.ts +1 -1
- package/scripts/req/req-next.ts +638 -0
- package/scripts/req/review-codex.ts +154 -10
- package/templates/CLAUDE.template.md +15 -0
- package/templates/claude-command.md +36 -0
- package/templates/claude-skill.md +59 -0
- package/templates/cursor-rule.mdc +55 -0
- package/workflow/req.config.schema.json +1 -0
- package/workflow/review-persona.md +45 -0
package/bin/uninstall.ts
CHANGED
|
@@ -21,12 +21,17 @@ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'
|
|
|
21
21
|
import { createHash } from 'node:crypto'
|
|
22
22
|
import { resolve, join, relative } from 'node:path'
|
|
23
23
|
import { pathToFileURL } from 'node:url'
|
|
24
|
-
import { loadConfig, stripBom, DEFAULTS } from '../scripts/req/lib/config'
|
|
24
|
+
import { loadConfig, stripBom, DEFAULTS, DEFAULT_REVIEW_PERSONA_RELPATH } from '../scripts/req/lib/config'
|
|
25
25
|
import { createGitAdapter, type GitAdapter, type GitRunner } from '../scripts/req/lib/adapters'
|
|
26
26
|
import {
|
|
27
27
|
PACKAGE_ROOT,
|
|
28
28
|
KIT_SOURCE_DIR_REL,
|
|
29
29
|
KIT_SCHEMA_RELPATHS,
|
|
30
|
+
KIT_COPY_RELPATHS,
|
|
31
|
+
KIT_AGENT_ENTRYPOINTS,
|
|
32
|
+
KIT_CLAUDE_TEMPLATE_REL,
|
|
33
|
+
KIT_CLAUDE_DEST_REL,
|
|
34
|
+
KIT_AGENTS_CONTRACT_COPY_REL,
|
|
30
35
|
REQ_SCRIPTS,
|
|
31
36
|
REQ_DEV_DEPS,
|
|
32
37
|
assertGitWorkTree,
|
|
@@ -36,7 +41,11 @@ export interface UninstallOptions {
|
|
|
36
41
|
dir: string
|
|
37
42
|
}
|
|
38
43
|
|
|
39
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* CommitGate가 복사한 파일. `path`는 **대상 repo의 경로(dest)**다.
|
|
46
|
+
* 원본(src)은 패키지 안에서 경로가 다를 수 있다(`templates/claude-skill.md` → `.claude/skills/commitgate/SKILL.md`).
|
|
47
|
+
* 바이트 비교로 무결성만 표기.
|
|
48
|
+
*/
|
|
40
49
|
export interface ToolArtifact {
|
|
41
50
|
path: string // repo-상대
|
|
42
51
|
present: boolean
|
|
@@ -173,11 +182,13 @@ export function collectFacts(opts: UninstallOptions, run?: GitRunner): Uninstall
|
|
|
173
182
|
// (깨진 config 때문에 제거 안내를 못 받는 건 부당하다 — planner는 쓰기가 없어 강등이 안전).
|
|
174
183
|
let ticketRoot = DEFAULTS.ticketRoot
|
|
175
184
|
let schemaPath = DEFAULTS.schemaPath
|
|
185
|
+
let reviewPersonaPath = DEFAULTS.reviewPersonaPath
|
|
176
186
|
let configError: string | null = null
|
|
177
187
|
try {
|
|
178
188
|
const cfg = loadConfig({ root: targetRoot })
|
|
179
189
|
ticketRoot = cfg.ticketRoot
|
|
180
190
|
schemaPath = cfg.schemaPath
|
|
191
|
+
reviewPersonaPath = cfg.reviewPersonaPath
|
|
181
192
|
} catch (e) {
|
|
182
193
|
configError = (e as Error).message
|
|
183
194
|
}
|
|
@@ -190,25 +201,41 @@ export function collectFacts(opts: UninstallOptions, run?: GitRunner): Uninstall
|
|
|
190
201
|
info.push(
|
|
191
202
|
`schemaPath=${schemaPath} — 런타임이 읽는 경로이지만 init이 복사한 파일이 아닙니다(제거 후보 아님).`,
|
|
192
203
|
)
|
|
204
|
+
// reviewPersonaPath도 같은 축 분기(REQ-2026-010). null=의도적 비활성이므로 알릴 것이 없다.
|
|
205
|
+
if (reviewPersonaPath !== null && reviewPersonaPath !== DEFAULT_REVIEW_PERSONA_RELPATH)
|
|
206
|
+
info.push(
|
|
207
|
+
`reviewPersonaPath=${reviewPersonaPath} — 런타임이 읽는 경로이지만 init이 복사한 파일이 아닙니다(제거 후보 아님).`,
|
|
208
|
+
)
|
|
193
209
|
|
|
194
|
-
// ── tool: kit 소스 + init이 **실제로 복사한**
|
|
210
|
+
// ── tool: kit 소스 + init이 **실제로 복사한** 파일들.
|
|
211
|
+
//
|
|
212
|
+
// ⚠️ `src`와 `dest`가 다른 항목이 있다(REQ-2026-010 D8). `KIT_AGENT_ENTRYPOINTS`는 `templates/*` →
|
|
213
|
+
// `.claude/…`·`.cursor/…`로 가고, `AGENTS.commitgate.md`는 `AGENTS.template.md`의 사본이다.
|
|
214
|
+
// "원본을 `join(PACKAGE_ROOT, rel)`로 찾는다"는 가정을 버리고 매핑을 명시한다.
|
|
215
|
+
// 복사 축(KIT_COPY_RELPATHS)을 쓴다. 위 info의 스키마 축(KIT_SCHEMA_RELPATHS)과 의도적으로 다른 상수다.
|
|
195
216
|
const kitSourceRels = walkFiles(join(PACKAGE_ROOT, KIT_SOURCE_DIR_REL)).map(toRel)
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
217
|
+
const toolEntries: { destRel: string; srcRel: string }[] = [
|
|
218
|
+
...kitSourceRels.map((r) => ({ destRel: r, srcRel: r })),
|
|
219
|
+
...KIT_COPY_RELPATHS.map((r) => ({ destRel: r, srcRel: r })),
|
|
220
|
+
...KIT_AGENT_ENTRYPOINTS.map((e) => ({ destRel: e.dest, srcRel: e.src })),
|
|
221
|
+
// 마커 없는 AGENTS.md 경로에서만 설치되는 계약 템플릿 사본. 부재가 정상이므로 present=false여도 문제 없다.
|
|
222
|
+
{ destRel: KIT_AGENTS_CONTRACT_COPY_REL, srcRel: 'AGENTS.template.md' },
|
|
223
|
+
]
|
|
224
|
+
const tool: ToolArtifact[] = toolEntries.map(({ destRel, srcRel }) => {
|
|
225
|
+
const dest = join(targetRoot, destRel)
|
|
226
|
+
const src = join(PACKAGE_ROOT, srcRel)
|
|
200
227
|
if (!existsSync(dest))
|
|
201
|
-
return { path:
|
|
228
|
+
return { path: destRel, present: false, match: 'absent', tracked: false, introducedBy: null }
|
|
202
229
|
const match: ToolArtifact['match'] =
|
|
203
230
|
existsSync(src) && sha256(dest) === sha256(src) ? 'identical' : 'differs'
|
|
204
|
-
const tracked = isTracked(git,
|
|
231
|
+
const tracked = isTracked(git, destRel)
|
|
205
232
|
return {
|
|
206
|
-
path:
|
|
233
|
+
path: destRel,
|
|
207
234
|
present: true,
|
|
208
235
|
match,
|
|
209
236
|
tracked,
|
|
210
237
|
// untracked면 이력의 add 커밋은 "지금 이 설치본"의 도입 커밋이 아니다 → 조회조차 하지 않는다(phase R2 P2).
|
|
211
|
-
introducedBy: tracked ? (introducingCommit(git,
|
|
238
|
+
introducedBy: tracked ? (introducingCommit(git, destRel)?.sha ?? null) : null,
|
|
212
239
|
}
|
|
213
240
|
})
|
|
214
241
|
|
|
@@ -228,6 +255,21 @@ export function collectFacts(opts: UninstallOptions, run?: GitRunner): Uninstall
|
|
|
228
255
|
})
|
|
229
256
|
}
|
|
230
257
|
|
|
258
|
+
// CLAUDE.md는 AGENTS.md와 같은 계층이다 — init이 **부재 시에만** 만들고 `--force`로도 덮어쓰지 않는다.
|
|
259
|
+
// 사용자 파일일 수 있으므로 자동 제거 대상이 아니다(REQ-2026-010 D7).
|
|
260
|
+
const claudeAbs = join(targetRoot, KIT_CLAUDE_DEST_REL)
|
|
261
|
+
if (existsSync(claudeAbs)) {
|
|
262
|
+
const tpl = join(PACKAGE_ROOT, KIT_CLAUDE_TEMPLATE_REL)
|
|
263
|
+
const same = existsSync(tpl) && sha256(claudeAbs) === sha256(tpl)
|
|
264
|
+
ambiguous.push({
|
|
265
|
+
path: KIT_CLAUDE_DEST_REL,
|
|
266
|
+
present: true,
|
|
267
|
+
note: same
|
|
268
|
+
? 'init 템플릿과 동일 — 그래도 자동 제거 대상이 아닙니다(에이전트 지침 파일)'
|
|
269
|
+
: '템플릿과 다름 — 사용자/팀이 작성했거나 편집했습니다',
|
|
270
|
+
})
|
|
271
|
+
}
|
|
272
|
+
|
|
231
273
|
const cfgAbs = join(targetRoot, 'req.config.json')
|
|
232
274
|
if (existsSync(cfgAbs)) {
|
|
233
275
|
const parsed = readJsonObject(cfgAbs)
|
|
@@ -421,7 +463,7 @@ export function renderPlan(plan: UninstallPlan): string {
|
|
|
421
463
|
|
|
422
464
|
L.push('## 5. 잔여물 경고')
|
|
423
465
|
L.push(' - git은 빈 디렉터리를 추적하지 않습니다. 위 파일을 지운 뒤 `git status`가 clean이어도')
|
|
424
|
-
L.push(` 빈 디렉터리(scripts/ · ${facts.ticketRoot}/)가 파일시스템에 남을 수 있습니다.`)
|
|
466
|
+
L.push(` 빈 디렉터리(scripts/ · ${facts.ticketRoot}/ · .claude/ · .cursor/)가 파일시스템에 남을 수 있습니다.`)
|
|
425
467
|
L.push(' - node_modules의 ajv · cross-spawn · tsx 는 다른 패키지도 쓸 수 있어 제거를 권하지 않습니다.')
|
|
426
468
|
L.push('')
|
|
427
469
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "commitgate",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "CommitGate — Builder↔Reviewer(Claude↔Codex) fail-closed 커밋 게이트: 리뷰·승인·증거 없인 커밋을 통과시키지 않는 AI REQ 워크플로 kit (req:new/review-codex/doctor/commit)",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "CommitGate — Builder↔Reviewer(Claude↔Codex) fail-closed 커밋 게이트: 리뷰·승인·증거 없인 커밋을 통과시키지 않는 AI REQ 워크플로 kit (req:new/next/review-codex/doctor/commit)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "sol5288",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"req:new": "tsx scripts/req/req-new.ts",
|
|
33
33
|
"req:review-codex": "tsx scripts/req/review-codex.ts",
|
|
34
34
|
"req:doctor": "tsx scripts/req/req-doctor.ts",
|
|
35
|
+
"req:next": "tsx scripts/req/req-next.ts",
|
|
35
36
|
"req:commit": "tsx scripts/req/req-commit.ts",
|
|
36
37
|
"test": "vitest run",
|
|
37
38
|
"typecheck": "tsc --noEmit",
|
|
@@ -41,7 +42,9 @@
|
|
|
41
42
|
"scripts/req",
|
|
42
43
|
"workflow/machine.schema.json",
|
|
43
44
|
"workflow/req.config.schema.json",
|
|
45
|
+
"workflow/review-persona.md",
|
|
44
46
|
"bin",
|
|
47
|
+
"templates",
|
|
45
48
|
"AGENTS.template.md",
|
|
46
49
|
"req.config.json.sample",
|
|
47
50
|
"README.md",
|
package/req.config.json.sample
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
{
|
|
2
|
-
"ticketRoot": "workflow",
|
|
3
|
-
"schemaPath": "workflow/machine.schema.json",
|
|
4
|
-
"handoffPath": null,
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"ticketRoot": "workflow",
|
|
3
|
+
"schemaPath": "workflow/machine.schema.json",
|
|
4
|
+
"handoffPath": null,
|
|
5
|
+
"reviewPersonaPath": "workflow/review-persona.md",
|
|
6
|
+
"branchPrefix": "feat/req-",
|
|
7
|
+
"packageManager": "npm",
|
|
8
|
+
"granularityMaxFiles": 8,
|
|
9
|
+
"designDocs": {
|
|
10
|
+
"requirement": "00-requirement.md",
|
|
11
|
+
"design": "01-design.md",
|
|
12
|
+
"plan": "02-plan.md"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* req 워크플로 config 모듈 (
|
|
2
|
+
* req 워크플로 config 모듈 (portability kit).
|
|
3
3
|
*
|
|
4
|
-
* 목적: 경로·이름·패키지매니저를 `req.config.json`으로
|
|
5
|
-
*
|
|
4
|
+
* 목적: 경로·이름·패키지매니저를 `req.config.json`으로 외부화한다. 파일이 없으면 `DEFAULTS`로 해소된다.
|
|
5
|
+
* ⚠️ `DEFAULTS`는 **모든 프로젝트에 유효한 중립 기본값**만 담는다(REQ-2026-009). 프로젝트 고유 값은 config가 흡수한다.
|
|
6
6
|
*
|
|
7
7
|
* 안전(fail-closed): config가 게이트를 무력화하거나 경로를 탈출하지 못하도록 AJV 스키마 + 해상도 confinement로 강제.
|
|
8
8
|
*/
|
|
@@ -26,6 +26,8 @@ export interface RawConfig {
|
|
|
26
26
|
ticketRoot?: string
|
|
27
27
|
schemaPath?: string
|
|
28
28
|
handoffPath?: string | null
|
|
29
|
+
/** null = 의도적 비활성(persona 블록 생략). 미지정 = DEFAULTS(활성). */
|
|
30
|
+
reviewPersonaPath?: string | null
|
|
29
31
|
branchPrefix?: string
|
|
30
32
|
packageManager?: PackageManager
|
|
31
33
|
granularityMaxFiles?: number
|
|
@@ -38,6 +40,7 @@ export interface ResolvedConfig {
|
|
|
38
40
|
ticketRoot: string
|
|
39
41
|
schemaPath: string
|
|
40
42
|
handoffPath: string | null
|
|
43
|
+
reviewPersonaPath: string | null
|
|
41
44
|
branchPrefix: string
|
|
42
45
|
packageManager: PackageManager
|
|
43
46
|
granularityMaxFiles: number
|
|
@@ -46,13 +49,40 @@ export interface ResolvedConfig {
|
|
|
46
49
|
workflowDirAbs: string
|
|
47
50
|
schemaPathAbs: string
|
|
48
51
|
handoffPathAbs: string | null
|
|
52
|
+
reviewPersonaPathAbs: string | null
|
|
49
53
|
}
|
|
50
54
|
|
|
51
|
-
/**
|
|
55
|
+
/**
|
|
56
|
+
* Codex 리뷰 프롬프트에 주입되는 **리뷰어 페르소나** 문서의 repo-상대 경로(코어 기본값).
|
|
57
|
+
*
|
|
58
|
+
* ⚠️ 이 상수는 두 축의 SSOT다(REQ-2026-010 D3-1).
|
|
59
|
+
* - **설치 축**: `bin/init.ts`의 `KIT_COPY_RELPATHS`가 이 경로를 대상 repo에 복사한다.
|
|
60
|
+
* - **설정 축**: `DEFAULTS.reviewPersonaPath`가 이 값으로 해소된다(phase-1b에서 도입).
|
|
61
|
+
*
|
|
62
|
+
* 둘이 갈라지면 신규 설치본은 프롬프트 조립 시 이 파일을 찾지 못하고 **모든 리뷰가 fail-closed로 멈춘다.**
|
|
63
|
+
* `tests/unit/init.test.ts`의 "설치 축 SSOT"가 그 드리프트를 회귀로 잡는다.
|
|
64
|
+
* `package.json`의 `files[]`는 또 **다른 축**(npm tarball 적재분)이므로 함께 갱신해야 한다.
|
|
65
|
+
*/
|
|
66
|
+
export const DEFAULT_REVIEW_PERSONA_RELPATH = 'workflow/review-persona.md'
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 코어 기본값. `req.config.json` 부재 시 이 값으로 해소된다.
|
|
70
|
+
*
|
|
71
|
+
* ⚠️ 여기 있는 값은 **모든 대상 프로젝트에 유효한 중립 기본값**이어야 한다.
|
|
72
|
+
* 특정 프로젝트에만 의미 있는 값(경로·문서 위치 등)은 코어가 아니라 `req.config.json`이 흡수한다.
|
|
73
|
+
* `handoffPath`가 그 예다 — 코어 기본은 **비활성(null)**이고, 쓰려면 config에 명시하거나 `--handoff <path>`로 준다.
|
|
74
|
+
* (REQ-2026-009: 이전 기본값은 특정 사설 프로젝트의 문서 경로였다.)
|
|
75
|
+
*
|
|
76
|
+
* `handoffPath`의 `as string | null`은 의도적이다. 없으면 TS가 리터럴 `null`로 좁혀
|
|
77
|
+
* `DEFAULTS`를 직접 import하는 소비자의 `string | null` 계약이 깨진다.
|
|
78
|
+
*/
|
|
52
79
|
export const DEFAULTS = {
|
|
53
80
|
ticketRoot: 'workflow',
|
|
54
81
|
schemaPath: 'workflow/machine.schema.json',
|
|
55
|
-
handoffPath:
|
|
82
|
+
handoffPath: null as string | null,
|
|
83
|
+
// ⚠️ handoffPath와 달리 코어 기본이 **활성**이다. init이 이 경로에 파일을 깔기 때문(KIT_COPY_RELPATHS).
|
|
84
|
+
// 비활성이 필요하면 config에 `null`을 명시한다. `as string | null`은 handoffPath와 같은 이유(직접 import 소비자 계약).
|
|
85
|
+
reviewPersonaPath: DEFAULT_REVIEW_PERSONA_RELPATH as string | null,
|
|
56
86
|
branchPrefix: 'feat/req-',
|
|
57
87
|
packageManager: 'pnpm' as PackageManager,
|
|
58
88
|
granularityMaxFiles: 8,
|
|
@@ -69,6 +99,8 @@ export const CONFIG_SCHEMA = {
|
|
|
69
99
|
ticketRoot: { type: 'string', minLength: 1 },
|
|
70
100
|
schemaPath: { type: 'string', minLength: 1 },
|
|
71
101
|
handoffPath: { type: ['string', 'null'] },
|
|
102
|
+
// null = 의도적 비활성. 문자열이면 minLength 1(빈 문자열은 "비활성"의 애매한 표현 → 거부, null을 쓰게 한다).
|
|
103
|
+
reviewPersonaPath: { type: ['string', 'null'], minLength: 1 },
|
|
72
104
|
branchPrefix: { type: 'string', minLength: 1 }, // 빈 prefix는 D11 무력화 → 금지
|
|
73
105
|
packageManager: { type: 'string', enum: ['pnpm', 'npm', 'yarn'] },
|
|
74
106
|
granularityMaxFiles: { type: 'integer', minimum: 1 },
|
|
@@ -146,17 +178,25 @@ export function loadConfig(opts: { root?: string | null; cwd?: string } = {}): R
|
|
|
146
178
|
ticketRoot: raw.ticketRoot ?? DEFAULTS.ticketRoot,
|
|
147
179
|
schemaPath: raw.schemaPath ?? DEFAULTS.schemaPath,
|
|
148
180
|
handoffPath: raw.handoffPath !== undefined ? raw.handoffPath : DEFAULTS.handoffPath, // null = 명시적 비활성
|
|
181
|
+
reviewPersonaPath:
|
|
182
|
+
raw.reviewPersonaPath !== undefined ? raw.reviewPersonaPath : DEFAULTS.reviewPersonaPath, // null = 명시적 비활성
|
|
149
183
|
branchPrefix: raw.branchPrefix ?? DEFAULTS.branchPrefix,
|
|
150
184
|
packageManager: raw.packageManager ?? DEFAULTS.packageManager,
|
|
151
185
|
granularityMaxFiles: raw.granularityMaxFiles ?? DEFAULTS.granularityMaxFiles,
|
|
152
186
|
designDocs: { ...DEFAULTS.designDocs, ...(raw.designDocs ?? {}) },
|
|
153
187
|
}
|
|
154
188
|
|
|
155
|
-
// repo-내부 자원(ticketRoot·schemaPath)은 **상대경로 + root 하위**만(절대경로·탈출 금지 → portable).
|
|
189
|
+
// repo-내부 자원(ticketRoot·schemaPath·reviewPersonaPath)은 **상대경로 + root 하위**만(절대경로·탈출 금지 → portable).
|
|
190
|
+
// handoffPath만 면제 — 형제 repo의 SSOT 문서를 읽는 **외부 참조**이기 때문.
|
|
191
|
+
// reviewPersonaPath는 패키지가 배포하고 init이 repo 안에 까는 자원이라 schemaPath와 같은 축이다(REQ-2026-010 D2).
|
|
156
192
|
assertRelative(merged.ticketRoot, 'ticketRoot')
|
|
157
193
|
assertRelative(merged.schemaPath, 'schemaPath')
|
|
158
194
|
assertUnderRoot(rootAbs, merged.ticketRoot, 'ticketRoot')
|
|
159
195
|
assertUnderRoot(rootAbs, merged.schemaPath, 'schemaPath')
|
|
196
|
+
if (merged.reviewPersonaPath !== null) {
|
|
197
|
+
assertRelative(merged.reviewPersonaPath, 'reviewPersonaPath')
|
|
198
|
+
assertUnderRoot(rootAbs, merged.reviewPersonaPath, 'reviewPersonaPath')
|
|
199
|
+
}
|
|
160
200
|
|
|
161
201
|
return {
|
|
162
202
|
root: rootAbs,
|
|
@@ -164,6 +204,7 @@ export function loadConfig(opts: { root?: string | null; cwd?: string } = {}): R
|
|
|
164
204
|
workflowDirAbs: resolve(rootAbs, merged.ticketRoot),
|
|
165
205
|
schemaPathAbs: resolve(rootAbs, merged.schemaPath),
|
|
166
206
|
handoffPathAbs: merged.handoffPath ? resolve(rootAbs, merged.handoffPath) : null,
|
|
207
|
+
reviewPersonaPathAbs: merged.reviewPersonaPath ? resolve(rootAbs, merged.reviewPersonaPath) : null,
|
|
167
208
|
}
|
|
168
209
|
}
|
|
169
210
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* req:commit — AI REQ 워크플로우 Phase B (REQ-2026-016). 승인된 phase를 커밋하는 래퍼.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* 설계 근거: 본 티켓 01-design.md D-016-3·3b·7·8·9.
|
|
6
6
|
* 책임(전체): req:doctor 통과 게이트 → HIGH 사람확인 게이트 → source 커밋(승인 코드만) →
|
|
7
7
|
* commit_allowed 소비 → evidence-finalize(approvals.jsonl 매니페스트 append + responses chore 커밋) → 2-커밋.
|
|
8
8
|
* 복구/finalize 모드(pending_evidence_for)·design-finalize 포함.
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* req:doctor — AI REQ 워크플로우 1차 (단계 4B): 일관성 점검(fail-closed).
|
|
4
4
|
*
|
|
5
|
-
* SSOT: palm-kiosk/docs/evaluation/ai-req-workflow-design.md §8.3.
|
|
6
5
|
* 1차 최소셋(registry 비의존): D2·D3·D5·D6·D9·D10·D11 + D13(design 선행·freshness)·D15(NEEDS_FIX actionable). (D1/D7/D7b·D4a 등 registry/merge 의존은 2차)
|
|
7
6
|
* FAIL 1건 이상 → exit 1, 자동 보정 금지(P9). review-codex 헬퍼 재사용.
|
|
8
7
|
*
|
package/scripts/req/req-new.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* req:new — AI REQ 워크플로우 1차 (단계 4A): REQ 티켓 + feat/req-* 브랜치 생성.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* 설계 근거: DEC-WF-020(D11) — REQ는 main에서 feat/req-* 브랜치로 시작한다.
|
|
6
6
|
* - state.json은 **BOM 없이** 생성(Node, review-codex의 writeState 재사용).
|
|
7
7
|
* - 기본 dry-run(계획 출력), `--run` 시 실제 브랜치 생성·티켓 파일·스캐폴드 커밋.
|
|
8
8
|
* - REQ id 채번은 registry 미사용(1차) — workflow/REQ-* 디렉터리 스캔으로 max+1.
|