kiro-memory 1.9.0 → 2.1.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/package.json +3 -3
- package/plugin/dist/cli/contextkit.js +305 -174
- package/plugin/dist/hooks/agentSpawn.js +311 -180
- package/plugin/dist/hooks/kiro-hooks.js +299 -168
- package/plugin/dist/hooks/postToolUse.js +303 -172
- package/plugin/dist/hooks/stop.js +308 -177
- package/plugin/dist/hooks/userPromptSubmit.js +303 -172
- package/plugin/dist/index.js +303 -299
- package/plugin/dist/sdk/index.js +299 -172
- package/plugin/dist/services/search/EmbeddingService.js +88 -23
- package/plugin/dist/services/search/HybridSearch.js +190 -84
- package/plugin/dist/services/search/VectorSearch.js +128 -45
- package/plugin/dist/services/search/index.js +192 -223
- package/plugin/dist/services/sqlite/Database.js +55 -153
- package/plugin/dist/services/sqlite/Observations.js +23 -12
- package/plugin/dist/services/sqlite/Search.js +31 -19
- package/plugin/dist/services/sqlite/index.js +108 -183
- package/plugin/dist/viewer.css +1 -0
- package/plugin/dist/viewer.html +2 -179
- package/plugin/dist/viewer.js +15 -24942
- package/plugin/dist/viewer.js.map +7 -0
- package/plugin/dist/worker-service.js +158 -5551
- package/plugin/dist/worker-service.js.map +7 -0
|
@@ -98,9 +98,25 @@ function consolidateObservations(db, project, options = {}) {
|
|
|
98
98
|
ORDER BY cnt DESC
|
|
99
99
|
`).all(project, minGroupSize);
|
|
100
100
|
if (groups.length === 0) return { merged: 0, removed: 0 };
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
if (options.dryRun) {
|
|
102
|
+
let totalMerged = 0;
|
|
103
|
+
let totalRemoved = 0;
|
|
104
|
+
for (const group of groups) {
|
|
105
|
+
const obsIds = group.ids.split(",").map(Number);
|
|
106
|
+
const placeholders = obsIds.map(() => "?").join(",");
|
|
107
|
+
const count = db.query(
|
|
108
|
+
`SELECT COUNT(*) as cnt FROM observations WHERE id IN (${placeholders})`
|
|
109
|
+
).get(...obsIds)?.cnt || 0;
|
|
110
|
+
if (count >= minGroupSize) {
|
|
111
|
+
totalMerged += 1;
|
|
112
|
+
totalRemoved += count - 1;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return { merged: totalMerged, removed: totalRemoved };
|
|
116
|
+
}
|
|
103
117
|
const runConsolidation = db.transaction(() => {
|
|
118
|
+
let merged = 0;
|
|
119
|
+
let removed = 0;
|
|
104
120
|
for (const group of groups) {
|
|
105
121
|
const obsIds = group.ids.split(",").map(Number);
|
|
106
122
|
const placeholders = obsIds.map(() => "?").join(",");
|
|
@@ -108,11 +124,6 @@ function consolidateObservations(db, project, options = {}) {
|
|
|
108
124
|
`SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC`
|
|
109
125
|
).all(...obsIds);
|
|
110
126
|
if (observations.length < minGroupSize) continue;
|
|
111
|
-
if (options.dryRun) {
|
|
112
|
-
totalMerged += 1;
|
|
113
|
-
totalRemoved += observations.length - 1;
|
|
114
|
-
continue;
|
|
115
|
-
}
|
|
116
127
|
const keeper = observations[0];
|
|
117
128
|
const others = observations.slice(1);
|
|
118
129
|
const uniqueTexts = /* @__PURE__ */ new Set();
|
|
@@ -125,18 +136,18 @@ function consolidateObservations(db, project, options = {}) {
|
|
|
125
136
|
const consolidatedText = Array.from(uniqueTexts).join("\n---\n").substring(0, 1e5);
|
|
126
137
|
db.run(
|
|
127
138
|
"UPDATE observations SET text = ?, title = ? WHERE id = ?",
|
|
128
|
-
[consolidatedText, `[
|
|
139
|
+
[consolidatedText, `[consolidated x${observations.length}] ${keeper.title}`, keeper.id]
|
|
129
140
|
);
|
|
130
141
|
const removeIds = others.map((o) => o.id);
|
|
131
142
|
const removePlaceholders = removeIds.map(() => "?").join(",");
|
|
132
143
|
db.run(`DELETE FROM observations WHERE id IN (${removePlaceholders})`, removeIds);
|
|
133
144
|
db.run(`DELETE FROM observation_embeddings WHERE observation_id IN (${removePlaceholders})`, removeIds);
|
|
134
|
-
|
|
135
|
-
|
|
145
|
+
merged += 1;
|
|
146
|
+
removed += removeIds.length;
|
|
136
147
|
}
|
|
148
|
+
return { merged, removed };
|
|
137
149
|
});
|
|
138
|
-
runConsolidation();
|
|
139
|
-
return { merged: totalMerged, removed: totalRemoved };
|
|
150
|
+
return runConsolidation();
|
|
140
151
|
}
|
|
141
152
|
var init_Observations = __esm({
|
|
142
153
|
"src/services/sqlite/Observations.ts"() {
|
|
@@ -163,7 +174,7 @@ function escapeLikePattern3(input) {
|
|
|
163
174
|
}
|
|
164
175
|
function sanitizeFTS5Query(query) {
|
|
165
176
|
const trimmed = query.length > 1e4 ? query.substring(0, 1e4) : query;
|
|
166
|
-
const terms = trimmed.replace(/[""]/g, "").split(/\s+/).filter((t) => t.length > 0).slice(0, 100).map((t) => `"${t}"`);
|
|
177
|
+
const terms = trimmed.replace(/[""\u0022]/g, "").split(/\s+/).filter((t) => t.length > 0).slice(0, 100).map((t) => `"${t}"`);
|
|
167
178
|
return terms.join(" ");
|
|
168
179
|
}
|
|
169
180
|
function searchObservationsFTS(db, query, filters = {}) {
|
|
@@ -328,26 +339,38 @@ function getTimeline(db, anchorId, depthBefore = 5, depthAfter = 5) {
|
|
|
328
339
|
return [...before, ...self, ...after];
|
|
329
340
|
}
|
|
330
341
|
function getProjectStats(db, project) {
|
|
331
|
-
const
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
342
|
+
const sql = `
|
|
343
|
+
WITH
|
|
344
|
+
obs_stats AS (
|
|
345
|
+
SELECT
|
|
346
|
+
COUNT(*) as count,
|
|
347
|
+
COALESCE(SUM(discovery_tokens), 0) as discovery_tokens,
|
|
348
|
+
COALESCE(SUM(
|
|
349
|
+
CAST((LENGTH(COALESCE(title, '')) + LENGTH(COALESCE(narrative, ''))) / 4 AS INTEGER)
|
|
350
|
+
), 0) as read_tokens
|
|
351
|
+
FROM observations WHERE project = ?
|
|
352
|
+
),
|
|
353
|
+
sum_count AS (SELECT COUNT(*) as count FROM summaries WHERE project = ?),
|
|
354
|
+
ses_count AS (SELECT COUNT(*) as count FROM sessions WHERE project = ?),
|
|
355
|
+
prm_count AS (SELECT COUNT(*) as count FROM prompts WHERE project = ?)
|
|
356
|
+
SELECT
|
|
357
|
+
obs_stats.count as observations,
|
|
358
|
+
obs_stats.discovery_tokens,
|
|
359
|
+
obs_stats.read_tokens,
|
|
360
|
+
sum_count.count as summaries,
|
|
361
|
+
ses_count.count as sessions,
|
|
362
|
+
prm_count.count as prompts
|
|
363
|
+
FROM obs_stats, sum_count, ses_count, prm_count
|
|
364
|
+
`;
|
|
365
|
+
const row = db.query(sql).get(project, project, project, project);
|
|
366
|
+
const discoveryTokens = row?.discovery_tokens || 0;
|
|
367
|
+
const readTokens = row?.read_tokens || 0;
|
|
345
368
|
const savings = Math.max(0, discoveryTokens - readTokens);
|
|
346
369
|
return {
|
|
347
|
-
observations:
|
|
348
|
-
summaries:
|
|
349
|
-
sessions:
|
|
350
|
-
prompts:
|
|
370
|
+
observations: row?.observations || 0,
|
|
371
|
+
summaries: row?.summaries || 0,
|
|
372
|
+
sessions: row?.sessions || 0,
|
|
373
|
+
prompts: row?.prompts || 0,
|
|
351
374
|
tokenEconomics: { discoveryTokens, readTokens, savings }
|
|
352
375
|
};
|
|
353
376
|
}
|
|
@@ -402,14 +425,15 @@ var init_Search = __esm({
|
|
|
402
425
|
import BetterSqlite3 from "better-sqlite3";
|
|
403
426
|
var Database = class {
|
|
404
427
|
_db;
|
|
428
|
+
_stmtCache = /* @__PURE__ */ new Map();
|
|
405
429
|
constructor(path, options) {
|
|
406
430
|
this._db = new BetterSqlite3(path, {
|
|
407
|
-
// better-sqlite3
|
|
431
|
+
// better-sqlite3 creates the file by default ('create' not needed)
|
|
408
432
|
readonly: options?.readwrite === false ? true : false
|
|
409
433
|
});
|
|
410
434
|
}
|
|
411
435
|
/**
|
|
412
|
-
*
|
|
436
|
+
* Execute a SQL query without results
|
|
413
437
|
*/
|
|
414
438
|
run(sql, params) {
|
|
415
439
|
const stmt = this._db.prepare(sql);
|
|
@@ -417,51 +441,53 @@ var Database = class {
|
|
|
417
441
|
return result;
|
|
418
442
|
}
|
|
419
443
|
/**
|
|
420
|
-
*
|
|
444
|
+
* Prepare a query with bun:sqlite-compatible interface.
|
|
445
|
+
* Returns a cached prepared statement for repeated queries.
|
|
421
446
|
*/
|
|
422
447
|
query(sql) {
|
|
423
|
-
|
|
448
|
+
let cached = this._stmtCache.get(sql);
|
|
449
|
+
if (!cached) {
|
|
450
|
+
cached = new BunQueryCompat(this._db, sql);
|
|
451
|
+
this._stmtCache.set(sql, cached);
|
|
452
|
+
}
|
|
453
|
+
return cached;
|
|
424
454
|
}
|
|
425
455
|
/**
|
|
426
|
-
*
|
|
456
|
+
* Create a transaction
|
|
427
457
|
*/
|
|
428
458
|
transaction(fn) {
|
|
429
459
|
return this._db.transaction(fn);
|
|
430
460
|
}
|
|
431
461
|
/**
|
|
432
|
-
*
|
|
462
|
+
* Close the connection
|
|
433
463
|
*/
|
|
434
464
|
close() {
|
|
465
|
+
this._stmtCache.clear();
|
|
435
466
|
this._db.close();
|
|
436
467
|
}
|
|
437
468
|
};
|
|
438
469
|
var BunQueryCompat = class {
|
|
439
|
-
|
|
440
|
-
_sql;
|
|
470
|
+
_stmt;
|
|
441
471
|
constructor(db, sql) {
|
|
442
|
-
this.
|
|
443
|
-
this._sql = sql;
|
|
472
|
+
this._stmt = db.prepare(sql);
|
|
444
473
|
}
|
|
445
474
|
/**
|
|
446
|
-
*
|
|
475
|
+
* Returns all rows
|
|
447
476
|
*/
|
|
448
477
|
all(...params) {
|
|
449
|
-
|
|
450
|
-
return params.length > 0 ? stmt.all(...params) : stmt.all();
|
|
478
|
+
return params.length > 0 ? this._stmt.all(...params) : this._stmt.all();
|
|
451
479
|
}
|
|
452
480
|
/**
|
|
453
|
-
*
|
|
481
|
+
* Returns the first row or null
|
|
454
482
|
*/
|
|
455
483
|
get(...params) {
|
|
456
|
-
|
|
457
|
-
return params.length > 0 ? stmt.get(...params) : stmt.get();
|
|
484
|
+
return params.length > 0 ? this._stmt.get(...params) : this._stmt.get();
|
|
458
485
|
}
|
|
459
486
|
/**
|
|
460
|
-
*
|
|
487
|
+
* Execute without results
|
|
461
488
|
*/
|
|
462
489
|
run(...params) {
|
|
463
|
-
|
|
464
|
-
return params.length > 0 ? stmt.run(...params) : stmt.run();
|
|
490
|
+
return params.length > 0 ? this._stmt.run(...params) : this._stmt.run();
|
|
465
491
|
}
|
|
466
492
|
};
|
|
467
493
|
|
|
@@ -723,40 +749,62 @@ function ensureDir(dirPath) {
|
|
|
723
749
|
var SQLITE_MMAP_SIZE_BYTES = 256 * 1024 * 1024;
|
|
724
750
|
var SQLITE_CACHE_SIZE_PAGES = 1e4;
|
|
725
751
|
var KiroMemoryDatabase = class {
|
|
726
|
-
|
|
752
|
+
_db;
|
|
727
753
|
/**
|
|
728
|
-
*
|
|
729
|
-
*
|
|
754
|
+
* Readonly accessor for the underlying Database instance.
|
|
755
|
+
* Prefer using query() and run() proxy methods directly.
|
|
756
|
+
*/
|
|
757
|
+
get db() {
|
|
758
|
+
return this._db;
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* @param dbPath - Path to the SQLite file (default: DB_PATH)
|
|
762
|
+
* @param skipMigrations - If true, skip the migration runner (for high-frequency hooks)
|
|
730
763
|
*/
|
|
731
764
|
constructor(dbPath = DB_PATH, skipMigrations = false) {
|
|
732
765
|
if (dbPath !== ":memory:") {
|
|
733
766
|
ensureDir(DATA_DIR);
|
|
734
767
|
}
|
|
735
|
-
this.
|
|
736
|
-
this.
|
|
737
|
-
this.
|
|
738
|
-
this.
|
|
739
|
-
this.
|
|
740
|
-
this.
|
|
741
|
-
this.
|
|
768
|
+
this._db = new Database(dbPath, { create: true, readwrite: true });
|
|
769
|
+
this._db.run("PRAGMA journal_mode = WAL");
|
|
770
|
+
this._db.run("PRAGMA busy_timeout = 5000");
|
|
771
|
+
this._db.run("PRAGMA synchronous = NORMAL");
|
|
772
|
+
this._db.run("PRAGMA foreign_keys = ON");
|
|
773
|
+
this._db.run("PRAGMA temp_store = memory");
|
|
774
|
+
this._db.run(`PRAGMA mmap_size = ${SQLITE_MMAP_SIZE_BYTES}`);
|
|
775
|
+
this._db.run(`PRAGMA cache_size = ${SQLITE_CACHE_SIZE_PAGES}`);
|
|
742
776
|
if (!skipMigrations) {
|
|
743
|
-
const migrationRunner = new MigrationRunner(this.
|
|
777
|
+
const migrationRunner = new MigrationRunner(this._db);
|
|
744
778
|
migrationRunner.runAllMigrations();
|
|
745
779
|
}
|
|
746
780
|
}
|
|
747
781
|
/**
|
|
748
|
-
*
|
|
749
|
-
*
|
|
782
|
+
* Prepare a query (delegates to underlying Database).
|
|
783
|
+
* Proxy method to avoid ctx.db.db.query() double access.
|
|
784
|
+
*/
|
|
785
|
+
query(sql) {
|
|
786
|
+
return this._db.query(sql);
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Execute a SQL statement without results (delegates to underlying Database).
|
|
790
|
+
* Proxy method to avoid ctx.db.db.run() double access.
|
|
791
|
+
*/
|
|
792
|
+
run(sql, params) {
|
|
793
|
+
return this._db.run(sql, params);
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* Executes a function within an atomic transaction.
|
|
797
|
+
* If fn() throws an error, the transaction is automatically rolled back.
|
|
750
798
|
*/
|
|
751
799
|
withTransaction(fn) {
|
|
752
|
-
const transaction = this.
|
|
753
|
-
return transaction(this.
|
|
800
|
+
const transaction = this._db.transaction(fn);
|
|
801
|
+
return transaction(this._db);
|
|
754
802
|
}
|
|
755
803
|
/**
|
|
756
804
|
* Close the database connection
|
|
757
805
|
*/
|
|
758
806
|
close() {
|
|
759
|
-
this.
|
|
807
|
+
this._db.close();
|
|
760
808
|
}
|
|
761
809
|
};
|
|
762
810
|
var MigrationRunner = class {
|
|
@@ -1251,8 +1299,8 @@ var EmbeddingService = class {
|
|
|
1251
1299
|
initialized = false;
|
|
1252
1300
|
initializing = null;
|
|
1253
1301
|
/**
|
|
1254
|
-
*
|
|
1255
|
-
*
|
|
1302
|
+
* Initialize the embedding service.
|
|
1303
|
+
* Tries fastembed, then @huggingface/transformers, then fallback to null.
|
|
1256
1304
|
*/
|
|
1257
1305
|
async initialize() {
|
|
1258
1306
|
if (this.initialized) return this.provider !== null;
|
|
@@ -1273,11 +1321,11 @@ var EmbeddingService = class {
|
|
|
1273
1321
|
});
|
|
1274
1322
|
this.provider = "fastembed";
|
|
1275
1323
|
this.initialized = true;
|
|
1276
|
-
logger.info("EMBEDDING", "
|
|
1324
|
+
logger.info("EMBEDDING", "Initialized with fastembed (BGE-small-en-v1.5)");
|
|
1277
1325
|
return true;
|
|
1278
1326
|
}
|
|
1279
1327
|
} catch (error) {
|
|
1280
|
-
logger.debug("EMBEDDING", `fastembed
|
|
1328
|
+
logger.debug("EMBEDDING", `fastembed not available: ${error}`);
|
|
1281
1329
|
}
|
|
1282
1330
|
try {
|
|
1283
1331
|
const transformers = await import("@huggingface/transformers");
|
|
@@ -1288,20 +1336,20 @@ var EmbeddingService = class {
|
|
|
1288
1336
|
});
|
|
1289
1337
|
this.provider = "transformers";
|
|
1290
1338
|
this.initialized = true;
|
|
1291
|
-
logger.info("EMBEDDING", "
|
|
1339
|
+
logger.info("EMBEDDING", "Initialized with @huggingface/transformers (all-MiniLM-L6-v2)");
|
|
1292
1340
|
return true;
|
|
1293
1341
|
}
|
|
1294
1342
|
} catch (error) {
|
|
1295
|
-
logger.debug("EMBEDDING", `@huggingface/transformers
|
|
1343
|
+
logger.debug("EMBEDDING", `@huggingface/transformers not available: ${error}`);
|
|
1296
1344
|
}
|
|
1297
1345
|
this.provider = null;
|
|
1298
1346
|
this.initialized = true;
|
|
1299
|
-
logger.warn("EMBEDDING", "
|
|
1347
|
+
logger.warn("EMBEDDING", "No embedding provider available, semantic search disabled");
|
|
1300
1348
|
return false;
|
|
1301
1349
|
}
|
|
1302
1350
|
/**
|
|
1303
|
-
*
|
|
1304
|
-
*
|
|
1351
|
+
* Generate embedding for a single text.
|
|
1352
|
+
* Returns Float32Array with 384 dimensions, or null if not available.
|
|
1305
1353
|
*/
|
|
1306
1354
|
async embed(text) {
|
|
1307
1355
|
if (!this.initialized) await this.initialize();
|
|
@@ -1314,46 +1362,111 @@ var EmbeddingService = class {
|
|
|
1314
1362
|
return await this._embedTransformers(truncated);
|
|
1315
1363
|
}
|
|
1316
1364
|
} catch (error) {
|
|
1317
|
-
logger.error("EMBEDDING", `
|
|
1365
|
+
logger.error("EMBEDDING", `Error generating embedding: ${error}`);
|
|
1318
1366
|
}
|
|
1319
1367
|
return null;
|
|
1320
1368
|
}
|
|
1321
1369
|
/**
|
|
1322
|
-
*
|
|
1370
|
+
* Generate embeddings in batch.
|
|
1371
|
+
* Uses native batch support when available (fastembed, transformers),
|
|
1372
|
+
* falls back to serial processing on batch failure.
|
|
1323
1373
|
*/
|
|
1324
1374
|
async embedBatch(texts) {
|
|
1325
1375
|
if (!this.initialized) await this.initialize();
|
|
1326
1376
|
if (!this.provider || !this.model) return texts.map(() => null);
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
}
|
|
1333
|
-
|
|
1377
|
+
if (texts.length === 0) return [];
|
|
1378
|
+
const truncated = texts.map((t) => t.substring(0, 2e3));
|
|
1379
|
+
try {
|
|
1380
|
+
if (this.provider === "fastembed") {
|
|
1381
|
+
return await this._embedBatchFastembed(truncated);
|
|
1382
|
+
} else if (this.provider === "transformers") {
|
|
1383
|
+
return await this._embedBatchTransformers(truncated);
|
|
1334
1384
|
}
|
|
1385
|
+
} catch (error) {
|
|
1386
|
+
logger.warn("EMBEDDING", `Batch embedding failed, falling back to serial: ${error}`);
|
|
1335
1387
|
}
|
|
1336
|
-
return
|
|
1388
|
+
return this._embedBatchSerial(truncated);
|
|
1337
1389
|
}
|
|
1338
1390
|
/**
|
|
1339
|
-
*
|
|
1391
|
+
* Check if the service is available.
|
|
1340
1392
|
*/
|
|
1341
1393
|
isAvailable() {
|
|
1342
1394
|
return this.initialized && this.provider !== null;
|
|
1343
1395
|
}
|
|
1344
1396
|
/**
|
|
1345
|
-
*
|
|
1397
|
+
* Name of the active provider.
|
|
1346
1398
|
*/
|
|
1347
1399
|
getProvider() {
|
|
1348
1400
|
return this.provider;
|
|
1349
1401
|
}
|
|
1350
1402
|
/**
|
|
1351
|
-
*
|
|
1403
|
+
* Embedding vector dimensions.
|
|
1352
1404
|
*/
|
|
1353
1405
|
getDimensions() {
|
|
1354
1406
|
return 384;
|
|
1355
1407
|
}
|
|
1356
|
-
// ---
|
|
1408
|
+
// --- Batch implementations ---
|
|
1409
|
+
/**
|
|
1410
|
+
* Native batch embedding with fastembed.
|
|
1411
|
+
* FlagEmbedding.embed() accepts string[] and returns an async iterable of batches.
|
|
1412
|
+
*/
|
|
1413
|
+
async _embedBatchFastembed(texts) {
|
|
1414
|
+
const results = [];
|
|
1415
|
+
const embeddings = this.model.embed(texts, texts.length);
|
|
1416
|
+
for await (const batch of embeddings) {
|
|
1417
|
+
if (batch) {
|
|
1418
|
+
for (const vec of batch) {
|
|
1419
|
+
results.push(vec instanceof Float32Array ? vec : new Float32Array(vec));
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
while (results.length < texts.length) {
|
|
1424
|
+
results.push(null);
|
|
1425
|
+
}
|
|
1426
|
+
return results;
|
|
1427
|
+
}
|
|
1428
|
+
/**
|
|
1429
|
+
* Batch embedding with @huggingface/transformers pipeline.
|
|
1430
|
+
* The pipeline accepts string[] and returns a Tensor with shape [N, dims].
|
|
1431
|
+
*/
|
|
1432
|
+
async _embedBatchTransformers(texts) {
|
|
1433
|
+
const output = await this.model(texts, {
|
|
1434
|
+
pooling: "mean",
|
|
1435
|
+
normalize: true
|
|
1436
|
+
});
|
|
1437
|
+
if (!output?.data) {
|
|
1438
|
+
return texts.map(() => null);
|
|
1439
|
+
}
|
|
1440
|
+
const dims = this.getDimensions();
|
|
1441
|
+
const data = output.data instanceof Float32Array ? output.data : new Float32Array(output.data);
|
|
1442
|
+
const results = [];
|
|
1443
|
+
for (let i = 0; i < texts.length; i++) {
|
|
1444
|
+
const offset = i * dims;
|
|
1445
|
+
if (offset + dims <= data.length) {
|
|
1446
|
+
results.push(data.slice(offset, offset + dims));
|
|
1447
|
+
} else {
|
|
1448
|
+
results.push(null);
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
return results;
|
|
1452
|
+
}
|
|
1453
|
+
/**
|
|
1454
|
+
* Serial fallback: embed texts one at a time.
|
|
1455
|
+
* Used when native batch fails.
|
|
1456
|
+
*/
|
|
1457
|
+
async _embedBatchSerial(texts) {
|
|
1458
|
+
const results = [];
|
|
1459
|
+
for (const text of texts) {
|
|
1460
|
+
try {
|
|
1461
|
+
const embedding = await this.embed(text);
|
|
1462
|
+
results.push(embedding);
|
|
1463
|
+
} catch {
|
|
1464
|
+
results.push(null);
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
return results;
|
|
1468
|
+
}
|
|
1469
|
+
// --- Single-text provider implementations ---
|
|
1357
1470
|
async _embedFastembed(text) {
|
|
1358
1471
|
const embeddings = this.model.embed([text], 1);
|
|
1359
1472
|
for await (const batch of embeddings) {
|
|
@@ -1384,17 +1497,21 @@ function getEmbeddingService() {
|
|
|
1384
1497
|
}
|
|
1385
1498
|
|
|
1386
1499
|
// src/services/search/VectorSearch.ts
|
|
1500
|
+
var DEFAULT_MAX_CANDIDATES = 2e3;
|
|
1387
1501
|
function cosineSimilarity(a, b) {
|
|
1388
|
-
|
|
1502
|
+
const len = a.length;
|
|
1503
|
+
if (len !== b.length) return 0;
|
|
1389
1504
|
let dotProduct = 0;
|
|
1390
1505
|
let normA = 0;
|
|
1391
1506
|
let normB = 0;
|
|
1392
|
-
for (let i = 0; i <
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1507
|
+
for (let i = 0; i < len; i++) {
|
|
1508
|
+
const ai = a[i];
|
|
1509
|
+
const bi = b[i];
|
|
1510
|
+
dotProduct += ai * bi;
|
|
1511
|
+
normA += ai * ai;
|
|
1512
|
+
normB += bi * bi;
|
|
1513
|
+
}
|
|
1514
|
+
const denominator = Math.sqrt(normA * normB);
|
|
1398
1515
|
if (denominator === 0) return 0;
|
|
1399
1516
|
return dotProduct / denominator;
|
|
1400
1517
|
}
|
|
@@ -1407,23 +1524,36 @@ function bufferToFloat32(buf) {
|
|
|
1407
1524
|
}
|
|
1408
1525
|
var VectorSearch = class {
|
|
1409
1526
|
/**
|
|
1410
|
-
*
|
|
1527
|
+
* Semantic search with SQL pre-filtering for scalability.
|
|
1528
|
+
*
|
|
1529
|
+
* 2-phase strategy:
|
|
1530
|
+
* 1. SQL pre-filters by project + sorts by recency (loads max N candidates)
|
|
1531
|
+
* 2. JS computes cosine similarity only on filtered candidates
|
|
1532
|
+
*
|
|
1533
|
+
* With 50k observations and maxCandidates=2000, loads only ~4% of data.
|
|
1411
1534
|
*/
|
|
1412
1535
|
async search(db, queryEmbedding, options = {}) {
|
|
1413
1536
|
const limit = options.limit || 10;
|
|
1414
1537
|
const threshold = options.threshold || 0.3;
|
|
1538
|
+
const maxCandidates = options.maxCandidates || DEFAULT_MAX_CANDIDATES;
|
|
1415
1539
|
try {
|
|
1416
|
-
|
|
1540
|
+
const conditions = [];
|
|
1541
|
+
const params = [];
|
|
1542
|
+
if (options.project) {
|
|
1543
|
+
conditions.push("o.project = ?");
|
|
1544
|
+
params.push(options.project);
|
|
1545
|
+
}
|
|
1546
|
+
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
1547
|
+
const sql = `
|
|
1417
1548
|
SELECT e.observation_id, e.embedding,
|
|
1418
1549
|
o.title, o.text, o.type, o.project, o.created_at, o.created_at_epoch
|
|
1419
1550
|
FROM observation_embeddings e
|
|
1420
1551
|
JOIN observations o ON o.id = e.observation_id
|
|
1552
|
+
${whereClause}
|
|
1553
|
+
ORDER BY o.created_at_epoch DESC
|
|
1554
|
+
LIMIT ?
|
|
1421
1555
|
`;
|
|
1422
|
-
|
|
1423
|
-
if (options.project) {
|
|
1424
|
-
sql += " WHERE o.project = ?";
|
|
1425
|
-
params.push(options.project);
|
|
1426
|
-
}
|
|
1556
|
+
params.push(maxCandidates);
|
|
1427
1557
|
const rows = db.query(sql).all(...params);
|
|
1428
1558
|
const scored = [];
|
|
1429
1559
|
for (const row of rows) {
|
|
@@ -1444,14 +1574,15 @@ var VectorSearch = class {
|
|
|
1444
1574
|
}
|
|
1445
1575
|
}
|
|
1446
1576
|
scored.sort((a, b) => b.similarity - a.similarity);
|
|
1577
|
+
logger.debug("VECTOR", `Search: ${rows.length} candidates \u2192 ${scored.length} above threshold \u2192 ${Math.min(scored.length, limit)} results`);
|
|
1447
1578
|
return scored.slice(0, limit);
|
|
1448
1579
|
} catch (error) {
|
|
1449
|
-
logger.error("VECTOR", `
|
|
1580
|
+
logger.error("VECTOR", `Vector search error: ${error}`);
|
|
1450
1581
|
return [];
|
|
1451
1582
|
}
|
|
1452
1583
|
}
|
|
1453
1584
|
/**
|
|
1454
|
-
*
|
|
1585
|
+
* Store embedding for an observation.
|
|
1455
1586
|
*/
|
|
1456
1587
|
async storeEmbedding(db, observationId, embedding, model) {
|
|
1457
1588
|
try {
|
|
@@ -1467,18 +1598,18 @@ var VectorSearch = class {
|
|
|
1467
1598
|
embedding.length,
|
|
1468
1599
|
(/* @__PURE__ */ new Date()).toISOString()
|
|
1469
1600
|
);
|
|
1470
|
-
logger.debug("VECTOR", `Embedding
|
|
1601
|
+
logger.debug("VECTOR", `Embedding saved for observation ${observationId}`);
|
|
1471
1602
|
} catch (error) {
|
|
1472
|
-
logger.error("VECTOR", `
|
|
1603
|
+
logger.error("VECTOR", `Error saving embedding: ${error}`);
|
|
1473
1604
|
}
|
|
1474
1605
|
}
|
|
1475
1606
|
/**
|
|
1476
|
-
*
|
|
1607
|
+
* Generate embeddings for observations that don't have them yet.
|
|
1477
1608
|
*/
|
|
1478
1609
|
async backfillEmbeddings(db, batchSize = 50) {
|
|
1479
1610
|
const embeddingService2 = getEmbeddingService();
|
|
1480
1611
|
if (!await embeddingService2.initialize()) {
|
|
1481
|
-
logger.warn("VECTOR", "Embedding service
|
|
1612
|
+
logger.warn("VECTOR", "Embedding service not available, backfill skipped");
|
|
1482
1613
|
return 0;
|
|
1483
1614
|
}
|
|
1484
1615
|
const rows = db.query(`
|
|
@@ -1504,11 +1635,11 @@ var VectorSearch = class {
|
|
|
1504
1635
|
count++;
|
|
1505
1636
|
}
|
|
1506
1637
|
}
|
|
1507
|
-
logger.info("VECTOR", `Backfill
|
|
1638
|
+
logger.info("VECTOR", `Backfill completed: ${count}/${rows.length} embeddings generated`);
|
|
1508
1639
|
return count;
|
|
1509
1640
|
}
|
|
1510
1641
|
/**
|
|
1511
|
-
*
|
|
1642
|
+
* Embedding statistics.
|
|
1512
1643
|
*/
|
|
1513
1644
|
getStats(db) {
|
|
1514
1645
|
try {
|
|
@@ -1581,21 +1712,21 @@ function knowledgeTypeBoost(type) {
|
|
|
1581
1712
|
var HybridSearch = class {
|
|
1582
1713
|
embeddingInitialized = false;
|
|
1583
1714
|
/**
|
|
1584
|
-
*
|
|
1715
|
+
* Initialize the embedding service (lazy, non-blocking)
|
|
1585
1716
|
*/
|
|
1586
1717
|
async initialize() {
|
|
1587
1718
|
try {
|
|
1588
1719
|
const embeddingService2 = getEmbeddingService();
|
|
1589
1720
|
await embeddingService2.initialize();
|
|
1590
1721
|
this.embeddingInitialized = embeddingService2.isAvailable();
|
|
1591
|
-
logger.info("SEARCH", `HybridSearch
|
|
1722
|
+
logger.info("SEARCH", `HybridSearch initialized (embedding: ${this.embeddingInitialized ? "active" : "disabled"})`);
|
|
1592
1723
|
} catch (error) {
|
|
1593
|
-
logger.warn("SEARCH", "
|
|
1724
|
+
logger.warn("SEARCH", "Embedding initialization failed, using only FTS5", {}, error);
|
|
1594
1725
|
this.embeddingInitialized = false;
|
|
1595
1726
|
}
|
|
1596
1727
|
}
|
|
1597
1728
|
/**
|
|
1598
|
-
*
|
|
1729
|
+
* Hybrid search with 4-signal scoring
|
|
1599
1730
|
*/
|
|
1600
1731
|
async search(db, query, options = {}) {
|
|
1601
1732
|
const limit = options.limit || 10;
|
|
@@ -1611,7 +1742,7 @@ var HybridSearch = class {
|
|
|
1611
1742
|
const vectorResults = await vectorSearch2.search(db, queryEmbedding, {
|
|
1612
1743
|
project: options.project,
|
|
1613
1744
|
limit: limit * 2,
|
|
1614
|
-
//
|
|
1745
|
+
// Fetch more results for ranking
|
|
1615
1746
|
threshold: 0.3
|
|
1616
1747
|
});
|
|
1617
1748
|
for (const hit of vectorResults) {
|
|
@@ -1628,10 +1759,10 @@ var HybridSearch = class {
|
|
|
1628
1759
|
source: "vector"
|
|
1629
1760
|
});
|
|
1630
1761
|
}
|
|
1631
|
-
logger.debug("SEARCH", `Vector search: ${vectorResults.length}
|
|
1762
|
+
logger.debug("SEARCH", `Vector search: ${vectorResults.length} results`);
|
|
1632
1763
|
}
|
|
1633
1764
|
} catch (error) {
|
|
1634
|
-
logger.warn("SEARCH", "
|
|
1765
|
+
logger.warn("SEARCH", "Vector search failed, using only keyword", {}, error);
|
|
1635
1766
|
}
|
|
1636
1767
|
}
|
|
1637
1768
|
try {
|
|
@@ -1661,9 +1792,9 @@ var HybridSearch = class {
|
|
|
1661
1792
|
});
|
|
1662
1793
|
}
|
|
1663
1794
|
}
|
|
1664
|
-
logger.debug("SEARCH", `Keyword search: ${keywordResults.length}
|
|
1795
|
+
logger.debug("SEARCH", `Keyword search: ${keywordResults.length} results`);
|
|
1665
1796
|
} catch (error) {
|
|
1666
|
-
logger.error("SEARCH", "
|
|
1797
|
+
logger.error("SEARCH", "Keyword search failed", {}, error);
|
|
1667
1798
|
}
|
|
1668
1799
|
if (rawItems.size === 0) return [];
|
|
1669
1800
|
const allFTS5Ranks = Array.from(rawItems.values()).filter((item) => item.fts5Rank !== null).map((item) => item.fts5Rank);
|
|
@@ -1751,33 +1882,33 @@ var KiroMemorySDK = class {
|
|
|
1751
1882
|
};
|
|
1752
1883
|
}
|
|
1753
1884
|
/**
|
|
1754
|
-
*
|
|
1885
|
+
* Validate input for storeObservation
|
|
1755
1886
|
*/
|
|
1756
1887
|
validateObservationInput(data) {
|
|
1757
1888
|
if (!data.type || typeof data.type !== "string" || data.type.length > 100) {
|
|
1758
|
-
throw new Error("type
|
|
1889
|
+
throw new Error("type is required (string, max 100 chars)");
|
|
1759
1890
|
}
|
|
1760
1891
|
if (!data.title || typeof data.title !== "string" || data.title.length > 500) {
|
|
1761
|
-
throw new Error("title
|
|
1892
|
+
throw new Error("title is required (string, max 500 chars)");
|
|
1762
1893
|
}
|
|
1763
1894
|
if (!data.content || typeof data.content !== "string" || data.content.length > 1e5) {
|
|
1764
|
-
throw new Error("content
|
|
1895
|
+
throw new Error("content is required (string, max 100KB)");
|
|
1765
1896
|
}
|
|
1766
1897
|
}
|
|
1767
1898
|
/**
|
|
1768
|
-
*
|
|
1899
|
+
* Validate input for storeSummary
|
|
1769
1900
|
*/
|
|
1770
1901
|
validateSummaryInput(data) {
|
|
1771
1902
|
const MAX = 5e4;
|
|
1772
1903
|
for (const [key, val] of Object.entries(data)) {
|
|
1773
1904
|
if (val !== void 0 && val !== null) {
|
|
1774
|
-
if (typeof val !== "string") throw new Error(`${key}
|
|
1775
|
-
if (val.length > MAX) throw new Error(`${key}
|
|
1905
|
+
if (typeof val !== "string") throw new Error(`${key} must be a string`);
|
|
1906
|
+
if (val.length > MAX) throw new Error(`${key} too large (max 50KB)`);
|
|
1776
1907
|
}
|
|
1777
1908
|
}
|
|
1778
1909
|
}
|
|
1779
1910
|
/**
|
|
1780
|
-
*
|
|
1911
|
+
* Generate and store embedding for an observation (fire-and-forget, non-blocking)
|
|
1781
1912
|
*/
|
|
1782
1913
|
async generateEmbeddingAsync(observationId, title, content, concepts) {
|
|
1783
1914
|
try {
|
|
@@ -1797,39 +1928,39 @@ var KiroMemorySDK = class {
|
|
|
1797
1928
|
);
|
|
1798
1929
|
}
|
|
1799
1930
|
} catch (error) {
|
|
1800
|
-
logger.debug("SDK", `Embedding generation
|
|
1931
|
+
logger.debug("SDK", `Embedding generation failed for obs ${observationId}: ${error}`);
|
|
1801
1932
|
}
|
|
1802
1933
|
}
|
|
1803
1934
|
/**
|
|
1804
|
-
*
|
|
1805
|
-
*
|
|
1806
|
-
*
|
|
1935
|
+
* Generate SHA256 content hash for content-based deduplication.
|
|
1936
|
+
* Uses (project + type + title + narrative) as semantic identity tuple.
|
|
1937
|
+
* Does NOT include sessionId since it's unique per invocation.
|
|
1807
1938
|
*/
|
|
1808
1939
|
generateContentHash(type, title, narrative) {
|
|
1809
1940
|
const payload = `${this.project}|${type}|${title}|${narrative || ""}`;
|
|
1810
1941
|
return createHash("sha256").update(payload).digest("hex");
|
|
1811
1942
|
}
|
|
1812
1943
|
/**
|
|
1813
|
-
*
|
|
1814
|
-
*
|
|
1944
|
+
* Deduplication windows per type (ms).
|
|
1945
|
+
* Types with many repetitions have wider windows.
|
|
1815
1946
|
*/
|
|
1816
1947
|
getDeduplicationWindow(type) {
|
|
1817
1948
|
switch (type) {
|
|
1818
1949
|
case "file-read":
|
|
1819
1950
|
return 6e4;
|
|
1820
|
-
// 60s —
|
|
1951
|
+
// 60s — frequent reads on the same files
|
|
1821
1952
|
case "file-write":
|
|
1822
1953
|
return 1e4;
|
|
1823
|
-
// 10s —
|
|
1954
|
+
// 10s — rapid consecutive writes
|
|
1824
1955
|
case "command":
|
|
1825
1956
|
return 3e4;
|
|
1826
1957
|
// 30s — standard
|
|
1827
1958
|
case "research":
|
|
1828
1959
|
return 12e4;
|
|
1829
|
-
// 120s — web search
|
|
1960
|
+
// 120s — repeated web search and fetch
|
|
1830
1961
|
case "delegation":
|
|
1831
1962
|
return 6e4;
|
|
1832
|
-
// 60s —
|
|
1963
|
+
// 60s — rapid delegations
|
|
1833
1964
|
default:
|
|
1834
1965
|
return 3e4;
|
|
1835
1966
|
}
|
|
@@ -1843,7 +1974,7 @@ var KiroMemorySDK = class {
|
|
|
1843
1974
|
const contentHash = this.generateContentHash(data.type, data.title, data.narrative);
|
|
1844
1975
|
const dedupWindow = this.getDeduplicationWindow(data.type);
|
|
1845
1976
|
if (isDuplicateObservation(this.db.db, contentHash, dedupWindow)) {
|
|
1846
|
-
logger.debug("SDK", `
|
|
1977
|
+
logger.debug("SDK", `Duplicate observation discarded (${data.type}, ${dedupWindow}ms): ${data.title}`);
|
|
1847
1978
|
return -1;
|
|
1848
1979
|
}
|
|
1849
1980
|
const filesRead = data.filesRead || (data.type === "file-read" ? data.files : void 0);
|
|
@@ -1871,12 +2002,12 @@ var KiroMemorySDK = class {
|
|
|
1871
2002
|
return observationId;
|
|
1872
2003
|
}
|
|
1873
2004
|
/**
|
|
1874
|
-
*
|
|
1875
|
-
*
|
|
2005
|
+
* Store structured knowledge (constraint, decision, heuristic, rejected).
|
|
2006
|
+
* Uses the `type` field for knowledgeType and `facts` for JSON metadata.
|
|
1876
2007
|
*/
|
|
1877
2008
|
async storeKnowledge(data) {
|
|
1878
2009
|
if (!KNOWLEDGE_TYPES.includes(data.knowledgeType)) {
|
|
1879
|
-
throw new Error(`knowledgeType
|
|
2010
|
+
throw new Error(`Invalid knowledgeType: ${data.knowledgeType}. Allowed values: ${KNOWLEDGE_TYPES.join(", ")}`);
|
|
1880
2011
|
}
|
|
1881
2012
|
this.validateObservationInput({ type: data.knowledgeType, title: data.title, content: data.content });
|
|
1882
2013
|
const metadata = (() => {
|
|
@@ -1908,9 +2039,9 @@ var KiroMemorySDK = class {
|
|
|
1908
2039
|
}
|
|
1909
2040
|
})();
|
|
1910
2041
|
const sessionId = "sdk-" + Date.now();
|
|
1911
|
-
const contentHash = this.generateContentHash(data.
|
|
2042
|
+
const contentHash = this.generateContentHash(data.knowledgeType, data.title);
|
|
1912
2043
|
if (isDuplicateObservation(this.db.db, contentHash)) {
|
|
1913
|
-
logger.debug("SDK", `
|
|
2044
|
+
logger.debug("SDK", `Duplicate knowledge discarded: ${data.title}`);
|
|
1914
2045
|
return -1;
|
|
1915
2046
|
}
|
|
1916
2047
|
const discoveryTokens = Math.ceil(data.content.length / 4);
|
|
@@ -1927,11 +2058,11 @@ var KiroMemorySDK = class {
|
|
|
1927
2058
|
null,
|
|
1928
2059
|
// narrative
|
|
1929
2060
|
JSON.stringify(metadata),
|
|
1930
|
-
// facts =
|
|
2061
|
+
// facts = JSON metadata
|
|
1931
2062
|
data.concepts?.join(", ") || null,
|
|
1932
2063
|
data.files?.join(", ") || null,
|
|
1933
2064
|
null,
|
|
1934
|
-
// filesModified: knowledge
|
|
2065
|
+
// filesModified: knowledge doesn't modify files
|
|
1935
2066
|
0,
|
|
1936
2067
|
// prompt_number
|
|
1937
2068
|
contentHash,
|
|
@@ -2042,8 +2173,8 @@ var KiroMemorySDK = class {
|
|
|
2042
2173
|
return this.project;
|
|
2043
2174
|
}
|
|
2044
2175
|
/**
|
|
2045
|
-
*
|
|
2046
|
-
*
|
|
2176
|
+
* Hybrid search: vector search + keyword FTS5
|
|
2177
|
+
* Requires HybridSearch initialization (embedding service)
|
|
2047
2178
|
*/
|
|
2048
2179
|
async hybridSearch(query, options = {}) {
|
|
2049
2180
|
const hybridSearch2 = getHybridSearch();
|
|
@@ -2053,8 +2184,8 @@ var KiroMemorySDK = class {
|
|
|
2053
2184
|
});
|
|
2054
2185
|
}
|
|
2055
2186
|
/**
|
|
2056
|
-
*
|
|
2057
|
-
*
|
|
2187
|
+
* Semantic-only search (vector search)
|
|
2188
|
+
* Returns results based on cosine similarity with embeddings
|
|
2058
2189
|
*/
|
|
2059
2190
|
async semanticSearch(query, options = {}) {
|
|
2060
2191
|
const embeddingService2 = getEmbeddingService();
|
|
@@ -2089,21 +2220,21 @@ var KiroMemorySDK = class {
|
|
|
2089
2220
|
}));
|
|
2090
2221
|
}
|
|
2091
2222
|
/**
|
|
2092
|
-
*
|
|
2223
|
+
* Generate embeddings for observations that don't have them yet
|
|
2093
2224
|
*/
|
|
2094
2225
|
async backfillEmbeddings(batchSize = 50) {
|
|
2095
2226
|
const vectorSearch2 = getVectorSearch();
|
|
2096
2227
|
return vectorSearch2.backfillEmbeddings(this.db.db, batchSize);
|
|
2097
2228
|
}
|
|
2098
2229
|
/**
|
|
2099
|
-
*
|
|
2230
|
+
* Embedding statistics in the database
|
|
2100
2231
|
*/
|
|
2101
2232
|
getEmbeddingStats() {
|
|
2102
2233
|
const vectorSearch2 = getVectorSearch();
|
|
2103
2234
|
return vectorSearch2.getStats(this.db.db);
|
|
2104
2235
|
}
|
|
2105
2236
|
/**
|
|
2106
|
-
*
|
|
2237
|
+
* Initialize the embedding service (lazy, call before hybridSearch)
|
|
2107
2238
|
*/
|
|
2108
2239
|
async initializeEmbeddings() {
|
|
2109
2240
|
const hybridSearch2 = getHybridSearch();
|
|
@@ -2111,10 +2242,10 @@ var KiroMemorySDK = class {
|
|
|
2111
2242
|
return getEmbeddingService().isAvailable();
|
|
2112
2243
|
}
|
|
2113
2244
|
/**
|
|
2114
|
-
*
|
|
2245
|
+
* Smart context with 4-signal ranking and token budget.
|
|
2115
2246
|
*
|
|
2116
|
-
*
|
|
2117
|
-
*
|
|
2247
|
+
* If query present: uses HybridSearch with SEARCH_WEIGHTS.
|
|
2248
|
+
* If no query: ranking by recency + project match (CONTEXT_WEIGHTS).
|
|
2118
2249
|
*/
|
|
2119
2250
|
async getSmartContext(options = {}) {
|
|
2120
2251
|
const tokenBudget = options.tokenBudget || parseInt(process.env.KIRO_MEMORY_CONTEXT_TOKENS || "0", 10) || 2e3;
|
|
@@ -2188,8 +2319,8 @@ var KiroMemorySDK = class {
|
|
|
2188
2319
|
};
|
|
2189
2320
|
}
|
|
2190
2321
|
/**
|
|
2191
|
-
*
|
|
2192
|
-
*
|
|
2322
|
+
* Detect stale observations (files modified after creation) and mark them in DB.
|
|
2323
|
+
* Returns the number of observations marked as stale.
|
|
2193
2324
|
*/
|
|
2194
2325
|
async detectStaleObservations() {
|
|
2195
2326
|
const staleObs = getStaleObservations(this.db.db, this.project);
|
|
@@ -2200,14 +2331,14 @@ var KiroMemorySDK = class {
|
|
|
2200
2331
|
return staleObs.length;
|
|
2201
2332
|
}
|
|
2202
2333
|
/**
|
|
2203
|
-
*
|
|
2204
|
-
*
|
|
2334
|
+
* Consolidate duplicate observations on the same file and type.
|
|
2335
|
+
* Groups by (project, type, files_modified), keeps the most recent.
|
|
2205
2336
|
*/
|
|
2206
2337
|
async consolidateObservations(options = {}) {
|
|
2207
2338
|
return consolidateObservations(this.db.db, this.project, options);
|
|
2208
2339
|
}
|
|
2209
2340
|
/**
|
|
2210
|
-
*
|
|
2341
|
+
* Decay statistics: total, stale, never accessed, recently accessed.
|
|
2211
2342
|
*/
|
|
2212
2343
|
async getDecayStats() {
|
|
2213
2344
|
const total = this.db.db.query(
|
|
@@ -2226,8 +2357,8 @@ var KiroMemorySDK = class {
|
|
|
2226
2357
|
return { total, stale, neverAccessed, recentlyAccessed };
|
|
2227
2358
|
}
|
|
2228
2359
|
/**
|
|
2229
|
-
*
|
|
2230
|
-
*
|
|
2360
|
+
* Create a structured checkpoint for session resume.
|
|
2361
|
+
* Automatically saves a context_snapshot with the last 10 observations.
|
|
2231
2362
|
*/
|
|
2232
2363
|
async createCheckpoint(sessionId, data) {
|
|
2233
2364
|
const recentObs = getObservationsByProject(this.db.db, this.project, 10);
|
|
@@ -2244,21 +2375,21 @@ var KiroMemorySDK = class {
|
|
|
2244
2375
|
});
|
|
2245
2376
|
}
|
|
2246
2377
|
/**
|
|
2247
|
-
*
|
|
2378
|
+
* Retrieve the latest checkpoint of a specific session.
|
|
2248
2379
|
*/
|
|
2249
2380
|
async getCheckpoint(sessionId) {
|
|
2250
2381
|
return getLatestCheckpoint(this.db.db, sessionId);
|
|
2251
2382
|
}
|
|
2252
2383
|
/**
|
|
2253
|
-
*
|
|
2254
|
-
*
|
|
2384
|
+
* Retrieve the latest checkpoint for the current project.
|
|
2385
|
+
* Useful for automatic resume without specifying session ID.
|
|
2255
2386
|
*/
|
|
2256
2387
|
async getLatestProjectCheckpoint() {
|
|
2257
2388
|
return getLatestCheckpointByProject(this.db.db, this.project);
|
|
2258
2389
|
}
|
|
2259
2390
|
/**
|
|
2260
|
-
*
|
|
2261
|
-
*
|
|
2391
|
+
* Generate an activity report for the current project.
|
|
2392
|
+
* Aggregates observations, sessions, summaries and files for a time period.
|
|
2262
2393
|
*/
|
|
2263
2394
|
async generateReport(options) {
|
|
2264
2395
|
const now = /* @__PURE__ */ new Date();
|