commitgate 0.2.1 → 0.3.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 +85 -48
- package/README.en.md +332 -238
- package/README.md +332 -238
- package/bin/commitgate.mjs +12 -4
- package/bin/init.ts +61 -12
- package/bin/uninstall.ts +551 -0
- package/package.json +1 -1
- package/scripts/req/lib/adapters.ts +35 -8
- package/scripts/req/review-codex.ts +243 -20
- package/workflow/machine.schema.json +19 -2
package/bin/init.ts
CHANGED
|
@@ -21,17 +21,28 @@ import {
|
|
|
21
21
|
copyFileSync,
|
|
22
22
|
realpathSync,
|
|
23
23
|
} from 'node:fs'
|
|
24
|
+
import { execFileSync } from 'node:child_process'
|
|
24
25
|
import { resolve, join, dirname, relative } from 'node:path'
|
|
25
26
|
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
26
27
|
import { loadConfig, stripBom, type PackageManager } from '../scripts/req/lib/config'
|
|
27
|
-
import { createGitAdapter } from '../scripts/req/lib/adapters'
|
|
28
|
+
import { createGitAdapter, type GitRunner } from '../scripts/req/lib/adapters'
|
|
28
29
|
import * as semver from 'semver'
|
|
29
30
|
|
|
30
31
|
/** 이 패키지 루트(bin/ 기준 1단계 위). 복사 원본. */
|
|
31
|
-
const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
32
|
+
export const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
33
|
+
|
|
34
|
+
/** kit 소스 디렉터리(패키지-상대 = 대상-상대). copyInto가 이 레이아웃을 그대로 재현. */
|
|
35
|
+
export const KIT_SOURCE_DIR_REL = 'scripts/req'
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* init이 **실제로 복사하는** 스키마 경로(패키지-상대 = 대상-상대). ⚠️ `req.config.json`의 `ticketRoot`/`schemaPath`와 무관하게
|
|
39
|
+
* 언제나 리터럴 `workflow/` 아래다 — `copyInto`가 `relative(PACKAGE_ROOT, src)`로 상대경로를 재현하기 때문.
|
|
40
|
+
* runInit(복사)과 uninstall planner(제거 후보)가 이 상수를 **공유**해야 드리프트가 없다(REQ-2026-007 design R1 P2 / D3b).
|
|
41
|
+
*/
|
|
42
|
+
export const KIT_SCHEMA_RELPATHS = ['workflow/machine.schema.json', 'workflow/req.config.schema.json'] as const
|
|
32
43
|
|
|
33
44
|
/** 대상 package.json에 주입할 req:* 스크립트. */
|
|
34
|
-
const REQ_SCRIPTS: Record<string, string> = {
|
|
45
|
+
export const REQ_SCRIPTS: Record<string, string> = {
|
|
35
46
|
'req:new': 'tsx scripts/req/req-new.ts',
|
|
36
47
|
'req:review-codex': 'tsx scripts/req/review-codex.ts',
|
|
37
48
|
'req:doctor': 'tsx scripts/req/req-doctor.ts',
|
|
@@ -42,7 +53,7 @@ const REQ_SCRIPTS: Record<string, string> = {
|
|
|
42
53
|
const CROSS_SPAWN_SPEC = '^7.0.6'
|
|
43
54
|
|
|
44
55
|
/** 대상 package.json에 주입할 devDeps(워크플로 실행 전제). cross-spawn = 복사된 adapters.ts의 안전 spawn(P1) 런타임 의존. */
|
|
45
|
-
const REQ_DEV_DEPS: Record<string, string> = {
|
|
56
|
+
export const REQ_DEV_DEPS: Record<string, string> = {
|
|
46
57
|
ajv: '^8.20.0',
|
|
47
58
|
'cross-spawn': CROSS_SPAWN_SPEC,
|
|
48
59
|
tsx: '^4.19.1',
|
|
@@ -71,9 +82,17 @@ export interface InitResult {
|
|
|
71
82
|
/**
|
|
72
83
|
* 대상이 진짜 git work tree인지 실제 git으로 검증(D5, design R1 P2). `.git` 경로 존재만으론 부족(fake 마커 통과).
|
|
73
84
|
* targetRoot가 repo top-level과 일치해야 함(하위 디렉터리에 스캐폴드 방지). git 미설치/비-repo → throw(fail-closed).
|
|
85
|
+
*
|
|
86
|
+
* `run` 주입(REQ-2026-007): uninstall planner가 자신의 감시 runner로 이 검증을 통과시켜
|
|
87
|
+
* **모든 git 호출을 단일 경계에서 관측**할 수 있게 한다. 미지정 시 기존 quiet runner(동작 불변).
|
|
74
88
|
*/
|
|
75
|
-
function assertGitWorkTree(targetRoot: string): void {
|
|
76
|
-
|
|
89
|
+
export function assertGitWorkTree(targetRoot: string, run?: GitRunner): void {
|
|
90
|
+
// probe 전용 runner: 비-repo일 때 git이 뱉는 `fatal: not a git repository` stderr를 삼킨다.
|
|
91
|
+
// 우리가 더 명확한 조치 메시지로 대체하므로 raw git stderr는 노이즈일 뿐(design 후속 UX).
|
|
92
|
+
// ⚠️ 전역 GitAdapter 기본(stderr 상속)은 그대로 — 다른 git 호출(req:commit 등)의 진단 손실 방지.
|
|
93
|
+
const quietRunner: GitRunner = (file, args, opts) =>
|
|
94
|
+
execFileSync(file, args, { ...opts, stdio: ['ignore', 'pipe', 'ignore'] })
|
|
95
|
+
const git = createGitAdapter(targetRoot, run ?? quietRunner)
|
|
77
96
|
let inside: string
|
|
78
97
|
let topLevel: string
|
|
79
98
|
try {
|
|
@@ -98,6 +117,16 @@ export function detectPackageManager(root: string): PackageManager {
|
|
|
98
117
|
return 'npm'
|
|
99
118
|
}
|
|
100
119
|
|
|
120
|
+
/**
|
|
121
|
+
* 대상 pm에 맞는 package.json script 실행 커맨드 문자열.
|
|
122
|
+
* npm은 임의 스크립트를 `npm run <script>`로만 실행하고 인자 전달에 `--` 구분자가 필요하다
|
|
123
|
+
* (`npm req:new …`은 "Unknown command"로 실패). pnpm/yarn은 bare script + 인자 직접 전달을 지원.
|
|
124
|
+
* → 안내 문구가 실제로 복붙 가능한 유효 커맨드가 되도록 pm별로 분기(README 수동 명령과 동일 형태).
|
|
125
|
+
*/
|
|
126
|
+
export function runScriptCmd(pm: PackageManager, script: string, args: string): string {
|
|
127
|
+
return pm === 'npm' ? `npm run ${script} -- ${args}` : `${pm} ${script} ${args}`
|
|
128
|
+
}
|
|
129
|
+
|
|
101
130
|
/** dir 하위 모든 파일의 절대경로(재귀). */
|
|
102
131
|
function walkFiles(dir: string): string[] {
|
|
103
132
|
const out: string[] = []
|
|
@@ -328,8 +357,9 @@ export function runInit(opts: InitOptions): InitResult {
|
|
|
328
357
|
// ══ Apply: 여기부터 쓰기(preflight 전부 통과 후에만) ═════════════════
|
|
329
358
|
const copied: string[] = []
|
|
330
359
|
const skipped: string[] = []
|
|
331
|
-
copyInto(walkFiles(join(PACKAGE_ROOT,
|
|
332
|
-
|
|
360
|
+
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))
|
|
333
363
|
copyInto(schemaFiles, PACKAGE_ROOT, targetRoot, opts, copied, skipped)
|
|
334
364
|
|
|
335
365
|
if (!opts.dryRun) {
|
|
@@ -388,7 +418,8 @@ function printHelp(): void {
|
|
|
388
418
|
console.log(`commitgate — AI REQ workflow(커밋 게이트) kit 설치
|
|
389
419
|
|
|
390
420
|
사용법:
|
|
391
|
-
npx commitgate [--dir <대상repo>] [--force] [--dry-run]
|
|
421
|
+
npx commitgate [--dir <대상repo>] [--force] [--dry-run] [--strict]
|
|
422
|
+
npx commitgate uninstall [--dir <대상repo>] # 제거 계획만 출력(아무것도 지우지 않음)
|
|
392
423
|
|
|
393
424
|
옵션:
|
|
394
425
|
--dir <path> 대상 repo 루트(기본: 현재 디렉터리)
|
|
@@ -401,7 +432,10 @@ function printHelp(): void {
|
|
|
401
432
|
1. <대상repo>에서 의존성 설치(감지된 패키지매니저)
|
|
402
433
|
2. codex CLI 설치 확인(리뷰 실호출용)
|
|
403
434
|
3. req.config.json 조정(branchPrefix/ticketRoot 등)
|
|
404
|
-
4.
|
|
435
|
+
4. 첫 티켓 생성:
|
|
436
|
+
npm → npm run req:new -- <slug> --run
|
|
437
|
+
pnpm → pnpm req:new <slug> --run
|
|
438
|
+
yarn → yarn req:new <slug> --run`)
|
|
405
439
|
}
|
|
406
440
|
|
|
407
441
|
export function main(argv: string[]): void {
|
|
@@ -430,9 +464,24 @@ export function main(argv: string[]): void {
|
|
|
430
464
|
console.log(` 1. cd ${r.targetRoot} && ${r.packageManager} install`)
|
|
431
465
|
console.log(` 2. codex --version # 리뷰 실호출 전제(미설치면 review-codex --run이 fail-closed)`)
|
|
432
466
|
console.log(` 3. req.config.json 확인(branchPrefix 등)`)
|
|
433
|
-
console.log(` 4. ${r.packageManager
|
|
467
|
+
console.log(` 4. ${runScriptCmd(r.packageManager, 'req:new', '<slug> --run')}`)
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* CLI 경계: main을 실행하되 사전조건 미충족 등 예상된 실패(throw)는
|
|
473
|
+
* raw 스택트레이스가 아니라 친절한 한 줄 메시지 + 종료코드 1로 표면화한다.
|
|
474
|
+
* (에러 문구 자체가 이미 조치 안내를 담고 있어 스택트레이스는 노이즈일 뿐 — REQ 후속 UX 개선.)
|
|
475
|
+
* bin/commitgate.mjs 런처와 직접 실행(`tsx bin/init.ts`)이 공유하는 단일 경계.
|
|
476
|
+
*/
|
|
477
|
+
export function runCli(argv: string[]): void {
|
|
478
|
+
try {
|
|
479
|
+
main(argv)
|
|
480
|
+
} catch (err) {
|
|
481
|
+
console.error(`commitgate: ${err instanceof Error ? err.message : String(err)}`)
|
|
482
|
+
process.exitCode = 1
|
|
434
483
|
}
|
|
435
484
|
}
|
|
436
485
|
|
|
437
486
|
const isMain = import.meta.url === pathToFileURL(process.argv[1] ?? '').href
|
|
438
|
-
if (isMain)
|
|
487
|
+
if (isMain) runCli(process.argv.slice(2))
|