mirror of
https://github.com/sdwolf4103/opencode-working-memory.git
synced 2026-06-02 06:19:36 +02:00
refactor: make memory dedupe repo-agnostic
This commit is contained in:
+67
-68
@@ -313,72 +313,80 @@ export function workspaceMemoryExactKey(entry: Pick<LongTermMemoryEntry, "type"
|
|||||||
return `${entry.type}:${canonicalMemoryText(entry.text)}`;
|
return `${entry.type}:${canonicalMemoryText(entry.text)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Extract entity/destination keys for project and reference dedup */
|
function normalizeUrlIdentity(raw: string): string | null {
|
||||||
function extractEntityKey(text: string): string | null {
|
const cleaned = raw.replace(/[),.;:!?]+$/g, "");
|
||||||
const normalized = canonicalMemoryText(text);
|
try {
|
||||||
// Check known key phrases (bilingual-friendly)
|
const url = new URL(cleaned);
|
||||||
// opencode + agenthub plugin system
|
if (url.protocol !== "http:" && url.protocol !== "https:") return null;
|
||||||
if (/opencode.*agenthub/i.test(normalized)) {
|
url.protocol = url.protocol.toLowerCase();
|
||||||
return "opencode-agenthub plugin system";
|
url.hostname = url.hostname.toLowerCase();
|
||||||
|
url.hash = "";
|
||||||
|
if (url.pathname.length > 1) {
|
||||||
|
url.pathname = url.pathname.replace(/\/+$/g, "");
|
||||||
|
}
|
||||||
|
return `url:${url.toString()}`;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
// For generic config references, fall back to canonical text dedup — no entity key
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Extract decision topic key for supersession detection */
|
function normalizePathIdentity(raw: string): string | null {
|
||||||
function decisionTopicKey(text: string): string | null {
|
const unwrapped = raw
|
||||||
const normalized = text.toLowerCase();
|
.trim()
|
||||||
// Parser format versions
|
.replace(/^[`"']+|[`"']+$/g, "")
|
||||||
if (/parser.*formats?|supports?\s*\d+\s*format/i.test(normalized)) {
|
.replace(/[),.;:!?]+$/g, "")
|
||||||
return "parser-supported-formats";
|
.replace(/\\+/g, "/");
|
||||||
}
|
|
||||||
// Compaction template replacement
|
if (!unwrapped) return null;
|
||||||
if (/compaction.*template|output\.prompt|template.*replace/i.test(normalized)) {
|
const collapsed = unwrapped.startsWith("/")
|
||||||
return "compaction-template-replacement";
|
? `/${unwrapped.slice(1).replace(/\/+$/g, "/").replace(/\/+/g, "/")}`
|
||||||
}
|
: unwrapped.replace(/\/+/g, "/");
|
||||||
// Plugin loading
|
const withoutTrailingSlash = collapsed.length > 1 ? collapsed.replace(/\/+$/g, "") : collapsed;
|
||||||
if (/plugin.*load|npm.*cache|plugin.*config/i.test(normalized)) {
|
return `path:${withoutTrailingSlash}`;
|
||||||
return "plugin-loading-config";
|
|
||||||
}
|
|
||||||
// Output format changes (purple/italic, YAML frontmatter, etc)
|
|
||||||
if (/purple.*italic|markup|markdown.*render|frontmatter/i.test(normalized)) {
|
|
||||||
return "output-format-rendering";
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Extract feedback topic key for supersession detection */
|
function isConcretePathIdentity(pathIdentity: string): boolean {
|
||||||
function feedbackTopicKey(text: string): string | null {
|
const path = pathIdentity.slice("path:".length);
|
||||||
const normalized = text.toLowerCase();
|
if (!path || path === "." || path === "..") return false;
|
||||||
// Purple/italic rendering issue
|
|
||||||
if (/purple.*italic/i.test(normalized)) {
|
if (path.startsWith("/")) return true;
|
||||||
return "purple-italic-rendering";
|
if (/^\.\.?\//.test(path)) return true;
|
||||||
|
if (/^\.[A-Za-z0-9_.-]+\//.test(path)) return true;
|
||||||
|
if (/^[A-Za-z0-9_.-]+\//.test(path)) return true;
|
||||||
|
return /\.(?:json|jsonc|ts|tsx|js|jsx|mjs|cjs|md|yaml|yml|toml|lock|config)$/i.test(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeConcretePathIdentity(raw: string): string | null {
|
||||||
|
const pathIdentity = normalizePathIdentity(raw);
|
||||||
|
if (!pathIdentity) return null;
|
||||||
|
return isConcretePathIdentity(pathIdentity) ? pathIdentity : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractConcreteIdentityKey(text: string): string | null {
|
||||||
|
const urlMatch = text.match(/https?:\/\/[^\s`"'<>]+/i);
|
||||||
|
if (urlMatch) {
|
||||||
|
const urlIdentity = normalizeUrlIdentity(urlMatch[0]);
|
||||||
|
if (urlIdentity) return urlIdentity;
|
||||||
}
|
}
|
||||||
// Browser login/server errors (500 internal_error)
|
|
||||||
if (/login.*500|500.*internal|internal_error|server.*error/i.test(normalized)) {
|
const wrappedPathPattern = /[`"']([^`"']+)[`"']/g;
|
||||||
return "server-error";
|
for (const match of text.matchAll(wrappedPathPattern)) {
|
||||||
|
const pathIdentity = normalizeConcretePathIdentity(match[1]);
|
||||||
|
if (pathIdentity) return pathIdentity;
|
||||||
}
|
}
|
||||||
// Port occupied / environment issues
|
|
||||||
if (/port.*occup|9473|端口|舊進程|旧进程/i.test(normalized)) {
|
const pathMatch = text.match(/(?:\/[^ | ||||||