archicore 0.1.8 → 0.2.0

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/dist/cli/commands/auth.d.ts +4 -0
  2. package/dist/cli/commands/auth.js +58 -0
  3. package/dist/cli/commands/index.d.ts +2 -1
  4. package/dist/cli/commands/index.js +2 -1
  5. package/dist/cli/commands/interactive.js +107 -59
  6. package/dist/cli/ui/index.d.ts +1 -0
  7. package/dist/cli/ui/index.js +1 -0
  8. package/dist/cli/ui/progress.d.ts +57 -0
  9. package/dist/cli/ui/progress.js +149 -0
  10. package/dist/cli/utils/error-handler.d.ts +40 -0
  11. package/dist/cli/utils/error-handler.js +234 -0
  12. package/dist/cli/utils/index.d.ts +2 -0
  13. package/dist/cli/utils/index.js +3 -0
  14. package/dist/cli/utils/project-selector.d.ts +33 -0
  15. package/dist/cli/utils/project-selector.js +148 -0
  16. package/dist/cli.js +3 -1
  17. package/dist/code-index/ast-parser.d.ts +1 -1
  18. package/dist/code-index/ast-parser.js +9 -3
  19. package/dist/code-index/index.d.ts +1 -1
  20. package/dist/code-index/index.js +2 -2
  21. package/dist/github/github-service.js +8 -1
  22. package/dist/orchestrator/index.js +29 -1
  23. package/dist/semantic-memory/embedding-service.d.ts +4 -1
  24. package/dist/semantic-memory/embedding-service.js +58 -11
  25. package/dist/semantic-memory/index.d.ts +1 -1
  26. package/dist/semantic-memory/index.js +54 -7
  27. package/dist/server/config.d.ts +52 -0
  28. package/dist/server/config.js +88 -0
  29. package/dist/server/index.d.ts +3 -0
  30. package/dist/server/index.js +115 -10
  31. package/dist/server/routes/admin.js +3 -3
  32. package/dist/server/routes/api.js +199 -2
  33. package/dist/server/routes/auth.js +79 -5
  34. package/dist/server/routes/device-auth.js +1 -1
  35. package/dist/server/routes/github.js +33 -0
  36. package/dist/server/services/auth-service.d.ts +5 -0
  37. package/dist/server/services/auth-service.js +10 -0
  38. package/dist/server/services/project-service.d.ts +15 -1
  39. package/dist/server/services/project-service.js +185 -26
  40. package/dist/types/user.d.ts +2 -2
  41. package/dist/types/user.js +13 -4
  42. package/package.json +9 -1
@@ -0,0 +1,234 @@
1
+ /**
2
+ * ArchiCore CLI - Error Handler
3
+ *
4
+ * User-friendly error messages with suggestions
5
+ */
6
+ import { colors, icons } from '../ui/colors.js';
7
+ const KNOWN_ERRORS = [
8
+ // Network errors
9
+ {
10
+ pattern: /ECONNREFUSED|Failed to fetch|NetworkError/i,
11
+ title: 'Connection refused',
12
+ suggestion: 'Make sure the ArchiCore server is running',
13
+ details: 'Run `npm run server` or check if the server is accessible',
14
+ },
15
+ {
16
+ pattern: /ETIMEDOUT|timeout/i,
17
+ title: 'Connection timeout',
18
+ suggestion: 'Check your network connection or server status',
19
+ details: 'The server might be overloaded or unreachable',
20
+ },
21
+ {
22
+ pattern: /ENOTFOUND|DNS/i,
23
+ title: 'Server not found',
24
+ suggestion: 'Check the server URL in your configuration',
25
+ details: 'Run `archicore config` to update server settings',
26
+ },
27
+ // Auth errors
28
+ {
29
+ pattern: /401|Unauthorized|Invalid token/i,
30
+ title: 'Authentication failed',
31
+ suggestion: 'Your session may have expired',
32
+ details: 'Run `archicore login` to authenticate again',
33
+ },
34
+ {
35
+ pattern: /403|Forbidden|Access denied/i,
36
+ title: 'Access denied',
37
+ suggestion: 'You don\'t have permission for this operation',
38
+ details: 'Check your subscription or contact support',
39
+ },
40
+ {
41
+ pattern: /429|Too many requests|rate limit/i,
42
+ title: 'Rate limit exceeded',
43
+ suggestion: 'Wait a moment before trying again',
44
+ details: 'Upgrade your plan for higher limits',
45
+ },
46
+ // Project errors
47
+ {
48
+ pattern: /Project not found|No project/i,
49
+ title: 'Project not found',
50
+ suggestion: 'Make sure the project exists and is indexed',
51
+ details: 'Run `archicore init` in your project directory',
52
+ },
53
+ {
54
+ pattern: /Not indexed|Index required/i,
55
+ title: 'Project not indexed',
56
+ suggestion: 'Index your project first',
57
+ details: 'Run `/index` command in interactive mode',
58
+ },
59
+ {
60
+ pattern: /No files found|Empty project/i,
61
+ title: 'No files found',
62
+ suggestion: 'Check that your project has source files',
63
+ details: 'Make sure .ts, .js, or .py files exist in the directory',
64
+ },
65
+ // Qdrant/Vector DB errors
66
+ {
67
+ pattern: /Qdrant|vector.*error|embedding/i,
68
+ title: 'Vector database error',
69
+ suggestion: 'Check if Qdrant is running',
70
+ details: 'Run `docker-compose up -d qdrant` or check Qdrant status',
71
+ },
72
+ // LLM/API errors
73
+ {
74
+ pattern: /OpenAI|Anthropic|DeepSeek|API key/i,
75
+ title: 'AI provider error',
76
+ suggestion: 'Check your API keys in .env file',
77
+ details: 'Make sure OPENAI_API_KEY, ANTHROPIC_API_KEY, or DEEPSEEK_API_KEY is set',
78
+ },
79
+ {
80
+ pattern: /quota|credits|billing/i,
81
+ title: 'API quota exceeded',
82
+ suggestion: 'Check your AI provider billing',
83
+ details: 'You may have run out of credits on OpenAI/Anthropic/DeepSeek',
84
+ },
85
+ // File errors
86
+ {
87
+ pattern: /ENOENT|file not found|no such file/i,
88
+ title: 'File not found',
89
+ suggestion: 'Check the file path',
90
+ details: 'Make sure the file exists and is accessible',
91
+ },
92
+ {
93
+ pattern: /EACCES|permission denied/i,
94
+ title: 'Permission denied',
95
+ suggestion: 'Check file permissions',
96
+ details: 'You may need to run with elevated privileges',
97
+ },
98
+ // Parse errors
99
+ {
100
+ pattern: /Syntax error|Parse error|Invalid JSON/i,
101
+ title: 'Parse error',
102
+ suggestion: 'Check the file for syntax errors',
103
+ details: 'Run your linter or IDE to find syntax issues',
104
+ },
105
+ ];
106
+ /**
107
+ * Format error for CLI display
108
+ */
109
+ export function formatError(error, context) {
110
+ const errorMessage = error instanceof Error ? error.message : String(error);
111
+ const statusCode = context?.statusCode;
112
+ // Find matching known error
113
+ const knownError = KNOWN_ERRORS.find(ke => {
114
+ if (typeof ke.pattern === 'string') {
115
+ return errorMessage.toLowerCase().includes(ke.pattern.toLowerCase());
116
+ }
117
+ return ke.pattern.test(errorMessage);
118
+ });
119
+ const lines = [];
120
+ // Title line
121
+ const title = knownError?.title || context?.operation || 'Error';
122
+ lines.push(colors.error(` ${icons.error} ${title}`));
123
+ // Error message
124
+ if (knownError) {
125
+ lines.push(colors.muted(` ${errorMessage}`));
126
+ }
127
+ else {
128
+ lines.push(` ${errorMessage}`);
129
+ }
130
+ // Status code if available
131
+ if (statusCode && statusCode >= 400) {
132
+ lines.push(colors.dim(` HTTP ${statusCode}`));
133
+ }
134
+ // Suggestion
135
+ const suggestion = context?.suggestion || knownError?.suggestion;
136
+ if (suggestion) {
137
+ lines.push('');
138
+ lines.push(colors.info(` ${icons.info} ${suggestion}`));
139
+ }
140
+ // Details
141
+ const details = context?.details || knownError?.details;
142
+ if (details) {
143
+ lines.push(colors.dim(` ${details}`));
144
+ }
145
+ return lines.join('\n');
146
+ }
147
+ /**
148
+ * Print error with formatting
149
+ */
150
+ export function printFormattedError(error, context) {
151
+ console.log();
152
+ console.log(formatError(error, context));
153
+ console.log();
154
+ }
155
+ /**
156
+ * Wrap async operation with error handling
157
+ */
158
+ export async function withErrorHandler(operation, context) {
159
+ try {
160
+ return await operation();
161
+ }
162
+ catch (error) {
163
+ printFormattedError(error, context);
164
+ return null;
165
+ }
166
+ }
167
+ /**
168
+ * Create contextual error handler
169
+ */
170
+ export function createErrorHandler(defaultContext) {
171
+ return {
172
+ handle: (error, additionalContext) => {
173
+ printFormattedError(error, { ...defaultContext, ...additionalContext });
174
+ },
175
+ wrap: async (operation, additionalContext) => {
176
+ return withErrorHandler(operation, { ...defaultContext, ...additionalContext });
177
+ },
178
+ };
179
+ }
180
+ /**
181
+ * HTTP response error handler
182
+ */
183
+ export async function handleResponseError(response, operation) {
184
+ let errorMessage = `HTTP ${response.status}`;
185
+ try {
186
+ const data = await response.json();
187
+ errorMessage = data.error || data.message || errorMessage;
188
+ }
189
+ catch {
190
+ // Use status text if JSON parsing fails
191
+ errorMessage = response.statusText || errorMessage;
192
+ }
193
+ const error = new Error(errorMessage);
194
+ printFormattedError(error, {
195
+ statusCode: response.status,
196
+ operation,
197
+ });
198
+ throw error;
199
+ }
200
+ /**
201
+ * Print startup errors nicely
202
+ */
203
+ export function printStartupError(error) {
204
+ console.log();
205
+ console.log(colors.error.bold(' ArchiCore failed to start'));
206
+ console.log();
207
+ const message = error instanceof Error ? error.message : String(error);
208
+ // Check for common startup issues
209
+ if (message.includes('ECONNREFUSED') || message.includes('Cannot connect')) {
210
+ console.log(colors.warning(` ${icons.warning} Server connection failed`));
211
+ console.log();
212
+ console.log(colors.muted(' Possible solutions:'));
213
+ console.log(colors.dim(' 1. Start the server: npm run server'));
214
+ console.log(colors.dim(' 2. Check server URL in .archicore/config.json'));
215
+ console.log(colors.dim(' 3. Make sure port 3000 is not blocked'));
216
+ }
217
+ else if (message.includes('not initialized')) {
218
+ console.log(colors.warning(` ${icons.warning} Project not initialized`));
219
+ console.log();
220
+ console.log(colors.muted(' Run this command to initialize:'));
221
+ console.log(colors.primary(' archicore init'));
222
+ }
223
+ else if (message.includes('Authentication') || message.includes('token')) {
224
+ console.log(colors.warning(` ${icons.warning} Authentication required`));
225
+ console.log();
226
+ console.log(colors.muted(' Run this command to login:'));
227
+ console.log(colors.primary(' archicore login'));
228
+ }
229
+ else {
230
+ console.log(colors.muted(` ${message}`));
231
+ }
232
+ console.log();
233
+ }
234
+ //# sourceMappingURL=error-handler.js.map
@@ -3,4 +3,6 @@
3
3
  */
4
4
  export * from './config.js';
5
5
  export * from './session.js';
6
+ export { fetchUserProjects, selectProject as selectProjectInteractive, quickProjectSwitch, displayProjectInfo, searchProjects, type ProjectInfo as ProjectSelectorInfo, } from './project-selector.js';
7
+ export * from './error-handler.js';
6
8
  //# sourceMappingURL=index.d.ts.map
@@ -3,4 +3,7 @@
3
3
  */
4
4
  export * from './config.js';
5
5
  export * from './session.js';
6
+ // project-selector has renamed exports to avoid conflicts with session.js
7
+ export { fetchUserProjects, selectProject as selectProjectInteractive, quickProjectSwitch, displayProjectInfo, searchProjects, } from './project-selector.js';
8
+ export * from './error-handler.js';
6
9
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,33 @@
1
+ /**
2
+ * ArchiCore CLI - Project Selector
3
+ *
4
+ * Auto-suggest projects when starting CLI
5
+ */
6
+ export interface ProjectInfo {
7
+ id: string;
8
+ name: string;
9
+ path?: string;
10
+ indexed?: boolean;
11
+ lastAccess?: string;
12
+ }
13
+ /**
14
+ * Fetch user's projects from server
15
+ */
16
+ export declare function fetchUserProjects(): Promise<ProjectInfo[]>;
17
+ /**
18
+ * Interactive project selector
19
+ */
20
+ export declare function selectProject(projects: ProjectInfo[]): Promise<ProjectInfo | null>;
21
+ /**
22
+ * Quick project switcher (for use during session)
23
+ */
24
+ export declare function quickProjectSwitch(currentProjectId: string | null): Promise<ProjectInfo | null>;
25
+ /**
26
+ * Display project info in a nice format
27
+ */
28
+ export declare function displayProjectInfo(project: ProjectInfo): void;
29
+ /**
30
+ * Search projects by name
31
+ */
32
+ export declare function searchProjects(projects: ProjectInfo[], query: string): ProjectInfo[];
33
+ //# sourceMappingURL=project-selector.d.ts.map
@@ -0,0 +1,148 @@
1
+ /**
2
+ * ArchiCore CLI - Project Selector
3
+ *
4
+ * Auto-suggest projects when starting CLI
5
+ */
6
+ import * as readline from 'readline';
7
+ import { colors, icons } from '../ui/colors.js';
8
+ import { loadConfig } from './config.js';
9
+ /**
10
+ * Fetch user's projects from server
11
+ */
12
+ export async function fetchUserProjects() {
13
+ try {
14
+ const config = await loadConfig();
15
+ if (!config.accessToken)
16
+ return [];
17
+ const response = await fetch(`${config.serverUrl}/api/projects`, {
18
+ headers: {
19
+ 'Authorization': `Bearer ${config.accessToken}`,
20
+ },
21
+ });
22
+ if (!response.ok)
23
+ return [];
24
+ const data = await response.json();
25
+ return (data.projects || data || []).map((p) => ({
26
+ id: p.id,
27
+ name: p.name,
28
+ path: p.path,
29
+ indexed: p.indexed,
30
+ lastAccess: p.lastAccess || p.updatedAt,
31
+ }));
32
+ }
33
+ catch {
34
+ return [];
35
+ }
36
+ }
37
+ /**
38
+ * Interactive project selector
39
+ */
40
+ export async function selectProject(projects) {
41
+ if (projects.length === 0) {
42
+ return null;
43
+ }
44
+ if (projects.length === 1) {
45
+ console.log(colors.muted(` Using project: ${colors.primary(projects[0].name)}`));
46
+ return projects[0];
47
+ }
48
+ // Sort by last access
49
+ const sorted = [...projects].sort((a, b) => {
50
+ if (!a.lastAccess)
51
+ return 1;
52
+ if (!b.lastAccess)
53
+ return -1;
54
+ return new Date(b.lastAccess).getTime() - new Date(a.lastAccess).getTime();
55
+ });
56
+ console.log();
57
+ console.log(colors.primary.bold(' Select a project:'));
58
+ console.log();
59
+ sorted.forEach((project, index) => {
60
+ const num = colors.secondary(` ${index + 1}.`);
61
+ const name = colors.highlight(project.name);
62
+ const indexed = project.indexed
63
+ ? colors.success(` ${icons.success}`)
64
+ : colors.muted(' (not indexed)');
65
+ const path = project.path ? colors.dim(` ${project.path}`) : '';
66
+ console.log(`${num} ${name}${indexed}${path}`);
67
+ });
68
+ console.log();
69
+ console.log(colors.muted(' Press number to select, Enter for first, or 0 to skip'));
70
+ return new Promise((resolve) => {
71
+ const rl = readline.createInterface({
72
+ input: process.stdin,
73
+ output: process.stdout,
74
+ terminal: true,
75
+ });
76
+ // Set up timeout for auto-select (5 seconds)
77
+ const timeout = setTimeout(() => {
78
+ console.log(colors.muted(` Auto-selecting: ${sorted[0].name}`));
79
+ rl.close();
80
+ resolve(sorted[0]);
81
+ }, 5000);
82
+ rl.question(colors.primary(` ${icons.arrow} `), (answer) => {
83
+ clearTimeout(timeout);
84
+ rl.close();
85
+ const trimmed = answer.trim();
86
+ if (!trimmed) {
87
+ // Default to first project
88
+ resolve(sorted[0]);
89
+ return;
90
+ }
91
+ if (trimmed === '0') {
92
+ resolve(null);
93
+ return;
94
+ }
95
+ const index = parseInt(trimmed) - 1;
96
+ if (index >= 0 && index < sorted.length) {
97
+ resolve(sorted[index]);
98
+ }
99
+ else {
100
+ console.log(colors.warning(' Invalid selection, using first project'));
101
+ resolve(sorted[0]);
102
+ }
103
+ });
104
+ });
105
+ }
106
+ /**
107
+ * Quick project switcher (for use during session)
108
+ */
109
+ export async function quickProjectSwitch(currentProjectId) {
110
+ const projects = await fetchUserProjects();
111
+ if (projects.length === 0) {
112
+ console.log(colors.warning(' No projects found'));
113
+ return null;
114
+ }
115
+ // Filter out current project
116
+ const available = projects.filter(p => p.id !== currentProjectId);
117
+ if (available.length === 0) {
118
+ console.log(colors.muted(' No other projects available'));
119
+ return null;
120
+ }
121
+ return selectProject(available);
122
+ }
123
+ /**
124
+ * Display project info in a nice format
125
+ */
126
+ export function displayProjectInfo(project) {
127
+ console.log();
128
+ console.log(colors.primary.bold(` ${icons.lightning} ${project.name}`));
129
+ if (project.path) {
130
+ console.log(colors.muted(` Path: ${project.path}`));
131
+ }
132
+ if (project.indexed) {
133
+ console.log(colors.success(` Status: Indexed ${icons.success}`));
134
+ }
135
+ else {
136
+ console.log(colors.warning(` Status: Not indexed (run /index)`));
137
+ }
138
+ console.log();
139
+ }
140
+ /**
141
+ * Search projects by name
142
+ */
143
+ export function searchProjects(projects, query) {
144
+ const lower = query.toLowerCase();
145
+ return projects.filter(p => p.name.toLowerCase().includes(lower) ||
146
+ p.path?.toLowerCase().includes(lower));
147
+ }
148
+ //# sourceMappingURL=project-selector.js.map
package/dist/cli.js CHANGED
@@ -12,7 +12,7 @@ import chalk from 'chalk';
12
12
  import ora from 'ora';
13
13
  import { writeFile } from 'fs/promises';
14
14
  // Import new CLI modules
15
- import { registerAnalyzerCommands, registerExportCommand, startInteractiveMode, initProject, } from './cli/index.js';
15
+ import { registerAnalyzerCommands, registerExportCommand, registerProjectsCommand, registerAuthCommands, startInteractiveMode, initProject, } from './cli/index.js';
16
16
  const program = new Command();
17
17
  program
18
18
  .name('archicore')
@@ -29,6 +29,8 @@ program
29
29
  // Register modular commands
30
30
  registerAnalyzerCommands(program);
31
31
  registerExportCommand(program);
32
+ registerProjectsCommand(program);
33
+ registerAuthCommands(program);
32
34
  // Interactive mode (default when no command)
33
35
  program
34
36
  .command('chat', { isDefault: false })
@@ -8,7 +8,7 @@ export declare class ASTParser {
8
8
  private parseWithRegex;
9
9
  private inferTypeFromPattern;
10
10
  private extractVueScript;
11
- parseProject(rootDir: string): Promise<Map<string, ASTNode>>;
11
+ parseProject(rootDir: string, progressCallback?: (current: number, total: number, file: string) => void): Promise<Map<string, ASTNode>>;
12
12
  private convertToASTNode;
13
13
  private extractName;
14
14
  extractLocation(node: Parser.SyntaxNode, filePath: string): Location;
@@ -278,11 +278,17 @@ export class ASTParser {
278
278
  isTypeScript
279
279
  };
280
280
  }
281
- async parseProject(rootDir) {
281
+ async parseProject(rootDir, progressCallback) {
282
282
  const files = await FileUtils.getAllFiles(rootDir);
283
283
  const asts = new Map();
284
- Logger.progress(`Parsing ${files.length} files...`);
285
- for (const file of files) {
284
+ const totalFiles = files.length;
285
+ Logger.progress(`Parsing ${totalFiles} files...`);
286
+ for (let i = 0; i < totalFiles; i++) {
287
+ const file = files[i];
288
+ // Emit progress
289
+ if (progressCallback) {
290
+ progressCallback(i + 1, totalFiles, file);
291
+ }
286
292
  const ast = await this.parseFile(file);
287
293
  if (ast) {
288
294
  asts.set(file, ast);
@@ -21,7 +21,7 @@ export declare class CodeIndex {
21
21
  private graph;
22
22
  constructor(rootDir?: string);
23
23
  indexProject(rootDir?: string): Promise<void>;
24
- parseProject(): Promise<Map<string, ASTNode>>;
24
+ parseProject(progressCallback?: (current: number, total: number, file: string) => void): Promise<Map<string, ASTNode>>;
25
25
  extractSymbols(asts: Map<string, ASTNode>): Map<string, Symbol>;
26
26
  buildDependencyGraph(asts: Map<string, ASTNode>, symbols: Map<string, Symbol>): DependencyGraph;
27
27
  getGraph(): DependencyGraph | null;
@@ -35,8 +35,8 @@ export class CodeIndex {
35
35
  Logger.success('Project indexed successfully');
36
36
  }
37
37
  // Методы для использования в ProjectService
38
- async parseProject() {
39
- this.asts = await this.astParser.parseProject(this.rootDir);
38
+ async parseProject(progressCallback) {
39
+ this.asts = await this.astParser.parseProject(this.rootDir, progressCallback);
40
40
  return this.asts;
41
41
  }
42
42
  extractSymbols(asts) {
@@ -103,7 +103,11 @@ export class GitHubService {
103
103
  state,
104
104
  allow_signup: 'true'
105
105
  });
106
- return `https://github.com/login/oauth/authorize?${params.toString()}`;
106
+ const url = `https://github.com/login/oauth/authorize?${params.toString()}`;
107
+ Logger.info(`GitHub OAuth URL: ${url}`);
108
+ Logger.info(`Requested scopes: ${scopes.join(', ')}`);
109
+ Logger.info(`Redirect URI: ${this.redirectUri}`);
110
+ return url;
107
111
  }
108
112
  /**
109
113
  * Exchange authorization code for access token
@@ -126,6 +130,9 @@ export class GitHubService {
126
130
  throw new Error('Failed to exchange code for token');
127
131
  }
128
132
  const data = await response.json();
133
+ // Debug: log full token response (without sensitive data)
134
+ Logger.info(`GitHub token response keys: ${Object.keys(data).join(', ')}`);
135
+ Logger.info(`GitHub token response scope field: "${data.scope}" (type: ${typeof data.scope})`);
129
136
  if (data.error) {
130
137
  throw new Error(data.error_description || data.error);
131
138
  }
@@ -11,6 +11,33 @@ import Anthropic from '@anthropic-ai/sdk';
11
11
  import OpenAI from 'openai';
12
12
  import { Logger } from '../utils/logger.js';
13
13
  import { DeepSeekOptimizer } from './deepseek-optimizer.js';
14
+ /**
15
+ * Sanitize file paths to hide server directories
16
+ * Converts absolute paths to relative project paths
17
+ */
18
+ function sanitizePath(filePath) {
19
+ if (!filePath)
20
+ return filePath;
21
+ // Common server path patterns to strip
22
+ const serverPatterns = [
23
+ /^\/scripts\/archicore\/.archicore\/projects\/[^/]+\//,
24
+ /^\/home\/[^/]+\/[^/]+\/.archicore\/projects\/[^/]+\//,
25
+ /^C:\\[^\\]+\\[^\\]+\\.archicore\\projects\\[^\\]+\\/i,
26
+ /^\/var\/[^/]+\/.archicore\/projects\/[^/]+\//,
27
+ /^.*\.archicore[\/\\]projects[\/\\][^\/\\]+[\/\\]/
28
+ ];
29
+ for (const pattern of serverPatterns) {
30
+ if (pattern.test(filePath)) {
31
+ return filePath.replace(pattern, '');
32
+ }
33
+ }
34
+ // If path contains .archicore/projects, strip everything before the actual project path
35
+ const archiMatch = filePath.match(/\.archicore[\/\\]projects[\/\\][^\/\\]+[\/\\](.+)/);
36
+ if (archiMatch) {
37
+ return archiMatch[1];
38
+ }
39
+ return filePath;
40
+ }
14
41
  export class LLMOrchestrator {
15
42
  anthropic;
16
43
  openai;
@@ -291,7 +318,8 @@ ABSOLUTE RULES:
291
318
  if (context?.semanticMemory && context.semanticMemory.length > 0) {
292
319
  prompt += '\n\n###PROJECT FILES (ONLY reference these - nothing else exists)###\n';
293
320
  for (const result of context.semanticMemory.slice(0, 10)) {
294
- prompt += `\nФайл: ${result.chunk.metadata.filePath}\n`;
321
+ const cleanPath = sanitizePath(result.chunk.metadata.filePath);
322
+ prompt += `\nФайл: ${cleanPath}\n`;
295
323
  prompt += `${result.context}\n`;
296
324
  }
297
325
  prompt += '\n###END PROJECT FILES###';
@@ -15,7 +15,10 @@ export declare class EmbeddingService {
15
15
  isAvailable(): boolean;
16
16
  generateEmbedding(text: string): Promise<number[]>;
17
17
  private generateJinaEmbedding;
18
- generateBatchEmbeddings(texts: string[]): Promise<number[][]>;
18
+ /**
19
+ * Generate embeddings for multiple texts - uses true batch API when available
20
+ */
21
+ generateBatchEmbeddings(texts: string[], progressCallback?: (current: number, total: number) => void): Promise<number[][]>;
19
22
  private generateOpenAIEmbedding;
20
23
  prepareCodeForEmbedding(code: string, context?: string): string;
21
24
  generateCodeEmbedding(code: string, metadata: {
@@ -96,23 +96,70 @@ export class EmbeddingService {
96
96
  const data = await response.json();
97
97
  return data.data[0].embedding;
98
98
  }
99
- async generateBatchEmbeddings(texts) {
99
+ /**
100
+ * Generate embeddings for multiple texts - uses true batch API when available
101
+ */
102
+ async generateBatchEmbeddings(texts, progressCallback) {
100
103
  this.ensureInitialized();
101
104
  if (!this._isAvailable) {
102
105
  Logger.warn('Embedding service not available, skipping');
103
106
  return texts.map(() => new Array(this.embeddingDimension).fill(0));
104
107
  }
105
- Logger.progress('Generating embeddings for ' + texts.length + ' texts...');
106
- const embeddings = [];
107
- const batchSize = 100;
108
- for (let i = 0; i < texts.length; i += batchSize) {
109
- const batch = texts.slice(i, i + batchSize);
110
- const batchEmbeddings = await Promise.all(batch.map(text => this.generateEmbedding(text)));
111
- embeddings.push(...batchEmbeddings);
112
- Logger.debug('Processed ' + Math.min(i + batchSize, texts.length) + '/' + texts.length);
108
+ if (texts.length === 0)
109
+ return [];
110
+ Logger.progress(`Generating embeddings for ${texts.length} texts...`);
111
+ try {
112
+ // Use true batch API for Jina (much faster!)
113
+ if (this.config.provider === 'jina' && this.jinaApiKey) {
114
+ const BATCH_SIZE = 500;
115
+ const allEmbeddings = [];
116
+ for (let i = 0; i < texts.length; i += BATCH_SIZE) {
117
+ const batch = texts.slice(i, i + BATCH_SIZE).map(t => t.substring(0, 8000));
118
+ const response = await fetch('https://api.jina.ai/v1/embeddings', {
119
+ method: 'POST',
120
+ headers: {
121
+ 'Content-Type': 'application/json',
122
+ 'Authorization': `Bearer ${this.jinaApiKey}`
123
+ },
124
+ body: JSON.stringify({
125
+ model: this.config.model || 'jina-embeddings-v3',
126
+ task: 'text-matching',
127
+ dimensions: 1024,
128
+ input: batch
129
+ })
130
+ });
131
+ if (!response.ok) {
132
+ const error = await response.text();
133
+ throw new Error(`Jina API error: ${response.status} ${error}`);
134
+ }
135
+ const data = await response.json();
136
+ allEmbeddings.push(...data.data.map(d => d.embedding));
137
+ if (progressCallback) {
138
+ progressCallback(Math.min(i + BATCH_SIZE, texts.length), texts.length);
139
+ }
140
+ }
141
+ Logger.success(`Generated ${allEmbeddings.length} embeddings (batch mode)`);
142
+ return allEmbeddings;
143
+ }
144
+ // Fallback: parallel individual calls for OpenAI
145
+ const embeddings = [];
146
+ const CONCURRENT_LIMIT = 10; // Limit concurrent requests
147
+ for (let i = 0; i < texts.length; i += CONCURRENT_LIMIT) {
148
+ const batch = texts.slice(i, i + CONCURRENT_LIMIT);
149
+ const batchEmbeddings = await Promise.all(batch.map(text => this.generateEmbedding(text)));
150
+ embeddings.push(...batchEmbeddings);
151
+ if (progressCallback) {
152
+ progressCallback(Math.min(i + CONCURRENT_LIMIT, texts.length), texts.length);
153
+ }
154
+ }
155
+ Logger.success(`Generated ${embeddings.length} embeddings`);
156
+ return embeddings;
157
+ }
158
+ catch (error) {
159
+ Logger.error('Batch embedding failed:', error);
160
+ // Return zero vectors on error
161
+ return texts.map(() => new Array(this.embeddingDimension).fill(0));
113
162
  }
114
- Logger.success('Generated ' + embeddings.length + ' embeddings');
115
- return embeddings;
116
163
  }
117
164
  async generateOpenAIEmbedding(text) {
118
165
  if (!this.openai)
@@ -15,7 +15,7 @@ export declare class SemanticMemory {
15
15
  private vectorStore;
16
16
  constructor(embeddingConfig: EmbeddingConfig, vectorStoreConfig: VectorStoreConfig);
17
17
  initialize(): Promise<void>;
18
- indexSymbols(symbols: Map<string, Symbol>, asts: Map<string, ASTNode>): Promise<void>;
18
+ indexSymbols(symbols: Map<string, Symbol>, asts: Map<string, ASTNode>, progressCallback?: (current: number, total: number) => void): Promise<void>;
19
19
  indexModules(asts: Map<string, ASTNode>): Promise<void>;
20
20
  searchByQuery(query: string, limit?: number): Promise<SemanticSearchResult[]>;
21
21
  searchSimilarCode(code: string, metadata?: {