From 3d4426922878e56c235d23f0025ed36b2e12cbce Mon Sep 17 00:00:00 2001 From: Ralph Chang Date: Sun, 26 Apr 2026 16:54:24 +0800 Subject: [PATCH] fix: resolve remaining architect issues - split feedback keys, remove generic config key, supersession mode - Split feedbackTopicKey: server-error now separate from port-occupied-environment - Remove generic plugin.*config entity key (too broad), fall back to canonical dedup - Feedback topic conflicts now use supersession mode (newer beats longer) - Add 3 regression tests: English port/split, unrelated configs, feedback supersession 70/70 tests pass. --- src/workspace-memory.ts | 23 ++++++++++++++--------- tests/workspace-memory.test.ts | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/src/workspace-memory.ts b/src/workspace-memory.ts index 9d7c5f3..624bd18 100644 --- a/src/workspace-memory.ts +++ b/src/workspace-memory.ts @@ -84,10 +84,7 @@ function extractEntityKey(text: string): string | null { if (/opencode.*agenthub/i.test(normalized)) { return "opencode-agenthub plugin system"; } - // plugin config variations - if (/plugin.*config|config.*plugin/i.test(normalized)) { - return "plugin config"; - } + // For generic config references, fall back to canonical text dedup — no entity key return null; } @@ -120,9 +117,13 @@ function feedbackTopicKey(text: string): string | null { if (/purple.*italic/i.test(normalized)) { return "purple-italic-rendering"; } - // Browser login/server errors - if (/login.*500|500.*internal|server.*error|port.*occup/i.test(normalized)) { - return "server-error-port-issue"; + // Browser login/server errors (500 internal_error) + if (/login.*500|500.*internal|internal_error|server.*error/i.test(normalized)) { + return "server-error"; + } + // Port occupied / environment issues + if (/port.*occup|9473|端口|舊進程|旧进程/i.test(normalized)) { + return "port-occupied-environment"; } // Theme preferences if (/theme|dark.*light|prefer.*theme/i.test(normalized)) { @@ -198,8 +199,12 @@ export function enforceLongTermLimits(entries: LongTermMemoryEntry[]): LongTermM const existing = entityDeduped.get(key); if (!existing) { entityDeduped.set(key, entry); - } else if (chooseBetterMemory(entry, existing) === entry) { - entityDeduped.set(key, entry); + } else { + // Feedback topic conflicts use supersession mode (newer beats longer) + const mode = entry.type === "feedback" && entityKey ? "supersession" as const : "entity" as const; + if (chooseBetterMemory(entry, existing, mode) === entry) { + entityDeduped.set(key, entry); + } } } diff --git a/tests/workspace-memory.test.ts b/tests/workspace-memory.test.ts index 5759f54..371af6c 100644 --- a/tests/workspace-memory.test.ts +++ b/tests/workspace-memory.test.ts @@ -380,4 +380,37 @@ test("enforceLongTermLimits supersession: newer shorter decision beats older lon const decisions = kept.filter(e => e.type === "decision" && /parser.*format/i.test(e.text)); assert.equal(decisions.length, 1, "Newer shorter decision should supersede older longer one"); assert.ok(decisions[0].text.includes("4 formats"), "Kept entry should be the newer 4-formats"); +}); + +test("enforceLongTermLimits feedback: English port issue does NOT collapse with server error", () => { + const entries = [ + agedEntry("e1", "Browser login 500 internal_error, code correct but cause unknown", "feedback", { daysAgo: 0 }), + agedEntry("e2", "Port 9473 occupied by old process, may need to kill and restart", "feedback", { daysAgo: 0 }), + ]; + + const kept = enforceLongTermLimits(entries); + const feedbackEntries = kept.filter(e => e.type === "feedback"); + assert.equal(feedbackEntries.length, 2, "English port issue and server error should remain separate"); +}); + +test("enforceLongTermLimits config: unrelated generic plugin configs do NOT collapse", () => { + const entries = [ + agedEntry("c1", "Vite plugin config location: vite.config.ts at project root", "reference", { daysAgo: 0 }), + agedEntry("c2", "ESLint plugin config location: eslint.config.js at project root", "reference", { daysAgo: 0 }), + ]; + + const kept = enforceLongTermLimits(entries); + const refEntries = kept.filter(e => e.type === "reference"); + assert.equal(refEntries.length, 2, "Unrelated plugin configs without entity key should remain separate"); +}); + +test("enforceLongTermLimits feedback: supersession prefers newer shorter over older longer", () => { + // Same purple/italic issue, newer shorter fix supersedes older verbose fix + const older = agedEntry("f1", "Purple/italic text issue resolved by using plain text labels instead of any special markup syntax in the prompt", "feedback", { daysAgo: 5 }); + const newer = agedEntry("f2", "Purple/italic text fixed via template replacement", "feedback", { daysAgo: 0 }); + + const kept = enforceLongTermLimits([older, newer]); + const feedbackEntries = kept.filter(e => e.type === "feedback"); + assert.equal(feedbackEntries.length, 1, "Newer shorter feedback should supersede older longer"); + assert.ok(feedbackEntries[0].text.includes("template replacement"), "Kept entry should be the newer fix"); }); \ No newline at end of file