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.
@@ -15,82 +15,20 @@ var __export = (target, all) => {
15
15
  __defProp(target, name, { get: all[name], enumerable: true });
16
16
  };
17
17
 
18
- // src/services/sqlite/Sessions.ts
19
- var Sessions_exports = {};
20
- __export(Sessions_exports, {
21
- completeSession: () => completeSession,
22
- createSession: () => createSession,
23
- failSession: () => failSession,
24
- getActiveSessions: () => getActiveSessions,
25
- getSessionByContentId: () => getSessionByContentId,
26
- getSessionById: () => getSessionById,
27
- getSessionsByProject: () => getSessionsByProject,
28
- updateSessionMemoryId: () => updateSessionMemoryId
29
- });
30
- function createSession(db, contentSessionId, project, userPrompt) {
31
- const now = /* @__PURE__ */ new Date();
32
- const result = db.run(
33
- `INSERT INTO sessions (content_session_id, project, user_prompt, status, started_at, started_at_epoch)
34
- VALUES (?, ?, ?, 'active', ?, ?)`,
35
- [contentSessionId, project, userPrompt, now.toISOString(), now.getTime()]
36
- );
37
- return Number(result.lastInsertRowid);
38
- }
39
- function getSessionByContentId(db, contentSessionId) {
40
- const query = db.query("SELECT * FROM sessions WHERE content_session_id = ?");
41
- return query.get(contentSessionId);
42
- }
43
- function getSessionById(db, id) {
44
- const query = db.query("SELECT * FROM sessions WHERE id = ?");
45
- return query.get(id);
46
- }
47
- function updateSessionMemoryId(db, id, memorySessionId) {
48
- db.run(
49
- "UPDATE sessions SET memory_session_id = ? WHERE id = ?",
50
- [memorySessionId, id]
51
- );
52
- }
53
- function completeSession(db, id) {
54
- const now = /* @__PURE__ */ new Date();
55
- db.run(
56
- `UPDATE sessions
57
- SET status = 'completed', completed_at = ?, completed_at_epoch = ?
58
- WHERE id = ?`,
59
- [now.toISOString(), now.getTime(), id]
60
- );
61
- }
62
- function failSession(db, id) {
63
- const now = /* @__PURE__ */ new Date();
64
- db.run(
65
- `UPDATE sessions
66
- SET status = 'failed', completed_at = ?, completed_at_epoch = ?
67
- WHERE id = ?`,
68
- [now.toISOString(), now.getTime(), id]
69
- );
70
- }
71
- function getActiveSessions(db) {
72
- const query = db.query("SELECT * FROM sessions WHERE status = 'active' ORDER BY started_at_epoch DESC");
73
- return query.all();
74
- }
75
- function getSessionsByProject(db, project, limit = 100) {
76
- const query = db.query("SELECT * FROM sessions WHERE project = ? ORDER BY started_at_epoch DESC LIMIT ?");
77
- return query.all(project, limit);
78
- }
79
- var init_Sessions = __esm({
80
- "src/services/sqlite/Sessions.ts"() {
81
- "use strict";
82
- }
83
- });
84
-
85
18
  // src/services/sqlite/Observations.ts
86
19
  var Observations_exports = {};
87
20
  __export(Observations_exports, {
21
+ consolidateObservations: () => consolidateObservations,
88
22
  createObservation: () => createObservation,
89
23
  deleteObservation: () => deleteObservation,
90
24
  getObservationsByProject: () => getObservationsByProject,
91
25
  getObservationsBySession: () => getObservationsBySession,
92
- searchObservations: () => searchObservations
26
+ searchObservations: () => searchObservations,
27
+ updateLastAccessed: () => updateLastAccessed
93
28
  });
29
+ function escapeLikePattern(input) {
30
+ return input.replace(/[%_\\]/g, "\\$&");
31
+ }
94
32
  function createObservation(db, memorySessionId, project, type, title, subtitle, text, narrative, facts, concepts, filesRead, filesModified, promptNumber) {
95
33
  const now = /* @__PURE__ */ new Date();
96
34
  const result = db.run(
@@ -114,12 +52,12 @@ function getObservationsByProject(db, project, limit = 100) {
114
52
  return query.all(project, limit);
115
53
  }
116
54
  function searchObservations(db, searchTerm, project) {
117
- const sql = project ? `SELECT * FROM observations
118
- WHERE project = ? AND (title LIKE ? OR text LIKE ? OR narrative LIKE ?)
119
- ORDER BY created_at_epoch DESC` : `SELECT * FROM observations
120
- WHERE title LIKE ? OR text LIKE ? OR narrative LIKE ?
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 '\\'
121
59
  ORDER BY created_at_epoch DESC`;
122
- const pattern = `%${searchTerm}%`;
60
+ const pattern = `%${escapeLikePattern(searchTerm)}%`;
123
61
  const query = db.query(sql);
124
62
  if (project) {
125
63
  return query.all(project, pattern, pattern, pattern);
@@ -129,105 +67,67 @@ function searchObservations(db, searchTerm, project) {
129
67
  function deleteObservation(db, id) {
130
68
  db.run("DELETE FROM observations WHERE id = ?", [id]);
131
69
  }
132
- var init_Observations = __esm({
133
- "src/services/sqlite/Observations.ts"() {
134
- "use strict";
135
- }
136
- });
137
-
138
- // src/services/sqlite/Summaries.ts
139
- var Summaries_exports = {};
140
- __export(Summaries_exports, {
141
- createSummary: () => createSummary,
142
- deleteSummary: () => deleteSummary,
143
- getSummariesByProject: () => getSummariesByProject,
144
- getSummaryBySession: () => getSummaryBySession,
145
- searchSummaries: () => searchSummaries
146
- });
147
- function createSummary(db, sessionId, project, request, investigated, learned, completed, nextSteps, notes) {
148
- const now = /* @__PURE__ */ new Date();
149
- const result = db.run(
150
- `INSERT INTO summaries
151
- (session_id, project, request, investigated, learned, completed, next_steps, notes, created_at, created_at_epoch)
152
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
153
- [sessionId, project, request, investigated, learned, completed, nextSteps, notes, now.toISOString(), now.getTime()]
154
- );
155
- return Number(result.lastInsertRowid);
156
- }
157
- function getSummaryBySession(db, sessionId) {
158
- const query = db.query("SELECT * FROM summaries WHERE session_id = ? ORDER BY created_at_epoch DESC LIMIT 1");
159
- return query.get(sessionId);
160
- }
161
- function getSummariesByProject(db, project, limit = 50) {
162
- const query = db.query(
163
- "SELECT * FROM summaries WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ?"
164
- );
165
- return query.all(project, limit);
166
- }
167
- function searchSummaries(db, searchTerm, project) {
168
- const sql = project ? `SELECT * FROM summaries
169
- WHERE project = ? AND (request LIKE ? OR learned LIKE ? OR completed LIKE ? OR notes LIKE ?)
170
- ORDER BY created_at_epoch DESC` : `SELECT * FROM summaries
171
- WHERE request LIKE ? OR learned LIKE ? OR completed LIKE ? OR notes LIKE ?
172
- ORDER BY created_at_epoch DESC`;
173
- const pattern = `%${searchTerm}%`;
174
- const query = db.query(sql);
175
- if (project) {
176
- return query.all(project, pattern, pattern, pattern, pattern);
177
- }
178
- return query.all(pattern, pattern, pattern, pattern);
179
- }
180
- function deleteSummary(db, id) {
181
- db.run("DELETE FROM summaries WHERE id = ?", [id]);
182
- }
183
- var init_Summaries = __esm({
184
- "src/services/sqlite/Summaries.ts"() {
185
- "use strict";
186
- }
187
- });
188
-
189
- // src/services/sqlite/Prompts.ts
190
- var Prompts_exports = {};
191
- __export(Prompts_exports, {
192
- createPrompt: () => createPrompt,
193
- deletePrompt: () => deletePrompt,
194
- getLatestPrompt: () => getLatestPrompt,
195
- getPromptsByProject: () => getPromptsByProject,
196
- getPromptsBySession: () => getPromptsBySession
197
- });
198
- function createPrompt(db, contentSessionId, project, promptNumber, promptText) {
199
- const now = /* @__PURE__ */ new Date();
200
- const result = db.run(
201
- `INSERT INTO prompts
202
- (content_session_id, project, prompt_number, prompt_text, created_at, created_at_epoch)
203
- VALUES (?, ?, ?, ?, ?, ?)`,
204
- [contentSessionId, project, promptNumber, promptText, now.toISOString(), now.getTime()]
205
- );
206
- return Number(result.lastInsertRowid);
207
- }
208
- function getPromptsBySession(db, contentSessionId) {
209
- const query = db.query(
210
- "SELECT * FROM prompts WHERE content_session_id = ? ORDER BY prompt_number ASC"
211
- );
212
- return query.all(contentSessionId);
213
- }
214
- function getPromptsByProject(db, project, limit = 100) {
215
- const query = db.query(
216
- "SELECT * FROM prompts WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ?"
217
- );
218
- return query.all(project, limit);
219
- }
220
- function getLatestPrompt(db, contentSessionId) {
221
- const query = db.query(
222
- "SELECT * FROM prompts WHERE content_session_id = ? ORDER BY prompt_number DESC LIMIT 1"
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]
223
79
  );
224
- return query.get(contentSessionId);
225
80
  }
226
- function deletePrompt(db, id) {
227
- db.run("DELETE FROM prompts WHERE id = ?", [id]);
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 };
228
128
  }
229
- var init_Prompts = __esm({
230
- "src/services/sqlite/Prompts.ts"() {
129
+ var init_Observations = __esm({
130
+ "src/services/sqlite/Observations.ts"() {
231
131
  "use strict";
232
132
  }
233
133
  });
@@ -237,20 +137,34 @@ var Search_exports = {};
237
137
  __export(Search_exports, {
238
138
  getObservationsByIds: () => getObservationsByIds,
239
139
  getProjectStats: () => getProjectStats,
140
+ getStaleObservations: () => getStaleObservations,
240
141
  getTimeline: () => getTimeline,
142
+ markObservationsStale: () => markObservationsStale,
241
143
  searchObservationsFTS: () => searchObservationsFTS,
144
+ searchObservationsFTSWithRank: () => searchObservationsFTSWithRank,
242
145
  searchObservationsLIKE: () => searchObservationsLIKE,
243
146
  searchSummariesFiltered: () => searchSummariesFiltered
244
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
+ }
245
157
  function searchObservationsFTS(db, query, filters = {}) {
246
158
  const limit = filters.limit || 50;
247
159
  try {
160
+ const safeQuery = sanitizeFTS5Query(query);
161
+ if (!safeQuery) return searchObservationsLIKE(db, query, filters);
248
162
  let sql = `
249
163
  SELECT o.* FROM observations o
250
164
  JOIN observations_fts fts ON o.id = fts.rowid
251
165
  WHERE observations_fts MATCH ?
252
166
  `;
253
- const params = [query];
167
+ const params = [safeQuery];
254
168
  if (filters.project) {
255
169
  sql += " AND o.project = ?";
256
170
  params.push(filters.project);
@@ -275,12 +189,47 @@ function searchObservationsFTS(db, query, filters = {}) {
275
189
  return searchObservationsLIKE(db, query, filters);
276
190
  }
277
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
+ }
278
227
  function searchObservationsLIKE(db, query, filters = {}) {
279
228
  const limit = filters.limit || 50;
280
- const pattern = `%${query}%`;
229
+ const pattern = `%${escapeLikePattern3(query)}%`;
281
230
  let sql = `
282
231
  SELECT * FROM observations
283
- WHERE (title LIKE ? OR text LIKE ? OR narrative LIKE ? OR concepts LIKE ?)
232
+ WHERE (title LIKE ? ESCAPE '\\' OR text LIKE ? ESCAPE '\\' OR narrative LIKE ? ESCAPE '\\' OR concepts LIKE ? ESCAPE '\\')
284
233
  `;
285
234
  const params = [pattern, pattern, pattern, pattern];
286
235
  if (filters.project) {
@@ -306,10 +255,10 @@ function searchObservationsLIKE(db, query, filters = {}) {
306
255
  }
307
256
  function searchSummariesFiltered(db, query, filters = {}) {
308
257
  const limit = filters.limit || 20;
309
- const pattern = `%${query}%`;
258
+ const pattern = `%${escapeLikePattern3(query)}%`;
310
259
  let sql = `
311
260
  SELECT * FROM summaries
312
- WHERE (request LIKE ? OR learned LIKE ? OR completed LIKE ? OR notes LIKE ? OR next_steps LIKE ?)
261
+ WHERE (request LIKE ? ESCAPE '\\' OR learned LIKE ? ESCAPE '\\' OR completed LIKE ? ESCAPE '\\' OR notes LIKE ? ESCAPE '\\' OR next_steps LIKE ? ESCAPE '\\')
313
262
  `;
314
263
  const params = [pattern, pattern, pattern, pattern, pattern];
315
264
  if (filters.project) {
@@ -330,11 +279,13 @@ function searchSummariesFiltered(db, query, filters = {}) {
330
279
  return stmt.all(...params);
331
280
  }
332
281
  function getObservationsByIds(db, ids) {
333
- if (ids.length === 0) return [];
334
- const placeholders = ids.map(() => "?").join(",");
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(",");
335
286
  const sql = `SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC`;
336
287
  const stmt = db.query(sql);
337
- return stmt.all(...ids);
288
+ return stmt.all(...validIds);
338
289
  }
339
290
  function getTimeline(db, anchorId, depthBefore = 5, depthAfter = 5) {
340
291
  const anchorStmt = db.query("SELECT created_at_epoch FROM observations WHERE id = ?");
@@ -376,6 +327,45 @@ function getProjectStats(db, project) {
376
327
  prompts: prmStmt.get(project)?.count || 0
377
328
  };
378
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
+ }
379
369
  var init_Search = __esm({
380
370
  "src/services/sqlite/Search.ts"() {
381
371
  "use strict";
@@ -452,7 +442,7 @@ var BunQueryCompat = class {
452
442
  // src/shared/paths.ts
453
443
  import { join as join2, dirname, basename } from "path";
454
444
  import { homedir as homedir2 } from "os";
455
- import { mkdirSync as mkdirSync2 } from "fs";
445
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
456
446
  import { fileURLToPath } from "url";
457
447
 
458
448
  // src/utils/logger.ts
@@ -682,7 +672,9 @@ function getDirname() {
682
672
  return dirname(fileURLToPath(import.meta.url));
683
673
  }
684
674
  var _dirname = getDirname();
685
- var DATA_DIR = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR || join2(homedir2(), ".contextkit");
675
+ var _legacyDir = join2(homedir2(), ".contextkit");
676
+ var _defaultDir = existsSync2(_legacyDir) ? _legacyDir : join2(homedir2(), ".kiro-memory");
677
+ var DATA_DIR = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR || _defaultDir;
686
678
  var KIRO_CONFIG_DIR = process.env.KIRO_CONFIG_DIR || join2(homedir2(), ".kiro");
687
679
  var PLUGIN_ROOT = join2(KIRO_CONFIG_DIR, "plugins", "kiro-memory");
688
680
  var ARCHIVES_DIR = join2(DATA_DIR, "archives");
@@ -691,7 +683,8 @@ var TRASH_DIR = join2(DATA_DIR, "trash");
691
683
  var BACKUPS_DIR = join2(DATA_DIR, "backups");
692
684
  var MODES_DIR = join2(DATA_DIR, "modes");
693
685
  var USER_SETTINGS_PATH = join2(DATA_DIR, "settings.json");
694
- var DB_PATH = join2(DATA_DIR, "contextkit.db");
686
+ var _legacyDb = join2(DATA_DIR, "contextkit.db");
687
+ var DB_PATH = existsSync2(_legacyDb) ? _legacyDb : join2(DATA_DIR, "kiro-memory.db");
695
688
  var VECTOR_DB_DIR = join2(DATA_DIR, "vector-db");
696
689
  var OBSERVER_SESSIONS_DIR = join2(DATA_DIR, "observer-sessions");
697
690
  var KIRO_SETTINGS_PATH = join2(KIRO_CONFIG_DIR, "settings.json");
@@ -706,7 +699,11 @@ var SQLITE_CACHE_SIZE_PAGES = 1e4;
706
699
  var dbInstance = null;
707
700
  var KiroMemoryDatabase = class {
708
701
  db;
709
- constructor(dbPath = DB_PATH) {
702
+ /**
703
+ * @param dbPath - Percorso al file SQLite (default: DB_PATH)
704
+ * @param skipMigrations - Se true, salta il migration runner (per hook ad alta frequenza)
705
+ */
706
+ constructor(dbPath = DB_PATH, skipMigrations = false) {
710
707
  if (dbPath !== ":memory:") {
711
708
  ensureDir(DATA_DIR);
712
709
  }
@@ -717,8 +714,18 @@ var KiroMemoryDatabase = class {
717
714
  this.db.run("PRAGMA temp_store = memory");
718
715
  this.db.run(`PRAGMA mmap_size = ${SQLITE_MMAP_SIZE_BYTES}`);
719
716
  this.db.run(`PRAGMA cache_size = ${SQLITE_CACHE_SIZE_PAGES}`);
720
- const migrationRunner = new MigrationRunner(this.db);
721
- migrationRunner.runAllMigrations();
717
+ if (!skipMigrations) {
718
+ const migrationRunner = new MigrationRunner(this.db);
719
+ migrationRunner.runAllMigrations();
720
+ }
721
+ }
722
+ /**
723
+ * Esegue una funzione all'interno di una transazione atomica.
724
+ * Se fn() lancia un errore, la transazione viene annullata automaticamente.
725
+ */
726
+ withTransaction(fn) {
727
+ const transaction = this.db.transaction(fn);
728
+ return transaction(this.db);
722
729
  }
723
730
  /**
724
731
  * Close the database connection
@@ -1001,6 +1008,55 @@ var MigrationRunner = class {
1001
1008
  `);
1002
1009
  db.run("CREATE UNIQUE INDEX IF NOT EXISTS idx_project_aliases_name ON project_aliases(project_name)");
1003
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
+ }
1004
1060
  }
1005
1061
  ];
1006
1062
  }
@@ -1016,54 +1072,809 @@ async function initializeDatabase() {
1016
1072
  return await manager.initialize();
1017
1073
  }
1018
1074
 
1075
+ // src/services/sqlite/Sessions.ts
1076
+ function createSession(db, contentSessionId, project, userPrompt) {
1077
+ const now = /* @__PURE__ */ new Date();
1078
+ const result = db.run(
1079
+ `INSERT INTO sessions (content_session_id, project, user_prompt, status, started_at, started_at_epoch)
1080
+ VALUES (?, ?, ?, 'active', ?, ?)`,
1081
+ [contentSessionId, project, userPrompt, now.toISOString(), now.getTime()]
1082
+ );
1083
+ return Number(result.lastInsertRowid);
1084
+ }
1085
+ function getSessionByContentId(db, contentSessionId) {
1086
+ const query = db.query("SELECT * FROM sessions WHERE content_session_id = ?");
1087
+ return query.get(contentSessionId);
1088
+ }
1089
+ function completeSession(db, id) {
1090
+ const now = /* @__PURE__ */ new Date();
1091
+ db.run(
1092
+ `UPDATE sessions
1093
+ SET status = 'completed', completed_at = ?, completed_at_epoch = ?
1094
+ WHERE id = ?`,
1095
+ [now.toISOString(), now.getTime(), id]
1096
+ );
1097
+ }
1098
+
1019
1099
  // src/services/sqlite/index.ts
1020
- init_Sessions();
1021
1100
  init_Observations();
1022
- init_Summaries();
1023
- init_Prompts();
1024
- init_Search();
1025
1101
 
1026
- // src/sdk/index.ts
1027
- var KiroMemorySDK = class {
1028
- db;
1029
- project;
1030
- constructor(config = {}) {
1031
- this.db = new KiroMemoryDatabase(config.dataDir);
1032
- this.project = config.project || this.detectProject();
1033
- }
1034
- detectProject() {
1035
- try {
1036
- const { execSync } = __require("child_process");
1037
- const gitRoot = execSync("git rev-parse --show-toplevel", {
1038
- cwd: process.cwd(),
1039
- encoding: "utf8",
1040
- stdio: ["pipe", "pipe", "ignore"]
1041
- }).trim();
1042
- return gitRoot.split("/").pop() || "default";
1043
- } catch {
1044
- return "default";
1045
- }
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) {
1107
+ const now = /* @__PURE__ */ new Date();
1108
+ const result = db.run(
1109
+ `INSERT INTO summaries
1110
+ (session_id, project, request, investigated, learned, completed, next_steps, notes, created_at, created_at_epoch)
1111
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1112
+ [sessionId, project, request, investigated, learned, completed, nextSteps, notes, now.toISOString(), now.getTime()]
1113
+ );
1114
+ return Number(result.lastInsertRowid);
1115
+ }
1116
+ function getSummariesByProject(db, project, limit = 50) {
1117
+ const query = db.query(
1118
+ "SELECT * FROM summaries WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ?"
1119
+ );
1120
+ return query.all(project, limit);
1121
+ }
1122
+ function searchSummaries(db, searchTerm, project) {
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 '\\'
1127
+ ORDER BY created_at_epoch DESC`;
1128
+ const pattern = `%${escapeLikePattern2(searchTerm)}%`;
1129
+ const query = db.query(sql);
1130
+ if (project) {
1131
+ return query.all(project, pattern, pattern, pattern, pattern);
1046
1132
  }
1047
- /**
1048
- * Get context for the current project
1049
- */
1133
+ return query.all(pattern, pattern, pattern, pattern);
1134
+ }
1135
+
1136
+ // src/services/sqlite/Prompts.ts
1137
+ function createPrompt(db, contentSessionId, project, promptNumber, promptText) {
1138
+ const now = /* @__PURE__ */ new Date();
1139
+ const result = db.run(
1140
+ `INSERT INTO prompts
1141
+ (content_session_id, project, prompt_number, prompt_text, created_at, created_at_epoch)
1142
+ VALUES (?, ?, ?, ?, ?, ?)`,
1143
+ [contentSessionId, project, promptNumber, promptText, now.toISOString(), now.getTime()]
1144
+ );
1145
+ return Number(result.lastInsertRowid);
1146
+ }
1147
+ function getPromptsByProject(db, project, limit = 100) {
1148
+ const query = db.query(
1149
+ "SELECT * FROM prompts WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ?"
1150
+ );
1151
+ return query.all(project, limit);
1152
+ }
1153
+
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);
1174
+ }
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);
1256
+ }
1257
+ if (row.completed) {
1258
+ const parts = row.completed.split("; ").filter(Boolean);
1259
+ completedTasks.push(...parts);
1260
+ }
1261
+ if (row.next_steps) {
1262
+ const parts = row.next_steps.split("; ").filter(Boolean);
1263
+ nextStepsArr.push(...parts);
1264
+ }
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);
1277
+ }
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
+ };
1307
+ }
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;
1333
+ }
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;
1370
+ }
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;
1389
+ }
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;
1406
+ }
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;
1453
+ }
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
+ }
1521
+ }
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
+ }
1543
+ }
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;
1578
+ }
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;
1601
+ }
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);
1623
+ }
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);
1631
+ }
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;
1647
+ }
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
+
1790
+ // src/sdk/index.ts
1791
+ var KiroMemorySDK = class {
1792
+ db;
1793
+ project;
1794
+ constructor(config = {}) {
1795
+ this.db = new KiroMemoryDatabase(config.dataDir, config.skipMigrations || false);
1796
+ this.project = config.project || this.detectProject();
1797
+ }
1798
+ detectProject() {
1799
+ try {
1800
+ const { execSync } = __require("child_process");
1801
+ const gitRoot = execSync("git rev-parse --show-toplevel", {
1802
+ cwd: process.cwd(),
1803
+ encoding: "utf8",
1804
+ stdio: ["pipe", "pipe", "ignore"]
1805
+ }).trim();
1806
+ return gitRoot.split("/").pop() || "default";
1807
+ } catch {
1808
+ return "default";
1809
+ }
1810
+ }
1811
+ /**
1812
+ * Get context for the current project
1813
+ */
1050
1814
  async getContext() {
1051
- const { getObservationsByProject: getObservationsByProject2 } = await Promise.resolve().then(() => (init_Observations(), Observations_exports));
1052
- const { getSummariesByProject: getSummariesByProject2 } = await Promise.resolve().then(() => (init_Summaries(), Summaries_exports));
1053
- const { getPromptsByProject: getPromptsByProject2 } = await Promise.resolve().then(() => (init_Prompts(), Prompts_exports));
1054
1815
  return {
1055
1816
  project: this.project,
1056
- relevantObservations: getObservationsByProject2(this.db.db, this.project, 20),
1057
- relevantSummaries: getSummariesByProject2(this.db.db, this.project, 5),
1058
- recentPrompts: getPromptsByProject2(this.db.db, this.project, 10)
1817
+ relevantObservations: getObservationsByProject(this.db.db, this.project, 20),
1818
+ relevantSummaries: getSummariesByProject(this.db.db, this.project, 5),
1819
+ recentPrompts: getPromptsByProject(this.db.db, this.project, 10)
1059
1820
  };
1060
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
+ }
1061
1872
  /**
1062
1873
  * Store a new observation
1063
1874
  */
1064
1875
  async storeObservation(data) {
1065
- const { createObservation: createObservation2 } = await Promise.resolve().then(() => (init_Observations(), Observations_exports));
1066
- return createObservation2(
1876
+ this.validateObservationInput(data);
1877
+ const observationId = createObservation(
1067
1878
  this.db.db,
1068
1879
  "sdk-" + Date.now(),
1069
1880
  this.project,
@@ -1084,13 +1895,77 @@ var KiroMemorySDK = class {
1084
1895
  0
1085
1896
  // prompt_number
1086
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;
1087
1962
  }
1088
1963
  /**
1089
1964
  * Store a session summary
1090
1965
  */
1091
1966
  async storeSummary(data) {
1092
- const { createSummary: createSummary2 } = await Promise.resolve().then(() => (init_Summaries(), Summaries_exports));
1093
- return createSummary2(
1967
+ this.validateSummaryInput(data);
1968
+ return createSummary(
1094
1969
  this.db.db,
1095
1970
  "sdk-" + Date.now(),
1096
1971
  this.project,
@@ -1106,61 +1981,52 @@ var KiroMemorySDK = class {
1106
1981
  * Search across all stored context
1107
1982
  */
1108
1983
  async search(query) {
1109
- const { searchObservations: searchObservations2 } = await Promise.resolve().then(() => (init_Observations(), Observations_exports));
1110
- const { searchSummaries: searchSummaries2 } = await Promise.resolve().then(() => (init_Summaries(), Summaries_exports));
1111
1984
  return {
1112
- observations: searchObservations2(this.db.db, query, this.project),
1113
- summaries: searchSummaries2(this.db.db, query, this.project)
1985
+ observations: searchObservations(this.db.db, query, this.project),
1986
+ summaries: searchSummaries(this.db.db, query, this.project)
1114
1987
  };
1115
1988
  }
1116
1989
  /**
1117
1990
  * Get recent observations
1118
1991
  */
1119
1992
  async getRecentObservations(limit = 10) {
1120
- const { getObservationsByProject: getObservationsByProject2 } = await Promise.resolve().then(() => (init_Observations(), Observations_exports));
1121
- return getObservationsByProject2(this.db.db, this.project, limit);
1993
+ return getObservationsByProject(this.db.db, this.project, limit);
1122
1994
  }
1123
1995
  /**
1124
1996
  * Get recent summaries
1125
1997
  */
1126
1998
  async getRecentSummaries(limit = 5) {
1127
- const { getSummariesByProject: getSummariesByProject2 } = await Promise.resolve().then(() => (init_Summaries(), Summaries_exports));
1128
- return getSummariesByProject2(this.db.db, this.project, limit);
1999
+ return getSummariesByProject(this.db.db, this.project, limit);
1129
2000
  }
1130
2001
  /**
1131
2002
  * Advanced search with FTS5 and filters
1132
2003
  */
1133
2004
  async searchAdvanced(query, filters = {}) {
1134
- const { searchObservationsFTS: searchObservationsFTS2 } = await Promise.resolve().then(() => (init_Search(), Search_exports));
1135
- const { searchSummariesFiltered: searchSummariesFiltered2 } = await Promise.resolve().then(() => (init_Search(), Search_exports));
1136
2005
  const projectFilters = { ...filters, project: filters.project || this.project };
1137
2006
  return {
1138
- observations: searchObservationsFTS2(this.db.db, query, projectFilters),
1139
- summaries: searchSummariesFiltered2(this.db.db, query, projectFilters)
2007
+ observations: searchObservationsFTS(this.db.db, query, projectFilters),
2008
+ summaries: searchSummariesFiltered(this.db.db, query, projectFilters)
1140
2009
  };
1141
2010
  }
1142
2011
  /**
1143
2012
  * Retrieve observations by ID (batch)
1144
2013
  */
1145
2014
  async getObservationsByIds(ids) {
1146
- const { getObservationsByIds: getObservationsByIds2 } = await Promise.resolve().then(() => (init_Search(), Search_exports));
1147
- return getObservationsByIds2(this.db.db, ids);
2015
+ return getObservationsByIds(this.db.db, ids);
1148
2016
  }
1149
2017
  /**
1150
2018
  * Timeline: chronological context around an observation
1151
2019
  */
1152
2020
  async getTimeline(anchorId, depthBefore = 5, depthAfter = 5) {
1153
- const { getTimeline: getTimeline2 } = await Promise.resolve().then(() => (init_Search(), Search_exports));
1154
- return getTimeline2(this.db.db, anchorId, depthBefore, depthAfter);
2021
+ return getTimeline(this.db.db, anchorId, depthBefore, depthAfter);
1155
2022
  }
1156
2023
  /**
1157
2024
  * Create or retrieve a session for the current project
1158
2025
  */
1159
2026
  async getOrCreateSession(contentSessionId) {
1160
- const { getSessionByContentId: getSessionByContentId2, createSession: createSession2 } = await Promise.resolve().then(() => (init_Sessions(), Sessions_exports));
1161
- let session = getSessionByContentId2(this.db.db, contentSessionId);
2027
+ let session = getSessionByContentId(this.db.db, contentSessionId);
1162
2028
  if (!session) {
1163
- const id = createSession2(this.db.db, contentSessionId, this.project, "");
2029
+ const id = createSession(this.db.db, contentSessionId, this.project, "");
1164
2030
  session = {
1165
2031
  id,
1166
2032
  content_session_id: contentSessionId,
@@ -1180,15 +2046,13 @@ var KiroMemorySDK = class {
1180
2046
  * Store a user prompt
1181
2047
  */
1182
2048
  async storePrompt(contentSessionId, promptNumber, text) {
1183
- const { createPrompt: createPrompt2 } = await Promise.resolve().then(() => (init_Prompts(), Prompts_exports));
1184
- return createPrompt2(this.db.db, contentSessionId, this.project, promptNumber, text);
2049
+ return createPrompt(this.db.db, contentSessionId, this.project, promptNumber, text);
1185
2050
  }
1186
2051
  /**
1187
2052
  * Complete a session
1188
2053
  */
1189
2054
  async completeSession(sessionId) {
1190
- const { completeSession: completeSession2 } = await Promise.resolve().then(() => (init_Sessions(), Sessions_exports));
1191
- completeSession2(this.db.db, sessionId);
2055
+ completeSession(this.db.db, sessionId);
1192
2056
  }
1193
2057
  /**
1194
2058
  * Getter for current project name
@@ -1196,6 +2060,227 @@ var KiroMemorySDK = class {
1196
2060
  getProject() {
1197
2061
  return this.project;
1198
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
+ }
1199
2284
  /**
1200
2285
  * Getter for direct database access (for API routes)
1201
2286
  */
@@ -1219,14 +2304,16 @@ var createContextKit = createKiroMemory;
1219
2304
  init_Search();
1220
2305
 
1221
2306
  // src/hooks/utils.ts
1222
- import { writeFileSync, mkdirSync as mkdirSync3, existsSync as existsSync3 } from "fs";
2307
+ import { writeFileSync, mkdirSync as mkdirSync3, existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
1223
2308
  import { join as join3 } from "path";
2309
+ var DATA_DIR2 = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR || join3(process.env.HOME || "/tmp", ".kiro-memory");
2310
+ var TOKEN_FILE = join3(DATA_DIR2, "worker.token");
1224
2311
  function debugLog(hookName, label, data) {
1225
2312
  if ((process.env.KIRO_MEMORY_LOG_LEVEL || "").toUpperCase() !== "DEBUG") return;
1226
2313
  try {
1227
2314
  const dataDir = process.env.KIRO_MEMORY_DATA_DIR || join3(process.env.HOME || "/tmp", ".kiro-memory");
1228
2315
  const logDir = join3(dataDir, "logs");
1229
- if (!existsSync3(logDir)) mkdirSync3(logDir, { recursive: true });
2316
+ if (!existsSync4(logDir)) mkdirSync3(logDir, { recursive: true });
1230
2317
  const ts = (/* @__PURE__ */ new Date()).toISOString();
1231
2318
  const line = `[${ts}] [${hookName}] ${label}: ${JSON.stringify(data)}
1232
2319
  `;
@@ -1239,10 +2326,19 @@ async function readStdin() {
1239
2326
  return new Promise((resolve, reject) => {
1240
2327
  let data = "";
1241
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);
1242
2337
  process.stdin.on("data", (chunk) => {
1243
2338
  data += chunk;
1244
2339
  });
1245
2340
  process.stdin.on("end", () => {
2341
+ clearTimeout(safetyTimeout);
1246
2342
  try {
1247
2343
  if (!data.trim()) {
1248
2344
  resolve({
@@ -1256,15 +2352,10 @@ async function readStdin() {
1256
2352
  reject(new Error(`Errore parsing stdin JSON: ${err}`));
1257
2353
  }
1258
2354
  });
1259
- process.stdin.on("error", reject);
1260
- setTimeout(() => {
1261
- if (!data.trim()) {
1262
- resolve({
1263
- hook_event_name: "agentSpawn",
1264
- cwd: process.cwd()
1265
- });
1266
- }
1267
- }, 5e3);
2355
+ process.stdin.on("error", (err) => {
2356
+ clearTimeout(safetyTimeout);
2357
+ reject(err);
2358
+ });
1268
2359
  });
1269
2360
  }
1270
2361
  function detectProject(cwd) {
@@ -1308,6 +2399,12 @@ function formatContext(data) {
1308
2399
  async function runHook(name, handler) {
1309
2400
  try {
1310
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
+ }
1311
2408
  debugLog(name, "stdin", input);
1312
2409
  await handler(input);
1313
2410
  debugLog(name, "completato", { success: true });
@@ -1321,7 +2418,7 @@ async function runHook(name, handler) {
1321
2418
  }
1322
2419
 
1323
2420
  // src/index.ts
1324
- var VERSION = "1.0.0";
2421
+ var VERSION = "1.5.0";
1325
2422
  export {
1326
2423
  KiroMemoryDatabase as ContextKitDatabase,
1327
2424
  ContextKitSDK,