@prom.codes/context-mcp 0.2.0 → 0.2.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 (3) hide show
  1. package/README.md +2 -2
  2. package/dist/bin.js +480 -7
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @prom.codes/context-mcp
2
2
 
3
- Prometheus Context Engine — local-first codebase indexing & retrieval as an MCP server (stdio).
3
+ prom.codes Context — local-first codebase indexing & retrieval as an MCP server (stdio).
4
4
 
5
5
  ## Quick start
6
6
 
@@ -22,7 +22,7 @@ Prometheus Context Engine — local-first codebase indexing & retrieval as an MC
22
22
 
23
23
  With a `PROMETHEUS_API_KEY` the server runs in managed mode: it indexes
24
24
  your workspace into a local SQLite database (`~/.prometheus/<hash>.db`)
25
- and embeds via the Prometheus API. Your code never leaves your machine —
25
+ and embeds via the prom.codes API. Your code never leaves your machine —
26
26
  only embedding text transits to the API.
27
27
 
28
28
  ## Native modules
package/dist/bin.js CHANGED
@@ -7160,6 +7160,377 @@ var HashEmbeddingProvider = class {
7160
7160
  }
7161
7161
  };
7162
7162
 
7163
+ // ../rerank-voyage/dist/index.js
7164
+ var DEFAULT_MODEL2 = "rerank-2.5";
7165
+ var DEFAULT_BASE_URL = "https://api.voyageai.com/v1";
7166
+ var DEFAULT_BATCH5 = 100;
7167
+ var DEFAULT_RETRIES5 = 6;
7168
+ var DEFAULT_BACKOFF5 = 2e3;
7169
+ var DEFAULT_RETRY_MAX2 = 6e4;
7170
+ function parseRetryAfterMs2(value, now = Date.now()) {
7171
+ if (value === null)
7172
+ return null;
7173
+ const trimmed = value.trim();
7174
+ if (trimmed === "")
7175
+ return null;
7176
+ if (/^[0-9]+(\.[0-9]+)?$/.test(trimmed)) {
7177
+ const secs = Number(trimmed);
7178
+ if (!Number.isFinite(secs) || secs < 0)
7179
+ return null;
7180
+ return Math.round(secs * 1e3);
7181
+ }
7182
+ if (!/[A-Za-z]/.test(trimmed))
7183
+ return null;
7184
+ const ts = Date.parse(trimmed);
7185
+ if (!Number.isFinite(ts))
7186
+ return null;
7187
+ const delta = ts - now;
7188
+ return delta > 0 ? delta : 0;
7189
+ }
7190
+ function sleep5(ms, signal) {
7191
+ return new Promise((resolve6, reject) => {
7192
+ if (signal?.aborted === true) {
7193
+ reject(new Error("aborted"));
7194
+ return;
7195
+ }
7196
+ const timer = setTimeout(() => {
7197
+ signal?.removeEventListener("abort", onAbort);
7198
+ resolve6();
7199
+ }, ms);
7200
+ const onAbort = () => {
7201
+ clearTimeout(timer);
7202
+ reject(new Error("aborted"));
7203
+ };
7204
+ signal?.addEventListener("abort", onAbort, { once: true });
7205
+ });
7206
+ }
7207
+ function nonRetryable5(message) {
7208
+ const err = new Error(message);
7209
+ err.nonRetryable = true;
7210
+ return err;
7211
+ }
7212
+ var VoyageRerankProvider = class {
7213
+ name;
7214
+ model;
7215
+ region;
7216
+ #baseUrl;
7217
+ #apiKey;
7218
+ #batchSize;
7219
+ #maxRetries;
7220
+ #retryBaseMs;
7221
+ #retryMaxMs;
7222
+ #fetch;
7223
+ constructor(opts) {
7224
+ if (typeof opts.apiKey !== "string" || opts.apiKey === "") {
7225
+ throw new Error("VoyageRerankProvider: apiKey is required");
7226
+ }
7227
+ if (opts.batchSize !== void 0 && (!Number.isInteger(opts.batchSize) || opts.batchSize <= 0 || opts.batchSize > 1e3)) {
7228
+ throw new Error(`VoyageRerankProvider: batchSize must be an integer in 1..1000, got ${opts.batchSize}`);
7229
+ }
7230
+ this.model = opts.model ?? DEFAULT_MODEL2;
7231
+ this.name = opts.name ?? `voyage:${this.model}`;
7232
+ this.region = opts.region ?? "us";
7233
+ this.#baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
7234
+ this.#apiKey = opts.apiKey;
7235
+ this.#batchSize = opts.batchSize ?? DEFAULT_BATCH5;
7236
+ this.#maxRetries = opts.maxRetries ?? DEFAULT_RETRIES5;
7237
+ this.#retryBaseMs = opts.retryBaseMs ?? DEFAULT_BACKOFF5;
7238
+ this.#retryMaxMs = opts.retryMaxMs ?? DEFAULT_RETRY_MAX2;
7239
+ this.#fetch = opts.fetch ?? fetch;
7240
+ }
7241
+ async rerank(query, candidates, opts) {
7242
+ if (candidates.length === 0)
7243
+ return [];
7244
+ const all = new Array(candidates.length);
7245
+ let cursor = 0;
7246
+ for (let start = 0; start < candidates.length; start += this.#batchSize) {
7247
+ const slice = candidates.slice(start, start + this.#batchSize);
7248
+ const scored = await this.#rerankBatch(query, slice, opts?.signal);
7249
+ for (const hit of scored) {
7250
+ const globalIndex = start + hit.localIndex;
7251
+ const cand = candidates[globalIndex];
7252
+ all[cursor++] = { id: cand.id, index: globalIndex, score: hit.score };
7253
+ }
7254
+ }
7255
+ all.sort((a, b) => b.score - a.score);
7256
+ if (opts?.topK !== void 0 && opts.topK >= 0 && opts.topK < all.length) {
7257
+ return all.slice(0, opts.topK);
7258
+ }
7259
+ return all;
7260
+ }
7261
+ async #rerankBatch(query, batch, signal) {
7262
+ const body = {
7263
+ query,
7264
+ documents: batch.map((c) => c.text),
7265
+ model: this.model,
7266
+ return_documents: false,
7267
+ truncation: true
7268
+ };
7269
+ const init = {
7270
+ method: "POST",
7271
+ headers: {
7272
+ "content-type": "application/json",
7273
+ authorization: `Bearer ${this.#apiKey}`
7274
+ },
7275
+ body: JSON.stringify(body)
7276
+ };
7277
+ if (signal !== void 0)
7278
+ init.signal = signal;
7279
+ let attempt = 0;
7280
+ let lastError = null;
7281
+ while (attempt <= this.#maxRetries) {
7282
+ try {
7283
+ const res = await this.#fetch(`${this.#baseUrl}/rerank`, init);
7284
+ if (res.status === 429 || res.status >= 500 && res.status < 600) {
7285
+ lastError = new Error(`${this.name}: HTTP ${res.status}`);
7286
+ attempt += 1;
7287
+ if (attempt > this.#maxRetries)
7288
+ break;
7289
+ const backoff = this.#computeBackoff(attempt, res.headers.get("retry-after"));
7290
+ await sleep5(backoff, signal);
7291
+ continue;
7292
+ }
7293
+ if (!res.ok) {
7294
+ const text = await res.text().catch(() => "");
7295
+ throw nonRetryable5(`${this.name}: HTTP ${res.status} ${res.statusText}${text === "" ? "" : ` \u2014 ${text}`}`);
7296
+ }
7297
+ const payload = await res.json();
7298
+ return this.#decode(payload, batch.length);
7299
+ } catch (err) {
7300
+ if (err?.name === "AbortError")
7301
+ throw err;
7302
+ if (err?.nonRetryable === true)
7303
+ throw err;
7304
+ if (attempt >= this.#maxRetries)
7305
+ throw err;
7306
+ lastError = err;
7307
+ attempt += 1;
7308
+ await sleep5(this.#computeBackoff(attempt, null), signal);
7309
+ }
7310
+ }
7311
+ throw lastError instanceof Error ? lastError : new Error(`${this.name}: exhausted ${this.#maxRetries} retries`);
7312
+ }
7313
+ #computeBackoff(attempt, retryAfterHeader) {
7314
+ const exp = this.#retryBaseMs * 2 ** Math.max(0, attempt - 1);
7315
+ const advised = parseRetryAfterMs2(retryAfterHeader);
7316
+ const lower = advised === null ? exp : Math.max(exp, advised);
7317
+ return Math.min(lower, this.#retryMaxMs);
7318
+ }
7319
+ #decode(payload, expected) {
7320
+ if (!Array.isArray(payload.data) || payload.data.length !== expected) {
7321
+ throw nonRetryable5(`${this.name}: expected ${expected} rerank rows, got ${payload.data?.length ?? 0}`);
7322
+ }
7323
+ return payload.data.map((row) => {
7324
+ if (!Number.isInteger(row.index) || row.index < 0 || row.index >= expected) {
7325
+ throw nonRetryable5(`${this.name}: invalid index ${row.index} in rerank response`);
7326
+ }
7327
+ if (typeof row.relevance_score !== "number" || !Number.isFinite(row.relevance_score)) {
7328
+ throw nonRetryable5(`${this.name}: invalid relevance_score ${row.relevance_score} at index ${row.index}`);
7329
+ }
7330
+ return { localIndex: row.index, score: row.relevance_score };
7331
+ });
7332
+ }
7333
+ };
7334
+
7335
+ // ../rerank-openai-compat/dist/index.js
7336
+ var DEFAULT_MODEL3 = "bge-reranker-base";
7337
+ var DEFAULT_BATCH6 = 100;
7338
+ var DEFAULT_RETRIES6 = 6;
7339
+ var DEFAULT_BACKOFF6 = 2e3;
7340
+ var DEFAULT_RETRY_MAX3 = 6e4;
7341
+ var DEFAULT_TIMEOUT = 18e4;
7342
+ function parseRetryAfterMs3(value, now = Date.now()) {
7343
+ if (value === null)
7344
+ return null;
7345
+ const trimmed = value.trim();
7346
+ if (trimmed === "")
7347
+ return null;
7348
+ if (/^[0-9]+(\.[0-9]+)?$/.test(trimmed)) {
7349
+ const secs = Number(trimmed);
7350
+ if (!Number.isFinite(secs) || secs < 0)
7351
+ return null;
7352
+ return Math.round(secs * 1e3);
7353
+ }
7354
+ if (!/[A-Za-z]/.test(trimmed))
7355
+ return null;
7356
+ const ts = Date.parse(trimmed);
7357
+ if (!Number.isFinite(ts))
7358
+ return null;
7359
+ const delta = ts - now;
7360
+ return delta > 0 ? delta : 0;
7361
+ }
7362
+ function sleep6(ms, signal) {
7363
+ return new Promise((resolve6, reject) => {
7364
+ if (signal?.aborted === true) {
7365
+ reject(new Error("aborted"));
7366
+ return;
7367
+ }
7368
+ const timer = setTimeout(() => {
7369
+ signal?.removeEventListener("abort", onAbort);
7370
+ resolve6();
7371
+ }, ms);
7372
+ const onAbort = () => {
7373
+ clearTimeout(timer);
7374
+ reject(new Error("aborted"));
7375
+ };
7376
+ signal?.addEventListener("abort", onAbort, { once: true });
7377
+ });
7378
+ }
7379
+ function nonRetryable6(message) {
7380
+ const err = new Error(message);
7381
+ err.nonRetryable = true;
7382
+ return err;
7383
+ }
7384
+ var OpenAICompatRerankProvider = class {
7385
+ name;
7386
+ model;
7387
+ region;
7388
+ #baseUrl;
7389
+ #apiKey;
7390
+ #batchSize;
7391
+ #maxRetries;
7392
+ #retryBaseMs;
7393
+ #retryMaxMs;
7394
+ #timeoutMs;
7395
+ #fetch;
7396
+ constructor(opts) {
7397
+ if (typeof opts.baseUrl !== "string" || opts.baseUrl === "") {
7398
+ throw new Error("OpenAICompatRerankProvider: baseUrl is required");
7399
+ }
7400
+ if (opts.batchSize !== void 0 && (!Number.isInteger(opts.batchSize) || opts.batchSize <= 0 || opts.batchSize > 1e3)) {
7401
+ throw new Error(`OpenAICompatRerankProvider: batchSize must be an integer in 1..1000, got ${opts.batchSize}`);
7402
+ }
7403
+ if (opts.timeoutMs !== void 0 && (!Number.isInteger(opts.timeoutMs) || opts.timeoutMs < 0)) {
7404
+ throw new Error(`OpenAICompatRerankProvider: timeoutMs must be a non-negative integer (0 disables), got ${opts.timeoutMs}`);
7405
+ }
7406
+ this.model = opts.model ?? DEFAULT_MODEL3;
7407
+ this.name = opts.name ?? `openai-compat:${this.model}`;
7408
+ this.region = opts.region ?? "self-hosted";
7409
+ this.#baseUrl = opts.baseUrl.replace(/\/+$/, "");
7410
+ this.#apiKey = opts.apiKey === void 0 || opts.apiKey === "" ? void 0 : opts.apiKey;
7411
+ this.#batchSize = opts.batchSize ?? DEFAULT_BATCH6;
7412
+ this.#maxRetries = opts.maxRetries ?? DEFAULT_RETRIES6;
7413
+ this.#retryBaseMs = opts.retryBaseMs ?? DEFAULT_BACKOFF6;
7414
+ this.#retryMaxMs = opts.retryMaxMs ?? DEFAULT_RETRY_MAX3;
7415
+ this.#timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT;
7416
+ this.#fetch = opts.fetch ?? fetch;
7417
+ }
7418
+ async rerank(query, candidates, opts) {
7419
+ if (candidates.length === 0)
7420
+ return [];
7421
+ const all = new Array(candidates.length);
7422
+ let cursor = 0;
7423
+ for (let start = 0; start < candidates.length; start += this.#batchSize) {
7424
+ const slice = candidates.slice(start, start + this.#batchSize);
7425
+ const scored = await this.#rerankBatch(query, slice, opts?.signal);
7426
+ for (const hit of scored) {
7427
+ const globalIndex = start + hit.localIndex;
7428
+ const cand = candidates[globalIndex];
7429
+ all[cursor++] = { id: cand.id, index: globalIndex, score: hit.score };
7430
+ }
7431
+ }
7432
+ all.sort((a, b) => b.score - a.score);
7433
+ if (opts?.topK !== void 0 && opts.topK >= 0 && opts.topK < all.length) {
7434
+ return all.slice(0, opts.topK);
7435
+ }
7436
+ return all;
7437
+ }
7438
+ async #rerankBatch(query, batch, signal) {
7439
+ const body = {
7440
+ query,
7441
+ documents: batch.map((c) => c.text),
7442
+ model: this.model,
7443
+ return_documents: false
7444
+ };
7445
+ const headers = { "content-type": "application/json" };
7446
+ if (this.#apiKey !== void 0)
7447
+ headers.authorization = `Bearer ${this.#apiKey}`;
7448
+ const payloadJson = JSON.stringify(body);
7449
+ let attempt = 0;
7450
+ let lastError = null;
7451
+ while (attempt <= this.#maxRetries) {
7452
+ const controller = new AbortController();
7453
+ let timedOut = false;
7454
+ let timer;
7455
+ if (this.#timeoutMs > 0) {
7456
+ timer = setTimeout(() => {
7457
+ timedOut = true;
7458
+ controller.abort();
7459
+ }, this.#timeoutMs);
7460
+ }
7461
+ const onParentAbort = () => controller.abort();
7462
+ if (signal !== void 0) {
7463
+ if (signal.aborted)
7464
+ controller.abort();
7465
+ else
7466
+ signal.addEventListener("abort", onParentAbort, { once: true });
7467
+ }
7468
+ const init = {
7469
+ method: "POST",
7470
+ headers,
7471
+ body: payloadJson,
7472
+ signal: controller.signal
7473
+ };
7474
+ try {
7475
+ const res = await this.#fetch(`${this.#baseUrl}/rerank`, init);
7476
+ if (res.status === 429 || res.status >= 500 && res.status < 600) {
7477
+ lastError = new Error(`${this.name}: HTTP ${res.status}`);
7478
+ attempt += 1;
7479
+ if (attempt > this.#maxRetries)
7480
+ break;
7481
+ const backoff = this.#computeBackoff(attempt, res.headers.get("retry-after"));
7482
+ await sleep6(backoff, signal);
7483
+ continue;
7484
+ }
7485
+ if (!res.ok) {
7486
+ const text = await res.text().catch(() => "");
7487
+ throw nonRetryable6(`${this.name}: HTTP ${res.status} ${res.statusText}${text === "" ? "" : ` \u2014 ${text}`}`);
7488
+ }
7489
+ const payload = await res.json();
7490
+ return this.#decode(payload, batch.length);
7491
+ } catch (err) {
7492
+ const isAbort = err?.name === "AbortError";
7493
+ if (isAbort && !timedOut)
7494
+ throw err;
7495
+ if (!isAbort && err?.nonRetryable === true)
7496
+ throw err;
7497
+ const normalized = timedOut ? new Error(`${this.name}: request timed out after ${this.#timeoutMs}ms`) : err;
7498
+ if (attempt >= this.#maxRetries)
7499
+ throw normalized;
7500
+ lastError = normalized;
7501
+ attempt += 1;
7502
+ await sleep6(this.#computeBackoff(attempt, null), signal);
7503
+ } finally {
7504
+ if (timer !== void 0)
7505
+ clearTimeout(timer);
7506
+ if (signal !== void 0)
7507
+ signal.removeEventListener("abort", onParentAbort);
7508
+ }
7509
+ }
7510
+ throw lastError instanceof Error ? lastError : new Error(`${this.name}: exhausted ${this.#maxRetries} retries`);
7511
+ }
7512
+ #computeBackoff(attempt, retryAfterHeader) {
7513
+ const exp = this.#retryBaseMs * 2 ** Math.max(0, attempt - 1);
7514
+ const advised = parseRetryAfterMs3(retryAfterHeader);
7515
+ const lower = advised === null ? exp : Math.max(exp, advised);
7516
+ return Math.min(lower, this.#retryMaxMs);
7517
+ }
7518
+ #decode(payload, expected) {
7519
+ if (!Array.isArray(payload.results) || payload.results.length !== expected) {
7520
+ throw nonRetryable6(`${this.name}: expected ${expected} rerank rows, got ${payload.results?.length ?? 0}`);
7521
+ }
7522
+ return payload.results.map((row) => {
7523
+ if (!Number.isInteger(row.index) || row.index < 0 || row.index >= expected) {
7524
+ throw nonRetryable6(`${this.name}: invalid index ${row.index} in rerank response`);
7525
+ }
7526
+ if (typeof row.relevance_score !== "number" || !Number.isFinite(row.relevance_score)) {
7527
+ throw nonRetryable6(`${this.name}: invalid relevance_score ${row.relevance_score} at index ${row.index}`);
7528
+ }
7529
+ return { localIndex: row.index, score: row.relevance_score };
7530
+ });
7531
+ }
7532
+ };
7533
+
7163
7534
  // dist/composition.js
7164
7535
  var RegionModeViolation = class extends Error {
7165
7536
  mode;
@@ -7395,6 +7766,56 @@ function discoverEmbeddingProvider(env, fetchImpl) {
7395
7766
  validateRegionMode(regionMode, picked.id, picked.provider.region);
7396
7767
  return { ...picked, regionMode };
7397
7768
  }
7769
+ function discoverRerankProvider(env, fetchImpl) {
7770
+ const regionMode = parseRegionMode(env.PROMETHEUS_REGION_MODE);
7771
+ const forced = env.PROMETHEUS_RERANK_PROVIDER?.toLowerCase() ?? "none";
7772
+ if (forced === "" || forced === "none")
7773
+ return { id: "none", provider: null };
7774
+ if (forced === "voyage") {
7775
+ const apiKey = env.VOYAGE_API_KEY;
7776
+ if (apiKey === void 0 || apiKey === "") {
7777
+ throw new NoProviderError(`rerank provider "voyage" requested but VOYAGE_API_KEY is missing`);
7778
+ }
7779
+ const model = env.VOYAGE_RERANK_MODEL ?? "rerank-2.5";
7780
+ const region = "us";
7781
+ if (regionMode !== "default") {
7782
+ throw new RegionModeViolation(regionMode, "voyage", region, regionMode === "eu-strict" ? ["nomic", "bge-m3", "mistral", "hash"] : ["nomic", "bge-m3", "hash"]);
7783
+ }
7784
+ const provider = new VoyageRerankProvider({
7785
+ name: "voyage-rerank",
7786
+ apiKey,
7787
+ model,
7788
+ region,
7789
+ baseUrl: env.VOYAGE_BASE_URL ?? "https://api.voyageai.com/v1",
7790
+ maxRetries: intEnv(env, "VOYAGE_RERANK_MAX_RETRIES", 6),
7791
+ retryBaseMs: intEnv(env, "VOYAGE_RERANK_RETRY_BASE_MS", 2e3),
7792
+ batchSize: intEnv(env, "VOYAGE_RERANK_BATCH", 100),
7793
+ ...fetchOpt(fetchImpl)
7794
+ });
7795
+ return { id: "voyage", provider };
7796
+ }
7797
+ if (forced === "bge" || forced === "generic") {
7798
+ const baseUrl = env.PROMETHEUS_RERANK_ENDPOINT;
7799
+ if (baseUrl === void 0 || baseUrl === "") {
7800
+ throw new NoProviderError(`rerank provider "${forced}" requested but PROMETHEUS_RERANK_ENDPOINT is missing`);
7801
+ }
7802
+ const model = env.PROMETHEUS_RERANK_MODEL ?? "bge-reranker-base";
7803
+ const provider = new OpenAICompatRerankProvider({
7804
+ name: env.PROMETHEUS_RERANK_NAME ?? `bge-rerank:${model}`,
7805
+ model,
7806
+ region: "self-hosted",
7807
+ baseUrl,
7808
+ maxRetries: intEnv(env, "PROMETHEUS_RERANK_MAX_RETRIES", 6),
7809
+ retryBaseMs: intEnv(env, "PROMETHEUS_RERANK_RETRY_BASE_MS", 2e3),
7810
+ batchSize: intEnv(env, "PROMETHEUS_RERANK_BATCH", 100),
7811
+ timeoutMs: intEnv(env, "PROMETHEUS_RERANK_TIMEOUT_MS", 18e4),
7812
+ ...apiKeyOpt(env.PROMETHEUS_RERANK_API_KEY),
7813
+ ...fetchOpt(fetchImpl)
7814
+ });
7815
+ return { id: "bge", provider };
7816
+ }
7817
+ throw new NoProviderError(`unknown PROMETHEUS_RERANK_PROVIDER="${forced}" (expected "none", "voyage", or "bge")`);
7818
+ }
7398
7819
  function getStableDbPath(workspaceRoot) {
7399
7820
  const abs = resolve4(workspaceRoot);
7400
7821
  const hash = createHash3("sha256").update(abs).digest("hex").slice(0, 16);
@@ -7478,6 +7899,8 @@ async function composeFromEnv(opts) {
7478
7899
  const { id: storageBackend, adapter: storage, dbPath } = discoverStorageBackend(env, regionMode, storageOptions);
7479
7900
  await storage.init();
7480
7901
  const retriever = new HybridRetriever({ storage, embedder });
7902
+ const { id: rerankId, provider: reranker } = discoverRerankProvider(env, opts.fetch);
7903
+ const rerankTopN = intEnv(env, "PROMETHEUS_RERANK_TOP_N", 100);
7481
7904
  const managed = apiKeyPresent && storageBackend === "sqlite";
7482
7905
  let closed = false;
7483
7906
  return {
@@ -7492,6 +7915,9 @@ async function composeFromEnv(opts) {
7492
7915
  storageBackend,
7493
7916
  managed,
7494
7917
  dbPath,
7918
+ reranker,
7919
+ rerankId,
7920
+ rerankTopN,
7495
7921
  async close() {
7496
7922
  if (closed)
7497
7923
  return;
@@ -7645,6 +8071,7 @@ var MAX_K = 50;
7645
8071
  var DEFAULT_K2 = 10;
7646
8072
  var MAX_FILE_BYTES = 256 * 1024;
7647
8073
  var MAX_SNIPPET_BYTES = 1500;
8074
+ var RERANK_DOC_BYTES = 4096;
7648
8075
  function symbolToJson(s) {
7649
8076
  return {
7650
8077
  name: s.name,
@@ -7671,7 +8098,7 @@ function textResult(payload) {
7671
8098
  content: [{ type: "text", text: JSON.stringify(payload, null, 2) }]
7672
8099
  };
7673
8100
  }
7674
- async function snippetForSymbol(workspaceRoot, symbol, cache) {
8101
+ async function snippetForSymbol(workspaceRoot, symbol, cache, capBytes = MAX_SNIPPET_BYTES) {
7675
8102
  try {
7676
8103
  const relPath = symbol.filePath;
7677
8104
  if (isSensitivePath(relPath))
@@ -7689,13 +8116,46 @@ async function snippetForSymbol(workspaceRoot, symbol, cache) {
7689
8116
  if (!(endByte > startByte))
7690
8117
  return null;
7691
8118
  const full = buf.subarray(startByte, endByte);
7692
- const truncated = full.byteLength > MAX_SNIPPET_BYTES;
7693
- const view = truncated ? full.subarray(0, MAX_SNIPPET_BYTES) : full;
8119
+ const truncated = full.byteLength > capBytes;
8120
+ const view = truncated ? full.subarray(0, capBytes) : full;
7694
8121
  return { text: view.toString("utf8"), truncated };
7695
8122
  } catch {
7696
8123
  return null;
7697
8124
  }
7698
8125
  }
8126
+ async function rerankHead(reranker, query, head, workspaceRoot, cache) {
8127
+ const candidates = [];
8128
+ for (let i = 0; i < head.length; i++) {
8129
+ const snip = await snippetForSymbol(workspaceRoot, head[i].symbol, cache, RERANK_DOC_BYTES);
8130
+ if (snip === null)
8131
+ continue;
8132
+ candidates.push({ id: String(i), text: snip.text });
8133
+ }
8134
+ if (candidates.length === 0)
8135
+ return null;
8136
+ let hits;
8137
+ try {
8138
+ hits = await reranker.rerank(query, candidates, { topK: candidates.length });
8139
+ } catch {
8140
+ return null;
8141
+ }
8142
+ const out = [];
8143
+ const seen = /* @__PURE__ */ new Set();
8144
+ for (const hit of hits) {
8145
+ const i = Number(hit.id);
8146
+ if (!Number.isFinite(i) || seen.has(i))
8147
+ continue;
8148
+ seen.add(i);
8149
+ const r = head[i];
8150
+ if (r !== void 0)
8151
+ out.push(r);
8152
+ }
8153
+ for (let i = 0; i < head.length; i++) {
8154
+ if (!seen.has(i))
8155
+ out.push(head[i]);
8156
+ }
8157
+ return out;
8158
+ }
7699
8159
  function resolveInWorkspace(workspaceRoot, input) {
7700
8160
  if (input === "")
7701
8161
  throw new Error("path must not be empty.");
@@ -7768,7 +8228,7 @@ var changedSinceInput = {
7768
8228
  };
7769
8229
  var emptyInput = {};
7770
8230
  function registerTools(server, deps) {
7771
- const { storage, retriever, workspaceRoot, workspaceId, workspaceName, regionMode, providerId, storageBackend } = deps;
8231
+ const { storage, retriever, workspaceRoot, workspaceId, workspaceName, regionMode, providerId, storageBackend, reranker, rerankTopN } = deps;
7772
8232
  server.registerTool("search_code", {
7773
8233
  title: "Hybrid code search",
7774
8234
  description: "PRIMARY code search for this workspace \u2014 call this FIRST to find where something is defined, used or implemented, before reading files or guessing paths. Hybrid retrieval (lexical FTS + vector + symbol graph, RRF-fused) over natural-language or symbol queries. Returns the top-k symbols with provenance AND an inline source snippet per hit, so the result is usually actionable without a follow-up get_file. Set `includeSnippet: false` to omit the inline code (symbols only).",
@@ -7776,8 +8236,21 @@ function registerTools(server, deps) {
7776
8236
  }, async (args) => {
7777
8237
  const k = clampK(args.k);
7778
8238
  const includeSnippet = args.includeSnippet ?? true;
7779
- const results = await retriever.search(args.query, { k });
7780
8239
  const cache = /* @__PURE__ */ new Map();
8240
+ const poolK = reranker ? Math.max(k, rerankTopN ?? 100) : k;
8241
+ const pool = await retriever.search(args.query, { k: poolK });
8242
+ let ordered = pool;
8243
+ let reranked = false;
8244
+ if (reranker && pool.length > 0) {
8245
+ const head = pool.slice(0, rerankTopN ?? 100);
8246
+ const tail = pool.slice(rerankTopN);
8247
+ const reorderedHead = await rerankHead(reranker, args.query, head, workspaceRoot, cache);
8248
+ if (reorderedHead !== null) {
8249
+ ordered = reorderedHead.concat(tail);
8250
+ reranked = true;
8251
+ }
8252
+ }
8253
+ const results = ordered.slice(0, k);
7781
8254
  const mapped = await Promise.all(results.map(async (r) => {
7782
8255
  const base = {
7783
8256
  score: r.score,
@@ -7789,7 +8262,7 @@ function registerTools(server, deps) {
7789
8262
  const snip = await snippetForSymbol(workspaceRoot, r.symbol, cache);
7790
8263
  return snip === null ? base : { ...base, snippet: snip.text, snippetTruncated: snip.truncated };
7791
8264
  }));
7792
- return textResult({ query: args.query, k, results: mapped });
8265
+ return textResult({ query: args.query, k, reranked, results: mapped });
7793
8266
  });
7794
8267
  server.registerTool("get_symbol", {
7795
8268
  title: "Exact symbol lookup",
@@ -7951,7 +8424,7 @@ function registerTools(server, deps) {
7951
8424
  var SERVER_IDENTITY = {
7952
8425
  name: "prometheus-context-mcp",
7953
8426
  version: PROMETHEUS_VERSION,
7954
- title: "Prometheus Context Engine"
8427
+ title: "prom.codes Context"
7955
8428
  };
7956
8429
  function createServer(deps, options = {}) {
7957
8430
  const identity = { ...SERVER_IDENTITY, ...options.identity ?? {} };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@prom.codes/context-mcp",
3
- "version": "0.2.0",
4
- "description": "Prometheus Context Engine — local-first codebase indexing & retrieval as an MCP server.",
3
+ "version": "0.2.2",
4
+ "description": "prom.codes Context — local-first codebase indexing & retrieval as an MCP server.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "prometheus-context-mcp": "dist/bin.js"