@su-record/vibe 2.7.0 → 2.7.2
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 +4 -5
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +1 -3
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/update.js +1 -3
- package/dist/cli/commands/update.js.map +1 -1
- package/dist/cli/postinstall/fs-utils.d.ts +6 -0
- package/dist/cli/postinstall/fs-utils.d.ts.map +1 -1
- package/dist/cli/postinstall/fs-utils.js +24 -0
- package/dist/cli/postinstall/fs-utils.js.map +1 -1
- package/dist/cli/postinstall/index.d.ts +1 -1
- package/dist/cli/postinstall/index.d.ts.map +1 -1
- package/dist/cli/postinstall/index.js +1 -1
- package/dist/cli/postinstall/index.js.map +1 -1
- package/dist/cli/postinstall/main.d.ts.map +1 -1
- package/dist/cli/postinstall/main.js +4 -1
- package/dist/cli/postinstall/main.js.map +1 -1
- package/dist/cli/setup/GlobalInstaller.d.ts +0 -4
- package/dist/cli/setup/GlobalInstaller.d.ts.map +1 -1
- package/dist/cli/setup/GlobalInstaller.js +0 -51
- package/dist/cli/setup/GlobalInstaller.js.map +1 -1
- package/dist/cli/setup/index.d.ts +2 -2
- package/dist/cli/setup/index.d.ts.map +1 -1
- package/dist/cli/setup/index.js +2 -2
- package/dist/cli/setup/index.js.map +1 -1
- package/dist/cli/setup.d.ts +2 -2
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +2 -2
- package/dist/cli/setup.js.map +1 -1
- package/hooks/scripts/post-edit.js +18 -48
- package/hooks/scripts/prompt-dispatcher.js +0 -25
- package/package.json +1 -1
- package/hooks/scripts/__tests__/skill-injector.test.js +0 -234
- package/hooks/scripts/autonomy-controller.js +0 -101
- package/hooks/scripts/code-review.js +0 -22
- package/hooks/scripts/complexity.js +0 -22
- package/hooks/scripts/compound.js +0 -23
- package/hooks/scripts/evolution-engine.js +0 -101
- package/hooks/scripts/hud-multiline.js +0 -262
- package/hooks/scripts/post-tool-verify.js +0 -210
- package/hooks/scripts/recall.js +0 -22
- package/hooks/scripts/skill-injector.js +0 -654
- package/hooks/scripts/skill-requirements.js +0 -83
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import os from 'os';
|
|
5
|
-
import {
|
|
6
|
-
loadSkillMetadataOnly,
|
|
7
|
-
loadSkillBody,
|
|
8
|
-
scoreSkillMatch,
|
|
9
|
-
listSkillResources,
|
|
10
|
-
parseSkillFrontmatter,
|
|
11
|
-
} from '../skill-injector.js';
|
|
12
|
-
|
|
13
|
-
// ============================================
|
|
14
|
-
// loadSkillMetadataOnly
|
|
15
|
-
// ============================================
|
|
16
|
-
|
|
17
|
-
describe('loadSkillMetadataOnly', () => {
|
|
18
|
-
const tmpDir = path.join(os.tmpdir(), 'skill-injector-test-' + Date.now());
|
|
19
|
-
|
|
20
|
-
beforeEach(() => {
|
|
21
|
-
if (!fs.existsSync(tmpDir)) fs.mkdirSync(tmpDir, { recursive: true });
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('should parse frontmatter with string, array, and number fields', () => {
|
|
25
|
-
const skillPath = path.join(tmpDir, 'test-skill.md');
|
|
26
|
-
fs.writeFileSync(skillPath, `---
|
|
27
|
-
name: test-skill
|
|
28
|
-
description: A test skill
|
|
29
|
-
triggers: [build, compile, deploy]
|
|
30
|
-
tier: 2
|
|
31
|
-
maxBodyTokens: 500
|
|
32
|
-
---
|
|
33
|
-
# Body content here
|
|
34
|
-
`);
|
|
35
|
-
|
|
36
|
-
const meta = loadSkillMetadataOnly(skillPath);
|
|
37
|
-
expect(meta).not.toBeNull();
|
|
38
|
-
expect(meta.name).toBe('test-skill');
|
|
39
|
-
expect(meta.description).toBe('A test skill');
|
|
40
|
-
expect(meta.triggers).toEqual(['build', 'compile', 'deploy']);
|
|
41
|
-
expect(meta.tier).toBe(2);
|
|
42
|
-
expect(meta.maxBodyTokens).toBe(500);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('should return null for non-frontmatter files', () => {
|
|
46
|
-
const skillPath = path.join(tmpDir, 'no-frontmatter.md');
|
|
47
|
-
fs.writeFileSync(skillPath, '# Just a markdown file\nNo frontmatter here.');
|
|
48
|
-
|
|
49
|
-
const meta = loadSkillMetadataOnly(skillPath);
|
|
50
|
-
expect(meta).toBeNull();
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('should return null for nonexistent files', () => {
|
|
54
|
-
const meta = loadSkillMetadataOnly(path.join(tmpDir, 'nonexistent.md'));
|
|
55
|
-
expect(meta).toBeNull();
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('should parse quoted string values', () => {
|
|
59
|
-
const skillPath = path.join(tmpDir, 'quoted.md');
|
|
60
|
-
fs.writeFileSync(skillPath, `---
|
|
61
|
-
name: "quoted-skill"
|
|
62
|
-
description: 'single quoted'
|
|
63
|
-
---
|
|
64
|
-
body
|
|
65
|
-
`);
|
|
66
|
-
|
|
67
|
-
const meta = loadSkillMetadataOnly(skillPath);
|
|
68
|
-
expect(meta.name).toBe('quoted-skill');
|
|
69
|
-
expect(meta.description).toBe('single quoted');
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('should parse os array field', () => {
|
|
73
|
-
const skillPath = path.join(tmpDir, 'os-skill.md');
|
|
74
|
-
fs.writeFileSync(skillPath, `---
|
|
75
|
-
name: platform-specific
|
|
76
|
-
os: [darwin, linux]
|
|
77
|
-
requires: [ffmpeg, imagemagick]
|
|
78
|
-
---
|
|
79
|
-
body
|
|
80
|
-
`);
|
|
81
|
-
|
|
82
|
-
const meta = loadSkillMetadataOnly(skillPath);
|
|
83
|
-
expect(meta.os).toEqual(['darwin', 'linux']);
|
|
84
|
-
expect(meta.requires).toEqual(['ffmpeg', 'imagemagick']);
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
// ============================================
|
|
89
|
-
// loadSkillBody
|
|
90
|
-
// ============================================
|
|
91
|
-
|
|
92
|
-
describe('loadSkillBody', () => {
|
|
93
|
-
const tmpDir = path.join(os.tmpdir(), 'skill-body-test-' + Date.now());
|
|
94
|
-
|
|
95
|
-
beforeEach(() => {
|
|
96
|
-
if (!fs.existsSync(tmpDir)) fs.mkdirSync(tmpDir, { recursive: true });
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it('should extract body after frontmatter', () => {
|
|
100
|
-
const skillPath = path.join(tmpDir, 'with-body.md');
|
|
101
|
-
fs.writeFileSync(skillPath, `---
|
|
102
|
-
name: test
|
|
103
|
-
---
|
|
104
|
-
# Skill Body
|
|
105
|
-
|
|
106
|
-
Instructions here.`);
|
|
107
|
-
|
|
108
|
-
const body = loadSkillBody(skillPath);
|
|
109
|
-
expect(body).toBe('# Skill Body\n\nInstructions here.');
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it('should return full content if no frontmatter', () => {
|
|
113
|
-
const skillPath = path.join(tmpDir, 'no-fm.md');
|
|
114
|
-
fs.writeFileSync(skillPath, '# Just content\nNo frontmatter.');
|
|
115
|
-
|
|
116
|
-
const body = loadSkillBody(skillPath);
|
|
117
|
-
expect(body).toBe('# Just content\nNo frontmatter.');
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it('should return empty string for nonexistent files', () => {
|
|
121
|
-
const body = loadSkillBody(path.join(tmpDir, 'nope.md'));
|
|
122
|
-
expect(body).toBe('');
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
// ============================================
|
|
127
|
-
// scoreSkillMatch
|
|
128
|
-
// ============================================
|
|
129
|
-
|
|
130
|
-
describe('scoreSkillMatch', () => {
|
|
131
|
-
it('should return 0 for no triggers', () => {
|
|
132
|
-
expect(scoreSkillMatch([], 'build the project')).toBe(0);
|
|
133
|
-
expect(scoreSkillMatch(null, 'build the project')).toBe(0);
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it('should score partial match (substring)', () => {
|
|
137
|
-
const score = scoreSkillMatch(['build'], 'rebuild the project');
|
|
138
|
-
expect(score).toBeGreaterThan(0);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it('should score higher for word boundary match', () => {
|
|
142
|
-
const partialScore = scoreSkillMatch(['build'], 'rebuild the project');
|
|
143
|
-
const exactScore = scoreSkillMatch(['build'], 'build the project');
|
|
144
|
-
expect(exactScore).toBeGreaterThan(partialScore);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it('should accumulate scores for multiple trigger matches', () => {
|
|
148
|
-
const singleScore = scoreSkillMatch(['build'], 'build and deploy');
|
|
149
|
-
const doubleScore = scoreSkillMatch(['build', 'deploy'], 'build and deploy');
|
|
150
|
-
expect(doubleScore).toBeGreaterThan(singleScore);
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it('should be case insensitive', () => {
|
|
154
|
-
const score = scoreSkillMatch(['BUILD'], 'build the project');
|
|
155
|
-
expect(score).toBeGreaterThan(0);
|
|
156
|
-
});
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
// ============================================
|
|
160
|
-
// listSkillResources
|
|
161
|
-
// ============================================
|
|
162
|
-
|
|
163
|
-
describe('listSkillResources', () => {
|
|
164
|
-
const tmpDir = path.join(os.tmpdir(), 'skill-resources-test-' + Date.now());
|
|
165
|
-
|
|
166
|
-
beforeEach(() => {
|
|
167
|
-
if (!fs.existsSync(tmpDir)) fs.mkdirSync(tmpDir, { recursive: true });
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
it('should list files from scripts/, references/, assets/ subdirs', () => {
|
|
171
|
-
const scriptsDir = path.join(tmpDir, 'scripts');
|
|
172
|
-
const assetsDir = path.join(tmpDir, 'assets');
|
|
173
|
-
fs.mkdirSync(scriptsDir, { recursive: true });
|
|
174
|
-
fs.mkdirSync(assetsDir, { recursive: true });
|
|
175
|
-
fs.writeFileSync(path.join(scriptsDir, 'run.sh'), '#!/bin/bash');
|
|
176
|
-
fs.writeFileSync(path.join(assetsDir, 'logo.png'), 'fake-png');
|
|
177
|
-
|
|
178
|
-
const resources = listSkillResources(tmpDir);
|
|
179
|
-
expect(resources).toContain('scripts/run.sh');
|
|
180
|
-
expect(resources).toContain('assets/logo.png');
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
it('should return empty array for dir without subdirs', () => {
|
|
184
|
-
const emptyDir = path.join(tmpDir, 'empty-skill');
|
|
185
|
-
fs.mkdirSync(emptyDir, { recursive: true });
|
|
186
|
-
|
|
187
|
-
const resources = listSkillResources(emptyDir);
|
|
188
|
-
expect(resources).toEqual([]);
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
it('should skip hidden files', () => {
|
|
192
|
-
const scriptsDir = path.join(tmpDir, 'scripts2');
|
|
193
|
-
fs.mkdirSync(scriptsDir, { recursive: true });
|
|
194
|
-
fs.writeFileSync(path.join(scriptsDir, '.hidden'), 'secret');
|
|
195
|
-
fs.writeFileSync(path.join(scriptsDir, 'visible.sh'), 'ok');
|
|
196
|
-
|
|
197
|
-
const parentDir = path.join(tmpDir, 'skip-hidden');
|
|
198
|
-
fs.mkdirSync(parentDir, { recursive: true });
|
|
199
|
-
// Create scripts subdir under parent
|
|
200
|
-
const parentScripts = path.join(parentDir, 'scripts');
|
|
201
|
-
fs.mkdirSync(parentScripts, { recursive: true });
|
|
202
|
-
fs.writeFileSync(path.join(parentScripts, '.hidden'), 'secret');
|
|
203
|
-
fs.writeFileSync(path.join(parentScripts, 'visible.sh'), 'ok');
|
|
204
|
-
|
|
205
|
-
const resources = listSkillResources(parentDir);
|
|
206
|
-
expect(resources).toContain('scripts/visible.sh');
|
|
207
|
-
expect(resources).not.toContain('scripts/.hidden');
|
|
208
|
-
});
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
// ============================================
|
|
212
|
-
// parseSkillFrontmatter (backward compat)
|
|
213
|
-
// ============================================
|
|
214
|
-
|
|
215
|
-
describe('parseSkillFrontmatter', () => {
|
|
216
|
-
it('should parse frontmatter and return metadata + template', () => {
|
|
217
|
-
const content = `---
|
|
218
|
-
name: legacy-skill
|
|
219
|
-
triggers: [test, verify]
|
|
220
|
-
---
|
|
221
|
-
# Legacy body content`;
|
|
222
|
-
|
|
223
|
-
const result = parseSkillFrontmatter(content);
|
|
224
|
-
expect(result).not.toBeNull();
|
|
225
|
-
expect(result.metadata.name).toBe('legacy-skill');
|
|
226
|
-
expect(result.metadata.triggers).toEqual(['test', 'verify']);
|
|
227
|
-
expect(result.template).toBe('# Legacy body content');
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
it('should return null for non-frontmatter content', () => {
|
|
231
|
-
const result = parseSkillFrontmatter('# No frontmatter');
|
|
232
|
-
expect(result).toBeNull();
|
|
233
|
-
});
|
|
234
|
-
});
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Autonomy Controller — /autonomy suggest|auto|disabled|status
|
|
4
|
-
*
|
|
5
|
-
* config.json의 autonomy.mode를 변경하거나 상태를 표시합니다.
|
|
6
|
-
*/
|
|
7
|
-
import fs from 'fs';
|
|
8
|
-
import path from 'path';
|
|
9
|
-
|
|
10
|
-
const prompt = process.argv[2] || '';
|
|
11
|
-
const match = /^\/autonomy\s+(\w+)/i.exec(prompt);
|
|
12
|
-
if (!match) process.exit(0);
|
|
13
|
-
|
|
14
|
-
const action = match[1].toLowerCase();
|
|
15
|
-
const PROJECT_DIR = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
16
|
-
const configPath = path.join(PROJECT_DIR, '.claude', 'vibe', 'config.json');
|
|
17
|
-
|
|
18
|
-
function loadConfig() {
|
|
19
|
-
try {
|
|
20
|
-
if (fs.existsSync(configPath)) {
|
|
21
|
-
return JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
22
|
-
}
|
|
23
|
-
} catch { /* ignore */ }
|
|
24
|
-
return {};
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function saveConfig(config) {
|
|
28
|
-
try {
|
|
29
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
30
|
-
} catch (e) {
|
|
31
|
-
process.stderr.write(`[Autonomy] Config save error: ${e.message}\n`);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const VALID_MODES = ['suggest', 'auto', 'disabled'];
|
|
36
|
-
|
|
37
|
-
if (VALID_MODES.includes(action)) {
|
|
38
|
-
const config = loadConfig();
|
|
39
|
-
if (!config.autonomy) config.autonomy = {};
|
|
40
|
-
config.autonomy.mode = action;
|
|
41
|
-
saveConfig(config);
|
|
42
|
-
|
|
43
|
-
const modeDesc = {
|
|
44
|
-
suggest: 'Suggest mode — 분해 결과만 표시, 사용자 승인 후 실행',
|
|
45
|
-
auto: 'Auto mode — LOW/MEDIUM 자동 실행, HIGH는 오너 확인',
|
|
46
|
-
disabled: 'Disabled — TaskDecomposer 비활성화',
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
console.log(`🤖 Autonomy mode → ${action}`);
|
|
50
|
-
console.log(` ${modeDesc[action]}`);
|
|
51
|
-
} else if (action === 'status') {
|
|
52
|
-
const config = loadConfig();
|
|
53
|
-
const mode = config.autonomy?.mode || 'suggest';
|
|
54
|
-
const sentinelEnabled = config.sentinel?.enabled !== false;
|
|
55
|
-
const proactiveEnabled = config.autonomy?.proactive?.enabled !== false;
|
|
56
|
-
const timeout = config.sentinel?.confirmationTimeout || 300;
|
|
57
|
-
const maxSteps = config.autonomy?.maxConcurrentSteps || 3;
|
|
58
|
-
|
|
59
|
-
console.log(`🤖 Agent Autonomy: ${mode} mode`);
|
|
60
|
-
console.log(` 🛡️ Sentinel: ${sentinelEnabled ? 'enabled' : 'disabled'}`);
|
|
61
|
-
console.log(` 💡 Proactive: ${proactiveEnabled ? 'enabled' : 'disabled'}`);
|
|
62
|
-
console.log(` ⏱️ Confirmation timeout: ${timeout}s`);
|
|
63
|
-
console.log(` 🔄 Max concurrent steps: ${maxSteps}`);
|
|
64
|
-
|
|
65
|
-
// DB 통계 (가능하면)
|
|
66
|
-
try {
|
|
67
|
-
const { getLibBaseUrl } = await import('./utils.js');
|
|
68
|
-
const LIB_BASE = getLibBaseUrl();
|
|
69
|
-
const memMod = await import(`${LIB_BASE}memory/MemoryStorage.js`);
|
|
70
|
-
const storage = new memMod.MemoryStorage(PROJECT_DIR);
|
|
71
|
-
const db = storage.getDatabase();
|
|
72
|
-
|
|
73
|
-
const last24h = new Date(Date.now() - 86_400_000).toISOString();
|
|
74
|
-
|
|
75
|
-
let totalActions = 0;
|
|
76
|
-
let blockedActions = 0;
|
|
77
|
-
let pendingSuggestions = 0;
|
|
78
|
-
let pendingConfirmations = 0;
|
|
79
|
-
|
|
80
|
-
try {
|
|
81
|
-
totalActions = db.prepare('SELECT COUNT(*) as c FROM audit_events WHERE createdAt >= ?').get(last24h)?.c ?? 0;
|
|
82
|
-
blockedActions = db.prepare("SELECT COUNT(*) as c FROM audit_events WHERE createdAt >= ? AND outcome = 'blocked'").get(last24h)?.c ?? 0;
|
|
83
|
-
} catch { /* table may not exist */ }
|
|
84
|
-
|
|
85
|
-
try {
|
|
86
|
-
pendingSuggestions = db.prepare("SELECT COUNT(*) as c FROM suggestions WHERE status = 'pending'").get()?.c ?? 0;
|
|
87
|
-
} catch { /* table may not exist */ }
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
pendingConfirmations = db.prepare("SELECT COUNT(*) as c FROM confirmations WHERE status = 'pending'").get()?.c ?? 0;
|
|
91
|
-
} catch { /* table may not exist */ }
|
|
92
|
-
|
|
93
|
-
if (totalActions > 0 || pendingSuggestions > 0 || pendingConfirmations > 0) {
|
|
94
|
-
console.log(` 📊 Last 24h: ${totalActions} actions (${totalActions - blockedActions} allowed, ${blockedActions} blocked)`);
|
|
95
|
-
if (pendingSuggestions > 0) console.log(` 💡 ${pendingSuggestions} pending suggestions`);
|
|
96
|
-
if (pendingConfirmations > 0) console.log(` ⏳ ${pendingConfirmations} pending confirmations`);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
storage.close();
|
|
100
|
-
} catch { /* DB not available, skip stats */ }
|
|
101
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* UserPromptSubmit Hook - 코드 리뷰 요청 시 품질 검사
|
|
3
|
-
*/
|
|
4
|
-
import { getToolsBaseUrl, PROJECT_DIR } from './utils.js';
|
|
5
|
-
|
|
6
|
-
const BASE_URL = getToolsBaseUrl();
|
|
7
|
-
|
|
8
|
-
async function main() {
|
|
9
|
-
try {
|
|
10
|
-
const module = await import(`${BASE_URL}convention/index.js`);
|
|
11
|
-
const result = await module.validateCodeQuality({
|
|
12
|
-
targetPath: '.',
|
|
13
|
-
projectPath: PROJECT_DIR,
|
|
14
|
-
});
|
|
15
|
-
const lines = result.content[0].text.split('\n').slice(0, 5).join(' | ');
|
|
16
|
-
console.log('[CODE REVIEW]', lines);
|
|
17
|
-
} catch (e) {
|
|
18
|
-
console.log('[CODE REVIEW] Error:', e.message);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
main();
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* UserPromptSubmit Hook - 복잡도 분석
|
|
3
|
-
*/
|
|
4
|
-
import { getToolsBaseUrl, PROJECT_DIR } from './utils.js';
|
|
5
|
-
|
|
6
|
-
const BASE_URL = getToolsBaseUrl();
|
|
7
|
-
|
|
8
|
-
async function main() {
|
|
9
|
-
try {
|
|
10
|
-
const module = await import(`${BASE_URL}convention/index.js`);
|
|
11
|
-
const result = await module.analyzeComplexity({
|
|
12
|
-
targetPath: '.',
|
|
13
|
-
projectPath: PROJECT_DIR,
|
|
14
|
-
});
|
|
15
|
-
const lines = result.content[0].text.split('\n').slice(0, 5).join(' | ');
|
|
16
|
-
console.log('[COMPLEXITY]', lines);
|
|
17
|
-
} catch (e) {
|
|
18
|
-
console.log('[COMPLEXITY] Error:', e.message);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
main();
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* UserPromptSubmit Hook - 버그 해결/PR 머지 시 솔루션 저장
|
|
3
|
-
*/
|
|
4
|
-
import { getToolsBaseUrl, PROJECT_DIR } from './utils.js';
|
|
5
|
-
|
|
6
|
-
const BASE_URL = getToolsBaseUrl();
|
|
7
|
-
|
|
8
|
-
async function main() {
|
|
9
|
-
try {
|
|
10
|
-
const module = await import(`${BASE_URL}memory/index.js`);
|
|
11
|
-
const result = await module.saveMemory({
|
|
12
|
-
key: `solution-${Date.now()}`,
|
|
13
|
-
value: `Solution documented at ${new Date().toISOString()}`,
|
|
14
|
-
category: 'solution',
|
|
15
|
-
projectPath: PROJECT_DIR,
|
|
16
|
-
});
|
|
17
|
-
console.log('[COMPOUND]', result.content[0].text);
|
|
18
|
-
} catch (e) {
|
|
19
|
-
console.log('[COMPOUND] ✗ Error:', e.message);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
main();
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Evolution Engine Hook - PostToolUse (Write/Edit)
|
|
4
|
-
* Triggers insight extraction and optional generation every N tool uses.
|
|
5
|
-
* Fire-and-forget: hook returns immediately, processing runs in background.
|
|
6
|
-
*/
|
|
7
|
-
import { getToolsBaseUrl, getLibBaseUrl, PROJECT_DIR } from './utils.js';
|
|
8
|
-
import fs from 'fs';
|
|
9
|
-
import path from 'path';
|
|
10
|
-
import os from 'os';
|
|
11
|
-
|
|
12
|
-
// Throttle: only run every 5th tool use per session
|
|
13
|
-
let toolUseCount = 0;
|
|
14
|
-
const TRIGGER_EVERY = 5;
|
|
15
|
-
|
|
16
|
-
// Read config
|
|
17
|
-
function getEvolutionConfig() {
|
|
18
|
-
try {
|
|
19
|
-
const configPath = path.join(PROJECT_DIR, '.claude', 'vibe', 'config.json');
|
|
20
|
-
if (fs.existsSync(configPath)) {
|
|
21
|
-
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
22
|
-
return config.evolution || {};
|
|
23
|
-
}
|
|
24
|
-
} catch { /* ignore */ }
|
|
25
|
-
return {};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// stdin에서 hook input 읽기
|
|
29
|
-
let inputData = '';
|
|
30
|
-
for await (const chunk of process.stdin) {
|
|
31
|
-
inputData += chunk;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
let hookInput;
|
|
35
|
-
try {
|
|
36
|
-
hookInput = JSON.parse(inputData);
|
|
37
|
-
} catch {
|
|
38
|
-
process.exit(0);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Only trigger on Write/Edit tool completions
|
|
42
|
-
const toolName = hookInput?.tool_name || '';
|
|
43
|
-
if (toolName !== 'Write' && toolName !== 'Edit') {
|
|
44
|
-
process.exit(0);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
toolUseCount++;
|
|
48
|
-
if (toolUseCount % TRIGGER_EVERY !== 0) {
|
|
49
|
-
process.exit(0);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Check if evolution is enabled
|
|
53
|
-
const config = getEvolutionConfig();
|
|
54
|
-
if (config.enabled === false) {
|
|
55
|
-
process.exit(0);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Fire-and-forget: schedule background processing
|
|
59
|
-
setImmediate(async () => {
|
|
60
|
-
try {
|
|
61
|
-
const LIB_BASE = getLibBaseUrl();
|
|
62
|
-
const [memModule, insightModule, orchestratorModule] = await Promise.all([
|
|
63
|
-
import(`${LIB_BASE}memory/MemoryStorage.js`),
|
|
64
|
-
import(`${LIB_BASE}evolution/InsightExtractor.js`),
|
|
65
|
-
import(`${LIB_BASE}evolution/EvolutionOrchestrator.js`),
|
|
66
|
-
]);
|
|
67
|
-
|
|
68
|
-
const storage = new memModule.MemoryStorage(PROJECT_DIR);
|
|
69
|
-
const extractor = new insightModule.InsightExtractor(storage);
|
|
70
|
-
|
|
71
|
-
// Step 1: Extract insights
|
|
72
|
-
const result = extractor.extractFromRecent(20);
|
|
73
|
-
|
|
74
|
-
if (result.newInsights.length > 0) {
|
|
75
|
-
const mode = config.mode || 'suggest';
|
|
76
|
-
|
|
77
|
-
// Step 2: Generate if new insights found
|
|
78
|
-
const orchestrator = new orchestratorModule.EvolutionOrchestrator(storage, {
|
|
79
|
-
mode,
|
|
80
|
-
maxGenerationsPerCycle: config.maxGenerationsPerCycle || 5,
|
|
81
|
-
minQualityScore: config.minQualityScore || 60,
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
const genResult = orchestrator.generate();
|
|
85
|
-
|
|
86
|
-
if (mode === 'suggest' && genResult.generated.length > 0) {
|
|
87
|
-
// Notify user about new candidates
|
|
88
|
-
process.stderr.write(
|
|
89
|
-
`[Evolution] New candidates: ${genResult.generated.length} generated, ` +
|
|
90
|
-
`${genResult.rejected.length} rejected\n`
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
storage.close();
|
|
96
|
-
} catch (error) {
|
|
97
|
-
process.stderr.write(
|
|
98
|
-
`[Evolution] Background error: ${error instanceof Error ? error.message : 'Unknown'}\n`
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
});
|