sigma-memory 0.1.2 → 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/index.d.ts +11 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +36 -33
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +11 -10
- package/dist/types.d.ts.map +1 -1
- package/dist/vector-store.d.ts +100 -0
- package/dist/vector-store.d.ts.map +1 -0
- package/dist/vector-store.js +322 -0
- package/dist/vector-store.js.map +1 -0
- package/package.json +10 -6
- package/src/index.ts +38 -32
- package/src/types.ts +14 -12
- package/src/vector-store.ts +384 -0
- package/src/vendor.d.ts +30 -0
- package/dist/qmd.d.ts +0 -35
- package/dist/qmd.d.ts.map +0 -1
- package/dist/qmd.js +0 -162
- package/dist/qmd.js.map +0 -1
- package/src/qmd.ts +0 -180
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
2
|
+
import { dirname } from 'path';
|
|
3
|
+
import initSqlJs, { type Database } from 'sql.js';
|
|
4
|
+
import type { VectorSearchResult } from './types.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Helper for loading ESM-only modules from a CJS context.
|
|
8
|
+
* Uses native import() via Function constructor to bypass
|
|
9
|
+
* TypeScript's CJS transformation of dynamic imports.
|
|
10
|
+
*/
|
|
11
|
+
const esmImport = new Function(
|
|
12
|
+
'specifier',
|
|
13
|
+
'return import(specifier)'
|
|
14
|
+
) as (specifier: string) => Promise<any>;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Self-contained vector store using sql.js (SQLite via WebAssembly) and
|
|
18
|
+
* @huggingface/transformers for local embeddings.
|
|
19
|
+
*
|
|
20
|
+
* Zero configuration — works out of the box on any platform.
|
|
21
|
+
* No native compilation required.
|
|
22
|
+
*/
|
|
23
|
+
export class VectorStore {
|
|
24
|
+
private db: Database | null = null;
|
|
25
|
+
private pipeline: any = null;
|
|
26
|
+
private dbPath: string;
|
|
27
|
+
private initialized = false;
|
|
28
|
+
private initPromise: Promise<void> | null = null;
|
|
29
|
+
private modelPromise: Promise<void> | null = null;
|
|
30
|
+
|
|
31
|
+
constructor(dbPath: string) {
|
|
32
|
+
this.dbPath = dbPath;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Initialize the vector store: sets up the SQLite database.
|
|
37
|
+
* The embedding model is loaded lazily on first embed operation.
|
|
38
|
+
* Safe to call multiple times — only initializes once.
|
|
39
|
+
*/
|
|
40
|
+
async init(): Promise<void> {
|
|
41
|
+
if (this.initialized) return;
|
|
42
|
+
if (this.initPromise) return this.initPromise;
|
|
43
|
+
|
|
44
|
+
this.initPromise = this._init();
|
|
45
|
+
await this.initPromise;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private async _init(): Promise<void> {
|
|
49
|
+
// Ensure directory exists
|
|
50
|
+
const dir = dirname(this.dbPath);
|
|
51
|
+
if (!existsSync(dir)) {
|
|
52
|
+
mkdirSync(dir, { recursive: true });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Initialize sql.js (loads WASM automatically)
|
|
56
|
+
const SQL = await initSqlJs();
|
|
57
|
+
|
|
58
|
+
// Load existing DB or create a new one
|
|
59
|
+
if (existsSync(this.dbPath)) {
|
|
60
|
+
const fileBuffer = readFileSync(this.dbPath);
|
|
61
|
+
this.db = new SQL.Database(fileBuffer);
|
|
62
|
+
} else {
|
|
63
|
+
this.db = new SQL.Database();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Create schema
|
|
67
|
+
this.db.run(`
|
|
68
|
+
CREATE TABLE IF NOT EXISTS documents (
|
|
69
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
70
|
+
file TEXT NOT NULL,
|
|
71
|
+
chunk_index INTEGER NOT NULL,
|
|
72
|
+
content TEXT NOT NULL,
|
|
73
|
+
embedding BLOB NOT NULL,
|
|
74
|
+
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
75
|
+
UNIQUE(file, chunk_index)
|
|
76
|
+
)
|
|
77
|
+
`);
|
|
78
|
+
|
|
79
|
+
// Index for fast file lookups
|
|
80
|
+
this.db.run('CREATE INDEX IF NOT EXISTS idx_documents_file ON documents(file)');
|
|
81
|
+
|
|
82
|
+
this.persist();
|
|
83
|
+
this.initialized = true;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Load the embedding model. Called lazily on first embed operation.
|
|
88
|
+
* Downloads the model on first use (~23MB for all-MiniLM-L6-v2).
|
|
89
|
+
*/
|
|
90
|
+
private async loadEmbeddingModel(): Promise<void> {
|
|
91
|
+
if (this.pipeline) return;
|
|
92
|
+
if (this.modelPromise) return this.modelPromise;
|
|
93
|
+
|
|
94
|
+
this.modelPromise = (async () => {
|
|
95
|
+
console.log('[VectorStore] Loading embedding model (Xenova/all-MiniLM-L6-v2)...');
|
|
96
|
+
console.log('[VectorStore] First run may download the model (~23MB).');
|
|
97
|
+
|
|
98
|
+
// Dynamic ESM import for @huggingface/transformers (ESM-only package)
|
|
99
|
+
const { pipeline: createPipeline } = await esmImport('@huggingface/transformers');
|
|
100
|
+
|
|
101
|
+
this.pipeline = await createPipeline(
|
|
102
|
+
'feature-extraction',
|
|
103
|
+
'Xenova/all-MiniLM-L6-v2'
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
console.log('[VectorStore] Embedding model loaded.');
|
|
107
|
+
})();
|
|
108
|
+
|
|
109
|
+
await this.modelPromise;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Ensure the embedding model is loaded before use.
|
|
114
|
+
*/
|
|
115
|
+
private async ensurePipeline(): Promise<void> {
|
|
116
|
+
if (!this.pipeline) {
|
|
117
|
+
await this.loadEmbeddingModel();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Persist the in-memory SQLite database to disk.
|
|
123
|
+
*/
|
|
124
|
+
private persist(): void {
|
|
125
|
+
if (!this.db) return;
|
|
126
|
+
const data = this.db.export();
|
|
127
|
+
const buffer = Buffer.from(data);
|
|
128
|
+
writeFileSync(this.dbPath, buffer);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Embed text into a Float32Array vector (384 dimensions).
|
|
133
|
+
*/
|
|
134
|
+
private async embed(text: string): Promise<Float32Array> {
|
|
135
|
+
await this.ensurePipeline();
|
|
136
|
+
|
|
137
|
+
const output = await this.pipeline(text, { pooling: 'mean', normalize: true });
|
|
138
|
+
return new Float32Array(output.data);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Chunk text into overlapping segments.
|
|
143
|
+
*
|
|
144
|
+
* Strategy:
|
|
145
|
+
* 1. Split by paragraphs (double newline)
|
|
146
|
+
* 2. If a paragraph exceeds 500 chars, split by sentences
|
|
147
|
+
* 3. Each chunk gets a 100-char overlap with the previous chunk
|
|
148
|
+
*/
|
|
149
|
+
private chunkText(text: string): string[] {
|
|
150
|
+
const MAX_CHUNK_SIZE = 500;
|
|
151
|
+
const OVERLAP = 100;
|
|
152
|
+
|
|
153
|
+
// Split by paragraphs (double newline)
|
|
154
|
+
const paragraphs = text.split(/\n\s*\n/).filter(p => p.trim().length > 0);
|
|
155
|
+
|
|
156
|
+
const rawChunks: string[] = [];
|
|
157
|
+
|
|
158
|
+
for (const paragraph of paragraphs) {
|
|
159
|
+
const trimmed = paragraph.trim();
|
|
160
|
+
|
|
161
|
+
if (trimmed.length <= MAX_CHUNK_SIZE) {
|
|
162
|
+
rawChunks.push(trimmed);
|
|
163
|
+
} else {
|
|
164
|
+
// Split long paragraphs by sentences
|
|
165
|
+
const sentences = trimmed.split(/(?<=[.!?])\s+/);
|
|
166
|
+
let current = '';
|
|
167
|
+
|
|
168
|
+
for (const sentence of sentences) {
|
|
169
|
+
if (current.length + sentence.length + 1 > MAX_CHUNK_SIZE && current.length > 0) {
|
|
170
|
+
rawChunks.push(current.trim());
|
|
171
|
+
current = sentence;
|
|
172
|
+
} else {
|
|
173
|
+
current = current ? current + ' ' + sentence : sentence;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (current.trim().length > 0) {
|
|
178
|
+
rawChunks.push(current.trim());
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (rawChunks.length === 0) return [];
|
|
184
|
+
|
|
185
|
+
// Apply overlap: each chunk (except the first) gets the last 100 chars
|
|
186
|
+
// of the previous chunk prepended
|
|
187
|
+
const chunks: string[] = [rawChunks[0]];
|
|
188
|
+
|
|
189
|
+
for (let i = 1; i < rawChunks.length; i++) {
|
|
190
|
+
const prevChunk = rawChunks[i - 1];
|
|
191
|
+
const overlap = prevChunk.slice(-OVERLAP);
|
|
192
|
+
chunks.push(overlap + ' ' + rawChunks[i]);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return chunks;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Serialize a Float32Array to a Buffer for BLOB storage.
|
|
200
|
+
*/
|
|
201
|
+
private serializeEmbedding(embedding: Float32Array): Uint8Array {
|
|
202
|
+
return new Uint8Array(embedding.buffer, embedding.byteOffset, embedding.byteLength);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Deserialize a BLOB (Uint8Array) back to Float32Array.
|
|
207
|
+
*/
|
|
208
|
+
private deserializeEmbedding(blob: Uint8Array): Float32Array {
|
|
209
|
+
// Create a proper copy to ensure alignment
|
|
210
|
+
const buffer = new ArrayBuffer(blob.byteLength);
|
|
211
|
+
new Uint8Array(buffer).set(blob);
|
|
212
|
+
return new Float32Array(buffer);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Compute cosine similarity between two vectors.
|
|
217
|
+
* Both vectors should be normalized (which they are from the model),
|
|
218
|
+
* so this is equivalent to the dot product.
|
|
219
|
+
*/
|
|
220
|
+
private cosineSimilarity(a: Float32Array, b: Float32Array): number {
|
|
221
|
+
let dotProduct = 0;
|
|
222
|
+
let normA = 0;
|
|
223
|
+
let normB = 0;
|
|
224
|
+
|
|
225
|
+
for (let i = 0; i < a.length; i++) {
|
|
226
|
+
dotProduct += a[i] * b[i];
|
|
227
|
+
normA += a[i] * a[i];
|
|
228
|
+
normB += b[i] * b[i];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const denominator = Math.sqrt(normA) * Math.sqrt(normB);
|
|
232
|
+
if (denominator === 0) return 0;
|
|
233
|
+
|
|
234
|
+
return dotProduct / denominator;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Add a document to the vector store.
|
|
239
|
+
* Chunks the content, embeds each chunk, and stores in the DB.
|
|
240
|
+
* Replaces any existing chunks for this file.
|
|
241
|
+
*/
|
|
242
|
+
async addDocument(file: string, content: string): Promise<void> {
|
|
243
|
+
if (!this.db) throw new Error('VectorStore not initialized. Call init() first.');
|
|
244
|
+
|
|
245
|
+
const chunks = this.chunkText(content);
|
|
246
|
+
if (chunks.length === 0) return;
|
|
247
|
+
|
|
248
|
+
// Remove existing chunks for this file
|
|
249
|
+
this.db.run('DELETE FROM documents WHERE file = ?', [file]);
|
|
250
|
+
|
|
251
|
+
// Embed and insert each chunk
|
|
252
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
253
|
+
const embedding = await this.embed(chunks[i]);
|
|
254
|
+
const embeddingBlob = this.serializeEmbedding(embedding);
|
|
255
|
+
const now = new Date().toISOString();
|
|
256
|
+
|
|
257
|
+
this.db.run(
|
|
258
|
+
'INSERT INTO documents (file, chunk_index, content, embedding, updated_at) VALUES (?, ?, ?, ?, ?)',
|
|
259
|
+
[file, i, chunks[i], embeddingBlob as any, now]
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
this.persist();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Search the vector store for content similar to the query.
|
|
268
|
+
* Returns top-k results sorted by cosine similarity (descending).
|
|
269
|
+
*
|
|
270
|
+
* For performance with large DBs (>10K chunks), embeddings are loaded
|
|
271
|
+
* as Float32Array and compared using optimized JS computation.
|
|
272
|
+
*/
|
|
273
|
+
async search(query: string, limit: number = 10): Promise<VectorSearchResult[]> {
|
|
274
|
+
if (!this.db) throw new Error('VectorStore not initialized. Call init() first.');
|
|
275
|
+
|
|
276
|
+
// Embed the query
|
|
277
|
+
const queryEmbedding = await this.embed(query);
|
|
278
|
+
|
|
279
|
+
// Load all documents with embeddings
|
|
280
|
+
const results = this.db.exec(
|
|
281
|
+
'SELECT file, chunk_index, content, embedding FROM documents'
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
if (results.length === 0 || results[0].values.length === 0) {
|
|
285
|
+
return [];
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Compute cosine similarity for each document
|
|
289
|
+
const scored: VectorSearchResult[] = [];
|
|
290
|
+
|
|
291
|
+
for (const row of results[0].values) {
|
|
292
|
+
const [file, chunkIndex, content, embeddingBlob] = row as [string, number, string, Uint8Array];
|
|
293
|
+
|
|
294
|
+
const docEmbedding = this.deserializeEmbedding(
|
|
295
|
+
embeddingBlob instanceof Uint8Array ? embeddingBlob : new Uint8Array(embeddingBlob as any)
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
const score = this.cosineSimilarity(queryEmbedding, docEmbedding);
|
|
299
|
+
|
|
300
|
+
scored.push({
|
|
301
|
+
file: file as string,
|
|
302
|
+
chunkIndex: chunkIndex as number,
|
|
303
|
+
content: content as string,
|
|
304
|
+
score
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Sort by score descending and return top-k
|
|
309
|
+
scored.sort((a, b) => b.score - a.score);
|
|
310
|
+
return scored.slice(0, limit);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Remove all chunks for a given file.
|
|
315
|
+
*/
|
|
316
|
+
async removeDocument(file: string): Promise<void> {
|
|
317
|
+
if (!this.db) throw new Error('VectorStore not initialized. Call init() first.');
|
|
318
|
+
|
|
319
|
+
this.db.run('DELETE FROM documents WHERE file = ?', [file]);
|
|
320
|
+
this.persist();
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Full reindex from a file map (filename → content).
|
|
325
|
+
* Clears all existing data and re-indexes everything.
|
|
326
|
+
*/
|
|
327
|
+
async reindex(files: Map<string, string>): Promise<void> {
|
|
328
|
+
if (!this.db) throw new Error('VectorStore not initialized. Call init() first.');
|
|
329
|
+
|
|
330
|
+
// Clear all existing documents
|
|
331
|
+
this.db.run('DELETE FROM documents');
|
|
332
|
+
|
|
333
|
+
// Re-add all files
|
|
334
|
+
for (const [file, content] of files) {
|
|
335
|
+
await this.addDocument(file, content);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
this.persist();
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Get statistics about the vector store.
|
|
343
|
+
*/
|
|
344
|
+
getStats(): { documentCount: number; chunkCount: number; lastUpdate: string } {
|
|
345
|
+
if (!this.db) {
|
|
346
|
+
return { documentCount: 0, chunkCount: 0, lastUpdate: '' };
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const countResult = this.db.exec(
|
|
350
|
+
'SELECT COUNT(DISTINCT file) as docs, COUNT(*) as chunks FROM documents'
|
|
351
|
+
);
|
|
352
|
+
const updateResult = this.db.exec(
|
|
353
|
+
'SELECT MAX(updated_at) as last_update FROM documents'
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
const docs = countResult.length > 0 && countResult[0].values.length > 0
|
|
357
|
+
? (countResult[0].values[0][0] as number)
|
|
358
|
+
: 0;
|
|
359
|
+
|
|
360
|
+
const chunks = countResult.length > 0 && countResult[0].values.length > 0
|
|
361
|
+
? (countResult[0].values[0][1] as number)
|
|
362
|
+
: 0;
|
|
363
|
+
|
|
364
|
+
const lastUpdate = updateResult.length > 0 && updateResult[0].values.length > 0
|
|
365
|
+
? ((updateResult[0].values[0][0] as string) || '')
|
|
366
|
+
: '';
|
|
367
|
+
|
|
368
|
+
return { documentCount: docs, chunkCount: chunks, lastUpdate };
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Close the database connection and persist to disk.
|
|
373
|
+
*/
|
|
374
|
+
close(): void {
|
|
375
|
+
if (this.db) {
|
|
376
|
+
this.persist();
|
|
377
|
+
this.db.close();
|
|
378
|
+
this.db = null;
|
|
379
|
+
}
|
|
380
|
+
this.initialized = false;
|
|
381
|
+
this.initPromise = null;
|
|
382
|
+
this.modelPromise = null;
|
|
383
|
+
}
|
|
384
|
+
}
|
package/src/vendor.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type declarations for external dependencies.
|
|
3
|
+
* Minimal declarations to avoid needing @types packages.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
declare module 'sql.js' {
|
|
7
|
+
interface Database {
|
|
8
|
+
run(sql: string, params?: any[]): Database;
|
|
9
|
+
exec(sql: string): Array<{ columns: string[]; values: any[][] }>;
|
|
10
|
+
prepare(sql: string): Statement;
|
|
11
|
+
export(): Uint8Array;
|
|
12
|
+
close(): void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface Statement {
|
|
16
|
+
bind(params?: any[]): boolean;
|
|
17
|
+
step(): boolean;
|
|
18
|
+
get(): any[];
|
|
19
|
+
getAsObject(): Record<string, any>;
|
|
20
|
+
free(): void;
|
|
21
|
+
run(params?: any[]): void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface SqlJsStatic {
|
|
25
|
+
Database: new (data?: ArrayLike<number> | null) => Database;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type { Database, Statement, SqlJsStatic };
|
|
29
|
+
export default function initSqlJs(config?: any): Promise<SqlJsStatic>;
|
|
30
|
+
}
|
package/dist/qmd.d.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import type { MemoryConfig, SearchResult } from './types.js';
|
|
2
|
-
export declare class QMDManager {
|
|
3
|
-
private config;
|
|
4
|
-
private qmdCommand;
|
|
5
|
-
constructor(config: MemoryConfig);
|
|
6
|
-
/**
|
|
7
|
-
* Retourne true si QMD est installé et fonctionnel
|
|
8
|
-
*/
|
|
9
|
-
isAvailable(): boolean;
|
|
10
|
-
/**
|
|
11
|
-
* Lance `qmd query` et parse les résultats
|
|
12
|
-
*/
|
|
13
|
-
search(query: string, maxResults?: number): Promise<SearchResult[]>;
|
|
14
|
-
/**
|
|
15
|
-
* Lance `qmd update` pour reindexer
|
|
16
|
-
*/
|
|
17
|
-
update(): Promise<boolean>;
|
|
18
|
-
/**
|
|
19
|
-
* Lance `qmd embed` pour recalculer les embeddings
|
|
20
|
-
*/
|
|
21
|
-
embed(): Promise<boolean>;
|
|
22
|
-
/**
|
|
23
|
-
* Liste les collections QMD
|
|
24
|
-
*/
|
|
25
|
-
collections(): Promise<string[]>;
|
|
26
|
-
/**
|
|
27
|
-
* Retourne le statut (nb fichiers, nb chunks, dernière mise à jour)
|
|
28
|
-
*/
|
|
29
|
-
status(): Promise<{
|
|
30
|
-
files: number;
|
|
31
|
-
chunks: number;
|
|
32
|
-
lastUpdate: string | null;
|
|
33
|
-
} | null>;
|
|
34
|
-
}
|
|
35
|
-
//# sourceMappingURL=qmd.d.ts.map
|
package/dist/qmd.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"qmd.d.ts","sourceRoot":"","sources":["../src/qmd.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE7D,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,UAAU,CAAS;gBAEf,MAAM,EAAE,YAAY;IAKhC;;OAEG;IACH,WAAW,IAAI,OAAO;IAiBtB;;OAEG;IACG,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,SAAK,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAsCrE;;OAEG;IACG,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC;IAkBhC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,OAAO,CAAC;IAkB/B;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IA8BtC;;OAEG;IACG,MAAM,IAAI,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;CA4B7F"}
|
package/dist/qmd.js
DELETED
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.QMDManager = void 0;
|
|
4
|
-
const child_process_1 = require("child_process");
|
|
5
|
-
class QMDManager {
|
|
6
|
-
config;
|
|
7
|
-
qmdCommand;
|
|
8
|
-
constructor(config) {
|
|
9
|
-
this.config = config;
|
|
10
|
-
this.qmdCommand = config.qmdCommand || 'qmd';
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Retourne true si QMD est installé et fonctionnel
|
|
14
|
-
*/
|
|
15
|
-
isAvailable() {
|
|
16
|
-
if (!this.config.qmdEnabled) {
|
|
17
|
-
return false;
|
|
18
|
-
}
|
|
19
|
-
try {
|
|
20
|
-
// Teste si le binaire QMD est accessible
|
|
21
|
-
(0, child_process_1.execSync)(`${this.qmdCommand} --version`, {
|
|
22
|
-
stdio: 'ignore',
|
|
23
|
-
timeout: 5000
|
|
24
|
-
});
|
|
25
|
-
return true;
|
|
26
|
-
}
|
|
27
|
-
catch (error) {
|
|
28
|
-
return false;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Lance `qmd query` et parse les résultats
|
|
33
|
-
*/
|
|
34
|
-
async search(query, maxResults = 10) {
|
|
35
|
-
if (!this.isAvailable()) {
|
|
36
|
-
return [];
|
|
37
|
-
}
|
|
38
|
-
try {
|
|
39
|
-
const output = (0, child_process_1.execSync)(`${this.qmdCommand} query "${query.replace(/"/g, '\\"')}" --limit ${maxResults} --format json`, {
|
|
40
|
-
encoding: 'utf8',
|
|
41
|
-
timeout: 30000,
|
|
42
|
-
cwd: this.config.memoryDir
|
|
43
|
-
});
|
|
44
|
-
if (!output.trim()) {
|
|
45
|
-
return [];
|
|
46
|
-
}
|
|
47
|
-
const result = JSON.parse(output);
|
|
48
|
-
// Le format attendu est { results: [{file, line, content, score}] }
|
|
49
|
-
if (result.results && Array.isArray(result.results)) {
|
|
50
|
-
return result.results.map((item) => ({
|
|
51
|
-
file: item.file || '',
|
|
52
|
-
line: item.line || 0,
|
|
53
|
-
content: item.content || '',
|
|
54
|
-
score: item.score || 0
|
|
55
|
-
}));
|
|
56
|
-
}
|
|
57
|
-
return [];
|
|
58
|
-
}
|
|
59
|
-
catch (error) {
|
|
60
|
-
// QMD search failed silently
|
|
61
|
-
return [];
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Lance `qmd update` pour reindexer
|
|
66
|
-
*/
|
|
67
|
-
async update() {
|
|
68
|
-
if (!this.isAvailable()) {
|
|
69
|
-
return false;
|
|
70
|
-
}
|
|
71
|
-
try {
|
|
72
|
-
(0, child_process_1.execSync)(`${this.qmdCommand} update`, {
|
|
73
|
-
stdio: 'ignore',
|
|
74
|
-
timeout: 60000,
|
|
75
|
-
cwd: this.config.memoryDir
|
|
76
|
-
});
|
|
77
|
-
return true;
|
|
78
|
-
}
|
|
79
|
-
catch (error) {
|
|
80
|
-
// QMD update failed silently
|
|
81
|
-
return false;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Lance `qmd embed` pour recalculer les embeddings
|
|
86
|
-
*/
|
|
87
|
-
async embed() {
|
|
88
|
-
if (!this.isAvailable()) {
|
|
89
|
-
return false;
|
|
90
|
-
}
|
|
91
|
-
try {
|
|
92
|
-
(0, child_process_1.execSync)(`${this.qmdCommand} embed`, {
|
|
93
|
-
stdio: 'ignore',
|
|
94
|
-
timeout: 300000, // 5 minutes pour les embeddings
|
|
95
|
-
cwd: this.config.memoryDir
|
|
96
|
-
});
|
|
97
|
-
return true;
|
|
98
|
-
}
|
|
99
|
-
catch (error) {
|
|
100
|
-
// QMD embed failed silently
|
|
101
|
-
return false;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
|
-
* Liste les collections QMD
|
|
106
|
-
*/
|
|
107
|
-
async collections() {
|
|
108
|
-
if (!this.isAvailable()) {
|
|
109
|
-
return [];
|
|
110
|
-
}
|
|
111
|
-
try {
|
|
112
|
-
const output = (0, child_process_1.execSync)(`${this.qmdCommand} collections --format json`, {
|
|
113
|
-
encoding: 'utf8',
|
|
114
|
-
timeout: 10000,
|
|
115
|
-
cwd: this.config.memoryDir
|
|
116
|
-
});
|
|
117
|
-
if (!output.trim()) {
|
|
118
|
-
return [];
|
|
119
|
-
}
|
|
120
|
-
const result = JSON.parse(output);
|
|
121
|
-
// Retourne la liste des noms de collections
|
|
122
|
-
if (result.collections && Array.isArray(result.collections)) {
|
|
123
|
-
return result.collections.map((col) => col.name || col);
|
|
124
|
-
}
|
|
125
|
-
return [];
|
|
126
|
-
}
|
|
127
|
-
catch (error) {
|
|
128
|
-
// QMD collections failed silently
|
|
129
|
-
return [];
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
/**
|
|
133
|
-
* Retourne le statut (nb fichiers, nb chunks, dernière mise à jour)
|
|
134
|
-
*/
|
|
135
|
-
async status() {
|
|
136
|
-
if (!this.isAvailable()) {
|
|
137
|
-
return null;
|
|
138
|
-
}
|
|
139
|
-
try {
|
|
140
|
-
const output = (0, child_process_1.execSync)(`${this.qmdCommand} status --format json`, {
|
|
141
|
-
encoding: 'utf8',
|
|
142
|
-
timeout: 10000,
|
|
143
|
-
cwd: this.config.memoryDir
|
|
144
|
-
});
|
|
145
|
-
if (!output.trim()) {
|
|
146
|
-
return { files: 0, chunks: 0, lastUpdate: null };
|
|
147
|
-
}
|
|
148
|
-
const result = JSON.parse(output);
|
|
149
|
-
return {
|
|
150
|
-
files: result.files || 0,
|
|
151
|
-
chunks: result.chunks || 0,
|
|
152
|
-
lastUpdate: result.lastUpdate || null
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
catch (error) {
|
|
156
|
-
// QMD status check failed silently
|
|
157
|
-
return { files: 0, chunks: 0, lastUpdate: null };
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
exports.QMDManager = QMDManager;
|
|
162
|
-
//# sourceMappingURL=qmd.js.map
|
package/dist/qmd.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"qmd.js","sourceRoot":"","sources":["../src/qmd.ts"],"names":[],"mappings":"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"}
|