claude-memory-layer 1.0.17 → 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 (40) 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 +29 -13
  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 +4 -0
  24. package/memory/agent_response/uncategorized/2026-02-26.md +151 -1
  25. package/memory/agent_response/uncategorized/2026-03-03.md +14 -0
  26. package/memory/session_summary/uncategorized/2026-02-26.md +13 -0
  27. package/memory/session_summary/uncategorized/2026-03-03.md +5 -0
  28. package/memory/tool_observation/uncategorized/2026-02-26.md +9 -1
  29. package/memory/tool_observation/uncategorized/2026-03-03.md +21 -0
  30. package/memory/user_prompt/uncategorized/2026-02-26.md +9 -0
  31. package/package.json +3 -2
  32. package/scripts/delete-unknown-projects.js +154 -0
  33. package/src/hooks/session-start.ts +9 -3
  34. package/src/hooks/user-prompt-submit.ts +225 -29
  35. package/src/server/api/events.ts +1 -0
  36. package/src/server/api/stats.ts +346 -0
  37. package/src/services/memory-service.ts +7 -0
  38. package/src/ui/app.js +236 -4
  39. package/src/ui/index.html +51 -0
  40. package/src/ui/style.css +34 -0
@@ -83,57 +83,57 @@ function toDate(value) {
83
83
  return new Date(value);
84
84
  return new Date(String(value));
85
85
  }
86
- function createDatabase(path6, options) {
86
+ function createDatabase(path7, options) {
87
87
  if (options?.readOnly) {
88
- return new duckdb.Database(path6, { access_mode: "READ_ONLY" });
88
+ return new duckdb.Database(path7, { access_mode: "READ_ONLY" });
89
89
  }
90
- return new duckdb.Database(path6);
90
+ return new duckdb.Database(path7);
91
91
  }
92
92
  function dbRun(db, sql, params = []) {
93
- return new Promise((resolve2, reject) => {
93
+ return new Promise((resolve3, reject) => {
94
94
  if (params.length === 0) {
95
95
  db.run(sql, (err) => {
96
96
  if (err)
97
97
  reject(err);
98
98
  else
99
- resolve2();
99
+ resolve3();
100
100
  });
101
101
  } else {
102
102
  db.run(sql, ...params, (err) => {
103
103
  if (err)
104
104
  reject(err);
105
105
  else
106
- resolve2();
106
+ resolve3();
107
107
  });
108
108
  }
109
109
  });
110
110
  }
111
111
  function dbAll(db, sql, params = []) {
112
- return new Promise((resolve2, reject) => {
112
+ return new Promise((resolve3, reject) => {
113
113
  if (params.length === 0) {
114
114
  db.all(sql, (err, rows) => {
115
115
  if (err)
116
116
  reject(err);
117
117
  else
118
- resolve2(convertBigInts(rows || []));
118
+ resolve3(convertBigInts(rows || []));
119
119
  });
120
120
  } else {
121
121
  db.all(sql, ...params, (err, rows) => {
122
122
  if (err)
123
123
  reject(err);
124
124
  else
125
- resolve2(convertBigInts(rows || []));
125
+ resolve3(convertBigInts(rows || []));
126
126
  });
127
127
  }
128
128
  });
129
129
  }
130
130
  function dbClose(db) {
131
- return new Promise((resolve2, reject) => {
131
+ return new Promise((resolve3, reject) => {
132
132
  db.close((err) => {
133
133
  if (err)
134
134
  reject(err);
135
135
  else
136
- resolve2();
136
+ resolve3();
137
137
  });
138
138
  });
139
139
  }
@@ -771,12 +771,12 @@ import { randomUUID as randomUUID2 } from "crypto";
771
771
  import Database from "better-sqlite3";
772
772
  import * as fs from "fs";
773
773
  import * as nodePath from "path";
774
- function createSQLiteDatabase(path6, options) {
775
- const dir = nodePath.dirname(path6);
774
+ function createSQLiteDatabase(path7, options) {
775
+ const dir = nodePath.dirname(path7);
776
776
  if (!fs.existsSync(dir)) {
777
777
  fs.mkdirSync(dir, { recursive: true });
778
778
  }
779
- const db = new Database(path6, {
779
+ const db = new Database(path7, {
780
780
  readonly: options?.readonly ?? false
781
781
  });
782
782
  if (!options?.readonly && (options?.walMode ?? true)) {
@@ -2422,7 +2422,7 @@ var SyncWorker = class {
2422
2422
  * Sleep utility
2423
2423
  */
2424
2424
  sleep(ms) {
2425
- return new Promise((resolve2) => setTimeout(resolve2, ms));
2425
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
2426
2426
  }
2427
2427
  /**
2428
2428
  * Get sync statistics
@@ -3452,8 +3452,8 @@ _Context:_ ${sessionContext}`;
3452
3452
  matchesMetadataScope(metadata, expected) {
3453
3453
  if (!metadata)
3454
3454
  return false;
3455
- return Object.entries(expected).every(([path6, value]) => {
3456
- const actual = path6.split(".").reduce((acc, key) => {
3455
+ return Object.entries(expected).every(([path7, value]) => {
3456
+ const actual = path7.split(".").reduce((acc, key) => {
3457
3457
  if (typeof acc !== "object" || acc === null)
3458
3458
  return void 0;
3459
3459
  return acc[key];
@@ -6890,6 +6890,12 @@ var MemoryService = class {
6890
6890
  recordMemoryAccess(eventId, sessionId, confidence = 1) {
6891
6891
  this.graduation.recordAccess(eventId, sessionId, confidence);
6892
6892
  }
6893
+ /**
6894
+ * Backward-compatible alias used by some hooks
6895
+ */
6896
+ async close() {
6897
+ await this.shutdown();
6898
+ }
6893
6899
  /**
6894
6900
  * Shutdown service
6895
6901
  */
@@ -7106,6 +7112,7 @@ eventsRouter.get("/", async (c) => {
7106
7112
  sessionId: e.sessionId,
7107
7113
  preview: e.content.slice(0, 200) + (e.content.length > 200 ? "..." : ""),
7108
7114
  contentLength: e.content.length,
7115
+ metadata: e.metadata,
7109
7116
  accessCount: e.access_count || 0,
7110
7117
  lastAccessedAt: e.last_accessed_at || null
7111
7118
  })),
@@ -7232,7 +7239,175 @@ searchRouter.get("/", async (c) => {
7232
7239
 
7233
7240
  // src/server/api/stats.ts
7234
7241
  import { Hono as Hono4 } from "hono";
7242
+ import * as fs5 from "fs";
7243
+ import * as path5 from "path";
7235
7244
  var statsRouter = new Hono4();
7245
+ var DEFAULT_KPI_THRESHOLDS = {
7246
+ usefulRecallRateMin: 0.45,
7247
+ reworkRateMax: 0.25,
7248
+ postChangeFailureRateMax: 0.2,
7249
+ avgCompletionTurnsMax: 12,
7250
+ memoryHitRateMin: 0.35
7251
+ };
7252
+ function loadKpiThresholds() {
7253
+ try {
7254
+ const filePath = path5.resolve(process.cwd(), "config", "kpi-thresholds.json");
7255
+ if (!fs5.existsSync(filePath))
7256
+ return DEFAULT_KPI_THRESHOLDS;
7257
+ const parsed = JSON.parse(fs5.readFileSync(filePath, "utf-8"));
7258
+ return {
7259
+ usefulRecallRateMin: Number(parsed.usefulRecallRateMin ?? DEFAULT_KPI_THRESHOLDS.usefulRecallRateMin),
7260
+ reworkRateMax: Number(parsed.reworkRateMax ?? DEFAULT_KPI_THRESHOLDS.reworkRateMax),
7261
+ postChangeFailureRateMax: Number(parsed.postChangeFailureRateMax ?? DEFAULT_KPI_THRESHOLDS.postChangeFailureRateMax),
7262
+ avgCompletionTurnsMax: Number(parsed.avgCompletionTurnsMax ?? DEFAULT_KPI_THRESHOLDS.avgCompletionTurnsMax),
7263
+ memoryHitRateMin: Number(parsed.memoryHitRateMin ?? DEFAULT_KPI_THRESHOLDS.memoryHitRateMin)
7264
+ };
7265
+ } catch {
7266
+ return DEFAULT_KPI_THRESHOLDS;
7267
+ }
7268
+ }
7269
+ function windowToMs(window) {
7270
+ if (window === "24h")
7271
+ return 24 * 60 * 60 * 1e3;
7272
+ if (window === "7d")
7273
+ return 7 * 24 * 60 * 60 * 1e3;
7274
+ return 30 * 24 * 60 * 60 * 1e3;
7275
+ }
7276
+ function inWindow(e, now, window) {
7277
+ return now - e.timestamp.getTime() <= windowToMs(window);
7278
+ }
7279
+ function isEditToolName(name) {
7280
+ return ["Write", "Edit", "MultiEdit", "NotebookEdit"].includes(name);
7281
+ }
7282
+ function parseToolPayload(e) {
7283
+ if (e.eventType !== "tool_observation")
7284
+ return null;
7285
+ try {
7286
+ const payload = JSON.parse(e.content);
7287
+ return {
7288
+ toolName: payload?.toolName,
7289
+ success: payload?.success,
7290
+ filePath: payload?.metadata?.filePath,
7291
+ command: payload?.metadata?.command
7292
+ };
7293
+ } catch {
7294
+ return {
7295
+ toolName: e.metadata?.toolName,
7296
+ success: e.metadata?.success,
7297
+ filePath: e.metadata?.filePath,
7298
+ command: e.metadata?.command
7299
+ };
7300
+ }
7301
+ }
7302
+ function isTestLikeCommand(command) {
7303
+ if (!command)
7304
+ return false;
7305
+ return /(test|jest|vitest|pytest|go test|cargo test|lint|eslint|build|tsc)/i.test(command);
7306
+ }
7307
+ function safeRatio(num, den) {
7308
+ if (!Number.isFinite(num) || !Number.isFinite(den) || den <= 0)
7309
+ return 0;
7310
+ return num / den;
7311
+ }
7312
+ function round(value, digits = 4) {
7313
+ const factor = 10 ** digits;
7314
+ return Math.round(value * factor) / factor;
7315
+ }
7316
+ function computeSessionTurnCount(sessionEvents) {
7317
+ const turnIds = /* @__PURE__ */ new Set();
7318
+ for (const e of sessionEvents) {
7319
+ const turnId = e.metadata?.turnId;
7320
+ if (typeof turnId === "string" && turnId.length > 0)
7321
+ turnIds.add(turnId);
7322
+ }
7323
+ if (turnIds.size > 0)
7324
+ return turnIds.size;
7325
+ return sessionEvents.filter((e) => e.eventType === "user_prompt").length;
7326
+ }
7327
+ function computeKpiMetrics(events, usefulRecallRate) {
7328
+ const prompts = events.filter((e) => e.eventType === "user_prompt");
7329
+ const promptCount = prompts.length;
7330
+ const memoryHitPrompts = prompts.filter((p) => p.metadata?.adherence?.checked).length;
7331
+ const memoryHitRate = round(safeRatio(memoryHitPrompts, promptCount));
7332
+ const sessions = /* @__PURE__ */ new Map();
7333
+ for (const e of events) {
7334
+ const arr = sessions.get(e.sessionId) || [];
7335
+ arr.push(e);
7336
+ sessions.set(e.sessionId, arr);
7337
+ }
7338
+ let sessionTurnTotal = 0;
7339
+ let sessionTurnSamples = 0;
7340
+ let firstValidEditMinutesTotal = 0;
7341
+ let firstValidEditSamples = 0;
7342
+ for (const sessionEvents of sessions.values()) {
7343
+ sessionEvents.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
7344
+ const turns = computeSessionTurnCount(sessionEvents);
7345
+ if (turns > 0) {
7346
+ sessionTurnTotal += turns;
7347
+ sessionTurnSamples++;
7348
+ }
7349
+ const firstPrompt = sessionEvents.find((e) => e.eventType === "user_prompt");
7350
+ const firstEdit = sessionEvents.find((e) => {
7351
+ const payload = parseToolPayload(e);
7352
+ return payload?.toolName && isEditToolName(payload.toolName) && payload.success === true;
7353
+ });
7354
+ if (firstPrompt && firstEdit) {
7355
+ const minutes = (firstEdit.timestamp.getTime() - firstPrompt.timestamp.getTime()) / 6e4;
7356
+ if (minutes >= 0) {
7357
+ firstValidEditMinutesTotal += minutes;
7358
+ firstValidEditSamples++;
7359
+ }
7360
+ }
7361
+ }
7362
+ const avgCompletionTurns = round(safeRatio(sessionTurnTotal, sessionTurnSamples), 2);
7363
+ const timeToFirstValidEditMinutes = round(safeRatio(firstValidEditMinutesTotal, firstValidEditSamples), 2);
7364
+ const editActions = [];
7365
+ let testRunsAfterEdit = 0;
7366
+ let failedTestRunsAfterEdit = 0;
7367
+ for (const [sessionId, sessionEvents] of sessions.entries()) {
7368
+ const sorted = [...sessionEvents].sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
7369
+ let seenEdit = false;
7370
+ for (const e of sorted) {
7371
+ const payload = parseToolPayload(e);
7372
+ if (!payload?.toolName)
7373
+ continue;
7374
+ if (isEditToolName(payload.toolName) && payload.success === true) {
7375
+ editActions.push({ sessionId, timestamp: e.timestamp.getTime(), filePath: payload.filePath });
7376
+ seenEdit = true;
7377
+ continue;
7378
+ }
7379
+ if (seenEdit && isTestLikeCommand(payload.command)) {
7380
+ testRunsAfterEdit++;
7381
+ if (payload.success === false)
7382
+ failedTestRunsAfterEdit++;
7383
+ }
7384
+ }
7385
+ }
7386
+ const THIRTY_MIN_MS = 30 * 60 * 1e3;
7387
+ let reworkCount = 0;
7388
+ const bySessionFile = /* @__PURE__ */ new Map();
7389
+ const sortedEdits = [...editActions].sort((a, b) => a.timestamp - b.timestamp);
7390
+ for (const edit of sortedEdits) {
7391
+ if (!edit.filePath)
7392
+ continue;
7393
+ const key = `${edit.sessionId}::${edit.filePath}`;
7394
+ const prev = bySessionFile.get(key);
7395
+ if (typeof prev === "number" && edit.timestamp - prev <= THIRTY_MIN_MS) {
7396
+ reworkCount++;
7397
+ }
7398
+ bySessionFile.set(key, edit.timestamp);
7399
+ }
7400
+ const reworkRate = round(safeRatio(reworkCount, editActions.length));
7401
+ const postChangeFailureRate = round(safeRatio(failedTestRunsAfterEdit, testRunsAfterEdit));
7402
+ return {
7403
+ memoryHitRate,
7404
+ usefulRecallRate,
7405
+ avgCompletionTurns,
7406
+ timeToFirstValidEditMinutes,
7407
+ reworkRate,
7408
+ postChangeFailureRate
7409
+ };
7410
+ }
7236
7411
  statsRouter.get("/shared", async (c) => {
7237
7412
  const memoryService = getServiceFromQuery(c);
7238
7413
  try {
@@ -7512,6 +7687,129 @@ statsRouter.get("/retrieval-traces", async (c) => {
7512
7687
  await memoryService.shutdown();
7513
7688
  }
7514
7689
  });
7690
+ statsRouter.get("/kpi", async (c) => {
7691
+ const rawWindow = c.req.query("window") || "7d";
7692
+ const window = rawWindow === "24h" || rawWindow === "30d" ? rawWindow : "7d";
7693
+ const memoryService = getServiceFromQuery(c);
7694
+ try {
7695
+ await memoryService.initialize();
7696
+ const now = Date.now();
7697
+ const thresholds = loadKpiThresholds();
7698
+ const allEvents = await memoryService.getRecentEvents(2e4);
7699
+ const events = allEvents.filter((e) => inWindow(e, now, window));
7700
+ const helpfulness = await memoryService.getHelpfulnessStats();
7701
+ const usefulRecallRate = helpfulness.totalEvaluated > 0 ? round(safeRatio(helpfulness.helpful, helpfulness.totalEvaluated)) : 0;
7702
+ const metrics = computeKpiMetrics(events, usefulRecallRate);
7703
+ const windowMs = windowToMs(window);
7704
+ const prevEvents = allEvents.filter((e) => {
7705
+ const age = now - e.timestamp.getTime();
7706
+ return age > windowMs && age <= windowMs * 2;
7707
+ });
7708
+ const previousMetrics = computeKpiMetrics(prevEvents, usefulRecallRate);
7709
+ const deltas = {
7710
+ memoryHitRate: round(metrics.memoryHitRate - previousMetrics.memoryHitRate),
7711
+ usefulRecallRate: round(metrics.usefulRecallRate - previousMetrics.usefulRecallRate),
7712
+ avgCompletionTurns: round(metrics.avgCompletionTurns - previousMetrics.avgCompletionTurns, 2),
7713
+ timeToFirstValidEditMinutes: round(metrics.timeToFirstValidEditMinutes - previousMetrics.timeToFirstValidEditMinutes, 2),
7714
+ reworkRate: round(metrics.reworkRate - previousMetrics.reworkRate),
7715
+ postChangeFailureRate: round(metrics.postChangeFailureRate - previousMetrics.postChangeFailureRate)
7716
+ };
7717
+ const THIRTY_MIN_MS = 30 * 60 * 1e3;
7718
+ const trendWindowMs = 30 * 24 * 60 * 60 * 1e3;
7719
+ const trendEvents = allEvents.filter((e) => now - e.timestamp.getTime() <= trendWindowMs);
7720
+ const buckets = /* @__PURE__ */ new Map();
7721
+ for (const e of trendEvents) {
7722
+ const day = e.timestamp.toISOString().split("T")[0];
7723
+ const arr = buckets.get(day) || [];
7724
+ arr.push(e);
7725
+ buckets.set(day, arr);
7726
+ }
7727
+ const trendDaily = Array.from(buckets.entries()).sort((a, b) => a[0].localeCompare(b[0])).map(([date, dayEvents]) => {
7728
+ const dayPrompts = dayEvents.filter((e) => e.eventType === "user_prompt");
7729
+ const dayPromptCount = dayPrompts.length;
7730
+ const dayMemoryHit = dayPrompts.filter((p) => p.metadata?.adherence?.checked).length;
7731
+ const dayEdits = dayEvents.filter((e) => {
7732
+ const p = parseToolPayload(e);
7733
+ return Boolean(p?.toolName && isEditToolName(p.toolName) && p.success === true);
7734
+ });
7735
+ const dayEditActions = dayEdits.map((e) => {
7736
+ const p = parseToolPayload(e);
7737
+ return { sessionId: e.sessionId, timestamp: e.timestamp.getTime(), filePath: p?.filePath };
7738
+ }).filter((x) => Boolean(x.filePath));
7739
+ let dayReworkCount = 0;
7740
+ const dayBySessionFile = /* @__PURE__ */ new Map();
7741
+ for (const edit of dayEditActions) {
7742
+ const key = `${edit.sessionId}::${edit.filePath}`;
7743
+ const prev = dayBySessionFile.get(key);
7744
+ if (typeof prev === "number" && edit.timestamp - prev <= THIRTY_MIN_MS)
7745
+ dayReworkCount++;
7746
+ dayBySessionFile.set(key, edit.timestamp);
7747
+ }
7748
+ const dayTests = dayEvents.filter((e) => {
7749
+ const p = parseToolPayload(e);
7750
+ return Boolean(p?.toolName && isTestLikeCommand(p.command));
7751
+ });
7752
+ const dayFailedTests = dayEvents.filter((e) => {
7753
+ const p = parseToolPayload(e);
7754
+ return Boolean(p?.toolName && isTestLikeCommand(p.command) && p.success === false);
7755
+ });
7756
+ const turnsBySession = /* @__PURE__ */ new Map();
7757
+ for (const e of dayEvents) {
7758
+ const arr = turnsBySession.get(e.sessionId) || [];
7759
+ arr.push(e);
7760
+ turnsBySession.set(e.sessionId, arr);
7761
+ }
7762
+ let dayTurnsTotal = 0;
7763
+ let dayTurnsSamples = 0;
7764
+ for (const sessionEvents of turnsBySession.values()) {
7765
+ const turns = computeSessionTurnCount(sessionEvents);
7766
+ if (turns > 0) {
7767
+ dayTurnsTotal += turns;
7768
+ dayTurnsSamples++;
7769
+ }
7770
+ }
7771
+ return {
7772
+ date,
7773
+ memoryHitRate: round(safeRatio(dayMemoryHit, dayPromptCount)),
7774
+ usefulRecallRate,
7775
+ reworkRate: round(safeRatio(dayReworkCount, dayEditActions.length)),
7776
+ postChangeFailureRate: round(safeRatio(dayFailedTests.length, dayTests.length)),
7777
+ avgCompletionTurns: round(safeRatio(dayTurnsTotal, dayTurnsSamples), 2)
7778
+ };
7779
+ });
7780
+ const alerts = [];
7781
+ if (metrics.usefulRecallRate < thresholds.usefulRecallRateMin) {
7782
+ alerts.push({ metric: "usefulRecallRate", level: "warn", message: "Useful recall rate is below threshold", value: metrics.usefulRecallRate, threshold: thresholds.usefulRecallRateMin });
7783
+ }
7784
+ if (metrics.reworkRate > thresholds.reworkRateMax) {
7785
+ alerts.push({ metric: "reworkRate", level: "warn", message: "Rework rate is above threshold", value: metrics.reworkRate, threshold: thresholds.reworkRateMax });
7786
+ }
7787
+ if (metrics.postChangeFailureRate > thresholds.postChangeFailureRateMax) {
7788
+ alerts.push({ metric: "postChangeFailureRate", level: "warn", message: "Post-change failure rate is above threshold", value: metrics.postChangeFailureRate, threshold: thresholds.postChangeFailureRateMax });
7789
+ }
7790
+ if (metrics.avgCompletionTurns > thresholds.avgCompletionTurnsMax) {
7791
+ alerts.push({ metric: "avgCompletionTurns", level: "warn", message: "Average completion turns is above threshold", value: metrics.avgCompletionTurns, threshold: thresholds.avgCompletionTurnsMax });
7792
+ }
7793
+ if (metrics.memoryHitRate < thresholds.memoryHitRateMin) {
7794
+ alerts.push({ metric: "memoryHitRate", level: "warn", message: "Memory hit rate is below threshold", value: metrics.memoryHitRate, threshold: thresholds.memoryHitRateMin });
7795
+ }
7796
+ return c.json({
7797
+ window,
7798
+ metrics,
7799
+ previousMetrics,
7800
+ deltas,
7801
+ trend: {
7802
+ daily: trendDaily
7803
+ },
7804
+ thresholds,
7805
+ alerts
7806
+ });
7807
+ } catch (error) {
7808
+ return c.json({ error: error.message }, 500);
7809
+ } finally {
7810
+ await memoryService.shutdown();
7811
+ }
7812
+ });
7515
7813
  statsRouter.post("/graduation/run", async (c) => {
7516
7814
  const memoryService = getServiceFromQuery(c);
7517
7815
  try {
@@ -7759,19 +8057,19 @@ turnsRouter.post("/backfill", async (c) => {
7759
8057
 
7760
8058
  // src/server/api/projects.ts
7761
8059
  import { Hono as Hono7 } from "hono";
7762
- import * as fs5 from "fs";
7763
- import * as path5 from "path";
8060
+ import * as fs6 from "fs";
8061
+ import * as path6 from "path";
7764
8062
  import * as os3 from "os";
7765
8063
  var projectsRouter = new Hono7();
7766
8064
  projectsRouter.get("/", async (c) => {
7767
8065
  try {
7768
- const projectsDir = path5.join(os3.homedir(), ".claude-code", "memory", "projects");
7769
- if (!fs5.existsSync(projectsDir)) {
8066
+ const projectsDir = path6.join(os3.homedir(), ".claude-code", "memory", "projects");
8067
+ if (!fs6.existsSync(projectsDir)) {
7770
8068
  return c.json({ projects: [] });
7771
8069
  }
7772
- const projectHashes = fs5.readdirSync(projectsDir).filter((name) => {
7773
- const fullPath = path5.join(projectsDir, name);
7774
- return fs5.statSync(fullPath).isDirectory();
8070
+ const projectHashes = fs6.readdirSync(projectsDir).filter((name) => {
8071
+ const fullPath = path6.join(projectsDir, name);
8072
+ return fs6.statSync(fullPath).isDirectory();
7775
8073
  });
7776
8074
  const registry = loadSessionRegistry();
7777
8075
  const hashToPath = /* @__PURE__ */ new Map();
@@ -7781,17 +8079,17 @@ projectsRouter.get("/", async (c) => {
7781
8079
  }
7782
8080
  }
7783
8081
  const projects = projectHashes.map((hash) => {
7784
- const dirPath = path5.join(projectsDir, hash);
7785
- const dbPath = path5.join(dirPath, "events.sqlite");
8082
+ const dirPath = path6.join(projectsDir, hash);
8083
+ const dbPath = path6.join(dirPath, "events.sqlite");
7786
8084
  let dbSize = 0;
7787
- if (fs5.existsSync(dbPath)) {
7788
- dbSize = fs5.statSync(dbPath).size;
8085
+ if (fs6.existsSync(dbPath)) {
8086
+ dbSize = fs6.statSync(dbPath).size;
7789
8087
  }
7790
8088
  const projectPath = hashToPath.get(hash) || `unknown (${hash})`;
7791
8089
  return {
7792
8090
  hash,
7793
8091
  projectPath,
7794
- projectName: path5.basename(projectPath),
8092
+ projectName: path6.basename(projectPath),
7795
8093
  dbSize,
7796
8094
  dbSizeHuman: formatBytes(dbSize)
7797
8095
  };
@@ -7916,7 +8214,7 @@ function buildPrompt(statsContext, memoryContext, history, currentMessage) {
7916
8214
  return parts.join("\n");
7917
8215
  }
7918
8216
  function streamClaudeResponse(prompt, stream) {
7919
- return new Promise((resolve2, reject) => {
8217
+ return new Promise((resolve3, reject) => {
7920
8218
  const proc = spawn("claude", [
7921
8219
  "-p",
7922
8220
  "--output-format",
@@ -7988,7 +8286,7 @@ function streamClaudeResponse(prompt, stream) {
7988
8286
  if (code !== 0 && code !== null) {
7989
8287
  reject(new Error(`Claude CLI exited with code ${code}`));
7990
8288
  } else {
7991
- resolve2();
8289
+ resolve3();
7992
8290
  }
7993
8291
  });
7994
8292
  });