claude-memory-layer 1.0.8 → 1.0.9

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 (43) 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/HANDOFF.md +92 -0
  5. package/dist/cli/index.js +1150 -71
  6. package/dist/cli/index.js.map +4 -4
  7. package/dist/core/index.js +1033 -47
  8. package/dist/core/index.js.map +4 -4
  9. package/dist/hooks/post-tool-use.js +5589 -0
  10. package/dist/hooks/post-tool-use.js.map +7 -0
  11. package/dist/hooks/session-end.js +1117 -64
  12. package/dist/hooks/session-end.js.map +4 -4
  13. package/dist/hooks/session-start.js +1112 -63
  14. package/dist/hooks/session-start.js.map +4 -4
  15. package/dist/hooks/stop.js +1117 -64
  16. package/dist/hooks/stop.js.map +4 -4
  17. package/dist/hooks/user-prompt-submit.js +1151 -67
  18. package/dist/hooks/user-prompt-submit.js.map +4 -4
  19. package/dist/server/api/index.js +1145 -70
  20. package/dist/server/api/index.js.map +4 -4
  21. package/dist/server/index.js +1145 -70
  22. package/dist/server/index.js.map +4 -4
  23. package/dist/services/memory-service.js +1122 -65
  24. package/dist/services/memory-service.js.map +4 -4
  25. package/dist/ui/app.js +304 -0
  26. package/dist/ui/index.html +195 -1188
  27. package/dist/ui/style.css +595 -0
  28. package/package.json +3 -1
  29. package/scripts/build.ts +2 -0
  30. package/src/core/event-store.ts +18 -0
  31. package/src/core/index.ts +3 -0
  32. package/src/core/retriever.ts +4 -1
  33. package/src/core/sqlite-event-store.ts +849 -0
  34. package/src/core/sqlite-wrapper.ts +108 -0
  35. package/src/core/sync-worker.ts +228 -0
  36. package/src/core/vector-worker.ts +44 -14
  37. package/src/hooks/user-prompt-submit.ts +53 -4
  38. package/src/server/api/stats.ts +37 -7
  39. package/src/services/memory-service.ts +168 -39
  40. package/src/ui/app.js +304 -0
  41. package/src/ui/index.html +195 -1188
  42. package/src/ui/style.css +595 -0
  43. package/test_access.js +49 -0
@@ -701,26 +701,975 @@ var EventStore = class {
701
701
  tags: row.tags ? JSON.parse(row.tags) : void 0
702
702
  }));
703
703
  }
704
+ /**
705
+ * Increment access count for events (stub for compatibility)
706
+ */
707
+ async incrementAccessCount(eventIds) {
708
+ return Promise.resolve();
709
+ }
710
+ /**
711
+ * Get most accessed memories (stub for compatibility)
712
+ */
713
+ async getMostAccessed(limit = 10) {
714
+ return [];
715
+ }
716
+ /**
717
+ * Close database connection
718
+ */
719
+ async close() {
720
+ await dbClose(this.db);
721
+ }
722
+ /**
723
+ * Convert database row to MemoryEvent
724
+ */
725
+ rowToEvent(row) {
726
+ return {
727
+ id: row.id,
728
+ eventType: row.event_type,
729
+ sessionId: row.session_id,
730
+ timestamp: toDate(row.timestamp),
731
+ content: row.content,
732
+ canonicalKey: row.canonical_key,
733
+ dedupeKey: row.dedupe_key,
734
+ metadata: row.metadata ? JSON.parse(row.metadata) : void 0
735
+ };
736
+ }
737
+ };
738
+
739
+ // src/core/sqlite-event-store.ts
740
+ import { randomUUID as randomUUID2 } from "crypto";
741
+
742
+ // src/core/sqlite-wrapper.ts
743
+ import Database from "better-sqlite3";
744
+ function createSQLiteDatabase(path2, options) {
745
+ const db = new Database(path2, {
746
+ readonly: options?.readonly ?? false
747
+ });
748
+ if (!options?.readonly && (options?.walMode ?? true)) {
749
+ db.pragma("journal_mode = WAL");
750
+ db.pragma("synchronous = NORMAL");
751
+ db.pragma("busy_timeout = 5000");
752
+ }
753
+ return db;
754
+ }
755
+ function sqliteRun(db, sql, params = []) {
756
+ const stmt = db.prepare(sql);
757
+ return stmt.run(...params);
758
+ }
759
+ function sqliteAll(db, sql, params = []) {
760
+ const stmt = db.prepare(sql);
761
+ return stmt.all(...params);
762
+ }
763
+ function sqliteGet(db, sql, params = []) {
764
+ const stmt = db.prepare(sql);
765
+ return stmt.get(...params);
766
+ }
767
+ function sqliteExec(db, sql) {
768
+ db.exec(sql);
769
+ }
770
+ function sqliteClose(db) {
771
+ db.close();
772
+ }
773
+ function toDateFromSQLite(value) {
774
+ if (value instanceof Date)
775
+ return value;
776
+ if (typeof value === "string")
777
+ return new Date(value);
778
+ if (typeof value === "number")
779
+ return new Date(value);
780
+ return new Date(String(value));
781
+ }
782
+ function toSQLiteTimestamp(date) {
783
+ return date.toISOString();
784
+ }
785
+
786
+ // src/core/sqlite-event-store.ts
787
+ var SQLiteEventStore = class {
788
+ constructor(dbPath, options) {
789
+ this.dbPath = dbPath;
790
+ this.readOnly = options?.readonly ?? false;
791
+ this.db = createSQLiteDatabase(dbPath, {
792
+ readonly: this.readOnly,
793
+ walMode: !this.readOnly
794
+ });
795
+ }
796
+ db;
797
+ initialized = false;
798
+ readOnly;
799
+ /**
800
+ * Initialize database schema
801
+ */
802
+ async initialize() {
803
+ if (this.initialized)
804
+ return;
805
+ if (this.readOnly) {
806
+ this.initialized = true;
807
+ return;
808
+ }
809
+ sqliteExec(this.db, `
810
+ -- L0 EventStore: Single Source of Truth (immutable, append-only)
811
+ CREATE TABLE IF NOT EXISTS events (
812
+ id TEXT PRIMARY KEY,
813
+ event_type TEXT NOT NULL,
814
+ session_id TEXT NOT NULL,
815
+ timestamp TEXT NOT NULL,
816
+ content TEXT NOT NULL,
817
+ canonical_key TEXT NOT NULL,
818
+ dedupe_key TEXT UNIQUE,
819
+ metadata TEXT,
820
+ access_count INTEGER DEFAULT 0,
821
+ last_accessed_at TEXT
822
+ );
823
+
824
+ -- Dedup table for idempotency
825
+ CREATE TABLE IF NOT EXISTS event_dedup (
826
+ dedupe_key TEXT PRIMARY KEY,
827
+ event_id TEXT NOT NULL,
828
+ created_at TEXT DEFAULT (datetime('now'))
829
+ );
830
+
831
+ -- Session metadata
832
+ CREATE TABLE IF NOT EXISTS sessions (
833
+ id TEXT PRIMARY KEY,
834
+ started_at TEXT NOT NULL,
835
+ ended_at TEXT,
836
+ project_path TEXT,
837
+ summary TEXT,
838
+ tags TEXT
839
+ );
840
+
841
+ -- Insights (derived data, rebuildable)
842
+ CREATE TABLE IF NOT EXISTS insights (
843
+ id TEXT PRIMARY KEY,
844
+ insight_type TEXT NOT NULL,
845
+ content TEXT NOT NULL,
846
+ canonical_key TEXT NOT NULL,
847
+ confidence REAL,
848
+ source_events TEXT,
849
+ created_at TEXT,
850
+ last_updated TEXT
851
+ );
852
+
853
+ -- Embedding Outbox (Single-Writer Pattern)
854
+ CREATE TABLE IF NOT EXISTS embedding_outbox (
855
+ id TEXT PRIMARY KEY,
856
+ event_id TEXT NOT NULL,
857
+ content TEXT NOT NULL,
858
+ status TEXT DEFAULT 'pending',
859
+ retry_count INTEGER DEFAULT 0,
860
+ created_at TEXT DEFAULT (datetime('now')),
861
+ processed_at TEXT,
862
+ error_message TEXT
863
+ );
864
+
865
+ -- Projection offset tracking
866
+ CREATE TABLE IF NOT EXISTS projection_offsets (
867
+ projection_name TEXT PRIMARY KEY,
868
+ last_event_id TEXT,
869
+ last_timestamp TEXT,
870
+ updated_at TEXT DEFAULT (datetime('now'))
871
+ );
872
+
873
+ -- Memory level tracking
874
+ CREATE TABLE IF NOT EXISTS memory_levels (
875
+ event_id TEXT PRIMARY KEY,
876
+ level TEXT NOT NULL DEFAULT 'L0',
877
+ promoted_at TEXT DEFAULT (datetime('now'))
878
+ );
879
+
880
+ -- Entries (immutable memory units)
881
+ CREATE TABLE IF NOT EXISTS entries (
882
+ entry_id TEXT PRIMARY KEY,
883
+ created_ts TEXT NOT NULL,
884
+ entry_type TEXT NOT NULL,
885
+ title TEXT NOT NULL,
886
+ content_json TEXT NOT NULL,
887
+ stage TEXT NOT NULL DEFAULT 'raw',
888
+ status TEXT DEFAULT 'active',
889
+ superseded_by TEXT,
890
+ build_id TEXT,
891
+ evidence_json TEXT,
892
+ canonical_key TEXT,
893
+ created_at TEXT DEFAULT (datetime('now'))
894
+ );
895
+
896
+ -- Entities (task/condition/artifact)
897
+ CREATE TABLE IF NOT EXISTS entities (
898
+ entity_id TEXT PRIMARY KEY,
899
+ entity_type TEXT NOT NULL,
900
+ canonical_key TEXT NOT NULL,
901
+ title TEXT NOT NULL,
902
+ stage TEXT NOT NULL DEFAULT 'raw',
903
+ status TEXT NOT NULL DEFAULT 'active',
904
+ current_json TEXT NOT NULL,
905
+ title_norm TEXT,
906
+ search_text TEXT,
907
+ created_at TEXT DEFAULT (datetime('now')),
908
+ updated_at TEXT DEFAULT (datetime('now'))
909
+ );
910
+
911
+ -- Entity aliases for canonical key lookup
912
+ CREATE TABLE IF NOT EXISTS entity_aliases (
913
+ entity_type TEXT NOT NULL,
914
+ canonical_key TEXT NOT NULL,
915
+ entity_id TEXT NOT NULL,
916
+ is_primary INTEGER DEFAULT 0,
917
+ created_at TEXT DEFAULT (datetime('now')),
918
+ PRIMARY KEY(entity_type, canonical_key)
919
+ );
920
+
921
+ -- Edges (relationships between entries/entities)
922
+ CREATE TABLE IF NOT EXISTS edges (
923
+ edge_id TEXT PRIMARY KEY,
924
+ src_type TEXT NOT NULL,
925
+ src_id TEXT NOT NULL,
926
+ rel_type TEXT NOT NULL,
927
+ dst_type TEXT NOT NULL,
928
+ dst_id TEXT NOT NULL,
929
+ meta_json TEXT,
930
+ created_at TEXT DEFAULT (datetime('now'))
931
+ );
932
+
933
+ -- Vector Outbox V2 Table
934
+ CREATE TABLE IF NOT EXISTS vector_outbox (
935
+ job_id TEXT PRIMARY KEY,
936
+ item_kind TEXT NOT NULL,
937
+ item_id TEXT NOT NULL,
938
+ embedding_version TEXT NOT NULL,
939
+ status TEXT NOT NULL DEFAULT 'pending',
940
+ retry_count INTEGER DEFAULT 0,
941
+ error TEXT,
942
+ created_at TEXT DEFAULT (datetime('now')),
943
+ updated_at TEXT DEFAULT (datetime('now')),
944
+ UNIQUE(item_kind, item_id, embedding_version)
945
+ );
946
+
947
+ -- Build Runs
948
+ CREATE TABLE IF NOT EXISTS build_runs (
949
+ build_id TEXT PRIMARY KEY,
950
+ started_at TEXT NOT NULL,
951
+ finished_at TEXT,
952
+ extractor_model TEXT NOT NULL,
953
+ extractor_prompt_hash TEXT NOT NULL,
954
+ embedder_model TEXT NOT NULL,
955
+ embedding_version TEXT NOT NULL,
956
+ idris_version TEXT NOT NULL,
957
+ schema_version TEXT NOT NULL,
958
+ status TEXT NOT NULL DEFAULT 'running',
959
+ error TEXT
960
+ );
961
+
962
+ -- Pipeline Metrics
963
+ CREATE TABLE IF NOT EXISTS pipeline_metrics (
964
+ id TEXT PRIMARY KEY,
965
+ ts TEXT NOT NULL,
966
+ stage TEXT NOT NULL,
967
+ latency_ms REAL NOT NULL,
968
+ success INTEGER NOT NULL,
969
+ error TEXT,
970
+ session_id TEXT
971
+ );
972
+
973
+ -- Working Set table (active memory window)
974
+ CREATE TABLE IF NOT EXISTS working_set (
975
+ id TEXT PRIMARY KEY,
976
+ event_id TEXT NOT NULL,
977
+ added_at TEXT DEFAULT (datetime('now')),
978
+ relevance_score REAL DEFAULT 1.0,
979
+ topics TEXT,
980
+ expires_at TEXT
981
+ );
982
+
983
+ -- Consolidated Memories table (long-term integrated memories)
984
+ CREATE TABLE IF NOT EXISTS consolidated_memories (
985
+ memory_id TEXT PRIMARY KEY,
986
+ summary TEXT NOT NULL,
987
+ topics TEXT,
988
+ source_events TEXT,
989
+ confidence REAL DEFAULT 0.5,
990
+ created_at TEXT DEFAULT (datetime('now')),
991
+ accessed_at TEXT,
992
+ access_count INTEGER DEFAULT 0
993
+ );
994
+
995
+ -- Continuity Log table (tracks context transitions)
996
+ CREATE TABLE IF NOT EXISTS continuity_log (
997
+ log_id TEXT PRIMARY KEY,
998
+ from_context_id TEXT,
999
+ to_context_id TEXT,
1000
+ continuity_score REAL,
1001
+ transition_type TEXT,
1002
+ created_at TEXT DEFAULT (datetime('now'))
1003
+ );
1004
+
1005
+ -- Endless Mode Config table
1006
+ CREATE TABLE IF NOT EXISTS endless_config (
1007
+ key TEXT PRIMARY KEY,
1008
+ value TEXT,
1009
+ updated_at TEXT DEFAULT (datetime('now'))
1010
+ );
1011
+
1012
+ -- Sync position tracking (for SQLite -> DuckDB sync)
1013
+ CREATE TABLE IF NOT EXISTS sync_positions (
1014
+ target_name TEXT PRIMARY KEY,
1015
+ last_event_id TEXT,
1016
+ last_timestamp TEXT,
1017
+ updated_at TEXT DEFAULT (datetime('now'))
1018
+ );
1019
+
1020
+ -- Create indexes
1021
+ CREATE INDEX IF NOT EXISTS idx_events_session ON events(session_id);
1022
+ CREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp);
1023
+ CREATE INDEX IF NOT EXISTS idx_entries_type ON entries(entry_type);
1024
+ CREATE INDEX IF NOT EXISTS idx_entries_stage ON entries(stage);
1025
+ CREATE INDEX IF NOT EXISTS idx_entries_canonical ON entries(canonical_key);
1026
+ CREATE INDEX IF NOT EXISTS idx_entities_type_key ON entities(entity_type, canonical_key);
1027
+ CREATE INDEX IF NOT EXISTS idx_entities_status ON entities(status);
1028
+ CREATE INDEX IF NOT EXISTS idx_edges_src ON edges(src_id, rel_type);
1029
+ CREATE INDEX IF NOT EXISTS idx_edges_dst ON edges(dst_id, rel_type);
1030
+ CREATE INDEX IF NOT EXISTS idx_edges_rel ON edges(rel_type);
1031
+ CREATE INDEX IF NOT EXISTS idx_outbox_status ON vector_outbox(status);
1032
+ CREATE INDEX IF NOT EXISTS idx_working_set_expires ON working_set(expires_at);
1033
+ CREATE INDEX IF NOT EXISTS idx_working_set_relevance ON working_set(relevance_score);
1034
+ CREATE INDEX IF NOT EXISTS idx_consolidated_confidence ON consolidated_memories(confidence);
1035
+ CREATE INDEX IF NOT EXISTS idx_continuity_created ON continuity_log(created_at);
1036
+ CREATE INDEX IF NOT EXISTS idx_embedding_outbox_status ON embedding_outbox(status);
1037
+ `);
1038
+ const tableInfo = sqliteAll(this.db, "PRAGMA table_info(events)", []);
1039
+ const columnNames = tableInfo.map((col) => col.name);
1040
+ if (!columnNames.includes("access_count")) {
1041
+ try {
1042
+ sqliteExec(this.db, `
1043
+ ALTER TABLE events ADD COLUMN access_count INTEGER DEFAULT 0;
1044
+ `);
1045
+ } catch (err) {
1046
+ console.error("Error adding access_count column:", err);
1047
+ }
1048
+ }
1049
+ if (!columnNames.includes("last_accessed_at")) {
1050
+ try {
1051
+ sqliteExec(this.db, `
1052
+ ALTER TABLE events ADD COLUMN last_accessed_at TEXT;
1053
+ `);
1054
+ } catch (err) {
1055
+ console.error("Error adding last_accessed_at column:", err);
1056
+ }
1057
+ }
1058
+ try {
1059
+ sqliteExec(this.db, `
1060
+ CREATE INDEX IF NOT EXISTS idx_events_access_count ON events(access_count DESC);
1061
+ `);
1062
+ } catch (err) {
1063
+ }
1064
+ try {
1065
+ sqliteExec(this.db, `
1066
+ CREATE INDEX IF NOT EXISTS idx_events_last_accessed ON events(last_accessed_at DESC);
1067
+ `);
1068
+ } catch (err) {
1069
+ }
1070
+ this.initialized = true;
1071
+ }
1072
+ /**
1073
+ * Append event to store (Append-only, Idempotent)
1074
+ */
1075
+ async append(input) {
1076
+ await this.initialize();
1077
+ const canonicalKey = makeCanonicalKey(input.content);
1078
+ const dedupeKey = makeDedupeKey(input.content, input.sessionId);
1079
+ const existing = sqliteGet(
1080
+ this.db,
1081
+ `SELECT event_id FROM event_dedup WHERE dedupe_key = ?`,
1082
+ [dedupeKey]
1083
+ );
1084
+ if (existing) {
1085
+ return {
1086
+ success: true,
1087
+ eventId: existing.event_id,
1088
+ isDuplicate: true
1089
+ };
1090
+ }
1091
+ const id = randomUUID2();
1092
+ const timestamp = toSQLiteTimestamp(input.timestamp);
1093
+ try {
1094
+ const insertEvent = this.db.prepare(`
1095
+ INSERT INTO events (id, event_type, session_id, timestamp, content, canonical_key, dedupe_key, metadata)
1096
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
1097
+ `);
1098
+ const insertDedup = this.db.prepare(`
1099
+ INSERT INTO event_dedup (dedupe_key, event_id) VALUES (?, ?)
1100
+ `);
1101
+ const insertLevel = this.db.prepare(`
1102
+ INSERT INTO memory_levels (event_id, level) VALUES (?, 'L0')
1103
+ `);
1104
+ const transaction = this.db.transaction(() => {
1105
+ insertEvent.run(
1106
+ id,
1107
+ input.eventType,
1108
+ input.sessionId,
1109
+ timestamp,
1110
+ input.content,
1111
+ canonicalKey,
1112
+ dedupeKey,
1113
+ JSON.stringify(input.metadata || {})
1114
+ );
1115
+ insertDedup.run(dedupeKey, id);
1116
+ insertLevel.run(id);
1117
+ });
1118
+ transaction();
1119
+ return { success: true, eventId: id, isDuplicate: false };
1120
+ } catch (error) {
1121
+ return {
1122
+ success: false,
1123
+ error: error instanceof Error ? error.message : String(error)
1124
+ };
1125
+ }
1126
+ }
1127
+ /**
1128
+ * Get events by session ID
1129
+ */
1130
+ async getSessionEvents(sessionId) {
1131
+ await this.initialize();
1132
+ const rows = sqliteAll(
1133
+ this.db,
1134
+ `SELECT * FROM events WHERE session_id = ? ORDER BY timestamp ASC`,
1135
+ [sessionId]
1136
+ );
1137
+ return rows.map(this.rowToEvent);
1138
+ }
1139
+ /**
1140
+ * Get recent events
1141
+ */
1142
+ async getRecentEvents(limit = 100) {
1143
+ await this.initialize();
1144
+ const rows = sqliteAll(
1145
+ this.db,
1146
+ `SELECT * FROM events ORDER BY timestamp DESC LIMIT ?`,
1147
+ [limit]
1148
+ );
1149
+ return rows.map(this.rowToEvent);
1150
+ }
1151
+ /**
1152
+ * Get event by ID
1153
+ */
1154
+ async getEvent(id) {
1155
+ await this.initialize();
1156
+ const row = sqliteGet(
1157
+ this.db,
1158
+ `SELECT * FROM events WHERE id = ?`,
1159
+ [id]
1160
+ );
1161
+ if (!row)
1162
+ return null;
1163
+ return this.rowToEvent(row);
1164
+ }
1165
+ /**
1166
+ * Get events since a timestamp (for sync)
1167
+ */
1168
+ async getEventsSince(timestamp, limit = 1e3) {
1169
+ await this.initialize();
1170
+ const rows = sqliteAll(
1171
+ this.db,
1172
+ `SELECT * FROM events WHERE timestamp > ? ORDER BY timestamp ASC LIMIT ?`,
1173
+ [timestamp, limit]
1174
+ );
1175
+ return rows.map(this.rowToEvent);
1176
+ }
1177
+ /**
1178
+ * Create or update session
1179
+ */
1180
+ async upsertSession(session) {
1181
+ await this.initialize();
1182
+ const existing = sqliteGet(
1183
+ this.db,
1184
+ `SELECT id FROM sessions WHERE id = ?`,
1185
+ [session.id]
1186
+ );
1187
+ if (!existing) {
1188
+ sqliteRun(
1189
+ this.db,
1190
+ `INSERT INTO sessions (id, started_at, project_path, tags)
1191
+ VALUES (?, ?, ?, ?)`,
1192
+ [
1193
+ session.id,
1194
+ toSQLiteTimestamp(session.startedAt || /* @__PURE__ */ new Date()),
1195
+ session.projectPath || null,
1196
+ JSON.stringify(session.tags || [])
1197
+ ]
1198
+ );
1199
+ } else {
1200
+ const updates = [];
1201
+ const values = [];
1202
+ if (session.endedAt) {
1203
+ updates.push("ended_at = ?");
1204
+ values.push(toSQLiteTimestamp(session.endedAt));
1205
+ }
1206
+ if (session.summary) {
1207
+ updates.push("summary = ?");
1208
+ values.push(session.summary);
1209
+ }
1210
+ if (session.tags) {
1211
+ updates.push("tags = ?");
1212
+ values.push(JSON.stringify(session.tags));
1213
+ }
1214
+ if (updates.length > 0) {
1215
+ values.push(session.id);
1216
+ sqliteRun(
1217
+ this.db,
1218
+ `UPDATE sessions SET ${updates.join(", ")} WHERE id = ?`,
1219
+ values
1220
+ );
1221
+ }
1222
+ }
1223
+ }
1224
+ /**
1225
+ * Get session by ID
1226
+ */
1227
+ async getSession(id) {
1228
+ await this.initialize();
1229
+ const row = sqliteGet(
1230
+ this.db,
1231
+ `SELECT * FROM sessions WHERE id = ?`,
1232
+ [id]
1233
+ );
1234
+ if (!row)
1235
+ return null;
1236
+ return {
1237
+ id: row.id,
1238
+ startedAt: toDateFromSQLite(row.started_at),
1239
+ endedAt: row.ended_at ? toDateFromSQLite(row.ended_at) : void 0,
1240
+ projectPath: row.project_path,
1241
+ summary: row.summary,
1242
+ tags: row.tags ? JSON.parse(row.tags) : void 0
1243
+ };
1244
+ }
1245
+ /**
1246
+ * Get all sessions
1247
+ */
1248
+ async getAllSessions() {
1249
+ await this.initialize();
1250
+ const rows = sqliteAll(
1251
+ this.db,
1252
+ `SELECT * FROM sessions ORDER BY started_at DESC`
1253
+ );
1254
+ return rows.map((row) => ({
1255
+ id: row.id,
1256
+ startedAt: toDateFromSQLite(row.started_at),
1257
+ endedAt: row.ended_at ? toDateFromSQLite(row.ended_at) : void 0,
1258
+ projectPath: row.project_path,
1259
+ summary: row.summary,
1260
+ tags: row.tags ? JSON.parse(row.tags) : void 0
1261
+ }));
1262
+ }
1263
+ /**
1264
+ * Add to embedding outbox
1265
+ */
1266
+ async enqueueForEmbedding(eventId, content) {
1267
+ await this.initialize();
1268
+ const id = randomUUID2();
1269
+ sqliteRun(
1270
+ this.db,
1271
+ `INSERT INTO embedding_outbox (id, event_id, content, status, retry_count)
1272
+ VALUES (?, ?, ?, 'pending', 0)`,
1273
+ [id, eventId, content]
1274
+ );
1275
+ return id;
1276
+ }
1277
+ /**
1278
+ * Get pending outbox items
1279
+ */
1280
+ async getPendingOutboxItems(limit = 32) {
1281
+ await this.initialize();
1282
+ const pending = sqliteAll(
1283
+ this.db,
1284
+ `SELECT * FROM embedding_outbox
1285
+ WHERE status = 'pending'
1286
+ ORDER BY created_at
1287
+ LIMIT ?`,
1288
+ [limit]
1289
+ );
1290
+ if (pending.length === 0)
1291
+ return [];
1292
+ const ids = pending.map((r) => r.id);
1293
+ const placeholders = ids.map(() => "?").join(",");
1294
+ sqliteRun(
1295
+ this.db,
1296
+ `UPDATE embedding_outbox SET status = 'processing' WHERE id IN (${placeholders})`,
1297
+ ids
1298
+ );
1299
+ return pending.map((row) => ({
1300
+ id: row.id,
1301
+ eventId: row.event_id,
1302
+ content: row.content,
1303
+ status: "processing",
1304
+ retryCount: row.retry_count,
1305
+ createdAt: toDateFromSQLite(row.created_at),
1306
+ errorMessage: row.error_message
1307
+ }));
1308
+ }
1309
+ /**
1310
+ * Mark outbox items as done
1311
+ */
1312
+ async completeOutboxItems(ids) {
1313
+ if (ids.length === 0)
1314
+ return;
1315
+ const placeholders = ids.map(() => "?").join(",");
1316
+ sqliteRun(
1317
+ this.db,
1318
+ `DELETE FROM embedding_outbox WHERE id IN (${placeholders})`,
1319
+ ids
1320
+ );
1321
+ }
1322
+ /**
1323
+ * Mark outbox items as failed
1324
+ */
1325
+ async failOutboxItems(ids, error) {
1326
+ if (ids.length === 0)
1327
+ return;
1328
+ const placeholders = ids.map(() => "?").join(",");
1329
+ sqliteRun(
1330
+ this.db,
1331
+ `UPDATE embedding_outbox
1332
+ SET status = CASE WHEN retry_count >= 3 THEN 'failed' ELSE 'pending' END,
1333
+ retry_count = retry_count + 1,
1334
+ error_message = ?
1335
+ WHERE id IN (${placeholders})`,
1336
+ [error, ...ids]
1337
+ );
1338
+ }
1339
+ /**
1340
+ * Update memory level
1341
+ */
1342
+ async updateMemoryLevel(eventId, level) {
1343
+ await this.initialize();
1344
+ sqliteRun(
1345
+ this.db,
1346
+ `UPDATE memory_levels SET level = ?, promoted_at = datetime('now') WHERE event_id = ?`,
1347
+ [level, eventId]
1348
+ );
1349
+ }
1350
+ /**
1351
+ * Get memory level statistics
1352
+ */
1353
+ async getLevelStats() {
1354
+ await this.initialize();
1355
+ const rows = sqliteAll(
1356
+ this.db,
1357
+ `SELECT level, COUNT(*) as count FROM memory_levels GROUP BY level`
1358
+ );
1359
+ return rows;
1360
+ }
1361
+ /**
1362
+ * Get events by memory level
1363
+ */
1364
+ async getEventsByLevel(level, options) {
1365
+ await this.initialize();
1366
+ const limit = options?.limit || 50;
1367
+ const offset = options?.offset || 0;
1368
+ const rows = sqliteAll(
1369
+ this.db,
1370
+ `SELECT e.* FROM events e
1371
+ INNER JOIN memory_levels ml ON e.id = ml.event_id
1372
+ WHERE ml.level = ?
1373
+ ORDER BY e.timestamp DESC
1374
+ LIMIT ? OFFSET ?`,
1375
+ [level, limit, offset]
1376
+ );
1377
+ return rows.map((row) => this.rowToEvent(row));
1378
+ }
1379
+ /**
1380
+ * Get memory level for a specific event
1381
+ */
1382
+ async getEventLevel(eventId) {
1383
+ await this.initialize();
1384
+ const row = sqliteGet(
1385
+ this.db,
1386
+ `SELECT level FROM memory_levels WHERE event_id = ?`,
1387
+ [eventId]
1388
+ );
1389
+ return row ? row.level : null;
1390
+ }
1391
+ /**
1392
+ * Get sync position for a target
1393
+ */
1394
+ async getSyncPosition(targetName) {
1395
+ await this.initialize();
1396
+ const row = sqliteGet(
1397
+ this.db,
1398
+ `SELECT last_event_id, last_timestamp FROM sync_positions WHERE target_name = ?`,
1399
+ [targetName]
1400
+ );
1401
+ return {
1402
+ lastEventId: row?.last_event_id ?? null,
1403
+ lastTimestamp: row?.last_timestamp ?? null
1404
+ };
1405
+ }
1406
+ /**
1407
+ * Update sync position for a target
1408
+ */
1409
+ async updateSyncPosition(targetName, lastEventId, lastTimestamp) {
1410
+ await this.initialize();
1411
+ sqliteRun(
1412
+ this.db,
1413
+ `INSERT OR REPLACE INTO sync_positions (target_name, last_event_id, last_timestamp, updated_at)
1414
+ VALUES (?, ?, ?, datetime('now'))`,
1415
+ [targetName, lastEventId, lastTimestamp]
1416
+ );
1417
+ }
1418
+ /**
1419
+ * Get config value for endless mode
1420
+ */
1421
+ async getEndlessConfig(key) {
1422
+ await this.initialize();
1423
+ const row = sqliteGet(
1424
+ this.db,
1425
+ `SELECT value FROM endless_config WHERE key = ?`,
1426
+ [key]
1427
+ );
1428
+ if (!row)
1429
+ return null;
1430
+ return JSON.parse(row.value);
1431
+ }
1432
+ /**
1433
+ * Set config value for endless mode
1434
+ */
1435
+ async setEndlessConfig(key, value) {
1436
+ await this.initialize();
1437
+ sqliteRun(
1438
+ this.db,
1439
+ `INSERT OR REPLACE INTO endless_config (key, value, updated_at)
1440
+ VALUES (?, ?, datetime('now'))`,
1441
+ [key, JSON.stringify(value)]
1442
+ );
1443
+ }
1444
+ /**
1445
+ * Increment access count for events
1446
+ */
1447
+ async incrementAccessCount(eventIds) {
1448
+ if (eventIds.length === 0 || this.readOnly)
1449
+ return;
1450
+ await this.initialize();
1451
+ const placeholders = eventIds.map(() => "?").join(",");
1452
+ const currentTime = toSQLiteTimestamp(/* @__PURE__ */ new Date());
1453
+ sqliteRun(
1454
+ this.db,
1455
+ `UPDATE events
1456
+ SET access_count = access_count + 1,
1457
+ last_accessed_at = ?
1458
+ WHERE id IN (${placeholders})`,
1459
+ [currentTime, ...eventIds]
1460
+ );
1461
+ }
1462
+ /**
1463
+ * Get most accessed memories
1464
+ */
1465
+ async getMostAccessed(limit = 10) {
1466
+ await this.initialize();
1467
+ const rows = sqliteAll(
1468
+ this.db,
1469
+ `SELECT * FROM events
1470
+ WHERE access_count > 0
1471
+ ORDER BY access_count DESC, last_accessed_at DESC
1472
+ LIMIT ?`,
1473
+ [limit]
1474
+ );
1475
+ return rows.map((row) => this.rowToEvent(row));
1476
+ }
1477
+ /**
1478
+ * Get database instance for direct access
1479
+ */
1480
+ getDatabase() {
1481
+ return this.db;
1482
+ }
704
1483
  /**
705
1484
  * Close database connection
706
1485
  */
707
1486
  async close() {
708
- await dbClose(this.db);
1487
+ sqliteClose(this.db);
709
1488
  }
710
1489
  /**
711
1490
  * Convert database row to MemoryEvent
712
1491
  */
713
1492
  rowToEvent(row) {
714
- return {
1493
+ const event = {
715
1494
  id: row.id,
716
1495
  eventType: row.event_type,
717
1496
  sessionId: row.session_id,
718
- timestamp: toDate(row.timestamp),
1497
+ timestamp: toDateFromSQLite(row.timestamp),
719
1498
  content: row.content,
720
1499
  canonicalKey: row.canonical_key,
721
1500
  dedupeKey: row.dedupe_key,
722
1501
  metadata: row.metadata ? JSON.parse(row.metadata) : void 0
723
1502
  };
1503
+ if (row.access_count !== void 0) {
1504
+ event.access_count = row.access_count;
1505
+ }
1506
+ if (row.last_accessed_at !== void 0) {
1507
+ event.last_accessed_at = row.last_accessed_at;
1508
+ }
1509
+ return event;
1510
+ }
1511
+ };
1512
+
1513
+ // src/core/sync-worker.ts
1514
+ var DEFAULT_CONFIG = {
1515
+ intervalMs: 3e4,
1516
+ batchSize: 500,
1517
+ maxRetries: 3,
1518
+ retryDelayMs: 5e3
1519
+ };
1520
+ var SyncWorker = class {
1521
+ constructor(sqliteStore, duckdbStore, config) {
1522
+ this.sqliteStore = sqliteStore;
1523
+ this.duckdbStore = duckdbStore;
1524
+ this.config = { ...DEFAULT_CONFIG, ...config };
1525
+ }
1526
+ config;
1527
+ intervalHandle = null;
1528
+ running = false;
1529
+ stats = {
1530
+ lastSyncAt: null,
1531
+ eventsSynced: 0,
1532
+ sessionsSynced: 0,
1533
+ errors: 0,
1534
+ status: "idle"
1535
+ };
1536
+ /**
1537
+ * Start the sync worker
1538
+ */
1539
+ start() {
1540
+ if (this.running)
1541
+ return;
1542
+ this.running = true;
1543
+ this.stats.status = "idle";
1544
+ this.syncNow().catch((err) => {
1545
+ console.error("[SyncWorker] Initial sync failed:", err);
1546
+ });
1547
+ this.intervalHandle = setInterval(() => {
1548
+ this.syncNow().catch((err) => {
1549
+ console.error("[SyncWorker] Periodic sync failed:", err);
1550
+ });
1551
+ }, this.config.intervalMs);
1552
+ }
1553
+ /**
1554
+ * Stop the sync worker
1555
+ */
1556
+ stop() {
1557
+ this.running = false;
1558
+ this.stats.status = "stopped";
1559
+ if (this.intervalHandle) {
1560
+ clearInterval(this.intervalHandle);
1561
+ this.intervalHandle = null;
1562
+ }
1563
+ }
1564
+ /**
1565
+ * Trigger immediate sync
1566
+ */
1567
+ async syncNow() {
1568
+ if (this.stats.status === "syncing") {
1569
+ return;
1570
+ }
1571
+ this.stats.status = "syncing";
1572
+ try {
1573
+ await this.syncEvents();
1574
+ await this.syncSessions();
1575
+ this.stats.lastSyncAt = /* @__PURE__ */ new Date();
1576
+ this.stats.status = "idle";
1577
+ } catch (error) {
1578
+ this.stats.errors++;
1579
+ this.stats.status = "error";
1580
+ throw error;
1581
+ }
1582
+ }
1583
+ /**
1584
+ * Sync events from SQLite to DuckDB
1585
+ */
1586
+ async syncEvents() {
1587
+ const targetName = "duckdb_analytics";
1588
+ const position = await this.sqliteStore.getSyncPosition(targetName);
1589
+ const lastTimestamp = position.lastTimestamp || "1970-01-01T00:00:00.000Z";
1590
+ let hasMore = true;
1591
+ let totalSynced = 0;
1592
+ while (hasMore) {
1593
+ const events = await this.sqliteStore.getEventsSince(lastTimestamp, this.config.batchSize);
1594
+ if (events.length === 0) {
1595
+ hasMore = false;
1596
+ break;
1597
+ }
1598
+ await this.retryWithBackoff(async () => {
1599
+ for (const event of events) {
1600
+ await this.insertEventToDuckDB(event);
1601
+ }
1602
+ });
1603
+ totalSynced += events.length;
1604
+ const lastEvent = events[events.length - 1];
1605
+ await this.sqliteStore.updateSyncPosition(
1606
+ targetName,
1607
+ lastEvent.id,
1608
+ lastEvent.timestamp.toISOString()
1609
+ );
1610
+ hasMore = events.length === this.config.batchSize;
1611
+ }
1612
+ this.stats.eventsSynced += totalSynced;
1613
+ }
1614
+ /**
1615
+ * Sync sessions from SQLite to DuckDB
1616
+ */
1617
+ async syncSessions() {
1618
+ const sessions = await this.sqliteStore.getAllSessions();
1619
+ for (const session of sessions) {
1620
+ await this.retryWithBackoff(async () => {
1621
+ await this.duckdbStore.upsertSession(session);
1622
+ });
1623
+ }
1624
+ this.stats.sessionsSynced = sessions.length;
1625
+ }
1626
+ /**
1627
+ * Insert a single event into DuckDB
1628
+ */
1629
+ async insertEventToDuckDB(event) {
1630
+ await this.duckdbStore.append({
1631
+ eventType: event.eventType,
1632
+ sessionId: event.sessionId,
1633
+ timestamp: event.timestamp,
1634
+ content: event.content,
1635
+ metadata: event.metadata
1636
+ });
1637
+ }
1638
+ /**
1639
+ * Retry operation with exponential backoff
1640
+ */
1641
+ async retryWithBackoff(fn) {
1642
+ let lastError = null;
1643
+ for (let attempt = 0; attempt < this.config.maxRetries; attempt++) {
1644
+ try {
1645
+ return await fn();
1646
+ } catch (error) {
1647
+ lastError = error instanceof Error ? error : new Error(String(error));
1648
+ if (attempt < this.config.maxRetries - 1) {
1649
+ const delay = this.config.retryDelayMs * Math.pow(2, attempt);
1650
+ await this.sleep(delay);
1651
+ }
1652
+ }
1653
+ }
1654
+ throw lastError;
1655
+ }
1656
+ /**
1657
+ * Sleep utility
1658
+ */
1659
+ sleep(ms) {
1660
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
1661
+ }
1662
+ /**
1663
+ * Get sync statistics
1664
+ */
1665
+ getStats() {
1666
+ return { ...this.stats };
1667
+ }
1668
+ /**
1669
+ * Check if worker is running
1670
+ */
1671
+ isRunning() {
1672
+ return this.running;
724
1673
  }
725
1674
  };
726
1675
 
@@ -952,7 +1901,7 @@ function getDefaultEmbedder() {
952
1901
  }
953
1902
 
954
1903
  // src/core/vector-outbox.ts
955
- var DEFAULT_CONFIG = {
1904
+ var DEFAULT_CONFIG2 = {
956
1905
  embeddingVersion: "v1",
957
1906
  maxRetries: 3,
958
1907
  stuckThresholdMs: 5 * 60 * 1e3,
@@ -961,7 +1910,7 @@ var DEFAULT_CONFIG = {
961
1910
  };
962
1911
 
963
1912
  // src/core/vector-worker.ts
964
- var DEFAULT_CONFIG2 = {
1913
+ var DEFAULT_CONFIG3 = {
965
1914
  batchSize: 32,
966
1915
  pollIntervalMs: 1e3,
967
1916
  maxRetries: 3
@@ -972,12 +1921,13 @@ var VectorWorker = class {
972
1921
  embedder;
973
1922
  config;
974
1923
  running = false;
1924
+ stopping = false;
975
1925
  pollTimeout = null;
976
1926
  constructor(eventStore, vectorStore, embedder, config = {}) {
977
1927
  this.eventStore = eventStore;
978
1928
  this.vectorStore = vectorStore;
979
1929
  this.embedder = embedder;
980
- this.config = { ...DEFAULT_CONFIG2, ...config };
1930
+ this.config = { ...DEFAULT_CONFIG3, ...config };
981
1931
  }
982
1932
  /**
983
1933
  * Start the worker polling loop
@@ -986,6 +1936,7 @@ var VectorWorker = class {
986
1936
  if (this.running)
987
1937
  return;
988
1938
  this.running = true;
1939
+ this.stopping = false;
989
1940
  this.poll();
990
1941
  }
991
1942
  /**
@@ -993,6 +1944,7 @@ var VectorWorker = class {
993
1944
  */
994
1945
  stop() {
995
1946
  this.running = false;
1947
+ this.stopping = true;
996
1948
  if (this.pollTimeout) {
997
1949
  clearTimeout(this.pollTimeout);
998
1950
  this.pollTimeout = null;
@@ -1042,9 +1994,15 @@ var VectorWorker = class {
1042
1994
  }
1043
1995
  return successful.length;
1044
1996
  } catch (error) {
1045
- const allIds = items.map((i) => i.id);
1046
- const errorMessage = error instanceof Error ? error.message : String(error);
1047
- await this.eventStore.failOutboxItems(allIds, errorMessage);
1997
+ if (!this.stopping) {
1998
+ try {
1999
+ const allIds = items.map((i) => i.id);
2000
+ const errorMessage = error instanceof Error ? error.message : String(error);
2001
+ await this.eventStore.failOutboxItems(allIds, errorMessage);
2002
+ } catch (failError) {
2003
+ console.warn("Could not mark outbox items as failed (database may be closed)");
2004
+ }
2005
+ }
1048
2006
  throw error;
1049
2007
  }
1050
2008
  }
@@ -1052,14 +2010,18 @@ var VectorWorker = class {
1052
2010
  * Poll for new items
1053
2011
  */
1054
2012
  async poll() {
1055
- if (!this.running)
2013
+ if (!this.running || this.stopping)
1056
2014
  return;
1057
2015
  try {
1058
2016
  await this.processBatch();
1059
2017
  } catch (error) {
1060
- console.error("Vector worker error:", error);
2018
+ if (!this.stopping) {
2019
+ console.error("Vector worker error:", error);
2020
+ }
2021
+ }
2022
+ if (this.running && !this.stopping) {
2023
+ this.pollTimeout = setTimeout(() => this.poll(), this.config.pollIntervalMs);
1061
2024
  }
1062
- this.pollTimeout = setTimeout(() => this.poll(), this.config.pollIntervalMs);
1063
2025
  }
1064
2026
  /**
1065
2027
  * Process all pending items (blocking)
@@ -1086,7 +2048,7 @@ function createVectorWorker(eventStore, vectorStore, embedder, config) {
1086
2048
  }
1087
2049
 
1088
2050
  // src/core/matcher.ts
1089
- var DEFAULT_CONFIG3 = {
2051
+ var DEFAULT_CONFIG4 = {
1090
2052
  weights: {
1091
2053
  semanticSimilarity: 0.4,
1092
2054
  ftsScore: 0.25,
@@ -1101,9 +2063,9 @@ var Matcher = class {
1101
2063
  config;
1102
2064
  constructor(config = {}) {
1103
2065
  this.config = {
1104
- ...DEFAULT_CONFIG3,
2066
+ ...DEFAULT_CONFIG4,
1105
2067
  ...config,
1106
- weights: { ...DEFAULT_CONFIG3.weights, ...config.weights }
2068
+ weights: { ...DEFAULT_CONFIG4.weights, ...config.weights }
1107
2069
  };
1108
2070
  }
1109
2071
  /**
@@ -1811,7 +2773,7 @@ function createSharedEventStore(dbPath) {
1811
2773
  }
1812
2774
 
1813
2775
  // src/core/shared-store.ts
1814
- import { randomUUID as randomUUID2 } from "crypto";
2776
+ import { randomUUID as randomUUID3 } from "crypto";
1815
2777
  var SharedStore = class {
1816
2778
  constructor(sharedEventStore) {
1817
2779
  this.sharedEventStore = sharedEventStore;
@@ -1823,7 +2785,7 @@ var SharedStore = class {
1823
2785
  * Promote a verified troubleshooting entry to shared storage
1824
2786
  */
1825
2787
  async promoteEntry(input) {
1826
- const entryId = randomUUID2();
2788
+ const entryId = randomUUID3();
1827
2789
  await dbRun(
1828
2790
  this.db,
1829
2791
  `INSERT INTO shared_troubleshooting (
@@ -2188,7 +3150,7 @@ function createSharedVectorStore(dbPath) {
2188
3150
  }
2189
3151
 
2190
3152
  // src/core/shared-promoter.ts
2191
- import { randomUUID as randomUUID3 } from "crypto";
3153
+ import { randomUUID as randomUUID4 } from "crypto";
2192
3154
  var SharedPromoter = class {
2193
3155
  constructor(sharedStore, sharedVectorStore, embedder, config) {
2194
3156
  this.sharedStore = sharedStore;
@@ -2256,7 +3218,7 @@ var SharedPromoter = class {
2256
3218
  const embeddingContent = this.createEmbeddingContent(input);
2257
3219
  const embedding = await this.embedder.embed(embeddingContent);
2258
3220
  await this.sharedVectorStore.upsert({
2259
- id: randomUUID3(),
3221
+ id: randomUUID4(),
2260
3222
  entryId,
2261
3223
  entryType: "troubleshooting",
2262
3224
  content: embeddingContent,
@@ -2396,7 +3358,7 @@ function createToolObservationEmbedding(toolName, metadata, success) {
2396
3358
  }
2397
3359
 
2398
3360
  // src/core/working-set-store.ts
2399
- import { randomUUID as randomUUID4 } from "crypto";
3361
+ import { randomUUID as randomUUID5 } from "crypto";
2400
3362
  var WorkingSetStore = class {
2401
3363
  constructor(eventStore, config) {
2402
3364
  this.eventStore = eventStore;
@@ -2417,7 +3379,7 @@ var WorkingSetStore = class {
2417
3379
  `INSERT OR REPLACE INTO working_set (id, event_id, added_at, relevance_score, topics, expires_at)
2418
3380
  VALUES (?, ?, CURRENT_TIMESTAMP, ?, ?, ?)`,
2419
3381
  [
2420
- randomUUID4(),
3382
+ randomUUID5(),
2421
3383
  eventId,
2422
3384
  relevanceScore,
2423
3385
  JSON.stringify(topics || []),
@@ -2601,7 +3563,7 @@ function createWorkingSetStore(eventStore, config) {
2601
3563
  }
2602
3564
 
2603
3565
  // src/core/consolidated-store.ts
2604
- import { randomUUID as randomUUID5 } from "crypto";
3566
+ import { randomUUID as randomUUID6 } from "crypto";
2605
3567
  var ConsolidatedStore = class {
2606
3568
  constructor(eventStore) {
2607
3569
  this.eventStore = eventStore;
@@ -2613,7 +3575,7 @@ var ConsolidatedStore = class {
2613
3575
  * Create a new consolidated memory
2614
3576
  */
2615
3577
  async create(input) {
2616
- const memoryId = randomUUID5();
3578
+ const memoryId = randomUUID6();
2617
3579
  await dbRun(
2618
3580
  this.db,
2619
3581
  `INSERT INTO consolidated_memories
@@ -3140,7 +4102,7 @@ function createConsolidationWorker(workingSetStore, consolidatedStore, config) {
3140
4102
  }
3141
4103
 
3142
4104
  // src/core/continuity-manager.ts
3143
- import { randomUUID as randomUUID6 } from "crypto";
4105
+ import { randomUUID as randomUUID7 } from "crypto";
3144
4106
  var ContinuityManager = class {
3145
4107
  constructor(eventStore, config) {
3146
4108
  this.eventStore = eventStore;
@@ -3294,7 +4256,7 @@ var ContinuityManager = class {
3294
4256
  `INSERT INTO continuity_log
3295
4257
  (log_id, from_context_id, to_context_id, continuity_score, transition_type, created_at)
3296
4258
  VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`,
3297
- [randomUUID6(), previous.id, current.id, score, type]
4259
+ [randomUUID7(), previous.id, current.id, score, type]
3298
4260
  );
3299
4261
  }
3300
4262
  /**
@@ -3403,7 +4365,7 @@ function createContinuityManager(eventStore, config) {
3403
4365
  }
3404
4366
 
3405
4367
  // src/core/graduation-worker.ts
3406
- var DEFAULT_CONFIG4 = {
4368
+ var DEFAULT_CONFIG5 = {
3407
4369
  evaluationIntervalMs: 3e5,
3408
4370
  // 5 minutes
3409
4371
  batchSize: 50,
@@ -3411,7 +4373,7 @@ var DEFAULT_CONFIG4 = {
3411
4373
  // 1 hour cooldown between evaluations
3412
4374
  };
3413
4375
  var GraduationWorker = class {
3414
- constructor(eventStore, graduation, config = DEFAULT_CONFIG4) {
4376
+ constructor(eventStore, graduation, config = DEFAULT_CONFIG5) {
3415
4377
  this.eventStore = eventStore;
3416
4378
  this.graduation = graduation;
3417
4379
  this.config = config;
@@ -3519,7 +4481,7 @@ function createGraduationWorker(eventStore, graduation, config) {
3519
4481
  return new GraduationWorker(
3520
4482
  eventStore,
3521
4483
  graduation,
3522
- { ...DEFAULT_CONFIG4, ...config }
4484
+ { ...DEFAULT_CONFIG5, ...config }
3523
4485
  );
3524
4486
  }
3525
4487
 
@@ -3558,7 +4520,11 @@ function getSessionProject(sessionId) {
3558
4520
  return registry.sessions[sessionId] || null;
3559
4521
  }
3560
4522
  var MemoryService = class {
3561
- eventStore;
4523
+ // Primary store: SQLite (WAL mode) - for hooks, always available
4524
+ sqliteStore;
4525
+ // Analytics store: DuckDB - for server reads (optional, synced from SQLite)
4526
+ analyticsStore;
4527
+ syncWorker = null;
3562
4528
  vectorStore;
3563
4529
  embedder;
3564
4530
  matcher;
@@ -3589,17 +4555,39 @@ var MemoryService = class {
3589
4555
  }
3590
4556
  this.projectHash = config.projectHash || null;
3591
4557
  this.sharedStoreConfig = config.sharedStoreConfig ?? { enabled: true };
3592
- this.eventStore = new EventStore(path.join(storagePath, "events.duckdb"), { readOnly: this.readOnly });
4558
+ this.sqliteStore = new SQLiteEventStore(
4559
+ path.join(storagePath, "events.sqlite"),
4560
+ { readonly: this.readOnly }
4561
+ );
4562
+ const analyticsEnabled = config.analyticsEnabled ?? this.readOnly;
4563
+ if (!analyticsEnabled) {
4564
+ this.analyticsStore = null;
4565
+ } else if (this.readOnly) {
4566
+ try {
4567
+ this.analyticsStore = new EventStore(
4568
+ path.join(storagePath, "analytics.duckdb"),
4569
+ { readOnly: true }
4570
+ );
4571
+ } catch {
4572
+ this.analyticsStore = null;
4573
+ }
4574
+ } else {
4575
+ this.analyticsStore = new EventStore(
4576
+ path.join(storagePath, "analytics.duckdb"),
4577
+ { readOnly: false }
4578
+ );
4579
+ }
3593
4580
  this.vectorStore = new VectorStore(path.join(storagePath, "vectors"));
3594
4581
  this.embedder = config.embeddingModel ? new Embedder(config.embeddingModel) : getDefaultEmbedder();
3595
4582
  this.matcher = getDefaultMatcher();
3596
4583
  this.retriever = createRetriever(
3597
- this.eventStore,
4584
+ this.sqliteStore,
4585
+ // Interface compatible
3598
4586
  this.vectorStore,
3599
4587
  this.embedder,
3600
4588
  this.matcher
3601
4589
  );
3602
- this.graduation = createGraduationPipeline(this.eventStore);
4590
+ this.graduation = createGraduationPipeline(this.sqliteStore);
3603
4591
  }
3604
4592
  /**
3605
4593
  * Initialize all components
@@ -3607,23 +4595,38 @@ var MemoryService = class {
3607
4595
  async initialize() {
3608
4596
  if (this.initialized)
3609
4597
  return;
3610
- await this.eventStore.initialize();
4598
+ await this.sqliteStore.initialize();
4599
+ if (this.analyticsStore) {
4600
+ try {
4601
+ await this.analyticsStore.initialize();
4602
+ } catch (error) {
4603
+ console.warn("[MemoryService] Analytics store (DuckDB) initialization failed, using SQLite for reads:", error);
4604
+ }
4605
+ }
3611
4606
  await this.vectorStore.initialize();
3612
4607
  await this.embedder.initialize();
3613
4608
  if (!this.readOnly) {
3614
4609
  this.vectorWorker = createVectorWorker(
3615
- this.eventStore,
4610
+ this.sqliteStore,
3616
4611
  this.vectorStore,
3617
4612
  this.embedder
3618
4613
  );
3619
4614
  this.vectorWorker.start();
3620
4615
  this.retriever.setGraduationPipeline(this.graduation);
3621
4616
  this.graduationWorker = createGraduationWorker(
3622
- this.eventStore,
4617
+ this.sqliteStore,
3623
4618
  this.graduation
3624
4619
  );
3625
4620
  this.graduationWorker.start();
3626
- const savedMode = await this.eventStore.getEndlessConfig("mode");
4621
+ if (this.analyticsStore) {
4622
+ this.syncWorker = new SyncWorker(
4623
+ this.sqliteStore,
4624
+ this.analyticsStore,
4625
+ { intervalMs: 3e4, batchSize: 500 }
4626
+ );
4627
+ this.syncWorker.start();
4628
+ }
4629
+ const savedMode = await this.sqliteStore.getEndlessConfig("mode");
3627
4630
  if (savedMode === "endless") {
3628
4631
  this.endlessMode = "endless";
3629
4632
  await this.initializeEndlessMode();
@@ -3664,7 +4667,7 @@ var MemoryService = class {
3664
4667
  */
3665
4668
  async startSession(sessionId, projectPath) {
3666
4669
  await this.initialize();
3667
- await this.eventStore.upsertSession({
4670
+ await this.sqliteStore.upsertSession({
3668
4671
  id: sessionId,
3669
4672
  startedAt: /* @__PURE__ */ new Date(),
3670
4673
  projectPath
@@ -3675,7 +4678,7 @@ var MemoryService = class {
3675
4678
  */
3676
4679
  async endSession(sessionId, summary) {
3677
4680
  await this.initialize();
3678
- await this.eventStore.upsertSession({
4681
+ await this.sqliteStore.upsertSession({
3679
4682
  id: sessionId,
3680
4683
  endedAt: /* @__PURE__ */ new Date(),
3681
4684
  summary
@@ -3686,7 +4689,7 @@ var MemoryService = class {
3686
4689
  */
3687
4690
  async storeUserPrompt(sessionId, content, metadata) {
3688
4691
  await this.initialize();
3689
- const result = await this.eventStore.append({
4692
+ const result = await this.sqliteStore.append({
3690
4693
  eventType: "user_prompt",
3691
4694
  sessionId,
3692
4695
  timestamp: /* @__PURE__ */ new Date(),
@@ -3694,7 +4697,7 @@ var MemoryService = class {
3694
4697
  metadata
3695
4698
  });
3696
4699
  if (result.success && !result.isDuplicate) {
3697
- await this.eventStore.enqueueForEmbedding(result.eventId, content);
4700
+ await this.sqliteStore.enqueueForEmbedding(result.eventId, content);
3698
4701
  }
3699
4702
  return result;
3700
4703
  }
@@ -3703,7 +4706,7 @@ var MemoryService = class {
3703
4706
  */
3704
4707
  async storeAgentResponse(sessionId, content, metadata) {
3705
4708
  await this.initialize();
3706
- const result = await this.eventStore.append({
4709
+ const result = await this.sqliteStore.append({
3707
4710
  eventType: "agent_response",
3708
4711
  sessionId,
3709
4712
  timestamp: /* @__PURE__ */ new Date(),
@@ -3711,7 +4714,7 @@ var MemoryService = class {
3711
4714
  metadata
3712
4715
  });
3713
4716
  if (result.success && !result.isDuplicate) {
3714
- await this.eventStore.enqueueForEmbedding(result.eventId, content);
4717
+ await this.sqliteStore.enqueueForEmbedding(result.eventId, content);
3715
4718
  }
3716
4719
  return result;
3717
4720
  }
@@ -3720,14 +4723,14 @@ var MemoryService = class {
3720
4723
  */
3721
4724
  async storeSessionSummary(sessionId, summary) {
3722
4725
  await this.initialize();
3723
- const result = await this.eventStore.append({
4726
+ const result = await this.sqliteStore.append({
3724
4727
  eventType: "session_summary",
3725
4728
  sessionId,
3726
4729
  timestamp: /* @__PURE__ */ new Date(),
3727
4730
  content: summary
3728
4731
  });
3729
4732
  if (result.success && !result.isDuplicate) {
3730
- await this.eventStore.enqueueForEmbedding(result.eventId, summary);
4733
+ await this.sqliteStore.enqueueForEmbedding(result.eventId, summary);
3731
4734
  }
3732
4735
  return result;
3733
4736
  }
@@ -3737,7 +4740,7 @@ var MemoryService = class {
3737
4740
  async storeToolObservation(sessionId, payload) {
3738
4741
  await this.initialize();
3739
4742
  const content = JSON.stringify(payload);
3740
- const result = await this.eventStore.append({
4743
+ const result = await this.sqliteStore.append({
3741
4744
  eventType: "tool_observation",
3742
4745
  sessionId,
3743
4746
  timestamp: /* @__PURE__ */ new Date(),
@@ -3753,7 +4756,7 @@ var MemoryService = class {
3753
4756
  payload.metadata || {},
3754
4757
  payload.success
3755
4758
  );
3756
- await this.eventStore.enqueueForEmbedding(result.eventId, embeddingContent);
4759
+ await this.sqliteStore.enqueueForEmbedding(result.eventId, embeddingContent);
3757
4760
  }
3758
4761
  return result;
3759
4762
  }
@@ -3779,21 +4782,21 @@ var MemoryService = class {
3779
4782
  */
3780
4783
  async getSessionHistory(sessionId) {
3781
4784
  await this.initialize();
3782
- return this.eventStore.getSessionEvents(sessionId);
4785
+ return this.sqliteStore.getSessionEvents(sessionId);
3783
4786
  }
3784
4787
  /**
3785
4788
  * Get recent events
3786
4789
  */
3787
4790
  async getRecentEvents(limit = 100) {
3788
4791
  await this.initialize();
3789
- return this.eventStore.getRecentEvents(limit);
4792
+ return this.sqliteStore.getRecentEvents(limit);
3790
4793
  }
3791
4794
  /**
3792
4795
  * Get memory statistics
3793
4796
  */
3794
4797
  async getStats() {
3795
4798
  await this.initialize();
3796
- const recentEvents = await this.eventStore.getRecentEvents(1e4);
4799
+ const recentEvents = await this.sqliteStore.getRecentEvents(1e4);
3797
4800
  const vectorCount = await this.vectorStore.count();
3798
4801
  const levelStats = await this.graduation.getStats();
3799
4802
  return {
@@ -3816,14 +4819,14 @@ var MemoryService = class {
3816
4819
  */
3817
4820
  async getEventsByLevel(level, options) {
3818
4821
  await this.initialize();
3819
- return this.eventStore.getEventsByLevel(level, options);
4822
+ return this.sqliteStore.getEventsByLevel(level, options);
3820
4823
  }
3821
4824
  /**
3822
4825
  * Get memory level for a specific event
3823
4826
  */
3824
4827
  async getEventLevel(eventId) {
3825
4828
  await this.initialize();
3826
- return this.eventStore.getEventLevel(eventId);
4829
+ return this.sqliteStore.getEventLevel(eventId);
3827
4830
  }
3828
4831
  /**
3829
4832
  * Format retrieval results as context for Claude
@@ -3917,21 +4920,21 @@ var MemoryService = class {
3917
4920
  */
3918
4921
  async initializeEndlessMode() {
3919
4922
  const config = await this.getEndlessConfig();
3920
- this.workingSetStore = createWorkingSetStore(this.eventStore, config);
3921
- this.consolidatedStore = createConsolidatedStore(this.eventStore);
4923
+ this.workingSetStore = createWorkingSetStore(this.sqliteStore, config);
4924
+ this.consolidatedStore = createConsolidatedStore(this.sqliteStore);
3922
4925
  this.consolidationWorker = createConsolidationWorker(
3923
4926
  this.workingSetStore,
3924
4927
  this.consolidatedStore,
3925
4928
  config
3926
4929
  );
3927
- this.continuityManager = createContinuityManager(this.eventStore, config);
4930
+ this.continuityManager = createContinuityManager(this.sqliteStore, config);
3928
4931
  this.consolidationWorker.start();
3929
4932
  }
3930
4933
  /**
3931
4934
  * Get Endless Mode configuration
3932
4935
  */
3933
4936
  async getEndlessConfig() {
3934
- const savedConfig = await this.eventStore.getEndlessConfig("config");
4937
+ const savedConfig = await this.sqliteStore.getEndlessConfig("config");
3935
4938
  return savedConfig || this.getDefaultEndlessConfig();
3936
4939
  }
3937
4940
  /**
@@ -3940,7 +4943,7 @@ var MemoryService = class {
3940
4943
  async setEndlessConfig(config) {
3941
4944
  const current = await this.getEndlessConfig();
3942
4945
  const merged = { ...current, ...config };
3943
- await this.eventStore.setEndlessConfig("config", merged);
4946
+ await this.sqliteStore.setEndlessConfig("config", merged);
3944
4947
  }
3945
4948
  /**
3946
4949
  * Set memory mode (session or endless)
@@ -3950,7 +4953,7 @@ var MemoryService = class {
3950
4953
  if (mode === this.endlessMode)
3951
4954
  return;
3952
4955
  this.endlessMode = mode;
3953
- await this.eventStore.setEndlessConfig("mode", mode);
4956
+ await this.sqliteStore.setEndlessConfig("mode", mode);
3954
4957
  if (mode === "endless") {
3955
4958
  await this.initializeEndlessMode();
3956
4959
  } else {
@@ -4008,12 +5011,49 @@ var MemoryService = class {
4008
5011
  return this.consolidatedStore.getAll({ limit });
4009
5012
  }
4010
5013
  /**
4011
- * Get most accessed consolidated memories
5014
+ * Increment access count for memories that were used in prompts
5015
+ */
5016
+ async incrementMemoryAccess(eventIds) {
5017
+ if (eventIds.length === 0)
5018
+ return;
5019
+ if (this.sqliteStore) {
5020
+ await this.sqliteStore.incrementAccessCount(eventIds);
5021
+ } else if (this.eventStore) {
5022
+ await this.eventStore.incrementAccessCount(eventIds);
5023
+ }
5024
+ }
5025
+ /**
5026
+ * Get most accessed memories from events
4012
5027
  */
4013
5028
  async getMostAccessedMemories(limit = 10) {
4014
- if (!this.consolidatedStore)
4015
- return [];
4016
- return this.consolidatedStore.getMostAccessed(limit);
5029
+ console.log("[getMostAccessedMemories] sqliteStore available:", !!this.sqliteStore);
5030
+ if (this.sqliteStore) {
5031
+ const events = await this.sqliteStore.getMostAccessed(limit);
5032
+ console.log("[getMostAccessedMemories] Got events from SQLite:", events.length);
5033
+ return events.map((event) => ({
5034
+ memoryId: event.id,
5035
+ summary: event.content.substring(0, 200) + (event.content.length > 200 ? "..." : ""),
5036
+ topics: [],
5037
+ // Could extract topics from content if needed
5038
+ accessCount: event.access_count || 0,
5039
+ lastAccessed: event.last_accessed_at || null,
5040
+ confidence: 1,
5041
+ createdAt: event.timestamp
5042
+ }));
5043
+ }
5044
+ if (this.consolidatedStore) {
5045
+ const consolidated = await this.consolidatedStore.getMostAccessed(limit);
5046
+ return consolidated.map((m) => ({
5047
+ memoryId: m.memoryId,
5048
+ summary: m.summary,
5049
+ topics: m.topics,
5050
+ accessCount: m.accessCount,
5051
+ lastAccessed: m.accessedAt,
5052
+ confidence: m.confidence,
5053
+ createdAt: m.createdAt
5054
+ }));
5055
+ }
5056
+ return [];
4017
5057
  }
4018
5058
  /**
4019
5059
  * Mark a consolidated memory as accessed
@@ -4138,10 +5178,16 @@ var MemoryService = class {
4138
5178
  if (this.vectorWorker) {
4139
5179
  this.vectorWorker.stop();
4140
5180
  }
5181
+ if (this.syncWorker) {
5182
+ this.syncWorker.stop();
5183
+ }
4141
5184
  if (this.sharedEventStore) {
4142
5185
  await this.sharedEventStore.close();
4143
5186
  }
4144
- await this.eventStore.close();
5187
+ await this.sqliteStore.close();
5188
+ if (this.analyticsStore) {
5189
+ await this.analyticsStore.close();
5190
+ }
4145
5191
  }
4146
5192
  /**
4147
5193
  * Expand ~ to home directory
@@ -4158,7 +5204,11 @@ var GLOBAL_KEY = "__global__";
4158
5204
  function getDefaultMemoryService() {
4159
5205
  if (!serviceCache.has(GLOBAL_KEY)) {
4160
5206
  serviceCache.set(GLOBAL_KEY, new MemoryService({
4161
- storagePath: "~/.claude-code/memory"
5207
+ storagePath: "~/.claude-code/memory",
5208
+ analyticsEnabled: false,
5209
+ // Hooks don't need DuckDB
5210
+ sharedStoreConfig: { enabled: false }
5211
+ // Shared store uses DuckDB too
4162
5212
  }));
4163
5213
  }
4164
5214
  return serviceCache.get(GLOBAL_KEY);
@@ -4170,7 +5220,10 @@ function getMemoryServiceForProject(projectPath, sharedStoreConfig) {
4170
5220
  serviceCache.set(hash, new MemoryService({
4171
5221
  storagePath,
4172
5222
  projectHash: hash,
4173
- sharedStoreConfig
5223
+ // Override shared store config - hooks don't need DuckDB
5224
+ sharedStoreConfig: sharedStoreConfig ?? { enabled: false },
5225
+ analyticsEnabled: false
5226
+ // Hooks don't need DuckDB
4174
5227
  }));
4175
5228
  }
4176
5229
  return serviceCache.get(hash);