remem-mcp 0.6.2 → 0.6.4
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 +85 -125
- package/dist/chunk-FA324IBN.js +903 -0
- package/dist/chunk-FA324IBN.js.map +1 -0
- package/dist/{chunk-ULMM6LKD.js → chunk-X4ECGLYH.js} +60 -16
- package/dist/chunk-X4ECGLYH.js.map +1 -0
- package/dist/chunk-XK4Y53J2.js +54 -0
- package/dist/chunk-XK4Y53J2.js.map +1 -0
- package/dist/{engine-EIOBO7ZE.js → engine-LVKGZHBY.js} +5 -2
- package/dist/index.js +1234 -185
- package/dist/index.js.map +1 -1
- package/dist/resolver-AVK7XKX7.js +300 -0
- package/dist/resolver-AVK7XKX7.js.map +1 -0
- package/dist/sdk.js +3 -2
- package/dist/storage/schema.sql +6 -2
- package/package.json +1 -2
- package/scripts/dogfood-claude-cli.sh +156 -0
- package/scripts/dogfood-codex.sh +145 -0
- package/skills/remem-mcp/SKILL.md +45 -91
- package/dist/chunk-SHLLMMMU.js +0 -480
- package/dist/chunk-SHLLMMMU.js.map +0 -1
- package/dist/chunk-ULMM6LKD.js.map +0 -1
- /package/dist/{engine-EIOBO7ZE.js.map → engine-LVKGZHBY.js.map} +0 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__esm,
|
|
3
|
+
init_esm_shims
|
|
4
|
+
} from "./chunk-XK4Y53J2.js";
|
|
5
|
+
|
|
6
|
+
// src/codegraph/resolver.ts
|
|
7
|
+
function resolveCall(calleeName, callerFilePath, registry, importMap) {
|
|
8
|
+
const parts = calleeName.split(".");
|
|
9
|
+
if (parts.length >= 2) {
|
|
10
|
+
const prefix = parts[0];
|
|
11
|
+
const suffix = parts.slice(1).join(".");
|
|
12
|
+
const modulePath = importMap.lookup(callerFilePath, prefix);
|
|
13
|
+
if (modulePath) {
|
|
14
|
+
const qualifiedName = `${modulePath}/${suffix}`;
|
|
15
|
+
const sym = registry.exactLookup(qualifiedName);
|
|
16
|
+
if (sym) {
|
|
17
|
+
return { calleeId: sym.id, confidence: 0.95, strategy: "import-map" };
|
|
18
|
+
}
|
|
19
|
+
const fullQualified = `${modulePath}/${calleeName}`;
|
|
20
|
+
const symFull = registry.exactLookup(fullQualified);
|
|
21
|
+
if (symFull) {
|
|
22
|
+
return { calleeId: symFull.id, confidence: 0.95, strategy: "import-map" };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (parts.length >= 2) {
|
|
27
|
+
const prefix = parts[0];
|
|
28
|
+
const suffix = parts[parts.length - 1];
|
|
29
|
+
const modulePath = importMap.lookup(callerFilePath, prefix);
|
|
30
|
+
if (modulePath) {
|
|
31
|
+
const candidates2 = registry.reverseLookup(suffix);
|
|
32
|
+
for (const c of candidates2) {
|
|
33
|
+
const cModule = registry.getModuleForFile(c.file_path);
|
|
34
|
+
if (cModule === modulePath || cModule.endsWith(`/${modulePath}`)) {
|
|
35
|
+
return { calleeId: c.id, confidence: 0.85, strategy: "import-map-suffix" };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const callerModule = registry.getModuleForFile(callerFilePath);
|
|
41
|
+
const sameModuleQualified = `${callerModule}/${calleeName}`;
|
|
42
|
+
const symSame = registry.exactLookup(sameModuleQualified);
|
|
43
|
+
if (symSame) {
|
|
44
|
+
return { calleeId: symSame.id, confidence: 0.9, strategy: "same-module" };
|
|
45
|
+
}
|
|
46
|
+
const sameModuleSimple = registry.reverseLookup(calleeName).find(
|
|
47
|
+
(s) => registry.getModuleForFile(s.file_path) === callerModule
|
|
48
|
+
);
|
|
49
|
+
if (sameModuleSimple) {
|
|
50
|
+
return { calleeId: sameModuleSimple.id, confidence: 0.9, strategy: "same-module" };
|
|
51
|
+
}
|
|
52
|
+
const candidates = registry.reverseLookup(calleeName);
|
|
53
|
+
if (candidates.length === 1) {
|
|
54
|
+
return { calleeId: candidates[0].id, confidence: 0.75, strategy: "unique-name" };
|
|
55
|
+
}
|
|
56
|
+
if (candidates.length > 1) {
|
|
57
|
+
const fileImports = importMap.getImportsForFile(callerFilePath);
|
|
58
|
+
if (fileImports) {
|
|
59
|
+
for (const c of candidates) {
|
|
60
|
+
const cModule = registry.getModuleForFile(c.file_path);
|
|
61
|
+
for (const [, impModule] of fileImports) {
|
|
62
|
+
if (cModule === impModule || cModule.startsWith(`${impModule}/`)) {
|
|
63
|
+
return { calleeId: c.id, confidence: 0.55, strategy: "suffix-match" };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const callerDir = callerFilePath.slice(0, callerFilePath.lastIndexOf("/"));
|
|
69
|
+
let best = candidates[0];
|
|
70
|
+
let bestDist = Infinity;
|
|
71
|
+
for (const c of candidates) {
|
|
72
|
+
const cDir = c.file_path.slice(0, c.file_path.lastIndexOf("/"));
|
|
73
|
+
const dist = pathDistance(callerDir, cDir);
|
|
74
|
+
if (dist < bestDist) {
|
|
75
|
+
bestDist = dist;
|
|
76
|
+
best = c;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return { calleeId: best.id, confidence: 0.55, strategy: "suffix-match" };
|
|
80
|
+
}
|
|
81
|
+
const fuzzyMatch = fuzzyLookup(calleeName, registry);
|
|
82
|
+
if (fuzzyMatch) {
|
|
83
|
+
return { calleeId: fuzzyMatch.id, confidence: 0.35, strategy: "fuzzy" };
|
|
84
|
+
}
|
|
85
|
+
return { calleeId: null, confidence: 0, strategy: "unresolved" };
|
|
86
|
+
}
|
|
87
|
+
function shouldSkipResolution(calleeName) {
|
|
88
|
+
if (calleeName.length <= 2) return true;
|
|
89
|
+
if (SKIP_NAMES.has(calleeName)) return true;
|
|
90
|
+
const parts = calleeName.split(".");
|
|
91
|
+
if (parts.length >= 2 && parts[0].length <= 1) return true;
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
function resolveAllCalls(db) {
|
|
95
|
+
try {
|
|
96
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_calls_callee_name ON calls(callee_name)");
|
|
97
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_symbols_name ON symbols(name)");
|
|
98
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_symbols_module ON symbols(module_path)");
|
|
99
|
+
} catch {
|
|
100
|
+
}
|
|
101
|
+
const symbols = db.prepare("SELECT id, name, kind, file_path, module_path, language, parent_id FROM symbols").all();
|
|
102
|
+
const registry = new FunctionRegistry(symbols);
|
|
103
|
+
const imports = db.prepare("SELECT file_path, symbol_name, source_path FROM imports").all();
|
|
104
|
+
const importMap = new ImportMap(imports);
|
|
105
|
+
const calls = db.prepare("SELECT id, caller_id, callee_name, callee_id, line, call_type FROM calls WHERE callee_id IS NULL").all();
|
|
106
|
+
const stats = { total: calls.length, resolved: 0, byStrategy: {} };
|
|
107
|
+
let skipped = 0;
|
|
108
|
+
const callerFiles = /* @__PURE__ */ new Map();
|
|
109
|
+
const callerIds = [...new Set(calls.map((c) => c.caller_id))];
|
|
110
|
+
if (callerIds.length > 0) {
|
|
111
|
+
for (let i = 0; i < callerIds.length; i += 500) {
|
|
112
|
+
const chunk = callerIds.slice(i, i + 500);
|
|
113
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
114
|
+
const rows = db.prepare(`SELECT id, file_path FROM symbols WHERE id IN (${placeholders})`).all(...chunk);
|
|
115
|
+
for (const r of rows) {
|
|
116
|
+
callerFiles.set(r.id, r.file_path);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const updateStmt = db.prepare(
|
|
121
|
+
"UPDATE calls SET callee_id = ?, confidence = ? WHERE id = ?"
|
|
122
|
+
);
|
|
123
|
+
const batch = db.transaction(() => {
|
|
124
|
+
for (const call of calls) {
|
|
125
|
+
if (shouldSkipResolution(call.callee_name)) {
|
|
126
|
+
skipped++;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const callerFile = callerFiles.get(call.caller_id) ?? "";
|
|
130
|
+
const result = resolveCall(call.callee_name, callerFile, registry, importMap);
|
|
131
|
+
if (result.calleeId) {
|
|
132
|
+
updateStmt.run(result.calleeId, result.confidence, call.id);
|
|
133
|
+
stats.resolved++;
|
|
134
|
+
stats.byStrategy[result.strategy] = (stats.byStrategy[result.strategy] ?? 0) + 1;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
batch();
|
|
139
|
+
if (skipped > 0) {
|
|
140
|
+
console.error(`[remem-mcp] resolveAllCalls: skipped ${skipped} short/builtin names`);
|
|
141
|
+
}
|
|
142
|
+
return stats;
|
|
143
|
+
}
|
|
144
|
+
function deriveModulePath(filePath) {
|
|
145
|
+
return filePath.replace(/\.(ts|tsx|js|jsx|mjs|cjs|py|go|rs|java|c|cpp|cc|cxx|hpp|cs)$/, "");
|
|
146
|
+
}
|
|
147
|
+
function pathDistance(a, b) {
|
|
148
|
+
if (a === b) return 0;
|
|
149
|
+
const aParts = a.split("/");
|
|
150
|
+
const bParts = b.split("/");
|
|
151
|
+
let common = 0;
|
|
152
|
+
for (let i = 0; i < Math.min(aParts.length, bParts.length); i++) {
|
|
153
|
+
if (aParts[i] === bParts[i]) common++;
|
|
154
|
+
else break;
|
|
155
|
+
}
|
|
156
|
+
return aParts.length + bParts.length - 2 * common;
|
|
157
|
+
}
|
|
158
|
+
function fuzzyLookup(name, registry) {
|
|
159
|
+
let best = null;
|
|
160
|
+
let bestScore = 0;
|
|
161
|
+
for (const [symName, candidates] of registry.reverseLookupEntries()) {
|
|
162
|
+
const score = similarity(name, symName);
|
|
163
|
+
if (score > bestScore && score > 0.6) {
|
|
164
|
+
bestScore = score;
|
|
165
|
+
best = candidates[0];
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return best;
|
|
169
|
+
}
|
|
170
|
+
function similarity(a, b) {
|
|
171
|
+
if (a === b) return 1;
|
|
172
|
+
const aBigrams = bigrams(a);
|
|
173
|
+
const bBigrams = bigrams(b);
|
|
174
|
+
const intersection = aBigrams.filter((bg) => bBigrams.includes(bg)).length;
|
|
175
|
+
const union = aBigrams.length + bBigrams.length - intersection;
|
|
176
|
+
return union > 0 ? intersection / union : 0;
|
|
177
|
+
}
|
|
178
|
+
function bigrams(s) {
|
|
179
|
+
const result = [];
|
|
180
|
+
for (let i = 0; i < s.length - 1; i++) {
|
|
181
|
+
result.push(s.slice(i, i + 2));
|
|
182
|
+
}
|
|
183
|
+
return result;
|
|
184
|
+
}
|
|
185
|
+
var FunctionRegistry, ImportMap, SKIP_NAMES;
|
|
186
|
+
var init_resolver = __esm({
|
|
187
|
+
"src/codegraph/resolver.ts"() {
|
|
188
|
+
init_esm_shims();
|
|
189
|
+
FunctionRegistry = class {
|
|
190
|
+
exactMap = /* @__PURE__ */ new Map();
|
|
191
|
+
reverseIndex = /* @__PURE__ */ new Map();
|
|
192
|
+
fileModules = /* @__PURE__ */ new Map();
|
|
193
|
+
// file_path → module_path
|
|
194
|
+
constructor(symbols) {
|
|
195
|
+
for (const sym of symbols) {
|
|
196
|
+
const modulePath = sym.module_path ?? deriveModulePath(sym.file_path);
|
|
197
|
+
this.fileModules.set(sym.file_path, modulePath);
|
|
198
|
+
const qualifiedName = `${modulePath}/${sym.name}`;
|
|
199
|
+
this.exactMap.set(qualifiedName, sym);
|
|
200
|
+
this.exactMap.set(`${sym.file_path}/${sym.name}`, sym);
|
|
201
|
+
const existing = this.reverseIndex.get(sym.name);
|
|
202
|
+
if (existing) {
|
|
203
|
+
existing.push(sym);
|
|
204
|
+
} else {
|
|
205
|
+
this.reverseIndex.set(sym.name, [sym]);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/** Look up by qualified name (module_path/name). */
|
|
210
|
+
exactLookup(qualifiedName) {
|
|
211
|
+
return this.exactMap.get(qualifiedName);
|
|
212
|
+
}
|
|
213
|
+
/** Look up by simple name — returns all candidates. */
|
|
214
|
+
reverseLookup(simpleName) {
|
|
215
|
+
return this.reverseIndex.get(simpleName) ?? [];
|
|
216
|
+
}
|
|
217
|
+
/** Get all reverse index entries (for fuzzy lookup). */
|
|
218
|
+
reverseLookupEntries() {
|
|
219
|
+
return this.reverseIndex.entries();
|
|
220
|
+
}
|
|
221
|
+
/** Get module_path for a file. */
|
|
222
|
+
getModuleForFile(filePath) {
|
|
223
|
+
return this.fileModules.get(filePath) ?? deriveModulePath(filePath);
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
ImportMap = class {
|
|
227
|
+
fileImports = /* @__PURE__ */ new Map();
|
|
228
|
+
constructor(imports) {
|
|
229
|
+
for (const imp of imports) {
|
|
230
|
+
if (!imp.source_path) continue;
|
|
231
|
+
let fileMap = this.fileImports.get(imp.file_path);
|
|
232
|
+
if (!fileMap) {
|
|
233
|
+
fileMap = /* @__PURE__ */ new Map();
|
|
234
|
+
this.fileImports.set(imp.file_path, fileMap);
|
|
235
|
+
}
|
|
236
|
+
const prefix = imp.symbol_name.split(".")[0] ?? imp.symbol_name;
|
|
237
|
+
const modulePath = deriveModulePath(imp.source_path);
|
|
238
|
+
fileMap.set(prefix, modulePath);
|
|
239
|
+
fileMap.set(imp.symbol_name, modulePath);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
/** Look up module path for an import prefix in a given file. */
|
|
243
|
+
lookup(filePath, prefix) {
|
|
244
|
+
return this.fileImports.get(filePath)?.get(prefix);
|
|
245
|
+
}
|
|
246
|
+
/** Get all imports for a file. */
|
|
247
|
+
getImportsForFile(filePath) {
|
|
248
|
+
return this.fileImports.get(filePath);
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
SKIP_NAMES = /* @__PURE__ */ new Set([
|
|
252
|
+
"T",
|
|
253
|
+
"L",
|
|
254
|
+
"R",
|
|
255
|
+
"S",
|
|
256
|
+
"P",
|
|
257
|
+
"C",
|
|
258
|
+
"D",
|
|
259
|
+
"E",
|
|
260
|
+
"F",
|
|
261
|
+
"M",
|
|
262
|
+
"N",
|
|
263
|
+
"V",
|
|
264
|
+
"X",
|
|
265
|
+
"Y",
|
|
266
|
+
"String",
|
|
267
|
+
"Array",
|
|
268
|
+
"Object",
|
|
269
|
+
"Number",
|
|
270
|
+
"Boolean",
|
|
271
|
+
"Map",
|
|
272
|
+
"Set",
|
|
273
|
+
"Date",
|
|
274
|
+
"Error",
|
|
275
|
+
"RegExp",
|
|
276
|
+
"Promise",
|
|
277
|
+
"Symbol",
|
|
278
|
+
"Buffer",
|
|
279
|
+
"Vec",
|
|
280
|
+
"Box",
|
|
281
|
+
"Option",
|
|
282
|
+
"Result",
|
|
283
|
+
"Some",
|
|
284
|
+
"None",
|
|
285
|
+
"Ok",
|
|
286
|
+
"Err",
|
|
287
|
+
"List",
|
|
288
|
+
"Dict",
|
|
289
|
+
"Tuple"
|
|
290
|
+
]);
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
init_resolver();
|
|
294
|
+
export {
|
|
295
|
+
FunctionRegistry,
|
|
296
|
+
ImportMap,
|
|
297
|
+
resolveAllCalls,
|
|
298
|
+
resolveCall
|
|
299
|
+
};
|
|
300
|
+
//# sourceMappingURL=resolver-AVK7XKX7.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): 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.90, strategy: \"same-module\" };\n }\n // Also try just the simple name in same module\n const sameModuleSimple = registry.reverseLookup(calleeName).find(\n (s) => registry.getModuleForFile(s.file_path) === callerModule,\n );\n if (sameModuleSimple) {\n return { calleeId: sameModuleSimple.id, confidence: 0.90, 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\n const fuzzyMatch = fuzzyLookup(calleeName, registry);\n if (fuzzyMatch) {\n return { calleeId: fuzzyMatch.id, confidence: 0.35, strategy: \"fuzzy\" };\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\", \"L\", \"R\", \"S\", \"P\", \"C\", \"D\", \"E\", \"F\", \"M\", \"N\", \"V\", \"X\", \"Y\",\n \"String\", \"Array\", \"Object\", \"Number\", \"Boolean\", \"Map\", \"Set\", \"Date\",\n \"Error\", \"RegExp\", \"Promise\", \"Symbol\", \"Buffer\", \"Vec\", \"Box\", \"Option\",\n \"Result\", \"Some\", \"None\", \"Ok\", \"Err\", \"List\", \"Dict\", \"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 all unresolved calls in the database.\n * Called after indexDirectory() completes.\n */\nexport function resolveAllCalls(db: Database): {\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 all symbols into FunctionRegistry\n const symbols = db\n .prepare(\"SELECT id, name, kind, file_path, module_path, language, parent_id FROM symbols\")\n .all() as SymbolRow[];\n const registry = new FunctionRegistry(symbols);\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 all unresolved calls — skip short/builtin names to save resolution time\n const calls = db\n .prepare(\"SELECT id, caller_id, callee_name, callee_id, line, call_type FROM calls WHERE callee_id IS NULL\")\n .all() as CallRow[];\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 callerIds = [...new Set(calls.map((c) => c.caller_id))];\n if (callerIds.length > 0) {\n // Batch in chunks of 500 to avoid SQLite param limit\n for (let i = 0; i < callerIds.length; i += 500) {\n const chunk = callerIds.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(\n \"UPDATE calls SET callee_id = ?, confidence = ? WHERE id = ?\",\n );\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);\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":"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","names":["candidates"]}
|
package/dist/sdk.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
ingestDirectory,
|
|
9
9
|
ingestFile,
|
|
10
10
|
searchWiki
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-X4ECGLYH.js";
|
|
12
12
|
import {
|
|
13
13
|
SUPPORTED_LANGUAGES,
|
|
14
14
|
detectLanguage,
|
|
@@ -19,7 +19,8 @@ import {
|
|
|
19
19
|
indexFile,
|
|
20
20
|
listSymbols,
|
|
21
21
|
searchSymbols
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-FA324IBN.js";
|
|
23
|
+
import "./chunk-XK4Y53J2.js";
|
|
23
24
|
export {
|
|
24
25
|
LocalEmbedder,
|
|
25
26
|
Memory,
|
package/dist/storage/schema.sql
CHANGED
|
@@ -53,7 +53,8 @@ CREATE TABLE IF NOT EXISTS messages (
|
|
|
53
53
|
created_at INTEGER NOT NULL
|
|
54
54
|
);
|
|
55
55
|
|
|
56
|
-
-- L1: Atomic facts (populated by atom-extract pipeline
|
|
56
|
+
-- L1: Atomic facts (phase 2 — populated by atom-extract pipeline or CLI extract command.
|
|
57
|
+
-- Currently written by the extract CLI subcommand; NoopPipeline does not populate this.)
|
|
57
58
|
CREATE TABLE IF NOT EXISTS atoms (
|
|
58
59
|
id TEXT PRIMARY KEY,
|
|
59
60
|
capture_id TEXT NOT NULL REFERENCES captures(id) ON DELETE CASCADE,
|
|
@@ -65,7 +66,7 @@ CREATE TABLE IF NOT EXISTS atoms (
|
|
|
65
66
|
user_id TEXT
|
|
66
67
|
);
|
|
67
68
|
|
|
68
|
-
-- L2: Scenario blocks (populated by scenario pipeline)
|
|
69
|
+
-- L2: Scenario blocks (phase 2 — populated by scenario pipeline, not yet implemented)
|
|
69
70
|
CREATE TABLE IF NOT EXISTS scenarios (
|
|
70
71
|
id TEXT PRIMARY KEY,
|
|
71
72
|
atom_ids TEXT NOT NULL,
|
|
@@ -179,6 +180,7 @@ CREATE TABLE IF NOT EXISTS symbols (
|
|
|
179
180
|
team_id TEXT,
|
|
180
181
|
repo_path TEXT, -- root path of the indexed repo
|
|
181
182
|
content_hash TEXT, -- hash of the symbol body for change detection
|
|
183
|
+
module_path TEXT, -- qualified module path (e.g. src/storage/sqlite) for call resolution
|
|
182
184
|
created_at INTEGER NOT NULL,
|
|
183
185
|
updated_at INTEGER NOT NULL
|
|
184
186
|
);
|
|
@@ -191,6 +193,8 @@ CREATE TABLE IF NOT EXISTS calls (
|
|
|
191
193
|
callee_id TEXT REFERENCES symbols(id), -- resolved callee (null if unresolved)
|
|
192
194
|
line INTEGER NOT NULL, -- line where the call occurs
|
|
193
195
|
kind TEXT NOT NULL DEFAULT 'call', -- call, import, reference
|
|
196
|
+
confidence REAL, -- resolution confidence (0.0-1.0, null if unresolved)
|
|
197
|
+
call_type TEXT NOT NULL DEFAULT 'direct', -- direct, method, qualified, constructor
|
|
194
198
|
team_id TEXT
|
|
195
199
|
);
|
|
196
200
|
|
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.6.
|
|
4
|
+
"version": "0.6.4",
|
|
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",
|
|
@@ -68,7 +68,6 @@
|
|
|
68
68
|
"better-sqlite3": "^13.0.3",
|
|
69
69
|
"gpt-tokenizer": "^3.4.0",
|
|
70
70
|
"sqlite-vec": "^0.1.9",
|
|
71
|
-
"tree-sitter": "^0.25.1",
|
|
72
71
|
"ulid": "^3.0.2"
|
|
73
72
|
},
|
|
74
73
|
"devDependencies": {
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# dogfood-claude-cli.sh — dogfood remem-mcp using Claude CLI as the agent
|
|
3
|
+
# Claude CLI gets remem-mcp as its MCP server, then works on remem-mcp's own codebase.
|
|
4
|
+
# Memory persists across iterations — Claude recalls past errors/fixes from previous runs.
|
|
5
|
+
#
|
|
6
|
+
# Usage: bash scripts/dogfood-claude-cli.sh [max_iterations] [model]
|
|
7
|
+
# Example: bash scripts/dogfood-claude-cli.sh 3 sonnet
|
|
8
|
+
|
|
9
|
+
set -euo pipefail
|
|
10
|
+
cd "$(dirname "$0")/.."
|
|
11
|
+
|
|
12
|
+
MAX_ITER="${1:-3}"
|
|
13
|
+
MODEL="${2:-sonnet}"
|
|
14
|
+
REPO_DIR="$(pwd)"
|
|
15
|
+
TMP_DIR="/tmp/remem-dogfood"
|
|
16
|
+
mkdir -p "$TMP_DIR"
|
|
17
|
+
|
|
18
|
+
# MCP config — point Claude CLI at the locally built remem-mcp
|
|
19
|
+
MCP_CONFIG="$TMP_DIR/mcp-config.json"
|
|
20
|
+
cat > "$MCP_CONFIG" <<EOF
|
|
21
|
+
{
|
|
22
|
+
"mcpServers": {
|
|
23
|
+
"remem-mcp": {
|
|
24
|
+
"command": "node",
|
|
25
|
+
"args": ["$REPO_DIR/dist/index.js"],
|
|
26
|
+
"env": {
|
|
27
|
+
"REMEM_DB_PATH": "$HOME/.local/share/remem-mcp/memory.db",
|
|
28
|
+
"REMEM_GLOBAL_SESSION_KEY": "global",
|
|
29
|
+
"REMEM_AUTO_GLOBAL": "true"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
EOF
|
|
35
|
+
|
|
36
|
+
echo "══════════════════════════════════════════════════════════════"
|
|
37
|
+
echo " 🐕 DOGFOOD WITH CLAUDE CLI"
|
|
38
|
+
echo " Repo: $REPO_DIR"
|
|
39
|
+
echo " MCP: $MCP_CONFIG"
|
|
40
|
+
echo " Model: $MODEL"
|
|
41
|
+
echo " Iterations: $MAX_ITER"
|
|
42
|
+
echo "══════════════════════════════════════════════════════════════"
|
|
43
|
+
echo ""
|
|
44
|
+
|
|
45
|
+
# Build first
|
|
46
|
+
echo "[build] Compiling..."
|
|
47
|
+
npm run build 2>&1 | tail -1
|
|
48
|
+
echo ""
|
|
49
|
+
|
|
50
|
+
ITER=0
|
|
51
|
+
while [ "$ITER" -lt "$MAX_ITER" ]; do
|
|
52
|
+
ITER=$((ITER + 1))
|
|
53
|
+
echo ""
|
|
54
|
+
echo "────────────────────────────────────────────────────────────"
|
|
55
|
+
echo " ITERATION $ITER/$MAX_ITER"
|
|
56
|
+
echo "────────────────────────────────────────────────────────────"
|
|
57
|
+
|
|
58
|
+
# 1. Snapshot current state
|
|
59
|
+
echo ""
|
|
60
|
+
echo "[1/4] State snapshot:"
|
|
61
|
+
TS_ERRS=$(npx tsc --noEmit 2>&1 | grep -c "error TS" | tr -d '[:space:]' || echo "0")
|
|
62
|
+
TEST_OUT=$(npx vitest run 2>&1 || true)
|
|
63
|
+
TEST_FAILS=$(echo "$TEST_OUT" | grep -oE "[0-9]+ failed" | head -1 | grep -oE "[0-9]+" | tr -d '[:space:]' || echo "0")
|
|
64
|
+
TEST_PASSES=$(echo "$TEST_OUT" | grep -oE "[0-9]+ passed" | head -1 | grep -oE "[0-9]+" | tr -d '[:space:]' || echo "0")
|
|
65
|
+
[ -z "$TS_ERRS" ] && TS_ERRS=0
|
|
66
|
+
[ -z "$TEST_FAILS" ] && TEST_FAILS=0
|
|
67
|
+
[ -z "$TEST_PASSES" ] && TEST_PASSES=0
|
|
68
|
+
echo " TS errors: $TS_ERRS | Tests: $TEST_PASSES passed, $TEST_FAILS failed"
|
|
69
|
+
|
|
70
|
+
# 2. Capture errors to feed Claude context
|
|
71
|
+
ERR_FILE="$TMP_DIR/errors-iter-$ITER.txt"
|
|
72
|
+
{
|
|
73
|
+
echo "## Build errors (iteration $ITER)"
|
|
74
|
+
npm run build 2>&1 | grep -i "error\|fail" | head -20 || echo "none"
|
|
75
|
+
echo ""
|
|
76
|
+
echo "## TypeScript errors"
|
|
77
|
+
npx tsc --noEmit 2>&1 | grep "error TS" | head -20 || echo "none"
|
|
78
|
+
echo ""
|
|
79
|
+
echo "## Test failures"
|
|
80
|
+
echo "$TEST_OUT" | grep -B2 "FAIL\|AssertionError\|Error:" | head -30 || echo "none"
|
|
81
|
+
} > "$ERR_FILE" 2>&1
|
|
82
|
+
|
|
83
|
+
ERR_COUNT=$(grep -c "error\|fail\|FAIL" "$ERR_FILE" || echo "0")
|
|
84
|
+
echo " Error file: $ERR_FILE ($ERR_COUNT lines with errors)"
|
|
85
|
+
|
|
86
|
+
# 3. Run Claude CLI with remem-mcp as MCP server
|
|
87
|
+
echo ""
|
|
88
|
+
echo "[2/4] Launching Claude CLI (model=$MODEL)..."
|
|
89
|
+
|
|
90
|
+
PROMPT="You are working on the remem-mcp codebase (a local MCP memory server).
|
|
91
|
+
|
|
92
|
+
Current state:
|
|
93
|
+
- TypeScript errors: $TS_ERRS
|
|
94
|
+
- Test failures: $TEST_FAILS (out of $TEST_PASSES passed)
|
|
95
|
+
|
|
96
|
+
Error details from this iteration:
|
|
97
|
+
$(cat "$ERR_FILE")
|
|
98
|
+
|
|
99
|
+
Your tasks:
|
|
100
|
+
1. Use recall to check if we've seen these errors before and how we fixed them.
|
|
101
|
+
2. Fix the TypeScript errors and test failures shown above.
|
|
102
|
+
3. After fixing, run \`npm run build\` and \`npx vitest run\` to verify.
|
|
103
|
+
4. Use capture to store what you fixed and how, so future sessions can recall it.
|
|
104
|
+
5. If everything passes, look for edge cases or potential bugs in the codebase and fix them.
|
|
105
|
+
|
|
106
|
+
Focus on real fixes, not workarounds. Check src/ for the relevant code."
|
|
107
|
+
|
|
108
|
+
claude -p "$PROMPT" \
|
|
109
|
+
--model "$MODEL" \
|
|
110
|
+
--mcp-config "$MCP_CONFIG" \
|
|
111
|
+
--add-dir "$REPO_DIR" \
|
|
112
|
+
--allowedTools "Bash(npm *),Bash(npx *),Bash(node *),Bash(git *),Bash(cat *),Bash(grep *),Bash(ls *),Bash(wc *),Bash(head *),Bash(tail *),Edit,Read,Write,Grep,Glob" \
|
|
113
|
+
--output-format text \
|
|
114
|
+
--max-turns 30 \
|
|
115
|
+
2>&1 | tee "$TMP_DIR/claude-output-iter-$ITER.log" || true
|
|
116
|
+
|
|
117
|
+
echo ""
|
|
118
|
+
echo "[3/4] Claude CLI finished. Output saved to $TMP_DIR/claude-output-iter-$ITER.log"
|
|
119
|
+
|
|
120
|
+
# 4. Re-check state after Claude's fixes
|
|
121
|
+
echo ""
|
|
122
|
+
echo "[4/4] Post-fix state:"
|
|
123
|
+
TS_ERRS_AFTER=$(npx tsc --noEmit 2>&1 | grep -c "error TS" | tr -d '[:space:]' || echo "0")
|
|
124
|
+
TEST_OUT_AFTER=$(npx vitest run 2>&1 || true)
|
|
125
|
+
TEST_FAILS_AFTER=$(echo "$TEST_OUT_AFTER" | grep -oE "[0-9]+ failed" | head -1 | grep -oE "[0-9]+" | tr -d '[:space:]' || echo "0")
|
|
126
|
+
TEST_PASSES_AFTER=$(echo "$TEST_OUT_AFTER" | grep -oE "[0-9]+ passed" | head -1 | grep -oE "[0-9]+" | tr -d '[:space:]' || echo "0")
|
|
127
|
+
[ -z "$TS_ERRS_AFTER" ] && TS_ERRS_AFTER=0
|
|
128
|
+
[ -z "$TEST_FAILS_AFTER" ] && TEST_FAILS_AFTER=0
|
|
129
|
+
[ -z "$TEST_PASSES_AFTER" ] && TEST_PASSES_AFTER=0
|
|
130
|
+
|
|
131
|
+
echo " TS errors: $TS_ERRS → $TS_ERRS_AFTER"
|
|
132
|
+
echo " Test failures: $TEST_FAILS → $TEST_FAILS_AFTER"
|
|
133
|
+
echo " Tests passed: $TEST_PASSES → $TEST_PASSES_AFTER"
|
|
134
|
+
|
|
135
|
+
# Summary
|
|
136
|
+
echo ""
|
|
137
|
+
echo "┌──────────────────────────────────┐"
|
|
138
|
+
echo "│ Iteration $ITER summary:"
|
|
139
|
+
echo "│ TS errors: $TS_ERRS → $TS_ERRS_AFTER"
|
|
140
|
+
echo "│ Test fails: $TEST_FAILS → $TEST_FAILS_AFTER"
|
|
141
|
+
echo "│ Tests passed: $TEST_PASSES → $TEST_PASSES_AFTER"
|
|
142
|
+
echo "└──────────────────────────────────┘"
|
|
143
|
+
|
|
144
|
+
if [ "$TS_ERRS_AFTER" -eq 0 ] && [ "$TEST_FAILS_AFTER" -eq 0 ]; then
|
|
145
|
+
echo ""
|
|
146
|
+
echo "★★★ ALL GREEN — 0 TS errors, 0 test failures ★★★"
|
|
147
|
+
echo "Claude CLI + remem-mcp dogfood iteration $ITER succeeded."
|
|
148
|
+
break
|
|
149
|
+
fi
|
|
150
|
+
done
|
|
151
|
+
|
|
152
|
+
echo ""
|
|
153
|
+
echo "══════════════════════════════════════════════════════════════"
|
|
154
|
+
echo " DOGFOOD COMPLETE — $ITER iteration(s)"
|
|
155
|
+
echo " Logs: $TMP_DIR/"
|
|
156
|
+
echo "══════════════════════════════════════════════════════════════"
|