claude-memory-layer 1.0.8 → 1.0.10

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.
Files changed (44) hide show
  1. package/.claude/settings.local.json +7 -1
  2. package/.claude-memory/test.sqlite +0 -0
  3. package/.history/package_20260202114053.json +49 -0
  4. package/.history/package_20260202121115.json +49 -0
  5. package/HANDOFF.md +92 -0
  6. package/dist/cli/index.js +1257 -74
  7. package/dist/cli/index.js.map +4 -4
  8. package/dist/core/index.js +1111 -47
  9. package/dist/core/index.js.map +4 -4
  10. package/dist/hooks/post-tool-use.js +5693 -0
  11. package/dist/hooks/post-tool-use.js.map +7 -0
  12. package/dist/hooks/session-end.js +1224 -67
  13. package/dist/hooks/session-end.js.map +4 -4
  14. package/dist/hooks/session-start.js +1219 -66
  15. package/dist/hooks/session-start.js.map +4 -4
  16. package/dist/hooks/stop.js +1224 -67
  17. package/dist/hooks/stop.js.map +4 -4
  18. package/dist/hooks/user-prompt-submit.js +1252 -98
  19. package/dist/hooks/user-prompt-submit.js.map +4 -4
  20. package/dist/server/api/index.js +1252 -73
  21. package/dist/server/api/index.js.map +4 -4
  22. package/dist/server/index.js +1252 -73
  23. package/dist/server/index.js.map +4 -4
  24. package/dist/services/memory-service.js +1246 -68
  25. package/dist/services/memory-service.js.map +4 -4
  26. package/dist/ui/app.js +304 -0
  27. package/dist/ui/index.html +195 -1188
  28. package/dist/ui/style.css +595 -0
  29. package/package.json +3 -1
  30. package/scripts/build.ts +2 -0
  31. package/src/core/event-store.ts +18 -0
  32. package/src/core/index.ts +3 -0
  33. package/src/core/retriever.ts +4 -1
  34. package/src/core/sqlite-event-store.ts +947 -0
  35. package/src/core/sqlite-wrapper.ts +108 -0
  36. package/src/core/sync-worker.ts +228 -0
  37. package/src/core/vector-worker.ts +44 -14
  38. package/src/hooks/user-prompt-submit.ts +40 -17
  39. package/src/server/api/stats.ts +37 -7
  40. package/src/services/memory-service.ts +239 -43
  41. package/src/ui/app.js +304 -0
  42. package/src/ui/index.html +195 -1188
  43. package/src/ui/style.css +595 -0
  44. package/test_access.js +49 -0
@@ -700,26 +700,1053 @@ var EventStore = class {
700
700
  tags: row.tags ? JSON.parse(row.tags) : void 0
701
701
  }));
702
702
  }
703
+ /**
704
+ * Increment access count for events (stub for compatibility)
705
+ */
706
+ async incrementAccessCount(eventIds) {
707
+ return Promise.resolve();
708
+ }
709
+ /**
710
+ * Get most accessed memories (stub for compatibility)
711
+ */
712
+ async getMostAccessed(limit = 10) {
713
+ return [];
714
+ }
715
+ /**
716
+ * Close database connection
717
+ */
718
+ async close() {
719
+ await dbClose(this.db);
720
+ }
721
+ /**
722
+ * Convert database row to MemoryEvent
723
+ */
724
+ rowToEvent(row) {
725
+ return {
726
+ id: row.id,
727
+ eventType: row.event_type,
728
+ sessionId: row.session_id,
729
+ timestamp: toDate(row.timestamp),
730
+ content: row.content,
731
+ canonicalKey: row.canonical_key,
732
+ dedupeKey: row.dedupe_key,
733
+ metadata: row.metadata ? JSON.parse(row.metadata) : void 0
734
+ };
735
+ }
736
+ };
737
+
738
+ // src/core/sqlite-event-store.ts
739
+ import { randomUUID as randomUUID2 } from "crypto";
740
+
741
+ // src/core/sqlite-wrapper.ts
742
+ import Database from "better-sqlite3";
743
+ function createSQLiteDatabase(path2, options) {
744
+ const db = new Database(path2, {
745
+ readonly: options?.readonly ?? false
746
+ });
747
+ if (!options?.readonly && (options?.walMode ?? true)) {
748
+ db.pragma("journal_mode = WAL");
749
+ db.pragma("synchronous = NORMAL");
750
+ db.pragma("busy_timeout = 5000");
751
+ }
752
+ return db;
753
+ }
754
+ function sqliteRun(db, sql, params = []) {
755
+ const stmt = db.prepare(sql);
756
+ return stmt.run(...params);
757
+ }
758
+ function sqliteAll(db, sql, params = []) {
759
+ const stmt = db.prepare(sql);
760
+ return stmt.all(...params);
761
+ }
762
+ function sqliteGet(db, sql, params = []) {
763
+ const stmt = db.prepare(sql);
764
+ return stmt.get(...params);
765
+ }
766
+ function sqliteExec(db, sql) {
767
+ db.exec(sql);
768
+ }
769
+ function sqliteClose(db) {
770
+ db.close();
771
+ }
772
+ function toDateFromSQLite(value) {
773
+ if (value instanceof Date)
774
+ return value;
775
+ if (typeof value === "string")
776
+ return new Date(value);
777
+ if (typeof value === "number")
778
+ return new Date(value);
779
+ return new Date(String(value));
780
+ }
781
+ function toSQLiteTimestamp(date) {
782
+ return date.toISOString();
783
+ }
784
+
785
+ // src/core/sqlite-event-store.ts
786
+ var SQLiteEventStore = class {
787
+ constructor(dbPath, options) {
788
+ this.dbPath = dbPath;
789
+ this.readOnly = options?.readonly ?? false;
790
+ this.db = createSQLiteDatabase(dbPath, {
791
+ readonly: this.readOnly,
792
+ walMode: !this.readOnly
793
+ });
794
+ }
795
+ db;
796
+ initialized = false;
797
+ readOnly;
798
+ /**
799
+ * Initialize database schema
800
+ */
801
+ async initialize() {
802
+ if (this.initialized)
803
+ return;
804
+ if (this.readOnly) {
805
+ this.initialized = true;
806
+ return;
807
+ }
808
+ sqliteExec(this.db, `
809
+ -- L0 EventStore: Single Source of Truth (immutable, append-only)
810
+ CREATE TABLE IF NOT EXISTS events (
811
+ id TEXT PRIMARY KEY,
812
+ event_type TEXT NOT NULL,
813
+ session_id TEXT NOT NULL,
814
+ timestamp TEXT NOT NULL,
815
+ content TEXT NOT NULL,
816
+ canonical_key TEXT NOT NULL,
817
+ dedupe_key TEXT UNIQUE,
818
+ metadata TEXT,
819
+ access_count INTEGER DEFAULT 0,
820
+ last_accessed_at TEXT
821
+ );
822
+
823
+ -- Dedup table for idempotency
824
+ CREATE TABLE IF NOT EXISTS event_dedup (
825
+ dedupe_key TEXT PRIMARY KEY,
826
+ event_id TEXT NOT NULL,
827
+ created_at TEXT DEFAULT (datetime('now'))
828
+ );
829
+
830
+ -- Session metadata
831
+ CREATE TABLE IF NOT EXISTS sessions (
832
+ id TEXT PRIMARY KEY,
833
+ started_at TEXT NOT NULL,
834
+ ended_at TEXT,
835
+ project_path TEXT,
836
+ summary TEXT,
837
+ tags TEXT
838
+ );
839
+
840
+ -- Insights (derived data, rebuildable)
841
+ CREATE TABLE IF NOT EXISTS insights (
842
+ id TEXT PRIMARY KEY,
843
+ insight_type TEXT NOT NULL,
844
+ content TEXT NOT NULL,
845
+ canonical_key TEXT NOT NULL,
846
+ confidence REAL,
847
+ source_events TEXT,
848
+ created_at TEXT,
849
+ last_updated TEXT
850
+ );
851
+
852
+ -- Embedding Outbox (Single-Writer Pattern)
853
+ CREATE TABLE IF NOT EXISTS embedding_outbox (
854
+ id TEXT PRIMARY KEY,
855
+ event_id TEXT NOT NULL,
856
+ content TEXT NOT NULL,
857
+ status TEXT DEFAULT 'pending',
858
+ retry_count INTEGER DEFAULT 0,
859
+ created_at TEXT DEFAULT (datetime('now')),
860
+ processed_at TEXT,
861
+ error_message TEXT
862
+ );
863
+
864
+ -- Projection offset tracking
865
+ CREATE TABLE IF NOT EXISTS projection_offsets (
866
+ projection_name TEXT PRIMARY KEY,
867
+ last_event_id TEXT,
868
+ last_timestamp TEXT,
869
+ updated_at TEXT DEFAULT (datetime('now'))
870
+ );
871
+
872
+ -- Memory level tracking
873
+ CREATE TABLE IF NOT EXISTS memory_levels (
874
+ event_id TEXT PRIMARY KEY,
875
+ level TEXT NOT NULL DEFAULT 'L0',
876
+ promoted_at TEXT DEFAULT (datetime('now'))
877
+ );
878
+
879
+ -- Entries (immutable memory units)
880
+ CREATE TABLE IF NOT EXISTS entries (
881
+ entry_id TEXT PRIMARY KEY,
882
+ created_ts TEXT NOT NULL,
883
+ entry_type TEXT NOT NULL,
884
+ title TEXT NOT NULL,
885
+ content_json TEXT NOT NULL,
886
+ stage TEXT NOT NULL DEFAULT 'raw',
887
+ status TEXT DEFAULT 'active',
888
+ superseded_by TEXT,
889
+ build_id TEXT,
890
+ evidence_json TEXT,
891
+ canonical_key TEXT,
892
+ created_at TEXT DEFAULT (datetime('now'))
893
+ );
894
+
895
+ -- Entities (task/condition/artifact)
896
+ CREATE TABLE IF NOT EXISTS entities (
897
+ entity_id TEXT PRIMARY KEY,
898
+ entity_type TEXT NOT NULL,
899
+ canonical_key TEXT NOT NULL,
900
+ title TEXT NOT NULL,
901
+ stage TEXT NOT NULL DEFAULT 'raw',
902
+ status TEXT NOT NULL DEFAULT 'active',
903
+ current_json TEXT NOT NULL,
904
+ title_norm TEXT,
905
+ search_text TEXT,
906
+ created_at TEXT DEFAULT (datetime('now')),
907
+ updated_at TEXT DEFAULT (datetime('now'))
908
+ );
909
+
910
+ -- Entity aliases for canonical key lookup
911
+ CREATE TABLE IF NOT EXISTS entity_aliases (
912
+ entity_type TEXT NOT NULL,
913
+ canonical_key TEXT NOT NULL,
914
+ entity_id TEXT NOT NULL,
915
+ is_primary INTEGER DEFAULT 0,
916
+ created_at TEXT DEFAULT (datetime('now')),
917
+ PRIMARY KEY(entity_type, canonical_key)
918
+ );
919
+
920
+ -- Edges (relationships between entries/entities)
921
+ CREATE TABLE IF NOT EXISTS edges (
922
+ edge_id TEXT PRIMARY KEY,
923
+ src_type TEXT NOT NULL,
924
+ src_id TEXT NOT NULL,
925
+ rel_type TEXT NOT NULL,
926
+ dst_type TEXT NOT NULL,
927
+ dst_id TEXT NOT NULL,
928
+ meta_json TEXT,
929
+ created_at TEXT DEFAULT (datetime('now'))
930
+ );
931
+
932
+ -- Vector Outbox V2 Table
933
+ CREATE TABLE IF NOT EXISTS vector_outbox (
934
+ job_id TEXT PRIMARY KEY,
935
+ item_kind TEXT NOT NULL,
936
+ item_id TEXT NOT NULL,
937
+ embedding_version TEXT NOT NULL,
938
+ status TEXT NOT NULL DEFAULT 'pending',
939
+ retry_count INTEGER DEFAULT 0,
940
+ error TEXT,
941
+ created_at TEXT DEFAULT (datetime('now')),
942
+ updated_at TEXT DEFAULT (datetime('now')),
943
+ UNIQUE(item_kind, item_id, embedding_version)
944
+ );
945
+
946
+ -- Build Runs
947
+ CREATE TABLE IF NOT EXISTS build_runs (
948
+ build_id TEXT PRIMARY KEY,
949
+ started_at TEXT NOT NULL,
950
+ finished_at TEXT,
951
+ extractor_model TEXT NOT NULL,
952
+ extractor_prompt_hash TEXT NOT NULL,
953
+ embedder_model TEXT NOT NULL,
954
+ embedding_version TEXT NOT NULL,
955
+ idris_version TEXT NOT NULL,
956
+ schema_version TEXT NOT NULL,
957
+ status TEXT NOT NULL DEFAULT 'running',
958
+ error TEXT
959
+ );
960
+
961
+ -- Pipeline Metrics
962
+ CREATE TABLE IF NOT EXISTS pipeline_metrics (
963
+ id TEXT PRIMARY KEY,
964
+ ts TEXT NOT NULL,
965
+ stage TEXT NOT NULL,
966
+ latency_ms REAL NOT NULL,
967
+ success INTEGER NOT NULL,
968
+ error TEXT,
969
+ session_id TEXT
970
+ );
971
+
972
+ -- Working Set table (active memory window)
973
+ CREATE TABLE IF NOT EXISTS working_set (
974
+ id TEXT PRIMARY KEY,
975
+ event_id TEXT NOT NULL,
976
+ added_at TEXT DEFAULT (datetime('now')),
977
+ relevance_score REAL DEFAULT 1.0,
978
+ topics TEXT,
979
+ expires_at TEXT
980
+ );
981
+
982
+ -- Consolidated Memories table (long-term integrated memories)
983
+ CREATE TABLE IF NOT EXISTS consolidated_memories (
984
+ memory_id TEXT PRIMARY KEY,
985
+ summary TEXT NOT NULL,
986
+ topics TEXT,
987
+ source_events TEXT,
988
+ confidence REAL DEFAULT 0.5,
989
+ created_at TEXT DEFAULT (datetime('now')),
990
+ accessed_at TEXT,
991
+ access_count INTEGER DEFAULT 0
992
+ );
993
+
994
+ -- Continuity Log table (tracks context transitions)
995
+ CREATE TABLE IF NOT EXISTS continuity_log (
996
+ log_id TEXT PRIMARY KEY,
997
+ from_context_id TEXT,
998
+ to_context_id TEXT,
999
+ continuity_score REAL,
1000
+ transition_type TEXT,
1001
+ created_at TEXT DEFAULT (datetime('now'))
1002
+ );
1003
+
1004
+ -- Endless Mode Config table
1005
+ CREATE TABLE IF NOT EXISTS endless_config (
1006
+ key TEXT PRIMARY KEY,
1007
+ value TEXT,
1008
+ updated_at TEXT DEFAULT (datetime('now'))
1009
+ );
1010
+
1011
+ -- Sync position tracking (for SQLite -> DuckDB sync)
1012
+ CREATE TABLE IF NOT EXISTS sync_positions (
1013
+ target_name TEXT PRIMARY KEY,
1014
+ last_event_id TEXT,
1015
+ last_timestamp TEXT,
1016
+ updated_at TEXT DEFAULT (datetime('now'))
1017
+ );
1018
+
1019
+ -- Create indexes
1020
+ CREATE INDEX IF NOT EXISTS idx_events_session ON events(session_id);
1021
+ CREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp);
1022
+ CREATE INDEX IF NOT EXISTS idx_entries_type ON entries(entry_type);
1023
+ CREATE INDEX IF NOT EXISTS idx_entries_stage ON entries(stage);
1024
+ CREATE INDEX IF NOT EXISTS idx_entries_canonical ON entries(canonical_key);
1025
+ CREATE INDEX IF NOT EXISTS idx_entities_type_key ON entities(entity_type, canonical_key);
1026
+ CREATE INDEX IF NOT EXISTS idx_entities_status ON entities(status);
1027
+ CREATE INDEX IF NOT EXISTS idx_edges_src ON edges(src_id, rel_type);
1028
+ CREATE INDEX IF NOT EXISTS idx_edges_dst ON edges(dst_id, rel_type);
1029
+ CREATE INDEX IF NOT EXISTS idx_edges_rel ON edges(rel_type);
1030
+ CREATE INDEX IF NOT EXISTS idx_outbox_status ON vector_outbox(status);
1031
+ CREATE INDEX IF NOT EXISTS idx_working_set_expires ON working_set(expires_at);
1032
+ CREATE INDEX IF NOT EXISTS idx_working_set_relevance ON working_set(relevance_score);
1033
+ CREATE INDEX IF NOT EXISTS idx_consolidated_confidence ON consolidated_memories(confidence);
1034
+ CREATE INDEX IF NOT EXISTS idx_continuity_created ON continuity_log(created_at);
1035
+ CREATE INDEX IF NOT EXISTS idx_embedding_outbox_status ON embedding_outbox(status);
1036
+
1037
+ -- FTS5 Full-Text Search for fast keyword search
1038
+ CREATE VIRTUAL TABLE IF NOT EXISTS events_fts USING fts5(
1039
+ content,
1040
+ event_id UNINDEXED,
1041
+ content='events',
1042
+ content_rowid='rowid'
1043
+ );
1044
+
1045
+ -- Triggers to keep FTS in sync with events table
1046
+ CREATE TRIGGER IF NOT EXISTS events_fts_insert AFTER INSERT ON events BEGIN
1047
+ INSERT INTO events_fts(rowid, content, event_id) VALUES (NEW.rowid, NEW.content, NEW.id);
1048
+ END;
1049
+
1050
+ CREATE TRIGGER IF NOT EXISTS events_fts_delete AFTER DELETE ON events BEGIN
1051
+ INSERT INTO events_fts(events_fts, rowid, content, event_id) VALUES('delete', OLD.rowid, OLD.content, OLD.id);
1052
+ END;
1053
+
1054
+ CREATE TRIGGER IF NOT EXISTS events_fts_update AFTER UPDATE ON events BEGIN
1055
+ INSERT INTO events_fts(events_fts, rowid, content, event_id) VALUES('delete', OLD.rowid, OLD.content, OLD.id);
1056
+ INSERT INTO events_fts(rowid, content, event_id) VALUES (NEW.rowid, NEW.content, NEW.id);
1057
+ END;
1058
+ `);
1059
+ const tableInfo = sqliteAll(this.db, "PRAGMA table_info(events)", []);
1060
+ const columnNames = tableInfo.map((col) => col.name);
1061
+ if (!columnNames.includes("access_count")) {
1062
+ try {
1063
+ sqliteExec(this.db, `
1064
+ ALTER TABLE events ADD COLUMN access_count INTEGER DEFAULT 0;
1065
+ `);
1066
+ } catch (err) {
1067
+ console.error("Error adding access_count column:", err);
1068
+ }
1069
+ }
1070
+ if (!columnNames.includes("last_accessed_at")) {
1071
+ try {
1072
+ sqliteExec(this.db, `
1073
+ ALTER TABLE events ADD COLUMN last_accessed_at TEXT;
1074
+ `);
1075
+ } catch (err) {
1076
+ console.error("Error adding last_accessed_at column:", err);
1077
+ }
1078
+ }
1079
+ try {
1080
+ sqliteExec(this.db, `
1081
+ CREATE INDEX IF NOT EXISTS idx_events_access_count ON events(access_count DESC);
1082
+ `);
1083
+ } catch (err) {
1084
+ }
1085
+ try {
1086
+ sqliteExec(this.db, `
1087
+ CREATE INDEX IF NOT EXISTS idx_events_last_accessed ON events(last_accessed_at DESC);
1088
+ `);
1089
+ } catch (err) {
1090
+ }
1091
+ this.initialized = true;
1092
+ }
1093
+ /**
1094
+ * Append event to store (Append-only, Idempotent)
1095
+ */
1096
+ async append(input) {
1097
+ await this.initialize();
1098
+ const canonicalKey = makeCanonicalKey(input.content);
1099
+ const dedupeKey = makeDedupeKey(input.content, input.sessionId);
1100
+ const existing = sqliteGet(
1101
+ this.db,
1102
+ `SELECT event_id FROM event_dedup WHERE dedupe_key = ?`,
1103
+ [dedupeKey]
1104
+ );
1105
+ if (existing) {
1106
+ return {
1107
+ success: true,
1108
+ eventId: existing.event_id,
1109
+ isDuplicate: true
1110
+ };
1111
+ }
1112
+ const id = randomUUID2();
1113
+ const timestamp = toSQLiteTimestamp(input.timestamp);
1114
+ try {
1115
+ const insertEvent = this.db.prepare(`
1116
+ INSERT INTO events (id, event_type, session_id, timestamp, content, canonical_key, dedupe_key, metadata)
1117
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
1118
+ `);
1119
+ const insertDedup = this.db.prepare(`
1120
+ INSERT INTO event_dedup (dedupe_key, event_id) VALUES (?, ?)
1121
+ `);
1122
+ const insertLevel = this.db.prepare(`
1123
+ INSERT INTO memory_levels (event_id, level) VALUES (?, 'L0')
1124
+ `);
1125
+ const transaction = this.db.transaction(() => {
1126
+ insertEvent.run(
1127
+ id,
1128
+ input.eventType,
1129
+ input.sessionId,
1130
+ timestamp,
1131
+ input.content,
1132
+ canonicalKey,
1133
+ dedupeKey,
1134
+ JSON.stringify(input.metadata || {})
1135
+ );
1136
+ insertDedup.run(dedupeKey, id);
1137
+ insertLevel.run(id);
1138
+ });
1139
+ transaction();
1140
+ return { success: true, eventId: id, isDuplicate: false };
1141
+ } catch (error) {
1142
+ return {
1143
+ success: false,
1144
+ error: error instanceof Error ? error.message : String(error)
1145
+ };
1146
+ }
1147
+ }
1148
+ /**
1149
+ * Get events by session ID
1150
+ */
1151
+ async getSessionEvents(sessionId) {
1152
+ await this.initialize();
1153
+ const rows = sqliteAll(
1154
+ this.db,
1155
+ `SELECT * FROM events WHERE session_id = ? ORDER BY timestamp ASC`,
1156
+ [sessionId]
1157
+ );
1158
+ return rows.map(this.rowToEvent);
1159
+ }
1160
+ /**
1161
+ * Get recent events
1162
+ */
1163
+ async getRecentEvents(limit = 100) {
1164
+ await this.initialize();
1165
+ const rows = sqliteAll(
1166
+ this.db,
1167
+ `SELECT * FROM events ORDER BY timestamp DESC LIMIT ?`,
1168
+ [limit]
1169
+ );
1170
+ return rows.map(this.rowToEvent);
1171
+ }
1172
+ /**
1173
+ * Get event by ID
1174
+ */
1175
+ async getEvent(id) {
1176
+ await this.initialize();
1177
+ const row = sqliteGet(
1178
+ this.db,
1179
+ `SELECT * FROM events WHERE id = ?`,
1180
+ [id]
1181
+ );
1182
+ if (!row)
1183
+ return null;
1184
+ return this.rowToEvent(row);
1185
+ }
1186
+ /**
1187
+ * Get events since a timestamp (for sync)
1188
+ */
1189
+ async getEventsSince(timestamp, limit = 1e3) {
1190
+ await this.initialize();
1191
+ const rows = sqliteAll(
1192
+ this.db,
1193
+ `SELECT * FROM events WHERE timestamp > ? ORDER BY timestamp ASC LIMIT ?`,
1194
+ [timestamp, limit]
1195
+ );
1196
+ return rows.map(this.rowToEvent);
1197
+ }
1198
+ /**
1199
+ * Create or update session
1200
+ */
1201
+ async upsertSession(session) {
1202
+ await this.initialize();
1203
+ const existing = sqliteGet(
1204
+ this.db,
1205
+ `SELECT id FROM sessions WHERE id = ?`,
1206
+ [session.id]
1207
+ );
1208
+ if (!existing) {
1209
+ sqliteRun(
1210
+ this.db,
1211
+ `INSERT INTO sessions (id, started_at, project_path, tags)
1212
+ VALUES (?, ?, ?, ?)`,
1213
+ [
1214
+ session.id,
1215
+ toSQLiteTimestamp(session.startedAt || /* @__PURE__ */ new Date()),
1216
+ session.projectPath || null,
1217
+ JSON.stringify(session.tags || [])
1218
+ ]
1219
+ );
1220
+ } else {
1221
+ const updates = [];
1222
+ const values = [];
1223
+ if (session.endedAt) {
1224
+ updates.push("ended_at = ?");
1225
+ values.push(toSQLiteTimestamp(session.endedAt));
1226
+ }
1227
+ if (session.summary) {
1228
+ updates.push("summary = ?");
1229
+ values.push(session.summary);
1230
+ }
1231
+ if (session.tags) {
1232
+ updates.push("tags = ?");
1233
+ values.push(JSON.stringify(session.tags));
1234
+ }
1235
+ if (updates.length > 0) {
1236
+ values.push(session.id);
1237
+ sqliteRun(
1238
+ this.db,
1239
+ `UPDATE sessions SET ${updates.join(", ")} WHERE id = ?`,
1240
+ values
1241
+ );
1242
+ }
1243
+ }
1244
+ }
1245
+ /**
1246
+ * Get session by ID
1247
+ */
1248
+ async getSession(id) {
1249
+ await this.initialize();
1250
+ const row = sqliteGet(
1251
+ this.db,
1252
+ `SELECT * FROM sessions WHERE id = ?`,
1253
+ [id]
1254
+ );
1255
+ if (!row)
1256
+ return null;
1257
+ return {
1258
+ id: row.id,
1259
+ startedAt: toDateFromSQLite(row.started_at),
1260
+ endedAt: row.ended_at ? toDateFromSQLite(row.ended_at) : void 0,
1261
+ projectPath: row.project_path,
1262
+ summary: row.summary,
1263
+ tags: row.tags ? JSON.parse(row.tags) : void 0
1264
+ };
1265
+ }
1266
+ /**
1267
+ * Get all sessions
1268
+ */
1269
+ async getAllSessions() {
1270
+ await this.initialize();
1271
+ const rows = sqliteAll(
1272
+ this.db,
1273
+ `SELECT * FROM sessions ORDER BY started_at DESC`
1274
+ );
1275
+ return rows.map((row) => ({
1276
+ id: row.id,
1277
+ startedAt: toDateFromSQLite(row.started_at),
1278
+ endedAt: row.ended_at ? toDateFromSQLite(row.ended_at) : void 0,
1279
+ projectPath: row.project_path,
1280
+ summary: row.summary,
1281
+ tags: row.tags ? JSON.parse(row.tags) : void 0
1282
+ }));
1283
+ }
1284
+ /**
1285
+ * Add to embedding outbox
1286
+ */
1287
+ async enqueueForEmbedding(eventId, content) {
1288
+ await this.initialize();
1289
+ const id = randomUUID2();
1290
+ sqliteRun(
1291
+ this.db,
1292
+ `INSERT INTO embedding_outbox (id, event_id, content, status, retry_count)
1293
+ VALUES (?, ?, ?, 'pending', 0)`,
1294
+ [id, eventId, content]
1295
+ );
1296
+ return id;
1297
+ }
1298
+ /**
1299
+ * Get pending outbox items
1300
+ */
1301
+ async getPendingOutboxItems(limit = 32) {
1302
+ await this.initialize();
1303
+ const pending = sqliteAll(
1304
+ this.db,
1305
+ `SELECT * FROM embedding_outbox
1306
+ WHERE status = 'pending'
1307
+ ORDER BY created_at
1308
+ LIMIT ?`,
1309
+ [limit]
1310
+ );
1311
+ if (pending.length === 0)
1312
+ return [];
1313
+ const ids = pending.map((r) => r.id);
1314
+ const placeholders = ids.map(() => "?").join(",");
1315
+ sqliteRun(
1316
+ this.db,
1317
+ `UPDATE embedding_outbox SET status = 'processing' WHERE id IN (${placeholders})`,
1318
+ ids
1319
+ );
1320
+ return pending.map((row) => ({
1321
+ id: row.id,
1322
+ eventId: row.event_id,
1323
+ content: row.content,
1324
+ status: "processing",
1325
+ retryCount: row.retry_count,
1326
+ createdAt: toDateFromSQLite(row.created_at),
1327
+ errorMessage: row.error_message
1328
+ }));
1329
+ }
1330
+ /**
1331
+ * Mark outbox items as done
1332
+ */
1333
+ async completeOutboxItems(ids) {
1334
+ if (ids.length === 0)
1335
+ return;
1336
+ const placeholders = ids.map(() => "?").join(",");
1337
+ sqliteRun(
1338
+ this.db,
1339
+ `DELETE FROM embedding_outbox WHERE id IN (${placeholders})`,
1340
+ ids
1341
+ );
1342
+ }
1343
+ /**
1344
+ * Mark outbox items as failed
1345
+ */
1346
+ async failOutboxItems(ids, error) {
1347
+ if (ids.length === 0)
1348
+ return;
1349
+ const placeholders = ids.map(() => "?").join(",");
1350
+ sqliteRun(
1351
+ this.db,
1352
+ `UPDATE embedding_outbox
1353
+ SET status = CASE WHEN retry_count >= 3 THEN 'failed' ELSE 'pending' END,
1354
+ retry_count = retry_count + 1,
1355
+ error_message = ?
1356
+ WHERE id IN (${placeholders})`,
1357
+ [error, ...ids]
1358
+ );
1359
+ }
1360
+ /**
1361
+ * Update memory level
1362
+ */
1363
+ async updateMemoryLevel(eventId, level) {
1364
+ await this.initialize();
1365
+ sqliteRun(
1366
+ this.db,
1367
+ `UPDATE memory_levels SET level = ?, promoted_at = datetime('now') WHERE event_id = ?`,
1368
+ [level, eventId]
1369
+ );
1370
+ }
1371
+ /**
1372
+ * Get memory level statistics
1373
+ */
1374
+ async getLevelStats() {
1375
+ await this.initialize();
1376
+ const rows = sqliteAll(
1377
+ this.db,
1378
+ `SELECT level, COUNT(*) as count FROM memory_levels GROUP BY level`
1379
+ );
1380
+ return rows;
1381
+ }
1382
+ /**
1383
+ * Get events by memory level
1384
+ */
1385
+ async getEventsByLevel(level, options) {
1386
+ await this.initialize();
1387
+ const limit = options?.limit || 50;
1388
+ const offset = options?.offset || 0;
1389
+ const rows = sqliteAll(
1390
+ this.db,
1391
+ `SELECT e.* FROM events e
1392
+ INNER JOIN memory_levels ml ON e.id = ml.event_id
1393
+ WHERE ml.level = ?
1394
+ ORDER BY e.timestamp DESC
1395
+ LIMIT ? OFFSET ?`,
1396
+ [level, limit, offset]
1397
+ );
1398
+ return rows.map((row) => this.rowToEvent(row));
1399
+ }
1400
+ /**
1401
+ * Get memory level for a specific event
1402
+ */
1403
+ async getEventLevel(eventId) {
1404
+ await this.initialize();
1405
+ const row = sqliteGet(
1406
+ this.db,
1407
+ `SELECT level FROM memory_levels WHERE event_id = ?`,
1408
+ [eventId]
1409
+ );
1410
+ return row ? row.level : null;
1411
+ }
1412
+ /**
1413
+ * Get sync position for a target
1414
+ */
1415
+ async getSyncPosition(targetName) {
1416
+ await this.initialize();
1417
+ const row = sqliteGet(
1418
+ this.db,
1419
+ `SELECT last_event_id, last_timestamp FROM sync_positions WHERE target_name = ?`,
1420
+ [targetName]
1421
+ );
1422
+ return {
1423
+ lastEventId: row?.last_event_id ?? null,
1424
+ lastTimestamp: row?.last_timestamp ?? null
1425
+ };
1426
+ }
1427
+ /**
1428
+ * Update sync position for a target
1429
+ */
1430
+ async updateSyncPosition(targetName, lastEventId, lastTimestamp) {
1431
+ await this.initialize();
1432
+ sqliteRun(
1433
+ this.db,
1434
+ `INSERT OR REPLACE INTO sync_positions (target_name, last_event_id, last_timestamp, updated_at)
1435
+ VALUES (?, ?, ?, datetime('now'))`,
1436
+ [targetName, lastEventId, lastTimestamp]
1437
+ );
1438
+ }
1439
+ /**
1440
+ * Get config value for endless mode
1441
+ */
1442
+ async getEndlessConfig(key) {
1443
+ await this.initialize();
1444
+ const row = sqliteGet(
1445
+ this.db,
1446
+ `SELECT value FROM endless_config WHERE key = ?`,
1447
+ [key]
1448
+ );
1449
+ if (!row)
1450
+ return null;
1451
+ return JSON.parse(row.value);
1452
+ }
1453
+ /**
1454
+ * Set config value for endless mode
1455
+ */
1456
+ async setEndlessConfig(key, value) {
1457
+ await this.initialize();
1458
+ sqliteRun(
1459
+ this.db,
1460
+ `INSERT OR REPLACE INTO endless_config (key, value, updated_at)
1461
+ VALUES (?, ?, datetime('now'))`,
1462
+ [key, JSON.stringify(value)]
1463
+ );
1464
+ }
1465
+ /**
1466
+ * Increment access count for events
1467
+ */
1468
+ async incrementAccessCount(eventIds) {
1469
+ if (eventIds.length === 0 || this.readOnly)
1470
+ return;
1471
+ await this.initialize();
1472
+ const placeholders = eventIds.map(() => "?").join(",");
1473
+ const currentTime = toSQLiteTimestamp(/* @__PURE__ */ new Date());
1474
+ sqliteRun(
1475
+ this.db,
1476
+ `UPDATE events
1477
+ SET access_count = access_count + 1,
1478
+ last_accessed_at = ?
1479
+ WHERE id IN (${placeholders})`,
1480
+ [currentTime, ...eventIds]
1481
+ );
1482
+ }
1483
+ /**
1484
+ * Get most accessed memories
1485
+ */
1486
+ async getMostAccessed(limit = 10) {
1487
+ await this.initialize();
1488
+ const rows = sqliteAll(
1489
+ this.db,
1490
+ `SELECT * FROM events
1491
+ WHERE access_count > 0
1492
+ ORDER BY access_count DESC, last_accessed_at DESC
1493
+ LIMIT ?`,
1494
+ [limit]
1495
+ );
1496
+ return rows.map((row) => this.rowToEvent(row));
1497
+ }
1498
+ /**
1499
+ * Fast keyword search using FTS5
1500
+ * Returns events matching the search query, ranked by relevance
1501
+ */
1502
+ async keywordSearch(query, limit = 10) {
1503
+ await this.initialize();
1504
+ const searchTerms = query.replace(/['"(){}[\]^~*?:\\/-]/g, " ").split(/\s+/).filter((term) => term.length > 1).map((term) => `"${term}"*`).join(" OR ");
1505
+ if (!searchTerms) {
1506
+ return [];
1507
+ }
1508
+ try {
1509
+ const rows = sqliteAll(
1510
+ this.db,
1511
+ `SELECT e.*, fts.rank
1512
+ FROM events_fts fts
1513
+ JOIN events e ON e.id = fts.event_id
1514
+ WHERE events_fts MATCH ?
1515
+ ORDER BY fts.rank
1516
+ LIMIT ?`,
1517
+ [searchTerms, limit]
1518
+ );
1519
+ return rows.map((row) => ({
1520
+ event: this.rowToEvent(row),
1521
+ rank: row.rank
1522
+ }));
1523
+ } catch (error) {
1524
+ const likePattern = `%${query}%`;
1525
+ const rows = sqliteAll(
1526
+ this.db,
1527
+ `SELECT *, 0 as rank FROM events
1528
+ WHERE content LIKE ?
1529
+ ORDER BY timestamp DESC
1530
+ LIMIT ?`,
1531
+ [likePattern, limit]
1532
+ );
1533
+ return rows.map((row) => ({
1534
+ event: this.rowToEvent(row),
1535
+ rank: 0
1536
+ }));
1537
+ }
1538
+ }
1539
+ /**
1540
+ * Rebuild FTS index from existing events
1541
+ * Call this once after upgrading to FTS5
1542
+ */
1543
+ async rebuildFtsIndex() {
1544
+ await this.initialize();
1545
+ const countRow = sqliteGet(this.db, "SELECT COUNT(*) as count FROM events", []);
1546
+ const totalEvents = countRow?.count ?? 0;
1547
+ sqliteExec(this.db, `
1548
+ DELETE FROM events_fts;
1549
+ INSERT INTO events_fts(rowid, content, event_id)
1550
+ SELECT rowid, content, id FROM events;
1551
+ `);
1552
+ return totalEvents;
1553
+ }
1554
+ /**
1555
+ * Get database instance for direct access
1556
+ */
1557
+ getDatabase() {
1558
+ return this.db;
1559
+ }
703
1560
  /**
704
1561
  * Close database connection
705
1562
  */
706
1563
  async close() {
707
- await dbClose(this.db);
1564
+ sqliteClose(this.db);
708
1565
  }
709
1566
  /**
710
1567
  * Convert database row to MemoryEvent
711
1568
  */
712
1569
  rowToEvent(row) {
713
- return {
1570
+ const event = {
714
1571
  id: row.id,
715
1572
  eventType: row.event_type,
716
1573
  sessionId: row.session_id,
717
- timestamp: toDate(row.timestamp),
1574
+ timestamp: toDateFromSQLite(row.timestamp),
718
1575
  content: row.content,
719
1576
  canonicalKey: row.canonical_key,
720
1577
  dedupeKey: row.dedupe_key,
721
1578
  metadata: row.metadata ? JSON.parse(row.metadata) : void 0
722
1579
  };
1580
+ if (row.access_count !== void 0) {
1581
+ event.access_count = row.access_count;
1582
+ }
1583
+ if (row.last_accessed_at !== void 0) {
1584
+ event.last_accessed_at = row.last_accessed_at;
1585
+ }
1586
+ return event;
1587
+ }
1588
+ };
1589
+
1590
+ // src/core/sync-worker.ts
1591
+ var DEFAULT_CONFIG = {
1592
+ intervalMs: 3e4,
1593
+ batchSize: 500,
1594
+ maxRetries: 3,
1595
+ retryDelayMs: 5e3
1596
+ };
1597
+ var SyncWorker = class {
1598
+ constructor(sqliteStore, duckdbStore, config) {
1599
+ this.sqliteStore = sqliteStore;
1600
+ this.duckdbStore = duckdbStore;
1601
+ this.config = { ...DEFAULT_CONFIG, ...config };
1602
+ }
1603
+ config;
1604
+ intervalHandle = null;
1605
+ running = false;
1606
+ stats = {
1607
+ lastSyncAt: null,
1608
+ eventsSynced: 0,
1609
+ sessionsSynced: 0,
1610
+ errors: 0,
1611
+ status: "idle"
1612
+ };
1613
+ /**
1614
+ * Start the sync worker
1615
+ */
1616
+ start() {
1617
+ if (this.running)
1618
+ return;
1619
+ this.running = true;
1620
+ this.stats.status = "idle";
1621
+ this.syncNow().catch((err) => {
1622
+ console.error("[SyncWorker] Initial sync failed:", err);
1623
+ });
1624
+ this.intervalHandle = setInterval(() => {
1625
+ this.syncNow().catch((err) => {
1626
+ console.error("[SyncWorker] Periodic sync failed:", err);
1627
+ });
1628
+ }, this.config.intervalMs);
1629
+ }
1630
+ /**
1631
+ * Stop the sync worker
1632
+ */
1633
+ stop() {
1634
+ this.running = false;
1635
+ this.stats.status = "stopped";
1636
+ if (this.intervalHandle) {
1637
+ clearInterval(this.intervalHandle);
1638
+ this.intervalHandle = null;
1639
+ }
1640
+ }
1641
+ /**
1642
+ * Trigger immediate sync
1643
+ */
1644
+ async syncNow() {
1645
+ if (this.stats.status === "syncing") {
1646
+ return;
1647
+ }
1648
+ this.stats.status = "syncing";
1649
+ try {
1650
+ await this.syncEvents();
1651
+ await this.syncSessions();
1652
+ this.stats.lastSyncAt = /* @__PURE__ */ new Date();
1653
+ this.stats.status = "idle";
1654
+ } catch (error) {
1655
+ this.stats.errors++;
1656
+ this.stats.status = "error";
1657
+ throw error;
1658
+ }
1659
+ }
1660
+ /**
1661
+ * Sync events from SQLite to DuckDB
1662
+ */
1663
+ async syncEvents() {
1664
+ const targetName = "duckdb_analytics";
1665
+ const position = await this.sqliteStore.getSyncPosition(targetName);
1666
+ const lastTimestamp = position.lastTimestamp || "1970-01-01T00:00:00.000Z";
1667
+ let hasMore = true;
1668
+ let totalSynced = 0;
1669
+ while (hasMore) {
1670
+ const events = await this.sqliteStore.getEventsSince(lastTimestamp, this.config.batchSize);
1671
+ if (events.length === 0) {
1672
+ hasMore = false;
1673
+ break;
1674
+ }
1675
+ await this.retryWithBackoff(async () => {
1676
+ for (const event of events) {
1677
+ await this.insertEventToDuckDB(event);
1678
+ }
1679
+ });
1680
+ totalSynced += events.length;
1681
+ const lastEvent = events[events.length - 1];
1682
+ await this.sqliteStore.updateSyncPosition(
1683
+ targetName,
1684
+ lastEvent.id,
1685
+ lastEvent.timestamp.toISOString()
1686
+ );
1687
+ hasMore = events.length === this.config.batchSize;
1688
+ }
1689
+ this.stats.eventsSynced += totalSynced;
1690
+ }
1691
+ /**
1692
+ * Sync sessions from SQLite to DuckDB
1693
+ */
1694
+ async syncSessions() {
1695
+ const sessions = await this.sqliteStore.getAllSessions();
1696
+ for (const session of sessions) {
1697
+ await this.retryWithBackoff(async () => {
1698
+ await this.duckdbStore.upsertSession(session);
1699
+ });
1700
+ }
1701
+ this.stats.sessionsSynced = sessions.length;
1702
+ }
1703
+ /**
1704
+ * Insert a single event into DuckDB
1705
+ */
1706
+ async insertEventToDuckDB(event) {
1707
+ await this.duckdbStore.append({
1708
+ eventType: event.eventType,
1709
+ sessionId: event.sessionId,
1710
+ timestamp: event.timestamp,
1711
+ content: event.content,
1712
+ metadata: event.metadata
1713
+ });
1714
+ }
1715
+ /**
1716
+ * Retry operation with exponential backoff
1717
+ */
1718
+ async retryWithBackoff(fn) {
1719
+ let lastError = null;
1720
+ for (let attempt = 0; attempt < this.config.maxRetries; attempt++) {
1721
+ try {
1722
+ return await fn();
1723
+ } catch (error) {
1724
+ lastError = error instanceof Error ? error : new Error(String(error));
1725
+ if (attempt < this.config.maxRetries - 1) {
1726
+ const delay = this.config.retryDelayMs * Math.pow(2, attempt);
1727
+ await this.sleep(delay);
1728
+ }
1729
+ }
1730
+ }
1731
+ throw lastError;
1732
+ }
1733
+ /**
1734
+ * Sleep utility
1735
+ */
1736
+ sleep(ms) {
1737
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
1738
+ }
1739
+ /**
1740
+ * Get sync statistics
1741
+ */
1742
+ getStats() {
1743
+ return { ...this.stats };
1744
+ }
1745
+ /**
1746
+ * Check if worker is running
1747
+ */
1748
+ isRunning() {
1749
+ return this.running;
723
1750
  }
724
1751
  };
725
1752
 
@@ -951,7 +1978,7 @@ function getDefaultEmbedder() {
951
1978
  }
952
1979
 
953
1980
  // src/core/vector-outbox.ts
954
- var DEFAULT_CONFIG = {
1981
+ var DEFAULT_CONFIG2 = {
955
1982
  embeddingVersion: "v1",
956
1983
  maxRetries: 3,
957
1984
  stuckThresholdMs: 5 * 60 * 1e3,
@@ -960,7 +1987,7 @@ var DEFAULT_CONFIG = {
960
1987
  };
961
1988
 
962
1989
  // src/core/vector-worker.ts
963
- var DEFAULT_CONFIG2 = {
1990
+ var DEFAULT_CONFIG3 = {
964
1991
  batchSize: 32,
965
1992
  pollIntervalMs: 1e3,
966
1993
  maxRetries: 3
@@ -971,12 +1998,13 @@ var VectorWorker = class {
971
1998
  embedder;
972
1999
  config;
973
2000
  running = false;
2001
+ stopping = false;
974
2002
  pollTimeout = null;
975
2003
  constructor(eventStore, vectorStore, embedder, config = {}) {
976
2004
  this.eventStore = eventStore;
977
2005
  this.vectorStore = vectorStore;
978
2006
  this.embedder = embedder;
979
- this.config = { ...DEFAULT_CONFIG2, ...config };
2007
+ this.config = { ...DEFAULT_CONFIG3, ...config };
980
2008
  }
981
2009
  /**
982
2010
  * Start the worker polling loop
@@ -985,6 +2013,7 @@ var VectorWorker = class {
985
2013
  if (this.running)
986
2014
  return;
987
2015
  this.running = true;
2016
+ this.stopping = false;
988
2017
  this.poll();
989
2018
  }
990
2019
  /**
@@ -992,6 +2021,7 @@ var VectorWorker = class {
992
2021
  */
993
2022
  stop() {
994
2023
  this.running = false;
2024
+ this.stopping = true;
995
2025
  if (this.pollTimeout) {
996
2026
  clearTimeout(this.pollTimeout);
997
2027
  this.pollTimeout = null;
@@ -1041,9 +2071,15 @@ var VectorWorker = class {
1041
2071
  }
1042
2072
  return successful.length;
1043
2073
  } catch (error) {
1044
- const allIds = items.map((i) => i.id);
1045
- const errorMessage = error instanceof Error ? error.message : String(error);
1046
- await this.eventStore.failOutboxItems(allIds, errorMessage);
2074
+ if (!this.stopping) {
2075
+ try {
2076
+ const allIds = items.map((i) => i.id);
2077
+ const errorMessage = error instanceof Error ? error.message : String(error);
2078
+ await this.eventStore.failOutboxItems(allIds, errorMessage);
2079
+ } catch (failError) {
2080
+ console.warn("Could not mark outbox items as failed (database may be closed)");
2081
+ }
2082
+ }
1047
2083
  throw error;
1048
2084
  }
1049
2085
  }
@@ -1051,14 +2087,18 @@ var VectorWorker = class {
1051
2087
  * Poll for new items
1052
2088
  */
1053
2089
  async poll() {
1054
- if (!this.running)
2090
+ if (!this.running || this.stopping)
1055
2091
  return;
1056
2092
  try {
1057
2093
  await this.processBatch();
1058
2094
  } catch (error) {
1059
- console.error("Vector worker error:", error);
2095
+ if (!this.stopping) {
2096
+ console.error("Vector worker error:", error);
2097
+ }
2098
+ }
2099
+ if (this.running && !this.stopping) {
2100
+ this.pollTimeout = setTimeout(() => this.poll(), this.config.pollIntervalMs);
1060
2101
  }
1061
- this.pollTimeout = setTimeout(() => this.poll(), this.config.pollIntervalMs);
1062
2102
  }
1063
2103
  /**
1064
2104
  * Process all pending items (blocking)
@@ -1085,7 +2125,7 @@ function createVectorWorker(eventStore, vectorStore, embedder, config) {
1085
2125
  }
1086
2126
 
1087
2127
  // src/core/matcher.ts
1088
- var DEFAULT_CONFIG3 = {
2128
+ var DEFAULT_CONFIG4 = {
1089
2129
  weights: {
1090
2130
  semanticSimilarity: 0.4,
1091
2131
  ftsScore: 0.25,
@@ -1100,9 +2140,9 @@ var Matcher = class {
1100
2140
  config;
1101
2141
  constructor(config = {}) {
1102
2142
  this.config = {
1103
- ...DEFAULT_CONFIG3,
2143
+ ...DEFAULT_CONFIG4,
1104
2144
  ...config,
1105
- weights: { ...DEFAULT_CONFIG3.weights, ...config.weights }
2145
+ weights: { ...DEFAULT_CONFIG4.weights, ...config.weights }
1106
2146
  };
1107
2147
  }
1108
2148
  /**
@@ -1810,7 +2850,7 @@ function createSharedEventStore(dbPath) {
1810
2850
  }
1811
2851
 
1812
2852
  // src/core/shared-store.ts
1813
- import { randomUUID as randomUUID2 } from "crypto";
2853
+ import { randomUUID as randomUUID3 } from "crypto";
1814
2854
  var SharedStore = class {
1815
2855
  constructor(sharedEventStore) {
1816
2856
  this.sharedEventStore = sharedEventStore;
@@ -1822,7 +2862,7 @@ var SharedStore = class {
1822
2862
  * Promote a verified troubleshooting entry to shared storage
1823
2863
  */
1824
2864
  async promoteEntry(input) {
1825
- const entryId = randomUUID2();
2865
+ const entryId = randomUUID3();
1826
2866
  await dbRun(
1827
2867
  this.db,
1828
2868
  `INSERT INTO shared_troubleshooting (
@@ -2187,7 +3227,7 @@ function createSharedVectorStore(dbPath) {
2187
3227
  }
2188
3228
 
2189
3229
  // src/core/shared-promoter.ts
2190
- import { randomUUID as randomUUID3 } from "crypto";
3230
+ import { randomUUID as randomUUID4 } from "crypto";
2191
3231
  var SharedPromoter = class {
2192
3232
  constructor(sharedStore, sharedVectorStore, embedder, config) {
2193
3233
  this.sharedStore = sharedStore;
@@ -2255,7 +3295,7 @@ var SharedPromoter = class {
2255
3295
  const embeddingContent = this.createEmbeddingContent(input);
2256
3296
  const embedding = await this.embedder.embed(embeddingContent);
2257
3297
  await this.sharedVectorStore.upsert({
2258
- id: randomUUID3(),
3298
+ id: randomUUID4(),
2259
3299
  entryId,
2260
3300
  entryType: "troubleshooting",
2261
3301
  content: embeddingContent,
@@ -2395,7 +3435,7 @@ function createToolObservationEmbedding(toolName, metadata, success) {
2395
3435
  }
2396
3436
 
2397
3437
  // src/core/working-set-store.ts
2398
- import { randomUUID as randomUUID4 } from "crypto";
3438
+ import { randomUUID as randomUUID5 } from "crypto";
2399
3439
  var WorkingSetStore = class {
2400
3440
  constructor(eventStore, config) {
2401
3441
  this.eventStore = eventStore;
@@ -2416,7 +3456,7 @@ var WorkingSetStore = class {
2416
3456
  `INSERT OR REPLACE INTO working_set (id, event_id, added_at, relevance_score, topics, expires_at)
2417
3457
  VALUES (?, ?, CURRENT_TIMESTAMP, ?, ?, ?)`,
2418
3458
  [
2419
- randomUUID4(),
3459
+ randomUUID5(),
2420
3460
  eventId,
2421
3461
  relevanceScore,
2422
3462
  JSON.stringify(topics || []),
@@ -2600,7 +3640,7 @@ function createWorkingSetStore(eventStore, config) {
2600
3640
  }
2601
3641
 
2602
3642
  // src/core/consolidated-store.ts
2603
- import { randomUUID as randomUUID5 } from "crypto";
3643
+ import { randomUUID as randomUUID6 } from "crypto";
2604
3644
  var ConsolidatedStore = class {
2605
3645
  constructor(eventStore) {
2606
3646
  this.eventStore = eventStore;
@@ -2612,7 +3652,7 @@ var ConsolidatedStore = class {
2612
3652
  * Create a new consolidated memory
2613
3653
  */
2614
3654
  async create(input) {
2615
- const memoryId = randomUUID5();
3655
+ const memoryId = randomUUID6();
2616
3656
  await dbRun(
2617
3657
  this.db,
2618
3658
  `INSERT INTO consolidated_memories
@@ -3139,7 +4179,7 @@ function createConsolidationWorker(workingSetStore, consolidatedStore, config) {
3139
4179
  }
3140
4180
 
3141
4181
  // src/core/continuity-manager.ts
3142
- import { randomUUID as randomUUID6 } from "crypto";
4182
+ import { randomUUID as randomUUID7 } from "crypto";
3143
4183
  var ContinuityManager = class {
3144
4184
  constructor(eventStore, config) {
3145
4185
  this.eventStore = eventStore;
@@ -3293,7 +4333,7 @@ var ContinuityManager = class {
3293
4333
  `INSERT INTO continuity_log
3294
4334
  (log_id, from_context_id, to_context_id, continuity_score, transition_type, created_at)
3295
4335
  VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`,
3296
- [randomUUID6(), previous.id, current.id, score, type]
4336
+ [randomUUID7(), previous.id, current.id, score, type]
3297
4337
  );
3298
4338
  }
3299
4339
  /**
@@ -3402,7 +4442,7 @@ function createContinuityManager(eventStore, config) {
3402
4442
  }
3403
4443
 
3404
4444
  // src/core/graduation-worker.ts
3405
- var DEFAULT_CONFIG4 = {
4445
+ var DEFAULT_CONFIG5 = {
3406
4446
  evaluationIntervalMs: 3e5,
3407
4447
  // 5 minutes
3408
4448
  batchSize: 50,
@@ -3410,7 +4450,7 @@ var DEFAULT_CONFIG4 = {
3410
4450
  // 1 hour cooldown between evaluations
3411
4451
  };
3412
4452
  var GraduationWorker = class {
3413
- constructor(eventStore, graduation, config = DEFAULT_CONFIG4) {
4453
+ constructor(eventStore, graduation, config = DEFAULT_CONFIG5) {
3414
4454
  this.eventStore = eventStore;
3415
4455
  this.graduation = graduation;
3416
4456
  this.config = config;
@@ -3518,7 +4558,7 @@ function createGraduationWorker(eventStore, graduation, config) {
3518
4558
  return new GraduationWorker(
3519
4559
  eventStore,
3520
4560
  graduation,
3521
- { ...DEFAULT_CONFIG4, ...config }
4561
+ { ...DEFAULT_CONFIG5, ...config }
3522
4562
  );
3523
4563
  }
3524
4564
 
@@ -3582,7 +4622,11 @@ function getSessionProject(sessionId) {
3582
4622
  return registry.sessions[sessionId] || null;
3583
4623
  }
3584
4624
  var MemoryService = class {
3585
- eventStore;
4625
+ // Primary store: SQLite (WAL mode) - for hooks, always available
4626
+ sqliteStore;
4627
+ // Analytics store: DuckDB - for server reads (optional, synced from SQLite)
4628
+ analyticsStore;
4629
+ syncWorker = null;
3586
4630
  vectorStore;
3587
4631
  embedder;
3588
4632
  matcher;
@@ -3605,25 +4649,49 @@ var MemoryService = class {
3605
4649
  sharedStoreConfig = null;
3606
4650
  projectHash = null;
3607
4651
  readOnly;
4652
+ lightweightMode;
3608
4653
  constructor(config) {
3609
4654
  const storagePath = this.expandPath(config.storagePath);
3610
4655
  this.readOnly = config.readOnly ?? false;
4656
+ this.lightweightMode = config.lightweightMode ?? false;
3611
4657
  if (!this.readOnly && !fs.existsSync(storagePath)) {
3612
4658
  fs.mkdirSync(storagePath, { recursive: true });
3613
4659
  }
3614
4660
  this.projectHash = config.projectHash || null;
3615
4661
  this.sharedStoreConfig = config.sharedStoreConfig ?? { enabled: true };
3616
- this.eventStore = new EventStore(path.join(storagePath, "events.duckdb"), { readOnly: this.readOnly });
4662
+ this.sqliteStore = new SQLiteEventStore(
4663
+ path.join(storagePath, "events.sqlite"),
4664
+ { readonly: this.readOnly }
4665
+ );
4666
+ const analyticsEnabled = config.analyticsEnabled ?? this.readOnly;
4667
+ if (!analyticsEnabled) {
4668
+ this.analyticsStore = null;
4669
+ } else if (this.readOnly) {
4670
+ try {
4671
+ this.analyticsStore = new EventStore(
4672
+ path.join(storagePath, "analytics.duckdb"),
4673
+ { readOnly: true }
4674
+ );
4675
+ } catch {
4676
+ this.analyticsStore = null;
4677
+ }
4678
+ } else {
4679
+ this.analyticsStore = new EventStore(
4680
+ path.join(storagePath, "analytics.duckdb"),
4681
+ { readOnly: false }
4682
+ );
4683
+ }
3617
4684
  this.vectorStore = new VectorStore(path.join(storagePath, "vectors"));
3618
4685
  this.embedder = config.embeddingModel ? new Embedder(config.embeddingModel) : getDefaultEmbedder();
3619
4686
  this.matcher = getDefaultMatcher();
3620
4687
  this.retriever = createRetriever(
3621
- this.eventStore,
4688
+ this.sqliteStore,
4689
+ // Interface compatible
3622
4690
  this.vectorStore,
3623
4691
  this.embedder,
3624
4692
  this.matcher
3625
4693
  );
3626
- this.graduation = createGraduationPipeline(this.eventStore);
4694
+ this.graduation = createGraduationPipeline(this.sqliteStore);
3627
4695
  }
3628
4696
  /**
3629
4697
  * Initialize all components
@@ -3631,23 +4699,42 @@ var MemoryService = class {
3631
4699
  async initialize() {
3632
4700
  if (this.initialized)
3633
4701
  return;
3634
- await this.eventStore.initialize();
4702
+ await this.sqliteStore.initialize();
4703
+ if (this.lightweightMode) {
4704
+ this.initialized = true;
4705
+ return;
4706
+ }
4707
+ if (this.analyticsStore) {
4708
+ try {
4709
+ await this.analyticsStore.initialize();
4710
+ } catch (error) {
4711
+ console.warn("[MemoryService] Analytics store (DuckDB) initialization failed, using SQLite for reads:", error);
4712
+ }
4713
+ }
3635
4714
  await this.vectorStore.initialize();
3636
4715
  await this.embedder.initialize();
3637
4716
  if (!this.readOnly) {
3638
4717
  this.vectorWorker = createVectorWorker(
3639
- this.eventStore,
4718
+ this.sqliteStore,
3640
4719
  this.vectorStore,
3641
4720
  this.embedder
3642
4721
  );
3643
4722
  this.vectorWorker.start();
3644
4723
  this.retriever.setGraduationPipeline(this.graduation);
3645
4724
  this.graduationWorker = createGraduationWorker(
3646
- this.eventStore,
4725
+ this.sqliteStore,
3647
4726
  this.graduation
3648
4727
  );
3649
4728
  this.graduationWorker.start();
3650
- const savedMode = await this.eventStore.getEndlessConfig("mode");
4729
+ if (this.analyticsStore) {
4730
+ this.syncWorker = new SyncWorker(
4731
+ this.sqliteStore,
4732
+ this.analyticsStore,
4733
+ { intervalMs: 3e4, batchSize: 500 }
4734
+ );
4735
+ this.syncWorker.start();
4736
+ }
4737
+ const savedMode = await this.sqliteStore.getEndlessConfig("mode");
3651
4738
  if (savedMode === "endless") {
3652
4739
  this.endlessMode = "endless";
3653
4740
  await this.initializeEndlessMode();
@@ -3688,7 +4775,7 @@ var MemoryService = class {
3688
4775
  */
3689
4776
  async startSession(sessionId, projectPath) {
3690
4777
  await this.initialize();
3691
- await this.eventStore.upsertSession({
4778
+ await this.sqliteStore.upsertSession({
3692
4779
  id: sessionId,
3693
4780
  startedAt: /* @__PURE__ */ new Date(),
3694
4781
  projectPath
@@ -3699,7 +4786,7 @@ var MemoryService = class {
3699
4786
  */
3700
4787
  async endSession(sessionId, summary) {
3701
4788
  await this.initialize();
3702
- await this.eventStore.upsertSession({
4789
+ await this.sqliteStore.upsertSession({
3703
4790
  id: sessionId,
3704
4791
  endedAt: /* @__PURE__ */ new Date(),
3705
4792
  summary
@@ -3710,7 +4797,7 @@ var MemoryService = class {
3710
4797
  */
3711
4798
  async storeUserPrompt(sessionId, content, metadata) {
3712
4799
  await this.initialize();
3713
- const result = await this.eventStore.append({
4800
+ const result = await this.sqliteStore.append({
3714
4801
  eventType: "user_prompt",
3715
4802
  sessionId,
3716
4803
  timestamp: /* @__PURE__ */ new Date(),
@@ -3718,7 +4805,7 @@ var MemoryService = class {
3718
4805
  metadata
3719
4806
  });
3720
4807
  if (result.success && !result.isDuplicate) {
3721
- await this.eventStore.enqueueForEmbedding(result.eventId, content);
4808
+ await this.sqliteStore.enqueueForEmbedding(result.eventId, content);
3722
4809
  }
3723
4810
  return result;
3724
4811
  }
@@ -3727,7 +4814,7 @@ var MemoryService = class {
3727
4814
  */
3728
4815
  async storeAgentResponse(sessionId, content, metadata) {
3729
4816
  await this.initialize();
3730
- const result = await this.eventStore.append({
4817
+ const result = await this.sqliteStore.append({
3731
4818
  eventType: "agent_response",
3732
4819
  sessionId,
3733
4820
  timestamp: /* @__PURE__ */ new Date(),
@@ -3735,7 +4822,7 @@ var MemoryService = class {
3735
4822
  metadata
3736
4823
  });
3737
4824
  if (result.success && !result.isDuplicate) {
3738
- await this.eventStore.enqueueForEmbedding(result.eventId, content);
4825
+ await this.sqliteStore.enqueueForEmbedding(result.eventId, content);
3739
4826
  }
3740
4827
  return result;
3741
4828
  }
@@ -3744,14 +4831,14 @@ var MemoryService = class {
3744
4831
  */
3745
4832
  async storeSessionSummary(sessionId, summary) {
3746
4833
  await this.initialize();
3747
- const result = await this.eventStore.append({
4834
+ const result = await this.sqliteStore.append({
3748
4835
  eventType: "session_summary",
3749
4836
  sessionId,
3750
4837
  timestamp: /* @__PURE__ */ new Date(),
3751
4838
  content: summary
3752
4839
  });
3753
4840
  if (result.success && !result.isDuplicate) {
3754
- await this.eventStore.enqueueForEmbedding(result.eventId, summary);
4841
+ await this.sqliteStore.enqueueForEmbedding(result.eventId, summary);
3755
4842
  }
3756
4843
  return result;
3757
4844
  }
@@ -3761,7 +4848,7 @@ var MemoryService = class {
3761
4848
  async storeToolObservation(sessionId, payload) {
3762
4849
  await this.initialize();
3763
4850
  const content = JSON.stringify(payload);
3764
- const result = await this.eventStore.append({
4851
+ const result = await this.sqliteStore.append({
3765
4852
  eventType: "tool_observation",
3766
4853
  sessionId,
3767
4854
  timestamp: /* @__PURE__ */ new Date(),
@@ -3777,7 +4864,7 @@ var MemoryService = class {
3777
4864
  payload.metadata || {},
3778
4865
  payload.success
3779
4866
  );
3780
- await this.eventStore.enqueueForEmbedding(result.eventId, embeddingContent);
4867
+ await this.sqliteStore.enqueueForEmbedding(result.eventId, embeddingContent);
3781
4868
  }
3782
4869
  return result;
3783
4870
  }
@@ -3786,9 +4873,6 @@ var MemoryService = class {
3786
4873
  */
3787
4874
  async retrieveMemories(query, options) {
3788
4875
  await this.initialize();
3789
- if (this.vectorWorker) {
3790
- await this.vectorWorker.processAll();
3791
- }
3792
4876
  if (options?.includeShared && this.sharedStore) {
3793
4877
  return this.retriever.retrieveUnified(query, {
3794
4878
  ...options,
@@ -3798,26 +4882,49 @@ var MemoryService = class {
3798
4882
  }
3799
4883
  return this.retriever.retrieve(query, options);
3800
4884
  }
4885
+ /**
4886
+ * Fast keyword search using SQLite FTS5
4887
+ * Much faster than vector search - no embedding model needed
4888
+ */
4889
+ async keywordSearch(query, options) {
4890
+ await this.initialize();
4891
+ const results = await this.sqliteStore.keywordSearch(query, options?.topK ?? 10);
4892
+ const maxRank = Math.min(...results.map((r) => r.rank), -1e-3);
4893
+ const minRank = Math.max(...results.map((r) => r.rank), -1e3);
4894
+ const rankRange = maxRank - minRank || 1;
4895
+ return results.map((r) => ({
4896
+ event: r.event,
4897
+ score: 1 - (r.rank - minRank) / rankRange
4898
+ // Normalize to 0-1
4899
+ })).filter((r) => !options?.minScore || r.score >= options.minScore);
4900
+ }
4901
+ /**
4902
+ * Rebuild FTS index (call after database upgrade)
4903
+ */
4904
+ async rebuildFtsIndex() {
4905
+ await this.initialize();
4906
+ return this.sqliteStore.rebuildFtsIndex();
4907
+ }
3801
4908
  /**
3802
4909
  * Get session history
3803
4910
  */
3804
4911
  async getSessionHistory(sessionId) {
3805
4912
  await this.initialize();
3806
- return this.eventStore.getSessionEvents(sessionId);
4913
+ return this.sqliteStore.getSessionEvents(sessionId);
3807
4914
  }
3808
4915
  /**
3809
4916
  * Get recent events
3810
4917
  */
3811
4918
  async getRecentEvents(limit = 100) {
3812
4919
  await this.initialize();
3813
- return this.eventStore.getRecentEvents(limit);
4920
+ return this.sqliteStore.getRecentEvents(limit);
3814
4921
  }
3815
4922
  /**
3816
4923
  * Get memory statistics
3817
4924
  */
3818
4925
  async getStats() {
3819
4926
  await this.initialize();
3820
- const recentEvents = await this.eventStore.getRecentEvents(1e4);
4927
+ const recentEvents = await this.sqliteStore.getRecentEvents(1e4);
3821
4928
  const vectorCount = await this.vectorStore.count();
3822
4929
  const levelStats = await this.graduation.getStats();
3823
4930
  return {
@@ -3840,14 +4947,14 @@ var MemoryService = class {
3840
4947
  */
3841
4948
  async getEventsByLevel(level, options) {
3842
4949
  await this.initialize();
3843
- return this.eventStore.getEventsByLevel(level, options);
4950
+ return this.sqliteStore.getEventsByLevel(level, options);
3844
4951
  }
3845
4952
  /**
3846
4953
  * Get memory level for a specific event
3847
4954
  */
3848
4955
  async getEventLevel(eventId) {
3849
4956
  await this.initialize();
3850
- return this.eventStore.getEventLevel(eventId);
4957
+ return this.sqliteStore.getEventLevel(eventId);
3851
4958
  }
3852
4959
  /**
3853
4960
  * Format retrieval results as context for Claude
@@ -3941,21 +5048,21 @@ var MemoryService = class {
3941
5048
  */
3942
5049
  async initializeEndlessMode() {
3943
5050
  const config = await this.getEndlessConfig();
3944
- this.workingSetStore = createWorkingSetStore(this.eventStore, config);
3945
- this.consolidatedStore = createConsolidatedStore(this.eventStore);
5051
+ this.workingSetStore = createWorkingSetStore(this.sqliteStore, config);
5052
+ this.consolidatedStore = createConsolidatedStore(this.sqliteStore);
3946
5053
  this.consolidationWorker = createConsolidationWorker(
3947
5054
  this.workingSetStore,
3948
5055
  this.consolidatedStore,
3949
5056
  config
3950
5057
  );
3951
- this.continuityManager = createContinuityManager(this.eventStore, config);
5058
+ this.continuityManager = createContinuityManager(this.sqliteStore, config);
3952
5059
  this.consolidationWorker.start();
3953
5060
  }
3954
5061
  /**
3955
5062
  * Get Endless Mode configuration
3956
5063
  */
3957
5064
  async getEndlessConfig() {
3958
- const savedConfig = await this.eventStore.getEndlessConfig("config");
5065
+ const savedConfig = await this.sqliteStore.getEndlessConfig("config");
3959
5066
  return savedConfig || this.getDefaultEndlessConfig();
3960
5067
  }
3961
5068
  /**
@@ -3964,7 +5071,7 @@ var MemoryService = class {
3964
5071
  async setEndlessConfig(config) {
3965
5072
  const current = await this.getEndlessConfig();
3966
5073
  const merged = { ...current, ...config };
3967
- await this.eventStore.setEndlessConfig("config", merged);
5074
+ await this.sqliteStore.setEndlessConfig("config", merged);
3968
5075
  }
3969
5076
  /**
3970
5077
  * Set memory mode (session or endless)
@@ -3974,7 +5081,7 @@ var MemoryService = class {
3974
5081
  if (mode === this.endlessMode)
3975
5082
  return;
3976
5083
  this.endlessMode = mode;
3977
- await this.eventStore.setEndlessConfig("mode", mode);
5084
+ await this.sqliteStore.setEndlessConfig("mode", mode);
3978
5085
  if (mode === "endless") {
3979
5086
  await this.initializeEndlessMode();
3980
5087
  } else {
@@ -4032,12 +5139,49 @@ var MemoryService = class {
4032
5139
  return this.consolidatedStore.getAll({ limit });
4033
5140
  }
4034
5141
  /**
4035
- * Get most accessed consolidated memories
5142
+ * Increment access count for memories that were used in prompts
5143
+ */
5144
+ async incrementMemoryAccess(eventIds) {
5145
+ if (eventIds.length === 0)
5146
+ return;
5147
+ if (this.sqliteStore) {
5148
+ await this.sqliteStore.incrementAccessCount(eventIds);
5149
+ } else if (this.eventStore) {
5150
+ await this.eventStore.incrementAccessCount(eventIds);
5151
+ }
5152
+ }
5153
+ /**
5154
+ * Get most accessed memories from events
4036
5155
  */
4037
5156
  async getMostAccessedMemories(limit = 10) {
4038
- if (!this.consolidatedStore)
4039
- return [];
4040
- return this.consolidatedStore.getMostAccessed(limit);
5157
+ console.log("[getMostAccessedMemories] sqliteStore available:", !!this.sqliteStore);
5158
+ if (this.sqliteStore) {
5159
+ const events = await this.sqliteStore.getMostAccessed(limit);
5160
+ console.log("[getMostAccessedMemories] Got events from SQLite:", events.length);
5161
+ return events.map((event) => ({
5162
+ memoryId: event.id,
5163
+ summary: event.content.substring(0, 200) + (event.content.length > 200 ? "..." : ""),
5164
+ topics: [],
5165
+ // Could extract topics from content if needed
5166
+ accessCount: event.access_count || 0,
5167
+ lastAccessed: event.last_accessed_at || null,
5168
+ confidence: 1,
5169
+ createdAt: event.timestamp
5170
+ }));
5171
+ }
5172
+ if (this.consolidatedStore) {
5173
+ const consolidated = await this.consolidatedStore.getMostAccessed(limit);
5174
+ return consolidated.map((m) => ({
5175
+ memoryId: m.memoryId,
5176
+ summary: m.summary,
5177
+ topics: m.topics,
5178
+ accessCount: m.accessCount,
5179
+ lastAccessed: m.accessedAt,
5180
+ confidence: m.confidence,
5181
+ createdAt: m.createdAt
5182
+ }));
5183
+ }
5184
+ return [];
4041
5185
  }
4042
5186
  /**
4043
5187
  * Mark a consolidated memory as accessed
@@ -4162,10 +5306,16 @@ var MemoryService = class {
4162
5306
  if (this.vectorWorker) {
4163
5307
  this.vectorWorker.stop();
4164
5308
  }
5309
+ if (this.syncWorker) {
5310
+ this.syncWorker.stop();
5311
+ }
4165
5312
  if (this.sharedEventStore) {
4166
5313
  await this.sharedEventStore.close();
4167
5314
  }
4168
- await this.eventStore.close();
5315
+ await this.sqliteStore.close();
5316
+ if (this.analyticsStore) {
5317
+ await this.analyticsStore.close();
5318
+ }
4169
5319
  }
4170
5320
  /**
4171
5321
  * Expand ~ to home directory
@@ -4182,7 +5332,11 @@ var GLOBAL_KEY = "__global__";
4182
5332
  function getDefaultMemoryService() {
4183
5333
  if (!serviceCache.has(GLOBAL_KEY)) {
4184
5334
  serviceCache.set(GLOBAL_KEY, new MemoryService({
4185
- storagePath: "~/.claude-code/memory"
5335
+ storagePath: "~/.claude-code/memory",
5336
+ analyticsEnabled: false,
5337
+ // Hooks don't need DuckDB
5338
+ sharedStoreConfig: { enabled: false }
5339
+ // Shared store uses DuckDB too
4186
5340
  }));
4187
5341
  }
4188
5342
  return serviceCache.get(GLOBAL_KEY);
@@ -4190,7 +5344,11 @@ function getDefaultMemoryService() {
4190
5344
  function getReadOnlyMemoryService() {
4191
5345
  return new MemoryService({
4192
5346
  storagePath: "~/.claude-code/memory",
4193
- readOnly: true
5347
+ readOnly: true,
5348
+ analyticsEnabled: false,
5349
+ // Use SQLite for reads (WAL supports concurrent readers)
5350
+ sharedStoreConfig: { enabled: false }
5351
+ // Skip shared store for now
4194
5352
  });
4195
5353
  }
4196
5354
  function getMemoryServiceForProject(projectPath, sharedStoreConfig) {
@@ -4200,7 +5358,10 @@ function getMemoryServiceForProject(projectPath, sharedStoreConfig) {
4200
5358
  serviceCache.set(hash, new MemoryService({
4201
5359
  storagePath,
4202
5360
  projectHash: hash,
4203
- sharedStoreConfig
5361
+ // Override shared store config - hooks don't need DuckDB
5362
+ sharedStoreConfig: sharedStoreConfig ?? { enabled: false },
5363
+ analyticsEnabled: false
5364
+ // Hooks don't need DuckDB
4204
5365
  }));
4205
5366
  }
4206
5367
  return serviceCache.get(hash);
@@ -4212,6 +5373,22 @@ function getMemoryServiceForSession(sessionId) {
4212
5373
  }
4213
5374
  return getDefaultMemoryService();
4214
5375
  }
5376
+ function getLightweightMemoryService(sessionId) {
5377
+ const projectInfo = getSessionProject(sessionId);
5378
+ const key = projectInfo ? `lightweight_${projectInfo.projectHash}` : "lightweight_global";
5379
+ if (!serviceCache.has(key)) {
5380
+ const storagePath = projectInfo ? getProjectStoragePath(projectInfo.projectPath) : path.join(os.homedir(), ".claude-code", "memory");
5381
+ serviceCache.set(key, new MemoryService({
5382
+ storagePath,
5383
+ projectHash: projectInfo?.projectHash,
5384
+ lightweightMode: true,
5385
+ // Skip embedder/vector/workers
5386
+ analyticsEnabled: false,
5387
+ sharedStoreConfig: { enabled: false }
5388
+ }));
5389
+ }
5390
+ return serviceCache.get(key);
5391
+ }
4215
5392
  function createMemoryService(config) {
4216
5393
  return new MemoryService(config);
4217
5394
  }
@@ -4219,6 +5396,7 @@ export {
4219
5396
  MemoryService,
4220
5397
  createMemoryService,
4221
5398
  getDefaultMemoryService,
5399
+ getLightweightMemoryService,
4222
5400
  getMemoryServiceForProject,
4223
5401
  getMemoryServiceForSession,
4224
5402
  getProjectStoragePath,