@prom.codes/memory-mcp 0.4.1 → 0.5.0

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.
Files changed (2) hide show
  1. package/dist/bin.js +153 -4
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -999,6 +999,79 @@ function delay(ms) {
999
999
  return new Promise((r) => setTimeout(r, ms));
1000
1000
  }
1001
1001
 
1002
+ // dist/rewrite.js
1003
+ var SYSTEM_PROMPT2 = "You help search an AI agent's long-term memory of a software project and its conversations. Given a question, write ONE short hypothetical memory entry (1\u20132 sentences, plain statements) that \u2014 if it existed \u2014 would directly answer it. Use concrete nouns, names and likely phrasings a developer would store. Do NOT answer that you don't know, do NOT add preamble or quotes \u2014 output only the hypothetical statement.";
1004
+ var OpenAICompatRewriter = class {
1005
+ name;
1006
+ model;
1007
+ #url;
1008
+ #apiKey;
1009
+ #maxRetries;
1010
+ #retryBaseMs;
1011
+ #maxTokens;
1012
+ #temperature;
1013
+ #fetch;
1014
+ constructor(opts) {
1015
+ this.name = opts.name;
1016
+ this.model = opts.model;
1017
+ this.#url = `${opts.baseUrl.replace(/\/+$/, "")}/chat/completions`;
1018
+ this.#apiKey = opts.apiKey;
1019
+ this.#maxRetries = opts.maxRetries ?? 2;
1020
+ this.#retryBaseMs = opts.retryBaseMs ?? 400;
1021
+ this.#maxTokens = opts.maxTokens ?? 160;
1022
+ this.#temperature = opts.temperature ?? 0;
1023
+ this.#fetch = opts.fetchImpl ?? fetch;
1024
+ }
1025
+ async rewrite(query, opts) {
1026
+ const trimmed = query.trim();
1027
+ if (trimmed === "")
1028
+ return [];
1029
+ const n = Math.max(1, opts?.n ?? 1);
1030
+ const body = JSON.stringify({
1031
+ model: this.model,
1032
+ temperature: this.#temperature,
1033
+ max_tokens: this.#maxTokens,
1034
+ n,
1035
+ messages: [
1036
+ { role: "system", content: SYSTEM_PROMPT2 },
1037
+ { role: "user", content: `Question: ${trimmed}` }
1038
+ ]
1039
+ });
1040
+ const headers = { "content-type": "application/json" };
1041
+ if (this.#apiKey !== void 0 && this.#apiKey !== "") {
1042
+ headers.authorization = `Bearer ${this.#apiKey}`;
1043
+ }
1044
+ for (let attempt = 0; attempt <= this.#maxRetries; attempt++) {
1045
+ try {
1046
+ const res = await this.#fetch(this.#url, {
1047
+ method: "POST",
1048
+ headers,
1049
+ body,
1050
+ ...opts?.signal ? { signal: opts.signal } : {}
1051
+ });
1052
+ if (res.status === 429 || res.status >= 500) {
1053
+ } else if (!res.ok) {
1054
+ return [];
1055
+ } else {
1056
+ const json = await res.json();
1057
+ const out = [];
1058
+ for (const c of json.choices ?? []) {
1059
+ const t = (c.message?.content ?? "").trim();
1060
+ if (t !== "")
1061
+ out.push(t.slice(0, 1e3));
1062
+ }
1063
+ return out;
1064
+ }
1065
+ } catch {
1066
+ }
1067
+ if (attempt < this.#maxRetries) {
1068
+ await new Promise((r) => setTimeout(r, this.#retryBaseMs * 2 ** attempt));
1069
+ }
1070
+ }
1071
+ return [];
1072
+ }
1073
+ };
1074
+
1002
1075
  // dist/sqlite.js
1003
1076
  import { randomUUID } from "node:crypto";
1004
1077
  import { mkdirSync } from "node:fs";
@@ -1227,6 +1300,7 @@ var SqliteMemoryBackend = class {
1227
1300
  db;
1228
1301
  embedder;
1229
1302
  reranker;
1303
+ rewriter;
1230
1304
  /** Record ids whose vector is missing/stale, awaiting a batched embed. */
1231
1305
  pendingEmbed = /* @__PURE__ */ new Set();
1232
1306
  closed = false;
@@ -1242,6 +1316,7 @@ var SqliteMemoryBackend = class {
1242
1316
  this.db.exec(`INSERT INTO agent_memory_fts (agent_memory_fts) VALUES ('rebuild')`);
1243
1317
  this.embedder = opts.embedder;
1244
1318
  this.reranker = opts.reranker;
1319
+ this.rewriter = opts.rewriter;
1245
1320
  if (this.embedder !== void 0)
1246
1321
  this.queueUnembedded();
1247
1322
  }
@@ -1343,11 +1418,12 @@ var SqliteMemoryBackend = class {
1343
1418
  return [];
1344
1419
  const finalLimit = input.limit ?? 20;
1345
1420
  const poolLimit = Math.max(finalLimit * 4, 40);
1346
- const ftsHits = this.ftsSearch(input, poolLimit);
1421
+ const channelInput = await this.applyRewrite(input);
1422
+ const ftsHits = this.ftsSearch(channelInput, poolLimit);
1347
1423
  let vecHits = [];
1348
1424
  if (this.embedder !== void 0) {
1349
1425
  try {
1350
- vecHits = await this.vectorSearch(input, poolLimit);
1426
+ vecHits = await this.vectorSearch(channelInput, poolLimit);
1351
1427
  } catch {
1352
1428
  vecHits = [];
1353
1429
  }
@@ -1366,6 +1442,24 @@ var SqliteMemoryBackend = class {
1366
1442
  const reranked = input.rerank === false ? pool : await this.rerankPool(input.query, pool, finalLimit);
1367
1443
  return reranked.slice(0, finalLimit);
1368
1444
  }
1445
+ /**
1446
+ * M4: rewrite the query for the retrieval channels (HyDE concat). Returns the
1447
+ * input unchanged when no rewriter is configured or it errors/empties.
1448
+ */
1449
+ async applyRewrite(input) {
1450
+ if (this.rewriter === void 0 || input.rewrite === false)
1451
+ return input;
1452
+ try {
1453
+ const docs = await this.rewriter.rewrite(input.query, { n: 1 });
1454
+ const hyp = docs[0]?.trim();
1455
+ if (hyp)
1456
+ return { ...input, query: `${input.query}
1457
+
1458
+ ${hyp}` };
1459
+ } catch {
1460
+ }
1461
+ return input;
1462
+ }
1369
1463
  /**
1370
1464
  * Reorder a first-stage pool with the cross-encoder reranker, scoring each
1371
1465
  * candidate's `key + value` jointly against the query. Returns the pool
@@ -1752,6 +1846,57 @@ function discoverMemoryExtractor(env) {
1752
1846
  }
1753
1847
  throw new Error(`unknown PROMETHEUS_MEMORY_EXTRACT_PROVIDER="${forced}" (expected "none", "mistral", "openai", or "generic")`);
1754
1848
  }
1849
+ function discoverMemoryRewriter(env) {
1850
+ const forced = (env.PROMETHEUS_MEMORY_REWRITE_PROVIDER ?? "none").toLowerCase();
1851
+ if (forced === "" || forced === "none")
1852
+ return { id: "none", provider: null };
1853
+ if (forced === "mistral") {
1854
+ const apiKey = env.MISTRAL_API_KEY;
1855
+ if (apiKey === void 0 || apiKey === "") {
1856
+ throw new Error('PROMETHEUS_MEMORY_REWRITE_PROVIDER="mistral" requires MISTRAL_API_KEY.');
1857
+ }
1858
+ return {
1859
+ id: "mistral",
1860
+ provider: new OpenAICompatRewriter({
1861
+ name: "mistral-rewrite",
1862
+ model: env.PROMETHEUS_MEMORY_REWRITE_MODEL ?? "mistral-small-latest",
1863
+ baseUrl: env.MISTRAL_BASE_URL ?? "https://api.mistral.ai/v1",
1864
+ apiKey
1865
+ })
1866
+ };
1867
+ }
1868
+ if (forced === "openai") {
1869
+ const apiKey = env.OPENAI_API_KEY;
1870
+ if (apiKey === void 0 || apiKey === "") {
1871
+ throw new Error('PROMETHEUS_MEMORY_REWRITE_PROVIDER="openai" requires OPENAI_API_KEY.');
1872
+ }
1873
+ return {
1874
+ id: "openai",
1875
+ provider: new OpenAICompatRewriter({
1876
+ name: "openai-rewrite",
1877
+ model: env.PROMETHEUS_MEMORY_REWRITE_MODEL ?? "gpt-4o-mini",
1878
+ baseUrl: env.OPENAI_BASE_URL ?? "https://api.openai.com/v1",
1879
+ apiKey
1880
+ })
1881
+ };
1882
+ }
1883
+ if (forced === "generic" || forced === "openai-compat") {
1884
+ const baseUrl = env.PROMETHEUS_MEMORY_REWRITE_ENDPOINT;
1885
+ if (baseUrl === void 0 || baseUrl === "") {
1886
+ throw new Error(`PROMETHEUS_MEMORY_REWRITE_PROVIDER="${forced}" requires PROMETHEUS_MEMORY_REWRITE_ENDPOINT.`);
1887
+ }
1888
+ return {
1889
+ id: "generic",
1890
+ provider: new OpenAICompatRewriter({
1891
+ name: env.PROMETHEUS_MEMORY_REWRITE_NAME ?? "generic-rewrite",
1892
+ model: env.PROMETHEUS_MEMORY_REWRITE_MODEL ?? "default",
1893
+ baseUrl,
1894
+ ...env.PROMETHEUS_MEMORY_REWRITE_API_KEY ? { apiKey: env.PROMETHEUS_MEMORY_REWRITE_API_KEY } : {}
1895
+ })
1896
+ };
1897
+ }
1898
+ throw new Error(`unknown PROMETHEUS_MEMORY_REWRITE_PROVIDER="${forced}" (expected "none", "mistral", "openai", or "generic")`);
1899
+ }
1755
1900
  function composeFromEnv(opts) {
1756
1901
  const env = opts.env;
1757
1902
  const override = (opts.workspaceRootOverride ?? "").trim();
@@ -1765,9 +1910,11 @@ function composeFromEnv(opts) {
1765
1910
  const { id: embedderId, embedder } = discoverMemoryEmbedder(env);
1766
1911
  const { id: rerankerId, provider: reranker } = discoverMemoryReranker(env);
1767
1912
  const { id: extractorId, provider: extractor } = discoverMemoryExtractor(env);
1913
+ const { id: rewriterId, provider: rewriter } = discoverMemoryRewriter(env);
1768
1914
  const backend = new SqliteMemoryBackend(dbPath, {
1769
1915
  ...embedder !== void 0 ? { embedder } : {},
1770
- ...reranker !== null ? { reranker } : {}
1916
+ ...reranker !== null ? { reranker } : {},
1917
+ ...rewriter !== null ? { rewriter } : {}
1771
1918
  });
1772
1919
  return {
1773
1920
  backend,
@@ -1781,6 +1928,8 @@ function composeFromEnv(opts) {
1781
1928
  rerankerId,
1782
1929
  extractor,
1783
1930
  extractorId,
1931
+ rewriter,
1932
+ rewriterId,
1784
1933
  close: () => backend.close()
1785
1934
  };
1786
1935
  }
@@ -2438,7 +2587,7 @@ async function main() {
2438
2587
  env,
2439
2588
  ...override !== void 0 && override !== "" ? { workspaceRootOverride: override } : {}
2440
2589
  });
2441
- process.stderr.write(`prometheus-memory-mcp: workspace=${composed.workspaceRoot} (via ${via}) project=${composed.projectName} (${composed.projectId}) db=${composed.dbPath} embed=${composed.embedderId}${composed.embeddingsEnabled ? "" : " (keyword-only)"} rerank=${composed.rerankerId} extract=${composed.extractorId}
2590
+ process.stderr.write(`prometheus-memory-mcp: workspace=${composed.workspaceRoot} (via ${via}) project=${composed.projectName} (${composed.projectId}) db=${composed.dbPath} embed=${composed.embedderId}${composed.embeddingsEnabled ? "" : " (keyword-only)"} rerank=${composed.rerankerId} extract=${composed.extractorId} rewrite=${composed.rewriterId}
2442
2591
  `);
2443
2592
  registerTools(server, composed);
2444
2593
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prom.codes/memory-mcp",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "prom.codes Memory — persistent, local-first agent memory as an MCP server.",
5
5
  "type": "module",
6
6
  "bin": {