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.
@@ -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
- var init_Observations = __esm({
134
- "src/services/sqlite/Observations.ts"() {
135
- "use strict";
136
- }
137
- });
138
-
139
- // src/services/sqlite/Summaries.ts
140
- var Summaries_exports = {};
141
- __export(Summaries_exports, {
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 deletePrompt(db, id) {
228
- db.run("DELETE FROM prompts WHERE id = ?", [id]);
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 init_Prompts = __esm({
231
- "src/services/sqlite/Prompts.ts"() {
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 = [query];
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 placeholders = ids.map(() => "?").join(",");
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(...ids);
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,62 @@ 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
+ function estimateTokens(text) {
426
+ if (!text) return 0;
427
+ return Math.ceil(text.length / 4);
428
+ }
429
+
430
+ // src/hooks/utils.ts
431
+ var DATA_DIR = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR || join(process.env.HOME || "/tmp", ".kiro-memory");
432
+ var TOKEN_FILE = join(DATA_DIR, "worker.token");
389
433
  function debugLog(hookName, label, data) {
390
434
  if ((process.env.KIRO_MEMORY_LOG_LEVEL || "").toUpperCase() !== "DEBUG") return;
391
435
  try {
@@ -404,10 +448,19 @@ async function readStdin() {
404
448
  return new Promise((resolve, reject) => {
405
449
  let data = "";
406
450
  process.stdin.setEncoding("utf8");
451
+ const safetyTimeout = setTimeout(() => {
452
+ if (!data.trim()) {
453
+ resolve({
454
+ hook_event_name: "agentSpawn",
455
+ cwd: process.cwd()
456
+ });
457
+ }
458
+ }, 5e3);
407
459
  process.stdin.on("data", (chunk) => {
408
460
  data += chunk;
409
461
  });
410
462
  process.stdin.on("end", () => {
463
+ clearTimeout(safetyTimeout);
411
464
  try {
412
465
  if (!data.trim()) {
413
466
  resolve({
@@ -421,15 +474,10 @@ async function readStdin() {
421
474
  reject(new Error(`Errore parsing stdin JSON: ${err}`));
422
475
  }
423
476
  });
424
- process.stdin.on("error", reject);
425
- setTimeout(() => {
426
- if (!data.trim()) {
427
- resolve({
428
- hook_event_name: "agentSpawn",
429
- cwd: process.cwd()
430
- });
431
- }
432
- }, 5e3);
477
+ process.stdin.on("error", (err) => {
478
+ clearTimeout(safetyTimeout);
479
+ reject(err);
480
+ });
433
481
  });
434
482
  }
435
483
  function detectProject(cwd) {
@@ -445,34 +493,61 @@ function detectProject(cwd) {
445
493
  return cwd.split("/").pop() || "default";
446
494
  }
447
495
  }
448
- function formatContext(data) {
496
+ function formatSmartContext(data) {
497
+ const budget = data.tokenBudget || parseInt(process.env.KIRO_MEMORY_CONTEXT_TOKENS || "0", 10) || 2e3;
449
498
  let output = "";
499
+ let tokensUsed = 0;
500
+ const header = "# Kiro Memory: Contesto Sessioni Precedenti\n\n";
501
+ tokensUsed += estimateTokens(header);
502
+ output += header;
450
503
  if (data.summaries && data.summaries.length > 0) {
451
- output += "## Sessioni Precedenti\n\n";
452
- data.summaries.slice(0, 3).forEach((sum) => {
453
- if (sum.learned) output += `- **Appreso**: ${sum.learned}
504
+ let sumSection = "## Sessioni Precedenti\n\n";
505
+ for (const sum of data.summaries.slice(0, 3)) {
506
+ if (sum.learned) sumSection += `- **Appreso**: ${sum.learned}
454
507
  `;
455
- if (sum.completed) output += `- **Completato**: ${sum.completed}
508
+ if (sum.completed) sumSection += `- **Completato**: ${sum.completed}
456
509
  `;
457
- if (sum.next_steps) output += `- **Prossimi passi**: ${sum.next_steps}
510
+ if (sum.next_steps) sumSection += `- **Prossimi passi**: ${sum.next_steps}
458
511
  `;
459
- output += "\n";
460
- });
461
- }
462
- if (data.observations && data.observations.length > 0) {
463
- output += "## Osservazioni Recenti\n\n";
464
- data.observations.slice(0, 10).forEach((obs) => {
465
- const text = obs.text ? obs.text.substring(0, 150) : "";
466
- output += `- **[${obs.type || "obs"}] ${obs.title}**: ${text}
512
+ sumSection += "\n";
513
+ }
514
+ tokensUsed += estimateTokens(sumSection);
515
+ output += sumSection;
516
+ }
517
+ if (data.items && data.items.length > 0) {
518
+ let obsSection = "## Osservazioni Rilevanti\n\n";
519
+ tokensUsed += estimateTokens(obsSection);
520
+ const sorted = [...data.items].sort((a, b) => b.score - a.score);
521
+ for (const item of sorted) {
522
+ const linePrefix = `- **[${item.type}] ${item.title}**: `;
523
+ const linePrefixTokens = estimateTokens(linePrefix);
524
+ const remainingTokens = budget - tokensUsed - linePrefixTokens - 1;
525
+ if (remainingTokens <= 0) break;
526
+ const maxContentChars = remainingTokens * 4;
527
+ const content = item.content ? item.content.substring(0, Math.min(maxContentChars, 300)) : "";
528
+ const line = `${linePrefix}${content}
467
529
  `;
468
- });
469
- output += "\n";
530
+ tokensUsed += estimateTokens(line);
531
+ obsSection += line;
532
+ if (tokensUsed >= budget) break;
533
+ }
534
+ output += obsSection;
470
535
  }
536
+ const footer = `
537
+ > Progetto: ${data.project} | Items: ${data.items?.length || 0} | Token usati: ~${tokensUsed}/${budget}
538
+ `;
539
+ output += footer;
471
540
  return output;
472
541
  }
473
542
  async function runHook(name, handler) {
474
543
  try {
475
544
  const input = await readStdin();
545
+ if (!input.cwd && input.workspace_roots?.[0]) {
546
+ input.cwd = input.workspace_roots[0];
547
+ }
548
+ if (!input.session_id && input.conversation_id) {
549
+ input.session_id = input.conversation_id;
550
+ }
476
551
  debugLog(name, "stdin", input);
477
552
  await handler(input);
478
553
  debugLog(name, "completato", { success: true });
@@ -555,11 +630,11 @@ var BunQueryCompat = class {
555
630
  // src/shared/paths.ts
556
631
  import { join as join3, dirname, basename } from "path";
557
632
  import { homedir as homedir2 } from "os";
558
- import { mkdirSync as mkdirSync3 } from "fs";
633
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3 } from "fs";
559
634
  import { fileURLToPath } from "url";
560
635
 
561
636
  // src/utils/logger.ts
562
- import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync } from "fs";
637
+ import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2 } from "fs";
563
638
  import { join as join2 } from "path";
564
639
  import { homedir } from "os";
565
640
  var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
@@ -605,7 +680,7 @@ var Logger = class {
605
680
  try {
606
681
  const settingsPath = join2(DEFAULT_DATA_DIR, "settings.json");
607
682
  if (existsSync2(settingsPath)) {
608
- const settingsData = readFileSync(settingsPath, "utf-8");
683
+ const settingsData = readFileSync2(settingsPath, "utf-8");
609
684
  const settings = JSON.parse(settingsData);
610
685
  const envLevel = (settings.KIRO_MEMORY_LOG_LEVEL || settings.CONTEXTKIT_LOG_LEVEL || "INFO").toUpperCase();
611
686
  this.level = LogLevel[envLevel] ?? 1 /* INFO */;
@@ -785,18 +860,21 @@ function getDirname() {
785
860
  return dirname(fileURLToPath(import.meta.url));
786
861
  }
787
862
  var _dirname = getDirname();
788
- var DATA_DIR = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR || join3(homedir2(), ".contextkit");
863
+ var _legacyDir = join3(homedir2(), ".contextkit");
864
+ var _defaultDir = existsSync3(_legacyDir) ? _legacyDir : join3(homedir2(), ".kiro-memory");
865
+ var DATA_DIR2 = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR || _defaultDir;
789
866
  var KIRO_CONFIG_DIR = process.env.KIRO_CONFIG_DIR || join3(homedir2(), ".kiro");
790
867
  var PLUGIN_ROOT = join3(KIRO_CONFIG_DIR, "plugins", "kiro-memory");
791
- var ARCHIVES_DIR = join3(DATA_DIR, "archives");
792
- var LOGS_DIR = join3(DATA_DIR, "logs");
793
- var TRASH_DIR = join3(DATA_DIR, "trash");
794
- var BACKUPS_DIR = join3(DATA_DIR, "backups");
795
- var MODES_DIR = join3(DATA_DIR, "modes");
796
- var USER_SETTINGS_PATH = join3(DATA_DIR, "settings.json");
797
- var DB_PATH = join3(DATA_DIR, "contextkit.db");
798
- var VECTOR_DB_DIR = join3(DATA_DIR, "vector-db");
799
- var OBSERVER_SESSIONS_DIR = join3(DATA_DIR, "observer-sessions");
868
+ var ARCHIVES_DIR = join3(DATA_DIR2, "archives");
869
+ var LOGS_DIR = join3(DATA_DIR2, "logs");
870
+ var TRASH_DIR = join3(DATA_DIR2, "trash");
871
+ var BACKUPS_DIR = join3(DATA_DIR2, "backups");
872
+ var MODES_DIR = join3(DATA_DIR2, "modes");
873
+ var USER_SETTINGS_PATH = join3(DATA_DIR2, "settings.json");
874
+ var _legacyDb = join3(DATA_DIR2, "contextkit.db");
875
+ var DB_PATH = existsSync3(_legacyDb) ? _legacyDb : join3(DATA_DIR2, "kiro-memory.db");
876
+ var VECTOR_DB_DIR = join3(DATA_DIR2, "vector-db");
877
+ var OBSERVER_SESSIONS_DIR = join3(DATA_DIR2, "observer-sessions");
800
878
  var KIRO_SETTINGS_PATH = join3(KIRO_CONFIG_DIR, "settings.json");
801
879
  var KIRO_CONTEXT_PATH = join3(KIRO_CONFIG_DIR, "context.md");
802
880
  function ensureDir(dirPath) {
@@ -808,9 +886,13 @@ var SQLITE_MMAP_SIZE_BYTES = 256 * 1024 * 1024;
808
886
  var SQLITE_CACHE_SIZE_PAGES = 1e4;
809
887
  var KiroMemoryDatabase = class {
810
888
  db;
811
- constructor(dbPath = DB_PATH) {
889
+ /**
890
+ * @param dbPath - Percorso al file SQLite (default: DB_PATH)
891
+ * @param skipMigrations - Se true, salta il migration runner (per hook ad alta frequenza)
892
+ */
893
+ constructor(dbPath = DB_PATH, skipMigrations = false) {
812
894
  if (dbPath !== ":memory:") {
813
- ensureDir(DATA_DIR);
895
+ ensureDir(DATA_DIR2);
814
896
  }
815
897
  this.db = new Database(dbPath, { create: true, readwrite: true });
816
898
  this.db.run("PRAGMA journal_mode = WAL");
@@ -819,8 +901,18 @@ var KiroMemoryDatabase = class {
819
901
  this.db.run("PRAGMA temp_store = memory");
820
902
  this.db.run(`PRAGMA mmap_size = ${SQLITE_MMAP_SIZE_BYTES}`);
821
903
  this.db.run(`PRAGMA cache_size = ${SQLITE_CACHE_SIZE_PAGES}`);
822
- const migrationRunner = new MigrationRunner(this.db);
823
- migrationRunner.runAllMigrations();
904
+ if (!skipMigrations) {
905
+ const migrationRunner = new MigrationRunner(this.db);
906
+ migrationRunner.runAllMigrations();
907
+ }
908
+ }
909
+ /**
910
+ * Esegue una funzione all'interno di una transazione atomica.
911
+ * Se fn() lancia un errore, la transazione viene annullata automaticamente.
912
+ */
913
+ withTransaction(fn) {
914
+ const transaction = this.db.transaction(fn);
915
+ return transaction(this.db);
824
916
  }
825
917
  /**
826
918
  * Close the database connection
@@ -995,30 +1087,741 @@ var MigrationRunner = class {
995
1087
  `);
996
1088
  db.run("CREATE UNIQUE INDEX IF NOT EXISTS idx_project_aliases_name ON project_aliases(project_name)");
997
1089
  }
1090
+ },
1091
+ {
1092
+ version: 4,
1093
+ up: (db) => {
1094
+ db.run(`
1095
+ CREATE TABLE IF NOT EXISTS observation_embeddings (
1096
+ observation_id INTEGER PRIMARY KEY,
1097
+ embedding BLOB NOT NULL,
1098
+ model TEXT NOT NULL,
1099
+ dimensions INTEGER NOT NULL,
1100
+ created_at TEXT NOT NULL,
1101
+ FOREIGN KEY (observation_id) REFERENCES observations(id) ON DELETE CASCADE
1102
+ )
1103
+ `);
1104
+ db.run("CREATE INDEX IF NOT EXISTS idx_embeddings_model ON observation_embeddings(model)");
1105
+ }
1106
+ },
1107
+ {
1108
+ version: 5,
1109
+ up: (db) => {
1110
+ db.run("ALTER TABLE observations ADD COLUMN last_accessed_epoch INTEGER");
1111
+ db.run("ALTER TABLE observations ADD COLUMN is_stale INTEGER DEFAULT 0");
1112
+ db.run("CREATE INDEX IF NOT EXISTS idx_observations_last_accessed ON observations(last_accessed_epoch)");
1113
+ db.run("CREATE INDEX IF NOT EXISTS idx_observations_stale ON observations(is_stale)");
1114
+ }
1115
+ },
1116
+ {
1117
+ version: 6,
1118
+ up: (db) => {
1119
+ db.run(`
1120
+ CREATE TABLE IF NOT EXISTS checkpoints (
1121
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1122
+ session_id INTEGER NOT NULL,
1123
+ project TEXT NOT NULL,
1124
+ task TEXT NOT NULL,
1125
+ progress TEXT,
1126
+ next_steps TEXT,
1127
+ open_questions TEXT,
1128
+ relevant_files TEXT,
1129
+ context_snapshot TEXT,
1130
+ created_at TEXT NOT NULL,
1131
+ created_at_epoch INTEGER NOT NULL,
1132
+ FOREIGN KEY (session_id) REFERENCES sessions(id)
1133
+ )
1134
+ `);
1135
+ db.run("CREATE INDEX IF NOT EXISTS idx_checkpoints_session ON checkpoints(session_id)");
1136
+ db.run("CREATE INDEX IF NOT EXISTS idx_checkpoints_project ON checkpoints(project)");
1137
+ db.run("CREATE INDEX IF NOT EXISTS idx_checkpoints_epoch ON checkpoints(created_at_epoch)");
1138
+ }
998
1139
  }
999
1140
  ];
1000
1141
  }
1001
1142
  };
1002
1143
 
1144
+ // src/services/sqlite/Sessions.ts
1145
+ function createSession(db, contentSessionId, project, userPrompt) {
1146
+ const now = /* @__PURE__ */ new Date();
1147
+ const result = db.run(
1148
+ `INSERT INTO sessions (content_session_id, project, user_prompt, status, started_at, started_at_epoch)
1149
+ VALUES (?, ?, ?, 'active', ?, ?)`,
1150
+ [contentSessionId, project, userPrompt, now.toISOString(), now.getTime()]
1151
+ );
1152
+ return Number(result.lastInsertRowid);
1153
+ }
1154
+ function getSessionByContentId(db, contentSessionId) {
1155
+ const query = db.query("SELECT * FROM sessions WHERE content_session_id = ?");
1156
+ return query.get(contentSessionId);
1157
+ }
1158
+ function completeSession(db, id) {
1159
+ const now = /* @__PURE__ */ new Date();
1160
+ db.run(
1161
+ `UPDATE sessions
1162
+ SET status = 'completed', completed_at = ?, completed_at_epoch = ?
1163
+ WHERE id = ?`,
1164
+ [now.toISOString(), now.getTime(), id]
1165
+ );
1166
+ }
1167
+
1003
1168
  // src/services/sqlite/index.ts
1004
- init_Sessions();
1005
1169
  init_Observations();
1006
- init_Summaries();
1007
- init_Prompts();
1008
- init_Search();
1009
1170
 
1010
- // src/sdk/index.ts
1011
- var KiroMemorySDK = class {
1012
- db;
1013
- project;
1014
- constructor(config = {}) {
1015
- this.db = new KiroMemoryDatabase(config.dataDir);
1016
- this.project = config.project || this.detectProject();
1017
- }
1018
- detectProject() {
1019
- try {
1020
- const { execSync } = __require("child_process");
1021
- const gitRoot = execSync("git rev-parse --show-toplevel", {
1171
+ // src/services/sqlite/Summaries.ts
1172
+ function escapeLikePattern2(input) {
1173
+ return input.replace(/[%_\\]/g, "\\$&");
1174
+ }
1175
+ function createSummary(db, sessionId, project, request, investigated, learned, completed, nextSteps, notes) {
1176
+ const now = /* @__PURE__ */ new Date();
1177
+ const result = db.run(
1178
+ `INSERT INTO summaries
1179
+ (session_id, project, request, investigated, learned, completed, next_steps, notes, created_at, created_at_epoch)
1180
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1181
+ [sessionId, project, request, investigated, learned, completed, nextSteps, notes, now.toISOString(), now.getTime()]
1182
+ );
1183
+ return Number(result.lastInsertRowid);
1184
+ }
1185
+ function getSummariesByProject(db, project, limit = 50) {
1186
+ const query = db.query(
1187
+ "SELECT * FROM summaries WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ?"
1188
+ );
1189
+ return query.all(project, limit);
1190
+ }
1191
+ function searchSummaries(db, searchTerm, project) {
1192
+ const sql = project ? `SELECT * FROM summaries
1193
+ WHERE project = ? AND (request LIKE ? ESCAPE '\\' OR learned LIKE ? ESCAPE '\\' OR completed LIKE ? ESCAPE '\\' OR notes LIKE ? ESCAPE '\\')
1194
+ ORDER BY created_at_epoch DESC` : `SELECT * FROM summaries
1195
+ WHERE request LIKE ? ESCAPE '\\' OR learned LIKE ? ESCAPE '\\' OR completed LIKE ? ESCAPE '\\' OR notes LIKE ? ESCAPE '\\'
1196
+ ORDER BY created_at_epoch DESC`;
1197
+ const pattern = `%${escapeLikePattern2(searchTerm)}%`;
1198
+ const query = db.query(sql);
1199
+ if (project) {
1200
+ return query.all(project, pattern, pattern, pattern, pattern);
1201
+ }
1202
+ return query.all(pattern, pattern, pattern, pattern);
1203
+ }
1204
+
1205
+ // src/services/sqlite/Prompts.ts
1206
+ function createPrompt(db, contentSessionId, project, promptNumber, promptText) {
1207
+ const now = /* @__PURE__ */ new Date();
1208
+ const result = db.run(
1209
+ `INSERT INTO prompts
1210
+ (content_session_id, project, prompt_number, prompt_text, created_at, created_at_epoch)
1211
+ VALUES (?, ?, ?, ?, ?, ?)`,
1212
+ [contentSessionId, project, promptNumber, promptText, now.toISOString(), now.getTime()]
1213
+ );
1214
+ return Number(result.lastInsertRowid);
1215
+ }
1216
+ function getPromptsByProject(db, project, limit = 100) {
1217
+ const query = db.query(
1218
+ "SELECT * FROM prompts WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ?"
1219
+ );
1220
+ return query.all(project, limit);
1221
+ }
1222
+
1223
+ // src/services/sqlite/Checkpoints.ts
1224
+ function createCheckpoint(db, sessionId, project, data) {
1225
+ const now = /* @__PURE__ */ new Date();
1226
+ const result = db.run(
1227
+ `INSERT INTO checkpoints (session_id, project, task, progress, next_steps, open_questions, relevant_files, context_snapshot, created_at, created_at_epoch)
1228
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1229
+ [
1230
+ sessionId,
1231
+ project,
1232
+ data.task,
1233
+ data.progress || null,
1234
+ data.nextSteps || null,
1235
+ data.openQuestions || null,
1236
+ data.relevantFiles || null,
1237
+ data.contextSnapshot || null,
1238
+ now.toISOString(),
1239
+ now.getTime()
1240
+ ]
1241
+ );
1242
+ return Number(result.lastInsertRowid);
1243
+ }
1244
+ function getLatestCheckpoint(db, sessionId) {
1245
+ const query = db.query(
1246
+ "SELECT * FROM checkpoints WHERE session_id = ? ORDER BY created_at_epoch DESC LIMIT 1"
1247
+ );
1248
+ return query.get(sessionId);
1249
+ }
1250
+ function getLatestCheckpointByProject(db, project) {
1251
+ const query = db.query(
1252
+ "SELECT * FROM checkpoints WHERE project = ? ORDER BY created_at_epoch DESC LIMIT 1"
1253
+ );
1254
+ return query.get(project);
1255
+ }
1256
+
1257
+ // src/services/sqlite/Reports.ts
1258
+ function getReportData(db, project, startEpoch, endEpoch) {
1259
+ const startDate = new Date(startEpoch);
1260
+ const endDate = new Date(endEpoch);
1261
+ const days = Math.ceil((endEpoch - startEpoch) / (24 * 60 * 60 * 1e3));
1262
+ const label = days <= 7 ? "Weekly" : days <= 31 ? "Monthly" : "Custom";
1263
+ const countInRange = (table, epochCol = "created_at_epoch") => {
1264
+ 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} <= ?`;
1265
+ const stmt = db.query(sql);
1266
+ const row = project ? stmt.get(project, startEpoch, endEpoch) : stmt.get(startEpoch, endEpoch);
1267
+ return row?.count || 0;
1268
+ };
1269
+ const observations = countInRange("observations");
1270
+ const summaries = countInRange("summaries");
1271
+ const prompts = countInRange("prompts");
1272
+ const sessions = countInRange("sessions", "started_at_epoch");
1273
+ const timelineSql = project ? `SELECT DATE(datetime(created_at_epoch / 1000, 'unixepoch')) as day, COUNT(*) as count
1274
+ FROM observations
1275
+ WHERE project = ? AND created_at_epoch >= ? AND created_at_epoch <= ?
1276
+ GROUP BY day ORDER BY day ASC` : `SELECT DATE(datetime(created_at_epoch / 1000, 'unixepoch')) as day, COUNT(*) as count
1277
+ FROM observations
1278
+ WHERE created_at_epoch >= ? AND created_at_epoch <= ?
1279
+ GROUP BY day ORDER BY day ASC`;
1280
+ const timelineStmt = db.query(timelineSql);
1281
+ const timeline = project ? timelineStmt.all(project, startEpoch, endEpoch) : timelineStmt.all(startEpoch, endEpoch);
1282
+ const typeSql = project ? `SELECT type, COUNT(*) as count FROM observations
1283
+ WHERE project = ? AND created_at_epoch >= ? AND created_at_epoch <= ?
1284
+ GROUP BY type ORDER BY count DESC` : `SELECT type, COUNT(*) as count FROM observations
1285
+ WHERE created_at_epoch >= ? AND created_at_epoch <= ?
1286
+ GROUP BY type ORDER BY count DESC`;
1287
+ const typeStmt = db.query(typeSql);
1288
+ const typeDistribution = project ? typeStmt.all(project, startEpoch, endEpoch) : typeStmt.all(startEpoch, endEpoch);
1289
+ 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 <= ?`;
1290
+ const sessionTotal = (project ? db.query(sessionTotalSql).get(project, startEpoch, endEpoch)?.count : db.query(sessionTotalSql).get(startEpoch, endEpoch)?.count) || 0;
1291
+ 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'`;
1292
+ const sessionCompleted = (project ? db.query(sessionCompletedSql).get(project, startEpoch, endEpoch)?.count : db.query(sessionCompletedSql).get(startEpoch, endEpoch)?.count) || 0;
1293
+ const sessionAvgSql = project ? `SELECT AVG((completed_at_epoch - started_at_epoch) / 1000.0 / 60.0) as avg_min
1294
+ FROM sessions
1295
+ WHERE project = ? AND started_at_epoch >= ? AND started_at_epoch <= ?
1296
+ 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
1297
+ FROM sessions
1298
+ WHERE started_at_epoch >= ? AND started_at_epoch <= ?
1299
+ AND status = 'completed' AND completed_at_epoch IS NOT NULL AND completed_at_epoch > started_at_epoch`;
1300
+ const avgRow = project ? db.query(sessionAvgSql).get(project, startEpoch, endEpoch) : db.query(sessionAvgSql).get(startEpoch, endEpoch);
1301
+ const avgDurationMinutes = Math.round((avgRow?.avg_min || 0) * 10) / 10;
1302
+ const knowledgeSql = project ? `SELECT COUNT(*) as count FROM observations
1303
+ WHERE project = ? AND created_at_epoch >= ? AND created_at_epoch <= ?
1304
+ AND type IN ('constraint', 'decision', 'heuristic', 'rejected')` : `SELECT COUNT(*) as count FROM observations
1305
+ WHERE created_at_epoch >= ? AND created_at_epoch <= ?
1306
+ AND type IN ('constraint', 'decision', 'heuristic', 'rejected')`;
1307
+ const knowledgeCount = (project ? db.query(knowledgeSql).get(project, startEpoch, endEpoch)?.count : db.query(knowledgeSql).get(startEpoch, endEpoch)?.count) || 0;
1308
+ const staleSql = project ? `SELECT COUNT(*) as count FROM observations
1309
+ WHERE project = ? AND created_at_epoch >= ? AND created_at_epoch <= ? AND is_stale = 1` : `SELECT COUNT(*) as count FROM observations
1310
+ WHERE created_at_epoch >= ? AND created_at_epoch <= ? AND is_stale = 1`;
1311
+ const staleCount = (project ? db.query(staleSql).get(project, startEpoch, endEpoch)?.count : db.query(staleSql).get(startEpoch, endEpoch)?.count) || 0;
1312
+ const summarySql = project ? `SELECT learned, completed, next_steps FROM summaries
1313
+ WHERE project = ? AND created_at_epoch >= ? AND created_at_epoch <= ?
1314
+ ORDER BY created_at_epoch DESC` : `SELECT learned, completed, next_steps FROM summaries
1315
+ WHERE created_at_epoch >= ? AND created_at_epoch <= ?
1316
+ ORDER BY created_at_epoch DESC`;
1317
+ const summaryRows = project ? db.query(summarySql).all(project, startEpoch, endEpoch) : db.query(summarySql).all(startEpoch, endEpoch);
1318
+ const topLearnings = [];
1319
+ const completedTasks = [];
1320
+ const nextStepsArr = [];
1321
+ for (const row of summaryRows) {
1322
+ if (row.learned) {
1323
+ const parts = row.learned.split("; ").filter(Boolean);
1324
+ topLearnings.push(...parts);
1325
+ }
1326
+ if (row.completed) {
1327
+ const parts = row.completed.split("; ").filter(Boolean);
1328
+ completedTasks.push(...parts);
1329
+ }
1330
+ if (row.next_steps) {
1331
+ const parts = row.next_steps.split("; ").filter(Boolean);
1332
+ nextStepsArr.push(...parts);
1333
+ }
1334
+ }
1335
+ const filesSql = project ? `SELECT files_modified FROM observations
1336
+ WHERE project = ? AND created_at_epoch >= ? AND created_at_epoch <= ?
1337
+ AND files_modified IS NOT NULL AND files_modified != ''` : `SELECT files_modified FROM observations
1338
+ WHERE created_at_epoch >= ? AND created_at_epoch <= ?
1339
+ AND files_modified IS NOT NULL AND files_modified != ''`;
1340
+ const fileRows = project ? db.query(filesSql).all(project, startEpoch, endEpoch) : db.query(filesSql).all(startEpoch, endEpoch);
1341
+ const fileCounts = /* @__PURE__ */ new Map();
1342
+ for (const row of fileRows) {
1343
+ const files = row.files_modified.split(",").map((f) => f.trim()).filter(Boolean);
1344
+ for (const file of files) {
1345
+ fileCounts.set(file, (fileCounts.get(file) || 0) + 1);
1346
+ }
1347
+ }
1348
+ const fileHotspots = Array.from(fileCounts.entries()).map(([file, count]) => ({ file, count })).sort((a, b) => b.count - a.count).slice(0, 15);
1349
+ return {
1350
+ period: {
1351
+ start: startDate.toISOString().split("T")[0],
1352
+ end: endDate.toISOString().split("T")[0],
1353
+ days,
1354
+ label
1355
+ },
1356
+ overview: {
1357
+ observations,
1358
+ summaries,
1359
+ sessions,
1360
+ prompts,
1361
+ knowledgeCount,
1362
+ staleCount
1363
+ },
1364
+ timeline,
1365
+ typeDistribution,
1366
+ sessionStats: {
1367
+ total: sessionTotal,
1368
+ completed: sessionCompleted,
1369
+ avgDurationMinutes
1370
+ },
1371
+ topLearnings: [...new Set(topLearnings)].slice(0, 10),
1372
+ completedTasks: [...new Set(completedTasks)].slice(0, 10),
1373
+ nextSteps: [...new Set(nextStepsArr)].slice(0, 10),
1374
+ fileHotspots
1375
+ };
1376
+ }
1377
+
1378
+ // src/services/sqlite/index.ts
1379
+ init_Search();
1380
+
1381
+ // src/sdk/index.ts
1382
+ init_Observations();
1383
+ init_Search();
1384
+
1385
+ // src/services/search/EmbeddingService.ts
1386
+ var EmbeddingService = class {
1387
+ provider = null;
1388
+ model = null;
1389
+ initialized = false;
1390
+ initializing = null;
1391
+ /**
1392
+ * Inizializza il servizio di embedding.
1393
+ * Tenta fastembed, poi @huggingface/transformers, poi fallback a null.
1394
+ */
1395
+ async initialize() {
1396
+ if (this.initialized) return this.provider !== null;
1397
+ if (this.initializing) return this.initializing;
1398
+ this.initializing = this._doInitialize();
1399
+ const result = await this.initializing;
1400
+ this.initializing = null;
1401
+ return result;
1402
+ }
1403
+ async _doInitialize() {
1404
+ try {
1405
+ const fastembed = await import("fastembed");
1406
+ const EmbeddingModel = fastembed.EmbeddingModel || fastembed.default?.EmbeddingModel;
1407
+ const FlagEmbedding = fastembed.FlagEmbedding || fastembed.default?.FlagEmbedding;
1408
+ if (FlagEmbedding && EmbeddingModel) {
1409
+ this.model = await FlagEmbedding.init({
1410
+ model: EmbeddingModel.BGESmallENV15
1411
+ });
1412
+ this.provider = "fastembed";
1413
+ this.initialized = true;
1414
+ logger.info("EMBEDDING", "Inizializzato con fastembed (BGE-small-en-v1.5)");
1415
+ return true;
1416
+ }
1417
+ } catch (error) {
1418
+ logger.debug("EMBEDDING", `fastembed non disponibile: ${error}`);
1419
+ }
1420
+ try {
1421
+ const transformers = await import("@huggingface/transformers");
1422
+ const pipeline = transformers.pipeline || transformers.default?.pipeline;
1423
+ if (pipeline) {
1424
+ this.model = await pipeline("feature-extraction", "Xenova/all-MiniLM-L6-v2", {
1425
+ quantized: true
1426
+ });
1427
+ this.provider = "transformers";
1428
+ this.initialized = true;
1429
+ logger.info("EMBEDDING", "Inizializzato con @huggingface/transformers (all-MiniLM-L6-v2)");
1430
+ return true;
1431
+ }
1432
+ } catch (error) {
1433
+ logger.debug("EMBEDDING", `@huggingface/transformers non disponibile: ${error}`);
1434
+ }
1435
+ this.provider = null;
1436
+ this.initialized = true;
1437
+ logger.warn("EMBEDDING", "Nessun provider embedding disponibile, ricerca semantica disabilitata");
1438
+ return false;
1439
+ }
1440
+ /**
1441
+ * Genera embedding per un singolo testo.
1442
+ * Ritorna Float32Array con 384 dimensioni, o null se non disponibile.
1443
+ */
1444
+ async embed(text) {
1445
+ if (!this.initialized) await this.initialize();
1446
+ if (!this.provider || !this.model) return null;
1447
+ try {
1448
+ const truncated = text.substring(0, 2e3);
1449
+ if (this.provider === "fastembed") {
1450
+ return await this._embedFastembed(truncated);
1451
+ } else if (this.provider === "transformers") {
1452
+ return await this._embedTransformers(truncated);
1453
+ }
1454
+ } catch (error) {
1455
+ logger.error("EMBEDDING", `Errore generazione embedding: ${error}`);
1456
+ }
1457
+ return null;
1458
+ }
1459
+ /**
1460
+ * Genera embeddings in batch.
1461
+ */
1462
+ async embedBatch(texts) {
1463
+ if (!this.initialized) await this.initialize();
1464
+ if (!this.provider || !this.model) return texts.map(() => null);
1465
+ const results = [];
1466
+ for (const text of texts) {
1467
+ try {
1468
+ const embedding = await this.embed(text);
1469
+ results.push(embedding);
1470
+ } catch {
1471
+ results.push(null);
1472
+ }
1473
+ }
1474
+ return results;
1475
+ }
1476
+ /**
1477
+ * Verifica se il servizio è disponibile.
1478
+ */
1479
+ isAvailable() {
1480
+ return this.initialized && this.provider !== null;
1481
+ }
1482
+ /**
1483
+ * Nome del provider attivo.
1484
+ */
1485
+ getProvider() {
1486
+ return this.provider;
1487
+ }
1488
+ /**
1489
+ * Dimensioni del vettore embedding.
1490
+ */
1491
+ getDimensions() {
1492
+ return 384;
1493
+ }
1494
+ // --- Provider specifici ---
1495
+ async _embedFastembed(text) {
1496
+ const embeddings = this.model.embed([text], 1);
1497
+ for await (const batch of embeddings) {
1498
+ if (batch && batch.length > 0) {
1499
+ const vec = batch[0];
1500
+ return vec instanceof Float32Array ? vec : new Float32Array(vec);
1501
+ }
1502
+ }
1503
+ return null;
1504
+ }
1505
+ async _embedTransformers(text) {
1506
+ const output = await this.model(text, {
1507
+ pooling: "mean",
1508
+ normalize: true
1509
+ });
1510
+ if (output?.data) {
1511
+ return output.data instanceof Float32Array ? output.data : new Float32Array(output.data);
1512
+ }
1513
+ return null;
1514
+ }
1515
+ };
1516
+ var embeddingService = null;
1517
+ function getEmbeddingService() {
1518
+ if (!embeddingService) {
1519
+ embeddingService = new EmbeddingService();
1520
+ }
1521
+ return embeddingService;
1522
+ }
1523
+
1524
+ // src/services/search/VectorSearch.ts
1525
+ function cosineSimilarity(a, b) {
1526
+ if (a.length !== b.length) return 0;
1527
+ let dotProduct = 0;
1528
+ let normA = 0;
1529
+ let normB = 0;
1530
+ for (let i = 0; i < a.length; i++) {
1531
+ dotProduct += a[i] * b[i];
1532
+ normA += a[i] * a[i];
1533
+ normB += b[i] * b[i];
1534
+ }
1535
+ const denominator = Math.sqrt(normA) * Math.sqrt(normB);
1536
+ if (denominator === 0) return 0;
1537
+ return dotProduct / denominator;
1538
+ }
1539
+ function float32ToBuffer(arr) {
1540
+ return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength);
1541
+ }
1542
+ function bufferToFloat32(buf) {
1543
+ const arrayBuffer = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
1544
+ return new Float32Array(arrayBuffer);
1545
+ }
1546
+ var VectorSearch = class {
1547
+ /**
1548
+ * Ricerca semantica: calcola cosine similarity tra query e tutti gli embeddings.
1549
+ */
1550
+ async search(db, queryEmbedding, options = {}) {
1551
+ const limit = options.limit || 10;
1552
+ const threshold = options.threshold || 0.3;
1553
+ try {
1554
+ let sql = `
1555
+ SELECT e.observation_id, e.embedding,
1556
+ o.title, o.text, o.type, o.project, o.created_at, o.created_at_epoch
1557
+ FROM observation_embeddings e
1558
+ JOIN observations o ON o.id = e.observation_id
1559
+ `;
1560
+ const params = [];
1561
+ if (options.project) {
1562
+ sql += " WHERE o.project = ?";
1563
+ params.push(options.project);
1564
+ }
1565
+ const rows = db.query(sql).all(...params);
1566
+ const scored = [];
1567
+ for (const row of rows) {
1568
+ const embedding = bufferToFloat32(row.embedding);
1569
+ const similarity = cosineSimilarity(queryEmbedding, embedding);
1570
+ if (similarity >= threshold) {
1571
+ scored.push({
1572
+ id: row.observation_id,
1573
+ observationId: row.observation_id,
1574
+ similarity,
1575
+ title: row.title,
1576
+ text: row.text,
1577
+ type: row.type,
1578
+ project: row.project,
1579
+ created_at: row.created_at,
1580
+ created_at_epoch: row.created_at_epoch
1581
+ });
1582
+ }
1583
+ }
1584
+ scored.sort((a, b) => b.similarity - a.similarity);
1585
+ return scored.slice(0, limit);
1586
+ } catch (error) {
1587
+ logger.error("VECTOR", `Errore ricerca vettoriale: ${error}`);
1588
+ return [];
1589
+ }
1590
+ }
1591
+ /**
1592
+ * Salva embedding per un'osservazione.
1593
+ */
1594
+ async storeEmbedding(db, observationId, embedding, model) {
1595
+ try {
1596
+ const blob = float32ToBuffer(embedding);
1597
+ db.query(`
1598
+ INSERT OR REPLACE INTO observation_embeddings
1599
+ (observation_id, embedding, model, dimensions, created_at)
1600
+ VALUES (?, ?, ?, ?, ?)
1601
+ `).run(
1602
+ observationId,
1603
+ blob,
1604
+ model,
1605
+ embedding.length,
1606
+ (/* @__PURE__ */ new Date()).toISOString()
1607
+ );
1608
+ logger.debug("VECTOR", `Embedding salvato per osservazione ${observationId}`);
1609
+ } catch (error) {
1610
+ logger.error("VECTOR", `Errore salvataggio embedding: ${error}`);
1611
+ }
1612
+ }
1613
+ /**
1614
+ * Genera embeddings per osservazioni che non li hanno ancora.
1615
+ */
1616
+ async backfillEmbeddings(db, batchSize = 50) {
1617
+ const embeddingService2 = getEmbeddingService();
1618
+ if (!await embeddingService2.initialize()) {
1619
+ logger.warn("VECTOR", "Embedding service non disponibile, backfill saltato");
1620
+ return 0;
1621
+ }
1622
+ const rows = db.query(`
1623
+ SELECT o.id, o.title, o.text, o.narrative, o.concepts
1624
+ FROM observations o
1625
+ LEFT JOIN observation_embeddings e ON e.observation_id = o.id
1626
+ WHERE e.observation_id IS NULL
1627
+ ORDER BY o.created_at_epoch DESC
1628
+ LIMIT ?
1629
+ `).all(batchSize);
1630
+ if (rows.length === 0) return 0;
1631
+ let count = 0;
1632
+ const model = embeddingService2.getProvider() || "unknown";
1633
+ for (const row of rows) {
1634
+ const parts = [row.title];
1635
+ if (row.text) parts.push(row.text);
1636
+ if (row.narrative) parts.push(row.narrative);
1637
+ if (row.concepts) parts.push(row.concepts);
1638
+ const fullText = parts.join(" ").substring(0, 2e3);
1639
+ const embedding = await embeddingService2.embed(fullText);
1640
+ if (embedding) {
1641
+ await this.storeEmbedding(db, row.id, embedding, model);
1642
+ count++;
1643
+ }
1644
+ }
1645
+ logger.info("VECTOR", `Backfill completato: ${count}/${rows.length} embeddings generati`);
1646
+ return count;
1647
+ }
1648
+ /**
1649
+ * Statistiche sugli embeddings.
1650
+ */
1651
+ getStats(db) {
1652
+ try {
1653
+ const totalRow = db.query("SELECT COUNT(*) as count FROM observations").get();
1654
+ const embeddedRow = db.query("SELECT COUNT(*) as count FROM observation_embeddings").get();
1655
+ const total = totalRow?.count || 0;
1656
+ const embedded = embeddedRow?.count || 0;
1657
+ const percentage = total > 0 ? Math.round(embedded / total * 100) : 0;
1658
+ return { total, embedded, percentage };
1659
+ } catch {
1660
+ return { total: 0, embedded: 0, percentage: 0 };
1661
+ }
1662
+ }
1663
+ };
1664
+ var vectorSearch = null;
1665
+ function getVectorSearch() {
1666
+ if (!vectorSearch) {
1667
+ vectorSearch = new VectorSearch();
1668
+ }
1669
+ return vectorSearch;
1670
+ }
1671
+
1672
+ // src/services/search/HybridSearch.ts
1673
+ var HybridSearch = class {
1674
+ embeddingInitialized = false;
1675
+ /**
1676
+ * Inizializza il servizio di embedding (lazy, non bloccante)
1677
+ */
1678
+ async initialize() {
1679
+ try {
1680
+ const embeddingService2 = getEmbeddingService();
1681
+ await embeddingService2.initialize();
1682
+ this.embeddingInitialized = embeddingService2.isAvailable();
1683
+ logger.info("SEARCH", `HybridSearch inizializzato (embedding: ${this.embeddingInitialized ? "attivo" : "disattivato"})`);
1684
+ } catch (error) {
1685
+ logger.warn("SEARCH", "Inizializzazione embedding fallita, uso solo FTS5", {}, error);
1686
+ this.embeddingInitialized = false;
1687
+ }
1688
+ }
1689
+ /**
1690
+ * Ricerca ibrida con scoring a 4 segnali
1691
+ */
1692
+ async search(db, query, options = {}) {
1693
+ const limit = options.limit || 10;
1694
+ const weights = options.weights || SEARCH_WEIGHTS;
1695
+ const targetProject = options.project || "";
1696
+ const rawItems = /* @__PURE__ */ new Map();
1697
+ if (this.embeddingInitialized) {
1698
+ try {
1699
+ const embeddingService2 = getEmbeddingService();
1700
+ const queryEmbedding = await embeddingService2.embed(query);
1701
+ if (queryEmbedding) {
1702
+ const vectorSearch2 = getVectorSearch();
1703
+ const vectorResults = await vectorSearch2.search(db, queryEmbedding, {
1704
+ project: options.project,
1705
+ limit: limit * 2,
1706
+ // Prendiamo piu risultati per il ranking
1707
+ threshold: 0.3
1708
+ });
1709
+ for (const hit of vectorResults) {
1710
+ rawItems.set(String(hit.observationId), {
1711
+ id: String(hit.observationId),
1712
+ title: hit.title,
1713
+ content: hit.text || "",
1714
+ type: hit.type,
1715
+ project: hit.project,
1716
+ created_at: hit.created_at,
1717
+ created_at_epoch: hit.created_at_epoch,
1718
+ semanticScore: hit.similarity,
1719
+ fts5Rank: null,
1720
+ source: "vector"
1721
+ });
1722
+ }
1723
+ logger.debug("SEARCH", `Vector search: ${vectorResults.length} risultati`);
1724
+ }
1725
+ } catch (error) {
1726
+ logger.warn("SEARCH", "Ricerca vettoriale fallita, uso solo keyword", {}, error);
1727
+ }
1728
+ }
1729
+ try {
1730
+ const { searchObservationsFTSWithRank: searchObservationsFTSWithRank2 } = await Promise.resolve().then(() => (init_Search(), Search_exports));
1731
+ const keywordResults = searchObservationsFTSWithRank2(db, query, {
1732
+ project: options.project,
1733
+ limit: limit * 2
1734
+ });
1735
+ for (const obs of keywordResults) {
1736
+ const id = String(obs.id);
1737
+ const existing = rawItems.get(id);
1738
+ if (existing) {
1739
+ existing.fts5Rank = obs.fts5_rank;
1740
+ existing.source = "vector";
1741
+ } else {
1742
+ rawItems.set(id, {
1743
+ id,
1744
+ title: obs.title,
1745
+ content: obs.text || obs.narrative || "",
1746
+ type: obs.type,
1747
+ project: obs.project,
1748
+ created_at: obs.created_at,
1749
+ created_at_epoch: obs.created_at_epoch,
1750
+ semanticScore: 0,
1751
+ fts5Rank: obs.fts5_rank,
1752
+ source: "keyword"
1753
+ });
1754
+ }
1755
+ }
1756
+ logger.debug("SEARCH", `Keyword search: ${keywordResults.length} risultati`);
1757
+ } catch (error) {
1758
+ logger.error("SEARCH", "Ricerca keyword fallita", {}, error);
1759
+ }
1760
+ if (rawItems.size === 0) return [];
1761
+ const allFTS5Ranks = Array.from(rawItems.values()).filter((item) => item.fts5Rank !== null).map((item) => item.fts5Rank);
1762
+ const scored = [];
1763
+ for (const item of rawItems.values()) {
1764
+ const signals = {
1765
+ semantic: item.semanticScore,
1766
+ fts5: item.fts5Rank !== null ? normalizeFTS5Rank(item.fts5Rank, allFTS5Ranks) : 0,
1767
+ recency: recencyScore(item.created_at_epoch),
1768
+ projectMatch: targetProject ? projectMatchScore(item.project, targetProject) : 0
1769
+ };
1770
+ const score = computeCompositeScore(signals, weights);
1771
+ const isHybrid = item.semanticScore > 0 && item.fts5Rank !== null;
1772
+ const hybridBoost = isHybrid ? 1.15 : 1;
1773
+ const finalScore = Math.min(1, score * hybridBoost * knowledgeTypeBoost(item.type));
1774
+ scored.push({
1775
+ id: item.id,
1776
+ title: item.title,
1777
+ content: item.content,
1778
+ type: item.type,
1779
+ project: item.project,
1780
+ created_at: item.created_at,
1781
+ created_at_epoch: item.created_at_epoch,
1782
+ score: finalScore,
1783
+ source: isHybrid ? "hybrid" : item.source,
1784
+ signals
1785
+ });
1786
+ }
1787
+ scored.sort((a, b) => b.score - a.score);
1788
+ const finalResults = scored.slice(0, limit);
1789
+ if (finalResults.length > 0) {
1790
+ try {
1791
+ const { updateLastAccessed: updateLastAccessed3 } = await Promise.resolve().then(() => (init_Observations(), Observations_exports));
1792
+ const ids = finalResults.map((r) => parseInt(r.id, 10)).filter((id) => id > 0);
1793
+ if (ids.length > 0) {
1794
+ updateLastAccessed3(db, ids);
1795
+ }
1796
+ } catch {
1797
+ }
1798
+ }
1799
+ return finalResults;
1800
+ }
1801
+ };
1802
+ var hybridSearch = null;
1803
+ function getHybridSearch() {
1804
+ if (!hybridSearch) {
1805
+ hybridSearch = new HybridSearch();
1806
+ }
1807
+ return hybridSearch;
1808
+ }
1809
+
1810
+ // src/types/worker-types.ts
1811
+ var KNOWLEDGE_TYPES = ["constraint", "decision", "heuristic", "rejected"];
1812
+
1813
+ // src/sdk/index.ts
1814
+ var KiroMemorySDK = class {
1815
+ db;
1816
+ project;
1817
+ constructor(config = {}) {
1818
+ this.db = new KiroMemoryDatabase(config.dataDir, config.skipMigrations || false);
1819
+ this.project = config.project || this.detectProject();
1820
+ }
1821
+ detectProject() {
1822
+ try {
1823
+ const { execSync } = __require("child_process");
1824
+ const gitRoot = execSync("git rev-parse --show-toplevel", {
1022
1825
  cwd: process.cwd(),
1023
1826
  encoding: "utf8",
1024
1827
  stdio: ["pipe", "pipe", "ignore"]
@@ -1032,22 +1835,69 @@ var KiroMemorySDK = class {
1032
1835
  * Get context for the current project
1033
1836
  */
1034
1837
  async getContext() {
1035
- const { getObservationsByProject: getObservationsByProject2 } = await Promise.resolve().then(() => (init_Observations(), Observations_exports));
1036
- const { getSummariesByProject: getSummariesByProject2 } = await Promise.resolve().then(() => (init_Summaries(), Summaries_exports));
1037
- const { getPromptsByProject: getPromptsByProject2 } = await Promise.resolve().then(() => (init_Prompts(), Prompts_exports));
1038
1838
  return {
1039
1839
  project: this.project,
1040
- relevantObservations: getObservationsByProject2(this.db.db, this.project, 20),
1041
- relevantSummaries: getSummariesByProject2(this.db.db, this.project, 5),
1042
- recentPrompts: getPromptsByProject2(this.db.db, this.project, 10)
1840
+ relevantObservations: getObservationsByProject(this.db.db, this.project, 20),
1841
+ relevantSummaries: getSummariesByProject(this.db.db, this.project, 5),
1842
+ recentPrompts: getPromptsByProject(this.db.db, this.project, 10)
1043
1843
  };
1044
1844
  }
1845
+ /**
1846
+ * Valida input per storeObservation
1847
+ */
1848
+ validateObservationInput(data) {
1849
+ if (!data.type || typeof data.type !== "string" || data.type.length > 100) {
1850
+ throw new Error("type \xE8 obbligatorio (stringa, max 100 caratteri)");
1851
+ }
1852
+ if (!data.title || typeof data.title !== "string" || data.title.length > 500) {
1853
+ throw new Error("title \xE8 obbligatorio (stringa, max 500 caratteri)");
1854
+ }
1855
+ if (!data.content || typeof data.content !== "string" || data.content.length > 1e5) {
1856
+ throw new Error("content \xE8 obbligatorio (stringa, max 100KB)");
1857
+ }
1858
+ }
1859
+ /**
1860
+ * Valida input per storeSummary
1861
+ */
1862
+ validateSummaryInput(data) {
1863
+ const MAX = 5e4;
1864
+ for (const [key, val] of Object.entries(data)) {
1865
+ if (val !== void 0 && val !== null) {
1866
+ if (typeof val !== "string") throw new Error(`${key} deve essere una stringa`);
1867
+ if (val.length > MAX) throw new Error(`${key} troppo grande (max 50KB)`);
1868
+ }
1869
+ }
1870
+ }
1871
+ /**
1872
+ * Genera e salva embedding per un'osservazione (fire-and-forget, non blocca)
1873
+ */
1874
+ async generateEmbeddingAsync(observationId, title, content, concepts) {
1875
+ try {
1876
+ const embeddingService2 = getEmbeddingService();
1877
+ if (!embeddingService2.isAvailable()) return;
1878
+ const parts = [title, content];
1879
+ if (concepts?.length) parts.push(concepts.join(", "));
1880
+ const fullText = parts.join(" ").substring(0, 2e3);
1881
+ const embedding = await embeddingService2.embed(fullText);
1882
+ if (embedding) {
1883
+ const vectorSearch2 = getVectorSearch();
1884
+ await vectorSearch2.storeEmbedding(
1885
+ this.db.db,
1886
+ observationId,
1887
+ embedding,
1888
+ embeddingService2.getProvider() || "unknown"
1889
+ );
1890
+ }
1891
+ } catch (error) {
1892
+ logger.debug("SDK", `Embedding generation fallita per obs ${observationId}: ${error}`);
1893
+ }
1894
+ }
1045
1895
  /**
1046
1896
  * Store a new observation
1047
1897
  */
1048
1898
  async storeObservation(data) {
1049
- const { createObservation: createObservation2 } = await Promise.resolve().then(() => (init_Observations(), Observations_exports));
1050
- return createObservation2(
1899
+ this.validateObservationInput(data);
1900
+ const observationId = createObservation(
1051
1901
  this.db.db,
1052
1902
  "sdk-" + Date.now(),
1053
1903
  this.project,
@@ -1068,13 +1918,77 @@ var KiroMemorySDK = class {
1068
1918
  0
1069
1919
  // prompt_number
1070
1920
  );
1921
+ this.generateEmbeddingAsync(observationId, data.title, data.content, data.concepts).catch(() => {
1922
+ });
1923
+ return observationId;
1924
+ }
1925
+ /**
1926
+ * Salva conoscenza strutturata (constraint, decision, heuristic, rejected).
1927
+ * Usa il campo `type` per il knowledgeType e `facts` per i metadati JSON.
1928
+ */
1929
+ async storeKnowledge(data) {
1930
+ if (!KNOWLEDGE_TYPES.includes(data.knowledgeType)) {
1931
+ throw new Error(`knowledgeType non valido: ${data.knowledgeType}. Valori ammessi: ${KNOWLEDGE_TYPES.join(", ")}`);
1932
+ }
1933
+ this.validateObservationInput({ type: data.knowledgeType, title: data.title, content: data.content });
1934
+ const metadata = (() => {
1935
+ switch (data.knowledgeType) {
1936
+ case "constraint":
1937
+ return {
1938
+ knowledgeType: "constraint",
1939
+ severity: data.metadata?.severity || "soft",
1940
+ reason: data.metadata?.reason
1941
+ };
1942
+ case "decision":
1943
+ return {
1944
+ knowledgeType: "decision",
1945
+ alternatives: data.metadata?.alternatives,
1946
+ reason: data.metadata?.reason
1947
+ };
1948
+ case "heuristic":
1949
+ return {
1950
+ knowledgeType: "heuristic",
1951
+ context: data.metadata?.context,
1952
+ confidence: data.metadata?.confidence
1953
+ };
1954
+ case "rejected":
1955
+ return {
1956
+ knowledgeType: "rejected",
1957
+ reason: data.metadata?.reason || "",
1958
+ alternatives: data.metadata?.alternatives
1959
+ };
1960
+ }
1961
+ })();
1962
+ const observationId = createObservation(
1963
+ this.db.db,
1964
+ "sdk-" + Date.now(),
1965
+ data.project || this.project,
1966
+ data.knowledgeType,
1967
+ // type = knowledgeType
1968
+ data.title,
1969
+ null,
1970
+ // subtitle
1971
+ data.content,
1972
+ null,
1973
+ // narrative
1974
+ JSON.stringify(metadata),
1975
+ // facts = metadati JSON
1976
+ data.concepts?.join(", ") || null,
1977
+ data.files?.join(", ") || null,
1978
+ data.files?.join(", ") || null,
1979
+ 0
1980
+ // prompt_number
1981
+ );
1982
+ this.generateEmbeddingAsync(observationId, data.title, data.content, data.concepts).catch(() => {
1983
+ });
1984
+ return observationId;
1071
1985
  }
1072
1986
  /**
1073
1987
  * Store a session summary
1074
1988
  */
1075
1989
  async storeSummary(data) {
1076
- const { createSummary: createSummary2 } = await Promise.resolve().then(() => (init_Summaries(), Summaries_exports));
1077
- return createSummary2(
1990
+ this.validateSummaryInput(data);
1991
+ return createSummary(
1078
1992
  this.db.db,
1079
1993
  "sdk-" + Date.now(),
1080
1994
  this.project,
@@ -1090,61 +2004,52 @@ var KiroMemorySDK = class {
1090
2004
  * Search across all stored context
1091
2005
  */
1092
2006
  async search(query) {
1093
- const { searchObservations: searchObservations2 } = await Promise.resolve().then(() => (init_Observations(), Observations_exports));
1094
- const { searchSummaries: searchSummaries2 } = await Promise.resolve().then(() => (init_Summaries(), Summaries_exports));
1095
2007
  return {
1096
- observations: searchObservations2(this.db.db, query, this.project),
1097
- summaries: searchSummaries2(this.db.db, query, this.project)
2008
+ observations: searchObservations(this.db.db, query, this.project),
2009
+ summaries: searchSummaries(this.db.db, query, this.project)
1098
2010
  };
1099
2011
  }
1100
2012
  /**
1101
2013
  * Get recent observations
1102
2014
  */
1103
2015
  async getRecentObservations(limit = 10) {
1104
- const { getObservationsByProject: getObservationsByProject2 } = await Promise.resolve().then(() => (init_Observations(), Observations_exports));
1105
- return getObservationsByProject2(this.db.db, this.project, limit);
2016
+ return getObservationsByProject(this.db.db, this.project, limit);
1106
2017
  }
1107
2018
  /**
1108
2019
  * Get recent summaries
1109
2020
  */
1110
2021
  async getRecentSummaries(limit = 5) {
1111
- const { getSummariesByProject: getSummariesByProject2 } = await Promise.resolve().then(() => (init_Summaries(), Summaries_exports));
1112
- return getSummariesByProject2(this.db.db, this.project, limit);
2022
+ return getSummariesByProject(this.db.db, this.project, limit);
1113
2023
  }
1114
2024
  /**
1115
2025
  * Advanced search with FTS5 and filters
1116
2026
  */
1117
2027
  async searchAdvanced(query, filters = {}) {
1118
- const { searchObservationsFTS: searchObservationsFTS2 } = await Promise.resolve().then(() => (init_Search(), Search_exports));
1119
- const { searchSummariesFiltered: searchSummariesFiltered2 } = await Promise.resolve().then(() => (init_Search(), Search_exports));
1120
2028
  const projectFilters = { ...filters, project: filters.project || this.project };
1121
2029
  return {
1122
- observations: searchObservationsFTS2(this.db.db, query, projectFilters),
1123
- summaries: searchSummariesFiltered2(this.db.db, query, projectFilters)
2030
+ observations: searchObservationsFTS(this.db.db, query, projectFilters),
2031
+ summaries: searchSummariesFiltered(this.db.db, query, projectFilters)
1124
2032
  };
1125
2033
  }
1126
2034
  /**
1127
2035
  * Retrieve observations by ID (batch)
1128
2036
  */
1129
2037
  async getObservationsByIds(ids) {
1130
- const { getObservationsByIds: getObservationsByIds2 } = await Promise.resolve().then(() => (init_Search(), Search_exports));
1131
- return getObservationsByIds2(this.db.db, ids);
2038
+ return getObservationsByIds(this.db.db, ids);
1132
2039
  }
1133
2040
  /**
1134
2041
  * Timeline: chronological context around an observation
1135
2042
  */
1136
2043
  async getTimeline(anchorId, depthBefore = 5, depthAfter = 5) {
1137
- const { getTimeline: getTimeline2 } = await Promise.resolve().then(() => (init_Search(), Search_exports));
1138
- return getTimeline2(this.db.db, anchorId, depthBefore, depthAfter);
2044
+ return getTimeline(this.db.db, anchorId, depthBefore, depthAfter);
1139
2045
  }
1140
2046
  /**
1141
2047
  * Create or retrieve a session for the current project
1142
2048
  */
1143
2049
  async getOrCreateSession(contentSessionId) {
1144
- const { getSessionByContentId: getSessionByContentId2, createSession: createSession2 } = await Promise.resolve().then(() => (init_Sessions(), Sessions_exports));
1145
- let session = getSessionByContentId2(this.db.db, contentSessionId);
2050
+ let session = getSessionByContentId(this.db.db, contentSessionId);
1146
2051
  if (!session) {
1147
- const id = createSession2(this.db.db, contentSessionId, this.project, "");
2052
+ const id = createSession(this.db.db, contentSessionId, this.project, "");
1148
2053
  session = {
1149
2054
  id,
1150
2055
  content_session_id: contentSessionId,
@@ -1164,15 +2069,13 @@ var KiroMemorySDK = class {
1164
2069
  * Store a user prompt
1165
2070
  */
1166
2071
  async storePrompt(contentSessionId, promptNumber, text) {
1167
- const { createPrompt: createPrompt2 } = await Promise.resolve().then(() => (init_Prompts(), Prompts_exports));
1168
- return createPrompt2(this.db.db, contentSessionId, this.project, promptNumber, text);
2072
+ return createPrompt(this.db.db, contentSessionId, this.project, promptNumber, text);
1169
2073
  }
1170
2074
  /**
1171
2075
  * Complete a session
1172
2076
  */
1173
2077
  async completeSession(sessionId) {
1174
- const { completeSession: completeSession2 } = await Promise.resolve().then(() => (init_Sessions(), Sessions_exports));
1175
- completeSession2(this.db.db, sessionId);
2078
+ completeSession(this.db.db, sessionId);
1176
2079
  }
1177
2080
  /**
1178
2081
  * Getter for current project name
@@ -1180,6 +2083,227 @@ var KiroMemorySDK = class {
1180
2083
  getProject() {
1181
2084
  return this.project;
1182
2085
  }
2086
+ /**
2087
+ * Ricerca ibrida: vector search + keyword FTS5
2088
+ * Richiede inizializzazione HybridSearch (embedding service)
2089
+ */
2090
+ async hybridSearch(query, options = {}) {
2091
+ const hybridSearch2 = getHybridSearch();
2092
+ return hybridSearch2.search(this.db.db, query, {
2093
+ project: this.project,
2094
+ limit: options.limit || 10
2095
+ });
2096
+ }
2097
+ /**
2098
+ * Ricerca solo semantica (vector search)
2099
+ * Ritorna risultati basati su similarità coseno con gli embeddings
2100
+ */
2101
+ async semanticSearch(query, options = {}) {
2102
+ const embeddingService2 = getEmbeddingService();
2103
+ if (!embeddingService2.isAvailable()) {
2104
+ await embeddingService2.initialize();
2105
+ }
2106
+ if (!embeddingService2.isAvailable()) return [];
2107
+ const queryEmbedding = await embeddingService2.embed(query);
2108
+ if (!queryEmbedding) return [];
2109
+ const vectorSearch2 = getVectorSearch();
2110
+ const results = await vectorSearch2.search(this.db.db, queryEmbedding, {
2111
+ project: this.project,
2112
+ limit: options.limit || 10,
2113
+ threshold: options.threshold || 0.3
2114
+ });
2115
+ return results.map((r) => ({
2116
+ id: String(r.observationId),
2117
+ title: r.title,
2118
+ content: r.text || "",
2119
+ type: r.type,
2120
+ project: r.project,
2121
+ created_at: r.created_at,
2122
+ created_at_epoch: r.created_at_epoch,
2123
+ score: r.similarity,
2124
+ source: "vector",
2125
+ signals: {
2126
+ semantic: r.similarity,
2127
+ fts5: 0,
2128
+ recency: recencyScore(r.created_at_epoch),
2129
+ projectMatch: projectMatchScore(r.project, this.project)
2130
+ }
2131
+ }));
2132
+ }
2133
+ /**
2134
+ * Genera embeddings per osservazioni che non li hanno ancora
2135
+ */
2136
+ async backfillEmbeddings(batchSize = 50) {
2137
+ const vectorSearch2 = getVectorSearch();
2138
+ return vectorSearch2.backfillEmbeddings(this.db.db, batchSize);
2139
+ }
2140
+ /**
2141
+ * Statistiche sugli embeddings nel database
2142
+ */
2143
+ getEmbeddingStats() {
2144
+ const vectorSearch2 = getVectorSearch();
2145
+ return vectorSearch2.getStats(this.db.db);
2146
+ }
2147
+ /**
2148
+ * Inizializza il servizio di embedding (lazy, chiamare prima di hybridSearch)
2149
+ */
2150
+ async initializeEmbeddings() {
2151
+ const hybridSearch2 = getHybridSearch();
2152
+ await hybridSearch2.initialize();
2153
+ return getEmbeddingService().isAvailable();
2154
+ }
2155
+ /**
2156
+ * Contesto smart con ranking a 4 segnali e budget token.
2157
+ *
2158
+ * Se query presente: usa HybridSearch con SEARCH_WEIGHTS.
2159
+ * Se senza query: ranking per recency + project match (CONTEXT_WEIGHTS).
2160
+ */
2161
+ async getSmartContext(options = {}) {
2162
+ const tokenBudget = options.tokenBudget || parseInt(process.env.KIRO_MEMORY_CONTEXT_TOKENS || "0", 10) || 2e3;
2163
+ const summaries = getSummariesByProject(this.db.db, this.project, 5);
2164
+ let items;
2165
+ if (options.query) {
2166
+ const hybridSearch2 = getHybridSearch();
2167
+ const results = await hybridSearch2.search(this.db.db, options.query, {
2168
+ project: this.project,
2169
+ limit: 30
2170
+ });
2171
+ items = results.map((r) => ({
2172
+ id: parseInt(r.id, 10) || 0,
2173
+ title: r.title,
2174
+ content: r.content,
2175
+ type: r.type,
2176
+ project: r.project,
2177
+ created_at: r.created_at,
2178
+ created_at_epoch: r.created_at_epoch,
2179
+ score: r.score,
2180
+ signals: r.signals
2181
+ }));
2182
+ } else {
2183
+ const observations = getObservationsByProject(this.db.db, this.project, 30);
2184
+ items = observations.map((obs) => {
2185
+ const signals = {
2186
+ semantic: 0,
2187
+ fts5: 0,
2188
+ recency: recencyScore(obs.created_at_epoch),
2189
+ projectMatch: projectMatchScore(obs.project, this.project)
2190
+ };
2191
+ const baseScore = computeCompositeScore(signals, CONTEXT_WEIGHTS);
2192
+ const boostedScore = Math.min(1, baseScore * knowledgeTypeBoost(obs.type));
2193
+ return {
2194
+ id: obs.id,
2195
+ title: obs.title,
2196
+ content: obs.text || obs.narrative || "",
2197
+ type: obs.type,
2198
+ project: obs.project,
2199
+ created_at: obs.created_at,
2200
+ created_at_epoch: obs.created_at_epoch,
2201
+ score: boostedScore,
2202
+ signals
2203
+ };
2204
+ });
2205
+ items.sort((a, b) => b.score - a.score);
2206
+ }
2207
+ let tokensUsed = 0;
2208
+ for (const item of items) {
2209
+ tokensUsed += Math.ceil((item.title.length + item.content.length) / 4);
2210
+ if (tokensUsed > tokenBudget) break;
2211
+ }
2212
+ return {
2213
+ project: this.project,
2214
+ items,
2215
+ summaries,
2216
+ tokenBudget,
2217
+ tokensUsed: Math.min(tokensUsed, tokenBudget)
2218
+ };
2219
+ }
2220
+ /**
2221
+ * Rileva osservazioni stale (file modificati dopo la creazione) e le marca nel DB.
2222
+ * Ritorna il numero di osservazioni marcate come stale.
2223
+ */
2224
+ async detectStaleObservations() {
2225
+ const staleObs = getStaleObservations(this.db.db, this.project);
2226
+ if (staleObs.length > 0) {
2227
+ const ids = staleObs.map((o) => o.id);
2228
+ markObservationsStale(this.db.db, ids, true);
2229
+ }
2230
+ return staleObs.length;
2231
+ }
2232
+ /**
2233
+ * Consolida osservazioni duplicate sullo stesso file e tipo.
2234
+ * Raggruppa per (project, type, files_modified), mantiene la piu recente.
2235
+ */
2236
+ async consolidateObservations(options = {}) {
2237
+ return consolidateObservations(this.db.db, this.project, options);
2238
+ }
2239
+ /**
2240
+ * Statistiche decay: totale, stale, mai accedute, accedute di recente.
2241
+ */
2242
+ async getDecayStats() {
2243
+ const total = this.db.db.query(
2244
+ "SELECT COUNT(*) as count FROM observations WHERE project = ?"
2245
+ ).get(this.project)?.count || 0;
2246
+ const stale = this.db.db.query(
2247
+ "SELECT COUNT(*) as count FROM observations WHERE project = ? AND is_stale = 1"
2248
+ ).get(this.project)?.count || 0;
2249
+ const neverAccessed = this.db.db.query(
2250
+ "SELECT COUNT(*) as count FROM observations WHERE project = ? AND last_accessed_epoch IS NULL"
2251
+ ).get(this.project)?.count || 0;
2252
+ const recentThreshold = Date.now() - 48 * 60 * 60 * 1e3;
2253
+ const recentlyAccessed = this.db.db.query(
2254
+ "SELECT COUNT(*) as count FROM observations WHERE project = ? AND last_accessed_epoch > ?"
2255
+ ).get(this.project, recentThreshold)?.count || 0;
2256
+ return { total, stale, neverAccessed, recentlyAccessed };
2257
+ }
2258
+ /**
2259
+ * Crea un checkpoint strutturato per resume sessione.
2260
+ * Salva automaticamente un context_snapshot con le ultime 10 osservazioni.
2261
+ */
2262
+ async createCheckpoint(sessionId, data) {
2263
+ const recentObs = getObservationsByProject(this.db.db, this.project, 10);
2264
+ const contextSnapshot = JSON.stringify(
2265
+ recentObs.map((o) => ({ id: o.id, type: o.type, title: o.title, text: o.text?.substring(0, 200) }))
2266
+ );
2267
+ return createCheckpoint(this.db.db, sessionId, this.project, {
2268
+ task: data.task,
2269
+ progress: data.progress,
2270
+ nextSteps: data.nextSteps,
2271
+ openQuestions: data.openQuestions,
2272
+ relevantFiles: data.relevantFiles?.join(", "),
2273
+ contextSnapshot
2274
+ });
2275
+ }
2276
+ /**
2277
+ * Recupera l'ultimo checkpoint di una sessione specifica.
2278
+ */
2279
+ async getCheckpoint(sessionId) {
2280
+ return getLatestCheckpoint(this.db.db, sessionId);
2281
+ }
2282
+ /**
2283
+ * Recupera l'ultimo checkpoint per il progetto corrente.
2284
+ * Utile per resume automatico senza specificare session ID.
2285
+ */
2286
+ async getLatestProjectCheckpoint() {
2287
+ return getLatestCheckpointByProject(this.db.db, this.project);
2288
+ }
2289
+ /**
2290
+ * Genera un report di attività per il progetto corrente.
2291
+ * Aggrega osservazioni, sessioni, summaries e file per un periodo temporale.
2292
+ */
2293
+ async generateReport(options) {
2294
+ const now = /* @__PURE__ */ new Date();
2295
+ let startEpoch;
2296
+ let endEpoch = now.getTime();
2297
+ if (options?.startDate && options?.endDate) {
2298
+ startEpoch = options.startDate.getTime();
2299
+ endEpoch = options.endDate.getTime();
2300
+ } else {
2301
+ const period = options?.period || "weekly";
2302
+ const daysBack = period === "monthly" ? 30 : 7;
2303
+ startEpoch = endEpoch - daysBack * 24 * 60 * 60 * 1e3;
2304
+ }
2305
+ return getReportData(this.db.db, this.project, startEpoch, endEpoch);
2306
+ }
1183
2307
  /**
1184
2308
  * Getter for direct database access (for API routes)
1185
2309
  */
@@ -1240,19 +2364,15 @@ runHook("agentSpawn", async (input) => {
1240
2364
  const project = detectProject(input.cwd);
1241
2365
  const sdk = createKiroMemory({ project });
1242
2366
  try {
1243
- const ctx = await sdk.getContext();
1244
- if (ctx.relevantObservations.length === 0 && ctx.relevantSummaries.length === 0) {
2367
+ const smartCtx = await sdk.getSmartContext();
2368
+ if (smartCtx.items.length === 0 && smartCtx.summaries.length === 0) {
1245
2369
  return;
1246
2370
  }
1247
- let output = "# Kiro Memory: Contesto Sessioni Precedenti\n\n";
1248
- output += formatContext({
1249
- observations: ctx.relevantObservations,
1250
- summaries: ctx.relevantSummaries,
1251
- prompts: ctx.recentPrompts
2371
+ let output = formatSmartContext({
2372
+ items: smartCtx.items,
2373
+ summaries: smartCtx.summaries,
2374
+ project
1252
2375
  });
1253
- output += `
1254
- > Progetto: ${project} | Osservazioni: ${ctx.relevantObservations.length} | Sommari: ${ctx.relevantSummaries.length}
1255
- `;
1256
2376
  output += `> UI disponibile su http://127.0.0.1:${process.env.KIRO_MEMORY_WORKER_PORT || "3001"}
1257
2377
  `;
1258
2378
  process.stdout.write(output);