@prom.codes/memory-mcp 0.15.1 → 0.16.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 +224 -29
  2. package/package.json +2 -2
package/dist/bin.js CHANGED
@@ -301,6 +301,20 @@ function isHomeOrFilesystemRoot(root) {
301
301
  return false;
302
302
  }
303
303
 
304
+ // ../shared/dist/workspace-path.js
305
+ import { createHash } from "node:crypto";
306
+ import { resolve as resolve2 } from "node:path";
307
+ function canonicalizeWorkspacePath(p) {
308
+ const abs = resolve2(p);
309
+ return process.platform === "win32" ? abs.replace(/\\/g, "/").toLowerCase() : abs;
310
+ }
311
+ function stableWorkspaceId(root) {
312
+ return createHash("sha256").update(canonicalizeWorkspacePath(root)).digest("hex").slice(0, 16);
313
+ }
314
+ function legacyWorkspaceId(root) {
315
+ return createHash("sha256").update(resolve2(root)).digest("hex").slice(0, 16);
316
+ }
317
+
304
318
  // ../shared/dist/heartbeat.js
305
319
  import { mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
306
320
  import { homedir as homedir3 } from "node:os";
@@ -471,9 +485,8 @@ function nativeBindingOption() {
471
485
  }
472
486
 
473
487
  // dist/composition.js
474
- import { createHash } from "node:crypto";
475
488
  import { homedir as homedir5 } from "node:os";
476
- import { basename, join as join7, resolve as resolve2 } from "node:path";
489
+ import { basename, join as join7, resolve as resolve3 } from "node:path";
477
490
 
478
491
  // ../embeddings-openai-compat/dist/index.js
479
492
  var DEFAULT_BATCH = 96;
@@ -504,14 +517,14 @@ function parseRetryAfterMs(value, now = Date.now()) {
504
517
  return delta > 0 ? delta : 0;
505
518
  }
506
519
  function sleep(ms, signal) {
507
- return new Promise((resolve3, reject) => {
520
+ return new Promise((resolve4, reject) => {
508
521
  if (signal?.aborted === true) {
509
522
  reject(new Error("aborted"));
510
523
  return;
511
524
  }
512
525
  const timer = setTimeout(() => {
513
526
  signal?.removeEventListener("abort", onAbort);
514
- resolve3();
527
+ resolve4();
515
528
  }, ms);
516
529
  const onAbort = () => {
517
530
  clearTimeout(timer);
@@ -754,14 +767,14 @@ var DEFAULT_RETRIES2 = 4;
754
767
  var DEFAULT_BACKOFF2 = 250;
755
768
  var DEFAULT_TIMEOUT_MS2 = 15e3;
756
769
  function sleep2(ms, signal) {
757
- return new Promise((resolve3, reject) => {
770
+ return new Promise((resolve4, reject) => {
758
771
  if (signal?.aborted === true) {
759
772
  reject(new Error("aborted"));
760
773
  return;
761
774
  }
762
775
  const timer = setTimeout(() => {
763
776
  signal?.removeEventListener("abort", onAbort);
764
- resolve3();
777
+ resolve4();
765
778
  }, ms);
766
779
  const onAbort = () => {
767
780
  clearTimeout(timer);
@@ -1025,14 +1038,14 @@ function parseRetryAfterMs2(value, now = Date.now()) {
1025
1038
  return delta > 0 ? delta : 0;
1026
1039
  }
1027
1040
  function sleep3(ms, signal) {
1028
- return new Promise((resolve3, reject) => {
1041
+ return new Promise((resolve4, reject) => {
1029
1042
  if (signal?.aborted === true) {
1030
1043
  reject(new Error("aborted"));
1031
1044
  return;
1032
1045
  }
1033
1046
  const timer = setTimeout(() => {
1034
1047
  signal?.removeEventListener("abort", onAbort);
1035
- resolve3();
1048
+ resolve4();
1036
1049
  }, ms);
1037
1050
  const onAbort = () => {
1038
1051
  clearTimeout(timer);
@@ -1179,14 +1192,14 @@ var DEFAULT_RETRIES4 = 4;
1179
1192
  var DEFAULT_BACKOFF4 = 250;
1180
1193
  var DEFAULT_TIMEOUT_MS3 = 15e3;
1181
1194
  function sleep4(ms, signal) {
1182
- return new Promise((resolve3, reject) => {
1195
+ return new Promise((resolve4, reject) => {
1183
1196
  if (signal?.aborted === true) {
1184
1197
  reject(new Error("aborted"));
1185
1198
  return;
1186
1199
  }
1187
1200
  const timer = setTimeout(() => {
1188
1201
  signal?.removeEventListener("abort", onAbort);
1189
- resolve3();
1202
+ resolve4();
1190
1203
  }, ms);
1191
1204
  const onAbort = () => {
1192
1205
  clearTimeout(timer);
@@ -1405,14 +1418,14 @@ function parseRetryAfterMs3(value, now = Date.now()) {
1405
1418
  return delta > 0 ? delta : 0;
1406
1419
  }
1407
1420
  function sleep5(ms, signal) {
1408
- return new Promise((resolve3, reject) => {
1421
+ return new Promise((resolve4, reject) => {
1409
1422
  if (signal?.aborted === true) {
1410
1423
  reject(new Error("aborted"));
1411
1424
  return;
1412
1425
  }
1413
1426
  const timer = setTimeout(() => {
1414
1427
  signal?.removeEventListener("abort", onAbort);
1415
- resolve3();
1428
+ resolve4();
1416
1429
  }, ms);
1417
1430
  const onAbort = () => {
1418
1431
  clearTimeout(timer);
@@ -1806,7 +1819,7 @@ var OpenAICompatRewriter = class {
1806
1819
 
1807
1820
  // dist/sqlite.js
1808
1821
  import { randomUUID } from "node:crypto";
1809
- import { mkdirSync as mkdirSync3, readdirSync as readdirSync2, readFileSync as readFileSync3, rmSync as rmSync2 } from "node:fs";
1822
+ import { mkdirSync as mkdirSync3, readdirSync as readdirSync3, readFileSync as readFileSync3, rmSync as rmSync2 } from "node:fs";
1810
1823
  import { dirname as dirname4, join as join6 } from "node:path";
1811
1824
  import Database from "better-sqlite3";
1812
1825
 
@@ -1854,7 +1867,7 @@ function assertNoSecrets(text) {
1854
1867
  }
1855
1868
 
1856
1869
  // dist/recorder.js
1857
- import { copyFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
1870
+ import { copyFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, renameSync, rmdirSync, writeFileSync as writeFileSync2 } from "node:fs";
1858
1871
  import { homedir as homedir4 } from "node:os";
1859
1872
  import { dirname as dirname3, join as join5 } from "node:path";
1860
1873
  var SPOOL_VERSION = 1;
@@ -1890,6 +1903,46 @@ function recorderRoot(env = process.env) {
1890
1903
  function sanitizeSegment(s) {
1891
1904
  return s.replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 128) || "_";
1892
1905
  }
1906
+ function migrateRecorderSpoolDir(oldId, newId, env = process.env) {
1907
+ if (oldId === newId)
1908
+ return { renamedDir: false, movedFiles: 0 };
1909
+ const root = recorderRoot(env);
1910
+ const oldDir = join5(root, sanitizeSegment(oldId));
1911
+ const newDir = join5(root, sanitizeSegment(newId));
1912
+ if (!existsSync2(oldDir))
1913
+ return { renamedDir: false, movedFiles: 0 };
1914
+ if (!existsSync2(newDir)) {
1915
+ try {
1916
+ renameSync(oldDir, newDir);
1917
+ return { renamedDir: true, movedFiles: 0 };
1918
+ } catch {
1919
+ }
1920
+ }
1921
+ mkdirSync2(newDir, { recursive: true });
1922
+ let moved = 0;
1923
+ let names;
1924
+ try {
1925
+ names = readdirSync2(oldDir);
1926
+ } catch {
1927
+ return { renamedDir: false, movedFiles: 0 };
1928
+ }
1929
+ for (const name of names) {
1930
+ const to = join5(newDir, name);
1931
+ if (existsSync2(to))
1932
+ continue;
1933
+ try {
1934
+ renameSync(join5(oldDir, name), to);
1935
+ moved++;
1936
+ } catch {
1937
+ }
1938
+ }
1939
+ try {
1940
+ if (readdirSync2(oldDir).length === 0)
1941
+ rmdirSync(oldDir);
1942
+ } catch {
1943
+ }
1944
+ return { renamedDir: false, movedFiles: moved };
1945
+ }
1893
1946
  function parseSpool(raw) {
1894
1947
  const lines = raw.split("\n");
1895
1948
  const events = [];
@@ -1921,7 +1974,7 @@ var RECORDER_HOOK_SCRIPT = String.raw`#!/usr/bin/env node
1921
1974
  // never disturb a coding session. Managed by prom.codes (memory-mcp recorder_setup).
1922
1975
  import { appendFileSync, mkdirSync, readFileSync, statSync } from "node:fs";
1923
1976
  import { homedir } from "node:os";
1924
- import { join } from "node:path";
1977
+ import { join, resolve } from "node:path";
1925
1978
  import { createHash } from "node:crypto";
1926
1979
 
1927
1980
  const SPOOL_CAP = ${SPOOL_CAP_BYTES};
@@ -1944,7 +1997,11 @@ function sensitivePath(p) {
1944
1997
  if (/(^|\/)(secrets?|credentials?)(\/|$)/.test(s)) return true;
1945
1998
  return false;
1946
1999
  }
1947
- function projectIdOf(root) { return createHash("sha256").update(String(root)).digest("hex").slice(0, 16); }
2000
+ // Canonical workspace id MUST match @prometheus/shared stableWorkspaceId so
2001
+ // the spool key equals memory-mcp's projectIdFor (the ingest side). Inlined
2002
+ // because this standalone hook cannot import from the package.
2003
+ function canonicalizeWorkspacePath(p) { const abs = resolve(p); return process.platform === "win32" ? abs.replace(/\\/g, "/").toLowerCase() : abs; }
2004
+ function projectIdOf(root) { return createHash("sha256").update(canonicalizeWorkspacePath(root)).digest("hex").slice(0, 16); }
1948
2005
 
1949
2006
  function dietTool(toolName, inp, result) {
1950
2007
  const tool = toolName; inp = inp || {};
@@ -2604,6 +2661,61 @@ var SqliteMemoryBackend = class {
2604
2661
  if (this.embedder !== void 0)
2605
2662
  this.queueUnembedded();
2606
2663
  }
2664
+ /**
2665
+ * Canonical-hash migration (spec 2026-07-19 §3.3): move every row keyed on the
2666
+ * PRE-flip `oldId` onto the canonical `newId`, in ONE transaction. No re-embed
2667
+ * (the vector is over `value`, which is unchanged) — the memory analogue of
2668
+ * context's "no reindex".
2669
+ *
2670
+ * - `agent_memory`: change `project_id`; for project-scoped rows the
2671
+ * `scope_id` (which equals the project id) moves too. The UNIQUE
2672
+ * `(scope, scope_id, type, key)` can then clash with a row already under
2673
+ * `newId` (the user used both path spellings). **Merge rule: newest
2674
+ * `updated_at` wins**; the loser is deleted (its vector + FTS entry GC'd by
2675
+ * the existing delete triggers). This mirrors `resolveScopeChain`'s own
2676
+ * newer-wins tie-break, so the survivor is the record recall already prefers.
2677
+ * - `agent_sessions` / `session_events`: `UPDATE OR IGNORE` the `project_id`;
2678
+ * a `(session_id, seq)` clash across two spellings is effectively impossible
2679
+ * (ids are unique per run under one spelling), and OR IGNORE leaves any
2680
+ * clash inert under the old id rather than throwing.
2681
+ *
2682
+ * Idempotent (afterwards no row has `project_id = oldId`) and a no-op when
2683
+ * `oldId === newId` (POSIX / already-canonical).
2684
+ */
2685
+ migrateProjectIdentity(oldId, newId) {
2686
+ if (oldId === newId) {
2687
+ return { memoryRows: 0, memoryMerged: 0, sessions: 0, events: 0 };
2688
+ }
2689
+ const run = this.db.transaction(() => {
2690
+ const incoming = this.db.prepare(`SELECT * FROM agent_memory WHERE project_id = ?`).all(oldId);
2691
+ const findClash = this.db.prepare(`SELECT id, updated_at FROM agent_memory
2692
+ WHERE scope = ? AND scope_id = ? AND type = ? AND key = ? AND id != ?`);
2693
+ const del = this.db.prepare(`DELETE FROM agent_memory WHERE id = ?`);
2694
+ const upd = this.db.prepare(`UPDATE agent_memory SET project_id = ?, scope_id = ? WHERE id = ?`);
2695
+ let merged = 0;
2696
+ for (const row of incoming) {
2697
+ const newScopeId = row.scope === "project" && row.scope_id === oldId ? newId : row.scope_id;
2698
+ const clash = findClash.get(row.scope, newScopeId, row.type, row.key, row.id);
2699
+ if (clash !== void 0) {
2700
+ if (row.updated_at > clash.updated_at) {
2701
+ del.run(clash.id);
2702
+ this.pendingEmbed.delete(clash.id);
2703
+ upd.run(newId, newScopeId, row.id);
2704
+ } else {
2705
+ del.run(row.id);
2706
+ this.pendingEmbed.delete(row.id);
2707
+ }
2708
+ merged++;
2709
+ } else {
2710
+ upd.run(newId, newScopeId, row.id);
2711
+ }
2712
+ }
2713
+ const sessions = this.db.prepare(`UPDATE OR IGNORE agent_sessions SET project_id = ? WHERE project_id = ?`).run(newId, oldId).changes;
2714
+ const events = this.db.prepare(`UPDATE OR IGNORE session_events SET project_id = ? WHERE project_id = ?`).run(newId, oldId).changes;
2715
+ return { memoryRows: incoming.length, memoryMerged: merged, sessions, events };
2716
+ });
2717
+ return run();
2718
+ }
2607
2719
  /**
2608
2720
  * Queue every record lacking a stored vector for a (re)embed. Run at
2609
2721
  * open so a DB first built keyword-only — or one whose vectors were
@@ -2688,7 +2800,8 @@ var SqliteMemoryBackend = class {
2688
2800
  const rows = this.db.prepare(sql).all(...params);
2689
2801
  const resolved = resolveScopeChain(rows.map(rowToRecord), query.chain);
2690
2802
  const deduped = this.dedupeRecords(resolved);
2691
- const limited = query.limit !== void 0 ? deduped.slice(0, query.limit) : deduped;
2803
+ const offset = query.offset ?? 0;
2804
+ const limited = query.limit !== void 0 ? deduped.slice(offset, offset + query.limit) : deduped.slice(offset);
2692
2805
  const bump = this.db.prepare(`UPDATE agent_memory SET use_count = use_count + 1 WHERE id = ?`);
2693
2806
  for (const rec of limited) {
2694
2807
  bump.run(rec.id);
@@ -3037,7 +3150,7 @@ ${h.record.value}`
3037
3150
  let deletedSpools = 0;
3038
3151
  let files;
3039
3152
  try {
3040
- files = readdirSync2(dir).filter((f) => f.endsWith(".jsonl"));
3153
+ files = readdirSync3(dir).filter((f) => f.endsWith(".jsonl"));
3041
3154
  } catch {
3042
3155
  return { sessions: 0, events: 0, deletedSpools: 0 };
3043
3156
  }
@@ -3211,8 +3324,10 @@ ${h.record.value}`
3211
3324
 
3212
3325
  // dist/composition.js
3213
3326
  function projectIdFor(workspaceRoot) {
3214
- const abs = resolve2(workspaceRoot);
3215
- return createHash("sha256").update(abs).digest("hex").slice(0, 16);
3327
+ return stableWorkspaceId(workspaceRoot);
3328
+ }
3329
+ function legacyProjectIdFor(workspaceRoot) {
3330
+ return legacyWorkspaceId(workspaceRoot);
3216
3331
  }
3217
3332
  function defaultMemoryDbPath() {
3218
3333
  return join7(homedir5(), ".prometheus", "memory.db");
@@ -3462,7 +3577,7 @@ function composeFromEnv(opts) {
3462
3577
  const override = (opts.workspaceRootOverride ?? "").trim();
3463
3578
  const envRoot = (env.PROMETHEUS_WORKSPACE_ROOT ?? "").trim();
3464
3579
  const claudeRoot = (env.CLAUDE_PROJECT_DIR ?? "").trim();
3465
- const workspaceRoot = resolve2(override !== "" ? override : envRoot !== "" ? envRoot : claudeRoot !== "" ? claudeRoot : process.cwd());
3580
+ const workspaceRoot = resolve3(override !== "" ? override : envRoot !== "" ? envRoot : claudeRoot !== "" ? claudeRoot : process.cwd());
3466
3581
  const projectId = projectIdFor(workspaceRoot);
3467
3582
  const projectName = basename(workspaceRoot) || workspaceRoot;
3468
3583
  const rawDbPath = env.PROMETHEUS_MEMORY_DB_PATH;
@@ -3481,6 +3596,21 @@ function composeFromEnv(opts) {
3481
3596
  temporal,
3482
3597
  dedup
3483
3598
  });
3599
+ const legacyId = legacyProjectIdFor(workspaceRoot);
3600
+ const canonMigrationOff = /^(off|0|false|no)$/i.test((env.PROMETHEUS_CANON_MIGRATION ?? "").trim());
3601
+ if (legacyId !== projectId && !canonMigrationOff) {
3602
+ try {
3603
+ const m = backend.migrateProjectIdentity(legacyId, projectId);
3604
+ const spool = migrateRecorderSpoolDir(legacyId, projectId, env);
3605
+ if (m.memoryRows > 0 || m.sessions > 0 || m.events > 0 || spool.renamedDir || spool.movedFiles > 0) {
3606
+ process.stderr.write(`[prom.codes] canonical-hash migration: ${m.memoryRows} memory row(s) (${m.memoryMerged} merged), ${m.sessions} session(s), ${m.events} event(s), spool ${spool.renamedDir ? "dir renamed" : `${spool.movedFiles} file(s)`} ${legacyId} -> ${projectId}
3607
+ `);
3608
+ }
3609
+ } catch (err) {
3610
+ process.stderr.write(`[prom.codes] canonical-hash memory migration skipped (non-fatal): ${err.message}
3611
+ `);
3612
+ }
3613
+ }
3484
3614
  return {
3485
3615
  backend,
3486
3616
  recorder: backend,
@@ -3984,8 +4114,10 @@ function weave(records, options = {}) {
3984
4114
 
3985
4115
  // dist/tools.js
3986
4116
  var MAX_LIMIT = 100;
3987
- var DEFAULT_READ_LIMIT = 50;
4117
+ var DEFAULT_READ_LIMIT = 25;
3988
4118
  var MAX_VALUE_CHARS = 64 * 1024;
4119
+ var RECORDS_TOKEN_BUDGET = 5e3;
4120
+ var MAX_VALUE_CHARS_IN_VIEW = 4e3;
3989
4121
  function textResult(payload) {
3990
4122
  return {
3991
4123
  content: [{ type: "text", text: JSON.stringify(payload, null, 2) }]
@@ -4009,6 +4141,52 @@ function recordToJson(rec) {
4009
4141
  updatedAt: rec.updatedAt
4010
4142
  };
4011
4143
  }
4144
+ function recordToJsonPreview(rec, maxValueChars) {
4145
+ const view = recordToJson(rec);
4146
+ if (rec.value.length > maxValueChars) {
4147
+ const omitted = rec.value.length - maxValueChars;
4148
+ view.value = `${rec.value.slice(0, maxValueChars)}
4149
+ \u2026[+${omitted} chars omitted \u2014 use \`search\` for the full value]`;
4150
+ view.valueTruncated = true;
4151
+ view.valueChars = rec.value.length;
4152
+ }
4153
+ return view;
4154
+ }
4155
+ function boundRecordsForResult(records) {
4156
+ const views = [];
4157
+ let valuesPreviewed = 0;
4158
+ let tokens = 0;
4159
+ let overBudget = false;
4160
+ for (const rec of records) {
4161
+ const view = recordToJsonPreview(rec, MAX_VALUE_CHARS_IN_VIEW);
4162
+ if (view.valueTruncated === true)
4163
+ valuesPreviewed += 1;
4164
+ const cost = estimateTokens(JSON.stringify(view));
4165
+ if (views.length > 0 && tokens + cost > RECORDS_TOKEN_BUDGET) {
4166
+ overBudget = true;
4167
+ break;
4168
+ }
4169
+ views.push(view);
4170
+ tokens += cost;
4171
+ }
4172
+ return { views, shown: views.length, fetched: records.length, valuesPreviewed, overBudget };
4173
+ }
4174
+ function boundedRecordsNote(b, opts) {
4175
+ if (!b.overBudget && b.valuesPreviewed === 0 && b.fetched < opts.limit)
4176
+ return void 0;
4177
+ const parts = [];
4178
+ if (b.overBudget) {
4179
+ parts.push(`Showing ${b.shown} of ${b.fetched} fetched records \u2014 the result was trimmed to stay under the token budget. The \`woven\` block already carries the highest-value facts within its own cap.`);
4180
+ }
4181
+ if (b.valuesPreviewed > 0) {
4182
+ parts.push(`${b.valuesPreviewed} value(s) previewed to ${MAX_VALUE_CHARS_IN_VIEW} chars.`);
4183
+ }
4184
+ if (b.fetched >= opts.limit) {
4185
+ parts.push(`More records may exist beyond limit=${opts.limit}.`);
4186
+ }
4187
+ parts.push(`To see more: narrow with \`types\`, page with \`offset\` (e.g. offset=${(opts.offset ?? 0) + b.shown}), raise \`limit\`, or use \`search\` for specific facts.`);
4188
+ return parts.join(" ");
4189
+ }
4012
4190
  function clampLimit(limit, def) {
4013
4191
  if (limit === void 0)
4014
4192
  return def;
@@ -4026,7 +4204,9 @@ var scopeEnum = z.enum(MEMORY_SCOPES);
4026
4204
  var typeEnum = z.enum(MEMORY_TYPES);
4027
4205
  var readInput = {
4028
4206
  types: z.array(typeEnum).min(1).optional(),
4029
- limit: z.number().int().positive().max(MAX_LIMIT).optional()
4207
+ limit: z.number().int().positive().max(MAX_LIMIT).optional(),
4208
+ /** Skip the first N resolved records — page through large memory sets. */
4209
+ offset: z.number().int().nonnegative().optional()
4030
4210
  };
4031
4211
  var writeInput = {
4032
4212
  scope: scopeEnum.optional(),
@@ -4113,19 +4293,23 @@ function registerTools(server, source, hooks = {}) {
4113
4293
  }));
4114
4294
  reg("read", {
4115
4295
  title: "Recall agent memory",
4116
- 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.",
4296
+ 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). The `records` array is bounded to a token budget; a `note` explains how to page (`offset`) or narrow (`types`, `search`) when trimmed. Call this at the START of a session or task to recall what earlier sessions learned.",
4117
4297
  inputSchema: readInput
4118
4298
  }, async (args) => {
4119
4299
  const deps = await ready();
4120
4300
  const { backend, workspaceRoot, projectId, projectName } = deps;
4121
4301
  const mirrorToFiles = !deps.rootIsHomeOrFsRoot;
4122
4302
  const limit = clampLimit(args.limit, DEFAULT_READ_LIMIT);
4303
+ const offset = args.offset ?? 0;
4123
4304
  const sync = mirrorToFiles ? await syncProjectFiles(backend, { projectId, workspaceRoot }) : { synced: 0, pruned: 0, skipped: [] };
4124
4305
  const records = await backend.read({
4125
4306
  chain: defaultScopeChain(projectId),
4126
4307
  types: args.types,
4127
- limit
4308
+ limit,
4309
+ offset
4128
4310
  });
4311
+ const bounded = boundRecordsForResult(records);
4312
+ const note = boundedRecordsNote(bounded, { limit, offset });
4129
4313
  return textResult({
4130
4314
  projectId,
4131
4315
  projectName,
@@ -4133,7 +4317,12 @@ function registerTools(server, source, hooks = {}) {
4133
4317
  projectFilesPruned: sync.pruned,
4134
4318
  ...sync.skipped.length > 0 ? { skippedFiles: sync.skipped } : {},
4135
4319
  woven: weave(records),
4136
- records: records.map(recordToJson)
4320
+ recordsShown: bounded.shown,
4321
+ recordsFetched: bounded.fetched,
4322
+ limit,
4323
+ offset,
4324
+ ...note !== void 0 ? { note } : {},
4325
+ records: bounded.views
4137
4326
  });
4138
4327
  });
4139
4328
  reg("write", {
@@ -4285,11 +4474,17 @@ ${f.value}`);
4285
4474
  keyContains: args.keyContains,
4286
4475
  limit
4287
4476
  });
4477
+ const bounded = boundRecordsForResult(records);
4478
+ const note = boundedRecordsNote(bounded, { limit });
4288
4479
  return textResult({
4289
4480
  projectId,
4290
4481
  projectName,
4291
4482
  dbPath,
4292
- records: records.map(recordToJson)
4483
+ recordsShown: bounded.shown,
4484
+ recordsFetched: bounded.fetched,
4485
+ limit,
4486
+ ...note !== void 0 ? { note } : {},
4487
+ records: bounded.views
4293
4488
  });
4294
4489
  });
4295
4490
  reg("delete", {
@@ -4433,7 +4628,7 @@ ${f.value}`);
4433
4628
  embeddingsError = err instanceof Error ? err.message : String(err);
4434
4629
  }
4435
4630
  }
4436
- const update = await buildUpdateStatus("@prom.codes/memory-mcp", "0.15.1", { isDevBuild: false });
4631
+ const update = await buildUpdateStatus("@prom.codes/memory-mcp", "0.16.0", { isDevBuild: false });
4437
4632
  let recorder;
4438
4633
  try {
4439
4634
  const scopes = ["project-local", "project", "user"];
@@ -4515,7 +4710,7 @@ ${f.value}`);
4515
4710
  // dist/server.js
4516
4711
  var SERVER_IDENTITY = {
4517
4712
  name: "prometheus-memory-mcp",
4518
- version: "0.15.1",
4713
+ version: "0.16.0",
4519
4714
  title: "prom.codes Memory"
4520
4715
  };
4521
4716
  var SERVER_INSTRUCTIONS = "Persistent agent memory for this workspace \u2014 USE IT PROACTIVELY; the user will not tell you to. Protocol:\n1. ONE-TIME: if this workspace has no Prometheus memory rule yet, call memory_setup now (idempotent) so the protocol is installed into the runtime rule files and survives future sessions. (The server also auto-installs it on startup when a project rule file already exists \u2014 memory_setup covers the rest.)\n2. SESSION START: before any non-trivial task, call memory_read to recall facts, decisions and procedures from earlier sessions.\n3. DURING WORK: when the user states a durable preference, decision, correction or project fact, store it with memory_write (semantic for facts, procedural for how-tos) \u2014 without being asked.\n4. LOOK-UP: use memory_search for keyword recall when memory_read is not specific enough.\n5. SESSION END: consolidate what was learned with memory_capture.\nCall memory_status anytime to check what is stored and whether the rule is installed. Never store secrets, API keys or credentials \u2014 such writes are rejected.";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prom.codes/memory-mcp",
3
- "version": "0.15.1",
3
+ "version": "0.16.0",
4
4
  "description": "prom.codes Memory — persistent, local-first agent memory as an MCP server.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -26,7 +26,7 @@
26
26
  },
27
27
  "dependencies": {
28
28
  "@modelcontextprotocol/sdk": "^1.29.0",
29
- "better-sqlite3": "^12.10.0",
29
+ "better-sqlite3": "12.10.0",
30
30
  "zod": "^4.4.3"
31
31
  },
32
32
  "optionalDependencies": {