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
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
ingestFile,
|
|
9
9
|
redact,
|
|
10
10
|
searchWiki
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-ITMR6THY.js";
|
|
12
12
|
import {
|
|
13
13
|
engine_exports,
|
|
14
14
|
findCallees,
|
|
@@ -19,14 +19,14 @@ import {
|
|
|
19
19
|
init_engine,
|
|
20
20
|
listSymbols,
|
|
21
21
|
searchSymbols
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-YU7WBJU5.js";
|
|
23
23
|
import {
|
|
24
24
|
LocalEmbedder,
|
|
25
25
|
SQLiteBackend,
|
|
26
26
|
init_local,
|
|
27
27
|
local_exports,
|
|
28
28
|
stripQueryProperNouns
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-4WCDAEPP.js";
|
|
30
30
|
import {
|
|
31
31
|
generateId,
|
|
32
32
|
init_ulid
|
|
@@ -5954,8 +5954,7 @@ function hookPreToolUse(dbPath) {
|
|
|
5954
5954
|
}
|
|
5955
5955
|
return;
|
|
5956
5956
|
}
|
|
5957
|
-
|
|
5958
|
-
if (!isRelevantCommand) {
|
|
5957
|
+
if (!isRelevantBuildCommand(command) && !isDecisionLearningCommand(command)) {
|
|
5959
5958
|
if (contextBlocks.length > 0) {
|
|
5960
5959
|
process.stdout.write(JSON.stringify({
|
|
5961
5960
|
hookSpecificOutput: {
|
|
@@ -6032,7 +6031,8 @@ function hookPreToolUse(dbPath) {
|
|
|
6032
6031
|
provenance: m.fix_provenance ?? "auto_captured",
|
|
6033
6032
|
inherited: r.session_key !== sessionKey,
|
|
6034
6033
|
rollbackPlan: m.rollback_plan ?? null,
|
|
6035
|
-
autoNotes: m.auto_notes ?? []
|
|
6034
|
+
autoNotes: m.auto_notes ?? [],
|
|
6035
|
+
failedCommand: m.command ?? ""
|
|
6036
6036
|
};
|
|
6037
6037
|
});
|
|
6038
6038
|
if (resolvedFixes.length < 2) {
|
|
@@ -6058,14 +6058,20 @@ function hookPreToolUse(dbPath) {
|
|
|
6058
6058
|
provenance: "inherited",
|
|
6059
6059
|
inherited: true,
|
|
6060
6060
|
rollbackPlan: m.rollback_plan ?? null,
|
|
6061
|
-
autoNotes: m.auto_notes ?? []
|
|
6061
|
+
autoNotes: m.auto_notes ?? [],
|
|
6062
|
+
failedCommand: m.command ?? ""
|
|
6062
6063
|
});
|
|
6063
6064
|
}
|
|
6064
6065
|
}
|
|
6065
6066
|
rDb.close();
|
|
6066
6067
|
} catch {
|
|
6067
6068
|
}
|
|
6068
|
-
|
|
6069
|
+
const relevantErrors = errors2.filter((err) => {
|
|
6070
|
+
const meta = err.metadata ? JSON.parse(err.metadata) : {};
|
|
6071
|
+
return isErrorRelevantToCommand(command, meta);
|
|
6072
|
+
});
|
|
6073
|
+
const relevantFixes = resolvedFixes;
|
|
6074
|
+
if (relevantErrors.length === 0 && relevantFixes.length === 0) {
|
|
6069
6075
|
try {
|
|
6070
6076
|
const decDb = new Database8(dbPath, { readonly: true });
|
|
6071
6077
|
const lowerCmd = command.toLowerCase();
|
|
@@ -6154,13 +6160,13 @@ function hookPreToolUse(dbPath) {
|
|
|
6154
6160
|
process.stdout.write(JSON.stringify({}));
|
|
6155
6161
|
return;
|
|
6156
6162
|
}
|
|
6157
|
-
const validErrors =
|
|
6163
|
+
const validErrors = relevantErrors.filter((err) => {
|
|
6158
6164
|
const meta = err.metadata ? JSON.parse(err.metadata) : {};
|
|
6159
6165
|
const filesInError = extractFilePaths(err.content);
|
|
6160
6166
|
if (filesInError.length === 0) return true;
|
|
6161
6167
|
return filesInError.some((f) => existsSync8(join9(cwd, f)));
|
|
6162
6168
|
});
|
|
6163
|
-
if (validErrors.length === 0 &&
|
|
6169
|
+
if (validErrors.length === 0 && relevantFixes.length === 0) {
|
|
6164
6170
|
logToFile("PreToolUse: all past errors are stale and no proven fixes");
|
|
6165
6171
|
process.stdout.write(JSON.stringify({}));
|
|
6166
6172
|
return;
|
|
@@ -6199,12 +6205,12 @@ function hookPreToolUse(dbPath) {
|
|
|
6199
6205
|
}
|
|
6200
6206
|
}
|
|
6201
6207
|
}
|
|
6202
|
-
if (
|
|
6208
|
+
if (relevantFixes.length > 0) {
|
|
6203
6209
|
lines.push("");
|
|
6204
6210
|
lines.push("Proven fixes from past resolved errors:");
|
|
6205
6211
|
const stalenessDays = Number(process.env.REMEM_FIX_STALENESS_DAYS ?? 180);
|
|
6206
6212
|
const stalenessMs = stalenessDays * 864e5;
|
|
6207
|
-
for (const fix of
|
|
6213
|
+
for (const fix of relevantFixes) {
|
|
6208
6214
|
const fixAgeMs = Date.now() - new Date(fix.resolvedAt).getTime();
|
|
6209
6215
|
const isStale = fixAgeMs > stalenessMs;
|
|
6210
6216
|
const staleTag = isStale ? " [STALE \u2014 verify before applying]" : "";
|
|
@@ -6417,6 +6423,98 @@ function isNoiseCommand(command) {
|
|
|
6417
6423
|
if (/git\s+diff\s+.*--exit-code/.test(lower)) return true;
|
|
6418
6424
|
return false;
|
|
6419
6425
|
}
|
|
6426
|
+
var COMMAND_INTENTS = {
|
|
6427
|
+
biome: "check",
|
|
6428
|
+
build: "build",
|
|
6429
|
+
cargo: "build",
|
|
6430
|
+
check: "check",
|
|
6431
|
+
eslint: "lint",
|
|
6432
|
+
format: "format",
|
|
6433
|
+
jest: "test",
|
|
6434
|
+
lint: "lint",
|
|
6435
|
+
make: "build",
|
|
6436
|
+
mvn: "build",
|
|
6437
|
+
mypy: "typecheck",
|
|
6438
|
+
pytest: "test",
|
|
6439
|
+
ruff: "lint",
|
|
6440
|
+
tsc: "typecheck",
|
|
6441
|
+
typecheck: "typecheck",
|
|
6442
|
+
vitest: "test"
|
|
6443
|
+
};
|
|
6444
|
+
function getCommandIntent(command) {
|
|
6445
|
+
for (const segment of command.split(/&&|\|\||[;|]/)) {
|
|
6446
|
+
let part = segment.trim();
|
|
6447
|
+
if (!part) continue;
|
|
6448
|
+
while (/^[A-Za-z_][A-Za-z0-9_]*=(?:"[^"]*"|'[^']*'|\S*)\s+/.test(part)) {
|
|
6449
|
+
part = part.replace(/^[A-Za-z_][A-Za-z0-9_]*=(?:"[^"]*"|'[^']*'|\S*)\s+/, "");
|
|
6450
|
+
}
|
|
6451
|
+
const tokens = part.split(/\s+/).filter(Boolean);
|
|
6452
|
+
if (tokens.length === 0) continue;
|
|
6453
|
+
while (tokens[0] === "sudo" || tokens[0] === "time") tokens.shift();
|
|
6454
|
+
const executable = (tokens[0] ?? "").split("/").pop() ?? "";
|
|
6455
|
+
if (["cd", "pushd", "popd"].includes(executable)) continue;
|
|
6456
|
+
if (["npm", "pnpm", "yarn"].includes(executable)) {
|
|
6457
|
+
const args = tokens.slice(1);
|
|
6458
|
+
const runAt = args.findIndex((arg) => arg === "run" || arg === "exec");
|
|
6459
|
+
const action = runAt >= 0 ? args[runAt + 1] : args.find((arg) => !arg.startsWith("-"));
|
|
6460
|
+
if (action && COMMAND_INTENTS[action]) return COMMAND_INTENTS[action];
|
|
6461
|
+
continue;
|
|
6462
|
+
}
|
|
6463
|
+
if (executable === "python" && tokens[1] === "-m") {
|
|
6464
|
+
const module = (tokens[2] ?? "").split("/").pop() ?? "";
|
|
6465
|
+
if (COMMAND_INTENTS[module]) return COMMAND_INTENTS[module];
|
|
6466
|
+
continue;
|
|
6467
|
+
}
|
|
6468
|
+
if (COMMAND_INTENTS[executable]) return COMMAND_INTENTS[executable];
|
|
6469
|
+
if (["cargo", "dotnet", "go", "gradle", "mvn"].includes(executable)) {
|
|
6470
|
+
const action = (tokens[1] ?? "").toLowerCase();
|
|
6471
|
+
if (action === "test") return "test";
|
|
6472
|
+
if (action === "fmt") return "format";
|
|
6473
|
+
if (["build", "vet", "clippy"].includes(action)) return "check";
|
|
6474
|
+
}
|
|
6475
|
+
}
|
|
6476
|
+
return null;
|
|
6477
|
+
}
|
|
6478
|
+
function isRelevantBuildCommand(command) {
|
|
6479
|
+
return getCommandIntent(command) !== null;
|
|
6480
|
+
}
|
|
6481
|
+
function isDecisionLearningCommand(command) {
|
|
6482
|
+
return /\b(?:npm|pnpm|yarn)\s+(?:install|i|add)\b/.test(command) || /\b(?:pip|uv)\s+install\b/.test(command) || /\bcargo\s+add\b/.test(command) || /\bgit\s+commit\b/.test(command);
|
|
6483
|
+
}
|
|
6484
|
+
function isErrorRelevantToCommand(command, meta) {
|
|
6485
|
+
const intent = getCommandIntent(command);
|
|
6486
|
+
if (!intent) return false;
|
|
6487
|
+
const failedCommand = typeof meta.command === "string" ? meta.command : "";
|
|
6488
|
+
if (failedCommand) {
|
|
6489
|
+
if (getCommandIntent(failedCommand) === intent) return true;
|
|
6490
|
+
const genericWords = /* @__PURE__ */ new Set([
|
|
6491
|
+
"and",
|
|
6492
|
+
"error",
|
|
6493
|
+
"failed",
|
|
6494
|
+
"false",
|
|
6495
|
+
"npm",
|
|
6496
|
+
"npx",
|
|
6497
|
+
"pnpm",
|
|
6498
|
+
"run",
|
|
6499
|
+
"the",
|
|
6500
|
+
"true",
|
|
6501
|
+
"yarn"
|
|
6502
|
+
]);
|
|
6503
|
+
const words = (text) => new Set(
|
|
6504
|
+
text.toLowerCase().split(/[^a-z0-9@/_-]+/).filter(
|
|
6505
|
+
(word) => word.length >= 3 && !genericWords.has(word)
|
|
6506
|
+
)
|
|
6507
|
+
);
|
|
6508
|
+
const currentWords = words(command);
|
|
6509
|
+
const failedWords = words(failedCommand);
|
|
6510
|
+
for (const word of currentWords) {
|
|
6511
|
+
if (failedWords.has(word)) return true;
|
|
6512
|
+
}
|
|
6513
|
+
return false;
|
|
6514
|
+
}
|
|
6515
|
+
const errorType = typeof meta.error_type === "string" ? meta.error_type.toLowerCase() : "";
|
|
6516
|
+
return !errorType || errorType === intent || intent === "check";
|
|
6517
|
+
}
|
|
6420
6518
|
function checkDangerousCommand(command) {
|
|
6421
6519
|
const lower = command.toLowerCase();
|
|
6422
6520
|
if (/git\s+push\s+(--force|-f)(?!\S)/.test(lower)) {
|
|
@@ -7137,7 +7235,7 @@ async function hookPostCommit(dbPath) {
|
|
|
7137
7235
|
const { join: join17, dirname: dirname11 } = await import("path");
|
|
7138
7236
|
const { fileURLToPath: fileURLToPath5 } = await import("url");
|
|
7139
7237
|
const sqliteVec5 = await import("sqlite-vec");
|
|
7140
|
-
const { indexFile: indexFile2 } = await import("./engine-
|
|
7238
|
+
const { indexFile: indexFile2 } = await import("./engine-S47HHGAO.js");
|
|
7141
7239
|
const db = new Database8(dbPath);
|
|
7142
7240
|
ensureSchema2(db);
|
|
7143
7241
|
try {
|
|
@@ -7685,6 +7783,53 @@ function hookCommand(subcommand) {
|
|
|
7685
7783
|
}
|
|
7686
7784
|
return `npx --prefer-offline -y remem-mcp ${subcommand}`;
|
|
7687
7785
|
}
|
|
7786
|
+
var CODEX_HOOK_SUBCOMMANDS = [
|
|
7787
|
+
"hook-recall",
|
|
7788
|
+
"hook-pre-tool-use",
|
|
7789
|
+
"hook-post-tool-use",
|
|
7790
|
+
"hook-post-compaction",
|
|
7791
|
+
"hook-user-prompt",
|
|
7792
|
+
"hook-stop",
|
|
7793
|
+
"hook-session-end"
|
|
7794
|
+
];
|
|
7795
|
+
function hasCodexRememHooks(content) {
|
|
7796
|
+
return CODEX_HOOK_SUBCOMMANDS.some((subcommand) => content.includes(` ${subcommand}"`));
|
|
7797
|
+
}
|
|
7798
|
+
function removeCodexHooksFromToml(content) {
|
|
7799
|
+
const lines = content.split(/\r?\n/);
|
|
7800
|
+
const output2 = [];
|
|
7801
|
+
let index = 0;
|
|
7802
|
+
while (index < lines.length) {
|
|
7803
|
+
const match = lines[index].match(/^\[\[hooks\.([A-Za-z]+)\]\]\s*$/);
|
|
7804
|
+
if (!match) {
|
|
7805
|
+
output2.push(lines[index]);
|
|
7806
|
+
index++;
|
|
7807
|
+
continue;
|
|
7808
|
+
}
|
|
7809
|
+
const event = match[1];
|
|
7810
|
+
const nestedPrefix = `[[hooks.${event}.hooks]]`;
|
|
7811
|
+
let end = index + 1;
|
|
7812
|
+
while (end < lines.length) {
|
|
7813
|
+
const line2 = lines[end];
|
|
7814
|
+
if (line2.startsWith(nestedPrefix)) {
|
|
7815
|
+
end++;
|
|
7816
|
+
continue;
|
|
7817
|
+
}
|
|
7818
|
+
if (/^\[./.test(line2)) break;
|
|
7819
|
+
end++;
|
|
7820
|
+
}
|
|
7821
|
+
const block = lines.slice(index, end);
|
|
7822
|
+
if (CODEX_HOOK_SUBCOMMANDS.some((subcommand) => block.includes(` ${subcommand}"`))) {
|
|
7823
|
+
while (output2.length > 0 && (output2[output2.length - 1].trim() === "" || /^\s*#/.test(output2[output2.length - 1]) && output2[output2.length - 1].includes("remem-mcp"))) {
|
|
7824
|
+
output2.pop();
|
|
7825
|
+
}
|
|
7826
|
+
} else {
|
|
7827
|
+
output2.push(...block);
|
|
7828
|
+
}
|
|
7829
|
+
index = end;
|
|
7830
|
+
}
|
|
7831
|
+
return output2.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd();
|
|
7832
|
+
}
|
|
7688
7833
|
var HOOKS_CONFIG = {
|
|
7689
7834
|
SessionStart: [
|
|
7690
7835
|
{
|
|
@@ -7860,7 +8005,7 @@ function installCodexHooks() {
|
|
|
7860
8005
|
return false;
|
|
7861
8006
|
}
|
|
7862
8007
|
let content = readFileSync7(configPath, "utf-8");
|
|
7863
|
-
if (content
|
|
8008
|
+
if (hasCodexRememHooks(content)) {
|
|
7864
8009
|
console.log(` Codex CLI: Hooks already installed in ${configPath}`);
|
|
7865
8010
|
return true;
|
|
7866
8011
|
}
|
|
@@ -8030,8 +8175,9 @@ async function uninstallHooks() {
|
|
|
8030
8175
|
const codexPath = join10(homedir8(), ".codex", "config.toml");
|
|
8031
8176
|
if (existsSync9(codexPath)) {
|
|
8032
8177
|
let content = readFileSync7(codexPath, "utf-8");
|
|
8033
|
-
|
|
8034
|
-
|
|
8178
|
+
const cleaned = removeCodexHooksFromToml(content);
|
|
8179
|
+
if (cleaned !== content) {
|
|
8180
|
+
content = cleaned;
|
|
8035
8181
|
writeFileSync6(codexPath, content.trimEnd() + "\n", "utf-8");
|
|
8036
8182
|
console.log(` Codex CLI: Hooks removed from ${codexPath}`);
|
|
8037
8183
|
removed++;
|
|
@@ -8587,8 +8733,8 @@ var AuditLogger = class {
|
|
|
8587
8733
|
// src/server.ts
|
|
8588
8734
|
init_esm_shims();
|
|
8589
8735
|
init_engine();
|
|
8590
|
-
import { createHash as createHash5 } from "crypto";
|
|
8591
8736
|
import { execSync as execSync3 } from "child_process";
|
|
8737
|
+
import { createHash as createHash5 } from "crypto";
|
|
8592
8738
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
8593
8739
|
import {
|
|
8594
8740
|
CallToolRequestSchema,
|
|
@@ -8651,6 +8797,7 @@ function formatResults(results) {
|
|
|
8651
8797
|
|
|
8652
8798
|
// src/server.ts
|
|
8653
8799
|
init_ulid();
|
|
8800
|
+
var indexingStatus = /* @__PURE__ */ new Map();
|
|
8654
8801
|
function defaultSessionKey() {
|
|
8655
8802
|
if (process.env.REMEM_SESSION_KEY) return process.env.REMEM_SESSION_KEY;
|
|
8656
8803
|
const cwd = process.cwd();
|
|
@@ -8838,6 +8985,29 @@ var TOOLS = [
|
|
|
8838
8985
|
}
|
|
8839
8986
|
}
|
|
8840
8987
|
},
|
|
8988
|
+
{
|
|
8989
|
+
name: "feedback",
|
|
8990
|
+
description: "Record a quality signal for a recalled memory. Call this AFTER using a recall result: use 'helpful' if the memory answered your question, 'not_helpful' if it was irrelevant, 'stale' if the information is outdated, or 'wrong' if it is incorrect. This creates a feedback flywheel \u2014 useful memories rise, unhelpful ones fade. Do not call this for memories you have not actually used.",
|
|
8991
|
+
inputSchema: {
|
|
8992
|
+
type: "object",
|
|
8993
|
+
properties: {
|
|
8994
|
+
capture_id: {
|
|
8995
|
+
type: "string",
|
|
8996
|
+
description: "The capture ID from a recall/search result."
|
|
8997
|
+
},
|
|
8998
|
+
signal: {
|
|
8999
|
+
type: "string",
|
|
9000
|
+
enum: ["helpful", "not_helpful", "stale", "wrong"],
|
|
9001
|
+
description: "helpful = memory was useful (salience +0.1, max 2.0). not_helpful = memory was irrelevant (salience -0.1, min 0.1). stale = information is outdated (salience floored at 0.3). wrong = information is incorrect (salience floored at 0.1)."
|
|
9002
|
+
},
|
|
9003
|
+
reason: {
|
|
9004
|
+
type: "string",
|
|
9005
|
+
description: "Optional explanation for the feedback signal."
|
|
9006
|
+
}
|
|
9007
|
+
},
|
|
9008
|
+
required: ["capture_id", "signal"]
|
|
9009
|
+
}
|
|
9010
|
+
},
|
|
8841
9011
|
{
|
|
8842
9012
|
name: "search",
|
|
8843
9013
|
description: "Search memory by keyword or by semantic similarity. Use this tool when recall is too broad and you need specific facts. Automatically searches both project and global cross-project memory when REMEM_GLOBAL_SESSION_KEY is configured.",
|
|
@@ -8881,6 +9051,11 @@ var TOOLS = [
|
|
|
8881
9051
|
enum: ["text", "json"],
|
|
8882
9052
|
default: "text",
|
|
8883
9053
|
description: "The response format. Use 'json' for structured data (e.g. benchmarks). Defaults to 'text'."
|
|
9054
|
+
},
|
|
9055
|
+
explain: {
|
|
9056
|
+
type: "boolean",
|
|
9057
|
+
default: false,
|
|
9058
|
+
description: "When true, attach per-hit score_details explaining ranking (BM25 rank, vector score, entity matches, authority multiplier, feedback salience, link provenance). Useful for debugging search quality."
|
|
8884
9059
|
}
|
|
8885
9060
|
},
|
|
8886
9061
|
required: ["query"]
|
|
@@ -9274,7 +9449,7 @@ var TOOLS = [
|
|
|
9274
9449
|
},
|
|
9275
9450
|
{
|
|
9276
9451
|
name: "codegraph_search",
|
|
9277
|
-
description: "Search for code symbols by name. Returns matching functions, classes, methods, etc. with file paths and line numbers. Use this INSTEAD of grep when looking for function/class/method definitions. If the codebase hasn't been indexed yet, this
|
|
9452
|
+
description: "Search for code symbols by name. Returns matching functions, classes, methods, etc. with file paths and line numbers. Use this INSTEAD of grep when looking for function/class/method definitions. If the codebase hasn't been indexed yet, this starts background indexing and asks you to retry in a few seconds \u2014 non-blocking, so you can continue working while indexing runs.",
|
|
9278
9453
|
inputSchema: {
|
|
9279
9454
|
type: "object",
|
|
9280
9455
|
properties: {
|
|
@@ -9379,6 +9554,20 @@ var TOOLS = [
|
|
|
9379
9554
|
required: ["repo_path"]
|
|
9380
9555
|
}
|
|
9381
9556
|
},
|
|
9557
|
+
{
|
|
9558
|
+
name: "codegraph_stats",
|
|
9559
|
+
description: "Return indexed CodeGraph statistics: total symbols, calls, resolved calls, imports, and file counts. Optionally scoped to a repo_path. Use this to check indexing coverage before searching.",
|
|
9560
|
+
inputSchema: {
|
|
9561
|
+
type: "object",
|
|
9562
|
+
properties: {
|
|
9563
|
+
repo_path: {
|
|
9564
|
+
type: "string",
|
|
9565
|
+
description: "Optional: scope stats to this repository root path."
|
|
9566
|
+
},
|
|
9567
|
+
team_id: { type: "string", description: "The team ID for isolation." }
|
|
9568
|
+
}
|
|
9569
|
+
}
|
|
9570
|
+
},
|
|
9382
9571
|
// ─── Wiki tools ───
|
|
9383
9572
|
{
|
|
9384
9573
|
name: "wiki_ingest",
|
|
@@ -9876,6 +10065,8 @@ function createServer(opts) {
|
|
|
9876
10065
|
return handleRecall(args, opts);
|
|
9877
10066
|
case "capture":
|
|
9878
10067
|
return handleCapture(args, opts);
|
|
10068
|
+
case "feedback":
|
|
10069
|
+
return handleFeedback(args, opts);
|
|
9879
10070
|
case "search":
|
|
9880
10071
|
return handleSearch(args, opts);
|
|
9881
10072
|
case "related":
|
|
@@ -9922,6 +10113,8 @@ function createServer(opts) {
|
|
|
9922
10113
|
return handleCodegraphList(args, opts);
|
|
9923
10114
|
case "codegraph_detect_changes":
|
|
9924
10115
|
return handleCodegraphDetectChanges(args, opts);
|
|
10116
|
+
case "codegraph_stats":
|
|
10117
|
+
return handleCodegraphStats(args, opts);
|
|
9925
10118
|
case "wiki_ingest":
|
|
9926
10119
|
return handleWikiIngest(args, opts);
|
|
9927
10120
|
case "wiki_search":
|
|
@@ -9979,6 +10172,12 @@ function extractTenant(args) {
|
|
|
9979
10172
|
taskId: args.task_id
|
|
9980
10173
|
};
|
|
9981
10174
|
}
|
|
10175
|
+
function textResult(text) {
|
|
10176
|
+
return { content: [{ type: "text", text }] };
|
|
10177
|
+
}
|
|
10178
|
+
function errorResult(text) {
|
|
10179
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
10180
|
+
}
|
|
9982
10181
|
async function handleRecall(args, opts) {
|
|
9983
10182
|
const query = args.query;
|
|
9984
10183
|
const sessionKey = args.session_key ?? defaultSessionKey();
|
|
@@ -10139,6 +10338,27 @@ ${text}`;
|
|
|
10139
10338
|
});
|
|
10140
10339
|
return { content: [{ type: "text", text: finalText }] };
|
|
10141
10340
|
}
|
|
10341
|
+
async function handleFeedback(args, opts) {
|
|
10342
|
+
const captureId = String(args.capture_id ?? "");
|
|
10343
|
+
const signal = String(args.signal ?? "");
|
|
10344
|
+
const reason = args.reason ? String(args.reason) : void 0;
|
|
10345
|
+
if (!captureId || !signal) {
|
|
10346
|
+
return errorResult("capture_id and signal are required");
|
|
10347
|
+
}
|
|
10348
|
+
const validSignals = ["helpful", "not_helpful", "stale", "wrong"];
|
|
10349
|
+
if (!validSignals.includes(signal)) {
|
|
10350
|
+
return errorResult(`signal must be one of: ${validSignals.join(", ")}`);
|
|
10351
|
+
}
|
|
10352
|
+
const storage = opts.storage;
|
|
10353
|
+
const entry = await storage.get(captureId);
|
|
10354
|
+
if (!entry) {
|
|
10355
|
+
return errorResult(`capture not found: ${captureId}`);
|
|
10356
|
+
}
|
|
10357
|
+
storage.recordFeedback(captureId, signal, reason, opts.agentId ?? detectAgentId());
|
|
10358
|
+
return textResult(
|
|
10359
|
+
`Recorded feedback: ${signal} for capture ${captureId.slice(-8)}. Salience multiplier adjusted. This memory will ${signal === "helpful" ? "rank higher" : signal === "not_helpful" ? "rank lower" : signal === "stale" ? "be deprioritized as outdated" : "be deprioritized as incorrect"} in future searches.`
|
|
10360
|
+
);
|
|
10361
|
+
}
|
|
10142
10362
|
async function handleCapture(args, opts) {
|
|
10143
10363
|
const type2 = args.type ?? "conversation";
|
|
10144
10364
|
const tags = args.tags ?? [];
|
|
@@ -10441,6 +10661,7 @@ async function handleSearch(args, opts) {
|
|
|
10441
10661
|
const query = args.query;
|
|
10442
10662
|
const mode = args.mode ?? "hybrid";
|
|
10443
10663
|
const format = args.format ?? "text";
|
|
10664
|
+
const explain = args.explain ?? false;
|
|
10444
10665
|
const filters = args.filters;
|
|
10445
10666
|
const limit = Math.min(args.limit ?? 20, 500);
|
|
10446
10667
|
let queryEmbedding = null;
|
|
@@ -10476,7 +10697,8 @@ async function handleSearch(args, opts) {
|
|
|
10476
10697
|
limit: projectLimit,
|
|
10477
10698
|
offset: 0,
|
|
10478
10699
|
mode,
|
|
10479
|
-
filters: searchFilters
|
|
10700
|
+
filters: searchFilters,
|
|
10701
|
+
explain
|
|
10480
10702
|
});
|
|
10481
10703
|
let globalResults = [];
|
|
10482
10704
|
try {
|
|
@@ -10485,7 +10707,8 @@ async function handleSearch(args, opts) {
|
|
|
10485
10707
|
limit: reservedGlobal,
|
|
10486
10708
|
offset: 0,
|
|
10487
10709
|
mode,
|
|
10488
|
-
filters: searchFilters
|
|
10710
|
+
filters: searchFilters,
|
|
10711
|
+
explain
|
|
10489
10712
|
});
|
|
10490
10713
|
} catch {
|
|
10491
10714
|
}
|
|
@@ -10498,7 +10721,8 @@ async function handleSearch(args, opts) {
|
|
|
10498
10721
|
limit,
|
|
10499
10722
|
offset: 0,
|
|
10500
10723
|
mode,
|
|
10501
|
-
filters: searchFilters
|
|
10724
|
+
filters: searchFilters,
|
|
10725
|
+
explain
|
|
10502
10726
|
});
|
|
10503
10727
|
}
|
|
10504
10728
|
results = dedupByContentPrefix(results, 60);
|
|
@@ -11262,6 +11486,7 @@ async function handleConfirm(args, opts) {
|
|
|
11262
11486
|
}
|
|
11263
11487
|
try {
|
|
11264
11488
|
db.prepare("UPDATE captures SET confirmations = confirmations + 1 WHERE id = ?").run(captureId);
|
|
11489
|
+
opts.storage.recordFeedback(captureId, "helpful", "auto: confirmed by agent", opts.agentId ?? detectAgentId());
|
|
11265
11490
|
} catch (err) {
|
|
11266
11491
|
return { content: [{ type: "text", text: `Error: ${err}` }], isError: true };
|
|
11267
11492
|
}
|
|
@@ -11272,7 +11497,7 @@ async function handleConfirm(args, opts) {
|
|
|
11272
11497
|
content: [
|
|
11273
11498
|
{
|
|
11274
11499
|
type: "text",
|
|
11275
|
-
text: `Confirmed capture ${captureId}. Confidence: ${confidence.toFixed(2)} (${row.confirmations + 1} confirmations, ${row.corrections} corrections).`
|
|
11500
|
+
text: `Confirmed capture ${captureId}. Confidence: ${confidence.toFixed(2)} (${row.confirmations + 1} confirmations, ${row.corrections} corrections). Feedback salience boosted.`
|
|
11276
11501
|
}
|
|
11277
11502
|
]
|
|
11278
11503
|
};
|
|
@@ -11301,6 +11526,7 @@ async function handleCorrect(args, opts) {
|
|
|
11301
11526
|
if (reason) {
|
|
11302
11527
|
db.prepare("UPDATE captures SET rejection_reason = ? WHERE id = ?").run(reason, captureId);
|
|
11303
11528
|
}
|
|
11529
|
+
opts.storage.recordFeedback(captureId, "wrong", reason ? `auto: ${reason}` : "auto: corrected by agent", opts.agentId ?? detectAgentId());
|
|
11304
11530
|
} catch (err) {
|
|
11305
11531
|
return { content: [{ type: "text", text: `Error: ${err}` }], isError: true };
|
|
11306
11532
|
}
|
|
@@ -12202,31 +12428,70 @@ async function handleCodegraphSearch(args, opts) {
|
|
|
12202
12428
|
indexPath = existsSync17(`${cwd}/src`) ? `${cwd}/src` : cwd;
|
|
12203
12429
|
repoRoot = cwd;
|
|
12204
12430
|
}
|
|
12205
|
-
|
|
12206
|
-
|
|
12207
|
-
|
|
12208
|
-
|
|
12209
|
-
|
|
12210
|
-
|
|
12431
|
+
const statusKey = `${repoRoot}:${teamId ?? "default"}`;
|
|
12432
|
+
const status2 = indexingStatus.get(statusKey);
|
|
12433
|
+
if (!status2 || status2.state === "error") {
|
|
12434
|
+
indexingStatus.set(statusKey, {
|
|
12435
|
+
state: "indexing",
|
|
12436
|
+
startedAt: Date.now()
|
|
12437
|
+
});
|
|
12438
|
+
indexDirectory(db, indexPath, repoRoot, teamId, 1e4).then((results) => {
|
|
12439
|
+
const indexed = results.filter((r) => !r.skipped);
|
|
12440
|
+
const totalSymbols = indexed.reduce((s, r) => s + r.symbols, 0);
|
|
12441
|
+
indexingStatus.set(statusKey, {
|
|
12442
|
+
state: "done",
|
|
12443
|
+
startedAt: status2?.startedAt ?? Date.now(),
|
|
12444
|
+
finishedAt: Date.now(),
|
|
12445
|
+
fileCount: indexed.length,
|
|
12446
|
+
symbolCount: totalSymbols
|
|
12447
|
+
});
|
|
12448
|
+
console.error(
|
|
12449
|
+
`[remem-mcp] background index complete: ${indexed.length} files, ${totalSymbols} symbols from ${indexPath}`
|
|
12450
|
+
);
|
|
12451
|
+
}).catch((err) => {
|
|
12452
|
+
indexingStatus.set(statusKey, {
|
|
12453
|
+
state: "error",
|
|
12454
|
+
startedAt: status2?.startedAt ?? Date.now(),
|
|
12455
|
+
finishedAt: Date.now(),
|
|
12456
|
+
error: String(err)
|
|
12457
|
+
});
|
|
12458
|
+
console.error(`[remem-mcp] background index failed: ${err}`);
|
|
12459
|
+
});
|
|
12460
|
+
return {
|
|
12461
|
+
content: [
|
|
12462
|
+
{
|
|
12463
|
+
type: "text",
|
|
12464
|
+
text: `No symbols found yet. Background indexing started for ${indexPath}.
|
|
12465
|
+
Retry codegraph_search in ~10-30s (large repos may take longer).
|
|
12466
|
+
You can continue working \u2014 indexing runs in the background.`
|
|
12467
|
+
}
|
|
12468
|
+
]
|
|
12469
|
+
};
|
|
12470
|
+
} else if (status2.state === "indexing") {
|
|
12471
|
+
const elapsed = Math.round((Date.now() - status2.startedAt) / 1e3);
|
|
12472
|
+
return {
|
|
12473
|
+
content: [
|
|
12474
|
+
{
|
|
12475
|
+
type: "text",
|
|
12476
|
+
text: `No symbols found yet. Indexing in progress (${elapsed}s elapsed).
|
|
12477
|
+
Retry codegraph_search in a few seconds.
|
|
12478
|
+
Meanwhile, use grep/glob for immediate code navigation.`
|
|
12479
|
+
}
|
|
12480
|
+
]
|
|
12481
|
+
};
|
|
12482
|
+
} else if (status2.state === "done") {
|
|
12483
|
+
symbols = searchSymbols(db, query, { teamId, kind, language, limit, repoPath: repoRoot });
|
|
12211
12484
|
if (symbols.length === 0) {
|
|
12212
12485
|
return {
|
|
12213
12486
|
content: [
|
|
12214
12487
|
{
|
|
12215
12488
|
type: "text",
|
|
12216
|
-
text: `No symbols found
|
|
12489
|
+
text: `No symbols found for "${query}". Indexing completed: ${status2.fileCount} files, ${status2.symbolCount} symbols.
|
|
12490
|
+
Try a different search term, or call codegraph_index with a specific path.`
|
|
12217
12491
|
}
|
|
12218
12492
|
]
|
|
12219
12493
|
};
|
|
12220
12494
|
}
|
|
12221
|
-
} catch {
|
|
12222
|
-
return {
|
|
12223
|
-
content: [
|
|
12224
|
-
{
|
|
12225
|
-
type: "text",
|
|
12226
|
-
text: "No symbols found. Call codegraph_index with a path to index your codebase first."
|
|
12227
|
-
}
|
|
12228
|
-
]
|
|
12229
|
-
};
|
|
12230
12495
|
}
|
|
12231
12496
|
}
|
|
12232
12497
|
const lines = symbols.map(
|
|
@@ -12418,6 +12683,49 @@ function handleCodegraphDetectChanges(args, opts) {
|
|
|
12418
12683
|
content: [{ type: "text", text: lines.join("\n") }]
|
|
12419
12684
|
};
|
|
12420
12685
|
}
|
|
12686
|
+
function handleCodegraphStats(args, opts) {
|
|
12687
|
+
const repoPath = args.repo_path ?? void 0;
|
|
12688
|
+
const teamId = args.team_id ?? void 0;
|
|
12689
|
+
const db = getDb(opts);
|
|
12690
|
+
const repoClause = repoPath ? "AND repo_path IS ?" : "";
|
|
12691
|
+
const repoParams = repoPath ? [repoPath] : [];
|
|
12692
|
+
const teamClause = teamId !== void 0 ? "AND team_id IS ?" : "";
|
|
12693
|
+
const teamParams = teamId !== void 0 ? [teamId] : [];
|
|
12694
|
+
const filter = `${repoClause} ${teamClause}`.trim();
|
|
12695
|
+
const symbolCount = db.prepare(`SELECT COUNT(*) as c FROM symbols WHERE 1=1 ${filter}`).get(...repoParams, ...teamParams);
|
|
12696
|
+
const callCount = db.prepare(
|
|
12697
|
+
`SELECT COUNT(*) as c FROM calls AS c
|
|
12698
|
+
JOIN symbols AS caller ON caller.id = c.caller_id
|
|
12699
|
+
WHERE 1=1 ${filter}`
|
|
12700
|
+
).get(...repoParams, ...teamParams);
|
|
12701
|
+
const resolvedCount = db.prepare(
|
|
12702
|
+
`SELECT COUNT(*) as c FROM calls AS c
|
|
12703
|
+
JOIN symbols AS caller ON caller.id = c.caller_id
|
|
12704
|
+
WHERE c.callee_id IS NOT NULL ${filter}`
|
|
12705
|
+
).get(...repoParams, ...teamParams);
|
|
12706
|
+
const importCount = db.prepare(`SELECT COUNT(*) as c FROM imports WHERE 1=1 ${filter}`).get(...repoParams, ...teamParams);
|
|
12707
|
+
const fileCount = db.prepare(`SELECT COUNT(DISTINCT file_path) as c FROM symbols WHERE 1=1 ${filter}`).get(...repoParams, ...teamParams);
|
|
12708
|
+
const byKind = db.prepare(
|
|
12709
|
+
`SELECT kind, COUNT(*) as c FROM symbols WHERE 1=1 ${filter} GROUP BY kind ORDER BY c DESC`
|
|
12710
|
+
).all(...repoParams, ...teamParams);
|
|
12711
|
+
const lines = [
|
|
12712
|
+
`CodeGraph Statistics${repoPath ? ` (repo: ${repoPath})` : ""}`,
|
|
12713
|
+
` files: ${fileCount.c}`,
|
|
12714
|
+
` symbols: ${symbolCount.c}`,
|
|
12715
|
+
` calls: ${callCount.c} (${resolvedCount.c} resolved, ${callCount.c - resolvedCount.c} unresolved)`,
|
|
12716
|
+
` imports: ${importCount.c}`,
|
|
12717
|
+
""
|
|
12718
|
+
];
|
|
12719
|
+
if (byKind.length > 0) {
|
|
12720
|
+
lines.push("By kind:");
|
|
12721
|
+
for (const k of byKind) {
|
|
12722
|
+
lines.push(` ${k.kind.padEnd(12)} ${k.c}`);
|
|
12723
|
+
}
|
|
12724
|
+
}
|
|
12725
|
+
return {
|
|
12726
|
+
content: [{ type: "text", text: lines.join("\n") }]
|
|
12727
|
+
};
|
|
12728
|
+
}
|
|
12421
12729
|
async function handleWikiIngest(args, opts) {
|
|
12422
12730
|
const path = args.path;
|
|
12423
12731
|
const repoPath = args.repo_path ?? path;
|
|
@@ -15514,7 +15822,7 @@ ${sorted.length} unique tag(s).`);
|
|
|
15514
15822
|
return;
|
|
15515
15823
|
}
|
|
15516
15824
|
if (arg === "proxy") {
|
|
15517
|
-
const { loadProxyConfig, startProxyServer } = await import("./server-
|
|
15825
|
+
const { loadProxyConfig, startProxyServer } = await import("./server-5PWQTRGZ.js");
|
|
15518
15826
|
const proxyConfig = loadProxyConfig();
|
|
15519
15827
|
if (!proxyConfig.dbPath) {
|
|
15520
15828
|
proxyConfig.dbPath = defaultDbPath3();
|