@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,233 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// Complexity metrics calculation - extracted from analyzeComplexity
|
|
2
|
+
import { Project, ScriptKind } from "ts-morph";
|
|
3
|
+
const AST_PROJECT = new Project({
|
|
4
|
+
useInMemoryFileSystem: true,
|
|
5
|
+
compilerOptions: { allowJs: true, skipLibCheck: true }
|
|
6
|
+
});
|
|
7
|
+
const COMPLEXITY_NODES = new Set([
|
|
8
|
+
'IfStatement', 'ForStatement', 'ForOfStatement', 'ForInStatement',
|
|
9
|
+
'WhileStatement', 'CaseClause', 'ConditionalExpression',
|
|
10
|
+
'DoStatement', 'CatchClause', 'BinaryExpression'
|
|
11
|
+
]);
|
|
12
|
+
export const THRESHOLDS = {
|
|
13
|
+
maxCyclomatic: 10,
|
|
14
|
+
maxCognitive: 15,
|
|
15
|
+
maxHalsteadDifficulty: 10
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Calculate AST-based cyclomatic complexity
|
|
19
|
+
*/
|
|
20
|
+
export function calculateASTCyclomatic(code) {
|
|
21
|
+
try {
|
|
22
|
+
const sourceFile = AST_PROJECT.createSourceFile('temp.ts', code, {
|
|
23
|
+
overwrite: true,
|
|
24
|
+
scriptKind: ScriptKind.TS
|
|
25
|
+
});
|
|
26
|
+
let complexity = 1;
|
|
27
|
+
sourceFile.forEachDescendant((node) => {
|
|
28
|
+
if (COMPLEXITY_NODES.has(node.getKindName())) {
|
|
29
|
+
complexity++;
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
return {
|
|
33
|
+
value: complexity,
|
|
34
|
+
threshold: THRESHOLDS.maxCyclomatic,
|
|
35
|
+
status: complexity <= THRESHOLDS.maxCyclomatic ? 'pass' : 'fail',
|
|
36
|
+
description: 'AST-based branch/decision point count'
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
return {
|
|
41
|
+
value: 0,
|
|
42
|
+
status: 'error',
|
|
43
|
+
description: `AST analysis failed: ${error instanceof Error ? error.message : String(error)}`
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Calculate regex-based cyclomatic complexity (fallback)
|
|
49
|
+
*/
|
|
50
|
+
export function calculateRegexCyclomatic(code) {
|
|
51
|
+
const patterns = /\bif\b|\bfor\b|\bwhile\b|\bcase\b|\b&&\b|\b\|\|\b/g;
|
|
52
|
+
const matches = code.match(patterns) || [];
|
|
53
|
+
const complexity = matches.length + 1;
|
|
54
|
+
return {
|
|
55
|
+
value: complexity,
|
|
56
|
+
threshold: THRESHOLDS.maxCyclomatic,
|
|
57
|
+
status: complexity <= THRESHOLDS.maxCyclomatic ? 'pass' : 'fail',
|
|
58
|
+
description: 'Number of linearly independent paths'
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Calculate cognitive complexity with nesting awareness
|
|
63
|
+
*/
|
|
64
|
+
export function calculateCognitiveComplexity(code) {
|
|
65
|
+
const lines = code.split('\n');
|
|
66
|
+
let complexity = 0;
|
|
67
|
+
let nestingLevel = 0;
|
|
68
|
+
for (const line of lines) {
|
|
69
|
+
const trimmed = line.trim();
|
|
70
|
+
// Control structures add complexity based on nesting
|
|
71
|
+
if (/(if|for|while|catch|switch)\s*\(/.test(trimmed)) {
|
|
72
|
+
complexity += 1 + nestingLevel;
|
|
73
|
+
}
|
|
74
|
+
// Track nesting level
|
|
75
|
+
const openBraces = (line.match(/\{/g) || []).length;
|
|
76
|
+
const closeBraces = (line.match(/\}/g) || []).length;
|
|
77
|
+
nestingLevel = Math.max(0, nestingLevel + openBraces - closeBraces);
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
value: complexity,
|
|
81
|
+
threshold: THRESHOLDS.maxCognitive,
|
|
82
|
+
status: complexity <= THRESHOLDS.maxCognitive ? 'pass' : 'fail',
|
|
83
|
+
description: 'Difficulty to understand the code'
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Calculate Halstead complexity metrics
|
|
88
|
+
*/
|
|
89
|
+
export function calculateHalsteadMetrics(code) {
|
|
90
|
+
const operators = code.match(/[+\-*/=<>!&|%^~?:]/g) || [];
|
|
91
|
+
const operands = code.match(/\b[a-zA-Z_]\w*\b/g) || [];
|
|
92
|
+
const uniqueOperators = new Set(operators).size;
|
|
93
|
+
const uniqueOperands = new Set(operands).size;
|
|
94
|
+
const vocabulary = uniqueOperators + uniqueOperands;
|
|
95
|
+
const length = operators.length + operands.length;
|
|
96
|
+
const calculatedLength = vocabulary > 0
|
|
97
|
+
? uniqueOperators * Math.log2(uniqueOperators || 1) + uniqueOperands * Math.log2(uniqueOperands || 1)
|
|
98
|
+
: 0;
|
|
99
|
+
const volume = length * Math.log2(vocabulary || 1);
|
|
100
|
+
const difficulty = vocabulary > 0
|
|
101
|
+
? (uniqueOperators / 2) * (operands.length / (uniqueOperands || 1))
|
|
102
|
+
: 0;
|
|
103
|
+
const effort = difficulty * volume;
|
|
104
|
+
return {
|
|
105
|
+
vocabulary,
|
|
106
|
+
length,
|
|
107
|
+
calculatedLength: Math.round(calculatedLength),
|
|
108
|
+
volume: Math.round(volume),
|
|
109
|
+
difficulty: Math.round(difficulty * 100) / 100,
|
|
110
|
+
effort: Math.round(effort),
|
|
111
|
+
timeToProgram: Math.round(effort / 18),
|
|
112
|
+
bugsDelivered: Math.round(volume / 3000 * 100) / 100,
|
|
113
|
+
description: 'Software science metrics measuring program complexity'
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Calculate additional code metrics
|
|
118
|
+
*/
|
|
119
|
+
export function calculateAdditionalMetrics(code) {
|
|
120
|
+
const lines = code.split('\n');
|
|
121
|
+
const nonEmptyLines = lines.filter(line => line.trim().length > 0).length;
|
|
122
|
+
const comments = (code.match(/\/\*[\s\S]*?\*\/|\/\/.*$/gm) || []).length;
|
|
123
|
+
const functions = (code.match(/function\s+\w+|\w+\s*=\s*\(/g) || []).length;
|
|
124
|
+
const classes = (code.match(/class\s+\w+/g) || []).length;
|
|
125
|
+
return {
|
|
126
|
+
linesOfCode: nonEmptyLines,
|
|
127
|
+
comments,
|
|
128
|
+
commentRatio: nonEmptyLines > 0 ? Math.round((comments / nonEmptyLines) * 100) / 100 : 0,
|
|
129
|
+
functions,
|
|
130
|
+
classes,
|
|
131
|
+
averageFunctionLength: functions > 0 ? Math.round(nonEmptyLines / functions) : 0
|
|
132
|
+
};
|
|
133
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dart/Flutter-specific complexity analysis
|
|
3
|
+
*/
|
|
4
|
+
import { THRESHOLDS } from './complexityMetrics.js';
|
|
5
|
+
/**
|
|
6
|
+
* Calculate cyclomatic complexity for Dart code
|
|
7
|
+
*/
|
|
8
|
+
export function calculateDartComplexity(code) {
|
|
9
|
+
// Dart control flow keywords
|
|
10
|
+
const patterns = [
|
|
11
|
+
/\bif\b/g, // if statements
|
|
12
|
+
/\belse\s+if\b/g, // else if
|
|
13
|
+
/\bfor\b/g, // for loops
|
|
14
|
+
/\bwhile\b/g, // while loops
|
|
15
|
+
/\bdo\b/g, // do-while
|
|
16
|
+
/\bswitch\b/g, // switch statements
|
|
17
|
+
/\bcase\b/g, // case clauses
|
|
18
|
+
/\btry\b/g, // try blocks
|
|
19
|
+
/\bcatch\b/g, // catch blocks
|
|
20
|
+
/&&/g, // logical and
|
|
21
|
+
/\|\|/g, // logical or
|
|
22
|
+
/\?\?/g // null-coalescing
|
|
23
|
+
];
|
|
24
|
+
let complexity = 1; // Base complexity
|
|
25
|
+
for (const pattern of patterns) {
|
|
26
|
+
const matches = code.match(pattern);
|
|
27
|
+
if (matches) {
|
|
28
|
+
complexity += matches.length;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
value: complexity,
|
|
33
|
+
threshold: THRESHOLDS.maxCyclomatic,
|
|
34
|
+
status: complexity <= THRESHOLDS.maxCyclomatic ? 'pass' : 'fail',
|
|
35
|
+
description: 'Dart cyclomatic complexity (control flow branches)'
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Calculate cognitive complexity for Dart
|
|
40
|
+
*/
|
|
41
|
+
export function calculateDartCognitiveComplexity(code) {
|
|
42
|
+
let complexity = 0;
|
|
43
|
+
// Nested control structures
|
|
44
|
+
let nestingLevel = 0;
|
|
45
|
+
const lines = code.split('\n');
|
|
46
|
+
for (const line of lines) {
|
|
47
|
+
const trimmed = line.trim();
|
|
48
|
+
// Track nesting level
|
|
49
|
+
const openBraces = (line.match(/{/g) || []).length;
|
|
50
|
+
const closeBraces = (line.match(/}/g) || []).length;
|
|
51
|
+
// Control flow keywords
|
|
52
|
+
if (/\b(if|else|for|while|switch|try|catch)\b/.test(trimmed)) {
|
|
53
|
+
complexity += 1 + nestingLevel;
|
|
54
|
+
}
|
|
55
|
+
// Ternary operators
|
|
56
|
+
const ternaryCount = (trimmed.match(/\?.*:/g) || []).length;
|
|
57
|
+
complexity += ternaryCount;
|
|
58
|
+
// Null-aware operators (Flutter specific)
|
|
59
|
+
const nullAwareCount = (trimmed.match(/\?\?|!\.|\/\?/g) || []).length;
|
|
60
|
+
complexity += nullAwareCount * 0.5; // Half weight
|
|
61
|
+
// Update nesting
|
|
62
|
+
nestingLevel += openBraces - closeBraces;
|
|
63
|
+
if (nestingLevel < 0)
|
|
64
|
+
nestingLevel = 0;
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
value: Math.round(complexity),
|
|
68
|
+
threshold: THRESHOLDS.maxCognitive,
|
|
69
|
+
status: complexity <= THRESHOLDS.maxCognitive ? 'pass' : 'fail',
|
|
70
|
+
description: 'Dart cognitive complexity (mental load to understand)'
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Analyze Flutter/Dart code quality
|
|
75
|
+
*/
|
|
76
|
+
export function analyzeDartQuality(code) {
|
|
77
|
+
const issues = [];
|
|
78
|
+
const recommendations = [];
|
|
79
|
+
// Flutter-specific anti-patterns
|
|
80
|
+
if (/setState\(\(\)\s*{\s*[\s\S]*?}\s*\)/.test(code)) {
|
|
81
|
+
const setStateCount = (code.match(/setState/g) || []).length;
|
|
82
|
+
if (setStateCount > 5) {
|
|
83
|
+
issues.push('Too many setState calls - consider state management solution');
|
|
84
|
+
recommendations.push('Use Provider, Riverpod, or Bloc for complex state');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// Deep widget nesting
|
|
88
|
+
const widgetNesting = (code.match(/child:\s*\w+\(/g) || []).length;
|
|
89
|
+
if (widgetNesting > 5) {
|
|
90
|
+
issues.push('Deep widget nesting detected');
|
|
91
|
+
recommendations.push('Extract nested widgets into separate methods or classes');
|
|
92
|
+
}
|
|
93
|
+
// Missing const constructors
|
|
94
|
+
const widgetConstructors = (code.match(/\w+\(/g) || []).length;
|
|
95
|
+
const constConstructors = (code.match(/const\s+\w+\(/g) || []).length;
|
|
96
|
+
if (widgetConstructors > 10 && constConstructors < widgetConstructors * 0.3) {
|
|
97
|
+
issues.push('Many widgets without const constructors');
|
|
98
|
+
recommendations.push('Use const constructors for immutable widgets to improve performance');
|
|
99
|
+
}
|
|
100
|
+
// Missing key in lists
|
|
101
|
+
if (/ListView\.builder|GridView\.builder/.test(code) && !/key:/.test(code)) {
|
|
102
|
+
issues.push('ListView/GridView without keys');
|
|
103
|
+
recommendations.push('Add keys to list items for better performance');
|
|
104
|
+
}
|
|
105
|
+
// Missing null safety
|
|
106
|
+
if (!/\w+\?|\w+!/.test(code) && code.includes('null')) {
|
|
107
|
+
issues.push('Possible null safety issues');
|
|
108
|
+
recommendations.push('Enable null safety and use ? and ! operators appropriately');
|
|
109
|
+
}
|
|
110
|
+
// Large build methods
|
|
111
|
+
const buildMethodMatch = code.match(/Widget build\(BuildContext context\)\s*{([\s\S]*?)^ }/m);
|
|
112
|
+
if (buildMethodMatch && buildMethodMatch[1].split('\n').length > 50) {
|
|
113
|
+
issues.push('Build method is too large (> 50 lines)');
|
|
114
|
+
recommendations.push('Extract widgets into separate methods or classes');
|
|
115
|
+
}
|
|
116
|
+
return { issues, recommendations };
|
|
117
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Convention management tool - completely independent
|
|
2
|
+
import { promises as fs } from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
const GUIDES_DIR = path.join(process.cwd(), 'guides');
|
|
5
|
+
const GUIDES_FILE = path.join(GUIDES_DIR, 'coding_guides.json');
|
|
6
|
+
async function ensureGuidesDir() {
|
|
7
|
+
try {
|
|
8
|
+
await fs.access(GUIDES_DIR);
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
await fs.mkdir(GUIDES_DIR, { recursive: true });
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
async function loadGuides() {
|
|
15
|
+
try {
|
|
16
|
+
await ensureGuidesDir();
|
|
17
|
+
const data = await fs.readFile(GUIDES_FILE, 'utf-8');
|
|
18
|
+
return JSON.parse(data);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async function findGuide(name) {
|
|
25
|
+
const guides = await loadGuides();
|
|
26
|
+
return guides.find(g => g.name === name);
|
|
27
|
+
}
|
|
28
|
+
export const getCodingGuideDefinition = {
|
|
29
|
+
name: 'get_coding_guide',
|
|
30
|
+
description: '가이드|규칙|컨벤션|guide|rules|convention|standards|best practices - Get coding guide',
|
|
31
|
+
inputSchema: {
|
|
32
|
+
type: 'object',
|
|
33
|
+
properties: {
|
|
34
|
+
name: { type: 'string', description: 'Guide name to retrieve' },
|
|
35
|
+
category: { type: 'string', description: 'Guide category' }
|
|
36
|
+
},
|
|
37
|
+
required: ['name']
|
|
38
|
+
},
|
|
39
|
+
annotations: {
|
|
40
|
+
title: 'Get Coding Guide',
|
|
41
|
+
audience: ['user', 'assistant']
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
export async function getCodingGuide(args) {
|
|
45
|
+
const { name: guideName, category: guideCategory } = args;
|
|
46
|
+
try {
|
|
47
|
+
const guide = await findGuide(guideName);
|
|
48
|
+
if (guide) {
|
|
49
|
+
return {
|
|
50
|
+
content: [{ type: 'text', text: `Guide: ${guide.name}\nCategory: ${guide.category}\n\n${guide.content}\n\nTags: ${guide.tags.join(', ')} | Updated: ${guide.lastUpdated}` }]
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
return {
|
|
55
|
+
content: [{ type: 'text', text: `Guide not found: "${guideName}". Use list_coding_guides to see available guides.` }]
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
return {
|
|
61
|
+
content: [{ type: 'text', text: `Error retrieving guide: ${error instanceof Error ? error.message : 'Unknown error'}` }]
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language detection utility for code quality tools
|
|
3
|
+
* Supports TypeScript, JavaScript, Python, and Dart/Flutter
|
|
4
|
+
*/
|
|
5
|
+
export function detectLanguage(code) {
|
|
6
|
+
// Dart/Flutter indicators (check first - most specific)
|
|
7
|
+
if (/\bWidget\b/.test(code) ||
|
|
8
|
+
/\bStatelessWidget\b/.test(code) ||
|
|
9
|
+
/\bStatefulWidget\b/.test(code) ||
|
|
10
|
+
/\bBuildContext\b/.test(code) ||
|
|
11
|
+
/Widget build\(/.test(code) ||
|
|
12
|
+
/extends\s+(StatelessWidget|StatefulWidget|State)/.test(code) ||
|
|
13
|
+
(/@override/i.test(code) && /Widget|BuildContext/.test(code))) {
|
|
14
|
+
return 'dart';
|
|
15
|
+
}
|
|
16
|
+
// Python indicators
|
|
17
|
+
if (/^(def|async def)\s/m.test(code) ||
|
|
18
|
+
/^from\s+\w+\s+import/.test(code) ||
|
|
19
|
+
/^\s+def\s/m.test(code) || // Indented function definitions
|
|
20
|
+
/\belif\b/.test(code) ||
|
|
21
|
+
/\b__init__\b/.test(code) ||
|
|
22
|
+
/\bprint\(/m.test(code) ||
|
|
23
|
+
(/:\s*$/m.test(code) && !/;/.test(code)) // Python colon without semicolon
|
|
24
|
+
) {
|
|
25
|
+
return 'python';
|
|
26
|
+
}
|
|
27
|
+
// TypeScript indicators
|
|
28
|
+
if (/:\s*(string|number|boolean|any|void|unknown|never)\b/.test(code) ||
|
|
29
|
+
/interface\s+\w+/.test(code) ||
|
|
30
|
+
/type\s+\w+\s*=/.test(code) ||
|
|
31
|
+
/<[A-Z]\w*>/.test(code) // generics with capital letters
|
|
32
|
+
) {
|
|
33
|
+
return 'typescript';
|
|
34
|
+
}
|
|
35
|
+
// JavaScript (default if none match)
|
|
36
|
+
if (/\b(const|let|var|function|class|async|await|import|export)\b/.test(code)) {
|
|
37
|
+
return 'javascript';
|
|
38
|
+
}
|
|
39
|
+
return 'unknown';
|
|
40
|
+
}
|
|
41
|
+
export function getLanguageName(lang) {
|
|
42
|
+
const names = {
|
|
43
|
+
typescript: 'TypeScript',
|
|
44
|
+
javascript: 'JavaScript',
|
|
45
|
+
python: 'Python',
|
|
46
|
+
dart: 'Dart/Flutter',
|
|
47
|
+
unknown: 'Unknown'
|
|
48
|
+
};
|
|
49
|
+
return names[lang];
|
|
50
|
+
}
|