{"version":3,"file":"mmr.cjs","sources":["../../../src/memory/mmr.ts"],"sourcesContent":["/**\n * Maximal Marginal Relevance (MMR) re-ranking — Phase 2.\n *\n * Ported from upstream `extensions/memory-core/src/memory/mmr.ts` with\n * minor adaptation for our `MemoryEntry` shape (content vs snippet, id vs\n * path+startLine). Behavior is identical: normalize scores, iteratively\n * pick the item that maximizes `λ * relevance - (1-λ) * max_similarity`\n * using Jaccard on tokenized content.\n *\n * @see Carbonell & Goldstein, \"The Use of MMR, Diversity-Based Reranking\" (1998)\n */\n\nexport interface MMRConfig {\n  /** Opt-in. Upstream default is false. */\n  enabled: boolean;\n  /** 0 = max diversity, 1 = max relevance. Upstream default 0.7. */\n  lambda: number;\n}\n\nexport const DEFAULT_MMR_CONFIG: MMRConfig = {\n  enabled: false,\n  lambda: 0.7,\n};\n\n/**\n * CJK Unified Ideographs + Extension A, Hiragana/Katakana, Hangul.\n * These lack whitespace boundaries so we must tokenize them differently.\n */\nconst CJK_RE =\n  /[\\u3040-\\u309f\\u30a0-\\u30ff\\u3400-\\u4dbf\\u4e00-\\u9fff\\uac00-\\ud7af\\u1100-\\u11ff]/;\n\n/**\n * Tokenize content into a set for Jaccard similarity.\n *\n * ASCII: alphanumeric + underscore runs, lowercased.\n * CJK: each char becomes a unigram; consecutive pairs become a bigram.\n * Non-adjacent CJK chars (e.g. `我a好`) do NOT form a bigram.\n */\nexport function tokenize(text: string): Set<string> {\n  const lower = (text ?? '').toLowerCase();\n  const ascii = lower.match(/[a-z0-9_]+/g) ?? [];\n\n  const chars = Array.from(lower);\n  const cjkData: Array<{ char: string; index: number }> = [];\n  for (let i = 0; i < chars.length; i++) {\n    if (CJK_RE.test(chars[i])) cjkData.push({ char: chars[i], index: i });\n  }\n\n  const bigrams: string[] = [];\n  for (let i = 0; i < cjkData.length - 1; i++) {\n    if (cjkData[i + 1].index === cjkData[i].index + 1) {\n      bigrams.push(cjkData[i].char + cjkData[i + 1].char);\n    }\n  }\n\n  return new Set([...ascii, ...bigrams, ...cjkData.map((d) => d.char)]);\n}\n\nexport function jaccardSimilarity(a: Set<string>, b: Set<string>): number {\n  if (a.size === 0 && b.size === 0) return 1;\n  if (a.size === 0 || b.size === 0) return 0;\n\n  const [smaller, larger] = a.size <= b.size ? [a, b] : [b, a];\n  let intersection = 0;\n  for (const t of smaller) if (larger.has(t)) intersection++;\n  const union = a.size + b.size - intersection;\n  return union === 0 ? 0 : intersection / union;\n}\n\nexport function textSimilarity(a: string, b: string): number {\n  return jaccardSimilarity(tokenize(a), tokenize(b));\n}\n\nexport function computeMMRScore(\n  relevance: number,\n  maxSimilarity: number,\n  lambda: number\n): number {\n  return lambda * relevance - (1 - lambda) * maxSimilarity;\n}\n\nexport interface MMRItem {\n  id: string;\n  score: number;\n  content: string;\n}\n\n/**\n * Re-rank items using MMR. Returns a new array in MMR order.\n */\nexport function mmrRerank<T extends MMRItem>(\n  items: T[],\n  config: Partial<MMRConfig> = {}\n): T[] {\n  const enabled = config.enabled ?? DEFAULT_MMR_CONFIG.enabled;\n  const rawLambda = config.lambda ?? DEFAULT_MMR_CONFIG.lambda;\n\n  if (!enabled || items.length <= 1) return [...items];\n\n  const lambda = Math.max(0, Math.min(1, rawLambda));\n  if (lambda === 1) return [...items].sort((a, b) => b.score - a.score);\n\n  const tokenCache = new Map<string, Set<string>>();\n  for (const item of items) tokenCache.set(item.id, tokenize(item.content));\n\n  const scores = items.map((i) => i.score);\n  const maxScore = Math.max(...scores);\n  const minScore = Math.min(...scores);\n  const range = maxScore - minScore;\n  const normalize = (s: number): number =>\n    range === 0 ? 1 : (s - minScore) / range;\n\n  const selected: T[] = [];\n  const remaining = new Set(items);\n\n  while (remaining.size > 0) {\n    let best: T | null = null;\n    let bestMMR = -Infinity;\n    for (const cand of remaining) {\n      const rel = normalize(cand.score);\n      const candTokens = tokenCache.get(cand.id)!;\n      let maxSim = 0;\n      for (const sel of selected) {\n        const sim = jaccardSimilarity(candTokens, tokenCache.get(sel.id)!);\n        if (sim > maxSim) maxSim = sim;\n      }\n      const mmr = computeMMRScore(rel, maxSim, lambda);\n      if (\n        mmr > bestMMR ||\n        (mmr === bestMMR && cand.score > (best?.score ?? -Infinity))\n      ) {\n        bestMMR = mmr;\n        best = cand;\n      }\n    }\n    if (!best) break;\n    selected.push(best);\n    remaining.delete(best);\n  }\n\n  return selected;\n}\n\n/**\n * Adapter: apply MMR to an array of MemoryEntry-shaped hits.\n *\n * Uses (path|id|index) as the stable ID so two hits from the same file at\n * different content still get distinct MMR identities.\n */\nexport function applyMMRToMemoryHits<\n  T extends { id: string; path: string; content: string; score: number },\n>(results: T[], config: Partial<MMRConfig> = {}): T[] {\n  if (results.length <= 1) return results;\n  const byId = new Map<string, T>();\n  const items: MMRItem[] = results.map((r, i) => {\n    const id = `${r.path}#${r.id}#${i}`;\n    byId.set(id, r);\n    return { id, score: r.score, content: r.content };\n  });\n  return mmrRerank(items, config).map((m) => byId.get(m.id)!);\n}\n"],"names":[],"mappings":"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"}