@su-record/vibe 3.2.22 → 3.2.24

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.
@@ -39,6 +39,15 @@ export declare function extractPostinstallReport(npmOutput: string): string[];
39
39
  * @returns 복구한 하네스 목록 (빈 배열이면 복구 불필요 또는 vibe 프로젝트 아님)
40
40
  */
41
41
  export declare function repairProjectHooks(projectRoot: string): string[];
42
+ /**
43
+ * 전역 자산(`~/.vibe/`)이 방금 설치한 버전으로 갱신됐는지 판정한다.
44
+ *
45
+ * postinstall 이 패키지를 `~/.vibe/node_modules/@su-record/vibe` 로 복사하므로,
46
+ * 그 사본의 버전이 **마지막으로 postinstall 이 성공한 시점**을 말해준다.
47
+ *
48
+ * @returns 갱신됐으면 null, 아니면 발견된 사본 버전(없으면 'none')
49
+ */
50
+ export declare function staleGlobalAssets(installedVersion: string): string | null;
42
51
  /**
43
52
  * Upgrade global package to latest version
44
53
  * npm install -g → postinstall handles global config
@@ -1 +1 @@
1
- {"version":3,"file":"upgrade.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/upgrade.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAwBzC;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAYjE;AAQD;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAMpE;AAcD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CA+BhE;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,QAAQ,GAAE,UAA8B,GAAG,IAAI,CAsDtE"}
1
+ {"version":3,"file":"upgrade.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/upgrade.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAyBzC;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAYjE;AAQD;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAMpE;AAcD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CA+BhE;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,gBAAgB,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAUzE;AAwBD;;;GAGG;AACH,wBAAgB,OAAO,CAAC,QAAQ,GAAE,UAA8B,GAAG,IAAI,CAkEtE"}
@@ -9,6 +9,7 @@ import { log, getPackageJson } from '../utils.js';
9
9
  import { formatLLMStatus } from '../auth.js';
10
10
  import { installProjectHooks, installProjectCodexHooks } from '../setup.js';
11
11
  import { detectCodexCli } from '../utils/cli-detector.js';
12
+ import { getCoreConfigDir } from '../setup/GlobalInstaller.js';
12
13
  /**
13
14
  * Remove stale npm temp directories that cause ENOTEMPTY errors
14
15
  */
@@ -125,6 +126,46 @@ export function repairProjectHooks(projectRoot) {
125
126
  }
126
127
  return repaired;
127
128
  }
129
+ /**
130
+ * 전역 자산(`~/.vibe/`)이 방금 설치한 버전으로 갱신됐는지 판정한다.
131
+ *
132
+ * postinstall 이 패키지를 `~/.vibe/node_modules/@su-record/vibe` 로 복사하므로,
133
+ * 그 사본의 버전이 **마지막으로 postinstall 이 성공한 시점**을 말해준다.
134
+ *
135
+ * @returns 갱신됐으면 null, 아니면 발견된 사본 버전(없으면 'none')
136
+ */
137
+ export function staleGlobalAssets(installedVersion) {
138
+ try {
139
+ const copied = JSON.parse(readFileSync(join(getCoreConfigDir(), 'node_modules', '@su-record', 'vibe', 'package.json'), 'utf-8'));
140
+ return copied.version === installedVersion ? null : (copied.version ?? 'unknown');
141
+ }
142
+ catch {
143
+ return 'none';
144
+ }
145
+ }
146
+ /**
147
+ * postinstall 을 직접 실행해 전역 자산을 복구한다.
148
+ *
149
+ * WHY: npm 이 lifecycle script 를 건너뛰는 환경이 있다 (`npm warn install-scripts`).
150
+ * 그러면 전역 **패키지**는 새 버전인데 `~/.vibe/hooks/scripts/` 는 옛날 그대로다 —
151
+ * sentinel·scope·run-ledger·verify 가 전부 구버전 코드로 돌면서 upgrade 는
152
+ * "✅ 성공" 을 출력한다. 실측: 두 번의 릴리즈 동안 훅이 5일 전 상태로 멈춰 있었다.
153
+ * 결정론적 가드의 생사는 관측 가능해야 한다 — 여기서만큼은 경고가 아니라 복구를 한다.
154
+ *
155
+ * @returns 복구 성공 여부
156
+ */
157
+ function runInstalledPostinstall(globalRoot) {
158
+ try {
159
+ const entry = join(globalRoot, '@su-record', 'vibe', 'dist', 'cli', 'postinstall', 'main.js');
160
+ if (!existsSync(entry))
161
+ return false;
162
+ execFileSync(process.execPath, [entry], { stdio: 'ignore', timeout: 120_000 });
163
+ return true;
164
+ }
165
+ catch {
166
+ return false;
167
+ }
168
+ }
128
169
  /**
129
170
  * Upgrade global package to latest version
130
171
  * npm install -g → postinstall handles global config
@@ -159,6 +200,17 @@ export function upgrade(_options = { silent: false }) {
159
200
  llmStatus = readInstalledLLMStatus(globalRoot);
160
201
  }
161
202
  catch { /* fallback to current process formatter */ }
203
+ // 전역 자산이 실제로 갱신됐는지 확인한다 — npm 이 lifecycle script 를 건너뛰면
204
+ // 패키지만 새 버전이고 ~/.vibe/hooks/ 는 옛날 그대로인 채 "성공" 이 출력된다
205
+ let globalNote = '';
206
+ const stale = staleGlobalAssets(newVersion);
207
+ if (stale && globalRoot) {
208
+ const recovered = runInstalledPostinstall(globalRoot);
209
+ const after = recovered ? staleGlobalAssets(newVersion) : stale;
210
+ globalNote = after === null
211
+ ? `\n🔧 Global assets restored (postinstall did not run — was v${stale})\n`
212
+ : `\n⚠️ Global assets stale (v${after}) — hooks may run old code. Try: npm install -g @su-record/vibe@latest --force --foreground-scripts\n`;
213
+ }
162
214
  const repaired = repairProjectHooks(process.cwd());
163
215
  const hookNote = repaired.length > 0
164
216
  ? `\n🔧 Project hooks restored: ${repaired.join(', ')}\n`
@@ -166,7 +218,7 @@ export function upgrade(_options = { silent: false }) {
166
218
  const pruneNote = postinstallReport.length > 0
167
219
  ? `\n🧹 ${postinstallReport.join('\n🧹 ')}\n`
168
220
  : '';
169
- log(`\n✅ vibe upgraded (v${newVersion})\n${hookNote}${pruneNote}\n${llmStatus}\n`);
221
+ log(`\n✅ vibe upgraded (v${newVersion})\n${globalNote}${hookNote}${pruneNote}\n${llmStatus}\n`);
170
222
  }
171
223
  catch (error) {
172
224
  const message = error instanceof Error ? error.message : String(error);
@@ -1 +1 @@
1
- {"version":3,"file":"upgrade.js","sourceRoot":"","sources":["../../../src/cli/commands/upgrade.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"upgrade.js","sourceRoot":"","sources":["../../../src/cli/commands/upgrade.ts"],"names":[],"mappings":"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"}
@@ -36,7 +36,7 @@ export function hashDiscoverOutput(text) {
36
36
  * 루프 이벤트를 jsonl 파일에 append한다.
37
37
  *
38
38
  * @param {string} projectDir
39
- * @param {{ loop: string, event: 'start'|'discover'|'end', result?: 'ok'|'fail'|'stuck', summary?: string, discoverHash?: string }} opts
39
+ * @param {{ loop: string, event: 'start'|'discover'|'end'|'iteration', result?: 'ok'|'fail'|'stuck', summary?: string, discoverHash?: string, verified?: boolean }} opts
40
40
  * @returns {boolean} 성공 여부
41
41
  */
42
42
  export function appendLoopEvent(projectDir, opts) {
@@ -50,6 +50,7 @@ export function appendLoopEvent(projectDir, opts) {
50
50
  ...(opts.result !== undefined ? { result: opts.result } : {}),
51
51
  ...(opts.summary !== undefined ? { summary: opts.summary } : {}),
52
52
  ...(opts.discoverHash !== undefined ? { discoverHash: opts.discoverHash } : {}),
53
+ ...(opts.verified !== undefined ? { verified: opts.verified } : {}),
53
54
  };
54
55
  fs.appendFileSync(p, JSON.stringify(entry) + '\n', 'utf-8');
55
56
  return true;
@@ -116,3 +117,55 @@ export function isStuck(projectDir, loop, discoverHash) {
116
117
  return false;
117
118
  }
118
119
  }
120
+
121
+ /**
122
+ * 회전 1회를 기록한다.
123
+ *
124
+ * @param {string} projectDir
125
+ * @param {string} loop
126
+ * @param {boolean} verified - 이 회전이 검증을 통과했는가 (JUDGE 결정론 게이트 기준)
127
+ * @returns {boolean}
128
+ */
129
+ export function recordIteration(projectDir, loop, verified) {
130
+ return appendLoopEvent(projectDir, { loop, event: 'iteration', verified: Boolean(verified) });
131
+ }
132
+
133
+ /**
134
+ * 현재 루프 실행의 예산 상태.
135
+ *
136
+ * 두 축을 **따로** 센다:
137
+ * - `iterations` — 모든 회전. `max_iterations` 와 비교하는 폭주 방어 축이다.
138
+ * - `verified` — 검증을 통과한 회전만. 실제로 전진한 양이다.
139
+ *
140
+ * 둘을 하나로 뭉치면 "10회를 썼다" 는 알아도 "그중 8회가 헛돌았다" 는 모른다 —
141
+ * 헛도는 루프와 원래 큰 작업을 구분할 수 없다. loop-contract 는 폭주 방어가
142
+ * 모델의 양심이 아니라 코드여야 한다고 선언하는데, 정작 max_iterations 에는
143
+ * 런타임 계수가 없어 모델이 스스로 세고 있었다 (감사 2026-08-10).
144
+ *
145
+ * 직전 `start` 이후만 센다 — 새 실행은 예산도 새로 시작한다.
146
+ *
147
+ * @param {string} projectDir
148
+ * @param {string} loop
149
+ * @param {number} [maxIterations=10]
150
+ * @returns {{ iterations: number, verified: number, remaining: number, exhausted: boolean }}
151
+ */
152
+ export function readBudget(projectDir, loop, maxIterations = 10) {
153
+ const empty = { iterations: 0, verified: 0, remaining: maxIterations, exhausted: false };
154
+ try {
155
+ const raw = fs.readFileSync(historyPath(projectDir), 'utf-8');
156
+ const events = raw.split('\n')
157
+ .map(line => { try { return JSON.parse(line); } catch { return null; } })
158
+ .filter(e => e && e.loop === loop);
159
+
160
+ const lastStart = events.map(e => e.event).lastIndexOf('start');
161
+ const scoped = lastStart === -1 ? events : events.slice(lastStart);
162
+
163
+ const iters = scoped.filter(e => e.event === 'iteration');
164
+ const iterations = iters.length;
165
+ const verified = iters.filter(e => e.verified === true).length;
166
+ const remaining = Math.max(0, maxIterations - iterations);
167
+ return { iterations, verified, remaining, exhausted: remaining === 0 };
168
+ } catch {
169
+ return empty;
170
+ }
171
+ }
@@ -11,13 +11,15 @@
11
11
  * node hooks/scripts/loop-ledger.js gate open <id> <question> [option...]
12
12
  * node hooks/scripts/loop-ledger.js gate list
13
13
  * node hooks/scripts/loop-ledger.js gate answer <id> <answer>
14
+ * node hooks/scripts/loop-ledger.js iteration <name> <verified|unverified>
15
+ * node hooks/scripts/loop-ledger.js budget <name> [maxIterations]
14
16
  *
15
17
  * check-stuck: 'stuck' 또는 'ok'를 stdout에 출력하고 항상 exit 0.
16
18
  * anchor: 재고정 번들 JSON을 stdout에 출력한다 (loop-contract ANCHOR 절).
17
19
  * 항상 exit 0 (fail-open).
18
20
  */
19
21
 
20
- import { appendLoopEvent, isStuck } from './lib/loop-ledger.js';
22
+ import { appendLoopEvent, isStuck, recordIteration, readBudget } from './lib/loop-ledger.js';
21
23
  import { buildAnchor } from './lib/anchor.js';
22
24
  import { prependInboxBlock } from './lib/inbox.js';
23
25
  import { openGate, listOpenGates, answerGate, formatOpenGates } from './lib/gates.js';
@@ -73,6 +75,27 @@ if (subcommand === 'start') {
73
75
  : '[loop-ledger] WARNING: inbox write failed\n'
74
76
  );
75
77
 
78
+ } else if (subcommand === 'iteration') {
79
+ // 회전 계수는 코드가 한다 — max_iterations 를 모델이 세면 폭주 방어가 양심이 된다
80
+ const [loop, verifiedArg] = args;
81
+ if (!loop || !['verified', 'unverified'].includes(verifiedArg || '')) {
82
+ process.stdout.write('[loop-ledger] error: iteration 에 루프 이름과 verified|unverified 가 필요합니다\n');
83
+ process.exit(0);
84
+ }
85
+ recordIteration(projectDir, loop, verifiedArg === 'verified');
86
+ const b = readBudget(projectDir, loop);
87
+ process.stdout.write(`[loop-ledger] iteration recorded: ${loop} ${verifiedArg} (${b.iterations} 회전 / 검증 ${b.verified})\n`);
88
+
89
+ } else if (subcommand === 'budget') {
90
+ const [loop, maxRaw] = args;
91
+ if (!loop) {
92
+ process.stdout.write('[loop-ledger] error: budget 에 루프 이름이 필요합니다\n');
93
+ process.exit(0);
94
+ }
95
+ const max = Number.parseInt(maxRaw ?? '10', 10);
96
+ const b = readBudget(projectDir, loop, Number.isInteger(max) && max > 0 ? max : 10);
97
+ process.stdout.write(JSON.stringify(b) + '\n');
98
+
76
99
  } else if (subcommand === 'gate') {
77
100
  // 사람 판단 지점을 디스크에 남긴다 — 세션이 죽어도 무엇을 묻고 있었는지 남는다
78
101
  const [action, id, ...rest] = args;
@@ -105,7 +128,7 @@ if (subcommand === 'start') {
105
128
  } else {
106
129
  process.stdout.write(
107
130
  '[loop-ledger] 사용법: start <name> | end <name> <ok|fail|stuck> [summary] | '
108
- + 'check-stuck <name> <hash> | anchor [feature] | inbox <name> <ok|fail|stuck> [line...] | gate <open|list|answer> …\n'
131
+ + 'check-stuck <name> <hash> | anchor [feature] | inbox <name> <ok|fail|stuck> [line...] | gate <open|list|answer> | iteration <name> <verified|unverified> | budget <name> [max]\n'
109
132
  );
110
133
  }
111
134
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@su-record/vibe",
3
- "version": "3.2.22",
3
+ "version": "3.2.24",
4
4
  "description": "AI Coding Framework for Claude Code — 7+ agents, 52 skills, multi-LLM orchestration",
5
5
  "type": "module",
6
6
  "main": "dist/cli/index.js",
@@ -86,6 +86,11 @@ node -e "import('{{VIBE_PATH_URL}}/node_modules/@su-record/vibe/dist/tools/index
86
86
  · ledger: cat .vibe/metrics/run-ledger.json → verifyPassed === true 만 성공
87
87
  · tests: 정의의 test_command 실행 → exit 0 만 성공
88
88
  · none: 판정 생략(보고만). "코드를 보니 잘 된 것 같다"는 판정이 아니다.
89
+ 6-b. 회전 기록 node "$HOOKS_DIR/loop-ledger.js" iteration <name> <verified|unverified>
90
+ VERIFY 결과를 그대로 넣는다. 이어서 예산을 확인한다:
91
+ node "$HOOKS_DIR/loop-ledger.js" budget <name> <max_iterations>
92
+ → exhausted 면 잔여를 인박스로 이월하고 종료한다. 회전 수를 모델이
93
+ 세지 않는다 — 폭주 방어는 코드가 판정한다 (loop-contract 예산 절).
89
94
  7. 종료 기록 node "$HOOKS_DIR/loop-ledger.js" end <name> <ok|fail|stuck> "<한 줄 요약>"
90
95
  8. 인박스 node "$HOOKS_DIR/loop-ledger.js" inbox <name> <ok|fail|stuck> \
91
96
  "발견: N건 / 처리: M건 / 검증: <기준과 결과>" \
@@ -111,6 +116,7 @@ node -e "import('{{VIBE_PATH_URL}}/node_modules/@su-record/vibe/dist/tools/index
111
116
  |------|------|
112
117
  | 완료는 게이트가 판정 | run-ledger `verifyPassed` / 테스트 exit code (REQ-005) |
113
118
  | stuck은 해시가 판정 | loop-ledger check-stuck, 동일 발견 2회 연속 (REQ-009) |
119
+ | 회전 수도 코드가 판정 | loop-ledger iteration/budget — iterations(폭주 방어) 와 verified(전진량) 를 분리 |
114
120
  | 이해 부채 가드 | 인박스 리뷰 큐 + push/release 금지 (REQ-008) |
115
121
  | 기록 없는 실행 없음 | loop-history.jsonl start/end 의무 (REQ-006) |
116
122
 
@@ -62,6 +62,27 @@ node "$HOOKS_DIR/loop-ledger.js" anchor [feature]
62
62
 
63
63
  > `autonomous` 의 "계속" 은 **stuck 난 루프를 더 돌린다는 뜻이 아니다** — 2회 연속 동일 발견은 정의상 재시도가 무의미하다. 같은 목표를 붙잡지 않고 다음 단위로 넘어간다는 뜻이며, 미달은 TODO/인박스에 남는다. 미달 상태를 **완료로 기록하지 않는다.**
64
64
 
65
+ ### 예산 — 회전은 코드가 세고, 전진과 헛돎을 구분한다
66
+
67
+ 이 문서는 서두에서 "폭주 방어가 모델의 양심이 아니라 결정론적 가드(코드)" 라고 선언한다. 그런데 정작 폭주 방어인 `max_iterations` 에는 런타임 계수가 없었다 — stuck 만 명령으로 판정되고 회전 수는 모델이 스스로 셌다. 선언과 구현이 어긋난 지점이었다 (감사 2026-08-10).
68
+
69
+ ```bash
70
+ node "$HOOKS_DIR/loop-ledger.js" iteration <name> <verified|unverified> # 회전 종료 시 1회
71
+ node "$HOOKS_DIR/loop-ledger.js" budget <name> [max] # → {iterations, verified, remaining, exhausted}
72
+ ```
73
+
74
+ **두 축을 따로 센다:**
75
+
76
+ | 축 | 의미 | 쓰임 |
77
+ |---|---|---|
78
+ | `iterations` | 모든 회전 | `max_iterations` 와 비교하는 **폭주 방어** |
79
+ | `verified` | JUDGE 결정론 게이트를 통과한 회전만 | 실제로 **전진한 양** |
80
+
81
+ 둘을 하나로 뭉치면 "10회를 썼다" 는 알아도 "그중 8회가 헛돌았다" 는 모른다 — 헛도는 루프와 원래 큰 작업을 구분할 수 없다. `exhausted: true` 면 잔여를 인박스로 이월하고 종료한다.
82
+
83
+ - **실행 실패(error)는 회전을 소비하지 않는다** — 위 실행 실패 절대로 루프가 즉시 종료되므로 예산을 갉아먹지 않는다. 재시도가 예산을 태우는 형태를 만들지 않는다.
84
+ - 계수는 직전 `start` 이후만 센다 — 새 실행은 예산도 새로 시작한다.
85
+
65
86
  ### 게이트 객체 — 사람을 기다리는 이유는 디스크에 산다
66
87
 
67
88
  사람 개입 지점의 질문이 컨텍스트에만 있으면, 세션이 죽거나 compact 로 소실될 때 **무엇을 묻고 있었는지가 사라진다.** 사람은 돌아왔는데 답할 대상이 없다. run-ledger·loop-history·인박스가 전부 디스크에 사는데 "지금 왜 멈춰 있는가"만 컨텍스트에 있었다.
@@ -131,7 +152,7 @@ stuck 은 **같은 발견이 반복되는** 상태다. 스킬이 로드되지
131
152
 
132
153
  | 파라미터 | 기본 | 의미 |
133
154
  |---|---|---|
134
- | `max_iterations` | 10 | 회전 상한. 도달 시 잔여를 인박스로 이월 |
155
+ | `max_iterations` | 10 | 회전 상한. 도달 시 잔여를 인박스로 이월. **계수는 코드가 한다** — 아래 예산 절 |
135
156
  | `exit` | 게이트 통과 (**측정된** P1=0 ∧ verifyPassed) | 종료 기준. coverage 100% 등으로 상향 가능. 판정된 P1 은 위 Judge 권한 경계 표를 따른다 |
136
157
  | `--interactive` | off | 단계별 확인 모드 (회전마다 사람 승인 — 과거의 기본값) |
137
158
  | `--max-iter N` | — | 회전 상한 명시 (N=1이면 1회 시도) |