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
|
@@ -324,9 +324,25 @@ function consolidateObservations(db, project, options = {}) {
|
|
|
324
324
|
ORDER BY cnt DESC
|
|
325
325
|
`).all(project, minGroupSize);
|
|
326
326
|
if (groups.length === 0) return { merged: 0, removed: 0 };
|
|
327
|
-
|
|
328
|
-
|
|
327
|
+
if (options.dryRun) {
|
|
328
|
+
let totalMerged = 0;
|
|
329
|
+
let totalRemoved = 0;
|
|
330
|
+
for (const group of groups) {
|
|
331
|
+
const obsIds = group.ids.split(",").map(Number);
|
|
332
|
+
const placeholders = obsIds.map(() => "?").join(",");
|
|
333
|
+
const count = db.query(
|
|
334
|
+
`SELECT COUNT(*) as cnt FROM observations WHERE id IN (${placeholders})`
|
|
335
|
+
).get(...obsIds)?.cnt || 0;
|
|
336
|
+
if (count >= minGroupSize) {
|
|
337
|
+
totalMerged += 1;
|
|
338
|
+
totalRemoved += count - 1;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return { merged: totalMerged, removed: totalRemoved };
|
|
342
|
+
}
|
|
329
343
|
const runConsolidation = db.transaction(() => {
|
|
344
|
+
let merged = 0;
|
|
345
|
+
let removed = 0;
|
|
330
346
|
for (const group of groups) {
|
|
331
347
|
const obsIds = group.ids.split(",").map(Number);
|
|
332
348
|
const placeholders = obsIds.map(() => "?").join(",");
|
|
@@ -334,11 +350,6 @@ function consolidateObservations(db, project, options = {}) {
|
|
|
334
350
|
`SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC`
|
|
335
351
|
).all(...obsIds);
|
|
336
352
|
if (observations.length < minGroupSize) continue;
|
|
337
|
-
if (options.dryRun) {
|
|
338
|
-
totalMerged += 1;
|
|
339
|
-
totalRemoved += observations.length - 1;
|
|
340
|
-
continue;
|
|
341
|
-
}
|
|
342
353
|
const keeper = observations[0];
|
|
343
354
|
const others = observations.slice(1);
|
|
344
355
|
const uniqueTexts = /* @__PURE__ */ new Set();
|
|
@@ -351,18 +362,18 @@ function consolidateObservations(db, project, options = {}) {
|
|
|
351
362
|
const consolidatedText = Array.from(uniqueTexts).join("\n---\n").substring(0, 1e5);
|
|
352
363
|
db.run(
|
|
353
364
|
"UPDATE observations SET text = ?, title = ? WHERE id = ?",
|
|
354
|
-
[consolidatedText, `[
|
|
365
|
+
[consolidatedText, `[consolidated x${observations.length}] ${keeper.title}`, keeper.id]
|
|
355
366
|
);
|
|
356
367
|
const removeIds = others.map((o) => o.id);
|
|
357
368
|
const removePlaceholders = removeIds.map(() => "?").join(",");
|
|
358
369
|
db.run(`DELETE FROM observations WHERE id IN (${removePlaceholders})`, removeIds);
|
|
359
370
|
db.run(`DELETE FROM observation_embeddings WHERE observation_id IN (${removePlaceholders})`, removeIds);
|
|
360
|
-
|
|
361
|
-
|
|
371
|
+
merged += 1;
|
|
372
|
+
removed += removeIds.length;
|
|
362
373
|
}
|
|
374
|
+
return { merged, removed };
|
|
363
375
|
});
|
|
364
|
-
runConsolidation();
|
|
365
|
-
return { merged: totalMerged, removed: totalRemoved };
|
|
376
|
+
return runConsolidation();
|
|
366
377
|
}
|
|
367
378
|
var init_Observations = __esm({
|
|
368
379
|
"src/services/sqlite/Observations.ts"() {
|
|
@@ -389,7 +400,7 @@ function escapeLikePattern3(input) {
|
|
|
389
400
|
}
|
|
390
401
|
function sanitizeFTS5Query(query) {
|
|
391
402
|
const trimmed = query.length > 1e4 ? query.substring(0, 1e4) : query;
|
|
392
|
-
const terms = trimmed.replace(/[""]/g, "").split(/\s+/).filter((t) => t.length > 0).slice(0, 100).map((t) => `"${t}"`);
|
|
403
|
+
const terms = trimmed.replace(/[""\u0022]/g, "").split(/\s+/).filter((t) => t.length > 0).slice(0, 100).map((t) => `"${t}"`);
|
|
393
404
|
return terms.join(" ");
|
|
394
405
|
}
|
|
395
406
|
function searchObservationsFTS(db, query, filters = {}) {
|
|
@@ -554,26 +565,38 @@ function getTimeline(db, anchorId, depthBefore = 5, depthAfter = 5) {
|
|
|
554
565
|
return [...before, ...self, ...after];
|
|
555
566
|
}
|
|
556
567
|
function getProjectStats(db, project) {
|
|
557
|
-
const
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
568
|
+
const sql = `
|
|
569
|
+
WITH
|
|
570
|
+
obs_stats AS (
|
|
571
|
+
SELECT
|
|
572
|
+
COUNT(*) as count,
|
|
573
|
+
COALESCE(SUM(discovery_tokens), 0) as discovery_tokens,
|
|
574
|
+
COALESCE(SUM(
|
|
575
|
+
CAST((LENGTH(COALESCE(title, '')) + LENGTH(COALESCE(narrative, ''))) / 4 AS INTEGER)
|
|
576
|
+
), 0) as read_tokens
|
|
577
|
+
FROM observations WHERE project = ?
|
|
578
|
+
),
|
|
579
|
+
sum_count AS (SELECT COUNT(*) as count FROM summaries WHERE project = ?),
|
|
580
|
+
ses_count AS (SELECT COUNT(*) as count FROM sessions WHERE project = ?),
|
|
581
|
+
prm_count AS (SELECT COUNT(*) as count FROM prompts WHERE project = ?)
|
|
582
|
+
SELECT
|
|
583
|
+
obs_stats.count as observations,
|
|
584
|
+
obs_stats.discovery_tokens,
|
|
585
|
+
obs_stats.read_tokens,
|
|
586
|
+
sum_count.count as summaries,
|
|
587
|
+
ses_count.count as sessions,
|
|
588
|
+
prm_count.count as prompts
|
|
589
|
+
FROM obs_stats, sum_count, ses_count, prm_count
|
|
590
|
+
`;
|
|
591
|
+
const row = db.query(sql).get(project, project, project, project);
|
|
592
|
+
const discoveryTokens = row?.discovery_tokens || 0;
|
|
593
|
+
const readTokens = row?.read_tokens || 0;
|
|
571
594
|
const savings = Math.max(0, discoveryTokens - readTokens);
|
|
572
595
|
return {
|
|
573
|
-
observations:
|
|
574
|
-
summaries:
|
|
575
|
-
sessions:
|
|
576
|
-
prompts:
|
|
596
|
+
observations: row?.observations || 0,
|
|
597
|
+
summaries: row?.summaries || 0,
|
|
598
|
+
sessions: row?.sessions || 0,
|
|
599
|
+
prompts: row?.prompts || 0,
|
|
577
600
|
tokenEconomics: { discoveryTokens, readTokens, savings }
|
|
578
601
|
};
|
|
579
602
|
}
|
|
@@ -647,8 +670,8 @@ var init_EmbeddingService = __esm({
|
|
|
647
670
|
initialized = false;
|
|
648
671
|
initializing = null;
|
|
649
672
|
/**
|
|
650
|
-
*
|
|
651
|
-
*
|
|
673
|
+
* Initialize the embedding service.
|
|
674
|
+
* Tries fastembed, then @huggingface/transformers, then fallback to null.
|
|
652
675
|
*/
|
|
653
676
|
async initialize() {
|
|
654
677
|
if (this.initialized) return this.provider !== null;
|
|
@@ -669,11 +692,11 @@ var init_EmbeddingService = __esm({
|
|
|
669
692
|
});
|
|
670
693
|
this.provider = "fastembed";
|
|
671
694
|
this.initialized = true;
|
|
672
|
-
logger.info("EMBEDDING", "
|
|
695
|
+
logger.info("EMBEDDING", "Initialized with fastembed (BGE-small-en-v1.5)");
|
|
673
696
|
return true;
|
|
674
697
|
}
|
|
675
698
|
} catch (error) {
|
|
676
|
-
logger.debug("EMBEDDING", `fastembed
|
|
699
|
+
logger.debug("EMBEDDING", `fastembed not available: ${error}`);
|
|
677
700
|
}
|
|
678
701
|
try {
|
|
679
702
|
const transformers = await import("@huggingface/transformers");
|
|
@@ -684,20 +707,20 @@ var init_EmbeddingService = __esm({
|
|
|
684
707
|
});
|
|
685
708
|
this.provider = "transformers";
|
|
686
709
|
this.initialized = true;
|
|
687
|
-
logger.info("EMBEDDING", "
|
|
710
|
+
logger.info("EMBEDDING", "Initialized with @huggingface/transformers (all-MiniLM-L6-v2)");
|
|
688
711
|
return true;
|
|
689
712
|
}
|
|
690
713
|
} catch (error) {
|
|
691
|
-
logger.debug("EMBEDDING", `@huggingface/transformers
|
|
714
|
+
logger.debug("EMBEDDING", `@huggingface/transformers not available: ${error}`);
|
|
692
715
|
}
|
|
693
716
|
this.provider = null;
|
|
694
717
|
this.initialized = true;
|
|
695
|
-
logger.warn("EMBEDDING", "
|
|
718
|
+
logger.warn("EMBEDDING", "No embedding provider available, semantic search disabled");
|
|
696
719
|
return false;
|
|
697
720
|
}
|
|
698
721
|
/**
|
|
699
|
-
*
|
|
700
|
-
*
|
|
722
|
+
* Generate embedding for a single text.
|
|
723
|
+
* Returns Float32Array with 384 dimensions, or null if not available.
|
|
701
724
|
*/
|
|
702
725
|
async embed(text) {
|
|
703
726
|
if (!this.initialized) await this.initialize();
|
|
@@ -710,46 +733,111 @@ var init_EmbeddingService = __esm({
|
|
|
710
733
|
return await this._embedTransformers(truncated);
|
|
711
734
|
}
|
|
712
735
|
} catch (error) {
|
|
713
|
-
logger.error("EMBEDDING", `
|
|
736
|
+
logger.error("EMBEDDING", `Error generating embedding: ${error}`);
|
|
714
737
|
}
|
|
715
738
|
return null;
|
|
716
739
|
}
|
|
717
740
|
/**
|
|
718
|
-
*
|
|
741
|
+
* Generate embeddings in batch.
|
|
742
|
+
* Uses native batch support when available (fastembed, transformers),
|
|
743
|
+
* falls back to serial processing on batch failure.
|
|
719
744
|
*/
|
|
720
745
|
async embedBatch(texts) {
|
|
721
746
|
if (!this.initialized) await this.initialize();
|
|
722
747
|
if (!this.provider || !this.model) return texts.map(() => null);
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
}
|
|
729
|
-
|
|
748
|
+
if (texts.length === 0) return [];
|
|
749
|
+
const truncated = texts.map((t) => t.substring(0, 2e3));
|
|
750
|
+
try {
|
|
751
|
+
if (this.provider === "fastembed") {
|
|
752
|
+
return await this._embedBatchFastembed(truncated);
|
|
753
|
+
} else if (this.provider === "transformers") {
|
|
754
|
+
return await this._embedBatchTransformers(truncated);
|
|
730
755
|
}
|
|
756
|
+
} catch (error) {
|
|
757
|
+
logger.warn("EMBEDDING", `Batch embedding failed, falling back to serial: ${error}`);
|
|
731
758
|
}
|
|
732
|
-
return
|
|
759
|
+
return this._embedBatchSerial(truncated);
|
|
733
760
|
}
|
|
734
761
|
/**
|
|
735
|
-
*
|
|
762
|
+
* Check if the service is available.
|
|
736
763
|
*/
|
|
737
764
|
isAvailable() {
|
|
738
765
|
return this.initialized && this.provider !== null;
|
|
739
766
|
}
|
|
740
767
|
/**
|
|
741
|
-
*
|
|
768
|
+
* Name of the active provider.
|
|
742
769
|
*/
|
|
743
770
|
getProvider() {
|
|
744
771
|
return this.provider;
|
|
745
772
|
}
|
|
746
773
|
/**
|
|
747
|
-
*
|
|
774
|
+
* Embedding vector dimensions.
|
|
748
775
|
*/
|
|
749
776
|
getDimensions() {
|
|
750
777
|
return 384;
|
|
751
778
|
}
|
|
752
|
-
// ---
|
|
779
|
+
// --- Batch implementations ---
|
|
780
|
+
/**
|
|
781
|
+
* Native batch embedding with fastembed.
|
|
782
|
+
* FlagEmbedding.embed() accepts string[] and returns an async iterable of batches.
|
|
783
|
+
*/
|
|
784
|
+
async _embedBatchFastembed(texts) {
|
|
785
|
+
const results = [];
|
|
786
|
+
const embeddings = this.model.embed(texts, texts.length);
|
|
787
|
+
for await (const batch of embeddings) {
|
|
788
|
+
if (batch) {
|
|
789
|
+
for (const vec of batch) {
|
|
790
|
+
results.push(vec instanceof Float32Array ? vec : new Float32Array(vec));
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
while (results.length < texts.length) {
|
|
795
|
+
results.push(null);
|
|
796
|
+
}
|
|
797
|
+
return results;
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Batch embedding with @huggingface/transformers pipeline.
|
|
801
|
+
* The pipeline accepts string[] and returns a Tensor with shape [N, dims].
|
|
802
|
+
*/
|
|
803
|
+
async _embedBatchTransformers(texts) {
|
|
804
|
+
const output = await this.model(texts, {
|
|
805
|
+
pooling: "mean",
|
|
806
|
+
normalize: true
|
|
807
|
+
});
|
|
808
|
+
if (!output?.data) {
|
|
809
|
+
return texts.map(() => null);
|
|
810
|
+
}
|
|
811
|
+
const dims = this.getDimensions();
|
|
812
|
+
const data = output.data instanceof Float32Array ? output.data : new Float32Array(output.data);
|
|
813
|
+
const results = [];
|
|
814
|
+
for (let i = 0; i < texts.length; i++) {
|
|
815
|
+
const offset = i * dims;
|
|
816
|
+
if (offset + dims <= data.length) {
|
|
817
|
+
results.push(data.slice(offset, offset + dims));
|
|
818
|
+
} else {
|
|
819
|
+
results.push(null);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
return results;
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* Serial fallback: embed texts one at a time.
|
|
826
|
+
* Used when native batch fails.
|
|
827
|
+
*/
|
|
828
|
+
async _embedBatchSerial(texts) {
|
|
829
|
+
const results = [];
|
|
830
|
+
for (const text of texts) {
|
|
831
|
+
try {
|
|
832
|
+
const embedding = await this.embed(text);
|
|
833
|
+
results.push(embedding);
|
|
834
|
+
} catch {
|
|
835
|
+
results.push(null);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
return results;
|
|
839
|
+
}
|
|
840
|
+
// --- Single-text provider implementations ---
|
|
753
841
|
async _embedFastembed(text) {
|
|
754
842
|
const embeddings = this.model.embed([text], 1);
|
|
755
843
|
for await (const batch of embeddings) {
|
|
@@ -779,14 +867,15 @@ var init_EmbeddingService = __esm({
|
|
|
779
867
|
import BetterSqlite3 from "better-sqlite3";
|
|
780
868
|
var Database = class {
|
|
781
869
|
_db;
|
|
870
|
+
_stmtCache = /* @__PURE__ */ new Map();
|
|
782
871
|
constructor(path, options) {
|
|
783
872
|
this._db = new BetterSqlite3(path, {
|
|
784
|
-
// better-sqlite3
|
|
873
|
+
// better-sqlite3 creates the file by default ('create' not needed)
|
|
785
874
|
readonly: options?.readwrite === false ? true : false
|
|
786
875
|
});
|
|
787
876
|
}
|
|
788
877
|
/**
|
|
789
|
-
*
|
|
878
|
+
* Execute a SQL query without results
|
|
790
879
|
*/
|
|
791
880
|
run(sql, params) {
|
|
792
881
|
const stmt = this._db.prepare(sql);
|
|
@@ -794,51 +883,53 @@ var Database = class {
|
|
|
794
883
|
return result;
|
|
795
884
|
}
|
|
796
885
|
/**
|
|
797
|
-
*
|
|
886
|
+
* Prepare a query with bun:sqlite-compatible interface.
|
|
887
|
+
* Returns a cached prepared statement for repeated queries.
|
|
798
888
|
*/
|
|
799
889
|
query(sql) {
|
|
800
|
-
|
|
890
|
+
let cached = this._stmtCache.get(sql);
|
|
891
|
+
if (!cached) {
|
|
892
|
+
cached = new BunQueryCompat(this._db, sql);
|
|
893
|
+
this._stmtCache.set(sql, cached);
|
|
894
|
+
}
|
|
895
|
+
return cached;
|
|
801
896
|
}
|
|
802
897
|
/**
|
|
803
|
-
*
|
|
898
|
+
* Create a transaction
|
|
804
899
|
*/
|
|
805
900
|
transaction(fn) {
|
|
806
901
|
return this._db.transaction(fn);
|
|
807
902
|
}
|
|
808
903
|
/**
|
|
809
|
-
*
|
|
904
|
+
* Close the connection
|
|
810
905
|
*/
|
|
811
906
|
close() {
|
|
907
|
+
this._stmtCache.clear();
|
|
812
908
|
this._db.close();
|
|
813
909
|
}
|
|
814
910
|
};
|
|
815
911
|
var BunQueryCompat = class {
|
|
816
|
-
|
|
817
|
-
_sql;
|
|
912
|
+
_stmt;
|
|
818
913
|
constructor(db, sql) {
|
|
819
|
-
this.
|
|
820
|
-
this._sql = sql;
|
|
914
|
+
this._stmt = db.prepare(sql);
|
|
821
915
|
}
|
|
822
916
|
/**
|
|
823
|
-
*
|
|
917
|
+
* Returns all rows
|
|
824
918
|
*/
|
|
825
919
|
all(...params) {
|
|
826
|
-
|
|
827
|
-
return params.length > 0 ? stmt.all(...params) : stmt.all();
|
|
920
|
+
return params.length > 0 ? this._stmt.all(...params) : this._stmt.all();
|
|
828
921
|
}
|
|
829
922
|
/**
|
|
830
|
-
*
|
|
923
|
+
* Returns the first row or null
|
|
831
924
|
*/
|
|
832
925
|
get(...params) {
|
|
833
|
-
|
|
834
|
-
return params.length > 0 ? stmt.get(...params) : stmt.get();
|
|
926
|
+
return params.length > 0 ? this._stmt.get(...params) : this._stmt.get();
|
|
835
927
|
}
|
|
836
928
|
/**
|
|
837
|
-
*
|
|
929
|
+
* Execute without results
|
|
838
930
|
*/
|
|
839
931
|
run(...params) {
|
|
840
|
-
|
|
841
|
-
return params.length > 0 ? stmt.run(...params) : stmt.run();
|
|
932
|
+
return params.length > 0 ? this._stmt.run(...params) : this._stmt.run();
|
|
842
933
|
}
|
|
843
934
|
};
|
|
844
935
|
|
|
@@ -881,40 +972,62 @@ init_logger();
|
|
|
881
972
|
var SQLITE_MMAP_SIZE_BYTES = 256 * 1024 * 1024;
|
|
882
973
|
var SQLITE_CACHE_SIZE_PAGES = 1e4;
|
|
883
974
|
var KiroMemoryDatabase = class {
|
|
884
|
-
|
|
975
|
+
_db;
|
|
976
|
+
/**
|
|
977
|
+
* Readonly accessor for the underlying Database instance.
|
|
978
|
+
* Prefer using query() and run() proxy methods directly.
|
|
979
|
+
*/
|
|
980
|
+
get db() {
|
|
981
|
+
return this._db;
|
|
982
|
+
}
|
|
885
983
|
/**
|
|
886
|
-
* @param dbPath -
|
|
887
|
-
* @param skipMigrations -
|
|
984
|
+
* @param dbPath - Path to the SQLite file (default: DB_PATH)
|
|
985
|
+
* @param skipMigrations - If true, skip the migration runner (for high-frequency hooks)
|
|
888
986
|
*/
|
|
889
987
|
constructor(dbPath = DB_PATH, skipMigrations = false) {
|
|
890
988
|
if (dbPath !== ":memory:") {
|
|
891
989
|
ensureDir(DATA_DIR);
|
|
892
990
|
}
|
|
893
|
-
this.
|
|
894
|
-
this.
|
|
895
|
-
this.
|
|
896
|
-
this.
|
|
897
|
-
this.
|
|
898
|
-
this.
|
|
899
|
-
this.
|
|
991
|
+
this._db = new Database(dbPath, { create: true, readwrite: true });
|
|
992
|
+
this._db.run("PRAGMA journal_mode = WAL");
|
|
993
|
+
this._db.run("PRAGMA busy_timeout = 5000");
|
|
994
|
+
this._db.run("PRAGMA synchronous = NORMAL");
|
|
995
|
+
this._db.run("PRAGMA foreign_keys = ON");
|
|
996
|
+
this._db.run("PRAGMA temp_store = memory");
|
|
997
|
+
this._db.run(`PRAGMA mmap_size = ${SQLITE_MMAP_SIZE_BYTES}`);
|
|
998
|
+
this._db.run(`PRAGMA cache_size = ${SQLITE_CACHE_SIZE_PAGES}`);
|
|
900
999
|
if (!skipMigrations) {
|
|
901
|
-
const migrationRunner = new MigrationRunner(this.
|
|
1000
|
+
const migrationRunner = new MigrationRunner(this._db);
|
|
902
1001
|
migrationRunner.runAllMigrations();
|
|
903
1002
|
}
|
|
904
1003
|
}
|
|
905
1004
|
/**
|
|
906
|
-
*
|
|
907
|
-
*
|
|
1005
|
+
* Prepare a query (delegates to underlying Database).
|
|
1006
|
+
* Proxy method to avoid ctx.db.db.query() double access.
|
|
1007
|
+
*/
|
|
1008
|
+
query(sql) {
|
|
1009
|
+
return this._db.query(sql);
|
|
1010
|
+
}
|
|
1011
|
+
/**
|
|
1012
|
+
* Execute a SQL statement without results (delegates to underlying Database).
|
|
1013
|
+
* Proxy method to avoid ctx.db.db.run() double access.
|
|
1014
|
+
*/
|
|
1015
|
+
run(sql, params) {
|
|
1016
|
+
return this._db.run(sql, params);
|
|
1017
|
+
}
|
|
1018
|
+
/**
|
|
1019
|
+
* Executes a function within an atomic transaction.
|
|
1020
|
+
* If fn() throws an error, the transaction is automatically rolled back.
|
|
908
1021
|
*/
|
|
909
1022
|
withTransaction(fn) {
|
|
910
|
-
const transaction = this.
|
|
911
|
-
return transaction(this.
|
|
1023
|
+
const transaction = this._db.transaction(fn);
|
|
1024
|
+
return transaction(this._db);
|
|
912
1025
|
}
|
|
913
1026
|
/**
|
|
914
1027
|
* Close the database connection
|
|
915
1028
|
*/
|
|
916
1029
|
close() {
|
|
917
|
-
this.
|
|
1030
|
+
this._db.close();
|
|
918
1031
|
}
|
|
919
1032
|
};
|
|
920
1033
|
var MigrationRunner = class {
|
|
@@ -1408,17 +1521,21 @@ init_EmbeddingService();
|
|
|
1408
1521
|
// src/services/search/VectorSearch.ts
|
|
1409
1522
|
init_EmbeddingService();
|
|
1410
1523
|
init_logger();
|
|
1524
|
+
var DEFAULT_MAX_CANDIDATES = 2e3;
|
|
1411
1525
|
function cosineSimilarity(a, b) {
|
|
1412
|
-
|
|
1526
|
+
const len = a.length;
|
|
1527
|
+
if (len !== b.length) return 0;
|
|
1413
1528
|
let dotProduct = 0;
|
|
1414
1529
|
let normA = 0;
|
|
1415
1530
|
let normB = 0;
|
|
1416
|
-
for (let i = 0; i <
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1531
|
+
for (let i = 0; i < len; i++) {
|
|
1532
|
+
const ai = a[i];
|
|
1533
|
+
const bi = b[i];
|
|
1534
|
+
dotProduct += ai * bi;
|
|
1535
|
+
normA += ai * ai;
|
|
1536
|
+
normB += bi * bi;
|
|
1537
|
+
}
|
|
1538
|
+
const denominator = Math.sqrt(normA * normB);
|
|
1422
1539
|
if (denominator === 0) return 0;
|
|
1423
1540
|
return dotProduct / denominator;
|
|
1424
1541
|
}
|
|
@@ -1431,23 +1548,36 @@ function bufferToFloat32(buf) {
|
|
|
1431
1548
|
}
|
|
1432
1549
|
var VectorSearch = class {
|
|
1433
1550
|
/**
|
|
1434
|
-
*
|
|
1551
|
+
* Semantic search with SQL pre-filtering for scalability.
|
|
1552
|
+
*
|
|
1553
|
+
* 2-phase strategy:
|
|
1554
|
+
* 1. SQL pre-filters by project + sorts by recency (loads max N candidates)
|
|
1555
|
+
* 2. JS computes cosine similarity only on filtered candidates
|
|
1556
|
+
*
|
|
1557
|
+
* With 50k observations and maxCandidates=2000, loads only ~4% of data.
|
|
1435
1558
|
*/
|
|
1436
1559
|
async search(db, queryEmbedding, options = {}) {
|
|
1437
1560
|
const limit = options.limit || 10;
|
|
1438
1561
|
const threshold = options.threshold || 0.3;
|
|
1562
|
+
const maxCandidates = options.maxCandidates || DEFAULT_MAX_CANDIDATES;
|
|
1439
1563
|
try {
|
|
1440
|
-
|
|
1564
|
+
const conditions = [];
|
|
1565
|
+
const params = [];
|
|
1566
|
+
if (options.project) {
|
|
1567
|
+
conditions.push("o.project = ?");
|
|
1568
|
+
params.push(options.project);
|
|
1569
|
+
}
|
|
1570
|
+
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
1571
|
+
const sql = `
|
|
1441
1572
|
SELECT e.observation_id, e.embedding,
|
|
1442
1573
|
o.title, o.text, o.type, o.project, o.created_at, o.created_at_epoch
|
|
1443
1574
|
FROM observation_embeddings e
|
|
1444
1575
|
JOIN observations o ON o.id = e.observation_id
|
|
1576
|
+
${whereClause}
|
|
1577
|
+
ORDER BY o.created_at_epoch DESC
|
|
1578
|
+
LIMIT ?
|
|
1445
1579
|
`;
|
|
1446
|
-
|
|
1447
|
-
if (options.project) {
|
|
1448
|
-
sql += " WHERE o.project = ?";
|
|
1449
|
-
params.push(options.project);
|
|
1450
|
-
}
|
|
1580
|
+
params.push(maxCandidates);
|
|
1451
1581
|
const rows = db.query(sql).all(...params);
|
|
1452
1582
|
const scored = [];
|
|
1453
1583
|
for (const row of rows) {
|
|
@@ -1468,14 +1598,15 @@ var VectorSearch = class {
|
|
|
1468
1598
|
}
|
|
1469
1599
|
}
|
|
1470
1600
|
scored.sort((a, b) => b.similarity - a.similarity);
|
|
1601
|
+
logger.debug("VECTOR", `Search: ${rows.length} candidates \u2192 ${scored.length} above threshold \u2192 ${Math.min(scored.length, limit)} results`);
|
|
1471
1602
|
return scored.slice(0, limit);
|
|
1472
1603
|
} catch (error) {
|
|
1473
|
-
logger.error("VECTOR", `
|
|
1604
|
+
logger.error("VECTOR", `Vector search error: ${error}`);
|
|
1474
1605
|
return [];
|
|
1475
1606
|
}
|
|
1476
1607
|
}
|
|
1477
1608
|
/**
|
|
1478
|
-
*
|
|
1609
|
+
* Store embedding for an observation.
|
|
1479
1610
|
*/
|
|
1480
1611
|
async storeEmbedding(db, observationId, embedding, model) {
|
|
1481
1612
|
try {
|
|
@@ -1491,18 +1622,18 @@ var VectorSearch = class {
|
|
|
1491
1622
|
embedding.length,
|
|
1492
1623
|
(/* @__PURE__ */ new Date()).toISOString()
|
|
1493
1624
|
);
|
|
1494
|
-
logger.debug("VECTOR", `Embedding
|
|
1625
|
+
logger.debug("VECTOR", `Embedding saved for observation ${observationId}`);
|
|
1495
1626
|
} catch (error) {
|
|
1496
|
-
logger.error("VECTOR", `
|
|
1627
|
+
logger.error("VECTOR", `Error saving embedding: ${error}`);
|
|
1497
1628
|
}
|
|
1498
1629
|
}
|
|
1499
1630
|
/**
|
|
1500
|
-
*
|
|
1631
|
+
* Generate embeddings for observations that don't have them yet.
|
|
1501
1632
|
*/
|
|
1502
1633
|
async backfillEmbeddings(db, batchSize = 50) {
|
|
1503
1634
|
const embeddingService2 = getEmbeddingService();
|
|
1504
1635
|
if (!await embeddingService2.initialize()) {
|
|
1505
|
-
logger.warn("VECTOR", "Embedding service
|
|
1636
|
+
logger.warn("VECTOR", "Embedding service not available, backfill skipped");
|
|
1506
1637
|
return 0;
|
|
1507
1638
|
}
|
|
1508
1639
|
const rows = db.query(`
|
|
@@ -1528,11 +1659,11 @@ var VectorSearch = class {
|
|
|
1528
1659
|
count++;
|
|
1529
1660
|
}
|
|
1530
1661
|
}
|
|
1531
|
-
logger.info("VECTOR", `Backfill
|
|
1662
|
+
logger.info("VECTOR", `Backfill completed: ${count}/${rows.length} embeddings generated`);
|
|
1532
1663
|
return count;
|
|
1533
1664
|
}
|
|
1534
1665
|
/**
|
|
1535
|
-
*
|
|
1666
|
+
* Embedding statistics.
|
|
1536
1667
|
*/
|
|
1537
1668
|
getStats(db) {
|
|
1538
1669
|
try {
|
|
@@ -1606,21 +1737,21 @@ init_logger();
|
|
|
1606
1737
|
var HybridSearch = class {
|
|
1607
1738
|
embeddingInitialized = false;
|
|
1608
1739
|
/**
|
|
1609
|
-
*
|
|
1740
|
+
* Initialize the embedding service (lazy, non-blocking)
|
|
1610
1741
|
*/
|
|
1611
1742
|
async initialize() {
|
|
1612
1743
|
try {
|
|
1613
1744
|
const embeddingService2 = getEmbeddingService();
|
|
1614
1745
|
await embeddingService2.initialize();
|
|
1615
1746
|
this.embeddingInitialized = embeddingService2.isAvailable();
|
|
1616
|
-
logger.info("SEARCH", `HybridSearch
|
|
1747
|
+
logger.info("SEARCH", `HybridSearch initialized (embedding: ${this.embeddingInitialized ? "active" : "disabled"})`);
|
|
1617
1748
|
} catch (error) {
|
|
1618
|
-
logger.warn("SEARCH", "
|
|
1749
|
+
logger.warn("SEARCH", "Embedding initialization failed, using only FTS5", {}, error);
|
|
1619
1750
|
this.embeddingInitialized = false;
|
|
1620
1751
|
}
|
|
1621
1752
|
}
|
|
1622
1753
|
/**
|
|
1623
|
-
*
|
|
1754
|
+
* Hybrid search with 4-signal scoring
|
|
1624
1755
|
*/
|
|
1625
1756
|
async search(db, query, options = {}) {
|
|
1626
1757
|
const limit = options.limit || 10;
|
|
@@ -1636,7 +1767,7 @@ var HybridSearch = class {
|
|
|
1636
1767
|
const vectorResults = await vectorSearch2.search(db, queryEmbedding, {
|
|
1637
1768
|
project: options.project,
|
|
1638
1769
|
limit: limit * 2,
|
|
1639
|
-
//
|
|
1770
|
+
// Fetch more results for ranking
|
|
1640
1771
|
threshold: 0.3
|
|
1641
1772
|
});
|
|
1642
1773
|
for (const hit of vectorResults) {
|
|
@@ -1653,10 +1784,10 @@ var HybridSearch = class {
|
|
|
1653
1784
|
source: "vector"
|
|
1654
1785
|
});
|
|
1655
1786
|
}
|
|
1656
|
-
logger.debug("SEARCH", `Vector search: ${vectorResults.length}
|
|
1787
|
+
logger.debug("SEARCH", `Vector search: ${vectorResults.length} results`);
|
|
1657
1788
|
}
|
|
1658
1789
|
} catch (error) {
|
|
1659
|
-
logger.warn("SEARCH", "
|
|
1790
|
+
logger.warn("SEARCH", "Vector search failed, using only keyword", {}, error);
|
|
1660
1791
|
}
|
|
1661
1792
|
}
|
|
1662
1793
|
try {
|
|
@@ -1686,9 +1817,9 @@ var HybridSearch = class {
|
|
|
1686
1817
|
});
|
|
1687
1818
|
}
|
|
1688
1819
|
}
|
|
1689
|
-
logger.debug("SEARCH", `Keyword search: ${keywordResults.length}
|
|
1820
|
+
logger.debug("SEARCH", `Keyword search: ${keywordResults.length} results`);
|
|
1690
1821
|
} catch (error) {
|
|
1691
|
-
logger.error("SEARCH", "
|
|
1822
|
+
logger.error("SEARCH", "Keyword search failed", {}, error);
|
|
1692
1823
|
}
|
|
1693
1824
|
if (rawItems.size === 0) return [];
|
|
1694
1825
|
const allFTS5Ranks = Array.from(rawItems.values()).filter((item) => item.fts5Rank !== null).map((item) => item.fts5Rank);
|
|
@@ -1780,33 +1911,33 @@ var KiroMemorySDK = class {
|
|
|
1780
1911
|
};
|
|
1781
1912
|
}
|
|
1782
1913
|
/**
|
|
1783
|
-
*
|
|
1914
|
+
* Validate input for storeObservation
|
|
1784
1915
|
*/
|
|
1785
1916
|
validateObservationInput(data) {
|
|
1786
1917
|
if (!data.type || typeof data.type !== "string" || data.type.length > 100) {
|
|
1787
|
-
throw new Error("type
|
|
1918
|
+
throw new Error("type is required (string, max 100 chars)");
|
|
1788
1919
|
}
|
|
1789
1920
|
if (!data.title || typeof data.title !== "string" || data.title.length > 500) {
|
|
1790
|
-
throw new Error("title
|
|
1921
|
+
throw new Error("title is required (string, max 500 chars)");
|
|
1791
1922
|
}
|
|
1792
1923
|
if (!data.content || typeof data.content !== "string" || data.content.length > 1e5) {
|
|
1793
|
-
throw new Error("content
|
|
1924
|
+
throw new Error("content is required (string, max 100KB)");
|
|
1794
1925
|
}
|
|
1795
1926
|
}
|
|
1796
1927
|
/**
|
|
1797
|
-
*
|
|
1928
|
+
* Validate input for storeSummary
|
|
1798
1929
|
*/
|
|
1799
1930
|
validateSummaryInput(data) {
|
|
1800
1931
|
const MAX = 5e4;
|
|
1801
1932
|
for (const [key, val] of Object.entries(data)) {
|
|
1802
1933
|
if (val !== void 0 && val !== null) {
|
|
1803
|
-
if (typeof val !== "string") throw new Error(`${key}
|
|
1804
|
-
if (val.length > MAX) throw new Error(`${key}
|
|
1934
|
+
if (typeof val !== "string") throw new Error(`${key} must be a string`);
|
|
1935
|
+
if (val.length > MAX) throw new Error(`${key} too large (max 50KB)`);
|
|
1805
1936
|
}
|
|
1806
1937
|
}
|
|
1807
1938
|
}
|
|
1808
1939
|
/**
|
|
1809
|
-
*
|
|
1940
|
+
* Generate and store embedding for an observation (fire-and-forget, non-blocking)
|
|
1810
1941
|
*/
|
|
1811
1942
|
async generateEmbeddingAsync(observationId, title, content, concepts) {
|
|
1812
1943
|
try {
|
|
@@ -1826,39 +1957,39 @@ var KiroMemorySDK = class {
|
|
|
1826
1957
|
);
|
|
1827
1958
|
}
|
|
1828
1959
|
} catch (error) {
|
|
1829
|
-
logger.debug("SDK", `Embedding generation
|
|
1960
|
+
logger.debug("SDK", `Embedding generation failed for obs ${observationId}: ${error}`);
|
|
1830
1961
|
}
|
|
1831
1962
|
}
|
|
1832
1963
|
/**
|
|
1833
|
-
*
|
|
1834
|
-
*
|
|
1835
|
-
*
|
|
1964
|
+
* Generate SHA256 content hash for content-based deduplication.
|
|
1965
|
+
* Uses (project + type + title + narrative) as semantic identity tuple.
|
|
1966
|
+
* Does NOT include sessionId since it's unique per invocation.
|
|
1836
1967
|
*/
|
|
1837
1968
|
generateContentHash(type, title, narrative) {
|
|
1838
1969
|
const payload = `${this.project}|${type}|${title}|${narrative || ""}`;
|
|
1839
1970
|
return createHash("sha256").update(payload).digest("hex");
|
|
1840
1971
|
}
|
|
1841
1972
|
/**
|
|
1842
|
-
*
|
|
1843
|
-
*
|
|
1973
|
+
* Deduplication windows per type (ms).
|
|
1974
|
+
* Types with many repetitions have wider windows.
|
|
1844
1975
|
*/
|
|
1845
1976
|
getDeduplicationWindow(type) {
|
|
1846
1977
|
switch (type) {
|
|
1847
1978
|
case "file-read":
|
|
1848
1979
|
return 6e4;
|
|
1849
|
-
// 60s —
|
|
1980
|
+
// 60s — frequent reads on the same files
|
|
1850
1981
|
case "file-write":
|
|
1851
1982
|
return 1e4;
|
|
1852
|
-
// 10s —
|
|
1983
|
+
// 10s — rapid consecutive writes
|
|
1853
1984
|
case "command":
|
|
1854
1985
|
return 3e4;
|
|
1855
1986
|
// 30s — standard
|
|
1856
1987
|
case "research":
|
|
1857
1988
|
return 12e4;
|
|
1858
|
-
// 120s — web search
|
|
1989
|
+
// 120s — repeated web search and fetch
|
|
1859
1990
|
case "delegation":
|
|
1860
1991
|
return 6e4;
|
|
1861
|
-
// 60s —
|
|
1992
|
+
// 60s — rapid delegations
|
|
1862
1993
|
default:
|
|
1863
1994
|
return 3e4;
|
|
1864
1995
|
}
|
|
@@ -1872,7 +2003,7 @@ var KiroMemorySDK = class {
|
|
|
1872
2003
|
const contentHash = this.generateContentHash(data.type, data.title, data.narrative);
|
|
1873
2004
|
const dedupWindow = this.getDeduplicationWindow(data.type);
|
|
1874
2005
|
if (isDuplicateObservation(this.db.db, contentHash, dedupWindow)) {
|
|
1875
|
-
logger.debug("SDK", `
|
|
2006
|
+
logger.debug("SDK", `Duplicate observation discarded (${data.type}, ${dedupWindow}ms): ${data.title}`);
|
|
1876
2007
|
return -1;
|
|
1877
2008
|
}
|
|
1878
2009
|
const filesRead = data.filesRead || (data.type === "file-read" ? data.files : void 0);
|
|
@@ -1900,12 +2031,12 @@ var KiroMemorySDK = class {
|
|
|
1900
2031
|
return observationId;
|
|
1901
2032
|
}
|
|
1902
2033
|
/**
|
|
1903
|
-
*
|
|
1904
|
-
*
|
|
2034
|
+
* Store structured knowledge (constraint, decision, heuristic, rejected).
|
|
2035
|
+
* Uses the `type` field for knowledgeType and `facts` for JSON metadata.
|
|
1905
2036
|
*/
|
|
1906
2037
|
async storeKnowledge(data) {
|
|
1907
2038
|
if (!KNOWLEDGE_TYPES.includes(data.knowledgeType)) {
|
|
1908
|
-
throw new Error(`knowledgeType
|
|
2039
|
+
throw new Error(`Invalid knowledgeType: ${data.knowledgeType}. Allowed values: ${KNOWLEDGE_TYPES.join(", ")}`);
|
|
1909
2040
|
}
|
|
1910
2041
|
this.validateObservationInput({ type: data.knowledgeType, title: data.title, content: data.content });
|
|
1911
2042
|
const metadata = (() => {
|
|
@@ -1937,9 +2068,9 @@ var KiroMemorySDK = class {
|
|
|
1937
2068
|
}
|
|
1938
2069
|
})();
|
|
1939
2070
|
const sessionId = "sdk-" + Date.now();
|
|
1940
|
-
const contentHash = this.generateContentHash(data.
|
|
2071
|
+
const contentHash = this.generateContentHash(data.knowledgeType, data.title);
|
|
1941
2072
|
if (isDuplicateObservation(this.db.db, contentHash)) {
|
|
1942
|
-
logger.debug("SDK", `
|
|
2073
|
+
logger.debug("SDK", `Duplicate knowledge discarded: ${data.title}`);
|
|
1943
2074
|
return -1;
|
|
1944
2075
|
}
|
|
1945
2076
|
const discoveryTokens = Math.ceil(data.content.length / 4);
|
|
@@ -1956,11 +2087,11 @@ var KiroMemorySDK = class {
|
|
|
1956
2087
|
null,
|
|
1957
2088
|
// narrative
|
|
1958
2089
|
JSON.stringify(metadata),
|
|
1959
|
-
// facts =
|
|
2090
|
+
// facts = JSON metadata
|
|
1960
2091
|
data.concepts?.join(", ") || null,
|
|
1961
2092
|
data.files?.join(", ") || null,
|
|
1962
2093
|
null,
|
|
1963
|
-
// filesModified: knowledge
|
|
2094
|
+
// filesModified: knowledge doesn't modify files
|
|
1964
2095
|
0,
|
|
1965
2096
|
// prompt_number
|
|
1966
2097
|
contentHash,
|
|
@@ -2071,8 +2202,8 @@ var KiroMemorySDK = class {
|
|
|
2071
2202
|
return this.project;
|
|
2072
2203
|
}
|
|
2073
2204
|
/**
|
|
2074
|
-
*
|
|
2075
|
-
*
|
|
2205
|
+
* Hybrid search: vector search + keyword FTS5
|
|
2206
|
+
* Requires HybridSearch initialization (embedding service)
|
|
2076
2207
|
*/
|
|
2077
2208
|
async hybridSearch(query, options = {}) {
|
|
2078
2209
|
const hybridSearch2 = getHybridSearch();
|
|
@@ -2082,8 +2213,8 @@ var KiroMemorySDK = class {
|
|
|
2082
2213
|
});
|
|
2083
2214
|
}
|
|
2084
2215
|
/**
|
|
2085
|
-
*
|
|
2086
|
-
*
|
|
2216
|
+
* Semantic-only search (vector search)
|
|
2217
|
+
* Returns results based on cosine similarity with embeddings
|
|
2087
2218
|
*/
|
|
2088
2219
|
async semanticSearch(query, options = {}) {
|
|
2089
2220
|
const embeddingService2 = getEmbeddingService();
|
|
@@ -2118,21 +2249,21 @@ var KiroMemorySDK = class {
|
|
|
2118
2249
|
}));
|
|
2119
2250
|
}
|
|
2120
2251
|
/**
|
|
2121
|
-
*
|
|
2252
|
+
* Generate embeddings for observations that don't have them yet
|
|
2122
2253
|
*/
|
|
2123
2254
|
async backfillEmbeddings(batchSize = 50) {
|
|
2124
2255
|
const vectorSearch2 = getVectorSearch();
|
|
2125
2256
|
return vectorSearch2.backfillEmbeddings(this.db.db, batchSize);
|
|
2126
2257
|
}
|
|
2127
2258
|
/**
|
|
2128
|
-
*
|
|
2259
|
+
* Embedding statistics in the database
|
|
2129
2260
|
*/
|
|
2130
2261
|
getEmbeddingStats() {
|
|
2131
2262
|
const vectorSearch2 = getVectorSearch();
|
|
2132
2263
|
return vectorSearch2.getStats(this.db.db);
|
|
2133
2264
|
}
|
|
2134
2265
|
/**
|
|
2135
|
-
*
|
|
2266
|
+
* Initialize the embedding service (lazy, call before hybridSearch)
|
|
2136
2267
|
*/
|
|
2137
2268
|
async initializeEmbeddings() {
|
|
2138
2269
|
const hybridSearch2 = getHybridSearch();
|
|
@@ -2140,10 +2271,10 @@ var KiroMemorySDK = class {
|
|
|
2140
2271
|
return getEmbeddingService().isAvailable();
|
|
2141
2272
|
}
|
|
2142
2273
|
/**
|
|
2143
|
-
*
|
|
2274
|
+
* Smart context with 4-signal ranking and token budget.
|
|
2144
2275
|
*
|
|
2145
|
-
*
|
|
2146
|
-
*
|
|
2276
|
+
* If query present: uses HybridSearch with SEARCH_WEIGHTS.
|
|
2277
|
+
* If no query: ranking by recency + project match (CONTEXT_WEIGHTS).
|
|
2147
2278
|
*/
|
|
2148
2279
|
async getSmartContext(options = {}) {
|
|
2149
2280
|
const tokenBudget = options.tokenBudget || parseInt(process.env.KIRO_MEMORY_CONTEXT_TOKENS || "0", 10) || 2e3;
|
|
@@ -2217,8 +2348,8 @@ var KiroMemorySDK = class {
|
|
|
2217
2348
|
};
|
|
2218
2349
|
}
|
|
2219
2350
|
/**
|
|
2220
|
-
*
|
|
2221
|
-
*
|
|
2351
|
+
* Detect stale observations (files modified after creation) and mark them in DB.
|
|
2352
|
+
* Returns the number of observations marked as stale.
|
|
2222
2353
|
*/
|
|
2223
2354
|
async detectStaleObservations() {
|
|
2224
2355
|
const staleObs = getStaleObservations(this.db.db, this.project);
|
|
@@ -2229,14 +2360,14 @@ var KiroMemorySDK = class {
|
|
|
2229
2360
|
return staleObs.length;
|
|
2230
2361
|
}
|
|
2231
2362
|
/**
|
|
2232
|
-
*
|
|
2233
|
-
*
|
|
2363
|
+
* Consolidate duplicate observations on the same file and type.
|
|
2364
|
+
* Groups by (project, type, files_modified), keeps the most recent.
|
|
2234
2365
|
*/
|
|
2235
2366
|
async consolidateObservations(options = {}) {
|
|
2236
2367
|
return consolidateObservations(this.db.db, this.project, options);
|
|
2237
2368
|
}
|
|
2238
2369
|
/**
|
|
2239
|
-
*
|
|
2370
|
+
* Decay statistics: total, stale, never accessed, recently accessed.
|
|
2240
2371
|
*/
|
|
2241
2372
|
async getDecayStats() {
|
|
2242
2373
|
const total = this.db.db.query(
|
|
@@ -2255,8 +2386,8 @@ var KiroMemorySDK = class {
|
|
|
2255
2386
|
return { total, stale, neverAccessed, recentlyAccessed };
|
|
2256
2387
|
}
|
|
2257
2388
|
/**
|
|
2258
|
-
*
|
|
2259
|
-
*
|
|
2389
|
+
* Create a structured checkpoint for session resume.
|
|
2390
|
+
* Automatically saves a context_snapshot with the last 10 observations.
|
|
2260
2391
|
*/
|
|
2261
2392
|
async createCheckpoint(sessionId, data) {
|
|
2262
2393
|
const recentObs = getObservationsByProject(this.db.db, this.project, 10);
|
|
@@ -2273,21 +2404,21 @@ var KiroMemorySDK = class {
|
|
|
2273
2404
|
});
|
|
2274
2405
|
}
|
|
2275
2406
|
/**
|
|
2276
|
-
*
|
|
2407
|
+
* Retrieve the latest checkpoint of a specific session.
|
|
2277
2408
|
*/
|
|
2278
2409
|
async getCheckpoint(sessionId) {
|
|
2279
2410
|
return getLatestCheckpoint(this.db.db, sessionId);
|
|
2280
2411
|
}
|
|
2281
2412
|
/**
|
|
2282
|
-
*
|
|
2283
|
-
*
|
|
2413
|
+
* Retrieve the latest checkpoint for the current project.
|
|
2414
|
+
* Useful for automatic resume without specifying session ID.
|
|
2284
2415
|
*/
|
|
2285
2416
|
async getLatestProjectCheckpoint() {
|
|
2286
2417
|
return getLatestCheckpointByProject(this.db.db, this.project);
|
|
2287
2418
|
}
|
|
2288
2419
|
/**
|
|
2289
|
-
*
|
|
2290
|
-
*
|
|
2420
|
+
* Generate an activity report for the current project.
|
|
2421
|
+
* Aggregates observations, sessions, summaries and files for a time period.
|
|
2291
2422
|
*/
|
|
2292
2423
|
async generateReport(options) {
|
|
2293
2424
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -2483,6 +2614,71 @@ function formatReportJson(data) {
|
|
|
2483
2614
|
return JSON.stringify(data, null, 2);
|
|
2484
2615
|
}
|
|
2485
2616
|
|
|
2617
|
+
// src/cli/banner.ts
|
|
2618
|
+
var G = [
|
|
2619
|
+
"\x1B[38;5;135m",
|
|
2620
|
+
// viola
|
|
2621
|
+
"\x1B[38;5;99m",
|
|
2622
|
+
// viola-blu
|
|
2623
|
+
"\x1B[38;5;63m",
|
|
2624
|
+
// indaco
|
|
2625
|
+
"\x1B[38;5;33m",
|
|
2626
|
+
// blu
|
|
2627
|
+
"\x1B[38;5;39m",
|
|
2628
|
+
// blu chiaro
|
|
2629
|
+
"\x1B[38;5;44m"
|
|
2630
|
+
// ciano
|
|
2631
|
+
];
|
|
2632
|
+
var R = "\x1B[0m";
|
|
2633
|
+
var B = "\x1B[1m";
|
|
2634
|
+
var D = "\x1B[2m";
|
|
2635
|
+
var U = "\x1B[4m";
|
|
2636
|
+
var GRN = "\x1B[32m";
|
|
2637
|
+
var CYN = "\x1B[36m";
|
|
2638
|
+
var LOGO = [
|
|
2639
|
+
" \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 ",
|
|
2640
|
+
" \u2588\u2588\u2551 \u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557",
|
|
2641
|
+
" \u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551",
|
|
2642
|
+
" \u2588\u2588\u2554\u2550\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551",
|
|
2643
|
+
" \u2588\u2588\u2551 \u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D",
|
|
2644
|
+
" \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D"
|
|
2645
|
+
];
|
|
2646
|
+
var MEMORY_TAG = " M E M O R Y";
|
|
2647
|
+
var LINE = "\u2500".repeat(48);
|
|
2648
|
+
function supportsColor() {
|
|
2649
|
+
if (process.env.NO_COLOR || process.env.TERM === "dumb") return false;
|
|
2650
|
+
return process.stdout.isTTY ?? false;
|
|
2651
|
+
}
|
|
2652
|
+
function printBanner(opts) {
|
|
2653
|
+
const color = supportsColor();
|
|
2654
|
+
const c = (code, text) => color ? `${code}${text}${R}` : text;
|
|
2655
|
+
console.log("");
|
|
2656
|
+
for (let i = 0; i < LOGO.length; i++) {
|
|
2657
|
+
console.log(` ${c(G[i], LOGO[i])}`);
|
|
2658
|
+
}
|
|
2659
|
+
console.log(` ${c(`${G[G.length - 1]}${B}`, MEMORY_TAG)}`);
|
|
2660
|
+
console.log("");
|
|
2661
|
+
console.log(` ${c(D, LINE)}`);
|
|
2662
|
+
console.log("");
|
|
2663
|
+
console.log(` ${c(`${GRN}${B}`, "\u2713 Installation complete!")} v${opts.version}`);
|
|
2664
|
+
console.log(` ${c(D, `Editor: ${opts.editor}`)}`);
|
|
2665
|
+
console.log("");
|
|
2666
|
+
console.log(` ${c(`${CYN}${B}`, "Installed:")}`);
|
|
2667
|
+
for (const p of opts.configPaths) {
|
|
2668
|
+
console.log(` ${c(D, "\u2192")} ${p}`);
|
|
2669
|
+
}
|
|
2670
|
+
console.log(` ${c(D, "\u2192")} Data: ${opts.dataDir}`);
|
|
2671
|
+
console.log("");
|
|
2672
|
+
console.log(` ${c(`${CYN}${B}`, "Dashboard:")} ${c(U, opts.dashboardUrl)}`);
|
|
2673
|
+
console.log(` ${c(D, "Docs: https://auritidesign.it/docs/kiro-memory/")}`);
|
|
2674
|
+
console.log("");
|
|
2675
|
+
console.log(` ${c(D, LINE)}`);
|
|
2676
|
+
console.log(` ${c(G[2], "Your AI assistant now has persistent memory.")}`);
|
|
2677
|
+
console.log(` ${c(G[3], "Every session builds on the last.")}`);
|
|
2678
|
+
console.log(` ${c(D, LINE)}`);
|
|
2679
|
+
console.log("");
|
|
2680
|
+
}
|
|
2681
|
+
|
|
2486
2682
|
// src/cli/contextkit.ts
|
|
2487
2683
|
import { execSync } from "child_process";
|
|
2488
2684
|
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync, appendFileSync as appendFileSync2 } from "fs";
|
|
@@ -2495,6 +2691,12 @@ var command = args[0];
|
|
|
2495
2691
|
var __filename = fileURLToPath2(import.meta.url);
|
|
2496
2692
|
var __dirname2 = dirname2(__filename);
|
|
2497
2693
|
var DIST_DIR = dirname2(__dirname2);
|
|
2694
|
+
var PKG_VERSION = "unknown";
|
|
2695
|
+
try {
|
|
2696
|
+
const pkgPath = join3(DIST_DIR, "..", "..", "package.json");
|
|
2697
|
+
PKG_VERSION = JSON.parse(readFileSync2(pkgPath, "utf8")).version;
|
|
2698
|
+
} catch {
|
|
2699
|
+
}
|
|
2498
2700
|
var AGENT_TEMPLATE = JSON.stringify({
|
|
2499
2701
|
name: "kiro-memory",
|
|
2500
2702
|
description: "Agent with persistent cross-session memory. Uses Kiro Memory to remember context from previous sessions and automatically save what it learns.",
|
|
@@ -2917,17 +3119,23 @@ ${aliasLine}
|
|
|
2917
3119
|
}
|
|
2918
3120
|
}
|
|
2919
3121
|
console.log("\n[4/4] Done!\n");
|
|
2920
|
-
|
|
3122
|
+
printBanner({
|
|
3123
|
+
editor: "Kiro CLI",
|
|
3124
|
+
version: PKG_VERSION,
|
|
3125
|
+
dashboardUrl: "http://localhost:3001",
|
|
3126
|
+
dataDir,
|
|
3127
|
+
configPaths: [
|
|
3128
|
+
`Agent: ${agentDestPath}`,
|
|
3129
|
+
`MCP: ${mcpFilePath}`,
|
|
3130
|
+
`Steering: ${steeringDestPath}`
|
|
3131
|
+
]
|
|
3132
|
+
});
|
|
2921
3133
|
console.log(" Start Kiro with memory:");
|
|
2922
3134
|
if (aliasAlreadySet) {
|
|
2923
|
-
console.log(" \x1B[1mkiro\x1B[0m");
|
|
3135
|
+
console.log(" \x1B[1mkiro\x1B[0m\n");
|
|
2924
3136
|
} else {
|
|
2925
|
-
console.log(" \x1B[1mkiro-cli --agent kiro-memory\x1B[0m");
|
|
3137
|
+
console.log(" \x1B[1mkiro-cli --agent kiro-memory\x1B[0m\n");
|
|
2926
3138
|
}
|
|
2927
|
-
console.log("");
|
|
2928
|
-
console.log(" The worker starts automatically when a Kiro session begins.");
|
|
2929
|
-
console.log(` Web dashboard: \x1B[4mhttp://localhost:3001\x1B[0m
|
|
2930
|
-
`);
|
|
2931
3139
|
}
|
|
2932
3140
|
var CLAUDE_CODE_STEERING = `# Kiro Memory - Persistent Cross-Session Memory
|
|
2933
3141
|
|
|
@@ -3052,12 +3260,17 @@ async function installClaudeCode() {
|
|
|
3052
3260
|
}
|
|
3053
3261
|
console.log(` \u2192 Data dir: ${dataDir}`);
|
|
3054
3262
|
console.log("\n[3/3] Done!\n");
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3263
|
+
printBanner({
|
|
3264
|
+
editor: "Claude Code",
|
|
3265
|
+
version: PKG_VERSION,
|
|
3266
|
+
dashboardUrl: "http://localhost:3001",
|
|
3267
|
+
dataDir,
|
|
3268
|
+
configPaths: [
|
|
3269
|
+
`Hooks: ${settingsPath}`,
|
|
3270
|
+
`MCP: ${mcpPath}`,
|
|
3271
|
+
`Steering: ${steeringPath}`
|
|
3272
|
+
]
|
|
3273
|
+
});
|
|
3061
3274
|
}
|
|
3062
3275
|
async function installCursor() {
|
|
3063
3276
|
console.log("\n=== Kiro Memory - Cursor Installation ===\n");
|
|
@@ -3139,12 +3352,16 @@ async function installCursor() {
|
|
|
3139
3352
|
console.log(` \u2192 MCP config: ${mcpPath}`);
|
|
3140
3353
|
console.log(` \u2192 Data dir: ${dataDir}`);
|
|
3141
3354
|
console.log("\n[3/3] Done!\n");
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3355
|
+
printBanner({
|
|
3356
|
+
editor: "Cursor",
|
|
3357
|
+
version: PKG_VERSION,
|
|
3358
|
+
dashboardUrl: "http://localhost:3001",
|
|
3359
|
+
dataDir,
|
|
3360
|
+
configPaths: [
|
|
3361
|
+
`Hooks: ${hooksPath}`,
|
|
3362
|
+
`MCP: ${mcpPath}`
|
|
3363
|
+
]
|
|
3364
|
+
});
|
|
3148
3365
|
}
|
|
3149
3366
|
async function installWindsurf() {
|
|
3150
3367
|
console.log("\n=== Kiro Memory - Windsurf Installation ===\n");
|
|
@@ -3193,12 +3410,15 @@ async function installWindsurf() {
|
|
|
3193
3410
|
console.log(` \u2192 MCP config: ${mcpPath}`);
|
|
3194
3411
|
console.log(` \u2192 Data dir: ${dataDir}`);
|
|
3195
3412
|
console.log("\n[3/3] Done!\n");
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3413
|
+
printBanner({
|
|
3414
|
+
editor: "Windsurf",
|
|
3415
|
+
version: PKG_VERSION,
|
|
3416
|
+
dashboardUrl: "http://localhost:3001",
|
|
3417
|
+
dataDir,
|
|
3418
|
+
configPaths: [
|
|
3419
|
+
`MCP: ${mcpPath}`
|
|
3420
|
+
]
|
|
3421
|
+
});
|
|
3202
3422
|
console.log(" \x1B[2mTip: Add a .windsurfrules file to your project with instructions");
|
|
3203
3423
|
console.log(" to use the kiro-memory MCP tools for persistent context.\x1B[0m\n");
|
|
3204
3424
|
}
|
|
@@ -3255,12 +3475,15 @@ async function installCline() {
|
|
|
3255
3475
|
console.log(` \u2192 MCP config: ${mcpPath}`);
|
|
3256
3476
|
console.log(` \u2192 Data dir: ${dataDir}`);
|
|
3257
3477
|
console.log("\n[3/3] Done!\n");
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3478
|
+
printBanner({
|
|
3479
|
+
editor: "Cline",
|
|
3480
|
+
version: PKG_VERSION,
|
|
3481
|
+
dashboardUrl: "http://localhost:3001",
|
|
3482
|
+
dataDir,
|
|
3483
|
+
configPaths: [
|
|
3484
|
+
`MCP: ${mcpPath}`
|
|
3485
|
+
]
|
|
3486
|
+
});
|
|
3264
3487
|
console.log(" \x1B[2mTip: Add a .clinerules file to your project with instructions");
|
|
3265
3488
|
console.log(" to use the kiro-memory MCP tools for persistent context.\x1B[0m\n");
|
|
3266
3489
|
}
|
|
@@ -3322,13 +3545,13 @@ async function runDoctor() {
|
|
|
3322
3545
|
checks.push({
|
|
3323
3546
|
name: "Claude Code hooks",
|
|
3324
3547
|
ok: true,
|
|
3325
|
-
// Non-blocking:
|
|
3548
|
+
// Non-blocking: optional installation
|
|
3326
3549
|
message: claudeHooksOk ? "Configured in ~/.claude/settings.json" : "Not configured (optional: run kiro-memory install --claude-code)"
|
|
3327
3550
|
});
|
|
3328
3551
|
checks.push({
|
|
3329
3552
|
name: "Claude Code MCP",
|
|
3330
3553
|
ok: true,
|
|
3331
|
-
// Non-blocking:
|
|
3554
|
+
// Non-blocking: optional installation
|
|
3332
3555
|
message: claudeMcpOk ? "kiro-memory registered in ~/.mcp.json" : "Not configured (optional: run kiro-memory install --claude-code)"
|
|
3333
3556
|
});
|
|
3334
3557
|
const cursorDir = join3(homedir3(), ".cursor");
|
|
@@ -3357,13 +3580,13 @@ async function runDoctor() {
|
|
|
3357
3580
|
checks.push({
|
|
3358
3581
|
name: "Cursor hooks",
|
|
3359
3582
|
ok: true,
|
|
3360
|
-
// Non-blocking:
|
|
3583
|
+
// Non-blocking: optional installation
|
|
3361
3584
|
message: cursorHooksOk ? "Configured in ~/.cursor/hooks.json" : "Not configured (optional: run kiro-memory install --cursor)"
|
|
3362
3585
|
});
|
|
3363
3586
|
checks.push({
|
|
3364
3587
|
name: "Cursor MCP",
|
|
3365
3588
|
ok: true,
|
|
3366
|
-
// Non-blocking:
|
|
3589
|
+
// Non-blocking: optional installation
|
|
3367
3590
|
message: cursorMcpOk ? "kiro-memory registered in ~/.cursor/mcp.json" : "Not configured (optional: run kiro-memory install --cursor)"
|
|
3368
3591
|
});
|
|
3369
3592
|
const windsurfMcpPath = join3(homedir3(), ".codeium", "windsurf", "mcp_config.json");
|
|
@@ -3378,7 +3601,7 @@ async function runDoctor() {
|
|
|
3378
3601
|
checks.push({
|
|
3379
3602
|
name: "Windsurf MCP",
|
|
3380
3603
|
ok: true,
|
|
3381
|
-
// Non-blocking:
|
|
3604
|
+
// Non-blocking: optional installation
|
|
3382
3605
|
message: windsurfMcpOk ? "kiro-memory registered in ~/.codeium/windsurf/mcp_config.json" : "Not configured (optional: run kiro-memory install --windsurf)"
|
|
3383
3606
|
});
|
|
3384
3607
|
const clinePlatform = process.platform;
|
|
@@ -3400,7 +3623,7 @@ async function runDoctor() {
|
|
|
3400
3623
|
checks.push({
|
|
3401
3624
|
name: "Cline MCP",
|
|
3402
3625
|
ok: true,
|
|
3403
|
-
// Non-blocking:
|
|
3626
|
+
// Non-blocking: optional installation
|
|
3404
3627
|
message: clineMcpOk ? `kiro-memory registered in cline_mcp_settings.json` : "Not configured (optional: run kiro-memory install --cline)"
|
|
3405
3628
|
});
|
|
3406
3629
|
let workerOk = false;
|