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.
@@ -324,9 +324,25 @@ function consolidateObservations(db, project, options = {}) {
324
324
  ORDER BY cnt DESC
325
325
  `).all(project, minGroupSize);
326
326
  if (groups.length === 0) return { merged: 0, removed: 0 };
327
- let totalMerged = 0;
328
- let totalRemoved = 0;
327
+ if (options.dryRun) {
328
+ let totalMerged = 0;
329
+ let totalRemoved = 0;
330
+ for (const group of groups) {
331
+ const obsIds = group.ids.split(",").map(Number);
332
+ const placeholders = obsIds.map(() => "?").join(",");
333
+ const count = db.query(
334
+ `SELECT COUNT(*) as cnt FROM observations WHERE id IN (${placeholders})`
335
+ ).get(...obsIds)?.cnt || 0;
336
+ if (count >= minGroupSize) {
337
+ totalMerged += 1;
338
+ totalRemoved += count - 1;
339
+ }
340
+ }
341
+ return { merged: totalMerged, removed: totalRemoved };
342
+ }
329
343
  const runConsolidation = db.transaction(() => {
344
+ let merged = 0;
345
+ let removed = 0;
330
346
  for (const group of groups) {
331
347
  const obsIds = group.ids.split(",").map(Number);
332
348
  const placeholders = obsIds.map(() => "?").join(",");
@@ -334,11 +350,6 @@ function consolidateObservations(db, project, options = {}) {
334
350
  `SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC`
335
351
  ).all(...obsIds);
336
352
  if (observations.length < minGroupSize) continue;
337
- if (options.dryRun) {
338
- totalMerged += 1;
339
- totalRemoved += observations.length - 1;
340
- continue;
341
- }
342
353
  const keeper = observations[0];
343
354
  const others = observations.slice(1);
344
355
  const uniqueTexts = /* @__PURE__ */ new Set();
@@ -351,18 +362,18 @@ function consolidateObservations(db, project, options = {}) {
351
362
  const consolidatedText = Array.from(uniqueTexts).join("\n---\n").substring(0, 1e5);
352
363
  db.run(
353
364
  "UPDATE observations SET text = ?, title = ? WHERE id = ?",
354
- [consolidatedText, `[consolidato x${observations.length}] ${keeper.title}`, keeper.id]
365
+ [consolidatedText, `[consolidated x${observations.length}] ${keeper.title}`, keeper.id]
355
366
  );
356
367
  const removeIds = others.map((o) => o.id);
357
368
  const removePlaceholders = removeIds.map(() => "?").join(",");
358
369
  db.run(`DELETE FROM observations WHERE id IN (${removePlaceholders})`, removeIds);
359
370
  db.run(`DELETE FROM observation_embeddings WHERE observation_id IN (${removePlaceholders})`, removeIds);
360
- totalMerged += 1;
361
- totalRemoved += removeIds.length;
371
+ merged += 1;
372
+ removed += removeIds.length;
362
373
  }
374
+ return { merged, removed };
363
375
  });
364
- runConsolidation();
365
- return { merged: totalMerged, removed: totalRemoved };
376
+ return runConsolidation();
366
377
  }
367
378
  var init_Observations = __esm({
368
379
  "src/services/sqlite/Observations.ts"() {
@@ -389,7 +400,7 @@ function escapeLikePattern3(input) {
389
400
  }
390
401
  function sanitizeFTS5Query(query) {
391
402
  const trimmed = query.length > 1e4 ? query.substring(0, 1e4) : query;
392
- const terms = trimmed.replace(/[""]/g, "").split(/\s+/).filter((t) => t.length > 0).slice(0, 100).map((t) => `"${t}"`);
403
+ const terms = trimmed.replace(/[""\u0022]/g, "").split(/\s+/).filter((t) => t.length > 0).slice(0, 100).map((t) => `"${t}"`);
393
404
  return terms.join(" ");
394
405
  }
395
406
  function searchObservationsFTS(db, query, filters = {}) {
@@ -554,26 +565,38 @@ function getTimeline(db, anchorId, depthBefore = 5, depthAfter = 5) {
554
565
  return [...before, ...self, ...after];
555
566
  }
556
567
  function getProjectStats(db, project) {
557
- const obsStmt = db.query("SELECT COUNT(*) as count FROM observations WHERE project = ?");
558
- const sumStmt = db.query("SELECT COUNT(*) as count FROM summaries WHERE project = ?");
559
- const sesStmt = db.query("SELECT COUNT(*) as count FROM sessions WHERE project = ?");
560
- const prmStmt = db.query("SELECT COUNT(*) as count FROM prompts WHERE project = ?");
561
- const discoveryStmt = db.query(
562
- "SELECT COALESCE(SUM(discovery_tokens), 0) as total FROM observations WHERE project = ?"
563
- );
564
- const discoveryTokens = discoveryStmt.get(project)?.total || 0;
565
- const readStmt = db.query(
566
- `SELECT COALESCE(SUM(
567
- CAST((LENGTH(COALESCE(title, '')) + LENGTH(COALESCE(narrative, ''))) / 4 AS INTEGER)
568
- ), 0) as total FROM observations WHERE project = ?`
569
- );
570
- const readTokens = readStmt.get(project)?.total || 0;
568
+ const sql = `
569
+ WITH
570
+ obs_stats AS (
571
+ SELECT
572
+ COUNT(*) as count,
573
+ COALESCE(SUM(discovery_tokens), 0) as discovery_tokens,
574
+ COALESCE(SUM(
575
+ CAST((LENGTH(COALESCE(title, '')) + LENGTH(COALESCE(narrative, ''))) / 4 AS INTEGER)
576
+ ), 0) as read_tokens
577
+ FROM observations WHERE project = ?
578
+ ),
579
+ sum_count AS (SELECT COUNT(*) as count FROM summaries WHERE project = ?),
580
+ ses_count AS (SELECT COUNT(*) as count FROM sessions WHERE project = ?),
581
+ prm_count AS (SELECT COUNT(*) as count FROM prompts WHERE project = ?)
582
+ SELECT
583
+ obs_stats.count as observations,
584
+ obs_stats.discovery_tokens,
585
+ obs_stats.read_tokens,
586
+ sum_count.count as summaries,
587
+ ses_count.count as sessions,
588
+ prm_count.count as prompts
589
+ FROM obs_stats, sum_count, ses_count, prm_count
590
+ `;
591
+ const row = db.query(sql).get(project, project, project, project);
592
+ const discoveryTokens = row?.discovery_tokens || 0;
593
+ const readTokens = row?.read_tokens || 0;
571
594
  const savings = Math.max(0, discoveryTokens - readTokens);
572
595
  return {
573
- observations: obsStmt.get(project)?.count || 0,
574
- summaries: sumStmt.get(project)?.count || 0,
575
- sessions: sesStmt.get(project)?.count || 0,
576
- prompts: prmStmt.get(project)?.count || 0,
596
+ observations: row?.observations || 0,
597
+ summaries: row?.summaries || 0,
598
+ sessions: row?.sessions || 0,
599
+ prompts: row?.prompts || 0,
577
600
  tokenEconomics: { discoveryTokens, readTokens, savings }
578
601
  };
579
602
  }
@@ -647,8 +670,8 @@ var init_EmbeddingService = __esm({
647
670
  initialized = false;
648
671
  initializing = null;
649
672
  /**
650
- * Inizializza il servizio di embedding.
651
- * Tenta fastembed, poi @huggingface/transformers, poi fallback a null.
673
+ * Initialize the embedding service.
674
+ * Tries fastembed, then @huggingface/transformers, then fallback to null.
652
675
  */
653
676
  async initialize() {
654
677
  if (this.initialized) return this.provider !== null;
@@ -669,11 +692,11 @@ var init_EmbeddingService = __esm({
669
692
  });
670
693
  this.provider = "fastembed";
671
694
  this.initialized = true;
672
- logger.info("EMBEDDING", "Inizializzato con fastembed (BGE-small-en-v1.5)");
695
+ logger.info("EMBEDDING", "Initialized with fastembed (BGE-small-en-v1.5)");
673
696
  return true;
674
697
  }
675
698
  } catch (error) {
676
- logger.debug("EMBEDDING", `fastembed non disponibile: ${error}`);
699
+ logger.debug("EMBEDDING", `fastembed not available: ${error}`);
677
700
  }
678
701
  try {
679
702
  const transformers = await import("@huggingface/transformers");
@@ -684,20 +707,20 @@ var init_EmbeddingService = __esm({
684
707
  });
685
708
  this.provider = "transformers";
686
709
  this.initialized = true;
687
- logger.info("EMBEDDING", "Inizializzato con @huggingface/transformers (all-MiniLM-L6-v2)");
710
+ logger.info("EMBEDDING", "Initialized with @huggingface/transformers (all-MiniLM-L6-v2)");
688
711
  return true;
689
712
  }
690
713
  } catch (error) {
691
- logger.debug("EMBEDDING", `@huggingface/transformers non disponibile: ${error}`);
714
+ logger.debug("EMBEDDING", `@huggingface/transformers not available: ${error}`);
692
715
  }
693
716
  this.provider = null;
694
717
  this.initialized = true;
695
- logger.warn("EMBEDDING", "Nessun provider embedding disponibile, ricerca semantica disabilitata");
718
+ logger.warn("EMBEDDING", "No embedding provider available, semantic search disabled");
696
719
  return false;
697
720
  }
698
721
  /**
699
- * Genera embedding per un singolo testo.
700
- * Ritorna Float32Array con 384 dimensioni, o null se non disponibile.
722
+ * Generate embedding for a single text.
723
+ * Returns Float32Array with 384 dimensions, or null if not available.
701
724
  */
702
725
  async embed(text) {
703
726
  if (!this.initialized) await this.initialize();
@@ -710,46 +733,111 @@ var init_EmbeddingService = __esm({
710
733
  return await this._embedTransformers(truncated);
711
734
  }
712
735
  } catch (error) {
713
- logger.error("EMBEDDING", `Errore generazione embedding: ${error}`);
736
+ logger.error("EMBEDDING", `Error generating embedding: ${error}`);
714
737
  }
715
738
  return null;
716
739
  }
717
740
  /**
718
- * Genera embeddings in batch.
741
+ * Generate embeddings in batch.
742
+ * Uses native batch support when available (fastembed, transformers),
743
+ * falls back to serial processing on batch failure.
719
744
  */
720
745
  async embedBatch(texts) {
721
746
  if (!this.initialized) await this.initialize();
722
747
  if (!this.provider || !this.model) return texts.map(() => null);
723
- const results = [];
724
- for (const text of texts) {
725
- try {
726
- const embedding = await this.embed(text);
727
- results.push(embedding);
728
- } catch {
729
- results.push(null);
748
+ if (texts.length === 0) return [];
749
+ const truncated = texts.map((t) => t.substring(0, 2e3));
750
+ try {
751
+ if (this.provider === "fastembed") {
752
+ return await this._embedBatchFastembed(truncated);
753
+ } else if (this.provider === "transformers") {
754
+ return await this._embedBatchTransformers(truncated);
730
755
  }
756
+ } catch (error) {
757
+ logger.warn("EMBEDDING", `Batch embedding failed, falling back to serial: ${error}`);
731
758
  }
732
- return results;
759
+ return this._embedBatchSerial(truncated);
733
760
  }
734
761
  /**
735
- * Verifica se il servizio è disponibile.
762
+ * Check if the service is available.
736
763
  */
737
764
  isAvailable() {
738
765
  return this.initialized && this.provider !== null;
739
766
  }
740
767
  /**
741
- * Nome del provider attivo.
768
+ * Name of the active provider.
742
769
  */
743
770
  getProvider() {
744
771
  return this.provider;
745
772
  }
746
773
  /**
747
- * Dimensioni del vettore embedding.
774
+ * Embedding vector dimensions.
748
775
  */
749
776
  getDimensions() {
750
777
  return 384;
751
778
  }
752
- // --- Provider specifici ---
779
+ // --- Batch implementations ---
780
+ /**
781
+ * Native batch embedding with fastembed.
782
+ * FlagEmbedding.embed() accepts string[] and returns an async iterable of batches.
783
+ */
784
+ async _embedBatchFastembed(texts) {
785
+ const results = [];
786
+ const embeddings = this.model.embed(texts, texts.length);
787
+ for await (const batch of embeddings) {
788
+ if (batch) {
789
+ for (const vec of batch) {
790
+ results.push(vec instanceof Float32Array ? vec : new Float32Array(vec));
791
+ }
792
+ }
793
+ }
794
+ while (results.length < texts.length) {
795
+ results.push(null);
796
+ }
797
+ return results;
798
+ }
799
+ /**
800
+ * Batch embedding with @huggingface/transformers pipeline.
801
+ * The pipeline accepts string[] and returns a Tensor with shape [N, dims].
802
+ */
803
+ async _embedBatchTransformers(texts) {
804
+ const output = await this.model(texts, {
805
+ pooling: "mean",
806
+ normalize: true
807
+ });
808
+ if (!output?.data) {
809
+ return texts.map(() => null);
810
+ }
811
+ const dims = this.getDimensions();
812
+ const data = output.data instanceof Float32Array ? output.data : new Float32Array(output.data);
813
+ const results = [];
814
+ for (let i = 0; i < texts.length; i++) {
815
+ const offset = i * dims;
816
+ if (offset + dims <= data.length) {
817
+ results.push(data.slice(offset, offset + dims));
818
+ } else {
819
+ results.push(null);
820
+ }
821
+ }
822
+ return results;
823
+ }
824
+ /**
825
+ * Serial fallback: embed texts one at a time.
826
+ * Used when native batch fails.
827
+ */
828
+ async _embedBatchSerial(texts) {
829
+ const results = [];
830
+ for (const text of texts) {
831
+ try {
832
+ const embedding = await this.embed(text);
833
+ results.push(embedding);
834
+ } catch {
835
+ results.push(null);
836
+ }
837
+ }
838
+ return results;
839
+ }
840
+ // --- Single-text provider implementations ---
753
841
  async _embedFastembed(text) {
754
842
  const embeddings = this.model.embed([text], 1);
755
843
  for await (const batch of embeddings) {
@@ -779,14 +867,15 @@ var init_EmbeddingService = __esm({
779
867
  import BetterSqlite3 from "better-sqlite3";
780
868
  var Database = class {
781
869
  _db;
870
+ _stmtCache = /* @__PURE__ */ new Map();
782
871
  constructor(path, options) {
783
872
  this._db = new BetterSqlite3(path, {
784
- // better-sqlite3 crea il file di default (non serve 'create')
873
+ // better-sqlite3 creates the file by default ('create' not needed)
785
874
  readonly: options?.readwrite === false ? true : false
786
875
  });
787
876
  }
788
877
  /**
789
- * Esegui una query SQL senza risultati
878
+ * Execute a SQL query without results
790
879
  */
791
880
  run(sql, params) {
792
881
  const stmt = this._db.prepare(sql);
@@ -794,51 +883,53 @@ var Database = class {
794
883
  return result;
795
884
  }
796
885
  /**
797
- * Prepara una query con interfaccia compatibile bun:sqlite
886
+ * Prepare a query with bun:sqlite-compatible interface.
887
+ * Returns a cached prepared statement for repeated queries.
798
888
  */
799
889
  query(sql) {
800
- return new BunQueryCompat(this._db, sql);
890
+ let cached = this._stmtCache.get(sql);
891
+ if (!cached) {
892
+ cached = new BunQueryCompat(this._db, sql);
893
+ this._stmtCache.set(sql, cached);
894
+ }
895
+ return cached;
801
896
  }
802
897
  /**
803
- * Crea una transazione
898
+ * Create a transaction
804
899
  */
805
900
  transaction(fn) {
806
901
  return this._db.transaction(fn);
807
902
  }
808
903
  /**
809
- * Chiudi la connessione
904
+ * Close the connection
810
905
  */
811
906
  close() {
907
+ this._stmtCache.clear();
812
908
  this._db.close();
813
909
  }
814
910
  };
815
911
  var BunQueryCompat = class {
816
- _db;
817
- _sql;
912
+ _stmt;
818
913
  constructor(db, sql) {
819
- this._db = db;
820
- this._sql = sql;
914
+ this._stmt = db.prepare(sql);
821
915
  }
822
916
  /**
823
- * Restituisce tutte le righe
917
+ * Returns all rows
824
918
  */
825
919
  all(...params) {
826
- const stmt = this._db.prepare(this._sql);
827
- return params.length > 0 ? stmt.all(...params) : stmt.all();
920
+ return params.length > 0 ? this._stmt.all(...params) : this._stmt.all();
828
921
  }
829
922
  /**
830
- * Restituisce la prima riga o null
923
+ * Returns the first row or null
831
924
  */
832
925
  get(...params) {
833
- const stmt = this._db.prepare(this._sql);
834
- return params.length > 0 ? stmt.get(...params) : stmt.get();
926
+ return params.length > 0 ? this._stmt.get(...params) : this._stmt.get();
835
927
  }
836
928
  /**
837
- * Esegui senza risultati
929
+ * Execute without results
838
930
  */
839
931
  run(...params) {
840
- const stmt = this._db.prepare(this._sql);
841
- return params.length > 0 ? stmt.run(...params) : stmt.run();
932
+ return params.length > 0 ? this._stmt.run(...params) : this._stmt.run();
842
933
  }
843
934
  };
844
935
 
@@ -881,40 +972,62 @@ init_logger();
881
972
  var SQLITE_MMAP_SIZE_BYTES = 256 * 1024 * 1024;
882
973
  var SQLITE_CACHE_SIZE_PAGES = 1e4;
883
974
  var KiroMemoryDatabase = class {
884
- db;
975
+ _db;
976
+ /**
977
+ * Readonly accessor for the underlying Database instance.
978
+ * Prefer using query() and run() proxy methods directly.
979
+ */
980
+ get db() {
981
+ return this._db;
982
+ }
885
983
  /**
886
- * @param dbPath - Percorso al file SQLite (default: DB_PATH)
887
- * @param skipMigrations - Se true, salta il migration runner (per hook ad alta frequenza)
984
+ * @param dbPath - Path to the SQLite file (default: DB_PATH)
985
+ * @param skipMigrations - If true, skip the migration runner (for high-frequency hooks)
888
986
  */
889
987
  constructor(dbPath = DB_PATH, skipMigrations = false) {
890
988
  if (dbPath !== ":memory:") {
891
989
  ensureDir(DATA_DIR);
892
990
  }
893
- this.db = new Database(dbPath, { create: true, readwrite: true });
894
- this.db.run("PRAGMA journal_mode = WAL");
895
- this.db.run("PRAGMA synchronous = NORMAL");
896
- this.db.run("PRAGMA foreign_keys = ON");
897
- this.db.run("PRAGMA temp_store = memory");
898
- this.db.run(`PRAGMA mmap_size = ${SQLITE_MMAP_SIZE_BYTES}`);
899
- this.db.run(`PRAGMA cache_size = ${SQLITE_CACHE_SIZE_PAGES}`);
991
+ this._db = new Database(dbPath, { create: true, readwrite: true });
992
+ this._db.run("PRAGMA journal_mode = WAL");
993
+ this._db.run("PRAGMA busy_timeout = 5000");
994
+ this._db.run("PRAGMA synchronous = NORMAL");
995
+ this._db.run("PRAGMA foreign_keys = ON");
996
+ this._db.run("PRAGMA temp_store = memory");
997
+ this._db.run(`PRAGMA mmap_size = ${SQLITE_MMAP_SIZE_BYTES}`);
998
+ this._db.run(`PRAGMA cache_size = ${SQLITE_CACHE_SIZE_PAGES}`);
900
999
  if (!skipMigrations) {
901
- const migrationRunner = new MigrationRunner(this.db);
1000
+ const migrationRunner = new MigrationRunner(this._db);
902
1001
  migrationRunner.runAllMigrations();
903
1002
  }
904
1003
  }
905
1004
  /**
906
- * Esegue una funzione all'interno di una transazione atomica.
907
- * Se fn() lancia un errore, la transazione viene annullata automaticamente.
1005
+ * Prepare a query (delegates to underlying Database).
1006
+ * Proxy method to avoid ctx.db.db.query() double access.
1007
+ */
1008
+ query(sql) {
1009
+ return this._db.query(sql);
1010
+ }
1011
+ /**
1012
+ * Execute a SQL statement without results (delegates to underlying Database).
1013
+ * Proxy method to avoid ctx.db.db.run() double access.
1014
+ */
1015
+ run(sql, params) {
1016
+ return this._db.run(sql, params);
1017
+ }
1018
+ /**
1019
+ * Executes a function within an atomic transaction.
1020
+ * If fn() throws an error, the transaction is automatically rolled back.
908
1021
  */
909
1022
  withTransaction(fn) {
910
- const transaction = this.db.transaction(fn);
911
- return transaction(this.db);
1023
+ const transaction = this._db.transaction(fn);
1024
+ return transaction(this._db);
912
1025
  }
913
1026
  /**
914
1027
  * Close the database connection
915
1028
  */
916
1029
  close() {
917
- this.db.close();
1030
+ this._db.close();
918
1031
  }
919
1032
  };
920
1033
  var MigrationRunner = class {
@@ -1408,17 +1521,21 @@ init_EmbeddingService();
1408
1521
  // src/services/search/VectorSearch.ts
1409
1522
  init_EmbeddingService();
1410
1523
  init_logger();
1524
+ var DEFAULT_MAX_CANDIDATES = 2e3;
1411
1525
  function cosineSimilarity(a, b) {
1412
- if (a.length !== b.length) return 0;
1526
+ const len = a.length;
1527
+ if (len !== b.length) return 0;
1413
1528
  let dotProduct = 0;
1414
1529
  let normA = 0;
1415
1530
  let normB = 0;
1416
- for (let i = 0; i < a.length; i++) {
1417
- dotProduct += a[i] * b[i];
1418
- normA += a[i] * a[i];
1419
- normB += b[i] * b[i];
1420
- }
1421
- const denominator = Math.sqrt(normA) * Math.sqrt(normB);
1531
+ for (let i = 0; i < len; i++) {
1532
+ const ai = a[i];
1533
+ const bi = b[i];
1534
+ dotProduct += ai * bi;
1535
+ normA += ai * ai;
1536
+ normB += bi * bi;
1537
+ }
1538
+ const denominator = Math.sqrt(normA * normB);
1422
1539
  if (denominator === 0) return 0;
1423
1540
  return dotProduct / denominator;
1424
1541
  }
@@ -1431,23 +1548,36 @@ function bufferToFloat32(buf) {
1431
1548
  }
1432
1549
  var VectorSearch = class {
1433
1550
  /**
1434
- * Ricerca semantica: calcola cosine similarity tra query e tutti gli embeddings.
1551
+ * Semantic search with SQL pre-filtering for scalability.
1552
+ *
1553
+ * 2-phase strategy:
1554
+ * 1. SQL pre-filters by project + sorts by recency (loads max N candidates)
1555
+ * 2. JS computes cosine similarity only on filtered candidates
1556
+ *
1557
+ * With 50k observations and maxCandidates=2000, loads only ~4% of data.
1435
1558
  */
1436
1559
  async search(db, queryEmbedding, options = {}) {
1437
1560
  const limit = options.limit || 10;
1438
1561
  const threshold = options.threshold || 0.3;
1562
+ const maxCandidates = options.maxCandidates || DEFAULT_MAX_CANDIDATES;
1439
1563
  try {
1440
- let sql = `
1564
+ const conditions = [];
1565
+ const params = [];
1566
+ if (options.project) {
1567
+ conditions.push("o.project = ?");
1568
+ params.push(options.project);
1569
+ }
1570
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
1571
+ const sql = `
1441
1572
  SELECT e.observation_id, e.embedding,
1442
1573
  o.title, o.text, o.type, o.project, o.created_at, o.created_at_epoch
1443
1574
  FROM observation_embeddings e
1444
1575
  JOIN observations o ON o.id = e.observation_id
1576
+ ${whereClause}
1577
+ ORDER BY o.created_at_epoch DESC
1578
+ LIMIT ?
1445
1579
  `;
1446
- const params = [];
1447
- if (options.project) {
1448
- sql += " WHERE o.project = ?";
1449
- params.push(options.project);
1450
- }
1580
+ params.push(maxCandidates);
1451
1581
  const rows = db.query(sql).all(...params);
1452
1582
  const scored = [];
1453
1583
  for (const row of rows) {
@@ -1468,14 +1598,15 @@ var VectorSearch = class {
1468
1598
  }
1469
1599
  }
1470
1600
  scored.sort((a, b) => b.similarity - a.similarity);
1601
+ logger.debug("VECTOR", `Search: ${rows.length} candidates \u2192 ${scored.length} above threshold \u2192 ${Math.min(scored.length, limit)} results`);
1471
1602
  return scored.slice(0, limit);
1472
1603
  } catch (error) {
1473
- logger.error("VECTOR", `Errore ricerca vettoriale: ${error}`);
1604
+ logger.error("VECTOR", `Vector search error: ${error}`);
1474
1605
  return [];
1475
1606
  }
1476
1607
  }
1477
1608
  /**
1478
- * Salva embedding per un'osservazione.
1609
+ * Store embedding for an observation.
1479
1610
  */
1480
1611
  async storeEmbedding(db, observationId, embedding, model) {
1481
1612
  try {
@@ -1491,18 +1622,18 @@ var VectorSearch = class {
1491
1622
  embedding.length,
1492
1623
  (/* @__PURE__ */ new Date()).toISOString()
1493
1624
  );
1494
- logger.debug("VECTOR", `Embedding salvato per osservazione ${observationId}`);
1625
+ logger.debug("VECTOR", `Embedding saved for observation ${observationId}`);
1495
1626
  } catch (error) {
1496
- logger.error("VECTOR", `Errore salvataggio embedding: ${error}`);
1627
+ logger.error("VECTOR", `Error saving embedding: ${error}`);
1497
1628
  }
1498
1629
  }
1499
1630
  /**
1500
- * Genera embeddings per osservazioni che non li hanno ancora.
1631
+ * Generate embeddings for observations that don't have them yet.
1501
1632
  */
1502
1633
  async backfillEmbeddings(db, batchSize = 50) {
1503
1634
  const embeddingService2 = getEmbeddingService();
1504
1635
  if (!await embeddingService2.initialize()) {
1505
- logger.warn("VECTOR", "Embedding service non disponibile, backfill saltato");
1636
+ logger.warn("VECTOR", "Embedding service not available, backfill skipped");
1506
1637
  return 0;
1507
1638
  }
1508
1639
  const rows = db.query(`
@@ -1528,11 +1659,11 @@ var VectorSearch = class {
1528
1659
  count++;
1529
1660
  }
1530
1661
  }
1531
- logger.info("VECTOR", `Backfill completato: ${count}/${rows.length} embeddings generati`);
1662
+ logger.info("VECTOR", `Backfill completed: ${count}/${rows.length} embeddings generated`);
1532
1663
  return count;
1533
1664
  }
1534
1665
  /**
1535
- * Statistiche sugli embeddings.
1666
+ * Embedding statistics.
1536
1667
  */
1537
1668
  getStats(db) {
1538
1669
  try {
@@ -1606,21 +1737,21 @@ init_logger();
1606
1737
  var HybridSearch = class {
1607
1738
  embeddingInitialized = false;
1608
1739
  /**
1609
- * Inizializza il servizio di embedding (lazy, non bloccante)
1740
+ * Initialize the embedding service (lazy, non-blocking)
1610
1741
  */
1611
1742
  async initialize() {
1612
1743
  try {
1613
1744
  const embeddingService2 = getEmbeddingService();
1614
1745
  await embeddingService2.initialize();
1615
1746
  this.embeddingInitialized = embeddingService2.isAvailable();
1616
- logger.info("SEARCH", `HybridSearch inizializzato (embedding: ${this.embeddingInitialized ? "attivo" : "disattivato"})`);
1747
+ logger.info("SEARCH", `HybridSearch initialized (embedding: ${this.embeddingInitialized ? "active" : "disabled"})`);
1617
1748
  } catch (error) {
1618
- logger.warn("SEARCH", "Inizializzazione embedding fallita, uso solo FTS5", {}, error);
1749
+ logger.warn("SEARCH", "Embedding initialization failed, using only FTS5", {}, error);
1619
1750
  this.embeddingInitialized = false;
1620
1751
  }
1621
1752
  }
1622
1753
  /**
1623
- * Ricerca ibrida con scoring a 4 segnali
1754
+ * Hybrid search with 4-signal scoring
1624
1755
  */
1625
1756
  async search(db, query, options = {}) {
1626
1757
  const limit = options.limit || 10;
@@ -1636,7 +1767,7 @@ var HybridSearch = class {
1636
1767
  const vectorResults = await vectorSearch2.search(db, queryEmbedding, {
1637
1768
  project: options.project,
1638
1769
  limit: limit * 2,
1639
- // Prendiamo piu risultati per il ranking
1770
+ // Fetch more results for ranking
1640
1771
  threshold: 0.3
1641
1772
  });
1642
1773
  for (const hit of vectorResults) {
@@ -1653,10 +1784,10 @@ var HybridSearch = class {
1653
1784
  source: "vector"
1654
1785
  });
1655
1786
  }
1656
- logger.debug("SEARCH", `Vector search: ${vectorResults.length} risultati`);
1787
+ logger.debug("SEARCH", `Vector search: ${vectorResults.length} results`);
1657
1788
  }
1658
1789
  } catch (error) {
1659
- logger.warn("SEARCH", "Ricerca vettoriale fallita, uso solo keyword", {}, error);
1790
+ logger.warn("SEARCH", "Vector search failed, using only keyword", {}, error);
1660
1791
  }
1661
1792
  }
1662
1793
  try {
@@ -1686,9 +1817,9 @@ var HybridSearch = class {
1686
1817
  });
1687
1818
  }
1688
1819
  }
1689
- logger.debug("SEARCH", `Keyword search: ${keywordResults.length} risultati`);
1820
+ logger.debug("SEARCH", `Keyword search: ${keywordResults.length} results`);
1690
1821
  } catch (error) {
1691
- logger.error("SEARCH", "Ricerca keyword fallita", {}, error);
1822
+ logger.error("SEARCH", "Keyword search failed", {}, error);
1692
1823
  }
1693
1824
  if (rawItems.size === 0) return [];
1694
1825
  const allFTS5Ranks = Array.from(rawItems.values()).filter((item) => item.fts5Rank !== null).map((item) => item.fts5Rank);
@@ -1780,33 +1911,33 @@ var KiroMemorySDK = class {
1780
1911
  };
1781
1912
  }
1782
1913
  /**
1783
- * Valida input per storeObservation
1914
+ * Validate input for storeObservation
1784
1915
  */
1785
1916
  validateObservationInput(data) {
1786
1917
  if (!data.type || typeof data.type !== "string" || data.type.length > 100) {
1787
- throw new Error("type \xE8 obbligatorio (stringa, max 100 caratteri)");
1918
+ throw new Error("type is required (string, max 100 chars)");
1788
1919
  }
1789
1920
  if (!data.title || typeof data.title !== "string" || data.title.length > 500) {
1790
- throw new Error("title \xE8 obbligatorio (stringa, max 500 caratteri)");
1921
+ throw new Error("title is required (string, max 500 chars)");
1791
1922
  }
1792
1923
  if (!data.content || typeof data.content !== "string" || data.content.length > 1e5) {
1793
- throw new Error("content \xE8 obbligatorio (stringa, max 100KB)");
1924
+ throw new Error("content is required (string, max 100KB)");
1794
1925
  }
1795
1926
  }
1796
1927
  /**
1797
- * Valida input per storeSummary
1928
+ * Validate input for storeSummary
1798
1929
  */
1799
1930
  validateSummaryInput(data) {
1800
1931
  const MAX = 5e4;
1801
1932
  for (const [key, val] of Object.entries(data)) {
1802
1933
  if (val !== void 0 && val !== null) {
1803
- if (typeof val !== "string") throw new Error(`${key} deve essere una stringa`);
1804
- if (val.length > MAX) throw new Error(`${key} troppo grande (max 50KB)`);
1934
+ if (typeof val !== "string") throw new Error(`${key} must be a string`);
1935
+ if (val.length > MAX) throw new Error(`${key} too large (max 50KB)`);
1805
1936
  }
1806
1937
  }
1807
1938
  }
1808
1939
  /**
1809
- * Genera e salva embedding per un'osservazione (fire-and-forget, non blocca)
1940
+ * Generate and store embedding for an observation (fire-and-forget, non-blocking)
1810
1941
  */
1811
1942
  async generateEmbeddingAsync(observationId, title, content, concepts) {
1812
1943
  try {
@@ -1826,39 +1957,39 @@ var KiroMemorySDK = class {
1826
1957
  );
1827
1958
  }
1828
1959
  } catch (error) {
1829
- logger.debug("SDK", `Embedding generation fallita per obs ${observationId}: ${error}`);
1960
+ logger.debug("SDK", `Embedding generation failed for obs ${observationId}: ${error}`);
1830
1961
  }
1831
1962
  }
1832
1963
  /**
1833
- * Genera content hash SHA256 per deduplicazione basata su contenuto.
1834
- * Usa (project + type + title + narrative) come tupla di identità semantica.
1835
- * NON include sessionId perché è unico ad ogni invocazione.
1964
+ * Generate SHA256 content hash for content-based deduplication.
1965
+ * Uses (project + type + title + narrative) as semantic identity tuple.
1966
+ * Does NOT include sessionId since it's unique per invocation.
1836
1967
  */
1837
1968
  generateContentHash(type, title, narrative) {
1838
1969
  const payload = `${this.project}|${type}|${title}|${narrative || ""}`;
1839
1970
  return createHash("sha256").update(payload).digest("hex");
1840
1971
  }
1841
1972
  /**
1842
- * Finestre di deduplicazione per tipo (ms).
1843
- * Tipi con molte ripetizioni hanno finestre più ampie.
1973
+ * Deduplication windows per type (ms).
1974
+ * Types with many repetitions have wider windows.
1844
1975
  */
1845
1976
  getDeduplicationWindow(type) {
1846
1977
  switch (type) {
1847
1978
  case "file-read":
1848
1979
  return 6e4;
1849
- // 60s — letture frequenti sugli stessi file
1980
+ // 60s — frequent reads on the same files
1850
1981
  case "file-write":
1851
1982
  return 1e4;
1852
- // 10s — scritture rapide consecutive
1983
+ // 10s — rapid consecutive writes
1853
1984
  case "command":
1854
1985
  return 3e4;
1855
1986
  // 30s — standard
1856
1987
  case "research":
1857
1988
  return 12e4;
1858
- // 120s — web search e fetch ripetuti
1989
+ // 120s — repeated web search and fetch
1859
1990
  case "delegation":
1860
1991
  return 6e4;
1861
- // 60s — delegazioni rapide
1992
+ // 60s — rapid delegations
1862
1993
  default:
1863
1994
  return 3e4;
1864
1995
  }
@@ -1872,7 +2003,7 @@ var KiroMemorySDK = class {
1872
2003
  const contentHash = this.generateContentHash(data.type, data.title, data.narrative);
1873
2004
  const dedupWindow = this.getDeduplicationWindow(data.type);
1874
2005
  if (isDuplicateObservation(this.db.db, contentHash, dedupWindow)) {
1875
- logger.debug("SDK", `Osservazione duplicata scartata (${data.type}, ${dedupWindow}ms): ${data.title}`);
2006
+ logger.debug("SDK", `Duplicate observation discarded (${data.type}, ${dedupWindow}ms): ${data.title}`);
1876
2007
  return -1;
1877
2008
  }
1878
2009
  const filesRead = data.filesRead || (data.type === "file-read" ? data.files : void 0);
@@ -1900,12 +2031,12 @@ var KiroMemorySDK = class {
1900
2031
  return observationId;
1901
2032
  }
1902
2033
  /**
1903
- * Salva conoscenza strutturata (constraint, decision, heuristic, rejected).
1904
- * Usa il campo `type` per il knowledgeType e `facts` per i metadati JSON.
2034
+ * Store structured knowledge (constraint, decision, heuristic, rejected).
2035
+ * Uses the `type` field for knowledgeType and `facts` for JSON metadata.
1905
2036
  */
1906
2037
  async storeKnowledge(data) {
1907
2038
  if (!KNOWLEDGE_TYPES.includes(data.knowledgeType)) {
1908
- throw new Error(`knowledgeType non valido: ${data.knowledgeType}. Valori ammessi: ${KNOWLEDGE_TYPES.join(", ")}`);
2039
+ throw new Error(`Invalid knowledgeType: ${data.knowledgeType}. Allowed values: ${KNOWLEDGE_TYPES.join(", ")}`);
1909
2040
  }
1910
2041
  this.validateObservationInput({ type: data.knowledgeType, title: data.title, content: data.content });
1911
2042
  const metadata = (() => {
@@ -1937,9 +2068,9 @@ var KiroMemorySDK = class {
1937
2068
  }
1938
2069
  })();
1939
2070
  const sessionId = "sdk-" + Date.now();
1940
- const contentHash = this.generateContentHash(data.type, data.title);
2071
+ const contentHash = this.generateContentHash(data.knowledgeType, data.title);
1941
2072
  if (isDuplicateObservation(this.db.db, contentHash)) {
1942
- logger.debug("SDK", `Knowledge duplicata scartata: ${data.title}`);
2073
+ logger.debug("SDK", `Duplicate knowledge discarded: ${data.title}`);
1943
2074
  return -1;
1944
2075
  }
1945
2076
  const discoveryTokens = Math.ceil(data.content.length / 4);
@@ -1956,11 +2087,11 @@ var KiroMemorySDK = class {
1956
2087
  null,
1957
2088
  // narrative
1958
2089
  JSON.stringify(metadata),
1959
- // facts = metadati JSON
2090
+ // facts = JSON metadata
1960
2091
  data.concepts?.join(", ") || null,
1961
2092
  data.files?.join(", ") || null,
1962
2093
  null,
1963
- // filesModified: knowledge non modifica file
2094
+ // filesModified: knowledge doesn't modify files
1964
2095
  0,
1965
2096
  // prompt_number
1966
2097
  contentHash,
@@ -2071,8 +2202,8 @@ var KiroMemorySDK = class {
2071
2202
  return this.project;
2072
2203
  }
2073
2204
  /**
2074
- * Ricerca ibrida: vector search + keyword FTS5
2075
- * Richiede inizializzazione HybridSearch (embedding service)
2205
+ * Hybrid search: vector search + keyword FTS5
2206
+ * Requires HybridSearch initialization (embedding service)
2076
2207
  */
2077
2208
  async hybridSearch(query, options = {}) {
2078
2209
  const hybridSearch2 = getHybridSearch();
@@ -2082,8 +2213,8 @@ var KiroMemorySDK = class {
2082
2213
  });
2083
2214
  }
2084
2215
  /**
2085
- * Ricerca solo semantica (vector search)
2086
- * Ritorna risultati basati su similarità coseno con gli embeddings
2216
+ * Semantic-only search (vector search)
2217
+ * Returns results based on cosine similarity with embeddings
2087
2218
  */
2088
2219
  async semanticSearch(query, options = {}) {
2089
2220
  const embeddingService2 = getEmbeddingService();
@@ -2118,21 +2249,21 @@ var KiroMemorySDK = class {
2118
2249
  }));
2119
2250
  }
2120
2251
  /**
2121
- * Genera embeddings per osservazioni che non li hanno ancora
2252
+ * Generate embeddings for observations that don't have them yet
2122
2253
  */
2123
2254
  async backfillEmbeddings(batchSize = 50) {
2124
2255
  const vectorSearch2 = getVectorSearch();
2125
2256
  return vectorSearch2.backfillEmbeddings(this.db.db, batchSize);
2126
2257
  }
2127
2258
  /**
2128
- * Statistiche sugli embeddings nel database
2259
+ * Embedding statistics in the database
2129
2260
  */
2130
2261
  getEmbeddingStats() {
2131
2262
  const vectorSearch2 = getVectorSearch();
2132
2263
  return vectorSearch2.getStats(this.db.db);
2133
2264
  }
2134
2265
  /**
2135
- * Inizializza il servizio di embedding (lazy, chiamare prima di hybridSearch)
2266
+ * Initialize the embedding service (lazy, call before hybridSearch)
2136
2267
  */
2137
2268
  async initializeEmbeddings() {
2138
2269
  const hybridSearch2 = getHybridSearch();
@@ -2140,10 +2271,10 @@ var KiroMemorySDK = class {
2140
2271
  return getEmbeddingService().isAvailable();
2141
2272
  }
2142
2273
  /**
2143
- * Contesto smart con ranking a 4 segnali e budget token.
2274
+ * Smart context with 4-signal ranking and token budget.
2144
2275
  *
2145
- * Se query presente: usa HybridSearch con SEARCH_WEIGHTS.
2146
- * Se senza query: ranking per recency + project match (CONTEXT_WEIGHTS).
2276
+ * If query present: uses HybridSearch with SEARCH_WEIGHTS.
2277
+ * If no query: ranking by recency + project match (CONTEXT_WEIGHTS).
2147
2278
  */
2148
2279
  async getSmartContext(options = {}) {
2149
2280
  const tokenBudget = options.tokenBudget || parseInt(process.env.KIRO_MEMORY_CONTEXT_TOKENS || "0", 10) || 2e3;
@@ -2217,8 +2348,8 @@ var KiroMemorySDK = class {
2217
2348
  };
2218
2349
  }
2219
2350
  /**
2220
- * Rileva osservazioni stale (file modificati dopo la creazione) e le marca nel DB.
2221
- * Ritorna il numero di osservazioni marcate come stale.
2351
+ * Detect stale observations (files modified after creation) and mark them in DB.
2352
+ * Returns the number of observations marked as stale.
2222
2353
  */
2223
2354
  async detectStaleObservations() {
2224
2355
  const staleObs = getStaleObservations(this.db.db, this.project);
@@ -2229,14 +2360,14 @@ var KiroMemorySDK = class {
2229
2360
  return staleObs.length;
2230
2361
  }
2231
2362
  /**
2232
- * Consolida osservazioni duplicate sullo stesso file e tipo.
2233
- * Raggruppa per (project, type, files_modified), mantiene la piu recente.
2363
+ * Consolidate duplicate observations on the same file and type.
2364
+ * Groups by (project, type, files_modified), keeps the most recent.
2234
2365
  */
2235
2366
  async consolidateObservations(options = {}) {
2236
2367
  return consolidateObservations(this.db.db, this.project, options);
2237
2368
  }
2238
2369
  /**
2239
- * Statistiche decay: totale, stale, mai accedute, accedute di recente.
2370
+ * Decay statistics: total, stale, never accessed, recently accessed.
2240
2371
  */
2241
2372
  async getDecayStats() {
2242
2373
  const total = this.db.db.query(
@@ -2255,8 +2386,8 @@ var KiroMemorySDK = class {
2255
2386
  return { total, stale, neverAccessed, recentlyAccessed };
2256
2387
  }
2257
2388
  /**
2258
- * Crea un checkpoint strutturato per resume sessione.
2259
- * Salva automaticamente un context_snapshot con le ultime 10 osservazioni.
2389
+ * Create a structured checkpoint for session resume.
2390
+ * Automatically saves a context_snapshot with the last 10 observations.
2260
2391
  */
2261
2392
  async createCheckpoint(sessionId, data) {
2262
2393
  const recentObs = getObservationsByProject(this.db.db, this.project, 10);
@@ -2273,21 +2404,21 @@ var KiroMemorySDK = class {
2273
2404
  });
2274
2405
  }
2275
2406
  /**
2276
- * Recupera l'ultimo checkpoint di una sessione specifica.
2407
+ * Retrieve the latest checkpoint of a specific session.
2277
2408
  */
2278
2409
  async getCheckpoint(sessionId) {
2279
2410
  return getLatestCheckpoint(this.db.db, sessionId);
2280
2411
  }
2281
2412
  /**
2282
- * Recupera l'ultimo checkpoint per il progetto corrente.
2283
- * Utile per resume automatico senza specificare session ID.
2413
+ * Retrieve the latest checkpoint for the current project.
2414
+ * Useful for automatic resume without specifying session ID.
2284
2415
  */
2285
2416
  async getLatestProjectCheckpoint() {
2286
2417
  return getLatestCheckpointByProject(this.db.db, this.project);
2287
2418
  }
2288
2419
  /**
2289
- * Genera un report di attività per il progetto corrente.
2290
- * Aggrega osservazioni, sessioni, summaries e file per un periodo temporale.
2420
+ * Generate an activity report for the current project.
2421
+ * Aggregates observations, sessions, summaries and files for a time period.
2291
2422
  */
2292
2423
  async generateReport(options) {
2293
2424
  const now = /* @__PURE__ */ new Date();
@@ -2483,6 +2614,71 @@ function formatReportJson(data) {
2483
2614
  return JSON.stringify(data, null, 2);
2484
2615
  }
2485
2616
 
2617
+ // src/cli/banner.ts
2618
+ var G = [
2619
+ "\x1B[38;5;135m",
2620
+ // viola
2621
+ "\x1B[38;5;99m",
2622
+ // viola-blu
2623
+ "\x1B[38;5;63m",
2624
+ // indaco
2625
+ "\x1B[38;5;33m",
2626
+ // blu
2627
+ "\x1B[38;5;39m",
2628
+ // blu chiaro
2629
+ "\x1B[38;5;44m"
2630
+ // ciano
2631
+ ];
2632
+ var R = "\x1B[0m";
2633
+ var B = "\x1B[1m";
2634
+ var D = "\x1B[2m";
2635
+ var U = "\x1B[4m";
2636
+ var GRN = "\x1B[32m";
2637
+ var CYN = "\x1B[36m";
2638
+ var LOGO = [
2639
+ " \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 ",
2640
+ " \u2588\u2588\u2551 \u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557",
2641
+ " \u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551",
2642
+ " \u2588\u2588\u2554\u2550\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551",
2643
+ " \u2588\u2588\u2551 \u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D",
2644
+ " \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D"
2645
+ ];
2646
+ var MEMORY_TAG = " M E M O R Y";
2647
+ var LINE = "\u2500".repeat(48);
2648
+ function supportsColor() {
2649
+ if (process.env.NO_COLOR || process.env.TERM === "dumb") return false;
2650
+ return process.stdout.isTTY ?? false;
2651
+ }
2652
+ function printBanner(opts) {
2653
+ const color = supportsColor();
2654
+ const c = (code, text) => color ? `${code}${text}${R}` : text;
2655
+ console.log("");
2656
+ for (let i = 0; i < LOGO.length; i++) {
2657
+ console.log(` ${c(G[i], LOGO[i])}`);
2658
+ }
2659
+ console.log(` ${c(`${G[G.length - 1]}${B}`, MEMORY_TAG)}`);
2660
+ console.log("");
2661
+ console.log(` ${c(D, LINE)}`);
2662
+ console.log("");
2663
+ console.log(` ${c(`${GRN}${B}`, "\u2713 Installation complete!")} v${opts.version}`);
2664
+ console.log(` ${c(D, `Editor: ${opts.editor}`)}`);
2665
+ console.log("");
2666
+ console.log(` ${c(`${CYN}${B}`, "Installed:")}`);
2667
+ for (const p of opts.configPaths) {
2668
+ console.log(` ${c(D, "\u2192")} ${p}`);
2669
+ }
2670
+ console.log(` ${c(D, "\u2192")} Data: ${opts.dataDir}`);
2671
+ console.log("");
2672
+ console.log(` ${c(`${CYN}${B}`, "Dashboard:")} ${c(U, opts.dashboardUrl)}`);
2673
+ console.log(` ${c(D, "Docs: https://auritidesign.it/docs/kiro-memory/")}`);
2674
+ console.log("");
2675
+ console.log(` ${c(D, LINE)}`);
2676
+ console.log(` ${c(G[2], "Your AI assistant now has persistent memory.")}`);
2677
+ console.log(` ${c(G[3], "Every session builds on the last.")}`);
2678
+ console.log(` ${c(D, LINE)}`);
2679
+ console.log("");
2680
+ }
2681
+
2486
2682
  // src/cli/contextkit.ts
2487
2683
  import { execSync } from "child_process";
2488
2684
  import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync, appendFileSync as appendFileSync2 } from "fs";
@@ -2495,6 +2691,12 @@ var command = args[0];
2495
2691
  var __filename = fileURLToPath2(import.meta.url);
2496
2692
  var __dirname2 = dirname2(__filename);
2497
2693
  var DIST_DIR = dirname2(__dirname2);
2694
+ var PKG_VERSION = "unknown";
2695
+ try {
2696
+ const pkgPath = join3(DIST_DIR, "..", "..", "package.json");
2697
+ PKG_VERSION = JSON.parse(readFileSync2(pkgPath, "utf8")).version;
2698
+ } catch {
2699
+ }
2498
2700
  var AGENT_TEMPLATE = JSON.stringify({
2499
2701
  name: "kiro-memory",
2500
2702
  description: "Agent with persistent cross-session memory. Uses Kiro Memory to remember context from previous sessions and automatically save what it learns.",
@@ -2917,17 +3119,23 @@ ${aliasLine}
2917
3119
  }
2918
3120
  }
2919
3121
  console.log("\n[4/4] Done!\n");
2920
- console.log(" \x1B[32m\u2550\u2550\u2550 Installation complete! \u2550\u2550\u2550\x1B[0m\n");
3122
+ printBanner({
3123
+ editor: "Kiro CLI",
3124
+ version: PKG_VERSION,
3125
+ dashboardUrl: "http://localhost:3001",
3126
+ dataDir,
3127
+ configPaths: [
3128
+ `Agent: ${agentDestPath}`,
3129
+ `MCP: ${mcpFilePath}`,
3130
+ `Steering: ${steeringDestPath}`
3131
+ ]
3132
+ });
2921
3133
  console.log(" Start Kiro with memory:");
2922
3134
  if (aliasAlreadySet) {
2923
- console.log(" \x1B[1mkiro\x1B[0m");
3135
+ console.log(" \x1B[1mkiro\x1B[0m\n");
2924
3136
  } else {
2925
- console.log(" \x1B[1mkiro-cli --agent kiro-memory\x1B[0m");
3137
+ console.log(" \x1B[1mkiro-cli --agent kiro-memory\x1B[0m\n");
2926
3138
  }
2927
- console.log("");
2928
- console.log(" The worker starts automatically when a Kiro session begins.");
2929
- console.log(` Web dashboard: \x1B[4mhttp://localhost:3001\x1B[0m
2930
- `);
2931
3139
  }
2932
3140
  var CLAUDE_CODE_STEERING = `# Kiro Memory - Persistent Cross-Session Memory
2933
3141
 
@@ -3052,12 +3260,17 @@ async function installClaudeCode() {
3052
3260
  }
3053
3261
  console.log(` \u2192 Data dir: ${dataDir}`);
3054
3262
  console.log("\n[3/3] Done!\n");
3055
- console.log(" \x1B[32m\u2550\u2550\u2550 Claude Code integration complete! \u2550\u2550\u2550\x1B[0m\n");
3056
- console.log(" Memory hooks are now active for Claude Code.");
3057
- console.log(" Start a new Claude Code session to begin tracking context.\n");
3058
- console.log(" The worker starts automatically on first session.");
3059
- console.log(` Web dashboard: \x1B[4mhttp://localhost:3001\x1B[0m
3060
- `);
3263
+ printBanner({
3264
+ editor: "Claude Code",
3265
+ version: PKG_VERSION,
3266
+ dashboardUrl: "http://localhost:3001",
3267
+ dataDir,
3268
+ configPaths: [
3269
+ `Hooks: ${settingsPath}`,
3270
+ `MCP: ${mcpPath}`,
3271
+ `Steering: ${steeringPath}`
3272
+ ]
3273
+ });
3061
3274
  }
3062
3275
  async function installCursor() {
3063
3276
  console.log("\n=== Kiro Memory - Cursor Installation ===\n");
@@ -3139,12 +3352,16 @@ async function installCursor() {
3139
3352
  console.log(` \u2192 MCP config: ${mcpPath}`);
3140
3353
  console.log(` \u2192 Data dir: ${dataDir}`);
3141
3354
  console.log("\n[3/3] Done!\n");
3142
- console.log(" \x1B[32m\u2550\u2550\u2550 Cursor integration complete! \u2550\u2550\u2550\x1B[0m\n");
3143
- console.log(" Memory hooks are now active for Cursor IDE.");
3144
- console.log(" Start a new Cursor Agent session to begin tracking context.\n");
3145
- console.log(" The worker starts automatically on first session.");
3146
- console.log(` Web dashboard: \x1B[4mhttp://localhost:3001\x1B[0m
3147
- `);
3355
+ printBanner({
3356
+ editor: "Cursor",
3357
+ version: PKG_VERSION,
3358
+ dashboardUrl: "http://localhost:3001",
3359
+ dataDir,
3360
+ configPaths: [
3361
+ `Hooks: ${hooksPath}`,
3362
+ `MCP: ${mcpPath}`
3363
+ ]
3364
+ });
3148
3365
  }
3149
3366
  async function installWindsurf() {
3150
3367
  console.log("\n=== Kiro Memory - Windsurf Installation ===\n");
@@ -3193,12 +3410,15 @@ async function installWindsurf() {
3193
3410
  console.log(` \u2192 MCP config: ${mcpPath}`);
3194
3411
  console.log(` \u2192 Data dir: ${dataDir}`);
3195
3412
  console.log("\n[3/3] Done!\n");
3196
- console.log(" \x1B[32m\u2550\u2550\u2550 Windsurf integration complete! \u2550\u2550\u2550\x1B[0m\n");
3197
- console.log(" Kiro Memory MCP server is now registered for Windsurf.");
3198
- console.log(" Restart Windsurf to activate the MCP server.\n");
3199
- console.log(" The worker starts automatically on first use.");
3200
- console.log(` Web dashboard: \x1B[4mhttp://localhost:3001\x1B[0m
3201
- `);
3413
+ printBanner({
3414
+ editor: "Windsurf",
3415
+ version: PKG_VERSION,
3416
+ dashboardUrl: "http://localhost:3001",
3417
+ dataDir,
3418
+ configPaths: [
3419
+ `MCP: ${mcpPath}`
3420
+ ]
3421
+ });
3202
3422
  console.log(" \x1B[2mTip: Add a .windsurfrules file to your project with instructions");
3203
3423
  console.log(" to use the kiro-memory MCP tools for persistent context.\x1B[0m\n");
3204
3424
  }
@@ -3255,12 +3475,15 @@ async function installCline() {
3255
3475
  console.log(` \u2192 MCP config: ${mcpPath}`);
3256
3476
  console.log(` \u2192 Data dir: ${dataDir}`);
3257
3477
  console.log("\n[3/3] Done!\n");
3258
- console.log(" \x1B[32m\u2550\u2550\u2550 Cline integration complete! \u2550\u2550\u2550\x1B[0m\n");
3259
- console.log(" Kiro Memory MCP server is now registered for Cline.");
3260
- console.log(" Restart VS Code to activate the MCP server in Cline.\n");
3261
- console.log(" The worker starts automatically on first use.");
3262
- console.log(` Web dashboard: \x1B[4mhttp://localhost:3001\x1B[0m
3263
- `);
3478
+ printBanner({
3479
+ editor: "Cline",
3480
+ version: PKG_VERSION,
3481
+ dashboardUrl: "http://localhost:3001",
3482
+ dataDir,
3483
+ configPaths: [
3484
+ `MCP: ${mcpPath}`
3485
+ ]
3486
+ });
3264
3487
  console.log(" \x1B[2mTip: Add a .clinerules file to your project with instructions");
3265
3488
  console.log(" to use the kiro-memory MCP tools for persistent context.\x1B[0m\n");
3266
3489
  }
@@ -3322,13 +3545,13 @@ async function runDoctor() {
3322
3545
  checks.push({
3323
3546
  name: "Claude Code hooks",
3324
3547
  ok: true,
3325
- // Non-blocking: installazione opzionale
3548
+ // Non-blocking: optional installation
3326
3549
  message: claudeHooksOk ? "Configured in ~/.claude/settings.json" : "Not configured (optional: run kiro-memory install --claude-code)"
3327
3550
  });
3328
3551
  checks.push({
3329
3552
  name: "Claude Code MCP",
3330
3553
  ok: true,
3331
- // Non-blocking: installazione opzionale
3554
+ // Non-blocking: optional installation
3332
3555
  message: claudeMcpOk ? "kiro-memory registered in ~/.mcp.json" : "Not configured (optional: run kiro-memory install --claude-code)"
3333
3556
  });
3334
3557
  const cursorDir = join3(homedir3(), ".cursor");
@@ -3357,13 +3580,13 @@ async function runDoctor() {
3357
3580
  checks.push({
3358
3581
  name: "Cursor hooks",
3359
3582
  ok: true,
3360
- // Non-blocking: installazione opzionale
3583
+ // Non-blocking: optional installation
3361
3584
  message: cursorHooksOk ? "Configured in ~/.cursor/hooks.json" : "Not configured (optional: run kiro-memory install --cursor)"
3362
3585
  });
3363
3586
  checks.push({
3364
3587
  name: "Cursor MCP",
3365
3588
  ok: true,
3366
- // Non-blocking: installazione opzionale
3589
+ // Non-blocking: optional installation
3367
3590
  message: cursorMcpOk ? "kiro-memory registered in ~/.cursor/mcp.json" : "Not configured (optional: run kiro-memory install --cursor)"
3368
3591
  });
3369
3592
  const windsurfMcpPath = join3(homedir3(), ".codeium", "windsurf", "mcp_config.json");
@@ -3378,7 +3601,7 @@ async function runDoctor() {
3378
3601
  checks.push({
3379
3602
  name: "Windsurf MCP",
3380
3603
  ok: true,
3381
- // Non-blocking: installazione opzionale
3604
+ // Non-blocking: optional installation
3382
3605
  message: windsurfMcpOk ? "kiro-memory registered in ~/.codeium/windsurf/mcp_config.json" : "Not configured (optional: run kiro-memory install --windsurf)"
3383
3606
  });
3384
3607
  const clinePlatform = process.platform;
@@ -3400,7 +3623,7 @@ async function runDoctor() {
3400
3623
  checks.push({
3401
3624
  name: "Cline MCP",
3402
3625
  ok: true,
3403
- // Non-blocking: installazione opzionale
3626
+ // Non-blocking: optional installation
3404
3627
  message: clineMcpOk ? `kiro-memory registered in cline_mcp_settings.json` : "Not configured (optional: run kiro-memory install --cline)"
3405
3628
  });
3406
3629
  let workerOk = false;