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,245 @@ 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"() {
19
+ // src/utils/logger.ts
20
+ import { appendFileSync, existsSync, mkdirSync, readFileSync } from "fs";
21
+ import { join } from "path";
22
+ import { homedir } from "os";
23
+ var LogLevel, DEFAULT_DATA_DIR, Logger, logger;
24
+ var init_logger = __esm({
25
+ "src/utils/logger.ts"() {
82
26
  "use strict";
27
+ LogLevel = /* @__PURE__ */ ((LogLevel2) => {
28
+ LogLevel2[LogLevel2["DEBUG"] = 0] = "DEBUG";
29
+ LogLevel2[LogLevel2["INFO"] = 1] = "INFO";
30
+ LogLevel2[LogLevel2["WARN"] = 2] = "WARN";
31
+ LogLevel2[LogLevel2["ERROR"] = 3] = "ERROR";
32
+ LogLevel2[LogLevel2["SILENT"] = 4] = "SILENT";
33
+ return LogLevel2;
34
+ })(LogLevel || {});
35
+ DEFAULT_DATA_DIR = join(homedir(), ".contextkit");
36
+ Logger = class {
37
+ level = null;
38
+ useColor;
39
+ logFilePath = null;
40
+ logFileInitialized = false;
41
+ constructor() {
42
+ this.useColor = process.stdout.isTTY ?? false;
43
+ }
44
+ /**
45
+ * Initialize log file path and ensure directory exists (lazy initialization)
46
+ */
47
+ ensureLogFileInitialized() {
48
+ if (this.logFileInitialized) return;
49
+ this.logFileInitialized = true;
50
+ try {
51
+ const logsDir = join(DEFAULT_DATA_DIR, "logs");
52
+ if (!existsSync(logsDir)) {
53
+ mkdirSync(logsDir, { recursive: true });
54
+ }
55
+ const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
56
+ this.logFilePath = join(logsDir, `kiro-memory-${date}.log`);
57
+ } catch (error) {
58
+ console.error("[LOGGER] Failed to initialize log file:", error);
59
+ this.logFilePath = null;
60
+ }
61
+ }
62
+ /**
63
+ * Lazy-load log level from settings file
64
+ */
65
+ getLevel() {
66
+ if (this.level === null) {
67
+ try {
68
+ const settingsPath = join(DEFAULT_DATA_DIR, "settings.json");
69
+ if (existsSync(settingsPath)) {
70
+ const settingsData = readFileSync(settingsPath, "utf-8");
71
+ const settings = JSON.parse(settingsData);
72
+ const envLevel = (settings.KIRO_MEMORY_LOG_LEVEL || settings.CONTEXTKIT_LOG_LEVEL || "INFO").toUpperCase();
73
+ this.level = LogLevel[envLevel] ?? 1 /* INFO */;
74
+ } else {
75
+ this.level = 1 /* INFO */;
76
+ }
77
+ } catch (error) {
78
+ this.level = 1 /* INFO */;
79
+ }
80
+ }
81
+ return this.level;
82
+ }
83
+ /**
84
+ * Create correlation ID for tracking an observation through the pipeline
85
+ */
86
+ correlationId(sessionId, observationNum) {
87
+ return `obs-${sessionId}-${observationNum}`;
88
+ }
89
+ /**
90
+ * Create session correlation ID
91
+ */
92
+ sessionId(sessionId) {
93
+ return `session-${sessionId}`;
94
+ }
95
+ /**
96
+ * Format data for logging - create compact summaries instead of full dumps
97
+ */
98
+ formatData(data) {
99
+ if (data === null || data === void 0) return "";
100
+ if (typeof data === "string") return data;
101
+ if (typeof data === "number") return data.toString();
102
+ if (typeof data === "boolean") return data.toString();
103
+ if (typeof data === "object") {
104
+ if (data instanceof Error) {
105
+ return this.getLevel() === 0 /* DEBUG */ ? `${data.message}
106
+ ${data.stack}` : data.message;
107
+ }
108
+ if (Array.isArray(data)) {
109
+ return `[${data.length} items]`;
110
+ }
111
+ const keys = Object.keys(data);
112
+ if (keys.length === 0) return "{}";
113
+ if (keys.length <= 3) {
114
+ return JSON.stringify(data);
115
+ }
116
+ return `{${keys.length} keys: ${keys.slice(0, 3).join(", ")}...}`;
117
+ }
118
+ return String(data);
119
+ }
120
+ /**
121
+ * Format timestamp in local timezone (YYYY-MM-DD HH:MM:SS.mmm)
122
+ */
123
+ formatTimestamp(date) {
124
+ const year = date.getFullYear();
125
+ const month = String(date.getMonth() + 1).padStart(2, "0");
126
+ const day = String(date.getDate()).padStart(2, "0");
127
+ const hours = String(date.getHours()).padStart(2, "0");
128
+ const minutes = String(date.getMinutes()).padStart(2, "0");
129
+ const seconds = String(date.getSeconds()).padStart(2, "0");
130
+ const ms = String(date.getMilliseconds()).padStart(3, "0");
131
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${ms}`;
132
+ }
133
+ /**
134
+ * Core logging method
135
+ */
136
+ log(level, component, message, context, data) {
137
+ if (level < this.getLevel()) return;
138
+ this.ensureLogFileInitialized();
139
+ const timestamp = this.formatTimestamp(/* @__PURE__ */ new Date());
140
+ const levelStr = LogLevel[level].padEnd(5);
141
+ const componentStr = component.padEnd(6);
142
+ let correlationStr = "";
143
+ if (context?.correlationId) {
144
+ correlationStr = `[${context.correlationId}] `;
145
+ } else if (context?.sessionId) {
146
+ correlationStr = `[session-${context.sessionId}] `;
147
+ }
148
+ let dataStr = "";
149
+ if (data !== void 0 && data !== null) {
150
+ if (data instanceof Error) {
151
+ dataStr = this.getLevel() === 0 /* DEBUG */ ? `
152
+ ${data.message}
153
+ ${data.stack}` : ` ${data.message}`;
154
+ } else if (this.getLevel() === 0 /* DEBUG */ && typeof data === "object") {
155
+ dataStr = "\n" + JSON.stringify(data, null, 2);
156
+ } else {
157
+ dataStr = " " + this.formatData(data);
158
+ }
159
+ }
160
+ let contextStr = "";
161
+ if (context) {
162
+ const { sessionId, memorySessionId, correlationId, ...rest } = context;
163
+ if (Object.keys(rest).length > 0) {
164
+ const pairs = Object.entries(rest).map(([k, v]) => `${k}=${v}`);
165
+ contextStr = ` {${pairs.join(", ")}}`;
166
+ }
167
+ }
168
+ const logLine = `[${timestamp}] [${levelStr}] [${componentStr}] ${correlationStr}${message}${contextStr}${dataStr}`;
169
+ if (this.logFilePath) {
170
+ try {
171
+ appendFileSync(this.logFilePath, logLine + "\n", "utf8");
172
+ } catch (error) {
173
+ process.stderr.write(`[LOGGER] Failed to write to log file: ${error}
174
+ `);
175
+ }
176
+ } else {
177
+ process.stderr.write(logLine + "\n");
178
+ }
179
+ }
180
+ // Public logging methods
181
+ debug(component, message, context, data) {
182
+ this.log(0 /* DEBUG */, component, message, context, data);
183
+ }
184
+ info(component, message, context, data) {
185
+ this.log(1 /* INFO */, component, message, context, data);
186
+ }
187
+ warn(component, message, context, data) {
188
+ this.log(2 /* WARN */, component, message, context, data);
189
+ }
190
+ error(component, message, context, data) {
191
+ this.log(3 /* ERROR */, component, message, context, data);
192
+ }
193
+ /**
194
+ * Log data flow: input → processing
195
+ */
196
+ dataIn(component, message, context, data) {
197
+ this.info(component, `\u2192 ${message}`, context, data);
198
+ }
199
+ /**
200
+ * Log data flow: processing → output
201
+ */
202
+ dataOut(component, message, context, data) {
203
+ this.info(component, `\u2190 ${message}`, context, data);
204
+ }
205
+ /**
206
+ * Log successful completion
207
+ */
208
+ success(component, message, context, data) {
209
+ this.info(component, `\u2713 ${message}`, context, data);
210
+ }
211
+ /**
212
+ * Log failure
213
+ */
214
+ failure(component, message, context, data) {
215
+ this.error(component, `\u2717 ${message}`, context, data);
216
+ }
217
+ /**
218
+ * Log timing information
219
+ */
220
+ timing(component, message, durationMs, context) {
221
+ this.info(component, `\u23F1 ${message}`, context, { duration: `${durationMs}ms` });
222
+ }
223
+ /**
224
+ * Happy Path Error - logs when the expected "happy path" fails but we have a fallback
225
+ */
226
+ happyPathError(component, message, context, data, fallback = "") {
227
+ const stack = new Error().stack || "";
228
+ const stackLines = stack.split("\n");
229
+ const callerLine = stackLines[2] || "";
230
+ const callerMatch = callerLine.match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/);
231
+ const location = callerMatch ? `${callerMatch[1].split("/").pop()}:${callerMatch[2]}` : "unknown";
232
+ const enhancedContext = {
233
+ ...context,
234
+ location
235
+ };
236
+ this.warn(component, `[HAPPY-PATH] ${message}`, enhancedContext, data);
237
+ return fallback;
238
+ }
239
+ };
240
+ logger = new Logger();
83
241
  }
84
242
  });
85
243
 
86
244
  // src/services/sqlite/Observations.ts
87
245
  var Observations_exports = {};
88
246
  __export(Observations_exports, {
247
+ consolidateObservations: () => consolidateObservations,
89
248
  createObservation: () => createObservation,
90
249
  deleteObservation: () => deleteObservation,
91
250
  getObservationsByProject: () => getObservationsByProject,
92
251
  getObservationsBySession: () => getObservationsBySession,
93
- searchObservations: () => searchObservations
252
+ searchObservations: () => searchObservations,
253
+ updateLastAccessed: () => updateLastAccessed
94
254
  });
255
+ function escapeLikePattern(input) {
256
+ return input.replace(/[%_\\]/g, "\\$&");
257
+ }
95
258
  function createObservation(db, memorySessionId, project, type, title, subtitle, text, narrative, facts, concepts, filesRead, filesModified, promptNumber) {
96
259
  const now = /* @__PURE__ */ new Date();
97
260
  const result = db.run(
@@ -115,12 +278,12 @@ function getObservationsByProject(db, project, limit = 100) {
115
278
  return query.all(project, limit);
116
279
  }
117
280
  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 ?
281
+ const sql = project ? `SELECT * FROM observations
282
+ WHERE project = ? AND (title LIKE ? ESCAPE '\\' OR text LIKE ? ESCAPE '\\' OR narrative LIKE ? ESCAPE '\\')
283
+ ORDER BY created_at_epoch DESC` : `SELECT * FROM observations
284
+ WHERE title LIKE ? ESCAPE '\\' OR text LIKE ? ESCAPE '\\' OR narrative LIKE ? ESCAPE '\\'
122
285
  ORDER BY created_at_epoch DESC`;
123
- const pattern = `%${searchTerm}%`;
286
+ const pattern = `%${escapeLikePattern(searchTerm)}%`;
124
287
  const query = db.query(sql);
125
288
  if (project) {
126
289
  return query.all(project, pattern, pattern, pattern);
@@ -130,105 +293,67 @@ function searchObservations(db, searchTerm, project) {
130
293
  function deleteObservation(db, id) {
131
294
  db.run("DELETE FROM observations WHERE id = ?", [id]);
132
295
  }
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"
296
+ function updateLastAccessed(db, ids) {
297
+ if (!Array.isArray(ids) || ids.length === 0) return;
298
+ const validIds = ids.filter((id) => typeof id === "number" && Number.isInteger(id) && id > 0).slice(0, 500);
299
+ if (validIds.length === 0) return;
300
+ const now = Date.now();
301
+ const placeholders = validIds.map(() => "?").join(",");
302
+ db.run(
303
+ `UPDATE observations SET last_accessed_epoch = ? WHERE id IN (${placeholders})`,
304
+ [now, ...validIds]
224
305
  );
225
- return query.get(contentSessionId);
226
306
  }
227
- function deletePrompt(db, id) {
228
- db.run("DELETE FROM prompts WHERE id = ?", [id]);
307
+ function consolidateObservations(db, project, options = {}) {
308
+ const minGroupSize = options.minGroupSize || 3;
309
+ const groups = db.query(`
310
+ SELECT type, files_modified, COUNT(*) as cnt, GROUP_CONCAT(id) as ids
311
+ FROM observations
312
+ WHERE project = ? AND files_modified IS NOT NULL AND files_modified != ''
313
+ GROUP BY type, files_modified
314
+ HAVING cnt >= ?
315
+ ORDER BY cnt DESC
316
+ `).all(project, minGroupSize);
317
+ if (groups.length === 0) return { merged: 0, removed: 0 };
318
+ let totalMerged = 0;
319
+ let totalRemoved = 0;
320
+ for (const group of groups) {
321
+ const obsIds = group.ids.split(",").map(Number);
322
+ const placeholders = obsIds.map(() => "?").join(",");
323
+ const observations = db.query(
324
+ `SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC`
325
+ ).all(...obsIds);
326
+ if (observations.length < minGroupSize) continue;
327
+ if (options.dryRun) {
328
+ totalMerged += 1;
329
+ totalRemoved += observations.length - 1;
330
+ continue;
331
+ }
332
+ const keeper = observations[0];
333
+ const others = observations.slice(1);
334
+ const uniqueTexts = /* @__PURE__ */ new Set();
335
+ if (keeper.text) uniqueTexts.add(keeper.text);
336
+ for (const obs of others) {
337
+ if (obs.text && !uniqueTexts.has(obs.text)) {
338
+ uniqueTexts.add(obs.text);
339
+ }
340
+ }
341
+ const consolidatedText = Array.from(uniqueTexts).join("\n---\n").substring(0, 1e5);
342
+ db.run(
343
+ "UPDATE observations SET text = ?, title = ? WHERE id = ?",
344
+ [consolidatedText, `[consolidato x${observations.length}] ${keeper.title}`, keeper.id]
345
+ );
346
+ const removeIds = others.map((o) => o.id);
347
+ const removePlaceholders = removeIds.map(() => "?").join(",");
348
+ db.run(`DELETE FROM observations WHERE id IN (${removePlaceholders})`, removeIds);
349
+ db.run(`DELETE FROM observation_embeddings WHERE observation_id IN (${removePlaceholders})`, removeIds);
350
+ totalMerged += 1;
351
+ totalRemoved += removeIds.length;
352
+ }
353
+ return { merged: totalMerged, removed: totalRemoved };
229
354
  }
230
- var init_Prompts = __esm({
231
- "src/services/sqlite/Prompts.ts"() {
355
+ var init_Observations = __esm({
356
+ "src/services/sqlite/Observations.ts"() {
232
357
  "use strict";
233
358
  }
234
359
  });
@@ -238,20 +363,34 @@ var Search_exports = {};
238
363
  __export(Search_exports, {
239
364
  getObservationsByIds: () => getObservationsByIds,
240
365
  getProjectStats: () => getProjectStats,
366
+ getStaleObservations: () => getStaleObservations,
241
367
  getTimeline: () => getTimeline,
368
+ markObservationsStale: () => markObservationsStale,
242
369
  searchObservationsFTS: () => searchObservationsFTS,
370
+ searchObservationsFTSWithRank: () => searchObservationsFTSWithRank,
243
371
  searchObservationsLIKE: () => searchObservationsLIKE,
244
372
  searchSummariesFiltered: () => searchSummariesFiltered
245
373
  });
374
+ import { existsSync as existsSync3, statSync } from "fs";
375
+ function escapeLikePattern3(input) {
376
+ return input.replace(/[%_\\]/g, "\\$&");
377
+ }
378
+ function sanitizeFTS5Query(query) {
379
+ const trimmed = query.length > 1e4 ? query.substring(0, 1e4) : query;
380
+ const terms = trimmed.replace(/[""]/g, "").split(/\s+/).filter((t) => t.length > 0).slice(0, 100).map((t) => `"${t}"`);
381
+ return terms.join(" ");
382
+ }
246
383
  function searchObservationsFTS(db, query, filters = {}) {
247
384
  const limit = filters.limit || 50;
248
385
  try {
386
+ const safeQuery = sanitizeFTS5Query(query);
387
+ if (!safeQuery) return searchObservationsLIKE(db, query, filters);
249
388
  let sql = `
250
389
  SELECT o.* FROM observations o
251
390
  JOIN observations_fts fts ON o.id = fts.rowid
252
391
  WHERE observations_fts MATCH ?
253
392
  `;
254
- const params = [query];
393
+ const params = [safeQuery];
255
394
  if (filters.project) {
256
395
  sql += " AND o.project = ?";
257
396
  params.push(filters.project);
@@ -276,12 +415,47 @@ function searchObservationsFTS(db, query, filters = {}) {
276
415
  return searchObservationsLIKE(db, query, filters);
277
416
  }
278
417
  }
418
+ function searchObservationsFTSWithRank(db, query, filters = {}) {
419
+ const limit = filters.limit || 50;
420
+ try {
421
+ const safeQuery = sanitizeFTS5Query(query);
422
+ if (!safeQuery) return [];
423
+ let sql = `
424
+ SELECT o.*, rank as fts5_rank FROM observations o
425
+ JOIN observations_fts fts ON o.id = fts.rowid
426
+ WHERE observations_fts MATCH ?
427
+ `;
428
+ const params = [safeQuery];
429
+ if (filters.project) {
430
+ sql += " AND o.project = ?";
431
+ params.push(filters.project);
432
+ }
433
+ if (filters.type) {
434
+ sql += " AND o.type = ?";
435
+ params.push(filters.type);
436
+ }
437
+ if (filters.dateStart) {
438
+ sql += " AND o.created_at_epoch >= ?";
439
+ params.push(filters.dateStart);
440
+ }
441
+ if (filters.dateEnd) {
442
+ sql += " AND o.created_at_epoch <= ?";
443
+ params.push(filters.dateEnd);
444
+ }
445
+ sql += " ORDER BY rank LIMIT ?";
446
+ params.push(limit);
447
+ const stmt = db.query(sql);
448
+ return stmt.all(...params);
449
+ } catch {
450
+ return [];
451
+ }
452
+ }
279
453
  function searchObservationsLIKE(db, query, filters = {}) {
280
454
  const limit = filters.limit || 50;
281
- const pattern = `%${query}%`;
455
+ const pattern = `%${escapeLikePattern3(query)}%`;
282
456
  let sql = `
283
457
  SELECT * FROM observations
284
- WHERE (title LIKE ? OR text LIKE ? OR narrative LIKE ? OR concepts LIKE ?)
458
+ WHERE (title LIKE ? ESCAPE '\\' OR text LIKE ? ESCAPE '\\' OR narrative LIKE ? ESCAPE '\\' OR concepts LIKE ? ESCAPE '\\')
285
459
  `;
286
460
  const params = [pattern, pattern, pattern, pattern];
287
461
  if (filters.project) {
@@ -307,10 +481,10 @@ function searchObservationsLIKE(db, query, filters = {}) {
307
481
  }
308
482
  function searchSummariesFiltered(db, query, filters = {}) {
309
483
  const limit = filters.limit || 20;
310
- const pattern = `%${query}%`;
484
+ const pattern = `%${escapeLikePattern3(query)}%`;
311
485
  let sql = `
312
486
  SELECT * FROM summaries
313
- WHERE (request LIKE ? OR learned LIKE ? OR completed LIKE ? OR notes LIKE ? OR next_steps LIKE ?)
487
+ WHERE (request LIKE ? ESCAPE '\\' OR learned LIKE ? ESCAPE '\\' OR completed LIKE ? ESCAPE '\\' OR notes LIKE ? ESCAPE '\\' OR next_steps LIKE ? ESCAPE '\\')
314
488
  `;
315
489
  const params = [pattern, pattern, pattern, pattern, pattern];
316
490
  if (filters.project) {
@@ -331,11 +505,13 @@ function searchSummariesFiltered(db, query, filters = {}) {
331
505
  return stmt.all(...params);
332
506
  }
333
507
  function getObservationsByIds(db, ids) {
334
- if (ids.length === 0) return [];
335
- const placeholders = ids.map(() => "?").join(",");
508
+ if (!Array.isArray(ids) || ids.length === 0) return [];
509
+ const validIds = ids.filter((id) => typeof id === "number" && Number.isInteger(id) && id > 0).slice(0, 500);
510
+ if (validIds.length === 0) return [];
511
+ const placeholders = validIds.map(() => "?").join(",");
336
512
  const sql = `SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC`;
337
513
  const stmt = db.query(sql);
338
- return stmt.all(...ids);
514
+ return stmt.all(...validIds);
339
515
  }
340
516
  function getTimeline(db, anchorId, depthBefore = 5, depthAfter = 5) {
341
517
  const anchorStmt = db.query("SELECT created_at_epoch FROM observations WHERE id = ?");
@@ -377,12 +553,202 @@ function getProjectStats(db, project) {
377
553
  prompts: prmStmt.get(project)?.count || 0
378
554
  };
379
555
  }
556
+ function getStaleObservations(db, project) {
557
+ const rows = db.query(`
558
+ SELECT * FROM observations
559
+ WHERE project = ? AND files_modified IS NOT NULL AND files_modified != ''
560
+ ORDER BY created_at_epoch DESC
561
+ LIMIT 500
562
+ `).all(project);
563
+ const staleObs = [];
564
+ for (const obs of rows) {
565
+ if (!obs.files_modified) continue;
566
+ const files = obs.files_modified.split(",").map((f) => f.trim()).filter(Boolean);
567
+ let isStale = false;
568
+ for (const filepath of files) {
569
+ try {
570
+ if (!existsSync3(filepath)) continue;
571
+ const stat = statSync(filepath);
572
+ if (stat.mtimeMs > obs.created_at_epoch) {
573
+ isStale = true;
574
+ break;
575
+ }
576
+ } catch {
577
+ }
578
+ }
579
+ if (isStale) {
580
+ staleObs.push(obs);
581
+ }
582
+ }
583
+ return staleObs;
584
+ }
585
+ function markObservationsStale(db, ids, stale) {
586
+ if (!Array.isArray(ids) || ids.length === 0) return;
587
+ const validIds = ids.filter((id) => typeof id === "number" && Number.isInteger(id) && id > 0).slice(0, 500);
588
+ if (validIds.length === 0) return;
589
+ const placeholders = validIds.map(() => "?").join(",");
590
+ db.run(
591
+ `UPDATE observations SET is_stale = ? WHERE id IN (${placeholders})`,
592
+ [stale ? 1 : 0, ...validIds]
593
+ );
594
+ }
380
595
  var init_Search = __esm({
381
596
  "src/services/sqlite/Search.ts"() {
382
597
  "use strict";
383
598
  }
384
599
  });
385
600
 
601
+ // src/services/search/EmbeddingService.ts
602
+ var EmbeddingService_exports = {};
603
+ __export(EmbeddingService_exports, {
604
+ EmbeddingService: () => EmbeddingService,
605
+ getEmbeddingService: () => getEmbeddingService
606
+ });
607
+ function getEmbeddingService() {
608
+ if (!embeddingService) {
609
+ embeddingService = new EmbeddingService();
610
+ }
611
+ return embeddingService;
612
+ }
613
+ var EmbeddingService, embeddingService;
614
+ var init_EmbeddingService = __esm({
615
+ "src/services/search/EmbeddingService.ts"() {
616
+ "use strict";
617
+ init_logger();
618
+ EmbeddingService = class {
619
+ provider = null;
620
+ model = null;
621
+ initialized = false;
622
+ initializing = null;
623
+ /**
624
+ * Inizializza il servizio di embedding.
625
+ * Tenta fastembed, poi @huggingface/transformers, poi fallback a null.
626
+ */
627
+ async initialize() {
628
+ if (this.initialized) return this.provider !== null;
629
+ if (this.initializing) return this.initializing;
630
+ this.initializing = this._doInitialize();
631
+ const result = await this.initializing;
632
+ this.initializing = null;
633
+ return result;
634
+ }
635
+ async _doInitialize() {
636
+ try {
637
+ const fastembed = await import("fastembed");
638
+ const EmbeddingModel = fastembed.EmbeddingModel || fastembed.default?.EmbeddingModel;
639
+ const FlagEmbedding = fastembed.FlagEmbedding || fastembed.default?.FlagEmbedding;
640
+ if (FlagEmbedding && EmbeddingModel) {
641
+ this.model = await FlagEmbedding.init({
642
+ model: EmbeddingModel.BGESmallENV15
643
+ });
644
+ this.provider = "fastembed";
645
+ this.initialized = true;
646
+ logger.info("EMBEDDING", "Inizializzato con fastembed (BGE-small-en-v1.5)");
647
+ return true;
648
+ }
649
+ } catch (error) {
650
+ logger.debug("EMBEDDING", `fastembed non disponibile: ${error}`);
651
+ }
652
+ try {
653
+ const transformers = await import("@huggingface/transformers");
654
+ const pipeline = transformers.pipeline || transformers.default?.pipeline;
655
+ if (pipeline) {
656
+ this.model = await pipeline("feature-extraction", "Xenova/all-MiniLM-L6-v2", {
657
+ quantized: true
658
+ });
659
+ this.provider = "transformers";
660
+ this.initialized = true;
661
+ logger.info("EMBEDDING", "Inizializzato con @huggingface/transformers (all-MiniLM-L6-v2)");
662
+ return true;
663
+ }
664
+ } catch (error) {
665
+ logger.debug("EMBEDDING", `@huggingface/transformers non disponibile: ${error}`);
666
+ }
667
+ this.provider = null;
668
+ this.initialized = true;
669
+ logger.warn("EMBEDDING", "Nessun provider embedding disponibile, ricerca semantica disabilitata");
670
+ return false;
671
+ }
672
+ /**
673
+ * Genera embedding per un singolo testo.
674
+ * Ritorna Float32Array con 384 dimensioni, o null se non disponibile.
675
+ */
676
+ async embed(text) {
677
+ if (!this.initialized) await this.initialize();
678
+ if (!this.provider || !this.model) return null;
679
+ try {
680
+ const truncated = text.substring(0, 2e3);
681
+ if (this.provider === "fastembed") {
682
+ return await this._embedFastembed(truncated);
683
+ } else if (this.provider === "transformers") {
684
+ return await this._embedTransformers(truncated);
685
+ }
686
+ } catch (error) {
687
+ logger.error("EMBEDDING", `Errore generazione embedding: ${error}`);
688
+ }
689
+ return null;
690
+ }
691
+ /**
692
+ * Genera embeddings in batch.
693
+ */
694
+ async embedBatch(texts) {
695
+ if (!this.initialized) await this.initialize();
696
+ if (!this.provider || !this.model) return texts.map(() => null);
697
+ const results = [];
698
+ for (const text of texts) {
699
+ try {
700
+ const embedding = await this.embed(text);
701
+ results.push(embedding);
702
+ } catch {
703
+ results.push(null);
704
+ }
705
+ }
706
+ return results;
707
+ }
708
+ /**
709
+ * Verifica se il servizio è disponibile.
710
+ */
711
+ isAvailable() {
712
+ return this.initialized && this.provider !== null;
713
+ }
714
+ /**
715
+ * Nome del provider attivo.
716
+ */
717
+ getProvider() {
718
+ return this.provider;
719
+ }
720
+ /**
721
+ * Dimensioni del vettore embedding.
722
+ */
723
+ getDimensions() {
724
+ return 384;
725
+ }
726
+ // --- Provider specifici ---
727
+ async _embedFastembed(text) {
728
+ const embeddings = this.model.embed([text], 1);
729
+ for await (const batch of embeddings) {
730
+ if (batch && batch.length > 0) {
731
+ const vec = batch[0];
732
+ return vec instanceof Float32Array ? vec : new Float32Array(vec);
733
+ }
734
+ }
735
+ return null;
736
+ }
737
+ async _embedTransformers(text) {
738
+ const output = await this.model(text, {
739
+ pooling: "mean",
740
+ normalize: true
741
+ });
742
+ if (output?.data) {
743
+ return output.data instanceof Float32Array ? output.data : new Float32Array(output.data);
744
+ }
745
+ return null;
746
+ }
747
+ };
748
+ embeddingService = null;
749
+ }
750
+ });
751
+
386
752
  // src/shims/bun-sqlite.ts
387
753
  import BetterSqlite3 from "better-sqlite3";
388
754
  var Database = class {
@@ -453,272 +819,70 @@ var BunQueryCompat = class {
453
819
  // src/shared/paths.ts
454
820
  import { join as join2, dirname, basename } from "path";
455
821
  import { homedir as homedir2 } from "os";
456
- import { mkdirSync as mkdirSync2 } from "fs";
822
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
823
+ init_logger();
457
824
  import { fileURLToPath } from "url";
458
-
459
- // src/utils/logger.ts
460
- import { appendFileSync, existsSync, mkdirSync, readFileSync } from "fs";
461
- import { join } from "path";
462
- import { homedir } from "os";
463
- var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
464
- LogLevel2[LogLevel2["DEBUG"] = 0] = "DEBUG";
465
- LogLevel2[LogLevel2["INFO"] = 1] = "INFO";
466
- LogLevel2[LogLevel2["WARN"] = 2] = "WARN";
467
- LogLevel2[LogLevel2["ERROR"] = 3] = "ERROR";
468
- LogLevel2[LogLevel2["SILENT"] = 4] = "SILENT";
469
- return LogLevel2;
470
- })(LogLevel || {});
471
- var DEFAULT_DATA_DIR = join(homedir(), ".contextkit");
472
- var Logger = class {
473
- level = null;
474
- useColor;
475
- logFilePath = null;
476
- logFileInitialized = false;
477
- constructor() {
478
- this.useColor = process.stdout.isTTY ?? false;
825
+ function getDirname() {
826
+ if (typeof __dirname !== "undefined") {
827
+ return __dirname;
479
828
  }
829
+ return dirname(fileURLToPath(import.meta.url));
830
+ }
831
+ var _dirname = getDirname();
832
+ var _legacyDir = join2(homedir2(), ".contextkit");
833
+ var _defaultDir = existsSync2(_legacyDir) ? _legacyDir : join2(homedir2(), ".kiro-memory");
834
+ var DATA_DIR = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR || _defaultDir;
835
+ var KIRO_CONFIG_DIR = process.env.KIRO_CONFIG_DIR || join2(homedir2(), ".kiro");
836
+ var PLUGIN_ROOT = join2(KIRO_CONFIG_DIR, "plugins", "kiro-memory");
837
+ var ARCHIVES_DIR = join2(DATA_DIR, "archives");
838
+ var LOGS_DIR = join2(DATA_DIR, "logs");
839
+ var TRASH_DIR = join2(DATA_DIR, "trash");
840
+ var BACKUPS_DIR = join2(DATA_DIR, "backups");
841
+ var MODES_DIR = join2(DATA_DIR, "modes");
842
+ var USER_SETTINGS_PATH = join2(DATA_DIR, "settings.json");
843
+ var _legacyDb = join2(DATA_DIR, "contextkit.db");
844
+ var DB_PATH = existsSync2(_legacyDb) ? _legacyDb : join2(DATA_DIR, "kiro-memory.db");
845
+ var VECTOR_DB_DIR = join2(DATA_DIR, "vector-db");
846
+ var OBSERVER_SESSIONS_DIR = join2(DATA_DIR, "observer-sessions");
847
+ var KIRO_SETTINGS_PATH = join2(KIRO_CONFIG_DIR, "settings.json");
848
+ var KIRO_CONTEXT_PATH = join2(KIRO_CONFIG_DIR, "context.md");
849
+ function ensureDir(dirPath) {
850
+ mkdirSync2(dirPath, { recursive: true });
851
+ }
852
+
853
+ // src/services/sqlite/Database.ts
854
+ init_logger();
855
+ var SQLITE_MMAP_SIZE_BYTES = 256 * 1024 * 1024;
856
+ var SQLITE_CACHE_SIZE_PAGES = 1e4;
857
+ var KiroMemoryDatabase = class {
858
+ db;
480
859
  /**
481
- * Initialize log file path and ensure directory exists (lazy initialization)
860
+ * @param dbPath - Percorso al file SQLite (default: DB_PATH)
861
+ * @param skipMigrations - Se true, salta il migration runner (per hook ad alta frequenza)
482
862
  */
483
- ensureLogFileInitialized() {
484
- if (this.logFileInitialized) return;
485
- this.logFileInitialized = true;
486
- try {
487
- const logsDir = join(DEFAULT_DATA_DIR, "logs");
488
- if (!existsSync(logsDir)) {
489
- mkdirSync(logsDir, { recursive: true });
490
- }
491
- const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
492
- this.logFilePath = join(logsDir, `kiro-memory-${date}.log`);
493
- } catch (error) {
494
- console.error("[LOGGER] Failed to initialize log file:", error);
495
- this.logFilePath = null;
863
+ constructor(dbPath = DB_PATH, skipMigrations = false) {
864
+ if (dbPath !== ":memory:") {
865
+ ensureDir(DATA_DIR);
866
+ }
867
+ this.db = new Database(dbPath, { create: true, readwrite: true });
868
+ this.db.run("PRAGMA journal_mode = WAL");
869
+ this.db.run("PRAGMA synchronous = NORMAL");
870
+ this.db.run("PRAGMA foreign_keys = ON");
871
+ this.db.run("PRAGMA temp_store = memory");
872
+ this.db.run(`PRAGMA mmap_size = ${SQLITE_MMAP_SIZE_BYTES}`);
873
+ this.db.run(`PRAGMA cache_size = ${SQLITE_CACHE_SIZE_PAGES}`);
874
+ if (!skipMigrations) {
875
+ const migrationRunner = new MigrationRunner(this.db);
876
+ migrationRunner.runAllMigrations();
496
877
  }
497
878
  }
498
879
  /**
499
- * Lazy-load log level from settings file
880
+ * Esegue una funzione all'interno di una transazione atomica.
881
+ * Se fn() lancia un errore, la transazione viene annullata automaticamente.
500
882
  */
501
- getLevel() {
502
- if (this.level === null) {
503
- try {
504
- const settingsPath = join(DEFAULT_DATA_DIR, "settings.json");
505
- if (existsSync(settingsPath)) {
506
- const settingsData = readFileSync(settingsPath, "utf-8");
507
- const settings = JSON.parse(settingsData);
508
- const envLevel = (settings.KIRO_MEMORY_LOG_LEVEL || settings.CONTEXTKIT_LOG_LEVEL || "INFO").toUpperCase();
509
- this.level = LogLevel[envLevel] ?? 1 /* INFO */;
510
- } else {
511
- this.level = 1 /* INFO */;
512
- }
513
- } catch (error) {
514
- this.level = 1 /* INFO */;
515
- }
516
- }
517
- return this.level;
518
- }
519
- /**
520
- * Create correlation ID for tracking an observation through the pipeline
521
- */
522
- correlationId(sessionId, observationNum) {
523
- return `obs-${sessionId}-${observationNum}`;
524
- }
525
- /**
526
- * Create session correlation ID
527
- */
528
- sessionId(sessionId) {
529
- return `session-${sessionId}`;
530
- }
531
- /**
532
- * Format data for logging - create compact summaries instead of full dumps
533
- */
534
- formatData(data) {
535
- if (data === null || data === void 0) return "";
536
- if (typeof data === "string") return data;
537
- if (typeof data === "number") return data.toString();
538
- if (typeof data === "boolean") return data.toString();
539
- if (typeof data === "object") {
540
- if (data instanceof Error) {
541
- return this.getLevel() === 0 /* DEBUG */ ? `${data.message}
542
- ${data.stack}` : data.message;
543
- }
544
- if (Array.isArray(data)) {
545
- return `[${data.length} items]`;
546
- }
547
- const keys = Object.keys(data);
548
- if (keys.length === 0) return "{}";
549
- if (keys.length <= 3) {
550
- return JSON.stringify(data);
551
- }
552
- return `{${keys.length} keys: ${keys.slice(0, 3).join(", ")}...}`;
553
- }
554
- return String(data);
555
- }
556
- /**
557
- * Format timestamp in local timezone (YYYY-MM-DD HH:MM:SS.mmm)
558
- */
559
- formatTimestamp(date) {
560
- const year = date.getFullYear();
561
- const month = String(date.getMonth() + 1).padStart(2, "0");
562
- const day = String(date.getDate()).padStart(2, "0");
563
- const hours = String(date.getHours()).padStart(2, "0");
564
- const minutes = String(date.getMinutes()).padStart(2, "0");
565
- const seconds = String(date.getSeconds()).padStart(2, "0");
566
- const ms = String(date.getMilliseconds()).padStart(3, "0");
567
- return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${ms}`;
568
- }
569
- /**
570
- * Core logging method
571
- */
572
- log(level, component, message, context, data) {
573
- if (level < this.getLevel()) return;
574
- this.ensureLogFileInitialized();
575
- const timestamp = this.formatTimestamp(/* @__PURE__ */ new Date());
576
- const levelStr = LogLevel[level].padEnd(5);
577
- const componentStr = component.padEnd(6);
578
- let correlationStr = "";
579
- if (context?.correlationId) {
580
- correlationStr = `[${context.correlationId}] `;
581
- } else if (context?.sessionId) {
582
- correlationStr = `[session-${context.sessionId}] `;
583
- }
584
- let dataStr = "";
585
- if (data !== void 0 && data !== null) {
586
- if (data instanceof Error) {
587
- dataStr = this.getLevel() === 0 /* DEBUG */ ? `
588
- ${data.message}
589
- ${data.stack}` : ` ${data.message}`;
590
- } else if (this.getLevel() === 0 /* DEBUG */ && typeof data === "object") {
591
- dataStr = "\n" + JSON.stringify(data, null, 2);
592
- } else {
593
- dataStr = " " + this.formatData(data);
594
- }
595
- }
596
- let contextStr = "";
597
- if (context) {
598
- const { sessionId, memorySessionId, correlationId, ...rest } = context;
599
- if (Object.keys(rest).length > 0) {
600
- const pairs = Object.entries(rest).map(([k, v]) => `${k}=${v}`);
601
- contextStr = ` {${pairs.join(", ")}}`;
602
- }
603
- }
604
- const logLine = `[${timestamp}] [${levelStr}] [${componentStr}] ${correlationStr}${message}${contextStr}${dataStr}`;
605
- if (this.logFilePath) {
606
- try {
607
- appendFileSync(this.logFilePath, logLine + "\n", "utf8");
608
- } catch (error) {
609
- process.stderr.write(`[LOGGER] Failed to write to log file: ${error}
610
- `);
611
- }
612
- } else {
613
- process.stderr.write(logLine + "\n");
614
- }
615
- }
616
- // Public logging methods
617
- debug(component, message, context, data) {
618
- this.log(0 /* DEBUG */, component, message, context, data);
619
- }
620
- info(component, message, context, data) {
621
- this.log(1 /* INFO */, component, message, context, data);
622
- }
623
- warn(component, message, context, data) {
624
- this.log(2 /* WARN */, component, message, context, data);
625
- }
626
- error(component, message, context, data) {
627
- this.log(3 /* ERROR */, component, message, context, data);
628
- }
629
- /**
630
- * Log data flow: input → processing
631
- */
632
- dataIn(component, message, context, data) {
633
- this.info(component, `\u2192 ${message}`, context, data);
634
- }
635
- /**
636
- * Log data flow: processing → output
637
- */
638
- dataOut(component, message, context, data) {
639
- this.info(component, `\u2190 ${message}`, context, data);
640
- }
641
- /**
642
- * Log successful completion
643
- */
644
- success(component, message, context, data) {
645
- this.info(component, `\u2713 ${message}`, context, data);
646
- }
647
- /**
648
- * Log failure
649
- */
650
- failure(component, message, context, data) {
651
- this.error(component, `\u2717 ${message}`, context, data);
652
- }
653
- /**
654
- * Log timing information
655
- */
656
- timing(component, message, durationMs, context) {
657
- this.info(component, `\u23F1 ${message}`, context, { duration: `${durationMs}ms` });
658
- }
659
- /**
660
- * Happy Path Error - logs when the expected "happy path" fails but we have a fallback
661
- */
662
- happyPathError(component, message, context, data, fallback = "") {
663
- const stack = new Error().stack || "";
664
- const stackLines = stack.split("\n");
665
- const callerLine = stackLines[2] || "";
666
- const callerMatch = callerLine.match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/);
667
- const location = callerMatch ? `${callerMatch[1].split("/").pop()}:${callerMatch[2]}` : "unknown";
668
- const enhancedContext = {
669
- ...context,
670
- location
671
- };
672
- this.warn(component, `[HAPPY-PATH] ${message}`, enhancedContext, data);
673
- return fallback;
674
- }
675
- };
676
- var logger = new Logger();
677
-
678
- // src/shared/paths.ts
679
- function getDirname() {
680
- if (typeof __dirname !== "undefined") {
681
- return __dirname;
682
- }
683
- return dirname(fileURLToPath(import.meta.url));
684
- }
685
- var _dirname = getDirname();
686
- var DATA_DIR = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR || join2(homedir2(), ".contextkit");
687
- var KIRO_CONFIG_DIR = process.env.KIRO_CONFIG_DIR || join2(homedir2(), ".kiro");
688
- var PLUGIN_ROOT = join2(KIRO_CONFIG_DIR, "plugins", "kiro-memory");
689
- var ARCHIVES_DIR = join2(DATA_DIR, "archives");
690
- var LOGS_DIR = join2(DATA_DIR, "logs");
691
- var TRASH_DIR = join2(DATA_DIR, "trash");
692
- var BACKUPS_DIR = join2(DATA_DIR, "backups");
693
- var MODES_DIR = join2(DATA_DIR, "modes");
694
- var USER_SETTINGS_PATH = join2(DATA_DIR, "settings.json");
695
- var DB_PATH = join2(DATA_DIR, "contextkit.db");
696
- var VECTOR_DB_DIR = join2(DATA_DIR, "vector-db");
697
- var OBSERVER_SESSIONS_DIR = join2(DATA_DIR, "observer-sessions");
698
- var KIRO_SETTINGS_PATH = join2(KIRO_CONFIG_DIR, "settings.json");
699
- var KIRO_CONTEXT_PATH = join2(KIRO_CONFIG_DIR, "context.md");
700
- function ensureDir(dirPath) {
701
- mkdirSync2(dirPath, { recursive: true });
702
- }
703
-
704
- // src/services/sqlite/Database.ts
705
- var SQLITE_MMAP_SIZE_BYTES = 256 * 1024 * 1024;
706
- var SQLITE_CACHE_SIZE_PAGES = 1e4;
707
- var KiroMemoryDatabase = class {
708
- db;
709
- constructor(dbPath = DB_PATH) {
710
- if (dbPath !== ":memory:") {
711
- ensureDir(DATA_DIR);
712
- }
713
- this.db = new Database(dbPath, { create: true, readwrite: true });
714
- this.db.run("PRAGMA journal_mode = WAL");
715
- this.db.run("PRAGMA synchronous = NORMAL");
716
- this.db.run("PRAGMA foreign_keys = ON");
717
- this.db.run("PRAGMA temp_store = memory");
718
- this.db.run(`PRAGMA mmap_size = ${SQLITE_MMAP_SIZE_BYTES}`);
719
- this.db.run(`PRAGMA cache_size = ${SQLITE_CACHE_SIZE_PAGES}`);
720
- const migrationRunner = new MigrationRunner(this.db);
721
- migrationRunner.runAllMigrations();
883
+ withTransaction(fn) {
884
+ const transaction = this.db.transaction(fn);
885
+ return transaction(this.db);
722
886
  }
723
887
  /**
724
888
  * Close the database connection
@@ -893,24 +1057,652 @@ var MigrationRunner = class {
893
1057
  `);
894
1058
  db.run("CREATE UNIQUE INDEX IF NOT EXISTS idx_project_aliases_name ON project_aliases(project_name)");
895
1059
  }
1060
+ },
1061
+ {
1062
+ version: 4,
1063
+ up: (db) => {
1064
+ db.run(`
1065
+ CREATE TABLE IF NOT EXISTS observation_embeddings (
1066
+ observation_id INTEGER PRIMARY KEY,
1067
+ embedding BLOB NOT NULL,
1068
+ model TEXT NOT NULL,
1069
+ dimensions INTEGER NOT NULL,
1070
+ created_at TEXT NOT NULL,
1071
+ FOREIGN KEY (observation_id) REFERENCES observations(id) ON DELETE CASCADE
1072
+ )
1073
+ `);
1074
+ db.run("CREATE INDEX IF NOT EXISTS idx_embeddings_model ON observation_embeddings(model)");
1075
+ }
1076
+ },
1077
+ {
1078
+ version: 5,
1079
+ up: (db) => {
1080
+ db.run("ALTER TABLE observations ADD COLUMN last_accessed_epoch INTEGER");
1081
+ db.run("ALTER TABLE observations ADD COLUMN is_stale INTEGER DEFAULT 0");
1082
+ db.run("CREATE INDEX IF NOT EXISTS idx_observations_last_accessed ON observations(last_accessed_epoch)");
1083
+ db.run("CREATE INDEX IF NOT EXISTS idx_observations_stale ON observations(is_stale)");
1084
+ }
1085
+ },
1086
+ {
1087
+ version: 6,
1088
+ up: (db) => {
1089
+ db.run(`
1090
+ CREATE TABLE IF NOT EXISTS checkpoints (
1091
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1092
+ session_id INTEGER NOT NULL,
1093
+ project TEXT NOT NULL,
1094
+ task TEXT NOT NULL,
1095
+ progress TEXT,
1096
+ next_steps TEXT,
1097
+ open_questions TEXT,
1098
+ relevant_files TEXT,
1099
+ context_snapshot TEXT,
1100
+ created_at TEXT NOT NULL,
1101
+ created_at_epoch INTEGER NOT NULL,
1102
+ FOREIGN KEY (session_id) REFERENCES sessions(id)
1103
+ )
1104
+ `);
1105
+ db.run("CREATE INDEX IF NOT EXISTS idx_checkpoints_session ON checkpoints(session_id)");
1106
+ db.run("CREATE INDEX IF NOT EXISTS idx_checkpoints_project ON checkpoints(project)");
1107
+ db.run("CREATE INDEX IF NOT EXISTS idx_checkpoints_epoch ON checkpoints(created_at_epoch)");
1108
+ }
896
1109
  }
897
1110
  ];
898
1111
  }
899
1112
  };
900
1113
 
1114
+ // src/services/sqlite/Sessions.ts
1115
+ function createSession(db, contentSessionId, project, userPrompt) {
1116
+ const now = /* @__PURE__ */ new Date();
1117
+ const result = db.run(
1118
+ `INSERT INTO sessions (content_session_id, project, user_prompt, status, started_at, started_at_epoch)
1119
+ VALUES (?, ?, ?, 'active', ?, ?)`,
1120
+ [contentSessionId, project, userPrompt, now.toISOString(), now.getTime()]
1121
+ );
1122
+ return Number(result.lastInsertRowid);
1123
+ }
1124
+ function getSessionByContentId(db, contentSessionId) {
1125
+ const query = db.query("SELECT * FROM sessions WHERE content_session_id = ?");
1126
+ return query.get(contentSessionId);
1127
+ }
1128
+ function completeSession(db, id) {
1129
+ const now = /* @__PURE__ */ new Date();
1130
+ db.run(
1131
+ `UPDATE sessions
1132
+ SET status = 'completed', completed_at = ?, completed_at_epoch = ?
1133
+ WHERE id = ?`,
1134
+ [now.toISOString(), now.getTime(), id]
1135
+ );
1136
+ }
1137
+
1138
+ // src/services/sqlite/index.ts
1139
+ init_Observations();
1140
+
1141
+ // src/services/sqlite/Summaries.ts
1142
+ function escapeLikePattern2(input) {
1143
+ return input.replace(/[%_\\]/g, "\\$&");
1144
+ }
1145
+ function createSummary(db, sessionId, project, request, investigated, learned, completed, nextSteps, notes) {
1146
+ const now = /* @__PURE__ */ new Date();
1147
+ const result = db.run(
1148
+ `INSERT INTO summaries
1149
+ (session_id, project, request, investigated, learned, completed, next_steps, notes, created_at, created_at_epoch)
1150
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1151
+ [sessionId, project, request, investigated, learned, completed, nextSteps, notes, now.toISOString(), now.getTime()]
1152
+ );
1153
+ return Number(result.lastInsertRowid);
1154
+ }
1155
+ function getSummariesByProject(db, project, limit = 50) {
1156
+ const query = db.query(
1157
+ "SELECT * FROM summaries WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ?"
1158
+ );
1159
+ return query.all(project, limit);
1160
+ }
1161
+ function searchSummaries(db, searchTerm, project) {
1162
+ const sql = project ? `SELECT * FROM summaries
1163
+ WHERE project = ? AND (request LIKE ? ESCAPE '\\' OR learned LIKE ? ESCAPE '\\' OR completed LIKE ? ESCAPE '\\' OR notes LIKE ? ESCAPE '\\')
1164
+ ORDER BY created_at_epoch DESC` : `SELECT * FROM summaries
1165
+ WHERE request LIKE ? ESCAPE '\\' OR learned LIKE ? ESCAPE '\\' OR completed LIKE ? ESCAPE '\\' OR notes LIKE ? ESCAPE '\\'
1166
+ ORDER BY created_at_epoch DESC`;
1167
+ const pattern = `%${escapeLikePattern2(searchTerm)}%`;
1168
+ const query = db.query(sql);
1169
+ if (project) {
1170
+ return query.all(project, pattern, pattern, pattern, pattern);
1171
+ }
1172
+ return query.all(pattern, pattern, pattern, pattern);
1173
+ }
1174
+
1175
+ // src/services/sqlite/Prompts.ts
1176
+ function createPrompt(db, contentSessionId, project, promptNumber, promptText) {
1177
+ const now = /* @__PURE__ */ new Date();
1178
+ const result = db.run(
1179
+ `INSERT INTO prompts
1180
+ (content_session_id, project, prompt_number, prompt_text, created_at, created_at_epoch)
1181
+ VALUES (?, ?, ?, ?, ?, ?)`,
1182
+ [contentSessionId, project, promptNumber, promptText, now.toISOString(), now.getTime()]
1183
+ );
1184
+ return Number(result.lastInsertRowid);
1185
+ }
1186
+ function getPromptsByProject(db, project, limit = 100) {
1187
+ const query = db.query(
1188
+ "SELECT * FROM prompts WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ?"
1189
+ );
1190
+ return query.all(project, limit);
1191
+ }
1192
+
1193
+ // src/services/sqlite/Checkpoints.ts
1194
+ function createCheckpoint(db, sessionId, project, data) {
1195
+ const now = /* @__PURE__ */ new Date();
1196
+ const result = db.run(
1197
+ `INSERT INTO checkpoints (session_id, project, task, progress, next_steps, open_questions, relevant_files, context_snapshot, created_at, created_at_epoch)
1198
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1199
+ [
1200
+ sessionId,
1201
+ project,
1202
+ data.task,
1203
+ data.progress || null,
1204
+ data.nextSteps || null,
1205
+ data.openQuestions || null,
1206
+ data.relevantFiles || null,
1207
+ data.contextSnapshot || null,
1208
+ now.toISOString(),
1209
+ now.getTime()
1210
+ ]
1211
+ );
1212
+ return Number(result.lastInsertRowid);
1213
+ }
1214
+ function getLatestCheckpoint(db, sessionId) {
1215
+ const query = db.query(
1216
+ "SELECT * FROM checkpoints WHERE session_id = ? ORDER BY created_at_epoch DESC LIMIT 1"
1217
+ );
1218
+ return query.get(sessionId);
1219
+ }
1220
+ function getLatestCheckpointByProject(db, project) {
1221
+ const query = db.query(
1222
+ "SELECT * FROM checkpoints WHERE project = ? ORDER BY created_at_epoch DESC LIMIT 1"
1223
+ );
1224
+ return query.get(project);
1225
+ }
1226
+
1227
+ // src/services/sqlite/Reports.ts
1228
+ function getReportData(db, project, startEpoch, endEpoch) {
1229
+ const startDate = new Date(startEpoch);
1230
+ const endDate = new Date(endEpoch);
1231
+ const days = Math.ceil((endEpoch - startEpoch) / (24 * 60 * 60 * 1e3));
1232
+ const label = days <= 7 ? "Weekly" : days <= 31 ? "Monthly" : "Custom";
1233
+ const countInRange = (table, epochCol = "created_at_epoch") => {
1234
+ 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} <= ?`;
1235
+ const stmt = db.query(sql);
1236
+ const row = project ? stmt.get(project, startEpoch, endEpoch) : stmt.get(startEpoch, endEpoch);
1237
+ return row?.count || 0;
1238
+ };
1239
+ const observations = countInRange("observations");
1240
+ const summaries = countInRange("summaries");
1241
+ const prompts = countInRange("prompts");
1242
+ const sessions = countInRange("sessions", "started_at_epoch");
1243
+ const timelineSql = project ? `SELECT DATE(datetime(created_at_epoch / 1000, 'unixepoch')) as day, COUNT(*) as count
1244
+ FROM observations
1245
+ WHERE project = ? AND created_at_epoch >= ? AND created_at_epoch <= ?
1246
+ GROUP BY day ORDER BY day ASC` : `SELECT DATE(datetime(created_at_epoch / 1000, 'unixepoch')) as day, COUNT(*) as count
1247
+ FROM observations
1248
+ WHERE created_at_epoch >= ? AND created_at_epoch <= ?
1249
+ GROUP BY day ORDER BY day ASC`;
1250
+ const timelineStmt = db.query(timelineSql);
1251
+ const timeline = project ? timelineStmt.all(project, startEpoch, endEpoch) : timelineStmt.all(startEpoch, endEpoch);
1252
+ const typeSql = project ? `SELECT type, COUNT(*) as count FROM observations
1253
+ WHERE project = ? AND created_at_epoch >= ? AND created_at_epoch <= ?
1254
+ GROUP BY type ORDER BY count DESC` : `SELECT type, COUNT(*) as count FROM observations
1255
+ WHERE created_at_epoch >= ? AND created_at_epoch <= ?
1256
+ GROUP BY type ORDER BY count DESC`;
1257
+ const typeStmt = db.query(typeSql);
1258
+ const typeDistribution = project ? typeStmt.all(project, startEpoch, endEpoch) : typeStmt.all(startEpoch, endEpoch);
1259
+ 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 <= ?`;
1260
+ const sessionTotal = (project ? db.query(sessionTotalSql).get(project, startEpoch, endEpoch)?.count : db.query(sessionTotalSql).get(startEpoch, endEpoch)?.count) || 0;
1261
+ 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'`;
1262
+ const sessionCompleted = (project ? db.query(sessionCompletedSql).get(project, startEpoch, endEpoch)?.count : db.query(sessionCompletedSql).get(startEpoch, endEpoch)?.count) || 0;
1263
+ const sessionAvgSql = project ? `SELECT AVG((completed_at_epoch - started_at_epoch) / 1000.0 / 60.0) as avg_min
1264
+ FROM sessions
1265
+ WHERE project = ? AND started_at_epoch >= ? AND started_at_epoch <= ?
1266
+ 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
1267
+ FROM sessions
1268
+ WHERE started_at_epoch >= ? AND started_at_epoch <= ?
1269
+ AND status = 'completed' AND completed_at_epoch IS NOT NULL AND completed_at_epoch > started_at_epoch`;
1270
+ const avgRow = project ? db.query(sessionAvgSql).get(project, startEpoch, endEpoch) : db.query(sessionAvgSql).get(startEpoch, endEpoch);
1271
+ const avgDurationMinutes = Math.round((avgRow?.avg_min || 0) * 10) / 10;
1272
+ const knowledgeSql = project ? `SELECT COUNT(*) as count FROM observations
1273
+ WHERE project = ? AND created_at_epoch >= ? AND created_at_epoch <= ?
1274
+ AND type IN ('constraint', 'decision', 'heuristic', 'rejected')` : `SELECT COUNT(*) as count FROM observations
1275
+ WHERE created_at_epoch >= ? AND created_at_epoch <= ?
1276
+ AND type IN ('constraint', 'decision', 'heuristic', 'rejected')`;
1277
+ const knowledgeCount = (project ? db.query(knowledgeSql).get(project, startEpoch, endEpoch)?.count : db.query(knowledgeSql).get(startEpoch, endEpoch)?.count) || 0;
1278
+ const staleSql = project ? `SELECT COUNT(*) as count FROM observations
1279
+ WHERE project = ? AND created_at_epoch >= ? AND created_at_epoch <= ? AND is_stale = 1` : `SELECT COUNT(*) as count FROM observations
1280
+ WHERE created_at_epoch >= ? AND created_at_epoch <= ? AND is_stale = 1`;
1281
+ const staleCount = (project ? db.query(staleSql).get(project, startEpoch, endEpoch)?.count : db.query(staleSql).get(startEpoch, endEpoch)?.count) || 0;
1282
+ const summarySql = project ? `SELECT learned, completed, next_steps FROM summaries
1283
+ WHERE project = ? AND created_at_epoch >= ? AND created_at_epoch <= ?
1284
+ ORDER BY created_at_epoch DESC` : `SELECT learned, completed, next_steps FROM summaries
1285
+ WHERE created_at_epoch >= ? AND created_at_epoch <= ?
1286
+ ORDER BY created_at_epoch DESC`;
1287
+ const summaryRows = project ? db.query(summarySql).all(project, startEpoch, endEpoch) : db.query(summarySql).all(startEpoch, endEpoch);
1288
+ const topLearnings = [];
1289
+ const completedTasks = [];
1290
+ const nextStepsArr = [];
1291
+ for (const row of summaryRows) {
1292
+ if (row.learned) {
1293
+ const parts = row.learned.split("; ").filter(Boolean);
1294
+ topLearnings.push(...parts);
1295
+ }
1296
+ if (row.completed) {
1297
+ const parts = row.completed.split("; ").filter(Boolean);
1298
+ completedTasks.push(...parts);
1299
+ }
1300
+ if (row.next_steps) {
1301
+ const parts = row.next_steps.split("; ").filter(Boolean);
1302
+ nextStepsArr.push(...parts);
1303
+ }
1304
+ }
1305
+ const filesSql = project ? `SELECT files_modified FROM observations
1306
+ WHERE project = ? AND created_at_epoch >= ? AND created_at_epoch <= ?
1307
+ AND files_modified IS NOT NULL AND files_modified != ''` : `SELECT files_modified FROM observations
1308
+ WHERE created_at_epoch >= ? AND created_at_epoch <= ?
1309
+ AND files_modified IS NOT NULL AND files_modified != ''`;
1310
+ const fileRows = project ? db.query(filesSql).all(project, startEpoch, endEpoch) : db.query(filesSql).all(startEpoch, endEpoch);
1311
+ const fileCounts = /* @__PURE__ */ new Map();
1312
+ for (const row of fileRows) {
1313
+ const files = row.files_modified.split(",").map((f) => f.trim()).filter(Boolean);
1314
+ for (const file of files) {
1315
+ fileCounts.set(file, (fileCounts.get(file) || 0) + 1);
1316
+ }
1317
+ }
1318
+ const fileHotspots = Array.from(fileCounts.entries()).map(([file, count]) => ({ file, count })).sort((a, b) => b.count - a.count).slice(0, 15);
1319
+ return {
1320
+ period: {
1321
+ start: startDate.toISOString().split("T")[0],
1322
+ end: endDate.toISOString().split("T")[0],
1323
+ days,
1324
+ label
1325
+ },
1326
+ overview: {
1327
+ observations,
1328
+ summaries,
1329
+ sessions,
1330
+ prompts,
1331
+ knowledgeCount,
1332
+ staleCount
1333
+ },
1334
+ timeline,
1335
+ typeDistribution,
1336
+ sessionStats: {
1337
+ total: sessionTotal,
1338
+ completed: sessionCompleted,
1339
+ avgDurationMinutes
1340
+ },
1341
+ topLearnings: [...new Set(topLearnings)].slice(0, 10),
1342
+ completedTasks: [...new Set(completedTasks)].slice(0, 10),
1343
+ nextSteps: [...new Set(nextStepsArr)].slice(0, 10),
1344
+ fileHotspots
1345
+ };
1346
+ }
1347
+
901
1348
  // src/services/sqlite/index.ts
902
- init_Sessions();
1349
+ init_Search();
1350
+
1351
+ // src/sdk/index.ts
903
1352
  init_Observations();
904
- init_Summaries();
905
- init_Prompts();
906
1353
  init_Search();
907
1354
 
1355
+ // src/services/search/HybridSearch.ts
1356
+ init_EmbeddingService();
1357
+
1358
+ // src/services/search/VectorSearch.ts
1359
+ init_EmbeddingService();
1360
+ init_logger();
1361
+ function cosineSimilarity(a, b) {
1362
+ if (a.length !== b.length) return 0;
1363
+ let dotProduct = 0;
1364
+ let normA = 0;
1365
+ let normB = 0;
1366
+ for (let i = 0; i < a.length; i++) {
1367
+ dotProduct += a[i] * b[i];
1368
+ normA += a[i] * a[i];
1369
+ normB += b[i] * b[i];
1370
+ }
1371
+ const denominator = Math.sqrt(normA) * Math.sqrt(normB);
1372
+ if (denominator === 0) return 0;
1373
+ return dotProduct / denominator;
1374
+ }
1375
+ function float32ToBuffer(arr) {
1376
+ return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength);
1377
+ }
1378
+ function bufferToFloat32(buf) {
1379
+ const arrayBuffer = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
1380
+ return new Float32Array(arrayBuffer);
1381
+ }
1382
+ var VectorSearch = class {
1383
+ /**
1384
+ * Ricerca semantica: calcola cosine similarity tra query e tutti gli embeddings.
1385
+ */
1386
+ async search(db, queryEmbedding, options = {}) {
1387
+ const limit = options.limit || 10;
1388
+ const threshold = options.threshold || 0.3;
1389
+ try {
1390
+ let sql = `
1391
+ SELECT e.observation_id, e.embedding,
1392
+ o.title, o.text, o.type, o.project, o.created_at, o.created_at_epoch
1393
+ FROM observation_embeddings e
1394
+ JOIN observations o ON o.id = e.observation_id
1395
+ `;
1396
+ const params = [];
1397
+ if (options.project) {
1398
+ sql += " WHERE o.project = ?";
1399
+ params.push(options.project);
1400
+ }
1401
+ const rows = db.query(sql).all(...params);
1402
+ const scored = [];
1403
+ for (const row of rows) {
1404
+ const embedding = bufferToFloat32(row.embedding);
1405
+ const similarity = cosineSimilarity(queryEmbedding, embedding);
1406
+ if (similarity >= threshold) {
1407
+ scored.push({
1408
+ id: row.observation_id,
1409
+ observationId: row.observation_id,
1410
+ similarity,
1411
+ title: row.title,
1412
+ text: row.text,
1413
+ type: row.type,
1414
+ project: row.project,
1415
+ created_at: row.created_at,
1416
+ created_at_epoch: row.created_at_epoch
1417
+ });
1418
+ }
1419
+ }
1420
+ scored.sort((a, b) => b.similarity - a.similarity);
1421
+ return scored.slice(0, limit);
1422
+ } catch (error) {
1423
+ logger.error("VECTOR", `Errore ricerca vettoriale: ${error}`);
1424
+ return [];
1425
+ }
1426
+ }
1427
+ /**
1428
+ * Salva embedding per un'osservazione.
1429
+ */
1430
+ async storeEmbedding(db, observationId, embedding, model) {
1431
+ try {
1432
+ const blob = float32ToBuffer(embedding);
1433
+ db.query(`
1434
+ INSERT OR REPLACE INTO observation_embeddings
1435
+ (observation_id, embedding, model, dimensions, created_at)
1436
+ VALUES (?, ?, ?, ?, ?)
1437
+ `).run(
1438
+ observationId,
1439
+ blob,
1440
+ model,
1441
+ embedding.length,
1442
+ (/* @__PURE__ */ new Date()).toISOString()
1443
+ );
1444
+ logger.debug("VECTOR", `Embedding salvato per osservazione ${observationId}`);
1445
+ } catch (error) {
1446
+ logger.error("VECTOR", `Errore salvataggio embedding: ${error}`);
1447
+ }
1448
+ }
1449
+ /**
1450
+ * Genera embeddings per osservazioni che non li hanno ancora.
1451
+ */
1452
+ async backfillEmbeddings(db, batchSize = 50) {
1453
+ const embeddingService2 = getEmbeddingService();
1454
+ if (!await embeddingService2.initialize()) {
1455
+ logger.warn("VECTOR", "Embedding service non disponibile, backfill saltato");
1456
+ return 0;
1457
+ }
1458
+ const rows = db.query(`
1459
+ SELECT o.id, o.title, o.text, o.narrative, o.concepts
1460
+ FROM observations o
1461
+ LEFT JOIN observation_embeddings e ON e.observation_id = o.id
1462
+ WHERE e.observation_id IS NULL
1463
+ ORDER BY o.created_at_epoch DESC
1464
+ LIMIT ?
1465
+ `).all(batchSize);
1466
+ if (rows.length === 0) return 0;
1467
+ let count = 0;
1468
+ const model = embeddingService2.getProvider() || "unknown";
1469
+ for (const row of rows) {
1470
+ const parts = [row.title];
1471
+ if (row.text) parts.push(row.text);
1472
+ if (row.narrative) parts.push(row.narrative);
1473
+ if (row.concepts) parts.push(row.concepts);
1474
+ const fullText = parts.join(" ").substring(0, 2e3);
1475
+ const embedding = await embeddingService2.embed(fullText);
1476
+ if (embedding) {
1477
+ await this.storeEmbedding(db, row.id, embedding, model);
1478
+ count++;
1479
+ }
1480
+ }
1481
+ logger.info("VECTOR", `Backfill completato: ${count}/${rows.length} embeddings generati`);
1482
+ return count;
1483
+ }
1484
+ /**
1485
+ * Statistiche sugli embeddings.
1486
+ */
1487
+ getStats(db) {
1488
+ try {
1489
+ const totalRow = db.query("SELECT COUNT(*) as count FROM observations").get();
1490
+ const embeddedRow = db.query("SELECT COUNT(*) as count FROM observation_embeddings").get();
1491
+ const total = totalRow?.count || 0;
1492
+ const embedded = embeddedRow?.count || 0;
1493
+ const percentage = total > 0 ? Math.round(embedded / total * 100) : 0;
1494
+ return { total, embedded, percentage };
1495
+ } catch {
1496
+ return { total: 0, embedded: 0, percentage: 0 };
1497
+ }
1498
+ }
1499
+ };
1500
+ var vectorSearch = null;
1501
+ function getVectorSearch() {
1502
+ if (!vectorSearch) {
1503
+ vectorSearch = new VectorSearch();
1504
+ }
1505
+ return vectorSearch;
1506
+ }
1507
+
1508
+ // src/services/search/ScoringEngine.ts
1509
+ var SEARCH_WEIGHTS = {
1510
+ semantic: 0.4,
1511
+ fts5: 0.3,
1512
+ recency: 0.2,
1513
+ projectMatch: 0.1
1514
+ };
1515
+ var CONTEXT_WEIGHTS = {
1516
+ semantic: 0,
1517
+ fts5: 0,
1518
+ recency: 0.7,
1519
+ projectMatch: 0.3
1520
+ };
1521
+ function recencyScore(createdAtEpoch, halfLifeHours = 168) {
1522
+ if (!createdAtEpoch || createdAtEpoch <= 0) return 0;
1523
+ const nowMs = Date.now();
1524
+ const ageMs = nowMs - createdAtEpoch;
1525
+ if (ageMs <= 0) return 1;
1526
+ const ageHours = ageMs / (1e3 * 60 * 60);
1527
+ return Math.exp(-ageHours * Math.LN2 / halfLifeHours);
1528
+ }
1529
+ function normalizeFTS5Rank(rank, allRanks) {
1530
+ if (allRanks.length === 0) return 0;
1531
+ if (allRanks.length === 1) return 1;
1532
+ const minRank = Math.min(...allRanks);
1533
+ const maxRank = Math.max(...allRanks);
1534
+ if (minRank === maxRank) return 1;
1535
+ return (maxRank - rank) / (maxRank - minRank);
1536
+ }
1537
+ function projectMatchScore(itemProject, targetProject) {
1538
+ if (!itemProject || !targetProject) return 0;
1539
+ return itemProject.toLowerCase() === targetProject.toLowerCase() ? 1 : 0;
1540
+ }
1541
+ function computeCompositeScore(signals, weights) {
1542
+ return signals.semantic * weights.semantic + signals.fts5 * weights.fts5 + signals.recency * weights.recency + signals.projectMatch * weights.projectMatch;
1543
+ }
1544
+ var KNOWLEDGE_TYPE_BOOST = {
1545
+ constraint: 1.3,
1546
+ decision: 1.25,
1547
+ heuristic: 1.15,
1548
+ rejected: 1.1
1549
+ };
1550
+ function knowledgeTypeBoost(type) {
1551
+ return KNOWLEDGE_TYPE_BOOST[type] ?? 1;
1552
+ }
1553
+
1554
+ // src/services/search/HybridSearch.ts
1555
+ init_logger();
1556
+ var HybridSearch = class {
1557
+ embeddingInitialized = false;
1558
+ /**
1559
+ * Inizializza il servizio di embedding (lazy, non bloccante)
1560
+ */
1561
+ async initialize() {
1562
+ try {
1563
+ const embeddingService2 = getEmbeddingService();
1564
+ await embeddingService2.initialize();
1565
+ this.embeddingInitialized = embeddingService2.isAvailable();
1566
+ logger.info("SEARCH", `HybridSearch inizializzato (embedding: ${this.embeddingInitialized ? "attivo" : "disattivato"})`);
1567
+ } catch (error) {
1568
+ logger.warn("SEARCH", "Inizializzazione embedding fallita, uso solo FTS5", {}, error);
1569
+ this.embeddingInitialized = false;
1570
+ }
1571
+ }
1572
+ /**
1573
+ * Ricerca ibrida con scoring a 4 segnali
1574
+ */
1575
+ async search(db, query, options = {}) {
1576
+ const limit = options.limit || 10;
1577
+ const weights = options.weights || SEARCH_WEIGHTS;
1578
+ const targetProject = options.project || "";
1579
+ const rawItems = /* @__PURE__ */ new Map();
1580
+ if (this.embeddingInitialized) {
1581
+ try {
1582
+ const embeddingService2 = getEmbeddingService();
1583
+ const queryEmbedding = await embeddingService2.embed(query);
1584
+ if (queryEmbedding) {
1585
+ const vectorSearch2 = getVectorSearch();
1586
+ const vectorResults = await vectorSearch2.search(db, queryEmbedding, {
1587
+ project: options.project,
1588
+ limit: limit * 2,
1589
+ // Prendiamo piu risultati per il ranking
1590
+ threshold: 0.3
1591
+ });
1592
+ for (const hit of vectorResults) {
1593
+ rawItems.set(String(hit.observationId), {
1594
+ id: String(hit.observationId),
1595
+ title: hit.title,
1596
+ content: hit.text || "",
1597
+ type: hit.type,
1598
+ project: hit.project,
1599
+ created_at: hit.created_at,
1600
+ created_at_epoch: hit.created_at_epoch,
1601
+ semanticScore: hit.similarity,
1602
+ fts5Rank: null,
1603
+ source: "vector"
1604
+ });
1605
+ }
1606
+ logger.debug("SEARCH", `Vector search: ${vectorResults.length} risultati`);
1607
+ }
1608
+ } catch (error) {
1609
+ logger.warn("SEARCH", "Ricerca vettoriale fallita, uso solo keyword", {}, error);
1610
+ }
1611
+ }
1612
+ try {
1613
+ const { searchObservationsFTSWithRank: searchObservationsFTSWithRank2 } = await Promise.resolve().then(() => (init_Search(), Search_exports));
1614
+ const keywordResults = searchObservationsFTSWithRank2(db, query, {
1615
+ project: options.project,
1616
+ limit: limit * 2
1617
+ });
1618
+ for (const obs of keywordResults) {
1619
+ const id = String(obs.id);
1620
+ const existing = rawItems.get(id);
1621
+ if (existing) {
1622
+ existing.fts5Rank = obs.fts5_rank;
1623
+ existing.source = "vector";
1624
+ } else {
1625
+ rawItems.set(id, {
1626
+ id,
1627
+ title: obs.title,
1628
+ content: obs.text || obs.narrative || "",
1629
+ type: obs.type,
1630
+ project: obs.project,
1631
+ created_at: obs.created_at,
1632
+ created_at_epoch: obs.created_at_epoch,
1633
+ semanticScore: 0,
1634
+ fts5Rank: obs.fts5_rank,
1635
+ source: "keyword"
1636
+ });
1637
+ }
1638
+ }
1639
+ logger.debug("SEARCH", `Keyword search: ${keywordResults.length} risultati`);
1640
+ } catch (error) {
1641
+ logger.error("SEARCH", "Ricerca keyword fallita", {}, error);
1642
+ }
1643
+ if (rawItems.size === 0) return [];
1644
+ const allFTS5Ranks = Array.from(rawItems.values()).filter((item) => item.fts5Rank !== null).map((item) => item.fts5Rank);
1645
+ const scored = [];
1646
+ for (const item of rawItems.values()) {
1647
+ const signals = {
1648
+ semantic: item.semanticScore,
1649
+ fts5: item.fts5Rank !== null ? normalizeFTS5Rank(item.fts5Rank, allFTS5Ranks) : 0,
1650
+ recency: recencyScore(item.created_at_epoch),
1651
+ projectMatch: targetProject ? projectMatchScore(item.project, targetProject) : 0
1652
+ };
1653
+ const score = computeCompositeScore(signals, weights);
1654
+ const isHybrid = item.semanticScore > 0 && item.fts5Rank !== null;
1655
+ const hybridBoost = isHybrid ? 1.15 : 1;
1656
+ const finalScore = Math.min(1, score * hybridBoost * knowledgeTypeBoost(item.type));
1657
+ scored.push({
1658
+ id: item.id,
1659
+ title: item.title,
1660
+ content: item.content,
1661
+ type: item.type,
1662
+ project: item.project,
1663
+ created_at: item.created_at,
1664
+ created_at_epoch: item.created_at_epoch,
1665
+ score: finalScore,
1666
+ source: isHybrid ? "hybrid" : item.source,
1667
+ signals
1668
+ });
1669
+ }
1670
+ scored.sort((a, b) => b.score - a.score);
1671
+ const finalResults = scored.slice(0, limit);
1672
+ if (finalResults.length > 0) {
1673
+ try {
1674
+ const { updateLastAccessed: updateLastAccessed3 } = await Promise.resolve().then(() => (init_Observations(), Observations_exports));
1675
+ const ids = finalResults.map((r) => parseInt(r.id, 10)).filter((id) => id > 0);
1676
+ if (ids.length > 0) {
1677
+ updateLastAccessed3(db, ids);
1678
+ }
1679
+ } catch {
1680
+ }
1681
+ }
1682
+ return finalResults;
1683
+ }
1684
+ };
1685
+ var hybridSearch = null;
1686
+ function getHybridSearch() {
1687
+ if (!hybridSearch) {
1688
+ hybridSearch = new HybridSearch();
1689
+ }
1690
+ return hybridSearch;
1691
+ }
1692
+
1693
+ // src/sdk/index.ts
1694
+ init_EmbeddingService();
1695
+ init_logger();
1696
+
1697
+ // src/types/worker-types.ts
1698
+ var KNOWLEDGE_TYPES = ["constraint", "decision", "heuristic", "rejected"];
1699
+
908
1700
  // src/sdk/index.ts
909
1701
  var KiroMemorySDK = class {
910
1702
  db;
911
1703
  project;
912
1704
  constructor(config = {}) {
913
- this.db = new KiroMemoryDatabase(config.dataDir);
1705
+ this.db = new KiroMemoryDatabase(config.dataDir, config.skipMigrations || false);
914
1706
  this.project = config.project || this.detectProject();
915
1707
  }
916
1708
  detectProject() {
@@ -930,22 +1722,69 @@ var KiroMemorySDK = class {
930
1722
  * Get context for the current project
931
1723
  */
932
1724
  async getContext() {
933
- const { getObservationsByProject: getObservationsByProject2 } = await Promise.resolve().then(() => (init_Observations(), Observations_exports));
934
- const { getSummariesByProject: getSummariesByProject2 } = await Promise.resolve().then(() => (init_Summaries(), Summaries_exports));
935
- const { getPromptsByProject: getPromptsByProject2 } = await Promise.resolve().then(() => (init_Prompts(), Prompts_exports));
936
1725
  return {
937
1726
  project: this.project,
938
- relevantObservations: getObservationsByProject2(this.db.db, this.project, 20),
939
- relevantSummaries: getSummariesByProject2(this.db.db, this.project, 5),
940
- recentPrompts: getPromptsByProject2(this.db.db, this.project, 10)
1727
+ relevantObservations: getObservationsByProject(this.db.db, this.project, 20),
1728
+ relevantSummaries: getSummariesByProject(this.db.db, this.project, 5),
1729
+ recentPrompts: getPromptsByProject(this.db.db, this.project, 10)
941
1730
  };
942
1731
  }
1732
+ /**
1733
+ * Valida input per storeObservation
1734
+ */
1735
+ validateObservationInput(data) {
1736
+ if (!data.type || typeof data.type !== "string" || data.type.length > 100) {
1737
+ throw new Error("type \xE8 obbligatorio (stringa, max 100 caratteri)");
1738
+ }
1739
+ if (!data.title || typeof data.title !== "string" || data.title.length > 500) {
1740
+ throw new Error("title \xE8 obbligatorio (stringa, max 500 caratteri)");
1741
+ }
1742
+ if (!data.content || typeof data.content !== "string" || data.content.length > 1e5) {
1743
+ throw new Error("content \xE8 obbligatorio (stringa, max 100KB)");
1744
+ }
1745
+ }
1746
+ /**
1747
+ * Valida input per storeSummary
1748
+ */
1749
+ validateSummaryInput(data) {
1750
+ const MAX = 5e4;
1751
+ for (const [key, val] of Object.entries(data)) {
1752
+ if (val !== void 0 && val !== null) {
1753
+ if (typeof val !== "string") throw new Error(`${key} deve essere una stringa`);
1754
+ if (val.length > MAX) throw new Error(`${key} troppo grande (max 50KB)`);
1755
+ }
1756
+ }
1757
+ }
1758
+ /**
1759
+ * Genera e salva embedding per un'osservazione (fire-and-forget, non blocca)
1760
+ */
1761
+ async generateEmbeddingAsync(observationId, title, content, concepts) {
1762
+ try {
1763
+ const embeddingService2 = getEmbeddingService();
1764
+ if (!embeddingService2.isAvailable()) return;
1765
+ const parts = [title, content];
1766
+ if (concepts?.length) parts.push(concepts.join(", "));
1767
+ const fullText = parts.join(" ").substring(0, 2e3);
1768
+ const embedding = await embeddingService2.embed(fullText);
1769
+ if (embedding) {
1770
+ const vectorSearch2 = getVectorSearch();
1771
+ await vectorSearch2.storeEmbedding(
1772
+ this.db.db,
1773
+ observationId,
1774
+ embedding,
1775
+ embeddingService2.getProvider() || "unknown"
1776
+ );
1777
+ }
1778
+ } catch (error) {
1779
+ logger.debug("SDK", `Embedding generation fallita per obs ${observationId}: ${error}`);
1780
+ }
1781
+ }
943
1782
  /**
944
1783
  * Store a new observation
945
1784
  */
946
1785
  async storeObservation(data) {
947
- const { createObservation: createObservation2 } = await Promise.resolve().then(() => (init_Observations(), Observations_exports));
948
- return createObservation2(
1786
+ this.validateObservationInput(data);
1787
+ const observationId = createObservation(
949
1788
  this.db.db,
950
1789
  "sdk-" + Date.now(),
951
1790
  this.project,
@@ -966,13 +1805,77 @@ var KiroMemorySDK = class {
966
1805
  0
967
1806
  // prompt_number
968
1807
  );
1808
+ this.generateEmbeddingAsync(observationId, data.title, data.content, data.concepts).catch(() => {
1809
+ });
1810
+ return observationId;
1811
+ }
1812
+ /**
1813
+ * Salva conoscenza strutturata (constraint, decision, heuristic, rejected).
1814
+ * Usa il campo `type` per il knowledgeType e `facts` per i metadati JSON.
1815
+ */
1816
+ async storeKnowledge(data) {
1817
+ if (!KNOWLEDGE_TYPES.includes(data.knowledgeType)) {
1818
+ throw new Error(`knowledgeType non valido: ${data.knowledgeType}. Valori ammessi: ${KNOWLEDGE_TYPES.join(", ")}`);
1819
+ }
1820
+ this.validateObservationInput({ type: data.knowledgeType, title: data.title, content: data.content });
1821
+ const metadata = (() => {
1822
+ switch (data.knowledgeType) {
1823
+ case "constraint":
1824
+ return {
1825
+ knowledgeType: "constraint",
1826
+ severity: data.metadata?.severity || "soft",
1827
+ reason: data.metadata?.reason
1828
+ };
1829
+ case "decision":
1830
+ return {
1831
+ knowledgeType: "decision",
1832
+ alternatives: data.metadata?.alternatives,
1833
+ reason: data.metadata?.reason
1834
+ };
1835
+ case "heuristic":
1836
+ return {
1837
+ knowledgeType: "heuristic",
1838
+ context: data.metadata?.context,
1839
+ confidence: data.metadata?.confidence
1840
+ };
1841
+ case "rejected":
1842
+ return {
1843
+ knowledgeType: "rejected",
1844
+ reason: data.metadata?.reason || "",
1845
+ alternatives: data.metadata?.alternatives
1846
+ };
1847
+ }
1848
+ })();
1849
+ const observationId = createObservation(
1850
+ this.db.db,
1851
+ "sdk-" + Date.now(),
1852
+ data.project || this.project,
1853
+ data.knowledgeType,
1854
+ // type = knowledgeType
1855
+ data.title,
1856
+ null,
1857
+ // subtitle
1858
+ data.content,
1859
+ null,
1860
+ // narrative
1861
+ JSON.stringify(metadata),
1862
+ // facts = metadati JSON
1863
+ data.concepts?.join(", ") || null,
1864
+ data.files?.join(", ") || null,
1865
+ data.files?.join(", ") || null,
1866
+ 0
1867
+ // prompt_number
1868
+ );
1869
+ this.generateEmbeddingAsync(observationId, data.title, data.content, data.concepts).catch(() => {
1870
+ });
1871
+ return observationId;
969
1872
  }
970
1873
  /**
971
1874
  * Store a session summary
972
1875
  */
973
1876
  async storeSummary(data) {
974
- const { createSummary: createSummary2 } = await Promise.resolve().then(() => (init_Summaries(), Summaries_exports));
975
- return createSummary2(
1877
+ this.validateSummaryInput(data);
1878
+ return createSummary(
976
1879
  this.db.db,
977
1880
  "sdk-" + Date.now(),
978
1881
  this.project,
@@ -988,61 +1891,52 @@ var KiroMemorySDK = class {
988
1891
  * Search across all stored context
989
1892
  */
990
1893
  async search(query) {
991
- const { searchObservations: searchObservations2 } = await Promise.resolve().then(() => (init_Observations(), Observations_exports));
992
- const { searchSummaries: searchSummaries2 } = await Promise.resolve().then(() => (init_Summaries(), Summaries_exports));
993
1894
  return {
994
- observations: searchObservations2(this.db.db, query, this.project),
995
- summaries: searchSummaries2(this.db.db, query, this.project)
1895
+ observations: searchObservations(this.db.db, query, this.project),
1896
+ summaries: searchSummaries(this.db.db, query, this.project)
996
1897
  };
997
1898
  }
998
1899
  /**
999
1900
  * Get recent observations
1000
1901
  */
1001
1902
  async getRecentObservations(limit = 10) {
1002
- const { getObservationsByProject: getObservationsByProject2 } = await Promise.resolve().then(() => (init_Observations(), Observations_exports));
1003
- return getObservationsByProject2(this.db.db, this.project, limit);
1903
+ return getObservationsByProject(this.db.db, this.project, limit);
1004
1904
  }
1005
1905
  /**
1006
1906
  * Get recent summaries
1007
1907
  */
1008
1908
  async getRecentSummaries(limit = 5) {
1009
- const { getSummariesByProject: getSummariesByProject2 } = await Promise.resolve().then(() => (init_Summaries(), Summaries_exports));
1010
- return getSummariesByProject2(this.db.db, this.project, limit);
1909
+ return getSummariesByProject(this.db.db, this.project, limit);
1011
1910
  }
1012
1911
  /**
1013
1912
  * Advanced search with FTS5 and filters
1014
1913
  */
1015
1914
  async searchAdvanced(query, filters = {}) {
1016
- const { searchObservationsFTS: searchObservationsFTS2 } = await Promise.resolve().then(() => (init_Search(), Search_exports));
1017
- const { searchSummariesFiltered: searchSummariesFiltered2 } = await Promise.resolve().then(() => (init_Search(), Search_exports));
1018
1915
  const projectFilters = { ...filters, project: filters.project || this.project };
1019
1916
  return {
1020
- observations: searchObservationsFTS2(this.db.db, query, projectFilters),
1021
- summaries: searchSummariesFiltered2(this.db.db, query, projectFilters)
1917
+ observations: searchObservationsFTS(this.db.db, query, projectFilters),
1918
+ summaries: searchSummariesFiltered(this.db.db, query, projectFilters)
1022
1919
  };
1023
1920
  }
1024
1921
  /**
1025
1922
  * Retrieve observations by ID (batch)
1026
1923
  */
1027
1924
  async getObservationsByIds(ids) {
1028
- const { getObservationsByIds: getObservationsByIds2 } = await Promise.resolve().then(() => (init_Search(), Search_exports));
1029
- return getObservationsByIds2(this.db.db, ids);
1925
+ return getObservationsByIds(this.db.db, ids);
1030
1926
  }
1031
1927
  /**
1032
1928
  * Timeline: chronological context around an observation
1033
1929
  */
1034
1930
  async getTimeline(anchorId, depthBefore = 5, depthAfter = 5) {
1035
- const { getTimeline: getTimeline2 } = await Promise.resolve().then(() => (init_Search(), Search_exports));
1036
- return getTimeline2(this.db.db, anchorId, depthBefore, depthAfter);
1931
+ return getTimeline(this.db.db, anchorId, depthBefore, depthAfter);
1037
1932
  }
1038
1933
  /**
1039
1934
  * Create or retrieve a session for the current project
1040
1935
  */
1041
1936
  async getOrCreateSession(contentSessionId) {
1042
- const { getSessionByContentId: getSessionByContentId2, createSession: createSession2 } = await Promise.resolve().then(() => (init_Sessions(), Sessions_exports));
1043
- let session = getSessionByContentId2(this.db.db, contentSessionId);
1937
+ let session = getSessionByContentId(this.db.db, contentSessionId);
1044
1938
  if (!session) {
1045
- const id = createSession2(this.db.db, contentSessionId, this.project, "");
1939
+ const id = createSession(this.db.db, contentSessionId, this.project, "");
1046
1940
  session = {
1047
1941
  id,
1048
1942
  content_session_id: contentSessionId,
@@ -1056,27 +1950,246 @@ var KiroMemorySDK = class {
1056
1950
  completed_at_epoch: null
1057
1951
  };
1058
1952
  }
1059
- return session;
1953
+ return session;
1954
+ }
1955
+ /**
1956
+ * Store a user prompt
1957
+ */
1958
+ async storePrompt(contentSessionId, promptNumber, text) {
1959
+ return createPrompt(this.db.db, contentSessionId, this.project, promptNumber, text);
1960
+ }
1961
+ /**
1962
+ * Complete a session
1963
+ */
1964
+ async completeSession(sessionId) {
1965
+ completeSession(this.db.db, sessionId);
1966
+ }
1967
+ /**
1968
+ * Getter for current project name
1969
+ */
1970
+ getProject() {
1971
+ return this.project;
1972
+ }
1973
+ /**
1974
+ * Ricerca ibrida: vector search + keyword FTS5
1975
+ * Richiede inizializzazione HybridSearch (embedding service)
1976
+ */
1977
+ async hybridSearch(query, options = {}) {
1978
+ const hybridSearch2 = getHybridSearch();
1979
+ return hybridSearch2.search(this.db.db, query, {
1980
+ project: this.project,
1981
+ limit: options.limit || 10
1982
+ });
1983
+ }
1984
+ /**
1985
+ * Ricerca solo semantica (vector search)
1986
+ * Ritorna risultati basati su similarità coseno con gli embeddings
1987
+ */
1988
+ async semanticSearch(query, options = {}) {
1989
+ const embeddingService2 = getEmbeddingService();
1990
+ if (!embeddingService2.isAvailable()) {
1991
+ await embeddingService2.initialize();
1992
+ }
1993
+ if (!embeddingService2.isAvailable()) return [];
1994
+ const queryEmbedding = await embeddingService2.embed(query);
1995
+ if (!queryEmbedding) return [];
1996
+ const vectorSearch2 = getVectorSearch();
1997
+ const results = await vectorSearch2.search(this.db.db, queryEmbedding, {
1998
+ project: this.project,
1999
+ limit: options.limit || 10,
2000
+ threshold: options.threshold || 0.3
2001
+ });
2002
+ return results.map((r) => ({
2003
+ id: String(r.observationId),
2004
+ title: r.title,
2005
+ content: r.text || "",
2006
+ type: r.type,
2007
+ project: r.project,
2008
+ created_at: r.created_at,
2009
+ created_at_epoch: r.created_at_epoch,
2010
+ score: r.similarity,
2011
+ source: "vector",
2012
+ signals: {
2013
+ semantic: r.similarity,
2014
+ fts5: 0,
2015
+ recency: recencyScore(r.created_at_epoch),
2016
+ projectMatch: projectMatchScore(r.project, this.project)
2017
+ }
2018
+ }));
2019
+ }
2020
+ /**
2021
+ * Genera embeddings per osservazioni che non li hanno ancora
2022
+ */
2023
+ async backfillEmbeddings(batchSize = 50) {
2024
+ const vectorSearch2 = getVectorSearch();
2025
+ return vectorSearch2.backfillEmbeddings(this.db.db, batchSize);
2026
+ }
2027
+ /**
2028
+ * Statistiche sugli embeddings nel database
2029
+ */
2030
+ getEmbeddingStats() {
2031
+ const vectorSearch2 = getVectorSearch();
2032
+ return vectorSearch2.getStats(this.db.db);
2033
+ }
2034
+ /**
2035
+ * Inizializza il servizio di embedding (lazy, chiamare prima di hybridSearch)
2036
+ */
2037
+ async initializeEmbeddings() {
2038
+ const hybridSearch2 = getHybridSearch();
2039
+ await hybridSearch2.initialize();
2040
+ return getEmbeddingService().isAvailable();
2041
+ }
2042
+ /**
2043
+ * Contesto smart con ranking a 4 segnali e budget token.
2044
+ *
2045
+ * Se query presente: usa HybridSearch con SEARCH_WEIGHTS.
2046
+ * Se senza query: ranking per recency + project match (CONTEXT_WEIGHTS).
2047
+ */
2048
+ async getSmartContext(options = {}) {
2049
+ const tokenBudget = options.tokenBudget || parseInt(process.env.KIRO_MEMORY_CONTEXT_TOKENS || "0", 10) || 2e3;
2050
+ const summaries = getSummariesByProject(this.db.db, this.project, 5);
2051
+ let items;
2052
+ if (options.query) {
2053
+ const hybridSearch2 = getHybridSearch();
2054
+ const results = await hybridSearch2.search(this.db.db, options.query, {
2055
+ project: this.project,
2056
+ limit: 30
2057
+ });
2058
+ items = results.map((r) => ({
2059
+ id: parseInt(r.id, 10) || 0,
2060
+ title: r.title,
2061
+ content: r.content,
2062
+ type: r.type,
2063
+ project: r.project,
2064
+ created_at: r.created_at,
2065
+ created_at_epoch: r.created_at_epoch,
2066
+ score: r.score,
2067
+ signals: r.signals
2068
+ }));
2069
+ } else {
2070
+ const observations = getObservationsByProject(this.db.db, this.project, 30);
2071
+ items = observations.map((obs) => {
2072
+ const signals = {
2073
+ semantic: 0,
2074
+ fts5: 0,
2075
+ recency: recencyScore(obs.created_at_epoch),
2076
+ projectMatch: projectMatchScore(obs.project, this.project)
2077
+ };
2078
+ const baseScore = computeCompositeScore(signals, CONTEXT_WEIGHTS);
2079
+ const boostedScore = Math.min(1, baseScore * knowledgeTypeBoost(obs.type));
2080
+ return {
2081
+ id: obs.id,
2082
+ title: obs.title,
2083
+ content: obs.text || obs.narrative || "",
2084
+ type: obs.type,
2085
+ project: obs.project,
2086
+ created_at: obs.created_at,
2087
+ created_at_epoch: obs.created_at_epoch,
2088
+ score: boostedScore,
2089
+ signals
2090
+ };
2091
+ });
2092
+ items.sort((a, b) => b.score - a.score);
2093
+ }
2094
+ let tokensUsed = 0;
2095
+ for (const item of items) {
2096
+ tokensUsed += Math.ceil((item.title.length + item.content.length) / 4);
2097
+ if (tokensUsed > tokenBudget) break;
2098
+ }
2099
+ return {
2100
+ project: this.project,
2101
+ items,
2102
+ summaries,
2103
+ tokenBudget,
2104
+ tokensUsed: Math.min(tokensUsed, tokenBudget)
2105
+ };
2106
+ }
2107
+ /**
2108
+ * Rileva osservazioni stale (file modificati dopo la creazione) e le marca nel DB.
2109
+ * Ritorna il numero di osservazioni marcate come stale.
2110
+ */
2111
+ async detectStaleObservations() {
2112
+ const staleObs = getStaleObservations(this.db.db, this.project);
2113
+ if (staleObs.length > 0) {
2114
+ const ids = staleObs.map((o) => o.id);
2115
+ markObservationsStale(this.db.db, ids, true);
2116
+ }
2117
+ return staleObs.length;
1060
2118
  }
1061
2119
  /**
1062
- * Store a user prompt
2120
+ * Consolida osservazioni duplicate sullo stesso file e tipo.
2121
+ * Raggruppa per (project, type, files_modified), mantiene la piu recente.
1063
2122
  */
1064
- async storePrompt(contentSessionId, promptNumber, text) {
1065
- const { createPrompt: createPrompt2 } = await Promise.resolve().then(() => (init_Prompts(), Prompts_exports));
1066
- return createPrompt2(this.db.db, contentSessionId, this.project, promptNumber, text);
2123
+ async consolidateObservations(options = {}) {
2124
+ return consolidateObservations(this.db.db, this.project, options);
1067
2125
  }
1068
2126
  /**
1069
- * Complete a session
2127
+ * Statistiche decay: totale, stale, mai accedute, accedute di recente.
1070
2128
  */
1071
- async completeSession(sessionId) {
1072
- const { completeSession: completeSession2 } = await Promise.resolve().then(() => (init_Sessions(), Sessions_exports));
1073
- completeSession2(this.db.db, sessionId);
2129
+ async getDecayStats() {
2130
+ const total = this.db.db.query(
2131
+ "SELECT COUNT(*) as count FROM observations WHERE project = ?"
2132
+ ).get(this.project)?.count || 0;
2133
+ const stale = this.db.db.query(
2134
+ "SELECT COUNT(*) as count FROM observations WHERE project = ? AND is_stale = 1"
2135
+ ).get(this.project)?.count || 0;
2136
+ const neverAccessed = this.db.db.query(
2137
+ "SELECT COUNT(*) as count FROM observations WHERE project = ? AND last_accessed_epoch IS NULL"
2138
+ ).get(this.project)?.count || 0;
2139
+ const recentThreshold = Date.now() - 48 * 60 * 60 * 1e3;
2140
+ const recentlyAccessed = this.db.db.query(
2141
+ "SELECT COUNT(*) as count FROM observations WHERE project = ? AND last_accessed_epoch > ?"
2142
+ ).get(this.project, recentThreshold)?.count || 0;
2143
+ return { total, stale, neverAccessed, recentlyAccessed };
1074
2144
  }
1075
2145
  /**
1076
- * Getter for current project name
2146
+ * Crea un checkpoint strutturato per resume sessione.
2147
+ * Salva automaticamente un context_snapshot con le ultime 10 osservazioni.
1077
2148
  */
1078
- getProject() {
1079
- return this.project;
2149
+ async createCheckpoint(sessionId, data) {
2150
+ const recentObs = getObservationsByProject(this.db.db, this.project, 10);
2151
+ const contextSnapshot = JSON.stringify(
2152
+ recentObs.map((o) => ({ id: o.id, type: o.type, title: o.title, text: o.text?.substring(0, 200) }))
2153
+ );
2154
+ return createCheckpoint(this.db.db, sessionId, this.project, {
2155
+ task: data.task,
2156
+ progress: data.progress,
2157
+ nextSteps: data.nextSteps,
2158
+ openQuestions: data.openQuestions,
2159
+ relevantFiles: data.relevantFiles?.join(", "),
2160
+ contextSnapshot
2161
+ });
2162
+ }
2163
+ /**
2164
+ * Recupera l'ultimo checkpoint di una sessione specifica.
2165
+ */
2166
+ async getCheckpoint(sessionId) {
2167
+ return getLatestCheckpoint(this.db.db, sessionId);
2168
+ }
2169
+ /**
2170
+ * Recupera l'ultimo checkpoint per il progetto corrente.
2171
+ * Utile per resume automatico senza specificare session ID.
2172
+ */
2173
+ async getLatestProjectCheckpoint() {
2174
+ return getLatestCheckpointByProject(this.db.db, this.project);
2175
+ }
2176
+ /**
2177
+ * Genera un report di attività per il progetto corrente.
2178
+ * Aggrega osservazioni, sessioni, summaries e file per un periodo temporale.
2179
+ */
2180
+ async generateReport(options) {
2181
+ const now = /* @__PURE__ */ new Date();
2182
+ let startEpoch;
2183
+ let endEpoch = now.getTime();
2184
+ if (options?.startDate && options?.endDate) {
2185
+ startEpoch = options.startDate.getTime();
2186
+ endEpoch = options.endDate.getTime();
2187
+ } else {
2188
+ const period = options?.period || "weekly";
2189
+ const daysBack = period === "monthly" ? 30 : 7;
2190
+ startEpoch = endEpoch - daysBack * 24 * 60 * 60 * 1e3;
2191
+ }
2192
+ return getReportData(this.db.db, this.project, startEpoch, endEpoch);
1080
2193
  }
1081
2194
  /**
1082
2195
  * Getter for direct database access (for API routes)
@@ -1095,9 +2208,172 @@ function createKiroMemory(config) {
1095
2208
  return new KiroMemorySDK(config);
1096
2209
  }
1097
2210
 
2211
+ // src/services/report-formatter.ts
2212
+ function formatReportText(data) {
2213
+ const lines = [];
2214
+ lines.push("");
2215
+ lines.push(` \x1B[36m\u2550\u2550\u2550 Kiro Memory Report \u2014 ${data.period.label} \u2550\u2550\u2550\x1B[0m`);
2216
+ lines.push(` \x1B[2m${data.period.start} \u2192 ${data.period.end} (${data.period.days} days)\x1B[0m`);
2217
+ lines.push("");
2218
+ lines.push(` \x1B[1mOverview\x1B[0m`);
2219
+ lines.push(` Observations: ${data.overview.observations}`);
2220
+ lines.push(` Summaries: ${data.overview.summaries}`);
2221
+ lines.push(` Sessions: ${data.overview.sessions}`);
2222
+ lines.push(` Prompts: ${data.overview.prompts}`);
2223
+ lines.push(` Knowledge: ${data.overview.knowledgeCount}`);
2224
+ if (data.overview.staleCount > 0) {
2225
+ lines.push(` Stale: ${data.overview.staleCount}`);
2226
+ }
2227
+ lines.push("");
2228
+ if (data.sessionStats.total > 0) {
2229
+ const completionPct = data.sessionStats.total > 0 ? Math.round(data.sessionStats.completed / data.sessionStats.total * 100) : 0;
2230
+ lines.push(` \x1B[1mSessions\x1B[0m`);
2231
+ lines.push(` Total: ${data.sessionStats.total} | Completed: ${data.sessionStats.completed} (${completionPct}%)`);
2232
+ if (data.sessionStats.avgDurationMinutes > 0) {
2233
+ lines.push(` Avg duration: ${data.sessionStats.avgDurationMinutes} min`);
2234
+ }
2235
+ lines.push("");
2236
+ }
2237
+ if (data.timeline.length > 0) {
2238
+ lines.push(` \x1B[1mTimeline\x1B[0m`);
2239
+ const maxCount = Math.max(...data.timeline.map((t) => t.count));
2240
+ const maxBarLen = 30;
2241
+ for (const entry of data.timeline) {
2242
+ const barLen = maxCount > 0 ? Math.round(entry.count / maxCount * maxBarLen) : 0;
2243
+ const bar = "\x1B[32m" + "\u2593".repeat(barLen) + "\x1B[0m";
2244
+ const dayShort = entry.day.substring(5);
2245
+ lines.push(` ${dayShort} ${bar} ${entry.count}`);
2246
+ }
2247
+ lines.push("");
2248
+ }
2249
+ if (data.typeDistribution.length > 0) {
2250
+ lines.push(` \x1B[1mBy Type\x1B[0m`);
2251
+ for (const entry of data.typeDistribution) {
2252
+ lines.push(` ${entry.type.padEnd(16)} ${entry.count}`);
2253
+ }
2254
+ lines.push("");
2255
+ }
2256
+ if (data.topLearnings.length > 0) {
2257
+ lines.push(` \x1B[1mKey Learnings\x1B[0m`);
2258
+ for (const learning of data.topLearnings) {
2259
+ lines.push(` - ${learning}`);
2260
+ }
2261
+ lines.push("");
2262
+ }
2263
+ if (data.completedTasks.length > 0) {
2264
+ lines.push(` \x1B[1mCompleted\x1B[0m`);
2265
+ for (const task of data.completedTasks) {
2266
+ lines.push(` - ${task}`);
2267
+ }
2268
+ lines.push("");
2269
+ }
2270
+ if (data.nextSteps.length > 0) {
2271
+ lines.push(` \x1B[1mNext Steps\x1B[0m`);
2272
+ for (const step of data.nextSteps) {
2273
+ lines.push(` - ${step}`);
2274
+ }
2275
+ lines.push("");
2276
+ }
2277
+ if (data.fileHotspots.length > 0) {
2278
+ lines.push(` \x1B[1mFile Hotspots\x1B[0m`);
2279
+ for (const entry of data.fileHotspots.slice(0, 10)) {
2280
+ lines.push(` ${entry.file} (${entry.count}x)`);
2281
+ }
2282
+ lines.push("");
2283
+ }
2284
+ return lines.join("\n");
2285
+ }
2286
+ function formatReportMarkdown(data) {
2287
+ const lines = [];
2288
+ lines.push(`# Kiro Memory Report \u2014 ${data.period.label}`);
2289
+ lines.push("");
2290
+ lines.push(`**Period**: ${data.period.start} \u2192 ${data.period.end} (${data.period.days} days)`);
2291
+ lines.push("");
2292
+ lines.push("## Overview");
2293
+ lines.push("");
2294
+ lines.push("| Metric | Count |");
2295
+ lines.push("|--------|------:|");
2296
+ lines.push(`| Observations | ${data.overview.observations} |`);
2297
+ lines.push(`| Summaries | ${data.overview.summaries} |`);
2298
+ lines.push(`| Sessions | ${data.overview.sessions} |`);
2299
+ lines.push(`| Prompts | ${data.overview.prompts} |`);
2300
+ lines.push(`| Knowledge items | ${data.overview.knowledgeCount} |`);
2301
+ if (data.overview.staleCount > 0) {
2302
+ lines.push(`| Stale observations | ${data.overview.staleCount} |`);
2303
+ }
2304
+ lines.push("");
2305
+ if (data.sessionStats.total > 0) {
2306
+ const completionPct = Math.round(data.sessionStats.completed / data.sessionStats.total * 100);
2307
+ lines.push("## Sessions");
2308
+ lines.push("");
2309
+ lines.push(`- **Total**: ${data.sessionStats.total}`);
2310
+ lines.push(`- **Completed**: ${data.sessionStats.completed} (${completionPct}%)`);
2311
+ if (data.sessionStats.avgDurationMinutes > 0) {
2312
+ lines.push(`- **Avg duration**: ${data.sessionStats.avgDurationMinutes} min`);
2313
+ }
2314
+ lines.push("");
2315
+ }
2316
+ if (data.timeline.length > 0) {
2317
+ lines.push("## Activity Timeline");
2318
+ lines.push("");
2319
+ lines.push("| Date | Observations |");
2320
+ lines.push("|------|------------:|");
2321
+ for (const entry of data.timeline) {
2322
+ lines.push(`| ${entry.day} | ${entry.count} |`);
2323
+ }
2324
+ lines.push("");
2325
+ }
2326
+ if (data.typeDistribution.length > 0) {
2327
+ lines.push("## Observation Types");
2328
+ lines.push("");
2329
+ for (const entry of data.typeDistribution) {
2330
+ lines.push(`- **${entry.type}**: ${entry.count}`);
2331
+ }
2332
+ lines.push("");
2333
+ }
2334
+ if (data.topLearnings.length > 0) {
2335
+ lines.push("## Key Learnings");
2336
+ lines.push("");
2337
+ for (const learning of data.topLearnings) {
2338
+ lines.push(`- ${learning}`);
2339
+ }
2340
+ lines.push("");
2341
+ }
2342
+ if (data.completedTasks.length > 0) {
2343
+ lines.push("## Completed");
2344
+ lines.push("");
2345
+ for (const task of data.completedTasks) {
2346
+ lines.push(`- ${task}`);
2347
+ }
2348
+ lines.push("");
2349
+ }
2350
+ if (data.nextSteps.length > 0) {
2351
+ lines.push("## Next Steps");
2352
+ lines.push("");
2353
+ for (const step of data.nextSteps) {
2354
+ lines.push(`- ${step}`);
2355
+ }
2356
+ lines.push("");
2357
+ }
2358
+ if (data.fileHotspots.length > 0) {
2359
+ lines.push("## File Hotspots");
2360
+ lines.push("");
2361
+ lines.push("| File | Modifications |");
2362
+ lines.push("|------|-------------:|");
2363
+ for (const entry of data.fileHotspots.slice(0, 10)) {
2364
+ lines.push(`| \`${entry.file}\` | ${entry.count} |`);
2365
+ }
2366
+ lines.push("");
2367
+ }
2368
+ return lines.join("\n");
2369
+ }
2370
+ function formatReportJson(data) {
2371
+ return JSON.stringify(data, null, 2);
2372
+ }
2373
+
1098
2374
  // src/cli/contextkit.ts
1099
2375
  import { execSync } from "child_process";
1100
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync, appendFileSync as appendFileSync2 } from "fs";
2376
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync, appendFileSync as appendFileSync2 } from "fs";
1101
2377
  import { join as join3, dirname as dirname2 } from "path";
1102
2378
  import { homedir as homedir3, platform, release } from "os";
1103
2379
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -1158,7 +2434,7 @@ function isWSL() {
1158
2434
  try {
1159
2435
  const rel = release().toLowerCase();
1160
2436
  if (rel.includes("microsoft") || rel.includes("wsl")) return true;
1161
- if (existsSync3("/proc/version")) {
2437
+ if (existsSync4("/proc/version")) {
1162
2438
  const proc = readFileSync2("/proc/version", "utf8").toLowerCase();
1163
2439
  return proc.includes("microsoft") || proc.includes("wsl");
1164
2440
  }
@@ -1332,10 +2608,11 @@ async function tryAutoFix(failedChecks) {
1332
2608
  try {
1333
2609
  const npmGlobalDir = join3(homedir3(), ".npm-global");
1334
2610
  mkdirSync3(npmGlobalDir, { recursive: true });
1335
- execSync(`npm config set prefix "${npmGlobalDir}"`, { stdio: "ignore" });
2611
+ const { spawnSync: spawnNpmConfig } = __require("child_process");
2612
+ spawnNpmConfig("npm", ["config", "set", "prefix", npmGlobalDir], { stdio: "ignore" });
1336
2613
  const exportLine = 'export PATH="$HOME/.npm-global/bin:$PATH"';
1337
2614
  let alreadyInRc = false;
1338
- if (existsSync3(rcFile)) {
2615
+ if (existsSync4(rcFile)) {
1339
2616
  const content = readFileSync2(rcFile, "utf8");
1340
2617
  alreadyInRc = content.includes(".npm-global/bin");
1341
2618
  }
@@ -1358,7 +2635,7 @@ ${exportLine}
1358
2635
  console.log("\n Fixing npm binary (installing nvm + Node.js 22)...");
1359
2636
  const nvmDir = join3(homedir3(), ".nvm");
1360
2637
  try {
1361
- if (existsSync3(nvmDir)) {
2638
+ if (existsSync4(nvmDir)) {
1362
2639
  console.log(` nvm already installed at ${nvmDir}`);
1363
2640
  } else {
1364
2641
  console.log(" Downloading nvm...");
@@ -1403,139 +2680,477 @@ ${exportLine}
1403
2680
  if (sqliteCheck) {
1404
2681
  console.log("\n Rebuilding better-sqlite3...");
1405
2682
  try {
1406
- const globalDir = execSync("npm prefix -g", { encoding: "utf8" }).trim();
1407
- const sqlitePkg = join3(globalDir, "lib", "node_modules", "kiro-memory");
1408
- if (existsSync3(sqlitePkg)) {
1409
- execSync(`cd "${sqlitePkg}" && npm rebuild better-sqlite3`, {
1410
- stdio: "inherit",
1411
- timeout: 6e4
1412
- });
1413
- } else {
1414
- execSync("npm rebuild better-sqlite3", { stdio: "inherit", timeout: 6e4 });
1415
- }
1416
- console.log(` \x1B[32m\u2713\x1B[0m better-sqlite3 rebuilt`);
1417
- anyFixed = true;
1418
- } catch (err) {
1419
- console.log(` \x1B[31m\u2717\x1B[0m Could not rebuild: ${err.message}`);
1420
- console.log(" Try: npm install -g kiro-memory --build-from-source");
2683
+ const { spawnSync: spawnRebuild } = __require("child_process");
2684
+ const globalDirResult = spawnRebuild("npm", ["prefix", "-g"], { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"] });
2685
+ const globalDir = (globalDirResult.stdout || "").trim();
2686
+ const sqlitePkg = join3(globalDir, "lib", "node_modules", "kiro-memory");
2687
+ if (existsSync4(sqlitePkg)) {
2688
+ spawnRebuild("npm", ["rebuild", "better-sqlite3"], {
2689
+ cwd: sqlitePkg,
2690
+ stdio: "inherit",
2691
+ timeout: 6e4
2692
+ });
2693
+ } else {
2694
+ spawnRebuild("npm", ["rebuild", "better-sqlite3"], { stdio: "inherit", timeout: 6e4 });
2695
+ }
2696
+ console.log(` \x1B[32m\u2713\x1B[0m better-sqlite3 rebuilt`);
2697
+ anyFixed = true;
2698
+ } catch (err) {
2699
+ console.log(` \x1B[31m\u2717\x1B[0m Could not rebuild: ${err.message}`);
2700
+ console.log(" Try: npm install -g kiro-memory --build-from-source");
2701
+ }
2702
+ }
2703
+ console.log("");
2704
+ return { fixed: anyFixed, needsRestart };
2705
+ }
2706
+ async function installKiro() {
2707
+ console.log("\n=== Kiro Memory - Installation ===\n");
2708
+ console.log("[1/4] Running environment checks...");
2709
+ let checks = runEnvironmentChecks();
2710
+ let { hasErrors } = printChecks(checks);
2711
+ if (hasErrors) {
2712
+ const { fixed, needsRestart } = await tryAutoFix(checks);
2713
+ if (needsRestart) {
2714
+ console.log(" \x1B[33m\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\x1B[0m");
2715
+ console.log(" \x1B[33m\u2502\x1B[0m Node.js was installed via nvm. To activate it: \x1B[33m\u2502\x1B[0m");
2716
+ console.log(" \x1B[33m\u2502\x1B[0m \x1B[33m\u2502\x1B[0m");
2717
+ console.log(" \x1B[33m\u2502\x1B[0m 1. Close and reopen your terminal \x1B[33m\u2502\x1B[0m");
2718
+ console.log(" \x1B[33m\u2502\x1B[0m 2. Run: \x1B[1mnpm install -g kiro-memory\x1B[0m \x1B[33m\u2502\x1B[0m");
2719
+ console.log(" \x1B[33m\u2502\x1B[0m 3. Run: \x1B[1mkiro-memory install\x1B[0m \x1B[33m\u2502\x1B[0m");
2720
+ console.log(" \x1B[33m\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\x1B[0m\n");
2721
+ process.exit(0);
2722
+ }
2723
+ if (fixed) {
2724
+ console.log(" Re-running checks...\n");
2725
+ checks = runEnvironmentChecks();
2726
+ ({ hasErrors } = printChecks(checks));
2727
+ }
2728
+ if (hasErrors) {
2729
+ console.log("\x1B[31mInstallation aborted.\x1B[0m Fix the remaining issues and retry.");
2730
+ console.log("After fixing, run: kiro-memory install\n");
2731
+ process.exit(1);
2732
+ }
2733
+ }
2734
+ const distDir = DIST_DIR;
2735
+ const kiroDir = process.env.KIRO_CONFIG_DIR || join3(homedir3(), ".kiro");
2736
+ const agentsDir = join3(kiroDir, "agents");
2737
+ const settingsDir = join3(kiroDir, "settings");
2738
+ const steeringDir = join3(kiroDir, "steering");
2739
+ const dataDir = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR || join3(homedir3(), ".contextkit");
2740
+ console.log("[2/4] Installing Kiro configuration...\n");
2741
+ for (const dir of [agentsDir, settingsDir, steeringDir, dataDir]) {
2742
+ mkdirSync3(dir, { recursive: true });
2743
+ }
2744
+ const agentConfig = AGENT_TEMPLATE.replace(/__DIST_DIR__/g, distDir);
2745
+ const agentDestPath = join3(agentsDir, "kiro-memory.json");
2746
+ writeFileSync(agentDestPath, agentConfig, "utf8");
2747
+ console.log(` \u2192 Agent config: ${agentDestPath}`);
2748
+ const mcpFilePath = join3(settingsDir, "mcp.json");
2749
+ let mcpConfig = { mcpServers: {} };
2750
+ if (existsSync4(mcpFilePath)) {
2751
+ try {
2752
+ mcpConfig = JSON.parse(readFileSync2(mcpFilePath, "utf8"));
2753
+ if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
2754
+ } catch {
2755
+ }
2756
+ }
2757
+ mcpConfig.mcpServers["kiro-memory"] = {
2758
+ command: "node",
2759
+ args: [join3(distDir, "servers", "mcp-server.js")]
2760
+ };
2761
+ writeFileSync(mcpFilePath, JSON.stringify(mcpConfig, null, 2), "utf8");
2762
+ console.log(` \u2192 MCP config: ${mcpFilePath}`);
2763
+ const steeringDestPath = join3(steeringDir, "kiro-memory.md");
2764
+ writeFileSync(steeringDestPath, STEERING_CONTENT, "utf8");
2765
+ console.log(` \u2192 Steering: ${steeringDestPath}`);
2766
+ console.log(` \u2192 Data dir: ${dataDir}`);
2767
+ console.log("\n[3/4] Shell alias setup\n");
2768
+ const { rcFile } = detectShellRc();
2769
+ const aliasLine = 'alias kiro="kiro-cli --agent kiro-memory"';
2770
+ let aliasAlreadySet = false;
2771
+ if (existsSync4(rcFile)) {
2772
+ const rcContent = readFileSync2(rcFile, "utf8");
2773
+ aliasAlreadySet = rcContent.includes("alias kiro=") && rcContent.includes("kiro-memory");
2774
+ }
2775
+ if (aliasAlreadySet) {
2776
+ console.log(` \x1B[32m\u2713\x1B[0m Alias already configured in ${rcFile}`);
2777
+ } else {
2778
+ console.log(" \x1B[36m\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\x1B[0m");
2779
+ console.log(" \x1B[36m\u2502\x1B[0m Without an alias, you must type every time: \x1B[36m\u2502\x1B[0m");
2780
+ console.log(" \x1B[36m\u2502\x1B[0m \x1B[2mkiro-cli --agent kiro-memory\x1B[0m \x1B[36m\u2502\x1B[0m");
2781
+ console.log(" \x1B[36m\u2502\x1B[0m \x1B[36m\u2502\x1B[0m");
2782
+ console.log(" \x1B[36m\u2502\x1B[0m With the alias, just type: \x1B[36m\u2502\x1B[0m");
2783
+ console.log(" \x1B[36m\u2502\x1B[0m \x1B[1m\x1B[32mkiro\x1B[0m \x1B[36m\u2502\x1B[0m");
2784
+ console.log(" \x1B[36m\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\x1B[0m");
2785
+ console.log("");
2786
+ const answer = await askUser(` Add alias to ${rcFile}? [Y/n] `);
2787
+ if (answer === "" || answer === "y" || answer === "yes") {
2788
+ try {
2789
+ appendFileSync2(rcFile, `
2790
+ # Kiro Memory \u2014 persistent memory alias
2791
+ ${aliasLine}
2792
+ `);
2793
+ console.log(`
2794
+ \x1B[32m\u2713\x1B[0m Alias added to ${rcFile}`);
2795
+ console.log(` \x1B[33m\u2192\x1B[0m Run \x1B[1msource ${rcFile}\x1B[0m or open a new terminal to activate it.`);
2796
+ } catch (err) {
2797
+ console.log(`
2798
+ \x1B[31m\u2717\x1B[0m Could not write to ${rcFile}: ${err.message}`);
2799
+ console.log(` \x1B[33m\u2192\x1B[0m Add manually: ${aliasLine}`);
2800
+ }
2801
+ } else {
2802
+ console.log(`
2803
+ Skipped. You can add it manually later:`);
2804
+ console.log(` echo '${aliasLine}' >> ${rcFile}`);
2805
+ }
2806
+ }
2807
+ console.log("\n[4/4] Done!\n");
2808
+ console.log(" \x1B[32m\u2550\u2550\u2550 Installation complete! \u2550\u2550\u2550\x1B[0m\n");
2809
+ console.log(" Start Kiro with memory:");
2810
+ if (aliasAlreadySet) {
2811
+ console.log(" \x1B[1mkiro\x1B[0m");
2812
+ } else {
2813
+ console.log(" \x1B[1mkiro-cli --agent kiro-memory\x1B[0m");
2814
+ }
2815
+ console.log("");
2816
+ console.log(" The worker starts automatically when a Kiro session begins.");
2817
+ console.log(` Web dashboard: \x1B[4mhttp://localhost:3001\x1B[0m
2818
+ `);
2819
+ }
2820
+ var CLAUDE_CODE_STEERING = `# Kiro Memory - Persistent Cross-Session Memory
2821
+
2822
+ You have access to Kiro Memory, a persistent cross-session memory system that remembers context across sessions.
2823
+
2824
+ ## Available MCP Tools
2825
+
2826
+ ### kiro-memory/search
2827
+ Search previous session memory. Use when:
2828
+ - The user mentions past work or previous sessions
2829
+ - You need context on previous decisions
2830
+ - You want to check if a problem was already addressed
2831
+
2832
+ ### kiro-memory/get_context
2833
+ Retrieve recent context for the current project. Use at the start of complex tasks.
2834
+
2835
+ ### kiro-memory/timeline
2836
+ Show chronological context around an observation. Use to understand sequences of events.
2837
+
2838
+ ### kiro-memory/get_observations
2839
+ Retrieve full details of specific observations by ID. Use after search to drill down.
2840
+
2841
+ ## Behavior
2842
+
2843
+ - Previous session context is automatically injected at startup via hooks
2844
+ - Your actions (files written, commands run, searches) are tracked automatically
2845
+ - A summary is generated at the end of each session
2846
+ - No manual saving needed: the system is fully automatic
2847
+ `;
2848
+ async function installClaudeCode() {
2849
+ console.log("\n=== Kiro Memory - Claude Code Installation ===\n");
2850
+ console.log("[1/3] Running environment checks...");
2851
+ const checks = runEnvironmentChecks();
2852
+ const { hasErrors } = printChecks(checks);
2853
+ if (hasErrors) {
2854
+ const { fixed, needsRestart } = await tryAutoFix(checks);
2855
+ if (needsRestart) {
2856
+ console.log(" \x1B[33mRestart your terminal and re-run: kiro-memory install --claude-code\x1B[0m\n");
2857
+ process.exit(0);
2858
+ }
2859
+ if (fixed) {
2860
+ console.log(" Re-running checks...\n");
2861
+ const reChecks = runEnvironmentChecks();
2862
+ const reResult = printChecks(reChecks);
2863
+ if (reResult.hasErrors) {
2864
+ console.log("\x1B[31mInstallation aborted.\x1B[0m Fix the remaining issues and retry.\n");
2865
+ process.exit(1);
2866
+ }
2867
+ } else if (hasErrors) {
2868
+ console.log("\x1B[31mInstallation aborted.\x1B[0m Fix the issues and retry.\n");
2869
+ process.exit(1);
2870
+ }
2871
+ }
2872
+ const distDir = DIST_DIR;
2873
+ const claudeDir = join3(homedir3(), ".claude");
2874
+ const dataDir = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR || join3(homedir3(), ".kiro-memory");
2875
+ console.log("[2/3] Installing Claude Code configuration...\n");
2876
+ mkdirSync3(claudeDir, { recursive: true });
2877
+ mkdirSync3(dataDir, { recursive: true });
2878
+ const settingsPath = join3(claudeDir, "settings.json");
2879
+ let settings = {};
2880
+ if (existsSync4(settingsPath)) {
2881
+ try {
2882
+ settings = JSON.parse(readFileSync2(settingsPath, "utf8"));
2883
+ } catch {
2884
+ }
2885
+ }
2886
+ const hookMap = {
2887
+ "SessionStart": { script: "hooks/agentSpawn.js", timeout: 10 },
2888
+ "UserPromptSubmit": { script: "hooks/userPromptSubmit.js", timeout: 5 },
2889
+ "PostToolUse": { script: "hooks/postToolUse.js", timeout: 5 },
2890
+ "Stop": { script: "hooks/stop.js", timeout: 10 }
2891
+ };
2892
+ for (const [event, config] of Object.entries(hookMap)) {
2893
+ const hookEntry = {
2894
+ matcher: "",
2895
+ hooks: [{
2896
+ type: "command",
2897
+ command: `node ${join3(distDir, config.script)}`,
2898
+ timeout: config.timeout
2899
+ }]
2900
+ };
2901
+ if (!settings[event]) {
2902
+ settings[event] = [hookEntry];
2903
+ } else if (Array.isArray(settings[event])) {
2904
+ settings[event] = settings[event].filter(
2905
+ (h) => !h.hooks?.some(
2906
+ (hk) => hk.command?.includes("kiro-memory") || hk.command?.includes("contextkit")
2907
+ )
2908
+ );
2909
+ settings[event].push(hookEntry);
2910
+ }
2911
+ }
2912
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2), "utf8");
2913
+ console.log(` \u2192 Hooks config: ${settingsPath}`);
2914
+ const mcpPath = join3(homedir3(), ".mcp.json");
2915
+ let mcpConfig = {};
2916
+ if (existsSync4(mcpPath)) {
2917
+ try {
2918
+ mcpConfig = JSON.parse(readFileSync2(mcpPath, "utf8"));
2919
+ } catch {
2920
+ }
2921
+ }
2922
+ if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
2923
+ mcpConfig.mcpServers["kiro-memory"] = {
2924
+ command: "node",
2925
+ args: [join3(distDir, "servers", "mcp-server.js")]
2926
+ };
2927
+ writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), "utf8");
2928
+ console.log(` \u2192 MCP config: ${mcpPath}`);
2929
+ const steeringPath = join3(claudeDir, "CLAUDE.md");
2930
+ let existingSteering = "";
2931
+ if (existsSync4(steeringPath)) {
2932
+ existingSteering = readFileSync2(steeringPath, "utf8");
2933
+ }
2934
+ if (!existingSteering.includes("Kiro Memory")) {
2935
+ const separator = existingSteering.length > 0 ? "\n\n---\n\n" : "";
2936
+ writeFileSync(steeringPath, existingSteering + separator + CLAUDE_CODE_STEERING, "utf8");
2937
+ console.log(` \u2192 Steering: ${steeringPath}`);
2938
+ } else {
2939
+ console.log(` \u2192 Steering: ${steeringPath} (already configured)`);
2940
+ }
2941
+ console.log(` \u2192 Data dir: ${dataDir}`);
2942
+ console.log("\n[3/3] Done!\n");
2943
+ console.log(" \x1B[32m\u2550\u2550\u2550 Claude Code integration complete! \u2550\u2550\u2550\x1B[0m\n");
2944
+ console.log(" Memory hooks are now active for Claude Code.");
2945
+ console.log(" Start a new Claude Code session to begin tracking context.\n");
2946
+ console.log(" The worker starts automatically on first session.");
2947
+ console.log(` Web dashboard: \x1B[4mhttp://localhost:3001\x1B[0m
2948
+ `);
2949
+ }
2950
+ async function installCursor() {
2951
+ console.log("\n=== Kiro Memory - Cursor Installation ===\n");
2952
+ console.log("[1/3] Running environment checks...");
2953
+ const checks = runEnvironmentChecks();
2954
+ const { hasErrors } = printChecks(checks);
2955
+ if (hasErrors) {
2956
+ const { fixed, needsRestart } = await tryAutoFix(checks);
2957
+ if (needsRestart) {
2958
+ console.log(" \x1B[33mRestart your terminal and re-run: kiro-memory install --cursor\x1B[0m\n");
2959
+ process.exit(0);
2960
+ }
2961
+ if (fixed) {
2962
+ console.log(" Re-running checks...\n");
2963
+ const reChecks = runEnvironmentChecks();
2964
+ const reResult = printChecks(reChecks);
2965
+ if (reResult.hasErrors) {
2966
+ console.log("\x1B[31mInstallation aborted.\x1B[0m Fix the remaining issues and retry.\n");
2967
+ process.exit(1);
2968
+ }
2969
+ } else if (hasErrors) {
2970
+ console.log("\x1B[31mInstallation aborted.\x1B[0m Fix the issues and retry.\n");
2971
+ process.exit(1);
2972
+ }
2973
+ }
2974
+ const distDir = DIST_DIR;
2975
+ const cursorDir = join3(homedir3(), ".cursor");
2976
+ const dataDir = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR || join3(homedir3(), ".kiro-memory");
2977
+ console.log("[2/3] Installing Cursor configuration...\n");
2978
+ mkdirSync3(cursorDir, { recursive: true });
2979
+ mkdirSync3(dataDir, { recursive: true });
2980
+ const hooksPath = join3(cursorDir, "hooks.json");
2981
+ let hooksConfig = { version: 1, hooks: {} };
2982
+ if (existsSync4(hooksPath)) {
2983
+ try {
2984
+ hooksConfig = JSON.parse(readFileSync2(hooksPath, "utf8"));
2985
+ if (!hooksConfig.hooks) hooksConfig.hooks = {};
2986
+ if (!hooksConfig.version) hooksConfig.version = 1;
2987
+ } catch {
2988
+ }
2989
+ }
2990
+ const cursorHookMap = {
2991
+ "sessionStart": "hooks/agentSpawn.js",
2992
+ "beforeSubmitPrompt": "hooks/userPromptSubmit.js",
2993
+ "afterFileEdit": "hooks/postToolUse.js",
2994
+ "afterShellExecution": "hooks/postToolUse.js",
2995
+ "afterMCPExecution": "hooks/postToolUse.js",
2996
+ "stop": "hooks/stop.js"
2997
+ };
2998
+ for (const [event, script] of Object.entries(cursorHookMap)) {
2999
+ const hookEntry = {
3000
+ command: `node ${join3(distDir, script)}`
3001
+ };
3002
+ if (!hooksConfig.hooks[event]) {
3003
+ hooksConfig.hooks[event] = [hookEntry];
3004
+ } else if (Array.isArray(hooksConfig.hooks[event])) {
3005
+ hooksConfig.hooks[event] = hooksConfig.hooks[event].filter(
3006
+ (h) => !h.command?.includes("kiro-memory") && !h.command?.includes("contextkit")
3007
+ );
3008
+ hooksConfig.hooks[event].push(hookEntry);
3009
+ }
3010
+ }
3011
+ writeFileSync(hooksPath, JSON.stringify(hooksConfig, null, 2), "utf8");
3012
+ console.log(` \u2192 Hooks config: ${hooksPath}`);
3013
+ const mcpPath = join3(cursorDir, "mcp.json");
3014
+ let mcpConfig = {};
3015
+ if (existsSync4(mcpPath)) {
3016
+ try {
3017
+ mcpConfig = JSON.parse(readFileSync2(mcpPath, "utf8"));
3018
+ } catch {
1421
3019
  }
1422
3020
  }
1423
- console.log("");
1424
- return { fixed: anyFixed, needsRestart };
3021
+ if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
3022
+ mcpConfig.mcpServers["kiro-memory"] = {
3023
+ command: "node",
3024
+ args: [join3(distDir, "servers", "mcp-server.js")]
3025
+ };
3026
+ writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), "utf8");
3027
+ console.log(` \u2192 MCP config: ${mcpPath}`);
3028
+ console.log(` \u2192 Data dir: ${dataDir}`);
3029
+ console.log("\n[3/3] Done!\n");
3030
+ console.log(" \x1B[32m\u2550\u2550\u2550 Cursor integration complete! \u2550\u2550\u2550\x1B[0m\n");
3031
+ console.log(" Memory hooks are now active for Cursor IDE.");
3032
+ console.log(" Start a new Cursor Agent session to begin tracking context.\n");
3033
+ console.log(" The worker starts automatically on first session.");
3034
+ console.log(` Web dashboard: \x1B[4mhttp://localhost:3001\x1B[0m
3035
+ `);
1425
3036
  }
1426
- async function installKiro() {
1427
- console.log("\n=== Kiro Memory - Installation ===\n");
1428
- console.log("[1/4] Running environment checks...");
1429
- let checks = runEnvironmentChecks();
1430
- let { hasErrors } = printChecks(checks);
3037
+ async function installWindsurf() {
3038
+ console.log("\n=== Kiro Memory - Windsurf Installation ===\n");
3039
+ console.log("[1/3] Running environment checks...");
3040
+ const checks = runEnvironmentChecks();
3041
+ const { hasErrors } = printChecks(checks);
1431
3042
  if (hasErrors) {
1432
3043
  const { fixed, needsRestart } = await tryAutoFix(checks);
1433
3044
  if (needsRestart) {
1434
- console.log(" \x1B[33m\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\x1B[0m");
1435
- console.log(" \x1B[33m\u2502\x1B[0m Node.js was installed via nvm. To activate it: \x1B[33m\u2502\x1B[0m");
1436
- console.log(" \x1B[33m\u2502\x1B[0m \x1B[33m\u2502\x1B[0m");
1437
- console.log(" \x1B[33m\u2502\x1B[0m 1. Close and reopen your terminal \x1B[33m\u2502\x1B[0m");
1438
- console.log(" \x1B[33m\u2502\x1B[0m 2. Run: \x1B[1mnpm install -g kiro-memory\x1B[0m \x1B[33m\u2502\x1B[0m");
1439
- console.log(" \x1B[33m\u2502\x1B[0m 3. Run: \x1B[1mkiro-memory install\x1B[0m \x1B[33m\u2502\x1B[0m");
1440
- console.log(" \x1B[33m\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\x1B[0m\n");
3045
+ console.log(" \x1B[33mRestart your terminal and re-run: kiro-memory install --windsurf\x1B[0m\n");
1441
3046
  process.exit(0);
1442
3047
  }
1443
3048
  if (fixed) {
1444
3049
  console.log(" Re-running checks...\n");
1445
- checks = runEnvironmentChecks();
1446
- ({ hasErrors } = printChecks(checks));
1447
- }
1448
- if (hasErrors) {
1449
- console.log("\x1B[31mInstallation aborted.\x1B[0m Fix the remaining issues and retry.");
1450
- console.log("After fixing, run: kiro-memory install\n");
3050
+ const reChecks = runEnvironmentChecks();
3051
+ const reResult = printChecks(reChecks);
3052
+ if (reResult.hasErrors) {
3053
+ console.log("\x1B[31mInstallation aborted.\x1B[0m Fix the remaining issues and retry.\n");
3054
+ process.exit(1);
3055
+ }
3056
+ } else if (hasErrors) {
3057
+ console.log("\x1B[31mInstallation aborted.\x1B[0m Fix the issues and retry.\n");
1451
3058
  process.exit(1);
1452
3059
  }
1453
3060
  }
1454
3061
  const distDir = DIST_DIR;
1455
- const kiroDir = process.env.KIRO_CONFIG_DIR || join3(homedir3(), ".kiro");
1456
- const agentsDir = join3(kiroDir, "agents");
1457
- const settingsDir = join3(kiroDir, "settings");
1458
- const steeringDir = join3(kiroDir, "steering");
1459
- const dataDir = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR || join3(homedir3(), ".contextkit");
1460
- console.log("[2/4] Installing Kiro configuration...\n");
1461
- for (const dir of [agentsDir, settingsDir, steeringDir, dataDir]) {
1462
- mkdirSync3(dir, { recursive: true });
1463
- }
1464
- const agentConfig = AGENT_TEMPLATE.replace(/__DIST_DIR__/g, distDir);
1465
- const agentDestPath = join3(agentsDir, "kiro-memory.json");
1466
- writeFileSync(agentDestPath, agentConfig, "utf8");
1467
- console.log(` \u2192 Agent config: ${agentDestPath}`);
1468
- const mcpFilePath = join3(settingsDir, "mcp.json");
1469
- let mcpConfig = { mcpServers: {} };
1470
- if (existsSync3(mcpFilePath)) {
3062
+ const dataDir = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR || join3(homedir3(), ".kiro-memory");
3063
+ console.log("[2/3] Installing Windsurf configuration...\n");
3064
+ mkdirSync3(dataDir, { recursive: true });
3065
+ const windsurfDir = join3(homedir3(), ".codeium", "windsurf");
3066
+ mkdirSync3(windsurfDir, { recursive: true });
3067
+ const mcpPath = join3(windsurfDir, "mcp_config.json");
3068
+ let mcpConfig = {};
3069
+ if (existsSync4(mcpPath)) {
1471
3070
  try {
1472
- mcpConfig = JSON.parse(readFileSync2(mcpFilePath, "utf8"));
1473
- if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
3071
+ mcpConfig = JSON.parse(readFileSync2(mcpPath, "utf8"));
1474
3072
  } catch {
1475
3073
  }
1476
3074
  }
3075
+ if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
1477
3076
  mcpConfig.mcpServers["kiro-memory"] = {
1478
3077
  command: "node",
1479
3078
  args: [join3(distDir, "servers", "mcp-server.js")]
1480
3079
  };
1481
- writeFileSync(mcpFilePath, JSON.stringify(mcpConfig, null, 2), "utf8");
1482
- console.log(` \u2192 MCP config: ${mcpFilePath}`);
1483
- const steeringDestPath = join3(steeringDir, "kiro-memory.md");
1484
- writeFileSync(steeringDestPath, STEERING_CONTENT, "utf8");
1485
- console.log(` \u2192 Steering: ${steeringDestPath}`);
3080
+ writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), "utf8");
3081
+ console.log(` \u2192 MCP config: ${mcpPath}`);
1486
3082
  console.log(` \u2192 Data dir: ${dataDir}`);
1487
- console.log("\n[3/4] Shell alias setup\n");
1488
- const { rcFile } = detectShellRc();
1489
- const aliasLine = 'alias kiro="kiro-cli --agent kiro-memory"';
1490
- let aliasAlreadySet = false;
1491
- if (existsSync3(rcFile)) {
1492
- const rcContent = readFileSync2(rcFile, "utf8");
1493
- aliasAlreadySet = rcContent.includes("alias kiro=") && rcContent.includes("kiro-memory");
1494
- }
1495
- if (aliasAlreadySet) {
1496
- console.log(` \x1B[32m\u2713\x1B[0m Alias already configured in ${rcFile}`);
1497
- } else {
1498
- console.log(" \x1B[36m\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\x1B[0m");
1499
- console.log(" \x1B[36m\u2502\x1B[0m Without an alias, you must type every time: \x1B[36m\u2502\x1B[0m");
1500
- console.log(" \x1B[36m\u2502\x1B[0m \x1B[2mkiro-cli --agent kiro-memory\x1B[0m \x1B[36m\u2502\x1B[0m");
1501
- console.log(" \x1B[36m\u2502\x1B[0m \x1B[36m\u2502\x1B[0m");
1502
- console.log(" \x1B[36m\u2502\x1B[0m With the alias, just type: \x1B[36m\u2502\x1B[0m");
1503
- console.log(" \x1B[36m\u2502\x1B[0m \x1B[1m\x1B[32mkiro\x1B[0m \x1B[36m\u2502\x1B[0m");
1504
- console.log(" \x1B[36m\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\x1B[0m");
1505
- console.log("");
1506
- const answer = await askUser(` Add alias to ${rcFile}? [Y/n] `);
1507
- if (answer === "" || answer === "y" || answer === "yes") {
1508
- try {
1509
- appendFileSync2(rcFile, `
1510
- # Kiro Memory \u2014 persistent memory alias
1511
- ${aliasLine}
3083
+ console.log("\n[3/3] Done!\n");
3084
+ console.log(" \x1B[32m\u2550\u2550\u2550 Windsurf integration complete! \u2550\u2550\u2550\x1B[0m\n");
3085
+ console.log(" Kiro Memory MCP server is now registered for Windsurf.");
3086
+ console.log(" Restart Windsurf to activate the MCP server.\n");
3087
+ console.log(" The worker starts automatically on first use.");
3088
+ console.log(` Web dashboard: \x1B[4mhttp://localhost:3001\x1B[0m
1512
3089
  `);
1513
- console.log(`
1514
- \x1B[32m\u2713\x1B[0m Alias added to ${rcFile}`);
1515
- console.log(` \x1B[33m\u2192\x1B[0m Run \x1B[1msource ${rcFile}\x1B[0m or open a new terminal to activate it.`);
1516
- } catch (err) {
1517
- console.log(`
1518
- \x1B[31m\u2717\x1B[0m Could not write to ${rcFile}: ${err.message}`);
1519
- console.log(` \x1B[33m\u2192\x1B[0m Add manually: ${aliasLine}`);
3090
+ console.log(" \x1B[2mTip: Add a .windsurfrules file to your project with instructions");
3091
+ console.log(" to use the kiro-memory MCP tools for persistent context.\x1B[0m\n");
3092
+ }
3093
+ async function installCline() {
3094
+ console.log("\n=== Kiro Memory - Cline Installation ===\n");
3095
+ console.log("[1/3] Running environment checks...");
3096
+ const checks = runEnvironmentChecks();
3097
+ const { hasErrors } = printChecks(checks);
3098
+ if (hasErrors) {
3099
+ const { fixed, needsRestart } = await tryAutoFix(checks);
3100
+ if (needsRestart) {
3101
+ console.log(" \x1B[33mRestart your terminal and re-run: kiro-memory install --cline\x1B[0m\n");
3102
+ process.exit(0);
3103
+ }
3104
+ if (fixed) {
3105
+ console.log(" Re-running checks...\n");
3106
+ const reChecks = runEnvironmentChecks();
3107
+ const reResult = printChecks(reChecks);
3108
+ if (reResult.hasErrors) {
3109
+ console.log("\x1B[31mInstallation aborted.\x1B[0m Fix the remaining issues and retry.\n");
3110
+ process.exit(1);
1520
3111
  }
1521
- } else {
1522
- console.log(`
1523
- Skipped. You can add it manually later:`);
1524
- console.log(` echo '${aliasLine}' >> ${rcFile}`);
3112
+ } else if (hasErrors) {
3113
+ console.log("\x1B[31mInstallation aborted.\x1B[0m Fix the issues and retry.\n");
3114
+ process.exit(1);
1525
3115
  }
1526
3116
  }
1527
- console.log("\n[4/4] Done!\n");
1528
- console.log(" \x1B[32m\u2550\u2550\u2550 Installation complete! \u2550\u2550\u2550\x1B[0m\n");
1529
- console.log(" Start Kiro with memory:");
1530
- if (aliasAlreadySet) {
1531
- console.log(" \x1B[1mkiro\x1B[0m");
3117
+ const distDir = DIST_DIR;
3118
+ const dataDir = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR || join3(homedir3(), ".kiro-memory");
3119
+ console.log("[2/3] Installing Cline configuration...\n");
3120
+ mkdirSync3(dataDir, { recursive: true });
3121
+ const platform2 = process.platform;
3122
+ let clineSettingsDir;
3123
+ if (platform2 === "darwin") {
3124
+ clineSettingsDir = join3(homedir3(), "Library", "Application Support", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings");
1532
3125
  } else {
1533
- console.log(" \x1B[1mkiro-cli --agent kiro-memory\x1B[0m");
3126
+ clineSettingsDir = join3(homedir3(), ".config", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings");
1534
3127
  }
1535
- console.log("");
1536
- console.log(" The worker starts automatically when a Kiro session begins.");
3128
+ mkdirSync3(clineSettingsDir, { recursive: true });
3129
+ const mcpPath = join3(clineSettingsDir, "cline_mcp_settings.json");
3130
+ let mcpConfig = {};
3131
+ if (existsSync4(mcpPath)) {
3132
+ try {
3133
+ mcpConfig = JSON.parse(readFileSync2(mcpPath, "utf8"));
3134
+ } catch {
3135
+ }
3136
+ }
3137
+ if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
3138
+ mcpConfig.mcpServers["kiro-memory"] = {
3139
+ command: "node",
3140
+ args: [join3(distDir, "servers", "mcp-server.js")]
3141
+ };
3142
+ writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), "utf8");
3143
+ console.log(` \u2192 MCP config: ${mcpPath}`);
3144
+ console.log(` \u2192 Data dir: ${dataDir}`);
3145
+ console.log("\n[3/3] Done!\n");
3146
+ console.log(" \x1B[32m\u2550\u2550\u2550 Cline integration complete! \u2550\u2550\u2550\x1B[0m\n");
3147
+ console.log(" Kiro Memory MCP server is now registered for Cline.");
3148
+ console.log(" Restart VS Code to activate the MCP server in Cline.\n");
3149
+ console.log(" The worker starts automatically on first use.");
1537
3150
  console.log(` Web dashboard: \x1B[4mhttp://localhost:3001\x1B[0m
1538
3151
  `);
3152
+ console.log(" \x1B[2mTip: Add a .clinerules file to your project with instructions");
3153
+ console.log(" to use the kiro-memory MCP tools for persistent context.\x1B[0m\n");
1539
3154
  }
1540
3155
  async function runDoctor() {
1541
3156
  console.log("\n=== Kiro Memory - Diagnostics ===");
@@ -1546,12 +3161,12 @@ async function runDoctor() {
1546
3161
  const dataDir = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR || join3(homedir3(), ".contextkit");
1547
3162
  checks.push({
1548
3163
  name: "Kiro agent config",
1549
- ok: existsSync3(agentPath),
1550
- message: existsSync3(agentPath) ? agentPath : "Not found",
1551
- fix: !existsSync3(agentPath) ? "Run: kiro-memory install" : void 0
3164
+ ok: existsSync4(agentPath),
3165
+ message: existsSync4(agentPath) ? agentPath : "Not found",
3166
+ fix: !existsSync4(agentPath) ? "Run: kiro-memory install" : void 0
1552
3167
  });
1553
3168
  let mcpOk = false;
1554
- if (existsSync3(mcpPath)) {
3169
+ if (existsSync4(mcpPath)) {
1555
3170
  try {
1556
3171
  const mcp = JSON.parse(readFileSync2(mcpPath, "utf8"));
1557
3172
  mcpOk = !!mcp.mcpServers?.["kiro-memory"] || !!mcp.mcpServers?.contextkit;
@@ -1566,13 +3181,120 @@ async function runDoctor() {
1566
3181
  });
1567
3182
  checks.push({
1568
3183
  name: "Data directory",
1569
- ok: existsSync3(dataDir),
1570
- message: existsSync3(dataDir) ? dataDir : "Not created (will be created on first use)"
3184
+ ok: existsSync4(dataDir),
3185
+ message: existsSync4(dataDir) ? dataDir : "Not created (will be created on first use)"
3186
+ });
3187
+ const claudeDir = join3(homedir3(), ".claude");
3188
+ const claudeSettingsPath = join3(claudeDir, "settings.json");
3189
+ let claudeHooksOk = false;
3190
+ if (existsSync4(claudeSettingsPath)) {
3191
+ try {
3192
+ const claudeSettings = JSON.parse(readFileSync2(claudeSettingsPath, "utf8"));
3193
+ claudeHooksOk = !!(claudeSettings?.SessionStart || claudeSettings?.PostToolUse);
3194
+ if (claudeHooksOk) {
3195
+ const allSettings = JSON.stringify(claudeSettings);
3196
+ claudeHooksOk = allSettings.includes("kiro-memory") || allSettings.includes("agentSpawn");
3197
+ }
3198
+ } catch {
3199
+ }
3200
+ }
3201
+ const claudeMcpPath = join3(homedir3(), ".mcp.json");
3202
+ let claudeMcpOk = false;
3203
+ if (existsSync4(claudeMcpPath)) {
3204
+ try {
3205
+ const claudeMcp = JSON.parse(readFileSync2(claudeMcpPath, "utf8"));
3206
+ claudeMcpOk = !!claudeMcp.mcpServers?.["kiro-memory"];
3207
+ } catch {
3208
+ }
3209
+ }
3210
+ checks.push({
3211
+ name: "Claude Code hooks",
3212
+ ok: true,
3213
+ // Non-blocking: installazione opzionale
3214
+ message: claudeHooksOk ? "Configured in ~/.claude/settings.json" : "Not configured (optional: run kiro-memory install --claude-code)"
3215
+ });
3216
+ checks.push({
3217
+ name: "Claude Code MCP",
3218
+ ok: true,
3219
+ // Non-blocking: installazione opzionale
3220
+ message: claudeMcpOk ? "kiro-memory registered in ~/.mcp.json" : "Not configured (optional: run kiro-memory install --claude-code)"
3221
+ });
3222
+ const cursorDir = join3(homedir3(), ".cursor");
3223
+ const cursorHooksPath = join3(cursorDir, "hooks.json");
3224
+ let cursorHooksOk = false;
3225
+ if (existsSync4(cursorHooksPath)) {
3226
+ try {
3227
+ const cursorHooks = JSON.parse(readFileSync2(cursorHooksPath, "utf8"));
3228
+ cursorHooksOk = !!(cursorHooks.hooks?.sessionStart || cursorHooks.hooks?.afterFileEdit);
3229
+ if (cursorHooksOk) {
3230
+ const allHooks = JSON.stringify(cursorHooks.hooks);
3231
+ cursorHooksOk = allHooks.includes("kiro-memory") || allHooks.includes("agentSpawn");
3232
+ }
3233
+ } catch {
3234
+ }
3235
+ }
3236
+ const cursorMcpPath = join3(cursorDir, "mcp.json");
3237
+ let cursorMcpOk = false;
3238
+ if (existsSync4(cursorMcpPath)) {
3239
+ try {
3240
+ const cursorMcp = JSON.parse(readFileSync2(cursorMcpPath, "utf8"));
3241
+ cursorMcpOk = !!cursorMcp.mcpServers?.["kiro-memory"];
3242
+ } catch {
3243
+ }
3244
+ }
3245
+ checks.push({
3246
+ name: "Cursor hooks",
3247
+ ok: true,
3248
+ // Non-blocking: installazione opzionale
3249
+ message: cursorHooksOk ? "Configured in ~/.cursor/hooks.json" : "Not configured (optional: run kiro-memory install --cursor)"
3250
+ });
3251
+ checks.push({
3252
+ name: "Cursor MCP",
3253
+ ok: true,
3254
+ // Non-blocking: installazione opzionale
3255
+ message: cursorMcpOk ? "kiro-memory registered in ~/.cursor/mcp.json" : "Not configured (optional: run kiro-memory install --cursor)"
3256
+ });
3257
+ const windsurfMcpPath = join3(homedir3(), ".codeium", "windsurf", "mcp_config.json");
3258
+ let windsurfMcpOk = false;
3259
+ if (existsSync4(windsurfMcpPath)) {
3260
+ try {
3261
+ const windsurfMcp = JSON.parse(readFileSync2(windsurfMcpPath, "utf8"));
3262
+ windsurfMcpOk = !!windsurfMcp.mcpServers?.["kiro-memory"];
3263
+ } catch {
3264
+ }
3265
+ }
3266
+ checks.push({
3267
+ name: "Windsurf MCP",
3268
+ ok: true,
3269
+ // Non-blocking: installazione opzionale
3270
+ message: windsurfMcpOk ? "kiro-memory registered in ~/.codeium/windsurf/mcp_config.json" : "Not configured (optional: run kiro-memory install --windsurf)"
3271
+ });
3272
+ const clinePlatform = process.platform;
3273
+ let clineSettingsBase;
3274
+ if (clinePlatform === "darwin") {
3275
+ clineSettingsBase = join3(homedir3(), "Library", "Application Support", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings");
3276
+ } else {
3277
+ clineSettingsBase = join3(homedir3(), ".config", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings");
3278
+ }
3279
+ const clineMcpPath = join3(clineSettingsBase, "cline_mcp_settings.json");
3280
+ let clineMcpOk = false;
3281
+ if (existsSync4(clineMcpPath)) {
3282
+ try {
3283
+ const clineMcp = JSON.parse(readFileSync2(clineMcpPath, "utf8"));
3284
+ clineMcpOk = !!clineMcp.mcpServers?.["kiro-memory"];
3285
+ } catch {
3286
+ }
3287
+ }
3288
+ checks.push({
3289
+ name: "Cline MCP",
3290
+ ok: true,
3291
+ // Non-blocking: installazione opzionale
3292
+ message: clineMcpOk ? `kiro-memory registered in cline_mcp_settings.json` : "Not configured (optional: run kiro-memory install --cline)"
1571
3293
  });
1572
3294
  let workerOk = false;
1573
3295
  try {
1574
3296
  const port = process.env.KIRO_MEMORY_WORKER_PORT || "3001";
1575
- execSync(`curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:${port}/api/health`, {
3297
+ execSync(`curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:${port}/health`, {
1576
3298
  timeout: 2e3,
1577
3299
  encoding: "utf8"
1578
3300
  });
@@ -1595,7 +3317,17 @@ async function runDoctor() {
1595
3317
  }
1596
3318
  async function main() {
1597
3319
  if (command === "install") {
1598
- await installKiro();
3320
+ if (args.includes("--claude-code")) {
3321
+ await installClaudeCode();
3322
+ } else if (args.includes("--cursor")) {
3323
+ await installCursor();
3324
+ } else if (args.includes("--windsurf")) {
3325
+ await installWindsurf();
3326
+ } else if (args.includes("--cline")) {
3327
+ await installCline();
3328
+ } else {
3329
+ await installKiro();
3330
+ }
1599
3331
  return;
1600
3332
  }
1601
3333
  if (command === "doctor") {
@@ -1628,6 +3360,27 @@ async function main() {
1628
3360
  case "add-sum":
1629
3361
  await addSummary(sdk, args.slice(1).join(" "));
1630
3362
  break;
3363
+ case "add-knowledge":
3364
+ case "add-k":
3365
+ await addKnowledge(sdk, args[1], args[2], args.slice(3).join(" "));
3366
+ break;
3367
+ case "decay":
3368
+ await handleDecay(sdk, args[1]);
3369
+ break;
3370
+ case "embeddings":
3371
+ case "emb":
3372
+ await handleEmbeddings(sdk, args[1]);
3373
+ break;
3374
+ case "semantic-search":
3375
+ case "sem":
3376
+ await semanticSearchCli(sdk, args[1]);
3377
+ break;
3378
+ case "resume":
3379
+ await resumeSession(sdk, args[1] ? parseInt(args[1]) : void 0);
3380
+ break;
3381
+ case "report":
3382
+ await generateReportCli(sdk, args.slice(1));
3383
+ break;
1631
3384
  case "help":
1632
3385
  case "--help":
1633
3386
  case "-h":
@@ -1755,27 +3508,290 @@ async function addSummary(sdk, content) {
1755
3508
  console.log(`\u2705 Summary stored with ID: ${id}
1756
3509
  `);
1757
3510
  }
3511
+ async function addKnowledge(sdk, knowledgeType, title, content) {
3512
+ const validTypes = ["constraint", "decision", "heuristic", "rejected"];
3513
+ if (!knowledgeType || !validTypes.includes(knowledgeType)) {
3514
+ console.error(`Error: knowledge type must be one of: ${validTypes.join(", ")}`);
3515
+ process.exit(1);
3516
+ }
3517
+ if (!title) {
3518
+ console.error("Error: title is required");
3519
+ process.exit(1);
3520
+ }
3521
+ if (!content) {
3522
+ console.error("Error: content is required");
3523
+ process.exit(1);
3524
+ }
3525
+ const severity = args.find((a) => a.startsWith("--severity="))?.split("=")[1];
3526
+ const alternativesRaw = args.find((a) => a.startsWith("--alternatives="))?.split("=")[1];
3527
+ const alternatives = alternativesRaw ? alternativesRaw.split(",").map((s) => s.trim()) : void 0;
3528
+ const reason = args.find((a) => a.startsWith("--reason="))?.split("=")[1];
3529
+ const context = args.find((a) => a.startsWith("--context="))?.split("=")[1];
3530
+ const confidence = args.find((a) => a.startsWith("--confidence="))?.split("=")[1];
3531
+ const conceptsRaw = args.find((a) => a.startsWith("--concepts="))?.split("=")[1];
3532
+ const concepts = conceptsRaw ? conceptsRaw.split(",").map((s) => s.trim()) : void 0;
3533
+ const filesRaw = args.find((a) => a.startsWith("--files="))?.split("=")[1];
3534
+ const files = filesRaw ? filesRaw.split(",").map((s) => s.trim()) : void 0;
3535
+ const cleanContent = content.split(" ").filter((w) => !w.startsWith("--")).join(" ");
3536
+ const id = await sdk.storeKnowledge({
3537
+ project: sdk.getProject(),
3538
+ knowledgeType,
3539
+ title,
3540
+ content: cleanContent || content,
3541
+ concepts,
3542
+ files,
3543
+ metadata: { severity, alternatives, reason, context, confidence }
3544
+ });
3545
+ console.log(`
3546
+ Knowledge stored successfully.`);
3547
+ console.log(` ID: ${id}`);
3548
+ console.log(` Type: ${knowledgeType}`);
3549
+ console.log(` Title: ${title}
3550
+ `);
3551
+ }
3552
+ async function handleEmbeddings(sdk, subcommand) {
3553
+ switch (subcommand) {
3554
+ case "stats": {
3555
+ const stats = sdk.getEmbeddingStats();
3556
+ console.log("\nEmbedding Statistics:\n");
3557
+ console.log(` Total observations: ${stats.total}`);
3558
+ console.log(` With embeddings: ${stats.embedded}`);
3559
+ console.log(` Coverage: ${stats.percentage}%`);
3560
+ await sdk.initializeEmbeddings();
3561
+ const { getEmbeddingService: getEmbeddingService2 } = await Promise.resolve().then(() => (init_EmbeddingService(), EmbeddingService_exports));
3562
+ const embService = getEmbeddingService2();
3563
+ console.log(` Provider: ${embService.getProvider() || "none"}`);
3564
+ console.log(` Dimensions: ${embService.getDimensions()}`);
3565
+ console.log(` Available: ${embService.isAvailable() ? "yes" : "no"}`);
3566
+ if (stats.percentage < 100 && stats.total > 0) {
3567
+ console.log(`
3568
+ Run 'kiro-memory embeddings backfill' to generate missing embeddings.`);
3569
+ }
3570
+ console.log("");
3571
+ break;
3572
+ }
3573
+ case "backfill": {
3574
+ const batchSize = parseInt(args[2]) || 50;
3575
+ console.log(`
3576
+ Generating embeddings (batch size: ${batchSize})...
3577
+ `);
3578
+ const available = await sdk.initializeEmbeddings();
3579
+ if (!available) {
3580
+ console.log(" No embedding provider available.");
3581
+ console.log(" Install fastembed or @huggingface/transformers:");
3582
+ console.log(" npm install fastembed");
3583
+ console.log(" npm install @huggingface/transformers\n");
3584
+ process.exit(1);
3585
+ }
3586
+ const count = await sdk.backfillEmbeddings(batchSize);
3587
+ console.log(` Generated ${count} embeddings.
3588
+ `);
3589
+ const stats = sdk.getEmbeddingStats();
3590
+ console.log(` Coverage: ${stats.embedded}/${stats.total} (${stats.percentage}%)
3591
+ `);
3592
+ break;
3593
+ }
3594
+ default:
3595
+ console.log("\nUsage: kiro-memory embeddings <subcommand>\n");
3596
+ console.log("Subcommands:");
3597
+ console.log(" stats Show embedding statistics");
3598
+ console.log(" backfill [size] Generate embeddings for observations without them (default: 50)\n");
3599
+ }
3600
+ }
3601
+ async function semanticSearchCli(sdk, query) {
3602
+ if (!query) {
3603
+ console.error("Error: Please provide a search query");
3604
+ process.exit(1);
3605
+ }
3606
+ console.log(`
3607
+ Semantic search: "${query}"...
3608
+ `);
3609
+ await sdk.initializeEmbeddings();
3610
+ const results = await sdk.hybridSearch(query, { limit: 10 });
3611
+ if (results.length === 0) {
3612
+ console.log("No results found.\n");
3613
+ return;
3614
+ }
3615
+ console.log(`Found ${results.length} results:
3616
+ `);
3617
+ results.forEach((r, i) => {
3618
+ const scorePercent = Math.round(r.score * 100);
3619
+ console.log(` ${i + 1}. [${r.source}] ${r.title} (score: ${scorePercent}%)`);
3620
+ if (r.content) {
3621
+ console.log(` ${r.content.substring(0, 150)}${r.content.length > 150 ? "..." : ""}`);
3622
+ }
3623
+ console.log("");
3624
+ });
3625
+ }
3626
+ async function handleDecay(sdk, subcommand) {
3627
+ switch (subcommand) {
3628
+ case "stats": {
3629
+ const stats = await sdk.getDecayStats();
3630
+ console.log("\nDecay Statistics:\n");
3631
+ console.log(` Total observations: ${stats.total}`);
3632
+ console.log(` Stale (file changed): ${stats.stale}`);
3633
+ console.log(` Never accessed: ${stats.neverAccessed}`);
3634
+ console.log(` Recently accessed: ${stats.recentlyAccessed} (last 48h)`);
3635
+ if (stats.total > 0) {
3636
+ const freshPercent = Math.round((stats.total - stats.stale) / stats.total * 100);
3637
+ console.log(` Freshness: ${freshPercent}%`);
3638
+ }
3639
+ console.log("");
3640
+ break;
3641
+ }
3642
+ case "detect-stale": {
3643
+ console.log("\nDetecting stale observations...\n");
3644
+ const count = await sdk.detectStaleObservations();
3645
+ if (count > 0) {
3646
+ console.log(` Found and marked ${count} stale observation(s).`);
3647
+ console.log(` These observations reference files that have been modified since they were recorded.
3648
+ `);
3649
+ } else {
3650
+ console.log(" No stale observations found. All observations are fresh.\n");
3651
+ }
3652
+ break;
3653
+ }
3654
+ case "consolidate": {
3655
+ const dryRun = args.includes("--dry-run");
3656
+ console.log(`
3657
+ ${dryRun ? "[DRY RUN] " : ""}Consolidating duplicate observations...
3658
+ `);
3659
+ const result = await sdk.consolidateObservations({ dryRun });
3660
+ if (result.merged > 0) {
3661
+ console.log(` Merged ${result.merged} group(s), removed ${result.removed} duplicate(s).`);
3662
+ if (dryRun) {
3663
+ console.log(` (Dry run: no changes were made. Remove --dry-run to apply.)`);
3664
+ }
3665
+ } else {
3666
+ console.log(" No duplicate observations found to consolidate.");
3667
+ }
3668
+ console.log("");
3669
+ break;
3670
+ }
3671
+ default:
3672
+ console.log("\nUsage: kiro-memory decay <subcommand>\n");
3673
+ console.log("Subcommands:");
3674
+ console.log(" stats Show decay statistics (stale, never accessed, etc.)");
3675
+ console.log(" detect-stale Detect and mark stale observations (files changed)");
3676
+ console.log(" consolidate [--dry-run] Consolidate duplicate observations\n");
3677
+ }
3678
+ }
3679
+ async function generateReportCli(sdk, cliArgs) {
3680
+ const periodArg = cliArgs.find((a) => a.startsWith("--period="))?.split("=")[1];
3681
+ const formatArg = cliArgs.find((a) => a.startsWith("--format="))?.split("=")[1];
3682
+ const outputArg = cliArgs.find((a) => a.startsWith("--output="))?.split("=")[1];
3683
+ const period = periodArg === "monthly" ? "monthly" : "weekly";
3684
+ const format = formatArg === "md" || formatArg === "markdown" ? "markdown" : formatArg === "json" ? "json" : "text";
3685
+ const data = await sdk.generateReport({ period });
3686
+ let output;
3687
+ switch (format) {
3688
+ case "markdown":
3689
+ output = formatReportMarkdown(data);
3690
+ break;
3691
+ case "json":
3692
+ output = formatReportJson(data);
3693
+ break;
3694
+ default:
3695
+ output = formatReportText(data);
3696
+ }
3697
+ if (outputArg) {
3698
+ writeFileSync(outputArg, output, "utf8");
3699
+ console.log(`
3700
+ Report saved to: ${outputArg}
3701
+ `);
3702
+ } else {
3703
+ console.log(output);
3704
+ }
3705
+ }
3706
+ async function resumeSession(sdk, sessionId) {
3707
+ const checkpoint = sessionId ? await sdk.getCheckpoint(sessionId) : await sdk.getLatestProjectCheckpoint();
3708
+ if (!checkpoint) {
3709
+ console.log("\n No checkpoint found.");
3710
+ if (sessionId) {
3711
+ console.log(` Session ${sessionId} has no checkpoint.`);
3712
+ } else {
3713
+ console.log(` No recent checkpoints for project "${sdk.getProject()}".`);
3714
+ }
3715
+ console.log(" Checkpoints are created automatically at the end of each session.\n");
3716
+ return;
3717
+ }
3718
+ console.log("");
3719
+ console.log(` \x1B[36m\u2550\u2550\u2550 Session Checkpoint \u2550\u2550\u2550\x1B[0m`);
3720
+ console.log(` \x1B[2mProject: ${checkpoint.project} | Session: ${checkpoint.session_id}\x1B[0m`);
3721
+ console.log(` \x1B[2m${new Date(checkpoint.created_at).toLocaleString()}\x1B[0m`);
3722
+ console.log("");
3723
+ console.log(` \x1B[1mTask:\x1B[0m ${checkpoint.task}`);
3724
+ if (checkpoint.progress) {
3725
+ console.log(` \x1B[1mProgress:\x1B[0m ${checkpoint.progress}`);
3726
+ }
3727
+ if (checkpoint.next_steps) {
3728
+ console.log(` \x1B[1mNext Steps:\x1B[0m ${checkpoint.next_steps}`);
3729
+ }
3730
+ if (checkpoint.open_questions) {
3731
+ console.log(` \x1B[1mOpen Questions:\x1B[0m ${checkpoint.open_questions}`);
3732
+ }
3733
+ if (checkpoint.relevant_files) {
3734
+ console.log(` \x1B[1mRelevant Files:\x1B[0m`);
3735
+ const files = checkpoint.relevant_files.split(",").map((f) => f.trim());
3736
+ files.forEach((f) => {
3737
+ console.log(` - ${f}`);
3738
+ });
3739
+ }
3740
+ console.log("");
3741
+ }
1758
3742
  function showHelp() {
1759
3743
  console.log(`Usage: kiro-memory <command> [options]
1760
3744
 
1761
3745
  Setup:
1762
- install Install hooks, MCP server, and agent config into Kiro CLI
3746
+ install Install for Kiro CLI (default)
3747
+ install --claude-code Install hooks and MCP server for Claude Code
3748
+ install --cursor Install hooks and MCP server for Cursor IDE
3749
+ install --windsurf Install MCP server for Windsurf IDE
3750
+ install --cline Install MCP server for Cline (VS Code)
1763
3751
  doctor Run environment diagnostics (checks Node, build tools, WSL, etc.)
1764
3752
 
1765
3753
  Commands:
1766
3754
  context, ctx Show current project context
1767
- search <query> Search across all context
3755
+ resume [session-id] Resume previous session (shows checkpoint)
3756
+ report [options] Generate activity report
3757
+ --period=weekly|monthly Time period (default: weekly)
3758
+ --format=text|md|json Output format (default: text)
3759
+ --output=<file> Write to file instead of stdout
3760
+ search <query> Search across all context (keyword FTS5)
3761
+ semantic-search <query> Hybrid search: vector + keyword (semantic)
1768
3762
  observations [limit] Show recent observations (default: 10)
1769
3763
  summaries [limit] Show recent summaries (default: 5)
1770
3764
  add-observation <title> <content> Add a new observation
1771
3765
  add-summary <content> Add a new summary
3766
+ add-knowledge <type> <title> <content> Store structured knowledge
3767
+ Types: constraint, decision, heuristic, rejected
3768
+ Options: --severity=hard|soft --alternatives=a,b,c --reason=...
3769
+ --context=... --confidence=high|medium|low
3770
+ --concepts=a,b --files=path1,path2
3771
+ embeddings stats Show embedding statistics
3772
+ embeddings backfill [n] Generate embeddings for unprocessed observations
3773
+ decay stats Show decay statistics (stale, never accessed, etc.)
3774
+ decay detect-stale Detect and mark stale observations
3775
+ decay consolidate [--dry-run] Consolidate duplicate observations
1772
3776
  help Show this help message
1773
3777
 
1774
3778
  Examples:
1775
3779
  kiro-memory install
1776
3780
  kiro-memory doctor
1777
3781
  kiro-memory context
3782
+ kiro-memory resume
3783
+ kiro-memory resume 42
3784
+ kiro-memory report
3785
+ kiro-memory report --period=monthly --format=md --output=report.md
1778
3786
  kiro-memory search "authentication"
3787
+ kiro-memory semantic-search "how did I fix the auth bug"
3788
+ kiro-memory add-knowledge constraint "No any in TypeScript" "Never use any type" --severity=hard
3789
+ kiro-memory add-knowledge decision "PostgreSQL over MongoDB" "Chosen for ACID" --alternatives=MongoDB,DynamoDB
3790
+ kiro-memory embeddings stats
3791
+ kiro-memory embeddings backfill 100
3792
+ kiro-memory decay stats
3793
+ kiro-memory decay detect-stale
3794
+ kiro-memory decay consolidate --dry-run
1779
3795
  kiro-memory observations 20
1780
3796
  `);
1781
3797
  }