kiro-memory 2.1.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 (33) hide show
  1. package/README.md +5 -1
  2. package/package.json +3 -3
  3. package/plugin/dist/cli/contextkit.js +2315 -180
  4. package/plugin/dist/hooks/agentSpawn.js +548 -49
  5. package/plugin/dist/hooks/kiro-hooks.js +548 -49
  6. package/plugin/dist/hooks/postToolUse.js +556 -56
  7. package/plugin/dist/hooks/stop.js +548 -49
  8. package/plugin/dist/hooks/userPromptSubmit.js +551 -50
  9. package/plugin/dist/index.js +549 -50
  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 +548 -49
  16. package/plugin/dist/servers/mcp-server.js +4461 -397
  17. package/plugin/dist/services/search/EmbeddingService.js +64 -20
  18. package/plugin/dist/services/search/HybridSearch.js +380 -38
  19. package/plugin/dist/services/search/VectorSearch.js +65 -21
  20. package/plugin/dist/services/search/index.js +380 -38
  21. package/plugin/dist/services/sqlite/Backup.js +416 -0
  22. package/plugin/dist/services/sqlite/Database.js +71 -0
  23. package/plugin/dist/services/sqlite/ImportExport.js +452 -0
  24. package/plugin/dist/services/sqlite/Observations.js +291 -7
  25. package/plugin/dist/services/sqlite/Prompts.js +1 -1
  26. package/plugin/dist/services/sqlite/Search.js +10 -10
  27. package/plugin/dist/services/sqlite/Summaries.js +4 -4
  28. package/plugin/dist/services/sqlite/index.js +1323 -28
  29. package/plugin/dist/viewer.css +1 -1
  30. package/plugin/dist/viewer.js +16 -8
  31. package/plugin/dist/viewer.js.map +4 -4
  32. package/plugin/dist/worker-service.js +326 -75
  33. package/plugin/dist/worker-service.js.map +4 -4
@@ -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) {
@@ -121,7 +417,7 @@ function consolidateObservations(db, project, options = {}) {
121
417
  const obsIds = group.ids.split(",").map(Number);
122
418
  const placeholders = obsIds.map(() => "?").join(",");
123
419
  const observations = db.query(
124
- `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`
125
421
  ).all(...obsIds);
126
422
  if (observations.length < minGroupSize) continue;
127
423
  const keeper = observations[0];
@@ -152,6 +448,8 @@ function consolidateObservations(db, project, options = {}) {
152
448
  var init_Observations = __esm({
153
449
  "src/services/sqlite/Observations.ts"() {
154
450
  "use strict";
451
+ init_secrets();
452
+ init_categorizer();
155
453
  }
156
454
  });
157
455
 
@@ -271,7 +569,7 @@ function searchObservationsLIKE(db, query, filters = {}) {
271
569
  sql += " AND created_at_epoch <= ?";
272
570
  params.push(filters.dateEnd);
273
571
  }
274
- sql += " ORDER BY created_at_epoch DESC LIMIT ?";
572
+ sql += " ORDER BY created_at_epoch DESC, id DESC LIMIT ?";
275
573
  params.push(limit);
276
574
  const stmt = db.query(sql);
277
575
  return stmt.all(...params);
@@ -296,7 +594,7 @@ function searchSummariesFiltered(db, query, filters = {}) {
296
594
  sql += " AND created_at_epoch <= ?";
297
595
  params.push(filters.dateEnd);
298
596
  }
299
- sql += " ORDER BY created_at_epoch DESC LIMIT ?";
597
+ sql += " ORDER BY created_at_epoch DESC, id DESC LIMIT ?";
300
598
  params.push(limit);
301
599
  const stmt = db.query(sql);
302
600
  return stmt.all(...params);
@@ -306,7 +604,7 @@ function getObservationsByIds(db, ids) {
306
604
  const validIds = ids.filter((id) => typeof id === "number" && Number.isInteger(id) && id > 0).slice(0, 500);
307
605
  if (validIds.length === 0) return [];
308
606
  const placeholders = validIds.map(() => "?").join(",");
309
- 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`;
310
608
  const stmt = db.query(sql);
311
609
  return stmt.all(...validIds);
312
610
  }
@@ -318,11 +616,11 @@ function getTimeline(db, anchorId, depthBefore = 5, depthAfter = 5) {
318
616
  const beforeStmt = db.query(`
319
617
  SELECT id, 'observation' as type, title, text as content, project, created_at, created_at_epoch
320
618
  FROM observations
321
- WHERE created_at_epoch < ?
322
- 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
323
621
  LIMIT ?
324
622
  `);
325
- const before = beforeStmt.all(anchorEpoch, depthBefore).reverse();
623
+ const before = beforeStmt.all(anchorEpoch, anchorEpoch, anchorId, depthBefore).reverse();
326
624
  const selfStmt = db.query(`
327
625
  SELECT id, 'observation' as type, title, text as content, project, created_at, created_at_epoch
328
626
  FROM observations WHERE id = ?
@@ -331,11 +629,11 @@ function getTimeline(db, anchorId, depthBefore = 5, depthAfter = 5) {
331
629
  const afterStmt = db.query(`
332
630
  SELECT id, 'observation' as type, title, text as content, project, created_at, created_at_epoch
333
631
  FROM observations
334
- WHERE created_at_epoch > ?
335
- 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
336
634
  LIMIT ?
337
635
  `);
338
- const after = afterStmt.all(anchorEpoch, depthAfter);
636
+ const after = afterStmt.all(anchorEpoch, anchorEpoch, anchorId, depthAfter);
339
637
  return [...before, ...self, ...after];
340
638
  }
341
639
  function getProjectStats(db, project) {
@@ -378,7 +676,7 @@ function getStaleObservations(db, project) {
378
676
  const rows = db.query(`
379
677
  SELECT * FROM observations
380
678
  WHERE project = ? AND files_modified IS NOT NULL AND files_modified != ''
381
- ORDER BY created_at_epoch DESC
679
+ ORDER BY created_at_epoch DESC, id DESC
382
680
  LIMIT 500
383
681
  `).all(project);
384
682
  const staleObs = [];
@@ -1045,11 +1343,104 @@ var MigrationRunner = class {
1045
1343
  db.run("CREATE INDEX IF NOT EXISTS idx_summaries_project_epoch ON summaries(project, created_at_epoch DESC)");
1046
1344
  db.run("CREATE INDEX IF NOT EXISTS idx_prompts_project_epoch ON prompts(project, created_at_epoch DESC)");
1047
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
+ }
1048
1417
  }
1049
1418
  ];
1050
1419
  }
1051
1420
  };
1052
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");
1426
+ }
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
+ }
1442
+ }
1443
+
1053
1444
  // src/services/sqlite/Sessions.ts
1054
1445
  function createSession(db, contentSessionId, project, userPrompt) {
1055
1446
  const now = /* @__PURE__ */ new Date();
@@ -1093,16 +1484,16 @@ function createSummary(db, sessionId, project, request, investigated, learned, c
1093
1484
  }
1094
1485
  function getSummariesByProject(db, project, limit = 50) {
1095
1486
  const query = db.query(
1096
- "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 ?"
1097
1488
  );
1098
1489
  return query.all(project, limit);
1099
1490
  }
1100
1491
  function searchSummaries(db, searchTerm, project) {
1101
1492
  const sql = project ? `SELECT * FROM summaries
1102
1493
  WHERE project = ? AND (request LIKE ? ESCAPE '\\' OR learned LIKE ? ESCAPE '\\' OR completed LIKE ? ESCAPE '\\' OR notes LIKE ? ESCAPE '\\')
1103
- ORDER BY created_at_epoch DESC` : `SELECT * FROM summaries
1494
+ ORDER BY created_at_epoch DESC, id DESC` : `SELECT * FROM summaries
1104
1495
  WHERE request LIKE ? ESCAPE '\\' OR learned LIKE ? ESCAPE '\\' OR completed LIKE ? ESCAPE '\\' OR notes LIKE ? ESCAPE '\\'
1105
- ORDER BY created_at_epoch DESC`;
1496
+ ORDER BY created_at_epoch DESC, id DESC`;
1106
1497
  const pattern = `%${escapeLikePattern2(searchTerm)}%`;
1107
1498
  const query = db.query(sql);
1108
1499
  if (project) {
@@ -1124,7 +1515,7 @@ function createPrompt(db, contentSessionId, project, promptNumber, promptText) {
1124
1515
  }
1125
1516
  function getPromptsByProject(db, project, limit = 100) {
1126
1517
  const query = db.query(
1127
- "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 ?"
1128
1519
  );
1129
1520
  return query.all(project, limit);
1130
1521
  }
@@ -1152,13 +1543,13 @@ function createCheckpoint(db, sessionId, project, data) {
1152
1543
  }
1153
1544
  function getLatestCheckpoint(db, sessionId) {
1154
1545
  const query = db.query(
1155
- "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"
1156
1547
  );
1157
1548
  return query.get(sessionId);
1158
1549
  }
1159
1550
  function getLatestCheckpointByProject(db, project) {
1160
1551
  const query = db.query(
1161
- "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"
1162
1553
  );
1163
1554
  return query.get(project);
1164
1555
  }
@@ -1220,9 +1611,9 @@ function getReportData(db, project, startEpoch, endEpoch) {
1220
1611
  const staleCount = (project ? db.query(staleSql).get(project, startEpoch, endEpoch)?.count : db.query(staleSql).get(startEpoch, endEpoch)?.count) || 0;
1221
1612
  const summarySql = project ? `SELECT learned, completed, next_steps FROM summaries
1222
1613
  WHERE project = ? AND created_at_epoch >= ? AND created_at_epoch <= ?
1223
- 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
1224
1615
  WHERE created_at_epoch >= ? AND created_at_epoch <= ?
1225
- ORDER BY created_at_epoch DESC`;
1616
+ ORDER BY created_at_epoch DESC, id DESC`;
1226
1617
  const summaryRows = project ? db.query(summarySql).all(project, startEpoch, endEpoch) : db.query(summarySql).all(startEpoch, endEpoch);
1227
1618
  const topLearnings = [];
1228
1619
  const completedTasks = [];
@@ -1287,20 +1678,61 @@ function getReportData(db, project, startEpoch, endEpoch) {
1287
1678
  // src/services/sqlite/index.ts
1288
1679
  init_Search();
1289
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
+
1290
1688
  // src/sdk/index.ts
1291
1689
  init_Observations();
1292
1690
  import { createHash } from "crypto";
1293
1691
  init_Search();
1294
1692
 
1295
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"]);
1296
1709
  var EmbeddingService = class {
1297
1710
  provider = null;
1298
1711
  model = null;
1299
1712
  initialized = false;
1300
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
+ }
1301
1733
  /**
1302
1734
  * Initialize the embedding service.
1303
- * Tries fastembed, then @huggingface/transformers, then fallback to null.
1735
+ * Tries fastembed (when compatible), then @huggingface/transformers, then falls back to null.
1304
1736
  */
1305
1737
  async initialize() {
1306
1738
  if (this.initialized) return this.provider !== null;
@@ -1311,32 +1743,35 @@ var EmbeddingService = class {
1311
1743
  return result;
1312
1744
  }
1313
1745
  async _doInitialize() {
1314
- try {
1315
- const fastembed = await import("fastembed");
1316
- const EmbeddingModel = fastembed.EmbeddingModel || fastembed.default?.EmbeddingModel;
1317
- const FlagEmbedding = fastembed.FlagEmbedding || fastembed.default?.FlagEmbedding;
1318
- if (FlagEmbedding && EmbeddingModel) {
1319
- this.model = await FlagEmbedding.init({
1320
- model: EmbeddingModel.BGESmallENV15
1321
- });
1322
- this.provider = "fastembed";
1323
- this.initialized = true;
1324
- logger.info("EMBEDDING", "Initialized with fastembed (BGE-small-en-v1.5)");
1325
- 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}`);
1326
1763
  }
1327
- } catch (error) {
1328
- logger.debug("EMBEDDING", `fastembed not available: ${error}`);
1329
1764
  }
1330
1765
  try {
1331
1766
  const transformers = await import("@huggingface/transformers");
1332
1767
  const pipeline = transformers.pipeline || transformers.default?.pipeline;
1333
1768
  if (pipeline) {
1334
- this.model = await pipeline("feature-extraction", "Xenova/all-MiniLM-L6-v2", {
1769
+ this.model = await pipeline("feature-extraction", this.config.modelId, {
1335
1770
  quantized: true
1336
1771
  });
1337
1772
  this.provider = "transformers";
1338
1773
  this.initialized = true;
1339
- logger.info("EMBEDDING", "Initialized with @huggingface/transformers (all-MiniLM-L6-v2)");
1774
+ logger.info("EMBEDDING", `Initialized with @huggingface/transformers (${this.config.modelId})`);
1340
1775
  return true;
1341
1776
  }
1342
1777
  } catch (error) {
@@ -1349,7 +1784,7 @@ var EmbeddingService = class {
1349
1784
  }
1350
1785
  /**
1351
1786
  * Generate embedding for a single text.
1352
- * Returns Float32Array with 384 dimensions, or null if not available.
1787
+ * Returns Float32Array with configured dimensions, or null if not available.
1353
1788
  */
1354
1789
  async embed(text) {
1355
1790
  if (!this.initialized) await this.initialize();
@@ -1400,10 +1835,17 @@ var EmbeddingService = class {
1400
1835
  return this.provider;
1401
1836
  }
1402
1837
  /**
1403
- * Embedding vector dimensions.
1838
+ * Embedding vector dimensions for the active model configuration.
1404
1839
  */
1405
1840
  getDimensions() {
1406
- return 384;
1841
+ return this.config.dimensions;
1842
+ }
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;
1407
1849
  }
1408
1850
  // --- Batch implementations ---
1409
1851
  /**
@@ -1622,7 +2064,7 @@ var VectorSearch = class {
1622
2064
  `).all(batchSize);
1623
2065
  if (rows.length === 0) return 0;
1624
2066
  let count = 0;
1625
- const model = embeddingService2.getProvider() || "unknown";
2067
+ const model = embeddingService2.getModelName();
1626
2068
  for (const row of rows) {
1627
2069
  const parts = [row.title];
1628
2070
  if (row.text) parts.push(row.text);
@@ -1846,9 +2288,6 @@ function getHybridSearch() {
1846
2288
  return hybridSearch;
1847
2289
  }
1848
2290
 
1849
- // src/types/worker-types.ts
1850
- var KNOWLEDGE_TYPES = ["constraint", "decision", "heuristic", "rejected"];
1851
-
1852
2291
  // src/sdk/index.ts
1853
2292
  var KiroMemorySDK = class {
1854
2293
  db;
@@ -2405,6 +2844,66 @@ var KiroMemorySDK = class {
2405
2844
  }
2406
2845
  return getReportData(this.db.db, this.project, startEpoch, endEpoch);
2407
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
+ }
2408
2907
  /**
2409
2908
  * Getter for direct database access (for API routes)
2410
2909
  */