archicore 0.1.7 → 0.1.8
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/interactive.js +2 -0
- package/dist/cli/ui/spinner.js +1 -0
- package/dist/index.js +5 -1
- package/dist/semantic-memory/embedding-service.d.ts +5 -1
- package/dist/semantic-memory/embedding-service.js +47 -13
- package/dist/semantic-memory/index.js +4 -2
- package/dist/semantic-memory/vector-store.js +16 -4
- package/dist/server/routes/api.js +16 -0
- package/dist/server/services/project-service.d.ts +8 -0
- package/dist/server/services/project-service.js +45 -9
- package/package.json +1 -1
|
@@ -126,6 +126,8 @@ export async function startInteractiveMode() {
|
|
|
126
126
|
output: process.stdout,
|
|
127
127
|
terminal: true,
|
|
128
128
|
});
|
|
129
|
+
// Keep the process alive
|
|
130
|
+
rl.ref?.();
|
|
129
131
|
const prompt = () => {
|
|
130
132
|
const prefix = state.projectName
|
|
131
133
|
? colors.muted(`[${state.projectName}] `)
|
package/dist/cli/ui/spinner.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -22,7 +22,11 @@ export class AIArhitector {
|
|
|
22
22
|
constructor(config) {
|
|
23
23
|
this.config = config;
|
|
24
24
|
this.codeIndex = new CodeIndex();
|
|
25
|
-
|
|
25
|
+
// Determine embedding provider: Jina (free) > OpenAI
|
|
26
|
+
const embeddingProvider = process.env.JINA_API_KEY ? 'jina'
|
|
27
|
+
: process.env.OPENAI_API_KEY ? 'openai'
|
|
28
|
+
: 'none';
|
|
29
|
+
this.semanticMemory = new SemanticMemory({ provider: embeddingProvider }, config.vectorStore);
|
|
26
30
|
this.architecture = new ArchitectureKnowledge(config.architectureConfigPath || '.aiarhitector/architecture.json');
|
|
27
31
|
this.impactEngine = new ImpactEngine();
|
|
28
32
|
this.orchestrator = new LLMOrchestrator(config.llm);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export interface EmbeddingConfig {
|
|
2
|
-
provider: '
|
|
2
|
+
provider: 'openai' | 'jina' | 'none';
|
|
3
3
|
model?: string;
|
|
4
4
|
}
|
|
5
5
|
export declare class EmbeddingService {
|
|
@@ -7,10 +7,14 @@ export declare class EmbeddingService {
|
|
|
7
7
|
private config;
|
|
8
8
|
private initialized;
|
|
9
9
|
private _isAvailable;
|
|
10
|
+
private jinaApiKey?;
|
|
11
|
+
private embeddingDimension;
|
|
10
12
|
constructor(config: EmbeddingConfig);
|
|
13
|
+
getEmbeddingDimension(): number;
|
|
11
14
|
private ensureInitialized;
|
|
12
15
|
isAvailable(): boolean;
|
|
13
16
|
generateEmbedding(text: string): Promise<number[]>;
|
|
17
|
+
private generateJinaEmbedding;
|
|
14
18
|
generateBatchEmbeddings(texts: string[]): Promise<number[][]>;
|
|
15
19
|
private generateOpenAIEmbedding;
|
|
16
20
|
prepareCodeForEmbedding(code: string, context?: string): string;
|
|
@@ -5,9 +5,14 @@ export class EmbeddingService {
|
|
|
5
5
|
config;
|
|
6
6
|
initialized = false;
|
|
7
7
|
_isAvailable = false;
|
|
8
|
+
jinaApiKey;
|
|
9
|
+
embeddingDimension = 1024; // Jina default, OpenAI small = 1536
|
|
8
10
|
constructor(config) {
|
|
9
11
|
this.config = config;
|
|
10
12
|
}
|
|
13
|
+
getEmbeddingDimension() {
|
|
14
|
+
return this.embeddingDimension;
|
|
15
|
+
}
|
|
11
16
|
ensureInitialized() {
|
|
12
17
|
if (this.initialized)
|
|
13
18
|
return;
|
|
@@ -17,26 +22,29 @@ export class EmbeddingService {
|
|
|
17
22
|
return;
|
|
18
23
|
}
|
|
19
24
|
try {
|
|
20
|
-
if (this.config.provider === '
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
if (this.config.provider === 'jina') {
|
|
26
|
+
// Jina AI - free embeddings
|
|
27
|
+
this.jinaApiKey = process.env.JINA_API_KEY;
|
|
28
|
+
if (!this.jinaApiKey) {
|
|
29
|
+
Logger.warn('JINA_API_KEY not set - semantic search disabled');
|
|
30
|
+
Logger.info('Get free API key at: https://jina.ai/embeddings/');
|
|
24
31
|
return;
|
|
25
32
|
}
|
|
26
|
-
this.
|
|
27
|
-
apiKey,
|
|
28
|
-
baseURL: 'https://api.deepseek.com'
|
|
29
|
-
});
|
|
33
|
+
this.embeddingDimension = 1024;
|
|
30
34
|
this._isAvailable = true;
|
|
35
|
+
Logger.success('Jina AI embeddings enabled');
|
|
31
36
|
}
|
|
32
37
|
else {
|
|
38
|
+
// OpenAI embeddings
|
|
33
39
|
const apiKey = process.env.OPENAI_API_KEY;
|
|
34
40
|
if (!apiKey) {
|
|
35
41
|
Logger.warn('OPENAI_API_KEY not set - semantic search disabled');
|
|
36
42
|
return;
|
|
37
43
|
}
|
|
38
44
|
this.openai = new OpenAI({ apiKey });
|
|
45
|
+
this.embeddingDimension = 1536;
|
|
39
46
|
this._isAvailable = true;
|
|
47
|
+
Logger.success('OpenAI embeddings enabled');
|
|
40
48
|
}
|
|
41
49
|
}
|
|
42
50
|
catch (error) {
|
|
@@ -50,23 +58,49 @@ export class EmbeddingService {
|
|
|
50
58
|
async generateEmbedding(text) {
|
|
51
59
|
this.ensureInitialized();
|
|
52
60
|
if (!this._isAvailable)
|
|
53
|
-
return new Array(
|
|
61
|
+
return new Array(this.embeddingDimension).fill(0);
|
|
54
62
|
try {
|
|
55
|
-
if (this.
|
|
63
|
+
if (this.config.provider === 'jina' && this.jinaApiKey) {
|
|
64
|
+
return await this.generateJinaEmbedding(text);
|
|
65
|
+
}
|
|
66
|
+
else if (this.openai) {
|
|
56
67
|
return await this.generateOpenAIEmbedding(text);
|
|
57
68
|
}
|
|
58
|
-
return new Array(
|
|
69
|
+
return new Array(this.embeddingDimension).fill(0);
|
|
59
70
|
}
|
|
60
71
|
catch (error) {
|
|
61
72
|
Logger.error('Failed to generate embedding', error);
|
|
62
|
-
return new Array(
|
|
73
|
+
return new Array(this.embeddingDimension).fill(0);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async generateJinaEmbedding(text) {
|
|
77
|
+
if (!this.jinaApiKey)
|
|
78
|
+
throw new Error('Jina API key not set');
|
|
79
|
+
const response = await fetch('https://api.jina.ai/v1/embeddings', {
|
|
80
|
+
method: 'POST',
|
|
81
|
+
headers: {
|
|
82
|
+
'Content-Type': 'application/json',
|
|
83
|
+
'Authorization': `Bearer ${this.jinaApiKey}`
|
|
84
|
+
},
|
|
85
|
+
body: JSON.stringify({
|
|
86
|
+
model: this.config.model || 'jina-embeddings-v3',
|
|
87
|
+
task: 'text-matching',
|
|
88
|
+
dimensions: 1024,
|
|
89
|
+
input: [text.substring(0, 8000)]
|
|
90
|
+
})
|
|
91
|
+
});
|
|
92
|
+
if (!response.ok) {
|
|
93
|
+
const error = await response.text();
|
|
94
|
+
throw new Error(`Jina API error: ${response.status} ${error}`);
|
|
63
95
|
}
|
|
96
|
+
const data = await response.json();
|
|
97
|
+
return data.data[0].embedding;
|
|
64
98
|
}
|
|
65
99
|
async generateBatchEmbeddings(texts) {
|
|
66
100
|
this.ensureInitialized();
|
|
67
101
|
if (!this._isAvailable) {
|
|
68
102
|
Logger.warn('Embedding service not available, skipping');
|
|
69
|
-
return texts.map(() => new Array(
|
|
103
|
+
return texts.map(() => new Array(this.embeddingDimension).fill(0));
|
|
70
104
|
}
|
|
71
105
|
Logger.progress('Generating embeddings for ' + texts.length + ' texts...');
|
|
72
106
|
const embeddings = [];
|
|
@@ -19,8 +19,10 @@ export class SemanticMemory {
|
|
|
19
19
|
}
|
|
20
20
|
async initialize() {
|
|
21
21
|
Logger.info('Initializing Semantic Memory...');
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
// Pass the correct embedding dimension based on provider (Jina=1024, OpenAI=1536)
|
|
23
|
+
const dimension = this.embeddingService.getEmbeddingDimension();
|
|
24
|
+
await this.vectorStore.initialize(dimension);
|
|
25
|
+
Logger.success(`Semantic Memory initialized (dimension: ${dimension})`);
|
|
24
26
|
}
|
|
25
27
|
async indexSymbols(symbols, asts) {
|
|
26
28
|
Logger.progress('Indexing symbols into semantic memory...');
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import { QdrantClient } from '@qdrant/js-client-rest';
|
|
2
|
+
import { createHash } from 'crypto';
|
|
2
3
|
import { Logger } from '../utils/logger.js';
|
|
4
|
+
/**
|
|
5
|
+
* Convert string ID to UUID format for Qdrant
|
|
6
|
+
* Uses MD5 hash to create deterministic UUID from any string
|
|
7
|
+
*/
|
|
8
|
+
function stringToUuid(str) {
|
|
9
|
+
const hash = createHash('md5').update(str).digest('hex');
|
|
10
|
+
// Format as UUID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
|
11
|
+
return `${hash.slice(0, 8)}-${hash.slice(8, 12)}-${hash.slice(12, 16)}-${hash.slice(16, 20)}-${hash.slice(20, 32)}`;
|
|
12
|
+
}
|
|
3
13
|
export class VectorStore {
|
|
4
14
|
client;
|
|
5
15
|
collectionName;
|
|
@@ -44,9 +54,10 @@ export class VectorStore {
|
|
|
44
54
|
await this.client.upsert(this.collectionName, {
|
|
45
55
|
points: [
|
|
46
56
|
{
|
|
47
|
-
id: chunk.id,
|
|
57
|
+
id: stringToUuid(chunk.id), // Convert string ID to UUID
|
|
48
58
|
vector: chunk.embedding,
|
|
49
59
|
payload: {
|
|
60
|
+
originalId: chunk.id, // Store original ID
|
|
50
61
|
content: chunk.content,
|
|
51
62
|
metadata: chunk.metadata
|
|
52
63
|
}
|
|
@@ -69,9 +80,10 @@ export class VectorStore {
|
|
|
69
80
|
const batch = chunks.slice(i, i + batchSize);
|
|
70
81
|
await this.client.upsert(this.collectionName, {
|
|
71
82
|
points: batch.map(chunk => ({
|
|
72
|
-
id: chunk.id,
|
|
83
|
+
id: stringToUuid(chunk.id), // Convert string ID to UUID
|
|
73
84
|
vector: chunk.embedding,
|
|
74
85
|
payload: {
|
|
86
|
+
originalId: chunk.id, // Store original ID
|
|
75
87
|
content: chunk.content,
|
|
76
88
|
metadata: chunk.metadata
|
|
77
89
|
}
|
|
@@ -93,7 +105,7 @@ export class VectorStore {
|
|
|
93
105
|
});
|
|
94
106
|
return response.map(point => ({
|
|
95
107
|
chunk: {
|
|
96
|
-
id: String(point.id),
|
|
108
|
+
id: point.payload?.originalId || String(point.id), // Use original ID
|
|
97
109
|
content: point.payload?.content || '',
|
|
98
110
|
embedding: [],
|
|
99
111
|
metadata: point.payload?.metadata || {
|
|
@@ -119,7 +131,7 @@ export class VectorStore {
|
|
|
119
131
|
return;
|
|
120
132
|
try {
|
|
121
133
|
await this.client.delete(this.collectionName, {
|
|
122
|
-
points: [id]
|
|
134
|
+
points: [stringToUuid(id)] // Convert to UUID
|
|
123
135
|
});
|
|
124
136
|
}
|
|
125
137
|
catch (error) {
|
|
@@ -109,6 +109,22 @@ apiRouter.post('/projects/:id/index', authMiddleware, checkProjectAccess, async
|
|
|
109
109
|
res.status(500).json({ error: 'Failed to index project' });
|
|
110
110
|
}
|
|
111
111
|
});
|
|
112
|
+
/**
|
|
113
|
+
* POST /api/projects/:id/reindex-embeddings
|
|
114
|
+
* Переиндексировать embeddings из сохранённых данных
|
|
115
|
+
* Используется когда добавили JINA_API_KEY или OPENAI_API_KEY
|
|
116
|
+
*/
|
|
117
|
+
apiRouter.post('/projects/:id/reindex-embeddings', authMiddleware, checkProjectAccess, async (req, res) => {
|
|
118
|
+
try {
|
|
119
|
+
const { id } = req.params;
|
|
120
|
+
const result = await projectService.reindexEmbeddings(id);
|
|
121
|
+
res.json(result);
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
Logger.error('Failed to reindex embeddings:', error);
|
|
125
|
+
res.status(500).json({ error: String(error) });
|
|
126
|
+
}
|
|
127
|
+
});
|
|
112
128
|
/**
|
|
113
129
|
* POST /api/projects/:id/upload-index
|
|
114
130
|
* Загрузить индексированные данные с CLI
|
|
@@ -105,6 +105,14 @@ export declare class ProjectService {
|
|
|
105
105
|
* Загрузить сохранённые индексные данные с диска
|
|
106
106
|
*/
|
|
107
107
|
loadIndexedDataFromDisk(projectId: string): Promise<boolean>;
|
|
108
|
+
/**
|
|
109
|
+
* Переиндексировать embeddings из сохранённых данных
|
|
110
|
+
* Используется когда добавили API ключ для embeddings
|
|
111
|
+
*/
|
|
112
|
+
reindexEmbeddings(projectId: string): Promise<{
|
|
113
|
+
success: boolean;
|
|
114
|
+
indexed: number;
|
|
115
|
+
}>;
|
|
108
116
|
/**
|
|
109
117
|
* Получить метрики кода
|
|
110
118
|
*/
|
|
@@ -121,24 +121,33 @@ export class ProjectService {
|
|
|
121
121
|
maxTokens: 4096,
|
|
122
122
|
baseURL: 'https://api.deepseek.com'
|
|
123
123
|
};
|
|
124
|
-
// SemanticMemory
|
|
124
|
+
// SemanticMemory - поддержка Jina (бесплатно) или OpenAI
|
|
125
125
|
let semanticMemory = null;
|
|
126
|
-
|
|
127
|
-
|
|
126
|
+
const vectorStoreConfig = {
|
|
127
|
+
url: process.env.QDRANT_URL || 'http://localhost:6333',
|
|
128
|
+
apiKey: process.env.QDRANT_API_KEY,
|
|
129
|
+
collectionName: `archicore_${projectId.replace(/-/g, '_')}`
|
|
130
|
+
};
|
|
131
|
+
// Приоритет: JINA_API_KEY (бесплатный) > OPENAI_API_KEY
|
|
132
|
+
if (process.env.JINA_API_KEY) {
|
|
133
|
+
const embeddingConfig = {
|
|
134
|
+
provider: 'jina',
|
|
135
|
+
model: 'jina-embeddings-v3'
|
|
136
|
+
};
|
|
137
|
+
semanticMemory = new SemanticMemory(embeddingConfig, vectorStoreConfig);
|
|
138
|
+
Logger.info('SemanticMemory enabled (Jina AI embeddings - free)');
|
|
139
|
+
}
|
|
140
|
+
else if (process.env.OPENAI_API_KEY) {
|
|
128
141
|
const embeddingConfig = {
|
|
129
142
|
provider: 'openai',
|
|
130
143
|
model: 'text-embedding-3-small'
|
|
131
144
|
};
|
|
132
|
-
const vectorStoreConfig = {
|
|
133
|
-
url: process.env.QDRANT_URL || 'http://localhost:6333',
|
|
134
|
-
apiKey: process.env.QDRANT_API_KEY,
|
|
135
|
-
collectionName: `archicore_${projectId.replace(/-/g, '_')}`
|
|
136
|
-
};
|
|
137
145
|
semanticMemory = new SemanticMemory(embeddingConfig, vectorStoreConfig);
|
|
138
146
|
Logger.info('SemanticMemory enabled (OpenAI embeddings)');
|
|
139
147
|
}
|
|
140
148
|
else {
|
|
141
|
-
Logger.warn('SemanticMemory disabled - no
|
|
149
|
+
Logger.warn('SemanticMemory disabled - no JINA_API_KEY or OPENAI_API_KEY');
|
|
150
|
+
Logger.info('Get free Jina API key at: https://jina.ai/embeddings/');
|
|
142
151
|
}
|
|
143
152
|
const data = {
|
|
144
153
|
codeIndex: new CodeIndex(project.path),
|
|
@@ -547,6 +556,33 @@ export class ProjectService {
|
|
|
547
556
|
return false;
|
|
548
557
|
}
|
|
549
558
|
}
|
|
559
|
+
/**
|
|
560
|
+
* Переиндексировать embeddings из сохранённых данных
|
|
561
|
+
* Используется когда добавили API ключ для embeddings
|
|
562
|
+
*/
|
|
563
|
+
async reindexEmbeddings(projectId) {
|
|
564
|
+
const project = this.projects.get(projectId);
|
|
565
|
+
if (!project) {
|
|
566
|
+
throw new Error(`Project not found: ${projectId}`);
|
|
567
|
+
}
|
|
568
|
+
// Загружаем данные с диска если ещё не загружены
|
|
569
|
+
const loaded = await this.loadIndexedDataFromDisk(projectId);
|
|
570
|
+
if (!loaded) {
|
|
571
|
+
throw new Error('No indexed data found. Please index project first via CLI.');
|
|
572
|
+
}
|
|
573
|
+
const data = await this.getProjectData(projectId);
|
|
574
|
+
if (!data.symbols || !data.asts) {
|
|
575
|
+
throw new Error('No symbols/ASTs found. Please re-index via CLI.');
|
|
576
|
+
}
|
|
577
|
+
if (!data.semanticMemory) {
|
|
578
|
+
throw new Error('SemanticMemory not available. Check JINA_API_KEY or OPENAI_API_KEY.');
|
|
579
|
+
}
|
|
580
|
+
Logger.progress(`Re-indexing embeddings for: ${project.name}`);
|
|
581
|
+
await data.semanticMemory.initialize();
|
|
582
|
+
await data.semanticMemory.indexSymbols(data.symbols, data.asts);
|
|
583
|
+
Logger.success(`Embeddings indexed: ${data.symbols.size} symbols`);
|
|
584
|
+
return { success: true, indexed: data.symbols.size };
|
|
585
|
+
}
|
|
550
586
|
/**
|
|
551
587
|
* Получить метрики кода
|
|
552
588
|
*/
|