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 명령 감지 시 검증 실행.
- lockfile로 pnpm/yarn/bun/npm을 감지해
<pm> tsc --noEmit — 타입 에러 시 차단
- <pm> test — 테스트 실패 시 차단 (package.json에 test 스크립트가 없으면 통과)
+ <pm> run lint — lint 위반 시 차단 (package.json에 lint 스크립트가 없으면 건너뜀). 린터를 실행할 수 없으면(exit 127, 린터 미설치) ⚠ 경고만 내고 통과
+ <pm> test — 테스트 실패 시 차단 (package.json에 test 스크립트가 없으면 통과). lint와 달리 exit 127도 차단한다
- timeout: 120초
@@ -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 명령 감지 시 검증 실행.
- lockfile로 pnpm/yarn/bun/npm을 감지해
<pm> tsc --noEmit — 타입 에러 시 차단
- <pm> test — 테스트 실패 시 차단 (package.json에 test 스크립트가 없으면 통과)
+ <pm> run lint — lint 위반 시 차단 (package.json에 lint 스크립트가 없으면 건너뜀). 린터를 실행할 수 없으면(exit 127, 린터 미설치) ⚠ 경고만 내고 통과
+ <pm> test — 테스트 실패 시 차단 (package.json에 test 스크립트가 없으면 통과). lint와 달리 exit 127도 차단한다
- timeout: 120초
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..f9676898
--- /dev/null
+++ b/docs/hslee/commit-gate-lint/commit-gate-lint-artifact.md
@@ -0,0 +1,81 @@
+# 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 테이블 갱신만 늘린다).
+
+### 검증 증거 (ship, HEAD 732beb4, origin/main 5c12947 대비 rebase 불요)
+```
+$ npm run test → exit 0
+ℹ tests 1088 · pass 1087 · fail 0 · skipped 1 (skip = CI 전용 jq 매트릭스 가드)
+ℹ tests 1 · pass 1 · fail 0 (perf)
+$ npm run docs:check → harness overview 생성 상태가 최신입니다.
+```
+- 판별력: 구 훅(pre-lint fixture)으로 바꿔 `--test-name-pattern=lint` 실행 시 신규 6건 fail(pass 2 = "lint 없음" 2건).
+- 다이어그램: 옵트인하지 않은 task(plan에 단계 없음) — 미실행.
+
+### 착수 전 실측 (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 등 리뷰 실행 시 결과(요약·발견·조치)를 날짜와 함께 남긴다. 남기지 않은 리뷰는 "안 한 것"으로 간주.*
+*기계 판독용 마커를 함께 남긴다: ``*
+
+### 2026-10-05 — Codex read-only (`codex exec --sandbox read-only`, model gpt-6.1-sol)
+
+- **요약**: P0–P3 결함 없음. 종료 코드 캡처, 유효한 scripts 값의 jq/node 판정, npm 테스트 스텁,
+ migrate sha·fixture provenance, macOS bash 3.2 구문을 확인. lint/test 127 비대칭은 요구대로 유지.
+- **한계**: read-only 샌드박스라 테스트 스위트는 돌리지 않았다(작성 세션이 `npm run test` 1087 pass로 대신 입증).
+- **조치**: 없음. 작성 세션의 추가 관찰(기존 동작, 무변경): `"lint": ""`(빈 문자열)은 jq(`"" // empty`는 참)와
+ node(`""`는 거짓)의 판정이 갈린다 — jq 쪽은 빈 스크립트를 실행해 exit 0이라 결과는 같다. test도 동일한 기존 동작.
+
+### 2026-10-05T04:06:41.520Z — codex-shipcheck (harness-team review)
+
+- engine: codex · scope: diff · tip: 732beb4b2caee20938ce0cc9ed8519f1f162fabf · exit 0 · 2658 B
+
+```text
+전하, `git status`와 `git diff origin/main`을 직접 확인했습니다. 아래 판정은 **현재 워킹트리 포함** 기준입니다. artifact와 handoff에 미커밋 변경이 있으며, 테스트는 재실행하지 않았습니다. 파일은 수정하지 않았습니다.
+
+| id | 항목 | 심각도 | 판정 | 근거 |
+|---|---|---|---|---|
+| S1 | 요구사항과 구현 대응 | BLOCKER | pass | spec의 “순서: typecheck → lint → test”와 “127 … 경고 두 줄 후 통과”에 대응하여 diff에 `if has_script lint`, `"$PM" run lint … \|\| lint_rc=$?`, `lint_rc -eq 127` 경고 및 그 외 실패의 `exit 2`가 추가됐습니다. jq/node 공용 판정, stock SHA·fixture·refresh 테스트도 추가됐으며, 공통 jq 블록과 기존 test 차단 동작은 유지됩니다. |
+| S2 | 완료 체크의 실재 근거 | MAJOR | pass | plan의 훅·배달·테스트·문서 항목은 해당 diff에 모두 존재합니다. handoff에 `1f38b9c feat(hooks)`와 `732beb4 chore(task)`가 기록되어 실제 git log와 일치합니다. 검증 항목은 artifact의 `$ npm run test → exit 0` 및 `$ npm run docs:check` 출력, 리뷰 항목은 `## Reviews` 기록에 대응합니다. |
+| S3 | 스코프 밖 변경 | MAJOR | pass | 제품 변경은 훅·migrate SHA·fixture·관련 테스트·overview에 한정됩니다. artifact는 “overview 카드…와 `hooks.mmd`…갱신”을 명시하고, 후속 범위는 “이번 task에서 고치지 않음”으로 기록합니다. 나머지는 해당 task의 관리 문서입니다. |
+| S4 | 실행 리뷰 기록과 마커 | MAJOR | pass | 확인된 기존 리뷰는 artifact `## Reviews`에 날짜·요약·한계·조치와 함께 ``로 기록되어 있습니다. 제공된 문서·diff에서 추가 완료 리뷰의 누락 증거는 없습니다. |
+| S5 | 검증 결과의 명령·출력 인용 | BLOCKER | pass | artifact의 fenced block에 `$ npm run test → exit 0`, `ℹ tests 1088 · pass 1087 · fail 0 · skipped 1`, perf 결과 및 `$ npm run docs:check → harness overview 생성 상태가 최신입니다.`가 있습니다. 단순 “통과했다” 선언을 넘어 명령과 결과를 인용합니다. 다만 원본 실행 로그의 진위까지 독립 확인한 판정은 아닙니다. |
+
+검증 증거 블록은 **미커밋 artifact 변경**에 포함되어 있으므로, 현재 HEAD만으로는 동일한 증거 상태가 전달되지 않습니다. 이번 검증 결과도 작성 세션에서 `## Reviews`에 기록해야 합니다.
+
+**Verdict: PASS — fail 항목 없음.**
+```
+
+
+
+- **판별·조치**: S1–S5 전부 pass, 진짜 결함 없음. "증거 블록이 미커밋"이라는 지적은 사실이며, ship 커밋에 함께 담아 해소한다.
+ shipcheck는 scope CLI가 `worktree`(미커밋 handoff뿐)를 줬지만 코드 diff가 커밋에 있어 `--scope diff --base origin/main`으로 돌렸다 —
+ 검증자는 `git status`로 미커밋 문서 변경까지 함께 읽었다.
+
+## 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..2b442992
--- /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: ship 완료 — push·PR 생성, 머지 금지
+- 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..d602728a
--- /dev/null
+++ b/docs/hslee/commit-gate-lint/commit-gate-lint-handoff.md
@@ -0,0 +1,35 @@
+# commit-gate-lint — Handoff
+
+(세션 종료 시 post-commit hook이 자동 갱신합니다)
+
+## 2026-10-05T02:56:11.972Z — 1f38b9c feat(hooks): 커밋 게이트가 package.json lint 스크립트를 돌린다
+docs/diagrams/harness-overview/hooks.mmd | 5 +-
+ docs/harness-overview.html | 13 +-
+ docs/harness-overview.template.html | 3 +-
+ .../commit-gate-lint/commit-gate-lint-artifact.md | 38 ++++++
+ .../commit-gate-lint/commit-gate-lint-context.md | 22 ++++
+ .../commit-gate-lint/commit-gate-lint-handoff.md | 3 +
+ .../commit-gate-lint/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(-)
+
+## 2026-10-05T02:57:56.841Z — 732beb4 chore(task): commit-gate-lint 검증·Codex 리뷰 기록
+.../commit-gate-lint/commit-gate-lint-artifact.md | 8 ++++++++
+ .../hslee/commit-gate-lint/commit-gate-lint-context.md | 2 +-
+ .../hslee/commit-gate-lint/commit-gate-lint-handoff.md | 18 ++++++++++++++++++
+ docs/hslee/commit-gate-lint/commit-gate-lint-plan.md | 4 ++--
+ 4 files changed, 29 insertions(+), 3 deletions(-)
+
+## 2026-10-05T04:06:53.880Z — fe3e7fe chore(task): commit-gate-lint ship
+.../commit-gate-lint/commit-gate-lint-artifact.md | 35 ++++++++++++++++++++++
+ .../commit-gate-lint/commit-gate-lint-context.md | 2 +-
+ .../commit-gate-lint/commit-gate-lint-handoff.md | 7 +++++
+ .../commit-gate-lint/commit-gate-lint-meta.json | 12 +++++++-
+ 4 files changed, 54 insertions(+), 2 deletions(-)
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..d3b27b9a
--- /dev/null
+++ b/docs/hslee/commit-gate-lint/commit-gate-lint-meta.json
@@ -0,0 +1,21 @@
+{
+ "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": [
+ {
+ "kind": "codex-shipcheck",
+ "engine": "codex",
+ "scope": "diff",
+ "tip": "732beb4b2caee20938ce0cc9ed8519f1f162fabf",
+ "at": "2026-10-05T04:06:41.520Z",
+ "exitCode": 0,
+ "outputBytes": 2658
+ }
+ ]
+}
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..ceb1c1da
--- /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`
+- [x] 검증: `npm run test` 전체 통과
+- [x] 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