@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.
Files changed (44) hide show
  1. package/CLAUDE.md +4 -5
  2. package/dist/cli/commands/init.d.ts.map +1 -1
  3. package/dist/cli/commands/init.js +1 -3
  4. package/dist/cli/commands/init.js.map +1 -1
  5. package/dist/cli/commands/update.d.ts.map +1 -1
  6. package/dist/cli/commands/update.js +1 -3
  7. package/dist/cli/commands/update.js.map +1 -1
  8. package/dist/cli/postinstall/fs-utils.d.ts +6 -0
  9. package/dist/cli/postinstall/fs-utils.d.ts.map +1 -1
  10. package/dist/cli/postinstall/fs-utils.js +24 -0
  11. package/dist/cli/postinstall/fs-utils.js.map +1 -1
  12. package/dist/cli/postinstall/index.d.ts +1 -1
  13. package/dist/cli/postinstall/index.d.ts.map +1 -1
  14. package/dist/cli/postinstall/index.js +1 -1
  15. package/dist/cli/postinstall/index.js.map +1 -1
  16. package/dist/cli/postinstall/main.d.ts.map +1 -1
  17. package/dist/cli/postinstall/main.js +4 -1
  18. package/dist/cli/postinstall/main.js.map +1 -1
  19. package/dist/cli/setup/GlobalInstaller.d.ts +0 -4
  20. package/dist/cli/setup/GlobalInstaller.d.ts.map +1 -1
  21. package/dist/cli/setup/GlobalInstaller.js +0 -51
  22. package/dist/cli/setup/GlobalInstaller.js.map +1 -1
  23. package/dist/cli/setup/index.d.ts +2 -2
  24. package/dist/cli/setup/index.d.ts.map +1 -1
  25. package/dist/cli/setup/index.js +2 -2
  26. package/dist/cli/setup/index.js.map +1 -1
  27. package/dist/cli/setup.d.ts +2 -2
  28. package/dist/cli/setup.d.ts.map +1 -1
  29. package/dist/cli/setup.js +2 -2
  30. package/dist/cli/setup.js.map +1 -1
  31. package/hooks/scripts/post-edit.js +18 -48
  32. package/hooks/scripts/prompt-dispatcher.js +0 -25
  33. package/package.json +1 -1
  34. package/hooks/scripts/__tests__/skill-injector.test.js +0 -234
  35. package/hooks/scripts/autonomy-controller.js +0 -101
  36. package/hooks/scripts/code-review.js +0 -22
  37. package/hooks/scripts/complexity.js +0 -22
  38. package/hooks/scripts/compound.js +0 -23
  39. package/hooks/scripts/evolution-engine.js +0 -101
  40. package/hooks/scripts/hud-multiline.js +0 -262
  41. package/hooks/scripts/post-tool-verify.js +0 -210
  42. package/hooks/scripts/recall.js +0 -22
  43. package/hooks/scripts/skill-injector.js +0 -654
  44. 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
- });