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
package/dist/cli/index.js CHANGED
@@ -9,6 +9,9 @@ const __dirname = dirname(__filename);
9
9
  // src/cli/index.ts
10
10
  import { Command } from "commander";
11
11
  import { exec } from "child_process";
12
+ import * as fs4 from "fs";
13
+ import * as path4 from "path";
14
+ import * as os3 from "os";
12
15
 
13
16
  // src/services/memory-service.ts
14
17
  import * as path from "path";
@@ -71,8 +74,11 @@ function toDate(value) {
71
74
  return new Date(value);
72
75
  return new Date(String(value));
73
76
  }
74
- function createDatabase(path4) {
75
- return new duckdb.Database(path4);
77
+ function createDatabase(path5, options) {
78
+ if (options?.readOnly) {
79
+ return new duckdb.Database(path5, { access_mode: "READ_ONLY" });
80
+ }
81
+ return new duckdb.Database(path5);
76
82
  }
77
83
  function dbRun(db, sql, params = []) {
78
84
  return new Promise((resolve2, reject) => {
@@ -125,18 +131,24 @@ function dbClose(db) {
125
131
 
126
132
  // src/core/event-store.ts
127
133
  var EventStore = class {
128
- constructor(dbPath) {
134
+ constructor(dbPath, options) {
129
135
  this.dbPath = dbPath;
130
- this.db = createDatabase(dbPath);
136
+ this.readOnly = options?.readOnly ?? false;
137
+ this.db = createDatabase(dbPath, { readOnly: this.readOnly });
131
138
  }
132
139
  db;
133
140
  initialized = false;
141
+ readOnly;
134
142
  /**
135
143
  * Initialize database schema
136
144
  */
137
145
  async initialize() {
138
146
  if (this.initialized)
139
147
  return;
148
+ if (this.readOnly) {
149
+ this.initialized = true;
150
+ return;
151
+ }
140
152
  await dbRun(this.db, `
141
153
  CREATE TABLE IF NOT EXISTS events (
142
154
  id VARCHAR PRIMARY KEY,
@@ -613,6 +625,36 @@ var EventStore = class {
613
625
  );
614
626
  return rows;
615
627
  }
628
+ /**
629
+ * Get events by memory level
630
+ */
631
+ async getEventsByLevel(level, options) {
632
+ await this.initialize();
633
+ const limit = options?.limit || 50;
634
+ const offset = options?.offset || 0;
635
+ const rows = await dbAll(
636
+ this.db,
637
+ `SELECT e.* FROM events e
638
+ INNER JOIN memory_levels ml ON e.id = ml.event_id
639
+ WHERE ml.level = ?
640
+ ORDER BY e.timestamp DESC
641
+ LIMIT ? OFFSET ?`,
642
+ [level, limit, offset]
643
+ );
644
+ return rows.map((row) => this.rowToEvent(row));
645
+ }
646
+ /**
647
+ * Get memory level for a specific event
648
+ */
649
+ async getEventLevel(eventId) {
650
+ await this.initialize();
651
+ const rows = await dbAll(
652
+ this.db,
653
+ `SELECT level FROM memory_levels WHERE event_id = ?`,
654
+ [eventId]
655
+ );
656
+ return rows.length > 0 ? rows[0].level : null;
657
+ }
616
658
  // ============================================================
617
659
  // Endless Mode Helper Methods
618
660
  // ============================================================
@@ -667,25 +709,974 @@ var EventStore = class {
667
709
  }));
668
710
  }
669
711
  /**
670
- * Close database connection
712
+ * Increment access count for events (stub for compatibility)
713
+ */
714
+ async incrementAccessCount(eventIds) {
715
+ return Promise.resolve();
716
+ }
717
+ /**
718
+ * Get most accessed memories (stub for compatibility)
719
+ */
720
+ async getMostAccessed(limit = 10) {
721
+ return [];
722
+ }
723
+ /**
724
+ * Close database connection
725
+ */
726
+ async close() {
727
+ await dbClose(this.db);
728
+ }
729
+ /**
730
+ * Convert database row to MemoryEvent
731
+ */
732
+ rowToEvent(row) {
733
+ return {
734
+ id: row.id,
735
+ eventType: row.event_type,
736
+ sessionId: row.session_id,
737
+ timestamp: toDate(row.timestamp),
738
+ content: row.content,
739
+ canonicalKey: row.canonical_key,
740
+ dedupeKey: row.dedupe_key,
741
+ metadata: row.metadata ? JSON.parse(row.metadata) : void 0
742
+ };
743
+ }
744
+ };
745
+
746
+ // src/core/sqlite-event-store.ts
747
+ import { randomUUID as randomUUID2 } from "crypto";
748
+
749
+ // src/core/sqlite-wrapper.ts
750
+ import Database from "better-sqlite3";
751
+ function createSQLiteDatabase(path5, options) {
752
+ const db = new Database(path5, {
753
+ readonly: options?.readonly ?? false
754
+ });
755
+ if (!options?.readonly && (options?.walMode ?? true)) {
756
+ db.pragma("journal_mode = WAL");
757
+ db.pragma("synchronous = NORMAL");
758
+ db.pragma("busy_timeout = 5000");
759
+ }
760
+ return db;
761
+ }
762
+ function sqliteRun(db, sql, params = []) {
763
+ const stmt = db.prepare(sql);
764
+ return stmt.run(...params);
765
+ }
766
+ function sqliteAll(db, sql, params = []) {
767
+ const stmt = db.prepare(sql);
768
+ return stmt.all(...params);
769
+ }
770
+ function sqliteGet(db, sql, params = []) {
771
+ const stmt = db.prepare(sql);
772
+ return stmt.get(...params);
773
+ }
774
+ function sqliteExec(db, sql) {
775
+ db.exec(sql);
776
+ }
777
+ function sqliteClose(db) {
778
+ db.close();
779
+ }
780
+ function toDateFromSQLite(value) {
781
+ if (value instanceof Date)
782
+ return value;
783
+ if (typeof value === "string")
784
+ return new Date(value);
785
+ if (typeof value === "number")
786
+ return new Date(value);
787
+ return new Date(String(value));
788
+ }
789
+ function toSQLiteTimestamp(date) {
790
+ return date.toISOString();
791
+ }
792
+
793
+ // src/core/sqlite-event-store.ts
794
+ var SQLiteEventStore = class {
795
+ constructor(dbPath, options) {
796
+ this.dbPath = dbPath;
797
+ this.readOnly = options?.readonly ?? false;
798
+ this.db = createSQLiteDatabase(dbPath, {
799
+ readonly: this.readOnly,
800
+ walMode: !this.readOnly
801
+ });
802
+ }
803
+ db;
804
+ initialized = false;
805
+ readOnly;
806
+ /**
807
+ * Initialize database schema
808
+ */
809
+ async initialize() {
810
+ if (this.initialized)
811
+ return;
812
+ if (this.readOnly) {
813
+ this.initialized = true;
814
+ return;
815
+ }
816
+ sqliteExec(this.db, `
817
+ -- L0 EventStore: Single Source of Truth (immutable, append-only)
818
+ CREATE TABLE IF NOT EXISTS events (
819
+ id TEXT PRIMARY KEY,
820
+ event_type TEXT NOT NULL,
821
+ session_id TEXT NOT NULL,
822
+ timestamp TEXT NOT NULL,
823
+ content TEXT NOT NULL,
824
+ canonical_key TEXT NOT NULL,
825
+ dedupe_key TEXT UNIQUE,
826
+ metadata TEXT,
827
+ access_count INTEGER DEFAULT 0,
828
+ last_accessed_at TEXT
829
+ );
830
+
831
+ -- Dedup table for idempotency
832
+ CREATE TABLE IF NOT EXISTS event_dedup (
833
+ dedupe_key TEXT PRIMARY KEY,
834
+ event_id TEXT NOT NULL,
835
+ created_at TEXT DEFAULT (datetime('now'))
836
+ );
837
+
838
+ -- Session metadata
839
+ CREATE TABLE IF NOT EXISTS sessions (
840
+ id TEXT PRIMARY KEY,
841
+ started_at TEXT NOT NULL,
842
+ ended_at TEXT,
843
+ project_path TEXT,
844
+ summary TEXT,
845
+ tags TEXT
846
+ );
847
+
848
+ -- Insights (derived data, rebuildable)
849
+ CREATE TABLE IF NOT EXISTS insights (
850
+ id TEXT PRIMARY KEY,
851
+ insight_type TEXT NOT NULL,
852
+ content TEXT NOT NULL,
853
+ canonical_key TEXT NOT NULL,
854
+ confidence REAL,
855
+ source_events TEXT,
856
+ created_at TEXT,
857
+ last_updated TEXT
858
+ );
859
+
860
+ -- Embedding Outbox (Single-Writer Pattern)
861
+ CREATE TABLE IF NOT EXISTS embedding_outbox (
862
+ id TEXT PRIMARY KEY,
863
+ event_id TEXT NOT NULL,
864
+ content TEXT NOT NULL,
865
+ status TEXT DEFAULT 'pending',
866
+ retry_count INTEGER DEFAULT 0,
867
+ created_at TEXT DEFAULT (datetime('now')),
868
+ processed_at TEXT,
869
+ error_message TEXT
870
+ );
871
+
872
+ -- Projection offset tracking
873
+ CREATE TABLE IF NOT EXISTS projection_offsets (
874
+ projection_name TEXT PRIMARY KEY,
875
+ last_event_id TEXT,
876
+ last_timestamp TEXT,
877
+ updated_at TEXT DEFAULT (datetime('now'))
878
+ );
879
+
880
+ -- Memory level tracking
881
+ CREATE TABLE IF NOT EXISTS memory_levels (
882
+ event_id TEXT PRIMARY KEY,
883
+ level TEXT NOT NULL DEFAULT 'L0',
884
+ promoted_at TEXT DEFAULT (datetime('now'))
885
+ );
886
+
887
+ -- Entries (immutable memory units)
888
+ CREATE TABLE IF NOT EXISTS entries (
889
+ entry_id TEXT PRIMARY KEY,
890
+ created_ts TEXT NOT NULL,
891
+ entry_type TEXT NOT NULL,
892
+ title TEXT NOT NULL,
893
+ content_json TEXT NOT NULL,
894
+ stage TEXT NOT NULL DEFAULT 'raw',
895
+ status TEXT DEFAULT 'active',
896
+ superseded_by TEXT,
897
+ build_id TEXT,
898
+ evidence_json TEXT,
899
+ canonical_key TEXT,
900
+ created_at TEXT DEFAULT (datetime('now'))
901
+ );
902
+
903
+ -- Entities (task/condition/artifact)
904
+ CREATE TABLE IF NOT EXISTS entities (
905
+ entity_id TEXT PRIMARY KEY,
906
+ entity_type TEXT NOT NULL,
907
+ canonical_key TEXT NOT NULL,
908
+ title TEXT NOT NULL,
909
+ stage TEXT NOT NULL DEFAULT 'raw',
910
+ status TEXT NOT NULL DEFAULT 'active',
911
+ current_json TEXT NOT NULL,
912
+ title_norm TEXT,
913
+ search_text TEXT,
914
+ created_at TEXT DEFAULT (datetime('now')),
915
+ updated_at TEXT DEFAULT (datetime('now'))
916
+ );
917
+
918
+ -- Entity aliases for canonical key lookup
919
+ CREATE TABLE IF NOT EXISTS entity_aliases (
920
+ entity_type TEXT NOT NULL,
921
+ canonical_key TEXT NOT NULL,
922
+ entity_id TEXT NOT NULL,
923
+ is_primary INTEGER DEFAULT 0,
924
+ created_at TEXT DEFAULT (datetime('now')),
925
+ PRIMARY KEY(entity_type, canonical_key)
926
+ );
927
+
928
+ -- Edges (relationships between entries/entities)
929
+ CREATE TABLE IF NOT EXISTS edges (
930
+ edge_id TEXT PRIMARY KEY,
931
+ src_type TEXT NOT NULL,
932
+ src_id TEXT NOT NULL,
933
+ rel_type TEXT NOT NULL,
934
+ dst_type TEXT NOT NULL,
935
+ dst_id TEXT NOT NULL,
936
+ meta_json TEXT,
937
+ created_at TEXT DEFAULT (datetime('now'))
938
+ );
939
+
940
+ -- Vector Outbox V2 Table
941
+ CREATE TABLE IF NOT EXISTS vector_outbox (
942
+ job_id TEXT PRIMARY KEY,
943
+ item_kind TEXT NOT NULL,
944
+ item_id TEXT NOT NULL,
945
+ embedding_version TEXT NOT NULL,
946
+ status TEXT NOT NULL DEFAULT 'pending',
947
+ retry_count INTEGER DEFAULT 0,
948
+ error TEXT,
949
+ created_at TEXT DEFAULT (datetime('now')),
950
+ updated_at TEXT DEFAULT (datetime('now')),
951
+ UNIQUE(item_kind, item_id, embedding_version)
952
+ );
953
+
954
+ -- Build Runs
955
+ CREATE TABLE IF NOT EXISTS build_runs (
956
+ build_id TEXT PRIMARY KEY,
957
+ started_at TEXT NOT NULL,
958
+ finished_at TEXT,
959
+ extractor_model TEXT NOT NULL,
960
+ extractor_prompt_hash TEXT NOT NULL,
961
+ embedder_model TEXT NOT NULL,
962
+ embedding_version TEXT NOT NULL,
963
+ idris_version TEXT NOT NULL,
964
+ schema_version TEXT NOT NULL,
965
+ status TEXT NOT NULL DEFAULT 'running',
966
+ error TEXT
967
+ );
968
+
969
+ -- Pipeline Metrics
970
+ CREATE TABLE IF NOT EXISTS pipeline_metrics (
971
+ id TEXT PRIMARY KEY,
972
+ ts TEXT NOT NULL,
973
+ stage TEXT NOT NULL,
974
+ latency_ms REAL NOT NULL,
975
+ success INTEGER NOT NULL,
976
+ error TEXT,
977
+ session_id TEXT
978
+ );
979
+
980
+ -- Working Set table (active memory window)
981
+ CREATE TABLE IF NOT EXISTS working_set (
982
+ id TEXT PRIMARY KEY,
983
+ event_id TEXT NOT NULL,
984
+ added_at TEXT DEFAULT (datetime('now')),
985
+ relevance_score REAL DEFAULT 1.0,
986
+ topics TEXT,
987
+ expires_at TEXT
988
+ );
989
+
990
+ -- Consolidated Memories table (long-term integrated memories)
991
+ CREATE TABLE IF NOT EXISTS consolidated_memories (
992
+ memory_id TEXT PRIMARY KEY,
993
+ summary TEXT NOT NULL,
994
+ topics TEXT,
995
+ source_events TEXT,
996
+ confidence REAL DEFAULT 0.5,
997
+ created_at TEXT DEFAULT (datetime('now')),
998
+ accessed_at TEXT,
999
+ access_count INTEGER DEFAULT 0
1000
+ );
1001
+
1002
+ -- Continuity Log table (tracks context transitions)
1003
+ CREATE TABLE IF NOT EXISTS continuity_log (
1004
+ log_id TEXT PRIMARY KEY,
1005
+ from_context_id TEXT,
1006
+ to_context_id TEXT,
1007
+ continuity_score REAL,
1008
+ transition_type TEXT,
1009
+ created_at TEXT DEFAULT (datetime('now'))
1010
+ );
1011
+
1012
+ -- Endless Mode Config table
1013
+ CREATE TABLE IF NOT EXISTS endless_config (
1014
+ key TEXT PRIMARY KEY,
1015
+ value TEXT,
1016
+ updated_at TEXT DEFAULT (datetime('now'))
1017
+ );
1018
+
1019
+ -- Sync position tracking (for SQLite -> DuckDB sync)
1020
+ CREATE TABLE IF NOT EXISTS sync_positions (
1021
+ target_name TEXT PRIMARY KEY,
1022
+ last_event_id TEXT,
1023
+ last_timestamp TEXT,
1024
+ updated_at TEXT DEFAULT (datetime('now'))
1025
+ );
1026
+
1027
+ -- Create indexes
1028
+ CREATE INDEX IF NOT EXISTS idx_events_session ON events(session_id);
1029
+ CREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp);
1030
+ CREATE INDEX IF NOT EXISTS idx_entries_type ON entries(entry_type);
1031
+ CREATE INDEX IF NOT EXISTS idx_entries_stage ON entries(stage);
1032
+ CREATE INDEX IF NOT EXISTS idx_entries_canonical ON entries(canonical_key);
1033
+ CREATE INDEX IF NOT EXISTS idx_entities_type_key ON entities(entity_type, canonical_key);
1034
+ CREATE INDEX IF NOT EXISTS idx_entities_status ON entities(status);
1035
+ CREATE INDEX IF NOT EXISTS idx_edges_src ON edges(src_id, rel_type);
1036
+ CREATE INDEX IF NOT EXISTS idx_edges_dst ON edges(dst_id, rel_type);
1037
+ CREATE INDEX IF NOT EXISTS idx_edges_rel ON edges(rel_type);
1038
+ CREATE INDEX IF NOT EXISTS idx_outbox_status ON vector_outbox(status);
1039
+ CREATE INDEX IF NOT EXISTS idx_working_set_expires ON working_set(expires_at);
1040
+ CREATE INDEX IF NOT EXISTS idx_working_set_relevance ON working_set(relevance_score);
1041
+ CREATE INDEX IF NOT EXISTS idx_consolidated_confidence ON consolidated_memories(confidence);
1042
+ CREATE INDEX IF NOT EXISTS idx_continuity_created ON continuity_log(created_at);
1043
+ CREATE INDEX IF NOT EXISTS idx_embedding_outbox_status ON embedding_outbox(status);
1044
+ `);
1045
+ const tableInfo = sqliteAll(this.db, "PRAGMA table_info(events)", []);
1046
+ const columnNames = tableInfo.map((col) => col.name);
1047
+ if (!columnNames.includes("access_count")) {
1048
+ try {
1049
+ sqliteExec(this.db, `
1050
+ ALTER TABLE events ADD COLUMN access_count INTEGER DEFAULT 0;
1051
+ `);
1052
+ } catch (err) {
1053
+ console.error("Error adding access_count column:", err);
1054
+ }
1055
+ }
1056
+ if (!columnNames.includes("last_accessed_at")) {
1057
+ try {
1058
+ sqliteExec(this.db, `
1059
+ ALTER TABLE events ADD COLUMN last_accessed_at TEXT;
1060
+ `);
1061
+ } catch (err) {
1062
+ console.error("Error adding last_accessed_at column:", err);
1063
+ }
1064
+ }
1065
+ try {
1066
+ sqliteExec(this.db, `
1067
+ CREATE INDEX IF NOT EXISTS idx_events_access_count ON events(access_count DESC);
1068
+ `);
1069
+ } catch (err) {
1070
+ }
1071
+ try {
1072
+ sqliteExec(this.db, `
1073
+ CREATE INDEX IF NOT EXISTS idx_events_last_accessed ON events(last_accessed_at DESC);
1074
+ `);
1075
+ } catch (err) {
1076
+ }
1077
+ this.initialized = true;
1078
+ }
1079
+ /**
1080
+ * Append event to store (Append-only, Idempotent)
1081
+ */
1082
+ async append(input) {
1083
+ await this.initialize();
1084
+ const canonicalKey = makeCanonicalKey(input.content);
1085
+ const dedupeKey = makeDedupeKey(input.content, input.sessionId);
1086
+ const existing = sqliteGet(
1087
+ this.db,
1088
+ `SELECT event_id FROM event_dedup WHERE dedupe_key = ?`,
1089
+ [dedupeKey]
1090
+ );
1091
+ if (existing) {
1092
+ return {
1093
+ success: true,
1094
+ eventId: existing.event_id,
1095
+ isDuplicate: true
1096
+ };
1097
+ }
1098
+ const id = randomUUID2();
1099
+ const timestamp = toSQLiteTimestamp(input.timestamp);
1100
+ try {
1101
+ const insertEvent = this.db.prepare(`
1102
+ INSERT INTO events (id, event_type, session_id, timestamp, content, canonical_key, dedupe_key, metadata)
1103
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
1104
+ `);
1105
+ const insertDedup = this.db.prepare(`
1106
+ INSERT INTO event_dedup (dedupe_key, event_id) VALUES (?, ?)
1107
+ `);
1108
+ const insertLevel = this.db.prepare(`
1109
+ INSERT INTO memory_levels (event_id, level) VALUES (?, 'L0')
1110
+ `);
1111
+ const transaction = this.db.transaction(() => {
1112
+ insertEvent.run(
1113
+ id,
1114
+ input.eventType,
1115
+ input.sessionId,
1116
+ timestamp,
1117
+ input.content,
1118
+ canonicalKey,
1119
+ dedupeKey,
1120
+ JSON.stringify(input.metadata || {})
1121
+ );
1122
+ insertDedup.run(dedupeKey, id);
1123
+ insertLevel.run(id);
1124
+ });
1125
+ transaction();
1126
+ return { success: true, eventId: id, isDuplicate: false };
1127
+ } catch (error) {
1128
+ return {
1129
+ success: false,
1130
+ error: error instanceof Error ? error.message : String(error)
1131
+ };
1132
+ }
1133
+ }
1134
+ /**
1135
+ * Get events by session ID
1136
+ */
1137
+ async getSessionEvents(sessionId) {
1138
+ await this.initialize();
1139
+ const rows = sqliteAll(
1140
+ this.db,
1141
+ `SELECT * FROM events WHERE session_id = ? ORDER BY timestamp ASC`,
1142
+ [sessionId]
1143
+ );
1144
+ return rows.map(this.rowToEvent);
1145
+ }
1146
+ /**
1147
+ * Get recent events
1148
+ */
1149
+ async getRecentEvents(limit = 100) {
1150
+ await this.initialize();
1151
+ const rows = sqliteAll(
1152
+ this.db,
1153
+ `SELECT * FROM events ORDER BY timestamp DESC LIMIT ?`,
1154
+ [limit]
1155
+ );
1156
+ return rows.map(this.rowToEvent);
1157
+ }
1158
+ /**
1159
+ * Get event by ID
1160
+ */
1161
+ async getEvent(id) {
1162
+ await this.initialize();
1163
+ const row = sqliteGet(
1164
+ this.db,
1165
+ `SELECT * FROM events WHERE id = ?`,
1166
+ [id]
1167
+ );
1168
+ if (!row)
1169
+ return null;
1170
+ return this.rowToEvent(row);
1171
+ }
1172
+ /**
1173
+ * Get events since a timestamp (for sync)
1174
+ */
1175
+ async getEventsSince(timestamp, limit = 1e3) {
1176
+ await this.initialize();
1177
+ const rows = sqliteAll(
1178
+ this.db,
1179
+ `SELECT * FROM events WHERE timestamp > ? ORDER BY timestamp ASC LIMIT ?`,
1180
+ [timestamp, limit]
1181
+ );
1182
+ return rows.map(this.rowToEvent);
1183
+ }
1184
+ /**
1185
+ * Create or update session
1186
+ */
1187
+ async upsertSession(session) {
1188
+ await this.initialize();
1189
+ const existing = sqliteGet(
1190
+ this.db,
1191
+ `SELECT id FROM sessions WHERE id = ?`,
1192
+ [session.id]
1193
+ );
1194
+ if (!existing) {
1195
+ sqliteRun(
1196
+ this.db,
1197
+ `INSERT INTO sessions (id, started_at, project_path, tags)
1198
+ VALUES (?, ?, ?, ?)`,
1199
+ [
1200
+ session.id,
1201
+ toSQLiteTimestamp(session.startedAt || /* @__PURE__ */ new Date()),
1202
+ session.projectPath || null,
1203
+ JSON.stringify(session.tags || [])
1204
+ ]
1205
+ );
1206
+ } else {
1207
+ const updates = [];
1208
+ const values = [];
1209
+ if (session.endedAt) {
1210
+ updates.push("ended_at = ?");
1211
+ values.push(toSQLiteTimestamp(session.endedAt));
1212
+ }
1213
+ if (session.summary) {
1214
+ updates.push("summary = ?");
1215
+ values.push(session.summary);
1216
+ }
1217
+ if (session.tags) {
1218
+ updates.push("tags = ?");
1219
+ values.push(JSON.stringify(session.tags));
1220
+ }
1221
+ if (updates.length > 0) {
1222
+ values.push(session.id);
1223
+ sqliteRun(
1224
+ this.db,
1225
+ `UPDATE sessions SET ${updates.join(", ")} WHERE id = ?`,
1226
+ values
1227
+ );
1228
+ }
1229
+ }
1230
+ }
1231
+ /**
1232
+ * Get session by ID
1233
+ */
1234
+ async getSession(id) {
1235
+ await this.initialize();
1236
+ const row = sqliteGet(
1237
+ this.db,
1238
+ `SELECT * FROM sessions WHERE id = ?`,
1239
+ [id]
1240
+ );
1241
+ if (!row)
1242
+ return null;
1243
+ return {
1244
+ id: row.id,
1245
+ startedAt: toDateFromSQLite(row.started_at),
1246
+ endedAt: row.ended_at ? toDateFromSQLite(row.ended_at) : void 0,
1247
+ projectPath: row.project_path,
1248
+ summary: row.summary,
1249
+ tags: row.tags ? JSON.parse(row.tags) : void 0
1250
+ };
1251
+ }
1252
+ /**
1253
+ * Get all sessions
1254
+ */
1255
+ async getAllSessions() {
1256
+ await this.initialize();
1257
+ const rows = sqliteAll(
1258
+ this.db,
1259
+ `SELECT * FROM sessions ORDER BY started_at DESC`
1260
+ );
1261
+ return rows.map((row) => ({
1262
+ id: row.id,
1263
+ startedAt: toDateFromSQLite(row.started_at),
1264
+ endedAt: row.ended_at ? toDateFromSQLite(row.ended_at) : void 0,
1265
+ projectPath: row.project_path,
1266
+ summary: row.summary,
1267
+ tags: row.tags ? JSON.parse(row.tags) : void 0
1268
+ }));
1269
+ }
1270
+ /**
1271
+ * Add to embedding outbox
1272
+ */
1273
+ async enqueueForEmbedding(eventId, content) {
1274
+ await this.initialize();
1275
+ const id = randomUUID2();
1276
+ sqliteRun(
1277
+ this.db,
1278
+ `INSERT INTO embedding_outbox (id, event_id, content, status, retry_count)
1279
+ VALUES (?, ?, ?, 'pending', 0)`,
1280
+ [id, eventId, content]
1281
+ );
1282
+ return id;
1283
+ }
1284
+ /**
1285
+ * Get pending outbox items
1286
+ */
1287
+ async getPendingOutboxItems(limit = 32) {
1288
+ await this.initialize();
1289
+ const pending = sqliteAll(
1290
+ this.db,
1291
+ `SELECT * FROM embedding_outbox
1292
+ WHERE status = 'pending'
1293
+ ORDER BY created_at
1294
+ LIMIT ?`,
1295
+ [limit]
1296
+ );
1297
+ if (pending.length === 0)
1298
+ return [];
1299
+ const ids = pending.map((r) => r.id);
1300
+ const placeholders = ids.map(() => "?").join(",");
1301
+ sqliteRun(
1302
+ this.db,
1303
+ `UPDATE embedding_outbox SET status = 'processing' WHERE id IN (${placeholders})`,
1304
+ ids
1305
+ );
1306
+ return pending.map((row) => ({
1307
+ id: row.id,
1308
+ eventId: row.event_id,
1309
+ content: row.content,
1310
+ status: "processing",
1311
+ retryCount: row.retry_count,
1312
+ createdAt: toDateFromSQLite(row.created_at),
1313
+ errorMessage: row.error_message
1314
+ }));
1315
+ }
1316
+ /**
1317
+ * Mark outbox items as done
1318
+ */
1319
+ async completeOutboxItems(ids) {
1320
+ if (ids.length === 0)
1321
+ return;
1322
+ const placeholders = ids.map(() => "?").join(",");
1323
+ sqliteRun(
1324
+ this.db,
1325
+ `DELETE FROM embedding_outbox WHERE id IN (${placeholders})`,
1326
+ ids
1327
+ );
1328
+ }
1329
+ /**
1330
+ * Mark outbox items as failed
1331
+ */
1332
+ async failOutboxItems(ids, error) {
1333
+ if (ids.length === 0)
1334
+ return;
1335
+ const placeholders = ids.map(() => "?").join(",");
1336
+ sqliteRun(
1337
+ this.db,
1338
+ `UPDATE embedding_outbox
1339
+ SET status = CASE WHEN retry_count >= 3 THEN 'failed' ELSE 'pending' END,
1340
+ retry_count = retry_count + 1,
1341
+ error_message = ?
1342
+ WHERE id IN (${placeholders})`,
1343
+ [error, ...ids]
1344
+ );
1345
+ }
1346
+ /**
1347
+ * Update memory level
1348
+ */
1349
+ async updateMemoryLevel(eventId, level) {
1350
+ await this.initialize();
1351
+ sqliteRun(
1352
+ this.db,
1353
+ `UPDATE memory_levels SET level = ?, promoted_at = datetime('now') WHERE event_id = ?`,
1354
+ [level, eventId]
1355
+ );
1356
+ }
1357
+ /**
1358
+ * Get memory level statistics
1359
+ */
1360
+ async getLevelStats() {
1361
+ await this.initialize();
1362
+ const rows = sqliteAll(
1363
+ this.db,
1364
+ `SELECT level, COUNT(*) as count FROM memory_levels GROUP BY level`
1365
+ );
1366
+ return rows;
1367
+ }
1368
+ /**
1369
+ * Get events by memory level
1370
+ */
1371
+ async getEventsByLevel(level, options) {
1372
+ await this.initialize();
1373
+ const limit = options?.limit || 50;
1374
+ const offset = options?.offset || 0;
1375
+ const rows = sqliteAll(
1376
+ this.db,
1377
+ `SELECT e.* FROM events e
1378
+ INNER JOIN memory_levels ml ON e.id = ml.event_id
1379
+ WHERE ml.level = ?
1380
+ ORDER BY e.timestamp DESC
1381
+ LIMIT ? OFFSET ?`,
1382
+ [level, limit, offset]
1383
+ );
1384
+ return rows.map((row) => this.rowToEvent(row));
1385
+ }
1386
+ /**
1387
+ * Get memory level for a specific event
1388
+ */
1389
+ async getEventLevel(eventId) {
1390
+ await this.initialize();
1391
+ const row = sqliteGet(
1392
+ this.db,
1393
+ `SELECT level FROM memory_levels WHERE event_id = ?`,
1394
+ [eventId]
1395
+ );
1396
+ return row ? row.level : null;
1397
+ }
1398
+ /**
1399
+ * Get sync position for a target
1400
+ */
1401
+ async getSyncPosition(targetName) {
1402
+ await this.initialize();
1403
+ const row = sqliteGet(
1404
+ this.db,
1405
+ `SELECT last_event_id, last_timestamp FROM sync_positions WHERE target_name = ?`,
1406
+ [targetName]
1407
+ );
1408
+ return {
1409
+ lastEventId: row?.last_event_id ?? null,
1410
+ lastTimestamp: row?.last_timestamp ?? null
1411
+ };
1412
+ }
1413
+ /**
1414
+ * Update sync position for a target
1415
+ */
1416
+ async updateSyncPosition(targetName, lastEventId, lastTimestamp) {
1417
+ await this.initialize();
1418
+ sqliteRun(
1419
+ this.db,
1420
+ `INSERT OR REPLACE INTO sync_positions (target_name, last_event_id, last_timestamp, updated_at)
1421
+ VALUES (?, ?, ?, datetime('now'))`,
1422
+ [targetName, lastEventId, lastTimestamp]
1423
+ );
1424
+ }
1425
+ /**
1426
+ * Get config value for endless mode
1427
+ */
1428
+ async getEndlessConfig(key) {
1429
+ await this.initialize();
1430
+ const row = sqliteGet(
1431
+ this.db,
1432
+ `SELECT value FROM endless_config WHERE key = ?`,
1433
+ [key]
1434
+ );
1435
+ if (!row)
1436
+ return null;
1437
+ return JSON.parse(row.value);
1438
+ }
1439
+ /**
1440
+ * Set config value for endless mode
1441
+ */
1442
+ async setEndlessConfig(key, value) {
1443
+ await this.initialize();
1444
+ sqliteRun(
1445
+ this.db,
1446
+ `INSERT OR REPLACE INTO endless_config (key, value, updated_at)
1447
+ VALUES (?, ?, datetime('now'))`,
1448
+ [key, JSON.stringify(value)]
1449
+ );
1450
+ }
1451
+ /**
1452
+ * Increment access count for events
1453
+ */
1454
+ async incrementAccessCount(eventIds) {
1455
+ if (eventIds.length === 0 || this.readOnly)
1456
+ return;
1457
+ await this.initialize();
1458
+ const placeholders = eventIds.map(() => "?").join(",");
1459
+ const currentTime = toSQLiteTimestamp(/* @__PURE__ */ new Date());
1460
+ sqliteRun(
1461
+ this.db,
1462
+ `UPDATE events
1463
+ SET access_count = access_count + 1,
1464
+ last_accessed_at = ?
1465
+ WHERE id IN (${placeholders})`,
1466
+ [currentTime, ...eventIds]
1467
+ );
1468
+ }
1469
+ /**
1470
+ * Get most accessed memories
1471
+ */
1472
+ async getMostAccessed(limit = 10) {
1473
+ await this.initialize();
1474
+ const rows = sqliteAll(
1475
+ this.db,
1476
+ `SELECT * FROM events
1477
+ WHERE access_count > 0
1478
+ ORDER BY access_count DESC, last_accessed_at DESC
1479
+ LIMIT ?`,
1480
+ [limit]
1481
+ );
1482
+ return rows.map((row) => this.rowToEvent(row));
1483
+ }
1484
+ /**
1485
+ * Get database instance for direct access
1486
+ */
1487
+ getDatabase() {
1488
+ return this.db;
1489
+ }
1490
+ /**
1491
+ * Close database connection
1492
+ */
1493
+ async close() {
1494
+ sqliteClose(this.db);
1495
+ }
1496
+ /**
1497
+ * Convert database row to MemoryEvent
1498
+ */
1499
+ rowToEvent(row) {
1500
+ const event = {
1501
+ id: row.id,
1502
+ eventType: row.event_type,
1503
+ sessionId: row.session_id,
1504
+ timestamp: toDateFromSQLite(row.timestamp),
1505
+ content: row.content,
1506
+ canonicalKey: row.canonical_key,
1507
+ dedupeKey: row.dedupe_key,
1508
+ metadata: row.metadata ? JSON.parse(row.metadata) : void 0
1509
+ };
1510
+ if (row.access_count !== void 0) {
1511
+ event.access_count = row.access_count;
1512
+ }
1513
+ if (row.last_accessed_at !== void 0) {
1514
+ event.last_accessed_at = row.last_accessed_at;
1515
+ }
1516
+ return event;
1517
+ }
1518
+ };
1519
+
1520
+ // src/core/sync-worker.ts
1521
+ var DEFAULT_CONFIG = {
1522
+ intervalMs: 3e4,
1523
+ batchSize: 500,
1524
+ maxRetries: 3,
1525
+ retryDelayMs: 5e3
1526
+ };
1527
+ var SyncWorker = class {
1528
+ constructor(sqliteStore, duckdbStore, config) {
1529
+ this.sqliteStore = sqliteStore;
1530
+ this.duckdbStore = duckdbStore;
1531
+ this.config = { ...DEFAULT_CONFIG, ...config };
1532
+ }
1533
+ config;
1534
+ intervalHandle = null;
1535
+ running = false;
1536
+ stats = {
1537
+ lastSyncAt: null,
1538
+ eventsSynced: 0,
1539
+ sessionsSynced: 0,
1540
+ errors: 0,
1541
+ status: "idle"
1542
+ };
1543
+ /**
1544
+ * Start the sync worker
1545
+ */
1546
+ start() {
1547
+ if (this.running)
1548
+ return;
1549
+ this.running = true;
1550
+ this.stats.status = "idle";
1551
+ this.syncNow().catch((err) => {
1552
+ console.error("[SyncWorker] Initial sync failed:", err);
1553
+ });
1554
+ this.intervalHandle = setInterval(() => {
1555
+ this.syncNow().catch((err) => {
1556
+ console.error("[SyncWorker] Periodic sync failed:", err);
1557
+ });
1558
+ }, this.config.intervalMs);
1559
+ }
1560
+ /**
1561
+ * Stop the sync worker
1562
+ */
1563
+ stop() {
1564
+ this.running = false;
1565
+ this.stats.status = "stopped";
1566
+ if (this.intervalHandle) {
1567
+ clearInterval(this.intervalHandle);
1568
+ this.intervalHandle = null;
1569
+ }
1570
+ }
1571
+ /**
1572
+ * Trigger immediate sync
1573
+ */
1574
+ async syncNow() {
1575
+ if (this.stats.status === "syncing") {
1576
+ return;
1577
+ }
1578
+ this.stats.status = "syncing";
1579
+ try {
1580
+ await this.syncEvents();
1581
+ await this.syncSessions();
1582
+ this.stats.lastSyncAt = /* @__PURE__ */ new Date();
1583
+ this.stats.status = "idle";
1584
+ } catch (error) {
1585
+ this.stats.errors++;
1586
+ this.stats.status = "error";
1587
+ throw error;
1588
+ }
1589
+ }
1590
+ /**
1591
+ * Sync events from SQLite to DuckDB
1592
+ */
1593
+ async syncEvents() {
1594
+ const targetName = "duckdb_analytics";
1595
+ const position = await this.sqliteStore.getSyncPosition(targetName);
1596
+ const lastTimestamp = position.lastTimestamp || "1970-01-01T00:00:00.000Z";
1597
+ let hasMore = true;
1598
+ let totalSynced = 0;
1599
+ while (hasMore) {
1600
+ const events = await this.sqliteStore.getEventsSince(lastTimestamp, this.config.batchSize);
1601
+ if (events.length === 0) {
1602
+ hasMore = false;
1603
+ break;
1604
+ }
1605
+ await this.retryWithBackoff(async () => {
1606
+ for (const event of events) {
1607
+ await this.insertEventToDuckDB(event);
1608
+ }
1609
+ });
1610
+ totalSynced += events.length;
1611
+ const lastEvent = events[events.length - 1];
1612
+ await this.sqliteStore.updateSyncPosition(
1613
+ targetName,
1614
+ lastEvent.id,
1615
+ lastEvent.timestamp.toISOString()
1616
+ );
1617
+ hasMore = events.length === this.config.batchSize;
1618
+ }
1619
+ this.stats.eventsSynced += totalSynced;
1620
+ }
1621
+ /**
1622
+ * Sync sessions from SQLite to DuckDB
1623
+ */
1624
+ async syncSessions() {
1625
+ const sessions = await this.sqliteStore.getAllSessions();
1626
+ for (const session of sessions) {
1627
+ await this.retryWithBackoff(async () => {
1628
+ await this.duckdbStore.upsertSession(session);
1629
+ });
1630
+ }
1631
+ this.stats.sessionsSynced = sessions.length;
1632
+ }
1633
+ /**
1634
+ * Insert a single event into DuckDB
1635
+ */
1636
+ async insertEventToDuckDB(event) {
1637
+ await this.duckdbStore.append({
1638
+ eventType: event.eventType,
1639
+ sessionId: event.sessionId,
1640
+ timestamp: event.timestamp,
1641
+ content: event.content,
1642
+ metadata: event.metadata
1643
+ });
1644
+ }
1645
+ /**
1646
+ * Retry operation with exponential backoff
1647
+ */
1648
+ async retryWithBackoff(fn) {
1649
+ let lastError = null;
1650
+ for (let attempt = 0; attempt < this.config.maxRetries; attempt++) {
1651
+ try {
1652
+ return await fn();
1653
+ } catch (error) {
1654
+ lastError = error instanceof Error ? error : new Error(String(error));
1655
+ if (attempt < this.config.maxRetries - 1) {
1656
+ const delay = this.config.retryDelayMs * Math.pow(2, attempt);
1657
+ await this.sleep(delay);
1658
+ }
1659
+ }
1660
+ }
1661
+ throw lastError;
1662
+ }
1663
+ /**
1664
+ * Sleep utility
1665
+ */
1666
+ sleep(ms) {
1667
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
1668
+ }
1669
+ /**
1670
+ * Get sync statistics
671
1671
  */
672
- async close() {
673
- await dbClose(this.db);
1672
+ getStats() {
1673
+ return { ...this.stats };
674
1674
  }
675
1675
  /**
676
- * Convert database row to MemoryEvent
1676
+ * Check if worker is running
677
1677
  */
678
- rowToEvent(row) {
679
- return {
680
- id: row.id,
681
- eventType: row.event_type,
682
- sessionId: row.session_id,
683
- timestamp: toDate(row.timestamp),
684
- content: row.content,
685
- canonicalKey: row.canonical_key,
686
- dedupeKey: row.dedupe_key,
687
- metadata: row.metadata ? JSON.parse(row.metadata) : void 0
688
- };
1678
+ isRunning() {
1679
+ return this.running;
689
1680
  }
690
1681
  };
691
1682
 
@@ -917,7 +1908,7 @@ function getDefaultEmbedder() {
917
1908
  }
918
1909
 
919
1910
  // src/core/vector-outbox.ts
920
- var DEFAULT_CONFIG = {
1911
+ var DEFAULT_CONFIG2 = {
921
1912
  embeddingVersion: "v1",
922
1913
  maxRetries: 3,
923
1914
  stuckThresholdMs: 5 * 60 * 1e3,
@@ -926,7 +1917,7 @@ var DEFAULT_CONFIG = {
926
1917
  };
927
1918
 
928
1919
  // src/core/vector-worker.ts
929
- var DEFAULT_CONFIG2 = {
1920
+ var DEFAULT_CONFIG3 = {
930
1921
  batchSize: 32,
931
1922
  pollIntervalMs: 1e3,
932
1923
  maxRetries: 3
@@ -937,12 +1928,13 @@ var VectorWorker = class {
937
1928
  embedder;
938
1929
  config;
939
1930
  running = false;
1931
+ stopping = false;
940
1932
  pollTimeout = null;
941
1933
  constructor(eventStore, vectorStore, embedder, config = {}) {
942
1934
  this.eventStore = eventStore;
943
1935
  this.vectorStore = vectorStore;
944
1936
  this.embedder = embedder;
945
- this.config = { ...DEFAULT_CONFIG2, ...config };
1937
+ this.config = { ...DEFAULT_CONFIG3, ...config };
946
1938
  }
947
1939
  /**
948
1940
  * Start the worker polling loop
@@ -951,6 +1943,7 @@ var VectorWorker = class {
951
1943
  if (this.running)
952
1944
  return;
953
1945
  this.running = true;
1946
+ this.stopping = false;
954
1947
  this.poll();
955
1948
  }
956
1949
  /**
@@ -958,6 +1951,7 @@ var VectorWorker = class {
958
1951
  */
959
1952
  stop() {
960
1953
  this.running = false;
1954
+ this.stopping = true;
961
1955
  if (this.pollTimeout) {
962
1956
  clearTimeout(this.pollTimeout);
963
1957
  this.pollTimeout = null;
@@ -1007,9 +2001,15 @@ var VectorWorker = class {
1007
2001
  }
1008
2002
  return successful.length;
1009
2003
  } catch (error) {
1010
- const allIds = items.map((i) => i.id);
1011
- const errorMessage = error instanceof Error ? error.message : String(error);
1012
- await this.eventStore.failOutboxItems(allIds, errorMessage);
2004
+ if (!this.stopping) {
2005
+ try {
2006
+ const allIds = items.map((i) => i.id);
2007
+ const errorMessage = error instanceof Error ? error.message : String(error);
2008
+ await this.eventStore.failOutboxItems(allIds, errorMessage);
2009
+ } catch (failError) {
2010
+ console.warn("Could not mark outbox items as failed (database may be closed)");
2011
+ }
2012
+ }
1013
2013
  throw error;
1014
2014
  }
1015
2015
  }
@@ -1017,14 +2017,18 @@ var VectorWorker = class {
1017
2017
  * Poll for new items
1018
2018
  */
1019
2019
  async poll() {
1020
- if (!this.running)
2020
+ if (!this.running || this.stopping)
1021
2021
  return;
1022
2022
  try {
1023
2023
  await this.processBatch();
1024
2024
  } catch (error) {
1025
- console.error("Vector worker error:", error);
2025
+ if (!this.stopping) {
2026
+ console.error("Vector worker error:", error);
2027
+ }
2028
+ }
2029
+ if (this.running && !this.stopping) {
2030
+ this.pollTimeout = setTimeout(() => this.poll(), this.config.pollIntervalMs);
1026
2031
  }
1027
- this.pollTimeout = setTimeout(() => this.poll(), this.config.pollIntervalMs);
1028
2032
  }
1029
2033
  /**
1030
2034
  * Process all pending items (blocking)
@@ -1051,7 +2055,7 @@ function createVectorWorker(eventStore, vectorStore, embedder, config) {
1051
2055
  }
1052
2056
 
1053
2057
  // src/core/matcher.ts
1054
- var DEFAULT_CONFIG3 = {
2058
+ var DEFAULT_CONFIG4 = {
1055
2059
  weights: {
1056
2060
  semanticSimilarity: 0.4,
1057
2061
  ftsScore: 0.25,
@@ -1066,9 +2070,9 @@ var Matcher = class {
1066
2070
  config;
1067
2071
  constructor(config = {}) {
1068
2072
  this.config = {
1069
- ...DEFAULT_CONFIG3,
2073
+ ...DEFAULT_CONFIG4,
1070
2074
  ...config,
1071
- weights: { ...DEFAULT_CONFIG3.weights, ...config.weights }
2075
+ weights: { ...DEFAULT_CONFIG4.weights, ...config.weights }
1072
2076
  };
1073
2077
  }
1074
2078
  /**
@@ -1195,6 +2199,7 @@ var Retriever = class {
1195
2199
  matcher;
1196
2200
  sharedStore;
1197
2201
  sharedVectorStore;
2202
+ graduation;
1198
2203
  constructor(eventStore, vectorStore, embedder, matcher, sharedOptions) {
1199
2204
  this.eventStore = eventStore;
1200
2205
  this.vectorStore = vectorStore;
@@ -1203,6 +2208,12 @@ var Retriever = class {
1203
2208
  this.sharedStore = sharedOptions?.sharedStore;
1204
2209
  this.sharedVectorStore = sharedOptions?.sharedVectorStore;
1205
2210
  }
2211
+ /**
2212
+ * Set graduation pipeline for access tracking
2213
+ */
2214
+ setGraduationPipeline(graduation) {
2215
+ this.graduation = graduation;
2216
+ }
1206
2217
  /**
1207
2218
  * Set shared stores after construction
1208
2219
  */
@@ -1325,6 +2336,13 @@ var Retriever = class {
1325
2336
  const event = await this.eventStore.getEvent(result.eventId);
1326
2337
  if (!event)
1327
2338
  continue;
2339
+ if (this.graduation) {
2340
+ this.graduation.recordAccess(
2341
+ event.id,
2342
+ options.sessionId || "unknown",
2343
+ result.score
2344
+ );
2345
+ }
1328
2346
  let sessionContext;
1329
2347
  if (options.includeSessionContext) {
1330
2348
  sessionContext = await this.getSessionContext(event.sessionId, event.id);
@@ -1446,15 +2464,26 @@ var GraduationPipeline = class {
1446
2464
  L3toL4: { ...DEFAULT_CRITERIA.L3toL4, ...criteria.L3toL4 }
1447
2465
  };
1448
2466
  }
2467
+ // Track which sessions have accessed each event
2468
+ sessionAccesses = /* @__PURE__ */ new Map();
1449
2469
  /**
1450
2470
  * Record an access to an event (used for graduation scoring)
1451
2471
  */
1452
2472
  recordAccess(eventId, fromSessionId, confidence = 1) {
1453
2473
  const existing = this.metrics.get(eventId);
2474
+ if (!this.sessionAccesses.has(eventId)) {
2475
+ this.sessionAccesses.set(eventId, /* @__PURE__ */ new Set());
2476
+ }
2477
+ const sessions = this.sessionAccesses.get(eventId);
2478
+ const isNewSession = !sessions.has(fromSessionId);
2479
+ sessions.add(fromSessionId);
1454
2480
  if (existing) {
1455
2481
  existing.accessCount++;
1456
2482
  existing.lastAccessed = /* @__PURE__ */ new Date();
1457
2483
  existing.confidence = Math.max(existing.confidence, confidence);
2484
+ if (isNewSession && sessions.size > 1) {
2485
+ existing.crossSessionRefs = sessions.size - 1;
2486
+ }
1458
2487
  } else {
1459
2488
  this.metrics.set(eventId, {
1460
2489
  eventId,
@@ -1751,7 +2780,7 @@ function createSharedEventStore(dbPath) {
1751
2780
  }
1752
2781
 
1753
2782
  // src/core/shared-store.ts
1754
- import { randomUUID as randomUUID2 } from "crypto";
2783
+ import { randomUUID as randomUUID3 } from "crypto";
1755
2784
  var SharedStore = class {
1756
2785
  constructor(sharedEventStore) {
1757
2786
  this.sharedEventStore = sharedEventStore;
@@ -1763,7 +2792,7 @@ var SharedStore = class {
1763
2792
  * Promote a verified troubleshooting entry to shared storage
1764
2793
  */
1765
2794
  async promoteEntry(input) {
1766
- const entryId = randomUUID2();
2795
+ const entryId = randomUUID3();
1767
2796
  await dbRun(
1768
2797
  this.db,
1769
2798
  `INSERT INTO shared_troubleshooting (
@@ -2128,7 +3157,7 @@ function createSharedVectorStore(dbPath) {
2128
3157
  }
2129
3158
 
2130
3159
  // src/core/shared-promoter.ts
2131
- import { randomUUID as randomUUID3 } from "crypto";
3160
+ import { randomUUID as randomUUID4 } from "crypto";
2132
3161
  var SharedPromoter = class {
2133
3162
  constructor(sharedStore, sharedVectorStore, embedder, config) {
2134
3163
  this.sharedStore = sharedStore;
@@ -2196,7 +3225,7 @@ var SharedPromoter = class {
2196
3225
  const embeddingContent = this.createEmbeddingContent(input);
2197
3226
  const embedding = await this.embedder.embed(embeddingContent);
2198
3227
  await this.sharedVectorStore.upsert({
2199
- id: randomUUID3(),
3228
+ id: randomUUID4(),
2200
3229
  entryId,
2201
3230
  entryType: "troubleshooting",
2202
3231
  content: embeddingContent,
@@ -2336,7 +3365,7 @@ function createToolObservationEmbedding(toolName, metadata, success) {
2336
3365
  }
2337
3366
 
2338
3367
  // src/core/working-set-store.ts
2339
- import { randomUUID as randomUUID4 } from "crypto";
3368
+ import { randomUUID as randomUUID5 } from "crypto";
2340
3369
  var WorkingSetStore = class {
2341
3370
  constructor(eventStore, config) {
2342
3371
  this.eventStore = eventStore;
@@ -2357,7 +3386,7 @@ var WorkingSetStore = class {
2357
3386
  `INSERT OR REPLACE INTO working_set (id, event_id, added_at, relevance_score, topics, expires_at)
2358
3387
  VALUES (?, ?, CURRENT_TIMESTAMP, ?, ?, ?)`,
2359
3388
  [
2360
- randomUUID4(),
3389
+ randomUUID5(),
2361
3390
  eventId,
2362
3391
  relevanceScore,
2363
3392
  JSON.stringify(topics || []),
@@ -2541,7 +3570,7 @@ function createWorkingSetStore(eventStore, config) {
2541
3570
  }
2542
3571
 
2543
3572
  // src/core/consolidated-store.ts
2544
- import { randomUUID as randomUUID5 } from "crypto";
3573
+ import { randomUUID as randomUUID6 } from "crypto";
2545
3574
  var ConsolidatedStore = class {
2546
3575
  constructor(eventStore) {
2547
3576
  this.eventStore = eventStore;
@@ -2553,7 +3582,7 @@ var ConsolidatedStore = class {
2553
3582
  * Create a new consolidated memory
2554
3583
  */
2555
3584
  async create(input) {
2556
- const memoryId = randomUUID5();
3585
+ const memoryId = randomUUID6();
2557
3586
  await dbRun(
2558
3587
  this.db,
2559
3588
  `INSERT INTO consolidated_memories
@@ -3080,7 +4109,7 @@ function createConsolidationWorker(workingSetStore, consolidatedStore, config) {
3080
4109
  }
3081
4110
 
3082
4111
  // src/core/continuity-manager.ts
3083
- import { randomUUID as randomUUID6 } from "crypto";
4112
+ import { randomUUID as randomUUID7 } from "crypto";
3084
4113
  var ContinuityManager = class {
3085
4114
  constructor(eventStore, config) {
3086
4115
  this.eventStore = eventStore;
@@ -3234,7 +4263,7 @@ var ContinuityManager = class {
3234
4263
  `INSERT INTO continuity_log
3235
4264
  (log_id, from_context_id, to_context_id, continuity_score, transition_type, created_at)
3236
4265
  VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`,
3237
- [randomUUID6(), previous.id, current.id, score, type]
4266
+ [randomUUID7(), previous.id, current.id, score, type]
3238
4267
  );
3239
4268
  }
3240
4269
  /**
@@ -3342,6 +4371,127 @@ function createContinuityManager(eventStore, config) {
3342
4371
  return new ContinuityManager(eventStore, config);
3343
4372
  }
3344
4373
 
4374
+ // src/core/graduation-worker.ts
4375
+ var DEFAULT_CONFIG5 = {
4376
+ evaluationIntervalMs: 3e5,
4377
+ // 5 minutes
4378
+ batchSize: 50,
4379
+ cooldownMs: 36e5
4380
+ // 1 hour cooldown between evaluations
4381
+ };
4382
+ var GraduationWorker = class {
4383
+ constructor(eventStore, graduation, config = DEFAULT_CONFIG5) {
4384
+ this.eventStore = eventStore;
4385
+ this.graduation = graduation;
4386
+ this.config = config;
4387
+ }
4388
+ running = false;
4389
+ timeout = null;
4390
+ lastEvaluated = /* @__PURE__ */ new Map();
4391
+ /**
4392
+ * Start the graduation worker
4393
+ */
4394
+ start() {
4395
+ if (this.running)
4396
+ return;
4397
+ this.running = true;
4398
+ this.scheduleNext();
4399
+ }
4400
+ /**
4401
+ * Stop the graduation worker
4402
+ */
4403
+ stop() {
4404
+ this.running = false;
4405
+ if (this.timeout) {
4406
+ clearTimeout(this.timeout);
4407
+ this.timeout = null;
4408
+ }
4409
+ }
4410
+ /**
4411
+ * Check if currently running
4412
+ */
4413
+ isRunning() {
4414
+ return this.running;
4415
+ }
4416
+ /**
4417
+ * Force a graduation evaluation run
4418
+ */
4419
+ async forceRun() {
4420
+ return await this.runGraduation();
4421
+ }
4422
+ /**
4423
+ * Schedule the next graduation check
4424
+ */
4425
+ scheduleNext() {
4426
+ if (!this.running)
4427
+ return;
4428
+ this.timeout = setTimeout(
4429
+ () => this.run(),
4430
+ this.config.evaluationIntervalMs
4431
+ );
4432
+ }
4433
+ /**
4434
+ * Run graduation evaluation
4435
+ */
4436
+ async run() {
4437
+ if (!this.running)
4438
+ return;
4439
+ try {
4440
+ await this.runGraduation();
4441
+ } catch (error) {
4442
+ console.error("Graduation error:", error);
4443
+ }
4444
+ this.scheduleNext();
4445
+ }
4446
+ /**
4447
+ * Perform graduation evaluation across all levels
4448
+ */
4449
+ async runGraduation() {
4450
+ const result = {
4451
+ evaluated: 0,
4452
+ graduated: 0,
4453
+ byLevel: {}
4454
+ };
4455
+ const levels = ["L0", "L1", "L2", "L3"];
4456
+ const now = Date.now();
4457
+ for (const level of levels) {
4458
+ const events = await this.eventStore.getEventsByLevel(level, {
4459
+ limit: this.config.batchSize
4460
+ });
4461
+ let levelGraduated = 0;
4462
+ for (const event of events) {
4463
+ const lastEval = this.lastEvaluated.get(event.id);
4464
+ if (lastEval && now - lastEval < this.config.cooldownMs) {
4465
+ continue;
4466
+ }
4467
+ result.evaluated++;
4468
+ this.lastEvaluated.set(event.id, now);
4469
+ const gradResult = await this.graduation.evaluateGraduation(event.id, level);
4470
+ if (gradResult.success) {
4471
+ result.graduated++;
4472
+ levelGraduated++;
4473
+ }
4474
+ }
4475
+ if (levelGraduated > 0) {
4476
+ result.byLevel[level] = levelGraduated;
4477
+ }
4478
+ }
4479
+ if (this.lastEvaluated.size > 1e3) {
4480
+ const entries = Array.from(this.lastEvaluated.entries());
4481
+ entries.sort((a, b) => b[1] - a[1]);
4482
+ this.lastEvaluated = new Map(entries.slice(0, 1e3));
4483
+ }
4484
+ return result;
4485
+ }
4486
+ };
4487
+ function createGraduationWorker(eventStore, graduation, config) {
4488
+ return new GraduationWorker(
4489
+ eventStore,
4490
+ graduation,
4491
+ { ...DEFAULT_CONFIG5, ...config }
4492
+ );
4493
+ }
4494
+
3345
4495
  // src/services/memory-service.ts
3346
4496
  function normalizePath(projectPath) {
3347
4497
  const expanded = projectPath.startsWith("~") ? path.join(os.homedir(), projectPath.slice(1)) : projectPath;
@@ -3362,13 +4512,18 @@ function getProjectStoragePath(projectPath) {
3362
4512
  var REGISTRY_PATH = path.join(os.homedir(), ".claude-code", "memory", "session-registry.json");
3363
4513
  var SHARED_STORAGE_PATH = path.join(os.homedir(), ".claude-code", "memory", "shared");
3364
4514
  var MemoryService = class {
3365
- eventStore;
4515
+ // Primary store: SQLite (WAL mode) - for hooks, always available
4516
+ sqliteStore;
4517
+ // Analytics store: DuckDB - for server reads (optional, synced from SQLite)
4518
+ analyticsStore;
4519
+ syncWorker = null;
3366
4520
  vectorStore;
3367
4521
  embedder;
3368
4522
  matcher;
3369
4523
  retriever;
3370
4524
  graduation;
3371
4525
  vectorWorker = null;
4526
+ graduationWorker = null;
3372
4527
  initialized = false;
3373
4528
  // Endless Mode components
3374
4529
  workingSetStore = null;
@@ -3383,24 +4538,48 @@ var MemoryService = class {
3383
4538
  sharedPromoter = null;
3384
4539
  sharedStoreConfig = null;
3385
4540
  projectHash = null;
4541
+ readOnly;
3386
4542
  constructor(config) {
3387
4543
  const storagePath = this.expandPath(config.storagePath);
3388
- if (!fs.existsSync(storagePath)) {
4544
+ this.readOnly = config.readOnly ?? false;
4545
+ if (!this.readOnly && !fs.existsSync(storagePath)) {
3389
4546
  fs.mkdirSync(storagePath, { recursive: true });
3390
4547
  }
3391
4548
  this.projectHash = config.projectHash || null;
3392
4549
  this.sharedStoreConfig = config.sharedStoreConfig ?? { enabled: true };
3393
- this.eventStore = new EventStore(path.join(storagePath, "events.duckdb"));
4550
+ this.sqliteStore = new SQLiteEventStore(
4551
+ path.join(storagePath, "events.sqlite"),
4552
+ { readonly: this.readOnly }
4553
+ );
4554
+ const analyticsEnabled = config.analyticsEnabled ?? this.readOnly;
4555
+ if (!analyticsEnabled) {
4556
+ this.analyticsStore = null;
4557
+ } else if (this.readOnly) {
4558
+ try {
4559
+ this.analyticsStore = new EventStore(
4560
+ path.join(storagePath, "analytics.duckdb"),
4561
+ { readOnly: true }
4562
+ );
4563
+ } catch {
4564
+ this.analyticsStore = null;
4565
+ }
4566
+ } else {
4567
+ this.analyticsStore = new EventStore(
4568
+ path.join(storagePath, "analytics.duckdb"),
4569
+ { readOnly: false }
4570
+ );
4571
+ }
3394
4572
  this.vectorStore = new VectorStore(path.join(storagePath, "vectors"));
3395
4573
  this.embedder = config.embeddingModel ? new Embedder(config.embeddingModel) : getDefaultEmbedder();
3396
4574
  this.matcher = getDefaultMatcher();
3397
4575
  this.retriever = createRetriever(
3398
- this.eventStore,
4576
+ this.sqliteStore,
4577
+ // Interface compatible
3399
4578
  this.vectorStore,
3400
4579
  this.embedder,
3401
4580
  this.matcher
3402
4581
  );
3403
- this.graduation = createGraduationPipeline(this.eventStore);
4582
+ this.graduation = createGraduationPipeline(this.sqliteStore);
3404
4583
  }
3405
4584
  /**
3406
4585
  * Initialize all components
@@ -3408,22 +4587,45 @@ var MemoryService = class {
3408
4587
  async initialize() {
3409
4588
  if (this.initialized)
3410
4589
  return;
3411
- await this.eventStore.initialize();
4590
+ await this.sqliteStore.initialize();
4591
+ if (this.analyticsStore) {
4592
+ try {
4593
+ await this.analyticsStore.initialize();
4594
+ } catch (error) {
4595
+ console.warn("[MemoryService] Analytics store (DuckDB) initialization failed, using SQLite for reads:", error);
4596
+ }
4597
+ }
3412
4598
  await this.vectorStore.initialize();
3413
4599
  await this.embedder.initialize();
3414
- this.vectorWorker = createVectorWorker(
3415
- this.eventStore,
3416
- this.vectorStore,
3417
- this.embedder
3418
- );
3419
- this.vectorWorker.start();
3420
- const savedMode = await this.eventStore.getEndlessConfig("mode");
3421
- if (savedMode === "endless") {
3422
- this.endlessMode = "endless";
3423
- await this.initializeEndlessMode();
3424
- }
3425
- if (this.sharedStoreConfig?.enabled !== false) {
3426
- await this.initializeSharedStore();
4600
+ if (!this.readOnly) {
4601
+ this.vectorWorker = createVectorWorker(
4602
+ this.sqliteStore,
4603
+ this.vectorStore,
4604
+ this.embedder
4605
+ );
4606
+ this.vectorWorker.start();
4607
+ this.retriever.setGraduationPipeline(this.graduation);
4608
+ this.graduationWorker = createGraduationWorker(
4609
+ this.sqliteStore,
4610
+ this.graduation
4611
+ );
4612
+ this.graduationWorker.start();
4613
+ if (this.analyticsStore) {
4614
+ this.syncWorker = new SyncWorker(
4615
+ this.sqliteStore,
4616
+ this.analyticsStore,
4617
+ { intervalMs: 3e4, batchSize: 500 }
4618
+ );
4619
+ this.syncWorker.start();
4620
+ }
4621
+ const savedMode = await this.sqliteStore.getEndlessConfig("mode");
4622
+ if (savedMode === "endless") {
4623
+ this.endlessMode = "endless";
4624
+ await this.initializeEndlessMode();
4625
+ }
4626
+ if (this.sharedStoreConfig?.enabled !== false) {
4627
+ await this.initializeSharedStore();
4628
+ }
3427
4629
  }
3428
4630
  this.initialized = true;
3429
4631
  }
@@ -3457,7 +4659,7 @@ var MemoryService = class {
3457
4659
  */
3458
4660
  async startSession(sessionId, projectPath) {
3459
4661
  await this.initialize();
3460
- await this.eventStore.upsertSession({
4662
+ await this.sqliteStore.upsertSession({
3461
4663
  id: sessionId,
3462
4664
  startedAt: /* @__PURE__ */ new Date(),
3463
4665
  projectPath
@@ -3468,7 +4670,7 @@ var MemoryService = class {
3468
4670
  */
3469
4671
  async endSession(sessionId, summary) {
3470
4672
  await this.initialize();
3471
- await this.eventStore.upsertSession({
4673
+ await this.sqliteStore.upsertSession({
3472
4674
  id: sessionId,
3473
4675
  endedAt: /* @__PURE__ */ new Date(),
3474
4676
  summary
@@ -3479,7 +4681,7 @@ var MemoryService = class {
3479
4681
  */
3480
4682
  async storeUserPrompt(sessionId, content, metadata) {
3481
4683
  await this.initialize();
3482
- const result = await this.eventStore.append({
4684
+ const result = await this.sqliteStore.append({
3483
4685
  eventType: "user_prompt",
3484
4686
  sessionId,
3485
4687
  timestamp: /* @__PURE__ */ new Date(),
@@ -3487,7 +4689,7 @@ var MemoryService = class {
3487
4689
  metadata
3488
4690
  });
3489
4691
  if (result.success && !result.isDuplicate) {
3490
- await this.eventStore.enqueueForEmbedding(result.eventId, content);
4692
+ await this.sqliteStore.enqueueForEmbedding(result.eventId, content);
3491
4693
  }
3492
4694
  return result;
3493
4695
  }
@@ -3496,7 +4698,7 @@ var MemoryService = class {
3496
4698
  */
3497
4699
  async storeAgentResponse(sessionId, content, metadata) {
3498
4700
  await this.initialize();
3499
- const result = await this.eventStore.append({
4701
+ const result = await this.sqliteStore.append({
3500
4702
  eventType: "agent_response",
3501
4703
  sessionId,
3502
4704
  timestamp: /* @__PURE__ */ new Date(),
@@ -3504,7 +4706,7 @@ var MemoryService = class {
3504
4706
  metadata
3505
4707
  });
3506
4708
  if (result.success && !result.isDuplicate) {
3507
- await this.eventStore.enqueueForEmbedding(result.eventId, content);
4709
+ await this.sqliteStore.enqueueForEmbedding(result.eventId, content);
3508
4710
  }
3509
4711
  return result;
3510
4712
  }
@@ -3513,14 +4715,14 @@ var MemoryService = class {
3513
4715
  */
3514
4716
  async storeSessionSummary(sessionId, summary) {
3515
4717
  await this.initialize();
3516
- const result = await this.eventStore.append({
4718
+ const result = await this.sqliteStore.append({
3517
4719
  eventType: "session_summary",
3518
4720
  sessionId,
3519
4721
  timestamp: /* @__PURE__ */ new Date(),
3520
4722
  content: summary
3521
4723
  });
3522
4724
  if (result.success && !result.isDuplicate) {
3523
- await this.eventStore.enqueueForEmbedding(result.eventId, summary);
4725
+ await this.sqliteStore.enqueueForEmbedding(result.eventId, summary);
3524
4726
  }
3525
4727
  return result;
3526
4728
  }
@@ -3530,7 +4732,7 @@ var MemoryService = class {
3530
4732
  async storeToolObservation(sessionId, payload) {
3531
4733
  await this.initialize();
3532
4734
  const content = JSON.stringify(payload);
3533
- const result = await this.eventStore.append({
4735
+ const result = await this.sqliteStore.append({
3534
4736
  eventType: "tool_observation",
3535
4737
  sessionId,
3536
4738
  timestamp: /* @__PURE__ */ new Date(),
@@ -3546,7 +4748,7 @@ var MemoryService = class {
3546
4748
  payload.metadata || {},
3547
4749
  payload.success
3548
4750
  );
3549
- await this.eventStore.enqueueForEmbedding(result.eventId, embeddingContent);
4751
+ await this.sqliteStore.enqueueForEmbedding(result.eventId, embeddingContent);
3550
4752
  }
3551
4753
  return result;
3552
4754
  }
@@ -3572,21 +4774,21 @@ var MemoryService = class {
3572
4774
  */
3573
4775
  async getSessionHistory(sessionId) {
3574
4776
  await this.initialize();
3575
- return this.eventStore.getSessionEvents(sessionId);
4777
+ return this.sqliteStore.getSessionEvents(sessionId);
3576
4778
  }
3577
4779
  /**
3578
4780
  * Get recent events
3579
4781
  */
3580
4782
  async getRecentEvents(limit = 100) {
3581
4783
  await this.initialize();
3582
- return this.eventStore.getRecentEvents(limit);
4784
+ return this.sqliteStore.getRecentEvents(limit);
3583
4785
  }
3584
4786
  /**
3585
4787
  * Get memory statistics
3586
4788
  */
3587
4789
  async getStats() {
3588
4790
  await this.initialize();
3589
- const recentEvents = await this.eventStore.getRecentEvents(1e4);
4791
+ const recentEvents = await this.sqliteStore.getRecentEvents(1e4);
3590
4792
  const vectorCount = await this.vectorStore.count();
3591
4793
  const levelStats = await this.graduation.getStats();
3592
4794
  return {
@@ -3604,6 +4806,20 @@ var MemoryService = class {
3604
4806
  }
3605
4807
  return 0;
3606
4808
  }
4809
+ /**
4810
+ * Get events by memory level
4811
+ */
4812
+ async getEventsByLevel(level, options) {
4813
+ await this.initialize();
4814
+ return this.sqliteStore.getEventsByLevel(level, options);
4815
+ }
4816
+ /**
4817
+ * Get memory level for a specific event
4818
+ */
4819
+ async getEventLevel(eventId) {
4820
+ await this.initialize();
4821
+ return this.sqliteStore.getEventLevel(eventId);
4822
+ }
3607
4823
  /**
3608
4824
  * Format retrieval results as context for Claude
3609
4825
  */
@@ -3696,21 +4912,21 @@ var MemoryService = class {
3696
4912
  */
3697
4913
  async initializeEndlessMode() {
3698
4914
  const config = await this.getEndlessConfig();
3699
- this.workingSetStore = createWorkingSetStore(this.eventStore, config);
3700
- this.consolidatedStore = createConsolidatedStore(this.eventStore);
4915
+ this.workingSetStore = createWorkingSetStore(this.sqliteStore, config);
4916
+ this.consolidatedStore = createConsolidatedStore(this.sqliteStore);
3701
4917
  this.consolidationWorker = createConsolidationWorker(
3702
4918
  this.workingSetStore,
3703
4919
  this.consolidatedStore,
3704
4920
  config
3705
4921
  );
3706
- this.continuityManager = createContinuityManager(this.eventStore, config);
4922
+ this.continuityManager = createContinuityManager(this.sqliteStore, config);
3707
4923
  this.consolidationWorker.start();
3708
4924
  }
3709
4925
  /**
3710
4926
  * Get Endless Mode configuration
3711
4927
  */
3712
4928
  async getEndlessConfig() {
3713
- const savedConfig = await this.eventStore.getEndlessConfig("config");
4929
+ const savedConfig = await this.sqliteStore.getEndlessConfig("config");
3714
4930
  return savedConfig || this.getDefaultEndlessConfig();
3715
4931
  }
3716
4932
  /**
@@ -3719,7 +4935,7 @@ var MemoryService = class {
3719
4935
  async setEndlessConfig(config) {
3720
4936
  const current = await this.getEndlessConfig();
3721
4937
  const merged = { ...current, ...config };
3722
- await this.eventStore.setEndlessConfig("config", merged);
4938
+ await this.sqliteStore.setEndlessConfig("config", merged);
3723
4939
  }
3724
4940
  /**
3725
4941
  * Set memory mode (session or endless)
@@ -3729,7 +4945,7 @@ var MemoryService = class {
3729
4945
  if (mode === this.endlessMode)
3730
4946
  return;
3731
4947
  this.endlessMode = mode;
3732
- await this.eventStore.setEndlessConfig("mode", mode);
4948
+ await this.sqliteStore.setEndlessConfig("mode", mode);
3733
4949
  if (mode === "endless") {
3734
4950
  await this.initializeEndlessMode();
3735
4951
  } else {
@@ -3786,6 +5002,59 @@ var MemoryService = class {
3786
5002
  return [];
3787
5003
  return this.consolidatedStore.getAll({ limit });
3788
5004
  }
5005
+ /**
5006
+ * Increment access count for memories that were used in prompts
5007
+ */
5008
+ async incrementMemoryAccess(eventIds) {
5009
+ if (eventIds.length === 0)
5010
+ return;
5011
+ if (this.sqliteStore) {
5012
+ await this.sqliteStore.incrementAccessCount(eventIds);
5013
+ } else if (this.eventStore) {
5014
+ await this.eventStore.incrementAccessCount(eventIds);
5015
+ }
5016
+ }
5017
+ /**
5018
+ * Get most accessed memories from events
5019
+ */
5020
+ async getMostAccessedMemories(limit = 10) {
5021
+ console.log("[getMostAccessedMemories] sqliteStore available:", !!this.sqliteStore);
5022
+ if (this.sqliteStore) {
5023
+ const events = await this.sqliteStore.getMostAccessed(limit);
5024
+ console.log("[getMostAccessedMemories] Got events from SQLite:", events.length);
5025
+ return events.map((event) => ({
5026
+ memoryId: event.id,
5027
+ summary: event.content.substring(0, 200) + (event.content.length > 200 ? "..." : ""),
5028
+ topics: [],
5029
+ // Could extract topics from content if needed
5030
+ accessCount: event.access_count || 0,
5031
+ lastAccessed: event.last_accessed_at || null,
5032
+ confidence: 1,
5033
+ createdAt: event.timestamp
5034
+ }));
5035
+ }
5036
+ if (this.consolidatedStore) {
5037
+ const consolidated = await this.consolidatedStore.getMostAccessed(limit);
5038
+ return consolidated.map((m) => ({
5039
+ memoryId: m.memoryId,
5040
+ summary: m.summary,
5041
+ topics: m.topics,
5042
+ accessCount: m.accessCount,
5043
+ lastAccessed: m.accessedAt,
5044
+ confidence: m.confidence,
5045
+ createdAt: m.createdAt
5046
+ }));
5047
+ }
5048
+ return [];
5049
+ }
5050
+ /**
5051
+ * Mark a consolidated memory as accessed
5052
+ */
5053
+ async markMemoryAccessed(memoryId) {
5054
+ if (!this.consolidatedStore)
5055
+ return;
5056
+ await this.consolidatedStore.markAccessed(memoryId);
5057
+ }
3789
5058
  /**
3790
5059
  * Calculate continuity score for current context
3791
5060
  */
@@ -3873,20 +5142,44 @@ var MemoryService = class {
3873
5142
  }
3874
5143
  return parts.join("\n");
3875
5144
  }
5145
+ /**
5146
+ * Force a graduation evaluation run
5147
+ */
5148
+ async forceGraduation() {
5149
+ if (!this.graduationWorker) {
5150
+ return { evaluated: 0, graduated: 0, byLevel: {} };
5151
+ }
5152
+ return this.graduationWorker.forceRun();
5153
+ }
5154
+ /**
5155
+ * Record access to a memory event (for graduation scoring)
5156
+ */
5157
+ recordMemoryAccess(eventId, sessionId, confidence = 1) {
5158
+ this.graduation.recordAccess(eventId, sessionId, confidence);
5159
+ }
3876
5160
  /**
3877
5161
  * Shutdown service
3878
5162
  */
3879
5163
  async shutdown() {
5164
+ if (this.graduationWorker) {
5165
+ this.graduationWorker.stop();
5166
+ }
3880
5167
  if (this.consolidationWorker) {
3881
5168
  this.consolidationWorker.stop();
3882
5169
  }
3883
5170
  if (this.vectorWorker) {
3884
5171
  this.vectorWorker.stop();
3885
5172
  }
5173
+ if (this.syncWorker) {
5174
+ this.syncWorker.stop();
5175
+ }
3886
5176
  if (this.sharedEventStore) {
3887
5177
  await this.sharedEventStore.close();
3888
5178
  }
3889
- await this.eventStore.close();
5179
+ await this.sqliteStore.close();
5180
+ if (this.analyticsStore) {
5181
+ await this.analyticsStore.close();
5182
+ }
3890
5183
  }
3891
5184
  /**
3892
5185
  * Expand ~ to home directory
@@ -3903,11 +5196,25 @@ var GLOBAL_KEY = "__global__";
3903
5196
  function getDefaultMemoryService() {
3904
5197
  if (!serviceCache.has(GLOBAL_KEY)) {
3905
5198
  serviceCache.set(GLOBAL_KEY, new MemoryService({
3906
- storagePath: "~/.claude-code/memory"
5199
+ storagePath: "~/.claude-code/memory",
5200
+ analyticsEnabled: false,
5201
+ // Hooks don't need DuckDB
5202
+ sharedStoreConfig: { enabled: false }
5203
+ // Shared store uses DuckDB too
3907
5204
  }));
3908
5205
  }
3909
5206
  return serviceCache.get(GLOBAL_KEY);
3910
5207
  }
5208
+ function getReadOnlyMemoryService() {
5209
+ return new MemoryService({
5210
+ storagePath: "~/.claude-code/memory",
5211
+ readOnly: true,
5212
+ analyticsEnabled: false,
5213
+ // Use SQLite for reads (WAL supports concurrent readers)
5214
+ sharedStoreConfig: { enabled: false }
5215
+ // Skip shared store for now
5216
+ });
5217
+ }
3911
5218
  function getMemoryServiceForProject(projectPath, sharedStoreConfig) {
3912
5219
  const hash = hashProjectPath(projectPath);
3913
5220
  if (!serviceCache.has(hash)) {
@@ -3915,7 +5222,10 @@ function getMemoryServiceForProject(projectPath, sharedStoreConfig) {
3915
5222
  serviceCache.set(hash, new MemoryService({
3916
5223
  storagePath,
3917
5224
  projectHash: hash,
3918
- sharedStoreConfig
5225
+ // Override shared store config - hooks don't need DuckDB
5226
+ sharedStoreConfig: sharedStoreConfig ?? { enabled: false },
5227
+ analyticsEnabled: false
5228
+ // Hooks don't need DuckDB
3919
5229
  }));
3920
5230
  }
3921
5231
  return serviceCache.get(hash);
@@ -4163,7 +5473,8 @@ function createSessionHistoryImporter(memoryService) {
4163
5473
  import { Hono as Hono7 } from "hono";
4164
5474
  import { cors } from "hono/cors";
4165
5475
  import { logger } from "hono/logger";
4166
- import { serveStatic } from "hono/bun";
5476
+ import { serve } from "@hono/node-server";
5477
+ import { serveStatic } from "@hono/node-server/serve-static";
4167
5478
  import * as path3 from "path";
4168
5479
  import * as fs3 from "fs";
4169
5480
 
@@ -4176,8 +5487,8 @@ var sessionsRouter = new Hono();
4176
5487
  sessionsRouter.get("/", async (c) => {
4177
5488
  const page = parseInt(c.req.query("page") || "1", 10);
4178
5489
  const pageSize = parseInt(c.req.query("pageSize") || "20", 10);
5490
+ const memoryService = getReadOnlyMemoryService();
4179
5491
  try {
4180
- const memoryService = getDefaultMemoryService();
4181
5492
  await memoryService.initialize();
4182
5493
  const recentEvents = await memoryService.getRecentEvents(1e3);
4183
5494
  const sessionMap = /* @__PURE__ */ new Map();
@@ -4214,12 +5525,14 @@ sessionsRouter.get("/", async (c) => {
4214
5525
  });
4215
5526
  } catch (error) {
4216
5527
  return c.json({ error: error.message }, 500);
5528
+ } finally {
5529
+ await memoryService.shutdown();
4217
5530
  }
4218
5531
  });
4219
5532
  sessionsRouter.get("/:id", async (c) => {
4220
5533
  const { id } = c.req.param();
5534
+ const memoryService = getReadOnlyMemoryService();
4221
5535
  try {
4222
- const memoryService = getDefaultMemoryService();
4223
5536
  await memoryService.initialize();
4224
5537
  const events = await memoryService.getSessionHistory(id);
4225
5538
  if (events.length === 0) {
@@ -4248,6 +5561,8 @@ sessionsRouter.get("/:id", async (c) => {
4248
5561
  });
4249
5562
  } catch (error) {
4250
5563
  return c.json({ error: error.message }, 500);
5564
+ } finally {
5565
+ await memoryService.shutdown();
4251
5566
  }
4252
5567
  });
4253
5568
 
@@ -4259,8 +5574,8 @@ eventsRouter.get("/", async (c) => {
4259
5574
  const eventType = c.req.query("type");
4260
5575
  const limit = parseInt(c.req.query("limit") || "100", 10);
4261
5576
  const offset = parseInt(c.req.query("offset") || "0", 10);
5577
+ const memoryService = getReadOnlyMemoryService();
4262
5578
  try {
4263
- const memoryService = getDefaultMemoryService();
4264
5579
  await memoryService.initialize();
4265
5580
  let events = await memoryService.getRecentEvents(limit + offset + 1e3);
4266
5581
  if (sessionId) {
@@ -4287,12 +5602,14 @@ eventsRouter.get("/", async (c) => {
4287
5602
  });
4288
5603
  } catch (error) {
4289
5604
  return c.json({ error: error.message }, 500);
5605
+ } finally {
5606
+ await memoryService.shutdown();
4290
5607
  }
4291
5608
  });
4292
5609
  eventsRouter.get("/:id", async (c) => {
4293
5610
  const { id } = c.req.param();
5611
+ const memoryService = getReadOnlyMemoryService();
4294
5612
  try {
4295
- const memoryService = getDefaultMemoryService();
4296
5613
  await memoryService.initialize();
4297
5614
  const recentEvents = await memoryService.getRecentEvents(1e4);
4298
5615
  const event = recentEvents.find((e) => e.id === id);
@@ -4322,6 +5639,8 @@ eventsRouter.get("/:id", async (c) => {
4322
5639
  });
4323
5640
  } catch (error) {
4324
5641
  return c.json({ error: error.message }, 500);
5642
+ } finally {
5643
+ await memoryService.shutdown();
4325
5644
  }
4326
5645
  });
4327
5646
 
@@ -4329,12 +5648,12 @@ eventsRouter.get("/:id", async (c) => {
4329
5648
  import { Hono as Hono3 } from "hono";
4330
5649
  var searchRouter = new Hono3();
4331
5650
  searchRouter.post("/", async (c) => {
5651
+ const memoryService = getReadOnlyMemoryService();
4332
5652
  try {
4333
5653
  const body = await c.req.json();
4334
5654
  if (!body.query) {
4335
5655
  return c.json({ error: "Query is required" }, 400);
4336
5656
  }
4337
- const memoryService = getDefaultMemoryService();
4338
5657
  await memoryService.initialize();
4339
5658
  const startTime = Date.now();
4340
5659
  const result = await memoryService.retrieveMemories(body.query, {
@@ -4363,6 +5682,8 @@ searchRouter.post("/", async (c) => {
4363
5682
  });
4364
5683
  } catch (error) {
4365
5684
  return c.json({ error: error.message }, 500);
5685
+ } finally {
5686
+ await memoryService.shutdown();
4366
5687
  }
4367
5688
  });
4368
5689
  searchRouter.get("/", async (c) => {
@@ -4371,8 +5692,8 @@ searchRouter.get("/", async (c) => {
4371
5692
  return c.json({ error: 'Query parameter "q" is required' }, 400);
4372
5693
  }
4373
5694
  const topK = parseInt(c.req.query("topK") || "5", 10);
5695
+ const memoryService = getReadOnlyMemoryService();
4374
5696
  try {
4375
- const memoryService = getDefaultMemoryService();
4376
5697
  await memoryService.initialize();
4377
5698
  const result = await memoryService.retrieveMemories(query, { topK });
4378
5699
  return c.json({
@@ -4390,6 +5711,8 @@ searchRouter.get("/", async (c) => {
4390
5711
  });
4391
5712
  } catch (error) {
4392
5713
  return c.json({ error: error.message }, 500);
5714
+ } finally {
5715
+ await memoryService.shutdown();
4393
5716
  }
4394
5717
  });
4395
5718
 
@@ -4397,8 +5720,8 @@ searchRouter.get("/", async (c) => {
4397
5720
  import { Hono as Hono4 } from "hono";
4398
5721
  var statsRouter = new Hono4();
4399
5722
  statsRouter.get("/shared", async (c) => {
5723
+ const memoryService = getReadOnlyMemoryService();
4400
5724
  try {
4401
- const memoryService = getDefaultMemoryService();
4402
5725
  await memoryService.initialize();
4403
5726
  const sharedStats = await memoryService.getSharedStoreStats();
4404
5727
  return c.json({
@@ -4416,12 +5739,14 @@ statsRouter.get("/shared", async (c) => {
4416
5739
  totalUsageCount: 0,
4417
5740
  lastUpdated: null
4418
5741
  });
5742
+ } finally {
5743
+ await memoryService.shutdown();
4419
5744
  }
4420
5745
  });
4421
5746
  statsRouter.get("/endless", async (c) => {
5747
+ const projectPath = c.req.query("project") || process.cwd();
5748
+ const memoryService = getMemoryServiceForProject(projectPath);
4422
5749
  try {
4423
- const projectPath = c.req.query("project") || process.cwd();
4424
- const memoryService = getMemoryServiceForProject(projectPath);
4425
5750
  await memoryService.initialize();
4426
5751
  const status = await memoryService.getEndlessModeStatus();
4427
5752
  return c.json({
@@ -4439,11 +5764,68 @@ statsRouter.get("/endless", async (c) => {
4439
5764
  consolidatedCount: 0,
4440
5765
  lastConsolidation: null
4441
5766
  });
5767
+ } finally {
5768
+ await memoryService.shutdown();
5769
+ }
5770
+ });
5771
+ statsRouter.get("/levels/:level", async (c) => {
5772
+ const { level } = c.req.param();
5773
+ const limit = parseInt(c.req.query("limit") || "20", 10);
5774
+ const offset = parseInt(c.req.query("offset") || "0", 10);
5775
+ const sort = c.req.query("sort") || "recent";
5776
+ const validLevels = ["L0", "L1", "L2", "L3", "L4"];
5777
+ if (!validLevels.includes(level)) {
5778
+ return c.json({ error: `Invalid level. Must be one of: ${validLevels.join(", ")}` }, 400);
5779
+ }
5780
+ const memoryService = getReadOnlyMemoryService();
5781
+ try {
5782
+ await memoryService.initialize();
5783
+ let events = await memoryService.getEventsByLevel(level, { limit: limit * 2, offset });
5784
+ const stats = await memoryService.getStats();
5785
+ const levelStat = stats.levelStats.find((s) => s.level === level);
5786
+ if (sort === "accessed") {
5787
+ const sqliteStore = memoryService.sqliteEventStore;
5788
+ if (sqliteStore) {
5789
+ const eventIds = events.map((e) => e.id);
5790
+ const accessedEvents = await sqliteStore.getMostAccessed(1e3);
5791
+ const accessMap = new Map(accessedEvents.map((e) => [e.id, e.access_count || 0]));
5792
+ events = events.map((e) => ({
5793
+ ...e,
5794
+ accessCount: accessMap.get(e.id) || 0
5795
+ }));
5796
+ events.sort((a, b) => b.accessCount - a.accessCount);
5797
+ }
5798
+ } else if (sort === "oldest") {
5799
+ events.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
5800
+ } else {
5801
+ events.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
5802
+ }
5803
+ events = events.slice(0, limit);
5804
+ return c.json({
5805
+ level,
5806
+ events: events.map((e) => ({
5807
+ id: e.id,
5808
+ eventType: e.eventType,
5809
+ sessionId: e.sessionId,
5810
+ timestamp: e.timestamp.toISOString(),
5811
+ content: e.content.slice(0, 500) + (e.content.length > 500 ? "..." : ""),
5812
+ metadata: e.metadata,
5813
+ accessCount: e.accessCount || 0
5814
+ })),
5815
+ total: levelStat?.count || 0,
5816
+ limit,
5817
+ offset,
5818
+ hasMore: events.length === limit
5819
+ });
5820
+ } catch (error) {
5821
+ return c.json({ error: error.message }, 500);
5822
+ } finally {
5823
+ await memoryService.shutdown();
4442
5824
  }
4443
5825
  });
4444
5826
  statsRouter.get("/", async (c) => {
5827
+ const memoryService = getReadOnlyMemoryService();
4445
5828
  try {
4446
- const memoryService = getDefaultMemoryService();
4447
5829
  await memoryService.initialize();
4448
5830
  const stats = await memoryService.getStats();
4449
5831
  const recentEvents = await memoryService.getRecentEvents(1e4);
@@ -4480,12 +5862,45 @@ statsRouter.get("/", async (c) => {
4480
5862
  });
4481
5863
  } catch (error) {
4482
5864
  return c.json({ error: error.message }, 500);
5865
+ } finally {
5866
+ await memoryService.shutdown();
5867
+ }
5868
+ });
5869
+ statsRouter.get("/most-accessed", async (c) => {
5870
+ const limit = parseInt(c.req.query("limit") || "10", 10);
5871
+ const memoryService = getReadOnlyMemoryService();
5872
+ try {
5873
+ await memoryService.initialize();
5874
+ console.log("[most-accessed] Fetching most accessed memories, limit:", limit);
5875
+ const memories = await memoryService.getMostAccessedMemories(limit);
5876
+ console.log("[most-accessed] Got memories:", memories.length);
5877
+ return c.json({
5878
+ memories: memories.map((m) => ({
5879
+ memoryId: m.memoryId,
5880
+ summary: m.summary,
5881
+ topics: m.topics,
5882
+ accessCount: m.accessCount,
5883
+ lastAccessed: m.lastAccessed || null,
5884
+ confidence: m.confidence,
5885
+ createdAt: m.createdAt instanceof Date ? m.createdAt.toISOString() : m.createdAt
5886
+ })),
5887
+ total: memories.length
5888
+ });
5889
+ } catch (error) {
5890
+ console.error("[most-accessed] Error:", error);
5891
+ return c.json({
5892
+ memories: [],
5893
+ total: 0,
5894
+ error: error.message
5895
+ });
5896
+ } finally {
5897
+ await memoryService.shutdown();
4483
5898
  }
4484
5899
  });
4485
5900
  statsRouter.get("/timeline", async (c) => {
4486
5901
  const days = parseInt(c.req.query("days") || "7", 10);
5902
+ const memoryService = getReadOnlyMemoryService();
4487
5903
  try {
4488
- const memoryService = getDefaultMemoryService();
4489
5904
  await memoryService.initialize();
4490
5905
  const recentEvents = await memoryService.getRecentEvents(1e4);
4491
5906
  const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
@@ -4510,8 +5925,46 @@ statsRouter.get("/timeline", async (c) => {
4510
5925
  });
4511
5926
  } catch (error) {
4512
5927
  return c.json({ error: error.message }, 500);
5928
+ } finally {
5929
+ await memoryService.shutdown();
5930
+ }
5931
+ });
5932
+ statsRouter.post("/graduation/run", async (c) => {
5933
+ const memoryService = getReadOnlyMemoryService();
5934
+ try {
5935
+ await memoryService.initialize();
5936
+ const result = await memoryService.forceGraduation();
5937
+ return c.json({
5938
+ success: true,
5939
+ evaluated: result.evaluated,
5940
+ graduated: result.graduated,
5941
+ byLevel: result.byLevel
5942
+ });
5943
+ } catch (error) {
5944
+ return c.json({
5945
+ success: false,
5946
+ error: error.message
5947
+ }, 500);
5948
+ } finally {
5949
+ await memoryService.shutdown();
4513
5950
  }
4514
5951
  });
5952
+ statsRouter.get("/graduation", async (c) => {
5953
+ return c.json({
5954
+ criteria: {
5955
+ L0toL1: { minAccessCount: 1, minConfidence: 0.5, minCrossSessionRefs: 0, maxAgeDays: 30 },
5956
+ L1toL2: { minAccessCount: 3, minConfidence: 0.7, minCrossSessionRefs: 1, maxAgeDays: 60 },
5957
+ L2toL3: { minAccessCount: 5, minConfidence: 0.85, minCrossSessionRefs: 2, maxAgeDays: 90 },
5958
+ L3toL4: { minAccessCount: 10, minConfidence: 0.92, minCrossSessionRefs: 3, maxAgeDays: 180 }
5959
+ },
5960
+ description: {
5961
+ accessCount: "Number of times the memory was retrieved/referenced",
5962
+ confidence: "Match confidence score when retrieved (0.0-1.0)",
5963
+ crossSessionRefs: "Number of different sessions that referenced this memory",
5964
+ maxAgeDays: "Maximum days since last access (prevents stale promotion)"
5965
+ }
5966
+ });
5967
+ });
4515
5968
 
4516
5969
  // src/server/api/citations.ts
4517
5970
  import { Hono as Hono5 } from "hono";
@@ -4538,8 +5991,8 @@ var citationsRouter = new Hono5();
4538
5991
  citationsRouter.get("/:id", async (c) => {
4539
5992
  const { id } = c.req.param();
4540
5993
  const citationId = parseCitationId(id) || id;
5994
+ const memoryService = getReadOnlyMemoryService();
4541
5995
  try {
4542
- const memoryService = getDefaultMemoryService();
4543
5996
  await memoryService.initialize();
4544
5997
  const recentEvents = await memoryService.getRecentEvents(1e4);
4545
5998
  const event = recentEvents.find((e) => {
@@ -4565,13 +6018,15 @@ citationsRouter.get("/:id", async (c) => {
4565
6018
  });
4566
6019
  } catch (error) {
4567
6020
  return c.json({ error: error.message }, 500);
6021
+ } finally {
6022
+ await memoryService.shutdown();
4568
6023
  }
4569
6024
  });
4570
6025
  citationsRouter.get("/:id/related", async (c) => {
4571
6026
  const { id } = c.req.param();
4572
6027
  const citationId = parseCitationId(id) || id;
6028
+ const memoryService = getReadOnlyMemoryService();
4573
6029
  try {
4574
- const memoryService = getDefaultMemoryService();
4575
6030
  await memoryService.initialize();
4576
6031
  const recentEvents = await memoryService.getRecentEvents(1e4);
4577
6032
  const event = recentEvents.find((e) => {
@@ -4601,6 +6056,8 @@ citationsRouter.get("/:id/related", async (c) => {
4601
6056
  });
4602
6057
  } catch (error) {
4603
6058
  return c.json({ error: error.message }, 500);
6059
+ } finally {
6060
+ await memoryService.shutdown();
4604
6061
  }
4605
6062
  });
4606
6063
 
@@ -4613,7 +6070,7 @@ app.use("/*", cors());
4613
6070
  app.use("/*", logger());
4614
6071
  app.route("/api", apiRouter);
4615
6072
  app.get("/health", (c) => c.json({ status: "ok", timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
4616
- var uiPath = path3.join(import.meta.dir, "../../dist/ui");
6073
+ var uiPath = path3.join(__dirname, "../../dist/ui");
4617
6074
  if (fs3.existsSync(uiPath)) {
4618
6075
  app.use("/*", serveStatic({ root: uiPath }));
4619
6076
  }
@@ -4629,17 +6086,17 @@ function startServer(port = 37777) {
4629
6086
  if (serverInstance) {
4630
6087
  return serverInstance;
4631
6088
  }
4632
- serverInstance = Bun.serve({
4633
- hostname: "127.0.0.1",
6089
+ serverInstance = serve({
6090
+ fetch: app.fetch,
4634
6091
  port,
4635
- fetch: app.fetch
6092
+ hostname: "127.0.0.1"
4636
6093
  });
4637
6094
  console.log(`\u{1F9E0} Code Memory viewer started at http://localhost:${port}`);
4638
6095
  return serverInstance;
4639
6096
  }
4640
6097
  function stopServer() {
4641
6098
  if (serverInstance) {
4642
- serverInstance.stop();
6099
+ serverInstance.close();
4643
6100
  serverInstance = null;
4644
6101
  }
4645
6102
  }
@@ -4651,14 +6108,166 @@ async function isServerRunning(port = 37777) {
4651
6108
  return false;
4652
6109
  }
4653
6110
  }
4654
- if (import.meta.main) {
6111
+ var isMainModule = process.argv[1]?.includes("server/index") || process.argv[1]?.endsWith("server.js");
6112
+ if (isMainModule) {
4655
6113
  const port = parseInt(process.env.PORT || "37777", 10);
4656
6114
  startServer(port);
4657
6115
  }
4658
6116
 
4659
6117
  // src/cli/index.ts
6118
+ var CLAUDE_SETTINGS_PATH = path4.join(os3.homedir(), ".claude", "settings.json");
6119
+ function getPluginPath() {
6120
+ const possiblePaths = [
6121
+ path4.join(__dirname, ".."),
6122
+ // When running from dist/cli
6123
+ path4.join(__dirname, "../..", "dist"),
6124
+ // When running from src
6125
+ path4.join(process.cwd(), "dist")
6126
+ // Current working directory
6127
+ ];
6128
+ for (const p of possiblePaths) {
6129
+ const hooksPath = path4.join(p, "hooks", "user-prompt-submit.js");
6130
+ if (fs4.existsSync(hooksPath)) {
6131
+ return p;
6132
+ }
6133
+ }
6134
+ return path4.join(os3.homedir(), ".npm-global", "lib", "node_modules", "claude-memory-layer", "dist");
6135
+ }
6136
+ function loadClaudeSettings() {
6137
+ try {
6138
+ if (fs4.existsSync(CLAUDE_SETTINGS_PATH)) {
6139
+ const content = fs4.readFileSync(CLAUDE_SETTINGS_PATH, "utf-8");
6140
+ return JSON.parse(content);
6141
+ }
6142
+ } catch (error) {
6143
+ console.error("Warning: Could not read existing settings:", error);
6144
+ }
6145
+ return {};
6146
+ }
6147
+ function saveClaudeSettings(settings) {
6148
+ const dir = path4.dirname(CLAUDE_SETTINGS_PATH);
6149
+ if (!fs4.existsSync(dir)) {
6150
+ fs4.mkdirSync(dir, { recursive: true });
6151
+ }
6152
+ const tempPath = CLAUDE_SETTINGS_PATH + ".tmp";
6153
+ fs4.writeFileSync(tempPath, JSON.stringify(settings, null, 2));
6154
+ fs4.renameSync(tempPath, CLAUDE_SETTINGS_PATH);
6155
+ }
6156
+ function getHooksConfig(pluginPath) {
6157
+ return {
6158
+ UserPromptSubmit: [
6159
+ {
6160
+ matcher: "",
6161
+ hooks: [
6162
+ {
6163
+ type: "command",
6164
+ command: `node ${path4.join(pluginPath, "hooks", "user-prompt-submit.js")}`
6165
+ }
6166
+ ]
6167
+ }
6168
+ ],
6169
+ PostToolUse: [
6170
+ {
6171
+ matcher: "",
6172
+ hooks: [
6173
+ {
6174
+ type: "command",
6175
+ command: `node ${path4.join(pluginPath, "hooks", "post-tool-use.js")}`
6176
+ }
6177
+ ]
6178
+ }
6179
+ ]
6180
+ };
6181
+ }
4660
6182
  var program = new Command();
4661
6183
  program.name("claude-memory-layer").description("Claude Code Memory Plugin CLI").version("1.0.0");
6184
+ program.command("install").description("Install hooks into Claude Code settings").option("--path <path>", "Custom plugin path (defaults to auto-detect)").action(async (options) => {
6185
+ try {
6186
+ const pluginPath = options.path || getPluginPath();
6187
+ const userPromptHook = path4.join(pluginPath, "hooks", "user-prompt-submit.js");
6188
+ if (!fs4.existsSync(userPromptHook)) {
6189
+ console.error(`
6190
+ \u274C Hook files not found at: ${pluginPath}`);
6191
+ console.error(' Make sure you have built the plugin with "npm run build"');
6192
+ process.exit(1);
6193
+ }
6194
+ const settings = loadClaudeSettings();
6195
+ const newHooks = getHooksConfig(pluginPath);
6196
+ settings.hooks = {
6197
+ ...settings.hooks,
6198
+ ...newHooks
6199
+ };
6200
+ saveClaudeSettings(settings);
6201
+ console.log("\n\u2705 Claude Memory Layer installed!\n");
6202
+ console.log("Hooks registered:");
6203
+ console.log(" - UserPromptSubmit: Memory retrieval on user input");
6204
+ console.log(" - PostToolUse: Store tool observations\n");
6205
+ console.log("Plugin path:", pluginPath);
6206
+ console.log("\n\u26A0\uFE0F Restart Claude Code for changes to take effect.\n");
6207
+ console.log("Commands:");
6208
+ console.log(" claude-memory-layer dashboard - Open web dashboard");
6209
+ console.log(" claude-memory-layer search - Search memories");
6210
+ console.log(" claude-memory-layer stats - View statistics");
6211
+ console.log(" claude-memory-layer uninstall - Remove hooks\n");
6212
+ } catch (error) {
6213
+ console.error("Install failed:", error);
6214
+ process.exit(1);
6215
+ }
6216
+ });
6217
+ program.command("uninstall").description("Remove hooks from Claude Code settings").action(async () => {
6218
+ try {
6219
+ const settings = loadClaudeSettings();
6220
+ if (!settings.hooks) {
6221
+ console.log("\n\u{1F4CB} No hooks installed.\n");
6222
+ return;
6223
+ }
6224
+ delete settings.hooks.UserPromptSubmit;
6225
+ delete settings.hooks.PostToolUse;
6226
+ if (Object.keys(settings.hooks).length === 0) {
6227
+ delete settings.hooks;
6228
+ }
6229
+ saveClaudeSettings(settings);
6230
+ console.log("\n\u2705 Claude Memory Layer uninstalled!\n");
6231
+ console.log("Hooks removed from Claude Code settings.");
6232
+ console.log("Your memory data is preserved and can be accessed with:");
6233
+ console.log(" claude-memory-layer dashboard\n");
6234
+ console.log("\u26A0\uFE0F Restart Claude Code for changes to take effect.\n");
6235
+ } catch (error) {
6236
+ console.error("Uninstall failed:", error);
6237
+ process.exit(1);
6238
+ }
6239
+ });
6240
+ program.command("status").description("Check plugin installation status").action(async () => {
6241
+ try {
6242
+ const settings = loadClaudeSettings();
6243
+ const pluginPath = getPluginPath();
6244
+ console.log("\n\u{1F9E0} Claude Memory Layer Status\n");
6245
+ const hasUserPromptHook = settings.hooks?.UserPromptSubmit?.some(
6246
+ (h) => h.hooks?.some((hook) => hook.command?.includes("user-prompt-submit"))
6247
+ );
6248
+ const hasPostToolHook = settings.hooks?.PostToolUse?.some(
6249
+ (h) => h.hooks?.some((hook) => hook.command?.includes("post-tool-use"))
6250
+ );
6251
+ console.log("Hooks:");
6252
+ console.log(` UserPromptSubmit: ${hasUserPromptHook ? "\u2705 Installed" : "\u274C Not installed"}`);
6253
+ console.log(` PostToolUse: ${hasPostToolHook ? "\u2705 Installed" : "\u274C Not installed"}`);
6254
+ const hooksExist = fs4.existsSync(path4.join(pluginPath, "hooks", "user-prompt-submit.js"));
6255
+ console.log(`
6256
+ Plugin files: ${hooksExist ? "\u2705 Found" : "\u274C Not found"}`);
6257
+ console.log(` Path: ${pluginPath}`);
6258
+ const dashboardRunning = await isServerRunning(37777);
6259
+ console.log(`
6260
+ Dashboard: ${dashboardRunning ? "\u2705 Running at http://localhost:37777" : "\u23F9\uFE0F Not running"}`);
6261
+ if (!hasUserPromptHook || !hasPostToolHook) {
6262
+ console.log('\n\u{1F4A1} Run "claude-memory-layer install" to set up hooks.\n');
6263
+ } else {
6264
+ console.log("\n\u2705 Plugin is fully installed and configured.\n");
6265
+ }
6266
+ } catch (error) {
6267
+ console.error("Status check failed:", error);
6268
+ process.exit(1);
6269
+ }
6270
+ });
4662
6271
  program.command("search <query>").description("Search memories using semantic search").option("-k, --top-k <number>", "Number of results", "5").option("-s, --min-score <number>", "Minimum similarity score", "0.7").option("--session <id>", "Filter by session ID").option("-p, --project <path>", "Project path (defaults to cwd)").action(async (query, options) => {
4663
6272
  const projectPath = options.project || process.cwd();
4664
6273
  const service = getMemoryServiceForProject(projectPath);