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.
- package/README.md +120 -101
- package/package.json +18 -8
- package/plugin/dist/cli/contextkit.js +2609 -593
- package/plugin/dist/hooks/agentSpawn.js +1399 -279
- package/plugin/dist/hooks/kiro-hooks.js +1307 -222
- package/plugin/dist/hooks/postToolUse.js +1404 -264
- package/plugin/dist/hooks/stop.js +1381 -261
- package/plugin/dist/hooks/userPromptSubmit.js +1369 -258
- package/plugin/dist/index.js +1352 -255
- package/plugin/dist/sdk/index.js +1306 -220
- 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 +73 -6
- package/plugin/dist/services/sqlite/Observations.js +70 -6
- package/plugin/dist/services/sqlite/Search.js +98 -8
- package/plugin/dist/services/sqlite/Summaries.js +8 -5
- package/plugin/dist/services/sqlite/index.js +413 -24
- package/plugin/dist/shared/paths.js +6 -3
- package/plugin/dist/types/worker-types.js +6 -0
- package/plugin/dist/viewer.js +381 -73
- package/plugin/dist/worker-service.js +4585 -195
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
339
|
-
|
|
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 = [
|
|
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
|
|
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(...
|
|
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
|
|
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
|
|
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");
|