archicore 0.3.6 → 0.3.8

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.
@@ -25,6 +25,8 @@ const COMMANDS = [
25
25
  { name: 'rules', aliases: [], description: 'Check architectural rules' },
26
26
  { name: 'docs', aliases: ['documentation'], description: 'Generate architecture documentation' },
27
27
  { name: 'export', aliases: [], description: 'Export analysis results' },
28
+ { name: 'enterprise', aliases: ['ent'], description: 'Enterprise analysis for large projects (50K+ files)' },
29
+ { name: 'estimate', aliases: ['est'], description: 'Estimate project size and recommended tier' },
28
30
  { name: 'history', aliases: ['hist'], description: 'View and search conversation history' },
29
31
  { name: 'resume', aliases: [], description: 'Resume previous conversation session' },
30
32
  { name: 'status', aliases: [], description: 'Show connection and project status' },
@@ -532,6 +534,14 @@ async function handleCommand(input) {
532
534
  case 'resume':
533
535
  await handleResumeCommand(args);
534
536
  break;
537
+ case 'enterprise':
538
+ case 'ent':
539
+ await handleEnterpriseCommand(args);
540
+ break;
541
+ case 'estimate':
542
+ case 'est':
543
+ await handleEstimateCommand();
544
+ break;
535
545
  default:
536
546
  printFormattedError(`Unknown command: /${command}`, {
537
547
  suggestion: 'Use /help to see available commands',
@@ -1321,8 +1331,11 @@ async function handleDocsCommand(args) {
1321
1331
  printInfo('Use /index first');
1322
1332
  return;
1323
1333
  }
1334
+ const pathModule = await import('path');
1324
1335
  const format = args[0] || 'markdown';
1325
- const output = args[1] || `documentation.${format === 'markdown' ? 'md' : format}`;
1336
+ const filename = `ARCHITECTURE.${format === 'markdown' ? 'md' : format}`;
1337
+ // Save to project root by default, or custom path if specified
1338
+ const output = args[1] || pathModule.default.join(state.projectPath, filename);
1326
1339
  const spinner = createSpinner('Generating documentation...').start();
1327
1340
  try {
1328
1341
  const config = await loadConfig();
@@ -1339,6 +1352,7 @@ async function handleDocsCommand(args) {
1339
1352
  spinner.succeed('Documentation generated');
1340
1353
  printKeyValue('Output', output);
1341
1354
  printKeyValue('Format', format);
1355
+ printInfo(`Documentation saved to project root: ${filename}`);
1342
1356
  showTokenUsage();
1343
1357
  }
1344
1358
  catch (error) {
@@ -1510,4 +1524,209 @@ async function handleResumeCommand(args) {
1510
1524
  printError(String(error));
1511
1525
  }
1512
1526
  }
1527
+ async function handleEstimateCommand() {
1528
+ if (!state.projectId) {
1529
+ printError('No project indexed');
1530
+ printInfo('Use /index first to register the project');
1531
+ return;
1532
+ }
1533
+ const spinner = createSpinner('Analyzing project size...').start();
1534
+ try {
1535
+ const config = await loadConfig();
1536
+ const response = await apiFetch(`${config.serverUrl}/api/projects/${state.projectId}/enterprise/estimate`);
1537
+ if (!response.ok) {
1538
+ spinner.fail('Failed to estimate project');
1539
+ const errorData = await response.json().catch(() => ({}));
1540
+ printError(errorData.error || 'Unknown error');
1541
+ return;
1542
+ }
1543
+ const data = await response.json();
1544
+ spinner.succeed('Project analyzed');
1545
+ console.log();
1546
+ printSection('Project Size Estimation');
1547
+ printKeyValue('Total Files', data.totalFiles?.toLocaleString() || 'N/A');
1548
+ printKeyValue('Total Size', `${data.totalSizeMB?.toFixed(1) || 'N/A'} MB`);
1549
+ printKeyValue('Recommended Tier', colors.primary(data.recommendation?.toUpperCase() || 'STANDARD'));
1550
+ printKeyValue('Estimated Time', `~${data.estimatedTimeMinutes || 'N/A'} minutes`);
1551
+ // Language distribution
1552
+ if (data.languageDistribution && Object.keys(data.languageDistribution).length > 0) {
1553
+ console.log();
1554
+ console.log(colors.highlight(' Language Distribution:'));
1555
+ const sorted = Object.entries(data.languageDistribution)
1556
+ .sort((a, b) => b[1] - a[1])
1557
+ .slice(0, 8);
1558
+ for (const [lang, count] of sorted) {
1559
+ const percent = data.totalFiles ? ((count / data.totalFiles) * 100).toFixed(1) : '?';
1560
+ console.log(` ${lang.padEnd(12)} ${colors.muted(count.toLocaleString().padStart(6))} (${percent}%)`);
1561
+ }
1562
+ }
1563
+ // Tier recommendations
1564
+ if (data.tiers) {
1565
+ console.log();
1566
+ console.log(colors.highlight(' Available Tiers:'));
1567
+ console.log(` ${colors.success('quick')} - ${data.tiers.quick.estimatedFiles.toLocaleString()} files - Fast overview`);
1568
+ console.log(` ${colors.warning('standard')} - ${data.tiers.standard.estimatedFiles.toLocaleString()} files - Balanced analysis`);
1569
+ console.log(` ${colors.error('deep')} - ${data.tiers.deep.estimatedFiles.toLocaleString()} files - Full analysis (Enterprise)`);
1570
+ }
1571
+ console.log();
1572
+ printInfo('Use /enterprise [tier] to start analysis');
1573
+ printInfo('Example: /enterprise quick or /enterprise standard');
1574
+ }
1575
+ catch (error) {
1576
+ spinner.fail('Failed to estimate project');
1577
+ printError(String(error));
1578
+ }
1579
+ }
1580
+ async function handleEnterpriseCommand(args) {
1581
+ if (!state.projectId) {
1582
+ printError('No project indexed');
1583
+ printInfo('Use /index first to register the project');
1584
+ return;
1585
+ }
1586
+ const tier = args[0]?.toLowerCase() || 'standard';
1587
+ const strategy = args[1] || 'smart';
1588
+ if (!['quick', 'standard', 'deep'].includes(tier)) {
1589
+ printError(`Invalid tier: ${tier}`);
1590
+ printInfo('Available tiers: quick, standard, deep');
1591
+ return;
1592
+ }
1593
+ // First, get estimate to show what we're doing
1594
+ const estimateSpinner = createSpinner('Analyzing project...').start();
1595
+ try {
1596
+ const config = await loadConfig();
1597
+ // Get estimate first
1598
+ const estimateResponse = await apiFetch(`${config.serverUrl}/api/projects/${state.projectId}/enterprise/estimate`);
1599
+ if (!estimateResponse.ok) {
1600
+ estimateSpinner.fail('Failed to analyze project');
1601
+ return;
1602
+ }
1603
+ const estimate = await estimateResponse.json();
1604
+ estimateSpinner.succeed(`Project has ${estimate.totalFiles?.toLocaleString()} files`);
1605
+ // Determine files to analyze
1606
+ const tierConfig = estimate.tiers?.[tier];
1607
+ const filesToAnalyze = tierConfig?.estimatedFiles || estimate.totalFiles || 0;
1608
+ console.log();
1609
+ console.log(` ${colors.highlight('Enterprise Analysis Configuration:')}`);
1610
+ console.log(` Tier: ${colors.primary(tier.toUpperCase())}`);
1611
+ console.log(` Strategy: ${colors.muted(strategy)}`);
1612
+ console.log(` Files: ${colors.muted(filesToAnalyze.toLocaleString())} of ${estimate.totalFiles?.toLocaleString()}`);
1613
+ console.log();
1614
+ // Confirm for large projects
1615
+ if ((estimate.totalFiles || 0) > 10000 && tier !== 'quick') {
1616
+ const confirmed = await promptYesNo(`This is a large project (${estimate.totalFiles?.toLocaleString()} files). Continue with ${tier} analysis?`);
1617
+ if (!confirmed) {
1618
+ printInfo('Cancelled. Use /enterprise quick for faster analysis.');
1619
+ return;
1620
+ }
1621
+ }
1622
+ // Start enterprise indexing
1623
+ const indexSpinner = createSpinner(`Starting ${tier} analysis with ${strategy} sampling...`).start();
1624
+ // For CLI, we do local indexing with enterprise options
1625
+ const { CodeIndex } = await import('../../code-index/index.js');
1626
+ const { EnterpriseIndexer } = await import('../../server/services/enterprise-indexer.js');
1627
+ const fs = await import('fs/promises');
1628
+ const pathModule = await import('path');
1629
+ // Initialize enterprise indexer
1630
+ const enterpriseIndexer = new EnterpriseIndexer(state.projectPath, {
1631
+ tier,
1632
+ sampling: {
1633
+ enabled: true,
1634
+ maxFiles: tierConfig?.maxFiles || 5000,
1635
+ strategy: strategy,
1636
+ },
1637
+ });
1638
+ await enterpriseIndexer.initialize();
1639
+ indexSpinner.update('Selecting files to analyze...');
1640
+ const filesToIndex = await enterpriseIndexer.getFilesToIndex();
1641
+ indexSpinner.update(`Parsing ${filesToIndex.length} selected files...`);
1642
+ // Parse only selected files
1643
+ const codeIndex = new CodeIndex(state.projectPath);
1644
+ const asts = new Map();
1645
+ let parsed = 0;
1646
+ for (const filePath of filesToIndex) {
1647
+ try {
1648
+ const { ASTParser } = await import('../../code-index/ast-parser.js');
1649
+ const parser = new ASTParser();
1650
+ const ast = await parser.parseFile(filePath);
1651
+ if (ast) {
1652
+ asts.set(filePath, ast);
1653
+ parsed++;
1654
+ if (parsed % 100 === 0) {
1655
+ indexSpinner.update(`Parsed ${parsed}/${filesToIndex.length} files...`);
1656
+ }
1657
+ }
1658
+ }
1659
+ catch {
1660
+ // Skip files that can't be parsed
1661
+ }
1662
+ }
1663
+ indexSpinner.update(`Extracting symbols from ${asts.size} files...`);
1664
+ // Extract symbols
1665
+ const symbols = codeIndex.extractSymbols(asts);
1666
+ indexSpinner.update(`Found ${symbols.size} symbols, building graph...`);
1667
+ // Build dependency graph
1668
+ const graph = codeIndex.buildDependencyGraph(asts, symbols);
1669
+ // Read file contents (only for smaller selections)
1670
+ let fileContents = [];
1671
+ if (filesToIndex.length <= 2000) {
1672
+ indexSpinner.update('Reading file contents...');
1673
+ for (const [filePath] of asts) {
1674
+ try {
1675
+ const fullPath = pathModule.default.isAbsolute(filePath)
1676
+ ? filePath
1677
+ : pathModule.default.join(state.projectPath, filePath);
1678
+ const content = await fs.readFile(fullPath, 'utf-8');
1679
+ fileContents.push([filePath, content]);
1680
+ }
1681
+ catch {
1682
+ // Skip unreadable files
1683
+ }
1684
+ }
1685
+ }
1686
+ // Prepare data for upload
1687
+ const astsArray = Array.from(asts.entries());
1688
+ const symbolsArray = Array.from(symbols.entries());
1689
+ const graphData = {
1690
+ nodes: Array.from(graph.nodes.entries()),
1691
+ edges: Array.from(graph.edges.entries()),
1692
+ };
1693
+ const indexData = {
1694
+ asts: astsArray,
1695
+ symbols: symbolsArray,
1696
+ graph: graphData,
1697
+ fileContents,
1698
+ statistics: {
1699
+ totalFiles: asts.size,
1700
+ totalSymbols: symbols.size,
1701
+ },
1702
+ };
1703
+ // Upload to server
1704
+ indexSpinner.update('Uploading analysis data to server...');
1705
+ const uploadResult = await uploadIndexData(state.projectId, indexData, (progress) => {
1706
+ indexSpinner.update(progress.message);
1707
+ });
1708
+ if (!uploadResult.success) {
1709
+ indexSpinner.fail('Failed to upload analysis');
1710
+ if (uploadResult.errorDetails) {
1711
+ printError(uploadResult.errorDetails.message);
1712
+ printInfo(uploadResult.errorDetails.suggestion);
1713
+ }
1714
+ return;
1715
+ }
1716
+ indexSpinner.succeed(`Enterprise analysis complete!`);
1717
+ console.log();
1718
+ printSection('Analysis Summary');
1719
+ printKeyValue('Tier', tier.toUpperCase());
1720
+ printKeyValue('Strategy', strategy);
1721
+ printKeyValue('Files Analyzed', asts.size.toLocaleString());
1722
+ printKeyValue('Symbols Found', symbols.size.toLocaleString());
1723
+ printKeyValue('Graph Nodes', graph.nodes.size.toLocaleString());
1724
+ console.log();
1725
+ printSuccess('Project is ready for queries!');
1726
+ printInfo('Try: /search "authentication" or /metrics or /security');
1727
+ }
1728
+ catch (error) {
1729
+ printError(`Enterprise analysis failed: ${error}`);
1730
+ }
1731
+ }
1513
1732
  //# sourceMappingURL=interactive.js.map
@@ -9,11 +9,11 @@ export declare class GitHubService {
9
9
  private connections;
10
10
  private repositories;
11
11
  private initialized;
12
- private clientId;
13
- private clientSecret;
14
- private redirectUri;
15
- private webhookBaseUrl;
16
12
  constructor(dataDir?: string);
13
+ private get clientId();
14
+ private get clientSecret();
15
+ private get redirectUri();
16
+ private get webhookBaseUrl();
17
17
  private ensureInitialized;
18
18
  private saveConnections;
19
19
  private saveRepositories;
@@ -10,8 +10,19 @@ import { Logger } from '../utils/logger.js';
10
10
  const DATA_DIR = '.archicore';
11
11
  const CONNECTIONS_FILE = 'github-connections.json';
12
12
  const REPOSITORIES_FILE = 'github-repositories.json';
13
- // Encryption key (in production, use env variable)
14
- const ENCRYPTION_KEY = process.env.GITHUB_ENCRYPTION_KEY || 'archicore-github-key-32bytes!!';
13
+ // Encryption key - REQUIRED in production
14
+ function getEncryptionKey() {
15
+ const key = process.env.GITHUB_ENCRYPTION_KEY || process.env.ENCRYPTION_KEY;
16
+ if (!key) {
17
+ if (process.env.NODE_ENV === 'production') {
18
+ throw new Error('ENCRYPTION_KEY or GITHUB_ENCRYPTION_KEY is required in production');
19
+ }
20
+ // Dev-only fallback (logs warning)
21
+ console.warn('WARNING: Using dev-only encryption key. Set ENCRYPTION_KEY in production.');
22
+ return 'dev-only-github-key-32bytes!!!';
23
+ }
24
+ return key;
25
+ }
15
26
  // Singleton instance
16
27
  let instance = null;
17
28
  export class GitHubService {
@@ -19,22 +30,27 @@ export class GitHubService {
19
30
  connections = [];
20
31
  repositories = [];
21
32
  initialized = false;
22
- clientId;
23
- clientSecret;
24
- redirectUri;
25
- webhookBaseUrl;
26
33
  constructor(dataDir = DATA_DIR) {
27
34
  // Singleton pattern - return existing instance
28
35
  if (instance) {
29
36
  return instance;
30
37
  }
31
38
  this.dataDir = dataDir;
32
- this.clientId = process.env.GITHUB_CLIENT_ID || '';
33
- this.clientSecret = process.env.GITHUB_CLIENT_SECRET || '';
34
- this.redirectUri = process.env.GITHUB_REDIRECT_URI || 'http://localhost:3000/api/github/callback';
35
- this.webhookBaseUrl = process.env.ARCHICORE_WEBHOOK_URL || 'http://localhost:3000/api/github/webhook';
36
39
  instance = this;
37
40
  }
41
+ // Lazy getters for environment variables (read at runtime, not at module load)
42
+ get clientId() {
43
+ return process.env.GITHUB_CLIENT_ID || '';
44
+ }
45
+ get clientSecret() {
46
+ return process.env.GITHUB_CLIENT_SECRET || '';
47
+ }
48
+ get redirectUri() {
49
+ return process.env.GITHUB_REDIRECT_URI || process.env.GITHUB_CALLBACK_URL || 'http://localhost:3000/api/github/callback';
50
+ }
51
+ get webhookBaseUrl() {
52
+ return process.env.ARCHICORE_WEBHOOK_URL || 'http://localhost:3000/api/github/webhook';
53
+ }
38
54
  async ensureInitialized() {
39
55
  if (this.initialized)
40
56
  return;
@@ -75,7 +91,7 @@ export class GitHubService {
75
91
  // ===== ENCRYPTION =====
76
92
  encrypt(text) {
77
93
  const iv = randomBytes(16);
78
- const key = createHash('sha256').update(ENCRYPTION_KEY).digest();
94
+ const key = createHash('sha256').update(getEncryptionKey()).digest();
79
95
  const cipher = createCipheriv('aes-256-cbc', key, iv);
80
96
  let encrypted = cipher.update(text, 'utf8', 'hex');
81
97
  encrypted += cipher.final('hex');
@@ -84,7 +100,7 @@ export class GitHubService {
84
100
  decrypt(encrypted) {
85
101
  const parts = encrypted.split(':');
86
102
  const iv = Buffer.from(parts[0], 'hex');
87
- const key = createHash('sha256').update(ENCRYPTION_KEY).digest();
103
+ const key = createHash('sha256').update(getEncryptionKey()).digest();
88
104
  const decipher = createDecipheriv('aes-256-cbc', key, iv);
89
105
  let decrypted = decipher.update(parts[1], 'hex', 'utf8');
90
106
  decrypted += decipher.final('utf8');
@@ -640,7 +656,7 @@ export class GitHubService {
640
656
  const response = await fetch(url, {
641
657
  headers: {
642
658
  'Authorization': `Bearer ${accessToken}`,
643
- 'Accept': 'application/vnd.github+json',
659
+ 'Accept': 'application/vnd.github.v3+raw',
644
660
  'User-Agent': 'ArchiCore',
645
661
  'X-GitHub-Api-Version': '2022-11-28'
646
662
  },
@@ -19,8 +19,19 @@ import { Logger } from '../utils/logger.js';
19
19
  const DATA_DIR = process.env.ARCHICORE_DATA_DIR || '.archicore';
20
20
  const INSTANCES_FILE = 'gitlab-instances.json';
21
21
  const REPOSITORIES_FILE = 'gitlab-repositories.json';
22
- // Encryption key for tokens
23
- const ENCRYPTION_KEY = process.env.GITLAB_ENCRYPTION_KEY || process.env.ENCRYPTION_KEY || 'archicore-gitlab-key-32bytes!!';
22
+ // Encryption key - REQUIRED in production
23
+ function getEncryptionKey() {
24
+ const key = process.env.GITLAB_ENCRYPTION_KEY || process.env.ENCRYPTION_KEY;
25
+ if (!key) {
26
+ if (process.env.NODE_ENV === 'production') {
27
+ throw new Error('ENCRYPTION_KEY or GITLAB_ENCRYPTION_KEY is required in production');
28
+ }
29
+ // Dev-only fallback (logs warning)
30
+ console.warn('WARNING: Using dev-only encryption key. Set ENCRYPTION_KEY in production.');
31
+ return 'dev-only-gitlab-key-32bytes!!!';
32
+ }
33
+ return key;
34
+ }
24
35
  // Webhook base URL - construct from PUBLIC_URL or API_URL
25
36
  const getWebhookUrl = () => {
26
37
  // Use explicit GitLab webhook URL if set
@@ -118,7 +129,7 @@ export class GitLabService {
118
129
  // ===== ENCRYPTION =====
119
130
  encrypt(text) {
120
131
  const iv = randomBytes(16);
121
- const key = createHash('sha256').update(ENCRYPTION_KEY).digest();
132
+ const key = createHash('sha256').update(getEncryptionKey()).digest();
122
133
  const cipher = createCipheriv('aes-256-cbc', key, iv);
123
134
  let encrypted = cipher.update(text, 'utf8', 'hex');
124
135
  encrypted += cipher.final('hex');
@@ -130,7 +141,7 @@ export class GitLabService {
130
141
  if (parts.length !== 2)
131
142
  return encrypted; // Not encrypted
132
143
  const iv = Buffer.from(parts[0], 'hex');
133
- const key = createHash('sha256').update(ENCRYPTION_KEY).digest();
144
+ const key = createHash('sha256').update(getEncryptionKey()).digest();
134
145
  const decipher = createDecipheriv('aes-256-cbc', key, iv);
135
146
  let decrypted = decipher.update(parts[1], 'hex', 'utf8');
136
147
  decrypted += decipher.final('utf8');
@@ -488,19 +499,41 @@ export class GitLabService {
488
499
  throw new Error('GitLab instance not found');
489
500
  }
490
501
  const accessToken = this.decrypt(instance.accessToken);
491
- const encodedId = encodeURIComponent(String(projectId));
492
- const sha = ref || 'HEAD';
493
- const url = `${instance.instanceUrl}/api/v4/projects/${encodedId}/repository/archive.zip?sha=${encodeURIComponent(sha)}`;
494
- const response = await fetch(url, {
495
- headers: {
496
- 'PRIVATE-TOKEN': accessToken
502
+ // Get project info for default branch
503
+ const project = await this.getProject(userId, instanceId, projectId);
504
+ const branch = ref || project.default_branch || 'main';
505
+ // Use API endpoint with wget (Node.js fetch sends headers that GitLab rejects)
506
+ const apiUrl = `${instance.instanceUrl}/api/v4/projects/${encodeURIComponent(String(projectId))}/repository/archive?sha=${encodeURIComponent(branch)}`;
507
+ Logger.info(`[GitLab] Downloading from: ${apiUrl}`);
508
+ // Use wget via child_process - it works where fetch fails
509
+ const { execSync } = await import('child_process');
510
+ const { tmpdir } = await import('os');
511
+ const { join: pathJoin } = await import('path');
512
+ const { readFileSync, unlinkSync } = await import('fs');
513
+ const tempFile = pathJoin(tmpdir(), `gitlab-archive-${Date.now()}.tar.gz`);
514
+ try {
515
+ execSync(`wget -q --header="PRIVATE-TOKEN: ${accessToken}" -O "${tempFile}" "${apiUrl}"`, {
516
+ timeout: 300000, // 5 min timeout
517
+ stdio: 'pipe'
518
+ });
519
+ const buffer = readFileSync(tempFile);
520
+ Logger.info(`[GitLab] Downloaded ${buffer.length} bytes`);
521
+ // Cleanup temp file
522
+ try {
523
+ unlinkSync(tempFile);
497
524
  }
498
- });
499
- if (!response.ok) {
500
- throw new Error(`Failed to download repository: ${response.status}`);
525
+ catch { }
526
+ return buffer;
527
+ }
528
+ catch (error) {
529
+ // Cleanup on error
530
+ try {
531
+ unlinkSync(tempFile);
532
+ }
533
+ catch { }
534
+ Logger.error(`[GitLab] wget failed: ${error}`);
535
+ throw new Error(`Failed to download repository: wget failed`);
501
536
  }
502
- const arrayBuffer = await response.arrayBuffer();
503
- return Buffer.from(arrayBuffer);
504
537
  }
505
538
  /**
506
539
  * Get file content from repository
package/dist/index.d.ts CHANGED
@@ -20,6 +20,7 @@ export declare class AIArhitector {
20
20
  private architecture;
21
21
  private impactEngine;
22
22
  private orchestrator;
23
+ private projectMetadata;
23
24
  private config;
24
25
  private initialized;
25
26
  constructor(config: AIArhitectorConfig);
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, 10);
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, {}, 5);
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, {}, 5);
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. ONLY USE PROVIDED DATA: You may ONLY mention files that appear in "PROJECT FILES" section below.
328
- 2. NO INVENTION: NEVER invent file paths, class names, or code. If not shown - it doesn't exist.
329
- 3. WHEN NO DATA: Say "Нет данных в индексе" (Russian) or "No indexed data" (English).
330
- 4. BE HONEST: If you don't know - say so. Don't guess.`;
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
- prompt += '\n\n###PROJECT FILES (ONLY reference these - nothing else exists)###\n';
339
- for (const result of context.semanticMemory.slice(0, 10)) {
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`;