crosscheck-mcp 0.1.8 → 0.1.10

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.
@@ -942,8 +942,8 @@ __export(node_stdio_exports, {
942
942
  });
943
943
  module.exports = __toCommonJS(node_stdio_exports);
944
944
  init_cjs_shims();
945
- var import_node_fs14 = require("fs");
946
- var import_node_path14 = __toESM(require("path"), 1);
945
+ var import_node_fs15 = require("fs");
946
+ var import_node_path15 = __toESM(require("path"), 1);
947
947
  var import_node_url2 = require("url");
948
948
  var import_stdio2 = require("@modelcontextprotocol/sdk/server/stdio.js");
949
949
 
@@ -1104,10 +1104,10 @@ var StderrEmitter = class {
1104
1104
  }
1105
1105
  };
1106
1106
  var FileEmitter = class {
1107
- constructor(path11) {
1108
- this.path = path11;
1107
+ constructor(path12) {
1108
+ this.path = path12;
1109
1109
  try {
1110
- (0, import_node_fs.mkdirSync)((0, import_node_path2.dirname)(path11), { recursive: true });
1110
+ (0, import_node_fs.mkdirSync)((0, import_node_path2.dirname)(path12), { recursive: true });
1111
1111
  } catch {
1112
1112
  }
1113
1113
  }
@@ -1140,13 +1140,13 @@ var NullEmitter = class {
1140
1140
  function buildDefaultEmitter(env) {
1141
1141
  const mode = (env["CROSSCHECK_EVENTS"] ?? "stderr").toLowerCase();
1142
1142
  if (mode === "off") return new NullEmitter();
1143
- const path11 = env["CROSSCHECK_EVENTS_PATH"];
1143
+ const path12 = env["CROSSCHECK_EVENTS_PATH"];
1144
1144
  if (mode === "file") {
1145
- return path11 ? new FileEmitter(path11) : new NullEmitter();
1145
+ return path12 ? new FileEmitter(path12) : new NullEmitter();
1146
1146
  }
1147
1147
  if (mode === "both") {
1148
1148
  const ems = [new StderrEmitter()];
1149
- if (path11) ems.push(new FileEmitter(path11));
1149
+ if (path12) ems.push(new FileEmitter(path12));
1150
1150
  return new TeeEmitter(ems);
1151
1151
  }
1152
1152
  return new StderrEmitter();
@@ -1195,10 +1195,10 @@ function envelopeBytes(v) {
1195
1195
  // src/core/pricing.ts
1196
1196
  init_cjs_shims();
1197
1197
  var import_node_fs2 = require("fs");
1198
- function loadPricing(path11) {
1198
+ function loadPricing(path12) {
1199
1199
  let raw;
1200
1200
  try {
1201
- raw = (0, import_node_fs2.readFileSync)(path11, "utf8");
1201
+ raw = (0, import_node_fs2.readFileSync)(path12, "utf8");
1202
1202
  } catch {
1203
1203
  return {};
1204
1204
  }
@@ -2070,27 +2070,27 @@ function extractJson(text) {
2070
2070
 
2071
2071
  // src/core/json-schema.ts
2072
2072
  init_cjs_shims();
2073
- function validateSchema(value, schema, path11 = "") {
2073
+ function validateSchema(value, schema, path12 = "") {
2074
2074
  const errs = [];
2075
2075
  if ("anyOf" in schema) {
2076
2076
  const subs = schema["anyOf"].map(
2077
- (s) => validateSchema(value, s, path11)
2077
+ (s) => validateSchema(value, s, path12)
2078
2078
  );
2079
2079
  if (!subs.some((e) => e.length === 0)) {
2080
- errs.push(`${path11 || "<root>"}: did not match anyOf`);
2080
+ errs.push(`${path12 || "<root>"}: did not match anyOf`);
2081
2081
  }
2082
2082
  return errs;
2083
2083
  }
2084
2084
  if ("oneOf" in schema) {
2085
- const passed = schema["oneOf"].filter((s) => validateSchema(value, s, path11).length === 0).length;
2085
+ const passed = schema["oneOf"].filter((s) => validateSchema(value, s, path12).length === 0).length;
2086
2086
  if (passed !== 1) {
2087
- errs.push(`${path11 || "<root>"}: matched ${passed} of oneOf, expected 1`);
2087
+ errs.push(`${path12 || "<root>"}: matched ${passed} of oneOf, expected 1`);
2088
2088
  }
2089
2089
  return errs;
2090
2090
  }
2091
2091
  if ("const" in schema && !deepEqual(value, schema["const"])) {
2092
2092
  errs.push(
2093
- `${path11 || "<root>"}: expected const ${pyRepr(schema["const"])}, got ${pyRepr(value)}`
2093
+ `${path12 || "<root>"}: expected const ${pyRepr(schema["const"])}, got ${pyRepr(value)}`
2094
2094
  );
2095
2095
  return errs;
2096
2096
  }
@@ -2100,7 +2100,7 @@ function validateSchema(value, schema, path11 = "") {
2100
2100
  const ok = types.some((tt) => matchesType(value, String(tt)));
2101
2101
  if (!ok) {
2102
2102
  errs.push(
2103
- `${path11 || "<root>"}: expected type ${pyReprType(t)}, got ${pyTypeName(value)}`
2103
+ `${path12 || "<root>"}: expected type ${pyReprType(t)}, got ${pyTypeName(value)}`
2104
2104
  );
2105
2105
  return errs;
2106
2106
  }
@@ -2109,29 +2109,29 @@ function validateSchema(value, schema, path11 = "") {
2109
2109
  if ("enum" in schema) {
2110
2110
  const en = schema["enum"];
2111
2111
  if (!en.some((x) => deepEqual(x, value))) {
2112
- errs.push(`${path11 || "<root>"}: value ${pyRepr(value)} not in enum`);
2112
+ errs.push(`${path12 || "<root>"}: value ${pyRepr(value)} not in enum`);
2113
2113
  }
2114
2114
  }
2115
2115
  if ("minLength" in schema && value.length < schema["minLength"]) {
2116
- errs.push(`${path11 || "<root>"}: shorter than minLength ${schema["minLength"]}`);
2116
+ errs.push(`${path12 || "<root>"}: shorter than minLength ${schema["minLength"]}`);
2117
2117
  }
2118
2118
  }
2119
2119
  if (typeof value === "number" && !Number.isNaN(value)) {
2120
2120
  if ("minimum" in schema && value < schema["minimum"]) {
2121
- errs.push(`${path11 || "<root>"}: ${value} < minimum ${schema["minimum"]}`);
2121
+ errs.push(`${path12 || "<root>"}: ${value} < minimum ${schema["minimum"]}`);
2122
2122
  }
2123
2123
  if ("maximum" in schema && value > schema["maximum"]) {
2124
- errs.push(`${path11 || "<root>"}: ${value} > maximum ${schema["maximum"]}`);
2124
+ errs.push(`${path12 || "<root>"}: ${value} > maximum ${schema["maximum"]}`);
2125
2125
  }
2126
2126
  }
2127
2127
  if (Array.isArray(value)) {
2128
2128
  if ("minItems" in schema && value.length < schema["minItems"]) {
2129
- errs.push(`${path11 || "<root>"}: fewer items than minItems ${schema["minItems"]}`);
2129
+ errs.push(`${path12 || "<root>"}: fewer items than minItems ${schema["minItems"]}`);
2130
2130
  }
2131
2131
  const itemSchema = schema["items"];
2132
2132
  if (isObj(itemSchema)) {
2133
2133
  for (let i = 0; i < value.length; i++) {
2134
- errs.push(...validateSchema(value[i], itemSchema, `${path11}[${i}]`));
2134
+ errs.push(...validateSchema(value[i], itemSchema, `${path12}[${i}]`));
2135
2135
  }
2136
2136
  }
2137
2137
  }
@@ -2140,19 +2140,19 @@ function validateSchema(value, schema, path11 = "") {
2140
2140
  const required = schema["required"] ?? [];
2141
2141
  for (const r of required) {
2142
2142
  if (!(r in value)) {
2143
- errs.push(`${path11 || "<root>"}: missing required key ${pyRepr(r)}`);
2143
+ errs.push(`${path12 || "<root>"}: missing required key ${pyRepr(r)}`);
2144
2144
  }
2145
2145
  }
2146
2146
  if (schema["additionalProperties"] === false) {
2147
2147
  for (const k of Object.keys(value)) {
2148
2148
  if (!(k in props)) {
2149
- errs.push(`${path11 || "<root>"}: unknown key ${pyRepr(k)}`);
2149
+ errs.push(`${path12 || "<root>"}: unknown key ${pyRepr(k)}`);
2150
2150
  }
2151
2151
  }
2152
2152
  }
2153
2153
  for (const [k, v] of Object.entries(value)) {
2154
2154
  const ps = props[k];
2155
- if (ps) errs.push(...validateSchema(v, ps, path11 ? `${path11}.${k}` : k));
2155
+ if (ps) errs.push(...validateSchema(v, ps, path12 ? `${path12}.${k}` : k));
2156
2156
  }
2157
2157
  }
2158
2158
  return errs;
@@ -6615,11 +6615,11 @@ function getNodeCache(cfg, repoRoot, key, nowMs) {
6615
6615
  return null;
6616
6616
  }
6617
6617
  }
6618
- function atomicWriteJson(path11, payload) {
6619
- (0, import_node_fs7.mkdirSync)((0, import_node_path8.dirname)(path11), { recursive: true });
6620
- const tmp = `${path11}.${process.pid}.${Date.now()}.tmp`;
6618
+ function atomicWriteJson(path12, payload) {
6619
+ (0, import_node_fs7.mkdirSync)((0, import_node_path8.dirname)(path12), { recursive: true });
6620
+ const tmp = `${path12}.${process.pid}.${Date.now()}.tmp`;
6621
6621
  (0, import_node_fs7.writeFileSync)(tmp, JSON.stringify(payload), "utf8");
6622
- (0, import_node_fs7.renameSync)(tmp, path11);
6622
+ (0, import_node_fs7.renameSync)(tmp, path12);
6623
6623
  }
6624
6624
  function putNodeCache(cfg, repoRoot, key, value) {
6625
6625
  if (!cfg || cfg.enabled === false) return;
@@ -7449,14 +7449,14 @@ function detectCycle(nodes) {
7449
7449
  indeg[id] = (indeg[id] ?? 0) + 1;
7450
7450
  }
7451
7451
  }
7452
- const queue = Object.keys(indeg).filter((id) => indeg[id] === 0);
7452
+ const queue2 = Object.keys(indeg).filter((id) => indeg[id] === 0);
7453
7453
  let processed = 0;
7454
- while (queue.length > 0) {
7455
- const id = queue.shift();
7454
+ while (queue2.length > 0) {
7455
+ const id = queue2.shift();
7456
7456
  processed++;
7457
7457
  for (const next of consumers[id] ?? []) {
7458
7458
  indeg[next] = (indeg[next] ?? 0) - 1;
7459
- if (indeg[next] === 0) queue.push(next);
7459
+ if (indeg[next] === 0) queue2.push(next);
7460
7460
  }
7461
7461
  }
7462
7462
  return processed === Object.keys(byId).length ? null : "cycle detected (Kahn's algorithm didn't process all nodes)";
@@ -7495,13 +7495,13 @@ function topologicalSort(nodesById) {
7495
7495
  }
7496
7496
  }
7497
7497
  const order = [];
7498
- const queue = Object.keys(indeg).filter((id) => indeg[id] === 0);
7499
- while (queue.length > 0) {
7500
- const id = queue.shift();
7498
+ const queue2 = Object.keys(indeg).filter((id) => indeg[id] === 0);
7499
+ while (queue2.length > 0) {
7500
+ const id = queue2.shift();
7501
7501
  order.push(id);
7502
7502
  for (const next of consumers[id] ?? []) {
7503
7503
  indeg[next] = (indeg[next] ?? 0) - 1;
7504
- if (indeg[next] === 0) queue.push(next);
7504
+ if (indeg[next] === 0) queue2.push(next);
7505
7505
  }
7506
7506
  }
7507
7507
  return order;
@@ -11330,8 +11330,110 @@ function pyRepr7(v) {
11330
11330
  // src/tools/update-crosscheck.ts
11331
11331
  init_cjs_shims();
11332
11332
  var import_node_child_process2 = require("child_process");
11333
+ var import_node_fs13 = require("fs");
11334
+ var import_node_path13 = __toESM(require("path"), 1);
11335
+
11336
+ // src/core/update-notify.ts
11337
+ init_cjs_shims();
11333
11338
  var import_node_fs12 = require("fs");
11339
+ var import_node_os2 = __toESM(require("os"), 1);
11334
11340
  var import_node_path12 = __toESM(require("path"), 1);
11341
+ var NPM_REGISTRY = "https://registry.npmjs.org";
11342
+ var CHECK_INTERVAL_SECONDS = 3 * 24 * 60 * 60;
11343
+ var DEFAULT_PACKAGE = "crosscheck-cli";
11344
+ var FETCH_TIMEOUT_MS = 3e3;
11345
+ function engineVersion() {
11346
+ return true ? "0.1.10" : "0.0.0-dev";
11347
+ }
11348
+ function defaultUpdateCachePath() {
11349
+ const base = process.env["CROSSCHECK_DATA_DIR"] || import_node_path12.default.join(import_node_os2.default.homedir() || import_node_os2.default.tmpdir(), ".crosscheck");
11350
+ return import_node_path12.default.join(base, "update_check.json");
11351
+ }
11352
+ function isNewer(latest, current) {
11353
+ const a = parseTriple(latest);
11354
+ const b = parseTriple(current);
11355
+ for (let i = 0; i < 3; i += 1) {
11356
+ if (a[i] !== b[i]) return a[i] > b[i];
11357
+ }
11358
+ return false;
11359
+ }
11360
+ function parseTriple(v) {
11361
+ const parts = (v.split("-")[0] ?? "").split(".").map((n) => Number.parseInt(n, 10) || 0);
11362
+ return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];
11363
+ }
11364
+ function buildNotice(pkg, current, latest) {
11365
+ return {
11366
+ update_available: true,
11367
+ package: pkg,
11368
+ current_version: current,
11369
+ latest_version: latest,
11370
+ message: `crosscheck ${latest} is available \u2014 you're on ${current}. To upgrade, run:
11371
+
11372
+ npm install -g crosscheck-cli@latest
11373
+
11374
+ then reconnect the crosscheck MCP server (run \`/mcp\` in Claude Code, or restart your session) so the new version loads. Ask the user whether they'd like to upgrade now before doing anything else.`,
11375
+ ask_user: true
11376
+ };
11377
+ }
11378
+ function readCache(cachePath2) {
11379
+ try {
11380
+ if (!(0, import_node_fs12.existsSync)(cachePath2)) return null;
11381
+ const data = JSON.parse((0, import_node_fs12.readFileSync)(cachePath2, "utf8"));
11382
+ if (data && typeof data === "object" && !Array.isArray(data)) {
11383
+ return data;
11384
+ }
11385
+ return null;
11386
+ } catch {
11387
+ return null;
11388
+ }
11389
+ }
11390
+ function writeCache(cachePath2, record2) {
11391
+ try {
11392
+ (0, import_node_fs12.mkdirSync)(import_node_path12.default.dirname(cachePath2), { recursive: true });
11393
+ (0, import_node_fs12.writeFileSync)(cachePath2, JSON.stringify(record2, null, 2), "utf8");
11394
+ } catch {
11395
+ }
11396
+ }
11397
+ async function fetchLatest(fetchFn, pkg) {
11398
+ try {
11399
+ const resp = await fetchFn(`${NPM_REGISTRY}/${pkg}/latest`, {
11400
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
11401
+ headers: { Accept: "application/json" }
11402
+ });
11403
+ if (!resp.ok) return null;
11404
+ const data = await resp.json();
11405
+ return typeof data?.version === "string" ? data.version : null;
11406
+ } catch {
11407
+ return null;
11408
+ }
11409
+ }
11410
+ async function maybeCheckForUpdate(opts) {
11411
+ const now = opts.nowEpochSeconds ? opts.nowEpochSeconds() : Math.floor(Date.now() / 1e3);
11412
+ const pkg = opts.packageName ?? DEFAULT_PACKAGE;
11413
+ const cachePath2 = opts.cachePath ?? defaultUpdateCachePath();
11414
+ const minInterval = opts.minIntervalSeconds ?? CHECK_INTERVAL_SECONDS;
11415
+ const fetchFn = opts.httpFetch ?? globalThis.fetch;
11416
+ const cached = readCache(cachePath2);
11417
+ const fresh = cached !== null && typeof cached.checked_at === "number" && now - cached.checked_at < minInterval && cached.current_version === opts.currentVersion && cached.package === pkg;
11418
+ if (fresh) return cached;
11419
+ const latest = await fetchLatest(fetchFn, pkg);
11420
+ if (latest === null) {
11421
+ return cached;
11422
+ }
11423
+ const updateAvailable = isNewer(latest, opts.currentVersion);
11424
+ const record2 = {
11425
+ checked_at: now,
11426
+ package: pkg,
11427
+ current_version: opts.currentVersion,
11428
+ latest_version: latest,
11429
+ update_available: updateAvailable,
11430
+ ...updateAvailable ? { notice: buildNotice(pkg, opts.currentVersion, latest) } : {}
11431
+ };
11432
+ writeCache(cachePath2, record2);
11433
+ return record2;
11434
+ }
11435
+
11436
+ // src/tools/update-crosscheck.ts
11335
11437
  var UPDATE_REMOTE_REPO = "fxspeiser/crosscheck-agent";
11336
11438
  var UPDATE_REMOTE_URL = `https://github.com/${UPDATE_REMOTE_REPO}`;
11337
11439
  var UPDATE_API_URL = `https://api.github.com/repos/${UPDATE_REMOTE_REPO}/commits/main`;
@@ -11341,7 +11443,7 @@ async function runUpdateCrosscheck(args, opts) {
11341
11443
  const gitRun = opts.gitRun ?? defaultGitRun(opts.repoRoot);
11342
11444
  const fetchFn = opts.httpFetch ?? globalThis.fetch;
11343
11445
  const now = opts.nowEpochSeconds ? opts.nowEpochSeconds() : Math.floor(Date.now() / 1e3);
11344
- const cachePath2 = opts.cachePath ?? import_node_path12.default.join(opts.repoRoot, ".crosscheck", "update_check.json");
11446
+ const cachePath2 = opts.cachePath ?? import_node_path13.default.join(opts.repoRoot, ".crosscheck", "update_check.json");
11345
11447
  const local = readLocalSha(gitRun);
11346
11448
  if (!local) {
11347
11449
  return {
@@ -11513,8 +11615,8 @@ function gitRelationship(gitRun, local, remote) {
11513
11615
  }
11514
11616
  function writeUpdateCache(p, payload) {
11515
11617
  try {
11516
- (0, import_node_fs12.mkdirSync)(import_node_path12.default.dirname(p), { recursive: true });
11517
- (0, import_node_fs12.writeFileSync)(p, JSON.stringify(payload, null, 2), "utf8");
11618
+ (0, import_node_fs13.mkdirSync)(import_node_path13.default.dirname(p), { recursive: true });
11619
+ (0, import_node_fs13.writeFileSync)(p, JSON.stringify(payload, null, 2), "utf8");
11518
11620
  } catch {
11519
11621
  }
11520
11622
  }
@@ -11530,6 +11632,43 @@ function buildUpdateNotice(local, remote, behindCount) {
11530
11632
  ask_user: true
11531
11633
  };
11532
11634
  }
11635
+ async function npmUpdateFallback(opts) {
11636
+ const cliVersion = process.env["CROSSCHECK_CLI_VERSION"];
11637
+ const currentVersion = cliVersion ?? engineVersion();
11638
+ const pkg = cliVersion ? "crosscheck-cli" : "crosscheck-mcp";
11639
+ const rec = await maybeCheckForUpdate({
11640
+ currentVersion,
11641
+ packageName: pkg,
11642
+ minIntervalSeconds: 0,
11643
+ // explicit invocation → always check fresh
11644
+ ...opts?.httpFetch ? { httpFetch: opts.httpFetch } : {},
11645
+ ...opts?.nowEpochSeconds ? { nowEpochSeconds: opts.nowEpochSeconds } : {},
11646
+ ...opts?.cachePath ? { cachePath: opts.cachePath } : {}
11647
+ });
11648
+ const base = {
11649
+ tool: "update_crosscheck",
11650
+ install_type: "npm",
11651
+ package: pkg,
11652
+ current_version: currentVersion
11653
+ };
11654
+ if (!rec || rec.latest_version === null) {
11655
+ return {
11656
+ ...base,
11657
+ status: "error",
11658
+ reason: "could not reach the npm registry to check for updates."
11659
+ };
11660
+ }
11661
+ if (rec.update_available) {
11662
+ return {
11663
+ ...base,
11664
+ status: "update_available",
11665
+ latest_version: rec.latest_version,
11666
+ install_command: "npm install -g crosscheck-cli@latest",
11667
+ next_step: "Run `npm install -g crosscheck-cli@latest`, then reconnect the crosscheck MCP server (run `/mcp` in Claude Code, or restart your session) so the new version loads. Ask the user before upgrading."
11668
+ };
11669
+ }
11670
+ return { ...base, status: "up_to_date", latest_version: rec.latest_version };
11671
+ }
11533
11672
  function withTimeout2(promise, ms) {
11534
11673
  return new Promise((resolve2, reject) => {
11535
11674
  const t = setTimeout(() => reject(new Error(`timeout after ${ms / 1e3}s`)), ms);
@@ -11884,7 +12023,7 @@ function solveTool(providers, allowlist, bridge, storage, breakers, transcriptsD
11884
12023
  })
11885
12024
  };
11886
12025
  }
11887
- function configPinTool(repoRoot, config, rejectDriftEnv) {
12026
+ function configPinTool(repoRoot, config2, rejectDriftEnv) {
11888
12027
  return {
11889
12028
  name: "config_pin",
11890
12029
  description: "CRUD over the config-pinning ledger. Tracks SHA256s of the canonical config files (config/pricing.json, etc.) and detects drift. Actions: show, set, accept_drift, clear. The actual drift-rejection gate is wired separately in the host.",
@@ -11909,7 +12048,7 @@ function configPinTool(repoRoot, config, rejectDriftEnv) {
11909
12048
  }
11910
12049
  return runConfigPin(args, {
11911
12050
  repoRoot,
11912
- ...config ? { config } : {},
12051
+ ...config2 ? { config: config2 } : {},
11913
12052
  rejectDriftEnv
11914
12053
  });
11915
12054
  }
@@ -11945,7 +12084,7 @@ function benchTool(providers, allowlist, storage, bridge, moderator, defaultGold
11945
12084
  function updateCrosscheckTool(repoRoot) {
11946
12085
  return {
11947
12086
  name: "update_crosscheck",
11948
- description: "Compare local git HEAD to remote GitHub main HEAD and report the relationship (equal / ahead / behind / diverged / unknown). With apply=true, fast-forwards via git pull --ff-only when the local is strictly behind. Restart of the MCP connection is required after a successful update.",
12087
+ description: "Check whether a newer version of crosscheck is available and report how to upgrade. For npm installs (the default), compares the running version to the published `latest` on npm and returns the `npm install -g crosscheck-cli@latest` upgrade command. For a git checkout, compares local HEAD to remote GitHub main (equal / ahead / behind / diverged) and, with apply=true, fast-forwards via git pull --ff-only. A reconnect of the MCP server is required after upgrading.",
11949
12088
  inputSchema: {
11950
12089
  type: "object",
11951
12090
  additionalProperties: true,
@@ -11955,12 +12094,7 @@ function updateCrosscheckTool(repoRoot) {
11955
12094
  },
11956
12095
  handler: async (args) => {
11957
12096
  if (!repoRoot) {
11958
- return {
11959
- tool: "update_crosscheck",
11960
- status: "error",
11961
- reason: "could not determine repo root; the entrypoint did not supply a repoRoot. crosscheck-agent must be installed as a git checkout for in-place updates.",
11962
- remote_url: "https://github.com/fxspeiser/crosscheck-agent"
11963
- };
12097
+ return npmUpdateFallback();
11964
12098
  }
11965
12099
  return runUpdateCrosscheck(args, { repoRoot });
11966
12100
  }
@@ -12497,8 +12631,8 @@ function pingTool() {
12497
12631
 
12498
12632
  // src/core/wrappers.ts
12499
12633
  init_cjs_shims();
12500
- var import_node_fs13 = require("fs");
12501
- var import_node_path13 = __toESM(require("path"), 1);
12634
+ var import_node_fs14 = require("fs");
12635
+ var import_node_path14 = __toESM(require("path"), 1);
12502
12636
  function configPinGate(toolName, opts) {
12503
12637
  if (toolName === "config_pin") return null;
12504
12638
  if (!opts || !opts.repoRoot) return null;
@@ -12527,12 +12661,11 @@ function configPinGate(toolName, opts) {
12527
12661
  pin_file: drift.pin_file
12528
12662
  };
12529
12663
  }
12530
- var updateCheckedThisProcess = false;
12531
12664
  var cachedUpdateNotice = null;
12532
12665
  function readUpdateCache(cachePath2) {
12533
12666
  try {
12534
- if (!(0, import_node_fs13.existsSync)(cachePath2)) return null;
12535
- const data = JSON.parse((0, import_node_fs13.readFileSync)(cachePath2, "utf8"));
12667
+ if (!(0, import_node_fs14.existsSync)(cachePath2)) return null;
12668
+ const data = JSON.parse((0, import_node_fs14.readFileSync)(cachePath2, "utf8"));
12536
12669
  if (data && typeof data === "object" && !Array.isArray(data)) return data;
12537
12670
  return null;
12538
12671
  } catch {
@@ -12543,10 +12676,8 @@ function attachUpdateNotice(result, toolName, opts) {
12543
12676
  if (!result || typeof result !== "object" || Array.isArray(result)) return;
12544
12677
  if (toolName === "update_crosscheck") return;
12545
12678
  if ("update_notice" in result) return;
12546
- if (!opts || !opts.repoRoot) return;
12547
- if (!updateCheckedThisProcess) {
12548
- updateCheckedThisProcess = true;
12549
- const cachePath2 = opts.cachePath ?? import_node_path13.default.join(opts.repoRoot, ".crosscheck", "update_check.json");
12679
+ if (cachedUpdateNotice === null) {
12680
+ const cachePath2 = opts?.cachePath ?? (opts?.repoRoot ? import_node_path14.default.join(opts.repoRoot, ".crosscheck", "update_check.json") : defaultUpdateCachePath());
12550
12681
  const cached = readUpdateCache(cachePath2);
12551
12682
  if (cached && cached.update_available && cached.notice && typeof cached.notice === "object") {
12552
12683
  cachedUpdateNotice = cached.notice;
@@ -12557,9 +12688,203 @@ function attachUpdateNotice(result, toolName, opts) {
12557
12688
  }
12558
12689
  }
12559
12690
 
12691
+ // src/core/telemetry.ts
12692
+ init_cjs_shims();
12693
+ var import_node_crypto7 = __toESM(require("crypto"), 1);
12694
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
12695
+ var BATCH_MAX = 100;
12696
+ var FLUSH_BATCH_AT = 25;
12697
+ var FLUSH_INTERVAL_MS = 15e3;
12698
+ var MAX_QUEUE = 500;
12699
+ var POST_TIMEOUT_MS = 5e3;
12700
+ var PROVIDER_TO_SCHEMA = {
12701
+ anthropic: "anthropic",
12702
+ openai: "openai",
12703
+ gemini: "gemini",
12704
+ xai: "grok",
12705
+ grok: "grok"
12706
+ };
12707
+ var TOOL_TO_PATTERN = {
12708
+ confer: "confer",
12709
+ debate: "debate",
12710
+ audit: "audit",
12711
+ plan: "plan",
12712
+ pick: "panel_pick",
12713
+ verify: "verify",
12714
+ critique: "critique",
12715
+ solve: "solve"
12716
+ };
12717
+ function providerToSchema(provider) {
12718
+ return PROVIDER_TO_SCHEMA[String(provider).toLowerCase()] ?? null;
12719
+ }
12720
+ function toolToPattern(toolName) {
12721
+ return TOOL_TO_PATTERN[String(toolName).toLowerCase()] ?? "other";
12722
+ }
12723
+ function canonicalEventBody(e) {
12724
+ return [
12725
+ e.eventId,
12726
+ e.seatId,
12727
+ e.orgId,
12728
+ e.ts,
12729
+ e.provider,
12730
+ e.model,
12731
+ e.pattern,
12732
+ String(e.promptTokens),
12733
+ String(e.completionTokens),
12734
+ e.costUsdEstimate.toFixed(6),
12735
+ String(e.latencyMs),
12736
+ e.status,
12737
+ e.errorClass ?? ""
12738
+ ].join("|");
12739
+ }
12740
+ function signEvent(seatSigningKey, e) {
12741
+ return import_node_crypto7.default.createHmac("sha256", seatSigningKey).update(canonicalEventBody(e)).digest("hex");
12742
+ }
12743
+ var configResolved = false;
12744
+ var config = null;
12745
+ function getConfig() {
12746
+ if (configResolved) return config;
12747
+ configResolved = true;
12748
+ if (process.env["CROSSCHECK_TELEMETRY"] === "off") return config = null;
12749
+ const url = process.env["CROSSCHECK_TELEMETRY_URL"];
12750
+ const seatId = process.env["CROSSCHECK_SEAT_ID"];
12751
+ const orgId = process.env["CROSSCHECK_ORG_ID"];
12752
+ const seatKey = process.env["CROSSCHECK_SEAT_HMAC_KEY"];
12753
+ if (!url || !seatId || !orgId || !seatKey) return config = null;
12754
+ if (!UUID_RE.test(seatId) || !UUID_RE.test(orgId)) return config = null;
12755
+ config = { url, seatId, orgId, seatKey };
12756
+ return config;
12757
+ }
12758
+ function intNonNeg(v) {
12759
+ const n = Math.trunc(Number(v));
12760
+ return Number.isFinite(n) && n > 0 ? n : 0;
12761
+ }
12762
+ function usageEventsFromEnvelope(out, toolName) {
12763
+ if (!out || typeof out !== "object" || Array.isArray(out)) return [];
12764
+ const usage = out["usage"];
12765
+ if (!usage || typeof usage !== "object") return [];
12766
+ const byCall = usage["by_call"];
12767
+ if (!Array.isArray(byCall) || byCall.length === 0) return [];
12768
+ const timing = out["timing"];
12769
+ const timingByCall = timing && typeof timing === "object" ? timing["by_call"] : void 0;
12770
+ const latencies = Array.isArray(timingByCall) ? timingByCall : [];
12771
+ const aligned = latencies.length === byCall.length;
12772
+ const pattern = toolToPattern(toolName);
12773
+ const events = [];
12774
+ for (let i = 0; i < byCall.length; i += 1) {
12775
+ const u = byCall[i];
12776
+ const provider = providerToSchema(String(u.provider ?? ""));
12777
+ if (!provider) continue;
12778
+ const model = String(u.model ?? "").slice(0, 128) || "unknown";
12779
+ const cost = Math.max(0, Number(u.cost_usd) || 0);
12780
+ const latencyMs = aligned ? intNonNeg(latencies[i]?.["wall_ms"]) : 0;
12781
+ events.push({
12782
+ provider,
12783
+ model,
12784
+ pattern,
12785
+ promptTokens: intNonNeg(u.prompt_tokens),
12786
+ completionTokens: intNonNeg(u.completion_tokens),
12787
+ costUsdEstimate: cost,
12788
+ latencyMs,
12789
+ status: "ok"
12790
+ });
12791
+ }
12792
+ return events;
12793
+ }
12794
+ var queue = [];
12795
+ var flushTimer = null;
12796
+ function scheduleFlush() {
12797
+ if (flushTimer) return;
12798
+ flushTimer = setTimeout(() => {
12799
+ flushTimer = null;
12800
+ void flush();
12801
+ }, FLUSH_INTERVAL_MS);
12802
+ if (typeof flushTimer.unref === "function") flushTimer.unref();
12803
+ }
12804
+ function enqueue(cfg, input) {
12805
+ const base = {
12806
+ eventId: import_node_crypto7.default.randomUUID(),
12807
+ seatId: cfg.seatId,
12808
+ orgId: cfg.orgId,
12809
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
12810
+ provider: input.provider,
12811
+ model: input.model,
12812
+ pattern: input.pattern,
12813
+ promptTokens: input.promptTokens,
12814
+ completionTokens: input.completionTokens,
12815
+ costUsdEstimate: input.costUsdEstimate,
12816
+ latencyMs: input.latencyMs,
12817
+ status: input.status
12818
+ };
12819
+ const signatureHmac = signEvent(cfg.seatKey, base);
12820
+ queue.push({ ...base, signatureHmac });
12821
+ if (queue.length > MAX_QUEUE) queue.splice(0, queue.length - MAX_QUEUE);
12822
+ if (queue.length >= FLUSH_BATCH_AT) void flush();
12823
+ else scheduleFlush();
12824
+ }
12825
+ function recordUsage(out, toolName) {
12826
+ try {
12827
+ const cfg = getConfig();
12828
+ if (!cfg) return;
12829
+ if (toolName === "update_crosscheck") return;
12830
+ for (const ev of usageEventsFromEnvelope(out, toolName)) enqueue(cfg, ev);
12831
+ } catch {
12832
+ }
12833
+ }
12834
+ async function postBatch(url, batch) {
12835
+ let resp;
12836
+ try {
12837
+ resp = await fetch(url, {
12838
+ method: "POST",
12839
+ headers: { "content-type": "application/json" },
12840
+ body: JSON.stringify({ events: batch }),
12841
+ signal: AbortSignal.timeout(POST_TIMEOUT_MS)
12842
+ });
12843
+ } catch {
12844
+ queue.unshift(...batch);
12845
+ if (queue.length > MAX_QUEUE) queue.splice(0, queue.length - MAX_QUEUE);
12846
+ return;
12847
+ }
12848
+ if (resp.ok) return;
12849
+ if (resp.status === 400) {
12850
+ try {
12851
+ process.stderr.write(
12852
+ `crosscheck-agent: telemetry batch rejected (400), dropped ${batch.length} event(s)
12853
+ `
12854
+ );
12855
+ } catch {
12856
+ }
12857
+ return;
12858
+ }
12859
+ queue.unshift(...batch);
12860
+ if (queue.length > MAX_QUEUE) queue.splice(0, queue.length - MAX_QUEUE);
12861
+ }
12862
+ async function flush() {
12863
+ const cfg = getConfig();
12864
+ if (!cfg) return;
12865
+ if (flushTimer) {
12866
+ clearTimeout(flushTimer);
12867
+ flushTimer = null;
12868
+ }
12869
+ while (queue.length > 0) {
12870
+ const batch = queue.splice(0, BATCH_MAX);
12871
+ await postBatch(cfg.url, batch);
12872
+ if (queue.length > 0 && queue[0] === batch[0]) break;
12873
+ }
12874
+ }
12875
+
12560
12876
  // src/server.ts
12561
12877
  var SERVER_NAME = "crosscheck-agent";
12562
- var SERVER_VERSION = true ? "0.1.8" : "0.0.0-dev";
12878
+ var SERVER_VERSION = true ? "0.1.10" : "0.0.0-dev";
12879
+ function extractRunSummaryText(out) {
12880
+ if (out === null || typeof out !== "object" || Array.isArray(out)) return void 0;
12881
+ const rs = out["run_summary"];
12882
+ if (rs === null || typeof rs !== "object" || Array.isArray(rs)) return void 0;
12883
+ const text = rs["text"];
12884
+ if (typeof text !== "string") return void 0;
12885
+ const trimmed = text.trim();
12886
+ return trimmed === "" ? void 0 : text;
12887
+ }
12563
12888
  function createServer(opts = {}) {
12564
12889
  const server = new import_server2.Server(
12565
12890
  { name: SERVER_NAME, version: SERVER_VERSION },
@@ -12632,10 +12957,9 @@ function createServer(opts = {}) {
12632
12957
  () => tool.handler(args)
12633
12958
  );
12634
12959
  if (out && typeof out === "object" && !Array.isArray(out)) {
12635
- attachUpdateNotice(out, name, {
12636
- ...opts.repoRoot ? { repoRoot: opts.repoRoot } : {}
12637
- });
12960
+ attachUpdateNotice(out, name);
12638
12961
  }
12962
+ recordUsage(out, name);
12639
12963
  const durationMs = Number(
12640
12964
  (process.hrtime.bigint() - startedHr) / 1000000n
12641
12965
  );
@@ -12654,9 +12978,10 @@ function createServer(opts = {}) {
12654
12978
  };
12655
12979
  if (typeof errCode === "string") ev.error_code = errCode;
12656
12980
  emitEvent(ev);
12657
- return {
12658
- content: [{ type: "text", text: JSON.stringify(out, null, 2) }]
12659
- };
12981
+ const summary = extractRunSummaryText(out);
12982
+ const jsonBlock = { type: "text", text: JSON.stringify(out, null, 2) };
12983
+ const content = summary !== void 0 ? [{ type: "text", text: summary }, jsonBlock] : [jsonBlock];
12984
+ return { content };
12660
12985
  } catch (e) {
12661
12986
  const durationMs = Number(
12662
12987
  (process.hrtime.bigint() - startedHr) / 1000000n
@@ -12681,6 +13006,11 @@ function createServer(opts = {}) {
12681
13006
  async function connectAndServe(transport, opts = {}) {
12682
13007
  const server = createServer(opts);
12683
13008
  await server.connect(transport);
13009
+ const cliVersion = process.env["CROSSCHECK_CLI_VERSION"];
13010
+ void maybeCheckForUpdate({
13011
+ currentVersion: cliVersion ?? SERVER_VERSION,
13012
+ packageName: cliVersion ? "crosscheck-cli" : "crosscheck-mcp"
13013
+ });
12684
13014
  return server;
12685
13015
  }
12686
13016
  function buildToolRegistry(opts) {
@@ -12729,7 +13059,7 @@ async function main() {
12729
13059
  installShutdownHandlers(bridge);
12730
13060
  const bundledPricing = (() => {
12731
13061
  try {
12732
- return import_node_path14.default.join(import_node_path14.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl)), "pricing.json");
13062
+ return import_node_path15.default.join(import_node_path15.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl)), "pricing.json");
12733
13063
  } catch {
12734
13064
  return void 0;
12735
13065
  }
@@ -12738,7 +13068,7 @@ async function main() {
12738
13068
  process.env["CROSSCHECK_PRICING_PATH"],
12739
13069
  resolveRepoFile("config/pricing.json"),
12740
13070
  bundledPricing
12741
- ].find((p) => p && (0, import_node_fs14.existsSync)(p));
13071
+ ].find((p) => p && (0, import_node_fs15.existsSync)(p));
12742
13072
  const pricing = pricingPath ? loadPricing(pricingPath) : {};
12743
13073
  const providers = buildProviders({ env: process.env, pricing });
12744
13074
  if (Object.keys(providers).length > 0) {
@@ -12752,7 +13082,7 @@ async function main() {
12752
13082
  const dbPath = process.env["CROSSCHECK_DB_PATH"] ?? resolveRepoFile(".crosscheck/db.sqlite");
12753
13083
  if (dbPath) {
12754
13084
  try {
12755
- (0, import_node_fs14.mkdirSync)(import_node_path14.default.dirname(dbPath), { recursive: true });
13085
+ (0, import_node_fs15.mkdirSync)(import_node_path15.default.dirname(dbPath), { recursive: true });
12756
13086
  const { openBetterSqliteStorage: openBetterSqliteStorage2 } = await Promise.resolve().then(() => (init_better_sqlite3(), better_sqlite3_exports));
12757
13087
  storage = openBetterSqliteStorage2({ path: dbPath });
12758
13088
  await storage.migrate();
@@ -12796,6 +13126,11 @@ function installShutdownHandlers(bridge) {
12796
13126
  ]).catch(() => {
12797
13127
  });
12798
13128
  }
13129
+ await Promise.race([
13130
+ flush(),
13131
+ new Promise((resolve2) => setTimeout(resolve2, SHUTDOWN_TIMEOUT_MS))
13132
+ ]).catch(() => {
13133
+ });
12799
13134
  process.stderr.write(`crosscheck-agent: shutdown (${reason})
12800
13135
  `);
12801
13136
  process.exit(0);
@@ -12816,9 +13151,9 @@ function installShutdownHandlers(bridge) {
12816
13151
  function resolveRepoFile(rel) {
12817
13152
  let dir = __dirname;
12818
13153
  for (let i = 0; i < 8; i++) {
12819
- const candidate = import_node_path14.default.join(dir, rel);
12820
- if ((0, import_node_fs14.existsSync)(candidate)) return candidate;
12821
- const parent = import_node_path14.default.dirname(dir);
13154
+ const candidate = import_node_path15.default.join(dir, rel);
13155
+ if ((0, import_node_fs15.existsSync)(candidate)) return candidate;
13156
+ const parent = import_node_path15.default.dirname(dir);
12822
13157
  if (parent === dir) break;
12823
13158
  dir = parent;
12824
13159
  }
@@ -12827,8 +13162,8 @@ function resolveRepoFile(rel) {
12827
13162
  function findGitRoot(startDir) {
12828
13163
  let dir = startDir;
12829
13164
  for (let i = 0; i < 12; i++) {
12830
- if ((0, import_node_fs14.existsSync)(import_node_path14.default.join(dir, ".git"))) return dir;
12831
- const parent = import_node_path14.default.dirname(dir);
13165
+ if ((0, import_node_fs15.existsSync)(import_node_path15.default.join(dir, ".git"))) return dir;
13166
+ const parent = import_node_path15.default.dirname(dir);
12832
13167
  if (parent === dir) break;
12833
13168
  dir = parent;
12834
13169
  }