archicore 0.1.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/README.md +530 -0
- package/dist/analyzers/dead-code.d.ts +95 -0
- package/dist/analyzers/dead-code.js +327 -0
- package/dist/analyzers/duplication.d.ts +90 -0
- package/dist/analyzers/duplication.js +344 -0
- package/dist/analyzers/security.d.ts +79 -0
- package/dist/analyzers/security.js +484 -0
- package/dist/architecture/index.d.ts +35 -0
- package/dist/architecture/index.js +249 -0
- package/dist/cli/commands/analyzers.d.ts +6 -0
- package/dist/cli/commands/analyzers.js +431 -0
- package/dist/cli/commands/export.d.ts +6 -0
- package/dist/cli/commands/export.js +78 -0
- package/dist/cli/commands/index.d.ts +8 -0
- package/dist/cli/commands/index.js +8 -0
- package/dist/cli/commands/init.d.ts +26 -0
- package/dist/cli/commands/init.js +140 -0
- package/dist/cli/commands/interactive.d.ts +7 -0
- package/dist/cli/commands/interactive.js +522 -0
- package/dist/cli/commands/projects.d.ts +6 -0
- package/dist/cli/commands/projects.js +249 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.js +7 -0
- package/dist/cli/ui/box.d.ts +17 -0
- package/dist/cli/ui/box.js +62 -0
- package/dist/cli/ui/colors.d.ts +49 -0
- package/dist/cli/ui/colors.js +86 -0
- package/dist/cli/ui/index.d.ts +9 -0
- package/dist/cli/ui/index.js +9 -0
- package/dist/cli/ui/prompt.d.ts +34 -0
- package/dist/cli/ui/prompt.js +122 -0
- package/dist/cli/ui/spinner.d.ts +29 -0
- package/dist/cli/ui/spinner.js +80 -0
- package/dist/cli/ui/table.d.ts +33 -0
- package/dist/cli/ui/table.js +84 -0
- package/dist/cli/utils/config.d.ts +23 -0
- package/dist/cli/utils/config.js +73 -0
- package/dist/cli/utils/index.d.ts +6 -0
- package/dist/cli/utils/index.js +6 -0
- package/dist/cli/utils/session.d.ts +27 -0
- package/dist/cli/utils/session.js +117 -0
- package/dist/cli.d.ts +8 -0
- package/dist/cli.js +295 -0
- package/dist/code-index/ast-parser.d.ts +16 -0
- package/dist/code-index/ast-parser.js +330 -0
- package/dist/code-index/dependency-graph.d.ts +16 -0
- package/dist/code-index/dependency-graph.js +161 -0
- package/dist/code-index/index.d.ts +44 -0
- package/dist/code-index/index.js +124 -0
- package/dist/code-index/symbol-extractor.d.ts +13 -0
- package/dist/code-index/symbol-extractor.js +150 -0
- package/dist/export/index.d.ts +92 -0
- package/dist/export/index.js +676 -0
- package/dist/github/github-service.d.ts +146 -0
- package/dist/github/github-service.js +609 -0
- package/dist/impact-engine/index.d.ts +25 -0
- package/dist/impact-engine/index.js +284 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.js +149 -0
- package/dist/metrics/index.d.ts +136 -0
- package/dist/metrics/index.js +525 -0
- package/dist/orchestrator/deepseek-optimizer.d.ts +67 -0
- package/dist/orchestrator/deepseek-optimizer.js +320 -0
- package/dist/orchestrator/index.d.ts +34 -0
- package/dist/orchestrator/index.js +305 -0
- package/dist/pr-guardian/index.d.ts +143 -0
- package/dist/pr-guardian/index.js +553 -0
- package/dist/refactoring/index.d.ts +108 -0
- package/dist/refactoring/index.js +580 -0
- package/dist/rules-engine/index.d.ts +129 -0
- package/dist/rules-engine/index.js +482 -0
- package/dist/semantic-memory/embedding-service.d.ts +24 -0
- package/dist/semantic-memory/embedding-service.js +120 -0
- package/dist/semantic-memory/index.d.ts +45 -0
- package/dist/semantic-memory/index.js +206 -0
- package/dist/semantic-memory/vector-store.d.ts +27 -0
- package/dist/semantic-memory/vector-store.js +166 -0
- package/dist/server/index.d.ts +28 -0
- package/dist/server/index.js +141 -0
- package/dist/server/middleware/api-auth.d.ts +43 -0
- package/dist/server/middleware/api-auth.js +256 -0
- package/dist/server/routes/admin.d.ts +5 -0
- package/dist/server/routes/admin.js +123 -0
- package/dist/server/routes/api.d.ts +7 -0
- package/dist/server/routes/api.js +362 -0
- package/dist/server/routes/auth.d.ts +16 -0
- package/dist/server/routes/auth.js +191 -0
- package/dist/server/routes/developer.d.ts +8 -0
- package/dist/server/routes/developer.js +439 -0
- package/dist/server/routes/github.d.ts +7 -0
- package/dist/server/routes/github.js +495 -0
- package/dist/server/routes/upload.d.ts +7 -0
- package/dist/server/routes/upload.js +196 -0
- package/dist/server/services/api-key-service.d.ts +81 -0
- package/dist/server/services/api-key-service.js +281 -0
- package/dist/server/services/auth-service.d.ts +40 -0
- package/dist/server/services/auth-service.js +315 -0
- package/dist/server/services/project-service.d.ts +123 -0
- package/dist/server/services/project-service.js +533 -0
- package/dist/server/services/token-service.d.ts +107 -0
- package/dist/server/services/token-service.js +416 -0
- package/dist/server/services/upload-service.d.ts +93 -0
- package/dist/server/services/upload-service.js +464 -0
- package/dist/types/api.d.ts +188 -0
- package/dist/types/api.js +86 -0
- package/dist/types/github.d.ts +335 -0
- package/dist/types/github.js +5 -0
- package/dist/types/index.d.ts +265 -0
- package/dist/types/index.js +32 -0
- package/dist/types/user.d.ts +69 -0
- package/dist/types/user.js +42 -0
- package/dist/utils/file-utils.d.ts +20 -0
- package/dist/utils/file-utils.js +163 -0
- package/dist/utils/logger.d.ts +17 -0
- package/dist/utils/logger.js +41 -0
- package/dist/watcher/index.d.ts +125 -0
- package/dist/watcher/index.js +397 -0
- package/package.json +71 -0
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Service
|
|
3
|
+
*
|
|
4
|
+
* Сервис для управления проектами в ArchiCore.
|
|
5
|
+
* Связывает API с основными модулями системы.
|
|
6
|
+
*/
|
|
7
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
8
|
+
import { CodeIndex } from '../../code-index/index.js';
|
|
9
|
+
import { SemanticMemory } from '../../semantic-memory/index.js';
|
|
10
|
+
import { ImpactEngine } from '../../impact-engine/index.js';
|
|
11
|
+
import { LLMOrchestrator } from '../../orchestrator/index.js';
|
|
12
|
+
import { ArchitectureKnowledge } from '../../architecture/index.js';
|
|
13
|
+
import { Logger } from '../../utils/logger.js';
|
|
14
|
+
import { readFile, writeFile, mkdir } from 'fs/promises';
|
|
15
|
+
import { existsSync } from 'fs';
|
|
16
|
+
import path from 'path';
|
|
17
|
+
// New analyzers
|
|
18
|
+
import { RulesEngine } from '../../rules-engine/index.js';
|
|
19
|
+
import { MetricsCalculator } from '../../metrics/index.js';
|
|
20
|
+
import { DeadCodeDetector } from '../../analyzers/dead-code.js';
|
|
21
|
+
import { DuplicationDetector } from '../../analyzers/duplication.js';
|
|
22
|
+
import { SecurityAnalyzer } from '../../analyzers/security.js';
|
|
23
|
+
import { RefactoringEngine } from '../../refactoring/index.js';
|
|
24
|
+
// Singleton instance
|
|
25
|
+
let instance = null;
|
|
26
|
+
export class ProjectService {
|
|
27
|
+
projects = new Map();
|
|
28
|
+
projectData = new Map();
|
|
29
|
+
dataDir;
|
|
30
|
+
constructor() {
|
|
31
|
+
// Singleton pattern
|
|
32
|
+
if (instance) {
|
|
33
|
+
return instance;
|
|
34
|
+
}
|
|
35
|
+
instance = this;
|
|
36
|
+
this.dataDir = process.env.ARCHICORE_DATA_DIR || '.archicore';
|
|
37
|
+
this.loadProjects();
|
|
38
|
+
}
|
|
39
|
+
async loadProjects() {
|
|
40
|
+
try {
|
|
41
|
+
const projectsFile = path.join(this.dataDir, 'projects.json');
|
|
42
|
+
if (existsSync(projectsFile)) {
|
|
43
|
+
const content = await readFile(projectsFile, 'utf-8');
|
|
44
|
+
const data = JSON.parse(content);
|
|
45
|
+
for (const project of data.projects) {
|
|
46
|
+
this.projects.set(project.id, project);
|
|
47
|
+
}
|
|
48
|
+
Logger.info(`Loaded ${this.projects.size} projects`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
Logger.warn('Could not load projects:', error);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async saveProjects() {
|
|
56
|
+
try {
|
|
57
|
+
if (!existsSync(this.dataDir)) {
|
|
58
|
+
await mkdir(this.dataDir, { recursive: true });
|
|
59
|
+
}
|
|
60
|
+
const projectsFile = path.join(this.dataDir, 'projects.json');
|
|
61
|
+
const data = { projects: Array.from(this.projects.values()) };
|
|
62
|
+
await writeFile(projectsFile, JSON.stringify(data, null, 2));
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
Logger.error('Could not save projects:', error);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async listProjects() {
|
|
69
|
+
return Array.from(this.projects.values());
|
|
70
|
+
}
|
|
71
|
+
async createProject(name, projectPath) {
|
|
72
|
+
const id = uuidv4();
|
|
73
|
+
const project = {
|
|
74
|
+
id,
|
|
75
|
+
name,
|
|
76
|
+
path: projectPath,
|
|
77
|
+
createdAt: new Date().toISOString(),
|
|
78
|
+
status: 'pending'
|
|
79
|
+
};
|
|
80
|
+
this.projects.set(id, project);
|
|
81
|
+
await this.saveProjects();
|
|
82
|
+
// Инициализируем данные проекта
|
|
83
|
+
await this.initializeProjectData(id);
|
|
84
|
+
Logger.success(`Created project: ${name} (${id})`);
|
|
85
|
+
return project;
|
|
86
|
+
}
|
|
87
|
+
async getProject(id) {
|
|
88
|
+
return this.projects.get(id) || null;
|
|
89
|
+
}
|
|
90
|
+
async initializeProjectData(projectId) {
|
|
91
|
+
const project = this.projects.get(projectId);
|
|
92
|
+
if (!project) {
|
|
93
|
+
throw new Error(`Project not found: ${projectId}`);
|
|
94
|
+
}
|
|
95
|
+
// Конфигурация для DeepSeek
|
|
96
|
+
const llmConfig = {
|
|
97
|
+
provider: 'deepseek',
|
|
98
|
+
model: process.env.DEEPSEEK_MODEL || 'deepseek-chat',
|
|
99
|
+
temperature: 0.1,
|
|
100
|
+
maxTokens: 4096,
|
|
101
|
+
baseURL: 'https://api.deepseek.com'
|
|
102
|
+
};
|
|
103
|
+
// SemanticMemory опционален - DeepSeek не поддерживает embeddings
|
|
104
|
+
let semanticMemory = null;
|
|
105
|
+
// Только если есть OpenAI ключ - используем embeddings
|
|
106
|
+
if (process.env.OPENAI_API_KEY) {
|
|
107
|
+
const embeddingConfig = {
|
|
108
|
+
provider: 'openai',
|
|
109
|
+
model: 'text-embedding-3-small'
|
|
110
|
+
};
|
|
111
|
+
const vectorStoreConfig = {
|
|
112
|
+
url: process.env.QDRANT_URL || 'http://localhost:6333',
|
|
113
|
+
apiKey: process.env.QDRANT_API_KEY,
|
|
114
|
+
collectionName: `archicore_${projectId.replace(/-/g, '_')}`
|
|
115
|
+
};
|
|
116
|
+
semanticMemory = new SemanticMemory(embeddingConfig, vectorStoreConfig);
|
|
117
|
+
Logger.info('SemanticMemory enabled (OpenAI embeddings)');
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
Logger.warn('SemanticMemory disabled - no OPENAI_API_KEY. Semantic search unavailable.');
|
|
121
|
+
}
|
|
122
|
+
const data = {
|
|
123
|
+
codeIndex: new CodeIndex(project.path),
|
|
124
|
+
semanticMemory,
|
|
125
|
+
impactEngine: new ImpactEngine(),
|
|
126
|
+
orchestrator: new LLMOrchestrator(llmConfig),
|
|
127
|
+
architecture: new ArchitectureKnowledge(path.join(project.path, '.aiarhitector/architecture.json'))
|
|
128
|
+
};
|
|
129
|
+
this.projectData.set(projectId, data);
|
|
130
|
+
return data;
|
|
131
|
+
}
|
|
132
|
+
async getProjectData(projectId) {
|
|
133
|
+
let data = this.projectData.get(projectId);
|
|
134
|
+
if (!data) {
|
|
135
|
+
data = await this.initializeProjectData(projectId);
|
|
136
|
+
}
|
|
137
|
+
return data;
|
|
138
|
+
}
|
|
139
|
+
async indexProject(projectId) {
|
|
140
|
+
const project = this.projects.get(projectId);
|
|
141
|
+
if (!project) {
|
|
142
|
+
throw new Error(`Project not found: ${projectId}`);
|
|
143
|
+
}
|
|
144
|
+
project.status = 'indexing';
|
|
145
|
+
await this.saveProjects();
|
|
146
|
+
try {
|
|
147
|
+
const data = await this.getProjectData(projectId);
|
|
148
|
+
Logger.progress(`Indexing project: ${project.name}`);
|
|
149
|
+
// 1. Парсинг AST
|
|
150
|
+
const asts = await data.codeIndex.parseProject();
|
|
151
|
+
data.asts = asts;
|
|
152
|
+
// 2. Извлечение символов
|
|
153
|
+
const symbols = data.codeIndex.extractSymbols(asts);
|
|
154
|
+
data.symbols = symbols;
|
|
155
|
+
// 3. Построение графа зависимостей
|
|
156
|
+
const graph = data.codeIndex.buildDependencyGraph(asts, symbols);
|
|
157
|
+
data.graph = graph;
|
|
158
|
+
// 4. Индексация в семантическую память (если доступна)
|
|
159
|
+
if (data.semanticMemory) {
|
|
160
|
+
await data.semanticMemory.initialize();
|
|
161
|
+
await data.semanticMemory.indexSymbols(symbols, asts);
|
|
162
|
+
}
|
|
163
|
+
// 5. Загрузка архитектурных знаний
|
|
164
|
+
await data.architecture.load();
|
|
165
|
+
// Обновляем статус и статистику
|
|
166
|
+
const stats = {
|
|
167
|
+
filesCount: asts.size,
|
|
168
|
+
symbolsCount: symbols.size,
|
|
169
|
+
nodesCount: graph.nodes.size,
|
|
170
|
+
edgesCount: Array.from(graph.edges.values()).reduce((sum, edges) => sum + edges.length, 0)
|
|
171
|
+
};
|
|
172
|
+
project.status = 'ready';
|
|
173
|
+
project.lastIndexedAt = new Date().toISOString();
|
|
174
|
+
project.stats = stats;
|
|
175
|
+
await this.saveProjects();
|
|
176
|
+
Logger.success(`Project indexed: ${stats.filesCount} files, ${stats.symbolsCount} symbols`);
|
|
177
|
+
return { success: true, stats };
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
project.status = 'error';
|
|
181
|
+
await this.saveProjects();
|
|
182
|
+
Logger.error('Indexing failed:', error);
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
async getArchitecture(projectId) {
|
|
187
|
+
const data = await this.getProjectData(projectId);
|
|
188
|
+
const model = data.architecture.getModel();
|
|
189
|
+
// Преобразуем граф в формат для визуализации
|
|
190
|
+
const graphData = this.convertGraphForVisualization(data.graph);
|
|
191
|
+
return {
|
|
192
|
+
boundedContexts: model.boundedContexts,
|
|
193
|
+
entities: model.entities,
|
|
194
|
+
rules: model.rules.map(r => ({
|
|
195
|
+
id: r.id,
|
|
196
|
+
description: r.description,
|
|
197
|
+
type: r.type,
|
|
198
|
+
severity: r.severity
|
|
199
|
+
})),
|
|
200
|
+
graph: graphData
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
async getDependencyGraph(projectId) {
|
|
204
|
+
const data = await this.getProjectData(projectId);
|
|
205
|
+
return this.convertGraphForVisualization(data.graph);
|
|
206
|
+
}
|
|
207
|
+
convertGraphForVisualization(graph) {
|
|
208
|
+
if (!graph) {
|
|
209
|
+
return { nodes: [], edges: [] };
|
|
210
|
+
}
|
|
211
|
+
const nodes = Array.from(graph.nodes.entries()).map(([id, node]) => ({
|
|
212
|
+
id,
|
|
213
|
+
label: node.name,
|
|
214
|
+
type: node.type,
|
|
215
|
+
filePath: node.filePath,
|
|
216
|
+
// Для визуализации - группировка по папкам
|
|
217
|
+
group: node.filePath.split('/').slice(-2, -1)[0] || 'root'
|
|
218
|
+
}));
|
|
219
|
+
const edges = [];
|
|
220
|
+
for (const [from, edgeList] of graph.edges) {
|
|
221
|
+
for (const edge of edgeList) {
|
|
222
|
+
edges.push({
|
|
223
|
+
from,
|
|
224
|
+
to: edge.to,
|
|
225
|
+
type: edge.type,
|
|
226
|
+
weight: edge.weight || 1
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return { nodes, edges };
|
|
231
|
+
}
|
|
232
|
+
async analyzeImpact(projectId, change) {
|
|
233
|
+
const data = await this.getProjectData(projectId);
|
|
234
|
+
if (!data.graph || !data.symbols) {
|
|
235
|
+
throw new Error('Project not indexed. Please index first.');
|
|
236
|
+
}
|
|
237
|
+
const changeObj = {
|
|
238
|
+
type: change.type,
|
|
239
|
+
description: change.description,
|
|
240
|
+
files: change.files,
|
|
241
|
+
symbols: change.symbols
|
|
242
|
+
};
|
|
243
|
+
const impact = data.impactEngine.analyzeChange(changeObj, data.graph, data.symbols, data.architecture.getModel());
|
|
244
|
+
return impact;
|
|
245
|
+
}
|
|
246
|
+
async simulateChange(projectId, changeDescription) {
|
|
247
|
+
const data = await this.getProjectData(projectId);
|
|
248
|
+
let affectedFiles = [];
|
|
249
|
+
let affectedSymbols = [];
|
|
250
|
+
let searchResults = [];
|
|
251
|
+
// Если есть семантическая память - ищем релевантные файлы
|
|
252
|
+
if (data.semanticMemory) {
|
|
253
|
+
const results = await data.semanticMemory.searchByQuery(changeDescription, 5);
|
|
254
|
+
searchResults = results;
|
|
255
|
+
affectedFiles = results.map(r => r.chunk.metadata.filePath);
|
|
256
|
+
affectedSymbols = results.flatMap(r => r.chunk.metadata.symbols);
|
|
257
|
+
}
|
|
258
|
+
// Анализируем impact
|
|
259
|
+
const impact = await this.analyzeImpact(projectId, {
|
|
260
|
+
description: changeDescription,
|
|
261
|
+
files: affectedFiles,
|
|
262
|
+
symbols: affectedSymbols,
|
|
263
|
+
type: 'modify'
|
|
264
|
+
});
|
|
265
|
+
// Получаем AI анализ
|
|
266
|
+
const aiAnalysis = await data.orchestrator.analyzeImpact(impact, {
|
|
267
|
+
architecture: data.architecture.getModel(),
|
|
268
|
+
semanticMemory: searchResults
|
|
269
|
+
});
|
|
270
|
+
return {
|
|
271
|
+
impact,
|
|
272
|
+
aiAnalysis,
|
|
273
|
+
recommendations: impact.recommendations.map(r => r.description)
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
async semanticSearch(projectId, query, limit = 10) {
|
|
277
|
+
const data = await this.getProjectData(projectId);
|
|
278
|
+
if (!data.semanticMemory) {
|
|
279
|
+
throw new Error('Semantic search unavailable - no embedding API configured');
|
|
280
|
+
}
|
|
281
|
+
const results = await data.semanticMemory.searchByQuery(query, limit);
|
|
282
|
+
return results.map(r => ({
|
|
283
|
+
filePath: r.chunk.metadata.filePath,
|
|
284
|
+
startLine: r.chunk.metadata.startLine,
|
|
285
|
+
endLine: r.chunk.metadata.endLine,
|
|
286
|
+
type: r.chunk.metadata.type,
|
|
287
|
+
symbols: r.chunk.metadata.symbols,
|
|
288
|
+
score: r.score,
|
|
289
|
+
context: r.context
|
|
290
|
+
}));
|
|
291
|
+
}
|
|
292
|
+
async askArchitect(projectId, question, language = 'en') {
|
|
293
|
+
const data = await this.getProjectData(projectId);
|
|
294
|
+
// Ищем релевантный контекст (если есть семантическая память)
|
|
295
|
+
let searchResults = [];
|
|
296
|
+
if (data.semanticMemory) {
|
|
297
|
+
try {
|
|
298
|
+
searchResults = await data.semanticMemory.searchByQuery(question, 5);
|
|
299
|
+
Logger.debug(`Semantic search returned ${searchResults.length} results`);
|
|
300
|
+
}
|
|
301
|
+
catch (err) {
|
|
302
|
+
Logger.warn('Semantic search failed, using fallback');
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
// Fallback: если семантический поиск не работает, даём контекст из графа
|
|
306
|
+
if (searchResults.length === 0 && data.graph) {
|
|
307
|
+
Logger.info('Using graph fallback for context');
|
|
308
|
+
const files = Array.from(data.graph.nodes.values()).slice(0, 10);
|
|
309
|
+
for (const file of files) {
|
|
310
|
+
searchResults.push({
|
|
311
|
+
chunk: {
|
|
312
|
+
id: file.id,
|
|
313
|
+
content: `File: ${file.filePath}\nType: ${file.type}`,
|
|
314
|
+
embedding: [],
|
|
315
|
+
metadata: {
|
|
316
|
+
filePath: file.filePath,
|
|
317
|
+
startLine: 0,
|
|
318
|
+
endLine: 0,
|
|
319
|
+
type: 'module',
|
|
320
|
+
symbols: [],
|
|
321
|
+
tags: []
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
score: 1,
|
|
325
|
+
context: `File: ${file.filePath}`
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
// Добавляем информацию о символах если есть
|
|
330
|
+
if (data.symbols && data.symbols.size > 0) {
|
|
331
|
+
const symbolsList = Array.from(data.symbols.values()).slice(0, 20);
|
|
332
|
+
for (const sym of symbolsList) {
|
|
333
|
+
const existing = searchResults.find(r => r.chunk.metadata.filePath === sym.filePath);
|
|
334
|
+
if (existing) {
|
|
335
|
+
existing.context += `\n- ${sym.kind}: ${sym.name}`;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
Logger.debug(`Passing ${searchResults.length} context items to AI`);
|
|
340
|
+
const answer = await data.orchestrator.answerArchitecturalQuestion(question, {
|
|
341
|
+
architecture: data.architecture.getModel(),
|
|
342
|
+
semanticMemory: searchResults,
|
|
343
|
+
language
|
|
344
|
+
});
|
|
345
|
+
return answer;
|
|
346
|
+
}
|
|
347
|
+
async getStatistics(projectId) {
|
|
348
|
+
const project = this.projects.get(projectId);
|
|
349
|
+
if (!project) {
|
|
350
|
+
throw new Error(`Project not found: ${projectId}`);
|
|
351
|
+
}
|
|
352
|
+
let memoryStats = null;
|
|
353
|
+
try {
|
|
354
|
+
const data = await this.getProjectData(projectId);
|
|
355
|
+
if (data.semanticMemory) {
|
|
356
|
+
memoryStats = await data.semanticMemory.getStatistics();
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
catch {
|
|
360
|
+
// Игнорируем если семантическая память не инициализирована
|
|
361
|
+
}
|
|
362
|
+
return {
|
|
363
|
+
project,
|
|
364
|
+
indexStats: project.stats || null,
|
|
365
|
+
memoryStats
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
async deleteProject(projectId) {
|
|
369
|
+
this.projects.delete(projectId);
|
|
370
|
+
this.projectData.delete(projectId);
|
|
371
|
+
await this.saveProjects();
|
|
372
|
+
Logger.info(`Deleted project: ${projectId}`);
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Получить метрики кода
|
|
376
|
+
*/
|
|
377
|
+
async getMetrics(projectId) {
|
|
378
|
+
const data = await this.getProjectData(projectId);
|
|
379
|
+
if (!data.graph || !data.symbols || !data.asts) {
|
|
380
|
+
throw new Error('Project not indexed. Please index first.');
|
|
381
|
+
}
|
|
382
|
+
const fileContents = await this.getFileContents(projectId);
|
|
383
|
+
const calculator = new MetricsCalculator();
|
|
384
|
+
return calculator.calculateProjectMetrics(data.graph, data.symbols, fileContents, data.asts);
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Проверить правила архитектуры
|
|
388
|
+
*/
|
|
389
|
+
async checkRules(projectId) {
|
|
390
|
+
const data = await this.getProjectData(projectId);
|
|
391
|
+
if (!data.graph || !data.symbols) {
|
|
392
|
+
throw new Error('Project not indexed. Please index first.');
|
|
393
|
+
}
|
|
394
|
+
const fileContents = await this.getFileContents(projectId);
|
|
395
|
+
const rulesEngine = new RulesEngine();
|
|
396
|
+
return rulesEngine.check(data.graph, data.symbols, fileContents);
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Найти мёртвый код
|
|
400
|
+
*/
|
|
401
|
+
async findDeadCode(projectId) {
|
|
402
|
+
const data = await this.getProjectData(projectId);
|
|
403
|
+
if (!data.graph || !data.symbols) {
|
|
404
|
+
throw new Error('Project not indexed. Please index first.');
|
|
405
|
+
}
|
|
406
|
+
const fileContents = await this.getFileContents(projectId);
|
|
407
|
+
const detector = new DeadCodeDetector();
|
|
408
|
+
return detector.analyze(data.graph, data.symbols, fileContents);
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Найти дублирование кода
|
|
412
|
+
*/
|
|
413
|
+
async findDuplication(projectId) {
|
|
414
|
+
const fileContents = await this.getFileContents(projectId);
|
|
415
|
+
const detector = new DuplicationDetector();
|
|
416
|
+
return detector.analyze(fileContents);
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Анализ безопасности
|
|
420
|
+
*/
|
|
421
|
+
async analyzeSecurity(projectId) {
|
|
422
|
+
const fileContents = await this.getFileContents(projectId);
|
|
423
|
+
const analyzer = new SecurityAnalyzer();
|
|
424
|
+
return analyzer.analyze(fileContents);
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Получить предложения по рефакторингу
|
|
428
|
+
*/
|
|
429
|
+
async getRefactoringSuggestions(projectId) {
|
|
430
|
+
const data = await this.getProjectData(projectId);
|
|
431
|
+
if (!data.graph || !data.symbols || !data.asts) {
|
|
432
|
+
throw new Error('Project not indexed. Please index first.');
|
|
433
|
+
}
|
|
434
|
+
const fileContents = await this.getFileContents(projectId);
|
|
435
|
+
// Запускаем необходимые анализаторы
|
|
436
|
+
const metrics = await this.getMetrics(projectId);
|
|
437
|
+
const duplication = await this.findDuplication(projectId);
|
|
438
|
+
const deadCode = await this.findDeadCode(projectId);
|
|
439
|
+
const rulesResult = await this.checkRules(projectId);
|
|
440
|
+
const engine = new RefactoringEngine();
|
|
441
|
+
return engine.analyze(data.graph, data.symbols, fileContents, metrics, duplication, deadCode, rulesResult);
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Получить данные для экспорта
|
|
445
|
+
*/
|
|
446
|
+
async getExportData(projectId) {
|
|
447
|
+
const project = this.projects.get(projectId);
|
|
448
|
+
if (!project) {
|
|
449
|
+
throw new Error(`Project not found: ${projectId}`);
|
|
450
|
+
}
|
|
451
|
+
const data = await this.getProjectData(projectId);
|
|
452
|
+
// Собираем все данные
|
|
453
|
+
const metrics = data.graph && data.symbols && data.asts
|
|
454
|
+
? await this.getMetrics(projectId)
|
|
455
|
+
: undefined;
|
|
456
|
+
const rules = data.graph && data.symbols
|
|
457
|
+
? await this.checkRules(projectId)
|
|
458
|
+
: undefined;
|
|
459
|
+
const deadCode = data.graph && data.symbols
|
|
460
|
+
? await this.findDeadCode(projectId)
|
|
461
|
+
: undefined;
|
|
462
|
+
const duplication = await this.findDuplication(projectId);
|
|
463
|
+
const security = await this.analyzeSecurity(projectId);
|
|
464
|
+
return {
|
|
465
|
+
projectName: project.name,
|
|
466
|
+
exportDate: new Date().toISOString(),
|
|
467
|
+
version: '1.0.0',
|
|
468
|
+
graph: data.graph,
|
|
469
|
+
symbols: data.symbols,
|
|
470
|
+
metrics,
|
|
471
|
+
rules,
|
|
472
|
+
deadCode,
|
|
473
|
+
duplication,
|
|
474
|
+
security
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Полный анализ проекта
|
|
479
|
+
*/
|
|
480
|
+
async runFullAnalysis(projectId) {
|
|
481
|
+
Logger.progress('Running full analysis...');
|
|
482
|
+
const [metrics, rules, deadCode, duplication, security] = await Promise.all([
|
|
483
|
+
this.getMetrics(projectId),
|
|
484
|
+
this.checkRules(projectId),
|
|
485
|
+
this.findDeadCode(projectId),
|
|
486
|
+
this.findDuplication(projectId),
|
|
487
|
+
this.analyzeSecurity(projectId)
|
|
488
|
+
]);
|
|
489
|
+
// Refactoring зависит от предыдущих результатов
|
|
490
|
+
const data = await this.getProjectData(projectId);
|
|
491
|
+
const fileContents = await this.getFileContents(projectId);
|
|
492
|
+
const engine = new RefactoringEngine();
|
|
493
|
+
const refactoring = await engine.analyze(data.graph, data.symbols, fileContents, metrics, duplication, deadCode, rules);
|
|
494
|
+
Logger.success('Full analysis complete');
|
|
495
|
+
return {
|
|
496
|
+
metrics,
|
|
497
|
+
rules,
|
|
498
|
+
deadCode,
|
|
499
|
+
duplication,
|
|
500
|
+
security,
|
|
501
|
+
refactoring
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Получить содержимое файлов проекта
|
|
506
|
+
*/
|
|
507
|
+
async getFileContents(projectId) {
|
|
508
|
+
const project = this.projects.get(projectId);
|
|
509
|
+
if (!project) {
|
|
510
|
+
throw new Error(`Project not found: ${projectId}`);
|
|
511
|
+
}
|
|
512
|
+
const data = await this.getProjectData(projectId);
|
|
513
|
+
const fileContents = new Map();
|
|
514
|
+
if (data.asts) {
|
|
515
|
+
for (const [filePath] of data.asts) {
|
|
516
|
+
try {
|
|
517
|
+
const fullPath = path.isAbsolute(filePath)
|
|
518
|
+
? filePath
|
|
519
|
+
: path.join(project.path, filePath);
|
|
520
|
+
if (existsSync(fullPath)) {
|
|
521
|
+
const content = await readFile(fullPath, 'utf-8');
|
|
522
|
+
fileContents.set(filePath, content);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
catch {
|
|
526
|
+
// Игнорируем ошибки чтения
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
return fileContents;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
//# sourceMappingURL=project-service.js.map
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token Service for ArchiCore Developer API
|
|
3
|
+
*
|
|
4
|
+
* Подсчёт токенов, биллинг и rate limiting
|
|
5
|
+
*/
|
|
6
|
+
import { TokenUsage, ApiOperation, BillingAccount, RateLimitState, RateLimitResult, UsageStats } from '../../types/api.js';
|
|
7
|
+
export declare class TokenService {
|
|
8
|
+
private dataDir;
|
|
9
|
+
private usageRecords;
|
|
10
|
+
private billingAccounts;
|
|
11
|
+
private rateLimitStates;
|
|
12
|
+
private initialized;
|
|
13
|
+
constructor(dataDir?: string);
|
|
14
|
+
private ensureInitialized;
|
|
15
|
+
private saveUsage;
|
|
16
|
+
private saveBilling;
|
|
17
|
+
private saveRateLimits;
|
|
18
|
+
/**
|
|
19
|
+
* Подсчёт токенов для операции
|
|
20
|
+
*/
|
|
21
|
+
calculateTokens(operation: ApiOperation, params: {
|
|
22
|
+
fileCount?: number;
|
|
23
|
+
contentSizeKb?: number;
|
|
24
|
+
inputText?: string;
|
|
25
|
+
outputText?: string;
|
|
26
|
+
}): {
|
|
27
|
+
inputTokens: number;
|
|
28
|
+
outputTokens: number;
|
|
29
|
+
totalTokens: number;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Расчёт стоимости в центах
|
|
33
|
+
*/
|
|
34
|
+
calculateCost(totalTokens: number, pricingTier: string): number;
|
|
35
|
+
/**
|
|
36
|
+
* Запись использования токенов
|
|
37
|
+
*/
|
|
38
|
+
recordUsage(apiKeyId: string, userId: string, operation: ApiOperation, tokens: {
|
|
39
|
+
inputTokens: number;
|
|
40
|
+
outputTokens: number;
|
|
41
|
+
totalTokens: number;
|
|
42
|
+
}, projectId?: string, metadata?: Record<string, unknown>): Promise<TokenUsage>;
|
|
43
|
+
/**
|
|
44
|
+
* Получение истории использования
|
|
45
|
+
*/
|
|
46
|
+
getUsageHistory(userId: string, options: {
|
|
47
|
+
limit?: number;
|
|
48
|
+
offset?: number;
|
|
49
|
+
apiKeyId?: string;
|
|
50
|
+
operation?: ApiOperation;
|
|
51
|
+
}): Promise<TokenUsage[]>;
|
|
52
|
+
/**
|
|
53
|
+
* Получение статистики использования
|
|
54
|
+
*/
|
|
55
|
+
getUsageStats(userId: string, period: 'hour' | 'day' | 'week' | 'month'): Promise<UsageStats>;
|
|
56
|
+
/**
|
|
57
|
+
* Получение или создание billing account
|
|
58
|
+
*/
|
|
59
|
+
getBillingAccount(userId: string): Promise<BillingAccount>;
|
|
60
|
+
/**
|
|
61
|
+
* Обновление использования в billing account
|
|
62
|
+
*/
|
|
63
|
+
private updateBillingUsage;
|
|
64
|
+
/**
|
|
65
|
+
* Пополнение баланса
|
|
66
|
+
*/
|
|
67
|
+
addCredits(userId: string, amount: number): Promise<BillingAccount>;
|
|
68
|
+
/**
|
|
69
|
+
* Изменение pricing tier
|
|
70
|
+
*/
|
|
71
|
+
updatePricingTier(userId: string, tier: string): Promise<BillingAccount>;
|
|
72
|
+
/**
|
|
73
|
+
* Проверка достаточности баланса
|
|
74
|
+
*/
|
|
75
|
+
checkBalance(userId: string, estimatedTokens: number): Promise<{
|
|
76
|
+
allowed: boolean;
|
|
77
|
+
balance: number;
|
|
78
|
+
estimatedCost: number;
|
|
79
|
+
tokensRemaining: number;
|
|
80
|
+
}>;
|
|
81
|
+
/**
|
|
82
|
+
* Проверка rate limit
|
|
83
|
+
*/
|
|
84
|
+
checkRateLimit(apiKeyId: string, limits: {
|
|
85
|
+
requestsPerMinute: number;
|
|
86
|
+
requestsPerDay: number;
|
|
87
|
+
tokensPerMinute: number;
|
|
88
|
+
tokensPerDay: number;
|
|
89
|
+
}): Promise<RateLimitResult>;
|
|
90
|
+
/**
|
|
91
|
+
* Инкремент rate limit счётчиков
|
|
92
|
+
*/
|
|
93
|
+
incrementRateLimit(apiKeyId: string, tokens: number): Promise<void>;
|
|
94
|
+
/**
|
|
95
|
+
* Получение текущего состояния rate limit
|
|
96
|
+
*/
|
|
97
|
+
getRateLimitState(apiKeyId: string): Promise<RateLimitState | null>;
|
|
98
|
+
/**
|
|
99
|
+
* Очистка старых записей usage (старше 90 дней)
|
|
100
|
+
*/
|
|
101
|
+
cleanupOldUsageRecords(): Promise<number>;
|
|
102
|
+
/**
|
|
103
|
+
* Сброс месячных лимитов (вызывается в начале месяца)
|
|
104
|
+
*/
|
|
105
|
+
resetMonthlyLimits(): Promise<void>;
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=token-service.d.ts.map
|