sdd-mcp-server 1.0.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 +256 -0
- package/dist/__tests__/setup.d.ts +44 -0
- package/dist/__tests__/setup.js +178 -0
- package/dist/__tests__/setup.js.map +1 -0
- package/dist/__tests__/test-helpers/mock-factories.d.ts +26 -0
- package/dist/__tests__/test-helpers/mock-factories.js +466 -0
- package/dist/__tests__/test-helpers/mock-factories.js.map +1 -0
- package/dist/adapters/cli/SDDToolAdapter.d.ts +26 -0
- package/dist/adapters/cli/SDDToolAdapter.js +273 -0
- package/dist/adapters/cli/SDDToolAdapter.js.map +1 -0
- package/dist/application/services/CodebaseAnalysisService.d.ts +38 -0
- package/dist/application/services/CodebaseAnalysisService.js +737 -0
- package/dist/application/services/CodebaseAnalysisService.js.map +1 -0
- package/dist/application/services/LocalizationService.d.ts +184 -0
- package/dist/application/services/LocalizationService.js +536 -0
- package/dist/application/services/LocalizationService.js.map +1 -0
- package/dist/application/services/ProjectContextService.d.ts +61 -0
- package/dist/application/services/ProjectContextService.js +550 -0
- package/dist/application/services/ProjectContextService.js.map +1 -0
- package/dist/application/services/ProjectInitializationService.d.ts +57 -0
- package/dist/application/services/ProjectInitializationService.js +485 -0
- package/dist/application/services/ProjectInitializationService.js.map +1 -0
- package/dist/application/services/ProjectService.d.ts +19 -0
- package/dist/application/services/ProjectService.js +159 -0
- package/dist/application/services/ProjectService.js.map +1 -0
- package/dist/application/services/QualityGateService.d.ts +62 -0
- package/dist/application/services/QualityGateService.js +428 -0
- package/dist/application/services/QualityGateService.js.map +1 -0
- package/dist/application/services/QualityService.d.ts +43 -0
- package/dist/application/services/QualityService.js +245 -0
- package/dist/application/services/QualityService.js.map +1 -0
- package/dist/application/services/SteeringDocumentService.d.ts +62 -0
- package/dist/application/services/SteeringDocumentService.js +694 -0
- package/dist/application/services/SteeringDocumentService.js.map +1 -0
- package/dist/application/services/TemplateService.d.ts +47 -0
- package/dist/application/services/TemplateService.js +438 -0
- package/dist/application/services/TemplateService.js.map +1 -0
- package/dist/application/services/WorkflowEngineService.d.ts +56 -0
- package/dist/application/services/WorkflowEngineService.js +348 -0
- package/dist/application/services/WorkflowEngineService.js.map +1 -0
- package/dist/application/services/WorkflowService.d.ts +22 -0
- package/dist/application/services/WorkflowService.js +147 -0
- package/dist/application/services/WorkflowService.js.map +1 -0
- package/dist/application/services/WorkflowValidationService.d.ts +51 -0
- package/dist/application/services/WorkflowValidationService.js +665 -0
- package/dist/application/services/WorkflowValidationService.js.map +1 -0
- package/dist/domain/context/ProjectContext.d.ts +350 -0
- package/dist/domain/context/ProjectContext.js +138 -0
- package/dist/domain/context/ProjectContext.js.map +1 -0
- package/dist/domain/i18n/index.d.ts +286 -0
- package/dist/domain/i18n/index.js +97 -0
- package/dist/domain/i18n/index.js.map +1 -0
- package/dist/domain/plugins/index.d.ts +498 -0
- package/dist/domain/plugins/index.js +157 -0
- package/dist/domain/plugins/index.js.map +1 -0
- package/dist/domain/ports.d.ts +54 -0
- package/dist/domain/ports.js +3 -0
- package/dist/domain/ports.js.map +1 -0
- package/dist/domain/quality/index.d.ts +361 -0
- package/dist/domain/quality/index.js +113 -0
- package/dist/domain/quality/index.js.map +1 -0
- package/dist/domain/services/DomainService.d.ts +18 -0
- package/dist/domain/services/DomainService.js +71 -0
- package/dist/domain/services/DomainService.js.map +1 -0
- package/dist/domain/templates/index.d.ts +158 -0
- package/dist/domain/templates/index.js +22 -0
- package/dist/domain/templates/index.js.map +1 -0
- package/dist/domain/types.d.ts +115 -0
- package/dist/domain/types.js +37 -0
- package/dist/domain/types.js.map +1 -0
- package/dist/domain/workflow/WorkflowStateMachine.d.ts +62 -0
- package/dist/domain/workflow/WorkflowStateMachine.js +286 -0
- package/dist/domain/workflow/WorkflowStateMachine.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +97 -0
- package/dist/index.js.map +1 -0
- package/dist/infrastructure/adapters/AjvValidationAdapter.d.ts +7 -0
- package/dist/infrastructure/adapters/AjvValidationAdapter.js +37 -0
- package/dist/infrastructure/adapters/AjvValidationAdapter.js.map +1 -0
- package/dist/infrastructure/adapters/ConsoleLoggerAdapter.d.ts +8 -0
- package/dist/infrastructure/adapters/ConsoleLoggerAdapter.js +41 -0
- package/dist/infrastructure/adapters/ConsoleLoggerAdapter.js.map +1 -0
- package/dist/infrastructure/adapters/FileBasedTaskTracker.d.ts +9 -0
- package/dist/infrastructure/adapters/FileBasedTaskTracker.js +41 -0
- package/dist/infrastructure/adapters/FileBasedTaskTracker.js.map +1 -0
- package/dist/infrastructure/adapters/HandlebarsTemplateEngine.d.ts +8 -0
- package/dist/infrastructure/adapters/HandlebarsTemplateEngine.js +38 -0
- package/dist/infrastructure/adapters/HandlebarsTemplateEngine.js.map +1 -0
- package/dist/infrastructure/adapters/JsonConfigurationAdapter.d.ts +7 -0
- package/dist/infrastructure/adapters/JsonConfigurationAdapter.js +24 -0
- package/dist/infrastructure/adapters/JsonConfigurationAdapter.js.map +1 -0
- package/dist/infrastructure/adapters/LinusQualityAnalyzer.d.ts +9 -0
- package/dist/infrastructure/adapters/LinusQualityAnalyzer.js +75 -0
- package/dist/infrastructure/adapters/LinusQualityAnalyzer.js.map +1 -0
- package/dist/infrastructure/adapters/NodeFileSystemAdapter.d.ts +12 -0
- package/dist/infrastructure/adapters/NodeFileSystemAdapter.js +39 -0
- package/dist/infrastructure/adapters/NodeFileSystemAdapter.js.map +1 -0
- package/dist/infrastructure/di/container.d.ts +3 -0
- package/dist/infrastructure/di/container.js +98 -0
- package/dist/infrastructure/di/container.js.map +1 -0
- package/dist/infrastructure/di/types.d.ts +39 -0
- package/dist/infrastructure/di/types.js +45 -0
- package/dist/infrastructure/di/types.js.map +1 -0
- package/dist/infrastructure/i18n/I18nextService.d.ts +27 -0
- package/dist/infrastructure/i18n/I18nextService.js +357 -0
- package/dist/infrastructure/i18n/I18nextService.js.map +1 -0
- package/dist/infrastructure/mcp/CapabilityNegotiator.d.ts +21 -0
- package/dist/infrastructure/mcp/CapabilityNegotiator.js +75 -0
- package/dist/infrastructure/mcp/CapabilityNegotiator.js.map +1 -0
- package/dist/infrastructure/mcp/ErrorHandler.d.ts +29 -0
- package/dist/infrastructure/mcp/ErrorHandler.js +101 -0
- package/dist/infrastructure/mcp/ErrorHandler.js.map +1 -0
- package/dist/infrastructure/mcp/MCPServer.d.ts +25 -0
- package/dist/infrastructure/mcp/MCPServer.js +246 -0
- package/dist/infrastructure/mcp/MCPServer.js.map +1 -0
- package/dist/infrastructure/mcp/PromptManager.d.ts +18 -0
- package/dist/infrastructure/mcp/PromptManager.js +373 -0
- package/dist/infrastructure/mcp/PromptManager.js.map +1 -0
- package/dist/infrastructure/mcp/ResourceManager.d.ts +15 -0
- package/dist/infrastructure/mcp/ResourceManager.js +229 -0
- package/dist/infrastructure/mcp/ResourceManager.js.map +1 -0
- package/dist/infrastructure/mcp/SessionManager.d.ts +64 -0
- package/dist/infrastructure/mcp/SessionManager.js +221 -0
- package/dist/infrastructure/mcp/SessionManager.js.map +1 -0
- package/dist/infrastructure/mcp/ToolRegistry.d.ts +48 -0
- package/dist/infrastructure/mcp/ToolRegistry.js +235 -0
- package/dist/infrastructure/mcp/ToolRegistry.js.map +1 -0
- package/dist/infrastructure/platform/PlatformAdapter.d.ts +46 -0
- package/dist/infrastructure/platform/PlatformAdapter.js +355 -0
- package/dist/infrastructure/platform/PlatformAdapter.js.map +1 -0
- package/dist/infrastructure/plugins/HookSystem.d.ts +40 -0
- package/dist/infrastructure/plugins/HookSystem.js +415 -0
- package/dist/infrastructure/plugins/HookSystem.js.map +1 -0
- package/dist/infrastructure/plugins/PluginManager.d.ts +51 -0
- package/dist/infrastructure/plugins/PluginManager.js +650 -0
- package/dist/infrastructure/plugins/PluginManager.js.map +1 -0
- package/dist/infrastructure/plugins/PluginSteeringRegistry.d.ts +63 -0
- package/dist/infrastructure/plugins/PluginSteeringRegistry.js +439 -0
- package/dist/infrastructure/plugins/PluginSteeringRegistry.js.map +1 -0
- package/dist/infrastructure/plugins/PluginToolRegistry.d.ts +54 -0
- package/dist/infrastructure/plugins/PluginToolRegistry.js +490 -0
- package/dist/infrastructure/plugins/PluginToolRegistry.js.map +1 -0
- package/dist/infrastructure/quality/ASTAnalyzer.d.ts +65 -0
- package/dist/infrastructure/quality/ASTAnalyzer.js +439 -0
- package/dist/infrastructure/quality/ASTAnalyzer.js.map +1 -0
- package/dist/infrastructure/quality/LinusCodeReviewer.d.ts +52 -0
- package/dist/infrastructure/quality/LinusCodeReviewer.js +551 -0
- package/dist/infrastructure/quality/LinusCodeReviewer.js.map +1 -0
- package/dist/infrastructure/repositories/InMemoryProjectRepository.d.ts +10 -0
- package/dist/infrastructure/repositories/InMemoryProjectRepository.js +35 -0
- package/dist/infrastructure/repositories/InMemoryProjectRepository.js.map +1 -0
- package/dist/infrastructure/schemas/project.schema.d.ts +192 -0
- package/dist/infrastructure/schemas/project.schema.js +114 -0
- package/dist/infrastructure/schemas/project.schema.js.map +1 -0
- package/dist/infrastructure/templates/FileGenerator.d.ts +15 -0
- package/dist/infrastructure/templates/FileGenerator.js +385 -0
- package/dist/infrastructure/templates/FileGenerator.js.map +1 -0
- package/dist/infrastructure/templates/HandlebarsRenderer.d.ts +16 -0
- package/dist/infrastructure/templates/HandlebarsRenderer.js +252 -0
- package/dist/infrastructure/templates/HandlebarsRenderer.js.map +1 -0
- package/dist/infrastructure/templates/TemplateManager.d.ts +36 -0
- package/dist/infrastructure/templates/TemplateManager.js +777 -0
- package/dist/infrastructure/templates/TemplateManager.js.map +1 -0
- package/package.json +89 -0
|
@@ -0,0 +1,737 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
11
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
12
|
+
};
|
|
13
|
+
import { injectable, inject } from 'inversify';
|
|
14
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
15
|
+
import path from 'path';
|
|
16
|
+
import { TYPES } from '../../infrastructure/di/types.js';
|
|
17
|
+
import { DirectoryPurpose, ProgrammingLanguage, FileType, TechnologyCategory, ArchitecturePatternType, ComplexityType, DependencyType } from '../../domain/context/ProjectContext.js';
|
|
18
|
+
let CodebaseAnalysisService = class CodebaseAnalysisService {
|
|
19
|
+
fileSystem;
|
|
20
|
+
logger;
|
|
21
|
+
validation;
|
|
22
|
+
defaultIgnorePatterns = [
|
|
23
|
+
'node_modules',
|
|
24
|
+
'.git',
|
|
25
|
+
'dist',
|
|
26
|
+
'build',
|
|
27
|
+
'coverage',
|
|
28
|
+
'.next',
|
|
29
|
+
'.nuxt',
|
|
30
|
+
'__pycache__',
|
|
31
|
+
'.pytest_cache',
|
|
32
|
+
'target',
|
|
33
|
+
'bin',
|
|
34
|
+
'obj'
|
|
35
|
+
];
|
|
36
|
+
constructor(fileSystem, logger, validation) {
|
|
37
|
+
this.fileSystem = fileSystem;
|
|
38
|
+
this.logger = logger;
|
|
39
|
+
this.validation = validation;
|
|
40
|
+
}
|
|
41
|
+
async analyzeCodebase(projectPath, options = {}) {
|
|
42
|
+
const correlationId = uuidv4();
|
|
43
|
+
this.logger.info('Starting codebase analysis', {
|
|
44
|
+
correlationId,
|
|
45
|
+
projectPath,
|
|
46
|
+
options
|
|
47
|
+
});
|
|
48
|
+
try {
|
|
49
|
+
const startTime = Date.now();
|
|
50
|
+
// Analyze file structure
|
|
51
|
+
const structure = await this.analyzeFileStructure(projectPath, options);
|
|
52
|
+
// Detect technology stack
|
|
53
|
+
const technologyStack = await this.detectTechnologyStack(structure);
|
|
54
|
+
// Analyze dependencies
|
|
55
|
+
const dependencies = await this.analyzeDependencies(projectPath, structure);
|
|
56
|
+
// Detect architecture patterns
|
|
57
|
+
const patterns = await this.detectArchitecturePatterns(structure, technologyStack);
|
|
58
|
+
// Calculate code metrics
|
|
59
|
+
const metrics = await this.calculateCodeMetrics(structure, projectPath);
|
|
60
|
+
// Identify complexity hotspots
|
|
61
|
+
const hotspots = await this.identifyComplexityHotspots(structure, projectPath);
|
|
62
|
+
const analysisTime = Date.now() - startTime;
|
|
63
|
+
this.logger.info('Codebase analysis completed', {
|
|
64
|
+
correlationId,
|
|
65
|
+
analysisTime,
|
|
66
|
+
fileCount: structure.totalFiles,
|
|
67
|
+
directoryCount: structure.totalDirectories,
|
|
68
|
+
technologyCount: technologyStack.primary.length,
|
|
69
|
+
patternCount: patterns.length,
|
|
70
|
+
hotspotCount: hotspots.length
|
|
71
|
+
});
|
|
72
|
+
return {
|
|
73
|
+
structure,
|
|
74
|
+
dependencies,
|
|
75
|
+
patterns,
|
|
76
|
+
metrics,
|
|
77
|
+
hotspots,
|
|
78
|
+
lastAnalyzed: new Date()
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
this.logger.error('Codebase analysis failed', error, {
|
|
83
|
+
correlationId,
|
|
84
|
+
projectPath
|
|
85
|
+
});
|
|
86
|
+
// Return minimal analysis on failure
|
|
87
|
+
return {
|
|
88
|
+
structure: {
|
|
89
|
+
root: projectPath,
|
|
90
|
+
directories: [],
|
|
91
|
+
files: [],
|
|
92
|
+
totalFiles: 0,
|
|
93
|
+
totalDirectories: 0,
|
|
94
|
+
gitIgnored: []
|
|
95
|
+
},
|
|
96
|
+
dependencies: {
|
|
97
|
+
external: [],
|
|
98
|
+
internal: [],
|
|
99
|
+
devDependencies: [],
|
|
100
|
+
peerDependencies: [],
|
|
101
|
+
graph: { nodes: [], edges: [], cycles: [], orphans: [] }
|
|
102
|
+
},
|
|
103
|
+
patterns: [],
|
|
104
|
+
metrics: {
|
|
105
|
+
linesOfCode: 0,
|
|
106
|
+
complexity: {
|
|
107
|
+
cyclomatic: 0,
|
|
108
|
+
cognitive: 0,
|
|
109
|
+
halstead: {
|
|
110
|
+
vocabulary: 0,
|
|
111
|
+
length: 0,
|
|
112
|
+
difficulty: 0,
|
|
113
|
+
effort: 0,
|
|
114
|
+
timeToCode: 0,
|
|
115
|
+
bugsDelivered: 0
|
|
116
|
+
},
|
|
117
|
+
nesting: { average: 0, maximum: 0, violationsOver3: 0 }
|
|
118
|
+
},
|
|
119
|
+
maintainability: {
|
|
120
|
+
index: 0,
|
|
121
|
+
duplication: 0,
|
|
122
|
+
cohesion: 0,
|
|
123
|
+
coupling: 0,
|
|
124
|
+
debtRatio: 0
|
|
125
|
+
},
|
|
126
|
+
qualityScore: 0
|
|
127
|
+
},
|
|
128
|
+
hotspots: [],
|
|
129
|
+
lastAnalyzed: new Date()
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
async analyzeFileStructure(projectPath, options) {
|
|
134
|
+
const directories = [];
|
|
135
|
+
const files = [];
|
|
136
|
+
const gitIgnored = [];
|
|
137
|
+
let totalFiles = 0;
|
|
138
|
+
let totalDirectories = 0;
|
|
139
|
+
const ignorePatterns = [
|
|
140
|
+
...this.defaultIgnorePatterns,
|
|
141
|
+
...(options.skipPatterns || [])
|
|
142
|
+
];
|
|
143
|
+
await this.traverseDirectory(projectPath, projectPath, directories, files, ignorePatterns, options.maxDepth || 10, 0);
|
|
144
|
+
totalFiles = files.length;
|
|
145
|
+
totalDirectories = directories.length;
|
|
146
|
+
// Read .gitignore if exists
|
|
147
|
+
const gitignorePath = path.join(projectPath, '.gitignore');
|
|
148
|
+
if (await this.fileSystem.exists(gitignorePath)) {
|
|
149
|
+
try {
|
|
150
|
+
const gitignoreContent = await this.fileSystem.readFile(gitignorePath);
|
|
151
|
+
gitIgnored.push(...gitignoreContent.split('\n').filter(line => line.trim() && !line.startsWith('#')));
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
this.logger.warn('Failed to read .gitignore', { projectPath });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
root: projectPath,
|
|
159
|
+
directories,
|
|
160
|
+
files,
|
|
161
|
+
totalFiles,
|
|
162
|
+
totalDirectories,
|
|
163
|
+
gitIgnored
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
async traverseDirectory(basePath, currentPath, directories, files, ignorePatterns, maxDepth, currentDepth) {
|
|
167
|
+
if (currentDepth >= maxDepth)
|
|
168
|
+
return;
|
|
169
|
+
try {
|
|
170
|
+
const items = await this.fileSystem.readdir(currentPath);
|
|
171
|
+
const children = [];
|
|
172
|
+
let fileCount = 0;
|
|
173
|
+
const relativePath = path.relative(basePath, currentPath);
|
|
174
|
+
for (const item of items) {
|
|
175
|
+
const itemPath = path.join(currentPath, item);
|
|
176
|
+
const itemRelativePath = path.relative(basePath, itemPath);
|
|
177
|
+
// Skip ignored patterns
|
|
178
|
+
if (ignorePatterns.some(pattern => itemRelativePath.includes(pattern))) {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
try {
|
|
182
|
+
const stat = await this.fileSystem.stat(itemPath);
|
|
183
|
+
if (stat.isDirectory()) {
|
|
184
|
+
children.push(item);
|
|
185
|
+
// Recursively analyze subdirectory
|
|
186
|
+
await this.traverseDirectory(basePath, itemPath, directories, files, ignorePatterns, maxDepth, currentDepth + 1);
|
|
187
|
+
fileCount += await this.countFilesInDirectory(itemPath, ignorePatterns);
|
|
188
|
+
}
|
|
189
|
+
else if (stat.isFile()) {
|
|
190
|
+
const fileNode = await this.analyzeFile(basePath, itemPath);
|
|
191
|
+
files.push(fileNode);
|
|
192
|
+
children.push(item);
|
|
193
|
+
fileCount++;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
this.logger.debug('Failed to analyze item', { itemPath });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// Create directory node
|
|
201
|
+
const directoryNode = {
|
|
202
|
+
path: relativePath || '.',
|
|
203
|
+
name: path.basename(currentPath),
|
|
204
|
+
children,
|
|
205
|
+
fileCount,
|
|
206
|
+
purpose: this.determineDirectoryPurpose(currentPath, children)
|
|
207
|
+
};
|
|
208
|
+
directories.push(directoryNode);
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
this.logger.debug('Failed to read directory', { currentPath });
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
async analyzeFile(basePath, filePath) {
|
|
215
|
+
const relativePath = path.relative(basePath, filePath);
|
|
216
|
+
const fileName = path.basename(filePath);
|
|
217
|
+
const extension = path.extname(filePath).toLowerCase();
|
|
218
|
+
let size = 0;
|
|
219
|
+
let lastModified = new Date();
|
|
220
|
+
try {
|
|
221
|
+
const stat = await this.fileSystem.stat(filePath);
|
|
222
|
+
size = stat.size || 0;
|
|
223
|
+
lastModified = stat.mtime || new Date();
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
this.logger.debug('Failed to get file stats', { filePath });
|
|
227
|
+
}
|
|
228
|
+
const language = this.detectFileLanguage(extension, fileName);
|
|
229
|
+
const type = this.determineFileType(relativePath, fileName, extension);
|
|
230
|
+
const dependencies = await this.extractFileDependencies(filePath, language);
|
|
231
|
+
const complexity = await this.calculateFileComplexity(filePath, language);
|
|
232
|
+
return {
|
|
233
|
+
path: relativePath,
|
|
234
|
+
name: fileName,
|
|
235
|
+
extension,
|
|
236
|
+
size,
|
|
237
|
+
language,
|
|
238
|
+
type,
|
|
239
|
+
complexity,
|
|
240
|
+
dependencies,
|
|
241
|
+
lastModified
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
detectFileLanguage(extension, fileName) {
|
|
245
|
+
const languageMap = {
|
|
246
|
+
'.ts': ProgrammingLanguage.TYPESCRIPT,
|
|
247
|
+
'.tsx': ProgrammingLanguage.TYPESCRIPT,
|
|
248
|
+
'.js': ProgrammingLanguage.JAVASCRIPT,
|
|
249
|
+
'.jsx': ProgrammingLanguage.JAVASCRIPT,
|
|
250
|
+
'.mjs': ProgrammingLanguage.JAVASCRIPT,
|
|
251
|
+
'.py': ProgrammingLanguage.PYTHON,
|
|
252
|
+
'.java': ProgrammingLanguage.JAVA,
|
|
253
|
+
'.go': ProgrammingLanguage.GO,
|
|
254
|
+
'.rs': ProgrammingLanguage.RUST,
|
|
255
|
+
'.cpp': ProgrammingLanguage.CPP,
|
|
256
|
+
'.cc': ProgrammingLanguage.CPP,
|
|
257
|
+
'.cxx': ProgrammingLanguage.CPP,
|
|
258
|
+
'.cs': ProgrammingLanguage.CSHARP,
|
|
259
|
+
'.php': ProgrammingLanguage.PHP,
|
|
260
|
+
'.rb': ProgrammingLanguage.RUBY,
|
|
261
|
+
'.swift': ProgrammingLanguage.SWIFT,
|
|
262
|
+
'.kt': ProgrammingLanguage.KOTLIN,
|
|
263
|
+
'.dart': ProgrammingLanguage.DART
|
|
264
|
+
};
|
|
265
|
+
return languageMap[extension] || ProgrammingLanguage.UNKNOWN;
|
|
266
|
+
}
|
|
267
|
+
determineFileType(relativePath, fileName, extension) {
|
|
268
|
+
// Configuration files
|
|
269
|
+
if (fileName.includes('config') ||
|
|
270
|
+
['package.json', 'tsconfig.json', '.eslintrc', 'webpack.config'].some(config => fileName.includes(config))) {
|
|
271
|
+
return FileType.CONFIG;
|
|
272
|
+
}
|
|
273
|
+
// Test files
|
|
274
|
+
if (fileName.includes('.test.') ||
|
|
275
|
+
fileName.includes('.spec.') ||
|
|
276
|
+
relativePath.includes('test') ||
|
|
277
|
+
relativePath.includes('__tests__')) {
|
|
278
|
+
return FileType.TEST;
|
|
279
|
+
}
|
|
280
|
+
// Documentation
|
|
281
|
+
if (['.md', '.txt', '.rst', '.adoc'].includes(extension) ||
|
|
282
|
+
fileName.toLowerCase().includes('readme') ||
|
|
283
|
+
relativePath.includes('docs')) {
|
|
284
|
+
return FileType.DOCUMENTATION;
|
|
285
|
+
}
|
|
286
|
+
// Build outputs
|
|
287
|
+
if (relativePath.includes('dist') ||
|
|
288
|
+
relativePath.includes('build') ||
|
|
289
|
+
relativePath.includes('target')) {
|
|
290
|
+
return FileType.BUILD;
|
|
291
|
+
}
|
|
292
|
+
// Source files
|
|
293
|
+
if (['.ts', '.tsx', '.js', '.jsx', '.py', '.java', '.go', '.rs', '.cpp', '.cs'].includes(extension)) {
|
|
294
|
+
return FileType.SOURCE;
|
|
295
|
+
}
|
|
296
|
+
// Assets
|
|
297
|
+
if (['.png', '.jpg', '.jpeg', '.gif', '.svg', '.css', '.scss', '.less'].includes(extension)) {
|
|
298
|
+
return FileType.ASSET;
|
|
299
|
+
}
|
|
300
|
+
return FileType.UNKNOWN;
|
|
301
|
+
}
|
|
302
|
+
determineDirectoryPurpose(dirPath, children) {
|
|
303
|
+
const dirName = path.basename(dirPath).toLowerCase();
|
|
304
|
+
// Source directories
|
|
305
|
+
if (['src', 'source', 'lib', 'app'].includes(dirName)) {
|
|
306
|
+
return DirectoryPurpose.SOURCE;
|
|
307
|
+
}
|
|
308
|
+
// Test directories
|
|
309
|
+
if (['test', 'tests', '__tests__', 'spec', 'specs'].includes(dirName)) {
|
|
310
|
+
return DirectoryPurpose.TEST;
|
|
311
|
+
}
|
|
312
|
+
// Configuration directories
|
|
313
|
+
if (['config', 'configuration', '.vscode', '.github'].includes(dirName)) {
|
|
314
|
+
return DirectoryPurpose.CONFIG;
|
|
315
|
+
}
|
|
316
|
+
// Documentation directories
|
|
317
|
+
if (['docs', 'doc', 'documentation'].includes(dirName)) {
|
|
318
|
+
return DirectoryPurpose.DOCS;
|
|
319
|
+
}
|
|
320
|
+
// Build directories
|
|
321
|
+
if (['dist', 'build', 'target', 'bin', 'out'].includes(dirName)) {
|
|
322
|
+
return DirectoryPurpose.BUILD;
|
|
323
|
+
}
|
|
324
|
+
// Asset directories
|
|
325
|
+
if (['assets', 'static', 'public', 'resources'].includes(dirName)) {
|
|
326
|
+
return DirectoryPurpose.ASSETS;
|
|
327
|
+
}
|
|
328
|
+
// Vendor directories
|
|
329
|
+
if (['vendor', 'third_party', 'external'].includes(dirName)) {
|
|
330
|
+
return DirectoryPurpose.VENDOR;
|
|
331
|
+
}
|
|
332
|
+
return DirectoryPurpose.UNKNOWN;
|
|
333
|
+
}
|
|
334
|
+
async extractFileDependencies(filePath, language) {
|
|
335
|
+
try {
|
|
336
|
+
const content = await this.fileSystem.readFile(filePath);
|
|
337
|
+
const dependencies = [];
|
|
338
|
+
switch (language) {
|
|
339
|
+
case ProgrammingLanguage.TYPESCRIPT:
|
|
340
|
+
case ProgrammingLanguage.JAVASCRIPT:
|
|
341
|
+
// Extract import/require statements
|
|
342
|
+
const importRegex = /(?:import.*from\s+['"`]([^'"`]+)['"`]|require\(['"`]([^'"`]+)['"`]\))/g;
|
|
343
|
+
let match;
|
|
344
|
+
while ((match = importRegex.exec(content)) !== null) {
|
|
345
|
+
const dep = match[1] || match[2];
|
|
346
|
+
if (dep && !dep.startsWith('.')) {
|
|
347
|
+
const packageName = dep.split('/')[0];
|
|
348
|
+
if (packageName) {
|
|
349
|
+
dependencies.push(packageName); // Get package name
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
break;
|
|
354
|
+
case ProgrammingLanguage.PYTHON:
|
|
355
|
+
// Extract import statements
|
|
356
|
+
const pythonImportRegex = /(?:^|\n)\s*(?:import\s+([a-zA-Z_][a-zA-Z0-9_.]*)|from\s+([a-zA-Z_][a-zA-Z0-9_.]*)\s+import)/gm;
|
|
357
|
+
let pythonMatch;
|
|
358
|
+
while ((pythonMatch = pythonImportRegex.exec(content)) !== null) {
|
|
359
|
+
const dep = pythonMatch[1] || pythonMatch[2];
|
|
360
|
+
if (dep && !dep.startsWith('.')) {
|
|
361
|
+
const packageName = dep.split('.')[0];
|
|
362
|
+
if (packageName) {
|
|
363
|
+
dependencies.push(packageName);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
break;
|
|
368
|
+
case ProgrammingLanguage.JAVA:
|
|
369
|
+
// Extract import statements
|
|
370
|
+
const javaImportRegex = /import\s+([a-zA-Z_][a-zA-Z0-9_.]*)/g;
|
|
371
|
+
let javaMatch;
|
|
372
|
+
while ((javaMatch = javaImportRegex.exec(content)) !== null) {
|
|
373
|
+
const dep = javaMatch[1];
|
|
374
|
+
if (dep && !dep.startsWith('java.') && !dep.startsWith('javax.')) {
|
|
375
|
+
const packageName = dep.split('.')[0];
|
|
376
|
+
if (packageName) {
|
|
377
|
+
dependencies.push(packageName);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
return [...new Set(dependencies)]; // Remove duplicates
|
|
384
|
+
}
|
|
385
|
+
catch (error) {
|
|
386
|
+
return [];
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
async calculateFileComplexity(filePath, language) {
|
|
390
|
+
try {
|
|
391
|
+
const content = await this.fileSystem.readFile(filePath);
|
|
392
|
+
// Simple cyclomatic complexity calculation
|
|
393
|
+
const complexityKeywords = {
|
|
394
|
+
[ProgrammingLanguage.TYPESCRIPT]: ['if', 'else', 'while', 'for', 'switch', 'case', 'catch', '&&', '||'],
|
|
395
|
+
[ProgrammingLanguage.JAVASCRIPT]: ['if', 'else', 'while', 'for', 'switch', 'case', 'catch', '&&', '||'],
|
|
396
|
+
[ProgrammingLanguage.PYTHON]: ['if', 'elif', 'else', 'while', 'for', 'try', 'except', 'and', 'or'],
|
|
397
|
+
[ProgrammingLanguage.JAVA]: ['if', 'else', 'while', 'for', 'switch', 'case', 'catch', '&&', '||'],
|
|
398
|
+
[ProgrammingLanguage.GO]: ['if', 'else', 'for', 'switch', 'case', '&&', '||'],
|
|
399
|
+
[ProgrammingLanguage.RUST]: ['if', 'else', 'while', 'for', 'match', '&&', '||'],
|
|
400
|
+
[ProgrammingLanguage.CPP]: ['if', 'else', 'while', 'for', 'switch', 'case', 'catch', '&&', '||'],
|
|
401
|
+
[ProgrammingLanguage.CSHARP]: ['if', 'else', 'while', 'for', 'switch', 'case', 'catch', '&&', '||'],
|
|
402
|
+
[ProgrammingLanguage.PHP]: ['if', 'else', 'while', 'for', 'switch', 'case', 'catch', '&&', '||'],
|
|
403
|
+
[ProgrammingLanguage.RUBY]: ['if', 'else', 'while', 'for', 'case', '&&', '||'],
|
|
404
|
+
[ProgrammingLanguage.SWIFT]: ['if', 'else', 'while', 'for', 'switch', 'case', '&&', '||'],
|
|
405
|
+
[ProgrammingLanguage.KOTLIN]: ['if', 'else', 'while', 'for', 'when', '&&', '||'],
|
|
406
|
+
[ProgrammingLanguage.DART]: ['if', 'else', 'while', 'for', 'switch', 'case', '&&', '||'],
|
|
407
|
+
[ProgrammingLanguage.UNKNOWN]: []
|
|
408
|
+
};
|
|
409
|
+
const keywords = complexityKeywords[language] || [];
|
|
410
|
+
let complexity = 1; // Base complexity
|
|
411
|
+
for (const keyword of keywords) {
|
|
412
|
+
const regex = new RegExp(`\\b${keyword}\\b`, 'g');
|
|
413
|
+
const matches = content.match(regex);
|
|
414
|
+
if (matches) {
|
|
415
|
+
complexity += matches.length;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return complexity;
|
|
419
|
+
}
|
|
420
|
+
catch (error) {
|
|
421
|
+
return 1;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
async detectTechnologyStack(structure) {
|
|
425
|
+
const technologies = [];
|
|
426
|
+
// Language detection based on file extensions
|
|
427
|
+
const languageCount = new Map();
|
|
428
|
+
for (const file of structure.files) {
|
|
429
|
+
if (file.language !== ProgrammingLanguage.UNKNOWN) {
|
|
430
|
+
languageCount.set(file.language, (languageCount.get(file.language) || 0) + 1);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
// Convert to technologies
|
|
434
|
+
const totalFiles = structure.files.length;
|
|
435
|
+
for (const [lang, count] of languageCount.entries()) {
|
|
436
|
+
const confidence = count / totalFiles;
|
|
437
|
+
if (confidence > 0.1) { // At least 10% of files
|
|
438
|
+
technologies.push({
|
|
439
|
+
name: lang,
|
|
440
|
+
category: TechnologyCategory.LANGUAGE,
|
|
441
|
+
confidence,
|
|
442
|
+
evidence: [`${count} files use ${lang}`]
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
// Framework detection based on package.json or specific files
|
|
447
|
+
const frameworks = await this.detectFrameworks(structure);
|
|
448
|
+
technologies.push(...frameworks);
|
|
449
|
+
// Sort by confidence
|
|
450
|
+
technologies.sort((a, b) => b.confidence - a.confidence);
|
|
451
|
+
return {
|
|
452
|
+
primary: technologies.filter(t => t.category === TechnologyCategory.LANGUAGE),
|
|
453
|
+
frameworks: technologies.filter(t => t.category === TechnologyCategory.FRAMEWORK),
|
|
454
|
+
tools: technologies.filter(t => t.category === TechnologyCategory.TOOL),
|
|
455
|
+
runtime: technologies.filter(t => t.category === TechnologyCategory.RUNTIME)
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
async detectFrameworks(structure) {
|
|
459
|
+
const frameworks = [];
|
|
460
|
+
// Look for framework-specific files
|
|
461
|
+
const frameworkIndicators = {
|
|
462
|
+
'package.json': { name: 'Node.js', category: TechnologyCategory.RUNTIME, evidence: 'package.json found' },
|
|
463
|
+
'requirements.txt': { name: 'Python', category: TechnologyCategory.RUNTIME, evidence: 'requirements.txt found' },
|
|
464
|
+
'pom.xml': { name: 'Maven', category: TechnologyCategory.TOOL, evidence: 'pom.xml found' },
|
|
465
|
+
'build.gradle': { name: 'Gradle', category: TechnologyCategory.TOOL, evidence: 'build.gradle found' },
|
|
466
|
+
'Cargo.toml': { name: 'Cargo', category: TechnologyCategory.TOOL, evidence: 'Cargo.toml found' },
|
|
467
|
+
'go.mod': { name: 'Go Modules', category: TechnologyCategory.TOOL, evidence: 'go.mod found' },
|
|
468
|
+
'tsconfig.json': { name: 'TypeScript', category: TechnologyCategory.LANGUAGE, evidence: 'tsconfig.json found' },
|
|
469
|
+
'webpack.config.js': { name: 'Webpack', category: TechnologyCategory.TOOL, evidence: 'webpack.config.js found' },
|
|
470
|
+
'next.config.js': { name: 'Next.js', category: TechnologyCategory.FRAMEWORK, evidence: 'next.config.js found' },
|
|
471
|
+
'nuxt.config.js': { name: 'Nuxt.js', category: TechnologyCategory.FRAMEWORK, evidence: 'nuxt.config.js found' },
|
|
472
|
+
'angular.json': { name: 'Angular', category: TechnologyCategory.FRAMEWORK, evidence: 'angular.json found' },
|
|
473
|
+
'vue.config.js': { name: 'Vue.js', category: TechnologyCategory.FRAMEWORK, evidence: 'vue.config.js found' }
|
|
474
|
+
};
|
|
475
|
+
for (const file of structure.files) {
|
|
476
|
+
const indicator = frameworkIndicators[file.name];
|
|
477
|
+
if (indicator) {
|
|
478
|
+
frameworks.push({
|
|
479
|
+
name: indicator.name,
|
|
480
|
+
category: indicator.category,
|
|
481
|
+
confidence: 0.9,
|
|
482
|
+
evidence: [indicator.evidence]
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return frameworks;
|
|
487
|
+
}
|
|
488
|
+
async detectArchitecturePatterns(structure, technologyStack) {
|
|
489
|
+
const patterns = [];
|
|
490
|
+
// Detect Clean Architecture
|
|
491
|
+
const hasLayers = ['domain', 'application', 'infrastructure'].every(layer => structure.directories.some(dir => dir.name.toLowerCase().includes(layer)));
|
|
492
|
+
if (hasLayers) {
|
|
493
|
+
patterns.push({
|
|
494
|
+
name: ArchitecturePatternType.CLEAN,
|
|
495
|
+
confidence: 0.8,
|
|
496
|
+
evidence: [
|
|
497
|
+
{ description: 'Domain layer found', location: 'src/domain', strength: 'strong' },
|
|
498
|
+
{ description: 'Application layer found', location: 'src/application', strength: 'strong' },
|
|
499
|
+
{ description: 'Infrastructure layer found', location: 'src/infrastructure', strength: 'strong' }
|
|
500
|
+
],
|
|
501
|
+
violations: []
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
// Detect MVC pattern
|
|
505
|
+
const hasMVC = ['models', 'views', 'controllers'].every(component => structure.directories.some(dir => dir.name.toLowerCase().includes(component)) ||
|
|
506
|
+
structure.files.some(file => file.name.toLowerCase().includes(component)));
|
|
507
|
+
if (hasMVC) {
|
|
508
|
+
patterns.push({
|
|
509
|
+
name: ArchitecturePatternType.MVC,
|
|
510
|
+
confidence: 0.7,
|
|
511
|
+
evidence: [
|
|
512
|
+
{ description: 'MVC structure detected', location: 'project root', strength: 'medium' }
|
|
513
|
+
],
|
|
514
|
+
violations: []
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
// Detect Microservices (based on multiple service directories or docker-compose)
|
|
518
|
+
const hasServices = structure.directories.filter(dir => dir.name.includes('service') || dir.name.includes('microservice')).length > 1;
|
|
519
|
+
const hasDockerCompose = structure.files.some(file => file.name === 'docker-compose.yml' || file.name === 'docker-compose.yaml');
|
|
520
|
+
if (hasServices || hasDockerCompose) {
|
|
521
|
+
patterns.push({
|
|
522
|
+
name: ArchitecturePatternType.MICROSERVICES,
|
|
523
|
+
confidence: hasServices ? 0.8 : 0.6,
|
|
524
|
+
evidence: [
|
|
525
|
+
{ description: 'Multiple services detected', location: 'project structure', strength: 'medium' }
|
|
526
|
+
],
|
|
527
|
+
violations: []
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
return patterns;
|
|
531
|
+
}
|
|
532
|
+
async analyzeDependencies(projectPath, structure) {
|
|
533
|
+
const external = [];
|
|
534
|
+
const devDependencies = [];
|
|
535
|
+
const peerDependencies = [];
|
|
536
|
+
const internal = [];
|
|
537
|
+
// Analyze package.json if it exists
|
|
538
|
+
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
539
|
+
if (await this.fileSystem.exists(packageJsonPath)) {
|
|
540
|
+
try {
|
|
541
|
+
const packageContent = await this.fileSystem.readFile(packageJsonPath);
|
|
542
|
+
const packageJson = JSON.parse(packageContent);
|
|
543
|
+
// External dependencies
|
|
544
|
+
if (packageJson.dependencies) {
|
|
545
|
+
for (const [name, version] of Object.entries(packageJson.dependencies)) {
|
|
546
|
+
external.push({
|
|
547
|
+
name,
|
|
548
|
+
version: version,
|
|
549
|
+
type: DependencyType.RUNTIME,
|
|
550
|
+
purpose: 'Runtime dependency'
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
// Dev dependencies
|
|
555
|
+
if (packageJson.devDependencies) {
|
|
556
|
+
for (const [name, version] of Object.entries(packageJson.devDependencies)) {
|
|
557
|
+
devDependencies.push({
|
|
558
|
+
name,
|
|
559
|
+
version: version,
|
|
560
|
+
type: DependencyType.DEVELOPMENT,
|
|
561
|
+
purpose: 'Development dependency'
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
// Peer dependencies
|
|
566
|
+
if (packageJson.peerDependencies) {
|
|
567
|
+
for (const [name, version] of Object.entries(packageJson.peerDependencies)) {
|
|
568
|
+
peerDependencies.push({
|
|
569
|
+
name,
|
|
570
|
+
version: version,
|
|
571
|
+
type: DependencyType.PEER,
|
|
572
|
+
purpose: 'Peer dependency'
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
catch (error) {
|
|
578
|
+
this.logger.debug('Failed to parse package.json', { packageJsonPath });
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
// Build dependency graph
|
|
582
|
+
const graph = this.buildDependencyGraph(structure, internal);
|
|
583
|
+
return {
|
|
584
|
+
external,
|
|
585
|
+
internal,
|
|
586
|
+
devDependencies,
|
|
587
|
+
peerDependencies,
|
|
588
|
+
graph
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
buildDependencyGraph(structure, internal) {
|
|
592
|
+
const nodes = [];
|
|
593
|
+
const edges = [];
|
|
594
|
+
// Add all source files as nodes
|
|
595
|
+
for (const file of structure.files) {
|
|
596
|
+
if (file.type === FileType.SOURCE) {
|
|
597
|
+
nodes.push(file.path);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
// Build edges from file dependencies
|
|
601
|
+
for (const file of structure.files) {
|
|
602
|
+
if (file.type === FileType.SOURCE && file.dependencies.length > 0) {
|
|
603
|
+
for (const dep of file.dependencies) {
|
|
604
|
+
const depFile = structure.files.find(f => f.name.includes(dep) || f.path.includes(dep));
|
|
605
|
+
if (depFile) {
|
|
606
|
+
edges.push({
|
|
607
|
+
from: file.path,
|
|
608
|
+
to: depFile.path,
|
|
609
|
+
weight: 1
|
|
610
|
+
});
|
|
611
|
+
internal.push({
|
|
612
|
+
from: file.path,
|
|
613
|
+
to: depFile.path,
|
|
614
|
+
type: 'import',
|
|
615
|
+
strength: 'medium'
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
// Detect cycles (simplified)
|
|
622
|
+
const cycles = [];
|
|
623
|
+
// TODO: Implement proper cycle detection algorithm
|
|
624
|
+
// Find orphan nodes (no dependencies)
|
|
625
|
+
const orphans = nodes.filter(node => !edges.some(edge => edge.from === node || edge.to === node));
|
|
626
|
+
return {
|
|
627
|
+
nodes,
|
|
628
|
+
edges,
|
|
629
|
+
cycles,
|
|
630
|
+
orphans
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
async calculateCodeMetrics(structure, projectPath) {
|
|
634
|
+
let totalLines = 0;
|
|
635
|
+
let totalComplexity = 0;
|
|
636
|
+
let sourceFiles = 0;
|
|
637
|
+
for (const file of structure.files) {
|
|
638
|
+
if (file.type === FileType.SOURCE) {
|
|
639
|
+
try {
|
|
640
|
+
const filePath = path.join(projectPath, file.path);
|
|
641
|
+
const content = await this.fileSystem.readFile(filePath);
|
|
642
|
+
const lines = content.split('\n').length;
|
|
643
|
+
totalLines += lines;
|
|
644
|
+
totalComplexity += file.complexity || 1;
|
|
645
|
+
sourceFiles++;
|
|
646
|
+
}
|
|
647
|
+
catch (error) {
|
|
648
|
+
this.logger.debug('Failed to read file for metrics', { filePath: file.path });
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
const averageComplexity = sourceFiles > 0 ? totalComplexity / sourceFiles : 0;
|
|
653
|
+
// Calculate quality score (simplified)
|
|
654
|
+
const qualityScore = Math.max(0, Math.min(100, 100 - (averageComplexity - 10) * 5));
|
|
655
|
+
return {
|
|
656
|
+
linesOfCode: totalLines,
|
|
657
|
+
complexity: {
|
|
658
|
+
cyclomatic: totalComplexity,
|
|
659
|
+
cognitive: totalComplexity * 0.8, // Approximation
|
|
660
|
+
halstead: {
|
|
661
|
+
vocabulary: sourceFiles * 10, // Approximation
|
|
662
|
+
length: totalLines,
|
|
663
|
+
difficulty: averageComplexity,
|
|
664
|
+
effort: totalLines * averageComplexity,
|
|
665
|
+
timeToCode: (totalLines * averageComplexity) / 100,
|
|
666
|
+
bugsDelivered: totalLines / 3000 // Approximation
|
|
667
|
+
},
|
|
668
|
+
nesting: {
|
|
669
|
+
average: 2, // Default approximation
|
|
670
|
+
maximum: Math.ceil(averageComplexity / 2),
|
|
671
|
+
violationsOver3: Math.max(0, Math.ceil(averageComplexity - 10))
|
|
672
|
+
}
|
|
673
|
+
},
|
|
674
|
+
maintainability: {
|
|
675
|
+
index: qualityScore,
|
|
676
|
+
duplication: Math.min(20, sourceFiles * 0.1), // Approximation
|
|
677
|
+
cohesion: Math.max(0.3, 1 - (averageComplexity / 20)),
|
|
678
|
+
coupling: Math.min(0.8, averageComplexity / 20),
|
|
679
|
+
debtRatio: Math.min(0.3, (averageComplexity - 5) / 20)
|
|
680
|
+
},
|
|
681
|
+
qualityScore
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
async identifyComplexityHotspots(structure, projectPath) {
|
|
685
|
+
const hotspots = [];
|
|
686
|
+
for (const file of structure.files) {
|
|
687
|
+
if (file.type === FileType.SOURCE && (file.complexity || 0) > 10) {
|
|
688
|
+
hotspots.push({
|
|
689
|
+
file: file.path,
|
|
690
|
+
line: 1,
|
|
691
|
+
type: ComplexityType.CYCLOMATIC,
|
|
692
|
+
score: file.complexity || 0,
|
|
693
|
+
reason: `High cyclomatic complexity: ${file.complexity}`,
|
|
694
|
+
suggestion: 'Consider breaking this file into smaller, focused modules'
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
return hotspots.sort((a, b) => b.score - a.score);
|
|
699
|
+
}
|
|
700
|
+
async countFilesInDirectory(dirPath, ignorePatterns) {
|
|
701
|
+
try {
|
|
702
|
+
const items = await this.fileSystem.readdir(dirPath);
|
|
703
|
+
let count = 0;
|
|
704
|
+
for (const item of items) {
|
|
705
|
+
const itemPath = path.join(dirPath, item);
|
|
706
|
+
if (ignorePatterns.some(pattern => itemPath.includes(pattern))) {
|
|
707
|
+
continue;
|
|
708
|
+
}
|
|
709
|
+
try {
|
|
710
|
+
const stat = await this.fileSystem.stat(itemPath);
|
|
711
|
+
if (stat.isFile()) {
|
|
712
|
+
count++;
|
|
713
|
+
}
|
|
714
|
+
else if (stat.isDirectory()) {
|
|
715
|
+
count += await this.countFilesInDirectory(itemPath, ignorePatterns);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
catch (error) {
|
|
719
|
+
// Skip items that can't be accessed
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
return count;
|
|
723
|
+
}
|
|
724
|
+
catch (error) {
|
|
725
|
+
return 0;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
};
|
|
729
|
+
CodebaseAnalysisService = __decorate([
|
|
730
|
+
injectable(),
|
|
731
|
+
__param(0, inject(TYPES.FileSystemPort)),
|
|
732
|
+
__param(1, inject(TYPES.LoggerPort)),
|
|
733
|
+
__param(2, inject(TYPES.ValidationPort)),
|
|
734
|
+
__metadata("design:paramtypes", [Object, Object, Object])
|
|
735
|
+
], CodebaseAnalysisService);
|
|
736
|
+
export { CodebaseAnalysisService };
|
|
737
|
+
//# sourceMappingURL=CodebaseAnalysisService.js.map
|