claude-memory-layer 1.0.23 → 1.0.24

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 (51) hide show
  1. package/.claude/settings.local.json +11 -0
  2. package/README.md +2 -0
  3. package/dist/cli/index.js +85 -17
  4. package/dist/cli/index.js.map +2 -2
  5. package/dist/core/index.js +28 -5
  6. package/dist/core/index.js.map +2 -2
  7. package/dist/hooks/post-tool-use.js +115 -18
  8. package/dist/hooks/post-tool-use.js.map +2 -2
  9. package/dist/hooks/semantic-daemon.js +7337 -0
  10. package/dist/hooks/semantic-daemon.js.map +7 -0
  11. package/dist/hooks/session-end.js +69 -16
  12. package/dist/hooks/session-end.js.map +2 -2
  13. package/dist/hooks/session-start.js +154 -24
  14. package/dist/hooks/session-start.js.map +4 -4
  15. package/dist/hooks/stop.js +99 -18
  16. package/dist/hooks/stop.js.map +2 -2
  17. package/dist/hooks/user-prompt-submit.js +289 -102
  18. package/dist/hooks/user-prompt-submit.js.map +4 -4
  19. package/dist/server/api/index.js +69 -16
  20. package/dist/server/api/index.js.map +2 -2
  21. package/dist/server/index.js +69 -16
  22. package/dist/server/index.js.map +2 -2
  23. package/dist/services/memory-service.js +69 -16
  24. package/dist/services/memory-service.js.map +2 -2
  25. package/dist/ui/app.js +48 -1
  26. package/dist/ui/index.html +11 -3
  27. package/memory/_index.md +1 -0
  28. package/memory/agent_response/uncategorized/2026-03-04.md +1098 -1
  29. package/memory/session_summary/uncategorized/2026-03-04.md +31 -0
  30. package/memory/tool_observation/uncategorized/2026-03-04.md +733 -1
  31. package/memory/user_prompt/uncategorized/2026-03-04.md +371 -1
  32. package/package.json +1 -1
  33. package/scripts/build.ts +2 -1
  34. package/specs/selective-tool-observation/context.md +100 -0
  35. package/specs/selective-tool-observation/plan.md +158 -0
  36. package/specs/selective-tool-observation/spec.md +127 -0
  37. package/src/cli/index.ts +1 -0
  38. package/src/core/embedder.ts +13 -4
  39. package/src/core/sqlite-event-store.ts +16 -0
  40. package/src/core/turn-state.ts +48 -0
  41. package/src/core/types.ts +1 -0
  42. package/src/hooks/post-tool-use.ts +47 -2
  43. package/src/hooks/semantic-daemon-client.ts +208 -0
  44. package/src/hooks/semantic-daemon.ts +276 -0
  45. package/src/hooks/session-start.ts +7 -0
  46. package/src/hooks/stop.ts +19 -4
  47. package/src/hooks/user-prompt-submit.ts +48 -40
  48. package/src/services/memory-service.ts +59 -16
  49. package/src/services/session-history-importer.ts +18 -0
  50. package/src/ui/app.js +48 -1
  51. package/src/ui/index.html +11 -3
@@ -1834,6 +1834,21 @@ var SQLiteEventStore = class {
1834
1834
  [id, eventId, sessionId, score, query.slice(0, 100)]
1835
1835
  );
1836
1836
  }
1837
+ /**
1838
+ * Get session IDs that have unevaluated retrievals (measured_at IS NULL).
1839
+ * Excludes the current session. Used to backfill sessions that ended without Stop hook.
1840
+ */
1841
+ async getUnevaluatedSessions(currentSessionId, limit = 5) {
1842
+ await this.initialize();
1843
+ const rows = sqliteAll(
1844
+ this.db,
1845
+ `SELECT DISTINCT session_id FROM memory_helpfulness
1846
+ WHERE measured_at IS NULL AND session_id != ?
1847
+ ORDER BY created_at DESC LIMIT ?`,
1848
+ [currentSessionId, limit]
1849
+ );
1850
+ return rows.map((r) => r.session_id);
1851
+ }
1837
1852
  /**
1838
1853
  * Evaluate helpfulness for all retrievals in a session
1839
1854
  * Called at session end - uses behavioral signals to compute score
@@ -2621,7 +2636,7 @@ var VectorStore = class {
2621
2636
 
2622
2637
  // src/core/embedder.ts
2623
2638
  import { pipeline } from "@huggingface/transformers";
2624
- var Embedder = class {
2639
+ var Embedder = class _Embedder {
2625
2640
  pipeline = null;
2626
2641
  modelName;
2627
2642
  activeModelName;
@@ -2652,6 +2667,11 @@ var Embedder = class {
2652
2667
  this.initialized = true;
2653
2668
  }
2654
2669
  }
2670
+ // ~4 chars per token; 512 tokens * 4 = 2048, use 2000 to be safe
2671
+ static MAX_CHARS = 2e3;
2672
+ truncate(text) {
2673
+ return text.length > _Embedder.MAX_CHARS ? text.slice(0, _Embedder.MAX_CHARS) : text;
2674
+ }
2655
2675
  /**
2656
2676
  * Generate embedding for a single text
2657
2677
  */
@@ -2660,10 +2680,11 @@ var Embedder = class {
2660
2680
  if (!this.pipeline) {
2661
2681
  throw new Error("Embedding pipeline not initialized");
2662
2682
  }
2663
- const output = await this.pipeline(text, {
2683
+ const output = await this.pipeline(this.truncate(text), {
2664
2684
  pooling: "mean",
2665
2685
  normalize: true,
2666
- truncation: true
2686
+ truncation: true,
2687
+ max_length: 512
2667
2688
  });
2668
2689
  const vector = Array.from(output.data);
2669
2690
  return {
@@ -2685,10 +2706,11 @@ var Embedder = class {
2685
2706
  for (let i = 0; i < texts.length; i += batchSize) {
2686
2707
  const batch = texts.slice(i, i + batchSize);
2687
2708
  for (const text of batch) {
2688
- const output = await this.pipeline(text, {
2709
+ const output = await this.pipeline(this.truncate(text), {
2689
2710
  pooling: "mean",
2690
2711
  normalize: true,
2691
- truncation: true
2712
+ truncation: true,
2713
+ max_length: 512
2692
2714
  });
2693
2715
  const vector = Array.from(output.data);
2694
2716
  results.push({
@@ -6077,6 +6099,7 @@ var MemoryService = class {
6077
6099
  projectPath = null;
6078
6100
  readOnly;
6079
6101
  lightweightMode;
6102
+ embeddingOnly;
6080
6103
  mdMirror;
6081
6104
  storagePath;
6082
6105
  constructor(config) {
@@ -6084,6 +6107,7 @@ var MemoryService = class {
6084
6107
  this.storagePath = storagePath;
6085
6108
  this.readOnly = config.readOnly ?? false;
6086
6109
  this.lightweightMode = config.lightweightMode ?? false;
6110
+ this.embeddingOnly = config.embeddingOnly ?? false;
6087
6111
  this.mdMirror = new MarkdownMirror2(process.cwd());
6088
6112
  if (!this.readOnly && !fs4.existsSync(storagePath)) {
6089
6113
  fs4.mkdirSync(storagePath, { recursive: true });
@@ -6157,19 +6181,21 @@ var MemoryService = class {
6157
6181
  this.embedder
6158
6182
  );
6159
6183
  this.vectorWorker.start();
6160
- this.retriever.setGraduationPipeline(this.graduation);
6161
- this.graduationWorker = createGraduationWorker(
6162
- this.sqliteStore,
6163
- this.graduation
6164
- );
6165
- this.graduationWorker.start();
6166
- if (this.analyticsStore) {
6167
- this.syncWorker = new SyncWorker(
6184
+ if (!this.embeddingOnly) {
6185
+ this.retriever.setGraduationPipeline(this.graduation);
6186
+ this.graduationWorker = createGraduationWorker(
6168
6187
  this.sqliteStore,
6169
- this.analyticsStore,
6170
- { intervalMs: 3e4, batchSize: 500 }
6188
+ this.graduation
6171
6189
  );
6172
- this.syncWorker.start();
6190
+ this.graduationWorker.start();
6191
+ if (this.analyticsStore) {
6192
+ this.syncWorker = new SyncWorker(
6193
+ this.sqliteStore,
6194
+ this.analyticsStore,
6195
+ { intervalMs: 3e4, batchSize: 500 }
6196
+ );
6197
+ this.syncWorker.start();
6198
+ }
6173
6199
  }
6174
6200
  const savedMode = await this.sqliteStore.getEndlessConfig("mode");
6175
6201
  if (savedMode === "endless") {
@@ -6893,6 +6919,19 @@ var MemoryService = class {
6893
6919
  await this.initialize();
6894
6920
  await this.sqliteStore.recordRetrieval(eventId, sessionId, score, query);
6895
6921
  }
6922
+ /**
6923
+ * Record a query-level retrieval trace (used by user-prompt-submit hook).
6924
+ * Feeds the retrieval_traces table that powers dashboard stats.
6925
+ */
6926
+ async recordQueryTrace(input) {
6927
+ await this.initialize();
6928
+ await this.sqliteStore.recordRetrievalTrace({
6929
+ ...input,
6930
+ candidateDetails: [],
6931
+ selectedDetails: [],
6932
+ fallbackTrace: []
6933
+ });
6934
+ }
6896
6935
  /**
6897
6936
  * Evaluate helpfulness of retrievals in a session (called at session end)
6898
6937
  */
@@ -6900,6 +6939,20 @@ var MemoryService = class {
6900
6939
  await this.initialize();
6901
6940
  await this.sqliteStore.evaluateSessionHelpfulness(sessionId);
6902
6941
  }
6942
+ /**
6943
+ * Backfill helpfulness evaluation for sessions that ended without Stop hook.
6944
+ * Call on first turn of a new session to catch missed evaluations.
6945
+ */
6946
+ async evaluatePendingSessions(currentSessionId) {
6947
+ await this.initialize();
6948
+ const sessions = await this.sqliteStore.getUnevaluatedSessions(currentSessionId, 5);
6949
+ for (const sid of sessions) {
6950
+ try {
6951
+ await this.sqliteStore.evaluateSessionHelpfulness(sid);
6952
+ } catch {
6953
+ }
6954
+ }
6955
+ }
6903
6956
  /**
6904
6957
  * Get most helpful memories ranked by helpfulness score
6905
6958
  */
@@ -7452,11 +7505,42 @@ function clearTurnState(sessionId) {
7452
7505
  // src/hooks/post-tool-use.ts
7453
7506
  var DEFAULT_CONFIG6 = {
7454
7507
  enabled: true,
7455
- excludedTools: ["TodoWrite", "TodoRead"],
7508
+ excludedTools: [
7509
+ // Trivial meta tools
7510
+ "TodoWrite",
7511
+ "TodoRead",
7512
+ // Reproducible query tools (no storage value)
7513
+ "Read",
7514
+ "Grep",
7515
+ "Glob",
7516
+ "ToolSearch",
7517
+ "WebFetch",
7518
+ "WebSearch",
7519
+ "NotebookRead",
7520
+ // Low-value system tools
7521
+ "Skill",
7522
+ "EnterPlanMode"
7523
+ ],
7524
+ minOutputLength: parseInt(process.env.CLAUDE_MEMORY_TOOL_MIN_OUTPUT_LEN || "100"),
7456
7525
  maxOutputLength: 1e4,
7457
7526
  maxOutputLines: 100,
7458
7527
  storeOnlyOnSuccess: false
7459
7528
  };
7529
+ var ALWAYS_STORE_TOOLS = /* @__PURE__ */ new Set([
7530
+ "Write",
7531
+ "Edit",
7532
+ "MultiEdit",
7533
+ "Agent",
7534
+ "Task",
7535
+ "ExitPlanMode"
7536
+ ]);
7537
+ function hasSignificantOutput(toolName, output, response, minLen) {
7538
+ if (ALWAYS_STORE_TOOLS.has(toolName))
7539
+ return true;
7540
+ if (response?.stderr && response.stderr.trim().length > 0)
7541
+ return true;
7542
+ return output.trim().length >= minLen;
7543
+ }
7460
7544
  var DEFAULT_PRIVACY_CONFIG = {
7461
7545
  excludePatterns: ["password", "secret", "api_key", "token", "bearer"],
7462
7546
  anonymize: false,
@@ -7493,8 +7577,12 @@ function isToolSuccess(response) {
7493
7577
  async function main() {
7494
7578
  const inputData = await readStdin();
7495
7579
  const input = JSON.parse(inputData);
7496
- const config = DEFAULT_CONFIG6;
7580
+ const config = { ...DEFAULT_CONFIG6 };
7497
7581
  const privacyConfig = DEFAULT_PRIVACY_CONFIG;
7582
+ const envBlocklist = process.env.CLAUDE_MEMORY_TOOL_BLOCKLIST;
7583
+ if (envBlocklist !== void 0) {
7584
+ config.excludedTools = envBlocklist.split(",").map((s) => s.trim()).filter(Boolean);
7585
+ }
7498
7586
  if (!config.enabled) {
7499
7587
  console.log(JSON.stringify({}));
7500
7588
  return;
@@ -7509,6 +7597,15 @@ async function main() {
7509
7597
  console.log(JSON.stringify({}));
7510
7598
  return;
7511
7599
  }
7600
+ if (!hasSignificantOutput(
7601
+ input.tool_name,
7602
+ toolOutput,
7603
+ input.tool_response,
7604
+ config.minOutputLength ?? 100
7605
+ )) {
7606
+ console.log(JSON.stringify({}));
7607
+ return;
7608
+ }
7512
7609
  try {
7513
7610
  const memoryService = getLightweightMemoryService(input.session_id);
7514
7611
  const maskedInput = maskSensitiveInput(input.tool_input);