kiro-memory 1.9.0 → 3.0.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.
Files changed (34) hide show
  1. package/README.md +5 -1
  2. package/package.json +5 -5
  3. package/plugin/dist/cli/contextkit.js +2611 -345
  4. package/plugin/dist/hooks/agentSpawn.js +853 -223
  5. package/plugin/dist/hooks/kiro-hooks.js +841 -211
  6. package/plugin/dist/hooks/postToolUse.js +853 -222
  7. package/plugin/dist/hooks/stop.js +850 -220
  8. package/plugin/dist/hooks/userPromptSubmit.js +848 -216
  9. package/plugin/dist/index.js +843 -340
  10. package/plugin/dist/plugins/github/github-client.js +152 -0
  11. package/plugin/dist/plugins/github/index.js +412 -0
  12. package/plugin/dist/plugins/github/issue-parser.js +54 -0
  13. package/plugin/dist/plugins/slack/formatter.js +90 -0
  14. package/plugin/dist/plugins/slack/index.js +215 -0
  15. package/plugin/dist/sdk/index.js +841 -215
  16. package/plugin/dist/servers/mcp-server.js +4461 -397
  17. package/plugin/dist/services/search/EmbeddingService.js +146 -37
  18. package/plugin/dist/services/search/HybridSearch.js +564 -116
  19. package/plugin/dist/services/search/VectorSearch.js +187 -60
  20. package/plugin/dist/services/search/index.js +565 -254
  21. package/plugin/dist/services/sqlite/Backup.js +416 -0
  22. package/plugin/dist/services/sqlite/Database.js +126 -153
  23. package/plugin/dist/services/sqlite/ImportExport.js +452 -0
  24. package/plugin/dist/services/sqlite/Observations.js +314 -19
  25. package/plugin/dist/services/sqlite/Prompts.js +1 -1
  26. package/plugin/dist/services/sqlite/Search.js +41 -29
  27. package/plugin/dist/services/sqlite/Summaries.js +4 -4
  28. package/plugin/dist/services/sqlite/index.js +1428 -208
  29. package/plugin/dist/viewer.css +1 -0
  30. package/plugin/dist/viewer.html +2 -179
  31. package/plugin/dist/viewer.js +23 -24942
  32. package/plugin/dist/viewer.js.map +7 -0
  33. package/plugin/dist/worker-service.js +427 -5569
  34. package/plugin/dist/worker-service.js.map +7 -0
@@ -15,6 +15,290 @@ var __export = (target, all) => {
15
15
  __defProp(target, name, { get: all[name], enumerable: true });
16
16
  };
17
17
 
18
+ // src/utils/secrets.ts
19
+ function redactSecrets(text) {
20
+ if (!text) return text;
21
+ let redacted = text;
22
+ for (const { pattern } of SECRET_PATTERNS) {
23
+ pattern.lastIndex = 0;
24
+ redacted = redacted.replace(pattern, (match) => {
25
+ const prefix = match.substring(0, Math.min(4, match.length));
26
+ return `${prefix}***REDACTED***`;
27
+ });
28
+ }
29
+ return redacted;
30
+ }
31
+ var SECRET_PATTERNS;
32
+ var init_secrets = __esm({
33
+ "src/utils/secrets.ts"() {
34
+ "use strict";
35
+ SECRET_PATTERNS = [
36
+ // AWS Access Keys (AKIA, ABIA, ACCA, ASIA prefixes + 16 alphanumeric chars)
37
+ { name: "aws-key", pattern: /(?:AKIA|ABIA|ACCA|ASIA)[A-Z0-9]{16}/g },
38
+ // JWT tokens (three base64url segments separated by dots)
39
+ { name: "jwt", pattern: /eyJ[a-zA-Z0-9_-]{10,}\.eyJ[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}/g },
40
+ // Generic API keys in key=value or key: value assignments
41
+ { name: "api-key", pattern: /(?:api[_-]?key|apikey|api[_-]?secret)\s*[:=]\s*['"]?([a-zA-Z0-9_\-]{20,})['"]?/gi },
42
+ // Password/secret/token in variable assignments
43
+ { name: "credential", pattern: /(?:password|passwd|pwd|secret|token|auth[_-]?token|access[_-]?token|bearer)\s*[:=]\s*['"]?([^\s'"]{8,})['"]?/gi },
44
+ // Credentials embedded in URLs (user:pass@host)
45
+ { name: "url-credential", pattern: /(?:https?:\/\/)([^:]+):([^@]+)@/g },
46
+ // PEM-encoded private keys (RSA, EC, DSA, OpenSSH)
47
+ { name: "private-key", pattern: /-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/g },
48
+ // GitHub personal access tokens (ghp_, gho_, ghu_, ghs_, ghr_ prefixes)
49
+ { name: "github-token", pattern: /gh[pousr]_[a-zA-Z0-9]{36,}/g },
50
+ // Slack bot/user/app tokens
51
+ { name: "slack-token", pattern: /xox[bpoas]-[a-zA-Z0-9-]{10,}/g },
52
+ // HTTP Authorization Bearer header values
53
+ { name: "bearer-header", pattern: /\bBearer\s+([a-zA-Z0-9_\-\.]{20,})/g },
54
+ // Generic hex secrets (32+ hex chars after a key/secret/token/password label)
55
+ { name: "hex-secret", pattern: /(?:key|secret|token|password)\s*[:=]\s*['"]?([0-9a-f]{32,})['"]?/gi }
56
+ ];
57
+ }
58
+ });
59
+
60
+ // src/utils/categorizer.ts
61
+ function categorize(input) {
62
+ const scores = /* @__PURE__ */ new Map();
63
+ const searchText = [
64
+ input.title,
65
+ input.text || "",
66
+ input.narrative || "",
67
+ input.concepts || ""
68
+ ].join(" ").toLowerCase();
69
+ const allFiles = [input.filesModified || "", input.filesRead || ""].join(",");
70
+ for (const rule of CATEGORY_RULES) {
71
+ let score = 0;
72
+ for (const kw of rule.keywords) {
73
+ if (searchText.includes(kw.toLowerCase())) {
74
+ score += rule.weight;
75
+ }
76
+ }
77
+ if (rule.types && rule.types.includes(input.type)) {
78
+ score += rule.weight * 2;
79
+ }
80
+ if (rule.filePatterns && allFiles) {
81
+ for (const pattern of rule.filePatterns) {
82
+ if (pattern.test(allFiles)) {
83
+ score += rule.weight;
84
+ }
85
+ }
86
+ }
87
+ if (score > 0) {
88
+ scores.set(rule.category, (scores.get(rule.category) || 0) + score);
89
+ }
90
+ }
91
+ let bestCategory = "general";
92
+ let bestScore = 0;
93
+ for (const [category, score] of scores) {
94
+ if (score > bestScore) {
95
+ bestScore = score;
96
+ bestCategory = category;
97
+ }
98
+ }
99
+ return bestCategory;
100
+ }
101
+ var CATEGORY_RULES;
102
+ var init_categorizer = __esm({
103
+ "src/utils/categorizer.ts"() {
104
+ "use strict";
105
+ CATEGORY_RULES = [
106
+ {
107
+ category: "security",
108
+ keywords: [
109
+ "security",
110
+ "vulnerability",
111
+ "cve",
112
+ "xss",
113
+ "csrf",
114
+ "injection",
115
+ "sanitize",
116
+ "escape",
117
+ "auth",
118
+ "authentication",
119
+ "authorization",
120
+ "permission",
121
+ "helmet",
122
+ "cors",
123
+ "rate-limit",
124
+ "token",
125
+ "encrypt",
126
+ "decrypt",
127
+ "secret",
128
+ "redact",
129
+ "owasp"
130
+ ],
131
+ filePatterns: [/security/i, /auth/i, /secrets?\.ts/i],
132
+ weight: 10
133
+ },
134
+ {
135
+ category: "testing",
136
+ keywords: [
137
+ "test",
138
+ "spec",
139
+ "expect",
140
+ "assert",
141
+ "mock",
142
+ "stub",
143
+ "fixture",
144
+ "coverage",
145
+ "jest",
146
+ "vitest",
147
+ "bun test",
148
+ "unit test",
149
+ "integration test",
150
+ "e2e"
151
+ ],
152
+ types: ["test"],
153
+ filePatterns: [/\.test\./i, /\.spec\./i, /tests?\//i, /__tests__/i],
154
+ weight: 8
155
+ },
156
+ {
157
+ category: "debugging",
158
+ keywords: [
159
+ "debug",
160
+ "fix",
161
+ "bug",
162
+ "error",
163
+ "crash",
164
+ "stacktrace",
165
+ "stack trace",
166
+ "exception",
167
+ "breakpoint",
168
+ "investigate",
169
+ "root cause",
170
+ "troubleshoot",
171
+ "diagnose",
172
+ "bisect",
173
+ "regression"
174
+ ],
175
+ types: ["bugfix"],
176
+ weight: 8
177
+ },
178
+ {
179
+ category: "architecture",
180
+ keywords: [
181
+ "architect",
182
+ "design",
183
+ "pattern",
184
+ "modular",
185
+ "migration",
186
+ "schema",
187
+ "database",
188
+ "api design",
189
+ "abstract",
190
+ "dependency injection",
191
+ "singleton",
192
+ "factory",
193
+ "observer",
194
+ "middleware",
195
+ "pipeline",
196
+ "microservice",
197
+ "monolith"
198
+ ],
199
+ types: ["decision", "constraint"],
200
+ weight: 7
201
+ },
202
+ {
203
+ category: "refactoring",
204
+ keywords: [
205
+ "refactor",
206
+ "rename",
207
+ "extract",
208
+ "inline",
209
+ "move",
210
+ "split",
211
+ "merge",
212
+ "simplify",
213
+ "cleanup",
214
+ "clean up",
215
+ "dead code",
216
+ "consolidate",
217
+ "reorganize",
218
+ "restructure",
219
+ "decouple"
220
+ ],
221
+ weight: 6
222
+ },
223
+ {
224
+ category: "config",
225
+ keywords: [
226
+ "config",
227
+ "configuration",
228
+ "env",
229
+ "environment",
230
+ "dotenv",
231
+ ".env",
232
+ "settings",
233
+ "tsconfig",
234
+ "eslint",
235
+ "prettier",
236
+ "webpack",
237
+ "vite",
238
+ "esbuild",
239
+ "docker",
240
+ "ci/cd",
241
+ "github actions",
242
+ "deploy",
243
+ "build",
244
+ "bundle",
245
+ "package.json"
246
+ ],
247
+ filePatterns: [
248
+ /\.config\./i,
249
+ /\.env/i,
250
+ /tsconfig/i,
251
+ /\.ya?ml/i,
252
+ /Dockerfile/i,
253
+ /docker-compose/i
254
+ ],
255
+ weight: 5
256
+ },
257
+ {
258
+ category: "docs",
259
+ keywords: [
260
+ "document",
261
+ "readme",
262
+ "changelog",
263
+ "jsdoc",
264
+ "comment",
265
+ "explain",
266
+ "guide",
267
+ "tutorial",
268
+ "api doc",
269
+ "openapi",
270
+ "swagger"
271
+ ],
272
+ types: ["docs"],
273
+ filePatterns: [/\.md$/i, /docs?\//i, /readme/i, /changelog/i],
274
+ weight: 5
275
+ },
276
+ {
277
+ category: "feature-dev",
278
+ keywords: [
279
+ "feature",
280
+ "implement",
281
+ "add",
282
+ "create",
283
+ "new",
284
+ "endpoint",
285
+ "component",
286
+ "module",
287
+ "service",
288
+ "handler",
289
+ "route",
290
+ "hook",
291
+ "plugin",
292
+ "integration"
293
+ ],
294
+ types: ["feature", "file-write"],
295
+ weight: 3
296
+ // lowest — generic catch-all for development
297
+ }
298
+ ];
299
+ }
300
+ });
301
+
18
302
  // src/services/sqlite/Observations.ts
19
303
  var Observations_exports = {};
20
304
  __export(Observations_exports, {
@@ -40,11 +324,23 @@ function isDuplicateObservation(db, contentHash, windowMs = 3e4) {
40
324
  }
41
325
  function createObservation(db, memorySessionId, project, type, title, subtitle, text, narrative, facts, concepts, filesRead, filesModified, promptNumber, contentHash = null, discoveryTokens = 0) {
42
326
  const now = /* @__PURE__ */ new Date();
327
+ const safeTitle = redactSecrets(title);
328
+ const safeText = text ? redactSecrets(text) : text;
329
+ const safeNarrative = narrative ? redactSecrets(narrative) : narrative;
330
+ const autoCategory = categorize({
331
+ type,
332
+ title: safeTitle,
333
+ text: safeText,
334
+ narrative: safeNarrative,
335
+ concepts,
336
+ filesModified,
337
+ filesRead
338
+ });
43
339
  const result = db.run(
44
340
  `INSERT INTO observations
45
- (memory_session_id, project, type, title, subtitle, text, narrative, facts, concepts, files_read, files_modified, prompt_number, created_at, created_at_epoch, content_hash, discovery_tokens)
46
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
47
- [memorySessionId, project, type, title, subtitle, text, narrative, facts, concepts, filesRead, filesModified, promptNumber, now.toISOString(), now.getTime(), contentHash, discoveryTokens]
341
+ (memory_session_id, project, type, title, subtitle, text, narrative, facts, concepts, files_read, files_modified, prompt_number, created_at, created_at_epoch, content_hash, discovery_tokens, auto_category)
342
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
343
+ [memorySessionId, project, type, safeTitle, subtitle, safeText, safeNarrative, facts, concepts, filesRead, filesModified, promptNumber, now.toISOString(), now.getTime(), contentHash, discoveryTokens, autoCategory]
48
344
  );
49
345
  return Number(result.lastInsertRowid);
50
346
  }
@@ -56,16 +352,16 @@ function getObservationsBySession(db, memorySessionId) {
56
352
  }
57
353
  function getObservationsByProject(db, project, limit = 100) {
58
354
  const query = db.query(
59
- "SELECT * FROM observations WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ?"
355
+ "SELECT * FROM observations WHERE project = ? ORDER BY created_at_epoch DESC, id DESC LIMIT ?"
60
356
  );
61
357
  return query.all(project, limit);
62
358
  }
63
359
  function searchObservations(db, searchTerm, project) {
64
360
  const sql = project ? `SELECT * FROM observations
65
361
  WHERE project = ? AND (title LIKE ? ESCAPE '\\' OR text LIKE ? ESCAPE '\\' OR narrative LIKE ? ESCAPE '\\')
66
- ORDER BY created_at_epoch DESC` : `SELECT * FROM observations
362
+ ORDER BY created_at_epoch DESC, id DESC` : `SELECT * FROM observations
67
363
  WHERE title LIKE ? ESCAPE '\\' OR text LIKE ? ESCAPE '\\' OR narrative LIKE ? ESCAPE '\\'
68
- ORDER BY created_at_epoch DESC`;
364
+ ORDER BY created_at_epoch DESC, id DESC`;
69
365
  const pattern = `%${escapeLikePattern(searchTerm)}%`;
70
366
  const query = db.query(sql);
71
367
  if (project) {
@@ -98,21 +394,32 @@ function consolidateObservations(db, project, options = {}) {
98
394
  ORDER BY cnt DESC
99
395
  `).all(project, minGroupSize);
100
396
  if (groups.length === 0) return { merged: 0, removed: 0 };
101
- let totalMerged = 0;
102
- let totalRemoved = 0;
397
+ if (options.dryRun) {
398
+ let totalMerged = 0;
399
+ let totalRemoved = 0;
400
+ for (const group of groups) {
401
+ const obsIds = group.ids.split(",").map(Number);
402
+ const placeholders = obsIds.map(() => "?").join(",");
403
+ const count = db.query(
404
+ `SELECT COUNT(*) as cnt FROM observations WHERE id IN (${placeholders})`
405
+ ).get(...obsIds)?.cnt || 0;
406
+ if (count >= minGroupSize) {
407
+ totalMerged += 1;
408
+ totalRemoved += count - 1;
409
+ }
410
+ }
411
+ return { merged: totalMerged, removed: totalRemoved };
412
+ }
103
413
  const runConsolidation = db.transaction(() => {
414
+ let merged = 0;
415
+ let removed = 0;
104
416
  for (const group of groups) {
105
417
  const obsIds = group.ids.split(",").map(Number);
106
418
  const placeholders = obsIds.map(() => "?").join(",");
107
419
  const observations = db.query(
108
- `SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC`
420
+ `SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC, id DESC`
109
421
  ).all(...obsIds);
110
422
  if (observations.length < minGroupSize) continue;
111
- if (options.dryRun) {
112
- totalMerged += 1;
113
- totalRemoved += observations.length - 1;
114
- continue;
115
- }
116
423
  const keeper = observations[0];
117
424
  const others = observations.slice(1);
118
425
  const uniqueTexts = /* @__PURE__ */ new Set();
@@ -125,22 +432,24 @@ function consolidateObservations(db, project, options = {}) {
125
432
  const consolidatedText = Array.from(uniqueTexts).join("\n---\n").substring(0, 1e5);
126
433
  db.run(
127
434
  "UPDATE observations SET text = ?, title = ? WHERE id = ?",
128
- [consolidatedText, `[consolidato x${observations.length}] ${keeper.title}`, keeper.id]
435
+ [consolidatedText, `[consolidated x${observations.length}] ${keeper.title}`, keeper.id]
129
436
  );
130
437
  const removeIds = others.map((o) => o.id);
131
438
  const removePlaceholders = removeIds.map(() => "?").join(",");
132
439
  db.run(`DELETE FROM observations WHERE id IN (${removePlaceholders})`, removeIds);
133
440
  db.run(`DELETE FROM observation_embeddings WHERE observation_id IN (${removePlaceholders})`, removeIds);
134
- totalMerged += 1;
135
- totalRemoved += removeIds.length;
441
+ merged += 1;
442
+ removed += removeIds.length;
136
443
  }
444
+ return { merged, removed };
137
445
  });
138
- runConsolidation();
139
- return { merged: totalMerged, removed: totalRemoved };
446
+ return runConsolidation();
140
447
  }
141
448
  var init_Observations = __esm({
142
449
  "src/services/sqlite/Observations.ts"() {
143
450
  "use strict";
451
+ init_secrets();
452
+ init_categorizer();
144
453
  }
145
454
  });
146
455
 
@@ -163,7 +472,7 @@ function escapeLikePattern3(input) {
163
472
  }
164
473
  function sanitizeFTS5Query(query) {
165
474
  const trimmed = query.length > 1e4 ? query.substring(0, 1e4) : query;
166
- const terms = trimmed.replace(/[""]/g, "").split(/\s+/).filter((t) => t.length > 0).slice(0, 100).map((t) => `"${t}"`);
475
+ const terms = trimmed.replace(/[""\u0022]/g, "").split(/\s+/).filter((t) => t.length > 0).slice(0, 100).map((t) => `"${t}"`);
167
476
  return terms.join(" ");
168
477
  }
169
478
  function searchObservationsFTS(db, query, filters = {}) {
@@ -260,7 +569,7 @@ function searchObservationsLIKE(db, query, filters = {}) {
260
569
  sql += " AND created_at_epoch <= ?";
261
570
  params.push(filters.dateEnd);
262
571
  }
263
- sql += " ORDER BY created_at_epoch DESC LIMIT ?";
572
+ sql += " ORDER BY created_at_epoch DESC, id DESC LIMIT ?";
264
573
  params.push(limit);
265
574
  const stmt = db.query(sql);
266
575
  return stmt.all(...params);
@@ -285,7 +594,7 @@ function searchSummariesFiltered(db, query, filters = {}) {
285
594
  sql += " AND created_at_epoch <= ?";
286
595
  params.push(filters.dateEnd);
287
596
  }
288
- sql += " ORDER BY created_at_epoch DESC LIMIT ?";
597
+ sql += " ORDER BY created_at_epoch DESC, id DESC LIMIT ?";
289
598
  params.push(limit);
290
599
  const stmt = db.query(sql);
291
600
  return stmt.all(...params);
@@ -295,7 +604,7 @@ function getObservationsByIds(db, ids) {
295
604
  const validIds = ids.filter((id) => typeof id === "number" && Number.isInteger(id) && id > 0).slice(0, 500);
296
605
  if (validIds.length === 0) return [];
297
606
  const placeholders = validIds.map(() => "?").join(",");
298
- const sql = `SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC`;
607
+ const sql = `SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC, id DESC`;
299
608
  const stmt = db.query(sql);
300
609
  return stmt.all(...validIds);
301
610
  }
@@ -307,11 +616,11 @@ function getTimeline(db, anchorId, depthBefore = 5, depthAfter = 5) {
307
616
  const beforeStmt = db.query(`
308
617
  SELECT id, 'observation' as type, title, text as content, project, created_at, created_at_epoch
309
618
  FROM observations
310
- WHERE created_at_epoch < ?
311
- ORDER BY created_at_epoch DESC
619
+ WHERE (created_at_epoch < ? OR (created_at_epoch = ? AND id < ?))
620
+ ORDER BY created_at_epoch DESC, id DESC
312
621
  LIMIT ?
313
622
  `);
314
- const before = beforeStmt.all(anchorEpoch, depthBefore).reverse();
623
+ const before = beforeStmt.all(anchorEpoch, anchorEpoch, anchorId, depthBefore).reverse();
315
624
  const selfStmt = db.query(`
316
625
  SELECT id, 'observation' as type, title, text as content, project, created_at, created_at_epoch
317
626
  FROM observations WHERE id = ?
@@ -320,34 +629,46 @@ function getTimeline(db, anchorId, depthBefore = 5, depthAfter = 5) {
320
629
  const afterStmt = db.query(`
321
630
  SELECT id, 'observation' as type, title, text as content, project, created_at, created_at_epoch
322
631
  FROM observations
323
- WHERE created_at_epoch > ?
324
- ORDER BY created_at_epoch ASC
632
+ WHERE (created_at_epoch > ? OR (created_at_epoch = ? AND id > ?))
633
+ ORDER BY created_at_epoch ASC, id ASC
325
634
  LIMIT ?
326
635
  `);
327
- const after = afterStmt.all(anchorEpoch, depthAfter);
636
+ const after = afterStmt.all(anchorEpoch, anchorEpoch, anchorId, depthAfter);
328
637
  return [...before, ...self, ...after];
329
638
  }
330
639
  function getProjectStats(db, project) {
331
- const obsStmt = db.query("SELECT COUNT(*) as count FROM observations WHERE project = ?");
332
- const sumStmt = db.query("SELECT COUNT(*) as count FROM summaries WHERE project = ?");
333
- const sesStmt = db.query("SELECT COUNT(*) as count FROM sessions WHERE project = ?");
334
- const prmStmt = db.query("SELECT COUNT(*) as count FROM prompts WHERE project = ?");
335
- const discoveryStmt = db.query(
336
- "SELECT COALESCE(SUM(discovery_tokens), 0) as total FROM observations WHERE project = ?"
337
- );
338
- const discoveryTokens = discoveryStmt.get(project)?.total || 0;
339
- const readStmt = db.query(
340
- `SELECT COALESCE(SUM(
341
- CAST((LENGTH(COALESCE(title, '')) + LENGTH(COALESCE(narrative, ''))) / 4 AS INTEGER)
342
- ), 0) as total FROM observations WHERE project = ?`
343
- );
344
- const readTokens = readStmt.get(project)?.total || 0;
640
+ const sql = `
641
+ WITH
642
+ obs_stats AS (
643
+ SELECT
644
+ COUNT(*) as count,
645
+ COALESCE(SUM(discovery_tokens), 0) as discovery_tokens,
646
+ COALESCE(SUM(
647
+ CAST((LENGTH(COALESCE(title, '')) + LENGTH(COALESCE(narrative, ''))) / 4 AS INTEGER)
648
+ ), 0) as read_tokens
649
+ FROM observations WHERE project = ?
650
+ ),
651
+ sum_count AS (SELECT COUNT(*) as count FROM summaries WHERE project = ?),
652
+ ses_count AS (SELECT COUNT(*) as count FROM sessions WHERE project = ?),
653
+ prm_count AS (SELECT COUNT(*) as count FROM prompts WHERE project = ?)
654
+ SELECT
655
+ obs_stats.count as observations,
656
+ obs_stats.discovery_tokens,
657
+ obs_stats.read_tokens,
658
+ sum_count.count as summaries,
659
+ ses_count.count as sessions,
660
+ prm_count.count as prompts
661
+ FROM obs_stats, sum_count, ses_count, prm_count
662
+ `;
663
+ const row = db.query(sql).get(project, project, project, project);
664
+ const discoveryTokens = row?.discovery_tokens || 0;
665
+ const readTokens = row?.read_tokens || 0;
345
666
  const savings = Math.max(0, discoveryTokens - readTokens);
346
667
  return {
347
- observations: obsStmt.get(project)?.count || 0,
348
- summaries: sumStmt.get(project)?.count || 0,
349
- sessions: sesStmt.get(project)?.count || 0,
350
- prompts: prmStmt.get(project)?.count || 0,
668
+ observations: row?.observations || 0,
669
+ summaries: row?.summaries || 0,
670
+ sessions: row?.sessions || 0,
671
+ prompts: row?.prompts || 0,
351
672
  tokenEconomics: { discoveryTokens, readTokens, savings }
352
673
  };
353
674
  }
@@ -355,7 +676,7 @@ function getStaleObservations(db, project) {
355
676
  const rows = db.query(`
356
677
  SELECT * FROM observations
357
678
  WHERE project = ? AND files_modified IS NOT NULL AND files_modified != ''
358
- ORDER BY created_at_epoch DESC
679
+ ORDER BY created_at_epoch DESC, id DESC
359
680
  LIMIT 500
360
681
  `).all(project);
361
682
  const staleObs = [];
@@ -402,14 +723,15 @@ var init_Search = __esm({
402
723
  import BetterSqlite3 from "better-sqlite3";
403
724
  var Database = class {
404
725
  _db;
726
+ _stmtCache = /* @__PURE__ */ new Map();
405
727
  constructor(path, options) {
406
728
  this._db = new BetterSqlite3(path, {
407
- // better-sqlite3 crea il file di default (non serve 'create')
729
+ // better-sqlite3 creates the file by default ('create' not needed)
408
730
  readonly: options?.readwrite === false ? true : false
409
731
  });
410
732
  }
411
733
  /**
412
- * Esegui una query SQL senza risultati
734
+ * Execute a SQL query without results
413
735
  */
414
736
  run(sql, params) {
415
737
  const stmt = this._db.prepare(sql);
@@ -417,51 +739,53 @@ var Database = class {
417
739
  return result;
418
740
  }
419
741
  /**
420
- * Prepara una query con interfaccia compatibile bun:sqlite
742
+ * Prepare a query with bun:sqlite-compatible interface.
743
+ * Returns a cached prepared statement for repeated queries.
421
744
  */
422
745
  query(sql) {
423
- return new BunQueryCompat(this._db, sql);
746
+ let cached = this._stmtCache.get(sql);
747
+ if (!cached) {
748
+ cached = new BunQueryCompat(this._db, sql);
749
+ this._stmtCache.set(sql, cached);
750
+ }
751
+ return cached;
424
752
  }
425
753
  /**
426
- * Crea una transazione
754
+ * Create a transaction
427
755
  */
428
756
  transaction(fn) {
429
757
  return this._db.transaction(fn);
430
758
  }
431
759
  /**
432
- * Chiudi la connessione
760
+ * Close the connection
433
761
  */
434
762
  close() {
763
+ this._stmtCache.clear();
435
764
  this._db.close();
436
765
  }
437
766
  };
438
767
  var BunQueryCompat = class {
439
- _db;
440
- _sql;
768
+ _stmt;
441
769
  constructor(db, sql) {
442
- this._db = db;
443
- this._sql = sql;
770
+ this._stmt = db.prepare(sql);
444
771
  }
445
772
  /**
446
- * Restituisce tutte le righe
773
+ * Returns all rows
447
774
  */
448
775
  all(...params) {
449
- const stmt = this._db.prepare(this._sql);
450
- return params.length > 0 ? stmt.all(...params) : stmt.all();
776
+ return params.length > 0 ? this._stmt.all(...params) : this._stmt.all();
451
777
  }
452
778
  /**
453
- * Restituisce la prima riga o null
779
+ * Returns the first row or null
454
780
  */
455
781
  get(...params) {
456
- const stmt = this._db.prepare(this._sql);
457
- return params.length > 0 ? stmt.get(...params) : stmt.get();
782
+ return params.length > 0 ? this._stmt.get(...params) : this._stmt.get();
458
783
  }
459
784
  /**
460
- * Esegui senza risultati
785
+ * Execute without results
461
786
  */
462
787
  run(...params) {
463
- const stmt = this._db.prepare(this._sql);
464
- return params.length > 0 ? stmt.run(...params) : stmt.run();
788
+ return params.length > 0 ? this._stmt.run(...params) : this._stmt.run();
465
789
  }
466
790
  };
467
791
 
@@ -722,150 +1046,63 @@ function ensureDir(dirPath) {
722
1046
  // src/services/sqlite/Database.ts
723
1047
  var SQLITE_MMAP_SIZE_BYTES = 256 * 1024 * 1024;
724
1048
  var SQLITE_CACHE_SIZE_PAGES = 1e4;
725
- var dbInstance = null;
726
1049
  var KiroMemoryDatabase = class {
727
- db;
1050
+ _db;
1051
+ /**
1052
+ * Readonly accessor for the underlying Database instance.
1053
+ * Prefer using query() and run() proxy methods directly.
1054
+ */
1055
+ get db() {
1056
+ return this._db;
1057
+ }
728
1058
  /**
729
- * @param dbPath - Percorso al file SQLite (default: DB_PATH)
730
- * @param skipMigrations - Se true, salta il migration runner (per hook ad alta frequenza)
1059
+ * @param dbPath - Path to the SQLite file (default: DB_PATH)
1060
+ * @param skipMigrations - If true, skip the migration runner (for high-frequency hooks)
731
1061
  */
732
1062
  constructor(dbPath = DB_PATH, skipMigrations = false) {
733
1063
  if (dbPath !== ":memory:") {
734
1064
  ensureDir(DATA_DIR);
735
1065
  }
736
- this.db = new Database(dbPath, { create: true, readwrite: true });
737
- this.db.run("PRAGMA journal_mode = WAL");
738
- this.db.run("PRAGMA synchronous = NORMAL");
739
- this.db.run("PRAGMA foreign_keys = ON");
740
- this.db.run("PRAGMA temp_store = memory");
741
- this.db.run(`PRAGMA mmap_size = ${SQLITE_MMAP_SIZE_BYTES}`);
742
- this.db.run(`PRAGMA cache_size = ${SQLITE_CACHE_SIZE_PAGES}`);
1066
+ this._db = new Database(dbPath, { create: true, readwrite: true });
1067
+ this._db.run("PRAGMA journal_mode = WAL");
1068
+ this._db.run("PRAGMA busy_timeout = 5000");
1069
+ this._db.run("PRAGMA synchronous = NORMAL");
1070
+ this._db.run("PRAGMA foreign_keys = ON");
1071
+ this._db.run("PRAGMA temp_store = memory");
1072
+ this._db.run(`PRAGMA mmap_size = ${SQLITE_MMAP_SIZE_BYTES}`);
1073
+ this._db.run(`PRAGMA cache_size = ${SQLITE_CACHE_SIZE_PAGES}`);
743
1074
  if (!skipMigrations) {
744
- const migrationRunner = new MigrationRunner(this.db);
1075
+ const migrationRunner = new MigrationRunner(this._db);
745
1076
  migrationRunner.runAllMigrations();
746
1077
  }
747
1078
  }
748
1079
  /**
749
- * Esegue una funzione all'interno di una transazione atomica.
750
- * Se fn() lancia un errore, la transazione viene annullata automaticamente.
1080
+ * Prepare a query (delegates to underlying Database).
1081
+ * Proxy method to avoid ctx.db.db.query() double access.
751
1082
  */
752
- withTransaction(fn) {
753
- const transaction = this.db.transaction(fn);
754
- return transaction(this.db);
755
- }
756
- /**
757
- * Close the database connection
758
- */
759
- close() {
760
- this.db.close();
761
- }
762
- };
763
- var DatabaseManager = class _DatabaseManager {
764
- static instance;
765
- db = null;
766
- migrations = [];
767
- static getInstance() {
768
- if (!_DatabaseManager.instance) {
769
- _DatabaseManager.instance = new _DatabaseManager();
770
- }
771
- return _DatabaseManager.instance;
772
- }
773
- /**
774
- * Register a migration to be run during initialization
775
- */
776
- registerMigration(migration) {
777
- this.migrations.push(migration);
778
- this.migrations.sort((a, b) => a.version - b.version);
779
- }
780
- /**
781
- * Initialize database connection with optimized settings
782
- */
783
- async initialize() {
784
- if (this.db) {
785
- return this.db;
786
- }
787
- ensureDir(DATA_DIR);
788
- this.db = new Database(DB_PATH, { create: true, readwrite: true });
789
- this.db.run("PRAGMA journal_mode = WAL");
790
- this.db.run("PRAGMA synchronous = NORMAL");
791
- this.db.run("PRAGMA foreign_keys = ON");
792
- this.db.run("PRAGMA temp_store = memory");
793
- this.db.run(`PRAGMA mmap_size = ${SQLITE_MMAP_SIZE_BYTES}`);
794
- this.db.run(`PRAGMA cache_size = ${SQLITE_CACHE_SIZE_PAGES}`);
795
- this.initializeSchemaVersions();
796
- await this.runMigrations();
797
- dbInstance = this.db;
798
- return this.db;
1083
+ query(sql) {
1084
+ return this._db.query(sql);
799
1085
  }
800
1086
  /**
801
- * Get the current database connection
1087
+ * Execute a SQL statement without results (delegates to underlying Database).
1088
+ * Proxy method to avoid ctx.db.db.run() double access.
802
1089
  */
803
- getConnection() {
804
- if (!this.db) {
805
- throw new Error("Database not initialized. Call initialize() first.");
806
- }
807
- return this.db;
1090
+ run(sql, params) {
1091
+ return this._db.run(sql, params);
808
1092
  }
809
1093
  /**
810
- * Execute a function within a transaction
1094
+ * Executes a function within an atomic transaction.
1095
+ * If fn() throws an error, the transaction is automatically rolled back.
811
1096
  */
812
1097
  withTransaction(fn) {
813
- const db = this.getConnection();
814
- const transaction = db.transaction(fn);
815
- return transaction(db);
1098
+ const transaction = this._db.transaction(fn);
1099
+ return transaction(this._db);
816
1100
  }
817
1101
  /**
818
1102
  * Close the database connection
819
1103
  */
820
1104
  close() {
821
- if (this.db) {
822
- this.db.close();
823
- this.db = null;
824
- dbInstance = null;
825
- }
826
- }
827
- /**
828
- * Initialize the schema_versions table
829
- */
830
- initializeSchemaVersions() {
831
- if (!this.db) return;
832
- this.db.run(`
833
- CREATE TABLE IF NOT EXISTS schema_versions (
834
- id INTEGER PRIMARY KEY,
835
- version INTEGER UNIQUE NOT NULL,
836
- applied_at TEXT NOT NULL
837
- )
838
- `);
839
- }
840
- /**
841
- * Run all pending migrations
842
- */
843
- async runMigrations() {
844
- if (!this.db) return;
845
- const query = this.db.query("SELECT version FROM schema_versions ORDER BY version");
846
- const appliedVersions = query.all().map((row) => row.version);
847
- const maxApplied = appliedVersions.length > 0 ? Math.max(...appliedVersions) : 0;
848
- for (const migration of this.migrations) {
849
- if (migration.version > maxApplied) {
850
- logger.info("DB", `Applying migration ${migration.version}`);
851
- const transaction = this.db.transaction(() => {
852
- migration.up(this.db);
853
- const insertQuery = this.db.query("INSERT INTO schema_versions (version, applied_at) VALUES (?, ?)");
854
- insertQuery.run(migration.version, (/* @__PURE__ */ new Date()).toISOString());
855
- });
856
- transaction();
857
- logger.info("DB", `Migration ${migration.version} applied successfully`);
858
- }
859
- }
860
- }
861
- /**
862
- * Get current schema version
863
- */
864
- getCurrentVersion() {
865
- if (!this.db) return 0;
866
- const query = this.db.query("SELECT MAX(version) as version FROM schema_versions");
867
- const result = query.get();
868
- return result?.version || 0;
1105
+ this._db.close();
869
1106
  }
870
1107
  };
871
1108
  var MigrationRunner = class {
@@ -1106,19 +1343,102 @@ var MigrationRunner = class {
1106
1343
  db.run("CREATE INDEX IF NOT EXISTS idx_summaries_project_epoch ON summaries(project, created_at_epoch DESC)");
1107
1344
  db.run("CREATE INDEX IF NOT EXISTS idx_prompts_project_epoch ON prompts(project, created_at_epoch DESC)");
1108
1345
  }
1346
+ },
1347
+ {
1348
+ version: 10,
1349
+ up: (db) => {
1350
+ db.run(`
1351
+ CREATE TABLE IF NOT EXISTS job_queue (
1352
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1353
+ type TEXT NOT NULL,
1354
+ status TEXT NOT NULL DEFAULT 'pending',
1355
+ payload TEXT,
1356
+ result TEXT,
1357
+ error TEXT,
1358
+ retry_count INTEGER DEFAULT 0,
1359
+ max_retries INTEGER DEFAULT 3,
1360
+ priority INTEGER DEFAULT 0,
1361
+ created_at TEXT NOT NULL,
1362
+ created_at_epoch INTEGER NOT NULL,
1363
+ started_at_epoch INTEGER,
1364
+ completed_at_epoch INTEGER
1365
+ )
1366
+ `);
1367
+ db.run("CREATE INDEX IF NOT EXISTS idx_jobs_status ON job_queue(status)");
1368
+ db.run("CREATE INDEX IF NOT EXISTS idx_jobs_type ON job_queue(type)");
1369
+ db.run("CREATE INDEX IF NOT EXISTS idx_jobs_priority ON job_queue(status, priority DESC, created_at_epoch ASC)");
1370
+ }
1371
+ },
1372
+ {
1373
+ version: 11,
1374
+ up: (db) => {
1375
+ db.run("ALTER TABLE observations ADD COLUMN auto_category TEXT");
1376
+ db.run("CREATE INDEX IF NOT EXISTS idx_observations_category ON observations(auto_category)");
1377
+ }
1378
+ },
1379
+ {
1380
+ version: 12,
1381
+ up: (db) => {
1382
+ db.run(`
1383
+ CREATE TABLE IF NOT EXISTS github_links (
1384
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1385
+ observation_id INTEGER,
1386
+ session_id TEXT,
1387
+ repo TEXT NOT NULL,
1388
+ issue_number INTEGER,
1389
+ pr_number INTEGER,
1390
+ event_type TEXT NOT NULL,
1391
+ action TEXT,
1392
+ title TEXT,
1393
+ url TEXT,
1394
+ author TEXT,
1395
+ created_at TEXT NOT NULL,
1396
+ created_at_epoch INTEGER NOT NULL,
1397
+ FOREIGN KEY (observation_id) REFERENCES observations(id)
1398
+ )
1399
+ `);
1400
+ db.run("CREATE INDEX IF NOT EXISTS idx_github_links_repo ON github_links(repo)");
1401
+ db.run("CREATE INDEX IF NOT EXISTS idx_github_links_obs ON github_links(observation_id)");
1402
+ db.run("CREATE INDEX IF NOT EXISTS idx_github_links_event ON github_links(event_type)");
1403
+ db.run("CREATE INDEX IF NOT EXISTS idx_github_links_repo_issue ON github_links(repo, issue_number)");
1404
+ db.run("CREATE INDEX IF NOT EXISTS idx_github_links_repo_pr ON github_links(repo, pr_number)");
1405
+ }
1406
+ },
1407
+ {
1408
+ version: 13,
1409
+ up: (db) => {
1410
+ db.run("CREATE INDEX IF NOT EXISTS idx_observations_keyset ON observations(created_at_epoch DESC, id DESC)");
1411
+ db.run("CREATE INDEX IF NOT EXISTS idx_observations_project_keyset ON observations(project, created_at_epoch DESC, id DESC)");
1412
+ db.run("CREATE INDEX IF NOT EXISTS idx_summaries_keyset ON summaries(created_at_epoch DESC, id DESC)");
1413
+ db.run("CREATE INDEX IF NOT EXISTS idx_summaries_project_keyset ON summaries(project, created_at_epoch DESC, id DESC)");
1414
+ db.run("CREATE INDEX IF NOT EXISTS idx_prompts_keyset ON prompts(created_at_epoch DESC, id DESC)");
1415
+ db.run("CREATE INDEX IF NOT EXISTS idx_prompts_project_keyset ON prompts(project, created_at_epoch DESC, id DESC)");
1416
+ }
1109
1417
  }
1110
1418
  ];
1111
1419
  }
1112
1420
  };
1113
- function getDatabase() {
1114
- if (!dbInstance) {
1115
- throw new Error("Database not initialized. Call DatabaseManager.getInstance().initialize() first.");
1116
- }
1117
- return dbInstance;
1421
+
1422
+ // src/services/sqlite/cursor.ts
1423
+ function encodeCursor(id, epoch) {
1424
+ const raw = `${epoch}:${id}`;
1425
+ return Buffer.from(raw, "utf8").toString("base64url");
1118
1426
  }
1119
- async function initializeDatabase() {
1120
- const manager = DatabaseManager.getInstance();
1121
- return await manager.initialize();
1427
+ function decodeCursor(cursor) {
1428
+ try {
1429
+ const raw = Buffer.from(cursor, "base64url").toString("utf8");
1430
+ const colonIdx = raw.indexOf(":");
1431
+ if (colonIdx === -1) return null;
1432
+ const epochStr = raw.substring(0, colonIdx);
1433
+ const idStr = raw.substring(colonIdx + 1);
1434
+ const epoch = parseInt(epochStr, 10);
1435
+ const id = parseInt(idStr, 10);
1436
+ if (!Number.isInteger(epoch) || epoch <= 0) return null;
1437
+ if (!Number.isInteger(id) || id <= 0) return null;
1438
+ return { epoch, id };
1439
+ } catch {
1440
+ return null;
1441
+ }
1122
1442
  }
1123
1443
 
1124
1444
  // src/services/sqlite/Sessions.ts
@@ -1164,16 +1484,16 @@ function createSummary(db, sessionId, project, request, investigated, learned, c
1164
1484
  }
1165
1485
  function getSummariesByProject(db, project, limit = 50) {
1166
1486
  const query = db.query(
1167
- "SELECT * FROM summaries WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ?"
1487
+ "SELECT * FROM summaries WHERE project = ? ORDER BY created_at_epoch DESC, id DESC LIMIT ?"
1168
1488
  );
1169
1489
  return query.all(project, limit);
1170
1490
  }
1171
1491
  function searchSummaries(db, searchTerm, project) {
1172
1492
  const sql = project ? `SELECT * FROM summaries
1173
1493
  WHERE project = ? AND (request LIKE ? ESCAPE '\\' OR learned LIKE ? ESCAPE '\\' OR completed LIKE ? ESCAPE '\\' OR notes LIKE ? ESCAPE '\\')
1174
- ORDER BY created_at_epoch DESC` : `SELECT * FROM summaries
1494
+ ORDER BY created_at_epoch DESC, id DESC` : `SELECT * FROM summaries
1175
1495
  WHERE request LIKE ? ESCAPE '\\' OR learned LIKE ? ESCAPE '\\' OR completed LIKE ? ESCAPE '\\' OR notes LIKE ? ESCAPE '\\'
1176
- ORDER BY created_at_epoch DESC`;
1496
+ ORDER BY created_at_epoch DESC, id DESC`;
1177
1497
  const pattern = `%${escapeLikePattern2(searchTerm)}%`;
1178
1498
  const query = db.query(sql);
1179
1499
  if (project) {
@@ -1195,7 +1515,7 @@ function createPrompt(db, contentSessionId, project, promptNumber, promptText) {
1195
1515
  }
1196
1516
  function getPromptsByProject(db, project, limit = 100) {
1197
1517
  const query = db.query(
1198
- "SELECT * FROM prompts WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ?"
1518
+ "SELECT * FROM prompts WHERE project = ? ORDER BY created_at_epoch DESC, id DESC LIMIT ?"
1199
1519
  );
1200
1520
  return query.all(project, limit);
1201
1521
  }
@@ -1223,13 +1543,13 @@ function createCheckpoint(db, sessionId, project, data) {
1223
1543
  }
1224
1544
  function getLatestCheckpoint(db, sessionId) {
1225
1545
  const query = db.query(
1226
- "SELECT * FROM checkpoints WHERE session_id = ? ORDER BY created_at_epoch DESC LIMIT 1"
1546
+ "SELECT * FROM checkpoints WHERE session_id = ? ORDER BY created_at_epoch DESC, id DESC LIMIT 1"
1227
1547
  );
1228
1548
  return query.get(sessionId);
1229
1549
  }
1230
1550
  function getLatestCheckpointByProject(db, project) {
1231
1551
  const query = db.query(
1232
- "SELECT * FROM checkpoints WHERE project = ? ORDER BY created_at_epoch DESC LIMIT 1"
1552
+ "SELECT * FROM checkpoints WHERE project = ? ORDER BY created_at_epoch DESC, id DESC LIMIT 1"
1233
1553
  );
1234
1554
  return query.get(project);
1235
1555
  }
@@ -1291,9 +1611,9 @@ function getReportData(db, project, startEpoch, endEpoch) {
1291
1611
  const staleCount = (project ? db.query(staleSql).get(project, startEpoch, endEpoch)?.count : db.query(staleSql).get(startEpoch, endEpoch)?.count) || 0;
1292
1612
  const summarySql = project ? `SELECT learned, completed, next_steps FROM summaries
1293
1613
  WHERE project = ? AND created_at_epoch >= ? AND created_at_epoch <= ?
1294
- ORDER BY created_at_epoch DESC` : `SELECT learned, completed, next_steps FROM summaries
1614
+ ORDER BY created_at_epoch DESC, id DESC` : `SELECT learned, completed, next_steps FROM summaries
1295
1615
  WHERE created_at_epoch >= ? AND created_at_epoch <= ?
1296
- ORDER BY created_at_epoch DESC`;
1616
+ ORDER BY created_at_epoch DESC, id DESC`;
1297
1617
  const summaryRows = project ? db.query(summarySql).all(project, startEpoch, endEpoch) : db.query(summarySql).all(startEpoch, endEpoch);
1298
1618
  const topLearnings = [];
1299
1619
  const completedTasks = [];
@@ -1358,20 +1678,61 @@ function getReportData(db, project, startEpoch, endEpoch) {
1358
1678
  // src/services/sqlite/index.ts
1359
1679
  init_Search();
1360
1680
 
1681
+ // src/types/worker-types.ts
1682
+ var KNOWLEDGE_TYPES = ["constraint", "decision", "heuristic", "rejected"];
1683
+
1684
+ // src/services/sqlite/Retention.ts
1685
+ var KNOWLEDGE_TYPE_LIST = KNOWLEDGE_TYPES;
1686
+ var KNOWLEDGE_PLACEHOLDERS = KNOWLEDGE_TYPE_LIST.map(() => "?").join(", ");
1687
+
1361
1688
  // src/sdk/index.ts
1362
1689
  init_Observations();
1363
1690
  import { createHash } from "crypto";
1364
1691
  init_Search();
1365
1692
 
1366
1693
  // src/services/search/EmbeddingService.ts
1694
+ var MODEL_CONFIGS = {
1695
+ "all-MiniLM-L6-v2": {
1696
+ modelId: "Xenova/all-MiniLM-L6-v2",
1697
+ dimensions: 384
1698
+ },
1699
+ "jina-code-v2": {
1700
+ modelId: "jinaai/jina-embeddings-v2-base-code",
1701
+ dimensions: 768
1702
+ },
1703
+ "bge-small-en": {
1704
+ modelId: "BAAI/bge-small-en-v1.5",
1705
+ dimensions: 384
1706
+ }
1707
+ };
1708
+ var FASTEMBED_COMPATIBLE_MODELS = /* @__PURE__ */ new Set(["all-MiniLM-L6-v2", "bge-small-en"]);
1367
1709
  var EmbeddingService = class {
1368
1710
  provider = null;
1369
1711
  model = null;
1370
1712
  initialized = false;
1371
1713
  initializing = null;
1714
+ config;
1715
+ configName;
1716
+ constructor() {
1717
+ const envModel = process.env.KIRO_MEMORY_EMBEDDING_MODEL || "all-MiniLM-L6-v2";
1718
+ this.configName = envModel;
1719
+ if (MODEL_CONFIGS[envModel]) {
1720
+ this.config = MODEL_CONFIGS[envModel];
1721
+ } else if (envModel.includes("/")) {
1722
+ const dimensions = parseInt(process.env.KIRO_MEMORY_EMBEDDING_DIMENSIONS || "384", 10);
1723
+ this.config = {
1724
+ modelId: envModel,
1725
+ dimensions: isNaN(dimensions) ? 384 : dimensions
1726
+ };
1727
+ } else {
1728
+ logger.warn("EMBEDDING", `Unknown model name '${envModel}', falling back to 'all-MiniLM-L6-v2'`);
1729
+ this.configName = "all-MiniLM-L6-v2";
1730
+ this.config = MODEL_CONFIGS["all-MiniLM-L6-v2"];
1731
+ }
1732
+ }
1372
1733
  /**
1373
- * Inizializza il servizio di embedding.
1374
- * Tenta fastembed, poi @huggingface/transformers, poi fallback a null.
1734
+ * Initialize the embedding service.
1735
+ * Tries fastembed (when compatible), then @huggingface/transformers, then falls back to null.
1375
1736
  */
1376
1737
  async initialize() {
1377
1738
  if (this.initialized) return this.provider !== null;
@@ -1382,45 +1743,48 @@ var EmbeddingService = class {
1382
1743
  return result;
1383
1744
  }
1384
1745
  async _doInitialize() {
1385
- try {
1386
- const fastembed = await import("fastembed");
1387
- const EmbeddingModel = fastembed.EmbeddingModel || fastembed.default?.EmbeddingModel;
1388
- const FlagEmbedding = fastembed.FlagEmbedding || fastembed.default?.FlagEmbedding;
1389
- if (FlagEmbedding && EmbeddingModel) {
1390
- this.model = await FlagEmbedding.init({
1391
- model: EmbeddingModel.BGESmallENV15
1392
- });
1393
- this.provider = "fastembed";
1394
- this.initialized = true;
1395
- logger.info("EMBEDDING", "Inizializzato con fastembed (BGE-small-en-v1.5)");
1396
- return true;
1746
+ const fastembedCompatible = FASTEMBED_COMPATIBLE_MODELS.has(this.configName);
1747
+ if (fastembedCompatible) {
1748
+ try {
1749
+ const fastembed = await import("fastembed");
1750
+ const EmbeddingModel = fastembed.EmbeddingModel || fastembed.default?.EmbeddingModel;
1751
+ const FlagEmbedding = fastembed.FlagEmbedding || fastembed.default?.FlagEmbedding;
1752
+ if (FlagEmbedding && EmbeddingModel) {
1753
+ this.model = await FlagEmbedding.init({
1754
+ model: EmbeddingModel.BGESmallENV15
1755
+ });
1756
+ this.provider = "fastembed";
1757
+ this.initialized = true;
1758
+ logger.info("EMBEDDING", `Initialized with fastembed (BGE-small-en-v1.5) for model '${this.configName}'`);
1759
+ return true;
1760
+ }
1761
+ } catch (error) {
1762
+ logger.debug("EMBEDDING", `fastembed not available: ${error}`);
1397
1763
  }
1398
- } catch (error) {
1399
- logger.debug("EMBEDDING", `fastembed non disponibile: ${error}`);
1400
1764
  }
1401
1765
  try {
1402
1766
  const transformers = await import("@huggingface/transformers");
1403
1767
  const pipeline = transformers.pipeline || transformers.default?.pipeline;
1404
1768
  if (pipeline) {
1405
- this.model = await pipeline("feature-extraction", "Xenova/all-MiniLM-L6-v2", {
1769
+ this.model = await pipeline("feature-extraction", this.config.modelId, {
1406
1770
  quantized: true
1407
1771
  });
1408
1772
  this.provider = "transformers";
1409
1773
  this.initialized = true;
1410
- logger.info("EMBEDDING", "Inizializzato con @huggingface/transformers (all-MiniLM-L6-v2)");
1774
+ logger.info("EMBEDDING", `Initialized with @huggingface/transformers (${this.config.modelId})`);
1411
1775
  return true;
1412
1776
  }
1413
1777
  } catch (error) {
1414
- logger.debug("EMBEDDING", `@huggingface/transformers non disponibile: ${error}`);
1778
+ logger.debug("EMBEDDING", `@huggingface/transformers not available: ${error}`);
1415
1779
  }
1416
1780
  this.provider = null;
1417
1781
  this.initialized = true;
1418
- logger.warn("EMBEDDING", "Nessun provider embedding disponibile, ricerca semantica disabilitata");
1782
+ logger.warn("EMBEDDING", "No embedding provider available, semantic search disabled");
1419
1783
  return false;
1420
1784
  }
1421
1785
  /**
1422
- * Genera embedding per un singolo testo.
1423
- * Ritorna Float32Array con 384 dimensioni, o null se non disponibile.
1786
+ * Generate embedding for a single text.
1787
+ * Returns Float32Array with configured dimensions, or null if not available.
1424
1788
  */
1425
1789
  async embed(text) {
1426
1790
  if (!this.initialized) await this.initialize();
@@ -1433,46 +1797,118 @@ var EmbeddingService = class {
1433
1797
  return await this._embedTransformers(truncated);
1434
1798
  }
1435
1799
  } catch (error) {
1436
- logger.error("EMBEDDING", `Errore generazione embedding: ${error}`);
1800
+ logger.error("EMBEDDING", `Error generating embedding: ${error}`);
1437
1801
  }
1438
1802
  return null;
1439
1803
  }
1440
1804
  /**
1441
- * Genera embeddings in batch.
1805
+ * Generate embeddings in batch.
1806
+ * Uses native batch support when available (fastembed, transformers),
1807
+ * falls back to serial processing on batch failure.
1442
1808
  */
1443
1809
  async embedBatch(texts) {
1444
1810
  if (!this.initialized) await this.initialize();
1445
1811
  if (!this.provider || !this.model) return texts.map(() => null);
1446
- const results = [];
1447
- for (const text of texts) {
1448
- try {
1449
- const embedding = await this.embed(text);
1450
- results.push(embedding);
1451
- } catch {
1452
- results.push(null);
1812
+ if (texts.length === 0) return [];
1813
+ const truncated = texts.map((t) => t.substring(0, 2e3));
1814
+ try {
1815
+ if (this.provider === "fastembed") {
1816
+ return await this._embedBatchFastembed(truncated);
1817
+ } else if (this.provider === "transformers") {
1818
+ return await this._embedBatchTransformers(truncated);
1453
1819
  }
1820
+ } catch (error) {
1821
+ logger.warn("EMBEDDING", `Batch embedding failed, falling back to serial: ${error}`);
1454
1822
  }
1455
- return results;
1823
+ return this._embedBatchSerial(truncated);
1456
1824
  }
1457
1825
  /**
1458
- * Verifica se il servizio è disponibile.
1826
+ * Check if the service is available.
1459
1827
  */
1460
1828
  isAvailable() {
1461
1829
  return this.initialized && this.provider !== null;
1462
1830
  }
1463
1831
  /**
1464
- * Nome del provider attivo.
1832
+ * Name of the active provider.
1465
1833
  */
1466
1834
  getProvider() {
1467
1835
  return this.provider;
1468
1836
  }
1469
1837
  /**
1470
- * Dimensioni del vettore embedding.
1838
+ * Embedding vector dimensions for the active model configuration.
1471
1839
  */
1472
1840
  getDimensions() {
1473
- return 384;
1841
+ return this.config.dimensions;
1474
1842
  }
1475
- // --- Provider specifici ---
1843
+ /**
1844
+ * Human-readable model name used as identifier in the observation_embeddings table.
1845
+ * Returns the short name (e.g., 'all-MiniLM-L6-v2') or the full HF model ID for custom models.
1846
+ */
1847
+ getModelName() {
1848
+ return this.configName;
1849
+ }
1850
+ // --- Batch implementations ---
1851
+ /**
1852
+ * Native batch embedding with fastembed.
1853
+ * FlagEmbedding.embed() accepts string[] and returns an async iterable of batches.
1854
+ */
1855
+ async _embedBatchFastembed(texts) {
1856
+ const results = [];
1857
+ const embeddings = this.model.embed(texts, texts.length);
1858
+ for await (const batch of embeddings) {
1859
+ if (batch) {
1860
+ for (const vec of batch) {
1861
+ results.push(vec instanceof Float32Array ? vec : new Float32Array(vec));
1862
+ }
1863
+ }
1864
+ }
1865
+ while (results.length < texts.length) {
1866
+ results.push(null);
1867
+ }
1868
+ return results;
1869
+ }
1870
+ /**
1871
+ * Batch embedding with @huggingface/transformers pipeline.
1872
+ * The pipeline accepts string[] and returns a Tensor with shape [N, dims].
1873
+ */
1874
+ async _embedBatchTransformers(texts) {
1875
+ const output = await this.model(texts, {
1876
+ pooling: "mean",
1877
+ normalize: true
1878
+ });
1879
+ if (!output?.data) {
1880
+ return texts.map(() => null);
1881
+ }
1882
+ const dims = this.getDimensions();
1883
+ const data = output.data instanceof Float32Array ? output.data : new Float32Array(output.data);
1884
+ const results = [];
1885
+ for (let i = 0; i < texts.length; i++) {
1886
+ const offset = i * dims;
1887
+ if (offset + dims <= data.length) {
1888
+ results.push(data.slice(offset, offset + dims));
1889
+ } else {
1890
+ results.push(null);
1891
+ }
1892
+ }
1893
+ return results;
1894
+ }
1895
+ /**
1896
+ * Serial fallback: embed texts one at a time.
1897
+ * Used when native batch fails.
1898
+ */
1899
+ async _embedBatchSerial(texts) {
1900
+ const results = [];
1901
+ for (const text of texts) {
1902
+ try {
1903
+ const embedding = await this.embed(text);
1904
+ results.push(embedding);
1905
+ } catch {
1906
+ results.push(null);
1907
+ }
1908
+ }
1909
+ return results;
1910
+ }
1911
+ // --- Single-text provider implementations ---
1476
1912
  async _embedFastembed(text) {
1477
1913
  const embeddings = this.model.embed([text], 1);
1478
1914
  for await (const batch of embeddings) {
@@ -1503,17 +1939,21 @@ function getEmbeddingService() {
1503
1939
  }
1504
1940
 
1505
1941
  // src/services/search/VectorSearch.ts
1942
+ var DEFAULT_MAX_CANDIDATES = 2e3;
1506
1943
  function cosineSimilarity(a, b) {
1507
- if (a.length !== b.length) return 0;
1944
+ const len = a.length;
1945
+ if (len !== b.length) return 0;
1508
1946
  let dotProduct = 0;
1509
1947
  let normA = 0;
1510
1948
  let normB = 0;
1511
- for (let i = 0; i < a.length; i++) {
1512
- dotProduct += a[i] * b[i];
1513
- normA += a[i] * a[i];
1514
- normB += b[i] * b[i];
1515
- }
1516
- const denominator = Math.sqrt(normA) * Math.sqrt(normB);
1949
+ for (let i = 0; i < len; i++) {
1950
+ const ai = a[i];
1951
+ const bi = b[i];
1952
+ dotProduct += ai * bi;
1953
+ normA += ai * ai;
1954
+ normB += bi * bi;
1955
+ }
1956
+ const denominator = Math.sqrt(normA * normB);
1517
1957
  if (denominator === 0) return 0;
1518
1958
  return dotProduct / denominator;
1519
1959
  }
@@ -1526,23 +1966,36 @@ function bufferToFloat32(buf) {
1526
1966
  }
1527
1967
  var VectorSearch = class {
1528
1968
  /**
1529
- * Ricerca semantica: calcola cosine similarity tra query e tutti gli embeddings.
1969
+ * Semantic search with SQL pre-filtering for scalability.
1970
+ *
1971
+ * 2-phase strategy:
1972
+ * 1. SQL pre-filters by project + sorts by recency (loads max N candidates)
1973
+ * 2. JS computes cosine similarity only on filtered candidates
1974
+ *
1975
+ * With 50k observations and maxCandidates=2000, loads only ~4% of data.
1530
1976
  */
1531
1977
  async search(db, queryEmbedding, options = {}) {
1532
1978
  const limit = options.limit || 10;
1533
1979
  const threshold = options.threshold || 0.3;
1980
+ const maxCandidates = options.maxCandidates || DEFAULT_MAX_CANDIDATES;
1534
1981
  try {
1535
- let sql = `
1982
+ const conditions = [];
1983
+ const params = [];
1984
+ if (options.project) {
1985
+ conditions.push("o.project = ?");
1986
+ params.push(options.project);
1987
+ }
1988
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
1989
+ const sql = `
1536
1990
  SELECT e.observation_id, e.embedding,
1537
1991
  o.title, o.text, o.type, o.project, o.created_at, o.created_at_epoch
1538
1992
  FROM observation_embeddings e
1539
1993
  JOIN observations o ON o.id = e.observation_id
1994
+ ${whereClause}
1995
+ ORDER BY o.created_at_epoch DESC
1996
+ LIMIT ?
1540
1997
  `;
1541
- const params = [];
1542
- if (options.project) {
1543
- sql += " WHERE o.project = ?";
1544
- params.push(options.project);
1545
- }
1998
+ params.push(maxCandidates);
1546
1999
  const rows = db.query(sql).all(...params);
1547
2000
  const scored = [];
1548
2001
  for (const row of rows) {
@@ -1563,14 +2016,15 @@ var VectorSearch = class {
1563
2016
  }
1564
2017
  }
1565
2018
  scored.sort((a, b) => b.similarity - a.similarity);
2019
+ logger.debug("VECTOR", `Search: ${rows.length} candidates \u2192 ${scored.length} above threshold \u2192 ${Math.min(scored.length, limit)} results`);
1566
2020
  return scored.slice(0, limit);
1567
2021
  } catch (error) {
1568
- logger.error("VECTOR", `Errore ricerca vettoriale: ${error}`);
2022
+ logger.error("VECTOR", `Vector search error: ${error}`);
1569
2023
  return [];
1570
2024
  }
1571
2025
  }
1572
2026
  /**
1573
- * Salva embedding per un'osservazione.
2027
+ * Store embedding for an observation.
1574
2028
  */
1575
2029
  async storeEmbedding(db, observationId, embedding, model) {
1576
2030
  try {
@@ -1586,18 +2040,18 @@ var VectorSearch = class {
1586
2040
  embedding.length,
1587
2041
  (/* @__PURE__ */ new Date()).toISOString()
1588
2042
  );
1589
- logger.debug("VECTOR", `Embedding salvato per osservazione ${observationId}`);
2043
+ logger.debug("VECTOR", `Embedding saved for observation ${observationId}`);
1590
2044
  } catch (error) {
1591
- logger.error("VECTOR", `Errore salvataggio embedding: ${error}`);
2045
+ logger.error("VECTOR", `Error saving embedding: ${error}`);
1592
2046
  }
1593
2047
  }
1594
2048
  /**
1595
- * Genera embeddings per osservazioni che non li hanno ancora.
2049
+ * Generate embeddings for observations that don't have them yet.
1596
2050
  */
1597
2051
  async backfillEmbeddings(db, batchSize = 50) {
1598
2052
  const embeddingService2 = getEmbeddingService();
1599
2053
  if (!await embeddingService2.initialize()) {
1600
- logger.warn("VECTOR", "Embedding service non disponibile, backfill saltato");
2054
+ logger.warn("VECTOR", "Embedding service not available, backfill skipped");
1601
2055
  return 0;
1602
2056
  }
1603
2057
  const rows = db.query(`
@@ -1610,7 +2064,7 @@ var VectorSearch = class {
1610
2064
  `).all(batchSize);
1611
2065
  if (rows.length === 0) return 0;
1612
2066
  let count = 0;
1613
- const model = embeddingService2.getProvider() || "unknown";
2067
+ const model = embeddingService2.getModelName();
1614
2068
  for (const row of rows) {
1615
2069
  const parts = [row.title];
1616
2070
  if (row.text) parts.push(row.text);
@@ -1623,11 +2077,11 @@ var VectorSearch = class {
1623
2077
  count++;
1624
2078
  }
1625
2079
  }
1626
- logger.info("VECTOR", `Backfill completato: ${count}/${rows.length} embeddings generati`);
2080
+ logger.info("VECTOR", `Backfill completed: ${count}/${rows.length} embeddings generated`);
1627
2081
  return count;
1628
2082
  }
1629
2083
  /**
1630
- * Statistiche sugli embeddings.
2084
+ * Embedding statistics.
1631
2085
  */
1632
2086
  getStats(db) {
1633
2087
  try {
@@ -1700,21 +2154,21 @@ function knowledgeTypeBoost(type) {
1700
2154
  var HybridSearch = class {
1701
2155
  embeddingInitialized = false;
1702
2156
  /**
1703
- * Inizializza il servizio di embedding (lazy, non bloccante)
2157
+ * Initialize the embedding service (lazy, non-blocking)
1704
2158
  */
1705
2159
  async initialize() {
1706
2160
  try {
1707
2161
  const embeddingService2 = getEmbeddingService();
1708
2162
  await embeddingService2.initialize();
1709
2163
  this.embeddingInitialized = embeddingService2.isAvailable();
1710
- logger.info("SEARCH", `HybridSearch inizializzato (embedding: ${this.embeddingInitialized ? "attivo" : "disattivato"})`);
2164
+ logger.info("SEARCH", `HybridSearch initialized (embedding: ${this.embeddingInitialized ? "active" : "disabled"})`);
1711
2165
  } catch (error) {
1712
- logger.warn("SEARCH", "Inizializzazione embedding fallita, uso solo FTS5", {}, error);
2166
+ logger.warn("SEARCH", "Embedding initialization failed, using only FTS5", {}, error);
1713
2167
  this.embeddingInitialized = false;
1714
2168
  }
1715
2169
  }
1716
2170
  /**
1717
- * Ricerca ibrida con scoring a 4 segnali
2171
+ * Hybrid search with 4-signal scoring
1718
2172
  */
1719
2173
  async search(db, query, options = {}) {
1720
2174
  const limit = options.limit || 10;
@@ -1730,7 +2184,7 @@ var HybridSearch = class {
1730
2184
  const vectorResults = await vectorSearch2.search(db, queryEmbedding, {
1731
2185
  project: options.project,
1732
2186
  limit: limit * 2,
1733
- // Prendiamo piu risultati per il ranking
2187
+ // Fetch more results for ranking
1734
2188
  threshold: 0.3
1735
2189
  });
1736
2190
  for (const hit of vectorResults) {
@@ -1747,10 +2201,10 @@ var HybridSearch = class {
1747
2201
  source: "vector"
1748
2202
  });
1749
2203
  }
1750
- logger.debug("SEARCH", `Vector search: ${vectorResults.length} risultati`);
2204
+ logger.debug("SEARCH", `Vector search: ${vectorResults.length} results`);
1751
2205
  }
1752
2206
  } catch (error) {
1753
- logger.warn("SEARCH", "Ricerca vettoriale fallita, uso solo keyword", {}, error);
2207
+ logger.warn("SEARCH", "Vector search failed, using only keyword", {}, error);
1754
2208
  }
1755
2209
  }
1756
2210
  try {
@@ -1780,9 +2234,9 @@ var HybridSearch = class {
1780
2234
  });
1781
2235
  }
1782
2236
  }
1783
- logger.debug("SEARCH", `Keyword search: ${keywordResults.length} risultati`);
2237
+ logger.debug("SEARCH", `Keyword search: ${keywordResults.length} results`);
1784
2238
  } catch (error) {
1785
- logger.error("SEARCH", "Ricerca keyword fallita", {}, error);
2239
+ logger.error("SEARCH", "Keyword search failed", {}, error);
1786
2240
  }
1787
2241
  if (rawItems.size === 0) return [];
1788
2242
  const allFTS5Ranks = Array.from(rawItems.values()).filter((item) => item.fts5Rank !== null).map((item) => item.fts5Rank);
@@ -1834,9 +2288,6 @@ function getHybridSearch() {
1834
2288
  return hybridSearch;
1835
2289
  }
1836
2290
 
1837
- // src/types/worker-types.ts
1838
- var KNOWLEDGE_TYPES = ["constraint", "decision", "heuristic", "rejected"];
1839
-
1840
2291
  // src/sdk/index.ts
1841
2292
  var KiroMemorySDK = class {
1842
2293
  db;
@@ -1870,33 +2321,33 @@ var KiroMemorySDK = class {
1870
2321
  };
1871
2322
  }
1872
2323
  /**
1873
- * Valida input per storeObservation
2324
+ * Validate input for storeObservation
1874
2325
  */
1875
2326
  validateObservationInput(data) {
1876
2327
  if (!data.type || typeof data.type !== "string" || data.type.length > 100) {
1877
- throw new Error("type \xE8 obbligatorio (stringa, max 100 caratteri)");
2328
+ throw new Error("type is required (string, max 100 chars)");
1878
2329
  }
1879
2330
  if (!data.title || typeof data.title !== "string" || data.title.length > 500) {
1880
- throw new Error("title \xE8 obbligatorio (stringa, max 500 caratteri)");
2331
+ throw new Error("title is required (string, max 500 chars)");
1881
2332
  }
1882
2333
  if (!data.content || typeof data.content !== "string" || data.content.length > 1e5) {
1883
- throw new Error("content \xE8 obbligatorio (stringa, max 100KB)");
2334
+ throw new Error("content is required (string, max 100KB)");
1884
2335
  }
1885
2336
  }
1886
2337
  /**
1887
- * Valida input per storeSummary
2338
+ * Validate input for storeSummary
1888
2339
  */
1889
2340
  validateSummaryInput(data) {
1890
2341
  const MAX = 5e4;
1891
2342
  for (const [key, val] of Object.entries(data)) {
1892
2343
  if (val !== void 0 && val !== null) {
1893
- if (typeof val !== "string") throw new Error(`${key} deve essere una stringa`);
1894
- if (val.length > MAX) throw new Error(`${key} troppo grande (max 50KB)`);
2344
+ if (typeof val !== "string") throw new Error(`${key} must be a string`);
2345
+ if (val.length > MAX) throw new Error(`${key} too large (max 50KB)`);
1895
2346
  }
1896
2347
  }
1897
2348
  }
1898
2349
  /**
1899
- * Genera e salva embedding per un'osservazione (fire-and-forget, non blocca)
2350
+ * Generate and store embedding for an observation (fire-and-forget, non-blocking)
1900
2351
  */
1901
2352
  async generateEmbeddingAsync(observationId, title, content, concepts) {
1902
2353
  try {
@@ -1916,39 +2367,39 @@ var KiroMemorySDK = class {
1916
2367
  );
1917
2368
  }
1918
2369
  } catch (error) {
1919
- logger.debug("SDK", `Embedding generation fallita per obs ${observationId}: ${error}`);
2370
+ logger.debug("SDK", `Embedding generation failed for obs ${observationId}: ${error}`);
1920
2371
  }
1921
2372
  }
1922
2373
  /**
1923
- * Genera content hash SHA256 per deduplicazione basata su contenuto.
1924
- * Usa (project + type + title + narrative) come tupla di identità semantica.
1925
- * NON include sessionId perché è unico ad ogni invocazione.
2374
+ * Generate SHA256 content hash for content-based deduplication.
2375
+ * Uses (project + type + title + narrative) as semantic identity tuple.
2376
+ * Does NOT include sessionId since it's unique per invocation.
1926
2377
  */
1927
2378
  generateContentHash(type, title, narrative) {
1928
2379
  const payload = `${this.project}|${type}|${title}|${narrative || ""}`;
1929
2380
  return createHash("sha256").update(payload).digest("hex");
1930
2381
  }
1931
2382
  /**
1932
- * Finestre di deduplicazione per tipo (ms).
1933
- * Tipi con molte ripetizioni hanno finestre più ampie.
2383
+ * Deduplication windows per type (ms).
2384
+ * Types with many repetitions have wider windows.
1934
2385
  */
1935
2386
  getDeduplicationWindow(type) {
1936
2387
  switch (type) {
1937
2388
  case "file-read":
1938
2389
  return 6e4;
1939
- // 60s — letture frequenti sugli stessi file
2390
+ // 60s — frequent reads on the same files
1940
2391
  case "file-write":
1941
2392
  return 1e4;
1942
- // 10s — scritture rapide consecutive
2393
+ // 10s — rapid consecutive writes
1943
2394
  case "command":
1944
2395
  return 3e4;
1945
2396
  // 30s — standard
1946
2397
  case "research":
1947
2398
  return 12e4;
1948
- // 120s — web search e fetch ripetuti
2399
+ // 120s — repeated web search and fetch
1949
2400
  case "delegation":
1950
2401
  return 6e4;
1951
- // 60s — delegazioni rapide
2402
+ // 60s — rapid delegations
1952
2403
  default:
1953
2404
  return 3e4;
1954
2405
  }
@@ -1962,7 +2413,7 @@ var KiroMemorySDK = class {
1962
2413
  const contentHash = this.generateContentHash(data.type, data.title, data.narrative);
1963
2414
  const dedupWindow = this.getDeduplicationWindow(data.type);
1964
2415
  if (isDuplicateObservation(this.db.db, contentHash, dedupWindow)) {
1965
- logger.debug("SDK", `Osservazione duplicata scartata (${data.type}, ${dedupWindow}ms): ${data.title}`);
2416
+ logger.debug("SDK", `Duplicate observation discarded (${data.type}, ${dedupWindow}ms): ${data.title}`);
1966
2417
  return -1;
1967
2418
  }
1968
2419
  const filesRead = data.filesRead || (data.type === "file-read" ? data.files : void 0);
@@ -1990,12 +2441,12 @@ var KiroMemorySDK = class {
1990
2441
  return observationId;
1991
2442
  }
1992
2443
  /**
1993
- * Salva conoscenza strutturata (constraint, decision, heuristic, rejected).
1994
- * Usa il campo `type` per il knowledgeType e `facts` per i metadati JSON.
2444
+ * Store structured knowledge (constraint, decision, heuristic, rejected).
2445
+ * Uses the `type` field for knowledgeType and `facts` for JSON metadata.
1995
2446
  */
1996
2447
  async storeKnowledge(data) {
1997
2448
  if (!KNOWLEDGE_TYPES.includes(data.knowledgeType)) {
1998
- throw new Error(`knowledgeType non valido: ${data.knowledgeType}. Valori ammessi: ${KNOWLEDGE_TYPES.join(", ")}`);
2449
+ throw new Error(`Invalid knowledgeType: ${data.knowledgeType}. Allowed values: ${KNOWLEDGE_TYPES.join(", ")}`);
1999
2450
  }
2000
2451
  this.validateObservationInput({ type: data.knowledgeType, title: data.title, content: data.content });
2001
2452
  const metadata = (() => {
@@ -2027,9 +2478,9 @@ var KiroMemorySDK = class {
2027
2478
  }
2028
2479
  })();
2029
2480
  const sessionId = "sdk-" + Date.now();
2030
- const contentHash = this.generateContentHash(data.type, data.title);
2481
+ const contentHash = this.generateContentHash(data.knowledgeType, data.title);
2031
2482
  if (isDuplicateObservation(this.db.db, contentHash)) {
2032
- logger.debug("SDK", `Knowledge duplicata scartata: ${data.title}`);
2483
+ logger.debug("SDK", `Duplicate knowledge discarded: ${data.title}`);
2033
2484
  return -1;
2034
2485
  }
2035
2486
  const discoveryTokens = Math.ceil(data.content.length / 4);
@@ -2046,11 +2497,11 @@ var KiroMemorySDK = class {
2046
2497
  null,
2047
2498
  // narrative
2048
2499
  JSON.stringify(metadata),
2049
- // facts = metadati JSON
2500
+ // facts = JSON metadata
2050
2501
  data.concepts?.join(", ") || null,
2051
2502
  data.files?.join(", ") || null,
2052
2503
  null,
2053
- // filesModified: knowledge non modifica file
2504
+ // filesModified: knowledge doesn't modify files
2054
2505
  0,
2055
2506
  // prompt_number
2056
2507
  contentHash,
@@ -2161,8 +2612,8 @@ var KiroMemorySDK = class {
2161
2612
  return this.project;
2162
2613
  }
2163
2614
  /**
2164
- * Ricerca ibrida: vector search + keyword FTS5
2165
- * Richiede inizializzazione HybridSearch (embedding service)
2615
+ * Hybrid search: vector search + keyword FTS5
2616
+ * Requires HybridSearch initialization (embedding service)
2166
2617
  */
2167
2618
  async hybridSearch(query, options = {}) {
2168
2619
  const hybridSearch2 = getHybridSearch();
@@ -2172,8 +2623,8 @@ var KiroMemorySDK = class {
2172
2623
  });
2173
2624
  }
2174
2625
  /**
2175
- * Ricerca solo semantica (vector search)
2176
- * Ritorna risultati basati su similarità coseno con gli embeddings
2626
+ * Semantic-only search (vector search)
2627
+ * Returns results based on cosine similarity with embeddings
2177
2628
  */
2178
2629
  async semanticSearch(query, options = {}) {
2179
2630
  const embeddingService2 = getEmbeddingService();
@@ -2208,21 +2659,21 @@ var KiroMemorySDK = class {
2208
2659
  }));
2209
2660
  }
2210
2661
  /**
2211
- * Genera embeddings per osservazioni che non li hanno ancora
2662
+ * Generate embeddings for observations that don't have them yet
2212
2663
  */
2213
2664
  async backfillEmbeddings(batchSize = 50) {
2214
2665
  const vectorSearch2 = getVectorSearch();
2215
2666
  return vectorSearch2.backfillEmbeddings(this.db.db, batchSize);
2216
2667
  }
2217
2668
  /**
2218
- * Statistiche sugli embeddings nel database
2669
+ * Embedding statistics in the database
2219
2670
  */
2220
2671
  getEmbeddingStats() {
2221
2672
  const vectorSearch2 = getVectorSearch();
2222
2673
  return vectorSearch2.getStats(this.db.db);
2223
2674
  }
2224
2675
  /**
2225
- * Inizializza il servizio di embedding (lazy, chiamare prima di hybridSearch)
2676
+ * Initialize the embedding service (lazy, call before hybridSearch)
2226
2677
  */
2227
2678
  async initializeEmbeddings() {
2228
2679
  const hybridSearch2 = getHybridSearch();
@@ -2230,10 +2681,10 @@ var KiroMemorySDK = class {
2230
2681
  return getEmbeddingService().isAvailable();
2231
2682
  }
2232
2683
  /**
2233
- * Contesto smart con ranking a 4 segnali e budget token.
2684
+ * Smart context with 4-signal ranking and token budget.
2234
2685
  *
2235
- * Se query presente: usa HybridSearch con SEARCH_WEIGHTS.
2236
- * Se senza query: ranking per recency + project match (CONTEXT_WEIGHTS).
2686
+ * If query present: uses HybridSearch with SEARCH_WEIGHTS.
2687
+ * If no query: ranking by recency + project match (CONTEXT_WEIGHTS).
2237
2688
  */
2238
2689
  async getSmartContext(options = {}) {
2239
2690
  const tokenBudget = options.tokenBudget || parseInt(process.env.KIRO_MEMORY_CONTEXT_TOKENS || "0", 10) || 2e3;
@@ -2307,8 +2758,8 @@ var KiroMemorySDK = class {
2307
2758
  };
2308
2759
  }
2309
2760
  /**
2310
- * Rileva osservazioni stale (file modificati dopo la creazione) e le marca nel DB.
2311
- * Ritorna il numero di osservazioni marcate come stale.
2761
+ * Detect stale observations (files modified after creation) and mark them in DB.
2762
+ * Returns the number of observations marked as stale.
2312
2763
  */
2313
2764
  async detectStaleObservations() {
2314
2765
  const staleObs = getStaleObservations(this.db.db, this.project);
@@ -2319,14 +2770,14 @@ var KiroMemorySDK = class {
2319
2770
  return staleObs.length;
2320
2771
  }
2321
2772
  /**
2322
- * Consolida osservazioni duplicate sullo stesso file e tipo.
2323
- * Raggruppa per (project, type, files_modified), mantiene la piu recente.
2773
+ * Consolidate duplicate observations on the same file and type.
2774
+ * Groups by (project, type, files_modified), keeps the most recent.
2324
2775
  */
2325
2776
  async consolidateObservations(options = {}) {
2326
2777
  return consolidateObservations(this.db.db, this.project, options);
2327
2778
  }
2328
2779
  /**
2329
- * Statistiche decay: totale, stale, mai accedute, accedute di recente.
2780
+ * Decay statistics: total, stale, never accessed, recently accessed.
2330
2781
  */
2331
2782
  async getDecayStats() {
2332
2783
  const total = this.db.db.query(
@@ -2345,8 +2796,8 @@ var KiroMemorySDK = class {
2345
2796
  return { total, stale, neverAccessed, recentlyAccessed };
2346
2797
  }
2347
2798
  /**
2348
- * Crea un checkpoint strutturato per resume sessione.
2349
- * Salva automaticamente un context_snapshot con le ultime 10 osservazioni.
2799
+ * Create a structured checkpoint for session resume.
2800
+ * Automatically saves a context_snapshot with the last 10 observations.
2350
2801
  */
2351
2802
  async createCheckpoint(sessionId, data) {
2352
2803
  const recentObs = getObservationsByProject(this.db.db, this.project, 10);
@@ -2363,21 +2814,21 @@ var KiroMemorySDK = class {
2363
2814
  });
2364
2815
  }
2365
2816
  /**
2366
- * Recupera l'ultimo checkpoint di una sessione specifica.
2817
+ * Retrieve the latest checkpoint of a specific session.
2367
2818
  */
2368
2819
  async getCheckpoint(sessionId) {
2369
2820
  return getLatestCheckpoint(this.db.db, sessionId);
2370
2821
  }
2371
2822
  /**
2372
- * Recupera l'ultimo checkpoint per il progetto corrente.
2373
- * Utile per resume automatico senza specificare session ID.
2823
+ * Retrieve the latest checkpoint for the current project.
2824
+ * Useful for automatic resume without specifying session ID.
2374
2825
  */
2375
2826
  async getLatestProjectCheckpoint() {
2376
2827
  return getLatestCheckpointByProject(this.db.db, this.project);
2377
2828
  }
2378
2829
  /**
2379
- * Genera un report di attività per il progetto corrente.
2380
- * Aggrega osservazioni, sessioni, summaries e file per un periodo temporale.
2830
+ * Generate an activity report for the current project.
2831
+ * Aggregates observations, sessions, summaries and files for a time period.
2381
2832
  */
2382
2833
  async generateReport(options) {
2383
2834
  const now = /* @__PURE__ */ new Date();
@@ -2393,6 +2844,66 @@ var KiroMemorySDK = class {
2393
2844
  }
2394
2845
  return getReportData(this.db.db, this.project, startEpoch, endEpoch);
2395
2846
  }
2847
+ /**
2848
+ * Lista osservazioni con keyset pagination.
2849
+ * Restituisce un oggetto { data, next_cursor, has_more }.
2850
+ *
2851
+ * Esempio:
2852
+ * const page1 = await sdk.listObservations({ limit: 50 });
2853
+ * const page2 = await sdk.listObservations({ cursor: page1.next_cursor });
2854
+ */
2855
+ async listObservations(options = {}) {
2856
+ const limit = Math.min(Math.max(options.limit ?? 50, 1), 200);
2857
+ const project = options.project ?? this.project;
2858
+ let rows;
2859
+ if (options.cursor) {
2860
+ const decoded = decodeCursor(options.cursor);
2861
+ if (!decoded) throw new Error("Cursor non valido");
2862
+ const sql = project ? `SELECT * FROM observations
2863
+ WHERE project = ? AND (created_at_epoch < ? OR (created_at_epoch = ? AND id < ?))
2864
+ ORDER BY created_at_epoch DESC, id DESC
2865
+ LIMIT ?` : `SELECT * FROM observations
2866
+ WHERE (created_at_epoch < ? OR (created_at_epoch = ? AND id < ?))
2867
+ ORDER BY created_at_epoch DESC, id DESC
2868
+ LIMIT ?`;
2869
+ rows = project ? this.db.db.query(sql).all(project, decoded.epoch, decoded.epoch, decoded.id, limit) : this.db.db.query(sql).all(decoded.epoch, decoded.epoch, decoded.id, limit);
2870
+ } else {
2871
+ const sql = project ? "SELECT * FROM observations WHERE project = ? ORDER BY created_at_epoch DESC, id DESC LIMIT ?" : "SELECT * FROM observations ORDER BY created_at_epoch DESC, id DESC LIMIT ?";
2872
+ rows = project ? this.db.db.query(sql).all(project, limit) : this.db.db.query(sql).all(limit);
2873
+ }
2874
+ const next_cursor = rows.length >= limit ? encodeCursor(rows[rows.length - 1].id, rows[rows.length - 1].created_at_epoch) : null;
2875
+ return { data: rows, next_cursor, has_more: next_cursor !== null };
2876
+ }
2877
+ /**
2878
+ * Lista sommari con keyset pagination.
2879
+ * Restituisce un oggetto { data, next_cursor, has_more }.
2880
+ *
2881
+ * Esempio:
2882
+ * const page1 = await sdk.listSummaries({ limit: 20 });
2883
+ * const page2 = await sdk.listSummaries({ cursor: page1.next_cursor });
2884
+ */
2885
+ async listSummaries(options = {}) {
2886
+ const limit = Math.min(Math.max(options.limit ?? 20, 1), 200);
2887
+ const project = options.project ?? this.project;
2888
+ let rows;
2889
+ if (options.cursor) {
2890
+ const decoded = decodeCursor(options.cursor);
2891
+ if (!decoded) throw new Error("Cursor non valido");
2892
+ const sql = project ? `SELECT * FROM summaries
2893
+ WHERE project = ? AND (created_at_epoch < ? OR (created_at_epoch = ? AND id < ?))
2894
+ ORDER BY created_at_epoch DESC, id DESC
2895
+ LIMIT ?` : `SELECT * FROM summaries
2896
+ WHERE (created_at_epoch < ? OR (created_at_epoch = ? AND id < ?))
2897
+ ORDER BY created_at_epoch DESC, id DESC
2898
+ LIMIT ?`;
2899
+ rows = project ? this.db.db.query(sql).all(project, decoded.epoch, decoded.epoch, decoded.id, limit) : this.db.db.query(sql).all(decoded.epoch, decoded.epoch, decoded.id, limit);
2900
+ } else {
2901
+ const sql = project ? "SELECT * FROM summaries WHERE project = ? ORDER BY created_at_epoch DESC, id DESC LIMIT ?" : "SELECT * FROM summaries ORDER BY created_at_epoch DESC, id DESC LIMIT ?";
2902
+ rows = project ? this.db.db.query(sql).all(project, limit) : this.db.db.query(sql).all(limit);
2903
+ }
2904
+ const next_cursor = rows.length >= limit ? encodeCursor(rows[rows.length - 1].id, rows[rows.length - 1].created_at_epoch) : null;
2905
+ return { data: rows, next_cursor, has_more: next_cursor !== null };
2906
+ }
2396
2907
  /**
2397
2908
  * Getter for direct database access (for API routes)
2398
2909
  */
@@ -2409,8 +2920,6 @@ var KiroMemorySDK = class {
2409
2920
  function createKiroMemory(config) {
2410
2921
  return new KiroMemorySDK(config);
2411
2922
  }
2412
- var ContextKitSDK = KiroMemorySDK;
2413
- var createContextKit = createKiroMemory;
2414
2923
 
2415
2924
  // src/index.ts
2416
2925
  init_Search();
@@ -2461,7 +2970,7 @@ async function readStdin() {
2461
2970
  }
2462
2971
  resolve(JSON.parse(data));
2463
2972
  } catch (err) {
2464
- reject(new Error(`Errore parsing stdin JSON: ${err}`));
2973
+ reject(new Error(`Error parsing stdin JSON: ${err}`));
2465
2974
  }
2466
2975
  });
2467
2976
  process.stdin.on("error", (err) => {
@@ -2486,19 +2995,19 @@ function detectProject(cwd) {
2486
2995
  function formatContext(data) {
2487
2996
  let output = "";
2488
2997
  if (data.summaries && data.summaries.length > 0) {
2489
- output += "## Sessioni Precedenti\n\n";
2998
+ output += "## Previous Sessions\n\n";
2490
2999
  data.summaries.slice(0, 3).forEach((sum) => {
2491
- if (sum.learned) output += `- **Appreso**: ${sum.learned}
3000
+ if (sum.learned) output += `- **Learned**: ${sum.learned}
2492
3001
  `;
2493
- if (sum.completed) output += `- **Completato**: ${sum.completed}
3002
+ if (sum.completed) output += `- **Completed**: ${sum.completed}
2494
3003
  `;
2495
- if (sum.next_steps) output += `- **Prossimi passi**: ${sum.next_steps}
3004
+ if (sum.next_steps) output += `- **Next steps**: ${sum.next_steps}
2496
3005
  `;
2497
3006
  output += "\n";
2498
3007
  });
2499
3008
  }
2500
3009
  if (data.observations && data.observations.length > 0) {
2501
- output += "## Osservazioni Recenti\n\n";
3010
+ output += "## Recent Observations\n\n";
2502
3011
  data.observations.slice(0, 10).forEach((obs) => {
2503
3012
  const text = obs.text ? obs.text.substring(0, 150) : "";
2504
3013
  output += `- **[${obs.type || "obs"}] ${obs.title}**: ${text}
@@ -2519,35 +3028,29 @@ async function runHook(name, handler) {
2519
3028
  }
2520
3029
  debugLog(name, "stdin", input);
2521
3030
  await handler(input);
2522
- debugLog(name, "completato", { success: true });
3031
+ debugLog(name, "completed", { success: true });
2523
3032
  process.exit(0);
2524
3033
  } catch (error) {
2525
- debugLog(name, "errore", { error: String(error) });
2526
- process.stderr.write(`[kiro-memory:${name}] Errore: ${error}
3034
+ debugLog(name, "error", { error: String(error) });
3035
+ process.stderr.write(`[kiro-memory:${name}] Error: ${error}
2527
3036
  `);
2528
3037
  process.exit(0);
2529
3038
  }
2530
3039
  }
2531
3040
 
2532
3041
  // src/index.ts
2533
- var VERSION = "1.5.0";
3042
+ var VERSION = "3.0.0";
2534
3043
  export {
2535
- KiroMemoryDatabase as ContextKitDatabase,
2536
- ContextKitSDK,
2537
- DatabaseManager,
2538
3044
  KiroMemoryDatabase,
2539
3045
  KiroMemorySDK,
2540
3046
  LogLevel,
2541
3047
  VERSION,
2542
- createContextKit,
2543
3048
  createKiroMemory,
2544
3049
  detectProject,
2545
3050
  formatContext,
2546
- getDatabase,
2547
3051
  getObservationsByIds,
2548
3052
  getProjectStats,
2549
3053
  getTimeline,
2550
- initializeDatabase,
2551
3054
  logger,
2552
3055
  readStdin,
2553
3056
  runHook,