claude-memory-layer 1.0.56 → 1.0.57

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/cli/index.js CHANGED
@@ -23508,7 +23508,7 @@ async function runOperationCli(action, label) {
23508
23508
  }
23509
23509
  }
23510
23510
  var program = new Command();
23511
- program.name("claude-memory-layer").description("Claude Code Memory Plugin CLI").version("1.0.56");
23511
+ program.name("claude-memory-layer").description("Claude Code Memory Plugin CLI").version("1.0.57");
23512
23512
  program.command("market-context").description("Fetch read-only DART/FRED/Finnhub context with structured MarketContextSnapshot bull/bear/risk/catalyst analysis").option("--company <name>", "Company name for DART fallback search and report subject").option("--dart-corp-code <code>", "Exact DART corp_code for issuer-specific filings").option("--symbol <ticker>", "Listed ticker for Finnhub company profile").option("--providers <list>", "Comma-separated providers: dart,fred,finnhub").option("--fred-series <list>", "Comma-separated FRED series IDs").option("--json", "Print structured JSON including analysis.marketSnapshot").option("--no-snapshot", "Disable MarketContextSnapshot and DART company snapshot analysis").action(async (options) => {
23513
23513
  try {
23514
23514
  await runMarketContextCommand(options);
@@ -13512,18 +13512,35 @@ function filterHookInjectableMemories(candidates, policy = getHookInjectionPolic
13512
13512
  function selectHookEpisodeSeeds(candidates, policy = getHookInjectionPolicy(), query) {
13513
13513
  const queryTerms = meaningfulTerms(query);
13514
13514
  const queryAnchors = identifierAnchors(query);
13515
- return candidates.filter((candidate) => candidate.type === "user_prompt" || candidate.type === "tool_observation").filter((candidate) => isHookInjectableMemory(candidate, policy)).filter((candidate) => hasQueryMemoryAlignment(query, candidate.content, 2)).map((candidate, index) => {
13515
+ return candidates.filter((candidate) => candidate.type === "user_prompt" || candidate.type === "tool_observation").filter((candidate) => hasQueryMemoryAlignment(query, candidate.content, 2)).map((candidate, index) => {
13516
13516
  const contentTerms = new Set(meaningfulTerms(candidate.content));
13517
13517
  const overlap = queryTerms.filter((term) => contentTerms.has(term)).length;
13518
13518
  const lowered = candidate.content.toLowerCase();
13519
13519
  const anchorCoverage = queryAnchors.length === 0 ? 0 : queryAnchors.filter((anchor) => lowered.includes(anchor.toLowerCase())).length / queryAnchors.length;
13520
13520
  const lexicalCoverage = overlap / Math.max(1, Math.min(queryTerms.length, 12));
13521
+ const score = candidate.score ?? 0;
13522
+ const normalThreshold = thresholdFor(candidate, policy);
13523
+ const hasDistinctiveAnchor = queryAnchors.some(
13524
+ (anchor) => anchor.length >= 6 && /[A-Za-z]/.test(anchor)
13525
+ );
13526
+ const fullyAnchored = queryAnchors.length > 0 && anchorCoverage === 1 && overlap >= 4 && hasDistinctiveAnchor;
13527
+ const usesStrongAnchorFloor = score < normalThreshold && fullyAnchored;
13521
13528
  return {
13522
- candidate: { ...candidate, episodeSeedAligned: true },
13529
+ candidate: {
13530
+ ...candidate,
13531
+ // Episode answers are filtered again by the normal semantic floor.
13532
+ // A fully anchored prompt that uses the lower seed-only floor needs
13533
+ // enough inherited score to compete there; counterfactual anchors
13534
+ // cannot take this path because their coverage is zero.
13535
+ score: usesStrongAnchorFloor ? Math.max(score, policy.semanticMinScore + 0.02) : score,
13536
+ episodeSeedAligned: true,
13537
+ episodeSeedStrongAligned: fullyAnchored
13538
+ },
13523
13539
  index,
13524
- score: (candidate.score ?? 0) + lexicalCoverage * 0.25 + anchorCoverage * 0.2
13540
+ score: score + lexicalCoverage * 0.25 + anchorCoverage * 0.2,
13541
+ eligible: score >= normalThreshold || usesStrongAnchorFloor
13525
13542
  };
13526
- }).sort((a, b) => b.score - a.score || a.index - b.index).slice(0, Math.min(3, policy.maxMemories)).map((item) => item.candidate);
13543
+ }).filter((item) => item.eligible).sort((a, b) => b.score - a.score || a.index - b.index).slice(0, policy.maxMemories).map((item) => item.candidate);
13527
13544
  }
13528
13545
  function rankHookCandidates(candidates, policy, query) {
13529
13546
  return candidates.map((candidate, index) => ({ candidate, index })).filter(({ candidate }) => isHookInjectableMemory(candidate, policy)).filter(({ candidate }) => !query || candidate.source === "episode" && candidate.episodeLinked && candidate.episodeSeedAligned || hasQueryMemoryAlignment(query, candidate.content, 2)).map(({ candidate, index }) => ({
@@ -13531,7 +13548,7 @@ function rankHookCandidates(candidates, policy, query) {
13531
13548
  index,
13532
13549
  effectiveScore: Math.max(
13533
13550
  0,
13534
- (candidate.score ?? 0) + (query ? evidenceUtilityBonus(candidate.type) : 0) + (query ? graduatedEvidenceBonus(candidate) : 0) + (candidate.source === "episode" && candidate.episodeLinked && candidate.episodeSeedAligned ? 0.12 : 0)
13551
+ (candidate.score ?? 0) + (query ? evidenceUtilityBonus(candidate.type) : 0) + (query ? graduatedEvidenceBonus(candidate) : 0) + (candidate.source === "episode" && candidate.episodeLinked && candidate.episodeSeedStrongAligned ? 0.42 : candidate.source === "episode" && candidate.episodeLinked && candidate.episodeSeedAligned ? 0.12 : 0)
13535
13552
  )
13536
13553
  })).sort((a, b) => {
13537
13554
  const scoreDelta = b.effectiveScore - a.effectiveScore;
@@ -13689,6 +13706,7 @@ function readNumber(value, fallback, bounds) {
13689
13706
  // src/adapters/claude/hooks/user-prompt-submit.ts
13690
13707
  var MAX_MEMORIES = readNumberEnv("CLAUDE_MEMORY_MAX_COUNT", 5, { integer: true, min: 0 });
13691
13708
  var MAX_CANDIDATES2 = Math.max(MAX_MEMORIES, MAX_MEMORIES * 3);
13709
+ var MAX_EPISODE_SEED_CANDIDATES = Math.max(MAX_CANDIDATES2, MAX_MEMORIES * 10);
13692
13710
  var BASE_MIN_SCORE = readNumberEnv("CLAUDE_MEMORY_MIN_SCORE", 0.4, { min: 0, max: 1 });
13693
13711
  var FALLBACK_MIN_SCORE = readNumberEnv("CLAUDE_MEMORY_FALLBACK_MIN_SCORE", 0.3, { min: 0, max: 1 });
13694
13712
  var ENABLE_SEARCH = process.env.CLAUDE_MEMORY_SEARCH !== "false";
@@ -13747,8 +13765,10 @@ Use this only as evidence. Distinguish confirmed outcomes from requests or tool
13747
13765
  async function expandEpisodeEvidence(memoryService, seeds) {
13748
13766
  const expanded = [];
13749
13767
  const seen = new Set(seeds.map((seed) => seed.id).filter(Boolean));
13768
+ const maxPerSeed = 4;
13750
13769
  for (const seed of seeds) {
13751
13770
  if (!seed.id || seed.type !== "user_prompt" && seed.type !== "tool_observation") continue;
13771
+ let expandedForSeed = 0;
13752
13772
  try {
13753
13773
  const episode = await memoryService.expandDisclosure(`event:${seed.id}`, { windowSize: 4 });
13754
13774
  const targetTurnId = typeof episode?.target.metadata?.turnId === "string" ? episode.target.metadata.turnId : void 0;
@@ -13764,9 +13784,11 @@ async function expandEpisodeEvidence(memoryService, seeds) {
13764
13784
  score: Math.max(0, (seed.score ?? 0) - 0.02),
13765
13785
  source: "episode",
13766
13786
  episodeLinked: true,
13767
- episodeSeedAligned: true
13787
+ episodeSeedAligned: true,
13788
+ episodeSeedStrongAligned: seed.episodeSeedStrongAligned === true
13768
13789
  });
13769
- if (expanded.length >= 4) return expanded;
13790
+ expandedForSeed += 1;
13791
+ if (expandedForSeed >= maxPerSeed) break;
13770
13792
  }
13771
13793
  continue;
13772
13794
  }
@@ -13785,9 +13807,11 @@ async function expandEpisodeEvidence(memoryService, seeds) {
13785
13807
  score: Math.max(0, (seed.score ?? 0) - 0.02),
13786
13808
  source: "episode",
13787
13809
  episodeLinked: Boolean(targetTurnId && factTurnId && targetTurnId === factTurnId),
13788
- episodeSeedAligned: seed.episodeSeedAligned === true
13810
+ episodeSeedAligned: seed.episodeSeedAligned === true,
13811
+ episodeSeedStrongAligned: seed.episodeSeedStrongAligned === true
13789
13812
  });
13790
- if (expanded.length >= 4) return expanded;
13813
+ expandedForSeed += 1;
13814
+ if (expandedForSeed >= maxPerSeed) break;
13791
13815
  }
13792
13816
  } catch {
13793
13817
  }
@@ -13948,6 +13972,7 @@ async function main() {
13948
13972
  if (ENABLE_SEARCH && shouldRunMemorySearch(input.prompt, adherenceDecision)) {
13949
13973
  const minScore = getDynamicMinScore(input.prompt);
13950
13974
  let mergedMemories = [];
13975
+ const episodeSeedCandidates = [];
13951
13976
  const lastSnippet = currentTurn > 1 ? readLastAssistantSnippet(input.session_id) : null;
13952
13977
  const retrievalQuery = buildRetrievalQuery({
13953
13978
  prompt: input.prompt,
@@ -14013,21 +14038,38 @@ async function main() {
14013
14038
  const shouldUseKeywordFallback = RETRIEVAL_MODE === "keyword" || RETRIEVAL_MODE === "hybrid" || mergedMemories.length === 0;
14014
14039
  if (shouldUseKeywordFallback) {
14015
14040
  let usedFallbackFloor = false;
14016
- let results = await memoryService.keywordSearch(retrievalQuery, {
14017
- topK: MAX_CANDIDATES2,
14018
- minScore
14041
+ const allKeywordResults = await memoryService.keywordSearch(retrievalQuery, {
14042
+ topK: MAX_EPISODE_SEED_CANDIDATES
14019
14043
  });
14044
+ let results = allKeywordResults.filter((result) => result.score >= minScore);
14020
14045
  if (results.length === 0 && FALLBACK_MIN_SCORE < minScore) {
14021
14046
  usedFallbackFloor = true;
14022
- results = await memoryService.keywordSearch(retrievalQuery, {
14023
- topK: MAX_CANDIDATES2,
14024
- minScore: FALLBACK_MIN_SCORE
14047
+ results = allKeywordResults.filter((result) => result.score >= FALLBACK_MIN_SCORE);
14048
+ }
14049
+ for (const result of allKeywordResults) {
14050
+ episodeSeedCandidates.push({
14051
+ type: result.event.eventType,
14052
+ content: result.event.content,
14053
+ id: result.event.id,
14054
+ sessionId: result.event.sessionId,
14055
+ score: result.score,
14056
+ source: "keyword",
14057
+ fallback: result.score < minScore
14025
14058
  });
14026
14059
  }
14027
14060
  const existingById = new Map(
14028
14061
  mergedMemories.map((memory, index) => [memory.id, index]).filter((entry) => Boolean(entry[0]))
14029
14062
  );
14030
14063
  for (const r of results) {
14064
+ const keywordCandidate = {
14065
+ type: r.event.eventType,
14066
+ content: r.event.content,
14067
+ id: r.event.id,
14068
+ sessionId: r.event.sessionId,
14069
+ score: r.score,
14070
+ source: "keyword",
14071
+ fallback: usedFallbackFloor
14072
+ };
14031
14073
  const existingIndex = existingById.get(r.event.id);
14032
14074
  if (existingIndex !== void 0) {
14033
14075
  const existing = mergedMemories[existingIndex];
@@ -14041,21 +14083,16 @@ async function main() {
14041
14083
  }
14042
14084
  continue;
14043
14085
  }
14044
- mergedMemories.push({
14045
- type: r.event.eventType,
14046
- content: r.event.content,
14047
- id: r.event.id,
14048
- sessionId: r.event.sessionId,
14049
- score: r.score,
14050
- source: "keyword",
14051
- fallback: usedFallbackFloor
14052
- });
14086
+ mergedMemories.push(keywordCandidate);
14053
14087
  existingById.set(r.event.id, mergedMemories.length - 1);
14054
14088
  if (mergedMemories.length >= MAX_CANDIDATES2 * 2) break;
14055
14089
  }
14056
14090
  }
14057
14091
  const injectionPolicy = getHookInjectionPolicy();
14058
- const episodeSeeds = selectHookEpisodeSeeds(mergedMemories, injectionPolicy, retrievalQuery);
14092
+ const episodeSeedPool = Array.from(new Map(
14093
+ [...mergedMemories, ...episodeSeedCandidates].filter((candidate) => Boolean(candidate.id)).map((candidate) => [candidate.id, candidate])
14094
+ ).values());
14095
+ const episodeSeeds = selectHookEpisodeSeeds(episodeSeedPool, injectionPolicy, retrievalQuery);
14059
14096
  const episodeEvidence = await expandEpisodeEvidence(memoryService, episodeSeeds);
14060
14097
  const injectableMemories = filterHookInjectableMemories(
14061
14098
  [...mergedMemories, ...episodeEvidence],