From 1f38b9cfe8c4e523a6c86810b08439a4c948cc1a Mon Sep 17 00:00:00 2001 From: hslee Date: Mon, 5 Oct 2026 11:56:11 +0900 Subject: [PATCH 1/4] =?UTF-8?q?feat(hooks):=20=EC=BB=A4=EB=B0=8B=20?= =?UTF-8?q?=EA=B2=8C=EC=9D=B4=ED=8A=B8=EA=B0=80=20package.json=20lint=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=EB=A5=BC=20=EB=8F=8C?= =?UTF-8?q?=EB=A6=B0=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit typecheck → lint → test 순서. lint 위반은 exit 2로 차단하고, 실행 불가(exit 127 — 린터 미설치)는 경고 후 통과한다. test 게이트는 127도 계속 차단한다(의도된 비대칭). 직전 판을 stock sha 테이블·fixture에 추가해 migrate refresh로 배달한다. Co-Authored-By: Claude Opus 5.5 --- docs/diagrams/harness-overview/hooks.mmd | 5 +- docs/harness-overview.html | 13 +- docs/harness-overview.template.html | 3 +- .../commit-gate-lint-artifact.md | 38 +++++ .../commit-gate-lint-context.md | 22 +++ .../commit-gate-lint-handoff.md | 3 + .../commit-gate-lint-meta.json | 11 ++ .../commit-gate-lint/commit-gate-lint-plan.md | 20 +++ .../commit-gate-lint/commit-gate-lint-spec.md | 84 +++++++++++ src/commands/migrate.mjs | 1 + templates/.claude/hooks/pre-commit-check.sh | 30 +++- tests/fixtures/stock-hooks/README.md | 3 + .../stock-hooks/pre-lint/pre-commit-check.sh | 133 ++++++++++++++++++ tests/hooks-jq-fallback.test.mjs | 56 ++++++++ tests/migrate-hooks.test.mjs | 12 +- 15 files changed, 420 insertions(+), 14 deletions(-) create mode 100644 docs/hslee/commit-gate-lint/commit-gate-lint-artifact.md create mode 100644 docs/hslee/commit-gate-lint/commit-gate-lint-context.md create mode 100644 docs/hslee/commit-gate-lint/commit-gate-lint-handoff.md create mode 100644 docs/hslee/commit-gate-lint/commit-gate-lint-meta.json create mode 100644 docs/hslee/commit-gate-lint/commit-gate-lint-plan.md create mode 100644 docs/hslee/commit-gate-lint/commit-gate-lint-spec.md create mode 100755 tests/fixtures/stock-hooks/pre-lint/pre-commit-check.sh diff --git a/docs/diagrams/harness-overview/hooks.mmd b/docs/diagrams/harness-overview/hooks.mmd index af0eb457..8da4da8e 100644 --- a/docs/diagrams/harness-overview/hooks.mmd +++ b/docs/diagrams/harness-overview/hooks.mmd @@ -27,8 +27,9 @@ sequenceDiagram rect rgb(40, 26, 26) Note over Claude,Tool: PreToolUse — Bash git commit 실행 전 Claude->>Hook: pre-commit-check.sh (명령어 전달) - Hook->>Hook: pnpm tsc --noEmit - Hook->>Hook: pnpm test + Hook->>Hook: $PM tsc --noEmit (tsconfig.json 있을 때) + Hook->>Hook: $PM run lint (127이면 경고 후 통과) + Hook->>Hook: $PM test alt 실패 Hook-->>Claude: exit 2 (커밋 차단) else 통과 diff --git a/docs/harness-overview.html b/docs/harness-overview.html index b0e31b83..d6c902bc 100644 --- a/docs/harness-overview.html +++ b/docs/harness-overview.html @@ -847,8 +847,9 @@

release --json

rect rgb(40, 26, 26) Note over Claude,Tool: PreToolUse — Bash git commit 실행 전 Claude->>Hook: pre-commit-check.sh (명령어 전달) - Hook->>Hook: pnpm tsc --noEmit - Hook->>Hook: pnpm test + Hook->>Hook: $PM tsc --noEmit (tsconfig.json 있을 때) + Hook->>Hook: $PM run lint (127이면 경고 후 통과) + Hook->>Hook: $PM test alt 실패 Hook-->>Claude: exit 2 (커밋 차단) else 통과 @@ -882,7 +883,8 @@

PreToolUse pre-commit-check.sh

Bash 도구에서 git commit 명령 감지 시 검증 실행.

@@ -2529,6 +2531,11 @@

병합 마커 포맷

Test 자동 회귀 검증 + + tests/fixtures/stock-hooks/pre-lint/pre-commit-check.sh + Test + 자동 회귀 검증 + tests/fixtures/stock-hooks/pre-rn-ios-secrets/protect-files.sh Test diff --git a/docs/harness-overview.template.html b/docs/harness-overview.template.html index 1502a1e9..1f8bcf7f 100644 --- a/docs/harness-overview.template.html +++ b/docs/harness-overview.template.html @@ -606,7 +606,8 @@

PreToolUse pre-commit-check.sh

Bash 도구에서 git commit 명령 감지 시 검증 실행.

diff --git a/docs/hslee/commit-gate-lint/commit-gate-lint-artifact.md b/docs/hslee/commit-gate-lint/commit-gate-lint-artifact.md new file mode 100644 index 00000000..045426f9 --- /dev/null +++ b/docs/hslee/commit-gate-lint/commit-gate-lint-artifact.md @@ -0,0 +1,38 @@ +# commit-gate-lint — Artifact + +*최종 결과물과 학습 내용을 기록한다.* + +## 결과 + +- 커밋 게이트(`templates/.claude/hooks/pre-commit-check.sh`)가 typecheck → **lint** → test 순으로 돈다. + package.json `lint`가 있을 때만 ` run lint` — 위반은 `exit 2`, 127(실행 불가)은 ⚠ 경고 후 통과. + test 게이트는 바꾸지 않았다(127도 차단 — 의도된 비대칭, spec 참조). `has_test_script` → `has_script ` 일반화. +- 배달: 직전 판(blob `ed90db5b`)을 `KNOWN_STOCK_HOOK_SHA256`·`tests/fixtures/stock-hooks/pre-lint/`에 추가 — + stock 설치본은 migrate refresh로 갱신, 커스터마이즈본은 보존(기존 계약). +- 테스트: `tests/hooks-jq-fallback.test.mjs`에 npm 스텁 기반 4케이스 × jq 있음/없음(위반·통과·없음·127), + `tests/migrate-hooks.test.mjs`에 pre-lint 판 refresh 1건. 구 훅으로 바꿔 돌리면 신규 6건이 실패(판별력 확인). +- 문서: overview 카드(lint 단계·127 비대칭)와 `hooks.mmd`(낡은 `pnpm` 하드코딩 → `$PM`, lint 단계) 갱신. + `/verify` 스킬·README는 훅 동작을 서술하지 않아 변경하지 않았다(`/verify` 수정은 stock-template 테이블 갱신만 늘린다). + +### 착수 전 실측 (2026-10-05) +- deep-math `npm run lint` → exit 127 `sh: eslint: command not found` (eslint 의존성·설정 모두 없음 — 죽은 스크립트). +- heliosent-profile `bun run lint` → exit 0. +- → 사용자 결정 C안: 위반은 차단, 127만 경고. + +### 후속 후보 (이번 task에서 고치지 않음 — 사용자 지시) +- 템플릿 `detect_pm`이 `bun.lockb`만 봐서 bun 1.2+ 텍스트 락파일 `bun.lock` 프로젝트를 npm으로 오인한다 + (heliosent-profile은 로컬 수정으로 우회 중). +- heliosent-profile의 `pre-commit-check.sh`는 커스터마이즈본이라 migrate가 덮지 않는다 → lint 게이트가 배달되지 않는다. + 수동 병합 또는 bun.lock 수정을 템플릿에 올린 뒤 stock 판정 재검토가 필요하다. +- deep-math의 죽은 `lint` 스크립트: eslint 설치 또는 스크립트 제거(현재는 매 커밋 ⚠ 경고). +- package.json 없는 프로젝트(ruff의 job-scraper)로의 확장. + +## Reviews +*Codex 등 리뷰 실행 시 결과(요약·발견·조치)를 날짜와 함께 남긴다. 남기지 않은 리뷰는 "안 한 것"으로 간주.* +*기계 판독용 마커를 함께 남긴다: ``* + + +## Learnings + +- 훅 템플릿 바이트를 바꾸면 세 곳을 함께 바꿔야 한다: `KNOWN_STOCK_HOOK_SHA256` 직전 판 sha, `stock-hooks//` fixture, + 드리프트 가드의 fixture 개수(`checked`). 이력 완전성 테스트는 `HEAD` 기준이라 **커밋 전에는 반드시 실패**한다. diff --git a/docs/hslee/commit-gate-lint/commit-gate-lint-context.md b/docs/hslee/commit-gate-lint/commit-gate-lint-context.md new file mode 100644 index 00000000..b0a9bc31 --- /dev/null +++ b/docs/hslee/commit-gate-lint/commit-gate-lint-context.md @@ -0,0 +1,22 @@ +# commit-gate-lint — Context Card + + +## Now +- Goal: 커밋 게이트에 package.json `lint` 단계 추가 — 위반 exit 2, 127 경고 후 통과 +- Current atomic step: 커밋 후 `npm run test` 전체 → Codex read-only 리뷰 +- Stop / human-decision condition: push·PR은 사용자 명시 지시 전 금지 + +## Constraints and settled decisions +- 사용자 결정 C안(2026-10-05): lint 127만 경고, test 게이트는 127도 차단(불변) +- jq-fallback 블록·timeout 120s 불변, detect_pm bun.lock·heliosent 커스텀 훅은 후속(artifact) + +## JIT retrieval map +- Identifiers / symbols: `has_script`, `lint_rc`, `KNOWN_STOCK_HOOK_SHA256` +- Narrow globs: `templates/.claude/hooks/pre-commit-check.sh`, `tests/fixtures/stock-hooks/pre-lint/` +- Read next: `tests/hooks-jq-fallback.test.mjs` (runCommitWithNpm), `tests/migrate-hooks.test.mjs` +- Verification command: `npm run test`, `npm run docs:check` + +## Failure capsules (max 3 unresolved) + +## Resume checklist +- 이력 완전성 테스트는 HEAD 기준 — 커밋 후 재실행 diff --git a/docs/hslee/commit-gate-lint/commit-gate-lint-handoff.md b/docs/hslee/commit-gate-lint/commit-gate-lint-handoff.md new file mode 100644 index 00000000..038d4477 --- /dev/null +++ b/docs/hslee/commit-gate-lint/commit-gate-lint-handoff.md @@ -0,0 +1,3 @@ +# commit-gate-lint — Handoff + +(세션 종료 시 post-commit hook이 자동 갱신합니다) diff --git a/docs/hslee/commit-gate-lint/commit-gate-lint-meta.json b/docs/hslee/commit-gate-lint/commit-gate-lint-meta.json new file mode 100644 index 00000000..a8e94f52 --- /dev/null +++ b/docs/hslee/commit-gate-lint/commit-gate-lint-meta.json @@ -0,0 +1,11 @@ +{ + "user": "hslee", + "task": "commit-gate-lint", + "created": "2026-10-05", + "firstActivatedAt": "2026-10-05T02:50:50.742Z", + "status": "open", + "closedAt": null, + "forcedAt": null, + "forcedIssues": null, + "reviews": [] +} diff --git a/docs/hslee/commit-gate-lint/commit-gate-lint-plan.md b/docs/hslee/commit-gate-lint/commit-gate-lint-plan.md new file mode 100644 index 00000000..6e1d0968 --- /dev/null +++ b/docs/hslee/commit-gate-lint/commit-gate-lint-plan.md @@ -0,0 +1,20 @@ +# commit-gate-lint — Plan + +## 목표 +커밋 게이트가 package.json `lint`를 typecheck와 test 사이에 돌린다 — 위반은 차단, 127은 경고 후 통과. + +## 단계 +- [x] 훅: `has_script` 일반화 + lint 단계(0 통과 / 127 ⚠ / 그 외 exit 2) +- [x] 배달: 직전 판을 `KNOWN_STOCK_HOOK_SHA256`·`tests/fixtures/stock-hooks/pre-lint/`·fixture README에 추가 +- [x] 테스트: hooks-jq-fallback(위반·통과·없음·127, jq 매트릭스) + migrate-hooks(pre-lint 판 refresh) +- [x] 문서: overview 카드·`hooks.mmd`(127 비대칭 명시), `npm run docs:check` +- [ ] 검증: `npm run test` 전체 통과 +- [ ] Codex read-only 리뷰 → artifact `## Reviews` + +## Ontology 변경 로그 +*개념이 새로 정의되거나 의미가 바뀌면 한 줄로 기록. spec.md의 Ontology 섹션을 갱신할 트리거가 된다.* + +- 2026-10-05: "lint 위반"과 "lint 실행 불가(127)"를 구분 — 후자는 경고만. + +## 참고 +- spec의 PM별 127 실측 표 diff --git a/docs/hslee/commit-gate-lint/commit-gate-lint-spec.md b/docs/hslee/commit-gate-lint/commit-gate-lint-spec.md new file mode 100644 index 00000000..0e788025 --- /dev/null +++ b/docs/hslee/commit-gate-lint/commit-gate-lint-spec.md @@ -0,0 +1,84 @@ +# commit-gate-lint — Spec + +## 목적 / 요구사항 +*문제(오늘 무엇이 안 되는가) → 영향받는 사용자·시스템 → 기대 결과 → 제약 순으로 쓴다. +답 없는 질문은 요구가 아니다 — `## 참고` 절에 `- (open) …`으로 남긴다.* + +- **문제**: 템플릿 커밋 게이트 `templates/.claude/hooks/pre-commit-check.sh`는 `tsc --noEmit`(tsconfig.json 있을 때)과 + package.json `test`만 돌린다. lint는 사람이 `/verify`(`disable-model-invocation: true`)를 부를 때만 돌아, + AI가 무시할 수 있는 규범에 머문다. +- **영향**: 하네스를 설치한 JS 소비자 프로젝트(deep-math·heliosent-profile 등)의 Claude Code 커밋. +- **기대 결과**: package.json에 `lint` 스크립트가 있으면 커밋 직전에 결정론적으로 실행한다. + - 실제 위반(exit ≠ 0, ≠ 127) → `exit 2`로 차단, 기존 두 줄 형식(❌ + `→ 확인 명령`). + - 실행 불가(exit 127, command not found — 린터 미설치·스크립트만 남은 경우) → ⚠ 경고 두 줄 후 통과. + - lint 스크립트 없음 → 건너뜀. jq 유무와 무관하게 같은 판정(jq 없으면 node 폴백). + - 순서: typecheck → lint → test (싼 검사 먼저). +- **의도된 비대칭**: test 게이트는 127이어도 **차단**한다(변경하지 않음). lint만 127을 경고로 강등한다 — + 사용자 결정 C안(2026-10-05). 근거: 착수 전 실측에서 deep-math의 `lint` 스크립트가 eslint 미설치로 exit 127 + (죽은 스크립트)이라, 차단 도입 시 stock 훅을 쓰는 소비자의 모든 커밋이 갑자기 막히는 회귀가 난다. +- **제약**: 특정 린터·규칙 세트를 내장하지 않는다(스택 중립). 훅 timeout(120s)과 `harness:jq-fallback` 공통 블록은 + 건드리지 않는다. package.json 없는 프로젝트(ruff의 job-scraper)·AGENTS.md `lint:` 파싱·auto-format·린터 규칙은 범위 밖. + +## 설계 / 접근 + +- `has_test_script`를 `has_script `으로 일반화(jq `.scripts[$s]` / node `scripts[argv]`), test·lint 공용. +- lint 실행은 `"$PM" run lint` — npm·pnpm·yarn·bun 모두 `run