@su-record/vibe 2.12.5 → 2.14.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 +25 -16
- package/README.en.md +16 -14
- package/README.md +13 -11
- package/dist/cli/postinstall/constants.d.ts.map +1 -1
- package/dist/cli/postinstall/constants.js +1 -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/main.d.ts.map +1 -1
- package/dist/cli/postinstall/main.js +12 -2
- package/dist/cli/postinstall/main.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.d.ts.map +1 -1
- package/dist/cli/setup/ProjectSetup.js +6 -5
- package/dist/cli/setup/ProjectSetup.js.map +1 -1
- 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/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/index.d.ts +2 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +2 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/loop/index.d.ts +6 -0
- package/dist/tools/loop/index.d.ts.map +1 -0
- package/dist/tools/loop/index.js +5 -0
- package/dist/tools/loop/index.js.map +1 -0
- package/dist/tools/loop/validateLoopDefinition.d.ts +38 -0
- package/dist/tools/loop/validateLoopDefinition.d.ts.map +1 -0
- package/dist/tools/loop/validateLoopDefinition.js +224 -0
- package/dist/tools/loop/validateLoopDefinition.js.map +1 -0
- package/dist/tools/loop/validateLoopDefinition.test.d.ts +14 -0
- package/dist/tools/loop/validateLoopDefinition.test.d.ts.map +1 -0
- package/dist/tools/loop/validateLoopDefinition.test.js +229 -0
- package/dist/tools/loop/validateLoopDefinition.test.js.map +1 -0
- 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 +1 -0
- package/hooks/scripts/__tests__/.vibe/command-log.txt +60 -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__/keyword-detector.test.js +26 -18
- package/hooks/scripts/__tests__/loop-ledger.test.js +321 -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 +27 -1
- package/hooks/scripts/auto-format.js +85 -20
- package/hooks/scripts/auto-test.js +187 -37
- package/hooks/scripts/code-check.js +286 -90
- package/hooks/scripts/codex-hook-adapter.js +12 -1
- package/hooks/scripts/command-log.js +26 -16
- package/hooks/scripts/keyword-detector.js +22 -22
- package/hooks/scripts/lib/dispatcher.js +38 -0
- package/hooks/scripts/lib/hook-context.js +130 -0
- package/hooks/scripts/lib/loop-ledger.js +118 -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/loop-ledger.js +56 -0
- 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 +13 -1
- package/hooks/scripts/step-counter.js +100 -7
- package/hooks/scripts/stop-dispatcher.js +26 -0
- package/hooks/scripts/utils.js +63 -21
- package/hooks/scripts/verify-ledger.js +22 -0
- package/package.json +2 -2
- package/skills/spec/references/templates.md +11 -6
- package/skills/vibe/SKILL.md +40 -23
- package/skills/vibe.loop/SKILL.md +116 -0
- package/skills/vibe.run/SKILL.md +153 -1686
- 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 +173 -0
- package/skills/vibe.run/references/ultrawork-mode.md +151 -0
- package/skills/vibe.trace/SKILL.md +25 -38
- package/skills/vibe.verify/SKILL.md +15 -0
- package/vibe/rules/loop-contract.md +54 -0
- package/vibe/templates/loop-template.md +69 -0
- package/hooks/scripts/figma-guard.js +0 -219
|
@@ -1,13 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* Sentinel Guard — PreToolUse hook
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* 핵심 자율 기계 경로를 Write/Edit/Bash 로부터 보호한다.
|
|
5
|
+
* pre-tool-guard.js 이전에 실행된다.
|
|
6
|
+
*
|
|
7
|
+
* 보호 경로 결정 근거:
|
|
8
|
+
* src/infra/lib/autonomy/ 는 레포에 존재하지 않는 팬텀 경로였다.
|
|
9
|
+
* git log 와 src/ 전수 조사 결과, 자율 기계(evolution machinery)의 실제
|
|
10
|
+
* 위치는 src/infra/lib/evolution/ 이다. GuardAnalyzer·HookTraceReader 등이
|
|
11
|
+
* hook-traces.jsonl을 분석해 하네스 자기 개선 인사이트를 생성한다.
|
|
12
|
+
* 따라서 sentinel 대상을 이 실존 경로로 교체한다.
|
|
13
|
+
*
|
|
14
|
+
* 추가로 hooks/scripts/lib/ (디스패처 인프라)도 보호 — 이 디렉토리의
|
|
15
|
+
* hook-context.js / dispatcher.js 가 손상되면 모든 pre-tool 게이트가
|
|
16
|
+
* 무력화된다.
|
|
17
|
+
*
|
|
18
|
+
* exit 2 = deny 규약 (PreToolUse 차단).
|
|
6
19
|
*/
|
|
7
20
|
|
|
8
|
-
const
|
|
21
|
+
const SENTINEL_PREFIXES = [
|
|
22
|
+
'src/infra/lib/evolution/',
|
|
23
|
+
'hooks/scripts/lib/',
|
|
24
|
+
];
|
|
9
25
|
|
|
10
|
-
|
|
26
|
+
// 빠른 prefix 검사용 정규식 (backslash 포함)
|
|
27
|
+
const SENTINEL_PATH_RE =
|
|
28
|
+
/^(?:\.[\\/])?(?:src[\\/]infra[\\/]lib[\\/]evolution|hooks[\\/]scripts[\\/]lib)[\\/]/;
|
|
11
29
|
|
|
12
30
|
const DANGEROUS_BASH_RE =
|
|
13
31
|
/\b(rm\s+-rf|kill\s+-9|drop\s+table|truncate|shutdown|reboot|mkfs|dd\s+if=)\b/i;
|
|
@@ -23,7 +41,6 @@ function extractFilePath(toolName, input) {
|
|
|
23
41
|
return parsed.file_path || parsed.filePath || null;
|
|
24
42
|
}
|
|
25
43
|
} catch {
|
|
26
|
-
// Not JSON, try as plain string
|
|
27
44
|
return typeof input === 'string' ? input : null;
|
|
28
45
|
}
|
|
29
46
|
return null;
|
|
@@ -48,7 +65,8 @@ function extractBashCommand(input) {
|
|
|
48
65
|
function isSentinelPath(filePath) {
|
|
49
66
|
if (!filePath) return false;
|
|
50
67
|
const normalized = filePath.replace(/\\/g, '/').replace(/^\.\//, '');
|
|
51
|
-
return normalized.startsWith(
|
|
68
|
+
return SENTINEL_PREFIXES.some(p => normalized.startsWith(p)) ||
|
|
69
|
+
SENTINEL_PATH_RE.test(filePath);
|
|
52
70
|
}
|
|
53
71
|
|
|
54
72
|
/**
|
|
@@ -76,8 +94,7 @@ function guard(toolName, toolInput) {
|
|
|
76
94
|
};
|
|
77
95
|
}
|
|
78
96
|
if (command && DANGEROUS_BASH_RE.test(command)) {
|
|
79
|
-
|
|
80
|
-
if (command.includes(SENTINEL_PATH_PREFIX) || command.includes('src/infra/lib/autonomy')) {
|
|
97
|
+
if (SENTINEL_PREFIXES.some(p => command.includes(p))) {
|
|
81
98
|
return {
|
|
82
99
|
decision: 'block',
|
|
83
100
|
reason: `Dangerous command targeting sentinel path: ${command}`,
|
|
@@ -86,45 +103,28 @@ function guard(toolName, toolInput) {
|
|
|
86
103
|
}
|
|
87
104
|
}
|
|
88
105
|
|
|
89
|
-
// Allow — return undefined for normal flow
|
|
90
106
|
return undefined;
|
|
91
107
|
}
|
|
92
108
|
|
|
93
|
-
import
|
|
109
|
+
import { logHookDecision } from './utils.js';
|
|
110
|
+
import { buildCliCtx, isDirectRun } from './lib/hook-context.js';
|
|
94
111
|
|
|
95
112
|
/**
|
|
96
|
-
*
|
|
113
|
+
* in-process 진입점 — 디스패처가 ctx를 전달해 직접 호출.
|
|
114
|
+
* @param {{ toolName: string, toolInput: string }} ctx
|
|
115
|
+
* @returns {Promise<number>} exit code (2 = deny 규약, 0 = allow)
|
|
97
116
|
*/
|
|
98
|
-
function
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
} catch { /* 폴백 */ }
|
|
108
|
-
return null;
|
|
117
|
+
export async function run(ctx) {
|
|
118
|
+
const result = guard(ctx.toolName, ctx.toolInput);
|
|
119
|
+
if (result) {
|
|
120
|
+
logHookDecision('sentinel-guard', ctx.toolName, 'block', result.reason);
|
|
121
|
+
console.log(JSON.stringify(result));
|
|
122
|
+
return 2;
|
|
123
|
+
}
|
|
124
|
+
return 0;
|
|
109
125
|
}
|
|
110
126
|
|
|
111
|
-
//
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const toolInput = stdinPayload?.tool_input
|
|
115
|
-
? (typeof stdinPayload.tool_input === 'string'
|
|
116
|
-
? stdinPayload.tool_input
|
|
117
|
-
: JSON.stringify(stdinPayload.tool_input))
|
|
118
|
-
: (process.argv[3] || process.env.TOOL_INPUT || '');
|
|
119
|
-
|
|
120
|
-
import { logHookDecision } from './utils.js';
|
|
121
|
-
|
|
122
|
-
const result = guard(toolName, toolInput);
|
|
123
|
-
|
|
124
|
-
if (result) {
|
|
125
|
-
logHookDecision('sentinel-guard', toolName, 'block', result.reason);
|
|
126
|
-
console.log(JSON.stringify(result));
|
|
127
|
-
process.exit(2); // deny 규약
|
|
127
|
+
// standalone CLI 모드 (antigravity-hooks.json / 기존 테스트): stdin JSON 우선, argv 폴백
|
|
128
|
+
if (isDirectRun(import.meta.url)) {
|
|
129
|
+
process.exit(await run(buildCliCtx()));
|
|
128
130
|
}
|
|
129
|
-
|
|
130
|
-
process.exit(0);
|
|
@@ -126,7 +126,7 @@ async function main() {
|
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
// Scope sync —
|
|
129
|
+
// Scope sync — 기본 ON. scopeGuard.enabled=false 로 명시적 opt-out 가능.
|
|
130
130
|
try {
|
|
131
131
|
const { syncScopeFile, isScopeGuardEnabled } = await import('./lib/scope-from-spec.js');
|
|
132
132
|
if (isScopeGuardEnabled(PROJECT_DIR)) {
|
|
@@ -135,6 +135,18 @@ async function main() {
|
|
|
135
135
|
console.log(`\n🚧 Scope ${result.action} from active SPECs (${path.relative(PROJECT_DIR, path.join(projectVibeRoot(PROJECT_DIR), 'scope.json'))})`);
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
|
+
// scope 상태 1줄 알림 — 컨텍스트에 주입되어 모델이 현재 scope 범위를 인지한다
|
|
139
|
+
const scopeJsonPath = path.join(projectVibeRoot(PROJECT_DIR), 'scope.json');
|
|
140
|
+
if (fs.existsSync(scopeJsonPath)) {
|
|
141
|
+
try {
|
|
142
|
+
const scopeData = JSON.parse(fs.readFileSync(scopeJsonPath, 'utf-8'));
|
|
143
|
+
const featureLabel = scopeData.reason || path.basename(scopeJsonPath);
|
|
144
|
+
const modeLabel = scopeData.mode || 'warn';
|
|
145
|
+
console.log(`\nscope-guard: ${featureLabel} (${modeLabel})`);
|
|
146
|
+
} catch { /* ignore */ }
|
|
147
|
+
} else {
|
|
148
|
+
console.log('\nscope-guard: no active scope (no active SPEC or no derivable paths) — out-of-scope edits are not monitored');
|
|
149
|
+
}
|
|
138
150
|
} catch { /* scope sync is best-effort */ }
|
|
139
151
|
|
|
140
152
|
// Autonomy status summary
|
|
@@ -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
|
@@ -163,29 +163,71 @@ function toFileUrl(fsPath) {
|
|
|
163
163
|
return url.endsWith('/') ? url : url + '/';
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
-
// 전역 npm 경로 캐시
|
|
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;
|
|
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 인-프로세스 캐시
|
|
167
212
|
let _globalNpmPath = null;
|
|
168
213
|
export function getGlobalNpmPath() {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
_globalNpmPath = defaultPaths.find(p => fs.existsSync(p)) || defaultPaths[0];
|
|
187
|
-
}
|
|
188
|
-
}
|
|
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
|
+
// 폴백은 캐시에 저장하지 않음 — 다음 프로세스에서 재시도
|
|
189
231
|
}
|
|
190
232
|
return _globalNpmPath;
|
|
191
233
|
}
|
|
@@ -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.14.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": {
|
|
@@ -86,19 +86,24 @@ Define AI role and expertise for implementation
|
|
|
86
86
|
|
|
87
87
|
## Task
|
|
88
88
|
<task>
|
|
89
|
+
<!-- ⚠️ MANDATORY: Every functional requirement item MUST carry a REQ-ID so the RTM can extract it.
|
|
90
|
+
Format: REQ-{feature-name}-NNN (feature = SPEC file basename, NNN = 3-digit zero-padded number)
|
|
91
|
+
Example: REQ-auth-login-001
|
|
92
|
+
Regex the RTM uses: /\b(REQ-[a-z0-9-]+-\d{3})[:\s]/gi — use uppercase REQ-, lowercase feature segment, exactly 3 digits.
|
|
93
|
+
status === 'empty' means the RTM gate MUST be treated as failed/not-applicable, never as 100% pass. -->
|
|
89
94
|
### Phase 1: {phase-name}
|
|
90
|
-
1. [ ] {specific task}
|
|
95
|
+
1. [ ] REQ-{feature-name}-001: {specific task}
|
|
91
96
|
- File: `path/to/file`
|
|
92
97
|
- Verify: `command`
|
|
93
|
-
2. [ ] {specific task}
|
|
98
|
+
2. [ ] REQ-{feature-name}-002: {specific task}
|
|
94
99
|
|
|
95
100
|
### Phase 2: {phase-name}
|
|
96
|
-
1. [ ] {specific task}
|
|
97
|
-
2. [ ] {specific task}
|
|
101
|
+
1. [ ] REQ-{feature-name}-003: {specific task}
|
|
102
|
+
2. [ ] REQ-{feature-name}-004: {specific task}
|
|
98
103
|
|
|
99
104
|
### Phase 3: Testing and Verification
|
|
100
|
-
1. [ ] Unit Tests
|
|
101
|
-
2. [ ] Integration Tests
|
|
105
|
+
1. [ ] REQ-{feature-name}-005: Unit Tests
|
|
106
|
+
2. [ ] REQ-{feature-name}-006: Integration Tests
|
|
102
107
|
</task>
|
|
103
108
|
|
|
104
109
|
## Constraints
|
package/skills/vibe/SKILL.md
CHANGED
|
@@ -7,7 +7,9 @@ user-invocable: true
|
|
|
7
7
|
|
|
8
8
|
# /vibe
|
|
9
9
|
|
|
10
|
-
**vibe의 메인 진입점.** 사용자는 "무엇을 만들지 / 무엇을 할지"만 자연어로 말한다. vibe가 의도를 분석해 어떤 `/vibe.*` 스킬을 어떤 순서로 호출할지 파이프라인을 동적으로 설계하고, **1회 승인 후 실행**한다.
|
|
10
|
+
**vibe의 메인 진입점.** 사용자는 "무엇을 만들지 / 무엇을 할지"만 자연어로 말한다. vibe가 의도를 분석해 어떤 `/vibe.*` 스킬을 어떤 순서로 호출할지 파이프라인을 동적으로 설계하고, **SPEC 확정 1회 승인 후 게이트 통과까지 루프 실행**한다.
|
|
11
|
+
|
|
12
|
+
> **루프 시맨틱 SSOT**: `vibe/rules/loop-contract.md` — 이 문서가 ANCHOR→ACT→JUDGE→RECORD 계약과 모든 파라미터(exit, stuck, max_iterations, automationLevel)를 정의한다.
|
|
11
13
|
|
|
12
14
|
## Usage
|
|
13
15
|
|
|
@@ -17,7 +19,8 @@ user-invocable: true
|
|
|
17
19
|
/vibe "로그인 회귀 테스트 다시 돌려서 통과시켜줘"
|
|
18
20
|
/vibe "이 SPEC 리뷰만 한번 봐줘" + 📎 .vibe/specs/login.md
|
|
19
21
|
/vibe "PRD 문서 기반으로 진행" + 📎 docs/prd.pdf
|
|
20
|
-
/vibe "..."
|
|
22
|
+
/vibe "..." --interactive # 단계별 확인 모드 (회전마다 승인)
|
|
23
|
+
/vibe "..." --max-iter 1 # 1회 시도만
|
|
21
24
|
```
|
|
22
25
|
|
|
23
26
|
## Philosophy
|
|
@@ -29,7 +32,7 @@ user-invocable: true
|
|
|
29
32
|
- **무제한 라우팅**: 라우팅 표는 빠른 경로일 뿐 닫힌 화이트리스트가 아니다. 설치된 모든 `vibe.*` 스킬이 라우팅 후보이며, 표에 없는 요구사항도 description 기반 의미 매칭으로 처리한다 (Catch-all).
|
|
30
33
|
- **하네스 정규화 (추론 앞단)**: vibe는 CC(추론)·Codex(직역) 어느 하네스의 암묵적 동작에도 의존하지 않는다. `/vibe`가 모호한 NL을 **명시적·직역 가능한 지시로 먼저 전개**하고, 하위 skill은 모호한 입력을 받지 않는다. 이로써 모든 하네스에서 동일 결과 + CC급 편의를 제공한다. 전문: `vibe/rules/principles/dual-harness-doctrine.md`.
|
|
31
34
|
- **Smart Resume**: `.vibe/{interviews,plans,specs,features}/` 감지하여 "이어서 진행?" 자동 제안.
|
|
32
|
-
- **1회 승인
|
|
35
|
+
- **SPEC 확정 1회 승인 → 루프**: SPEC(Done의 정의 + 수용 기준)을 사용자와 확정하는 것이 유일한 의무적 사람 개입. 승인 후에는 `vibe/rules/loop-contract.md`의 ANCHOR→ACT→JUDGE→RECORD 루프를 게이트 전부 통과할 때까지 자동 반복한다. 루프 종료 = 게이트 통과 또는 stuck 또는 max_iterations 도달.
|
|
33
36
|
- **위임자 역할**: `/vibe` 본인은 코드를 직접 쓰지 않는다. 라우팅·설계·실행 위임만 한다.
|
|
34
37
|
|
|
35
38
|
## Process
|
|
@@ -45,7 +48,7 @@ user-invocable: true
|
|
|
45
48
|
| 자연어 텍스트 | 의도 추출 (요구 종류, 도메인, 키워드) |
|
|
46
49
|
| 첨부 파일 (`📎 path`) | 확장자로 분류 (md/feature/pdf/png/jpg/...) |
|
|
47
50
|
| URL | 도메인으로 분류 (figma.com / github.com / 기타) |
|
|
48
|
-
|
|
|
51
|
+
| 옵션 플래그 | `--interactive`, `--max-iter N` 등 루프 파라미터 추출; `ultrawork`/`ralph`/`quick` 등 deprecated 별칭은 loop-contract 매핑표로 변환 |
|
|
49
52
|
|
|
50
53
|
### Phase 1: Intent 분류
|
|
51
54
|
|
|
@@ -113,7 +116,7 @@ user-invocable: true
|
|
|
113
116
|
- **research 명시**: 조사가 필요하면 파이프라인에 명시적 탐색 단계를 넣는다. planning mode 같은 하네스 스위치에 의존하지 않는다.
|
|
114
117
|
- **도메인 지식 흡수**: 사용자가 준 라이브러리·함수·파일 위치를 SPEC 입력으로 전달한다.
|
|
115
118
|
|
|
116
|
-
이어서 분류된 의도 + resume 상태 +
|
|
119
|
+
이어서 분류된 의도 + resume 상태 + 루프 파라미터(automationLevel, --max-iter 등)를 종합해 실행 계획 작성:
|
|
117
120
|
|
|
118
121
|
```
|
|
119
122
|
📋 Pipeline Plan
|
|
@@ -143,9 +146,10 @@ Phase 6: /vibe.trace → RTM 출력
|
|
|
143
146
|
```
|
|
144
147
|
|
|
145
148
|
**Skip 조건:**
|
|
146
|
-
-
|
|
147
|
-
- `
|
|
148
|
-
|
|
149
|
+
- `automationLevel: autonomous` 설정 (`.vibe/config.json`) 또는 `ultrawork`/`ulw` 별칭 → SPEC 승인 게이트만 skip, 루프는 정상 동작
|
|
150
|
+
- `--max-iter 1` 또는 `quick` 별칭 → 1회 시도 후 종료
|
|
151
|
+
|
|
152
|
+
> 참고: Phase 4의 승인 게이트는 **파이프라인 설계 승인**이다. SPEC 확정 시점의 승인(loop-contract의 "유일한 의무 개입")과 구별된다.
|
|
149
153
|
|
|
150
154
|
### Phase 5: 체인 실행
|
|
151
155
|
|
|
@@ -161,10 +165,11 @@ Phase 6: /vibe.trace → RTM 출력
|
|
|
161
165
|
...
|
|
162
166
|
```
|
|
163
167
|
|
|
164
|
-
각 phase 종료
|
|
165
|
-
-
|
|
166
|
-
-
|
|
167
|
-
-
|
|
168
|
+
각 phase 종료 후 JUDGE 단계:
|
|
169
|
+
- 게이트 통과 (P1=0 ∧ verifyPassed) → 루프 종료, Phase 6 보고
|
|
170
|
+
- 게이트 미통과 → RECORD(run-ledger + loop-history.jsonl) 후 다음 ANCHOR로
|
|
171
|
+
- stuck(연속 2회 동일 findings 해시) → `automationLevel: confirm`이면 사용자 질문; `autonomous`이면 TODO 기록 후 루프 종료
|
|
172
|
+
- max_iterations(기본 10) 도달 → 잔여를 인박스로 이월
|
|
168
173
|
|
|
169
174
|
### Phase 6: 종료 보고
|
|
170
175
|
|
|
@@ -228,31 +233,43 @@ Phase 6: /vibe.trace → RTM 출력
|
|
|
228
233
|
1. /vibe.review (단일 phase)
|
|
229
234
|
```
|
|
230
235
|
|
|
231
|
-
### 예시 4: ultrawork
|
|
236
|
+
### 예시 4: automationLevel autonomous (ultrawork 별칭)
|
|
232
237
|
|
|
233
238
|
```
|
|
234
239
|
입력: /vibe "결제 API 만들어줘" ultrawork
|
|
235
240
|
|
|
236
|
-
→
|
|
237
|
-
→
|
|
238
|
-
→
|
|
241
|
+
→ automationLevel: autonomous 설정 → SPEC 승인 게이트 skip
|
|
242
|
+
→ 병렬 ACT 활성화
|
|
243
|
+
→ ANCHOR→ACT→JUDGE→RECORD 루프 (게이트 통과까지 자동 반복)
|
|
244
|
+
→ stuck 시 TODO 기록 후 루프 종료 (사용자 질문 없음)
|
|
239
245
|
```
|
|
240
246
|
|
|
241
247
|
## ⛔ 하지 않는 것
|
|
242
248
|
|
|
243
249
|
- 직접 코드 작성 / 파일 수정 (위임만)
|
|
244
|
-
-
|
|
250
|
+
- SPEC 확정 없이 루프 진입 (SPEC = Done의 정의, 유일한 의무 승인 지점)
|
|
245
251
|
- 사용자가 명시한 phase 를 임의로 추가/제거
|
|
246
252
|
- Resume 상태를 무시하고 처음부터 다시 시작
|
|
247
253
|
|
|
248
|
-
##
|
|
254
|
+
## 루프 옵션
|
|
255
|
+
|
|
256
|
+
| 옵션 | 효과 |
|
|
257
|
+
|---|---|
|
|
258
|
+
| `--interactive` | 매 회전마다 사람 승인 (과거 기본값) |
|
|
259
|
+
| `--max-iter N` | 회전 상한 N 으로 설정 (N=1이면 1회 시도) |
|
|
260
|
+
| `automationLevel: autonomous` (`.vibe/config.json`) | stuck/SPEC 게이트 외 모든 확인 skip |
|
|
261
|
+
|
|
262
|
+
## Deprecated 별칭 (하위 호환 — 새 코드에서 사용하지 말 것)
|
|
263
|
+
|
|
264
|
+
> 아래 키워드는 계속 동작하지만 loop-contract 파라미터로 환원된다. 새 문서나 예시에서 가르치지 않는다. 전문: `vibe/rules/loop-contract.md` Deprecated 별칭 매핑표.
|
|
249
265
|
|
|
250
|
-
|
|
|
266
|
+
| 별칭 | 환원 |
|
|
251
267
|
|---|---|
|
|
252
|
-
| `ultrawork` / `ulw` |
|
|
253
|
-
| `ralph` | 100
|
|
254
|
-
| `quick` |
|
|
255
|
-
| `verify` |
|
|
268
|
+
| `ultrawork` / `ulw` | `automationLevel: autonomous` + 병렬 ACT |
|
|
269
|
+
| `ralph` | 기본 동작과 동일 (no-op); exit=coverage-100으로 해석 가능 |
|
|
270
|
+
| `quick` | `--max-iter 1` + 최소 JUDGE |
|
|
271
|
+
| `verify` | 기본 동작과 동일 (no-op) — JUDGE는 항상 결정론 검증 |
|
|
272
|
+
| `ralplan` | 동일 loop-contract를 계획 단계에 적용 |
|
|
256
273
|
|
|
257
274
|
## Output
|
|
258
275
|
|