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
|
@@ -16,82 +16,20 @@ var __export = (target, all) => {
|
|
|
16
16
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
// src/services/sqlite/Sessions.ts
|
|
20
|
-
var Sessions_exports = {};
|
|
21
|
-
__export(Sessions_exports, {
|
|
22
|
-
completeSession: () => completeSession,
|
|
23
|
-
createSession: () => createSession,
|
|
24
|
-
failSession: () => failSession,
|
|
25
|
-
getActiveSessions: () => getActiveSessions,
|
|
26
|
-
getSessionByContentId: () => getSessionByContentId,
|
|
27
|
-
getSessionById: () => getSessionById,
|
|
28
|
-
getSessionsByProject: () => getSessionsByProject,
|
|
29
|
-
updateSessionMemoryId: () => updateSessionMemoryId
|
|
30
|
-
});
|
|
31
|
-
function createSession(db, contentSessionId, project, userPrompt) {
|
|
32
|
-
const now = /* @__PURE__ */ new Date();
|
|
33
|
-
const result = db.run(
|
|
34
|
-
`INSERT INTO sessions (content_session_id, project, user_prompt, status, started_at, started_at_epoch)
|
|
35
|
-
VALUES (?, ?, ?, 'active', ?, ?)`,
|
|
36
|
-
[contentSessionId, project, userPrompt, now.toISOString(), now.getTime()]
|
|
37
|
-
);
|
|
38
|
-
return Number(result.lastInsertRowid);
|
|
39
|
-
}
|
|
40
|
-
function getSessionByContentId(db, contentSessionId) {
|
|
41
|
-
const query = db.query("SELECT * FROM sessions WHERE content_session_id = ?");
|
|
42
|
-
return query.get(contentSessionId);
|
|
43
|
-
}
|
|
44
|
-
function getSessionById(db, id) {
|
|
45
|
-
const query = db.query("SELECT * FROM sessions WHERE id = ?");
|
|
46
|
-
return query.get(id);
|
|
47
|
-
}
|
|
48
|
-
function updateSessionMemoryId(db, id, memorySessionId) {
|
|
49
|
-
db.run(
|
|
50
|
-
"UPDATE sessions SET memory_session_id = ? WHERE id = ?",
|
|
51
|
-
[memorySessionId, id]
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
function completeSession(db, id) {
|
|
55
|
-
const now = /* @__PURE__ */ new Date();
|
|
56
|
-
db.run(
|
|
57
|
-
`UPDATE sessions
|
|
58
|
-
SET status = 'completed', completed_at = ?, completed_at_epoch = ?
|
|
59
|
-
WHERE id = ?`,
|
|
60
|
-
[now.toISOString(), now.getTime(), id]
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
function failSession(db, id) {
|
|
64
|
-
const now = /* @__PURE__ */ new Date();
|
|
65
|
-
db.run(
|
|
66
|
-
`UPDATE sessions
|
|
67
|
-
SET status = 'failed', completed_at = ?, completed_at_epoch = ?
|
|
68
|
-
WHERE id = ?`,
|
|
69
|
-
[now.toISOString(), now.getTime(), id]
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
function getActiveSessions(db) {
|
|
73
|
-
const query = db.query("SELECT * FROM sessions WHERE status = 'active' ORDER BY started_at_epoch DESC");
|
|
74
|
-
return query.all();
|
|
75
|
-
}
|
|
76
|
-
function getSessionsByProject(db, project, limit = 100) {
|
|
77
|
-
const query = db.query("SELECT * FROM sessions WHERE project = ? ORDER BY started_at_epoch DESC LIMIT ?");
|
|
78
|
-
return query.all(project, limit);
|
|
79
|
-
}
|
|
80
|
-
var init_Sessions = __esm({
|
|
81
|
-
"src/services/sqlite/Sessions.ts"() {
|
|
82
|
-
"use strict";
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
|
|
86
19
|
// src/services/sqlite/Observations.ts
|
|
87
20
|
var Observations_exports = {};
|
|
88
21
|
__export(Observations_exports, {
|
|
22
|
+
consolidateObservations: () => consolidateObservations,
|
|
89
23
|
createObservation: () => createObservation,
|
|
90
24
|
deleteObservation: () => deleteObservation,
|
|
91
25
|
getObservationsByProject: () => getObservationsByProject,
|
|
92
26
|
getObservationsBySession: () => getObservationsBySession,
|
|
93
|
-
searchObservations: () => searchObservations
|
|
27
|
+
searchObservations: () => searchObservations,
|
|
28
|
+
updateLastAccessed: () => updateLastAccessed
|
|
94
29
|
});
|
|
30
|
+
function escapeLikePattern(input) {
|
|
31
|
+
return input.replace(/[%_\\]/g, "\\$&");
|
|
32
|
+
}
|
|
95
33
|
function createObservation(db, memorySessionId, project, type, title, subtitle, text, narrative, facts, concepts, filesRead, filesModified, promptNumber) {
|
|
96
34
|
const now = /* @__PURE__ */ new Date();
|
|
97
35
|
const result = db.run(
|
|
@@ -115,12 +53,12 @@ function getObservationsByProject(db, project, limit = 100) {
|
|
|
115
53
|
return query.all(project, limit);
|
|
116
54
|
}
|
|
117
55
|
function searchObservations(db, searchTerm, project) {
|
|
118
|
-
const sql = project ? `SELECT * FROM observations
|
|
119
|
-
WHERE project = ? AND (title LIKE ? OR text LIKE ? OR narrative LIKE ?)
|
|
120
|
-
ORDER BY created_at_epoch DESC` : `SELECT * FROM observations
|
|
121
|
-
WHERE title LIKE ? OR text LIKE ? OR narrative LIKE ?
|
|
56
|
+
const sql = project ? `SELECT * FROM observations
|
|
57
|
+
WHERE project = ? AND (title LIKE ? ESCAPE '\\' OR text LIKE ? ESCAPE '\\' OR narrative LIKE ? ESCAPE '\\')
|
|
58
|
+
ORDER BY created_at_epoch DESC` : `SELECT * FROM observations
|
|
59
|
+
WHERE title LIKE ? ESCAPE '\\' OR text LIKE ? ESCAPE '\\' OR narrative LIKE ? ESCAPE '\\'
|
|
122
60
|
ORDER BY created_at_epoch DESC`;
|
|
123
|
-
const pattern = `%${searchTerm}%`;
|
|
61
|
+
const pattern = `%${escapeLikePattern(searchTerm)}%`;
|
|
124
62
|
const query = db.query(sql);
|
|
125
63
|
if (project) {
|
|
126
64
|
return query.all(project, pattern, pattern, pattern);
|
|
@@ -130,105 +68,67 @@ function searchObservations(db, searchTerm, project) {
|
|
|
130
68
|
function deleteObservation(db, id) {
|
|
131
69
|
db.run("DELETE FROM observations WHERE id = ?", [id]);
|
|
132
70
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
createSummary: () => createSummary,
|
|
143
|
-
deleteSummary: () => deleteSummary,
|
|
144
|
-
getSummariesByProject: () => getSummariesByProject,
|
|
145
|
-
getSummaryBySession: () => getSummaryBySession,
|
|
146
|
-
searchSummaries: () => searchSummaries
|
|
147
|
-
});
|
|
148
|
-
function createSummary(db, sessionId, project, request, investigated, learned, completed, nextSteps, notes) {
|
|
149
|
-
const now = /* @__PURE__ */ new Date();
|
|
150
|
-
const result = db.run(
|
|
151
|
-
`INSERT INTO summaries
|
|
152
|
-
(session_id, project, request, investigated, learned, completed, next_steps, notes, created_at, created_at_epoch)
|
|
153
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
154
|
-
[sessionId, project, request, investigated, learned, completed, nextSteps, notes, now.toISOString(), now.getTime()]
|
|
155
|
-
);
|
|
156
|
-
return Number(result.lastInsertRowid);
|
|
157
|
-
}
|
|
158
|
-
function getSummaryBySession(db, sessionId) {
|
|
159
|
-
const query = db.query("SELECT * FROM summaries WHERE session_id = ? ORDER BY created_at_epoch DESC LIMIT 1");
|
|
160
|
-
return query.get(sessionId);
|
|
161
|
-
}
|
|
162
|
-
function getSummariesByProject(db, project, limit = 50) {
|
|
163
|
-
const query = db.query(
|
|
164
|
-
"SELECT * FROM summaries WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ?"
|
|
165
|
-
);
|
|
166
|
-
return query.all(project, limit);
|
|
167
|
-
}
|
|
168
|
-
function searchSummaries(db, searchTerm, project) {
|
|
169
|
-
const sql = project ? `SELECT * FROM summaries
|
|
170
|
-
WHERE project = ? AND (request LIKE ? OR learned LIKE ? OR completed LIKE ? OR notes LIKE ?)
|
|
171
|
-
ORDER BY created_at_epoch DESC` : `SELECT * FROM summaries
|
|
172
|
-
WHERE request LIKE ? OR learned LIKE ? OR completed LIKE ? OR notes LIKE ?
|
|
173
|
-
ORDER BY created_at_epoch DESC`;
|
|
174
|
-
const pattern = `%${searchTerm}%`;
|
|
175
|
-
const query = db.query(sql);
|
|
176
|
-
if (project) {
|
|
177
|
-
return query.all(project, pattern, pattern, pattern, pattern);
|
|
178
|
-
}
|
|
179
|
-
return query.all(pattern, pattern, pattern, pattern);
|
|
180
|
-
}
|
|
181
|
-
function deleteSummary(db, id) {
|
|
182
|
-
db.run("DELETE FROM summaries WHERE id = ?", [id]);
|
|
183
|
-
}
|
|
184
|
-
var init_Summaries = __esm({
|
|
185
|
-
"src/services/sqlite/Summaries.ts"() {
|
|
186
|
-
"use strict";
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
// src/services/sqlite/Prompts.ts
|
|
191
|
-
var Prompts_exports = {};
|
|
192
|
-
__export(Prompts_exports, {
|
|
193
|
-
createPrompt: () => createPrompt,
|
|
194
|
-
deletePrompt: () => deletePrompt,
|
|
195
|
-
getLatestPrompt: () => getLatestPrompt,
|
|
196
|
-
getPromptsByProject: () => getPromptsByProject,
|
|
197
|
-
getPromptsBySession: () => getPromptsBySession
|
|
198
|
-
});
|
|
199
|
-
function createPrompt(db, contentSessionId, project, promptNumber, promptText) {
|
|
200
|
-
const now = /* @__PURE__ */ new Date();
|
|
201
|
-
const result = db.run(
|
|
202
|
-
`INSERT INTO prompts
|
|
203
|
-
(content_session_id, project, prompt_number, prompt_text, created_at, created_at_epoch)
|
|
204
|
-
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
205
|
-
[contentSessionId, project, promptNumber, promptText, now.toISOString(), now.getTime()]
|
|
206
|
-
);
|
|
207
|
-
return Number(result.lastInsertRowid);
|
|
208
|
-
}
|
|
209
|
-
function getPromptsBySession(db, contentSessionId) {
|
|
210
|
-
const query = db.query(
|
|
211
|
-
"SELECT * FROM prompts WHERE content_session_id = ? ORDER BY prompt_number ASC"
|
|
212
|
-
);
|
|
213
|
-
return query.all(contentSessionId);
|
|
214
|
-
}
|
|
215
|
-
function getPromptsByProject(db, project, limit = 100) {
|
|
216
|
-
const query = db.query(
|
|
217
|
-
"SELECT * FROM prompts WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ?"
|
|
218
|
-
);
|
|
219
|
-
return query.all(project, limit);
|
|
220
|
-
}
|
|
221
|
-
function getLatestPrompt(db, contentSessionId) {
|
|
222
|
-
const query = db.query(
|
|
223
|
-
"SELECT * FROM prompts WHERE content_session_id = ? ORDER BY prompt_number DESC LIMIT 1"
|
|
71
|
+
function updateLastAccessed(db, ids) {
|
|
72
|
+
if (!Array.isArray(ids) || ids.length === 0) return;
|
|
73
|
+
const validIds = ids.filter((id) => typeof id === "number" && Number.isInteger(id) && id > 0).slice(0, 500);
|
|
74
|
+
if (validIds.length === 0) return;
|
|
75
|
+
const now = Date.now();
|
|
76
|
+
const placeholders = validIds.map(() => "?").join(",");
|
|
77
|
+
db.run(
|
|
78
|
+
`UPDATE observations SET last_accessed_epoch = ? WHERE id IN (${placeholders})`,
|
|
79
|
+
[now, ...validIds]
|
|
224
80
|
);
|
|
225
|
-
return query.get(contentSessionId);
|
|
226
81
|
}
|
|
227
|
-
function
|
|
228
|
-
|
|
82
|
+
function consolidateObservations(db, project, options = {}) {
|
|
83
|
+
const minGroupSize = options.minGroupSize || 3;
|
|
84
|
+
const groups = db.query(`
|
|
85
|
+
SELECT type, files_modified, COUNT(*) as cnt, GROUP_CONCAT(id) as ids
|
|
86
|
+
FROM observations
|
|
87
|
+
WHERE project = ? AND files_modified IS NOT NULL AND files_modified != ''
|
|
88
|
+
GROUP BY type, files_modified
|
|
89
|
+
HAVING cnt >= ?
|
|
90
|
+
ORDER BY cnt DESC
|
|
91
|
+
`).all(project, minGroupSize);
|
|
92
|
+
if (groups.length === 0) return { merged: 0, removed: 0 };
|
|
93
|
+
let totalMerged = 0;
|
|
94
|
+
let totalRemoved = 0;
|
|
95
|
+
for (const group of groups) {
|
|
96
|
+
const obsIds = group.ids.split(",").map(Number);
|
|
97
|
+
const placeholders = obsIds.map(() => "?").join(",");
|
|
98
|
+
const observations = db.query(
|
|
99
|
+
`SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC`
|
|
100
|
+
).all(...obsIds);
|
|
101
|
+
if (observations.length < minGroupSize) continue;
|
|
102
|
+
if (options.dryRun) {
|
|
103
|
+
totalMerged += 1;
|
|
104
|
+
totalRemoved += observations.length - 1;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const keeper = observations[0];
|
|
108
|
+
const others = observations.slice(1);
|
|
109
|
+
const uniqueTexts = /* @__PURE__ */ new Set();
|
|
110
|
+
if (keeper.text) uniqueTexts.add(keeper.text);
|
|
111
|
+
for (const obs of others) {
|
|
112
|
+
if (obs.text && !uniqueTexts.has(obs.text)) {
|
|
113
|
+
uniqueTexts.add(obs.text);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const consolidatedText = Array.from(uniqueTexts).join("\n---\n").substring(0, 1e5);
|
|
117
|
+
db.run(
|
|
118
|
+
"UPDATE observations SET text = ?, title = ? WHERE id = ?",
|
|
119
|
+
[consolidatedText, `[consolidato x${observations.length}] ${keeper.title}`, keeper.id]
|
|
120
|
+
);
|
|
121
|
+
const removeIds = others.map((o) => o.id);
|
|
122
|
+
const removePlaceholders = removeIds.map(() => "?").join(",");
|
|
123
|
+
db.run(`DELETE FROM observations WHERE id IN (${removePlaceholders})`, removeIds);
|
|
124
|
+
db.run(`DELETE FROM observation_embeddings WHERE observation_id IN (${removePlaceholders})`, removeIds);
|
|
125
|
+
totalMerged += 1;
|
|
126
|
+
totalRemoved += removeIds.length;
|
|
127
|
+
}
|
|
128
|
+
return { merged: totalMerged, removed: totalRemoved };
|
|
229
129
|
}
|
|
230
|
-
var
|
|
231
|
-
"src/services/sqlite/
|
|
130
|
+
var init_Observations = __esm({
|
|
131
|
+
"src/services/sqlite/Observations.ts"() {
|
|
232
132
|
"use strict";
|
|
233
133
|
}
|
|
234
134
|
});
|
|
@@ -238,20 +138,34 @@ var Search_exports = {};
|
|
|
238
138
|
__export(Search_exports, {
|
|
239
139
|
getObservationsByIds: () => getObservationsByIds,
|
|
240
140
|
getProjectStats: () => getProjectStats,
|
|
141
|
+
getStaleObservations: () => getStaleObservations,
|
|
241
142
|
getTimeline: () => getTimeline,
|
|
143
|
+
markObservationsStale: () => markObservationsStale,
|
|
242
144
|
searchObservationsFTS: () => searchObservationsFTS,
|
|
145
|
+
searchObservationsFTSWithRank: () => searchObservationsFTSWithRank,
|
|
243
146
|
searchObservationsLIKE: () => searchObservationsLIKE,
|
|
244
147
|
searchSummariesFiltered: () => searchSummariesFiltered
|
|
245
148
|
});
|
|
149
|
+
import { existsSync as existsSync4, statSync } from "fs";
|
|
150
|
+
function escapeLikePattern3(input) {
|
|
151
|
+
return input.replace(/[%_\\]/g, "\\$&");
|
|
152
|
+
}
|
|
153
|
+
function sanitizeFTS5Query(query) {
|
|
154
|
+
const trimmed = query.length > 1e4 ? query.substring(0, 1e4) : query;
|
|
155
|
+
const terms = trimmed.replace(/[""]/g, "").split(/\s+/).filter((t) => t.length > 0).slice(0, 100).map((t) => `"${t}"`);
|
|
156
|
+
return terms.join(" ");
|
|
157
|
+
}
|
|
246
158
|
function searchObservationsFTS(db, query, filters = {}) {
|
|
247
159
|
const limit = filters.limit || 50;
|
|
248
160
|
try {
|
|
161
|
+
const safeQuery = sanitizeFTS5Query(query);
|
|
162
|
+
if (!safeQuery) return searchObservationsLIKE(db, query, filters);
|
|
249
163
|
let sql = `
|
|
250
164
|
SELECT o.* FROM observations o
|
|
251
165
|
JOIN observations_fts fts ON o.id = fts.rowid
|
|
252
166
|
WHERE observations_fts MATCH ?
|
|
253
167
|
`;
|
|
254
|
-
const params = [
|
|
168
|
+
const params = [safeQuery];
|
|
255
169
|
if (filters.project) {
|
|
256
170
|
sql += " AND o.project = ?";
|
|
257
171
|
params.push(filters.project);
|
|
@@ -276,12 +190,47 @@ function searchObservationsFTS(db, query, filters = {}) {
|
|
|
276
190
|
return searchObservationsLIKE(db, query, filters);
|
|
277
191
|
}
|
|
278
192
|
}
|
|
193
|
+
function searchObservationsFTSWithRank(db, query, filters = {}) {
|
|
194
|
+
const limit = filters.limit || 50;
|
|
195
|
+
try {
|
|
196
|
+
const safeQuery = sanitizeFTS5Query(query);
|
|
197
|
+
if (!safeQuery) return [];
|
|
198
|
+
let sql = `
|
|
199
|
+
SELECT o.*, rank as fts5_rank FROM observations o
|
|
200
|
+
JOIN observations_fts fts ON o.id = fts.rowid
|
|
201
|
+
WHERE observations_fts MATCH ?
|
|
202
|
+
`;
|
|
203
|
+
const params = [safeQuery];
|
|
204
|
+
if (filters.project) {
|
|
205
|
+
sql += " AND o.project = ?";
|
|
206
|
+
params.push(filters.project);
|
|
207
|
+
}
|
|
208
|
+
if (filters.type) {
|
|
209
|
+
sql += " AND o.type = ?";
|
|
210
|
+
params.push(filters.type);
|
|
211
|
+
}
|
|
212
|
+
if (filters.dateStart) {
|
|
213
|
+
sql += " AND o.created_at_epoch >= ?";
|
|
214
|
+
params.push(filters.dateStart);
|
|
215
|
+
}
|
|
216
|
+
if (filters.dateEnd) {
|
|
217
|
+
sql += " AND o.created_at_epoch <= ?";
|
|
218
|
+
params.push(filters.dateEnd);
|
|
219
|
+
}
|
|
220
|
+
sql += " ORDER BY rank LIMIT ?";
|
|
221
|
+
params.push(limit);
|
|
222
|
+
const stmt = db.query(sql);
|
|
223
|
+
return stmt.all(...params);
|
|
224
|
+
} catch {
|
|
225
|
+
return [];
|
|
226
|
+
}
|
|
227
|
+
}
|
|
279
228
|
function searchObservationsLIKE(db, query, filters = {}) {
|
|
280
229
|
const limit = filters.limit || 50;
|
|
281
|
-
const pattern = `%${query}%`;
|
|
230
|
+
const pattern = `%${escapeLikePattern3(query)}%`;
|
|
282
231
|
let sql = `
|
|
283
232
|
SELECT * FROM observations
|
|
284
|
-
WHERE (title LIKE ? OR text LIKE ? OR narrative LIKE ? OR concepts LIKE ?)
|
|
233
|
+
WHERE (title LIKE ? ESCAPE '\\' OR text LIKE ? ESCAPE '\\' OR narrative LIKE ? ESCAPE '\\' OR concepts LIKE ? ESCAPE '\\')
|
|
285
234
|
`;
|
|
286
235
|
const params = [pattern, pattern, pattern, pattern];
|
|
287
236
|
if (filters.project) {
|
|
@@ -307,10 +256,10 @@ function searchObservationsLIKE(db, query, filters = {}) {
|
|
|
307
256
|
}
|
|
308
257
|
function searchSummariesFiltered(db, query, filters = {}) {
|
|
309
258
|
const limit = filters.limit || 20;
|
|
310
|
-
const pattern = `%${query}%`;
|
|
259
|
+
const pattern = `%${escapeLikePattern3(query)}%`;
|
|
311
260
|
let sql = `
|
|
312
261
|
SELECT * FROM summaries
|
|
313
|
-
WHERE (request LIKE ? OR learned LIKE ? OR completed LIKE ? OR notes LIKE ? OR next_steps LIKE ?)
|
|
262
|
+
WHERE (request LIKE ? ESCAPE '\\' OR learned LIKE ? ESCAPE '\\' OR completed LIKE ? ESCAPE '\\' OR notes LIKE ? ESCAPE '\\' OR next_steps LIKE ? ESCAPE '\\')
|
|
314
263
|
`;
|
|
315
264
|
const params = [pattern, pattern, pattern, pattern, pattern];
|
|
316
265
|
if (filters.project) {
|
|
@@ -331,11 +280,13 @@ function searchSummariesFiltered(db, query, filters = {}) {
|
|
|
331
280
|
return stmt.all(...params);
|
|
332
281
|
}
|
|
333
282
|
function getObservationsByIds(db, ids) {
|
|
334
|
-
if (ids.length === 0) return [];
|
|
335
|
-
const
|
|
283
|
+
if (!Array.isArray(ids) || ids.length === 0) return [];
|
|
284
|
+
const validIds = ids.filter((id) => typeof id === "number" && Number.isInteger(id) && id > 0).slice(0, 500);
|
|
285
|
+
if (validIds.length === 0) return [];
|
|
286
|
+
const placeholders = validIds.map(() => "?").join(",");
|
|
336
287
|
const sql = `SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC`;
|
|
337
288
|
const stmt = db.query(sql);
|
|
338
|
-
return stmt.all(...
|
|
289
|
+
return stmt.all(...validIds);
|
|
339
290
|
}
|
|
340
291
|
function getTimeline(db, anchorId, depthBefore = 5, depthAfter = 5) {
|
|
341
292
|
const anchorStmt = db.query("SELECT created_at_epoch FROM observations WHERE id = ?");
|
|
@@ -377,6 +328,45 @@ function getProjectStats(db, project) {
|
|
|
377
328
|
prompts: prmStmt.get(project)?.count || 0
|
|
378
329
|
};
|
|
379
330
|
}
|
|
331
|
+
function getStaleObservations(db, project) {
|
|
332
|
+
const rows = db.query(`
|
|
333
|
+
SELECT * FROM observations
|
|
334
|
+
WHERE project = ? AND files_modified IS NOT NULL AND files_modified != ''
|
|
335
|
+
ORDER BY created_at_epoch DESC
|
|
336
|
+
LIMIT 500
|
|
337
|
+
`).all(project);
|
|
338
|
+
const staleObs = [];
|
|
339
|
+
for (const obs of rows) {
|
|
340
|
+
if (!obs.files_modified) continue;
|
|
341
|
+
const files = obs.files_modified.split(",").map((f) => f.trim()).filter(Boolean);
|
|
342
|
+
let isStale = false;
|
|
343
|
+
for (const filepath of files) {
|
|
344
|
+
try {
|
|
345
|
+
if (!existsSync4(filepath)) continue;
|
|
346
|
+
const stat = statSync(filepath);
|
|
347
|
+
if (stat.mtimeMs > obs.created_at_epoch) {
|
|
348
|
+
isStale = true;
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
} catch {
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (isStale) {
|
|
355
|
+
staleObs.push(obs);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return staleObs;
|
|
359
|
+
}
|
|
360
|
+
function markObservationsStale(db, ids, stale) {
|
|
361
|
+
if (!Array.isArray(ids) || ids.length === 0) return;
|
|
362
|
+
const validIds = ids.filter((id) => typeof id === "number" && Number.isInteger(id) && id > 0).slice(0, 500);
|
|
363
|
+
if (validIds.length === 0) return;
|
|
364
|
+
const placeholders = validIds.map(() => "?").join(",");
|
|
365
|
+
db.run(
|
|
366
|
+
`UPDATE observations SET is_stale = ? WHERE id IN (${placeholders})`,
|
|
367
|
+
[stale ? 1 : 0, ...validIds]
|
|
368
|
+
);
|
|
369
|
+
}
|
|
380
370
|
var init_Search = __esm({
|
|
381
371
|
"src/services/sqlite/Search.ts"() {
|
|
382
372
|
"use strict";
|
|
@@ -384,8 +374,58 @@ var init_Search = __esm({
|
|
|
384
374
|
});
|
|
385
375
|
|
|
386
376
|
// src/hooks/utils.ts
|
|
387
|
-
import { writeFileSync, mkdirSync, existsSync } from "fs";
|
|
377
|
+
import { writeFileSync, mkdirSync, existsSync, readFileSync } from "fs";
|
|
388
378
|
import { join } from "path";
|
|
379
|
+
|
|
380
|
+
// src/services/search/ScoringEngine.ts
|
|
381
|
+
var SEARCH_WEIGHTS = {
|
|
382
|
+
semantic: 0.4,
|
|
383
|
+
fts5: 0.3,
|
|
384
|
+
recency: 0.2,
|
|
385
|
+
projectMatch: 0.1
|
|
386
|
+
};
|
|
387
|
+
var CONTEXT_WEIGHTS = {
|
|
388
|
+
semantic: 0,
|
|
389
|
+
fts5: 0,
|
|
390
|
+
recency: 0.7,
|
|
391
|
+
projectMatch: 0.3
|
|
392
|
+
};
|
|
393
|
+
function recencyScore(createdAtEpoch, halfLifeHours = 168) {
|
|
394
|
+
if (!createdAtEpoch || createdAtEpoch <= 0) return 0;
|
|
395
|
+
const nowMs = Date.now();
|
|
396
|
+
const ageMs = nowMs - createdAtEpoch;
|
|
397
|
+
if (ageMs <= 0) return 1;
|
|
398
|
+
const ageHours = ageMs / (1e3 * 60 * 60);
|
|
399
|
+
return Math.exp(-ageHours * Math.LN2 / halfLifeHours);
|
|
400
|
+
}
|
|
401
|
+
function normalizeFTS5Rank(rank, allRanks) {
|
|
402
|
+
if (allRanks.length === 0) return 0;
|
|
403
|
+
if (allRanks.length === 1) return 1;
|
|
404
|
+
const minRank = Math.min(...allRanks);
|
|
405
|
+
const maxRank = Math.max(...allRanks);
|
|
406
|
+
if (minRank === maxRank) return 1;
|
|
407
|
+
return (maxRank - rank) / (maxRank - minRank);
|
|
408
|
+
}
|
|
409
|
+
function projectMatchScore(itemProject, targetProject) {
|
|
410
|
+
if (!itemProject || !targetProject) return 0;
|
|
411
|
+
return itemProject.toLowerCase() === targetProject.toLowerCase() ? 1 : 0;
|
|
412
|
+
}
|
|
413
|
+
function computeCompositeScore(signals, weights) {
|
|
414
|
+
return signals.semantic * weights.semantic + signals.fts5 * weights.fts5 + signals.recency * weights.recency + signals.projectMatch * weights.projectMatch;
|
|
415
|
+
}
|
|
416
|
+
var KNOWLEDGE_TYPE_BOOST = {
|
|
417
|
+
constraint: 1.3,
|
|
418
|
+
decision: 1.25,
|
|
419
|
+
heuristic: 1.15,
|
|
420
|
+
rejected: 1.1
|
|
421
|
+
};
|
|
422
|
+
function knowledgeTypeBoost(type) {
|
|
423
|
+
return KNOWLEDGE_TYPE_BOOST[type] ?? 1;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// src/hooks/utils.ts
|
|
427
|
+
var DATA_DIR = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR || join(process.env.HOME || "/tmp", ".kiro-memory");
|
|
428
|
+
var TOKEN_FILE = join(DATA_DIR, "worker.token");
|
|
389
429
|
function debugLog(hookName, label, data) {
|
|
390
430
|
if ((process.env.KIRO_MEMORY_LOG_LEVEL || "").toUpperCase() !== "DEBUG") return;
|
|
391
431
|
try {
|
|
@@ -404,10 +444,19 @@ async function readStdin() {
|
|
|
404
444
|
return new Promise((resolve, reject) => {
|
|
405
445
|
let data = "";
|
|
406
446
|
process.stdin.setEncoding("utf8");
|
|
447
|
+
const safetyTimeout = setTimeout(() => {
|
|
448
|
+
if (!data.trim()) {
|
|
449
|
+
resolve({
|
|
450
|
+
hook_event_name: "agentSpawn",
|
|
451
|
+
cwd: process.cwd()
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
}, 5e3);
|
|
407
455
|
process.stdin.on("data", (chunk) => {
|
|
408
456
|
data += chunk;
|
|
409
457
|
});
|
|
410
458
|
process.stdin.on("end", () => {
|
|
459
|
+
clearTimeout(safetyTimeout);
|
|
411
460
|
try {
|
|
412
461
|
if (!data.trim()) {
|
|
413
462
|
resolve({
|
|
@@ -421,15 +470,10 @@ async function readStdin() {
|
|
|
421
470
|
reject(new Error(`Errore parsing stdin JSON: ${err}`));
|
|
422
471
|
}
|
|
423
472
|
});
|
|
424
|
-
process.stdin.on("error",
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
hook_event_name: "agentSpawn",
|
|
429
|
-
cwd: process.cwd()
|
|
430
|
-
});
|
|
431
|
-
}
|
|
432
|
-
}, 5e3);
|
|
473
|
+
process.stdin.on("error", (err) => {
|
|
474
|
+
clearTimeout(safetyTimeout);
|
|
475
|
+
reject(err);
|
|
476
|
+
});
|
|
433
477
|
});
|
|
434
478
|
}
|
|
435
479
|
function detectProject(cwd) {
|
|
@@ -449,11 +493,20 @@ async function notifyWorker(event, data) {
|
|
|
449
493
|
const host = process.env.KIRO_MEMORY_WORKER_HOST || "127.0.0.1";
|
|
450
494
|
const port = process.env.KIRO_MEMORY_WORKER_PORT || "3001";
|
|
451
495
|
try {
|
|
496
|
+
let token = "";
|
|
497
|
+
try {
|
|
498
|
+
token = readFileSync(TOKEN_FILE, "utf-8").trim();
|
|
499
|
+
} catch {
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
452
502
|
const controller = new AbortController();
|
|
453
503
|
const timeout = setTimeout(() => controller.abort(), 1500);
|
|
454
504
|
await fetch(`http://${host}:${port}/api/notify`, {
|
|
455
505
|
method: "POST",
|
|
456
|
-
headers: {
|
|
506
|
+
headers: {
|
|
507
|
+
"Content-Type": "application/json",
|
|
508
|
+
"X-Worker-Token": token
|
|
509
|
+
},
|
|
457
510
|
body: JSON.stringify({ event, data: data || {} }),
|
|
458
511
|
signal: controller.signal
|
|
459
512
|
});
|
|
@@ -464,6 +517,12 @@ async function notifyWorker(event, data) {
|
|
|
464
517
|
async function runHook(name, handler) {
|
|
465
518
|
try {
|
|
466
519
|
const input = await readStdin();
|
|
520
|
+
if (!input.cwd && input.workspace_roots?.[0]) {
|
|
521
|
+
input.cwd = input.workspace_roots[0];
|
|
522
|
+
}
|
|
523
|
+
if (!input.session_id && input.conversation_id) {
|
|
524
|
+
input.session_id = input.conversation_id;
|
|
525
|
+
}
|
|
467
526
|
debugLog(name, "stdin", input);
|
|
468
527
|
await handler(input);
|
|
469
528
|
debugLog(name, "completato", { success: true });
|
|
@@ -546,11 +605,11 @@ var BunQueryCompat = class {
|
|
|
546
605
|
// src/shared/paths.ts
|
|
547
606
|
import { join as join3, dirname, basename } from "path";
|
|
548
607
|
import { homedir as homedir2 } from "os";
|
|
549
|
-
import { mkdirSync as mkdirSync3 } from "fs";
|
|
608
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3 } from "fs";
|
|
550
609
|
import { fileURLToPath } from "url";
|
|
551
610
|
|
|
552
611
|
// src/utils/logger.ts
|
|
553
|
-
import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync } from "fs";
|
|
612
|
+
import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2 } from "fs";
|
|
554
613
|
import { join as join2 } from "path";
|
|
555
614
|
import { homedir } from "os";
|
|
556
615
|
var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
@@ -596,7 +655,7 @@ var Logger = class {
|
|
|
596
655
|
try {
|
|
597
656
|
const settingsPath = join2(DEFAULT_DATA_DIR, "settings.json");
|
|
598
657
|
if (existsSync2(settingsPath)) {
|
|
599
|
-
const settingsData =
|
|
658
|
+
const settingsData = readFileSync2(settingsPath, "utf-8");
|
|
600
659
|
const settings = JSON.parse(settingsData);
|
|
601
660
|
const envLevel = (settings.KIRO_MEMORY_LOG_LEVEL || settings.CONTEXTKIT_LOG_LEVEL || "INFO").toUpperCase();
|
|
602
661
|
this.level = LogLevel[envLevel] ?? 1 /* INFO */;
|
|
@@ -776,18 +835,21 @@ function getDirname() {
|
|
|
776
835
|
return dirname(fileURLToPath(import.meta.url));
|
|
777
836
|
}
|
|
778
837
|
var _dirname = getDirname();
|
|
779
|
-
var
|
|
838
|
+
var _legacyDir = join3(homedir2(), ".contextkit");
|
|
839
|
+
var _defaultDir = existsSync3(_legacyDir) ? _legacyDir : join3(homedir2(), ".kiro-memory");
|
|
840
|
+
var DATA_DIR2 = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR || _defaultDir;
|
|
780
841
|
var KIRO_CONFIG_DIR = process.env.KIRO_CONFIG_DIR || join3(homedir2(), ".kiro");
|
|
781
842
|
var PLUGIN_ROOT = join3(KIRO_CONFIG_DIR, "plugins", "kiro-memory");
|
|
782
|
-
var ARCHIVES_DIR = join3(
|
|
783
|
-
var LOGS_DIR = join3(
|
|
784
|
-
var TRASH_DIR = join3(
|
|
785
|
-
var BACKUPS_DIR = join3(
|
|
786
|
-
var MODES_DIR = join3(
|
|
787
|
-
var USER_SETTINGS_PATH = join3(
|
|
788
|
-
var
|
|
789
|
-
var
|
|
790
|
-
var
|
|
843
|
+
var ARCHIVES_DIR = join3(DATA_DIR2, "archives");
|
|
844
|
+
var LOGS_DIR = join3(DATA_DIR2, "logs");
|
|
845
|
+
var TRASH_DIR = join3(DATA_DIR2, "trash");
|
|
846
|
+
var BACKUPS_DIR = join3(DATA_DIR2, "backups");
|
|
847
|
+
var MODES_DIR = join3(DATA_DIR2, "modes");
|
|
848
|
+
var USER_SETTINGS_PATH = join3(DATA_DIR2, "settings.json");
|
|
849
|
+
var _legacyDb = join3(DATA_DIR2, "contextkit.db");
|
|
850
|
+
var DB_PATH = existsSync3(_legacyDb) ? _legacyDb : join3(DATA_DIR2, "kiro-memory.db");
|
|
851
|
+
var VECTOR_DB_DIR = join3(DATA_DIR2, "vector-db");
|
|
852
|
+
var OBSERVER_SESSIONS_DIR = join3(DATA_DIR2, "observer-sessions");
|
|
791
853
|
var KIRO_SETTINGS_PATH = join3(KIRO_CONFIG_DIR, "settings.json");
|
|
792
854
|
var KIRO_CONTEXT_PATH = join3(KIRO_CONFIG_DIR, "context.md");
|
|
793
855
|
function ensureDir(dirPath) {
|
|
@@ -799,9 +861,13 @@ var SQLITE_MMAP_SIZE_BYTES = 256 * 1024 * 1024;
|
|
|
799
861
|
var SQLITE_CACHE_SIZE_PAGES = 1e4;
|
|
800
862
|
var KiroMemoryDatabase = class {
|
|
801
863
|
db;
|
|
802
|
-
|
|
864
|
+
/**
|
|
865
|
+
* @param dbPath - Percorso al file SQLite (default: DB_PATH)
|
|
866
|
+
* @param skipMigrations - Se true, salta il migration runner (per hook ad alta frequenza)
|
|
867
|
+
*/
|
|
868
|
+
constructor(dbPath = DB_PATH, skipMigrations = false) {
|
|
803
869
|
if (dbPath !== ":memory:") {
|
|
804
|
-
ensureDir(
|
|
870
|
+
ensureDir(DATA_DIR2);
|
|
805
871
|
}
|
|
806
872
|
this.db = new Database(dbPath, { create: true, readwrite: true });
|
|
807
873
|
this.db.run("PRAGMA journal_mode = WAL");
|
|
@@ -810,8 +876,18 @@ var KiroMemoryDatabase = class {
|
|
|
810
876
|
this.db.run("PRAGMA temp_store = memory");
|
|
811
877
|
this.db.run(`PRAGMA mmap_size = ${SQLITE_MMAP_SIZE_BYTES}`);
|
|
812
878
|
this.db.run(`PRAGMA cache_size = ${SQLITE_CACHE_SIZE_PAGES}`);
|
|
813
|
-
|
|
814
|
-
|
|
879
|
+
if (!skipMigrations) {
|
|
880
|
+
const migrationRunner = new MigrationRunner(this.db);
|
|
881
|
+
migrationRunner.runAllMigrations();
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
/**
|
|
885
|
+
* Esegue una funzione all'interno di una transazione atomica.
|
|
886
|
+
* Se fn() lancia un errore, la transazione viene annullata automaticamente.
|
|
887
|
+
*/
|
|
888
|
+
withTransaction(fn) {
|
|
889
|
+
const transaction = this.db.transaction(fn);
|
|
890
|
+
return transaction(this.db);
|
|
815
891
|
}
|
|
816
892
|
/**
|
|
817
893
|
* Close the database connection
|
|
@@ -986,33 +1062,744 @@ var MigrationRunner = class {
|
|
|
986
1062
|
`);
|
|
987
1063
|
db.run("CREATE UNIQUE INDEX IF NOT EXISTS idx_project_aliases_name ON project_aliases(project_name)");
|
|
988
1064
|
}
|
|
1065
|
+
},
|
|
1066
|
+
{
|
|
1067
|
+
version: 4,
|
|
1068
|
+
up: (db) => {
|
|
1069
|
+
db.run(`
|
|
1070
|
+
CREATE TABLE IF NOT EXISTS observation_embeddings (
|
|
1071
|
+
observation_id INTEGER PRIMARY KEY,
|
|
1072
|
+
embedding BLOB NOT NULL,
|
|
1073
|
+
model TEXT NOT NULL,
|
|
1074
|
+
dimensions INTEGER NOT NULL,
|
|
1075
|
+
created_at TEXT NOT NULL,
|
|
1076
|
+
FOREIGN KEY (observation_id) REFERENCES observations(id) ON DELETE CASCADE
|
|
1077
|
+
)
|
|
1078
|
+
`);
|
|
1079
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_embeddings_model ON observation_embeddings(model)");
|
|
1080
|
+
}
|
|
1081
|
+
},
|
|
1082
|
+
{
|
|
1083
|
+
version: 5,
|
|
1084
|
+
up: (db) => {
|
|
1085
|
+
db.run("ALTER TABLE observations ADD COLUMN last_accessed_epoch INTEGER");
|
|
1086
|
+
db.run("ALTER TABLE observations ADD COLUMN is_stale INTEGER DEFAULT 0");
|
|
1087
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_observations_last_accessed ON observations(last_accessed_epoch)");
|
|
1088
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_observations_stale ON observations(is_stale)");
|
|
1089
|
+
}
|
|
1090
|
+
},
|
|
1091
|
+
{
|
|
1092
|
+
version: 6,
|
|
1093
|
+
up: (db) => {
|
|
1094
|
+
db.run(`
|
|
1095
|
+
CREATE TABLE IF NOT EXISTS checkpoints (
|
|
1096
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1097
|
+
session_id INTEGER NOT NULL,
|
|
1098
|
+
project TEXT NOT NULL,
|
|
1099
|
+
task TEXT NOT NULL,
|
|
1100
|
+
progress TEXT,
|
|
1101
|
+
next_steps TEXT,
|
|
1102
|
+
open_questions TEXT,
|
|
1103
|
+
relevant_files TEXT,
|
|
1104
|
+
context_snapshot TEXT,
|
|
1105
|
+
created_at TEXT NOT NULL,
|
|
1106
|
+
created_at_epoch INTEGER NOT NULL,
|
|
1107
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id)
|
|
1108
|
+
)
|
|
1109
|
+
`);
|
|
1110
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_checkpoints_session ON checkpoints(session_id)");
|
|
1111
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_checkpoints_project ON checkpoints(project)");
|
|
1112
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_checkpoints_epoch ON checkpoints(created_at_epoch)");
|
|
1113
|
+
}
|
|
989
1114
|
}
|
|
990
1115
|
];
|
|
991
1116
|
}
|
|
992
1117
|
};
|
|
993
1118
|
|
|
1119
|
+
// src/services/sqlite/Sessions.ts
|
|
1120
|
+
function createSession(db, contentSessionId, project, userPrompt) {
|
|
1121
|
+
const now = /* @__PURE__ */ new Date();
|
|
1122
|
+
const result = db.run(
|
|
1123
|
+
`INSERT INTO sessions (content_session_id, project, user_prompt, status, started_at, started_at_epoch)
|
|
1124
|
+
VALUES (?, ?, ?, 'active', ?, ?)`,
|
|
1125
|
+
[contentSessionId, project, userPrompt, now.toISOString(), now.getTime()]
|
|
1126
|
+
);
|
|
1127
|
+
return Number(result.lastInsertRowid);
|
|
1128
|
+
}
|
|
1129
|
+
function getSessionByContentId(db, contentSessionId) {
|
|
1130
|
+
const query = db.query("SELECT * FROM sessions WHERE content_session_id = ?");
|
|
1131
|
+
return query.get(contentSessionId);
|
|
1132
|
+
}
|
|
1133
|
+
function completeSession(db, id) {
|
|
1134
|
+
const now = /* @__PURE__ */ new Date();
|
|
1135
|
+
db.run(
|
|
1136
|
+
`UPDATE sessions
|
|
1137
|
+
SET status = 'completed', completed_at = ?, completed_at_epoch = ?
|
|
1138
|
+
WHERE id = ?`,
|
|
1139
|
+
[now.toISOString(), now.getTime(), id]
|
|
1140
|
+
);
|
|
1141
|
+
}
|
|
1142
|
+
|
|
994
1143
|
// src/services/sqlite/index.ts
|
|
995
|
-
init_Sessions();
|
|
996
1144
|
init_Observations();
|
|
997
|
-
init_Summaries();
|
|
998
|
-
init_Prompts();
|
|
999
|
-
init_Search();
|
|
1000
1145
|
|
|
1001
|
-
// src/
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1146
|
+
// src/services/sqlite/Summaries.ts
|
|
1147
|
+
function escapeLikePattern2(input) {
|
|
1148
|
+
return input.replace(/[%_\\]/g, "\\$&");
|
|
1149
|
+
}
|
|
1150
|
+
function createSummary(db, sessionId, project, request, investigated, learned, completed, nextSteps, notes) {
|
|
1151
|
+
const now = /* @__PURE__ */ new Date();
|
|
1152
|
+
const result = db.run(
|
|
1153
|
+
`INSERT INTO summaries
|
|
1154
|
+
(session_id, project, request, investigated, learned, completed, next_steps, notes, created_at, created_at_epoch)
|
|
1155
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1156
|
+
[sessionId, project, request, investigated, learned, completed, nextSteps, notes, now.toISOString(), now.getTime()]
|
|
1157
|
+
);
|
|
1158
|
+
return Number(result.lastInsertRowid);
|
|
1159
|
+
}
|
|
1160
|
+
function getSummariesByProject(db, project, limit = 50) {
|
|
1161
|
+
const query = db.query(
|
|
1162
|
+
"SELECT * FROM summaries WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ?"
|
|
1163
|
+
);
|
|
1164
|
+
return query.all(project, limit);
|
|
1165
|
+
}
|
|
1166
|
+
function searchSummaries(db, searchTerm, project) {
|
|
1167
|
+
const sql = project ? `SELECT * FROM summaries
|
|
1168
|
+
WHERE project = ? AND (request LIKE ? ESCAPE '\\' OR learned LIKE ? ESCAPE '\\' OR completed LIKE ? ESCAPE '\\' OR notes LIKE ? ESCAPE '\\')
|
|
1169
|
+
ORDER BY created_at_epoch DESC` : `SELECT * FROM summaries
|
|
1170
|
+
WHERE request LIKE ? ESCAPE '\\' OR learned LIKE ? ESCAPE '\\' OR completed LIKE ? ESCAPE '\\' OR notes LIKE ? ESCAPE '\\'
|
|
1171
|
+
ORDER BY created_at_epoch DESC`;
|
|
1172
|
+
const pattern = `%${escapeLikePattern2(searchTerm)}%`;
|
|
1173
|
+
const query = db.query(sql);
|
|
1174
|
+
if (project) {
|
|
1175
|
+
return query.all(project, pattern, pattern, pattern, pattern);
|
|
1176
|
+
}
|
|
1177
|
+
return query.all(pattern, pattern, pattern, pattern);
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
// src/services/sqlite/Prompts.ts
|
|
1181
|
+
function createPrompt(db, contentSessionId, project, promptNumber, promptText) {
|
|
1182
|
+
const now = /* @__PURE__ */ new Date();
|
|
1183
|
+
const result = db.run(
|
|
1184
|
+
`INSERT INTO prompts
|
|
1185
|
+
(content_session_id, project, prompt_number, prompt_text, created_at, created_at_epoch)
|
|
1186
|
+
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
1187
|
+
[contentSessionId, project, promptNumber, promptText, now.toISOString(), now.getTime()]
|
|
1188
|
+
);
|
|
1189
|
+
return Number(result.lastInsertRowid);
|
|
1190
|
+
}
|
|
1191
|
+
function getPromptsByProject(db, project, limit = 100) {
|
|
1192
|
+
const query = db.query(
|
|
1193
|
+
"SELECT * FROM prompts WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ?"
|
|
1194
|
+
);
|
|
1195
|
+
return query.all(project, limit);
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
// src/services/sqlite/Checkpoints.ts
|
|
1199
|
+
function createCheckpoint(db, sessionId, project, data) {
|
|
1200
|
+
const now = /* @__PURE__ */ new Date();
|
|
1201
|
+
const result = db.run(
|
|
1202
|
+
`INSERT INTO checkpoints (session_id, project, task, progress, next_steps, open_questions, relevant_files, context_snapshot, created_at, created_at_epoch)
|
|
1203
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1204
|
+
[
|
|
1205
|
+
sessionId,
|
|
1206
|
+
project,
|
|
1207
|
+
data.task,
|
|
1208
|
+
data.progress || null,
|
|
1209
|
+
data.nextSteps || null,
|
|
1210
|
+
data.openQuestions || null,
|
|
1211
|
+
data.relevantFiles || null,
|
|
1212
|
+
data.contextSnapshot || null,
|
|
1213
|
+
now.toISOString(),
|
|
1214
|
+
now.getTime()
|
|
1215
|
+
]
|
|
1216
|
+
);
|
|
1217
|
+
return Number(result.lastInsertRowid);
|
|
1218
|
+
}
|
|
1219
|
+
function getLatestCheckpoint(db, sessionId) {
|
|
1220
|
+
const query = db.query(
|
|
1221
|
+
"SELECT * FROM checkpoints WHERE session_id = ? ORDER BY created_at_epoch DESC LIMIT 1"
|
|
1222
|
+
);
|
|
1223
|
+
return query.get(sessionId);
|
|
1224
|
+
}
|
|
1225
|
+
function getLatestCheckpointByProject(db, project) {
|
|
1226
|
+
const query = db.query(
|
|
1227
|
+
"SELECT * FROM checkpoints WHERE project = ? ORDER BY created_at_epoch DESC LIMIT 1"
|
|
1228
|
+
);
|
|
1229
|
+
return query.get(project);
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
// src/services/sqlite/Reports.ts
|
|
1233
|
+
function getReportData(db, project, startEpoch, endEpoch) {
|
|
1234
|
+
const startDate = new Date(startEpoch);
|
|
1235
|
+
const endDate = new Date(endEpoch);
|
|
1236
|
+
const days = Math.ceil((endEpoch - startEpoch) / (24 * 60 * 60 * 1e3));
|
|
1237
|
+
const label = days <= 7 ? "Weekly" : days <= 31 ? "Monthly" : "Custom";
|
|
1238
|
+
const countInRange = (table, epochCol = "created_at_epoch") => {
|
|
1239
|
+
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} <= ?`;
|
|
1240
|
+
const stmt = db.query(sql);
|
|
1241
|
+
const row = project ? stmt.get(project, startEpoch, endEpoch) : stmt.get(startEpoch, endEpoch);
|
|
1242
|
+
return row?.count || 0;
|
|
1243
|
+
};
|
|
1244
|
+
const observations = countInRange("observations");
|
|
1245
|
+
const summaries = countInRange("summaries");
|
|
1246
|
+
const prompts = countInRange("prompts");
|
|
1247
|
+
const sessions = countInRange("sessions", "started_at_epoch");
|
|
1248
|
+
const timelineSql = project ? `SELECT DATE(datetime(created_at_epoch / 1000, 'unixepoch')) as day, COUNT(*) as count
|
|
1249
|
+
FROM observations
|
|
1250
|
+
WHERE project = ? AND created_at_epoch >= ? AND created_at_epoch <= ?
|
|
1251
|
+
GROUP BY day ORDER BY day ASC` : `SELECT DATE(datetime(created_at_epoch / 1000, 'unixepoch')) as day, COUNT(*) as count
|
|
1252
|
+
FROM observations
|
|
1253
|
+
WHERE created_at_epoch >= ? AND created_at_epoch <= ?
|
|
1254
|
+
GROUP BY day ORDER BY day ASC`;
|
|
1255
|
+
const timelineStmt = db.query(timelineSql);
|
|
1256
|
+
const timeline = project ? timelineStmt.all(project, startEpoch, endEpoch) : timelineStmt.all(startEpoch, endEpoch);
|
|
1257
|
+
const typeSql = project ? `SELECT type, COUNT(*) as count FROM observations
|
|
1258
|
+
WHERE project = ? AND created_at_epoch >= ? AND created_at_epoch <= ?
|
|
1259
|
+
GROUP BY type ORDER BY count DESC` : `SELECT type, COUNT(*) as count FROM observations
|
|
1260
|
+
WHERE created_at_epoch >= ? AND created_at_epoch <= ?
|
|
1261
|
+
GROUP BY type ORDER BY count DESC`;
|
|
1262
|
+
const typeStmt = db.query(typeSql);
|
|
1263
|
+
const typeDistribution = project ? typeStmt.all(project, startEpoch, endEpoch) : typeStmt.all(startEpoch, endEpoch);
|
|
1264
|
+
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 <= ?`;
|
|
1265
|
+
const sessionTotal = (project ? db.query(sessionTotalSql).get(project, startEpoch, endEpoch)?.count : db.query(sessionTotalSql).get(startEpoch, endEpoch)?.count) || 0;
|
|
1266
|
+
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'`;
|
|
1267
|
+
const sessionCompleted = (project ? db.query(sessionCompletedSql).get(project, startEpoch, endEpoch)?.count : db.query(sessionCompletedSql).get(startEpoch, endEpoch)?.count) || 0;
|
|
1268
|
+
const sessionAvgSql = project ? `SELECT AVG((completed_at_epoch - started_at_epoch) / 1000.0 / 60.0) as avg_min
|
|
1269
|
+
FROM sessions
|
|
1270
|
+
WHERE project = ? AND started_at_epoch >= ? AND started_at_epoch <= ?
|
|
1271
|
+
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
|
|
1272
|
+
FROM sessions
|
|
1273
|
+
WHERE started_at_epoch >= ? AND started_at_epoch <= ?
|
|
1274
|
+
AND status = 'completed' AND completed_at_epoch IS NOT NULL AND completed_at_epoch > started_at_epoch`;
|
|
1275
|
+
const avgRow = project ? db.query(sessionAvgSql).get(project, startEpoch, endEpoch) : db.query(sessionAvgSql).get(startEpoch, endEpoch);
|
|
1276
|
+
const avgDurationMinutes = Math.round((avgRow?.avg_min || 0) * 10) / 10;
|
|
1277
|
+
const knowledgeSql = project ? `SELECT COUNT(*) as count FROM observations
|
|
1278
|
+
WHERE project = ? AND created_at_epoch >= ? AND created_at_epoch <= ?
|
|
1279
|
+
AND type IN ('constraint', 'decision', 'heuristic', 'rejected')` : `SELECT COUNT(*) as count FROM observations
|
|
1280
|
+
WHERE created_at_epoch >= ? AND created_at_epoch <= ?
|
|
1281
|
+
AND type IN ('constraint', 'decision', 'heuristic', 'rejected')`;
|
|
1282
|
+
const knowledgeCount = (project ? db.query(knowledgeSql).get(project, startEpoch, endEpoch)?.count : db.query(knowledgeSql).get(startEpoch, endEpoch)?.count) || 0;
|
|
1283
|
+
const staleSql = project ? `SELECT COUNT(*) as count FROM observations
|
|
1284
|
+
WHERE project = ? AND created_at_epoch >= ? AND created_at_epoch <= ? AND is_stale = 1` : `SELECT COUNT(*) as count FROM observations
|
|
1285
|
+
WHERE created_at_epoch >= ? AND created_at_epoch <= ? AND is_stale = 1`;
|
|
1286
|
+
const staleCount = (project ? db.query(staleSql).get(project, startEpoch, endEpoch)?.count : db.query(staleSql).get(startEpoch, endEpoch)?.count) || 0;
|
|
1287
|
+
const summarySql = project ? `SELECT learned, completed, next_steps FROM summaries
|
|
1288
|
+
WHERE project = ? AND created_at_epoch >= ? AND created_at_epoch <= ?
|
|
1289
|
+
ORDER BY created_at_epoch DESC` : `SELECT learned, completed, next_steps FROM summaries
|
|
1290
|
+
WHERE created_at_epoch >= ? AND created_at_epoch <= ?
|
|
1291
|
+
ORDER BY created_at_epoch DESC`;
|
|
1292
|
+
const summaryRows = project ? db.query(summarySql).all(project, startEpoch, endEpoch) : db.query(summarySql).all(startEpoch, endEpoch);
|
|
1293
|
+
const topLearnings = [];
|
|
1294
|
+
const completedTasks = [];
|
|
1295
|
+
const nextStepsArr = [];
|
|
1296
|
+
for (const row of summaryRows) {
|
|
1297
|
+
if (row.learned) {
|
|
1298
|
+
const parts = row.learned.split("; ").filter(Boolean);
|
|
1299
|
+
topLearnings.push(...parts);
|
|
1300
|
+
}
|
|
1301
|
+
if (row.completed) {
|
|
1302
|
+
const parts = row.completed.split("; ").filter(Boolean);
|
|
1303
|
+
completedTasks.push(...parts);
|
|
1304
|
+
}
|
|
1305
|
+
if (row.next_steps) {
|
|
1306
|
+
const parts = row.next_steps.split("; ").filter(Boolean);
|
|
1307
|
+
nextStepsArr.push(...parts);
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
const filesSql = project ? `SELECT files_modified FROM observations
|
|
1311
|
+
WHERE project = ? AND created_at_epoch >= ? AND created_at_epoch <= ?
|
|
1312
|
+
AND files_modified IS NOT NULL AND files_modified != ''` : `SELECT files_modified FROM observations
|
|
1313
|
+
WHERE created_at_epoch >= ? AND created_at_epoch <= ?
|
|
1314
|
+
AND files_modified IS NOT NULL AND files_modified != ''`;
|
|
1315
|
+
const fileRows = project ? db.query(filesSql).all(project, startEpoch, endEpoch) : db.query(filesSql).all(startEpoch, endEpoch);
|
|
1316
|
+
const fileCounts = /* @__PURE__ */ new Map();
|
|
1317
|
+
for (const row of fileRows) {
|
|
1318
|
+
const files = row.files_modified.split(",").map((f) => f.trim()).filter(Boolean);
|
|
1319
|
+
for (const file of files) {
|
|
1320
|
+
fileCounts.set(file, (fileCounts.get(file) || 0) + 1);
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
const fileHotspots = Array.from(fileCounts.entries()).map(([file, count]) => ({ file, count })).sort((a, b) => b.count - a.count).slice(0, 15);
|
|
1324
|
+
return {
|
|
1325
|
+
period: {
|
|
1326
|
+
start: startDate.toISOString().split("T")[0],
|
|
1327
|
+
end: endDate.toISOString().split("T")[0],
|
|
1328
|
+
days,
|
|
1329
|
+
label
|
|
1330
|
+
},
|
|
1331
|
+
overview: {
|
|
1332
|
+
observations,
|
|
1333
|
+
summaries,
|
|
1334
|
+
sessions,
|
|
1335
|
+
prompts,
|
|
1336
|
+
knowledgeCount,
|
|
1337
|
+
staleCount
|
|
1338
|
+
},
|
|
1339
|
+
timeline,
|
|
1340
|
+
typeDistribution,
|
|
1341
|
+
sessionStats: {
|
|
1342
|
+
total: sessionTotal,
|
|
1343
|
+
completed: sessionCompleted,
|
|
1344
|
+
avgDurationMinutes
|
|
1345
|
+
},
|
|
1346
|
+
topLearnings: [...new Set(topLearnings)].slice(0, 10),
|
|
1347
|
+
completedTasks: [...new Set(completedTasks)].slice(0, 10),
|
|
1348
|
+
nextSteps: [...new Set(nextStepsArr)].slice(0, 10),
|
|
1349
|
+
fileHotspots
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
// src/services/sqlite/index.ts
|
|
1354
|
+
init_Search();
|
|
1355
|
+
|
|
1356
|
+
// src/sdk/index.ts
|
|
1357
|
+
init_Observations();
|
|
1358
|
+
init_Search();
|
|
1359
|
+
|
|
1360
|
+
// src/services/search/EmbeddingService.ts
|
|
1361
|
+
var EmbeddingService = class {
|
|
1362
|
+
provider = null;
|
|
1363
|
+
model = null;
|
|
1364
|
+
initialized = false;
|
|
1365
|
+
initializing = null;
|
|
1366
|
+
/**
|
|
1367
|
+
* Inizializza il servizio di embedding.
|
|
1368
|
+
* Tenta fastembed, poi @huggingface/transformers, poi fallback a null.
|
|
1369
|
+
*/
|
|
1370
|
+
async initialize() {
|
|
1371
|
+
if (this.initialized) return this.provider !== null;
|
|
1372
|
+
if (this.initializing) return this.initializing;
|
|
1373
|
+
this.initializing = this._doInitialize();
|
|
1374
|
+
const result = await this.initializing;
|
|
1375
|
+
this.initializing = null;
|
|
1376
|
+
return result;
|
|
1377
|
+
}
|
|
1378
|
+
async _doInitialize() {
|
|
1379
|
+
try {
|
|
1380
|
+
const fastembed = await import("fastembed");
|
|
1381
|
+
const EmbeddingModel = fastembed.EmbeddingModel || fastembed.default?.EmbeddingModel;
|
|
1382
|
+
const FlagEmbedding = fastembed.FlagEmbedding || fastembed.default?.FlagEmbedding;
|
|
1383
|
+
if (FlagEmbedding && EmbeddingModel) {
|
|
1384
|
+
this.model = await FlagEmbedding.init({
|
|
1385
|
+
model: EmbeddingModel.BGESmallENV15
|
|
1386
|
+
});
|
|
1387
|
+
this.provider = "fastembed";
|
|
1388
|
+
this.initialized = true;
|
|
1389
|
+
logger.info("EMBEDDING", "Inizializzato con fastembed (BGE-small-en-v1.5)");
|
|
1390
|
+
return true;
|
|
1391
|
+
}
|
|
1392
|
+
} catch (error) {
|
|
1393
|
+
logger.debug("EMBEDDING", `fastembed non disponibile: ${error}`);
|
|
1394
|
+
}
|
|
1395
|
+
try {
|
|
1396
|
+
const transformers = await import("@huggingface/transformers");
|
|
1397
|
+
const pipeline = transformers.pipeline || transformers.default?.pipeline;
|
|
1398
|
+
if (pipeline) {
|
|
1399
|
+
this.model = await pipeline("feature-extraction", "Xenova/all-MiniLM-L6-v2", {
|
|
1400
|
+
quantized: true
|
|
1401
|
+
});
|
|
1402
|
+
this.provider = "transformers";
|
|
1403
|
+
this.initialized = true;
|
|
1404
|
+
logger.info("EMBEDDING", "Inizializzato con @huggingface/transformers (all-MiniLM-L6-v2)");
|
|
1405
|
+
return true;
|
|
1406
|
+
}
|
|
1407
|
+
} catch (error) {
|
|
1408
|
+
logger.debug("EMBEDDING", `@huggingface/transformers non disponibile: ${error}`);
|
|
1409
|
+
}
|
|
1410
|
+
this.provider = null;
|
|
1411
|
+
this.initialized = true;
|
|
1412
|
+
logger.warn("EMBEDDING", "Nessun provider embedding disponibile, ricerca semantica disabilitata");
|
|
1413
|
+
return false;
|
|
1414
|
+
}
|
|
1415
|
+
/**
|
|
1416
|
+
* Genera embedding per un singolo testo.
|
|
1417
|
+
* Ritorna Float32Array con 384 dimensioni, o null se non disponibile.
|
|
1418
|
+
*/
|
|
1419
|
+
async embed(text) {
|
|
1420
|
+
if (!this.initialized) await this.initialize();
|
|
1421
|
+
if (!this.provider || !this.model) return null;
|
|
1422
|
+
try {
|
|
1423
|
+
const truncated = text.substring(0, 2e3);
|
|
1424
|
+
if (this.provider === "fastembed") {
|
|
1425
|
+
return await this._embedFastembed(truncated);
|
|
1426
|
+
} else if (this.provider === "transformers") {
|
|
1427
|
+
return await this._embedTransformers(truncated);
|
|
1428
|
+
}
|
|
1429
|
+
} catch (error) {
|
|
1430
|
+
logger.error("EMBEDDING", `Errore generazione embedding: ${error}`);
|
|
1431
|
+
}
|
|
1432
|
+
return null;
|
|
1433
|
+
}
|
|
1434
|
+
/**
|
|
1435
|
+
* Genera embeddings in batch.
|
|
1436
|
+
*/
|
|
1437
|
+
async embedBatch(texts) {
|
|
1438
|
+
if (!this.initialized) await this.initialize();
|
|
1439
|
+
if (!this.provider || !this.model) return texts.map(() => null);
|
|
1440
|
+
const results = [];
|
|
1441
|
+
for (const text of texts) {
|
|
1442
|
+
try {
|
|
1443
|
+
const embedding = await this.embed(text);
|
|
1444
|
+
results.push(embedding);
|
|
1445
|
+
} catch {
|
|
1446
|
+
results.push(null);
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
return results;
|
|
1450
|
+
}
|
|
1451
|
+
/**
|
|
1452
|
+
* Verifica se il servizio è disponibile.
|
|
1453
|
+
*/
|
|
1454
|
+
isAvailable() {
|
|
1455
|
+
return this.initialized && this.provider !== null;
|
|
1456
|
+
}
|
|
1457
|
+
/**
|
|
1458
|
+
* Nome del provider attivo.
|
|
1459
|
+
*/
|
|
1460
|
+
getProvider() {
|
|
1461
|
+
return this.provider;
|
|
1462
|
+
}
|
|
1463
|
+
/**
|
|
1464
|
+
* Dimensioni del vettore embedding.
|
|
1465
|
+
*/
|
|
1466
|
+
getDimensions() {
|
|
1467
|
+
return 384;
|
|
1468
|
+
}
|
|
1469
|
+
// --- Provider specifici ---
|
|
1470
|
+
async _embedFastembed(text) {
|
|
1471
|
+
const embeddings = this.model.embed([text], 1);
|
|
1472
|
+
for await (const batch of embeddings) {
|
|
1473
|
+
if (batch && batch.length > 0) {
|
|
1474
|
+
const vec = batch[0];
|
|
1475
|
+
return vec instanceof Float32Array ? vec : new Float32Array(vec);
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
return null;
|
|
1479
|
+
}
|
|
1480
|
+
async _embedTransformers(text) {
|
|
1481
|
+
const output = await this.model(text, {
|
|
1482
|
+
pooling: "mean",
|
|
1483
|
+
normalize: true
|
|
1484
|
+
});
|
|
1485
|
+
if (output?.data) {
|
|
1486
|
+
return output.data instanceof Float32Array ? output.data : new Float32Array(output.data);
|
|
1487
|
+
}
|
|
1488
|
+
return null;
|
|
1489
|
+
}
|
|
1490
|
+
};
|
|
1491
|
+
var embeddingService = null;
|
|
1492
|
+
function getEmbeddingService() {
|
|
1493
|
+
if (!embeddingService) {
|
|
1494
|
+
embeddingService = new EmbeddingService();
|
|
1495
|
+
}
|
|
1496
|
+
return embeddingService;
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
// src/services/search/VectorSearch.ts
|
|
1500
|
+
function cosineSimilarity(a, b) {
|
|
1501
|
+
if (a.length !== b.length) return 0;
|
|
1502
|
+
let dotProduct = 0;
|
|
1503
|
+
let normA = 0;
|
|
1504
|
+
let normB = 0;
|
|
1505
|
+
for (let i = 0; i < a.length; i++) {
|
|
1506
|
+
dotProduct += a[i] * b[i];
|
|
1507
|
+
normA += a[i] * a[i];
|
|
1508
|
+
normB += b[i] * b[i];
|
|
1509
|
+
}
|
|
1510
|
+
const denominator = Math.sqrt(normA) * Math.sqrt(normB);
|
|
1511
|
+
if (denominator === 0) return 0;
|
|
1512
|
+
return dotProduct / denominator;
|
|
1513
|
+
}
|
|
1514
|
+
function float32ToBuffer(arr) {
|
|
1515
|
+
return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength);
|
|
1516
|
+
}
|
|
1517
|
+
function bufferToFloat32(buf) {
|
|
1518
|
+
const arrayBuffer = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
1519
|
+
return new Float32Array(arrayBuffer);
|
|
1520
|
+
}
|
|
1521
|
+
var VectorSearch = class {
|
|
1522
|
+
/**
|
|
1523
|
+
* Ricerca semantica: calcola cosine similarity tra query e tutti gli embeddings.
|
|
1524
|
+
*/
|
|
1525
|
+
async search(db, queryEmbedding, options = {}) {
|
|
1526
|
+
const limit = options.limit || 10;
|
|
1527
|
+
const threshold = options.threshold || 0.3;
|
|
1528
|
+
try {
|
|
1529
|
+
let sql = `
|
|
1530
|
+
SELECT e.observation_id, e.embedding,
|
|
1531
|
+
o.title, o.text, o.type, o.project, o.created_at, o.created_at_epoch
|
|
1532
|
+
FROM observation_embeddings e
|
|
1533
|
+
JOIN observations o ON o.id = e.observation_id
|
|
1534
|
+
`;
|
|
1535
|
+
const params = [];
|
|
1536
|
+
if (options.project) {
|
|
1537
|
+
sql += " WHERE o.project = ?";
|
|
1538
|
+
params.push(options.project);
|
|
1539
|
+
}
|
|
1540
|
+
const rows = db.query(sql).all(...params);
|
|
1541
|
+
const scored = [];
|
|
1542
|
+
for (const row of rows) {
|
|
1543
|
+
const embedding = bufferToFloat32(row.embedding);
|
|
1544
|
+
const similarity = cosineSimilarity(queryEmbedding, embedding);
|
|
1545
|
+
if (similarity >= threshold) {
|
|
1546
|
+
scored.push({
|
|
1547
|
+
id: row.observation_id,
|
|
1548
|
+
observationId: row.observation_id,
|
|
1549
|
+
similarity,
|
|
1550
|
+
title: row.title,
|
|
1551
|
+
text: row.text,
|
|
1552
|
+
type: row.type,
|
|
1553
|
+
project: row.project,
|
|
1554
|
+
created_at: row.created_at,
|
|
1555
|
+
created_at_epoch: row.created_at_epoch
|
|
1556
|
+
});
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
scored.sort((a, b) => b.similarity - a.similarity);
|
|
1560
|
+
return scored.slice(0, limit);
|
|
1561
|
+
} catch (error) {
|
|
1562
|
+
logger.error("VECTOR", `Errore ricerca vettoriale: ${error}`);
|
|
1563
|
+
return [];
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
/**
|
|
1567
|
+
* Salva embedding per un'osservazione.
|
|
1568
|
+
*/
|
|
1569
|
+
async storeEmbedding(db, observationId, embedding, model) {
|
|
1570
|
+
try {
|
|
1571
|
+
const blob = float32ToBuffer(embedding);
|
|
1572
|
+
db.query(`
|
|
1573
|
+
INSERT OR REPLACE INTO observation_embeddings
|
|
1574
|
+
(observation_id, embedding, model, dimensions, created_at)
|
|
1575
|
+
VALUES (?, ?, ?, ?, ?)
|
|
1576
|
+
`).run(
|
|
1577
|
+
observationId,
|
|
1578
|
+
blob,
|
|
1579
|
+
model,
|
|
1580
|
+
embedding.length,
|
|
1581
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
1582
|
+
);
|
|
1583
|
+
logger.debug("VECTOR", `Embedding salvato per osservazione ${observationId}`);
|
|
1584
|
+
} catch (error) {
|
|
1585
|
+
logger.error("VECTOR", `Errore salvataggio embedding: ${error}`);
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
/**
|
|
1589
|
+
* Genera embeddings per osservazioni che non li hanno ancora.
|
|
1590
|
+
*/
|
|
1591
|
+
async backfillEmbeddings(db, batchSize = 50) {
|
|
1592
|
+
const embeddingService2 = getEmbeddingService();
|
|
1593
|
+
if (!await embeddingService2.initialize()) {
|
|
1594
|
+
logger.warn("VECTOR", "Embedding service non disponibile, backfill saltato");
|
|
1595
|
+
return 0;
|
|
1596
|
+
}
|
|
1597
|
+
const rows = db.query(`
|
|
1598
|
+
SELECT o.id, o.title, o.text, o.narrative, o.concepts
|
|
1599
|
+
FROM observations o
|
|
1600
|
+
LEFT JOIN observation_embeddings e ON e.observation_id = o.id
|
|
1601
|
+
WHERE e.observation_id IS NULL
|
|
1602
|
+
ORDER BY o.created_at_epoch DESC
|
|
1603
|
+
LIMIT ?
|
|
1604
|
+
`).all(batchSize);
|
|
1605
|
+
if (rows.length === 0) return 0;
|
|
1606
|
+
let count = 0;
|
|
1607
|
+
const model = embeddingService2.getProvider() || "unknown";
|
|
1608
|
+
for (const row of rows) {
|
|
1609
|
+
const parts = [row.title];
|
|
1610
|
+
if (row.text) parts.push(row.text);
|
|
1611
|
+
if (row.narrative) parts.push(row.narrative);
|
|
1612
|
+
if (row.concepts) parts.push(row.concepts);
|
|
1613
|
+
const fullText = parts.join(" ").substring(0, 2e3);
|
|
1614
|
+
const embedding = await embeddingService2.embed(fullText);
|
|
1615
|
+
if (embedding) {
|
|
1616
|
+
await this.storeEmbedding(db, row.id, embedding, model);
|
|
1617
|
+
count++;
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
logger.info("VECTOR", `Backfill completato: ${count}/${rows.length} embeddings generati`);
|
|
1621
|
+
return count;
|
|
1622
|
+
}
|
|
1623
|
+
/**
|
|
1624
|
+
* Statistiche sugli embeddings.
|
|
1625
|
+
*/
|
|
1626
|
+
getStats(db) {
|
|
1627
|
+
try {
|
|
1628
|
+
const totalRow = db.query("SELECT COUNT(*) as count FROM observations").get();
|
|
1629
|
+
const embeddedRow = db.query("SELECT COUNT(*) as count FROM observation_embeddings").get();
|
|
1630
|
+
const total = totalRow?.count || 0;
|
|
1631
|
+
const embedded = embeddedRow?.count || 0;
|
|
1632
|
+
const percentage = total > 0 ? Math.round(embedded / total * 100) : 0;
|
|
1633
|
+
return { total, embedded, percentage };
|
|
1634
|
+
} catch {
|
|
1635
|
+
return { total: 0, embedded: 0, percentage: 0 };
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
};
|
|
1639
|
+
var vectorSearch = null;
|
|
1640
|
+
function getVectorSearch() {
|
|
1641
|
+
if (!vectorSearch) {
|
|
1642
|
+
vectorSearch = new VectorSearch();
|
|
1643
|
+
}
|
|
1644
|
+
return vectorSearch;
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
// src/services/search/HybridSearch.ts
|
|
1648
|
+
var HybridSearch = class {
|
|
1649
|
+
embeddingInitialized = false;
|
|
1650
|
+
/**
|
|
1651
|
+
* Inizializza il servizio di embedding (lazy, non bloccante)
|
|
1652
|
+
*/
|
|
1653
|
+
async initialize() {
|
|
1654
|
+
try {
|
|
1655
|
+
const embeddingService2 = getEmbeddingService();
|
|
1656
|
+
await embeddingService2.initialize();
|
|
1657
|
+
this.embeddingInitialized = embeddingService2.isAvailable();
|
|
1658
|
+
logger.info("SEARCH", `HybridSearch inizializzato (embedding: ${this.embeddingInitialized ? "attivo" : "disattivato"})`);
|
|
1659
|
+
} catch (error) {
|
|
1660
|
+
logger.warn("SEARCH", "Inizializzazione embedding fallita, uso solo FTS5", {}, error);
|
|
1661
|
+
this.embeddingInitialized = false;
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
/**
|
|
1665
|
+
* Ricerca ibrida con scoring a 4 segnali
|
|
1666
|
+
*/
|
|
1667
|
+
async search(db, query, options = {}) {
|
|
1668
|
+
const limit = options.limit || 10;
|
|
1669
|
+
const weights = options.weights || SEARCH_WEIGHTS;
|
|
1670
|
+
const targetProject = options.project || "";
|
|
1671
|
+
const rawItems = /* @__PURE__ */ new Map();
|
|
1672
|
+
if (this.embeddingInitialized) {
|
|
1673
|
+
try {
|
|
1674
|
+
const embeddingService2 = getEmbeddingService();
|
|
1675
|
+
const queryEmbedding = await embeddingService2.embed(query);
|
|
1676
|
+
if (queryEmbedding) {
|
|
1677
|
+
const vectorSearch2 = getVectorSearch();
|
|
1678
|
+
const vectorResults = await vectorSearch2.search(db, queryEmbedding, {
|
|
1679
|
+
project: options.project,
|
|
1680
|
+
limit: limit * 2,
|
|
1681
|
+
// Prendiamo piu risultati per il ranking
|
|
1682
|
+
threshold: 0.3
|
|
1683
|
+
});
|
|
1684
|
+
for (const hit of vectorResults) {
|
|
1685
|
+
rawItems.set(String(hit.observationId), {
|
|
1686
|
+
id: String(hit.observationId),
|
|
1687
|
+
title: hit.title,
|
|
1688
|
+
content: hit.text || "",
|
|
1689
|
+
type: hit.type,
|
|
1690
|
+
project: hit.project,
|
|
1691
|
+
created_at: hit.created_at,
|
|
1692
|
+
created_at_epoch: hit.created_at_epoch,
|
|
1693
|
+
semanticScore: hit.similarity,
|
|
1694
|
+
fts5Rank: null,
|
|
1695
|
+
source: "vector"
|
|
1696
|
+
});
|
|
1697
|
+
}
|
|
1698
|
+
logger.debug("SEARCH", `Vector search: ${vectorResults.length} risultati`);
|
|
1699
|
+
}
|
|
1700
|
+
} catch (error) {
|
|
1701
|
+
logger.warn("SEARCH", "Ricerca vettoriale fallita, uso solo keyword", {}, error);
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
try {
|
|
1705
|
+
const { searchObservationsFTSWithRank: searchObservationsFTSWithRank2 } = await Promise.resolve().then(() => (init_Search(), Search_exports));
|
|
1706
|
+
const keywordResults = searchObservationsFTSWithRank2(db, query, {
|
|
1707
|
+
project: options.project,
|
|
1708
|
+
limit: limit * 2
|
|
1709
|
+
});
|
|
1710
|
+
for (const obs of keywordResults) {
|
|
1711
|
+
const id = String(obs.id);
|
|
1712
|
+
const existing = rawItems.get(id);
|
|
1713
|
+
if (existing) {
|
|
1714
|
+
existing.fts5Rank = obs.fts5_rank;
|
|
1715
|
+
existing.source = "vector";
|
|
1716
|
+
} else {
|
|
1717
|
+
rawItems.set(id, {
|
|
1718
|
+
id,
|
|
1719
|
+
title: obs.title,
|
|
1720
|
+
content: obs.text || obs.narrative || "",
|
|
1721
|
+
type: obs.type,
|
|
1722
|
+
project: obs.project,
|
|
1723
|
+
created_at: obs.created_at,
|
|
1724
|
+
created_at_epoch: obs.created_at_epoch,
|
|
1725
|
+
semanticScore: 0,
|
|
1726
|
+
fts5Rank: obs.fts5_rank,
|
|
1727
|
+
source: "keyword"
|
|
1728
|
+
});
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
logger.debug("SEARCH", `Keyword search: ${keywordResults.length} risultati`);
|
|
1732
|
+
} catch (error) {
|
|
1733
|
+
logger.error("SEARCH", "Ricerca keyword fallita", {}, error);
|
|
1734
|
+
}
|
|
1735
|
+
if (rawItems.size === 0) return [];
|
|
1736
|
+
const allFTS5Ranks = Array.from(rawItems.values()).filter((item) => item.fts5Rank !== null).map((item) => item.fts5Rank);
|
|
1737
|
+
const scored = [];
|
|
1738
|
+
for (const item of rawItems.values()) {
|
|
1739
|
+
const signals = {
|
|
1740
|
+
semantic: item.semanticScore,
|
|
1741
|
+
fts5: item.fts5Rank !== null ? normalizeFTS5Rank(item.fts5Rank, allFTS5Ranks) : 0,
|
|
1742
|
+
recency: recencyScore(item.created_at_epoch),
|
|
1743
|
+
projectMatch: targetProject ? projectMatchScore(item.project, targetProject) : 0
|
|
1744
|
+
};
|
|
1745
|
+
const score = computeCompositeScore(signals, weights);
|
|
1746
|
+
const isHybrid = item.semanticScore > 0 && item.fts5Rank !== null;
|
|
1747
|
+
const hybridBoost = isHybrid ? 1.15 : 1;
|
|
1748
|
+
const finalScore = Math.min(1, score * hybridBoost * knowledgeTypeBoost(item.type));
|
|
1749
|
+
scored.push({
|
|
1750
|
+
id: item.id,
|
|
1751
|
+
title: item.title,
|
|
1752
|
+
content: item.content,
|
|
1753
|
+
type: item.type,
|
|
1754
|
+
project: item.project,
|
|
1755
|
+
created_at: item.created_at,
|
|
1756
|
+
created_at_epoch: item.created_at_epoch,
|
|
1757
|
+
score: finalScore,
|
|
1758
|
+
source: isHybrid ? "hybrid" : item.source,
|
|
1759
|
+
signals
|
|
1760
|
+
});
|
|
1761
|
+
}
|
|
1762
|
+
scored.sort((a, b) => b.score - a.score);
|
|
1763
|
+
const finalResults = scored.slice(0, limit);
|
|
1764
|
+
if (finalResults.length > 0) {
|
|
1765
|
+
try {
|
|
1766
|
+
const { updateLastAccessed: updateLastAccessed3 } = await Promise.resolve().then(() => (init_Observations(), Observations_exports));
|
|
1767
|
+
const ids = finalResults.map((r) => parseInt(r.id, 10)).filter((id) => id > 0);
|
|
1768
|
+
if (ids.length > 0) {
|
|
1769
|
+
updateLastAccessed3(db, ids);
|
|
1770
|
+
}
|
|
1771
|
+
} catch {
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
return finalResults;
|
|
1775
|
+
}
|
|
1776
|
+
};
|
|
1777
|
+
var hybridSearch = null;
|
|
1778
|
+
function getHybridSearch() {
|
|
1779
|
+
if (!hybridSearch) {
|
|
1780
|
+
hybridSearch = new HybridSearch();
|
|
1781
|
+
}
|
|
1782
|
+
return hybridSearch;
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
// src/types/worker-types.ts
|
|
1786
|
+
var KNOWLEDGE_TYPES = ["constraint", "decision", "heuristic", "rejected"];
|
|
1787
|
+
|
|
1788
|
+
// src/sdk/index.ts
|
|
1789
|
+
var KiroMemorySDK = class {
|
|
1790
|
+
db;
|
|
1791
|
+
project;
|
|
1792
|
+
constructor(config = {}) {
|
|
1793
|
+
this.db = new KiroMemoryDatabase(config.dataDir, config.skipMigrations || false);
|
|
1794
|
+
this.project = config.project || this.detectProject();
|
|
1795
|
+
}
|
|
1796
|
+
detectProject() {
|
|
1797
|
+
try {
|
|
1798
|
+
const { execSync } = __require("child_process");
|
|
1799
|
+
const gitRoot = execSync("git rev-parse --show-toplevel", {
|
|
1800
|
+
cwd: process.cwd(),
|
|
1801
|
+
encoding: "utf8",
|
|
1802
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
1016
1803
|
}).trim();
|
|
1017
1804
|
return gitRoot.split("/").pop() || "default";
|
|
1018
1805
|
} catch {
|
|
@@ -1023,22 +1810,69 @@ var KiroMemorySDK = class {
|
|
|
1023
1810
|
* Get context for the current project
|
|
1024
1811
|
*/
|
|
1025
1812
|
async getContext() {
|
|
1026
|
-
const { getObservationsByProject: getObservationsByProject2 } = await Promise.resolve().then(() => (init_Observations(), Observations_exports));
|
|
1027
|
-
const { getSummariesByProject: getSummariesByProject2 } = await Promise.resolve().then(() => (init_Summaries(), Summaries_exports));
|
|
1028
|
-
const { getPromptsByProject: getPromptsByProject2 } = await Promise.resolve().then(() => (init_Prompts(), Prompts_exports));
|
|
1029
1813
|
return {
|
|
1030
1814
|
project: this.project,
|
|
1031
|
-
relevantObservations:
|
|
1032
|
-
relevantSummaries:
|
|
1033
|
-
recentPrompts:
|
|
1815
|
+
relevantObservations: getObservationsByProject(this.db.db, this.project, 20),
|
|
1816
|
+
relevantSummaries: getSummariesByProject(this.db.db, this.project, 5),
|
|
1817
|
+
recentPrompts: getPromptsByProject(this.db.db, this.project, 10)
|
|
1034
1818
|
};
|
|
1035
1819
|
}
|
|
1820
|
+
/**
|
|
1821
|
+
* Valida input per storeObservation
|
|
1822
|
+
*/
|
|
1823
|
+
validateObservationInput(data) {
|
|
1824
|
+
if (!data.type || typeof data.type !== "string" || data.type.length > 100) {
|
|
1825
|
+
throw new Error("type \xE8 obbligatorio (stringa, max 100 caratteri)");
|
|
1826
|
+
}
|
|
1827
|
+
if (!data.title || typeof data.title !== "string" || data.title.length > 500) {
|
|
1828
|
+
throw new Error("title \xE8 obbligatorio (stringa, max 500 caratteri)");
|
|
1829
|
+
}
|
|
1830
|
+
if (!data.content || typeof data.content !== "string" || data.content.length > 1e5) {
|
|
1831
|
+
throw new Error("content \xE8 obbligatorio (stringa, max 100KB)");
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
/**
|
|
1835
|
+
* Valida input per storeSummary
|
|
1836
|
+
*/
|
|
1837
|
+
validateSummaryInput(data) {
|
|
1838
|
+
const MAX = 5e4;
|
|
1839
|
+
for (const [key, val] of Object.entries(data)) {
|
|
1840
|
+
if (val !== void 0 && val !== null) {
|
|
1841
|
+
if (typeof val !== "string") throw new Error(`${key} deve essere una stringa`);
|
|
1842
|
+
if (val.length > MAX) throw new Error(`${key} troppo grande (max 50KB)`);
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
/**
|
|
1847
|
+
* Genera e salva embedding per un'osservazione (fire-and-forget, non blocca)
|
|
1848
|
+
*/
|
|
1849
|
+
async generateEmbeddingAsync(observationId, title, content, concepts) {
|
|
1850
|
+
try {
|
|
1851
|
+
const embeddingService2 = getEmbeddingService();
|
|
1852
|
+
if (!embeddingService2.isAvailable()) return;
|
|
1853
|
+
const parts = [title, content];
|
|
1854
|
+
if (concepts?.length) parts.push(concepts.join(", "));
|
|
1855
|
+
const fullText = parts.join(" ").substring(0, 2e3);
|
|
1856
|
+
const embedding = await embeddingService2.embed(fullText);
|
|
1857
|
+
if (embedding) {
|
|
1858
|
+
const vectorSearch2 = getVectorSearch();
|
|
1859
|
+
await vectorSearch2.storeEmbedding(
|
|
1860
|
+
this.db.db,
|
|
1861
|
+
observationId,
|
|
1862
|
+
embedding,
|
|
1863
|
+
embeddingService2.getProvider() || "unknown"
|
|
1864
|
+
);
|
|
1865
|
+
}
|
|
1866
|
+
} catch (error) {
|
|
1867
|
+
logger.debug("SDK", `Embedding generation fallita per obs ${observationId}: ${error}`);
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1036
1870
|
/**
|
|
1037
1871
|
* Store a new observation
|
|
1038
1872
|
*/
|
|
1039
1873
|
async storeObservation(data) {
|
|
1040
|
-
|
|
1041
|
-
|
|
1874
|
+
this.validateObservationInput(data);
|
|
1875
|
+
const observationId = createObservation(
|
|
1042
1876
|
this.db.db,
|
|
1043
1877
|
"sdk-" + Date.now(),
|
|
1044
1878
|
this.project,
|
|
@@ -1059,13 +1893,77 @@ var KiroMemorySDK = class {
|
|
|
1059
1893
|
0
|
|
1060
1894
|
// prompt_number
|
|
1061
1895
|
);
|
|
1896
|
+
this.generateEmbeddingAsync(observationId, data.title, data.content, data.concepts).catch(() => {
|
|
1897
|
+
});
|
|
1898
|
+
return observationId;
|
|
1899
|
+
}
|
|
1900
|
+
/**
|
|
1901
|
+
* Salva conoscenza strutturata (constraint, decision, heuristic, rejected).
|
|
1902
|
+
* Usa il campo `type` per il knowledgeType e `facts` per i metadati JSON.
|
|
1903
|
+
*/
|
|
1904
|
+
async storeKnowledge(data) {
|
|
1905
|
+
if (!KNOWLEDGE_TYPES.includes(data.knowledgeType)) {
|
|
1906
|
+
throw new Error(`knowledgeType non valido: ${data.knowledgeType}. Valori ammessi: ${KNOWLEDGE_TYPES.join(", ")}`);
|
|
1907
|
+
}
|
|
1908
|
+
this.validateObservationInput({ type: data.knowledgeType, title: data.title, content: data.content });
|
|
1909
|
+
const metadata = (() => {
|
|
1910
|
+
switch (data.knowledgeType) {
|
|
1911
|
+
case "constraint":
|
|
1912
|
+
return {
|
|
1913
|
+
knowledgeType: "constraint",
|
|
1914
|
+
severity: data.metadata?.severity || "soft",
|
|
1915
|
+
reason: data.metadata?.reason
|
|
1916
|
+
};
|
|
1917
|
+
case "decision":
|
|
1918
|
+
return {
|
|
1919
|
+
knowledgeType: "decision",
|
|
1920
|
+
alternatives: data.metadata?.alternatives,
|
|
1921
|
+
reason: data.metadata?.reason
|
|
1922
|
+
};
|
|
1923
|
+
case "heuristic":
|
|
1924
|
+
return {
|
|
1925
|
+
knowledgeType: "heuristic",
|
|
1926
|
+
context: data.metadata?.context,
|
|
1927
|
+
confidence: data.metadata?.confidence
|
|
1928
|
+
};
|
|
1929
|
+
case "rejected":
|
|
1930
|
+
return {
|
|
1931
|
+
knowledgeType: "rejected",
|
|
1932
|
+
reason: data.metadata?.reason || "",
|
|
1933
|
+
alternatives: data.metadata?.alternatives
|
|
1934
|
+
};
|
|
1935
|
+
}
|
|
1936
|
+
})();
|
|
1937
|
+
const observationId = createObservation(
|
|
1938
|
+
this.db.db,
|
|
1939
|
+
"sdk-" + Date.now(),
|
|
1940
|
+
data.project || this.project,
|
|
1941
|
+
data.knowledgeType,
|
|
1942
|
+
// type = knowledgeType
|
|
1943
|
+
data.title,
|
|
1944
|
+
null,
|
|
1945
|
+
// subtitle
|
|
1946
|
+
data.content,
|
|
1947
|
+
null,
|
|
1948
|
+
// narrative
|
|
1949
|
+
JSON.stringify(metadata),
|
|
1950
|
+
// facts = metadati JSON
|
|
1951
|
+
data.concepts?.join(", ") || null,
|
|
1952
|
+
data.files?.join(", ") || null,
|
|
1953
|
+
data.files?.join(", ") || null,
|
|
1954
|
+
0
|
|
1955
|
+
// prompt_number
|
|
1956
|
+
);
|
|
1957
|
+
this.generateEmbeddingAsync(observationId, data.title, data.content, data.concepts).catch(() => {
|
|
1958
|
+
});
|
|
1959
|
+
return observationId;
|
|
1062
1960
|
}
|
|
1063
1961
|
/**
|
|
1064
1962
|
* Store a session summary
|
|
1065
1963
|
*/
|
|
1066
1964
|
async storeSummary(data) {
|
|
1067
|
-
|
|
1068
|
-
return
|
|
1965
|
+
this.validateSummaryInput(data);
|
|
1966
|
+
return createSummary(
|
|
1069
1967
|
this.db.db,
|
|
1070
1968
|
"sdk-" + Date.now(),
|
|
1071
1969
|
this.project,
|
|
@@ -1081,61 +1979,52 @@ var KiroMemorySDK = class {
|
|
|
1081
1979
|
* Search across all stored context
|
|
1082
1980
|
*/
|
|
1083
1981
|
async search(query) {
|
|
1084
|
-
const { searchObservations: searchObservations2 } = await Promise.resolve().then(() => (init_Observations(), Observations_exports));
|
|
1085
|
-
const { searchSummaries: searchSummaries2 } = await Promise.resolve().then(() => (init_Summaries(), Summaries_exports));
|
|
1086
1982
|
return {
|
|
1087
|
-
observations:
|
|
1088
|
-
summaries:
|
|
1983
|
+
observations: searchObservations(this.db.db, query, this.project),
|
|
1984
|
+
summaries: searchSummaries(this.db.db, query, this.project)
|
|
1089
1985
|
};
|
|
1090
1986
|
}
|
|
1091
1987
|
/**
|
|
1092
1988
|
* Get recent observations
|
|
1093
1989
|
*/
|
|
1094
1990
|
async getRecentObservations(limit = 10) {
|
|
1095
|
-
|
|
1096
|
-
return getObservationsByProject2(this.db.db, this.project, limit);
|
|
1991
|
+
return getObservationsByProject(this.db.db, this.project, limit);
|
|
1097
1992
|
}
|
|
1098
1993
|
/**
|
|
1099
1994
|
* Get recent summaries
|
|
1100
1995
|
*/
|
|
1101
1996
|
async getRecentSummaries(limit = 5) {
|
|
1102
|
-
|
|
1103
|
-
return getSummariesByProject2(this.db.db, this.project, limit);
|
|
1997
|
+
return getSummariesByProject(this.db.db, this.project, limit);
|
|
1104
1998
|
}
|
|
1105
1999
|
/**
|
|
1106
2000
|
* Advanced search with FTS5 and filters
|
|
1107
2001
|
*/
|
|
1108
2002
|
async searchAdvanced(query, filters = {}) {
|
|
1109
|
-
const { searchObservationsFTS: searchObservationsFTS2 } = await Promise.resolve().then(() => (init_Search(), Search_exports));
|
|
1110
|
-
const { searchSummariesFiltered: searchSummariesFiltered2 } = await Promise.resolve().then(() => (init_Search(), Search_exports));
|
|
1111
2003
|
const projectFilters = { ...filters, project: filters.project || this.project };
|
|
1112
2004
|
return {
|
|
1113
|
-
observations:
|
|
1114
|
-
summaries:
|
|
2005
|
+
observations: searchObservationsFTS(this.db.db, query, projectFilters),
|
|
2006
|
+
summaries: searchSummariesFiltered(this.db.db, query, projectFilters)
|
|
1115
2007
|
};
|
|
1116
2008
|
}
|
|
1117
2009
|
/**
|
|
1118
2010
|
* Retrieve observations by ID (batch)
|
|
1119
2011
|
*/
|
|
1120
2012
|
async getObservationsByIds(ids) {
|
|
1121
|
-
|
|
1122
|
-
return getObservationsByIds2(this.db.db, ids);
|
|
2013
|
+
return getObservationsByIds(this.db.db, ids);
|
|
1123
2014
|
}
|
|
1124
2015
|
/**
|
|
1125
2016
|
* Timeline: chronological context around an observation
|
|
1126
2017
|
*/
|
|
1127
2018
|
async getTimeline(anchorId, depthBefore = 5, depthAfter = 5) {
|
|
1128
|
-
|
|
1129
|
-
return getTimeline2(this.db.db, anchorId, depthBefore, depthAfter);
|
|
2019
|
+
return getTimeline(this.db.db, anchorId, depthBefore, depthAfter);
|
|
1130
2020
|
}
|
|
1131
2021
|
/**
|
|
1132
2022
|
* Create or retrieve a session for the current project
|
|
1133
2023
|
*/
|
|
1134
2024
|
async getOrCreateSession(contentSessionId) {
|
|
1135
|
-
|
|
1136
|
-
let session = getSessionByContentId2(this.db.db, contentSessionId);
|
|
2025
|
+
let session = getSessionByContentId(this.db.db, contentSessionId);
|
|
1137
2026
|
if (!session) {
|
|
1138
|
-
const id =
|
|
2027
|
+
const id = createSession(this.db.db, contentSessionId, this.project, "");
|
|
1139
2028
|
session = {
|
|
1140
2029
|
id,
|
|
1141
2030
|
content_session_id: contentSessionId,
|
|
@@ -1155,15 +2044,13 @@ var KiroMemorySDK = class {
|
|
|
1155
2044
|
* Store a user prompt
|
|
1156
2045
|
*/
|
|
1157
2046
|
async storePrompt(contentSessionId, promptNumber, text) {
|
|
1158
|
-
|
|
1159
|
-
return createPrompt2(this.db.db, contentSessionId, this.project, promptNumber, text);
|
|
2047
|
+
return createPrompt(this.db.db, contentSessionId, this.project, promptNumber, text);
|
|
1160
2048
|
}
|
|
1161
2049
|
/**
|
|
1162
2050
|
* Complete a session
|
|
1163
2051
|
*/
|
|
1164
2052
|
async completeSession(sessionId) {
|
|
1165
|
-
|
|
1166
|
-
completeSession2(this.db.db, sessionId);
|
|
2053
|
+
completeSession(this.db.db, sessionId);
|
|
1167
2054
|
}
|
|
1168
2055
|
/**
|
|
1169
2056
|
* Getter for current project name
|
|
@@ -1171,6 +2058,227 @@ var KiroMemorySDK = class {
|
|
|
1171
2058
|
getProject() {
|
|
1172
2059
|
return this.project;
|
|
1173
2060
|
}
|
|
2061
|
+
/**
|
|
2062
|
+
* Ricerca ibrida: vector search + keyword FTS5
|
|
2063
|
+
* Richiede inizializzazione HybridSearch (embedding service)
|
|
2064
|
+
*/
|
|
2065
|
+
async hybridSearch(query, options = {}) {
|
|
2066
|
+
const hybridSearch2 = getHybridSearch();
|
|
2067
|
+
return hybridSearch2.search(this.db.db, query, {
|
|
2068
|
+
project: this.project,
|
|
2069
|
+
limit: options.limit || 10
|
|
2070
|
+
});
|
|
2071
|
+
}
|
|
2072
|
+
/**
|
|
2073
|
+
* Ricerca solo semantica (vector search)
|
|
2074
|
+
* Ritorna risultati basati su similarità coseno con gli embeddings
|
|
2075
|
+
*/
|
|
2076
|
+
async semanticSearch(query, options = {}) {
|
|
2077
|
+
const embeddingService2 = getEmbeddingService();
|
|
2078
|
+
if (!embeddingService2.isAvailable()) {
|
|
2079
|
+
await embeddingService2.initialize();
|
|
2080
|
+
}
|
|
2081
|
+
if (!embeddingService2.isAvailable()) return [];
|
|
2082
|
+
const queryEmbedding = await embeddingService2.embed(query);
|
|
2083
|
+
if (!queryEmbedding) return [];
|
|
2084
|
+
const vectorSearch2 = getVectorSearch();
|
|
2085
|
+
const results = await vectorSearch2.search(this.db.db, queryEmbedding, {
|
|
2086
|
+
project: this.project,
|
|
2087
|
+
limit: options.limit || 10,
|
|
2088
|
+
threshold: options.threshold || 0.3
|
|
2089
|
+
});
|
|
2090
|
+
return results.map((r) => ({
|
|
2091
|
+
id: String(r.observationId),
|
|
2092
|
+
title: r.title,
|
|
2093
|
+
content: r.text || "",
|
|
2094
|
+
type: r.type,
|
|
2095
|
+
project: r.project,
|
|
2096
|
+
created_at: r.created_at,
|
|
2097
|
+
created_at_epoch: r.created_at_epoch,
|
|
2098
|
+
score: r.similarity,
|
|
2099
|
+
source: "vector",
|
|
2100
|
+
signals: {
|
|
2101
|
+
semantic: r.similarity,
|
|
2102
|
+
fts5: 0,
|
|
2103
|
+
recency: recencyScore(r.created_at_epoch),
|
|
2104
|
+
projectMatch: projectMatchScore(r.project, this.project)
|
|
2105
|
+
}
|
|
2106
|
+
}));
|
|
2107
|
+
}
|
|
2108
|
+
/**
|
|
2109
|
+
* Genera embeddings per osservazioni che non li hanno ancora
|
|
2110
|
+
*/
|
|
2111
|
+
async backfillEmbeddings(batchSize = 50) {
|
|
2112
|
+
const vectorSearch2 = getVectorSearch();
|
|
2113
|
+
return vectorSearch2.backfillEmbeddings(this.db.db, batchSize);
|
|
2114
|
+
}
|
|
2115
|
+
/**
|
|
2116
|
+
* Statistiche sugli embeddings nel database
|
|
2117
|
+
*/
|
|
2118
|
+
getEmbeddingStats() {
|
|
2119
|
+
const vectorSearch2 = getVectorSearch();
|
|
2120
|
+
return vectorSearch2.getStats(this.db.db);
|
|
2121
|
+
}
|
|
2122
|
+
/**
|
|
2123
|
+
* Inizializza il servizio di embedding (lazy, chiamare prima di hybridSearch)
|
|
2124
|
+
*/
|
|
2125
|
+
async initializeEmbeddings() {
|
|
2126
|
+
const hybridSearch2 = getHybridSearch();
|
|
2127
|
+
await hybridSearch2.initialize();
|
|
2128
|
+
return getEmbeddingService().isAvailable();
|
|
2129
|
+
}
|
|
2130
|
+
/**
|
|
2131
|
+
* Contesto smart con ranking a 4 segnali e budget token.
|
|
2132
|
+
*
|
|
2133
|
+
* Se query presente: usa HybridSearch con SEARCH_WEIGHTS.
|
|
2134
|
+
* Se senza query: ranking per recency + project match (CONTEXT_WEIGHTS).
|
|
2135
|
+
*/
|
|
2136
|
+
async getSmartContext(options = {}) {
|
|
2137
|
+
const tokenBudget = options.tokenBudget || parseInt(process.env.KIRO_MEMORY_CONTEXT_TOKENS || "0", 10) || 2e3;
|
|
2138
|
+
const summaries = getSummariesByProject(this.db.db, this.project, 5);
|
|
2139
|
+
let items;
|
|
2140
|
+
if (options.query) {
|
|
2141
|
+
const hybridSearch2 = getHybridSearch();
|
|
2142
|
+
const results = await hybridSearch2.search(this.db.db, options.query, {
|
|
2143
|
+
project: this.project,
|
|
2144
|
+
limit: 30
|
|
2145
|
+
});
|
|
2146
|
+
items = results.map((r) => ({
|
|
2147
|
+
id: parseInt(r.id, 10) || 0,
|
|
2148
|
+
title: r.title,
|
|
2149
|
+
content: r.content,
|
|
2150
|
+
type: r.type,
|
|
2151
|
+
project: r.project,
|
|
2152
|
+
created_at: r.created_at,
|
|
2153
|
+
created_at_epoch: r.created_at_epoch,
|
|
2154
|
+
score: r.score,
|
|
2155
|
+
signals: r.signals
|
|
2156
|
+
}));
|
|
2157
|
+
} else {
|
|
2158
|
+
const observations = getObservationsByProject(this.db.db, this.project, 30);
|
|
2159
|
+
items = observations.map((obs) => {
|
|
2160
|
+
const signals = {
|
|
2161
|
+
semantic: 0,
|
|
2162
|
+
fts5: 0,
|
|
2163
|
+
recency: recencyScore(obs.created_at_epoch),
|
|
2164
|
+
projectMatch: projectMatchScore(obs.project, this.project)
|
|
2165
|
+
};
|
|
2166
|
+
const baseScore = computeCompositeScore(signals, CONTEXT_WEIGHTS);
|
|
2167
|
+
const boostedScore = Math.min(1, baseScore * knowledgeTypeBoost(obs.type));
|
|
2168
|
+
return {
|
|
2169
|
+
id: obs.id,
|
|
2170
|
+
title: obs.title,
|
|
2171
|
+
content: obs.text || obs.narrative || "",
|
|
2172
|
+
type: obs.type,
|
|
2173
|
+
project: obs.project,
|
|
2174
|
+
created_at: obs.created_at,
|
|
2175
|
+
created_at_epoch: obs.created_at_epoch,
|
|
2176
|
+
score: boostedScore,
|
|
2177
|
+
signals
|
|
2178
|
+
};
|
|
2179
|
+
});
|
|
2180
|
+
items.sort((a, b) => b.score - a.score);
|
|
2181
|
+
}
|
|
2182
|
+
let tokensUsed = 0;
|
|
2183
|
+
for (const item of items) {
|
|
2184
|
+
tokensUsed += Math.ceil((item.title.length + item.content.length) / 4);
|
|
2185
|
+
if (tokensUsed > tokenBudget) break;
|
|
2186
|
+
}
|
|
2187
|
+
return {
|
|
2188
|
+
project: this.project,
|
|
2189
|
+
items,
|
|
2190
|
+
summaries,
|
|
2191
|
+
tokenBudget,
|
|
2192
|
+
tokensUsed: Math.min(tokensUsed, tokenBudget)
|
|
2193
|
+
};
|
|
2194
|
+
}
|
|
2195
|
+
/**
|
|
2196
|
+
* Rileva osservazioni stale (file modificati dopo la creazione) e le marca nel DB.
|
|
2197
|
+
* Ritorna il numero di osservazioni marcate come stale.
|
|
2198
|
+
*/
|
|
2199
|
+
async detectStaleObservations() {
|
|
2200
|
+
const staleObs = getStaleObservations(this.db.db, this.project);
|
|
2201
|
+
if (staleObs.length > 0) {
|
|
2202
|
+
const ids = staleObs.map((o) => o.id);
|
|
2203
|
+
markObservationsStale(this.db.db, ids, true);
|
|
2204
|
+
}
|
|
2205
|
+
return staleObs.length;
|
|
2206
|
+
}
|
|
2207
|
+
/**
|
|
2208
|
+
* Consolida osservazioni duplicate sullo stesso file e tipo.
|
|
2209
|
+
* Raggruppa per (project, type, files_modified), mantiene la piu recente.
|
|
2210
|
+
*/
|
|
2211
|
+
async consolidateObservations(options = {}) {
|
|
2212
|
+
return consolidateObservations(this.db.db, this.project, options);
|
|
2213
|
+
}
|
|
2214
|
+
/**
|
|
2215
|
+
* Statistiche decay: totale, stale, mai accedute, accedute di recente.
|
|
2216
|
+
*/
|
|
2217
|
+
async getDecayStats() {
|
|
2218
|
+
const total = this.db.db.query(
|
|
2219
|
+
"SELECT COUNT(*) as count FROM observations WHERE project = ?"
|
|
2220
|
+
).get(this.project)?.count || 0;
|
|
2221
|
+
const stale = this.db.db.query(
|
|
2222
|
+
"SELECT COUNT(*) as count FROM observations WHERE project = ? AND is_stale = 1"
|
|
2223
|
+
).get(this.project)?.count || 0;
|
|
2224
|
+
const neverAccessed = this.db.db.query(
|
|
2225
|
+
"SELECT COUNT(*) as count FROM observations WHERE project = ? AND last_accessed_epoch IS NULL"
|
|
2226
|
+
).get(this.project)?.count || 0;
|
|
2227
|
+
const recentThreshold = Date.now() - 48 * 60 * 60 * 1e3;
|
|
2228
|
+
const recentlyAccessed = this.db.db.query(
|
|
2229
|
+
"SELECT COUNT(*) as count FROM observations WHERE project = ? AND last_accessed_epoch > ?"
|
|
2230
|
+
).get(this.project, recentThreshold)?.count || 0;
|
|
2231
|
+
return { total, stale, neverAccessed, recentlyAccessed };
|
|
2232
|
+
}
|
|
2233
|
+
/**
|
|
2234
|
+
* Crea un checkpoint strutturato per resume sessione.
|
|
2235
|
+
* Salva automaticamente un context_snapshot con le ultime 10 osservazioni.
|
|
2236
|
+
*/
|
|
2237
|
+
async createCheckpoint(sessionId, data) {
|
|
2238
|
+
const recentObs = getObservationsByProject(this.db.db, this.project, 10);
|
|
2239
|
+
const contextSnapshot = JSON.stringify(
|
|
2240
|
+
recentObs.map((o) => ({ id: o.id, type: o.type, title: o.title, text: o.text?.substring(0, 200) }))
|
|
2241
|
+
);
|
|
2242
|
+
return createCheckpoint(this.db.db, sessionId, this.project, {
|
|
2243
|
+
task: data.task,
|
|
2244
|
+
progress: data.progress,
|
|
2245
|
+
nextSteps: data.nextSteps,
|
|
2246
|
+
openQuestions: data.openQuestions,
|
|
2247
|
+
relevantFiles: data.relevantFiles?.join(", "),
|
|
2248
|
+
contextSnapshot
|
|
2249
|
+
});
|
|
2250
|
+
}
|
|
2251
|
+
/**
|
|
2252
|
+
* Recupera l'ultimo checkpoint di una sessione specifica.
|
|
2253
|
+
*/
|
|
2254
|
+
async getCheckpoint(sessionId) {
|
|
2255
|
+
return getLatestCheckpoint(this.db.db, sessionId);
|
|
2256
|
+
}
|
|
2257
|
+
/**
|
|
2258
|
+
* Recupera l'ultimo checkpoint per il progetto corrente.
|
|
2259
|
+
* Utile per resume automatico senza specificare session ID.
|
|
2260
|
+
*/
|
|
2261
|
+
async getLatestProjectCheckpoint() {
|
|
2262
|
+
return getLatestCheckpointByProject(this.db.db, this.project);
|
|
2263
|
+
}
|
|
2264
|
+
/**
|
|
2265
|
+
* Genera un report di attività per il progetto corrente.
|
|
2266
|
+
* Aggrega osservazioni, sessioni, summaries e file per un periodo temporale.
|
|
2267
|
+
*/
|
|
2268
|
+
async generateReport(options) {
|
|
2269
|
+
const now = /* @__PURE__ */ new Date();
|
|
2270
|
+
let startEpoch;
|
|
2271
|
+
let endEpoch = now.getTime();
|
|
2272
|
+
if (options?.startDate && options?.endDate) {
|
|
2273
|
+
startEpoch = options.startDate.getTime();
|
|
2274
|
+
endEpoch = options.endDate.getTime();
|
|
2275
|
+
} else {
|
|
2276
|
+
const period = options?.period || "weekly";
|
|
2277
|
+
const daysBack = period === "monthly" ? 30 : 7;
|
|
2278
|
+
startEpoch = endEpoch - daysBack * 24 * 60 * 60 * 1e3;
|
|
2279
|
+
}
|
|
2280
|
+
return getReportData(this.db.db, this.project, startEpoch, endEpoch);
|
|
2281
|
+
}
|
|
1174
2282
|
/**
|
|
1175
2283
|
* Getter for direct database access (for API routes)
|
|
1176
2284
|
*/
|
|
@@ -1190,13 +2298,22 @@ function createKiroMemory(config) {
|
|
|
1190
2298
|
|
|
1191
2299
|
// src/hooks/postToolUse.ts
|
|
1192
2300
|
runHook("postToolUse", async (input) => {
|
|
2301
|
+
if (input.hook_event_name === "afterFileEdit" && !input.tool_name) {
|
|
2302
|
+
input.tool_name = "Write";
|
|
2303
|
+
input.tool_input = { path: input.file_path };
|
|
2304
|
+
input.tool_response = { edits: input.edits };
|
|
2305
|
+
}
|
|
2306
|
+
if (input.hook_event_name === "afterShellExecution" && !input.tool_name) {
|
|
2307
|
+
input.tool_name = "Bash";
|
|
2308
|
+
input.tool_input = { command: input.command };
|
|
2309
|
+
}
|
|
1193
2310
|
if (!input.tool_name) return;
|
|
1194
|
-
const ignoredTools = ["introspect", "thinking", "todo"];
|
|
2311
|
+
const ignoredTools = ["introspect", "thinking", "todo", "TodoWrite"];
|
|
1195
2312
|
if (ignoredTools.includes(input.tool_name)) return;
|
|
1196
|
-
const readOnlyTools = ["glob", "grep", "fs_read", "read"];
|
|
2313
|
+
const readOnlyTools = ["glob", "grep", "fs_read", "read", "Read", "Glob", "Grep"];
|
|
1197
2314
|
if (readOnlyTools.includes(input.tool_name)) {
|
|
1198
2315
|
const project2 = detectProject(input.cwd);
|
|
1199
|
-
const sdk2 = createKiroMemory({ project: project2 });
|
|
2316
|
+
const sdk2 = createKiroMemory({ project: project2, skipMigrations: true });
|
|
1200
2317
|
try {
|
|
1201
2318
|
const files = extractFiles(input.tool_input, input.tool_response);
|
|
1202
2319
|
const query = input.tool_input?.pattern || input.tool_input?.regex || input.tool_input?.query || "";
|
|
@@ -1214,7 +2331,7 @@ runHook("postToolUse", async (input) => {
|
|
|
1214
2331
|
return;
|
|
1215
2332
|
}
|
|
1216
2333
|
const project = detectProject(input.cwd);
|
|
1217
|
-
const sdk = createKiroMemory({ project });
|
|
2334
|
+
const sdk = createKiroMemory({ project, skipMigrations: true });
|
|
1218
2335
|
try {
|
|
1219
2336
|
const title = buildTitle(input.tool_name, input.tool_input);
|
|
1220
2337
|
const content = buildContent(input.tool_name, input.tool_input, input.tool_response);
|
|
@@ -1234,19 +2351,30 @@ runHook("postToolUse", async (input) => {
|
|
|
1234
2351
|
function buildTitle(toolName, toolInput) {
|
|
1235
2352
|
if (!toolInput) return `Tool: ${toolName}`;
|
|
1236
2353
|
switch (toolName) {
|
|
2354
|
+
// Kiro CLI + Claude Code: scrittura file
|
|
1237
2355
|
case "fs_write":
|
|
1238
2356
|
case "write":
|
|
1239
|
-
|
|
2357
|
+
case "Write":
|
|
2358
|
+
case "Edit":
|
|
2359
|
+
case "NotebookEdit":
|
|
2360
|
+
return `Written: ${toolInput.path || toolInput.file_path || toolInput.notebook_path || "file"}`;
|
|
2361
|
+
// Kiro CLI + Claude Code: comandi shell
|
|
1240
2362
|
case "execute_bash":
|
|
1241
2363
|
case "shell":
|
|
2364
|
+
case "Bash":
|
|
1242
2365
|
return `Executed: ${(toolInput.command || "").substring(0, 80)}`;
|
|
2366
|
+
// Kiro CLI + Claude Code: ricerca web
|
|
1243
2367
|
case "web_search":
|
|
2368
|
+
case "WebSearch":
|
|
1244
2369
|
return `Searched: ${toolInput.query || ""}`;
|
|
1245
2370
|
case "web_fetch":
|
|
2371
|
+
case "WebFetch":
|
|
1246
2372
|
return `Fetch: ${toolInput.url || ""}`;
|
|
2373
|
+
// Kiro CLI + Claude Code: delegazione
|
|
1247
2374
|
case "delegate":
|
|
1248
2375
|
case "use_subagent":
|
|
1249
|
-
|
|
2376
|
+
case "Task":
|
|
2377
|
+
return `Delegated: ${toolInput.task || toolInput.prompt || toolInput.description || ""}`.substring(0, 100);
|
|
1250
2378
|
default:
|
|
1251
2379
|
return `${toolName}: ${JSON.stringify(toolInput).substring(0, 80)}`;
|
|
1252
2380
|
}
|
|
@@ -1266,6 +2394,7 @@ function buildContent(toolName, toolInput, toolResponse) {
|
|
|
1266
2394
|
}
|
|
1267
2395
|
function categorizeToolUse(toolName) {
|
|
1268
2396
|
const categories = {
|
|
2397
|
+
// Kiro CLI tool names
|
|
1269
2398
|
"fs_write": "file-write",
|
|
1270
2399
|
"write": "file-write",
|
|
1271
2400
|
"fs_read": "file-read",
|
|
@@ -1280,7 +2409,18 @@ function categorizeToolUse(toolName) {
|
|
|
1280
2409
|
"use_subagent": "delegation",
|
|
1281
2410
|
"use_aws": "cloud-operation",
|
|
1282
2411
|
"aws": "cloud-operation",
|
|
1283
|
-
"code": "code-intelligence"
|
|
2412
|
+
"code": "code-intelligence",
|
|
2413
|
+
// Claude Code tool names (PascalCase)
|
|
2414
|
+
"Write": "file-write",
|
|
2415
|
+
"Edit": "file-write",
|
|
2416
|
+
"NotebookEdit": "file-write",
|
|
2417
|
+
"Read": "file-read",
|
|
2418
|
+
"Glob": "file-read",
|
|
2419
|
+
"Grep": "file-read",
|
|
2420
|
+
"Bash": "command",
|
|
2421
|
+
"WebSearch": "research",
|
|
2422
|
+
"WebFetch": "research",
|
|
2423
|
+
"Task": "delegation"
|
|
1284
2424
|
};
|
|
1285
2425
|
return categories[toolName] || "tool-use";
|
|
1286
2426
|
}
|