kiro-memory 1.8.1 → 2.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.
@@ -226,8 +226,8 @@ var EmbeddingService = class {
226
226
  initialized = false;
227
227
  initializing = null;
228
228
  /**
229
- * Inizializza il servizio di embedding.
230
- * Tenta fastembed, poi @huggingface/transformers, poi fallback a null.
229
+ * Initialize the embedding service.
230
+ * Tries fastembed, then @huggingface/transformers, then fallback to null.
231
231
  */
232
232
  async initialize() {
233
233
  if (this.initialized) return this.provider !== null;
@@ -248,11 +248,11 @@ var EmbeddingService = class {
248
248
  });
249
249
  this.provider = "fastembed";
250
250
  this.initialized = true;
251
- logger.info("EMBEDDING", "Inizializzato con fastembed (BGE-small-en-v1.5)");
251
+ logger.info("EMBEDDING", "Initialized with fastembed (BGE-small-en-v1.5)");
252
252
  return true;
253
253
  }
254
254
  } catch (error) {
255
- logger.debug("EMBEDDING", `fastembed non disponibile: ${error}`);
255
+ logger.debug("EMBEDDING", `fastembed not available: ${error}`);
256
256
  }
257
257
  try {
258
258
  const transformers = await import("@huggingface/transformers");
@@ -263,20 +263,20 @@ var EmbeddingService = class {
263
263
  });
264
264
  this.provider = "transformers";
265
265
  this.initialized = true;
266
- logger.info("EMBEDDING", "Inizializzato con @huggingface/transformers (all-MiniLM-L6-v2)");
266
+ logger.info("EMBEDDING", "Initialized with @huggingface/transformers (all-MiniLM-L6-v2)");
267
267
  return true;
268
268
  }
269
269
  } catch (error) {
270
- logger.debug("EMBEDDING", `@huggingface/transformers non disponibile: ${error}`);
270
+ logger.debug("EMBEDDING", `@huggingface/transformers not available: ${error}`);
271
271
  }
272
272
  this.provider = null;
273
273
  this.initialized = true;
274
- logger.warn("EMBEDDING", "Nessun provider embedding disponibile, ricerca semantica disabilitata");
274
+ logger.warn("EMBEDDING", "No embedding provider available, semantic search disabled");
275
275
  return false;
276
276
  }
277
277
  /**
278
- * Genera embedding per un singolo testo.
279
- * Ritorna Float32Array con 384 dimensioni, o null se non disponibile.
278
+ * Generate embedding for a single text.
279
+ * Returns Float32Array with 384 dimensions, or null if not available.
280
280
  */
281
281
  async embed(text) {
282
282
  if (!this.initialized) await this.initialize();
@@ -289,46 +289,111 @@ var EmbeddingService = class {
289
289
  return await this._embedTransformers(truncated);
290
290
  }
291
291
  } catch (error) {
292
- logger.error("EMBEDDING", `Errore generazione embedding: ${error}`);
292
+ logger.error("EMBEDDING", `Error generating embedding: ${error}`);
293
293
  }
294
294
  return null;
295
295
  }
296
296
  /**
297
- * Genera embeddings in batch.
297
+ * Generate embeddings in batch.
298
+ * Uses native batch support when available (fastembed, transformers),
299
+ * falls back to serial processing on batch failure.
298
300
  */
299
301
  async embedBatch(texts) {
300
302
  if (!this.initialized) await this.initialize();
301
303
  if (!this.provider || !this.model) return texts.map(() => null);
302
- const results = [];
303
- for (const text of texts) {
304
- try {
305
- const embedding = await this.embed(text);
306
- results.push(embedding);
307
- } catch {
308
- results.push(null);
304
+ if (texts.length === 0) return [];
305
+ const truncated = texts.map((t) => t.substring(0, 2e3));
306
+ try {
307
+ if (this.provider === "fastembed") {
308
+ return await this._embedBatchFastembed(truncated);
309
+ } else if (this.provider === "transformers") {
310
+ return await this._embedBatchTransformers(truncated);
309
311
  }
312
+ } catch (error) {
313
+ logger.warn("EMBEDDING", `Batch embedding failed, falling back to serial: ${error}`);
310
314
  }
311
- return results;
315
+ return this._embedBatchSerial(truncated);
312
316
  }
313
317
  /**
314
- * Verifica se il servizio è disponibile.
318
+ * Check if the service is available.
315
319
  */
316
320
  isAvailable() {
317
321
  return this.initialized && this.provider !== null;
318
322
  }
319
323
  /**
320
- * Nome del provider attivo.
324
+ * Name of the active provider.
321
325
  */
322
326
  getProvider() {
323
327
  return this.provider;
324
328
  }
325
329
  /**
326
- * Dimensioni del vettore embedding.
330
+ * Embedding vector dimensions.
327
331
  */
328
332
  getDimensions() {
329
333
  return 384;
330
334
  }
331
- // --- Provider specifici ---
335
+ // --- Batch implementations ---
336
+ /**
337
+ * Native batch embedding with fastembed.
338
+ * FlagEmbedding.embed() accepts string[] and returns an async iterable of batches.
339
+ */
340
+ async _embedBatchFastembed(texts) {
341
+ const results = [];
342
+ const embeddings = this.model.embed(texts, texts.length);
343
+ for await (const batch of embeddings) {
344
+ if (batch) {
345
+ for (const vec of batch) {
346
+ results.push(vec instanceof Float32Array ? vec : new Float32Array(vec));
347
+ }
348
+ }
349
+ }
350
+ while (results.length < texts.length) {
351
+ results.push(null);
352
+ }
353
+ return results;
354
+ }
355
+ /**
356
+ * Batch embedding with @huggingface/transformers pipeline.
357
+ * The pipeline accepts string[] and returns a Tensor with shape [N, dims].
358
+ */
359
+ async _embedBatchTransformers(texts) {
360
+ const output = await this.model(texts, {
361
+ pooling: "mean",
362
+ normalize: true
363
+ });
364
+ if (!output?.data) {
365
+ return texts.map(() => null);
366
+ }
367
+ const dims = this.getDimensions();
368
+ const data = output.data instanceof Float32Array ? output.data : new Float32Array(output.data);
369
+ const results = [];
370
+ for (let i = 0; i < texts.length; i++) {
371
+ const offset = i * dims;
372
+ if (offset + dims <= data.length) {
373
+ results.push(data.slice(offset, offset + dims));
374
+ } else {
375
+ results.push(null);
376
+ }
377
+ }
378
+ return results;
379
+ }
380
+ /**
381
+ * Serial fallback: embed texts one at a time.
382
+ * Used when native batch fails.
383
+ */
384
+ async _embedBatchSerial(texts) {
385
+ const results = [];
386
+ for (const text of texts) {
387
+ try {
388
+ const embedding = await this.embed(text);
389
+ results.push(embedding);
390
+ } catch {
391
+ results.push(null);
392
+ }
393
+ }
394
+ return results;
395
+ }
396
+ // --- Single-text provider implementations ---
332
397
  async _embedFastembed(text) {
333
398
  const embeddings = this.model.embed([text], 1);
334
399
  for await (const batch of embeddings) {
@@ -359,17 +424,21 @@ function getEmbeddingService() {
359
424
  }
360
425
 
361
426
  // src/services/search/VectorSearch.ts
427
+ var DEFAULT_MAX_CANDIDATES = 2e3;
362
428
  function cosineSimilarity(a, b) {
363
- if (a.length !== b.length) return 0;
429
+ const len = a.length;
430
+ if (len !== b.length) return 0;
364
431
  let dotProduct = 0;
365
432
  let normA = 0;
366
433
  let normB = 0;
367
- for (let i = 0; i < a.length; i++) {
368
- dotProduct += a[i] * b[i];
369
- normA += a[i] * a[i];
370
- normB += b[i] * b[i];
371
- }
372
- const denominator = Math.sqrt(normA) * Math.sqrt(normB);
434
+ for (let i = 0; i < len; i++) {
435
+ const ai = a[i];
436
+ const bi = b[i];
437
+ dotProduct += ai * bi;
438
+ normA += ai * ai;
439
+ normB += bi * bi;
440
+ }
441
+ const denominator = Math.sqrt(normA * normB);
373
442
  if (denominator === 0) return 0;
374
443
  return dotProduct / denominator;
375
444
  }
@@ -382,23 +451,36 @@ function bufferToFloat32(buf) {
382
451
  }
383
452
  var VectorSearch = class {
384
453
  /**
385
- * Ricerca semantica: calcola cosine similarity tra query e tutti gli embeddings.
454
+ * Semantic search with SQL pre-filtering for scalability.
455
+ *
456
+ * 2-phase strategy:
457
+ * 1. SQL pre-filters by project + sorts by recency (loads max N candidates)
458
+ * 2. JS computes cosine similarity only on filtered candidates
459
+ *
460
+ * With 50k observations and maxCandidates=2000, loads only ~4% of data.
386
461
  */
387
462
  async search(db, queryEmbedding, options = {}) {
388
463
  const limit = options.limit || 10;
389
464
  const threshold = options.threshold || 0.3;
465
+ const maxCandidates = options.maxCandidates || DEFAULT_MAX_CANDIDATES;
390
466
  try {
391
- let sql = `
467
+ const conditions = [];
468
+ const params = [];
469
+ if (options.project) {
470
+ conditions.push("o.project = ?");
471
+ params.push(options.project);
472
+ }
473
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
474
+ const sql = `
392
475
  SELECT e.observation_id, e.embedding,
393
476
  o.title, o.text, o.type, o.project, o.created_at, o.created_at_epoch
394
477
  FROM observation_embeddings e
395
478
  JOIN observations o ON o.id = e.observation_id
479
+ ${whereClause}
480
+ ORDER BY o.created_at_epoch DESC
481
+ LIMIT ?
396
482
  `;
397
- const params = [];
398
- if (options.project) {
399
- sql += " WHERE o.project = ?";
400
- params.push(options.project);
401
- }
483
+ params.push(maxCandidates);
402
484
  const rows = db.query(sql).all(...params);
403
485
  const scored = [];
404
486
  for (const row of rows) {
@@ -419,14 +501,15 @@ var VectorSearch = class {
419
501
  }
420
502
  }
421
503
  scored.sort((a, b) => b.similarity - a.similarity);
504
+ logger.debug("VECTOR", `Search: ${rows.length} candidates \u2192 ${scored.length} above threshold \u2192 ${Math.min(scored.length, limit)} results`);
422
505
  return scored.slice(0, limit);
423
506
  } catch (error) {
424
- logger.error("VECTOR", `Errore ricerca vettoriale: ${error}`);
507
+ logger.error("VECTOR", `Vector search error: ${error}`);
425
508
  return [];
426
509
  }
427
510
  }
428
511
  /**
429
- * Salva embedding per un'osservazione.
512
+ * Store embedding for an observation.
430
513
  */
431
514
  async storeEmbedding(db, observationId, embedding, model) {
432
515
  try {
@@ -442,18 +525,18 @@ var VectorSearch = class {
442
525
  embedding.length,
443
526
  (/* @__PURE__ */ new Date()).toISOString()
444
527
  );
445
- logger.debug("VECTOR", `Embedding salvato per osservazione ${observationId}`);
528
+ logger.debug("VECTOR", `Embedding saved for observation ${observationId}`);
446
529
  } catch (error) {
447
- logger.error("VECTOR", `Errore salvataggio embedding: ${error}`);
530
+ logger.error("VECTOR", `Error saving embedding: ${error}`);
448
531
  }
449
532
  }
450
533
  /**
451
- * Genera embeddings per osservazioni che non li hanno ancora.
534
+ * Generate embeddings for observations that don't have them yet.
452
535
  */
453
536
  async backfillEmbeddings(db, batchSize = 50) {
454
537
  const embeddingService2 = getEmbeddingService();
455
538
  if (!await embeddingService2.initialize()) {
456
- logger.warn("VECTOR", "Embedding service non disponibile, backfill saltato");
539
+ logger.warn("VECTOR", "Embedding service not available, backfill skipped");
457
540
  return 0;
458
541
  }
459
542
  const rows = db.query(`
@@ -479,11 +562,11 @@ var VectorSearch = class {
479
562
  count++;
480
563
  }
481
564
  }
482
- logger.info("VECTOR", `Backfill completato: ${count}/${rows.length} embeddings generati`);
565
+ logger.info("VECTOR", `Backfill completed: ${count}/${rows.length} embeddings generated`);
483
566
  return count;
484
567
  }
485
568
  /**
486
- * Statistiche sugli embeddings.
569
+ * Embedding statistics.
487
570
  */
488
571
  getStats(db) {
489
572
  try {