From a41dff556d014f6755ed989508b3b0f746634670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=9A=A9=EC=84=B1?= Date: Sun, 14 Jun 2026 18:08:37 +0900 Subject: [PATCH 1/4] fix(atelier): fire version-drift hook on all atelier sessions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SessionStart 버전 드리프트 알림 hook(check-cli-version.sh)이 두 겹 게이트로 autopilot 프로젝트에서만 작동해, 정작 드리프트가 잦은 plugin-dev/git-only 환경에서 침묵했다. - check-cli-version.sh: 런타임 autopilot 게이트(github-autopilot.local.md 체크) 제거 + atelier 미설치 시 무음 exit 0 (미사용 환경 노이즈 방지). 설치 && installed < plugin 일 때만 1줄 안내. - setup.md: 등록을 Step 2b(autopilot 모듈)에서 Step 0b(공통)로 이동. 버전 드리프트는 autopilot 전용이 아니라 CLI 라이프사이클 관심사 (Step 0 ensure-binary 와 한 쌍: setup 시점 보장 vs 이후 드리프트 알림). 미설치/최신/상위 무음, 과거 버전만 경고 — 3개 상태 수동 검증. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- plugins/atelier/commands/setup.md | 19 +++++++++++++++---- plugins/atelier/hooks/check-cli-version.sh | 22 ++++++---------------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/plugins/atelier/commands/setup.md b/plugins/atelier/commands/setup.md index 0c1c2dc2..48952ab7 100644 --- a/plugins/atelier/commands/setup.md +++ b/plugins/atelier/commands/setup.md @@ -29,6 +29,20 @@ bash "${CLAUDE_PLUGIN_ROOT}/scripts/ensure-binary.sh" - plugin.json 버전과 설치된 `atelier --version` 을 SemVer 비교해 필요 시 빌드/설치합니다 (`~/.local/bin/atelier`) - 실패하면(cargo 부재 등) 이후 Step 을 진행하지 말고 에러를 안내합니다 +## Step 0b — CLI 버전 드리프트 알림 hook (공통) + +Step 0 은 **setup 시점**의 바이너리만 보장합니다. 이후 플러그인이 업데이트되면 설치본이 뒤처질 수 있으므로, +세션 시작마다 드리프트를 알리는 SessionStart hook 을 등록합니다 (autopilot 여부와 무관 — atelier 를 쓰는 모든 +환경의 공통 CLI 라이프사이클 관심사). hook 은 atelier 미설치 시 무음이라 미사용 환경에 노이즈를 주지 않습니다: + +```bash +atelier git hook register SessionStart "*" \ + '${CLAUDE_PLUGIN_ROOT}/hooks/check-cli-version.sh' --project-dir "$HOME" +``` + +> `${CLAUDE_PLUGIN_ROOT}` 는 **리터럴로 보존**합니다 (hook 실행 시점에 활성 플러그인 버전 경로로 해석 → +> 버전 업데이트마다 자동으로 최신 스크립트를 가리킴). 절대경로로 expand 하면 frozen 되어 드리프트를 못 잡습니다. + ## Step 1 — 설치 모듈 선택 `AskUserQuestion` 으로 설치할 모듈을 선택합니다 (multiSelect). @@ -86,11 +100,8 @@ bash "${CLAUDE_PLUGIN_ROOT}/scripts/ensure-binary.sh" ## Step 2b — autopilot 모듈 1. 프로젝트 설정 파일 `github-autopilot.local.md` 생성 (기존 스키마/경로 동일 — 호환). -2. autopilot hook 3종 등록 — 로직이 아직 `.sh` 에 있으므로(#776 에서 CLI 이전 예정) `${CLAUDE_PLUGIN_ROOT}` 리터럴 shim 으로 기록: +2. autopilot hook 2종 등록 — 로직이 아직 `.sh` 에 있으므로(#776 에서 CLI 이전 예정) `${CLAUDE_PLUGIN_ROOT}` 리터럴 shim 으로 기록 (버전 드리프트 알림 hook 은 Step 0b 에서 공통 등록): ```bash - atelier git hook register SessionStart "*" \ - '${CLAUDE_PLUGIN_ROOT}/hooks/check-cli-version.sh' --project-dir "$HOME" - atelier git hook register PreToolUse "Bash" \ '${CLAUDE_PLUGIN_ROOT}/hooks/guard-pr-base.sh' --project-dir "$HOME" diff --git a/plugins/atelier/hooks/check-cli-version.sh b/plugins/atelier/hooks/check-cli-version.sh index c92d8c96..93e10da0 100755 --- a/plugins/atelier/hooks/check-cli-version.sh +++ b/plugins/atelier/hooks/check-cli-version.sh @@ -1,24 +1,15 @@ #!/usr/bin/env bash # check-cli-version.sh — SessionStart hook -# 세션 시작 시 atelier CLI 버전이 plugin.json과 일치하는지 확인합니다. -# 과거 버전이면 업데이트 안내를 출력합니다. +# 세션 시작 시 설치된 atelier CLI 버전이 활성 플러그인 버전과 일치하는지 확인합니다. # -# 트리거: SessionStart +# 트리거: SessionStart (글로벌 등록 — atelier 설치 여부로 스스로 판단) # 동작: -# - autopilot 프로젝트가 아니면 → exit 0 (skip) -# - CLI 미설치 또는 과거 버전 → 안내 출력 후 exit 0 -# - 최신 버전 → exit 0 +# - atelier CLI 미설치 → exit 0 (무음 — atelier 미사용 환경 배려) +# - 설치 버전 < 플러그인 버전 → 업데이트 안내 한 줄 출력 후 exit 0 +# - 최신/상위 버전 → exit 0 (무음) set -euo pipefail -PROJECT_DIR="${CLAUDE_PROJECT_DIR:-.}" -CONFIG_FILE="${PROJECT_DIR}/github-autopilot.local.md" - -# autopilot 프로젝트가 아니면 skip -if [[ ! -f "$CONFIG_FILE" ]]; then - exit 0 -fi - # stdin 소비 (SessionStart hook은 session info를 stdin으로 받음) cat > /dev/null @@ -36,9 +27,8 @@ if [[ -z "$PLUGIN_VERSION" ]]; then exit 0 fi -# --- 설치된 CLI 버전 확인 --- +# --- 설치된 CLI 버전 확인 (미설치면 무음 skip — atelier 미사용 환경) --- if ! command -v atelier &> /dev/null; then - echo "atelier CLI가 설치되어 있지 않습니다. /atelier:setup 을 실행하여 설치하세요." exit 0 fi From 280839f1ece52620662208ee1fd5ed4709c71a90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=9A=A9=EC=84=B1?= Date: Sun, 14 Jun 2026 18:57:31 +0900 Subject: [PATCH 2/4] refactor(atelier): ship version-drift hook as plugin-declared, not setup-registered MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit check-cli-version SessionStart hook 을 setup 시점 settings.json 등록 대신 플러그인이 hooks/hooks.json 으로 직접 선언하도록 전환. - hooks/hooks.json: SessionStart "*" → check-cli-version.sh 선언. 플러그인 활성화 시 자동 적용, ${CLAUDE_PLUGIN_ROOT} 가 활성 버전 경로로 해석되어 frozen-path 문제 원천 차단. - setup.md: 직전 커밋의 Step 0b(공통 등록) 제거 — 더 이상 setup 이 이 hook 을 등록하지 않음. Step 0 ensure-binary 와의 관계만 주석으로 남김. guard-pr-base/protect-stagnation 은 autopilot 고유(opt-in)라 setup 등록 유지. NOTE: 기존 setup 으로 settings.json 에 frozen 경로 등록된 사용자는 plugin-declared hook 과 command 문자열이 달라 dedup 되지 않아 둘 다 실행됨. 레거시 SessionStart 항목 제거는 별도 마이그레이션. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- plugins/atelier/commands/setup.md | 16 +++------------- plugins/atelier/hooks/hooks.json | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 13 deletions(-) create mode 100644 plugins/atelier/hooks/hooks.json diff --git a/plugins/atelier/commands/setup.md b/plugins/atelier/commands/setup.md index 48952ab7..7e0e6f49 100644 --- a/plugins/atelier/commands/setup.md +++ b/plugins/atelier/commands/setup.md @@ -29,19 +29,9 @@ bash "${CLAUDE_PLUGIN_ROOT}/scripts/ensure-binary.sh" - plugin.json 버전과 설치된 `atelier --version` 을 SemVer 비교해 필요 시 빌드/설치합니다 (`~/.local/bin/atelier`) - 실패하면(cargo 부재 등) 이후 Step 을 진행하지 말고 에러를 안내합니다 -## Step 0b — CLI 버전 드리프트 알림 hook (공통) - -Step 0 은 **setup 시점**의 바이너리만 보장합니다. 이후 플러그인이 업데이트되면 설치본이 뒤처질 수 있으므로, -세션 시작마다 드리프트를 알리는 SessionStart hook 을 등록합니다 (autopilot 여부와 무관 — atelier 를 쓰는 모든 -환경의 공통 CLI 라이프사이클 관심사). hook 은 atelier 미설치 시 무음이라 미사용 환경에 노이즈를 주지 않습니다: - -```bash -atelier git hook register SessionStart "*" \ - '${CLAUDE_PLUGIN_ROOT}/hooks/check-cli-version.sh' --project-dir "$HOME" -``` - -> `${CLAUDE_PLUGIN_ROOT}` 는 **리터럴로 보존**합니다 (hook 실행 시점에 활성 플러그인 버전 경로로 해석 → -> 버전 업데이트마다 자동으로 최신 스크립트를 가리킴). 절대경로로 expand 하면 frozen 되어 드리프트를 못 잡습니다. +> CLI 버전 드리프트 알림(SessionStart)은 setup 이 등록하지 않습니다 — 플러그인이 `hooks/hooks.json` 으로 +> 직접 선언해 활성화 시 자동 적용됩니다(`${CLAUDE_PLUGIN_ROOT}` 해석으로 버전 frozen 없음). Step 0 이 +> *setup 시점* 바이너리를 보장하면, 그 hook 이 *이후 드리프트*를 알리는 한 쌍입니다. ## Step 1 — 설치 모듈 선택 diff --git a/plugins/atelier/hooks/hooks.json b/plugins/atelier/hooks/hooks.json new file mode 100644 index 00000000..700ecf7c --- /dev/null +++ b/plugins/atelier/hooks/hooks.json @@ -0,0 +1,15 @@ +{ + "hooks": { + "SessionStart": [ + { + "matcher": "*", + "hooks": [ + { + "type": "command", + "command": "\"${CLAUDE_PLUGIN_ROOT}\"/hooks/check-cli-version.sh" + } + ] + } + ] + } +} From c027e79fc5bbe4005d6446a42199a2be7bec2ed8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=9A=A9=EC=84=B1?= Date: Sun, 14 Jun 2026 20:26:26 +0900 Subject: [PATCH 3/4] refactor(atelier): apply simplify findings to version-drift hook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /simplify 4-angle 리뷰 후속: - check-cli-version.sh: command -v atelier 검사를 최상단으로 이동(효율). hook 이 모든 세션에서 실행되므로 미설치 환경(다수)에서 plugin.json grep|head|sed 파이프라인·stdin 처리 전에 builtin 검사로 즉시 종료. - setup.md Step 2b: 제거된 "Step 0b" 를 가리키던 dangling 참조 수정 → plugin-declared(hooks.json) 안내로 교정. - setup.md Step 3: frozen 마이그레이션 표에서 check-cli-version 을 "제거만(재등록 안 함)" 으로 변경. plugin-declared 로 대체됐는데 settings.json 으로 재등록하면 SessionStart 이중 실행되던 모순 제거. - tool-layer-boundary.md: hook 등록의 제3형식(plugin-declared hooks.json)을 규칙에 명시 — 기존 "shim OR hook register" 이분법에서 hooks.json 이 위반으로 오독되던 갭 보완. Simplify findings (skipped): - compare_semver 가 ensure-binary.sh 와 중복 — pre-existing 부트스트랩 shim (바이너리 부재 시 동작해야 해 CLI 위임 불가, 공유 sourcing 컨벤션 없음). 이 PR 이 만들거나 악화시키지 않았고 dedup 은 범위 밖. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .claude/rules/tool-layer-boundary.md | 6 ++++++ plugins/atelier/commands/setup.md | 8 +++++--- plugins/atelier/hooks/check-cli-version.sh | 10 +++++----- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/.claude/rules/tool-layer-boundary.md b/.claude/rules/tool-layer-boundary.md index c575ce1c..989adf97 100644 --- a/.claude/rules/tool-layer-boundary.md +++ b/.claude/rules/tool-layer-boundary.md @@ -54,6 +54,12 @@ atelier autopilot check stagnation # stdin payload 해석 - 등록 자체도 결정적 변환이므로 `atelier git hook register ` 로 수행한다. LLM 이 `Write` 로 settings.json 을 직접 편집하지 않는다 (#762). +- **plugin-declared hook**: setup·프로젝트 설정과 무관하게 플러그인 활성화만으로 모든 + 세션에 적용돼야 하는 config-free·non-blocking hook(예: `check-cli-version`)은 + 플러그인 루트의 `hooks/hooks.json` 에 리터럴 `${CLAUDE_PLUGIN_ROOT}` 로 선언한다. + 이는 settings.json 편집이 아니므로 `hook register` 가 필요 없고, 활성 플러그인 버전 + 경로로 해석돼 frozen 이 없다. 반대로 프로젝트별 설정이나 차단(exit 2)이 필요한 hook + (guard 류)은 opt-in 이라 `hook register` 로 settings.json 에 등록한다. ## 판단 기준 diff --git a/plugins/atelier/commands/setup.md b/plugins/atelier/commands/setup.md index 7e0e6f49..9731bd96 100644 --- a/plugins/atelier/commands/setup.md +++ b/plugins/atelier/commands/setup.md @@ -90,7 +90,7 @@ bash "${CLAUDE_PLUGIN_ROOT}/scripts/ensure-binary.sh" ## Step 2b — autopilot 모듈 1. 프로젝트 설정 파일 `github-autopilot.local.md` 생성 (기존 스키마/경로 동일 — 호환). -2. autopilot hook 2종 등록 — 로직이 아직 `.sh` 에 있으므로(#776 에서 CLI 이전 예정) `${CLAUDE_PLUGIN_ROOT}` 리터럴 shim 으로 기록 (버전 드리프트 알림 hook 은 Step 0b 에서 공통 등록): +2. autopilot hook 2종 등록 — 로직이 아직 `.sh` 에 있으므로(#776 에서 CLI 이전 예정) `${CLAUDE_PLUGIN_ROOT}` 리터럴 shim 으로 기록 (버전 드리프트 알림 hook 은 setup 이 등록하지 않음 — Step 0 안내 참조, 플러그인이 `hooks/hooks.json` 으로 직접 선언): ```bash atelier git hook register PreToolUse "Bash" \ '${CLAUDE_PLUGIN_ROOT}/hooks/guard-pr-base.sh' --project-dir "$HOME" @@ -135,7 +135,9 @@ bash "${CLAUDE_PLUGIN_ROOT}/scripts/ensure-binary.sh" 4. 사용자에게 치환 목록을 보여주고 AskUserQuestion 으로 확인 5. 매칭 entry 마다: atelier git hook unregister --project-dir "$HOME" → 아래 표의 대응 command 로 atelier git hook register (hook register 는 command 기준 - 중복 제거를 하므로 frozen + atelier 양쪽에 있던 hook 도 한 개만 남음) + 중복 제거를 하므로 frozen + atelier 양쪽에 있던 hook 도 한 개만 남음). + 단, 표에서 "제거만" 으로 표시된 hook 은 plugin-declared 로 대체됐으므로 unregister 만 + 하고 재등록하지 않는다 (재등록 시 hooks.json 선언과 SessionStart 이중 실행) ``` > **멱등성**: 이미 atelier 로 재작성된 settings.json 에 재실행하면 변경 0건이어야 합니다. @@ -144,7 +146,7 @@ bash "${CLAUDE_PLUGIN_ROOT}/scripts/ensure-binary.sh" | frozen 경로 | atelier 등록 command | |---|---| -| `github-autopilot/hooks/check-cli-version.sh` | `${CLAUDE_PLUGIN_ROOT}/hooks/check-cli-version.sh` (리터럴) | +| `github-autopilot/hooks/check-cli-version.sh` | **제거만** (재등록 안 함) — 플러그인이 `hooks/hooks.json` 으로 직접 선언 | | `github-autopilot/hooks/guard-pr-base.sh` | `${CLAUDE_PLUGIN_ROOT}/hooks/guard-pr-base.sh` (리터럴) | | `github-autopilot/hooks/protect-stagnation.sh` | `${CLAUDE_PLUGIN_ROOT}/hooks/protect-stagnation.sh` (리터럴) | | `coding-style/hooks/suggest-simplify.sh` | `${CLAUDE_PLUGIN_ROOT}/hooks/suggest-simplify.sh` (리터럴) | diff --git a/plugins/atelier/hooks/check-cli-version.sh b/plugins/atelier/hooks/check-cli-version.sh index 93e10da0..d852e38b 100755 --- a/plugins/atelier/hooks/check-cli-version.sh +++ b/plugins/atelier/hooks/check-cli-version.sh @@ -10,6 +10,10 @@ set -euo pipefail +# atelier 미설치 환경은 가장 싼 builtin 검사로 먼저 가른다 — 이 hook 은 모든 세션에서 +# 실행되므로(미설치가 다수), plugin.json 파싱·stdin 처리 전에 즉시 종료한다. +command -v atelier &> /dev/null || exit 0 + # stdin 소비 (SessionStart hook은 session info를 stdin으로 받음) cat > /dev/null @@ -27,11 +31,7 @@ if [[ -z "$PLUGIN_VERSION" ]]; then exit 0 fi -# --- 설치된 CLI 버전 확인 (미설치면 무음 skip — atelier 미사용 환경) --- -if ! command -v atelier &> /dev/null; then - exit 0 -fi - +# --- 설치된 CLI 버전 확인 --- INSTALLED_VERSION=$(atelier --version 2>/dev/null | awk '{print $NF}' || echo "") if [[ -z "$INSTALLED_VERSION" ]]; then From 50f5063c429c59610a27bafe62dd7e716bfc0040 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=9A=A9=EC=84=B1?= Date: Sun, 14 Jun 2026 23:42:26 +0900 Subject: [PATCH 4/4] refactor(atelier): use bare CLAUDE_PLUGIN_ROOT in hooks.json command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hooks.json 의 SessionStart command 를 escaped-quote 형태에서 bare 형태로 통일. 이전: "\"${CLAUDE_PLUGIN_ROOT}\"/hooks/check-cli-version.sh" 이후: "${CLAUDE_PLUGIN_ROOT}/hooks/check-cli-version.sh" 둘 다 shell form 으로 동작하지만(args 없음 → sh -c 해석), repo 전역이 bare 형태를 사용하고 tool-layer-boundary.md 규칙 line 49 의 이 스크립트 예시도 bare 라 일관성을 맞춤. plugin cache 경로는 공백이 없어 quote 불필요. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- plugins/atelier/hooks/hooks.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/atelier/hooks/hooks.json b/plugins/atelier/hooks/hooks.json index 700ecf7c..3b67d244 100644 --- a/plugins/atelier/hooks/hooks.json +++ b/plugins/atelier/hooks/hooks.json @@ -6,7 +6,7 @@ "hooks": [ { "type": "command", - "command": "\"${CLAUDE_PLUGIN_ROOT}\"/hooks/check-cli-version.sh" + "command": "${CLAUDE_PLUGIN_ROOT}/hooks/check-cli-version.sh" } ] }