@prom.codes/memory-mcp 0.5.1 → 0.7.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 +74 -3
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -1307,6 +1307,45 @@ function reciprocalRankFusion(lists, options = {}) {
1307
1307
  return options.limit !== void 0 && options.limit >= 0 ? merged.slice(0, options.limit) : merged;
1308
1308
  }
1309
1309
 
1310
+ // dist/temporal.js
1311
+ var RRF_K = 60;
1312
+ var DEFAULT_TEMPORAL_WEIGHT = 2;
1313
+ var RECENT_RE = /\b(latest|most[\s-]?recent|recently|recent|current|currently|now|nowadays|today|newest|up[\s-]?to[\s-]?date|so far)\b/i;
1314
+ var EARLIEST_RE = /\b(earliest|oldest|first|initial|initially|original|originally|at the (?:very )?(?:start|beginning)|back then)\b/i;
1315
+ function detectTemporalIntent(query) {
1316
+ if (query === "")
1317
+ return null;
1318
+ const recent = RECENT_RE.test(query);
1319
+ const earliest = EARLIEST_RE.test(query);
1320
+ if (recent === earliest)
1321
+ return null;
1322
+ return { direction: recent ? "recent" : "earliest" };
1323
+ }
1324
+ function parseTimestamp(iso) {
1325
+ if (iso === null || iso === void 0 || iso === "")
1326
+ return null;
1327
+ const ms = Date.parse(iso);
1328
+ return Number.isFinite(ms) ? ms : null;
1329
+ }
1330
+ function applyTemporalRanking(hits, intent, getTimestampMs, weight = DEFAULT_TEMPORAL_WEIGHT) {
1331
+ if (hits.length <= 1 || weight <= 0)
1332
+ return [...hits];
1333
+ const stamped = hits.map((hit, index) => ({ hit, index, ts: getTimestampMs(hit) })).filter((x) => x.ts !== null);
1334
+ if (stamped.length <= 1)
1335
+ return [...hits];
1336
+ const byTime = [...stamped].sort((a, b) => intent.direction === "recent" ? b.ts - a.ts : a.ts - b.ts);
1337
+ const tempRank = /* @__PURE__ */ new Map();
1338
+ byTime.forEach((x, rank) => tempRank.set(x.index, rank));
1339
+ const scored = hits.map((hit, index) => {
1340
+ const posScore = 1 / (RRF_K + index);
1341
+ const tr = tempRank.get(index);
1342
+ const tempScore = tr === void 0 ? 0 : 1 / (RRF_K + tr);
1343
+ return { hit, index, score: posScore + weight * tempScore };
1344
+ });
1345
+ scored.sort((a, b) => b.score - a.score || a.index - b.index);
1346
+ return scored.map((s) => s.hit);
1347
+ }
1348
+
1310
1349
  // dist/types.js
1311
1350
  var MEMORY_SCOPES = [
1312
1351
  "system",
@@ -1480,6 +1519,8 @@ var SqliteMemoryBackend = class {
1480
1519
  embedder;
1481
1520
  reranker;
1482
1521
  rewriter;
1522
+ temporalEnabled;
1523
+ temporalWeight;
1483
1524
  /** Record ids whose vector is missing/stale, awaiting a batched embed. */
1484
1525
  pendingEmbed = /* @__PURE__ */ new Set();
1485
1526
  closed = false;
@@ -1496,6 +1537,8 @@ var SqliteMemoryBackend = class {
1496
1537
  this.embedder = opts.embedder;
1497
1538
  this.reranker = opts.reranker;
1498
1539
  this.rewriter = opts.rewriter;
1540
+ this.temporalEnabled = opts.temporal?.enabled ?? false;
1541
+ this.temporalWeight = opts.temporal?.weight ?? DEFAULT_TEMPORAL_WEIGHT;
1499
1542
  if (this.embedder !== void 0)
1500
1543
  this.queueUnembedded();
1501
1544
  }
@@ -1619,7 +1662,20 @@ var SqliteMemoryBackend = class {
1619
1662
  ], { limit: poolLimit }).map((f) => f.payload);
1620
1663
  }
1621
1664
  const reranked = input.rerank === false ? pool : await this.rerankPool(input.query, pool, finalLimit);
1622
- return reranked.slice(0, finalLimit);
1665
+ const temporalOn = input.temporal ?? this.temporalEnabled;
1666
+ const ordered = temporalOn ? this.applyTemporal(input.query, reranked) : reranked;
1667
+ return ordered.slice(0, finalLimit);
1668
+ }
1669
+ /**
1670
+ * M5: blend the current order with a timestamp order when the query expresses
1671
+ * a clear temporal direction. Uses `updatedAt` (falls back to `createdAt`) as
1672
+ * the record's time. No-op when no intent is detected.
1673
+ */
1674
+ applyTemporal(query, hits) {
1675
+ const intent = detectTemporalIntent(query);
1676
+ if (intent === null)
1677
+ return hits;
1678
+ return applyTemporalRanking(hits, intent, (h) => parseTimestamp(h.record.updatedAt) ?? parseTimestamp(h.record.createdAt), this.temporalWeight);
1623
1679
  }
1624
1680
  /**
1625
1681
  * M4: rewrite the query for the retrieval channels (HyDE concat). Returns the
@@ -2076,6 +2132,18 @@ function discoverMemoryRewriter(env) {
2076
2132
  }
2077
2133
  throw new Error(`unknown PROMETHEUS_MEMORY_REWRITE_PROVIDER="${forced}" (expected "none", "mistral", "openai", or "generic")`);
2078
2134
  }
2135
+ function discoverMemoryTemporal(env) {
2136
+ const raw = (env.PROMETHEUS_MEMORY_TEMPORAL ?? "on").toLowerCase();
2137
+ const enabled = !(raw === "off" || raw === "0" || raw === "false" || raw === "no");
2138
+ const rawWeight = env.PROMETHEUS_MEMORY_TEMPORAL_WEIGHT;
2139
+ let weight = DEFAULT_TEMPORAL_WEIGHT;
2140
+ if (rawWeight !== void 0 && rawWeight !== "") {
2141
+ const n = Number.parseFloat(rawWeight);
2142
+ if (Number.isFinite(n) && n >= 0)
2143
+ weight = n;
2144
+ }
2145
+ return { enabled, weight };
2146
+ }
2079
2147
  function composeFromEnv(opts) {
2080
2148
  const env = opts.env;
2081
2149
  const override = (opts.workspaceRootOverride ?? "").trim();
@@ -2090,10 +2158,12 @@ function composeFromEnv(opts) {
2090
2158
  const { id: rerankerId, provider: reranker } = discoverMemoryReranker(env);
2091
2159
  const { id: extractorId, provider: extractor } = discoverMemoryExtractor(env);
2092
2160
  const { id: rewriterId, provider: rewriter } = discoverMemoryRewriter(env);
2161
+ const temporal = discoverMemoryTemporal(env);
2093
2162
  const backend = new SqliteMemoryBackend(dbPath, {
2094
2163
  ...embedder !== void 0 ? { embedder } : {},
2095
2164
  ...reranker !== null ? { reranker } : {},
2096
- ...rewriter !== null ? { rewriter } : {}
2165
+ ...rewriter !== null ? { rewriter } : {},
2166
+ temporal
2097
2167
  });
2098
2168
  return {
2099
2169
  backend,
@@ -2109,6 +2179,7 @@ function composeFromEnv(opts) {
2109
2179
  extractorId,
2110
2180
  rewriter,
2111
2181
  rewriterId,
2182
+ temporalEnabled: temporal.enabled,
2112
2183
  close: () => backend.close()
2113
2184
  };
2114
2185
  }
@@ -2735,7 +2806,7 @@ async function main() {
2735
2806
  env,
2736
2807
  ...override !== void 0 && override !== "" ? { workspaceRootOverride: override } : {}
2737
2808
  });
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}
2809
+ 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"}
2739
2810
  `);
2740
2811
  registerTools(server, composed);
2741
2812
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prom.codes/memory-mcp",
3
- "version": "0.5.1",
3
+ "version": "0.7.0",
4
4
  "description": "prom.codes Memory — persistent, local-first agent memory as an MCP server.",
5
5
  "type": "module",
6
6
  "bin": {