claude-memory-layer 1.0.7 → 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 (53) hide show
  1. package/.claude/settings.local.json +10 -1
  2. package/.claude-memory/test.sqlite +0 -0
  3. package/.history/package_20260201192048.json +47 -0
  4. package/.history/package_20260202114053.json +49 -0
  5. package/HANDOFF.md +92 -0
  6. package/dist/cli/index.js +1711 -102
  7. package/dist/cli/index.js.map +4 -4
  8. package/dist/core/index.js +1257 -84
  9. package/dist/core/index.js.map +4 -4
  10. package/dist/hooks/post-tool-use.js +5589 -0
  11. package/dist/hooks/post-tool-use.js.map +7 -0
  12. package/dist/hooks/session-end.js +1382 -85
  13. package/dist/hooks/session-end.js.map +4 -4
  14. package/dist/hooks/session-start.js +1377 -84
  15. package/dist/hooks/session-start.js.map +4 -4
  16. package/dist/hooks/stop.js +1383 -86
  17. package/dist/hooks/stop.js.map +4 -4
  18. package/dist/hooks/user-prompt-submit.js +1412 -84
  19. package/dist/hooks/user-prompt-submit.js.map +4 -4
  20. package/dist/server/api/index.js +1576 -136
  21. package/dist/server/api/index.js.map +4 -4
  22. package/dist/server/index.js +1585 -143
  23. package/dist/server/index.js.map +4 -4
  24. package/dist/services/memory-service.js +1392 -84
  25. package/dist/services/memory-service.js.map +4 -4
  26. package/dist/ui/app.js +304 -0
  27. package/dist/ui/index.html +202 -715
  28. package/dist/ui/style.css +595 -0
  29. package/package.json +4 -1
  30. package/scripts/build.ts +5 -2
  31. package/src/cli/index.ts +226 -0
  32. package/src/core/db-wrapper.ts +8 -1
  33. package/src/core/event-store.ts +70 -3
  34. package/src/core/graduation-worker.ts +171 -0
  35. package/src/core/graduation.ts +15 -2
  36. package/src/core/index.ts +4 -0
  37. package/src/core/retriever.ts +21 -0
  38. package/src/core/sqlite-event-store.ts +849 -0
  39. package/src/core/sqlite-wrapper.ts +108 -0
  40. package/src/core/sync-worker.ts +228 -0
  41. package/src/core/vector-worker.ts +44 -14
  42. package/src/hooks/user-prompt-submit.ts +53 -4
  43. package/src/server/api/citations.ts +7 -3
  44. package/src/server/api/events.ts +7 -3
  45. package/src/server/api/search.ts +7 -3
  46. package/src/server/api/sessions.ts +7 -3
  47. package/src/server/api/stats.ts +159 -12
  48. package/src/server/index.ts +18 -9
  49. package/src/services/memory-service.ts +263 -46
  50. package/src/ui/app.js +304 -0
  51. package/src/ui/index.html +202 -715
  52. package/src/ui/style.css +595 -0
  53. package/test_access.js +49 -0
@@ -66,7 +66,10 @@ function toDate(value) {
66
66
  return new Date(value);
67
67
  return new Date(String(value));
68
68
  }
69
- function createDatabase(path2) {
69
+ function createDatabase(path2, options) {
70
+ if (options?.readOnly) {
71
+ return new duckdb.Database(path2, { access_mode: "READ_ONLY" });
72
+ }
70
73
  return new duckdb.Database(path2);
71
74
  }
72
75
  function dbRun(db, sql, params = []) {
@@ -120,18 +123,24 @@ function dbClose(db) {
120
123
 
121
124
  // src/core/event-store.ts
122
125
  var EventStore = class {
123
- constructor(dbPath) {
126
+ constructor(dbPath, options) {
124
127
  this.dbPath = dbPath;
125
- this.db = createDatabase(dbPath);
128
+ this.readOnly = options?.readOnly ?? false;
129
+ this.db = createDatabase(dbPath, { readOnly: this.readOnly });
126
130
  }
127
131
  db;
128
132
  initialized = false;
133
+ readOnly;
129
134
  /**
130
135
  * Initialize database schema
131
136
  */
132
137
  async initialize() {
133
138
  if (this.initialized)
134
139
  return;
140
+ if (this.readOnly) {
141
+ this.initialized = true;
142
+ return;
143
+ }
135
144
  await dbRun(this.db, `
136
145
  CREATE TABLE IF NOT EXISTS events (
137
146
  id VARCHAR PRIMARY KEY,
@@ -608,6 +617,36 @@ var EventStore = class {
608
617
  );
609
618
  return rows;
610
619
  }
620
+ /**
621
+ * Get events by memory level
622
+ */
623
+ async getEventsByLevel(level, options) {
624
+ await this.initialize();
625
+ const limit = options?.limit || 50;
626
+ const offset = options?.offset || 0;
627
+ const rows = await dbAll(
628
+ this.db,
629
+ `SELECT e.* FROM events e
630
+ INNER JOIN memory_levels ml ON e.id = ml.event_id
631
+ WHERE ml.level = ?
632
+ ORDER BY e.timestamp DESC
633
+ LIMIT ? OFFSET ?`,
634
+ [level, limit, offset]
635
+ );
636
+ return rows.map((row) => this.rowToEvent(row));
637
+ }
638
+ /**
639
+ * Get memory level for a specific event
640
+ */
641
+ async getEventLevel(eventId) {
642
+ await this.initialize();
643
+ const rows = await dbAll(
644
+ this.db,
645
+ `SELECT level FROM memory_levels WHERE event_id = ?`,
646
+ [eventId]
647
+ );
648
+ return rows.length > 0 ? rows[0].level : null;
649
+ }
611
650
  // ============================================================
612
651
  // Endless Mode Helper Methods
613
652
  // ============================================================
@@ -638,49 +677,998 @@ var EventStore = class {
638
677
  await this.initialize();
639
678
  await dbRun(
640
679
  this.db,
641
- `INSERT OR REPLACE INTO endless_config (key, value, updated_at)
642
- VALUES (?, ?, CURRENT_TIMESTAMP)`,
643
- [key, JSON.stringify(value)]
680
+ `INSERT OR REPLACE INTO endless_config (key, value, updated_at)
681
+ VALUES (?, ?, CURRENT_TIMESTAMP)`,
682
+ [key, JSON.stringify(value)]
683
+ );
684
+ }
685
+ /**
686
+ * Get all sessions
687
+ */
688
+ async getAllSessions() {
689
+ await this.initialize();
690
+ const rows = await dbAll(
691
+ this.db,
692
+ `SELECT * FROM sessions ORDER BY started_at DESC`
693
+ );
694
+ return rows.map((row) => ({
695
+ id: row.id,
696
+ startedAt: toDate(row.started_at),
697
+ endedAt: row.ended_at ? toDate(row.ended_at) : void 0,
698
+ projectPath: row.project_path,
699
+ summary: row.summary,
700
+ tags: row.tags ? JSON.parse(row.tags) : void 0
701
+ }));
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
+ const tableInfo = sqliteAll(this.db, "PRAGMA table_info(events)", []);
1038
+ const columnNames = tableInfo.map((col) => col.name);
1039
+ if (!columnNames.includes("access_count")) {
1040
+ try {
1041
+ sqliteExec(this.db, `
1042
+ ALTER TABLE events ADD COLUMN access_count INTEGER DEFAULT 0;
1043
+ `);
1044
+ } catch (err) {
1045
+ console.error("Error adding access_count column:", err);
1046
+ }
1047
+ }
1048
+ if (!columnNames.includes("last_accessed_at")) {
1049
+ try {
1050
+ sqliteExec(this.db, `
1051
+ ALTER TABLE events ADD COLUMN last_accessed_at TEXT;
1052
+ `);
1053
+ } catch (err) {
1054
+ console.error("Error adding last_accessed_at column:", err);
1055
+ }
1056
+ }
1057
+ try {
1058
+ sqliteExec(this.db, `
1059
+ CREATE INDEX IF NOT EXISTS idx_events_access_count ON events(access_count DESC);
1060
+ `);
1061
+ } catch (err) {
1062
+ }
1063
+ try {
1064
+ sqliteExec(this.db, `
1065
+ CREATE INDEX IF NOT EXISTS idx_events_last_accessed ON events(last_accessed_at DESC);
1066
+ `);
1067
+ } catch (err) {
1068
+ }
1069
+ this.initialized = true;
1070
+ }
1071
+ /**
1072
+ * Append event to store (Append-only, Idempotent)
1073
+ */
1074
+ async append(input) {
1075
+ await this.initialize();
1076
+ const canonicalKey = makeCanonicalKey(input.content);
1077
+ const dedupeKey = makeDedupeKey(input.content, input.sessionId);
1078
+ const existing = sqliteGet(
1079
+ this.db,
1080
+ `SELECT event_id FROM event_dedup WHERE dedupe_key = ?`,
1081
+ [dedupeKey]
1082
+ );
1083
+ if (existing) {
1084
+ return {
1085
+ success: true,
1086
+ eventId: existing.event_id,
1087
+ isDuplicate: true
1088
+ };
1089
+ }
1090
+ const id = randomUUID2();
1091
+ const timestamp = toSQLiteTimestamp(input.timestamp);
1092
+ try {
1093
+ const insertEvent = this.db.prepare(`
1094
+ INSERT INTO events (id, event_type, session_id, timestamp, content, canonical_key, dedupe_key, metadata)
1095
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
1096
+ `);
1097
+ const insertDedup = this.db.prepare(`
1098
+ INSERT INTO event_dedup (dedupe_key, event_id) VALUES (?, ?)
1099
+ `);
1100
+ const insertLevel = this.db.prepare(`
1101
+ INSERT INTO memory_levels (event_id, level) VALUES (?, 'L0')
1102
+ `);
1103
+ const transaction = this.db.transaction(() => {
1104
+ insertEvent.run(
1105
+ id,
1106
+ input.eventType,
1107
+ input.sessionId,
1108
+ timestamp,
1109
+ input.content,
1110
+ canonicalKey,
1111
+ dedupeKey,
1112
+ JSON.stringify(input.metadata || {})
1113
+ );
1114
+ insertDedup.run(dedupeKey, id);
1115
+ insertLevel.run(id);
1116
+ });
1117
+ transaction();
1118
+ return { success: true, eventId: id, isDuplicate: false };
1119
+ } catch (error) {
1120
+ return {
1121
+ success: false,
1122
+ error: error instanceof Error ? error.message : String(error)
1123
+ };
1124
+ }
1125
+ }
1126
+ /**
1127
+ * Get events by session ID
1128
+ */
1129
+ async getSessionEvents(sessionId) {
1130
+ await this.initialize();
1131
+ const rows = sqliteAll(
1132
+ this.db,
1133
+ `SELECT * FROM events WHERE session_id = ? ORDER BY timestamp ASC`,
1134
+ [sessionId]
1135
+ );
1136
+ return rows.map(this.rowToEvent);
1137
+ }
1138
+ /**
1139
+ * Get recent events
1140
+ */
1141
+ async getRecentEvents(limit = 100) {
1142
+ await this.initialize();
1143
+ const rows = sqliteAll(
1144
+ this.db,
1145
+ `SELECT * FROM events ORDER BY timestamp DESC LIMIT ?`,
1146
+ [limit]
1147
+ );
1148
+ return rows.map(this.rowToEvent);
1149
+ }
1150
+ /**
1151
+ * Get event by ID
1152
+ */
1153
+ async getEvent(id) {
1154
+ await this.initialize();
1155
+ const row = sqliteGet(
1156
+ this.db,
1157
+ `SELECT * FROM events WHERE id = ?`,
1158
+ [id]
1159
+ );
1160
+ if (!row)
1161
+ return null;
1162
+ return this.rowToEvent(row);
1163
+ }
1164
+ /**
1165
+ * Get events since a timestamp (for sync)
1166
+ */
1167
+ async getEventsSince(timestamp, limit = 1e3) {
1168
+ await this.initialize();
1169
+ const rows = sqliteAll(
1170
+ this.db,
1171
+ `SELECT * FROM events WHERE timestamp > ? ORDER BY timestamp ASC LIMIT ?`,
1172
+ [timestamp, limit]
1173
+ );
1174
+ return rows.map(this.rowToEvent);
1175
+ }
1176
+ /**
1177
+ * Create or update session
1178
+ */
1179
+ async upsertSession(session) {
1180
+ await this.initialize();
1181
+ const existing = sqliteGet(
1182
+ this.db,
1183
+ `SELECT id FROM sessions WHERE id = ?`,
1184
+ [session.id]
1185
+ );
1186
+ if (!existing) {
1187
+ sqliteRun(
1188
+ this.db,
1189
+ `INSERT INTO sessions (id, started_at, project_path, tags)
1190
+ VALUES (?, ?, ?, ?)`,
1191
+ [
1192
+ session.id,
1193
+ toSQLiteTimestamp(session.startedAt || /* @__PURE__ */ new Date()),
1194
+ session.projectPath || null,
1195
+ JSON.stringify(session.tags || [])
1196
+ ]
1197
+ );
1198
+ } else {
1199
+ const updates = [];
1200
+ const values = [];
1201
+ if (session.endedAt) {
1202
+ updates.push("ended_at = ?");
1203
+ values.push(toSQLiteTimestamp(session.endedAt));
1204
+ }
1205
+ if (session.summary) {
1206
+ updates.push("summary = ?");
1207
+ values.push(session.summary);
1208
+ }
1209
+ if (session.tags) {
1210
+ updates.push("tags = ?");
1211
+ values.push(JSON.stringify(session.tags));
1212
+ }
1213
+ if (updates.length > 0) {
1214
+ values.push(session.id);
1215
+ sqliteRun(
1216
+ this.db,
1217
+ `UPDATE sessions SET ${updates.join(", ")} WHERE id = ?`,
1218
+ values
1219
+ );
1220
+ }
1221
+ }
1222
+ }
1223
+ /**
1224
+ * Get session by ID
1225
+ */
1226
+ async getSession(id) {
1227
+ await this.initialize();
1228
+ const row = sqliteGet(
1229
+ this.db,
1230
+ `SELECT * FROM sessions WHERE id = ?`,
1231
+ [id]
1232
+ );
1233
+ if (!row)
1234
+ return null;
1235
+ return {
1236
+ id: row.id,
1237
+ startedAt: toDateFromSQLite(row.started_at),
1238
+ endedAt: row.ended_at ? toDateFromSQLite(row.ended_at) : void 0,
1239
+ projectPath: row.project_path,
1240
+ summary: row.summary,
1241
+ tags: row.tags ? JSON.parse(row.tags) : void 0
1242
+ };
1243
+ }
1244
+ /**
1245
+ * Get all sessions
1246
+ */
1247
+ async getAllSessions() {
1248
+ await this.initialize();
1249
+ const rows = sqliteAll(
1250
+ this.db,
1251
+ `SELECT * FROM sessions ORDER BY started_at DESC`
1252
+ );
1253
+ return rows.map((row) => ({
1254
+ id: row.id,
1255
+ startedAt: toDateFromSQLite(row.started_at),
1256
+ endedAt: row.ended_at ? toDateFromSQLite(row.ended_at) : void 0,
1257
+ projectPath: row.project_path,
1258
+ summary: row.summary,
1259
+ tags: row.tags ? JSON.parse(row.tags) : void 0
1260
+ }));
1261
+ }
1262
+ /**
1263
+ * Add to embedding outbox
1264
+ */
1265
+ async enqueueForEmbedding(eventId, content) {
1266
+ await this.initialize();
1267
+ const id = randomUUID2();
1268
+ sqliteRun(
1269
+ this.db,
1270
+ `INSERT INTO embedding_outbox (id, event_id, content, status, retry_count)
1271
+ VALUES (?, ?, ?, 'pending', 0)`,
1272
+ [id, eventId, content]
1273
+ );
1274
+ return id;
1275
+ }
1276
+ /**
1277
+ * Get pending outbox items
1278
+ */
1279
+ async getPendingOutboxItems(limit = 32) {
1280
+ await this.initialize();
1281
+ const pending = sqliteAll(
1282
+ this.db,
1283
+ `SELECT * FROM embedding_outbox
1284
+ WHERE status = 'pending'
1285
+ ORDER BY created_at
1286
+ LIMIT ?`,
1287
+ [limit]
1288
+ );
1289
+ if (pending.length === 0)
1290
+ return [];
1291
+ const ids = pending.map((r) => r.id);
1292
+ const placeholders = ids.map(() => "?").join(",");
1293
+ sqliteRun(
1294
+ this.db,
1295
+ `UPDATE embedding_outbox SET status = 'processing' WHERE id IN (${placeholders})`,
1296
+ ids
1297
+ );
1298
+ return pending.map((row) => ({
1299
+ id: row.id,
1300
+ eventId: row.event_id,
1301
+ content: row.content,
1302
+ status: "processing",
1303
+ retryCount: row.retry_count,
1304
+ createdAt: toDateFromSQLite(row.created_at),
1305
+ errorMessage: row.error_message
1306
+ }));
1307
+ }
1308
+ /**
1309
+ * Mark outbox items as done
1310
+ */
1311
+ async completeOutboxItems(ids) {
1312
+ if (ids.length === 0)
1313
+ return;
1314
+ const placeholders = ids.map(() => "?").join(",");
1315
+ sqliteRun(
1316
+ this.db,
1317
+ `DELETE FROM embedding_outbox WHERE id IN (${placeholders})`,
1318
+ ids
1319
+ );
1320
+ }
1321
+ /**
1322
+ * Mark outbox items as failed
1323
+ */
1324
+ async failOutboxItems(ids, error) {
1325
+ if (ids.length === 0)
1326
+ return;
1327
+ const placeholders = ids.map(() => "?").join(",");
1328
+ sqliteRun(
1329
+ this.db,
1330
+ `UPDATE embedding_outbox
1331
+ SET status = CASE WHEN retry_count >= 3 THEN 'failed' ELSE 'pending' END,
1332
+ retry_count = retry_count + 1,
1333
+ error_message = ?
1334
+ WHERE id IN (${placeholders})`,
1335
+ [error, ...ids]
1336
+ );
1337
+ }
1338
+ /**
1339
+ * Update memory level
1340
+ */
1341
+ async updateMemoryLevel(eventId, level) {
1342
+ await this.initialize();
1343
+ sqliteRun(
1344
+ this.db,
1345
+ `UPDATE memory_levels SET level = ?, promoted_at = datetime('now') WHERE event_id = ?`,
1346
+ [level, eventId]
1347
+ );
1348
+ }
1349
+ /**
1350
+ * Get memory level statistics
1351
+ */
1352
+ async getLevelStats() {
1353
+ await this.initialize();
1354
+ const rows = sqliteAll(
1355
+ this.db,
1356
+ `SELECT level, COUNT(*) as count FROM memory_levels GROUP BY level`
1357
+ );
1358
+ return rows;
1359
+ }
1360
+ /**
1361
+ * Get events by memory level
1362
+ */
1363
+ async getEventsByLevel(level, options) {
1364
+ await this.initialize();
1365
+ const limit = options?.limit || 50;
1366
+ const offset = options?.offset || 0;
1367
+ const rows = sqliteAll(
1368
+ this.db,
1369
+ `SELECT e.* FROM events e
1370
+ INNER JOIN memory_levels ml ON e.id = ml.event_id
1371
+ WHERE ml.level = ?
1372
+ ORDER BY e.timestamp DESC
1373
+ LIMIT ? OFFSET ?`,
1374
+ [level, limit, offset]
1375
+ );
1376
+ return rows.map((row) => this.rowToEvent(row));
1377
+ }
1378
+ /**
1379
+ * Get memory level for a specific event
1380
+ */
1381
+ async getEventLevel(eventId) {
1382
+ await this.initialize();
1383
+ const row = sqliteGet(
1384
+ this.db,
1385
+ `SELECT level FROM memory_levels WHERE event_id = ?`,
1386
+ [eventId]
1387
+ );
1388
+ return row ? row.level : null;
1389
+ }
1390
+ /**
1391
+ * Get sync position for a target
1392
+ */
1393
+ async getSyncPosition(targetName) {
1394
+ await this.initialize();
1395
+ const row = sqliteGet(
1396
+ this.db,
1397
+ `SELECT last_event_id, last_timestamp FROM sync_positions WHERE target_name = ?`,
1398
+ [targetName]
1399
+ );
1400
+ return {
1401
+ lastEventId: row?.last_event_id ?? null,
1402
+ lastTimestamp: row?.last_timestamp ?? null
1403
+ };
1404
+ }
1405
+ /**
1406
+ * Update sync position for a target
1407
+ */
1408
+ async updateSyncPosition(targetName, lastEventId, lastTimestamp) {
1409
+ await this.initialize();
1410
+ sqliteRun(
1411
+ this.db,
1412
+ `INSERT OR REPLACE INTO sync_positions (target_name, last_event_id, last_timestamp, updated_at)
1413
+ VALUES (?, ?, ?, datetime('now'))`,
1414
+ [targetName, lastEventId, lastTimestamp]
1415
+ );
1416
+ }
1417
+ /**
1418
+ * Get config value for endless mode
1419
+ */
1420
+ async getEndlessConfig(key) {
1421
+ await this.initialize();
1422
+ const row = sqliteGet(
1423
+ this.db,
1424
+ `SELECT value FROM endless_config WHERE key = ?`,
1425
+ [key]
1426
+ );
1427
+ if (!row)
1428
+ return null;
1429
+ return JSON.parse(row.value);
1430
+ }
1431
+ /**
1432
+ * Set config value for endless mode
1433
+ */
1434
+ async setEndlessConfig(key, value) {
1435
+ await this.initialize();
1436
+ sqliteRun(
1437
+ this.db,
1438
+ `INSERT OR REPLACE INTO endless_config (key, value, updated_at)
1439
+ VALUES (?, ?, datetime('now'))`,
1440
+ [key, JSON.stringify(value)]
1441
+ );
1442
+ }
1443
+ /**
1444
+ * Increment access count for events
1445
+ */
1446
+ async incrementAccessCount(eventIds) {
1447
+ if (eventIds.length === 0 || this.readOnly)
1448
+ return;
1449
+ await this.initialize();
1450
+ const placeholders = eventIds.map(() => "?").join(",");
1451
+ const currentTime = toSQLiteTimestamp(/* @__PURE__ */ new Date());
1452
+ sqliteRun(
1453
+ this.db,
1454
+ `UPDATE events
1455
+ SET access_count = access_count + 1,
1456
+ last_accessed_at = ?
1457
+ WHERE id IN (${placeholders})`,
1458
+ [currentTime, ...eventIds]
644
1459
  );
645
1460
  }
646
1461
  /**
647
- * Get all sessions
1462
+ * Get most accessed memories
648
1463
  */
649
- async getAllSessions() {
1464
+ async getMostAccessed(limit = 10) {
650
1465
  await this.initialize();
651
- const rows = await dbAll(
1466
+ const rows = sqliteAll(
652
1467
  this.db,
653
- `SELECT * FROM sessions ORDER BY started_at DESC`
1468
+ `SELECT * FROM events
1469
+ WHERE access_count > 0
1470
+ ORDER BY access_count DESC, last_accessed_at DESC
1471
+ LIMIT ?`,
1472
+ [limit]
654
1473
  );
655
- return rows.map((row) => ({
656
- id: row.id,
657
- startedAt: toDate(row.started_at),
658
- endedAt: row.ended_at ? toDate(row.ended_at) : void 0,
659
- projectPath: row.project_path,
660
- summary: row.summary,
661
- tags: row.tags ? JSON.parse(row.tags) : void 0
662
- }));
1474
+ return rows.map((row) => this.rowToEvent(row));
1475
+ }
1476
+ /**
1477
+ * Get database instance for direct access
1478
+ */
1479
+ getDatabase() {
1480
+ return this.db;
663
1481
  }
664
1482
  /**
665
1483
  * Close database connection
666
1484
  */
667
1485
  async close() {
668
- await dbClose(this.db);
1486
+ sqliteClose(this.db);
669
1487
  }
670
1488
  /**
671
1489
  * Convert database row to MemoryEvent
672
1490
  */
673
1491
  rowToEvent(row) {
674
- return {
1492
+ const event = {
675
1493
  id: row.id,
676
1494
  eventType: row.event_type,
677
1495
  sessionId: row.session_id,
678
- timestamp: toDate(row.timestamp),
1496
+ timestamp: toDateFromSQLite(row.timestamp),
679
1497
  content: row.content,
680
1498
  canonicalKey: row.canonical_key,
681
1499
  dedupeKey: row.dedupe_key,
682
1500
  metadata: row.metadata ? JSON.parse(row.metadata) : void 0
683
1501
  };
1502
+ if (row.access_count !== void 0) {
1503
+ event.access_count = row.access_count;
1504
+ }
1505
+ if (row.last_accessed_at !== void 0) {
1506
+ event.last_accessed_at = row.last_accessed_at;
1507
+ }
1508
+ return event;
1509
+ }
1510
+ };
1511
+
1512
+ // src/core/sync-worker.ts
1513
+ var DEFAULT_CONFIG = {
1514
+ intervalMs: 3e4,
1515
+ batchSize: 500,
1516
+ maxRetries: 3,
1517
+ retryDelayMs: 5e3
1518
+ };
1519
+ var SyncWorker = class {
1520
+ constructor(sqliteStore, duckdbStore, config) {
1521
+ this.sqliteStore = sqliteStore;
1522
+ this.duckdbStore = duckdbStore;
1523
+ this.config = { ...DEFAULT_CONFIG, ...config };
1524
+ }
1525
+ config;
1526
+ intervalHandle = null;
1527
+ running = false;
1528
+ stats = {
1529
+ lastSyncAt: null,
1530
+ eventsSynced: 0,
1531
+ sessionsSynced: 0,
1532
+ errors: 0,
1533
+ status: "idle"
1534
+ };
1535
+ /**
1536
+ * Start the sync worker
1537
+ */
1538
+ start() {
1539
+ if (this.running)
1540
+ return;
1541
+ this.running = true;
1542
+ this.stats.status = "idle";
1543
+ this.syncNow().catch((err) => {
1544
+ console.error("[SyncWorker] Initial sync failed:", err);
1545
+ });
1546
+ this.intervalHandle = setInterval(() => {
1547
+ this.syncNow().catch((err) => {
1548
+ console.error("[SyncWorker] Periodic sync failed:", err);
1549
+ });
1550
+ }, this.config.intervalMs);
1551
+ }
1552
+ /**
1553
+ * Stop the sync worker
1554
+ */
1555
+ stop() {
1556
+ this.running = false;
1557
+ this.stats.status = "stopped";
1558
+ if (this.intervalHandle) {
1559
+ clearInterval(this.intervalHandle);
1560
+ this.intervalHandle = null;
1561
+ }
1562
+ }
1563
+ /**
1564
+ * Trigger immediate sync
1565
+ */
1566
+ async syncNow() {
1567
+ if (this.stats.status === "syncing") {
1568
+ return;
1569
+ }
1570
+ this.stats.status = "syncing";
1571
+ try {
1572
+ await this.syncEvents();
1573
+ await this.syncSessions();
1574
+ this.stats.lastSyncAt = /* @__PURE__ */ new Date();
1575
+ this.stats.status = "idle";
1576
+ } catch (error) {
1577
+ this.stats.errors++;
1578
+ this.stats.status = "error";
1579
+ throw error;
1580
+ }
1581
+ }
1582
+ /**
1583
+ * Sync events from SQLite to DuckDB
1584
+ */
1585
+ async syncEvents() {
1586
+ const targetName = "duckdb_analytics";
1587
+ const position = await this.sqliteStore.getSyncPosition(targetName);
1588
+ const lastTimestamp = position.lastTimestamp || "1970-01-01T00:00:00.000Z";
1589
+ let hasMore = true;
1590
+ let totalSynced = 0;
1591
+ while (hasMore) {
1592
+ const events = await this.sqliteStore.getEventsSince(lastTimestamp, this.config.batchSize);
1593
+ if (events.length === 0) {
1594
+ hasMore = false;
1595
+ break;
1596
+ }
1597
+ await this.retryWithBackoff(async () => {
1598
+ for (const event of events) {
1599
+ await this.insertEventToDuckDB(event);
1600
+ }
1601
+ });
1602
+ totalSynced += events.length;
1603
+ const lastEvent = events[events.length - 1];
1604
+ await this.sqliteStore.updateSyncPosition(
1605
+ targetName,
1606
+ lastEvent.id,
1607
+ lastEvent.timestamp.toISOString()
1608
+ );
1609
+ hasMore = events.length === this.config.batchSize;
1610
+ }
1611
+ this.stats.eventsSynced += totalSynced;
1612
+ }
1613
+ /**
1614
+ * Sync sessions from SQLite to DuckDB
1615
+ */
1616
+ async syncSessions() {
1617
+ const sessions = await this.sqliteStore.getAllSessions();
1618
+ for (const session of sessions) {
1619
+ await this.retryWithBackoff(async () => {
1620
+ await this.duckdbStore.upsertSession(session);
1621
+ });
1622
+ }
1623
+ this.stats.sessionsSynced = sessions.length;
1624
+ }
1625
+ /**
1626
+ * Insert a single event into DuckDB
1627
+ */
1628
+ async insertEventToDuckDB(event) {
1629
+ await this.duckdbStore.append({
1630
+ eventType: event.eventType,
1631
+ sessionId: event.sessionId,
1632
+ timestamp: event.timestamp,
1633
+ content: event.content,
1634
+ metadata: event.metadata
1635
+ });
1636
+ }
1637
+ /**
1638
+ * Retry operation with exponential backoff
1639
+ */
1640
+ async retryWithBackoff(fn) {
1641
+ let lastError = null;
1642
+ for (let attempt = 0; attempt < this.config.maxRetries; attempt++) {
1643
+ try {
1644
+ return await fn();
1645
+ } catch (error) {
1646
+ lastError = error instanceof Error ? error : new Error(String(error));
1647
+ if (attempt < this.config.maxRetries - 1) {
1648
+ const delay = this.config.retryDelayMs * Math.pow(2, attempt);
1649
+ await this.sleep(delay);
1650
+ }
1651
+ }
1652
+ }
1653
+ throw lastError;
1654
+ }
1655
+ /**
1656
+ * Sleep utility
1657
+ */
1658
+ sleep(ms) {
1659
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
1660
+ }
1661
+ /**
1662
+ * Get sync statistics
1663
+ */
1664
+ getStats() {
1665
+ return { ...this.stats };
1666
+ }
1667
+ /**
1668
+ * Check if worker is running
1669
+ */
1670
+ isRunning() {
1671
+ return this.running;
684
1672
  }
685
1673
  };
686
1674
 
@@ -912,7 +1900,7 @@ function getDefaultEmbedder() {
912
1900
  }
913
1901
 
914
1902
  // src/core/vector-outbox.ts
915
- var DEFAULT_CONFIG = {
1903
+ var DEFAULT_CONFIG2 = {
916
1904
  embeddingVersion: "v1",
917
1905
  maxRetries: 3,
918
1906
  stuckThresholdMs: 5 * 60 * 1e3,
@@ -921,7 +1909,7 @@ var DEFAULT_CONFIG = {
921
1909
  };
922
1910
 
923
1911
  // src/core/vector-worker.ts
924
- var DEFAULT_CONFIG2 = {
1912
+ var DEFAULT_CONFIG3 = {
925
1913
  batchSize: 32,
926
1914
  pollIntervalMs: 1e3,
927
1915
  maxRetries: 3
@@ -932,12 +1920,13 @@ var VectorWorker = class {
932
1920
  embedder;
933
1921
  config;
934
1922
  running = false;
1923
+ stopping = false;
935
1924
  pollTimeout = null;
936
1925
  constructor(eventStore, vectorStore, embedder, config = {}) {
937
1926
  this.eventStore = eventStore;
938
1927
  this.vectorStore = vectorStore;
939
1928
  this.embedder = embedder;
940
- this.config = { ...DEFAULT_CONFIG2, ...config };
1929
+ this.config = { ...DEFAULT_CONFIG3, ...config };
941
1930
  }
942
1931
  /**
943
1932
  * Start the worker polling loop
@@ -946,6 +1935,7 @@ var VectorWorker = class {
946
1935
  if (this.running)
947
1936
  return;
948
1937
  this.running = true;
1938
+ this.stopping = false;
949
1939
  this.poll();
950
1940
  }
951
1941
  /**
@@ -953,6 +1943,7 @@ var VectorWorker = class {
953
1943
  */
954
1944
  stop() {
955
1945
  this.running = false;
1946
+ this.stopping = true;
956
1947
  if (this.pollTimeout) {
957
1948
  clearTimeout(this.pollTimeout);
958
1949
  this.pollTimeout = null;
@@ -1002,9 +1993,15 @@ var VectorWorker = class {
1002
1993
  }
1003
1994
  return successful.length;
1004
1995
  } catch (error) {
1005
- const allIds = items.map((i) => i.id);
1006
- const errorMessage = error instanceof Error ? error.message : String(error);
1007
- await this.eventStore.failOutboxItems(allIds, errorMessage);
1996
+ if (!this.stopping) {
1997
+ try {
1998
+ const allIds = items.map((i) => i.id);
1999
+ const errorMessage = error instanceof Error ? error.message : String(error);
2000
+ await this.eventStore.failOutboxItems(allIds, errorMessage);
2001
+ } catch (failError) {
2002
+ console.warn("Could not mark outbox items as failed (database may be closed)");
2003
+ }
2004
+ }
1008
2005
  throw error;
1009
2006
  }
1010
2007
  }
@@ -1012,14 +2009,18 @@ var VectorWorker = class {
1012
2009
  * Poll for new items
1013
2010
  */
1014
2011
  async poll() {
1015
- if (!this.running)
2012
+ if (!this.running || this.stopping)
1016
2013
  return;
1017
2014
  try {
1018
2015
  await this.processBatch();
1019
2016
  } catch (error) {
1020
- console.error("Vector worker error:", error);
2017
+ if (!this.stopping) {
2018
+ console.error("Vector worker error:", error);
2019
+ }
2020
+ }
2021
+ if (this.running && !this.stopping) {
2022
+ this.pollTimeout = setTimeout(() => this.poll(), this.config.pollIntervalMs);
1021
2023
  }
1022
- this.pollTimeout = setTimeout(() => this.poll(), this.config.pollIntervalMs);
1023
2024
  }
1024
2025
  /**
1025
2026
  * Process all pending items (blocking)
@@ -1046,7 +2047,7 @@ function createVectorWorker(eventStore, vectorStore, embedder, config) {
1046
2047
  }
1047
2048
 
1048
2049
  // src/core/matcher.ts
1049
- var DEFAULT_CONFIG3 = {
2050
+ var DEFAULT_CONFIG4 = {
1050
2051
  weights: {
1051
2052
  semanticSimilarity: 0.4,
1052
2053
  ftsScore: 0.25,
@@ -1061,9 +2062,9 @@ var Matcher = class {
1061
2062
  config;
1062
2063
  constructor(config = {}) {
1063
2064
  this.config = {
1064
- ...DEFAULT_CONFIG3,
2065
+ ...DEFAULT_CONFIG4,
1065
2066
  ...config,
1066
- weights: { ...DEFAULT_CONFIG3.weights, ...config.weights }
2067
+ weights: { ...DEFAULT_CONFIG4.weights, ...config.weights }
1067
2068
  };
1068
2069
  }
1069
2070
  /**
@@ -1190,6 +2191,7 @@ var Retriever = class {
1190
2191
  matcher;
1191
2192
  sharedStore;
1192
2193
  sharedVectorStore;
2194
+ graduation;
1193
2195
  constructor(eventStore, vectorStore, embedder, matcher, sharedOptions) {
1194
2196
  this.eventStore = eventStore;
1195
2197
  this.vectorStore = vectorStore;
@@ -1198,6 +2200,12 @@ var Retriever = class {
1198
2200
  this.sharedStore = sharedOptions?.sharedStore;
1199
2201
  this.sharedVectorStore = sharedOptions?.sharedVectorStore;
1200
2202
  }
2203
+ /**
2204
+ * Set graduation pipeline for access tracking
2205
+ */
2206
+ setGraduationPipeline(graduation) {
2207
+ this.graduation = graduation;
2208
+ }
1201
2209
  /**
1202
2210
  * Set shared stores after construction
1203
2211
  */
@@ -1320,6 +2328,13 @@ var Retriever = class {
1320
2328
  const event = await this.eventStore.getEvent(result.eventId);
1321
2329
  if (!event)
1322
2330
  continue;
2331
+ if (this.graduation) {
2332
+ this.graduation.recordAccess(
2333
+ event.id,
2334
+ options.sessionId || "unknown",
2335
+ result.score
2336
+ );
2337
+ }
1323
2338
  let sessionContext;
1324
2339
  if (options.includeSessionContext) {
1325
2340
  sessionContext = await this.getSessionContext(event.sessionId, event.id);
@@ -1441,15 +2456,26 @@ var GraduationPipeline = class {
1441
2456
  L3toL4: { ...DEFAULT_CRITERIA.L3toL4, ...criteria.L3toL4 }
1442
2457
  };
1443
2458
  }
2459
+ // Track which sessions have accessed each event
2460
+ sessionAccesses = /* @__PURE__ */ new Map();
1444
2461
  /**
1445
2462
  * Record an access to an event (used for graduation scoring)
1446
2463
  */
1447
2464
  recordAccess(eventId, fromSessionId, confidence = 1) {
1448
2465
  const existing = this.metrics.get(eventId);
2466
+ if (!this.sessionAccesses.has(eventId)) {
2467
+ this.sessionAccesses.set(eventId, /* @__PURE__ */ new Set());
2468
+ }
2469
+ const sessions = this.sessionAccesses.get(eventId);
2470
+ const isNewSession = !sessions.has(fromSessionId);
2471
+ sessions.add(fromSessionId);
1449
2472
  if (existing) {
1450
2473
  existing.accessCount++;
1451
2474
  existing.lastAccessed = /* @__PURE__ */ new Date();
1452
2475
  existing.confidence = Math.max(existing.confidence, confidence);
2476
+ if (isNewSession && sessions.size > 1) {
2477
+ existing.crossSessionRefs = sessions.size - 1;
2478
+ }
1453
2479
  } else {
1454
2480
  this.metrics.set(eventId, {
1455
2481
  eventId,
@@ -1746,7 +2772,7 @@ function createSharedEventStore(dbPath) {
1746
2772
  }
1747
2773
 
1748
2774
  // src/core/shared-store.ts
1749
- import { randomUUID as randomUUID2 } from "crypto";
2775
+ import { randomUUID as randomUUID3 } from "crypto";
1750
2776
  var SharedStore = class {
1751
2777
  constructor(sharedEventStore) {
1752
2778
  this.sharedEventStore = sharedEventStore;
@@ -1758,7 +2784,7 @@ var SharedStore = class {
1758
2784
  * Promote a verified troubleshooting entry to shared storage
1759
2785
  */
1760
2786
  async promoteEntry(input) {
1761
- const entryId = randomUUID2();
2787
+ const entryId = randomUUID3();
1762
2788
  await dbRun(
1763
2789
  this.db,
1764
2790
  `INSERT INTO shared_troubleshooting (
@@ -2123,7 +3149,7 @@ function createSharedVectorStore(dbPath) {
2123
3149
  }
2124
3150
 
2125
3151
  // src/core/shared-promoter.ts
2126
- import { randomUUID as randomUUID3 } from "crypto";
3152
+ import { randomUUID as randomUUID4 } from "crypto";
2127
3153
  var SharedPromoter = class {
2128
3154
  constructor(sharedStore, sharedVectorStore, embedder, config) {
2129
3155
  this.sharedStore = sharedStore;
@@ -2191,7 +3217,7 @@ var SharedPromoter = class {
2191
3217
  const embeddingContent = this.createEmbeddingContent(input);
2192
3218
  const embedding = await this.embedder.embed(embeddingContent);
2193
3219
  await this.sharedVectorStore.upsert({
2194
- id: randomUUID3(),
3220
+ id: randomUUID4(),
2195
3221
  entryId,
2196
3222
  entryType: "troubleshooting",
2197
3223
  content: embeddingContent,
@@ -2331,7 +3357,7 @@ function createToolObservationEmbedding(toolName, metadata, success) {
2331
3357
  }
2332
3358
 
2333
3359
  // src/core/working-set-store.ts
2334
- import { randomUUID as randomUUID4 } from "crypto";
3360
+ import { randomUUID as randomUUID5 } from "crypto";
2335
3361
  var WorkingSetStore = class {
2336
3362
  constructor(eventStore, config) {
2337
3363
  this.eventStore = eventStore;
@@ -2352,7 +3378,7 @@ var WorkingSetStore = class {
2352
3378
  `INSERT OR REPLACE INTO working_set (id, event_id, added_at, relevance_score, topics, expires_at)
2353
3379
  VALUES (?, ?, CURRENT_TIMESTAMP, ?, ?, ?)`,
2354
3380
  [
2355
- randomUUID4(),
3381
+ randomUUID5(),
2356
3382
  eventId,
2357
3383
  relevanceScore,
2358
3384
  JSON.stringify(topics || []),
@@ -2536,7 +3562,7 @@ function createWorkingSetStore(eventStore, config) {
2536
3562
  }
2537
3563
 
2538
3564
  // src/core/consolidated-store.ts
2539
- import { randomUUID as randomUUID5 } from "crypto";
3565
+ import { randomUUID as randomUUID6 } from "crypto";
2540
3566
  var ConsolidatedStore = class {
2541
3567
  constructor(eventStore) {
2542
3568
  this.eventStore = eventStore;
@@ -2548,7 +3574,7 @@ var ConsolidatedStore = class {
2548
3574
  * Create a new consolidated memory
2549
3575
  */
2550
3576
  async create(input) {
2551
- const memoryId = randomUUID5();
3577
+ const memoryId = randomUUID6();
2552
3578
  await dbRun(
2553
3579
  this.db,
2554
3580
  `INSERT INTO consolidated_memories
@@ -3075,7 +4101,7 @@ function createConsolidationWorker(workingSetStore, consolidatedStore, config) {
3075
4101
  }
3076
4102
 
3077
4103
  // src/core/continuity-manager.ts
3078
- import { randomUUID as randomUUID6 } from "crypto";
4104
+ import { randomUUID as randomUUID7 } from "crypto";
3079
4105
  var ContinuityManager = class {
3080
4106
  constructor(eventStore, config) {
3081
4107
  this.eventStore = eventStore;
@@ -3229,7 +4255,7 @@ var ContinuityManager = class {
3229
4255
  `INSERT INTO continuity_log
3230
4256
  (log_id, from_context_id, to_context_id, continuity_score, transition_type, created_at)
3231
4257
  VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`,
3232
- [randomUUID6(), previous.id, current.id, score, type]
4258
+ [randomUUID7(), previous.id, current.id, score, type]
3233
4259
  );
3234
4260
  }
3235
4261
  /**
@@ -3337,6 +4363,127 @@ function createContinuityManager(eventStore, config) {
3337
4363
  return new ContinuityManager(eventStore, config);
3338
4364
  }
3339
4365
 
4366
+ // src/core/graduation-worker.ts
4367
+ var DEFAULT_CONFIG5 = {
4368
+ evaluationIntervalMs: 3e5,
4369
+ // 5 minutes
4370
+ batchSize: 50,
4371
+ cooldownMs: 36e5
4372
+ // 1 hour cooldown between evaluations
4373
+ };
4374
+ var GraduationWorker = class {
4375
+ constructor(eventStore, graduation, config = DEFAULT_CONFIG5) {
4376
+ this.eventStore = eventStore;
4377
+ this.graduation = graduation;
4378
+ this.config = config;
4379
+ }
4380
+ running = false;
4381
+ timeout = null;
4382
+ lastEvaluated = /* @__PURE__ */ new Map();
4383
+ /**
4384
+ * Start the graduation worker
4385
+ */
4386
+ start() {
4387
+ if (this.running)
4388
+ return;
4389
+ this.running = true;
4390
+ this.scheduleNext();
4391
+ }
4392
+ /**
4393
+ * Stop the graduation worker
4394
+ */
4395
+ stop() {
4396
+ this.running = false;
4397
+ if (this.timeout) {
4398
+ clearTimeout(this.timeout);
4399
+ this.timeout = null;
4400
+ }
4401
+ }
4402
+ /**
4403
+ * Check if currently running
4404
+ */
4405
+ isRunning() {
4406
+ return this.running;
4407
+ }
4408
+ /**
4409
+ * Force a graduation evaluation run
4410
+ */
4411
+ async forceRun() {
4412
+ return await this.runGraduation();
4413
+ }
4414
+ /**
4415
+ * Schedule the next graduation check
4416
+ */
4417
+ scheduleNext() {
4418
+ if (!this.running)
4419
+ return;
4420
+ this.timeout = setTimeout(
4421
+ () => this.run(),
4422
+ this.config.evaluationIntervalMs
4423
+ );
4424
+ }
4425
+ /**
4426
+ * Run graduation evaluation
4427
+ */
4428
+ async run() {
4429
+ if (!this.running)
4430
+ return;
4431
+ try {
4432
+ await this.runGraduation();
4433
+ } catch (error) {
4434
+ console.error("Graduation error:", error);
4435
+ }
4436
+ this.scheduleNext();
4437
+ }
4438
+ /**
4439
+ * Perform graduation evaluation across all levels
4440
+ */
4441
+ async runGraduation() {
4442
+ const result = {
4443
+ evaluated: 0,
4444
+ graduated: 0,
4445
+ byLevel: {}
4446
+ };
4447
+ const levels = ["L0", "L1", "L2", "L3"];
4448
+ const now = Date.now();
4449
+ for (const level of levels) {
4450
+ const events = await this.eventStore.getEventsByLevel(level, {
4451
+ limit: this.config.batchSize
4452
+ });
4453
+ let levelGraduated = 0;
4454
+ for (const event of events) {
4455
+ const lastEval = this.lastEvaluated.get(event.id);
4456
+ if (lastEval && now - lastEval < this.config.cooldownMs) {
4457
+ continue;
4458
+ }
4459
+ result.evaluated++;
4460
+ this.lastEvaluated.set(event.id, now);
4461
+ const gradResult = await this.graduation.evaluateGraduation(event.id, level);
4462
+ if (gradResult.success) {
4463
+ result.graduated++;
4464
+ levelGraduated++;
4465
+ }
4466
+ }
4467
+ if (levelGraduated > 0) {
4468
+ result.byLevel[level] = levelGraduated;
4469
+ }
4470
+ }
4471
+ if (this.lastEvaluated.size > 1e3) {
4472
+ const entries = Array.from(this.lastEvaluated.entries());
4473
+ entries.sort((a, b) => b[1] - a[1]);
4474
+ this.lastEvaluated = new Map(entries.slice(0, 1e3));
4475
+ }
4476
+ return result;
4477
+ }
4478
+ };
4479
+ function createGraduationWorker(eventStore, graduation, config) {
4480
+ return new GraduationWorker(
4481
+ eventStore,
4482
+ graduation,
4483
+ { ...DEFAULT_CONFIG5, ...config }
4484
+ );
4485
+ }
4486
+
3340
4487
  // src/services/memory-service.ts
3341
4488
  function normalizePath(projectPath) {
3342
4489
  const expanded = projectPath.startsWith("~") ? path.join(os.homedir(), projectPath.slice(1)) : projectPath;
@@ -3397,13 +4544,18 @@ function getSessionProject(sessionId) {
3397
4544
  return registry.sessions[sessionId] || null;
3398
4545
  }
3399
4546
  var MemoryService = class {
3400
- eventStore;
4547
+ // Primary store: SQLite (WAL mode) - for hooks, always available
4548
+ sqliteStore;
4549
+ // Analytics store: DuckDB - for server reads (optional, synced from SQLite)
4550
+ analyticsStore;
4551
+ syncWorker = null;
3401
4552
  vectorStore;
3402
4553
  embedder;
3403
4554
  matcher;
3404
4555
  retriever;
3405
4556
  graduation;
3406
4557
  vectorWorker = null;
4558
+ graduationWorker = null;
3407
4559
  initialized = false;
3408
4560
  // Endless Mode components
3409
4561
  workingSetStore = null;
@@ -3418,24 +4570,48 @@ var MemoryService = class {
3418
4570
  sharedPromoter = null;
3419
4571
  sharedStoreConfig = null;
3420
4572
  projectHash = null;
4573
+ readOnly;
3421
4574
  constructor(config) {
3422
4575
  const storagePath = this.expandPath(config.storagePath);
3423
- if (!fs.existsSync(storagePath)) {
4576
+ this.readOnly = config.readOnly ?? false;
4577
+ if (!this.readOnly && !fs.existsSync(storagePath)) {
3424
4578
  fs.mkdirSync(storagePath, { recursive: true });
3425
4579
  }
3426
4580
  this.projectHash = config.projectHash || null;
3427
4581
  this.sharedStoreConfig = config.sharedStoreConfig ?? { enabled: true };
3428
- this.eventStore = new EventStore(path.join(storagePath, "events.duckdb"));
4582
+ this.sqliteStore = new SQLiteEventStore(
4583
+ path.join(storagePath, "events.sqlite"),
4584
+ { readonly: this.readOnly }
4585
+ );
4586
+ const analyticsEnabled = config.analyticsEnabled ?? this.readOnly;
4587
+ if (!analyticsEnabled) {
4588
+ this.analyticsStore = null;
4589
+ } else if (this.readOnly) {
4590
+ try {
4591
+ this.analyticsStore = new EventStore(
4592
+ path.join(storagePath, "analytics.duckdb"),
4593
+ { readOnly: true }
4594
+ );
4595
+ } catch {
4596
+ this.analyticsStore = null;
4597
+ }
4598
+ } else {
4599
+ this.analyticsStore = new EventStore(
4600
+ path.join(storagePath, "analytics.duckdb"),
4601
+ { readOnly: false }
4602
+ );
4603
+ }
3429
4604
  this.vectorStore = new VectorStore(path.join(storagePath, "vectors"));
3430
4605
  this.embedder = config.embeddingModel ? new Embedder(config.embeddingModel) : getDefaultEmbedder();
3431
4606
  this.matcher = getDefaultMatcher();
3432
4607
  this.retriever = createRetriever(
3433
- this.eventStore,
4608
+ this.sqliteStore,
4609
+ // Interface compatible
3434
4610
  this.vectorStore,
3435
4611
  this.embedder,
3436
4612
  this.matcher
3437
4613
  );
3438
- this.graduation = createGraduationPipeline(this.eventStore);
4614
+ this.graduation = createGraduationPipeline(this.sqliteStore);
3439
4615
  }
3440
4616
  /**
3441
4617
  * Initialize all components
@@ -3443,22 +4619,45 @@ var MemoryService = class {
3443
4619
  async initialize() {
3444
4620
  if (this.initialized)
3445
4621
  return;
3446
- await this.eventStore.initialize();
4622
+ await this.sqliteStore.initialize();
4623
+ if (this.analyticsStore) {
4624
+ try {
4625
+ await this.analyticsStore.initialize();
4626
+ } catch (error) {
4627
+ console.warn("[MemoryService] Analytics store (DuckDB) initialization failed, using SQLite for reads:", error);
4628
+ }
4629
+ }
3447
4630
  await this.vectorStore.initialize();
3448
4631
  await this.embedder.initialize();
3449
- this.vectorWorker = createVectorWorker(
3450
- this.eventStore,
3451
- this.vectorStore,
3452
- this.embedder
3453
- );
3454
- this.vectorWorker.start();
3455
- const savedMode = await this.eventStore.getEndlessConfig("mode");
3456
- if (savedMode === "endless") {
3457
- this.endlessMode = "endless";
3458
- await this.initializeEndlessMode();
3459
- }
3460
- if (this.sharedStoreConfig?.enabled !== false) {
3461
- await this.initializeSharedStore();
4632
+ if (!this.readOnly) {
4633
+ this.vectorWorker = createVectorWorker(
4634
+ this.sqliteStore,
4635
+ this.vectorStore,
4636
+ this.embedder
4637
+ );
4638
+ this.vectorWorker.start();
4639
+ this.retriever.setGraduationPipeline(this.graduation);
4640
+ this.graduationWorker = createGraduationWorker(
4641
+ this.sqliteStore,
4642
+ this.graduation
4643
+ );
4644
+ this.graduationWorker.start();
4645
+ if (this.analyticsStore) {
4646
+ this.syncWorker = new SyncWorker(
4647
+ this.sqliteStore,
4648
+ this.analyticsStore,
4649
+ { intervalMs: 3e4, batchSize: 500 }
4650
+ );
4651
+ this.syncWorker.start();
4652
+ }
4653
+ const savedMode = await this.sqliteStore.getEndlessConfig("mode");
4654
+ if (savedMode === "endless") {
4655
+ this.endlessMode = "endless";
4656
+ await this.initializeEndlessMode();
4657
+ }
4658
+ if (this.sharedStoreConfig?.enabled !== false) {
4659
+ await this.initializeSharedStore();
4660
+ }
3462
4661
  }
3463
4662
  this.initialized = true;
3464
4663
  }
@@ -3492,7 +4691,7 @@ var MemoryService = class {
3492
4691
  */
3493
4692
  async startSession(sessionId, projectPath) {
3494
4693
  await this.initialize();
3495
- await this.eventStore.upsertSession({
4694
+ await this.sqliteStore.upsertSession({
3496
4695
  id: sessionId,
3497
4696
  startedAt: /* @__PURE__ */ new Date(),
3498
4697
  projectPath
@@ -3503,7 +4702,7 @@ var MemoryService = class {
3503
4702
  */
3504
4703
  async endSession(sessionId, summary) {
3505
4704
  await this.initialize();
3506
- await this.eventStore.upsertSession({
4705
+ await this.sqliteStore.upsertSession({
3507
4706
  id: sessionId,
3508
4707
  endedAt: /* @__PURE__ */ new Date(),
3509
4708
  summary
@@ -3514,7 +4713,7 @@ var MemoryService = class {
3514
4713
  */
3515
4714
  async storeUserPrompt(sessionId, content, metadata) {
3516
4715
  await this.initialize();
3517
- const result = await this.eventStore.append({
4716
+ const result = await this.sqliteStore.append({
3518
4717
  eventType: "user_prompt",
3519
4718
  sessionId,
3520
4719
  timestamp: /* @__PURE__ */ new Date(),
@@ -3522,7 +4721,7 @@ var MemoryService = class {
3522
4721
  metadata
3523
4722
  });
3524
4723
  if (result.success && !result.isDuplicate) {
3525
- await this.eventStore.enqueueForEmbedding(result.eventId, content);
4724
+ await this.sqliteStore.enqueueForEmbedding(result.eventId, content);
3526
4725
  }
3527
4726
  return result;
3528
4727
  }
@@ -3531,7 +4730,7 @@ var MemoryService = class {
3531
4730
  */
3532
4731
  async storeAgentResponse(sessionId, content, metadata) {
3533
4732
  await this.initialize();
3534
- const result = await this.eventStore.append({
4733
+ const result = await this.sqliteStore.append({
3535
4734
  eventType: "agent_response",
3536
4735
  sessionId,
3537
4736
  timestamp: /* @__PURE__ */ new Date(),
@@ -3539,7 +4738,7 @@ var MemoryService = class {
3539
4738
  metadata
3540
4739
  });
3541
4740
  if (result.success && !result.isDuplicate) {
3542
- await this.eventStore.enqueueForEmbedding(result.eventId, content);
4741
+ await this.sqliteStore.enqueueForEmbedding(result.eventId, content);
3543
4742
  }
3544
4743
  return result;
3545
4744
  }
@@ -3548,14 +4747,14 @@ var MemoryService = class {
3548
4747
  */
3549
4748
  async storeSessionSummary(sessionId, summary) {
3550
4749
  await this.initialize();
3551
- const result = await this.eventStore.append({
4750
+ const result = await this.sqliteStore.append({
3552
4751
  eventType: "session_summary",
3553
4752
  sessionId,
3554
4753
  timestamp: /* @__PURE__ */ new Date(),
3555
4754
  content: summary
3556
4755
  });
3557
4756
  if (result.success && !result.isDuplicate) {
3558
- await this.eventStore.enqueueForEmbedding(result.eventId, summary);
4757
+ await this.sqliteStore.enqueueForEmbedding(result.eventId, summary);
3559
4758
  }
3560
4759
  return result;
3561
4760
  }
@@ -3565,7 +4764,7 @@ var MemoryService = class {
3565
4764
  async storeToolObservation(sessionId, payload) {
3566
4765
  await this.initialize();
3567
4766
  const content = JSON.stringify(payload);
3568
- const result = await this.eventStore.append({
4767
+ const result = await this.sqliteStore.append({
3569
4768
  eventType: "tool_observation",
3570
4769
  sessionId,
3571
4770
  timestamp: /* @__PURE__ */ new Date(),
@@ -3581,7 +4780,7 @@ var MemoryService = class {
3581
4780
  payload.metadata || {},
3582
4781
  payload.success
3583
4782
  );
3584
- await this.eventStore.enqueueForEmbedding(result.eventId, embeddingContent);
4783
+ await this.sqliteStore.enqueueForEmbedding(result.eventId, embeddingContent);
3585
4784
  }
3586
4785
  return result;
3587
4786
  }
@@ -3607,21 +4806,21 @@ var MemoryService = class {
3607
4806
  */
3608
4807
  async getSessionHistory(sessionId) {
3609
4808
  await this.initialize();
3610
- return this.eventStore.getSessionEvents(sessionId);
4809
+ return this.sqliteStore.getSessionEvents(sessionId);
3611
4810
  }
3612
4811
  /**
3613
4812
  * Get recent events
3614
4813
  */
3615
4814
  async getRecentEvents(limit = 100) {
3616
4815
  await this.initialize();
3617
- return this.eventStore.getRecentEvents(limit);
4816
+ return this.sqliteStore.getRecentEvents(limit);
3618
4817
  }
3619
4818
  /**
3620
4819
  * Get memory statistics
3621
4820
  */
3622
4821
  async getStats() {
3623
4822
  await this.initialize();
3624
- const recentEvents = await this.eventStore.getRecentEvents(1e4);
4823
+ const recentEvents = await this.sqliteStore.getRecentEvents(1e4);
3625
4824
  const vectorCount = await this.vectorStore.count();
3626
4825
  const levelStats = await this.graduation.getStats();
3627
4826
  return {
@@ -3639,6 +4838,20 @@ var MemoryService = class {
3639
4838
  }
3640
4839
  return 0;
3641
4840
  }
4841
+ /**
4842
+ * Get events by memory level
4843
+ */
4844
+ async getEventsByLevel(level, options) {
4845
+ await this.initialize();
4846
+ return this.sqliteStore.getEventsByLevel(level, options);
4847
+ }
4848
+ /**
4849
+ * Get memory level for a specific event
4850
+ */
4851
+ async getEventLevel(eventId) {
4852
+ await this.initialize();
4853
+ return this.sqliteStore.getEventLevel(eventId);
4854
+ }
3642
4855
  /**
3643
4856
  * Format retrieval results as context for Claude
3644
4857
  */
@@ -3731,21 +4944,21 @@ var MemoryService = class {
3731
4944
  */
3732
4945
  async initializeEndlessMode() {
3733
4946
  const config = await this.getEndlessConfig();
3734
- this.workingSetStore = createWorkingSetStore(this.eventStore, config);
3735
- this.consolidatedStore = createConsolidatedStore(this.eventStore);
4947
+ this.workingSetStore = createWorkingSetStore(this.sqliteStore, config);
4948
+ this.consolidatedStore = createConsolidatedStore(this.sqliteStore);
3736
4949
  this.consolidationWorker = createConsolidationWorker(
3737
4950
  this.workingSetStore,
3738
4951
  this.consolidatedStore,
3739
4952
  config
3740
4953
  );
3741
- this.continuityManager = createContinuityManager(this.eventStore, config);
4954
+ this.continuityManager = createContinuityManager(this.sqliteStore, config);
3742
4955
  this.consolidationWorker.start();
3743
4956
  }
3744
4957
  /**
3745
4958
  * Get Endless Mode configuration
3746
4959
  */
3747
4960
  async getEndlessConfig() {
3748
- const savedConfig = await this.eventStore.getEndlessConfig("config");
4961
+ const savedConfig = await this.sqliteStore.getEndlessConfig("config");
3749
4962
  return savedConfig || this.getDefaultEndlessConfig();
3750
4963
  }
3751
4964
  /**
@@ -3754,7 +4967,7 @@ var MemoryService = class {
3754
4967
  async setEndlessConfig(config) {
3755
4968
  const current = await this.getEndlessConfig();
3756
4969
  const merged = { ...current, ...config };
3757
- await this.eventStore.setEndlessConfig("config", merged);
4970
+ await this.sqliteStore.setEndlessConfig("config", merged);
3758
4971
  }
3759
4972
  /**
3760
4973
  * Set memory mode (session or endless)
@@ -3764,7 +4977,7 @@ var MemoryService = class {
3764
4977
  if (mode === this.endlessMode)
3765
4978
  return;
3766
4979
  this.endlessMode = mode;
3767
- await this.eventStore.setEndlessConfig("mode", mode);
4980
+ await this.sqliteStore.setEndlessConfig("mode", mode);
3768
4981
  if (mode === "endless") {
3769
4982
  await this.initializeEndlessMode();
3770
4983
  } else {
@@ -3821,6 +5034,59 @@ var MemoryService = class {
3821
5034
  return [];
3822
5035
  return this.consolidatedStore.getAll({ limit });
3823
5036
  }
5037
+ /**
5038
+ * Increment access count for memories that were used in prompts
5039
+ */
5040
+ async incrementMemoryAccess(eventIds) {
5041
+ if (eventIds.length === 0)
5042
+ return;
5043
+ if (this.sqliteStore) {
5044
+ await this.sqliteStore.incrementAccessCount(eventIds);
5045
+ } else if (this.eventStore) {
5046
+ await this.eventStore.incrementAccessCount(eventIds);
5047
+ }
5048
+ }
5049
+ /**
5050
+ * Get most accessed memories from events
5051
+ */
5052
+ async getMostAccessedMemories(limit = 10) {
5053
+ console.log("[getMostAccessedMemories] sqliteStore available:", !!this.sqliteStore);
5054
+ if (this.sqliteStore) {
5055
+ const events = await this.sqliteStore.getMostAccessed(limit);
5056
+ console.log("[getMostAccessedMemories] Got events from SQLite:", events.length);
5057
+ return events.map((event) => ({
5058
+ memoryId: event.id,
5059
+ summary: event.content.substring(0, 200) + (event.content.length > 200 ? "..." : ""),
5060
+ topics: [],
5061
+ // Could extract topics from content if needed
5062
+ accessCount: event.access_count || 0,
5063
+ lastAccessed: event.last_accessed_at || null,
5064
+ confidence: 1,
5065
+ createdAt: event.timestamp
5066
+ }));
5067
+ }
5068
+ if (this.consolidatedStore) {
5069
+ const consolidated = await this.consolidatedStore.getMostAccessed(limit);
5070
+ return consolidated.map((m) => ({
5071
+ memoryId: m.memoryId,
5072
+ summary: m.summary,
5073
+ topics: m.topics,
5074
+ accessCount: m.accessCount,
5075
+ lastAccessed: m.accessedAt,
5076
+ confidence: m.confidence,
5077
+ createdAt: m.createdAt
5078
+ }));
5079
+ }
5080
+ return [];
5081
+ }
5082
+ /**
5083
+ * Mark a consolidated memory as accessed
5084
+ */
5085
+ async markMemoryAccessed(memoryId) {
5086
+ if (!this.consolidatedStore)
5087
+ return;
5088
+ await this.consolidatedStore.markAccessed(memoryId);
5089
+ }
3824
5090
  /**
3825
5091
  * Calculate continuity score for current context
3826
5092
  */
@@ -3908,20 +5174,44 @@ var MemoryService = class {
3908
5174
  }
3909
5175
  return parts.join("\n");
3910
5176
  }
5177
+ /**
5178
+ * Force a graduation evaluation run
5179
+ */
5180
+ async forceGraduation() {
5181
+ if (!this.graduationWorker) {
5182
+ return { evaluated: 0, graduated: 0, byLevel: {} };
5183
+ }
5184
+ return this.graduationWorker.forceRun();
5185
+ }
5186
+ /**
5187
+ * Record access to a memory event (for graduation scoring)
5188
+ */
5189
+ recordMemoryAccess(eventId, sessionId, confidence = 1) {
5190
+ this.graduation.recordAccess(eventId, sessionId, confidence);
5191
+ }
3911
5192
  /**
3912
5193
  * Shutdown service
3913
5194
  */
3914
5195
  async shutdown() {
5196
+ if (this.graduationWorker) {
5197
+ this.graduationWorker.stop();
5198
+ }
3915
5199
  if (this.consolidationWorker) {
3916
5200
  this.consolidationWorker.stop();
3917
5201
  }
3918
5202
  if (this.vectorWorker) {
3919
5203
  this.vectorWorker.stop();
3920
5204
  }
5205
+ if (this.syncWorker) {
5206
+ this.syncWorker.stop();
5207
+ }
3921
5208
  if (this.sharedEventStore) {
3922
5209
  await this.sharedEventStore.close();
3923
5210
  }
3924
- await this.eventStore.close();
5211
+ await this.sqliteStore.close();
5212
+ if (this.analyticsStore) {
5213
+ await this.analyticsStore.close();
5214
+ }
3925
5215
  }
3926
5216
  /**
3927
5217
  * Expand ~ to home directory
@@ -3938,11 +5228,25 @@ var GLOBAL_KEY = "__global__";
3938
5228
  function getDefaultMemoryService() {
3939
5229
  if (!serviceCache.has(GLOBAL_KEY)) {
3940
5230
  serviceCache.set(GLOBAL_KEY, new MemoryService({
3941
- storagePath: "~/.claude-code/memory"
5231
+ storagePath: "~/.claude-code/memory",
5232
+ analyticsEnabled: false,
5233
+ // Hooks don't need DuckDB
5234
+ sharedStoreConfig: { enabled: false }
5235
+ // Shared store uses DuckDB too
3942
5236
  }));
3943
5237
  }
3944
5238
  return serviceCache.get(GLOBAL_KEY);
3945
5239
  }
5240
+ function getReadOnlyMemoryService() {
5241
+ return new MemoryService({
5242
+ storagePath: "~/.claude-code/memory",
5243
+ readOnly: true,
5244
+ analyticsEnabled: false,
5245
+ // Use SQLite for reads (WAL supports concurrent readers)
5246
+ sharedStoreConfig: { enabled: false }
5247
+ // Skip shared store for now
5248
+ });
5249
+ }
3946
5250
  function getMemoryServiceForProject(projectPath, sharedStoreConfig) {
3947
5251
  const hash = hashProjectPath(projectPath);
3948
5252
  if (!serviceCache.has(hash)) {
@@ -3950,7 +5254,10 @@ function getMemoryServiceForProject(projectPath, sharedStoreConfig) {
3950
5254
  serviceCache.set(hash, new MemoryService({
3951
5255
  storagePath,
3952
5256
  projectHash: hash,
3953
- sharedStoreConfig
5257
+ // Override shared store config - hooks don't need DuckDB
5258
+ sharedStoreConfig: sharedStoreConfig ?? { enabled: false },
5259
+ analyticsEnabled: false
5260
+ // Hooks don't need DuckDB
3954
5261
  }));
3955
5262
  }
3956
5263
  return serviceCache.get(hash);
@@ -3972,6 +5279,7 @@ export {
3972
5279
  getMemoryServiceForProject,
3973
5280
  getMemoryServiceForSession,
3974
5281
  getProjectStoragePath,
5282
+ getReadOnlyMemoryService,
3975
5283
  getSessionProject,
3976
5284
  hashProjectPath,
3977
5285
  registerSession