@prom.codes/context-mcp 0.4.1 → 0.4.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 (2) hide show
  1. package/dist/bin.js +240 -31
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -3484,6 +3484,153 @@ function isSensitivePath(path) {
3484
3484
  }
3485
3485
  var SENSITIVE_PATH_ERROR = "path matches the sensitive-file deny-list";
3486
3486
 
3487
+ // ../shared/dist/update-check.js
3488
+ import { mkdir, readFile as readFile2, writeFile } from "node:fs/promises";
3489
+ import { homedir } from "node:os";
3490
+ import { join } from "node:path";
3491
+ import { fileURLToPath } from "node:url";
3492
+ async function packageIdentity(binImportMetaUrl) {
3493
+ try {
3494
+ const binPath = fileURLToPath(binImportMetaUrl);
3495
+ const pkgPath = join(binPath, "..", "..", "package.json");
3496
+ const raw = await readFile2(pkgPath, "utf8");
3497
+ const parsed = JSON.parse(raw);
3498
+ if (typeof parsed.name !== "string" || typeof parsed.version !== "string") {
3499
+ return null;
3500
+ }
3501
+ return { name: parsed.name, version: parsed.version };
3502
+ } catch {
3503
+ return null;
3504
+ }
3505
+ }
3506
+ async function maybeNotifyUpdate(binImportMetaUrl, env = process.env) {
3507
+ try {
3508
+ const id = await packageIdentity(binImportMetaUrl);
3509
+ if (id === null)
3510
+ return;
3511
+ await checkForUpdate({ name: id.name, version: id.version, env });
3512
+ } catch {
3513
+ }
3514
+ }
3515
+ var DEFAULT_TTL_MS = 24 * 60 * 60 * 1e3;
3516
+ var DEFAULT_TIMEOUT_MS = 1500;
3517
+ var OPT_OUT_RE = /^(1|true|yes|on)$/i;
3518
+ function parseSemver(v) {
3519
+ const m = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/.exec(v.trim());
3520
+ if (m === null)
3521
+ return null;
3522
+ return {
3523
+ core: [Number(m[1]), Number(m[2]), Number(m[3])],
3524
+ pre: m[4] ?? null
3525
+ };
3526
+ }
3527
+ function isNewerVersion(latest, current) {
3528
+ const a = parseSemver(latest);
3529
+ const b = parseSemver(current);
3530
+ if (a === null || b === null)
3531
+ return false;
3532
+ for (let i = 0; i < 3; i++) {
3533
+ if (a.core[i] > b.core[i])
3534
+ return true;
3535
+ if (a.core[i] < b.core[i])
3536
+ return false;
3537
+ }
3538
+ if (a.pre === null && b.pre !== null)
3539
+ return true;
3540
+ return false;
3541
+ }
3542
+ function cachePath(dir, name) {
3543
+ const safe = name.replace(/[^a-zA-Z0-9._-]+/g, "_");
3544
+ return join(dir, `.update-check-${safe}.json`);
3545
+ }
3546
+ async function readCache(path) {
3547
+ try {
3548
+ const raw = await readFile2(path, "utf8");
3549
+ const parsed = JSON.parse(raw);
3550
+ if (typeof parsed.checkedAt !== "number")
3551
+ return null;
3552
+ return {
3553
+ checkedAt: parsed.checkedAt,
3554
+ latest: typeof parsed.latest === "string" ? parsed.latest : null
3555
+ };
3556
+ } catch {
3557
+ return null;
3558
+ }
3559
+ }
3560
+ async function writeCache(path, data) {
3561
+ try {
3562
+ await writeFile(path, JSON.stringify(data), "utf8");
3563
+ } catch {
3564
+ }
3565
+ }
3566
+ async function fetchLatest(name, fetchImpl, timeoutMs) {
3567
+ const controller = new AbortController();
3568
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
3569
+ timer.unref?.();
3570
+ try {
3571
+ const url = `https://registry.npmjs.org/${name.replace("/", "%2F")}/latest`;
3572
+ const res = await fetchImpl(url, {
3573
+ signal: controller.signal,
3574
+ headers: { accept: "application/vnd.npm.install-v1+json" }
3575
+ });
3576
+ if (!res.ok)
3577
+ return null;
3578
+ const body = await res.json();
3579
+ return typeof body.version === "string" ? body.version : null;
3580
+ } catch {
3581
+ return null;
3582
+ } finally {
3583
+ clearTimeout(timer);
3584
+ }
3585
+ }
3586
+ async function checkForUpdate(options) {
3587
+ const { name, version, env = process.env, log = (line) => process.stderr.write(line), fetch: fetchImpl = globalThis.fetch, cacheDir = join(homedir(), ".prometheus"), cacheTtlMs = DEFAULT_TTL_MS, timeoutMs = DEFAULT_TIMEOUT_MS, force = false } = options;
3588
+ const base = {
3589
+ latest: null,
3590
+ current: version
3591
+ };
3592
+ if (OPT_OUT_RE.test(env.PROMETHEUS_NO_UPDATE_CHECK ?? "")) {
3593
+ return { ...base, checked: false, updateAvailable: false, reason: "opted-out" };
3594
+ }
3595
+ if (parseSemver(version) === null || version === "0.0.0") {
3596
+ return { ...base, checked: false, updateAvailable: false, reason: "invalid-version" };
3597
+ }
3598
+ if (typeof fetchImpl !== "function") {
3599
+ return { ...base, checked: false, updateAvailable: false, reason: "error" };
3600
+ }
3601
+ const file = cachePath(cacheDir, name);
3602
+ const now = Date.now();
3603
+ if (!force) {
3604
+ const cached = await readCache(file);
3605
+ if (cached !== null && now - cached.checkedAt < cacheTtlMs) {
3606
+ const updateAvailable2 = cached.latest !== null && isNewerVersion(cached.latest, version);
3607
+ if (updateAvailable2)
3608
+ notify(log, name, version, cached.latest);
3609
+ return {
3610
+ ...base,
3611
+ latest: cached.latest,
3612
+ checked: false,
3613
+ updateAvailable: updateAvailable2,
3614
+ reason: "throttled"
3615
+ };
3616
+ }
3617
+ }
3618
+ await mkdir(cacheDir, { recursive: true }).catch(() => void 0);
3619
+ const latest = await fetchLatest(name, fetchImpl, timeoutMs);
3620
+ await writeCache(file, { checkedAt: now, latest });
3621
+ if (latest === null) {
3622
+ return { ...base, checked: true, updateAvailable: false, reason: "error" };
3623
+ }
3624
+ const updateAvailable = isNewerVersion(latest, version);
3625
+ if (updateAvailable)
3626
+ notify(log, name, version, latest);
3627
+ return { ...base, latest, checked: true, updateAvailable };
3628
+ }
3629
+ function notify(log, name, current, latest) {
3630
+ log(`${name}: a newer version (${latest}) is available \u2014 you are on ${current}. npx users get it automatically on the next restart; for a global install run \`npm update -g ${name}\`. (Set PROMETHEUS_NO_UPDATE_CHECK=1 to silence.)
3631
+ `);
3632
+ }
3633
+
3487
3634
  // ../shared/dist/index.js
3488
3635
  var PROMETHEUS_VERSION = "0.1.0";
3489
3636
 
@@ -3542,6 +3689,8 @@ var WorkspaceWatcher = class extends EventEmitter {
3542
3689
  #root;
3543
3690
  #ignored;
3544
3691
  #debounceMs;
3692
+ #usePolling;
3693
+ #pollIntervalMs;
3545
3694
  #watcher = null;
3546
3695
  #pending = /* @__PURE__ */ new Map();
3547
3696
  constructor(options) {
@@ -3549,6 +3698,10 @@ var WorkspaceWatcher = class extends EventEmitter {
3549
3698
  this.#root = resolve(options.root);
3550
3699
  this.#ignored = options.ignored ?? DEFAULT_IGNORED;
3551
3700
  this.#debounceMs = options.debounceMs ?? 50;
3701
+ const envPolling = process.env.CHOKIDAR_USEPOLLING === "true" || process.env.CHOKIDAR_USEPOLLING === "1";
3702
+ this.#usePolling = options.usePolling ?? envPolling;
3703
+ const envInterval = Number.parseInt(process.env.CHOKIDAR_INTERVAL ?? "", 10);
3704
+ this.#pollIntervalMs = options.pollIntervalMs ?? (Number.isInteger(envInterval) && envInterval > 0 ? envInterval : 80);
3552
3705
  }
3553
3706
  /** Absolute workspace root. */
3554
3707
  get root() {
@@ -3558,14 +3711,25 @@ var WorkspaceWatcher = class extends EventEmitter {
3558
3711
  async start() {
3559
3712
  if (this.#watcher !== null)
3560
3713
  return;
3714
+ const relativeMatch = (pat) => (abs) => {
3715
+ const rel = toRelative(this.#root, abs);
3716
+ if (rel === abs)
3717
+ return false;
3718
+ if (typeof pat === "string") {
3719
+ return rel === pat || rel.startsWith(`${pat}/`);
3720
+ }
3721
+ return pat.test(rel);
3722
+ };
3561
3723
  const denySensitive = (abs) => {
3562
3724
  const rel = toRelative(this.#root, abs);
3563
3725
  return rel !== abs && isSensitivePath(rel);
3564
3726
  };
3565
3727
  const watcher = chokidar.watch(this.#root, {
3566
- ignored: [...this.#ignored, denySensitive],
3728
+ ignored: [...this.#ignored.map(relativeMatch), denySensitive],
3567
3729
  ignoreInitial: false,
3568
3730
  persistent: true,
3731
+ usePolling: this.#usePolling,
3732
+ ...this.#usePolling ? { interval: this.#pollIntervalMs, binaryInterval: this.#pollIntervalMs } : {},
3569
3733
  awaitWriteFinish: { stabilityThreshold: 50, pollInterval: 10 }
3570
3734
  });
3571
3735
  this.#watcher = watcher;
@@ -3608,8 +3772,8 @@ var WorkspaceWatcher = class extends EventEmitter {
3608
3772
  };
3609
3773
 
3610
3774
  // ../indexer/dist/workspace-indexer.js
3611
- import { readFile as readFile2, readdir, stat } from "node:fs/promises";
3612
- import { join, resolve as resolve2, sep as sep2 } from "node:path";
3775
+ import { readFile as readFile3, readdir, stat } from "node:fs/promises";
3776
+ import { join as join2, resolve as resolve2, sep as sep2 } from "node:path";
3613
3777
 
3614
3778
  // ../indexer/dist/co-change.js
3615
3779
  import { spawn } from "node:child_process";
@@ -3811,6 +3975,7 @@ var WorkspaceIndexer = class {
3811
3975
  #ignored;
3812
3976
  #concurrency;
3813
3977
  #coChangeBuilder;
3978
+ #watchTuning;
3814
3979
  #watcher = null;
3815
3980
  constructor(options) {
3816
3981
  this.#root = resolve2(options.root);
@@ -3819,6 +3984,7 @@ var WorkspaceIndexer = class {
3819
3984
  this.#ownsIndexer = options.indexer === void 0;
3820
3985
  this.#ignored = options.ignored;
3821
3986
  this.#concurrency = Math.max(1, options.concurrency ?? 8);
3987
+ this.#watchTuning = options.watch;
3822
3988
  this.#coChangeBuilder = options.coChange === false ? null : new CoChangeBuilder({
3823
3989
  repoRoot: this.#root,
3824
3990
  storage: this.#storage,
@@ -3882,7 +4048,7 @@ var WorkspaceIndexer = class {
3882
4048
  return;
3883
4049
  }
3884
4050
  for (const entry of entries) {
3885
- const abs = join(dir, entry.name);
4051
+ const abs = join2(dir, entry.name);
3886
4052
  const rel = toRelative2(this.#root, abs);
3887
4053
  if (this.#isIgnored(rel))
3888
4054
  continue;
@@ -3929,7 +4095,11 @@ var WorkspaceIndexer = class {
3929
4095
  this.#indexer.dispose();
3930
4096
  }
3931
4097
  #watcherOptions() {
3932
- return this.#ignored === void 0 ? { root: this.#root } : { root: this.#root, ignored: this.#ignored };
4098
+ return {
4099
+ root: this.#root,
4100
+ ...this.#ignored !== void 0 ? { ignored: this.#ignored } : {},
4101
+ ...this.#watchTuning ?? {}
4102
+ };
3933
4103
  }
3934
4104
  async #handleEvent(ev) {
3935
4105
  try {
@@ -3949,7 +4119,7 @@ var WorkspaceIndexer = class {
3949
4119
  let raw;
3950
4120
  let stats;
3951
4121
  try {
3952
- raw = await readFile2(absPath);
4122
+ raw = await readFile3(absPath);
3953
4123
  stats = await stat(absPath);
3954
4124
  } catch (err) {
3955
4125
  return { error: err instanceof Error ? err.message : String(err) };
@@ -4001,8 +4171,8 @@ var WorkspaceIndexer = class {
4001
4171
  // dist/composition.js
4002
4172
  import { createHash as createHash3 } from "node:crypto";
4003
4173
  import { mkdirSync } from "node:fs";
4004
- import { homedir } from "node:os";
4005
- import { basename as basename5, dirname as dirname2, join as join3, resolve as resolve4 } from "node:path";
4174
+ import { homedir as homedir2 } from "node:os";
4175
+ import { basename as basename5, dirname as dirname2, join as join4, resolve as resolve4 } from "node:path";
4006
4176
 
4007
4177
  // ../storage-sqlite/dist/adapter.js
4008
4178
  import Database from "better-sqlite3";
@@ -4727,8 +4897,8 @@ var DEFAULT_VECTOR_DIMENSION = 1024;
4727
4897
  var DEFAULT_SCHEMA = "public";
4728
4898
 
4729
4899
  // ../storage-supabase/dist/migrations.js
4730
- import { dirname, join as join2, resolve as resolve3 } from "node:path";
4731
- import { fileURLToPath } from "node:url";
4900
+ import { dirname, join as join3, resolve as resolve3 } from "node:path";
4901
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
4732
4902
 
4733
4903
  // ../storage-supabase/dist/migrations-data.generated.js
4734
4904
  var EMBEDDED_MIGRATIONS = {
@@ -4819,13 +4989,13 @@ COMMIT;
4819
4989
  };
4820
4990
 
4821
4991
  // ../storage-supabase/dist/migrations.js
4822
- var HERE = dirname(fileURLToPath(import.meta.url));
4992
+ var HERE = dirname(fileURLToPath2(import.meta.url));
4823
4993
  var REPO_ROOT = resolve3(HERE, "..", "..", "..");
4824
- var MIGRATIONS_DIR = join2(REPO_ROOT, "infra", "supabase", "migrations");
4994
+ var MIGRATIONS_DIR = join3(REPO_ROOT, "infra", "supabase", "migrations");
4825
4995
  var MIGRATIONS = Object.keys(EMBEDDED_MIGRATIONS).sort().map((filename, idx) => ({
4826
4996
  id: idx + 1,
4827
4997
  filename,
4828
- path: join2(MIGRATIONS_DIR, filename)
4998
+ path: join3(MIGRATIONS_DIR, filename)
4829
4999
  }));
4830
5000
  async function loadMigrationSql(filename) {
4831
5001
  const sql = EMBEDDED_MIGRATIONS[filename];
@@ -6149,7 +6319,7 @@ var HybridRetriever = class {
6149
6319
  const coChangeActive = effectiveEdgeWeights[CO_CHANGE] > 0;
6150
6320
  const lexCols = options.lexicalColumnWeights;
6151
6321
  const lexicalPromise = wLex > 0 ? this.#storage.searchByText(trimmed, candidateLimit, lexCols !== void 0 ? { columnWeights: lexCols } : void 0) : Promise.resolve([]);
6152
- const vectorPromise = wVec > 0 ? this.#runVector(trimmed, candidateLimit, options.signal) : Promise.resolve({ hits: [], queryVector: null });
6322
+ const vectorPromise = wVec > 0 ? this.#runVector(trimmed, candidateLimit, options.signal, options.onVectorError) : Promise.resolve({ hits: [], queryVector: null });
6153
6323
  const [lexicalHits, vectorResult] = await Promise.all([
6154
6324
  lexicalPromise,
6155
6325
  vectorPromise
@@ -6240,15 +6410,24 @@ var HybridRetriever = class {
6240
6410
  }
6241
6411
  return fusedResults;
6242
6412
  }
6243
- async #runVector(query, limit, signal) {
6413
+ async #runVector(query, limit, signal, onVectorError) {
6244
6414
  const opts = signal !== void 0 ? { inputType: "query", signal } : { inputType: "query" };
6245
- const vectors = await this.#embedder.embed([query], opts);
6246
- if (vectors.length === 0 || vectors[0] === void 0) {
6415
+ try {
6416
+ const vectors = await this.#embedder.embed([query], opts);
6417
+ if (vectors.length === 0 || vectors[0] === void 0) {
6418
+ return { hits: [], queryVector: null };
6419
+ }
6420
+ const queryVector = vectors[0];
6421
+ const hits = await this.#storage.searchByVector(queryVector, limit);
6422
+ return { hits, queryVector };
6423
+ } catch (err) {
6424
+ if (err?.name === "AbortError")
6425
+ throw err;
6426
+ if (onVectorError === void 0)
6427
+ throw err;
6428
+ onVectorError(err);
6247
6429
  return { hits: [], queryVector: null };
6248
6430
  }
6249
- const queryVector = vectors[0];
6250
- const hits = await this.#storage.searchByVector(queryVector, limit);
6251
- return { hits, queryVector };
6252
6431
  }
6253
6432
  /**
6254
6433
  * Run typed symbol-graph expansion from `seeds`.
@@ -8257,7 +8436,7 @@ function discoverQueryRewriter(env, fetchImpl) {
8257
8436
  function getStableDbPath(workspaceRoot) {
8258
8437
  const abs = resolve4(workspaceRoot);
8259
8438
  const hash = createHash3("sha256").update(abs).digest("hex").slice(0, 16);
8260
- return join3(homedir(), ".prometheus", `${hash}.db`);
8439
+ return join4(homedir2(), ".prometheus", `${hash}.db`);
8261
8440
  }
8262
8441
  var StorageConfigError = class extends Error {
8263
8442
  constructor(reason) {
@@ -8336,7 +8515,10 @@ async function composeFromEnv(opts) {
8336
8515
  const workspaceName = (env.PROMETHEUS_WORKSPACE_NAME ?? "") !== "" ? env.PROMETHEUS_WORKSPACE_NAME : basename5(workspaceRoot) || workspaceRoot;
8337
8516
  const { id: providerId, provider: embedder, regionMode } = discoverEmbeddingProvider(env, opts.fetch);
8338
8517
  const apiKeyPresent = (env.PROMETHEUS_API_KEY ?? "") !== "";
8339
- const storageOptions = apiKeyPresent ? { writable: true, defaultDbPath: getStableDbPath(workspaceRoot) } : {};
8518
+ const storageOptions = {
8519
+ writable: true,
8520
+ defaultDbPath: getStableDbPath(workspaceRoot)
8521
+ };
8340
8522
  const { id: storageBackend, adapter: storage, dbPath } = discoverStorageBackend(env, regionMode, storageOptions);
8341
8523
  await storage.init();
8342
8524
  const retriever = new HybridRetriever({ storage, embedder });
@@ -8372,7 +8554,7 @@ async function composeFromEnv(opts) {
8372
8554
  }
8373
8555
 
8374
8556
  // dist/roots.js
8375
- import { fileURLToPath as fileURLToPath2 } from "node:url";
8557
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
8376
8558
  async function rootFromClient(server, timeoutMs = 2500) {
8377
8559
  let supportsRoots = false;
8378
8560
  try {
@@ -8393,7 +8575,7 @@ async function rootFromClient(server, timeoutMs = 2500) {
8393
8575
  const uri = typeof r?.uri === "string" ? r.uri : "";
8394
8576
  if (uri.startsWith("file://")) {
8395
8577
  try {
8396
- return fileURLToPath2(uri);
8578
+ return fileURLToPath3(uri);
8397
8579
  } catch {
8398
8580
  }
8399
8581
  }
@@ -8405,8 +8587,8 @@ async function rootFromClient(server, timeoutMs = 2500) {
8405
8587
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8406
8588
 
8407
8589
  // dist/tools.js
8408
- import { readFile as readFile3 } from "node:fs/promises";
8409
- import { isAbsolute, join as join4, normalize, relative, resolve as resolve5, sep as sep3 } from "node:path";
8590
+ import { readFile as readFile4 } from "node:fs/promises";
8591
+ import { isAbsolute, join as join5, normalize, relative, resolve as resolve5, sep as sep3 } from "node:path";
8410
8592
  import { z } from "zod";
8411
8593
 
8412
8594
  // dist/frameworks.js
@@ -8608,7 +8790,7 @@ async function snippetForSymbol(workspaceRoot, symbol, cache, capBytes = MAX_SNI
8608
8790
  let buf = cache.get(relPath);
8609
8791
  if (buf === void 0) {
8610
8792
  const abs = resolveInWorkspace(workspaceRoot, relPath);
8611
- buf = await readFile3(abs).catch(() => null);
8793
+ buf = await readFile4(abs).catch(() => null);
8612
8794
  cache.set(relPath, buf);
8613
8795
  }
8614
8796
  if (buf === null)
@@ -8757,7 +8939,16 @@ ${hyp}`;
8757
8939
  } catch {
8758
8940
  }
8759
8941
  }
8760
- const pool = await retriever.search(searchQuery, { k: poolK });
8942
+ let vectorError = null;
8943
+ const pool = await retriever.search(searchQuery, {
8944
+ k: poolK,
8945
+ onVectorError: (err) => {
8946
+ if (vectorError !== null)
8947
+ return;
8948
+ vectorError = err instanceof Error ? err.message : String(err);
8949
+ console.error(`[context-mcp] search_code: vector (semantic) search unavailable \u2014 falling back to lexical + symbol-graph. Likely a missing/invalid embedding API key; set PROMETHEUS_API_KEY (a real minted prom_live_\u2026 key) or a provider key (e.g. VOYAGE_API_KEY) to enable semantic ranking. Cause: ${vectorError}`);
8950
+ }
8951
+ });
8761
8952
  let ordered = pool;
8762
8953
  let reranked = false;
8763
8954
  if (reranker && pool.length > 0) {
@@ -8782,7 +8973,18 @@ ${hyp}`;
8782
8973
  const snip = await snippetForSymbol(workspaceRoot, r.symbol, cache);
8783
8974
  return snip === null ? base : { ...base, snippet: snip.text, snippetTruncated: snip.truncated };
8784
8975
  }));
8785
- return textResult({ query: args.query, k, reranked, results: mapped });
8976
+ const payload = {
8977
+ query: args.query,
8978
+ k,
8979
+ reranked,
8980
+ results: mapped
8981
+ };
8982
+ if (vectorError !== null) {
8983
+ payload.degraded = "lexical+graph-only";
8984
+ payload.note = "Semantic (vector) search was unavailable \u2014 results are lexical (keyword) + symbol-graph only, which can rank less precisely on concept queries. Set a valid embedding key (PROMETHEUS_API_KEY = a real minted prom_live_\u2026 key, or e.g. VOYAGE_API_KEY) to enable semantic ranking.";
8985
+ payload.vectorError = vectorError;
8986
+ }
8987
+ return textResult(payload);
8786
8988
  });
8787
8989
  server.registerTool("get_symbol", {
8788
8990
  title: "Exact symbol lookup",
@@ -8852,7 +9054,7 @@ ${hyp}`;
8852
9054
  if (isSensitivePath(relative(workspaceRoot, abs))) {
8853
9055
  throw new Error(`${SENSITIVE_PATH_ERROR}: "${args.path}".`);
8854
9056
  }
8855
- const buf = await readFile3(abs);
9057
+ const buf = await readFile4(abs);
8856
9058
  const start = args.startByte ?? 0;
8857
9059
  const end = args.endByte ?? buf.byteLength;
8858
9060
  if (end < start) {
@@ -8923,7 +9125,7 @@ ${hyp}`;
8923
9125
  const manifests = {};
8924
9126
  for (const name of FRAMEWORK_MANIFESTS) {
8925
9127
  try {
8926
- manifests[name] = await readFile3(join4(workspaceRoot, name), "utf8");
9128
+ manifests[name] = await readFile4(join5(workspaceRoot, name), "utf8");
8927
9129
  } catch {
8928
9130
  }
8929
9131
  }
@@ -8951,6 +9153,10 @@ var SERVER_IDENTITY = {
8951
9153
  function errMessage(err) {
8952
9154
  return err instanceof Error ? err.message : String(err);
8953
9155
  }
9156
+ function looksLikeMissingNativeBinding(msg) {
9157
+ return /bindings file|better_sqlite3\.node|could not locate the bindings|node_module_version|was compiled against a different|invalid elf|\.node['"\s]/i.test(msg);
9158
+ }
9159
+ var NATIVE_BINDING_HINT = '\nThis looks like the native `better-sqlite3` module failed to load \u2014 usually\nbecause npm `ignore-scripts=true` (corporate hardening) made `npx` skip the\nnative build, so the binary was never produced. Fix it once, keeping your\nhardening intact:\n npm install -g @prom.codes/context-mcp --ignore-scripts=false --foreground-scripts\nthen point Claude Code at the built binary instead of npx:\n claude mcp add context -- node "$(npm root -g)/@prom.codes/context-mcp/dist/bin.js"\nDocs: https://prom.codes/docs/mcp/claude-code\n';
8954
9160
  function startManagedIndexing(composed) {
8955
9161
  const indexer = new WorkspaceIndexer({
8956
9162
  root: composed.workspaceRoot,
@@ -8987,6 +9193,7 @@ async function main() {
8987
9193
  const explicitRoot = (env.PROMETHEUS_WORKSPACE_ROOT ?? "").trim();
8988
9194
  const claudeRoot = (env.CLAUDE_PROJECT_DIR ?? "").trim();
8989
9195
  const eagerVia = explicitRoot !== "" ? "PROMETHEUS_WORKSPACE_ROOT" : claudeRoot !== "" ? "CLAUDE_PROJECT_DIR" : null;
9196
+ void maybeNotifyUpdate(import.meta.url, env);
8990
9197
  const transport = new StdioServerTransport();
8991
9198
  const server = new McpServer2(SERVER_IDENTITY, { capabilities: { tools: {} } });
8992
9199
  let composed = null;
@@ -9054,5 +9261,7 @@ main().catch((err) => {
9054
9261
  const message = err instanceof Error ? err.message : String(err);
9055
9262
  process.stderr.write(`prometheus-context-mcp: fatal: ${message}
9056
9263
  `);
9264
+ if (looksLikeMissingNativeBinding(message))
9265
+ process.stderr.write(NATIVE_BINDING_HINT);
9057
9266
  process.exit(1);
9058
9267
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prom.codes/context-mcp",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "prom.codes Context — local-first codebase indexing & retrieval as an MCP server.",
5
5
  "type": "module",
6
6
  "bin": {