claude-memory-layer 1.0.33 → 1.0.35

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.
package/dist/mcp/index.js CHANGED
@@ -13999,6 +13999,16 @@ var SQLiteEventStore = class {
13999
13999
  getDatabase() {
14000
14000
  return this.db;
14001
14001
  }
14002
+ hasTableColumn(tableName, columnName) {
14003
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(tableName))
14004
+ return false;
14005
+ try {
14006
+ const rows = sqliteAll(this.db, `PRAGMA table_info("${tableName}")`, []);
14007
+ return rows.some((row) => row.name === columnName);
14008
+ } catch {
14009
+ return false;
14010
+ }
14011
+ }
14002
14012
  async recordRetrievalTrace(input) {
14003
14013
  await this.initialize();
14004
14014
  const traceId = randomUUID5();
@@ -14064,17 +14074,18 @@ var SQLiteEventStore = class {
14064
14074
  async getRetrievalTraceStats() {
14065
14075
  await this.initialize();
14066
14076
  try {
14077
+ const rewrittenQueryRewriteKindSql = this.hasTableColumn("retrieval_traces", "query_rewrite_kind") ? REWRITTEN_QUERY_REWRITE_KIND_SQL : "0";
14067
14078
  const row = sqliteGet(
14068
14079
  this.db,
14069
14080
  `SELECT
14070
14081
  COUNT(*) as total_queries,
14071
14082
  AVG(candidate_count) as avg_candidate_count,
14072
14083
  AVG(selected_count) as avg_selected_count,
14073
- SUM(CASE WHEN ${REWRITTEN_QUERY_REWRITE_KIND_SQL} THEN 1 ELSE 0 END) as rewritten_queries,
14074
- SUM(CASE WHEN ${REWRITTEN_QUERY_REWRITE_KIND_SQL} AND selected_count > 0 THEN 1 ELSE 0 END) as rewritten_queries_with_selection,
14075
- SUM(CASE WHEN NOT (${REWRITTEN_QUERY_REWRITE_KIND_SQL}) AND selected_count > 0 THEN 1 ELSE 0 END) as raw_queries_with_selection,
14076
- AVG(CASE WHEN ${REWRITTEN_QUERY_REWRITE_KIND_SQL} THEN selected_count END) as avg_selected_count_for_rewritten_queries,
14077
- AVG(CASE WHEN NOT (${REWRITTEN_QUERY_REWRITE_KIND_SQL}) THEN selected_count END) as avg_selected_count_for_raw_queries,
14084
+ SUM(CASE WHEN ${rewrittenQueryRewriteKindSql} THEN 1 ELSE 0 END) as rewritten_queries,
14085
+ SUM(CASE WHEN ${rewrittenQueryRewriteKindSql} AND selected_count > 0 THEN 1 ELSE 0 END) as rewritten_queries_with_selection,
14086
+ SUM(CASE WHEN NOT (${rewrittenQueryRewriteKindSql}) AND selected_count > 0 THEN 1 ELSE 0 END) as raw_queries_with_selection,
14087
+ AVG(CASE WHEN ${rewrittenQueryRewriteKindSql} THEN selected_count END) as avg_selected_count_for_rewritten_queries,
14088
+ AVG(CASE WHEN NOT (${rewrittenQueryRewriteKindSql}) THEN selected_count END) as avg_selected_count_for_raw_queries,
14078
14089
  CASE
14079
14090
  WHEN SUM(candidate_count) > 0 THEN (SUM(selected_count) * 1.0 / SUM(candidate_count))
14080
14091
  ELSE 0
@@ -19859,7 +19870,8 @@ var HermesSessionHistoryImporter = class {
19859
19870
  source: "hermes",
19860
19871
  hermesSource: session.source,
19861
19872
  sourceSessionId: session.id,
19862
- sourceSessionHash: hashLabel(session.id)
19873
+ sourceSessionHash: hashLabel(session.id),
19874
+ projectPath: effectiveProjectPath
19863
19875
  }
19864
19876
  );
19865
19877
  if (appendResult.success && appendResult.isDuplicate) {
@@ -19896,7 +19908,8 @@ var HermesSessionHistoryImporter = class {
19896
19908
  source: "hermes",
19897
19909
  hermesSource: session.source,
19898
19910
  sourceSessionId: session.id,
19899
- sourceSessionHash: hashLabel(session.id)
19911
+ sourceSessionHash: hashLabel(session.id),
19912
+ projectPath: effectiveProjectPath
19900
19913
  }
19901
19914
  );
19902
19915
  if (appendResult.success && appendResult.isDuplicate) {
@@ -20445,7 +20458,7 @@ async function handleToolCall(name, args) {
20445
20458
  case "mem-details":
20446
20459
  return await handleMemDetails(memoryService, args);
20447
20460
  case "mem-stats":
20448
- return await handleMemStats(memoryService);
20461
+ return await handleMemStats(memoryService, args);
20449
20462
  case "mem-context-pack":
20450
20463
  return await handleMemContextPack(memoryService, args);
20451
20464
  case "mem-import-latest":
@@ -20481,19 +20494,19 @@ async function handleExternalMarketContext(args) {
20481
20494
  async function handleMemSearch(memoryService, args) {
20482
20495
  const query = args.query;
20483
20496
  const topK = Math.min(args.topK || 5, 20);
20484
- const result = await memoryService.retrieveMemories(query, {
20485
- topK,
20486
- sessionId: args.sessionId,
20487
- recordTrace: false
20488
- });
20497
+ const sessionId = args.sessionId;
20498
+ const search = await retrieveMcpMemories(memoryService, query, { topK, sessionId });
20489
20499
  const lines = [
20490
20500
  "## Memory Search Results",
20491
20501
  "",
20492
- `Found ${result.memories.length} relevant memories:`,
20502
+ `Found ${search.memories.length} relevant memories:`,
20493
20503
  ""
20494
20504
  ];
20495
- for (let i = 0; i < result.memories.length; i++) {
20496
- const m = result.memories[i];
20505
+ if (search.warning) {
20506
+ lines.push(search.warning, "");
20507
+ }
20508
+ for (let i = 0; i < search.memories.length; i++) {
20509
+ const m = search.memories[i];
20497
20510
  const citationId = generateCitationId(m.event.id);
20498
20511
  const date = m.event.timestamp.toISOString().split("T")[0];
20499
20512
  const preview = m.event.content.slice(0, 100) + (m.event.content.length > 100 ? "..." : "");
@@ -20508,6 +20521,52 @@ async function handleMemSearch(memoryService, args) {
20508
20521
  content: [{ type: "text", text: lines.join("\n") }]
20509
20522
  };
20510
20523
  }
20524
+ var SEMANTIC_VECTOR_FALLBACK_WARNING = "Warning: semantic/vector retrieval unavailable; used keyword fallback.";
20525
+ var SEMANTIC_VECTOR_FALLBACK_FAILED_WARNING = "Warning: semantic/vector retrieval unavailable; keyword fallback failed.";
20526
+ async function retrieveMcpMemories(memoryService, query, options) {
20527
+ try {
20528
+ const result = await memoryService.retrieveMemories(query, {
20529
+ topK: options.topK,
20530
+ sessionId: options.sessionId,
20531
+ recordTrace: false
20532
+ });
20533
+ return { memories: result.memories };
20534
+ } catch (error) {
20535
+ if (!isVectorSchemaMismatchError(error)) {
20536
+ throw error;
20537
+ }
20538
+ try {
20539
+ const memories = options.sessionId ? rankSessionKeywordMatches(
20540
+ query,
20541
+ await memoryService.getSessionHistory(options.sessionId),
20542
+ options.topK
20543
+ ) : await memoryService.keywordSearch(query, { topK: options.topK });
20544
+ return { memories, warning: SEMANTIC_VECTOR_FALLBACK_WARNING };
20545
+ } catch {
20546
+ return { memories: [], warning: SEMANTIC_VECTOR_FALLBACK_FAILED_WARNING };
20547
+ }
20548
+ }
20549
+ }
20550
+ function isVectorSchemaMismatchError(error) {
20551
+ const message = error instanceof Error ? error.message : String(error);
20552
+ return /no vector column/i.test(message) || /query vector dimension/i.test(message) || /vector[^\n]{0,80}dimension/i.test(message) || /dimension[^\n]{0,80}vector/i.test(message) || /lancedb[^\n]{0,120}schema/i.test(message);
20553
+ }
20554
+ function rankSessionKeywordMatches(query, events, topK) {
20555
+ const queryTokens = tokenizeKeywordQuery(query);
20556
+ if (queryTokens.length === 0)
20557
+ return [];
20558
+ return events.map((event) => ({ event, score: scoreKeywordMatch(event.content, queryTokens) })).filter((match) => match.score > 0).sort((a, b) => b.score - a.score || b.event.timestamp.getTime() - a.event.timestamp.getTime()).slice(0, topK);
20559
+ }
20560
+ function tokenizeKeywordQuery(value) {
20561
+ return Array.from(new Set(
20562
+ value.toLowerCase().split(/[^a-z0-9가-힣_]+/).map((token) => token.trim()).filter((token) => token.length > 1)
20563
+ ));
20564
+ }
20565
+ function scoreKeywordMatch(content, queryTokens) {
20566
+ const haystack = content.toLowerCase();
20567
+ const hits = queryTokens.filter((token) => haystack.includes(token)).length;
20568
+ return hits / queryTokens.length;
20569
+ }
20511
20570
  async function handleMemTimeline(memoryService, args) {
20512
20571
  const ids = args.ids;
20513
20572
  const windowSize = args.windowSize || 3;
@@ -20599,10 +20658,8 @@ async function handleMemContextPack(memoryService, args) {
20599
20658
  sessionsDir: optionalString(args.sessionsDir),
20600
20659
  stateDb: optionalString(args.stateDb)
20601
20660
  }) : void 0;
20602
- const [searchResult, recentEvents] = await Promise.all([
20603
- memoryService.retrieveMemories(query, { topK: retrievalTopK, sessionId, recordTrace: false }),
20604
- memoryService.getRecentEvents(recentLimit)
20605
- ]);
20661
+ const search = await retrieveMcpMemories(memoryService, query, { topK: retrievalTopK, sessionId });
20662
+ const recentEvents = await memoryService.getRecentEvents(recentLimit);
20606
20663
  const timelineEvents = selectContextPackTimelineEvents(
20607
20664
  recentEvents,
20608
20665
  projectPath,
@@ -20610,7 +20667,7 @@ async function handleMemContextPack(memoryService, args) {
20610
20667
  );
20611
20668
  const sessions = summarizeSessions(timelineEvents, sessionLimit);
20612
20669
  const recentSessionIds = new Set(sessions.map((session) => session.sessionId));
20613
- const relevantMemories = selectContextPackMemories(searchResult.memories, {
20670
+ const relevantMemories = selectContextPackMemories(search.memories, {
20614
20671
  genericContinuationQuery,
20615
20672
  topK,
20616
20673
  recentSessionIds,
@@ -20631,6 +20688,9 @@ async function handleMemContextPack(memoryService, args) {
20631
20688
  `- Refresh limits: sessions=${freshnessRun.sessionLimit} messages=${freshnessRun.messageLimit} force=${freshnessRun.force ? "yes" : "no"} embeddings=${freshnessRun.processEmbeddings ? `processed ${freshnessRun.embeddingsProcessed ?? 0}` : "skipped"}`
20632
20689
  );
20633
20690
  }
20691
+ if (search.warning) {
20692
+ lines.push(`- ${search.warning}`);
20693
+ }
20634
20694
  if (genericContinuationQuery) {
20635
20695
  lines.push("- Generic continuation query: recent project timeline prioritized.");
20636
20696
  }
@@ -20870,7 +20930,7 @@ function shouldShowContextPackTimelineEvent(event, projectPath, genericContinuat
20870
20930
  return false;
20871
20931
  if (event.eventType === "tool_observation")
20872
20932
  return false;
20873
- if (mentionsDifferentWorkspaceProject(content, projectPath))
20933
+ if (eventBelongsToDifferentProject(event, projectPath))
20874
20934
  return false;
20875
20935
  if (genericContinuationQuery && isGenericContinuationQuery(content))
20876
20936
  return false;
@@ -20882,7 +20942,7 @@ function shouldShowContextPackMemory(memory, options) {
20882
20942
  return false;
20883
20943
  if (memory.event.eventType === "tool_observation")
20884
20944
  return false;
20885
- if (mentionsDifferentWorkspaceProject(content, options.projectPath))
20945
+ if (eventBelongsToDifferentProject(memory.event, options.projectPath))
20886
20946
  return false;
20887
20947
  if (!options.genericContinuationQuery)
20888
20948
  return true;
@@ -20894,6 +20954,54 @@ function shouldShowContextPackMemory(memory, options) {
20894
20954
  return memory.score >= GENERIC_RECENT_MEMORY_MIN_SCORE;
20895
20955
  return memory.score >= GENERIC_STALE_MEMORY_MIN_SCORE;
20896
20956
  }
20957
+ function eventBelongsToDifferentProject(event, projectPath) {
20958
+ if (!projectPath)
20959
+ return false;
20960
+ const metadata = event.metadata || {};
20961
+ const metadataProjectRefs = metadataProjectReferenceValues(metadata);
20962
+ if (metadataProjectRefs.length > 0) {
20963
+ return !metadataProjectRefs.some((value) => projectReferenceMatches(value, projectPath));
20964
+ }
20965
+ if (isUnscopedImportedHistory(metadata))
20966
+ return true;
20967
+ return mentionsDifferentWorkspaceProject(event.content || "", projectPath);
20968
+ }
20969
+ function isUnscopedImportedHistory(metadata) {
20970
+ return typeof metadata.importedFrom === "string" || typeof metadata.sourceSessionId === "string" || typeof metadata.sourceSessionHash === "string" || typeof metadata.transcriptPath === "string";
20971
+ }
20972
+ var PROJECT_METADATA_KEYS = /* @__PURE__ */ new Set([
20973
+ "projectPath",
20974
+ "sourceProjectPath",
20975
+ "workspacePath",
20976
+ "sourceWorkspacePath",
20977
+ "cwd",
20978
+ "sourceCwd",
20979
+ "currentWorkingDirectory",
20980
+ "projectRoot",
20981
+ "repoPath",
20982
+ "repositoryPath"
20983
+ ]);
20984
+ function metadataProjectReferenceValues(metadata) {
20985
+ const values = [];
20986
+ for (const [key, value] of Object.entries(metadata)) {
20987
+ if (!PROJECT_METADATA_KEYS.has(key))
20988
+ continue;
20989
+ if (typeof value === "string" && value.trim().length > 0) {
20990
+ values.push(value.trim());
20991
+ }
20992
+ }
20993
+ return values;
20994
+ }
20995
+ function projectReferenceMatches(reference, projectPath) {
20996
+ const normalizedReference = normalizeProjectReference(reference);
20997
+ const normalizedProjectPath = normalizeProjectReference(projectPath);
20998
+ if (normalizedReference === normalizedProjectPath)
20999
+ return true;
21000
+ return normalizedReference.startsWith(`${normalizedProjectPath}/`);
21001
+ }
21002
+ function normalizeProjectReference(value) {
21003
+ return value.trim().replace(/\\/g, "/").replace(/\/+$/g, "").toLowerCase();
21004
+ }
20897
21005
  function mentionsDifferentWorkspaceProject(content, projectPath) {
20898
21006
  const currentProject = basenameOfPath(projectPath);
20899
21007
  if (!currentProject)
@@ -21124,9 +21232,11 @@ function formatMetadataValue(value) {
21124
21232
  function textResult(text) {
21125
21233
  return { content: [{ type: "text", text }] };
21126
21234
  }
21127
- async function handleMemStats(memoryService) {
21235
+ async function handleMemStats(memoryService, args) {
21128
21236
  const stats = await memoryService.getStats();
21129
21237
  const recentEvents = await memoryService.getRecentEvents(1e4);
21238
+ const outboxStats = await readMcpOutboxStats(memoryService);
21239
+ const storageView = buildMcpStatsStorageView(optionalString(args.projectPath));
21130
21240
  const uniqueSessions = new Set(recentEvents.map((e) => e.sessionId));
21131
21241
  const lines = [
21132
21242
  "## Memory Statistics",
@@ -21135,6 +21245,19 @@ async function handleMemStats(memoryService) {
21135
21245
  `- **Total Vectors**: ${stats.vectorCount}`,
21136
21246
  `- **Sessions**: ${uniqueSessions.size}`,
21137
21247
  "",
21248
+ "### Storage View / Freshness",
21249
+ "",
21250
+ `- Storage View: ${storageView.storageView}`,
21251
+ `- Storage Path Label: ${storageView.storagePathLabel}`,
21252
+ `- Embedder Model: ${storageView.embedderModel}`,
21253
+ `- Vector Table Dimension: ${storageView.vectorTableDimension}`,
21254
+ `- Pending Embeddings: ${outboxStats.embedding.pending}`,
21255
+ `- Embedding Outbox: pending=${outboxStats.embedding.pending}, processing=${outboxStats.embedding.processing}, failed=${outboxStats.embedding.failed}, total=${outboxStats.embedding.total}`,
21256
+ `- Vector Outbox Pending: ${outboxStats.vector.pending}`,
21257
+ `- Vector Outbox: pending=${outboxStats.vector.pending}, processing=${outboxStats.vector.processing}, failed=${outboxStats.vector.failed}, total=${outboxStats.vector.total}`,
21258
+ "- MCP/CLI parity: CLI `stats -p <project>` and MCP `mem-stats(projectPath=...)` should use this same storage view label.",
21259
+ "- Restart guidance: if CLI and MCP counts differ for this storage view after import/build, restart the long-lived MCP/Hermes gateway process.",
21260
+ "",
21138
21261
  "### Events by Type",
21139
21262
  ""
21140
21263
  ];
@@ -21149,6 +21272,35 @@ async function handleMemStats(memoryService) {
21149
21272
  content: [{ type: "text", text: lines.join("\n") }]
21150
21273
  };
21151
21274
  }
21275
+ async function readMcpOutboxStats(memoryService) {
21276
+ try {
21277
+ return await memoryService.getOutboxStats();
21278
+ } catch {
21279
+ return {
21280
+ embedding: { pending: 0, processing: 0, failed: 0, total: 0 },
21281
+ vector: { pending: 0, processing: 0, failed: 0, total: 0 }
21282
+ };
21283
+ }
21284
+ }
21285
+ function buildMcpStatsStorageView(projectPath) {
21286
+ const embedderModel = process.env.CLAUDE_MEMORY_EMBEDDING_MODEL || DEFAULT_EMBEDDING_MODEL;
21287
+ const requestedProjectPath = projectPath?.trim();
21288
+ if (requestedProjectPath) {
21289
+ const projectHash = hashProjectPath(requestedProjectPath);
21290
+ return {
21291
+ storageView: `project:${projectHash}`,
21292
+ storagePathLabel: `~/.claude-code/memory/projects/${projectHash}`,
21293
+ embedderModel,
21294
+ vectorTableDimension: "unknown (not recorded in current vector metadata)"
21295
+ };
21296
+ }
21297
+ return {
21298
+ storageView: "global",
21299
+ storagePathLabel: "~/.claude-code/memory",
21300
+ embedderModel,
21301
+ vectorTableDimension: "unknown (not recorded in current vector metadata)"
21302
+ };
21303
+ }
21152
21304
 
21153
21305
  // src/extensions/mcp/index.ts
21154
21306
  var server = new Server(