geobuke-code 0.2.7 → 0.2.9
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 +31 -0
- package/dist/cli.js +57 -8
- package/dist/hook.js +52 -2
- package/dist/install.js +25 -5
- package/dist/repos.js +32 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -148,9 +148,40 @@ phase-protocol/계획 → /plan(SubTask) → 【게이트: 구현 직전 케이
|
|
|
148
148
|
| `gbc spec clear` | 명세 비우기(작업단위 종료) |
|
|
149
149
|
| `gbc gate reset` | 작업단위 게이트 리셋 |
|
|
150
150
|
| `gbc metrics [--json]` | 계측 리포트(M1~M3) |
|
|
151
|
+
| `gbc repos add [경로]` | 크로스-repo 레지스트리에 추가(생략 시 현재 폴더) |
|
|
152
|
+
| `gbc repos list` | 등록된 repo + 각 repo의 미해결 defer 수 |
|
|
153
|
+
| `gbc repos remove [경로]` | 레지스트리에서 제거 |
|
|
151
154
|
|
|
152
155
|
우회: `GBC_NO_GATE=1` (계측됨 — 우회 자체가 게이트 가치 측정 데이터).
|
|
153
156
|
|
|
157
|
+
## 크로스-repo 가시성
|
|
158
|
+
|
|
159
|
+
여러 repo를 오가며 작업할 때, **다른 repo에 걸린 미완 작업**을 그 repo를 열지 않고도 인지하기 위한 기능이다. 세션 진입(SessionStart) 시 현재 repo의 미해결 defer 상세에 더해, **등록된 다른 repo들의 미해결 defer 요약**을 한 줄로 환기한다.
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
# 감시할 repo를 글로벌 레지스트리(~/.gbc/repos.json)에 등록 (각 repo에서 1회, 또는 경로 지정)
|
|
163
|
+
gbc repos add # 현재 폴더
|
|
164
|
+
gbc repos add /path/to/other-repo
|
|
165
|
+
gbc repos list # 등록 현황 + 각 repo 미해결 defer 수
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
이후 등록된 repo에서 세션을 열면 진입 시 이렇게 뜬다:
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
🐢 거북이 게이트 — 미해결 defer 1건 (진행중 0 · 미착수 1, 이전 작업 잔여):
|
|
172
|
+
1. [미착수] dist 재빌드 자동화 ← 현재 repo: 번호 매긴 상세
|
|
173
|
+
필요하면 사용자에게 이어서 처리할지 확인하세요. 규약 — …
|
|
174
|
+
|
|
175
|
+
🌐 타 repo 미해결: dev-note 진행중1·미착수1 · fa-support 미착수1 ← 등록된 타 repo: 카운트만
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
- **카운트만** 표시한다 — 번호 매긴 상세 리스트는 **현재 repo에만**. 번호는 `gbc defer <N>` 인덱스 ref와 현재 cwd 기준으로 묶이므로, 타 repo에 번호를 주면 "어느 repo의 N"인지 ref가 깨진다. 타 repo 항목을 다루려면 그 repo로 들어가서 번호로 조작한다.
|
|
179
|
+
- **SessionStart에서만** 환기한다(매 대화 종료 Stop 리마인드엔 미첨부 — 노이즈 방지). 현재 repo와 미해결 0건 repo는 요약에서 제외(전부 깨끗하면 `🌐` 줄 자체가 없다).
|
|
180
|
+
- gbc가 설치되지 않은(`.gbc/` 없는) repo는 등록돼 있어도 조용히 건너뛴다(fail-silent) — 레지스트리는 넉넉히 등록해도 안전하다.
|
|
181
|
+
- 끄려면 `GBC_NO_CROSS_REPO=1`(이 줄만) 또는 `GBC_NO_SESSION_HINT=1`(세션 진입 힌트 전체).
|
|
182
|
+
|
|
183
|
+
> CLI에서 repo별 alias(`alias cc-x='cd <dir> && claude'`)를 쓴다면, alias에 `gbc repos add . 2>/dev/null;`를 끼워 **여는 repo를 자동 등록**할 수 있다(등록은 멱등).
|
|
184
|
+
|
|
154
185
|
## 계측 (M1~M3)
|
|
155
186
|
|
|
156
187
|
게이트는 모든 결정을 `.gbc/events.jsonl`(append-only, 메타데이터만 — 코드 본문 미기록)에 기록한다. `gbc metrics`로 집계를 본다. 끄려면 `GBC_NO_METRICS=1`.
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// gbc — 거북이코드 CLI. zero-dep 인자 파싱(핫패스 보호).
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { dirname, join } from "node:path";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
5
|
import { homedir } from "node:os";
|
|
6
6
|
import { spawnSync } from "node:child_process";
|
|
7
7
|
import { mkdirSync, existsSync, readFileSync, writeFileSync, copyFileSync, } from "node:fs";
|
|
@@ -9,9 +9,10 @@ import { runPreToolUse, runStop, runSessionStart } from "./hook.js";
|
|
|
9
9
|
import { loadPlanSpec, computeSpecHash, addSpecCase, readSpecCases, clearSpec } from "./spec.js";
|
|
10
10
|
import { loadState, resetGate } from "./state.js";
|
|
11
11
|
import { addDefer, loadDefers, resolveDefer, startDefer, reopenDefer } from "./defer.js";
|
|
12
|
+
import { loadRepos, addRepo, removeRepo } from "./repos.js";
|
|
12
13
|
import { isStopHintMuted, setStopHintMuted } from "./config.js";
|
|
13
14
|
import { selectedTransport } from "./judge.js";
|
|
14
|
-
import { buildPreCommand, normalizeHooks, ensureSessionStartHook } from "./install.js";
|
|
15
|
+
import { buildPreCommand, normalizeHooks, ensureSessionStartHook, DEV_PLACEHOLDER } from "./install.js";
|
|
15
16
|
import { isCacheStale, readVersionCache, refreshVersionCache, } from "./version.js";
|
|
16
17
|
import { logEvent, parseEvents, computeMetrics } from "./metrics.js";
|
|
17
18
|
const CLI_PATH = fileURLToPath(import.meta.url);
|
|
@@ -30,8 +31,8 @@ const PKG_VERSION = readPkgVersion();
|
|
|
30
31
|
function hasApiKey() {
|
|
31
32
|
return existsSync(join(homedir(), ".gbc", "api-key"));
|
|
32
33
|
}
|
|
33
|
-
function stopCommand() {
|
|
34
|
-
return `node "${
|
|
34
|
+
function stopCommand(hookPath) {
|
|
35
|
+
return `node "${hookPath}" hook stop`;
|
|
35
36
|
}
|
|
36
37
|
function nowIso() {
|
|
37
38
|
try {
|
|
@@ -65,6 +66,10 @@ function nowStamp() {
|
|
|
65
66
|
async function cmdInit(args) {
|
|
66
67
|
const cwd = process.cwd();
|
|
67
68
|
const yes = args.includes("--yes") || args.includes("-y");
|
|
69
|
+
// --dev: hook 명령에 절대경로(CLI_PATH) 대신 ${CLAUDE_PROJECT_DIR} placeholder를 굽는다.
|
|
70
|
+
// geobuke-code 자기 repo 도그푸딩 전용(dist 위치가 옮겨다녀도 안 깨짐). 기본(false)은 절대경로.
|
|
71
|
+
const dev = args.includes("--dev");
|
|
72
|
+
const hookPath = dev ? DEV_PLACEHOLDER : CLI_PATH;
|
|
68
73
|
const claudeDir = join(cwd, ".claude");
|
|
69
74
|
const settingsPath = join(claudeDir, "settings.json");
|
|
70
75
|
// 설치 대상 스킬들(제품소스 skills/<name>/SKILL.md → .claude/skills/<name>/SKILL.md).
|
|
@@ -76,7 +81,7 @@ async function cmdInit(args) {
|
|
|
76
81
|
1) ${settingsPath} 에 PreToolUse(Edit|Write) + Stop + SessionStart hook 추가 (머지·멱등)
|
|
77
82
|
- 기존 settings.json 있으면 백업: settings.json.bak-<시각>
|
|
78
83
|
2) ${join(claudeDir, "skills")} 에 ${skillNames.map((n) => `/${n}`).join(", ")} 스킬 설치
|
|
79
|
-
3) hook 명령: ${buildPreCommand(
|
|
84
|
+
3) hook 명령: ${buildPreCommand(hookPath)}${dev ? " (--dev: ${CLAUDE_PROJECT_DIR} placeholder)" : ""}
|
|
80
85
|
${hasApiKey()
|
|
81
86
|
? " (~/.gbc/api-key 감지됨 → 빠른 haiku API 경로로 동작)"
|
|
82
87
|
: " (~/.gbc/api-key 없음 → claude -p 폴백. 빠른 경로 원하면 키 파일 생성)"}
|
|
@@ -105,7 +110,7 @@ ${hasApiKey()
|
|
|
105
110
|
if (!serialized.includes("hook pre-tool-use")) {
|
|
106
111
|
(hooks.PreToolUse ??= []).push({
|
|
107
112
|
matcher: "Edit|Write|MultiEdit",
|
|
108
|
-
hooks: [{ type: "command", command: buildPreCommand(
|
|
113
|
+
hooks: [{ type: "command", command: buildPreCommand(hookPath) }],
|
|
109
114
|
});
|
|
110
115
|
console.log(` + PreToolUse hook 추가`);
|
|
111
116
|
}
|
|
@@ -119,7 +124,7 @@ ${hasApiKey()
|
|
|
119
124
|
// Stop (멱등)
|
|
120
125
|
if (!serialized.includes("hook stop")) {
|
|
121
126
|
(hooks.Stop ??= []).push({
|
|
122
|
-
hooks: [{ type: "command", command: stopCommand() }],
|
|
127
|
+
hooks: [{ type: "command", command: stopCommand(hookPath) }],
|
|
123
128
|
});
|
|
124
129
|
console.log(` + Stop hook 추가`);
|
|
125
130
|
}
|
|
@@ -127,7 +132,7 @@ ${hasApiKey()
|
|
|
127
132
|
console.log(` = Stop hook 이미 존재 (skip)`);
|
|
128
133
|
}
|
|
129
134
|
// SessionStart (멱등) — 세션 진입(startup|resume) 시 미해결 defer 알림
|
|
130
|
-
if (ensureSessionStartHook(settings,
|
|
135
|
+
if (ensureSessionStartHook(settings, hookPath)) {
|
|
131
136
|
console.log(` + SessionStart hook 추가`);
|
|
132
137
|
}
|
|
133
138
|
else {
|
|
@@ -368,6 +373,45 @@ function cmdUpdate(args) {
|
|
|
368
373
|
}
|
|
369
374
|
console.log("✅ gbc update 완료.");
|
|
370
375
|
}
|
|
376
|
+
/**
|
|
377
|
+
* 크로스-repo 레지스트리 관리(0.2.9). 등록된 타 repo의 미해결 defer가 SessionStart에 환기된다.
|
|
378
|
+
* 글로벌 ~/.gbc/repos.json. 경로 생략 시 현재 폴더(cwd).
|
|
379
|
+
*/
|
|
380
|
+
function cmdRepos(args) {
|
|
381
|
+
const [sub, ...rest] = args;
|
|
382
|
+
if (sub === "add") {
|
|
383
|
+
const abs = resolve(rest[0] ?? process.cwd());
|
|
384
|
+
const repos = addRepo(abs);
|
|
385
|
+
const gated = existsSync(join(abs, ".gbc"));
|
|
386
|
+
console.log(`📁 등록: ${abs}${gated ? "" : " ⚠️ (.gbc 없음 — gbc init 전이면 표면화될 defer 없음)"}`);
|
|
387
|
+
console.log(` 현재 ${repos.length}개 등록됨.`);
|
|
388
|
+
}
|
|
389
|
+
else if (sub === "remove" || sub === "rm") {
|
|
390
|
+
const abs = resolve(rest[0] ?? process.cwd());
|
|
391
|
+
const before = loadRepos().length;
|
|
392
|
+
const repos = removeRepo(abs);
|
|
393
|
+
console.log(repos.length < before ? `🗑️ 해제: ${abs}` : `(미등록 경로: ${abs})`);
|
|
394
|
+
console.log(` 현재 ${repos.length}개 등록됨.`);
|
|
395
|
+
}
|
|
396
|
+
else if (sub === "list" || sub === undefined) {
|
|
397
|
+
const repos = loadRepos();
|
|
398
|
+
if (repos.length === 0) {
|
|
399
|
+
console.log("등록된 repo 없음. 'gbc repos add [경로]'로 추가(경로 생략 시 현재 폴더).");
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
console.log(`📁 등록된 repo ${repos.length}개:`);
|
|
403
|
+
for (const r of repos) {
|
|
404
|
+
const exists = existsSync(r);
|
|
405
|
+
const gated = exists && existsSync(join(r, ".gbc"));
|
|
406
|
+
const unresolved = gated ? loadDefers(r).filter((d) => d.status !== "resolved").length : 0;
|
|
407
|
+
const mark = !exists ? "✗부재" : !gated ? "·gbc없음" : unresolved ? `●미해결${unresolved}` : "○깨끗";
|
|
408
|
+
console.log(` [${mark}] ${r}`);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
console.log("사용법: gbc repos [add|remove|list] [경로]");
|
|
413
|
+
}
|
|
414
|
+
}
|
|
371
415
|
function usage() {
|
|
372
416
|
console.log(`🐢 gbc — 거북이코드 구현-전 게이트
|
|
373
417
|
|
|
@@ -387,6 +431,9 @@ function usage() {
|
|
|
387
431
|
gbc spec clear 명세 비우기(작업단위 종료)
|
|
388
432
|
gbc gate reset 작업단위 게이트 리셋
|
|
389
433
|
gbc metrics [--json] 계측 리포트(M1~M3, B-모드 관측 프록시)
|
|
434
|
+
gbc repos add [경로] 크로스-repo 레지스트리에 추가(생략 시 현재 폴더)
|
|
435
|
+
gbc repos list 등록된 repo + 미해결 defer 수
|
|
436
|
+
gbc repos remove [경로] 레지스트리에서 제거
|
|
390
437
|
gbc hook pre-tool-use (내부) PreToolUse hook
|
|
391
438
|
gbc hook stop (내부) Stop hook
|
|
392
439
|
gbc hook session-start (내부) SessionStart hook (미해결 defer 알림)
|
|
@@ -419,6 +466,8 @@ async function main() {
|
|
|
419
466
|
return cmdGate(rest);
|
|
420
467
|
case "metrics":
|
|
421
468
|
return cmdMetrics(rest);
|
|
469
|
+
case "repos":
|
|
470
|
+
return cmdRepos(rest);
|
|
422
471
|
case undefined:
|
|
423
472
|
case "help":
|
|
424
473
|
case "--help":
|
package/dist/hook.js
CHANGED
|
@@ -6,10 +6,11 @@ import { loadPlanSpec, computeSpecHash } from "./spec.js";
|
|
|
6
6
|
import { isGated, markGated } from "./state.js";
|
|
7
7
|
import { activeDeferItems, loadDefers } from "./defer.js";
|
|
8
8
|
import { isStopHintMuted } from "./config.js";
|
|
9
|
+
import { loadRepos } from "./repos.js";
|
|
9
10
|
import { readProjectSettings, buildUpdateNotice, wasNotified, markNotified } from "./notice.js";
|
|
10
11
|
import { isCacheStale, readVersionCache, refreshVersionCache } from "./version.js";
|
|
11
|
-
import { appendFileSync } from "node:fs";
|
|
12
|
-
import { join } from "node:path";
|
|
12
|
+
import { appendFileSync, existsSync, lstatSync } from "node:fs";
|
|
13
|
+
import { join, resolve } from "node:path";
|
|
13
14
|
import { gbcDir } from "./store.js";
|
|
14
15
|
import { logEvent } from "./metrics.js";
|
|
15
16
|
/**
|
|
@@ -285,6 +286,43 @@ export function buildSessionStartHint(all) {
|
|
|
285
286
|
`${formatDeferList(all)}\n` +
|
|
286
287
|
`필요하면 사용자에게 이어서 처리할지 확인하세요. ${DEFER_PROTOCOL}`);
|
|
287
288
|
}
|
|
289
|
+
/**
|
|
290
|
+
* 등록된 타 repo들의 미해결 defer 요약 한 줄(0.2.9 크로스-repo 가시성). 없으면 "".
|
|
291
|
+
* - 현재 cwd 제외(이미 buildSessionStartHint가 상세 표시), 미해결 0건 repo 제외.
|
|
292
|
+
* - 경로 부재/비-디렉터리/읽기실패는 repo별 조용히 skip(fail-silent). hook이 cwd 밖을 읽는
|
|
293
|
+
* 유일한 지점이라 방어적으로 가드한다(사용자가 명시 등록한 경로만 대상이라 위협은 낮음).
|
|
294
|
+
* - ★ 카운트만. 번호 매긴 상세 리스트는 현재 repo만(formatDeferList) — 번호는 'gbc defer <N>'
|
|
295
|
+
* 인덱스 ref와 cwd 기준으로 묶여, 타 repo에 번호를 주면 어느 repo의 N인지 ref가 깨진다.
|
|
296
|
+
*/
|
|
297
|
+
export function buildCrossRepoHint(repos, cwd) {
|
|
298
|
+
const here = resolve(cwd);
|
|
299
|
+
const segs = [];
|
|
300
|
+
for (const repo of repos) {
|
|
301
|
+
const abs = resolve(repo);
|
|
302
|
+
if (abs === here)
|
|
303
|
+
continue;
|
|
304
|
+
let unresolved;
|
|
305
|
+
try {
|
|
306
|
+
// lstatSync(statSync 아님)로 심볼릭 링크를 거부한다 — 등록된 symlink가 가리키는 cwd 밖
|
|
307
|
+
// 임의 경로(/etc 등)의 .gbc/defers.json을 읽으려 시도하는 간접 확장을 막는다(보안검토 S3).
|
|
308
|
+
// 실디렉터리만 isDirectory()=true; symlink면 false라 skip된다.
|
|
309
|
+
if (!existsSync(abs) || !lstatSync(abs).isDirectory())
|
|
310
|
+
continue;
|
|
311
|
+
unresolved = loadDefers(abs).filter((d) => d.status !== "resolved");
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
if (unresolved.length === 0)
|
|
317
|
+
continue;
|
|
318
|
+
const ip = unresolved.filter((d) => d.status === "in_progress").length;
|
|
319
|
+
const op = unresolved.length - ip;
|
|
320
|
+
const counts = [ip ? `진행중${ip}` : "", op ? `미착수${op}` : ""].filter(Boolean).join("·");
|
|
321
|
+
const name = abs.split(/[\\/]/).filter(Boolean).pop() ?? abs;
|
|
322
|
+
segs.push(`${name} ${counts}`);
|
|
323
|
+
}
|
|
324
|
+
return segs.length ? `🌐 타 repo 미해결: ${segs.join(" · ")}` : "";
|
|
325
|
+
}
|
|
288
326
|
/**
|
|
289
327
|
* Stop hook 리마인드 문자열. 없으면 "". 입력은 전체 defer 리스트(번호=전체-인덱스, 인덱스 ref 정합).
|
|
290
328
|
* SessionStart와 동일하게 in_progress를 차등 표면화한다 — "착수했지만 미종결" 항목이 레이더에서
|
|
@@ -326,6 +364,18 @@ export async function runSessionStart(ctx) {
|
|
|
326
364
|
}
|
|
327
365
|
}
|
|
328
366
|
}
|
|
367
|
+
// 크로스-repo defer 가시성(0.2.9) — 등록된 타 repo 미해결 요약. SessionStart만(Stop엔 미첨부).
|
|
368
|
+
// opt-out 둘: GBC_NO_SESSION_HINT(세션힌트 전체) 또는 GBC_NO_CROSS_REPO(이 줄만).
|
|
369
|
+
if (process.env.GBC_NO_SESSION_HINT !== "1" && process.env.GBC_NO_CROSS_REPO !== "1") {
|
|
370
|
+
try {
|
|
371
|
+
const xr = buildCrossRepoHint(loadRepos(), cwd);
|
|
372
|
+
if (xr)
|
|
373
|
+
parts.push(xr);
|
|
374
|
+
}
|
|
375
|
+
catch {
|
|
376
|
+
/* 레지스트리 읽기 실패는 무시(fail-silent) */
|
|
377
|
+
}
|
|
378
|
+
}
|
|
329
379
|
// ①신버전 안내 — 캐시가 stale이면 '표시 전에' 먼저 갱신(1.5s 상한, fail-silent)해 이번 세션에
|
|
330
380
|
// 즉시 반영한다(표시-후-갱신의 '다음 세션 지연' 제거). SessionStart는 게이트를 막지 않으므로
|
|
331
381
|
// 짧은 네트워크가 안전(advisor 승인). 갱신은 24h TTL당 1회만 발생.
|
package/dist/install.js
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
// gbc init 설치 로직 (순수함수 — cli.ts main() 부작용 없이 단위테스트 가능).
|
|
2
2
|
// 키 주입은 셸이 아니라 gbc 코드(judge.ts resolveApiKey)가 처리한다 → hook 명령은
|
|
3
3
|
// 셸 무관 순수 형태라 native Windows(cmd.exe)/bash/zsh/Mac에서 동일하게 동작한다.
|
|
4
|
+
/**
|
|
5
|
+
* dev(도그푸딩) 설치용 hook 경로 placeholder. `gbc init --dev`가 절대경로(CLI_PATH) 대신 이걸
|
|
6
|
+
* 구워, geobuke-code 자기 repo처럼 dist 위치가 옮겨다니는 클론에서도 hook이 깨지지 않게 한다
|
|
7
|
+
* (CC 런타임이 ${CLAUDE_PROJECT_DIR}를 프로젝트 루트로 치환). npm 전역·외부 4곳 도그푸딩은 절대경로
|
|
8
|
+
* 유지(기본동작 불변) — 이 placeholder는 명시 opt-in일 때만 쓰인다.
|
|
9
|
+
*/
|
|
10
|
+
export const DEV_PLACEHOLDER = "${CLAUDE_PROJECT_DIR}/dist/cli.js";
|
|
11
|
+
/**
|
|
12
|
+
* PreToolUse hook의 *정식* 명령 집합(절대경로 + dev placeholder). stale/normalize 판정의 공통 기준.
|
|
13
|
+
* read-time(hasStalePreToolUse)은 런타임 cliPath=절대경로뿐이라 이 repo가 dev인지 모른다 → 두 정식
|
|
14
|
+
* 형태 중 하나면 stale 아님으로 봐야 placeholder를 구식으로 오판하지 않는다. substring이 아니라
|
|
15
|
+
* 완전일치 집합이라, 서브명령명이 바뀌면 placeholder 형태도 함께 갱신돼 진짜 구식 감지는 유지된다.
|
|
16
|
+
*/
|
|
17
|
+
function canonicalPreCommands(cliPath) {
|
|
18
|
+
return [buildPreCommand(cliPath), buildPreCommand(DEV_PLACEHOLDER)];
|
|
19
|
+
}
|
|
4
20
|
/**
|
|
5
21
|
* PreToolUse hook 명령 생성 — 셸 무관 순수 명령.
|
|
6
22
|
* `node "<cliPath>" hook pre-tool-use` 형태만 생성한다. 키 주입(셸 prefix)·셸 확장 없음.
|
|
@@ -19,12 +35,14 @@ export function buildPreCommand(cliPath) {
|
|
|
19
35
|
* settings를 제자리 수정하고 변경 건수를 반환한다(멱등: 이미 표준이면 0건).
|
|
20
36
|
*/
|
|
21
37
|
export function normalizeHooks(settings, cliPath) {
|
|
22
|
-
const
|
|
38
|
+
const canon = canonicalPreCommands(cliPath);
|
|
23
39
|
let changed = 0;
|
|
24
40
|
for (const entry of settings.hooks?.PreToolUse ?? []) {
|
|
25
41
|
for (const h of entry.hooks ?? []) {
|
|
26
|
-
|
|
27
|
-
|
|
42
|
+
// 이미 정식(절대 or placeholder)이면 건드리지 않는다 — dev placeholder를 절대경로로 덮어
|
|
43
|
+
// 도그푸딩 설치를 깨뜨리지 않게. 진짜 구식(옛 bash 키주입 등)만 절대경로로 교체.
|
|
44
|
+
if (h.command.includes("hook pre-tool-use") && !canon.includes(h.command)) {
|
|
45
|
+
h.command = buildPreCommand(cliPath);
|
|
28
46
|
changed++;
|
|
29
47
|
}
|
|
30
48
|
}
|
|
@@ -40,10 +58,12 @@ export function buildSessionStartCommand(cliPath) {
|
|
|
40
58
|
* 감지부만 떼어낸 비파괴 술어 — ②init-staleness 안내가 settings를 수정하지 않고 판단하게 한다.
|
|
41
59
|
*/
|
|
42
60
|
export function hasStalePreToolUse(settings, cliPath) {
|
|
43
|
-
const
|
|
61
|
+
const canon = canonicalPreCommands(cliPath);
|
|
44
62
|
for (const entry of settings.hooks?.PreToolUse ?? []) {
|
|
45
63
|
for (const h of entry.hooks ?? []) {
|
|
46
|
-
|
|
64
|
+
// dev placeholder도 정식이므로 stale 아님 — 절대경로 런타임에서 placeholder를 구식으로 오판해
|
|
65
|
+
// 'gbc init' 재실행을 헛권하던 false-positive 차단(B-잔여 #3의 실제 증상).
|
|
66
|
+
if (h.command.includes("hook pre-tool-use") && !canon.includes(h.command))
|
|
47
67
|
return true;
|
|
48
68
|
}
|
|
49
69
|
}
|
package/dist/repos.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// 크로스-repo 레지스트리 — 등록된 repo들의 미해결 defer를 SessionStart에 환기(0.2.9).
|
|
2
|
+
// 글로벌(~/.gbc/repos.json)에 저장한다 — 크로스프로젝트라 project .gbc/가 아니라 홈.
|
|
3
|
+
// (~/.gbc/api-key·~/.gbc/version-check.json과 동위. gbcDir(homedir())가 ~/.gbc를 보장.)
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { join, resolve } from "node:path";
|
|
6
|
+
import { gbcDir, readJson, writeJson } from "./store.js";
|
|
7
|
+
function reposPath() {
|
|
8
|
+
return join(gbcDir(homedir()), "repos.json");
|
|
9
|
+
}
|
|
10
|
+
/** 등록된 repo 절대경로 목록(없으면 []). */
|
|
11
|
+
export function loadRepos() {
|
|
12
|
+
return readJson(reposPath(), []);
|
|
13
|
+
}
|
|
14
|
+
/** repo 등록(절대경로 정규화·멱등 dedup). 반환=등록 후 전체 목록. */
|
|
15
|
+
export function addRepo(path) {
|
|
16
|
+
const abs = resolve(path);
|
|
17
|
+
const repos = loadRepos();
|
|
18
|
+
if (!repos.includes(abs)) {
|
|
19
|
+
repos.push(abs);
|
|
20
|
+
writeJson(reposPath(), repos);
|
|
21
|
+
}
|
|
22
|
+
return repos;
|
|
23
|
+
}
|
|
24
|
+
/** repo 등록 해제(절대경로 정규화). 반환=해제 후 전체 목록. */
|
|
25
|
+
export function removeRepo(path) {
|
|
26
|
+
const abs = resolve(path);
|
|
27
|
+
const repos = loadRepos();
|
|
28
|
+
const next = repos.filter((r) => r !== abs);
|
|
29
|
+
if (next.length !== repos.length)
|
|
30
|
+
writeJson(reposPath(), next);
|
|
31
|
+
return next;
|
|
32
|
+
}
|