kiro-memory 1.7.0 → 1.8.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 +26 -7
- package/package.json +3 -5
- package/plugin/dist/cli/contextkit.js +177 -65
- package/plugin/dist/hooks/agentSpawn.js +177 -65
- package/plugin/dist/hooks/kiro-hooks.js +177 -65
- package/plugin/dist/hooks/postToolUse.js +415 -106
- package/plugin/dist/hooks/stop.js +213 -74
- package/plugin/dist/hooks/userPromptSubmit.js +177 -65
- package/plugin/dist/index.js +177 -65
- package/plugin/dist/sdk/index.js +177 -65
- package/plugin/dist/servers/mcp-server.js +28 -0
- package/plugin/dist/services/search/HybridSearch.js +66 -40
- package/plugin/dist/services/search/index.js +66 -40
- package/plugin/dist/services/sqlite/Database.js +23 -0
- package/plugin/dist/services/sqlite/Observations.js +48 -36
- package/plugin/dist/services/sqlite/Search.js +17 -4
- package/plugin/dist/services/sqlite/index.js +88 -40
- package/plugin/dist/viewer.html +5 -0
- package/plugin/dist/viewer.js +529 -250
- package/plugin/dist/worker-service.js +266 -47
package/plugin/dist/sdk/index.js
CHANGED
|
@@ -23,19 +23,28 @@ __export(Observations_exports, {
|
|
|
23
23
|
deleteObservation: () => deleteObservation,
|
|
24
24
|
getObservationsByProject: () => getObservationsByProject,
|
|
25
25
|
getObservationsBySession: () => getObservationsBySession,
|
|
26
|
+
isDuplicateObservation: () => isDuplicateObservation,
|
|
26
27
|
searchObservations: () => searchObservations,
|
|
27
28
|
updateLastAccessed: () => updateLastAccessed
|
|
28
29
|
});
|
|
29
30
|
function escapeLikePattern(input) {
|
|
30
31
|
return input.replace(/[%_\\]/g, "\\$&");
|
|
31
32
|
}
|
|
32
|
-
function
|
|
33
|
+
function isDuplicateObservation(db, contentHash, windowMs = 3e4) {
|
|
34
|
+
if (!contentHash) return false;
|
|
35
|
+
const threshold = Date.now() - windowMs;
|
|
36
|
+
const result = db.query(
|
|
37
|
+
"SELECT id FROM observations WHERE content_hash = ? AND created_at_epoch > ? LIMIT 1"
|
|
38
|
+
).get(contentHash, threshold);
|
|
39
|
+
return !!result;
|
|
40
|
+
}
|
|
41
|
+
function createObservation(db, memorySessionId, project, type, title, subtitle, text, narrative, facts, concepts, filesRead, filesModified, promptNumber, contentHash = null, discoveryTokens = 0) {
|
|
33
42
|
const now = /* @__PURE__ */ new Date();
|
|
34
43
|
const result = db.run(
|
|
35
|
-
`INSERT INTO observations
|
|
36
|
-
(memory_session_id, project, type, title, subtitle, text, narrative, facts, concepts, files_read, files_modified, prompt_number, created_at, created_at_epoch)
|
|
37
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
38
|
-
[memorySessionId, project, type, title, subtitle, text, narrative, facts, concepts, filesRead, filesModified, promptNumber, now.toISOString(), now.getTime()]
|
|
44
|
+
`INSERT INTO observations
|
|
45
|
+
(memory_session_id, project, type, title, subtitle, text, narrative, facts, concepts, files_read, files_modified, prompt_number, created_at, created_at_epoch, content_hash, discovery_tokens)
|
|
46
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
47
|
+
[memorySessionId, project, type, title, subtitle, text, narrative, facts, concepts, filesRead, filesModified, promptNumber, now.toISOString(), now.getTime(), contentHash, discoveryTokens]
|
|
39
48
|
);
|
|
40
49
|
return Number(result.lastInsertRowid);
|
|
41
50
|
}
|
|
@@ -91,39 +100,42 @@ function consolidateObservations(db, project, options = {}) {
|
|
|
91
100
|
if (groups.length === 0) return { merged: 0, removed: 0 };
|
|
92
101
|
let totalMerged = 0;
|
|
93
102
|
let totalRemoved = 0;
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const keeper = observations[0];
|
|
107
|
-
const others = observations.slice(1);
|
|
108
|
-
const uniqueTexts = /* @__PURE__ */ new Set();
|
|
109
|
-
if (keeper.text) uniqueTexts.add(keeper.text);
|
|
110
|
-
for (const obs of others) {
|
|
111
|
-
if (obs.text && !uniqueTexts.has(obs.text)) {
|
|
112
|
-
uniqueTexts.add(obs.text);
|
|
103
|
+
const runConsolidation = db.transaction(() => {
|
|
104
|
+
for (const group of groups) {
|
|
105
|
+
const obsIds = group.ids.split(",").map(Number);
|
|
106
|
+
const placeholders = obsIds.map(() => "?").join(",");
|
|
107
|
+
const observations = db.query(
|
|
108
|
+
`SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC`
|
|
109
|
+
).all(...obsIds);
|
|
110
|
+
if (observations.length < minGroupSize) continue;
|
|
111
|
+
if (options.dryRun) {
|
|
112
|
+
totalMerged += 1;
|
|
113
|
+
totalRemoved += observations.length - 1;
|
|
114
|
+
continue;
|
|
113
115
|
}
|
|
116
|
+
const keeper = observations[0];
|
|
117
|
+
const others = observations.slice(1);
|
|
118
|
+
const uniqueTexts = /* @__PURE__ */ new Set();
|
|
119
|
+
if (keeper.text) uniqueTexts.add(keeper.text);
|
|
120
|
+
for (const obs of others) {
|
|
121
|
+
if (obs.text && !uniqueTexts.has(obs.text)) {
|
|
122
|
+
uniqueTexts.add(obs.text);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const consolidatedText = Array.from(uniqueTexts).join("\n---\n").substring(0, 1e5);
|
|
126
|
+
db.run(
|
|
127
|
+
"UPDATE observations SET text = ?, title = ? WHERE id = ?",
|
|
128
|
+
[consolidatedText, `[consolidato x${observations.length}] ${keeper.title}`, keeper.id]
|
|
129
|
+
);
|
|
130
|
+
const removeIds = others.map((o) => o.id);
|
|
131
|
+
const removePlaceholders = removeIds.map(() => "?").join(",");
|
|
132
|
+
db.run(`DELETE FROM observations WHERE id IN (${removePlaceholders})`, removeIds);
|
|
133
|
+
db.run(`DELETE FROM observation_embeddings WHERE observation_id IN (${removePlaceholders})`, removeIds);
|
|
134
|
+
totalMerged += 1;
|
|
135
|
+
totalRemoved += removeIds.length;
|
|
114
136
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
"UPDATE observations SET text = ?, title = ? WHERE id = ?",
|
|
118
|
-
[consolidatedText, `[consolidato x${observations.length}] ${keeper.title}`, keeper.id]
|
|
119
|
-
);
|
|
120
|
-
const removeIds = others.map((o) => o.id);
|
|
121
|
-
const removePlaceholders = removeIds.map(() => "?").join(",");
|
|
122
|
-
db.run(`DELETE FROM observations WHERE id IN (${removePlaceholders})`, removeIds);
|
|
123
|
-
db.run(`DELETE FROM observation_embeddings WHERE observation_id IN (${removePlaceholders})`, removeIds);
|
|
124
|
-
totalMerged += 1;
|
|
125
|
-
totalRemoved += removeIds.length;
|
|
126
|
-
}
|
|
137
|
+
});
|
|
138
|
+
runConsolidation();
|
|
127
139
|
return { merged: totalMerged, removed: totalRemoved };
|
|
128
140
|
}
|
|
129
141
|
var init_Observations = __esm({
|
|
@@ -181,7 +193,7 @@ function searchObservationsFTS(db, query, filters = {}) {
|
|
|
181
193
|
sql += " AND o.created_at_epoch <= ?";
|
|
182
194
|
params.push(filters.dateEnd);
|
|
183
195
|
}
|
|
184
|
-
sql +=
|
|
196
|
+
sql += ` ORDER BY bm25(observations_fts, ${BM25_WEIGHTS}) LIMIT ?`;
|
|
185
197
|
params.push(limit);
|
|
186
198
|
const stmt = db.query(sql);
|
|
187
199
|
return stmt.all(...params);
|
|
@@ -195,7 +207,7 @@ function searchObservationsFTSWithRank(db, query, filters = {}) {
|
|
|
195
207
|
const safeQuery = sanitizeFTS5Query(query);
|
|
196
208
|
if (!safeQuery) return [];
|
|
197
209
|
let sql = `
|
|
198
|
-
SELECT o.*,
|
|
210
|
+
SELECT o.*, bm25(observations_fts, ${BM25_WEIGHTS}) as fts5_rank FROM observations o
|
|
199
211
|
JOIN observations_fts fts ON o.id = fts.rowid
|
|
200
212
|
WHERE observations_fts MATCH ?
|
|
201
213
|
`;
|
|
@@ -216,7 +228,7 @@ function searchObservationsFTSWithRank(db, query, filters = {}) {
|
|
|
216
228
|
sql += " AND o.created_at_epoch <= ?";
|
|
217
229
|
params.push(filters.dateEnd);
|
|
218
230
|
}
|
|
219
|
-
sql +=
|
|
231
|
+
sql += ` ORDER BY bm25(observations_fts, ${BM25_WEIGHTS}) LIMIT ?`;
|
|
220
232
|
params.push(limit);
|
|
221
233
|
const stmt = db.query(sql);
|
|
222
234
|
return stmt.all(...params);
|
|
@@ -320,11 +332,23 @@ function getProjectStats(db, project) {
|
|
|
320
332
|
const sumStmt = db.query("SELECT COUNT(*) as count FROM summaries WHERE project = ?");
|
|
321
333
|
const sesStmt = db.query("SELECT COUNT(*) as count FROM sessions WHERE project = ?");
|
|
322
334
|
const prmStmt = db.query("SELECT COUNT(*) as count FROM prompts WHERE project = ?");
|
|
335
|
+
const discoveryStmt = db.query(
|
|
336
|
+
"SELECT COALESCE(SUM(discovery_tokens), 0) as total FROM observations WHERE project = ?"
|
|
337
|
+
);
|
|
338
|
+
const discoveryTokens = discoveryStmt.get(project)?.total || 0;
|
|
339
|
+
const readStmt = db.query(
|
|
340
|
+
`SELECT COALESCE(SUM(
|
|
341
|
+
CAST((LENGTH(COALESCE(title, '')) + LENGTH(COALESCE(narrative, ''))) / 4 AS INTEGER)
|
|
342
|
+
), 0) as total FROM observations WHERE project = ?`
|
|
343
|
+
);
|
|
344
|
+
const readTokens = readStmt.get(project)?.total || 0;
|
|
345
|
+
const savings = Math.max(0, discoveryTokens - readTokens);
|
|
323
346
|
return {
|
|
324
347
|
observations: obsStmt.get(project)?.count || 0,
|
|
325
348
|
summaries: sumStmt.get(project)?.count || 0,
|
|
326
349
|
sessions: sesStmt.get(project)?.count || 0,
|
|
327
|
-
prompts: prmStmt.get(project)?.count || 0
|
|
350
|
+
prompts: prmStmt.get(project)?.count || 0,
|
|
351
|
+
tokenEconomics: { discoveryTokens, readTokens, savings }
|
|
328
352
|
};
|
|
329
353
|
}
|
|
330
354
|
function getStaleObservations(db, project) {
|
|
@@ -366,9 +390,11 @@ function markObservationsStale(db, ids, stale) {
|
|
|
366
390
|
[stale ? 1 : 0, ...validIds]
|
|
367
391
|
);
|
|
368
392
|
}
|
|
393
|
+
var BM25_WEIGHTS;
|
|
369
394
|
var init_Search = __esm({
|
|
370
395
|
"src/services/sqlite/Search.ts"() {
|
|
371
396
|
"use strict";
|
|
397
|
+
BM25_WEIGHTS = "10.0, 1.0, 5.0, 3.0";
|
|
372
398
|
}
|
|
373
399
|
});
|
|
374
400
|
|
|
@@ -948,6 +974,29 @@ var MigrationRunner = class {
|
|
|
948
974
|
db.run("CREATE INDEX IF NOT EXISTS idx_checkpoints_project ON checkpoints(project)");
|
|
949
975
|
db.run("CREATE INDEX IF NOT EXISTS idx_checkpoints_epoch ON checkpoints(created_at_epoch)");
|
|
950
976
|
}
|
|
977
|
+
},
|
|
978
|
+
{
|
|
979
|
+
version: 7,
|
|
980
|
+
up: (db) => {
|
|
981
|
+
db.run("ALTER TABLE observations ADD COLUMN content_hash TEXT");
|
|
982
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_observations_hash ON observations(content_hash)");
|
|
983
|
+
}
|
|
984
|
+
},
|
|
985
|
+
{
|
|
986
|
+
version: 8,
|
|
987
|
+
up: (db) => {
|
|
988
|
+
db.run("ALTER TABLE observations ADD COLUMN discovery_tokens INTEGER DEFAULT 0");
|
|
989
|
+
db.run("ALTER TABLE summaries ADD COLUMN discovery_tokens INTEGER DEFAULT 0");
|
|
990
|
+
}
|
|
991
|
+
},
|
|
992
|
+
{
|
|
993
|
+
version: 9,
|
|
994
|
+
up: (db) => {
|
|
995
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_observations_project_epoch ON observations(project, created_at_epoch DESC)");
|
|
996
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_observations_project_type ON observations(project, type)");
|
|
997
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_summaries_project_epoch ON summaries(project, created_at_epoch DESC)");
|
|
998
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_prompts_project_epoch ON prompts(project, created_at_epoch DESC)");
|
|
999
|
+
}
|
|
951
1000
|
}
|
|
952
1001
|
];
|
|
953
1002
|
}
|
|
@@ -1192,6 +1241,7 @@ init_Search();
|
|
|
1192
1241
|
|
|
1193
1242
|
// src/sdk/index.ts
|
|
1194
1243
|
init_Observations();
|
|
1244
|
+
import { createHash } from "crypto";
|
|
1195
1245
|
init_Search();
|
|
1196
1246
|
|
|
1197
1247
|
// src/services/search/EmbeddingService.ts
|
|
@@ -1750,31 +1800,71 @@ var KiroMemorySDK = class {
|
|
|
1750
1800
|
logger.debug("SDK", `Embedding generation fallita per obs ${observationId}: ${error}`);
|
|
1751
1801
|
}
|
|
1752
1802
|
}
|
|
1803
|
+
/**
|
|
1804
|
+
* Genera content hash SHA256 per deduplicazione basata su contenuto.
|
|
1805
|
+
* Usa (project + type + title + narrative) come tupla di identità semantica.
|
|
1806
|
+
* NON include sessionId perché è unico ad ogni invocazione.
|
|
1807
|
+
*/
|
|
1808
|
+
generateContentHash(type, title, narrative) {
|
|
1809
|
+
const payload = `${this.project}|${type}|${title}|${narrative || ""}`;
|
|
1810
|
+
return createHash("sha256").update(payload).digest("hex");
|
|
1811
|
+
}
|
|
1812
|
+
/**
|
|
1813
|
+
* Finestre di deduplicazione per tipo (ms).
|
|
1814
|
+
* Tipi con molte ripetizioni hanno finestre più ampie.
|
|
1815
|
+
*/
|
|
1816
|
+
getDeduplicationWindow(type) {
|
|
1817
|
+
switch (type) {
|
|
1818
|
+
case "file-read":
|
|
1819
|
+
return 6e4;
|
|
1820
|
+
// 60s — letture frequenti sugli stessi file
|
|
1821
|
+
case "file-write":
|
|
1822
|
+
return 1e4;
|
|
1823
|
+
// 10s — scritture rapide consecutive
|
|
1824
|
+
case "command":
|
|
1825
|
+
return 3e4;
|
|
1826
|
+
// 30s — standard
|
|
1827
|
+
case "research":
|
|
1828
|
+
return 12e4;
|
|
1829
|
+
// 120s — web search e fetch ripetuti
|
|
1830
|
+
case "delegation":
|
|
1831
|
+
return 6e4;
|
|
1832
|
+
// 60s — delegazioni rapide
|
|
1833
|
+
default:
|
|
1834
|
+
return 3e4;
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1753
1837
|
/**
|
|
1754
1838
|
* Store a new observation
|
|
1755
1839
|
*/
|
|
1756
1840
|
async storeObservation(data) {
|
|
1757
1841
|
this.validateObservationInput(data);
|
|
1842
|
+
const sessionId = "sdk-" + Date.now();
|
|
1843
|
+
const contentHash = this.generateContentHash(data.type, data.title, data.narrative);
|
|
1844
|
+
const dedupWindow = this.getDeduplicationWindow(data.type);
|
|
1845
|
+
if (isDuplicateObservation(this.db.db, contentHash, dedupWindow)) {
|
|
1846
|
+
logger.debug("SDK", `Osservazione duplicata scartata (${data.type}, ${dedupWindow}ms): ${data.title}`);
|
|
1847
|
+
return -1;
|
|
1848
|
+
}
|
|
1849
|
+
const filesRead = data.filesRead || (data.type === "file-read" ? data.files : void 0);
|
|
1850
|
+
const filesModified = data.filesModified || (data.type === "file-write" ? data.files : void 0);
|
|
1851
|
+
const discoveryTokens = Math.ceil(data.content.length / 4);
|
|
1758
1852
|
const observationId = createObservation(
|
|
1759
1853
|
this.db.db,
|
|
1760
|
-
|
|
1854
|
+
sessionId,
|
|
1761
1855
|
this.project,
|
|
1762
1856
|
data.type,
|
|
1763
1857
|
data.title,
|
|
1764
|
-
null,
|
|
1765
|
-
// subtitle
|
|
1858
|
+
data.subtitle || null,
|
|
1766
1859
|
data.content,
|
|
1767
|
-
null,
|
|
1768
|
-
|
|
1769
|
-
null,
|
|
1770
|
-
// facts
|
|
1860
|
+
data.narrative || null,
|
|
1861
|
+
data.facts || null,
|
|
1771
1862
|
data.concepts?.join(", ") || null,
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
// prompt_number
|
|
1863
|
+
filesRead?.join(", ") || null,
|
|
1864
|
+
filesModified?.join(", ") || null,
|
|
1865
|
+
0,
|
|
1866
|
+
contentHash,
|
|
1867
|
+
discoveryTokens
|
|
1778
1868
|
);
|
|
1779
1869
|
this.generateEmbeddingAsync(observationId, data.title, data.content, data.concepts).catch(() => {
|
|
1780
1870
|
});
|
|
@@ -1817,9 +1907,16 @@ var KiroMemorySDK = class {
|
|
|
1817
1907
|
};
|
|
1818
1908
|
}
|
|
1819
1909
|
})();
|
|
1910
|
+
const sessionId = "sdk-" + Date.now();
|
|
1911
|
+
const contentHash = this.generateContentHash(data.type, data.title);
|
|
1912
|
+
if (isDuplicateObservation(this.db.db, contentHash)) {
|
|
1913
|
+
logger.debug("SDK", `Knowledge duplicata scartata: ${data.title}`);
|
|
1914
|
+
return -1;
|
|
1915
|
+
}
|
|
1916
|
+
const discoveryTokens = Math.ceil(data.content.length / 4);
|
|
1820
1917
|
const observationId = createObservation(
|
|
1821
1918
|
this.db.db,
|
|
1822
|
-
|
|
1919
|
+
sessionId,
|
|
1823
1920
|
data.project || this.project,
|
|
1824
1921
|
data.knowledgeType,
|
|
1825
1922
|
// type = knowledgeType
|
|
@@ -1833,9 +1930,12 @@ var KiroMemorySDK = class {
|
|
|
1833
1930
|
// facts = metadati JSON
|
|
1834
1931
|
data.concepts?.join(", ") || null,
|
|
1835
1932
|
data.files?.join(", ") || null,
|
|
1836
|
-
|
|
1837
|
-
|
|
1933
|
+
null,
|
|
1934
|
+
// filesModified: knowledge non modifica file
|
|
1935
|
+
0,
|
|
1838
1936
|
// prompt_number
|
|
1937
|
+
contentHash,
|
|
1938
|
+
discoveryTokens
|
|
1839
1939
|
);
|
|
1840
1940
|
this.generateEmbeddingAsync(observationId, data.title, data.content, data.concepts).catch(() => {
|
|
1841
1941
|
});
|
|
@@ -1851,11 +1951,11 @@ var KiroMemorySDK = class {
|
|
|
1851
1951
|
"sdk-" + Date.now(),
|
|
1852
1952
|
this.project,
|
|
1853
1953
|
data.request || null,
|
|
1854
|
-
null,
|
|
1954
|
+
data.investigated || null,
|
|
1855
1955
|
data.learned || null,
|
|
1856
1956
|
data.completed || null,
|
|
1857
1957
|
data.nextSteps || null,
|
|
1858
|
-
null
|
|
1958
|
+
data.notes || null
|
|
1859
1959
|
);
|
|
1860
1960
|
}
|
|
1861
1961
|
/**
|
|
@@ -2039,7 +2139,14 @@ var KiroMemorySDK = class {
|
|
|
2039
2139
|
}));
|
|
2040
2140
|
} else {
|
|
2041
2141
|
const observations = getObservationsByProject(this.db.db, this.project, 30);
|
|
2042
|
-
|
|
2142
|
+
const knowledgeTypes = new Set(KNOWLEDGE_TYPES);
|
|
2143
|
+
const knowledgeObs = [];
|
|
2144
|
+
const normalObs = [];
|
|
2145
|
+
for (const obs of observations) {
|
|
2146
|
+
if (knowledgeTypes.has(obs.type)) knowledgeObs.push(obs);
|
|
2147
|
+
else normalObs.push(obs);
|
|
2148
|
+
}
|
|
2149
|
+
const scoreObs = (obs) => {
|
|
2043
2150
|
const signals = {
|
|
2044
2151
|
semantic: 0,
|
|
2045
2152
|
fts5: 0,
|
|
@@ -2047,7 +2154,6 @@ var KiroMemorySDK = class {
|
|
|
2047
2154
|
projectMatch: projectMatchScore(obs.project, this.project)
|
|
2048
2155
|
};
|
|
2049
2156
|
const baseScore = computeCompositeScore(signals, CONTEXT_WEIGHTS);
|
|
2050
|
-
const boostedScore = Math.min(1, baseScore * knowledgeTypeBoost(obs.type));
|
|
2051
2157
|
return {
|
|
2052
2158
|
id: obs.id,
|
|
2053
2159
|
title: obs.title,
|
|
@@ -2056,17 +2162,23 @@ var KiroMemorySDK = class {
|
|
|
2056
2162
|
project: obs.project,
|
|
2057
2163
|
created_at: obs.created_at,
|
|
2058
2164
|
created_at_epoch: obs.created_at_epoch,
|
|
2059
|
-
score:
|
|
2165
|
+
score: Math.min(1, baseScore * knowledgeTypeBoost(obs.type)),
|
|
2060
2166
|
signals
|
|
2061
2167
|
};
|
|
2062
|
-
}
|
|
2063
|
-
|
|
2168
|
+
};
|
|
2169
|
+
const scoredKnowledge = knowledgeObs.map(scoreObs).sort((a, b) => b.score - a.score);
|
|
2170
|
+
const scoredNormal = normalObs.map(scoreObs).sort((a, b) => b.score - a.score);
|
|
2171
|
+
items = [...scoredKnowledge, ...scoredNormal];
|
|
2064
2172
|
}
|
|
2065
2173
|
let tokensUsed = 0;
|
|
2174
|
+
const budgetItems = [];
|
|
2066
2175
|
for (const item of items) {
|
|
2067
|
-
|
|
2068
|
-
if (tokensUsed > tokenBudget) break;
|
|
2176
|
+
const itemTokens = Math.ceil((item.title.length + item.content.length) / 4);
|
|
2177
|
+
if (tokensUsed + itemTokens > tokenBudget) break;
|
|
2178
|
+
tokensUsed += itemTokens;
|
|
2179
|
+
budgetItems.push(item);
|
|
2069
2180
|
}
|
|
2181
|
+
items = budgetItems;
|
|
2070
2182
|
return {
|
|
2071
2183
|
project: this.project,
|
|
2072
2184
|
items,
|
|
@@ -145,6 +145,21 @@ var TOOLS = [
|
|
|
145
145
|
required: []
|
|
146
146
|
}
|
|
147
147
|
},
|
|
148
|
+
{
|
|
149
|
+
name: "save_memory",
|
|
150
|
+
description: "Save a memory/observation manually. Use to persist important information, learnings, decisions, or context that should be remembered across sessions.",
|
|
151
|
+
inputSchema: {
|
|
152
|
+
type: "object",
|
|
153
|
+
properties: {
|
|
154
|
+
project: { type: "string", description: "Project name (required)" },
|
|
155
|
+
title: { type: "string", description: "Short descriptive title for the memory" },
|
|
156
|
+
content: { type: "string", description: "Full content of the memory to save" },
|
|
157
|
+
type: { type: "string", description: "Observation type: research, file-write, command, etc. (default: research)" },
|
|
158
|
+
concepts: { type: "array", items: { type: "string" }, description: "Related concepts/tags (optional)" }
|
|
159
|
+
},
|
|
160
|
+
required: ["project", "title", "content"]
|
|
161
|
+
}
|
|
162
|
+
},
|
|
148
163
|
{
|
|
149
164
|
name: "generate_report",
|
|
150
165
|
description: "Generate an activity report for a project. Returns a markdown summary with observations, sessions, learnings, completed tasks, and file hotspots for the specified time period.",
|
|
@@ -371,6 +386,19 @@ _Tip: Run \`kiro-memory embeddings backfill\` to generate missing embeddings._
|
|
|
371
386
|
_Checkpoint created: ${checkpoint.created_at}_`);
|
|
372
387
|
return parts.join("\n");
|
|
373
388
|
},
|
|
389
|
+
async save_memory(args) {
|
|
390
|
+
const result = await callWorkerPOST("/api/memory/save", {
|
|
391
|
+
project: args.project,
|
|
392
|
+
title: args.title,
|
|
393
|
+
content: args.content,
|
|
394
|
+
type: args.type || "research",
|
|
395
|
+
concepts: args.concepts
|
|
396
|
+
});
|
|
397
|
+
return `Memory saved successfully.
|
|
398
|
+
- **ID**: ${result.id}
|
|
399
|
+
- **Project**: ${args.project}
|
|
400
|
+
- **Title**: ${args.title}`;
|
|
401
|
+
},
|
|
374
402
|
async generate_report(args) {
|
|
375
403
|
const project = args.project || process.env.KIRO_MEMORY_PROJECT || "";
|
|
376
404
|
const period = args.period === "monthly" ? "monthly" : "weekly";
|
|
@@ -58,7 +58,7 @@ function searchObservationsFTS(db, query, filters = {}) {
|
|
|
58
58
|
sql += " AND o.created_at_epoch <= ?";
|
|
59
59
|
params.push(filters.dateEnd);
|
|
60
60
|
}
|
|
61
|
-
sql +=
|
|
61
|
+
sql += ` ORDER BY bm25(observations_fts, ${BM25_WEIGHTS}) LIMIT ?`;
|
|
62
62
|
params.push(limit);
|
|
63
63
|
const stmt = db.query(sql);
|
|
64
64
|
return stmt.all(...params);
|
|
@@ -72,7 +72,7 @@ function searchObservationsFTSWithRank(db, query, filters = {}) {
|
|
|
72
72
|
const safeQuery = sanitizeFTS5Query(query);
|
|
73
73
|
if (!safeQuery) return [];
|
|
74
74
|
let sql = `
|
|
75
|
-
SELECT o.*,
|
|
75
|
+
SELECT o.*, bm25(observations_fts, ${BM25_WEIGHTS}) as fts5_rank FROM observations o
|
|
76
76
|
JOIN observations_fts fts ON o.id = fts.rowid
|
|
77
77
|
WHERE observations_fts MATCH ?
|
|
78
78
|
`;
|
|
@@ -93,7 +93,7 @@ function searchObservationsFTSWithRank(db, query, filters = {}) {
|
|
|
93
93
|
sql += " AND o.created_at_epoch <= ?";
|
|
94
94
|
params.push(filters.dateEnd);
|
|
95
95
|
}
|
|
96
|
-
sql +=
|
|
96
|
+
sql += ` ORDER BY bm25(observations_fts, ${BM25_WEIGHTS}) LIMIT ?`;
|
|
97
97
|
params.push(limit);
|
|
98
98
|
const stmt = db.query(sql);
|
|
99
99
|
return stmt.all(...params);
|
|
@@ -197,11 +197,23 @@ function getProjectStats(db, project) {
|
|
|
197
197
|
const sumStmt = db.query("SELECT COUNT(*) as count FROM summaries WHERE project = ?");
|
|
198
198
|
const sesStmt = db.query("SELECT COUNT(*) as count FROM sessions WHERE project = ?");
|
|
199
199
|
const prmStmt = db.query("SELECT COUNT(*) as count FROM prompts WHERE project = ?");
|
|
200
|
+
const discoveryStmt = db.query(
|
|
201
|
+
"SELECT COALESCE(SUM(discovery_tokens), 0) as total FROM observations WHERE project = ?"
|
|
202
|
+
);
|
|
203
|
+
const discoveryTokens = discoveryStmt.get(project)?.total || 0;
|
|
204
|
+
const readStmt = db.query(
|
|
205
|
+
`SELECT COALESCE(SUM(
|
|
206
|
+
CAST((LENGTH(COALESCE(title, '')) + LENGTH(COALESCE(narrative, ''))) / 4 AS INTEGER)
|
|
207
|
+
), 0) as total FROM observations WHERE project = ?`
|
|
208
|
+
);
|
|
209
|
+
const readTokens = readStmt.get(project)?.total || 0;
|
|
210
|
+
const savings = Math.max(0, discoveryTokens - readTokens);
|
|
200
211
|
return {
|
|
201
212
|
observations: obsStmt.get(project)?.count || 0,
|
|
202
213
|
summaries: sumStmt.get(project)?.count || 0,
|
|
203
214
|
sessions: sesStmt.get(project)?.count || 0,
|
|
204
|
-
prompts: prmStmt.get(project)?.count || 0
|
|
215
|
+
prompts: prmStmt.get(project)?.count || 0,
|
|
216
|
+
tokenEconomics: { discoveryTokens, readTokens, savings }
|
|
205
217
|
};
|
|
206
218
|
}
|
|
207
219
|
function getStaleObservations(db, project) {
|
|
@@ -243,9 +255,11 @@ function markObservationsStale(db, ids, stale) {
|
|
|
243
255
|
[stale ? 1 : 0, ...validIds]
|
|
244
256
|
);
|
|
245
257
|
}
|
|
258
|
+
var BM25_WEIGHTS;
|
|
246
259
|
var init_Search = __esm({
|
|
247
260
|
"src/services/sqlite/Search.ts"() {
|
|
248
261
|
"use strict";
|
|
262
|
+
BM25_WEIGHTS = "10.0, 1.0, 5.0, 3.0";
|
|
249
263
|
}
|
|
250
264
|
});
|
|
251
265
|
|
|
@@ -257,19 +271,28 @@ __export(Observations_exports, {
|
|
|
257
271
|
deleteObservation: () => deleteObservation,
|
|
258
272
|
getObservationsByProject: () => getObservationsByProject,
|
|
259
273
|
getObservationsBySession: () => getObservationsBySession,
|
|
274
|
+
isDuplicateObservation: () => isDuplicateObservation,
|
|
260
275
|
searchObservations: () => searchObservations,
|
|
261
276
|
updateLastAccessed: () => updateLastAccessed
|
|
262
277
|
});
|
|
263
278
|
function escapeLikePattern2(input) {
|
|
264
279
|
return input.replace(/[%_\\]/g, "\\$&");
|
|
265
280
|
}
|
|
266
|
-
function
|
|
281
|
+
function isDuplicateObservation(db, contentHash, windowMs = 3e4) {
|
|
282
|
+
if (!contentHash) return false;
|
|
283
|
+
const threshold = Date.now() - windowMs;
|
|
284
|
+
const result = db.query(
|
|
285
|
+
"SELECT id FROM observations WHERE content_hash = ? AND created_at_epoch > ? LIMIT 1"
|
|
286
|
+
).get(contentHash, threshold);
|
|
287
|
+
return !!result;
|
|
288
|
+
}
|
|
289
|
+
function createObservation(db, memorySessionId, project, type, title, subtitle, text, narrative, facts, concepts, filesRead, filesModified, promptNumber, contentHash = null, discoveryTokens = 0) {
|
|
267
290
|
const now = /* @__PURE__ */ new Date();
|
|
268
291
|
const result = db.run(
|
|
269
|
-
`INSERT INTO observations
|
|
270
|
-
(memory_session_id, project, type, title, subtitle, text, narrative, facts, concepts, files_read, files_modified, prompt_number, created_at, created_at_epoch)
|
|
271
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
272
|
-
[memorySessionId, project, type, title, subtitle, text, narrative, facts, concepts, filesRead, filesModified, promptNumber, now.toISOString(), now.getTime()]
|
|
292
|
+
`INSERT INTO observations
|
|
293
|
+
(memory_session_id, project, type, title, subtitle, text, narrative, facts, concepts, files_read, files_modified, prompt_number, created_at, created_at_epoch, content_hash, discovery_tokens)
|
|
294
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
295
|
+
[memorySessionId, project, type, title, subtitle, text, narrative, facts, concepts, filesRead, filesModified, promptNumber, now.toISOString(), now.getTime(), contentHash, discoveryTokens]
|
|
273
296
|
);
|
|
274
297
|
return Number(result.lastInsertRowid);
|
|
275
298
|
}
|
|
@@ -325,39 +348,42 @@ function consolidateObservations(db, project, options = {}) {
|
|
|
325
348
|
if (groups.length === 0) return { merged: 0, removed: 0 };
|
|
326
349
|
let totalMerged = 0;
|
|
327
350
|
let totalRemoved = 0;
|
|
328
|
-
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
const keeper = observations[0];
|
|
341
|
-
const others = observations.slice(1);
|
|
342
|
-
const uniqueTexts = /* @__PURE__ */ new Set();
|
|
343
|
-
if (keeper.text) uniqueTexts.add(keeper.text);
|
|
344
|
-
for (const obs of others) {
|
|
345
|
-
if (obs.text && !uniqueTexts.has(obs.text)) {
|
|
346
|
-
uniqueTexts.add(obs.text);
|
|
351
|
+
const runConsolidation = db.transaction(() => {
|
|
352
|
+
for (const group of groups) {
|
|
353
|
+
const obsIds = group.ids.split(",").map(Number);
|
|
354
|
+
const placeholders = obsIds.map(() => "?").join(",");
|
|
355
|
+
const observations = db.query(
|
|
356
|
+
`SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC`
|
|
357
|
+
).all(...obsIds);
|
|
358
|
+
if (observations.length < minGroupSize) continue;
|
|
359
|
+
if (options.dryRun) {
|
|
360
|
+
totalMerged += 1;
|
|
361
|
+
totalRemoved += observations.length - 1;
|
|
362
|
+
continue;
|
|
347
363
|
}
|
|
364
|
+
const keeper = observations[0];
|
|
365
|
+
const others = observations.slice(1);
|
|
366
|
+
const uniqueTexts = /* @__PURE__ */ new Set();
|
|
367
|
+
if (keeper.text) uniqueTexts.add(keeper.text);
|
|
368
|
+
for (const obs of others) {
|
|
369
|
+
if (obs.text && !uniqueTexts.has(obs.text)) {
|
|
370
|
+
uniqueTexts.add(obs.text);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
const consolidatedText = Array.from(uniqueTexts).join("\n---\n").substring(0, 1e5);
|
|
374
|
+
db.run(
|
|
375
|
+
"UPDATE observations SET text = ?, title = ? WHERE id = ?",
|
|
376
|
+
[consolidatedText, `[consolidato x${observations.length}] ${keeper.title}`, keeper.id]
|
|
377
|
+
);
|
|
378
|
+
const removeIds = others.map((o) => o.id);
|
|
379
|
+
const removePlaceholders = removeIds.map(() => "?").join(",");
|
|
380
|
+
db.run(`DELETE FROM observations WHERE id IN (${removePlaceholders})`, removeIds);
|
|
381
|
+
db.run(`DELETE FROM observation_embeddings WHERE observation_id IN (${removePlaceholders})`, removeIds);
|
|
382
|
+
totalMerged += 1;
|
|
383
|
+
totalRemoved += removeIds.length;
|
|
348
384
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
"UPDATE observations SET text = ?, title = ? WHERE id = ?",
|
|
352
|
-
[consolidatedText, `[consolidato x${observations.length}] ${keeper.title}`, keeper.id]
|
|
353
|
-
);
|
|
354
|
-
const removeIds = others.map((o) => o.id);
|
|
355
|
-
const removePlaceholders = removeIds.map(() => "?").join(",");
|
|
356
|
-
db.run(`DELETE FROM observations WHERE id IN (${removePlaceholders})`, removeIds);
|
|
357
|
-
db.run(`DELETE FROM observation_embeddings WHERE observation_id IN (${removePlaceholders})`, removeIds);
|
|
358
|
-
totalMerged += 1;
|
|
359
|
-
totalRemoved += removeIds.length;
|
|
360
|
-
}
|
|
385
|
+
});
|
|
386
|
+
runConsolidation();
|
|
361
387
|
return { merged: totalMerged, removed: totalRemoved };
|
|
362
388
|
}
|
|
363
389
|
var init_Observations = __esm({
|