@su-record/vibe 2.12.3 → 2.12.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +15 -13
- package/README.md +2 -0
- package/dist/cli/collaborator.d.ts.map +1 -1
- package/dist/cli/collaborator.js +23 -6
- package/dist/cli/collaborator.js.map +1 -1
- 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 +26 -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 +37 -14
- package/dist/cli/postinstall/claude-agents.js.map +1 -1
- package/dist/cli/postinstall/constants.d.ts +20 -2
- package/dist/cli/postinstall/constants.d.ts.map +1 -1
- package/dist/cli/postinstall/constants.js +52 -2
- package/dist/cli/postinstall/constants.js.map +1 -1
- package/dist/cli/postinstall/fs-utils.d.ts +5 -0
- package/dist/cli/postinstall/fs-utils.d.ts.map +1 -1
- package/dist/cli/postinstall/fs-utils.js +55 -0
- package/dist/cli/postinstall/fs-utils.js.map +1 -1
- package/dist/cli/postinstall/fs-utils.test.d.ts +2 -0
- package/dist/cli/postinstall/fs-utils.test.d.ts.map +1 -0
- package/dist/cli/postinstall/fs-utils.test.js +31 -0
- package/dist/cli/postinstall/fs-utils.test.js.map +1 -0
- package/dist/cli/postinstall/index.d.ts +2 -2
- package/dist/cli/postinstall/index.d.ts.map +1 -1
- package/dist/cli/postinstall/index.js +2 -2
- 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 +9 -6
- 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/ProjectSetup.d.ts.map +1 -1
- package/dist/cli/setup/ProjectSetup.js +13 -1
- 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/CostAccumulator.d.ts +19 -2
- package/dist/infra/lib/CostAccumulator.d.ts.map +1 -1
- package/dist/infra/lib/CostAccumulator.js +60 -2
- package/dist/infra/lib/CostAccumulator.js.map +1 -1
- package/dist/infra/lib/antigravity/chat.d.ts.map +1 -1
- package/dist/infra/lib/antigravity/chat.js +6 -0
- package/dist/infra/lib/antigravity/chat.js.map +1 -1
- package/dist/infra/lib/antigravity/orchestration.d.ts.map +1 -1
- package/dist/infra/lib/antigravity/orchestration.js +13 -1
- package/dist/infra/lib/antigravity/orchestration.js.map +1 -1
- package/dist/infra/lib/antigravity/types.d.ts +8 -0
- package/dist/infra/lib/antigravity/types.d.ts.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/gpt/auth.d.ts.map +1 -1
- package/dist/infra/lib/gpt/auth.js +13 -1
- package/dist/infra/lib/gpt/auth.js.map +1 -1
- package/dist/infra/lib/gpt/chat.d.ts.map +1 -1
- package/dist/infra/lib/gpt/chat.js +52 -31
- package/dist/infra/lib/gpt/chat.js.map +1 -1
- package/dist/infra/lib/gpt/embedding.d.ts.map +1 -1
- package/dist/infra/lib/gpt/embedding.js +6 -5
- package/dist/infra/lib/gpt/embedding.js.map +1 -1
- package/dist/infra/lib/gpt/orchestration.d.ts.map +1 -1
- package/dist/infra/lib/gpt/orchestration.js +13 -1
- package/dist/infra/lib/gpt/orchestration.js.map +1 -1
- package/dist/infra/lib/gpt/types.d.ts +8 -0
- package/dist/infra/lib/gpt/types.d.ts.map +1 -1
- package/dist/infra/lib/llm/timeout.d.ts +34 -0
- package/dist/infra/lib/llm/timeout.d.ts.map +1 -0
- package/dist/infra/lib/llm/timeout.js +45 -0
- package/dist/infra/lib/llm/timeout.js.map +1 -0
- package/dist/infra/lib/llm/timeout.test.d.ts +2 -0
- package/dist/infra/lib/llm/timeout.test.d.ts.map +1 -0
- package/dist/infra/lib/llm/timeout.test.js +50 -0
- package/dist/infra/lib/llm/timeout.test.js.map +1 -0
- 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/AgentExecutor.d.ts.map +1 -1
- package/dist/infra/orchestrator/AgentExecutor.js +3 -1
- package/dist/infra/orchestrator/AgentExecutor.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 +39 -3
- package/dist/infra/orchestrator/LLMCluster.js.map +1 -1
- package/dist/infra/orchestrator/MultiLlmResearch.d.ts +5 -0
- package/dist/infra/orchestrator/MultiLlmResearch.d.ts.map +1 -1
- package/dist/infra/orchestrator/MultiLlmResearch.js +7 -0
- package/dist/infra/orchestrator/MultiLlmResearch.js.map +1 -1
- package/dist/infra/orchestrator/PhasePipeline.d.ts +5 -5
- package/dist/infra/orchestrator/PhasePipeline.d.ts.map +1 -1
- package/dist/infra/orchestrator/PhasePipeline.js +19 -15
- package/dist/infra/orchestrator/PhasePipeline.js.map +1 -1
- package/dist/infra/orchestrator/SmartRouter.d.ts +10 -0
- package/dist/infra/orchestrator/SmartRouter.d.ts.map +1 -1
- package/dist/infra/orchestrator/SmartRouter.js +23 -6
- package/dist/infra/orchestrator/SmartRouter.js.map +1 -1
- package/dist/infra/orchestrator/SmartRouter.test.js +46 -19
- package/dist/infra/orchestrator/SmartRouter.test.js.map +1 -1
- package/dist/infra/orchestrator/parallelResearch.d.ts.map +1 -1
- package/dist/infra/orchestrator/parallelResearch.js +41 -9
- package/dist/infra/orchestrator/parallelResearch.js.map +1 -1
- package/hooks/hooks.json +2 -9
- package/hooks/scripts/__tests__/keyword-detector.test.js +51 -16
- package/hooks/scripts/auto-commit.js +17 -17
- package/hooks/scripts/auto-format.js +11 -6
- package/hooks/scripts/code-check.js +17 -20
- package/hooks/scripts/codex-hook-adapter.js +5 -1
- package/hooks/scripts/keyword-detector.js +14 -2
- package/hooks/scripts/llm-orchestrate.js +13 -2
- package/hooks/scripts/prompt-dispatcher.js +32 -9
- package/hooks/scripts/session-start.js +28 -15
- package/hooks/scripts/utils.js +33 -9
- package/package.json +1 -1
- package/skills/arch-guard/SKILL.md +2 -2
- package/skills/characterization-test/SKILL.md +2 -2
- package/skills/design-audit/SKILL.md +1 -0
- package/skills/design-normalize/SKILL.md +1 -0
- package/skills/design-teach/SKILL.md +1 -1
- 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 +189 -0
- package/skills/test/SKILL.md +9 -9
- package/skills/ui-ux-pro-max/SKILL.md +1 -0
- package/skills/vibe.run/SKILL.md +5 -5
- package/skills/vibe.test/SKILL.md +1 -1
- package/skills/vibe.verify/SKILL.md +1 -1
- package/vibe/rules/standards/complexity-metrics.md +2 -2
- package/vibe/templates/claudemd-template.md +1 -1
|
@@ -71,38 +71,71 @@ describe('keyword-detector', () => {
|
|
|
71
71
|
});
|
|
72
72
|
});
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
// strict 키워드(일상어): 명령 끝 위치 또는 --flag 에서만 발동.
|
|
75
|
+
// 일상 영어("please verify", "quick question")의 오탐을 막기 위함.
|
|
76
|
+
describe('verify keyword (strict)', () => {
|
|
77
|
+
it('should detect verify at command tail', () => {
|
|
78
|
+
const result = runDetector('make the implementation correct, verify');
|
|
77
79
|
expect(result.stdout).toContain('[VERIFY MODE]');
|
|
78
80
|
expect(result.stdout).toContain('verification');
|
|
79
81
|
});
|
|
82
|
+
|
|
83
|
+
it('should detect --verify flag', () => {
|
|
84
|
+
const result = runDetector('fix the bug --verify');
|
|
85
|
+
expect(result.stdout).toContain('[VERIFY MODE]');
|
|
86
|
+
});
|
|
80
87
|
});
|
|
81
88
|
|
|
82
|
-
describe('quick keyword', () => {
|
|
83
|
-
it('should detect quick
|
|
84
|
-
const result = runDetector('
|
|
89
|
+
describe('quick keyword (strict)', () => {
|
|
90
|
+
it('should detect quick at command tail', () => {
|
|
91
|
+
const result = runDetector('fix this typo quick');
|
|
85
92
|
expect(result.stdout).toContain('[QUICK MODE]');
|
|
86
93
|
expect(result.stdout).toContain('fast');
|
|
87
94
|
});
|
|
95
|
+
|
|
96
|
+
it('should detect --quick flag', () => {
|
|
97
|
+
const result = runDetector('build the payment API --quick');
|
|
98
|
+
expect(result.stdout).toContain('[QUICK MODE]');
|
|
99
|
+
});
|
|
88
100
|
});
|
|
89
101
|
|
|
90
|
-
describe('explore keyword', () => {
|
|
91
|
-
it('should detect explore
|
|
92
|
-
const result = runDetector('
|
|
102
|
+
describe('explore keyword (strict)', () => {
|
|
103
|
+
it('should detect --explore flag', () => {
|
|
104
|
+
const result = runDetector('analyze the codebase --explore');
|
|
93
105
|
expect(result.stdout).toContain('[EXPLORE MODE]');
|
|
94
106
|
expect(result.stdout).toContain('exploration');
|
|
95
107
|
});
|
|
96
108
|
});
|
|
97
109
|
|
|
98
|
-
describe('plan keyword', () => {
|
|
99
|
-
it('should detect plan
|
|
100
|
-
const result = runDetector('
|
|
110
|
+
describe('plan keyword (strict)', () => {
|
|
111
|
+
it('should detect --plan flag', () => {
|
|
112
|
+
const result = runDetector('the new feature --plan');
|
|
101
113
|
expect(result.stdout).toContain('[PLAN MODE]');
|
|
102
114
|
expect(result.stdout).toContain('planning');
|
|
103
115
|
});
|
|
104
116
|
});
|
|
105
117
|
|
|
118
|
+
// ══════════════════════════════════════════════════
|
|
119
|
+
// 오탐 방지 회귀 테스트 — 일상어가 명령 중간/시작에 올 때 발동 금지
|
|
120
|
+
// ══════════════════════════════════════════════════
|
|
121
|
+
describe('strict keyword false-positive guard', () => {
|
|
122
|
+
it('should NOT trigger on "quick question on auth"', () => {
|
|
123
|
+
expect(runDetector('quick question on auth').stdout).toBe('');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should NOT trigger on "please verify the fix works"', () => {
|
|
127
|
+
expect(runDetector('please verify the fix works').stdout).toBe('');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should NOT trigger on "I plan to refactor later"', () => {
|
|
131
|
+
expect(runDetector('I plan to refactor later').stdout).toBe('');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('should NOT trigger on "let me explore the options first"', () => {
|
|
135
|
+
expect(runDetector('let me explore the options first').stdout).toBe('');
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
106
139
|
// ══════════════════════════════════════════════════
|
|
107
140
|
// Keyword combinations / synergies
|
|
108
141
|
// ══════════════════════════════════════════════════
|
|
@@ -115,15 +148,16 @@ describe('keyword-detector', () => {
|
|
|
115
148
|
});
|
|
116
149
|
|
|
117
150
|
it('should detect ralph+verify synergy', () => {
|
|
118
|
-
|
|
151
|
+
// verify is strict → use --verify flag (ralph stays bare)
|
|
152
|
+
const result = runDetector('ralph fix each step --verify');
|
|
119
153
|
expect(result.stdout).toContain('[RALPH+VERIFY]');
|
|
120
154
|
});
|
|
121
155
|
|
|
122
156
|
it('should output both keywords when no synergy key matches sorted order', () => {
|
|
123
157
|
// KEYWORD_SYNERGIES defines 'ultrawork+explore' but processCombinations
|
|
124
158
|
// sorts keywords alphabetically → tries 'explore+ultrawork' which has no match.
|
|
125
|
-
// So individual outputs are emitted instead.
|
|
126
|
-
const result = runDetector('ultrawork
|
|
159
|
+
// So individual outputs are emitted instead. explore is strict → --explore.
|
|
160
|
+
const result = runDetector('ultrawork analyze the entire project --explore');
|
|
127
161
|
expect(result.stdout).toContain('[ULTRAWORK MODE]');
|
|
128
162
|
expect(result.stdout).toContain('[EXPLORE MODE]');
|
|
129
163
|
expect(result.stdout).toContain('[FLAGS]');
|
|
@@ -190,7 +224,8 @@ describe('keyword-detector', () => {
|
|
|
190
224
|
});
|
|
191
225
|
|
|
192
226
|
it('should merge flags from multiple keywords', () => {
|
|
193
|
-
|
|
227
|
+
// quick is strict → place at command tail
|
|
228
|
+
const result = runDetector('ralph finish this quick');
|
|
194
229
|
expect(result.stdout).toContain('[FLAGS]');
|
|
195
230
|
expect(result.stdout).toContain('persistence');
|
|
196
231
|
expect(result.stdout).toContain('fast');
|
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Stop Hook - 에이전트 응답 완료 시 자동 커밋 + 롤백 체크포인트
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* ⚠️ OPT-IN ONLY (기본 비활성).
|
|
5
|
+
* 매 턴 자동 커밋은 "사용자가 요청할 때만 커밋한다"는 원칙과 충돌하고,
|
|
6
|
+
* `git add -A` 가 스코프 밖 파일(임시/미완성)까지 스테이징하는 부작용이 있다.
|
|
7
|
+
* 따라서 `.vibe/config.json` 에서 `hooks["auto-commit"].enabled === true`
|
|
8
|
+
* 로 명시적으로 켰을 때만 동작한다.
|
|
7
9
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
10
|
+
* 동작 시: 변경사항이 있으면 git add -A + commit (커밋 메시지는 변경 파일 목록 기반),
|
|
11
|
+
* feature branch 에서만 (main/master 보호).
|
|
12
|
+
* 체크포인트: 커밋마다 vibe-checkpoint 태그 생성 → `git reset --hard vibe-checkpoint-N`
|
|
13
|
+
* 으로 롤백 가능. 최근 5개만 유지.
|
|
11
14
|
*/
|
|
12
15
|
import { execSync } from 'child_process';
|
|
13
|
-
import { PROJECT_DIR } from './utils.js';
|
|
16
|
+
import { PROJECT_DIR, readProjectConfig } from './utils.js';
|
|
17
|
+
|
|
18
|
+
// Opt-in 가드 — 명시적으로 켜지 않았으면 아무것도 하지 않는다.
|
|
19
|
+
const __autoCommitCfg = readProjectConfig();
|
|
20
|
+
if (__autoCommitCfg?.hooks?.['auto-commit']?.enabled !== true) process.exit(0);
|
|
14
21
|
|
|
15
22
|
const PROTECTED_BRANCHES = ['main', 'master', 'develop', 'production'];
|
|
16
23
|
const MAX_FILES_IN_MSG = 5;
|
|
@@ -24,14 +31,6 @@ function getCurrentBranch() {
|
|
|
24
31
|
}).trim();
|
|
25
32
|
}
|
|
26
33
|
|
|
27
|
-
function hasChanges() {
|
|
28
|
-
const status = execSync('git status --porcelain', {
|
|
29
|
-
cwd: PROJECT_DIR,
|
|
30
|
-
encoding: 'utf-8',
|
|
31
|
-
}).trim();
|
|
32
|
-
return status.length > 0;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
34
|
function getChangedFiles() {
|
|
36
35
|
const status = execSync('git status --porcelain', {
|
|
37
36
|
cwd: PROJECT_DIR,
|
|
@@ -81,9 +80,10 @@ try {
|
|
|
81
80
|
process.exit(0);
|
|
82
81
|
}
|
|
83
82
|
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
// 변경 유무와 파일 목록을 단일 `git status --porcelain` 호출로 처리
|
|
86
84
|
const files = getChangedFiles();
|
|
85
|
+
if (files.length === 0) process.exit(0);
|
|
86
|
+
|
|
87
87
|
const msg = buildCommitMessage(files);
|
|
88
88
|
|
|
89
89
|
execSync('git add -A', { cwd: PROJECT_DIR, stdio: 'ignore' });
|
|
@@ -19,13 +19,18 @@ function getFilePath() {
|
|
|
19
19
|
return input.file_path || input.path || '';
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
// PATH 직접 스캔 — `which` execSync는 매 파일 저장마다 자식 프로세스를 동기
|
|
23
|
+
// spawn하므로, fs.existsSync로 대체하고 프로세스 내 캐싱한다.
|
|
24
|
+
const _binCache = new Map();
|
|
22
25
|
function hasBin(name) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
const cached = _binCache.get(name);
|
|
27
|
+
if (cached !== undefined) return cached;
|
|
28
|
+
const candidates = process.platform === 'win32' ? [`${name}.exe`, `${name}.cmd`, name] : [name];
|
|
29
|
+
const found = (process.env.PATH || '').split(path.delimiter).some(
|
|
30
|
+
dir => dir && candidates.some(c => existsSync(path.join(dir, c))),
|
|
31
|
+
);
|
|
32
|
+
_binCache.set(name, found);
|
|
33
|
+
return found;
|
|
29
34
|
}
|
|
30
35
|
|
|
31
36
|
function hasPrettier() {
|
|
@@ -225,35 +225,32 @@ function clearFailure(filePath) {
|
|
|
225
225
|
}
|
|
226
226
|
|
|
227
227
|
async function main() {
|
|
228
|
+
const files = getModifiedFiles();
|
|
229
|
+
if (files.length === 0) return;
|
|
230
|
+
|
|
228
231
|
// 1. Code quality check (changed files only — never scan entire project)
|
|
229
232
|
try {
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
} else {
|
|
244
|
-
clearFailure(files[0]);
|
|
245
|
-
}
|
|
246
|
-
emitSelfHealMessages(files[0]);
|
|
233
|
+
const module = await import(`${BASE_URL}convention/index.js`);
|
|
234
|
+
const result = await module.validateCodeQuality({
|
|
235
|
+
targetPath: files[0],
|
|
236
|
+
projectPath: PROJECT_DIR,
|
|
237
|
+
});
|
|
238
|
+
const text = result.content[0].text;
|
|
239
|
+
// Output P1/P2 only — skip P3 (style)
|
|
240
|
+
const critical = text.split('\n').filter(l => /\b(error|critical|P1|P2)\b/i.test(l)).slice(0, 3);
|
|
241
|
+
if (critical.length > 0) {
|
|
242
|
+
console.log('[CODE CHECK]', critical.join(' | '));
|
|
243
|
+
trackFailure(files[0], critical);
|
|
244
|
+
} else {
|
|
245
|
+
clearFailure(files[0]);
|
|
247
246
|
}
|
|
247
|
+
emitSelfHealMessages(files[0]);
|
|
248
248
|
} catch {
|
|
249
249
|
// Silently continue on check failure — never block progress
|
|
250
250
|
}
|
|
251
251
|
|
|
252
252
|
// 2. 관찰 자동 캡처
|
|
253
253
|
try {
|
|
254
|
-
const files = getModifiedFiles();
|
|
255
|
-
if (files.length === 0) return;
|
|
256
|
-
|
|
257
254
|
const memModule = await import(`${BASE_URL}memory/index.js`);
|
|
258
255
|
const { type, title } = classifyObservation(files);
|
|
259
256
|
|
|
@@ -37,11 +37,15 @@ function childEnv() {
|
|
|
37
37
|
|
|
38
38
|
function runScript(scriptName, args = []) {
|
|
39
39
|
const scriptPath = path.join(__dirname, scriptName);
|
|
40
|
+
// prompt-dispatcher 는 명시적 외부 LLM 호출(hook 모드 최대 ~50s)을 포함할 수 있어
|
|
41
|
+
// 그보다 약간 긴 timeout 으로 감싼다. 나머지 경량 스크립트는 30s. (B-2 정합 —
|
|
42
|
+
// 30s 고정이면 prompt-dispatcher 의 외부 LLM 호출을 다시 hard-kill 한다)
|
|
43
|
+
const timeout = scriptName.includes('prompt-dispatcher') ? 55000 : 30000;
|
|
40
44
|
return spawnSync(process.execPath, [scriptPath, ...args], {
|
|
41
45
|
input: stdinData || JSON.stringify(payload),
|
|
42
46
|
encoding: 'utf-8',
|
|
43
47
|
env: childEnv(),
|
|
44
|
-
timeout
|
|
48
|
+
timeout,
|
|
45
49
|
});
|
|
46
50
|
}
|
|
47
51
|
|
|
@@ -36,6 +36,7 @@ const MAGIC_KEYWORDS = {
|
|
|
36
36
|
description: 'Planning interview mode',
|
|
37
37
|
flags: ['planning', 'interview'],
|
|
38
38
|
output: '[PLAN MODE] Enter planning interview mode. Gather requirements before implementation.',
|
|
39
|
+
strict: true, // 일상어 — 명령 끝 위치 또는 --plan 플래그에서만 (오탐 방지)
|
|
39
40
|
},
|
|
40
41
|
|
|
41
42
|
// Ralph + Plan 조합
|
|
@@ -52,6 +53,7 @@ const MAGIC_KEYWORDS = {
|
|
|
52
53
|
description: 'Strict verification after each step',
|
|
53
54
|
flags: ['verification', 'strict'],
|
|
54
55
|
output: '[VERIFY MODE] Strict verification enabled. Every change must be verified before proceeding.',
|
|
56
|
+
strict: true, // 일상어 ("please verify the fix" 오탐 방지)
|
|
55
57
|
},
|
|
56
58
|
|
|
57
59
|
// 탐색 모드
|
|
@@ -60,6 +62,7 @@ const MAGIC_KEYWORDS = {
|
|
|
60
62
|
description: 'Deep codebase exploration',
|
|
61
63
|
flags: ['exploration', 'thorough'],
|
|
62
64
|
output: '[EXPLORE MODE] Deep exploration enabled. Use multiple Explore agents for thorough codebase analysis.',
|
|
65
|
+
strict: true, // 일상어 ("let me explore the options" 오탐 방지)
|
|
63
66
|
},
|
|
64
67
|
|
|
65
68
|
// 빠른 모드
|
|
@@ -68,6 +71,7 @@ const MAGIC_KEYWORDS = {
|
|
|
68
71
|
description: 'Fast execution, minimal verification',
|
|
69
72
|
flags: ['fast', 'minimal_verification'],
|
|
70
73
|
output: '[QUICK MODE] Fast execution mode. Minimal verification, single round reviews.',
|
|
74
|
+
strict: true, // 일상어 ("quick question on auth" 오탐 방지)
|
|
71
75
|
},
|
|
72
76
|
};
|
|
73
77
|
|
|
@@ -96,8 +100,16 @@ function detectKeywords(text) {
|
|
|
96
100
|
const resolvedKeywords = new Map();
|
|
97
101
|
|
|
98
102
|
for (const [keyword, config] of Object.entries(MAGIC_KEYWORDS)) {
|
|
99
|
-
//
|
|
100
|
-
const
|
|
103
|
+
// alias는 본체의 strict 설정을 따른다.
|
|
104
|
+
const target = config.alias ? MAGIC_KEYWORDS[config.alias] : config;
|
|
105
|
+
// strict 키워드(일상어: quick/plan/verify/explore)는 단어경계만으로는
|
|
106
|
+
// "quick question", "please verify" 등에 오탐한다. 따라서
|
|
107
|
+
// ① 명령 끝 위치의 독립 토큰 ("... 만들어줘 quick") 또는
|
|
108
|
+
// ② 명시 플래그 ("--quick", "-quick", "/quick")
|
|
109
|
+
// 에서만 매칭한다. 조어(ralph/ultrawork)는 오탐이 거의 없어 단어경계 유지.
|
|
110
|
+
const regex = target.strict
|
|
111
|
+
? new RegExp(`(?:(?:^|\\s)--?${keyword}\\b)|(?:(?:^|\\s)${keyword}\\s*$)`, 'i')
|
|
112
|
+
: new RegExp(`\\b${keyword}\\b`, 'i');
|
|
101
113
|
if (regex.test(lowerText)) {
|
|
102
114
|
// alias 해결
|
|
103
115
|
const resolved = config.alias ? MAGIC_KEYWORDS[config.alias] : config;
|
|
@@ -42,7 +42,9 @@ const mode = process.argv[3] || 'orchestrate';
|
|
|
42
42
|
// WHY 3 retries: Enough to ride out brief 503/overload blips (typically 1-2
|
|
43
43
|
// consecutive), but not so many that a genuinely down provider delays the
|
|
44
44
|
// fallback chain for minutes.
|
|
45
|
-
|
|
45
|
+
// VIBE_LLM_MAX_RETRIES override: UserPromptSubmit(hook) 모드에서는 1(재시도 없음)로
|
|
46
|
+
// 낮춰 부모 dispatcher timeout 안에 단일 시도로 끝낸다. (B-2: hard-kill/무음실패 방지)
|
|
47
|
+
const MAX_RETRIES = Number(process.env.VIBE_LLM_MAX_RETRIES) || 3;
|
|
46
48
|
// WHY 2000ms initial delay: LLM rate-limit windows are typically 1-5s;
|
|
47
49
|
// starting at 2s with exponential backoff (2s, 4s, 8s) covers most reset intervals.
|
|
48
50
|
const INITIAL_DELAY_MS = 2000;
|
|
@@ -203,7 +205,9 @@ function parseAnalyzeImageArgs(args) {
|
|
|
203
205
|
// CLI Provider Functions
|
|
204
206
|
// ============================================
|
|
205
207
|
|
|
206
|
-
|
|
208
|
+
// VIBE_LLM_PRIMARY_TIMEOUT_MS override: hook 모드에서는 부모 dispatcher timeout 보다
|
|
209
|
+
// 짧게 잡아(예: 45s) 자식이 스스로 정리하고 의미있는 메시지를 반환하게 한다.
|
|
210
|
+
const CLI_TIMEOUT_MS = Number(process.env.VIBE_LLM_PRIMARY_TIMEOUT_MS) || 180000;
|
|
207
211
|
const CLI_FALLBACK_TIMEOUT_MS = 30000;
|
|
208
212
|
const IS_WINDOWS = os.platform() === 'win32';
|
|
209
213
|
|
|
@@ -599,6 +603,13 @@ async function main() {
|
|
|
599
603
|
providerChain = claudeSecondary ? ['antigravity', 'claude'] : ['antigravity', 'gpt'];
|
|
600
604
|
}
|
|
601
605
|
|
|
606
|
+
// hook(UserPromptSubmit) 모드: 사용자가 `gpt`/`agy` 접두사로 명시적으로 부른 단발
|
|
607
|
+
// 보조 호출이라 cross-provider fallback 이 불필요하다. primary 1개로 단축해
|
|
608
|
+
// 부모 dispatcher timeout 안에 단일 시도로 끝낸다. (B-2)
|
|
609
|
+
if (process.env.VIBE_LLM_HOOK_MODE) {
|
|
610
|
+
providerChain = [providerChain[0]];
|
|
611
|
+
}
|
|
612
|
+
|
|
602
613
|
const vibeConfig = readVibeConfig();
|
|
603
614
|
|
|
604
615
|
for (let i = 0; i < providerChain.length; i++) {
|
|
@@ -82,39 +82,47 @@ const DISPATCH_RULES = [
|
|
|
82
82
|
label: 'e2e-echo',
|
|
83
83
|
},
|
|
84
84
|
|
|
85
|
-
// 외부 LLM 호출 (GPT/Antigravity)
|
|
85
|
+
// 외부 LLM 호출 (GPT/Antigravity) — 명시적 provider 접두사 필수.
|
|
86
|
+
//
|
|
87
|
+
// 과거에는 "추론해", "코드 리뷰", "디버깅해" 같은 자연어 패턴이 prompt
|
|
88
|
+
// 어디서든 매칭되어, 평범한 한국어/영어 요청에도 외부 LLM이 동기로 spawn되고
|
|
89
|
+
// 그 응답이 컨텍스트에 주입되었다(컨텍스트 오염 + 최대 30s 블로킹).
|
|
90
|
+
// 이제는 prompt가 `gpt`/`agy`/`antigravity` 로 **시작**할 때만 발동한다.
|
|
91
|
+
// 즉 사용자가 외부 LLM을 콕 집어 부를 때만 동작하고, 일상 요청엔 걸리지 않는다.
|
|
92
|
+
// (참고: `/vibe.reason` 스킬이 일반 추론을 담당하므로 자연어 자동호출은 불필요)
|
|
93
|
+
// `s` 플래그(dotAll)로 여러 줄 prompt 의 역할 키워드도 매칭한다.
|
|
86
94
|
{
|
|
87
|
-
pattern:
|
|
95
|
+
pattern: /^\s*gpt\b.*(아키텍처|architecture|설계|구조)/is,
|
|
88
96
|
script: 'llm-orchestrate.js',
|
|
89
97
|
args: ['gpt', 'orchestrate', 'You are a software architect. Analyze and review the architecture.'],
|
|
90
98
|
label: 'gpt-architecture',
|
|
91
99
|
},
|
|
92
100
|
{
|
|
93
|
-
pattern:
|
|
101
|
+
pattern: /^\s*(agy|antigravity)\b.*(ui|ux|디자인|design|사용자.*경험)/is,
|
|
94
102
|
script: 'llm-orchestrate.js',
|
|
95
103
|
args: ['antigravity', 'orchestrate', 'You are a UI/UX expert. Analyze and provide feedback.'],
|
|
96
104
|
label: 'antigravity-uiux',
|
|
97
105
|
},
|
|
98
106
|
{
|
|
99
|
-
pattern:
|
|
107
|
+
pattern: /^\s*gpt\b.*(디버깅|debug|버그|bug)/is,
|
|
100
108
|
script: 'llm-orchestrate.js',
|
|
101
109
|
args: ['gpt', 'orchestrate', 'You are a debugging expert. Find bugs and suggest fixes.'],
|
|
102
110
|
label: 'gpt-debug',
|
|
103
111
|
},
|
|
104
112
|
{
|
|
105
|
-
pattern:
|
|
113
|
+
pattern: /^\s*(agy|antigravity)\b.*(분석|analyz|코드.*품질|code.*quality)/is,
|
|
106
114
|
script: 'llm-orchestrate.js',
|
|
107
115
|
args: ['antigravity', 'orchestrate', 'You are a code analysis expert. Review and analyze the code.'],
|
|
108
116
|
label: 'antigravity-analysis',
|
|
109
117
|
},
|
|
110
118
|
{
|
|
111
|
-
pattern:
|
|
119
|
+
pattern: /^\s*gpt\b.*(리뷰|review)/is,
|
|
112
120
|
script: 'llm-orchestrate.js',
|
|
113
121
|
args: ['gpt', 'orchestrate', 'You are a code review expert. Review the code for best practices, security, and performance.'],
|
|
114
122
|
label: 'gpt-codereview',
|
|
115
123
|
},
|
|
116
124
|
{
|
|
117
|
-
pattern:
|
|
125
|
+
pattern: /^\s*gpt\b.*(추론|reasoning|복잡.*분석|deep.*analysis)/is,
|
|
118
126
|
script: 'llm-orchestrate.js',
|
|
119
127
|
args: ['gpt', 'orchestrate', 'You are a reasoning expert. Analyze the problem deeply and provide detailed reasoning.'],
|
|
120
128
|
label: 'gpt-reasoning',
|
|
@@ -155,14 +163,29 @@ for (const rule of DISPATCH_RULES) {
|
|
|
155
163
|
const scriptPath = path.join(__dirname, rule.script);
|
|
156
164
|
const args = rule.args || [];
|
|
157
165
|
|
|
166
|
+
// 외부 LLM 호출(llm-orchestrate)은 부모/자식 timeout 을 정합시킨다 (B-2):
|
|
167
|
+
// 자식은 hook 모드(primary 45s, fallback 없음, retry 없음)로 단일 시도 후 스스로
|
|
168
|
+
// 정리하고, 부모는 그보다 약간 긴 50s 로 감싸 hard-kill 을 피한다.
|
|
169
|
+
// 경량 스크립트(keyword-detector 등)는 기존 30s 유지.
|
|
170
|
+
const isLlm = rule.script === 'llm-orchestrate.js';
|
|
171
|
+
const execTimeout = isLlm ? 50000 : 30000;
|
|
172
|
+
const childEnv = isLlm
|
|
173
|
+
? { ...process.env, VIBE_LLM_HOOK_MODE: '1', VIBE_LLM_PRIMARY_TIMEOUT_MS: '45000', VIBE_LLM_MAX_RETRIES: '1' }
|
|
174
|
+
: { ...process.env };
|
|
175
|
+
|
|
158
176
|
execPromises.push(
|
|
159
177
|
new Promise((resolve) => {
|
|
160
178
|
execFile('node', [scriptPath, ...args], {
|
|
161
|
-
timeout:
|
|
162
|
-
env:
|
|
179
|
+
timeout: execTimeout,
|
|
180
|
+
env: childEnv,
|
|
163
181
|
}, (error, stdout, stderr) => {
|
|
164
182
|
if (stdout?.trim()) {
|
|
165
183
|
process.stdout.write(stdout);
|
|
184
|
+
} else if (error) {
|
|
185
|
+
// 무음실패 방지 — 외부 LLM 호출이 timeout 또는 에러로 결과 없이 죽을 때,
|
|
186
|
+
// 사용자가 원인을 알 수 있도록 한 줄만 노출한다.
|
|
187
|
+
const reason = error.killed ? `timed out (${execTimeout / 1000}s)` : (error.message || 'failed');
|
|
188
|
+
process.stdout.write(`[${rule.label}] external LLM call ${reason} — no result injected.\n`);
|
|
166
189
|
}
|
|
167
190
|
resolve();
|
|
168
191
|
});
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SessionStart Hook - 세션 시작 시 메모리/시간 로드 + 버전 체크
|
|
3
3
|
*/
|
|
4
|
-
import { getToolsBaseUrl, PROJECT_DIR, projectVibePath, projectVibeRoot } from './utils.js';
|
|
4
|
+
import { getToolsBaseUrl, getGlobalNpmPath, PROJECT_DIR, projectVibePath, projectVibeRoot } from './utils.js';
|
|
5
5
|
import fs from 'fs';
|
|
6
6
|
import path from 'path';
|
|
7
7
|
import os from 'os';
|
|
8
8
|
import https from 'https';
|
|
9
|
-
import { execSync } from 'child_process';
|
|
10
9
|
|
|
11
10
|
const BASE_URL = getToolsBaseUrl();
|
|
12
11
|
|
|
@@ -33,6 +32,30 @@ function fetchLatestVersion() {
|
|
|
33
32
|
});
|
|
34
33
|
}
|
|
35
34
|
|
|
35
|
+
// 버전 체크 결과 24시간 파일 캐시 — 매 SessionStart마다 npm registry에
|
|
36
|
+
// HTTP 요청(타임아웃 시 3초 블로킹)을 보내지 않도록 한다. 릴리즈 주기 대비
|
|
37
|
+
// 하루 1회 확인이면 충분하다.
|
|
38
|
+
const VERSION_CACHE_PATH = path.join(os.homedir(), '.vibe', 'version-check.json');
|
|
39
|
+
const VERSION_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
40
|
+
|
|
41
|
+
async function getLatestVersionCached() {
|
|
42
|
+
try {
|
|
43
|
+
const cached = JSON.parse(fs.readFileSync(VERSION_CACHE_PATH, 'utf8'));
|
|
44
|
+
if (cached.version && Date.now() - cached.checkedAt < VERSION_CACHE_TTL_MS) {
|
|
45
|
+
return cached.version;
|
|
46
|
+
}
|
|
47
|
+
} catch { /* 캐시 없음/손상 → 네트워크 조회 */ }
|
|
48
|
+
|
|
49
|
+
const version = await fetchLatestVersion();
|
|
50
|
+
if (version) {
|
|
51
|
+
try {
|
|
52
|
+
fs.mkdirSync(path.dirname(VERSION_CACHE_PATH), { recursive: true });
|
|
53
|
+
fs.writeFileSync(VERSION_CACHE_PATH, JSON.stringify({ version, checkedAt: Date.now() }));
|
|
54
|
+
} catch { /* 캐시 기록 실패는 무시 */ }
|
|
55
|
+
}
|
|
56
|
+
return version;
|
|
57
|
+
}
|
|
58
|
+
|
|
36
59
|
function compareVersions(a, b) {
|
|
37
60
|
const partsA = a.replace(/^v/, '').split('.').map(Number);
|
|
38
61
|
const partsB = b.replace(/^v/, '').split('.').map(Number);
|
|
@@ -47,18 +70,8 @@ function compareVersions(a, b) {
|
|
|
47
70
|
|
|
48
71
|
function getCurrentVersion() {
|
|
49
72
|
try {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
globalNpmPath = execSync('npm root -g', { encoding: 'utf8', timeout: 3000 }).trim();
|
|
53
|
-
} catch {
|
|
54
|
-
const homeDir = os.homedir();
|
|
55
|
-
const fallbacks = [
|
|
56
|
-
'/usr/local/lib/node_modules',
|
|
57
|
-
path.join(homeDir, '.npm-global', 'lib', 'node_modules'),
|
|
58
|
-
];
|
|
59
|
-
globalNpmPath = fallbacks.find(p => fs.existsSync(p)) || fallbacks[0];
|
|
60
|
-
}
|
|
61
|
-
const pkgPath = path.join(globalNpmPath, '@su-record', 'vibe', 'package.json');
|
|
73
|
+
// getToolsBaseUrl()이 이미 `npm root -g` 결과를 캐싱하므로 재사용 — 중복 spawn 제거
|
|
74
|
+
const pkgPath = path.join(getGlobalNpmPath(), '@su-record', 'vibe', 'package.json');
|
|
62
75
|
if (fs.existsSync(pkgPath)) {
|
|
63
76
|
return JSON.parse(fs.readFileSync(pkgPath, 'utf8')).version || null;
|
|
64
77
|
}
|
|
@@ -77,7 +90,7 @@ async function main() {
|
|
|
77
90
|
memoryModule.startSession({ projectPath: PROJECT_DIR }),
|
|
78
91
|
timeModule.getCurrentTime({ format: 'human', timezone: 'Asia/Seoul' }),
|
|
79
92
|
memoryModule.listMemories({ limit: 5, projectPath: PROJECT_DIR }),
|
|
80
|
-
|
|
93
|
+
getLatestVersionCached(),
|
|
81
94
|
]);
|
|
82
95
|
|
|
83
96
|
console.log(session.content[0].text);
|
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
|
/**
|
|
@@ -153,7 +165,7 @@ function toFileUrl(fsPath) {
|
|
|
153
165
|
|
|
154
166
|
// 전역 npm 경로 캐시
|
|
155
167
|
let _globalNpmPath = null;
|
|
156
|
-
function getGlobalNpmPath() {
|
|
168
|
+
export function getGlobalNpmPath() {
|
|
157
169
|
if (_globalNpmPath === null) {
|
|
158
170
|
try {
|
|
159
171
|
_globalNpmPath = execSync('npm root -g', { encoding: 'utf8' }).trim();
|
|
@@ -190,20 +202,32 @@ function hasRuntimeDeps(packageRoot) {
|
|
|
190
202
|
* 패키지 하위 경로의 file:// URL 반환 (크로스 플랫폼)
|
|
191
203
|
* 우선순위: 로컬 빌드 → ~/.vibe/ → 전역 npm
|
|
192
204
|
* 각 후보는 probeFile 존재 + 런타임 deps 해석 가능해야 채택 (dist-only 복사본 회피)
|
|
205
|
+
*
|
|
206
|
+
* 프로세스 내 메모이제이션: getToolsBaseUrl/getLibBaseUrl/getCliBaseUrl이 한 훅
|
|
207
|
+
* 프로세스에서 각각 호출되면 최악 6회 동기 stat이 반복되므로 결과를 캐싱한다.
|
|
193
208
|
*/
|
|
209
|
+
const _packageUrlCache = new Map();
|
|
194
210
|
function getPackageUrl(subpath, probeFile) {
|
|
211
|
+
const cacheKey = `${subpath}|${probeFile}`;
|
|
212
|
+
const cached = _packageUrlCache.get(cacheKey);
|
|
213
|
+
if (cached) return cached;
|
|
214
|
+
|
|
195
215
|
const localRoot = VIBE_PATH;
|
|
196
216
|
const vibeRoot = path.join(VIBE_HOME_DIR, 'node_modules', '@su-record', 'vibe');
|
|
197
217
|
const globalRoot = path.join(getGlobalNpmPath(), '@su-record', 'vibe');
|
|
198
218
|
|
|
199
219
|
const candidates = [localRoot, vibeRoot, globalRoot];
|
|
220
|
+
let result = null;
|
|
200
221
|
for (const root of candidates) {
|
|
201
222
|
const target = path.join(root, subpath);
|
|
202
223
|
if (fs.existsSync(path.join(target, probeFile)) && hasRuntimeDeps(root)) {
|
|
203
|
-
|
|
224
|
+
result = toFileUrl(target);
|
|
225
|
+
break;
|
|
204
226
|
}
|
|
205
227
|
}
|
|
206
|
-
|
|
228
|
+
if (result === null) result = toFileUrl(path.join(globalRoot, subpath));
|
|
229
|
+
_packageUrlCache.set(cacheKey, result);
|
|
230
|
+
return result;
|
|
207
231
|
}
|
|
208
232
|
|
|
209
233
|
export function getToolsBaseUrl() {
|
package/package.json
CHANGED
|
@@ -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
|
|