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.
- package/README.md +5 -1
- package/package.json +5 -5
- package/plugin/dist/cli/contextkit.js +2611 -345
- package/plugin/dist/hooks/agentSpawn.js +853 -223
- package/plugin/dist/hooks/kiro-hooks.js +841 -211
- package/plugin/dist/hooks/postToolUse.js +853 -222
- package/plugin/dist/hooks/stop.js +850 -220
- package/plugin/dist/hooks/userPromptSubmit.js +848 -216
- package/plugin/dist/index.js +843 -340
- package/plugin/dist/plugins/github/github-client.js +152 -0
- package/plugin/dist/plugins/github/index.js +412 -0
- package/plugin/dist/plugins/github/issue-parser.js +54 -0
- package/plugin/dist/plugins/slack/formatter.js +90 -0
- package/plugin/dist/plugins/slack/index.js +215 -0
- package/plugin/dist/sdk/index.js +841 -215
- package/plugin/dist/servers/mcp-server.js +4461 -397
- package/plugin/dist/services/search/EmbeddingService.js +146 -37
- package/plugin/dist/services/search/HybridSearch.js +564 -116
- package/plugin/dist/services/search/VectorSearch.js +187 -60
- package/plugin/dist/services/search/index.js +565 -254
- package/plugin/dist/services/sqlite/Backup.js +416 -0
- package/plugin/dist/services/sqlite/Database.js +126 -153
- package/plugin/dist/services/sqlite/ImportExport.js +452 -0
- package/plugin/dist/services/sqlite/Observations.js +314 -19
- package/plugin/dist/services/sqlite/Prompts.js +1 -1
- package/plugin/dist/services/sqlite/Search.js +41 -29
- package/plugin/dist/services/sqlite/Summaries.js +4 -4
- package/plugin/dist/services/sqlite/index.js +1428 -208
- package/plugin/dist/viewer.css +1 -0
- package/plugin/dist/viewer.html +2 -179
- package/plugin/dist/viewer.js +23 -24942
- package/plugin/dist/viewer.js.map +7 -0
- package/plugin/dist/worker-service.js +427 -5569
- package/plugin/dist/worker-service.js.map +7 -0
|
@@ -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,
|
|
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) {
|
|
@@ -99,21 +395,32 @@ function consolidateObservations(db, project, options = {}) {
|
|
|
99
395
|
ORDER BY cnt DESC
|
|
100
396
|
`).all(project, minGroupSize);
|
|
101
397
|
if (groups.length === 0) return { merged: 0, removed: 0 };
|
|
102
|
-
|
|
103
|
-
|
|
398
|
+
if (options.dryRun) {
|
|
399
|
+
let totalMerged = 0;
|
|
400
|
+
let totalRemoved = 0;
|
|
401
|
+
for (const group of groups) {
|
|
402
|
+
const obsIds = group.ids.split(",").map(Number);
|
|
403
|
+
const placeholders = obsIds.map(() => "?").join(",");
|
|
404
|
+
const count = db.query(
|
|
405
|
+
`SELECT COUNT(*) as cnt FROM observations WHERE id IN (${placeholders})`
|
|
406
|
+
).get(...obsIds)?.cnt || 0;
|
|
407
|
+
if (count >= minGroupSize) {
|
|
408
|
+
totalMerged += 1;
|
|
409
|
+
totalRemoved += count - 1;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return { merged: totalMerged, removed: totalRemoved };
|
|
413
|
+
}
|
|
104
414
|
const runConsolidation = db.transaction(() => {
|
|
415
|
+
let merged = 0;
|
|
416
|
+
let removed = 0;
|
|
105
417
|
for (const group of groups) {
|
|
106
418
|
const obsIds = group.ids.split(",").map(Number);
|
|
107
419
|
const placeholders = obsIds.map(() => "?").join(",");
|
|
108
420
|
const observations = db.query(
|
|
109
|
-
`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`
|
|
110
422
|
).all(...obsIds);
|
|
111
423
|
if (observations.length < minGroupSize) continue;
|
|
112
|
-
if (options.dryRun) {
|
|
113
|
-
totalMerged += 1;
|
|
114
|
-
totalRemoved += observations.length - 1;
|
|
115
|
-
continue;
|
|
116
|
-
}
|
|
117
424
|
const keeper = observations[0];
|
|
118
425
|
const others = observations.slice(1);
|
|
119
426
|
const uniqueTexts = /* @__PURE__ */ new Set();
|
|
@@ -126,22 +433,24 @@ function consolidateObservations(db, project, options = {}) {
|
|
|
126
433
|
const consolidatedText = Array.from(uniqueTexts).join("\n---\n").substring(0, 1e5);
|
|
127
434
|
db.run(
|
|
128
435
|
"UPDATE observations SET text = ?, title = ? WHERE id = ?",
|
|
129
|
-
[consolidatedText, `[
|
|
436
|
+
[consolidatedText, `[consolidated x${observations.length}] ${keeper.title}`, keeper.id]
|
|
130
437
|
);
|
|
131
438
|
const removeIds = others.map((o) => o.id);
|
|
132
439
|
const removePlaceholders = removeIds.map(() => "?").join(",");
|
|
133
440
|
db.run(`DELETE FROM observations WHERE id IN (${removePlaceholders})`, removeIds);
|
|
134
441
|
db.run(`DELETE FROM observation_embeddings WHERE observation_id IN (${removePlaceholders})`, removeIds);
|
|
135
|
-
|
|
136
|
-
|
|
442
|
+
merged += 1;
|
|
443
|
+
removed += removeIds.length;
|
|
137
444
|
}
|
|
445
|
+
return { merged, removed };
|
|
138
446
|
});
|
|
139
|
-
runConsolidation();
|
|
140
|
-
return { merged: totalMerged, removed: totalRemoved };
|
|
447
|
+
return runConsolidation();
|
|
141
448
|
}
|
|
142
449
|
var init_Observations = __esm({
|
|
143
450
|
"src/services/sqlite/Observations.ts"() {
|
|
144
451
|
"use strict";
|
|
452
|
+
init_secrets();
|
|
453
|
+
init_categorizer();
|
|
145
454
|
}
|
|
146
455
|
});
|
|
147
456
|
|
|
@@ -164,7 +473,7 @@ function escapeLikePattern3(input) {
|
|
|
164
473
|
}
|
|
165
474
|
function sanitizeFTS5Query(query) {
|
|
166
475
|
const trimmed = query.length > 1e4 ? query.substring(0, 1e4) : query;
|
|
167
|
-
const terms = trimmed.replace(/[""]/g, "").split(/\s+/).filter((t) => t.length > 0).slice(0, 100).map((t) => `"${t}"`);
|
|
476
|
+
const terms = trimmed.replace(/[""\u0022]/g, "").split(/\s+/).filter((t) => t.length > 0).slice(0, 100).map((t) => `"${t}"`);
|
|
168
477
|
return terms.join(" ");
|
|
169
478
|
}
|
|
170
479
|
function searchObservationsFTS(db, query, filters = {}) {
|
|
@@ -261,7 +570,7 @@ function searchObservationsLIKE(db, query, filters = {}) {
|
|
|
261
570
|
sql += " AND created_at_epoch <= ?";
|
|
262
571
|
params.push(filters.dateEnd);
|
|
263
572
|
}
|
|
264
|
-
sql += " ORDER BY created_at_epoch DESC LIMIT ?";
|
|
573
|
+
sql += " ORDER BY created_at_epoch DESC, id DESC LIMIT ?";
|
|
265
574
|
params.push(limit);
|
|
266
575
|
const stmt = db.query(sql);
|
|
267
576
|
return stmt.all(...params);
|
|
@@ -286,7 +595,7 @@ function searchSummariesFiltered(db, query, filters = {}) {
|
|
|
286
595
|
sql += " AND created_at_epoch <= ?";
|
|
287
596
|
params.push(filters.dateEnd);
|
|
288
597
|
}
|
|
289
|
-
sql += " ORDER BY created_at_epoch DESC LIMIT ?";
|
|
598
|
+
sql += " ORDER BY created_at_epoch DESC, id DESC LIMIT ?";
|
|
290
599
|
params.push(limit);
|
|
291
600
|
const stmt = db.query(sql);
|
|
292
601
|
return stmt.all(...params);
|
|
@@ -296,7 +605,7 @@ function getObservationsByIds(db, ids) {
|
|
|
296
605
|
const validIds = ids.filter((id) => typeof id === "number" && Number.isInteger(id) && id > 0).slice(0, 500);
|
|
297
606
|
if (validIds.length === 0) return [];
|
|
298
607
|
const placeholders = validIds.map(() => "?").join(",");
|
|
299
|
-
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`;
|
|
300
609
|
const stmt = db.query(sql);
|
|
301
610
|
return stmt.all(...validIds);
|
|
302
611
|
}
|
|
@@ -308,11 +617,11 @@ function getTimeline(db, anchorId, depthBefore = 5, depthAfter = 5) {
|
|
|
308
617
|
const beforeStmt = db.query(`
|
|
309
618
|
SELECT id, 'observation' as type, title, text as content, project, created_at, created_at_epoch
|
|
310
619
|
FROM observations
|
|
311
|
-
WHERE created_at_epoch < ?
|
|
312
|
-
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
|
|
313
622
|
LIMIT ?
|
|
314
623
|
`);
|
|
315
|
-
const before = beforeStmt.all(anchorEpoch, depthBefore).reverse();
|
|
624
|
+
const before = beforeStmt.all(anchorEpoch, anchorEpoch, anchorId, depthBefore).reverse();
|
|
316
625
|
const selfStmt = db.query(`
|
|
317
626
|
SELECT id, 'observation' as type, title, text as content, project, created_at, created_at_epoch
|
|
318
627
|
FROM observations WHERE id = ?
|
|
@@ -321,34 +630,46 @@ function getTimeline(db, anchorId, depthBefore = 5, depthAfter = 5) {
|
|
|
321
630
|
const afterStmt = db.query(`
|
|
322
631
|
SELECT id, 'observation' as type, title, text as content, project, created_at, created_at_epoch
|
|
323
632
|
FROM observations
|
|
324
|
-
WHERE created_at_epoch > ?
|
|
325
|
-
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
|
|
326
635
|
LIMIT ?
|
|
327
636
|
`);
|
|
328
|
-
const after = afterStmt.all(anchorEpoch, depthAfter);
|
|
637
|
+
const after = afterStmt.all(anchorEpoch, anchorEpoch, anchorId, depthAfter);
|
|
329
638
|
return [...before, ...self, ...after];
|
|
330
639
|
}
|
|
331
640
|
function getProjectStats(db, project) {
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
641
|
+
const sql = `
|
|
642
|
+
WITH
|
|
643
|
+
obs_stats AS (
|
|
644
|
+
SELECT
|
|
645
|
+
COUNT(*) as count,
|
|
646
|
+
COALESCE(SUM(discovery_tokens), 0) as discovery_tokens,
|
|
647
|
+
COALESCE(SUM(
|
|
648
|
+
CAST((LENGTH(COALESCE(title, '')) + LENGTH(COALESCE(narrative, ''))) / 4 AS INTEGER)
|
|
649
|
+
), 0) as read_tokens
|
|
650
|
+
FROM observations WHERE project = ?
|
|
651
|
+
),
|
|
652
|
+
sum_count AS (SELECT COUNT(*) as count FROM summaries WHERE project = ?),
|
|
653
|
+
ses_count AS (SELECT COUNT(*) as count FROM sessions WHERE project = ?),
|
|
654
|
+
prm_count AS (SELECT COUNT(*) as count FROM prompts WHERE project = ?)
|
|
655
|
+
SELECT
|
|
656
|
+
obs_stats.count as observations,
|
|
657
|
+
obs_stats.discovery_tokens,
|
|
658
|
+
obs_stats.read_tokens,
|
|
659
|
+
sum_count.count as summaries,
|
|
660
|
+
ses_count.count as sessions,
|
|
661
|
+
prm_count.count as prompts
|
|
662
|
+
FROM obs_stats, sum_count, ses_count, prm_count
|
|
663
|
+
`;
|
|
664
|
+
const row = db.query(sql).get(project, project, project, project);
|
|
665
|
+
const discoveryTokens = row?.discovery_tokens || 0;
|
|
666
|
+
const readTokens = row?.read_tokens || 0;
|
|
346
667
|
const savings = Math.max(0, discoveryTokens - readTokens);
|
|
347
668
|
return {
|
|
348
|
-
observations:
|
|
349
|
-
summaries:
|
|
350
|
-
sessions:
|
|
351
|
-
prompts:
|
|
669
|
+
observations: row?.observations || 0,
|
|
670
|
+
summaries: row?.summaries || 0,
|
|
671
|
+
sessions: row?.sessions || 0,
|
|
672
|
+
prompts: row?.prompts || 0,
|
|
352
673
|
tokenEconomics: { discoveryTokens, readTokens, savings }
|
|
353
674
|
};
|
|
354
675
|
}
|
|
@@ -356,7 +677,7 @@ function getStaleObservations(db, project) {
|
|
|
356
677
|
const rows = db.query(`
|
|
357
678
|
SELECT * FROM observations
|
|
358
679
|
WHERE project = ? AND files_modified IS NOT NULL AND files_modified != ''
|
|
359
|
-
ORDER BY created_at_epoch DESC
|
|
680
|
+
ORDER BY created_at_epoch DESC, id DESC
|
|
360
681
|
LIMIT 500
|
|
361
682
|
`).all(project);
|
|
362
683
|
const staleObs = [];
|
|
@@ -497,7 +818,7 @@ async function readStdin() {
|
|
|
497
818
|
}
|
|
498
819
|
resolve(JSON.parse(data));
|
|
499
820
|
} catch (err) {
|
|
500
|
-
reject(new Error(`
|
|
821
|
+
reject(new Error(`Error parsing stdin JSON: ${err}`));
|
|
501
822
|
}
|
|
502
823
|
});
|
|
503
824
|
process.stdin.on("error", (err) => {
|
|
@@ -523,17 +844,17 @@ function formatSmartContext(data) {
|
|
|
523
844
|
const budget = data.tokenBudget || parseInt(process.env.KIRO_MEMORY_CONTEXT_TOKENS || "0", 10) || 2e3;
|
|
524
845
|
let output = "";
|
|
525
846
|
let tokensUsed = 0;
|
|
526
|
-
const header = "# Kiro Memory:
|
|
847
|
+
const header = "# Kiro Memory: Previous Sessions Context\n\n";
|
|
527
848
|
tokensUsed += estimateTokens(header);
|
|
528
849
|
output += header;
|
|
529
850
|
if (data.summaries && data.summaries.length > 0) {
|
|
530
|
-
let sumSection = "##
|
|
851
|
+
let sumSection = "## Previous Sessions\n\n";
|
|
531
852
|
for (const sum of data.summaries.slice(0, 3)) {
|
|
532
|
-
if (sum.learned) sumSection += `- **
|
|
853
|
+
if (sum.learned) sumSection += `- **Learned**: ${sum.learned}
|
|
533
854
|
`;
|
|
534
|
-
if (sum.completed) sumSection += `- **
|
|
855
|
+
if (sum.completed) sumSection += `- **Completed**: ${sum.completed}
|
|
535
856
|
`;
|
|
536
|
-
if (sum.next_steps) sumSection += `- **
|
|
857
|
+
if (sum.next_steps) sumSection += `- **Next steps**: ${sum.next_steps}
|
|
537
858
|
`;
|
|
538
859
|
sumSection += "\n";
|
|
539
860
|
}
|
|
@@ -541,7 +862,7 @@ function formatSmartContext(data) {
|
|
|
541
862
|
output += sumSection;
|
|
542
863
|
}
|
|
543
864
|
if (data.items && data.items.length > 0) {
|
|
544
|
-
let obsSection = "##
|
|
865
|
+
let obsSection = "## Relevant Observations\n\n";
|
|
545
866
|
tokensUsed += estimateTokens(obsSection);
|
|
546
867
|
const sorted = [...data.items].sort((a, b) => b.score - a.score);
|
|
547
868
|
for (const item of sorted) {
|
|
@@ -560,7 +881,7 @@ function formatSmartContext(data) {
|
|
|
560
881
|
output += obsSection;
|
|
561
882
|
}
|
|
562
883
|
const footer = `
|
|
563
|
-
>
|
|
884
|
+
> Project: ${data.project} | Items: ${data.items?.length || 0} | Tokens used: ~${tokensUsed}/${budget}
|
|
564
885
|
`;
|
|
565
886
|
output += footer;
|
|
566
887
|
return output;
|
|
@@ -576,11 +897,11 @@ async function runHook(name, handler) {
|
|
|
576
897
|
}
|
|
577
898
|
debugLog(name, "stdin", input);
|
|
578
899
|
await handler(input);
|
|
579
|
-
debugLog(name, "
|
|
900
|
+
debugLog(name, "completed", { success: true });
|
|
580
901
|
process.exit(0);
|
|
581
902
|
} catch (error) {
|
|
582
|
-
debugLog(name, "
|
|
583
|
-
process.stderr.write(`[kiro-memory:${name}]
|
|
903
|
+
debugLog(name, "error", { error: String(error) });
|
|
904
|
+
process.stderr.write(`[kiro-memory:${name}] Error: ${error}
|
|
584
905
|
`);
|
|
585
906
|
process.exit(0);
|
|
586
907
|
}
|
|
@@ -590,14 +911,15 @@ async function runHook(name, handler) {
|
|
|
590
911
|
import BetterSqlite3 from "better-sqlite3";
|
|
591
912
|
var Database = class {
|
|
592
913
|
_db;
|
|
914
|
+
_stmtCache = /* @__PURE__ */ new Map();
|
|
593
915
|
constructor(path, options) {
|
|
594
916
|
this._db = new BetterSqlite3(path, {
|
|
595
|
-
// better-sqlite3
|
|
917
|
+
// better-sqlite3 creates the file by default ('create' not needed)
|
|
596
918
|
readonly: options?.readwrite === false ? true : false
|
|
597
919
|
});
|
|
598
920
|
}
|
|
599
921
|
/**
|
|
600
|
-
*
|
|
922
|
+
* Execute a SQL query without results
|
|
601
923
|
*/
|
|
602
924
|
run(sql, params) {
|
|
603
925
|
const stmt = this._db.prepare(sql);
|
|
@@ -605,51 +927,53 @@ var Database = class {
|
|
|
605
927
|
return result;
|
|
606
928
|
}
|
|
607
929
|
/**
|
|
608
|
-
*
|
|
930
|
+
* Prepare a query with bun:sqlite-compatible interface.
|
|
931
|
+
* Returns a cached prepared statement for repeated queries.
|
|
609
932
|
*/
|
|
610
933
|
query(sql) {
|
|
611
|
-
|
|
934
|
+
let cached = this._stmtCache.get(sql);
|
|
935
|
+
if (!cached) {
|
|
936
|
+
cached = new BunQueryCompat(this._db, sql);
|
|
937
|
+
this._stmtCache.set(sql, cached);
|
|
938
|
+
}
|
|
939
|
+
return cached;
|
|
612
940
|
}
|
|
613
941
|
/**
|
|
614
|
-
*
|
|
942
|
+
* Create a transaction
|
|
615
943
|
*/
|
|
616
944
|
transaction(fn) {
|
|
617
945
|
return this._db.transaction(fn);
|
|
618
946
|
}
|
|
619
947
|
/**
|
|
620
|
-
*
|
|
948
|
+
* Close the connection
|
|
621
949
|
*/
|
|
622
950
|
close() {
|
|
951
|
+
this._stmtCache.clear();
|
|
623
952
|
this._db.close();
|
|
624
953
|
}
|
|
625
954
|
};
|
|
626
955
|
var BunQueryCompat = class {
|
|
627
|
-
|
|
628
|
-
_sql;
|
|
956
|
+
_stmt;
|
|
629
957
|
constructor(db, sql) {
|
|
630
|
-
this.
|
|
631
|
-
this._sql = sql;
|
|
958
|
+
this._stmt = db.prepare(sql);
|
|
632
959
|
}
|
|
633
960
|
/**
|
|
634
|
-
*
|
|
961
|
+
* Returns all rows
|
|
635
962
|
*/
|
|
636
963
|
all(...params) {
|
|
637
|
-
|
|
638
|
-
return params.length > 0 ? stmt.all(...params) : stmt.all();
|
|
964
|
+
return params.length > 0 ? this._stmt.all(...params) : this._stmt.all();
|
|
639
965
|
}
|
|
640
966
|
/**
|
|
641
|
-
*
|
|
967
|
+
* Returns the first row or null
|
|
642
968
|
*/
|
|
643
969
|
get(...params) {
|
|
644
|
-
|
|
645
|
-
return params.length > 0 ? stmt.get(...params) : stmt.get();
|
|
970
|
+
return params.length > 0 ? this._stmt.get(...params) : this._stmt.get();
|
|
646
971
|
}
|
|
647
972
|
/**
|
|
648
|
-
*
|
|
973
|
+
* Execute without results
|
|
649
974
|
*/
|
|
650
975
|
run(...params) {
|
|
651
|
-
|
|
652
|
-
return params.length > 0 ? stmt.run(...params) : stmt.run();
|
|
976
|
+
return params.length > 0 ? this._stmt.run(...params) : this._stmt.run();
|
|
653
977
|
}
|
|
654
978
|
};
|
|
655
979
|
|
|
@@ -911,40 +1235,62 @@ function ensureDir(dirPath) {
|
|
|
911
1235
|
var SQLITE_MMAP_SIZE_BYTES = 256 * 1024 * 1024;
|
|
912
1236
|
var SQLITE_CACHE_SIZE_PAGES = 1e4;
|
|
913
1237
|
var KiroMemoryDatabase = class {
|
|
914
|
-
|
|
1238
|
+
_db;
|
|
915
1239
|
/**
|
|
916
|
-
*
|
|
917
|
-
*
|
|
1240
|
+
* Readonly accessor for the underlying Database instance.
|
|
1241
|
+
* Prefer using query() and run() proxy methods directly.
|
|
1242
|
+
*/
|
|
1243
|
+
get db() {
|
|
1244
|
+
return this._db;
|
|
1245
|
+
}
|
|
1246
|
+
/**
|
|
1247
|
+
* @param dbPath - Path to the SQLite file (default: DB_PATH)
|
|
1248
|
+
* @param skipMigrations - If true, skip the migration runner (for high-frequency hooks)
|
|
918
1249
|
*/
|
|
919
1250
|
constructor(dbPath = DB_PATH, skipMigrations = false) {
|
|
920
1251
|
if (dbPath !== ":memory:") {
|
|
921
1252
|
ensureDir(DATA_DIR2);
|
|
922
1253
|
}
|
|
923
|
-
this.
|
|
924
|
-
this.
|
|
925
|
-
this.
|
|
926
|
-
this.
|
|
927
|
-
this.
|
|
928
|
-
this.
|
|
929
|
-
this.
|
|
1254
|
+
this._db = new Database(dbPath, { create: true, readwrite: true });
|
|
1255
|
+
this._db.run("PRAGMA journal_mode = WAL");
|
|
1256
|
+
this._db.run("PRAGMA busy_timeout = 5000");
|
|
1257
|
+
this._db.run("PRAGMA synchronous = NORMAL");
|
|
1258
|
+
this._db.run("PRAGMA foreign_keys = ON");
|
|
1259
|
+
this._db.run("PRAGMA temp_store = memory");
|
|
1260
|
+
this._db.run(`PRAGMA mmap_size = ${SQLITE_MMAP_SIZE_BYTES}`);
|
|
1261
|
+
this._db.run(`PRAGMA cache_size = ${SQLITE_CACHE_SIZE_PAGES}`);
|
|
930
1262
|
if (!skipMigrations) {
|
|
931
|
-
const migrationRunner = new MigrationRunner(this.
|
|
1263
|
+
const migrationRunner = new MigrationRunner(this._db);
|
|
932
1264
|
migrationRunner.runAllMigrations();
|
|
933
1265
|
}
|
|
934
1266
|
}
|
|
935
1267
|
/**
|
|
936
|
-
*
|
|
937
|
-
*
|
|
1268
|
+
* Prepare a query (delegates to underlying Database).
|
|
1269
|
+
* Proxy method to avoid ctx.db.db.query() double access.
|
|
1270
|
+
*/
|
|
1271
|
+
query(sql) {
|
|
1272
|
+
return this._db.query(sql);
|
|
1273
|
+
}
|
|
1274
|
+
/**
|
|
1275
|
+
* Execute a SQL statement without results (delegates to underlying Database).
|
|
1276
|
+
* Proxy method to avoid ctx.db.db.run() double access.
|
|
1277
|
+
*/
|
|
1278
|
+
run(sql, params) {
|
|
1279
|
+
return this._db.run(sql, params);
|
|
1280
|
+
}
|
|
1281
|
+
/**
|
|
1282
|
+
* Executes a function within an atomic transaction.
|
|
1283
|
+
* If fn() throws an error, the transaction is automatically rolled back.
|
|
938
1284
|
*/
|
|
939
1285
|
withTransaction(fn) {
|
|
940
|
-
const transaction = this.
|
|
941
|
-
return transaction(this.
|
|
1286
|
+
const transaction = this._db.transaction(fn);
|
|
1287
|
+
return transaction(this._db);
|
|
942
1288
|
}
|
|
943
1289
|
/**
|
|
944
1290
|
* Close the database connection
|
|
945
1291
|
*/
|
|
946
1292
|
close() {
|
|
947
|
-
this.
|
|
1293
|
+
this._db.close();
|
|
948
1294
|
}
|
|
949
1295
|
};
|
|
950
1296
|
var MigrationRunner = class {
|
|
@@ -1185,11 +1531,104 @@ var MigrationRunner = class {
|
|
|
1185
1531
|
db.run("CREATE INDEX IF NOT EXISTS idx_summaries_project_epoch ON summaries(project, created_at_epoch DESC)");
|
|
1186
1532
|
db.run("CREATE INDEX IF NOT EXISTS idx_prompts_project_epoch ON prompts(project, created_at_epoch DESC)");
|
|
1187
1533
|
}
|
|
1534
|
+
},
|
|
1535
|
+
{
|
|
1536
|
+
version: 10,
|
|
1537
|
+
up: (db) => {
|
|
1538
|
+
db.run(`
|
|
1539
|
+
CREATE TABLE IF NOT EXISTS job_queue (
|
|
1540
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1541
|
+
type TEXT NOT NULL,
|
|
1542
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
1543
|
+
payload TEXT,
|
|
1544
|
+
result TEXT,
|
|
1545
|
+
error TEXT,
|
|
1546
|
+
retry_count INTEGER DEFAULT 0,
|
|
1547
|
+
max_retries INTEGER DEFAULT 3,
|
|
1548
|
+
priority INTEGER DEFAULT 0,
|
|
1549
|
+
created_at TEXT NOT NULL,
|
|
1550
|
+
created_at_epoch INTEGER NOT NULL,
|
|
1551
|
+
started_at_epoch INTEGER,
|
|
1552
|
+
completed_at_epoch INTEGER
|
|
1553
|
+
)
|
|
1554
|
+
`);
|
|
1555
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_jobs_status ON job_queue(status)");
|
|
1556
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_jobs_type ON job_queue(type)");
|
|
1557
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_jobs_priority ON job_queue(status, priority DESC, created_at_epoch ASC)");
|
|
1558
|
+
}
|
|
1559
|
+
},
|
|
1560
|
+
{
|
|
1561
|
+
version: 11,
|
|
1562
|
+
up: (db) => {
|
|
1563
|
+
db.run("ALTER TABLE observations ADD COLUMN auto_category TEXT");
|
|
1564
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_observations_category ON observations(auto_category)");
|
|
1565
|
+
}
|
|
1566
|
+
},
|
|
1567
|
+
{
|
|
1568
|
+
version: 12,
|
|
1569
|
+
up: (db) => {
|
|
1570
|
+
db.run(`
|
|
1571
|
+
CREATE TABLE IF NOT EXISTS github_links (
|
|
1572
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1573
|
+
observation_id INTEGER,
|
|
1574
|
+
session_id TEXT,
|
|
1575
|
+
repo TEXT NOT NULL,
|
|
1576
|
+
issue_number INTEGER,
|
|
1577
|
+
pr_number INTEGER,
|
|
1578
|
+
event_type TEXT NOT NULL,
|
|
1579
|
+
action TEXT,
|
|
1580
|
+
title TEXT,
|
|
1581
|
+
url TEXT,
|
|
1582
|
+
author TEXT,
|
|
1583
|
+
created_at TEXT NOT NULL,
|
|
1584
|
+
created_at_epoch INTEGER NOT NULL,
|
|
1585
|
+
FOREIGN KEY (observation_id) REFERENCES observations(id)
|
|
1586
|
+
)
|
|
1587
|
+
`);
|
|
1588
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_github_links_repo ON github_links(repo)");
|
|
1589
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_github_links_obs ON github_links(observation_id)");
|
|
1590
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_github_links_event ON github_links(event_type)");
|
|
1591
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_github_links_repo_issue ON github_links(repo, issue_number)");
|
|
1592
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_github_links_repo_pr ON github_links(repo, pr_number)");
|
|
1593
|
+
}
|
|
1594
|
+
},
|
|
1595
|
+
{
|
|
1596
|
+
version: 13,
|
|
1597
|
+
up: (db) => {
|
|
1598
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_observations_keyset ON observations(created_at_epoch DESC, id DESC)");
|
|
1599
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_observations_project_keyset ON observations(project, created_at_epoch DESC, id DESC)");
|
|
1600
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_summaries_keyset ON summaries(created_at_epoch DESC, id DESC)");
|
|
1601
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_summaries_project_keyset ON summaries(project, created_at_epoch DESC, id DESC)");
|
|
1602
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_prompts_keyset ON prompts(created_at_epoch DESC, id DESC)");
|
|
1603
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_prompts_project_keyset ON prompts(project, created_at_epoch DESC, id DESC)");
|
|
1604
|
+
}
|
|
1188
1605
|
}
|
|
1189
1606
|
];
|
|
1190
1607
|
}
|
|
1191
1608
|
};
|
|
1192
1609
|
|
|
1610
|
+
// src/services/sqlite/cursor.ts
|
|
1611
|
+
function encodeCursor(id, epoch) {
|
|
1612
|
+
const raw = `${epoch}:${id}`;
|
|
1613
|
+
return Buffer.from(raw, "utf8").toString("base64url");
|
|
1614
|
+
}
|
|
1615
|
+
function decodeCursor(cursor) {
|
|
1616
|
+
try {
|
|
1617
|
+
const raw = Buffer.from(cursor, "base64url").toString("utf8");
|
|
1618
|
+
const colonIdx = raw.indexOf(":");
|
|
1619
|
+
if (colonIdx === -1) return null;
|
|
1620
|
+
const epochStr = raw.substring(0, colonIdx);
|
|
1621
|
+
const idStr = raw.substring(colonIdx + 1);
|
|
1622
|
+
const epoch = parseInt(epochStr, 10);
|
|
1623
|
+
const id = parseInt(idStr, 10);
|
|
1624
|
+
if (!Number.isInteger(epoch) || epoch <= 0) return null;
|
|
1625
|
+
if (!Number.isInteger(id) || id <= 0) return null;
|
|
1626
|
+
return { epoch, id };
|
|
1627
|
+
} catch {
|
|
1628
|
+
return null;
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1193
1632
|
// src/services/sqlite/Sessions.ts
|
|
1194
1633
|
function createSession(db, contentSessionId, project, userPrompt) {
|
|
1195
1634
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -1233,16 +1672,16 @@ function createSummary(db, sessionId, project, request, investigated, learned, c
|
|
|
1233
1672
|
}
|
|
1234
1673
|
function getSummariesByProject(db, project, limit = 50) {
|
|
1235
1674
|
const query = db.query(
|
|
1236
|
-
"SELECT * FROM summaries WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ?"
|
|
1675
|
+
"SELECT * FROM summaries WHERE project = ? ORDER BY created_at_epoch DESC, id DESC LIMIT ?"
|
|
1237
1676
|
);
|
|
1238
1677
|
return query.all(project, limit);
|
|
1239
1678
|
}
|
|
1240
1679
|
function searchSummaries(db, searchTerm, project) {
|
|
1241
1680
|
const sql = project ? `SELECT * FROM summaries
|
|
1242
1681
|
WHERE project = ? AND (request LIKE ? ESCAPE '\\' OR learned LIKE ? ESCAPE '\\' OR completed LIKE ? ESCAPE '\\' OR notes LIKE ? ESCAPE '\\')
|
|
1243
|
-
ORDER BY created_at_epoch DESC` : `SELECT * FROM summaries
|
|
1682
|
+
ORDER BY created_at_epoch DESC, id DESC` : `SELECT * FROM summaries
|
|
1244
1683
|
WHERE request LIKE ? ESCAPE '\\' OR learned LIKE ? ESCAPE '\\' OR completed LIKE ? ESCAPE '\\' OR notes LIKE ? ESCAPE '\\'
|
|
1245
|
-
ORDER BY created_at_epoch DESC`;
|
|
1684
|
+
ORDER BY created_at_epoch DESC, id DESC`;
|
|
1246
1685
|
const pattern = `%${escapeLikePattern2(searchTerm)}%`;
|
|
1247
1686
|
const query = db.query(sql);
|
|
1248
1687
|
if (project) {
|
|
@@ -1264,7 +1703,7 @@ function createPrompt(db, contentSessionId, project, promptNumber, promptText) {
|
|
|
1264
1703
|
}
|
|
1265
1704
|
function getPromptsByProject(db, project, limit = 100) {
|
|
1266
1705
|
const query = db.query(
|
|
1267
|
-
"SELECT * FROM prompts WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ?"
|
|
1706
|
+
"SELECT * FROM prompts WHERE project = ? ORDER BY created_at_epoch DESC, id DESC LIMIT ?"
|
|
1268
1707
|
);
|
|
1269
1708
|
return query.all(project, limit);
|
|
1270
1709
|
}
|
|
@@ -1292,13 +1731,13 @@ function createCheckpoint(db, sessionId, project, data) {
|
|
|
1292
1731
|
}
|
|
1293
1732
|
function getLatestCheckpoint(db, sessionId) {
|
|
1294
1733
|
const query = db.query(
|
|
1295
|
-
"SELECT * FROM checkpoints WHERE session_id = ? ORDER BY created_at_epoch DESC LIMIT 1"
|
|
1734
|
+
"SELECT * FROM checkpoints WHERE session_id = ? ORDER BY created_at_epoch DESC, id DESC LIMIT 1"
|
|
1296
1735
|
);
|
|
1297
1736
|
return query.get(sessionId);
|
|
1298
1737
|
}
|
|
1299
1738
|
function getLatestCheckpointByProject(db, project) {
|
|
1300
1739
|
const query = db.query(
|
|
1301
|
-
"SELECT * FROM checkpoints WHERE project = ? ORDER BY created_at_epoch DESC LIMIT 1"
|
|
1740
|
+
"SELECT * FROM checkpoints WHERE project = ? ORDER BY created_at_epoch DESC, id DESC LIMIT 1"
|
|
1302
1741
|
);
|
|
1303
1742
|
return query.get(project);
|
|
1304
1743
|
}
|
|
@@ -1360,9 +1799,9 @@ function getReportData(db, project, startEpoch, endEpoch) {
|
|
|
1360
1799
|
const staleCount = (project ? db.query(staleSql).get(project, startEpoch, endEpoch)?.count : db.query(staleSql).get(startEpoch, endEpoch)?.count) || 0;
|
|
1361
1800
|
const summarySql = project ? `SELECT learned, completed, next_steps FROM summaries
|
|
1362
1801
|
WHERE project = ? AND created_at_epoch >= ? AND created_at_epoch <= ?
|
|
1363
|
-
ORDER BY created_at_epoch DESC` : `SELECT learned, completed, next_steps FROM summaries
|
|
1802
|
+
ORDER BY created_at_epoch DESC, id DESC` : `SELECT learned, completed, next_steps FROM summaries
|
|
1364
1803
|
WHERE created_at_epoch >= ? AND created_at_epoch <= ?
|
|
1365
|
-
ORDER BY created_at_epoch DESC`;
|
|
1804
|
+
ORDER BY created_at_epoch DESC, id DESC`;
|
|
1366
1805
|
const summaryRows = project ? db.query(summarySql).all(project, startEpoch, endEpoch) : db.query(summarySql).all(startEpoch, endEpoch);
|
|
1367
1806
|
const topLearnings = [];
|
|
1368
1807
|
const completedTasks = [];
|
|
@@ -1427,20 +1866,61 @@ function getReportData(db, project, startEpoch, endEpoch) {
|
|
|
1427
1866
|
// src/services/sqlite/index.ts
|
|
1428
1867
|
init_Search();
|
|
1429
1868
|
|
|
1869
|
+
// src/types/worker-types.ts
|
|
1870
|
+
var KNOWLEDGE_TYPES = ["constraint", "decision", "heuristic", "rejected"];
|
|
1871
|
+
|
|
1872
|
+
// src/services/sqlite/Retention.ts
|
|
1873
|
+
var KNOWLEDGE_TYPE_LIST = KNOWLEDGE_TYPES;
|
|
1874
|
+
var KNOWLEDGE_PLACEHOLDERS = KNOWLEDGE_TYPE_LIST.map(() => "?").join(", ");
|
|
1875
|
+
|
|
1430
1876
|
// src/sdk/index.ts
|
|
1431
1877
|
init_Observations();
|
|
1432
1878
|
import { createHash } from "crypto";
|
|
1433
1879
|
init_Search();
|
|
1434
1880
|
|
|
1435
1881
|
// src/services/search/EmbeddingService.ts
|
|
1882
|
+
var MODEL_CONFIGS = {
|
|
1883
|
+
"all-MiniLM-L6-v2": {
|
|
1884
|
+
modelId: "Xenova/all-MiniLM-L6-v2",
|
|
1885
|
+
dimensions: 384
|
|
1886
|
+
},
|
|
1887
|
+
"jina-code-v2": {
|
|
1888
|
+
modelId: "jinaai/jina-embeddings-v2-base-code",
|
|
1889
|
+
dimensions: 768
|
|
1890
|
+
},
|
|
1891
|
+
"bge-small-en": {
|
|
1892
|
+
modelId: "BAAI/bge-small-en-v1.5",
|
|
1893
|
+
dimensions: 384
|
|
1894
|
+
}
|
|
1895
|
+
};
|
|
1896
|
+
var FASTEMBED_COMPATIBLE_MODELS = /* @__PURE__ */ new Set(["all-MiniLM-L6-v2", "bge-small-en"]);
|
|
1436
1897
|
var EmbeddingService = class {
|
|
1437
1898
|
provider = null;
|
|
1438
1899
|
model = null;
|
|
1439
1900
|
initialized = false;
|
|
1440
1901
|
initializing = null;
|
|
1902
|
+
config;
|
|
1903
|
+
configName;
|
|
1904
|
+
constructor() {
|
|
1905
|
+
const envModel = process.env.KIRO_MEMORY_EMBEDDING_MODEL || "all-MiniLM-L6-v2";
|
|
1906
|
+
this.configName = envModel;
|
|
1907
|
+
if (MODEL_CONFIGS[envModel]) {
|
|
1908
|
+
this.config = MODEL_CONFIGS[envModel];
|
|
1909
|
+
} else if (envModel.includes("/")) {
|
|
1910
|
+
const dimensions = parseInt(process.env.KIRO_MEMORY_EMBEDDING_DIMENSIONS || "384", 10);
|
|
1911
|
+
this.config = {
|
|
1912
|
+
modelId: envModel,
|
|
1913
|
+
dimensions: isNaN(dimensions) ? 384 : dimensions
|
|
1914
|
+
};
|
|
1915
|
+
} else {
|
|
1916
|
+
logger.warn("EMBEDDING", `Unknown model name '${envModel}', falling back to 'all-MiniLM-L6-v2'`);
|
|
1917
|
+
this.configName = "all-MiniLM-L6-v2";
|
|
1918
|
+
this.config = MODEL_CONFIGS["all-MiniLM-L6-v2"];
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1441
1921
|
/**
|
|
1442
|
-
*
|
|
1443
|
-
*
|
|
1922
|
+
* Initialize the embedding service.
|
|
1923
|
+
* Tries fastembed (when compatible), then @huggingface/transformers, then falls back to null.
|
|
1444
1924
|
*/
|
|
1445
1925
|
async initialize() {
|
|
1446
1926
|
if (this.initialized) return this.provider !== null;
|
|
@@ -1451,45 +1931,48 @@ var EmbeddingService = class {
|
|
|
1451
1931
|
return result;
|
|
1452
1932
|
}
|
|
1453
1933
|
async _doInitialize() {
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1934
|
+
const fastembedCompatible = FASTEMBED_COMPATIBLE_MODELS.has(this.configName);
|
|
1935
|
+
if (fastembedCompatible) {
|
|
1936
|
+
try {
|
|
1937
|
+
const fastembed = await import("fastembed");
|
|
1938
|
+
const EmbeddingModel = fastembed.EmbeddingModel || fastembed.default?.EmbeddingModel;
|
|
1939
|
+
const FlagEmbedding = fastembed.FlagEmbedding || fastembed.default?.FlagEmbedding;
|
|
1940
|
+
if (FlagEmbedding && EmbeddingModel) {
|
|
1941
|
+
this.model = await FlagEmbedding.init({
|
|
1942
|
+
model: EmbeddingModel.BGESmallENV15
|
|
1943
|
+
});
|
|
1944
|
+
this.provider = "fastembed";
|
|
1945
|
+
this.initialized = true;
|
|
1946
|
+
logger.info("EMBEDDING", `Initialized with fastembed (BGE-small-en-v1.5) for model '${this.configName}'`);
|
|
1947
|
+
return true;
|
|
1948
|
+
}
|
|
1949
|
+
} catch (error) {
|
|
1950
|
+
logger.debug("EMBEDDING", `fastembed not available: ${error}`);
|
|
1466
1951
|
}
|
|
1467
|
-
} catch (error) {
|
|
1468
|
-
logger.debug("EMBEDDING", `fastembed non disponibile: ${error}`);
|
|
1469
1952
|
}
|
|
1470
1953
|
try {
|
|
1471
1954
|
const transformers = await import("@huggingface/transformers");
|
|
1472
1955
|
const pipeline = transformers.pipeline || transformers.default?.pipeline;
|
|
1473
1956
|
if (pipeline) {
|
|
1474
|
-
this.model = await pipeline("feature-extraction",
|
|
1957
|
+
this.model = await pipeline("feature-extraction", this.config.modelId, {
|
|
1475
1958
|
quantized: true
|
|
1476
1959
|
});
|
|
1477
1960
|
this.provider = "transformers";
|
|
1478
1961
|
this.initialized = true;
|
|
1479
|
-
logger.info("EMBEDDING",
|
|
1962
|
+
logger.info("EMBEDDING", `Initialized with @huggingface/transformers (${this.config.modelId})`);
|
|
1480
1963
|
return true;
|
|
1481
1964
|
}
|
|
1482
1965
|
} catch (error) {
|
|
1483
|
-
logger.debug("EMBEDDING", `@huggingface/transformers
|
|
1966
|
+
logger.debug("EMBEDDING", `@huggingface/transformers not available: ${error}`);
|
|
1484
1967
|
}
|
|
1485
1968
|
this.provider = null;
|
|
1486
1969
|
this.initialized = true;
|
|
1487
|
-
logger.warn("EMBEDDING", "
|
|
1970
|
+
logger.warn("EMBEDDING", "No embedding provider available, semantic search disabled");
|
|
1488
1971
|
return false;
|
|
1489
1972
|
}
|
|
1490
1973
|
/**
|
|
1491
|
-
*
|
|
1492
|
-
*
|
|
1974
|
+
* Generate embedding for a single text.
|
|
1975
|
+
* Returns Float32Array with configured dimensions, or null if not available.
|
|
1493
1976
|
*/
|
|
1494
1977
|
async embed(text) {
|
|
1495
1978
|
if (!this.initialized) await this.initialize();
|
|
@@ -1502,46 +1985,118 @@ var EmbeddingService = class {
|
|
|
1502
1985
|
return await this._embedTransformers(truncated);
|
|
1503
1986
|
}
|
|
1504
1987
|
} catch (error) {
|
|
1505
|
-
logger.error("EMBEDDING", `
|
|
1988
|
+
logger.error("EMBEDDING", `Error generating embedding: ${error}`);
|
|
1506
1989
|
}
|
|
1507
1990
|
return null;
|
|
1508
1991
|
}
|
|
1509
1992
|
/**
|
|
1510
|
-
*
|
|
1993
|
+
* Generate embeddings in batch.
|
|
1994
|
+
* Uses native batch support when available (fastembed, transformers),
|
|
1995
|
+
* falls back to serial processing on batch failure.
|
|
1511
1996
|
*/
|
|
1512
1997
|
async embedBatch(texts) {
|
|
1513
1998
|
if (!this.initialized) await this.initialize();
|
|
1514
1999
|
if (!this.provider || !this.model) return texts.map(() => null);
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
}
|
|
1521
|
-
|
|
2000
|
+
if (texts.length === 0) return [];
|
|
2001
|
+
const truncated = texts.map((t) => t.substring(0, 2e3));
|
|
2002
|
+
try {
|
|
2003
|
+
if (this.provider === "fastembed") {
|
|
2004
|
+
return await this._embedBatchFastembed(truncated);
|
|
2005
|
+
} else if (this.provider === "transformers") {
|
|
2006
|
+
return await this._embedBatchTransformers(truncated);
|
|
1522
2007
|
}
|
|
2008
|
+
} catch (error) {
|
|
2009
|
+
logger.warn("EMBEDDING", `Batch embedding failed, falling back to serial: ${error}`);
|
|
1523
2010
|
}
|
|
1524
|
-
return
|
|
2011
|
+
return this._embedBatchSerial(truncated);
|
|
1525
2012
|
}
|
|
1526
2013
|
/**
|
|
1527
|
-
*
|
|
2014
|
+
* Check if the service is available.
|
|
1528
2015
|
*/
|
|
1529
2016
|
isAvailable() {
|
|
1530
2017
|
return this.initialized && this.provider !== null;
|
|
1531
2018
|
}
|
|
1532
2019
|
/**
|
|
1533
|
-
*
|
|
2020
|
+
* Name of the active provider.
|
|
1534
2021
|
*/
|
|
1535
2022
|
getProvider() {
|
|
1536
2023
|
return this.provider;
|
|
1537
2024
|
}
|
|
1538
2025
|
/**
|
|
1539
|
-
*
|
|
2026
|
+
* Embedding vector dimensions for the active model configuration.
|
|
1540
2027
|
*/
|
|
1541
2028
|
getDimensions() {
|
|
1542
|
-
return
|
|
2029
|
+
return this.config.dimensions;
|
|
2030
|
+
}
|
|
2031
|
+
/**
|
|
2032
|
+
* Human-readable model name used as identifier in the observation_embeddings table.
|
|
2033
|
+
* Returns the short name (e.g., 'all-MiniLM-L6-v2') or the full HF model ID for custom models.
|
|
2034
|
+
*/
|
|
2035
|
+
getModelName() {
|
|
2036
|
+
return this.configName;
|
|
2037
|
+
}
|
|
2038
|
+
// --- Batch implementations ---
|
|
2039
|
+
/**
|
|
2040
|
+
* Native batch embedding with fastembed.
|
|
2041
|
+
* FlagEmbedding.embed() accepts string[] and returns an async iterable of batches.
|
|
2042
|
+
*/
|
|
2043
|
+
async _embedBatchFastembed(texts) {
|
|
2044
|
+
const results = [];
|
|
2045
|
+
const embeddings = this.model.embed(texts, texts.length);
|
|
2046
|
+
for await (const batch of embeddings) {
|
|
2047
|
+
if (batch) {
|
|
2048
|
+
for (const vec of batch) {
|
|
2049
|
+
results.push(vec instanceof Float32Array ? vec : new Float32Array(vec));
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
while (results.length < texts.length) {
|
|
2054
|
+
results.push(null);
|
|
2055
|
+
}
|
|
2056
|
+
return results;
|
|
1543
2057
|
}
|
|
1544
|
-
|
|
2058
|
+
/**
|
|
2059
|
+
* Batch embedding with @huggingface/transformers pipeline.
|
|
2060
|
+
* The pipeline accepts string[] and returns a Tensor with shape [N, dims].
|
|
2061
|
+
*/
|
|
2062
|
+
async _embedBatchTransformers(texts) {
|
|
2063
|
+
const output = await this.model(texts, {
|
|
2064
|
+
pooling: "mean",
|
|
2065
|
+
normalize: true
|
|
2066
|
+
});
|
|
2067
|
+
if (!output?.data) {
|
|
2068
|
+
return texts.map(() => null);
|
|
2069
|
+
}
|
|
2070
|
+
const dims = this.getDimensions();
|
|
2071
|
+
const data = output.data instanceof Float32Array ? output.data : new Float32Array(output.data);
|
|
2072
|
+
const results = [];
|
|
2073
|
+
for (let i = 0; i < texts.length; i++) {
|
|
2074
|
+
const offset = i * dims;
|
|
2075
|
+
if (offset + dims <= data.length) {
|
|
2076
|
+
results.push(data.slice(offset, offset + dims));
|
|
2077
|
+
} else {
|
|
2078
|
+
results.push(null);
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
return results;
|
|
2082
|
+
}
|
|
2083
|
+
/**
|
|
2084
|
+
* Serial fallback: embed texts one at a time.
|
|
2085
|
+
* Used when native batch fails.
|
|
2086
|
+
*/
|
|
2087
|
+
async _embedBatchSerial(texts) {
|
|
2088
|
+
const results = [];
|
|
2089
|
+
for (const text of texts) {
|
|
2090
|
+
try {
|
|
2091
|
+
const embedding = await this.embed(text);
|
|
2092
|
+
results.push(embedding);
|
|
2093
|
+
} catch {
|
|
2094
|
+
results.push(null);
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
return results;
|
|
2098
|
+
}
|
|
2099
|
+
// --- Single-text provider implementations ---
|
|
1545
2100
|
async _embedFastembed(text) {
|
|
1546
2101
|
const embeddings = this.model.embed([text], 1);
|
|
1547
2102
|
for await (const batch of embeddings) {
|
|
@@ -1572,17 +2127,21 @@ function getEmbeddingService() {
|
|
|
1572
2127
|
}
|
|
1573
2128
|
|
|
1574
2129
|
// src/services/search/VectorSearch.ts
|
|
2130
|
+
var DEFAULT_MAX_CANDIDATES = 2e3;
|
|
1575
2131
|
function cosineSimilarity(a, b) {
|
|
1576
|
-
|
|
2132
|
+
const len = a.length;
|
|
2133
|
+
if (len !== b.length) return 0;
|
|
1577
2134
|
let dotProduct = 0;
|
|
1578
2135
|
let normA = 0;
|
|
1579
2136
|
let normB = 0;
|
|
1580
|
-
for (let i = 0; i <
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
2137
|
+
for (let i = 0; i < len; i++) {
|
|
2138
|
+
const ai = a[i];
|
|
2139
|
+
const bi = b[i];
|
|
2140
|
+
dotProduct += ai * bi;
|
|
2141
|
+
normA += ai * ai;
|
|
2142
|
+
normB += bi * bi;
|
|
2143
|
+
}
|
|
2144
|
+
const denominator = Math.sqrt(normA * normB);
|
|
1586
2145
|
if (denominator === 0) return 0;
|
|
1587
2146
|
return dotProduct / denominator;
|
|
1588
2147
|
}
|
|
@@ -1595,23 +2154,36 @@ function bufferToFloat32(buf) {
|
|
|
1595
2154
|
}
|
|
1596
2155
|
var VectorSearch = class {
|
|
1597
2156
|
/**
|
|
1598
|
-
*
|
|
2157
|
+
* Semantic search with SQL pre-filtering for scalability.
|
|
2158
|
+
*
|
|
2159
|
+
* 2-phase strategy:
|
|
2160
|
+
* 1. SQL pre-filters by project + sorts by recency (loads max N candidates)
|
|
2161
|
+
* 2. JS computes cosine similarity only on filtered candidates
|
|
2162
|
+
*
|
|
2163
|
+
* With 50k observations and maxCandidates=2000, loads only ~4% of data.
|
|
1599
2164
|
*/
|
|
1600
2165
|
async search(db, queryEmbedding, options = {}) {
|
|
1601
2166
|
const limit = options.limit || 10;
|
|
1602
2167
|
const threshold = options.threshold || 0.3;
|
|
2168
|
+
const maxCandidates = options.maxCandidates || DEFAULT_MAX_CANDIDATES;
|
|
1603
2169
|
try {
|
|
1604
|
-
|
|
2170
|
+
const conditions = [];
|
|
2171
|
+
const params = [];
|
|
2172
|
+
if (options.project) {
|
|
2173
|
+
conditions.push("o.project = ?");
|
|
2174
|
+
params.push(options.project);
|
|
2175
|
+
}
|
|
2176
|
+
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
2177
|
+
const sql = `
|
|
1605
2178
|
SELECT e.observation_id, e.embedding,
|
|
1606
2179
|
o.title, o.text, o.type, o.project, o.created_at, o.created_at_epoch
|
|
1607
2180
|
FROM observation_embeddings e
|
|
1608
2181
|
JOIN observations o ON o.id = e.observation_id
|
|
2182
|
+
${whereClause}
|
|
2183
|
+
ORDER BY o.created_at_epoch DESC
|
|
2184
|
+
LIMIT ?
|
|
1609
2185
|
`;
|
|
1610
|
-
|
|
1611
|
-
if (options.project) {
|
|
1612
|
-
sql += " WHERE o.project = ?";
|
|
1613
|
-
params.push(options.project);
|
|
1614
|
-
}
|
|
2186
|
+
params.push(maxCandidates);
|
|
1615
2187
|
const rows = db.query(sql).all(...params);
|
|
1616
2188
|
const scored = [];
|
|
1617
2189
|
for (const row of rows) {
|
|
@@ -1632,14 +2204,15 @@ var VectorSearch = class {
|
|
|
1632
2204
|
}
|
|
1633
2205
|
}
|
|
1634
2206
|
scored.sort((a, b) => b.similarity - a.similarity);
|
|
2207
|
+
logger.debug("VECTOR", `Search: ${rows.length} candidates \u2192 ${scored.length} above threshold \u2192 ${Math.min(scored.length, limit)} results`);
|
|
1635
2208
|
return scored.slice(0, limit);
|
|
1636
2209
|
} catch (error) {
|
|
1637
|
-
logger.error("VECTOR", `
|
|
2210
|
+
logger.error("VECTOR", `Vector search error: ${error}`);
|
|
1638
2211
|
return [];
|
|
1639
2212
|
}
|
|
1640
2213
|
}
|
|
1641
2214
|
/**
|
|
1642
|
-
*
|
|
2215
|
+
* Store embedding for an observation.
|
|
1643
2216
|
*/
|
|
1644
2217
|
async storeEmbedding(db, observationId, embedding, model) {
|
|
1645
2218
|
try {
|
|
@@ -1655,18 +2228,18 @@ var VectorSearch = class {
|
|
|
1655
2228
|
embedding.length,
|
|
1656
2229
|
(/* @__PURE__ */ new Date()).toISOString()
|
|
1657
2230
|
);
|
|
1658
|
-
logger.debug("VECTOR", `Embedding
|
|
2231
|
+
logger.debug("VECTOR", `Embedding saved for observation ${observationId}`);
|
|
1659
2232
|
} catch (error) {
|
|
1660
|
-
logger.error("VECTOR", `
|
|
2233
|
+
logger.error("VECTOR", `Error saving embedding: ${error}`);
|
|
1661
2234
|
}
|
|
1662
2235
|
}
|
|
1663
2236
|
/**
|
|
1664
|
-
*
|
|
2237
|
+
* Generate embeddings for observations that don't have them yet.
|
|
1665
2238
|
*/
|
|
1666
2239
|
async backfillEmbeddings(db, batchSize = 50) {
|
|
1667
2240
|
const embeddingService2 = getEmbeddingService();
|
|
1668
2241
|
if (!await embeddingService2.initialize()) {
|
|
1669
|
-
logger.warn("VECTOR", "Embedding service
|
|
2242
|
+
logger.warn("VECTOR", "Embedding service not available, backfill skipped");
|
|
1670
2243
|
return 0;
|
|
1671
2244
|
}
|
|
1672
2245
|
const rows = db.query(`
|
|
@@ -1679,7 +2252,7 @@ var VectorSearch = class {
|
|
|
1679
2252
|
`).all(batchSize);
|
|
1680
2253
|
if (rows.length === 0) return 0;
|
|
1681
2254
|
let count = 0;
|
|
1682
|
-
const model = embeddingService2.
|
|
2255
|
+
const model = embeddingService2.getModelName();
|
|
1683
2256
|
for (const row of rows) {
|
|
1684
2257
|
const parts = [row.title];
|
|
1685
2258
|
if (row.text) parts.push(row.text);
|
|
@@ -1692,11 +2265,11 @@ var VectorSearch = class {
|
|
|
1692
2265
|
count++;
|
|
1693
2266
|
}
|
|
1694
2267
|
}
|
|
1695
|
-
logger.info("VECTOR", `Backfill
|
|
2268
|
+
logger.info("VECTOR", `Backfill completed: ${count}/${rows.length} embeddings generated`);
|
|
1696
2269
|
return count;
|
|
1697
2270
|
}
|
|
1698
2271
|
/**
|
|
1699
|
-
*
|
|
2272
|
+
* Embedding statistics.
|
|
1700
2273
|
*/
|
|
1701
2274
|
getStats(db) {
|
|
1702
2275
|
try {
|
|
@@ -1723,21 +2296,21 @@ function getVectorSearch() {
|
|
|
1723
2296
|
var HybridSearch = class {
|
|
1724
2297
|
embeddingInitialized = false;
|
|
1725
2298
|
/**
|
|
1726
|
-
*
|
|
2299
|
+
* Initialize the embedding service (lazy, non-blocking)
|
|
1727
2300
|
*/
|
|
1728
2301
|
async initialize() {
|
|
1729
2302
|
try {
|
|
1730
2303
|
const embeddingService2 = getEmbeddingService();
|
|
1731
2304
|
await embeddingService2.initialize();
|
|
1732
2305
|
this.embeddingInitialized = embeddingService2.isAvailable();
|
|
1733
|
-
logger.info("SEARCH", `HybridSearch
|
|
2306
|
+
logger.info("SEARCH", `HybridSearch initialized (embedding: ${this.embeddingInitialized ? "active" : "disabled"})`);
|
|
1734
2307
|
} catch (error) {
|
|
1735
|
-
logger.warn("SEARCH", "
|
|
2308
|
+
logger.warn("SEARCH", "Embedding initialization failed, using only FTS5", {}, error);
|
|
1736
2309
|
this.embeddingInitialized = false;
|
|
1737
2310
|
}
|
|
1738
2311
|
}
|
|
1739
2312
|
/**
|
|
1740
|
-
*
|
|
2313
|
+
* Hybrid search with 4-signal scoring
|
|
1741
2314
|
*/
|
|
1742
2315
|
async search(db, query, options = {}) {
|
|
1743
2316
|
const limit = options.limit || 10;
|
|
@@ -1753,7 +2326,7 @@ var HybridSearch = class {
|
|
|
1753
2326
|
const vectorResults = await vectorSearch2.search(db, queryEmbedding, {
|
|
1754
2327
|
project: options.project,
|
|
1755
2328
|
limit: limit * 2,
|
|
1756
|
-
//
|
|
2329
|
+
// Fetch more results for ranking
|
|
1757
2330
|
threshold: 0.3
|
|
1758
2331
|
});
|
|
1759
2332
|
for (const hit of vectorResults) {
|
|
@@ -1770,10 +2343,10 @@ var HybridSearch = class {
|
|
|
1770
2343
|
source: "vector"
|
|
1771
2344
|
});
|
|
1772
2345
|
}
|
|
1773
|
-
logger.debug("SEARCH", `Vector search: ${vectorResults.length}
|
|
2346
|
+
logger.debug("SEARCH", `Vector search: ${vectorResults.length} results`);
|
|
1774
2347
|
}
|
|
1775
2348
|
} catch (error) {
|
|
1776
|
-
logger.warn("SEARCH", "
|
|
2349
|
+
logger.warn("SEARCH", "Vector search failed, using only keyword", {}, error);
|
|
1777
2350
|
}
|
|
1778
2351
|
}
|
|
1779
2352
|
try {
|
|
@@ -1803,9 +2376,9 @@ var HybridSearch = class {
|
|
|
1803
2376
|
});
|
|
1804
2377
|
}
|
|
1805
2378
|
}
|
|
1806
|
-
logger.debug("SEARCH", `Keyword search: ${keywordResults.length}
|
|
2379
|
+
logger.debug("SEARCH", `Keyword search: ${keywordResults.length} results`);
|
|
1807
2380
|
} catch (error) {
|
|
1808
|
-
logger.error("SEARCH", "
|
|
2381
|
+
logger.error("SEARCH", "Keyword search failed", {}, error);
|
|
1809
2382
|
}
|
|
1810
2383
|
if (rawItems.size === 0) return [];
|
|
1811
2384
|
const allFTS5Ranks = Array.from(rawItems.values()).filter((item) => item.fts5Rank !== null).map((item) => item.fts5Rank);
|
|
@@ -1857,9 +2430,6 @@ function getHybridSearch() {
|
|
|
1857
2430
|
return hybridSearch;
|
|
1858
2431
|
}
|
|
1859
2432
|
|
|
1860
|
-
// src/types/worker-types.ts
|
|
1861
|
-
var KNOWLEDGE_TYPES = ["constraint", "decision", "heuristic", "rejected"];
|
|
1862
|
-
|
|
1863
2433
|
// src/sdk/index.ts
|
|
1864
2434
|
var KiroMemorySDK = class {
|
|
1865
2435
|
db;
|
|
@@ -1893,33 +2463,33 @@ var KiroMemorySDK = class {
|
|
|
1893
2463
|
};
|
|
1894
2464
|
}
|
|
1895
2465
|
/**
|
|
1896
|
-
*
|
|
2466
|
+
* Validate input for storeObservation
|
|
1897
2467
|
*/
|
|
1898
2468
|
validateObservationInput(data) {
|
|
1899
2469
|
if (!data.type || typeof data.type !== "string" || data.type.length > 100) {
|
|
1900
|
-
throw new Error("type
|
|
2470
|
+
throw new Error("type is required (string, max 100 chars)");
|
|
1901
2471
|
}
|
|
1902
2472
|
if (!data.title || typeof data.title !== "string" || data.title.length > 500) {
|
|
1903
|
-
throw new Error("title
|
|
2473
|
+
throw new Error("title is required (string, max 500 chars)");
|
|
1904
2474
|
}
|
|
1905
2475
|
if (!data.content || typeof data.content !== "string" || data.content.length > 1e5) {
|
|
1906
|
-
throw new Error("content
|
|
2476
|
+
throw new Error("content is required (string, max 100KB)");
|
|
1907
2477
|
}
|
|
1908
2478
|
}
|
|
1909
2479
|
/**
|
|
1910
|
-
*
|
|
2480
|
+
* Validate input for storeSummary
|
|
1911
2481
|
*/
|
|
1912
2482
|
validateSummaryInput(data) {
|
|
1913
2483
|
const MAX = 5e4;
|
|
1914
2484
|
for (const [key, val] of Object.entries(data)) {
|
|
1915
2485
|
if (val !== void 0 && val !== null) {
|
|
1916
|
-
if (typeof val !== "string") throw new Error(`${key}
|
|
1917
|
-
if (val.length > MAX) throw new Error(`${key}
|
|
2486
|
+
if (typeof val !== "string") throw new Error(`${key} must be a string`);
|
|
2487
|
+
if (val.length > MAX) throw new Error(`${key} too large (max 50KB)`);
|
|
1918
2488
|
}
|
|
1919
2489
|
}
|
|
1920
2490
|
}
|
|
1921
2491
|
/**
|
|
1922
|
-
*
|
|
2492
|
+
* Generate and store embedding for an observation (fire-and-forget, non-blocking)
|
|
1923
2493
|
*/
|
|
1924
2494
|
async generateEmbeddingAsync(observationId, title, content, concepts) {
|
|
1925
2495
|
try {
|
|
@@ -1939,39 +2509,39 @@ var KiroMemorySDK = class {
|
|
|
1939
2509
|
);
|
|
1940
2510
|
}
|
|
1941
2511
|
} catch (error) {
|
|
1942
|
-
logger.debug("SDK", `Embedding generation
|
|
2512
|
+
logger.debug("SDK", `Embedding generation failed for obs ${observationId}: ${error}`);
|
|
1943
2513
|
}
|
|
1944
2514
|
}
|
|
1945
2515
|
/**
|
|
1946
|
-
*
|
|
1947
|
-
*
|
|
1948
|
-
*
|
|
2516
|
+
* Generate SHA256 content hash for content-based deduplication.
|
|
2517
|
+
* Uses (project + type + title + narrative) as semantic identity tuple.
|
|
2518
|
+
* Does NOT include sessionId since it's unique per invocation.
|
|
1949
2519
|
*/
|
|
1950
2520
|
generateContentHash(type, title, narrative) {
|
|
1951
2521
|
const payload = `${this.project}|${type}|${title}|${narrative || ""}`;
|
|
1952
2522
|
return createHash("sha256").update(payload).digest("hex");
|
|
1953
2523
|
}
|
|
1954
2524
|
/**
|
|
1955
|
-
*
|
|
1956
|
-
*
|
|
2525
|
+
* Deduplication windows per type (ms).
|
|
2526
|
+
* Types with many repetitions have wider windows.
|
|
1957
2527
|
*/
|
|
1958
2528
|
getDeduplicationWindow(type) {
|
|
1959
2529
|
switch (type) {
|
|
1960
2530
|
case "file-read":
|
|
1961
2531
|
return 6e4;
|
|
1962
|
-
// 60s —
|
|
2532
|
+
// 60s — frequent reads on the same files
|
|
1963
2533
|
case "file-write":
|
|
1964
2534
|
return 1e4;
|
|
1965
|
-
// 10s —
|
|
2535
|
+
// 10s — rapid consecutive writes
|
|
1966
2536
|
case "command":
|
|
1967
2537
|
return 3e4;
|
|
1968
2538
|
// 30s — standard
|
|
1969
2539
|
case "research":
|
|
1970
2540
|
return 12e4;
|
|
1971
|
-
// 120s — web search
|
|
2541
|
+
// 120s — repeated web search and fetch
|
|
1972
2542
|
case "delegation":
|
|
1973
2543
|
return 6e4;
|
|
1974
|
-
// 60s —
|
|
2544
|
+
// 60s — rapid delegations
|
|
1975
2545
|
default:
|
|
1976
2546
|
return 3e4;
|
|
1977
2547
|
}
|
|
@@ -1985,7 +2555,7 @@ var KiroMemorySDK = class {
|
|
|
1985
2555
|
const contentHash = this.generateContentHash(data.type, data.title, data.narrative);
|
|
1986
2556
|
const dedupWindow = this.getDeduplicationWindow(data.type);
|
|
1987
2557
|
if (isDuplicateObservation(this.db.db, contentHash, dedupWindow)) {
|
|
1988
|
-
logger.debug("SDK", `
|
|
2558
|
+
logger.debug("SDK", `Duplicate observation discarded (${data.type}, ${dedupWindow}ms): ${data.title}`);
|
|
1989
2559
|
return -1;
|
|
1990
2560
|
}
|
|
1991
2561
|
const filesRead = data.filesRead || (data.type === "file-read" ? data.files : void 0);
|
|
@@ -2013,12 +2583,12 @@ var KiroMemorySDK = class {
|
|
|
2013
2583
|
return observationId;
|
|
2014
2584
|
}
|
|
2015
2585
|
/**
|
|
2016
|
-
*
|
|
2017
|
-
*
|
|
2586
|
+
* Store structured knowledge (constraint, decision, heuristic, rejected).
|
|
2587
|
+
* Uses the `type` field for knowledgeType and `facts` for JSON metadata.
|
|
2018
2588
|
*/
|
|
2019
2589
|
async storeKnowledge(data) {
|
|
2020
2590
|
if (!KNOWLEDGE_TYPES.includes(data.knowledgeType)) {
|
|
2021
|
-
throw new Error(`knowledgeType
|
|
2591
|
+
throw new Error(`Invalid knowledgeType: ${data.knowledgeType}. Allowed values: ${KNOWLEDGE_TYPES.join(", ")}`);
|
|
2022
2592
|
}
|
|
2023
2593
|
this.validateObservationInput({ type: data.knowledgeType, title: data.title, content: data.content });
|
|
2024
2594
|
const metadata = (() => {
|
|
@@ -2050,9 +2620,9 @@ var KiroMemorySDK = class {
|
|
|
2050
2620
|
}
|
|
2051
2621
|
})();
|
|
2052
2622
|
const sessionId = "sdk-" + Date.now();
|
|
2053
|
-
const contentHash = this.generateContentHash(data.
|
|
2623
|
+
const contentHash = this.generateContentHash(data.knowledgeType, data.title);
|
|
2054
2624
|
if (isDuplicateObservation(this.db.db, contentHash)) {
|
|
2055
|
-
logger.debug("SDK", `
|
|
2625
|
+
logger.debug("SDK", `Duplicate knowledge discarded: ${data.title}`);
|
|
2056
2626
|
return -1;
|
|
2057
2627
|
}
|
|
2058
2628
|
const discoveryTokens = Math.ceil(data.content.length / 4);
|
|
@@ -2069,11 +2639,11 @@ var KiroMemorySDK = class {
|
|
|
2069
2639
|
null,
|
|
2070
2640
|
// narrative
|
|
2071
2641
|
JSON.stringify(metadata),
|
|
2072
|
-
// facts =
|
|
2642
|
+
// facts = JSON metadata
|
|
2073
2643
|
data.concepts?.join(", ") || null,
|
|
2074
2644
|
data.files?.join(", ") || null,
|
|
2075
2645
|
null,
|
|
2076
|
-
// filesModified: knowledge
|
|
2646
|
+
// filesModified: knowledge doesn't modify files
|
|
2077
2647
|
0,
|
|
2078
2648
|
// prompt_number
|
|
2079
2649
|
contentHash,
|
|
@@ -2184,8 +2754,8 @@ var KiroMemorySDK = class {
|
|
|
2184
2754
|
return this.project;
|
|
2185
2755
|
}
|
|
2186
2756
|
/**
|
|
2187
|
-
*
|
|
2188
|
-
*
|
|
2757
|
+
* Hybrid search: vector search + keyword FTS5
|
|
2758
|
+
* Requires HybridSearch initialization (embedding service)
|
|
2189
2759
|
*/
|
|
2190
2760
|
async hybridSearch(query, options = {}) {
|
|
2191
2761
|
const hybridSearch2 = getHybridSearch();
|
|
@@ -2195,8 +2765,8 @@ var KiroMemorySDK = class {
|
|
|
2195
2765
|
});
|
|
2196
2766
|
}
|
|
2197
2767
|
/**
|
|
2198
|
-
*
|
|
2199
|
-
*
|
|
2768
|
+
* Semantic-only search (vector search)
|
|
2769
|
+
* Returns results based on cosine similarity with embeddings
|
|
2200
2770
|
*/
|
|
2201
2771
|
async semanticSearch(query, options = {}) {
|
|
2202
2772
|
const embeddingService2 = getEmbeddingService();
|
|
@@ -2231,21 +2801,21 @@ var KiroMemorySDK = class {
|
|
|
2231
2801
|
}));
|
|
2232
2802
|
}
|
|
2233
2803
|
/**
|
|
2234
|
-
*
|
|
2804
|
+
* Generate embeddings for observations that don't have them yet
|
|
2235
2805
|
*/
|
|
2236
2806
|
async backfillEmbeddings(batchSize = 50) {
|
|
2237
2807
|
const vectorSearch2 = getVectorSearch();
|
|
2238
2808
|
return vectorSearch2.backfillEmbeddings(this.db.db, batchSize);
|
|
2239
2809
|
}
|
|
2240
2810
|
/**
|
|
2241
|
-
*
|
|
2811
|
+
* Embedding statistics in the database
|
|
2242
2812
|
*/
|
|
2243
2813
|
getEmbeddingStats() {
|
|
2244
2814
|
const vectorSearch2 = getVectorSearch();
|
|
2245
2815
|
return vectorSearch2.getStats(this.db.db);
|
|
2246
2816
|
}
|
|
2247
2817
|
/**
|
|
2248
|
-
*
|
|
2818
|
+
* Initialize the embedding service (lazy, call before hybridSearch)
|
|
2249
2819
|
*/
|
|
2250
2820
|
async initializeEmbeddings() {
|
|
2251
2821
|
const hybridSearch2 = getHybridSearch();
|
|
@@ -2253,10 +2823,10 @@ var KiroMemorySDK = class {
|
|
|
2253
2823
|
return getEmbeddingService().isAvailable();
|
|
2254
2824
|
}
|
|
2255
2825
|
/**
|
|
2256
|
-
*
|
|
2826
|
+
* Smart context with 4-signal ranking and token budget.
|
|
2257
2827
|
*
|
|
2258
|
-
*
|
|
2259
|
-
*
|
|
2828
|
+
* If query present: uses HybridSearch with SEARCH_WEIGHTS.
|
|
2829
|
+
* If no query: ranking by recency + project match (CONTEXT_WEIGHTS).
|
|
2260
2830
|
*/
|
|
2261
2831
|
async getSmartContext(options = {}) {
|
|
2262
2832
|
const tokenBudget = options.tokenBudget || parseInt(process.env.KIRO_MEMORY_CONTEXT_TOKENS || "0", 10) || 2e3;
|
|
@@ -2330,8 +2900,8 @@ var KiroMemorySDK = class {
|
|
|
2330
2900
|
};
|
|
2331
2901
|
}
|
|
2332
2902
|
/**
|
|
2333
|
-
*
|
|
2334
|
-
*
|
|
2903
|
+
* Detect stale observations (files modified after creation) and mark them in DB.
|
|
2904
|
+
* Returns the number of observations marked as stale.
|
|
2335
2905
|
*/
|
|
2336
2906
|
async detectStaleObservations() {
|
|
2337
2907
|
const staleObs = getStaleObservations(this.db.db, this.project);
|
|
@@ -2342,14 +2912,14 @@ var KiroMemorySDK = class {
|
|
|
2342
2912
|
return staleObs.length;
|
|
2343
2913
|
}
|
|
2344
2914
|
/**
|
|
2345
|
-
*
|
|
2346
|
-
*
|
|
2915
|
+
* Consolidate duplicate observations on the same file and type.
|
|
2916
|
+
* Groups by (project, type, files_modified), keeps the most recent.
|
|
2347
2917
|
*/
|
|
2348
2918
|
async consolidateObservations(options = {}) {
|
|
2349
2919
|
return consolidateObservations(this.db.db, this.project, options);
|
|
2350
2920
|
}
|
|
2351
2921
|
/**
|
|
2352
|
-
*
|
|
2922
|
+
* Decay statistics: total, stale, never accessed, recently accessed.
|
|
2353
2923
|
*/
|
|
2354
2924
|
async getDecayStats() {
|
|
2355
2925
|
const total = this.db.db.query(
|
|
@@ -2368,8 +2938,8 @@ var KiroMemorySDK = class {
|
|
|
2368
2938
|
return { total, stale, neverAccessed, recentlyAccessed };
|
|
2369
2939
|
}
|
|
2370
2940
|
/**
|
|
2371
|
-
*
|
|
2372
|
-
*
|
|
2941
|
+
* Create a structured checkpoint for session resume.
|
|
2942
|
+
* Automatically saves a context_snapshot with the last 10 observations.
|
|
2373
2943
|
*/
|
|
2374
2944
|
async createCheckpoint(sessionId, data) {
|
|
2375
2945
|
const recentObs = getObservationsByProject(this.db.db, this.project, 10);
|
|
@@ -2386,21 +2956,21 @@ var KiroMemorySDK = class {
|
|
|
2386
2956
|
});
|
|
2387
2957
|
}
|
|
2388
2958
|
/**
|
|
2389
|
-
*
|
|
2959
|
+
* Retrieve the latest checkpoint of a specific session.
|
|
2390
2960
|
*/
|
|
2391
2961
|
async getCheckpoint(sessionId) {
|
|
2392
2962
|
return getLatestCheckpoint(this.db.db, sessionId);
|
|
2393
2963
|
}
|
|
2394
2964
|
/**
|
|
2395
|
-
*
|
|
2396
|
-
*
|
|
2965
|
+
* Retrieve the latest checkpoint for the current project.
|
|
2966
|
+
* Useful for automatic resume without specifying session ID.
|
|
2397
2967
|
*/
|
|
2398
2968
|
async getLatestProjectCheckpoint() {
|
|
2399
2969
|
return getLatestCheckpointByProject(this.db.db, this.project);
|
|
2400
2970
|
}
|
|
2401
2971
|
/**
|
|
2402
|
-
*
|
|
2403
|
-
*
|
|
2972
|
+
* Generate an activity report for the current project.
|
|
2973
|
+
* Aggregates observations, sessions, summaries and files for a time period.
|
|
2404
2974
|
*/
|
|
2405
2975
|
async generateReport(options) {
|
|
2406
2976
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -2416,6 +2986,66 @@ var KiroMemorySDK = class {
|
|
|
2416
2986
|
}
|
|
2417
2987
|
return getReportData(this.db.db, this.project, startEpoch, endEpoch);
|
|
2418
2988
|
}
|
|
2989
|
+
/**
|
|
2990
|
+
* Lista osservazioni con keyset pagination.
|
|
2991
|
+
* Restituisce un oggetto { data, next_cursor, has_more }.
|
|
2992
|
+
*
|
|
2993
|
+
* Esempio:
|
|
2994
|
+
* const page1 = await sdk.listObservations({ limit: 50 });
|
|
2995
|
+
* const page2 = await sdk.listObservations({ cursor: page1.next_cursor });
|
|
2996
|
+
*/
|
|
2997
|
+
async listObservations(options = {}) {
|
|
2998
|
+
const limit = Math.min(Math.max(options.limit ?? 50, 1), 200);
|
|
2999
|
+
const project = options.project ?? this.project;
|
|
3000
|
+
let rows;
|
|
3001
|
+
if (options.cursor) {
|
|
3002
|
+
const decoded = decodeCursor(options.cursor);
|
|
3003
|
+
if (!decoded) throw new Error("Cursor non valido");
|
|
3004
|
+
const sql = project ? `SELECT * FROM observations
|
|
3005
|
+
WHERE project = ? AND (created_at_epoch < ? OR (created_at_epoch = ? AND id < ?))
|
|
3006
|
+
ORDER BY created_at_epoch DESC, id DESC
|
|
3007
|
+
LIMIT ?` : `SELECT * FROM observations
|
|
3008
|
+
WHERE (created_at_epoch < ? OR (created_at_epoch = ? AND id < ?))
|
|
3009
|
+
ORDER BY created_at_epoch DESC, id DESC
|
|
3010
|
+
LIMIT ?`;
|
|
3011
|
+
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);
|
|
3012
|
+
} else {
|
|
3013
|
+
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 ?";
|
|
3014
|
+
rows = project ? this.db.db.query(sql).all(project, limit) : this.db.db.query(sql).all(limit);
|
|
3015
|
+
}
|
|
3016
|
+
const next_cursor = rows.length >= limit ? encodeCursor(rows[rows.length - 1].id, rows[rows.length - 1].created_at_epoch) : null;
|
|
3017
|
+
return { data: rows, next_cursor, has_more: next_cursor !== null };
|
|
3018
|
+
}
|
|
3019
|
+
/**
|
|
3020
|
+
* Lista sommari con keyset pagination.
|
|
3021
|
+
* Restituisce un oggetto { data, next_cursor, has_more }.
|
|
3022
|
+
*
|
|
3023
|
+
* Esempio:
|
|
3024
|
+
* const page1 = await sdk.listSummaries({ limit: 20 });
|
|
3025
|
+
* const page2 = await sdk.listSummaries({ cursor: page1.next_cursor });
|
|
3026
|
+
*/
|
|
3027
|
+
async listSummaries(options = {}) {
|
|
3028
|
+
const limit = Math.min(Math.max(options.limit ?? 20, 1), 200);
|
|
3029
|
+
const project = options.project ?? this.project;
|
|
3030
|
+
let rows;
|
|
3031
|
+
if (options.cursor) {
|
|
3032
|
+
const decoded = decodeCursor(options.cursor);
|
|
3033
|
+
if (!decoded) throw new Error("Cursor non valido");
|
|
3034
|
+
const sql = project ? `SELECT * FROM summaries
|
|
3035
|
+
WHERE project = ? AND (created_at_epoch < ? OR (created_at_epoch = ? AND id < ?))
|
|
3036
|
+
ORDER BY created_at_epoch DESC, id DESC
|
|
3037
|
+
LIMIT ?` : `SELECT * FROM summaries
|
|
3038
|
+
WHERE (created_at_epoch < ? OR (created_at_epoch = ? AND id < ?))
|
|
3039
|
+
ORDER BY created_at_epoch DESC, id DESC
|
|
3040
|
+
LIMIT ?`;
|
|
3041
|
+
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);
|
|
3042
|
+
} else {
|
|
3043
|
+
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 ?";
|
|
3044
|
+
rows = project ? this.db.db.query(sql).all(project, limit) : this.db.db.query(sql).all(limit);
|
|
3045
|
+
}
|
|
3046
|
+
const next_cursor = rows.length >= limit ? encodeCursor(rows[rows.length - 1].id, rows[rows.length - 1].created_at_epoch) : null;
|
|
3047
|
+
return { data: rows, next_cursor, has_more: next_cursor !== null };
|
|
3048
|
+
}
|
|
2419
3049
|
/**
|
|
2420
3050
|
* Getter for direct database access (for API routes)
|
|
2421
3051
|
*/
|
|
@@ -2485,7 +3115,7 @@ runHook("agentSpawn", async (input) => {
|
|
|
2485
3115
|
summaries: smartCtx.summaries,
|
|
2486
3116
|
project
|
|
2487
3117
|
});
|
|
2488
|
-
output += `> UI
|
|
3118
|
+
output += `> UI available at http://127.0.0.1:${process.env.KIRO_MEMORY_WORKER_PORT || "3001"}
|
|
2489
3119
|
`;
|
|
2490
3120
|
process.stdout.write(output);
|
|
2491
3121
|
} finally {
|