crosscheck-mcp 0.2.14 → 0.2.16

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.
@@ -958,8 +958,8 @@ __export(node_stdio_exports, {
958
958
  });
959
959
  module.exports = __toCommonJS(node_stdio_exports);
960
960
  init_cjs_shims();
961
- var import_node_fs18 = require("fs");
962
- var import_node_path17 = __toESM(require("path"), 1);
961
+ var import_node_fs19 = require("fs");
962
+ var import_node_path18 = __toESM(require("path"), 1);
963
963
  var import_node_url2 = require("url");
964
964
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
965
965
 
@@ -976,10 +976,10 @@ var StderrEmitter = class {
976
976
  }
977
977
  };
978
978
  var FileEmitter = class {
979
- constructor(path14) {
980
- this.path = path14;
979
+ constructor(path15) {
980
+ this.path = path15;
981
981
  try {
982
- (0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(path14), { recursive: true });
982
+ (0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(path15), { recursive: true });
983
983
  } catch {
984
984
  }
985
985
  }
@@ -1012,13 +1012,13 @@ var NullEmitter = class {
1012
1012
  function buildDefaultEmitter(env) {
1013
1013
  const mode = (env["CROSSCHECK_EVENTS"] ?? "stderr").toLowerCase();
1014
1014
  if (mode === "off") return new NullEmitter();
1015
- const path14 = env["CROSSCHECK_EVENTS_PATH"];
1015
+ const path15 = env["CROSSCHECK_EVENTS_PATH"];
1016
1016
  if (mode === "file") {
1017
- return path14 ? new FileEmitter(path14) : new NullEmitter();
1017
+ return path15 ? new FileEmitter(path15) : new NullEmitter();
1018
1018
  }
1019
1019
  if (mode === "both") {
1020
1020
  const ems = [new StderrEmitter()];
1021
- if (path14) ems.push(new FileEmitter(path14));
1021
+ if (path15) ems.push(new FileEmitter(path15));
1022
1022
  return new TeeEmitter(ems);
1023
1023
  }
1024
1024
  return new StderrEmitter();
@@ -1067,10 +1067,10 @@ function envelopeBytes(v) {
1067
1067
  // src/core/pricing.ts
1068
1068
  init_cjs_shims();
1069
1069
  var import_node_fs2 = require("fs");
1070
- function loadPricing(path14) {
1070
+ function loadPricing(path15) {
1071
1071
  let raw;
1072
1072
  try {
1073
- raw = (0, import_node_fs2.readFileSync)(path14, "utf8");
1073
+ raw = (0, import_node_fs2.readFileSync)(path15, "utf8");
1074
1074
  } catch {
1075
1075
  return {};
1076
1076
  }
@@ -1242,11 +1242,18 @@ var ProviderError = class extends Error {
1242
1242
  status;
1243
1243
  transient;
1244
1244
  retryAfterS;
1245
+ /** True specifically for "the key is fine but this model isn't
1246
+ * available to this account/project" (see http-errors.ts's
1247
+ * isModelAccessFailure) — the one failure a model fallback chain can
1248
+ * actually fix. Distinct from `transient`: this is never worth
1249
+ * retrying the SAME model, but IS worth trying a different one. */
1250
+ modelAccessFailure;
1245
1251
  constructor(kind, message, opts) {
1246
1252
  super(message);
1247
1253
  this.kind = kind;
1248
1254
  this.status = opts?.status;
1249
1255
  this.transient = opts?.transient ?? defaultTransient(kind);
1256
+ this.modelAccessFailure = opts?.modelAccessFailure ?? false;
1250
1257
  if (opts?.retryAfterS !== void 0) {
1251
1258
  this.retryAfterS = opts.retryAfterS;
1252
1259
  }
@@ -1278,7 +1285,7 @@ function httpFailureToProviderError(provider, status, bodyText, retryAfterS, mod
1278
1285
  return new ProviderError(
1279
1286
  "client",
1280
1287
  `${provider}: your account/project doesn't have access to ${modelRef} (HTTP ${status}). This is a model-access problem, not a key problem \u2014 your ${provider} API key is fine, ${modelRef} just isn't enabled for this account/project. Fix: run \`crosscheck models set ${provider} <a-model-you-have-access-to>\` (or set ${provider.toUpperCase()}_MODEL in your .env until that's available). Detail: ${detail}`,
1281
- { status }
1288
+ { status, modelAccessFailure: true }
1282
1289
  );
1283
1290
  }
1284
1291
  if (isCreditsFailure(status, bodyText)) {
@@ -1319,8 +1326,8 @@ function httpFailureToProviderError(provider, status, bodyText, retryAfterS, mod
1319
1326
  // src/providers/retry.ts
1320
1327
  init_cjs_shims();
1321
1328
  var override = null;
1322
- function setRetryConfig(cfg) {
1323
- override = cfg;
1329
+ function setRetryConfig(cfg2) {
1330
+ override = cfg2;
1324
1331
  }
1325
1332
  function resolveRetryConfig() {
1326
1333
  const envInt = (k, d) => {
@@ -1382,21 +1389,21 @@ function defaultSleep(ms, signal) {
1382
1389
  signal?.addEventListener?.("abort", onAbort);
1383
1390
  });
1384
1391
  }
1385
- async function sendWithRetry(attemptOnce, cfg, signal, deps) {
1392
+ async function sendWithRetry(attemptOnce, cfg2, signal, deps) {
1386
1393
  const sleep = deps?.sleepImpl ?? ((ms) => defaultSleep(ms, signal));
1387
1394
  const rand = deps?.random ?? Math.random;
1388
1395
  let lastErr;
1389
- for (let attempt = 1; attempt <= cfg.maxAttempts; attempt += 1) {
1396
+ for (let attempt = 1; attempt <= cfg2.maxAttempts; attempt += 1) {
1390
1397
  try {
1391
1398
  const result = await attemptOnce();
1392
1399
  return { ...result, attempts: attempt };
1393
1400
  } catch (e) {
1394
1401
  lastErr = e;
1395
1402
  const pe = e instanceof ProviderError ? e : null;
1396
- if (pe?.transient !== true || attempt >= cfg.maxAttempts) throw e;
1397
- let backoffMs = pe.retryAfterS !== void 0 ? pe.retryAfterS * 1e3 : cfg.backoffBaseMs * 2 ** (attempt - 1);
1398
- backoffMs += rand() * cfg.backoffBaseMs;
1399
- backoffMs = Math.min(backoffMs, cfg.maxBackoffMs);
1403
+ if (pe?.transient !== true || attempt >= cfg2.maxAttempts) throw e;
1404
+ let backoffMs = pe.retryAfterS !== void 0 ? pe.retryAfterS * 1e3 : cfg2.backoffBaseMs * 2 ** (attempt - 1);
1405
+ backoffMs += rand() * cfg2.backoffBaseMs;
1406
+ backoffMs = Math.min(backoffMs, cfg2.maxBackoffMs);
1400
1407
  await sleep(backoffMs);
1401
1408
  }
1402
1409
  }
@@ -1408,8 +1415,8 @@ init_cjs_shims();
1408
1415
  var DEFAULT_SPEC = { capacity: 5, refillPerSec: 5 };
1409
1416
  var MAX_WAIT_SLICE_MS = 250;
1410
1417
  var override2 = null;
1411
- function setRateLimitConfig(cfg) {
1412
- override2 = cfg;
1418
+ function setRateLimitConfig(cfg2) {
1419
+ override2 = cfg2;
1413
1420
  buckets.clear();
1414
1421
  }
1415
1422
  function disabled() {
@@ -1494,6 +1501,14 @@ function buildAnthropicRequest(opts) {
1494
1501
  if (system !== void 0) {
1495
1502
  body.system = system;
1496
1503
  }
1504
+ if (isReasoningModel("anthropic", opts.model)) {
1505
+ if (opts.model.toLowerCase().startsWith("claude-fable-5")) {
1506
+ body.thinking = { type: "adaptive" };
1507
+ body.output_config = { effort: "low" };
1508
+ } else {
1509
+ body.thinking = { type: "disabled" };
1510
+ }
1511
+ }
1497
1512
  if (opts.jsonSchema) {
1498
1513
  body.tools = [{
1499
1514
  name: ANTHROPIC_STRUCTURED_TOOL_NAME,
@@ -1561,7 +1576,12 @@ function parseAnthropicResponse(opts) {
1561
1576
  purpose: opts.purpose
1562
1577
  };
1563
1578
  usage.total_tokens = usage.prompt_tokens + usage.completion_tokens;
1564
- return { text, usage };
1579
+ const stopReasonRaw = r["stop_reason"];
1580
+ const stopReason = typeof stopReasonRaw === "string" ? stopReasonRaw : void 0;
1581
+ return { text, usage, ...stopReason !== void 0 ? { stopReason } : {} };
1582
+ }
1583
+ function isEmptyDueToMaxTokens(text, stopReason) {
1584
+ return text.trim() === "" && stopReason === "max_tokens";
1565
1585
  }
1566
1586
  function applyPricing(usage, pricing) {
1567
1587
  const { cost_usd, estimated } = calculateCost(
@@ -1579,49 +1599,64 @@ function applyPricing(usage, pricing) {
1579
1599
  estimated: usage.estimated || estimated
1580
1600
  };
1581
1601
  }
1602
+ var MAX_TOKENS_ESCALATIONS = 2;
1603
+ var ESCALATION_MULTIPLIER = 2;
1582
1604
  async function sendAnthropic(args) {
1583
- const { url, headers, body } = buildAnthropicRequest({
1584
- model: args.model,
1585
- apiKey: args.apiKey,
1586
- messages: args.messages,
1587
- maxTokens: args.maxTokens,
1588
- temperature: args.temperature,
1589
- ...args.jsonSchema ? { jsonSchema: args.jsonSchema } : {}
1590
- });
1591
1605
  const doFetch = args.fetchImpl ?? globalThis.fetch;
1592
- const init = {
1593
- method: "POST",
1594
- headers,
1595
- body: JSON.stringify(body)
1596
- };
1597
- if (args.signal) init.signal = args.signal;
1598
- const attemptOnce = async () => {
1599
- let respLike;
1600
- try {
1601
- respLike = await doFetch(url, init);
1602
- } catch (e) {
1603
- throw new ProviderError("network", `anthropic: fetch failed: ${e.message}`);
1604
- }
1605
- const status = respLike.status;
1606
- if (status >= 200 && status < 300) {
1607
- let parsed;
1606
+ let maxTokens = args.maxTokens;
1607
+ let result = null;
1608
+ let totalAttempts = 0;
1609
+ for (let escalation = 0; escalation <= MAX_TOKENS_ESCALATIONS; escalation += 1) {
1610
+ const { url, headers, body } = buildAnthropicRequest({
1611
+ model: args.model,
1612
+ apiKey: args.apiKey,
1613
+ messages: args.messages,
1614
+ maxTokens,
1615
+ temperature: args.temperature,
1616
+ ...args.jsonSchema ? { jsonSchema: args.jsonSchema } : {}
1617
+ });
1618
+ const init = {
1619
+ method: "POST",
1620
+ headers,
1621
+ body: JSON.stringify(body)
1622
+ };
1623
+ if (args.signal) init.signal = args.signal;
1624
+ let stopReason;
1625
+ const attemptOnce = async () => {
1626
+ let respLike;
1608
1627
  try {
1609
- parsed = await respLike.json();
1628
+ respLike = await doFetch(url, init);
1610
1629
  } catch (e) {
1611
- throw new ProviderError("parse", `anthropic: response body not JSON: ${e.message}`);
1630
+ throw new ProviderError("network", `anthropic: fetch failed: ${e.message}`);
1612
1631
  }
1613
- const { text, usage } = parseAnthropicResponse({
1614
- resp: parsed,
1615
- model: args.model,
1616
- purpose: args.purpose ?? "worker"
1617
- });
1618
- return { text, attempts: 1, usage: applyPricing(usage, args.pricing) };
1632
+ const status = respLike.status;
1633
+ if (status >= 200 && status < 300) {
1634
+ let parsed;
1635
+ try {
1636
+ parsed = await respLike.json();
1637
+ } catch (e) {
1638
+ throw new ProviderError("parse", `anthropic: response body not JSON: ${e.message}`);
1639
+ }
1640
+ const { text, usage, stopReason: sr } = parseAnthropicResponse({
1641
+ resp: parsed,
1642
+ model: args.model,
1643
+ purpose: args.purpose ?? "worker"
1644
+ });
1645
+ stopReason = sr;
1646
+ return { text, attempts: 1, usage: applyPricing(usage, args.pricing) };
1647
+ }
1648
+ const bodyText = await respLike.text().catch(() => "");
1649
+ throw httpFailureToProviderError("anthropic", status, bodyText, parseRetryAfter(respLike), args.model);
1650
+ };
1651
+ await acquireRateLimit("anthropic", args.signal ? { signal: args.signal } : void 0);
1652
+ result = await sendWithRetry(attemptOnce, resolveRetryConfig(), args.signal);
1653
+ totalAttempts += result.attempts;
1654
+ if (escalation >= MAX_TOKENS_ESCALATIONS || !isEmptyDueToMaxTokens(result.text, stopReason)) {
1655
+ break;
1619
1656
  }
1620
- const bodyText = await respLike.text().catch(() => "");
1621
- throw httpFailureToProviderError("anthropic", status, bodyText, parseRetryAfter(respLike), args.model);
1622
- };
1623
- await acquireRateLimit("anthropic", args.signal ? { signal: args.signal } : void 0);
1624
- return sendWithRetry(attemptOnce, resolveRetryConfig(), args.signal);
1657
+ maxTokens *= ESCALATION_MULTIPLIER;
1658
+ }
1659
+ return { ...result, attempts: totalAttempts };
1625
1660
  }
1626
1661
 
1627
1662
  // src/providers/gemini.ts
@@ -2007,19 +2042,27 @@ function detectStalePins(env) {
2007
2042
  const out = [];
2008
2043
  for (const [provider, envVar] of Object.entries(MODEL_ENV_VARS)) {
2009
2044
  const pinned = env[envVar];
2010
- const current = DEFAULT_MODELS[provider];
2011
- if (pinned && current && pinned !== current) {
2012
- out.push({ provider, envVar, pinned, current });
2045
+ const current2 = DEFAULT_MODELS[provider];
2046
+ if (pinned && current2 && pinned !== current2) {
2047
+ out.push({ provider, envVar, pinned, current: current2 });
2013
2048
  }
2014
2049
  }
2015
2050
  return out;
2016
2051
  }
2052
+ function parseFallbackModels(raw) {
2053
+ if (!raw) return [];
2054
+ return raw.split(",").map((s) => s.trim()).filter(Boolean);
2055
+ }
2056
+ function fallbackModelsFor(env, provider) {
2057
+ const envVar = MODEL_ENV_VARS[provider];
2058
+ return envVar ? parseFallbackModels(env[`${envVar}_FALLBACKS`]) : [];
2059
+ }
2017
2060
  function buildProviders(opts) {
2018
2061
  const out = {};
2019
2062
  const anthropicKey = opts.env["ANTHROPIC_API_KEY"];
2020
2063
  if (anthropicKey) {
2021
2064
  const model = opts.env["ANTHROPIC_MODEL"] ?? DEFAULT_MODELS.anthropic;
2022
- out["anthropic"] = makeAnthropicProvider(model, anthropicKey, opts);
2065
+ out["anthropic"] = makeAnthropicProvider(model, anthropicKey, opts, fallbackModelsFor(opts.env, "anthropic"));
2023
2066
  }
2024
2067
  const openAiCompatSpec = [
2025
2068
  { name: "openai", keyEnv: "OPENAI_API_KEY", modelEnv: "OPENAI_MODEL", defaultModel: DEFAULT_MODELS["openai"] },
@@ -2034,19 +2077,20 @@ function buildProviders(opts) {
2034
2077
  const apiKey = opts.env[s.keyEnv];
2035
2078
  if (!apiKey) continue;
2036
2079
  const model = opts.env[s.modelEnv] ?? s.defaultModel;
2037
- out[s.name] = makeOpenAICompatibleProvider(s.name, model, apiKey, opts);
2080
+ out[s.name] = makeOpenAICompatibleProvider(s.name, model, apiKey, opts, fallbackModelsFor(opts.env, s.name));
2038
2081
  }
2039
2082
  const geminiKey = opts.env["GEMINI_API_KEY"];
2040
2083
  if (geminiKey) {
2041
2084
  const model = opts.env["GEMINI_MODEL"] ?? DEFAULT_MODELS.gemini;
2042
- out["gemini"] = makeGeminiProvider(model, geminiKey, opts);
2085
+ out["gemini"] = makeGeminiProvider(model, geminiKey, opts, fallbackModelsFor(opts.env, "gemini"));
2043
2086
  }
2044
2087
  return out;
2045
2088
  }
2046
- function makeAnthropicProvider(model, apiKey, opts) {
2089
+ function makeAnthropicProvider(model, apiKey, opts, fallbackModels = []) {
2047
2090
  return {
2048
2091
  name: "anthropic",
2049
2092
  model,
2093
+ fallbackModels,
2050
2094
  send: async (args) => {
2051
2095
  const sendOpts = {
2052
2096
  ...args,
@@ -2059,11 +2103,12 @@ function makeAnthropicProvider(model, apiKey, opts) {
2059
2103
  }
2060
2104
  };
2061
2105
  }
2062
- function makeOpenAICompatibleProvider(name, model, apiKey, opts) {
2106
+ function makeOpenAICompatibleProvider(name, model, apiKey, opts, fallbackModels = []) {
2063
2107
  const url = OPENAI_COMPAT_DEFAULT_URLS[name];
2064
2108
  return {
2065
2109
  name,
2066
2110
  model,
2111
+ fallbackModels,
2067
2112
  send: async (args) => {
2068
2113
  const sendOpts = {
2069
2114
  ...args,
@@ -2078,10 +2123,11 @@ function makeOpenAICompatibleProvider(name, model, apiKey, opts) {
2078
2123
  }
2079
2124
  };
2080
2125
  }
2081
- function makeGeminiProvider(model, apiKey, opts) {
2126
+ function makeGeminiProvider(model, apiKey, opts, fallbackModels = []) {
2082
2127
  return {
2083
2128
  name: "gemini",
2084
2129
  model,
2130
+ fallbackModels,
2085
2131
  send: async (args) => {
2086
2132
  const sendOpts = {
2087
2133
  ...args,
@@ -2098,7 +2144,7 @@ function makeGeminiProvider(model, apiKey, opts) {
2098
2144
  // src/server.ts
2099
2145
  init_cjs_shims();
2100
2146
  var import_server = require("@modelcontextprotocol/sdk/server/index.js");
2101
- var import_types8 = require("@modelcontextprotocol/sdk/types.js");
2147
+ var import_types9 = require("@modelcontextprotocol/sdk/types.js");
2102
2148
 
2103
2149
  // src/bridge/index.ts
2104
2150
  init_cjs_shims();
@@ -2229,7 +2275,7 @@ var import_zod = require("zod");
2229
2275
  // src/server-meta.ts
2230
2276
  init_cjs_shims();
2231
2277
  var SERVER_NAME = "crosscheck-agent";
2232
- var SERVER_VERSION = true ? "0.2.14" : "0.0.0-dev";
2278
+ var SERVER_VERSION = true ? "0.2.16" : "0.0.0-dev";
2233
2279
 
2234
2280
  // src/tools/audit.ts
2235
2281
  init_cjs_shims();
@@ -2296,27 +2342,27 @@ function extractJson(text) {
2296
2342
 
2297
2343
  // src/core/json-schema.ts
2298
2344
  init_cjs_shims();
2299
- function validateSchema(value, schema, path14 = "") {
2345
+ function validateSchema(value, schema, path15 = "") {
2300
2346
  const errs = [];
2301
2347
  if ("anyOf" in schema) {
2302
2348
  const subs = schema["anyOf"].map(
2303
- (s) => validateSchema(value, s, path14)
2349
+ (s) => validateSchema(value, s, path15)
2304
2350
  );
2305
2351
  if (!subs.some((e) => e.length === 0)) {
2306
- errs.push(`${path14 || "<root>"}: did not match anyOf`);
2352
+ errs.push(`${path15 || "<root>"}: did not match anyOf`);
2307
2353
  }
2308
2354
  return errs;
2309
2355
  }
2310
2356
  if ("oneOf" in schema) {
2311
- const passed = schema["oneOf"].filter((s) => validateSchema(value, s, path14).length === 0).length;
2357
+ const passed = schema["oneOf"].filter((s) => validateSchema(value, s, path15).length === 0).length;
2312
2358
  if (passed !== 1) {
2313
- errs.push(`${path14 || "<root>"}: matched ${passed} of oneOf, expected 1`);
2359
+ errs.push(`${path15 || "<root>"}: matched ${passed} of oneOf, expected 1`);
2314
2360
  }
2315
2361
  return errs;
2316
2362
  }
2317
2363
  if ("const" in schema && !deepEqual(value, schema["const"])) {
2318
2364
  errs.push(
2319
- `${path14 || "<root>"}: expected const ${pyRepr(schema["const"])}, got ${pyRepr(value)}`
2365
+ `${path15 || "<root>"}: expected const ${pyRepr(schema["const"])}, got ${pyRepr(value)}`
2320
2366
  );
2321
2367
  return errs;
2322
2368
  }
@@ -2326,7 +2372,7 @@ function validateSchema(value, schema, path14 = "") {
2326
2372
  const ok = types.some((tt) => matchesType(value, String(tt)));
2327
2373
  if (!ok) {
2328
2374
  errs.push(
2329
- `${path14 || "<root>"}: expected type ${pyReprType(t)}, got ${pyTypeName(value)}`
2375
+ `${path15 || "<root>"}: expected type ${pyReprType(t)}, got ${pyTypeName(value)}`
2330
2376
  );
2331
2377
  return errs;
2332
2378
  }
@@ -2335,29 +2381,29 @@ function validateSchema(value, schema, path14 = "") {
2335
2381
  if ("enum" in schema) {
2336
2382
  const en = schema["enum"];
2337
2383
  if (!en.some((x) => deepEqual(x, value))) {
2338
- errs.push(`${path14 || "<root>"}: value ${pyRepr(value)} not in enum`);
2384
+ errs.push(`${path15 || "<root>"}: value ${pyRepr(value)} not in enum`);
2339
2385
  }
2340
2386
  }
2341
2387
  if ("minLength" in schema && value.length < schema["minLength"]) {
2342
- errs.push(`${path14 || "<root>"}: shorter than minLength ${schema["minLength"]}`);
2388
+ errs.push(`${path15 || "<root>"}: shorter than minLength ${schema["minLength"]}`);
2343
2389
  }
2344
2390
  }
2345
2391
  if (typeof value === "number" && !Number.isNaN(value)) {
2346
2392
  if ("minimum" in schema && value < schema["minimum"]) {
2347
- errs.push(`${path14 || "<root>"}: ${value} < minimum ${schema["minimum"]}`);
2393
+ errs.push(`${path15 || "<root>"}: ${value} < minimum ${schema["minimum"]}`);
2348
2394
  }
2349
2395
  if ("maximum" in schema && value > schema["maximum"]) {
2350
- errs.push(`${path14 || "<root>"}: ${value} > maximum ${schema["maximum"]}`);
2396
+ errs.push(`${path15 || "<root>"}: ${value} > maximum ${schema["maximum"]}`);
2351
2397
  }
2352
2398
  }
2353
2399
  if (Array.isArray(value)) {
2354
2400
  if ("minItems" in schema && value.length < schema["minItems"]) {
2355
- errs.push(`${path14 || "<root>"}: fewer items than minItems ${schema["minItems"]}`);
2401
+ errs.push(`${path15 || "<root>"}: fewer items than minItems ${schema["minItems"]}`);
2356
2402
  }
2357
2403
  const itemSchema = schema["items"];
2358
2404
  if (isObj(itemSchema)) {
2359
2405
  for (let i = 0; i < value.length; i++) {
2360
- errs.push(...validateSchema(value[i], itemSchema, `${path14}[${i}]`));
2406
+ errs.push(...validateSchema(value[i], itemSchema, `${path15}[${i}]`));
2361
2407
  }
2362
2408
  }
2363
2409
  }
@@ -2366,19 +2412,19 @@ function validateSchema(value, schema, path14 = "") {
2366
2412
  const required = schema["required"] ?? [];
2367
2413
  for (const r of required) {
2368
2414
  if (!(r in value)) {
2369
- errs.push(`${path14 || "<root>"}: missing required key ${pyRepr(r)}`);
2415
+ errs.push(`${path15 || "<root>"}: missing required key ${pyRepr(r)}`);
2370
2416
  }
2371
2417
  }
2372
2418
  if (schema["additionalProperties"] === false) {
2373
2419
  for (const k of Object.keys(value)) {
2374
2420
  if (!(k in props)) {
2375
- errs.push(`${path14 || "<root>"}: unknown key ${pyRepr(k)}`);
2421
+ errs.push(`${path15 || "<root>"}: unknown key ${pyRepr(k)}`);
2376
2422
  }
2377
2423
  }
2378
2424
  }
2379
2425
  for (const [k, v] of Object.entries(value)) {
2380
2426
  const ps = props[k];
2381
- if (ps) errs.push(...validateSchema(v, ps, path14 ? `${path14}.${k}` : k));
2427
+ if (ps) errs.push(...validateSchema(v, ps, path15 ? `${path15}.${k}` : k));
2382
2428
  }
2383
2429
  }
2384
2430
  return errs;
@@ -2879,8 +2925,8 @@ async function recordSessionCall(storage, sessionId, answers, wallMs, cpuMs, now
2879
2925
  // src/core/budgets.ts
2880
2926
  init_cjs_shims();
2881
2927
  var operatorBudgets = null;
2882
- function setOperatorBudgets(cfg) {
2883
- operatorBudgets = cfg;
2928
+ function setOperatorBudgets(cfg2) {
2929
+ operatorBudgets = cfg2;
2884
2930
  }
2885
2931
  function operatorCeiling(purpose, provider) {
2886
2932
  const direct = operatorBudgets?.token_budgets?.[purpose];
@@ -2891,6 +2937,55 @@ function operatorCeiling(purpose, provider) {
2891
2937
  return null;
2892
2938
  }
2893
2939
 
2940
+ // src/core/model-fallback.ts
2941
+ init_cjs_shims();
2942
+
2943
+ // src/core/retarget.ts
2944
+ init_cjs_shims();
2945
+ function retargetProvider(p, newModel) {
2946
+ if (p.model === newModel) return p;
2947
+ return {
2948
+ name: p.name,
2949
+ model: newModel,
2950
+ send: (args) => p.send({ ...args, modelOverride: newModel })
2951
+ };
2952
+ }
2953
+ async function loadProviderWeights(storage, names) {
2954
+ const out = {};
2955
+ for (const raw of names) {
2956
+ const name = raw.toLowerCase();
2957
+ if (name in out) continue;
2958
+ const row = await storage.getProviderStats(name);
2959
+ if (!row) {
2960
+ out[name] = 0.5;
2961
+ continue;
2962
+ }
2963
+ const total = row.wins + row.losses + row.abstains;
2964
+ out[name] = total <= 0 ? 0.5 : (row.wins + 0.5 * row.abstains) / total;
2965
+ }
2966
+ return out;
2967
+ }
2968
+
2969
+ // src/core/model-fallback.ts
2970
+ async function sendWithModelFallback(provider, args) {
2971
+ const chain = [provider.model, ...provider.fallbackModels ?? []];
2972
+ let lastErr;
2973
+ for (let i = 0; i < chain.length; i += 1) {
2974
+ const candidate = chain[i];
2975
+ const p = i === 0 ? provider : retargetProvider(provider, candidate);
2976
+ try {
2977
+ const result = await p.send(args);
2978
+ return { result, modelUsed: candidate };
2979
+ } catch (e) {
2980
+ lastErr = e;
2981
+ const isModelAccessFailure2 = e instanceof ProviderError && e.modelAccessFailure;
2982
+ const hasMoreCandidates = i < chain.length - 1;
2983
+ if (!isModelAccessFailure2 || !hasMoreCandidates) throw e;
2984
+ }
2985
+ }
2986
+ throw lastErr;
2987
+ }
2988
+
2894
2989
  // src/core/structured.ts
2895
2990
  async function requestStructured(provider, baseMessages, schema, opts) {
2896
2991
  const maxRetries = opts.maxRetries ?? 1;
@@ -2969,7 +3064,7 @@ async function askOne(provider, messages, opts) {
2969
3064
  const ceiling = operatorCeiling(opts.purpose, provider.name);
2970
3065
  if (ceiling !== null && ceiling < maxTokens) maxTokens = ceiling;
2971
3066
  try {
2972
- const r = await provider.send({
3067
+ const { result: r, modelUsed } = await sendWithModelFallback(provider, {
2973
3068
  messages,
2974
3069
  maxTokens,
2975
3070
  temperature: opts.temperature,
@@ -2982,7 +3077,10 @@ async function askOne(provider, messages, opts) {
2982
3077
  const cpuMs = Math.trunc((cpu.user + cpu.system) / 1e3);
2983
3078
  return {
2984
3079
  provider: provider.name,
2985
- model: provider.model,
3080
+ // modelUsed reflects whichever model actually answered — the
3081
+ // primary, or a fallback if one was needed. Equivalent to the old
3082
+ // `provider.model` whenever no fallback occurred.
3083
+ model: modelUsed,
2986
3084
  response: r.text,
2987
3085
  attempts: r.attempts,
2988
3086
  usage: r.usage,
@@ -3029,8 +3127,8 @@ function isPersonaId(s) {
3029
3127
  return PERSONA_IDS.includes(s);
3030
3128
  }
3031
3129
  var personaConfig = null;
3032
- function setPersonaConfig(cfg) {
3033
- personaConfig = cfg;
3130
+ function setPersonaConfig(cfg2) {
3131
+ personaConfig = cfg2;
3034
3132
  }
3035
3133
  var processFirstCallConsumed = false;
3036
3134
  var sessionsSeen = /* @__PURE__ */ new Set();
@@ -3097,8 +3195,8 @@ function routePersona(prompt, toolName) {
3097
3195
  return { id: "engineering", scores };
3098
3196
  }
3099
3197
  function resolvePersona(opts) {
3100
- const cfg = opts.config ?? personaConfig;
3101
- if (cfg?.enabled === false) return { id: null, source: "disabled", bypassed: true };
3198
+ const cfg2 = opts.config ?? personaConfig;
3199
+ if (cfg2?.enabled === false) return { id: null, source: "disabled", bypassed: true };
3102
3200
  const explicit = opts.explicit?.trim().toLowerCase();
3103
3201
  if (explicit === "none" || explicit === "off") {
3104
3202
  return { id: null, source: "none", bypassed: true };
@@ -3144,12 +3242,12 @@ function readIfExists(p) {
3144
3242
  }
3145
3243
  }
3146
3244
  function loadPersonaBody(id, opts) {
3147
- const cfg = opts?.config ?? personaConfig;
3148
- const cacheKey = `${id}|${opts?.repoRoot ?? ""}|${cfg?.dir ?? ""}`;
3245
+ const cfg2 = opts?.config ?? personaConfig;
3246
+ const cacheKey = `${id}|${opts?.repoRoot ?? ""}|${cfg2?.dir ?? ""}`;
3149
3247
  const cached = bodyCache.get(cacheKey);
3150
3248
  if (cached !== void 0) return cached;
3151
3249
  const candidates = [];
3152
- if (cfg?.dir) candidates.push(import_node_path2.default.join(cfg.dir, `${id}.md`));
3250
+ if (cfg2?.dir) candidates.push(import_node_path2.default.join(cfg2.dir, `${id}.md`));
3153
3251
  if (opts?.repoRoot) candidates.push(import_node_path2.default.join(opts.repoRoot, ".crosscheck", "personas", `${id}.md`));
3154
3252
  if (id === "engineering" && opts?.repoRoot) candidates.push(import_node_path2.default.join(opts.repoRoot, "CLAUDE.md"));
3155
3253
  const bundled = bundledPersonasDir();
@@ -3199,9 +3297,9 @@ function buildPersonaInjection(opts) {
3199
3297
 
3200
3298
  // src/core/utils.ts
3201
3299
  init_cjs_shims();
3202
- function checkSessionBreakers(session, cfg) {
3300
+ function checkSessionBreakers(session, cfg2) {
3203
3301
  if (!session || typeof session !== "object") return null;
3204
- const c = cfg ?? {};
3302
+ const c = cfg2 ?? {};
3205
3303
  const costCap = Number(c.max_session_cost_usd ?? 0);
3206
3304
  if (costCap > 0 && Number(session.total_cost_usd ?? 0) >= costCap) {
3207
3305
  return {
@@ -3225,10 +3323,10 @@ function checkSessionBreakers(session, cfg) {
3225
3323
  }
3226
3324
  return null;
3227
3325
  }
3228
- function checkDagBreakers(dag, cfg) {
3326
+ function checkDagBreakers(dag, cfg2) {
3229
3327
  if (!dag || typeof dag !== "object") return null;
3230
3328
  const nodes = Array.isArray(dag.nodes) ? dag.nodes : [];
3231
- const c = cfg ?? {};
3329
+ const c = cfg2 ?? {};
3232
3330
  const maxNodes = Math.trunc(Number(c.max_dag_nodes ?? 0));
3233
3331
  if (maxNodes > 0 && nodes.length > maxNodes) {
3234
3332
  return {
@@ -3313,8 +3411,8 @@ function breakerEnvelope(toolName, trip, sessionId) {
3313
3411
  transient: false
3314
3412
  };
3315
3413
  }
3316
- async function maybeBreakerEnvelope(storage, sessionId, toolName, cfg, extras) {
3317
- if (!sessionId || !storage || !cfg) return null;
3414
+ async function maybeBreakerEnvelope(storage, sessionId, toolName, cfg2, extras) {
3415
+ if (!sessionId || !storage || !cfg2) return null;
3318
3416
  let session;
3319
3417
  try {
3320
3418
  session = await storage.getSession(sessionId) ?? null;
@@ -3322,13 +3420,13 @@ async function maybeBreakerEnvelope(storage, sessionId, toolName, cfg, extras) {
3322
3420
  return null;
3323
3421
  }
3324
3422
  const target = extras && extras.length > 0 ? projectSessionWithAnswers(session, extras) : session;
3325
- const trip = checkSessionBreakers(target, cfg);
3423
+ const trip = checkSessionBreakers(target, cfg2);
3326
3424
  if (!trip) return null;
3327
3425
  return breakerEnvelope(toolName, trip, sessionId);
3328
3426
  }
3329
- function maybeDagBreakerEnvelope(dag, toolName, cfg, sessionId) {
3330
- if (!cfg) return null;
3331
- const trip = checkDagBreakers(dag ?? null, cfg);
3427
+ function maybeDagBreakerEnvelope(dag, toolName, cfg2, sessionId) {
3428
+ if (!cfg2) return null;
3429
+ const trip = checkDagBreakers(dag ?? null, cfg2);
3332
3430
  if (!trip) return null;
3333
3431
  return breakerEnvelope(toolName, trip, sessionId);
3334
3432
  }
@@ -3351,9 +3449,9 @@ function builtinRedactionRules() {
3351
3449
  { pattern: /\b(?:\d{4}[ -]?){3}\d{4}\b/g, label: "CARD", prefix_group: false }
3352
3450
  ];
3353
3451
  }
3354
- function compileRules(cfg) {
3452
+ function compileRules(cfg2) {
3355
3453
  const rules = builtinRedactionRules();
3356
- const extras = cfg?.patterns_extra ?? [];
3454
+ const extras = cfg2?.patterns_extra ?? [];
3357
3455
  for (const src of extras) {
3358
3456
  try {
3359
3457
  rules.push({ pattern: new RegExp(src, "g"), label: "EXTRA", prefix_group: false });
@@ -3362,25 +3460,25 @@ function compileRules(cfg) {
3362
3460
  }
3363
3461
  return { rules };
3364
3462
  }
3365
- function hmacSuffix(cfg, label, value) {
3366
- const secret = cfg.hmac_secret ?? Buffer.alloc(0);
3367
- const sid = cfg.session_id ?? "";
3463
+ function hmacSuffix(cfg2, label, value) {
3464
+ const secret = cfg2.hmac_secret ?? Buffer.alloc(0);
3465
+ const sid = cfg2.session_id ?? "";
3368
3466
  const sidBytes = Buffer.from(sid, "utf8");
3369
3467
  const key = Buffer.concat([Buffer.from(secret), sidBytes]);
3370
3468
  const digest = (0, import_node_crypto2.createHmac)("sha256", key).update(`${label}:${value}`, "utf8").digest("hex");
3371
3469
  return digest.slice(0, 8);
3372
3470
  }
3373
- function redactText(s, cfg) {
3471
+ function redactText(s, cfg2) {
3374
3472
  if (typeof s !== "string" || s.length === 0) return s;
3375
- if (cfg && cfg.enabled === false) return s;
3376
- const { rules } = compileRules(cfg);
3377
- const hmacMode = Boolean(cfg?.hmac_tokens);
3473
+ if (cfg2 && cfg2.enabled === false) return s;
3474
+ const { rules } = compileRules(cfg2);
3475
+ const hmacMode = Boolean(cfg2?.hmac_tokens);
3378
3476
  let out = s;
3379
3477
  for (const { pattern, label, prefix_group } of rules) {
3380
3478
  pattern.lastIndex = 0;
3381
3479
  if (hmacMode) {
3382
3480
  out = out.replace(pattern, (match, ...args) => {
3383
- const suffix = hmacSuffix(cfg ?? {}, label, match);
3481
+ const suffix = hmacSuffix(cfg2 ?? {}, label, match);
3384
3482
  const token = `[REDACTED_${label}:${suffix}]`;
3385
3483
  if (prefix_group) {
3386
3484
  const g1 = args[0];
@@ -3400,23 +3498,23 @@ function redactText(s, cfg) {
3400
3498
  }
3401
3499
  return out;
3402
3500
  }
3403
- function redactObj(obj, cfg) {
3404
- if (typeof obj === "string") return redactText(obj, cfg);
3501
+ function redactObj(obj, cfg2) {
3502
+ if (typeof obj === "string") return redactText(obj, cfg2);
3405
3503
  if (Array.isArray(obj)) {
3406
- return obj.map((v) => redactObj(v, cfg));
3504
+ return obj.map((v) => redactObj(v, cfg2));
3407
3505
  }
3408
3506
  if (obj && typeof obj === "object") {
3409
3507
  const out = {};
3410
3508
  for (const [k, v] of Object.entries(obj)) {
3411
- out[k] = redactObj(v, cfg);
3509
+ out[k] = redactObj(v, cfg2);
3412
3510
  }
3413
3511
  return out;
3414
3512
  }
3415
3513
  return obj;
3416
3514
  }
3417
3515
  var globalRedaction = null;
3418
- function setRedactionConfig(cfg) {
3419
- globalRedaction = cfg;
3516
+ function setRedactionConfig(cfg2) {
3517
+ globalRedaction = cfg2;
3420
3518
  }
3421
3519
  function getRedactionConfig() {
3422
3520
  return globalRedaction ?? void 0;
@@ -3703,32 +3801,6 @@ function numberOrNull(v) {
3703
3801
  // src/tools/audit.ts
3704
3802
  var import_node_perf_hooks = require("perf_hooks");
3705
3803
 
3706
- // src/core/retarget.ts
3707
- init_cjs_shims();
3708
- function retargetProvider(p, newModel) {
3709
- if (p.model === newModel) return p;
3710
- return {
3711
- name: p.name,
3712
- model: newModel,
3713
- send: (args) => p.send({ ...args, modelOverride: newModel })
3714
- };
3715
- }
3716
- async function loadProviderWeights(storage, names) {
3717
- const out = {};
3718
- for (const raw of names) {
3719
- const name = raw.toLowerCase();
3720
- if (name in out) continue;
3721
- const row = await storage.getProviderStats(name);
3722
- if (!row) {
3723
- out[name] = 0.5;
3724
- continue;
3725
- }
3726
- const total = row.wins + row.losses + row.abstains;
3727
- out[name] = total <= 0 ? 0.5 : (row.wins + 0.5 * row.abstains) / total;
3728
- }
3729
- return out;
3730
- }
3731
-
3732
3804
  // src/core/co-reason.ts
3733
3805
  init_cjs_shims();
3734
3806
  var CO_REASON_MODEL = "gpt-5.6";
@@ -4692,8 +4764,8 @@ function neutralizeInjection(s) {
4692
4764
 
4693
4765
  // src/core/worker.ts
4694
4766
  var WORKER_TOOL_COST_CAP_MODES = ["warn", "enforce", "off"];
4695
- function workerToolCostCapDefaults(callerCapUsd, callerMode, cfg) {
4696
- const cfgObj = cfg ?? {};
4767
+ function workerToolCostCapDefaults(callerCapUsd, callerMode, cfg2) {
4768
+ const cfgObj = cfg2 ?? {};
4697
4769
  let capRaw = callerCapUsd !== void 0 && callerCapUsd !== null ? callerCapUsd : cfgObj.cost_cap_usd;
4698
4770
  let cap;
4699
4771
  if (capRaw === void 0 || capRaw === null) {
@@ -6304,7 +6376,7 @@ function tryKill(child, signal, isUnix) {
6304
6376
  init_cjs_shims();
6305
6377
  function shlexSplit(s) {
6306
6378
  const out = [];
6307
- let current = "";
6379
+ let current2 = "";
6308
6380
  let hasCurrent = false;
6309
6381
  let mode = "normal";
6310
6382
  for (let i = 0; i < s.length; i++) {
@@ -6322,7 +6394,7 @@ function shlexSplit(s) {
6322
6394
  }
6323
6395
  if (ch === "\\") {
6324
6396
  if (i + 1 < s.length) {
6325
- current += s[i + 1];
6397
+ current2 += s[i + 1];
6326
6398
  i++;
6327
6399
  hasCurrent = true;
6328
6400
  }
@@ -6330,13 +6402,13 @@ function shlexSplit(s) {
6330
6402
  }
6331
6403
  if (ch === " " || ch === " " || ch === "\n" || ch === "\r") {
6332
6404
  if (hasCurrent) {
6333
- out.push(current);
6334
- current = "";
6405
+ out.push(current2);
6406
+ current2 = "";
6335
6407
  hasCurrent = false;
6336
6408
  }
6337
6409
  continue;
6338
6410
  }
6339
- current += ch;
6411
+ current2 += ch;
6340
6412
  hasCurrent = true;
6341
6413
  continue;
6342
6414
  }
@@ -6345,7 +6417,7 @@ function shlexSplit(s) {
6345
6417
  mode = "normal";
6346
6418
  continue;
6347
6419
  }
6348
- current += ch;
6420
+ current2 += ch;
6349
6421
  continue;
6350
6422
  }
6351
6423
  if (mode === "double") {
@@ -6356,21 +6428,21 @@ function shlexSplit(s) {
6356
6428
  if (ch === "\\") {
6357
6429
  const nxt = s[i + 1];
6358
6430
  if (nxt === "$" || nxt === "`" || nxt === '"' || nxt === "\\" || nxt === "\n") {
6359
- current += nxt;
6431
+ current2 += nxt;
6360
6432
  i++;
6361
6433
  continue;
6362
6434
  }
6363
- current += "\\";
6435
+ current2 += "\\";
6364
6436
  continue;
6365
6437
  }
6366
- current += ch;
6438
+ current2 += ch;
6367
6439
  continue;
6368
6440
  }
6369
6441
  }
6370
6442
  if (mode !== "normal") {
6371
6443
  throw new Error("unmatched quote");
6372
6444
  }
6373
- if (hasCurrent) out.push(current);
6445
+ if (hasCurrent) out.push(current2);
6374
6446
  return out;
6375
6447
  }
6376
6448
 
@@ -7078,16 +7150,16 @@ function computeDrift(opts) {
7078
7150
  const absPath = resolvePinPath(opts);
7079
7151
  const doc = loadDoc(absPath);
7080
7152
  const pinned = doc && typeof doc === "object" && doc["pins"] && typeof doc["pins"] === "object" ? doc["pins"] : {};
7081
- const current = computeCurrent(opts);
7153
+ const current2 = computeCurrent(opts);
7082
7154
  const drift = [];
7083
7155
  for (const [p, h] of Object.entries(pinned)) {
7084
- if (current[p] && current[p] !== h) drift.push(p);
7156
+ if (current2[p] && current2[p] !== h) drift.push(p);
7085
7157
  }
7086
- const missingFromPin = Object.keys(current).filter((p) => !(p in pinned));
7087
- const missingFiles = Object.keys(pinned).filter((p) => !(p in current));
7158
+ const missingFromPin = Object.keys(current2).filter((p) => !(p in pinned));
7159
+ const missingFiles = Object.keys(pinned).filter((p) => !(p in current2));
7088
7160
  return {
7089
7161
  pinned,
7090
- current,
7162
+ current: current2,
7091
7163
  drift: drift.sort(),
7092
7164
  missing_from_pin: missingFromPin.sort(),
7093
7165
  missing_files: missingFiles.sort(),
@@ -7372,19 +7444,19 @@ function sortKeyJsonifyTopLevel(json) {
7372
7444
  for (const k of Object.keys(obj).sort()) sorted[k] = obj[k];
7373
7445
  return JSON.stringify(sorted);
7374
7446
  }
7375
- function resolveCacheDir(cfg, repoRoot) {
7376
- const raw = cfg.dir ?? ".crosscheck/node_cache";
7447
+ function resolveCacheDir(cfg2, repoRoot) {
7448
+ const raw = cfg2.dir ?? ".crosscheck/node_cache";
7377
7449
  if ((0, import_node_path8.isAbsolute)(raw)) return raw;
7378
7450
  return (0, import_node_path8.resolve)(repoRoot ?? process.cwd(), raw);
7379
7451
  }
7380
- function cachePath(cfg, repoRoot, key) {
7381
- return (0, import_node_path8.join)(resolveCacheDir(cfg, repoRoot), key.slice(0, 2), `${key}.json`);
7452
+ function cachePath(cfg2, repoRoot, key) {
7453
+ return (0, import_node_path8.join)(resolveCacheDir(cfg2, repoRoot), key.slice(0, 2), `${key}.json`);
7382
7454
  }
7383
- function getNodeCache(cfg, repoRoot, key, nowMs) {
7384
- if (!cfg || cfg.enabled === false) return null;
7385
- const p = cachePath(cfg, repoRoot, key);
7455
+ function getNodeCache(cfg2, repoRoot, key, nowMs) {
7456
+ if (!cfg2 || cfg2.enabled === false) return null;
7457
+ const p = cachePath(cfg2, repoRoot, key);
7386
7458
  if (!(0, import_node_fs8.existsSync)(p)) return null;
7387
- const ttlS = cfg.ttl_seconds ?? 604800;
7459
+ const ttlS = cfg2.ttl_seconds ?? 604800;
7388
7460
  try {
7389
7461
  const st = (0, import_node_fs8.statSync)(p);
7390
7462
  if (ttlS > 0) {
@@ -7402,21 +7474,21 @@ function getNodeCache(cfg, repoRoot, key, nowMs) {
7402
7474
  return null;
7403
7475
  }
7404
7476
  }
7405
- function atomicWriteJson(path14, payload) {
7406
- (0, import_node_fs8.mkdirSync)((0, import_node_path8.dirname)(path14), { recursive: true });
7407
- const tmp = `${path14}.${process.pid}.${Date.now()}.tmp`;
7477
+ function atomicWriteJson(path15, payload) {
7478
+ (0, import_node_fs8.mkdirSync)((0, import_node_path8.dirname)(path15), { recursive: true });
7479
+ const tmp = `${path15}.${process.pid}.${Date.now()}.tmp`;
7408
7480
  (0, import_node_fs8.writeFileSync)(tmp, JSON.stringify(payload), "utf8");
7409
- (0, import_node_fs8.renameSync)(tmp, path14);
7481
+ (0, import_node_fs8.renameSync)(tmp, path15);
7410
7482
  }
7411
- function putNodeCache(cfg, repoRoot, key, value) {
7412
- if (!cfg || cfg.enabled === false) return;
7483
+ function putNodeCache(cfg2, repoRoot, key, value) {
7484
+ if (!cfg2 || cfg2.enabled === false) return;
7413
7485
  try {
7414
- const p = cachePath(cfg, repoRoot, key);
7486
+ const p = cachePath(cfg2, repoRoot, key);
7415
7487
  atomicWriteJson(p, value);
7416
- const maxEntries = cfg.max_entries ?? 2e3;
7417
- const maxBytes = cfg.max_cache_bytes ?? 100 * 1024 * 1024;
7488
+ const maxEntries = cfg2.max_entries ?? 2e3;
7489
+ const maxBytes = cfg2.max_cache_bytes ?? 100 * 1024 * 1024;
7418
7490
  if (maxEntries <= 0 && maxBytes <= 0) return;
7419
- const base = resolveCacheDir(cfg, repoRoot);
7491
+ const base = resolveCacheDir(cfg2, repoRoot);
7420
7492
  if (!(0, import_node_fs8.existsSync)(base)) return;
7421
7493
  const entries = [];
7422
7494
  let totalBytes = 0;
@@ -8534,31 +8606,31 @@ async function runFetch(args, opts) {
8534
8606
  const url = String(args["url"] ?? "");
8535
8607
  const force = Boolean(args["force_refresh"]);
8536
8608
  const sessionId = typeof args["session_id"] === "string" ? args["session_id"] : null;
8537
- const cfg = { ...DEFAULT_CONFIG, ...opts.config ?? {} };
8609
+ const cfg2 = { ...DEFAULT_CONFIG, ...opts.config ?? {} };
8538
8610
  const base = { tool: "fetch", url };
8539
- if (!cfg.enabled) {
8611
+ if (!cfg2.enabled) {
8540
8612
  return { ...base, accepted: false, reason: "fetch is disabled" };
8541
8613
  }
8542
8614
  if (!(url.startsWith("https://") || url.startsWith("http://"))) {
8543
8615
  return { ...base, accepted: false, reason: "only http/https schemes are supported" };
8544
8616
  }
8545
- if (cfg.url_allowlist.length === 0) {
8617
+ if (cfg2.url_allowlist.length === 0) {
8546
8618
  return {
8547
8619
  ...base,
8548
8620
  accepted: false,
8549
8621
  reason: "fetch.url_allowlist is empty; no URLs may be fetched"
8550
8622
  };
8551
8623
  }
8552
- if (!isUrlAllowed(url, cfg.url_allowlist)) {
8624
+ if (!isUrlAllowed(url, cfg2.url_allowlist)) {
8553
8625
  return {
8554
8626
  ...base,
8555
8627
  accepted: false,
8556
8628
  reason: "url is not covered by fetch.url_allowlist",
8557
- allowlist: cfg.url_allowlist
8629
+ allowlist: cfg2.url_allowlist
8558
8630
  };
8559
8631
  }
8560
- const maxBytesSession = Math.max(0, cfg.max_bytes_per_session);
8561
- const maxUniqueHosts = Math.max(0, cfg.max_unique_hosts_per_session);
8632
+ const maxBytesSession = Math.max(0, cfg2.max_bytes_per_session);
8633
+ const maxUniqueHosts = Math.max(0, cfg2.max_unique_hosts_per_session);
8562
8634
  const host = extractHost(url);
8563
8635
  if (sessionId && opts.storage && (maxBytesSession > 0 || maxUniqueHosts > 0)) {
8564
8636
  const { total_bytes, unique_hosts } = await opts.storage.getFetchEgressTotals(sessionId);
@@ -8598,7 +8670,7 @@ async function runFetch(args, opts) {
8598
8670
  }
8599
8671
  }
8600
8672
  }
8601
- const evDir = resolveEvidenceDir(cfg.evidence_dir, opts.repoRoot);
8673
+ const evDir = resolveEvidenceDir(cfg2.evidence_dir, opts.repoRoot);
8602
8674
  const urlHash16 = sha256Hex2(url).slice(0, 16);
8603
8675
  const metaPath = import_node_path9.default.join(evDir, `by-url-${urlHash16}.json`);
8604
8676
  if ((0, import_node_fs9.existsSync)(metaPath) && !force) {
@@ -8620,7 +8692,7 @@ async function runFetch(args, opts) {
8620
8692
  try {
8621
8693
  response = await withTimeout(
8622
8694
  fetchFn(url, { headers: { "User-Agent": "crosscheck-agent/0.1" } }),
8623
- cfg.timeout_s * 1e3
8695
+ cfg2.timeout_s * 1e3
8624
8696
  );
8625
8697
  } catch (e) {
8626
8698
  return {
@@ -8646,8 +8718,8 @@ async function runFetch(args, opts) {
8646
8718
  const reader = response.body?.getReader?.();
8647
8719
  if (!reader) {
8648
8720
  const buf = await response.arrayBuffer();
8649
- if (buf.byteLength > cfg.max_bytes) {
8650
- return { ...base, accepted: false, reason: `response exceeds max_bytes=${cfg.max_bytes}` };
8721
+ if (buf.byteLength > cfg2.max_bytes) {
8722
+ return { ...base, accepted: false, reason: `response exceeds max_bytes=${cfg2.max_bytes}` };
8651
8723
  }
8652
8724
  data = new Uint8Array(buf);
8653
8725
  } else {
@@ -8659,7 +8731,7 @@ async function runFetch(args, opts) {
8659
8731
  if (done) break;
8660
8732
  if (value) {
8661
8733
  total += value.byteLength;
8662
- if (total > cfg.max_bytes) {
8734
+ if (total > cfg2.max_bytes) {
8663
8735
  exceeded = true;
8664
8736
  await reader.cancel().catch(() => {
8665
8737
  });
@@ -8669,7 +8741,7 @@ async function runFetch(args, opts) {
8669
8741
  }
8670
8742
  }
8671
8743
  if (exceeded) {
8672
- return { ...base, accepted: false, reason: `response exceeds max_bytes=${cfg.max_bytes}` };
8744
+ return { ...base, accepted: false, reason: `response exceeds max_bytes=${cfg2.max_bytes}` };
8673
8745
  }
8674
8746
  data = new Uint8Array(total);
8675
8747
  let offset = 0;
@@ -12523,15 +12595,15 @@ var CHECK_INTERVAL_SECONDS = 3 * 24 * 60 * 60;
12523
12595
  var DEFAULT_PACKAGE = "crosscheck-cli";
12524
12596
  var FETCH_TIMEOUT_MS = 3e3;
12525
12597
  function engineVersion() {
12526
- return true ? "0.2.14" : "0.0.0-dev";
12598
+ return true ? "0.2.16" : "0.0.0-dev";
12527
12599
  }
12528
12600
  function defaultUpdateCachePath() {
12529
12601
  const base = process.env["CROSSCHECK_DATA_DIR"] || import_node_path13.default.join(import_node_os2.default.homedir() || import_node_os2.default.tmpdir(), ".crosscheck");
12530
12602
  return import_node_path13.default.join(base, "update_check.json");
12531
12603
  }
12532
- function isNewer(latest, current) {
12604
+ function isNewer(latest, current2) {
12533
12605
  const a = parseTriple(latest);
12534
- const b = parseTriple(current);
12606
+ const b = parseTriple(current2);
12535
12607
  for (let i = 0; i < 3; i += 1) {
12536
12608
  if (a[i] !== b[i]) return a[i] > b[i];
12537
12609
  }
@@ -12541,13 +12613,13 @@ function parseTriple(v) {
12541
12613
  const parts = (v.split("-")[0] ?? "").split(".").map((n) => Number.parseInt(n, 10) || 0);
12542
12614
  return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];
12543
12615
  }
12544
- function buildNotice(pkg, current, latest) {
12616
+ function buildNotice(pkg, current2, latest) {
12545
12617
  return {
12546
12618
  update_available: true,
12547
12619
  package: pkg,
12548
- current_version: current,
12620
+ current_version: current2,
12549
12621
  latest_version: latest,
12550
- message: `crosscheck ${latest} is available \u2014 you're on ${current}. To upgrade, run:
12622
+ message: `crosscheck ${latest} is available \u2014 you're on ${current2}. To upgrade, run:
12551
12623
 
12552
12624
  npm install -g crosscheck-cli@latest
12553
12625
 
@@ -14234,13 +14306,214 @@ function attachUpdateNotice(result, toolName, opts) {
14234
14306
 
14235
14307
  // src/core/telemetry.ts
14236
14308
  init_cjs_shims();
14309
+ var import_node_crypto10 = __toESM(require("crypto"), 1);
14310
+
14311
+ // src/core/segments.ts
14312
+ init_cjs_shims();
14313
+ var import_node_crypto9 = __toESM(require("crypto"), 1);
14314
+ var import_node_fs17 = require("fs");
14315
+ var import_node_path16 = __toESM(require("path"), 1);
14316
+
14317
+ // src/core/git-context.ts
14318
+ init_cjs_shims();
14319
+ var import_node_child_process3 = require("child_process");
14237
14320
  var import_node_crypto8 = __toESM(require("crypto"), 1);
14321
+ var TRUNK_BRANCHES = /* @__PURE__ */ new Set([
14322
+ "main",
14323
+ "master",
14324
+ "dev",
14325
+ "develop",
14326
+ "trunk"
14327
+ ]);
14328
+ function git(args, cwd) {
14329
+ try {
14330
+ return (0, import_node_child_process3.execFileSync)("git", args, {
14331
+ cwd,
14332
+ encoding: "utf8",
14333
+ stdio: ["ignore", "pipe", "ignore"],
14334
+ timeout: 2e3
14335
+ }).trim();
14336
+ } catch {
14337
+ return null;
14338
+ }
14339
+ }
14340
+ function repoIdentity(cwd) {
14341
+ const root = git(["rev-list", "--max-parents=0", "HEAD"], cwd);
14342
+ if (root) return `root:${root.split("\n").pop().trim()}`;
14343
+ const remote = git(["config", "--get", "remote.origin.url"], cwd);
14344
+ if (remote) return `remote:${remote}`;
14345
+ const top = git(["rev-parse", "--show-toplevel"], cwd);
14346
+ return top ? `path:${top}` : null;
14347
+ }
14348
+ function fingerprintOf(seatKey, parts) {
14349
+ return import_node_crypto8.default.createHmac("sha256", seatKey).update(parts.join("\0")).digest("hex");
14350
+ }
14351
+ function readGitContext(seatKey, cwd) {
14352
+ try {
14353
+ const inside = git(["rev-parse", "--is-inside-work-tree"], cwd);
14354
+ if (inside !== "true") return { signal: "no_repo" };
14355
+ const repo = repoIdentity(cwd);
14356
+ if (!repo) return { signal: "no_repo" };
14357
+ const branch = git(["rev-parse", "--abbrev-ref", "HEAD"], cwd);
14358
+ if (!branch || branch === "HEAD") {
14359
+ const wt = git(["rev-parse", "--show-toplevel"], cwd) ?? cwd;
14360
+ return {
14361
+ fingerprint: fingerprintOf(seatKey, [repo, "worktree", wt]),
14362
+ signal: "worktree",
14363
+ localLabel: `worktree:${wt}`
14364
+ };
14365
+ }
14366
+ if (TRUNK_BRANCHES.has(branch)) {
14367
+ return {
14368
+ fingerprint: fingerprintOf(seatKey, [repo, "trunk"]),
14369
+ signal: "trunk",
14370
+ localLabel: `${branch} (maintenance)`
14371
+ };
14372
+ }
14373
+ return {
14374
+ fingerprint: fingerprintOf(seatKey, [repo, "branch", branch]),
14375
+ signal: "branch",
14376
+ localLabel: branch
14377
+ };
14378
+ } catch {
14379
+ return { signal: "no_repo" };
14380
+ }
14381
+ }
14382
+ function sameContext(a, b) {
14383
+ return a.fingerprint === b.fingerprint && a.signal === b.signal;
14384
+ }
14385
+
14386
+ // src/core/segments.ts
14387
+ var POST_TIMEOUT_MS = 5e3;
14388
+ function canonicalSegmentBody(s) {
14389
+ return [
14390
+ s.segmentId,
14391
+ s.seatId,
14392
+ s.orgId,
14393
+ s.sessionId,
14394
+ String(s.seq),
14395
+ s.fingerprint ?? "",
14396
+ s.signal,
14397
+ s.startedAt,
14398
+ s.endedAt ?? ""
14399
+ ].join("|");
14400
+ }
14401
+ function signSegment(seatKey, s) {
14402
+ return import_node_crypto9.default.createHmac("sha256", seatKey).update(canonicalSegmentBody(s)).digest("hex");
14403
+ }
14404
+ var cfgResolved = false;
14405
+ var cfg = null;
14238
14406
  var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
14407
+ function getConfig() {
14408
+ if (cfgResolved) return cfg;
14409
+ cfgResolved = true;
14410
+ if (process.env["CROSSCHECK_TELEMETRY"] === "off") return cfg = null;
14411
+ const base = process.env["CROSSCHECK_TELEMETRY_URL"];
14412
+ const seatId = process.env["CROSSCHECK_SEAT_ID"];
14413
+ const orgId = process.env["CROSSCHECK_ORG_ID"];
14414
+ const seatKey = process.env["CROSSCHECK_SEAT_HMAC_KEY"];
14415
+ if (!base || !seatId || !orgId || !seatKey) return cfg = null;
14416
+ if (!UUID_RE.test(seatId) || !UUID_RE.test(orgId)) return cfg = null;
14417
+ const sessionId = process.env["CLAUDE_SESSION_ID"] || process.env["CLAUDE_CODE_SESSION_ID"] || `engine-${import_node_crypto9.default.randomUUID()}`;
14418
+ cfg = {
14419
+ url: base.replace(/\/telemetry\/?$/, "/segments"),
14420
+ seatId,
14421
+ orgId,
14422
+ seatKey,
14423
+ sessionId
14424
+ };
14425
+ return cfg;
14426
+ }
14427
+ var current = null;
14428
+ var nextSeq = 0;
14429
+ function currentFingerprint() {
14430
+ return current?.context.fingerprint;
14431
+ }
14432
+ function rememberLabel(context) {
14433
+ if (!context.fingerprint || !context.localLabel) return;
14434
+ try {
14435
+ const dir = process.env["CROSSCHECK_DATA_DIR"] || import_node_path16.default.join(process.env["HOME"] ?? process.env["USERPROFILE"] ?? ".", ".crosscheck");
14436
+ const file = import_node_path16.default.join(dir, "features.json");
14437
+ let map = {};
14438
+ if ((0, import_node_fs17.existsSync)(file)) {
14439
+ try {
14440
+ const parsed = JSON.parse((0, import_node_fs17.readFileSync)(file, "utf8"));
14441
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) map = parsed;
14442
+ } catch {
14443
+ }
14444
+ }
14445
+ map[context.fingerprint] = {
14446
+ label: context.localLabel,
14447
+ lastSeen: (/* @__PURE__ */ new Date()).toISOString()
14448
+ };
14449
+ (0, import_node_fs17.mkdirSync)(dir, { recursive: true });
14450
+ (0, import_node_fs17.writeFileSync)(file, `${JSON.stringify(map, null, 2)}
14451
+ `, "utf8");
14452
+ } catch {
14453
+ }
14454
+ }
14455
+ async function post(url, segments) {
14456
+ try {
14457
+ await fetch(url, {
14458
+ method: "POST",
14459
+ headers: { "content-type": "application/json" },
14460
+ body: JSON.stringify({ segments }),
14461
+ signal: AbortSignal.timeout(POST_TIMEOUT_MS)
14462
+ });
14463
+ } catch {
14464
+ }
14465
+ }
14466
+ function emit(c, seg, endedAt) {
14467
+ const payload = {
14468
+ segmentId: seg.segmentId,
14469
+ seatId: c.seatId,
14470
+ orgId: c.orgId,
14471
+ sessionId: c.sessionId,
14472
+ seq: seg.seq,
14473
+ ...seg.context.fingerprint ? { fingerprint: seg.context.fingerprint } : {},
14474
+ signal: seg.context.signal,
14475
+ startedAt: seg.startedAt,
14476
+ ...endedAt ? { endedAt } : {}
14477
+ };
14478
+ const signatureHmac = signSegment(c.seatKey, payload);
14479
+ void post(c.url, [{ ...payload, signatureHmac }]);
14480
+ }
14481
+ function syncSegment(cwd) {
14482
+ try {
14483
+ const c = getConfig();
14484
+ if (!c) return;
14485
+ const context = readGitContext(c.seatKey, cwd ?? process.cwd());
14486
+ if (current && sameContext(current.context, context)) return;
14487
+ const now = (/* @__PURE__ */ new Date()).toISOString();
14488
+ if (current) emit(c, current, now);
14489
+ current = {
14490
+ segmentId: import_node_crypto9.default.randomUUID(),
14491
+ seq: nextSeq++,
14492
+ context,
14493
+ startedAt: now
14494
+ };
14495
+ rememberLabel(context);
14496
+ emit(c, current);
14497
+ } catch {
14498
+ }
14499
+ }
14500
+ function closeSegment() {
14501
+ try {
14502
+ const c = getConfig();
14503
+ if (!c || !current) return;
14504
+ emit(c, current, (/* @__PURE__ */ new Date()).toISOString());
14505
+ current = null;
14506
+ } catch {
14507
+ }
14508
+ }
14509
+
14510
+ // src/core/telemetry.ts
14511
+ var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
14239
14512
  var BATCH_MAX = 100;
14240
14513
  var FLUSH_BATCH_AT = 25;
14241
14514
  var FLUSH_INTERVAL_MS = 15e3;
14242
14515
  var MAX_QUEUE = 500;
14243
- var POST_TIMEOUT_MS = 5e3;
14516
+ var POST_TIMEOUT_MS2 = 5e3;
14244
14517
  var PROVIDER_TO_SCHEMA = {
14245
14518
  anthropic: "anthropic",
14246
14519
  openai: "openai",
@@ -14270,7 +14543,7 @@ function toolToPattern(toolName) {
14270
14543
  return TOOL_TO_PATTERN[String(toolName).toLowerCase()] ?? "other";
14271
14544
  }
14272
14545
  function canonicalEventBody(e) {
14273
- return [
14546
+ const base = [
14274
14547
  e.eventId,
14275
14548
  e.seatId,
14276
14549
  e.orgId,
@@ -14285,13 +14558,22 @@ function canonicalEventBody(e) {
14285
14558
  e.status,
14286
14559
  e.errorClass ?? ""
14287
14560
  ].join("|");
14561
+ if (e.runId === void 0 && e.purpose === void 0 && e.fingerprint === void 0) {
14562
+ return base;
14563
+ }
14564
+ return [base, e.runId ?? "", e.purpose ?? "", e.fingerprint ?? ""].join("|");
14565
+ }
14566
+ function normalizePurpose(v) {
14567
+ if (typeof v !== "string") return void 0;
14568
+ const slug = v.trim().toLowerCase().replace(/[^a-z0-9_-]/g, "-").slice(0, 32);
14569
+ return /^[a-z0-9][a-z0-9_-]*$/.test(slug) ? slug : void 0;
14288
14570
  }
14289
14571
  function signEvent(seatSigningKey, e) {
14290
- return import_node_crypto8.default.createHmac("sha256", seatSigningKey).update(canonicalEventBody(e)).digest("hex");
14572
+ return import_node_crypto10.default.createHmac("sha256", seatSigningKey).update(canonicalEventBody(e)).digest("hex");
14291
14573
  }
14292
14574
  var configResolved = false;
14293
14575
  var config = null;
14294
- function getConfig() {
14576
+ function getConfig2() {
14295
14577
  if (configResolved) return config;
14296
14578
  configResolved = true;
14297
14579
  if (process.env["CROSSCHECK_TELEMETRY"] === "off") return config = null;
@@ -14300,7 +14582,7 @@ function getConfig() {
14300
14582
  const orgId = process.env["CROSSCHECK_ORG_ID"];
14301
14583
  const seatKey = process.env["CROSSCHECK_SEAT_HMAC_KEY"];
14302
14584
  if (!url || !seatId || !orgId || !seatKey) return config = null;
14303
- if (!UUID_RE.test(seatId) || !UUID_RE.test(orgId)) return config = null;
14585
+ if (!UUID_RE2.test(seatId) || !UUID_RE2.test(orgId)) return config = null;
14304
14586
  config = { url, seatId, orgId, seatKey };
14305
14587
  return config;
14306
14588
  }
@@ -14335,7 +14617,10 @@ function usageEventsFromEnvelope(out, toolName) {
14335
14617
  completionTokens: intNonNeg(u.completion_tokens),
14336
14618
  costUsdEstimate: cost,
14337
14619
  latencyMs,
14338
- status: "ok"
14620
+ status: "ok",
14621
+ // Envelopes have carried `purpose` per call all along (debate /
14622
+ // debate-coreason / synth / worker); it just was never forwarded.
14623
+ purpose: normalizePurpose(u.purpose)
14339
14624
  });
14340
14625
  }
14341
14626
  return events;
@@ -14350,11 +14635,11 @@ function scheduleFlush() {
14350
14635
  }, FLUSH_INTERVAL_MS);
14351
14636
  if (typeof flushTimer.unref === "function") flushTimer.unref();
14352
14637
  }
14353
- function enqueue(cfg, input) {
14638
+ function enqueue(cfg2, input, runId, fingerprint) {
14354
14639
  const base = {
14355
- eventId: import_node_crypto8.default.randomUUID(),
14356
- seatId: cfg.seatId,
14357
- orgId: cfg.orgId,
14640
+ eventId: import_node_crypto10.default.randomUUID(),
14641
+ seatId: cfg2.seatId,
14642
+ orgId: cfg2.orgId,
14358
14643
  ts: (/* @__PURE__ */ new Date()).toISOString(),
14359
14644
  provider: input.provider,
14360
14645
  model: input.model,
@@ -14363,9 +14648,17 @@ function enqueue(cfg, input) {
14363
14648
  completionTokens: input.completionTokens,
14364
14649
  costUsdEstimate: input.costUsdEstimate,
14365
14650
  latencyMs: input.latencyMs,
14366
- status: input.status
14367
- };
14368
- const signatureHmac = signEvent(cfg.seatKey, base);
14651
+ status: input.status,
14652
+ runId,
14653
+ // Omit entirely (not "") when unknown, so the canonical body stays
14654
+ // consistent with what the server reconstructs from the JSON.
14655
+ ...input.purpose === void 0 ? {} : { purpose: input.purpose },
14656
+ // Panel attribution is EXACT: we know the active git segment at emit
14657
+ // time, so there is no time-join and no ambiguity window — unlike host
14658
+ // tokens, which can only be matched by session and timestamp.
14659
+ ...fingerprint === void 0 ? {} : { fingerprint }
14660
+ };
14661
+ const signatureHmac = signEvent(cfg2.seatKey, base);
14369
14662
  queue.push({ ...base, signatureHmac });
14370
14663
  if (queue.length > MAX_QUEUE) queue.splice(0, queue.length - MAX_QUEUE);
14371
14664
  if (queue.length >= FLUSH_BATCH_AT) void flush();
@@ -14373,10 +14666,14 @@ function enqueue(cfg, input) {
14373
14666
  }
14374
14667
  function recordUsage(out, toolName) {
14375
14668
  try {
14376
- const cfg = getConfig();
14377
- if (!cfg) return;
14669
+ const cfg2 = getConfig2();
14670
+ if (!cfg2) return;
14378
14671
  if (toolName === "update_crosscheck") return;
14379
- for (const ev of usageEventsFromEnvelope(out, toolName)) enqueue(cfg, ev);
14672
+ const runId = import_node_crypto10.default.randomUUID();
14673
+ const fingerprint = currentFingerprint();
14674
+ for (const ev of usageEventsFromEnvelope(out, toolName)) {
14675
+ enqueue(cfg2, ev, runId, fingerprint);
14676
+ }
14380
14677
  } catch {
14381
14678
  }
14382
14679
  }
@@ -14387,7 +14684,7 @@ async function postBatch(url, batch) {
14387
14684
  method: "POST",
14388
14685
  headers: { "content-type": "application/json" },
14389
14686
  body: JSON.stringify({ events: batch }),
14390
- signal: AbortSignal.timeout(POST_TIMEOUT_MS)
14687
+ signal: AbortSignal.timeout(POST_TIMEOUT_MS2)
14391
14688
  });
14392
14689
  } catch {
14393
14690
  queue.unshift(...batch);
@@ -14409,15 +14706,15 @@ async function postBatch(url, batch) {
14409
14706
  if (queue.length > MAX_QUEUE) queue.splice(0, queue.length - MAX_QUEUE);
14410
14707
  }
14411
14708
  async function flush() {
14412
- const cfg = getConfig();
14413
- if (!cfg) return;
14709
+ const cfg2 = getConfig2();
14710
+ if (!cfg2) return;
14414
14711
  if (flushTimer) {
14415
14712
  clearTimeout(flushTimer);
14416
14713
  flushTimer = null;
14417
14714
  }
14418
14715
  while (queue.length > 0) {
14419
14716
  const batch = queue.splice(0, BATCH_MAX);
14420
- await postBatch(cfg.url, batch);
14717
+ await postBatch(cfg2.url, batch);
14421
14718
  if (queue.length > 0 && queue[0] === batch[0]) break;
14422
14719
  }
14423
14720
  }
@@ -14457,14 +14754,14 @@ function createServer(opts = {}) {
14457
14754
  });
14458
14755
  };
14459
14756
  const tools = buildToolRegistry(opts);
14460
- server.setRequestHandler(import_types8.ListToolsRequestSchema, async () => ({
14757
+ server.setRequestHandler(import_types9.ListToolsRequestSchema, async () => ({
14461
14758
  tools: Array.from(tools.values()).map((t) => ({
14462
14759
  name: t.name,
14463
14760
  description: t.description,
14464
14761
  inputSchema: t.inputSchema
14465
14762
  }))
14466
14763
  }));
14467
- server.setRequestHandler(import_types8.CallToolRequestSchema, async (req) => {
14764
+ server.setRequestHandler(import_types9.CallToolRequestSchema, async (req) => {
14468
14765
  const name = req.params.name;
14469
14766
  const tool = tools.get(name);
14470
14767
  if (!tool) {
@@ -14485,6 +14782,7 @@ function createServer(opts = {}) {
14485
14782
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
14486
14783
  const startedHr = process.hrtime.bigint();
14487
14784
  const id = nextEventId();
14785
+ syncSegment(opts.repoRoot);
14488
14786
  const gateBlock = configPinGate(name, {
14489
14787
  ...opts.repoRoot ? { repoRoot: opts.repoRoot } : {},
14490
14788
  ...opts.configPinning ? { config: opts.configPinning } : {},
@@ -14566,6 +14864,13 @@ function createServer(opts = {}) {
14566
14864
  async function connectAndServe(transport, opts = {}) {
14567
14865
  const server = createServer(opts);
14568
14866
  await server.connect(transport);
14867
+ for (const sig of ["SIGINT", "SIGTERM"]) {
14868
+ process.once(sig, () => {
14869
+ closeSegment();
14870
+ process.exit(0);
14871
+ });
14872
+ }
14873
+ process.once("beforeExit", () => closeSegment());
14569
14874
  const cliVersion = process.env["CROSSCHECK_CLI_VERSION"];
14570
14875
  void maybeCheckForUpdate({
14571
14876
  currentVersion: cliVersion ?? SERVER_VERSION,
@@ -14603,9 +14908,9 @@ function buildToolRegistry(opts) {
14603
14908
 
14604
14909
  // src/core/config-file.ts
14605
14910
  init_cjs_shims();
14606
- var import_node_fs17 = require("fs");
14607
- var import_node_path16 = __toESM(require("path"), 1);
14608
- var import_node_crypto9 = require("crypto");
14911
+ var import_node_fs18 = require("fs");
14912
+ var import_node_path17 = __toESM(require("path"), 1);
14913
+ var import_node_crypto11 = require("crypto");
14609
14914
  function asObj(v) {
14610
14915
  return v && typeof v === "object" && !Array.isArray(v) ? v : void 0;
14611
14916
  }
@@ -14632,18 +14937,18 @@ function pickNums(src, keys) {
14632
14937
  }
14633
14938
  function findConfigFile(opts) {
14634
14939
  const direct = opts.explicitPath ?? process.env["CROSSCHECK_CONFIG_PATH"];
14635
- if (direct) return (0, import_node_fs17.existsSync)(direct) ? direct : void 0;
14940
+ if (direct) return (0, import_node_fs18.existsSync)(direct) ? direct : void 0;
14636
14941
  const candidates = [];
14637
14942
  let dir = opts.startDir;
14638
14943
  for (let i = 0; dir && i < 8; i += 1) {
14639
- candidates.push(import_node_path16.default.join(dir, "crosscheck.config.json"));
14640
- const parent = import_node_path16.default.dirname(dir);
14944
+ candidates.push(import_node_path17.default.join(dir, "crosscheck.config.json"));
14945
+ const parent = import_node_path17.default.dirname(dir);
14641
14946
  if (parent === dir) break;
14642
14947
  dir = parent;
14643
14948
  }
14644
- if (opts.repoRoot) candidates.push(import_node_path16.default.join(opts.repoRoot, "crosscheck.config.json"));
14645
- if (opts.dataDir) candidates.push(import_node_path16.default.join(opts.dataDir, "crosscheck.config.json"));
14646
- return candidates.find((p) => (0, import_node_fs17.existsSync)(p));
14949
+ if (opts.repoRoot) candidates.push(import_node_path17.default.join(opts.repoRoot, "crosscheck.config.json"));
14950
+ if (opts.dataDir) candidates.push(import_node_path17.default.join(opts.dataDir, "crosscheck.config.json"));
14951
+ return candidates.find((p) => (0, import_node_fs18.existsSync)(p));
14647
14952
  }
14648
14953
  function mapConfig(raw, sourcePath) {
14649
14954
  const result = { sourcePath };
@@ -14660,16 +14965,16 @@ function mapConfig(raw, sourcePath) {
14660
14965
  }
14661
14966
  const nc = asObj(raw["node_cache"]);
14662
14967
  if (nc) {
14663
- const cfg = {
14968
+ const cfg2 = {
14664
14969
  ...pickNums(nc, ["ttl_seconds", "max_entries", "max_cache_bytes"])
14665
14970
  };
14666
- if (bool(nc["enabled"]) !== void 0) cfg.enabled = bool(nc["enabled"]);
14667
- if (str(nc["dir"]) !== void 0) cfg.dir = str(nc["dir"]);
14668
- result.nodeCache = cfg;
14971
+ if (bool(nc["enabled"]) !== void 0) cfg2.enabled = bool(nc["enabled"]);
14972
+ if (str(nc["dir"]) !== void 0) cfg2.dir = str(nc["dir"]);
14973
+ result.nodeCache = cfg2;
14669
14974
  }
14670
14975
  const f = asObj(raw["fetch"]);
14671
14976
  if (f) {
14672
- const cfg = {
14977
+ const cfg2 = {
14673
14978
  ...pickNums(f, [
14674
14979
  "max_bytes",
14675
14980
  "max_bytes_per_session",
@@ -14677,10 +14982,10 @@ function mapConfig(raw, sourcePath) {
14677
14982
  "timeout_s"
14678
14983
  ])
14679
14984
  };
14680
- if (bool(f["enabled"]) !== void 0) cfg.enabled = bool(f["enabled"]);
14681
- if (strArr(f["url_allowlist"]) !== void 0) cfg.url_allowlist = strArr(f["url_allowlist"]);
14682
- if (str(f["evidence_dir"]) !== void 0) cfg.evidence_dir = str(f["evidence_dir"]);
14683
- result.fetchConfig = cfg;
14985
+ if (bool(f["enabled"]) !== void 0) cfg2.enabled = bool(f["enabled"]);
14986
+ if (strArr(f["url_allowlist"]) !== void 0) cfg2.url_allowlist = strArr(f["url_allowlist"]);
14987
+ if (str(f["evidence_dir"]) !== void 0) cfg2.evidence_dir = str(f["evidence_dir"]);
14988
+ result.fetchConfig = cfg2;
14684
14989
  }
14685
14990
  if ("provider_allowlist" in raw) {
14686
14991
  const pa = raw["provider_allowlist"];
@@ -14704,10 +15009,10 @@ function mapConfig(raw, sourcePath) {
14704
15009
  if (retries) {
14705
15010
  const maxAttempts = num(retries["max_attempts"]);
14706
15011
  const baseS = num(retries["backoff_base_s"]);
14707
- const cfg = {};
14708
- if (maxAttempts !== void 0) cfg.maxAttempts = Math.max(1, Math.trunc(maxAttempts));
14709
- if (baseS !== void 0) cfg.backoffBaseMs = Math.round(baseS * 1e3);
14710
- if (Object.keys(cfg).length > 0) setRetryConfig(cfg);
15012
+ const cfg2 = {};
15013
+ if (maxAttempts !== void 0) cfg2.maxAttempts = Math.max(1, Math.trunc(maxAttempts));
15014
+ if (baseS !== void 0) cfg2.backoffBaseMs = Math.round(baseS * 1e3);
15015
+ if (Object.keys(cfg2).length > 0) setRetryConfig(cfg2);
14711
15016
  }
14712
15017
  const rl = asObj(raw["rate_limits"]);
14713
15018
  if (rl) {
@@ -14733,22 +15038,22 @@ function mapConfig(raw, sourcePath) {
14733
15038
  }
14734
15039
  const red = asObj(raw["redaction"]);
14735
15040
  if (red) {
14736
- const cfg = {};
14737
- if (bool(red["enabled"]) !== void 0) cfg.enabled = bool(red["enabled"]);
15041
+ const cfg2 = {};
15042
+ if (bool(red["enabled"]) !== void 0) cfg2.enabled = bool(red["enabled"]);
14738
15043
  const extras = strArr(red["patterns_extra"]);
14739
- if (extras && extras.length > 0) cfg.patterns_extra = extras;
15044
+ if (extras && extras.length > 0) cfg2.patterns_extra = extras;
14740
15045
  if (bool(red["hmac_tokens"]) === true) {
14741
- cfg.hmac_tokens = true;
14742
- cfg.hmac_secret = (0, import_node_crypto9.randomBytes)(32);
15046
+ cfg2.hmac_tokens = true;
15047
+ cfg2.hmac_secret = (0, import_node_crypto11.randomBytes)(32);
14743
15048
  }
14744
- if (Object.keys(cfg).length > 0) setRedactionConfig(cfg);
15049
+ if (Object.keys(cfg2).length > 0) setRedactionConfig(cfg2);
14745
15050
  }
14746
15051
  const personas = asObj(raw["personas"]);
14747
15052
  if (personas) {
14748
- const cfg = {};
14749
- if (bool(personas["enabled"]) !== void 0) cfg.enabled = bool(personas["enabled"]);
14750
- if (str(personas["dir"]) !== void 0) cfg.dir = str(personas["dir"]);
14751
- if (Object.keys(cfg).length > 0) setPersonaConfig(cfg);
15053
+ const cfg2 = {};
15054
+ if (bool(personas["enabled"]) !== void 0) cfg2.enabled = bool(personas["enabled"]);
15055
+ if (str(personas["dir"]) !== void 0) cfg2.dir = str(personas["dir"]);
15056
+ if (Object.keys(cfg2).length > 0) setPersonaConfig(cfg2);
14752
15057
  }
14753
15058
  return result;
14754
15059
  }
@@ -14757,7 +15062,7 @@ function loadConfigFile(opts) {
14757
15062
  if (!file) return null;
14758
15063
  let raw;
14759
15064
  try {
14760
- raw = JSON.parse((0, import_node_fs17.readFileSync)(file, "utf8"));
15065
+ raw = JSON.parse((0, import_node_fs18.readFileSync)(file, "utf8"));
14761
15066
  } catch (e) {
14762
15067
  try {
14763
15068
  process.stderr.write(
@@ -14777,7 +15082,7 @@ function loadConfigFile(opts) {
14777
15082
  var SHUTDOWN_TIMEOUT_MS = 2e3;
14778
15083
  async function main() {
14779
15084
  enforceLicenseOrExit();
14780
- const cfgDataDir = process.env["CROSSCHECK_DATA_DIR"] || import_node_path17.default.join(process.env["HOME"] ?? process.env["USERPROFILE"] ?? "", ".crosscheck");
15085
+ const cfgDataDir = process.env["CROSSCHECK_DATA_DIR"] || import_node_path18.default.join(process.env["HOME"] ?? process.env["USERPROFILE"] ?? "", ".crosscheck");
14781
15086
  const configFile = loadConfigFile({
14782
15087
  startDir: process.cwd(),
14783
15088
  repoRoot: findGitRoot(__dirname),
@@ -14802,7 +15107,7 @@ async function main() {
14802
15107
  installShutdownHandlers();
14803
15108
  const bundledPricing = (() => {
14804
15109
  try {
14805
- return import_node_path17.default.join(import_node_path17.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl)), "pricing.json");
15110
+ return import_node_path18.default.join(import_node_path18.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl)), "pricing.json");
14806
15111
  } catch {
14807
15112
  return void 0;
14808
15113
  }
@@ -14811,7 +15116,7 @@ async function main() {
14811
15116
  process.env["CROSSCHECK_PRICING_PATH"],
14812
15117
  resolveRepoFile("config/pricing.json"),
14813
15118
  bundledPricing
14814
- ].find((p) => p && (0, import_node_fs18.existsSync)(p));
15119
+ ].find((p) => p && (0, import_node_fs19.existsSync)(p));
14815
15120
  const pricing = pricingPath ? loadPricing(pricingPath) : {};
14816
15121
  const providers = buildProviders({ env: process.env, pricing });
14817
15122
  if (Object.keys(providers).length > 0) {
@@ -14829,10 +15134,10 @@ async function main() {
14829
15134
  }
14830
15135
  let storage;
14831
15136
  if (process.env["CROSSCHECK_DB_DISABLED"] !== "1") {
14832
- const dbPath = process.env["CROSSCHECK_DB_PATH"] ?? resolveRepoFile(".crosscheck/db.sqlite") ?? import_node_path17.default.join(cfgDataDir, "db.sqlite");
15137
+ const dbPath = process.env["CROSSCHECK_DB_PATH"] ?? resolveRepoFile(".crosscheck/db.sqlite") ?? import_node_path18.default.join(cfgDataDir, "db.sqlite");
14833
15138
  if (dbPath) {
14834
15139
  try {
14835
- (0, import_node_fs18.mkdirSync)(import_node_path17.default.dirname(dbPath), { recursive: true });
15140
+ (0, import_node_fs19.mkdirSync)(import_node_path18.default.dirname(dbPath), { recursive: true });
14836
15141
  const { openBetterSqliteStorage: openBetterSqliteStorage2 } = await Promise.resolve().then(() => (init_better_sqlite3(), better_sqlite3_exports));
14837
15142
  storage = openBetterSqliteStorage2({ path: dbPath });
14838
15143
  await storage.migrate();
@@ -14900,9 +15205,9 @@ function installShutdownHandlers() {
14900
15205
  function resolveRepoFile(rel) {
14901
15206
  let dir = __dirname;
14902
15207
  for (let i = 0; i < 8; i++) {
14903
- const candidate = import_node_path17.default.join(dir, rel);
14904
- if ((0, import_node_fs18.existsSync)(candidate)) return candidate;
14905
- const parent = import_node_path17.default.dirname(dir);
15208
+ const candidate = import_node_path18.default.join(dir, rel);
15209
+ if ((0, import_node_fs19.existsSync)(candidate)) return candidate;
15210
+ const parent = import_node_path18.default.dirname(dir);
14906
15211
  if (parent === dir) break;
14907
15212
  dir = parent;
14908
15213
  }
@@ -14911,8 +15216,8 @@ function resolveRepoFile(rel) {
14911
15216
  function findGitRoot(startDir) {
14912
15217
  let dir = startDir;
14913
15218
  for (let i = 0; i < 12; i++) {
14914
- if ((0, import_node_fs18.existsSync)(import_node_path17.default.join(dir, ".git"))) return dir;
14915
- const parent = import_node_path17.default.dirname(dir);
15219
+ if ((0, import_node_fs19.existsSync)(import_node_path18.default.join(dir, ".git"))) return dir;
15220
+ const parent = import_node_path18.default.dirname(dir);
14916
15221
  if (parent === dir) break;
14917
15222
  dir = parent;
14918
15223
  }