claude-memory-layer 1.0.45 → 1.0.46

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/index.js CHANGED
@@ -10662,6 +10662,18 @@ var COMMAND_ARTIFACT_PATTERNS = [
10662
10662
  /<local-command-stdout>[\s\S]*?<\/local-command-stdout>/i,
10663
10663
  /<local-command-stderr>[\s\S]*?<\/local-command-stderr>/i
10664
10664
  ];
10665
+ var LOW_SIGNAL_CONTEXT_PATTERNS = [
10666
+ /<environment_context\b[\s\S]*<\/environment_context>/i,
10667
+ /<turn_aborted>/i,
10668
+ /^#\s*AGENTS\.md\s+instructions\b[\s\S]*<INSTRUCTIONS>/i,
10669
+ /^\s*(?:understood[,\s.]*)?(?:stopping|stopped|pausing|paused)\s+here\b[\s\S]{0,180}\blet\s+me\s+know\s+when\s+you(?:'d|\s+would)?\s+like\s+to\s+continue\b/i,
10670
+ /^\s*\[?CONTEXT\s+COMPACTION\s*[—-]\s*REFERENCE\s+ONLY\]?\b[\s\S]{0,600}\b(?:earlier\s+turns\s+were\s+compacted|handoff\s+from\s+a\s+previous\s+context\s+window|active\s+task)\b/i,
10671
+ /^\s*Summary\s+generation\s+was\s+unavailable\.\s*\d+\s+message\(s\)\s+were\s+removed\s+to\s+free\s+context\s+space\b/i,
10672
+ /^\s*---\s*END\s+OF\s+CONTEXT\s+SUMMARY\b/i,
10673
+ /^\s*\[Your\s+active\s+task\s+list\s+was\s+preserved\s+across\s+context\s+compression\]/i,
10674
+ /^➜\s+\S+\s+git:\([^)]*\)\s+/i,
10675
+ /^\$\s+\S+/i
10676
+ ];
10665
10677
  var CONTINUATION_QUERY_PATTERNS = [
10666
10678
  /^\s*(?:continue|resume|next|what(?:'s| is)? next|next\s+(?:step|task|action)|recommended\s+(?:next\s+)?(?:step|task|action)|what should (?:we|i) do next)\??\s*$/i,
10667
10679
  /^\s*(?:응\s*)?(?:이어서(?:\s*진행(?:해줘)?)?|계속(?:\s*해줘)?|다음\s*(?:단계|작업|추천\s*작업|추천|할\s*일)?(?:은|는)?(?:\s*(?:뭐야|진행(?:해줘)?))?\??|남은\s*(?:추가(?:로)?\s*)?(?:(?:할\s*만한\s*)?(?:작업|일)|할\s*일)?(?:은|는)?\s*(?:있어|있나|있나요|뭐야)\??|추천\s*작업(?:은|는)?(?:\s*뭐야)?\??|진행해줘)\s*$/i
@@ -10673,7 +10685,7 @@ var SHORT_REPAIR_FOLLOW_UP_PATTERNS = [
10673
10685
  var CURRENT_STATE_QUERY_PATTERNS = [
10674
10686
  /\bcurrent\b.*\b(?:state|status|deployment|blocker|pr|pull request)\b/i,
10675
10687
  /\b(?:still|as current|current)\b.*\b(?:unresolved|open|pending|not completed)\b/i,
10676
- /\b(?:old|obsolete|stale|resolved|already resolved)\b.*\b(?:current|still|unresolved|open|state|status)\b/i,
10688
+ /\b(?:old|obsolete|stale|resolved|already resolved)\b.*\b(?:current|still|unresolved|open|status)\b/i,
10677
10689
  /(?:현재|아직|이전|오래된|해결된).*(?:상태|미해결|열린|블로커|PR|풀리퀘스트)/i
10678
10690
  ];
10679
10691
  var STALE_CONTENT_PATTERNS = [
@@ -10819,6 +10831,21 @@ function isCommandArtifactQuery(query) {
10819
10831
  if (normalized.includes("command-name") || normalized.includes("command-message")) return true;
10820
10832
  return COMMAND_ARTIFACT_PATTERNS.some((pattern) => pattern.test(trimmed));
10821
10833
  }
10834
+ function isCommandArtifactContent(content) {
10835
+ const trimmed = content.trim();
10836
+ if (!trimmed) return false;
10837
+ const normalized = trimmed.toLowerCase();
10838
+ if (normalized.includes("local-command-stdout") || normalized.includes("local-command-stderr")) return true;
10839
+ if (normalized.includes("command-name") || normalized.includes("command-message")) return true;
10840
+ return COMMAND_ARTIFACT_PATTERNS.some((pattern) => pattern.test(trimmed));
10841
+ }
10842
+ function isLowSignalContextContent(content) {
10843
+ const trimmed = content.trim();
10844
+ if (!trimmed) return true;
10845
+ if (isCommandArtifactContent(trimmed)) return true;
10846
+ if (LOW_SIGNAL_CONTEXT_PATTERNS.some((pattern) => pattern.test(trimmed))) return true;
10847
+ return false;
10848
+ }
10822
10849
  function isGenericContinuationQuery(query) {
10823
10850
  const trimmed = query.trim();
10824
10851
  if (!trimmed) return false;
@@ -10836,6 +10863,16 @@ function isShortRepairFollowUpQuery(query) {
10836
10863
  if (tokens.length > 8) return false;
10837
10864
  return SHORT_REPAIR_FOLLOW_UP_PATTERNS.some((pattern) => pattern.test(trimmed));
10838
10865
  }
10866
+ function isLowConfidenceContextFallbackQuery(query) {
10867
+ const trimmed = query.trim();
10868
+ if (!trimmed) return false;
10869
+ if (isGenericContinuationQuery(trimmed) || isShortRepairFollowUpQuery(trimmed)) return true;
10870
+ const terms = new Set(tokenizeQualityText(trimmed));
10871
+ if ((terms.has("compacted") || terms.has("compaction")) && terms.has("handoff")) return false;
10872
+ const hasContinuationRecall = /^(?:continue|resume)\b/i.test(trimmed) && (terms.has("work") || terms.has("step") || terms.has("task") || terms.has("last") || terms.has("completed"));
10873
+ const hasValidationGateRecall = terms.has("validation") && (terms.has("gate") || terms.has("check")) && (terms.has("run") || terms.has("before") || terms.has("commit") || terms.has("committing") || terms.has("change"));
10874
+ return hasContinuationRecall || hasValidationGateRecall;
10875
+ }
10839
10876
  function isCurrentStateQuery(query) {
10840
10877
  const trimmed = query.trim();
10841
10878
  if (!trimmed) return false;
@@ -11103,7 +11140,14 @@ var Retriever = class {
11103
11140
  };
11104
11141
  fallbackTrace.push("fallback:summary");
11105
11142
  }
11106
- const memories = await this.enrichResults(current.results.slice(0, opts.topK), opts);
11143
+ const selectedResults = current.results.slice(0, opts.topK).filter((result2) => {
11144
+ if (current.matchResult.confidence !== "none") return true;
11145
+ if (isLowConfidenceContextFallbackQuery(query)) {
11146
+ return (result2.semanticScore ?? result2.score) >= 0.5 || result2.score >= 0.5;
11147
+ }
11148
+ return (result2.semanticScore ?? result2.score) >= 0.62 || result2.score >= 0.62;
11149
+ });
11150
+ const memories = await this.enrichResults(selectedResults, opts, query);
11107
11151
  const context = this.buildContext(memories, opts.maxTokens);
11108
11152
  return {
11109
11153
  memories,
@@ -11111,7 +11155,7 @@ var Retriever = class {
11111
11155
  totalTokens: this.estimateTokens(context),
11112
11156
  context,
11113
11157
  fallbackTrace,
11114
- selectedDebug: current.results.slice(0, opts.topK).map((r) => this.debugDetailForResult(r)),
11158
+ selectedDebug: selectedResults.map((r) => this.debugDetailForResult(r)),
11115
11159
  candidateDebug: (current.candidateResults || []).slice(0, Math.max(opts.topK * 3, 20)).map((r) => this.debugDetailForResult(r)),
11116
11160
  rawQueryText: current.queryRewriteKind ? query : void 0,
11117
11161
  effectiveQueryText: current.effectiveQueryText,
@@ -11206,6 +11250,7 @@ var Retriever = class {
11206
11250
  if (isCurrentStateQuery(options.query)) {
11207
11251
  filtered = filtered.filter((result2) => !isStaleOrSupersededContent(result2.content));
11208
11252
  }
11253
+ filtered = filtered.filter((result2) => !isLowSignalContextContent(result2.content));
11209
11254
  filtered = filtered.filter(
11210
11255
  (result2) => this.isGraphPathResult(result2) || hasDiscriminativeTermOverlap(options.query, result2.content)
11211
11256
  );
@@ -11546,7 +11591,7 @@ var Retriever = class {
11546
11591
  async retrieveRecent(limit = 100) {
11547
11592
  return this.eventStore.getRecentEvents(limit);
11548
11593
  }
11549
- async enrichResults(results, options) {
11594
+ async enrichResults(results, options, query) {
11550
11595
  const memories = [];
11551
11596
  for (const result2 of results) {
11552
11597
  const event = await this.eventStore.getEvent(result2.eventId);
@@ -11556,13 +11601,13 @@ var Retriever = class {
11556
11601
  }
11557
11602
  let sessionContext;
11558
11603
  if (options.includeSessionContext) {
11559
- sessionContext = await this.getSessionContext(event.sessionId, event.id);
11604
+ sessionContext = await this.getSessionContext(event.sessionId, event.id, query);
11560
11605
  }
11561
11606
  memories.push({ event, score: result2.score, sessionContext });
11562
11607
  }
11563
11608
  return memories;
11564
11609
  }
11565
- async getSessionContext(sessionId, eventId) {
11610
+ async getSessionContext(sessionId, eventId, query) {
11566
11611
  const sessionEvents = await this.eventStore.getSessionEvents(sessionId);
11567
11612
  const eventIndex = sessionEvents.findIndex((e) => e.id === eventId);
11568
11613
  if (eventIndex === -1) return void 0;
@@ -11570,7 +11615,9 @@ var Retriever = class {
11570
11615
  const end = Math.min(sessionEvents.length, eventIndex + 2);
11571
11616
  const contextEvents = sessionEvents.slice(start, end);
11572
11617
  if (contextEvents.length <= 1) return void 0;
11573
- return contextEvents.filter((e) => e.id !== eventId).map((e) => `[${e.eventType}]: ${e.content.slice(0, 200)}...`).join("\n");
11618
+ const suppressStaleState = isCurrentStateQuery(query);
11619
+ const contextLines = contextEvents.filter((e) => e.id !== eventId).filter((e) => !isLowSignalContextContent(e.content)).filter((e) => !(suppressStaleState && isStaleOrSupersededContent(e.content))).map((e) => `[${e.eventType}]: ${e.content.slice(0, 200)}...`);
11620
+ return contextLines.length > 0 ? contextLines.join("\n") : void 0;
11574
11621
  }
11575
11622
  buildUnifiedContext(projectResult, sharedMemories) {
11576
11623
  let context = projectResult.context;
@@ -17118,12 +17165,13 @@ var productValidationMatrix = [
17118
17165
  requirements: [
17119
17166
  "Expose an agent-ready project context pack that combines relevant retrieval results with recent project timeline.",
17120
17167
  "Support projectPath scoping so Hermes, Codex, and Claude Code can share the same project memory backend.",
17121
- "Keep output compact and citation-oriented so agents can follow up with source-ref or timeline tools."
17168
+ "Keep output compact and citation-oriented so agents can follow up with source-ref or timeline tools.",
17169
+ "Document MCP-only context-pack budget controls (`maxChars`, approximate `maxTokens`, and `compression: off|safe|aggressive`) and make the CLI/API parity boundary explicit."
17122
17170
  ],
17123
17171
  evidence: [
17124
- { kind: "test", ref: "tests/extensions/mcp-context-tools.test.ts", note: "Asserts context-pack output, projectPath routing, compact relevant memory citations, and recent timeline inclusion." },
17125
- { kind: "source", ref: "src/extensions/mcp/handlers.ts", note: "mem-context-pack handler formats relevant memories plus session summaries." },
17126
- { kind: "source", ref: "src/extensions/mcp/tools.ts", note: "MCP tool schema advertises projectPath, topK, recentLimit, and sessionLimit options." }
17172
+ { kind: "test", ref: "tests/extensions/mcp-context-tools.test.ts", note: "Asserts context-pack output, projectPath routing, compact relevant memory citations, sourceRef preservation, maxChars/maxTokens budget enforcement, compression modes, and recent timeline inclusion." },
17173
+ { kind: "source", ref: "src/extensions/mcp/handlers.ts", note: "mem-context-pack handler formats relevant memories plus session summaries and validates MCP-only budget/compression arguments before storage access." },
17174
+ { kind: "source", ref: "src/extensions/mcp/tools.ts", note: "MCP tool schema advertises projectPath, topK, recentLimit, sessionLimit, maxChars, maxTokens, and compression options; CLI/API parity is documented as MCP-only for context-pack generation." }
17127
17175
  ]
17128
17176
  },
17129
17177
  {
@@ -17696,10 +17744,11 @@ var ContextCompressor = class {
17696
17744
  compress(content, options) {
17697
17745
  const source = safeLabel(options.source) || "unknown";
17698
17746
  const mode = options.mode;
17747
+ const sourceRef = safeSourceRef(options.sourceRef ?? sourceRefFromMetadata(options.metadata ?? {}));
17699
17748
  const contentType = detectContextContentType(content, options.metadata ?? {});
17700
17749
  const originalLines = nonEmptyLines(content).length;
17701
17750
  if (mode === "off") {
17702
- return result(content, {
17751
+ return withSourceRefHint(result(content, {
17703
17752
  mode,
17704
17753
  source,
17705
17754
  contentType,
@@ -17708,21 +17757,21 @@ var ContextCompressor = class {
17708
17757
  originalLines,
17709
17758
  omittedLines: 0,
17710
17759
  signalCount: 0
17711
- });
17760
+ }), sourceRef);
17712
17761
  }
17713
17762
  if (contentType === "log") {
17714
- return this.compressLog(content, mode, source, contentType);
17763
+ return withSourceRefHint(this.compressLog(content, mode, source, contentType), sourceRef);
17715
17764
  }
17716
17765
  if (contentType === "markdown") {
17717
- return this.compressMarkdown(content, mode, source, contentType);
17766
+ return withSourceRefHint(this.compressMarkdown(content, mode, source, contentType), sourceRef);
17718
17767
  }
17719
17768
  if (contentType === "diff") {
17720
- return this.compressDiff(content, mode, source, contentType);
17769
+ return withSourceRefHint(this.compressDiff(content, mode, source, contentType), sourceRef);
17721
17770
  }
17722
17771
  if (contentType === "code") {
17723
- return this.compressCode(content, mode, source, contentType);
17772
+ return withSourceRefHint(this.compressCode(content, mode, source, contentType), sourceRef);
17724
17773
  }
17725
- return this.compressPlain(content, mode, source, contentType);
17774
+ return withSourceRefHint(this.compressPlain(content, mode, source, contentType), sourceRef);
17726
17775
  }
17727
17776
  compressLog(content, mode, source, contentType) {
17728
17777
  const lines = nonEmptyLines(content);
@@ -17860,11 +17909,15 @@ function summarizeCompressionTelemetry(metadata) {
17860
17909
  const totalOriginalChars = metadata.reduce((sum, item) => sum + item.originalChars, 0);
17861
17910
  const totalCompressedChars = metadata.reduce((sum, item) => sum + item.compressedChars, 0);
17862
17911
  const totalSavedChars = metadata.reduce((sum, item) => sum + item.savedChars, 0);
17912
+ const totalOmittedLines = metadata.reduce((sum, item) => sum + item.omittedLines, 0);
17913
+ const sourceRefsPreserved = metadata.filter((item) => item.sourceRefPreserved).length;
17863
17914
  return {
17864
17915
  totalItems: metadata.length,
17865
17916
  totalOriginalChars,
17866
17917
  totalCompressedChars,
17867
17918
  totalSavedChars,
17919
+ totalOmittedLines,
17920
+ sourceRefsPreserved,
17868
17921
  bySource: summarizeBy(metadata, "source"),
17869
17922
  byStrategy: summarizeBy(metadata, "strategy")
17870
17923
  };
@@ -17878,12 +17931,16 @@ function summarizeBy(metadata, key) {
17878
17931
  items: 0,
17879
17932
  originalChars: 0,
17880
17933
  compressedChars: 0,
17881
- savedChars: 0
17934
+ savedChars: 0,
17935
+ omittedLines: 0,
17936
+ sourceRefsPreserved: 0
17882
17937
  };
17883
17938
  existing.items += 1;
17884
17939
  existing.originalChars += item.originalChars;
17885
17940
  existing.compressedChars += item.compressedChars;
17886
17941
  existing.savedChars += item.savedChars;
17942
+ existing.omittedLines += item.omittedLines;
17943
+ if (item.sourceRefPreserved) existing.sourceRefsPreserved += 1;
17887
17944
  groups.set(groupKey, existing);
17888
17945
  }
17889
17946
  return Array.from(groups.values()).sort((a, b) => String(a[key] ?? "").localeCompare(String(b[key] ?? "")));
@@ -17898,7 +17955,8 @@ function result(text, base) {
17898
17955
  ...base,
17899
17956
  compressedChars,
17900
17957
  savedChars,
17901
- compressionRatio
17958
+ compressionRatio,
17959
+ sourceRefPreserved: false
17902
17960
  }
17903
17961
  };
17904
17962
  }
@@ -17947,6 +18005,65 @@ function safeLabel(value) {
17947
18005
  const cleaned = value.replace(/[^A-Za-z0-9_.:-]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 80);
17948
18006
  return cleaned || void 0;
17949
18007
  }
18008
+ function sourceRefFromMetadata(metadata) {
18009
+ const explicit = firstString(metadata, ["sourceRef", "source_ref", "sourceReference", "source_reference"]);
18010
+ if (explicit) return explicit;
18011
+ const citation = firstString(metadata, ["citationId", "citation_id", "memoryCitationId", "memory_citation_id"]);
18012
+ if (citation) return citation.startsWith("mem:") ? citation : `mem:${citation}`;
18013
+ const eventId = firstString(metadata, ["eventId", "event_id"]);
18014
+ if (eventId) return eventId.startsWith("event:") ? eventId : `event:${eventId}`;
18015
+ return void 0;
18016
+ }
18017
+ function firstString(metadata, keys) {
18018
+ for (const key of keys) {
18019
+ const value = metadata[key];
18020
+ if (typeof value === "string" && value.trim().length > 0) return value.trim();
18021
+ }
18022
+ return void 0;
18023
+ }
18024
+ function safeSourceRef(value) {
18025
+ if (!value) return void 0;
18026
+ const normalized = value.trim().replace(/^\[?(mem|event):/i, (_, prefix) => `${prefix.toLowerCase()}:`).replace(/\]?$/g, "");
18027
+ if (!normalized || /(?:password|secret|api[_-]?key|token|bearer)/i.test(normalized)) return void 0;
18028
+ const cleaned = normalized.replace(/[^A-Za-z0-9_.:-]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 140);
18029
+ return cleaned || void 0;
18030
+ }
18031
+ function withSourceRefHint(compression, sourceRef) {
18032
+ if (!sourceRef) return compression;
18033
+ if (compression.metadata.mode === "off" || compression.metadata.strategy === "none") {
18034
+ return {
18035
+ text: compression.text,
18036
+ metadata: {
18037
+ ...compression.metadata,
18038
+ sourceRef,
18039
+ sourceRefPreserved: false
18040
+ }
18041
+ };
18042
+ }
18043
+ const hint = `sourceRef=${sourceRef} expand=mem-source-ref`;
18044
+ const text = compression.text.includes(hint) ? compression.text : addSourceRefHint(compression.text, hint);
18045
+ const compressedChars = text.length;
18046
+ const savedChars = Math.max(0, compression.metadata.originalChars - compressedChars);
18047
+ const compressionRatio = compression.metadata.originalChars > 0 ? compressedChars / compression.metadata.originalChars : 1;
18048
+ return {
18049
+ text,
18050
+ metadata: {
18051
+ ...compression.metadata,
18052
+ sourceRef,
18053
+ sourceRefPreserved: text.includes(`sourceRef=${sourceRef}`) && text.includes("expand=mem-source-ref"),
18054
+ compressedChars,
18055
+ savedChars,
18056
+ compressionRatio
18057
+ }
18058
+ };
18059
+ }
18060
+ function addSourceRefHint(text, hint) {
18061
+ if (text.startsWith("[compressed ")) {
18062
+ return text.replace(/^\[([^\]]+)\]/, `[$1; ${hint}]`);
18063
+ }
18064
+ return `[${hint}]
18065
+ ${text}`;
18066
+ }
17950
18067
 
17951
18068
  // src/core/task/task-matcher.ts
17952
18069
  var DEFAULT_CONFIG7 = {