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
@@ -67,7 +67,10 @@ function toDate(value) {
67
67
  return new Date(value);
68
68
  return new Date(String(value));
69
69
  }
70
- function createDatabase(path2) {
70
+ function createDatabase(path2, options) {
71
+ if (options?.readOnly) {
72
+ return new duckdb.Database(path2, { access_mode: "READ_ONLY" });
73
+ }
71
74
  return new duckdb.Database(path2);
72
75
  }
73
76
  function dbRun(db, sql, params = []) {
@@ -121,18 +124,24 @@ function dbClose(db) {
121
124
 
122
125
  // src/core/event-store.ts
123
126
  var EventStore = class {
124
- constructor(dbPath) {
127
+ constructor(dbPath, options) {
125
128
  this.dbPath = dbPath;
126
- this.db = createDatabase(dbPath);
129
+ this.readOnly = options?.readOnly ?? false;
130
+ this.db = createDatabase(dbPath, { readOnly: this.readOnly });
127
131
  }
128
132
  db;
129
133
  initialized = false;
134
+ readOnly;
130
135
  /**
131
136
  * Initialize database schema
132
137
  */
133
138
  async initialize() {
134
139
  if (this.initialized)
135
140
  return;
141
+ if (this.readOnly) {
142
+ this.initialized = true;
143
+ return;
144
+ }
136
145
  await dbRun(this.db, `
137
146
  CREATE TABLE IF NOT EXISTS events (
138
147
  id VARCHAR PRIMARY KEY,
@@ -609,6 +618,36 @@ var EventStore = class {
609
618
  );
610
619
  return rows;
611
620
  }
621
+ /**
622
+ * Get events by memory level
623
+ */
624
+ async getEventsByLevel(level, options) {
625
+ await this.initialize();
626
+ const limit = options?.limit || 50;
627
+ const offset = options?.offset || 0;
628
+ const rows = await dbAll(
629
+ this.db,
630
+ `SELECT e.* FROM events e
631
+ INNER JOIN memory_levels ml ON e.id = ml.event_id
632
+ WHERE ml.level = ?
633
+ ORDER BY e.timestamp DESC
634
+ LIMIT ? OFFSET ?`,
635
+ [level, limit, offset]
636
+ );
637
+ return rows.map((row) => this.rowToEvent(row));
638
+ }
639
+ /**
640
+ * Get memory level for a specific event
641
+ */
642
+ async getEventLevel(eventId) {
643
+ await this.initialize();
644
+ const rows = await dbAll(
645
+ this.db,
646
+ `SELECT level FROM memory_levels WHERE event_id = ?`,
647
+ [eventId]
648
+ );
649
+ return rows.length > 0 ? rows[0].level : null;
650
+ }
612
651
  // ============================================================
613
652
  // Endless Mode Helper Methods
614
653
  // ============================================================
@@ -637,51 +676,1000 @@ var EventStore = class {
637
676
  */
638
677
  async setEndlessConfig(key, value) {
639
678
  await this.initialize();
640
- await dbRun(
679
+ await dbRun(
680
+ this.db,
681
+ `INSERT OR REPLACE INTO endless_config (key, value, updated_at)
682
+ VALUES (?, ?, CURRENT_TIMESTAMP)`,
683
+ [key, JSON.stringify(value)]
684
+ );
685
+ }
686
+ /**
687
+ * Get all sessions
688
+ */
689
+ async getAllSessions() {
690
+ await this.initialize();
691
+ const rows = await dbAll(
692
+ this.db,
693
+ `SELECT * FROM sessions ORDER BY started_at DESC`
694
+ );
695
+ return rows.map((row) => ({
696
+ id: row.id,
697
+ startedAt: toDate(row.started_at),
698
+ endedAt: row.ended_at ? toDate(row.ended_at) : void 0,
699
+ projectPath: row.project_path,
700
+ summary: row.summary,
701
+ tags: row.tags ? JSON.parse(row.tags) : void 0
702
+ }));
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(
641
1454
  this.db,
642
- `INSERT OR REPLACE INTO endless_config (key, value, updated_at)
643
- VALUES (?, ?, CURRENT_TIMESTAMP)`,
644
- [key, JSON.stringify(value)]
1455
+ `UPDATE events
1456
+ SET access_count = access_count + 1,
1457
+ last_accessed_at = ?
1458
+ WHERE id IN (${placeholders})`,
1459
+ [currentTime, ...eventIds]
645
1460
  );
646
1461
  }
647
1462
  /**
648
- * Get all sessions
1463
+ * Get most accessed memories
649
1464
  */
650
- async getAllSessions() {
1465
+ async getMostAccessed(limit = 10) {
651
1466
  await this.initialize();
652
- const rows = await dbAll(
1467
+ const rows = sqliteAll(
653
1468
  this.db,
654
- `SELECT * FROM sessions ORDER BY started_at DESC`
1469
+ `SELECT * FROM events
1470
+ WHERE access_count > 0
1471
+ ORDER BY access_count DESC, last_accessed_at DESC
1472
+ LIMIT ?`,
1473
+ [limit]
655
1474
  );
656
- return rows.map((row) => ({
657
- id: row.id,
658
- startedAt: toDate(row.started_at),
659
- endedAt: row.ended_at ? toDate(row.ended_at) : void 0,
660
- projectPath: row.project_path,
661
- summary: row.summary,
662
- tags: row.tags ? JSON.parse(row.tags) : void 0
663
- }));
1475
+ return rows.map((row) => this.rowToEvent(row));
1476
+ }
1477
+ /**
1478
+ * Get database instance for direct access
1479
+ */
1480
+ getDatabase() {
1481
+ return this.db;
664
1482
  }
665
1483
  /**
666
1484
  * Close database connection
667
1485
  */
668
1486
  async close() {
669
- await dbClose(this.db);
1487
+ sqliteClose(this.db);
670
1488
  }
671
1489
  /**
672
1490
  * Convert database row to MemoryEvent
673
1491
  */
674
1492
  rowToEvent(row) {
675
- return {
1493
+ const event = {
676
1494
  id: row.id,
677
1495
  eventType: row.event_type,
678
1496
  sessionId: row.session_id,
679
- timestamp: toDate(row.timestamp),
1497
+ timestamp: toDateFromSQLite(row.timestamp),
680
1498
  content: row.content,
681
1499
  canonicalKey: row.canonical_key,
682
1500
  dedupeKey: row.dedupe_key,
683
1501
  metadata: row.metadata ? JSON.parse(row.metadata) : void 0
684
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;
685
1673
  }
686
1674
  };
687
1675
 
@@ -913,7 +1901,7 @@ function getDefaultEmbedder() {
913
1901
  }
914
1902
 
915
1903
  // src/core/vector-outbox.ts
916
- var DEFAULT_CONFIG = {
1904
+ var DEFAULT_CONFIG2 = {
917
1905
  embeddingVersion: "v1",
918
1906
  maxRetries: 3,
919
1907
  stuckThresholdMs: 5 * 60 * 1e3,
@@ -922,7 +1910,7 @@ var DEFAULT_CONFIG = {
922
1910
  };
923
1911
 
924
1912
  // src/core/vector-worker.ts
925
- var DEFAULT_CONFIG2 = {
1913
+ var DEFAULT_CONFIG3 = {
926
1914
  batchSize: 32,
927
1915
  pollIntervalMs: 1e3,
928
1916
  maxRetries: 3
@@ -933,12 +1921,13 @@ var VectorWorker = class {
933
1921
  embedder;
934
1922
  config;
935
1923
  running = false;
1924
+ stopping = false;
936
1925
  pollTimeout = null;
937
1926
  constructor(eventStore, vectorStore, embedder, config = {}) {
938
1927
  this.eventStore = eventStore;
939
1928
  this.vectorStore = vectorStore;
940
1929
  this.embedder = embedder;
941
- this.config = { ...DEFAULT_CONFIG2, ...config };
1930
+ this.config = { ...DEFAULT_CONFIG3, ...config };
942
1931
  }
943
1932
  /**
944
1933
  * Start the worker polling loop
@@ -947,6 +1936,7 @@ var VectorWorker = class {
947
1936
  if (this.running)
948
1937
  return;
949
1938
  this.running = true;
1939
+ this.stopping = false;
950
1940
  this.poll();
951
1941
  }
952
1942
  /**
@@ -954,6 +1944,7 @@ var VectorWorker = class {
954
1944
  */
955
1945
  stop() {
956
1946
  this.running = false;
1947
+ this.stopping = true;
957
1948
  if (this.pollTimeout) {
958
1949
  clearTimeout(this.pollTimeout);
959
1950
  this.pollTimeout = null;
@@ -1003,9 +1994,15 @@ var VectorWorker = class {
1003
1994
  }
1004
1995
  return successful.length;
1005
1996
  } catch (error) {
1006
- const allIds = items.map((i) => i.id);
1007
- const errorMessage = error instanceof Error ? error.message : String(error);
1008
- 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
+ }
1009
2006
  throw error;
1010
2007
  }
1011
2008
  }
@@ -1013,14 +2010,18 @@ var VectorWorker = class {
1013
2010
  * Poll for new items
1014
2011
  */
1015
2012
  async poll() {
1016
- if (!this.running)
2013
+ if (!this.running || this.stopping)
1017
2014
  return;
1018
2015
  try {
1019
2016
  await this.processBatch();
1020
2017
  } catch (error) {
1021
- 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);
1022
2024
  }
1023
- this.pollTimeout = setTimeout(() => this.poll(), this.config.pollIntervalMs);
1024
2025
  }
1025
2026
  /**
1026
2027
  * Process all pending items (blocking)
@@ -1047,7 +2048,7 @@ function createVectorWorker(eventStore, vectorStore, embedder, config) {
1047
2048
  }
1048
2049
 
1049
2050
  // src/core/matcher.ts
1050
- var DEFAULT_CONFIG3 = {
2051
+ var DEFAULT_CONFIG4 = {
1051
2052
  weights: {
1052
2053
  semanticSimilarity: 0.4,
1053
2054
  ftsScore: 0.25,
@@ -1062,9 +2063,9 @@ var Matcher = class {
1062
2063
  config;
1063
2064
  constructor(config = {}) {
1064
2065
  this.config = {
1065
- ...DEFAULT_CONFIG3,
2066
+ ...DEFAULT_CONFIG4,
1066
2067
  ...config,
1067
- weights: { ...DEFAULT_CONFIG3.weights, ...config.weights }
2068
+ weights: { ...DEFAULT_CONFIG4.weights, ...config.weights }
1068
2069
  };
1069
2070
  }
1070
2071
  /**
@@ -1191,6 +2192,7 @@ var Retriever = class {
1191
2192
  matcher;
1192
2193
  sharedStore;
1193
2194
  sharedVectorStore;
2195
+ graduation;
1194
2196
  constructor(eventStore, vectorStore, embedder, matcher, sharedOptions) {
1195
2197
  this.eventStore = eventStore;
1196
2198
  this.vectorStore = vectorStore;
@@ -1199,6 +2201,12 @@ var Retriever = class {
1199
2201
  this.sharedStore = sharedOptions?.sharedStore;
1200
2202
  this.sharedVectorStore = sharedOptions?.sharedVectorStore;
1201
2203
  }
2204
+ /**
2205
+ * Set graduation pipeline for access tracking
2206
+ */
2207
+ setGraduationPipeline(graduation) {
2208
+ this.graduation = graduation;
2209
+ }
1202
2210
  /**
1203
2211
  * Set shared stores after construction
1204
2212
  */
@@ -1321,6 +2329,13 @@ var Retriever = class {
1321
2329
  const event = await this.eventStore.getEvent(result.eventId);
1322
2330
  if (!event)
1323
2331
  continue;
2332
+ if (this.graduation) {
2333
+ this.graduation.recordAccess(
2334
+ event.id,
2335
+ options.sessionId || "unknown",
2336
+ result.score
2337
+ );
2338
+ }
1324
2339
  let sessionContext;
1325
2340
  if (options.includeSessionContext) {
1326
2341
  sessionContext = await this.getSessionContext(event.sessionId, event.id);
@@ -1442,15 +2457,26 @@ var GraduationPipeline = class {
1442
2457
  L3toL4: { ...DEFAULT_CRITERIA.L3toL4, ...criteria.L3toL4 }
1443
2458
  };
1444
2459
  }
2460
+ // Track which sessions have accessed each event
2461
+ sessionAccesses = /* @__PURE__ */ new Map();
1445
2462
  /**
1446
2463
  * Record an access to an event (used for graduation scoring)
1447
2464
  */
1448
2465
  recordAccess(eventId, fromSessionId, confidence = 1) {
1449
2466
  const existing = this.metrics.get(eventId);
2467
+ if (!this.sessionAccesses.has(eventId)) {
2468
+ this.sessionAccesses.set(eventId, /* @__PURE__ */ new Set());
2469
+ }
2470
+ const sessions = this.sessionAccesses.get(eventId);
2471
+ const isNewSession = !sessions.has(fromSessionId);
2472
+ sessions.add(fromSessionId);
1450
2473
  if (existing) {
1451
2474
  existing.accessCount++;
1452
2475
  existing.lastAccessed = /* @__PURE__ */ new Date();
1453
2476
  existing.confidence = Math.max(existing.confidence, confidence);
2477
+ if (isNewSession && sessions.size > 1) {
2478
+ existing.crossSessionRefs = sessions.size - 1;
2479
+ }
1454
2480
  } else {
1455
2481
  this.metrics.set(eventId, {
1456
2482
  eventId,
@@ -1747,7 +2773,7 @@ function createSharedEventStore(dbPath) {
1747
2773
  }
1748
2774
 
1749
2775
  // src/core/shared-store.ts
1750
- import { randomUUID as randomUUID2 } from "crypto";
2776
+ import { randomUUID as randomUUID3 } from "crypto";
1751
2777
  var SharedStore = class {
1752
2778
  constructor(sharedEventStore) {
1753
2779
  this.sharedEventStore = sharedEventStore;
@@ -1759,7 +2785,7 @@ var SharedStore = class {
1759
2785
  * Promote a verified troubleshooting entry to shared storage
1760
2786
  */
1761
2787
  async promoteEntry(input) {
1762
- const entryId = randomUUID2();
2788
+ const entryId = randomUUID3();
1763
2789
  await dbRun(
1764
2790
  this.db,
1765
2791
  `INSERT INTO shared_troubleshooting (
@@ -2124,7 +3150,7 @@ function createSharedVectorStore(dbPath) {
2124
3150
  }
2125
3151
 
2126
3152
  // src/core/shared-promoter.ts
2127
- import { randomUUID as randomUUID3 } from "crypto";
3153
+ import { randomUUID as randomUUID4 } from "crypto";
2128
3154
  var SharedPromoter = class {
2129
3155
  constructor(sharedStore, sharedVectorStore, embedder, config) {
2130
3156
  this.sharedStore = sharedStore;
@@ -2192,7 +3218,7 @@ var SharedPromoter = class {
2192
3218
  const embeddingContent = this.createEmbeddingContent(input);
2193
3219
  const embedding = await this.embedder.embed(embeddingContent);
2194
3220
  await this.sharedVectorStore.upsert({
2195
- id: randomUUID3(),
3221
+ id: randomUUID4(),
2196
3222
  entryId,
2197
3223
  entryType: "troubleshooting",
2198
3224
  content: embeddingContent,
@@ -2332,7 +3358,7 @@ function createToolObservationEmbedding(toolName, metadata, success) {
2332
3358
  }
2333
3359
 
2334
3360
  // src/core/working-set-store.ts
2335
- import { randomUUID as randomUUID4 } from "crypto";
3361
+ import { randomUUID as randomUUID5 } from "crypto";
2336
3362
  var WorkingSetStore = class {
2337
3363
  constructor(eventStore, config) {
2338
3364
  this.eventStore = eventStore;
@@ -2353,7 +3379,7 @@ var WorkingSetStore = class {
2353
3379
  `INSERT OR REPLACE INTO working_set (id, event_id, added_at, relevance_score, topics, expires_at)
2354
3380
  VALUES (?, ?, CURRENT_TIMESTAMP, ?, ?, ?)`,
2355
3381
  [
2356
- randomUUID4(),
3382
+ randomUUID5(),
2357
3383
  eventId,
2358
3384
  relevanceScore,
2359
3385
  JSON.stringify(topics || []),
@@ -2537,7 +3563,7 @@ function createWorkingSetStore(eventStore, config) {
2537
3563
  }
2538
3564
 
2539
3565
  // src/core/consolidated-store.ts
2540
- import { randomUUID as randomUUID5 } from "crypto";
3566
+ import { randomUUID as randomUUID6 } from "crypto";
2541
3567
  var ConsolidatedStore = class {
2542
3568
  constructor(eventStore) {
2543
3569
  this.eventStore = eventStore;
@@ -2549,7 +3575,7 @@ var ConsolidatedStore = class {
2549
3575
  * Create a new consolidated memory
2550
3576
  */
2551
3577
  async create(input) {
2552
- const memoryId = randomUUID5();
3578
+ const memoryId = randomUUID6();
2553
3579
  await dbRun(
2554
3580
  this.db,
2555
3581
  `INSERT INTO consolidated_memories
@@ -3076,7 +4102,7 @@ function createConsolidationWorker(workingSetStore, consolidatedStore, config) {
3076
4102
  }
3077
4103
 
3078
4104
  // src/core/continuity-manager.ts
3079
- import { randomUUID as randomUUID6 } from "crypto";
4105
+ import { randomUUID as randomUUID7 } from "crypto";
3080
4106
  var ContinuityManager = class {
3081
4107
  constructor(eventStore, config) {
3082
4108
  this.eventStore = eventStore;
@@ -3230,7 +4256,7 @@ var ContinuityManager = class {
3230
4256
  `INSERT INTO continuity_log
3231
4257
  (log_id, from_context_id, to_context_id, continuity_score, transition_type, created_at)
3232
4258
  VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`,
3233
- [randomUUID6(), previous.id, current.id, score, type]
4259
+ [randomUUID7(), previous.id, current.id, score, type]
3234
4260
  );
3235
4261
  }
3236
4262
  /**
@@ -3338,6 +4364,127 @@ function createContinuityManager(eventStore, config) {
3338
4364
  return new ContinuityManager(eventStore, config);
3339
4365
  }
3340
4366
 
4367
+ // src/core/graduation-worker.ts
4368
+ var DEFAULT_CONFIG5 = {
4369
+ evaluationIntervalMs: 3e5,
4370
+ // 5 minutes
4371
+ batchSize: 50,
4372
+ cooldownMs: 36e5
4373
+ // 1 hour cooldown between evaluations
4374
+ };
4375
+ var GraduationWorker = class {
4376
+ constructor(eventStore, graduation, config = DEFAULT_CONFIG5) {
4377
+ this.eventStore = eventStore;
4378
+ this.graduation = graduation;
4379
+ this.config = config;
4380
+ }
4381
+ running = false;
4382
+ timeout = null;
4383
+ lastEvaluated = /* @__PURE__ */ new Map();
4384
+ /**
4385
+ * Start the graduation worker
4386
+ */
4387
+ start() {
4388
+ if (this.running)
4389
+ return;
4390
+ this.running = true;
4391
+ this.scheduleNext();
4392
+ }
4393
+ /**
4394
+ * Stop the graduation worker
4395
+ */
4396
+ stop() {
4397
+ this.running = false;
4398
+ if (this.timeout) {
4399
+ clearTimeout(this.timeout);
4400
+ this.timeout = null;
4401
+ }
4402
+ }
4403
+ /**
4404
+ * Check if currently running
4405
+ */
4406
+ isRunning() {
4407
+ return this.running;
4408
+ }
4409
+ /**
4410
+ * Force a graduation evaluation run
4411
+ */
4412
+ async forceRun() {
4413
+ return await this.runGraduation();
4414
+ }
4415
+ /**
4416
+ * Schedule the next graduation check
4417
+ */
4418
+ scheduleNext() {
4419
+ if (!this.running)
4420
+ return;
4421
+ this.timeout = setTimeout(
4422
+ () => this.run(),
4423
+ this.config.evaluationIntervalMs
4424
+ );
4425
+ }
4426
+ /**
4427
+ * Run graduation evaluation
4428
+ */
4429
+ async run() {
4430
+ if (!this.running)
4431
+ return;
4432
+ try {
4433
+ await this.runGraduation();
4434
+ } catch (error) {
4435
+ console.error("Graduation error:", error);
4436
+ }
4437
+ this.scheduleNext();
4438
+ }
4439
+ /**
4440
+ * Perform graduation evaluation across all levels
4441
+ */
4442
+ async runGraduation() {
4443
+ const result = {
4444
+ evaluated: 0,
4445
+ graduated: 0,
4446
+ byLevel: {}
4447
+ };
4448
+ const levels = ["L0", "L1", "L2", "L3"];
4449
+ const now = Date.now();
4450
+ for (const level of levels) {
4451
+ const events = await this.eventStore.getEventsByLevel(level, {
4452
+ limit: this.config.batchSize
4453
+ });
4454
+ let levelGraduated = 0;
4455
+ for (const event of events) {
4456
+ const lastEval = this.lastEvaluated.get(event.id);
4457
+ if (lastEval && now - lastEval < this.config.cooldownMs) {
4458
+ continue;
4459
+ }
4460
+ result.evaluated++;
4461
+ this.lastEvaluated.set(event.id, now);
4462
+ const gradResult = await this.graduation.evaluateGraduation(event.id, level);
4463
+ if (gradResult.success) {
4464
+ result.graduated++;
4465
+ levelGraduated++;
4466
+ }
4467
+ }
4468
+ if (levelGraduated > 0) {
4469
+ result.byLevel[level] = levelGraduated;
4470
+ }
4471
+ }
4472
+ if (this.lastEvaluated.size > 1e3) {
4473
+ const entries = Array.from(this.lastEvaluated.entries());
4474
+ entries.sort((a, b) => b[1] - a[1]);
4475
+ this.lastEvaluated = new Map(entries.slice(0, 1e3));
4476
+ }
4477
+ return result;
4478
+ }
4479
+ };
4480
+ function createGraduationWorker(eventStore, graduation, config) {
4481
+ return new GraduationWorker(
4482
+ eventStore,
4483
+ graduation,
4484
+ { ...DEFAULT_CONFIG5, ...config }
4485
+ );
4486
+ }
4487
+
3341
4488
  // src/services/memory-service.ts
3342
4489
  function normalizePath(projectPath) {
3343
4490
  const expanded = projectPath.startsWith("~") ? path.join(os.homedir(), projectPath.slice(1)) : projectPath;
@@ -3394,13 +4541,18 @@ function registerSession(sessionId, projectPath) {
3394
4541
  saveSessionRegistry(registry);
3395
4542
  }
3396
4543
  var MemoryService = class {
3397
- eventStore;
4544
+ // Primary store: SQLite (WAL mode) - for hooks, always available
4545
+ sqliteStore;
4546
+ // Analytics store: DuckDB - for server reads (optional, synced from SQLite)
4547
+ analyticsStore;
4548
+ syncWorker = null;
3398
4549
  vectorStore;
3399
4550
  embedder;
3400
4551
  matcher;
3401
4552
  retriever;
3402
4553
  graduation;
3403
4554
  vectorWorker = null;
4555
+ graduationWorker = null;
3404
4556
  initialized = false;
3405
4557
  // Endless Mode components
3406
4558
  workingSetStore = null;
@@ -3415,24 +4567,48 @@ var MemoryService = class {
3415
4567
  sharedPromoter = null;
3416
4568
  sharedStoreConfig = null;
3417
4569
  projectHash = null;
4570
+ readOnly;
3418
4571
  constructor(config) {
3419
4572
  const storagePath = this.expandPath(config.storagePath);
3420
- if (!fs.existsSync(storagePath)) {
4573
+ this.readOnly = config.readOnly ?? false;
4574
+ if (!this.readOnly && !fs.existsSync(storagePath)) {
3421
4575
  fs.mkdirSync(storagePath, { recursive: true });
3422
4576
  }
3423
4577
  this.projectHash = config.projectHash || null;
3424
4578
  this.sharedStoreConfig = config.sharedStoreConfig ?? { enabled: true };
3425
- this.eventStore = new EventStore(path.join(storagePath, "events.duckdb"));
4579
+ this.sqliteStore = new SQLiteEventStore(
4580
+ path.join(storagePath, "events.sqlite"),
4581
+ { readonly: this.readOnly }
4582
+ );
4583
+ const analyticsEnabled = config.analyticsEnabled ?? this.readOnly;
4584
+ if (!analyticsEnabled) {
4585
+ this.analyticsStore = null;
4586
+ } else if (this.readOnly) {
4587
+ try {
4588
+ this.analyticsStore = new EventStore(
4589
+ path.join(storagePath, "analytics.duckdb"),
4590
+ { readOnly: true }
4591
+ );
4592
+ } catch {
4593
+ this.analyticsStore = null;
4594
+ }
4595
+ } else {
4596
+ this.analyticsStore = new EventStore(
4597
+ path.join(storagePath, "analytics.duckdb"),
4598
+ { readOnly: false }
4599
+ );
4600
+ }
3426
4601
  this.vectorStore = new VectorStore(path.join(storagePath, "vectors"));
3427
4602
  this.embedder = config.embeddingModel ? new Embedder(config.embeddingModel) : getDefaultEmbedder();
3428
4603
  this.matcher = getDefaultMatcher();
3429
4604
  this.retriever = createRetriever(
3430
- this.eventStore,
4605
+ this.sqliteStore,
4606
+ // Interface compatible
3431
4607
  this.vectorStore,
3432
4608
  this.embedder,
3433
4609
  this.matcher
3434
4610
  );
3435
- this.graduation = createGraduationPipeline(this.eventStore);
4611
+ this.graduation = createGraduationPipeline(this.sqliteStore);
3436
4612
  }
3437
4613
  /**
3438
4614
  * Initialize all components
@@ -3440,22 +4616,45 @@ var MemoryService = class {
3440
4616
  async initialize() {
3441
4617
  if (this.initialized)
3442
4618
  return;
3443
- await this.eventStore.initialize();
4619
+ await this.sqliteStore.initialize();
4620
+ if (this.analyticsStore) {
4621
+ try {
4622
+ await this.analyticsStore.initialize();
4623
+ } catch (error) {
4624
+ console.warn("[MemoryService] Analytics store (DuckDB) initialization failed, using SQLite for reads:", error);
4625
+ }
4626
+ }
3444
4627
  await this.vectorStore.initialize();
3445
4628
  await this.embedder.initialize();
3446
- this.vectorWorker = createVectorWorker(
3447
- this.eventStore,
3448
- this.vectorStore,
3449
- this.embedder
3450
- );
3451
- this.vectorWorker.start();
3452
- const savedMode = await this.eventStore.getEndlessConfig("mode");
3453
- if (savedMode === "endless") {
3454
- this.endlessMode = "endless";
3455
- await this.initializeEndlessMode();
3456
- }
3457
- if (this.sharedStoreConfig?.enabled !== false) {
3458
- await this.initializeSharedStore();
4629
+ if (!this.readOnly) {
4630
+ this.vectorWorker = createVectorWorker(
4631
+ this.sqliteStore,
4632
+ this.vectorStore,
4633
+ this.embedder
4634
+ );
4635
+ this.vectorWorker.start();
4636
+ this.retriever.setGraduationPipeline(this.graduation);
4637
+ this.graduationWorker = createGraduationWorker(
4638
+ this.sqliteStore,
4639
+ this.graduation
4640
+ );
4641
+ this.graduationWorker.start();
4642
+ if (this.analyticsStore) {
4643
+ this.syncWorker = new SyncWorker(
4644
+ this.sqliteStore,
4645
+ this.analyticsStore,
4646
+ { intervalMs: 3e4, batchSize: 500 }
4647
+ );
4648
+ this.syncWorker.start();
4649
+ }
4650
+ const savedMode = await this.sqliteStore.getEndlessConfig("mode");
4651
+ if (savedMode === "endless") {
4652
+ this.endlessMode = "endless";
4653
+ await this.initializeEndlessMode();
4654
+ }
4655
+ if (this.sharedStoreConfig?.enabled !== false) {
4656
+ await this.initializeSharedStore();
4657
+ }
3459
4658
  }
3460
4659
  this.initialized = true;
3461
4660
  }
@@ -3489,7 +4688,7 @@ var MemoryService = class {
3489
4688
  */
3490
4689
  async startSession(sessionId, projectPath) {
3491
4690
  await this.initialize();
3492
- await this.eventStore.upsertSession({
4691
+ await this.sqliteStore.upsertSession({
3493
4692
  id: sessionId,
3494
4693
  startedAt: /* @__PURE__ */ new Date(),
3495
4694
  projectPath
@@ -3500,7 +4699,7 @@ var MemoryService = class {
3500
4699
  */
3501
4700
  async endSession(sessionId, summary) {
3502
4701
  await this.initialize();
3503
- await this.eventStore.upsertSession({
4702
+ await this.sqliteStore.upsertSession({
3504
4703
  id: sessionId,
3505
4704
  endedAt: /* @__PURE__ */ new Date(),
3506
4705
  summary
@@ -3511,7 +4710,7 @@ var MemoryService = class {
3511
4710
  */
3512
4711
  async storeUserPrompt(sessionId, content, metadata) {
3513
4712
  await this.initialize();
3514
- const result = await this.eventStore.append({
4713
+ const result = await this.sqliteStore.append({
3515
4714
  eventType: "user_prompt",
3516
4715
  sessionId,
3517
4716
  timestamp: /* @__PURE__ */ new Date(),
@@ -3519,7 +4718,7 @@ var MemoryService = class {
3519
4718
  metadata
3520
4719
  });
3521
4720
  if (result.success && !result.isDuplicate) {
3522
- await this.eventStore.enqueueForEmbedding(result.eventId, content);
4721
+ await this.sqliteStore.enqueueForEmbedding(result.eventId, content);
3523
4722
  }
3524
4723
  return result;
3525
4724
  }
@@ -3528,7 +4727,7 @@ var MemoryService = class {
3528
4727
  */
3529
4728
  async storeAgentResponse(sessionId, content, metadata) {
3530
4729
  await this.initialize();
3531
- const result = await this.eventStore.append({
4730
+ const result = await this.sqliteStore.append({
3532
4731
  eventType: "agent_response",
3533
4732
  sessionId,
3534
4733
  timestamp: /* @__PURE__ */ new Date(),
@@ -3536,7 +4735,7 @@ var MemoryService = class {
3536
4735
  metadata
3537
4736
  });
3538
4737
  if (result.success && !result.isDuplicate) {
3539
- await this.eventStore.enqueueForEmbedding(result.eventId, content);
4738
+ await this.sqliteStore.enqueueForEmbedding(result.eventId, content);
3540
4739
  }
3541
4740
  return result;
3542
4741
  }
@@ -3545,14 +4744,14 @@ var MemoryService = class {
3545
4744
  */
3546
4745
  async storeSessionSummary(sessionId, summary) {
3547
4746
  await this.initialize();
3548
- const result = await this.eventStore.append({
4747
+ const result = await this.sqliteStore.append({
3549
4748
  eventType: "session_summary",
3550
4749
  sessionId,
3551
4750
  timestamp: /* @__PURE__ */ new Date(),
3552
4751
  content: summary
3553
4752
  });
3554
4753
  if (result.success && !result.isDuplicate) {
3555
- await this.eventStore.enqueueForEmbedding(result.eventId, summary);
4754
+ await this.sqliteStore.enqueueForEmbedding(result.eventId, summary);
3556
4755
  }
3557
4756
  return result;
3558
4757
  }
@@ -3562,7 +4761,7 @@ var MemoryService = class {
3562
4761
  async storeToolObservation(sessionId, payload) {
3563
4762
  await this.initialize();
3564
4763
  const content = JSON.stringify(payload);
3565
- const result = await this.eventStore.append({
4764
+ const result = await this.sqliteStore.append({
3566
4765
  eventType: "tool_observation",
3567
4766
  sessionId,
3568
4767
  timestamp: /* @__PURE__ */ new Date(),
@@ -3578,7 +4777,7 @@ var MemoryService = class {
3578
4777
  payload.metadata || {},
3579
4778
  payload.success
3580
4779
  );
3581
- await this.eventStore.enqueueForEmbedding(result.eventId, embeddingContent);
4780
+ await this.sqliteStore.enqueueForEmbedding(result.eventId, embeddingContent);
3582
4781
  }
3583
4782
  return result;
3584
4783
  }
@@ -3604,21 +4803,21 @@ var MemoryService = class {
3604
4803
  */
3605
4804
  async getSessionHistory(sessionId) {
3606
4805
  await this.initialize();
3607
- return this.eventStore.getSessionEvents(sessionId);
4806
+ return this.sqliteStore.getSessionEvents(sessionId);
3608
4807
  }
3609
4808
  /**
3610
4809
  * Get recent events
3611
4810
  */
3612
4811
  async getRecentEvents(limit = 100) {
3613
4812
  await this.initialize();
3614
- return this.eventStore.getRecentEvents(limit);
4813
+ return this.sqliteStore.getRecentEvents(limit);
3615
4814
  }
3616
4815
  /**
3617
4816
  * Get memory statistics
3618
4817
  */
3619
4818
  async getStats() {
3620
4819
  await this.initialize();
3621
- const recentEvents = await this.eventStore.getRecentEvents(1e4);
4820
+ const recentEvents = await this.sqliteStore.getRecentEvents(1e4);
3622
4821
  const vectorCount = await this.vectorStore.count();
3623
4822
  const levelStats = await this.graduation.getStats();
3624
4823
  return {
@@ -3636,6 +4835,20 @@ var MemoryService = class {
3636
4835
  }
3637
4836
  return 0;
3638
4837
  }
4838
+ /**
4839
+ * Get events by memory level
4840
+ */
4841
+ async getEventsByLevel(level, options) {
4842
+ await this.initialize();
4843
+ return this.sqliteStore.getEventsByLevel(level, options);
4844
+ }
4845
+ /**
4846
+ * Get memory level for a specific event
4847
+ */
4848
+ async getEventLevel(eventId) {
4849
+ await this.initialize();
4850
+ return this.sqliteStore.getEventLevel(eventId);
4851
+ }
3639
4852
  /**
3640
4853
  * Format retrieval results as context for Claude
3641
4854
  */
@@ -3728,21 +4941,21 @@ var MemoryService = class {
3728
4941
  */
3729
4942
  async initializeEndlessMode() {
3730
4943
  const config = await this.getEndlessConfig();
3731
- this.workingSetStore = createWorkingSetStore(this.eventStore, config);
3732
- this.consolidatedStore = createConsolidatedStore(this.eventStore);
4944
+ this.workingSetStore = createWorkingSetStore(this.sqliteStore, config);
4945
+ this.consolidatedStore = createConsolidatedStore(this.sqliteStore);
3733
4946
  this.consolidationWorker = createConsolidationWorker(
3734
4947
  this.workingSetStore,
3735
4948
  this.consolidatedStore,
3736
4949
  config
3737
4950
  );
3738
- this.continuityManager = createContinuityManager(this.eventStore, config);
4951
+ this.continuityManager = createContinuityManager(this.sqliteStore, config);
3739
4952
  this.consolidationWorker.start();
3740
4953
  }
3741
4954
  /**
3742
4955
  * Get Endless Mode configuration
3743
4956
  */
3744
4957
  async getEndlessConfig() {
3745
- const savedConfig = await this.eventStore.getEndlessConfig("config");
4958
+ const savedConfig = await this.sqliteStore.getEndlessConfig("config");
3746
4959
  return savedConfig || this.getDefaultEndlessConfig();
3747
4960
  }
3748
4961
  /**
@@ -3751,7 +4964,7 @@ var MemoryService = class {
3751
4964
  async setEndlessConfig(config) {
3752
4965
  const current = await this.getEndlessConfig();
3753
4966
  const merged = { ...current, ...config };
3754
- await this.eventStore.setEndlessConfig("config", merged);
4967
+ await this.sqliteStore.setEndlessConfig("config", merged);
3755
4968
  }
3756
4969
  /**
3757
4970
  * Set memory mode (session or endless)
@@ -3761,7 +4974,7 @@ var MemoryService = class {
3761
4974
  if (mode === this.endlessMode)
3762
4975
  return;
3763
4976
  this.endlessMode = mode;
3764
- await this.eventStore.setEndlessConfig("mode", mode);
4977
+ await this.sqliteStore.setEndlessConfig("mode", mode);
3765
4978
  if (mode === "endless") {
3766
4979
  await this.initializeEndlessMode();
3767
4980
  } else {
@@ -3818,6 +5031,59 @@ var MemoryService = class {
3818
5031
  return [];
3819
5032
  return this.consolidatedStore.getAll({ limit });
3820
5033
  }
5034
+ /**
5035
+ * Increment access count for memories that were used in prompts
5036
+ */
5037
+ async incrementMemoryAccess(eventIds) {
5038
+ if (eventIds.length === 0)
5039
+ return;
5040
+ if (this.sqliteStore) {
5041
+ await this.sqliteStore.incrementAccessCount(eventIds);
5042
+ } else if (this.eventStore) {
5043
+ await this.eventStore.incrementAccessCount(eventIds);
5044
+ }
5045
+ }
5046
+ /**
5047
+ * Get most accessed memories from events
5048
+ */
5049
+ async getMostAccessedMemories(limit = 10) {
5050
+ console.log("[getMostAccessedMemories] sqliteStore available:", !!this.sqliteStore);
5051
+ if (this.sqliteStore) {
5052
+ const events = await this.sqliteStore.getMostAccessed(limit);
5053
+ console.log("[getMostAccessedMemories] Got events from SQLite:", events.length);
5054
+ return events.map((event) => ({
5055
+ memoryId: event.id,
5056
+ summary: event.content.substring(0, 200) + (event.content.length > 200 ? "..." : ""),
5057
+ topics: [],
5058
+ // Could extract topics from content if needed
5059
+ accessCount: event.access_count || 0,
5060
+ lastAccessed: event.last_accessed_at || null,
5061
+ confidence: 1,
5062
+ createdAt: event.timestamp
5063
+ }));
5064
+ }
5065
+ if (this.consolidatedStore) {
5066
+ const consolidated = await this.consolidatedStore.getMostAccessed(limit);
5067
+ return consolidated.map((m) => ({
5068
+ memoryId: m.memoryId,
5069
+ summary: m.summary,
5070
+ topics: m.topics,
5071
+ accessCount: m.accessCount,
5072
+ lastAccessed: m.accessedAt,
5073
+ confidence: m.confidence,
5074
+ createdAt: m.createdAt
5075
+ }));
5076
+ }
5077
+ return [];
5078
+ }
5079
+ /**
5080
+ * Mark a consolidated memory as accessed
5081
+ */
5082
+ async markMemoryAccessed(memoryId) {
5083
+ if (!this.consolidatedStore)
5084
+ return;
5085
+ await this.consolidatedStore.markAccessed(memoryId);
5086
+ }
3821
5087
  /**
3822
5088
  * Calculate continuity score for current context
3823
5089
  */
@@ -3905,20 +5171,44 @@ var MemoryService = class {
3905
5171
  }
3906
5172
  return parts.join("\n");
3907
5173
  }
5174
+ /**
5175
+ * Force a graduation evaluation run
5176
+ */
5177
+ async forceGraduation() {
5178
+ if (!this.graduationWorker) {
5179
+ return { evaluated: 0, graduated: 0, byLevel: {} };
5180
+ }
5181
+ return this.graduationWorker.forceRun();
5182
+ }
5183
+ /**
5184
+ * Record access to a memory event (for graduation scoring)
5185
+ */
5186
+ recordMemoryAccess(eventId, sessionId, confidence = 1) {
5187
+ this.graduation.recordAccess(eventId, sessionId, confidence);
5188
+ }
3908
5189
  /**
3909
5190
  * Shutdown service
3910
5191
  */
3911
5192
  async shutdown() {
5193
+ if (this.graduationWorker) {
5194
+ this.graduationWorker.stop();
5195
+ }
3912
5196
  if (this.consolidationWorker) {
3913
5197
  this.consolidationWorker.stop();
3914
5198
  }
3915
5199
  if (this.vectorWorker) {
3916
5200
  this.vectorWorker.stop();
3917
5201
  }
5202
+ if (this.syncWorker) {
5203
+ this.syncWorker.stop();
5204
+ }
3918
5205
  if (this.sharedEventStore) {
3919
5206
  await this.sharedEventStore.close();
3920
5207
  }
3921
- await this.eventStore.close();
5208
+ await this.sqliteStore.close();
5209
+ if (this.analyticsStore) {
5210
+ await this.analyticsStore.close();
5211
+ }
3922
5212
  }
3923
5213
  /**
3924
5214
  * Expand ~ to home directory
@@ -3938,7 +5228,10 @@ function getMemoryServiceForProject(projectPath, sharedStoreConfig) {
3938
5228
  serviceCache.set(hash, new MemoryService({
3939
5229
  storagePath,
3940
5230
  projectHash: hash,
3941
- sharedStoreConfig
5231
+ // Override shared store config - hooks don't need DuckDB
5232
+ sharedStoreConfig: sharedStoreConfig ?? { enabled: false },
5233
+ analyticsEnabled: false
5234
+ // Hooks don't need DuckDB
3942
5235
  }));
3943
5236
  }
3944
5237
  return serviceCache.get(hash);