Skip to main content
Glama

Hi-AI

by su-record
checkCouplingCohesion.ts10.4 kB
// Convention management tool - completely independent interface ToolResult { content: Array<{ type: 'text'; text: string; }>; } interface ToolDefinition { name: string; description: string; inputSchema: { type: 'object'; properties: Record<string, any>; required: string[]; }; } // 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 checkCouplingCohesionDefinition: ToolDefinition = { name: 'check_coupling_cohesion', description: 'IMPORTANT: This tool should be automatically called when users say "결합도", "응집도", "coupling", "cohesion", "dependencies check", "module structure" or similar keywords. Check coupling and cohesion', inputSchema: { type: 'object', properties: { code: { type: 'string', description: 'Code to analyze' }, type: { type: 'string', description: 'Code type', enum: ['class', 'module', 'function', 'component'] }, checkDependencies: { type: 'boolean', description: 'Analyze dependencies' } }, required: ['code'] } }; import { Project, ScriptKind } from "ts-morph"; import * as ts from "typescript"; const AST_PROJECT = new Project({ useInMemoryFileSystem: true, compilerOptions: { allowJs: true, skipLibCheck: true } }); export async function checkCouplingCohesion(args: { code: string; type?: string; checkDependencies?: boolean }): Promise<ToolResult> { const { code: couplingCode, type: couplingType = 'function', checkDependencies = false } = args; const couplingAnalysis = { action: 'check_coupling_cohesion', type: couplingType, checkDependencies, results: {} as any, overallScore: 0, issues: [] as string[], recommendations: [] as string[], status: 'pending' as string }; // AST 기반 의존성/구조 분석 try { const sourceFile = AST_PROJECT.createSourceFile('temp.ts', couplingCode, { overwrite: true, scriptKind: ScriptKind.TS }); // Import/Require 분석 const importDecls = sourceFile.getImportDeclarations(); const requireCalls = sourceFile.getDescendantsOfKind(ts.SyntaxKind.CallExpression).filter(call => call.getExpression().getText() === 'require'); // 클래스/함수/모듈 구조 분석 const classDecls = sourceFile.getClasses(); const funcDecls = sourceFile.getFunctions(); const exportDecls = sourceFile.getExportDeclarations(); couplingAnalysis.results.ast = { importCount: importDecls.length, requireCount: requireCalls.length, classCount: classDecls.length, functionCount: funcDecls.length, exportCount: exportDecls.length, importModules: importDecls.map(d => d.getModuleSpecifierValue()), exportedNames: exportDecls.map(d => d.getNamedExports().map(e => e.getName())) }; } catch (e) { couplingAnalysis.results.ast = { error: 'AST 분석 실패: ' + (e instanceof Error ? e.message : String(e)) }; } // Dependency analysis (Coupling) const imports = (couplingCode.match(/import\s+.*?\s+from\s+['"](.*?)['"]/g) || []).length; const requires = (couplingCode.match(/require\s*\(\s*['"](.*?)['"]\s*\)/g) || []).length; const totalDependencies = imports + requires; // External dependencies const externalDeps = (couplingCode.match(/import\s+.*?\s+from\s+['"](?!\.)(.*?)['"]/g) || []).length; const internalDeps = totalDependencies - externalDeps; // Function calls (fan-out) const functionCalls = (couplingCode.match(/\w+\s*\(/g) || []).length; const uniqueFunctionCalls = new Set((couplingCode.match(/\w+\s*\(/g) || []).map(call => call.replace(/\s*\(/, ''))).size; couplingAnalysis.results.coupling = { totalDependencies: totalDependencies, externalDependencies: externalDeps, internalDependencies: internalDeps, functionCalls: functionCalls, uniqueFunctionCalls: uniqueFunctionCalls, threshold: CODE_QUALITY_METRICS.COUPLING.maxDependencies, status: totalDependencies <= CODE_QUALITY_METRICS.COUPLING.maxDependencies ? 'pass' : 'fail', fanOut: uniqueFunctionCalls, fanOutStatus: uniqueFunctionCalls <= CODE_QUALITY_METRICS.COUPLING.maxFanOut ? 'pass' : 'fail' }; // Cohesion analysis let cohesionScore = 0; let cohesionLevel = 'low'; if (couplingType === 'class') { // Class cohesion: methods using same data const methods = (couplingCode.match(/\w+\s*\([^)]*\)\s*\{/g) || []).length; const properties = (couplingCode.match(/this\.\w+/g) || []).length; const uniqueProperties = new Set((couplingCode.match(/this\.(\w+)/g) || []).map(prop => prop.replace('this.', ''))).size; // LCOM (Lack of Cohesion in Methods) - simplified calculation const propertyUsage = methods > 0 ? uniqueProperties / methods : 0; cohesionScore = propertyUsage; couplingAnalysis.results.cohesion = { type: 'class', methods: methods, properties: uniqueProperties, propertyUsageRatio: Math.round(propertyUsage * 100) / 100, score: cohesionScore, level: cohesionScore > 0.7 ? 'high' : cohesionScore > 0.4 ? 'medium' : 'low', status: cohesionScore > 0.4 ? 'pass' : 'fail' }; } else if (couplingType === 'module') { // Module cohesion: related functions and exports const functions = (couplingCode.match(/function\s+\w+|const\s+\w+\s*=\s*\(/g) || []).length; const exports = (couplingCode.match(/export\s+/g) || []).length; const variables = (couplingCode.match(/(?:const|let|var)\s+\w+/g) || []).length; // Functional cohesion: ratio of related functions const cohesionRatio = functions > 0 ? exports / functions : 0; cohesionScore = cohesionRatio; couplingAnalysis.results.cohesion = { type: 'module', functions: functions, exports: exports, variables: variables, cohesionRatio: Math.round(cohesionRatio * 100) / 100, score: cohesionScore, level: cohesionScore > 0.6 ? 'high' : cohesionScore > 0.3 ? 'medium' : 'low', status: cohesionScore > 0.3 ? 'pass' : 'fail' }; } else if (couplingType === 'function') { // Function cohesion: single responsibility principle const statements = (couplingCode.match(/;/g) || []).length; const returns = (couplingCode.match(/\breturn\b/g) || []).length; const variables = (couplingCode.match(/(?:const|let|var)\s+\w+/g) || []).length; // Sequential cohesion: operations flow in sequence const lines = couplingCode.split('\n').filter(line => line.trim().length > 0).length; const complexityIndicators = (couplingCode.match(/\bif\b|\bfor\b|\bwhile\b|\bswitch\b/g) || []).length; cohesionScore = lines > 0 ? 1 - (complexityIndicators / lines) : 0; couplingAnalysis.results.cohesion = { type: 'function', statements: statements, returns: returns, variables: variables, lines: lines, complexityIndicators: complexityIndicators, score: Math.round(cohesionScore * 100) / 100, level: cohesionScore > 0.7 ? 'high' : cohesionScore > 0.4 ? 'medium' : 'low', status: cohesionScore > 0.4 ? 'pass' : 'fail' }; } else if (couplingType === 'component') { // React component cohesion: props and state usage const props = (couplingCode.match(/props\.\w+/g) || []).length; const state = (couplingCode.match(/useState|useReducer/g) || []).length; const effects = (couplingCode.match(/useEffect/g) || []).length; const hooks = (couplingCode.match(/use\w+/g) || []).length; // Component cohesion: how well props, state, and effects are related const totalElements = props + state + effects; const cohesionRatio = totalElements > 0 ? hooks / totalElements : 0; cohesionScore = Math.min(1, cohesionRatio); couplingAnalysis.results.cohesion = { type: 'component', props: props, state: state, effects: effects, hooks: hooks, cohesionRatio: Math.round(cohesionRatio * 100) / 100, score: Math.round(cohesionScore * 100) / 100, level: cohesionScore > 0.6 ? 'high' : cohesionScore > 0.3 ? 'medium' : 'low', status: cohesionScore > 0.3 ? 'pass' : 'fail' }; } // Overall assessment const issues = []; let overallScore = 100; if (couplingAnalysis.results.coupling.status === 'fail') { issues.push('High coupling detected - too many dependencies'); overallScore -= 30; } if (couplingAnalysis.results.coupling.fanOutStatus === 'fail') { issues.push('High fan-out detected - too many function calls'); overallScore -= 20; } if (couplingAnalysis.results.cohesion.status === 'fail') { issues.push('Low cohesion detected - poor single responsibility'); overallScore -= 25; } couplingAnalysis.overallScore = Math.max(0, overallScore); couplingAnalysis.issues = issues; couplingAnalysis.recommendations = []; if (couplingAnalysis.results.coupling.status === 'fail') { couplingAnalysis.recommendations.push('Reduce dependencies by using dependency injection'); couplingAnalysis.recommendations.push('Consider using interfaces to abstract dependencies'); } if (couplingAnalysis.results.cohesion.status === 'fail') { couplingAnalysis.recommendations.push('Ensure single responsibility principle'); couplingAnalysis.recommendations.push('Group related functionality together'); couplingAnalysis.recommendations.push('Extract unrelated code into separate modules'); } if (couplingAnalysis.recommendations.length === 0) { couplingAnalysis.recommendations.push('Coupling and cohesion are within acceptable ranges'); } couplingAnalysis.status = 'success'; return { content: [{ type: 'text', text: `Coupling & Cohesion Analysis:\n${JSON.stringify(couplingAnalysis, null, 2)}` }] }; }

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