archicore 0.3.1 → 0.3.2

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.
Files changed (42) hide show
  1. package/README.md +48 -4
  2. package/dist/cli/commands/interactive.js +83 -23
  3. package/dist/cli/commands/projects.js +3 -3
  4. package/dist/cli/ui/prompt.d.ts +4 -0
  5. package/dist/cli/ui/prompt.js +22 -0
  6. package/dist/cli/utils/config.js +2 -2
  7. package/dist/cli/utils/upload-utils.js +65 -18
  8. package/dist/code-index/ast-parser.d.ts +4 -0
  9. package/dist/code-index/ast-parser.js +42 -0
  10. package/dist/code-index/index.d.ts +21 -1
  11. package/dist/code-index/index.js +45 -1
  12. package/dist/code-index/source-map-extractor.d.ts +71 -0
  13. package/dist/code-index/source-map-extractor.js +194 -0
  14. package/dist/gitlab/gitlab-service.d.ts +162 -0
  15. package/dist/gitlab/gitlab-service.js +652 -0
  16. package/dist/gitlab/index.d.ts +8 -0
  17. package/dist/gitlab/index.js +8 -0
  18. package/dist/server/config/passport.d.ts +14 -0
  19. package/dist/server/config/passport.js +86 -0
  20. package/dist/server/index.js +52 -10
  21. package/dist/server/middleware/api-auth.d.ts +2 -2
  22. package/dist/server/middleware/api-auth.js +21 -2
  23. package/dist/server/middleware/csrf.d.ts +23 -0
  24. package/dist/server/middleware/csrf.js +96 -0
  25. package/dist/server/routes/auth.d.ts +2 -2
  26. package/dist/server/routes/auth.js +204 -5
  27. package/dist/server/routes/device-auth.js +2 -2
  28. package/dist/server/routes/gitlab.d.ts +12 -0
  29. package/dist/server/routes/gitlab.js +528 -0
  30. package/dist/server/routes/oauth.d.ts +6 -0
  31. package/dist/server/routes/oauth.js +198 -0
  32. package/dist/server/services/audit-service.d.ts +1 -1
  33. package/dist/server/services/auth-service.d.ts +13 -1
  34. package/dist/server/services/auth-service.js +108 -7
  35. package/dist/server/services/email-service.d.ts +63 -0
  36. package/dist/server/services/email-service.js +586 -0
  37. package/dist/server/utils/disposable-email-domains.d.ts +14 -0
  38. package/dist/server/utils/disposable-email-domains.js +192 -0
  39. package/dist/types/api.d.ts +98 -0
  40. package/dist/types/gitlab.d.ts +245 -0
  41. package/dist/types/gitlab.js +11 -0
  42. package/package.json +1 -1
package/README.md CHANGED
@@ -41,6 +41,14 @@
41
41
 
42
42
  ---
43
43
 
44
+ **📚 Complete Documentation:**
45
+ - **[API & CLI Reference](./API_CLI_REFERENCE.md)** - Полная документация всех API endpoints, CLI команд и поддерживаемых языков
46
+ - **[TODO](./TODO.md)** - Список задач и планов развития
47
+ - **[Business Model](./BUSINESS-QA.md)** - Бизнес-модель и ответы на вопросы
48
+ - **[Deployment Guide](./DEPLOYMENT.md)** - Инструкция по развертыванию
49
+
50
+ ---
51
+
44
52
  ## 🎯 Overview
45
53
 
46
54
  **ArchiCore** - это интеллектуальная платформа для анализа и управления архитектурой программного обеспечения, построенная на основе AI. Система понимает код на глубоком семантическом уровне, отслеживает все зависимости и помогает принимать обоснованные архитектурные решения.
@@ -133,12 +141,13 @@
133
141
 
134
142
  ### 🚀 Developer Experience
135
143
 
136
- - **40+ Language Support** - TypeScript, JavaScript, Python, Go, Rust, Java, Kotlin, C#, PHP, Ruby и др.
144
+ - **60+ Language Support** - TypeScript, JavaScript, Python, Go, Rust, Java, Kotlin, Scala, C#, F#, Swift, Dart, PHP, Ruby, Haskell, Elixir и другие
137
145
  - **IDE Integration** - VS Code, JetBrains (в разработке)
138
146
  - **CI/CD Ready** - Интеграция с GitLab CI, GitHub Actions, Jenkins
139
147
  - **Docker Support** - Полная containerization для легкого deployment
140
148
  - **Auto-Scaling** - Поддержка горизонтального масштабирования
141
149
  - **Real-time Updates** - Live reload при изменениях кода
150
+ - **Interactive CLI** - Autocomplete, progress bars, colored output
142
151
 
143
152
  ---
144
153
 
@@ -254,8 +263,25 @@ ArchiCore построен по принципам чистой архитект
254
263
  - **Testing**: Jest (planned)
255
264
  - **Git Hooks**: Husky (planned)
256
265
 
257
- ### Supported Languages (Tree-sitter)
258
- TypeScript, JavaScript, Python, Go, Rust, Java, Kotlin, Scala, C#, F#, Swift, Dart, PHP, Ruby, Haskell, Elixir, Clojure, Vue, Svelte, Terraform, GraphQL, YAML, JSON, CSS, HTML, SQL, Bash, Dockerfile и другие (40+)
266
+ ### Supported Languages
267
+ **60+ языков** через Tree-sitter AST parsing и regex-based analysis:
268
+
269
+ **JavaScript/TypeScript Ecosystem:** TypeScript, JavaScript, JSX/TSX, Vue.js, Svelte, Astro
270
+ **Systems Programming:** Go, Rust, Zig, Nim, C, C++
271
+ **JVM Languages:** Java, Kotlin, Scala, Groovy, Clojure
272
+ **.NET Languages:** C#, F#, Visual Basic
273
+ **Web/Scripting:** PHP, Ruby, Perl, Lua
274
+ **Mobile:** Swift, Dart/Flutter, Objective-C
275
+ **Functional:** Haskell, OCaml, Erlang, Elixir, Julia, R
276
+ **Other:** Python, Crystal
277
+ **Markup/Styles:** HTML, CSS, SCSS, Sass, Less, Stylus, XML
278
+ **Data Formats:** JSON, YAML, TOML, INI
279
+ **Database:** SQL, Prisma, GraphQL
280
+ **Infrastructure:** Terraform, Protobuf, Docker, Makefile, CMake
281
+ **Shell:** Bash, Zsh, PowerShell, Batch
282
+ **Documentation:** Markdown, reStructuredText
283
+
284
+ 📖 **[Complete Language Support Matrix →](./API_CLI_REFERENCE.md#supported-languages)**
259
285
 
260
286
  ---
261
287
 
@@ -782,6 +808,8 @@ archicore chat
782
808
  # > /exit - Выход
783
809
  ```
784
810
 
811
+ 📖 **[Complete CLI Documentation →](./API_CLI_REFERENCE.md#complete-cli-reference)**
812
+
785
813
  ---
786
814
 
787
815
  ### 🔌 REST API
@@ -912,6 +940,16 @@ DELETE /api/developer/keys/:id - Удалить API ключ
912
940
  POST /api/developer/keys/:id/revoke - Отозвать API ключ
913
941
  ```
914
942
 
943
+ **Upload & Utilities:**
944
+ ```
945
+ POST /api/upload - Загрузить файлы проекта
946
+ POST /api/report-issue - Отправить bug report
947
+ GET /api/tasks/:taskId - Статус задачи
948
+ GET /api/tasks/:taskId/stream - WebSocket progress updates
949
+ ```
950
+
951
+ 📖 **[Complete API Documentation →](./API_CLI_REFERENCE.md#complete-api-reference)**
952
+
915
953
  #### Example: Analyze Impact
916
954
 
917
955
  ```bash
@@ -2346,9 +2384,15 @@ SOFTWARE.
2346
2384
 
2347
2385
  ---
2348
2386
 
2387
+ ## 📚 Documentation
2388
+
2389
+ - **[Complete API & CLI Reference](./API_CLI_REFERENCE.md)** - Все endpoints, команды и языки
2390
+ - **[Online Documentation](https://docs.archicore.io)** - Интерактивная документация
2391
+ - **[TODO & Roadmap](./TODO.md)** - Планы развития
2392
+ - **[Business Model](./BUSINESS-QA.md)** - Бизнес-модель
2393
+
2349
2394
  ## 🤝 Support
2350
2395
 
2351
- - **Documentation**: https://docs.archicore.io
2352
2396
  - **Email**: support@archicore.io
2353
2397
  - **GitHub Issues**: https://github.com/yourusername/archicore/issues
2354
2398
  - **Twitter**: [@archicore_ai](https://twitter.com/archicore_ai)
@@ -7,9 +7,9 @@ import * as readline from 'readline';
7
7
  import { loadConfig } from '../utils/config.js';
8
8
  import { checkServerConnection } from '../utils/session.js';
9
9
  import { printFormattedError, printStartupError, } from '../utils/error-handler.js';
10
- import { isInitialized, getLocalProject } from './init.js';
10
+ import { isInitialized, getLocalProject, initProject } from './init.js';
11
11
  import { requireAuth, logout } from './auth.js';
12
- import { colors, icons, createSpinner, printHelp, printGoodbye, printSection, printSuccess, printError, printWarning, printInfo, printKeyValue, header, } from '../ui/index.js';
12
+ import { colors, icons, createSpinner, printHelp, printGoodbye, printSection, printSuccess, printError, printWarning, printInfo, printKeyValue, header, promptYesNo, } from '../ui/index.js';
13
13
  import { createSession, loadLastSession, addMessage, getContextMessages, listSessions, searchHistory, exportSessionAsText, clearAllHistory, } from '../utils/conversation-history.js';
14
14
  import { uploadIndexData, analyzeNetworkError, analyzeHttpError, } from '../utils/upload-utils.js';
15
15
  // Command registry with descriptions (like Claude CLI)
@@ -102,13 +102,22 @@ export async function startInteractiveMode() {
102
102
  printFormattedError(reason, { operation: 'Unhandled promise' });
103
103
  });
104
104
  // Check if initialized in current directory
105
- const initialized = await isInitialized(state.projectPath);
105
+ let initialized = await isInitialized(state.projectPath);
106
+ // AUTO-INIT: If not initialized, automatically initialize
106
107
  if (!initialized) {
107
- printStartupError(new Error('ArchiCore not initialized in this directory'));
108
- process.exit(1);
108
+ console.log();
109
+ console.log(colors.muted(` ${icons.info} ArchiCore not initialized in this directory.`));
110
+ console.log(colors.muted(` ${icons.lightning} Initializing automatically...`));
111
+ console.log();
112
+ await initProject(state.projectPath);
113
+ initialized = await isInitialized(state.projectPath);
114
+ if (!initialized) {
115
+ printStartupError(new Error('Failed to initialize ArchiCore'));
116
+ process.exit(1);
117
+ }
109
118
  }
110
119
  // Load local project config
111
- const localProject = await getLocalProject(state.projectPath);
120
+ let localProject = await getLocalProject(state.projectPath);
112
121
  if (localProject) {
113
122
  state.projectId = localProject.id || null;
114
123
  state.projectName = localProject.name;
@@ -146,6 +155,26 @@ export async function startInteractiveMode() {
146
155
  else {
147
156
  state.conversationSession = await createSession(state.projectId || undefined, state.projectName || undefined);
148
157
  }
158
+ // AUTO-INDEX: Check if project needs indexing and ask user
159
+ localProject = await getLocalProject(state.projectPath);
160
+ if (localProject && !localProject.indexed) {
161
+ console.log();
162
+ console.log(colors.warning(` ${icons.warning} Project not indexed yet.`));
163
+ console.log(colors.muted(' Indexing allows ArchiCore to analyze your code structure.'));
164
+ console.log();
165
+ const shouldIndex = await promptYesNo('Index project now?', true);
166
+ if (shouldIndex) {
167
+ console.log();
168
+ // Run indexing directly
169
+ await handleIndexCommand();
170
+ console.log();
171
+ }
172
+ else {
173
+ console.log();
174
+ console.log(colors.muted(` ${icons.info} You can index later using /index command.`));
175
+ console.log();
176
+ }
177
+ }
149
178
  // Print welcome
150
179
  console.log();
151
180
  console.log(header('ArchiCore', 'AI Software Architect'));
@@ -525,7 +554,7 @@ async function handleIndexCommand() {
525
554
  });
526
555
  if (response.ok) {
527
556
  const data = await response.json();
528
- state.projectId = data.id || data.project?.id;
557
+ state.projectId = data.id || data.project?.id ?? null;
529
558
  registerSpinner.succeed('Project registered');
530
559
  }
531
560
  else {
@@ -552,15 +581,40 @@ async function handleIndexCommand() {
552
581
  return;
553
582
  }
554
583
  // Локальная индексация
555
- const indexSpinner = createSpinner('Parsing local files...').start();
584
+ const indexSpinner = createSpinner('Analyzing project structure...').start();
556
585
  try {
557
586
  // Динамический импорт CodeIndex
558
587
  const { CodeIndex } = await import('../../code-index/index.js');
559
588
  const fs = await import('fs/promises');
560
589
  const pathModule = await import('path');
561
590
  const codeIndex = new CodeIndex(state.projectPath);
562
- // Парсим AST
563
- const asts = await codeIndex.parseProject();
591
+ // Проверяем, является ли проект bundled (содержит source maps)
592
+ const isBundled = await codeIndex.isBundledProject();
593
+ let asts;
594
+ let virtualFileContents = [];
595
+ if (isBundled) {
596
+ // Извлекаем исходники из source maps
597
+ indexSpinner.update('Bundled project detected, extracting from source maps...');
598
+ const extractionResult = await codeIndex.extractFromSourceMaps();
599
+ if (extractionResult.files.length > 0) {
600
+ indexSpinner.update(`Extracted ${extractionResult.files.length} files from source maps, parsing...`);
601
+ // Парсим виртуальные файлы
602
+ asts = codeIndex.parseVirtualFiles(extractionResult.files);
603
+ // Сохраняем содержимое виртуальных файлов для загрузки
604
+ virtualFileContents = extractionResult.files.map(f => [f.path, f.content]);
605
+ indexSpinner.update(`Parsed ${asts.size} files from source maps, extracting symbols...`);
606
+ }
607
+ else {
608
+ // Fallback: парсим обычные файлы
609
+ indexSpinner.update('No extractable sources in source maps, parsing regular files...');
610
+ asts = await codeIndex.parseProject();
611
+ }
612
+ }
613
+ else {
614
+ // Обычный проект - парсим файлы напрямую
615
+ indexSpinner.update('Parsing local files...');
616
+ asts = await codeIndex.parseProject();
617
+ }
564
618
  indexSpinner.update(`Parsed ${asts.size} files, extracting symbols...`);
565
619
  // Извлекаем символы
566
620
  const symbols = codeIndex.extractSymbols(asts);
@@ -570,8 +624,13 @@ async function handleIndexCommand() {
570
624
  // Определяем размер проекта для оптимизации
571
625
  const isLargeProject = symbols.size > 50000 || asts.size > 1000;
572
626
  // Читаем содержимое файлов (с оптимизацией для больших проектов)
573
- const fileContents = [];
574
- if (!isLargeProject) {
627
+ let fileContents = [];
628
+ // Если есть виртуальные файлы из source maps — используем их
629
+ if (virtualFileContents.length > 0) {
630
+ indexSpinner.update('Using extracted source files...');
631
+ fileContents = virtualFileContents;
632
+ }
633
+ else if (!isLargeProject) {
575
634
  indexSpinner.update('Reading file contents...');
576
635
  for (const [filePath] of asts) {
577
636
  try {
@@ -634,7 +693,7 @@ async function handleIndexCommand() {
634
693
  });
635
694
  if (reRegisterResponse.ok) {
636
695
  const reData = await reRegisterResponse.json();
637
- state.projectId = reData.id || reData.project?.id;
696
+ state.projectId = reData.id || reData.project?.id ?? null;
638
697
  printInfo('Project re-registered. Run /index again to complete indexing.');
639
698
  // Обновляем локальный конфиг
640
699
  const localProjectRetry = await getLocalProject(state.projectPath);
@@ -727,7 +786,7 @@ async function handleAnalyzeCommand(args) {
727
786
  spinner.succeed('Analysis complete');
728
787
  printSection('Impact Analysis');
729
788
  // Handle various response formats
730
- const impact = data.impact || data.result || data || {};
789
+ const impact = data.impact || data.result || {};
731
790
  const affected = impact.affectedNodes || impact.affected || impact.nodes || [];
732
791
  console.log(` ${colors.highlight('Affected Components:')} ${affected.length}`);
733
792
  if (affected.length === 0) {
@@ -914,7 +973,7 @@ async function handleDeadCodeCommand() {
914
973
  throw new Error('Analysis failed');
915
974
  const data = await response.json();
916
975
  // Handle various response formats
917
- const result = data.deadCode || data.result || data || {};
976
+ const result = data.deadCode || data.result || {};
918
977
  spinner.succeed('Analysis complete');
919
978
  printSection('Dead Code');
920
979
  const unusedExports = result.unusedExports || [];
@@ -955,7 +1014,7 @@ async function handleSecurityCommand() {
955
1014
  throw new Error('Analysis failed');
956
1015
  const data = await response.json();
957
1016
  // Handle various response formats
958
- const security = data.security || data.result || data || {};
1017
+ const security = data.security || data.result || {};
959
1018
  const vulns = security.vulnerabilities || security.issues || [];
960
1019
  spinner.succeed('Analysis complete');
961
1020
  printSection('Security');
@@ -1001,8 +1060,9 @@ async function handleMetricsCommand() {
1001
1060
  throw new Error('Analysis failed');
1002
1061
  const data = await response.json();
1003
1062
  // Handle various response formats
1004
- const metrics = data.metrics || data.result || data || {};
1005
- const summary = metrics.summary || metrics || {};
1063
+ const metricsData = data.metrics || data.result || {};
1064
+ const metrics = metricsData;
1065
+ const summary = metricsData.summary || metricsData;
1006
1066
  spinner.succeed('Metrics calculated');
1007
1067
  printSection('Code Metrics');
1008
1068
  // Display available metrics
@@ -1054,7 +1114,7 @@ async function handleExportCommand(args) {
1054
1114
  const { writeFile } = await import('fs/promises');
1055
1115
  const content = format === 'json'
1056
1116
  ? JSON.stringify(data.data || data, null, 2)
1057
- : data.content || JSON.stringify(data, null, 2);
1117
+ : (data.content || JSON.stringify(data, null, 2));
1058
1118
  await writeFile(output, content);
1059
1119
  spinner.succeed('Export complete');
1060
1120
  printKeyValue('Output', output);
@@ -1125,7 +1185,7 @@ async function handleDuplicationCommand() {
1125
1185
  throw new Error('Analysis failed');
1126
1186
  const data = await response.json();
1127
1187
  // Handle various response formats
1128
- const result = data.duplication || data.result || data || {};
1188
+ const result = data.duplication || data.result || {};
1129
1189
  const clones = result.clones || [];
1130
1190
  const duplicationRate = result.duplicationRate || result.rate || 0;
1131
1191
  const duplicatedLines = result.duplicatedLines || result.lines || 0;
@@ -1171,7 +1231,7 @@ async function handleRefactoringCommand() {
1171
1231
  throw new Error('Analysis failed');
1172
1232
  const data = await response.json();
1173
1233
  // Handle various response formats
1174
- const refactoring = data.refactoring || data.result || data || {};
1234
+ const refactoring = data.refactoring || data.result || {};
1175
1235
  const suggestions = refactoring.suggestions || refactoring.items || [];
1176
1236
  spinner.succeed('Analysis complete');
1177
1237
  printSection('Refactoring Suggestions');
@@ -1221,7 +1281,7 @@ async function handleRulesCommand() {
1221
1281
  throw new Error('Analysis failed');
1222
1282
  const data = await response.json();
1223
1283
  // Handle various response formats
1224
- const rules = data.rules || data.result || data || {};
1284
+ const rules = data.rules || data.result || {};
1225
1285
  const violations = rules.violations || rules.issues || [];
1226
1286
  spinner.succeed('Analysis complete');
1227
1287
  printSection('Architectural Rules');
@@ -1275,7 +1335,7 @@ async function handleDocsCommand(args) {
1275
1335
  const data = await response.json();
1276
1336
  updateTokensFromResponse(data);
1277
1337
  const { writeFile } = await import('fs/promises');
1278
- await writeFile(output, data.documentation);
1338
+ await writeFile(output, data.documentation || '');
1279
1339
  spinner.succeed('Documentation generated');
1280
1340
  printKeyValue('Output', output);
1281
1341
  printKeyValue('Format', format);
@@ -77,7 +77,7 @@ export function registerProjectsCommand(program) {
77
77
  });
78
78
  if (!response.ok) {
79
79
  const error = await response.json();
80
- throw new Error(error.error || 'Failed to create project');
80
+ throw new Error(error.error || error.message || 'Failed to create project');
81
81
  }
82
82
  const data = await response.json();
83
83
  spinner.succeed('Project created');
@@ -133,7 +133,7 @@ export function registerProjectsCommand(program) {
133
133
  });
134
134
  if (!response.ok) {
135
135
  const error = await response.json();
136
- throw new Error(error.error || 'Failed to delete project');
136
+ throw new Error(error.error || error.message || 'Failed to delete project');
137
137
  }
138
138
  spinner.succeed('Project deleted');
139
139
  // Clear active project if it was deleted
@@ -254,7 +254,7 @@ export function registerProjectsCommand(program) {
254
254
  });
255
255
  if (!response.ok) {
256
256
  const error = await response.json();
257
- throw new Error(error.error || 'Failed to index project');
257
+ throw new Error(error.error || error.message || 'Failed to index project');
258
258
  }
259
259
  const data = await response.json();
260
260
  spinner.succeed('Project indexed');
@@ -31,4 +31,8 @@ export declare function printSuccess(message: string): void;
31
31
  export declare function printError(message: string): void;
32
32
  export declare function printWarning(message: string): void;
33
33
  export declare function printInfo(message: string): void;
34
+ /**
35
+ * Simple yes/no prompt (standalone, doesn't require Prompt instance)
36
+ */
37
+ export declare function promptYesNo(question: string, defaultValue?: boolean): Promise<boolean>;
34
38
  //# sourceMappingURL=prompt.d.ts.map
@@ -123,4 +123,26 @@ export function printWarning(message) {
123
123
  export function printInfo(message) {
124
124
  console.log(colors.info(` ${icons.info} ${message}`));
125
125
  }
126
+ /**
127
+ * Simple yes/no prompt (standalone, doesn't require Prompt instance)
128
+ */
129
+ export async function promptYesNo(question, defaultValue = true) {
130
+ const hint = defaultValue ? '[Y/n]' : '[y/N]';
131
+ return new Promise((resolve) => {
132
+ const rl = readline.createInterface({
133
+ input: process.stdin,
134
+ output: process.stdout,
135
+ terminal: true,
136
+ });
137
+ rl.question(colors.primary(` ${question} ${colors.muted(hint)} `), (answer) => {
138
+ rl.close();
139
+ const trimmed = answer.trim().toLowerCase();
140
+ if (!trimmed) {
141
+ resolve(defaultValue);
142
+ return;
143
+ }
144
+ resolve(trimmed.startsWith('y'));
145
+ });
146
+ });
147
+ }
126
148
  //# sourceMappingURL=prompt.js.map
@@ -7,10 +7,10 @@ import { join } from 'path';
7
7
  const CONFIG_DIR = join(homedir(), '.archicore');
8
8
  const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
9
9
  const DEFAULT_CONFIG = {
10
- serverUrl: 'http://194.156.66.251:3000',
10
+ serverUrl: 'https://api.archicore.io',
11
11
  colorOutput: true,
12
12
  verboseOutput: false,
13
- qdrantUrl: 'http://194.156.66.251:6333',
13
+ qdrantUrl: 'http://localhost:6333', // Local Qdrant for CLI
14
14
  };
15
15
  let cachedConfig = null;
16
16
  export async function loadConfig() {
@@ -7,12 +7,12 @@
7
7
  * - Детальная обработка ошибок
8
8
  */
9
9
  import { loadConfig } from './config.js';
10
- // Лимиты для chunked upload (оптимизировано для очень больших проектов)
11
- const MAX_PAYLOAD_SIZE = 5 * 1024 * 1024; // 5MB per chunk (уменьшено для надёжности)
12
- const MAX_SYMBOLS_PER_CHUNK = 2000; // Меньше символов на chunk
13
- const MAX_FILES_PER_CHUNK = 50; // Меньше файлов на chunk
14
- const UPLOAD_TIMEOUT = 180000; // 3 минуты на chunk
15
- const MAX_RETRIES = 5; // Больше попыток
10
+ // Лимиты для chunked upload (оптимизировано для очень больших проектов и нестабильных соединений)
11
+ const MAX_PAYLOAD_SIZE = 3 * 1024 * 1024; // 3MB per chunk (уменьшено для надёжности на медленных соединениях)
12
+ const MAX_SYMBOLS_PER_CHUNK = 1500; // Меньше символов на chunk для стабильности
13
+ const MAX_FILES_PER_CHUNK = 30; // Меньше файлов на chunk для стабильности
14
+ const UPLOAD_TIMEOUT = 300000; // 5 минут на chunk (увеличено для медленных соединений)
15
+ const MAX_RETRIES = 7; // Ещё больше попыток для нестабильных сетей
16
16
  // Лимиты для минимальной загрузки
17
17
  const MINIMAL_MAX_SYMBOLS = 10000;
18
18
  const MINIMAL_MAX_FILES = 500;
@@ -84,8 +84,8 @@ export function analyzeNetworkError(error) {
84
84
  return {
85
85
  code: 'NETWORK_ERROR',
86
86
  message: 'Network request failed',
87
- suggestion: 'Check your internet connection and try again.',
88
- technicalDetails: `${errorName}: ${errorMessage}`,
87
+ suggestion: 'Check your internet connection and try again. If on unstable network, ArchiCore will automatically retry with longer delays.',
88
+ technicalDetails: `Error: ${errorName}: ${errorMessage}`,
89
89
  };
90
90
  }
91
91
  /**
@@ -186,9 +186,11 @@ async function fetchWithRetry(url, options, timeout = UPLOAD_TIMEOUT, maxRetries
186
186
  errorStr.includes('CERT')) {
187
187
  throw error;
188
188
  }
189
- // Экспоненциальная задержка перед повтором
189
+ // Экспоненциальная задержка перед повтором (увеличена для нестабильных соединений)
190
190
  if (attempt < maxRetries) {
191
- const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000);
191
+ // 2s, 4s, 8s, 16s, 32s, до 60s max
192
+ const delay = Math.min(2000 * Math.pow(2, attempt - 1), 60000);
193
+ console.log(`[DEBUG] fetchWithRetry: waiting ${delay / 1000}s before attempt ${attempt + 1}...`);
192
194
  await new Promise(resolve => setTimeout(resolve, delay));
193
195
  }
194
196
  }
@@ -339,25 +341,70 @@ async function uploadChunked(baseUrl, projectId, data, accessToken, onProgress)
339
341
  const uploadId = initResult.uploadId;
340
342
  // Параллельная загрузка chunks (по 3 одновременно)
341
343
  const PARALLEL_UPLOADS = 3;
342
- // Helper для параллельной загрузки
344
+ // Helper для параллельной загрузки с retry для отдельных chunks
343
345
  async function uploadChunksParallel(chunks, chunkType, label) {
344
346
  console.log(`[DEBUG] Starting parallel upload of ${chunks.length} ${chunkType} chunks`);
347
+ const failedChunks = [];
348
+ const MAX_CHUNK_RETRIES = 5; // Увеличено для нестабильных соединений (Debian и т.д.)
345
349
  for (let batch = 0; batch < chunks.length; batch += PARALLEL_UPLOADS) {
346
350
  const batchChunks = chunks.slice(batch, batch + PARALLEL_UPLOADS);
347
- console.log(`[DEBUG] Uploading batch ${batch / PARALLEL_UPLOADS + 1} (${batchChunks.length} chunks)`);
348
- const promises = batchChunks.map((chunk, idx) => uploadChunk(config.serverUrl, projectId, uploadId, chunkType, batch + idx, chunk, accessToken).then(() => {
349
- console.log(`[DEBUG] Chunk ${chunkType}[${batch + idx}] uploaded`);
351
+ const batchNum = Math.floor(batch / PARALLEL_UPLOADS) + 1;
352
+ console.log(`[DEBUG] Uploading batch ${batchNum} (${batchChunks.length} chunks)`);
353
+ // Upload each chunk with individual retry logic
354
+ const results = await Promise.allSettled(batchChunks.map(async (chunk, idx) => {
355
+ const chunkIndex = batch + idx;
356
+ let lastError = null;
357
+ for (let retry = 0; retry < MAX_CHUNK_RETRIES; retry++) {
358
+ try {
359
+ await uploadChunk(config.serverUrl, projectId, uploadId, chunkType, chunkIndex, chunk, accessToken);
360
+ console.log(`[DEBUG] Chunk ${chunkType}[${chunkIndex}] uploaded`);
361
+ return { success: true, chunkIndex };
362
+ }
363
+ catch (error) {
364
+ lastError = error instanceof Error ? error : new Error(String(error));
365
+ console.log(`[DEBUG] Chunk ${chunkType}[${chunkIndex}] failed (attempt ${retry + 1}/${MAX_CHUNK_RETRIES}): ${lastError.message}`);
366
+ // Exponential backoff before retry (увеличено для нестабильных соединений)
367
+ if (retry < MAX_CHUNK_RETRIES - 1) {
368
+ // Более агрессивный backoff: 2s, 4s, 8s, 16s, до 30s max
369
+ const delay = Math.min(2000 * Math.pow(2, retry), 30000);
370
+ console.log(`[DEBUG] Waiting ${delay / 1000}s before retry ${retry + 2}...`);
371
+ await new Promise(resolve => setTimeout(resolve, delay));
372
+ }
373
+ }
374
+ }
375
+ // All retries failed
376
+ throw lastError || new Error(`Chunk ${chunkType}[${chunkIndex}] upload failed`);
350
377
  }));
351
- await Promise.all(promises);
352
- completedChunks += batchChunks.length;
378
+ // Count successes and failures
379
+ let batchSuccesses = 0;
380
+ for (let i = 0; i < results.length; i++) {
381
+ const result = results[i];
382
+ if (result.status === 'fulfilled') {
383
+ batchSuccesses++;
384
+ }
385
+ else {
386
+ const chunkIndex = batch + i;
387
+ failedChunks.push(chunkIndex);
388
+ console.log(`[DEBUG] Chunk ${chunkType}[${chunkIndex}] failed permanently: ${result.reason}`);
389
+ }
390
+ }
391
+ completedChunks += batchSuccesses;
353
392
  onProgress?.({
354
393
  phase: 'uploading',
355
394
  current: completedChunks,
356
395
  total: totalChunks,
357
- message: `${label} (${Math.min(batch + PARALLEL_UPLOADS, chunks.length)}/${chunks.length})...`,
396
+ message: `${label} (${Math.min(batch + PARALLEL_UPLOADS, chunks.length)}/${chunks.length})${failedChunks.length > 0 ? ` [${failedChunks.length} failed]` : ''}...`,
358
397
  });
398
+ // If too many chunks failed, abort (увеличен порог до 20% для нестабильных соединений)
399
+ if (failedChunks.length > Math.ceil(chunks.length * 0.2)) {
400
+ throw new Error(`Too many chunks failed (${failedChunks.length}/${chunks.length}). Network may be unstable. Try again or check your connection.`);
401
+ }
402
+ }
403
+ console.log(`[DEBUG] Finished uploading ${chunks.length} ${chunkType} chunks (${failedChunks.length} failed)`);
404
+ // If any chunks failed, warn but continue if under threshold
405
+ if (failedChunks.length > 0) {
406
+ console.log(`[DEBUG] Warning: ${failedChunks.length} ${chunkType} chunks failed to upload`);
359
407
  }
360
- console.log(`[DEBUG] Finished uploading ${chunks.length} ${chunkType} chunks`);
361
408
  }
362
409
  // 2. Загружаем ASTs параллельно
363
410
  await uploadChunksParallel(astChunks, 'asts', 'Uploading ASTs');
@@ -4,6 +4,10 @@ export declare class ASTParser {
4
4
  private parsers;
5
5
  constructor();
6
6
  private initializeParsers;
7
+ /**
8
+ * Парсит контент напрямую (для виртуальных файлов из source maps)
9
+ */
10
+ parseContent(content: string, virtualPath: string): ASTNode | null;
7
11
  parseFile(filePath: string): Promise<ASTNode | null>;
8
12
  private parseWithRegex;
9
13
  private inferTypeFromPattern;
@@ -24,6 +24,48 @@ export class ASTParser {
24
24
  this.parsers.set('python', pyParser);
25
25
  Logger.debug('AST parsers initialized');
26
26
  }
27
+ /**
28
+ * Парсит контент напрямую (для виртуальных файлов из source maps)
29
+ */
30
+ parseContent(content, virtualPath) {
31
+ try {
32
+ let processedContent = content;
33
+ let language = FileUtils.getLanguageFromExtension(virtualPath);
34
+ // Vue SFC: extract <script> section and parse as TS/JS
35
+ if (language === 'vue') {
36
+ const scriptResult = this.extractVueScript(processedContent);
37
+ if (!scriptResult) {
38
+ return null;
39
+ }
40
+ processedContent = scriptResult.content;
41
+ language = scriptResult.isTypeScript ? 'typescript' : 'javascript';
42
+ }
43
+ // Validate content before parsing
44
+ if (!processedContent || typeof processedContent !== 'string' || !processedContent.trim()) {
45
+ return null;
46
+ }
47
+ // For large files, use regex-based fallback
48
+ if (processedContent.length > MAX_TREE_SITTER_SIZE) {
49
+ return this.parseWithRegex(processedContent, virtualPath, language);
50
+ }
51
+ const parser = this.parsers.get(language);
52
+ if (!parser) {
53
+ return this.parseWithRegex(processedContent, virtualPath, language);
54
+ }
55
+ const tree = parser.parse(processedContent);
56
+ return this.convertToASTNode(tree.rootNode, virtualPath);
57
+ }
58
+ catch {
59
+ // Try regex fallback
60
+ try {
61
+ const language = FileUtils.getLanguageFromExtension(virtualPath);
62
+ return this.parseWithRegex(content, virtualPath, language);
63
+ }
64
+ catch {
65
+ return null;
66
+ }
67
+ }
68
+ }
27
69
  async parseFile(filePath) {
28
70
  try {
29
71
  let content = await FileUtils.readFileContent(filePath);
@@ -10,16 +10,35 @@
10
10
  import { ASTParser } from './ast-parser.js';
11
11
  import { SymbolExtractor } from './symbol-extractor.js';
12
12
  import { DependencyGraphBuilder } from './dependency-graph.js';
13
+ import { SourceMapExtractor, VirtualFile, ExtractionResult } from './source-map-extractor.js';
13
14
  import { DependencyGraph, Symbol, ASTNode } from '../types/index.js';
14
15
  export declare class CodeIndex {
15
16
  private astParser;
16
17
  private symbolExtractor;
17
18
  private graphBuilder;
19
+ private sourceMapExtractor;
18
20
  private rootDir;
19
21
  private asts;
20
22
  private symbols;
21
23
  private graph;
24
+ private virtualFiles;
22
25
  constructor(rootDir?: string);
26
+ /**
27
+ * Проверяет, является ли проект bundled (содержит source maps)
28
+ */
29
+ isBundledProject(): Promise<boolean>;
30
+ /**
31
+ * Извлекает исходный код из source maps (для bundled проектов)
32
+ */
33
+ extractFromSourceMaps(): Promise<ExtractionResult>;
34
+ /**
35
+ * Парсит виртуальные файлы из source maps
36
+ */
37
+ parseVirtualFiles(files: VirtualFile[], progressCallback?: (current: number, total: number, file: string) => void): Map<string, ASTNode>;
38
+ /**
39
+ * Получает виртуальные файлы (для передачи на сервер)
40
+ */
41
+ getVirtualFiles(): VirtualFile[];
23
42
  indexProject(rootDir?: string): Promise<void>;
24
43
  parseProject(progressCallback?: (current: number, total: number, file: string) => void): Promise<Map<string, ASTNode>>;
25
44
  extractSymbols(asts: Map<string, ASTNode>): Map<string, Symbol>;
@@ -40,5 +59,6 @@ export declare class CodeIndex {
40
59
  private countGraphEdges;
41
60
  private getSymbolsByKind;
42
61
  }
43
- export { ASTParser, SymbolExtractor, DependencyGraphBuilder };
62
+ export { ASTParser, SymbolExtractor, DependencyGraphBuilder, SourceMapExtractor };
63
+ export type { VirtualFile, ExtractionResult } from './source-map-extractor.js';
44
64
  //# sourceMappingURL=index.d.ts.map