@su-record/vibe 0.1.3 → 0.1.7

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.
Files changed (73) hide show
  1. package/.claude/commands/vibe.analyze.md +123 -0
  2. package/.claude/commands/vibe.diagram.md +174 -0
  3. package/.claude/commands/vibe.plan.md +75 -0
  4. package/.claude/commands/vibe.run.md +151 -0
  5. package/.claude/commands/vibe.spec.md +273 -0
  6. package/.claude/commands/vibe.tasks.md +77 -0
  7. package/.claude/commands/vibe.ui.md +133 -0
  8. package/.claude/commands/vibe.verify.md +128 -0
  9. package/.claude/settings.local.json +14 -0
  10. package/README.md +170 -108
  11. package/bin/vibe +113 -226
  12. package/package.json +7 -7
  13. package/scripts/install-mcp.js +16 -6
  14. package/skills/quality/bdd-contract-testing.md +388 -0
  15. package/templates/contract-backend-template.md +517 -0
  16. package/templates/contract-frontend-template.md +594 -0
  17. package/templates/feature-template.md +259 -0
  18. package/templates/spec-template.md +60 -3
  19. package/mcp/dist/__tests__/complexity.test.js +0 -126
  20. package/mcp/dist/__tests__/memory.test.js +0 -120
  21. package/mcp/dist/__tests__/python-dart-complexity.test.js +0 -146
  22. package/mcp/dist/index.js +0 -230
  23. package/mcp/dist/lib/ContextCompressor.js +0 -305
  24. package/mcp/dist/lib/MemoryManager.js +0 -334
  25. package/mcp/dist/lib/ProjectCache.js +0 -126
  26. package/mcp/dist/lib/PythonParser.js +0 -241
  27. package/mcp/dist/tools/browser/browserPool.js +0 -76
  28. package/mcp/dist/tools/browser/browserUtils.js +0 -135
  29. package/mcp/dist/tools/browser/inspectNetworkRequests.js +0 -140
  30. package/mcp/dist/tools/browser/monitorConsoleLogs.js +0 -97
  31. package/mcp/dist/tools/convention/analyzeComplexity.js +0 -248
  32. package/mcp/dist/tools/convention/applyQualityRules.js +0 -102
  33. package/mcp/dist/tools/convention/checkCouplingCohesion.js +0 -233
  34. package/mcp/dist/tools/convention/complexityMetrics.js +0 -133
  35. package/mcp/dist/tools/convention/dartComplexity.js +0 -117
  36. package/mcp/dist/tools/convention/getCodingGuide.js +0 -64
  37. package/mcp/dist/tools/convention/languageDetector.js +0 -50
  38. package/mcp/dist/tools/convention/pythonComplexity.js +0 -109
  39. package/mcp/dist/tools/convention/suggestImprovements.js +0 -257
  40. package/mcp/dist/tools/convention/validateCodeQuality.js +0 -177
  41. package/mcp/dist/tools/memory/autoSaveContext.js +0 -79
  42. package/mcp/dist/tools/memory/database.js +0 -123
  43. package/mcp/dist/tools/memory/deleteMemory.js +0 -39
  44. package/mcp/dist/tools/memory/listMemories.js +0 -38
  45. package/mcp/dist/tools/memory/memoryConfig.js +0 -27
  46. package/mcp/dist/tools/memory/memorySQLite.js +0 -138
  47. package/mcp/dist/tools/memory/memoryUtils.js +0 -34
  48. package/mcp/dist/tools/memory/migrate.js +0 -113
  49. package/mcp/dist/tools/memory/prioritizeMemory.js +0 -109
  50. package/mcp/dist/tools/memory/recallMemory.js +0 -40
  51. package/mcp/dist/tools/memory/restoreSessionContext.js +0 -69
  52. package/mcp/dist/tools/memory/saveMemory.js +0 -34
  53. package/mcp/dist/tools/memory/searchMemories.js +0 -37
  54. package/mcp/dist/tools/memory/startSession.js +0 -100
  55. package/mcp/dist/tools/memory/updateMemory.js +0 -46
  56. package/mcp/dist/tools/planning/analyzeRequirements.js +0 -166
  57. package/mcp/dist/tools/planning/createUserStories.js +0 -119
  58. package/mcp/dist/tools/planning/featureRoadmap.js +0 -202
  59. package/mcp/dist/tools/planning/generatePrd.js +0 -156
  60. package/mcp/dist/tools/prompt/analyzePrompt.js +0 -145
  61. package/mcp/dist/tools/prompt/enhancePrompt.js +0 -105
  62. package/mcp/dist/tools/semantic/findReferences.js +0 -195
  63. package/mcp/dist/tools/semantic/findSymbol.js +0 -200
  64. package/mcp/dist/tools/thinking/analyzeProblem.js +0 -50
  65. package/mcp/dist/tools/thinking/breakDownProblem.js +0 -140
  66. package/mcp/dist/tools/thinking/createThinkingChain.js +0 -39
  67. package/mcp/dist/tools/thinking/formatAsPlan.js +0 -73
  68. package/mcp/dist/tools/thinking/stepByStepAnalysis.js +0 -58
  69. package/mcp/dist/tools/thinking/thinkAloudProcess.js +0 -75
  70. package/mcp/dist/tools/time/getCurrentTime.js +0 -61
  71. package/mcp/dist/tools/ui/previewUiAscii.js +0 -232
  72. package/mcp/dist/types/tool.js +0 -2
  73. 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
- }