geobuke-code 0.2.9 → 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/README.md CHANGED
@@ -100,11 +100,11 @@ phase-protocol/계획 → /plan(SubTask) → 【게이트: 구현 직전 케이
100
100
  | **코드 변경 직전** | PreToolUse (`Edit\|Write\|MultiEdit`) | 명세 ↔ 변경 ↔ defer 대조 → 통과(침묵)/차단(시나리오 도출 지시)/fail-open |
101
101
  | **작업단위당 1회** | (PreToolUse 캐시) | 같은 명세 해시 내에선 첫 편집만 판정, 이후 통과 → 매 편집 지연 회피 |
102
102
  | **응답 종료** | Stop | 계측 flush(`events.jsonl`) + 미해결 defer가 있으면 리마인드(매 대화 종료마다). 거슬리면 `gbc defer mute`(또는 `/gbc-mute` 스킬)로 끈다 — SessionStart 진입 알림은 유지 |
103
- | **업데이트 필요 시** | (PreToolUse·SessionStart) | hook 구버전(②) 또는 신버전 출시(①)면 갱신 안내. PreToolUse는 세션당 1회(`systemMessage` 비차단), SessionStart는 진입 시 표시. `gbc status`는 캐시만 갱신하고 안내는 **표시하지 않는다**(명시 진단 명령). 게이트 통과/차단 동작은 불변 |
103
+ | **업데이트 필요 시** | (PreToolUse·SessionStart) | hook 구버전(②) 또는 신버전 출시(①)면 갱신 안내. PreToolUse는 세션당 1회(`systemMessage` 비차단) — **통과된 작업단위(cached-skip) 편집에도 표시**(0.3.0: 평상 작업 대부분이 cached-skip이라, 여기서 빠지면 배너가 거의 안 떴음). SessionStart는 진입 시 표시(모델 컨텍스트). `gbc status`는 캐시만 갱신하고 안내는 **표시하지 않는다**(명시 진단 명령). 게이트 통과/차단 동작은 불변 |
104
104
 
105
105
  > 세션 진입 알림만 끄려면 `GBC_NO_SESSION_HINT=1`. 매 대화 종료(Stop) defer 리마인드만 끄려면 `gbc defer mute`(영속, 해제 `unmute` · 스킬 `/gbc-mute`) — 진입 알림은 남는다. 업데이트 안내만 끄려면 `GBC_NO_UPDATE_NOTICE=1`.
106
106
  > 프로젝트 hook이 구식이거나(SessionStart 누락·옛 명령) 새 버전이 나오면 gbc가 감지해 **`gbc update`**(전역 최신 + 현재 프로젝트 재init 한방) 또는 수동 `npm i -g geobuke-code@latest → gbc init --yes`를 안내한다. 단 안내는 **이미 hook이 등록된 프로젝트**(=한 번이라도 `gbc init`을 한 코호트)에만 도달한다 — 전혀 init하지 않은 프로젝트엔 실행할 hook이 없어 구조적으로 알릴 수 없다(gbc는 전역 hook을 깔지 않는다).
107
- > **업데이트 안내(①)는 네트워크를 게이트 핫패스에 들이지 않는다**: `~/.gbc/version-check.json` 캐시만 비교하고, 갱신 fetch는 SessionStart·`gbc status`에서만 짧은 타임아웃(1.5s)으로. SessionStart는 캐시가 stale이면 **표시 전에 갱신**해 신버전이 그 세션에 바로 뜬다(1세션 지연 없음). 조회 실패는 조용히 무시(fail-silent)되어 게이트 결정에 영향이 없다. 캐시 TTL 24h.
107
+ > **업데이트 안내(①)는 네트워크를 게이트 핫패스에 들이지 않는다**: `~/.gbc/version-check.json` 캐시만 비교하고, 갱신 fetch는 안전한 비-핫패스에서만 짧은 타임아웃(1.5s)으로. SessionStart는 캐시가 stale이면 **표시 전에 갱신**해 신버전이 그 세션에 바로 뜬다(1세션 지연 없음). ⓑ**PreToolUse는 judge를 도는 편집(cache-miss)에서 캐시가 stale이면 refresh를 judge와 *병렬*로 건다**(0.3.0) — judge가 ≥1.5s라 지연 0이고, 사용자가 `gbc status`를 직접 치지 않아도 캐시가 최신이 된다. **cached-skip 핫패스에는 네트워크를 절대 넣지 않는다.** 조회 실패는 조용히 무시(fail-silent)되어 게이트 결정에 영향이 없다. 캐시 TTL 24h.
108
108
 
109
109
  ### 시나리오 도출 루프 (수기 입력 불필요)
110
110
 
package/dist/hook.js CHANGED
@@ -8,7 +8,7 @@ import { activeDeferItems, loadDefers } from "./defer.js";
8
8
  import { isStopHintMuted } from "./config.js";
9
9
  import { loadRepos } from "./repos.js";
10
10
  import { readProjectSettings, buildUpdateNotice, wasNotified, markNotified } from "./notice.js";
11
- import { isCacheStale, readVersionCache, refreshVersionCache } from "./version.js";
11
+ import { isCacheStale, readVersionCache, refreshVersionCache, shouldRefreshCache, } from "./version.js";
12
12
  import { appendFileSync, existsSync, lstatSync } from "node:fs";
13
13
  import { join, resolve } from "node:path";
14
14
  import { gbcDir } from "./store.js";
@@ -142,13 +142,27 @@ export async function runPreToolUse(ctx) {
142
142
  tool: toolName,
143
143
  decision: "cached",
144
144
  });
145
+ // 업데이트 안내(있으면)를 cached-skip에서도 노출 — 평상 작업은 대부분 통과된 작업단위라
146
+ // 이 경로가 가장 흔하다. 여기서 빠지면 보이는 배너(PreToolUse systemMessage)가 거의 안 떴음
147
+ // (0.2.x 가시성 갭). maybeUpdateNotice는 세션당 1회 dedup이라 노이즈 없음(매 세션 첫 편집 1회).
148
+ // permissionDecision 없음 → cached-pass 통과 동작 불변. 네트워크 없음(캐시만 읽음).
149
+ const cachedNotice = maybeUpdateNotice(cwd, session, ctx);
150
+ if (cachedNotice)
151
+ emit({ systemMessage: cachedNotice });
145
152
  process.exit(0);
146
153
  }
147
154
  // judge는 여기서만 동적 import (SDK lazy)
148
155
  const { judge } = await import("./judge.js");
149
156
  const editText = normalizeEdit(toolName, input.tool_input ?? {});
150
157
  const defers = activeDeferItems(cwd);
158
+ // ①신버전 캐시 자동 refresh(0.3.0) — 사용자가 'gbc status'를 안 쳐도 캐시가 최신이 되게.
159
+ // judge(네트워크·≥1.5s)와 *병렬*로만 건다 → 핫패스 지연 0. cache-miss(여기 = judge 도는
160
+ // 비-핫패스)에서만 stale일 때. cached-skip 핫패스엔 절대 네트워크 안 넣는다(0.2.7 원칙 보존).
161
+ // refreshVersionCache는 내부 fail-silent(reject 불가)라 judge 경로를 깨지 않는다.
162
+ const refreshP = shouldRefreshCache(Boolean(ctx?.cliPath)) ? refreshVersionCache() : null;
151
163
  const verdict = await judge(specText, editText, defers);
164
+ if (refreshP)
165
+ await refreshP; // judge 동안 이미 완료 — 이 편집의 notice가 갱신된 캐시를 읽도록
152
166
  if (verdict.verdict === "pass") {
153
167
  // fail-open(판정 실패) 먼저 분기 — 빈-spec 정상 pass가 fail-open으로 오분류되지 않게.
154
168
  if (verdict.failOpen) {
package/dist/version.js CHANGED
@@ -69,6 +69,18 @@ export function buildVersionNotice(current, cache) {
69
69
  return (`🐢 거북이코드 신버전 ${cache.latest} 사용 가능(현재 ${current}). ` +
70
70
  `갱신: 'gbc update'(전역 최신 + 현재 프로젝트 재init) 또는 수동 'npm i -g geobuke-code@latest → gbc init --yes'`);
71
71
  }
72
+ /**
73
+ * 캐시 자동 refresh를 해야 하는지(순수 술어). cliPath 없으면(직접 hook 호출) X, 안내 opt-out X,
74
+ * 캐시가 stale일 때만 true. PreToolUse cache-miss(judge 경로)에서 judge와 병렬 refresh를 거는
75
+ * 게이트 — 핫패스(cached-skip)는 절대 호출 안 함. env·파일 의존이라 테스트는 home/now 주입.
76
+ */
77
+ export function shouldRefreshCache(hasCliPath, home, now = Date.now()) {
78
+ if (!hasCliPath)
79
+ return false;
80
+ if (process.env.GBC_NO_UPDATE_NOTICE === "1")
81
+ return false;
82
+ return isCacheStale(readVersionCache(home), now);
83
+ }
72
84
  /**
73
85
  * npm 레지스트리에서 최신 버전을 받아 캐시에 쓴다(짧은 타임아웃, 비차단·fail-silent).
74
86
  * spawn(npm) 대신 fetch — Windows .cmd 실행 문제를 피한다. 실패·타임아웃은 조용히 무시.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "geobuke-code",
3
- "version": "0.2.9",
3
+ "version": "0.3.0",
4
4
  "description": "거북이코드 — 구현 직전 강제 게이트. Claude Code PreToolUse hook으로 코드 변경 전 계획 케이스 누락·시나리오 미지정을 차단한다.",
5
5
  "type": "module",
6
6
  "bin": {