archicore 0.3.7 → 0.3.9
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/cli/commands/interactive.js +41 -14
- package/dist/cli.js +4 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +19 -9
- package/dist/orchestrator/index.js +72 -7
- package/dist/server/routes/device-auth.js +6 -1
- package/dist/server/services/project-service.js +15 -6
- package/dist/types/index.d.ts +9 -0
- package/dist/utils/project-analyzer.d.ts +13 -0
- package/dist/utils/project-analyzer.js +130 -0
- package/package.json +1 -1
|
@@ -600,7 +600,7 @@ async function handleIndexCommand() {
|
|
|
600
600
|
const codeIndex = new CodeIndex(state.projectPath);
|
|
601
601
|
// Проверяем, является ли проект bundled (содержит source maps)
|
|
602
602
|
const isBundled = await codeIndex.isBundledProject();
|
|
603
|
-
let asts;
|
|
603
|
+
let asts = new Map();
|
|
604
604
|
let virtualFileContents = [];
|
|
605
605
|
if (isBundled) {
|
|
606
606
|
// Извлекаем исходники из source maps
|
|
@@ -608,17 +608,31 @@ async function handleIndexCommand() {
|
|
|
608
608
|
const extractionResult = await codeIndex.extractFromSourceMaps();
|
|
609
609
|
if (extractionResult.files.length > 0) {
|
|
610
610
|
indexSpinner.update(`Extracted ${extractionResult.files.length} files from source maps, parsing...`);
|
|
611
|
-
// Парсим виртуальные файлы
|
|
611
|
+
// Парсим виртуальные файлы из source maps
|
|
612
612
|
asts = codeIndex.parseVirtualFiles(extractionResult.files);
|
|
613
613
|
// Сохраняем содержимое виртуальных файлов для загрузки
|
|
614
614
|
virtualFileContents = extractionResult.files.map(f => [f.path, f.content]);
|
|
615
|
-
indexSpinner.update(`Parsed ${asts.size} files from source maps
|
|
615
|
+
indexSpinner.update(`Parsed ${asts.size} virtual files from source maps`);
|
|
616
616
|
}
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
617
|
+
// ВАЖНО: Также парсим PHP/Python/другие backend файлы (не JS бандлы)
|
|
618
|
+
indexSpinner.update('Also parsing backend files (PHP, Python, etc.)...');
|
|
619
|
+
const backendAsts = await codeIndex.parseProject();
|
|
620
|
+
// Объединяем: source maps + backend файлы (исключая bundled JS)
|
|
621
|
+
let addedBackendFiles = 0;
|
|
622
|
+
for (const [path, ast] of backendAsts) {
|
|
623
|
+
// Пропускаем bundled JS файлы - они уже извлечены из source maps
|
|
624
|
+
const isBundledJs = path.match(/\.(min\.js|bundle\.js)$/) ||
|
|
625
|
+
path.match(/\/(dist|build|vendor)\//i) ||
|
|
626
|
+
path.match(/\.(js|ts)$/) && path.match(/\d+\.[a-f0-9]+\.(js|ts)$/i);
|
|
627
|
+
if (!isBundledJs && !asts.has(path)) {
|
|
628
|
+
asts.set(path, ast);
|
|
629
|
+
addedBackendFiles++;
|
|
630
|
+
}
|
|
621
631
|
}
|
|
632
|
+
if (addedBackendFiles > 0) {
|
|
633
|
+
indexSpinner.update(`Added ${addedBackendFiles} backend files (PHP, Python, etc.)`);
|
|
634
|
+
}
|
|
635
|
+
indexSpinner.update(`Total: ${asts.size} files (source maps + backend)`);
|
|
622
636
|
}
|
|
623
637
|
else {
|
|
624
638
|
// Обычный проект - парсим файлы напрямую
|
|
@@ -635,25 +649,38 @@ async function handleIndexCommand() {
|
|
|
635
649
|
const isLargeProject = symbols.size > 50000 || asts.size > 1000;
|
|
636
650
|
// Читаем содержимое файлов (с оптимизацией для больших проектов)
|
|
637
651
|
let fileContents = [];
|
|
638
|
-
|
|
652
|
+
const virtualFilePaths = new Set(virtualFileContents.map(([path]) => path));
|
|
653
|
+
// Добавляем виртуальные файлы из source maps
|
|
639
654
|
if (virtualFileContents.length > 0) {
|
|
640
|
-
indexSpinner.update('Using extracted source files...');
|
|
641
|
-
fileContents = virtualFileContents;
|
|
655
|
+
indexSpinner.update('Using extracted source files from source maps...');
|
|
656
|
+
fileContents = [...virtualFileContents];
|
|
642
657
|
}
|
|
643
|
-
|
|
644
|
-
|
|
658
|
+
// Также читаем backend файлы (PHP, Python и т.д.) которых нет в source maps
|
|
659
|
+
if (!isLargeProject) {
|
|
660
|
+
indexSpinner.update('Reading backend file contents (PHP, Python, etc.)...');
|
|
661
|
+
let backendFilesRead = 0;
|
|
645
662
|
for (const [filePath] of asts) {
|
|
663
|
+
// Пропускаем файлы которые уже есть из source maps
|
|
664
|
+
if (virtualFilePaths.has(filePath))
|
|
665
|
+
continue;
|
|
666
|
+
// Пропускаем bundled JS файлы
|
|
667
|
+
if (filePath.match(/\d+\.[a-f0-9]+\.(js|ts)$/i))
|
|
668
|
+
continue;
|
|
646
669
|
try {
|
|
647
670
|
const fullPath = pathModule.default.isAbsolute(filePath)
|
|
648
671
|
? filePath
|
|
649
672
|
: pathModule.default.join(state.projectPath, filePath);
|
|
650
673
|
const content = await fs.readFile(fullPath, 'utf-8');
|
|
651
674
|
fileContents.push([filePath, content]);
|
|
675
|
+
backendFilesRead++;
|
|
652
676
|
}
|
|
653
677
|
catch {
|
|
654
678
|
// Игнорируем ошибки чтения отдельных файлов
|
|
655
679
|
}
|
|
656
680
|
}
|
|
681
|
+
if (backendFilesRead > 0) {
|
|
682
|
+
indexSpinner.update(`Read ${backendFilesRead} additional backend files`);
|
|
683
|
+
}
|
|
657
684
|
}
|
|
658
685
|
else {
|
|
659
686
|
indexSpinner.update('Large project detected, skipping file contents for faster upload...');
|
|
@@ -868,7 +895,7 @@ async function handleAnalyzeCommand(args) {
|
|
|
868
895
|
async function handleSearchCommand(query) {
|
|
869
896
|
if (!state.projectId) {
|
|
870
897
|
printError('No project selected');
|
|
871
|
-
printInfo('Use /
|
|
898
|
+
printInfo('Use /index first to register the project');
|
|
872
899
|
return;
|
|
873
900
|
}
|
|
874
901
|
if (!query) {
|
|
@@ -915,7 +942,7 @@ async function handleSearchCommand(query) {
|
|
|
915
942
|
async function handleQuery(query) {
|
|
916
943
|
if (!state.projectId) {
|
|
917
944
|
printError('No project selected');
|
|
918
|
-
printInfo('Use /
|
|
945
|
+
printInfo('Use /index first to register the project');
|
|
919
946
|
return;
|
|
920
947
|
}
|
|
921
948
|
// Save user message to history
|
package/dist/cli.js
CHANGED
|
@@ -56,7 +56,7 @@ program
|
|
|
56
56
|
provider: 'deepseek',
|
|
57
57
|
model: 'deepseek-chat',
|
|
58
58
|
temperature: 0.1,
|
|
59
|
-
maxTokens:
|
|
59
|
+
maxTokens: 8192
|
|
60
60
|
},
|
|
61
61
|
vectorStore: {
|
|
62
62
|
url: process.env.QDRANT_URL || 'http://localhost:6333',
|
|
@@ -107,7 +107,7 @@ program
|
|
|
107
107
|
provider: 'deepseek',
|
|
108
108
|
model: 'deepseek-chat',
|
|
109
109
|
temperature: 0.1,
|
|
110
|
-
maxTokens:
|
|
110
|
+
maxTokens: 8192
|
|
111
111
|
},
|
|
112
112
|
vectorStore: {
|
|
113
113
|
url: process.env.QDRANT_URL || 'http://localhost:6333',
|
|
@@ -192,7 +192,7 @@ program
|
|
|
192
192
|
provider: 'deepseek',
|
|
193
193
|
model: 'deepseek-chat',
|
|
194
194
|
temperature: 0.1,
|
|
195
|
-
maxTokens:
|
|
195
|
+
maxTokens: 8192
|
|
196
196
|
},
|
|
197
197
|
vectorStore: {
|
|
198
198
|
url: process.env.QDRANT_URL || 'http://localhost:6333',
|
|
@@ -230,7 +230,7 @@ program
|
|
|
230
230
|
provider: 'deepseek',
|
|
231
231
|
model: 'deepseek-chat',
|
|
232
232
|
temperature: 0.3,
|
|
233
|
-
maxTokens:
|
|
233
|
+
maxTokens: 8192
|
|
234
234
|
},
|
|
235
235
|
vectorStore: {
|
|
236
236
|
url: process.env.QDRANT_URL || 'http://localhost:6333',
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import { ImpactEngine } from './impact-engine/index.js';
|
|
|
10
10
|
import { LLMOrchestrator } from './orchestrator/index.js';
|
|
11
11
|
import { Logger } from './utils/logger.js';
|
|
12
12
|
import { config } from 'dotenv';
|
|
13
|
+
import { analyzeProjectStack } from './utils/project-analyzer.js';
|
|
13
14
|
config();
|
|
14
15
|
export class AIArhitector {
|
|
15
16
|
codeIndex;
|
|
@@ -17,6 +18,7 @@ export class AIArhitector {
|
|
|
17
18
|
architecture;
|
|
18
19
|
impactEngine;
|
|
19
20
|
orchestrator;
|
|
21
|
+
projectMetadata = null;
|
|
20
22
|
config;
|
|
21
23
|
initialized = false;
|
|
22
24
|
constructor(config) {
|
|
@@ -38,6 +40,8 @@ export class AIArhitector {
|
|
|
38
40
|
return;
|
|
39
41
|
}
|
|
40
42
|
Logger.info('Initializing AIArhitector...');
|
|
43
|
+
// Анализируем стек технологий проекта
|
|
44
|
+
this.projectMetadata = analyzeProjectStack(this.config.rootDir);
|
|
41
45
|
await this.codeIndex.indexProject(this.config.rootDir);
|
|
42
46
|
await this.semanticMemory.initialize();
|
|
43
47
|
await this.semanticMemory.indexSymbols(this.codeIndex.getSymbols(), this.codeIndex.getASTs());
|
|
@@ -53,12 +57,14 @@ export class AIArhitector {
|
|
|
53
57
|
throw new Error('Dependency graph not built');
|
|
54
58
|
}
|
|
55
59
|
const impact = this.impactEngine.analyzeChange(change, graph, this.codeIndex.getSymbols(), this.architecture.getModel());
|
|
56
|
-
const semanticContext = await this.semanticMemory.searchByQuery(change.description, 5
|
|
60
|
+
const semanticContext = await this.semanticMemory.searchByQuery(change.description, 30 // Увеличено с 5 до 30
|
|
61
|
+
);
|
|
57
62
|
const llmAnalysis = await this.orchestrator.analyzeImpact(impact, {
|
|
58
63
|
architecture: this.architecture.getModel(),
|
|
59
64
|
semanticMemory: semanticContext,
|
|
60
65
|
codeIndex: graph,
|
|
61
|
-
recentChanges: [change]
|
|
66
|
+
recentChanges: [change],
|
|
67
|
+
projectMetadata: this.projectMetadata || undefined
|
|
62
68
|
});
|
|
63
69
|
Logger.info('LLM Analysis:\n' + llmAnalysis);
|
|
64
70
|
return impact;
|
|
@@ -69,30 +75,33 @@ export class AIArhitector {
|
|
|
69
75
|
}
|
|
70
76
|
async askQuestion(question) {
|
|
71
77
|
this.ensureInitialized();
|
|
72
|
-
const semanticContext = await this.semanticMemory.searchByQuery(question,
|
|
78
|
+
const semanticContext = await this.semanticMemory.searchByQuery(question, 30); // Увеличено с 10 до 30
|
|
73
79
|
const answer = await this.orchestrator.answerArchitecturalQuestion(question, {
|
|
74
80
|
architecture: this.architecture.getModel(),
|
|
75
81
|
semanticMemory: semanticContext,
|
|
76
|
-
codeIndex: this.codeIndex.getGraph() || undefined
|
|
82
|
+
codeIndex: this.codeIndex.getGraph() || undefined,
|
|
83
|
+
projectMetadata: this.projectMetadata || undefined
|
|
77
84
|
});
|
|
78
85
|
return answer;
|
|
79
86
|
}
|
|
80
87
|
async suggestRefactoring(code, goal) {
|
|
81
88
|
this.ensureInitialized();
|
|
82
|
-
const semanticContext = await this.semanticMemory.searchSimilarCode(code, {},
|
|
89
|
+
const semanticContext = await this.semanticMemory.searchSimilarCode(code, {}, 30); // Увеличено с 5 до 30
|
|
83
90
|
const suggestion = await this.orchestrator.suggestRefactoring(code, goal, {
|
|
84
91
|
architecture: this.architecture.getModel(),
|
|
85
|
-
semanticMemory: semanticContext
|
|
92
|
+
semanticMemory: semanticContext,
|
|
93
|
+
projectMetadata: this.projectMetadata || undefined
|
|
86
94
|
});
|
|
87
95
|
return suggestion;
|
|
88
96
|
}
|
|
89
97
|
async reviewCode(code, changedFiles) {
|
|
90
98
|
this.ensureInitialized();
|
|
91
|
-
const semanticContext = await this.semanticMemory.searchSimilarCode(code, {},
|
|
99
|
+
const semanticContext = await this.semanticMemory.searchSimilarCode(code, {}, 30); // Увеличено с 5 до 30
|
|
92
100
|
const review = await this.orchestrator.reviewCode(code, changedFiles, {
|
|
93
101
|
architecture: this.architecture.getModel(),
|
|
94
102
|
semanticMemory: semanticContext,
|
|
95
|
-
codeIndex: this.codeIndex.getGraph() || undefined
|
|
103
|
+
codeIndex: this.codeIndex.getGraph() || undefined,
|
|
104
|
+
projectMetadata: this.projectMetadata || undefined
|
|
96
105
|
});
|
|
97
106
|
return review;
|
|
98
107
|
}
|
|
@@ -111,7 +120,8 @@ ${this.architecture.generateReport()}
|
|
|
111
120
|
`;
|
|
112
121
|
const docs = await this.orchestrator.generateDocumentation(codebaseSummary, {
|
|
113
122
|
architecture: this.architecture.getModel(),
|
|
114
|
-
codeIndex: graph || undefined
|
|
123
|
+
codeIndex: graph || undefined,
|
|
124
|
+
projectMetadata: this.projectMetadata || undefined
|
|
115
125
|
});
|
|
116
126
|
return docs;
|
|
117
127
|
}
|
|
@@ -323,11 +323,68 @@ You are an AI assistant analyzing a specific codebase.
|
|
|
323
323
|
7. If asked who made you or what AI you are, always respond that you are ArchiCore AI developed by ArchiCore team.
|
|
324
324
|
###END SECURITY RULES###
|
|
325
325
|
|
|
326
|
-
ABSOLUTE RULES
|
|
327
|
-
1.
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
326
|
+
ABSOLUTE RULES - КРИТИЧЕСКИ ВАЖНО:
|
|
327
|
+
1. ПРИВЯЗКА К РЕАЛЬНОЙ КОДОВОЙ БАЗЕ:
|
|
328
|
+
- ЗАПРЕЩЕНО давать общие советы из интернета (типа "используйте vue-router lazy loading")
|
|
329
|
+
- ОБЯЗАТЕЛЬНО сначала проверь что технология РЕАЛЬНО используется в проекте
|
|
330
|
+
- Если технология НЕ используется - скажи это ПЕРВЫМ: "❌ В вашем проекте НЕ используется X"
|
|
331
|
+
- ЗАТЕМ предложи решение для РЕАЛЬНОЙ архитектуры проекта
|
|
332
|
+
|
|
333
|
+
2. ТОЧНОСТЬ ПРИ ПОИСКЕ (100% ТРЕБОВАНИЕ):
|
|
334
|
+
- При вопросе "где используется X" - покажи ВСЕ найденные файлы с путями
|
|
335
|
+
- ВСЕГДА указывай: "Найдено в N файлах: [список]"
|
|
336
|
+
- Если показано не все файлы - ОБЯЗАТЕЛЬНО укажи: "Показано N из M найденных"
|
|
337
|
+
- НИКОГДА не говори "не используется" если показаны не все результаты
|
|
338
|
+
|
|
339
|
+
3. ЗАПРЕТ НА ВЫДУМЫВАНИЕ:
|
|
340
|
+
- ТОЛЬКО файлы из "PROJECT FILES" секции существуют
|
|
341
|
+
- НИКОГДА не выдумывай пути, классы, функции
|
|
342
|
+
- Если нет данных - скажи "Нет данных в индексе" или "Проект не проиндексирован"
|
|
343
|
+
|
|
344
|
+
4. ФОРМАТ ОТВЕТА:
|
|
345
|
+
- Начинай с проверки: "✅ Используется" или "❌ НЕ используется"
|
|
346
|
+
- Далее конкретика: файлы, строки, примеры ТОЛЬКО из PROJECT FILES
|
|
347
|
+
- В конце: рекомендации для КОНКРЕТНОЙ архитектуры проекта
|
|
348
|
+
|
|
349
|
+
Примеры ПРАВИЛЬНЫХ ответов:
|
|
350
|
+
Q: "Как оптимизировать vue-router?"
|
|
351
|
+
A: "❌ В вашем проекте НЕ используется vue-router.
|
|
352
|
+
Ваш стек: vanilla JS + Express.
|
|
353
|
+
Для оптимизации загрузки рекомендую:
|
|
354
|
+
[конкретные советы для вашей архитектуры]"
|
|
355
|
+
|
|
356
|
+
Q: "Где используется компонент Comments?"
|
|
357
|
+
A: "✅ Компонент Comments найден в 3 файлах:
|
|
358
|
+
1. src/pages/Post.vue:45
|
|
359
|
+
2. src/pages/Article.vue:89
|
|
360
|
+
3. src/components/Feed.vue:120
|
|
361
|
+
Показано 3 из 3 найденных файлов."`;
|
|
362
|
+
// Добавляем метаданные проекта (стек технологий)
|
|
363
|
+
if (context?.projectMetadata) {
|
|
364
|
+
prompt += '\n\n###PROJECT STACK (РЕАЛЬНЫЕ технологии проекта)###\n';
|
|
365
|
+
if (context.projectMetadata.framework) {
|
|
366
|
+
prompt += `Frontend Framework: ${context.projectMetadata.framework}\n`;
|
|
367
|
+
}
|
|
368
|
+
if (context.projectMetadata.backend) {
|
|
369
|
+
prompt += `Backend: ${context.projectMetadata.backend}\n`;
|
|
370
|
+
}
|
|
371
|
+
if (context.projectMetadata.database) {
|
|
372
|
+
prompt += `Database: ${context.projectMetadata.database}\n`;
|
|
373
|
+
}
|
|
374
|
+
if (context.projectMetadata.buildTool) {
|
|
375
|
+
prompt += `Build Tool: ${context.projectMetadata.buildTool}\n`;
|
|
376
|
+
}
|
|
377
|
+
// Ключевые зависимости
|
|
378
|
+
if (context.projectMetadata.dependencies) {
|
|
379
|
+
const keyDeps = Object.keys(context.projectMetadata.dependencies).filter(dep => ['vue', 'react', 'angular', 'svelte', 'express', 'fastify', 'nest'].some(key => dep.includes(key)));
|
|
380
|
+
if (keyDeps.length > 0) {
|
|
381
|
+
prompt += `Key Dependencies: ${keyDeps.join(', ')}\n`;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
prompt += '\n⚠️ ИСПОЛЬЗУЙ ТОЛЬКО ЭТИ ТЕХНОЛОГИИ в советах!\n';
|
|
385
|
+
prompt += '⚠️ Если спрашивают про технологию которой нет выше - скажи что НЕ используется!\n';
|
|
386
|
+
prompt += '###END PROJECT STACK###\n';
|
|
387
|
+
}
|
|
331
388
|
if (context?.architecture?.boundedContexts && context.architecture.boundedContexts.length > 0) {
|
|
332
389
|
prompt += '\n\n**Defined Architecture:**\n';
|
|
333
390
|
for (const bc of context.architecture.boundedContexts) {
|
|
@@ -335,8 +392,16 @@ ABSOLUTE RULES:
|
|
|
335
392
|
}
|
|
336
393
|
}
|
|
337
394
|
if (context?.semanticMemory && context.semanticMemory.length > 0) {
|
|
338
|
-
|
|
339
|
-
|
|
395
|
+
const totalResults = context.semanticMemory.length;
|
|
396
|
+
const maxResults = Math.min(totalResults, 30); // Увеличено с 10 до 30
|
|
397
|
+
prompt += `\n\n###PROJECT FILES (${maxResults} из ${totalResults} найденных)###\n`;
|
|
398
|
+
prompt += `⚠️ ВНИМАНИЕ: Показаны только ${maxResults} наиболее релевантных файлов из ${totalResults}.\n`;
|
|
399
|
+
if (totalResults > maxResults) {
|
|
400
|
+
prompt += `⚠️ КРИТИЧНО: Есть еще ${totalResults - maxResults} файлов, которые могут содержать искомое.\n`;
|
|
401
|
+
prompt += `⚠️ ПРИ ОТВЕТЕ ОБЯЗАТЕЛЬНО УКАЖИ: "Найдено в ${maxResults} файлах, возможно есть еще в ${totalResults - maxResults} файлах"\n`;
|
|
402
|
+
}
|
|
403
|
+
prompt += '\n';
|
|
404
|
+
for (const result of context.semanticMemory.slice(0, maxResults)) {
|
|
340
405
|
const cleanPath = sanitizePath(result.chunk.metadata.filePath);
|
|
341
406
|
prompt += `\nФайл: ${cleanPath}\n`;
|
|
342
407
|
prompt += `${result.context}\n`;
|
|
@@ -136,7 +136,12 @@ router.get('/verify/:userCode', (req, res) => {
|
|
|
136
136
|
* Authorize device (called from web after user logs in)
|
|
137
137
|
*/
|
|
138
138
|
router.post('/authorize', async (req, res) => {
|
|
139
|
-
const { userCode, userId,
|
|
139
|
+
const { userCode, userId, action } = req.body;
|
|
140
|
+
// Get access token from cookie or Authorization header (not from body!)
|
|
141
|
+
// Web frontend sends credentials: 'include' which sends the httpOnly cookie
|
|
142
|
+
const accessToken = req.cookies?.archicore_token ||
|
|
143
|
+
req.headers.authorization?.substring(7) ||
|
|
144
|
+
req.body.accessToken; // Fallback to body for backwards compatibility
|
|
140
145
|
if (!userCode) {
|
|
141
146
|
res.status(400).json({ error: 'invalid_request', message: 'Missing user_code' });
|
|
142
147
|
return;
|
|
@@ -178,7 +178,7 @@ export class ProjectService {
|
|
|
178
178
|
provider: 'deepseek',
|
|
179
179
|
model: process.env.DEEPSEEK_MODEL || 'deepseek-chat',
|
|
180
180
|
temperature: 0.1,
|
|
181
|
-
maxTokens:
|
|
181
|
+
maxTokens: 8192,
|
|
182
182
|
baseURL: 'https://api.deepseek.com'
|
|
183
183
|
};
|
|
184
184
|
// SemanticMemory - поддержка Jina (бесплатно) или OpenAI
|
|
@@ -420,12 +420,20 @@ export class ProjectService {
|
|
|
420
420
|
}));
|
|
421
421
|
}
|
|
422
422
|
async askArchitect(projectId, question, language = 'en') {
|
|
423
|
+
const project = this.projects.get(projectId);
|
|
424
|
+
if (!project) {
|
|
425
|
+
throw new Error(`Project not found: ${projectId}`);
|
|
426
|
+
}
|
|
423
427
|
const data = await this.getProjectData(projectId);
|
|
428
|
+
// Анализируем стек технологий проекта
|
|
429
|
+
const { analyzeProjectStack } = await import('../../utils/project-analyzer.js');
|
|
430
|
+
const projectMetadata = analyzeProjectStack(project.path);
|
|
424
431
|
// Ищем релевантный контекст (если есть семантическая память)
|
|
425
432
|
let searchResults = [];
|
|
426
433
|
if (data.semanticMemory) {
|
|
427
434
|
try {
|
|
428
|
-
|
|
435
|
+
// Увеличиваем лимит результатов поиска с 5 до 30
|
|
436
|
+
searchResults = await data.semanticMemory.searchByQuery(question, 30);
|
|
429
437
|
Logger.debug(`Semantic search returned ${searchResults.length} results`);
|
|
430
438
|
}
|
|
431
439
|
catch (err) {
|
|
@@ -435,7 +443,7 @@ export class ProjectService {
|
|
|
435
443
|
// Fallback: если семантический поиск не работает, даём контекст из графа
|
|
436
444
|
if (searchResults.length === 0 && data.graph) {
|
|
437
445
|
Logger.info('Using graph fallback for context');
|
|
438
|
-
const files = Array.from(data.graph.nodes.values()).slice(0,
|
|
446
|
+
const files = Array.from(data.graph.nodes.values()).slice(0, 30); // Увеличено с 10 до 30
|
|
439
447
|
for (const file of files) {
|
|
440
448
|
searchResults.push({
|
|
441
449
|
chunk: {
|
|
@@ -458,7 +466,7 @@ export class ProjectService {
|
|
|
458
466
|
}
|
|
459
467
|
// Добавляем информацию о символах если есть
|
|
460
468
|
if (data.symbols && data.symbols.size > 0) {
|
|
461
|
-
const symbolsList = Array.from(data.symbols.values()).slice(0,
|
|
469
|
+
const symbolsList = Array.from(data.symbols.values()).slice(0, 50); // Увеличено с 20 до 50
|
|
462
470
|
for (const sym of symbolsList) {
|
|
463
471
|
const existing = searchResults.find(r => r.chunk.metadata.filePath === sym.filePath);
|
|
464
472
|
if (existing) {
|
|
@@ -466,11 +474,12 @@ export class ProjectService {
|
|
|
466
474
|
}
|
|
467
475
|
}
|
|
468
476
|
}
|
|
469
|
-
Logger.debug(`Passing ${searchResults.length} context items to AI`);
|
|
477
|
+
Logger.debug(`Passing ${searchResults.length} context items + project metadata to AI`);
|
|
470
478
|
const answer = await data.orchestrator.answerArchitecturalQuestion(question, {
|
|
471
479
|
architecture: data.architecture.getModel(),
|
|
472
480
|
semanticMemory: searchResults,
|
|
473
|
-
language
|
|
481
|
+
language,
|
|
482
|
+
projectMetadata: projectMetadata || undefined
|
|
474
483
|
});
|
|
475
484
|
return answer;
|
|
476
485
|
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -218,12 +218,21 @@ export interface LLMPrompt {
|
|
|
218
218
|
user: string;
|
|
219
219
|
context?: LLMContext;
|
|
220
220
|
}
|
|
221
|
+
export interface ProjectMetadata {
|
|
222
|
+
framework?: string;
|
|
223
|
+
dependencies?: Record<string, string>;
|
|
224
|
+
devDependencies?: Record<string, string>;
|
|
225
|
+
buildTool?: string;
|
|
226
|
+
backend?: string;
|
|
227
|
+
database?: string;
|
|
228
|
+
}
|
|
221
229
|
export interface LLMContext {
|
|
222
230
|
codeIndex?: Partial<DependencyGraph>;
|
|
223
231
|
semanticMemory?: SemanticSearchResult[];
|
|
224
232
|
architecture?: Partial<ArchitectureModel>;
|
|
225
233
|
recentChanges?: Change[];
|
|
226
234
|
language?: 'en' | 'ru';
|
|
235
|
+
projectMetadata?: ProjectMetadata;
|
|
227
236
|
}
|
|
228
237
|
export interface LLMResponse {
|
|
229
238
|
content: string;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Analyzer - анализ package.json для определения стека технологий
|
|
3
|
+
*/
|
|
4
|
+
import { ProjectMetadata } from '../types/index.js';
|
|
5
|
+
/**
|
|
6
|
+
* Анализирует package.json проекта и определяет используемые технологии
|
|
7
|
+
*/
|
|
8
|
+
export declare function analyzeProjectStack(projectPath: string): ProjectMetadata | null;
|
|
9
|
+
/**
|
|
10
|
+
* Создает человекочитаемое описание стека проекта
|
|
11
|
+
*/
|
|
12
|
+
export declare function describeProjectStack(metadata: ProjectMetadata | null): string;
|
|
13
|
+
//# sourceMappingURL=project-analyzer.d.ts.map
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Analyzer - анализ package.json для определения стека технологий
|
|
3
|
+
*/
|
|
4
|
+
import { readFileSync, existsSync } from 'fs';
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
import { Logger } from './logger.js';
|
|
7
|
+
/**
|
|
8
|
+
* Анализирует package.json проекта и определяет используемые технологии
|
|
9
|
+
*/
|
|
10
|
+
export function analyzeProjectStack(projectPath) {
|
|
11
|
+
try {
|
|
12
|
+
const packageJsonPath = join(projectPath, 'package.json');
|
|
13
|
+
if (!existsSync(packageJsonPath)) {
|
|
14
|
+
Logger.warn('package.json not found in project');
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
18
|
+
const deps = packageJson.dependencies || {};
|
|
19
|
+
const devDeps = packageJson.devDependencies || {};
|
|
20
|
+
const allDeps = { ...deps, ...devDeps };
|
|
21
|
+
const metadata = {
|
|
22
|
+
dependencies: deps,
|
|
23
|
+
devDependencies: devDeps
|
|
24
|
+
};
|
|
25
|
+
// Определяем frontend framework
|
|
26
|
+
if (allDeps['vue'] || allDeps['@vue/cli']) {
|
|
27
|
+
metadata.framework = 'Vue.js';
|
|
28
|
+
}
|
|
29
|
+
else if (allDeps['react'] || allDeps['react-dom']) {
|
|
30
|
+
metadata.framework = 'React';
|
|
31
|
+
}
|
|
32
|
+
else if (allDeps['@angular/core']) {
|
|
33
|
+
metadata.framework = 'Angular';
|
|
34
|
+
}
|
|
35
|
+
else if (allDeps['svelte']) {
|
|
36
|
+
metadata.framework = 'Svelte';
|
|
37
|
+
}
|
|
38
|
+
else if (allDeps['next']) {
|
|
39
|
+
metadata.framework = 'Next.js (React)';
|
|
40
|
+
}
|
|
41
|
+
else if (allDeps['nuxt']) {
|
|
42
|
+
metadata.framework = 'Nuxt.js (Vue)';
|
|
43
|
+
}
|
|
44
|
+
else if (allDeps['@remix-run/react']) {
|
|
45
|
+
metadata.framework = 'Remix (React)';
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
// Проверяем есть ли хоть что-то frontend
|
|
49
|
+
const hasFrontend = ['jquery', 'bootstrap', 'tailwindcss', '@mui/material'].some(dep => allDeps[dep]);
|
|
50
|
+
metadata.framework = hasFrontend ? 'Vanilla JS (no framework)' : 'None (backend only)';
|
|
51
|
+
}
|
|
52
|
+
// Определяем backend framework
|
|
53
|
+
if (allDeps['express']) {
|
|
54
|
+
metadata.backend = 'Express';
|
|
55
|
+
}
|
|
56
|
+
else if (allDeps['fastify']) {
|
|
57
|
+
metadata.backend = 'Fastify';
|
|
58
|
+
}
|
|
59
|
+
else if (allDeps['@nestjs/core']) {
|
|
60
|
+
metadata.backend = 'NestJS';
|
|
61
|
+
}
|
|
62
|
+
else if (allDeps['koa']) {
|
|
63
|
+
metadata.backend = 'Koa';
|
|
64
|
+
}
|
|
65
|
+
else if (allDeps['hapi']) {
|
|
66
|
+
metadata.backend = 'Hapi';
|
|
67
|
+
}
|
|
68
|
+
// Определяем database
|
|
69
|
+
if (allDeps['pg'] || allDeps['postgres']) {
|
|
70
|
+
metadata.database = 'PostgreSQL';
|
|
71
|
+
}
|
|
72
|
+
else if (allDeps['mongodb'] || allDeps['mongoose']) {
|
|
73
|
+
metadata.database = 'MongoDB';
|
|
74
|
+
}
|
|
75
|
+
else if (allDeps['mysql'] || allDeps['mysql2']) {
|
|
76
|
+
metadata.database = 'MySQL';
|
|
77
|
+
}
|
|
78
|
+
else if (allDeps['sqlite3'] || allDeps['better-sqlite3']) {
|
|
79
|
+
metadata.database = 'SQLite';
|
|
80
|
+
}
|
|
81
|
+
else if (allDeps['redis'] || allDeps['ioredis']) {
|
|
82
|
+
metadata.database = 'Redis';
|
|
83
|
+
}
|
|
84
|
+
// Определяем build tool
|
|
85
|
+
if (allDeps['vite'] || devDeps['vite']) {
|
|
86
|
+
metadata.buildTool = 'Vite';
|
|
87
|
+
}
|
|
88
|
+
else if (allDeps['webpack'] || devDeps['webpack']) {
|
|
89
|
+
metadata.buildTool = 'Webpack';
|
|
90
|
+
}
|
|
91
|
+
else if (allDeps['rollup'] || devDeps['rollup']) {
|
|
92
|
+
metadata.buildTool = 'Rollup';
|
|
93
|
+
}
|
|
94
|
+
else if (allDeps['parcel'] || devDeps['parcel']) {
|
|
95
|
+
metadata.buildTool = 'Parcel';
|
|
96
|
+
}
|
|
97
|
+
else if (allDeps['esbuild'] || devDeps['esbuild']) {
|
|
98
|
+
metadata.buildTool = 'esbuild';
|
|
99
|
+
}
|
|
100
|
+
Logger.info(`Project stack detected: ${metadata.framework || 'Unknown'} + ${metadata.backend || 'Unknown'}`);
|
|
101
|
+
return metadata;
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
Logger.error('Failed to analyze project stack:', error);
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Создает человекочитаемое описание стека проекта
|
|
110
|
+
*/
|
|
111
|
+
export function describeProjectStack(metadata) {
|
|
112
|
+
if (!metadata) {
|
|
113
|
+
return 'Unknown stack (package.json not found)';
|
|
114
|
+
}
|
|
115
|
+
const parts = [];
|
|
116
|
+
if (metadata.framework && metadata.framework !== 'None (backend only)') {
|
|
117
|
+
parts.push(`Frontend: ${metadata.framework}`);
|
|
118
|
+
}
|
|
119
|
+
if (metadata.backend) {
|
|
120
|
+
parts.push(`Backend: ${metadata.backend}`);
|
|
121
|
+
}
|
|
122
|
+
if (metadata.database) {
|
|
123
|
+
parts.push(`Database: ${metadata.database}`);
|
|
124
|
+
}
|
|
125
|
+
if (metadata.buildTool) {
|
|
126
|
+
parts.push(`Build: ${metadata.buildTool}`);
|
|
127
|
+
}
|
|
128
|
+
return parts.length > 0 ? parts.join(', ') : 'Minimal project (no major frameworks)';
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=project-analyzer.js.map
|