archicore 0.3.1 → 0.3.3
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/README.md +48 -4
- package/dist/cli/commands/interactive.js +83 -23
- package/dist/cli/commands/projects.js +3 -3
- package/dist/cli/ui/prompt.d.ts +4 -0
- package/dist/cli/ui/prompt.js +22 -0
- package/dist/cli/utils/config.js +2 -2
- package/dist/cli/utils/upload-utils.js +65 -18
- package/dist/code-index/ast-parser.d.ts +4 -0
- package/dist/code-index/ast-parser.js +42 -0
- package/dist/code-index/index.d.ts +21 -1
- package/dist/code-index/index.js +45 -1
- package/dist/code-index/source-map-extractor.d.ts +71 -0
- package/dist/code-index/source-map-extractor.js +194 -0
- package/dist/gitlab/gitlab-service.d.ts +162 -0
- package/dist/gitlab/gitlab-service.js +652 -0
- package/dist/gitlab/index.d.ts +8 -0
- package/dist/gitlab/index.js +8 -0
- package/dist/server/config/passport.d.ts +14 -0
- package/dist/server/config/passport.js +86 -0
- package/dist/server/index.js +52 -10
- package/dist/server/middleware/api-auth.d.ts +2 -2
- package/dist/server/middleware/api-auth.js +21 -2
- package/dist/server/middleware/csrf.d.ts +23 -0
- package/dist/server/middleware/csrf.js +96 -0
- package/dist/server/routes/auth.d.ts +2 -2
- package/dist/server/routes/auth.js +204 -5
- package/dist/server/routes/device-auth.js +2 -2
- package/dist/server/routes/gitlab.d.ts +12 -0
- package/dist/server/routes/gitlab.js +528 -0
- package/dist/server/routes/oauth.d.ts +6 -0
- package/dist/server/routes/oauth.js +198 -0
- package/dist/server/services/audit-service.d.ts +1 -1
- package/dist/server/services/auth-service.d.ts +13 -1
- package/dist/server/services/auth-service.js +108 -7
- package/dist/server/services/email-service.d.ts +63 -0
- package/dist/server/services/email-service.js +586 -0
- package/dist/server/utils/disposable-email-domains.d.ts +14 -0
- package/dist/server/utils/disposable-email-domains.js +192 -0
- package/dist/types/api.d.ts +98 -0
- package/dist/types/gitlab.d.ts +245 -0
- package/dist/types/gitlab.js +11 -0
- 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
|
-
- **
|
|
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
|
|
258
|
-
|
|
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
|
-
|
|
105
|
+
let initialized = await isInitialized(state.projectPath);
|
|
106
|
+
// AUTO-INIT: If not initialized, automatically initialize
|
|
106
107
|
if (!initialized) {
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
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
|
|
557
|
+
state.projectId = data.id || (data.project && 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('
|
|
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
|
-
//
|
|
563
|
-
const
|
|
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
|
-
|
|
574
|
-
|
|
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 ||
|
|
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 ||
|
|
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 ||
|
|
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
|
|
1005
|
-
const
|
|
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 ||
|
|
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 ||
|
|
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 ||
|
|
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');
|
package/dist/cli/ui/prompt.d.ts
CHANGED
|
@@ -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
|
package/dist/cli/ui/prompt.js
CHANGED
|
@@ -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
|
package/dist/cli/utils/config.js
CHANGED
|
@@ -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: '
|
|
10
|
+
serverUrl: 'https://api.archicore.io',
|
|
11
11
|
colorOutput: true,
|
|
12
12
|
verboseOutput: false,
|
|
13
|
-
qdrantUrl: 'http://
|
|
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 =
|
|
12
|
-
const MAX_SYMBOLS_PER_CHUNK =
|
|
13
|
-
const MAX_FILES_PER_CHUNK =
|
|
14
|
-
const UPLOAD_TIMEOUT =
|
|
15
|
-
const MAX_RETRIES =
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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
|
-
|
|
352
|
-
|
|
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
|