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
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { createRequire } from 'module';const require = createRequire(import.meta.url);
|
|
2
2
|
|
|
3
3
|
// src/services/sqlite/Observations.ts
|
|
4
|
+
function escapeLikePattern(input) {
|
|
5
|
+
return input.replace(/[%_\\]/g, "\\$&");
|
|
6
|
+
}
|
|
4
7
|
function createObservation(db, memorySessionId, project, type, title, subtitle, text, narrative, facts, concepts, filesRead, filesModified, promptNumber) {
|
|
5
8
|
const now = /* @__PURE__ */ new Date();
|
|
6
9
|
const result = db.run(
|
|
@@ -24,12 +27,12 @@ function getObservationsByProject(db, project, limit = 100) {
|
|
|
24
27
|
return query.all(project, limit);
|
|
25
28
|
}
|
|
26
29
|
function searchObservations(db, searchTerm, project) {
|
|
27
|
-
const sql = project ? `SELECT * FROM observations
|
|
28
|
-
WHERE project = ? AND (title LIKE ? OR text LIKE ? OR narrative LIKE ?)
|
|
29
|
-
ORDER BY created_at_epoch DESC` : `SELECT * FROM observations
|
|
30
|
-
WHERE title LIKE ? OR text LIKE ? OR narrative LIKE ?
|
|
30
|
+
const sql = project ? `SELECT * FROM observations
|
|
31
|
+
WHERE project = ? AND (title LIKE ? ESCAPE '\\' OR text LIKE ? ESCAPE '\\' OR narrative LIKE ? ESCAPE '\\')
|
|
32
|
+
ORDER BY created_at_epoch DESC` : `SELECT * FROM observations
|
|
33
|
+
WHERE title LIKE ? ESCAPE '\\' OR text LIKE ? ESCAPE '\\' OR narrative LIKE ? ESCAPE '\\'
|
|
31
34
|
ORDER BY created_at_epoch DESC`;
|
|
32
|
-
const pattern = `%${searchTerm}%`;
|
|
35
|
+
const pattern = `%${escapeLikePattern(searchTerm)}%`;
|
|
33
36
|
const query = db.query(sql);
|
|
34
37
|
if (project) {
|
|
35
38
|
return query.all(project, pattern, pattern, pattern);
|
|
@@ -39,10 +42,71 @@ function searchObservations(db, searchTerm, project) {
|
|
|
39
42
|
function deleteObservation(db, id) {
|
|
40
43
|
db.run("DELETE FROM observations WHERE id = ?", [id]);
|
|
41
44
|
}
|
|
45
|
+
function updateLastAccessed(db, ids) {
|
|
46
|
+
if (!Array.isArray(ids) || ids.length === 0) return;
|
|
47
|
+
const validIds = ids.filter((id) => typeof id === "number" && Number.isInteger(id) && id > 0).slice(0, 500);
|
|
48
|
+
if (validIds.length === 0) return;
|
|
49
|
+
const now = Date.now();
|
|
50
|
+
const placeholders = validIds.map(() => "?").join(",");
|
|
51
|
+
db.run(
|
|
52
|
+
`UPDATE observations SET last_accessed_epoch = ? WHERE id IN (${placeholders})`,
|
|
53
|
+
[now, ...validIds]
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
function consolidateObservations(db, project, options = {}) {
|
|
57
|
+
const minGroupSize = options.minGroupSize || 3;
|
|
58
|
+
const groups = db.query(`
|
|
59
|
+
SELECT type, files_modified, COUNT(*) as cnt, GROUP_CONCAT(id) as ids
|
|
60
|
+
FROM observations
|
|
61
|
+
WHERE project = ? AND files_modified IS NOT NULL AND files_modified != ''
|
|
62
|
+
GROUP BY type, files_modified
|
|
63
|
+
HAVING cnt >= ?
|
|
64
|
+
ORDER BY cnt DESC
|
|
65
|
+
`).all(project, minGroupSize);
|
|
66
|
+
if (groups.length === 0) return { merged: 0, removed: 0 };
|
|
67
|
+
let totalMerged = 0;
|
|
68
|
+
let totalRemoved = 0;
|
|
69
|
+
for (const group of groups) {
|
|
70
|
+
const obsIds = group.ids.split(",").map(Number);
|
|
71
|
+
const placeholders = obsIds.map(() => "?").join(",");
|
|
72
|
+
const observations = db.query(
|
|
73
|
+
`SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC`
|
|
74
|
+
).all(...obsIds);
|
|
75
|
+
if (observations.length < minGroupSize) continue;
|
|
76
|
+
if (options.dryRun) {
|
|
77
|
+
totalMerged += 1;
|
|
78
|
+
totalRemoved += observations.length - 1;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const keeper = observations[0];
|
|
82
|
+
const others = observations.slice(1);
|
|
83
|
+
const uniqueTexts = /* @__PURE__ */ new Set();
|
|
84
|
+
if (keeper.text) uniqueTexts.add(keeper.text);
|
|
85
|
+
for (const obs of others) {
|
|
86
|
+
if (obs.text && !uniqueTexts.has(obs.text)) {
|
|
87
|
+
uniqueTexts.add(obs.text);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const consolidatedText = Array.from(uniqueTexts).join("\n---\n").substring(0, 1e5);
|
|
91
|
+
db.run(
|
|
92
|
+
"UPDATE observations SET text = ?, title = ? WHERE id = ?",
|
|
93
|
+
[consolidatedText, `[consolidato x${observations.length}] ${keeper.title}`, keeper.id]
|
|
94
|
+
);
|
|
95
|
+
const removeIds = others.map((o) => o.id);
|
|
96
|
+
const removePlaceholders = removeIds.map(() => "?").join(",");
|
|
97
|
+
db.run(`DELETE FROM observations WHERE id IN (${removePlaceholders})`, removeIds);
|
|
98
|
+
db.run(`DELETE FROM observation_embeddings WHERE observation_id IN (${removePlaceholders})`, removeIds);
|
|
99
|
+
totalMerged += 1;
|
|
100
|
+
totalRemoved += removeIds.length;
|
|
101
|
+
}
|
|
102
|
+
return { merged: totalMerged, removed: totalRemoved };
|
|
103
|
+
}
|
|
42
104
|
export {
|
|
105
|
+
consolidateObservations,
|
|
43
106
|
createObservation,
|
|
44
107
|
deleteObservation,
|
|
45
108
|
getObservationsByProject,
|
|
46
109
|
getObservationsBySession,
|
|
47
|
-
searchObservations
|
|
110
|
+
searchObservations,
|
|
111
|
+
updateLastAccessed
|
|
48
112
|
};
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { createRequire } from 'module';const require = createRequire(import.meta.url);
|
|
2
2
|
|
|
3
3
|
// src/services/sqlite/Search.ts
|
|
4
|
+
import { existsSync, statSync } from "fs";
|
|
5
|
+
function escapeLikePattern(input) {
|
|
6
|
+
return input.replace(/[%_\\]/g, "\\$&");
|
|
7
|
+
}
|
|
4
8
|
function sanitizeFTS5Query(query) {
|
|
5
|
-
const
|
|
9
|
+
const trimmed = query.length > 1e4 ? query.substring(0, 1e4) : query;
|
|
10
|
+
const terms = trimmed.replace(/[""]/g, "").split(/\s+/).filter((t) => t.length > 0).slice(0, 100).map((t) => `"${t}"`);
|
|
6
11
|
return terms.join(" ");
|
|
7
12
|
}
|
|
8
13
|
function searchObservationsFTS(db, query, filters = {}) {
|
|
@@ -40,12 +45,47 @@ function searchObservationsFTS(db, query, filters = {}) {
|
|
|
40
45
|
return searchObservationsLIKE(db, query, filters);
|
|
41
46
|
}
|
|
42
47
|
}
|
|
48
|
+
function searchObservationsFTSWithRank(db, query, filters = {}) {
|
|
49
|
+
const limit = filters.limit || 50;
|
|
50
|
+
try {
|
|
51
|
+
const safeQuery = sanitizeFTS5Query(query);
|
|
52
|
+
if (!safeQuery) return [];
|
|
53
|
+
let sql = `
|
|
54
|
+
SELECT o.*, rank as fts5_rank FROM observations o
|
|
55
|
+
JOIN observations_fts fts ON o.id = fts.rowid
|
|
56
|
+
WHERE observations_fts MATCH ?
|
|
57
|
+
`;
|
|
58
|
+
const params = [safeQuery];
|
|
59
|
+
if (filters.project) {
|
|
60
|
+
sql += " AND o.project = ?";
|
|
61
|
+
params.push(filters.project);
|
|
62
|
+
}
|
|
63
|
+
if (filters.type) {
|
|
64
|
+
sql += " AND o.type = ?";
|
|
65
|
+
params.push(filters.type);
|
|
66
|
+
}
|
|
67
|
+
if (filters.dateStart) {
|
|
68
|
+
sql += " AND o.created_at_epoch >= ?";
|
|
69
|
+
params.push(filters.dateStart);
|
|
70
|
+
}
|
|
71
|
+
if (filters.dateEnd) {
|
|
72
|
+
sql += " AND o.created_at_epoch <= ?";
|
|
73
|
+
params.push(filters.dateEnd);
|
|
74
|
+
}
|
|
75
|
+
sql += " ORDER BY rank LIMIT ?";
|
|
76
|
+
params.push(limit);
|
|
77
|
+
const stmt = db.query(sql);
|
|
78
|
+
return stmt.all(...params);
|
|
79
|
+
} catch {
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
43
83
|
function searchObservationsLIKE(db, query, filters = {}) {
|
|
44
84
|
const limit = filters.limit || 50;
|
|
45
|
-
const pattern = `%${query}%`;
|
|
85
|
+
const pattern = `%${escapeLikePattern(query)}%`;
|
|
46
86
|
let sql = `
|
|
47
87
|
SELECT * FROM observations
|
|
48
|
-
WHERE (title LIKE ? OR text LIKE ? OR narrative LIKE ? OR concepts LIKE ?)
|
|
88
|
+
WHERE (title LIKE ? ESCAPE '\\' OR text LIKE ? ESCAPE '\\' OR narrative LIKE ? ESCAPE '\\' OR concepts LIKE ? ESCAPE '\\')
|
|
49
89
|
`;
|
|
50
90
|
const params = [pattern, pattern, pattern, pattern];
|
|
51
91
|
if (filters.project) {
|
|
@@ -71,10 +111,10 @@ function searchObservationsLIKE(db, query, filters = {}) {
|
|
|
71
111
|
}
|
|
72
112
|
function searchSummariesFiltered(db, query, filters = {}) {
|
|
73
113
|
const limit = filters.limit || 20;
|
|
74
|
-
const pattern = `%${query}%`;
|
|
114
|
+
const pattern = `%${escapeLikePattern(query)}%`;
|
|
75
115
|
let sql = `
|
|
76
116
|
SELECT * FROM summaries
|
|
77
|
-
WHERE (request LIKE ? OR learned LIKE ? OR completed LIKE ? OR notes LIKE ? OR next_steps LIKE ?)
|
|
117
|
+
WHERE (request LIKE ? ESCAPE '\\' OR learned LIKE ? ESCAPE '\\' OR completed LIKE ? ESCAPE '\\' OR notes LIKE ? ESCAPE '\\' OR next_steps LIKE ? ESCAPE '\\')
|
|
78
118
|
`;
|
|
79
119
|
const params = [pattern, pattern, pattern, pattern, pattern];
|
|
80
120
|
if (filters.project) {
|
|
@@ -95,11 +135,13 @@ function searchSummariesFiltered(db, query, filters = {}) {
|
|
|
95
135
|
return stmt.all(...params);
|
|
96
136
|
}
|
|
97
137
|
function getObservationsByIds(db, ids) {
|
|
98
|
-
if (ids.length === 0) return [];
|
|
99
|
-
const
|
|
138
|
+
if (!Array.isArray(ids) || ids.length === 0) return [];
|
|
139
|
+
const validIds = ids.filter((id) => typeof id === "number" && Number.isInteger(id) && id > 0).slice(0, 500);
|
|
140
|
+
if (validIds.length === 0) return [];
|
|
141
|
+
const placeholders = validIds.map(() => "?").join(",");
|
|
100
142
|
const sql = `SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC`;
|
|
101
143
|
const stmt = db.query(sql);
|
|
102
|
-
return stmt.all(...
|
|
144
|
+
return stmt.all(...validIds);
|
|
103
145
|
}
|
|
104
146
|
function getTimeline(db, anchorId, depthBefore = 5, depthAfter = 5) {
|
|
105
147
|
const anchorStmt = db.query("SELECT created_at_epoch FROM observations WHERE id = ?");
|
|
@@ -141,11 +183,53 @@ function getProjectStats(db, project) {
|
|
|
141
183
|
prompts: prmStmt.get(project)?.count || 0
|
|
142
184
|
};
|
|
143
185
|
}
|
|
186
|
+
function getStaleObservations(db, project) {
|
|
187
|
+
const rows = db.query(`
|
|
188
|
+
SELECT * FROM observations
|
|
189
|
+
WHERE project = ? AND files_modified IS NOT NULL AND files_modified != ''
|
|
190
|
+
ORDER BY created_at_epoch DESC
|
|
191
|
+
LIMIT 500
|
|
192
|
+
`).all(project);
|
|
193
|
+
const staleObs = [];
|
|
194
|
+
for (const obs of rows) {
|
|
195
|
+
if (!obs.files_modified) continue;
|
|
196
|
+
const files = obs.files_modified.split(",").map((f) => f.trim()).filter(Boolean);
|
|
197
|
+
let isStale = false;
|
|
198
|
+
for (const filepath of files) {
|
|
199
|
+
try {
|
|
200
|
+
if (!existsSync(filepath)) continue;
|
|
201
|
+
const stat = statSync(filepath);
|
|
202
|
+
if (stat.mtimeMs > obs.created_at_epoch) {
|
|
203
|
+
isStale = true;
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
} catch {
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (isStale) {
|
|
210
|
+
staleObs.push(obs);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return staleObs;
|
|
214
|
+
}
|
|
215
|
+
function markObservationsStale(db, ids, stale) {
|
|
216
|
+
if (!Array.isArray(ids) || ids.length === 0) return;
|
|
217
|
+
const validIds = ids.filter((id) => typeof id === "number" && Number.isInteger(id) && id > 0).slice(0, 500);
|
|
218
|
+
if (validIds.length === 0) return;
|
|
219
|
+
const placeholders = validIds.map(() => "?").join(",");
|
|
220
|
+
db.run(
|
|
221
|
+
`UPDATE observations SET is_stale = ? WHERE id IN (${placeholders})`,
|
|
222
|
+
[stale ? 1 : 0, ...validIds]
|
|
223
|
+
);
|
|
224
|
+
}
|
|
144
225
|
export {
|
|
145
226
|
getObservationsByIds,
|
|
146
227
|
getProjectStats,
|
|
228
|
+
getStaleObservations,
|
|
147
229
|
getTimeline,
|
|
230
|
+
markObservationsStale,
|
|
148
231
|
searchObservationsFTS,
|
|
232
|
+
searchObservationsFTSWithRank,
|
|
149
233
|
searchObservationsLIKE,
|
|
150
234
|
searchSummariesFiltered
|
|
151
235
|
};
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { createRequire } from 'module';const require = createRequire(import.meta.url);
|
|
2
2
|
|
|
3
3
|
// src/services/sqlite/Summaries.ts
|
|
4
|
+
function escapeLikePattern(input) {
|
|
5
|
+
return input.replace(/[%_\\]/g, "\\$&");
|
|
6
|
+
}
|
|
4
7
|
function createSummary(db, sessionId, project, request, investigated, learned, completed, nextSteps, notes) {
|
|
5
8
|
const now = /* @__PURE__ */ new Date();
|
|
6
9
|
const result = db.run(
|
|
@@ -22,12 +25,12 @@ function getSummariesByProject(db, project, limit = 50) {
|
|
|
22
25
|
return query.all(project, limit);
|
|
23
26
|
}
|
|
24
27
|
function searchSummaries(db, searchTerm, project) {
|
|
25
|
-
const sql = project ? `SELECT * FROM summaries
|
|
26
|
-
WHERE project = ? AND (request LIKE ? OR learned LIKE ? OR completed LIKE ? OR notes LIKE ?)
|
|
27
|
-
ORDER BY created_at_epoch DESC` : `SELECT * FROM summaries
|
|
28
|
-
WHERE request LIKE ? OR learned LIKE ? OR completed LIKE ? OR notes LIKE ?
|
|
28
|
+
const sql = project ? `SELECT * FROM summaries
|
|
29
|
+
WHERE project = ? AND (request LIKE ? ESCAPE '\\' OR learned LIKE ? ESCAPE '\\' OR completed LIKE ? ESCAPE '\\' OR notes LIKE ? ESCAPE '\\')
|
|
30
|
+
ORDER BY created_at_epoch DESC` : `SELECT * FROM summaries
|
|
31
|
+
WHERE request LIKE ? ESCAPE '\\' OR learned LIKE ? ESCAPE '\\' OR completed LIKE ? ESCAPE '\\' OR notes LIKE ? ESCAPE '\\'
|
|
29
32
|
ORDER BY created_at_epoch DESC`;
|
|
30
|
-
const pattern = `%${searchTerm}%`;
|
|
33
|
+
const pattern = `%${escapeLikePattern(searchTerm)}%`;
|
|
31
34
|
const query = db.query(sql);
|
|
32
35
|
if (project) {
|
|
33
36
|
return query.all(project, pattern, pattern, pattern, pattern);
|