export interface SelectedCompactMemory { content: string; keywords: string[]; memoryKind: string | null; } const MAX_SELECTED_MEMORIES = 3; const MEMORY_PATTERNS: Array<{ kind: string | null; regex: RegExp; keyword: string; }> = [ { kind: 'room_norm', regex: /(규칙|원칙|금지|반드시|하지 않|세션 시작 시에만|새 세션 시작 시에만)/i, keyword: 'rule', }, { kind: 'preference', regex: /(선호|원함|원한다|원하는|원했다)/i, keyword: 'preference', }, { kind: 'decision', regex: /(합의|결정|방향|기준|우선|책임지는 방향)/i, keyword: 'decision', }, { kind: 'project_fact', regex: /(owner|reviewer|trigger|모드|메모리|기억|세션 리셋|recall|persist|compact)/i, keyword: 'memory', }, ]; function normalizeSentence(raw: string): string | null { const normalized = raw .trim() .replace(/^[-*]\s*/, '') .replace(/\s+/g, ' '); return normalized ? normalized : null; } function splitSummaryIntoSentences(summary: string): string[] { return summary .split(/\n+|(?<=[.!?。])\s+/) .map(normalizeSentence) .filter((value): value is string => Boolean(value)); } function classifyMemorySentence(content: string): SelectedCompactMemory | null { if (!content) return null; const matchedPatterns = MEMORY_PATTERNS.filter(({ regex }) => regex.test(content), ); if (matchedPatterns.length === 0) return null; const primary = matchedPatterns[0]; const keywords = [ ...new Set( matchedPatterns.map((pattern) => pattern.keyword).filter(Boolean), ), ]; return { content, keywords, memoryKind: primary?.kind ?? null, }; } export function selectCompactMemoriesFromSummary( summary: string, roomKey: string, ): SelectedCompactMemory[] { if (!summary.trim()) return []; const selected = splitSummaryIntoSentences(summary) .map((sentence) => classifyMemorySentence(sentence)) .filter((value): value is SelectedCompactMemory => Boolean(value)) .slice(0, MAX_SELECTED_MEMORIES) .map((entry) => ({ ...entry, keywords: [...new Set([roomKey, ...entry.keywords])], })); return selected; }