Skip to main content
Glama
analyzeComplexity.ts9.77 kB
// Convention management tool - completely independent import { Project, ScriptKind } from "ts-morph"; import { PythonParser } from '../../lib/PythonParser.js'; import { ToolResult, ToolDefinition } from '../../types/tool.js'; // Reusable in-memory project to avoid re-parsing standard lib every call const AST_PROJECT = new Project({ useInMemoryFileSystem: true, compilerOptions: { allowJs: true, skipLibCheck: true } }); // Enhanced Software Engineering Metrics const CODE_QUALITY_METRICS = { COMPLEXITY: { maxCyclomaticComplexity: 10, maxCognitiveComplexity: 15, maxFunctionLines: 20, maxNestingDepth: 3, maxParameters: 5 }, COUPLING: { maxDependencies: 7, maxFanOut: 5, preventCircularDeps: true }, COHESION: { singleResponsibility: true, relatedFunctionsOnly: true }, MAINTAINABILITY: { noMagicNumbers: true, consistentNaming: true, properErrorHandling: true, typesSafety: true }, PERFORMANCE: { memoizeExpensiveCalc: true, lazyLoading: true, batchOperations: true } }; export const analyzeComplexityDefinition: ToolDefinition = { name: 'analyze_complexity', description: '복잡도|복잡한지|complexity|how complex|난이도 - Analyze code complexity', inputSchema: { type: 'object', properties: { code: { type: 'string', description: 'Code to analyze' }, metrics: { type: 'string', description: 'Metrics to calculate', enum: ['cyclomatic', 'cognitive', 'halstead', 'all'] } }, required: ['code'] }, annotations: { title: 'Analyze Complexity', audience: ['user', 'assistant'] } }; /** * Calculate cognitive complexity (how hard code is to understand) */ function calculateCognitiveComplexity(code: string) { const CONTROL_STRUCTURES = ['if', 'for', 'while']; let cognitiveScore = 0; const lines = code.split('\n'); let nestingLevel = 0; for (const line of lines) { const trimmed = line.trim(); // Increment for control structures if (CONTROL_STRUCTURES.some(keyword => trimmed.includes(keyword))) { cognitiveScore += 1 + nestingLevel; } // Increment for catch/switch if (trimmed.includes('catch') || trimmed.includes('switch')) { cognitiveScore += 1 + nestingLevel; } // Update nesting level const openBraces = (line.match(/\{/g) || []).length; const closeBraces = (line.match(/\}/g) || []).length; nestingLevel = Math.max(0, nestingLevel + openBraces - closeBraces); } const threshold = CODE_QUALITY_METRICS.COMPLEXITY.maxCognitiveComplexity; return { value: cognitiveScore, threshold, status: cognitiveScore <= threshold ? 'pass' : 'fail', description: 'How difficult the code is to understand' }; } /** * Calculate AST-based cyclomatic complexity */ function calculateAstComplexity(code: string) { const CONTROL_FLOW_NODES = [ 'IfStatement', 'ForStatement', 'ForOfStatement', 'ForInStatement', 'WhileStatement', 'CaseClause', 'ConditionalExpression', 'DoStatement', 'CatchClause', 'BinaryExpression' ]; let astCyclomatic = 1; try { const sourceFile = AST_PROJECT.createSourceFile('temp.ts', code, { overwrite: true, scriptKind: ScriptKind.TS }); sourceFile.forEachDescendant((node) => { if (CONTROL_FLOW_NODES.includes(node.getKindName())) { astCyclomatic++; } }); const threshold = CODE_QUALITY_METRICS.COMPLEXITY.maxCyclomaticComplexity; return { value: astCyclomatic, threshold, status: astCyclomatic <= threshold ? 'pass' : 'fail', description: 'AST 기반 분기/조건문 수를 통한 cyclomatic complexity' }; } catch (e) { return { value: null, status: 'error', description: 'AST 분석 실패: ' + (e instanceof Error ? e.message : String(e)) }; } } /** * Analyze Python code complexity */ async function analyzePythonComplexity(code: string): Promise<ToolResult> { try { const pythonComplexity = await PythonParser.analyzeComplexity(code); const totalComplexity = pythonComplexity.cyclomaticComplexity; const issues: string[] = []; const MAX_COMPLEXITY = 10; if (totalComplexity > MAX_COMPLEXITY) { issues.push('High complexity'); } pythonComplexity.functions.forEach(f => { if (f.complexity > MAX_COMPLEXITY) { issues.push(`Function ${f.name}: complexity ${f.complexity}`); } }); const issuesText = issues.length ? `\nIssues: ${issues.join(', ')}` : ''; return { content: [{ type: 'text', text: `Python Complexity: ${totalComplexity}\nFunctions: ${pythonComplexity.functions.length}\nClasses: ${pythonComplexity.classes.length}${issuesText}` }] }; } catch (error) { return { content: [{ type: 'text', text: `Python analysis error: ${error instanceof Error ? error.message : 'Unknown error'}` }] }; } } export async function analyzeComplexity(args: { code: string; metrics?: string }): Promise<ToolResult> { const { code: complexityCode, metrics: complexityMetrics = 'all' } = args; // Check if this is Python code if (PythonParser.isPythonCode(complexityCode)) { return analyzePythonComplexity(complexityCode); } const complexityAnalysis = { action: 'analyze_complexity', metrics: complexityMetrics, results: {} as any, overallScore: 0, issues: [] as string[], recommendations: [] as string[], status: 'pending' as string }; // AST 기반 cyclomatic complexity 분석 complexityAnalysis.results.astCyclomaticComplexity = calculateAstComplexity(complexityCode); if (complexityMetrics === 'cyclomatic' || complexityMetrics === 'all') { const cyclomaticComplexityScore = (complexityCode.match(/\bif\b|\bfor\b|\bwhile\b|\bcase\b|\b&&\b|\b\|\|\b/g) || []).length + 1; complexityAnalysis.results.cyclomaticComplexity = { value: cyclomaticComplexityScore, threshold: CODE_QUALITY_METRICS.COMPLEXITY.maxCyclomaticComplexity, status: cyclomaticComplexityScore <= CODE_QUALITY_METRICS.COMPLEXITY.maxCyclomaticComplexity ? 'pass' : 'fail', description: 'Number of linearly independent paths through the code' }; } if (complexityMetrics === 'cognitive' || complexityMetrics === 'all') { complexityAnalysis.results.cognitiveComplexity = calculateCognitiveComplexity(complexityCode); } if (complexityMetrics === 'halstead' || complexityMetrics === 'all') { // Halstead metrics calculation (simplified version) const operators = (complexityCode.match(/[+\-*/=<>!&|%^~?:]/g) || []).length; const operands = (complexityCode.match(/\b[a-zA-Z_]\w*\b/g) || []).length; const uniqueOperators = new Set(complexityCode.match(/[+\-*/=<>!&|%^~?:]/g) || []).size; const uniqueOperands = new Set(complexityCode.match(/\b[a-zA-Z_]\w*\b/g) || []).size; const vocabulary = uniqueOperators + uniqueOperands; const length = operators + operands; const calculatedLength = vocabulary > 0 ? uniqueOperators * Math.log2(uniqueOperators) + uniqueOperands * Math.log2(uniqueOperands) : 0; const volume = length * Math.log2(vocabulary); const difficulty = vocabulary > 0 ? (uniqueOperators / 2) * (operands / uniqueOperands) : 0; const effort = difficulty * volume; complexityAnalysis.results.halsteadMetrics = { vocabulary: vocabulary, length: length, calculatedLength: Math.round(calculatedLength), volume: Math.round(volume), difficulty: Math.round(difficulty * 100) / 100, effort: Math.round(effort), timeToProgram: Math.round(effort / 18), // Halstead's formula: effort / 18 seconds bugsDelivered: Math.round(volume / 3000 * 100) / 100, // Halstead's formula: volume / 3000 description: 'Software science metrics measuring program complexity' }; } // Additional complexity metrics if (complexityMetrics === 'all') { const lines = complexityCode.split('\n'); const nonEmptyLines = lines.filter(line => line.trim().length > 0).length; const comments = (complexityCode.match(/\/\*[\s\S]*?\*\/|\/\/.*$/gm) || []).length; const functions = (complexityCode.match(/function\s+\w+|\w+\s*=\s*\(/g) || []).length; const classes = (complexityCode.match(/class\s+\w+/g) || []).length; complexityAnalysis.results.additionalMetrics = { linesOfCode: nonEmptyLines, comments: comments, commentRatio: nonEmptyLines > 0 ? Math.round((comments / nonEmptyLines) * 100) / 100 : 0, functions: functions, classes: classes, averageFunctionLength: functions > 0 ? Math.round(nonEmptyLines / functions) : 0 }; } // Overall assessment const issues = []; let overallScore = 100; if (complexityAnalysis.results.cyclomaticComplexity && complexityAnalysis.results.cyclomaticComplexity.status === 'fail') { issues.push('High cyclomatic complexity detected'); overallScore -= 20; } if (complexityAnalysis.results.cognitiveComplexity && complexityAnalysis.results.cognitiveComplexity.status === 'fail') { issues.push('High cognitive complexity detected'); overallScore -= 25; } if (complexityAnalysis.results.halsteadMetrics && complexityAnalysis.results.halsteadMetrics.difficulty > 10) { issues.push('High Halstead difficulty detected'); overallScore -= 15; } complexityAnalysis.overallScore = Math.max(0, overallScore); complexityAnalysis.issues = issues; return { content: [{ type: 'text', text: `Complexity: ${complexityAnalysis.results.astCyclomaticComplexity?.value ?? 'N/A'}\nScore: ${complexityAnalysis.overallScore}${issues.length ? '\nIssues: ' + issues.join(', ') : ''}` }] }; }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/su-record/hi-ai'

If you have feedback or need assistance with the MCP directory API, please join our Discord server