@su-record/vibe 2.12.4 → 2.13.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/CLAUDE.md +10 -3
- package/README.en.md +11 -11
- package/README.md +7 -7
- package/dist/cli/commands/init.d.ts +8 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +24 -1
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/postinstall/claude-agents.d.ts +11 -1
- package/dist/cli/postinstall/claude-agents.d.ts.map +1 -1
- package/dist/cli/postinstall/claude-agents.js +22 -2
- package/dist/cli/postinstall/claude-agents.js.map +1 -1
- package/dist/cli/postinstall/constants.d.ts +18 -0
- package/dist/cli/postinstall/constants.d.ts.map +1 -1
- package/dist/cli/postinstall/constants.js +50 -0
- package/dist/cli/postinstall/constants.js.map +1 -1
- package/dist/cli/postinstall/fs-utils.d.ts +23 -0
- package/dist/cli/postinstall/fs-utils.d.ts.map +1 -1
- package/dist/cli/postinstall/fs-utils.js +71 -0
- package/dist/cli/postinstall/fs-utils.js.map +1 -1
- package/dist/cli/postinstall/fs-utils.test.js +69 -1
- package/dist/cli/postinstall/fs-utils.test.js.map +1 -1
- package/dist/cli/postinstall/index.d.ts +1 -1
- package/dist/cli/postinstall/index.d.ts.map +1 -1
- package/dist/cli/postinstall/index.js +1 -1
- package/dist/cli/postinstall/index.js.map +1 -1
- package/dist/cli/postinstall/main.d.ts.map +1 -1
- package/dist/cli/postinstall/main.js +15 -4
- package/dist/cli/postinstall/main.js.map +1 -1
- package/dist/cli/postinstall.d.ts +1 -1
- package/dist/cli/postinstall.d.ts.map +1 -1
- package/dist/cli/postinstall.js +1 -1
- package/dist/cli/postinstall.js.map +1 -1
- package/dist/cli/setup/CodexHooks.test.js +27 -0
- package/dist/cli/setup/CodexHooks.test.js.map +1 -1
- package/dist/cli/setup/ProjectSetup.js +2 -2
- package/dist/cli/setup/ProjectSetup.js.map +1 -1
- package/dist/infra/lib/ContextCompressor.d.ts +11 -2
- package/dist/infra/lib/ContextCompressor.d.ts.map +1 -1
- package/dist/infra/lib/ContextCompressor.js +26 -41
- package/dist/infra/lib/ContextCompressor.js.map +1 -1
- package/dist/infra/lib/ContextCompressor.test.d.ts +2 -0
- package/dist/infra/lib/ContextCompressor.test.d.ts.map +1 -0
- package/dist/infra/lib/ContextCompressor.test.js +25 -0
- package/dist/infra/lib/ContextCompressor.test.js.map +1 -0
- package/dist/infra/lib/DecisionTracer.d.ts +4 -0
- package/dist/infra/lib/DecisionTracer.d.ts.map +1 -1
- package/dist/infra/lib/DecisionTracer.js +4 -0
- package/dist/infra/lib/DecisionTracer.js.map +1 -1
- package/dist/infra/lib/LoopBreaker.d.ts +4 -0
- package/dist/infra/lib/LoopBreaker.d.ts.map +1 -1
- package/dist/infra/lib/LoopBreaker.js +4 -0
- package/dist/infra/lib/LoopBreaker.js.map +1 -1
- package/dist/infra/lib/ReviewRace.d.ts +4 -0
- package/dist/infra/lib/ReviewRace.d.ts.map +1 -1
- package/dist/infra/lib/ReviewRace.js +4 -0
- package/dist/infra/lib/ReviewRace.js.map +1 -1
- package/dist/infra/lib/SkillQualityGate.d.ts +4 -0
- package/dist/infra/lib/SkillQualityGate.d.ts.map +1 -1
- package/dist/infra/lib/SkillQualityGate.js +4 -0
- package/dist/infra/lib/SkillQualityGate.js.map +1 -1
- package/dist/infra/lib/UltraQA.d.ts +4 -0
- package/dist/infra/lib/UltraQA.d.ts.map +1 -1
- package/dist/infra/lib/UltraQA.js +4 -0
- package/dist/infra/lib/UltraQA.js.map +1 -1
- package/dist/infra/lib/VerificationLoop.d.ts +4 -0
- package/dist/infra/lib/VerificationLoop.d.ts.map +1 -1
- package/dist/infra/lib/VerificationLoop.js +4 -0
- package/dist/infra/lib/VerificationLoop.js.map +1 -1
- package/dist/infra/lib/embedding/VectorStore.d.ts +9 -2
- package/dist/infra/lib/embedding/VectorStore.d.ts.map +1 -1
- package/dist/infra/lib/embedding/VectorStore.js +42 -19
- package/dist/infra/lib/embedding/VectorStore.js.map +1 -1
- package/dist/infra/lib/memory/MemoryStorage.d.ts +12 -0
- package/dist/infra/lib/memory/MemoryStorage.d.ts.map +1 -1
- package/dist/infra/lib/memory/MemoryStorage.js +57 -0
- package/dist/infra/lib/memory/MemoryStorage.js.map +1 -1
- package/dist/infra/lib/memory/ReflectionStore.d.ts.map +1 -1
- package/dist/infra/lib/memory/ReflectionStore.js +8 -27
- package/dist/infra/lib/memory/ReflectionStore.js.map +1 -1
- package/dist/infra/orchestrator/LLMCluster.d.ts +4 -0
- package/dist/infra/orchestrator/LLMCluster.d.ts.map +1 -1
- package/dist/infra/orchestrator/LLMCluster.js +35 -8
- package/dist/infra/orchestrator/LLMCluster.js.map +1 -1
- package/dist/infra/orchestrator/index.d.ts.map +1 -1
- package/dist/infra/orchestrator/index.js +1 -3
- package/dist/infra/orchestrator/index.js.map +1 -1
- package/dist/infra/orchestrator/parallelResearch.d.ts.map +1 -1
- package/dist/infra/orchestrator/parallelResearch.js +1 -4
- package/dist/infra/orchestrator/parallelResearch.js.map +1 -1
- package/dist/tools/convention/validateCodeQuality.d.ts.map +1 -1
- package/dist/tools/convention/validateCodeQuality.js +5 -4
- package/dist/tools/convention/validateCodeQuality.js.map +1 -1
- package/dist/tools/spec/traceabilityMatrix.d.ts +2 -0
- package/dist/tools/spec/traceabilityMatrix.d.ts.map +1 -1
- package/dist/tools/spec/traceabilityMatrix.js +50 -1
- package/dist/tools/spec/traceabilityMatrix.js.map +1 -1
- package/dist/tools/spec/traceabilityMatrix.path-resolution.test.d.ts +10 -0
- package/dist/tools/spec/traceabilityMatrix.path-resolution.test.d.ts.map +1 -0
- package/dist/tools/spec/traceabilityMatrix.path-resolution.test.js +89 -0
- package/dist/tools/spec/traceabilityMatrix.path-resolution.test.js.map +1 -0
- package/dist/tools/spec/traceabilityMatrix.test.js +19 -0
- package/dist/tools/spec/traceabilityMatrix.test.js.map +1 -1
- package/hooks/hooks.json +3 -9
- package/hooks/scripts/__tests__/.vibe/command-log.txt +39 -0
- package/hooks/scripts/__tests__/.vibe/memories/memories.db +0 -0
- package/hooks/scripts/__tests__/.vibe/memories/memories.db-shm +0 -0
- package/hooks/scripts/__tests__/.vibe/memories/memories.db-wal +0 -0
- package/hooks/scripts/__tests__/auto-test-debounce.test.js +145 -0
- package/hooks/scripts/__tests__/code-check-detectors.test.js +155 -0
- package/hooks/scripts/__tests__/dispatcher-inprocess.test.js +99 -0
- package/hooks/scripts/__tests__/post-edit-dispatcher.test.js +139 -0
- package/hooks/scripts/__tests__/pre-tool-guard.test.js +115 -1
- package/hooks/scripts/__tests__/run-ledger-verify-required.test.js +146 -0
- package/hooks/scripts/__tests__/run-ledger.test.js +330 -0
- package/hooks/scripts/__tests__/scope-from-spec.test.js +215 -0
- package/hooks/scripts/__tests__/sentinel-guard.test.js +79 -24
- package/hooks/scripts/__tests__/step-counter.test.js +95 -15
- package/hooks/scripts/__tests__/utils-npm-root.test.js +98 -0
- package/hooks/scripts/auto-commit.js +30 -11
- package/hooks/scripts/auto-format.js +96 -26
- package/hooks/scripts/auto-test.js +187 -37
- package/hooks/scripts/code-check.js +292 -99
- package/hooks/scripts/codex-hook-adapter.js +12 -1
- package/hooks/scripts/command-log.js +26 -16
- package/hooks/scripts/lib/dispatcher.js +38 -0
- package/hooks/scripts/lib/hook-context.js +101 -0
- package/hooks/scripts/lib/pr-gate-runner.js +62 -0
- package/hooks/scripts/lib/run-ledger.js +169 -0
- package/hooks/scripts/lib/scope-from-spec.js +40 -7
- package/hooks/scripts/post-edit-dispatcher.js +93 -20
- package/hooks/scripts/post-edit.js +40 -19
- package/hooks/scripts/pr-test-gate.js +8 -37
- package/hooks/scripts/pre-tool-dispatcher.js +18 -16
- package/hooks/scripts/pre-tool-guard.js +55 -52
- package/hooks/scripts/prompt-dispatcher.js +10 -0
- package/hooks/scripts/scope-guard.js +40 -39
- package/hooks/scripts/sentinel-guard.js +41 -41
- package/hooks/scripts/session-start.js +41 -16
- package/hooks/scripts/step-counter.js +100 -7
- package/hooks/scripts/stop-dispatcher.js +26 -0
- package/hooks/scripts/utils.js +96 -30
- package/hooks/scripts/verify-ledger.js +22 -0
- package/package.json +2 -2
- package/skills/arch-guard/SKILL.md +2 -2
- package/skills/characterization-test/SKILL.md +2 -2
- package/skills/exec-plan/SKILL.md +2 -2
- package/skills/spec/SKILL.md +6 -312
- package/skills/spec/references/askuser-examples.md +57 -0
- package/skills/spec/references/example-session.md +87 -0
- package/skills/spec/references/templates.md +194 -0
- package/skills/vibe.run/SKILL.md +145 -1682
- package/skills/vibe.run/references/brand-assets.md +59 -0
- package/skills/vibe.run/references/parallel-agents.md +326 -0
- package/skills/vibe.run/references/race-review.md +272 -0
- package/skills/vibe.run/references/ralph-loop.md +172 -0
- package/skills/vibe.run/references/ultrawork-mode.md +148 -0
- package/skills/vibe.trace/SKILL.md +25 -38
- package/skills/vibe.verify/SKILL.md +15 -0
- package/vibe/templates/claudemd-template.md +1 -1
- package/hooks/scripts/figma-guard.js +0 -219
|
@@ -2,9 +2,19 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* PostToolUse Hook — 툴콜 스텝 카운터 + 패턴 로거 + 3-fail 감지기
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* hooks.json matcher: "Edit|Write|Bash|Task|SlashCommand|NotebookEdit"
|
|
6
|
+
* → Read/Grep/Glob/WebFetch/WebSearch/TodoWrite/ToolSearch/ListMcpResourcesTool
|
|
7
|
+
* 는 matcher 단에서 제외되어 이 프로세스 자체가 spawn 되지 않는다.
|
|
8
|
+
* 아래 READ_ONLY_TOOLS 는 defense-in-depth 용 추가 가드이다.
|
|
9
|
+
*
|
|
10
|
+
* 책임 1) 액션 툴콜을 1 스텝으로 집계 → `current-run.json`
|
|
6
11
|
* ↳ /vibe.verify 가 history.jsonl에 append 후 출력
|
|
7
|
-
*
|
|
12
|
+
* ↳ recipe-extractor 가 meta(startedAt, steps) 소비
|
|
13
|
+
* [쓰기 스로틀] current-run.json 전체 재작성은 (a) 마지막 재작성 후
|
|
14
|
+
* ≥2초 경과, 또는 (b) 10이벤트마다 1회로 제한.
|
|
15
|
+
* 액션 툴(Edit/Write/Bash) 호출 + ≥2초 조건을 동시 충족할 때도 재작성.
|
|
16
|
+
* Stop 경로 등 최종 상태가 필요한 소비자는 staleness ≤10 이벤트를 허용.
|
|
17
|
+
* 책임 2) 각 툴콜 한 줄을 `current-run.jsonl` 에 append (항상 즉시 — 스로틀 없음)
|
|
8
18
|
* ↳ Phase 3 의 recipe extractor 가 소비
|
|
9
19
|
* 책임 3) (Phase 2) 실패 메시지 분류 + 같은 (file, category) 3회 반복 감지 →
|
|
10
20
|
* `.vibe/anti-patterns/<slug>.md` 로 자동 저장.
|
|
@@ -24,6 +34,16 @@ const MAX_JSONL_LINES = 5000;
|
|
|
24
34
|
const FAIL_WINDOW = 10;
|
|
25
35
|
const FAIL_THRESHOLD = 3;
|
|
26
36
|
|
|
37
|
+
// 스로틀 파라미터
|
|
38
|
+
const REWRITE_INTERVAL_MS = 2000; // 최소 재작성 간격
|
|
39
|
+
const REWRITE_EVERY_N = 10; // N이벤트마다 강제 재작성
|
|
40
|
+
|
|
41
|
+
// 읽기 전용 툴 — defense-in-depth (matcher 단에서 이미 제거됨)
|
|
42
|
+
const READ_ONLY_TOOLS = new Set([
|
|
43
|
+
'Read', 'Grep', 'Glob', 'WebFetch', 'WebSearch',
|
|
44
|
+
'TodoWrite', 'ToolSearch', 'ListMcpResourcesTool',
|
|
45
|
+
]);
|
|
46
|
+
|
|
27
47
|
// ─────────────────────────────────────────────────────
|
|
28
48
|
// stdin / env 에서 PostToolUse payload 추출
|
|
29
49
|
// ─────────────────────────────────────────────────────
|
|
@@ -97,9 +117,43 @@ function classifyError(toolResponse) {
|
|
|
97
117
|
}
|
|
98
118
|
|
|
99
119
|
// ─────────────────────────────────────────────────────
|
|
100
|
-
//
|
|
120
|
+
// 스로틀: current-run.json 재작성 여부 결정
|
|
121
|
+
// ─────────────────────────────────────────────────────
|
|
122
|
+
/**
|
|
123
|
+
* 재작성해야 하는지 판단.
|
|
124
|
+
* - jsonlLineCount: jsonl 에 추가한 후의 총 라인 수 (이벤트 카운터 대리)
|
|
125
|
+
* - toolName: Edit/Write/Bash 인 경우 mtime 조건 충족 시 강제 재작성
|
|
126
|
+
* @param {string} toolName
|
|
127
|
+
* @param {number} jsonlLineCount
|
|
128
|
+
* @returns {boolean}
|
|
129
|
+
*/
|
|
130
|
+
function shouldRewriteJson(toolName, jsonlLineCount) {
|
|
131
|
+
// N이벤트마다 강제 재작성 (race-tolerant — jsonl 줄 수가 카운터 역할)
|
|
132
|
+
if (jsonlLineCount % REWRITE_EVERY_N === 0) return true;
|
|
133
|
+
|
|
134
|
+
// 마지막 재작성 후 ≥2초 경과 여부 (mtime 기반)
|
|
135
|
+
try {
|
|
136
|
+
const stat = fs.statSync(CURRENT_RUN_JSON);
|
|
137
|
+
const elapsed = Date.now() - stat.mtimeMs;
|
|
138
|
+
if (elapsed >= REWRITE_INTERVAL_MS) return true;
|
|
139
|
+
} catch {
|
|
140
|
+
// 파일 없음 → 첫 쓰기, 허용
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ─────────────────────────────────────────────────────
|
|
148
|
+
// 책임 1: current-run.json 카운터 (스로틀 적용)
|
|
149
|
+
//
|
|
150
|
+
// steps 는 current-run.jsonl 의 라인 수에서 파생한다.
|
|
151
|
+
// 이렇게 하면 스로틀로 재작성을 건너뛰어도 재작성 시점에
|
|
152
|
+
// 정확한 steps 가 보장되며, 프로세스간 누적도 자연스럽게 처리된다.
|
|
101
153
|
// ─────────────────────────────────────────────────────
|
|
102
|
-
function bumpCounter() {
|
|
154
|
+
function bumpCounter(toolName, currentLineCount) {
|
|
155
|
+
if (!shouldRewriteJson(toolName, currentLineCount)) return; // 스로틀 — 재작성 생략
|
|
156
|
+
|
|
103
157
|
let data = { feature: null, startedAt: null, steps: 0 };
|
|
104
158
|
if (fs.existsSync(CURRENT_RUN_JSON)) {
|
|
105
159
|
try {
|
|
@@ -107,12 +161,24 @@ function bumpCounter() {
|
|
|
107
161
|
} catch { /* 손상 → 새로 시작 */ }
|
|
108
162
|
}
|
|
109
163
|
if (!data.startedAt) data.startedAt = new Date().toISOString();
|
|
110
|
-
|
|
111
|
-
|
|
164
|
+
// steps = jsonl 라인 수에서 파생 (매 재작성 시 최신값 보장)
|
|
165
|
+
data.steps = currentLineCount;
|
|
166
|
+
|
|
167
|
+
// 원자적 쓰기 (tmp → rename) — 동시 에이전트 파일 손상 방지
|
|
168
|
+
const tmp = CURRENT_RUN_JSON + '.tmp';
|
|
169
|
+
try {
|
|
170
|
+
fs.writeFileSync(tmp, JSON.stringify(data, null, 2));
|
|
171
|
+
fs.renameSync(tmp, CURRENT_RUN_JSON);
|
|
172
|
+
} catch {
|
|
173
|
+
// rename 실패 시 직접 쓰기 (fail-open)
|
|
174
|
+
try { fs.writeFileSync(CURRENT_RUN_JSON, JSON.stringify(data, null, 2)); } catch { /* ignore */ }
|
|
175
|
+
try { fs.unlinkSync(tmp); } catch { /* ignore */ }
|
|
176
|
+
}
|
|
112
177
|
}
|
|
113
178
|
|
|
114
179
|
// ─────────────────────────────────────────────────────
|
|
115
180
|
// 책임 2: current-run.jsonl append + error_category 채움
|
|
181
|
+
// 항상 즉시 append — 스로틀 없음 (3-fail detector, recipe-extractor 가 소비)
|
|
116
182
|
// ─────────────────────────────────────────────────────
|
|
117
183
|
function appendJsonl(stdinPayload) {
|
|
118
184
|
const toolName = stdinPayload?.tool_name || process.argv[2] || '';
|
|
@@ -147,6 +213,21 @@ function appendJsonl(stdinPayload) {
|
|
|
147
213
|
return record;
|
|
148
214
|
}
|
|
149
215
|
|
|
216
|
+
/**
|
|
217
|
+
* jsonl 의 현재 라인 수를 반환 (stat 으로만 — 전체 파일 읽기 없음).
|
|
218
|
+
* 경합 안전 — 정확한 값이 아니어도 스로틀 판단에 충분하다.
|
|
219
|
+
*/
|
|
220
|
+
function jsonlLineCount() {
|
|
221
|
+
try {
|
|
222
|
+
// 라인 수 근사: 파일 크기 / 평균 라인 길이(100바이트) — 정확도 불필요
|
|
223
|
+
// 실제 라인 수는 appendJsonl 직후 여기서 읽으면 +1 반영됨
|
|
224
|
+
const content = fs.readFileSync(CURRENT_RUN_JSONL, 'utf-8');
|
|
225
|
+
return content.split('\n').filter(Boolean).length;
|
|
226
|
+
} catch {
|
|
227
|
+
return 1; // 파일 없음 → 첫 이벤트로 간주
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
150
231
|
// ─────────────────────────────────────────────────────
|
|
151
232
|
// 책임 3b: 3-fail detector → anti-pattern md
|
|
152
233
|
// ─────────────────────────────────────────────────────
|
|
@@ -238,10 +319,22 @@ try {
|
|
|
238
319
|
}
|
|
239
320
|
|
|
240
321
|
const stdinPayload = readStdinSync();
|
|
322
|
+
const toolName = stdinPayload?.tool_name || process.argv[2] || '';
|
|
241
323
|
|
|
242
|
-
|
|
324
|
+
// defense-in-depth: 읽기 전용 툴은 조기 종료 (matcher 단에서 이미 제거됨)
|
|
325
|
+
if (toolName && READ_ONLY_TOOLS.has(toolName)) {
|
|
326
|
+
process.exit(0);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// 책임 2를 먼저 실행 — jsonl 라인 수가 bumpCounter 스로틀 판단에 쓰임
|
|
243
330
|
let lastRecord = null;
|
|
244
331
|
try { lastRecord = appendJsonl(stdinPayload); } catch { /* 로깅 실패 무시 */ }
|
|
332
|
+
|
|
333
|
+
// 책임 1: jsonl append 후 라인 수로 스로틀 판단
|
|
334
|
+
const lineCount = jsonlLineCount();
|
|
335
|
+
try { bumpCounter(toolName, lineCount); } catch { /* 카운터 실패 무시 */ }
|
|
336
|
+
|
|
337
|
+
// 책임 3
|
|
245
338
|
try {
|
|
246
339
|
if (lastRecord && !lastRecord.ok && lastRecord.error_category) {
|
|
247
340
|
const trip = detectThreeFail();
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* 현재: 단일 디스패처에서 순차 실행.
|
|
8
8
|
*
|
|
9
9
|
* 실행 순서:
|
|
10
|
+
* 0. verify-skip 경고/차단 (in-process)
|
|
10
11
|
* 1. codex-review-gate — 리뷰 필요 여부 판단 (stdout → Claude 지시 주입)
|
|
11
12
|
* 2. stop-notify — 완료 알림
|
|
12
13
|
* 3. auto-commit — 변경 자동 커밋 (git hook cascade 주의)
|
|
@@ -16,9 +17,34 @@
|
|
|
16
17
|
* 이 Stop dispatcher도 건너뛴다 (자식 세션에서 auto-commit 등이 돌 이유 없음).
|
|
17
18
|
*/
|
|
18
19
|
import { dispatch } from './lib/dispatcher.js';
|
|
20
|
+
import { readLedger, markStopWarned } from './lib/run-ledger.js';
|
|
21
|
+
import { PROJECT_DIR, readProjectConfig } from './utils.js';
|
|
19
22
|
|
|
20
23
|
if (process.env.VIBE_HOOK_DEPTH) process.exit(0);
|
|
21
24
|
|
|
25
|
+
// verify-skip 게이트 — runStarted가 있고 verifyPassed가 false이면 경고 또는 차단.
|
|
26
|
+
// stopWarned가 이미 true이면 루프 방지를 위해 스킵.
|
|
27
|
+
try {
|
|
28
|
+
const ledger = readLedger(PROJECT_DIR);
|
|
29
|
+
if (ledger && ledger.runStarted && !ledger.verifyPassed && !ledger.stopWarned) {
|
|
30
|
+
const config = readProjectConfig();
|
|
31
|
+
const gateMode = config?.verifyGate?.mode;
|
|
32
|
+
|
|
33
|
+
if (gateMode === 'block') {
|
|
34
|
+
// 1회에 한해 Stop 차단 — 루프 방지 플래그 먼저 세팅
|
|
35
|
+
markStopWarned(PROJECT_DIR);
|
|
36
|
+
const reason = '/vibe.verify not run after /vibe.run — stop blocked (verifyGate.mode=block)';
|
|
37
|
+
process.stdout.write(JSON.stringify({ decision: 'block', reason }) + '\n');
|
|
38
|
+
process.exit(0);
|
|
39
|
+
} else {
|
|
40
|
+
// 기본: warn 모드 — stderr에 경고, 루프 방지 플래그 세팅
|
|
41
|
+
markStopWarned(PROJECT_DIR);
|
|
42
|
+
const feature = ledger.runFeature ? ` (feature: ${ledger.runFeature})` : '';
|
|
43
|
+
process.stderr.write(`[vibe] WARNING: /vibe.run${feature} completed but /vibe.verify was not run.\n`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
} catch { /* verify-skip 게이트 실패는 이후 실행을 막지 않음 */ }
|
|
47
|
+
|
|
22
48
|
try {
|
|
23
49
|
await dispatch([
|
|
24
50
|
{ name: 'codex-review-gate', script: 'codex-review-gate.js' },
|
package/hooks/scripts/utils.js
CHANGED
|
@@ -75,32 +75,44 @@ export function projectMemoryDir(projectDir = PROJECT_DIR) {
|
|
|
75
75
|
return path.join(projectDir, '.vibe', 'memories');
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
// config 캐시 — 훅 스크립트는 단명 프로세스이므로 프로세스 생명주기 내에서
|
|
79
|
+
// config 파일이 바뀌지 않는다고 가정한다 (llm-orchestrate처럼 한 프로세스에서
|
|
80
|
+
// 3회 이상 읽는 경로의 중복 read+parse 제거).
|
|
81
|
+
let _vibeConfigCache = null;
|
|
82
|
+
let _projectConfigCache = null;
|
|
83
|
+
|
|
78
84
|
/**
|
|
79
|
-
* ~/.vibe/config.json 읽기
|
|
85
|
+
* ~/.vibe/config.json 읽기 (프로세스 내 캐시)
|
|
80
86
|
* @returns {object} 파싱된 config 또는 빈 객체
|
|
81
87
|
*/
|
|
82
88
|
export function readVibeConfig() {
|
|
89
|
+
if (_vibeConfigCache !== null) return _vibeConfigCache;
|
|
83
90
|
const configPath = path.join(VIBE_HOME_DIR, 'config.json');
|
|
84
91
|
try {
|
|
85
92
|
if (fs.existsSync(configPath)) {
|
|
86
|
-
|
|
93
|
+
_vibeConfigCache = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
94
|
+
return _vibeConfigCache;
|
|
87
95
|
}
|
|
88
96
|
} catch { /* ignore */ }
|
|
89
|
-
|
|
97
|
+
_vibeConfigCache = {};
|
|
98
|
+
return _vibeConfigCache;
|
|
90
99
|
}
|
|
91
100
|
|
|
92
101
|
/**
|
|
93
|
-
* 프로젝트 설정(.vibe/config.json) 읽기 — legacy `.claude/vibe/` fallback 포함
|
|
102
|
+
* 프로젝트 설정(.vibe/config.json) 읽기 — legacy `.claude/vibe/` fallback 포함 (프로세스 내 캐시)
|
|
94
103
|
* @returns {object} 파싱된 config 또는 빈 객체
|
|
95
104
|
*/
|
|
96
105
|
export function readProjectConfig() {
|
|
106
|
+
if (_projectConfigCache !== null) return _projectConfigCache;
|
|
97
107
|
const configPath = projectVibePath(PROJECT_DIR, 'config.json');
|
|
98
108
|
try {
|
|
99
109
|
if (fs.existsSync(configPath)) {
|
|
100
|
-
|
|
110
|
+
_projectConfigCache = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
111
|
+
return _projectConfigCache;
|
|
101
112
|
}
|
|
102
113
|
} catch { /* ignore */ }
|
|
103
|
-
|
|
114
|
+
_projectConfigCache = {};
|
|
115
|
+
return _projectConfigCache;
|
|
104
116
|
}
|
|
105
117
|
|
|
106
118
|
/**
|
|
@@ -151,29 +163,71 @@ function toFileUrl(fsPath) {
|
|
|
151
163
|
return url.endsWith('/') ? url : url + '/';
|
|
152
164
|
}
|
|
153
165
|
|
|
154
|
-
// 전역 npm 경로 캐시
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
166
|
+
// 전역 npm 경로 캐시 — L1: 인-프로세스, L2: 파일 캐시 (TTL 24h)
|
|
167
|
+
// VIBE_NPM_ROOT_CACHE_FILE 환경 변수로 테스트 시 격리된 경로 주입 가능
|
|
168
|
+
const NPM_ROOT_CACHE_FILE = process.env.VIBE_NPM_ROOT_CACHE_FILE ||
|
|
169
|
+
path.join(os.homedir(), '.vibe', 'cache', 'npm-root.json');
|
|
170
|
+
const NPM_ROOT_CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24시간
|
|
171
|
+
|
|
172
|
+
/** npm root -g 결과를 파일로 캐시 (원자적 쓰기). 실패는 조용히 무시. */
|
|
173
|
+
function saveNpmRootCache(npmRoot) {
|
|
174
|
+
try {
|
|
175
|
+
const dir = path.dirname(NPM_ROOT_CACHE_FILE);
|
|
176
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
177
|
+
const payload = JSON.stringify({ npmRoot, savedAt: Date.now() });
|
|
178
|
+
const tmp = NPM_ROOT_CACHE_FILE + '.tmp';
|
|
179
|
+
fs.writeFileSync(tmp, payload, { mode: 0o600 });
|
|
180
|
+
fs.renameSync(tmp, NPM_ROOT_CACHE_FILE);
|
|
181
|
+
} catch { /* 캐시 저장 실패는 무시 */ }
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** 파일 캐시에서 npm root 읽기. TTL 초과 또는 손상 시 null 반환. */
|
|
185
|
+
function loadNpmRootCache() {
|
|
186
|
+
try {
|
|
187
|
+
const raw = fs.readFileSync(NPM_ROOT_CACHE_FILE, 'utf-8');
|
|
188
|
+
const { npmRoot, savedAt } = JSON.parse(raw);
|
|
189
|
+
if (typeof npmRoot === 'string' && typeof savedAt === 'number') {
|
|
190
|
+
if (Date.now() - savedAt < NPM_ROOT_CACHE_TTL_MS) return npmRoot;
|
|
176
191
|
}
|
|
192
|
+
} catch { /* 파일 없음 / 손상 / 권한 → null */ }
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** npm root -g 플랫폼 폴백 (execSync 실패 시 사용). */
|
|
197
|
+
function npmRootFallback() {
|
|
198
|
+
if (IS_WINDOWS) {
|
|
199
|
+
const appData = process.env.APPDATA || process.env.LOCALAPPDATA || '';
|
|
200
|
+
return path.join(appData, 'npm', 'node_modules');
|
|
201
|
+
}
|
|
202
|
+
const homeDir = os.homedir();
|
|
203
|
+
const candidates = [
|
|
204
|
+
'/usr/local/lib/node_modules',
|
|
205
|
+
path.join(homeDir, '.npm-global', 'lib', 'node_modules'),
|
|
206
|
+
path.join(homeDir, '.nvm', 'versions', 'node', process.version, 'lib', 'node_modules'),
|
|
207
|
+
];
|
|
208
|
+
return candidates.find(p => fs.existsSync(p)) || candidates[0];
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// L1 인-프로세스 캐시
|
|
212
|
+
let _globalNpmPath = null;
|
|
213
|
+
export function getGlobalNpmPath() {
|
|
214
|
+
// L1: 인-프로세스 캐시 히트
|
|
215
|
+
if (_globalNpmPath !== null) return _globalNpmPath;
|
|
216
|
+
|
|
217
|
+
// L2: 파일 캐시 히트
|
|
218
|
+
const cached = loadNpmRootCache();
|
|
219
|
+
if (cached !== null) {
|
|
220
|
+
_globalNpmPath = cached;
|
|
221
|
+
return _globalNpmPath;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// L3: execSync 실행 후 파일 캐시에 저장
|
|
225
|
+
try {
|
|
226
|
+
_globalNpmPath = execSync('npm root -g', { encoding: 'utf8' }).trim();
|
|
227
|
+
saveNpmRootCache(_globalNpmPath);
|
|
228
|
+
} catch {
|
|
229
|
+
_globalNpmPath = npmRootFallback();
|
|
230
|
+
// 폴백은 캐시에 저장하지 않음 — 다음 프로세스에서 재시도
|
|
177
231
|
}
|
|
178
232
|
return _globalNpmPath;
|
|
179
233
|
}
|
|
@@ -190,20 +244,32 @@ function hasRuntimeDeps(packageRoot) {
|
|
|
190
244
|
* 패키지 하위 경로의 file:// URL 반환 (크로스 플랫폼)
|
|
191
245
|
* 우선순위: 로컬 빌드 → ~/.vibe/ → 전역 npm
|
|
192
246
|
* 각 후보는 probeFile 존재 + 런타임 deps 해석 가능해야 채택 (dist-only 복사본 회피)
|
|
247
|
+
*
|
|
248
|
+
* 프로세스 내 메모이제이션: getToolsBaseUrl/getLibBaseUrl/getCliBaseUrl이 한 훅
|
|
249
|
+
* 프로세스에서 각각 호출되면 최악 6회 동기 stat이 반복되므로 결과를 캐싱한다.
|
|
193
250
|
*/
|
|
251
|
+
const _packageUrlCache = new Map();
|
|
194
252
|
function getPackageUrl(subpath, probeFile) {
|
|
253
|
+
const cacheKey = `${subpath}|${probeFile}`;
|
|
254
|
+
const cached = _packageUrlCache.get(cacheKey);
|
|
255
|
+
if (cached) return cached;
|
|
256
|
+
|
|
195
257
|
const localRoot = VIBE_PATH;
|
|
196
258
|
const vibeRoot = path.join(VIBE_HOME_DIR, 'node_modules', '@su-record', 'vibe');
|
|
197
259
|
const globalRoot = path.join(getGlobalNpmPath(), '@su-record', 'vibe');
|
|
198
260
|
|
|
199
261
|
const candidates = [localRoot, vibeRoot, globalRoot];
|
|
262
|
+
let result = null;
|
|
200
263
|
for (const root of candidates) {
|
|
201
264
|
const target = path.join(root, subpath);
|
|
202
265
|
if (fs.existsSync(path.join(target, probeFile)) && hasRuntimeDeps(root)) {
|
|
203
|
-
|
|
266
|
+
result = toFileUrl(target);
|
|
267
|
+
break;
|
|
204
268
|
}
|
|
205
269
|
}
|
|
206
|
-
|
|
270
|
+
if (result === null) result = toFileUrl(path.join(globalRoot, subpath));
|
|
271
|
+
_packageUrlCache.set(cacheKey, result);
|
|
272
|
+
return result;
|
|
207
273
|
}
|
|
208
274
|
|
|
209
275
|
export function getToolsBaseUrl() {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* verify-ledger CLI — vibe.verify 결과를 run-ledger에 기록.
|
|
4
|
+
*
|
|
5
|
+
* 사용법: node hooks/scripts/verify-ledger.js pass|fail
|
|
6
|
+
*
|
|
7
|
+
* vibe.verify/SKILL.md 에서 검증 완료 시 호출.
|
|
8
|
+
* stdout 출력은 에이전트가 Bash로 실행하는 컨텍스트이므로 허용.
|
|
9
|
+
* 항상 exit 0.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { recordVerify } from './lib/run-ledger.js';
|
|
13
|
+
|
|
14
|
+
const arg = process.argv[2];
|
|
15
|
+
const passed = arg === 'pass';
|
|
16
|
+
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
17
|
+
|
|
18
|
+
recordVerify(projectDir, passed);
|
|
19
|
+
|
|
20
|
+
const status = passed ? 'pass' : 'fail';
|
|
21
|
+
process.stdout.write(`[verify-ledger] recorded: verifyPassed=${passed} (${status})\n`);
|
|
22
|
+
process.exit(0);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@su-record/vibe",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "AI Coding Framework for Claude Code —
|
|
3
|
+
"version": "2.13.0",
|
|
4
|
+
"description": "AI Coding Framework for Claude Code — 60+ agents, 69 skills, multi-LLM orchestration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/cli/index.js",
|
|
7
7
|
"exports": {
|
|
@@ -3,8 +3,8 @@ name: arch-guard
|
|
|
3
3
|
user-invocable: false
|
|
4
4
|
invocation: [auto]
|
|
5
5
|
tier: core
|
|
6
|
-
description: "Generate
|
|
7
|
-
triggers: [arch guard, architecture test, layer test, boundary test, structural test, arch validation]
|
|
6
|
+
description: "Generate import-rule tests that mechanically enforce architecture layer constraints (e.g., 'UI must not import DB') — for new modules, layer refactors, or circular dependencies."
|
|
7
|
+
triggers: [arch guard, architecture test, layer test, boundary test, structural test, arch validation, layer enforcement, dependency rules, architectural boundaries, circular dependency]
|
|
8
8
|
priority: 60
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -3,8 +3,8 @@ name: characterization-test
|
|
|
3
3
|
user-invocable: false
|
|
4
4
|
invocation: [auto]
|
|
5
5
|
tier: core
|
|
6
|
-
description: "Lock existing behavior with characterization tests
|
|
7
|
-
triggers: [legacy, characterization test, lock behavior, regression prevention, before refactor, large file]
|
|
6
|
+
description: "Lock existing behavior with characterization tests BEFORE refactoring or modifying legacy/untested code (files >200 lines without tests)."
|
|
7
|
+
triggers: [legacy, characterization test, lock behavior, regression prevention, before refactor, large file, refactor, rewrite, modernize, clean up]
|
|
8
8
|
priority: 65
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -3,8 +3,8 @@ name: exec-plan
|
|
|
3
3
|
user-invocable: false
|
|
4
4
|
invocation: [auto, chain]
|
|
5
5
|
tier: core
|
|
6
|
-
description: "Convert
|
|
7
|
-
triggers: [exec plan, execution plan, autonomous plan, self-contained plan, long-running]
|
|
6
|
+
description: "Convert a SPEC (3+ phases or multi-file) into a self-contained execution plan — explicit file paths, interfaces, acceptance criteria — that agents can run autonomously."
|
|
7
|
+
triggers: [exec plan, execution plan, autonomous plan, self-contained plan, long-running, execute this spec, run this plan, parallel implementation]
|
|
8
8
|
priority: 70
|
|
9
9
|
chain-next: [commit-push-pr]
|
|
10
10
|
---
|