kiro-memory 1.9.0 → 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.
@@ -98,9 +98,25 @@ function consolidateObservations(db, project, options = {}) {
98
98
  ORDER BY cnt DESC
99
99
  `).all(project, minGroupSize);
100
100
  if (groups.length === 0) return { merged: 0, removed: 0 };
101
- let totalMerged = 0;
102
- let totalRemoved = 0;
101
+ if (options.dryRun) {
102
+ let totalMerged = 0;
103
+ let totalRemoved = 0;
104
+ for (const group of groups) {
105
+ const obsIds = group.ids.split(",").map(Number);
106
+ const placeholders = obsIds.map(() => "?").join(",");
107
+ const count = db.query(
108
+ `SELECT COUNT(*) as cnt FROM observations WHERE id IN (${placeholders})`
109
+ ).get(...obsIds)?.cnt || 0;
110
+ if (count >= minGroupSize) {
111
+ totalMerged += 1;
112
+ totalRemoved += count - 1;
113
+ }
114
+ }
115
+ return { merged: totalMerged, removed: totalRemoved };
116
+ }
103
117
  const runConsolidation = db.transaction(() => {
118
+ let merged = 0;
119
+ let removed = 0;
104
120
  for (const group of groups) {
105
121
  const obsIds = group.ids.split(",").map(Number);
106
122
  const placeholders = obsIds.map(() => "?").join(",");
@@ -108,11 +124,6 @@ function consolidateObservations(db, project, options = {}) {
108
124
  `SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC`
109
125
  ).all(...obsIds);
110
126
  if (observations.length < minGroupSize) continue;
111
- if (options.dryRun) {
112
- totalMerged += 1;
113
- totalRemoved += observations.length - 1;
114
- continue;
115
- }
116
127
  const keeper = observations[0];
117
128
  const others = observations.slice(1);
118
129
  const uniqueTexts = /* @__PURE__ */ new Set();
@@ -125,18 +136,18 @@ function consolidateObservations(db, project, options = {}) {
125
136
  const consolidatedText = Array.from(uniqueTexts).join("\n---\n").substring(0, 1e5);
126
137
  db.run(
127
138
  "UPDATE observations SET text = ?, title = ? WHERE id = ?",
128
- [consolidatedText, `[consolidato x${observations.length}] ${keeper.title}`, keeper.id]
139
+ [consolidatedText, `[consolidated x${observations.length}] ${keeper.title}`, keeper.id]
129
140
  );
130
141
  const removeIds = others.map((o) => o.id);
131
142
  const removePlaceholders = removeIds.map(() => "?").join(",");
132
143
  db.run(`DELETE FROM observations WHERE id IN (${removePlaceholders})`, removeIds);
133
144
  db.run(`DELETE FROM observation_embeddings WHERE observation_id IN (${removePlaceholders})`, removeIds);
134
- totalMerged += 1;
135
- totalRemoved += removeIds.length;
145
+ merged += 1;
146
+ removed += removeIds.length;
136
147
  }
148
+ return { merged, removed };
137
149
  });
138
- runConsolidation();
139
- return { merged: totalMerged, removed: totalRemoved };
150
+ return runConsolidation();
140
151
  }
141
152
  var init_Observations = __esm({
142
153
  "src/services/sqlite/Observations.ts"() {
@@ -163,7 +174,7 @@ function escapeLikePattern3(input) {
163
174
  }
164
175
  function sanitizeFTS5Query(query) {
165
176
  const trimmed = query.length > 1e4 ? query.substring(0, 1e4) : query;
166
- const terms = trimmed.replace(/[""]/g, "").split(/\s+/).filter((t) => t.length > 0).slice(0, 100).map((t) => `"${t}"`);
177
+ const terms = trimmed.replace(/[""\u0022]/g, "").split(/\s+/).filter((t) => t.length > 0).slice(0, 100).map((t) => `"${t}"`);
167
178
  return terms.join(" ");
168
179
  }
169
180
  function searchObservationsFTS(db, query, filters = {}) {
@@ -328,26 +339,38 @@ function getTimeline(db, anchorId, depthBefore = 5, depthAfter = 5) {
328
339
  return [...before, ...self, ...after];
329
340
  }
330
341
  function getProjectStats(db, project) {
331
- const obsStmt = db.query("SELECT COUNT(*) as count FROM observations WHERE project = ?");
332
- const sumStmt = db.query("SELECT COUNT(*) as count FROM summaries WHERE project = ?");
333
- const sesStmt = db.query("SELECT COUNT(*) as count FROM sessions WHERE project = ?");
334
- const prmStmt = db.query("SELECT COUNT(*) as count FROM prompts WHERE project = ?");
335
- const discoveryStmt = db.query(
336
- "SELECT COALESCE(SUM(discovery_tokens), 0) as total FROM observations WHERE project = ?"
337
- );
338
- const discoveryTokens = discoveryStmt.get(project)?.total || 0;
339
- const readStmt = db.query(
340
- `SELECT COALESCE(SUM(
341
- CAST((LENGTH(COALESCE(title, '')) + LENGTH(COALESCE(narrative, ''))) / 4 AS INTEGER)
342
- ), 0) as total FROM observations WHERE project = ?`
343
- );
344
- const readTokens = readStmt.get(project)?.total || 0;
342
+ const sql = `
343
+ WITH
344
+ obs_stats AS (
345
+ SELECT
346
+ COUNT(*) as count,
347
+ COALESCE(SUM(discovery_tokens), 0) as discovery_tokens,
348
+ COALESCE(SUM(
349
+ CAST((LENGTH(COALESCE(title, '')) + LENGTH(COALESCE(narrative, ''))) / 4 AS INTEGER)
350
+ ), 0) as read_tokens
351
+ FROM observations WHERE project = ?
352
+ ),
353
+ sum_count AS (SELECT COUNT(*) as count FROM summaries WHERE project = ?),
354
+ ses_count AS (SELECT COUNT(*) as count FROM sessions WHERE project = ?),
355
+ prm_count AS (SELECT COUNT(*) as count FROM prompts WHERE project = ?)
356
+ SELECT
357
+ obs_stats.count as observations,
358
+ obs_stats.discovery_tokens,
359
+ obs_stats.read_tokens,
360
+ sum_count.count as summaries,
361
+ ses_count.count as sessions,
362
+ prm_count.count as prompts
363
+ FROM obs_stats, sum_count, ses_count, prm_count
364
+ `;
365
+ const row = db.query(sql).get(project, project, project, project);
366
+ const discoveryTokens = row?.discovery_tokens || 0;
367
+ const readTokens = row?.read_tokens || 0;
345
368
  const savings = Math.max(0, discoveryTokens - readTokens);
346
369
  return {
347
- observations: obsStmt.get(project)?.count || 0,
348
- summaries: sumStmt.get(project)?.count || 0,
349
- sessions: sesStmt.get(project)?.count || 0,
350
- prompts: prmStmt.get(project)?.count || 0,
370
+ observations: row?.observations || 0,
371
+ summaries: row?.summaries || 0,
372
+ sessions: row?.sessions || 0,
373
+ prompts: row?.prompts || 0,
351
374
  tokenEconomics: { discoveryTokens, readTokens, savings }
352
375
  };
353
376
  }
@@ -402,14 +425,15 @@ var init_Search = __esm({
402
425
  import BetterSqlite3 from "better-sqlite3";
403
426
  var Database = class {
404
427
  _db;
428
+ _stmtCache = /* @__PURE__ */ new Map();
405
429
  constructor(path, options) {
406
430
  this._db = new BetterSqlite3(path, {
407
- // better-sqlite3 crea il file di default (non serve 'create')
431
+ // better-sqlite3 creates the file by default ('create' not needed)
408
432
  readonly: options?.readwrite === false ? true : false
409
433
  });
410
434
  }
411
435
  /**
412
- * Esegui una query SQL senza risultati
436
+ * Execute a SQL query without results
413
437
  */
414
438
  run(sql, params) {
415
439
  const stmt = this._db.prepare(sql);
@@ -417,51 +441,53 @@ var Database = class {
417
441
  return result;
418
442
  }
419
443
  /**
420
- * Prepara una query con interfaccia compatibile bun:sqlite
444
+ * Prepare a query with bun:sqlite-compatible interface.
445
+ * Returns a cached prepared statement for repeated queries.
421
446
  */
422
447
  query(sql) {
423
- return new BunQueryCompat(this._db, sql);
448
+ let cached = this._stmtCache.get(sql);
449
+ if (!cached) {
450
+ cached = new BunQueryCompat(this._db, sql);
451
+ this._stmtCache.set(sql, cached);
452
+ }
453
+ return cached;
424
454
  }
425
455
  /**
426
- * Crea una transazione
456
+ * Create a transaction
427
457
  */
428
458
  transaction(fn) {
429
459
  return this._db.transaction(fn);
430
460
  }
431
461
  /**
432
- * Chiudi la connessione
462
+ * Close the connection
433
463
  */
434
464
  close() {
465
+ this._stmtCache.clear();
435
466
  this._db.close();
436
467
  }
437
468
  };
438
469
  var BunQueryCompat = class {
439
- _db;
440
- _sql;
470
+ _stmt;
441
471
  constructor(db, sql) {
442
- this._db = db;
443
- this._sql = sql;
472
+ this._stmt = db.prepare(sql);
444
473
  }
445
474
  /**
446
- * Restituisce tutte le righe
475
+ * Returns all rows
447
476
  */
448
477
  all(...params) {
449
- const stmt = this._db.prepare(this._sql);
450
- return params.length > 0 ? stmt.all(...params) : stmt.all();
478
+ return params.length > 0 ? this._stmt.all(...params) : this._stmt.all();
451
479
  }
452
480
  /**
453
- * Restituisce la prima riga o null
481
+ * Returns the first row or null
454
482
  */
455
483
  get(...params) {
456
- const stmt = this._db.prepare(this._sql);
457
- return params.length > 0 ? stmt.get(...params) : stmt.get();
484
+ return params.length > 0 ? this._stmt.get(...params) : this._stmt.get();
458
485
  }
459
486
  /**
460
- * Esegui senza risultati
487
+ * Execute without results
461
488
  */
462
489
  run(...params) {
463
- const stmt = this._db.prepare(this._sql);
464
- return params.length > 0 ? stmt.run(...params) : stmt.run();
490
+ return params.length > 0 ? this._stmt.run(...params) : this._stmt.run();
465
491
  }
466
492
  };
467
493
 
@@ -723,40 +749,62 @@ function ensureDir(dirPath) {
723
749
  var SQLITE_MMAP_SIZE_BYTES = 256 * 1024 * 1024;
724
750
  var SQLITE_CACHE_SIZE_PAGES = 1e4;
725
751
  var KiroMemoryDatabase = class {
726
- db;
752
+ _db;
727
753
  /**
728
- * @param dbPath - Percorso al file SQLite (default: DB_PATH)
729
- * @param skipMigrations - Se true, salta il migration runner (per hook ad alta frequenza)
754
+ * Readonly accessor for the underlying Database instance.
755
+ * Prefer using query() and run() proxy methods directly.
756
+ */
757
+ get db() {
758
+ return this._db;
759
+ }
760
+ /**
761
+ * @param dbPath - Path to the SQLite file (default: DB_PATH)
762
+ * @param skipMigrations - If true, skip the migration runner (for high-frequency hooks)
730
763
  */
731
764
  constructor(dbPath = DB_PATH, skipMigrations = false) {
732
765
  if (dbPath !== ":memory:") {
733
766
  ensureDir(DATA_DIR);
734
767
  }
735
- this.db = new Database(dbPath, { create: true, readwrite: true });
736
- this.db.run("PRAGMA journal_mode = WAL");
737
- this.db.run("PRAGMA synchronous = NORMAL");
738
- this.db.run("PRAGMA foreign_keys = ON");
739
- this.db.run("PRAGMA temp_store = memory");
740
- this.db.run(`PRAGMA mmap_size = ${SQLITE_MMAP_SIZE_BYTES}`);
741
- this.db.run(`PRAGMA cache_size = ${SQLITE_CACHE_SIZE_PAGES}`);
768
+ this._db = new Database(dbPath, { create: true, readwrite: true });
769
+ this._db.run("PRAGMA journal_mode = WAL");
770
+ this._db.run("PRAGMA busy_timeout = 5000");
771
+ this._db.run("PRAGMA synchronous = NORMAL");
772
+ this._db.run("PRAGMA foreign_keys = ON");
773
+ this._db.run("PRAGMA temp_store = memory");
774
+ this._db.run(`PRAGMA mmap_size = ${SQLITE_MMAP_SIZE_BYTES}`);
775
+ this._db.run(`PRAGMA cache_size = ${SQLITE_CACHE_SIZE_PAGES}`);
742
776
  if (!skipMigrations) {
743
- const migrationRunner = new MigrationRunner(this.db);
777
+ const migrationRunner = new MigrationRunner(this._db);
744
778
  migrationRunner.runAllMigrations();
745
779
  }
746
780
  }
747
781
  /**
748
- * Esegue una funzione all'interno di una transazione atomica.
749
- * Se fn() lancia un errore, la transazione viene annullata automaticamente.
782
+ * Prepare a query (delegates to underlying Database).
783
+ * Proxy method to avoid ctx.db.db.query() double access.
784
+ */
785
+ query(sql) {
786
+ return this._db.query(sql);
787
+ }
788
+ /**
789
+ * Execute a SQL statement without results (delegates to underlying Database).
790
+ * Proxy method to avoid ctx.db.db.run() double access.
791
+ */
792
+ run(sql, params) {
793
+ return this._db.run(sql, params);
794
+ }
795
+ /**
796
+ * Executes a function within an atomic transaction.
797
+ * If fn() throws an error, the transaction is automatically rolled back.
750
798
  */
751
799
  withTransaction(fn) {
752
- const transaction = this.db.transaction(fn);
753
- return transaction(this.db);
800
+ const transaction = this._db.transaction(fn);
801
+ return transaction(this._db);
754
802
  }
755
803
  /**
756
804
  * Close the database connection
757
805
  */
758
806
  close() {
759
- this.db.close();
807
+ this._db.close();
760
808
  }
761
809
  };
762
810
  var MigrationRunner = class {
@@ -1251,8 +1299,8 @@ var EmbeddingService = class {
1251
1299
  initialized = false;
1252
1300
  initializing = null;
1253
1301
  /**
1254
- * Inizializza il servizio di embedding.
1255
- * Tenta fastembed, poi @huggingface/transformers, poi fallback a null.
1302
+ * Initialize the embedding service.
1303
+ * Tries fastembed, then @huggingface/transformers, then fallback to null.
1256
1304
  */
1257
1305
  async initialize() {
1258
1306
  if (this.initialized) return this.provider !== null;
@@ -1273,11 +1321,11 @@ var EmbeddingService = class {
1273
1321
  });
1274
1322
  this.provider = "fastembed";
1275
1323
  this.initialized = true;
1276
- logger.info("EMBEDDING", "Inizializzato con fastembed (BGE-small-en-v1.5)");
1324
+ logger.info("EMBEDDING", "Initialized with fastembed (BGE-small-en-v1.5)");
1277
1325
  return true;
1278
1326
  }
1279
1327
  } catch (error) {
1280
- logger.debug("EMBEDDING", `fastembed non disponibile: ${error}`);
1328
+ logger.debug("EMBEDDING", `fastembed not available: ${error}`);
1281
1329
  }
1282
1330
  try {
1283
1331
  const transformers = await import("@huggingface/transformers");
@@ -1288,20 +1336,20 @@ var EmbeddingService = class {
1288
1336
  });
1289
1337
  this.provider = "transformers";
1290
1338
  this.initialized = true;
1291
- logger.info("EMBEDDING", "Inizializzato con @huggingface/transformers (all-MiniLM-L6-v2)");
1339
+ logger.info("EMBEDDING", "Initialized with @huggingface/transformers (all-MiniLM-L6-v2)");
1292
1340
  return true;
1293
1341
  }
1294
1342
  } catch (error) {
1295
- logger.debug("EMBEDDING", `@huggingface/transformers non disponibile: ${error}`);
1343
+ logger.debug("EMBEDDING", `@huggingface/transformers not available: ${error}`);
1296
1344
  }
1297
1345
  this.provider = null;
1298
1346
  this.initialized = true;
1299
- logger.warn("EMBEDDING", "Nessun provider embedding disponibile, ricerca semantica disabilitata");
1347
+ logger.warn("EMBEDDING", "No embedding provider available, semantic search disabled");
1300
1348
  return false;
1301
1349
  }
1302
1350
  /**
1303
- * Genera embedding per un singolo testo.
1304
- * Ritorna Float32Array con 384 dimensioni, o null se non disponibile.
1351
+ * Generate embedding for a single text.
1352
+ * Returns Float32Array with 384 dimensions, or null if not available.
1305
1353
  */
1306
1354
  async embed(text) {
1307
1355
  if (!this.initialized) await this.initialize();
@@ -1314,46 +1362,111 @@ var EmbeddingService = class {
1314
1362
  return await this._embedTransformers(truncated);
1315
1363
  }
1316
1364
  } catch (error) {
1317
- logger.error("EMBEDDING", `Errore generazione embedding: ${error}`);
1365
+ logger.error("EMBEDDING", `Error generating embedding: ${error}`);
1318
1366
  }
1319
1367
  return null;
1320
1368
  }
1321
1369
  /**
1322
- * Genera embeddings in batch.
1370
+ * Generate embeddings in batch.
1371
+ * Uses native batch support when available (fastembed, transformers),
1372
+ * falls back to serial processing on batch failure.
1323
1373
  */
1324
1374
  async embedBatch(texts) {
1325
1375
  if (!this.initialized) await this.initialize();
1326
1376
  if (!this.provider || !this.model) return texts.map(() => null);
1327
- const results = [];
1328
- for (const text of texts) {
1329
- try {
1330
- const embedding = await this.embed(text);
1331
- results.push(embedding);
1332
- } catch {
1333
- results.push(null);
1377
+ if (texts.length === 0) return [];
1378
+ const truncated = texts.map((t) => t.substring(0, 2e3));
1379
+ try {
1380
+ if (this.provider === "fastembed") {
1381
+ return await this._embedBatchFastembed(truncated);
1382
+ } else if (this.provider === "transformers") {
1383
+ return await this._embedBatchTransformers(truncated);
1334
1384
  }
1385
+ } catch (error) {
1386
+ logger.warn("EMBEDDING", `Batch embedding failed, falling back to serial: ${error}`);
1335
1387
  }
1336
- return results;
1388
+ return this._embedBatchSerial(truncated);
1337
1389
  }
1338
1390
  /**
1339
- * Verifica se il servizio è disponibile.
1391
+ * Check if the service is available.
1340
1392
  */
1341
1393
  isAvailable() {
1342
1394
  return this.initialized && this.provider !== null;
1343
1395
  }
1344
1396
  /**
1345
- * Nome del provider attivo.
1397
+ * Name of the active provider.
1346
1398
  */
1347
1399
  getProvider() {
1348
1400
  return this.provider;
1349
1401
  }
1350
1402
  /**
1351
- * Dimensioni del vettore embedding.
1403
+ * Embedding vector dimensions.
1352
1404
  */
1353
1405
  getDimensions() {
1354
1406
  return 384;
1355
1407
  }
1356
- // --- Provider specifici ---
1408
+ // --- Batch implementations ---
1409
+ /**
1410
+ * Native batch embedding with fastembed.
1411
+ * FlagEmbedding.embed() accepts string[] and returns an async iterable of batches.
1412
+ */
1413
+ async _embedBatchFastembed(texts) {
1414
+ const results = [];
1415
+ const embeddings = this.model.embed(texts, texts.length);
1416
+ for await (const batch of embeddings) {
1417
+ if (batch) {
1418
+ for (const vec of batch) {
1419
+ results.push(vec instanceof Float32Array ? vec : new Float32Array(vec));
1420
+ }
1421
+ }
1422
+ }
1423
+ while (results.length < texts.length) {
1424
+ results.push(null);
1425
+ }
1426
+ return results;
1427
+ }
1428
+ /**
1429
+ * Batch embedding with @huggingface/transformers pipeline.
1430
+ * The pipeline accepts string[] and returns a Tensor with shape [N, dims].
1431
+ */
1432
+ async _embedBatchTransformers(texts) {
1433
+ const output = await this.model(texts, {
1434
+ pooling: "mean",
1435
+ normalize: true
1436
+ });
1437
+ if (!output?.data) {
1438
+ return texts.map(() => null);
1439
+ }
1440
+ const dims = this.getDimensions();
1441
+ const data = output.data instanceof Float32Array ? output.data : new Float32Array(output.data);
1442
+ const results = [];
1443
+ for (let i = 0; i < texts.length; i++) {
1444
+ const offset = i * dims;
1445
+ if (offset + dims <= data.length) {
1446
+ results.push(data.slice(offset, offset + dims));
1447
+ } else {
1448
+ results.push(null);
1449
+ }
1450
+ }
1451
+ return results;
1452
+ }
1453
+ /**
1454
+ * Serial fallback: embed texts one at a time.
1455
+ * Used when native batch fails.
1456
+ */
1457
+ async _embedBatchSerial(texts) {
1458
+ const results = [];
1459
+ for (const text of texts) {
1460
+ try {
1461
+ const embedding = await this.embed(text);
1462
+ results.push(embedding);
1463
+ } catch {
1464
+ results.push(null);
1465
+ }
1466
+ }
1467
+ return results;
1468
+ }
1469
+ // --- Single-text provider implementations ---
1357
1470
  async _embedFastembed(text) {
1358
1471
  const embeddings = this.model.embed([text], 1);
1359
1472
  for await (const batch of embeddings) {
@@ -1384,17 +1497,21 @@ function getEmbeddingService() {
1384
1497
  }
1385
1498
 
1386
1499
  // src/services/search/VectorSearch.ts
1500
+ var DEFAULT_MAX_CANDIDATES = 2e3;
1387
1501
  function cosineSimilarity(a, b) {
1388
- if (a.length !== b.length) return 0;
1502
+ const len = a.length;
1503
+ if (len !== b.length) return 0;
1389
1504
  let dotProduct = 0;
1390
1505
  let normA = 0;
1391
1506
  let normB = 0;
1392
- for (let i = 0; i < a.length; i++) {
1393
- dotProduct += a[i] * b[i];
1394
- normA += a[i] * a[i];
1395
- normB += b[i] * b[i];
1396
- }
1397
- const denominator = Math.sqrt(normA) * Math.sqrt(normB);
1507
+ for (let i = 0; i < len; i++) {
1508
+ const ai = a[i];
1509
+ const bi = b[i];
1510
+ dotProduct += ai * bi;
1511
+ normA += ai * ai;
1512
+ normB += bi * bi;
1513
+ }
1514
+ const denominator = Math.sqrt(normA * normB);
1398
1515
  if (denominator === 0) return 0;
1399
1516
  return dotProduct / denominator;
1400
1517
  }
@@ -1407,23 +1524,36 @@ function bufferToFloat32(buf) {
1407
1524
  }
1408
1525
  var VectorSearch = class {
1409
1526
  /**
1410
- * Ricerca semantica: calcola cosine similarity tra query e tutti gli embeddings.
1527
+ * Semantic search with SQL pre-filtering for scalability.
1528
+ *
1529
+ * 2-phase strategy:
1530
+ * 1. SQL pre-filters by project + sorts by recency (loads max N candidates)
1531
+ * 2. JS computes cosine similarity only on filtered candidates
1532
+ *
1533
+ * With 50k observations and maxCandidates=2000, loads only ~4% of data.
1411
1534
  */
1412
1535
  async search(db, queryEmbedding, options = {}) {
1413
1536
  const limit = options.limit || 10;
1414
1537
  const threshold = options.threshold || 0.3;
1538
+ const maxCandidates = options.maxCandidates || DEFAULT_MAX_CANDIDATES;
1415
1539
  try {
1416
- let sql = `
1540
+ const conditions = [];
1541
+ const params = [];
1542
+ if (options.project) {
1543
+ conditions.push("o.project = ?");
1544
+ params.push(options.project);
1545
+ }
1546
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
1547
+ const sql = `
1417
1548
  SELECT e.observation_id, e.embedding,
1418
1549
  o.title, o.text, o.type, o.project, o.created_at, o.created_at_epoch
1419
1550
  FROM observation_embeddings e
1420
1551
  JOIN observations o ON o.id = e.observation_id
1552
+ ${whereClause}
1553
+ ORDER BY o.created_at_epoch DESC
1554
+ LIMIT ?
1421
1555
  `;
1422
- const params = [];
1423
- if (options.project) {
1424
- sql += " WHERE o.project = ?";
1425
- params.push(options.project);
1426
- }
1556
+ params.push(maxCandidates);
1427
1557
  const rows = db.query(sql).all(...params);
1428
1558
  const scored = [];
1429
1559
  for (const row of rows) {
@@ -1444,14 +1574,15 @@ var VectorSearch = class {
1444
1574
  }
1445
1575
  }
1446
1576
  scored.sort((a, b) => b.similarity - a.similarity);
1577
+ logger.debug("VECTOR", `Search: ${rows.length} candidates \u2192 ${scored.length} above threshold \u2192 ${Math.min(scored.length, limit)} results`);
1447
1578
  return scored.slice(0, limit);
1448
1579
  } catch (error) {
1449
- logger.error("VECTOR", `Errore ricerca vettoriale: ${error}`);
1580
+ logger.error("VECTOR", `Vector search error: ${error}`);
1450
1581
  return [];
1451
1582
  }
1452
1583
  }
1453
1584
  /**
1454
- * Salva embedding per un'osservazione.
1585
+ * Store embedding for an observation.
1455
1586
  */
1456
1587
  async storeEmbedding(db, observationId, embedding, model) {
1457
1588
  try {
@@ -1467,18 +1598,18 @@ var VectorSearch = class {
1467
1598
  embedding.length,
1468
1599
  (/* @__PURE__ */ new Date()).toISOString()
1469
1600
  );
1470
- logger.debug("VECTOR", `Embedding salvato per osservazione ${observationId}`);
1601
+ logger.debug("VECTOR", `Embedding saved for observation ${observationId}`);
1471
1602
  } catch (error) {
1472
- logger.error("VECTOR", `Errore salvataggio embedding: ${error}`);
1603
+ logger.error("VECTOR", `Error saving embedding: ${error}`);
1473
1604
  }
1474
1605
  }
1475
1606
  /**
1476
- * Genera embeddings per osservazioni che non li hanno ancora.
1607
+ * Generate embeddings for observations that don't have them yet.
1477
1608
  */
1478
1609
  async backfillEmbeddings(db, batchSize = 50) {
1479
1610
  const embeddingService2 = getEmbeddingService();
1480
1611
  if (!await embeddingService2.initialize()) {
1481
- logger.warn("VECTOR", "Embedding service non disponibile, backfill saltato");
1612
+ logger.warn("VECTOR", "Embedding service not available, backfill skipped");
1482
1613
  return 0;
1483
1614
  }
1484
1615
  const rows = db.query(`
@@ -1504,11 +1635,11 @@ var VectorSearch = class {
1504
1635
  count++;
1505
1636
  }
1506
1637
  }
1507
- logger.info("VECTOR", `Backfill completato: ${count}/${rows.length} embeddings generati`);
1638
+ logger.info("VECTOR", `Backfill completed: ${count}/${rows.length} embeddings generated`);
1508
1639
  return count;
1509
1640
  }
1510
1641
  /**
1511
- * Statistiche sugli embeddings.
1642
+ * Embedding statistics.
1512
1643
  */
1513
1644
  getStats(db) {
1514
1645
  try {
@@ -1581,21 +1712,21 @@ function knowledgeTypeBoost(type) {
1581
1712
  var HybridSearch = class {
1582
1713
  embeddingInitialized = false;
1583
1714
  /**
1584
- * Inizializza il servizio di embedding (lazy, non bloccante)
1715
+ * Initialize the embedding service (lazy, non-blocking)
1585
1716
  */
1586
1717
  async initialize() {
1587
1718
  try {
1588
1719
  const embeddingService2 = getEmbeddingService();
1589
1720
  await embeddingService2.initialize();
1590
1721
  this.embeddingInitialized = embeddingService2.isAvailable();
1591
- logger.info("SEARCH", `HybridSearch inizializzato (embedding: ${this.embeddingInitialized ? "attivo" : "disattivato"})`);
1722
+ logger.info("SEARCH", `HybridSearch initialized (embedding: ${this.embeddingInitialized ? "active" : "disabled"})`);
1592
1723
  } catch (error) {
1593
- logger.warn("SEARCH", "Inizializzazione embedding fallita, uso solo FTS5", {}, error);
1724
+ logger.warn("SEARCH", "Embedding initialization failed, using only FTS5", {}, error);
1594
1725
  this.embeddingInitialized = false;
1595
1726
  }
1596
1727
  }
1597
1728
  /**
1598
- * Ricerca ibrida con scoring a 4 segnali
1729
+ * Hybrid search with 4-signal scoring
1599
1730
  */
1600
1731
  async search(db, query, options = {}) {
1601
1732
  const limit = options.limit || 10;
@@ -1611,7 +1742,7 @@ var HybridSearch = class {
1611
1742
  const vectorResults = await vectorSearch2.search(db, queryEmbedding, {
1612
1743
  project: options.project,
1613
1744
  limit: limit * 2,
1614
- // Prendiamo piu risultati per il ranking
1745
+ // Fetch more results for ranking
1615
1746
  threshold: 0.3
1616
1747
  });
1617
1748
  for (const hit of vectorResults) {
@@ -1628,10 +1759,10 @@ var HybridSearch = class {
1628
1759
  source: "vector"
1629
1760
  });
1630
1761
  }
1631
- logger.debug("SEARCH", `Vector search: ${vectorResults.length} risultati`);
1762
+ logger.debug("SEARCH", `Vector search: ${vectorResults.length} results`);
1632
1763
  }
1633
1764
  } catch (error) {
1634
- logger.warn("SEARCH", "Ricerca vettoriale fallita, uso solo keyword", {}, error);
1765
+ logger.warn("SEARCH", "Vector search failed, using only keyword", {}, error);
1635
1766
  }
1636
1767
  }
1637
1768
  try {
@@ -1661,9 +1792,9 @@ var HybridSearch = class {
1661
1792
  });
1662
1793
  }
1663
1794
  }
1664
- logger.debug("SEARCH", `Keyword search: ${keywordResults.length} risultati`);
1795
+ logger.debug("SEARCH", `Keyword search: ${keywordResults.length} results`);
1665
1796
  } catch (error) {
1666
- logger.error("SEARCH", "Ricerca keyword fallita", {}, error);
1797
+ logger.error("SEARCH", "Keyword search failed", {}, error);
1667
1798
  }
1668
1799
  if (rawItems.size === 0) return [];
1669
1800
  const allFTS5Ranks = Array.from(rawItems.values()).filter((item) => item.fts5Rank !== null).map((item) => item.fts5Rank);
@@ -1751,33 +1882,33 @@ var KiroMemorySDK = class {
1751
1882
  };
1752
1883
  }
1753
1884
  /**
1754
- * Valida input per storeObservation
1885
+ * Validate input for storeObservation
1755
1886
  */
1756
1887
  validateObservationInput(data) {
1757
1888
  if (!data.type || typeof data.type !== "string" || data.type.length > 100) {
1758
- throw new Error("type \xE8 obbligatorio (stringa, max 100 caratteri)");
1889
+ throw new Error("type is required (string, max 100 chars)");
1759
1890
  }
1760
1891
  if (!data.title || typeof data.title !== "string" || data.title.length > 500) {
1761
- throw new Error("title \xE8 obbligatorio (stringa, max 500 caratteri)");
1892
+ throw new Error("title is required (string, max 500 chars)");
1762
1893
  }
1763
1894
  if (!data.content || typeof data.content !== "string" || data.content.length > 1e5) {
1764
- throw new Error("content \xE8 obbligatorio (stringa, max 100KB)");
1895
+ throw new Error("content is required (string, max 100KB)");
1765
1896
  }
1766
1897
  }
1767
1898
  /**
1768
- * Valida input per storeSummary
1899
+ * Validate input for storeSummary
1769
1900
  */
1770
1901
  validateSummaryInput(data) {
1771
1902
  const MAX = 5e4;
1772
1903
  for (const [key, val] of Object.entries(data)) {
1773
1904
  if (val !== void 0 && val !== null) {
1774
- if (typeof val !== "string") throw new Error(`${key} deve essere una stringa`);
1775
- if (val.length > MAX) throw new Error(`${key} troppo grande (max 50KB)`);
1905
+ if (typeof val !== "string") throw new Error(`${key} must be a string`);
1906
+ if (val.length > MAX) throw new Error(`${key} too large (max 50KB)`);
1776
1907
  }
1777
1908
  }
1778
1909
  }
1779
1910
  /**
1780
- * Genera e salva embedding per un'osservazione (fire-and-forget, non blocca)
1911
+ * Generate and store embedding for an observation (fire-and-forget, non-blocking)
1781
1912
  */
1782
1913
  async generateEmbeddingAsync(observationId, title, content, concepts) {
1783
1914
  try {
@@ -1797,39 +1928,39 @@ var KiroMemorySDK = class {
1797
1928
  );
1798
1929
  }
1799
1930
  } catch (error) {
1800
- logger.debug("SDK", `Embedding generation fallita per obs ${observationId}: ${error}`);
1931
+ logger.debug("SDK", `Embedding generation failed for obs ${observationId}: ${error}`);
1801
1932
  }
1802
1933
  }
1803
1934
  /**
1804
- * Genera content hash SHA256 per deduplicazione basata su contenuto.
1805
- * Usa (project + type + title + narrative) come tupla di identità semantica.
1806
- * NON include sessionId perché è unico ad ogni invocazione.
1935
+ * Generate SHA256 content hash for content-based deduplication.
1936
+ * Uses (project + type + title + narrative) as semantic identity tuple.
1937
+ * Does NOT include sessionId since it's unique per invocation.
1807
1938
  */
1808
1939
  generateContentHash(type, title, narrative) {
1809
1940
  const payload = `${this.project}|${type}|${title}|${narrative || ""}`;
1810
1941
  return createHash("sha256").update(payload).digest("hex");
1811
1942
  }
1812
1943
  /**
1813
- * Finestre di deduplicazione per tipo (ms).
1814
- * Tipi con molte ripetizioni hanno finestre più ampie.
1944
+ * Deduplication windows per type (ms).
1945
+ * Types with many repetitions have wider windows.
1815
1946
  */
1816
1947
  getDeduplicationWindow(type) {
1817
1948
  switch (type) {
1818
1949
  case "file-read":
1819
1950
  return 6e4;
1820
- // 60s — letture frequenti sugli stessi file
1951
+ // 60s — frequent reads on the same files
1821
1952
  case "file-write":
1822
1953
  return 1e4;
1823
- // 10s — scritture rapide consecutive
1954
+ // 10s — rapid consecutive writes
1824
1955
  case "command":
1825
1956
  return 3e4;
1826
1957
  // 30s — standard
1827
1958
  case "research":
1828
1959
  return 12e4;
1829
- // 120s — web search e fetch ripetuti
1960
+ // 120s — repeated web search and fetch
1830
1961
  case "delegation":
1831
1962
  return 6e4;
1832
- // 60s — delegazioni rapide
1963
+ // 60s — rapid delegations
1833
1964
  default:
1834
1965
  return 3e4;
1835
1966
  }
@@ -1843,7 +1974,7 @@ var KiroMemorySDK = class {
1843
1974
  const contentHash = this.generateContentHash(data.type, data.title, data.narrative);
1844
1975
  const dedupWindow = this.getDeduplicationWindow(data.type);
1845
1976
  if (isDuplicateObservation(this.db.db, contentHash, dedupWindow)) {
1846
- logger.debug("SDK", `Osservazione duplicata scartata (${data.type}, ${dedupWindow}ms): ${data.title}`);
1977
+ logger.debug("SDK", `Duplicate observation discarded (${data.type}, ${dedupWindow}ms): ${data.title}`);
1847
1978
  return -1;
1848
1979
  }
1849
1980
  const filesRead = data.filesRead || (data.type === "file-read" ? data.files : void 0);
@@ -1871,12 +2002,12 @@ var KiroMemorySDK = class {
1871
2002
  return observationId;
1872
2003
  }
1873
2004
  /**
1874
- * Salva conoscenza strutturata (constraint, decision, heuristic, rejected).
1875
- * Usa il campo `type` per il knowledgeType e `facts` per i metadati JSON.
2005
+ * Store structured knowledge (constraint, decision, heuristic, rejected).
2006
+ * Uses the `type` field for knowledgeType and `facts` for JSON metadata.
1876
2007
  */
1877
2008
  async storeKnowledge(data) {
1878
2009
  if (!KNOWLEDGE_TYPES.includes(data.knowledgeType)) {
1879
- throw new Error(`knowledgeType non valido: ${data.knowledgeType}. Valori ammessi: ${KNOWLEDGE_TYPES.join(", ")}`);
2010
+ throw new Error(`Invalid knowledgeType: ${data.knowledgeType}. Allowed values: ${KNOWLEDGE_TYPES.join(", ")}`);
1880
2011
  }
1881
2012
  this.validateObservationInput({ type: data.knowledgeType, title: data.title, content: data.content });
1882
2013
  const metadata = (() => {
@@ -1908,9 +2039,9 @@ var KiroMemorySDK = class {
1908
2039
  }
1909
2040
  })();
1910
2041
  const sessionId = "sdk-" + Date.now();
1911
- const contentHash = this.generateContentHash(data.type, data.title);
2042
+ const contentHash = this.generateContentHash(data.knowledgeType, data.title);
1912
2043
  if (isDuplicateObservation(this.db.db, contentHash)) {
1913
- logger.debug("SDK", `Knowledge duplicata scartata: ${data.title}`);
2044
+ logger.debug("SDK", `Duplicate knowledge discarded: ${data.title}`);
1914
2045
  return -1;
1915
2046
  }
1916
2047
  const discoveryTokens = Math.ceil(data.content.length / 4);
@@ -1927,11 +2058,11 @@ var KiroMemorySDK = class {
1927
2058
  null,
1928
2059
  // narrative
1929
2060
  JSON.stringify(metadata),
1930
- // facts = metadati JSON
2061
+ // facts = JSON metadata
1931
2062
  data.concepts?.join(", ") || null,
1932
2063
  data.files?.join(", ") || null,
1933
2064
  null,
1934
- // filesModified: knowledge non modifica file
2065
+ // filesModified: knowledge doesn't modify files
1935
2066
  0,
1936
2067
  // prompt_number
1937
2068
  contentHash,
@@ -2042,8 +2173,8 @@ var KiroMemorySDK = class {
2042
2173
  return this.project;
2043
2174
  }
2044
2175
  /**
2045
- * Ricerca ibrida: vector search + keyword FTS5
2046
- * Richiede inizializzazione HybridSearch (embedding service)
2176
+ * Hybrid search: vector search + keyword FTS5
2177
+ * Requires HybridSearch initialization (embedding service)
2047
2178
  */
2048
2179
  async hybridSearch(query, options = {}) {
2049
2180
  const hybridSearch2 = getHybridSearch();
@@ -2053,8 +2184,8 @@ var KiroMemorySDK = class {
2053
2184
  });
2054
2185
  }
2055
2186
  /**
2056
- * Ricerca solo semantica (vector search)
2057
- * Ritorna risultati basati su similarità coseno con gli embeddings
2187
+ * Semantic-only search (vector search)
2188
+ * Returns results based on cosine similarity with embeddings
2058
2189
  */
2059
2190
  async semanticSearch(query, options = {}) {
2060
2191
  const embeddingService2 = getEmbeddingService();
@@ -2089,21 +2220,21 @@ var KiroMemorySDK = class {
2089
2220
  }));
2090
2221
  }
2091
2222
  /**
2092
- * Genera embeddings per osservazioni che non li hanno ancora
2223
+ * Generate embeddings for observations that don't have them yet
2093
2224
  */
2094
2225
  async backfillEmbeddings(batchSize = 50) {
2095
2226
  const vectorSearch2 = getVectorSearch();
2096
2227
  return vectorSearch2.backfillEmbeddings(this.db.db, batchSize);
2097
2228
  }
2098
2229
  /**
2099
- * Statistiche sugli embeddings nel database
2230
+ * Embedding statistics in the database
2100
2231
  */
2101
2232
  getEmbeddingStats() {
2102
2233
  const vectorSearch2 = getVectorSearch();
2103
2234
  return vectorSearch2.getStats(this.db.db);
2104
2235
  }
2105
2236
  /**
2106
- * Inizializza il servizio di embedding (lazy, chiamare prima di hybridSearch)
2237
+ * Initialize the embedding service (lazy, call before hybridSearch)
2107
2238
  */
2108
2239
  async initializeEmbeddings() {
2109
2240
  const hybridSearch2 = getHybridSearch();
@@ -2111,10 +2242,10 @@ var KiroMemorySDK = class {
2111
2242
  return getEmbeddingService().isAvailable();
2112
2243
  }
2113
2244
  /**
2114
- * Contesto smart con ranking a 4 segnali e budget token.
2245
+ * Smart context with 4-signal ranking and token budget.
2115
2246
  *
2116
- * Se query presente: usa HybridSearch con SEARCH_WEIGHTS.
2117
- * Se senza query: ranking per recency + project match (CONTEXT_WEIGHTS).
2247
+ * If query present: uses HybridSearch with SEARCH_WEIGHTS.
2248
+ * If no query: ranking by recency + project match (CONTEXT_WEIGHTS).
2118
2249
  */
2119
2250
  async getSmartContext(options = {}) {
2120
2251
  const tokenBudget = options.tokenBudget || parseInt(process.env.KIRO_MEMORY_CONTEXT_TOKENS || "0", 10) || 2e3;
@@ -2188,8 +2319,8 @@ var KiroMemorySDK = class {
2188
2319
  };
2189
2320
  }
2190
2321
  /**
2191
- * Rileva osservazioni stale (file modificati dopo la creazione) e le marca nel DB.
2192
- * Ritorna il numero di osservazioni marcate come stale.
2322
+ * Detect stale observations (files modified after creation) and mark them in DB.
2323
+ * Returns the number of observations marked as stale.
2193
2324
  */
2194
2325
  async detectStaleObservations() {
2195
2326
  const staleObs = getStaleObservations(this.db.db, this.project);
@@ -2200,14 +2331,14 @@ var KiroMemorySDK = class {
2200
2331
  return staleObs.length;
2201
2332
  }
2202
2333
  /**
2203
- * Consolida osservazioni duplicate sullo stesso file e tipo.
2204
- * Raggruppa per (project, type, files_modified), mantiene la piu recente.
2334
+ * Consolidate duplicate observations on the same file and type.
2335
+ * Groups by (project, type, files_modified), keeps the most recent.
2205
2336
  */
2206
2337
  async consolidateObservations(options = {}) {
2207
2338
  return consolidateObservations(this.db.db, this.project, options);
2208
2339
  }
2209
2340
  /**
2210
- * Statistiche decay: totale, stale, mai accedute, accedute di recente.
2341
+ * Decay statistics: total, stale, never accessed, recently accessed.
2211
2342
  */
2212
2343
  async getDecayStats() {
2213
2344
  const total = this.db.db.query(
@@ -2226,8 +2357,8 @@ var KiroMemorySDK = class {
2226
2357
  return { total, stale, neverAccessed, recentlyAccessed };
2227
2358
  }
2228
2359
  /**
2229
- * Crea un checkpoint strutturato per resume sessione.
2230
- * Salva automaticamente un context_snapshot con le ultime 10 osservazioni.
2360
+ * Create a structured checkpoint for session resume.
2361
+ * Automatically saves a context_snapshot with the last 10 observations.
2231
2362
  */
2232
2363
  async createCheckpoint(sessionId, data) {
2233
2364
  const recentObs = getObservationsByProject(this.db.db, this.project, 10);
@@ -2244,21 +2375,21 @@ var KiroMemorySDK = class {
2244
2375
  });
2245
2376
  }
2246
2377
  /**
2247
- * Recupera l'ultimo checkpoint di una sessione specifica.
2378
+ * Retrieve the latest checkpoint of a specific session.
2248
2379
  */
2249
2380
  async getCheckpoint(sessionId) {
2250
2381
  return getLatestCheckpoint(this.db.db, sessionId);
2251
2382
  }
2252
2383
  /**
2253
- * Recupera l'ultimo checkpoint per il progetto corrente.
2254
- * Utile per resume automatico senza specificare session ID.
2384
+ * Retrieve the latest checkpoint for the current project.
2385
+ * Useful for automatic resume without specifying session ID.
2255
2386
  */
2256
2387
  async getLatestProjectCheckpoint() {
2257
2388
  return getLatestCheckpointByProject(this.db.db, this.project);
2258
2389
  }
2259
2390
  /**
2260
- * Genera un report di attività per il progetto corrente.
2261
- * Aggrega osservazioni, sessioni, summaries e file per un periodo temporale.
2391
+ * Generate an activity report for the current project.
2392
+ * Aggregates observations, sessions, summaries and files for a time period.
2262
2393
  */
2263
2394
  async generateReport(options) {
2264
2395
  const now = /* @__PURE__ */ new Date();