kiro-memory 1.6.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.
@@ -636,6 +636,55 @@ var MigrationRunner = class {
636
636
  `);
637
637
  db.run("CREATE UNIQUE INDEX IF NOT EXISTS idx_project_aliases_name ON project_aliases(project_name)");
638
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
+ }
639
688
  }
640
689
  ];
641
690
  }
@@ -703,6 +752,9 @@ function getSessionsByProject(db, project, limit = 100) {
703
752
  }
704
753
 
705
754
  // src/services/sqlite/Observations.ts
755
+ function escapeLikePattern(input) {
756
+ return input.replace(/[%_\\]/g, "\\$&");
757
+ }
706
758
  function createObservation(db, memorySessionId, project, type, title, subtitle, text, narrative, facts, concepts, filesRead, filesModified, promptNumber) {
707
759
  const now = /* @__PURE__ */ new Date();
708
760
  const result = db.run(
@@ -726,12 +778,12 @@ function getObservationsByProject(db, project, limit = 100) {
726
778
  return query.all(project, limit);
727
779
  }
728
780
  function searchObservations(db, searchTerm, project) {
729
- const sql = project ? `SELECT * FROM observations
730
- WHERE project = ? AND (title LIKE ? OR text LIKE ? OR narrative LIKE ?)
731
- ORDER BY created_at_epoch DESC` : `SELECT * FROM observations
732
- 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 '\\'
733
785
  ORDER BY created_at_epoch DESC`;
734
- const pattern = `%${searchTerm}%`;
786
+ const pattern = `%${escapeLikePattern(searchTerm)}%`;
735
787
  const query = db.query(sql);
736
788
  if (project) {
737
789
  return query.all(project, pattern, pattern, pattern);
@@ -741,8 +793,70 @@ function searchObservations(db, searchTerm, project) {
741
793
  function deleteObservation(db, id) {
742
794
  db.run("DELETE FROM observations WHERE id = ?", [id]);
743
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
+ }
744
855
 
745
856
  // src/services/sqlite/Summaries.ts
857
+ function escapeLikePattern2(input) {
858
+ return input.replace(/[%_\\]/g, "\\$&");
859
+ }
746
860
  function createSummary(db, sessionId, project, request, investigated, learned, completed, nextSteps, notes) {
747
861
  const now = /* @__PURE__ */ new Date();
748
862
  const result = db.run(
@@ -764,12 +878,12 @@ function getSummariesByProject(db, project, limit = 50) {
764
878
  return query.all(project, limit);
765
879
  }
766
880
  function searchSummaries(db, searchTerm, project) {
767
- const sql = project ? `SELECT * FROM summaries
768
- WHERE project = ? AND (request LIKE ? OR learned LIKE ? OR completed LIKE ? OR notes LIKE ?)
769
- ORDER BY created_at_epoch DESC` : `SELECT * FROM summaries
770
- 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 '\\'
771
885
  ORDER BY created_at_epoch DESC`;
772
- const pattern = `%${searchTerm}%`;
886
+ const pattern = `%${escapeLikePattern2(searchTerm)}%`;
773
887
  const query = db.query(sql);
774
888
  if (project) {
775
889
  return query.all(project, pattern, pattern, pattern, pattern);
@@ -813,9 +927,175 @@ function deletePrompt(db, id) {
813
927
  db.run("DELETE FROM prompts WHERE id = ?", [id]);
814
928
  }
815
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
+
816
1091
  // src/services/sqlite/Search.ts
1092
+ import { existsSync as existsSync3, statSync } from "fs";
1093
+ function escapeLikePattern3(input) {
1094
+ return input.replace(/[%_\\]/g, "\\$&");
1095
+ }
817
1096
  function sanitizeFTS5Query(query) {
818
- const terms = query.replace(/[""]/g, "").split(/\s+/).filter((t) => t.length > 0).map((t) => `"${t}"`);
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}"`);
819
1099
  return terms.join(" ");
820
1100
  }
821
1101
  function searchObservationsFTS(db, query, filters = {}) {
@@ -853,12 +1133,47 @@ function searchObservationsFTS(db, query, filters = {}) {
853
1133
  return searchObservationsLIKE(db, query, filters);
854
1134
  }
855
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
+ }
856
1171
  function searchObservationsLIKE(db, query, filters = {}) {
857
1172
  const limit = filters.limit || 50;
858
- const pattern = `%${query}%`;
1173
+ const pattern = `%${escapeLikePattern3(query)}%`;
859
1174
  let sql = `
860
1175
  SELECT * FROM observations
861
- 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 '\\')
862
1177
  `;
863
1178
  const params = [pattern, pattern, pattern, pattern];
864
1179
  if (filters.project) {
@@ -884,10 +1199,10 @@ function searchObservationsLIKE(db, query, filters = {}) {
884
1199
  }
885
1200
  function searchSummariesFiltered(db, query, filters = {}) {
886
1201
  const limit = filters.limit || 20;
887
- const pattern = `%${query}%`;
1202
+ const pattern = `%${escapeLikePattern3(query)}%`;
888
1203
  let sql = `
889
1204
  SELECT * FROM summaries
890
- 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 '\\')
891
1206
  `;
892
1207
  const params = [pattern, pattern, pattern, pattern, pattern];
893
1208
  if (filters.project) {
@@ -908,11 +1223,13 @@ function searchSummariesFiltered(db, query, filters = {}) {
908
1223
  return stmt.all(...params);
909
1224
  }
910
1225
  function getObservationsByIds(db, ids) {
911
- if (ids.length === 0) return [];
912
- 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(",");
913
1230
  const sql = `SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC`;
914
1231
  const stmt = db.query(sql);
915
- return stmt.all(...ids);
1232
+ return stmt.all(...validIds);
916
1233
  }
917
1234
  function getTimeline(db, anchorId, depthBefore = 5, depthAfter = 5) {
918
1235
  const anchorStmt = db.query("SELECT created_at_epoch FROM observations WHERE id = ?");
@@ -954,11 +1271,52 @@ function getProjectStats(db, project) {
954
1271
  prompts: prmStmt.get(project)?.count || 0
955
1272
  };
956
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
+ }
957
1313
  export {
958
1314
  KiroMemoryDatabase as ContextKitDatabase,
959
1315
  DatabaseManager,
960
1316
  KiroMemoryDatabase,
961
1317
  completeSession,
1318
+ consolidateObservations,
1319
+ createCheckpoint,
962
1320
  createObservation,
963
1321
  createPrompt,
964
1322
  createSession,
@@ -968,7 +1326,10 @@ export {
968
1326
  deleteSummary,
969
1327
  failSession,
970
1328
  getActiveSessions,
1329
+ getCheckpointsBySession,
971
1330
  getDatabase,
1331
+ getLatestCheckpoint,
1332
+ getLatestCheckpointByProject,
972
1333
  getLatestPrompt,
973
1334
  getObservationsByIds,
974
1335
  getObservationsByProject,
@@ -976,17 +1337,22 @@ export {
976
1337
  getProjectStats,
977
1338
  getPromptsByProject,
978
1339
  getPromptsBySession,
1340
+ getReportData,
979
1341
  getSessionByContentId,
980
1342
  getSessionById,
981
1343
  getSessionsByProject,
1344
+ getStaleObservations,
982
1345
  getSummariesByProject,
983
1346
  getSummaryBySession,
984
1347
  getTimeline,
985
1348
  initializeDatabase,
1349
+ markObservationsStale,
986
1350
  searchObservations,
987
1351
  searchObservationsFTS,
1352
+ searchObservationsFTSWithRank,
988
1353
  searchObservationsLIKE,
989
1354
  searchSummaries,
990
1355
  searchSummariesFiltered,
1356
+ updateLastAccessed,
991
1357
  updateSessionMemoryId
992
1358
  };
@@ -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
+ };