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.
- package/dist/cli/commands/auth.d.ts +4 -0
- package/dist/cli/commands/auth.js +58 -0
- package/dist/cli/commands/index.d.ts +2 -1
- package/dist/cli/commands/index.js +2 -1
- package/dist/cli/commands/interactive.js +107 -59
- package/dist/cli/ui/index.d.ts +1 -0
- package/dist/cli/ui/index.js +1 -0
- package/dist/cli/ui/progress.d.ts +57 -0
- package/dist/cli/ui/progress.js +149 -0
- package/dist/cli/utils/error-handler.d.ts +40 -0
- package/dist/cli/utils/error-handler.js +234 -0
- package/dist/cli/utils/index.d.ts +2 -0
- package/dist/cli/utils/index.js +3 -0
- package/dist/cli/utils/project-selector.d.ts +33 -0
- package/dist/cli/utils/project-selector.js +148 -0
- package/dist/cli.js +3 -1
- package/dist/code-index/ast-parser.d.ts +1 -1
- package/dist/code-index/ast-parser.js +9 -3
- package/dist/code-index/index.d.ts +1 -1
- package/dist/code-index/index.js +2 -2
- package/dist/github/github-service.js +8 -1
- package/dist/orchestrator/index.js +29 -1
- package/dist/semantic-memory/embedding-service.d.ts +4 -1
- package/dist/semantic-memory/embedding-service.js +58 -11
- package/dist/semantic-memory/index.d.ts +1 -1
- package/dist/semantic-memory/index.js +54 -7
- package/dist/server/config.d.ts +52 -0
- package/dist/server/config.js +88 -0
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.js +115 -10
- package/dist/server/routes/admin.js +3 -3
- package/dist/server/routes/api.js +199 -2
- package/dist/server/routes/auth.js +79 -5
- package/dist/server/routes/device-auth.js +1 -1
- package/dist/server/routes/github.js +33 -0
- package/dist/server/services/auth-service.d.ts +5 -0
- package/dist/server/services/auth-service.js +10 -0
- package/dist/server/services/project-service.d.ts +15 -1
- package/dist/server/services/project-service.js +185 -26
- package/dist/types/user.d.ts +2 -2
- package/dist/types/user.js +13 -4
- package/package.json +9 -1
|
@@ -5,7 +5,7 @@ import { Router } from 'express';
|
|
|
5
5
|
import { AuthService } from '../services/auth-service.js';
|
|
6
6
|
import { Logger } from '../../utils/logger.js';
|
|
7
7
|
export const authRouter = Router();
|
|
8
|
-
const authService =
|
|
8
|
+
const authService = AuthService.getInstance();
|
|
9
9
|
// Middleware to check authentication
|
|
10
10
|
export async function authMiddleware(req, res, next) {
|
|
11
11
|
const authHeader = req.headers.authorization;
|
|
@@ -91,7 +91,7 @@ authRouter.post('/logout', authMiddleware, async (req, res) => {
|
|
|
91
91
|
});
|
|
92
92
|
/**
|
|
93
93
|
* GET /api/auth/me
|
|
94
|
-
* Get current user info
|
|
94
|
+
* Get current user info with usage and limits
|
|
95
95
|
*/
|
|
96
96
|
authRouter.get('/me', authMiddleware, async (req, res) => {
|
|
97
97
|
try {
|
|
@@ -99,9 +99,27 @@ authRouter.get('/me', authMiddleware, async (req, res) => {
|
|
|
99
99
|
res.status(401).json({ error: 'Not authenticated' });
|
|
100
100
|
return;
|
|
101
101
|
}
|
|
102
|
+
// Get fresh user data with usage
|
|
103
|
+
const freshUser = await authService.getUser(req.user.id);
|
|
104
|
+
if (!freshUser) {
|
|
105
|
+
res.status(404).json({ error: 'User not found' });
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
102
108
|
// Remove sensitive data
|
|
103
|
-
const { passwordHash, ...user } =
|
|
104
|
-
|
|
109
|
+
const { passwordHash, ...user } = freshUser;
|
|
110
|
+
// Get tier limits
|
|
111
|
+
const { TIER_LIMITS } = await import('../../types/user.js');
|
|
112
|
+
const limits = TIER_LIMITS[user.tier];
|
|
113
|
+
res.json({
|
|
114
|
+
user,
|
|
115
|
+
limits: {
|
|
116
|
+
requestsPerDay: limits.requestsPerDay,
|
|
117
|
+
fullAnalysisPerDay: limits.fullAnalysisPerDay,
|
|
118
|
+
projectsPerDay: limits.projectsPerDay,
|
|
119
|
+
maxProjectSizeMB: limits.maxProjectSizeMB
|
|
120
|
+
},
|
|
121
|
+
usage: user.usage
|
|
122
|
+
});
|
|
105
123
|
}
|
|
106
124
|
catch (error) {
|
|
107
125
|
Logger.error('Get user error:', error);
|
|
@@ -138,6 +156,53 @@ authRouter.put('/me', authMiddleware, async (req, res) => {
|
|
|
138
156
|
res.status(500).json({ error: 'Failed to update profile' });
|
|
139
157
|
}
|
|
140
158
|
});
|
|
159
|
+
/**
|
|
160
|
+
* POST /api/auth/avatar
|
|
161
|
+
* Upload user avatar (base64 image)
|
|
162
|
+
*/
|
|
163
|
+
authRouter.post('/avatar', authMiddleware, async (req, res) => {
|
|
164
|
+
try {
|
|
165
|
+
const { avatar } = req.body;
|
|
166
|
+
if (!req.user) {
|
|
167
|
+
res.status(401).json({ error: 'Not authenticated' });
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
// Validate avatar - must be a valid base64 image data URL
|
|
171
|
+
if (!avatar || typeof avatar !== 'string') {
|
|
172
|
+
res.status(400).json({ error: 'Avatar is required' });
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
// Security: Only allow specific image formats
|
|
176
|
+
const validImagePattern = /^data:image\/(png|jpeg|jpg|gif|webp);base64,/;
|
|
177
|
+
if (!validImagePattern.test(avatar)) {
|
|
178
|
+
res.status(400).json({ error: 'Invalid image format. Only PNG, JPEG, GIF, and WebP are allowed.' });
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
// Limit avatar size (max 500KB base64 = ~375KB actual image)
|
|
182
|
+
const maxBase64Size = 500 * 1024;
|
|
183
|
+
if (avatar.length > maxBase64Size) {
|
|
184
|
+
res.status(400).json({ error: 'Avatar image is too large. Maximum size is 500KB.' });
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
// Additional security: Check for script injection in base64
|
|
188
|
+
const base64Data = avatar.split(',')[1];
|
|
189
|
+
if (!base64Data || /<script|javascript:|onerror|onload/i.test(base64Data)) {
|
|
190
|
+
res.status(400).json({ error: 'Invalid image data' });
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
// Update user avatar
|
|
194
|
+
const updated = await authService.updateUser(req.user.id, { avatar });
|
|
195
|
+
if (!updated) {
|
|
196
|
+
res.status(400).json({ error: 'Failed to update avatar' });
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
res.json({ success: true, avatar });
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
Logger.error('Avatar upload error:', error);
|
|
203
|
+
res.status(500).json({ error: 'Failed to upload avatar' });
|
|
204
|
+
}
|
|
205
|
+
});
|
|
141
206
|
/**
|
|
142
207
|
* POST /api/auth/oauth/:provider
|
|
143
208
|
* OAuth login (GitHub, Google)
|
|
@@ -164,7 +229,7 @@ authRouter.post('/oauth/:provider', async (req, res) => {
|
|
|
164
229
|
});
|
|
165
230
|
/**
|
|
166
231
|
* GET /api/auth/usage
|
|
167
|
-
* Get current user's usage stats
|
|
232
|
+
* Get current user's usage stats with limits
|
|
168
233
|
*/
|
|
169
234
|
authRouter.get('/usage', authMiddleware, async (req, res) => {
|
|
170
235
|
try {
|
|
@@ -177,9 +242,18 @@ authRouter.get('/usage', authMiddleware, async (req, res) => {
|
|
|
177
242
|
res.status(404).json({ error: 'User not found' });
|
|
178
243
|
return;
|
|
179
244
|
}
|
|
245
|
+
// Get tier limits
|
|
246
|
+
const { TIER_LIMITS } = await import('../../types/user.js');
|
|
247
|
+
const limits = TIER_LIMITS[user.tier];
|
|
180
248
|
res.json({
|
|
181
249
|
tier: user.tier,
|
|
182
250
|
usage: user.usage,
|
|
251
|
+
limits: {
|
|
252
|
+
requestsPerDay: limits.requestsPerDay,
|
|
253
|
+
fullAnalysisPerDay: limits.fullAnalysisPerDay,
|
|
254
|
+
projectsPerDay: limits.projectsPerDay,
|
|
255
|
+
maxProjectSizeMB: limits.maxProjectSizeMB
|
|
256
|
+
},
|
|
183
257
|
subscription: user.subscription
|
|
184
258
|
});
|
|
185
259
|
}
|
|
@@ -7,7 +7,7 @@ import { Router } from 'express';
|
|
|
7
7
|
import { randomBytes } from 'crypto';
|
|
8
8
|
import { AuthService } from '../services/auth-service.js';
|
|
9
9
|
const router = Router();
|
|
10
|
-
const authService =
|
|
10
|
+
const authService = AuthService.getInstance();
|
|
11
11
|
const pendingAuths = new Map();
|
|
12
12
|
// Generate random codes
|
|
13
13
|
function generateDeviceCode() {
|
|
@@ -10,12 +10,15 @@ import { join } from 'path';
|
|
|
10
10
|
import AdmZip from 'adm-zip';
|
|
11
11
|
import { GitHubService } from '../../github/github-service.js';
|
|
12
12
|
import { ProjectService } from '../services/project-service.js';
|
|
13
|
+
import { AuthService } from '../services/auth-service.js';
|
|
13
14
|
import { authMiddleware } from './auth.js';
|
|
14
15
|
import { Logger } from '../../utils/logger.js';
|
|
16
|
+
import { TIER_LIMITS } from '../../types/user.js';
|
|
15
17
|
export const githubRouter = Router();
|
|
16
18
|
// Services
|
|
17
19
|
const githubService = new GitHubService();
|
|
18
20
|
const projectService = new ProjectService();
|
|
21
|
+
const authService = AuthService.getInstance();
|
|
19
22
|
// Store OAuth states (in production, use Redis)
|
|
20
23
|
const oauthStates = new Map();
|
|
21
24
|
// ===== OAUTH FLOW =====
|
|
@@ -76,8 +79,12 @@ githubRouter.get('/callback', async (req, res) => {
|
|
|
76
79
|
oauthStates.delete(state);
|
|
77
80
|
// Exchange code for token
|
|
78
81
|
const tokenResponse = await githubService.exchangeCodeForToken(code);
|
|
82
|
+
// Log granted scopes for debugging
|
|
83
|
+
Logger.info(`GitHub OAuth scopes granted: ${tokenResponse.scope}`);
|
|
84
|
+
Logger.info(`GitHub token type: ${tokenResponse.token_type}`);
|
|
79
85
|
// Get GitHub user info
|
|
80
86
|
const githubUser = await githubService.getGitHubUser(tokenResponse.access_token);
|
|
87
|
+
Logger.info(`GitHub user connected: ${githubUser.login}`);
|
|
81
88
|
// Save connection
|
|
82
89
|
await githubService.saveConnection(stateData.userId, tokenResponse, githubUser);
|
|
83
90
|
// Redirect to app with success
|
|
@@ -268,6 +275,22 @@ githubRouter.post('/repositories/connect', authMiddleware, async (req, res) => {
|
|
|
268
275
|
Logger.progress(`Downloading repository: ${connectedRepo.fullName} (branch: ${targetBranch})`);
|
|
269
276
|
const zipBuffer = await githubService.downloadRepository(req.user.id, connectedRepo.fullName, targetBranch);
|
|
270
277
|
Logger.info(`Downloaded ZIP: ${zipBuffer.length} bytes`);
|
|
278
|
+
// Check project size limit
|
|
279
|
+
const user = await authService.getUser(req.user.id);
|
|
280
|
+
if (user) {
|
|
281
|
+
const limits = TIER_LIMITS[user.tier];
|
|
282
|
+
const projectSizeMB = zipBuffer.length / (1024 * 1024);
|
|
283
|
+
if (projectSizeMB > limits.maxProjectSizeMB) {
|
|
284
|
+
res.status(413).json({
|
|
285
|
+
error: 'Project size limit exceeded',
|
|
286
|
+
message: `Project size (${projectSizeMB.toFixed(1)}MB) exceeds your plan limit (${limits.maxProjectSizeMB}MB). Upgrade to a higher tier for larger projects.`,
|
|
287
|
+
size: projectSizeMB,
|
|
288
|
+
limit: limits.maxProjectSizeMB,
|
|
289
|
+
tier: user.tier
|
|
290
|
+
});
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
271
294
|
// Create projects directory
|
|
272
295
|
const projectsDir = process.env.PROJECTS_DIR || join('.archicore', 'projects');
|
|
273
296
|
await mkdir(projectsDir, { recursive: true });
|
|
@@ -322,6 +345,16 @@ githubRouter.post('/repositories/connect', authMiddleware, async (req, res) => {
|
|
|
322
345
|
}
|
|
323
346
|
}
|
|
324
347
|
Logger.success(`Downloaded and extracted to: ${actualPath}`);
|
|
348
|
+
// Check project creation limit
|
|
349
|
+
const usageResult = await authService.checkAndUpdateUsage(req.user.id, 'project');
|
|
350
|
+
if (!usageResult.allowed) {
|
|
351
|
+
res.status(429).json({
|
|
352
|
+
error: 'Project limit reached',
|
|
353
|
+
message: `You have reached your daily project limit (${usageResult.limit})`,
|
|
354
|
+
usage: { used: usageResult.limit, limit: usageResult.limit, remaining: 0 }
|
|
355
|
+
});
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
325
358
|
const project = await projectService.createProject(projectName || connectedRepo.name, actualPath, req.user.id);
|
|
326
359
|
projectId = project.id;
|
|
327
360
|
// Update connected repo with project ID and save to file
|
|
@@ -3,11 +3,16 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { User, SubscriptionTier, AuthResponse } from '../../types/user.js';
|
|
5
5
|
export declare class AuthService {
|
|
6
|
+
private static instance;
|
|
6
7
|
private dataDir;
|
|
7
8
|
private users;
|
|
8
9
|
private sessions;
|
|
9
10
|
private initialized;
|
|
10
11
|
constructor(dataDir?: string);
|
|
12
|
+
/**
|
|
13
|
+
* Get singleton instance of AuthService
|
|
14
|
+
*/
|
|
15
|
+
static getInstance(): AuthService;
|
|
11
16
|
private ensureInitialized;
|
|
12
17
|
private createDefaultAdmin;
|
|
13
18
|
private saveUsers;
|
|
@@ -10,6 +10,7 @@ const DATA_DIR = '.archicore';
|
|
|
10
10
|
const USERS_FILE = 'users.json';
|
|
11
11
|
const SESSIONS_FILE = 'sessions.json';
|
|
12
12
|
export class AuthService {
|
|
13
|
+
static instance = null;
|
|
13
14
|
dataDir;
|
|
14
15
|
users = [];
|
|
15
16
|
sessions = [];
|
|
@@ -17,6 +18,15 @@ export class AuthService {
|
|
|
17
18
|
constructor(dataDir = DATA_DIR) {
|
|
18
19
|
this.dataDir = dataDir;
|
|
19
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Get singleton instance of AuthService
|
|
23
|
+
*/
|
|
24
|
+
static getInstance() {
|
|
25
|
+
if (!AuthService.instance) {
|
|
26
|
+
AuthService.instance = new AuthService();
|
|
27
|
+
}
|
|
28
|
+
return AuthService.instance;
|
|
29
|
+
}
|
|
20
30
|
async ensureInitialized() {
|
|
21
31
|
if (this.initialized)
|
|
22
32
|
return;
|
|
@@ -28,6 +28,20 @@ export interface ProjectStats {
|
|
|
28
28
|
nodesCount: number;
|
|
29
29
|
edgesCount: number;
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Progress callback type for indexing
|
|
33
|
+
*/
|
|
34
|
+
export interface IndexingProgress {
|
|
35
|
+
step: 'scanning' | 'parsing' | 'symbols' | 'graph' | 'embeddings' | 'complete' | 'error';
|
|
36
|
+
stepNumber: number;
|
|
37
|
+
totalSteps: number;
|
|
38
|
+
current: number;
|
|
39
|
+
total: number;
|
|
40
|
+
message: string;
|
|
41
|
+
percentage: number;
|
|
42
|
+
}
|
|
43
|
+
export type ProgressCallback = (progress: IndexingProgress) => void;
|
|
44
|
+
export declare function subscribeToProgress(projectId: string, callback: ProgressCallback): () => void;
|
|
31
45
|
export declare class ProjectService {
|
|
32
46
|
private projects;
|
|
33
47
|
private projectData;
|
|
@@ -159,7 +173,7 @@ export declare class ProjectService {
|
|
|
159
173
|
*/
|
|
160
174
|
private getFileContents;
|
|
161
175
|
/**
|
|
162
|
-
* Генерация документации по коду проекта
|
|
176
|
+
* Генерация документации по коду проекта с использованием AI
|
|
163
177
|
*/
|
|
164
178
|
generateDocumentation(projectId: string, options?: {
|
|
165
179
|
format?: string;
|
|
@@ -22,6 +22,66 @@ import { DuplicationDetector } from '../../analyzers/duplication.js';
|
|
|
22
22
|
import { SecurityAnalyzer } from '../../analyzers/security.js';
|
|
23
23
|
import { RefactoringEngine } from '../../refactoring/index.js';
|
|
24
24
|
import { GitHubService } from '../../github/github-service.js';
|
|
25
|
+
/**
|
|
26
|
+
* Sanitize file paths to hide server directories
|
|
27
|
+
* Converts absolute paths to relative project paths
|
|
28
|
+
*/
|
|
29
|
+
function sanitizePath(filePath) {
|
|
30
|
+
if (!filePath)
|
|
31
|
+
return filePath;
|
|
32
|
+
// Common server path patterns to strip
|
|
33
|
+
const serverPatterns = [
|
|
34
|
+
/^\/scripts\/archicore\/.archicore\/projects\/[^/]+\/[^/]+\//,
|
|
35
|
+
/^\/scripts\/archicore\/.archicore\/projects\/[^/]+\//,
|
|
36
|
+
/^\/home\/[^/]+\/[^/]+\/.archicore\/projects\/[^/]+\//,
|
|
37
|
+
/^C:\\[^\\]+\\[^\\]+\\.archicore\\projects\\[^\\]+\\/i,
|
|
38
|
+
/^\/var\/[^/]+\/.archicore\/projects\/[^/]+\//,
|
|
39
|
+
/^.*\.archicore[\/\\]projects[\/\\][^\/\\]+[\/\\][^\/\\]+[\/\\]/,
|
|
40
|
+
/^.*\.archicore[\/\\]projects[\/\\][^\/\\]+[\/\\]/
|
|
41
|
+
];
|
|
42
|
+
for (const pattern of serverPatterns) {
|
|
43
|
+
if (pattern.test(filePath)) {
|
|
44
|
+
return filePath.replace(pattern, '');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// If path contains .archicore/projects, strip everything before the actual project path
|
|
48
|
+
const archiMatch = filePath.match(/\.archicore[\/\\]projects[\/\\][^\/\\]+[\/\\](?:[^\/\\]+[\/\\])?(.+)/);
|
|
49
|
+
if (archiMatch) {
|
|
50
|
+
return archiMatch[1];
|
|
51
|
+
}
|
|
52
|
+
return filePath;
|
|
53
|
+
}
|
|
54
|
+
// Store active progress callbacks for SSE
|
|
55
|
+
const progressCallbacks = new Map();
|
|
56
|
+
export function subscribeToProgress(projectId, callback) {
|
|
57
|
+
if (!progressCallbacks.has(projectId)) {
|
|
58
|
+
progressCallbacks.set(projectId, []);
|
|
59
|
+
}
|
|
60
|
+
progressCallbacks.get(projectId).push(callback);
|
|
61
|
+
// Return unsubscribe function
|
|
62
|
+
return () => {
|
|
63
|
+
const callbacks = progressCallbacks.get(projectId);
|
|
64
|
+
if (callbacks) {
|
|
65
|
+
const index = callbacks.indexOf(callback);
|
|
66
|
+
if (index > -1) {
|
|
67
|
+
callbacks.splice(index, 1);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function emitProgress(projectId, progress) {
|
|
73
|
+
const callbacks = progressCallbacks.get(projectId);
|
|
74
|
+
if (callbacks) {
|
|
75
|
+
for (const cb of callbacks) {
|
|
76
|
+
try {
|
|
77
|
+
cb(progress);
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
// Ignore callback errors
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
25
85
|
// Singleton instance
|
|
26
86
|
let instance = null;
|
|
27
87
|
export class ProjectService {
|
|
@@ -173,24 +233,54 @@ export class ProjectService {
|
|
|
173
233
|
}
|
|
174
234
|
project.status = 'indexing';
|
|
175
235
|
await this.saveProjects();
|
|
236
|
+
const totalSteps = 5;
|
|
237
|
+
const sendProgress = (step, stepNumber, current, total, message) => {
|
|
238
|
+
const basePercentage = ((stepNumber - 1) / totalSteps) * 100;
|
|
239
|
+
const stepPercentage = total > 0 ? (current / total) * (100 / totalSteps) : 0;
|
|
240
|
+
const percentage = Math.min(100, Math.round(basePercentage + stepPercentage));
|
|
241
|
+
emitProgress(projectId, {
|
|
242
|
+
step,
|
|
243
|
+
stepNumber,
|
|
244
|
+
totalSteps,
|
|
245
|
+
current,
|
|
246
|
+
total,
|
|
247
|
+
message,
|
|
248
|
+
percentage
|
|
249
|
+
});
|
|
250
|
+
};
|
|
176
251
|
try {
|
|
177
252
|
const data = await this.getProjectData(projectId);
|
|
178
253
|
Logger.progress(`Indexing project: ${project.name}`);
|
|
254
|
+
sendProgress('scanning', 1, 0, 1, 'Scanning project files...');
|
|
179
255
|
// 1. Парсинг AST
|
|
180
|
-
|
|
256
|
+
sendProgress('parsing', 1, 0, 1, 'Parsing source files...');
|
|
257
|
+
const asts = await data.codeIndex.parseProject((current, total, file) => {
|
|
258
|
+
sendProgress('parsing', 1, current, total, `Parsing: ${file.split(/[/\\]/).pop()}`);
|
|
259
|
+
});
|
|
181
260
|
data.asts = asts;
|
|
182
261
|
// 2. Извлечение символов
|
|
262
|
+
sendProgress('symbols', 2, 0, 1, 'Extracting symbols...');
|
|
183
263
|
const symbols = data.codeIndex.extractSymbols(asts);
|
|
184
264
|
data.symbols = symbols;
|
|
265
|
+
sendProgress('symbols', 2, 1, 1, `Extracted ${symbols.size} symbols`);
|
|
185
266
|
// 3. Построение графа зависимостей
|
|
267
|
+
sendProgress('graph', 3, 0, 1, 'Building dependency graph...');
|
|
186
268
|
const graph = data.codeIndex.buildDependencyGraph(asts, symbols);
|
|
187
269
|
data.graph = graph;
|
|
270
|
+
sendProgress('graph', 3, 1, 1, `Built graph: ${graph.nodes.size} nodes`);
|
|
188
271
|
// 4. Индексация в семантическую память (если доступна)
|
|
189
272
|
if (data.semanticMemory) {
|
|
273
|
+
sendProgress('embeddings', 4, 0, symbols.size, 'Initializing vector store...');
|
|
190
274
|
await data.semanticMemory.initialize();
|
|
191
|
-
await data.semanticMemory.indexSymbols(symbols, asts)
|
|
275
|
+
await data.semanticMemory.indexSymbols(symbols, asts, (current, total) => {
|
|
276
|
+
sendProgress('embeddings', 4, current, total, `Generating embeddings: ${current}/${total}`);
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
sendProgress('embeddings', 4, 1, 1, 'Skipping embeddings (no API key)');
|
|
192
281
|
}
|
|
193
282
|
// 5. Загрузка архитектурных знаний
|
|
283
|
+
sendProgress('complete', 5, 0, 1, 'Loading architecture...');
|
|
194
284
|
await data.architecture.load();
|
|
195
285
|
// Обновляем статус и статистику
|
|
196
286
|
const stats = {
|
|
@@ -203,12 +293,22 @@ export class ProjectService {
|
|
|
203
293
|
project.lastIndexedAt = new Date().toISOString();
|
|
204
294
|
project.stats = stats;
|
|
205
295
|
await this.saveProjects();
|
|
296
|
+
sendProgress('complete', 5, 1, 1, 'Indexing complete!');
|
|
206
297
|
Logger.success(`Project indexed: ${stats.filesCount} files, ${stats.symbolsCount} symbols`);
|
|
207
298
|
return { success: true, stats };
|
|
208
299
|
}
|
|
209
300
|
catch (error) {
|
|
210
301
|
project.status = 'error';
|
|
211
302
|
await this.saveProjects();
|
|
303
|
+
emitProgress(projectId, {
|
|
304
|
+
step: 'error',
|
|
305
|
+
stepNumber: 0,
|
|
306
|
+
totalSteps,
|
|
307
|
+
current: 0,
|
|
308
|
+
total: 0,
|
|
309
|
+
message: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
310
|
+
percentage: 0
|
|
311
|
+
});
|
|
212
312
|
Logger.error('Indexing failed:', error);
|
|
213
313
|
throw error;
|
|
214
314
|
}
|
|
@@ -758,7 +858,7 @@ export class ProjectService {
|
|
|
758
858
|
return fileContents;
|
|
759
859
|
}
|
|
760
860
|
/**
|
|
761
|
-
* Генерация документации по коду проекта
|
|
861
|
+
* Генерация документации по коду проекта с использованием AI
|
|
762
862
|
*/
|
|
763
863
|
async generateDocumentation(projectId, options = {}) {
|
|
764
864
|
const project = this.projects.get(projectId);
|
|
@@ -773,24 +873,91 @@ export class ProjectService {
|
|
|
773
873
|
// Собираем информацию о проекте
|
|
774
874
|
const stats = project.stats;
|
|
775
875
|
const symbols = data.symbols ? Array.from(data.symbols.values()) : [];
|
|
776
|
-
// graph is available via data.graph if needed for more detailed docs
|
|
777
876
|
// Группируем символы по типу (kind)
|
|
778
877
|
const classes = symbols.filter(s => s.kind === 'class');
|
|
779
878
|
const functions = symbols.filter(s => s.kind === 'function');
|
|
780
879
|
const interfaces = symbols.filter(s => s.kind === 'interface');
|
|
781
|
-
// Формируем структуру файлов
|
|
782
|
-
const files = fileContents ? Array.from(fileContents.keys()) : [];
|
|
880
|
+
// Формируем структуру файлов (с очисткой путей)
|
|
881
|
+
const files = fileContents ? Array.from(fileContents.keys()).map(sanitizePath) : [];
|
|
783
882
|
const fileStructure = this.buildFileStructure(files);
|
|
883
|
+
// Собираем код для анализа AI (первые 10 важных файлов)
|
|
884
|
+
const codeSnippets = [];
|
|
885
|
+
if (fileContents) {
|
|
886
|
+
const importantFiles = Array.from(fileContents.entries())
|
|
887
|
+
.filter(([path]) => !path.includes('node_modules') && !path.includes('.git'))
|
|
888
|
+
.slice(0, 10);
|
|
889
|
+
for (const [filePath, content] of importantFiles) {
|
|
890
|
+
const cleanPath = sanitizePath(filePath);
|
|
891
|
+
const truncatedContent = content.substring(0, 2000);
|
|
892
|
+
codeSnippets.push(`### ${cleanPath}\n\`\`\`\n${truncatedContent}\n\`\`\``);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
// Используем AI для генерации подробной документации
|
|
896
|
+
const orchestrator = data.orchestrator;
|
|
897
|
+
let aiDocumentation = '';
|
|
898
|
+
if (orchestrator.isAvailable()) {
|
|
899
|
+
const langInstruction = language === 'ru'
|
|
900
|
+
? 'ВАЖНО: Отвечай ТОЛЬКО на русском языке!'
|
|
901
|
+
: 'Respond in English.';
|
|
902
|
+
const prompt = {
|
|
903
|
+
system: `You are a technical documentation writer. Generate comprehensive project documentation.
|
|
904
|
+
${langInstruction}
|
|
905
|
+
|
|
906
|
+
The documentation should include:
|
|
907
|
+
1. Project Overview - what the project does, its purpose
|
|
908
|
+
2. Architecture Description - how components are organized
|
|
909
|
+
3. Key Components - main classes and their responsibilities
|
|
910
|
+
4. Main Functions - important functions and what they do
|
|
911
|
+
5. Data Flow - how data moves through the system
|
|
912
|
+
6. Usage Examples - how to use the main features
|
|
913
|
+
|
|
914
|
+
Be specific and detailed. Analyze the actual code provided to understand the project's purpose.`,
|
|
915
|
+
user: `Generate detailed documentation for project "${project.name}".
|
|
916
|
+
|
|
917
|
+
Project Statistics:
|
|
918
|
+
- Files: ${stats?.filesCount || files.length}
|
|
919
|
+
- Symbols: ${stats?.symbolsCount || symbols.length}
|
|
920
|
+
- Classes: ${classes.length}
|
|
921
|
+
- Functions: ${functions.length}
|
|
922
|
+
- Interfaces: ${interfaces.length}
|
|
923
|
+
|
|
924
|
+
File Structure:
|
|
925
|
+
${fileStructure}
|
|
926
|
+
|
|
927
|
+
Classes: ${classes.slice(0, 15).map(c => `${c.name} (${sanitizePath(c.filePath)})`).join(', ') || 'None'}
|
|
928
|
+
|
|
929
|
+
Key Functions: ${functions.slice(0, 20).map(f => f.name).join(', ') || 'None'}
|
|
930
|
+
|
|
931
|
+
Interfaces: ${interfaces.slice(0, 10).map(i => i.name).join(', ') || 'None'}
|
|
932
|
+
|
|
933
|
+
Code Samples:
|
|
934
|
+
${codeSnippets.slice(0, 5).join('\n\n')}
|
|
935
|
+
|
|
936
|
+
Generate comprehensive markdown documentation based on this analysis.`
|
|
937
|
+
};
|
|
938
|
+
try {
|
|
939
|
+
const response = await orchestrator.query(prompt);
|
|
940
|
+
aiDocumentation = response.content;
|
|
941
|
+
}
|
|
942
|
+
catch (err) {
|
|
943
|
+
Logger.warn('AI documentation generation failed, using basic template');
|
|
944
|
+
}
|
|
945
|
+
}
|
|
784
946
|
// Генерируем документацию
|
|
785
947
|
let documentation = '';
|
|
786
948
|
if (format === 'markdown') {
|
|
787
949
|
const title = language === 'ru' ? 'Документация проекта' : 'Project Documentation';
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
950
|
+
if (aiDocumentation) {
|
|
951
|
+
// Используем AI-сгенерированную документацию
|
|
952
|
+
documentation = `# ${title}: ${project.name}\n\n${aiDocumentation}\n\n---\n*Generated by ArchiCore AI*`;
|
|
953
|
+
}
|
|
954
|
+
else {
|
|
955
|
+
// Fallback на базовый шаблон
|
|
956
|
+
const overview = language === 'ru' ? 'Обзор' : 'Overview';
|
|
957
|
+
const structure = language === 'ru' ? 'Структура проекта' : 'Project Structure';
|
|
958
|
+
const classesTitle = language === 'ru' ? 'Классы' : 'Classes';
|
|
959
|
+
const functionsTitle = language === 'ru' ? 'Функции' : 'Functions';
|
|
960
|
+
documentation = `# ${title}: ${project.name}
|
|
794
961
|
|
|
795
962
|
## ${overview}
|
|
796
963
|
|
|
@@ -810,37 +977,29 @@ ${classes.length > 0 ? `## ${classesTitle}
|
|
|
810
977
|
|
|
811
978
|
${classes.slice(0, 20).map(c => `### ${c.name}
|
|
812
979
|
|
|
813
|
-
- **File:** \`${c.filePath}\`
|
|
980
|
+
- **File:** \`${sanitizePath(c.filePath)}\`
|
|
814
981
|
- **Line:** ${c.location?.startLine || 'N/A'}
|
|
815
982
|
`).join('\n')}
|
|
816
983
|
` : ''}
|
|
817
984
|
|
|
818
|
-
${interfaces.length > 0 ? `## ${interfacesTitle}
|
|
819
|
-
|
|
820
|
-
${interfaces.slice(0, 20).map(i => `### ${i.name}
|
|
821
|
-
|
|
822
|
-
- **File:** \`${i.filePath}\`
|
|
823
|
-
- **Line:** ${i.location?.startLine || 'N/A'}
|
|
824
|
-
`).join('\n')}
|
|
825
|
-
` : ''}
|
|
826
|
-
|
|
827
985
|
${functions.length > 0 ? `## ${functionsTitle}
|
|
828
986
|
|
|
829
|
-
${functions.slice(0, 30).map(f => `- \`${f.name}\` - ${f.filePath}:${f.location?.startLine || 'N/A'}`).join('\n')}
|
|
987
|
+
${functions.slice(0, 30).map(f => `- \`${f.name}\` - ${sanitizePath(f.filePath)}:${f.location?.startLine || 'N/A'}`).join('\n')}
|
|
830
988
|
` : ''}
|
|
831
989
|
|
|
832
990
|
---
|
|
833
991
|
*Generated by ArchiCore*
|
|
834
992
|
`;
|
|
993
|
+
}
|
|
835
994
|
}
|
|
836
995
|
else {
|
|
837
996
|
// JSON format
|
|
838
997
|
documentation = JSON.stringify({
|
|
839
998
|
project: project.name,
|
|
840
999
|
stats,
|
|
841
|
-
classes: classes.map(c => ({ name: c.name, file: c.filePath, line: c.location?.startLine })),
|
|
842
|
-
functions: functions.map(f => ({ name: f.name, file: f.filePath, line: f.location?.startLine })),
|
|
843
|
-
interfaces: interfaces.map(i => ({ name: i.name, file: i.filePath, line: i.location?.startLine })),
|
|
1000
|
+
classes: classes.map(c => ({ name: c.name, file: sanitizePath(c.filePath), line: c.location?.startLine })),
|
|
1001
|
+
functions: functions.map(f => ({ name: f.name, file: sanitizePath(f.filePath), line: f.location?.startLine })),
|
|
1002
|
+
interfaces: interfaces.map(i => ({ name: i.name, file: sanitizePath(i.filePath), line: i.location?.startLine })),
|
|
844
1003
|
files
|
|
845
1004
|
}, null, 2);
|
|
846
1005
|
}
|
package/dist/types/user.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* User and Subscription Types for ArchiCore
|
|
3
3
|
*/
|
|
4
|
-
export type SubscriptionTier = 'free' | 'mid' | 'pro' | 'admin';
|
|
4
|
+
export type SubscriptionTier = 'free' | 'mid' | 'pro' | 'enterprise' | 'admin';
|
|
5
5
|
export interface TierLimits {
|
|
6
6
|
requestsPerDay: number;
|
|
7
7
|
fullAnalysisPerDay: number;
|
|
8
8
|
projectsPerDay: number;
|
|
9
|
-
|
|
9
|
+
maxProjectSizeMB: number;
|
|
10
10
|
retentionDays: number;
|
|
11
11
|
price: number;
|
|
12
12
|
priceYearly: number;
|
package/dist/types/user.js
CHANGED
|
@@ -6,7 +6,7 @@ export const TIER_LIMITS = {
|
|
|
6
6
|
requestsPerDay: 10,
|
|
7
7
|
fullAnalysisPerDay: 1,
|
|
8
8
|
projectsPerDay: 3,
|
|
9
|
-
|
|
9
|
+
maxProjectSizeMB: 30,
|
|
10
10
|
retentionDays: 7,
|
|
11
11
|
price: 0,
|
|
12
12
|
priceYearly: 0
|
|
@@ -15,7 +15,7 @@ export const TIER_LIMITS = {
|
|
|
15
15
|
requestsPerDay: 50,
|
|
16
16
|
fullAnalysisPerDay: 5,
|
|
17
17
|
projectsPerDay: 10,
|
|
18
|
-
|
|
18
|
+
maxProjectSizeMB: 50,
|
|
19
19
|
retentionDays: 14,
|
|
20
20
|
price: 39,
|
|
21
21
|
priceYearly: 390 // ~2 months free
|
|
@@ -24,16 +24,25 @@ export const TIER_LIMITS = {
|
|
|
24
24
|
requestsPerDay: 150,
|
|
25
25
|
fullAnalysisPerDay: 15,
|
|
26
26
|
projectsPerDay: 20,
|
|
27
|
-
|
|
27
|
+
maxProjectSizeMB: 80,
|
|
28
28
|
retentionDays: 30,
|
|
29
29
|
price: 99,
|
|
30
30
|
priceYearly: 990 // ~2 months free
|
|
31
31
|
},
|
|
32
|
+
enterprise: {
|
|
33
|
+
requestsPerDay: Infinity,
|
|
34
|
+
fullAnalysisPerDay: Infinity,
|
|
35
|
+
projectsPerDay: Infinity,
|
|
36
|
+
maxProjectSizeMB: 500,
|
|
37
|
+
retentionDays: 365,
|
|
38
|
+
price: -1, // Custom pricing
|
|
39
|
+
priceYearly: -1
|
|
40
|
+
},
|
|
32
41
|
admin: {
|
|
33
42
|
requestsPerDay: Infinity,
|
|
34
43
|
fullAnalysisPerDay: Infinity,
|
|
35
44
|
projectsPerDay: Infinity,
|
|
36
|
-
|
|
45
|
+
maxProjectSizeMB: 1000,
|
|
37
46
|
retentionDays: 365,
|
|
38
47
|
price: 0,
|
|
39
48
|
priceYearly: 0
|