kiro-memory 2.1.0 → 3.0.1

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 +2342 -183
  4. package/plugin/dist/hooks/agentSpawn.js +575 -52
  5. package/plugin/dist/hooks/kiro-hooks.js +575 -52
  6. package/plugin/dist/hooks/postToolUse.js +583 -59
  7. package/plugin/dist/hooks/stop.js +575 -52
  8. package/plugin/dist/hooks/userPromptSubmit.js +578 -53
  9. package/plugin/dist/index.js +576 -53
  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 +575 -52
  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 +98 -3
  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 +1350 -31
  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
@@ -16,6 +16,290 @@ var __export = (target, all) => {
16
16
  __defProp(target, name, { get: all[name], enumerable: true });
17
17
  };
18
18
 
19
+ // src/utils/secrets.ts
20
+ function redactSecrets(text) {
21
+ if (!text) return text;
22
+ let redacted = text;
23
+ for (const { pattern } of SECRET_PATTERNS) {
24
+ pattern.lastIndex = 0;
25
+ redacted = redacted.replace(pattern, (match) => {
26
+ const prefix = match.substring(0, Math.min(4, match.length));
27
+ return `${prefix}***REDACTED***`;
28
+ });
29
+ }
30
+ return redacted;
31
+ }
32
+ var SECRET_PATTERNS;
33
+ var init_secrets = __esm({
34
+ "src/utils/secrets.ts"() {
35
+ "use strict";
36
+ SECRET_PATTERNS = [
37
+ // AWS Access Keys (AKIA, ABIA, ACCA, ASIA prefixes + 16 alphanumeric chars)
38
+ { name: "aws-key", pattern: /(?:AKIA|ABIA|ACCA|ASIA)[A-Z0-9]{16}/g },
39
+ // JWT tokens (three base64url segments separated by dots)
40
+ { name: "jwt", pattern: /eyJ[a-zA-Z0-9_-]{10,}\.eyJ[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}/g },
41
+ // Generic API keys in key=value or key: value assignments
42
+ { name: "api-key", pattern: /(?:api[_-]?key|apikey|api[_-]?secret)\s*[:=]\s*['"]?([a-zA-Z0-9_\-]{20,})['"]?/gi },
43
+ // Password/secret/token in variable assignments
44
+ { name: "credential", pattern: /(?:password|passwd|pwd|secret|token|auth[_-]?token|access[_-]?token|bearer)\s*[:=]\s*['"]?([^\s'"]{8,})['"]?/gi },
45
+ // Credentials embedded in URLs (user:pass@host)
46
+ { name: "url-credential", pattern: /(?:https?:\/\/)([^:]+):([^@]+)@/g },
47
+ // PEM-encoded private keys (RSA, EC, DSA, OpenSSH)
48
+ { name: "private-key", pattern: /-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/g },
49
+ // GitHub personal access tokens (ghp_, gho_, ghu_, ghs_, ghr_ prefixes)
50
+ { name: "github-token", pattern: /gh[pousr]_[a-zA-Z0-9]{36,}/g },
51
+ // Slack bot/user/app tokens
52
+ { name: "slack-token", pattern: /xox[bpoas]-[a-zA-Z0-9-]{10,}/g },
53
+ // HTTP Authorization Bearer header values
54
+ { name: "bearer-header", pattern: /\bBearer\s+([a-zA-Z0-9_\-\.]{20,})/g },
55
+ // Generic hex secrets (32+ hex chars after a key/secret/token/password label)
56
+ { name: "hex-secret", pattern: /(?:key|secret|token|password)\s*[:=]\s*['"]?([0-9a-f]{32,})['"]?/gi }
57
+ ];
58
+ }
59
+ });
60
+
61
+ // src/utils/categorizer.ts
62
+ function categorize(input) {
63
+ const scores = /* @__PURE__ */ new Map();
64
+ const searchText = [
65
+ input.title,
66
+ input.text || "",
67
+ input.narrative || "",
68
+ input.concepts || ""
69
+ ].join(" ").toLowerCase();
70
+ const allFiles = [input.filesModified || "", input.filesRead || ""].join(",");
71
+ for (const rule of CATEGORY_RULES) {
72
+ let score = 0;
73
+ for (const kw of rule.keywords) {
74
+ if (searchText.includes(kw.toLowerCase())) {
75
+ score += rule.weight;
76
+ }
77
+ }
78
+ if (rule.types && rule.types.includes(input.type)) {
79
+ score += rule.weight * 2;
80
+ }
81
+ if (rule.filePatterns && allFiles) {
82
+ for (const pattern of rule.filePatterns) {
83
+ if (pattern.test(allFiles)) {
84
+ score += rule.weight;
85
+ }
86
+ }
87
+ }
88
+ if (score > 0) {
89
+ scores.set(rule.category, (scores.get(rule.category) || 0) + score);
90
+ }
91
+ }
92
+ let bestCategory = "general";
93
+ let bestScore = 0;
94
+ for (const [category, score] of scores) {
95
+ if (score > bestScore) {
96
+ bestScore = score;
97
+ bestCategory = category;
98
+ }
99
+ }
100
+ return bestCategory;
101
+ }
102
+ var CATEGORY_RULES;
103
+ var init_categorizer = __esm({
104
+ "src/utils/categorizer.ts"() {
105
+ "use strict";
106
+ CATEGORY_RULES = [
107
+ {
108
+ category: "security",
109
+ keywords: [
110
+ "security",
111
+ "vulnerability",
112
+ "cve",
113
+ "xss",
114
+ "csrf",
115
+ "injection",
116
+ "sanitize",
117
+ "escape",
118
+ "auth",
119
+ "authentication",
120
+ "authorization",
121
+ "permission",
122
+ "helmet",
123
+ "cors",
124
+ "rate-limit",
125
+ "token",
126
+ "encrypt",
127
+ "decrypt",
128
+ "secret",
129
+ "redact",
130
+ "owasp"
131
+ ],
132
+ filePatterns: [/security/i, /auth/i, /secrets?\.ts/i],
133
+ weight: 10
134
+ },
135
+ {
136
+ category: "testing",
137
+ keywords: [
138
+ "test",
139
+ "spec",
140
+ "expect",
141
+ "assert",
142
+ "mock",
143
+ "stub",
144
+ "fixture",
145
+ "coverage",
146
+ "jest",
147
+ "vitest",
148
+ "bun test",
149
+ "unit test",
150
+ "integration test",
151
+ "e2e"
152
+ ],
153
+ types: ["test"],
154
+ filePatterns: [/\.test\./i, /\.spec\./i, /tests?\//i, /__tests__/i],
155
+ weight: 8
156
+ },
157
+ {
158
+ category: "debugging",
159
+ keywords: [
160
+ "debug",
161
+ "fix",
162
+ "bug",
163
+ "error",
164
+ "crash",
165
+ "stacktrace",
166
+ "stack trace",
167
+ "exception",
168
+ "breakpoint",
169
+ "investigate",
170
+ "root cause",
171
+ "troubleshoot",
172
+ "diagnose",
173
+ "bisect",
174
+ "regression"
175
+ ],
176
+ types: ["bugfix"],
177
+ weight: 8
178
+ },
179
+ {
180
+ category: "architecture",
181
+ keywords: [
182
+ "architect",
183
+ "design",
184
+ "pattern",
185
+ "modular",
186
+ "migration",
187
+ "schema",
188
+ "database",
189
+ "api design",
190
+ "abstract",
191
+ "dependency injection",
192
+ "singleton",
193
+ "factory",
194
+ "observer",
195
+ "middleware",
196
+ "pipeline",
197
+ "microservice",
198
+ "monolith"
199
+ ],
200
+ types: ["decision", "constraint"],
201
+ weight: 7
202
+ },
203
+ {
204
+ category: "refactoring",
205
+ keywords: [
206
+ "refactor",
207
+ "rename",
208
+ "extract",
209
+ "inline",
210
+ "move",
211
+ "split",
212
+ "merge",
213
+ "simplify",
214
+ "cleanup",
215
+ "clean up",
216
+ "dead code",
217
+ "consolidate",
218
+ "reorganize",
219
+ "restructure",
220
+ "decouple"
221
+ ],
222
+ weight: 6
223
+ },
224
+ {
225
+ category: "config",
226
+ keywords: [
227
+ "config",
228
+ "configuration",
229
+ "env",
230
+ "environment",
231
+ "dotenv",
232
+ ".env",
233
+ "settings",
234
+ "tsconfig",
235
+ "eslint",
236
+ "prettier",
237
+ "webpack",
238
+ "vite",
239
+ "esbuild",
240
+ "docker",
241
+ "ci/cd",
242
+ "github actions",
243
+ "deploy",
244
+ "build",
245
+ "bundle",
246
+ "package.json"
247
+ ],
248
+ filePatterns: [
249
+ /\.config\./i,
250
+ /\.env/i,
251
+ /tsconfig/i,
252
+ /\.ya?ml/i,
253
+ /Dockerfile/i,
254
+ /docker-compose/i
255
+ ],
256
+ weight: 5
257
+ },
258
+ {
259
+ category: "docs",
260
+ keywords: [
261
+ "document",
262
+ "readme",
263
+ "changelog",
264
+ "jsdoc",
265
+ "comment",
266
+ "explain",
267
+ "guide",
268
+ "tutorial",
269
+ "api doc",
270
+ "openapi",
271
+ "swagger"
272
+ ],
273
+ types: ["docs"],
274
+ filePatterns: [/\.md$/i, /docs?\//i, /readme/i, /changelog/i],
275
+ weight: 5
276
+ },
277
+ {
278
+ category: "feature-dev",
279
+ keywords: [
280
+ "feature",
281
+ "implement",
282
+ "add",
283
+ "create",
284
+ "new",
285
+ "endpoint",
286
+ "component",
287
+ "module",
288
+ "service",
289
+ "handler",
290
+ "route",
291
+ "hook",
292
+ "plugin",
293
+ "integration"
294
+ ],
295
+ types: ["feature", "file-write"],
296
+ weight: 3
297
+ // lowest — generic catch-all for development
298
+ }
299
+ ];
300
+ }
301
+ });
302
+
19
303
  // src/services/sqlite/Observations.ts
20
304
  var Observations_exports = {};
21
305
  __export(Observations_exports, {
@@ -41,11 +325,23 @@ function isDuplicateObservation(db, contentHash, windowMs = 3e4) {
41
325
  }
42
326
  function createObservation(db, memorySessionId, project, type, title, subtitle, text, narrative, facts, concepts, filesRead, filesModified, promptNumber, contentHash = null, discoveryTokens = 0) {
43
327
  const now = /* @__PURE__ */ new Date();
328
+ const safeTitle = redactSecrets(title);
329
+ const safeText = text ? redactSecrets(text) : text;
330
+ const safeNarrative = narrative ? redactSecrets(narrative) : narrative;
331
+ const autoCategory = categorize({
332
+ type,
333
+ title: safeTitle,
334
+ text: safeText,
335
+ narrative: safeNarrative,
336
+ concepts,
337
+ filesModified,
338
+ filesRead
339
+ });
44
340
  const result = db.run(
45
341
  `INSERT INTO observations
46
- (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)
47
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
48
- [memorySessionId, project, type, title, subtitle, text, narrative, facts, concepts, filesRead, filesModified, promptNumber, now.toISOString(), now.getTime(), contentHash, discoveryTokens]
342
+ (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)
343
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
344
+ [memorySessionId, project, type, safeTitle, subtitle, safeText, safeNarrative, facts, concepts, filesRead, filesModified, promptNumber, now.toISOString(), now.getTime(), contentHash, discoveryTokens, autoCategory]
49
345
  );
50
346
  return Number(result.lastInsertRowid);
51
347
  }
@@ -57,16 +353,16 @@ function getObservationsBySession(db, memorySessionId) {
57
353
  }
58
354
  function getObservationsByProject(db, project, limit = 100) {
59
355
  const query = db.query(
60
- "SELECT * FROM observations WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ?"
356
+ "SELECT * FROM observations WHERE project = ? ORDER BY created_at_epoch DESC, id DESC LIMIT ?"
61
357
  );
62
358
  return query.all(project, limit);
63
359
  }
64
360
  function searchObservations(db, searchTerm, project) {
65
361
  const sql = project ? `SELECT * FROM observations
66
362
  WHERE project = ? AND (title LIKE ? ESCAPE '\\' OR text LIKE ? ESCAPE '\\' OR narrative LIKE ? ESCAPE '\\')
67
- ORDER BY created_at_epoch DESC` : `SELECT * FROM observations
363
+ ORDER BY created_at_epoch DESC, id DESC` : `SELECT * FROM observations
68
364
  WHERE title LIKE ? ESCAPE '\\' OR text LIKE ? ESCAPE '\\' OR narrative LIKE ? ESCAPE '\\'
69
- ORDER BY created_at_epoch DESC`;
365
+ ORDER BY created_at_epoch DESC, id DESC`;
70
366
  const pattern = `%${escapeLikePattern(searchTerm)}%`;
71
367
  const query = db.query(sql);
72
368
  if (project) {
@@ -122,7 +418,7 @@ function consolidateObservations(db, project, options = {}) {
122
418
  const obsIds = group.ids.split(",").map(Number);
123
419
  const placeholders = obsIds.map(() => "?").join(",");
124
420
  const observations = db.query(
125
- `SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC`
421
+ `SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC, id DESC`
126
422
  ).all(...obsIds);
127
423
  if (observations.length < minGroupSize) continue;
128
424
  const keeper = observations[0];
@@ -153,6 +449,8 @@ function consolidateObservations(db, project, options = {}) {
153
449
  var init_Observations = __esm({
154
450
  "src/services/sqlite/Observations.ts"() {
155
451
  "use strict";
452
+ init_secrets();
453
+ init_categorizer();
156
454
  }
157
455
  });
158
456
 
@@ -272,7 +570,7 @@ function searchObservationsLIKE(db, query, filters = {}) {
272
570
  sql += " AND created_at_epoch <= ?";
273
571
  params.push(filters.dateEnd);
274
572
  }
275
- sql += " ORDER BY created_at_epoch DESC LIMIT ?";
573
+ sql += " ORDER BY created_at_epoch DESC, id DESC LIMIT ?";
276
574
  params.push(limit);
277
575
  const stmt = db.query(sql);
278
576
  return stmt.all(...params);
@@ -297,7 +595,7 @@ function searchSummariesFiltered(db, query, filters = {}) {
297
595
  sql += " AND created_at_epoch <= ?";
298
596
  params.push(filters.dateEnd);
299
597
  }
300
- sql += " ORDER BY created_at_epoch DESC LIMIT ?";
598
+ sql += " ORDER BY created_at_epoch DESC, id DESC LIMIT ?";
301
599
  params.push(limit);
302
600
  const stmt = db.query(sql);
303
601
  return stmt.all(...params);
@@ -307,7 +605,7 @@ function getObservationsByIds(db, ids) {
307
605
  const validIds = ids.filter((id) => typeof id === "number" && Number.isInteger(id) && id > 0).slice(0, 500);
308
606
  if (validIds.length === 0) return [];
309
607
  const placeholders = validIds.map(() => "?").join(",");
310
- const sql = `SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC`;
608
+ const sql = `SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC, id DESC`;
311
609
  const stmt = db.query(sql);
312
610
  return stmt.all(...validIds);
313
611
  }
@@ -319,11 +617,11 @@ function getTimeline(db, anchorId, depthBefore = 5, depthAfter = 5) {
319
617
  const beforeStmt = db.query(`
320
618
  SELECT id, 'observation' as type, title, text as content, project, created_at, created_at_epoch
321
619
  FROM observations
322
- WHERE created_at_epoch < ?
323
- ORDER BY created_at_epoch DESC
620
+ WHERE (created_at_epoch < ? OR (created_at_epoch = ? AND id < ?))
621
+ ORDER BY created_at_epoch DESC, id DESC
324
622
  LIMIT ?
325
623
  `);
326
- const before = beforeStmt.all(anchorEpoch, depthBefore).reverse();
624
+ const before = beforeStmt.all(anchorEpoch, anchorEpoch, anchorId, depthBefore).reverse();
327
625
  const selfStmt = db.query(`
328
626
  SELECT id, 'observation' as type, title, text as content, project, created_at, created_at_epoch
329
627
  FROM observations WHERE id = ?
@@ -332,11 +630,11 @@ function getTimeline(db, anchorId, depthBefore = 5, depthAfter = 5) {
332
630
  const afterStmt = db.query(`
333
631
  SELECT id, 'observation' as type, title, text as content, project, created_at, created_at_epoch
334
632
  FROM observations
335
- WHERE created_at_epoch > ?
336
- ORDER BY created_at_epoch ASC
633
+ WHERE (created_at_epoch > ? OR (created_at_epoch = ? AND id > ?))
634
+ ORDER BY created_at_epoch ASC, id ASC
337
635
  LIMIT ?
338
636
  `);
339
- const after = afterStmt.all(anchorEpoch, depthAfter);
637
+ const after = afterStmt.all(anchorEpoch, anchorEpoch, anchorId, depthAfter);
340
638
  return [...before, ...self, ...after];
341
639
  }
342
640
  function getProjectStats(db, project) {
@@ -379,7 +677,7 @@ function getStaleObservations(db, project) {
379
677
  const rows = db.query(`
380
678
  SELECT * FROM observations
381
679
  WHERE project = ? AND files_modified IS NOT NULL AND files_modified != ''
382
- ORDER BY created_at_epoch DESC
680
+ ORDER BY created_at_epoch DESC, id DESC
383
681
  LIMIT 500
384
682
  `).all(project);
385
683
  const staleObs = [];
@@ -659,23 +957,47 @@ var BunQueryCompat = class {
659
957
  constructor(db, sql) {
660
958
  this._stmt = db.prepare(sql);
661
959
  }
960
+ /**
961
+ * Adatta parametri named da formato bun:sqlite a better-sqlite3.
962
+ * bun:sqlite: chiavi CON prefisso (es. { $todayStart: 123 })
963
+ * better-sqlite3: chiavi SENZA prefisso (es. { todayStart: 123 })
964
+ */
965
+ _adaptParams(params) {
966
+ if (params.length !== 1 || typeof params[0] !== "object" || params[0] === null || Array.isArray(params[0])) {
967
+ return params;
968
+ }
969
+ const obj = params[0];
970
+ const keys = Object.keys(obj);
971
+ if (keys.length === 0) return params;
972
+ if (!keys[0].startsWith("$") && !keys[0].startsWith("@") && !keys[0].startsWith(":")) {
973
+ return params;
974
+ }
975
+ const adapted = {};
976
+ for (const key of keys) {
977
+ adapted[key.slice(1)] = obj[key];
978
+ }
979
+ return [adapted];
980
+ }
662
981
  /**
663
982
  * Returns all rows
664
983
  */
665
984
  all(...params) {
666
- return params.length > 0 ? this._stmt.all(...params) : this._stmt.all();
985
+ if (params.length === 0) return this._stmt.all();
986
+ return this._stmt.all(...this._adaptParams(params));
667
987
  }
668
988
  /**
669
989
  * Returns the first row or null
670
990
  */
671
991
  get(...params) {
672
- return params.length > 0 ? this._stmt.get(...params) : this._stmt.get();
992
+ if (params.length === 0) return this._stmt.get();
993
+ return this._stmt.get(...this._adaptParams(params));
673
994
  }
674
995
  /**
675
996
  * Execute without results
676
997
  */
677
998
  run(...params) {
678
- return params.length > 0 ? this._stmt.run(...params) : this._stmt.run();
999
+ if (params.length === 0) return this._stmt.run();
1000
+ return this._stmt.run(...this._adaptParams(params));
679
1001
  }
680
1002
  };
681
1003
 
@@ -1233,11 +1555,104 @@ var MigrationRunner = class {
1233
1555
  db.run("CREATE INDEX IF NOT EXISTS idx_summaries_project_epoch ON summaries(project, created_at_epoch DESC)");
1234
1556
  db.run("CREATE INDEX IF NOT EXISTS idx_prompts_project_epoch ON prompts(project, created_at_epoch DESC)");
1235
1557
  }
1558
+ },
1559
+ {
1560
+ version: 10,
1561
+ up: (db) => {
1562
+ db.run(`
1563
+ CREATE TABLE IF NOT EXISTS job_queue (
1564
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1565
+ type TEXT NOT NULL,
1566
+ status TEXT NOT NULL DEFAULT 'pending',
1567
+ payload TEXT,
1568
+ result TEXT,
1569
+ error TEXT,
1570
+ retry_count INTEGER DEFAULT 0,
1571
+ max_retries INTEGER DEFAULT 3,
1572
+ priority INTEGER DEFAULT 0,
1573
+ created_at TEXT NOT NULL,
1574
+ created_at_epoch INTEGER NOT NULL,
1575
+ started_at_epoch INTEGER,
1576
+ completed_at_epoch INTEGER
1577
+ )
1578
+ `);
1579
+ db.run("CREATE INDEX IF NOT EXISTS idx_jobs_status ON job_queue(status)");
1580
+ db.run("CREATE INDEX IF NOT EXISTS idx_jobs_type ON job_queue(type)");
1581
+ db.run("CREATE INDEX IF NOT EXISTS idx_jobs_priority ON job_queue(status, priority DESC, created_at_epoch ASC)");
1582
+ }
1583
+ },
1584
+ {
1585
+ version: 11,
1586
+ up: (db) => {
1587
+ db.run("ALTER TABLE observations ADD COLUMN auto_category TEXT");
1588
+ db.run("CREATE INDEX IF NOT EXISTS idx_observations_category ON observations(auto_category)");
1589
+ }
1590
+ },
1591
+ {
1592
+ version: 12,
1593
+ up: (db) => {
1594
+ db.run(`
1595
+ CREATE TABLE IF NOT EXISTS github_links (
1596
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1597
+ observation_id INTEGER,
1598
+ session_id TEXT,
1599
+ repo TEXT NOT NULL,
1600
+ issue_number INTEGER,
1601
+ pr_number INTEGER,
1602
+ event_type TEXT NOT NULL,
1603
+ action TEXT,
1604
+ title TEXT,
1605
+ url TEXT,
1606
+ author TEXT,
1607
+ created_at TEXT NOT NULL,
1608
+ created_at_epoch INTEGER NOT NULL,
1609
+ FOREIGN KEY (observation_id) REFERENCES observations(id)
1610
+ )
1611
+ `);
1612
+ db.run("CREATE INDEX IF NOT EXISTS idx_github_links_repo ON github_links(repo)");
1613
+ db.run("CREATE INDEX IF NOT EXISTS idx_github_links_obs ON github_links(observation_id)");
1614
+ db.run("CREATE INDEX IF NOT EXISTS idx_github_links_event ON github_links(event_type)");
1615
+ db.run("CREATE INDEX IF NOT EXISTS idx_github_links_repo_issue ON github_links(repo, issue_number)");
1616
+ db.run("CREATE INDEX IF NOT EXISTS idx_github_links_repo_pr ON github_links(repo, pr_number)");
1617
+ }
1618
+ },
1619
+ {
1620
+ version: 13,
1621
+ up: (db) => {
1622
+ db.run("CREATE INDEX IF NOT EXISTS idx_observations_keyset ON observations(created_at_epoch DESC, id DESC)");
1623
+ db.run("CREATE INDEX IF NOT EXISTS idx_observations_project_keyset ON observations(project, created_at_epoch DESC, id DESC)");
1624
+ db.run("CREATE INDEX IF NOT EXISTS idx_summaries_keyset ON summaries(created_at_epoch DESC, id DESC)");
1625
+ db.run("CREATE INDEX IF NOT EXISTS idx_summaries_project_keyset ON summaries(project, created_at_epoch DESC, id DESC)");
1626
+ db.run("CREATE INDEX IF NOT EXISTS idx_prompts_keyset ON prompts(created_at_epoch DESC, id DESC)");
1627
+ db.run("CREATE INDEX IF NOT EXISTS idx_prompts_project_keyset ON prompts(project, created_at_epoch DESC, id DESC)");
1628
+ }
1236
1629
  }
1237
1630
  ];
1238
1631
  }
1239
1632
  };
1240
1633
 
1634
+ // src/services/sqlite/cursor.ts
1635
+ function encodeCursor(id, epoch) {
1636
+ const raw = `${epoch}:${id}`;
1637
+ return Buffer.from(raw, "utf8").toString("base64url");
1638
+ }
1639
+ function decodeCursor(cursor) {
1640
+ try {
1641
+ const raw = Buffer.from(cursor, "base64url").toString("utf8");
1642
+ const colonIdx = raw.indexOf(":");
1643
+ if (colonIdx === -1) return null;
1644
+ const epochStr = raw.substring(0, colonIdx);
1645
+ const idStr = raw.substring(colonIdx + 1);
1646
+ const epoch = parseInt(epochStr, 10);
1647
+ const id = parseInt(idStr, 10);
1648
+ if (!Number.isInteger(epoch) || epoch <= 0) return null;
1649
+ if (!Number.isInteger(id) || id <= 0) return null;
1650
+ return { epoch, id };
1651
+ } catch {
1652
+ return null;
1653
+ }
1654
+ }
1655
+
1241
1656
  // src/services/sqlite/Sessions.ts
1242
1657
  function createSession(db, contentSessionId, project, userPrompt) {
1243
1658
  const now = /* @__PURE__ */ new Date();
@@ -1281,16 +1696,16 @@ function createSummary(db, sessionId, project, request, investigated, learned, c
1281
1696
  }
1282
1697
  function getSummariesByProject(db, project, limit = 50) {
1283
1698
  const query = db.query(
1284
- "SELECT * FROM summaries WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ?"
1699
+ "SELECT * FROM summaries WHERE project = ? ORDER BY created_at_epoch DESC, id DESC LIMIT ?"
1285
1700
  );
1286
1701
  return query.all(project, limit);
1287
1702
  }
1288
1703
  function searchSummaries(db, searchTerm, project) {
1289
1704
  const sql = project ? `SELECT * FROM summaries
1290
1705
  WHERE project = ? AND (request LIKE ? ESCAPE '\\' OR learned LIKE ? ESCAPE '\\' OR completed LIKE ? ESCAPE '\\' OR notes LIKE ? ESCAPE '\\')
1291
- ORDER BY created_at_epoch DESC` : `SELECT * FROM summaries
1706
+ ORDER BY created_at_epoch DESC, id DESC` : `SELECT * FROM summaries
1292
1707
  WHERE request LIKE ? ESCAPE '\\' OR learned LIKE ? ESCAPE '\\' OR completed LIKE ? ESCAPE '\\' OR notes LIKE ? ESCAPE '\\'
1293
- ORDER BY created_at_epoch DESC`;
1708
+ ORDER BY created_at_epoch DESC, id DESC`;
1294
1709
  const pattern = `%${escapeLikePattern2(searchTerm)}%`;
1295
1710
  const query = db.query(sql);
1296
1711
  if (project) {
@@ -1312,7 +1727,7 @@ function createPrompt(db, contentSessionId, project, promptNumber, promptText) {
1312
1727
  }
1313
1728
  function getPromptsByProject(db, project, limit = 100) {
1314
1729
  const query = db.query(
1315
- "SELECT * FROM prompts WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ?"
1730
+ "SELECT * FROM prompts WHERE project = ? ORDER BY created_at_epoch DESC, id DESC LIMIT ?"
1316
1731
  );
1317
1732
  return query.all(project, limit);
1318
1733
  }
@@ -1340,13 +1755,13 @@ function createCheckpoint(db, sessionId, project, data) {
1340
1755
  }
1341
1756
  function getLatestCheckpoint(db, sessionId) {
1342
1757
  const query = db.query(
1343
- "SELECT * FROM checkpoints WHERE session_id = ? ORDER BY created_at_epoch DESC LIMIT 1"
1758
+ "SELECT * FROM checkpoints WHERE session_id = ? ORDER BY created_at_epoch DESC, id DESC LIMIT 1"
1344
1759
  );
1345
1760
  return query.get(sessionId);
1346
1761
  }
1347
1762
  function getLatestCheckpointByProject(db, project) {
1348
1763
  const query = db.query(
1349
- "SELECT * FROM checkpoints WHERE project = ? ORDER BY created_at_epoch DESC LIMIT 1"
1764
+ "SELECT * FROM checkpoints WHERE project = ? ORDER BY created_at_epoch DESC, id DESC LIMIT 1"
1350
1765
  );
1351
1766
  return query.get(project);
1352
1767
  }
@@ -1408,9 +1823,9 @@ function getReportData(db, project, startEpoch, endEpoch) {
1408
1823
  const staleCount = (project ? db.query(staleSql).get(project, startEpoch, endEpoch)?.count : db.query(staleSql).get(startEpoch, endEpoch)?.count) || 0;
1409
1824
  const summarySql = project ? `SELECT learned, completed, next_steps FROM summaries
1410
1825
  WHERE project = ? AND created_at_epoch >= ? AND created_at_epoch <= ?
1411
- ORDER BY created_at_epoch DESC` : `SELECT learned, completed, next_steps FROM summaries
1826
+ ORDER BY created_at_epoch DESC, id DESC` : `SELECT learned, completed, next_steps FROM summaries
1412
1827
  WHERE created_at_epoch >= ? AND created_at_epoch <= ?
1413
- ORDER BY created_at_epoch DESC`;
1828
+ ORDER BY created_at_epoch DESC, id DESC`;
1414
1829
  const summaryRows = project ? db.query(summarySql).all(project, startEpoch, endEpoch) : db.query(summarySql).all(startEpoch, endEpoch);
1415
1830
  const topLearnings = [];
1416
1831
  const completedTasks = [];
@@ -1475,20 +1890,61 @@ function getReportData(db, project, startEpoch, endEpoch) {
1475
1890
  // src/services/sqlite/index.ts
1476
1891
  init_Search();
1477
1892
 
1893
+ // src/types/worker-types.ts
1894
+ var KNOWLEDGE_TYPES = ["constraint", "decision", "heuristic", "rejected"];
1895
+
1896
+ // src/services/sqlite/Retention.ts
1897
+ var KNOWLEDGE_TYPE_LIST = KNOWLEDGE_TYPES;
1898
+ var KNOWLEDGE_PLACEHOLDERS = KNOWLEDGE_TYPE_LIST.map(() => "?").join(", ");
1899
+
1478
1900
  // src/sdk/index.ts
1479
1901
  init_Observations();
1480
1902
  import { createHash } from "crypto";
1481
1903
  init_Search();
1482
1904
 
1483
1905
  // src/services/search/EmbeddingService.ts
1906
+ var MODEL_CONFIGS = {
1907
+ "all-MiniLM-L6-v2": {
1908
+ modelId: "Xenova/all-MiniLM-L6-v2",
1909
+ dimensions: 384
1910
+ },
1911
+ "jina-code-v2": {
1912
+ modelId: "jinaai/jina-embeddings-v2-base-code",
1913
+ dimensions: 768
1914
+ },
1915
+ "bge-small-en": {
1916
+ modelId: "BAAI/bge-small-en-v1.5",
1917
+ dimensions: 384
1918
+ }
1919
+ };
1920
+ var FASTEMBED_COMPATIBLE_MODELS = /* @__PURE__ */ new Set(["all-MiniLM-L6-v2", "bge-small-en"]);
1484
1921
  var EmbeddingService = class {
1485
1922
  provider = null;
1486
1923
  model = null;
1487
1924
  initialized = false;
1488
1925
  initializing = null;
1926
+ config;
1927
+ configName;
1928
+ constructor() {
1929
+ const envModel = process.env.KIRO_MEMORY_EMBEDDING_MODEL || "all-MiniLM-L6-v2";
1930
+ this.configName = envModel;
1931
+ if (MODEL_CONFIGS[envModel]) {
1932
+ this.config = MODEL_CONFIGS[envModel];
1933
+ } else if (envModel.includes("/")) {
1934
+ const dimensions = parseInt(process.env.KIRO_MEMORY_EMBEDDING_DIMENSIONS || "384", 10);
1935
+ this.config = {
1936
+ modelId: envModel,
1937
+ dimensions: isNaN(dimensions) ? 384 : dimensions
1938
+ };
1939
+ } else {
1940
+ logger.warn("EMBEDDING", `Unknown model name '${envModel}', falling back to 'all-MiniLM-L6-v2'`);
1941
+ this.configName = "all-MiniLM-L6-v2";
1942
+ this.config = MODEL_CONFIGS["all-MiniLM-L6-v2"];
1943
+ }
1944
+ }
1489
1945
  /**
1490
1946
  * Initialize the embedding service.
1491
- * Tries fastembed, then @huggingface/transformers, then fallback to null.
1947
+ * Tries fastembed (when compatible), then @huggingface/transformers, then falls back to null.
1492
1948
  */
1493
1949
  async initialize() {
1494
1950
  if (this.initialized) return this.provider !== null;
@@ -1499,32 +1955,35 @@ var EmbeddingService = class {
1499
1955
  return result;
1500
1956
  }
1501
1957
  async _doInitialize() {
1502
- try {
1503
- const fastembed = await import("fastembed");
1504
- const EmbeddingModel = fastembed.EmbeddingModel || fastembed.default?.EmbeddingModel;
1505
- const FlagEmbedding = fastembed.FlagEmbedding || fastembed.default?.FlagEmbedding;
1506
- if (FlagEmbedding && EmbeddingModel) {
1507
- this.model = await FlagEmbedding.init({
1508
- model: EmbeddingModel.BGESmallENV15
1509
- });
1510
- this.provider = "fastembed";
1511
- this.initialized = true;
1512
- logger.info("EMBEDDING", "Initialized with fastembed (BGE-small-en-v1.5)");
1513
- return true;
1958
+ const fastembedCompatible = FASTEMBED_COMPATIBLE_MODELS.has(this.configName);
1959
+ if (fastembedCompatible) {
1960
+ try {
1961
+ const fastembed = await import("fastembed");
1962
+ const EmbeddingModel = fastembed.EmbeddingModel || fastembed.default?.EmbeddingModel;
1963
+ const FlagEmbedding = fastembed.FlagEmbedding || fastembed.default?.FlagEmbedding;
1964
+ if (FlagEmbedding && EmbeddingModel) {
1965
+ this.model = await FlagEmbedding.init({
1966
+ model: EmbeddingModel.BGESmallENV15
1967
+ });
1968
+ this.provider = "fastembed";
1969
+ this.initialized = true;
1970
+ logger.info("EMBEDDING", `Initialized with fastembed (BGE-small-en-v1.5) for model '${this.configName}'`);
1971
+ return true;
1972
+ }
1973
+ } catch (error) {
1974
+ logger.debug("EMBEDDING", `fastembed not available: ${error}`);
1514
1975
  }
1515
- } catch (error) {
1516
- logger.debug("EMBEDDING", `fastembed not available: ${error}`);
1517
1976
  }
1518
1977
  try {
1519
1978
  const transformers = await import("@huggingface/transformers");
1520
1979
  const pipeline = transformers.pipeline || transformers.default?.pipeline;
1521
1980
  if (pipeline) {
1522
- this.model = await pipeline("feature-extraction", "Xenova/all-MiniLM-L6-v2", {
1981
+ this.model = await pipeline("feature-extraction", this.config.modelId, {
1523
1982
  quantized: true
1524
1983
  });
1525
1984
  this.provider = "transformers";
1526
1985
  this.initialized = true;
1527
- logger.info("EMBEDDING", "Initialized with @huggingface/transformers (all-MiniLM-L6-v2)");
1986
+ logger.info("EMBEDDING", `Initialized with @huggingface/transformers (${this.config.modelId})`);
1528
1987
  return true;
1529
1988
  }
1530
1989
  } catch (error) {
@@ -1537,7 +1996,7 @@ var EmbeddingService = class {
1537
1996
  }
1538
1997
  /**
1539
1998
  * Generate embedding for a single text.
1540
- * Returns Float32Array with 384 dimensions, or null if not available.
1999
+ * Returns Float32Array with configured dimensions, or null if not available.
1541
2000
  */
1542
2001
  async embed(text) {
1543
2002
  if (!this.initialized) await this.initialize();
@@ -1588,10 +2047,17 @@ var EmbeddingService = class {
1588
2047
  return this.provider;
1589
2048
  }
1590
2049
  /**
1591
- * Embedding vector dimensions.
2050
+ * Embedding vector dimensions for the active model configuration.
1592
2051
  */
1593
2052
  getDimensions() {
1594
- return 384;
2053
+ return this.config.dimensions;
2054
+ }
2055
+ /**
2056
+ * Human-readable model name used as identifier in the observation_embeddings table.
2057
+ * Returns the short name (e.g., 'all-MiniLM-L6-v2') or the full HF model ID for custom models.
2058
+ */
2059
+ getModelName() {
2060
+ return this.configName;
1595
2061
  }
1596
2062
  // --- Batch implementations ---
1597
2063
  /**
@@ -1810,7 +2276,7 @@ var VectorSearch = class {
1810
2276
  `).all(batchSize);
1811
2277
  if (rows.length === 0) return 0;
1812
2278
  let count = 0;
1813
- const model = embeddingService2.getProvider() || "unknown";
2279
+ const model = embeddingService2.getModelName();
1814
2280
  for (const row of rows) {
1815
2281
  const parts = [row.title];
1816
2282
  if (row.text) parts.push(row.text);
@@ -1988,9 +2454,6 @@ function getHybridSearch() {
1988
2454
  return hybridSearch;
1989
2455
  }
1990
2456
 
1991
- // src/types/worker-types.ts
1992
- var KNOWLEDGE_TYPES = ["constraint", "decision", "heuristic", "rejected"];
1993
-
1994
2457
  // src/sdk/index.ts
1995
2458
  var KiroMemorySDK = class {
1996
2459
  db;
@@ -2547,6 +3010,66 @@ var KiroMemorySDK = class {
2547
3010
  }
2548
3011
  return getReportData(this.db.db, this.project, startEpoch, endEpoch);
2549
3012
  }
3013
+ /**
3014
+ * Lista osservazioni con keyset pagination.
3015
+ * Restituisce un oggetto { data, next_cursor, has_more }.
3016
+ *
3017
+ * Esempio:
3018
+ * const page1 = await sdk.listObservations({ limit: 50 });
3019
+ * const page2 = await sdk.listObservations({ cursor: page1.next_cursor });
3020
+ */
3021
+ async listObservations(options = {}) {
3022
+ const limit = Math.min(Math.max(options.limit ?? 50, 1), 200);
3023
+ const project = options.project ?? this.project;
3024
+ let rows;
3025
+ if (options.cursor) {
3026
+ const decoded = decodeCursor(options.cursor);
3027
+ if (!decoded) throw new Error("Cursor non valido");
3028
+ const sql = project ? `SELECT * FROM observations
3029
+ WHERE project = ? AND (created_at_epoch < ? OR (created_at_epoch = ? AND id < ?))
3030
+ ORDER BY created_at_epoch DESC, id DESC
3031
+ LIMIT ?` : `SELECT * FROM observations
3032
+ WHERE (created_at_epoch < ? OR (created_at_epoch = ? AND id < ?))
3033
+ ORDER BY created_at_epoch DESC, id DESC
3034
+ LIMIT ?`;
3035
+ 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);
3036
+ } else {
3037
+ 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 ?";
3038
+ rows = project ? this.db.db.query(sql).all(project, limit) : this.db.db.query(sql).all(limit);
3039
+ }
3040
+ const next_cursor = rows.length >= limit ? encodeCursor(rows[rows.length - 1].id, rows[rows.length - 1].created_at_epoch) : null;
3041
+ return { data: rows, next_cursor, has_more: next_cursor !== null };
3042
+ }
3043
+ /**
3044
+ * Lista sommari con keyset pagination.
3045
+ * Restituisce un oggetto { data, next_cursor, has_more }.
3046
+ *
3047
+ * Esempio:
3048
+ * const page1 = await sdk.listSummaries({ limit: 20 });
3049
+ * const page2 = await sdk.listSummaries({ cursor: page1.next_cursor });
3050
+ */
3051
+ async listSummaries(options = {}) {
3052
+ const limit = Math.min(Math.max(options.limit ?? 20, 1), 200);
3053
+ const project = options.project ?? this.project;
3054
+ let rows;
3055
+ if (options.cursor) {
3056
+ const decoded = decodeCursor(options.cursor);
3057
+ if (!decoded) throw new Error("Cursor non valido");
3058
+ const sql = project ? `SELECT * FROM summaries
3059
+ WHERE project = ? AND (created_at_epoch < ? OR (created_at_epoch = ? AND id < ?))
3060
+ ORDER BY created_at_epoch DESC, id DESC
3061
+ LIMIT ?` : `SELECT * FROM summaries
3062
+ WHERE (created_at_epoch < ? OR (created_at_epoch = ? AND id < ?))
3063
+ ORDER BY created_at_epoch DESC, id DESC
3064
+ LIMIT ?`;
3065
+ 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);
3066
+ } else {
3067
+ 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 ?";
3068
+ rows = project ? this.db.db.query(sql).all(project, limit) : this.db.db.query(sql).all(limit);
3069
+ }
3070
+ const next_cursor = rows.length >= limit ? encodeCursor(rows[rows.length - 1].id, rows[rows.length - 1].created_at_epoch) : null;
3071
+ return { data: rows, next_cursor, has_more: next_cursor !== null };
3072
+ }
2550
3073
  /**
2551
3074
  * Getter for direct database access (for API routes)
2552
3075
  */