kiro-memory 1.5.0 → 1.7.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.
@@ -70,7 +70,7 @@ var BunQueryCompat = class {
70
70
  // src/shared/paths.ts
71
71
  import { join as join2, dirname, basename } from "path";
72
72
  import { homedir as homedir2 } from "os";
73
- import { mkdirSync as mkdirSync2 } from "fs";
73
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
74
74
  import { fileURLToPath } from "url";
75
75
 
76
76
  // src/utils/logger.ts
@@ -300,7 +300,9 @@ function getDirname() {
300
300
  return dirname(fileURLToPath(import.meta.url));
301
301
  }
302
302
  var _dirname = getDirname();
303
- var DATA_DIR = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR || join2(homedir2(), ".contextkit");
303
+ var _legacyDir = join2(homedir2(), ".contextkit");
304
+ var _defaultDir = existsSync2(_legacyDir) ? _legacyDir : join2(homedir2(), ".kiro-memory");
305
+ var DATA_DIR = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR || _defaultDir;
304
306
  var KIRO_CONFIG_DIR = process.env.KIRO_CONFIG_DIR || join2(homedir2(), ".kiro");
305
307
  var PLUGIN_ROOT = join2(KIRO_CONFIG_DIR, "plugins", "kiro-memory");
306
308
  var ARCHIVES_DIR = join2(DATA_DIR, "archives");
@@ -309,7 +311,8 @@ var TRASH_DIR = join2(DATA_DIR, "trash");
309
311
  var BACKUPS_DIR = join2(DATA_DIR, "backups");
310
312
  var MODES_DIR = join2(DATA_DIR, "modes");
311
313
  var USER_SETTINGS_PATH = join2(DATA_DIR, "settings.json");
312
- var DB_PATH = join2(DATA_DIR, "contextkit.db");
314
+ var _legacyDb = join2(DATA_DIR, "contextkit.db");
315
+ var DB_PATH = existsSync2(_legacyDb) ? _legacyDb : join2(DATA_DIR, "kiro-memory.db");
313
316
  var VECTOR_DB_DIR = join2(DATA_DIR, "vector-db");
314
317
  var OBSERVER_SESSIONS_DIR = join2(DATA_DIR, "observer-sessions");
315
318
  var KIRO_SETTINGS_PATH = join2(KIRO_CONFIG_DIR, "settings.json");
@@ -324,7 +327,11 @@ var SQLITE_CACHE_SIZE_PAGES = 1e4;
324
327
  var dbInstance = null;
325
328
  var KiroMemoryDatabase = class {
326
329
  db;
327
- constructor(dbPath = DB_PATH) {
330
+ /**
331
+ * @param dbPath - Percorso al file SQLite (default: DB_PATH)
332
+ * @param skipMigrations - Se true, salta il migration runner (per hook ad alta frequenza)
333
+ */
334
+ constructor(dbPath = DB_PATH, skipMigrations = false) {
328
335
  if (dbPath !== ":memory:") {
329
336
  ensureDir(DATA_DIR);
330
337
  }
@@ -335,8 +342,18 @@ var KiroMemoryDatabase = class {
335
342
  this.db.run("PRAGMA temp_store = memory");
336
343
  this.db.run(`PRAGMA mmap_size = ${SQLITE_MMAP_SIZE_BYTES}`);
337
344
  this.db.run(`PRAGMA cache_size = ${SQLITE_CACHE_SIZE_PAGES}`);
338
- const migrationRunner = new MigrationRunner(this.db);
339
- migrationRunner.runAllMigrations();
345
+ if (!skipMigrations) {
346
+ const migrationRunner = new MigrationRunner(this.db);
347
+ migrationRunner.runAllMigrations();
348
+ }
349
+ }
350
+ /**
351
+ * Esegue una funzione all'interno di una transazione atomica.
352
+ * Se fn() lancia un errore, la transazione viene annullata automaticamente.
353
+ */
354
+ withTransaction(fn) {
355
+ const transaction = this.db.transaction(fn);
356
+ return transaction(this.db);
340
357
  }
341
358
  /**
342
359
  * Close the database connection
@@ -619,6 +636,55 @@ var MigrationRunner = class {
619
636
  `);
620
637
  db.run("CREATE UNIQUE INDEX IF NOT EXISTS idx_project_aliases_name ON project_aliases(project_name)");
621
638
  }
639
+ },
640
+ {
641
+ version: 4,
642
+ up: (db) => {
643
+ db.run(`
644
+ CREATE TABLE IF NOT EXISTS observation_embeddings (
645
+ observation_id INTEGER PRIMARY KEY,
646
+ embedding BLOB NOT NULL,
647
+ model TEXT NOT NULL,
648
+ dimensions INTEGER NOT NULL,
649
+ created_at TEXT NOT NULL,
650
+ FOREIGN KEY (observation_id) REFERENCES observations(id) ON DELETE CASCADE
651
+ )
652
+ `);
653
+ db.run("CREATE INDEX IF NOT EXISTS idx_embeddings_model ON observation_embeddings(model)");
654
+ }
655
+ },
656
+ {
657
+ version: 5,
658
+ up: (db) => {
659
+ db.run("ALTER TABLE observations ADD COLUMN last_accessed_epoch INTEGER");
660
+ db.run("ALTER TABLE observations ADD COLUMN is_stale INTEGER DEFAULT 0");
661
+ db.run("CREATE INDEX IF NOT EXISTS idx_observations_last_accessed ON observations(last_accessed_epoch)");
662
+ db.run("CREATE INDEX IF NOT EXISTS idx_observations_stale ON observations(is_stale)");
663
+ }
664
+ },
665
+ {
666
+ version: 6,
667
+ up: (db) => {
668
+ db.run(`
669
+ CREATE TABLE IF NOT EXISTS checkpoints (
670
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
671
+ session_id INTEGER NOT NULL,
672
+ project TEXT NOT NULL,
673
+ task TEXT NOT NULL,
674
+ progress TEXT,
675
+ next_steps TEXT,
676
+ open_questions TEXT,
677
+ relevant_files TEXT,
678
+ context_snapshot TEXT,
679
+ created_at TEXT NOT NULL,
680
+ created_at_epoch INTEGER NOT NULL,
681
+ FOREIGN KEY (session_id) REFERENCES sessions(id)
682
+ )
683
+ `);
684
+ db.run("CREATE INDEX IF NOT EXISTS idx_checkpoints_session ON checkpoints(session_id)");
685
+ db.run("CREATE INDEX IF NOT EXISTS idx_checkpoints_project ON checkpoints(project)");
686
+ db.run("CREATE INDEX IF NOT EXISTS idx_checkpoints_epoch ON checkpoints(created_at_epoch)");
687
+ }
622
688
  }
623
689
  ];
624
690
  }
@@ -686,6 +752,9 @@ function getSessionsByProject(db, project, limit = 100) {
686
752
  }
687
753
 
688
754
  // src/services/sqlite/Observations.ts
755
+ function escapeLikePattern(input) {
756
+ return input.replace(/[%_\\]/g, "\\$&");
757
+ }
689
758
  function createObservation(db, memorySessionId, project, type, title, subtitle, text, narrative, facts, concepts, filesRead, filesModified, promptNumber) {
690
759
  const now = /* @__PURE__ */ new Date();
691
760
  const result = db.run(
@@ -709,12 +778,12 @@ function getObservationsByProject(db, project, limit = 100) {
709
778
  return query.all(project, limit);
710
779
  }
711
780
  function searchObservations(db, searchTerm, project) {
712
- const sql = project ? `SELECT * FROM observations
713
- WHERE project = ? AND (title LIKE ? OR text LIKE ? OR narrative LIKE ?)
714
- ORDER BY created_at_epoch DESC` : `SELECT * FROM observations
715
- WHERE title LIKE ? OR text LIKE ? OR narrative LIKE ?
781
+ const sql = project ? `SELECT * FROM observations
782
+ WHERE project = ? AND (title LIKE ? ESCAPE '\\' OR text LIKE ? ESCAPE '\\' OR narrative LIKE ? ESCAPE '\\')
783
+ ORDER BY created_at_epoch DESC` : `SELECT * FROM observations
784
+ WHERE title LIKE ? ESCAPE '\\' OR text LIKE ? ESCAPE '\\' OR narrative LIKE ? ESCAPE '\\'
716
785
  ORDER BY created_at_epoch DESC`;
717
- const pattern = `%${searchTerm}%`;
786
+ const pattern = `%${escapeLikePattern(searchTerm)}%`;
718
787
  const query = db.query(sql);
719
788
  if (project) {
720
789
  return query.all(project, pattern, pattern, pattern);
@@ -724,8 +793,70 @@ function searchObservations(db, searchTerm, project) {
724
793
  function deleteObservation(db, id) {
725
794
  db.run("DELETE FROM observations WHERE id = ?", [id]);
726
795
  }
796
+ function updateLastAccessed(db, ids) {
797
+ if (!Array.isArray(ids) || ids.length === 0) return;
798
+ const validIds = ids.filter((id) => typeof id === "number" && Number.isInteger(id) && id > 0).slice(0, 500);
799
+ if (validIds.length === 0) return;
800
+ const now = Date.now();
801
+ const placeholders = validIds.map(() => "?").join(",");
802
+ db.run(
803
+ `UPDATE observations SET last_accessed_epoch = ? WHERE id IN (${placeholders})`,
804
+ [now, ...validIds]
805
+ );
806
+ }
807
+ function consolidateObservations(db, project, options = {}) {
808
+ const minGroupSize = options.minGroupSize || 3;
809
+ const groups = db.query(`
810
+ SELECT type, files_modified, COUNT(*) as cnt, GROUP_CONCAT(id) as ids
811
+ FROM observations
812
+ WHERE project = ? AND files_modified IS NOT NULL AND files_modified != ''
813
+ GROUP BY type, files_modified
814
+ HAVING cnt >= ?
815
+ ORDER BY cnt DESC
816
+ `).all(project, minGroupSize);
817
+ if (groups.length === 0) return { merged: 0, removed: 0 };
818
+ let totalMerged = 0;
819
+ let totalRemoved = 0;
820
+ for (const group of groups) {
821
+ const obsIds = group.ids.split(",").map(Number);
822
+ const placeholders = obsIds.map(() => "?").join(",");
823
+ const observations = db.query(
824
+ `SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC`
825
+ ).all(...obsIds);
826
+ if (observations.length < minGroupSize) continue;
827
+ if (options.dryRun) {
828
+ totalMerged += 1;
829
+ totalRemoved += observations.length - 1;
830
+ continue;
831
+ }
832
+ const keeper = observations[0];
833
+ const others = observations.slice(1);
834
+ const uniqueTexts = /* @__PURE__ */ new Set();
835
+ if (keeper.text) uniqueTexts.add(keeper.text);
836
+ for (const obs of others) {
837
+ if (obs.text && !uniqueTexts.has(obs.text)) {
838
+ uniqueTexts.add(obs.text);
839
+ }
840
+ }
841
+ const consolidatedText = Array.from(uniqueTexts).join("\n---\n").substring(0, 1e5);
842
+ db.run(
843
+ "UPDATE observations SET text = ?, title = ? WHERE id = ?",
844
+ [consolidatedText, `[consolidato x${observations.length}] ${keeper.title}`, keeper.id]
845
+ );
846
+ const removeIds = others.map((o) => o.id);
847
+ const removePlaceholders = removeIds.map(() => "?").join(",");
848
+ db.run(`DELETE FROM observations WHERE id IN (${removePlaceholders})`, removeIds);
849
+ db.run(`DELETE FROM observation_embeddings WHERE observation_id IN (${removePlaceholders})`, removeIds);
850
+ totalMerged += 1;
851
+ totalRemoved += removeIds.length;
852
+ }
853
+ return { merged: totalMerged, removed: totalRemoved };
854
+ }
727
855
 
728
856
  // src/services/sqlite/Summaries.ts
857
+ function escapeLikePattern2(input) {
858
+ return input.replace(/[%_\\]/g, "\\$&");
859
+ }
729
860
  function createSummary(db, sessionId, project, request, investigated, learned, completed, nextSteps, notes) {
730
861
  const now = /* @__PURE__ */ new Date();
731
862
  const result = db.run(
@@ -747,12 +878,12 @@ function getSummariesByProject(db, project, limit = 50) {
747
878
  return query.all(project, limit);
748
879
  }
749
880
  function searchSummaries(db, searchTerm, project) {
750
- const sql = project ? `SELECT * FROM summaries
751
- WHERE project = ? AND (request LIKE ? OR learned LIKE ? OR completed LIKE ? OR notes LIKE ?)
752
- ORDER BY created_at_epoch DESC` : `SELECT * FROM summaries
753
- WHERE request LIKE ? OR learned LIKE ? OR completed LIKE ? OR notes LIKE ?
881
+ const sql = project ? `SELECT * FROM summaries
882
+ WHERE project = ? AND (request LIKE ? ESCAPE '\\' OR learned LIKE ? ESCAPE '\\' OR completed LIKE ? ESCAPE '\\' OR notes LIKE ? ESCAPE '\\')
883
+ ORDER BY created_at_epoch DESC` : `SELECT * FROM summaries
884
+ WHERE request LIKE ? ESCAPE '\\' OR learned LIKE ? ESCAPE '\\' OR completed LIKE ? ESCAPE '\\' OR notes LIKE ? ESCAPE '\\'
754
885
  ORDER BY created_at_epoch DESC`;
755
- const pattern = `%${searchTerm}%`;
886
+ const pattern = `%${escapeLikePattern2(searchTerm)}%`;
756
887
  const query = db.query(sql);
757
888
  if (project) {
758
889
  return query.all(project, pattern, pattern, pattern, pattern);
@@ -796,16 +927,188 @@ function deletePrompt(db, id) {
796
927
  db.run("DELETE FROM prompts WHERE id = ?", [id]);
797
928
  }
798
929
 
930
+ // src/services/sqlite/Checkpoints.ts
931
+ function createCheckpoint(db, sessionId, project, data) {
932
+ const now = /* @__PURE__ */ new Date();
933
+ const result = db.run(
934
+ `INSERT INTO checkpoints (session_id, project, task, progress, next_steps, open_questions, relevant_files, context_snapshot, created_at, created_at_epoch)
935
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
936
+ [
937
+ sessionId,
938
+ project,
939
+ data.task,
940
+ data.progress || null,
941
+ data.nextSteps || null,
942
+ data.openQuestions || null,
943
+ data.relevantFiles || null,
944
+ data.contextSnapshot || null,
945
+ now.toISOString(),
946
+ now.getTime()
947
+ ]
948
+ );
949
+ return Number(result.lastInsertRowid);
950
+ }
951
+ function getLatestCheckpoint(db, sessionId) {
952
+ const query = db.query(
953
+ "SELECT * FROM checkpoints WHERE session_id = ? ORDER BY created_at_epoch DESC LIMIT 1"
954
+ );
955
+ return query.get(sessionId);
956
+ }
957
+ function getLatestCheckpointByProject(db, project) {
958
+ const query = db.query(
959
+ "SELECT * FROM checkpoints WHERE project = ? ORDER BY created_at_epoch DESC LIMIT 1"
960
+ );
961
+ return query.get(project);
962
+ }
963
+ function getCheckpointsBySession(db, sessionId) {
964
+ const query = db.query(
965
+ "SELECT * FROM checkpoints WHERE session_id = ? ORDER BY created_at_epoch DESC"
966
+ );
967
+ return query.all(sessionId);
968
+ }
969
+
970
+ // src/services/sqlite/Reports.ts
971
+ function getReportData(db, project, startEpoch, endEpoch) {
972
+ const startDate = new Date(startEpoch);
973
+ const endDate = new Date(endEpoch);
974
+ const days = Math.ceil((endEpoch - startEpoch) / (24 * 60 * 60 * 1e3));
975
+ const label = days <= 7 ? "Weekly" : days <= 31 ? "Monthly" : "Custom";
976
+ const countInRange = (table, epochCol = "created_at_epoch") => {
977
+ const sql = project ? `SELECT COUNT(*) as count FROM ${table} WHERE project = ? AND ${epochCol} >= ? AND ${epochCol} <= ?` : `SELECT COUNT(*) as count FROM ${table} WHERE ${epochCol} >= ? AND ${epochCol} <= ?`;
978
+ const stmt = db.query(sql);
979
+ const row = project ? stmt.get(project, startEpoch, endEpoch) : stmt.get(startEpoch, endEpoch);
980
+ return row?.count || 0;
981
+ };
982
+ const observations = countInRange("observations");
983
+ const summaries = countInRange("summaries");
984
+ const prompts = countInRange("prompts");
985
+ const sessions = countInRange("sessions", "started_at_epoch");
986
+ const timelineSql = project ? `SELECT DATE(datetime(created_at_epoch / 1000, 'unixepoch')) as day, COUNT(*) as count
987
+ FROM observations
988
+ WHERE project = ? AND created_at_epoch >= ? AND created_at_epoch <= ?
989
+ GROUP BY day ORDER BY day ASC` : `SELECT DATE(datetime(created_at_epoch / 1000, 'unixepoch')) as day, COUNT(*) as count
990
+ FROM observations
991
+ WHERE created_at_epoch >= ? AND created_at_epoch <= ?
992
+ GROUP BY day ORDER BY day ASC`;
993
+ const timelineStmt = db.query(timelineSql);
994
+ const timeline = project ? timelineStmt.all(project, startEpoch, endEpoch) : timelineStmt.all(startEpoch, endEpoch);
995
+ const typeSql = project ? `SELECT type, COUNT(*) as count FROM observations
996
+ WHERE project = ? AND created_at_epoch >= ? AND created_at_epoch <= ?
997
+ GROUP BY type ORDER BY count DESC` : `SELECT type, COUNT(*) as count FROM observations
998
+ WHERE created_at_epoch >= ? AND created_at_epoch <= ?
999
+ GROUP BY type ORDER BY count DESC`;
1000
+ const typeStmt = db.query(typeSql);
1001
+ const typeDistribution = project ? typeStmt.all(project, startEpoch, endEpoch) : typeStmt.all(startEpoch, endEpoch);
1002
+ const sessionTotalSql = project ? `SELECT COUNT(*) as count FROM sessions WHERE project = ? AND started_at_epoch >= ? AND started_at_epoch <= ?` : `SELECT COUNT(*) as count FROM sessions WHERE started_at_epoch >= ? AND started_at_epoch <= ?`;
1003
+ const sessionTotal = (project ? db.query(sessionTotalSql).get(project, startEpoch, endEpoch)?.count : db.query(sessionTotalSql).get(startEpoch, endEpoch)?.count) || 0;
1004
+ const sessionCompletedSql = project ? `SELECT COUNT(*) as count FROM sessions WHERE project = ? AND started_at_epoch >= ? AND started_at_epoch <= ? AND status = 'completed'` : `SELECT COUNT(*) as count FROM sessions WHERE started_at_epoch >= ? AND started_at_epoch <= ? AND status = 'completed'`;
1005
+ const sessionCompleted = (project ? db.query(sessionCompletedSql).get(project, startEpoch, endEpoch)?.count : db.query(sessionCompletedSql).get(startEpoch, endEpoch)?.count) || 0;
1006
+ const sessionAvgSql = project ? `SELECT AVG((completed_at_epoch - started_at_epoch) / 1000.0 / 60.0) as avg_min
1007
+ FROM sessions
1008
+ WHERE project = ? AND started_at_epoch >= ? AND started_at_epoch <= ?
1009
+ AND status = 'completed' AND completed_at_epoch IS NOT NULL AND completed_at_epoch > started_at_epoch` : `SELECT AVG((completed_at_epoch - started_at_epoch) / 1000.0 / 60.0) as avg_min
1010
+ FROM sessions
1011
+ WHERE started_at_epoch >= ? AND started_at_epoch <= ?
1012
+ AND status = 'completed' AND completed_at_epoch IS NOT NULL AND completed_at_epoch > started_at_epoch`;
1013
+ const avgRow = project ? db.query(sessionAvgSql).get(project, startEpoch, endEpoch) : db.query(sessionAvgSql).get(startEpoch, endEpoch);
1014
+ const avgDurationMinutes = Math.round((avgRow?.avg_min || 0) * 10) / 10;
1015
+ const knowledgeSql = project ? `SELECT COUNT(*) as count FROM observations
1016
+ WHERE project = ? AND created_at_epoch >= ? AND created_at_epoch <= ?
1017
+ AND type IN ('constraint', 'decision', 'heuristic', 'rejected')` : `SELECT COUNT(*) as count FROM observations
1018
+ WHERE created_at_epoch >= ? AND created_at_epoch <= ?
1019
+ AND type IN ('constraint', 'decision', 'heuristic', 'rejected')`;
1020
+ const knowledgeCount = (project ? db.query(knowledgeSql).get(project, startEpoch, endEpoch)?.count : db.query(knowledgeSql).get(startEpoch, endEpoch)?.count) || 0;
1021
+ const staleSql = project ? `SELECT COUNT(*) as count FROM observations
1022
+ WHERE project = ? AND created_at_epoch >= ? AND created_at_epoch <= ? AND is_stale = 1` : `SELECT COUNT(*) as count FROM observations
1023
+ WHERE created_at_epoch >= ? AND created_at_epoch <= ? AND is_stale = 1`;
1024
+ const staleCount = (project ? db.query(staleSql).get(project, startEpoch, endEpoch)?.count : db.query(staleSql).get(startEpoch, endEpoch)?.count) || 0;
1025
+ const summarySql = project ? `SELECT learned, completed, next_steps FROM summaries
1026
+ WHERE project = ? AND created_at_epoch >= ? AND created_at_epoch <= ?
1027
+ ORDER BY created_at_epoch DESC` : `SELECT learned, completed, next_steps FROM summaries
1028
+ WHERE created_at_epoch >= ? AND created_at_epoch <= ?
1029
+ ORDER BY created_at_epoch DESC`;
1030
+ const summaryRows = project ? db.query(summarySql).all(project, startEpoch, endEpoch) : db.query(summarySql).all(startEpoch, endEpoch);
1031
+ const topLearnings = [];
1032
+ const completedTasks = [];
1033
+ const nextStepsArr = [];
1034
+ for (const row of summaryRows) {
1035
+ if (row.learned) {
1036
+ const parts = row.learned.split("; ").filter(Boolean);
1037
+ topLearnings.push(...parts);
1038
+ }
1039
+ if (row.completed) {
1040
+ const parts = row.completed.split("; ").filter(Boolean);
1041
+ completedTasks.push(...parts);
1042
+ }
1043
+ if (row.next_steps) {
1044
+ const parts = row.next_steps.split("; ").filter(Boolean);
1045
+ nextStepsArr.push(...parts);
1046
+ }
1047
+ }
1048
+ const filesSql = project ? `SELECT files_modified FROM observations
1049
+ WHERE project = ? AND created_at_epoch >= ? AND created_at_epoch <= ?
1050
+ AND files_modified IS NOT NULL AND files_modified != ''` : `SELECT files_modified FROM observations
1051
+ WHERE created_at_epoch >= ? AND created_at_epoch <= ?
1052
+ AND files_modified IS NOT NULL AND files_modified != ''`;
1053
+ const fileRows = project ? db.query(filesSql).all(project, startEpoch, endEpoch) : db.query(filesSql).all(startEpoch, endEpoch);
1054
+ const fileCounts = /* @__PURE__ */ new Map();
1055
+ for (const row of fileRows) {
1056
+ const files = row.files_modified.split(",").map((f) => f.trim()).filter(Boolean);
1057
+ for (const file of files) {
1058
+ fileCounts.set(file, (fileCounts.get(file) || 0) + 1);
1059
+ }
1060
+ }
1061
+ const fileHotspots = Array.from(fileCounts.entries()).map(([file, count]) => ({ file, count })).sort((a, b) => b.count - a.count).slice(0, 15);
1062
+ return {
1063
+ period: {
1064
+ start: startDate.toISOString().split("T")[0],
1065
+ end: endDate.toISOString().split("T")[0],
1066
+ days,
1067
+ label
1068
+ },
1069
+ overview: {
1070
+ observations,
1071
+ summaries,
1072
+ sessions,
1073
+ prompts,
1074
+ knowledgeCount,
1075
+ staleCount
1076
+ },
1077
+ timeline,
1078
+ typeDistribution,
1079
+ sessionStats: {
1080
+ total: sessionTotal,
1081
+ completed: sessionCompleted,
1082
+ avgDurationMinutes
1083
+ },
1084
+ topLearnings: [...new Set(topLearnings)].slice(0, 10),
1085
+ completedTasks: [...new Set(completedTasks)].slice(0, 10),
1086
+ nextSteps: [...new Set(nextStepsArr)].slice(0, 10),
1087
+ fileHotspots
1088
+ };
1089
+ }
1090
+
799
1091
  // src/services/sqlite/Search.ts
1092
+ import { existsSync as existsSync3, statSync } from "fs";
1093
+ function escapeLikePattern3(input) {
1094
+ return input.replace(/[%_\\]/g, "\\$&");
1095
+ }
1096
+ function sanitizeFTS5Query(query) {
1097
+ const trimmed = query.length > 1e4 ? query.substring(0, 1e4) : query;
1098
+ const terms = trimmed.replace(/[""]/g, "").split(/\s+/).filter((t) => t.length > 0).slice(0, 100).map((t) => `"${t}"`);
1099
+ return terms.join(" ");
1100
+ }
800
1101
  function searchObservationsFTS(db, query, filters = {}) {
801
1102
  const limit = filters.limit || 50;
802
1103
  try {
1104
+ const safeQuery = sanitizeFTS5Query(query);
1105
+ if (!safeQuery) return searchObservationsLIKE(db, query, filters);
803
1106
  let sql = `
804
1107
  SELECT o.* FROM observations o
805
1108
  JOIN observations_fts fts ON o.id = fts.rowid
806
1109
  WHERE observations_fts MATCH ?
807
1110
  `;
808
- const params = [query];
1111
+ const params = [safeQuery];
809
1112
  if (filters.project) {
810
1113
  sql += " AND o.project = ?";
811
1114
  params.push(filters.project);
@@ -830,12 +1133,47 @@ function searchObservationsFTS(db, query, filters = {}) {
830
1133
  return searchObservationsLIKE(db, query, filters);
831
1134
  }
832
1135
  }
1136
+ function searchObservationsFTSWithRank(db, query, filters = {}) {
1137
+ const limit = filters.limit || 50;
1138
+ try {
1139
+ const safeQuery = sanitizeFTS5Query(query);
1140
+ if (!safeQuery) return [];
1141
+ let sql = `
1142
+ SELECT o.*, rank as fts5_rank FROM observations o
1143
+ JOIN observations_fts fts ON o.id = fts.rowid
1144
+ WHERE observations_fts MATCH ?
1145
+ `;
1146
+ const params = [safeQuery];
1147
+ if (filters.project) {
1148
+ sql += " AND o.project = ?";
1149
+ params.push(filters.project);
1150
+ }
1151
+ if (filters.type) {
1152
+ sql += " AND o.type = ?";
1153
+ params.push(filters.type);
1154
+ }
1155
+ if (filters.dateStart) {
1156
+ sql += " AND o.created_at_epoch >= ?";
1157
+ params.push(filters.dateStart);
1158
+ }
1159
+ if (filters.dateEnd) {
1160
+ sql += " AND o.created_at_epoch <= ?";
1161
+ params.push(filters.dateEnd);
1162
+ }
1163
+ sql += " ORDER BY rank LIMIT ?";
1164
+ params.push(limit);
1165
+ const stmt = db.query(sql);
1166
+ return stmt.all(...params);
1167
+ } catch {
1168
+ return [];
1169
+ }
1170
+ }
833
1171
  function searchObservationsLIKE(db, query, filters = {}) {
834
1172
  const limit = filters.limit || 50;
835
- const pattern = `%${query}%`;
1173
+ const pattern = `%${escapeLikePattern3(query)}%`;
836
1174
  let sql = `
837
1175
  SELECT * FROM observations
838
- WHERE (title LIKE ? OR text LIKE ? OR narrative LIKE ? OR concepts LIKE ?)
1176
+ WHERE (title LIKE ? ESCAPE '\\' OR text LIKE ? ESCAPE '\\' OR narrative LIKE ? ESCAPE '\\' OR concepts LIKE ? ESCAPE '\\')
839
1177
  `;
840
1178
  const params = [pattern, pattern, pattern, pattern];
841
1179
  if (filters.project) {
@@ -861,10 +1199,10 @@ function searchObservationsLIKE(db, query, filters = {}) {
861
1199
  }
862
1200
  function searchSummariesFiltered(db, query, filters = {}) {
863
1201
  const limit = filters.limit || 20;
864
- const pattern = `%${query}%`;
1202
+ const pattern = `%${escapeLikePattern3(query)}%`;
865
1203
  let sql = `
866
1204
  SELECT * FROM summaries
867
- WHERE (request LIKE ? OR learned LIKE ? OR completed LIKE ? OR notes LIKE ? OR next_steps LIKE ?)
1205
+ WHERE (request LIKE ? ESCAPE '\\' OR learned LIKE ? ESCAPE '\\' OR completed LIKE ? ESCAPE '\\' OR notes LIKE ? ESCAPE '\\' OR next_steps LIKE ? ESCAPE '\\')
868
1206
  `;
869
1207
  const params = [pattern, pattern, pattern, pattern, pattern];
870
1208
  if (filters.project) {
@@ -885,11 +1223,13 @@ function searchSummariesFiltered(db, query, filters = {}) {
885
1223
  return stmt.all(...params);
886
1224
  }
887
1225
  function getObservationsByIds(db, ids) {
888
- if (ids.length === 0) return [];
889
- const placeholders = ids.map(() => "?").join(",");
1226
+ if (!Array.isArray(ids) || ids.length === 0) return [];
1227
+ const validIds = ids.filter((id) => typeof id === "number" && Number.isInteger(id) && id > 0).slice(0, 500);
1228
+ if (validIds.length === 0) return [];
1229
+ const placeholders = validIds.map(() => "?").join(",");
890
1230
  const sql = `SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC`;
891
1231
  const stmt = db.query(sql);
892
- return stmt.all(...ids);
1232
+ return stmt.all(...validIds);
893
1233
  }
894
1234
  function getTimeline(db, anchorId, depthBefore = 5, depthAfter = 5) {
895
1235
  const anchorStmt = db.query("SELECT created_at_epoch FROM observations WHERE id = ?");
@@ -931,11 +1271,52 @@ function getProjectStats(db, project) {
931
1271
  prompts: prmStmt.get(project)?.count || 0
932
1272
  };
933
1273
  }
1274
+ function getStaleObservations(db, project) {
1275
+ const rows = db.query(`
1276
+ SELECT * FROM observations
1277
+ WHERE project = ? AND files_modified IS NOT NULL AND files_modified != ''
1278
+ ORDER BY created_at_epoch DESC
1279
+ LIMIT 500
1280
+ `).all(project);
1281
+ const staleObs = [];
1282
+ for (const obs of rows) {
1283
+ if (!obs.files_modified) continue;
1284
+ const files = obs.files_modified.split(",").map((f) => f.trim()).filter(Boolean);
1285
+ let isStale = false;
1286
+ for (const filepath of files) {
1287
+ try {
1288
+ if (!existsSync3(filepath)) continue;
1289
+ const stat = statSync(filepath);
1290
+ if (stat.mtimeMs > obs.created_at_epoch) {
1291
+ isStale = true;
1292
+ break;
1293
+ }
1294
+ } catch {
1295
+ }
1296
+ }
1297
+ if (isStale) {
1298
+ staleObs.push(obs);
1299
+ }
1300
+ }
1301
+ return staleObs;
1302
+ }
1303
+ function markObservationsStale(db, ids, stale) {
1304
+ if (!Array.isArray(ids) || ids.length === 0) return;
1305
+ const validIds = ids.filter((id) => typeof id === "number" && Number.isInteger(id) && id > 0).slice(0, 500);
1306
+ if (validIds.length === 0) return;
1307
+ const placeholders = validIds.map(() => "?").join(",");
1308
+ db.run(
1309
+ `UPDATE observations SET is_stale = ? WHERE id IN (${placeholders})`,
1310
+ [stale ? 1 : 0, ...validIds]
1311
+ );
1312
+ }
934
1313
  export {
935
1314
  KiroMemoryDatabase as ContextKitDatabase,
936
1315
  DatabaseManager,
937
1316
  KiroMemoryDatabase,
938
1317
  completeSession,
1318
+ consolidateObservations,
1319
+ createCheckpoint,
939
1320
  createObservation,
940
1321
  createPrompt,
941
1322
  createSession,
@@ -945,7 +1326,10 @@ export {
945
1326
  deleteSummary,
946
1327
  failSession,
947
1328
  getActiveSessions,
1329
+ getCheckpointsBySession,
948
1330
  getDatabase,
1331
+ getLatestCheckpoint,
1332
+ getLatestCheckpointByProject,
949
1333
  getLatestPrompt,
950
1334
  getObservationsByIds,
951
1335
  getObservationsByProject,
@@ -953,17 +1337,22 @@ export {
953
1337
  getProjectStats,
954
1338
  getPromptsByProject,
955
1339
  getPromptsBySession,
1340
+ getReportData,
956
1341
  getSessionByContentId,
957
1342
  getSessionById,
958
1343
  getSessionsByProject,
1344
+ getStaleObservations,
959
1345
  getSummariesByProject,
960
1346
  getSummaryBySession,
961
1347
  getTimeline,
962
1348
  initializeDatabase,
1349
+ markObservationsStale,
963
1350
  searchObservations,
964
1351
  searchObservationsFTS,
1352
+ searchObservationsFTSWithRank,
965
1353
  searchObservationsLIKE,
966
1354
  searchSummaries,
967
1355
  searchSummariesFiltered,
1356
+ updateLastAccessed,
968
1357
  updateSessionMemoryId
969
1358
  };
@@ -3,7 +3,7 @@ import { createRequire } from 'module';const require = createRequire(import.meta
3
3
  // src/shared/paths.ts
4
4
  import { join as join2, dirname, basename } from "path";
5
5
  import { homedir as homedir2 } from "os";
6
- import { mkdirSync as mkdirSync2 } from "fs";
6
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
7
7
  import { execSync } from "child_process";
8
8
  import { fileURLToPath } from "url";
9
9
 
@@ -234,7 +234,9 @@ function getDirname() {
234
234
  return dirname(fileURLToPath(import.meta.url));
235
235
  }
236
236
  var _dirname = getDirname();
237
- var DATA_DIR = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR || join2(homedir2(), ".contextkit");
237
+ var _legacyDir = join2(homedir2(), ".contextkit");
238
+ var _defaultDir = existsSync2(_legacyDir) ? _legacyDir : join2(homedir2(), ".kiro-memory");
239
+ var DATA_DIR = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR || _defaultDir;
238
240
  var KIRO_CONFIG_DIR = process.env.KIRO_CONFIG_DIR || join2(homedir2(), ".kiro");
239
241
  var PLUGIN_ROOT = join2(KIRO_CONFIG_DIR, "plugins", "kiro-memory");
240
242
  var ARCHIVES_DIR = join2(DATA_DIR, "archives");
@@ -243,7 +245,8 @@ var TRASH_DIR = join2(DATA_DIR, "trash");
243
245
  var BACKUPS_DIR = join2(DATA_DIR, "backups");
244
246
  var MODES_DIR = join2(DATA_DIR, "modes");
245
247
  var USER_SETTINGS_PATH = join2(DATA_DIR, "settings.json");
246
- var DB_PATH = join2(DATA_DIR, "contextkit.db");
248
+ var _legacyDb = join2(DATA_DIR, "contextkit.db");
249
+ var DB_PATH = existsSync2(_legacyDb) ? _legacyDb : join2(DATA_DIR, "kiro-memory.db");
247
250
  var VECTOR_DB_DIR = join2(DATA_DIR, "vector-db");
248
251
  var OBSERVER_SESSIONS_DIR = join2(DATA_DIR, "observer-sessions");
249
252
  var KIRO_SETTINGS_PATH = join2(KIRO_CONFIG_DIR, "settings.json");
@@ -1 +1,7 @@
1
1
  import { createRequire } from 'module';const require = createRequire(import.meta.url);
2
+
3
+ // src/types/worker-types.ts
4
+ var KNOWLEDGE_TYPES = ["constraint", "decision", "heuristic", "rejected"];
5
+ export {
6
+ KNOWLEDGE_TYPES
7
+ };