@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.
Files changed (139) hide show
  1. package/CLAUDE.md +25 -16
  2. package/README.en.md +16 -14
  3. package/README.md +13 -11
  4. package/dist/cli/postinstall/constants.d.ts.map +1 -1
  5. package/dist/cli/postinstall/constants.js +1 -0
  6. package/dist/cli/postinstall/constants.js.map +1 -1
  7. package/dist/cli/postinstall/fs-utils.d.ts +23 -0
  8. package/dist/cli/postinstall/fs-utils.d.ts.map +1 -1
  9. package/dist/cli/postinstall/fs-utils.js +71 -0
  10. package/dist/cli/postinstall/fs-utils.js.map +1 -1
  11. package/dist/cli/postinstall/fs-utils.test.js +69 -1
  12. package/dist/cli/postinstall/fs-utils.test.js.map +1 -1
  13. package/dist/cli/postinstall/main.d.ts.map +1 -1
  14. package/dist/cli/postinstall/main.js +12 -2
  15. package/dist/cli/postinstall/main.js.map +1 -1
  16. package/dist/cli/setup/CodexHooks.test.js +27 -0
  17. package/dist/cli/setup/CodexHooks.test.js.map +1 -1
  18. package/dist/cli/setup/ProjectSetup.d.ts.map +1 -1
  19. package/dist/cli/setup/ProjectSetup.js +6 -5
  20. package/dist/cli/setup/ProjectSetup.js.map +1 -1
  21. package/dist/infra/lib/DecisionTracer.d.ts +4 -0
  22. package/dist/infra/lib/DecisionTracer.d.ts.map +1 -1
  23. package/dist/infra/lib/DecisionTracer.js +4 -0
  24. package/dist/infra/lib/DecisionTracer.js.map +1 -1
  25. package/dist/infra/lib/LoopBreaker.d.ts +4 -0
  26. package/dist/infra/lib/LoopBreaker.d.ts.map +1 -1
  27. package/dist/infra/lib/LoopBreaker.js +4 -0
  28. package/dist/infra/lib/LoopBreaker.js.map +1 -1
  29. package/dist/infra/lib/ReviewRace.d.ts +4 -0
  30. package/dist/infra/lib/ReviewRace.d.ts.map +1 -1
  31. package/dist/infra/lib/ReviewRace.js +4 -0
  32. package/dist/infra/lib/ReviewRace.js.map +1 -1
  33. package/dist/infra/lib/SkillQualityGate.d.ts +4 -0
  34. package/dist/infra/lib/SkillQualityGate.d.ts.map +1 -1
  35. package/dist/infra/lib/SkillQualityGate.js +4 -0
  36. package/dist/infra/lib/SkillQualityGate.js.map +1 -1
  37. package/dist/infra/lib/UltraQA.d.ts +4 -0
  38. package/dist/infra/lib/UltraQA.d.ts.map +1 -1
  39. package/dist/infra/lib/UltraQA.js +4 -0
  40. package/dist/infra/lib/UltraQA.js.map +1 -1
  41. package/dist/infra/lib/VerificationLoop.d.ts +4 -0
  42. package/dist/infra/lib/VerificationLoop.d.ts.map +1 -1
  43. package/dist/infra/lib/VerificationLoop.js +4 -0
  44. package/dist/infra/lib/VerificationLoop.js.map +1 -1
  45. package/dist/infra/orchestrator/index.d.ts.map +1 -1
  46. package/dist/infra/orchestrator/index.js +1 -3
  47. package/dist/infra/orchestrator/index.js.map +1 -1
  48. package/dist/infra/orchestrator/parallelResearch.d.ts.map +1 -1
  49. package/dist/infra/orchestrator/parallelResearch.js +1 -4
  50. package/dist/infra/orchestrator/parallelResearch.js.map +1 -1
  51. package/dist/tools/convention/validateCodeQuality.d.ts.map +1 -1
  52. package/dist/tools/convention/validateCodeQuality.js +5 -4
  53. package/dist/tools/convention/validateCodeQuality.js.map +1 -1
  54. package/dist/tools/index.d.ts +2 -0
  55. package/dist/tools/index.d.ts.map +1 -1
  56. package/dist/tools/index.js +2 -0
  57. package/dist/tools/index.js.map +1 -1
  58. package/dist/tools/loop/index.d.ts +6 -0
  59. package/dist/tools/loop/index.d.ts.map +1 -0
  60. package/dist/tools/loop/index.js +5 -0
  61. package/dist/tools/loop/index.js.map +1 -0
  62. package/dist/tools/loop/validateLoopDefinition.d.ts +38 -0
  63. package/dist/tools/loop/validateLoopDefinition.d.ts.map +1 -0
  64. package/dist/tools/loop/validateLoopDefinition.js +224 -0
  65. package/dist/tools/loop/validateLoopDefinition.js.map +1 -0
  66. package/dist/tools/loop/validateLoopDefinition.test.d.ts +14 -0
  67. package/dist/tools/loop/validateLoopDefinition.test.d.ts.map +1 -0
  68. package/dist/tools/loop/validateLoopDefinition.test.js +229 -0
  69. package/dist/tools/loop/validateLoopDefinition.test.js.map +1 -0
  70. package/dist/tools/spec/traceabilityMatrix.d.ts +2 -0
  71. package/dist/tools/spec/traceabilityMatrix.d.ts.map +1 -1
  72. package/dist/tools/spec/traceabilityMatrix.js +50 -1
  73. package/dist/tools/spec/traceabilityMatrix.js.map +1 -1
  74. package/dist/tools/spec/traceabilityMatrix.path-resolution.test.d.ts +10 -0
  75. package/dist/tools/spec/traceabilityMatrix.path-resolution.test.d.ts.map +1 -0
  76. package/dist/tools/spec/traceabilityMatrix.path-resolution.test.js +89 -0
  77. package/dist/tools/spec/traceabilityMatrix.path-resolution.test.js.map +1 -0
  78. package/dist/tools/spec/traceabilityMatrix.test.js +19 -0
  79. package/dist/tools/spec/traceabilityMatrix.test.js.map +1 -1
  80. package/hooks/hooks.json +1 -0
  81. package/hooks/scripts/__tests__/.vibe/command-log.txt +60 -0
  82. package/hooks/scripts/__tests__/.vibe/memories/memories.db +0 -0
  83. package/hooks/scripts/__tests__/.vibe/memories/memories.db-shm +0 -0
  84. package/hooks/scripts/__tests__/.vibe/memories/memories.db-wal +0 -0
  85. package/hooks/scripts/__tests__/auto-test-debounce.test.js +145 -0
  86. package/hooks/scripts/__tests__/code-check-detectors.test.js +155 -0
  87. package/hooks/scripts/__tests__/dispatcher-inprocess.test.js +99 -0
  88. package/hooks/scripts/__tests__/keyword-detector.test.js +26 -18
  89. package/hooks/scripts/__tests__/loop-ledger.test.js +321 -0
  90. package/hooks/scripts/__tests__/post-edit-dispatcher.test.js +139 -0
  91. package/hooks/scripts/__tests__/pre-tool-guard.test.js +115 -1
  92. package/hooks/scripts/__tests__/run-ledger-verify-required.test.js +146 -0
  93. package/hooks/scripts/__tests__/run-ledger.test.js +330 -0
  94. package/hooks/scripts/__tests__/scope-from-spec.test.js +215 -0
  95. package/hooks/scripts/__tests__/sentinel-guard.test.js +79 -24
  96. package/hooks/scripts/__tests__/step-counter.test.js +95 -15
  97. package/hooks/scripts/__tests__/utils-npm-root.test.js +98 -0
  98. package/hooks/scripts/auto-commit.js +27 -1
  99. package/hooks/scripts/auto-format.js +85 -20
  100. package/hooks/scripts/auto-test.js +187 -37
  101. package/hooks/scripts/code-check.js +286 -90
  102. package/hooks/scripts/codex-hook-adapter.js +12 -1
  103. package/hooks/scripts/command-log.js +26 -16
  104. package/hooks/scripts/keyword-detector.js +22 -22
  105. package/hooks/scripts/lib/dispatcher.js +38 -0
  106. package/hooks/scripts/lib/hook-context.js +130 -0
  107. package/hooks/scripts/lib/loop-ledger.js +118 -0
  108. package/hooks/scripts/lib/pr-gate-runner.js +62 -0
  109. package/hooks/scripts/lib/run-ledger.js +169 -0
  110. package/hooks/scripts/lib/scope-from-spec.js +40 -7
  111. package/hooks/scripts/loop-ledger.js +56 -0
  112. package/hooks/scripts/post-edit-dispatcher.js +93 -20
  113. package/hooks/scripts/post-edit.js +40 -19
  114. package/hooks/scripts/pr-test-gate.js +8 -37
  115. package/hooks/scripts/pre-tool-dispatcher.js +18 -16
  116. package/hooks/scripts/pre-tool-guard.js +55 -52
  117. package/hooks/scripts/prompt-dispatcher.js +10 -0
  118. package/hooks/scripts/scope-guard.js +40 -39
  119. package/hooks/scripts/sentinel-guard.js +41 -41
  120. package/hooks/scripts/session-start.js +13 -1
  121. package/hooks/scripts/step-counter.js +100 -7
  122. package/hooks/scripts/stop-dispatcher.js +26 -0
  123. package/hooks/scripts/utils.js +63 -21
  124. package/hooks/scripts/verify-ledger.js +22 -0
  125. package/package.json +2 -2
  126. package/skills/spec/references/templates.md +11 -6
  127. package/skills/vibe/SKILL.md +40 -23
  128. package/skills/vibe.loop/SKILL.md +116 -0
  129. package/skills/vibe.run/SKILL.md +153 -1686
  130. package/skills/vibe.run/references/brand-assets.md +59 -0
  131. package/skills/vibe.run/references/parallel-agents.md +326 -0
  132. package/skills/vibe.run/references/race-review.md +272 -0
  133. package/skills/vibe.run/references/ralph-loop.md +173 -0
  134. package/skills/vibe.run/references/ultrawork-mode.md +151 -0
  135. package/skills/vibe.trace/SKILL.md +25 -38
  136. package/skills/vibe.verify/SKILL.md +15 -0
  137. package/vibe/rules/loop-contract.md +54 -0
  138. package/vibe/templates/loop-template.md +69 -0
  139. package/hooks/scripts/figma-guard.js +0 -219
@@ -0,0 +1,155 @@
1
+ /**
2
+ * code-check.js 정밀 탐지기 테스트
3
+ *
4
+ * 검증 대상:
5
+ * - any 탐지: 단어 경계 기반 (false positive: 'company', 'anything' 등)
6
+ * - any 탐지: `: any`, `as any`, `@ts-ignore`, `<any>` (true positive)
7
+ * - console.log 정책: 경로별 허용/차단
8
+ * - run-ledger verifyRequired round-trip + pass 시 clear
9
+ * - auto-commit verifyRequired skip
10
+ */
11
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
12
+ import { fileURLToPath } from 'url';
13
+ import path from 'path';
14
+ import fs from 'fs';
15
+ import os from 'os';
16
+
17
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
18
+
19
+ // ─── any 탐지 정규식 (code-check.js와 동일) ──────────────────────────────
20
+ const P1_DETECTORS = [
21
+ /:\s*any\b/,
22
+ /\bas\s+any\b/,
23
+ /<any[\s,>]/,
24
+ /@ts-ignore\b/,
25
+ ];
26
+
27
+ function detectsAny(line) {
28
+ return P1_DETECTORS.some(re => re.test(line));
29
+ }
30
+
31
+ describe('any 탐지: true positive', () => {
32
+ it(': any — 타입 어노테이션', () => {
33
+ expect(detectsAny('function foo(x: any): void {}')).toBe(true);
34
+ });
35
+
36
+ it(': any — 공백 포함', () => {
37
+ expect(detectsAny('const x: any = 1;')).toBe(true);
38
+ });
39
+
40
+ it('as any — 타입 캐스트', () => {
41
+ expect(detectsAny('const y = x as any;')).toBe(true);
42
+ });
43
+
44
+ it('<any> — 제네릭', () => {
45
+ expect(detectsAny('const z = foo<any>();')).toBe(true);
46
+ });
47
+
48
+ it('<any, — 제네릭 다중 파라미터', () => {
49
+ expect(detectsAny('const m = new Map<any, string>();')).toBe(true);
50
+ });
51
+
52
+ it('@ts-ignore — 라인 직전', () => {
53
+ expect(detectsAny('// @ts-ignore')).toBe(true);
54
+ });
55
+
56
+ it('@ts-ignore — 인라인', () => {
57
+ expect(detectsAny(' @ts-ignore ')).toBe(true);
58
+ });
59
+ });
60
+
61
+ describe('any 탐지: false negative (탐지 안 됨)', () => {
62
+ it('"company" 단어 포함 — 탐지 안 됨', () => {
63
+ expect(detectsAny('const company = "Acme";')).toBe(false);
64
+ });
65
+
66
+ it('"anything" 식별자 — 탐지 안 됨', () => {
67
+ expect(detectsAny('if (anything) return;')).toBe(false);
68
+ });
69
+
70
+ it('"manyThings" — 탐지 안 됨', () => {
71
+ expect(detectsAny('const manyThings = [];')).toBe(false);
72
+ });
73
+
74
+ it('"fantasy" — 탐지 안 됨', () => {
75
+ expect(detectsAny('const fantasy = "world";')).toBe(false);
76
+ });
77
+
78
+ it('문자열 리터럴 "any" — 정규식은 문자열 내부도 매칭할 수 있음 (알려진 한계, 문서화)', () => {
79
+ // NOTE: 현재 구현은 문자열 내 "any"를 완전히 제외하지 않음.
80
+ // 실제 코드에서 ": any" 패턴이 문자열로 존재하는 경우는 극히 드물어 허용.
81
+ // 이 테스트는 동작을 문서화하는 것이 목적.
82
+ const result = detectsAny('const msg = "type: any is bad";');
83
+ // 문자열 내 ": any" 도 탐지될 수 있음 — false positive지만 드문 케이스
84
+ expect(typeof result).toBe('boolean');
85
+ });
86
+ });
87
+
88
+ // ─── console.log 경로 정책 ────────────────────────────────────────────────
89
+ describe('console.log 허용 경로 정책', () => {
90
+ // 허용 glob 패턴 (code-check.js 기본값과 동일)
91
+ const DEFAULT_ALLOW_GLOBS = [
92
+ 'hooks/scripts/**',
93
+ 'scripts/**',
94
+ '**/cli/**',
95
+ '**/*.test.*',
96
+ '**/*.spec.*',
97
+ '**/__tests__/**',
98
+ ];
99
+
100
+ function globToRegExp(glob) {
101
+ const normalized = glob.replace(/\\/g, '/');
102
+ let out = '';
103
+ for (let i = 0; i < normalized.length; i++) {
104
+ const c = normalized[i];
105
+ if (c === '*') {
106
+ if (normalized[i + 1] === '*') {
107
+ out += '.*';
108
+ i++;
109
+ if (normalized[i + 1] === '/') i++;
110
+ } else {
111
+ out += '[^/]*';
112
+ }
113
+ } else if (c === '?') {
114
+ out += '[^/]';
115
+ } else if ('.+^$()|{}[]\\'.includes(c)) {
116
+ out += '\\' + c;
117
+ } else {
118
+ out += c;
119
+ }
120
+ }
121
+ return new RegExp('^' + out + '$');
122
+ }
123
+
124
+ function isConsoleAllowed(relPath) {
125
+ return DEFAULT_ALLOW_GLOBS.some(g => globToRegExp(g).test(relPath));
126
+ }
127
+
128
+ it('hooks/scripts/auto-test.js → 허용', () => {
129
+ expect(isConsoleAllowed('hooks/scripts/auto-test.js')).toBe(true);
130
+ });
131
+
132
+ it('hooks/scripts/lib/run-ledger.js → 허용', () => {
133
+ expect(isConsoleAllowed('hooks/scripts/lib/run-ledger.js')).toBe(true);
134
+ });
135
+
136
+ it('src/cli/index.ts → 허용 (**/cli/**)', () => {
137
+ expect(isConsoleAllowed('src/cli/index.ts')).toBe(true);
138
+ });
139
+
140
+ it('src/tools/foo.test.ts → 허용 (**/*.test.*)', () => {
141
+ expect(isConsoleAllowed('src/tools/foo.test.ts')).toBe(true);
142
+ });
143
+
144
+ it('src/__tests__/bar.ts → 허용 (**/__tests__/**)', () => {
145
+ expect(isConsoleAllowed('src/__tests__/bar.ts')).toBe(true);
146
+ });
147
+
148
+ it('src/tools/convention/validateCodeQuality.ts → 차단', () => {
149
+ expect(isConsoleAllowed('src/tools/convention/validateCodeQuality.ts')).toBe(false);
150
+ });
151
+
152
+ it('src/infra/orchestrator/index.ts → 차단', () => {
153
+ expect(isConsoleAllowed('src/infra/orchestrator/index.ts')).toBe(false);
154
+ });
155
+ });
@@ -0,0 +1,99 @@
1
+ /**
2
+ * in-process 디스패처 외부 계약 테스트
3
+ *
4
+ * SPEC: .vibe/specs/hook-dispatcher-inprocess.md
5
+ * 디스패처를 Claude Code와 동일하게 CLI로 spawn하여 검증한다 —
6
+ * 내부 구현(in-process)이 아니라 외부 계약(exit code, stdout 주입)이 대상.
7
+ */
8
+ import { describe, it, expect } from 'vitest';
9
+ import { execFileSync } from 'child_process';
10
+ import path from 'path';
11
+ import { fileURLToPath } from 'url';
12
+
13
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
14
+ const PRE_DISPATCHER = path.resolve(__dirname, '..', 'pre-tool-dispatcher.js');
15
+ const POST_DISPATCHER = path.resolve(__dirname, '..', 'post-edit-dispatcher.js');
16
+
17
+ /**
18
+ * 디스패처를 stdin 페이로드와 함께 실행. { stdout, exitCode } 반환.
19
+ */
20
+ function runDispatcher(script, args, payload) {
21
+ const input = typeof payload === 'string' ? payload : JSON.stringify(payload);
22
+ try {
23
+ const stdout = execFileSync('node', [script, ...args], {
24
+ encoding: 'utf-8',
25
+ input,
26
+ timeout: 15000,
27
+ // scope.json이 없는 격리된 cwd에서 실행 (scope-guard no-op 보장)
28
+ env: { ...process.env, CLAUDE_PROJECT_DIR: __dirname },
29
+ });
30
+ return { stdout: stdout.trim(), exitCode: 0 };
31
+ } catch (err) {
32
+ return { stdout: (err.stdout || '').trim(), exitCode: err.status };
33
+ }
34
+ }
35
+
36
+ describe('pre-tool-dispatcher (in-process)', () => {
37
+ it('AC-1: 위험 Bash 명령은 exit 2로 deny', () => {
38
+ const { exitCode } = runDispatcher(PRE_DISPATCHER, ['Bash'], {
39
+ tool_name: 'Bash',
40
+ tool_input: { command: 'rm -rf /' },
41
+ });
42
+ expect(exitCode).toBe(2);
43
+ });
44
+
45
+ it('AC-2: sentinel 경로 Edit은 exit 2 + stdout에 block JSON 주입', () => {
46
+ // 실제 sentinel 경로: src/infra/lib/evolution/ (autonomy/ 는 팬텀 경로였음)
47
+ const { stdout, exitCode } = runDispatcher(PRE_DISPATCHER, ['Edit'], {
48
+ tool_name: 'Edit',
49
+ tool_input: { file_path: 'src/infra/lib/evolution/GuardAnalyzer.ts', old_string: 'a', new_string: 'b' },
50
+ });
51
+ expect(exitCode).toBe(2);
52
+ expect(stdout).toContain('"decision":"block"');
53
+ });
54
+
55
+ it('AC-3: 안전한 Bash 명령은 exit 0', () => {
56
+ const { exitCode } = runDispatcher(PRE_DISPATCHER, ['Bash'], {
57
+ tool_name: 'Bash',
58
+ tool_input: { command: 'ls -la' },
59
+ });
60
+ expect(exitCode).toBe(0);
61
+ });
62
+
63
+ it('AC-3: 안전한 Edit은 exit 0 (scope.json 없음 → scope-guard no-op)', () => {
64
+ const { exitCode } = runDispatcher(PRE_DISPATCHER, ['Edit'], {
65
+ tool_name: 'Edit',
66
+ tool_input: { file_path: 'src/cli/index.ts', old_string: 'a', new_string: 'b' },
67
+ });
68
+ expect(exitCode).toBe(0);
69
+ });
70
+
71
+ it('AC-2 변형: 위험 명령이 sentinel 경로를 노리면 sentinel-guard가 차단', () => {
72
+ // 실제 sentinel 경로: src/infra/lib/evolution/
73
+ const { exitCode } = runDispatcher(PRE_DISPATCHER, ['Bash'], {
74
+ tool_name: 'Bash',
75
+ tool_input: { command: 'rm -rf src/infra/lib/evolution/' },
76
+ });
77
+ expect(exitCode).toBe(2);
78
+ });
79
+ });
80
+
81
+ describe('post-edit-dispatcher (in-process)', () => {
82
+ it('AC-4: 손상된 stdin(비JSON)에도 exit 0 (fail-open)', () => {
83
+ const { exitCode } = runDispatcher(POST_DISPATCHER, [], 'not-json');
84
+ expect(exitCode).toBe(0);
85
+ });
86
+
87
+ it('AC-4: 빈 페이로드에도 exit 0', () => {
88
+ const { exitCode } = runDispatcher(POST_DISPATCHER, [], {});
89
+ expect(exitCode).toBe(0);
90
+ });
91
+
92
+ it('존재하지 않는 파일 경로 페이로드에도 exit 0 (step 내부 격리)', () => {
93
+ const { exitCode } = runDispatcher(POST_DISPATCHER, [], {
94
+ tool_name: 'Edit',
95
+ tool_input: { file_path: '/nonexistent/__vibe_test__.xyz' },
96
+ });
97
+ expect(exitCode).toBe(0);
98
+ });
99
+ });
@@ -27,28 +27,30 @@ function runDetector(text) {
27
27
  // ══════════════════════════════════════════════════
28
28
  describe('keyword-detector', () => {
29
29
  describe('ralph keyword', () => {
30
- it('should detect ralph keyword', () => {
30
+ it('should detect ralph keyword and emit deprecation hint', () => {
31
31
  const result = runDetector('implement the login feature ralph');
32
- expect(result.stdout).toContain('[RALPH MODE]');
32
+ expect(result.stdout).toContain('[vibe]');
33
+ expect(result.stdout).toContain('deprecated');
33
34
  expect(result.stdout).toContain('persistence');
34
35
  });
35
36
 
36
37
  it('should detect ralph case-insensitively', () => {
37
38
  const result = runDetector('RALPH fix all the bugs');
38
- expect(result.stdout).toContain('[RALPH MODE]');
39
+ expect(result.stdout).toContain('[vibe]');
40
+ expect(result.stdout).toContain('deprecated');
39
41
  });
40
42
  });
41
43
 
42
44
  describe('ultrawork keyword', () => {
43
- it('should detect ultrawork keyword', () => {
45
+ it('should detect ultrawork keyword and emit automationLevel banner', () => {
44
46
  const result = runDetector('ultrawork build the entire app');
45
- expect(result.stdout).toContain('[ULTRAWORK MODE]');
47
+ expect(result.stdout).toContain('[ULTRAWORK]');
46
48
  expect(result.stdout).toContain('parallel');
47
49
  });
48
50
 
49
51
  it('should detect ulw alias', () => {
50
52
  const result = runDetector('ulw refactor the codebase');
51
- expect(result.stdout).toContain('[ULTRAWORK MODE]');
53
+ expect(result.stdout).toContain('[ULTRAWORK]');
52
54
  });
53
55
 
54
56
  it('should detect Korean alias when word boundary matches', () => {
@@ -74,28 +76,32 @@ describe('keyword-detector', () => {
74
76
  // strict 키워드(일상어): 명령 끝 위치 또는 --flag 에서만 발동.
75
77
  // 일상 영어("please verify", "quick question")의 오탐을 막기 위함.
76
78
  describe('verify keyword (strict)', () => {
77
- it('should detect verify at command tail', () => {
79
+ it('should detect verify at command tail and emit deprecation hint', () => {
78
80
  const result = runDetector('make the implementation correct, verify');
79
- expect(result.stdout).toContain('[VERIFY MODE]');
81
+ expect(result.stdout).toContain('[vibe]');
82
+ expect(result.stdout).toContain('deprecated');
80
83
  expect(result.stdout).toContain('verification');
81
84
  });
82
85
 
83
86
  it('should detect --verify flag', () => {
84
87
  const result = runDetector('fix the bug --verify');
85
- expect(result.stdout).toContain('[VERIFY MODE]');
88
+ expect(result.stdout).toContain('[vibe]');
89
+ expect(result.stdout).toContain('deprecated');
86
90
  });
87
91
  });
88
92
 
89
93
  describe('quick keyword (strict)', () => {
90
- it('should detect quick at command tail', () => {
94
+ it('should detect quick at command tail and emit --max-iter 1 hint', () => {
91
95
  const result = runDetector('fix this typo quick');
92
- expect(result.stdout).toContain('[QUICK MODE]');
96
+ expect(result.stdout).toContain('[vibe]');
97
+ expect(result.stdout).toContain('--max-iter 1');
93
98
  expect(result.stdout).toContain('fast');
94
99
  });
95
100
 
96
101
  it('should detect --quick flag', () => {
97
102
  const result = runDetector('build the payment API --quick');
98
- expect(result.stdout).toContain('[QUICK MODE]');
103
+ expect(result.stdout).toContain('[vibe]');
104
+ expect(result.stdout).toContain('--max-iter 1');
99
105
  });
100
106
  });
101
107
 
@@ -140,17 +146,19 @@ describe('keyword-detector', () => {
140
146
  // Keyword combinations / synergies
141
147
  // ══════════════════════════════════════════════════
142
148
  describe('keyword combinations', () => {
143
- it('should detect ralph+ultrawork synergy', () => {
149
+ it('should detect ralph+ultrawork synergy and emit deprecation hint', () => {
144
150
  const result = runDetector('ralph ultrawork build everything from scratch');
145
- expect(result.stdout).toContain('[RALPH+ULTRAWORK]');
151
+ expect(result.stdout).toContain('[vibe]');
152
+ expect(result.stdout).toContain('deprecated');
146
153
  expect(result.stdout).toContain('persistence');
147
154
  expect(result.stdout).toContain('parallel');
148
155
  });
149
156
 
150
- it('should detect ralph+verify synergy', () => {
157
+ it('should detect ralph+verify synergy and emit deprecation hint', () => {
151
158
  // verify is strict → use --verify flag (ralph stays bare)
152
159
  const result = runDetector('ralph fix each step --verify');
153
- expect(result.stdout).toContain('[RALPH+VERIFY]');
160
+ expect(result.stdout).toContain('[vibe]');
161
+ expect(result.stdout).toContain('deprecated');
154
162
  });
155
163
 
156
164
  it('should output both keywords when no synergy key matches sorted order', () => {
@@ -158,7 +166,7 @@ describe('keyword-detector', () => {
158
166
  // sorts keywords alphabetically → tries 'explore+ultrawork' which has no match.
159
167
  // So individual outputs are emitted instead. explore is strict → --explore.
160
168
  const result = runDetector('ultrawork analyze the entire project --explore');
161
- expect(result.stdout).toContain('[ULTRAWORK MODE]');
169
+ expect(result.stdout).toContain('[ULTRAWORK]');
162
170
  expect(result.stdout).toContain('[EXPLORE MODE]');
163
171
  expect(result.stdout).toContain('[FLAGS]');
164
172
  });
@@ -224,7 +232,7 @@ describe('keyword-detector', () => {
224
232
  });
225
233
 
226
234
  it('should merge flags from multiple keywords', () => {
227
- // quick is strict → place at command tail
235
+ // quick is strict → place at command tail; ralph stays bare
228
236
  const result = runDetector('ralph finish this quick');
229
237
  expect(result.stdout).toContain('[FLAGS]');
230
238
  expect(result.stdout).toContain('persistence');