export interface DiffSegment { kind: 'same' | 'removed' | 'added' text: string } const MAX_TOKENS = 1500 /** * Word-level diff (LCS) between two texts — used by the review inbox to show what the reviewer * changed vs the agent's proposal. Inputs are capped so the O(n·m) table stays cheap. */ export function diffWords(before: string, after: string): DiffSegment[] { const a = tokenize(before).slice(0, MAX_TOKENS) const b = tokenize(after).slice(0, MAX_TOKENS) // LCS length table. const dp: number[][] = Array.from({ length: a.length + 1 }, () => new Array(b.length + 1).fill(0)) for (let i = a.length - 1; i >= 0; i--) { for (let j = b.length - 1; j >= 0; j--) { dp[i][j] = a[i] === b[j] ? dp[i + 1][j + 1] + 1 : Math.max(dp[i + 1][j], dp[i][j + 1]) } } // Walk the table, merging consecutive segments of the same kind. const segments: DiffSegment[] = [] const push = (kind: DiffSegment['kind'], text: string) => { const last = segments[segments.length - 1] if (last && last.kind === kind) { last.text += text } else { segments.push({ kind, text }) } } let i = 0 let j = 0 while (i < a.length && j < b.length) { if (a[i] === b[j]) { push('same', a[i]) i++ j++ } else if (dp[i + 1][j] >= dp[i][j + 1]) { push('removed', a[i]) i++ } else { push('added', b[j]) j++ } } while (i < a.length) push('removed', a[i++]) while (j < b.length) push('added', b[j++]) return segments } /** Splits text into words + whitespace separators (kept, so the diff re-renders faithfully). */ function tokenize(text: string): string[] { return text.split(/(\s+)/).filter((t) => t.length > 0) }