kiro-memory 1.8.1 → 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 +6 -4
- package/plugin/dist/cli/contextkit.js +428 -205
- 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/Sessions.js +5 -0
- package/plugin/dist/services/sqlite/index.js +113 -183
- package/plugin/dist/viewer.css +1 -0
- package/plugin/dist/viewer.html +2 -100
- package/plugin/dist/viewer.js +15 -24896
- 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
- package/scripts/postinstall.cjs +42 -0
|
@@ -99,9 +99,25 @@ function consolidateObservations(db, project, options = {}) {
|
|
|
99
99
|
ORDER BY cnt DESC
|
|
100
100
|
`).all(project, minGroupSize);
|
|
101
101
|
if (groups.length === 0) return { merged: 0, removed: 0 };
|
|
102
|
-
|
|
103
|
-
|
|
102
|
+
if (options.dryRun) {
|
|
103
|
+
let totalMerged = 0;
|
|
104
|
+
let totalRemoved = 0;
|
|
105
|
+
for (const group of groups) {
|
|
106
|
+
const obsIds = group.ids.split(",").map(Number);
|
|
107
|
+
const placeholders = obsIds.map(() => "?").join(",");
|
|
108
|
+
const count = db.query(
|
|
109
|
+
`SELECT COUNT(*) as cnt FROM observations WHERE id IN (${placeholders})`
|
|
110
|
+
).get(...obsIds)?.cnt || 0;
|
|
111
|
+
if (count >= minGroupSize) {
|
|
112
|
+
totalMerged += 1;
|
|
113
|
+
totalRemoved += count - 1;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return { merged: totalMerged, removed: totalRemoved };
|
|
117
|
+
}
|
|
104
118
|
const runConsolidation = db.transaction(() => {
|
|
119
|
+
let merged = 0;
|
|
120
|
+
let removed = 0;
|
|
105
121
|
for (const group of groups) {
|
|
106
122
|
const obsIds = group.ids.split(",").map(Number);
|
|
107
123
|
const placeholders = obsIds.map(() => "?").join(",");
|
|
@@ -109,11 +125,6 @@ function consolidateObservations(db, project, options = {}) {
|
|
|
109
125
|
`SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC`
|
|
110
126
|
).all(...obsIds);
|
|
111
127
|
if (observations.length < minGroupSize) continue;
|
|
112
|
-
if (options.dryRun) {
|
|
113
|
-
totalMerged += 1;
|
|
114
|
-
totalRemoved += observations.length - 1;
|
|
115
|
-
continue;
|
|
116
|
-
}
|
|
117
128
|
const keeper = observations[0];
|
|
118
129
|
const others = observations.slice(1);
|
|
119
130
|
const uniqueTexts = /* @__PURE__ */ new Set();
|
|
@@ -126,18 +137,18 @@ function consolidateObservations(db, project, options = {}) {
|
|
|
126
137
|
const consolidatedText = Array.from(uniqueTexts).join("\n---\n").substring(0, 1e5);
|
|
127
138
|
db.run(
|
|
128
139
|
"UPDATE observations SET text = ?, title = ? WHERE id = ?",
|
|
129
|
-
[consolidatedText, `[
|
|
140
|
+
[consolidatedText, `[consolidated x${observations.length}] ${keeper.title}`, keeper.id]
|
|
130
141
|
);
|
|
131
142
|
const removeIds = others.map((o) => o.id);
|
|
132
143
|
const removePlaceholders = removeIds.map(() => "?").join(",");
|
|
133
144
|
db.run(`DELETE FROM observations WHERE id IN (${removePlaceholders})`, removeIds);
|
|
134
145
|
db.run(`DELETE FROM observation_embeddings WHERE observation_id IN (${removePlaceholders})`, removeIds);
|
|
135
|
-
|
|
136
|
-
|
|
146
|
+
merged += 1;
|
|
147
|
+
removed += removeIds.length;
|
|
137
148
|
}
|
|
149
|
+
return { merged, removed };
|
|
138
150
|
});
|
|
139
|
-
runConsolidation();
|
|
140
|
-
return { merged: totalMerged, removed: totalRemoved };
|
|
151
|
+
return runConsolidation();
|
|
141
152
|
}
|
|
142
153
|
var init_Observations = __esm({
|
|
143
154
|
"src/services/sqlite/Observations.ts"() {
|
|
@@ -164,7 +175,7 @@ function escapeLikePattern3(input) {
|
|
|
164
175
|
}
|
|
165
176
|
function sanitizeFTS5Query(query) {
|
|
166
177
|
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}"`);
|
|
178
|
+
const terms = trimmed.replace(/[""\u0022]/g, "").split(/\s+/).filter((t) => t.length > 0).slice(0, 100).map((t) => `"${t}"`);
|
|
168
179
|
return terms.join(" ");
|
|
169
180
|
}
|
|
170
181
|
function searchObservationsFTS(db, query, filters = {}) {
|
|
@@ -329,26 +340,38 @@ function getTimeline(db, anchorId, depthBefore = 5, depthAfter = 5) {
|
|
|
329
340
|
return [...before, ...self, ...after];
|
|
330
341
|
}
|
|
331
342
|
function getProjectStats(db, project) {
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
343
|
+
const sql = `
|
|
344
|
+
WITH
|
|
345
|
+
obs_stats AS (
|
|
346
|
+
SELECT
|
|
347
|
+
COUNT(*) as count,
|
|
348
|
+
COALESCE(SUM(discovery_tokens), 0) as discovery_tokens,
|
|
349
|
+
COALESCE(SUM(
|
|
350
|
+
CAST((LENGTH(COALESCE(title, '')) + LENGTH(COALESCE(narrative, ''))) / 4 AS INTEGER)
|
|
351
|
+
), 0) as read_tokens
|
|
352
|
+
FROM observations WHERE project = ?
|
|
353
|
+
),
|
|
354
|
+
sum_count AS (SELECT COUNT(*) as count FROM summaries WHERE project = ?),
|
|
355
|
+
ses_count AS (SELECT COUNT(*) as count FROM sessions WHERE project = ?),
|
|
356
|
+
prm_count AS (SELECT COUNT(*) as count FROM prompts WHERE project = ?)
|
|
357
|
+
SELECT
|
|
358
|
+
obs_stats.count as observations,
|
|
359
|
+
obs_stats.discovery_tokens,
|
|
360
|
+
obs_stats.read_tokens,
|
|
361
|
+
sum_count.count as summaries,
|
|
362
|
+
ses_count.count as sessions,
|
|
363
|
+
prm_count.count as prompts
|
|
364
|
+
FROM obs_stats, sum_count, ses_count, prm_count
|
|
365
|
+
`;
|
|
366
|
+
const row = db.query(sql).get(project, project, project, project);
|
|
367
|
+
const discoveryTokens = row?.discovery_tokens || 0;
|
|
368
|
+
const readTokens = row?.read_tokens || 0;
|
|
346
369
|
const savings = Math.max(0, discoveryTokens - readTokens);
|
|
347
370
|
return {
|
|
348
|
-
observations:
|
|
349
|
-
summaries:
|
|
350
|
-
sessions:
|
|
351
|
-
prompts:
|
|
371
|
+
observations: row?.observations || 0,
|
|
372
|
+
summaries: row?.summaries || 0,
|
|
373
|
+
sessions: row?.sessions || 0,
|
|
374
|
+
prompts: row?.prompts || 0,
|
|
352
375
|
tokenEconomics: { discoveryTokens, readTokens, savings }
|
|
353
376
|
};
|
|
354
377
|
}
|
|
@@ -493,7 +516,7 @@ async function readStdin() {
|
|
|
493
516
|
}
|
|
494
517
|
resolve(JSON.parse(data));
|
|
495
518
|
} catch (err) {
|
|
496
|
-
reject(new Error(`
|
|
519
|
+
reject(new Error(`Error parsing stdin JSON: ${err}`));
|
|
497
520
|
}
|
|
498
521
|
});
|
|
499
522
|
process.stdin.on("error", (err) => {
|
|
@@ -551,11 +574,11 @@ async function runHook(name, handler) {
|
|
|
551
574
|
}
|
|
552
575
|
debugLog(name, "stdin", input);
|
|
553
576
|
await handler(input);
|
|
554
|
-
debugLog(name, "
|
|
577
|
+
debugLog(name, "completed", { success: true });
|
|
555
578
|
process.exit(0);
|
|
556
579
|
} catch (error) {
|
|
557
|
-
debugLog(name, "
|
|
558
|
-
process.stderr.write(`[kiro-memory:${name}]
|
|
580
|
+
debugLog(name, "error", { error: String(error) });
|
|
581
|
+
process.stderr.write(`[kiro-memory:${name}] Error: ${error}
|
|
559
582
|
`);
|
|
560
583
|
process.exit(0);
|
|
561
584
|
}
|
|
@@ -565,14 +588,15 @@ async function runHook(name, handler) {
|
|
|
565
588
|
import BetterSqlite3 from "better-sqlite3";
|
|
566
589
|
var Database = class {
|
|
567
590
|
_db;
|
|
591
|
+
_stmtCache = /* @__PURE__ */ new Map();
|
|
568
592
|
constructor(path, options) {
|
|
569
593
|
this._db = new BetterSqlite3(path, {
|
|
570
|
-
// better-sqlite3
|
|
594
|
+
// better-sqlite3 creates the file by default ('create' not needed)
|
|
571
595
|
readonly: options?.readwrite === false ? true : false
|
|
572
596
|
});
|
|
573
597
|
}
|
|
574
598
|
/**
|
|
575
|
-
*
|
|
599
|
+
* Execute a SQL query without results
|
|
576
600
|
*/
|
|
577
601
|
run(sql, params) {
|
|
578
602
|
const stmt = this._db.prepare(sql);
|
|
@@ -580,51 +604,53 @@ var Database = class {
|
|
|
580
604
|
return result;
|
|
581
605
|
}
|
|
582
606
|
/**
|
|
583
|
-
*
|
|
607
|
+
* Prepare a query with bun:sqlite-compatible interface.
|
|
608
|
+
* Returns a cached prepared statement for repeated queries.
|
|
584
609
|
*/
|
|
585
610
|
query(sql) {
|
|
586
|
-
|
|
611
|
+
let cached = this._stmtCache.get(sql);
|
|
612
|
+
if (!cached) {
|
|
613
|
+
cached = new BunQueryCompat(this._db, sql);
|
|
614
|
+
this._stmtCache.set(sql, cached);
|
|
615
|
+
}
|
|
616
|
+
return cached;
|
|
587
617
|
}
|
|
588
618
|
/**
|
|
589
|
-
*
|
|
619
|
+
* Create a transaction
|
|
590
620
|
*/
|
|
591
621
|
transaction(fn) {
|
|
592
622
|
return this._db.transaction(fn);
|
|
593
623
|
}
|
|
594
624
|
/**
|
|
595
|
-
*
|
|
625
|
+
* Close the connection
|
|
596
626
|
*/
|
|
597
627
|
close() {
|
|
628
|
+
this._stmtCache.clear();
|
|
598
629
|
this._db.close();
|
|
599
630
|
}
|
|
600
631
|
};
|
|
601
632
|
var BunQueryCompat = class {
|
|
602
|
-
|
|
603
|
-
_sql;
|
|
633
|
+
_stmt;
|
|
604
634
|
constructor(db, sql) {
|
|
605
|
-
this.
|
|
606
|
-
this._sql = sql;
|
|
635
|
+
this._stmt = db.prepare(sql);
|
|
607
636
|
}
|
|
608
637
|
/**
|
|
609
|
-
*
|
|
638
|
+
* Returns all rows
|
|
610
639
|
*/
|
|
611
640
|
all(...params) {
|
|
612
|
-
|
|
613
|
-
return params.length > 0 ? stmt.all(...params) : stmt.all();
|
|
641
|
+
return params.length > 0 ? this._stmt.all(...params) : this._stmt.all();
|
|
614
642
|
}
|
|
615
643
|
/**
|
|
616
|
-
*
|
|
644
|
+
* Returns the first row or null
|
|
617
645
|
*/
|
|
618
646
|
get(...params) {
|
|
619
|
-
|
|
620
|
-
return params.length > 0 ? stmt.get(...params) : stmt.get();
|
|
647
|
+
return params.length > 0 ? this._stmt.get(...params) : this._stmt.get();
|
|
621
648
|
}
|
|
622
649
|
/**
|
|
623
|
-
*
|
|
650
|
+
* Execute without results
|
|
624
651
|
*/
|
|
625
652
|
run(...params) {
|
|
626
|
-
|
|
627
|
-
return params.length > 0 ? stmt.run(...params) : stmt.run();
|
|
653
|
+
return params.length > 0 ? this._stmt.run(...params) : this._stmt.run();
|
|
628
654
|
}
|
|
629
655
|
};
|
|
630
656
|
|
|
@@ -886,40 +912,62 @@ function ensureDir(dirPath) {
|
|
|
886
912
|
var SQLITE_MMAP_SIZE_BYTES = 256 * 1024 * 1024;
|
|
887
913
|
var SQLITE_CACHE_SIZE_PAGES = 1e4;
|
|
888
914
|
var KiroMemoryDatabase = class {
|
|
889
|
-
|
|
915
|
+
_db;
|
|
890
916
|
/**
|
|
891
|
-
*
|
|
892
|
-
*
|
|
917
|
+
* Readonly accessor for the underlying Database instance.
|
|
918
|
+
* Prefer using query() and run() proxy methods directly.
|
|
919
|
+
*/
|
|
920
|
+
get db() {
|
|
921
|
+
return this._db;
|
|
922
|
+
}
|
|
923
|
+
/**
|
|
924
|
+
* @param dbPath - Path to the SQLite file (default: DB_PATH)
|
|
925
|
+
* @param skipMigrations - If true, skip the migration runner (for high-frequency hooks)
|
|
893
926
|
*/
|
|
894
927
|
constructor(dbPath = DB_PATH, skipMigrations = false) {
|
|
895
928
|
if (dbPath !== ":memory:") {
|
|
896
929
|
ensureDir(DATA_DIR2);
|
|
897
930
|
}
|
|
898
|
-
this.
|
|
899
|
-
this.
|
|
900
|
-
this.
|
|
901
|
-
this.
|
|
902
|
-
this.
|
|
903
|
-
this.
|
|
904
|
-
this.
|
|
931
|
+
this._db = new Database(dbPath, { create: true, readwrite: true });
|
|
932
|
+
this._db.run("PRAGMA journal_mode = WAL");
|
|
933
|
+
this._db.run("PRAGMA busy_timeout = 5000");
|
|
934
|
+
this._db.run("PRAGMA synchronous = NORMAL");
|
|
935
|
+
this._db.run("PRAGMA foreign_keys = ON");
|
|
936
|
+
this._db.run("PRAGMA temp_store = memory");
|
|
937
|
+
this._db.run(`PRAGMA mmap_size = ${SQLITE_MMAP_SIZE_BYTES}`);
|
|
938
|
+
this._db.run(`PRAGMA cache_size = ${SQLITE_CACHE_SIZE_PAGES}`);
|
|
905
939
|
if (!skipMigrations) {
|
|
906
|
-
const migrationRunner = new MigrationRunner(this.
|
|
940
|
+
const migrationRunner = new MigrationRunner(this._db);
|
|
907
941
|
migrationRunner.runAllMigrations();
|
|
908
942
|
}
|
|
909
943
|
}
|
|
910
944
|
/**
|
|
911
|
-
*
|
|
912
|
-
*
|
|
945
|
+
* Prepare a query (delegates to underlying Database).
|
|
946
|
+
* Proxy method to avoid ctx.db.db.query() double access.
|
|
947
|
+
*/
|
|
948
|
+
query(sql) {
|
|
949
|
+
return this._db.query(sql);
|
|
950
|
+
}
|
|
951
|
+
/**
|
|
952
|
+
* Execute a SQL statement without results (delegates to underlying Database).
|
|
953
|
+
* Proxy method to avoid ctx.db.db.run() double access.
|
|
954
|
+
*/
|
|
955
|
+
run(sql, params) {
|
|
956
|
+
return this._db.run(sql, params);
|
|
957
|
+
}
|
|
958
|
+
/**
|
|
959
|
+
* Executes a function within an atomic transaction.
|
|
960
|
+
* If fn() throws an error, the transaction is automatically rolled back.
|
|
913
961
|
*/
|
|
914
962
|
withTransaction(fn) {
|
|
915
|
-
const transaction = this.
|
|
916
|
-
return transaction(this.
|
|
963
|
+
const transaction = this._db.transaction(fn);
|
|
964
|
+
return transaction(this._db);
|
|
917
965
|
}
|
|
918
966
|
/**
|
|
919
967
|
* Close the database connection
|
|
920
968
|
*/
|
|
921
969
|
close() {
|
|
922
|
-
this.
|
|
970
|
+
this._db.close();
|
|
923
971
|
}
|
|
924
972
|
};
|
|
925
973
|
var MigrationRunner = class {
|
|
@@ -1414,8 +1462,8 @@ var EmbeddingService = class {
|
|
|
1414
1462
|
initialized = false;
|
|
1415
1463
|
initializing = null;
|
|
1416
1464
|
/**
|
|
1417
|
-
*
|
|
1418
|
-
*
|
|
1465
|
+
* Initialize the embedding service.
|
|
1466
|
+
* Tries fastembed, then @huggingface/transformers, then fallback to null.
|
|
1419
1467
|
*/
|
|
1420
1468
|
async initialize() {
|
|
1421
1469
|
if (this.initialized) return this.provider !== null;
|
|
@@ -1436,11 +1484,11 @@ var EmbeddingService = class {
|
|
|
1436
1484
|
});
|
|
1437
1485
|
this.provider = "fastembed";
|
|
1438
1486
|
this.initialized = true;
|
|
1439
|
-
logger.info("EMBEDDING", "
|
|
1487
|
+
logger.info("EMBEDDING", "Initialized with fastembed (BGE-small-en-v1.5)");
|
|
1440
1488
|
return true;
|
|
1441
1489
|
}
|
|
1442
1490
|
} catch (error) {
|
|
1443
|
-
logger.debug("EMBEDDING", `fastembed
|
|
1491
|
+
logger.debug("EMBEDDING", `fastembed not available: ${error}`);
|
|
1444
1492
|
}
|
|
1445
1493
|
try {
|
|
1446
1494
|
const transformers = await import("@huggingface/transformers");
|
|
@@ -1451,20 +1499,20 @@ var EmbeddingService = class {
|
|
|
1451
1499
|
});
|
|
1452
1500
|
this.provider = "transformers";
|
|
1453
1501
|
this.initialized = true;
|
|
1454
|
-
logger.info("EMBEDDING", "
|
|
1502
|
+
logger.info("EMBEDDING", "Initialized with @huggingface/transformers (all-MiniLM-L6-v2)");
|
|
1455
1503
|
return true;
|
|
1456
1504
|
}
|
|
1457
1505
|
} catch (error) {
|
|
1458
|
-
logger.debug("EMBEDDING", `@huggingface/transformers
|
|
1506
|
+
logger.debug("EMBEDDING", `@huggingface/transformers not available: ${error}`);
|
|
1459
1507
|
}
|
|
1460
1508
|
this.provider = null;
|
|
1461
1509
|
this.initialized = true;
|
|
1462
|
-
logger.warn("EMBEDDING", "
|
|
1510
|
+
logger.warn("EMBEDDING", "No embedding provider available, semantic search disabled");
|
|
1463
1511
|
return false;
|
|
1464
1512
|
}
|
|
1465
1513
|
/**
|
|
1466
|
-
*
|
|
1467
|
-
*
|
|
1514
|
+
* Generate embedding for a single text.
|
|
1515
|
+
* Returns Float32Array with 384 dimensions, or null if not available.
|
|
1468
1516
|
*/
|
|
1469
1517
|
async embed(text) {
|
|
1470
1518
|
if (!this.initialized) await this.initialize();
|
|
@@ -1477,46 +1525,111 @@ var EmbeddingService = class {
|
|
|
1477
1525
|
return await this._embedTransformers(truncated);
|
|
1478
1526
|
}
|
|
1479
1527
|
} catch (error) {
|
|
1480
|
-
logger.error("EMBEDDING", `
|
|
1528
|
+
logger.error("EMBEDDING", `Error generating embedding: ${error}`);
|
|
1481
1529
|
}
|
|
1482
1530
|
return null;
|
|
1483
1531
|
}
|
|
1484
1532
|
/**
|
|
1485
|
-
*
|
|
1533
|
+
* Generate embeddings in batch.
|
|
1534
|
+
* Uses native batch support when available (fastembed, transformers),
|
|
1535
|
+
* falls back to serial processing on batch failure.
|
|
1486
1536
|
*/
|
|
1487
1537
|
async embedBatch(texts) {
|
|
1488
1538
|
if (!this.initialized) await this.initialize();
|
|
1489
1539
|
if (!this.provider || !this.model) return texts.map(() => null);
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
}
|
|
1496
|
-
|
|
1540
|
+
if (texts.length === 0) return [];
|
|
1541
|
+
const truncated = texts.map((t) => t.substring(0, 2e3));
|
|
1542
|
+
try {
|
|
1543
|
+
if (this.provider === "fastembed") {
|
|
1544
|
+
return await this._embedBatchFastembed(truncated);
|
|
1545
|
+
} else if (this.provider === "transformers") {
|
|
1546
|
+
return await this._embedBatchTransformers(truncated);
|
|
1497
1547
|
}
|
|
1548
|
+
} catch (error) {
|
|
1549
|
+
logger.warn("EMBEDDING", `Batch embedding failed, falling back to serial: ${error}`);
|
|
1498
1550
|
}
|
|
1499
|
-
return
|
|
1551
|
+
return this._embedBatchSerial(truncated);
|
|
1500
1552
|
}
|
|
1501
1553
|
/**
|
|
1502
|
-
*
|
|
1554
|
+
* Check if the service is available.
|
|
1503
1555
|
*/
|
|
1504
1556
|
isAvailable() {
|
|
1505
1557
|
return this.initialized && this.provider !== null;
|
|
1506
1558
|
}
|
|
1507
1559
|
/**
|
|
1508
|
-
*
|
|
1560
|
+
* Name of the active provider.
|
|
1509
1561
|
*/
|
|
1510
1562
|
getProvider() {
|
|
1511
1563
|
return this.provider;
|
|
1512
1564
|
}
|
|
1513
1565
|
/**
|
|
1514
|
-
*
|
|
1566
|
+
* Embedding vector dimensions.
|
|
1515
1567
|
*/
|
|
1516
1568
|
getDimensions() {
|
|
1517
1569
|
return 384;
|
|
1518
1570
|
}
|
|
1519
|
-
// ---
|
|
1571
|
+
// --- Batch implementations ---
|
|
1572
|
+
/**
|
|
1573
|
+
* Native batch embedding with fastembed.
|
|
1574
|
+
* FlagEmbedding.embed() accepts string[] and returns an async iterable of batches.
|
|
1575
|
+
*/
|
|
1576
|
+
async _embedBatchFastembed(texts) {
|
|
1577
|
+
const results = [];
|
|
1578
|
+
const embeddings = this.model.embed(texts, texts.length);
|
|
1579
|
+
for await (const batch of embeddings) {
|
|
1580
|
+
if (batch) {
|
|
1581
|
+
for (const vec of batch) {
|
|
1582
|
+
results.push(vec instanceof Float32Array ? vec : new Float32Array(vec));
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
while (results.length < texts.length) {
|
|
1587
|
+
results.push(null);
|
|
1588
|
+
}
|
|
1589
|
+
return results;
|
|
1590
|
+
}
|
|
1591
|
+
/**
|
|
1592
|
+
* Batch embedding with @huggingface/transformers pipeline.
|
|
1593
|
+
* The pipeline accepts string[] and returns a Tensor with shape [N, dims].
|
|
1594
|
+
*/
|
|
1595
|
+
async _embedBatchTransformers(texts) {
|
|
1596
|
+
const output = await this.model(texts, {
|
|
1597
|
+
pooling: "mean",
|
|
1598
|
+
normalize: true
|
|
1599
|
+
});
|
|
1600
|
+
if (!output?.data) {
|
|
1601
|
+
return texts.map(() => null);
|
|
1602
|
+
}
|
|
1603
|
+
const dims = this.getDimensions();
|
|
1604
|
+
const data = output.data instanceof Float32Array ? output.data : new Float32Array(output.data);
|
|
1605
|
+
const results = [];
|
|
1606
|
+
for (let i = 0; i < texts.length; i++) {
|
|
1607
|
+
const offset = i * dims;
|
|
1608
|
+
if (offset + dims <= data.length) {
|
|
1609
|
+
results.push(data.slice(offset, offset + dims));
|
|
1610
|
+
} else {
|
|
1611
|
+
results.push(null);
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
return results;
|
|
1615
|
+
}
|
|
1616
|
+
/**
|
|
1617
|
+
* Serial fallback: embed texts one at a time.
|
|
1618
|
+
* Used when native batch fails.
|
|
1619
|
+
*/
|
|
1620
|
+
async _embedBatchSerial(texts) {
|
|
1621
|
+
const results = [];
|
|
1622
|
+
for (const text of texts) {
|
|
1623
|
+
try {
|
|
1624
|
+
const embedding = await this.embed(text);
|
|
1625
|
+
results.push(embedding);
|
|
1626
|
+
} catch {
|
|
1627
|
+
results.push(null);
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
return results;
|
|
1631
|
+
}
|
|
1632
|
+
// --- Single-text provider implementations ---
|
|
1520
1633
|
async _embedFastembed(text) {
|
|
1521
1634
|
const embeddings = this.model.embed([text], 1);
|
|
1522
1635
|
for await (const batch of embeddings) {
|
|
@@ -1547,17 +1660,21 @@ function getEmbeddingService() {
|
|
|
1547
1660
|
}
|
|
1548
1661
|
|
|
1549
1662
|
// src/services/search/VectorSearch.ts
|
|
1663
|
+
var DEFAULT_MAX_CANDIDATES = 2e3;
|
|
1550
1664
|
function cosineSimilarity(a, b) {
|
|
1551
|
-
|
|
1665
|
+
const len = a.length;
|
|
1666
|
+
if (len !== b.length) return 0;
|
|
1552
1667
|
let dotProduct = 0;
|
|
1553
1668
|
let normA = 0;
|
|
1554
1669
|
let normB = 0;
|
|
1555
|
-
for (let i = 0; i <
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1670
|
+
for (let i = 0; i < len; i++) {
|
|
1671
|
+
const ai = a[i];
|
|
1672
|
+
const bi = b[i];
|
|
1673
|
+
dotProduct += ai * bi;
|
|
1674
|
+
normA += ai * ai;
|
|
1675
|
+
normB += bi * bi;
|
|
1676
|
+
}
|
|
1677
|
+
const denominator = Math.sqrt(normA * normB);
|
|
1561
1678
|
if (denominator === 0) return 0;
|
|
1562
1679
|
return dotProduct / denominator;
|
|
1563
1680
|
}
|
|
@@ -1570,23 +1687,36 @@ function bufferToFloat32(buf) {
|
|
|
1570
1687
|
}
|
|
1571
1688
|
var VectorSearch = class {
|
|
1572
1689
|
/**
|
|
1573
|
-
*
|
|
1690
|
+
* Semantic search with SQL pre-filtering for scalability.
|
|
1691
|
+
*
|
|
1692
|
+
* 2-phase strategy:
|
|
1693
|
+
* 1. SQL pre-filters by project + sorts by recency (loads max N candidates)
|
|
1694
|
+
* 2. JS computes cosine similarity only on filtered candidates
|
|
1695
|
+
*
|
|
1696
|
+
* With 50k observations and maxCandidates=2000, loads only ~4% of data.
|
|
1574
1697
|
*/
|
|
1575
1698
|
async search(db, queryEmbedding, options = {}) {
|
|
1576
1699
|
const limit = options.limit || 10;
|
|
1577
1700
|
const threshold = options.threshold || 0.3;
|
|
1701
|
+
const maxCandidates = options.maxCandidates || DEFAULT_MAX_CANDIDATES;
|
|
1578
1702
|
try {
|
|
1579
|
-
|
|
1703
|
+
const conditions = [];
|
|
1704
|
+
const params = [];
|
|
1705
|
+
if (options.project) {
|
|
1706
|
+
conditions.push("o.project = ?");
|
|
1707
|
+
params.push(options.project);
|
|
1708
|
+
}
|
|
1709
|
+
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
1710
|
+
const sql = `
|
|
1580
1711
|
SELECT e.observation_id, e.embedding,
|
|
1581
1712
|
o.title, o.text, o.type, o.project, o.created_at, o.created_at_epoch
|
|
1582
1713
|
FROM observation_embeddings e
|
|
1583
1714
|
JOIN observations o ON o.id = e.observation_id
|
|
1715
|
+
${whereClause}
|
|
1716
|
+
ORDER BY o.created_at_epoch DESC
|
|
1717
|
+
LIMIT ?
|
|
1584
1718
|
`;
|
|
1585
|
-
|
|
1586
|
-
if (options.project) {
|
|
1587
|
-
sql += " WHERE o.project = ?";
|
|
1588
|
-
params.push(options.project);
|
|
1589
|
-
}
|
|
1719
|
+
params.push(maxCandidates);
|
|
1590
1720
|
const rows = db.query(sql).all(...params);
|
|
1591
1721
|
const scored = [];
|
|
1592
1722
|
for (const row of rows) {
|
|
@@ -1607,14 +1737,15 @@ var VectorSearch = class {
|
|
|
1607
1737
|
}
|
|
1608
1738
|
}
|
|
1609
1739
|
scored.sort((a, b) => b.similarity - a.similarity);
|
|
1740
|
+
logger.debug("VECTOR", `Search: ${rows.length} candidates \u2192 ${scored.length} above threshold \u2192 ${Math.min(scored.length, limit)} results`);
|
|
1610
1741
|
return scored.slice(0, limit);
|
|
1611
1742
|
} catch (error) {
|
|
1612
|
-
logger.error("VECTOR", `
|
|
1743
|
+
logger.error("VECTOR", `Vector search error: ${error}`);
|
|
1613
1744
|
return [];
|
|
1614
1745
|
}
|
|
1615
1746
|
}
|
|
1616
1747
|
/**
|
|
1617
|
-
*
|
|
1748
|
+
* Store embedding for an observation.
|
|
1618
1749
|
*/
|
|
1619
1750
|
async storeEmbedding(db, observationId, embedding, model) {
|
|
1620
1751
|
try {
|
|
@@ -1630,18 +1761,18 @@ var VectorSearch = class {
|
|
|
1630
1761
|
embedding.length,
|
|
1631
1762
|
(/* @__PURE__ */ new Date()).toISOString()
|
|
1632
1763
|
);
|
|
1633
|
-
logger.debug("VECTOR", `Embedding
|
|
1764
|
+
logger.debug("VECTOR", `Embedding saved for observation ${observationId}`);
|
|
1634
1765
|
} catch (error) {
|
|
1635
|
-
logger.error("VECTOR", `
|
|
1766
|
+
logger.error("VECTOR", `Error saving embedding: ${error}`);
|
|
1636
1767
|
}
|
|
1637
1768
|
}
|
|
1638
1769
|
/**
|
|
1639
|
-
*
|
|
1770
|
+
* Generate embeddings for observations that don't have them yet.
|
|
1640
1771
|
*/
|
|
1641
1772
|
async backfillEmbeddings(db, batchSize = 50) {
|
|
1642
1773
|
const embeddingService2 = getEmbeddingService();
|
|
1643
1774
|
if (!await embeddingService2.initialize()) {
|
|
1644
|
-
logger.warn("VECTOR", "Embedding service
|
|
1775
|
+
logger.warn("VECTOR", "Embedding service not available, backfill skipped");
|
|
1645
1776
|
return 0;
|
|
1646
1777
|
}
|
|
1647
1778
|
const rows = db.query(`
|
|
@@ -1667,11 +1798,11 @@ var VectorSearch = class {
|
|
|
1667
1798
|
count++;
|
|
1668
1799
|
}
|
|
1669
1800
|
}
|
|
1670
|
-
logger.info("VECTOR", `Backfill
|
|
1801
|
+
logger.info("VECTOR", `Backfill completed: ${count}/${rows.length} embeddings generated`);
|
|
1671
1802
|
return count;
|
|
1672
1803
|
}
|
|
1673
1804
|
/**
|
|
1674
|
-
*
|
|
1805
|
+
* Embedding statistics.
|
|
1675
1806
|
*/
|
|
1676
1807
|
getStats(db) {
|
|
1677
1808
|
try {
|
|
@@ -1698,21 +1829,21 @@ function getVectorSearch() {
|
|
|
1698
1829
|
var HybridSearch = class {
|
|
1699
1830
|
embeddingInitialized = false;
|
|
1700
1831
|
/**
|
|
1701
|
-
*
|
|
1832
|
+
* Initialize the embedding service (lazy, non-blocking)
|
|
1702
1833
|
*/
|
|
1703
1834
|
async initialize() {
|
|
1704
1835
|
try {
|
|
1705
1836
|
const embeddingService2 = getEmbeddingService();
|
|
1706
1837
|
await embeddingService2.initialize();
|
|
1707
1838
|
this.embeddingInitialized = embeddingService2.isAvailable();
|
|
1708
|
-
logger.info("SEARCH", `HybridSearch
|
|
1839
|
+
logger.info("SEARCH", `HybridSearch initialized (embedding: ${this.embeddingInitialized ? "active" : "disabled"})`);
|
|
1709
1840
|
} catch (error) {
|
|
1710
|
-
logger.warn("SEARCH", "
|
|
1841
|
+
logger.warn("SEARCH", "Embedding initialization failed, using only FTS5", {}, error);
|
|
1711
1842
|
this.embeddingInitialized = false;
|
|
1712
1843
|
}
|
|
1713
1844
|
}
|
|
1714
1845
|
/**
|
|
1715
|
-
*
|
|
1846
|
+
* Hybrid search with 4-signal scoring
|
|
1716
1847
|
*/
|
|
1717
1848
|
async search(db, query, options = {}) {
|
|
1718
1849
|
const limit = options.limit || 10;
|
|
@@ -1728,7 +1859,7 @@ var HybridSearch = class {
|
|
|
1728
1859
|
const vectorResults = await vectorSearch2.search(db, queryEmbedding, {
|
|
1729
1860
|
project: options.project,
|
|
1730
1861
|
limit: limit * 2,
|
|
1731
|
-
//
|
|
1862
|
+
// Fetch more results for ranking
|
|
1732
1863
|
threshold: 0.3
|
|
1733
1864
|
});
|
|
1734
1865
|
for (const hit of vectorResults) {
|
|
@@ -1745,10 +1876,10 @@ var HybridSearch = class {
|
|
|
1745
1876
|
source: "vector"
|
|
1746
1877
|
});
|
|
1747
1878
|
}
|
|
1748
|
-
logger.debug("SEARCH", `Vector search: ${vectorResults.length}
|
|
1879
|
+
logger.debug("SEARCH", `Vector search: ${vectorResults.length} results`);
|
|
1749
1880
|
}
|
|
1750
1881
|
} catch (error) {
|
|
1751
|
-
logger.warn("SEARCH", "
|
|
1882
|
+
logger.warn("SEARCH", "Vector search failed, using only keyword", {}, error);
|
|
1752
1883
|
}
|
|
1753
1884
|
}
|
|
1754
1885
|
try {
|
|
@@ -1778,9 +1909,9 @@ var HybridSearch = class {
|
|
|
1778
1909
|
});
|
|
1779
1910
|
}
|
|
1780
1911
|
}
|
|
1781
|
-
logger.debug("SEARCH", `Keyword search: ${keywordResults.length}
|
|
1912
|
+
logger.debug("SEARCH", `Keyword search: ${keywordResults.length} results`);
|
|
1782
1913
|
} catch (error) {
|
|
1783
|
-
logger.error("SEARCH", "
|
|
1914
|
+
logger.error("SEARCH", "Keyword search failed", {}, error);
|
|
1784
1915
|
}
|
|
1785
1916
|
if (rawItems.size === 0) return [];
|
|
1786
1917
|
const allFTS5Ranks = Array.from(rawItems.values()).filter((item) => item.fts5Rank !== null).map((item) => item.fts5Rank);
|
|
@@ -1868,33 +1999,33 @@ var KiroMemorySDK = class {
|
|
|
1868
1999
|
};
|
|
1869
2000
|
}
|
|
1870
2001
|
/**
|
|
1871
|
-
*
|
|
2002
|
+
* Validate input for storeObservation
|
|
1872
2003
|
*/
|
|
1873
2004
|
validateObservationInput(data) {
|
|
1874
2005
|
if (!data.type || typeof data.type !== "string" || data.type.length > 100) {
|
|
1875
|
-
throw new Error("type
|
|
2006
|
+
throw new Error("type is required (string, max 100 chars)");
|
|
1876
2007
|
}
|
|
1877
2008
|
if (!data.title || typeof data.title !== "string" || data.title.length > 500) {
|
|
1878
|
-
throw new Error("title
|
|
2009
|
+
throw new Error("title is required (string, max 500 chars)");
|
|
1879
2010
|
}
|
|
1880
2011
|
if (!data.content || typeof data.content !== "string" || data.content.length > 1e5) {
|
|
1881
|
-
throw new Error("content
|
|
2012
|
+
throw new Error("content is required (string, max 100KB)");
|
|
1882
2013
|
}
|
|
1883
2014
|
}
|
|
1884
2015
|
/**
|
|
1885
|
-
*
|
|
2016
|
+
* Validate input for storeSummary
|
|
1886
2017
|
*/
|
|
1887
2018
|
validateSummaryInput(data) {
|
|
1888
2019
|
const MAX = 5e4;
|
|
1889
2020
|
for (const [key, val] of Object.entries(data)) {
|
|
1890
2021
|
if (val !== void 0 && val !== null) {
|
|
1891
|
-
if (typeof val !== "string") throw new Error(`${key}
|
|
1892
|
-
if (val.length > MAX) throw new Error(`${key}
|
|
2022
|
+
if (typeof val !== "string") throw new Error(`${key} must be a string`);
|
|
2023
|
+
if (val.length > MAX) throw new Error(`${key} too large (max 50KB)`);
|
|
1893
2024
|
}
|
|
1894
2025
|
}
|
|
1895
2026
|
}
|
|
1896
2027
|
/**
|
|
1897
|
-
*
|
|
2028
|
+
* Generate and store embedding for an observation (fire-and-forget, non-blocking)
|
|
1898
2029
|
*/
|
|
1899
2030
|
async generateEmbeddingAsync(observationId, title, content, concepts) {
|
|
1900
2031
|
try {
|
|
@@ -1914,39 +2045,39 @@ var KiroMemorySDK = class {
|
|
|
1914
2045
|
);
|
|
1915
2046
|
}
|
|
1916
2047
|
} catch (error) {
|
|
1917
|
-
logger.debug("SDK", `Embedding generation
|
|
2048
|
+
logger.debug("SDK", `Embedding generation failed for obs ${observationId}: ${error}`);
|
|
1918
2049
|
}
|
|
1919
2050
|
}
|
|
1920
2051
|
/**
|
|
1921
|
-
*
|
|
1922
|
-
*
|
|
1923
|
-
*
|
|
2052
|
+
* Generate SHA256 content hash for content-based deduplication.
|
|
2053
|
+
* Uses (project + type + title + narrative) as semantic identity tuple.
|
|
2054
|
+
* Does NOT include sessionId since it's unique per invocation.
|
|
1924
2055
|
*/
|
|
1925
2056
|
generateContentHash(type, title, narrative) {
|
|
1926
2057
|
const payload = `${this.project}|${type}|${title}|${narrative || ""}`;
|
|
1927
2058
|
return createHash("sha256").update(payload).digest("hex");
|
|
1928
2059
|
}
|
|
1929
2060
|
/**
|
|
1930
|
-
*
|
|
1931
|
-
*
|
|
2061
|
+
* Deduplication windows per type (ms).
|
|
2062
|
+
* Types with many repetitions have wider windows.
|
|
1932
2063
|
*/
|
|
1933
2064
|
getDeduplicationWindow(type) {
|
|
1934
2065
|
switch (type) {
|
|
1935
2066
|
case "file-read":
|
|
1936
2067
|
return 6e4;
|
|
1937
|
-
// 60s —
|
|
2068
|
+
// 60s — frequent reads on the same files
|
|
1938
2069
|
case "file-write":
|
|
1939
2070
|
return 1e4;
|
|
1940
|
-
// 10s —
|
|
2071
|
+
// 10s — rapid consecutive writes
|
|
1941
2072
|
case "command":
|
|
1942
2073
|
return 3e4;
|
|
1943
2074
|
// 30s — standard
|
|
1944
2075
|
case "research":
|
|
1945
2076
|
return 12e4;
|
|
1946
|
-
// 120s — web search
|
|
2077
|
+
// 120s — repeated web search and fetch
|
|
1947
2078
|
case "delegation":
|
|
1948
2079
|
return 6e4;
|
|
1949
|
-
// 60s —
|
|
2080
|
+
// 60s — rapid delegations
|
|
1950
2081
|
default:
|
|
1951
2082
|
return 3e4;
|
|
1952
2083
|
}
|
|
@@ -1960,7 +2091,7 @@ var KiroMemorySDK = class {
|
|
|
1960
2091
|
const contentHash = this.generateContentHash(data.type, data.title, data.narrative);
|
|
1961
2092
|
const dedupWindow = this.getDeduplicationWindow(data.type);
|
|
1962
2093
|
if (isDuplicateObservation(this.db.db, contentHash, dedupWindow)) {
|
|
1963
|
-
logger.debug("SDK", `
|
|
2094
|
+
logger.debug("SDK", `Duplicate observation discarded (${data.type}, ${dedupWindow}ms): ${data.title}`);
|
|
1964
2095
|
return -1;
|
|
1965
2096
|
}
|
|
1966
2097
|
const filesRead = data.filesRead || (data.type === "file-read" ? data.files : void 0);
|
|
@@ -1988,12 +2119,12 @@ var KiroMemorySDK = class {
|
|
|
1988
2119
|
return observationId;
|
|
1989
2120
|
}
|
|
1990
2121
|
/**
|
|
1991
|
-
*
|
|
1992
|
-
*
|
|
2122
|
+
* Store structured knowledge (constraint, decision, heuristic, rejected).
|
|
2123
|
+
* Uses the `type` field for knowledgeType and `facts` for JSON metadata.
|
|
1993
2124
|
*/
|
|
1994
2125
|
async storeKnowledge(data) {
|
|
1995
2126
|
if (!KNOWLEDGE_TYPES.includes(data.knowledgeType)) {
|
|
1996
|
-
throw new Error(`knowledgeType
|
|
2127
|
+
throw new Error(`Invalid knowledgeType: ${data.knowledgeType}. Allowed values: ${KNOWLEDGE_TYPES.join(", ")}`);
|
|
1997
2128
|
}
|
|
1998
2129
|
this.validateObservationInput({ type: data.knowledgeType, title: data.title, content: data.content });
|
|
1999
2130
|
const metadata = (() => {
|
|
@@ -2025,9 +2156,9 @@ var KiroMemorySDK = class {
|
|
|
2025
2156
|
}
|
|
2026
2157
|
})();
|
|
2027
2158
|
const sessionId = "sdk-" + Date.now();
|
|
2028
|
-
const contentHash = this.generateContentHash(data.
|
|
2159
|
+
const contentHash = this.generateContentHash(data.knowledgeType, data.title);
|
|
2029
2160
|
if (isDuplicateObservation(this.db.db, contentHash)) {
|
|
2030
|
-
logger.debug("SDK", `
|
|
2161
|
+
logger.debug("SDK", `Duplicate knowledge discarded: ${data.title}`);
|
|
2031
2162
|
return -1;
|
|
2032
2163
|
}
|
|
2033
2164
|
const discoveryTokens = Math.ceil(data.content.length / 4);
|
|
@@ -2044,11 +2175,11 @@ var KiroMemorySDK = class {
|
|
|
2044
2175
|
null,
|
|
2045
2176
|
// narrative
|
|
2046
2177
|
JSON.stringify(metadata),
|
|
2047
|
-
// facts =
|
|
2178
|
+
// facts = JSON metadata
|
|
2048
2179
|
data.concepts?.join(", ") || null,
|
|
2049
2180
|
data.files?.join(", ") || null,
|
|
2050
2181
|
null,
|
|
2051
|
-
// filesModified: knowledge
|
|
2182
|
+
// filesModified: knowledge doesn't modify files
|
|
2052
2183
|
0,
|
|
2053
2184
|
// prompt_number
|
|
2054
2185
|
contentHash,
|
|
@@ -2159,8 +2290,8 @@ var KiroMemorySDK = class {
|
|
|
2159
2290
|
return this.project;
|
|
2160
2291
|
}
|
|
2161
2292
|
/**
|
|
2162
|
-
*
|
|
2163
|
-
*
|
|
2293
|
+
* Hybrid search: vector search + keyword FTS5
|
|
2294
|
+
* Requires HybridSearch initialization (embedding service)
|
|
2164
2295
|
*/
|
|
2165
2296
|
async hybridSearch(query, options = {}) {
|
|
2166
2297
|
const hybridSearch2 = getHybridSearch();
|
|
@@ -2170,8 +2301,8 @@ var KiroMemorySDK = class {
|
|
|
2170
2301
|
});
|
|
2171
2302
|
}
|
|
2172
2303
|
/**
|
|
2173
|
-
*
|
|
2174
|
-
*
|
|
2304
|
+
* Semantic-only search (vector search)
|
|
2305
|
+
* Returns results based on cosine similarity with embeddings
|
|
2175
2306
|
*/
|
|
2176
2307
|
async semanticSearch(query, options = {}) {
|
|
2177
2308
|
const embeddingService2 = getEmbeddingService();
|
|
@@ -2206,21 +2337,21 @@ var KiroMemorySDK = class {
|
|
|
2206
2337
|
}));
|
|
2207
2338
|
}
|
|
2208
2339
|
/**
|
|
2209
|
-
*
|
|
2340
|
+
* Generate embeddings for observations that don't have them yet
|
|
2210
2341
|
*/
|
|
2211
2342
|
async backfillEmbeddings(batchSize = 50) {
|
|
2212
2343
|
const vectorSearch2 = getVectorSearch();
|
|
2213
2344
|
return vectorSearch2.backfillEmbeddings(this.db.db, batchSize);
|
|
2214
2345
|
}
|
|
2215
2346
|
/**
|
|
2216
|
-
*
|
|
2347
|
+
* Embedding statistics in the database
|
|
2217
2348
|
*/
|
|
2218
2349
|
getEmbeddingStats() {
|
|
2219
2350
|
const vectorSearch2 = getVectorSearch();
|
|
2220
2351
|
return vectorSearch2.getStats(this.db.db);
|
|
2221
2352
|
}
|
|
2222
2353
|
/**
|
|
2223
|
-
*
|
|
2354
|
+
* Initialize the embedding service (lazy, call before hybridSearch)
|
|
2224
2355
|
*/
|
|
2225
2356
|
async initializeEmbeddings() {
|
|
2226
2357
|
const hybridSearch2 = getHybridSearch();
|
|
@@ -2228,10 +2359,10 @@ var KiroMemorySDK = class {
|
|
|
2228
2359
|
return getEmbeddingService().isAvailable();
|
|
2229
2360
|
}
|
|
2230
2361
|
/**
|
|
2231
|
-
*
|
|
2362
|
+
* Smart context with 4-signal ranking and token budget.
|
|
2232
2363
|
*
|
|
2233
|
-
*
|
|
2234
|
-
*
|
|
2364
|
+
* If query present: uses HybridSearch with SEARCH_WEIGHTS.
|
|
2365
|
+
* If no query: ranking by recency + project match (CONTEXT_WEIGHTS).
|
|
2235
2366
|
*/
|
|
2236
2367
|
async getSmartContext(options = {}) {
|
|
2237
2368
|
const tokenBudget = options.tokenBudget || parseInt(process.env.KIRO_MEMORY_CONTEXT_TOKENS || "0", 10) || 2e3;
|
|
@@ -2305,8 +2436,8 @@ var KiroMemorySDK = class {
|
|
|
2305
2436
|
};
|
|
2306
2437
|
}
|
|
2307
2438
|
/**
|
|
2308
|
-
*
|
|
2309
|
-
*
|
|
2439
|
+
* Detect stale observations (files modified after creation) and mark them in DB.
|
|
2440
|
+
* Returns the number of observations marked as stale.
|
|
2310
2441
|
*/
|
|
2311
2442
|
async detectStaleObservations() {
|
|
2312
2443
|
const staleObs = getStaleObservations(this.db.db, this.project);
|
|
@@ -2317,14 +2448,14 @@ var KiroMemorySDK = class {
|
|
|
2317
2448
|
return staleObs.length;
|
|
2318
2449
|
}
|
|
2319
2450
|
/**
|
|
2320
|
-
*
|
|
2321
|
-
*
|
|
2451
|
+
* Consolidate duplicate observations on the same file and type.
|
|
2452
|
+
* Groups by (project, type, files_modified), keeps the most recent.
|
|
2322
2453
|
*/
|
|
2323
2454
|
async consolidateObservations(options = {}) {
|
|
2324
2455
|
return consolidateObservations(this.db.db, this.project, options);
|
|
2325
2456
|
}
|
|
2326
2457
|
/**
|
|
2327
|
-
*
|
|
2458
|
+
* Decay statistics: total, stale, never accessed, recently accessed.
|
|
2328
2459
|
*/
|
|
2329
2460
|
async getDecayStats() {
|
|
2330
2461
|
const total = this.db.db.query(
|
|
@@ -2343,8 +2474,8 @@ var KiroMemorySDK = class {
|
|
|
2343
2474
|
return { total, stale, neverAccessed, recentlyAccessed };
|
|
2344
2475
|
}
|
|
2345
2476
|
/**
|
|
2346
|
-
*
|
|
2347
|
-
*
|
|
2477
|
+
* Create a structured checkpoint for session resume.
|
|
2478
|
+
* Automatically saves a context_snapshot with the last 10 observations.
|
|
2348
2479
|
*/
|
|
2349
2480
|
async createCheckpoint(sessionId, data) {
|
|
2350
2481
|
const recentObs = getObservationsByProject(this.db.db, this.project, 10);
|
|
@@ -2361,21 +2492,21 @@ var KiroMemorySDK = class {
|
|
|
2361
2492
|
});
|
|
2362
2493
|
}
|
|
2363
2494
|
/**
|
|
2364
|
-
*
|
|
2495
|
+
* Retrieve the latest checkpoint of a specific session.
|
|
2365
2496
|
*/
|
|
2366
2497
|
async getCheckpoint(sessionId) {
|
|
2367
2498
|
return getLatestCheckpoint(this.db.db, sessionId);
|
|
2368
2499
|
}
|
|
2369
2500
|
/**
|
|
2370
|
-
*
|
|
2371
|
-
*
|
|
2501
|
+
* Retrieve the latest checkpoint for the current project.
|
|
2502
|
+
* Useful for automatic resume without specifying session ID.
|
|
2372
2503
|
*/
|
|
2373
2504
|
async getLatestProjectCheckpoint() {
|
|
2374
2505
|
return getLatestCheckpointByProject(this.db.db, this.project);
|
|
2375
2506
|
}
|
|
2376
2507
|
/**
|
|
2377
|
-
*
|
|
2378
|
-
*
|
|
2508
|
+
* Generate an activity report for the current project.
|
|
2509
|
+
* Aggregates observations, sessions, summaries and files for a time period.
|
|
2379
2510
|
*/
|
|
2380
2511
|
async generateReport(options) {
|
|
2381
2512
|
const now = /* @__PURE__ */ new Date();
|