@prom.codes/memory-mcp 0.3.0 → 0.3.2

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 +467 -14
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -506,6 +506,377 @@ var PrometheusEmbeddingProvider = class {
506
506
  }
507
507
  };
508
508
 
509
+ // ../rerank-voyage/dist/index.js
510
+ var DEFAULT_MODEL = "rerank-2.5";
511
+ var DEFAULT_BASE_URL = "https://api.voyageai.com/v1";
512
+ var DEFAULT_BATCH3 = 100;
513
+ var DEFAULT_RETRIES3 = 6;
514
+ var DEFAULT_BACKOFF3 = 2e3;
515
+ var DEFAULT_RETRY_MAX2 = 6e4;
516
+ function parseRetryAfterMs2(value, now = Date.now()) {
517
+ if (value === null)
518
+ return null;
519
+ const trimmed = value.trim();
520
+ if (trimmed === "")
521
+ return null;
522
+ if (/^[0-9]+(\.[0-9]+)?$/.test(trimmed)) {
523
+ const secs = Number(trimmed);
524
+ if (!Number.isFinite(secs) || secs < 0)
525
+ return null;
526
+ return Math.round(secs * 1e3);
527
+ }
528
+ if (!/[A-Za-z]/.test(trimmed))
529
+ return null;
530
+ const ts = Date.parse(trimmed);
531
+ if (!Number.isFinite(ts))
532
+ return null;
533
+ const delta = ts - now;
534
+ return delta > 0 ? delta : 0;
535
+ }
536
+ function sleep3(ms, signal) {
537
+ return new Promise((resolve2, reject) => {
538
+ if (signal?.aborted === true) {
539
+ reject(new Error("aborted"));
540
+ return;
541
+ }
542
+ const timer = setTimeout(() => {
543
+ signal?.removeEventListener("abort", onAbort);
544
+ resolve2();
545
+ }, ms);
546
+ const onAbort = () => {
547
+ clearTimeout(timer);
548
+ reject(new Error("aborted"));
549
+ };
550
+ signal?.addEventListener("abort", onAbort, { once: true });
551
+ });
552
+ }
553
+ function nonRetryable3(message) {
554
+ const err = new Error(message);
555
+ err.nonRetryable = true;
556
+ return err;
557
+ }
558
+ var VoyageRerankProvider = class {
559
+ name;
560
+ model;
561
+ region;
562
+ #baseUrl;
563
+ #apiKey;
564
+ #batchSize;
565
+ #maxRetries;
566
+ #retryBaseMs;
567
+ #retryMaxMs;
568
+ #fetch;
569
+ constructor(opts) {
570
+ if (typeof opts.apiKey !== "string" || opts.apiKey === "") {
571
+ throw new Error("VoyageRerankProvider: apiKey is required");
572
+ }
573
+ if (opts.batchSize !== void 0 && (!Number.isInteger(opts.batchSize) || opts.batchSize <= 0 || opts.batchSize > 1e3)) {
574
+ throw new Error(`VoyageRerankProvider: batchSize must be an integer in 1..1000, got ${opts.batchSize}`);
575
+ }
576
+ this.model = opts.model ?? DEFAULT_MODEL;
577
+ this.name = opts.name ?? `voyage:${this.model}`;
578
+ this.region = opts.region ?? "us";
579
+ this.#baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
580
+ this.#apiKey = opts.apiKey;
581
+ this.#batchSize = opts.batchSize ?? DEFAULT_BATCH3;
582
+ this.#maxRetries = opts.maxRetries ?? DEFAULT_RETRIES3;
583
+ this.#retryBaseMs = opts.retryBaseMs ?? DEFAULT_BACKOFF3;
584
+ this.#retryMaxMs = opts.retryMaxMs ?? DEFAULT_RETRY_MAX2;
585
+ this.#fetch = opts.fetch ?? fetch;
586
+ }
587
+ async rerank(query, candidates, opts) {
588
+ if (candidates.length === 0)
589
+ return [];
590
+ const all = new Array(candidates.length);
591
+ let cursor = 0;
592
+ for (let start = 0; start < candidates.length; start += this.#batchSize) {
593
+ const slice = candidates.slice(start, start + this.#batchSize);
594
+ const scored = await this.#rerankBatch(query, slice, opts?.signal);
595
+ for (const hit of scored) {
596
+ const globalIndex = start + hit.localIndex;
597
+ const cand = candidates[globalIndex];
598
+ all[cursor++] = { id: cand.id, index: globalIndex, score: hit.score };
599
+ }
600
+ }
601
+ all.sort((a, b) => b.score - a.score);
602
+ if (opts?.topK !== void 0 && opts.topK >= 0 && opts.topK < all.length) {
603
+ return all.slice(0, opts.topK);
604
+ }
605
+ return all;
606
+ }
607
+ async #rerankBatch(query, batch, signal) {
608
+ const body = {
609
+ query,
610
+ documents: batch.map((c) => c.text),
611
+ model: this.model,
612
+ return_documents: false,
613
+ truncation: true
614
+ };
615
+ const init = {
616
+ method: "POST",
617
+ headers: {
618
+ "content-type": "application/json",
619
+ authorization: `Bearer ${this.#apiKey}`
620
+ },
621
+ body: JSON.stringify(body)
622
+ };
623
+ if (signal !== void 0)
624
+ init.signal = signal;
625
+ let attempt = 0;
626
+ let lastError = null;
627
+ while (attempt <= this.#maxRetries) {
628
+ try {
629
+ const res = await this.#fetch(`${this.#baseUrl}/rerank`, init);
630
+ if (res.status === 429 || res.status >= 500 && res.status < 600) {
631
+ lastError = new Error(`${this.name}: HTTP ${res.status}`);
632
+ attempt += 1;
633
+ if (attempt > this.#maxRetries)
634
+ break;
635
+ const backoff = this.#computeBackoff(attempt, res.headers.get("retry-after"));
636
+ await sleep3(backoff, signal);
637
+ continue;
638
+ }
639
+ if (!res.ok) {
640
+ const text = await res.text().catch(() => "");
641
+ throw nonRetryable3(`${this.name}: HTTP ${res.status} ${res.statusText}${text === "" ? "" : ` \u2014 ${text}`}`);
642
+ }
643
+ const payload = await res.json();
644
+ return this.#decode(payload, batch.length);
645
+ } catch (err) {
646
+ if (err?.name === "AbortError")
647
+ throw err;
648
+ if (err?.nonRetryable === true)
649
+ throw err;
650
+ if (attempt >= this.#maxRetries)
651
+ throw err;
652
+ lastError = err;
653
+ attempt += 1;
654
+ await sleep3(this.#computeBackoff(attempt, null), signal);
655
+ }
656
+ }
657
+ throw lastError instanceof Error ? lastError : new Error(`${this.name}: exhausted ${this.#maxRetries} retries`);
658
+ }
659
+ #computeBackoff(attempt, retryAfterHeader) {
660
+ const exp = this.#retryBaseMs * 2 ** Math.max(0, attempt - 1);
661
+ const advised = parseRetryAfterMs2(retryAfterHeader);
662
+ const lower = advised === null ? exp : Math.max(exp, advised);
663
+ return Math.min(lower, this.#retryMaxMs);
664
+ }
665
+ #decode(payload, expected) {
666
+ if (!Array.isArray(payload.data) || payload.data.length !== expected) {
667
+ throw nonRetryable3(`${this.name}: expected ${expected} rerank rows, got ${payload.data?.length ?? 0}`);
668
+ }
669
+ return payload.data.map((row) => {
670
+ if (!Number.isInteger(row.index) || row.index < 0 || row.index >= expected) {
671
+ throw nonRetryable3(`${this.name}: invalid index ${row.index} in rerank response`);
672
+ }
673
+ if (typeof row.relevance_score !== "number" || !Number.isFinite(row.relevance_score)) {
674
+ throw nonRetryable3(`${this.name}: invalid relevance_score ${row.relevance_score} at index ${row.index}`);
675
+ }
676
+ return { localIndex: row.index, score: row.relevance_score };
677
+ });
678
+ }
679
+ };
680
+
681
+ // ../rerank-openai-compat/dist/index.js
682
+ var DEFAULT_MODEL2 = "bge-reranker-base";
683
+ var DEFAULT_BATCH4 = 100;
684
+ var DEFAULT_RETRIES4 = 6;
685
+ var DEFAULT_BACKOFF4 = 2e3;
686
+ var DEFAULT_RETRY_MAX3 = 6e4;
687
+ var DEFAULT_TIMEOUT = 18e4;
688
+ function parseRetryAfterMs3(value, now = Date.now()) {
689
+ if (value === null)
690
+ return null;
691
+ const trimmed = value.trim();
692
+ if (trimmed === "")
693
+ return null;
694
+ if (/^[0-9]+(\.[0-9]+)?$/.test(trimmed)) {
695
+ const secs = Number(trimmed);
696
+ if (!Number.isFinite(secs) || secs < 0)
697
+ return null;
698
+ return Math.round(secs * 1e3);
699
+ }
700
+ if (!/[A-Za-z]/.test(trimmed))
701
+ return null;
702
+ const ts = Date.parse(trimmed);
703
+ if (!Number.isFinite(ts))
704
+ return null;
705
+ const delta = ts - now;
706
+ return delta > 0 ? delta : 0;
707
+ }
708
+ function sleep4(ms, signal) {
709
+ return new Promise((resolve2, reject) => {
710
+ if (signal?.aborted === true) {
711
+ reject(new Error("aborted"));
712
+ return;
713
+ }
714
+ const timer = setTimeout(() => {
715
+ signal?.removeEventListener("abort", onAbort);
716
+ resolve2();
717
+ }, ms);
718
+ const onAbort = () => {
719
+ clearTimeout(timer);
720
+ reject(new Error("aborted"));
721
+ };
722
+ signal?.addEventListener("abort", onAbort, { once: true });
723
+ });
724
+ }
725
+ function nonRetryable4(message) {
726
+ const err = new Error(message);
727
+ err.nonRetryable = true;
728
+ return err;
729
+ }
730
+ var OpenAICompatRerankProvider = class {
731
+ name;
732
+ model;
733
+ region;
734
+ #baseUrl;
735
+ #apiKey;
736
+ #batchSize;
737
+ #maxRetries;
738
+ #retryBaseMs;
739
+ #retryMaxMs;
740
+ #timeoutMs;
741
+ #fetch;
742
+ constructor(opts) {
743
+ if (typeof opts.baseUrl !== "string" || opts.baseUrl === "") {
744
+ throw new Error("OpenAICompatRerankProvider: baseUrl is required");
745
+ }
746
+ if (opts.batchSize !== void 0 && (!Number.isInteger(opts.batchSize) || opts.batchSize <= 0 || opts.batchSize > 1e3)) {
747
+ throw new Error(`OpenAICompatRerankProvider: batchSize must be an integer in 1..1000, got ${opts.batchSize}`);
748
+ }
749
+ if (opts.timeoutMs !== void 0 && (!Number.isInteger(opts.timeoutMs) || opts.timeoutMs < 0)) {
750
+ throw new Error(`OpenAICompatRerankProvider: timeoutMs must be a non-negative integer (0 disables), got ${opts.timeoutMs}`);
751
+ }
752
+ this.model = opts.model ?? DEFAULT_MODEL2;
753
+ this.name = opts.name ?? `openai-compat:${this.model}`;
754
+ this.region = opts.region ?? "self-hosted";
755
+ this.#baseUrl = opts.baseUrl.replace(/\/+$/, "");
756
+ this.#apiKey = opts.apiKey === void 0 || opts.apiKey === "" ? void 0 : opts.apiKey;
757
+ this.#batchSize = opts.batchSize ?? DEFAULT_BATCH4;
758
+ this.#maxRetries = opts.maxRetries ?? DEFAULT_RETRIES4;
759
+ this.#retryBaseMs = opts.retryBaseMs ?? DEFAULT_BACKOFF4;
760
+ this.#retryMaxMs = opts.retryMaxMs ?? DEFAULT_RETRY_MAX3;
761
+ this.#timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT;
762
+ this.#fetch = opts.fetch ?? fetch;
763
+ }
764
+ async rerank(query, candidates, opts) {
765
+ if (candidates.length === 0)
766
+ return [];
767
+ const all = new Array(candidates.length);
768
+ let cursor = 0;
769
+ for (let start = 0; start < candidates.length; start += this.#batchSize) {
770
+ const slice = candidates.slice(start, start + this.#batchSize);
771
+ const scored = await this.#rerankBatch(query, slice, opts?.signal);
772
+ for (const hit of scored) {
773
+ const globalIndex = start + hit.localIndex;
774
+ const cand = candidates[globalIndex];
775
+ all[cursor++] = { id: cand.id, index: globalIndex, score: hit.score };
776
+ }
777
+ }
778
+ all.sort((a, b) => b.score - a.score);
779
+ if (opts?.topK !== void 0 && opts.topK >= 0 && opts.topK < all.length) {
780
+ return all.slice(0, opts.topK);
781
+ }
782
+ return all;
783
+ }
784
+ async #rerankBatch(query, batch, signal) {
785
+ const body = {
786
+ query,
787
+ documents: batch.map((c) => c.text),
788
+ model: this.model,
789
+ return_documents: false
790
+ };
791
+ const headers = { "content-type": "application/json" };
792
+ if (this.#apiKey !== void 0)
793
+ headers.authorization = `Bearer ${this.#apiKey}`;
794
+ const payloadJson = JSON.stringify(body);
795
+ let attempt = 0;
796
+ let lastError = null;
797
+ while (attempt <= this.#maxRetries) {
798
+ const controller = new AbortController();
799
+ let timedOut = false;
800
+ let timer;
801
+ if (this.#timeoutMs > 0) {
802
+ timer = setTimeout(() => {
803
+ timedOut = true;
804
+ controller.abort();
805
+ }, this.#timeoutMs);
806
+ }
807
+ const onParentAbort = () => controller.abort();
808
+ if (signal !== void 0) {
809
+ if (signal.aborted)
810
+ controller.abort();
811
+ else
812
+ signal.addEventListener("abort", onParentAbort, { once: true });
813
+ }
814
+ const init = {
815
+ method: "POST",
816
+ headers,
817
+ body: payloadJson,
818
+ signal: controller.signal
819
+ };
820
+ try {
821
+ const res = await this.#fetch(`${this.#baseUrl}/rerank`, init);
822
+ if (res.status === 429 || res.status >= 500 && res.status < 600) {
823
+ lastError = new Error(`${this.name}: HTTP ${res.status}`);
824
+ attempt += 1;
825
+ if (attempt > this.#maxRetries)
826
+ break;
827
+ const backoff = this.#computeBackoff(attempt, res.headers.get("retry-after"));
828
+ await sleep4(backoff, signal);
829
+ continue;
830
+ }
831
+ if (!res.ok) {
832
+ const text = await res.text().catch(() => "");
833
+ throw nonRetryable4(`${this.name}: HTTP ${res.status} ${res.statusText}${text === "" ? "" : ` \u2014 ${text}`}`);
834
+ }
835
+ const payload = await res.json();
836
+ return this.#decode(payload, batch.length);
837
+ } catch (err) {
838
+ const isAbort = err?.name === "AbortError";
839
+ if (isAbort && !timedOut)
840
+ throw err;
841
+ if (!isAbort && err?.nonRetryable === true)
842
+ throw err;
843
+ const normalized = timedOut ? new Error(`${this.name}: request timed out after ${this.#timeoutMs}ms`) : err;
844
+ if (attempt >= this.#maxRetries)
845
+ throw normalized;
846
+ lastError = normalized;
847
+ attempt += 1;
848
+ await sleep4(this.#computeBackoff(attempt, null), signal);
849
+ } finally {
850
+ if (timer !== void 0)
851
+ clearTimeout(timer);
852
+ if (signal !== void 0)
853
+ signal.removeEventListener("abort", onParentAbort);
854
+ }
855
+ }
856
+ throw lastError instanceof Error ? lastError : new Error(`${this.name}: exhausted ${this.#maxRetries} retries`);
857
+ }
858
+ #computeBackoff(attempt, retryAfterHeader) {
859
+ const exp = this.#retryBaseMs * 2 ** Math.max(0, attempt - 1);
860
+ const advised = parseRetryAfterMs3(retryAfterHeader);
861
+ const lower = advised === null ? exp : Math.max(exp, advised);
862
+ return Math.min(lower, this.#retryMaxMs);
863
+ }
864
+ #decode(payload, expected) {
865
+ if (!Array.isArray(payload.results) || payload.results.length !== expected) {
866
+ throw nonRetryable4(`${this.name}: expected ${expected} rerank rows, got ${payload.results?.length ?? 0}`);
867
+ }
868
+ return payload.results.map((row) => {
869
+ if (!Number.isInteger(row.index) || row.index < 0 || row.index >= expected) {
870
+ throw nonRetryable4(`${this.name}: invalid index ${row.index} in rerank response`);
871
+ }
872
+ if (typeof row.relevance_score !== "number" || !Number.isFinite(row.relevance_score)) {
873
+ throw nonRetryable4(`${this.name}: invalid relevance_score ${row.relevance_score} at index ${row.index}`);
874
+ }
875
+ return { localIndex: row.index, score: row.relevance_score };
876
+ });
877
+ }
878
+ };
879
+
509
880
  // dist/api-key.js
510
881
  var KEY_PATTERN = /^prom_(live|test)_[A-Za-z0-9]{10,}$/;
511
882
  var API_KEY_ENV = "PROMETHEUS_API_KEY";
@@ -747,6 +1118,7 @@ function rowToRecord(row) {
747
1118
  var SqliteMemoryBackend = class {
748
1119
  db;
749
1120
  embedder;
1121
+ reranker;
750
1122
  /** Record ids whose vector is missing/stale, awaiting a batched embed. */
751
1123
  pendingEmbed = /* @__PURE__ */ new Set();
752
1124
  closed = false;
@@ -761,6 +1133,7 @@ var SqliteMemoryBackend = class {
761
1133
  this.db.exec(VEC_SCHEMA);
762
1134
  this.db.exec(`INSERT INTO agent_memory_fts (agent_memory_fts) VALUES ('rebuild')`);
763
1135
  this.embedder = opts.embedder;
1136
+ this.reranker = opts.reranker;
764
1137
  if (this.embedder !== void 0)
765
1138
  this.queueUnembedded();
766
1139
  }
@@ -871,15 +1244,44 @@ var SqliteMemoryBackend = class {
871
1244
  vecHits = [];
872
1245
  }
873
1246
  }
874
- if (vecHits.length === 0)
875
- return ftsHits.slice(0, finalLimit);
876
- if (ftsHits.length === 0)
877
- return vecHits.slice(0, finalLimit);
878
- const fused = reciprocalRankFusion([
879
- { id: "fts", items: ftsHits.map((h) => ({ key: h.record.id, payload: h })) },
880
- { id: "vec", items: vecHits.map((h) => ({ key: h.record.id, payload: h })) }
881
- ], { limit: finalLimit });
882
- return fused.map((f) => f.payload);
1247
+ let pool;
1248
+ if (vecHits.length === 0) {
1249
+ pool = ftsHits;
1250
+ } else if (ftsHits.length === 0) {
1251
+ pool = vecHits;
1252
+ } else {
1253
+ pool = reciprocalRankFusion([
1254
+ { id: "fts", items: ftsHits.map((h) => ({ key: h.record.id, payload: h })) },
1255
+ { id: "vec", items: vecHits.map((h) => ({ key: h.record.id, payload: h })) }
1256
+ ], { limit: poolLimit }).map((f) => f.payload);
1257
+ }
1258
+ const reranked = await this.rerankPool(input.query, pool, finalLimit);
1259
+ return reranked.slice(0, finalLimit);
1260
+ }
1261
+ /**
1262
+ * Reorder a first-stage pool with the cross-encoder reranker, scoring each
1263
+ * candidate's `key + value` jointly against the query. Returns the pool
1264
+ * unchanged when no reranker is configured, the pool is trivial, or the
1265
+ * provider errors.
1266
+ */
1267
+ async rerankPool(query, pool, topK) {
1268
+ if (this.reranker === void 0 || pool.length <= 1)
1269
+ return pool;
1270
+ try {
1271
+ const candidates = pool.map((h) => ({
1272
+ id: h.record.id,
1273
+ text: `${h.record.key}
1274
+ ${h.record.value}`
1275
+ }));
1276
+ const hits = await this.reranker.rerank(query, candidates, { topK });
1277
+ if (hits.length === 0)
1278
+ return pool;
1279
+ const byId = new Map(pool.map((h) => [h.record.id, h]));
1280
+ const reordered = hits.map((h) => byId.get(h.id)).filter((h) => h !== void 0);
1281
+ return reordered.length > 0 ? reordered : pool;
1282
+ } catch {
1283
+ return pool;
1284
+ }
883
1285
  }
884
1286
  /** FTS5 BM25 keyword channel → ranked hits (best first). */
885
1287
  ftsSearch(input, limit) {
@@ -1149,17 +1551,64 @@ function discoverMemoryEmbedder(env) {
1149
1551
  }
1150
1552
  return { id: "none", embedder: void 0 };
1151
1553
  }
1554
+ function discoverMemoryReranker(env) {
1555
+ const forced = (env.PROMETHEUS_MEMORY_RERANK_PROVIDER ?? "none").toLowerCase();
1556
+ if (forced === "" || forced === "none")
1557
+ return { id: "none", provider: null };
1558
+ if (forced === "voyage") {
1559
+ const apiKey = env.VOYAGE_API_KEY;
1560
+ if (apiKey === void 0 || apiKey === "") {
1561
+ throw new Error('PROMETHEUS_MEMORY_RERANK_PROVIDER="voyage" requires VOYAGE_API_KEY.');
1562
+ }
1563
+ const provider = new VoyageRerankProvider({
1564
+ name: "voyage-rerank",
1565
+ apiKey,
1566
+ model: env.VOYAGE_RERANK_MODEL ?? "rerank-2.5",
1567
+ region: "us",
1568
+ baseUrl: env.VOYAGE_BASE_URL ?? "https://api.voyageai.com/v1",
1569
+ maxRetries: intEnv(env, "VOYAGE_RERANK_MAX_RETRIES", 6),
1570
+ retryBaseMs: intEnv(env, "VOYAGE_RERANK_RETRY_BASE_MS", 2e3),
1571
+ batchSize: intEnv(env, "VOYAGE_RERANK_BATCH", 100)
1572
+ });
1573
+ return { id: "voyage", provider };
1574
+ }
1575
+ if (forced === "bge" || forced === "generic") {
1576
+ const baseUrl = env.PROMETHEUS_MEMORY_RERANK_ENDPOINT;
1577
+ if (baseUrl === void 0 || baseUrl === "") {
1578
+ throw new Error(`PROMETHEUS_MEMORY_RERANK_PROVIDER="${forced}" requires PROMETHEUS_MEMORY_RERANK_ENDPOINT.`);
1579
+ }
1580
+ const model = env.PROMETHEUS_MEMORY_RERANK_MODEL ?? "bge-reranker-base";
1581
+ const provider = new OpenAICompatRerankProvider({
1582
+ name: env.PROMETHEUS_MEMORY_RERANK_NAME ?? `bge-rerank:${model}`,
1583
+ model,
1584
+ region: "self-hosted",
1585
+ baseUrl,
1586
+ maxRetries: intEnv(env, "PROMETHEUS_MEMORY_RERANK_MAX_RETRIES", 6),
1587
+ retryBaseMs: intEnv(env, "PROMETHEUS_MEMORY_RERANK_RETRY_BASE_MS", 2e3),
1588
+ batchSize: intEnv(env, "PROMETHEUS_MEMORY_RERANK_BATCH", 100),
1589
+ timeoutMs: intEnv(env, "PROMETHEUS_MEMORY_RERANK_TIMEOUT_MS", 18e4),
1590
+ ...env.PROMETHEUS_MEMORY_RERANK_API_KEY ? { apiKey: env.PROMETHEUS_MEMORY_RERANK_API_KEY } : {}
1591
+ });
1592
+ return { id: "bge", provider };
1593
+ }
1594
+ throw new Error(`unknown PROMETHEUS_MEMORY_RERANK_PROVIDER="${forced}" (expected "none", "voyage", or "bge")`);
1595
+ }
1152
1596
  function composeFromEnv(opts) {
1153
1597
  const env = opts.env;
1154
1598
  const override = (opts.workspaceRootOverride ?? "").trim();
1155
1599
  const envRoot = (env.PROMETHEUS_WORKSPACE_ROOT ?? "").trim();
1156
- const workspaceRoot = resolve(override !== "" ? override : envRoot !== "" ? envRoot : process.cwd());
1600
+ const claudeRoot = (env.CLAUDE_PROJECT_DIR ?? "").trim();
1601
+ const workspaceRoot = resolve(override !== "" ? override : envRoot !== "" ? envRoot : claudeRoot !== "" ? claudeRoot : process.cwd());
1157
1602
  const projectId = projectIdFor(workspaceRoot);
1158
1603
  const projectName = basename(workspaceRoot) || workspaceRoot;
1159
1604
  const rawDbPath = env.PROMETHEUS_MEMORY_DB_PATH;
1160
1605
  const dbPath = rawDbPath !== void 0 && rawDbPath !== "" ? rawDbPath : defaultMemoryDbPath();
1161
1606
  const { id: embedderId, embedder } = discoverMemoryEmbedder(env);
1162
- const backend = new SqliteMemoryBackend(dbPath, embedder !== void 0 ? { embedder } : {});
1607
+ const { id: rerankerId, provider: reranker } = discoverMemoryReranker(env);
1608
+ const backend = new SqliteMemoryBackend(dbPath, {
1609
+ ...embedder !== void 0 ? { embedder } : {},
1610
+ ...reranker !== null ? { reranker } : {}
1611
+ });
1163
1612
  return {
1164
1613
  backend,
1165
1614
  workspaceRoot,
@@ -1168,6 +1617,8 @@ function composeFromEnv(opts) {
1168
1617
  dbPath,
1169
1618
  embeddingsEnabled: embedder !== void 0,
1170
1619
  embedderId,
1620
+ reranker,
1621
+ rerankerId,
1171
1622
  close: () => backend.close()
1172
1623
  };
1173
1624
  }
@@ -1760,6 +2211,8 @@ var SERVER_INSTRUCTIONS = "Persistent agent memory for this workspace. At the ST
1760
2211
  async function main() {
1761
2212
  const env = process.env;
1762
2213
  const explicitRoot = (env.PROMETHEUS_WORKSPACE_ROOT ?? "").trim();
2214
+ const claudeRoot = (env.CLAUDE_PROJECT_DIR ?? "").trim();
2215
+ const eagerVia = explicitRoot !== "" ? "PROMETHEUS_WORKSPACE_ROOT" : claudeRoot !== "" ? "CLAUDE_PROJECT_DIR" : null;
1763
2216
  const transport = new StdioServerTransport();
1764
2217
  const server = new McpServer2(SERVER_IDENTITY, {
1765
2218
  capabilities: { tools: {} },
@@ -1784,12 +2237,12 @@ async function main() {
1784
2237
  env,
1785
2238
  ...override !== void 0 && override !== "" ? { workspaceRootOverride: override } : {}
1786
2239
  });
1787
- 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)"}
2240
+ 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}
1788
2241
  `);
1789
2242
  registerTools(server, composed);
1790
2243
  };
1791
- if (explicitRoot !== "") {
1792
- boot(void 0, "PROMETHEUS_WORKSPACE_ROOT");
2244
+ if (eagerVia !== null) {
2245
+ boot(void 0, eagerVia);
1793
2246
  await server.connect(transport);
1794
2247
  return;
1795
2248
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prom.codes/memory-mcp",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "prom.codes Memory — persistent, local-first agent memory as an MCP server.",
5
5
  "type": "module",
6
6
  "bin": {