From 1b9457500d47ebfad68a4d4e67cfee774721e113 Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Tue, 14 Apr 2026 19:38:20 -0400 Subject: [PATCH] memory: harden dreaming contamination lead parsing --- .../src/short-term-promotion.test.ts | 8 ++++ .../memory-core/src/short-term-promotion.ts | 45 +++++++++++++++---- 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/extensions/memory-core/src/short-term-promotion.test.ts b/extensions/memory-core/src/short-term-promotion.test.ts index 1b9dfb4fbef..e3bb05f2ad0 100644 --- a/extensions/memory-core/src/short-term-promotion.test.ts +++ b/extensions/memory-core/src/short-term-promotion.test.ts @@ -1055,6 +1055,14 @@ describe("short-term promotion", () => { ).toBe(true); }); + it("treats bracket-prefixed dreaming snippets as contaminated", () => { + expect( + __testing.isContaminatedDreamingSnippet( + "([ Candidate: Default to action. confidence: 0.76 evidence: memory/.dreams/session-corpus/2026-04-08.txt:1-1 recalls: 3 status: staged", + ), + ).toBe(true); + }); + it("skips direct candidates that exceed maxAgeDays during apply", async () => { await withTempWorkspace(async (workspaceDir) => { const applied = await applyShortTermPromotions({ diff --git a/extensions/memory-core/src/short-term-promotion.ts b/extensions/memory-core/src/short-term-promotion.ts index 8f42deae663..8562b1f4aa2 100644 --- a/extensions/memory-core/src/short-term-promotion.ts +++ b/extensions/memory-core/src/short-term-promotion.ts @@ -39,10 +39,6 @@ const PHASE_SIGNAL_LIGHT_BOOST_MAX = 0.06; const PHASE_SIGNAL_REM_BOOST_MAX = 0.09; const PHASE_SIGNAL_HALF_LIFE_DAYS = 14; const DREAMING_NARRATIVE_RUN_PREFIX = "dreaming-narrative-"; -const DREAMING_CANDIDATE_LEAD_RE = - /^(?:[-*+>]\s+|@@\s*-\d+(?:,\d+)?\s+[-*+]\s+|[([]\s*)*Candidate:/i; -const DREAMING_REFLECTION_LEAD_RE = - /^(?:[-*+>]\s+|@@\s*-\d+(?:,\d+)?\s+[-*+]\s+|[([]\s*)*Reflections?:/i; const inProcessShortTermLocks = new Map>(); const ensuredShortTermDirs = new Map>(); @@ -241,6 +237,40 @@ function normalizeSnippet(raw: string): string { return trimmed.replace(/\s+/g, " "); } +function consumeDreamingLeadPrefix(snippet: string): string { + let index = 0; + while (index < snippet.length) { + const remaining = snippet.slice(index); + const diffMatch = /^@@\s*-\d+(?:,\d+)?\s+[-*+]\s+/i.exec(remaining); + if (diffMatch) { + index += diffMatch[0].length; + continue; + } + const char = snippet[index]; + if (char === "[" || char === "(") { + index += 1; + while (snippet[index] === " ") { + index += 1; + } + continue; + } + if ( + (char === "-" || char === "*" || char === "+" || char === ">") && + snippet[index + 1] === " " + ) { + index += 2; + continue; + } + break; + } + return snippet.slice(index); +} + +function hasDreamingNarrativeLead(snippet: string): boolean { + const withoutPrefix = consumeDreamingLeadPrefix(snippet); + return /^Candidate:/i.test(withoutPrefix) || /^Reflections?:/i.test(withoutPrefix); +} + function isContaminatedDreamingSnippet(raw: string): boolean { const snippet = normalizeSnippet(raw); if (!snippet) { @@ -254,21 +284,20 @@ function isContaminatedDreamingSnippet(raw: string): boolean { return true; } - const hasCandidateLead = DREAMING_CANDIDATE_LEAD_RE.test(snippet); - const hasReflectionLead = DREAMING_REFLECTION_LEAD_RE.test(snippet); + const hasNarrativeLead = hasDreamingNarrativeLead(snippet); const hasConfidence = /\bconfidence:\s*\d/i.test(snippet); const hasEvidence = /\bevidence:\s*(?:memory\/\.dreams\/session-corpus\/|memory\/)/i.test( snippet, ); const hasStatus = /\bstatus:\s*staged\b/i.test(snippet); const hasRecalls = /\brecalls:\s*\d+\b/i.test(snippet); - if (hasEvidence && (hasCandidateLead || hasReflectionLead)) { + if (hasEvidence && hasNarrativeLead) { return true; } const structuredMarkers = [hasConfidence, hasEvidence, hasStatus, hasRecalls].filter( Boolean, ).length; - return (hasCandidateLead || hasReflectionLead) && structuredMarkers >= 2; + return hasNarrativeLead && structuredMarkers >= 2; } function normalizeMemoryPath(rawPath: string): string {