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.
@@ -520,6 +520,11 @@ async function handleCommand(input) {
520
520
  case 'documentation':
521
521
  await handleDocsCommand(args);
522
522
  break;
523
+ case 'narrative':
524
+ case 'story':
525
+ case 'explain':
526
+ await handleNarrativeCommand();
527
+ break;
523
528
  case 'status':
524
529
  await handleStatusCommand();
525
530
  break;
@@ -965,7 +970,8 @@ async function handleQuery(query) {
965
970
  content: m.content,
966
971
  }));
967
972
  }
968
- const response = await apiFetch(`${config.serverUrl}/api/projects/${state.projectId}/ask`, {
973
+ // Используем enhanced endpoint для расширенных ответов
974
+ const response = await apiFetch(`${config.serverUrl}/api/projects/${state.projectId}/ask-enhanced`, {
969
975
  method: 'POST',
970
976
  body: JSON.stringify({
971
977
  question: query,
@@ -986,6 +992,55 @@ async function handleQuery(query) {
986
992
  for (const line of lines) {
987
993
  console.log(' ' + line);
988
994
  }
995
+ // Показываем диаграммы если есть
996
+ if (data.diagrams && data.diagrams.length > 0) {
997
+ for (const diagram of data.diagrams) {
998
+ console.log();
999
+ console.log(colors.secondary(` 📊 ${diagram.title}:`));
1000
+ console.log(diagram.content);
1001
+ }
1002
+ }
1003
+ // Показываем подсказки по производительности если есть
1004
+ if (data.performanceHints && data.performanceHints.length > 0) {
1005
+ console.log();
1006
+ console.log(colors.warning(' ⚡ Подсказки по производительности:'));
1007
+ for (const hint of data.performanceHints) {
1008
+ const icon = hint.severity === 'critical' ? '🔴' :
1009
+ hint.severity === 'warning' ? '🟡' : '🔵';
1010
+ console.log();
1011
+ console.log(colors.highlight(` ${icon} ${hint.type}: ${hint.description}`));
1012
+ console.log(colors.muted(` 📍 ${hint.file}:${hint.line}`));
1013
+ console.log(colors.info(` 💡 ${hint.suggestion}`));
1014
+ console.log(colors.muted(` 📈 ${hint.estimatedImpact}`));
1015
+ if (hint.codeExample) {
1016
+ console.log(colors.dim(hint.codeExample));
1017
+ }
1018
+ }
1019
+ }
1020
+ // Показываем сгенерированные тесты если есть
1021
+ if (data.generatedTests && data.generatedTests.length > 0) {
1022
+ console.log();
1023
+ console.log(colors.success(' 🧪 Сгенерированные тесты:'));
1024
+ for (const test of data.generatedTests) {
1025
+ console.log();
1026
+ console.log(colors.muted(` // Тест для ${test.functionName} (${test.testFramework})`));
1027
+ console.log(colors.dim(` // Файл: ${test.filePath}`));
1028
+ console.log(colors.dim(' ```' + test.language));
1029
+ const testLines = test.testCode.split('\n');
1030
+ for (const tl of testLines) {
1031
+ console.log(' ' + tl);
1032
+ }
1033
+ console.log(colors.dim(' ```'));
1034
+ }
1035
+ }
1036
+ // Показываем follow-up suggestions если есть
1037
+ if (data.followUpSuggestions && data.followUpSuggestions.length > 0) {
1038
+ console.log();
1039
+ console.log(colors.secondary(' 📌 Связанные вопросы:'));
1040
+ data.followUpSuggestions.forEach((suggestion, i) => {
1041
+ console.log(colors.muted(` [${i + 1}] ${suggestion.icon} ${suggestion.question}`));
1042
+ });
1043
+ }
989
1044
  // Save assistant response to history
990
1045
  if (state.conversationSession) {
991
1046
  await addMessage(state.conversationSession, 'assistant', answer, {
@@ -1389,6 +1444,101 @@ async function handleDocsCommand(args) {
1389
1444
  throw error;
1390
1445
  }
1391
1446
  }
1447
+ /**
1448
+ * /narrative command - AI Narrator: человекопонятное описание проекта
1449
+ *
1450
+ * Показывает:
1451
+ * - Что это за проект
1452
+ * - Стек технологий
1453
+ * - Архитектуру и паттерны
1454
+ * - Проблемные места
1455
+ * - Рекомендации
1456
+ */
1457
+ async function handleNarrativeCommand() {
1458
+ if (!state.projectId) {
1459
+ printError('No project indexed');
1460
+ printInfo('Use /index first');
1461
+ return;
1462
+ }
1463
+ const spinner = createSpinner('Analyzing project architecture...').start();
1464
+ try {
1465
+ const config = await loadConfig();
1466
+ const response = await apiFetch(`${config.serverUrl}/api/projects/${state.projectId}/narrative?language=ru`);
1467
+ if (!response.ok) {
1468
+ const error = await response.json().catch(() => ({}));
1469
+ throw new Error(error.error || 'Failed to generate narrative');
1470
+ }
1471
+ const data = await response.json();
1472
+ spinner.succeed('Analysis complete');
1473
+ // Красивый вывод в консоль
1474
+ console.log();
1475
+ console.log(colors.primary('═══════════════════════════════════════════════════════════════'));
1476
+ console.log(colors.primary(' AI NARRATOR - Анализ проекта'));
1477
+ console.log(colors.primary('═══════════════════════════════════════════════════════════════'));
1478
+ console.log();
1479
+ const report = data.report;
1480
+ // Обзор
1481
+ console.log(colors.highlight('📋 ОБЗОР'));
1482
+ console.log(` Тип: ${colors.secondary(report.summary.projectType)}`);
1483
+ console.log(` Язык: ${colors.secondary(report.summary.primaryLanguage)}`);
1484
+ console.log(` Файлов: ${colors.secondary(String(report.summary.totalFiles))}`);
1485
+ console.log(` Символов: ${colors.secondary(String(report.summary.totalSymbols))}`);
1486
+ console.log(` Строк кода: ${colors.secondary(report.summary.linesOfCode.toLocaleString())}`);
1487
+ console.log();
1488
+ // Стек технологий
1489
+ console.log(colors.highlight('🛠️ СТЕК ТЕХНОЛОГИЙ'));
1490
+ if (report.techStack.frontend.length > 0) {
1491
+ console.log(` Frontend: ${colors.secondary(report.techStack.frontend.join(', '))}`);
1492
+ }
1493
+ if (report.techStack.backend.length > 0) {
1494
+ console.log(` Backend: ${colors.secondary(report.techStack.backend.join(', '))}`);
1495
+ }
1496
+ if (report.techStack.database.length > 0) {
1497
+ console.log(` Database: ${colors.secondary(report.techStack.database.join(', '))}`);
1498
+ }
1499
+ console.log();
1500
+ // Архитектура
1501
+ console.log(colors.highlight('🏗️ АРХИТЕКТУРА'));
1502
+ console.log(` Паттерн: ${colors.secondary(report.architecture.detected)} (${report.architecture.confidence}%)`);
1503
+ if (report.architecture.patterns.length > 0) {
1504
+ console.log(' Используются:');
1505
+ for (const pattern of report.architecture.patterns.slice(0, 5)) {
1506
+ console.log(` • ${pattern.name} (${pattern.confidence}%)`);
1507
+ }
1508
+ }
1509
+ console.log();
1510
+ // Проблемы
1511
+ if (report.problems.length > 0) {
1512
+ console.log(colors.highlight('⚠️ ПРОБЛЕМНЫЕ МЕСТА'));
1513
+ for (const problem of report.problems.slice(0, 5)) {
1514
+ const icon = problem.severity === 'critical' ? '🔴' :
1515
+ problem.severity === 'warning' ? '🟡' : '🔵';
1516
+ console.log(` ${icon} ${colors.error(problem.title)}`);
1517
+ console.log(` ${problem.description}`);
1518
+ console.log(` 💡 ${colors.muted(problem.suggestion)}`);
1519
+ console.log();
1520
+ }
1521
+ }
1522
+ // Рекомендации
1523
+ if (report.recommendations.length > 0) {
1524
+ console.log(colors.highlight('💡 РЕКОМЕНДАЦИИ'));
1525
+ for (let i = 0; i < Math.min(report.recommendations.length, 5); i++) {
1526
+ console.log(` ${i + 1}. ${report.recommendations[i]}`);
1527
+ }
1528
+ console.log();
1529
+ }
1530
+ console.log(colors.primary('═══════════════════════════════════════════════════════════════'));
1531
+ console.log();
1532
+ // Также выводим полный Markdown нарратив
1533
+ printInfo('Полный отчёт в формате Markdown:');
1534
+ console.log();
1535
+ console.log(colors.muted(data.narrative));
1536
+ }
1537
+ catch (error) {
1538
+ spinner.fail('Analysis failed');
1539
+ printError(error instanceof Error ? error.message : 'Unknown error');
1540
+ }
1541
+ }
1392
1542
  /**
1393
1543
  * /history command - View and search conversation history
1394
1544
  *
@@ -70,6 +70,7 @@ export function printHelp() {
70
70
  console.log();
71
71
  const commands = [
72
72
  ['/index', 'Index current project'],
73
+ ['/narrative', 'AI Narrator - describe architecture'],
73
74
  ['/analyze [desc]', 'Run impact analysis'],
74
75
  ['/search <query>', 'Semantic code search'],
75
76
  ['/dead-code', 'Find dead code'],
@@ -14,17 +14,21 @@ function debugLog(message) {
14
14
  debugLog(` ${message}`);
15
15
  }
16
16
  }
17
- // Лимиты для chunked upload (оптимизировано для очень больших проектов и нестабильных соединений)
18
- const MAX_PAYLOAD_SIZE = 3 * 1024 * 1024; // 3MB per chunk (уменьшено для надёжности на медленных соединениях)
19
- const MAX_SYMBOLS_PER_CHUNK = 1500; // Меньше символов на chunk для стабильности
20
- const MAX_FILES_PER_CHUNK = 30; // Меньше файлов на chunk для стабильности
21
- const UPLOAD_TIMEOUT = 300000; // 5 минут на chunk (увеличено для медленных соединений)
22
- const MAX_RETRIES = 7; // Ещё больше попыток для нестабильных сетей
23
- // Лимиты для минимальной загрузки
24
- const MINIMAL_MAX_SYMBOLS = 10000;
25
- const MINIMAL_MAX_FILES = 500;
17
+ // Лимиты для single request upload (оптимизировано для средних проектов)
18
+ // ВАЖНО: chunked upload требует одного инстанса сервера (in-memory sessions)
19
+ // Для PM2 cluster mode используем single request до 50K символов
20
+ const MAX_PAYLOAD_SIZE = 50 * 1024 * 1024; // 50MB - увеличено для single request
21
+ const SINGLE_REQUEST_MAX_SYMBOLS = 50000; // До 50K символов - single request
22
+ const UPLOAD_TIMEOUT = 600000; // 10 минут для больших проектов
23
+ const MAX_RETRIES = 5; // Retry для нестабильных соединений
24
+ // Лимиты для chunked upload (только для очень больших проектов >50K символов)
25
+ const MAX_SYMBOLS_PER_CHUNK = 5000; // Больше символов на chunk
26
+ const MAX_FILES_PER_CHUNK = 100; // Больше файлов на chunk
27
+ // Лимиты для минимальной загрузки (fallback)
28
+ const MINIMAL_MAX_SYMBOLS = 20000;
29
+ const MINIMAL_MAX_FILES = 1000;
26
30
  // Порог для "очень большого" проекта (пропускаем fileContents)
27
- const VERY_LARGE_PROJECT_SYMBOLS = 30000;
31
+ const VERY_LARGE_PROJECT_SYMBOLS = 100000;
28
32
  /**
29
33
  * Определение размера JSON в байтах
30
34
  */
@@ -221,21 +225,43 @@ export async function uploadIndexData(projectId, data, onProgress) {
221
225
  const url = `${config.serverUrl}/api/projects/${projectId}/upload-index`;
222
226
  // Оценка размера данных
223
227
  const estimatedSize = estimateJsonSize(data);
224
- const isLargeProject = estimatedSize > MAX_PAYLOAD_SIZE ||
225
- data.symbols.length > MAX_SYMBOLS_PER_CHUNK * 2;
226
228
  onProgress?.({
227
229
  phase: 'preparing',
228
230
  current: 0,
229
231
  total: 100,
230
- message: `Preparing upload (${(estimatedSize / 1024 / 1024).toFixed(1)} MB)...`,
232
+ message: `Preparing upload (${(estimatedSize / 1024 / 1024).toFixed(1)} MB, ${data.symbols.length} symbols)...`,
231
233
  });
232
- // Для небольших проектов - обычная загрузка
233
- if (!isLargeProject) {
234
+ // ВАЖНО: Chunked upload НЕ работает с PM2 cluster mode (разные процессы = разная память)
235
+ // Поэтому для проектов до 50K символов ВСЕГДА используем single request
236
+ // Если payload слишком большой - урезаем данные вместо chunked upload
237
+ if (data.symbols.length <= SINGLE_REQUEST_MAX_SYMBOLS) {
238
+ // Проект средний - используем single request
239
+ // Если payload > 50MB, урезаем fileContents
240
+ let uploadData = data;
241
+ if (estimatedSize > MAX_PAYLOAD_SIZE) {
242
+ debugLog(` Payload ${(estimatedSize / 1024 / 1024).toFixed(1)}MB exceeds limit, trimming fileContents`);
243
+ onProgress?.({
244
+ phase: 'preparing',
245
+ current: 0,
246
+ total: 100,
247
+ message: `Optimizing payload (trimming file contents)...`,
248
+ });
249
+ // Урезаем fileContents чтобы уместиться в лимит
250
+ const maxFileContents = Math.floor(data.fileContents.length * (MAX_PAYLOAD_SIZE / estimatedSize));
251
+ uploadData = {
252
+ ...data,
253
+ fileContents: data.fileContents.slice(0, Math.max(maxFileContents, 100)),
254
+ };
255
+ const newSize = estimateJsonSize(uploadData);
256
+ debugLog(` Trimmed payload to ${(newSize / 1024 / 1024).toFixed(1)}MB (${uploadData.fileContents.length} files)`);
257
+ }
234
258
  debugLog(` Using single request upload for ${data.symbols.length} symbols`);
235
- return uploadSingleRequest(url, projectId, data, config.accessToken || '', onProgress);
259
+ return uploadSingleRequest(url, projectId, uploadData, config.accessToken || '', onProgress);
236
260
  }
237
- // Для больших проектов - chunked upload
238
- debugLog(` Using chunked upload for large project (${data.symbols.length} symbols)`);
261
+ // Проект очень большой (>50K символов) - chunked upload
262
+ // ВНИМАНИЕ: требует PM2 -i 1 (один инстанс)
263
+ debugLog(` Using chunked upload for very large project (${data.symbols.length} symbols)`);
264
+ console.log(' ⚠ Very large project (>50K symbols). Chunked upload requires single server instance (PM2 -i 1).');
239
265
  return uploadChunked(url, projectId, data, config.accessToken || '', onProgress);
240
266
  }
241
267
  /**
@@ -391,6 +391,72 @@ A: "✅ Компонент Comments найден в 3 файлах:
391
391
  prompt += `- ${bc.name}: ${bc.description}\n`;
392
392
  }
393
393
  }
394
+ // === SMART CONTEXT: умный контекст от ContextBuilder ===
395
+ if (context?.smartContext) {
396
+ const sc = context.smartContext;
397
+ prompt += '\n\n###SMART CONTEXT (ТОЧНЫЙ ПОИСК)###\n';
398
+ prompt += `Намерение пользователя: ${sc.intent}\n`;
399
+ prompt += `Проект: ${sc.projectStats.totalFiles} файлов, ${sc.projectStats.totalSymbols} символов\n`;
400
+ prompt += `Языки: ${sc.projectStats.languages.join(', ')}\n`;
401
+ // Найденные символы с кодом
402
+ if (sc.symbols.length > 0) {
403
+ prompt += `\n## НАЙДЕННЫЕ СИМВОЛЫ (${sc.symbols.length}):\n`;
404
+ prompt += `⚠️ ЭТО ТОЧНЫЕ РЕЗУЛЬТАТЫ ПОИСКА - используй их в ответе!\n\n`;
405
+ for (const sym of sc.symbols.slice(0, 5)) {
406
+ const cleanPath = sanitizePath(sym.file);
407
+ prompt += `### ${sym.type.toUpperCase()}: ${sym.name}\n`;
408
+ prompt += `📍 Файл: ${cleanPath}:${sym.line}\n`;
409
+ prompt += `Уверенность: ${Math.round(sym.confidence * 100)}%\n`;
410
+ prompt += '```\n' + sym.snippet + '\n```\n';
411
+ if (sym.usedIn.length > 0) {
412
+ prompt += `📎 Используется в ${sym.usedIn.length} местах:\n`;
413
+ for (const usage of sym.usedIn.slice(0, 3)) {
414
+ const usageCleanPath = sanitizePath(usage.file);
415
+ prompt += ` - ${usageCleanPath}:${usage.line}: \`${usage.snippet}\`\n`;
416
+ }
417
+ if (sym.usedIn.length > 3) {
418
+ prompt += ` ... и ещё ${sym.usedIn.length - 3} мест(а)\n`;
419
+ }
420
+ }
421
+ if (sym.calledBy.length > 0) {
422
+ prompt += `📞 Вызывается из: ${sym.calledBy.slice(0, 5).map(f => sanitizePath(f)).join(', ')}\n`;
423
+ }
424
+ prompt += '\n';
425
+ }
426
+ if (sc.symbols.length > 5) {
427
+ prompt += `... и ещё ${sc.symbols.length - 5} символов найдено\n`;
428
+ }
429
+ }
430
+ // Найденные проблемы (баги, уязвимости)
431
+ if (sc.issues.length > 0) {
432
+ prompt += `\n## НАЙДЕННЫЕ ПРОБЛЕМЫ (${sc.issues.length}):\n`;
433
+ prompt += `⚠️ ОБЯЗАТЕЛЬНО УПОМЯНИ ЭТИ ПРОБЛЕМЫ В ОТВЕТЕ!\n\n`;
434
+ const severityIcons = {
435
+ critical: '🔴 CRITICAL',
436
+ high: '🟠 HIGH',
437
+ medium: '🟡 MEDIUM',
438
+ low: '🔵 LOW'
439
+ };
440
+ for (const issue of sc.issues.slice(0, 10)) {
441
+ const cleanPath = sanitizePath(issue.file);
442
+ prompt += `${severityIcons[issue.severity]}: ${issue.message}\n`;
443
+ prompt += ` 📍 ${cleanPath}:${issue.line}\n`;
444
+ prompt += ` Код: \`${issue.snippet}\`\n`;
445
+ prompt += ` 💡 ${issue.suggestion}\n\n`;
446
+ }
447
+ if (sc.issues.length > 10) {
448
+ prompt += `... и ещё ${sc.issues.length - 10} проблем\n`;
449
+ }
450
+ }
451
+ // Зависимости
452
+ if (sc.dependencies.length > 0) {
453
+ prompt += `\n## ЗАВИСИМОСТИ:\n`;
454
+ for (const dep of sc.dependencies.slice(0, 10)) {
455
+ prompt += ` ${sanitizePath(dep.from)} → ${sanitizePath(dep.to)}\n`;
456
+ }
457
+ }
458
+ prompt += '\n###END SMART CONTEXT###\n';
459
+ }
394
460
  if (context?.semanticMemory && context.semanticMemory.length > 0) {
395
461
  const totalResults = context.semanticMemory.length;
396
462
  const maxResults = Math.min(totalResults, 30); // Увеличено с 10 до 30
@@ -419,7 +485,7 @@ A: "✅ Компонент Comments найден в 3 файлах:
419
485
  }
420
486
  prompt += '\n###END PROJECT FILES###';
421
487
  }
422
- else {
488
+ else if (!context?.smartContext) {
423
489
  prompt += '\n\n###PROJECT FILES: EMPTY###\nNo code indexed. Respond: "Проект не проиндексирован. Запустите индексацию."';
424
490
  }
425
491
  return prompt;
@@ -528,6 +528,54 @@ apiRouter.post('/projects/:id/ask', authMiddleware, checkProjectAccess, async (r
528
528
  res.status(500).json({ error: 'Failed to process question' });
529
529
  }
530
530
  });
531
+ /**
532
+ * POST /api/projects/:id/ask-enhanced
533
+ * Задать вопрос AI-архитектору с расширенным ответом
534
+ * Включает: follow-up suggestions, диаграммы, тесты, performance hints
535
+ */
536
+ apiRouter.post('/projects/:id/ask-enhanced', authMiddleware, checkProjectAccess, async (req, res) => {
537
+ try {
538
+ const { id } = req.params;
539
+ const { question, language } = req.body;
540
+ const userId = getUserId(req);
541
+ if (!question) {
542
+ res.status(400).json({ error: 'Question is required' });
543
+ return;
544
+ }
545
+ // Input validation
546
+ if (typeof question !== 'string') {
547
+ res.status(400).json({ error: 'Invalid question format' });
548
+ return;
549
+ }
550
+ const MAX_QUESTION_LENGTH = 5000;
551
+ if (question.length > MAX_QUESTION_LENGTH) {
552
+ res.status(400).json({ error: `Question too long. Maximum ${MAX_QUESTION_LENGTH} characters.` });
553
+ return;
554
+ }
555
+ // Sanitize question
556
+ const sanitizedQuestion = question
557
+ .replace(/\0/g, '')
558
+ .replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '');
559
+ // Check request limit
560
+ if (userId) {
561
+ const usageResult = await authService.checkAndUpdateUsage(userId, 'request');
562
+ if (!usageResult.allowed) {
563
+ res.status(429).json({
564
+ error: 'Request limit reached',
565
+ message: `You have reached your daily request limit (${usageResult.limit})`,
566
+ usage: { used: usageResult.limit, limit: usageResult.limit, remaining: 0 }
567
+ });
568
+ return;
569
+ }
570
+ }
571
+ const enhancedResponse = await projectService.askArchitectEnhanced(id, sanitizedQuestion, language || 'en');
572
+ res.json(enhancedResponse);
573
+ }
574
+ catch (error) {
575
+ Logger.error('Failed to ask architect (enhanced):', error);
576
+ res.status(500).json({ error: 'Failed to process question' });
577
+ }
578
+ });
531
579
  /**
532
580
  * GET /api/projects/:id/stats
533
581
  * Получить статистику проекта
@@ -755,6 +803,28 @@ apiRouter.post('/projects/:id/documentation', authMiddleware, checkProjectAccess
755
803
  res.status(500).json({ error: 'Failed to generate documentation' });
756
804
  }
757
805
  });
806
+ /**
807
+ * GET /api/projects/:id/narrative
808
+ * AI Narrator - человекопонятное описание архитектуры проекта
809
+ */
810
+ apiRouter.get('/projects/:id/narrative', authMiddleware, checkProjectAccess, async (req, res) => {
811
+ try {
812
+ const { id } = req.params;
813
+ const language = req.query.language || 'ru';
814
+ const report = await projectService.getNarrative(id, language);
815
+ res.json({
816
+ success: true,
817
+ report,
818
+ // Отдельно текстовый нарратив для удобства
819
+ narrative: language === 'ru' ? report.narrativeRu : report.narrative,
820
+ });
821
+ }
822
+ catch (error) {
823
+ Logger.error('Failed to generate narrative:', error);
824
+ const errorMessage = error instanceof Error ? error.message : 'Failed to generate narrative';
825
+ res.status(500).json({ error: errorMessage });
826
+ }
827
+ });
758
828
  /**
759
829
  * GET /api/projects/:id/index-progress
760
830
  * Server-Sent Events endpoint for real-time indexing progress
@@ -12,6 +12,8 @@ import { DuplicationResult } from '../../analyzers/duplication.js';
12
12
  import { SecurityResult } from '../../analyzers/security.js';
13
13
  import { RefactoringResult } from '../../refactoring/index.js';
14
14
  import { ExportData } from '../../export/index.js';
15
+ import { NarratorReport } from '../../analyzers/ai-narrator.js';
16
+ import { EnhancedResponse } from '../../analyzers/response-enhancer.js';
15
17
  export interface Project {
16
18
  id: string;
17
19
  name: string;
@@ -89,6 +91,11 @@ export declare class ProjectService {
89
91
  }>;
90
92
  semanticSearch(projectId: string, query: string, limit?: number): Promise<unknown[]>;
91
93
  askArchitect(projectId: string, question: string, language?: 'en' | 'ru'): Promise<string>;
94
+ /**
95
+ * Расширенный ответ архитектора с дополнительными инструментами
96
+ * Включает: follow-up suggestions, диаграммы, тесты, performance hints
97
+ */
98
+ askArchitectEnhanced(projectId: string, question: string, language?: 'en' | 'ru'): Promise<EnhancedResponse>;
92
99
  getStatistics(projectId: string): Promise<{
93
100
  project: Project;
94
101
  indexStats: ProjectStats | null;
@@ -188,5 +195,9 @@ export declare class ProjectService {
188
195
  * Построить текстовое представление структуры файлов
189
196
  */
190
197
  private buildFileStructure;
198
+ /**
199
+ * AI Narrator - человекопонятное описание архитектуры проекта
200
+ */
201
+ getNarrative(projectId: string, _language?: 'en' | 'ru'): Promise<NarratorReport>;
191
202
  }
192
203
  //# sourceMappingURL=project-service.d.ts.map
@@ -22,6 +22,9 @@ import { DuplicationDetector } from '../../analyzers/duplication.js';
22
22
  import { SecurityAnalyzer } from '../../analyzers/security.js';
23
23
  import { RefactoringEngine } from '../../refactoring/index.js';
24
24
  import { GitHubService } from '../../github/github-service.js';
25
+ import { AINarrator } from '../../analyzers/ai-narrator.js';
26
+ import { ContextBuilder } from '../../analyzers/context-builder.js';
27
+ import { createResponseEnhancer } from '../../analyzers/response-enhancer.js';
25
28
  /**
26
29
  * Sanitize file paths to hide server directories
27
30
  * Converts absolute paths to relative project paths
@@ -430,6 +433,20 @@ export class ProjectService {
430
433
  const projectMetadata = analyzeProjectStack(project.path);
431
434
  // Загружаем содержимое файлов для полного контекста
432
435
  const fileContents = await this.getFileContents(projectId);
436
+ // === SMART CONTEXT: используем ContextBuilder для умного поиска ===
437
+ let smartContext = null;
438
+ if (data.symbols && data.graph) {
439
+ try {
440
+ const contextBuilder = new ContextBuilder(data.symbols, data.graph, fileContents, {
441
+ framework: projectMetadata?.framework,
442
+ });
443
+ smartContext = await contextBuilder.buildContext(question);
444
+ Logger.info(`Smart context: intent=${smartContext.intent}, symbols=${smartContext.symbols.length}, issues=${smartContext.issues.length}`);
445
+ }
446
+ catch (err) {
447
+ Logger.warn('Smart context builder failed:', err);
448
+ }
449
+ }
433
450
  // Ищем релевантный контекст (если есть семантическая память)
434
451
  let searchResults = [];
435
452
  if (data.semanticMemory) {
@@ -490,15 +507,72 @@ export class ProjectService {
490
507
  }
491
508
  }
492
509
  }
493
- Logger.debug(`Passing ${searchResults.length} context items + project metadata to AI`);
510
+ Logger.debug(`Passing ${searchResults.length} context items + smart context + project metadata to AI`);
494
511
  const answer = await data.orchestrator.answerArchitecturalQuestion(question, {
495
512
  architecture: data.architecture.getModel(),
496
513
  semanticMemory: searchResults,
497
514
  language,
498
- projectMetadata: projectMetadata || undefined
515
+ projectMetadata: projectMetadata || undefined,
516
+ smartContext: smartContext || undefined
499
517
  });
500
518
  return answer;
501
519
  }
520
+ /**
521
+ * Расширенный ответ архитектора с дополнительными инструментами
522
+ * Включает: follow-up suggestions, диаграммы, тесты, performance hints
523
+ */
524
+ async askArchitectEnhanced(projectId, question, language = 'en') {
525
+ const project = this.projects.get(projectId);
526
+ if (!project) {
527
+ throw new Error(`Project not found: ${projectId}`);
528
+ }
529
+ const data = await this.getProjectData(projectId);
530
+ const fileContents = await this.getFileContents(projectId);
531
+ // Строим умный контекст
532
+ let smartContext = null;
533
+ if (data.symbols && data.graph) {
534
+ const contextBuilder = new ContextBuilder(data.symbols, data.graph, fileContents);
535
+ smartContext = await contextBuilder.buildContext(question);
536
+ }
537
+ // Получаем базовый ответ
538
+ const answer = await this.askArchitect(projectId, question, language);
539
+ // Создаём энхансер
540
+ if (!data.graph || !data.symbols) {
541
+ // Возвращаем минимальный ответ без улучшений
542
+ return {
543
+ answer,
544
+ followUpSuggestions: [],
545
+ diagrams: [],
546
+ generatedTests: [],
547
+ performanceHints: [],
548
+ metadata: {
549
+ intent: 'general',
550
+ processingTime: 0,
551
+ symbolsAnalyzed: 0,
552
+ filesAnalyzed: 0,
553
+ },
554
+ };
555
+ }
556
+ const enhancer = createResponseEnhancer(data.graph, data.symbols, fileContents);
557
+ // Если контекст есть - улучшаем ответ
558
+ if (smartContext) {
559
+ return enhancer.enhance(answer, smartContext);
560
+ }
561
+ // Fallback - минимальный ответ
562
+ return {
563
+ answer,
564
+ followUpSuggestions: [],
565
+ diagrams: [],
566
+ generatedTests: [],
567
+ performanceHints: [],
568
+ metadata: {
569
+ intent: 'general',
570
+ processingTime: 0,
571
+ symbolsAnalyzed: 0,
572
+ filesAnalyzed: 0,
573
+ },
574
+ };
575
+ }
502
576
  async getStatistics(projectId) {
503
577
  const project = this.projects.get(projectId);
504
578
  if (!project) {
@@ -1080,5 +1154,26 @@ ${functions.slice(0, 30).map(f => `- \`${f.name}\` - ${sanitizePath(f.filePath)}
1080
1154
  };
1081
1155
  return renderTree(tree);
1082
1156
  }
1157
+ /**
1158
+ * AI Narrator - человекопонятное описание архитектуры проекта
1159
+ */
1160
+ async getNarrative(projectId, _language = 'ru') {
1161
+ const project = this.projects.get(projectId);
1162
+ if (!project) {
1163
+ throw new Error(`Project not found: ${projectId}`);
1164
+ }
1165
+ const data = await this.getProjectData(projectId);
1166
+ if (!data.graph || !data.symbols) {
1167
+ throw new Error('Project not indexed. Please index first via CLI.');
1168
+ }
1169
+ const fileContents = await this.getFileContents(projectId);
1170
+ // Анализируем стек технологий
1171
+ const { analyzeProjectStack } = await import('../../utils/project-analyzer.js');
1172
+ const projectMetadata = analyzeProjectStack(project.path);
1173
+ const narrator = new AINarrator();
1174
+ const report = await narrator.analyze(data.graph, data.symbols, fileContents, projectMetadata || undefined);
1175
+ Logger.success(`AI Narrator generated report for: ${project.name}`);
1176
+ return report;
1177
+ }
1083
1178
  }
1084
1179
  //# sourceMappingURL=project-service.js.map
@@ -233,6 +233,50 @@ export interface LLMContext {
233
233
  recentChanges?: Change[];
234
234
  language?: 'en' | 'ru';
235
235
  projectMetadata?: ProjectMetadata;
236
+ smartContext?: {
237
+ intent: string;
238
+ query: string;
239
+ symbols: Array<{
240
+ name: string;
241
+ type: string;
242
+ file: string;
243
+ line: number;
244
+ snippet: string;
245
+ usedIn: Array<{
246
+ file: string;
247
+ line: number;
248
+ snippet: string;
249
+ }>;
250
+ calls: string[];
251
+ calledBy: string[];
252
+ confidence: number;
253
+ }>;
254
+ relevantFiles: Array<{
255
+ path: string;
256
+ relevance: number;
257
+ snippet?: string;
258
+ }>;
259
+ issues: Array<{
260
+ type: 'bug' | 'warning' | 'smell' | 'security';
261
+ severity: 'critical' | 'high' | 'medium' | 'low';
262
+ file: string;
263
+ line: number;
264
+ message: string;
265
+ snippet: string;
266
+ suggestion: string;
267
+ }>;
268
+ dependencies: Array<{
269
+ from: string;
270
+ to: string;
271
+ type: string;
272
+ }>;
273
+ projectStats: {
274
+ totalFiles: number;
275
+ totalSymbols: number;
276
+ languages: string[];
277
+ framework?: string;
278
+ };
279
+ };
236
280
  }
237
281
  export interface LLMResponse {
238
282
  content: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "archicore",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "AI Software Architect - code analysis, impact prediction, semantic search",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",