claude-memory-layer 1.0.7 → 1.0.8

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 (38) hide show
  1. package/.claude/settings.local.json +4 -1
  2. package/.history/package_20260201192048.json +47 -0
  3. package/dist/cli/index.js +569 -39
  4. package/dist/cli/index.js.map +4 -4
  5. package/dist/core/index.js +192 -5
  6. package/dist/core/index.js.map +4 -4
  7. package/dist/hooks/session-end.js +262 -18
  8. package/dist/hooks/session-end.js.map +4 -4
  9. package/dist/hooks/session-start.js +262 -18
  10. package/dist/hooks/session-start.js.map +4 -4
  11. package/dist/hooks/stop.js +262 -18
  12. package/dist/hooks/stop.js.map +4 -4
  13. package/dist/hooks/user-prompt-submit.js +262 -18
  14. package/dist/hooks/user-prompt-submit.js.map +4 -4
  15. package/dist/server/api/index.js +404 -39
  16. package/dist/server/api/index.js.map +4 -4
  17. package/dist/server/index.js +413 -46
  18. package/dist/server/index.js.map +4 -4
  19. package/dist/services/memory-service.js +269 -18
  20. package/dist/services/memory-service.js.map +4 -4
  21. package/dist/ui/index.html +495 -15
  22. package/package.json +2 -1
  23. package/scripts/build.ts +3 -2
  24. package/src/cli/index.ts +226 -0
  25. package/src/core/db-wrapper.ts +8 -1
  26. package/src/core/event-store.ts +52 -3
  27. package/src/core/graduation-worker.ts +171 -0
  28. package/src/core/graduation.ts +15 -2
  29. package/src/core/index.ts +1 -0
  30. package/src/core/retriever.ts +18 -0
  31. package/src/server/api/citations.ts +7 -3
  32. package/src/server/api/events.ts +7 -3
  33. package/src/server/api/search.ts +7 -3
  34. package/src/server/api/sessions.ts +7 -3
  35. package/src/server/api/stats.ts +129 -12
  36. package/src/server/index.ts +18 -9
  37. package/src/services/memory-service.ts +107 -19
  38. package/src/ui/index.html +495 -15
@@ -72,7 +72,10 @@ function toDate(value) {
72
72
  return new Date(value);
73
73
  return new Date(String(value));
74
74
  }
75
- function createDatabase(path2) {
75
+ function createDatabase(path2, options) {
76
+ if (options?.readOnly) {
77
+ return new duckdb.Database(path2, { access_mode: "READ_ONLY" });
78
+ }
76
79
  return new duckdb.Database(path2);
77
80
  }
78
81
  function dbRun(db, sql, params = []) {
@@ -126,18 +129,24 @@ function dbClose(db) {
126
129
 
127
130
  // src/core/event-store.ts
128
131
  var EventStore = class {
129
- constructor(dbPath) {
132
+ constructor(dbPath, options) {
130
133
  this.dbPath = dbPath;
131
- this.db = createDatabase(dbPath);
134
+ this.readOnly = options?.readOnly ?? false;
135
+ this.db = createDatabase(dbPath, { readOnly: this.readOnly });
132
136
  }
133
137
  db;
134
138
  initialized = false;
139
+ readOnly;
135
140
  /**
136
141
  * Initialize database schema
137
142
  */
138
143
  async initialize() {
139
144
  if (this.initialized)
140
145
  return;
146
+ if (this.readOnly) {
147
+ this.initialized = true;
148
+ return;
149
+ }
141
150
  await dbRun(this.db, `
142
151
  CREATE TABLE IF NOT EXISTS events (
143
152
  id VARCHAR PRIMARY KEY,
@@ -614,6 +623,36 @@ var EventStore = class {
614
623
  );
615
624
  return rows;
616
625
  }
626
+ /**
627
+ * Get events by memory level
628
+ */
629
+ async getEventsByLevel(level, options) {
630
+ await this.initialize();
631
+ const limit = options?.limit || 50;
632
+ const offset = options?.offset || 0;
633
+ const rows = await dbAll(
634
+ this.db,
635
+ `SELECT e.* FROM events e
636
+ INNER JOIN memory_levels ml ON e.id = ml.event_id
637
+ WHERE ml.level = ?
638
+ ORDER BY e.timestamp DESC
639
+ LIMIT ? OFFSET ?`,
640
+ [level, limit, offset]
641
+ );
642
+ return rows.map((row) => this.rowToEvent(row));
643
+ }
644
+ /**
645
+ * Get memory level for a specific event
646
+ */
647
+ async getEventLevel(eventId) {
648
+ await this.initialize();
649
+ const rows = await dbAll(
650
+ this.db,
651
+ `SELECT level FROM memory_levels WHERE event_id = ?`,
652
+ [eventId]
653
+ );
654
+ return rows.length > 0 ? rows[0].level : null;
655
+ }
617
656
  // ============================================================
618
657
  // Endless Mode Helper Methods
619
658
  // ============================================================
@@ -1196,6 +1235,7 @@ var Retriever = class {
1196
1235
  matcher;
1197
1236
  sharedStore;
1198
1237
  sharedVectorStore;
1238
+ graduation;
1199
1239
  constructor(eventStore, vectorStore, embedder, matcher, sharedOptions) {
1200
1240
  this.eventStore = eventStore;
1201
1241
  this.vectorStore = vectorStore;
@@ -1204,6 +1244,12 @@ var Retriever = class {
1204
1244
  this.sharedStore = sharedOptions?.sharedStore;
1205
1245
  this.sharedVectorStore = sharedOptions?.sharedVectorStore;
1206
1246
  }
1247
+ /**
1248
+ * Set graduation pipeline for access tracking
1249
+ */
1250
+ setGraduationPipeline(graduation) {
1251
+ this.graduation = graduation;
1252
+ }
1207
1253
  /**
1208
1254
  * Set shared stores after construction
1209
1255
  */
@@ -1326,6 +1372,13 @@ var Retriever = class {
1326
1372
  const event = await this.eventStore.getEvent(result.eventId);
1327
1373
  if (!event)
1328
1374
  continue;
1375
+ if (this.graduation) {
1376
+ this.graduation.recordAccess(
1377
+ event.id,
1378
+ options.sessionId || "unknown",
1379
+ result.score
1380
+ );
1381
+ }
1329
1382
  let sessionContext;
1330
1383
  if (options.includeSessionContext) {
1331
1384
  sessionContext = await this.getSessionContext(event.sessionId, event.id);
@@ -1447,15 +1500,26 @@ var GraduationPipeline = class {
1447
1500
  L3toL4: { ...DEFAULT_CRITERIA.L3toL4, ...criteria.L3toL4 }
1448
1501
  };
1449
1502
  }
1503
+ // Track which sessions have accessed each event
1504
+ sessionAccesses = /* @__PURE__ */ new Map();
1450
1505
  /**
1451
1506
  * Record an access to an event (used for graduation scoring)
1452
1507
  */
1453
1508
  recordAccess(eventId, fromSessionId, confidence = 1) {
1454
1509
  const existing = this.metrics.get(eventId);
1510
+ if (!this.sessionAccesses.has(eventId)) {
1511
+ this.sessionAccesses.set(eventId, /* @__PURE__ */ new Set());
1512
+ }
1513
+ const sessions = this.sessionAccesses.get(eventId);
1514
+ const isNewSession = !sessions.has(fromSessionId);
1515
+ sessions.add(fromSessionId);
1455
1516
  if (existing) {
1456
1517
  existing.accessCount++;
1457
1518
  existing.lastAccessed = /* @__PURE__ */ new Date();
1458
1519
  existing.confidence = Math.max(existing.confidence, confidence);
1520
+ if (isNewSession && sessions.size > 1) {
1521
+ existing.crossSessionRefs = sessions.size - 1;
1522
+ }
1459
1523
  } else {
1460
1524
  this.metrics.set(eventId, {
1461
1525
  eventId,
@@ -3343,6 +3407,127 @@ function createContinuityManager(eventStore, config) {
3343
3407
  return new ContinuityManager(eventStore, config);
3344
3408
  }
3345
3409
 
3410
+ // src/core/graduation-worker.ts
3411
+ var DEFAULT_CONFIG4 = {
3412
+ evaluationIntervalMs: 3e5,
3413
+ // 5 minutes
3414
+ batchSize: 50,
3415
+ cooldownMs: 36e5
3416
+ // 1 hour cooldown between evaluations
3417
+ };
3418
+ var GraduationWorker = class {
3419
+ constructor(eventStore, graduation, config = DEFAULT_CONFIG4) {
3420
+ this.eventStore = eventStore;
3421
+ this.graduation = graduation;
3422
+ this.config = config;
3423
+ }
3424
+ running = false;
3425
+ timeout = null;
3426
+ lastEvaluated = /* @__PURE__ */ new Map();
3427
+ /**
3428
+ * Start the graduation worker
3429
+ */
3430
+ start() {
3431
+ if (this.running)
3432
+ return;
3433
+ this.running = true;
3434
+ this.scheduleNext();
3435
+ }
3436
+ /**
3437
+ * Stop the graduation worker
3438
+ */
3439
+ stop() {
3440
+ this.running = false;
3441
+ if (this.timeout) {
3442
+ clearTimeout(this.timeout);
3443
+ this.timeout = null;
3444
+ }
3445
+ }
3446
+ /**
3447
+ * Check if currently running
3448
+ */
3449
+ isRunning() {
3450
+ return this.running;
3451
+ }
3452
+ /**
3453
+ * Force a graduation evaluation run
3454
+ */
3455
+ async forceRun() {
3456
+ return await this.runGraduation();
3457
+ }
3458
+ /**
3459
+ * Schedule the next graduation check
3460
+ */
3461
+ scheduleNext() {
3462
+ if (!this.running)
3463
+ return;
3464
+ this.timeout = setTimeout(
3465
+ () => this.run(),
3466
+ this.config.evaluationIntervalMs
3467
+ );
3468
+ }
3469
+ /**
3470
+ * Run graduation evaluation
3471
+ */
3472
+ async run() {
3473
+ if (!this.running)
3474
+ return;
3475
+ try {
3476
+ await this.runGraduation();
3477
+ } catch (error) {
3478
+ console.error("Graduation error:", error);
3479
+ }
3480
+ this.scheduleNext();
3481
+ }
3482
+ /**
3483
+ * Perform graduation evaluation across all levels
3484
+ */
3485
+ async runGraduation() {
3486
+ const result = {
3487
+ evaluated: 0,
3488
+ graduated: 0,
3489
+ byLevel: {}
3490
+ };
3491
+ const levels = ["L0", "L1", "L2", "L3"];
3492
+ const now = Date.now();
3493
+ for (const level of levels) {
3494
+ const events = await this.eventStore.getEventsByLevel(level, {
3495
+ limit: this.config.batchSize
3496
+ });
3497
+ let levelGraduated = 0;
3498
+ for (const event of events) {
3499
+ const lastEval = this.lastEvaluated.get(event.id);
3500
+ if (lastEval && now - lastEval < this.config.cooldownMs) {
3501
+ continue;
3502
+ }
3503
+ result.evaluated++;
3504
+ this.lastEvaluated.set(event.id, now);
3505
+ const gradResult = await this.graduation.evaluateGraduation(event.id, level);
3506
+ if (gradResult.success) {
3507
+ result.graduated++;
3508
+ levelGraduated++;
3509
+ }
3510
+ }
3511
+ if (levelGraduated > 0) {
3512
+ result.byLevel[level] = levelGraduated;
3513
+ }
3514
+ }
3515
+ if (this.lastEvaluated.size > 1e3) {
3516
+ const entries = Array.from(this.lastEvaluated.entries());
3517
+ entries.sort((a, b) => b[1] - a[1]);
3518
+ this.lastEvaluated = new Map(entries.slice(0, 1e3));
3519
+ }
3520
+ return result;
3521
+ }
3522
+ };
3523
+ function createGraduationWorker(eventStore, graduation, config) {
3524
+ return new GraduationWorker(
3525
+ eventStore,
3526
+ graduation,
3527
+ { ...DEFAULT_CONFIG4, ...config }
3528
+ );
3529
+ }
3530
+
3346
3531
  // src/services/memory-service.ts
3347
3532
  function normalizePath(projectPath) {
3348
3533
  const expanded = projectPath.startsWith("~") ? path.join(os.homedir(), projectPath.slice(1)) : projectPath;
@@ -3370,6 +3555,7 @@ var MemoryService = class {
3370
3555
  retriever;
3371
3556
  graduation;
3372
3557
  vectorWorker = null;
3558
+ graduationWorker = null;
3373
3559
  initialized = false;
3374
3560
  // Endless Mode components
3375
3561
  workingSetStore = null;
@@ -3384,14 +3570,16 @@ var MemoryService = class {
3384
3570
  sharedPromoter = null;
3385
3571
  sharedStoreConfig = null;
3386
3572
  projectHash = null;
3573
+ readOnly;
3387
3574
  constructor(config) {
3388
3575
  const storagePath = this.expandPath(config.storagePath);
3389
- if (!fs.existsSync(storagePath)) {
3576
+ this.readOnly = config.readOnly ?? false;
3577
+ if (!this.readOnly && !fs.existsSync(storagePath)) {
3390
3578
  fs.mkdirSync(storagePath, { recursive: true });
3391
3579
  }
3392
3580
  this.projectHash = config.projectHash || null;
3393
3581
  this.sharedStoreConfig = config.sharedStoreConfig ?? { enabled: true };
3394
- this.eventStore = new EventStore(path.join(storagePath, "events.duckdb"));
3582
+ this.eventStore = new EventStore(path.join(storagePath, "events.duckdb"), { readOnly: this.readOnly });
3395
3583
  this.vectorStore = new VectorStore(path.join(storagePath, "vectors"));
3396
3584
  this.embedder = config.embeddingModel ? new Embedder(config.embeddingModel) : getDefaultEmbedder();
3397
3585
  this.matcher = getDefaultMatcher();
@@ -3412,19 +3600,27 @@ var MemoryService = class {
3412
3600
  await this.eventStore.initialize();
3413
3601
  await this.vectorStore.initialize();
3414
3602
  await this.embedder.initialize();
3415
- this.vectorWorker = createVectorWorker(
3416
- this.eventStore,
3417
- this.vectorStore,
3418
- this.embedder
3419
- );
3420
- this.vectorWorker.start();
3421
- const savedMode = await this.eventStore.getEndlessConfig("mode");
3422
- if (savedMode === "endless") {
3423
- this.endlessMode = "endless";
3424
- await this.initializeEndlessMode();
3425
- }
3426
- if (this.sharedStoreConfig?.enabled !== false) {
3427
- await this.initializeSharedStore();
3603
+ if (!this.readOnly) {
3604
+ this.vectorWorker = createVectorWorker(
3605
+ this.eventStore,
3606
+ this.vectorStore,
3607
+ this.embedder
3608
+ );
3609
+ this.vectorWorker.start();
3610
+ this.retriever.setGraduationPipeline(this.graduation);
3611
+ this.graduationWorker = createGraduationWorker(
3612
+ this.eventStore,
3613
+ this.graduation
3614
+ );
3615
+ this.graduationWorker.start();
3616
+ const savedMode = await this.eventStore.getEndlessConfig("mode");
3617
+ if (savedMode === "endless") {
3618
+ this.endlessMode = "endless";
3619
+ await this.initializeEndlessMode();
3620
+ }
3621
+ if (this.sharedStoreConfig?.enabled !== false) {
3622
+ await this.initializeSharedStore();
3623
+ }
3428
3624
  }
3429
3625
  this.initialized = true;
3430
3626
  }
@@ -3605,6 +3801,20 @@ var MemoryService = class {
3605
3801
  }
3606
3802
  return 0;
3607
3803
  }
3804
+ /**
3805
+ * Get events by memory level
3806
+ */
3807
+ async getEventsByLevel(level, options) {
3808
+ await this.initialize();
3809
+ return this.eventStore.getEventsByLevel(level, options);
3810
+ }
3811
+ /**
3812
+ * Get memory level for a specific event
3813
+ */
3814
+ async getEventLevel(eventId) {
3815
+ await this.initialize();
3816
+ return this.eventStore.getEventLevel(eventId);
3817
+ }
3608
3818
  /**
3609
3819
  * Format retrieval results as context for Claude
3610
3820
  */
@@ -3787,6 +3997,22 @@ var MemoryService = class {
3787
3997
  return [];
3788
3998
  return this.consolidatedStore.getAll({ limit });
3789
3999
  }
4000
+ /**
4001
+ * Get most accessed consolidated memories
4002
+ */
4003
+ async getMostAccessedMemories(limit = 10) {
4004
+ if (!this.consolidatedStore)
4005
+ return [];
4006
+ return this.consolidatedStore.getMostAccessed(limit);
4007
+ }
4008
+ /**
4009
+ * Mark a consolidated memory as accessed
4010
+ */
4011
+ async markMemoryAccessed(memoryId) {
4012
+ if (!this.consolidatedStore)
4013
+ return;
4014
+ await this.consolidatedStore.markAccessed(memoryId);
4015
+ }
3790
4016
  /**
3791
4017
  * Calculate continuity score for current context
3792
4018
  */
@@ -3874,10 +4100,28 @@ var MemoryService = class {
3874
4100
  }
3875
4101
  return parts.join("\n");
3876
4102
  }
4103
+ /**
4104
+ * Force a graduation evaluation run
4105
+ */
4106
+ async forceGraduation() {
4107
+ if (!this.graduationWorker) {
4108
+ return { evaluated: 0, graduated: 0, byLevel: {} };
4109
+ }
4110
+ return this.graduationWorker.forceRun();
4111
+ }
4112
+ /**
4113
+ * Record access to a memory event (for graduation scoring)
4114
+ */
4115
+ recordMemoryAccess(eventId, sessionId, confidence = 1) {
4116
+ this.graduation.recordAccess(eventId, sessionId, confidence);
4117
+ }
3877
4118
  /**
3878
4119
  * Shutdown service
3879
4120
  */
3880
4121
  async shutdown() {
4122
+ if (this.graduationWorker) {
4123
+ this.graduationWorker.stop();
4124
+ }
3881
4125
  if (this.consolidationWorker) {
3882
4126
  this.consolidationWorker.stop();
3883
4127
  }
@@ -3900,14 +4144,11 @@ var MemoryService = class {
3900
4144
  }
3901
4145
  };
3902
4146
  var serviceCache = /* @__PURE__ */ new Map();
3903
- var GLOBAL_KEY = "__global__";
3904
- function getDefaultMemoryService() {
3905
- if (!serviceCache.has(GLOBAL_KEY)) {
3906
- serviceCache.set(GLOBAL_KEY, new MemoryService({
3907
- storagePath: "~/.claude-code/memory"
3908
- }));
3909
- }
3910
- return serviceCache.get(GLOBAL_KEY);
4147
+ function getReadOnlyMemoryService() {
4148
+ return new MemoryService({
4149
+ storagePath: "~/.claude-code/memory",
4150
+ readOnly: true
4151
+ });
3911
4152
  }
3912
4153
  function getMemoryServiceForProject(projectPath, sharedStoreConfig) {
3913
4154
  const hash = hashProjectPath(projectPath);
@@ -3927,8 +4168,8 @@ var sessionsRouter = new Hono();
3927
4168
  sessionsRouter.get("/", async (c) => {
3928
4169
  const page = parseInt(c.req.query("page") || "1", 10);
3929
4170
  const pageSize = parseInt(c.req.query("pageSize") || "20", 10);
4171
+ const memoryService = getReadOnlyMemoryService();
3930
4172
  try {
3931
- const memoryService = getDefaultMemoryService();
3932
4173
  await memoryService.initialize();
3933
4174
  const recentEvents = await memoryService.getRecentEvents(1e3);
3934
4175
  const sessionMap = /* @__PURE__ */ new Map();
@@ -3965,12 +4206,14 @@ sessionsRouter.get("/", async (c) => {
3965
4206
  });
3966
4207
  } catch (error) {
3967
4208
  return c.json({ error: error.message }, 500);
4209
+ } finally {
4210
+ await memoryService.shutdown();
3968
4211
  }
3969
4212
  });
3970
4213
  sessionsRouter.get("/:id", async (c) => {
3971
4214
  const { id } = c.req.param();
4215
+ const memoryService = getReadOnlyMemoryService();
3972
4216
  try {
3973
- const memoryService = getDefaultMemoryService();
3974
4217
  await memoryService.initialize();
3975
4218
  const events = await memoryService.getSessionHistory(id);
3976
4219
  if (events.length === 0) {
@@ -3999,6 +4242,8 @@ sessionsRouter.get("/:id", async (c) => {
3999
4242
  });
4000
4243
  } catch (error) {
4001
4244
  return c.json({ error: error.message }, 500);
4245
+ } finally {
4246
+ await memoryService.shutdown();
4002
4247
  }
4003
4248
  });
4004
4249
 
@@ -4010,8 +4255,8 @@ eventsRouter.get("/", async (c) => {
4010
4255
  const eventType = c.req.query("type");
4011
4256
  const limit = parseInt(c.req.query("limit") || "100", 10);
4012
4257
  const offset = parseInt(c.req.query("offset") || "0", 10);
4258
+ const memoryService = getReadOnlyMemoryService();
4013
4259
  try {
4014
- const memoryService = getDefaultMemoryService();
4015
4260
  await memoryService.initialize();
4016
4261
  let events = await memoryService.getRecentEvents(limit + offset + 1e3);
4017
4262
  if (sessionId) {
@@ -4038,12 +4283,14 @@ eventsRouter.get("/", async (c) => {
4038
4283
  });
4039
4284
  } catch (error) {
4040
4285
  return c.json({ error: error.message }, 500);
4286
+ } finally {
4287
+ await memoryService.shutdown();
4041
4288
  }
4042
4289
  });
4043
4290
  eventsRouter.get("/:id", async (c) => {
4044
4291
  const { id } = c.req.param();
4292
+ const memoryService = getReadOnlyMemoryService();
4045
4293
  try {
4046
- const memoryService = getDefaultMemoryService();
4047
4294
  await memoryService.initialize();
4048
4295
  const recentEvents = await memoryService.getRecentEvents(1e4);
4049
4296
  const event = recentEvents.find((e) => e.id === id);
@@ -4073,6 +4320,8 @@ eventsRouter.get("/:id", async (c) => {
4073
4320
  });
4074
4321
  } catch (error) {
4075
4322
  return c.json({ error: error.message }, 500);
4323
+ } finally {
4324
+ await memoryService.shutdown();
4076
4325
  }
4077
4326
  });
4078
4327
 
@@ -4080,12 +4329,12 @@ eventsRouter.get("/:id", async (c) => {
4080
4329
  import { Hono as Hono3 } from "hono";
4081
4330
  var searchRouter = new Hono3();
4082
4331
  searchRouter.post("/", async (c) => {
4332
+ const memoryService = getReadOnlyMemoryService();
4083
4333
  try {
4084
4334
  const body = await c.req.json();
4085
4335
  if (!body.query) {
4086
4336
  return c.json({ error: "Query is required" }, 400);
4087
4337
  }
4088
- const memoryService = getDefaultMemoryService();
4089
4338
  await memoryService.initialize();
4090
4339
  const startTime = Date.now();
4091
4340
  const result = await memoryService.retrieveMemories(body.query, {
@@ -4114,6 +4363,8 @@ searchRouter.post("/", async (c) => {
4114
4363
  });
4115
4364
  } catch (error) {
4116
4365
  return c.json({ error: error.message }, 500);
4366
+ } finally {
4367
+ await memoryService.shutdown();
4117
4368
  }
4118
4369
  });
4119
4370
  searchRouter.get("/", async (c) => {
@@ -4122,8 +4373,8 @@ searchRouter.get("/", async (c) => {
4122
4373
  return c.json({ error: 'Query parameter "q" is required' }, 400);
4123
4374
  }
4124
4375
  const topK = parseInt(c.req.query("topK") || "5", 10);
4376
+ const memoryService = getReadOnlyMemoryService();
4125
4377
  try {
4126
- const memoryService = getDefaultMemoryService();
4127
4378
  await memoryService.initialize();
4128
4379
  const result = await memoryService.retrieveMemories(query, { topK });
4129
4380
  return c.json({
@@ -4141,6 +4392,8 @@ searchRouter.get("/", async (c) => {
4141
4392
  });
4142
4393
  } catch (error) {
4143
4394
  return c.json({ error: error.message }, 500);
4395
+ } finally {
4396
+ await memoryService.shutdown();
4144
4397
  }
4145
4398
  });
4146
4399
 
@@ -4148,8 +4401,8 @@ searchRouter.get("/", async (c) => {
4148
4401
  import { Hono as Hono4 } from "hono";
4149
4402
  var statsRouter = new Hono4();
4150
4403
  statsRouter.get("/shared", async (c) => {
4404
+ const memoryService = getReadOnlyMemoryService();
4151
4405
  try {
4152
- const memoryService = getDefaultMemoryService();
4153
4406
  await memoryService.initialize();
4154
4407
  const sharedStats = await memoryService.getSharedStoreStats();
4155
4408
  return c.json({
@@ -4167,12 +4420,14 @@ statsRouter.get("/shared", async (c) => {
4167
4420
  totalUsageCount: 0,
4168
4421
  lastUpdated: null
4169
4422
  });
4423
+ } finally {
4424
+ await memoryService.shutdown();
4170
4425
  }
4171
4426
  });
4172
4427
  statsRouter.get("/endless", async (c) => {
4428
+ const projectPath = c.req.query("project") || process.cwd();
4429
+ const memoryService = getMemoryServiceForProject(projectPath);
4173
4430
  try {
4174
- const projectPath = c.req.query("project") || process.cwd();
4175
- const memoryService = getMemoryServiceForProject(projectPath);
4176
4431
  await memoryService.initialize();
4177
4432
  const status = await memoryService.getEndlessModeStatus();
4178
4433
  return c.json({
@@ -4190,11 +4445,48 @@ statsRouter.get("/endless", async (c) => {
4190
4445
  consolidatedCount: 0,
4191
4446
  lastConsolidation: null
4192
4447
  });
4448
+ } finally {
4449
+ await memoryService.shutdown();
4450
+ }
4451
+ });
4452
+ statsRouter.get("/levels/:level", async (c) => {
4453
+ const { level } = c.req.param();
4454
+ const limit = parseInt(c.req.query("limit") || "20", 10);
4455
+ const offset = parseInt(c.req.query("offset") || "0", 10);
4456
+ const validLevels = ["L0", "L1", "L2", "L3", "L4"];
4457
+ if (!validLevels.includes(level)) {
4458
+ return c.json({ error: `Invalid level. Must be one of: ${validLevels.join(", ")}` }, 400);
4459
+ }
4460
+ const memoryService = getReadOnlyMemoryService();
4461
+ try {
4462
+ await memoryService.initialize();
4463
+ const events = await memoryService.getEventsByLevel(level, { limit, offset });
4464
+ const stats = await memoryService.getStats();
4465
+ const levelStat = stats.levelStats.find((s) => s.level === level);
4466
+ return c.json({
4467
+ level,
4468
+ events: events.map((e) => ({
4469
+ id: e.id,
4470
+ eventType: e.eventType,
4471
+ sessionId: e.sessionId,
4472
+ timestamp: e.timestamp.toISOString(),
4473
+ content: e.content.slice(0, 500) + (e.content.length > 500 ? "..." : ""),
4474
+ metadata: e.metadata
4475
+ })),
4476
+ total: levelStat?.count || 0,
4477
+ limit,
4478
+ offset,
4479
+ hasMore: events.length === limit
4480
+ });
4481
+ } catch (error) {
4482
+ return c.json({ error: error.message }, 500);
4483
+ } finally {
4484
+ await memoryService.shutdown();
4193
4485
  }
4194
4486
  });
4195
4487
  statsRouter.get("/", async (c) => {
4488
+ const memoryService = getReadOnlyMemoryService();
4196
4489
  try {
4197
- const memoryService = getDefaultMemoryService();
4198
4490
  await memoryService.initialize();
4199
4491
  const stats = await memoryService.getStats();
4200
4492
  const recentEvents = await memoryService.getRecentEvents(1e4);
@@ -4231,12 +4523,43 @@ statsRouter.get("/", async (c) => {
4231
4523
  });
4232
4524
  } catch (error) {
4233
4525
  return c.json({ error: error.message }, 500);
4526
+ } finally {
4527
+ await memoryService.shutdown();
4528
+ }
4529
+ });
4530
+ statsRouter.get("/most-accessed", async (c) => {
4531
+ const limit = parseInt(c.req.query("limit") || "10", 10);
4532
+ const projectPath = c.req.query("project") || process.cwd();
4533
+ const memoryService = getMemoryServiceForProject(projectPath);
4534
+ try {
4535
+ await memoryService.initialize();
4536
+ const memories = await memoryService.getMostAccessedMemories(limit);
4537
+ return c.json({
4538
+ memories: memories.map((m) => ({
4539
+ memoryId: m.memoryId,
4540
+ summary: m.summary,
4541
+ topics: m.topics,
4542
+ accessCount: m.accessCount,
4543
+ lastAccessed: m.accessedAt?.toISOString() || null,
4544
+ confidence: m.confidence,
4545
+ createdAt: m.createdAt.toISOString()
4546
+ })),
4547
+ total: memories.length
4548
+ });
4549
+ } catch (error) {
4550
+ return c.json({
4551
+ memories: [],
4552
+ total: 0,
4553
+ error: error.message
4554
+ });
4555
+ } finally {
4556
+ await memoryService.shutdown();
4234
4557
  }
4235
4558
  });
4236
4559
  statsRouter.get("/timeline", async (c) => {
4237
4560
  const days = parseInt(c.req.query("days") || "7", 10);
4561
+ const memoryService = getReadOnlyMemoryService();
4238
4562
  try {
4239
- const memoryService = getDefaultMemoryService();
4240
4563
  await memoryService.initialize();
4241
4564
  const recentEvents = await memoryService.getRecentEvents(1e4);
4242
4565
  const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
@@ -4261,8 +4584,46 @@ statsRouter.get("/timeline", async (c) => {
4261
4584
  });
4262
4585
  } catch (error) {
4263
4586
  return c.json({ error: error.message }, 500);
4587
+ } finally {
4588
+ await memoryService.shutdown();
4264
4589
  }
4265
4590
  });
4591
+ statsRouter.post("/graduation/run", async (c) => {
4592
+ const memoryService = getReadOnlyMemoryService();
4593
+ try {
4594
+ await memoryService.initialize();
4595
+ const result = await memoryService.forceGraduation();
4596
+ return c.json({
4597
+ success: true,
4598
+ evaluated: result.evaluated,
4599
+ graduated: result.graduated,
4600
+ byLevel: result.byLevel
4601
+ });
4602
+ } catch (error) {
4603
+ return c.json({
4604
+ success: false,
4605
+ error: error.message
4606
+ }, 500);
4607
+ } finally {
4608
+ await memoryService.shutdown();
4609
+ }
4610
+ });
4611
+ statsRouter.get("/graduation", async (c) => {
4612
+ return c.json({
4613
+ criteria: {
4614
+ L0toL1: { minAccessCount: 1, minConfidence: 0.5, minCrossSessionRefs: 0, maxAgeDays: 30 },
4615
+ L1toL2: { minAccessCount: 3, minConfidence: 0.7, minCrossSessionRefs: 1, maxAgeDays: 60 },
4616
+ L2toL3: { minAccessCount: 5, minConfidence: 0.85, minCrossSessionRefs: 2, maxAgeDays: 90 },
4617
+ L3toL4: { minAccessCount: 10, minConfidence: 0.92, minCrossSessionRefs: 3, maxAgeDays: 180 }
4618
+ },
4619
+ description: {
4620
+ accessCount: "Number of times the memory was retrieved/referenced",
4621
+ confidence: "Match confidence score when retrieved (0.0-1.0)",
4622
+ crossSessionRefs: "Number of different sessions that referenced this memory",
4623
+ maxAgeDays: "Maximum days since last access (prevents stale promotion)"
4624
+ }
4625
+ });
4626
+ });
4266
4627
 
4267
4628
  // src/server/api/citations.ts
4268
4629
  import { Hono as Hono5 } from "hono";
@@ -4289,8 +4650,8 @@ var citationsRouter = new Hono5();
4289
4650
  citationsRouter.get("/:id", async (c) => {
4290
4651
  const { id } = c.req.param();
4291
4652
  const citationId = parseCitationId(id) || id;
4653
+ const memoryService = getReadOnlyMemoryService();
4292
4654
  try {
4293
- const memoryService = getDefaultMemoryService();
4294
4655
  await memoryService.initialize();
4295
4656
  const recentEvents = await memoryService.getRecentEvents(1e4);
4296
4657
  const event = recentEvents.find((e) => {
@@ -4316,13 +4677,15 @@ citationsRouter.get("/:id", async (c) => {
4316
4677
  });
4317
4678
  } catch (error) {
4318
4679
  return c.json({ error: error.message }, 500);
4680
+ } finally {
4681
+ await memoryService.shutdown();
4319
4682
  }
4320
4683
  });
4321
4684
  citationsRouter.get("/:id/related", async (c) => {
4322
4685
  const { id } = c.req.param();
4323
4686
  const citationId = parseCitationId(id) || id;
4687
+ const memoryService = getReadOnlyMemoryService();
4324
4688
  try {
4325
- const memoryService = getDefaultMemoryService();
4326
4689
  await memoryService.initialize();
4327
4690
  const recentEvents = await memoryService.getRecentEvents(1e4);
4328
4691
  const event = recentEvents.find((e) => {
@@ -4352,6 +4715,8 @@ citationsRouter.get("/:id/related", async (c) => {
4352
4715
  });
4353
4716
  } catch (error) {
4354
4717
  return c.json({ error: error.message }, 500);
4718
+ } finally {
4719
+ await memoryService.shutdown();
4355
4720
  }
4356
4721
  });
4357
4722