@su-record/vibe 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +448 -0
- package/agents/backend-python-expert.md +453 -0
- package/agents/database-postgres-expert.md +538 -0
- package/agents/frontend-flutter-expert.md +487 -0
- package/agents/frontend-react-expert.md +424 -0
- package/agents/quality-reviewer.md +542 -0
- package/agents/specification-agent.md +505 -0
- package/bin/sutory +332 -0
- package/bin/vibe +338 -0
- package/mcp/dist/__tests__/complexity.test.js +126 -0
- package/mcp/dist/__tests__/memory.test.js +120 -0
- package/mcp/dist/__tests__/python-dart-complexity.test.js +146 -0
- package/mcp/dist/index.js +230 -0
- package/mcp/dist/lib/ContextCompressor.js +305 -0
- package/mcp/dist/lib/MemoryManager.js +334 -0
- package/mcp/dist/lib/ProjectCache.js +126 -0
- package/mcp/dist/lib/PythonParser.js +241 -0
- package/mcp/dist/tools/browser/browserPool.js +76 -0
- package/mcp/dist/tools/browser/browserUtils.js +135 -0
- package/mcp/dist/tools/browser/inspectNetworkRequests.js +140 -0
- package/mcp/dist/tools/browser/monitorConsoleLogs.js +97 -0
- package/mcp/dist/tools/convention/analyzeComplexity.js +248 -0
- package/mcp/dist/tools/convention/applyQualityRules.js +102 -0
- package/mcp/dist/tools/convention/checkCouplingCohesion.js +233 -0
- package/mcp/dist/tools/convention/complexityMetrics.js +133 -0
- package/mcp/dist/tools/convention/dartComplexity.js +117 -0
- package/mcp/dist/tools/convention/getCodingGuide.js +64 -0
- package/mcp/dist/tools/convention/languageDetector.js +50 -0
- package/mcp/dist/tools/convention/pythonComplexity.js +109 -0
- package/mcp/dist/tools/convention/suggestImprovements.js +257 -0
- package/mcp/dist/tools/convention/validateCodeQuality.js +177 -0
- package/mcp/dist/tools/memory/autoSaveContext.js +79 -0
- package/mcp/dist/tools/memory/database.js +123 -0
- package/mcp/dist/tools/memory/deleteMemory.js +39 -0
- package/mcp/dist/tools/memory/listMemories.js +38 -0
- package/mcp/dist/tools/memory/memoryConfig.js +27 -0
- package/mcp/dist/tools/memory/memorySQLite.js +138 -0
- package/mcp/dist/tools/memory/memoryUtils.js +34 -0
- package/mcp/dist/tools/memory/migrate.js +113 -0
- package/mcp/dist/tools/memory/prioritizeMemory.js +109 -0
- package/mcp/dist/tools/memory/recallMemory.js +40 -0
- package/mcp/dist/tools/memory/restoreSessionContext.js +69 -0
- package/mcp/dist/tools/memory/saveMemory.js +34 -0
- package/mcp/dist/tools/memory/searchMemories.js +37 -0
- package/mcp/dist/tools/memory/startSession.js +100 -0
- package/mcp/dist/tools/memory/updateMemory.js +46 -0
- package/mcp/dist/tools/planning/analyzeRequirements.js +166 -0
- package/mcp/dist/tools/planning/createUserStories.js +119 -0
- package/mcp/dist/tools/planning/featureRoadmap.js +202 -0
- package/mcp/dist/tools/planning/generatePrd.js +156 -0
- package/mcp/dist/tools/prompt/analyzePrompt.js +145 -0
- package/mcp/dist/tools/prompt/enhancePrompt.js +105 -0
- package/mcp/dist/tools/semantic/findReferences.js +195 -0
- package/mcp/dist/tools/semantic/findSymbol.js +200 -0
- package/mcp/dist/tools/thinking/analyzeProblem.js +50 -0
- package/mcp/dist/tools/thinking/breakDownProblem.js +140 -0
- package/mcp/dist/tools/thinking/createThinkingChain.js +39 -0
- package/mcp/dist/tools/thinking/formatAsPlan.js +73 -0
- package/mcp/dist/tools/thinking/stepByStepAnalysis.js +58 -0
- package/mcp/dist/tools/thinking/thinkAloudProcess.js +75 -0
- package/mcp/dist/tools/time/getCurrentTime.js +61 -0
- package/mcp/dist/tools/ui/previewUiAscii.js +232 -0
- package/mcp/dist/types/tool.js +2 -0
- package/mcp/package.json +53 -0
- package/package.json +49 -0
- package/scripts/install-mcp.js +48 -0
- package/scripts/install.sh +70 -0
- package/skills/core/communication-guide.md +104 -0
- package/skills/core/development-philosophy.md +53 -0
- package/skills/core/quick-start.md +121 -0
- package/skills/languages/dart-flutter.md +509 -0
- package/skills/languages/python-fastapi.md +386 -0
- package/skills/languages/typescript-nextjs.md +441 -0
- package/skills/languages/typescript-react-native.md +446 -0
- package/skills/languages/typescript-react.md +525 -0
- package/skills/quality/checklist.md +276 -0
- package/skills/quality/testing-strategy.md +437 -0
- package/skills/standards/anti-patterns.md +369 -0
- package/skills/standards/code-structure.md +291 -0
- package/skills/standards/complexity-metrics.md +312 -0
- package/skills/standards/naming-conventions.md +198 -0
- package/skills/tools/mcp-hi-ai-guide.md +665 -0
- package/skills/tools/mcp-workflow.md +51 -0
- package/templates/constitution-template.md +193 -0
- package/templates/plan-template.md +237 -0
- package/templates/spec-template.md +142 -0
- package/templates/tasks-template.md +132 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { describe, test, expect } from 'vitest';
|
|
2
|
+
import { analyzeComplexity, analyzeComplexityDefinition } from '../tools/convention/analyzeComplexity.js';
|
|
3
|
+
describe('Code Complexity Analysis', () => {
|
|
4
|
+
describe('analyzeComplexity', () => {
|
|
5
|
+
test('should analyze simple function with low complexity', async () => {
|
|
6
|
+
const simpleCode = `
|
|
7
|
+
function add(a, b) {
|
|
8
|
+
return a + b;
|
|
9
|
+
}
|
|
10
|
+
`;
|
|
11
|
+
const result = await analyzeComplexity({ code: simpleCode, metrics: 'all' });
|
|
12
|
+
const parsed = JSON.parse(result.content[0].text.replace('Complexity Analysis:\n', ''));
|
|
13
|
+
expect(parsed.status).toBe('success');
|
|
14
|
+
expect(parsed.results.cyclomaticComplexity).toBeDefined();
|
|
15
|
+
expect(parsed.results.cognitiveComplexity).toBeDefined();
|
|
16
|
+
expect(parsed.results.halsteadMetrics).toBeDefined();
|
|
17
|
+
expect(parsed.results.cyclomaticComplexity.status).toBe('pass');
|
|
18
|
+
});
|
|
19
|
+
test('should detect high cyclomatic complexity', async () => {
|
|
20
|
+
const complexCode = `
|
|
21
|
+
function complexFunction(x) {
|
|
22
|
+
if (x > 0) {
|
|
23
|
+
if (x < 10) {
|
|
24
|
+
for (let i = 0; i < x; i++) {
|
|
25
|
+
if (i % 2 === 0) {
|
|
26
|
+
while (i < 5) {
|
|
27
|
+
if (i === 3) {
|
|
28
|
+
return i;
|
|
29
|
+
}
|
|
30
|
+
i++;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return x;
|
|
37
|
+
}
|
|
38
|
+
`;
|
|
39
|
+
const result = await analyzeComplexity({ code: complexCode, metrics: 'all' });
|
|
40
|
+
const parsed = JSON.parse(result.content[0].text.replace('Complexity Analysis:\n', ''));
|
|
41
|
+
expect(parsed.results.cyclomaticComplexity.value).toBeGreaterThan(5);
|
|
42
|
+
expect(parsed.issues.length).toBeGreaterThan(0);
|
|
43
|
+
});
|
|
44
|
+
test('should calculate AST-based complexity', async () => {
|
|
45
|
+
const code = `
|
|
46
|
+
function test() {
|
|
47
|
+
if (true) return 1;
|
|
48
|
+
for (let i = 0; i < 10; i++) {
|
|
49
|
+
console.log(i);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
`;
|
|
53
|
+
const result = await analyzeComplexity({ code, metrics: 'cyclomatic' });
|
|
54
|
+
const parsed = JSON.parse(result.content[0].text.replace('Complexity Analysis:\n', ''));
|
|
55
|
+
expect(parsed.results.astCyclomaticComplexity).toBeDefined();
|
|
56
|
+
expect(parsed.results.astCyclomaticComplexity.value).toBeGreaterThan(1);
|
|
57
|
+
});
|
|
58
|
+
test('should calculate cognitive complexity with nesting', async () => {
|
|
59
|
+
const code = `
|
|
60
|
+
function nested() {
|
|
61
|
+
if (a) {
|
|
62
|
+
if (b) {
|
|
63
|
+
if (c) {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
`;
|
|
70
|
+
const result = await analyzeComplexity({ code, metrics: 'cognitive' });
|
|
71
|
+
const parsed = JSON.parse(result.content[0].text.replace('Complexity Analysis:\n', ''));
|
|
72
|
+
expect(parsed.results.cognitiveComplexity.value).toBeGreaterThan(0);
|
|
73
|
+
});
|
|
74
|
+
test('should calculate Halstead metrics', async () => {
|
|
75
|
+
const code = `
|
|
76
|
+
function calculate(x, y) {
|
|
77
|
+
const sum = x + y;
|
|
78
|
+
const product = x * y;
|
|
79
|
+
return sum + product;
|
|
80
|
+
}
|
|
81
|
+
`;
|
|
82
|
+
const result = await analyzeComplexity({ code, metrics: 'halstead' });
|
|
83
|
+
const parsed = JSON.parse(result.content[0].text.replace('Complexity Analysis:\n', ''));
|
|
84
|
+
expect(parsed.results.halsteadMetrics).toBeDefined();
|
|
85
|
+
expect(parsed.results.halsteadMetrics.vocabulary).toBeGreaterThan(0);
|
|
86
|
+
expect(parsed.results.halsteadMetrics.difficulty).toBeGreaterThan(0);
|
|
87
|
+
});
|
|
88
|
+
test('should provide recommendations for complex code', async () => {
|
|
89
|
+
const veryComplexCode = `
|
|
90
|
+
function veryComplex(a, b, c, d, e) {
|
|
91
|
+
if (a) {
|
|
92
|
+
if (b) {
|
|
93
|
+
for (let i = 0; i < 10; i++) {
|
|
94
|
+
if (c) {
|
|
95
|
+
while (d) {
|
|
96
|
+
if (e) {
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
`;
|
|
107
|
+
const result = await analyzeComplexity({ code: veryComplexCode, metrics: 'all' });
|
|
108
|
+
const parsed = JSON.parse(result.content[0].text.replace('Complexity Analysis:\n', ''));
|
|
109
|
+
expect(parsed.recommendations).toBeDefined();
|
|
110
|
+
expect(parsed.recommendations.length).toBeGreaterThan(0);
|
|
111
|
+
expect(parsed.overallScore).toBeLessThan(100);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
describe('Tool Definition', () => {
|
|
115
|
+
test('should have correct definition', () => {
|
|
116
|
+
expect(analyzeComplexityDefinition.name).toBe('analyze_complexity');
|
|
117
|
+
expect(analyzeComplexityDefinition.description).toContain('IMPORTANT');
|
|
118
|
+
expect(analyzeComplexityDefinition.inputSchema.required).toContain('code');
|
|
119
|
+
});
|
|
120
|
+
test('should include keyword triggers', () => {
|
|
121
|
+
const desc = analyzeComplexityDefinition.description;
|
|
122
|
+
expect(desc).toContain('복잡도');
|
|
123
|
+
expect(desc).toContain('complexity');
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
});
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { saveMemory, saveMemoryDefinition } from '../tools/memory/saveMemory.js';
|
|
3
|
+
import { recallMemory } from '../tools/memory/recallMemory.js';
|
|
4
|
+
import { listMemories } from '../tools/memory/listMemories.js';
|
|
5
|
+
import { deleteMemory } from '../tools/memory/deleteMemory.js';
|
|
6
|
+
import { memoryDB } from '../tools/memory/database.js';
|
|
7
|
+
import { clearAllMemories } from '../tools/memory/memorySQLite.js';
|
|
8
|
+
import { promises as fs } from 'fs';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
const TEST_MEMORY_DIR = path.join(process.cwd(), 'memories');
|
|
11
|
+
describe('Memory Management Tools', () => {
|
|
12
|
+
beforeEach(async () => {
|
|
13
|
+
// Ensure fresh database for each test
|
|
14
|
+
try {
|
|
15
|
+
// Close existing connection if any
|
|
16
|
+
memoryDB.close();
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
// Ignore
|
|
20
|
+
}
|
|
21
|
+
// Clean up test files
|
|
22
|
+
try {
|
|
23
|
+
await fs.rm(TEST_MEMORY_DIR, { recursive: true, force: true });
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
// Ignore
|
|
27
|
+
}
|
|
28
|
+
// Clear all memories (this will recreate DB if needed)
|
|
29
|
+
try {
|
|
30
|
+
clearAllMemories();
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
// Database might not exist yet, will be created on first use
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
afterEach(async () => {
|
|
37
|
+
// Close database connection
|
|
38
|
+
try {
|
|
39
|
+
memoryDB.close();
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
// Ignore
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
describe('saveMemory', () => {
|
|
46
|
+
test('should save memory successfully', async () => {
|
|
47
|
+
const result = await saveMemory({
|
|
48
|
+
key: 'test-key',
|
|
49
|
+
value: 'test-value',
|
|
50
|
+
category: 'project'
|
|
51
|
+
});
|
|
52
|
+
expect(result.content[0].text).toContain('success');
|
|
53
|
+
expect(result.content[0].text).toContain('test-key');
|
|
54
|
+
});
|
|
55
|
+
test('should create memory database if not exists', async () => {
|
|
56
|
+
await saveMemory({ key: 'test-key', value: 'test-value' });
|
|
57
|
+
// Check if database file exists
|
|
58
|
+
const dbPath = path.join(TEST_MEMORY_DIR, 'memories.db');
|
|
59
|
+
const dbExists = await fs.access(dbPath).then(() => true).catch(() => false);
|
|
60
|
+
expect(dbExists).toBe(true);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
describe('recallMemory', () => {
|
|
64
|
+
test('should recall saved memory', async () => {
|
|
65
|
+
await saveMemory({ key: 'recall-test', value: 'recall-value' });
|
|
66
|
+
const result = await recallMemory({ key: 'recall-test' });
|
|
67
|
+
const parsedResult = JSON.parse(result.content[0].text.replace('Memory recalled:\n', ''));
|
|
68
|
+
expect(parsedResult.value).toBe('recall-value');
|
|
69
|
+
expect(parsedResult.key).toBe('recall-test');
|
|
70
|
+
});
|
|
71
|
+
test('should return not found for non-existent key', async () => {
|
|
72
|
+
const result = await recallMemory({ key: 'non-existent' });
|
|
73
|
+
expect(result.content[0].text).toContain('not found');
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
describe('listMemories', () => {
|
|
77
|
+
test('should list all memories', async () => {
|
|
78
|
+
await saveMemory({ key: 'key1', value: 'value1', category: 'project' });
|
|
79
|
+
await saveMemory({ key: 'key2', value: 'value2', category: 'code' });
|
|
80
|
+
const result = await listMemories({ summary: false });
|
|
81
|
+
const parsed = JSON.parse(result.content[0].text.replace('Memory list:\n', ''));
|
|
82
|
+
expect(parsed.memories.length).toBe(2);
|
|
83
|
+
expect(parsed.total).toBe(2);
|
|
84
|
+
});
|
|
85
|
+
test('should filter memories by category', async () => {
|
|
86
|
+
await saveMemory({ key: 'project1', value: 'val1', category: 'project' });
|
|
87
|
+
await saveMemory({ key: 'code1', value: 'val2', category: 'code' });
|
|
88
|
+
const result = await listMemories({ category: 'project', summary: false });
|
|
89
|
+
const parsed = JSON.parse(result.content[0].text.replace('Memory list:\n', ''));
|
|
90
|
+
expect(parsed.total).toBe(1);
|
|
91
|
+
expect(parsed.memories[0].key).toBe('project1');
|
|
92
|
+
});
|
|
93
|
+
test('should return empty array when no memories exist', async () => {
|
|
94
|
+
const result = await listMemories({ summary: false });
|
|
95
|
+
const parsed = JSON.parse(result.content[0].text.replace('Memory list:\n', ''));
|
|
96
|
+
expect(parsed.memories).toEqual([]);
|
|
97
|
+
expect(parsed.total).toBe(0);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
describe('deleteMemory', () => {
|
|
101
|
+
test('should delete existing memory', async () => {
|
|
102
|
+
await saveMemory({ key: 'delete-me', value: 'temporary' });
|
|
103
|
+
const deleteResult = await deleteMemory({ key: 'delete-me' });
|
|
104
|
+
expect(deleteResult.content[0].text).toContain('success');
|
|
105
|
+
const recallResult = await recallMemory({ key: 'delete-me' });
|
|
106
|
+
expect(recallResult.content[0].text).toContain('not found');
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
describe('Tool Definition', () => {
|
|
110
|
+
test('should have correct structure', () => {
|
|
111
|
+
expect(saveMemoryDefinition.name).toBe('save_memory');
|
|
112
|
+
expect(saveMemoryDefinition.description).toContain('IMPORTANT');
|
|
113
|
+
expect(saveMemoryDefinition.inputSchema.required).toContain('key');
|
|
114
|
+
});
|
|
115
|
+
test('should include keyword triggers', () => {
|
|
116
|
+
expect(saveMemoryDefinition.description).toContain('기억해');
|
|
117
|
+
expect(saveMemoryDefinition.description).toContain('remember');
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
});
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Python and Dart/Flutter code complexity analysis
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect } from 'vitest';
|
|
5
|
+
import { detectLanguage } from '../tools/convention/languageDetector.js';
|
|
6
|
+
import { calculatePythonComplexity, calculatePythonCognitiveComplexity } from '../tools/convention/pythonComplexity.js';
|
|
7
|
+
import { calculateDartComplexity, calculateDartCognitiveComplexity } from '../tools/convention/dartComplexity.js';
|
|
8
|
+
import { analyzeComplexity } from '../tools/convention/analyzeComplexity.js';
|
|
9
|
+
describe('Language Detection', () => {
|
|
10
|
+
it('should detect Python code', () => {
|
|
11
|
+
const pythonCode = `
|
|
12
|
+
def calculate_sum(numbers):
|
|
13
|
+
total = 0
|
|
14
|
+
for num in numbers:
|
|
15
|
+
total += num
|
|
16
|
+
return total
|
|
17
|
+
`;
|
|
18
|
+
expect(detectLanguage(pythonCode)).toBe('python');
|
|
19
|
+
});
|
|
20
|
+
it('should detect Dart/Flutter code', () => {
|
|
21
|
+
const dartCode = `
|
|
22
|
+
class MyWidget extends StatelessWidget {
|
|
23
|
+
@override
|
|
24
|
+
Widget build(BuildContext context) {
|
|
25
|
+
return Container(
|
|
26
|
+
child: Text('Hello'),
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
`;
|
|
31
|
+
expect(detectLanguage(dartCode)).toBe('dart');
|
|
32
|
+
});
|
|
33
|
+
it('should detect TypeScript code', () => {
|
|
34
|
+
const tsCode = `
|
|
35
|
+
interface User {
|
|
36
|
+
name: string;
|
|
37
|
+
age: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function greet(user: User): void {
|
|
41
|
+
console.log(\`Hello, \${user.name}\`);
|
|
42
|
+
}
|
|
43
|
+
`;
|
|
44
|
+
expect(detectLanguage(tsCode)).toBe('typescript');
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
describe('Python Complexity Analysis', () => {
|
|
48
|
+
it('should calculate Python cyclomatic complexity', () => {
|
|
49
|
+
const pythonCode = `
|
|
50
|
+
def complex_function(x, y, z):
|
|
51
|
+
if x > 0:
|
|
52
|
+
if y > 0:
|
|
53
|
+
return x + y
|
|
54
|
+
elif y < 0:
|
|
55
|
+
return x - y
|
|
56
|
+
elif z > 0:
|
|
57
|
+
for i in range(10):
|
|
58
|
+
if i % 2 == 0:
|
|
59
|
+
print(i)
|
|
60
|
+
return 0
|
|
61
|
+
`;
|
|
62
|
+
const result = calculatePythonComplexity(pythonCode);
|
|
63
|
+
expect(result.value).toBeGreaterThan(5);
|
|
64
|
+
expect(result.status).toBeDefined();
|
|
65
|
+
});
|
|
66
|
+
it('should calculate Python cognitive complexity', () => {
|
|
67
|
+
const pythonCode = `
|
|
68
|
+
def nested_function(items):
|
|
69
|
+
for item in items:
|
|
70
|
+
if item > 0:
|
|
71
|
+
if item % 2 == 0:
|
|
72
|
+
print("Even positive")
|
|
73
|
+
return items
|
|
74
|
+
`;
|
|
75
|
+
const result = calculatePythonCognitiveComplexity(pythonCode);
|
|
76
|
+
expect(result.value).toBeGreaterThan(0);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
describe('Dart Complexity Analysis', () => {
|
|
80
|
+
it('should calculate Dart cyclomatic complexity', () => {
|
|
81
|
+
const dartCode = `
|
|
82
|
+
Widget buildWidget(bool condition) {
|
|
83
|
+
if (condition) {
|
|
84
|
+
return Text('Yes');
|
|
85
|
+
} else {
|
|
86
|
+
return Text('No');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
`;
|
|
90
|
+
const result = calculateDartComplexity(dartCode);
|
|
91
|
+
expect(result.value).toBeGreaterThan(1);
|
|
92
|
+
});
|
|
93
|
+
it('should calculate Dart cognitive complexity', () => {
|
|
94
|
+
const dartCode = `
|
|
95
|
+
class MyWidget extends StatelessWidget {
|
|
96
|
+
@override
|
|
97
|
+
Widget build(BuildContext context) {
|
|
98
|
+
return condition1
|
|
99
|
+
? Text('A')
|
|
100
|
+
: condition2
|
|
101
|
+
? Text('B')
|
|
102
|
+
: Text('C');
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
`;
|
|
106
|
+
const result = calculateDartCognitiveComplexity(dartCode);
|
|
107
|
+
expect(result.value).toBeGreaterThanOrEqual(0); // Ternary operators may not be counted
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
describe('Integrated Complexity Analysis', () => {
|
|
111
|
+
it('should analyze Python code with auto-detection', async () => {
|
|
112
|
+
const pythonCode = `
|
|
113
|
+
def fibonacci(n):
|
|
114
|
+
if n <= 1:
|
|
115
|
+
return n
|
|
116
|
+
return fibonacci(n-1) + fibonacci(n-2)
|
|
117
|
+
`;
|
|
118
|
+
const result = await analyzeComplexity({ code: pythonCode });
|
|
119
|
+
expect(result.content[0].text).toContain('Python');
|
|
120
|
+
expect(result.content[0].text).toContain('cyclomaticComplexity');
|
|
121
|
+
});
|
|
122
|
+
it('should analyze Dart code with auto-detection', async () => {
|
|
123
|
+
const dartCode = `
|
|
124
|
+
class Counter extends StatefulWidget {
|
|
125
|
+
@override
|
|
126
|
+
_CounterState createState() => _CounterState();
|
|
127
|
+
}
|
|
128
|
+
`;
|
|
129
|
+
const result = await analyzeComplexity({ code: dartCode });
|
|
130
|
+
expect(result.content[0].text).toContain('Dart');
|
|
131
|
+
});
|
|
132
|
+
it('should analyze TypeScript code', async () => {
|
|
133
|
+
const tsCode = `
|
|
134
|
+
function complexFunc(a: number, b: number): number {
|
|
135
|
+
if (a > b) {
|
|
136
|
+
return a;
|
|
137
|
+
} else if (a < b) {
|
|
138
|
+
return b;
|
|
139
|
+
}
|
|
140
|
+
return 0;
|
|
141
|
+
}
|
|
142
|
+
`;
|
|
143
|
+
const result = await analyzeComplexity({ code: tsCode });
|
|
144
|
+
expect(result.content[0].text).toContain('TypeScript');
|
|
145
|
+
});
|
|
146
|
+
});
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
|
|
5
|
+
// Import all tool definitions and handlers
|
|
6
|
+
import { getCurrentTimeDefinition, getCurrentTime } from './tools/time/getCurrentTime.js';
|
|
7
|
+
// Semantic code analysis tools (Serena-inspired)
|
|
8
|
+
import { findSymbolDefinition, findSymbol } from './tools/semantic/findSymbol.js';
|
|
9
|
+
import { findReferencesDefinition, findReferences } from './tools/semantic/findReferences.js';
|
|
10
|
+
import { createThinkingChainDefinition, createThinkingChain } from './tools/thinking/createThinkingChain.js';
|
|
11
|
+
import { analyzeProblemDefinition, analyzeProblem } from './tools/thinking/analyzeProblem.js';
|
|
12
|
+
import { stepByStepAnalysisDefinition, stepByStepAnalysis } from './tools/thinking/stepByStepAnalysis.js';
|
|
13
|
+
import { breakDownProblemDefinition, breakDownProblem } from './tools/thinking/breakDownProblem.js';
|
|
14
|
+
import { thinkAloudProcessDefinition, thinkAloudProcess } from './tools/thinking/thinkAloudProcess.js';
|
|
15
|
+
import { formatAsPlanDefinition, formatAsPlan } from './tools/thinking/formatAsPlan.js';
|
|
16
|
+
import { monitorConsoleLogsDefinition, monitorConsoleLogs } from './tools/browser/monitorConsoleLogs.js';
|
|
17
|
+
import { inspectNetworkRequestsDefinition, inspectNetworkRequests } from './tools/browser/inspectNetworkRequests.js';
|
|
18
|
+
import { saveMemoryDefinition, saveMemory } from './tools/memory/saveMemory.js';
|
|
19
|
+
import { recallMemoryDefinition, recallMemory } from './tools/memory/recallMemory.js';
|
|
20
|
+
import { listMemoriesDefinition, listMemories } from './tools/memory/listMemories.js';
|
|
21
|
+
import { deleteMemoryDefinition, deleteMemory } from './tools/memory/deleteMemory.js';
|
|
22
|
+
import { searchMemoriesDefinition, searchMemoriesHandler } from './tools/memory/searchMemories.js';
|
|
23
|
+
import { updateMemoryDefinition, updateMemory } from './tools/memory/updateMemory.js';
|
|
24
|
+
import { autoSaveContextDefinition, autoSaveContext } from './tools/memory/autoSaveContext.js';
|
|
25
|
+
import { restoreSessionContextDefinition, restoreSessionContext } from './tools/memory/restoreSessionContext.js';
|
|
26
|
+
import { prioritizeMemoryDefinition, prioritizeMemory } from './tools/memory/prioritizeMemory.js';
|
|
27
|
+
import { startSessionDefinition, startSession } from './tools/memory/startSession.js';
|
|
28
|
+
import { getCodingGuideDefinition, getCodingGuide } from './tools/convention/getCodingGuide.js';
|
|
29
|
+
import { applyQualityRulesDefinition, applyQualityRules } from './tools/convention/applyQualityRules.js';
|
|
30
|
+
import { validateCodeQualityDefinition, validateCodeQuality } from './tools/convention/validateCodeQuality.js';
|
|
31
|
+
import { analyzeComplexityDefinition, analyzeComplexity } from './tools/convention/analyzeComplexity.js';
|
|
32
|
+
import { checkCouplingCohesionDefinition, checkCouplingCohesion } from './tools/convention/checkCouplingCohesion.js';
|
|
33
|
+
import { suggestImprovementsDefinition, suggestImprovements } from './tools/convention/suggestImprovements.js';
|
|
34
|
+
import { generatePrdDefinition, generatePrd } from './tools/planning/generatePrd.js';
|
|
35
|
+
import { createUserStoriesDefinition, createUserStories } from './tools/planning/createUserStories.js';
|
|
36
|
+
import { analyzeRequirementsDefinition, analyzeRequirements } from './tools/planning/analyzeRequirements.js';
|
|
37
|
+
import { featureRoadmapDefinition, featureRoadmap } from './tools/planning/featureRoadmap.js';
|
|
38
|
+
import { enhancePromptDefinition, enhancePrompt } from './tools/prompt/enhancePrompt.js';
|
|
39
|
+
import { analyzePromptDefinition, analyzePrompt } from './tools/prompt/analyzePrompt.js';
|
|
40
|
+
import { previewUiAsciiDefinition, previewUiAscii } from './tools/ui/previewUiAscii.js';
|
|
41
|
+
// Collect all tool definitions
|
|
42
|
+
const tools = [
|
|
43
|
+
// Time Utility Tools
|
|
44
|
+
getCurrentTimeDefinition,
|
|
45
|
+
// Semantic Code Analysis Tools (Serena-inspired)
|
|
46
|
+
findSymbolDefinition,
|
|
47
|
+
findReferencesDefinition,
|
|
48
|
+
// Sequential Thinking Tools
|
|
49
|
+
createThinkingChainDefinition,
|
|
50
|
+
analyzeProblemDefinition,
|
|
51
|
+
stepByStepAnalysisDefinition,
|
|
52
|
+
breakDownProblemDefinition,
|
|
53
|
+
thinkAloudProcessDefinition,
|
|
54
|
+
formatAsPlanDefinition,
|
|
55
|
+
// Browser Development Tools
|
|
56
|
+
monitorConsoleLogsDefinition,
|
|
57
|
+
inspectNetworkRequestsDefinition,
|
|
58
|
+
// Memory Management Tools
|
|
59
|
+
saveMemoryDefinition,
|
|
60
|
+
recallMemoryDefinition,
|
|
61
|
+
listMemoriesDefinition,
|
|
62
|
+
deleteMemoryDefinition,
|
|
63
|
+
searchMemoriesDefinition,
|
|
64
|
+
updateMemoryDefinition,
|
|
65
|
+
autoSaveContextDefinition,
|
|
66
|
+
restoreSessionContextDefinition,
|
|
67
|
+
prioritizeMemoryDefinition,
|
|
68
|
+
startSessionDefinition,
|
|
69
|
+
// Convention Tools
|
|
70
|
+
getCodingGuideDefinition,
|
|
71
|
+
applyQualityRulesDefinition,
|
|
72
|
+
validateCodeQualityDefinition,
|
|
73
|
+
analyzeComplexityDefinition,
|
|
74
|
+
checkCouplingCohesionDefinition,
|
|
75
|
+
suggestImprovementsDefinition,
|
|
76
|
+
// Planning Tools
|
|
77
|
+
generatePrdDefinition,
|
|
78
|
+
createUserStoriesDefinition,
|
|
79
|
+
analyzeRequirementsDefinition,
|
|
80
|
+
featureRoadmapDefinition,
|
|
81
|
+
// Prompt Enhancement Tools
|
|
82
|
+
enhancePromptDefinition,
|
|
83
|
+
analyzePromptDefinition,
|
|
84
|
+
// UI Preview Tools
|
|
85
|
+
previewUiAsciiDefinition
|
|
86
|
+
];
|
|
87
|
+
function createServer() {
|
|
88
|
+
const server = new Server({
|
|
89
|
+
name: 'Hi-AI',
|
|
90
|
+
version: '1.3.0',
|
|
91
|
+
}, {
|
|
92
|
+
capabilities: {
|
|
93
|
+
tools: {},
|
|
94
|
+
prompts: {},
|
|
95
|
+
resources: {},
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
99
|
+
return { tools };
|
|
100
|
+
});
|
|
101
|
+
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
102
|
+
const { name, arguments: args } = request.params;
|
|
103
|
+
try {
|
|
104
|
+
switch (name) {
|
|
105
|
+
// Time Utility Tools
|
|
106
|
+
case 'get_current_time':
|
|
107
|
+
return await getCurrentTime(args);
|
|
108
|
+
// Semantic Code Analysis Tools
|
|
109
|
+
case 'find_symbol':
|
|
110
|
+
return await findSymbol(args);
|
|
111
|
+
case 'find_references':
|
|
112
|
+
return await findReferences(args);
|
|
113
|
+
// Sequential Thinking Tools
|
|
114
|
+
case 'create_thinking_chain':
|
|
115
|
+
return await createThinkingChain(args);
|
|
116
|
+
case 'analyze_problem':
|
|
117
|
+
return await analyzeProblem(args);
|
|
118
|
+
case 'step_by_step_analysis':
|
|
119
|
+
return await stepByStepAnalysis(args);
|
|
120
|
+
case 'break_down_problem':
|
|
121
|
+
return await breakDownProblem(args);
|
|
122
|
+
case 'think_aloud_process':
|
|
123
|
+
return await thinkAloudProcess(args);
|
|
124
|
+
case 'format_as_plan':
|
|
125
|
+
return await formatAsPlan(args);
|
|
126
|
+
// Browser Development Tools
|
|
127
|
+
case 'monitor_console_logs':
|
|
128
|
+
return await monitorConsoleLogs(args);
|
|
129
|
+
case 'inspect_network_requests':
|
|
130
|
+
return await inspectNetworkRequests(args);
|
|
131
|
+
// Memory Management Tools
|
|
132
|
+
case 'save_memory':
|
|
133
|
+
return await saveMemory(args);
|
|
134
|
+
case 'recall_memory':
|
|
135
|
+
return await recallMemory(args);
|
|
136
|
+
case 'list_memories':
|
|
137
|
+
return await listMemories(args);
|
|
138
|
+
case 'delete_memory':
|
|
139
|
+
return await deleteMemory(args);
|
|
140
|
+
case 'search_memories':
|
|
141
|
+
return await searchMemoriesHandler(args);
|
|
142
|
+
case 'update_memory':
|
|
143
|
+
return await updateMemory(args);
|
|
144
|
+
case 'auto_save_context':
|
|
145
|
+
return await autoSaveContext(args);
|
|
146
|
+
case 'restore_session_context':
|
|
147
|
+
return await restoreSessionContext(args);
|
|
148
|
+
case 'prioritize_memory':
|
|
149
|
+
return await prioritizeMemory(args);
|
|
150
|
+
case 'start_session':
|
|
151
|
+
return await startSession(args);
|
|
152
|
+
// Convention Tools
|
|
153
|
+
case 'get_coding_guide':
|
|
154
|
+
return await getCodingGuide(args);
|
|
155
|
+
case 'apply_quality_rules':
|
|
156
|
+
return await applyQualityRules(args);
|
|
157
|
+
case 'validate_code_quality':
|
|
158
|
+
return await validateCodeQuality(args);
|
|
159
|
+
case 'analyze_complexity':
|
|
160
|
+
return await analyzeComplexity(args);
|
|
161
|
+
case 'check_coupling_cohesion':
|
|
162
|
+
return await checkCouplingCohesion(args);
|
|
163
|
+
case 'suggest_improvements':
|
|
164
|
+
return await suggestImprovements(args);
|
|
165
|
+
// Planning Tools
|
|
166
|
+
case 'generate_prd':
|
|
167
|
+
return await generatePrd(args);
|
|
168
|
+
case 'create_user_stories':
|
|
169
|
+
return await createUserStories(args);
|
|
170
|
+
case 'analyze_requirements':
|
|
171
|
+
return await analyzeRequirements(args);
|
|
172
|
+
case 'feature_roadmap':
|
|
173
|
+
return await featureRoadmap(args);
|
|
174
|
+
// Prompt Enhancement Tools
|
|
175
|
+
case 'enhance_prompt':
|
|
176
|
+
return await enhancePrompt(args);
|
|
177
|
+
case 'analyze_prompt':
|
|
178
|
+
return await analyzePrompt(args);
|
|
179
|
+
// UI Preview Tools
|
|
180
|
+
case 'preview_ui_ascii':
|
|
181
|
+
return await previewUiAscii(args);
|
|
182
|
+
default:
|
|
183
|
+
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
throw new McpError(ErrorCode.InternalError, `Error executing tool: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
return server;
|
|
191
|
+
}
|
|
192
|
+
// Default export for Smithery platform
|
|
193
|
+
export default function ({ sessionId, config }) {
|
|
194
|
+
// Return the configured server instance
|
|
195
|
+
return createServer();
|
|
196
|
+
}
|
|
197
|
+
async function main() {
|
|
198
|
+
const server = createServer();
|
|
199
|
+
const transport = new StdioServerTransport();
|
|
200
|
+
// Handle process termination gracefully
|
|
201
|
+
process.on('SIGINT', async () => {
|
|
202
|
+
await server.close();
|
|
203
|
+
process.exit(0);
|
|
204
|
+
});
|
|
205
|
+
process.on('SIGTERM', async () => {
|
|
206
|
+
await server.close();
|
|
207
|
+
process.exit(0);
|
|
208
|
+
});
|
|
209
|
+
// Handle EPIPE errors that occur with sidecar proxy
|
|
210
|
+
process.on('uncaughtException', (error) => {
|
|
211
|
+
if (error.message && error.message.includes('EPIPE')) {
|
|
212
|
+
// Gracefully handle EPIPE errors
|
|
213
|
+
console.error('Connection closed by client');
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
console.error('Uncaught exception:', error);
|
|
217
|
+
process.exit(1);
|
|
218
|
+
});
|
|
219
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
220
|
+
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
221
|
+
});
|
|
222
|
+
await server.connect(transport);
|
|
223
|
+
}
|
|
224
|
+
// Only run main when not being imported by Smithery
|
|
225
|
+
if (process.argv[1]?.includes('hi-ai') || process.argv[1]?.endsWith('index.js')) {
|
|
226
|
+
main().catch((error) => {
|
|
227
|
+
console.error('Server initialization failed:', error);
|
|
228
|
+
process.exit(1);
|
|
229
|
+
});
|
|
230
|
+
}
|