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.
@@ -0,0 +1,632 @@
1
+ /**
2
+ * ArchiCore Response Enhancer
3
+ *
4
+ * Улучшает ответы ИИ дополнительными инструментами:
5
+ * 1. Follow-up Suggestions - предложения следующих вопросов
6
+ * 2. Visual Diagrams - ASCII/Mermaid диаграммы
7
+ * 3. Test Generator - генерация тестов
8
+ * 4. Performance Hints - подсказки по оптимизации
9
+ */
10
+ // ==================== FOLLOW-UP SUGGESTIONS ====================
11
+ const FOLLOW_UP_TEMPLATES = {
12
+ find_symbol: [
13
+ { question: 'Где используется {symbol}?', intent: 'find_usage', relevance: 0.9, icon: '🔍' },
14
+ { question: 'Покажи тесты для {symbol}', intent: 'find_tests', relevance: 0.8, icon: '🧪' },
15
+ { question: 'Есть ли проблемы в {file}?', intent: 'find_bugs', relevance: 0.7, icon: '🐛' },
16
+ { question: 'Как работает {symbol}?', intent: 'explain_flow', relevance: 0.6, icon: '📖' },
17
+ ],
18
+ find_usage: [
19
+ { question: 'Покажи граф зависимостей для {symbol}', intent: 'show_dependencies', relevance: 0.9, icon: '📊' },
20
+ { question: 'Кто ещё вызывает {symbol}?', intent: 'find_callers', relevance: 0.8, icon: '📞' },
21
+ { question: 'Можно ли оптимизировать {symbol}?', intent: 'optimize', relevance: 0.7, icon: '⚡' },
22
+ ],
23
+ find_bugs: [
24
+ { question: 'Как исправить эту проблему?', intent: 'fix_bug', relevance: 0.9, icon: '🔧' },
25
+ { question: 'Покажи похожие проблемы в проекте', intent: 'find_similar_bugs', relevance: 0.8, icon: '🔍' },
26
+ { question: 'Сгенерируй тест для этого кейса', intent: 'generate_test', relevance: 0.7, icon: '🧪' },
27
+ ],
28
+ explain_flow: [
29
+ { question: 'Покажи диаграмму этого flow', intent: 'show_diagram', relevance: 0.9, icon: '📊' },
30
+ { question: 'Какие есть узкие места?', intent: 'find_bottlenecks', relevance: 0.8, icon: '⚡' },
31
+ { question: 'Как это можно улучшить?', intent: 'refactor', relevance: 0.7, icon: '✨' },
32
+ ],
33
+ architecture: [
34
+ { question: 'Покажи диаграмму архитектуры', intent: 'show_diagram', relevance: 0.9, icon: '📊' },
35
+ { question: 'Какие есть проблемы в архитектуре?', intent: 'architecture_issues', relevance: 0.8, icon: '⚠️' },
36
+ { question: 'Как масштабировать проект?', intent: 'scaling', relevance: 0.7, icon: '📈' },
37
+ ],
38
+ general: [
39
+ { question: 'Покажи структуру проекта', intent: 'architecture', relevance: 0.8, icon: '📁' },
40
+ { question: 'Найди проблемы в коде', intent: 'find_bugs', relevance: 0.7, icon: '🐛' },
41
+ { question: 'Какие метрики у проекта?', intent: 'metrics', relevance: 0.6, icon: '📊' },
42
+ ],
43
+ };
44
+ export function generateFollowUpSuggestions(context, _answer) {
45
+ const suggestions = [];
46
+ const templates = FOLLOW_UP_TEMPLATES[context.intent] || FOLLOW_UP_TEMPLATES.general;
47
+ // Извлекаем упомянутые символы и файлы для персонализации
48
+ const mentionedSymbol = context.symbols[0]?.name || '';
49
+ const mentionedFile = context.symbols[0]?.file || context.relevantFiles[0]?.path || '';
50
+ for (const template of templates) {
51
+ let question = template.question;
52
+ // Заменяем плейсхолдеры
53
+ question = question.replace('{symbol}', mentionedSymbol || 'этот код');
54
+ question = question.replace('{file}', mentionedFile ? mentionedFile.split('/').pop() || '' : 'этот файл');
55
+ // Не добавляем если нет контекста для плейсхолдера
56
+ if (question.includes('{'))
57
+ continue;
58
+ suggestions.push({
59
+ ...template,
60
+ question,
61
+ });
62
+ }
63
+ // Добавляем контекстные предложения на основе найденных проблем
64
+ if (context.issues.length > 0) {
65
+ suggestions.push({
66
+ question: `Как исправить ${context.issues.length} найденных проблем?`,
67
+ intent: 'fix_bugs',
68
+ relevance: 0.95,
69
+ icon: '🔧',
70
+ });
71
+ }
72
+ // Сортируем по релевантности
73
+ suggestions.sort((a, b) => b.relevance - a.relevance);
74
+ return suggestions.slice(0, 4);
75
+ }
76
+ // ==================== VISUAL DIAGRAMS ====================
77
+ export function generateDiagram(context, graph, diagramType = 'dependencies') {
78
+ switch (diagramType) {
79
+ case 'dependencies':
80
+ return generateDependencyDiagram(context, graph);
81
+ case 'flow':
82
+ return generateFlowDiagram(context);
83
+ case 'architecture':
84
+ return generateArchitectureDiagram(graph);
85
+ default:
86
+ return null;
87
+ }
88
+ }
89
+ function generateDependencyDiagram(context, _graph) {
90
+ if (context.dependencies.length === 0)
91
+ return null;
92
+ const lines = ['```'];
93
+ lines.push('┌─────────────────────────────────────────┐');
94
+ lines.push('│ Граф зависимостей │');
95
+ lines.push('└─────────────────────────────────────────┘');
96
+ lines.push('');
97
+ // Группируем по источнику
98
+ const grouped = new Map();
99
+ for (const dep of context.dependencies) {
100
+ const from = dep.from.split('/').pop() || dep.from;
101
+ const to = dep.to.split('/').pop() || dep.to;
102
+ if (!grouped.has(from)) {
103
+ grouped.set(from, []);
104
+ }
105
+ grouped.get(from).push(to);
106
+ }
107
+ // Рисуем ASCII диаграмму
108
+ let i = 0;
109
+ for (const [from, targets] of grouped) {
110
+ if (i > 0)
111
+ lines.push('│');
112
+ const boxWidth = Math.max(from.length + 4, 20);
113
+ const topBorder = '┌' + '─'.repeat(boxWidth) + '┐';
114
+ const bottomBorder = '└' + '─'.repeat(boxWidth) + '┘';
115
+ const content = '│ ' + from.padEnd(boxWidth - 2) + ' │';
116
+ lines.push(topBorder);
117
+ lines.push(content);
118
+ lines.push(bottomBorder);
119
+ for (let j = 0; j < targets.length; j++) {
120
+ const target = targets[j];
121
+ const isLast = j === targets.length - 1;
122
+ const connector = isLast ? '└──▶ ' : '├──▶ ';
123
+ lines.push(connector + target);
124
+ }
125
+ i++;
126
+ if (i >= 5) {
127
+ lines.push(`... и ещё ${grouped.size - 5} модулей`);
128
+ break;
129
+ }
130
+ }
131
+ lines.push('```');
132
+ return {
133
+ type: 'ascii',
134
+ title: 'Граф зависимостей',
135
+ content: lines.join('\n'),
136
+ };
137
+ }
138
+ function generateFlowDiagram(context) {
139
+ if (context.symbols.length === 0)
140
+ return null;
141
+ const symbol = context.symbols[0];
142
+ const lines = ['```mermaid', 'flowchart TD'];
143
+ // Добавляем главный символ
144
+ const mainId = 'A';
145
+ lines.push(` ${mainId}[${symbol.name}]`);
146
+ // Добавляем вызываемые функции
147
+ const calls = symbol.calls.slice(0, 5);
148
+ calls.forEach((call, i) => {
149
+ const callId = `B${i}`;
150
+ const callName = call.split('/').pop() || call;
151
+ lines.push(` ${mainId} --> ${callId}[${callName}]`);
152
+ });
153
+ // Добавляем вызывающие функции
154
+ const callers = symbol.calledBy.slice(0, 3);
155
+ callers.forEach((caller, i) => {
156
+ const callerId = `C${i}`;
157
+ const callerName = caller.split('/').pop() || caller;
158
+ lines.push(` ${callerId}[${callerName}] --> ${mainId}`);
159
+ });
160
+ lines.push('```');
161
+ // Также делаем ASCII версию для CLI
162
+ const asciiLines = ['```'];
163
+ if (callers.length > 0) {
164
+ asciiLines.push(' Вызывается из:');
165
+ for (const caller of callers) {
166
+ const name = caller.split('/').pop() || caller;
167
+ asciiLines.push(` ┌─────────────────┐`);
168
+ asciiLines.push(` │ ${name.padEnd(15)} │`);
169
+ asciiLines.push(` └────────┬────────┘`);
170
+ asciiLines.push(` │`);
171
+ asciiLines.push(` ▼`);
172
+ }
173
+ }
174
+ asciiLines.push(` ┌${'─'.repeat(symbol.name.length + 4)}┐`);
175
+ asciiLines.push(` │ ${symbol.name} │ ◀── текущий`);
176
+ asciiLines.push(` └${'─'.repeat(symbol.name.length + 4)}┘`);
177
+ if (calls.length > 0) {
178
+ asciiLines.push(` │`);
179
+ asciiLines.push(` ▼`);
180
+ asciiLines.push(' Вызывает:');
181
+ for (const call of calls) {
182
+ const name = call.split('/').pop() || call;
183
+ asciiLines.push(` ├──▶ ${name}`);
184
+ }
185
+ }
186
+ asciiLines.push('```');
187
+ return {
188
+ type: 'ascii',
189
+ title: `Flow: ${symbol.name}`,
190
+ content: asciiLines.join('\n'),
191
+ };
192
+ }
193
+ function generateArchitectureDiagram(graph) {
194
+ const nodes = Array.from(graph.nodes.values());
195
+ if (nodes.length === 0)
196
+ return null;
197
+ // Группируем файлы по директориям
198
+ const dirs = new Map();
199
+ for (const node of nodes) {
200
+ const parts = node.filePath.split('/');
201
+ if (parts.length > 1) {
202
+ const dir = parts.slice(0, -1).join('/');
203
+ dirs.set(dir, (dirs.get(dir) || 0) + 1);
204
+ }
205
+ }
206
+ const lines = ['```'];
207
+ lines.push('┌─────────────────────────────────────────┐');
208
+ lines.push('│ Архитектура проекта │');
209
+ lines.push('└─────────────────────────────────────────┘');
210
+ lines.push('');
211
+ // Показываем топ директории
212
+ const sortedDirs = Array.from(dirs.entries())
213
+ .sort((a, b) => b[1] - a[1])
214
+ .slice(0, 8);
215
+ for (const [dir, count] of sortedDirs) {
216
+ const shortDir = dir.split('/').slice(-2).join('/');
217
+ const bar = '█'.repeat(Math.min(count, 20));
218
+ lines.push(` 📁 ${shortDir.padEnd(25)} ${bar} (${count})`);
219
+ }
220
+ lines.push('');
221
+ lines.push(` Всего: ${nodes.length} файлов в ${dirs.size} директориях`);
222
+ lines.push('```');
223
+ return {
224
+ type: 'ascii',
225
+ title: 'Архитектура проекта',
226
+ content: lines.join('\n'),
227
+ };
228
+ }
229
+ // ==================== TEST GENERATOR ====================
230
+ export function generateTests(context) {
231
+ const tests = [];
232
+ for (const symbol of context.symbols.slice(0, 3)) {
233
+ if (symbol.type !== 'function' && symbol.type !== 'method')
234
+ continue;
235
+ const language = detectLanguage(symbol.file);
236
+ const test = generateTestForFunction(symbol, language);
237
+ if (test)
238
+ tests.push(test);
239
+ }
240
+ return tests;
241
+ }
242
+ function detectLanguage(filePath) {
243
+ const ext = filePath.split('.').pop()?.toLowerCase();
244
+ switch (ext) {
245
+ case 'ts':
246
+ case 'tsx':
247
+ return 'typescript';
248
+ case 'js':
249
+ case 'jsx':
250
+ return 'javascript';
251
+ case 'php':
252
+ return 'php';
253
+ case 'py':
254
+ return 'python';
255
+ default:
256
+ return 'typescript';
257
+ }
258
+ }
259
+ function generateTestForFunction(symbol, language) {
260
+ const funcName = symbol.name;
261
+ // Анализируем сниппет для понимания сигнатуры
262
+ const snippet = symbol.snippet;
263
+ const hasAsync = snippet.includes('async');
264
+ const hasParams = snippet.includes('(') && !snippet.includes('()');
265
+ let testCode;
266
+ let testFramework;
267
+ switch (language) {
268
+ case 'typescript':
269
+ case 'javascript':
270
+ testFramework = 'jest';
271
+ testCode = generateJestTest(funcName, hasAsync, hasParams, symbol.file);
272
+ break;
273
+ case 'php':
274
+ testFramework = 'phpunit';
275
+ testCode = generatePhpUnitTest(funcName, hasParams, symbol.file);
276
+ break;
277
+ case 'python':
278
+ testFramework = 'pytest';
279
+ testCode = generatePytestTest(funcName, hasAsync, hasParams);
280
+ break;
281
+ default:
282
+ return null;
283
+ }
284
+ return {
285
+ functionName: funcName,
286
+ filePath: symbol.file,
287
+ language,
288
+ testCode,
289
+ testFramework,
290
+ };
291
+ }
292
+ function generateJestTest(funcName, hasAsync, hasParams, filePath) {
293
+ const importPath = filePath.replace(/\.(ts|js)x?$/, '');
294
+ const awaitKeyword = hasAsync ? 'await ' : '';
295
+ const asyncKeyword = hasAsync ? 'async ' : '';
296
+ let params = '';
297
+ let setupCode = '';
298
+ if (hasParams) {
299
+ params = 'mockInput';
300
+ setupCode = ` const mockInput = {}; // TODO: добавить тестовые данные\n`;
301
+ }
302
+ return `import { ${funcName} } from '${importPath}';
303
+
304
+ describe('${funcName}', () => {
305
+ it('should work correctly with valid input', ${asyncKeyword}() => {
306
+ ${setupCode} const result = ${awaitKeyword}${funcName}(${params});
307
+
308
+ expect(result).toBeDefined();
309
+ // TODO: добавить конкретные assertions
310
+ });
311
+
312
+ it('should handle edge cases', ${asyncKeyword}() => {
313
+ // TODO: тест граничных случаев
314
+ expect(true).toBe(true);
315
+ });
316
+
317
+ it('should throw error on invalid input', ${asyncKeyword}() => {
318
+ ${hasAsync ? `await expect(${funcName}(null)).rejects.toThrow();` : `expect(() => ${funcName}(null)).toThrow();`}
319
+ });
320
+ });`;
321
+ }
322
+ function generatePhpUnitTest(funcName, hasParams, filePath) {
323
+ const className = filePath.split('/').pop()?.replace('.php', '') || 'TestClass';
324
+ const params = hasParams ? '$mockInput' : '';
325
+ const setupCode = hasParams ? ' $mockInput = []; // TODO: добавить тестовые данные\n' : '';
326
+ return `<?php
327
+
328
+ use PHPUnit\\Framework\\TestCase;
329
+
330
+ class ${className}Test extends TestCase
331
+ {
332
+ public function test${funcName.charAt(0).toUpperCase() + funcName.slice(1)}WithValidInput(): void
333
+ {
334
+ ${setupCode} $result = ${funcName}(${params});
335
+
336
+ $this->assertNotNull($result);
337
+ // TODO: добавить конкретные assertions
338
+ }
339
+
340
+ public function test${funcName.charAt(0).toUpperCase() + funcName.slice(1)}WithInvalidInput(): void
341
+ {
342
+ $this->expectException(\\InvalidArgumentException::class);
343
+ ${funcName}(null);
344
+ }
345
+ }`;
346
+ }
347
+ function generatePytestTest(funcName, hasAsync, hasParams) {
348
+ const asyncDef = hasAsync ? 'async ' : '';
349
+ const awaitKeyword = hasAsync ? 'await ' : '';
350
+ const params = hasParams ? 'mock_input' : '';
351
+ const setupCode = hasParams ? ' mock_input = {} # TODO: добавить тестовые данные\n' : '';
352
+ const pytestMark = hasAsync ? '@pytest.mark.asyncio\n' : '';
353
+ return `import pytest
354
+ from your_module import ${funcName}
355
+
356
+ ${pytestMark}${asyncDef}def test_${funcName}_with_valid_input():
357
+ ${setupCode} result = ${awaitKeyword}${funcName}(${params})
358
+
359
+ assert result is not None
360
+ # TODO: добавить конкретные assertions
361
+
362
+ ${pytestMark}${asyncDef}def test_${funcName}_with_invalid_input():
363
+ with pytest.raises(ValueError):
364
+ ${awaitKeyword}${funcName}(None)`;
365
+ }
366
+ const PERFORMANCE_PATTERNS = [
367
+ {
368
+ name: 'n-plus-one',
369
+ pattern: /for\s*\([^)]+\)\s*\{[^}]*await\s+\w+\.(find|get|fetch|query|select)/gi,
370
+ severity: 'critical',
371
+ type: 'N+1 Query',
372
+ description: 'Запрос к БД внутри цикла - N+1 проблема',
373
+ suggestion: 'Используй batch запрос или JOIN вместо цикла',
374
+ estimatedImpact: '10-100x improvement',
375
+ getCodeExample: () => `// Вместо:
376
+ for (const item of items) {
377
+ const data = await db.find(item.id);
378
+ }
379
+
380
+ // Используй:
381
+ const ids = items.map(i => i.id);
382
+ const data = await db.findMany({ id: { $in: ids } });`,
383
+ },
384
+ {
385
+ name: 'no-pagination',
386
+ pattern: /SELECT\s+\*\s+FROM\s+\w+(?!\s+LIMIT)/gi,
387
+ severity: 'warning',
388
+ type: 'Missing Pagination',
389
+ description: 'SELECT * без LIMIT может вернуть миллионы записей',
390
+ suggestion: 'Добавь LIMIT и OFFSET для пагинации',
391
+ estimatedImpact: 'Memory usage reduction',
392
+ getCodeExample: () => `// Вместо:
393
+ SELECT * FROM orders
394
+
395
+ // Используй:
396
+ SELECT * FROM orders LIMIT 100 OFFSET 0`,
397
+ },
398
+ {
399
+ name: 'sync-file-read',
400
+ pattern: /readFileSync|writeFileSync|existsSync\s*\(/g,
401
+ severity: 'warning',
402
+ type: 'Synchronous I/O',
403
+ description: 'Синхронное чтение файлов блокирует event loop',
404
+ suggestion: 'Используй async версии: readFile, writeFile, access',
405
+ estimatedImpact: 'Better throughput under load',
406
+ },
407
+ {
408
+ name: 'console-in-loop',
409
+ pattern: /for\s*\([^)]+\)\s*\{[^}]*console\.(log|debug|info)/gi,
410
+ severity: 'info',
411
+ type: 'Console in Loop',
412
+ description: 'console.log в цикле замедляет выполнение',
413
+ suggestion: 'Собери данные и выведи один раз после цикла',
414
+ estimatedImpact: '2-5x faster loops',
415
+ },
416
+ {
417
+ name: 'string-concat-loop',
418
+ pattern: /for\s*\([^)]+\)\s*\{[^}]*\+=/gi,
419
+ severity: 'info',
420
+ type: 'String Concatenation in Loop',
421
+ description: 'Конкатенация строк в цикле создаёт много временных объектов',
422
+ suggestion: 'Используй Array.join() или StringBuilder',
423
+ estimatedImpact: 'Reduced memory allocations',
424
+ },
425
+ {
426
+ name: 'no-index-hint',
427
+ pattern: /findOne\s*\(\s*\{\s*\w+:/g,
428
+ severity: 'info',
429
+ type: 'Possible Missing Index',
430
+ description: 'Проверь есть ли индекс на поле поиска',
431
+ suggestion: 'Создай индекс: db.collection.createIndex({ field: 1 })',
432
+ estimatedImpact: 'Query time: O(n) → O(log n)',
433
+ },
434
+ {
435
+ name: 'large-payload',
436
+ pattern: /res\.(json|send)\s*\(\s*\{[^}]{500,}/g,
437
+ severity: 'warning',
438
+ type: 'Large Response Payload',
439
+ description: 'Большой payload замедляет передачу и парсинг',
440
+ suggestion: 'Используй пагинацию или lazy loading для больших данных',
441
+ estimatedImpact: 'Faster API responses',
442
+ },
443
+ {
444
+ name: 'no-caching',
445
+ pattern: /async\s+\w+\([^)]*\)\s*\{[^}]*await\s+fetch\([^)]+\)/g,
446
+ severity: 'info',
447
+ type: 'No Caching',
448
+ description: 'Внешний API запрос без кэширования',
449
+ suggestion: 'Добавь Redis/memory cache для частых запросов',
450
+ estimatedImpact: 'Reduced latency, less API calls',
451
+ },
452
+ {
453
+ name: 'regexp-in-loop',
454
+ pattern: /for\s*\([^)]+\)\s*\{[^}]*new\s+RegExp/gi,
455
+ severity: 'warning',
456
+ type: 'RegExp Creation in Loop',
457
+ description: 'Создание RegExp в цикле - дорогая операция',
458
+ suggestion: 'Вынеси RegExp за пределы цикла',
459
+ estimatedImpact: '5-10x faster',
460
+ },
461
+ {
462
+ name: 'json-parse-loop',
463
+ pattern: /for\s*\([^)]+\)\s*\{[^}]*JSON\.(parse|stringify)/gi,
464
+ severity: 'warning',
465
+ type: 'JSON Parse/Stringify in Loop',
466
+ description: 'JSON операции в цикле замедляют выполнение',
467
+ suggestion: 'Минимизируй JSON операции, кэшируй результаты',
468
+ estimatedImpact: 'Reduced CPU usage',
469
+ },
470
+ ];
471
+ export function analyzePerformance(context, fileContents) {
472
+ const hints = [];
473
+ // Анализируем файлы из контекста
474
+ const filesToAnalyze = context.relevantFiles.length > 0
475
+ ? context.relevantFiles.map(f => f.path)
476
+ : Array.from(fileContents.keys()).slice(0, 20);
477
+ for (const filePath of filesToAnalyze) {
478
+ const content = fileContents.get(filePath);
479
+ if (!content)
480
+ continue;
481
+ for (const pattern of PERFORMANCE_PATTERNS) {
482
+ // Reset regex state
483
+ pattern.pattern.lastIndex = 0;
484
+ let match;
485
+ while ((match = pattern.pattern.exec(content)) !== null) {
486
+ // Находим номер строки
487
+ const beforeMatch = content.substring(0, match.index);
488
+ const lineNumber = beforeMatch.split('\n').length;
489
+ hints.push({
490
+ severity: pattern.severity,
491
+ type: pattern.type,
492
+ file: filePath,
493
+ line: lineNumber,
494
+ description: pattern.description,
495
+ suggestion: pattern.suggestion,
496
+ estimatedImpact: pattern.estimatedImpact,
497
+ codeExample: pattern.getCodeExample?.(match),
498
+ });
499
+ }
500
+ }
501
+ }
502
+ // Сортируем по severity
503
+ const severityOrder = { critical: 0, warning: 1, info: 2 };
504
+ hints.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
505
+ return hints.slice(0, 10);
506
+ }
507
+ // ==================== MAIN ENHANCER ====================
508
+ export class ResponseEnhancer {
509
+ graph;
510
+ fileContents;
511
+ constructor(graph, _symbols, fileContents) {
512
+ this.graph = graph;
513
+ this.fileContents = fileContents;
514
+ }
515
+ /**
516
+ * Улучшает ответ ИИ дополнительной информацией
517
+ */
518
+ enhance(originalAnswer, context) {
519
+ const startTime = Date.now();
520
+ // 1. Follow-up suggestions
521
+ const followUpSuggestions = generateFollowUpSuggestions(context, originalAnswer);
522
+ // 2. Visual diagrams
523
+ const diagrams = [];
524
+ // Добавляем диаграмму зависимостей если есть зависимости
525
+ if (context.dependencies.length > 0) {
526
+ const depDiagram = generateDiagram(context, this.graph, 'dependencies');
527
+ if (depDiagram)
528
+ diagrams.push(depDiagram);
529
+ }
530
+ // Добавляем flow диаграмму если искали символ
531
+ if (context.intent === 'find_symbol' || context.intent === 'explain_flow') {
532
+ const flowDiagram = generateDiagram(context, this.graph, 'flow');
533
+ if (flowDiagram)
534
+ diagrams.push(flowDiagram);
535
+ }
536
+ // 3. Generated tests
537
+ const generatedTests = context.intent === 'find_symbol' || context.intent === 'explain_flow'
538
+ ? generateTests(context)
539
+ : [];
540
+ // 4. Performance hints
541
+ const performanceHints = context.intent === 'find_bugs' ||
542
+ context.intent === 'refactor' ||
543
+ context.query.toLowerCase().includes('performance') ||
544
+ context.query.toLowerCase().includes('оптимиз') ||
545
+ context.query.toLowerCase().includes('тормоз')
546
+ ? analyzePerformance(context, this.fileContents)
547
+ : [];
548
+ return {
549
+ answer: originalAnswer,
550
+ followUpSuggestions,
551
+ diagrams,
552
+ generatedTests,
553
+ performanceHints,
554
+ metadata: {
555
+ intent: context.intent,
556
+ processingTime: Date.now() - startTime,
557
+ symbolsAnalyzed: context.symbols.length,
558
+ filesAnalyzed: context.relevantFiles.length,
559
+ },
560
+ };
561
+ }
562
+ /**
563
+ * Форматирует улучшенный ответ для CLI
564
+ */
565
+ formatForCLI(enhanced) {
566
+ const parts = [enhanced.answer];
567
+ // Диаграммы
568
+ if (enhanced.diagrams.length > 0) {
569
+ parts.push('\n');
570
+ for (const diagram of enhanced.diagrams) {
571
+ parts.push(`\n📊 ${diagram.title}:\n`);
572
+ parts.push(diagram.content);
573
+ }
574
+ }
575
+ // Performance hints
576
+ if (enhanced.performanceHints.length > 0) {
577
+ parts.push('\n\n⚡ Подсказки по производительности:\n');
578
+ for (const hint of enhanced.performanceHints) {
579
+ const icon = hint.severity === 'critical' ? '🔴' :
580
+ hint.severity === 'warning' ? '🟡' : '🔵';
581
+ parts.push(`\n${icon} ${hint.type}: ${hint.description}`);
582
+ parts.push(` 📍 ${hint.file}:${hint.line}`);
583
+ parts.push(` 💡 ${hint.suggestion}`);
584
+ parts.push(` 📈 ${hint.estimatedImpact}`);
585
+ if (hint.codeExample) {
586
+ parts.push(`\n${hint.codeExample}`);
587
+ }
588
+ }
589
+ }
590
+ // Сгенерированные тесты
591
+ if (enhanced.generatedTests.length > 0) {
592
+ parts.push('\n\n🧪 Сгенерированные тесты:\n');
593
+ for (const test of enhanced.generatedTests) {
594
+ parts.push(`\n// Тест для ${test.functionName} (${test.testFramework})`);
595
+ parts.push(`// Файл: ${test.filePath}`);
596
+ parts.push('```' + test.language);
597
+ parts.push(test.testCode);
598
+ parts.push('```');
599
+ }
600
+ }
601
+ // Follow-up suggestions
602
+ if (enhanced.followUpSuggestions.length > 0) {
603
+ parts.push('\n\n📌 Связанные вопросы:\n');
604
+ enhanced.followUpSuggestions.forEach((suggestion, i) => {
605
+ parts.push(` [${i + 1}] ${suggestion.icon} ${suggestion.question}`);
606
+ });
607
+ }
608
+ return parts.join('');
609
+ }
610
+ /**
611
+ * Форматирует улучшенный ответ для API/Web
612
+ */
613
+ formatForAPI(enhanced) {
614
+ // Для API возвращаем структурированные данные
615
+ // Фронтенд сам решит как их отобразить
616
+ return {
617
+ answer: enhanced.answer,
618
+ followUpSuggestions: enhanced.followUpSuggestions,
619
+ diagrams: enhanced.diagrams,
620
+ generatedTests: enhanced.generatedTests,
621
+ performanceHints: enhanced.performanceHints,
622
+ metadata: enhanced.metadata,
623
+ };
624
+ }
625
+ }
626
+ /**
627
+ * Создание энхансера
628
+ */
629
+ export function createResponseEnhancer(graph, symbols, fileContents) {
630
+ return new ResponseEnhancer(graph, symbols, fileContents);
631
+ }
632
+ //# sourceMappingURL=response-enhancer.js.map