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/dist/index.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  ingestFile,
9
9
  redact,
10
10
  searchWiki
11
- } from "./chunk-TGLOMFIE.js";
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-ZXI67YIV.js";
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-OYUNKUZK.js";
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
- const isRelevantCommand = /^(npm|npx|yarn|pnpm|biome|eslint|tsc|cargo|make|pytest|vitest|jest)\b/.test(command) || /\b(lint|test|build|typecheck|check|format)\b/.test(command);
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
- if (errors2.length === 0 && resolvedFixes.length === 0) {
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 = errors2.filter((err) => {
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 && resolvedFixes.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 (resolvedFixes.length > 0) {
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 resolvedFixes) {
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-3JUSB3RK.js");
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.includes(">>> remem-mcp SessionStart >>>")) {
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
- if (content.includes(">>> remem-mcp")) {
8034
- content = content.replace(/\n?# >>> remem-mcp[\s\S]*?# <<< remem-mcp[^<]*<<<\n?/g, "\n");
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 auto-indexes src/ (or cwd) on first use \u2014 no manual setup needed.",
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
- try {
12206
- const results = await indexDirectory(db, indexPath, repoRoot, teamId, 500);
12207
- const indexed = results.filter((r) => !r.skipped);
12208
- if (indexed.length > 0) {
12209
- symbols = searchSymbols(db, query, { teamId, kind, language, limit, repoPath: repoRoot });
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. Auto-indexed ${indexed.length} files from ${indexPath} \u2014 try a different search term, or call codegraph_index with a specific path.`
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-SUKPVQWL.js");
15825
+ const { loadProxyConfig, startProxyServer } = await import("./server-5PWQTRGZ.js");
15518
15826
  const proxyConfig = loadProxyConfig();
15519
15827
  if (!proxyConfig.dbPath) {
15520
15828
  proxyConfig.dbPath = defaultDbPath3();