remem-mcp 0.7.0 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +79 -218
- package/dist/{chunk-OYUNKUZK.js → chunk-4WCDAEPP.js} +788 -11
- package/dist/chunk-4WCDAEPP.js.map +1 -0
- package/dist/{chunk-TGLOMFIE.js → chunk-ITMR6THY.js} +47 -7
- package/dist/chunk-ITMR6THY.js.map +1 -0
- package/dist/{chunk-ZXI67YIV.js → chunk-YU7WBJU5.js} +15 -3
- package/dist/{chunk-ZXI67YIV.js.map → chunk-YU7WBJU5.js.map} +1 -1
- package/dist/{engine-3JUSB3RK.js → engine-S47HHGAO.js} +2 -2
- package/dist/index.js +347 -39
- package/dist/index.js.map +1 -1
- package/dist/{resolver-YIFWRNMS.js → resolver-UHU4MBQX.js} +50 -15
- package/dist/resolver-UHU4MBQX.js.map +1 -0
- package/dist/sdk.js +3 -3
- package/dist/{server-SUKPVQWL.js → server-5PWQTRGZ.js} +2 -2
- package/dist/storage/schema.sql +63 -2
- package/package.json +1 -1
- package/scripts/verify-codegraph-stats.mjs +122 -0
- package/skills/remem-mcp/SKILL.md +11 -3
- package/dist/chunk-OYUNKUZK.js.map +0 -1
- package/dist/chunk-TGLOMFIE.js.map +0 -1
- package/dist/resolver-YIFWRNMS.js.map +0 -1
- /package/dist/{engine-3JUSB3RK.js.map → engine-S47HHGAO.js.map} +0 -0
- /package/dist/{server-SUKPVQWL.js.map → server-5PWQTRGZ.js.map} +0 -0
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from "./chunk-ONQHLUGM.js";
|
|
5
5
|
|
|
6
6
|
// src/codegraph/resolver.ts
|
|
7
|
-
function resolveCall(calleeName, callerFilePath, registry, importMap) {
|
|
7
|
+
function resolveCall(calleeName, callerFilePath, registry, importMap, fuzzyEnabled = true) {
|
|
8
8
|
const parts = calleeName.split(".");
|
|
9
9
|
if (parts.length >= 2) {
|
|
10
10
|
const prefix = parts[0];
|
|
@@ -76,9 +76,11 @@ function resolveCall(calleeName, callerFilePath, registry, importMap) {
|
|
|
76
76
|
}
|
|
77
77
|
return { calleeId: best.id, confidence: 0.55, strategy: "suffix-match" };
|
|
78
78
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
if (fuzzyEnabled) {
|
|
80
|
+
const fuzzyMatch = fuzzyLookup(calleeName, registry);
|
|
81
|
+
if (fuzzyMatch) {
|
|
82
|
+
return { calleeId: fuzzyMatch.id, confidence: 0.35, strategy: "fuzzy" };
|
|
83
|
+
}
|
|
82
84
|
}
|
|
83
85
|
return { calleeId: null, confidence: 0, strategy: "unresolved" };
|
|
84
86
|
}
|
|
@@ -89,27 +91,60 @@ function shouldSkipResolution(calleeName) {
|
|
|
89
91
|
if (parts.length >= 2 && parts[0].length <= 1) return true;
|
|
90
92
|
return false;
|
|
91
93
|
}
|
|
92
|
-
function resolveAllCalls(db) {
|
|
94
|
+
function resolveAllCalls(db, callerIds) {
|
|
93
95
|
try {
|
|
94
96
|
db.exec("CREATE INDEX IF NOT EXISTS idx_calls_callee_name ON calls(callee_name)");
|
|
95
97
|
db.exec("CREATE INDEX IF NOT EXISTS idx_symbols_name ON symbols(name)");
|
|
96
98
|
db.exec("CREATE INDEX IF NOT EXISTS idx_symbols_module ON symbols(module_path)");
|
|
97
99
|
} catch {
|
|
98
100
|
}
|
|
99
|
-
|
|
101
|
+
let symbols;
|
|
102
|
+
if (callerIds && callerIds.size > 0) {
|
|
103
|
+
const callerIdList = [...callerIds];
|
|
104
|
+
const batchLangs = /* @__PURE__ */ new Set();
|
|
105
|
+
for (let i = 0; i < callerIdList.length; i += 500) {
|
|
106
|
+
const chunk = callerIdList.slice(i, i + 500);
|
|
107
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
108
|
+
const rows = db.prepare(`SELECT DISTINCT language FROM symbols WHERE id IN (${placeholders})`).all(...chunk);
|
|
109
|
+
for (const r of rows) batchLangs.add(r.language);
|
|
110
|
+
}
|
|
111
|
+
if (batchLangs.size > 0) {
|
|
112
|
+
const langPlaceholders = [...batchLangs].map(() => "?").join(",");
|
|
113
|
+
symbols = db.prepare(`SELECT id, name, kind, file_path, module_path, language, parent_id FROM symbols WHERE language IN (${langPlaceholders})`).all(...batchLangs);
|
|
114
|
+
} else {
|
|
115
|
+
symbols = db.prepare("SELECT id, name, kind, file_path, module_path, language, parent_id FROM symbols").all();
|
|
116
|
+
}
|
|
117
|
+
} else {
|
|
118
|
+
symbols = db.prepare("SELECT id, name, kind, file_path, module_path, language, parent_id FROM symbols").all();
|
|
119
|
+
}
|
|
100
120
|
const registry = new FunctionRegistry(symbols);
|
|
121
|
+
const fuzzyEnabled = symbols.length <= 500;
|
|
101
122
|
const imports = db.prepare("SELECT file_path, symbol_name, source_path FROM imports").all();
|
|
102
123
|
const importMap = new ImportMap(imports);
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
124
|
+
let calls;
|
|
125
|
+
if (callerIds && callerIds.size > 0) {
|
|
126
|
+
const idList = [...callerIds];
|
|
127
|
+
calls = [];
|
|
128
|
+
for (let i = 0; i < idList.length; i += 500) {
|
|
129
|
+
const chunk = idList.slice(i, i + 500);
|
|
130
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
131
|
+
const rows = db.prepare(
|
|
132
|
+
`SELECT id, caller_id, callee_name, callee_id, line, call_type FROM calls WHERE callee_id IS NULL AND caller_id IN (${placeholders})`
|
|
133
|
+
).all(...chunk);
|
|
134
|
+
calls.push(...rows);
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
calls = db.prepare(
|
|
138
|
+
"SELECT id, caller_id, callee_name, callee_id, line, call_type FROM calls WHERE callee_id IS NULL"
|
|
139
|
+
).all();
|
|
140
|
+
}
|
|
106
141
|
const stats = { total: calls.length, resolved: 0, byStrategy: {} };
|
|
107
142
|
let skipped = 0;
|
|
108
143
|
const callerFiles = /* @__PURE__ */ new Map();
|
|
109
|
-
const
|
|
110
|
-
if (
|
|
111
|
-
for (let i = 0; i <
|
|
112
|
-
const chunk =
|
|
144
|
+
const callerIdsFromCalls = [...new Set(calls.map((c) => c.caller_id))];
|
|
145
|
+
if (callerIdsFromCalls.length > 0) {
|
|
146
|
+
for (let i = 0; i < callerIdsFromCalls.length; i += 500) {
|
|
147
|
+
const chunk = callerIdsFromCalls.slice(i, i + 500);
|
|
113
148
|
const placeholders = chunk.map(() => "?").join(",");
|
|
114
149
|
const rows = db.prepare(`SELECT id, file_path FROM symbols WHERE id IN (${placeholders})`).all(...chunk);
|
|
115
150
|
for (const r of rows) {
|
|
@@ -125,7 +160,7 @@ function resolveAllCalls(db) {
|
|
|
125
160
|
continue;
|
|
126
161
|
}
|
|
127
162
|
const callerFile = callerFiles.get(call.caller_id) ?? "";
|
|
128
|
-
const result = resolveCall(call.callee_name, callerFile, registry, importMap);
|
|
163
|
+
const result = resolveCall(call.callee_name, callerFile, registry, importMap, fuzzyEnabled);
|
|
129
164
|
if (result.calleeId) {
|
|
130
165
|
updateStmt.run(result.calleeId, result.confidence, call.id);
|
|
131
166
|
stats.resolved++;
|
|
@@ -295,4 +330,4 @@ export {
|
|
|
295
330
|
resolveAllCalls,
|
|
296
331
|
resolveCall
|
|
297
332
|
};
|
|
298
|
-
//# sourceMappingURL=resolver-
|
|
333
|
+
//# sourceMappingURL=resolver-UHU4MBQX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/codegraph/resolver.ts"],"sourcesContent":["/**\n * Call resolver: 6-strategy cascade for resolving callee names to symbol IDs.\n *\n * Adapted from Codebase-Memory (arXiv:2603.27277, MIT, DeusData 2026).\n * https://github.com/DeusData/codebase-memory-mcp\n *\n * Strategies (in priority order):\n * 1. Import map (0.95): Split callee into prefix.suffix; look up prefix in file's import map\n * 2. Import map suffix (0.85): Suffix-based matching against import-resolved module paths\n * 3. Same module (0.90): Prefix callee with enclosing file's module path\n * 4. Unique name (0.75): Simple name lookup, accept if exactly 1 candidate\n * 5. Suffix match (0.55): Multiple candidates, select by suffix + import-distance\n * 6. Fuzzy (0.30-0.40): String similarity — last resort\n */\n\nimport type { Database } from \"better-sqlite3\";\n\nexport interface SymbolRow {\n id: string;\n name: string;\n kind: string;\n file_path: string;\n module_path: string | null;\n language: string;\n parent_id: string | null;\n}\n\nexport interface CallRow {\n id: number;\n caller_id: string;\n callee_name: string;\n callee_id: string | null;\n line: number;\n call_type: string;\n}\n\nexport interface ImportRow {\n file_path: string;\n symbol_name: string;\n source_path: string | null;\n}\n\nexport interface ResolutionResult {\n calleeId: string | null;\n confidence: number;\n strategy: string;\n}\n\n/**\n * FunctionRegistry indexes all symbol definitions by:\n * - qualified name (exact map): \"module_path/SymbolName\" → SymbolRow\n * - simple name (reverse index): \"SymbolName\" → SymbolRow[]\n */\nexport class FunctionRegistry {\n private exactMap = new Map<string, SymbolRow>();\n private reverseIndex = new Map<string, SymbolRow[]>();\n private fileModules = new Map<string, string>(); // file_path → module_path\n\n constructor(symbols: SymbolRow[]) {\n for (const sym of symbols) {\n // Build module_path from file_path if not set\n const modulePath = sym.module_path ?? deriveModulePath(sym.file_path);\n this.fileModules.set(sym.file_path, modulePath);\n\n // Exact map: module_path/name\n const qualifiedName = `${modulePath}/${sym.name}`;\n this.exactMap.set(qualifiedName, sym);\n // Also index by file_path/name for same-file resolution\n this.exactMap.set(`${sym.file_path}/${sym.name}`, sym);\n\n // Reverse index: simple name → all matching symbols\n const existing = this.reverseIndex.get(sym.name);\n if (existing) {\n existing.push(sym);\n } else {\n this.reverseIndex.set(sym.name, [sym]);\n }\n }\n }\n\n /** Look up by qualified name (module_path/name). */\n exactLookup(qualifiedName: string): SymbolRow | undefined {\n return this.exactMap.get(qualifiedName);\n }\n\n /** Look up by simple name — returns all candidates. */\n reverseLookup(simpleName: string): SymbolRow[] {\n return this.reverseIndex.get(simpleName) ?? [];\n }\n\n /** Get all reverse index entries (for fuzzy lookup). */\n reverseLookupEntries(): IterableIterator<[string, SymbolRow[]]> {\n return this.reverseIndex.entries();\n }\n\n /** Get module_path for a file. */\n getModuleForFile(filePath: string): string {\n return this.fileModules.get(filePath) ?? deriveModulePath(filePath);\n }\n}\n\n/**\n * ImportMap tracks what each file imports: { prefix → module_path }.\n * Built from the imports table.\n */\nexport class ImportMap {\n private fileImports = new Map<string, Map<string, string>>();\n\n constructor(imports: ImportRow[]) {\n for (const imp of imports) {\n if (!imp.source_path) continue;\n let fileMap = this.fileImports.get(imp.file_path);\n if (!fileMap) {\n fileMap = new Map();\n this.fileImports.set(imp.file_path, fileMap);\n }\n // Map: symbol_name (or prefix) → source module_path\n const prefix = imp.symbol_name.split(\".\")[0] ?? imp.symbol_name;\n const modulePath = deriveModulePath(imp.source_path);\n fileMap.set(prefix, modulePath);\n fileMap.set(imp.symbol_name, modulePath);\n }\n }\n\n /** Look up module path for an import prefix in a given file. */\n lookup(filePath: string, prefix: string): string | undefined {\n return this.fileImports.get(filePath)?.get(prefix);\n }\n\n /** Get all imports for a file. */\n getImportsForFile(filePath: string): Map<string, string> | undefined {\n return this.fileImports.get(filePath);\n }\n}\n\n/**\n * Resolve a callee name to a symbol ID using the 6-strategy cascade.\n * Returns { calleeId, confidence, strategy } or { calleeId: null, confidence: 0, strategy: \"unresolved\" }.\n */\nexport function resolveCall(\n calleeName: string,\n callerFilePath: string,\n registry: FunctionRegistry,\n importMap: ImportMap,\n fuzzyEnabled = true,\n): ResolutionResult {\n // Strategy 1: Import map (0.95)\n // Split callee into prefix.suffix; look up prefix in file's import map\n const parts = calleeName.split(\".\");\n if (parts.length >= 2) {\n const prefix = parts[0];\n const suffix = parts.slice(1).join(\".\");\n const modulePath = importMap.lookup(callerFilePath, prefix);\n if (modulePath) {\n const qualifiedName = `${modulePath}/${suffix}`;\n const sym = registry.exactLookup(qualifiedName);\n if (sym) {\n return { calleeId: sym.id, confidence: 0.95, strategy: \"import-map\" };\n }\n // Also try full callee name in the resolved module\n const fullQualified = `${modulePath}/${calleeName}`;\n const symFull = registry.exactLookup(fullQualified);\n if (symFull) {\n return { calleeId: symFull.id, confidence: 0.95, strategy: \"import-map\" };\n }\n }\n }\n\n // Strategy 2: Import map suffix (0.85)\n // Try suffix-based matching against import-resolved module paths\n if (parts.length >= 2) {\n const prefix = parts[0];\n const suffix = parts[parts.length - 1];\n const modulePath = importMap.lookup(callerFilePath, prefix);\n if (modulePath) {\n const candidates = registry.reverseLookup(suffix);\n for (const c of candidates) {\n const cModule = registry.getModuleForFile(c.file_path);\n if (cModule === modulePath || cModule.endsWith(`/${modulePath}`)) {\n return { calleeId: c.id, confidence: 0.85, strategy: \"import-map-suffix\" };\n }\n }\n }\n }\n\n // Strategy 3: Same module (0.90)\n // Prefix callee with enclosing file's module path\n const callerModule = registry.getModuleForFile(callerFilePath);\n const sameModuleQualified = `${callerModule}/${calleeName}`;\n const symSame = registry.exactLookup(sameModuleQualified);\n if (symSame) {\n return { calleeId: symSame.id, confidence: 0.9, strategy: \"same-module\" };\n }\n // Also try just the simple name in same module\n const sameModuleSimple = registry\n .reverseLookup(calleeName)\n .find((s) => registry.getModuleForFile(s.file_path) === callerModule);\n if (sameModuleSimple) {\n return { calleeId: sameModuleSimple.id, confidence: 0.9, strategy: \"same-module\" };\n }\n\n // Strategy 4: Unique name (0.75)\n // Simple name lookup, accept if exactly 1 candidate project-wide\n const candidates = registry.reverseLookup(calleeName);\n if (candidates.length === 1) {\n return { calleeId: candidates[0].id, confidence: 0.75, strategy: \"unique-name\" };\n }\n\n // Strategy 5: Suffix match (0.55)\n // Multiple candidates, select by suffix match with import-distance scoring\n if (candidates.length > 1) {\n // Prefer candidates in modules reachable from this file's imports\n const fileImports = importMap.getImportsForFile(callerFilePath);\n if (fileImports) {\n for (const c of candidates) {\n const cModule = registry.getModuleForFile(c.file_path);\n for (const [, impModule] of fileImports) {\n if (cModule === impModule || cModule.startsWith(`${impModule}/`)) {\n return { calleeId: c.id, confidence: 0.55, strategy: \"suffix-match\" };\n }\n }\n }\n }\n // Fallback: pick first candidate (nearest by file path similarity)\n const callerDir = callerFilePath.slice(0, callerFilePath.lastIndexOf(\"/\"));\n let best = candidates[0];\n let bestDist = Infinity;\n for (const c of candidates) {\n const cDir = c.file_path.slice(0, c.file_path.lastIndexOf(\"/\"));\n const dist = pathDistance(callerDir, cDir);\n if (dist < bestDist) {\n bestDist = dist;\n best = c;\n }\n }\n return { calleeId: best.id, confidence: 0.55, strategy: \"suffix-match\" };\n }\n\n // Strategy 6: Fuzzy (0.30-0.40)\n // String similarity — last resort. Skip when registry is large (O(n) per call).\n if (fuzzyEnabled) {\n const fuzzyMatch = fuzzyLookup(calleeName, registry);\n if (fuzzyMatch) {\n return { calleeId: fuzzyMatch.id, confidence: 0.35, strategy: \"fuzzy\" };\n }\n }\n\n return { calleeId: null, confidence: 0, strategy: \"unresolved\" };\n}\n\n/** Names too short or builtin to bother resolving. */\nconst SKIP_NAMES = new Set([\n \"T\",\n \"L\",\n \"R\",\n \"S\",\n \"P\",\n \"C\",\n \"D\",\n \"E\",\n \"F\",\n \"M\",\n \"N\",\n \"V\",\n \"X\",\n \"Y\",\n \"String\",\n \"Array\",\n \"Object\",\n \"Number\",\n \"Boolean\",\n \"Map\",\n \"Set\",\n \"Date\",\n \"Error\",\n \"RegExp\",\n \"Promise\",\n \"Symbol\",\n \"Buffer\",\n \"Vec\",\n \"Box\",\n \"Option\",\n \"Result\",\n \"Some\",\n \"None\",\n \"Ok\",\n \"Err\",\n \"List\",\n \"Dict\",\n \"Tuple\",\n]);\n\n/** Check if a callee name should be skipped (too short or builtin type). */\nfunction shouldSkipResolution(calleeName: string): boolean {\n if (calleeName.length <= 2) return true;\n if (SKIP_NAMES.has(calleeName)) return true;\n // Skip single-letter + dot patterns like \"T.foo\"\n const parts = calleeName.split(\".\");\n if (parts.length >= 2 && parts[0].length <= 1) return true;\n return false;\n}\n\n/**\n * Run call resolution on unresolved calls in the database.\n * Called after indexDirectory() completes.\n *\n * If callerIds is provided, only resolve calls whose caller_id is in the set\n * (i.e., calls from the just-indexed batch). This avoids re-resolving 25K+\n * historical calls on every index run — the O(n²) fuzzy strategy would hang.\n * If callerIds is omitted, resolve all unresolved calls (legacy behavior).\n */\nexport function resolveAllCalls(\n db: Database,\n callerIds?: Set<string>,\n): {\n total: number;\n resolved: number;\n byStrategy: Record<string, number>;\n} {\n // Create index for faster lookups (idempotent)\n try {\n db.exec(\"CREATE INDEX IF NOT EXISTS idx_calls_callee_name ON calls(callee_name)\");\n db.exec(\"CREATE INDEX IF NOT EXISTS idx_symbols_name ON symbols(name)\");\n db.exec(\"CREATE INDEX IF NOT EXISTS idx_symbols_module ON symbols(module_path)\");\n } catch {}\n\n // Load symbols into FunctionRegistry.\n // When resolving a batch (callerIds provided), only load symbols matching\n // the batch's languages — this avoids scanning 4800+ symbols from other\n // repos/languages during fuzzy lookup (O(n) per call).\n let symbols: SymbolRow[];\n if (callerIds && callerIds.size > 0) {\n // Determine languages of the caller symbols\n const callerIdList = [...callerIds];\n const batchLangs = new Set<string>();\n for (let i = 0; i < callerIdList.length; i += 500) {\n const chunk = callerIdList.slice(i, i + 500);\n const placeholders = chunk.map(() => \"?\").join(\",\");\n const rows = db\n .prepare(`SELECT DISTINCT language FROM symbols WHERE id IN (${placeholders})`)\n .all(...chunk) as { language: string }[];\n for (const r of rows) batchLangs.add(r.language);\n }\n if (batchLangs.size > 0) {\n const langPlaceholders = [...batchLangs].map(() => \"?\").join(\",\");\n symbols = db\n .prepare(`SELECT id, name, kind, file_path, module_path, language, parent_id FROM symbols WHERE language IN (${langPlaceholders})`)\n .all(...batchLangs) as SymbolRow[];\n } else {\n symbols = db\n .prepare(\"SELECT id, name, kind, file_path, module_path, language, parent_id FROM symbols\")\n .all() as SymbolRow[];\n }\n } else {\n symbols = db\n .prepare(\"SELECT id, name, kind, file_path, module_path, language, parent_id FROM symbols\")\n .all() as SymbolRow[];\n }\n const registry = new FunctionRegistry(symbols);\n const fuzzyEnabled = symbols.length <= 500;\n\n // Load all imports into ImportMap\n const imports = db\n .prepare(\"SELECT file_path, symbol_name, source_path FROM imports\")\n .all() as ImportRow[];\n const importMap = new ImportMap(imports);\n\n // Get unresolved calls — skip short/builtin names to save resolution time.\n // If callerIds is provided, only resolve calls from the just-indexed batch\n // (avoids re-resolving 25K+ historical calls — fuzzy is O(n²)).\n let calls: CallRow[];\n if (callerIds && callerIds.size > 0) {\n const idList = [...callerIds];\n calls = [];\n // Batch in chunks of 500 to avoid SQLite param limit\n for (let i = 0; i < idList.length; i += 500) {\n const chunk = idList.slice(i, i + 500);\n const placeholders = chunk.map(() => \"?\").join(\",\");\n const rows = db\n .prepare(\n `SELECT id, caller_id, callee_name, callee_id, line, call_type FROM calls WHERE callee_id IS NULL AND caller_id IN (${placeholders})`,\n )\n .all(...chunk) as CallRow[];\n calls.push(...rows);\n }\n } else {\n calls = db\n .prepare(\n \"SELECT id, caller_id, callee_name, callee_id, line, call_type FROM calls WHERE callee_id IS NULL\",\n )\n .all() as CallRow[];\n }\n\n const stats = { total: calls.length, resolved: 0, byStrategy: {} as Record<string, number> };\n let skipped = 0;\n\n // Get caller file paths — batch query\n const callerFiles = new Map<string, string>();\n const callerIdsFromCalls = [...new Set(calls.map((c) => c.caller_id))];\n if (callerIdsFromCalls.length > 0) {\n // Batch in chunks of 500 to avoid SQLite param limit\n for (let i = 0; i < callerIdsFromCalls.length; i += 500) {\n const chunk = callerIdsFromCalls.slice(i, i + 500);\n const placeholders = chunk.map(() => \"?\").join(\",\");\n const rows = db\n .prepare(`SELECT id, file_path FROM symbols WHERE id IN (${placeholders})`)\n .all(...chunk) as { id: string; file_path: string }[];\n for (const r of rows) {\n callerFiles.set(r.id, r.file_path);\n }\n }\n }\n\n // Resolve each call — skip short/builtin names\n const updateStmt = db.prepare(\"UPDATE calls SET callee_id = ?, confidence = ? WHERE id = ?\");\n const batch = db.transaction(() => {\n for (const call of calls) {\n if (shouldSkipResolution(call.callee_name)) {\n skipped++;\n continue;\n }\n const callerFile = callerFiles.get(call.caller_id) ?? \"\";\n const result = resolveCall(call.callee_name, callerFile, registry, importMap, fuzzyEnabled);\n if (result.calleeId) {\n updateStmt.run(result.calleeId, result.confidence, call.id);\n stats.resolved++;\n stats.byStrategy[result.strategy] = (stats.byStrategy[result.strategy] ?? 0) + 1;\n }\n }\n });\n batch();\n\n if (skipped > 0) {\n console.error(`[remem-mcp] resolveAllCalls: skipped ${skipped} short/builtin names`);\n }\n\n return stats;\n}\n\n/** Derive a module path from a file path: \"src/storage/sqlite.ts\" → \"src/storage/sqlite\" */\nfunction deriveModulePath(filePath: string): string {\n return filePath.replace(/\\.(ts|tsx|js|jsx|mjs|cjs|py|go|rs|java|c|cpp|cc|cxx|hpp|cs)$/, \"\");\n}\n\n/** Calculate directory path distance for suffix-match scoring. */\nfunction pathDistance(a: string, b: string): number {\n if (a === b) return 0;\n const aParts = a.split(\"/\");\n const bParts = b.split(\"/\");\n let common = 0;\n for (let i = 0; i < Math.min(aParts.length, bParts.length); i++) {\n if (aParts[i] === bParts[i]) common++;\n else break;\n }\n return aParts.length + bParts.length - 2 * common;\n}\n\n/** Fuzzy lookup: find symbol with most similar name. */\nfunction fuzzyLookup(name: string, registry: FunctionRegistry): SymbolRow | null {\n let best: SymbolRow | null = null;\n let bestScore = 0;\n for (const [symName, candidates] of registry.reverseLookupEntries()) {\n const score = similarity(name, symName);\n if (score > bestScore && score > 0.6) {\n bestScore = score;\n best = candidates[0];\n }\n }\n return best;\n}\n\n/** Simple string similarity (Jaccard on character bigrams). */\nfunction similarity(a: string, b: string): number {\n if (a === b) return 1;\n const aBigrams = bigrams(a);\n const bBigrams = bigrams(b);\n const intersection = aBigrams.filter((bg) => bBigrams.includes(bg)).length;\n const union = aBigrams.length + bBigrams.length - intersection;\n return union > 0 ? intersection / union : 0;\n}\n\nfunction bigrams(s: string): string[] {\n const result: string[] = [];\n for (let i = 0; i < s.length - 1; i++) {\n result.push(s.slice(i, i + 2));\n }\n return result;\n}\n"],"mappings":";;;;;;AA2IO,SAAS,YACd,YACA,gBACA,UACA,WACA,eAAe,MACG;AAGlB,QAAM,QAAQ,WAAW,MAAM,GAAG;AAClC,MAAI,MAAM,UAAU,GAAG;AACrB,UAAM,SAAS,MAAM,CAAC;AACtB,UAAM,SAAS,MAAM,MAAM,CAAC,EAAE,KAAK,GAAG;AACtC,UAAM,aAAa,UAAU,OAAO,gBAAgB,MAAM;AAC1D,QAAI,YAAY;AACd,YAAM,gBAAgB,GAAG,UAAU,IAAI,MAAM;AAC7C,YAAM,MAAM,SAAS,YAAY,aAAa;AAC9C,UAAI,KAAK;AACP,eAAO,EAAE,UAAU,IAAI,IAAI,YAAY,MAAM,UAAU,aAAa;AAAA,MACtE;AAEA,YAAM,gBAAgB,GAAG,UAAU,IAAI,UAAU;AACjD,YAAM,UAAU,SAAS,YAAY,aAAa;AAClD,UAAI,SAAS;AACX,eAAO,EAAE,UAAU,QAAQ,IAAI,YAAY,MAAM,UAAU,aAAa;AAAA,MAC1E;AAAA,IACF;AAAA,EACF;AAIA,MAAI,MAAM,UAAU,GAAG;AACrB,UAAM,SAAS,MAAM,CAAC;AACtB,UAAM,SAAS,MAAM,MAAM,SAAS,CAAC;AACrC,UAAM,aAAa,UAAU,OAAO,gBAAgB,MAAM;AAC1D,QAAI,YAAY;AACd,YAAMA,cAAa,SAAS,cAAc,MAAM;AAChD,iBAAW,KAAKA,aAAY;AAC1B,cAAM,UAAU,SAAS,iBAAiB,EAAE,SAAS;AACrD,YAAI,YAAY,cAAc,QAAQ,SAAS,IAAI,UAAU,EAAE,GAAG;AAChE,iBAAO,EAAE,UAAU,EAAE,IAAI,YAAY,MAAM,UAAU,oBAAoB;AAAA,QAC3E;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAIA,QAAM,eAAe,SAAS,iBAAiB,cAAc;AAC7D,QAAM,sBAAsB,GAAG,YAAY,IAAI,UAAU;AACzD,QAAM,UAAU,SAAS,YAAY,mBAAmB;AACxD,MAAI,SAAS;AACX,WAAO,EAAE,UAAU,QAAQ,IAAI,YAAY,KAAK,UAAU,cAAc;AAAA,EAC1E;AAEA,QAAM,mBAAmB,SACtB,cAAc,UAAU,EACxB,KAAK,CAAC,MAAM,SAAS,iBAAiB,EAAE,SAAS,MAAM,YAAY;AACtE,MAAI,kBAAkB;AACpB,WAAO,EAAE,UAAU,iBAAiB,IAAI,YAAY,KAAK,UAAU,cAAc;AAAA,EACnF;AAIA,QAAM,aAAa,SAAS,cAAc,UAAU;AACpD,MAAI,WAAW,WAAW,GAAG;AAC3B,WAAO,EAAE,UAAU,WAAW,CAAC,EAAE,IAAI,YAAY,MAAM,UAAU,cAAc;AAAA,EACjF;AAIA,MAAI,WAAW,SAAS,GAAG;AAEzB,UAAM,cAAc,UAAU,kBAAkB,cAAc;AAC9D,QAAI,aAAa;AACf,iBAAW,KAAK,YAAY;AAC1B,cAAM,UAAU,SAAS,iBAAiB,EAAE,SAAS;AACrD,mBAAW,CAAC,EAAE,SAAS,KAAK,aAAa;AACvC,cAAI,YAAY,aAAa,QAAQ,WAAW,GAAG,SAAS,GAAG,GAAG;AAChE,mBAAO,EAAE,UAAU,EAAE,IAAI,YAAY,MAAM,UAAU,eAAe;AAAA,UACtE;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,UAAM,YAAY,eAAe,MAAM,GAAG,eAAe,YAAY,GAAG,CAAC;AACzE,QAAI,OAAO,WAAW,CAAC;AACvB,QAAI,WAAW;AACf,eAAW,KAAK,YAAY;AAC1B,YAAM,OAAO,EAAE,UAAU,MAAM,GAAG,EAAE,UAAU,YAAY,GAAG,CAAC;AAC9D,YAAM,OAAO,aAAa,WAAW,IAAI;AACzC,UAAI,OAAO,UAAU;AACnB,mBAAW;AACX,eAAO;AAAA,MACT;AAAA,IACF;AACA,WAAO,EAAE,UAAU,KAAK,IAAI,YAAY,MAAM,UAAU,eAAe;AAAA,EACzE;AAIA,MAAI,cAAc;AAChB,UAAM,aAAa,YAAY,YAAY,QAAQ;AACnD,QAAI,YAAY;AACd,aAAO,EAAE,UAAU,WAAW,IAAI,YAAY,MAAM,UAAU,QAAQ;AAAA,IACxE;AAAA,EACF;AAEA,SAAO,EAAE,UAAU,MAAM,YAAY,GAAG,UAAU,aAAa;AACjE;AA6CA,SAAS,qBAAqB,YAA6B;AACzD,MAAI,WAAW,UAAU,EAAG,QAAO;AACnC,MAAI,WAAW,IAAI,UAAU,EAAG,QAAO;AAEvC,QAAM,QAAQ,WAAW,MAAM,GAAG;AAClC,MAAI,MAAM,UAAU,KAAK,MAAM,CAAC,EAAE,UAAU,EAAG,QAAO;AACtD,SAAO;AACT;AAWO,SAAS,gBACd,IACA,WAKA;AAEA,MAAI;AACF,OAAG,KAAK,wEAAwE;AAChF,OAAG,KAAK,8DAA8D;AACtE,OAAG,KAAK,uEAAuE;AAAA,EACjF,QAAQ;AAAA,EAAC;AAMT,MAAI;AACJ,MAAI,aAAa,UAAU,OAAO,GAAG;AAEnC,UAAM,eAAe,CAAC,GAAG,SAAS;AAClC,UAAM,aAAa,oBAAI,IAAY;AACnC,aAAS,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK,KAAK;AACjD,YAAM,QAAQ,aAAa,MAAM,GAAG,IAAI,GAAG;AAC3C,YAAM,eAAe,MAAM,IAAI,MAAM,GAAG,EAAE,KAAK,GAAG;AAClD,YAAM,OAAO,GACV,QAAQ,sDAAsD,YAAY,GAAG,EAC7E,IAAI,GAAG,KAAK;AACf,iBAAW,KAAK,KAAM,YAAW,IAAI,EAAE,QAAQ;AAAA,IACjD;AACA,QAAI,WAAW,OAAO,GAAG;AACvB,YAAM,mBAAmB,CAAC,GAAG,UAAU,EAAE,IAAI,MAAM,GAAG,EAAE,KAAK,GAAG;AAChE,gBAAU,GACP,QAAQ,sGAAsG,gBAAgB,GAAG,EACjI,IAAI,GAAG,UAAU;AAAA,IACtB,OAAO;AACL,gBAAU,GACP,QAAQ,iFAAiF,EACzF,IAAI;AAAA,IACT;AAAA,EACF,OAAO;AACL,cAAU,GACP,QAAQ,iFAAiF,EACzF,IAAI;AAAA,EACT;AACA,QAAM,WAAW,IAAI,iBAAiB,OAAO;AAC7C,QAAM,eAAe,QAAQ,UAAU;AAGvC,QAAM,UAAU,GACb,QAAQ,yDAAyD,EACjE,IAAI;AACP,QAAM,YAAY,IAAI,UAAU,OAAO;AAKvC,MAAI;AACJ,MAAI,aAAa,UAAU,OAAO,GAAG;AACnC,UAAM,SAAS,CAAC,GAAG,SAAS;AAC5B,YAAQ,CAAC;AAET,aAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK,KAAK;AAC3C,YAAM,QAAQ,OAAO,MAAM,GAAG,IAAI,GAAG;AACrC,YAAM,eAAe,MAAM,IAAI,MAAM,GAAG,EAAE,KAAK,GAAG;AAClD,YAAM,OAAO,GACV;AAAA,QACC,sHAAsH,YAAY;AAAA,MACpI,EACC,IAAI,GAAG,KAAK;AACf,YAAM,KAAK,GAAG,IAAI;AAAA,IACpB;AAAA,EACF,OAAO;AACL,YAAQ,GACL;AAAA,MACC;AAAA,IACF,EACC,IAAI;AAAA,EACT;AAEA,QAAM,QAAQ,EAAE,OAAO,MAAM,QAAQ,UAAU,GAAG,YAAY,CAAC,EAA4B;AAC3F,MAAI,UAAU;AAGd,QAAM,cAAc,oBAAI,IAAoB;AAC5C,QAAM,qBAAqB,CAAC,GAAG,IAAI,IAAI,MAAM,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AACrE,MAAI,mBAAmB,SAAS,GAAG;AAEjC,aAAS,IAAI,GAAG,IAAI,mBAAmB,QAAQ,KAAK,KAAK;AACvD,YAAM,QAAQ,mBAAmB,MAAM,GAAG,IAAI,GAAG;AACjD,YAAM,eAAe,MAAM,IAAI,MAAM,GAAG,EAAE,KAAK,GAAG;AAClD,YAAM,OAAO,GACV,QAAQ,kDAAkD,YAAY,GAAG,EACzE,IAAI,GAAG,KAAK;AACf,iBAAW,KAAK,MAAM;AACpB,oBAAY,IAAI,EAAE,IAAI,EAAE,SAAS;AAAA,MACnC;AAAA,IACF;AAAA,EACF;AAGA,QAAM,aAAa,GAAG,QAAQ,6DAA6D;AAC3F,QAAM,QAAQ,GAAG,YAAY,MAAM;AACjC,eAAW,QAAQ,OAAO;AACxB,UAAI,qBAAqB,KAAK,WAAW,GAAG;AAC1C;AACA;AAAA,MACF;AACA,YAAM,aAAa,YAAY,IAAI,KAAK,SAAS,KAAK;AACtD,YAAM,SAAS,YAAY,KAAK,aAAa,YAAY,UAAU,WAAW,YAAY;AAC1F,UAAI,OAAO,UAAU;AACnB,mBAAW,IAAI,OAAO,UAAU,OAAO,YAAY,KAAK,EAAE;AAC1D,cAAM;AACN,cAAM,WAAW,OAAO,QAAQ,KAAK,MAAM,WAAW,OAAO,QAAQ,KAAK,KAAK;AAAA,MACjF;AAAA,IACF;AAAA,EACF,CAAC;AACD,QAAM;AAEN,MAAI,UAAU,GAAG;AACf,YAAQ,MAAM,wCAAwC,OAAO,sBAAsB;AAAA,EACrF;AAEA,SAAO;AACT;AAGA,SAAS,iBAAiB,UAA0B;AAClD,SAAO,SAAS,QAAQ,gEAAgE,EAAE;AAC5F;AAGA,SAAS,aAAa,GAAW,GAAmB;AAClD,MAAI,MAAM,EAAG,QAAO;AACpB,QAAM,SAAS,EAAE,MAAM,GAAG;AAC1B,QAAM,SAAS,EAAE,MAAM,GAAG;AAC1B,MAAI,SAAS;AACb,WAAS,IAAI,GAAG,IAAI,KAAK,IAAI,OAAO,QAAQ,OAAO,MAAM,GAAG,KAAK;AAC/D,QAAI,OAAO,CAAC,MAAM,OAAO,CAAC,EAAG;AAAA,QACxB;AAAA,EACP;AACA,SAAO,OAAO,SAAS,OAAO,SAAS,IAAI;AAC7C;AAGA,SAAS,YAAY,MAAc,UAA8C;AAC/E,MAAI,OAAyB;AAC7B,MAAI,YAAY;AAChB,aAAW,CAAC,SAAS,UAAU,KAAK,SAAS,qBAAqB,GAAG;AACnE,UAAM,QAAQ,WAAW,MAAM,OAAO;AACtC,QAAI,QAAQ,aAAa,QAAQ,KAAK;AACpC,kBAAY;AACZ,aAAO,WAAW,CAAC;AAAA,IACrB;AAAA,EACF;AACA,SAAO;AACT;AAGA,SAAS,WAAW,GAAW,GAAmB;AAChD,MAAI,MAAM,EAAG,QAAO;AACpB,QAAM,WAAW,QAAQ,CAAC;AAC1B,QAAM,WAAW,QAAQ,CAAC;AAC1B,QAAM,eAAe,SAAS,OAAO,CAAC,OAAO,SAAS,SAAS,EAAE,CAAC,EAAE;AACpE,QAAM,QAAQ,SAAS,SAAS,SAAS,SAAS;AAClD,SAAO,QAAQ,IAAI,eAAe,QAAQ;AAC5C;AAEA,SAAS,QAAQ,GAAqB;AACpC,QAAM,SAAmB,CAAC;AAC1B,WAAS,IAAI,GAAG,IAAI,EAAE,SAAS,GAAG,KAAK;AACrC,WAAO,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;AAAA,EAC/B;AACA,SAAO;AACT;AAveA,IAqDa,kBAoDA,WAkJP;AA3PN;AAAA;AAAA;AAqDO,IAAM,mBAAN,MAAuB;AAAA,MACpB,WAAW,oBAAI,IAAuB;AAAA,MACtC,eAAe,oBAAI,IAAyB;AAAA,MAC5C,cAAc,oBAAI,IAAoB;AAAA;AAAA,MAE9C,YAAY,SAAsB;AAChC,mBAAW,OAAO,SAAS;AAEzB,gBAAM,aAAa,IAAI,eAAe,iBAAiB,IAAI,SAAS;AACpE,eAAK,YAAY,IAAI,IAAI,WAAW,UAAU;AAG9C,gBAAM,gBAAgB,GAAG,UAAU,IAAI,IAAI,IAAI;AAC/C,eAAK,SAAS,IAAI,eAAe,GAAG;AAEpC,eAAK,SAAS,IAAI,GAAG,IAAI,SAAS,IAAI,IAAI,IAAI,IAAI,GAAG;AAGrD,gBAAM,WAAW,KAAK,aAAa,IAAI,IAAI,IAAI;AAC/C,cAAI,UAAU;AACZ,qBAAS,KAAK,GAAG;AAAA,UACnB,OAAO;AACL,iBAAK,aAAa,IAAI,IAAI,MAAM,CAAC,GAAG,CAAC;AAAA,UACvC;AAAA,QACF;AAAA,MACF;AAAA;AAAA,MAGA,YAAY,eAA8C;AACxD,eAAO,KAAK,SAAS,IAAI,aAAa;AAAA,MACxC;AAAA;AAAA,MAGA,cAAc,YAAiC;AAC7C,eAAO,KAAK,aAAa,IAAI,UAAU,KAAK,CAAC;AAAA,MAC/C;AAAA;AAAA,MAGA,uBAAgE;AAC9D,eAAO,KAAK,aAAa,QAAQ;AAAA,MACnC;AAAA;AAAA,MAGA,iBAAiB,UAA0B;AACzC,eAAO,KAAK,YAAY,IAAI,QAAQ,KAAK,iBAAiB,QAAQ;AAAA,MACpE;AAAA,IACF;AAMO,IAAM,YAAN,MAAgB;AAAA,MACb,cAAc,oBAAI,IAAiC;AAAA,MAE3D,YAAY,SAAsB;AAChC,mBAAW,OAAO,SAAS;AACzB,cAAI,CAAC,IAAI,YAAa;AACtB,cAAI,UAAU,KAAK,YAAY,IAAI,IAAI,SAAS;AAChD,cAAI,CAAC,SAAS;AACZ,sBAAU,oBAAI,IAAI;AAClB,iBAAK,YAAY,IAAI,IAAI,WAAW,OAAO;AAAA,UAC7C;AAEA,gBAAM,SAAS,IAAI,YAAY,MAAM,GAAG,EAAE,CAAC,KAAK,IAAI;AACpD,gBAAM,aAAa,iBAAiB,IAAI,WAAW;AACnD,kBAAQ,IAAI,QAAQ,UAAU;AAC9B,kBAAQ,IAAI,IAAI,aAAa,UAAU;AAAA,QACzC;AAAA,MACF;AAAA;AAAA,MAGA,OAAO,UAAkB,QAAoC;AAC3D,eAAO,KAAK,YAAY,IAAI,QAAQ,GAAG,IAAI,MAAM;AAAA,MACnD;AAAA;AAAA,MAGA,kBAAkB,UAAmD;AACnE,eAAO,KAAK,YAAY,IAAI,QAAQ;AAAA,MACtC;AAAA,IACF;AAsHA,IAAM,aAAa,oBAAI,IAAI;AAAA,MACzB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA;AAAA;","names":["candidates"]}
|
package/dist/sdk.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
ingestDirectory,
|
|
7
7
|
ingestFile,
|
|
8
8
|
searchWiki
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-ITMR6THY.js";
|
|
10
10
|
import {
|
|
11
11
|
SUPPORTED_LANGUAGES,
|
|
12
12
|
detectLanguage,
|
|
@@ -17,11 +17,11 @@ import {
|
|
|
17
17
|
indexFile,
|
|
18
18
|
listSymbols,
|
|
19
19
|
searchSymbols
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-YU7WBJU5.js";
|
|
21
21
|
import {
|
|
22
22
|
LocalEmbedder,
|
|
23
23
|
SQLiteBackend
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-4WCDAEPP.js";
|
|
25
25
|
import "./chunk-XJGDYQ34.js";
|
|
26
26
|
import "./chunk-ONQHLUGM.js";
|
|
27
27
|
export {
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
LocalEmbedder,
|
|
3
3
|
SQLiteBackend,
|
|
4
4
|
init_local
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-4WCDAEPP.js";
|
|
6
6
|
import {
|
|
7
7
|
generateId,
|
|
8
8
|
init_ulid
|
|
@@ -514,4 +514,4 @@ export {
|
|
|
514
514
|
loadProxyConfig,
|
|
515
515
|
startProxyServer
|
|
516
516
|
};
|
|
517
|
-
//# sourceMappingURL=server-
|
|
517
|
+
//# sourceMappingURL=server-5PWQTRGZ.js.map
|
package/dist/storage/schema.sql
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
-- Schema for remem-mcp
|
|
2
|
-
-- Version:
|
|
2
|
+
-- Version: 13
|
|
3
3
|
--
|
|
4
4
|
-- This file runs on the first start. It creates all tables, triggers, and indexes.
|
|
5
5
|
-- It uses CREATE TABLE IF NOT EXISTS and CREATE INDEX IF NOT EXISTS.
|
|
@@ -75,7 +75,13 @@ CREATE TABLE IF NOT EXISTS captures (
|
|
|
75
75
|
retrieved_count INTEGER NOT NULL DEFAULT 0,
|
|
76
76
|
heeded_count INTEGER NOT NULL DEFAULT 0,
|
|
77
77
|
recurrence_count INTEGER NOT NULL DEFAULT 0,
|
|
78
|
-
last_outcome TEXT
|
|
78
|
+
last_outcome TEXT,
|
|
79
|
+
-- v10: Authority tier + salience for decay/forget sweep
|
|
80
|
+
tier TEXT NOT NULL DEFAULT 'episodic', -- rule, decision, episodic, test
|
|
81
|
+
salience REAL NOT NULL DEFAULT 1.0, -- decay score, updated by forget sweep
|
|
82
|
+
-- v12: TTL for time-sensitive captures + feedback-derived salience floor
|
|
83
|
+
expires_at INTEGER, -- timestamp; forgetSweep hard-deletes past this
|
|
84
|
+
feedback_salience REAL NOT NULL DEFAULT 1.0 -- multiplier from feedback signals (0.1–2.0)
|
|
79
85
|
);
|
|
80
86
|
|
|
81
87
|
-- L0 messages: role-based conversation messages linked to a capture.
|
|
@@ -114,6 +120,61 @@ CREATE TABLE IF NOT EXISTS scenarios (
|
|
|
114
120
|
user_id TEXT
|
|
115
121
|
);
|
|
116
122
|
|
|
123
|
+
-- v10: Entity index for entity-assisted recall.
|
|
124
|
+
-- Stores <=10 extracted nouns per capture for lexical RRF matching.
|
|
125
|
+
CREATE TABLE IF NOT EXISTS entities (
|
|
126
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
127
|
+
capture_id TEXT NOT NULL REFERENCES captures(id) ON DELETE CASCADE,
|
|
128
|
+
entity TEXT NOT NULL,
|
|
129
|
+
created_at INTEGER NOT NULL
|
|
130
|
+
);
|
|
131
|
+
CREATE INDEX IF NOT EXISTS idx_entities_capture ON entities (capture_id);
|
|
132
|
+
CREATE INDEX IF NOT EXISTS idx_entities_entity ON entities (entity);
|
|
133
|
+
|
|
134
|
+
-- v11: Memory-to-memory links for link-neighbor RRF expansion.
|
|
135
|
+
-- Auto-linked on capture via shared tags/entities/session-proximity.
|
|
136
|
+
-- Manually linked via link_captures MCP tool.
|
|
137
|
+
CREATE TABLE IF NOT EXISTS memory_links (
|
|
138
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
139
|
+
from_id TEXT NOT NULL REFERENCES captures(id) ON DELETE CASCADE,
|
|
140
|
+
to_id TEXT NOT NULL REFERENCES captures(id) ON DELETE CASCADE,
|
|
141
|
+
link_type TEXT NOT NULL DEFAULT 'related', -- shared-tag, shared-entity, session-proximity, related, cause-effect, supersedes, prerequisite
|
|
142
|
+
auto INTEGER NOT NULL DEFAULT 0, -- 1 = auto-generated, 0 = manual
|
|
143
|
+
weight REAL NOT NULL DEFAULT 1.0, -- v13: Hebbian weight, strengthened on co-retrieval
|
|
144
|
+
created_at INTEGER NOT NULL,
|
|
145
|
+
UNIQUE (from_id, to_id, link_type)
|
|
146
|
+
);
|
|
147
|
+
CREATE INDEX IF NOT EXISTS idx_memory_links_from ON memory_links (from_id);
|
|
148
|
+
CREATE INDEX IF NOT EXISTS idx_memory_links_to ON memory_links (to_id);
|
|
149
|
+
CREATE INDEX IF NOT EXISTS idx_memory_links_type ON memory_links (link_type);
|
|
150
|
+
|
|
151
|
+
-- v12: Capture feedback signals (helpful/not_helpful/stale/wrong).
|
|
152
|
+
-- Adjusts salience score. Creates feedback flywheel.
|
|
153
|
+
CREATE TABLE IF NOT EXISTS capture_feedback (
|
|
154
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
155
|
+
capture_id TEXT NOT NULL REFERENCES captures(id) ON DELETE CASCADE,
|
|
156
|
+
signal TEXT NOT NULL, -- helpful, not_helpful, stale, wrong
|
|
157
|
+
reason TEXT,
|
|
158
|
+
agent_id TEXT,
|
|
159
|
+
created_at INTEGER NOT NULL
|
|
160
|
+
);
|
|
161
|
+
CREATE INDEX IF NOT EXISTS idx_feedback_capture ON capture_feedback (capture_id);
|
|
162
|
+
CREATE INDEX IF NOT EXISTS idx_feedback_signal ON capture_feedback (signal);
|
|
163
|
+
|
|
164
|
+
-- v12: Mutation log — every storage mutation (put/delete/reject/supersede/feedback/link/forget).
|
|
165
|
+
-- Distinct from audit_log (which logs MCP tool calls).
|
|
166
|
+
CREATE TABLE IF NOT EXISTS mutation_log (
|
|
167
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
168
|
+
action TEXT NOT NULL, -- put, delete, reject, supersede, feedback, link, forget, correct, confirm
|
|
169
|
+
capture_id TEXT,
|
|
170
|
+
details TEXT, -- JSON blob with action-specific metadata
|
|
171
|
+
agent_id TEXT,
|
|
172
|
+
created_at INTEGER NOT NULL
|
|
173
|
+
);
|
|
174
|
+
CREATE INDEX IF NOT EXISTS idx_mutation_action ON mutation_log (action);
|
|
175
|
+
CREATE INDEX IF NOT EXISTS idx_mutation_capture ON mutation_log (capture_id);
|
|
176
|
+
CREATE INDEX IF NOT EXISTS idx_mutation_created ON mutation_log (created_at);
|
|
177
|
+
|
|
117
178
|
-- L3: Persona (long-term user profile, one per team/agent/user)
|
|
118
179
|
CREATE TABLE IF NOT EXISTS persona (
|
|
119
180
|
team_id TEXT NOT NULL,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "remem-mcp",
|
|
3
3
|
"mcpName": "io.github.tinhien11/remem-mcp",
|
|
4
|
-
"version": "0.7.
|
|
4
|
+
"version": "0.7.3",
|
|
5
5
|
"description": "Long-term memory for AI coding agents. Memory + CodeGraph + Wiki in one SQLite file. Auto-recall, auto-capture, no API key, no cloud.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "MIT",
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Verify codegraph_stats tool exists in the built server and returns correct stats.
|
|
4
|
+
*/
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
import { mkdtempSync, writeFileSync, mkdirSync, rmSync } from "node:fs";
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
|
|
10
|
+
const distPath = join(process.cwd(), "dist", "index.js");
|
|
11
|
+
|
|
12
|
+
// Create a small test repo with some symbols
|
|
13
|
+
const repoDir = mkdtempSync(join(tmpdir(), "cg-stats-"));
|
|
14
|
+
const srcDir = join(repoDir, "src");
|
|
15
|
+
mkdirSync(srcDir, { recursive: true });
|
|
16
|
+
writeFileSync(join(srcDir, "foo.ts"),
|
|
17
|
+
`export function alpha() { return 1; }\n` +
|
|
18
|
+
`export class Beta { gamma() {} }\n`);
|
|
19
|
+
writeFileSync(join(srcDir, "bar.ts"),
|
|
20
|
+
`export function delta() { alpha(); }\n`);
|
|
21
|
+
|
|
22
|
+
const proc = spawn("node", [distPath, "--stdio"], {
|
|
23
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
24
|
+
env: { ...process.env, REMEM_DB_PATH: join(repoDir, "test.db") },
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
let buf = "";
|
|
28
|
+
proc.stdout.on("data", (d) => { buf += d.toString(); });
|
|
29
|
+
proc.stderr.on("data", () => {});
|
|
30
|
+
|
|
31
|
+
function send(msg) { proc.stdin.write(JSON.stringify(msg) + "\n"); }
|
|
32
|
+
|
|
33
|
+
function waitForId(id, timeoutMs = 15000) {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
const start = Date.now();
|
|
36
|
+
const iv = setInterval(() => {
|
|
37
|
+
for (const line of buf.split("\n")) {
|
|
38
|
+
try {
|
|
39
|
+
const obj = JSON.parse(line);
|
|
40
|
+
if (obj.id === id) { clearInterval(iv); resolve(obj); return; }
|
|
41
|
+
} catch {}
|
|
42
|
+
}
|
|
43
|
+
if (Date.now() - start > timeoutMs) { clearInterval(iv); reject(new Error(`timeout id=${id}`)); }
|
|
44
|
+
}, 50);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 1. Check codegraph_stats is in the tools list
|
|
49
|
+
send({ jsonrpc: "2.0", id: 1, method: "initialize",
|
|
50
|
+
params: { protocolVersion: "2024-11-05", capabilities: {}, clientInfo: { name: "test", version: "1.0" } } });
|
|
51
|
+
await waitForId(1);
|
|
52
|
+
send({ jsonrpc: "2.0", method: "notifications/initialized" });
|
|
53
|
+
|
|
54
|
+
send({ jsonrpc: "2.0", id: 2, method: "tools/list", params: {} });
|
|
55
|
+
const toolsResp = await waitForId(2);
|
|
56
|
+
const toolNames = (toolsResp.result?.tools || []).map(t => t.name);
|
|
57
|
+
const hasStats = toolNames.includes("codegraph_stats");
|
|
58
|
+
|
|
59
|
+
if (!hasStats) {
|
|
60
|
+
console.error("FAIL: codegraph_stats not in tools list");
|
|
61
|
+
console.error("Available tools:", toolNames.join(", "));
|
|
62
|
+
proc.kill();
|
|
63
|
+
try { rmSync(repoDir, { recursive: true }); } catch {}
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
console.log("OK: codegraph_stats is in tools list");
|
|
67
|
+
|
|
68
|
+
// 2. Index the test repo first
|
|
69
|
+
send({ jsonrpc: "2.0", id: 3, method: "tools/call",
|
|
70
|
+
params: { name: "codegraph_index", arguments: { path: srcDir, repo_path: srcDir } } });
|
|
71
|
+
await waitForId(3, 30000);
|
|
72
|
+
|
|
73
|
+
// 3. Call codegraph_stats and verify it returns numbers
|
|
74
|
+
send({ jsonrpc: "2.0", id: 4, method: "tools/call",
|
|
75
|
+
params: { name: "codegraph_stats", arguments: { repo_path: srcDir } } });
|
|
76
|
+
|
|
77
|
+
let failures = 0;
|
|
78
|
+
try {
|
|
79
|
+
const resp = await waitForId(4, 15000);
|
|
80
|
+
const text = resp.result?.content?.[0]?.text || "";
|
|
81
|
+
|
|
82
|
+
if (text.includes("symbols") && text.includes("calls") && text.includes("files")) {
|
|
83
|
+
console.log("OK: codegraph_stats returns symbols, calls, files");
|
|
84
|
+
} else {
|
|
85
|
+
console.error(`FAIL: codegraph_stats output missing expected fields: ${text.slice(0, 300)}`);
|
|
86
|
+
failures++;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const files = Number((text.match(/files:\s*(\d+)/) || [])[1] || 0);
|
|
90
|
+
const symbols = Number((text.match(/symbols:\s*(\d+)/) || [])[1] || 0);
|
|
91
|
+
const calls = Number((text.match(/calls:\s*(\d+)/) || [])[1] || 0);
|
|
92
|
+
if (files >= 2 && symbols >= 3 && calls >= 1) {
|
|
93
|
+
console.log(`OK: codegraph_stats is repo-scoped (files=${files}, symbols=${symbols}, calls=${calls})`);
|
|
94
|
+
} else {
|
|
95
|
+
console.error(`FAIL: codegraph_stats stats look wrong: ${text.slice(0, 300)}`);
|
|
96
|
+
failures++;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Should not be an error
|
|
100
|
+
if (resp.result?.isError) {
|
|
101
|
+
console.error("FAIL: codegraph_stats returned an error");
|
|
102
|
+
failures++;
|
|
103
|
+
} else {
|
|
104
|
+
console.log("OK: codegraph_stats returned successfully (no error)");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
console.log(`\nStats output: ${text.slice(0, 500)}`);
|
|
108
|
+
} catch (err) {
|
|
109
|
+
console.error(`FAIL: codegraph_stats call failed: ${err.message}`);
|
|
110
|
+
failures++;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
proc.kill();
|
|
114
|
+
try { rmSync(repoDir, { recursive: true }); } catch {}
|
|
115
|
+
|
|
116
|
+
if (failures === 0) {
|
|
117
|
+
console.log("codegraph_stats verification passed");
|
|
118
|
+
process.exit(0);
|
|
119
|
+
} else {
|
|
120
|
+
console.error(`${failures} codegraph_stats check(s) failed`);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
@@ -7,8 +7,8 @@ You have a long-term memory server via MCP. Use the tools automatically — do n
|
|
|
7
7
|
|
|
8
8
|
## Tools
|
|
9
9
|
|
|
10
|
-
**Core:** `recall` `capture` `search` `forget` `resolve` `handoff` `adr` `update` `consolidate` `scenario_create` `persona_update`
|
|
11
|
-
**CodeGraph:** `
|
|
10
|
+
**Core:** `recall` `capture` `search` `feedback` `forget` `resolve` `handoff` `adr` `update` `consolidate` `scenario_create` `persona_update`
|
|
11
|
+
**CodeGraph:** `codegraph_index` (run once per session; idempotent) `codegraph_search` `codegraph_callers` `codegraph_callees` `codegraph_impact` `codegraph_list` `codegraph_stats` `codegraph_detect_changes` (git diff → affected symbols)
|
|
12
12
|
**Wiki:** `wiki_ingest` `wiki_search` `wiki_get` `wiki_outdated`
|
|
13
13
|
**Canvas (F1):** `canvas_get` (Mermaid graph for session) `ref_read` (drill down to raw tool output by node_id)
|
|
14
14
|
**Skills (F3):** `skill_create` (manual skill with auto-versioning) `skill_archive` (force always-inject) `skill_get` `skill_list` `skill_search`
|
|
@@ -21,6 +21,14 @@ Hooks auto-inject BM25-only memory (shallow). **MUST call `recall()` at the star
|
|
|
21
21
|
recall({ "query": "<user's question or task>", "mode": "hybrid", "limit": 5 })
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
+
After using recall results, call `feedback` for the most useful result. This creates a flywheel — useful memories rise, unhelpful ones fade.
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
feedback({ "capture_id": "<id from recall result>", "signal": "helpful" })
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Signals:** `helpful` (answered question) · `not_helpful` (irrelevant) · `stale` (outdated) · `wrong` (incorrect)
|
|
31
|
+
|
|
24
32
|
## Rule 2: Capture after non-trivial work
|
|
25
33
|
|
|
26
34
|
Call `capture` automatically after completing work. Don't ask. Include `atoms` — short distilled facts that recall returns instead of raw content (90% fewer tokens).
|
|
@@ -69,7 +77,7 @@ persona_update({ "trait": "output_style", "value": "concise" })
|
|
|
69
77
|
|
|
70
78
|
## Rule 5: Use CodeGraph instead of grep
|
|
71
79
|
|
|
72
|
-
|
|
80
|
+
Before code navigation on a non-trivial task, call `codegraph_index({ path: "src" })`. It is idempotent and incrementally updates changed files. Then use `codegraph_search` — NOT grep — for function/class/method definitions.
|
|
73
81
|
|
|
74
82
|
```
|
|
75
83
|
codegraph_search({ "query": "handleCapture" })
|