claude-memory-layer 1.0.18 → 1.0.19

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 (35) hide show
  1. package/config/kpi-thresholds.json +7 -0
  2. package/dist/cli/index.js +372 -74
  3. package/dist/cli/index.js.map +3 -3
  4. package/dist/hooks/post-tool-use.js +6 -0
  5. package/dist/hooks/post-tool-use.js.map +2 -2
  6. package/dist/hooks/session-end.js +6 -0
  7. package/dist/hooks/session-end.js.map +2 -2
  8. package/dist/hooks/session-start.js +6 -0
  9. package/dist/hooks/session-start.js.map +2 -2
  10. package/dist/hooks/stop.js +6 -0
  11. package/dist/hooks/stop.js.map +2 -2
  12. package/dist/hooks/user-prompt-submit.js +245 -31
  13. package/dist/hooks/user-prompt-submit.js.map +3 -3
  14. package/dist/server/api/index.js +329 -31
  15. package/dist/server/api/index.js.map +3 -3
  16. package/dist/server/index.js +336 -38
  17. package/dist/server/index.js.map +3 -3
  18. package/dist/services/memory-service.js +6 -0
  19. package/dist/services/memory-service.js.map +2 -2
  20. package/dist/ui/app.js +236 -4
  21. package/dist/ui/index.html +51 -0
  22. package/dist/ui/style.css +34 -0
  23. package/memory/_index.md +3 -0
  24. package/memory/agent_response/uncategorized/2026-03-03.md +14 -0
  25. package/memory/session_summary/uncategorized/2026-03-03.md +5 -0
  26. package/memory/tool_observation/uncategorized/2026-03-03.md +21 -0
  27. package/package.json +3 -2
  28. package/scripts/delete-unknown-projects.js +154 -0
  29. package/src/hooks/user-prompt-submit.ts +225 -29
  30. package/src/server/api/events.ts +1 -0
  31. package/src/server/api/stats.ts +346 -0
  32. package/src/services/memory-service.ts +7 -0
  33. package/src/ui/app.js +236 -4
  34. package/src/ui/index.html +51 -0
  35. package/src/ui/style.css +34 -0
@@ -18,8 +18,8 @@ import { cors } from "hono/cors";
18
18
  import { logger } from "hono/logger";
19
19
  import { serve } from "@hono/node-server";
20
20
  import { serveStatic } from "@hono/node-server/serve-static";
21
- import * as path6 from "path";
22
- import * as fs6 from "fs";
21
+ import * as path7 from "path";
22
+ import * as fs7 from "fs";
23
23
 
24
24
  // src/server/api/index.ts
25
25
  import { Hono as Hono10 } from "hono";
@@ -92,57 +92,57 @@ function toDate(value) {
92
92
  return new Date(value);
93
93
  return new Date(String(value));
94
94
  }
95
- function createDatabase(path7, options) {
95
+ function createDatabase(path8, options) {
96
96
  if (options?.readOnly) {
97
- return new duckdb.Database(path7, { access_mode: "READ_ONLY" });
97
+ return new duckdb.Database(path8, { access_mode: "READ_ONLY" });
98
98
  }
99
- return new duckdb.Database(path7);
99
+ return new duckdb.Database(path8);
100
100
  }
101
101
  function dbRun(db, sql, params = []) {
102
- return new Promise((resolve2, reject) => {
102
+ return new Promise((resolve3, reject) => {
103
103
  if (params.length === 0) {
104
104
  db.run(sql, (err) => {
105
105
  if (err)
106
106
  reject(err);
107
107
  else
108
- resolve2();
108
+ resolve3();
109
109
  });
110
110
  } else {
111
111
  db.run(sql, ...params, (err) => {
112
112
  if (err)
113
113
  reject(err);
114
114
  else
115
- resolve2();
115
+ resolve3();
116
116
  });
117
117
  }
118
118
  });
119
119
  }
120
120
  function dbAll(db, sql, params = []) {
121
- return new Promise((resolve2, reject) => {
121
+ return new Promise((resolve3, reject) => {
122
122
  if (params.length === 0) {
123
123
  db.all(sql, (err, rows) => {
124
124
  if (err)
125
125
  reject(err);
126
126
  else
127
- resolve2(convertBigInts(rows || []));
127
+ resolve3(convertBigInts(rows || []));
128
128
  });
129
129
  } else {
130
130
  db.all(sql, ...params, (err, rows) => {
131
131
  if (err)
132
132
  reject(err);
133
133
  else
134
- resolve2(convertBigInts(rows || []));
134
+ resolve3(convertBigInts(rows || []));
135
135
  });
136
136
  }
137
137
  });
138
138
  }
139
139
  function dbClose(db) {
140
- return new Promise((resolve2, reject) => {
140
+ return new Promise((resolve3, reject) => {
141
141
  db.close((err) => {
142
142
  if (err)
143
143
  reject(err);
144
144
  else
145
- resolve2();
145
+ resolve3();
146
146
  });
147
147
  });
148
148
  }
@@ -780,12 +780,12 @@ import { randomUUID as randomUUID2 } from "crypto";
780
780
  import Database from "better-sqlite3";
781
781
  import * as fs from "fs";
782
782
  import * as nodePath from "path";
783
- function createSQLiteDatabase(path7, options) {
784
- const dir = nodePath.dirname(path7);
783
+ function createSQLiteDatabase(path8, options) {
784
+ const dir = nodePath.dirname(path8);
785
785
  if (!fs.existsSync(dir)) {
786
786
  fs.mkdirSync(dir, { recursive: true });
787
787
  }
788
- const db = new Database(path7, {
788
+ const db = new Database(path8, {
789
789
  readonly: options?.readonly ?? false
790
790
  });
791
791
  if (!options?.readonly && (options?.walMode ?? true)) {
@@ -2431,7 +2431,7 @@ var SyncWorker = class {
2431
2431
  * Sleep utility
2432
2432
  */
2433
2433
  sleep(ms) {
2434
- return new Promise((resolve2) => setTimeout(resolve2, ms));
2434
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
2435
2435
  }
2436
2436
  /**
2437
2437
  * Get sync statistics
@@ -3461,8 +3461,8 @@ _Context:_ ${sessionContext}`;
3461
3461
  matchesMetadataScope(metadata, expected) {
3462
3462
  if (!metadata)
3463
3463
  return false;
3464
- return Object.entries(expected).every(([path7, value]) => {
3465
- const actual = path7.split(".").reduce((acc, key) => {
3464
+ return Object.entries(expected).every(([path8, value]) => {
3465
+ const actual = path8.split(".").reduce((acc, key) => {
3466
3466
  if (typeof acc !== "object" || acc === null)
3467
3467
  return void 0;
3468
3468
  return acc[key];
@@ -6899,6 +6899,12 @@ var MemoryService = class {
6899
6899
  recordMemoryAccess(eventId, sessionId, confidence = 1) {
6900
6900
  this.graduation.recordAccess(eventId, sessionId, confidence);
6901
6901
  }
6902
+ /**
6903
+ * Backward-compatible alias used by some hooks
6904
+ */
6905
+ async close() {
6906
+ await this.shutdown();
6907
+ }
6902
6908
  /**
6903
6909
  * Shutdown service
6904
6910
  */
@@ -7115,6 +7121,7 @@ eventsRouter.get("/", async (c) => {
7115
7121
  sessionId: e.sessionId,
7116
7122
  preview: e.content.slice(0, 200) + (e.content.length > 200 ? "..." : ""),
7117
7123
  contentLength: e.content.length,
7124
+ metadata: e.metadata,
7118
7125
  accessCount: e.access_count || 0,
7119
7126
  lastAccessedAt: e.last_accessed_at || null
7120
7127
  })),
@@ -7241,7 +7248,175 @@ searchRouter.get("/", async (c) => {
7241
7248
 
7242
7249
  // src/server/api/stats.ts
7243
7250
  import { Hono as Hono4 } from "hono";
7251
+ import * as fs5 from "fs";
7252
+ import * as path5 from "path";
7244
7253
  var statsRouter = new Hono4();
7254
+ var DEFAULT_KPI_THRESHOLDS = {
7255
+ usefulRecallRateMin: 0.45,
7256
+ reworkRateMax: 0.25,
7257
+ postChangeFailureRateMax: 0.2,
7258
+ avgCompletionTurnsMax: 12,
7259
+ memoryHitRateMin: 0.35
7260
+ };
7261
+ function loadKpiThresholds() {
7262
+ try {
7263
+ const filePath = path5.resolve(process.cwd(), "config", "kpi-thresholds.json");
7264
+ if (!fs5.existsSync(filePath))
7265
+ return DEFAULT_KPI_THRESHOLDS;
7266
+ const parsed = JSON.parse(fs5.readFileSync(filePath, "utf-8"));
7267
+ return {
7268
+ usefulRecallRateMin: Number(parsed.usefulRecallRateMin ?? DEFAULT_KPI_THRESHOLDS.usefulRecallRateMin),
7269
+ reworkRateMax: Number(parsed.reworkRateMax ?? DEFAULT_KPI_THRESHOLDS.reworkRateMax),
7270
+ postChangeFailureRateMax: Number(parsed.postChangeFailureRateMax ?? DEFAULT_KPI_THRESHOLDS.postChangeFailureRateMax),
7271
+ avgCompletionTurnsMax: Number(parsed.avgCompletionTurnsMax ?? DEFAULT_KPI_THRESHOLDS.avgCompletionTurnsMax),
7272
+ memoryHitRateMin: Number(parsed.memoryHitRateMin ?? DEFAULT_KPI_THRESHOLDS.memoryHitRateMin)
7273
+ };
7274
+ } catch {
7275
+ return DEFAULT_KPI_THRESHOLDS;
7276
+ }
7277
+ }
7278
+ function windowToMs(window) {
7279
+ if (window === "24h")
7280
+ return 24 * 60 * 60 * 1e3;
7281
+ if (window === "7d")
7282
+ return 7 * 24 * 60 * 60 * 1e3;
7283
+ return 30 * 24 * 60 * 60 * 1e3;
7284
+ }
7285
+ function inWindow(e, now, window) {
7286
+ return now - e.timestamp.getTime() <= windowToMs(window);
7287
+ }
7288
+ function isEditToolName(name) {
7289
+ return ["Write", "Edit", "MultiEdit", "NotebookEdit"].includes(name);
7290
+ }
7291
+ function parseToolPayload(e) {
7292
+ if (e.eventType !== "tool_observation")
7293
+ return null;
7294
+ try {
7295
+ const payload = JSON.parse(e.content);
7296
+ return {
7297
+ toolName: payload?.toolName,
7298
+ success: payload?.success,
7299
+ filePath: payload?.metadata?.filePath,
7300
+ command: payload?.metadata?.command
7301
+ };
7302
+ } catch {
7303
+ return {
7304
+ toolName: e.metadata?.toolName,
7305
+ success: e.metadata?.success,
7306
+ filePath: e.metadata?.filePath,
7307
+ command: e.metadata?.command
7308
+ };
7309
+ }
7310
+ }
7311
+ function isTestLikeCommand(command) {
7312
+ if (!command)
7313
+ return false;
7314
+ return /(test|jest|vitest|pytest|go test|cargo test|lint|eslint|build|tsc)/i.test(command);
7315
+ }
7316
+ function safeRatio(num, den) {
7317
+ if (!Number.isFinite(num) || !Number.isFinite(den) || den <= 0)
7318
+ return 0;
7319
+ return num / den;
7320
+ }
7321
+ function round(value, digits = 4) {
7322
+ const factor = 10 ** digits;
7323
+ return Math.round(value * factor) / factor;
7324
+ }
7325
+ function computeSessionTurnCount(sessionEvents) {
7326
+ const turnIds = /* @__PURE__ */ new Set();
7327
+ for (const e of sessionEvents) {
7328
+ const turnId = e.metadata?.turnId;
7329
+ if (typeof turnId === "string" && turnId.length > 0)
7330
+ turnIds.add(turnId);
7331
+ }
7332
+ if (turnIds.size > 0)
7333
+ return turnIds.size;
7334
+ return sessionEvents.filter((e) => e.eventType === "user_prompt").length;
7335
+ }
7336
+ function computeKpiMetrics(events, usefulRecallRate) {
7337
+ const prompts = events.filter((e) => e.eventType === "user_prompt");
7338
+ const promptCount = prompts.length;
7339
+ const memoryHitPrompts = prompts.filter((p) => p.metadata?.adherence?.checked).length;
7340
+ const memoryHitRate = round(safeRatio(memoryHitPrompts, promptCount));
7341
+ const sessions = /* @__PURE__ */ new Map();
7342
+ for (const e of events) {
7343
+ const arr = sessions.get(e.sessionId) || [];
7344
+ arr.push(e);
7345
+ sessions.set(e.sessionId, arr);
7346
+ }
7347
+ let sessionTurnTotal = 0;
7348
+ let sessionTurnSamples = 0;
7349
+ let firstValidEditMinutesTotal = 0;
7350
+ let firstValidEditSamples = 0;
7351
+ for (const sessionEvents of sessions.values()) {
7352
+ sessionEvents.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
7353
+ const turns = computeSessionTurnCount(sessionEvents);
7354
+ if (turns > 0) {
7355
+ sessionTurnTotal += turns;
7356
+ sessionTurnSamples++;
7357
+ }
7358
+ const firstPrompt = sessionEvents.find((e) => e.eventType === "user_prompt");
7359
+ const firstEdit = sessionEvents.find((e) => {
7360
+ const payload = parseToolPayload(e);
7361
+ return payload?.toolName && isEditToolName(payload.toolName) && payload.success === true;
7362
+ });
7363
+ if (firstPrompt && firstEdit) {
7364
+ const minutes = (firstEdit.timestamp.getTime() - firstPrompt.timestamp.getTime()) / 6e4;
7365
+ if (minutes >= 0) {
7366
+ firstValidEditMinutesTotal += minutes;
7367
+ firstValidEditSamples++;
7368
+ }
7369
+ }
7370
+ }
7371
+ const avgCompletionTurns = round(safeRatio(sessionTurnTotal, sessionTurnSamples), 2);
7372
+ const timeToFirstValidEditMinutes = round(safeRatio(firstValidEditMinutesTotal, firstValidEditSamples), 2);
7373
+ const editActions = [];
7374
+ let testRunsAfterEdit = 0;
7375
+ let failedTestRunsAfterEdit = 0;
7376
+ for (const [sessionId, sessionEvents] of sessions.entries()) {
7377
+ const sorted = [...sessionEvents].sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
7378
+ let seenEdit = false;
7379
+ for (const e of sorted) {
7380
+ const payload = parseToolPayload(e);
7381
+ if (!payload?.toolName)
7382
+ continue;
7383
+ if (isEditToolName(payload.toolName) && payload.success === true) {
7384
+ editActions.push({ sessionId, timestamp: e.timestamp.getTime(), filePath: payload.filePath });
7385
+ seenEdit = true;
7386
+ continue;
7387
+ }
7388
+ if (seenEdit && isTestLikeCommand(payload.command)) {
7389
+ testRunsAfterEdit++;
7390
+ if (payload.success === false)
7391
+ failedTestRunsAfterEdit++;
7392
+ }
7393
+ }
7394
+ }
7395
+ const THIRTY_MIN_MS = 30 * 60 * 1e3;
7396
+ let reworkCount = 0;
7397
+ const bySessionFile = /* @__PURE__ */ new Map();
7398
+ const sortedEdits = [...editActions].sort((a, b) => a.timestamp - b.timestamp);
7399
+ for (const edit of sortedEdits) {
7400
+ if (!edit.filePath)
7401
+ continue;
7402
+ const key = `${edit.sessionId}::${edit.filePath}`;
7403
+ const prev = bySessionFile.get(key);
7404
+ if (typeof prev === "number" && edit.timestamp - prev <= THIRTY_MIN_MS) {
7405
+ reworkCount++;
7406
+ }
7407
+ bySessionFile.set(key, edit.timestamp);
7408
+ }
7409
+ const reworkRate = round(safeRatio(reworkCount, editActions.length));
7410
+ const postChangeFailureRate = round(safeRatio(failedTestRunsAfterEdit, testRunsAfterEdit));
7411
+ return {
7412
+ memoryHitRate,
7413
+ usefulRecallRate,
7414
+ avgCompletionTurns,
7415
+ timeToFirstValidEditMinutes,
7416
+ reworkRate,
7417
+ postChangeFailureRate
7418
+ };
7419
+ }
7245
7420
  statsRouter.get("/shared", async (c) => {
7246
7421
  const memoryService = getServiceFromQuery(c);
7247
7422
  try {
@@ -7521,6 +7696,129 @@ statsRouter.get("/retrieval-traces", async (c) => {
7521
7696
  await memoryService.shutdown();
7522
7697
  }
7523
7698
  });
7699
+ statsRouter.get("/kpi", async (c) => {
7700
+ const rawWindow = c.req.query("window") || "7d";
7701
+ const window = rawWindow === "24h" || rawWindow === "30d" ? rawWindow : "7d";
7702
+ const memoryService = getServiceFromQuery(c);
7703
+ try {
7704
+ await memoryService.initialize();
7705
+ const now = Date.now();
7706
+ const thresholds = loadKpiThresholds();
7707
+ const allEvents = await memoryService.getRecentEvents(2e4);
7708
+ const events = allEvents.filter((e) => inWindow(e, now, window));
7709
+ const helpfulness = await memoryService.getHelpfulnessStats();
7710
+ const usefulRecallRate = helpfulness.totalEvaluated > 0 ? round(safeRatio(helpfulness.helpful, helpfulness.totalEvaluated)) : 0;
7711
+ const metrics = computeKpiMetrics(events, usefulRecallRate);
7712
+ const windowMs = windowToMs(window);
7713
+ const prevEvents = allEvents.filter((e) => {
7714
+ const age = now - e.timestamp.getTime();
7715
+ return age > windowMs && age <= windowMs * 2;
7716
+ });
7717
+ const previousMetrics = computeKpiMetrics(prevEvents, usefulRecallRate);
7718
+ const deltas = {
7719
+ memoryHitRate: round(metrics.memoryHitRate - previousMetrics.memoryHitRate),
7720
+ usefulRecallRate: round(metrics.usefulRecallRate - previousMetrics.usefulRecallRate),
7721
+ avgCompletionTurns: round(metrics.avgCompletionTurns - previousMetrics.avgCompletionTurns, 2),
7722
+ timeToFirstValidEditMinutes: round(metrics.timeToFirstValidEditMinutes - previousMetrics.timeToFirstValidEditMinutes, 2),
7723
+ reworkRate: round(metrics.reworkRate - previousMetrics.reworkRate),
7724
+ postChangeFailureRate: round(metrics.postChangeFailureRate - previousMetrics.postChangeFailureRate)
7725
+ };
7726
+ const THIRTY_MIN_MS = 30 * 60 * 1e3;
7727
+ const trendWindowMs = 30 * 24 * 60 * 60 * 1e3;
7728
+ const trendEvents = allEvents.filter((e) => now - e.timestamp.getTime() <= trendWindowMs);
7729
+ const buckets = /* @__PURE__ */ new Map();
7730
+ for (const e of trendEvents) {
7731
+ const day = e.timestamp.toISOString().split("T")[0];
7732
+ const arr = buckets.get(day) || [];
7733
+ arr.push(e);
7734
+ buckets.set(day, arr);
7735
+ }
7736
+ const trendDaily = Array.from(buckets.entries()).sort((a, b) => a[0].localeCompare(b[0])).map(([date, dayEvents]) => {
7737
+ const dayPrompts = dayEvents.filter((e) => e.eventType === "user_prompt");
7738
+ const dayPromptCount = dayPrompts.length;
7739
+ const dayMemoryHit = dayPrompts.filter((p) => p.metadata?.adherence?.checked).length;
7740
+ const dayEdits = dayEvents.filter((e) => {
7741
+ const p = parseToolPayload(e);
7742
+ return Boolean(p?.toolName && isEditToolName(p.toolName) && p.success === true);
7743
+ });
7744
+ const dayEditActions = dayEdits.map((e) => {
7745
+ const p = parseToolPayload(e);
7746
+ return { sessionId: e.sessionId, timestamp: e.timestamp.getTime(), filePath: p?.filePath };
7747
+ }).filter((x) => Boolean(x.filePath));
7748
+ let dayReworkCount = 0;
7749
+ const dayBySessionFile = /* @__PURE__ */ new Map();
7750
+ for (const edit of dayEditActions) {
7751
+ const key = `${edit.sessionId}::${edit.filePath}`;
7752
+ const prev = dayBySessionFile.get(key);
7753
+ if (typeof prev === "number" && edit.timestamp - prev <= THIRTY_MIN_MS)
7754
+ dayReworkCount++;
7755
+ dayBySessionFile.set(key, edit.timestamp);
7756
+ }
7757
+ const dayTests = dayEvents.filter((e) => {
7758
+ const p = parseToolPayload(e);
7759
+ return Boolean(p?.toolName && isTestLikeCommand(p.command));
7760
+ });
7761
+ const dayFailedTests = dayEvents.filter((e) => {
7762
+ const p = parseToolPayload(e);
7763
+ return Boolean(p?.toolName && isTestLikeCommand(p.command) && p.success === false);
7764
+ });
7765
+ const turnsBySession = /* @__PURE__ */ new Map();
7766
+ for (const e of dayEvents) {
7767
+ const arr = turnsBySession.get(e.sessionId) || [];
7768
+ arr.push(e);
7769
+ turnsBySession.set(e.sessionId, arr);
7770
+ }
7771
+ let dayTurnsTotal = 0;
7772
+ let dayTurnsSamples = 0;
7773
+ for (const sessionEvents of turnsBySession.values()) {
7774
+ const turns = computeSessionTurnCount(sessionEvents);
7775
+ if (turns > 0) {
7776
+ dayTurnsTotal += turns;
7777
+ dayTurnsSamples++;
7778
+ }
7779
+ }
7780
+ return {
7781
+ date,
7782
+ memoryHitRate: round(safeRatio(dayMemoryHit, dayPromptCount)),
7783
+ usefulRecallRate,
7784
+ reworkRate: round(safeRatio(dayReworkCount, dayEditActions.length)),
7785
+ postChangeFailureRate: round(safeRatio(dayFailedTests.length, dayTests.length)),
7786
+ avgCompletionTurns: round(safeRatio(dayTurnsTotal, dayTurnsSamples), 2)
7787
+ };
7788
+ });
7789
+ const alerts = [];
7790
+ if (metrics.usefulRecallRate < thresholds.usefulRecallRateMin) {
7791
+ alerts.push({ metric: "usefulRecallRate", level: "warn", message: "Useful recall rate is below threshold", value: metrics.usefulRecallRate, threshold: thresholds.usefulRecallRateMin });
7792
+ }
7793
+ if (metrics.reworkRate > thresholds.reworkRateMax) {
7794
+ alerts.push({ metric: "reworkRate", level: "warn", message: "Rework rate is above threshold", value: metrics.reworkRate, threshold: thresholds.reworkRateMax });
7795
+ }
7796
+ if (metrics.postChangeFailureRate > thresholds.postChangeFailureRateMax) {
7797
+ alerts.push({ metric: "postChangeFailureRate", level: "warn", message: "Post-change failure rate is above threshold", value: metrics.postChangeFailureRate, threshold: thresholds.postChangeFailureRateMax });
7798
+ }
7799
+ if (metrics.avgCompletionTurns > thresholds.avgCompletionTurnsMax) {
7800
+ alerts.push({ metric: "avgCompletionTurns", level: "warn", message: "Average completion turns is above threshold", value: metrics.avgCompletionTurns, threshold: thresholds.avgCompletionTurnsMax });
7801
+ }
7802
+ if (metrics.memoryHitRate < thresholds.memoryHitRateMin) {
7803
+ alerts.push({ metric: "memoryHitRate", level: "warn", message: "Memory hit rate is below threshold", value: metrics.memoryHitRate, threshold: thresholds.memoryHitRateMin });
7804
+ }
7805
+ return c.json({
7806
+ window,
7807
+ metrics,
7808
+ previousMetrics,
7809
+ deltas,
7810
+ trend: {
7811
+ daily: trendDaily
7812
+ },
7813
+ thresholds,
7814
+ alerts
7815
+ });
7816
+ } catch (error) {
7817
+ return c.json({ error: error.message }, 500);
7818
+ } finally {
7819
+ await memoryService.shutdown();
7820
+ }
7821
+ });
7524
7822
  statsRouter.post("/graduation/run", async (c) => {
7525
7823
  const memoryService = getServiceFromQuery(c);
7526
7824
  try {
@@ -7768,19 +8066,19 @@ turnsRouter.post("/backfill", async (c) => {
7768
8066
 
7769
8067
  // src/server/api/projects.ts
7770
8068
  import { Hono as Hono7 } from "hono";
7771
- import * as fs5 from "fs";
7772
- import * as path5 from "path";
8069
+ import * as fs6 from "fs";
8070
+ import * as path6 from "path";
7773
8071
  import * as os3 from "os";
7774
8072
  var projectsRouter = new Hono7();
7775
8073
  projectsRouter.get("/", async (c) => {
7776
8074
  try {
7777
- const projectsDir = path5.join(os3.homedir(), ".claude-code", "memory", "projects");
7778
- if (!fs5.existsSync(projectsDir)) {
8075
+ const projectsDir = path6.join(os3.homedir(), ".claude-code", "memory", "projects");
8076
+ if (!fs6.existsSync(projectsDir)) {
7779
8077
  return c.json({ projects: [] });
7780
8078
  }
7781
- const projectHashes = fs5.readdirSync(projectsDir).filter((name) => {
7782
- const fullPath = path5.join(projectsDir, name);
7783
- return fs5.statSync(fullPath).isDirectory();
8079
+ const projectHashes = fs6.readdirSync(projectsDir).filter((name) => {
8080
+ const fullPath = path6.join(projectsDir, name);
8081
+ return fs6.statSync(fullPath).isDirectory();
7784
8082
  });
7785
8083
  const registry = loadSessionRegistry();
7786
8084
  const hashToPath = /* @__PURE__ */ new Map();
@@ -7790,17 +8088,17 @@ projectsRouter.get("/", async (c) => {
7790
8088
  }
7791
8089
  }
7792
8090
  const projects = projectHashes.map((hash) => {
7793
- const dirPath = path5.join(projectsDir, hash);
7794
- const dbPath = path5.join(dirPath, "events.sqlite");
8091
+ const dirPath = path6.join(projectsDir, hash);
8092
+ const dbPath = path6.join(dirPath, "events.sqlite");
7795
8093
  let dbSize = 0;
7796
- if (fs5.existsSync(dbPath)) {
7797
- dbSize = fs5.statSync(dbPath).size;
8094
+ if (fs6.existsSync(dbPath)) {
8095
+ dbSize = fs6.statSync(dbPath).size;
7798
8096
  }
7799
8097
  const projectPath = hashToPath.get(hash) || `unknown (${hash})`;
7800
8098
  return {
7801
8099
  hash,
7802
8100
  projectPath,
7803
- projectName: path5.basename(projectPath),
8101
+ projectName: path6.basename(projectPath),
7804
8102
  dbSize,
7805
8103
  dbSizeHuman: formatBytes(dbSize)
7806
8104
  };
@@ -7925,7 +8223,7 @@ function buildPrompt(statsContext, memoryContext, history, currentMessage) {
7925
8223
  return parts.join("\n");
7926
8224
  }
7927
8225
  function streamClaudeResponse(prompt, stream) {
7928
- return new Promise((resolve2, reject) => {
8226
+ return new Promise((resolve3, reject) => {
7929
8227
  const proc = spawn("claude", [
7930
8228
  "-p",
7931
8229
  "--output-format",
@@ -7997,7 +8295,7 @@ function streamClaudeResponse(prompt, stream) {
7997
8295
  if (code !== 0 && code !== null) {
7998
8296
  reject(new Error(`Claude CLI exited with code ${code}`));
7999
8297
  } else {
8000
- resolve2();
8298
+ resolve3();
8001
8299
  }
8002
8300
  });
8003
8301
  });
@@ -8054,14 +8352,14 @@ app.use("/*", cors());
8054
8352
  app.use("/*", logger());
8055
8353
  app.route("/api", apiRouter);
8056
8354
  app.get("/health", (c) => c.json({ status: "ok", timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
8057
- var uiPath = path6.join(__dirname, "../../dist/ui");
8058
- if (fs6.existsSync(uiPath)) {
8355
+ var uiPath = path7.join(__dirname, "../../dist/ui");
8356
+ if (fs7.existsSync(uiPath)) {
8059
8357
  app.use("/*", serveStatic({ root: uiPath }));
8060
8358
  }
8061
8359
  app.get("*", (c) => {
8062
- const indexPath = path6.join(uiPath, "index.html");
8063
- if (fs6.existsSync(indexPath)) {
8064
- return c.html(fs6.readFileSync(indexPath, "utf-8"));
8360
+ const indexPath = path7.join(uiPath, "index.html");
8361
+ if (fs7.existsSync(indexPath)) {
8362
+ return c.html(fs7.readFileSync(indexPath, "utf-8"));
8065
8363
  }
8066
8364
  return c.text('UI not built. Run "npm run build:ui" first.', 404);
8067
8365
  });