From f8034b82dfcfaf1ced2de8b9b2394d403ad80eb7 Mon Sep 17 00:00:00 2001 From: JeongUk Park Date: Wed, 29 Jul 2026 20:31:49 +0900 Subject: [PATCH 1/5] =?UTF-8?q?fix:=20rutter-v2=20=ED=94=BD=EC=8A=A4?= =?UTF-8?q?=EC=B2=98=EC=9D=98=20docs=20=ED=8C=8C=EC=9D=BC=EC=9D=84=20?= =?UTF-8?q?=EC=B6=94=EC=A0=81=EC=97=90=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tests/fixtures/rutter-v2/ 의 docs/conventions/style.md 와 docs/maps/projects.md 가 커밋되지 않은 채 로컬에만 있었다. 로컬 .git/info/exclude 의 앵커 없는 docs/ 패턴이 모든 깊이의 docs 디렉터리를 가려서 형제 파일(rutter.yaml·defaults.yaml·policies/)만 커밋되고 이 둘이 빠진 것을 아무도 알아채지 못했다. 새로 클론하면 이 픽스처를 쓰는 테스트 4건이 실패한다 ("docs.conventions 디렉터리 'docs/conventions'이 없습니다"). npm test 는 릴리스 워크플로에서만 돌기 때문에 다음 배포 시점에야 터졌을 문제다. --- tests/fixtures/rutter-v2/docs/conventions/style.md | 7 +++++++ tests/fixtures/rutter-v2/docs/maps/projects.md | 5 +++++ 2 files changed, 12 insertions(+) create mode 100644 tests/fixtures/rutter-v2/docs/conventions/style.md create mode 100644 tests/fixtures/rutter-v2/docs/maps/projects.md diff --git a/tests/fixtures/rutter-v2/docs/conventions/style.md b/tests/fixtures/rutter-v2/docs/conventions/style.md new file mode 100644 index 0000000..63e4909 --- /dev/null +++ b/tests/fixtures/rutter-v2/docs/conventions/style.md @@ -0,0 +1,7 @@ +# 코드 스타일 + +## Why + +일관된 스타일은 리뷰 비용을 줄인다. + +- 커밋 메시지는 한글로 작성한다. diff --git a/tests/fixtures/rutter-v2/docs/maps/projects.md b/tests/fixtures/rutter-v2/docs/maps/projects.md new file mode 100644 index 0000000..e1e7d60 --- /dev/null +++ b/tests/fixtures/rutter-v2/docs/maps/projects.md @@ -0,0 +1,5 @@ +# 조직 저장소 조망 + +| 저장소 | 설명 | +|--------|------| +| payment-api | 결제 API | From edb142eb1feac7d37616c016f837f1d200645d1f Mon Sep 17 00:00:00 2001 From: JeongUk Park Date: Wed, 29 Jul 2026 20:32:11 +0900 Subject: [PATCH 2/5] =?UTF-8?q?ci:=20PR=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=C2=B7=ED=83=80=EC=9E=85=EA=B2=80=EC=82=AC=20=EC=9B=8C=ED=81=AC?= =?UTF-8?q?=ED=94=8C=EB=A1=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 지금까지 npm test 는 publish.yml(릴리스 발행 시점)에서만 돌았고 PR 검증은 commitlint 뿐이라, 깨진 테스트가 배포 시점에야 발견되는 구조다 — 직전 커밋의 픽스처 누락도 이 공백 탓에 어떤 자동 검증에도 걸리지 않았다. PR 마다 테스트를 돌려 앞당긴다. - typecheck 는 vitest(esbuild)·tsup 어느 쪽에서도 돌지 않아 여기서만 돈다 - engines 하한(20)과 publish.yml 이 배포에 쓰는 24 를 매트릭스로 함께 검증 - main push 에도 돌려 PR 간 의미 충돌을 잡고, concurrency 로 중복 실행 취소 --- .github/workflows/publish.yml | 2 +- .github/workflows/test.yml | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 6413a74..093d4d1 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -19,7 +19,7 @@ jobs: persist-credentials: false # 배포에 repo 자격증명이 필요 없다 — git config에 남기지 않는다 - uses: actions/setup-node@v4 with: - node-version: 24 # OIDC 요구: node 22.14+ / npm 11.5.1+ + node-version: 24 # OIDC 요구: node 22.14+ / npm 11.5.1+ — 올리면 test.yml 매트릭스에도 반영 registry-url: https://registry.npmjs.org # setup-node가 .npmrc에 남기는 _authToken placeholder가 OIDC 인증과 충돌할 수 있어 # Trusted Publishing을 지원하는 npm으로 고정한다(11.5.1+). id-token 권한이 있는 job이므로 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..9cf51df --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,33 @@ +name: test +# PR 시점에 테스트를 돌린다. npm test 가 릴리스 워크플로(publish.yml)에서만 돌면 +# 깨진 테스트가 배포 시점에야 발견된다 — PR 검증이 commitlint 뿐인 공백을 메운다. +on: + pull_request: + types: [opened, reopened, synchronize] + push: + branches: [main] # 각각 green 인 PR 들이 병합되며 생기는 의미 충돌을 잡는다 + +permissions: + contents: read + +# 같은 브랜치에 연속 푸시하면 이전 실행을 취소한다 +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + node: [20, 24] # engines 하한(>=20)과 publish.yml 이 배포에 쓰는 24 양쪽에서 검증한다 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + cache: npm + - run: npm ci + # vitest(esbuild)·tsup 어느 쪽도 타입 검사를 하지 않아 여기서만 돈다 + - run: npm run typecheck + - run: npm test From 103a0796c539fca428e831fcd36c09c1c818008f Mon Sep 17 00:00:00 2001 From: JeongUk Park Date: Wed, 29 Jul 2026 20:45:38 +0900 Subject: [PATCH 3/5] =?UTF-8?q?chore:=20.DS=5FStore=20=EC=99=80=20.claude/?= =?UTF-8?q?=20=EB=A5=BC=20=EC=B6=94=EC=A0=81=20=EB=8C=80=EC=83=81=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=A0=9C=EC=99=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pilot 워킹 트리에 로컬 Claude Code 산출물(.claude/, 안에 .DS_Store 포함)이 추적되지 않은 채 노출돼 있었다. 전역 ignore 는 settings.local.json 만 막아, git add -A 한 번이면 로컬 설정과 macOS 부산물이 공개 저장소에 올라간다. docs 차단과 같은 계열의 로컬 산출물 유출 방지다. --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 6dcda15..1bc4c01 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ node_modules/ dist/ *.log +.DS_Store +# 로컬 Claude Code 세션 산출물 — 공개 저장소에 올리지 않는다 +.claude/ # 로컬 산출물(세션 의도 기록·작업 노트 등)이 공개 저장소에 새지 않도록 docs/는 기본 차단하고, # 공개할 문서가 생기면 아래에 예외를 명시해 허용한다 (rutter의 저장소 위생 규약 참조). From 2724500bd9cb8c617eb6adcfa440ac564e29f444 Mon Sep 17 00:00:00 2001 From: JeongUk Park Date: Wed, 29 Jul 2026 20:54:05 +0900 Subject: [PATCH 4/5] =?UTF-8?q?fix:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EA=B0=80?= =?UTF-8?q?=20npx=20=EB=8C=80=EC=8B=A0=20=EB=A1=9C=EC=BB=AC=20tsx=20?= =?UTF-8?q?=EB=B0=94=EC=9D=B4=EB=84=88=EB=A6=AC=EB=A5=BC=20=EC=A7=81?= =?UTF-8?q?=EC=A0=91=20=EC=8B=A4=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 테스트가 임시 디렉토리를 cwd 로 'npx tsx' 를 스폰하면 npx 가 리포의 node_modules 를 보지 못하고 매번 네트워크에서 tsx 를 받아온다. 콜드 캐시인 CI 에서 스폰마다 수 초가 붙어 vitest 5초 타임아웃을 넘겼다 — 이 PR 의 node 20 매트릭스 잡이 실제로 이렇게 실패했다. tsx 는 이미 devDependency 라 node_modules/.bin/tsx 를 직접 실행하면 결정적이고, 로컬에서도 스위트가 37초 → 19초로 줄어든다. --- tests/apply-cli.test.ts | 6 ++++-- tests/cli.test.ts | 3 ++- tests/e2e.test.ts | 5 ++++- tests/init.test.ts | 6 ++++-- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/tests/apply-cli.test.ts b/tests/apply-cli.test.ts index a05106a..77e34d4 100644 --- a/tests/apply-cli.test.ts +++ b/tests/apply-cli.test.ts @@ -7,13 +7,15 @@ import { parse } from 'yaml' const FIXTURE_PKG = new URL('./fixtures/rutter-v2', import.meta.url).pathname const CLI = join(process.cwd(), 'src/cli/index.ts') +// npx 경유가 아니라 로컬 tsx 바이너리를 직접 실행한다 (e2e.test.ts 와 같은 이유) +const TSX = join(process.cwd(), 'node_modules', '.bin', 'tsx') let env: NodeJS.ProcessEnv const run = (args: string[], cwd: string) => - execFileSync('npx', ['tsx', CLI, ...args], { encoding: 'utf8', env, cwd }) + execFileSync(TSX, [CLI, ...args], { encoding: 'utf8', env, cwd }) // stdout+stderr 함께 캡처 (경고는 stderr로 나간다) const runOut = (args: string[], cwd: string): string => { - const r = spawnSync('npx', ['tsx', CLI, ...args], { encoding: 'utf8', env, cwd }) + const r = spawnSync(TSX, [CLI, ...args], { encoding: 'utf8', env, cwd }) return (r.stdout ?? '') + (r.stderr ?? '') } const runFail = (args: string[], cwd: string): string => { diff --git a/tests/cli.test.ts b/tests/cli.test.ts index ac248e5..262932d 100644 --- a/tests/cli.test.ts +++ b/tests/cli.test.ts @@ -4,7 +4,8 @@ import { mkdtempSync, writeFileSync, mkdirSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' -const CLI = ['npx', 'tsx', 'src/cli/index.ts'] +// npx 경유가 아니라 로컬 tsx 바이너리를 직접 실행한다 (e2e.test.ts 와 같은 이유) +const CLI = [join(process.cwd(), 'node_modules', '.bin', 'tsx'), 'src/cli/index.ts'] let env: NodeJS.ProcessEnv function run(args: string[], cwd?: string): string { diff --git a/tests/e2e.test.ts b/tests/e2e.test.ts index 3606567..2600ccb 100644 --- a/tests/e2e.test.ts +++ b/tests/e2e.test.ts @@ -5,8 +5,11 @@ import { tmpdir } from 'node:os' import { join } from 'node:path' let env: NodeJS.ProcessEnv +// npx 경유가 아니라 로컬 tsx 바이너리를 직접 실행한다 — 테스트 cwd 가 임시 디렉토리라 +// npx 는 리포의 node_modules 를 보지 못하고 매번 네트워크에서 tsx 를 받아온다(CI 타임아웃 원인). +const TSX = join(process.cwd(), 'node_modules', '.bin', 'tsx') const run = (args: string[], cwd: string) => - execFileSync('npx', ['tsx', join(process.cwd(), 'src/cli/index.ts'), ...args], { encoding: 'utf8', env, cwd }) + execFileSync(TSX, [join(process.cwd(), 'src/cli/index.ts'), ...args], { encoding: 'utf8', env, cwd }) beforeEach(() => { env = { ...process.env, diff --git a/tests/init.test.ts b/tests/init.test.ts index 16e1acd..3caeba7 100644 --- a/tests/init.test.ts +++ b/tests/init.test.ts @@ -5,8 +5,10 @@ import { tmpdir } from 'node:os' import { join } from 'node:path' let env: NodeJS.ProcessEnv +// npx 경유가 아니라 로컬 tsx 바이너리를 직접 실행한다 (e2e.test.ts 와 같은 이유) +const TSX = join(process.cwd(), 'node_modules', '.bin', 'tsx') const run = (args: string[], cwd: string) => - execFileSync('npx', ['tsx', join(process.cwd(), 'src/cli/index.ts'), ...args], { encoding: 'utf8', env, cwd }) + execFileSync(TSX, [join(process.cwd(), 'src/cli/index.ts'), ...args], { encoding: 'utf8', env, cwd }) function makeRutter(): string { const r = mkdtempSync(join(tmpdir(), 'rutter-')) @@ -52,7 +54,7 @@ describe('pilot init', () => { // 않았다"는 보안 불변식(성공 로그 없음 + CLAUDE.md 없음 + config 비어있음)만 검증한다. let stdout = '' try { - stdout = execFileSync('npx', ['tsx', join(process.cwd(), 'src/cli/index.ts'), 'init'], + stdout = execFileSync(TSX, [join(process.cwd(), 'src/cli/index.ts'), 'init'], { encoding: 'utf8', env, cwd: proj, input: 'n\n' }) } catch { // 실패 자체가 기대되는 경로 — 아래 불변식만 확인한다 From d161004a90ec1c4730babe5658a9b455c81cadc1 Mon Sep 17 00:00:00 2001 From: JeongUk Park Date: Wed, 29 Jul 2026 20:54:05 +0900 Subject: [PATCH 5/5] =?UTF-8?q?ci:=20checkout=20=EC=9E=90=EA=B2=A9?= =?UTF-8?q?=EC=A6=9D=EB=AA=85=EC=9D=84=20git=20config=20=EC=97=90=20?= =?UTF-8?q?=EB=82=A8=EA=B8=B0=EC=A7=80=20=EC=95=8A=EC=9D=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit actions/checkout 은 기본으로 GITHUB_TOKEN 을 git config 에 기록하고, 이후 스텝의 npm ci·npm test 는 저장소가 통제하는 코드를 실행한다. 테스트에 자격증명이 필요 없으므로 persist-credentials 를 끈다 — publish.yml 이 이미 쓰는 패턴이고 CodeRabbit 지적 반영이다. --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9cf51df..194b8d2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,6 +23,8 @@ jobs: node: [20, 24] # engines 하한(>=20)과 publish.yml 이 배포에 쓰는 24 양쪽에서 검증한다 steps: - uses: actions/checkout@v4 + with: + persist-credentials: false # 테스트는 repo 자격증명이 필요 없다 — git config 에 남기지 않는다 - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node }}