archicore 0.4.2 → 0.4.4
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/dist/analyzers/ai-narrator.d.ts +107 -0
- package/dist/analyzers/ai-narrator.js +632 -0
- package/dist/analyzers/context-builder.d.ts +150 -0
- package/dist/analyzers/context-builder.js +708 -0
- package/dist/analyzers/response-enhancer.d.ts +85 -0
- package/dist/analyzers/response-enhancer.js +632 -0
- package/dist/cli/commands/interactive.js +151 -1
- package/dist/cli/ui/prompt.js +1 -0
- package/dist/cli/utils/upload-utils.js +44 -18
- package/dist/orchestrator/index.js +67 -1
- package/dist/server/routes/api.js +70 -0
- package/dist/server/services/project-service.d.ts +11 -0
- package/dist/server/services/project-service.js +97 -2
- package/dist/types/index.d.ts +44 -0
- package/package.json +1 -1
|
@@ -0,0 +1,632 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Narrator
|
|
3
|
+
*
|
|
4
|
+
* Генерирует человекопонятное описание архитектуры проекта:
|
|
5
|
+
* - Что это за проект
|
|
6
|
+
* - Какая архитектура используется
|
|
7
|
+
* - Где проблемные места
|
|
8
|
+
* - Конкретные рекомендации
|
|
9
|
+
*/
|
|
10
|
+
import { Logger } from '../utils/logger.js';
|
|
11
|
+
export class AINarrator {
|
|
12
|
+
/**
|
|
13
|
+
* Анализирует проект и генерирует нарратив
|
|
14
|
+
*/
|
|
15
|
+
async analyze(graph, symbols, fileContents, projectMetadata) {
|
|
16
|
+
Logger.progress('AI Narrator analyzing project...');
|
|
17
|
+
// 1. Анализ структуры файлов
|
|
18
|
+
const fileAnalysis = this.analyzeFiles(graph, fileContents);
|
|
19
|
+
// 2. Определение стека технологий
|
|
20
|
+
const techStack = this.detectTechStack(fileAnalysis, projectMetadata);
|
|
21
|
+
// 3. Анализ символов и их распределения (используется для статистики)
|
|
22
|
+
this.analyzeSymbols(symbols);
|
|
23
|
+
// 4. Определение архитектуры
|
|
24
|
+
const architecture = this.detectArchitecture(graph, symbols, fileAnalysis);
|
|
25
|
+
// 5. Поиск проблем
|
|
26
|
+
const problems = this.findProblems(graph, symbols, fileContents, fileAnalysis);
|
|
27
|
+
// 6. Генерация рекомендаций
|
|
28
|
+
const recommendations = this.generateRecommendations(problems, architecture);
|
|
29
|
+
// 7. Генерация нарратива
|
|
30
|
+
const narrative = this.generateNarrative(fileAnalysis, techStack, architecture, problems, recommendations, 'en');
|
|
31
|
+
const narrativeRu = this.generateNarrative(fileAnalysis, techStack, architecture, problems, recommendations, 'ru');
|
|
32
|
+
Logger.success('AI Narrator analysis complete');
|
|
33
|
+
return {
|
|
34
|
+
summary: {
|
|
35
|
+
projectType: this.detectProjectType(techStack, fileAnalysis),
|
|
36
|
+
primaryLanguage: fileAnalysis.primaryLanguage,
|
|
37
|
+
secondaryLanguages: fileAnalysis.secondaryLanguages,
|
|
38
|
+
totalFiles: fileAnalysis.totalFiles,
|
|
39
|
+
totalSymbols: symbols.size,
|
|
40
|
+
linesOfCode: fileAnalysis.totalLines,
|
|
41
|
+
},
|
|
42
|
+
techStack,
|
|
43
|
+
architecture,
|
|
44
|
+
problems,
|
|
45
|
+
recommendations,
|
|
46
|
+
narrative,
|
|
47
|
+
narrativeRu,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Анализ файловой структуры
|
|
52
|
+
*/
|
|
53
|
+
analyzeFiles(graph, fileContents) {
|
|
54
|
+
const filesByLanguage = new Map();
|
|
55
|
+
const filesByDirectory = new Map();
|
|
56
|
+
const largeFiles = [];
|
|
57
|
+
let totalLines = 0;
|
|
58
|
+
for (const [filePath, content] of fileContents) {
|
|
59
|
+
// Подсчёт строк
|
|
60
|
+
const lines = content.split('\n').length;
|
|
61
|
+
totalLines += lines;
|
|
62
|
+
// Большие файлы (> 500 строк)
|
|
63
|
+
if (lines > 500) {
|
|
64
|
+
largeFiles.push({ path: filePath, lines });
|
|
65
|
+
}
|
|
66
|
+
// Язык по расширению
|
|
67
|
+
const ext = filePath.split('.').pop()?.toLowerCase() || 'unknown';
|
|
68
|
+
const lang = this.extToLanguage(ext);
|
|
69
|
+
filesByLanguage.set(lang, (filesByLanguage.get(lang) || 0) + 1);
|
|
70
|
+
// Директория
|
|
71
|
+
const dir = filePath.split(/[/\\]/).slice(0, -1).join('/') || '/';
|
|
72
|
+
if (!filesByDirectory.has(dir)) {
|
|
73
|
+
filesByDirectory.set(dir, []);
|
|
74
|
+
}
|
|
75
|
+
filesByDirectory.get(dir).push(filePath);
|
|
76
|
+
}
|
|
77
|
+
// Определяем основной язык
|
|
78
|
+
const sortedLanguages = [...filesByLanguage.entries()]
|
|
79
|
+
.sort((a, b) => b[1] - a[1]);
|
|
80
|
+
const primaryLanguage = sortedLanguages[0]?.[0] || 'unknown';
|
|
81
|
+
const secondaryLanguages = sortedLanguages.slice(1, 4).map(([lang]) => lang);
|
|
82
|
+
return {
|
|
83
|
+
totalFiles: fileContents.size || graph.nodes.size,
|
|
84
|
+
totalLines,
|
|
85
|
+
primaryLanguage,
|
|
86
|
+
secondaryLanguages,
|
|
87
|
+
filesByLanguage,
|
|
88
|
+
filesByDirectory,
|
|
89
|
+
largeFiles: largeFiles.sort((a, b) => b.lines - a.lines).slice(0, 10),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Определение стека технологий
|
|
94
|
+
*/
|
|
95
|
+
detectTechStack(fileAnalysis, projectMetadata) {
|
|
96
|
+
const frontend = [];
|
|
97
|
+
const backend = [];
|
|
98
|
+
const database = [];
|
|
99
|
+
const infrastructure = [];
|
|
100
|
+
// Из метаданных проекта
|
|
101
|
+
if (projectMetadata?.framework)
|
|
102
|
+
frontend.push(projectMetadata.framework);
|
|
103
|
+
if (projectMetadata?.backend)
|
|
104
|
+
backend.push(projectMetadata.backend);
|
|
105
|
+
if (projectMetadata?.database)
|
|
106
|
+
database.push(projectMetadata.database);
|
|
107
|
+
// Из зависимостей
|
|
108
|
+
const deps = projectMetadata?.dependencies || {};
|
|
109
|
+
const depNames = Object.keys(deps);
|
|
110
|
+
// Frontend frameworks
|
|
111
|
+
if (depNames.some(d => d.includes('vue')))
|
|
112
|
+
frontend.push('Vue.js');
|
|
113
|
+
if (depNames.some(d => d.includes('react')))
|
|
114
|
+
frontend.push('React');
|
|
115
|
+
if (depNames.some(d => d.includes('angular')))
|
|
116
|
+
frontend.push('Angular');
|
|
117
|
+
if (depNames.some(d => d.includes('svelte')))
|
|
118
|
+
frontend.push('Svelte');
|
|
119
|
+
if (depNames.some(d => d === 'next'))
|
|
120
|
+
frontend.push('Next.js');
|
|
121
|
+
if (depNames.some(d => d === 'nuxt'))
|
|
122
|
+
frontend.push('Nuxt.js');
|
|
123
|
+
// Backend frameworks
|
|
124
|
+
if (depNames.some(d => d.includes('express')))
|
|
125
|
+
backend.push('Express.js');
|
|
126
|
+
if (depNames.some(d => d.includes('fastify')))
|
|
127
|
+
backend.push('Fastify');
|
|
128
|
+
if (depNames.some(d => d.includes('nest')))
|
|
129
|
+
backend.push('NestJS');
|
|
130
|
+
if (depNames.some(d => d.includes('koa')))
|
|
131
|
+
backend.push('Koa');
|
|
132
|
+
// Из структуры файлов
|
|
133
|
+
const dirs = [...fileAnalysis.filesByDirectory.keys()].join(' ');
|
|
134
|
+
// PHP frameworks
|
|
135
|
+
if (dirs.includes('laravel') || dirs.includes('app/Http'))
|
|
136
|
+
backend.push('Laravel');
|
|
137
|
+
if (dirs.includes('symfony'))
|
|
138
|
+
backend.push('Symfony');
|
|
139
|
+
if (dirs.includes('bitrix') || dirs.includes('local/modules'))
|
|
140
|
+
backend.push('Bitrix');
|
|
141
|
+
if (dirs.includes('yii'))
|
|
142
|
+
backend.push('Yii');
|
|
143
|
+
if (dirs.includes('wordpress') || dirs.includes('wp-content'))
|
|
144
|
+
backend.push('WordPress');
|
|
145
|
+
// Database
|
|
146
|
+
if (depNames.some(d => d.includes('mysql') || d.includes('mysql2')))
|
|
147
|
+
database.push('MySQL');
|
|
148
|
+
if (depNames.some(d => d.includes('postgres') || d.includes('pg')))
|
|
149
|
+
database.push('PostgreSQL');
|
|
150
|
+
if (depNames.some(d => d.includes('mongodb') || d.includes('mongoose')))
|
|
151
|
+
database.push('MongoDB');
|
|
152
|
+
if (depNames.some(d => d.includes('redis')))
|
|
153
|
+
database.push('Redis');
|
|
154
|
+
if (depNames.some(d => d.includes('sqlite')))
|
|
155
|
+
database.push('SQLite');
|
|
156
|
+
// Infrastructure
|
|
157
|
+
if (depNames.some(d => d.includes('docker')))
|
|
158
|
+
infrastructure.push('Docker');
|
|
159
|
+
if (dirs.includes('kubernetes') || dirs.includes('k8s'))
|
|
160
|
+
infrastructure.push('Kubernetes');
|
|
161
|
+
if (depNames.some(d => d.includes('aws-sdk')))
|
|
162
|
+
infrastructure.push('AWS');
|
|
163
|
+
// Дедупликация
|
|
164
|
+
return {
|
|
165
|
+
frontend: [...new Set(frontend)],
|
|
166
|
+
backend: [...new Set(backend)],
|
|
167
|
+
database: [...new Set(database)],
|
|
168
|
+
infrastructure: [...new Set(infrastructure)],
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Анализ символов
|
|
173
|
+
*/
|
|
174
|
+
analyzeSymbols(symbols) {
|
|
175
|
+
const byKind = new Map();
|
|
176
|
+
const byFile = new Map();
|
|
177
|
+
const complexSymbols = [];
|
|
178
|
+
for (const symbol of symbols.values()) {
|
|
179
|
+
// По типу
|
|
180
|
+
byKind.set(symbol.kind, (byKind.get(symbol.kind) || 0) + 1);
|
|
181
|
+
// По файлу
|
|
182
|
+
byFile.set(symbol.filePath, (byFile.get(symbol.filePath) || 0) + 1);
|
|
183
|
+
// Сложные символы (много ссылок)
|
|
184
|
+
if (symbol.references.length > 20) {
|
|
185
|
+
complexSymbols.push(symbol);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return { byKind, byFile, complexSymbols };
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Определение архитектуры
|
|
192
|
+
*/
|
|
193
|
+
detectArchitecture(_graph, _symbols, fileAnalysis) {
|
|
194
|
+
const patterns = [];
|
|
195
|
+
const layers = [];
|
|
196
|
+
let detectedArch = 'Unknown';
|
|
197
|
+
let confidence = 0;
|
|
198
|
+
const dirs = [...fileAnalysis.filesByDirectory.keys()];
|
|
199
|
+
const allFiles = [...fileAnalysis.filesByDirectory.values()].flat();
|
|
200
|
+
// MVC Detection
|
|
201
|
+
const hasControllers = dirs.some(d => d.includes('controller') || d.includes('Controller'));
|
|
202
|
+
const hasModels = dirs.some(d => d.includes('model') || d.includes('Model') || d.includes('entities'));
|
|
203
|
+
const hasViews = dirs.some(d => d.includes('view') || d.includes('View') || d.includes('templates'));
|
|
204
|
+
if (hasControllers && hasModels) {
|
|
205
|
+
patterns.push({
|
|
206
|
+
name: 'MVC',
|
|
207
|
+
confidence: hasViews ? 90 : 70,
|
|
208
|
+
evidence: [
|
|
209
|
+
hasControllers ? 'Controllers directory found' : '',
|
|
210
|
+
hasModels ? 'Models directory found' : '',
|
|
211
|
+
hasViews ? 'Views directory found' : '',
|
|
212
|
+
].filter(Boolean),
|
|
213
|
+
files: dirs.filter(d => d.includes('controller') || d.includes('model') || d.includes('view')),
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
// Clean Architecture Detection
|
|
217
|
+
const hasDomain = dirs.some(d => d.includes('domain') || d.includes('entities'));
|
|
218
|
+
const hasUseCases = dirs.some(d => d.includes('usecase') || d.includes('use-case') || d.includes('application'));
|
|
219
|
+
const hasInfra = dirs.some(d => d.includes('infrastructure') || d.includes('infra'));
|
|
220
|
+
if (hasDomain && (hasUseCases || hasInfra)) {
|
|
221
|
+
patterns.push({
|
|
222
|
+
name: 'Clean Architecture',
|
|
223
|
+
confidence: hasUseCases && hasInfra ? 85 : 60,
|
|
224
|
+
evidence: [
|
|
225
|
+
hasDomain ? 'Domain layer found' : '',
|
|
226
|
+
hasUseCases ? 'Use Cases layer found' : '',
|
|
227
|
+
hasInfra ? 'Infrastructure layer found' : '',
|
|
228
|
+
].filter(Boolean),
|
|
229
|
+
files: dirs.filter(d => d.includes('domain') || d.includes('usecase') || d.includes('infrastructure')),
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
// Repository Pattern
|
|
233
|
+
const hasRepositories = dirs.some(d => d.includes('repositor'));
|
|
234
|
+
if (hasRepositories) {
|
|
235
|
+
patterns.push({
|
|
236
|
+
name: 'Repository Pattern',
|
|
237
|
+
confidence: 80,
|
|
238
|
+
evidence: ['Repository directory/files found'],
|
|
239
|
+
files: dirs.filter(d => d.includes('repositor')),
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
// Service Layer
|
|
243
|
+
const hasServices = dirs.some(d => d.includes('service') || d.includes('Service'));
|
|
244
|
+
if (hasServices) {
|
|
245
|
+
patterns.push({
|
|
246
|
+
name: 'Service Layer',
|
|
247
|
+
confidence: 85,
|
|
248
|
+
evidence: ['Services directory found'],
|
|
249
|
+
files: dirs.filter(d => d.includes('service')),
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
// Определяем основную архитектуру
|
|
253
|
+
if (patterns.length > 0) {
|
|
254
|
+
const bestPattern = patterns.sort((a, b) => b.confidence - a.confidence)[0];
|
|
255
|
+
detectedArch = bestPattern.name;
|
|
256
|
+
confidence = bestPattern.confidence;
|
|
257
|
+
}
|
|
258
|
+
// Формируем слои
|
|
259
|
+
const layerMappings = [
|
|
260
|
+
{ name: 'Presentation', patterns: ['controller', 'view', 'component', 'page'], role: 'UI and API endpoints' },
|
|
261
|
+
{ name: 'Application', patterns: ['service', 'usecase', 'handler'], role: 'Business logic orchestration' },
|
|
262
|
+
{ name: 'Domain', patterns: ['model', 'entity', 'domain', 'core'], role: 'Business rules and entities' },
|
|
263
|
+
{ name: 'Infrastructure', patterns: ['repository', 'database', 'api', 'external'], role: 'External integrations' },
|
|
264
|
+
];
|
|
265
|
+
for (const mapping of layerMappings) {
|
|
266
|
+
const layerFiles = allFiles.filter(f => mapping.patterns.some(p => f.toLowerCase().includes(p)));
|
|
267
|
+
if (layerFiles.length > 0) {
|
|
268
|
+
layers.push({
|
|
269
|
+
name: mapping.name,
|
|
270
|
+
files: layerFiles,
|
|
271
|
+
coverage: Math.round((layerFiles.length / allFiles.length) * 100),
|
|
272
|
+
role: mapping.role,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
detected: detectedArch,
|
|
278
|
+
confidence,
|
|
279
|
+
layers,
|
|
280
|
+
patterns,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Поиск проблем
|
|
285
|
+
*/
|
|
286
|
+
findProblems(graph, symbols, _fileContents, fileAnalysis) {
|
|
287
|
+
const problems = [];
|
|
288
|
+
// 1. Большие файлы (God Objects)
|
|
289
|
+
for (const { path, lines } of fileAnalysis.largeFiles) {
|
|
290
|
+
if (lines > 1000) {
|
|
291
|
+
problems.push({
|
|
292
|
+
severity: 'critical',
|
|
293
|
+
title: 'God Object',
|
|
294
|
+
description: `File ${this.shortPath(path)} has ${lines} lines - likely too many responsibilities`,
|
|
295
|
+
files: [path],
|
|
296
|
+
suggestion: 'Split into smaller, focused modules with single responsibility',
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
else if (lines > 500) {
|
|
300
|
+
problems.push({
|
|
301
|
+
severity: 'warning',
|
|
302
|
+
title: 'Large File',
|
|
303
|
+
description: `File ${this.shortPath(path)} has ${lines} lines`,
|
|
304
|
+
files: [path],
|
|
305
|
+
suggestion: 'Consider breaking into smaller modules',
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
// 2. Файлы с большим количеством символов (высокая сложность)
|
|
310
|
+
const symbolsPerFile = new Map();
|
|
311
|
+
for (const symbol of symbols.values()) {
|
|
312
|
+
symbolsPerFile.set(symbol.filePath, (symbolsPerFile.get(symbol.filePath) || 0) + 1);
|
|
313
|
+
}
|
|
314
|
+
for (const [file, count] of symbolsPerFile) {
|
|
315
|
+
if (count > 50) {
|
|
316
|
+
problems.push({
|
|
317
|
+
severity: 'warning',
|
|
318
|
+
title: 'High Complexity',
|
|
319
|
+
description: `File ${this.shortPath(file)} has ${count} symbols - may be too complex`,
|
|
320
|
+
files: [file],
|
|
321
|
+
suggestion: 'Extract related functions into separate modules',
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
// 3. Circular dependencies (если есть в графе)
|
|
326
|
+
const visited = new Set();
|
|
327
|
+
const recursionStack = new Set();
|
|
328
|
+
const cycles = [];
|
|
329
|
+
const detectCycle = (nodeId, path = []) => {
|
|
330
|
+
if (recursionStack.has(nodeId)) {
|
|
331
|
+
const cycleStart = path.indexOf(nodeId);
|
|
332
|
+
if (cycleStart !== -1) {
|
|
333
|
+
cycles.push(path.slice(cycleStart));
|
|
334
|
+
}
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
if (visited.has(nodeId))
|
|
338
|
+
return;
|
|
339
|
+
visited.add(nodeId);
|
|
340
|
+
recursionStack.add(nodeId);
|
|
341
|
+
const edges = graph.edges.get(nodeId) || [];
|
|
342
|
+
for (const edge of edges) {
|
|
343
|
+
detectCycle(edge.to, [...path, nodeId]);
|
|
344
|
+
}
|
|
345
|
+
recursionStack.delete(nodeId);
|
|
346
|
+
};
|
|
347
|
+
for (const nodeId of graph.nodes.keys()) {
|
|
348
|
+
detectCycle(nodeId);
|
|
349
|
+
}
|
|
350
|
+
if (cycles.length > 0) {
|
|
351
|
+
problems.push({
|
|
352
|
+
severity: 'critical',
|
|
353
|
+
title: 'Circular Dependencies',
|
|
354
|
+
description: `Found ${cycles.length} circular dependency chains`,
|
|
355
|
+
files: cycles.flat().slice(0, 10),
|
|
356
|
+
suggestion: 'Break cycles by introducing interfaces or restructuring modules',
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
// 4. Файлы без очевидной структуры
|
|
360
|
+
const unstructuredDirs = [...fileAnalysis.filesByDirectory.entries()]
|
|
361
|
+
.filter(([dir, files]) => {
|
|
362
|
+
// Много файлов в одной директории без подпапок
|
|
363
|
+
return files.length > 20 && !dir.includes('/');
|
|
364
|
+
});
|
|
365
|
+
if (unstructuredDirs.length > 0) {
|
|
366
|
+
problems.push({
|
|
367
|
+
severity: 'info',
|
|
368
|
+
title: 'Flat Structure',
|
|
369
|
+
description: 'Many files in root directories without clear organization',
|
|
370
|
+
files: unstructuredDirs.flatMap(([, files]) => files.slice(0, 5)),
|
|
371
|
+
suggestion: 'Organize files into feature-based or layer-based directories',
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
return problems.sort((a, b) => {
|
|
375
|
+
const severityOrder = { critical: 0, warning: 1, info: 2 };
|
|
376
|
+
return severityOrder[a.severity] - severityOrder[b.severity];
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Генерация рекомендаций
|
|
381
|
+
*/
|
|
382
|
+
generateRecommendations(problems, architecture) {
|
|
383
|
+
const recommendations = [];
|
|
384
|
+
// Из проблем
|
|
385
|
+
const criticalProblems = problems.filter(p => p.severity === 'critical');
|
|
386
|
+
if (criticalProblems.length > 0) {
|
|
387
|
+
recommendations.push(`Address ${criticalProblems.length} critical issues first: ${criticalProblems.map(p => p.title).join(', ')}`);
|
|
388
|
+
}
|
|
389
|
+
// Архитектурные
|
|
390
|
+
if (architecture.confidence < 50) {
|
|
391
|
+
recommendations.push('Define clear architectural boundaries - current structure is unclear');
|
|
392
|
+
}
|
|
393
|
+
if (!architecture.patterns.some(p => p.name === 'Repository Pattern')) {
|
|
394
|
+
recommendations.push('Consider implementing Repository Pattern to separate data access logic');
|
|
395
|
+
}
|
|
396
|
+
if (architecture.layers.length < 3) {
|
|
397
|
+
recommendations.push('Structure code into clear layers (Presentation, Business, Data) for better maintainability');
|
|
398
|
+
}
|
|
399
|
+
// Общие best practices
|
|
400
|
+
if (problems.some(p => p.title === 'God Object')) {
|
|
401
|
+
recommendations.push('Apply Single Responsibility Principle - each module should do one thing well');
|
|
402
|
+
}
|
|
403
|
+
return recommendations;
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Генерация человекопонятного текста
|
|
407
|
+
*/
|
|
408
|
+
generateNarrative(fileAnalysis, techStack, architecture, problems, recommendations, language) {
|
|
409
|
+
if (language === 'ru') {
|
|
410
|
+
return this.generateNarrativeRu(fileAnalysis, techStack, architecture, problems, recommendations);
|
|
411
|
+
}
|
|
412
|
+
const lines = [];
|
|
413
|
+
// Header
|
|
414
|
+
lines.push('# Project Architecture Analysis\n');
|
|
415
|
+
// Summary
|
|
416
|
+
const projectType = this.detectProjectType(techStack, fileAnalysis);
|
|
417
|
+
lines.push(`## Overview\n`);
|
|
418
|
+
lines.push(`This is a **${projectType}** with ${fileAnalysis.totalFiles} files and approximately ${fileAnalysis.totalLines.toLocaleString()} lines of code.\n`);
|
|
419
|
+
// Tech Stack
|
|
420
|
+
lines.push(`## Technology Stack\n`);
|
|
421
|
+
if (techStack.frontend.length > 0) {
|
|
422
|
+
lines.push(`- **Frontend:** ${techStack.frontend.join(', ')}`);
|
|
423
|
+
}
|
|
424
|
+
if (techStack.backend.length > 0) {
|
|
425
|
+
lines.push(`- **Backend:** ${techStack.backend.join(', ')}`);
|
|
426
|
+
}
|
|
427
|
+
if (techStack.database.length > 0) {
|
|
428
|
+
lines.push(`- **Database:** ${techStack.database.join(', ')}`);
|
|
429
|
+
}
|
|
430
|
+
lines.push('');
|
|
431
|
+
// Architecture
|
|
432
|
+
lines.push(`## Architecture\n`);
|
|
433
|
+
lines.push(`**Detected Pattern:** ${architecture.detected} (${architecture.confidence}% confidence)\n`);
|
|
434
|
+
if (architecture.layers.length > 0) {
|
|
435
|
+
lines.push('**Layers:**');
|
|
436
|
+
for (const layer of architecture.layers) {
|
|
437
|
+
lines.push(`- ${layer.name}: ${layer.files.length} files (${layer.coverage}%) - ${layer.role}`);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
lines.push('');
|
|
441
|
+
// Problems
|
|
442
|
+
if (problems.length > 0) {
|
|
443
|
+
lines.push(`## Issues Found\n`);
|
|
444
|
+
const criticalCount = problems.filter(p => p.severity === 'critical').length;
|
|
445
|
+
const warningCount = problems.filter(p => p.severity === 'warning').length;
|
|
446
|
+
lines.push(`Found **${criticalCount} critical** and **${warningCount} warning** issues:\n`);
|
|
447
|
+
for (const problem of problems.slice(0, 5)) {
|
|
448
|
+
const icon = problem.severity === 'critical' ? '🔴' : problem.severity === 'warning' ? '🟡' : '🔵';
|
|
449
|
+
lines.push(`${icon} **${problem.title}**: ${problem.description}`);
|
|
450
|
+
lines.push(` → ${problem.suggestion}\n`);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
// Recommendations
|
|
454
|
+
if (recommendations.length > 0) {
|
|
455
|
+
lines.push(`## Recommendations\n`);
|
|
456
|
+
for (let i = 0; i < Math.min(recommendations.length, 5); i++) {
|
|
457
|
+
lines.push(`${i + 1}. ${recommendations[i]}`);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
return lines.join('\n');
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Генерация нарратива на русском
|
|
464
|
+
*/
|
|
465
|
+
generateNarrativeRu(fileAnalysis, techStack, architecture, problems, recommendations) {
|
|
466
|
+
const lines = [];
|
|
467
|
+
// Заголовок
|
|
468
|
+
lines.push('# Анализ архитектуры проекта\n');
|
|
469
|
+
// Обзор
|
|
470
|
+
const projectType = this.detectProjectType(techStack, fileAnalysis);
|
|
471
|
+
lines.push(`## Обзор\n`);
|
|
472
|
+
lines.push(`Это **${this.translateProjectType(projectType)}** с ${fileAnalysis.totalFiles} файлами и примерно ${fileAnalysis.totalLines.toLocaleString()} строками кода.\n`);
|
|
473
|
+
lines.push(`Основной язык: **${fileAnalysis.primaryLanguage}**`);
|
|
474
|
+
if (fileAnalysis.secondaryLanguages.length > 0) {
|
|
475
|
+
lines.push(`Дополнительные: ${fileAnalysis.secondaryLanguages.join(', ')}`);
|
|
476
|
+
}
|
|
477
|
+
lines.push('');
|
|
478
|
+
// Стек технологий
|
|
479
|
+
lines.push(`## Стек технологий\n`);
|
|
480
|
+
if (techStack.frontend.length > 0) {
|
|
481
|
+
lines.push(`- **Frontend:** ${techStack.frontend.join(', ')}`);
|
|
482
|
+
}
|
|
483
|
+
if (techStack.backend.length > 0) {
|
|
484
|
+
lines.push(`- **Backend:** ${techStack.backend.join(', ')}`);
|
|
485
|
+
}
|
|
486
|
+
if (techStack.database.length > 0) {
|
|
487
|
+
lines.push(`- **База данных:** ${techStack.database.join(', ')}`);
|
|
488
|
+
}
|
|
489
|
+
if (techStack.infrastructure.length > 0) {
|
|
490
|
+
lines.push(`- **Инфраструктура:** ${techStack.infrastructure.join(', ')}`);
|
|
491
|
+
}
|
|
492
|
+
lines.push('');
|
|
493
|
+
// Архитектура
|
|
494
|
+
lines.push(`## Архитектура\n`);
|
|
495
|
+
lines.push(`**Обнаружен паттерн:** ${architecture.detected} (уверенность ${architecture.confidence}%)\n`);
|
|
496
|
+
if (architecture.layers.length > 0) {
|
|
497
|
+
lines.push('**Слои:**');
|
|
498
|
+
for (const layer of architecture.layers) {
|
|
499
|
+
lines.push(`- ${this.translateLayer(layer.name)}: ${layer.files.length} файлов (${layer.coverage}%)`);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
if (architecture.patterns.length > 0) {
|
|
503
|
+
lines.push('\n**Используемые паттерны:**');
|
|
504
|
+
for (const pattern of architecture.patterns) {
|
|
505
|
+
lines.push(`- ${pattern.name} (${pattern.confidence}%)`);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
lines.push('');
|
|
509
|
+
// Проблемы
|
|
510
|
+
if (problems.length > 0) {
|
|
511
|
+
lines.push(`## Проблемные места\n`);
|
|
512
|
+
const criticalCount = problems.filter(p => p.severity === 'critical').length;
|
|
513
|
+
const warningCount = problems.filter(p => p.severity === 'warning').length;
|
|
514
|
+
lines.push(`Найдено **${criticalCount} критических** и **${warningCount} предупреждений**:\n`);
|
|
515
|
+
for (const problem of problems.slice(0, 7)) {
|
|
516
|
+
const icon = problem.severity === 'critical' ? '🔴' : problem.severity === 'warning' ? '🟡' : '🔵';
|
|
517
|
+
lines.push(`${icon} **${this.translateProblem(problem.title)}**`);
|
|
518
|
+
lines.push(` ${problem.description}`);
|
|
519
|
+
lines.push(` 💡 ${this.translateSuggestion(problem.suggestion)}\n`);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
// Рекомендации
|
|
523
|
+
if (recommendations.length > 0) {
|
|
524
|
+
lines.push(`## Рекомендации\n`);
|
|
525
|
+
for (let i = 0; i < Math.min(recommendations.length, 5); i++) {
|
|
526
|
+
lines.push(`${i + 1}. ${this.translateRecommendation(recommendations[i])}`);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
return lines.join('\n');
|
|
530
|
+
}
|
|
531
|
+
// Вспомогательные методы
|
|
532
|
+
extToLanguage(ext) {
|
|
533
|
+
const map = {
|
|
534
|
+
'ts': 'TypeScript', 'tsx': 'TypeScript',
|
|
535
|
+
'js': 'JavaScript', 'jsx': 'JavaScript', 'mjs': 'JavaScript',
|
|
536
|
+
'vue': 'Vue', 'svelte': 'Svelte',
|
|
537
|
+
'php': 'PHP',
|
|
538
|
+
'py': 'Python',
|
|
539
|
+
'java': 'Java', 'kt': 'Kotlin',
|
|
540
|
+
'cs': 'C#',
|
|
541
|
+
'go': 'Go',
|
|
542
|
+
'rs': 'Rust',
|
|
543
|
+
'rb': 'Ruby',
|
|
544
|
+
'swift': 'Swift',
|
|
545
|
+
'css': 'CSS', 'scss': 'SCSS', 'less': 'LESS',
|
|
546
|
+
'html': 'HTML',
|
|
547
|
+
'sql': 'SQL',
|
|
548
|
+
'json': 'JSON', 'yaml': 'YAML', 'yml': 'YAML',
|
|
549
|
+
};
|
|
550
|
+
return map[ext] || ext.toUpperCase();
|
|
551
|
+
}
|
|
552
|
+
shortPath(path) {
|
|
553
|
+
const parts = path.split(/[/\\]/);
|
|
554
|
+
return parts.length > 3
|
|
555
|
+
? `.../${parts.slice(-3).join('/')}`
|
|
556
|
+
: path;
|
|
557
|
+
}
|
|
558
|
+
detectProjectType(techStack, fileAnalysis) {
|
|
559
|
+
const { frontend, backend } = techStack;
|
|
560
|
+
const { primaryLanguage } = fileAnalysis;
|
|
561
|
+
if (frontend.length > 0 && backend.length > 0) {
|
|
562
|
+
return `Full-stack ${frontend[0]} + ${backend[0]} application`;
|
|
563
|
+
}
|
|
564
|
+
if (frontend.length > 0) {
|
|
565
|
+
return `${frontend[0]} frontend application`;
|
|
566
|
+
}
|
|
567
|
+
if (backend.length > 0) {
|
|
568
|
+
return `${backend[0]} backend application`;
|
|
569
|
+
}
|
|
570
|
+
if (primaryLanguage === 'PHP') {
|
|
571
|
+
return 'PHP web application';
|
|
572
|
+
}
|
|
573
|
+
if (primaryLanguage === 'Python') {
|
|
574
|
+
return 'Python application';
|
|
575
|
+
}
|
|
576
|
+
return `${primaryLanguage} project`;
|
|
577
|
+
}
|
|
578
|
+
translateProjectType(type) {
|
|
579
|
+
return type
|
|
580
|
+
.replace('Full-stack', 'Полнoстековое')
|
|
581
|
+
.replace('frontend application', 'фронтенд приложение')
|
|
582
|
+
.replace('backend application', 'бэкенд приложение')
|
|
583
|
+
.replace('web application', 'веб-приложение')
|
|
584
|
+
.replace('application', 'приложение')
|
|
585
|
+
.replace('project', 'проект');
|
|
586
|
+
}
|
|
587
|
+
translateLayer(layer) {
|
|
588
|
+
const map = {
|
|
589
|
+
'Presentation': 'Представление',
|
|
590
|
+
'Application': 'Приложение',
|
|
591
|
+
'Domain': 'Домен',
|
|
592
|
+
'Infrastructure': 'Инфраструктура',
|
|
593
|
+
};
|
|
594
|
+
return map[layer] || layer;
|
|
595
|
+
}
|
|
596
|
+
translateProblem(title) {
|
|
597
|
+
const map = {
|
|
598
|
+
'God Object': 'God Object (слишком большой файл)',
|
|
599
|
+
'Large File': 'Большой файл',
|
|
600
|
+
'High Complexity': 'Высокая сложность',
|
|
601
|
+
'Circular Dependencies': 'Циклические зависимости',
|
|
602
|
+
'Flat Structure': 'Плоская структура',
|
|
603
|
+
};
|
|
604
|
+
return map[title] || title;
|
|
605
|
+
}
|
|
606
|
+
translateSuggestion(suggestion) {
|
|
607
|
+
return suggestion
|
|
608
|
+
.replace('Split into smaller', 'Разбейте на меньшие')
|
|
609
|
+
.replace('Consider breaking', 'Рассмотрите разбиение')
|
|
610
|
+
.replace('Extract related functions', 'Вынесите связанные функции')
|
|
611
|
+
.replace('Break cycles by', 'Разорвите циклы через')
|
|
612
|
+
.replace('Organize files into', 'Организуйте файлы в')
|
|
613
|
+
.replace('focused modules', 'модули с одной ответственностью')
|
|
614
|
+
.replace('feature-based', 'фича-ориентированные')
|
|
615
|
+
.replace('layer-based', 'слой-ориентированные')
|
|
616
|
+
.replace('directories', 'директории');
|
|
617
|
+
}
|
|
618
|
+
translateRecommendation(rec) {
|
|
619
|
+
return rec
|
|
620
|
+
.replace('Address', 'Решите')
|
|
621
|
+
.replace('critical issues first', 'критические проблемы в первую очередь')
|
|
622
|
+
.replace('Define clear architectural boundaries', 'Определите чёткие архитектурные границы')
|
|
623
|
+
.replace('current structure is unclear', 'текущая структура неясна')
|
|
624
|
+
.replace('Consider implementing', 'Рассмотрите внедрение')
|
|
625
|
+
.replace('to separate data access logic', 'для разделения логики доступа к данным')
|
|
626
|
+
.replace('Structure code into clear layers', 'Структурируйте код в чёткие слои')
|
|
627
|
+
.replace('for better maintainability', 'для лучшей поддерживаемости')
|
|
628
|
+
.replace('Apply Single Responsibility Principle', 'Применяйте принцип единой ответственности')
|
|
629
|
+
.replace('each module should do one thing well', 'каждый модуль должен делать одно дело хорошо');
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
//# sourceMappingURL=ai-narrator.js.map
|