@su-record/vibe 0.1.2 → 0.1.4
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/README.md +13 -6
- package/bin/vibe +20 -2
- package/package.json +5 -6
- package/scripts/install-mcp.js +31 -5
- package/mcp/dist/__tests__/complexity.test.js +0 -126
- package/mcp/dist/__tests__/memory.test.js +0 -120
- package/mcp/dist/__tests__/python-dart-complexity.test.js +0 -146
- package/mcp/dist/index.js +0 -230
- package/mcp/dist/lib/ContextCompressor.js +0 -305
- package/mcp/dist/lib/MemoryManager.js +0 -334
- package/mcp/dist/lib/ProjectCache.js +0 -126
- package/mcp/dist/lib/PythonParser.js +0 -241
- package/mcp/dist/tools/browser/browserPool.js +0 -76
- package/mcp/dist/tools/browser/browserUtils.js +0 -135
- package/mcp/dist/tools/browser/inspectNetworkRequests.js +0 -140
- package/mcp/dist/tools/browser/monitorConsoleLogs.js +0 -97
- package/mcp/dist/tools/convention/analyzeComplexity.js +0 -248
- package/mcp/dist/tools/convention/applyQualityRules.js +0 -102
- package/mcp/dist/tools/convention/checkCouplingCohesion.js +0 -233
- package/mcp/dist/tools/convention/complexityMetrics.js +0 -133
- package/mcp/dist/tools/convention/dartComplexity.js +0 -117
- package/mcp/dist/tools/convention/getCodingGuide.js +0 -64
- package/mcp/dist/tools/convention/languageDetector.js +0 -50
- package/mcp/dist/tools/convention/pythonComplexity.js +0 -109
- package/mcp/dist/tools/convention/suggestImprovements.js +0 -257
- package/mcp/dist/tools/convention/validateCodeQuality.js +0 -177
- package/mcp/dist/tools/memory/autoSaveContext.js +0 -79
- package/mcp/dist/tools/memory/database.js +0 -123
- package/mcp/dist/tools/memory/deleteMemory.js +0 -39
- package/mcp/dist/tools/memory/listMemories.js +0 -38
- package/mcp/dist/tools/memory/memoryConfig.js +0 -27
- package/mcp/dist/tools/memory/memorySQLite.js +0 -138
- package/mcp/dist/tools/memory/memoryUtils.js +0 -34
- package/mcp/dist/tools/memory/migrate.js +0 -113
- package/mcp/dist/tools/memory/prioritizeMemory.js +0 -109
- package/mcp/dist/tools/memory/recallMemory.js +0 -40
- package/mcp/dist/tools/memory/restoreSessionContext.js +0 -69
- package/mcp/dist/tools/memory/saveMemory.js +0 -34
- package/mcp/dist/tools/memory/searchMemories.js +0 -37
- package/mcp/dist/tools/memory/startSession.js +0 -100
- package/mcp/dist/tools/memory/updateMemory.js +0 -46
- package/mcp/dist/tools/planning/analyzeRequirements.js +0 -166
- package/mcp/dist/tools/planning/createUserStories.js +0 -119
- package/mcp/dist/tools/planning/featureRoadmap.js +0 -202
- package/mcp/dist/tools/planning/generatePrd.js +0 -156
- package/mcp/dist/tools/prompt/analyzePrompt.js +0 -145
- package/mcp/dist/tools/prompt/enhancePrompt.js +0 -105
- package/mcp/dist/tools/semantic/findReferences.js +0 -195
- package/mcp/dist/tools/semantic/findSymbol.js +0 -200
- package/mcp/dist/tools/thinking/analyzeProblem.js +0 -50
- package/mcp/dist/tools/thinking/breakDownProblem.js +0 -140
- package/mcp/dist/tools/thinking/createThinkingChain.js +0 -39
- package/mcp/dist/tools/thinking/formatAsPlan.js +0 -73
- package/mcp/dist/tools/thinking/stepByStepAnalysis.js +0 -58
- package/mcp/dist/tools/thinking/thinkAloudProcess.js +0 -75
- package/mcp/dist/tools/time/getCurrentTime.js +0 -61
- package/mcp/dist/tools/ui/previewUiAscii.js +0 -232
- package/mcp/dist/types/tool.js +0 -2
- package/mcp/package.json +0 -53
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
// Convention management tool - completely independent
|
|
2
|
-
import { Project, ScriptKind } from "ts-morph";
|
|
3
|
-
import { PythonParser } from '../../lib/PythonParser.js';
|
|
4
|
-
// Reusable in-memory project to avoid re-parsing standard lib every call
|
|
5
|
-
const AST_PROJECT = new Project({
|
|
6
|
-
useInMemoryFileSystem: true,
|
|
7
|
-
compilerOptions: { allowJs: true, skipLibCheck: true }
|
|
8
|
-
});
|
|
9
|
-
// Enhanced Software Engineering Metrics
|
|
10
|
-
const CODE_QUALITY_METRICS = {
|
|
11
|
-
COMPLEXITY: {
|
|
12
|
-
maxCyclomaticComplexity: 10,
|
|
13
|
-
maxCognitiveComplexity: 15,
|
|
14
|
-
maxFunctionLines: 20,
|
|
15
|
-
maxNestingDepth: 3,
|
|
16
|
-
maxParameters: 5
|
|
17
|
-
},
|
|
18
|
-
COUPLING: {
|
|
19
|
-
maxDependencies: 7,
|
|
20
|
-
maxFanOut: 5,
|
|
21
|
-
preventCircularDeps: true
|
|
22
|
-
},
|
|
23
|
-
COHESION: {
|
|
24
|
-
singleResponsibility: true,
|
|
25
|
-
relatedFunctionsOnly: true
|
|
26
|
-
},
|
|
27
|
-
MAINTAINABILITY: {
|
|
28
|
-
noMagicNumbers: true,
|
|
29
|
-
consistentNaming: true,
|
|
30
|
-
properErrorHandling: true,
|
|
31
|
-
typesSafety: true
|
|
32
|
-
},
|
|
33
|
-
PERFORMANCE: {
|
|
34
|
-
memoizeExpensiveCalc: true,
|
|
35
|
-
lazyLoading: true,
|
|
36
|
-
batchOperations: true
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
export const analyzeComplexityDefinition = {
|
|
40
|
-
name: 'analyze_complexity',
|
|
41
|
-
description: '복잡도|복잡한지|complexity|how complex|난이도 - Analyze code complexity',
|
|
42
|
-
inputSchema: {
|
|
43
|
-
type: 'object',
|
|
44
|
-
properties: {
|
|
45
|
-
code: { type: 'string', description: 'Code to analyze' },
|
|
46
|
-
metrics: { type: 'string', description: 'Metrics to calculate', enum: ['cyclomatic', 'cognitive', 'halstead', 'all'] }
|
|
47
|
-
},
|
|
48
|
-
required: ['code']
|
|
49
|
-
},
|
|
50
|
-
annotations: {
|
|
51
|
-
title: 'Analyze Complexity',
|
|
52
|
-
audience: ['user', 'assistant']
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
/**
|
|
56
|
-
* Calculate cognitive complexity (how hard code is to understand)
|
|
57
|
-
*/
|
|
58
|
-
function calculateCognitiveComplexity(code) {
|
|
59
|
-
const CONTROL_STRUCTURES = ['if', 'for', 'while'];
|
|
60
|
-
let cognitiveScore = 0;
|
|
61
|
-
const lines = code.split('\n');
|
|
62
|
-
let nestingLevel = 0;
|
|
63
|
-
for (const line of lines) {
|
|
64
|
-
const trimmed = line.trim();
|
|
65
|
-
// Increment for control structures
|
|
66
|
-
if (CONTROL_STRUCTURES.some(keyword => trimmed.includes(keyword))) {
|
|
67
|
-
cognitiveScore += 1 + nestingLevel;
|
|
68
|
-
}
|
|
69
|
-
// Increment for catch/switch
|
|
70
|
-
if (trimmed.includes('catch') || trimmed.includes('switch')) {
|
|
71
|
-
cognitiveScore += 1 + nestingLevel;
|
|
72
|
-
}
|
|
73
|
-
// Update nesting level
|
|
74
|
-
const openBraces = (line.match(/\{/g) || []).length;
|
|
75
|
-
const closeBraces = (line.match(/\}/g) || []).length;
|
|
76
|
-
nestingLevel = Math.max(0, nestingLevel + openBraces - closeBraces);
|
|
77
|
-
}
|
|
78
|
-
const threshold = CODE_QUALITY_METRICS.COMPLEXITY.maxCognitiveComplexity;
|
|
79
|
-
return {
|
|
80
|
-
value: cognitiveScore,
|
|
81
|
-
threshold,
|
|
82
|
-
status: cognitiveScore <= threshold ? 'pass' : 'fail',
|
|
83
|
-
description: 'How difficult the code is to understand'
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* Calculate AST-based cyclomatic complexity
|
|
88
|
-
*/
|
|
89
|
-
function calculateAstComplexity(code) {
|
|
90
|
-
const CONTROL_FLOW_NODES = [
|
|
91
|
-
'IfStatement', 'ForStatement', 'ForOfStatement', 'ForInStatement',
|
|
92
|
-
'WhileStatement', 'CaseClause', 'ConditionalExpression',
|
|
93
|
-
'DoStatement', 'CatchClause', 'BinaryExpression'
|
|
94
|
-
];
|
|
95
|
-
let astCyclomatic = 1;
|
|
96
|
-
try {
|
|
97
|
-
const sourceFile = AST_PROJECT.createSourceFile('temp.ts', code, {
|
|
98
|
-
overwrite: true,
|
|
99
|
-
scriptKind: ScriptKind.TS
|
|
100
|
-
});
|
|
101
|
-
sourceFile.forEachDescendant((node) => {
|
|
102
|
-
if (CONTROL_FLOW_NODES.includes(node.getKindName())) {
|
|
103
|
-
astCyclomatic++;
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
const threshold = CODE_QUALITY_METRICS.COMPLEXITY.maxCyclomaticComplexity;
|
|
107
|
-
return {
|
|
108
|
-
value: astCyclomatic,
|
|
109
|
-
threshold,
|
|
110
|
-
status: astCyclomatic <= threshold ? 'pass' : 'fail',
|
|
111
|
-
description: 'AST 기반 분기/조건문 수를 통한 cyclomatic complexity'
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
catch (e) {
|
|
115
|
-
return {
|
|
116
|
-
value: null,
|
|
117
|
-
status: 'error',
|
|
118
|
-
description: 'AST 분석 실패: ' + (e instanceof Error ? e.message : String(e))
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* Analyze Python code complexity
|
|
124
|
-
*/
|
|
125
|
-
async function analyzePythonComplexity(code) {
|
|
126
|
-
try {
|
|
127
|
-
const pythonComplexity = await PythonParser.analyzeComplexity(code);
|
|
128
|
-
const totalComplexity = pythonComplexity.cyclomaticComplexity;
|
|
129
|
-
const issues = [];
|
|
130
|
-
const MAX_COMPLEXITY = 10;
|
|
131
|
-
if (totalComplexity > MAX_COMPLEXITY) {
|
|
132
|
-
issues.push('High complexity');
|
|
133
|
-
}
|
|
134
|
-
pythonComplexity.functions.forEach(f => {
|
|
135
|
-
if (f.complexity > MAX_COMPLEXITY) {
|
|
136
|
-
issues.push(`Function ${f.name}: complexity ${f.complexity}`);
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
const issuesText = issues.length ? `\nIssues: ${issues.join(', ')}` : '';
|
|
140
|
-
return {
|
|
141
|
-
content: [{
|
|
142
|
-
type: 'text',
|
|
143
|
-
text: `Python Complexity: ${totalComplexity}\nFunctions: ${pythonComplexity.functions.length}\nClasses: ${pythonComplexity.classes.length}${issuesText}`
|
|
144
|
-
}]
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
catch (error) {
|
|
148
|
-
return {
|
|
149
|
-
content: [{
|
|
150
|
-
type: 'text',
|
|
151
|
-
text: `Python analysis error: ${error instanceof Error ? error.message : 'Unknown error'}`
|
|
152
|
-
}]
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
export async function analyzeComplexity(args) {
|
|
157
|
-
const { code: complexityCode, metrics: complexityMetrics = 'all' } = args;
|
|
158
|
-
// Check if this is Python code
|
|
159
|
-
if (PythonParser.isPythonCode(complexityCode)) {
|
|
160
|
-
return analyzePythonComplexity(complexityCode);
|
|
161
|
-
}
|
|
162
|
-
const complexityAnalysis = {
|
|
163
|
-
action: 'analyze_complexity',
|
|
164
|
-
metrics: complexityMetrics,
|
|
165
|
-
results: {},
|
|
166
|
-
overallScore: 0,
|
|
167
|
-
issues: [],
|
|
168
|
-
recommendations: [],
|
|
169
|
-
status: 'pending'
|
|
170
|
-
};
|
|
171
|
-
// AST 기반 cyclomatic complexity 분석
|
|
172
|
-
complexityAnalysis.results.astCyclomaticComplexity = calculateAstComplexity(complexityCode);
|
|
173
|
-
if (complexityMetrics === 'cyclomatic' || complexityMetrics === 'all') {
|
|
174
|
-
const cyclomaticComplexityScore = (complexityCode.match(/\bif\b|\bfor\b|\bwhile\b|\bcase\b|\b&&\b|\b\|\|\b/g) || []).length + 1;
|
|
175
|
-
complexityAnalysis.results.cyclomaticComplexity = {
|
|
176
|
-
value: cyclomaticComplexityScore,
|
|
177
|
-
threshold: CODE_QUALITY_METRICS.COMPLEXITY.maxCyclomaticComplexity,
|
|
178
|
-
status: cyclomaticComplexityScore <= CODE_QUALITY_METRICS.COMPLEXITY.maxCyclomaticComplexity ? 'pass' : 'fail',
|
|
179
|
-
description: 'Number of linearly independent paths through the code'
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
if (complexityMetrics === 'cognitive' || complexityMetrics === 'all') {
|
|
183
|
-
complexityAnalysis.results.cognitiveComplexity = calculateCognitiveComplexity(complexityCode);
|
|
184
|
-
}
|
|
185
|
-
if (complexityMetrics === 'halstead' || complexityMetrics === 'all') {
|
|
186
|
-
// Halstead metrics calculation (simplified version)
|
|
187
|
-
const operators = (complexityCode.match(/[+\-*/=<>!&|%^~?:]/g) || []).length;
|
|
188
|
-
const operands = (complexityCode.match(/\b[a-zA-Z_]\w*\b/g) || []).length;
|
|
189
|
-
const uniqueOperators = new Set(complexityCode.match(/[+\-*/=<>!&|%^~?:]/g) || []).size;
|
|
190
|
-
const uniqueOperands = new Set(complexityCode.match(/\b[a-zA-Z_]\w*\b/g) || []).size;
|
|
191
|
-
const vocabulary = uniqueOperators + uniqueOperands;
|
|
192
|
-
const length = operators + operands;
|
|
193
|
-
const calculatedLength = vocabulary > 0 ? uniqueOperators * Math.log2(uniqueOperators) + uniqueOperands * Math.log2(uniqueOperands) : 0;
|
|
194
|
-
const volume = length * Math.log2(vocabulary);
|
|
195
|
-
const difficulty = vocabulary > 0 ? (uniqueOperators / 2) * (operands / uniqueOperands) : 0;
|
|
196
|
-
const effort = difficulty * volume;
|
|
197
|
-
complexityAnalysis.results.halsteadMetrics = {
|
|
198
|
-
vocabulary: vocabulary,
|
|
199
|
-
length: length,
|
|
200
|
-
calculatedLength: Math.round(calculatedLength),
|
|
201
|
-
volume: Math.round(volume),
|
|
202
|
-
difficulty: Math.round(difficulty * 100) / 100,
|
|
203
|
-
effort: Math.round(effort),
|
|
204
|
-
timeToProgram: Math.round(effort / 18), // Halstead's formula: effort / 18 seconds
|
|
205
|
-
bugsDelivered: Math.round(volume / 3000 * 100) / 100, // Halstead's formula: volume / 3000
|
|
206
|
-
description: 'Software science metrics measuring program complexity'
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
// Additional complexity metrics
|
|
210
|
-
if (complexityMetrics === 'all') {
|
|
211
|
-
const lines = complexityCode.split('\n');
|
|
212
|
-
const nonEmptyLines = lines.filter(line => line.trim().length > 0).length;
|
|
213
|
-
const comments = (complexityCode.match(/\/\*[\s\S]*?\*\/|\/\/.*$/gm) || []).length;
|
|
214
|
-
const functions = (complexityCode.match(/function\s+\w+|\w+\s*=\s*\(/g) || []).length;
|
|
215
|
-
const classes = (complexityCode.match(/class\s+\w+/g) || []).length;
|
|
216
|
-
complexityAnalysis.results.additionalMetrics = {
|
|
217
|
-
linesOfCode: nonEmptyLines,
|
|
218
|
-
comments: comments,
|
|
219
|
-
commentRatio: nonEmptyLines > 0 ? Math.round((comments / nonEmptyLines) * 100) / 100 : 0,
|
|
220
|
-
functions: functions,
|
|
221
|
-
classes: classes,
|
|
222
|
-
averageFunctionLength: functions > 0 ? Math.round(nonEmptyLines / functions) : 0
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
// Overall assessment
|
|
226
|
-
const issues = [];
|
|
227
|
-
let overallScore = 100;
|
|
228
|
-
if (complexityAnalysis.results.cyclomaticComplexity && complexityAnalysis.results.cyclomaticComplexity.status === 'fail') {
|
|
229
|
-
issues.push('High cyclomatic complexity detected');
|
|
230
|
-
overallScore -= 20;
|
|
231
|
-
}
|
|
232
|
-
if (complexityAnalysis.results.cognitiveComplexity && complexityAnalysis.results.cognitiveComplexity.status === 'fail') {
|
|
233
|
-
issues.push('High cognitive complexity detected');
|
|
234
|
-
overallScore -= 25;
|
|
235
|
-
}
|
|
236
|
-
if (complexityAnalysis.results.halsteadMetrics && complexityAnalysis.results.halsteadMetrics.difficulty > 10) {
|
|
237
|
-
issues.push('High Halstead difficulty detected');
|
|
238
|
-
overallScore -= 15;
|
|
239
|
-
}
|
|
240
|
-
complexityAnalysis.overallScore = Math.max(0, overallScore);
|
|
241
|
-
complexityAnalysis.issues = issues;
|
|
242
|
-
return {
|
|
243
|
-
content: [{
|
|
244
|
-
type: 'text',
|
|
245
|
-
text: `Complexity: ${complexityAnalysis.results.astCyclomaticComplexity?.value ?? 'N/A'}\nScore: ${complexityAnalysis.overallScore}${issues.length ? '\nIssues: ' + issues.join(', ') : ''}`
|
|
246
|
-
}]
|
|
247
|
-
};
|
|
248
|
-
}
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
// Convention management tool - completely independent
|
|
2
|
-
// Code Quality Standards
|
|
3
|
-
const QUALITY_RULES = {
|
|
4
|
-
NAMING: {
|
|
5
|
-
variables: 'nouns (userList, userData)',
|
|
6
|
-
functions: 'verb+noun (fetchData, updateUser)',
|
|
7
|
-
events: 'handle prefix (handleClick, handleSubmit)',
|
|
8
|
-
booleans: 'is/has/can prefix (isLoading, hasError, canEdit)',
|
|
9
|
-
constants: 'UPPER_SNAKE_CASE (MAX_RETRY_COUNT, API_TIMEOUT)',
|
|
10
|
-
components: 'PascalCase (UserProfile, HeaderSection)',
|
|
11
|
-
hooks: 'use prefix (useUserData, useAuth)'
|
|
12
|
-
},
|
|
13
|
-
STRUCTURE: {
|
|
14
|
-
componentOrder: ['State & Refs', 'Custom Hooks', 'Event Handlers', 'Effects', 'Early returns', 'Main return JSX'],
|
|
15
|
-
functionMaxLines: 20,
|
|
16
|
-
componentMaxLines: 50,
|
|
17
|
-
maxNestingDepth: 3
|
|
18
|
-
},
|
|
19
|
-
ANTIPATTERNS: {
|
|
20
|
-
typescript: ['any type usage', '@ts-ignore usage', 'as any casting'],
|
|
21
|
-
react: ['dangerouslySetInnerHTML', 'props drilling (3+ levels)'],
|
|
22
|
-
javascript: ['var usage', '== instead of ===', 'eval() usage'],
|
|
23
|
-
css: ['!important abuse', 'inline style abuse']
|
|
24
|
-
},
|
|
25
|
-
ASYNC_STATES: ['data', 'isLoading', 'error'],
|
|
26
|
-
STATE_MANAGEMENT: {
|
|
27
|
-
simple: 'useState',
|
|
28
|
-
complex: 'useReducer',
|
|
29
|
-
globalUI: 'Context API',
|
|
30
|
-
globalApp: 'Zustand',
|
|
31
|
-
server: 'TanStack Query'
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
export const applyQualityRulesDefinition = {
|
|
35
|
-
name: 'apply_quality_rules',
|
|
36
|
-
description: '규칙 적용|표준 적용|apply rules|apply standards|follow conventions|적용해 - Apply quality rules',
|
|
37
|
-
inputSchema: {
|
|
38
|
-
type: 'object',
|
|
39
|
-
properties: {
|
|
40
|
-
scope: { type: 'string', description: 'Application scope', enum: ['naming', 'structure', 'typescript', 'react', 'accessibility', 'all'] },
|
|
41
|
-
language: { type: 'string', description: 'Programming language context', enum: ['javascript', 'typescript', 'react', 'vue', 'general'] }
|
|
42
|
-
},
|
|
43
|
-
required: ['scope']
|
|
44
|
-
},
|
|
45
|
-
annotations: {
|
|
46
|
-
title: 'Apply Quality Rules',
|
|
47
|
-
audience: ['user', 'assistant']
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
export async function applyQualityRules(args) {
|
|
51
|
-
const { scope, language: contextLanguage = 'general' } = args;
|
|
52
|
-
const applicableRules = [];
|
|
53
|
-
if (scope === 'naming' || scope === 'all') {
|
|
54
|
-
applicableRules.push({
|
|
55
|
-
category: 'Naming Conventions',
|
|
56
|
-
rules: QUALITY_RULES.NAMING
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
if (scope === 'structure' || scope === 'all') {
|
|
60
|
-
applicableRules.push({
|
|
61
|
-
category: 'Code Structure',
|
|
62
|
-
rules: QUALITY_RULES.STRUCTURE
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
if (scope === 'typescript' || scope === 'all') {
|
|
66
|
-
applicableRules.push({
|
|
67
|
-
category: 'TypeScript Guidelines',
|
|
68
|
-
rules: QUALITY_RULES.ANTIPATTERNS.typescript
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
if (scope === 'react' || scope === 'all') {
|
|
72
|
-
applicableRules.push({
|
|
73
|
-
category: 'React Guidelines',
|
|
74
|
-
rules: QUALITY_RULES.ANTIPATTERNS.react
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
if (scope === 'accessibility' || scope === 'all') {
|
|
78
|
-
applicableRules.push({
|
|
79
|
-
category: 'Accessibility Guidelines',
|
|
80
|
-
rules: [
|
|
81
|
-
'Use semantic HTML elements',
|
|
82
|
-
'Provide alt text for images',
|
|
83
|
-
'Ensure keyboard navigation',
|
|
84
|
-
'Maintain color contrast ratios',
|
|
85
|
-
'Use ARIA labels when needed'
|
|
86
|
-
]
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
const qualityRulesResult = {
|
|
90
|
-
action: 'apply_quality_rules',
|
|
91
|
-
scope,
|
|
92
|
-
language: contextLanguage,
|
|
93
|
-
rules: applicableRules,
|
|
94
|
-
asyncStates: QUALITY_RULES.ASYNC_STATES,
|
|
95
|
-
stateManagement: QUALITY_RULES.STATE_MANAGEMENT,
|
|
96
|
-
status: 'success'
|
|
97
|
-
};
|
|
98
|
-
const rulesSummary = applicableRules.map(r => `${r.category}: ${Array.isArray(r.rules) ? r.rules.length + ' rules' : Object.keys(r.rules).length + ' items'}`).join(', ');
|
|
99
|
-
return {
|
|
100
|
-
content: [{ type: 'text', text: `Scope: ${scope}\nLanguage: ${contextLanguage}\nRules Applied: ${rulesSummary}\n\nAsync States: ${QUALITY_RULES.ASYNC_STATES.join(', ')}\n\nState Mgmt:\n${Object.entries(QUALITY_RULES.STATE_MANAGEMENT).map(([k, v]) => `- ${k}: ${v}`).join('\n')}` }]
|
|
101
|
-
};
|
|
102
|
-
}
|
|
@@ -1,233 +0,0 @@
|
|
|
1
|
-
// Convention management tool - completely independent
|
|
2
|
-
// Enhanced Software Engineering Metrics
|
|
3
|
-
const CODE_QUALITY_METRICS = {
|
|
4
|
-
COMPLEXITY: {
|
|
5
|
-
maxCyclomaticComplexity: 10,
|
|
6
|
-
maxCognitiveComplexity: 15,
|
|
7
|
-
maxFunctionLines: 20,
|
|
8
|
-
maxNestingDepth: 3,
|
|
9
|
-
maxParameters: 5
|
|
10
|
-
},
|
|
11
|
-
COUPLING: {
|
|
12
|
-
maxDependencies: 7,
|
|
13
|
-
maxFanOut: 5,
|
|
14
|
-
preventCircularDeps: true
|
|
15
|
-
},
|
|
16
|
-
COHESION: {
|
|
17
|
-
singleResponsibility: true,
|
|
18
|
-
relatedFunctionsOnly: true
|
|
19
|
-
},
|
|
20
|
-
MAINTAINABILITY: {
|
|
21
|
-
noMagicNumbers: true,
|
|
22
|
-
consistentNaming: true,
|
|
23
|
-
properErrorHandling: true,
|
|
24
|
-
typesSafety: true
|
|
25
|
-
},
|
|
26
|
-
PERFORMANCE: {
|
|
27
|
-
memoizeExpensiveCalc: true,
|
|
28
|
-
lazyLoading: true,
|
|
29
|
-
batchOperations: true
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
export const checkCouplingCohesionDefinition = {
|
|
33
|
-
name: 'check_coupling_cohesion',
|
|
34
|
-
description: '결합도|응집도|coupling|cohesion|dependencies check|module structure - Check coupling and cohesion',
|
|
35
|
-
inputSchema: {
|
|
36
|
-
type: 'object',
|
|
37
|
-
properties: {
|
|
38
|
-
code: { type: 'string', description: 'Code to analyze' },
|
|
39
|
-
type: { type: 'string', description: 'Code type', enum: ['class', 'module', 'function', 'component'] },
|
|
40
|
-
checkDependencies: { type: 'boolean', description: 'Analyze dependencies' }
|
|
41
|
-
},
|
|
42
|
-
required: ['code']
|
|
43
|
-
},
|
|
44
|
-
annotations: {
|
|
45
|
-
title: 'Check Coupling & Cohesion',
|
|
46
|
-
audience: ['user', 'assistant']
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
import { Project, ScriptKind } from "ts-morph";
|
|
50
|
-
import * as ts from "typescript";
|
|
51
|
-
const AST_PROJECT = new Project({
|
|
52
|
-
useInMemoryFileSystem: true,
|
|
53
|
-
compilerOptions: { allowJs: true, skipLibCheck: true }
|
|
54
|
-
});
|
|
55
|
-
export async function checkCouplingCohesion(args) {
|
|
56
|
-
const { code: couplingCode, type: couplingType = 'function', checkDependencies = false } = args;
|
|
57
|
-
const couplingAnalysis = {
|
|
58
|
-
action: 'check_coupling_cohesion',
|
|
59
|
-
type: couplingType,
|
|
60
|
-
checkDependencies,
|
|
61
|
-
results: {},
|
|
62
|
-
overallScore: 0,
|
|
63
|
-
issues: [],
|
|
64
|
-
recommendations: [],
|
|
65
|
-
status: 'pending'
|
|
66
|
-
};
|
|
67
|
-
// AST 기반 의존성/구조 분석
|
|
68
|
-
try {
|
|
69
|
-
const sourceFile = AST_PROJECT.createSourceFile('temp.ts', couplingCode, {
|
|
70
|
-
overwrite: true,
|
|
71
|
-
scriptKind: ScriptKind.TS
|
|
72
|
-
});
|
|
73
|
-
// Import/Require 분석
|
|
74
|
-
const importDecls = sourceFile.getImportDeclarations();
|
|
75
|
-
const requireCalls = sourceFile.getDescendantsOfKind(ts.SyntaxKind.CallExpression).filter(call => call.getExpression().getText() === 'require');
|
|
76
|
-
// 클래스/함수/모듈 구조 분석
|
|
77
|
-
const classDecls = sourceFile.getClasses();
|
|
78
|
-
const funcDecls = sourceFile.getFunctions();
|
|
79
|
-
const exportDecls = sourceFile.getExportDeclarations();
|
|
80
|
-
couplingAnalysis.results.ast = {
|
|
81
|
-
importCount: importDecls.length,
|
|
82
|
-
requireCount: requireCalls.length,
|
|
83
|
-
classCount: classDecls.length,
|
|
84
|
-
functionCount: funcDecls.length,
|
|
85
|
-
exportCount: exportDecls.length,
|
|
86
|
-
importModules: importDecls.map(d => d.getModuleSpecifierValue()),
|
|
87
|
-
exportedNames: exportDecls.map(d => d.getNamedExports().map(e => e.getName()))
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
catch (e) {
|
|
91
|
-
couplingAnalysis.results.ast = {
|
|
92
|
-
error: 'AST 분석 실패: ' + (e instanceof Error ? e.message : String(e))
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
// Dependency analysis (Coupling)
|
|
96
|
-
const imports = (couplingCode.match(/import\s+.*?\s+from\s+['"](.*?)['"]/g) || []).length;
|
|
97
|
-
const requires = (couplingCode.match(/require\s*\(\s*['"](.*?)['"]\s*\)/g) || []).length;
|
|
98
|
-
const totalDependencies = imports + requires;
|
|
99
|
-
// External dependencies
|
|
100
|
-
const externalDeps = (couplingCode.match(/import\s+.*?\s+from\s+['"](?!\.)(.*?)['"]/g) || []).length;
|
|
101
|
-
const internalDeps = totalDependencies - externalDeps;
|
|
102
|
-
// Function calls (fan-out)
|
|
103
|
-
const functionCalls = (couplingCode.match(/\w+\s*\(/g) || []).length;
|
|
104
|
-
const uniqueFunctionCalls = new Set((couplingCode.match(/\w+\s*\(/g) || []).map(call => call.replace(/\s*\(/, ''))).size;
|
|
105
|
-
couplingAnalysis.results.coupling = {
|
|
106
|
-
totalDependencies: totalDependencies,
|
|
107
|
-
externalDependencies: externalDeps,
|
|
108
|
-
internalDependencies: internalDeps,
|
|
109
|
-
functionCalls: functionCalls,
|
|
110
|
-
uniqueFunctionCalls: uniqueFunctionCalls,
|
|
111
|
-
threshold: CODE_QUALITY_METRICS.COUPLING.maxDependencies,
|
|
112
|
-
status: totalDependencies <= CODE_QUALITY_METRICS.COUPLING.maxDependencies ? 'pass' : 'fail',
|
|
113
|
-
fanOut: uniqueFunctionCalls,
|
|
114
|
-
fanOutStatus: uniqueFunctionCalls <= CODE_QUALITY_METRICS.COUPLING.maxFanOut ? 'pass' : 'fail'
|
|
115
|
-
};
|
|
116
|
-
// Cohesion analysis
|
|
117
|
-
let cohesionScore = 0;
|
|
118
|
-
let cohesionLevel = 'low';
|
|
119
|
-
if (couplingType === 'class') {
|
|
120
|
-
// Class cohesion: methods using same data
|
|
121
|
-
const methods = (couplingCode.match(/\w+\s*\([^)]*\)\s*\{/g) || []).length;
|
|
122
|
-
const properties = (couplingCode.match(/this\.\w+/g) || []).length;
|
|
123
|
-
const uniqueProperties = new Set((couplingCode.match(/this\.(\w+)/g) || []).map(prop => prop.replace('this.', ''))).size;
|
|
124
|
-
// LCOM (Lack of Cohesion in Methods) - simplified calculation
|
|
125
|
-
const propertyUsage = methods > 0 ? uniqueProperties / methods : 0;
|
|
126
|
-
cohesionScore = propertyUsage;
|
|
127
|
-
couplingAnalysis.results.cohesion = {
|
|
128
|
-
type: 'class',
|
|
129
|
-
methods: methods,
|
|
130
|
-
properties: uniqueProperties,
|
|
131
|
-
propertyUsageRatio: Math.round(propertyUsage * 100) / 100,
|
|
132
|
-
score: cohesionScore,
|
|
133
|
-
level: cohesionScore > 0.7 ? 'high' : cohesionScore > 0.4 ? 'medium' : 'low',
|
|
134
|
-
status: cohesionScore > 0.4 ? 'pass' : 'fail'
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
else if (couplingType === 'module') {
|
|
138
|
-
// Module cohesion: related functions and exports
|
|
139
|
-
const functions = (couplingCode.match(/function\s+\w+|const\s+\w+\s*=\s*\(/g) || []).length;
|
|
140
|
-
const exports = (couplingCode.match(/export\s+/g) || []).length;
|
|
141
|
-
const variables = (couplingCode.match(/(?:const|let|var)\s+\w+/g) || []).length;
|
|
142
|
-
// Functional cohesion: ratio of related functions
|
|
143
|
-
const cohesionRatio = functions > 0 ? exports / functions : 0;
|
|
144
|
-
cohesionScore = cohesionRatio;
|
|
145
|
-
couplingAnalysis.results.cohesion = {
|
|
146
|
-
type: 'module',
|
|
147
|
-
functions: functions,
|
|
148
|
-
exports: exports,
|
|
149
|
-
variables: variables,
|
|
150
|
-
cohesionRatio: Math.round(cohesionRatio * 100) / 100,
|
|
151
|
-
score: cohesionScore,
|
|
152
|
-
level: cohesionScore > 0.6 ? 'high' : cohesionScore > 0.3 ? 'medium' : 'low',
|
|
153
|
-
status: cohesionScore > 0.3 ? 'pass' : 'fail'
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
else if (couplingType === 'function') {
|
|
157
|
-
// Function cohesion: single responsibility principle
|
|
158
|
-
const statements = (couplingCode.match(/;/g) || []).length;
|
|
159
|
-
const returns = (couplingCode.match(/\breturn\b/g) || []).length;
|
|
160
|
-
const variables = (couplingCode.match(/(?:const|let|var)\s+\w+/g) || []).length;
|
|
161
|
-
// Sequential cohesion: operations flow in sequence
|
|
162
|
-
const lines = couplingCode.split('\n').filter(line => line.trim().length > 0).length;
|
|
163
|
-
const complexityIndicators = (couplingCode.match(/\bif\b|\bfor\b|\bwhile\b|\bswitch\b/g) || []).length;
|
|
164
|
-
cohesionScore = lines > 0 ? 1 - (complexityIndicators / lines) : 0;
|
|
165
|
-
couplingAnalysis.results.cohesion = {
|
|
166
|
-
type: 'function',
|
|
167
|
-
statements: statements,
|
|
168
|
-
returns: returns,
|
|
169
|
-
variables: variables,
|
|
170
|
-
lines: lines,
|
|
171
|
-
complexityIndicators: complexityIndicators,
|
|
172
|
-
score: Math.round(cohesionScore * 100) / 100,
|
|
173
|
-
level: cohesionScore > 0.7 ? 'high' : cohesionScore > 0.4 ? 'medium' : 'low',
|
|
174
|
-
status: cohesionScore > 0.4 ? 'pass' : 'fail'
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
else if (couplingType === 'component') {
|
|
178
|
-
// React component cohesion: props and state usage
|
|
179
|
-
const props = (couplingCode.match(/props\.\w+/g) || []).length;
|
|
180
|
-
const state = (couplingCode.match(/useState|useReducer/g) || []).length;
|
|
181
|
-
const effects = (couplingCode.match(/useEffect/g) || []).length;
|
|
182
|
-
const hooks = (couplingCode.match(/use\w+/g) || []).length;
|
|
183
|
-
// Component cohesion: how well props, state, and effects are related
|
|
184
|
-
const totalElements = props + state + effects;
|
|
185
|
-
const cohesionRatio = totalElements > 0 ? hooks / totalElements : 0;
|
|
186
|
-
cohesionScore = Math.min(1, cohesionRatio);
|
|
187
|
-
couplingAnalysis.results.cohesion = {
|
|
188
|
-
type: 'component',
|
|
189
|
-
props: props,
|
|
190
|
-
state: state,
|
|
191
|
-
effects: effects,
|
|
192
|
-
hooks: hooks,
|
|
193
|
-
cohesionRatio: Math.round(cohesionRatio * 100) / 100,
|
|
194
|
-
score: Math.round(cohesionScore * 100) / 100,
|
|
195
|
-
level: cohesionScore > 0.6 ? 'high' : cohesionScore > 0.3 ? 'medium' : 'low',
|
|
196
|
-
status: cohesionScore > 0.3 ? 'pass' : 'fail'
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
// Overall assessment
|
|
200
|
-
const issues = [];
|
|
201
|
-
let overallScore = 100;
|
|
202
|
-
if (couplingAnalysis.results.coupling.status === 'fail') {
|
|
203
|
-
issues.push('High coupling detected - too many dependencies');
|
|
204
|
-
overallScore -= 30;
|
|
205
|
-
}
|
|
206
|
-
if (couplingAnalysis.results.coupling.fanOutStatus === 'fail') {
|
|
207
|
-
issues.push('High fan-out detected - too many function calls');
|
|
208
|
-
overallScore -= 20;
|
|
209
|
-
}
|
|
210
|
-
if (couplingAnalysis.results.cohesion.status === 'fail') {
|
|
211
|
-
issues.push('Low cohesion detected - poor single responsibility');
|
|
212
|
-
overallScore -= 25;
|
|
213
|
-
}
|
|
214
|
-
couplingAnalysis.overallScore = Math.max(0, overallScore);
|
|
215
|
-
couplingAnalysis.issues = issues;
|
|
216
|
-
couplingAnalysis.recommendations = [];
|
|
217
|
-
if (couplingAnalysis.results.coupling.status === 'fail') {
|
|
218
|
-
couplingAnalysis.recommendations.push('Reduce dependencies by using dependency injection');
|
|
219
|
-
couplingAnalysis.recommendations.push('Consider using interfaces to abstract dependencies');
|
|
220
|
-
}
|
|
221
|
-
if (couplingAnalysis.results.cohesion.status === 'fail') {
|
|
222
|
-
couplingAnalysis.recommendations.push('Ensure single responsibility principle');
|
|
223
|
-
couplingAnalysis.recommendations.push('Group related functionality together');
|
|
224
|
-
couplingAnalysis.recommendations.push('Extract unrelated code into separate modules');
|
|
225
|
-
}
|
|
226
|
-
if (couplingAnalysis.recommendations.length === 0) {
|
|
227
|
-
couplingAnalysis.recommendations.push('Coupling and cohesion are within acceptable ranges');
|
|
228
|
-
}
|
|
229
|
-
couplingAnalysis.status = 'success';
|
|
230
|
-
return {
|
|
231
|
-
content: [{ type: 'text', text: `Type: ${couplingType}\nScore: ${couplingAnalysis.overallScore}/100\n\nCoupling: ${couplingAnalysis.results.coupling.totalDependencies} deps (${couplingAnalysis.results.coupling.status}) | Fan-out: ${couplingAnalysis.results.coupling.fanOut} (${couplingAnalysis.results.coupling.fanOutStatus})\nCohesion: ${couplingAnalysis.results.cohesion.score} (${couplingAnalysis.results.cohesion.level}, ${couplingAnalysis.results.cohesion.status})\n\nIssues (${couplingAnalysis.issues.length}):\n${couplingAnalysis.issues.map(i => `- ${i}`).join('\n')}\n\nRecommendations:\n${couplingAnalysis.recommendations.map(r => `- ${r}`).join('\n')}` }]
|
|
232
|
-
};
|
|
233
|
-
}
|