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
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
  // ============================================================
@@ -1195,6 +1237,7 @@ var Retriever = class {
1195
1237
  matcher;
1196
1238
  sharedStore;
1197
1239
  sharedVectorStore;
1240
+ graduation;
1198
1241
  constructor(eventStore, vectorStore, embedder, matcher, sharedOptions) {
1199
1242
  this.eventStore = eventStore;
1200
1243
  this.vectorStore = vectorStore;
@@ -1203,6 +1246,12 @@ var Retriever = class {
1203
1246
  this.sharedStore = sharedOptions?.sharedStore;
1204
1247
  this.sharedVectorStore = sharedOptions?.sharedVectorStore;
1205
1248
  }
1249
+ /**
1250
+ * Set graduation pipeline for access tracking
1251
+ */
1252
+ setGraduationPipeline(graduation) {
1253
+ this.graduation = graduation;
1254
+ }
1206
1255
  /**
1207
1256
  * Set shared stores after construction
1208
1257
  */
@@ -1325,6 +1374,13 @@ var Retriever = class {
1325
1374
  const event = await this.eventStore.getEvent(result.eventId);
1326
1375
  if (!event)
1327
1376
  continue;
1377
+ if (this.graduation) {
1378
+ this.graduation.recordAccess(
1379
+ event.id,
1380
+ options.sessionId || "unknown",
1381
+ result.score
1382
+ );
1383
+ }
1328
1384
  let sessionContext;
1329
1385
  if (options.includeSessionContext) {
1330
1386
  sessionContext = await this.getSessionContext(event.sessionId, event.id);
@@ -1446,15 +1502,26 @@ var GraduationPipeline = class {
1446
1502
  L3toL4: { ...DEFAULT_CRITERIA.L3toL4, ...criteria.L3toL4 }
1447
1503
  };
1448
1504
  }
1505
+ // Track which sessions have accessed each event
1506
+ sessionAccesses = /* @__PURE__ */ new Map();
1449
1507
  /**
1450
1508
  * Record an access to an event (used for graduation scoring)
1451
1509
  */
1452
1510
  recordAccess(eventId, fromSessionId, confidence = 1) {
1453
1511
  const existing = this.metrics.get(eventId);
1512
+ if (!this.sessionAccesses.has(eventId)) {
1513
+ this.sessionAccesses.set(eventId, /* @__PURE__ */ new Set());
1514
+ }
1515
+ const sessions = this.sessionAccesses.get(eventId);
1516
+ const isNewSession = !sessions.has(fromSessionId);
1517
+ sessions.add(fromSessionId);
1454
1518
  if (existing) {
1455
1519
  existing.accessCount++;
1456
1520
  existing.lastAccessed = /* @__PURE__ */ new Date();
1457
1521
  existing.confidence = Math.max(existing.confidence, confidence);
1522
+ if (isNewSession && sessions.size > 1) {
1523
+ existing.crossSessionRefs = sessions.size - 1;
1524
+ }
1458
1525
  } else {
1459
1526
  this.metrics.set(eventId, {
1460
1527
  eventId,
@@ -3342,6 +3409,127 @@ function createContinuityManager(eventStore, config) {
3342
3409
  return new ContinuityManager(eventStore, config);
3343
3410
  }
3344
3411
 
3412
+ // src/core/graduation-worker.ts
3413
+ var DEFAULT_CONFIG4 = {
3414
+ evaluationIntervalMs: 3e5,
3415
+ // 5 minutes
3416
+ batchSize: 50,
3417
+ cooldownMs: 36e5
3418
+ // 1 hour cooldown between evaluations
3419
+ };
3420
+ var GraduationWorker = class {
3421
+ constructor(eventStore, graduation, config = DEFAULT_CONFIG4) {
3422
+ this.eventStore = eventStore;
3423
+ this.graduation = graduation;
3424
+ this.config = config;
3425
+ }
3426
+ running = false;
3427
+ timeout = null;
3428
+ lastEvaluated = /* @__PURE__ */ new Map();
3429
+ /**
3430
+ * Start the graduation worker
3431
+ */
3432
+ start() {
3433
+ if (this.running)
3434
+ return;
3435
+ this.running = true;
3436
+ this.scheduleNext();
3437
+ }
3438
+ /**
3439
+ * Stop the graduation worker
3440
+ */
3441
+ stop() {
3442
+ this.running = false;
3443
+ if (this.timeout) {
3444
+ clearTimeout(this.timeout);
3445
+ this.timeout = null;
3446
+ }
3447
+ }
3448
+ /**
3449
+ * Check if currently running
3450
+ */
3451
+ isRunning() {
3452
+ return this.running;
3453
+ }
3454
+ /**
3455
+ * Force a graduation evaluation run
3456
+ */
3457
+ async forceRun() {
3458
+ return await this.runGraduation();
3459
+ }
3460
+ /**
3461
+ * Schedule the next graduation check
3462
+ */
3463
+ scheduleNext() {
3464
+ if (!this.running)
3465
+ return;
3466
+ this.timeout = setTimeout(
3467
+ () => this.run(),
3468
+ this.config.evaluationIntervalMs
3469
+ );
3470
+ }
3471
+ /**
3472
+ * Run graduation evaluation
3473
+ */
3474
+ async run() {
3475
+ if (!this.running)
3476
+ return;
3477
+ try {
3478
+ await this.runGraduation();
3479
+ } catch (error) {
3480
+ console.error("Graduation error:", error);
3481
+ }
3482
+ this.scheduleNext();
3483
+ }
3484
+ /**
3485
+ * Perform graduation evaluation across all levels
3486
+ */
3487
+ async runGraduation() {
3488
+ const result = {
3489
+ evaluated: 0,
3490
+ graduated: 0,
3491
+ byLevel: {}
3492
+ };
3493
+ const levels = ["L0", "L1", "L2", "L3"];
3494
+ const now = Date.now();
3495
+ for (const level of levels) {
3496
+ const events = await this.eventStore.getEventsByLevel(level, {
3497
+ limit: this.config.batchSize
3498
+ });
3499
+ let levelGraduated = 0;
3500
+ for (const event of events) {
3501
+ const lastEval = this.lastEvaluated.get(event.id);
3502
+ if (lastEval && now - lastEval < this.config.cooldownMs) {
3503
+ continue;
3504
+ }
3505
+ result.evaluated++;
3506
+ this.lastEvaluated.set(event.id, now);
3507
+ const gradResult = await this.graduation.evaluateGraduation(event.id, level);
3508
+ if (gradResult.success) {
3509
+ result.graduated++;
3510
+ levelGraduated++;
3511
+ }
3512
+ }
3513
+ if (levelGraduated > 0) {
3514
+ result.byLevel[level] = levelGraduated;
3515
+ }
3516
+ }
3517
+ if (this.lastEvaluated.size > 1e3) {
3518
+ const entries = Array.from(this.lastEvaluated.entries());
3519
+ entries.sort((a, b) => b[1] - a[1]);
3520
+ this.lastEvaluated = new Map(entries.slice(0, 1e3));
3521
+ }
3522
+ return result;
3523
+ }
3524
+ };
3525
+ function createGraduationWorker(eventStore, graduation, config) {
3526
+ return new GraduationWorker(
3527
+ eventStore,
3528
+ graduation,
3529
+ { ...DEFAULT_CONFIG4, ...config }
3530
+ );
3531
+ }
3532
+
3345
3533
  // src/services/memory-service.ts
3346
3534
  function normalizePath(projectPath) {
3347
3535
  const expanded = projectPath.startsWith("~") ? path.join(os.homedir(), projectPath.slice(1)) : projectPath;
@@ -3369,6 +3557,7 @@ var MemoryService = class {
3369
3557
  retriever;
3370
3558
  graduation;
3371
3559
  vectorWorker = null;
3560
+ graduationWorker = null;
3372
3561
  initialized = false;
3373
3562
  // Endless Mode components
3374
3563
  workingSetStore = null;
@@ -3383,14 +3572,16 @@ var MemoryService = class {
3383
3572
  sharedPromoter = null;
3384
3573
  sharedStoreConfig = null;
3385
3574
  projectHash = null;
3575
+ readOnly;
3386
3576
  constructor(config) {
3387
3577
  const storagePath = this.expandPath(config.storagePath);
3388
- if (!fs.existsSync(storagePath)) {
3578
+ this.readOnly = config.readOnly ?? false;
3579
+ if (!this.readOnly && !fs.existsSync(storagePath)) {
3389
3580
  fs.mkdirSync(storagePath, { recursive: true });
3390
3581
  }
3391
3582
  this.projectHash = config.projectHash || null;
3392
3583
  this.sharedStoreConfig = config.sharedStoreConfig ?? { enabled: true };
3393
- this.eventStore = new EventStore(path.join(storagePath, "events.duckdb"));
3584
+ this.eventStore = new EventStore(path.join(storagePath, "events.duckdb"), { readOnly: this.readOnly });
3394
3585
  this.vectorStore = new VectorStore(path.join(storagePath, "vectors"));
3395
3586
  this.embedder = config.embeddingModel ? new Embedder(config.embeddingModel) : getDefaultEmbedder();
3396
3587
  this.matcher = getDefaultMatcher();
@@ -3411,19 +3602,27 @@ var MemoryService = class {
3411
3602
  await this.eventStore.initialize();
3412
3603
  await this.vectorStore.initialize();
3413
3604
  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();
3605
+ if (!this.readOnly) {
3606
+ this.vectorWorker = createVectorWorker(
3607
+ this.eventStore,
3608
+ this.vectorStore,
3609
+ this.embedder
3610
+ );
3611
+ this.vectorWorker.start();
3612
+ this.retriever.setGraduationPipeline(this.graduation);
3613
+ this.graduationWorker = createGraduationWorker(
3614
+ this.eventStore,
3615
+ this.graduation
3616
+ );
3617
+ this.graduationWorker.start();
3618
+ const savedMode = await this.eventStore.getEndlessConfig("mode");
3619
+ if (savedMode === "endless") {
3620
+ this.endlessMode = "endless";
3621
+ await this.initializeEndlessMode();
3622
+ }
3623
+ if (this.sharedStoreConfig?.enabled !== false) {
3624
+ await this.initializeSharedStore();
3625
+ }
3427
3626
  }
3428
3627
  this.initialized = true;
3429
3628
  }
@@ -3604,6 +3803,20 @@ var MemoryService = class {
3604
3803
  }
3605
3804
  return 0;
3606
3805
  }
3806
+ /**
3807
+ * Get events by memory level
3808
+ */
3809
+ async getEventsByLevel(level, options) {
3810
+ await this.initialize();
3811
+ return this.eventStore.getEventsByLevel(level, options);
3812
+ }
3813
+ /**
3814
+ * Get memory level for a specific event
3815
+ */
3816
+ async getEventLevel(eventId) {
3817
+ await this.initialize();
3818
+ return this.eventStore.getEventLevel(eventId);
3819
+ }
3607
3820
  /**
3608
3821
  * Format retrieval results as context for Claude
3609
3822
  */
@@ -3786,6 +3999,22 @@ var MemoryService = class {
3786
3999
  return [];
3787
4000
  return this.consolidatedStore.getAll({ limit });
3788
4001
  }
4002
+ /**
4003
+ * Get most accessed consolidated memories
4004
+ */
4005
+ async getMostAccessedMemories(limit = 10) {
4006
+ if (!this.consolidatedStore)
4007
+ return [];
4008
+ return this.consolidatedStore.getMostAccessed(limit);
4009
+ }
4010
+ /**
4011
+ * Mark a consolidated memory as accessed
4012
+ */
4013
+ async markMemoryAccessed(memoryId) {
4014
+ if (!this.consolidatedStore)
4015
+ return;
4016
+ await this.consolidatedStore.markAccessed(memoryId);
4017
+ }
3789
4018
  /**
3790
4019
  * Calculate continuity score for current context
3791
4020
  */
@@ -3873,10 +4102,28 @@ var MemoryService = class {
3873
4102
  }
3874
4103
  return parts.join("\n");
3875
4104
  }
4105
+ /**
4106
+ * Force a graduation evaluation run
4107
+ */
4108
+ async forceGraduation() {
4109
+ if (!this.graduationWorker) {
4110
+ return { evaluated: 0, graduated: 0, byLevel: {} };
4111
+ }
4112
+ return this.graduationWorker.forceRun();
4113
+ }
4114
+ /**
4115
+ * Record access to a memory event (for graduation scoring)
4116
+ */
4117
+ recordMemoryAccess(eventId, sessionId, confidence = 1) {
4118
+ this.graduation.recordAccess(eventId, sessionId, confidence);
4119
+ }
3876
4120
  /**
3877
4121
  * Shutdown service
3878
4122
  */
3879
4123
  async shutdown() {
4124
+ if (this.graduationWorker) {
4125
+ this.graduationWorker.stop();
4126
+ }
3880
4127
  if (this.consolidationWorker) {
3881
4128
  this.consolidationWorker.stop();
3882
4129
  }
@@ -3908,6 +4155,12 @@ function getDefaultMemoryService() {
3908
4155
  }
3909
4156
  return serviceCache.get(GLOBAL_KEY);
3910
4157
  }
4158
+ function getReadOnlyMemoryService() {
4159
+ return new MemoryService({
4160
+ storagePath: "~/.claude-code/memory",
4161
+ readOnly: true
4162
+ });
4163
+ }
3911
4164
  function getMemoryServiceForProject(projectPath, sharedStoreConfig) {
3912
4165
  const hash = hashProjectPath(projectPath);
3913
4166
  if (!serviceCache.has(hash)) {
@@ -4163,7 +4416,8 @@ function createSessionHistoryImporter(memoryService) {
4163
4416
  import { Hono as Hono7 } from "hono";
4164
4417
  import { cors } from "hono/cors";
4165
4418
  import { logger } from "hono/logger";
4166
- import { serveStatic } from "hono/bun";
4419
+ import { serve } from "@hono/node-server";
4420
+ import { serveStatic } from "@hono/node-server/serve-static";
4167
4421
  import * as path3 from "path";
4168
4422
  import * as fs3 from "fs";
4169
4423
 
@@ -4176,8 +4430,8 @@ var sessionsRouter = new Hono();
4176
4430
  sessionsRouter.get("/", async (c) => {
4177
4431
  const page = parseInt(c.req.query("page") || "1", 10);
4178
4432
  const pageSize = parseInt(c.req.query("pageSize") || "20", 10);
4433
+ const memoryService = getReadOnlyMemoryService();
4179
4434
  try {
4180
- const memoryService = getDefaultMemoryService();
4181
4435
  await memoryService.initialize();
4182
4436
  const recentEvents = await memoryService.getRecentEvents(1e3);
4183
4437
  const sessionMap = /* @__PURE__ */ new Map();
@@ -4214,12 +4468,14 @@ sessionsRouter.get("/", async (c) => {
4214
4468
  });
4215
4469
  } catch (error) {
4216
4470
  return c.json({ error: error.message }, 500);
4471
+ } finally {
4472
+ await memoryService.shutdown();
4217
4473
  }
4218
4474
  });
4219
4475
  sessionsRouter.get("/:id", async (c) => {
4220
4476
  const { id } = c.req.param();
4477
+ const memoryService = getReadOnlyMemoryService();
4221
4478
  try {
4222
- const memoryService = getDefaultMemoryService();
4223
4479
  await memoryService.initialize();
4224
4480
  const events = await memoryService.getSessionHistory(id);
4225
4481
  if (events.length === 0) {
@@ -4248,6 +4504,8 @@ sessionsRouter.get("/:id", async (c) => {
4248
4504
  });
4249
4505
  } catch (error) {
4250
4506
  return c.json({ error: error.message }, 500);
4507
+ } finally {
4508
+ await memoryService.shutdown();
4251
4509
  }
4252
4510
  });
4253
4511
 
@@ -4259,8 +4517,8 @@ eventsRouter.get("/", async (c) => {
4259
4517
  const eventType = c.req.query("type");
4260
4518
  const limit = parseInt(c.req.query("limit") || "100", 10);
4261
4519
  const offset = parseInt(c.req.query("offset") || "0", 10);
4520
+ const memoryService = getReadOnlyMemoryService();
4262
4521
  try {
4263
- const memoryService = getDefaultMemoryService();
4264
4522
  await memoryService.initialize();
4265
4523
  let events = await memoryService.getRecentEvents(limit + offset + 1e3);
4266
4524
  if (sessionId) {
@@ -4287,12 +4545,14 @@ eventsRouter.get("/", async (c) => {
4287
4545
  });
4288
4546
  } catch (error) {
4289
4547
  return c.json({ error: error.message }, 500);
4548
+ } finally {
4549
+ await memoryService.shutdown();
4290
4550
  }
4291
4551
  });
4292
4552
  eventsRouter.get("/:id", async (c) => {
4293
4553
  const { id } = c.req.param();
4554
+ const memoryService = getReadOnlyMemoryService();
4294
4555
  try {
4295
- const memoryService = getDefaultMemoryService();
4296
4556
  await memoryService.initialize();
4297
4557
  const recentEvents = await memoryService.getRecentEvents(1e4);
4298
4558
  const event = recentEvents.find((e) => e.id === id);
@@ -4322,6 +4582,8 @@ eventsRouter.get("/:id", async (c) => {
4322
4582
  });
4323
4583
  } catch (error) {
4324
4584
  return c.json({ error: error.message }, 500);
4585
+ } finally {
4586
+ await memoryService.shutdown();
4325
4587
  }
4326
4588
  });
4327
4589
 
@@ -4329,12 +4591,12 @@ eventsRouter.get("/:id", async (c) => {
4329
4591
  import { Hono as Hono3 } from "hono";
4330
4592
  var searchRouter = new Hono3();
4331
4593
  searchRouter.post("/", async (c) => {
4594
+ const memoryService = getReadOnlyMemoryService();
4332
4595
  try {
4333
4596
  const body = await c.req.json();
4334
4597
  if (!body.query) {
4335
4598
  return c.json({ error: "Query is required" }, 400);
4336
4599
  }
4337
- const memoryService = getDefaultMemoryService();
4338
4600
  await memoryService.initialize();
4339
4601
  const startTime = Date.now();
4340
4602
  const result = await memoryService.retrieveMemories(body.query, {
@@ -4363,6 +4625,8 @@ searchRouter.post("/", async (c) => {
4363
4625
  });
4364
4626
  } catch (error) {
4365
4627
  return c.json({ error: error.message }, 500);
4628
+ } finally {
4629
+ await memoryService.shutdown();
4366
4630
  }
4367
4631
  });
4368
4632
  searchRouter.get("/", async (c) => {
@@ -4371,8 +4635,8 @@ searchRouter.get("/", async (c) => {
4371
4635
  return c.json({ error: 'Query parameter "q" is required' }, 400);
4372
4636
  }
4373
4637
  const topK = parseInt(c.req.query("topK") || "5", 10);
4638
+ const memoryService = getReadOnlyMemoryService();
4374
4639
  try {
4375
- const memoryService = getDefaultMemoryService();
4376
4640
  await memoryService.initialize();
4377
4641
  const result = await memoryService.retrieveMemories(query, { topK });
4378
4642
  return c.json({
@@ -4390,6 +4654,8 @@ searchRouter.get("/", async (c) => {
4390
4654
  });
4391
4655
  } catch (error) {
4392
4656
  return c.json({ error: error.message }, 500);
4657
+ } finally {
4658
+ await memoryService.shutdown();
4393
4659
  }
4394
4660
  });
4395
4661
 
@@ -4397,8 +4663,8 @@ searchRouter.get("/", async (c) => {
4397
4663
  import { Hono as Hono4 } from "hono";
4398
4664
  var statsRouter = new Hono4();
4399
4665
  statsRouter.get("/shared", async (c) => {
4666
+ const memoryService = getReadOnlyMemoryService();
4400
4667
  try {
4401
- const memoryService = getDefaultMemoryService();
4402
4668
  await memoryService.initialize();
4403
4669
  const sharedStats = await memoryService.getSharedStoreStats();
4404
4670
  return c.json({
@@ -4416,12 +4682,14 @@ statsRouter.get("/shared", async (c) => {
4416
4682
  totalUsageCount: 0,
4417
4683
  lastUpdated: null
4418
4684
  });
4685
+ } finally {
4686
+ await memoryService.shutdown();
4419
4687
  }
4420
4688
  });
4421
4689
  statsRouter.get("/endless", async (c) => {
4690
+ const projectPath = c.req.query("project") || process.cwd();
4691
+ const memoryService = getMemoryServiceForProject(projectPath);
4422
4692
  try {
4423
- const projectPath = c.req.query("project") || process.cwd();
4424
- const memoryService = getMemoryServiceForProject(projectPath);
4425
4693
  await memoryService.initialize();
4426
4694
  const status = await memoryService.getEndlessModeStatus();
4427
4695
  return c.json({
@@ -4439,11 +4707,48 @@ statsRouter.get("/endless", async (c) => {
4439
4707
  consolidatedCount: 0,
4440
4708
  lastConsolidation: null
4441
4709
  });
4710
+ } finally {
4711
+ await memoryService.shutdown();
4712
+ }
4713
+ });
4714
+ statsRouter.get("/levels/:level", async (c) => {
4715
+ const { level } = c.req.param();
4716
+ const limit = parseInt(c.req.query("limit") || "20", 10);
4717
+ const offset = parseInt(c.req.query("offset") || "0", 10);
4718
+ const validLevels = ["L0", "L1", "L2", "L3", "L4"];
4719
+ if (!validLevels.includes(level)) {
4720
+ return c.json({ error: `Invalid level. Must be one of: ${validLevels.join(", ")}` }, 400);
4721
+ }
4722
+ const memoryService = getReadOnlyMemoryService();
4723
+ try {
4724
+ await memoryService.initialize();
4725
+ const events = await memoryService.getEventsByLevel(level, { limit, offset });
4726
+ const stats = await memoryService.getStats();
4727
+ const levelStat = stats.levelStats.find((s) => s.level === level);
4728
+ return c.json({
4729
+ level,
4730
+ events: events.map((e) => ({
4731
+ id: e.id,
4732
+ eventType: e.eventType,
4733
+ sessionId: e.sessionId,
4734
+ timestamp: e.timestamp.toISOString(),
4735
+ content: e.content.slice(0, 500) + (e.content.length > 500 ? "..." : ""),
4736
+ metadata: e.metadata
4737
+ })),
4738
+ total: levelStat?.count || 0,
4739
+ limit,
4740
+ offset,
4741
+ hasMore: events.length === limit
4742
+ });
4743
+ } catch (error) {
4744
+ return c.json({ error: error.message }, 500);
4745
+ } finally {
4746
+ await memoryService.shutdown();
4442
4747
  }
4443
4748
  });
4444
4749
  statsRouter.get("/", async (c) => {
4750
+ const memoryService = getReadOnlyMemoryService();
4445
4751
  try {
4446
- const memoryService = getDefaultMemoryService();
4447
4752
  await memoryService.initialize();
4448
4753
  const stats = await memoryService.getStats();
4449
4754
  const recentEvents = await memoryService.getRecentEvents(1e4);
@@ -4480,12 +4785,43 @@ statsRouter.get("/", async (c) => {
4480
4785
  });
4481
4786
  } catch (error) {
4482
4787
  return c.json({ error: error.message }, 500);
4788
+ } finally {
4789
+ await memoryService.shutdown();
4790
+ }
4791
+ });
4792
+ statsRouter.get("/most-accessed", async (c) => {
4793
+ const limit = parseInt(c.req.query("limit") || "10", 10);
4794
+ const projectPath = c.req.query("project") || process.cwd();
4795
+ const memoryService = getMemoryServiceForProject(projectPath);
4796
+ try {
4797
+ await memoryService.initialize();
4798
+ const memories = await memoryService.getMostAccessedMemories(limit);
4799
+ return c.json({
4800
+ memories: memories.map((m) => ({
4801
+ memoryId: m.memoryId,
4802
+ summary: m.summary,
4803
+ topics: m.topics,
4804
+ accessCount: m.accessCount,
4805
+ lastAccessed: m.accessedAt?.toISOString() || null,
4806
+ confidence: m.confidence,
4807
+ createdAt: m.createdAt.toISOString()
4808
+ })),
4809
+ total: memories.length
4810
+ });
4811
+ } catch (error) {
4812
+ return c.json({
4813
+ memories: [],
4814
+ total: 0,
4815
+ error: error.message
4816
+ });
4817
+ } finally {
4818
+ await memoryService.shutdown();
4483
4819
  }
4484
4820
  });
4485
4821
  statsRouter.get("/timeline", async (c) => {
4486
4822
  const days = parseInt(c.req.query("days") || "7", 10);
4823
+ const memoryService = getReadOnlyMemoryService();
4487
4824
  try {
4488
- const memoryService = getDefaultMemoryService();
4489
4825
  await memoryService.initialize();
4490
4826
  const recentEvents = await memoryService.getRecentEvents(1e4);
4491
4827
  const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
@@ -4510,8 +4846,46 @@ statsRouter.get("/timeline", async (c) => {
4510
4846
  });
4511
4847
  } catch (error) {
4512
4848
  return c.json({ error: error.message }, 500);
4849
+ } finally {
4850
+ await memoryService.shutdown();
4851
+ }
4852
+ });
4853
+ statsRouter.post("/graduation/run", async (c) => {
4854
+ const memoryService = getReadOnlyMemoryService();
4855
+ try {
4856
+ await memoryService.initialize();
4857
+ const result = await memoryService.forceGraduation();
4858
+ return c.json({
4859
+ success: true,
4860
+ evaluated: result.evaluated,
4861
+ graduated: result.graduated,
4862
+ byLevel: result.byLevel
4863
+ });
4864
+ } catch (error) {
4865
+ return c.json({
4866
+ success: false,
4867
+ error: error.message
4868
+ }, 500);
4869
+ } finally {
4870
+ await memoryService.shutdown();
4513
4871
  }
4514
4872
  });
4873
+ statsRouter.get("/graduation", async (c) => {
4874
+ return c.json({
4875
+ criteria: {
4876
+ L0toL1: { minAccessCount: 1, minConfidence: 0.5, minCrossSessionRefs: 0, maxAgeDays: 30 },
4877
+ L1toL2: { minAccessCount: 3, minConfidence: 0.7, minCrossSessionRefs: 1, maxAgeDays: 60 },
4878
+ L2toL3: { minAccessCount: 5, minConfidence: 0.85, minCrossSessionRefs: 2, maxAgeDays: 90 },
4879
+ L3toL4: { minAccessCount: 10, minConfidence: 0.92, minCrossSessionRefs: 3, maxAgeDays: 180 }
4880
+ },
4881
+ description: {
4882
+ accessCount: "Number of times the memory was retrieved/referenced",
4883
+ confidence: "Match confidence score when retrieved (0.0-1.0)",
4884
+ crossSessionRefs: "Number of different sessions that referenced this memory",
4885
+ maxAgeDays: "Maximum days since last access (prevents stale promotion)"
4886
+ }
4887
+ });
4888
+ });
4515
4889
 
4516
4890
  // src/server/api/citations.ts
4517
4891
  import { Hono as Hono5 } from "hono";
@@ -4538,8 +4912,8 @@ var citationsRouter = new Hono5();
4538
4912
  citationsRouter.get("/:id", async (c) => {
4539
4913
  const { id } = c.req.param();
4540
4914
  const citationId = parseCitationId(id) || id;
4915
+ const memoryService = getReadOnlyMemoryService();
4541
4916
  try {
4542
- const memoryService = getDefaultMemoryService();
4543
4917
  await memoryService.initialize();
4544
4918
  const recentEvents = await memoryService.getRecentEvents(1e4);
4545
4919
  const event = recentEvents.find((e) => {
@@ -4565,13 +4939,15 @@ citationsRouter.get("/:id", async (c) => {
4565
4939
  });
4566
4940
  } catch (error) {
4567
4941
  return c.json({ error: error.message }, 500);
4942
+ } finally {
4943
+ await memoryService.shutdown();
4568
4944
  }
4569
4945
  });
4570
4946
  citationsRouter.get("/:id/related", async (c) => {
4571
4947
  const { id } = c.req.param();
4572
4948
  const citationId = parseCitationId(id) || id;
4949
+ const memoryService = getReadOnlyMemoryService();
4573
4950
  try {
4574
- const memoryService = getDefaultMemoryService();
4575
4951
  await memoryService.initialize();
4576
4952
  const recentEvents = await memoryService.getRecentEvents(1e4);
4577
4953
  const event = recentEvents.find((e) => {
@@ -4601,6 +4977,8 @@ citationsRouter.get("/:id/related", async (c) => {
4601
4977
  });
4602
4978
  } catch (error) {
4603
4979
  return c.json({ error: error.message }, 500);
4980
+ } finally {
4981
+ await memoryService.shutdown();
4604
4982
  }
4605
4983
  });
4606
4984
 
@@ -4613,7 +4991,7 @@ app.use("/*", cors());
4613
4991
  app.use("/*", logger());
4614
4992
  app.route("/api", apiRouter);
4615
4993
  app.get("/health", (c) => c.json({ status: "ok", timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
4616
- var uiPath = path3.join(import.meta.dir, "../../dist/ui");
4994
+ var uiPath = path3.join(__dirname, "../../dist/ui");
4617
4995
  if (fs3.existsSync(uiPath)) {
4618
4996
  app.use("/*", serveStatic({ root: uiPath }));
4619
4997
  }
@@ -4629,17 +5007,17 @@ function startServer(port = 37777) {
4629
5007
  if (serverInstance) {
4630
5008
  return serverInstance;
4631
5009
  }
4632
- serverInstance = Bun.serve({
4633
- hostname: "127.0.0.1",
5010
+ serverInstance = serve({
5011
+ fetch: app.fetch,
4634
5012
  port,
4635
- fetch: app.fetch
5013
+ hostname: "127.0.0.1"
4636
5014
  });
4637
5015
  console.log(`\u{1F9E0} Code Memory viewer started at http://localhost:${port}`);
4638
5016
  return serverInstance;
4639
5017
  }
4640
5018
  function stopServer() {
4641
5019
  if (serverInstance) {
4642
- serverInstance.stop();
5020
+ serverInstance.close();
4643
5021
  serverInstance = null;
4644
5022
  }
4645
5023
  }
@@ -4651,14 +5029,166 @@ async function isServerRunning(port = 37777) {
4651
5029
  return false;
4652
5030
  }
4653
5031
  }
4654
- if (import.meta.main) {
5032
+ var isMainModule = process.argv[1]?.includes("server/index") || process.argv[1]?.endsWith("server.js");
5033
+ if (isMainModule) {
4655
5034
  const port = parseInt(process.env.PORT || "37777", 10);
4656
5035
  startServer(port);
4657
5036
  }
4658
5037
 
4659
5038
  // src/cli/index.ts
5039
+ var CLAUDE_SETTINGS_PATH = path4.join(os3.homedir(), ".claude", "settings.json");
5040
+ function getPluginPath() {
5041
+ const possiblePaths = [
5042
+ path4.join(__dirname, ".."),
5043
+ // When running from dist/cli
5044
+ path4.join(__dirname, "../..", "dist"),
5045
+ // When running from src
5046
+ path4.join(process.cwd(), "dist")
5047
+ // Current working directory
5048
+ ];
5049
+ for (const p of possiblePaths) {
5050
+ const hooksPath = path4.join(p, "hooks", "user-prompt-submit.js");
5051
+ if (fs4.existsSync(hooksPath)) {
5052
+ return p;
5053
+ }
5054
+ }
5055
+ return path4.join(os3.homedir(), ".npm-global", "lib", "node_modules", "claude-memory-layer", "dist");
5056
+ }
5057
+ function loadClaudeSettings() {
5058
+ try {
5059
+ if (fs4.existsSync(CLAUDE_SETTINGS_PATH)) {
5060
+ const content = fs4.readFileSync(CLAUDE_SETTINGS_PATH, "utf-8");
5061
+ return JSON.parse(content);
5062
+ }
5063
+ } catch (error) {
5064
+ console.error("Warning: Could not read existing settings:", error);
5065
+ }
5066
+ return {};
5067
+ }
5068
+ function saveClaudeSettings(settings) {
5069
+ const dir = path4.dirname(CLAUDE_SETTINGS_PATH);
5070
+ if (!fs4.existsSync(dir)) {
5071
+ fs4.mkdirSync(dir, { recursive: true });
5072
+ }
5073
+ const tempPath = CLAUDE_SETTINGS_PATH + ".tmp";
5074
+ fs4.writeFileSync(tempPath, JSON.stringify(settings, null, 2));
5075
+ fs4.renameSync(tempPath, CLAUDE_SETTINGS_PATH);
5076
+ }
5077
+ function getHooksConfig(pluginPath) {
5078
+ return {
5079
+ UserPromptSubmit: [
5080
+ {
5081
+ matcher: "",
5082
+ hooks: [
5083
+ {
5084
+ type: "command",
5085
+ command: `node ${path4.join(pluginPath, "hooks", "user-prompt-submit.js")}`
5086
+ }
5087
+ ]
5088
+ }
5089
+ ],
5090
+ PostToolUse: [
5091
+ {
5092
+ matcher: "",
5093
+ hooks: [
5094
+ {
5095
+ type: "command",
5096
+ command: `node ${path4.join(pluginPath, "hooks", "post-tool-use.js")}`
5097
+ }
5098
+ ]
5099
+ }
5100
+ ]
5101
+ };
5102
+ }
4660
5103
  var program = new Command();
4661
5104
  program.name("claude-memory-layer").description("Claude Code Memory Plugin CLI").version("1.0.0");
5105
+ program.command("install").description("Install hooks into Claude Code settings").option("--path <path>", "Custom plugin path (defaults to auto-detect)").action(async (options) => {
5106
+ try {
5107
+ const pluginPath = options.path || getPluginPath();
5108
+ const userPromptHook = path4.join(pluginPath, "hooks", "user-prompt-submit.js");
5109
+ if (!fs4.existsSync(userPromptHook)) {
5110
+ console.error(`
5111
+ \u274C Hook files not found at: ${pluginPath}`);
5112
+ console.error(' Make sure you have built the plugin with "npm run build"');
5113
+ process.exit(1);
5114
+ }
5115
+ const settings = loadClaudeSettings();
5116
+ const newHooks = getHooksConfig(pluginPath);
5117
+ settings.hooks = {
5118
+ ...settings.hooks,
5119
+ ...newHooks
5120
+ };
5121
+ saveClaudeSettings(settings);
5122
+ console.log("\n\u2705 Claude Memory Layer installed!\n");
5123
+ console.log("Hooks registered:");
5124
+ console.log(" - UserPromptSubmit: Memory retrieval on user input");
5125
+ console.log(" - PostToolUse: Store tool observations\n");
5126
+ console.log("Plugin path:", pluginPath);
5127
+ console.log("\n\u26A0\uFE0F Restart Claude Code for changes to take effect.\n");
5128
+ console.log("Commands:");
5129
+ console.log(" claude-memory-layer dashboard - Open web dashboard");
5130
+ console.log(" claude-memory-layer search - Search memories");
5131
+ console.log(" claude-memory-layer stats - View statistics");
5132
+ console.log(" claude-memory-layer uninstall - Remove hooks\n");
5133
+ } catch (error) {
5134
+ console.error("Install failed:", error);
5135
+ process.exit(1);
5136
+ }
5137
+ });
5138
+ program.command("uninstall").description("Remove hooks from Claude Code settings").action(async () => {
5139
+ try {
5140
+ const settings = loadClaudeSettings();
5141
+ if (!settings.hooks) {
5142
+ console.log("\n\u{1F4CB} No hooks installed.\n");
5143
+ return;
5144
+ }
5145
+ delete settings.hooks.UserPromptSubmit;
5146
+ delete settings.hooks.PostToolUse;
5147
+ if (Object.keys(settings.hooks).length === 0) {
5148
+ delete settings.hooks;
5149
+ }
5150
+ saveClaudeSettings(settings);
5151
+ console.log("\n\u2705 Claude Memory Layer uninstalled!\n");
5152
+ console.log("Hooks removed from Claude Code settings.");
5153
+ console.log("Your memory data is preserved and can be accessed with:");
5154
+ console.log(" claude-memory-layer dashboard\n");
5155
+ console.log("\u26A0\uFE0F Restart Claude Code for changes to take effect.\n");
5156
+ } catch (error) {
5157
+ console.error("Uninstall failed:", error);
5158
+ process.exit(1);
5159
+ }
5160
+ });
5161
+ program.command("status").description("Check plugin installation status").action(async () => {
5162
+ try {
5163
+ const settings = loadClaudeSettings();
5164
+ const pluginPath = getPluginPath();
5165
+ console.log("\n\u{1F9E0} Claude Memory Layer Status\n");
5166
+ const hasUserPromptHook = settings.hooks?.UserPromptSubmit?.some(
5167
+ (h) => h.hooks?.some((hook) => hook.command?.includes("user-prompt-submit"))
5168
+ );
5169
+ const hasPostToolHook = settings.hooks?.PostToolUse?.some(
5170
+ (h) => h.hooks?.some((hook) => hook.command?.includes("post-tool-use"))
5171
+ );
5172
+ console.log("Hooks:");
5173
+ console.log(` UserPromptSubmit: ${hasUserPromptHook ? "\u2705 Installed" : "\u274C Not installed"}`);
5174
+ console.log(` PostToolUse: ${hasPostToolHook ? "\u2705 Installed" : "\u274C Not installed"}`);
5175
+ const hooksExist = fs4.existsSync(path4.join(pluginPath, "hooks", "user-prompt-submit.js"));
5176
+ console.log(`
5177
+ Plugin files: ${hooksExist ? "\u2705 Found" : "\u274C Not found"}`);
5178
+ console.log(` Path: ${pluginPath}`);
5179
+ const dashboardRunning = await isServerRunning(37777);
5180
+ console.log(`
5181
+ Dashboard: ${dashboardRunning ? "\u2705 Running at http://localhost:37777" : "\u23F9\uFE0F Not running"}`);
5182
+ if (!hasUserPromptHook || !hasPostToolHook) {
5183
+ console.log('\n\u{1F4A1} Run "claude-memory-layer install" to set up hooks.\n');
5184
+ } else {
5185
+ console.log("\n\u2705 Plugin is fully installed and configured.\n");
5186
+ }
5187
+ } catch (error) {
5188
+ console.error("Status check failed:", error);
5189
+ process.exit(1);
5190
+ }
5191
+ });
4662
5192
  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
5193
  const projectPath = options.project || process.cwd();
4664
5194
  const service = getMemoryServiceForProject(projectPath);