@prom.codes/memory-mcp 0.4.1 → 0.5.1

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 +347 -50
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -4,10 +4,189 @@
4
4
  import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
5
5
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
6
 
7
+ // ../shared/dist/types.js
8
+ var GRAMMAR_LANGUAGE_IDS = [
9
+ "typescript",
10
+ "tsx",
11
+ "javascript",
12
+ "python",
13
+ "php",
14
+ "go",
15
+ "rust",
16
+ "java",
17
+ "csharp",
18
+ "c",
19
+ "cpp",
20
+ "ruby",
21
+ "kotlin",
22
+ "html"
23
+ ];
24
+ var DOCUMENT_LANGUAGE_IDS = [
25
+ "markdown",
26
+ "text",
27
+ "json",
28
+ "yaml",
29
+ "toml"
30
+ ];
31
+ var LANGUAGE_IDS = [
32
+ ...GRAMMAR_LANGUAGE_IDS,
33
+ ...DOCUMENT_LANGUAGE_IDS
34
+ ];
35
+
36
+ // ../shared/dist/update-check.js
37
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
38
+ import { homedir } from "node:os";
39
+ import { join } from "node:path";
40
+ import { fileURLToPath } from "node:url";
41
+ async function packageIdentity(binImportMetaUrl) {
42
+ try {
43
+ const binPath = fileURLToPath(binImportMetaUrl);
44
+ const pkgPath = join(binPath, "..", "..", "package.json");
45
+ const raw = await readFile(pkgPath, "utf8");
46
+ const parsed = JSON.parse(raw);
47
+ if (typeof parsed.name !== "string" || typeof parsed.version !== "string") {
48
+ return null;
49
+ }
50
+ return { name: parsed.name, version: parsed.version };
51
+ } catch {
52
+ return null;
53
+ }
54
+ }
55
+ async function maybeNotifyUpdate(binImportMetaUrl, env = process.env) {
56
+ try {
57
+ const id = await packageIdentity(binImportMetaUrl);
58
+ if (id === null)
59
+ return;
60
+ await checkForUpdate({ name: id.name, version: id.version, env });
61
+ } catch {
62
+ }
63
+ }
64
+ var DEFAULT_TTL_MS = 24 * 60 * 60 * 1e3;
65
+ var DEFAULT_TIMEOUT_MS = 1500;
66
+ var OPT_OUT_RE = /^(1|true|yes|on)$/i;
67
+ function parseSemver(v) {
68
+ const m = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/.exec(v.trim());
69
+ if (m === null)
70
+ return null;
71
+ return {
72
+ core: [Number(m[1]), Number(m[2]), Number(m[3])],
73
+ pre: m[4] ?? null
74
+ };
75
+ }
76
+ function isNewerVersion(latest, current) {
77
+ const a = parseSemver(latest);
78
+ const b = parseSemver(current);
79
+ if (a === null || b === null)
80
+ return false;
81
+ for (let i = 0; i < 3; i++) {
82
+ if (a.core[i] > b.core[i])
83
+ return true;
84
+ if (a.core[i] < b.core[i])
85
+ return false;
86
+ }
87
+ if (a.pre === null && b.pre !== null)
88
+ return true;
89
+ return false;
90
+ }
91
+ function cachePath(dir, name) {
92
+ const safe = name.replace(/[^a-zA-Z0-9._-]+/g, "_");
93
+ return join(dir, `.update-check-${safe}.json`);
94
+ }
95
+ async function readCache(path2) {
96
+ try {
97
+ const raw = await readFile(path2, "utf8");
98
+ const parsed = JSON.parse(raw);
99
+ if (typeof parsed.checkedAt !== "number")
100
+ return null;
101
+ return {
102
+ checkedAt: parsed.checkedAt,
103
+ latest: typeof parsed.latest === "string" ? parsed.latest : null
104
+ };
105
+ } catch {
106
+ return null;
107
+ }
108
+ }
109
+ async function writeCache(path2, data) {
110
+ try {
111
+ await writeFile(path2, JSON.stringify(data), "utf8");
112
+ } catch {
113
+ }
114
+ }
115
+ async function fetchLatest(name, fetchImpl, timeoutMs) {
116
+ const controller = new AbortController();
117
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
118
+ timer.unref?.();
119
+ try {
120
+ const url = `https://registry.npmjs.org/${name.replace("/", "%2F")}/latest`;
121
+ const res = await fetchImpl(url, {
122
+ signal: controller.signal,
123
+ headers: { accept: "application/vnd.npm.install-v1+json" }
124
+ });
125
+ if (!res.ok)
126
+ return null;
127
+ const body = await res.json();
128
+ return typeof body.version === "string" ? body.version : null;
129
+ } catch {
130
+ return null;
131
+ } finally {
132
+ clearTimeout(timer);
133
+ }
134
+ }
135
+ async function checkForUpdate(options) {
136
+ const { name, version, env = process.env, log = (line) => process.stderr.write(line), fetch: fetchImpl = globalThis.fetch, cacheDir = join(homedir(), ".prometheus"), cacheTtlMs = DEFAULT_TTL_MS, timeoutMs = DEFAULT_TIMEOUT_MS, force = false } = options;
137
+ const base = {
138
+ latest: null,
139
+ current: version
140
+ };
141
+ if (OPT_OUT_RE.test(env.PROMETHEUS_NO_UPDATE_CHECK ?? "")) {
142
+ return { ...base, checked: false, updateAvailable: false, reason: "opted-out" };
143
+ }
144
+ if (parseSemver(version) === null || version === "0.0.0") {
145
+ return { ...base, checked: false, updateAvailable: false, reason: "invalid-version" };
146
+ }
147
+ if (typeof fetchImpl !== "function") {
148
+ return { ...base, checked: false, updateAvailable: false, reason: "error" };
149
+ }
150
+ const file = cachePath(cacheDir, name);
151
+ const now = Date.now();
152
+ if (!force) {
153
+ const cached = await readCache(file);
154
+ if (cached !== null && now - cached.checkedAt < cacheTtlMs) {
155
+ const updateAvailable2 = cached.latest !== null && isNewerVersion(cached.latest, version);
156
+ if (updateAvailable2)
157
+ notify(log, name, version, cached.latest);
158
+ return {
159
+ ...base,
160
+ latest: cached.latest,
161
+ checked: false,
162
+ updateAvailable: updateAvailable2,
163
+ reason: "throttled"
164
+ };
165
+ }
166
+ }
167
+ await mkdir(cacheDir, { recursive: true }).catch(() => void 0);
168
+ const latest = await fetchLatest(name, fetchImpl, timeoutMs);
169
+ await writeCache(file, { checkedAt: now, latest });
170
+ if (latest === null) {
171
+ return { ...base, checked: true, updateAvailable: false, reason: "error" };
172
+ }
173
+ const updateAvailable = isNewerVersion(latest, version);
174
+ if (updateAvailable)
175
+ notify(log, name, version, latest);
176
+ return { ...base, latest, checked: true, updateAvailable };
177
+ }
178
+ function notify(log, name, current, latest) {
179
+ log(`${name}: a newer version (${latest}) is available \u2014 you are on ${current}. npx users get it automatically on the next restart; for a global install run \`npm update -g ${name}\`. (Set PROMETHEUS_NO_UPDATE_CHECK=1 to silence.)
180
+ `);
181
+ }
182
+
183
+ // ../shared/dist/index.js
184
+ var PROMETHEUS_VERSION = "0.1.0";
185
+
7
186
  // dist/composition.js
8
187
  import { createHash } from "node:crypto";
9
- import { homedir } from "node:os";
10
- import { basename, join, resolve } from "node:path";
188
+ import { homedir as homedir2 } from "node:os";
189
+ import { basename, join as join2, resolve } from "node:path";
11
190
 
12
191
  // ../embeddings-openai-compat/dist/index.js
13
192
  var DEFAULT_BATCH = 96;
@@ -999,6 +1178,79 @@ function delay(ms) {
999
1178
  return new Promise((r) => setTimeout(r, ms));
1000
1179
  }
1001
1180
 
1181
+ // dist/rewrite.js
1182
+ 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.";
1183
+ var OpenAICompatRewriter = class {
1184
+ name;
1185
+ model;
1186
+ #url;
1187
+ #apiKey;
1188
+ #maxRetries;
1189
+ #retryBaseMs;
1190
+ #maxTokens;
1191
+ #temperature;
1192
+ #fetch;
1193
+ constructor(opts) {
1194
+ this.name = opts.name;
1195
+ this.model = opts.model;
1196
+ this.#url = `${opts.baseUrl.replace(/\/+$/, "")}/chat/completions`;
1197
+ this.#apiKey = opts.apiKey;
1198
+ this.#maxRetries = opts.maxRetries ?? 2;
1199
+ this.#retryBaseMs = opts.retryBaseMs ?? 400;
1200
+ this.#maxTokens = opts.maxTokens ?? 160;
1201
+ this.#temperature = opts.temperature ?? 0;
1202
+ this.#fetch = opts.fetchImpl ?? fetch;
1203
+ }
1204
+ async rewrite(query, opts) {
1205
+ const trimmed = query.trim();
1206
+ if (trimmed === "")
1207
+ return [];
1208
+ const n = Math.max(1, opts?.n ?? 1);
1209
+ const body = JSON.stringify({
1210
+ model: this.model,
1211
+ temperature: this.#temperature,
1212
+ max_tokens: this.#maxTokens,
1213
+ n,
1214
+ messages: [
1215
+ { role: "system", content: SYSTEM_PROMPT2 },
1216
+ { role: "user", content: `Question: ${trimmed}` }
1217
+ ]
1218
+ });
1219
+ const headers = { "content-type": "application/json" };
1220
+ if (this.#apiKey !== void 0 && this.#apiKey !== "") {
1221
+ headers.authorization = `Bearer ${this.#apiKey}`;
1222
+ }
1223
+ for (let attempt = 0; attempt <= this.#maxRetries; attempt++) {
1224
+ try {
1225
+ const res = await this.#fetch(this.#url, {
1226
+ method: "POST",
1227
+ headers,
1228
+ body,
1229
+ ...opts?.signal ? { signal: opts.signal } : {}
1230
+ });
1231
+ if (res.status === 429 || res.status >= 500) {
1232
+ } else if (!res.ok) {
1233
+ return [];
1234
+ } else {
1235
+ const json = await res.json();
1236
+ const out = [];
1237
+ for (const c of json.choices ?? []) {
1238
+ const t = (c.message?.content ?? "").trim();
1239
+ if (t !== "")
1240
+ out.push(t.slice(0, 1e3));
1241
+ }
1242
+ return out;
1243
+ }
1244
+ } catch {
1245
+ }
1246
+ if (attempt < this.#maxRetries) {
1247
+ await new Promise((r) => setTimeout(r, this.#retryBaseMs * 2 ** attempt));
1248
+ }
1249
+ }
1250
+ return [];
1251
+ }
1252
+ };
1253
+
1002
1254
  // dist/sqlite.js
1003
1255
  import { randomUUID } from "node:crypto";
1004
1256
  import { mkdirSync } from "node:fs";
@@ -1227,6 +1479,7 @@ var SqliteMemoryBackend = class {
1227
1479
  db;
1228
1480
  embedder;
1229
1481
  reranker;
1482
+ rewriter;
1230
1483
  /** Record ids whose vector is missing/stale, awaiting a batched embed. */
1231
1484
  pendingEmbed = /* @__PURE__ */ new Set();
1232
1485
  closed = false;
@@ -1242,6 +1495,7 @@ var SqliteMemoryBackend = class {
1242
1495
  this.db.exec(`INSERT INTO agent_memory_fts (agent_memory_fts) VALUES ('rebuild')`);
1243
1496
  this.embedder = opts.embedder;
1244
1497
  this.reranker = opts.reranker;
1498
+ this.rewriter = opts.rewriter;
1245
1499
  if (this.embedder !== void 0)
1246
1500
  this.queueUnembedded();
1247
1501
  }
@@ -1343,11 +1597,12 @@ var SqliteMemoryBackend = class {
1343
1597
  return [];
1344
1598
  const finalLimit = input.limit ?? 20;
1345
1599
  const poolLimit = Math.max(finalLimit * 4, 40);
1346
- const ftsHits = this.ftsSearch(input, poolLimit);
1600
+ const channelInput = await this.applyRewrite(input);
1601
+ const ftsHits = this.ftsSearch(channelInput, poolLimit);
1347
1602
  let vecHits = [];
1348
1603
  if (this.embedder !== void 0) {
1349
1604
  try {
1350
- vecHits = await this.vectorSearch(input, poolLimit);
1605
+ vecHits = await this.vectorSearch(channelInput, poolLimit);
1351
1606
  } catch {
1352
1607
  vecHits = [];
1353
1608
  }
@@ -1366,6 +1621,24 @@ var SqliteMemoryBackend = class {
1366
1621
  const reranked = input.rerank === false ? pool : await this.rerankPool(input.query, pool, finalLimit);
1367
1622
  return reranked.slice(0, finalLimit);
1368
1623
  }
1624
+ /**
1625
+ * M4: rewrite the query for the retrieval channels (HyDE concat). Returns the
1626
+ * input unchanged when no rewriter is configured or it errors/empties.
1627
+ */
1628
+ async applyRewrite(input) {
1629
+ if (this.rewriter === void 0 || input.rewrite === false)
1630
+ return input;
1631
+ try {
1632
+ const docs = await this.rewriter.rewrite(input.query, { n: 1 });
1633
+ const hyp = docs[0]?.trim();
1634
+ if (hyp)
1635
+ return { ...input, query: `${input.query}
1636
+
1637
+ ${hyp}` };
1638
+ } catch {
1639
+ }
1640
+ return input;
1641
+ }
1369
1642
  /**
1370
1643
  * Reorder a first-stage pool with the cross-encoder reranker, scoring each
1371
1644
  * candidate's `key + value` jointly against the query. Returns the pool
@@ -1601,7 +1874,7 @@ function projectIdFor(workspaceRoot) {
1601
1874
  return createHash("sha256").update(abs).digest("hex").slice(0, 16);
1602
1875
  }
1603
1876
  function defaultMemoryDbPath() {
1604
- return join(homedir(), ".prometheus", "memory.db");
1877
+ return join2(homedir2(), ".prometheus", "memory.db");
1605
1878
  }
1606
1879
  function intEnv(env, name, def) {
1607
1880
  const raw = env[name];
@@ -1752,6 +2025,57 @@ function discoverMemoryExtractor(env) {
1752
2025
  }
1753
2026
  throw new Error(`unknown PROMETHEUS_MEMORY_EXTRACT_PROVIDER="${forced}" (expected "none", "mistral", "openai", or "generic")`);
1754
2027
  }
2028
+ function discoverMemoryRewriter(env) {
2029
+ const forced = (env.PROMETHEUS_MEMORY_REWRITE_PROVIDER ?? "none").toLowerCase();
2030
+ if (forced === "" || forced === "none")
2031
+ return { id: "none", provider: null };
2032
+ if (forced === "mistral") {
2033
+ const apiKey = env.MISTRAL_API_KEY;
2034
+ if (apiKey === void 0 || apiKey === "") {
2035
+ throw new Error('PROMETHEUS_MEMORY_REWRITE_PROVIDER="mistral" requires MISTRAL_API_KEY.');
2036
+ }
2037
+ return {
2038
+ id: "mistral",
2039
+ provider: new OpenAICompatRewriter({
2040
+ name: "mistral-rewrite",
2041
+ model: env.PROMETHEUS_MEMORY_REWRITE_MODEL ?? "mistral-small-latest",
2042
+ baseUrl: env.MISTRAL_BASE_URL ?? "https://api.mistral.ai/v1",
2043
+ apiKey
2044
+ })
2045
+ };
2046
+ }
2047
+ if (forced === "openai") {
2048
+ const apiKey = env.OPENAI_API_KEY;
2049
+ if (apiKey === void 0 || apiKey === "") {
2050
+ throw new Error('PROMETHEUS_MEMORY_REWRITE_PROVIDER="openai" requires OPENAI_API_KEY.');
2051
+ }
2052
+ return {
2053
+ id: "openai",
2054
+ provider: new OpenAICompatRewriter({
2055
+ name: "openai-rewrite",
2056
+ model: env.PROMETHEUS_MEMORY_REWRITE_MODEL ?? "gpt-4o-mini",
2057
+ baseUrl: env.OPENAI_BASE_URL ?? "https://api.openai.com/v1",
2058
+ apiKey
2059
+ })
2060
+ };
2061
+ }
2062
+ if (forced === "generic" || forced === "openai-compat") {
2063
+ const baseUrl = env.PROMETHEUS_MEMORY_REWRITE_ENDPOINT;
2064
+ if (baseUrl === void 0 || baseUrl === "") {
2065
+ throw new Error(`PROMETHEUS_MEMORY_REWRITE_PROVIDER="${forced}" requires PROMETHEUS_MEMORY_REWRITE_ENDPOINT.`);
2066
+ }
2067
+ return {
2068
+ id: "generic",
2069
+ provider: new OpenAICompatRewriter({
2070
+ name: env.PROMETHEUS_MEMORY_REWRITE_NAME ?? "generic-rewrite",
2071
+ model: env.PROMETHEUS_MEMORY_REWRITE_MODEL ?? "default",
2072
+ baseUrl,
2073
+ ...env.PROMETHEUS_MEMORY_REWRITE_API_KEY ? { apiKey: env.PROMETHEUS_MEMORY_REWRITE_API_KEY } : {}
2074
+ })
2075
+ };
2076
+ }
2077
+ throw new Error(`unknown PROMETHEUS_MEMORY_REWRITE_PROVIDER="${forced}" (expected "none", "mistral", "openai", or "generic")`);
2078
+ }
1755
2079
  function composeFromEnv(opts) {
1756
2080
  const env = opts.env;
1757
2081
  const override = (opts.workspaceRootOverride ?? "").trim();
@@ -1765,9 +2089,11 @@ function composeFromEnv(opts) {
1765
2089
  const { id: embedderId, embedder } = discoverMemoryEmbedder(env);
1766
2090
  const { id: rerankerId, provider: reranker } = discoverMemoryReranker(env);
1767
2091
  const { id: extractorId, provider: extractor } = discoverMemoryExtractor(env);
2092
+ const { id: rewriterId, provider: rewriter } = discoverMemoryRewriter(env);
1768
2093
  const backend = new SqliteMemoryBackend(dbPath, {
1769
2094
  ...embedder !== void 0 ? { embedder } : {},
1770
- ...reranker !== null ? { reranker } : {}
2095
+ ...reranker !== null ? { reranker } : {},
2096
+ ...rewriter !== null ? { rewriter } : {}
1771
2097
  });
1772
2098
  return {
1773
2099
  backend,
@@ -1781,12 +2107,14 @@ function composeFromEnv(opts) {
1781
2107
  rerankerId,
1782
2108
  extractor,
1783
2109
  extractorId,
2110
+ rewriter,
2111
+ rewriterId,
1784
2112
  close: () => backend.close()
1785
2113
  };
1786
2114
  }
1787
2115
 
1788
2116
  // dist/roots.js
1789
- import { fileURLToPath } from "node:url";
2117
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
1790
2118
  async function rootFromClient(server, timeoutMs = 2500) {
1791
2119
  let supportsRoots = false;
1792
2120
  try {
@@ -1807,7 +2135,7 @@ async function rootFromClient(server, timeoutMs = 2500) {
1807
2135
  const uri = typeof r?.uri === "string" ? r.uri : "";
1808
2136
  if (uri.startsWith("file://")) {
1809
2137
  try {
1810
- return fileURLToPath(uri);
2138
+ return fileURLToPath2(uri);
1811
2139
  } catch {
1812
2140
  }
1813
2141
  }
@@ -1818,38 +2146,6 @@ async function rootFromClient(server, timeoutMs = 2500) {
1818
2146
  // dist/server.js
1819
2147
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
1820
2148
 
1821
- // ../shared/dist/types.js
1822
- var GRAMMAR_LANGUAGE_IDS = [
1823
- "typescript",
1824
- "tsx",
1825
- "javascript",
1826
- "python",
1827
- "php",
1828
- "go",
1829
- "rust",
1830
- "java",
1831
- "csharp",
1832
- "c",
1833
- "cpp",
1834
- "ruby",
1835
- "kotlin",
1836
- "html"
1837
- ];
1838
- var DOCUMENT_LANGUAGE_IDS = [
1839
- "markdown",
1840
- "text",
1841
- "json",
1842
- "yaml",
1843
- "toml"
1844
- ];
1845
- var LANGUAGE_IDS = [
1846
- ...GRAMMAR_LANGUAGE_IDS,
1847
- ...DOCUMENT_LANGUAGE_IDS
1848
- ];
1849
-
1850
- // ../shared/dist/index.js
1851
- var PROMETHEUS_VERSION = "0.1.0";
1852
-
1853
2149
  // dist/tools.js
1854
2150
  import { z } from "zod";
1855
2151
 
@@ -1970,8 +2266,8 @@ function assertNoSecrets(text) {
1970
2266
 
1971
2267
  // dist/setup.js
1972
2268
  import { existsSync } from "node:fs";
1973
- import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
1974
- import { dirname as dirname2, join as join3 } from "node:path";
2269
+ import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
2270
+ import { dirname as dirname2, join as join4 } from "node:path";
1975
2271
  var MEMORY_RUNTIMES = [
1976
2272
  "claude-code",
1977
2273
  "cursor",
@@ -2015,13 +2311,13 @@ alwaysApply: true
2015
2311
  var TARGETS = {
2016
2312
  "claude-code": { relPath: "CLAUDE.md", mode: "block", detect: "CLAUDE.md" },
2017
2313
  cursor: {
2018
- relPath: join3(".cursor", "rules", "prometheus-memory.mdc"),
2314
+ relPath: join4(".cursor", "rules", "prometheus-memory.mdc"),
2019
2315
  mode: "file",
2020
2316
  fileContent: CURSOR_FRONTMATTER + withMarkers(RULE_BLOCK) + "\n",
2021
2317
  detect: ".cursor"
2022
2318
  },
2023
2319
  augment: {
2024
- relPath: join3(".augment", "rules", "prometheus-memory.md"),
2320
+ relPath: join4(".augment", "rules", "prometheus-memory.md"),
2025
2321
  mode: "file",
2026
2322
  fileContent: withMarkers(RULE_BLOCK) + "\n",
2027
2323
  detect: ".augment"
@@ -2029,7 +2325,7 @@ var TARGETS = {
2029
2325
  agents: { relPath: "AGENTS.md", mode: "block", detect: "AGENTS.md" }
2030
2326
  };
2031
2327
  function detectRuntimes(workspaceRoot) {
2032
- const found = MEMORY_RUNTIMES.filter((rt) => existsSync(join3(workspaceRoot, TARGETS[rt].detect)));
2328
+ const found = MEMORY_RUNTIMES.filter((rt) => existsSync(join4(workspaceRoot, TARGETS[rt].detect)));
2033
2329
  return found.length > 0 ? found : ["agents"];
2034
2330
  }
2035
2331
  function upsertBlock(existing, block) {
@@ -2044,15 +2340,15 @@ function upsertBlock(existing, block) {
2044
2340
  }
2045
2341
  async function installRuntime(workspaceRoot, runtime) {
2046
2342
  const target = TARGETS[runtime];
2047
- const absPath = join3(workspaceRoot, target.relPath);
2343
+ const absPath = join4(workspaceRoot, target.relPath);
2048
2344
  const exists = existsSync(absPath);
2049
- const before = exists ? await readFile2(absPath, "utf-8") : "";
2345
+ const before = exists ? await readFile3(absPath, "utf-8") : "";
2050
2346
  const after = target.mode === "file" ? target.fileContent : upsertBlock(before, RULE_BLOCK);
2051
2347
  if (exists && before === after) {
2052
2348
  return { runtime, path: absPath, action: "unchanged" };
2053
2349
  }
2054
- await mkdir2(dirname2(absPath), { recursive: true });
2055
- await writeFile2(absPath, after, "utf-8");
2350
+ await mkdir3(dirname2(absPath), { recursive: true });
2351
+ await writeFile3(absPath, after, "utf-8");
2056
2352
  return { runtime, path: absPath, action: exists ? "updated" : "created" };
2057
2353
  }
2058
2354
 
@@ -2414,6 +2710,7 @@ async function main() {
2414
2710
  const explicitRoot = (env.PROMETHEUS_WORKSPACE_ROOT ?? "").trim();
2415
2711
  const claudeRoot = (env.CLAUDE_PROJECT_DIR ?? "").trim();
2416
2712
  const eagerVia = explicitRoot !== "" ? "PROMETHEUS_WORKSPACE_ROOT" : claudeRoot !== "" ? "CLAUDE_PROJECT_DIR" : null;
2713
+ void maybeNotifyUpdate(import.meta.url, env);
2417
2714
  const transport = new StdioServerTransport();
2418
2715
  const server = new McpServer2(SERVER_IDENTITY, {
2419
2716
  capabilities: { tools: {} },
@@ -2438,7 +2735,7 @@ async function main() {
2438
2735
  env,
2439
2736
  ...override !== void 0 && override !== "" ? { workspaceRootOverride: override } : {}
2440
2737
  });
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}
2738
+ 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
2739
  `);
2443
2740
  registerTools(server, composed);
2444
2741
  };
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.1",
4
4
  "description": "prom.codes Memory — persistent, local-first agent memory as an MCP server.",
5
5
  "type": "module",
6
6
  "bin": {