kiro-memory 1.6.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 +105 -99
- package/package.json +14 -7
- package/plugin/dist/cli/contextkit.js +2661 -497
- package/plugin/dist/hooks/agentSpawn.js +1455 -189
- package/plugin/dist/hooks/kiro-hooks.js +1389 -156
- package/plugin/dist/hooks/postToolUse.js +1451 -174
- package/plugin/dist/hooks/stop.js +1426 -170
- package/plugin/dist/hooks/userPromptSubmit.js +1418 -170
- package/plugin/dist/index.js +1406 -172
- package/plugin/dist/sdk/index.js +1389 -155
- package/plugin/dist/servers/mcp-server.js +203 -2
- package/plugin/dist/services/search/EmbeddingService.js +363 -0
- package/plugin/dist/services/search/HybridSearch.js +703 -151
- package/plugin/dist/services/search/ScoringEngine.js +75 -0
- package/plugin/dist/services/search/VectorSearch.js +512 -0
- package/plugin/dist/services/search/index.js +776 -64
- package/plugin/dist/services/sqlite/Database.js +49 -0
- package/plugin/dist/services/sqlite/Observations.js +70 -6
- package/plugin/dist/services/sqlite/Search.js +92 -8
- package/plugin/dist/services/sqlite/Summaries.js +8 -5
- package/plugin/dist/services/sqlite/index.js +384 -18
- package/plugin/dist/types/worker-types.js +6 -0
- package/plugin/dist/viewer.js +369 -69
- package/plugin/dist/worker-service.js +1493 -145
package/plugin/dist/index.js
CHANGED
|
@@ -1,10 +1,376 @@
|
|
|
1
1
|
import { createRequire } from 'module';const require = createRequire(import.meta.url);
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
2
4
|
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
5
|
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
6
|
}) : x)(function(x) {
|
|
5
7
|
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
8
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
9
|
});
|
|
10
|
+
var __esm = (fn, res) => function __init() {
|
|
11
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
12
|
+
};
|
|
13
|
+
var __export = (target, all) => {
|
|
14
|
+
for (var name in all)
|
|
15
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// src/services/sqlite/Observations.ts
|
|
19
|
+
var Observations_exports = {};
|
|
20
|
+
__export(Observations_exports, {
|
|
21
|
+
consolidateObservations: () => consolidateObservations,
|
|
22
|
+
createObservation: () => createObservation,
|
|
23
|
+
deleteObservation: () => deleteObservation,
|
|
24
|
+
getObservationsByProject: () => getObservationsByProject,
|
|
25
|
+
getObservationsBySession: () => getObservationsBySession,
|
|
26
|
+
searchObservations: () => searchObservations,
|
|
27
|
+
updateLastAccessed: () => updateLastAccessed
|
|
28
|
+
});
|
|
29
|
+
function escapeLikePattern(input) {
|
|
30
|
+
return input.replace(/[%_\\]/g, "\\$&");
|
|
31
|
+
}
|
|
32
|
+
function createObservation(db, memorySessionId, project, type, title, subtitle, text, narrative, facts, concepts, filesRead, filesModified, promptNumber) {
|
|
33
|
+
const now = /* @__PURE__ */ new Date();
|
|
34
|
+
const result = db.run(
|
|
35
|
+
`INSERT INTO observations
|
|
36
|
+
(memory_session_id, project, type, title, subtitle, text, narrative, facts, concepts, files_read, files_modified, prompt_number, created_at, created_at_epoch)
|
|
37
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
38
|
+
[memorySessionId, project, type, title, subtitle, text, narrative, facts, concepts, filesRead, filesModified, promptNumber, now.toISOString(), now.getTime()]
|
|
39
|
+
);
|
|
40
|
+
return Number(result.lastInsertRowid);
|
|
41
|
+
}
|
|
42
|
+
function getObservationsBySession(db, memorySessionId) {
|
|
43
|
+
const query = db.query(
|
|
44
|
+
"SELECT * FROM observations WHERE memory_session_id = ? ORDER BY prompt_number ASC"
|
|
45
|
+
);
|
|
46
|
+
return query.all(memorySessionId);
|
|
47
|
+
}
|
|
48
|
+
function getObservationsByProject(db, project, limit = 100) {
|
|
49
|
+
const query = db.query(
|
|
50
|
+
"SELECT * FROM observations WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ?"
|
|
51
|
+
);
|
|
52
|
+
return query.all(project, limit);
|
|
53
|
+
}
|
|
54
|
+
function searchObservations(db, searchTerm, project) {
|
|
55
|
+
const sql = project ? `SELECT * FROM observations
|
|
56
|
+
WHERE project = ? AND (title LIKE ? ESCAPE '\\' OR text LIKE ? ESCAPE '\\' OR narrative LIKE ? ESCAPE '\\')
|
|
57
|
+
ORDER BY created_at_epoch DESC` : `SELECT * FROM observations
|
|
58
|
+
WHERE title LIKE ? ESCAPE '\\' OR text LIKE ? ESCAPE '\\' OR narrative LIKE ? ESCAPE '\\'
|
|
59
|
+
ORDER BY created_at_epoch DESC`;
|
|
60
|
+
const pattern = `%${escapeLikePattern(searchTerm)}%`;
|
|
61
|
+
const query = db.query(sql);
|
|
62
|
+
if (project) {
|
|
63
|
+
return query.all(project, pattern, pattern, pattern);
|
|
64
|
+
}
|
|
65
|
+
return query.all(pattern, pattern, pattern);
|
|
66
|
+
}
|
|
67
|
+
function deleteObservation(db, id) {
|
|
68
|
+
db.run("DELETE FROM observations WHERE id = ?", [id]);
|
|
69
|
+
}
|
|
70
|
+
function updateLastAccessed(db, ids) {
|
|
71
|
+
if (!Array.isArray(ids) || ids.length === 0) return;
|
|
72
|
+
const validIds = ids.filter((id) => typeof id === "number" && Number.isInteger(id) && id > 0).slice(0, 500);
|
|
73
|
+
if (validIds.length === 0) return;
|
|
74
|
+
const now = Date.now();
|
|
75
|
+
const placeholders = validIds.map(() => "?").join(",");
|
|
76
|
+
db.run(
|
|
77
|
+
`UPDATE observations SET last_accessed_epoch = ? WHERE id IN (${placeholders})`,
|
|
78
|
+
[now, ...validIds]
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
function consolidateObservations(db, project, options = {}) {
|
|
82
|
+
const minGroupSize = options.minGroupSize || 3;
|
|
83
|
+
const groups = db.query(`
|
|
84
|
+
SELECT type, files_modified, COUNT(*) as cnt, GROUP_CONCAT(id) as ids
|
|
85
|
+
FROM observations
|
|
86
|
+
WHERE project = ? AND files_modified IS NOT NULL AND files_modified != ''
|
|
87
|
+
GROUP BY type, files_modified
|
|
88
|
+
HAVING cnt >= ?
|
|
89
|
+
ORDER BY cnt DESC
|
|
90
|
+
`).all(project, minGroupSize);
|
|
91
|
+
if (groups.length === 0) return { merged: 0, removed: 0 };
|
|
92
|
+
let totalMerged = 0;
|
|
93
|
+
let totalRemoved = 0;
|
|
94
|
+
for (const group of groups) {
|
|
95
|
+
const obsIds = group.ids.split(",").map(Number);
|
|
96
|
+
const placeholders = obsIds.map(() => "?").join(",");
|
|
97
|
+
const observations = db.query(
|
|
98
|
+
`SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC`
|
|
99
|
+
).all(...obsIds);
|
|
100
|
+
if (observations.length < minGroupSize) continue;
|
|
101
|
+
if (options.dryRun) {
|
|
102
|
+
totalMerged += 1;
|
|
103
|
+
totalRemoved += observations.length - 1;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const keeper = observations[0];
|
|
107
|
+
const others = observations.slice(1);
|
|
108
|
+
const uniqueTexts = /* @__PURE__ */ new Set();
|
|
109
|
+
if (keeper.text) uniqueTexts.add(keeper.text);
|
|
110
|
+
for (const obs of others) {
|
|
111
|
+
if (obs.text && !uniqueTexts.has(obs.text)) {
|
|
112
|
+
uniqueTexts.add(obs.text);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const consolidatedText = Array.from(uniqueTexts).join("\n---\n").substring(0, 1e5);
|
|
116
|
+
db.run(
|
|
117
|
+
"UPDATE observations SET text = ?, title = ? WHERE id = ?",
|
|
118
|
+
[consolidatedText, `[consolidato x${observations.length}] ${keeper.title}`, keeper.id]
|
|
119
|
+
);
|
|
120
|
+
const removeIds = others.map((o) => o.id);
|
|
121
|
+
const removePlaceholders = removeIds.map(() => "?").join(",");
|
|
122
|
+
db.run(`DELETE FROM observations WHERE id IN (${removePlaceholders})`, removeIds);
|
|
123
|
+
db.run(`DELETE FROM observation_embeddings WHERE observation_id IN (${removePlaceholders})`, removeIds);
|
|
124
|
+
totalMerged += 1;
|
|
125
|
+
totalRemoved += removeIds.length;
|
|
126
|
+
}
|
|
127
|
+
return { merged: totalMerged, removed: totalRemoved };
|
|
128
|
+
}
|
|
129
|
+
var init_Observations = __esm({
|
|
130
|
+
"src/services/sqlite/Observations.ts"() {
|
|
131
|
+
"use strict";
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// src/services/sqlite/Search.ts
|
|
136
|
+
var Search_exports = {};
|
|
137
|
+
__export(Search_exports, {
|
|
138
|
+
getObservationsByIds: () => getObservationsByIds,
|
|
139
|
+
getProjectStats: () => getProjectStats,
|
|
140
|
+
getStaleObservations: () => getStaleObservations,
|
|
141
|
+
getTimeline: () => getTimeline,
|
|
142
|
+
markObservationsStale: () => markObservationsStale,
|
|
143
|
+
searchObservationsFTS: () => searchObservationsFTS,
|
|
144
|
+
searchObservationsFTSWithRank: () => searchObservationsFTSWithRank,
|
|
145
|
+
searchObservationsLIKE: () => searchObservationsLIKE,
|
|
146
|
+
searchSummariesFiltered: () => searchSummariesFiltered
|
|
147
|
+
});
|
|
148
|
+
import { existsSync as existsSync3, statSync } from "fs";
|
|
149
|
+
function escapeLikePattern3(input) {
|
|
150
|
+
return input.replace(/[%_\\]/g, "\\$&");
|
|
151
|
+
}
|
|
152
|
+
function sanitizeFTS5Query(query) {
|
|
153
|
+
const trimmed = query.length > 1e4 ? query.substring(0, 1e4) : query;
|
|
154
|
+
const terms = trimmed.replace(/[""]/g, "").split(/\s+/).filter((t) => t.length > 0).slice(0, 100).map((t) => `"${t}"`);
|
|
155
|
+
return terms.join(" ");
|
|
156
|
+
}
|
|
157
|
+
function searchObservationsFTS(db, query, filters = {}) {
|
|
158
|
+
const limit = filters.limit || 50;
|
|
159
|
+
try {
|
|
160
|
+
const safeQuery = sanitizeFTS5Query(query);
|
|
161
|
+
if (!safeQuery) return searchObservationsLIKE(db, query, filters);
|
|
162
|
+
let sql = `
|
|
163
|
+
SELECT o.* FROM observations o
|
|
164
|
+
JOIN observations_fts fts ON o.id = fts.rowid
|
|
165
|
+
WHERE observations_fts MATCH ?
|
|
166
|
+
`;
|
|
167
|
+
const params = [safeQuery];
|
|
168
|
+
if (filters.project) {
|
|
169
|
+
sql += " AND o.project = ?";
|
|
170
|
+
params.push(filters.project);
|
|
171
|
+
}
|
|
172
|
+
if (filters.type) {
|
|
173
|
+
sql += " AND o.type = ?";
|
|
174
|
+
params.push(filters.type);
|
|
175
|
+
}
|
|
176
|
+
if (filters.dateStart) {
|
|
177
|
+
sql += " AND o.created_at_epoch >= ?";
|
|
178
|
+
params.push(filters.dateStart);
|
|
179
|
+
}
|
|
180
|
+
if (filters.dateEnd) {
|
|
181
|
+
sql += " AND o.created_at_epoch <= ?";
|
|
182
|
+
params.push(filters.dateEnd);
|
|
183
|
+
}
|
|
184
|
+
sql += " ORDER BY rank LIMIT ?";
|
|
185
|
+
params.push(limit);
|
|
186
|
+
const stmt = db.query(sql);
|
|
187
|
+
return stmt.all(...params);
|
|
188
|
+
} catch {
|
|
189
|
+
return searchObservationsLIKE(db, query, filters);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function searchObservationsFTSWithRank(db, query, filters = {}) {
|
|
193
|
+
const limit = filters.limit || 50;
|
|
194
|
+
try {
|
|
195
|
+
const safeQuery = sanitizeFTS5Query(query);
|
|
196
|
+
if (!safeQuery) return [];
|
|
197
|
+
let sql = `
|
|
198
|
+
SELECT o.*, rank as fts5_rank FROM observations o
|
|
199
|
+
JOIN observations_fts fts ON o.id = fts.rowid
|
|
200
|
+
WHERE observations_fts MATCH ?
|
|
201
|
+
`;
|
|
202
|
+
const params = [safeQuery];
|
|
203
|
+
if (filters.project) {
|
|
204
|
+
sql += " AND o.project = ?";
|
|
205
|
+
params.push(filters.project);
|
|
206
|
+
}
|
|
207
|
+
if (filters.type) {
|
|
208
|
+
sql += " AND o.type = ?";
|
|
209
|
+
params.push(filters.type);
|
|
210
|
+
}
|
|
211
|
+
if (filters.dateStart) {
|
|
212
|
+
sql += " AND o.created_at_epoch >= ?";
|
|
213
|
+
params.push(filters.dateStart);
|
|
214
|
+
}
|
|
215
|
+
if (filters.dateEnd) {
|
|
216
|
+
sql += " AND o.created_at_epoch <= ?";
|
|
217
|
+
params.push(filters.dateEnd);
|
|
218
|
+
}
|
|
219
|
+
sql += " ORDER BY rank LIMIT ?";
|
|
220
|
+
params.push(limit);
|
|
221
|
+
const stmt = db.query(sql);
|
|
222
|
+
return stmt.all(...params);
|
|
223
|
+
} catch {
|
|
224
|
+
return [];
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
function searchObservationsLIKE(db, query, filters = {}) {
|
|
228
|
+
const limit = filters.limit || 50;
|
|
229
|
+
const pattern = `%${escapeLikePattern3(query)}%`;
|
|
230
|
+
let sql = `
|
|
231
|
+
SELECT * FROM observations
|
|
232
|
+
WHERE (title LIKE ? ESCAPE '\\' OR text LIKE ? ESCAPE '\\' OR narrative LIKE ? ESCAPE '\\' OR concepts LIKE ? ESCAPE '\\')
|
|
233
|
+
`;
|
|
234
|
+
const params = [pattern, pattern, pattern, pattern];
|
|
235
|
+
if (filters.project) {
|
|
236
|
+
sql += " AND project = ?";
|
|
237
|
+
params.push(filters.project);
|
|
238
|
+
}
|
|
239
|
+
if (filters.type) {
|
|
240
|
+
sql += " AND type = ?";
|
|
241
|
+
params.push(filters.type);
|
|
242
|
+
}
|
|
243
|
+
if (filters.dateStart) {
|
|
244
|
+
sql += " AND created_at_epoch >= ?";
|
|
245
|
+
params.push(filters.dateStart);
|
|
246
|
+
}
|
|
247
|
+
if (filters.dateEnd) {
|
|
248
|
+
sql += " AND created_at_epoch <= ?";
|
|
249
|
+
params.push(filters.dateEnd);
|
|
250
|
+
}
|
|
251
|
+
sql += " ORDER BY created_at_epoch DESC LIMIT ?";
|
|
252
|
+
params.push(limit);
|
|
253
|
+
const stmt = db.query(sql);
|
|
254
|
+
return stmt.all(...params);
|
|
255
|
+
}
|
|
256
|
+
function searchSummariesFiltered(db, query, filters = {}) {
|
|
257
|
+
const limit = filters.limit || 20;
|
|
258
|
+
const pattern = `%${escapeLikePattern3(query)}%`;
|
|
259
|
+
let sql = `
|
|
260
|
+
SELECT * FROM summaries
|
|
261
|
+
WHERE (request LIKE ? ESCAPE '\\' OR learned LIKE ? ESCAPE '\\' OR completed LIKE ? ESCAPE '\\' OR notes LIKE ? ESCAPE '\\' OR next_steps LIKE ? ESCAPE '\\')
|
|
262
|
+
`;
|
|
263
|
+
const params = [pattern, pattern, pattern, pattern, pattern];
|
|
264
|
+
if (filters.project) {
|
|
265
|
+
sql += " AND project = ?";
|
|
266
|
+
params.push(filters.project);
|
|
267
|
+
}
|
|
268
|
+
if (filters.dateStart) {
|
|
269
|
+
sql += " AND created_at_epoch >= ?";
|
|
270
|
+
params.push(filters.dateStart);
|
|
271
|
+
}
|
|
272
|
+
if (filters.dateEnd) {
|
|
273
|
+
sql += " AND created_at_epoch <= ?";
|
|
274
|
+
params.push(filters.dateEnd);
|
|
275
|
+
}
|
|
276
|
+
sql += " ORDER BY created_at_epoch DESC LIMIT ?";
|
|
277
|
+
params.push(limit);
|
|
278
|
+
const stmt = db.query(sql);
|
|
279
|
+
return stmt.all(...params);
|
|
280
|
+
}
|
|
281
|
+
function getObservationsByIds(db, ids) {
|
|
282
|
+
if (!Array.isArray(ids) || ids.length === 0) return [];
|
|
283
|
+
const validIds = ids.filter((id) => typeof id === "number" && Number.isInteger(id) && id > 0).slice(0, 500);
|
|
284
|
+
if (validIds.length === 0) return [];
|
|
285
|
+
const placeholders = validIds.map(() => "?").join(",");
|
|
286
|
+
const sql = `SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC`;
|
|
287
|
+
const stmt = db.query(sql);
|
|
288
|
+
return stmt.all(...validIds);
|
|
289
|
+
}
|
|
290
|
+
function getTimeline(db, anchorId, depthBefore = 5, depthAfter = 5) {
|
|
291
|
+
const anchorStmt = db.query("SELECT created_at_epoch FROM observations WHERE id = ?");
|
|
292
|
+
const anchor = anchorStmt.get(anchorId);
|
|
293
|
+
if (!anchor) return [];
|
|
294
|
+
const anchorEpoch = anchor.created_at_epoch;
|
|
295
|
+
const beforeStmt = db.query(`
|
|
296
|
+
SELECT id, 'observation' as type, title, text as content, project, created_at, created_at_epoch
|
|
297
|
+
FROM observations
|
|
298
|
+
WHERE created_at_epoch < ?
|
|
299
|
+
ORDER BY created_at_epoch DESC
|
|
300
|
+
LIMIT ?
|
|
301
|
+
`);
|
|
302
|
+
const before = beforeStmt.all(anchorEpoch, depthBefore).reverse();
|
|
303
|
+
const selfStmt = db.query(`
|
|
304
|
+
SELECT id, 'observation' as type, title, text as content, project, created_at, created_at_epoch
|
|
305
|
+
FROM observations WHERE id = ?
|
|
306
|
+
`);
|
|
307
|
+
const self = selfStmt.all(anchorId);
|
|
308
|
+
const afterStmt = db.query(`
|
|
309
|
+
SELECT id, 'observation' as type, title, text as content, project, created_at, created_at_epoch
|
|
310
|
+
FROM observations
|
|
311
|
+
WHERE created_at_epoch > ?
|
|
312
|
+
ORDER BY created_at_epoch ASC
|
|
313
|
+
LIMIT ?
|
|
314
|
+
`);
|
|
315
|
+
const after = afterStmt.all(anchorEpoch, depthAfter);
|
|
316
|
+
return [...before, ...self, ...after];
|
|
317
|
+
}
|
|
318
|
+
function getProjectStats(db, project) {
|
|
319
|
+
const obsStmt = db.query("SELECT COUNT(*) as count FROM observations WHERE project = ?");
|
|
320
|
+
const sumStmt = db.query("SELECT COUNT(*) as count FROM summaries WHERE project = ?");
|
|
321
|
+
const sesStmt = db.query("SELECT COUNT(*) as count FROM sessions WHERE project = ?");
|
|
322
|
+
const prmStmt = db.query("SELECT COUNT(*) as count FROM prompts WHERE project = ?");
|
|
323
|
+
return {
|
|
324
|
+
observations: obsStmt.get(project)?.count || 0,
|
|
325
|
+
summaries: sumStmt.get(project)?.count || 0,
|
|
326
|
+
sessions: sesStmt.get(project)?.count || 0,
|
|
327
|
+
prompts: prmStmt.get(project)?.count || 0
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
function getStaleObservations(db, project) {
|
|
331
|
+
const rows = db.query(`
|
|
332
|
+
SELECT * FROM observations
|
|
333
|
+
WHERE project = ? AND files_modified IS NOT NULL AND files_modified != ''
|
|
334
|
+
ORDER BY created_at_epoch DESC
|
|
335
|
+
LIMIT 500
|
|
336
|
+
`).all(project);
|
|
337
|
+
const staleObs = [];
|
|
338
|
+
for (const obs of rows) {
|
|
339
|
+
if (!obs.files_modified) continue;
|
|
340
|
+
const files = obs.files_modified.split(",").map((f) => f.trim()).filter(Boolean);
|
|
341
|
+
let isStale = false;
|
|
342
|
+
for (const filepath of files) {
|
|
343
|
+
try {
|
|
344
|
+
if (!existsSync3(filepath)) continue;
|
|
345
|
+
const stat = statSync(filepath);
|
|
346
|
+
if (stat.mtimeMs > obs.created_at_epoch) {
|
|
347
|
+
isStale = true;
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
} catch {
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
if (isStale) {
|
|
354
|
+
staleObs.push(obs);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return staleObs;
|
|
358
|
+
}
|
|
359
|
+
function markObservationsStale(db, ids, stale) {
|
|
360
|
+
if (!Array.isArray(ids) || ids.length === 0) return;
|
|
361
|
+
const validIds = ids.filter((id) => typeof id === "number" && Number.isInteger(id) && id > 0).slice(0, 500);
|
|
362
|
+
if (validIds.length === 0) return;
|
|
363
|
+
const placeholders = validIds.map(() => "?").join(",");
|
|
364
|
+
db.run(
|
|
365
|
+
`UPDATE observations SET is_stale = ? WHERE id IN (${placeholders})`,
|
|
366
|
+
[stale ? 1 : 0, ...validIds]
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
var init_Search = __esm({
|
|
370
|
+
"src/services/sqlite/Search.ts"() {
|
|
371
|
+
"use strict";
|
|
372
|
+
}
|
|
373
|
+
});
|
|
8
374
|
|
|
9
375
|
// src/shims/bun-sqlite.ts
|
|
10
376
|
import BetterSqlite3 from "better-sqlite3";
|
|
@@ -642,6 +1008,55 @@ var MigrationRunner = class {
|
|
|
642
1008
|
`);
|
|
643
1009
|
db.run("CREATE UNIQUE INDEX IF NOT EXISTS idx_project_aliases_name ON project_aliases(project_name)");
|
|
644
1010
|
}
|
|
1011
|
+
},
|
|
1012
|
+
{
|
|
1013
|
+
version: 4,
|
|
1014
|
+
up: (db) => {
|
|
1015
|
+
db.run(`
|
|
1016
|
+
CREATE TABLE IF NOT EXISTS observation_embeddings (
|
|
1017
|
+
observation_id INTEGER PRIMARY KEY,
|
|
1018
|
+
embedding BLOB NOT NULL,
|
|
1019
|
+
model TEXT NOT NULL,
|
|
1020
|
+
dimensions INTEGER NOT NULL,
|
|
1021
|
+
created_at TEXT NOT NULL,
|
|
1022
|
+
FOREIGN KEY (observation_id) REFERENCES observations(id) ON DELETE CASCADE
|
|
1023
|
+
)
|
|
1024
|
+
`);
|
|
1025
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_embeddings_model ON observation_embeddings(model)");
|
|
1026
|
+
}
|
|
1027
|
+
},
|
|
1028
|
+
{
|
|
1029
|
+
version: 5,
|
|
1030
|
+
up: (db) => {
|
|
1031
|
+
db.run("ALTER TABLE observations ADD COLUMN last_accessed_epoch INTEGER");
|
|
1032
|
+
db.run("ALTER TABLE observations ADD COLUMN is_stale INTEGER DEFAULT 0");
|
|
1033
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_observations_last_accessed ON observations(last_accessed_epoch)");
|
|
1034
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_observations_stale ON observations(is_stale)");
|
|
1035
|
+
}
|
|
1036
|
+
},
|
|
1037
|
+
{
|
|
1038
|
+
version: 6,
|
|
1039
|
+
up: (db) => {
|
|
1040
|
+
db.run(`
|
|
1041
|
+
CREATE TABLE IF NOT EXISTS checkpoints (
|
|
1042
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1043
|
+
session_id INTEGER NOT NULL,
|
|
1044
|
+
project TEXT NOT NULL,
|
|
1045
|
+
task TEXT NOT NULL,
|
|
1046
|
+
progress TEXT,
|
|
1047
|
+
next_steps TEXT,
|
|
1048
|
+
open_questions TEXT,
|
|
1049
|
+
relevant_files TEXT,
|
|
1050
|
+
context_snapshot TEXT,
|
|
1051
|
+
created_at TEXT NOT NULL,
|
|
1052
|
+
created_at_epoch INTEGER NOT NULL,
|
|
1053
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id)
|
|
1054
|
+
)
|
|
1055
|
+
`);
|
|
1056
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_checkpoints_session ON checkpoints(session_id)");
|
|
1057
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_checkpoints_project ON checkpoints(project)");
|
|
1058
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_checkpoints_epoch ON checkpoints(created_at_epoch)");
|
|
1059
|
+
}
|
|
645
1060
|
}
|
|
646
1061
|
];
|
|
647
1062
|
}
|
|
@@ -681,39 +1096,14 @@ function completeSession(db, id) {
|
|
|
681
1096
|
);
|
|
682
1097
|
}
|
|
683
1098
|
|
|
684
|
-
// src/services/sqlite/
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
);
|
|
693
|
-
return Number(result.lastInsertRowid);
|
|
694
|
-
}
|
|
695
|
-
function getObservationsByProject(db, project, limit = 100) {
|
|
696
|
-
const query = db.query(
|
|
697
|
-
"SELECT * FROM observations WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ?"
|
|
698
|
-
);
|
|
699
|
-
return query.all(project, limit);
|
|
700
|
-
}
|
|
701
|
-
function searchObservations(db, searchTerm, project) {
|
|
702
|
-
const sql = project ? `SELECT * FROM observations
|
|
703
|
-
WHERE project = ? AND (title LIKE ? OR text LIKE ? OR narrative LIKE ?)
|
|
704
|
-
ORDER BY created_at_epoch DESC` : `SELECT * FROM observations
|
|
705
|
-
WHERE title LIKE ? OR text LIKE ? OR narrative LIKE ?
|
|
706
|
-
ORDER BY created_at_epoch DESC`;
|
|
707
|
-
const pattern = `%${searchTerm}%`;
|
|
708
|
-
const query = db.query(sql);
|
|
709
|
-
if (project) {
|
|
710
|
-
return query.all(project, pattern, pattern, pattern);
|
|
711
|
-
}
|
|
712
|
-
return query.all(pattern, pattern, pattern);
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
// src/services/sqlite/Summaries.ts
|
|
716
|
-
function createSummary(db, sessionId, project, request, investigated, learned, completed, nextSteps, notes) {
|
|
1099
|
+
// src/services/sqlite/index.ts
|
|
1100
|
+
init_Observations();
|
|
1101
|
+
|
|
1102
|
+
// src/services/sqlite/Summaries.ts
|
|
1103
|
+
function escapeLikePattern2(input) {
|
|
1104
|
+
return input.replace(/[%_\\]/g, "\\$&");
|
|
1105
|
+
}
|
|
1106
|
+
function createSummary(db, sessionId, project, request, investigated, learned, completed, nextSteps, notes) {
|
|
717
1107
|
const now = /* @__PURE__ */ new Date();
|
|
718
1108
|
const result = db.run(
|
|
719
1109
|
`INSERT INTO summaries
|
|
@@ -730,12 +1120,12 @@ function getSummariesByProject(db, project, limit = 50) {
|
|
|
730
1120
|
return query.all(project, limit);
|
|
731
1121
|
}
|
|
732
1122
|
function searchSummaries(db, searchTerm, project) {
|
|
733
|
-
const sql = project ? `SELECT * FROM summaries
|
|
734
|
-
WHERE project = ? AND (request LIKE ? OR learned LIKE ? OR completed LIKE ? OR notes LIKE ?)
|
|
735
|
-
ORDER BY created_at_epoch DESC` : `SELECT * FROM summaries
|
|
736
|
-
WHERE request LIKE ? OR learned LIKE ? OR completed LIKE ? OR notes LIKE ?
|
|
1123
|
+
const sql = project ? `SELECT * FROM summaries
|
|
1124
|
+
WHERE project = ? AND (request LIKE ? ESCAPE '\\' OR learned LIKE ? ESCAPE '\\' OR completed LIKE ? ESCAPE '\\' OR notes LIKE ? ESCAPE '\\')
|
|
1125
|
+
ORDER BY created_at_epoch DESC` : `SELECT * FROM summaries
|
|
1126
|
+
WHERE request LIKE ? ESCAPE '\\' OR learned LIKE ? ESCAPE '\\' OR completed LIKE ? ESCAPE '\\' OR notes LIKE ? ESCAPE '\\'
|
|
737
1127
|
ORDER BY created_at_epoch DESC`;
|
|
738
|
-
const pattern = `%${searchTerm}%`;
|
|
1128
|
+
const pattern = `%${escapeLikePattern2(searchTerm)}%`;
|
|
739
1129
|
const query = db.query(sql);
|
|
740
1130
|
if (project) {
|
|
741
1131
|
return query.all(project, pattern, pattern, pattern, pattern);
|
|
@@ -761,148 +1151,642 @@ function getPromptsByProject(db, project, limit = 100) {
|
|
|
761
1151
|
return query.all(project, limit);
|
|
762
1152
|
}
|
|
763
1153
|
|
|
764
|
-
// src/services/sqlite/
|
|
765
|
-
function
|
|
766
|
-
const
|
|
767
|
-
|
|
1154
|
+
// src/services/sqlite/Checkpoints.ts
|
|
1155
|
+
function createCheckpoint(db, sessionId, project, data) {
|
|
1156
|
+
const now = /* @__PURE__ */ new Date();
|
|
1157
|
+
const result = db.run(
|
|
1158
|
+
`INSERT INTO checkpoints (session_id, project, task, progress, next_steps, open_questions, relevant_files, context_snapshot, created_at, created_at_epoch)
|
|
1159
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1160
|
+
[
|
|
1161
|
+
sessionId,
|
|
1162
|
+
project,
|
|
1163
|
+
data.task,
|
|
1164
|
+
data.progress || null,
|
|
1165
|
+
data.nextSteps || null,
|
|
1166
|
+
data.openQuestions || null,
|
|
1167
|
+
data.relevantFiles || null,
|
|
1168
|
+
data.contextSnapshot || null,
|
|
1169
|
+
now.toISOString(),
|
|
1170
|
+
now.getTime()
|
|
1171
|
+
]
|
|
1172
|
+
);
|
|
1173
|
+
return Number(result.lastInsertRowid);
|
|
768
1174
|
}
|
|
769
|
-
function
|
|
770
|
-
const
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
1175
|
+
function getLatestCheckpoint(db, sessionId) {
|
|
1176
|
+
const query = db.query(
|
|
1177
|
+
"SELECT * FROM checkpoints WHERE session_id = ? ORDER BY created_at_epoch DESC LIMIT 1"
|
|
1178
|
+
);
|
|
1179
|
+
return query.get(sessionId);
|
|
1180
|
+
}
|
|
1181
|
+
function getLatestCheckpointByProject(db, project) {
|
|
1182
|
+
const query = db.query(
|
|
1183
|
+
"SELECT * FROM checkpoints WHERE project = ? ORDER BY created_at_epoch DESC LIMIT 1"
|
|
1184
|
+
);
|
|
1185
|
+
return query.get(project);
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
// src/services/sqlite/Reports.ts
|
|
1189
|
+
function getReportData(db, project, startEpoch, endEpoch) {
|
|
1190
|
+
const startDate = new Date(startEpoch);
|
|
1191
|
+
const endDate = new Date(endEpoch);
|
|
1192
|
+
const days = Math.ceil((endEpoch - startEpoch) / (24 * 60 * 60 * 1e3));
|
|
1193
|
+
const label = days <= 7 ? "Weekly" : days <= 31 ? "Monthly" : "Custom";
|
|
1194
|
+
const countInRange = (table, epochCol = "created_at_epoch") => {
|
|
1195
|
+
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} <= ?`;
|
|
1196
|
+
const stmt = db.query(sql);
|
|
1197
|
+
const row = project ? stmt.get(project, startEpoch, endEpoch) : stmt.get(startEpoch, endEpoch);
|
|
1198
|
+
return row?.count || 0;
|
|
1199
|
+
};
|
|
1200
|
+
const observations = countInRange("observations");
|
|
1201
|
+
const summaries = countInRange("summaries");
|
|
1202
|
+
const prompts = countInRange("prompts");
|
|
1203
|
+
const sessions = countInRange("sessions", "started_at_epoch");
|
|
1204
|
+
const timelineSql = project ? `SELECT DATE(datetime(created_at_epoch / 1000, 'unixepoch')) as day, COUNT(*) as count
|
|
1205
|
+
FROM observations
|
|
1206
|
+
WHERE project = ? AND created_at_epoch >= ? AND created_at_epoch <= ?
|
|
1207
|
+
GROUP BY day ORDER BY day ASC` : `SELECT DATE(datetime(created_at_epoch / 1000, 'unixepoch')) as day, COUNT(*) as count
|
|
1208
|
+
FROM observations
|
|
1209
|
+
WHERE created_at_epoch >= ? AND created_at_epoch <= ?
|
|
1210
|
+
GROUP BY day ORDER BY day ASC`;
|
|
1211
|
+
const timelineStmt = db.query(timelineSql);
|
|
1212
|
+
const timeline = project ? timelineStmt.all(project, startEpoch, endEpoch) : timelineStmt.all(startEpoch, endEpoch);
|
|
1213
|
+
const typeSql = project ? `SELECT type, COUNT(*) as count FROM observations
|
|
1214
|
+
WHERE project = ? AND created_at_epoch >= ? AND created_at_epoch <= ?
|
|
1215
|
+
GROUP BY type ORDER BY count DESC` : `SELECT type, COUNT(*) as count FROM observations
|
|
1216
|
+
WHERE created_at_epoch >= ? AND created_at_epoch <= ?
|
|
1217
|
+
GROUP BY type ORDER BY count DESC`;
|
|
1218
|
+
const typeStmt = db.query(typeSql);
|
|
1219
|
+
const typeDistribution = project ? typeStmt.all(project, startEpoch, endEpoch) : typeStmt.all(startEpoch, endEpoch);
|
|
1220
|
+
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 <= ?`;
|
|
1221
|
+
const sessionTotal = (project ? db.query(sessionTotalSql).get(project, startEpoch, endEpoch)?.count : db.query(sessionTotalSql).get(startEpoch, endEpoch)?.count) || 0;
|
|
1222
|
+
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'`;
|
|
1223
|
+
const sessionCompleted = (project ? db.query(sessionCompletedSql).get(project, startEpoch, endEpoch)?.count : db.query(sessionCompletedSql).get(startEpoch, endEpoch)?.count) || 0;
|
|
1224
|
+
const sessionAvgSql = project ? `SELECT AVG((completed_at_epoch - started_at_epoch) / 1000.0 / 60.0) as avg_min
|
|
1225
|
+
FROM sessions
|
|
1226
|
+
WHERE project = ? AND started_at_epoch >= ? AND started_at_epoch <= ?
|
|
1227
|
+
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
|
|
1228
|
+
FROM sessions
|
|
1229
|
+
WHERE started_at_epoch >= ? AND started_at_epoch <= ?
|
|
1230
|
+
AND status = 'completed' AND completed_at_epoch IS NOT NULL AND completed_at_epoch > started_at_epoch`;
|
|
1231
|
+
const avgRow = project ? db.query(sessionAvgSql).get(project, startEpoch, endEpoch) : db.query(sessionAvgSql).get(startEpoch, endEpoch);
|
|
1232
|
+
const avgDurationMinutes = Math.round((avgRow?.avg_min || 0) * 10) / 10;
|
|
1233
|
+
const knowledgeSql = project ? `SELECT COUNT(*) as count FROM observations
|
|
1234
|
+
WHERE project = ? AND created_at_epoch >= ? AND created_at_epoch <= ?
|
|
1235
|
+
AND type IN ('constraint', 'decision', 'heuristic', 'rejected')` : `SELECT COUNT(*) as count FROM observations
|
|
1236
|
+
WHERE created_at_epoch >= ? AND created_at_epoch <= ?
|
|
1237
|
+
AND type IN ('constraint', 'decision', 'heuristic', 'rejected')`;
|
|
1238
|
+
const knowledgeCount = (project ? db.query(knowledgeSql).get(project, startEpoch, endEpoch)?.count : db.query(knowledgeSql).get(startEpoch, endEpoch)?.count) || 0;
|
|
1239
|
+
const staleSql = project ? `SELECT COUNT(*) as count FROM observations
|
|
1240
|
+
WHERE project = ? AND created_at_epoch >= ? AND created_at_epoch <= ? AND is_stale = 1` : `SELECT COUNT(*) as count FROM observations
|
|
1241
|
+
WHERE created_at_epoch >= ? AND created_at_epoch <= ? AND is_stale = 1`;
|
|
1242
|
+
const staleCount = (project ? db.query(staleSql).get(project, startEpoch, endEpoch)?.count : db.query(staleSql).get(startEpoch, endEpoch)?.count) || 0;
|
|
1243
|
+
const summarySql = project ? `SELECT learned, completed, next_steps FROM summaries
|
|
1244
|
+
WHERE project = ? AND created_at_epoch >= ? AND created_at_epoch <= ?
|
|
1245
|
+
ORDER BY created_at_epoch DESC` : `SELECT learned, completed, next_steps FROM summaries
|
|
1246
|
+
WHERE created_at_epoch >= ? AND created_at_epoch <= ?
|
|
1247
|
+
ORDER BY created_at_epoch DESC`;
|
|
1248
|
+
const summaryRows = project ? db.query(summarySql).all(project, startEpoch, endEpoch) : db.query(summarySql).all(startEpoch, endEpoch);
|
|
1249
|
+
const topLearnings = [];
|
|
1250
|
+
const completedTasks = [];
|
|
1251
|
+
const nextStepsArr = [];
|
|
1252
|
+
for (const row of summaryRows) {
|
|
1253
|
+
if (row.learned) {
|
|
1254
|
+
const parts = row.learned.split("; ").filter(Boolean);
|
|
1255
|
+
topLearnings.push(...parts);
|
|
783
1256
|
}
|
|
784
|
-
if (
|
|
785
|
-
|
|
786
|
-
|
|
1257
|
+
if (row.completed) {
|
|
1258
|
+
const parts = row.completed.split("; ").filter(Boolean);
|
|
1259
|
+
completedTasks.push(...parts);
|
|
787
1260
|
}
|
|
788
|
-
if (
|
|
789
|
-
|
|
790
|
-
|
|
1261
|
+
if (row.next_steps) {
|
|
1262
|
+
const parts = row.next_steps.split("; ").filter(Boolean);
|
|
1263
|
+
nextStepsArr.push(...parts);
|
|
791
1264
|
}
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
1265
|
+
}
|
|
1266
|
+
const filesSql = project ? `SELECT files_modified FROM observations
|
|
1267
|
+
WHERE project = ? AND created_at_epoch >= ? AND created_at_epoch <= ?
|
|
1268
|
+
AND files_modified IS NOT NULL AND files_modified != ''` : `SELECT files_modified FROM observations
|
|
1269
|
+
WHERE created_at_epoch >= ? AND created_at_epoch <= ?
|
|
1270
|
+
AND files_modified IS NOT NULL AND files_modified != ''`;
|
|
1271
|
+
const fileRows = project ? db.query(filesSql).all(project, startEpoch, endEpoch) : db.query(filesSql).all(startEpoch, endEpoch);
|
|
1272
|
+
const fileCounts = /* @__PURE__ */ new Map();
|
|
1273
|
+
for (const row of fileRows) {
|
|
1274
|
+
const files = row.files_modified.split(",").map((f) => f.trim()).filter(Boolean);
|
|
1275
|
+
for (const file of files) {
|
|
1276
|
+
fileCounts.set(file, (fileCounts.get(file) || 0) + 1);
|
|
795
1277
|
}
|
|
796
|
-
sql += " ORDER BY rank LIMIT ?";
|
|
797
|
-
params.push(limit);
|
|
798
|
-
const stmt = db.query(sql);
|
|
799
|
-
return stmt.all(...params);
|
|
800
|
-
} catch {
|
|
801
|
-
return searchObservationsLIKE(db, query, filters);
|
|
802
1278
|
}
|
|
1279
|
+
const fileHotspots = Array.from(fileCounts.entries()).map(([file, count]) => ({ file, count })).sort((a, b) => b.count - a.count).slice(0, 15);
|
|
1280
|
+
return {
|
|
1281
|
+
period: {
|
|
1282
|
+
start: startDate.toISOString().split("T")[0],
|
|
1283
|
+
end: endDate.toISOString().split("T")[0],
|
|
1284
|
+
days,
|
|
1285
|
+
label
|
|
1286
|
+
},
|
|
1287
|
+
overview: {
|
|
1288
|
+
observations,
|
|
1289
|
+
summaries,
|
|
1290
|
+
sessions,
|
|
1291
|
+
prompts,
|
|
1292
|
+
knowledgeCount,
|
|
1293
|
+
staleCount
|
|
1294
|
+
},
|
|
1295
|
+
timeline,
|
|
1296
|
+
typeDistribution,
|
|
1297
|
+
sessionStats: {
|
|
1298
|
+
total: sessionTotal,
|
|
1299
|
+
completed: sessionCompleted,
|
|
1300
|
+
avgDurationMinutes
|
|
1301
|
+
},
|
|
1302
|
+
topLearnings: [...new Set(topLearnings)].slice(0, 10),
|
|
1303
|
+
completedTasks: [...new Set(completedTasks)].slice(0, 10),
|
|
1304
|
+
nextSteps: [...new Set(nextStepsArr)].slice(0, 10),
|
|
1305
|
+
fileHotspots
|
|
1306
|
+
};
|
|
803
1307
|
}
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
1308
|
+
|
|
1309
|
+
// src/services/sqlite/index.ts
|
|
1310
|
+
init_Search();
|
|
1311
|
+
|
|
1312
|
+
// src/sdk/index.ts
|
|
1313
|
+
init_Observations();
|
|
1314
|
+
init_Search();
|
|
1315
|
+
|
|
1316
|
+
// src/services/search/EmbeddingService.ts
|
|
1317
|
+
var EmbeddingService = class {
|
|
1318
|
+
provider = null;
|
|
1319
|
+
model = null;
|
|
1320
|
+
initialized = false;
|
|
1321
|
+
initializing = null;
|
|
1322
|
+
/**
|
|
1323
|
+
* Inizializza il servizio di embedding.
|
|
1324
|
+
* Tenta fastembed, poi @huggingface/transformers, poi fallback a null.
|
|
1325
|
+
*/
|
|
1326
|
+
async initialize() {
|
|
1327
|
+
if (this.initialized) return this.provider !== null;
|
|
1328
|
+
if (this.initializing) return this.initializing;
|
|
1329
|
+
this.initializing = this._doInitialize();
|
|
1330
|
+
const result = await this.initializing;
|
|
1331
|
+
this.initializing = null;
|
|
1332
|
+
return result;
|
|
815
1333
|
}
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
1334
|
+
async _doInitialize() {
|
|
1335
|
+
try {
|
|
1336
|
+
const fastembed = await import("fastembed");
|
|
1337
|
+
const EmbeddingModel = fastembed.EmbeddingModel || fastembed.default?.EmbeddingModel;
|
|
1338
|
+
const FlagEmbedding = fastembed.FlagEmbedding || fastembed.default?.FlagEmbedding;
|
|
1339
|
+
if (FlagEmbedding && EmbeddingModel) {
|
|
1340
|
+
this.model = await FlagEmbedding.init({
|
|
1341
|
+
model: EmbeddingModel.BGESmallENV15
|
|
1342
|
+
});
|
|
1343
|
+
this.provider = "fastembed";
|
|
1344
|
+
this.initialized = true;
|
|
1345
|
+
logger.info("EMBEDDING", "Inizializzato con fastembed (BGE-small-en-v1.5)");
|
|
1346
|
+
return true;
|
|
1347
|
+
}
|
|
1348
|
+
} catch (error) {
|
|
1349
|
+
logger.debug("EMBEDDING", `fastembed non disponibile: ${error}`);
|
|
1350
|
+
}
|
|
1351
|
+
try {
|
|
1352
|
+
const transformers = await import("@huggingface/transformers");
|
|
1353
|
+
const pipeline = transformers.pipeline || transformers.default?.pipeline;
|
|
1354
|
+
if (pipeline) {
|
|
1355
|
+
this.model = await pipeline("feature-extraction", "Xenova/all-MiniLM-L6-v2", {
|
|
1356
|
+
quantized: true
|
|
1357
|
+
});
|
|
1358
|
+
this.provider = "transformers";
|
|
1359
|
+
this.initialized = true;
|
|
1360
|
+
logger.info("EMBEDDING", "Inizializzato con @huggingface/transformers (all-MiniLM-L6-v2)");
|
|
1361
|
+
return true;
|
|
1362
|
+
}
|
|
1363
|
+
} catch (error) {
|
|
1364
|
+
logger.debug("EMBEDDING", `@huggingface/transformers non disponibile: ${error}`);
|
|
1365
|
+
}
|
|
1366
|
+
this.provider = null;
|
|
1367
|
+
this.initialized = true;
|
|
1368
|
+
logger.warn("EMBEDDING", "Nessun provider embedding disponibile, ricerca semantica disabilitata");
|
|
1369
|
+
return false;
|
|
819
1370
|
}
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
1371
|
+
/**
|
|
1372
|
+
* Genera embedding per un singolo testo.
|
|
1373
|
+
* Ritorna Float32Array con 384 dimensioni, o null se non disponibile.
|
|
1374
|
+
*/
|
|
1375
|
+
async embed(text) {
|
|
1376
|
+
if (!this.initialized) await this.initialize();
|
|
1377
|
+
if (!this.provider || !this.model) return null;
|
|
1378
|
+
try {
|
|
1379
|
+
const truncated = text.substring(0, 2e3);
|
|
1380
|
+
if (this.provider === "fastembed") {
|
|
1381
|
+
return await this._embedFastembed(truncated);
|
|
1382
|
+
} else if (this.provider === "transformers") {
|
|
1383
|
+
return await this._embedTransformers(truncated);
|
|
1384
|
+
}
|
|
1385
|
+
} catch (error) {
|
|
1386
|
+
logger.error("EMBEDDING", `Errore generazione embedding: ${error}`);
|
|
1387
|
+
}
|
|
1388
|
+
return null;
|
|
823
1389
|
}
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
1390
|
+
/**
|
|
1391
|
+
* Genera embeddings in batch.
|
|
1392
|
+
*/
|
|
1393
|
+
async embedBatch(texts) {
|
|
1394
|
+
if (!this.initialized) await this.initialize();
|
|
1395
|
+
if (!this.provider || !this.model) return texts.map(() => null);
|
|
1396
|
+
const results = [];
|
|
1397
|
+
for (const text of texts) {
|
|
1398
|
+
try {
|
|
1399
|
+
const embedding = await this.embed(text);
|
|
1400
|
+
results.push(embedding);
|
|
1401
|
+
} catch {
|
|
1402
|
+
results.push(null);
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
return results;
|
|
827
1406
|
}
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
1407
|
+
/**
|
|
1408
|
+
* Verifica se il servizio è disponibile.
|
|
1409
|
+
*/
|
|
1410
|
+
isAvailable() {
|
|
1411
|
+
return this.initialized && this.provider !== null;
|
|
1412
|
+
}
|
|
1413
|
+
/**
|
|
1414
|
+
* Nome del provider attivo.
|
|
1415
|
+
*/
|
|
1416
|
+
getProvider() {
|
|
1417
|
+
return this.provider;
|
|
1418
|
+
}
|
|
1419
|
+
/**
|
|
1420
|
+
* Dimensioni del vettore embedding.
|
|
1421
|
+
*/
|
|
1422
|
+
getDimensions() {
|
|
1423
|
+
return 384;
|
|
1424
|
+
}
|
|
1425
|
+
// --- Provider specifici ---
|
|
1426
|
+
async _embedFastembed(text) {
|
|
1427
|
+
const embeddings = this.model.embed([text], 1);
|
|
1428
|
+
for await (const batch of embeddings) {
|
|
1429
|
+
if (batch && batch.length > 0) {
|
|
1430
|
+
const vec = batch[0];
|
|
1431
|
+
return vec instanceof Float32Array ? vec : new Float32Array(vec);
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
return null;
|
|
1435
|
+
}
|
|
1436
|
+
async _embedTransformers(text) {
|
|
1437
|
+
const output = await this.model(text, {
|
|
1438
|
+
pooling: "mean",
|
|
1439
|
+
normalize: true
|
|
1440
|
+
});
|
|
1441
|
+
if (output?.data) {
|
|
1442
|
+
return output.data instanceof Float32Array ? output.data : new Float32Array(output.data);
|
|
1443
|
+
}
|
|
1444
|
+
return null;
|
|
1445
|
+
}
|
|
1446
|
+
};
|
|
1447
|
+
var embeddingService = null;
|
|
1448
|
+
function getEmbeddingService() {
|
|
1449
|
+
if (!embeddingService) {
|
|
1450
|
+
embeddingService = new EmbeddingService();
|
|
1451
|
+
}
|
|
1452
|
+
return embeddingService;
|
|
832
1453
|
}
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
1454
|
+
|
|
1455
|
+
// src/services/search/VectorSearch.ts
|
|
1456
|
+
function cosineSimilarity(a, b) {
|
|
1457
|
+
if (a.length !== b.length) return 0;
|
|
1458
|
+
let dotProduct = 0;
|
|
1459
|
+
let normA = 0;
|
|
1460
|
+
let normB = 0;
|
|
1461
|
+
for (let i = 0; i < a.length; i++) {
|
|
1462
|
+
dotProduct += a[i] * b[i];
|
|
1463
|
+
normA += a[i] * a[i];
|
|
1464
|
+
normB += b[i] * b[i];
|
|
1465
|
+
}
|
|
1466
|
+
const denominator = Math.sqrt(normA) * Math.sqrt(normB);
|
|
1467
|
+
if (denominator === 0) return 0;
|
|
1468
|
+
return dotProduct / denominator;
|
|
1469
|
+
}
|
|
1470
|
+
function float32ToBuffer(arr) {
|
|
1471
|
+
return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength);
|
|
1472
|
+
}
|
|
1473
|
+
function bufferToFloat32(buf) {
|
|
1474
|
+
const arrayBuffer = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
1475
|
+
return new Float32Array(arrayBuffer);
|
|
1476
|
+
}
|
|
1477
|
+
var VectorSearch = class {
|
|
1478
|
+
/**
|
|
1479
|
+
* Ricerca semantica: calcola cosine similarity tra query e tutti gli embeddings.
|
|
1480
|
+
*/
|
|
1481
|
+
async search(db, queryEmbedding, options = {}) {
|
|
1482
|
+
const limit = options.limit || 10;
|
|
1483
|
+
const threshold = options.threshold || 0.3;
|
|
1484
|
+
try {
|
|
1485
|
+
let sql = `
|
|
1486
|
+
SELECT e.observation_id, e.embedding,
|
|
1487
|
+
o.title, o.text, o.type, o.project, o.created_at, o.created_at_epoch
|
|
1488
|
+
FROM observation_embeddings e
|
|
1489
|
+
JOIN observations o ON o.id = e.observation_id
|
|
1490
|
+
`;
|
|
1491
|
+
const params = [];
|
|
1492
|
+
if (options.project) {
|
|
1493
|
+
sql += " WHERE o.project = ?";
|
|
1494
|
+
params.push(options.project);
|
|
1495
|
+
}
|
|
1496
|
+
const rows = db.query(sql).all(...params);
|
|
1497
|
+
const scored = [];
|
|
1498
|
+
for (const row of rows) {
|
|
1499
|
+
const embedding = bufferToFloat32(row.embedding);
|
|
1500
|
+
const similarity = cosineSimilarity(queryEmbedding, embedding);
|
|
1501
|
+
if (similarity >= threshold) {
|
|
1502
|
+
scored.push({
|
|
1503
|
+
id: row.observation_id,
|
|
1504
|
+
observationId: row.observation_id,
|
|
1505
|
+
similarity,
|
|
1506
|
+
title: row.title,
|
|
1507
|
+
text: row.text,
|
|
1508
|
+
type: row.type,
|
|
1509
|
+
project: row.project,
|
|
1510
|
+
created_at: row.created_at,
|
|
1511
|
+
created_at_epoch: row.created_at_epoch
|
|
1512
|
+
});
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
scored.sort((a, b) => b.similarity - a.similarity);
|
|
1516
|
+
return scored.slice(0, limit);
|
|
1517
|
+
} catch (error) {
|
|
1518
|
+
logger.error("VECTOR", `Errore ricerca vettoriale: ${error}`);
|
|
1519
|
+
return [];
|
|
1520
|
+
}
|
|
844
1521
|
}
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
1522
|
+
/**
|
|
1523
|
+
* Salva embedding per un'osservazione.
|
|
1524
|
+
*/
|
|
1525
|
+
async storeEmbedding(db, observationId, embedding, model) {
|
|
1526
|
+
try {
|
|
1527
|
+
const blob = float32ToBuffer(embedding);
|
|
1528
|
+
db.query(`
|
|
1529
|
+
INSERT OR REPLACE INTO observation_embeddings
|
|
1530
|
+
(observation_id, embedding, model, dimensions, created_at)
|
|
1531
|
+
VALUES (?, ?, ?, ?, ?)
|
|
1532
|
+
`).run(
|
|
1533
|
+
observationId,
|
|
1534
|
+
blob,
|
|
1535
|
+
model,
|
|
1536
|
+
embedding.length,
|
|
1537
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
1538
|
+
);
|
|
1539
|
+
logger.debug("VECTOR", `Embedding salvato per osservazione ${observationId}`);
|
|
1540
|
+
} catch (error) {
|
|
1541
|
+
logger.error("VECTOR", `Errore salvataggio embedding: ${error}`);
|
|
1542
|
+
}
|
|
848
1543
|
}
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
1544
|
+
/**
|
|
1545
|
+
* Genera embeddings per osservazioni che non li hanno ancora.
|
|
1546
|
+
*/
|
|
1547
|
+
async backfillEmbeddings(db, batchSize = 50) {
|
|
1548
|
+
const embeddingService2 = getEmbeddingService();
|
|
1549
|
+
if (!await embeddingService2.initialize()) {
|
|
1550
|
+
logger.warn("VECTOR", "Embedding service non disponibile, backfill saltato");
|
|
1551
|
+
return 0;
|
|
1552
|
+
}
|
|
1553
|
+
const rows = db.query(`
|
|
1554
|
+
SELECT o.id, o.title, o.text, o.narrative, o.concepts
|
|
1555
|
+
FROM observations o
|
|
1556
|
+
LEFT JOIN observation_embeddings e ON e.observation_id = o.id
|
|
1557
|
+
WHERE e.observation_id IS NULL
|
|
1558
|
+
ORDER BY o.created_at_epoch DESC
|
|
1559
|
+
LIMIT ?
|
|
1560
|
+
`).all(batchSize);
|
|
1561
|
+
if (rows.length === 0) return 0;
|
|
1562
|
+
let count = 0;
|
|
1563
|
+
const model = embeddingService2.getProvider() || "unknown";
|
|
1564
|
+
for (const row of rows) {
|
|
1565
|
+
const parts = [row.title];
|
|
1566
|
+
if (row.text) parts.push(row.text);
|
|
1567
|
+
if (row.narrative) parts.push(row.narrative);
|
|
1568
|
+
if (row.concepts) parts.push(row.concepts);
|
|
1569
|
+
const fullText = parts.join(" ").substring(0, 2e3);
|
|
1570
|
+
const embedding = await embeddingService2.embed(fullText);
|
|
1571
|
+
if (embedding) {
|
|
1572
|
+
await this.storeEmbedding(db, row.id, embedding, model);
|
|
1573
|
+
count++;
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
logger.info("VECTOR", `Backfill completato: ${count}/${rows.length} embeddings generati`);
|
|
1577
|
+
return count;
|
|
852
1578
|
}
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
1579
|
+
/**
|
|
1580
|
+
* Statistiche sugli embeddings.
|
|
1581
|
+
*/
|
|
1582
|
+
getStats(db) {
|
|
1583
|
+
try {
|
|
1584
|
+
const totalRow = db.query("SELECT COUNT(*) as count FROM observations").get();
|
|
1585
|
+
const embeddedRow = db.query("SELECT COUNT(*) as count FROM observation_embeddings").get();
|
|
1586
|
+
const total = totalRow?.count || 0;
|
|
1587
|
+
const embedded = embeddedRow?.count || 0;
|
|
1588
|
+
const percentage = total > 0 ? Math.round(embedded / total * 100) : 0;
|
|
1589
|
+
return { total, embedded, percentage };
|
|
1590
|
+
} catch {
|
|
1591
|
+
return { total: 0, embedded: 0, percentage: 0 };
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
};
|
|
1595
|
+
var vectorSearch = null;
|
|
1596
|
+
function getVectorSearch() {
|
|
1597
|
+
if (!vectorSearch) {
|
|
1598
|
+
vectorSearch = new VectorSearch();
|
|
1599
|
+
}
|
|
1600
|
+
return vectorSearch;
|
|
857
1601
|
}
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
1602
|
+
|
|
1603
|
+
// src/services/search/ScoringEngine.ts
|
|
1604
|
+
var SEARCH_WEIGHTS = {
|
|
1605
|
+
semantic: 0.4,
|
|
1606
|
+
fts5: 0.3,
|
|
1607
|
+
recency: 0.2,
|
|
1608
|
+
projectMatch: 0.1
|
|
1609
|
+
};
|
|
1610
|
+
var CONTEXT_WEIGHTS = {
|
|
1611
|
+
semantic: 0,
|
|
1612
|
+
fts5: 0,
|
|
1613
|
+
recency: 0.7,
|
|
1614
|
+
projectMatch: 0.3
|
|
1615
|
+
};
|
|
1616
|
+
function recencyScore(createdAtEpoch, halfLifeHours = 168) {
|
|
1617
|
+
if (!createdAtEpoch || createdAtEpoch <= 0) return 0;
|
|
1618
|
+
const nowMs = Date.now();
|
|
1619
|
+
const ageMs = nowMs - createdAtEpoch;
|
|
1620
|
+
if (ageMs <= 0) return 1;
|
|
1621
|
+
const ageHours = ageMs / (1e3 * 60 * 60);
|
|
1622
|
+
return Math.exp(-ageHours * Math.LN2 / halfLifeHours);
|
|
864
1623
|
}
|
|
865
|
-
function
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
const
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
FROM observations
|
|
873
|
-
WHERE created_at_epoch < ?
|
|
874
|
-
ORDER BY created_at_epoch DESC
|
|
875
|
-
LIMIT ?
|
|
876
|
-
`);
|
|
877
|
-
const before = beforeStmt.all(anchorEpoch, depthBefore).reverse();
|
|
878
|
-
const selfStmt = db.query(`
|
|
879
|
-
SELECT id, 'observation' as type, title, text as content, project, created_at, created_at_epoch
|
|
880
|
-
FROM observations WHERE id = ?
|
|
881
|
-
`);
|
|
882
|
-
const self = selfStmt.all(anchorId);
|
|
883
|
-
const afterStmt = db.query(`
|
|
884
|
-
SELECT id, 'observation' as type, title, text as content, project, created_at, created_at_epoch
|
|
885
|
-
FROM observations
|
|
886
|
-
WHERE created_at_epoch > ?
|
|
887
|
-
ORDER BY created_at_epoch ASC
|
|
888
|
-
LIMIT ?
|
|
889
|
-
`);
|
|
890
|
-
const after = afterStmt.all(anchorEpoch, depthAfter);
|
|
891
|
-
return [...before, ...self, ...after];
|
|
1624
|
+
function normalizeFTS5Rank(rank, allRanks) {
|
|
1625
|
+
if (allRanks.length === 0) return 0;
|
|
1626
|
+
if (allRanks.length === 1) return 1;
|
|
1627
|
+
const minRank = Math.min(...allRanks);
|
|
1628
|
+
const maxRank = Math.max(...allRanks);
|
|
1629
|
+
if (minRank === maxRank) return 1;
|
|
1630
|
+
return (maxRank - rank) / (maxRank - minRank);
|
|
892
1631
|
}
|
|
893
|
-
function
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
return
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
1632
|
+
function projectMatchScore(itemProject, targetProject) {
|
|
1633
|
+
if (!itemProject || !targetProject) return 0;
|
|
1634
|
+
return itemProject.toLowerCase() === targetProject.toLowerCase() ? 1 : 0;
|
|
1635
|
+
}
|
|
1636
|
+
function computeCompositeScore(signals, weights) {
|
|
1637
|
+
return signals.semantic * weights.semantic + signals.fts5 * weights.fts5 + signals.recency * weights.recency + signals.projectMatch * weights.projectMatch;
|
|
1638
|
+
}
|
|
1639
|
+
var KNOWLEDGE_TYPE_BOOST = {
|
|
1640
|
+
constraint: 1.3,
|
|
1641
|
+
decision: 1.25,
|
|
1642
|
+
heuristic: 1.15,
|
|
1643
|
+
rejected: 1.1
|
|
1644
|
+
};
|
|
1645
|
+
function knowledgeTypeBoost(type) {
|
|
1646
|
+
return KNOWLEDGE_TYPE_BOOST[type] ?? 1;
|
|
904
1647
|
}
|
|
905
1648
|
|
|
1649
|
+
// src/services/search/HybridSearch.ts
|
|
1650
|
+
var HybridSearch = class {
|
|
1651
|
+
embeddingInitialized = false;
|
|
1652
|
+
/**
|
|
1653
|
+
* Inizializza il servizio di embedding (lazy, non bloccante)
|
|
1654
|
+
*/
|
|
1655
|
+
async initialize() {
|
|
1656
|
+
try {
|
|
1657
|
+
const embeddingService2 = getEmbeddingService();
|
|
1658
|
+
await embeddingService2.initialize();
|
|
1659
|
+
this.embeddingInitialized = embeddingService2.isAvailable();
|
|
1660
|
+
logger.info("SEARCH", `HybridSearch inizializzato (embedding: ${this.embeddingInitialized ? "attivo" : "disattivato"})`);
|
|
1661
|
+
} catch (error) {
|
|
1662
|
+
logger.warn("SEARCH", "Inizializzazione embedding fallita, uso solo FTS5", {}, error);
|
|
1663
|
+
this.embeddingInitialized = false;
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
/**
|
|
1667
|
+
* Ricerca ibrida con scoring a 4 segnali
|
|
1668
|
+
*/
|
|
1669
|
+
async search(db, query, options = {}) {
|
|
1670
|
+
const limit = options.limit || 10;
|
|
1671
|
+
const weights = options.weights || SEARCH_WEIGHTS;
|
|
1672
|
+
const targetProject = options.project || "";
|
|
1673
|
+
const rawItems = /* @__PURE__ */ new Map();
|
|
1674
|
+
if (this.embeddingInitialized) {
|
|
1675
|
+
try {
|
|
1676
|
+
const embeddingService2 = getEmbeddingService();
|
|
1677
|
+
const queryEmbedding = await embeddingService2.embed(query);
|
|
1678
|
+
if (queryEmbedding) {
|
|
1679
|
+
const vectorSearch2 = getVectorSearch();
|
|
1680
|
+
const vectorResults = await vectorSearch2.search(db, queryEmbedding, {
|
|
1681
|
+
project: options.project,
|
|
1682
|
+
limit: limit * 2,
|
|
1683
|
+
// Prendiamo piu risultati per il ranking
|
|
1684
|
+
threshold: 0.3
|
|
1685
|
+
});
|
|
1686
|
+
for (const hit of vectorResults) {
|
|
1687
|
+
rawItems.set(String(hit.observationId), {
|
|
1688
|
+
id: String(hit.observationId),
|
|
1689
|
+
title: hit.title,
|
|
1690
|
+
content: hit.text || "",
|
|
1691
|
+
type: hit.type,
|
|
1692
|
+
project: hit.project,
|
|
1693
|
+
created_at: hit.created_at,
|
|
1694
|
+
created_at_epoch: hit.created_at_epoch,
|
|
1695
|
+
semanticScore: hit.similarity,
|
|
1696
|
+
fts5Rank: null,
|
|
1697
|
+
source: "vector"
|
|
1698
|
+
});
|
|
1699
|
+
}
|
|
1700
|
+
logger.debug("SEARCH", `Vector search: ${vectorResults.length} risultati`);
|
|
1701
|
+
}
|
|
1702
|
+
} catch (error) {
|
|
1703
|
+
logger.warn("SEARCH", "Ricerca vettoriale fallita, uso solo keyword", {}, error);
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
try {
|
|
1707
|
+
const { searchObservationsFTSWithRank: searchObservationsFTSWithRank2 } = await Promise.resolve().then(() => (init_Search(), Search_exports));
|
|
1708
|
+
const keywordResults = searchObservationsFTSWithRank2(db, query, {
|
|
1709
|
+
project: options.project,
|
|
1710
|
+
limit: limit * 2
|
|
1711
|
+
});
|
|
1712
|
+
for (const obs of keywordResults) {
|
|
1713
|
+
const id = String(obs.id);
|
|
1714
|
+
const existing = rawItems.get(id);
|
|
1715
|
+
if (existing) {
|
|
1716
|
+
existing.fts5Rank = obs.fts5_rank;
|
|
1717
|
+
existing.source = "vector";
|
|
1718
|
+
} else {
|
|
1719
|
+
rawItems.set(id, {
|
|
1720
|
+
id,
|
|
1721
|
+
title: obs.title,
|
|
1722
|
+
content: obs.text || obs.narrative || "",
|
|
1723
|
+
type: obs.type,
|
|
1724
|
+
project: obs.project,
|
|
1725
|
+
created_at: obs.created_at,
|
|
1726
|
+
created_at_epoch: obs.created_at_epoch,
|
|
1727
|
+
semanticScore: 0,
|
|
1728
|
+
fts5Rank: obs.fts5_rank,
|
|
1729
|
+
source: "keyword"
|
|
1730
|
+
});
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
logger.debug("SEARCH", `Keyword search: ${keywordResults.length} risultati`);
|
|
1734
|
+
} catch (error) {
|
|
1735
|
+
logger.error("SEARCH", "Ricerca keyword fallita", {}, error);
|
|
1736
|
+
}
|
|
1737
|
+
if (rawItems.size === 0) return [];
|
|
1738
|
+
const allFTS5Ranks = Array.from(rawItems.values()).filter((item) => item.fts5Rank !== null).map((item) => item.fts5Rank);
|
|
1739
|
+
const scored = [];
|
|
1740
|
+
for (const item of rawItems.values()) {
|
|
1741
|
+
const signals = {
|
|
1742
|
+
semantic: item.semanticScore,
|
|
1743
|
+
fts5: item.fts5Rank !== null ? normalizeFTS5Rank(item.fts5Rank, allFTS5Ranks) : 0,
|
|
1744
|
+
recency: recencyScore(item.created_at_epoch),
|
|
1745
|
+
projectMatch: targetProject ? projectMatchScore(item.project, targetProject) : 0
|
|
1746
|
+
};
|
|
1747
|
+
const score = computeCompositeScore(signals, weights);
|
|
1748
|
+
const isHybrid = item.semanticScore > 0 && item.fts5Rank !== null;
|
|
1749
|
+
const hybridBoost = isHybrid ? 1.15 : 1;
|
|
1750
|
+
const finalScore = Math.min(1, score * hybridBoost * knowledgeTypeBoost(item.type));
|
|
1751
|
+
scored.push({
|
|
1752
|
+
id: item.id,
|
|
1753
|
+
title: item.title,
|
|
1754
|
+
content: item.content,
|
|
1755
|
+
type: item.type,
|
|
1756
|
+
project: item.project,
|
|
1757
|
+
created_at: item.created_at,
|
|
1758
|
+
created_at_epoch: item.created_at_epoch,
|
|
1759
|
+
score: finalScore,
|
|
1760
|
+
source: isHybrid ? "hybrid" : item.source,
|
|
1761
|
+
signals
|
|
1762
|
+
});
|
|
1763
|
+
}
|
|
1764
|
+
scored.sort((a, b) => b.score - a.score);
|
|
1765
|
+
const finalResults = scored.slice(0, limit);
|
|
1766
|
+
if (finalResults.length > 0) {
|
|
1767
|
+
try {
|
|
1768
|
+
const { updateLastAccessed: updateLastAccessed3 } = await Promise.resolve().then(() => (init_Observations(), Observations_exports));
|
|
1769
|
+
const ids = finalResults.map((r) => parseInt(r.id, 10)).filter((id) => id > 0);
|
|
1770
|
+
if (ids.length > 0) {
|
|
1771
|
+
updateLastAccessed3(db, ids);
|
|
1772
|
+
}
|
|
1773
|
+
} catch {
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
return finalResults;
|
|
1777
|
+
}
|
|
1778
|
+
};
|
|
1779
|
+
var hybridSearch = null;
|
|
1780
|
+
function getHybridSearch() {
|
|
1781
|
+
if (!hybridSearch) {
|
|
1782
|
+
hybridSearch = new HybridSearch();
|
|
1783
|
+
}
|
|
1784
|
+
return hybridSearch;
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
// src/types/worker-types.ts
|
|
1788
|
+
var KNOWLEDGE_TYPES = ["constraint", "decision", "heuristic", "rejected"];
|
|
1789
|
+
|
|
906
1790
|
// src/sdk/index.ts
|
|
907
1791
|
var KiroMemorySDK = class {
|
|
908
1792
|
db;
|
|
@@ -935,11 +1819,62 @@ var KiroMemorySDK = class {
|
|
|
935
1819
|
recentPrompts: getPromptsByProject(this.db.db, this.project, 10)
|
|
936
1820
|
};
|
|
937
1821
|
}
|
|
1822
|
+
/**
|
|
1823
|
+
* Valida input per storeObservation
|
|
1824
|
+
*/
|
|
1825
|
+
validateObservationInput(data) {
|
|
1826
|
+
if (!data.type || typeof data.type !== "string" || data.type.length > 100) {
|
|
1827
|
+
throw new Error("type \xE8 obbligatorio (stringa, max 100 caratteri)");
|
|
1828
|
+
}
|
|
1829
|
+
if (!data.title || typeof data.title !== "string" || data.title.length > 500) {
|
|
1830
|
+
throw new Error("title \xE8 obbligatorio (stringa, max 500 caratteri)");
|
|
1831
|
+
}
|
|
1832
|
+
if (!data.content || typeof data.content !== "string" || data.content.length > 1e5) {
|
|
1833
|
+
throw new Error("content \xE8 obbligatorio (stringa, max 100KB)");
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
/**
|
|
1837
|
+
* Valida input per storeSummary
|
|
1838
|
+
*/
|
|
1839
|
+
validateSummaryInput(data) {
|
|
1840
|
+
const MAX = 5e4;
|
|
1841
|
+
for (const [key, val] of Object.entries(data)) {
|
|
1842
|
+
if (val !== void 0 && val !== null) {
|
|
1843
|
+
if (typeof val !== "string") throw new Error(`${key} deve essere una stringa`);
|
|
1844
|
+
if (val.length > MAX) throw new Error(`${key} troppo grande (max 50KB)`);
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
/**
|
|
1849
|
+
* Genera e salva embedding per un'osservazione (fire-and-forget, non blocca)
|
|
1850
|
+
*/
|
|
1851
|
+
async generateEmbeddingAsync(observationId, title, content, concepts) {
|
|
1852
|
+
try {
|
|
1853
|
+
const embeddingService2 = getEmbeddingService();
|
|
1854
|
+
if (!embeddingService2.isAvailable()) return;
|
|
1855
|
+
const parts = [title, content];
|
|
1856
|
+
if (concepts?.length) parts.push(concepts.join(", "));
|
|
1857
|
+
const fullText = parts.join(" ").substring(0, 2e3);
|
|
1858
|
+
const embedding = await embeddingService2.embed(fullText);
|
|
1859
|
+
if (embedding) {
|
|
1860
|
+
const vectorSearch2 = getVectorSearch();
|
|
1861
|
+
await vectorSearch2.storeEmbedding(
|
|
1862
|
+
this.db.db,
|
|
1863
|
+
observationId,
|
|
1864
|
+
embedding,
|
|
1865
|
+
embeddingService2.getProvider() || "unknown"
|
|
1866
|
+
);
|
|
1867
|
+
}
|
|
1868
|
+
} catch (error) {
|
|
1869
|
+
logger.debug("SDK", `Embedding generation fallita per obs ${observationId}: ${error}`);
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
938
1872
|
/**
|
|
939
1873
|
* Store a new observation
|
|
940
1874
|
*/
|
|
941
1875
|
async storeObservation(data) {
|
|
942
|
-
|
|
1876
|
+
this.validateObservationInput(data);
|
|
1877
|
+
const observationId = createObservation(
|
|
943
1878
|
this.db.db,
|
|
944
1879
|
"sdk-" + Date.now(),
|
|
945
1880
|
this.project,
|
|
@@ -960,11 +1895,76 @@ var KiroMemorySDK = class {
|
|
|
960
1895
|
0
|
|
961
1896
|
// prompt_number
|
|
962
1897
|
);
|
|
1898
|
+
this.generateEmbeddingAsync(observationId, data.title, data.content, data.concepts).catch(() => {
|
|
1899
|
+
});
|
|
1900
|
+
return observationId;
|
|
1901
|
+
}
|
|
1902
|
+
/**
|
|
1903
|
+
* Salva conoscenza strutturata (constraint, decision, heuristic, rejected).
|
|
1904
|
+
* Usa il campo `type` per il knowledgeType e `facts` per i metadati JSON.
|
|
1905
|
+
*/
|
|
1906
|
+
async storeKnowledge(data) {
|
|
1907
|
+
if (!KNOWLEDGE_TYPES.includes(data.knowledgeType)) {
|
|
1908
|
+
throw new Error(`knowledgeType non valido: ${data.knowledgeType}. Valori ammessi: ${KNOWLEDGE_TYPES.join(", ")}`);
|
|
1909
|
+
}
|
|
1910
|
+
this.validateObservationInput({ type: data.knowledgeType, title: data.title, content: data.content });
|
|
1911
|
+
const metadata = (() => {
|
|
1912
|
+
switch (data.knowledgeType) {
|
|
1913
|
+
case "constraint":
|
|
1914
|
+
return {
|
|
1915
|
+
knowledgeType: "constraint",
|
|
1916
|
+
severity: data.metadata?.severity || "soft",
|
|
1917
|
+
reason: data.metadata?.reason
|
|
1918
|
+
};
|
|
1919
|
+
case "decision":
|
|
1920
|
+
return {
|
|
1921
|
+
knowledgeType: "decision",
|
|
1922
|
+
alternatives: data.metadata?.alternatives,
|
|
1923
|
+
reason: data.metadata?.reason
|
|
1924
|
+
};
|
|
1925
|
+
case "heuristic":
|
|
1926
|
+
return {
|
|
1927
|
+
knowledgeType: "heuristic",
|
|
1928
|
+
context: data.metadata?.context,
|
|
1929
|
+
confidence: data.metadata?.confidence
|
|
1930
|
+
};
|
|
1931
|
+
case "rejected":
|
|
1932
|
+
return {
|
|
1933
|
+
knowledgeType: "rejected",
|
|
1934
|
+
reason: data.metadata?.reason || "",
|
|
1935
|
+
alternatives: data.metadata?.alternatives
|
|
1936
|
+
};
|
|
1937
|
+
}
|
|
1938
|
+
})();
|
|
1939
|
+
const observationId = createObservation(
|
|
1940
|
+
this.db.db,
|
|
1941
|
+
"sdk-" + Date.now(),
|
|
1942
|
+
data.project || this.project,
|
|
1943
|
+
data.knowledgeType,
|
|
1944
|
+
// type = knowledgeType
|
|
1945
|
+
data.title,
|
|
1946
|
+
null,
|
|
1947
|
+
// subtitle
|
|
1948
|
+
data.content,
|
|
1949
|
+
null,
|
|
1950
|
+
// narrative
|
|
1951
|
+
JSON.stringify(metadata),
|
|
1952
|
+
// facts = metadati JSON
|
|
1953
|
+
data.concepts?.join(", ") || null,
|
|
1954
|
+
data.files?.join(", ") || null,
|
|
1955
|
+
data.files?.join(", ") || null,
|
|
1956
|
+
0
|
|
1957
|
+
// prompt_number
|
|
1958
|
+
);
|
|
1959
|
+
this.generateEmbeddingAsync(observationId, data.title, data.content, data.concepts).catch(() => {
|
|
1960
|
+
});
|
|
1961
|
+
return observationId;
|
|
963
1962
|
}
|
|
964
1963
|
/**
|
|
965
1964
|
* Store a session summary
|
|
966
1965
|
*/
|
|
967
1966
|
async storeSummary(data) {
|
|
1967
|
+
this.validateSummaryInput(data);
|
|
968
1968
|
return createSummary(
|
|
969
1969
|
this.db.db,
|
|
970
1970
|
"sdk-" + Date.now(),
|
|
@@ -1060,6 +2060,227 @@ var KiroMemorySDK = class {
|
|
|
1060
2060
|
getProject() {
|
|
1061
2061
|
return this.project;
|
|
1062
2062
|
}
|
|
2063
|
+
/**
|
|
2064
|
+
* Ricerca ibrida: vector search + keyword FTS5
|
|
2065
|
+
* Richiede inizializzazione HybridSearch (embedding service)
|
|
2066
|
+
*/
|
|
2067
|
+
async hybridSearch(query, options = {}) {
|
|
2068
|
+
const hybridSearch2 = getHybridSearch();
|
|
2069
|
+
return hybridSearch2.search(this.db.db, query, {
|
|
2070
|
+
project: this.project,
|
|
2071
|
+
limit: options.limit || 10
|
|
2072
|
+
});
|
|
2073
|
+
}
|
|
2074
|
+
/**
|
|
2075
|
+
* Ricerca solo semantica (vector search)
|
|
2076
|
+
* Ritorna risultati basati su similarità coseno con gli embeddings
|
|
2077
|
+
*/
|
|
2078
|
+
async semanticSearch(query, options = {}) {
|
|
2079
|
+
const embeddingService2 = getEmbeddingService();
|
|
2080
|
+
if (!embeddingService2.isAvailable()) {
|
|
2081
|
+
await embeddingService2.initialize();
|
|
2082
|
+
}
|
|
2083
|
+
if (!embeddingService2.isAvailable()) return [];
|
|
2084
|
+
const queryEmbedding = await embeddingService2.embed(query);
|
|
2085
|
+
if (!queryEmbedding) return [];
|
|
2086
|
+
const vectorSearch2 = getVectorSearch();
|
|
2087
|
+
const results = await vectorSearch2.search(this.db.db, queryEmbedding, {
|
|
2088
|
+
project: this.project,
|
|
2089
|
+
limit: options.limit || 10,
|
|
2090
|
+
threshold: options.threshold || 0.3
|
|
2091
|
+
});
|
|
2092
|
+
return results.map((r) => ({
|
|
2093
|
+
id: String(r.observationId),
|
|
2094
|
+
title: r.title,
|
|
2095
|
+
content: r.text || "",
|
|
2096
|
+
type: r.type,
|
|
2097
|
+
project: r.project,
|
|
2098
|
+
created_at: r.created_at,
|
|
2099
|
+
created_at_epoch: r.created_at_epoch,
|
|
2100
|
+
score: r.similarity,
|
|
2101
|
+
source: "vector",
|
|
2102
|
+
signals: {
|
|
2103
|
+
semantic: r.similarity,
|
|
2104
|
+
fts5: 0,
|
|
2105
|
+
recency: recencyScore(r.created_at_epoch),
|
|
2106
|
+
projectMatch: projectMatchScore(r.project, this.project)
|
|
2107
|
+
}
|
|
2108
|
+
}));
|
|
2109
|
+
}
|
|
2110
|
+
/**
|
|
2111
|
+
* Genera embeddings per osservazioni che non li hanno ancora
|
|
2112
|
+
*/
|
|
2113
|
+
async backfillEmbeddings(batchSize = 50) {
|
|
2114
|
+
const vectorSearch2 = getVectorSearch();
|
|
2115
|
+
return vectorSearch2.backfillEmbeddings(this.db.db, batchSize);
|
|
2116
|
+
}
|
|
2117
|
+
/**
|
|
2118
|
+
* Statistiche sugli embeddings nel database
|
|
2119
|
+
*/
|
|
2120
|
+
getEmbeddingStats() {
|
|
2121
|
+
const vectorSearch2 = getVectorSearch();
|
|
2122
|
+
return vectorSearch2.getStats(this.db.db);
|
|
2123
|
+
}
|
|
2124
|
+
/**
|
|
2125
|
+
* Inizializza il servizio di embedding (lazy, chiamare prima di hybridSearch)
|
|
2126
|
+
*/
|
|
2127
|
+
async initializeEmbeddings() {
|
|
2128
|
+
const hybridSearch2 = getHybridSearch();
|
|
2129
|
+
await hybridSearch2.initialize();
|
|
2130
|
+
return getEmbeddingService().isAvailable();
|
|
2131
|
+
}
|
|
2132
|
+
/**
|
|
2133
|
+
* Contesto smart con ranking a 4 segnali e budget token.
|
|
2134
|
+
*
|
|
2135
|
+
* Se query presente: usa HybridSearch con SEARCH_WEIGHTS.
|
|
2136
|
+
* Se senza query: ranking per recency + project match (CONTEXT_WEIGHTS).
|
|
2137
|
+
*/
|
|
2138
|
+
async getSmartContext(options = {}) {
|
|
2139
|
+
const tokenBudget = options.tokenBudget || parseInt(process.env.KIRO_MEMORY_CONTEXT_TOKENS || "0", 10) || 2e3;
|
|
2140
|
+
const summaries = getSummariesByProject(this.db.db, this.project, 5);
|
|
2141
|
+
let items;
|
|
2142
|
+
if (options.query) {
|
|
2143
|
+
const hybridSearch2 = getHybridSearch();
|
|
2144
|
+
const results = await hybridSearch2.search(this.db.db, options.query, {
|
|
2145
|
+
project: this.project,
|
|
2146
|
+
limit: 30
|
|
2147
|
+
});
|
|
2148
|
+
items = results.map((r) => ({
|
|
2149
|
+
id: parseInt(r.id, 10) || 0,
|
|
2150
|
+
title: r.title,
|
|
2151
|
+
content: r.content,
|
|
2152
|
+
type: r.type,
|
|
2153
|
+
project: r.project,
|
|
2154
|
+
created_at: r.created_at,
|
|
2155
|
+
created_at_epoch: r.created_at_epoch,
|
|
2156
|
+
score: r.score,
|
|
2157
|
+
signals: r.signals
|
|
2158
|
+
}));
|
|
2159
|
+
} else {
|
|
2160
|
+
const observations = getObservationsByProject(this.db.db, this.project, 30);
|
|
2161
|
+
items = observations.map((obs) => {
|
|
2162
|
+
const signals = {
|
|
2163
|
+
semantic: 0,
|
|
2164
|
+
fts5: 0,
|
|
2165
|
+
recency: recencyScore(obs.created_at_epoch),
|
|
2166
|
+
projectMatch: projectMatchScore(obs.project, this.project)
|
|
2167
|
+
};
|
|
2168
|
+
const baseScore = computeCompositeScore(signals, CONTEXT_WEIGHTS);
|
|
2169
|
+
const boostedScore = Math.min(1, baseScore * knowledgeTypeBoost(obs.type));
|
|
2170
|
+
return {
|
|
2171
|
+
id: obs.id,
|
|
2172
|
+
title: obs.title,
|
|
2173
|
+
content: obs.text || obs.narrative || "",
|
|
2174
|
+
type: obs.type,
|
|
2175
|
+
project: obs.project,
|
|
2176
|
+
created_at: obs.created_at,
|
|
2177
|
+
created_at_epoch: obs.created_at_epoch,
|
|
2178
|
+
score: boostedScore,
|
|
2179
|
+
signals
|
|
2180
|
+
};
|
|
2181
|
+
});
|
|
2182
|
+
items.sort((a, b) => b.score - a.score);
|
|
2183
|
+
}
|
|
2184
|
+
let tokensUsed = 0;
|
|
2185
|
+
for (const item of items) {
|
|
2186
|
+
tokensUsed += Math.ceil((item.title.length + item.content.length) / 4);
|
|
2187
|
+
if (tokensUsed > tokenBudget) break;
|
|
2188
|
+
}
|
|
2189
|
+
return {
|
|
2190
|
+
project: this.project,
|
|
2191
|
+
items,
|
|
2192
|
+
summaries,
|
|
2193
|
+
tokenBudget,
|
|
2194
|
+
tokensUsed: Math.min(tokensUsed, tokenBudget)
|
|
2195
|
+
};
|
|
2196
|
+
}
|
|
2197
|
+
/**
|
|
2198
|
+
* Rileva osservazioni stale (file modificati dopo la creazione) e le marca nel DB.
|
|
2199
|
+
* Ritorna il numero di osservazioni marcate come stale.
|
|
2200
|
+
*/
|
|
2201
|
+
async detectStaleObservations() {
|
|
2202
|
+
const staleObs = getStaleObservations(this.db.db, this.project);
|
|
2203
|
+
if (staleObs.length > 0) {
|
|
2204
|
+
const ids = staleObs.map((o) => o.id);
|
|
2205
|
+
markObservationsStale(this.db.db, ids, true);
|
|
2206
|
+
}
|
|
2207
|
+
return staleObs.length;
|
|
2208
|
+
}
|
|
2209
|
+
/**
|
|
2210
|
+
* Consolida osservazioni duplicate sullo stesso file e tipo.
|
|
2211
|
+
* Raggruppa per (project, type, files_modified), mantiene la piu recente.
|
|
2212
|
+
*/
|
|
2213
|
+
async consolidateObservations(options = {}) {
|
|
2214
|
+
return consolidateObservations(this.db.db, this.project, options);
|
|
2215
|
+
}
|
|
2216
|
+
/**
|
|
2217
|
+
* Statistiche decay: totale, stale, mai accedute, accedute di recente.
|
|
2218
|
+
*/
|
|
2219
|
+
async getDecayStats() {
|
|
2220
|
+
const total = this.db.db.query(
|
|
2221
|
+
"SELECT COUNT(*) as count FROM observations WHERE project = ?"
|
|
2222
|
+
).get(this.project)?.count || 0;
|
|
2223
|
+
const stale = this.db.db.query(
|
|
2224
|
+
"SELECT COUNT(*) as count FROM observations WHERE project = ? AND is_stale = 1"
|
|
2225
|
+
).get(this.project)?.count || 0;
|
|
2226
|
+
const neverAccessed = this.db.db.query(
|
|
2227
|
+
"SELECT COUNT(*) as count FROM observations WHERE project = ? AND last_accessed_epoch IS NULL"
|
|
2228
|
+
).get(this.project)?.count || 0;
|
|
2229
|
+
const recentThreshold = Date.now() - 48 * 60 * 60 * 1e3;
|
|
2230
|
+
const recentlyAccessed = this.db.db.query(
|
|
2231
|
+
"SELECT COUNT(*) as count FROM observations WHERE project = ? AND last_accessed_epoch > ?"
|
|
2232
|
+
).get(this.project, recentThreshold)?.count || 0;
|
|
2233
|
+
return { total, stale, neverAccessed, recentlyAccessed };
|
|
2234
|
+
}
|
|
2235
|
+
/**
|
|
2236
|
+
* Crea un checkpoint strutturato per resume sessione.
|
|
2237
|
+
* Salva automaticamente un context_snapshot con le ultime 10 osservazioni.
|
|
2238
|
+
*/
|
|
2239
|
+
async createCheckpoint(sessionId, data) {
|
|
2240
|
+
const recentObs = getObservationsByProject(this.db.db, this.project, 10);
|
|
2241
|
+
const contextSnapshot = JSON.stringify(
|
|
2242
|
+
recentObs.map((o) => ({ id: o.id, type: o.type, title: o.title, text: o.text?.substring(0, 200) }))
|
|
2243
|
+
);
|
|
2244
|
+
return createCheckpoint(this.db.db, sessionId, this.project, {
|
|
2245
|
+
task: data.task,
|
|
2246
|
+
progress: data.progress,
|
|
2247
|
+
nextSteps: data.nextSteps,
|
|
2248
|
+
openQuestions: data.openQuestions,
|
|
2249
|
+
relevantFiles: data.relevantFiles?.join(", "),
|
|
2250
|
+
contextSnapshot
|
|
2251
|
+
});
|
|
2252
|
+
}
|
|
2253
|
+
/**
|
|
2254
|
+
* Recupera l'ultimo checkpoint di una sessione specifica.
|
|
2255
|
+
*/
|
|
2256
|
+
async getCheckpoint(sessionId) {
|
|
2257
|
+
return getLatestCheckpoint(this.db.db, sessionId);
|
|
2258
|
+
}
|
|
2259
|
+
/**
|
|
2260
|
+
* Recupera l'ultimo checkpoint per il progetto corrente.
|
|
2261
|
+
* Utile per resume automatico senza specificare session ID.
|
|
2262
|
+
*/
|
|
2263
|
+
async getLatestProjectCheckpoint() {
|
|
2264
|
+
return getLatestCheckpointByProject(this.db.db, this.project);
|
|
2265
|
+
}
|
|
2266
|
+
/**
|
|
2267
|
+
* Genera un report di attività per il progetto corrente.
|
|
2268
|
+
* Aggrega osservazioni, sessioni, summaries e file per un periodo temporale.
|
|
2269
|
+
*/
|
|
2270
|
+
async generateReport(options) {
|
|
2271
|
+
const now = /* @__PURE__ */ new Date();
|
|
2272
|
+
let startEpoch;
|
|
2273
|
+
let endEpoch = now.getTime();
|
|
2274
|
+
if (options?.startDate && options?.endDate) {
|
|
2275
|
+
startEpoch = options.startDate.getTime();
|
|
2276
|
+
endEpoch = options.endDate.getTime();
|
|
2277
|
+
} else {
|
|
2278
|
+
const period = options?.period || "weekly";
|
|
2279
|
+
const daysBack = period === "monthly" ? 30 : 7;
|
|
2280
|
+
startEpoch = endEpoch - daysBack * 24 * 60 * 60 * 1e3;
|
|
2281
|
+
}
|
|
2282
|
+
return getReportData(this.db.db, this.project, startEpoch, endEpoch);
|
|
2283
|
+
}
|
|
1063
2284
|
/**
|
|
1064
2285
|
* Getter for direct database access (for API routes)
|
|
1065
2286
|
*/
|
|
@@ -1079,8 +2300,11 @@ function createKiroMemory(config) {
|
|
|
1079
2300
|
var ContextKitSDK = KiroMemorySDK;
|
|
1080
2301
|
var createContextKit = createKiroMemory;
|
|
1081
2302
|
|
|
2303
|
+
// src/index.ts
|
|
2304
|
+
init_Search();
|
|
2305
|
+
|
|
1082
2306
|
// src/hooks/utils.ts
|
|
1083
|
-
import { writeFileSync, mkdirSync as mkdirSync3, existsSync as
|
|
2307
|
+
import { writeFileSync, mkdirSync as mkdirSync3, existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
|
|
1084
2308
|
import { join as join3 } from "path";
|
|
1085
2309
|
var DATA_DIR2 = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR || join3(process.env.HOME || "/tmp", ".kiro-memory");
|
|
1086
2310
|
var TOKEN_FILE = join3(DATA_DIR2, "worker.token");
|
|
@@ -1089,7 +2313,7 @@ function debugLog(hookName, label, data) {
|
|
|
1089
2313
|
try {
|
|
1090
2314
|
const dataDir = process.env.KIRO_MEMORY_DATA_DIR || join3(process.env.HOME || "/tmp", ".kiro-memory");
|
|
1091
2315
|
const logDir = join3(dataDir, "logs");
|
|
1092
|
-
if (!
|
|
2316
|
+
if (!existsSync4(logDir)) mkdirSync3(logDir, { recursive: true });
|
|
1093
2317
|
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
1094
2318
|
const line = `[${ts}] [${hookName}] ${label}: ${JSON.stringify(data)}
|
|
1095
2319
|
`;
|
|
@@ -1102,10 +2326,19 @@ async function readStdin() {
|
|
|
1102
2326
|
return new Promise((resolve, reject) => {
|
|
1103
2327
|
let data = "";
|
|
1104
2328
|
process.stdin.setEncoding("utf8");
|
|
2329
|
+
const safetyTimeout = setTimeout(() => {
|
|
2330
|
+
if (!data.trim()) {
|
|
2331
|
+
resolve({
|
|
2332
|
+
hook_event_name: "agentSpawn",
|
|
2333
|
+
cwd: process.cwd()
|
|
2334
|
+
});
|
|
2335
|
+
}
|
|
2336
|
+
}, 5e3);
|
|
1105
2337
|
process.stdin.on("data", (chunk) => {
|
|
1106
2338
|
data += chunk;
|
|
1107
2339
|
});
|
|
1108
2340
|
process.stdin.on("end", () => {
|
|
2341
|
+
clearTimeout(safetyTimeout);
|
|
1109
2342
|
try {
|
|
1110
2343
|
if (!data.trim()) {
|
|
1111
2344
|
resolve({
|
|
@@ -1119,15 +2352,10 @@ async function readStdin() {
|
|
|
1119
2352
|
reject(new Error(`Errore parsing stdin JSON: ${err}`));
|
|
1120
2353
|
}
|
|
1121
2354
|
});
|
|
1122
|
-
process.stdin.on("error",
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
hook_event_name: "agentSpawn",
|
|
1127
|
-
cwd: process.cwd()
|
|
1128
|
-
});
|
|
1129
|
-
}
|
|
1130
|
-
}, 5e3);
|
|
2355
|
+
process.stdin.on("error", (err) => {
|
|
2356
|
+
clearTimeout(safetyTimeout);
|
|
2357
|
+
reject(err);
|
|
2358
|
+
});
|
|
1131
2359
|
});
|
|
1132
2360
|
}
|
|
1133
2361
|
function detectProject(cwd) {
|
|
@@ -1171,6 +2399,12 @@ function formatContext(data) {
|
|
|
1171
2399
|
async function runHook(name, handler) {
|
|
1172
2400
|
try {
|
|
1173
2401
|
const input = await readStdin();
|
|
2402
|
+
if (!input.cwd && input.workspace_roots?.[0]) {
|
|
2403
|
+
input.cwd = input.workspace_roots[0];
|
|
2404
|
+
}
|
|
2405
|
+
if (!input.session_id && input.conversation_id) {
|
|
2406
|
+
input.session_id = input.conversation_id;
|
|
2407
|
+
}
|
|
1174
2408
|
debugLog(name, "stdin", input);
|
|
1175
2409
|
await handler(input);
|
|
1176
2410
|
debugLog(name, "completato", { success: true });
|