geobuke-code 0.2.4 → 0.2.5
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 +6 -4
- package/dist/cli.js +35 -15
- package/dist/defer.js +70 -21
- package/dist/hook.js +49 -15
- package/package.json +1 -1
- package/skills/gate/SKILL.md +24 -4
package/README.md
CHANGED
|
@@ -96,7 +96,7 @@ phase-protocol/계획 → /plan(SubTask) → 【게이트: 구현 직전 케이
|
|
|
96
96
|
|
|
97
97
|
| 시점 | hook (matcher) | 동작 |
|
|
98
98
|
|---|---|---|
|
|
99
|
-
| **세션 시작·재개** | SessionStart (`startup\|resume`) | `.gbc/defers.json`의 미해결 항목을 표면화(이전 작업 잔여 환기). 잔여 없으면 무출력. `compact`엔 발화 안 함(노이즈 방지) |
|
|
99
|
+
| **세션 시작·재개** | SessionStart (`startup\|resume`) | `.gbc/defers.json`의 미해결 항목을 "진행중 N · 미착수 M"로 구분 표면화(이전 작업 잔여 환기). 잔여 없으면 무출력. `compact`엔 발화 안 함(노이즈 방지) |
|
|
100
100
|
| **코드 변경 직전** | PreToolUse (`Edit\|Write\|MultiEdit`) | 명세 ↔ 변경 ↔ defer 대조 → 통과(침묵)/차단(시나리오 도출 지시)/fail-open |
|
|
101
101
|
| **작업단위당 1회** | (PreToolUse 캐시) | 같은 명세 해시 내에선 첫 편집만 판정, 이후 통과 → 매 편집 지연 회피 |
|
|
102
102
|
| **응답 종료** | Stop | 계측 flush(`events.jsonl`) |
|
|
@@ -136,9 +136,11 @@ phase-protocol/계획 → /plan(SubTask) → 【게이트: 구현 직전 케이
|
|
|
136
136
|
|---|---|
|
|
137
137
|
| `gbc init` | hook + /gate skill 설치 |
|
|
138
138
|
| `gbc status` | 게이트 상태 + 로드된 명세 확인 |
|
|
139
|
-
| `gbc defer add "<케이스>"` | 케이스를 명시적으로 미루기 |
|
|
140
|
-
| `gbc defer list` | 미룬 항목 목록 |
|
|
141
|
-
| `gbc defer
|
|
139
|
+
| `gbc defer add "<케이스>"` | 케이스를 명시적으로 미루기 (→ open) |
|
|
140
|
+
| `gbc defer list` | 미룬 항목 목록 (상태: 미해결/진행중/해결) |
|
|
141
|
+
| `gbc defer start <번호\|텍스트\|all>` | 착수 표시 (open → 진행중) |
|
|
142
|
+
| `gbc defer resolve <번호\|텍스트\|all>` | 종결 표시 (→ 해결) |
|
|
143
|
+
| `gbc defer reopen <번호\|텍스트\|all>` | 백로그로 되돌리기 (→ open) |
|
|
142
144
|
| `gbc spec add "<케이스>"` | 승인된 시나리오를 `.gbc/spec.md`에 등록 |
|
|
143
145
|
| `gbc spec show` | 등록된 케이스 목록 |
|
|
144
146
|
| `gbc spec clear` | 명세 비우기(작업단위 종료) |
|
package/dist/cli.js
CHANGED
|
@@ -7,7 +7,7 @@ import { mkdirSync, existsSync, readFileSync, writeFileSync, copyFileSync, } fro
|
|
|
7
7
|
import { runPreToolUse, runStop, runSessionStart } from "./hook.js";
|
|
8
8
|
import { loadPlanSpec, computeSpecHash, addSpecCase, readSpecCases, clearSpec } from "./spec.js";
|
|
9
9
|
import { loadState, resetGate } from "./state.js";
|
|
10
|
-
import { addDefer, loadDefers, resolveDefer } from "./defer.js";
|
|
10
|
+
import { addDefer, loadDefers, resolveDefer, startDefer, reopenDefer } from "./defer.js";
|
|
11
11
|
import { selectedTransport } from "./judge.js";
|
|
12
12
|
import { buildPreCommand, normalizeHooks, ensureSessionStartHook } from "./install.js";
|
|
13
13
|
import { isCacheStale, readVersionCache, refreshVersionCache, } from "./version.js";
|
|
@@ -170,16 +170,19 @@ async function cmdStatus() {
|
|
|
170
170
|
const hash = computeSpecHash(text);
|
|
171
171
|
const state = loadState(cwd);
|
|
172
172
|
const defers = loadDefers(cwd);
|
|
173
|
-
const unresolved = defers.filter((d) =>
|
|
173
|
+
const unresolved = defers.filter((d) => d.status !== "resolved");
|
|
174
|
+
const inProgress = defers.filter((d) => d.status === "in_progress").length;
|
|
174
175
|
console.log(`🐢 거북이 게이트 상태 — ${cwd}
|
|
175
176
|
버전: ${PKG_VERSION || "(불명)"}
|
|
176
177
|
트랜스포트: ${selectedTransport()}
|
|
177
178
|
명세 소스: ${source} ${text ? `(${text.length}자)` : "(비어있음 → 모든 코드변경 차단)"}
|
|
178
179
|
명세 해시: ${hash}
|
|
179
180
|
작업단위 게이트: ${state && state.specHash === hash && state.gated ? "통과됨(이 단위 재게이트 안 함)" : "미통과(다음 편집에서 발동)"}
|
|
180
|
-
defer: 전체 ${defers.length} / 미해결 ${unresolved.length}`);
|
|
181
|
+
defer: 전체 ${defers.length} / 미해결 ${unresolved.length} (진행중 ${inProgress} · 미착수 ${unresolved.length - inProgress})`);
|
|
181
182
|
if (unresolved.length > 0) {
|
|
182
|
-
console.log(unresolved
|
|
183
|
+
console.log(unresolved
|
|
184
|
+
.map((d, i) => ` ${i + 1}. ${d.status === "in_progress" ? "▶[진행중] " : ""}${d.item}`)
|
|
185
|
+
.join("\n"));
|
|
183
186
|
}
|
|
184
187
|
// 신버전 업데이트 안내(buildVersionNotice)는 여기서 출력하지 않는다 — 안내 자리는
|
|
185
188
|
// SessionStart·PreToolUse 자동 채널 전용이고, status는 명시 진단 명령이라 나그 부적절.
|
|
@@ -205,17 +208,32 @@ function cmdDefer(args) {
|
|
|
205
208
|
console.log("(미룬 항목 없음)");
|
|
206
209
|
return;
|
|
207
210
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
+
const label = {
|
|
212
|
+
open: "미해결",
|
|
213
|
+
in_progress: "진행중",
|
|
214
|
+
resolved: "해결",
|
|
215
|
+
};
|
|
216
|
+
defers.forEach((d, i) => console.log(`${i + 1}. [${label[d.status]}] ${d.item}`));
|
|
217
|
+
}
|
|
218
|
+
else if (sub === "start" || sub === "resolve" || sub === "reopen") {
|
|
211
219
|
const ref = args.slice(1).join(" ").trim();
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
220
|
+
if (!ref) {
|
|
221
|
+
console.error(`사용: gbc defer ${sub} <번호|텍스트|all>`);
|
|
222
|
+
process.exit(1);
|
|
223
|
+
}
|
|
224
|
+
const fn = sub === "start" ? startDefer : sub === "resolve" ? resolveDefer : reopenDefer;
|
|
225
|
+
const verb = sub === "start" ? "착수" : sub === "resolve" ? "해결" : "되돌림(open)";
|
|
226
|
+
const changed = fn(cwd, ref);
|
|
227
|
+
if (changed.length > 0) {
|
|
228
|
+
logCli(cwd, `defer-${sub}`, curHash(cwd));
|
|
229
|
+
console.log(`🐢 ${verb} ${changed.length}건: ${changed.map((d) => d.item).join(", ")}`);
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
console.log(`매칭되는 항목 없음(0건): ${ref}`);
|
|
233
|
+
}
|
|
216
234
|
}
|
|
217
235
|
else {
|
|
218
|
-
console.error("사용: gbc defer <add|list|resolve> ...");
|
|
236
|
+
console.error("사용: gbc defer <add|list|start|resolve|reopen> ...");
|
|
219
237
|
process.exit(1);
|
|
220
238
|
}
|
|
221
239
|
}
|
|
@@ -296,9 +314,11 @@ function usage() {
|
|
|
296
314
|
사용:
|
|
297
315
|
gbc init [--yes] 프로젝트에 hook + /gate 스킬 설치
|
|
298
316
|
gbc status 게이트 상태 + 로드된 명세 확인
|
|
299
|
-
gbc defer add "<케이스>" 케이스를 명시적으로 미루기
|
|
300
|
-
gbc defer list 미룬 항목 목록
|
|
301
|
-
gbc defer
|
|
317
|
+
gbc defer add "<케이스>" 케이스를 명시적으로 미루기 (→ open)
|
|
318
|
+
gbc defer list 미룬 항목 목록 (상태: 미해결/진행중/해결)
|
|
319
|
+
gbc defer start <번호|텍스트|all> 착수 표시 (open → 진행중)
|
|
320
|
+
gbc defer resolve <번호|텍스트|all> 종결 표시 (→ 해결; 항상 사용자 점검 후)
|
|
321
|
+
gbc defer reopen <번호|텍스트|all> 백로그로 되돌리기 (→ open)
|
|
302
322
|
gbc spec add "<케이스>" 승인된 시나리오를 .gbc/spec.md에 등록
|
|
303
323
|
gbc spec show 등록된 케이스 목록
|
|
304
324
|
gbc spec clear 명세 비우기(작업단위 종료)
|
package/dist/defer.js
CHANGED
|
@@ -12,44 +12,93 @@ function nowIso() {
|
|
|
12
12
|
return "";
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* 원시 엔트리를 status 단일 소스로 정규화한다(마이그레이션).
|
|
17
|
+
* 옛 0.2.4 이하 포맷 {resolved:boolean}을 읽을 때 status로 승격:
|
|
18
|
+
* resolved:true→"resolved", false/부재→"open". 이미 status가 있으면 그대로.
|
|
19
|
+
*/
|
|
20
|
+
function promote(raw) {
|
|
21
|
+
const status = raw.status ?? (raw.resolved === true ? "resolved" : "open");
|
|
22
|
+
return { item: raw.item, at: raw.at, status };
|
|
23
|
+
}
|
|
24
|
+
/** 디스크에서 defer 엔트리를 읽어 status 포맷으로 정규화 반환(읽기 시 자동 승격) */
|
|
15
25
|
export function loadDefers(cwd) {
|
|
16
|
-
return readJson(deferPath(cwd), []);
|
|
26
|
+
return readJson(deferPath(cwd), []).map(promote);
|
|
17
27
|
}
|
|
28
|
+
/** 저장은 항상 status 포맷으로 통일 — promote가 옛 resolved 필드를 떨궈 단일 소스 보장(drift 방지) */
|
|
18
29
|
function save(cwd, defers) {
|
|
19
30
|
writeJson(deferPath(cwd), defers);
|
|
20
31
|
}
|
|
21
32
|
/** 명시적으로 항목을 미룬다 (침묵 누락 차단의 유일한 정당 경로) */
|
|
22
33
|
export function addDefer(cwd, item) {
|
|
23
34
|
const defers = loadDefers(cwd);
|
|
24
|
-
const entry = { item: normalizeCase(item), at: nowIso(),
|
|
35
|
+
const entry = { item: normalizeCase(item), at: nowIso(), status: "open" };
|
|
25
36
|
defers.push(entry);
|
|
26
37
|
save(cwd, defers);
|
|
27
38
|
return entry;
|
|
28
39
|
}
|
|
29
|
-
/**
|
|
40
|
+
/**
|
|
41
|
+
* 미해결(=resolved 아님) defer 항목 텍스트만 (게이트 판정 입력용).
|
|
42
|
+
* gate-neutral: open + in_progress 모두 '아직 안 끝난 의도적 미룸'으로 judge에 전달 → 차단 로직 무변경.
|
|
43
|
+
*/
|
|
30
44
|
export function activeDeferItems(cwd) {
|
|
31
45
|
return loadDefers(cwd)
|
|
32
|
-
.filter((d) =>
|
|
46
|
+
.filter((d) => d.status !== "resolved")
|
|
33
47
|
.map((d) => d.item);
|
|
34
48
|
}
|
|
35
|
-
/** 미해결 defer 엔트리 (Stop hook 리마인드용) */
|
|
49
|
+
/** 미해결(open+in_progress) defer 엔트리 (Stop hook·SessionStart 리마인드용) */
|
|
36
50
|
export function unresolvedDefers(cwd) {
|
|
37
|
-
return loadDefers(cwd).filter((d) =>
|
|
51
|
+
return loadDefers(cwd).filter((d) => d.status !== "resolved");
|
|
38
52
|
}
|
|
39
|
-
/**
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
53
|
+
/**
|
|
54
|
+
* ref 문자열로 전환 대상 엔트리를 고른다. 세 형태 지원:
|
|
55
|
+
* - "all": eligibleFrom 상태에 해당하는 전부
|
|
56
|
+
* - 공백구분 토큰이 전부 정수: 복수 인덱스(1-base). 인덱스는 명시 지정이라 적격 무시(사용자가 번호를 안다)
|
|
57
|
+
* - 그 외: 통째로 부분 텍스트 1건 매칭(적격 항목 중) — 공백 포함 문구 하위호환
|
|
58
|
+
*/
|
|
59
|
+
function selectTargets(defers, ref, eligibleFrom) {
|
|
60
|
+
const trimmed = ref.trim();
|
|
61
|
+
// 빈 ref 가드: includes("")는 항상 첫 항목을 매칭하므로 빈 문자열이 엉뚱한 항목을 고른다.
|
|
62
|
+
// CLI(cli.ts)는 이미 빈 ref를 사전 차단하지만, selectTargets가 라이브러리로 직접 호출될 때를 위한 방어.
|
|
63
|
+
if (trimmed === "")
|
|
64
|
+
return [];
|
|
65
|
+
const eligible = (d) => eligibleFrom.includes(d.status);
|
|
66
|
+
if (trimmed === "all")
|
|
67
|
+
return defers.filter(eligible);
|
|
68
|
+
const tokens = trimmed.split(/\s+/).filter(Boolean);
|
|
69
|
+
const allInts = tokens.length > 0 && tokens.every((t) => /^\d+$/.test(t));
|
|
70
|
+
if (allInts) {
|
|
71
|
+
const out = [];
|
|
72
|
+
for (const t of tokens) {
|
|
73
|
+
const idx = Number.parseInt(t, 10);
|
|
74
|
+
if (idx >= 1 && idx <= defers.length && !out.includes(defers[idx - 1])) {
|
|
75
|
+
out.push(defers[idx - 1]);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
46
79
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
80
|
+
const t = defers.find((d) => eligible(d) && d.item.includes(trimmed));
|
|
81
|
+
return t ? [t] : [];
|
|
82
|
+
}
|
|
83
|
+
/** 선택된 대상을 toStatus로 전환하고 저장. 전환된 엔트리 배열 반환(매칭 0건이면 빈 배열). */
|
|
84
|
+
function transition(cwd, ref, toStatus, eligibleFrom) {
|
|
85
|
+
const defers = loadDefers(cwd);
|
|
86
|
+
const targets = selectTargets(defers, ref, eligibleFrom);
|
|
87
|
+
for (const t of targets)
|
|
88
|
+
t.status = toStatus;
|
|
89
|
+
if (targets.length > 0)
|
|
90
|
+
save(cwd, defers);
|
|
91
|
+
return targets;
|
|
92
|
+
}
|
|
93
|
+
/** open → in_progress (착수). 텍스트/all 적격 = open. 인덱스는 명시 지정. */
|
|
94
|
+
export function startDefer(cwd, ref) {
|
|
95
|
+
return transition(cwd, ref, "in_progress", ["open"]);
|
|
96
|
+
}
|
|
97
|
+
/** → resolved (종결, 항상 사람 선언). 텍스트/all 적격 = open + in_progress. */
|
|
98
|
+
export function resolveDefer(cwd, ref) {
|
|
99
|
+
return transition(cwd, ref, "resolved", ["open", "in_progress"]);
|
|
100
|
+
}
|
|
101
|
+
/** → open (백로그로 되돌리기: 보류/이월 또는 잘못된 resolve 취소). 텍스트/all 적격 = in_progress + resolved. */
|
|
102
|
+
export function reopenDefer(cwd, ref) {
|
|
103
|
+
return transition(cwd, ref, "open", ["in_progress", "resolved"]);
|
|
55
104
|
}
|
package/dist/hook.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { isGatedTool, normalizeEdit } from "./normalize.js";
|
|
5
5
|
import { loadPlanSpec, computeSpecHash } from "./spec.js";
|
|
6
6
|
import { isGated, markGated } from "./state.js";
|
|
7
|
-
import { activeDeferItems,
|
|
7
|
+
import { activeDeferItems, loadDefers } from "./defer.js";
|
|
8
8
|
import { readProjectSettings, buildUpdateNotice, wasNotified, markNotified } from "./notice.js";
|
|
9
9
|
import { isCacheStale, readVersionCache, refreshVersionCache } from "./version.js";
|
|
10
10
|
import { appendFileSync } from "node:fs";
|
|
@@ -236,28 +236,62 @@ export async function runStop() {
|
|
|
236
236
|
// defers.json 없으면(파일 부재) 조용히 통과
|
|
237
237
|
if (loadDefers(cwd).length === 0)
|
|
238
238
|
process.exit(0);
|
|
239
|
-
const
|
|
240
|
-
if (
|
|
239
|
+
const all = loadDefers(cwd);
|
|
240
|
+
if (all.filter((d) => d.status !== "resolved").length === 0)
|
|
241
241
|
process.exit(0);
|
|
242
|
-
|
|
243
|
-
emit({
|
|
244
|
-
decision: "block",
|
|
245
|
-
reason: `🐢 미해결 defer ${un.length}건이 남아 있습니다:\n${items}\n` +
|
|
246
|
-
`해결했으면 'gbc defer resolve <번호>', 다음 세션으로 이월할 거면 의식적으로 확인하세요. ` +
|
|
247
|
-
`(이 리마인드는 1회만 표시됩니다.)`,
|
|
248
|
-
});
|
|
242
|
+
emit({ decision: "block", reason: buildStopReminder(all) });
|
|
249
243
|
process.exit(0);
|
|
250
244
|
}
|
|
245
|
+
/**
|
|
246
|
+
* defer 전환 행동 규약 — SessionStart/Stop 알림 문자열에 임베드한다.
|
|
247
|
+
* 규약 발화 자리가 hint 문자열인 이유: SKILL.md는 skill 실행 시점에만 읽혀 자유 편집·대화 중엔
|
|
248
|
+
* dead doc이 된다. 매 세션 컨텍스트에 신뢰성 있게 규약을 주입하는 유일한 결정론 채널이 이 문자열이다.
|
|
249
|
+
* (hook엔 추론을 넣지 않는다 — 텍스트만. 자연어/대상 감지·전환 실행은 에이전트 측 책임.)
|
|
250
|
+
*/
|
|
251
|
+
const DEFER_PROTOCOL = "규약 — 항목 착수 시 'gbc defer start <ref>'로 진행중 표시, 사용자가 완료를 명시하면 'gbc defer resolve <ref>'로 종결(신호가 모호하면 resolve하지 말고 확인). 되돌리기는 'gbc defer reopen <ref>'. ref=번호|텍스트|all. 모든 자동 전환은 사용자에게 표면화.";
|
|
252
|
+
/**
|
|
253
|
+
* 전체 defer 리스트에서 미해결(open+in_progress)만 골라 상태 마커와 함께 한 줄씩 포맷한다.
|
|
254
|
+
* ★ 번호는 전체-리스트 위치(인덱스+1)로 매긴다 — `gbc defer list`·`gbc defer <N>` 인덱스 ref와 동일.
|
|
255
|
+
* 부분집합 번호를 쓰면 resolved가 앞에 있을 때 표시 번호 ≠ 실제 인덱스가 되어 엉뚱한 항목을 친다.
|
|
256
|
+
*/
|
|
257
|
+
function formatDeferList(all) {
|
|
258
|
+
return all
|
|
259
|
+
.map((d, i) => ({ d, n: i + 1 }))
|
|
260
|
+
.filter((x) => x.d.status !== "resolved")
|
|
261
|
+
.map((x) => `${x.n}. ${x.d.status === "in_progress" ? "▶[진행중]" : "[미착수]"} ${x.d.item}`)
|
|
262
|
+
.join("\n");
|
|
263
|
+
}
|
|
264
|
+
/** 미해결 건수를 진행중/미착수로 분해한 머리말 조각 */
|
|
265
|
+
function statusBreakdown(unresolved) {
|
|
266
|
+
const inProgress = unresolved.filter((d) => d.status === "in_progress").length;
|
|
267
|
+
return `진행중 ${inProgress} · 미착수 ${unresolved.length - inProgress}`;
|
|
268
|
+
}
|
|
251
269
|
/**
|
|
252
270
|
* 세션 진입(startup|resume) 시 미해결 defer 잔여를 표면화하는 알림 문자열. 없으면 "".
|
|
271
|
+
* 입력은 전체 defer 리스트(loadDefers) — 표시 번호를 전체-인덱스로 맞추기 위함(인덱스 ref 정합).
|
|
253
272
|
* gbc 자기 소유 데이터(.gbc/defers.json)만 사용 — scratch/메모리 미접근(다른 하네스와 혼재·환각 방지).
|
|
273
|
+
* in_progress를 open과 구분 표면화("진행중 N · 미착수 M") — 착수했지만 미종결 항목이 잊히지 않게.
|
|
274
|
+
*/
|
|
275
|
+
export function buildSessionStartHint(all) {
|
|
276
|
+
const unresolved = all.filter((d) => d.status !== "resolved");
|
|
277
|
+
if (unresolved.length === 0)
|
|
278
|
+
return "";
|
|
279
|
+
return (`🐢 거북이 게이트 — 미해결 defer ${unresolved.length}건 (${statusBreakdown(unresolved)}, 이전 작업 잔여):\n` +
|
|
280
|
+
`${formatDeferList(all)}\n` +
|
|
281
|
+
`필요하면 사용자에게 이어서 처리할지 확인하세요. ${DEFER_PROTOCOL}`);
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Stop hook 리마인드 문자열. 없으면 "". 입력은 전체 defer 리스트(번호=전체-인덱스, 인덱스 ref 정합).
|
|
285
|
+
* SessionStart와 동일하게 in_progress를 차등 표면화한다 — "착수했지만 미종결" 항목이 레이더에서
|
|
286
|
+
* 사라지지 않게(resolve가 리마인드에서 항목을 떨구는 harm 완화).
|
|
254
287
|
*/
|
|
255
|
-
export function
|
|
288
|
+
export function buildStopReminder(all) {
|
|
289
|
+
const unresolved = all.filter((d) => d.status !== "resolved");
|
|
256
290
|
if (unresolved.length === 0)
|
|
257
291
|
return "";
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
292
|
+
return (`🐢 미해결 defer ${unresolved.length}건이 남아 있습니다 (${statusBreakdown(unresolved)}):\n` +
|
|
293
|
+
`${formatDeferList(all)}\n` +
|
|
294
|
+
`${DEFER_PROTOCOL} 다음 세션으로 이월할 거면 의식적으로 확인하세요. (이 리마인드는 1회만 표시됩니다.)`);
|
|
261
295
|
}
|
|
262
296
|
/**
|
|
263
297
|
* SessionStart: 세션 진입 시 미해결 defer를 stdout(plain text)으로 표면화 → Claude 컨텍스트 주입.
|
|
@@ -276,7 +310,7 @@ export async function runSessionStart(ctx) {
|
|
|
276
310
|
const parts = [];
|
|
277
311
|
// 미해결 defer 알림(GBC_NO_SESSION_HINT로 opt-out — 기존 동작 보존).
|
|
278
312
|
if (process.env.GBC_NO_SESSION_HINT !== "1") {
|
|
279
|
-
const hint = buildSessionStartHint(
|
|
313
|
+
const hint = buildSessionStartHint(loadDefers(cwd));
|
|
280
314
|
if (hint)
|
|
281
315
|
parts.push(hint);
|
|
282
316
|
}
|
package/package.json
CHANGED
package/skills/gate/SKILL.md
CHANGED
|
@@ -12,6 +12,24 @@ description: 거북이코드 구현-전 게이트를 관리한다. 로드된 계
|
|
|
12
12
|
- **미루기는 명시 등록만 허용한다.** "추후작업"이라고 머릿속/주석으로만 미루면 게이트가 침묵 누락으로 차단한다. 정당한 미루기는 반드시 `gbc defer add`로 등록해야 통과된다. (= 통증 "추후작업 미루다 누락" 직격)
|
|
13
13
|
- **게이트는 완전구현을 요구하지 않는다.** 케이스가 다뤄지기 시작했거나 명시 defer되면 통과. 침묵 누락과 시나리오 미지정만 막는다.
|
|
14
14
|
|
|
15
|
+
## defer 수명주기 — 자연어로 전환한다 (사용자가 명령을 직접 칠 필요 없음)
|
|
16
|
+
|
|
17
|
+
defer 항목은 **open(미착수) → in_progress(진행중) → resolved(해결)** 3상태를 갖는다. 대부분의 경우 사용자는 `gbc defer …`를 직접 입력하지 않는다 — **에이전트가 대화(자연어)와 편집 대상을 감지해 백그라운드에서 전환을 실행**하고, 사용자에게 표면화한다. (명령은 수동 보정용으로 항상 사용 가능.)
|
|
18
|
+
|
|
19
|
+
| 전환 | 트리거(감지) | 에이전트 행동 |
|
|
20
|
+
|---|---|---|
|
|
21
|
+
| **start** (open→진행중) | 그 defer 항목을 **실제로 착수**할 때(NL "이거 할게" 또는 해당 코드 편집 시작) | `gbc defer start <ref>` 자동 실행 + 표면화. 보수적으로 — 실제 착수할 때만(투기적 표시 금지). |
|
|
22
|
+
| **resolve** (→해결) | **사용자의 명시적 완료 선언**("X 끝났어", "점검 OK") | 명확하면 `gbc defer resolve <ref>` 실행 + **반드시 표면화**. |
|
|
23
|
+
| **reopen** (→open) | 사용자가 보류/이월/잘못된 resolve 취소를 요청 | `gbc defer reopen <ref>` 실행 + 표면화. |
|
|
24
|
+
|
|
25
|
+
**resolve 모호성 규칙 (load-bearing — 미완성 항목이 조용히 잊히는 harm 차단):**
|
|
26
|
+
- **명확한 완료 선언**("로그인 검증 끝냈어") → 자동 resolve + 표면화.
|
|
27
|
+
- **모호한 신호**("다음으로 넘어가자", "대충 됐어") → **resolve하지 말고 사용자에게 확인**한다. resolve된 항목은 리마인드/SessionStart에서 사라지므로, 잘못 resolve하면 미완성인 채 잊힌다.
|
|
28
|
+
- resolve는 **절대 게이트(judge)가 편집을 보고 추론하지 않는다** — 항상 사람의 명시 선언이 트리거. (start만 편집 감지로 자동.)
|
|
29
|
+
- 모든 자동 전환은 **사용자에게 표면화**해 catch·reopen할 수 있게 한다.
|
|
30
|
+
|
|
31
|
+
> 이상적 흐름: defer 확인 → (특정/전체 항목) start → 구현 → **사용자 점검** → resolve. 세션 내 완전 해소 안 되는 항목은 in_progress로 이월되고, SessionStart가 "진행중 N · 미착수 M"으로 구분 표면화한다.
|
|
32
|
+
|
|
15
33
|
## 명령 (bash로 실행)
|
|
16
34
|
|
|
17
35
|
게이트는 현재 프로젝트 루트의 `gbc`를 사용한다. 작업 디렉토리에서 실행:
|
|
@@ -19,9 +37,11 @@ description: 거북이코드 구현-전 게이트를 관리한다. 로드된 계
|
|
|
19
37
|
| 의도 | 명령 |
|
|
20
38
|
|---|---|
|
|
21
39
|
| 게이트 상태·로드된 명세 확인 | `gbc status` |
|
|
22
|
-
| 미룬 항목 목록 | `gbc defer list` |
|
|
23
|
-
| 케이스를 명시적으로 미루기 | `gbc defer add "<케이스 설명>"` |
|
|
24
|
-
|
|
|
40
|
+
| 미룬 항목 목록(상태: 미해결/진행중/해결) | `gbc defer list` |
|
|
41
|
+
| 케이스를 명시적으로 미루기 (→ open) | `gbc defer add "<케이스 설명>"` |
|
|
42
|
+
| 착수 표시 (open → 진행중) | `gbc defer start <번호\|텍스트\|all>` |
|
|
43
|
+
| 종결 표시 (→ 해결) | `gbc defer resolve <번호\|텍스트\|all>` |
|
|
44
|
+
| 백로그로 되돌리기 (→ open) | `gbc defer reopen <번호\|텍스트\|all>` |
|
|
25
45
|
| 승인된 시나리오를 명세에 등록 | `gbc spec add "<케이스>"` |
|
|
26
46
|
| 등록된 케이스 목록 | `gbc spec show` |
|
|
27
47
|
| 명세 비우기(작업단위 종료) | `gbc spec clear` |
|
|
@@ -38,7 +58,7 @@ description: 거북이코드 구현-전 게이트를 관리한다. 로드된 계
|
|
|
38
58
|
3. 승인된 케이스를 `gbc spec add "<케이스>"`로 등록하거나 `.gbc/spec.md`에 직접 작성한다.
|
|
39
59
|
4. 재시도하면 통과한다.
|
|
40
60
|
> 시나리오 도출은 코딩 에이전트 본체(Opus)가 대화 맥락으로, 게이트 판정은 haiku가 — 두 작업/두 모델 분리(gbc는 모델 계층을 소유하지 않는다).
|
|
41
|
-
3. **세션 종료 시**: Stop hook이 미해결 defer를 리마인드한다. `gbc defer list`로
|
|
61
|
+
3. **세션 종료 시**: Stop hook이 미해결 defer를 "진행중 N · 미착수 M"으로 구분 리마인드한다. `gbc defer list`로 확인하고, 사용자 완료 선언이 있었으면 resolve, 아니면 다음 세션으로 의식적으로 이월한다(진행중 항목은 in_progress 그대로 남아 다음 SessionStart에 표면화).
|
|
42
62
|
|
|
43
63
|
## 명세 소스
|
|
44
64
|
|