@prom.codes/memory-mcp 0.7.2 → 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.
Files changed (3) hide show
  1. package/README.md +9 -3
  2. package/dist/bin.js +101 -21
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -37,10 +37,16 @@ session end.
37
37
  ranking ("latest/earliest" queries) is **on by default**; disable with
38
38
  `PROMETHEUS_MEMORY_TEMPORAL=off`.
39
39
 
40
+ If a fresh window opens with no project, the workspace falls back to the host
41
+ cwd (often home); in that case memory still works but project memories are NOT
42
+ mirrored to markdown there and `memory_setup` refuses to write rule files into
43
+ your home dir. Open a project folder so memories scope correctly.
44
+
40
45
  Tools (docked as `memory`): `memory_read`, `memory_write`, `memory_capture`,
41
- `memory_search`, `memory_list`, `memory_delete`, `memory_setup`. Secrets are
42
- rejected on every write. Your memories never leave your machine (only short
43
- query/record text transits when embeddings are enabled).
46
+ `memory_search`, `memory_list`, `memory_delete`, `memory_setup`,
47
+ `memory_status` (health check: which folder, how many records, does the key
48
+ work?). Secrets are rejected on every write. Your memories never leave your
49
+ machine (only short query/record text transits when embeddings are enabled).
44
50
 
45
51
  ## Native modules
46
52
 
package/dist/bin.js CHANGED
@@ -180,13 +180,27 @@ function notify(log, name, current, latest) {
180
180
  `);
181
181
  }
182
182
 
183
+ // ../shared/dist/workspace-root.js
184
+ import { homedir as homedir2 } from "node:os";
185
+ import { dirname, resolve } from "node:path";
186
+ function isHomeOrFilesystemRoot(root) {
187
+ const abs = resolve(root);
188
+ if (abs === "")
189
+ return true;
190
+ if (abs === resolve(homedir2()))
191
+ return true;
192
+ if (dirname(abs) === abs)
193
+ return true;
194
+ return false;
195
+ }
196
+
183
197
  // ../shared/dist/index.js
184
198
  var PROMETHEUS_VERSION = "0.1.0";
185
199
 
186
200
  // dist/composition.js
187
201
  import { createHash } from "node:crypto";
188
- import { homedir as homedir2 } from "node:os";
189
- import { basename, join as join2, resolve } from "node:path";
202
+ import { homedir as homedir3 } from "node:os";
203
+ import { basename, join as join2, resolve as resolve2 } from "node:path";
190
204
 
191
205
  // ../embeddings-openai-compat/dist/index.js
192
206
  var DEFAULT_BATCH = 96;
@@ -217,14 +231,14 @@ function parseRetryAfterMs(value, now = Date.now()) {
217
231
  return delta > 0 ? delta : 0;
218
232
  }
219
233
  function sleep(ms, signal) {
220
- return new Promise((resolve2, reject) => {
234
+ return new Promise((resolve3, reject) => {
221
235
  if (signal?.aborted === true) {
222
236
  reject(new Error("aborted"));
223
237
  return;
224
238
  }
225
239
  const timer = setTimeout(() => {
226
240
  signal?.removeEventListener("abort", onAbort);
227
- resolve2();
241
+ resolve3();
228
242
  }, ms);
229
243
  const onAbort = () => {
230
244
  clearTimeout(timer);
@@ -466,14 +480,14 @@ var DEFAULT_BATCH_CHARS = 4e5;
466
480
  var DEFAULT_RETRIES2 = 4;
467
481
  var DEFAULT_BACKOFF2 = 250;
468
482
  function sleep2(ms, signal) {
469
- return new Promise((resolve2, reject) => {
483
+ return new Promise((resolve3, reject) => {
470
484
  if (signal?.aborted === true) {
471
485
  reject(new Error("aborted"));
472
486
  return;
473
487
  }
474
488
  const timer = setTimeout(() => {
475
489
  signal?.removeEventListener("abort", onAbort);
476
- resolve2();
490
+ resolve3();
477
491
  }, ms);
478
492
  const onAbort = () => {
479
493
  clearTimeout(timer);
@@ -713,14 +727,14 @@ function parseRetryAfterMs2(value, now = Date.now()) {
713
727
  return delta > 0 ? delta : 0;
714
728
  }
715
729
  function sleep3(ms, signal) {
716
- return new Promise((resolve2, reject) => {
730
+ return new Promise((resolve3, reject) => {
717
731
  if (signal?.aborted === true) {
718
732
  reject(new Error("aborted"));
719
733
  return;
720
734
  }
721
735
  const timer = setTimeout(() => {
722
736
  signal?.removeEventListener("abort", onAbort);
723
- resolve2();
737
+ resolve3();
724
738
  }, ms);
725
739
  const onAbort = () => {
726
740
  clearTimeout(timer);
@@ -885,14 +899,14 @@ function parseRetryAfterMs3(value, now = Date.now()) {
885
899
  return delta > 0 ? delta : 0;
886
900
  }
887
901
  function sleep4(ms, signal) {
888
- return new Promise((resolve2, reject) => {
902
+ return new Promise((resolve3, reject) => {
889
903
  if (signal?.aborted === true) {
890
904
  reject(new Error("aborted"));
891
905
  return;
892
906
  }
893
907
  const timer = setTimeout(() => {
894
908
  signal?.removeEventListener("abort", onAbort);
895
- resolve2();
909
+ resolve3();
896
910
  }, ms);
897
911
  const onAbort = () => {
898
912
  clearTimeout(timer);
@@ -1254,7 +1268,7 @@ var OpenAICompatRewriter = class {
1254
1268
  // dist/sqlite.js
1255
1269
  import { randomUUID } from "node:crypto";
1256
1270
  import { mkdirSync } from "node:fs";
1257
- import { dirname } from "node:path";
1271
+ import { dirname as dirname2 } from "node:path";
1258
1272
  import Database from "better-sqlite3";
1259
1273
 
1260
1274
  // dist/rrf.js
@@ -1526,7 +1540,7 @@ var SqliteMemoryBackend = class {
1526
1540
  closed = false;
1527
1541
  constructor(dbPath, opts = {}) {
1528
1542
  if (dbPath !== ":memory:") {
1529
- mkdirSync(dirname(dbPath), { recursive: true });
1543
+ mkdirSync(dirname2(dbPath), { recursive: true });
1530
1544
  }
1531
1545
  this.db = new Database(dbPath);
1532
1546
  this.db.pragma("journal_mode = WAL");
@@ -1870,6 +1884,16 @@ ${h.record.value}`
1870
1884
  this.audit("delete", input, result.changes > 0 ? "removed" : "no-op");
1871
1885
  return result.changes > 0;
1872
1886
  }
1887
+ async stats(projectId) {
1888
+ const rows = this.db.prepare(`SELECT scope, COUNT(*) AS n FROM agent_memory WHERE project_id = ? GROUP BY scope`).all(projectId);
1889
+ let total = 0;
1890
+ const byScope = {};
1891
+ for (const r of rows) {
1892
+ byScope[r.scope] = r.n;
1893
+ total += r.n;
1894
+ }
1895
+ return { total, byScope };
1896
+ }
1873
1897
  async consolidate(input) {
1874
1898
  const written = [];
1875
1899
  if (input.plan || input.outcome) {
@@ -1926,11 +1950,11 @@ ${h.record.value}`
1926
1950
 
1927
1951
  // dist/composition.js
1928
1952
  function projectIdFor(workspaceRoot) {
1929
- const abs = resolve(workspaceRoot);
1953
+ const abs = resolve2(workspaceRoot);
1930
1954
  return createHash("sha256").update(abs).digest("hex").slice(0, 16);
1931
1955
  }
1932
1956
  function defaultMemoryDbPath() {
1933
- return join2(homedir2(), ".prometheus", "memory.db");
1957
+ return join2(homedir3(), ".prometheus", "memory.db");
1934
1958
  }
1935
1959
  function intEnv(env, name, def) {
1936
1960
  const raw = env[name];
@@ -2149,7 +2173,7 @@ function composeFromEnv(opts) {
2149
2173
  const override = (opts.workspaceRootOverride ?? "").trim();
2150
2174
  const envRoot = (env.PROMETHEUS_WORKSPACE_ROOT ?? "").trim();
2151
2175
  const claudeRoot = (env.CLAUDE_PROJECT_DIR ?? "").trim();
2152
- const workspaceRoot = resolve(override !== "" ? override : envRoot !== "" ? envRoot : claudeRoot !== "" ? claudeRoot : process.cwd());
2176
+ const workspaceRoot = resolve2(override !== "" ? override : envRoot !== "" ? envRoot : claudeRoot !== "" ? claudeRoot : process.cwd());
2153
2177
  const projectId = projectIdFor(workspaceRoot);
2154
2178
  const projectName = basename(workspaceRoot) || workspaceRoot;
2155
2179
  const rawDbPath = env.PROMETHEUS_MEMORY_DB_PATH;
@@ -2173,6 +2197,7 @@ function composeFromEnv(opts) {
2173
2197
  dbPath,
2174
2198
  embeddingsEnabled: embedder !== void 0,
2175
2199
  embedderId,
2200
+ embedder,
2176
2201
  reranker,
2177
2202
  rerankerId,
2178
2203
  extractor,
@@ -2180,6 +2205,7 @@ function composeFromEnv(opts) {
2180
2205
  rewriter,
2181
2206
  rewriterId,
2182
2207
  temporalEnabled: temporal.enabled,
2208
+ rootIsHomeOrFsRoot: isHomeOrFilesystemRoot(workspaceRoot),
2183
2209
  close: () => backend.close()
2184
2210
  };
2185
2211
  }
@@ -2338,7 +2364,7 @@ function assertNoSecrets(text) {
2338
2364
  // dist/setup.js
2339
2365
  import { existsSync } from "node:fs";
2340
2366
  import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
2341
- import { dirname as dirname2, join as join4 } from "node:path";
2367
+ import { dirname as dirname3, join as join4 } from "node:path";
2342
2368
  var MEMORY_RUNTIMES = [
2343
2369
  "claude-code",
2344
2370
  "cursor",
@@ -2418,7 +2444,7 @@ async function installRuntime(workspaceRoot, runtime) {
2418
2444
  if (exists && before === after) {
2419
2445
  return { runtime, path: absPath, action: "unchanged" };
2420
2446
  }
2421
- await mkdir3(dirname2(absPath), { recursive: true });
2447
+ await mkdir3(dirname3(absPath), { recursive: true });
2422
2448
  await writeFile3(absPath, after, "utf-8");
2423
2449
  return { runtime, path: absPath, action: exists ? "updated" : "created" };
2424
2450
  }
@@ -2576,15 +2602,17 @@ var runtimeEnum = z.enum(MEMORY_RUNTIMES);
2576
2602
  var setupInput = {
2577
2603
  runtimes: z.array(runtimeEnum).min(1).optional()
2578
2604
  };
2605
+ var emptyInput = {};
2579
2606
  function registerTools(server, deps) {
2580
2607
  const { backend, workspaceRoot, projectId, projectName, dbPath, extractor } = deps;
2608
+ const mirrorToFiles = !deps.rootIsHomeOrFsRoot;
2581
2609
  server.registerTool("read", {
2582
2610
  title: "Recall agent memory",
2583
2611
  description: "Read agent memory for this project along the scope chain (project \u2192 workspace \u2192 tenant \u2192 system; narrowest scope wins). Syncs `.prometheus/memories/*.md` first, then returns the resolved records plus a prompt-ready `woven` markdown block (token-capped). Call this at the START of a session or task to recall what earlier sessions learned.",
2584
2612
  inputSchema: readInput
2585
2613
  }, async (args) => {
2586
2614
  const limit = clampLimit(args.limit, DEFAULT_READ_LIMIT);
2587
- const synced = await syncProjectFiles(backend, { projectId, workspaceRoot });
2615
+ const synced = mirrorToFiles ? await syncProjectFiles(backend, { projectId, workspaceRoot }) : 0;
2588
2616
  const records = await backend.read({
2589
2617
  chain: defaultScopeChain(projectId),
2590
2618
  types: args.types,
@@ -2620,7 +2648,7 @@ ${args.value}`);
2620
2648
  source: "user"
2621
2649
  });
2622
2650
  let projectFile = null;
2623
- if (scope === "project" && args.type === "semantic") {
2651
+ if (mirrorToFiles && scope === "project" && args.type === "semantic") {
2624
2652
  projectFile = await writeProjectFile(workspaceRoot, args.key, args.value);
2625
2653
  }
2626
2654
  return textResult({ record: recordToJson(record), projectFile });
@@ -2694,7 +2722,8 @@ ${f.value}`);
2694
2722
  inputSchema: searchInput
2695
2723
  }, async (args) => {
2696
2724
  const limit = clampLimit(args.limit, 20);
2697
- await syncProjectFiles(backend, { projectId, workspaceRoot });
2725
+ if (mirrorToFiles)
2726
+ await syncProjectFiles(backend, { projectId, workspaceRoot });
2698
2727
  const hits = await backend.search({
2699
2728
  chain: defaultScopeChain(projectId),
2700
2729
  query: args.query,
@@ -2744,7 +2773,7 @@ ${f.value}`);
2744
2773
  key: args.key
2745
2774
  });
2746
2775
  let fileRemoved = false;
2747
- if (scope === "project" && args.type === "semantic") {
2776
+ if (mirrorToFiles && scope === "project" && args.type === "semantic") {
2748
2777
  fileRemoved = await deleteProjectFile(workspaceRoot, args.key);
2749
2778
  }
2750
2779
  return textResult({ removed, fileRemoved });
@@ -2754,6 +2783,13 @@ ${f.value}`);
2754
2783
  description: "Idempotently install the Prometheus memory-protocol rule block into agent runtime configs in this workspace: CLAUDE.md (claude-code), .cursor/rules/prometheus-memory.mdc (cursor), .augment/rules/prometheus-memory.md (augment), AGENTS.md (agents). Without `runtimes` it auto-detects which runtimes are present (fallback: agents). Only the marked block is written \u2014 existing content is never touched. Re-running updates the block in place.",
2755
2784
  inputSchema: setupInput
2756
2785
  }, async (args) => {
2786
+ if (!mirrorToFiles) {
2787
+ return textResult({
2788
+ workspaceRoot,
2789
+ installed: false,
2790
+ reason: "Workspace resolved to your home directory or a filesystem root \u2014 refusing to write rule files there. Open your project folder (Claude Code passes it via CLAUDE_PROJECT_DIR) or set PROMETHEUS_WORKSPACE_ROOT, then retry."
2791
+ });
2792
+ }
2757
2793
  const runtimes = args.runtimes ?? detectRuntimes(workspaceRoot);
2758
2794
  const results = [];
2759
2795
  for (const runtime of runtimes) {
@@ -2761,6 +2797,46 @@ ${f.value}`);
2761
2797
  }
2762
2798
  return textResult({ workspaceRoot, results });
2763
2799
  });
2800
+ server.registerTool("status", {
2801
+ title: "Memory status / health check",
2802
+ description: "Health check for this project's agent memory. Reports the resolved workspace root, project id, DB path, how many records are stored (total + by scope), the embedding provider with a zero-cost key-reachability probe, and which quality levers are active (rerank / rewrite / temporal). CALL THIS to confirm where memory is stored, how much is there, and whether the API key works.",
2803
+ inputSchema: emptyInput
2804
+ }, async () => {
2805
+ const stats = await backend.stats(projectId);
2806
+ let embeddingsReachable = null;
2807
+ let embeddingsError = null;
2808
+ const resolveIdentity = deps.embedder?.resolveIdentity;
2809
+ if (deps.embeddingsEnabled && typeof resolveIdentity === "function") {
2810
+ try {
2811
+ await resolveIdentity.call(deps.embedder);
2812
+ embeddingsReachable = true;
2813
+ } catch (err) {
2814
+ embeddingsReachable = false;
2815
+ embeddingsError = err instanceof Error ? err.message : String(err);
2816
+ }
2817
+ }
2818
+ const summary = deps.rootIsHomeOrFsRoot ? `Memory at ${dbPath}: ${stats.total} records, but the workspace resolved to ${workspaceRoot} (home/root) \u2014 open a project folder so memories scope and mirror correctly.` : `Memory at ${dbPath}: ${stats.total} records for project "${projectName}".`;
2819
+ return textResult({
2820
+ installed: true,
2821
+ project: { id: projectId, name: projectName, workspaceRoot },
2822
+ rootIsHomeOrFsRoot: deps.rootIsHomeOrFsRoot,
2823
+ storage: { dbPath, projectFileMirror: mirrorToFiles },
2824
+ records: { total: stats.total, byScope: stats.byScope },
2825
+ embeddings: {
2826
+ enabled: deps.embeddingsEnabled,
2827
+ provider: deps.embedderId,
2828
+ reachable: embeddingsReachable,
2829
+ ...embeddingsError !== null ? { error: embeddingsError } : {}
2830
+ },
2831
+ levers: {
2832
+ rerank: deps.rerankerId,
2833
+ rewrite: deps.rewriterId,
2834
+ temporal: deps.temporalEnabled,
2835
+ extract: deps.extractorId
2836
+ },
2837
+ summary
2838
+ });
2839
+ });
2764
2840
  }
2765
2841
 
2766
2842
  // dist/server.js
@@ -2808,6 +2884,10 @@ async function main() {
2808
2884
  });
2809
2885
  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} temporal=${composed.temporalEnabled ? "on" : "off"}
2810
2886
  `);
2887
+ if (composed.rootIsHomeOrFsRoot) {
2888
+ process.stderr.write(`prometheus-memory-mcp: workspace resolved to ${composed.workspaceRoot} (your home directory or a filesystem root) \u2014 project memories will NOT be mirrored to markdown there. Open a project folder (Claude Code passes it via CLAUDE_PROJECT_DIR) or set PROMETHEUS_WORKSPACE_ROOT. Call memory_status for details.
2889
+ `);
2890
+ }
2811
2891
  registerTools(server, composed);
2812
2892
  };
2813
2893
  if (eagerVia !== null) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prom.codes/memory-mcp",
3
- "version": "0.7.2",
3
+ "version": "0.7.3",
4
4
  "description": "prom.codes Memory — persistent, local-first agent memory as an MCP server.",
5
5
  "type": "module",
6
6
  "bin": {