@sylphx/flow 1.1.1 → 1.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/CHANGELOG.md +14 -0
- package/package.json +1 -1
- package/src/commands/hook-command.ts +10 -230
- package/src/composables/index.ts +0 -1
- package/src/config/servers.ts +35 -78
- package/src/core/interfaces.ts +0 -33
- package/src/domains/index.ts +0 -2
- package/src/index.ts +0 -4
- package/src/services/mcp-service.ts +0 -16
- package/src/targets/claude-code.ts +3 -9
- package/src/targets/functional/claude-code-logic.ts +4 -22
- package/src/targets/opencode.ts +0 -6
- package/src/types/mcp.types.ts +29 -38
- package/src/types/target.types.ts +0 -2
- package/src/types.ts +0 -1
- package/src/commands/codebase-command.ts +0 -168
- package/src/commands/knowledge-command.ts +0 -161
- package/src/composables/useTargetConfig.ts +0 -45
- package/src/core/formatting/bytes.test.ts +0 -115
- package/src/core/validation/limit.test.ts +0 -155
- package/src/core/validation/query.test.ts +0 -44
- package/src/domains/codebase/index.ts +0 -5
- package/src/domains/codebase/tools.ts +0 -139
- package/src/domains/knowledge/index.ts +0 -10
- package/src/domains/knowledge/resources.ts +0 -537
- package/src/domains/knowledge/tools.ts +0 -174
- package/src/services/search/base-indexer.ts +0 -156
- package/src/services/search/codebase-indexer-types.ts +0 -38
- package/src/services/search/codebase-indexer.ts +0 -647
- package/src/services/search/embeddings-provider.ts +0 -455
- package/src/services/search/embeddings.ts +0 -316
- package/src/services/search/functional-indexer.ts +0 -323
- package/src/services/search/index.ts +0 -27
- package/src/services/search/indexer.ts +0 -380
- package/src/services/search/knowledge-indexer.ts +0 -422
- package/src/services/search/semantic-search.ts +0 -244
- package/src/services/search/tfidf.ts +0 -559
- package/src/services/search/unified-search-service.ts +0 -888
- package/src/services/storage/cache-storage.ts +0 -487
- package/src/services/storage/drizzle-storage.ts +0 -581
- package/src/services/storage/index.ts +0 -15
- package/src/services/storage/lancedb-vector-storage.ts +0 -494
- package/src/services/storage/memory-storage.ts +0 -268
- package/src/services/storage/separated-storage.ts +0 -467
- package/src/services/storage/vector-storage.ts +0 -13
|
@@ -1,494 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Simple LanceDB vector storage implementation
|
|
3
|
-
* Local-only, no cloud services required
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { logger } from '../../utils/logger.js';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Generate mock embedding for testing/fallback
|
|
10
|
-
*/
|
|
11
|
-
export function generateMockEmbedding(text: string, dimensions = 1536): number[] {
|
|
12
|
-
const words = text.toLowerCase().split(/\s+/);
|
|
13
|
-
const embedding = new Array(dimensions).fill(0);
|
|
14
|
-
|
|
15
|
-
// Simple hash-based pseudo-embedding
|
|
16
|
-
for (const word of words) {
|
|
17
|
-
let hash = 0;
|
|
18
|
-
for (let i = 0; i < word.length; i++) {
|
|
19
|
-
hash = (hash << 5) - hash + word.charCodeAt(i);
|
|
20
|
-
hash &= hash; // Convert to 32-bit integer
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Distribute hash across embedding dimensions
|
|
24
|
-
for (let i = 0; i < dimensions; i++) {
|
|
25
|
-
embedding[i] += Math.sin(hash * (i + 1)) * 0.1;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Normalize the embedding
|
|
30
|
-
const norm = Math.sqrt(embedding.reduce((sum, val) => sum + val * val, 0));
|
|
31
|
-
if (norm > 0) {
|
|
32
|
-
return embedding.map((val) => val / norm);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return embedding;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
import fs from 'node:fs';
|
|
39
|
-
import path from 'node:path';
|
|
40
|
-
import * as lancedb from '@lancedb/lancedb';
|
|
41
|
-
|
|
42
|
-
export interface VectorDocument {
|
|
43
|
-
id: string;
|
|
44
|
-
embedding: number[];
|
|
45
|
-
metadata: {
|
|
46
|
-
type: 'knowledge' | 'code';
|
|
47
|
-
content: string;
|
|
48
|
-
category: string;
|
|
49
|
-
language: string;
|
|
50
|
-
[key: string]: string | number | boolean;
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export interface VectorStorageMetadata {
|
|
55
|
-
dimensions: number;
|
|
56
|
-
count: number;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export interface VectorSearchResult {
|
|
60
|
-
doc: VectorDocument;
|
|
61
|
-
similarity: number;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* LanceDB vector storage - Local only, no cloud services
|
|
66
|
-
*/
|
|
67
|
-
export class VectorStorage {
|
|
68
|
-
private db: any = null;
|
|
69
|
-
private table: any = null;
|
|
70
|
-
private metadata: VectorStorageMetadata;
|
|
71
|
-
private indexPath: string;
|
|
72
|
-
private dimensions: number;
|
|
73
|
-
private tableName = 'vectors';
|
|
74
|
-
|
|
75
|
-
constructor(indexPath: string, dimensions: number) {
|
|
76
|
-
this.indexPath = indexPath;
|
|
77
|
-
this.dimensions = dimensions;
|
|
78
|
-
|
|
79
|
-
this.metadata = {
|
|
80
|
-
dimensions,
|
|
81
|
-
count: 0,
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Initialize the database connection
|
|
87
|
-
*/
|
|
88
|
-
async initialize(): Promise<void> {
|
|
89
|
-
if (this.db) {
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
try {
|
|
94
|
-
// Ensure directory exists
|
|
95
|
-
const dir = path.dirname(this.indexPath);
|
|
96
|
-
if (!fs.existsSync(dir)) {
|
|
97
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Connect to LanceDB (local only)
|
|
101
|
-
this.db = await lancedb.connect(this.indexPath);
|
|
102
|
-
|
|
103
|
-
// Check if table exists
|
|
104
|
-
const tables = await this.db.tableNames();
|
|
105
|
-
if (tables.includes(this.tableName)) {
|
|
106
|
-
this.table = await this.db.openTable(this.tableName);
|
|
107
|
-
|
|
108
|
-
// Update count
|
|
109
|
-
try {
|
|
110
|
-
this.metadata.count = await this.table.countRows();
|
|
111
|
-
logger.info('Loaded LanceDB table', { count: this.metadata.count });
|
|
112
|
-
} catch (_e) {
|
|
113
|
-
this.metadata.count = 0;
|
|
114
|
-
}
|
|
115
|
-
} else {
|
|
116
|
-
// Create simple table
|
|
117
|
-
const data = [
|
|
118
|
-
{
|
|
119
|
-
id: 'init',
|
|
120
|
-
vector: new Array(this.dimensions).fill(0),
|
|
121
|
-
type: 'code',
|
|
122
|
-
content: '',
|
|
123
|
-
category: '',
|
|
124
|
-
language: '',
|
|
125
|
-
},
|
|
126
|
-
];
|
|
127
|
-
|
|
128
|
-
this.table = await this.db.createTable(this.tableName, data);
|
|
129
|
-
|
|
130
|
-
// Remove the init record
|
|
131
|
-
await this.table.delete('id = ?', ['init']);
|
|
132
|
-
|
|
133
|
-
logger.info('Created new LanceDB table', { tableName: this.tableName });
|
|
134
|
-
}
|
|
135
|
-
} catch (error) {
|
|
136
|
-
logger.error('Failed to initialize LanceDB', { error });
|
|
137
|
-
// Fallback to simple in-memory storage
|
|
138
|
-
this.useFallback = true;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
private useFallback = false;
|
|
143
|
-
private fallbackData: Map<string, VectorDocument> = new Map();
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Save index to disk
|
|
147
|
-
*/
|
|
148
|
-
async save(): Promise<void> {
|
|
149
|
-
if (this.useFallback) {
|
|
150
|
-
logger.warn('Using fallback storage - save is no-op');
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// LanceDB automatically saves
|
|
155
|
-
logger.debug('LanceDB saves automatically');
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Add document to index
|
|
160
|
-
*/
|
|
161
|
-
async addDocument(doc: VectorDocument): Promise<void> {
|
|
162
|
-
if (doc.embedding.length !== this.dimensions) {
|
|
163
|
-
throw new Error(
|
|
164
|
-
`Embedding dimension mismatch: expected ${this.dimensions}, got ${doc.embedding.length}`
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (this.useFallback) {
|
|
169
|
-
this.fallbackData.set(doc.id, doc);
|
|
170
|
-
this.metadata.count = this.fallbackData.size;
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
await this.initialize();
|
|
175
|
-
|
|
176
|
-
try {
|
|
177
|
-
// Add to LanceDB
|
|
178
|
-
await this.table.add([
|
|
179
|
-
{
|
|
180
|
-
id: doc.id,
|
|
181
|
-
vector: doc.embedding,
|
|
182
|
-
type: doc.metadata.type,
|
|
183
|
-
content: doc.metadata.content,
|
|
184
|
-
category: doc.metadata.category,
|
|
185
|
-
language: doc.metadata.language,
|
|
186
|
-
},
|
|
187
|
-
]);
|
|
188
|
-
|
|
189
|
-
this.metadata.count++;
|
|
190
|
-
} catch (error) {
|
|
191
|
-
logger.error('Failed to add document, falling back', { error });
|
|
192
|
-
this.useFallback = true;
|
|
193
|
-
this.fallbackData.set(doc.id, doc);
|
|
194
|
-
this.metadata.count = this.fallbackData.size;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Add multiple documents
|
|
200
|
-
*/
|
|
201
|
-
async addDocuments(docs: VectorDocument[]): Promise<void> {
|
|
202
|
-
if (docs.length === 0) {
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Validate all documents
|
|
207
|
-
for (const doc of docs) {
|
|
208
|
-
if (doc.embedding.length !== this.dimensions) {
|
|
209
|
-
throw new Error(
|
|
210
|
-
`Embedding dimension mismatch: expected ${this.dimensions}, got ${doc.embedding.length}`
|
|
211
|
-
);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
if (this.useFallback) {
|
|
216
|
-
for (const doc of docs) {
|
|
217
|
-
this.fallbackData.set(doc.id, doc);
|
|
218
|
-
}
|
|
219
|
-
this.metadata.count = this.fallbackData.size;
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
await this.initialize();
|
|
224
|
-
|
|
225
|
-
try {
|
|
226
|
-
// Convert to LanceDB format
|
|
227
|
-
const records = docs.map((doc) => ({
|
|
228
|
-
id: doc.id,
|
|
229
|
-
vector: doc.embedding,
|
|
230
|
-
type: doc.metadata.type,
|
|
231
|
-
content: doc.metadata.content,
|
|
232
|
-
category: doc.metadata.category,
|
|
233
|
-
language: doc.metadata.language,
|
|
234
|
-
}));
|
|
235
|
-
|
|
236
|
-
// Batch add to LanceDB
|
|
237
|
-
await this.table.add(records);
|
|
238
|
-
this.metadata.count += docs.length;
|
|
239
|
-
|
|
240
|
-
logger.info('Added documents to LanceDB', { count: docs.length });
|
|
241
|
-
} catch (error) {
|
|
242
|
-
logger.error('Failed to add documents, falling back', { error });
|
|
243
|
-
this.useFallback = true;
|
|
244
|
-
for (const doc of docs) {
|
|
245
|
-
this.fallbackData.set(doc.id, doc);
|
|
246
|
-
}
|
|
247
|
-
this.metadata.count = this.fallbackData.size;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* Search for similar documents
|
|
253
|
-
*/
|
|
254
|
-
async search(
|
|
255
|
-
queryEmbedding: number[],
|
|
256
|
-
options: {
|
|
257
|
-
k?: number;
|
|
258
|
-
filter?: (doc: VectorDocument) => boolean;
|
|
259
|
-
} = {}
|
|
260
|
-
): Promise<VectorSearchResult[]> {
|
|
261
|
-
if (queryEmbedding.length !== this.dimensions) {
|
|
262
|
-
throw new Error(
|
|
263
|
-
`Query embedding dimension mismatch: expected ${this.dimensions}, got ${queryEmbedding.length}`
|
|
264
|
-
);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
const { k = 5, filter } = options;
|
|
268
|
-
|
|
269
|
-
if (this.useFallback) {
|
|
270
|
-
// Simple fallback search
|
|
271
|
-
const results: VectorSearchResult[] = [];
|
|
272
|
-
|
|
273
|
-
for (const doc of this.fallbackData.values()) {
|
|
274
|
-
if (filter && !filter(doc)) {
|
|
275
|
-
continue;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
const similarity = this.cosineSimilarity(queryEmbedding, doc.embedding);
|
|
279
|
-
results.push({ doc, similarity });
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
return results.sort((a, b) => b.similarity - a.similarity).slice(0, k);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
await this.initialize();
|
|
286
|
-
|
|
287
|
-
try {
|
|
288
|
-
// Search LanceDB
|
|
289
|
-
const query = this.table.vectorSearch(queryEmbedding).limit(k * 2);
|
|
290
|
-
const results = await query.toArray();
|
|
291
|
-
|
|
292
|
-
const filteredResults: VectorSearchResult[] = [];
|
|
293
|
-
|
|
294
|
-
for (const result of results) {
|
|
295
|
-
// Convert back to our format
|
|
296
|
-
const doc: VectorDocument = {
|
|
297
|
-
id: result.id,
|
|
298
|
-
embedding: result.vector,
|
|
299
|
-
metadata: {
|
|
300
|
-
type: result.type as 'knowledge' | 'code',
|
|
301
|
-
content: result.content || '',
|
|
302
|
-
category: result.category || '',
|
|
303
|
-
language: result.language || '',
|
|
304
|
-
},
|
|
305
|
-
};
|
|
306
|
-
|
|
307
|
-
// Apply filter if provided
|
|
308
|
-
if (filter && !filter(doc)) {
|
|
309
|
-
continue;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// Convert distance to similarity
|
|
313
|
-
const distance = result._distance || 0;
|
|
314
|
-
const similarity = 1 / (1 + distance);
|
|
315
|
-
|
|
316
|
-
filteredResults.push({
|
|
317
|
-
doc,
|
|
318
|
-
similarity,
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
// Stop if we have enough results
|
|
322
|
-
if (filteredResults.length >= k) {
|
|
323
|
-
break;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
return filteredResults;
|
|
328
|
-
} catch (error) {
|
|
329
|
-
logger.error('Vector search failed, falling back', { error });
|
|
330
|
-
// Fallback to simple search
|
|
331
|
-
return this.search(queryEmbedding, options);
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
/**
|
|
336
|
-
* Get document by ID
|
|
337
|
-
*/
|
|
338
|
-
async getDocument(id: string): Promise<VectorDocument | undefined> {
|
|
339
|
-
if (this.useFallback) {
|
|
340
|
-
return this.fallbackData.get(id);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
await this.initialize();
|
|
344
|
-
|
|
345
|
-
try {
|
|
346
|
-
// Simple scan for now (LanceDB API might be different)
|
|
347
|
-
const results = await this.table.limit(1000).toArray();
|
|
348
|
-
const result = results.find((r: any) => r.id === id);
|
|
349
|
-
|
|
350
|
-
if (!result) {
|
|
351
|
-
return undefined;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
return {
|
|
355
|
-
id: result.id,
|
|
356
|
-
embedding: result.vector,
|
|
357
|
-
metadata: {
|
|
358
|
-
type: result.type as 'knowledge' | 'code',
|
|
359
|
-
content: result.content || '',
|
|
360
|
-
category: result.category || '',
|
|
361
|
-
language: result.language || '',
|
|
362
|
-
},
|
|
363
|
-
};
|
|
364
|
-
} catch (error) {
|
|
365
|
-
logger.error('Failed to get document', { error });
|
|
366
|
-
return this.fallbackData.get(id);
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
/**
|
|
371
|
-
* Get all documents
|
|
372
|
-
*/
|
|
373
|
-
async getAllDocuments(): Promise<VectorDocument[]> {
|
|
374
|
-
if (this.useFallback) {
|
|
375
|
-
return Array.from(this.fallbackData.values());
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
await this.initialize();
|
|
379
|
-
|
|
380
|
-
try {
|
|
381
|
-
const results = await this.table.toArray();
|
|
382
|
-
return results.map((result: any) => ({
|
|
383
|
-
id: result.id,
|
|
384
|
-
embedding: result.vector,
|
|
385
|
-
metadata: {
|
|
386
|
-
type: result.type as 'knowledge' | 'code',
|
|
387
|
-
content: result.content || '',
|
|
388
|
-
category: result.category || '',
|
|
389
|
-
language: result.language || '',
|
|
390
|
-
},
|
|
391
|
-
}));
|
|
392
|
-
} catch (error) {
|
|
393
|
-
logger.error('Failed to get all documents', { error });
|
|
394
|
-
return Array.from(this.fallbackData.values());
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
/**
|
|
399
|
-
* Get metadata
|
|
400
|
-
*/
|
|
401
|
-
getMetadata(): VectorStorageMetadata {
|
|
402
|
-
return { ...this.metadata };
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
/**
|
|
406
|
-
* Clear all documents
|
|
407
|
-
*/
|
|
408
|
-
async clear(): Promise<void> {
|
|
409
|
-
if (this.useFallback) {
|
|
410
|
-
this.fallbackData.clear();
|
|
411
|
-
this.metadata.count = 0;
|
|
412
|
-
return;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
await this.initialize();
|
|
416
|
-
|
|
417
|
-
try {
|
|
418
|
-
await this.db.dropTable(this.tableName);
|
|
419
|
-
|
|
420
|
-
// Recreate empty table
|
|
421
|
-
const data = [
|
|
422
|
-
{
|
|
423
|
-
id: 'init',
|
|
424
|
-
vector: new Array(this.dimensions).fill(0),
|
|
425
|
-
type: 'code',
|
|
426
|
-
content: '',
|
|
427
|
-
category: '',
|
|
428
|
-
language: '',
|
|
429
|
-
},
|
|
430
|
-
];
|
|
431
|
-
|
|
432
|
-
this.table = await this.db.createTable(this.tableName, data);
|
|
433
|
-
await this.table.delete('id = ?', ['init']);
|
|
434
|
-
|
|
435
|
-
this.metadata.count = 0;
|
|
436
|
-
logger.info('Cleared all vectors from LanceDB');
|
|
437
|
-
} catch (error) {
|
|
438
|
-
logger.error('Failed to clear vectors', { error });
|
|
439
|
-
this.fallbackData.clear();
|
|
440
|
-
this.metadata.count = 0;
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
/**
|
|
445
|
-
* Load existing storage from disk
|
|
446
|
-
*/
|
|
447
|
-
static async load(indexPath: string): Promise<VectorStorage | null> {
|
|
448
|
-
try {
|
|
449
|
-
if (!fs.existsSync(indexPath)) {
|
|
450
|
-
return null;
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
// Try to connect to see if it's a valid LanceDB
|
|
454
|
-
const db = await lancedb.connect(indexPath);
|
|
455
|
-
const tables = await db.tableNames();
|
|
456
|
-
|
|
457
|
-
if (tables.length === 0) {
|
|
458
|
-
return null;
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
// Default to OpenAI dimensions
|
|
462
|
-
const storage = new VectorStorage(indexPath, 1536);
|
|
463
|
-
return storage;
|
|
464
|
-
} catch (error) {
|
|
465
|
-
logger.error('Failed to load LanceDB storage', { error });
|
|
466
|
-
return null;
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
/**
|
|
471
|
-
* Calculate cosine similarity
|
|
472
|
-
*/
|
|
473
|
-
private cosineSimilarity(a: number[], b: number[]): number {
|
|
474
|
-
if (a.length !== b.length) {
|
|
475
|
-
return 0;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
let dotProduct = 0;
|
|
479
|
-
let normA = 0;
|
|
480
|
-
let normB = 0;
|
|
481
|
-
|
|
482
|
-
for (let i = 0; i < a.length; i++) {
|
|
483
|
-
dotProduct += a[i] * b[i];
|
|
484
|
-
normA += a[i] * a[i];
|
|
485
|
-
normB += b[i] * b[i];
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
if (normA === 0 || normB === 0) {
|
|
489
|
-
return 0;
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
493
|
-
}
|
|
494
|
-
}
|