geobuke-code 0.2.5 → 0.2.7
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 +8 -6
- package/dist/cli.js +76 -11
- package/dist/config.js +18 -0
- package/dist/hook.js +25 -11
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/skills/gate/SKILL.md +4 -2
- package/skills/gbc-mute/SKILL.md +38 -0
package/README.md
CHANGED
|
@@ -99,12 +99,12 @@ phase-protocol/계획 → /plan(SubTask) → 【게이트: 구현 직전 케이
|
|
|
99
99
|
| **세션 시작·재개** | SessionStart (`startup\|resume`) | `.gbc/defers.json`의 미해결 항목을 "진행중 N · 미착수 M"로 구분 표면화(이전 작업 잔여 환기). 잔여 없으면 무출력. `compact`엔 발화 안 함(노이즈 방지) |
|
|
100
100
|
| **코드 변경 직전** | PreToolUse (`Edit\|Write\|MultiEdit`) | 명세 ↔ 변경 ↔ defer 대조 → 통과(침묵)/차단(시나리오 도출 지시)/fail-open |
|
|
101
101
|
| **작업단위당 1회** | (PreToolUse 캐시) | 같은 명세 해시 내에선 첫 편집만 판정, 이후 통과 → 매 편집 지연 회피 |
|
|
102
|
-
| **응답 종료** | Stop | 계측 flush(`events.jsonl`) |
|
|
102
|
+
| **응답 종료** | Stop | 계측 flush(`events.jsonl`) + 미해결 defer가 있으면 리마인드(매 대화 종료마다). 거슬리면 `gbc defer mute`(또는 `/gbc-mute` 스킬)로 끈다 — SessionStart 진입 알림은 유지 |
|
|
103
103
|
| **업데이트 필요 시** | (PreToolUse·SessionStart) | hook 구버전(②) 또는 신버전 출시(①)면 갱신 안내. PreToolUse는 세션당 1회(`systemMessage` 비차단), SessionStart는 진입 시 표시. `gbc status`는 캐시만 갱신하고 안내는 **표시하지 않는다**(명시 진단 명령). 게이트 통과/차단 동작은 불변 |
|
|
104
104
|
|
|
105
|
-
> 세션 진입 알림만 끄려면 `GBC_NO_SESSION_HINT=1`. 업데이트 안내만 끄려면 `GBC_NO_UPDATE_NOTICE=1`.
|
|
106
|
-
> 프로젝트 hook이 구식이거나(SessionStart 누락·옛 명령) 새 버전이 나오면 gbc가 감지해
|
|
107
|
-
> **업데이트 안내(①)는 네트워크를 게이트 핫패스에 들이지 않는다**: `~/.gbc/version-check.json` 캐시만 비교하고,
|
|
105
|
+
> 세션 진입 알림만 끄려면 `GBC_NO_SESSION_HINT=1`. 매 대화 종료(Stop) defer 리마인드만 끄려면 `gbc defer mute`(영속, 해제 `unmute` · 스킬 `/gbc-mute`) — 진입 알림은 남는다. 업데이트 안내만 끄려면 `GBC_NO_UPDATE_NOTICE=1`.
|
|
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.
|
|
108
108
|
|
|
109
109
|
### 시나리오 도출 루프 (수기 입력 불필요)
|
|
110
110
|
|
|
@@ -134,13 +134,15 @@ phase-protocol/계획 → /plan(SubTask) → 【게이트: 구현 직전 케이
|
|
|
134
134
|
|
|
135
135
|
| 명령 | 설명 |
|
|
136
136
|
|---|---|
|
|
137
|
-
| `gbc init` | hook +
|
|
138
|
-
| `gbc
|
|
137
|
+
| `gbc init` | hook + `/gate` · `/gbc-mute` 스킬 설치 |
|
|
138
|
+
| `gbc update` | 전역 최신 설치(`npm i -g …@latest`) + 현재 프로젝트 재init 한방. `--dry-run`으로 실행 명령만 미리보기 |
|
|
139
|
+
| `gbc status` | 게이트 상태 + 로드된 명세 + Stop 리마인드 음소거 여부 |
|
|
139
140
|
| `gbc defer add "<케이스>"` | 케이스를 명시적으로 미루기 (→ open) |
|
|
140
141
|
| `gbc defer list` | 미룬 항목 목록 (상태: 미해결/진행중/해결) |
|
|
141
142
|
| `gbc defer start <번호\|텍스트\|all>` | 착수 표시 (open → 진행중) |
|
|
142
143
|
| `gbc defer resolve <번호\|텍스트\|all>` | 종결 표시 (→ 해결) |
|
|
143
144
|
| `gbc defer reopen <번호\|텍스트\|all>` | 백로그로 되돌리기 (→ open) |
|
|
145
|
+
| `gbc defer mute` / `unmute` | 대화 종료(Stop)마다 뜨는 defer 리마인드 끄기/켜기 (영속) · 스킬: `/gbc-mute` |
|
|
144
146
|
| `gbc spec add "<케이스>"` | 승인된 시나리오를 `.gbc/spec.md`에 등록 |
|
|
145
147
|
| `gbc spec show` | 등록된 케이스 목록 |
|
|
146
148
|
| `gbc spec clear` | 명세 비우기(작업단위 종료) |
|
package/dist/cli.js
CHANGED
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
5
|
import { homedir } from "node:os";
|
|
6
|
+
import { spawnSync } from "node:child_process";
|
|
6
7
|
import { mkdirSync, existsSync, readFileSync, writeFileSync, copyFileSync, } from "node:fs";
|
|
7
8
|
import { runPreToolUse, runStop, runSessionStart } from "./hook.js";
|
|
8
9
|
import { loadPlanSpec, computeSpecHash, addSpecCase, readSpecCases, clearSpec } from "./spec.js";
|
|
9
10
|
import { loadState, resetGate } from "./state.js";
|
|
10
11
|
import { addDefer, loadDefers, resolveDefer, startDefer, reopenDefer } from "./defer.js";
|
|
12
|
+
import { isStopHintMuted, setStopHintMuted } from "./config.js";
|
|
11
13
|
import { selectedTransport } from "./judge.js";
|
|
12
14
|
import { buildPreCommand, normalizeHooks, ensureSessionStartHook } from "./install.js";
|
|
13
15
|
import { isCacheStale, readVersionCache, refreshVersionCache, } from "./version.js";
|
|
@@ -65,15 +67,15 @@ async function cmdInit(args) {
|
|
|
65
67
|
const yes = args.includes("--yes") || args.includes("-y");
|
|
66
68
|
const claudeDir = join(cwd, ".claude");
|
|
67
69
|
const settingsPath = join(claudeDir, "settings.json");
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
+
// 설치 대상 스킬들(제품소스 skills/<name>/SKILL.md → .claude/skills/<name>/SKILL.md).
|
|
71
|
+
const skillNames = ["gate", "gbc-mute"];
|
|
70
72
|
if (!yes) {
|
|
71
73
|
console.log(`🐢 gbc init — 다음을 수행합니다 (프로젝트 로컬만, 전역 ~/.claude 미변경):
|
|
72
74
|
|
|
73
75
|
대상 프로젝트: ${cwd}
|
|
74
76
|
1) ${settingsPath} 에 PreToolUse(Edit|Write) + Stop + SessionStart hook 추가 (머지·멱등)
|
|
75
77
|
- 기존 settings.json 있으면 백업: settings.json.bak-<시각>
|
|
76
|
-
2) ${join(
|
|
78
|
+
2) ${join(claudeDir, "skills")} 에 ${skillNames.map((n) => `/${n}`).join(", ")} 스킬 설치
|
|
77
79
|
3) hook 명령: ${buildPreCommand(CLI_PATH)}
|
|
78
80
|
${hasApiKey()
|
|
79
81
|
? " (~/.gbc/api-key 감지됨 → 빠른 haiku API 경로로 동작)"
|
|
@@ -82,7 +84,7 @@ ${hasApiKey()
|
|
|
82
84
|
`);
|
|
83
85
|
return;
|
|
84
86
|
}
|
|
85
|
-
mkdirSync(
|
|
87
|
+
mkdirSync(claudeDir, { recursive: true });
|
|
86
88
|
// settings.json 머지
|
|
87
89
|
let settings = {};
|
|
88
90
|
if (existsSync(settingsPath)) {
|
|
@@ -132,10 +134,15 @@ ${hasApiKey()
|
|
|
132
134
|
console.log(` = SessionStart hook 이미 존재 (skip)`);
|
|
133
135
|
}
|
|
134
136
|
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
137
|
+
// 스킬 설치 (gate + gbc-mute)
|
|
138
|
+
for (const name of skillNames) {
|
|
139
|
+
const src = join(PKG_ROOT, "skills", name, "SKILL.md");
|
|
140
|
+
if (existsSync(src)) {
|
|
141
|
+
const destDir = join(claudeDir, "skills", name);
|
|
142
|
+
mkdirSync(destDir, { recursive: true });
|
|
143
|
+
copyFileSync(src, join(destDir, "SKILL.md"));
|
|
144
|
+
console.log(` + /${name} 스킬 설치`);
|
|
145
|
+
}
|
|
139
146
|
}
|
|
140
147
|
const transport = selectedTransport();
|
|
141
148
|
console.log(`
|
|
@@ -178,7 +185,8 @@ async function cmdStatus() {
|
|
|
178
185
|
명세 소스: ${source} ${text ? `(${text.length}자)` : "(비어있음 → 모든 코드변경 차단)"}
|
|
179
186
|
명세 해시: ${hash}
|
|
180
187
|
작업단위 게이트: ${state && state.specHash === hash && state.gated ? "통과됨(이 단위 재게이트 안 함)" : "미통과(다음 편집에서 발동)"}
|
|
181
|
-
defer: 전체 ${defers.length} / 미해결 ${unresolved.length} (진행중 ${inProgress} · 미착수 ${unresolved.length - inProgress})
|
|
188
|
+
defer: 전체 ${defers.length} / 미해결 ${unresolved.length} (진행중 ${inProgress} · 미착수 ${unresolved.length - inProgress})
|
|
189
|
+
Stop 리마인드: ${isStopHintMuted(cwd) ? "🔕 음소거 (해제: /gbc-mute)" : "🔔 켜짐"}`);
|
|
182
190
|
if (unresolved.length > 0) {
|
|
183
191
|
console.log(unresolved
|
|
184
192
|
.map((d, i) => ` ${i + 1}. ${d.status === "in_progress" ? "▶[진행중] " : ""}${d.item}`)
|
|
@@ -202,8 +210,22 @@ function cmdDefer(args) {
|
|
|
202
210
|
logCli(cwd, "defer-add", curHash(cwd));
|
|
203
211
|
console.log(`🐢 미룸 등록: ${item}`);
|
|
204
212
|
}
|
|
213
|
+
else if (sub === "mute" || sub === "unmute") {
|
|
214
|
+
const muted = sub === "mute";
|
|
215
|
+
setStopHintMuted(cwd, muted);
|
|
216
|
+
if (muted) {
|
|
217
|
+
console.log("🔕 Stop 리마인드 음소거됨 — 대화 종료마다 뜨던 defer 알림을 끕니다.\n" +
|
|
218
|
+
" (SessionStart 진입 시엔 계속 표시 · 해제는 'gbc defer unmute')");
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
console.log("🔔 Stop 리마인드 음소거 해제됨 — 대화 종료 시 미해결 defer 알림이 다시 표시됩니다.");
|
|
222
|
+
}
|
|
223
|
+
}
|
|
205
224
|
else if (sub === "list") {
|
|
206
225
|
const defers = loadDefers(cwd);
|
|
226
|
+
if (isStopHintMuted(cwd)) {
|
|
227
|
+
console.log("🔕 Stop 리마인드 음소거 중 (해제: gbc defer unmute)");
|
|
228
|
+
}
|
|
207
229
|
if (defers.length === 0) {
|
|
208
230
|
console.log("(미룬 항목 없음)");
|
|
209
231
|
return;
|
|
@@ -233,7 +255,7 @@ function cmdDefer(args) {
|
|
|
233
255
|
}
|
|
234
256
|
}
|
|
235
257
|
else {
|
|
236
|
-
console.error("사용: gbc defer <add|list|start|resolve|reopen> ...");
|
|
258
|
+
console.error("사용: gbc defer <add|list|start|resolve|reopen|mute|unmute> ...");
|
|
237
259
|
process.exit(1);
|
|
238
260
|
}
|
|
239
261
|
}
|
|
@@ -308,17 +330,58 @@ function cmdMetrics(args) {
|
|
|
308
330
|
게이트 리셋 ${m.m1.resets} · 통과후 churn ${m.m1.churnAfterPass}
|
|
309
331
|
⚠️ ${m.m1.note}`);
|
|
310
332
|
}
|
|
333
|
+
// ---------- gbc update ----------
|
|
334
|
+
/**
|
|
335
|
+
* 전역 최신 설치 + (현재 프로젝트면) 재init을 한 번에. 자동 silent 업데이트가 아니라 명시 명령 —
|
|
336
|
+
* 사용자가 nag를 보고 'gbc update' 한 줄로 갱신한다(매번 두 명령 외울 필요 제거).
|
|
337
|
+
* ★재init은 '새로 깔린' 바이너리를 fresh spawn해야 신규 스킬·hook이 반영된다(현재 실행 중인 건 구버전).
|
|
338
|
+
*/
|
|
339
|
+
function cmdUpdate(args) {
|
|
340
|
+
const cwd = process.cwd();
|
|
341
|
+
const dry = args.includes("--dry-run");
|
|
342
|
+
const isProject = existsSync(join(cwd, ".gbc"));
|
|
343
|
+
const steps = ["npm i -g geobuke-code@latest", ...(isProject ? ["gbc init --yes"] : [])];
|
|
344
|
+
if (dry) {
|
|
345
|
+
console.log("🐢 gbc update — 실행 예정(--dry-run):");
|
|
346
|
+
steps.forEach((s) => console.log(` $ ${s}`));
|
|
347
|
+
if (!isProject)
|
|
348
|
+
console.log(" (현재 폴더에 .gbc 없음 → init 생략. 프로젝트에서 'gbc init --yes' 실행)");
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
console.log(`🐢 gbc update — 전역 최신 설치${isProject ? " + 현재 프로젝트 재init" : ""}`);
|
|
352
|
+
// 1) 전역 최신 설치. shell:true + 고정 명령 문자열(사용자 입력 없음 → 인젝션 무관, 크로스플랫폼).
|
|
353
|
+
const r1 = spawnSync("npm i -g geobuke-code@latest", { stdio: "inherit", shell: true });
|
|
354
|
+
if (r1.status !== 0) {
|
|
355
|
+
console.error("❌ 전역 설치 실패. 권한 문제면 관리자 권한(Windows)·sudo 또는 수동 'npm i -g geobuke-code@latest'.");
|
|
356
|
+
process.exit(1);
|
|
357
|
+
}
|
|
358
|
+
// 2) gbc 프로젝트면 재init — 신규 스킬(gbc-mute 등)·최신 hook 반영.
|
|
359
|
+
if (isProject) {
|
|
360
|
+
const r2 = spawnSync("gbc init --yes", { stdio: "inherit", shell: true, cwd });
|
|
361
|
+
if (r2.status !== 0) {
|
|
362
|
+
console.error("⚠️ 전역 설치는 됐으나 'gbc init --yes' 실패 — 프로젝트에서 수동 실행하세요.");
|
|
363
|
+
process.exit(1);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
console.log("ℹ️ 현재 폴더는 gbc 프로젝트 아님(.gbc 없음) → 각 프로젝트에서 'gbc init --yes' 실행하세요.");
|
|
368
|
+
}
|
|
369
|
+
console.log("✅ gbc update 완료.");
|
|
370
|
+
}
|
|
311
371
|
function usage() {
|
|
312
372
|
console.log(`🐢 gbc — 거북이코드 구현-전 게이트
|
|
313
373
|
|
|
314
374
|
사용:
|
|
315
|
-
gbc init [--yes] 프로젝트에 hook + /gate 스킬 설치
|
|
375
|
+
gbc init [--yes] 프로젝트에 hook + /gate · /gbc-mute 스킬 설치
|
|
376
|
+
gbc update [--dry-run] 전역 최신 설치 + 현재 프로젝트 재init (한방 갱신)
|
|
316
377
|
gbc status 게이트 상태 + 로드된 명세 확인
|
|
317
378
|
gbc defer add "<케이스>" 케이스를 명시적으로 미루기 (→ open)
|
|
318
379
|
gbc defer list 미룬 항목 목록 (상태: 미해결/진행중/해결)
|
|
319
380
|
gbc defer start <번호|텍스트|all> 착수 표시 (open → 진행중)
|
|
320
381
|
gbc defer resolve <번호|텍스트|all> 종결 표시 (→ 해결; 항상 사용자 점검 후)
|
|
321
382
|
gbc defer reopen <번호|텍스트|all> 백로그로 되돌리기 (→ open)
|
|
383
|
+
gbc defer mute 대화 종료(Stop)마다 뜨는 defer 알림 끄기 (영속)
|
|
384
|
+
gbc defer unmute Stop defer 알림 다시 켜기
|
|
322
385
|
gbc spec add "<케이스>" 승인된 시나리오를 .gbc/spec.md에 등록
|
|
323
386
|
gbc spec show 등록된 케이스 목록
|
|
324
387
|
gbc spec clear 명세 비우기(작업단위 종료)
|
|
@@ -344,6 +407,8 @@ async function main() {
|
|
|
344
407
|
break;
|
|
345
408
|
case "init":
|
|
346
409
|
return cmdInit(rest);
|
|
410
|
+
case "update":
|
|
411
|
+
return cmdUpdate(rest);
|
|
347
412
|
case "status":
|
|
348
413
|
return cmdStatus();
|
|
349
414
|
case "defer":
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { gbcDir, readJson, writeJson } from "./store.js";
|
|
3
|
+
function configPath(cwd) {
|
|
4
|
+
return join(gbcDir(cwd), "config.json");
|
|
5
|
+
}
|
|
6
|
+
function readConfig(cwd) {
|
|
7
|
+
return readJson(configPath(cwd), {});
|
|
8
|
+
}
|
|
9
|
+
/** Stop hook defer 리마인드가 음소거 상태인지. 파일/키 부재 시 false(기본=노출). */
|
|
10
|
+
export function isStopHintMuted(cwd) {
|
|
11
|
+
return readConfig(cwd).stopHintMuted === true;
|
|
12
|
+
}
|
|
13
|
+
/** Stop hook defer 리마인드 음소거 토글을 영속 저장(수동 unmute 전까지 유지). */
|
|
14
|
+
export function setStopHintMuted(cwd, muted) {
|
|
15
|
+
const cfg = readConfig(cwd);
|
|
16
|
+
cfg.stopHintMuted = muted;
|
|
17
|
+
writeJson(configPath(cwd), cfg);
|
|
18
|
+
}
|
package/dist/hook.js
CHANGED
|
@@ -5,6 +5,7 @@ import { isGatedTool, normalizeEdit } from "./normalize.js";
|
|
|
5
5
|
import { loadPlanSpec, computeSpecHash } from "./spec.js";
|
|
6
6
|
import { isGated, markGated } from "./state.js";
|
|
7
7
|
import { activeDeferItems, loadDefers } from "./defer.js";
|
|
8
|
+
import { isStopHintMuted } from "./config.js";
|
|
8
9
|
import { readProjectSettings, buildUpdateNotice, wasNotified, markNotified } from "./notice.js";
|
|
9
10
|
import { isCacheStale, readVersionCache, refreshVersionCache } from "./version.js";
|
|
10
11
|
import { appendFileSync } from "node:fs";
|
|
@@ -233,6 +234,10 @@ export async function runStop() {
|
|
|
233
234
|
if (input.stop_hook_active === true)
|
|
234
235
|
process.exit(0);
|
|
235
236
|
const cwd = input.cwd || process.cwd();
|
|
237
|
+
// 사용자가 'gbc defer mute'로 Stop 리마인드를 음소거했으면 조용히 통과(unmute 전까지 영속).
|
|
238
|
+
// SessionStart 진입 알림은 별개 채널이라 영향 없음. emit 없이 종료 = Claude 정상 stop 허용.
|
|
239
|
+
if (isStopHintMuted(cwd))
|
|
240
|
+
process.exit(0);
|
|
236
241
|
// defers.json 없으면(파일 부재) 조용히 통과
|
|
237
242
|
if (loadDefers(cwd).length === 0)
|
|
238
243
|
process.exit(0);
|
|
@@ -311,11 +316,30 @@ export async function runSessionStart(ctx) {
|
|
|
311
316
|
// 미해결 defer 알림(GBC_NO_SESSION_HINT로 opt-out — 기존 동작 보존).
|
|
312
317
|
if (process.env.GBC_NO_SESSION_HINT !== "1") {
|
|
313
318
|
const hint = buildSessionStartHint(loadDefers(cwd));
|
|
314
|
-
if (hint)
|
|
319
|
+
if (hint) {
|
|
315
320
|
parts.push(hint);
|
|
321
|
+
// Stop 리마인드 음소거 중이면 진입 시 1회 환기("꺼둔 걸 잊지 않게"). hint가 있을 때만
|
|
322
|
+
// = 미해결 defer가 있을 때만(잔여 0이면 음소거 무관·노이즈). buildSessionStartHint는
|
|
323
|
+
// 순수 유지하고 오케스트레이션에서만 한 줄 첨부(시그니처 미오염).
|
|
324
|
+
if (isStopHintMuted(cwd)) {
|
|
325
|
+
parts.push("🔕 Stop 리마인드 음소거 중 — 매 대화 종료 알림은 꺼져 있습니다 (해제: /gbc-mute).");
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
// ①신버전 안내 — 캐시가 stale이면 '표시 전에' 먼저 갱신(1.5s 상한, fail-silent)해 이번 세션에
|
|
330
|
+
// 즉시 반영한다(표시-후-갱신의 '다음 세션 지연' 제거). SessionStart는 게이트를 막지 않으므로
|
|
331
|
+
// 짧은 네트워크가 안전(advisor 승인). 갱신은 24h TTL당 1회만 발생.
|
|
332
|
+
try {
|
|
333
|
+
if (ctx?.cliPath && process.env.GBC_NO_UPDATE_NOTICE !== "1" && isCacheStale(readVersionCache())) {
|
|
334
|
+
await refreshVersionCache();
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
catch {
|
|
338
|
+
/* 갱신 실패는 무시(fail-silent) */
|
|
316
339
|
}
|
|
317
340
|
// 업데이트 안내(staleness + version) — SessionStart 보유 코호트(0.2.3+)용. 세션 식별자가 없어
|
|
318
341
|
// 항상 표시되므로 dedup 대신 GBC_NO_UPDATE_NOTICE opt-out에 맡긴다(buildUpdateNotice 내부).
|
|
342
|
+
// 위에서 갱신된 캐시를 읽으므로 신버전이 뜨는 그 세션에 즉시 표시된다.
|
|
319
343
|
try {
|
|
320
344
|
if (ctx?.cliPath) {
|
|
321
345
|
const notice = buildUpdateNotice(readProjectSettings(cwd), ctx.cliPath, ctx.version ?? "");
|
|
@@ -328,15 +352,5 @@ export async function runSessionStart(ctx) {
|
|
|
328
352
|
}
|
|
329
353
|
if (parts.length > 0)
|
|
330
354
|
process.stdout.write(parts.join("\n"));
|
|
331
|
-
// 버전 캐시 갱신은 '표시 후'에만(이번 출력은 캐시값 기준, 갱신은 다음 세션용). SessionStart는
|
|
332
|
-
// 게이트를 막지 않으므로 짧은 타임아웃 네트워크가 안전(advisor 승인). 실패는 조용히 무시.
|
|
333
|
-
try {
|
|
334
|
-
if (ctx?.cliPath && process.env.GBC_NO_UPDATE_NOTICE !== "1" && isCacheStale(readVersionCache())) {
|
|
335
|
-
await refreshVersionCache();
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
catch {
|
|
339
|
-
/* 갱신 실패는 무시(fail-silent) */
|
|
340
|
-
}
|
|
341
355
|
process.exit(0);
|
|
342
356
|
}
|
package/dist/version.js
CHANGED
|
@@ -67,7 +67,7 @@ export function buildVersionNotice(current, cache) {
|
|
|
67
67
|
if (compareVersions(current, cache.latest) >= 0)
|
|
68
68
|
return "";
|
|
69
69
|
return (`🐢 거북이코드 신버전 ${cache.latest} 사용 가능(현재 ${current}). ` +
|
|
70
|
-
`갱신: npm i -g geobuke-code@latest →
|
|
70
|
+
`갱신: 'gbc update'(전역 최신 + 현재 프로젝트 재init) 또는 수동 'npm i -g geobuke-code@latest → gbc init --yes'`);
|
|
71
71
|
}
|
|
72
72
|
/**
|
|
73
73
|
* npm 레지스트리에서 최신 버전을 받아 캐시에 쓴다(짧은 타임아웃, 비차단·fail-silent).
|
package/package.json
CHANGED
package/skills/gate/SKILL.md
CHANGED
|
@@ -63,9 +63,11 @@ defer 항목은 **open(미착수) → in_progress(진행중) → resolved(해결
|
|
|
63
63
|
## 명세 소스
|
|
64
64
|
|
|
65
65
|
게이트는 다음 우선순위로 계획 명세를 읽는다(durable 소스만):
|
|
66
|
-
`$GBC_SPEC_FILE` > `.gbc/spec.md`
|
|
66
|
+
`$GBC_SPEC_FILE` > `.gbc/spec.md`
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
`.gbc/spec.md`가 단일 정본(canonical)이다. `scratch.md` 자동 폴백은 0.2.2에서 제거됐다(진행추적 파일을 명세로 오인하던 거짓음성 차단) — 다른 파일을 명세로 쓰려면 `$GBC_SPEC_FILE`로 명시 지정한다.
|
|
69
|
+
|
|
70
|
+
명세가 비면 "시나리오 미지정"으로 모든 코드 변경이 차단된다. 보통 위 「사용 흐름」 2의 도출 루프가 `gbc spec add`로 `.gbc/spec.md`를 채운다(수기 작성 불필요).
|
|
69
71
|
|
|
70
72
|
## Known Pitfalls
|
|
71
73
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: gbc-mute
|
|
3
|
+
description: 거북이코드(gbc)의 defer Stop 리마인드를 on/off 토글한다. 미해결 defer가 있으면 Stop hook이 매 대화 종료(턴)마다 리마인드를 띄우는데, 이게 거슬릴 때 이 스킬로 음소거하거나 다시 켠다. '/gbc-mute', 'defer 알림 꺼줘', 'defer 알림 그만', '매번 뜨는 거 꺼줘', 'defer 음소거', 'Stop 리마인드 음소거', 'defer 알림 다시 켜줘', '음소거 해제', '리마인드 상태' 등 언급 시 호출.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /gbc-mute — defer Stop 리마인드 음소거 토글
|
|
7
|
+
|
|
8
|
+
미해결 defer가 있으면 거북이코드 **Stop hook이 매 대화 종료(턴)마다** 리마인드를 띄운다. `stop_hook_active` 가드는 한 턴 안의 루프만 끊을 뿐 세션 영속 억제가 아니라, **새 턴마다 재발화**한다(이월해도 계속 노출됨). 이 스킬은 그 매-턴 리마인드를 켜고 끄는 전용 토글이다.
|
|
9
|
+
|
|
10
|
+
## 동작
|
|
11
|
+
|
|
12
|
+
토글은 `.gbc/config.json`의 `stopHintMuted` 플래그로 영속된다(`gbc defer unmute` 전까지 유지 — 새 defer·세션 교체·`gbc gate reset`에도 풀리지 않음).
|
|
13
|
+
|
|
14
|
+
| 사용자 의도 | 명령 |
|
|
15
|
+
|---|---|
|
|
16
|
+
| 현재 상태 확인 | `gbc defer list` (상단에 음소거 여부 표기) |
|
|
17
|
+
| 음소거 켜기 (매-턴 Stop 리마인드 끄기) | `gbc defer mute` |
|
|
18
|
+
| 음소거 끄기 (리마인드 다시 켜기) | `gbc defer unmute` |
|
|
19
|
+
|
|
20
|
+
## 실행 흐름 (에이전트)
|
|
21
|
+
|
|
22
|
+
1. **먼저 현재 상태를 확인**한다 — `gbc defer list`로 음소거 여부를 읽는다.
|
|
23
|
+
2. 사용자 발화에서 의도를 판정해 토글한다:
|
|
24
|
+
- "꺼줘 / 그만 / 음소거 / 조용히" → `gbc defer mute`
|
|
25
|
+
- "켜줘 / 다시 / 해제" → `gbc defer unmute`
|
|
26
|
+
- 의도가 모호하면(예: "/gbc-mute"만 입력) → **현재 상태를 보여주고** 켤지 끌지 사용자에게 묻는다(상태-인지 토글, 무턱대고 뒤집지 않는다).
|
|
27
|
+
3. 실행 결과를 **사용자에게 표면화**한다(`gbc defer mute`/`unmute`의 출력을 그대로 전달).
|
|
28
|
+
|
|
29
|
+
## 끄는 범위 (중요)
|
|
30
|
+
|
|
31
|
+
- **Stop 채널만** 끈다 — 매 대화 종료마다 강요되던 알림.
|
|
32
|
+
- **SessionStart(세션 진입) 알림은 유지**한다. 새 세션 시작 시 "이전 작업 잔여"를 **한 번은** 회상하도록(완전 망각 방지). 즉 음소거는 "매 턴 강요"만 제거하고, 진입 시 1회 환기는 남긴다.
|
|
33
|
+
- 음소거 중이면 SessionStart 진입 알림 끝에 `🔕 음소거 중 (해제: /gbc-mute)` 한 줄이 따라붙고, `gbc status`·`gbc defer list`에도 상태가 표기되므로 "꺼둔 걸 잊는" 일이 없다.
|
|
34
|
+
|
|
35
|
+
## Known Pitfalls
|
|
36
|
+
|
|
37
|
+
- **음소거는 defer를 지우지 않는다.** 항목은 그대로 남아 게이트 판정·SessionStart 회상에 계속 쓰인다. 끄는 건 "매-턴 알림"뿐이다. 항목을 끝낸 거면 음소거가 아니라 `gbc defer resolve`다(→ `/gate`).
|
|
38
|
+
- **SessionStart는 음소거 대상이 아니다.** "진입 시에도 안 뜨게" 해달라는 요청이면 음소거로는 안 된다 — 그건 별개 채널(`GBC_NO_SESSION_HINT=1`)이며 의도적으로 분리돼 있다.
|