kiro-memory 1.6.0 → 1.7.1
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.
- package/README.md +105 -99
- package/package.json +14 -7
- package/plugin/dist/cli/contextkit.js +2661 -497
- package/plugin/dist/hooks/agentSpawn.js +1455 -189
- package/plugin/dist/hooks/kiro-hooks.js +1389 -156
- package/plugin/dist/hooks/postToolUse.js +1451 -174
- package/plugin/dist/hooks/stop.js +1426 -170
- package/plugin/dist/hooks/userPromptSubmit.js +1418 -170
- package/plugin/dist/index.js +1406 -172
- package/plugin/dist/sdk/index.js +1389 -155
- package/plugin/dist/servers/mcp-server.js +203 -2
- package/plugin/dist/services/search/EmbeddingService.js +363 -0
- package/plugin/dist/services/search/HybridSearch.js +703 -151
- package/plugin/dist/services/search/ScoringEngine.js +75 -0
- package/plugin/dist/services/search/VectorSearch.js +512 -0
- package/plugin/dist/services/search/index.js +776 -64
- package/plugin/dist/services/sqlite/Database.js +49 -0
- package/plugin/dist/services/sqlite/Observations.js +70 -6
- package/plugin/dist/services/sqlite/Search.js +92 -8
- package/plugin/dist/services/sqlite/Summaries.js +8 -5
- package/plugin/dist/services/sqlite/index.js +384 -18
- package/plugin/dist/types/worker-types.js +6 -0
- package/plugin/dist/viewer.js +369 -69
- package/plugin/dist/worker-service.js +1496 -148
|
@@ -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
|
|
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
|
|
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(...
|
|
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
|
};
|