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
|
}
|
|
@@ -497,7 +520,7 @@ async function readStdin() {
|
|
|
497
520
|
}
|
|
498
521
|
resolve(JSON.parse(data));
|
|
499
522
|
} catch (err) {
|
|
500
|
-
reject(new Error(`
|
|
523
|
+
reject(new Error(`Error parsing stdin JSON: ${err}`));
|
|
501
524
|
}
|
|
502
525
|
});
|
|
503
526
|
process.stdin.on("error", (err) => {
|
|
@@ -523,17 +546,17 @@ function formatSmartContext(data) {
|
|
|
523
546
|
const budget = data.tokenBudget || parseInt(process.env.KIRO_MEMORY_CONTEXT_TOKENS || "0", 10) || 2e3;
|
|
524
547
|
let output = "";
|
|
525
548
|
let tokensUsed = 0;
|
|
526
|
-
const header = "# Kiro Memory:
|
|
549
|
+
const header = "# Kiro Memory: Previous Sessions Context\n\n";
|
|
527
550
|
tokensUsed += estimateTokens(header);
|
|
528
551
|
output += header;
|
|
529
552
|
if (data.summaries && data.summaries.length > 0) {
|
|
530
|
-
let sumSection = "##
|
|
553
|
+
let sumSection = "## Previous Sessions\n\n";
|
|
531
554
|
for (const sum of data.summaries.slice(0, 3)) {
|
|
532
|
-
if (sum.learned) sumSection += `- **
|
|
555
|
+
if (sum.learned) sumSection += `- **Learned**: ${sum.learned}
|
|
533
556
|
`;
|
|
534
|
-
if (sum.completed) sumSection += `- **
|
|
557
|
+
if (sum.completed) sumSection += `- **Completed**: ${sum.completed}
|
|
535
558
|
`;
|
|
536
|
-
if (sum.next_steps) sumSection += `- **
|
|
559
|
+
if (sum.next_steps) sumSection += `- **Next steps**: ${sum.next_steps}
|
|
537
560
|
`;
|
|
538
561
|
sumSection += "\n";
|
|
539
562
|
}
|
|
@@ -541,7 +564,7 @@ function formatSmartContext(data) {
|
|
|
541
564
|
output += sumSection;
|
|
542
565
|
}
|
|
543
566
|
if (data.items && data.items.length > 0) {
|
|
544
|
-
let obsSection = "##
|
|
567
|
+
let obsSection = "## Relevant Observations\n\n";
|
|
545
568
|
tokensUsed += estimateTokens(obsSection);
|
|
546
569
|
const sorted = [...data.items].sort((a, b) => b.score - a.score);
|
|
547
570
|
for (const item of sorted) {
|
|
@@ -560,7 +583,7 @@ function formatSmartContext(data) {
|
|
|
560
583
|
output += obsSection;
|
|
561
584
|
}
|
|
562
585
|
const footer = `
|
|
563
|
-
>
|
|
586
|
+
> Project: ${data.project} | Items: ${data.items?.length || 0} | Tokens used: ~${tokensUsed}/${budget}
|
|
564
587
|
`;
|
|
565
588
|
output += footer;
|
|
566
589
|
return output;
|
|
@@ -576,11 +599,11 @@ async function runHook(name, handler) {
|
|
|
576
599
|
}
|
|
577
600
|
debugLog(name, "stdin", input);
|
|
578
601
|
await handler(input);
|
|
579
|
-
debugLog(name, "
|
|
602
|
+
debugLog(name, "completed", { success: true });
|
|
580
603
|
process.exit(0);
|
|
581
604
|
} catch (error) {
|
|
582
|
-
debugLog(name, "
|
|
583
|
-
process.stderr.write(`[kiro-memory:${name}]
|
|
605
|
+
debugLog(name, "error", { error: String(error) });
|
|
606
|
+
process.stderr.write(`[kiro-memory:${name}] Error: ${error}
|
|
584
607
|
`);
|
|
585
608
|
process.exit(0);
|
|
586
609
|
}
|
|
@@ -590,14 +613,15 @@ async function runHook(name, handler) {
|
|
|
590
613
|
import BetterSqlite3 from "better-sqlite3";
|
|
591
614
|
var Database = class {
|
|
592
615
|
_db;
|
|
616
|
+
_stmtCache = /* @__PURE__ */ new Map();
|
|
593
617
|
constructor(path, options) {
|
|
594
618
|
this._db = new BetterSqlite3(path, {
|
|
595
|
-
// better-sqlite3
|
|
619
|
+
// better-sqlite3 creates the file by default ('create' not needed)
|
|
596
620
|
readonly: options?.readwrite === false ? true : false
|
|
597
621
|
});
|
|
598
622
|
}
|
|
599
623
|
/**
|
|
600
|
-
*
|
|
624
|
+
* Execute a SQL query without results
|
|
601
625
|
*/
|
|
602
626
|
run(sql, params) {
|
|
603
627
|
const stmt = this._db.prepare(sql);
|
|
@@ -605,51 +629,53 @@ var Database = class {
|
|
|
605
629
|
return result;
|
|
606
630
|
}
|
|
607
631
|
/**
|
|
608
|
-
*
|
|
632
|
+
* Prepare a query with bun:sqlite-compatible interface.
|
|
633
|
+
* Returns a cached prepared statement for repeated queries.
|
|
609
634
|
*/
|
|
610
635
|
query(sql) {
|
|
611
|
-
|
|
636
|
+
let cached = this._stmtCache.get(sql);
|
|
637
|
+
if (!cached) {
|
|
638
|
+
cached = new BunQueryCompat(this._db, sql);
|
|
639
|
+
this._stmtCache.set(sql, cached);
|
|
640
|
+
}
|
|
641
|
+
return cached;
|
|
612
642
|
}
|
|
613
643
|
/**
|
|
614
|
-
*
|
|
644
|
+
* Create a transaction
|
|
615
645
|
*/
|
|
616
646
|
transaction(fn) {
|
|
617
647
|
return this._db.transaction(fn);
|
|
618
648
|
}
|
|
619
649
|
/**
|
|
620
|
-
*
|
|
650
|
+
* Close the connection
|
|
621
651
|
*/
|
|
622
652
|
close() {
|
|
653
|
+
this._stmtCache.clear();
|
|
623
654
|
this._db.close();
|
|
624
655
|
}
|
|
625
656
|
};
|
|
626
657
|
var BunQueryCompat = class {
|
|
627
|
-
|
|
628
|
-
_sql;
|
|
658
|
+
_stmt;
|
|
629
659
|
constructor(db, sql) {
|
|
630
|
-
this.
|
|
631
|
-
this._sql = sql;
|
|
660
|
+
this._stmt = db.prepare(sql);
|
|
632
661
|
}
|
|
633
662
|
/**
|
|
634
|
-
*
|
|
663
|
+
* Returns all rows
|
|
635
664
|
*/
|
|
636
665
|
all(...params) {
|
|
637
|
-
|
|
638
|
-
return params.length > 0 ? stmt.all(...params) : stmt.all();
|
|
666
|
+
return params.length > 0 ? this._stmt.all(...params) : this._stmt.all();
|
|
639
667
|
}
|
|
640
668
|
/**
|
|
641
|
-
*
|
|
669
|
+
* Returns the first row or null
|
|
642
670
|
*/
|
|
643
671
|
get(...params) {
|
|
644
|
-
|
|
645
|
-
return params.length > 0 ? stmt.get(...params) : stmt.get();
|
|
672
|
+
return params.length > 0 ? this._stmt.get(...params) : this._stmt.get();
|
|
646
673
|
}
|
|
647
674
|
/**
|
|
648
|
-
*
|
|
675
|
+
* Execute without results
|
|
649
676
|
*/
|
|
650
677
|
run(...params) {
|
|
651
|
-
|
|
652
|
-
return params.length > 0 ? stmt.run(...params) : stmt.run();
|
|
678
|
+
return params.length > 0 ? this._stmt.run(...params) : this._stmt.run();
|
|
653
679
|
}
|
|
654
680
|
};
|
|
655
681
|
|
|
@@ -911,40 +937,62 @@ function ensureDir(dirPath) {
|
|
|
911
937
|
var SQLITE_MMAP_SIZE_BYTES = 256 * 1024 * 1024;
|
|
912
938
|
var SQLITE_CACHE_SIZE_PAGES = 1e4;
|
|
913
939
|
var KiroMemoryDatabase = class {
|
|
914
|
-
|
|
940
|
+
_db;
|
|
915
941
|
/**
|
|
916
|
-
*
|
|
917
|
-
*
|
|
942
|
+
* Readonly accessor for the underlying Database instance.
|
|
943
|
+
* Prefer using query() and run() proxy methods directly.
|
|
944
|
+
*/
|
|
945
|
+
get db() {
|
|
946
|
+
return this._db;
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* @param dbPath - Path to the SQLite file (default: DB_PATH)
|
|
950
|
+
* @param skipMigrations - If true, skip the migration runner (for high-frequency hooks)
|
|
918
951
|
*/
|
|
919
952
|
constructor(dbPath = DB_PATH, skipMigrations = false) {
|
|
920
953
|
if (dbPath !== ":memory:") {
|
|
921
954
|
ensureDir(DATA_DIR2);
|
|
922
955
|
}
|
|
923
|
-
this.
|
|
924
|
-
this.
|
|
925
|
-
this.
|
|
926
|
-
this.
|
|
927
|
-
this.
|
|
928
|
-
this.
|
|
929
|
-
this.
|
|
956
|
+
this._db = new Database(dbPath, { create: true, readwrite: true });
|
|
957
|
+
this._db.run("PRAGMA journal_mode = WAL");
|
|
958
|
+
this._db.run("PRAGMA busy_timeout = 5000");
|
|
959
|
+
this._db.run("PRAGMA synchronous = NORMAL");
|
|
960
|
+
this._db.run("PRAGMA foreign_keys = ON");
|
|
961
|
+
this._db.run("PRAGMA temp_store = memory");
|
|
962
|
+
this._db.run(`PRAGMA mmap_size = ${SQLITE_MMAP_SIZE_BYTES}`);
|
|
963
|
+
this._db.run(`PRAGMA cache_size = ${SQLITE_CACHE_SIZE_PAGES}`);
|
|
930
964
|
if (!skipMigrations) {
|
|
931
|
-
const migrationRunner = new MigrationRunner(this.
|
|
965
|
+
const migrationRunner = new MigrationRunner(this._db);
|
|
932
966
|
migrationRunner.runAllMigrations();
|
|
933
967
|
}
|
|
934
968
|
}
|
|
935
969
|
/**
|
|
936
|
-
*
|
|
937
|
-
*
|
|
970
|
+
* Prepare a query (delegates to underlying Database).
|
|
971
|
+
* Proxy method to avoid ctx.db.db.query() double access.
|
|
972
|
+
*/
|
|
973
|
+
query(sql) {
|
|
974
|
+
return this._db.query(sql);
|
|
975
|
+
}
|
|
976
|
+
/**
|
|
977
|
+
* Execute a SQL statement without results (delegates to underlying Database).
|
|
978
|
+
* Proxy method to avoid ctx.db.db.run() double access.
|
|
979
|
+
*/
|
|
980
|
+
run(sql, params) {
|
|
981
|
+
return this._db.run(sql, params);
|
|
982
|
+
}
|
|
983
|
+
/**
|
|
984
|
+
* Executes a function within an atomic transaction.
|
|
985
|
+
* If fn() throws an error, the transaction is automatically rolled back.
|
|
938
986
|
*/
|
|
939
987
|
withTransaction(fn) {
|
|
940
|
-
const transaction = this.
|
|
941
|
-
return transaction(this.
|
|
988
|
+
const transaction = this._db.transaction(fn);
|
|
989
|
+
return transaction(this._db);
|
|
942
990
|
}
|
|
943
991
|
/**
|
|
944
992
|
* Close the database connection
|
|
945
993
|
*/
|
|
946
994
|
close() {
|
|
947
|
-
this.
|
|
995
|
+
this._db.close();
|
|
948
996
|
}
|
|
949
997
|
};
|
|
950
998
|
var MigrationRunner = class {
|
|
@@ -1439,8 +1487,8 @@ var EmbeddingService = class {
|
|
|
1439
1487
|
initialized = false;
|
|
1440
1488
|
initializing = null;
|
|
1441
1489
|
/**
|
|
1442
|
-
*
|
|
1443
|
-
*
|
|
1490
|
+
* Initialize the embedding service.
|
|
1491
|
+
* Tries fastembed, then @huggingface/transformers, then fallback to null.
|
|
1444
1492
|
*/
|
|
1445
1493
|
async initialize() {
|
|
1446
1494
|
if (this.initialized) return this.provider !== null;
|
|
@@ -1461,11 +1509,11 @@ var EmbeddingService = class {
|
|
|
1461
1509
|
});
|
|
1462
1510
|
this.provider = "fastembed";
|
|
1463
1511
|
this.initialized = true;
|
|
1464
|
-
logger.info("EMBEDDING", "
|
|
1512
|
+
logger.info("EMBEDDING", "Initialized with fastembed (BGE-small-en-v1.5)");
|
|
1465
1513
|
return true;
|
|
1466
1514
|
}
|
|
1467
1515
|
} catch (error) {
|
|
1468
|
-
logger.debug("EMBEDDING", `fastembed
|
|
1516
|
+
logger.debug("EMBEDDING", `fastembed not available: ${error}`);
|
|
1469
1517
|
}
|
|
1470
1518
|
try {
|
|
1471
1519
|
const transformers = await import("@huggingface/transformers");
|
|
@@ -1476,20 +1524,20 @@ var EmbeddingService = class {
|
|
|
1476
1524
|
});
|
|
1477
1525
|
this.provider = "transformers";
|
|
1478
1526
|
this.initialized = true;
|
|
1479
|
-
logger.info("EMBEDDING", "
|
|
1527
|
+
logger.info("EMBEDDING", "Initialized with @huggingface/transformers (all-MiniLM-L6-v2)");
|
|
1480
1528
|
return true;
|
|
1481
1529
|
}
|
|
1482
1530
|
} catch (error) {
|
|
1483
|
-
logger.debug("EMBEDDING", `@huggingface/transformers
|
|
1531
|
+
logger.debug("EMBEDDING", `@huggingface/transformers not available: ${error}`);
|
|
1484
1532
|
}
|
|
1485
1533
|
this.provider = null;
|
|
1486
1534
|
this.initialized = true;
|
|
1487
|
-
logger.warn("EMBEDDING", "
|
|
1535
|
+
logger.warn("EMBEDDING", "No embedding provider available, semantic search disabled");
|
|
1488
1536
|
return false;
|
|
1489
1537
|
}
|
|
1490
1538
|
/**
|
|
1491
|
-
*
|
|
1492
|
-
*
|
|
1539
|
+
* Generate embedding for a single text.
|
|
1540
|
+
* Returns Float32Array with 384 dimensions, or null if not available.
|
|
1493
1541
|
*/
|
|
1494
1542
|
async embed(text) {
|
|
1495
1543
|
if (!this.initialized) await this.initialize();
|
|
@@ -1502,46 +1550,111 @@ var EmbeddingService = class {
|
|
|
1502
1550
|
return await this._embedTransformers(truncated);
|
|
1503
1551
|
}
|
|
1504
1552
|
} catch (error) {
|
|
1505
|
-
logger.error("EMBEDDING", `
|
|
1553
|
+
logger.error("EMBEDDING", `Error generating embedding: ${error}`);
|
|
1506
1554
|
}
|
|
1507
1555
|
return null;
|
|
1508
1556
|
}
|
|
1509
1557
|
/**
|
|
1510
|
-
*
|
|
1558
|
+
* Generate embeddings in batch.
|
|
1559
|
+
* Uses native batch support when available (fastembed, transformers),
|
|
1560
|
+
* falls back to serial processing on batch failure.
|
|
1511
1561
|
*/
|
|
1512
1562
|
async embedBatch(texts) {
|
|
1513
1563
|
if (!this.initialized) await this.initialize();
|
|
1514
1564
|
if (!this.provider || !this.model) return texts.map(() => null);
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
}
|
|
1521
|
-
|
|
1565
|
+
if (texts.length === 0) return [];
|
|
1566
|
+
const truncated = texts.map((t) => t.substring(0, 2e3));
|
|
1567
|
+
try {
|
|
1568
|
+
if (this.provider === "fastembed") {
|
|
1569
|
+
return await this._embedBatchFastembed(truncated);
|
|
1570
|
+
} else if (this.provider === "transformers") {
|
|
1571
|
+
return await this._embedBatchTransformers(truncated);
|
|
1522
1572
|
}
|
|
1573
|
+
} catch (error) {
|
|
1574
|
+
logger.warn("EMBEDDING", `Batch embedding failed, falling back to serial: ${error}`);
|
|
1523
1575
|
}
|
|
1524
|
-
return
|
|
1576
|
+
return this._embedBatchSerial(truncated);
|
|
1525
1577
|
}
|
|
1526
1578
|
/**
|
|
1527
|
-
*
|
|
1579
|
+
* Check if the service is available.
|
|
1528
1580
|
*/
|
|
1529
1581
|
isAvailable() {
|
|
1530
1582
|
return this.initialized && this.provider !== null;
|
|
1531
1583
|
}
|
|
1532
1584
|
/**
|
|
1533
|
-
*
|
|
1585
|
+
* Name of the active provider.
|
|
1534
1586
|
*/
|
|
1535
1587
|
getProvider() {
|
|
1536
1588
|
return this.provider;
|
|
1537
1589
|
}
|
|
1538
1590
|
/**
|
|
1539
|
-
*
|
|
1591
|
+
* Embedding vector dimensions.
|
|
1540
1592
|
*/
|
|
1541
1593
|
getDimensions() {
|
|
1542
1594
|
return 384;
|
|
1543
1595
|
}
|
|
1544
|
-
// ---
|
|
1596
|
+
// --- Batch implementations ---
|
|
1597
|
+
/**
|
|
1598
|
+
* Native batch embedding with fastembed.
|
|
1599
|
+
* FlagEmbedding.embed() accepts string[] and returns an async iterable of batches.
|
|
1600
|
+
*/
|
|
1601
|
+
async _embedBatchFastembed(texts) {
|
|
1602
|
+
const results = [];
|
|
1603
|
+
const embeddings = this.model.embed(texts, texts.length);
|
|
1604
|
+
for await (const batch of embeddings) {
|
|
1605
|
+
if (batch) {
|
|
1606
|
+
for (const vec of batch) {
|
|
1607
|
+
results.push(vec instanceof Float32Array ? vec : new Float32Array(vec));
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
while (results.length < texts.length) {
|
|
1612
|
+
results.push(null);
|
|
1613
|
+
}
|
|
1614
|
+
return results;
|
|
1615
|
+
}
|
|
1616
|
+
/**
|
|
1617
|
+
* Batch embedding with @huggingface/transformers pipeline.
|
|
1618
|
+
* The pipeline accepts string[] and returns a Tensor with shape [N, dims].
|
|
1619
|
+
*/
|
|
1620
|
+
async _embedBatchTransformers(texts) {
|
|
1621
|
+
const output = await this.model(texts, {
|
|
1622
|
+
pooling: "mean",
|
|
1623
|
+
normalize: true
|
|
1624
|
+
});
|
|
1625
|
+
if (!output?.data) {
|
|
1626
|
+
return texts.map(() => null);
|
|
1627
|
+
}
|
|
1628
|
+
const dims = this.getDimensions();
|
|
1629
|
+
const data = output.data instanceof Float32Array ? output.data : new Float32Array(output.data);
|
|
1630
|
+
const results = [];
|
|
1631
|
+
for (let i = 0; i < texts.length; i++) {
|
|
1632
|
+
const offset = i * dims;
|
|
1633
|
+
if (offset + dims <= data.length) {
|
|
1634
|
+
results.push(data.slice(offset, offset + dims));
|
|
1635
|
+
} else {
|
|
1636
|
+
results.push(null);
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
return results;
|
|
1640
|
+
}
|
|
1641
|
+
/**
|
|
1642
|
+
* Serial fallback: embed texts one at a time.
|
|
1643
|
+
* Used when native batch fails.
|
|
1644
|
+
*/
|
|
1645
|
+
async _embedBatchSerial(texts) {
|
|
1646
|
+
const results = [];
|
|
1647
|
+
for (const text of texts) {
|
|
1648
|
+
try {
|
|
1649
|
+
const embedding = await this.embed(text);
|
|
1650
|
+
results.push(embedding);
|
|
1651
|
+
} catch {
|
|
1652
|
+
results.push(null);
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
return results;
|
|
1656
|
+
}
|
|
1657
|
+
// --- Single-text provider implementations ---
|
|
1545
1658
|
async _embedFastembed(text) {
|
|
1546
1659
|
const embeddings = this.model.embed([text], 1);
|
|
1547
1660
|
for await (const batch of embeddings) {
|
|
@@ -1572,17 +1685,21 @@ function getEmbeddingService() {
|
|
|
1572
1685
|
}
|
|
1573
1686
|
|
|
1574
1687
|
// src/services/search/VectorSearch.ts
|
|
1688
|
+
var DEFAULT_MAX_CANDIDATES = 2e3;
|
|
1575
1689
|
function cosineSimilarity(a, b) {
|
|
1576
|
-
|
|
1690
|
+
const len = a.length;
|
|
1691
|
+
if (len !== b.length) return 0;
|
|
1577
1692
|
let dotProduct = 0;
|
|
1578
1693
|
let normA = 0;
|
|
1579
1694
|
let normB = 0;
|
|
1580
|
-
for (let i = 0; i <
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1695
|
+
for (let i = 0; i < len; i++) {
|
|
1696
|
+
const ai = a[i];
|
|
1697
|
+
const bi = b[i];
|
|
1698
|
+
dotProduct += ai * bi;
|
|
1699
|
+
normA += ai * ai;
|
|
1700
|
+
normB += bi * bi;
|
|
1701
|
+
}
|
|
1702
|
+
const denominator = Math.sqrt(normA * normB);
|
|
1586
1703
|
if (denominator === 0) return 0;
|
|
1587
1704
|
return dotProduct / denominator;
|
|
1588
1705
|
}
|
|
@@ -1595,23 +1712,36 @@ function bufferToFloat32(buf) {
|
|
|
1595
1712
|
}
|
|
1596
1713
|
var VectorSearch = class {
|
|
1597
1714
|
/**
|
|
1598
|
-
*
|
|
1715
|
+
* Semantic search with SQL pre-filtering for scalability.
|
|
1716
|
+
*
|
|
1717
|
+
* 2-phase strategy:
|
|
1718
|
+
* 1. SQL pre-filters by project + sorts by recency (loads max N candidates)
|
|
1719
|
+
* 2. JS computes cosine similarity only on filtered candidates
|
|
1720
|
+
*
|
|
1721
|
+
* With 50k observations and maxCandidates=2000, loads only ~4% of data.
|
|
1599
1722
|
*/
|
|
1600
1723
|
async search(db, queryEmbedding, options = {}) {
|
|
1601
1724
|
const limit = options.limit || 10;
|
|
1602
1725
|
const threshold = options.threshold || 0.3;
|
|
1726
|
+
const maxCandidates = options.maxCandidates || DEFAULT_MAX_CANDIDATES;
|
|
1603
1727
|
try {
|
|
1604
|
-
|
|
1728
|
+
const conditions = [];
|
|
1729
|
+
const params = [];
|
|
1730
|
+
if (options.project) {
|
|
1731
|
+
conditions.push("o.project = ?");
|
|
1732
|
+
params.push(options.project);
|
|
1733
|
+
}
|
|
1734
|
+
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
1735
|
+
const sql = `
|
|
1605
1736
|
SELECT e.observation_id, e.embedding,
|
|
1606
1737
|
o.title, o.text, o.type, o.project, o.created_at, o.created_at_epoch
|
|
1607
1738
|
FROM observation_embeddings e
|
|
1608
1739
|
JOIN observations o ON o.id = e.observation_id
|
|
1740
|
+
${whereClause}
|
|
1741
|
+
ORDER BY o.created_at_epoch DESC
|
|
1742
|
+
LIMIT ?
|
|
1609
1743
|
`;
|
|
1610
|
-
|
|
1611
|
-
if (options.project) {
|
|
1612
|
-
sql += " WHERE o.project = ?";
|
|
1613
|
-
params.push(options.project);
|
|
1614
|
-
}
|
|
1744
|
+
params.push(maxCandidates);
|
|
1615
1745
|
const rows = db.query(sql).all(...params);
|
|
1616
1746
|
const scored = [];
|
|
1617
1747
|
for (const row of rows) {
|
|
@@ -1632,14 +1762,15 @@ var VectorSearch = class {
|
|
|
1632
1762
|
}
|
|
1633
1763
|
}
|
|
1634
1764
|
scored.sort((a, b) => b.similarity - a.similarity);
|
|
1765
|
+
logger.debug("VECTOR", `Search: ${rows.length} candidates \u2192 ${scored.length} above threshold \u2192 ${Math.min(scored.length, limit)} results`);
|
|
1635
1766
|
return scored.slice(0, limit);
|
|
1636
1767
|
} catch (error) {
|
|
1637
|
-
logger.error("VECTOR", `
|
|
1768
|
+
logger.error("VECTOR", `Vector search error: ${error}`);
|
|
1638
1769
|
return [];
|
|
1639
1770
|
}
|
|
1640
1771
|
}
|
|
1641
1772
|
/**
|
|
1642
|
-
*
|
|
1773
|
+
* Store embedding for an observation.
|
|
1643
1774
|
*/
|
|
1644
1775
|
async storeEmbedding(db, observationId, embedding, model) {
|
|
1645
1776
|
try {
|
|
@@ -1655,18 +1786,18 @@ var VectorSearch = class {
|
|
|
1655
1786
|
embedding.length,
|
|
1656
1787
|
(/* @__PURE__ */ new Date()).toISOString()
|
|
1657
1788
|
);
|
|
1658
|
-
logger.debug("VECTOR", `Embedding
|
|
1789
|
+
logger.debug("VECTOR", `Embedding saved for observation ${observationId}`);
|
|
1659
1790
|
} catch (error) {
|
|
1660
|
-
logger.error("VECTOR", `
|
|
1791
|
+
logger.error("VECTOR", `Error saving embedding: ${error}`);
|
|
1661
1792
|
}
|
|
1662
1793
|
}
|
|
1663
1794
|
/**
|
|
1664
|
-
*
|
|
1795
|
+
* Generate embeddings for observations that don't have them yet.
|
|
1665
1796
|
*/
|
|
1666
1797
|
async backfillEmbeddings(db, batchSize = 50) {
|
|
1667
1798
|
const embeddingService2 = getEmbeddingService();
|
|
1668
1799
|
if (!await embeddingService2.initialize()) {
|
|
1669
|
-
logger.warn("VECTOR", "Embedding service
|
|
1800
|
+
logger.warn("VECTOR", "Embedding service not available, backfill skipped");
|
|
1670
1801
|
return 0;
|
|
1671
1802
|
}
|
|
1672
1803
|
const rows = db.query(`
|
|
@@ -1692,11 +1823,11 @@ var VectorSearch = class {
|
|
|
1692
1823
|
count++;
|
|
1693
1824
|
}
|
|
1694
1825
|
}
|
|
1695
|
-
logger.info("VECTOR", `Backfill
|
|
1826
|
+
logger.info("VECTOR", `Backfill completed: ${count}/${rows.length} embeddings generated`);
|
|
1696
1827
|
return count;
|
|
1697
1828
|
}
|
|
1698
1829
|
/**
|
|
1699
|
-
*
|
|
1830
|
+
* Embedding statistics.
|
|
1700
1831
|
*/
|
|
1701
1832
|
getStats(db) {
|
|
1702
1833
|
try {
|
|
@@ -1723,21 +1854,21 @@ function getVectorSearch() {
|
|
|
1723
1854
|
var HybridSearch = class {
|
|
1724
1855
|
embeddingInitialized = false;
|
|
1725
1856
|
/**
|
|
1726
|
-
*
|
|
1857
|
+
* Initialize the embedding service (lazy, non-blocking)
|
|
1727
1858
|
*/
|
|
1728
1859
|
async initialize() {
|
|
1729
1860
|
try {
|
|
1730
1861
|
const embeddingService2 = getEmbeddingService();
|
|
1731
1862
|
await embeddingService2.initialize();
|
|
1732
1863
|
this.embeddingInitialized = embeddingService2.isAvailable();
|
|
1733
|
-
logger.info("SEARCH", `HybridSearch
|
|
1864
|
+
logger.info("SEARCH", `HybridSearch initialized (embedding: ${this.embeddingInitialized ? "active" : "disabled"})`);
|
|
1734
1865
|
} catch (error) {
|
|
1735
|
-
logger.warn("SEARCH", "
|
|
1866
|
+
logger.warn("SEARCH", "Embedding initialization failed, using only FTS5", {}, error);
|
|
1736
1867
|
this.embeddingInitialized = false;
|
|
1737
1868
|
}
|
|
1738
1869
|
}
|
|
1739
1870
|
/**
|
|
1740
|
-
*
|
|
1871
|
+
* Hybrid search with 4-signal scoring
|
|
1741
1872
|
*/
|
|
1742
1873
|
async search(db, query, options = {}) {
|
|
1743
1874
|
const limit = options.limit || 10;
|
|
@@ -1753,7 +1884,7 @@ var HybridSearch = class {
|
|
|
1753
1884
|
const vectorResults = await vectorSearch2.search(db, queryEmbedding, {
|
|
1754
1885
|
project: options.project,
|
|
1755
1886
|
limit: limit * 2,
|
|
1756
|
-
//
|
|
1887
|
+
// Fetch more results for ranking
|
|
1757
1888
|
threshold: 0.3
|
|
1758
1889
|
});
|
|
1759
1890
|
for (const hit of vectorResults) {
|
|
@@ -1770,10 +1901,10 @@ var HybridSearch = class {
|
|
|
1770
1901
|
source: "vector"
|
|
1771
1902
|
});
|
|
1772
1903
|
}
|
|
1773
|
-
logger.debug("SEARCH", `Vector search: ${vectorResults.length}
|
|
1904
|
+
logger.debug("SEARCH", `Vector search: ${vectorResults.length} results`);
|
|
1774
1905
|
}
|
|
1775
1906
|
} catch (error) {
|
|
1776
|
-
logger.warn("SEARCH", "
|
|
1907
|
+
logger.warn("SEARCH", "Vector search failed, using only keyword", {}, error);
|
|
1777
1908
|
}
|
|
1778
1909
|
}
|
|
1779
1910
|
try {
|
|
@@ -1803,9 +1934,9 @@ var HybridSearch = class {
|
|
|
1803
1934
|
});
|
|
1804
1935
|
}
|
|
1805
1936
|
}
|
|
1806
|
-
logger.debug("SEARCH", `Keyword search: ${keywordResults.length}
|
|
1937
|
+
logger.debug("SEARCH", `Keyword search: ${keywordResults.length} results`);
|
|
1807
1938
|
} catch (error) {
|
|
1808
|
-
logger.error("SEARCH", "
|
|
1939
|
+
logger.error("SEARCH", "Keyword search failed", {}, error);
|
|
1809
1940
|
}
|
|
1810
1941
|
if (rawItems.size === 0) return [];
|
|
1811
1942
|
const allFTS5Ranks = Array.from(rawItems.values()).filter((item) => item.fts5Rank !== null).map((item) => item.fts5Rank);
|
|
@@ -1893,33 +2024,33 @@ var KiroMemorySDK = class {
|
|
|
1893
2024
|
};
|
|
1894
2025
|
}
|
|
1895
2026
|
/**
|
|
1896
|
-
*
|
|
2027
|
+
* Validate input for storeObservation
|
|
1897
2028
|
*/
|
|
1898
2029
|
validateObservationInput(data) {
|
|
1899
2030
|
if (!data.type || typeof data.type !== "string" || data.type.length > 100) {
|
|
1900
|
-
throw new Error("type
|
|
2031
|
+
throw new Error("type is required (string, max 100 chars)");
|
|
1901
2032
|
}
|
|
1902
2033
|
if (!data.title || typeof data.title !== "string" || data.title.length > 500) {
|
|
1903
|
-
throw new Error("title
|
|
2034
|
+
throw new Error("title is required (string, max 500 chars)");
|
|
1904
2035
|
}
|
|
1905
2036
|
if (!data.content || typeof data.content !== "string" || data.content.length > 1e5) {
|
|
1906
|
-
throw new Error("content
|
|
2037
|
+
throw new Error("content is required (string, max 100KB)");
|
|
1907
2038
|
}
|
|
1908
2039
|
}
|
|
1909
2040
|
/**
|
|
1910
|
-
*
|
|
2041
|
+
* Validate input for storeSummary
|
|
1911
2042
|
*/
|
|
1912
2043
|
validateSummaryInput(data) {
|
|
1913
2044
|
const MAX = 5e4;
|
|
1914
2045
|
for (const [key, val] of Object.entries(data)) {
|
|
1915
2046
|
if (val !== void 0 && val !== null) {
|
|
1916
|
-
if (typeof val !== "string") throw new Error(`${key}
|
|
1917
|
-
if (val.length > MAX) throw new Error(`${key}
|
|
2047
|
+
if (typeof val !== "string") throw new Error(`${key} must be a string`);
|
|
2048
|
+
if (val.length > MAX) throw new Error(`${key} too large (max 50KB)`);
|
|
1918
2049
|
}
|
|
1919
2050
|
}
|
|
1920
2051
|
}
|
|
1921
2052
|
/**
|
|
1922
|
-
*
|
|
2053
|
+
* Generate and store embedding for an observation (fire-and-forget, non-blocking)
|
|
1923
2054
|
*/
|
|
1924
2055
|
async generateEmbeddingAsync(observationId, title, content, concepts) {
|
|
1925
2056
|
try {
|
|
@@ -1939,39 +2070,39 @@ var KiroMemorySDK = class {
|
|
|
1939
2070
|
);
|
|
1940
2071
|
}
|
|
1941
2072
|
} catch (error) {
|
|
1942
|
-
logger.debug("SDK", `Embedding generation
|
|
2073
|
+
logger.debug("SDK", `Embedding generation failed for obs ${observationId}: ${error}`);
|
|
1943
2074
|
}
|
|
1944
2075
|
}
|
|
1945
2076
|
/**
|
|
1946
|
-
*
|
|
1947
|
-
*
|
|
1948
|
-
*
|
|
2077
|
+
* Generate SHA256 content hash for content-based deduplication.
|
|
2078
|
+
* Uses (project + type + title + narrative) as semantic identity tuple.
|
|
2079
|
+
* Does NOT include sessionId since it's unique per invocation.
|
|
1949
2080
|
*/
|
|
1950
2081
|
generateContentHash(type, title, narrative) {
|
|
1951
2082
|
const payload = `${this.project}|${type}|${title}|${narrative || ""}`;
|
|
1952
2083
|
return createHash("sha256").update(payload).digest("hex");
|
|
1953
2084
|
}
|
|
1954
2085
|
/**
|
|
1955
|
-
*
|
|
1956
|
-
*
|
|
2086
|
+
* Deduplication windows per type (ms).
|
|
2087
|
+
* Types with many repetitions have wider windows.
|
|
1957
2088
|
*/
|
|
1958
2089
|
getDeduplicationWindow(type) {
|
|
1959
2090
|
switch (type) {
|
|
1960
2091
|
case "file-read":
|
|
1961
2092
|
return 6e4;
|
|
1962
|
-
// 60s —
|
|
2093
|
+
// 60s — frequent reads on the same files
|
|
1963
2094
|
case "file-write":
|
|
1964
2095
|
return 1e4;
|
|
1965
|
-
// 10s —
|
|
2096
|
+
// 10s — rapid consecutive writes
|
|
1966
2097
|
case "command":
|
|
1967
2098
|
return 3e4;
|
|
1968
2099
|
// 30s — standard
|
|
1969
2100
|
case "research":
|
|
1970
2101
|
return 12e4;
|
|
1971
|
-
// 120s — web search
|
|
2102
|
+
// 120s — repeated web search and fetch
|
|
1972
2103
|
case "delegation":
|
|
1973
2104
|
return 6e4;
|
|
1974
|
-
// 60s —
|
|
2105
|
+
// 60s — rapid delegations
|
|
1975
2106
|
default:
|
|
1976
2107
|
return 3e4;
|
|
1977
2108
|
}
|
|
@@ -1985,7 +2116,7 @@ var KiroMemorySDK = class {
|
|
|
1985
2116
|
const contentHash = this.generateContentHash(data.type, data.title, data.narrative);
|
|
1986
2117
|
const dedupWindow = this.getDeduplicationWindow(data.type);
|
|
1987
2118
|
if (isDuplicateObservation(this.db.db, contentHash, dedupWindow)) {
|
|
1988
|
-
logger.debug("SDK", `
|
|
2119
|
+
logger.debug("SDK", `Duplicate observation discarded (${data.type}, ${dedupWindow}ms): ${data.title}`);
|
|
1989
2120
|
return -1;
|
|
1990
2121
|
}
|
|
1991
2122
|
const filesRead = data.filesRead || (data.type === "file-read" ? data.files : void 0);
|
|
@@ -2013,12 +2144,12 @@ var KiroMemorySDK = class {
|
|
|
2013
2144
|
return observationId;
|
|
2014
2145
|
}
|
|
2015
2146
|
/**
|
|
2016
|
-
*
|
|
2017
|
-
*
|
|
2147
|
+
* Store structured knowledge (constraint, decision, heuristic, rejected).
|
|
2148
|
+
* Uses the `type` field for knowledgeType and `facts` for JSON metadata.
|
|
2018
2149
|
*/
|
|
2019
2150
|
async storeKnowledge(data) {
|
|
2020
2151
|
if (!KNOWLEDGE_TYPES.includes(data.knowledgeType)) {
|
|
2021
|
-
throw new Error(`knowledgeType
|
|
2152
|
+
throw new Error(`Invalid knowledgeType: ${data.knowledgeType}. Allowed values: ${KNOWLEDGE_TYPES.join(", ")}`);
|
|
2022
2153
|
}
|
|
2023
2154
|
this.validateObservationInput({ type: data.knowledgeType, title: data.title, content: data.content });
|
|
2024
2155
|
const metadata = (() => {
|
|
@@ -2050,9 +2181,9 @@ var KiroMemorySDK = class {
|
|
|
2050
2181
|
}
|
|
2051
2182
|
})();
|
|
2052
2183
|
const sessionId = "sdk-" + Date.now();
|
|
2053
|
-
const contentHash = this.generateContentHash(data.
|
|
2184
|
+
const contentHash = this.generateContentHash(data.knowledgeType, data.title);
|
|
2054
2185
|
if (isDuplicateObservation(this.db.db, contentHash)) {
|
|
2055
|
-
logger.debug("SDK", `
|
|
2186
|
+
logger.debug("SDK", `Duplicate knowledge discarded: ${data.title}`);
|
|
2056
2187
|
return -1;
|
|
2057
2188
|
}
|
|
2058
2189
|
const discoveryTokens = Math.ceil(data.content.length / 4);
|
|
@@ -2069,11 +2200,11 @@ var KiroMemorySDK = class {
|
|
|
2069
2200
|
null,
|
|
2070
2201
|
// narrative
|
|
2071
2202
|
JSON.stringify(metadata),
|
|
2072
|
-
// facts =
|
|
2203
|
+
// facts = JSON metadata
|
|
2073
2204
|
data.concepts?.join(", ") || null,
|
|
2074
2205
|
data.files?.join(", ") || null,
|
|
2075
2206
|
null,
|
|
2076
|
-
// filesModified: knowledge
|
|
2207
|
+
// filesModified: knowledge doesn't modify files
|
|
2077
2208
|
0,
|
|
2078
2209
|
// prompt_number
|
|
2079
2210
|
contentHash,
|
|
@@ -2184,8 +2315,8 @@ var KiroMemorySDK = class {
|
|
|
2184
2315
|
return this.project;
|
|
2185
2316
|
}
|
|
2186
2317
|
/**
|
|
2187
|
-
*
|
|
2188
|
-
*
|
|
2318
|
+
* Hybrid search: vector search + keyword FTS5
|
|
2319
|
+
* Requires HybridSearch initialization (embedding service)
|
|
2189
2320
|
*/
|
|
2190
2321
|
async hybridSearch(query, options = {}) {
|
|
2191
2322
|
const hybridSearch2 = getHybridSearch();
|
|
@@ -2195,8 +2326,8 @@ var KiroMemorySDK = class {
|
|
|
2195
2326
|
});
|
|
2196
2327
|
}
|
|
2197
2328
|
/**
|
|
2198
|
-
*
|
|
2199
|
-
*
|
|
2329
|
+
* Semantic-only search (vector search)
|
|
2330
|
+
* Returns results based on cosine similarity with embeddings
|
|
2200
2331
|
*/
|
|
2201
2332
|
async semanticSearch(query, options = {}) {
|
|
2202
2333
|
const embeddingService2 = getEmbeddingService();
|
|
@@ -2231,21 +2362,21 @@ var KiroMemorySDK = class {
|
|
|
2231
2362
|
}));
|
|
2232
2363
|
}
|
|
2233
2364
|
/**
|
|
2234
|
-
*
|
|
2365
|
+
* Generate embeddings for observations that don't have them yet
|
|
2235
2366
|
*/
|
|
2236
2367
|
async backfillEmbeddings(batchSize = 50) {
|
|
2237
2368
|
const vectorSearch2 = getVectorSearch();
|
|
2238
2369
|
return vectorSearch2.backfillEmbeddings(this.db.db, batchSize);
|
|
2239
2370
|
}
|
|
2240
2371
|
/**
|
|
2241
|
-
*
|
|
2372
|
+
* Embedding statistics in the database
|
|
2242
2373
|
*/
|
|
2243
2374
|
getEmbeddingStats() {
|
|
2244
2375
|
const vectorSearch2 = getVectorSearch();
|
|
2245
2376
|
return vectorSearch2.getStats(this.db.db);
|
|
2246
2377
|
}
|
|
2247
2378
|
/**
|
|
2248
|
-
*
|
|
2379
|
+
* Initialize the embedding service (lazy, call before hybridSearch)
|
|
2249
2380
|
*/
|
|
2250
2381
|
async initializeEmbeddings() {
|
|
2251
2382
|
const hybridSearch2 = getHybridSearch();
|
|
@@ -2253,10 +2384,10 @@ var KiroMemorySDK = class {
|
|
|
2253
2384
|
return getEmbeddingService().isAvailable();
|
|
2254
2385
|
}
|
|
2255
2386
|
/**
|
|
2256
|
-
*
|
|
2387
|
+
* Smart context with 4-signal ranking and token budget.
|
|
2257
2388
|
*
|
|
2258
|
-
*
|
|
2259
|
-
*
|
|
2389
|
+
* If query present: uses HybridSearch with SEARCH_WEIGHTS.
|
|
2390
|
+
* If no query: ranking by recency + project match (CONTEXT_WEIGHTS).
|
|
2260
2391
|
*/
|
|
2261
2392
|
async getSmartContext(options = {}) {
|
|
2262
2393
|
const tokenBudget = options.tokenBudget || parseInt(process.env.KIRO_MEMORY_CONTEXT_TOKENS || "0", 10) || 2e3;
|
|
@@ -2330,8 +2461,8 @@ var KiroMemorySDK = class {
|
|
|
2330
2461
|
};
|
|
2331
2462
|
}
|
|
2332
2463
|
/**
|
|
2333
|
-
*
|
|
2334
|
-
*
|
|
2464
|
+
* Detect stale observations (files modified after creation) and mark them in DB.
|
|
2465
|
+
* Returns the number of observations marked as stale.
|
|
2335
2466
|
*/
|
|
2336
2467
|
async detectStaleObservations() {
|
|
2337
2468
|
const staleObs = getStaleObservations(this.db.db, this.project);
|
|
@@ -2342,14 +2473,14 @@ var KiroMemorySDK = class {
|
|
|
2342
2473
|
return staleObs.length;
|
|
2343
2474
|
}
|
|
2344
2475
|
/**
|
|
2345
|
-
*
|
|
2346
|
-
*
|
|
2476
|
+
* Consolidate duplicate observations on the same file and type.
|
|
2477
|
+
* Groups by (project, type, files_modified), keeps the most recent.
|
|
2347
2478
|
*/
|
|
2348
2479
|
async consolidateObservations(options = {}) {
|
|
2349
2480
|
return consolidateObservations(this.db.db, this.project, options);
|
|
2350
2481
|
}
|
|
2351
2482
|
/**
|
|
2352
|
-
*
|
|
2483
|
+
* Decay statistics: total, stale, never accessed, recently accessed.
|
|
2353
2484
|
*/
|
|
2354
2485
|
async getDecayStats() {
|
|
2355
2486
|
const total = this.db.db.query(
|
|
@@ -2368,8 +2499,8 @@ var KiroMemorySDK = class {
|
|
|
2368
2499
|
return { total, stale, neverAccessed, recentlyAccessed };
|
|
2369
2500
|
}
|
|
2370
2501
|
/**
|
|
2371
|
-
*
|
|
2372
|
-
*
|
|
2502
|
+
* Create a structured checkpoint for session resume.
|
|
2503
|
+
* Automatically saves a context_snapshot with the last 10 observations.
|
|
2373
2504
|
*/
|
|
2374
2505
|
async createCheckpoint(sessionId, data) {
|
|
2375
2506
|
const recentObs = getObservationsByProject(this.db.db, this.project, 10);
|
|
@@ -2386,21 +2517,21 @@ var KiroMemorySDK = class {
|
|
|
2386
2517
|
});
|
|
2387
2518
|
}
|
|
2388
2519
|
/**
|
|
2389
|
-
*
|
|
2520
|
+
* Retrieve the latest checkpoint of a specific session.
|
|
2390
2521
|
*/
|
|
2391
2522
|
async getCheckpoint(sessionId) {
|
|
2392
2523
|
return getLatestCheckpoint(this.db.db, sessionId);
|
|
2393
2524
|
}
|
|
2394
2525
|
/**
|
|
2395
|
-
*
|
|
2396
|
-
*
|
|
2526
|
+
* Retrieve the latest checkpoint for the current project.
|
|
2527
|
+
* Useful for automatic resume without specifying session ID.
|
|
2397
2528
|
*/
|
|
2398
2529
|
async getLatestProjectCheckpoint() {
|
|
2399
2530
|
return getLatestCheckpointByProject(this.db.db, this.project);
|
|
2400
2531
|
}
|
|
2401
2532
|
/**
|
|
2402
|
-
*
|
|
2403
|
-
*
|
|
2533
|
+
* Generate an activity report for the current project.
|
|
2534
|
+
* Aggregates observations, sessions, summaries and files for a time period.
|
|
2404
2535
|
*/
|
|
2405
2536
|
async generateReport(options) {
|
|
2406
2537
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -2485,7 +2616,7 @@ runHook("agentSpawn", async (input) => {
|
|
|
2485
2616
|
summaries: smartCtx.summaries,
|
|
2486
2617
|
project
|
|
2487
2618
|
});
|
|
2488
|
-
output += `> UI
|
|
2619
|
+
output += `> UI available at http://127.0.0.1:${process.env.KIRO_MEMORY_WORKER_PORT || "3001"}
|
|
2489
2620
|
`;
|
|
2490
2621
|
process.stdout.write(output);
|
|
2491
2622
|
} finally {
|