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/mcp/index.js CHANGED
@@ -10412,11 +10412,15 @@ var tools = [
10412
10412
  },
10413
10413
  maxChars: {
10414
10414
  type: "number",
10415
- description: "Maximum final context-pack characters after safe compression (default: no hard cap, max: 50000)"
10415
+ minimum: 1e3,
10416
+ maximum: 5e4,
10417
+ description: "Maximum final context-pack characters after the selected compression mode and final assembly trimming (default: no hard cap, max: 50000)"
10416
10418
  },
10417
10419
  maxTokens: {
10418
10420
  type: "number",
10419
- description: "Maximum final context-pack tokens, estimated at ~4 chars/token, after safe compression (default: no hard cap)"
10421
+ minimum: 250,
10422
+ maximum: 12500,
10423
+ description: "Maximum final context-pack tokens, estimated at ~4 chars/token after the selected compression mode and final assembly trimming (default: no hard cap)"
10420
10424
  },
10421
10425
  refreshLatest: {
10422
10426
  type: "boolean",
@@ -21118,6 +21122,10 @@ var LOW_SIGNAL_CONTEXT_PATTERNS = [
21118
21122
  /<turn_aborted>/i,
21119
21123
  /^#\s*AGENTS\.md\s+instructions\b[\s\S]*<INSTRUCTIONS>/i,
21120
21124
  /^\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,
21125
+ /^\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,
21126
+ /^\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,
21127
+ /^\s*---\s*END\s+OF\s+CONTEXT\s+SUMMARY\b/i,
21128
+ /^\s*\[Your\s+active\s+task\s+list\s+was\s+preserved\s+across\s+context\s+compression\]/i,
21121
21129
  /^➜\s+\S+\s+git:\([^)]*\)\s+/i,
21122
21130
  /^\$\s+\S+/i
21123
21131
  ];
@@ -21132,7 +21140,7 @@ var SHORT_REPAIR_FOLLOW_UP_PATTERNS = [
21132
21140
  var CURRENT_STATE_QUERY_PATTERNS = [
21133
21141
  /\bcurrent\b.*\b(?:state|status|deployment|blocker|pr|pull request)\b/i,
21134
21142
  /\b(?:still|as current|current)\b.*\b(?:unresolved|open|pending|not completed)\b/i,
21135
- /\b(?:old|obsolete|stale|resolved|already resolved)\b.*\b(?:current|still|unresolved|open|state|status)\b/i,
21143
+ /\b(?:old|obsolete|stale|resolved|already resolved)\b.*\b(?:current|still|unresolved|open|status)\b/i,
21136
21144
  /(?:현재|아직|이전|오래된|해결된).*(?:상태|미해결|열린|블로커|PR|풀리퀘스트)/i
21137
21145
  ];
21138
21146
  var STALE_CONTENT_PATTERNS = [
@@ -21310,6 +21318,16 @@ function isShortRepairFollowUpQuery(query) {
21310
21318
  if (tokens.length > 8) return false;
21311
21319
  return SHORT_REPAIR_FOLLOW_UP_PATTERNS.some((pattern) => pattern.test(trimmed));
21312
21320
  }
21321
+ function isLowConfidenceContextFallbackQuery(query) {
21322
+ const trimmed = query.trim();
21323
+ if (!trimmed) return false;
21324
+ if (isGenericContinuationQuery(trimmed) || isShortRepairFollowUpQuery(trimmed)) return true;
21325
+ const terms = new Set(tokenizeQualityText(trimmed));
21326
+ if ((terms.has("compacted") || terms.has("compaction")) && terms.has("handoff")) return false;
21327
+ const hasContinuationRecall = /^(?:continue|resume)\b/i.test(trimmed) && (terms.has("work") || terms.has("step") || terms.has("task") || terms.has("last") || terms.has("completed"));
21328
+ 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"));
21329
+ return hasContinuationRecall || hasValidationGateRecall;
21330
+ }
21313
21331
  function isCurrentStateQuery(query) {
21314
21332
  const trimmed = query.trim();
21315
21333
  if (!trimmed) return false;
@@ -21577,7 +21595,14 @@ var Retriever = class {
21577
21595
  };
21578
21596
  fallbackTrace.push("fallback:summary");
21579
21597
  }
21580
- const memories = await this.enrichResults(current.results.slice(0, opts.topK), opts);
21598
+ const selectedResults = current.results.slice(0, opts.topK).filter((result2) => {
21599
+ if (current.matchResult.confidence !== "none") return true;
21600
+ if (isLowConfidenceContextFallbackQuery(query)) {
21601
+ return (result2.semanticScore ?? result2.score) >= 0.5 || result2.score >= 0.5;
21602
+ }
21603
+ return (result2.semanticScore ?? result2.score) >= 0.62 || result2.score >= 0.62;
21604
+ });
21605
+ const memories = await this.enrichResults(selectedResults, opts, query);
21581
21606
  const context = this.buildContext(memories, opts.maxTokens);
21582
21607
  return {
21583
21608
  memories,
@@ -21585,7 +21610,7 @@ var Retriever = class {
21585
21610
  totalTokens: this.estimateTokens(context),
21586
21611
  context,
21587
21612
  fallbackTrace,
21588
- selectedDebug: current.results.slice(0, opts.topK).map((r) => this.debugDetailForResult(r)),
21613
+ selectedDebug: selectedResults.map((r) => this.debugDetailForResult(r)),
21589
21614
  candidateDebug: (current.candidateResults || []).slice(0, Math.max(opts.topK * 3, 20)).map((r) => this.debugDetailForResult(r)),
21590
21615
  rawQueryText: current.queryRewriteKind ? query : void 0,
21591
21616
  effectiveQueryText: current.effectiveQueryText,
@@ -21680,6 +21705,7 @@ var Retriever = class {
21680
21705
  if (isCurrentStateQuery(options.query)) {
21681
21706
  filtered = filtered.filter((result2) => !isStaleOrSupersededContent(result2.content));
21682
21707
  }
21708
+ filtered = filtered.filter((result2) => !isLowSignalContextContent(result2.content));
21683
21709
  filtered = filtered.filter(
21684
21710
  (result2) => this.isGraphPathResult(result2) || hasDiscriminativeTermOverlap(options.query, result2.content)
21685
21711
  );
@@ -22020,7 +22046,7 @@ var Retriever = class {
22020
22046
  async retrieveRecent(limit = 100) {
22021
22047
  return this.eventStore.getRecentEvents(limit);
22022
22048
  }
22023
- async enrichResults(results, options) {
22049
+ async enrichResults(results, options, query) {
22024
22050
  const memories = [];
22025
22051
  for (const result2 of results) {
22026
22052
  const event = await this.eventStore.getEvent(result2.eventId);
@@ -22030,13 +22056,13 @@ var Retriever = class {
22030
22056
  }
22031
22057
  let sessionContext;
22032
22058
  if (options.includeSessionContext) {
22033
- sessionContext = await this.getSessionContext(event.sessionId, event.id);
22059
+ sessionContext = await this.getSessionContext(event.sessionId, event.id, query);
22034
22060
  }
22035
22061
  memories.push({ event, score: result2.score, sessionContext });
22036
22062
  }
22037
22063
  return memories;
22038
22064
  }
22039
- async getSessionContext(sessionId, eventId) {
22065
+ async getSessionContext(sessionId, eventId, query) {
22040
22066
  const sessionEvents = await this.eventStore.getSessionEvents(sessionId);
22041
22067
  const eventIndex = sessionEvents.findIndex((e) => e.id === eventId);
22042
22068
  if (eventIndex === -1) return void 0;
@@ -22044,7 +22070,9 @@ var Retriever = class {
22044
22070
  const end = Math.min(sessionEvents.length, eventIndex + 2);
22045
22071
  const contextEvents = sessionEvents.slice(start, end);
22046
22072
  if (contextEvents.length <= 1) return void 0;
22047
- return contextEvents.filter((e) => e.id !== eventId).map((e) => `[${e.eventType}]: ${e.content.slice(0, 200)}...`).join("\n");
22073
+ const suppressStaleState = isCurrentStateQuery(query);
22074
+ 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)}...`);
22075
+ return contextLines.length > 0 ? contextLines.join("\n") : void 0;
22048
22076
  }
22049
22077
  buildUnifiedContext(projectResult, sharedMemories) {
22050
22078
  let context = projectResult.context;
@@ -26689,10 +26717,11 @@ var ContextCompressor = class {
26689
26717
  compress(content, options) {
26690
26718
  const source = safeLabel(options.source) || "unknown";
26691
26719
  const mode = options.mode;
26720
+ const sourceRef = safeSourceRef(options.sourceRef ?? sourceRefFromMetadata(options.metadata ?? {}));
26692
26721
  const contentType = detectContextContentType(content, options.metadata ?? {});
26693
26722
  const originalLines = nonEmptyLines(content).length;
26694
26723
  if (mode === "off") {
26695
- return result(content, {
26724
+ return withSourceRefHint(result(content, {
26696
26725
  mode,
26697
26726
  source,
26698
26727
  contentType,
@@ -26701,21 +26730,21 @@ var ContextCompressor = class {
26701
26730
  originalLines,
26702
26731
  omittedLines: 0,
26703
26732
  signalCount: 0
26704
- });
26733
+ }), sourceRef);
26705
26734
  }
26706
26735
  if (contentType === "log") {
26707
- return this.compressLog(content, mode, source, contentType);
26736
+ return withSourceRefHint(this.compressLog(content, mode, source, contentType), sourceRef);
26708
26737
  }
26709
26738
  if (contentType === "markdown") {
26710
- return this.compressMarkdown(content, mode, source, contentType);
26739
+ return withSourceRefHint(this.compressMarkdown(content, mode, source, contentType), sourceRef);
26711
26740
  }
26712
26741
  if (contentType === "diff") {
26713
- return this.compressDiff(content, mode, source, contentType);
26742
+ return withSourceRefHint(this.compressDiff(content, mode, source, contentType), sourceRef);
26714
26743
  }
26715
26744
  if (contentType === "code") {
26716
- return this.compressCode(content, mode, source, contentType);
26745
+ return withSourceRefHint(this.compressCode(content, mode, source, contentType), sourceRef);
26717
26746
  }
26718
- return this.compressPlain(content, mode, source, contentType);
26747
+ return withSourceRefHint(this.compressPlain(content, mode, source, contentType), sourceRef);
26719
26748
  }
26720
26749
  compressLog(content, mode, source, contentType) {
26721
26750
  const lines = nonEmptyLines(content);
@@ -26853,11 +26882,15 @@ function summarizeCompressionTelemetry(metadata) {
26853
26882
  const totalOriginalChars = metadata.reduce((sum, item) => sum + item.originalChars, 0);
26854
26883
  const totalCompressedChars = metadata.reduce((sum, item) => sum + item.compressedChars, 0);
26855
26884
  const totalSavedChars = metadata.reduce((sum, item) => sum + item.savedChars, 0);
26885
+ const totalOmittedLines = metadata.reduce((sum, item) => sum + item.omittedLines, 0);
26886
+ const sourceRefsPreserved = metadata.filter((item) => item.sourceRefPreserved).length;
26856
26887
  return {
26857
26888
  totalItems: metadata.length,
26858
26889
  totalOriginalChars,
26859
26890
  totalCompressedChars,
26860
26891
  totalSavedChars,
26892
+ totalOmittedLines,
26893
+ sourceRefsPreserved,
26861
26894
  bySource: summarizeBy(metadata, "source"),
26862
26895
  byStrategy: summarizeBy(metadata, "strategy")
26863
26896
  };
@@ -26871,12 +26904,16 @@ function summarizeBy(metadata, key) {
26871
26904
  items: 0,
26872
26905
  originalChars: 0,
26873
26906
  compressedChars: 0,
26874
- savedChars: 0
26907
+ savedChars: 0,
26908
+ omittedLines: 0,
26909
+ sourceRefsPreserved: 0
26875
26910
  };
26876
26911
  existing.items += 1;
26877
26912
  existing.originalChars += item.originalChars;
26878
26913
  existing.compressedChars += item.compressedChars;
26879
26914
  existing.savedChars += item.savedChars;
26915
+ existing.omittedLines += item.omittedLines;
26916
+ if (item.sourceRefPreserved) existing.sourceRefsPreserved += 1;
26880
26917
  groups.set(groupKey, existing);
26881
26918
  }
26882
26919
  return Array.from(groups.values()).sort((a, b) => String(a[key] ?? "").localeCompare(String(b[key] ?? "")));
@@ -26891,7 +26928,8 @@ function result(text, base) {
26891
26928
  ...base,
26892
26929
  compressedChars,
26893
26930
  savedChars,
26894
- compressionRatio
26931
+ compressionRatio,
26932
+ sourceRefPreserved: false
26895
26933
  }
26896
26934
  };
26897
26935
  }
@@ -26940,6 +26978,65 @@ function safeLabel(value) {
26940
26978
  const cleaned = value.replace(/[^A-Za-z0-9_.:-]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 80);
26941
26979
  return cleaned || void 0;
26942
26980
  }
26981
+ function sourceRefFromMetadata(metadata) {
26982
+ const explicit = firstString(metadata, ["sourceRef", "source_ref", "sourceReference", "source_reference"]);
26983
+ if (explicit) return explicit;
26984
+ const citation = firstString(metadata, ["citationId", "citation_id", "memoryCitationId", "memory_citation_id"]);
26985
+ if (citation) return citation.startsWith("mem:") ? citation : `mem:${citation}`;
26986
+ const eventId = firstString(metadata, ["eventId", "event_id"]);
26987
+ if (eventId) return eventId.startsWith("event:") ? eventId : `event:${eventId}`;
26988
+ return void 0;
26989
+ }
26990
+ function firstString(metadata, keys) {
26991
+ for (const key of keys) {
26992
+ const value = metadata[key];
26993
+ if (typeof value === "string" && value.trim().length > 0) return value.trim();
26994
+ }
26995
+ return void 0;
26996
+ }
26997
+ function safeSourceRef(value) {
26998
+ if (!value) return void 0;
26999
+ const normalized = value.trim().replace(/^\[?(mem|event):/i, (_, prefix) => `${prefix.toLowerCase()}:`).replace(/\]?$/g, "");
27000
+ if (!normalized || /(?:password|secret|api[_-]?key|token|bearer)/i.test(normalized)) return void 0;
27001
+ const cleaned = normalized.replace(/[^A-Za-z0-9_.:-]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 140);
27002
+ return cleaned || void 0;
27003
+ }
27004
+ function withSourceRefHint(compression, sourceRef) {
27005
+ if (!sourceRef) return compression;
27006
+ if (compression.metadata.mode === "off" || compression.metadata.strategy === "none") {
27007
+ return {
27008
+ text: compression.text,
27009
+ metadata: {
27010
+ ...compression.metadata,
27011
+ sourceRef,
27012
+ sourceRefPreserved: false
27013
+ }
27014
+ };
27015
+ }
27016
+ const hint = `sourceRef=${sourceRef} expand=mem-source-ref`;
27017
+ const text = compression.text.includes(hint) ? compression.text : addSourceRefHint(compression.text, hint);
27018
+ const compressedChars = text.length;
27019
+ const savedChars = Math.max(0, compression.metadata.originalChars - compressedChars);
27020
+ const compressionRatio = compression.metadata.originalChars > 0 ? compressedChars / compression.metadata.originalChars : 1;
27021
+ return {
27022
+ text,
27023
+ metadata: {
27024
+ ...compression.metadata,
27025
+ sourceRef,
27026
+ sourceRefPreserved: text.includes(`sourceRef=${sourceRef}`) && text.includes("expand=mem-source-ref"),
27027
+ compressedChars,
27028
+ savedChars,
27029
+ compressionRatio
27030
+ }
27031
+ };
27032
+ }
27033
+ function addSourceRefHint(text, hint) {
27034
+ if (text.startsWith("[compressed ")) {
27035
+ return text.replace(/^\[([^\]]+)\]/, `[$1; ${hint}]`);
27036
+ }
27037
+ return `[${hint}]
27038
+ ${text}`;
27039
+ }
26943
27040
 
26944
27041
  // src/extensions/mcp/handlers.ts
26945
27042
  function resolveMemoryService(args) {
@@ -26977,6 +27074,9 @@ async function handleToolCall(name, args) {
26977
27074
  if (name === "mem-context-pack" && hasMemContextPackPerspectiveArgs(args)) {
26978
27075
  validateMemContextPackPerspectiveArgs(args);
26979
27076
  }
27077
+ if (name === "mem-context-pack") {
27078
+ validateMemContextPackBudgetArgs(args);
27079
+ }
26980
27080
  if (MEMORY_OPERATION_TOOL_NAMES.has(name)) {
26981
27081
  return await handleMemoryOperationTool(name, args);
26982
27082
  }
@@ -27723,9 +27823,11 @@ async function handleExternalMarketContext(args) {
27723
27823
  }
27724
27824
  async function handleMemSearch(memoryService, args) {
27725
27825
  const query = args.query;
27726
- const topK = Math.min(args.topK || 5, 20);
27826
+ const topK = numberArg(args.topK, 5, 1, 20);
27827
+ const fetchTopK = Math.min(topK * 3, 20);
27727
27828
  const sessionId = args.sessionId;
27728
- const search = await retrieveMcpMemories(memoryService, query, { topK, sessionId });
27829
+ const eventType = eventTypeArg(args.eventType);
27830
+ const search = await retrieveMcpMemories(memoryService, query, { topK, fetchTopK, sessionId, eventType });
27729
27831
  const lines = [
27730
27832
  "## Memory Search Results",
27731
27833
  "",
@@ -27739,7 +27841,7 @@ async function handleMemSearch(memoryService, args) {
27739
27841
  const m = search.memories[i];
27740
27842
  const citationId = generateCitationId(m.event.id);
27741
27843
  const date = m.event.timestamp.toISOString().split("T")[0];
27742
- const preview = m.event.content.slice(0, 100) + (m.event.content.length > 100 ? "..." : "");
27844
+ const preview = sanitizeOperationString(m.event.content, 100);
27743
27845
  lines.push(`### ${i + 1}. [mem:${citationId}] (score: ${m.score.toFixed(2)})`);
27744
27846
  lines.push(`**Type**: ${m.event.eventType} | **Date**: ${date}`);
27745
27847
  lines.push(`> ${preview}`);
@@ -27754,29 +27856,41 @@ async function handleMemSearch(memoryService, args) {
27754
27856
  var SEMANTIC_VECTOR_FALLBACK_WARNING = "Warning: semantic/vector retrieval unavailable; used keyword fallback.";
27755
27857
  var SEMANTIC_VECTOR_FALLBACK_FAILED_WARNING = "Warning: semantic/vector retrieval unavailable; keyword fallback failed.";
27756
27858
  async function retrieveMcpMemories(memoryService, query, options) {
27859
+ const fetchTopK = Math.max(options.topK, options.fetchTopK ?? options.topK);
27757
27860
  try {
27758
27861
  const result2 = await memoryService.retrieveMemories(query, {
27759
- topK: options.topK,
27862
+ topK: fetchTopK,
27760
27863
  sessionId: options.sessionId,
27761
27864
  recordTrace: false
27762
27865
  });
27763
- return { memories: result2.memories };
27866
+ return { memories: selectMcpMemoryResults(result2.memories, options.topK, options.eventType) };
27764
27867
  } catch (error) {
27765
27868
  if (!isVectorSchemaMismatchError(error)) {
27766
27869
  throw error;
27767
27870
  }
27768
27871
  try {
27769
- const memories = options.sessionId ? rankSessionKeywordMatches(
27872
+ const candidates = options.sessionId ? rankSessionKeywordMatches(
27770
27873
  query,
27771
27874
  await memoryService.getSessionHistory(options.sessionId),
27772
- options.topK
27773
- ) : await memoryService.keywordSearch(query, { topK: options.topK });
27774
- return { memories, warning: SEMANTIC_VECTOR_FALLBACK_WARNING };
27875
+ fetchTopK
27876
+ ) : await memoryService.keywordSearch(query, { topK: fetchTopK });
27877
+ return { memories: selectMcpMemoryResults(candidates, options.topK, options.eventType), warning: SEMANTIC_VECTOR_FALLBACK_WARNING };
27775
27878
  } catch {
27776
27879
  return { memories: [], warning: SEMANTIC_VECTOR_FALLBACK_FAILED_WARNING };
27777
27880
  }
27778
27881
  }
27779
27882
  }
27883
+ function selectMcpMemoryResults(memories, topK, eventType) {
27884
+ return memories.filter((memory) => !isLowSignalContextContent(memory.event.content || "")).filter((memory) => eventType === void 0 || memory.event.eventType === eventType).slice(0, topK);
27885
+ }
27886
+ function eventTypeArg(value) {
27887
+ if (value === void 0 || value === null || value === "") return void 0;
27888
+ const normalized = String(value).trim();
27889
+ if (normalized === "user_prompt" || normalized === "agent_response" || normalized === "tool_observation" || normalized === "session_summary") {
27890
+ return normalized;
27891
+ }
27892
+ throw new Error("Invalid eventType: expected user_prompt, agent_response, tool_observation, or session_summary");
27893
+ }
27780
27894
  function isVectorSchemaMismatchError(error) {
27781
27895
  const message = error instanceof Error ? error.message : String(error);
27782
27896
  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);
@@ -27812,7 +27926,7 @@ async function handleMemTimeline(memoryService, args) {
27812
27926
  lines.push(`Event ${targetId} not found.`);
27813
27927
  continue;
27814
27928
  }
27815
- const sessionEvents = recentEvents.filter((e) => e.sessionId === targetEvent.sessionId).sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
27929
+ const sessionEvents = recentEvents.filter((e) => e.sessionId === targetEvent.sessionId).filter((e) => e.id === targetEvent.id || !isLowSignalContextContent(e.content || "")).sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
27816
27930
  const eventIndex = sessionEvents.findIndex((e) => e.id === targetEvent.id);
27817
27931
  const start = Math.max(0, eventIndex - windowSize);
27818
27932
  const end = Math.min(sessionEvents.length, eventIndex + windowSize + 1);
@@ -27823,7 +27937,8 @@ async function handleMemTimeline(memoryService, args) {
27823
27937
  const isTarget = e.id === targetEvent.id;
27824
27938
  const marker = isTarget ? "**\u2192**" : " ";
27825
27939
  const time = e.timestamp.toLocaleTimeString();
27826
- const preview = e.content.slice(0, 60) + (e.content.length > 60 ? "..." : "");
27940
+ const lowSignal = isLowSignalContextContent(e.content || "");
27941
+ const preview = lowSignal ? "[low-signal context artifact suppressed; use mem-details for raw source]" : sanitizeOperationString(e.content, 60);
27827
27942
  const citationId = generateCitationId(e.id);
27828
27943
  lines.push(`${marker} ${time} [${citationId}] ${e.eventType}: ${preview}`);
27829
27944
  }
@@ -27955,6 +28070,7 @@ async function handleMemContextPack(memoryService, args) {
27955
28070
  if (compression !== "off" || maxContextChars !== void 0) {
27956
28071
  lines.push("### Compression Notice", "");
27957
28072
  lines.push("- Context-pack compression is applied only to LLM-facing previews; original events remain available through source refs.");
28073
+ lines.push("- Privacy filters run before compression and final preview sanitization runs after compression.");
27958
28074
  if (maxContextChars !== void 0) {
27959
28075
  lines.push(`- Hard output budget: ${maxContextChars} characters; use follow-up lookups to expand trimmed sources.`);
27960
28076
  }
@@ -28096,7 +28212,8 @@ async function handleMemProjectTimeline(memoryService, args) {
28096
28212
  const limit = numberArg(args.limit, 50, 1, 500);
28097
28213
  const sessionLimit = numberArg(args.sessionLimit, 10, 1, 50);
28098
28214
  const recentEvents = await memoryService.getRecentEvents(limit);
28099
- const sessions = summarizeSessions(recentEvents, sessionLimit);
28215
+ const timelineEvents = recentEvents.filter((event) => !isLowSignalContextContent(event.content || ""));
28216
+ const sessions = summarizeSessions(timelineEvents, sessionLimit);
28100
28217
  const lines = [
28101
28218
  "## Project Memory Timeline",
28102
28219
  "",
@@ -28376,10 +28493,11 @@ function sourceTypeForEvent(event) {
28376
28493
  }
28377
28494
  function formatRelevantMemoryLine(event, score, index, formatOptions = DEFAULT_CONTEXT_PACK_FORMAT_OPTIONS) {
28378
28495
  const citationId = generateCitationId(event.id);
28496
+ const previewBudget = formatOptions.compression === "off" ? 260 : 180;
28379
28497
  return [
28380
28498
  `${index}. [mem:${citationId}] score=${score.toFixed(2)} type=${event.eventType} date=${event.timestamp.toISOString()} session=${event.sessionId}`,
28381
28499
  ` source=${sourceForEvent(event)}`,
28382
- ` ${contextPackPreview(event, 260, formatOptions)}`,
28500
+ ` ${contextPackPreview(event, previewBudget, formatOptions)}`,
28383
28501
  ""
28384
28502
  ].join("\n");
28385
28503
  }
@@ -28456,6 +28574,7 @@ function contextPackPreview(event, maxLength, options = DEFAULT_CONTEXT_PACK_FOR
28456
28574
  const compressed = MCP_CONTEXT_COMPRESSOR.compress(privacyFiltered, {
28457
28575
  mode: options.compression,
28458
28576
  source: sourceForEvent(event),
28577
+ sourceRef: `mem:${generateCitationId(event.id)}`,
28459
28578
  metadata: {
28460
28579
  ...safeCompressionMetadata(event.metadata),
28461
28580
  eventType: event.eventType
@@ -28483,8 +28602,9 @@ function appendCompressionTelemetry(lines, metadata, maxChars) {
28483
28602
  const summary = summarizeCompressionTelemetry(metadata);
28484
28603
  const sources = summary.bySource.map((item) => `${item.source}:${item.items}`).join(",");
28485
28604
  const strategies = summary.byStrategy.map((item) => `${item.strategy}:${item.items}`).join(",");
28605
+ const sourceRefsAvailable = metadata.filter((item) => item.sourceRef !== void 0).length;
28486
28606
  lines.push(
28487
- `- Compression telemetry: items=${summary.totalItems} originalChars=${summary.totalOriginalChars} compressedChars=${summary.totalCompressedChars} savedChars=${summary.totalSavedChars} sources=${sources || "n/a"} strategies=${strategies || "n/a"}`,
28607
+ `- Compression telemetry: items=${summary.totalItems} originalChars=${summary.totalOriginalChars} compressedChars=${summary.totalCompressedChars} savedChars=${summary.totalSavedChars} omittedLines=${summary.totalOmittedLines} sourceRefsPreserved=${summary.sourceRefsPreserved}/${sourceRefsAvailable} sources=${sources || "n/a"} strategies=${strategies || "n/a"}`,
28488
28608
  ""
28489
28609
  );
28490
28610
  }
@@ -28494,18 +28614,24 @@ function contextCompressionModeArg(value, hasBudget) {
28494
28614
  if (normalized === "off" || normalized === "safe" || normalized === "aggressive") return normalized;
28495
28615
  throw new Error("Invalid compression: expected off, safe, or aggressive");
28496
28616
  }
28617
+ function validateMemContextPackBudgetArgs(args) {
28618
+ const maxContextChars = contextPackMaxCharsArg(args);
28619
+ contextCompressionModeArg(args.compression, maxContextChars !== void 0);
28620
+ }
28497
28621
  function contextPackMaxCharsArg(args) {
28498
- const maxChars = optionalNumberArg(args.maxChars, CONTEXT_PACK_MIN_HARD_BUDGET_CHARS, CONTEXT_PACK_MAX_CHARS_CAP);
28499
- const maxTokens = optionalNumberArg(args.maxTokens, 250, Math.floor(CONTEXT_PACK_MAX_CHARS_CAP / 4));
28622
+ const maxChars = optionalNumberArg(args.maxChars, "maxChars", CONTEXT_PACK_MIN_HARD_BUDGET_CHARS, CONTEXT_PACK_MAX_CHARS_CAP);
28623
+ const maxTokens = optionalNumberArg(args.maxTokens, "maxTokens", 250, Math.floor(CONTEXT_PACK_MAX_CHARS_CAP / 4));
28500
28624
  const tokenChars = maxTokens === void 0 ? void 0 : maxTokens * 4;
28501
28625
  if (maxChars !== void 0 && tokenChars !== void 0) return Math.min(maxChars, tokenChars);
28502
28626
  return maxChars ?? tokenChars;
28503
28627
  }
28504
- function optionalNumberArg(value, min, max) {
28628
+ function optionalNumberArg(value, name, min, max) {
28505
28629
  if (value === void 0 || value === null || value === "") return void 0;
28506
28630
  const parsed = typeof value === "number" ? value : Number(value);
28507
- if (!Number.isFinite(parsed)) return void 0;
28508
- return Math.min(max, Math.max(min, Math.floor(parsed)));
28631
+ if (!Number.isFinite(parsed) || parsed < min || parsed > max) {
28632
+ throw new Error(`Invalid ${name}: expected number between ${min} and ${max}`);
28633
+ }
28634
+ return Math.floor(parsed);
28509
28635
  }
28510
28636
  function applyContextPackBudget(text, maxChars) {
28511
28637
  if (maxChars === void 0 || text.length <= maxChars) return text;