crosscheck-mcp 0.1.11 → 0.1.12

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,159 +942,15 @@ __export(node_stdio_exports, {
942
942
  });
943
943
  module.exports = __toCommonJS(node_stdio_exports);
944
944
  init_cjs_shims();
945
- var import_node_fs15 = require("fs");
946
- var import_node_path15 = __toESM(require("path"), 1);
945
+ var import_node_fs18 = require("fs");
946
+ var import_node_path17 = __toESM(require("path"), 1);
947
947
  var import_node_url2 = require("url");
948
- var import_stdio2 = require("@modelcontextprotocol/sdk/server/stdio.js");
949
-
950
- // src/bridge/index.ts
951
- init_cjs_shims();
952
-
953
- // src/bridge/python-bridge.ts
954
- init_cjs_shims();
955
- var import_node_path = __toESM(require("path"), 1);
956
- var import_node_url = require("url");
957
- var import_client = require("@modelcontextprotocol/sdk/client/index.js");
958
- var import_stdio = require("@modelcontextprotocol/sdk/client/stdio.js");
959
- function defaultServerPath() {
960
- let here;
961
- try {
962
- here = import_node_path.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
963
- } catch {
964
- here = __dirname;
965
- }
966
- const upToPkg = here.includes(`${import_node_path.default.sep}src${import_node_path.default.sep}bridge`) ? "../.." : "..";
967
- return import_node_path.default.resolve(here, upToPkg, "..", "python", "crosscheck_server.py");
968
- }
969
- async function spawnPythonBridge(opts = {}) {
970
- const pythonPath = opts.pythonPath ?? "python3";
971
- const serverPath = opts.serverPath ?? defaultServerPath();
972
- const args = [...opts.extraArgs ?? [], serverPath];
973
- const transport = new import_stdio.StdioClientTransport({
974
- command: pythonPath,
975
- args,
976
- env: { ...process.env, ...opts.env ?? {} }
977
- });
978
- const client = new import_client.Client(
979
- { name: "crosscheck-agent-bridge", version: "0.1.0" },
980
- { capabilities: {} }
981
- );
982
- const initDeadline = opts.initTimeoutMs ?? 3e4;
983
- await Promise.race([
984
- client.connect(transport),
985
- new Promise(
986
- (_, rej) => setTimeout(
987
- () => rej(new Error(`bridge: handshake timeout after ${initDeadline}ms`)),
988
- initDeadline
989
- )
990
- )
991
- ]);
992
- let toolNames = await fetchToolNames(client, initDeadline);
993
- let closed = false;
994
- return {
995
- get toolNames() {
996
- return toolNames;
997
- },
998
- get pid() {
999
- const p = transport.pid;
1000
- return typeof p === "number" ? p : null;
1001
- },
1002
- async callTool(name, callArgs) {
1003
- const r = await client.callTool({ name, arguments: callArgs });
1004
- const content = r.content;
1005
- if (!Array.isArray(content)) {
1006
- throw new Error(
1007
- `bridge: tools/call(${name}) returned a malformed envelope: ${JSON.stringify(r).slice(0, 200)}`
1008
- );
1009
- }
1010
- const out = [];
1011
- for (const c of content) {
1012
- if (c && typeof c === "object") {
1013
- const co = c;
1014
- out.push({
1015
- type: String(co["type"] ?? "text"),
1016
- text: typeof co["text"] === "string" ? co["text"] : JSON.stringify(co["text"])
1017
- });
1018
- }
1019
- }
1020
- const isError = r.isError;
1021
- return isError !== void 0 ? { content: out, isError } : { content: out };
1022
- },
1023
- async refreshTools() {
1024
- toolNames = await fetchToolNames(client, initDeadline);
1025
- return toolNames;
1026
- },
1027
- async close() {
1028
- if (closed) return;
1029
- closed = true;
1030
- try {
1031
- await client.close();
1032
- } catch {
1033
- }
1034
- }
1035
- };
1036
- }
1037
- async function fetchToolNames(client, timeoutMs) {
1038
- const r = await Promise.race([
1039
- client.listTools(),
1040
- new Promise(
1041
- (_, rej) => setTimeout(
1042
- () => rej(new Error(`bridge: tools/list timeout after ${timeoutMs}ms`)),
1043
- timeoutMs
1044
- )
1045
- )
1046
- ]);
1047
- const tools = r.tools ?? [];
1048
- return new Set(
1049
- tools.map((t) => String(t?.name ?? "")).filter((n) => n.length > 0)
1050
- );
1051
- }
1052
-
1053
- // src/bridge/proxy-tools.ts
1054
- init_cjs_shims();
1055
- function buildPythonProxies(bridge) {
1056
- const proxies = /* @__PURE__ */ new Map();
1057
- for (const name of bridge.toolNames) {
1058
- proxies.set(name, makeProxy(bridge, name));
1059
- }
1060
- return proxies;
1061
- }
1062
- function makeProxy(bridge, name) {
1063
- return {
1064
- name,
1065
- description: `(forwarded to Python crosscheck-agent) ${name}`,
1066
- // We don't carry the per-tool input schema across the bridge in
1067
- // route-all mode — the TS server is a transparent forwarder, so
1068
- // arg validation happens on the Python side. inputSchema is left
1069
- // permissive; the Python validator throws structured errors that
1070
- // tunnel back through `callTool()`.
1071
- inputSchema: {
1072
- type: "object",
1073
- additionalProperties: true,
1074
- description: `Forwarded to Python \u2014 see Python tool '${name}' for full schema.`
1075
- },
1076
- handler: async (args) => {
1077
- const r = await bridge.callTool(name, args);
1078
- if (!r.content.length) {
1079
- return {};
1080
- }
1081
- const first = r.content[0];
1082
- if (first.type === "text" && first.text) {
1083
- try {
1084
- return JSON.parse(first.text);
1085
- } catch {
1086
- return { text: first.text };
1087
- }
1088
- }
1089
- return { content: r.content };
1090
- }
1091
- };
1092
- }
948
+ var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
1093
949
 
1094
950
  // src/core/events.ts
1095
951
  init_cjs_shims();
1096
952
  var import_node_fs = require("fs");
1097
- var import_node_path2 = require("path");
953
+ var import_node_path = require("path");
1098
954
  var StderrEmitter = class {
1099
955
  emit(event) {
1100
956
  try {
@@ -1104,10 +960,10 @@ var StderrEmitter = class {
1104
960
  }
1105
961
  };
1106
962
  var FileEmitter = class {
1107
- constructor(path12) {
1108
- this.path = path12;
963
+ constructor(path14) {
964
+ this.path = path14;
1109
965
  try {
1110
- (0, import_node_fs.mkdirSync)((0, import_node_path2.dirname)(path12), { recursive: true });
966
+ (0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(path14), { recursive: true });
1111
967
  } catch {
1112
968
  }
1113
969
  }
@@ -1140,13 +996,13 @@ var NullEmitter = class {
1140
996
  function buildDefaultEmitter(env) {
1141
997
  const mode = (env["CROSSCHECK_EVENTS"] ?? "stderr").toLowerCase();
1142
998
  if (mode === "off") return new NullEmitter();
1143
- const path12 = env["CROSSCHECK_EVENTS_PATH"];
999
+ const path14 = env["CROSSCHECK_EVENTS_PATH"];
1144
1000
  if (mode === "file") {
1145
- return path12 ? new FileEmitter(path12) : new NullEmitter();
1001
+ return path14 ? new FileEmitter(path14) : new NullEmitter();
1146
1002
  }
1147
1003
  if (mode === "both") {
1148
1004
  const ems = [new StderrEmitter()];
1149
- if (path12) ems.push(new FileEmitter(path12));
1005
+ if (path14) ems.push(new FileEmitter(path14));
1150
1006
  return new TeeEmitter(ems);
1151
1007
  }
1152
1008
  return new StderrEmitter();
@@ -1195,10 +1051,10 @@ function envelopeBytes(v) {
1195
1051
  // src/core/pricing.ts
1196
1052
  init_cjs_shims();
1197
1053
  var import_node_fs2 = require("fs");
1198
- function loadPricing(path12) {
1054
+ function loadPricing(path14) {
1199
1055
  let raw;
1200
1056
  try {
1201
- raw = (0, import_node_fs2.readFileSync)(path12, "utf8");
1057
+ raw = (0, import_node_fs2.readFileSync)(path14, "utf8");
1202
1058
  } catch {
1203
1059
  return {};
1204
1060
  }
@@ -1328,7 +1184,7 @@ function isCreditsFailure(status, bodyText) {
1328
1184
  }
1329
1185
  return false;
1330
1186
  }
1331
- function httpFailureToProviderError(provider, status, bodyText) {
1187
+ function httpFailureToProviderError(provider, status, bodyText, retryAfterS) {
1332
1188
  const detail = bodyText.slice(0, 512);
1333
1189
  if (isCreditsFailure(status, bodyText)) {
1334
1190
  return new ProviderError(
@@ -1348,14 +1204,14 @@ function httpFailureToProviderError(provider, status, bodyText) {
1348
1204
  return new ProviderError(
1349
1205
  "rate_limit",
1350
1206
  `${provider}: rate limited (HTTP 429). Detail: ${detail}`,
1351
- { status }
1207
+ { status, ...retryAfterS !== void 0 ? { retryAfterS } : {} }
1352
1208
  );
1353
1209
  }
1354
1210
  if (status >= 500 && status <= 599) {
1355
1211
  return new ProviderError(
1356
1212
  "server",
1357
1213
  `${provider}: upstream server error (HTTP ${status}). Detail: ${detail}`,
1358
- { status }
1214
+ { status, ...retryAfterS !== void 0 ? { retryAfterS } : {} }
1359
1215
  );
1360
1216
  }
1361
1217
  return new ProviderError(
@@ -1365,6 +1221,156 @@ function httpFailureToProviderError(provider, status, bodyText) {
1365
1221
  );
1366
1222
  }
1367
1223
 
1224
+ // src/providers/retry.ts
1225
+ init_cjs_shims();
1226
+ var override = null;
1227
+ function setRetryConfig(cfg) {
1228
+ override = cfg;
1229
+ }
1230
+ function resolveRetryConfig() {
1231
+ const envInt = (k, d) => {
1232
+ const n = Number(process.env[k]);
1233
+ return Number.isFinite(n) && n >= 0 ? Math.trunc(n) : d;
1234
+ };
1235
+ const envSecMs = (k, dMs) => {
1236
+ const n = Number(process.env[k]);
1237
+ return Number.isFinite(n) && n >= 0 ? Math.round(n * 1e3) : dMs;
1238
+ };
1239
+ return {
1240
+ maxAttempts: Math.max(1, override?.maxAttempts ?? envInt("CROSSCHECK_RETRY_MAX_ATTEMPTS", 3)),
1241
+ backoffBaseMs: override?.backoffBaseMs ?? envSecMs("CROSSCHECK_RETRY_BACKOFF_BASE_S", 750),
1242
+ maxBackoffMs: override?.maxBackoffMs ?? 2e4
1243
+ };
1244
+ }
1245
+ function parseRetryAfter(respLike) {
1246
+ try {
1247
+ const h = respLike?.headers;
1248
+ if (!h) return void 0;
1249
+ let raw = null;
1250
+ const getter = h.get;
1251
+ if (typeof getter === "function") {
1252
+ raw = getter.call(h, "retry-after");
1253
+ } else if (typeof h === "object") {
1254
+ const rec = h;
1255
+ raw = rec["retry-after"] ?? rec["Retry-After"] ?? null;
1256
+ }
1257
+ if (!raw) return void 0;
1258
+ const secs = Number(raw);
1259
+ if (Number.isFinite(secs) && secs >= 0) return secs;
1260
+ const dateMs = Date.parse(raw);
1261
+ if (Number.isFinite(dateMs)) {
1262
+ const delta = (dateMs - Date.now()) / 1e3;
1263
+ return delta > 0 ? delta : 0;
1264
+ }
1265
+ } catch {
1266
+ }
1267
+ return void 0;
1268
+ }
1269
+ function defaultSleep(ms, signal) {
1270
+ return new Promise((resolve2, reject) => {
1271
+ if (signal?.aborted) {
1272
+ reject(new ProviderError("network", "request aborted", { transient: false }));
1273
+ return;
1274
+ }
1275
+ const cleanup = () => {
1276
+ clearTimeout(t);
1277
+ signal?.removeEventListener?.("abort", onAbort);
1278
+ };
1279
+ const onAbort = () => {
1280
+ cleanup();
1281
+ reject(new ProviderError("network", "request aborted", { transient: false }));
1282
+ };
1283
+ const t = setTimeout(() => {
1284
+ cleanup();
1285
+ resolve2();
1286
+ }, ms);
1287
+ signal?.addEventListener?.("abort", onAbort);
1288
+ });
1289
+ }
1290
+ async function sendWithRetry(attemptOnce, cfg, signal, deps) {
1291
+ const sleep = deps?.sleepImpl ?? ((ms) => defaultSleep(ms, signal));
1292
+ const rand = deps?.random ?? Math.random;
1293
+ let lastErr;
1294
+ for (let attempt = 1; attempt <= cfg.maxAttempts; attempt += 1) {
1295
+ try {
1296
+ const result = await attemptOnce();
1297
+ return { ...result, attempts: attempt };
1298
+ } catch (e) {
1299
+ lastErr = e;
1300
+ const pe = e instanceof ProviderError ? e : null;
1301
+ if (pe?.transient !== true || attempt >= cfg.maxAttempts) throw e;
1302
+ let backoffMs = pe.retryAfterS !== void 0 ? pe.retryAfterS * 1e3 : cfg.backoffBaseMs * 2 ** (attempt - 1);
1303
+ backoffMs += rand() * cfg.backoffBaseMs;
1304
+ backoffMs = Math.min(backoffMs, cfg.maxBackoffMs);
1305
+ await sleep(backoffMs);
1306
+ }
1307
+ }
1308
+ throw lastErr;
1309
+ }
1310
+
1311
+ // src/providers/rate-limit.ts
1312
+ init_cjs_shims();
1313
+ var DEFAULT_SPEC = { capacity: 5, refillPerSec: 5 };
1314
+ var MAX_WAIT_SLICE_MS = 250;
1315
+ var override2 = null;
1316
+ function setRateLimitConfig(cfg) {
1317
+ override2 = cfg;
1318
+ buckets.clear();
1319
+ }
1320
+ function disabled() {
1321
+ return process.env["CROSSCHECK_RATE_LIMIT"] === "off";
1322
+ }
1323
+ function specFor(provider) {
1324
+ const fromCfg = override2?.[provider] ?? override2?.["default"];
1325
+ const spec = fromCfg ?? DEFAULT_SPEC;
1326
+ const capacity = Number.isFinite(spec.capacity) && spec.capacity > 0 ? spec.capacity : DEFAULT_SPEC.capacity;
1327
+ const refillPerSec = Number.isFinite(spec.refillPerSec) && spec.refillPerSec > 0 ? spec.refillPerSec : capacity;
1328
+ return { capacity, refillPerSec };
1329
+ }
1330
+ var Bucket = class {
1331
+ constructor(spec, now) {
1332
+ this.spec = spec;
1333
+ this.tokens = spec.capacity;
1334
+ this.last = now();
1335
+ }
1336
+ spec;
1337
+ tokens;
1338
+ last;
1339
+ refill(now) {
1340
+ const elapsed = (now - this.last) / 1e3;
1341
+ this.tokens = Math.min(this.spec.capacity, this.tokens + elapsed * this.spec.refillPerSec);
1342
+ this.last = now;
1343
+ }
1344
+ async acquire(deps) {
1345
+ for (; ; ) {
1346
+ this.refill(deps.now());
1347
+ if (this.tokens >= 1) {
1348
+ this.tokens -= 1;
1349
+ return;
1350
+ }
1351
+ if (deps.signal?.aborted) {
1352
+ throw new ProviderError("network", "request aborted while rate-limited", { transient: false });
1353
+ }
1354
+ const needed = 1 - this.tokens;
1355
+ const waitMs = needed / Math.max(this.spec.refillPerSec, 1e-6) * 1e3;
1356
+ await deps.sleep(Math.min(waitMs, MAX_WAIT_SLICE_MS));
1357
+ }
1358
+ }
1359
+ };
1360
+ var buckets = /* @__PURE__ */ new Map();
1361
+ var realSleep = (ms) => new Promise((r) => setTimeout(r, ms));
1362
+ async function acquireRateLimit(provider, deps) {
1363
+ if (disabled()) return;
1364
+ const now = deps?.now ?? Date.now;
1365
+ const sleep = deps?.sleep ?? realSleep;
1366
+ let bucket = buckets.get(provider);
1367
+ if (!bucket) {
1368
+ bucket = new Bucket(specFor(provider), now);
1369
+ buckets.set(provider, bucket);
1370
+ }
1371
+ await bucket.acquire({ now, sleep, ...deps?.signal ? { signal: deps.signal } : {} });
1372
+ }
1373
+
1368
1374
  // src/providers/anthropic.ts
1369
1375
  var ANTHROPIC_API_URL = "https://api.anthropic.com/v1/messages";
1370
1376
  var ANTHROPIC_VERSION_HEADER = "2023-06-01";
@@ -1491,29 +1497,33 @@ async function sendAnthropic(args) {
1491
1497
  body: JSON.stringify(body)
1492
1498
  };
1493
1499
  if (args.signal) init.signal = args.signal;
1494
- let respLike;
1495
- try {
1496
- respLike = await doFetch(url, init);
1497
- } catch (e) {
1498
- throw new ProviderError("network", `anthropic: fetch failed: ${e.message}`);
1499
- }
1500
- const status = respLike.status;
1501
- if (status >= 200 && status < 300) {
1502
- let parsed;
1500
+ const attemptOnce = async () => {
1501
+ let respLike;
1503
1502
  try {
1504
- parsed = await respLike.json();
1503
+ respLike = await doFetch(url, init);
1505
1504
  } catch (e) {
1506
- throw new ProviderError("parse", `anthropic: response body not JSON: ${e.message}`);
1505
+ throw new ProviderError("network", `anthropic: fetch failed: ${e.message}`);
1507
1506
  }
1508
- const { text, usage } = parseAnthropicResponse({
1509
- resp: parsed,
1510
- model: args.model,
1511
- purpose: args.purpose ?? "worker"
1512
- });
1513
- return { text, attempts: 1, usage: applyPricing(usage, args.pricing) };
1514
- }
1515
- const bodyText = await respLike.text().catch(() => "");
1516
- throw httpFailureToProviderError("anthropic", status, bodyText);
1507
+ const status = respLike.status;
1508
+ if (status >= 200 && status < 300) {
1509
+ let parsed;
1510
+ try {
1511
+ parsed = await respLike.json();
1512
+ } catch (e) {
1513
+ throw new ProviderError("parse", `anthropic: response body not JSON: ${e.message}`);
1514
+ }
1515
+ const { text, usage } = parseAnthropicResponse({
1516
+ resp: parsed,
1517
+ model: args.model,
1518
+ purpose: args.purpose ?? "worker"
1519
+ });
1520
+ return { text, attempts: 1, usage: applyPricing(usage, args.pricing) };
1521
+ }
1522
+ const bodyText = await respLike.text().catch(() => "");
1523
+ throw httpFailureToProviderError("anthropic", status, bodyText, parseRetryAfter(respLike));
1524
+ };
1525
+ await acquireRateLimit("anthropic", args.signal ? { signal: args.signal } : void 0);
1526
+ return sendWithRetry(attemptOnce, resolveRetryConfig(), args.signal);
1517
1527
  }
1518
1528
 
1519
1529
  // src/providers/gemini.ts
@@ -1665,29 +1675,33 @@ async function sendGemini(args) {
1665
1675
  body: JSON.stringify(body)
1666
1676
  };
1667
1677
  if (args.signal) init.signal = args.signal;
1668
- let respLike;
1669
- try {
1670
- respLike = await doFetch(url, init);
1671
- } catch (e) {
1672
- throw new ProviderError("network", `gemini: fetch failed: ${e.message}`);
1673
- }
1674
- const status = respLike.status;
1675
- if (status >= 200 && status < 300) {
1676
- let parsed;
1678
+ const attemptOnce = async () => {
1679
+ let respLike;
1677
1680
  try {
1678
- parsed = await respLike.json();
1681
+ respLike = await doFetch(url, init);
1679
1682
  } catch (e) {
1680
- throw new ProviderError("parse", `gemini: response body not JSON: ${e.message}`);
1683
+ throw new ProviderError("network", `gemini: fetch failed: ${e.message}`);
1681
1684
  }
1682
- const { text, usage } = parseGeminiResponse({
1683
- resp: parsed,
1684
- model: args.model,
1685
- purpose: args.purpose ?? "worker"
1686
- });
1687
- return { text, attempts: 1, usage: applyPricing2(usage, args.pricing) };
1688
- }
1689
- const bodyText = await respLike.text().catch(() => "");
1690
- throw httpFailureToProviderError("gemini", status, bodyText);
1685
+ const status = respLike.status;
1686
+ if (status >= 200 && status < 300) {
1687
+ let parsed;
1688
+ try {
1689
+ parsed = await respLike.json();
1690
+ } catch (e) {
1691
+ throw new ProviderError("parse", `gemini: response body not JSON: ${e.message}`);
1692
+ }
1693
+ const { text, usage } = parseGeminiResponse({
1694
+ resp: parsed,
1695
+ model: args.model,
1696
+ purpose: args.purpose ?? "worker"
1697
+ });
1698
+ return { text, attempts: 1, usage: applyPricing2(usage, args.pricing) };
1699
+ }
1700
+ const bodyText = await respLike.text().catch(() => "");
1701
+ throw httpFailureToProviderError("gemini", status, bodyText, parseRetryAfter(respLike));
1702
+ };
1703
+ await acquireRateLimit("gemini", args.signal ? { signal: args.signal } : void 0);
1704
+ return sendWithRetry(attemptOnce, resolveRetryConfig(), args.signal);
1691
1705
  }
1692
1706
 
1693
1707
  // src/providers/openai-compatible.ts
@@ -1827,30 +1841,34 @@ async function sendOpenAICompatible(args) {
1827
1841
  body: JSON.stringify(body)
1828
1842
  };
1829
1843
  if (args.signal) init.signal = args.signal;
1830
- let respLike;
1831
- try {
1832
- respLike = await doFetch(url, init);
1833
- } catch (e) {
1834
- throw new ProviderError("network", `${args.provider}: fetch failed: ${e.message}`);
1835
- }
1836
- const status = respLike.status;
1837
- if (status >= 200 && status < 300) {
1838
- let parsed;
1844
+ const attemptOnce = async () => {
1845
+ let respLike;
1839
1846
  try {
1840
- parsed = await respLike.json();
1847
+ respLike = await doFetch(url, init);
1841
1848
  } catch (e) {
1842
- throw new ProviderError("parse", `${args.provider}: response body not JSON: ${e.message}`);
1849
+ throw new ProviderError("network", `${args.provider}: fetch failed: ${e.message}`);
1843
1850
  }
1844
- const { text, usage } = parseOpenAICompatibleResponse({
1845
- resp: parsed,
1846
- provider: args.provider,
1847
- model: args.model,
1848
- purpose: args.purpose ?? "worker"
1849
- });
1850
- return { text, attempts: 1, usage: applyPricing3(usage, args.pricing) };
1851
- }
1852
- const bodyText = await respLike.text().catch(() => "");
1853
- throw httpFailureToProviderError(args.provider, status, bodyText);
1851
+ const status = respLike.status;
1852
+ if (status >= 200 && status < 300) {
1853
+ let parsed;
1854
+ try {
1855
+ parsed = await respLike.json();
1856
+ } catch (e) {
1857
+ throw new ProviderError("parse", `${args.provider}: response body not JSON: ${e.message}`);
1858
+ }
1859
+ const { text, usage } = parseOpenAICompatibleResponse({
1860
+ resp: parsed,
1861
+ provider: args.provider,
1862
+ model: args.model,
1863
+ purpose: args.purpose ?? "worker"
1864
+ });
1865
+ return { text, attempts: 1, usage: applyPricing3(usage, args.pricing) };
1866
+ }
1867
+ const bodyText = await respLike.text().catch(() => "");
1868
+ throw httpFailureToProviderError(args.provider, status, bodyText, parseRetryAfter(respLike));
1869
+ };
1870
+ await acquireRateLimit(args.provider, args.signal ? { signal: args.signal } : void 0);
1871
+ return sendWithRetry(attemptOnce, resolveRetryConfig(), args.signal);
1854
1872
  }
1855
1873
 
1856
1874
  // src/providers/registry.ts
@@ -1945,7 +1963,51 @@ function makeGeminiProvider(model, apiKey, opts) {
1945
1963
  // src/server.ts
1946
1964
  init_cjs_shims();
1947
1965
  var import_server2 = require("@modelcontextprotocol/sdk/server/index.js");
1948
- var import_types6 = require("@modelcontextprotocol/sdk/types.js");
1966
+ var import_types8 = require("@modelcontextprotocol/sdk/types.js");
1967
+
1968
+ // src/bridge/index.ts
1969
+ init_cjs_shims();
1970
+
1971
+ // src/bridge/proxy-tools.ts
1972
+ init_cjs_shims();
1973
+ function buildPythonProxies(bridge) {
1974
+ const proxies = /* @__PURE__ */ new Map();
1975
+ for (const name of bridge.toolNames) {
1976
+ proxies.set(name, makeProxy(bridge, name));
1977
+ }
1978
+ return proxies;
1979
+ }
1980
+ function makeProxy(bridge, name) {
1981
+ return {
1982
+ name,
1983
+ description: `(forwarded to Python crosscheck-agent) ${name}`,
1984
+ // We don't carry the per-tool input schema across the bridge in
1985
+ // route-all mode — the TS server is a transparent forwarder, so
1986
+ // arg validation happens on the Python side. inputSchema is left
1987
+ // permissive; the Python validator throws structured errors that
1988
+ // tunnel back through `callTool()`.
1989
+ inputSchema: {
1990
+ type: "object",
1991
+ additionalProperties: true,
1992
+ description: `Forwarded to Python \u2014 see Python tool '${name}' for full schema.`
1993
+ },
1994
+ handler: async (args) => {
1995
+ const r = await bridge.callTool(name, args);
1996
+ if (!r.content.length) {
1997
+ return {};
1998
+ }
1999
+ const first = r.content[0];
2000
+ if (first.type === "text" && first.text) {
2001
+ try {
2002
+ return JSON.parse(first.text);
2003
+ } catch {
2004
+ return { text: first.text };
2005
+ }
2006
+ }
2007
+ return { content: r.content };
2008
+ }
2009
+ };
2010
+ }
1949
2011
 
1950
2012
  // src/instructions.ts
1951
2013
  init_cjs_shims();
@@ -2012,7 +2074,7 @@ var import_zod = require("zod");
2012
2074
 
2013
2075
  // src/tools/audit.ts
2014
2076
  init_cjs_shims();
2015
- var import_node_fs4 = require("fs");
2077
+ var import_node_fs5 = require("fs");
2016
2078
  var import_node_path4 = require("path");
2017
2079
 
2018
2080
  // src/core/structured.ts
@@ -2075,27 +2137,27 @@ function extractJson(text) {
2075
2137
 
2076
2138
  // src/core/json-schema.ts
2077
2139
  init_cjs_shims();
2078
- function validateSchema(value, schema, path12 = "") {
2140
+ function validateSchema(value, schema, path14 = "") {
2079
2141
  const errs = [];
2080
2142
  if ("anyOf" in schema) {
2081
2143
  const subs = schema["anyOf"].map(
2082
- (s) => validateSchema(value, s, path12)
2144
+ (s) => validateSchema(value, s, path14)
2083
2145
  );
2084
2146
  if (!subs.some((e) => e.length === 0)) {
2085
- errs.push(`${path12 || "<root>"}: did not match anyOf`);
2147
+ errs.push(`${path14 || "<root>"}: did not match anyOf`);
2086
2148
  }
2087
2149
  return errs;
2088
2150
  }
2089
2151
  if ("oneOf" in schema) {
2090
- const passed = schema["oneOf"].filter((s) => validateSchema(value, s, path12).length === 0).length;
2152
+ const passed = schema["oneOf"].filter((s) => validateSchema(value, s, path14).length === 0).length;
2091
2153
  if (passed !== 1) {
2092
- errs.push(`${path12 || "<root>"}: matched ${passed} of oneOf, expected 1`);
2154
+ errs.push(`${path14 || "<root>"}: matched ${passed} of oneOf, expected 1`);
2093
2155
  }
2094
2156
  return errs;
2095
2157
  }
2096
2158
  if ("const" in schema && !deepEqual(value, schema["const"])) {
2097
2159
  errs.push(
2098
- `${path12 || "<root>"}: expected const ${pyRepr(schema["const"])}, got ${pyRepr(value)}`
2160
+ `${path14 || "<root>"}: expected const ${pyRepr(schema["const"])}, got ${pyRepr(value)}`
2099
2161
  );
2100
2162
  return errs;
2101
2163
  }
@@ -2105,7 +2167,7 @@ function validateSchema(value, schema, path12 = "") {
2105
2167
  const ok = types.some((tt) => matchesType(value, String(tt)));
2106
2168
  if (!ok) {
2107
2169
  errs.push(
2108
- `${path12 || "<root>"}: expected type ${pyReprType(t)}, got ${pyTypeName(value)}`
2170
+ `${path14 || "<root>"}: expected type ${pyReprType(t)}, got ${pyTypeName(value)}`
2109
2171
  );
2110
2172
  return errs;
2111
2173
  }
@@ -2114,29 +2176,29 @@ function validateSchema(value, schema, path12 = "") {
2114
2176
  if ("enum" in schema) {
2115
2177
  const en = schema["enum"];
2116
2178
  if (!en.some((x) => deepEqual(x, value))) {
2117
- errs.push(`${path12 || "<root>"}: value ${pyRepr(value)} not in enum`);
2179
+ errs.push(`${path14 || "<root>"}: value ${pyRepr(value)} not in enum`);
2118
2180
  }
2119
2181
  }
2120
2182
  if ("minLength" in schema && value.length < schema["minLength"]) {
2121
- errs.push(`${path12 || "<root>"}: shorter than minLength ${schema["minLength"]}`);
2183
+ errs.push(`${path14 || "<root>"}: shorter than minLength ${schema["minLength"]}`);
2122
2184
  }
2123
2185
  }
2124
2186
  if (typeof value === "number" && !Number.isNaN(value)) {
2125
2187
  if ("minimum" in schema && value < schema["minimum"]) {
2126
- errs.push(`${path12 || "<root>"}: ${value} < minimum ${schema["minimum"]}`);
2188
+ errs.push(`${path14 || "<root>"}: ${value} < minimum ${schema["minimum"]}`);
2127
2189
  }
2128
2190
  if ("maximum" in schema && value > schema["maximum"]) {
2129
- errs.push(`${path12 || "<root>"}: ${value} > maximum ${schema["maximum"]}`);
2191
+ errs.push(`${path14 || "<root>"}: ${value} > maximum ${schema["maximum"]}`);
2130
2192
  }
2131
2193
  }
2132
2194
  if (Array.isArray(value)) {
2133
2195
  if ("minItems" in schema && value.length < schema["minItems"]) {
2134
- errs.push(`${path12 || "<root>"}: fewer items than minItems ${schema["minItems"]}`);
2196
+ errs.push(`${path14 || "<root>"}: fewer items than minItems ${schema["minItems"]}`);
2135
2197
  }
2136
2198
  const itemSchema = schema["items"];
2137
2199
  if (isObj(itemSchema)) {
2138
2200
  for (let i = 0; i < value.length; i++) {
2139
- errs.push(...validateSchema(value[i], itemSchema, `${path12}[${i}]`));
2201
+ errs.push(...validateSchema(value[i], itemSchema, `${path14}[${i}]`));
2140
2202
  }
2141
2203
  }
2142
2204
  }
@@ -2145,19 +2207,19 @@ function validateSchema(value, schema, path12 = "") {
2145
2207
  const required = schema["required"] ?? [];
2146
2208
  for (const r of required) {
2147
2209
  if (!(r in value)) {
2148
- errs.push(`${path12 || "<root>"}: missing required key ${pyRepr(r)}`);
2210
+ errs.push(`${path14 || "<root>"}: missing required key ${pyRepr(r)}`);
2149
2211
  }
2150
2212
  }
2151
2213
  if (schema["additionalProperties"] === false) {
2152
2214
  for (const k of Object.keys(value)) {
2153
2215
  if (!(k in props)) {
2154
- errs.push(`${path12 || "<root>"}: unknown key ${pyRepr(k)}`);
2216
+ errs.push(`${path14 || "<root>"}: unknown key ${pyRepr(k)}`);
2155
2217
  }
2156
2218
  }
2157
2219
  }
2158
2220
  for (const [k, v] of Object.entries(value)) {
2159
2221
  const ps = props[k];
2160
- if (ps) errs.push(...validateSchema(v, ps, path12 ? `${path12}.${k}` : k));
2222
+ if (ps) errs.push(...validateSchema(v, ps, path14 ? `${path14}.${k}` : k));
2161
2223
  }
2162
2224
  }
2163
2225
  return errs;
@@ -2546,6 +2608,21 @@ async function recordSessionCall(storage, sessionId, answers, wallMs, cpuMs, now
2546
2608
  }
2547
2609
  }
2548
2610
 
2611
+ // src/core/budgets.ts
2612
+ init_cjs_shims();
2613
+ var operatorBudgets = null;
2614
+ function setOperatorBudgets(cfg) {
2615
+ operatorBudgets = cfg;
2616
+ }
2617
+ function operatorCeiling(purpose, provider) {
2618
+ const direct = operatorBudgets?.token_budgets?.[purpose];
2619
+ if (typeof direct === "number" && direct > 0) return Math.trunc(direct);
2620
+ const byProv = operatorBudgets?.token_budgets_by_provider?.[provider.toLowerCase()];
2621
+ const pv = byProv?.[purpose];
2622
+ if (typeof pv === "number" && pv > 0) return Math.trunc(pv);
2623
+ return null;
2624
+ }
2625
+
2549
2626
  // src/core/structured.ts
2550
2627
  async function requestStructured(provider, baseMessages, schema, opts) {
2551
2628
  const maxRetries = opts.maxRetries ?? 1;
@@ -2620,10 +2697,13 @@ ${schemaText}`;
2620
2697
  async function askOne(provider, messages, opts) {
2621
2698
  const startedWall = performance.now();
2622
2699
  const startedCpu = process.cpuUsage();
2700
+ let maxTokens = opts.maxTokens;
2701
+ const ceiling = operatorCeiling(opts.purpose, provider.name);
2702
+ if (ceiling !== null && ceiling < maxTokens) maxTokens = ceiling;
2623
2703
  try {
2624
2704
  const r = await provider.send({
2625
2705
  messages,
2626
- maxTokens: opts.maxTokens,
2706
+ maxTokens,
2627
2707
  temperature: opts.temperature,
2628
2708
  purpose: opts.purpose,
2629
2709
  ...opts.signal ? { signal: opts.signal } : {},
@@ -2664,6 +2744,191 @@ async function askOne(provider, messages, opts) {
2664
2744
  }
2665
2745
  }
2666
2746
 
2747
+ // src/core/personas.ts
2748
+ init_cjs_shims();
2749
+ var import_node_fs3 = require("fs");
2750
+ var import_node_path2 = __toESM(require("path"), 1);
2751
+ var import_node_url = require("url");
2752
+ var PERSONA_IDS = [
2753
+ "engineering",
2754
+ "research",
2755
+ "auditor-security",
2756
+ "auditor-finance",
2757
+ "finance",
2758
+ "design"
2759
+ ];
2760
+ function isPersonaId(s) {
2761
+ return PERSONA_IDS.includes(s);
2762
+ }
2763
+ var personaConfig = null;
2764
+ function setPersonaConfig(cfg) {
2765
+ personaConfig = cfg;
2766
+ }
2767
+ var processFirstCallConsumed = false;
2768
+ var sessionsSeen = /* @__PURE__ */ new Set();
2769
+ function consumeFirstCall(sessionId) {
2770
+ if (sessionId) {
2771
+ if (sessionsSeen.has(sessionId)) return false;
2772
+ sessionsSeen.add(sessionId);
2773
+ return true;
2774
+ }
2775
+ if (processFirstCallConsumed) return false;
2776
+ processFirstCallConsumed = true;
2777
+ return true;
2778
+ }
2779
+ var TOOL_PRIORS = {
2780
+ // The audit tool is for auditing — lean to security audit by default (prior
2781
+ // clears the >=2 margin on its own), while finance keywords still flip it to
2782
+ // auditor-finance.
2783
+ audit: { "auditor-security": 3, "auditor-finance": 1 }
2784
+ };
2785
+ var TERMS = {
2786
+ "auditor-security": [
2787
+ [/\b(vuln(erabilit|)|cve|cwe|exploit|owasp|injection|sql\s*i|xss|csrf|ssrf|rce|deserializ|auth[zn]|secret|threat\s*model|attack\s*surface|privilege)\b/i, 3],
2788
+ [/\b(security|hardening|pentest|malicious|sandbox\s*escape)\b/i, 1]
2789
+ ],
2790
+ "auditor-finance": [
2791
+ [/\b(gaap|ifrs|sox|materiality|reconcil|ledger|accrual|audit\s*trail|segregation\s*of\s*duties|internal\s*control|journal\s*entry)\b/i, 3],
2792
+ [/\b(audit|invoice|p&l|balance\s*sheet)\b/i, 1]
2793
+ ],
2794
+ finance: [
2795
+ [/\b(valuation|dcf|npv|irr|cash\s*flow|unit\s*economics|burn\s*rate|runway|cap\s*table|forecast|p&l|ebitda|cohort|ltv|cac)\b/i, 3],
2796
+ [/\b(budget|revenue|margin|pricing|financial\s*model)\b/i, 1]
2797
+ ],
2798
+ research: [
2799
+ [/\b(literature|hypothes(is|es)|methodology|empirical|citation|peer[-\s]?review|ablation|systematic\s*review|meta[-\s]?analysis)\b/i, 3],
2800
+ [/\b(research|study|survey|evidence|paper|findings)\b/i, 1]
2801
+ ],
2802
+ design: [
2803
+ [/\b(ux|usability|wireframe|figma|interaction\s*design|user\s*journey|design\s*system|a11y|accessibilit|heuristic\s*evaluation|information\s*architecture)\b/i, 3],
2804
+ [/\b(design|layout|visual|prototype|onboarding\s*flow)\b/i, 1]
2805
+ ]
2806
+ };
2807
+ function routePersona(prompt, toolName) {
2808
+ const text = String(prompt ?? "");
2809
+ const scores = { engineering: 1 };
2810
+ const priors = TOOL_PRIORS[toolName] ?? {};
2811
+ for (const id of PERSONA_IDS) {
2812
+ if (id === "engineering") continue;
2813
+ let s = priors[id] ?? 0;
2814
+ for (const [re, w] of TERMS[id]) {
2815
+ if (re.test(text)) s += w;
2816
+ }
2817
+ scores[id] = s;
2818
+ }
2819
+ let bestId = "engineering";
2820
+ let bestScore = -Infinity;
2821
+ for (const id of PERSONA_IDS) {
2822
+ if (id === "engineering") continue;
2823
+ if (scores[id] > bestScore) {
2824
+ bestScore = scores[id];
2825
+ bestId = id;
2826
+ }
2827
+ }
2828
+ if (bestScore - (scores["engineering"] ?? 0) >= 2) return { id: bestId, scores };
2829
+ return { id: "engineering", scores };
2830
+ }
2831
+ function resolvePersona(opts) {
2832
+ const cfg = opts.config ?? personaConfig;
2833
+ if (cfg?.enabled === false) return { id: null, source: "disabled", bypassed: true };
2834
+ const explicit = opts.explicit?.trim().toLowerCase();
2835
+ if (explicit === "none" || explicit === "off") {
2836
+ return { id: null, source: "none", bypassed: true };
2837
+ }
2838
+ if (explicit && explicit !== "auto" && isPersonaId(explicit)) {
2839
+ return { id: explicit, source: "explicit", bypassed: false };
2840
+ }
2841
+ const envPin = (opts.envPin ?? process.env["CROSSCHECK_PERSONA"])?.trim().toLowerCase();
2842
+ if (envPin && isPersonaId(envPin)) {
2843
+ return { id: envPin, source: "env", bypassed: false };
2844
+ }
2845
+ const routed = routePersona(opts.prompt, opts.toolName);
2846
+ if (routed.id !== "engineering") {
2847
+ return { id: routed.id, source: "heuristic", scores: routed.scores, bypassed: false };
2848
+ }
2849
+ if (opts.firstCall) {
2850
+ return { id: "engineering", source: "default-first-call", scores: routed.scores, bypassed: false };
2851
+ }
2852
+ return { id: null, source: "heuristic", scores: routed.scores, bypassed: true };
2853
+ }
2854
+ var bodyCache = /* @__PURE__ */ new Map();
2855
+ function bundledPersonasDir() {
2856
+ let dir;
2857
+ try {
2858
+ dir = import_node_path2.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
2859
+ } catch {
2860
+ dir = process.cwd();
2861
+ }
2862
+ for (let i = 0; i < 8; i += 1) {
2863
+ const cand = import_node_path2.default.join(dir, "personas");
2864
+ if ((0, import_node_fs3.existsSync)(import_node_path2.default.join(cand, "engineering.md"))) return cand;
2865
+ const parent = import_node_path2.default.dirname(dir);
2866
+ if (parent === dir) break;
2867
+ dir = parent;
2868
+ }
2869
+ return void 0;
2870
+ }
2871
+ function readIfExists(p) {
2872
+ try {
2873
+ return (0, import_node_fs3.existsSync)(p) ? (0, import_node_fs3.readFileSync)(p, "utf8") : null;
2874
+ } catch {
2875
+ return null;
2876
+ }
2877
+ }
2878
+ function loadPersonaBody(id, opts) {
2879
+ const cfg = opts?.config ?? personaConfig;
2880
+ const cacheKey = `${id}|${opts?.repoRoot ?? ""}|${cfg?.dir ?? ""}`;
2881
+ const cached = bodyCache.get(cacheKey);
2882
+ if (cached !== void 0) return cached;
2883
+ const candidates = [];
2884
+ if (cfg?.dir) candidates.push(import_node_path2.default.join(cfg.dir, `${id}.md`));
2885
+ if (opts?.repoRoot) candidates.push(import_node_path2.default.join(opts.repoRoot, ".crosscheck", "personas", `${id}.md`));
2886
+ if (id === "engineering" && opts?.repoRoot) candidates.push(import_node_path2.default.join(opts.repoRoot, "CLAUDE.md"));
2887
+ const bundled = bundledPersonasDir();
2888
+ if (bundled) candidates.push(import_node_path2.default.join(bundled, `${id}.md`));
2889
+ for (const c of candidates) {
2890
+ const body = readIfExists(c);
2891
+ if (body && body.trim()) {
2892
+ const trimmed = body.trim();
2893
+ bodyCache.set(cacheKey, trimmed);
2894
+ return trimmed;
2895
+ }
2896
+ }
2897
+ return null;
2898
+ }
2899
+ function renderPersonaBlock(id, body) {
2900
+ return `<operating-context source="${id}">
2901
+ ${body}
2902
+ </operating-context>`;
2903
+ }
2904
+ function buildPersonaInjection(opts) {
2905
+ try {
2906
+ const firstCall = consumeFirstCall(opts.sessionId);
2907
+ const res = resolvePersona({
2908
+ toolName: opts.toolName,
2909
+ prompt: opts.prompt,
2910
+ explicit: opts.explicit,
2911
+ firstCall,
2912
+ config: personaConfig
2913
+ });
2914
+ const meta = {
2915
+ used: res.id,
2916
+ source: res.source,
2917
+ injected_into: res.id ? ["moderator"] : [],
2918
+ bypassed: res.bypassed,
2919
+ ...res.scores ? { scores: res.scores } : {}
2920
+ };
2921
+ if (!res.id) return { block: null, meta };
2922
+ const body = loadPersonaBody(res.id, { repoRoot: opts.repoRoot });
2923
+ if (!body) {
2924
+ return { block: null, meta: { ...meta, injected_into: [], bypassed: true } };
2925
+ }
2926
+ return { block: renderPersonaBlock(res.id, body), meta };
2927
+ } catch {
2928
+ return { block: null, meta: { used: null, source: "none", injected_into: [], bypassed: true } };
2929
+ }
2930
+ }
2931
+
2667
2932
  // src/core/utils.ts
2668
2933
  init_cjs_shims();
2669
2934
  function checkSessionBreakers(session, cfg) {
@@ -2802,7 +3067,7 @@ function maybeDagBreakerEnvelope(dag, toolName, cfg, sessionId) {
2802
3067
 
2803
3068
  // src/core/transcripts.ts
2804
3069
  init_cjs_shims();
2805
- var import_node_fs3 = require("fs");
3070
+ var import_node_fs4 = require("fs");
2806
3071
  var import_node_path3 = __toESM(require("path"), 1);
2807
3072
 
2808
3073
  // src/core/redact.ts
@@ -2881,6 +3146,13 @@ function redactObj(obj, cfg) {
2881
3146
  }
2882
3147
  return obj;
2883
3148
  }
3149
+ var globalRedaction = null;
3150
+ function setRedactionConfig(cfg) {
3151
+ globalRedaction = cfg;
3152
+ }
3153
+ function getRedactionConfig() {
3154
+ return globalRedaction ?? void 0;
3155
+ }
2884
3156
 
2885
3157
  // src/core/transcripts.ts
2886
3158
  var FTS_INDEX_MAX_CHARS = 64 * 1024;
@@ -2938,12 +3210,12 @@ async function writeTranscript(opts) {
2938
3210
  const now = opts.nowMs ? opts.nowMs() : Date.now();
2939
3211
  const stampMs = Math.trunc(now);
2940
3212
  const fileName = `${stampMs}-${opts.kind}.json`;
2941
- const redacted = redactObj(opts.payload, opts.redact);
3213
+ const redacted = redactObj(opts.payload, opts.redact ?? getRedactionConfig());
2942
3214
  let absPath;
2943
3215
  try {
2944
- (0, import_node_fs3.mkdirSync)(opts.transcriptsDir, { recursive: true });
3216
+ (0, import_node_fs4.mkdirSync)(opts.transcriptsDir, { recursive: true });
2945
3217
  absPath = import_node_path3.default.resolve(opts.transcriptsDir, fileName);
2946
- (0, import_node_fs3.writeFileSync)(absPath, JSON.stringify(redacted, null, 2), "utf8");
3218
+ (0, import_node_fs4.writeFileSync)(absPath, JSON.stringify(redacted, null, 2), "utf8");
2947
3219
  } catch {
2948
3220
  return null;
2949
3221
  }
@@ -3435,7 +3707,18 @@ async function runAudit(args, opts) {
3435
3707
  for (const r2 of DEFAULT_AUDIT_RUBRICS) rubricItems.push({ ...r2 });
3436
3708
  }
3437
3709
  const rubricText = rubricItems.map((it) => `- ${it.id} (severity=${it.severity}): ${it.description}`).join("\n");
3438
- const sysMsg = "You are an independent auditor. Score the OUTPUT against each rubric item on a 0..1 likelihood that the rubric is satisfied. Set pass=true iff score >= 0.7. Be concise in `rationale` (1-2 sentences each).";
3710
+ const persona = buildPersonaInjection({
3711
+ toolName: "audit",
3712
+ prompt: `${userConstraints ?? ""}
3713
+ ${outputResolved}`,
3714
+ ...typeof args["persona"] === "string" ? { explicit: args["persona"] } : {},
3715
+ ...opts.repoRoot ? { repoRoot: opts.repoRoot } : {},
3716
+ sessionId: typeof args["session_id"] === "string" ? args["session_id"] : null
3717
+ });
3718
+ const auditorSys = "You are an independent auditor. Score the OUTPUT against each rubric item on a 0..1 likelihood that the rubric is satisfied. Set pass=true iff score >= 0.7. Be concise in `rationale` (1-2 sentences each).";
3719
+ const sysMsg = persona.block ? `${persona.block}
3720
+
3721
+ ${auditorSys}` : auditorSys;
3439
3722
  const userMsg = (userConstraints ? `USER CONSTRAINTS:
3440
3723
  ${userConstraints}
3441
3724
 
@@ -3532,6 +3815,7 @@ ${rubricText}`;
3532
3815
  audit_process_failure: flags.audit_process_failure,
3533
3816
  judges_stats: flags.judges_stats
3534
3817
  };
3818
+ if (persona.meta.used !== null) coalesceResult["persona"] = persona.meta;
3535
3819
  if (!allPass2 && opts.storage && typeof sessionId === "string" && sessionId) {
3536
3820
  const marked = await markStaleOnAuditFailure(
3537
3821
  opts.storage,
@@ -3612,6 +3896,7 @@ ${rubricText}`;
3612
3896
  overall_score: overall,
3613
3897
  passed: allPass
3614
3898
  };
3899
+ if (persona.meta.used !== null) result["persona"] = persona.meta;
3615
3900
  if (errs.length > 0) result["validation_errors"] = errs;
3616
3901
  if (!allPass && opts.storage && typeof sessionId === "string" && sessionId) {
3617
3902
  const marked = await markStaleOnAuditFailure(
@@ -3897,7 +4182,7 @@ function loadAuditTextFromSession(sessionId, transcriptsDir) {
3897
4182
  if (!transcriptsDir) return "";
3898
4183
  let entries;
3899
4184
  try {
3900
- entries = (0, import_node_fs4.readdirSync)(transcriptsDir);
4185
+ entries = (0, import_node_fs5.readdirSync)(transcriptsDir);
3901
4186
  } catch {
3902
4187
  return "";
3903
4188
  }
@@ -3908,7 +4193,7 @@ function loadAuditTextFromSession(sessionId, transcriptsDir) {
3908
4193
  const p = (0, import_node_path4.join)(transcriptsDir, name);
3909
4194
  let doc2;
3910
4195
  try {
3911
- doc2 = JSON.parse((0, import_node_fs4.readFileSync)(p, "utf8"));
4196
+ doc2 = JSON.parse((0, import_node_fs5.readFileSync)(p, "utf8"));
3912
4197
  } catch {
3913
4198
  continue;
3914
4199
  }
@@ -3916,7 +4201,7 @@ function loadAuditTextFromSession(sessionId, transcriptsDir) {
3916
4201
  if (sid !== sessionId) continue;
3917
4202
  let mtime;
3918
4203
  try {
3919
- mtime = (0, import_node_fs4.statSync)(p).mtimeMs;
4204
+ mtime = (0, import_node_fs5.statSync)(p).mtimeMs;
3920
4205
  } catch {
3921
4206
  continue;
3922
4207
  }
@@ -3928,7 +4213,7 @@ function loadAuditTextFromSession(sessionId, transcriptsDir) {
3928
4213
  if (bestPath === null) return "";
3929
4214
  let doc;
3930
4215
  try {
3931
- doc = JSON.parse((0, import_node_fs4.readFileSync)(bestPath, "utf8"));
4216
+ doc = JSON.parse((0, import_node_fs5.readFileSync)(bestPath, "utf8"));
3932
4217
  } catch {
3933
4218
  return "";
3934
4219
  }
@@ -3961,7 +4246,7 @@ function boolArg(v, defaultVal) {
3961
4246
 
3962
4247
  // src/tools/bench.ts
3963
4248
  init_cjs_shims();
3964
- var import_node_fs5 = require("fs");
4249
+ var import_node_fs6 = require("fs");
3965
4250
  var import_node_path6 = __toESM(require("path"), 1);
3966
4251
 
3967
4252
  // src/tools/confer.ts
@@ -4498,21 +4783,21 @@ function extractToolCall(text) {
4498
4783
  return { call, parseError: null };
4499
4784
  }
4500
4785
  function wrapToolResult(name, content) {
4501
- let str;
4502
- if (typeof content === "string") str = content;
4786
+ let str2;
4787
+ if (typeof content === "string") str2 = content;
4503
4788
  else {
4504
4789
  try {
4505
- str = JSON.stringify(content);
4790
+ str2 = JSON.stringify(content);
4506
4791
  } catch {
4507
- str = String(content);
4792
+ str2 = String(content);
4508
4793
  }
4509
4794
  }
4510
- if (str.length > WORKER_TOOLS_MAX_RESULT_CHARS) {
4511
- str = str.slice(0, WORKER_TOOLS_MAX_RESULT_CHARS) + "\n... (truncated)";
4795
+ if (str2.length > WORKER_TOOLS_MAX_RESULT_CHARS) {
4796
+ str2 = str2.slice(0, WORKER_TOOLS_MAX_RESULT_CHARS) + "\n... (truncated)";
4512
4797
  }
4513
4798
  return `<tool_result name="${name}">
4514
4799
  <untrusted_input>
4515
- ${neutralizeInjection(str)}
4800
+ ${neutralizeInjection(str2)}
4516
4801
  </untrusted_input>
4517
4802
  </tool_result>`;
4518
4803
  }
@@ -5077,8 +5362,18 @@ async function runConfer(args, opts) {
5077
5362
  const untrusted = Boolean(args["untrusted_input"]);
5078
5363
  const canary = untrusted ? mintCanary() : null;
5079
5364
  const baseSys = "You are part of a panel of LLMs consulted by an engineer working inside Claude Code. Answer directly, cite assumptions, and keep it crisp.";
5080
- const sysMsg = untrusted ? `${baseSys}
5081
- ${UNTRUSTED_SYSTEM_NOTE}` : baseSys;
5365
+ const persona = buildPersonaInjection({
5366
+ toolName: "confer",
5367
+ prompt: question,
5368
+ ...typeof args["persona"] === "string" ? { explicit: args["persona"] } : {},
5369
+ ...opts.repoRoot ? { repoRoot: opts.repoRoot } : {},
5370
+ sessionId: typeof args["session_id"] === "string" ? args["session_id"] : null
5371
+ });
5372
+ const baseSysP = persona.block ? `${persona.block}
5373
+
5374
+ ${baseSys}` : baseSys;
5375
+ const sysMsg = untrusted ? `${baseSysP}
5376
+ ${UNTRUSTED_SYSTEM_NOTE}` : baseSysP;
5082
5377
  const messages = [{ role: "system", content: sysMsg }];
5083
5378
  if (context) {
5084
5379
  const ctxBody = untrusted ? wrapUntrusted(context, canary) : context;
@@ -5192,6 +5487,7 @@ ${ctxBody}` });
5192
5487
  question,
5193
5488
  answers: sanitized
5194
5489
  };
5490
+ if (persona.meta.used !== null) result["persona"] = persona.meta;
5195
5491
  if (earlyStopped) {
5196
5492
  result["early_stopped"] = true;
5197
5493
  result["skipped_providers"] = skippedProviders;
@@ -5411,9 +5707,19 @@ async function runSandboxed(args) {
5411
5707
  }
5412
5708
  resolve2(result);
5413
5709
  };
5710
+ const memKb = isUnix && args.memoryMb && args.memoryMb > 0 ? Math.trunc(args.memoryMb * 1024) : 0;
5711
+ const cpuS = isUnix && args.cpuSeconds && args.cpuSeconds > 0 ? Math.trunc(args.cpuSeconds) : 0;
5712
+ const useUlimit = memKb > 0 || cpuS > 0;
5713
+ const spawnBin = useUlimit ? "/bin/sh" : args.cmd[0];
5714
+ const spawnArgv = useUlimit ? [
5715
+ "-c",
5716
+ `${[memKb > 0 ? `ulimit -v ${memKb}` : "", cpuS > 0 ? `ulimit -t ${cpuS}` : ""].filter(Boolean).join("; ")} 2>/dev/null; exec "$@"`,
5717
+ "sh",
5718
+ ...args.cmd
5719
+ ] : args.cmd.slice(1);
5414
5720
  let child;
5415
5721
  try {
5416
- child = (0, import_node_child_process.spawn)(args.cmd[0], args.cmd.slice(1), {
5722
+ child = (0, import_node_child_process.spawn)(spawnBin, spawnArgv, {
5417
5723
  cwd: tempCwd ?? process.cwd(),
5418
5724
  env,
5419
5725
  // detached: own process-group on Unix (equivalent to setsid).
@@ -6102,10 +6408,10 @@ async function runBench(args, opts) {
6102
6408
  return result;
6103
6409
  }
6104
6410
  function loadGoldens(dirPath, filter) {
6105
- if (!(0, import_node_fs5.existsSync)(dirPath)) return [];
6411
+ if (!(0, import_node_fs6.existsSync)(dirPath)) return [];
6106
6412
  let entries;
6107
6413
  try {
6108
- entries = (0, import_node_fs5.readdirSync)(dirPath);
6414
+ entries = (0, import_node_fs6.readdirSync)(dirPath);
6109
6415
  } catch {
6110
6416
  return [];
6111
6417
  }
@@ -6114,13 +6420,13 @@ function loadGoldens(dirPath, filter) {
6114
6420
  if (!name.endsWith(".json")) continue;
6115
6421
  const p = import_node_path6.default.join(dirPath, name);
6116
6422
  try {
6117
- (0, import_node_fs5.statSync)(p);
6423
+ (0, import_node_fs6.statSync)(p);
6118
6424
  } catch {
6119
6425
  continue;
6120
6426
  }
6121
6427
  let doc;
6122
6428
  try {
6123
- doc = JSON.parse((0, import_node_fs5.readFileSync)(p, "utf8"));
6429
+ doc = JSON.parse((0, import_node_fs6.readFileSync)(p, "utf8"));
6124
6430
  } catch {
6125
6431
  continue;
6126
6432
  }
@@ -6200,7 +6506,7 @@ function isObj4(v) {
6200
6506
  // src/tools/config-pin.ts
6201
6507
  init_cjs_shims();
6202
6508
  var import_node_crypto3 = require("crypto");
6203
- var import_node_fs6 = require("fs");
6509
+ var import_node_fs7 = require("fs");
6204
6510
  var import_node_path7 = __toESM(require("path"), 1);
6205
6511
  var DEFAULT_PIN_PATH = ".crosscheck/config_pins.json";
6206
6512
  var DEFAULT_TARGETS = ["crosscheck.config.json", "config/pricing.json"];
@@ -6273,10 +6579,10 @@ async function runConfigPin(args, opts) {
6273
6579
  };
6274
6580
  }
6275
6581
  const absPath = resolvePinPath(opts);
6276
- const existed = (0, import_node_fs6.existsSync)(absPath);
6582
+ const existed = (0, import_node_fs7.existsSync)(absPath);
6277
6583
  if (existed) {
6278
6584
  try {
6279
- (0, import_node_fs6.unlinkSync)(absPath);
6585
+ (0, import_node_fs7.unlinkSync)(absPath);
6280
6586
  } catch (e) {
6281
6587
  return errorEnvelope4(
6282
6588
  "CONFIG_PIN_CLEAR_FAILED",
@@ -6311,7 +6617,7 @@ function computeDrift(opts) {
6311
6617
  missing_files: missingFiles.sort(),
6312
6618
  pin_file: relPath(opts, absPath),
6313
6619
  pinned_at: Number(doc?.["pinned_at"] ?? 0) || 0,
6314
- has_pin_file: (0, import_node_fs6.existsSync)(absPath)
6620
+ has_pin_file: (0, import_node_fs7.existsSync)(absPath)
6315
6621
  };
6316
6622
  }
6317
6623
  function shouldBlock(opts, drift) {
@@ -6336,9 +6642,9 @@ function resolveTargets(opts) {
6336
6642
  const out = [];
6337
6643
  for (const p of list) {
6338
6644
  const abs = import_node_path7.default.isAbsolute(p) ? p : import_node_path7.default.join(opts.repoRoot, p);
6339
- if ((0, import_node_fs6.existsSync)(abs)) {
6645
+ if ((0, import_node_fs7.existsSync)(abs)) {
6340
6646
  try {
6341
- const st = (0, import_node_fs6.statSync)(abs);
6647
+ const st = (0, import_node_fs7.statSync)(abs);
6342
6648
  if (st.isFile()) out.push(abs);
6343
6649
  } catch {
6344
6650
  }
@@ -6349,7 +6655,7 @@ function resolveTargets(opts) {
6349
6655
  function hashFile(abs) {
6350
6656
  let raw;
6351
6657
  try {
6352
- raw = (0, import_node_fs6.readFileSync)(abs);
6658
+ raw = (0, import_node_fs7.readFileSync)(abs);
6353
6659
  } catch {
6354
6660
  return "";
6355
6661
  }
@@ -6369,9 +6675,9 @@ function resolvePinPath(opts) {
6369
6675
  return import_node_path7.default.isAbsolute(raw) ? raw : import_node_path7.default.join(opts.repoRoot, raw);
6370
6676
  }
6371
6677
  function loadDoc(absPath) {
6372
- if (!(0, import_node_fs6.existsSync)(absPath)) return null;
6678
+ if (!(0, import_node_fs7.existsSync)(absPath)) return null;
6373
6679
  try {
6374
- const txt = (0, import_node_fs6.readFileSync)(absPath, "utf8");
6680
+ const txt = (0, import_node_fs7.readFileSync)(absPath, "utf8");
6375
6681
  const doc = JSON.parse(txt);
6376
6682
  return doc && typeof doc === "object" && !Array.isArray(doc) ? doc : null;
6377
6683
  } catch {
@@ -6380,11 +6686,11 @@ function loadDoc(absPath) {
6380
6686
  }
6381
6687
  function saveDoc(opts, doc) {
6382
6688
  const absPath = resolvePinPath(opts);
6383
- (0, import_node_fs6.mkdirSync)(import_node_path7.default.dirname(absPath), { recursive: true });
6689
+ (0, import_node_fs7.mkdirSync)(import_node_path7.default.dirname(absPath), { recursive: true });
6384
6690
  const tmp = absPath + ".tmp";
6385
- (0, import_node_fs6.writeFileSync)(tmp, JSON.stringify(doc, null, 2), "utf8");
6691
+ (0, import_node_fs7.writeFileSync)(tmp, JSON.stringify(doc, null, 2), "utf8");
6386
6692
  try {
6387
- (0, import_node_fs6.unlinkSync)(absPath);
6693
+ (0, import_node_fs7.unlinkSync)(absPath);
6388
6694
  } catch {
6389
6695
  }
6390
6696
  const { renameSync: renameSync2 } = require("fs");
@@ -6433,7 +6739,7 @@ function pyRepr2(v) {
6433
6739
  // src/tools/create.ts
6434
6740
  init_cjs_shims();
6435
6741
  var import_node_crypto6 = require("crypto");
6436
- var import_node_fs9 = require("fs");
6742
+ var import_node_fs10 = require("fs");
6437
6743
  var import_node_path10 = __toESM(require("path"), 1);
6438
6744
 
6439
6745
  // src/tools/orchestrate.ts
@@ -6536,7 +6842,7 @@ function isDeadModelError(err) {
6536
6842
  // src/core/node-cache.ts
6537
6843
  init_cjs_shims();
6538
6844
  var import_node_crypto4 = require("crypto");
6539
- var import_node_fs7 = require("fs");
6845
+ var import_node_fs8 = require("fs");
6540
6846
  var import_node_path8 = require("path");
6541
6847
  var NODE_CACHE_KEY_VERSION = "v2";
6542
6848
  var CANON_PATTERNS = [
@@ -6601,18 +6907,18 @@ function cachePath(cfg, repoRoot, key) {
6601
6907
  function getNodeCache(cfg, repoRoot, key, nowMs) {
6602
6908
  if (!cfg || cfg.enabled === false) return null;
6603
6909
  const p = cachePath(cfg, repoRoot, key);
6604
- if (!(0, import_node_fs7.existsSync)(p)) return null;
6910
+ if (!(0, import_node_fs8.existsSync)(p)) return null;
6605
6911
  const ttlS = cfg.ttl_seconds ?? 604800;
6606
6912
  try {
6607
- const st = (0, import_node_fs7.statSync)(p);
6913
+ const st = (0, import_node_fs8.statSync)(p);
6608
6914
  if (ttlS > 0) {
6609
6915
  const now = nowMs ?? Date.now();
6610
6916
  if ((now - st.mtimeMs) / 1e3 > ttlS) return null;
6611
6917
  }
6612
- const data = JSON.parse((0, import_node_fs7.readFileSync)(p, "utf8"));
6918
+ const data = JSON.parse((0, import_node_fs8.readFileSync)(p, "utf8"));
6613
6919
  try {
6614
6920
  const t = (nowMs ?? Date.now()) / 1e3;
6615
- (0, import_node_fs7.utimesSync)(p, t, t);
6921
+ (0, import_node_fs8.utimesSync)(p, t, t);
6616
6922
  } catch {
6617
6923
  }
6618
6924
  return data;
@@ -6620,11 +6926,11 @@ function getNodeCache(cfg, repoRoot, key, nowMs) {
6620
6926
  return null;
6621
6927
  }
6622
6928
  }
6623
- function atomicWriteJson(path12, payload) {
6624
- (0, import_node_fs7.mkdirSync)((0, import_node_path8.dirname)(path12), { recursive: true });
6625
- const tmp = `${path12}.${process.pid}.${Date.now()}.tmp`;
6626
- (0, import_node_fs7.writeFileSync)(tmp, JSON.stringify(payload), "utf8");
6627
- (0, import_node_fs7.renameSync)(tmp, path12);
6929
+ function atomicWriteJson(path14, payload) {
6930
+ (0, import_node_fs8.mkdirSync)((0, import_node_path8.dirname)(path14), { recursive: true });
6931
+ const tmp = `${path14}.${process.pid}.${Date.now()}.tmp`;
6932
+ (0, import_node_fs8.writeFileSync)(tmp, JSON.stringify(payload), "utf8");
6933
+ (0, import_node_fs8.renameSync)(tmp, path14);
6628
6934
  }
6629
6935
  function putNodeCache(cfg, repoRoot, key, value) {
6630
6936
  if (!cfg || cfg.enabled === false) return;
@@ -6635,23 +6941,23 @@ function putNodeCache(cfg, repoRoot, key, value) {
6635
6941
  const maxBytes = cfg.max_cache_bytes ?? 100 * 1024 * 1024;
6636
6942
  if (maxEntries <= 0 && maxBytes <= 0) return;
6637
6943
  const base = resolveCacheDir(cfg, repoRoot);
6638
- if (!(0, import_node_fs7.existsSync)(base)) return;
6944
+ if (!(0, import_node_fs8.existsSync)(base)) return;
6639
6945
  const entries = [];
6640
6946
  let totalBytes = 0;
6641
- for (const shard of (0, import_node_fs7.readdirSync)(base)) {
6947
+ for (const shard of (0, import_node_fs8.readdirSync)(base)) {
6642
6948
  const shardDir = (0, import_node_path8.join)(base, shard);
6643
6949
  let stShard;
6644
6950
  try {
6645
- stShard = (0, import_node_fs7.statSync)(shardDir);
6951
+ stShard = (0, import_node_fs8.statSync)(shardDir);
6646
6952
  } catch {
6647
6953
  continue;
6648
6954
  }
6649
6955
  if (!stShard.isDirectory()) continue;
6650
- for (const f of (0, import_node_fs7.readdirSync)(shardDir)) {
6956
+ for (const f of (0, import_node_fs8.readdirSync)(shardDir)) {
6651
6957
  if (!f.endsWith(".json")) continue;
6652
6958
  const fp = (0, import_node_path8.join)(shardDir, f);
6653
6959
  try {
6654
- const st = (0, import_node_fs7.statSync)(fp);
6960
+ const st = (0, import_node_fs8.statSync)(fp);
6655
6961
  entries.push({ mtime: st.mtimeMs, size: st.size, path: fp });
6656
6962
  totalBytes += st.size;
6657
6963
  } catch {
@@ -6665,7 +6971,7 @@ function putNodeCache(cfg, repoRoot, key, value) {
6665
6971
  while (entries.length > 0 && (maxEntries > 0 && entries.length > maxEntries || maxBytes > 0 && totalBytes > maxBytes)) {
6666
6972
  const oldest = entries.shift();
6667
6973
  try {
6668
- (0, import_node_fs7.unlinkSync)(oldest.path);
6974
+ (0, import_node_fs8.unlinkSync)(oldest.path);
6669
6975
  totalBytes -= oldest.size;
6670
6976
  } catch {
6671
6977
  }
@@ -7227,8 +7533,19 @@ NODE OUTPUTS:
7227
7533
  ${recombineLines.join("\n\n")}
7228
7534
 
7229
7535
  Synthesize the node outputs into a single coherent deliverable. Preserve any [MISSING: ...] markers verbatim where the upstream node failed so the caller sees what is incomplete.`;
7536
+ const persona = buildPersonaInjection({
7537
+ toolName: "orchestrate",
7538
+ prompt: goal ?? recombinePrompt,
7539
+ ...typeof args["persona"] === "string" ? { explicit: args["persona"] } : {},
7540
+ ...opts.repoRoot ? { repoRoot: opts.repoRoot } : {},
7541
+ sessionId: typeof args["session_id"] === "string" ? args["session_id"] : null
7542
+ });
7543
+ const personaMeta = persona.meta;
7544
+ const recombineSys = "You are the orchestrator. Combine node outputs into the final result.";
7230
7545
  const recMsgs = [
7231
- { role: "system", content: "You are the orchestrator. Combine node outputs into the final result." },
7546
+ { role: "system", content: persona.block ? `${persona.block}
7547
+
7548
+ ${recombineSys}` : recombineSys },
7232
7549
  { role: "user", content: recombinePrompt }
7233
7550
  ];
7234
7551
  let synthProvider = moderator;
@@ -7301,6 +7618,7 @@ Synthesize the node outputs into a single coherent deliverable. Preserve any [MI
7301
7618
  fail_fast: failFast,
7302
7619
  cheap_mode: cheapMode
7303
7620
  };
7621
+ if (personaMeta.used !== null) result["persona"] = personaMeta;
7304
7622
  if (synthModel) result["synth_model"] = synthModel;
7305
7623
  if (synthErr) result["synth_error"] = synthErr;
7306
7624
  if (plannerErrors.length > 0) result["planner_errors"] = plannerErrors;
@@ -7692,7 +8010,7 @@ function pyRound6(x) {
7692
8010
  // src/tools/fetch.ts
7693
8011
  init_cjs_shims();
7694
8012
  var import_node_crypto5 = require("crypto");
7695
- var import_node_fs8 = require("fs");
8013
+ var import_node_fs9 = require("fs");
7696
8014
  var import_node_path9 = __toESM(require("path"), 1);
7697
8015
  var DEFAULT_CONFIG = {
7698
8016
  enabled: true,
@@ -7774,9 +8092,9 @@ async function runFetch(args, opts) {
7774
8092
  const evDir = resolveEvidenceDir(cfg.evidence_dir, opts.repoRoot);
7775
8093
  const urlHash16 = sha256Hex2(url).slice(0, 16);
7776
8094
  const metaPath = import_node_path9.default.join(evDir, `by-url-${urlHash16}.json`);
7777
- if ((0, import_node_fs8.existsSync)(metaPath) && !force) {
8095
+ if ((0, import_node_fs9.existsSync)(metaPath) && !force) {
7778
8096
  try {
7779
- const meta2 = JSON.parse((0, import_node_fs8.readFileSync)(metaPath, "utf8"));
8097
+ const meta2 = JSON.parse((0, import_node_fs9.readFileSync)(metaPath, "utf8"));
7780
8098
  return {
7781
8099
  ...base,
7782
8100
  accepted: true,
@@ -7816,15 +8134,41 @@ async function runFetch(args, opts) {
7816
8134
  }
7817
8135
  let data;
7818
8136
  try {
7819
- const buf = await response.arrayBuffer();
7820
- if (buf.byteLength > cfg.max_bytes) {
7821
- return {
7822
- ...base,
7823
- accepted: false,
7824
- reason: `response exceeds max_bytes=${cfg.max_bytes}`
7825
- };
8137
+ const reader = response.body?.getReader?.();
8138
+ if (!reader) {
8139
+ const buf = await response.arrayBuffer();
8140
+ if (buf.byteLength > cfg.max_bytes) {
8141
+ return { ...base, accepted: false, reason: `response exceeds max_bytes=${cfg.max_bytes}` };
8142
+ }
8143
+ data = new Uint8Array(buf);
8144
+ } else {
8145
+ const chunks = [];
8146
+ let total = 0;
8147
+ let exceeded = false;
8148
+ for (; ; ) {
8149
+ const { done, value } = await reader.read();
8150
+ if (done) break;
8151
+ if (value) {
8152
+ total += value.byteLength;
8153
+ if (total > cfg.max_bytes) {
8154
+ exceeded = true;
8155
+ await reader.cancel().catch(() => {
8156
+ });
8157
+ break;
8158
+ }
8159
+ chunks.push(value);
8160
+ }
8161
+ }
8162
+ if (exceeded) {
8163
+ return { ...base, accepted: false, reason: `response exceeds max_bytes=${cfg.max_bytes}` };
8164
+ }
8165
+ data = new Uint8Array(total);
8166
+ let offset = 0;
8167
+ for (const c of chunks) {
8168
+ data.set(c, offset);
8169
+ offset += c.byteLength;
8170
+ }
7826
8171
  }
7827
- data = new Uint8Array(buf);
7828
8172
  } catch (e) {
7829
8173
  return {
7830
8174
  ...base,
@@ -7836,8 +8180,8 @@ async function runFetch(args, opts) {
7836
8180
  const status = response.status;
7837
8181
  const sha = sha256HexBytes(data);
7838
8182
  const bodyPath = import_node_path9.default.join(evDir, `${sha}.bin`);
7839
- (0, import_node_fs8.mkdirSync)(evDir, { recursive: true });
7840
- (0, import_node_fs8.writeFileSync)(bodyPath, data);
8183
+ (0, import_node_fs9.mkdirSync)(evDir, { recursive: true });
8184
+ (0, import_node_fs9.writeFileSync)(bodyPath, data);
7841
8185
  const relBody = makeRelative2(bodyPath, opts.repoRoot);
7842
8186
  const now = opts.nowEpochSeconds ? opts.nowEpochSeconds() : Math.floor(Date.now() / 1e3);
7843
8187
  const meta = {
@@ -7849,7 +8193,7 @@ async function runFetch(args, opts) {
7849
8193
  status,
7850
8194
  fetched_at: now
7851
8195
  };
7852
- (0, import_node_fs8.writeFileSync)(metaPath, JSON.stringify(meta, null, 2));
8196
+ (0, import_node_fs9.writeFileSync)(metaPath, JSON.stringify(meta, null, 2));
7853
8197
  if (sessionId && host && opts.storage) {
7854
8198
  try {
7855
8199
  await opts.storage.recordFetchEgress(sessionId, host, data.byteLength, now);
@@ -8132,8 +8476,8 @@ ${documentsPayload}`
8132
8476
  if (targetPath && !dryRun && typeof orchestration["final"] === "string" && orchestration["final"] && !blockWriteStatuses.has(status)) {
8133
8477
  try {
8134
8478
  const tp = import_node_path10.default.isAbsolute(targetPath) ? targetPath : opts.repoRoot ? import_node_path10.default.join(opts.repoRoot, targetPath) : import_node_path10.default.resolve(targetPath);
8135
- (0, import_node_fs9.mkdirSync)(import_node_path10.default.dirname(tp), { recursive: true });
8136
- (0, import_node_fs9.writeFileSync)(tp, orchestration["final"], "utf8");
8479
+ (0, import_node_fs10.mkdirSync)(import_node_path10.default.dirname(tp), { recursive: true });
8480
+ (0, import_node_fs10.writeFileSync)(tp, orchestration["final"], "utf8");
8137
8481
  artifacts.push({ path: targetPath, bytes: orchestration["final"].length });
8138
8482
  if (status === "audit_failed" || status === "audit_inconclusive") {
8139
8483
  warnings.push(
@@ -8203,7 +8547,7 @@ async function ingestDocuments(documents, sessionId, opts) {
8203
8547
  if (typeof evidencePath === "string" && evidencePath) {
8204
8548
  const abs = import_node_path10.default.isAbsolute(evidencePath) ? evidencePath : opts.repoRoot ? import_node_path10.default.join(opts.repoRoot, evidencePath) : evidencePath;
8205
8549
  try {
8206
- text = (0, import_node_fs9.readFileSync)(abs, "utf8");
8550
+ text = (0, import_node_fs10.readFileSync)(abs, "utf8");
8207
8551
  } catch {
8208
8552
  text = "";
8209
8553
  }
@@ -8217,7 +8561,7 @@ async function ingestDocuments(documents, sessionId, opts) {
8217
8561
  }, text));
8218
8562
  } else {
8219
8563
  const abs = import_node_path10.default.isAbsolute(ref) ? ref : opts.repoRoot ? import_node_path10.default.join(opts.repoRoot, ref) : import_node_path10.default.resolve(ref);
8220
- if (!(0, import_node_fs9.existsSync)(abs)) {
8564
+ if (!(0, import_node_fs10.existsSync)(abs)) {
8221
8565
  out.push({
8222
8566
  source: ref,
8223
8567
  type: "file",
@@ -8229,7 +8573,7 @@ async function ingestDocuments(documents, sessionId, opts) {
8229
8573
  }
8230
8574
  let text = "";
8231
8575
  try {
8232
- text = (0, import_node_fs9.readFileSync)(abs, "utf8");
8576
+ text = (0, import_node_fs10.readFileSync)(abs, "utf8");
8233
8577
  } catch (e) {
8234
8578
  out.push({
8235
8579
  source: ref,
@@ -9244,15 +9588,27 @@ ${prior}` });
9244
9588
  let synthesis = null;
9245
9589
  let synthesisStructured = null;
9246
9590
  let synthesisErrors = [];
9591
+ let personaMeta = null;
9247
9592
  if (moderator) {
9248
9593
  const condensed = transcript.map(
9249
9594
  (e) => `[${e.provider} \u2014 round ${e.round}]
9250
9595
  ${e.response ?? "(error)"}`
9251
9596
  ).join("\n\n");
9597
+ const persona = buildPersonaInjection({
9598
+ toolName: "debate",
9599
+ prompt: topic,
9600
+ ...typeof args["persona"] === "string" ? { explicit: args["persona"] } : {},
9601
+ ...opts.repoRoot ? { repoRoot: opts.repoRoot } : {},
9602
+ sessionId: typeof args["session_id"] === "string" ? args["session_id"] : null
9603
+ });
9604
+ personaMeta = persona.meta;
9605
+ const synthSys = "You are the moderator. Synthesise the debate into a single grounded recommendation.";
9252
9606
  const synthMessages = [
9253
9607
  {
9254
9608
  role: "system",
9255
- content: "You are the moderator. Synthesise the debate into a single grounded recommendation."
9609
+ content: persona.block ? `${persona.block}
9610
+
9611
+ ${synthSys}` : synthSys
9256
9612
  },
9257
9613
  {
9258
9614
  role: "user",
@@ -9304,6 +9660,7 @@ ${condensed}`
9304
9660
  transcript,
9305
9661
  synthesis
9306
9662
  };
9663
+ if (personaMeta && personaMeta.used !== null) result["persona"] = personaMeta;
9307
9664
  if (synthesisStructured !== null) {
9308
9665
  result["synthesis_structured"] = synthesisStructured;
9309
9666
  }
@@ -9618,7 +9975,7 @@ function pyListRepr2(xs) {
9618
9975
 
9619
9976
  // src/tools/explain.ts
9620
9977
  init_cjs_shims();
9621
- var import_node_fs10 = require("fs");
9978
+ var import_node_fs11 = require("fs");
9622
9979
  var import_node_path11 = __toESM(require("path"), 1);
9623
9980
  async function runExplain(args, opts) {
9624
9981
  const sessionId = args["session_id"];
@@ -9720,10 +10077,10 @@ async function runExplain(args, opts) {
9720
10077
  return result;
9721
10078
  }
9722
10079
  function loadTranscriptsForSession(dir, sessionId) {
9723
- if (!(0, import_node_fs10.existsSync)(dir)) return [];
10080
+ if (!(0, import_node_fs11.existsSync)(dir)) return [];
9724
10081
  let names;
9725
10082
  try {
9726
- names = (0, import_node_fs10.readdirSync)(dir);
10083
+ names = (0, import_node_fs11.readdirSync)(dir);
9727
10084
  } catch {
9728
10085
  return [];
9729
10086
  }
@@ -9733,13 +10090,13 @@ function loadTranscriptsForSession(dir, sessionId) {
9733
10090
  const p = import_node_path11.default.join(dir, name);
9734
10091
  let stat;
9735
10092
  try {
9736
- stat = (0, import_node_fs10.statSync)(p);
10093
+ stat = (0, import_node_fs11.statSync)(p);
9737
10094
  } catch {
9738
10095
  continue;
9739
10096
  }
9740
10097
  let doc;
9741
10098
  try {
9742
- doc = JSON.parse((0, import_node_fs10.readFileSync)(p, "utf8"));
10099
+ doc = JSON.parse((0, import_node_fs11.readFileSync)(p, "utf8"));
9743
10100
  } catch {
9744
10101
  continue;
9745
10102
  }
@@ -10595,7 +10952,7 @@ function toStringArray3(v) {
10595
10952
 
10596
10953
  // src/tools/scoreboard.ts
10597
10954
  init_cjs_shims();
10598
- var import_node_fs11 = require("fs");
10955
+ var import_node_fs12 = require("fs");
10599
10956
  async function runScoreboard(args, opts) {
10600
10957
  if (!opts.storage) {
10601
10958
  if (opts.bridge && opts.bridge.toolNames.has("scoreboard")) {
@@ -10678,9 +11035,9 @@ async function runScoreboard(args, opts) {
10678
11035
  }
10679
11036
  usageTotals.cost_usd = Number(usageTotals.cost_usd.toFixed(8));
10680
11037
  let recentEvents = [];
10681
- if (recentLimit > 0 && opts.eventsPath && (0, import_node_fs11.existsSync)(opts.eventsPath)) {
11038
+ if (recentLimit > 0 && opts.eventsPath && (0, import_node_fs12.existsSync)(opts.eventsPath)) {
10682
11039
  try {
10683
- const lines = (0, import_node_fs11.readFileSync)(opts.eventsPath, "utf8").split("\n").filter((l) => l.length > 0);
11040
+ const lines = (0, import_node_fs12.readFileSync)(opts.eventsPath, "utf8").split("\n").filter((l) => l.length > 0);
10684
11041
  const tail = lines.slice(-recentLimit);
10685
11042
  for (const ln of tail) {
10686
11043
  try {
@@ -10929,6 +11286,122 @@ function pyRepr6(v) {
10929
11286
  // src/tools/solve.ts
10930
11287
  init_cjs_shims();
10931
11288
  var import_node_perf_hooks11 = require("perf_hooks");
11289
+ var import_node_fs13 = require("fs");
11290
+ var import_node_path12 = __toESM(require("path"), 1);
11291
+
11292
+ // src/core/unified-diff.ts
11293
+ init_cjs_shims();
11294
+ function diffLines(a, b) {
11295
+ const n = a.length;
11296
+ const m = b.length;
11297
+ const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));
11298
+ for (let i2 = n - 1; i2 >= 0; i2 -= 1) {
11299
+ for (let j2 = m - 1; j2 >= 0; j2 -= 1) {
11300
+ dp[i2][j2] = a[i2] === b[j2] ? dp[i2 + 1][j2 + 1] + 1 : Math.max(dp[i2 + 1][j2], dp[i2][j2 + 1]);
11301
+ }
11302
+ }
11303
+ const ops = [];
11304
+ let i = 0;
11305
+ let j = 0;
11306
+ while (i < n && j < m) {
11307
+ if (a[i] === b[j]) {
11308
+ ops.push({ type: "eq", line: a[i] });
11309
+ i += 1;
11310
+ j += 1;
11311
+ } else if (dp[i + 1][j] >= dp[i][j + 1]) {
11312
+ ops.push({ type: "del", line: a[i] });
11313
+ i += 1;
11314
+ } else {
11315
+ ops.push({ type: "ins", line: b[j] });
11316
+ j += 1;
11317
+ }
11318
+ }
11319
+ while (i < n) {
11320
+ ops.push({ type: "del", line: a[i] });
11321
+ i += 1;
11322
+ }
11323
+ while (j < m) {
11324
+ ops.push({ type: "ins", line: b[j] });
11325
+ j += 1;
11326
+ }
11327
+ return ops;
11328
+ }
11329
+ function unifiedDiff(oldText, newText, opts) {
11330
+ if (oldText === newText) return "";
11331
+ const context = Math.max(0, opts?.context ?? 3);
11332
+ const a = oldText.split("\n");
11333
+ const b = newText.split("\n");
11334
+ const ops = diffLines(a, b);
11335
+ const indexed = [];
11336
+ let ai = 0;
11337
+ let bi = 0;
11338
+ for (const op of ops) {
11339
+ if (op.type === "eq") {
11340
+ indexed.push({ ...op, aIdx: ai, bIdx: bi });
11341
+ ai += 1;
11342
+ bi += 1;
11343
+ } else if (op.type === "del") {
11344
+ indexed.push({ ...op, aIdx: ai, bIdx: bi });
11345
+ ai += 1;
11346
+ } else {
11347
+ indexed.push({ ...op, aIdx: ai, bIdx: bi });
11348
+ bi += 1;
11349
+ }
11350
+ }
11351
+ const changeIdx = indexed.map((o, k) => o.type === "eq" ? -1 : k).filter((k) => k >= 0);
11352
+ if (changeIdx.length === 0) return "";
11353
+ const groups = [];
11354
+ let gStart = changeIdx[0];
11355
+ let gEnd = changeIdx[0];
11356
+ for (let k = 1; k < changeIdx.length; k += 1) {
11357
+ const idx = changeIdx[k];
11358
+ if (idx - gEnd <= 2 * context + 1) {
11359
+ gEnd = idx;
11360
+ } else {
11361
+ groups.push([gStart, gEnd]);
11362
+ gStart = idx;
11363
+ gEnd = idx;
11364
+ }
11365
+ }
11366
+ groups.push([gStart, gEnd]);
11367
+ const lines = [];
11368
+ lines.push(`--- ${opts?.fromFile ?? "a"}`);
11369
+ lines.push(`+++ ${opts?.toFile ?? "b"}`);
11370
+ for (const [start, end] of groups) {
11371
+ const from = Math.max(0, start - context);
11372
+ const to = Math.min(indexed.length - 1, end + context);
11373
+ let aCount = 0;
11374
+ let bCount = 0;
11375
+ let aStart = -1;
11376
+ let bStart = -1;
11377
+ const body = [];
11378
+ for (let k = from; k <= to; k += 1) {
11379
+ const o = indexed[k];
11380
+ if (o.type === "eq") {
11381
+ if (aStart < 0) aStart = o.aIdx;
11382
+ if (bStart < 0) bStart = o.bIdx;
11383
+ aCount += 1;
11384
+ bCount += 1;
11385
+ body.push(` ${o.line}`);
11386
+ } else if (o.type === "del") {
11387
+ if (aStart < 0) aStart = o.aIdx;
11388
+ aCount += 1;
11389
+ body.push(`-${o.line}`);
11390
+ } else {
11391
+ if (bStart < 0) bStart = o.bIdx;
11392
+ bCount += 1;
11393
+ body.push(`+${o.line}`);
11394
+ }
11395
+ }
11396
+ const aHdr = aCount === 0 ? 0 : aStart + 1;
11397
+ const bHdr = bCount === 0 ? 0 : bStart + 1;
11398
+ lines.push(`@@ -${aHdr},${aCount} +${bHdr},${bCount} @@`);
11399
+ lines.push(...body);
11400
+ }
11401
+ return lines.join("\n") + "\n";
11402
+ }
11403
+
11404
+ // src/tools/solve.ts
10932
11405
  async function runSolve(args, opts) {
10933
11406
  const problem = typeof args["problem"] === "string" ? args["problem"] : String(args["problem"] ?? "");
10934
11407
  if (!problem) {
@@ -11070,7 +11543,23 @@ Re-emit the FULL solution. No commentary.`
11070
11543
  const targetPath = args["target_path"];
11071
11544
  if (typeof targetPath === "string" && targetPath) {
11072
11545
  result["target_path"] = targetPath;
11073
- result["patch"] = null;
11546
+ if (solved && typeof finalProposal === "string") {
11547
+ const abs = import_node_path12.default.isAbsolute(targetPath) ? targetPath : import_node_path12.default.join(opts.repoRoot ?? process.cwd(), targetPath);
11548
+ let oldText = "";
11549
+ try {
11550
+ oldText = (0, import_node_fs13.readFileSync)(abs, "utf8");
11551
+ } catch {
11552
+ oldText = "";
11553
+ }
11554
+ const newText = finalProposal.endsWith("\n") ? finalProposal : `${finalProposal}
11555
+ `;
11556
+ result["patch"] = unifiedDiff(oldText, newText, {
11557
+ fromFile: `a/${targetPath}`,
11558
+ toFile: `b/${targetPath}`
11559
+ }) || null;
11560
+ } else {
11561
+ result["patch"] = null;
11562
+ }
11074
11563
  }
11075
11564
  if (unknownNames.length > 0) result["skipped_unknown_providers"] = unknownNames;
11076
11565
  if (blocked.length > 0) result["blocked_by_allowlist"] = blocked;
@@ -11156,6 +11645,8 @@ async function verifyProposalShell(verifier, proposal) {
11156
11645
  }
11157
11646
  const argv = cmd.map((x) => String(x));
11158
11647
  const timeoutS = numberArg2(verifier["timeout_s"], 10);
11648
+ const memoryMb = numberArg2(verifier["memory_mb"], 0);
11649
+ const cpuSeconds = numberArg2(verifier["cpu_seconds"], 0);
11159
11650
  const expectExit = pyIntCoerce2(verifier["expect_exit_code"], 0);
11160
11651
  const expectContains = typeof verifier["expect_stdout_contains"] === "string" ? verifier["expect_stdout_contains"] : null;
11161
11652
  const expectRegex = typeof verifier["expect_stdout_regex"] === "string" ? verifier["expect_stdout_regex"] : null;
@@ -11165,7 +11656,9 @@ async function verifyProposalShell(verifier, proposal) {
11165
11656
  input: proposal,
11166
11657
  // proposal piped to stdin
11167
11658
  stdoutTailBytes: 2048,
11168
- stderrTailBytes: 2048
11659
+ stderrTailBytes: 2048,
11660
+ ...memoryMb > 0 ? { memoryMb } : {},
11661
+ ...cpuSeconds > 0 ? { cpuSeconds } : {}
11169
11662
  });
11170
11663
  if (r.status === "timeout") {
11171
11664
  return {
@@ -11335,24 +11828,24 @@ function pyRepr7(v) {
11335
11828
  // src/tools/update-crosscheck.ts
11336
11829
  init_cjs_shims();
11337
11830
  var import_node_child_process2 = require("child_process");
11338
- var import_node_fs13 = require("fs");
11339
- var import_node_path13 = __toESM(require("path"), 1);
11831
+ var import_node_fs15 = require("fs");
11832
+ var import_node_path14 = __toESM(require("path"), 1);
11340
11833
 
11341
11834
  // src/core/update-notify.ts
11342
11835
  init_cjs_shims();
11343
- var import_node_fs12 = require("fs");
11836
+ var import_node_fs14 = require("fs");
11344
11837
  var import_node_os2 = __toESM(require("os"), 1);
11345
- var import_node_path12 = __toESM(require("path"), 1);
11838
+ var import_node_path13 = __toESM(require("path"), 1);
11346
11839
  var NPM_REGISTRY = "https://registry.npmjs.org";
11347
11840
  var CHECK_INTERVAL_SECONDS = 3 * 24 * 60 * 60;
11348
11841
  var DEFAULT_PACKAGE = "crosscheck-cli";
11349
11842
  var FETCH_TIMEOUT_MS = 3e3;
11350
11843
  function engineVersion() {
11351
- return true ? "0.1.11" : "0.0.0-dev";
11844
+ return true ? "0.1.12" : "0.0.0-dev";
11352
11845
  }
11353
11846
  function defaultUpdateCachePath() {
11354
- const base = process.env["CROSSCHECK_DATA_DIR"] || import_node_path12.default.join(import_node_os2.default.homedir() || import_node_os2.default.tmpdir(), ".crosscheck");
11355
- return import_node_path12.default.join(base, "update_check.json");
11847
+ const base = process.env["CROSSCHECK_DATA_DIR"] || import_node_path13.default.join(import_node_os2.default.homedir() || import_node_os2.default.tmpdir(), ".crosscheck");
11848
+ return import_node_path13.default.join(base, "update_check.json");
11356
11849
  }
11357
11850
  function isNewer(latest, current) {
11358
11851
  const a = parseTriple(latest);
@@ -11382,8 +11875,8 @@ then reconnect the crosscheck MCP server (run \`/mcp\` in Claude Code, or restar
11382
11875
  }
11383
11876
  function readCache(cachePath2) {
11384
11877
  try {
11385
- if (!(0, import_node_fs12.existsSync)(cachePath2)) return null;
11386
- const data = JSON.parse((0, import_node_fs12.readFileSync)(cachePath2, "utf8"));
11878
+ if (!(0, import_node_fs14.existsSync)(cachePath2)) return null;
11879
+ const data = JSON.parse((0, import_node_fs14.readFileSync)(cachePath2, "utf8"));
11387
11880
  if (data && typeof data === "object" && !Array.isArray(data)) {
11388
11881
  return data;
11389
11882
  }
@@ -11394,8 +11887,8 @@ function readCache(cachePath2) {
11394
11887
  }
11395
11888
  function writeCache(cachePath2, record2) {
11396
11889
  try {
11397
- (0, import_node_fs12.mkdirSync)(import_node_path12.default.dirname(cachePath2), { recursive: true });
11398
- (0, import_node_fs12.writeFileSync)(cachePath2, JSON.stringify(record2, null, 2), "utf8");
11890
+ (0, import_node_fs14.mkdirSync)(import_node_path13.default.dirname(cachePath2), { recursive: true });
11891
+ (0, import_node_fs14.writeFileSync)(cachePath2, JSON.stringify(record2, null, 2), "utf8");
11399
11892
  } catch {
11400
11893
  }
11401
11894
  }
@@ -11448,7 +11941,7 @@ async function runUpdateCrosscheck(args, opts) {
11448
11941
  const gitRun = opts.gitRun ?? defaultGitRun(opts.repoRoot);
11449
11942
  const fetchFn = opts.httpFetch ?? globalThis.fetch;
11450
11943
  const now = opts.nowEpochSeconds ? opts.nowEpochSeconds() : Math.floor(Date.now() / 1e3);
11451
- const cachePath2 = opts.cachePath ?? import_node_path13.default.join(opts.repoRoot, ".crosscheck", "update_check.json");
11944
+ const cachePath2 = opts.cachePath ?? import_node_path14.default.join(opts.repoRoot, ".crosscheck", "update_check.json");
11452
11945
  const local = readLocalSha(gitRun);
11453
11946
  if (!local) {
11454
11947
  return {
@@ -11620,8 +12113,8 @@ function gitRelationship(gitRun, local, remote) {
11620
12113
  }
11621
12114
  function writeUpdateCache(p, payload) {
11622
12115
  try {
11623
- (0, import_node_fs13.mkdirSync)(import_node_path13.default.dirname(p), { recursive: true });
11624
- (0, import_node_fs13.writeFileSync)(p, JSON.stringify(payload, null, 2), "utf8");
12116
+ (0, import_node_fs15.mkdirSync)(import_node_path14.default.dirname(p), { recursive: true });
12117
+ (0, import_node_fs15.writeFileSync)(p, JSON.stringify(payload, null, 2), "utf8");
11625
12118
  } catch {
11626
12119
  }
11627
12120
  }
@@ -12636,8 +13129,8 @@ function pingTool() {
12636
13129
 
12637
13130
  // src/core/wrappers.ts
12638
13131
  init_cjs_shims();
12639
- var import_node_fs14 = require("fs");
12640
- var import_node_path14 = __toESM(require("path"), 1);
13132
+ var import_node_fs16 = require("fs");
13133
+ var import_node_path15 = __toESM(require("path"), 1);
12641
13134
  function configPinGate(toolName, opts) {
12642
13135
  if (toolName === "config_pin") return null;
12643
13136
  if (!opts || !opts.repoRoot) return null;
@@ -12669,8 +13162,8 @@ function configPinGate(toolName, opts) {
12669
13162
  var cachedUpdateNotice = null;
12670
13163
  function readUpdateCache(cachePath2) {
12671
13164
  try {
12672
- if (!(0, import_node_fs14.existsSync)(cachePath2)) return null;
12673
- const data = JSON.parse((0, import_node_fs14.readFileSync)(cachePath2, "utf8"));
13165
+ if (!(0, import_node_fs16.existsSync)(cachePath2)) return null;
13166
+ const data = JSON.parse((0, import_node_fs16.readFileSync)(cachePath2, "utf8"));
12674
13167
  if (data && typeof data === "object" && !Array.isArray(data)) return data;
12675
13168
  return null;
12676
13169
  } catch {
@@ -12682,7 +13175,7 @@ function attachUpdateNotice(result, toolName, opts) {
12682
13175
  if (toolName === "update_crosscheck") return;
12683
13176
  if ("update_notice" in result) return;
12684
13177
  if (cachedUpdateNotice === null) {
12685
- const cachePath2 = opts?.cachePath ?? (opts?.repoRoot ? import_node_path14.default.join(opts.repoRoot, ".crosscheck", "update_check.json") : defaultUpdateCachePath());
13178
+ const cachePath2 = opts?.cachePath ?? (opts?.repoRoot ? import_node_path15.default.join(opts.repoRoot, ".crosscheck", "update_check.json") : defaultUpdateCachePath());
12686
13179
  const cached = readUpdateCache(cachePath2);
12687
13180
  if (cached && cached.update_available && cached.notice && typeof cached.notice === "object") {
12688
13181
  cachedUpdateNotice = cached.notice;
@@ -12880,7 +13373,7 @@ async function flush() {
12880
13373
 
12881
13374
  // src/server.ts
12882
13375
  var SERVER_NAME = "crosscheck-agent";
12883
- var SERVER_VERSION = true ? "0.1.11" : "0.0.0-dev";
13376
+ var SERVER_VERSION = true ? "0.1.12" : "0.0.0-dev";
12884
13377
  function extractRunSummaryText(out) {
12885
13378
  if (out === null || typeof out !== "object" || Array.isArray(out)) return void 0;
12886
13379
  const rs = out["run_summary"];
@@ -12902,14 +13395,14 @@ function createServer(opts = {}) {
12902
13395
  }
12903
13396
  );
12904
13397
  const tools = buildToolRegistry(opts);
12905
- server.setRequestHandler(import_types6.ListToolsRequestSchema, async () => ({
13398
+ server.setRequestHandler(import_types8.ListToolsRequestSchema, async () => ({
12906
13399
  tools: Array.from(tools.values()).map((t) => ({
12907
13400
  name: t.name,
12908
13401
  description: t.description,
12909
13402
  inputSchema: t.inputSchema
12910
13403
  }))
12911
13404
  }));
12912
- server.setRequestHandler(import_types6.CallToolRequestSchema, async (req) => {
13405
+ server.setRequestHandler(import_types8.CallToolRequestSchema, async (req) => {
12913
13406
  const name = req.params.name;
12914
13407
  const tool = tools.get(name);
12915
13408
  if (!tool) {
@@ -13033,6 +13526,10 @@ function buildToolRegistry(opts) {
13033
13526
  if (opts.nodeCache) registerOpts.nodeCache = opts.nodeCache;
13034
13527
  if (opts.configPinning) registerOpts.configPinning = opts.configPinning;
13035
13528
  if (opts.rejectConfigDriftEnv) registerOpts.rejectConfigDriftEnv = opts.rejectConfigDriftEnv;
13529
+ if (opts.fetchConfig) registerOpts.fetchConfig = opts.fetchConfig;
13530
+ if (opts.moderatorDefault) registerOpts.moderatorDefault = opts.moderatorDefault;
13531
+ if (opts.benchGoldensDir) registerOpts.benchGoldensDir = opts.benchGoldensDir;
13532
+ if (opts.eventsPath) registerOpts.eventsPath = opts.eventsPath;
13036
13533
  const tools = registerCoreTools(registerOpts);
13037
13534
  if (!opts.bridge) return tools;
13038
13535
  const proxies = buildPythonProxies(opts.bridge);
@@ -13042,29 +13539,207 @@ function buildToolRegistry(opts) {
13042
13539
  return tools;
13043
13540
  }
13044
13541
 
13542
+ // src/core/config-file.ts
13543
+ init_cjs_shims();
13544
+ var import_node_fs17 = require("fs");
13545
+ var import_node_path16 = __toESM(require("path"), 1);
13546
+ var import_node_crypto8 = require("crypto");
13547
+ function asObj(v) {
13548
+ return v && typeof v === "object" && !Array.isArray(v) ? v : void 0;
13549
+ }
13550
+ function num(v) {
13551
+ const n = Number(v);
13552
+ return Number.isFinite(n) ? n : void 0;
13553
+ }
13554
+ function str(v) {
13555
+ return typeof v === "string" && v.length > 0 ? v : void 0;
13556
+ }
13557
+ function bool(v) {
13558
+ return typeof v === "boolean" ? v : void 0;
13559
+ }
13560
+ function strArr(v) {
13561
+ return Array.isArray(v) ? v.filter((x) => typeof x === "string") : void 0;
13562
+ }
13563
+ function pickNums(src, keys) {
13564
+ const out = {};
13565
+ for (const k of keys) {
13566
+ const n = num(src[k]);
13567
+ if (n !== void 0) out[k] = n;
13568
+ }
13569
+ return out;
13570
+ }
13571
+ function findConfigFile(opts) {
13572
+ const direct = opts.explicitPath ?? process.env["CROSSCHECK_CONFIG_PATH"];
13573
+ if (direct) return (0, import_node_fs17.existsSync)(direct) ? direct : void 0;
13574
+ const candidates = [];
13575
+ let dir = opts.startDir;
13576
+ for (let i = 0; dir && i < 8; i += 1) {
13577
+ candidates.push(import_node_path16.default.join(dir, "crosscheck.config.json"));
13578
+ const parent = import_node_path16.default.dirname(dir);
13579
+ if (parent === dir) break;
13580
+ dir = parent;
13581
+ }
13582
+ if (opts.repoRoot) candidates.push(import_node_path16.default.join(opts.repoRoot, "crosscheck.config.json"));
13583
+ if (opts.dataDir) candidates.push(import_node_path16.default.join(opts.dataDir, "crosscheck.config.json"));
13584
+ return candidates.find((p) => (0, import_node_fs17.existsSync)(p));
13585
+ }
13586
+ function mapConfig(raw, sourcePath) {
13587
+ const result = { sourcePath };
13588
+ const cb = asObj(raw["circuit_breakers"]);
13589
+ if (cb) {
13590
+ const picked = pickNums(cb, [
13591
+ "max_session_cost_usd",
13592
+ "max_session_tokens",
13593
+ "max_session_wall_seconds",
13594
+ "max_dag_nodes",
13595
+ "max_dag_depth"
13596
+ ]);
13597
+ if (Object.keys(picked).length > 0) result.circuitBreakers = picked;
13598
+ }
13599
+ const nc = asObj(raw["node_cache"]);
13600
+ if (nc) {
13601
+ const cfg = {
13602
+ ...pickNums(nc, ["ttl_seconds", "max_entries", "max_cache_bytes"])
13603
+ };
13604
+ if (bool(nc["enabled"]) !== void 0) cfg.enabled = bool(nc["enabled"]);
13605
+ if (str(nc["dir"]) !== void 0) cfg.dir = str(nc["dir"]);
13606
+ result.nodeCache = cfg;
13607
+ }
13608
+ const f = asObj(raw["fetch"]);
13609
+ if (f) {
13610
+ const cfg = {
13611
+ ...pickNums(f, [
13612
+ "max_bytes",
13613
+ "max_bytes_per_session",
13614
+ "max_unique_hosts_per_session",
13615
+ "timeout_s"
13616
+ ])
13617
+ };
13618
+ if (bool(f["enabled"]) !== void 0) cfg.enabled = bool(f["enabled"]);
13619
+ if (strArr(f["url_allowlist"]) !== void 0) cfg.url_allowlist = strArr(f["url_allowlist"]);
13620
+ if (str(f["evidence_dir"]) !== void 0) cfg.evidence_dir = str(f["evidence_dir"]);
13621
+ result.fetchConfig = cfg;
13622
+ }
13623
+ if ("provider_allowlist" in raw) {
13624
+ const pa = raw["provider_allowlist"];
13625
+ if (pa === null) result.providerAllowlist = null;
13626
+ else if (strArr(pa) !== void 0) result.providerAllowlist = strArr(pa);
13627
+ }
13628
+ const moderator = str(raw["moderator"]);
13629
+ if (moderator) result.moderatorDefault = moderator.toLowerCase();
13630
+ const bench = asObj(raw["bench"]);
13631
+ const goldens = bench ? str(bench["goldens_dir"]) : void 0;
13632
+ if (goldens) result.benchGoldensDir = goldens;
13633
+ const td = str(raw["transcript_dir"]);
13634
+ if (td) result.transcriptsDir = td;
13635
+ const ev = str(raw["events_log"]);
13636
+ if (ev) result.eventsPath = ev;
13637
+ const db = str(raw["session_db"]);
13638
+ if (db) result.dbPath = db;
13639
+ const lt = bool(raw["log_transcripts"]);
13640
+ if (lt !== void 0) result.logTranscripts = lt;
13641
+ const retries = asObj(raw["retries"]);
13642
+ if (retries) {
13643
+ const maxAttempts = num(retries["max_attempts"]);
13644
+ const baseS = num(retries["backoff_base_s"]);
13645
+ const cfg = {};
13646
+ if (maxAttempts !== void 0) cfg.maxAttempts = Math.max(1, Math.trunc(maxAttempts));
13647
+ if (baseS !== void 0) cfg.backoffBaseMs = Math.round(baseS * 1e3);
13648
+ if (Object.keys(cfg).length > 0) setRetryConfig(cfg);
13649
+ }
13650
+ const rl = asObj(raw["rate_limits"]);
13651
+ if (rl) {
13652
+ const mapped = {};
13653
+ for (const [provider, spec] of Object.entries(rl)) {
13654
+ const s = asObj(spec);
13655
+ const capacity = s ? num(s["capacity"]) : void 0;
13656
+ const refill = s ? num(s["refill_per_sec"]) : void 0;
13657
+ if (capacity !== void 0 && refill !== void 0) {
13658
+ mapped[provider] = { capacity, refillPerSec: refill };
13659
+ }
13660
+ }
13661
+ if (Object.keys(mapped).length > 0) setRateLimitConfig(mapped);
13662
+ }
13663
+ const tb = asObj(raw["token_budgets"]);
13664
+ if (tb) {
13665
+ const budgets = {};
13666
+ for (const [purpose, v] of Object.entries(tb)) {
13667
+ const n = num(v);
13668
+ if (n !== void 0 && n > 0) budgets[purpose] = Math.trunc(n);
13669
+ }
13670
+ if (Object.keys(budgets).length > 0) setOperatorBudgets({ token_budgets: budgets });
13671
+ }
13672
+ const red = asObj(raw["redaction"]);
13673
+ if (red) {
13674
+ const cfg = {};
13675
+ if (bool(red["enabled"]) !== void 0) cfg.enabled = bool(red["enabled"]);
13676
+ const extras = strArr(red["patterns_extra"]);
13677
+ if (extras && extras.length > 0) cfg.patterns_extra = extras;
13678
+ if (bool(red["hmac_tokens"]) === true) {
13679
+ cfg.hmac_tokens = true;
13680
+ cfg.hmac_secret = (0, import_node_crypto8.randomBytes)(32);
13681
+ }
13682
+ if (Object.keys(cfg).length > 0) setRedactionConfig(cfg);
13683
+ }
13684
+ const personas = asObj(raw["personas"]);
13685
+ if (personas) {
13686
+ const cfg = {};
13687
+ if (bool(personas["enabled"]) !== void 0) cfg.enabled = bool(personas["enabled"]);
13688
+ if (str(personas["dir"]) !== void 0) cfg.dir = str(personas["dir"]);
13689
+ if (Object.keys(cfg).length > 0) setPersonaConfig(cfg);
13690
+ }
13691
+ return result;
13692
+ }
13693
+ function loadConfigFile(opts) {
13694
+ const file = findConfigFile(opts);
13695
+ if (!file) return null;
13696
+ let raw;
13697
+ try {
13698
+ raw = JSON.parse((0, import_node_fs17.readFileSync)(file, "utf8"));
13699
+ } catch (e) {
13700
+ try {
13701
+ process.stderr.write(
13702
+ `crosscheck-agent: failed to parse ${file} (${e.message}); using defaults
13703
+ `
13704
+ );
13705
+ } catch {
13706
+ }
13707
+ return null;
13708
+ }
13709
+ const obj = asObj(raw);
13710
+ if (!obj) return null;
13711
+ return mapConfig(obj, file);
13712
+ }
13713
+
13045
13714
  // src/entrypoints/node-stdio.ts
13046
13715
  var SHUTDOWN_TIMEOUT_MS = 2e3;
13047
13716
  async function main() {
13717
+ const cfgDataDir = process.env["CROSSCHECK_DATA_DIR"] || import_node_path17.default.join(process.env["HOME"] ?? process.env["USERPROFILE"] ?? "", ".crosscheck");
13718
+ const configFile = loadConfigFile({
13719
+ startDir: process.cwd(),
13720
+ repoRoot: findGitRoot(__dirname),
13721
+ dataDir: cfgDataDir
13722
+ });
13723
+ if (configFile) {
13724
+ if (configFile.eventsPath && !process.env["CROSSCHECK_EVENTS_PATH"]) {
13725
+ process.env["CROSSCHECK_EVENTS_PATH"] = configFile.eventsPath;
13726
+ }
13727
+ if (configFile.dbPath && !process.env["CROSSCHECK_DB_PATH"]) {
13728
+ process.env["CROSSCHECK_DB_PATH"] = configFile.dbPath;
13729
+ }
13730
+ process.stderr.write(`crosscheck-agent: loaded config from ${configFile.sourcePath}
13731
+ `);
13732
+ }
13048
13733
  setEventEmitter(buildDefaultEmitter(process.env));
13049
- let bridge;
13050
13734
  if (process.env["CROSSCHECK_BRIDGE_PYTHON"] === "1") {
13051
- bridge = await spawnPythonBridge({
13052
- ...process.env["CROSSCHECK_PYTHON_PATH"] ? { pythonPath: process.env["CROSSCHECK_PYTHON_PATH"] } : {},
13053
- ...process.env["CROSSCHECK_PYTHON_SERVER"] ? { serverPath: process.env["CROSSCHECK_PYTHON_SERVER"] } : {}
13054
- });
13055
13735
  process.stderr.write(
13056
- `crosscheck-agent: bridge online (pid=${bridge.pid ?? "?"}); proxying ${bridge.toolNames.size} Python tool(s)
13057
- `
13058
- );
13059
- } else {
13060
- process.stderr.write(
13061
- "crosscheck-agent: native-only mode (set CROSSCHECK_BRIDGE_PYTHON=1 to enable the optional Python backstop)\n"
13736
+ "crosscheck-agent: CROSSCHECK_BRIDGE_PYTHON is deprecated and ignored \u2014 the engine is native-only.\n"
13062
13737
  );
13063
13738
  }
13064
- installShutdownHandlers(bridge);
13739
+ installShutdownHandlers();
13065
13740
  const bundledPricing = (() => {
13066
13741
  try {
13067
- return import_node_path15.default.join(import_node_path15.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl)), "pricing.json");
13742
+ return import_node_path17.default.join(import_node_path17.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl)), "pricing.json");
13068
13743
  } catch {
13069
13744
  return void 0;
13070
13745
  }
@@ -13073,7 +13748,7 @@ async function main() {
13073
13748
  process.env["CROSSCHECK_PRICING_PATH"],
13074
13749
  resolveRepoFile("config/pricing.json"),
13075
13750
  bundledPricing
13076
- ].find((p) => p && (0, import_node_fs15.existsSync)(p));
13751
+ ].find((p) => p && (0, import_node_fs18.existsSync)(p));
13077
13752
  const pricing = pricingPath ? loadPricing(pricingPath) : {};
13078
13753
  const providers = buildProviders({ env: process.env, pricing });
13079
13754
  if (Object.keys(providers).length > 0) {
@@ -13087,7 +13762,7 @@ async function main() {
13087
13762
  const dbPath = process.env["CROSSCHECK_DB_PATH"] ?? resolveRepoFile(".crosscheck/db.sqlite");
13088
13763
  if (dbPath) {
13089
13764
  try {
13090
- (0, import_node_fs15.mkdirSync)(import_node_path15.default.dirname(dbPath), { recursive: true });
13765
+ (0, import_node_fs18.mkdirSync)(import_node_path17.default.dirname(dbPath), { recursive: true });
13091
13766
  const { openBetterSqliteStorage: openBetterSqliteStorage2 } = await Promise.resolve().then(() => (init_better_sqlite3(), better_sqlite3_exports));
13092
13767
  storage = openBetterSqliteStorage2({ path: dbPath });
13093
13768
  await storage.migrate();
@@ -13104,33 +13779,32 @@ async function main() {
13104
13779
  }
13105
13780
  }
13106
13781
  }
13107
- const transcriptsDir = process.env["CROSSCHECK_TRANSCRIPTS_DIR"] ?? resolveRepoFile(".crosscheck/transcripts");
13782
+ const transcriptsDir = configFile?.logTranscripts === false ? void 0 : process.env["CROSSCHECK_TRANSCRIPTS_DIR"] ?? configFile?.transcriptsDir ?? resolveRepoFile(".crosscheck/transcripts");
13108
13783
  const repoRoot = process.env["CROSSCHECK_REPO_ROOT"] ?? findGitRoot(__dirname);
13109
- const transport = new import_stdio2.StdioServerTransport();
13784
+ const transport = new import_stdio.StdioServerTransport();
13110
13785
  const serverOpts = { providers };
13111
- if (bridge) serverOpts.bridge = bridge;
13112
13786
  if (storage) serverOpts.storage = storage;
13113
13787
  if (transcriptsDir) serverOpts.transcriptsDir = transcriptsDir;
13114
13788
  if (repoRoot) serverOpts.repoRoot = repoRoot;
13115
13789
  if (pricing && Object.keys(pricing).length > 0) {
13116
13790
  serverOpts.pricing = pricing;
13117
13791
  }
13792
+ if (configFile?.circuitBreakers) serverOpts.circuitBreakers = configFile.circuitBreakers;
13793
+ if (configFile?.nodeCache) serverOpts.nodeCache = configFile.nodeCache;
13794
+ if (configFile?.fetchConfig) serverOpts.fetchConfig = configFile.fetchConfig;
13795
+ if (configFile?.providerAllowlist !== void 0) {
13796
+ serverOpts.providerAllowlist = configFile.providerAllowlist;
13797
+ }
13798
+ if (configFile?.moderatorDefault) serverOpts.moderatorDefault = configFile.moderatorDefault;
13799
+ if (configFile?.benchGoldensDir) serverOpts.benchGoldensDir = configFile.benchGoldensDir;
13800
+ if (configFile?.eventsPath) serverOpts.eventsPath = configFile.eventsPath;
13118
13801
  await connectAndServe(transport, serverOpts);
13119
13802
  }
13120
- function installShutdownHandlers(bridge) {
13803
+ function installShutdownHandlers() {
13121
13804
  let shuttingDown = false;
13122
13805
  const shutdown = async (reason) => {
13123
13806
  if (shuttingDown) return;
13124
13807
  shuttingDown = true;
13125
- if (bridge) {
13126
- await Promise.race([
13127
- bridge.close(),
13128
- new Promise(
13129
- (resolve2) => setTimeout(resolve2, SHUTDOWN_TIMEOUT_MS)
13130
- )
13131
- ]).catch(() => {
13132
- });
13133
- }
13134
13808
  await Promise.race([
13135
13809
  flush(),
13136
13810
  new Promise((resolve2) => setTimeout(resolve2, SHUTDOWN_TIMEOUT_MS))
@@ -13156,9 +13830,9 @@ function installShutdownHandlers(bridge) {
13156
13830
  function resolveRepoFile(rel) {
13157
13831
  let dir = __dirname;
13158
13832
  for (let i = 0; i < 8; i++) {
13159
- const candidate = import_node_path15.default.join(dir, rel);
13160
- if ((0, import_node_fs15.existsSync)(candidate)) return candidate;
13161
- const parent = import_node_path15.default.dirname(dir);
13833
+ const candidate = import_node_path17.default.join(dir, rel);
13834
+ if ((0, import_node_fs18.existsSync)(candidate)) return candidate;
13835
+ const parent = import_node_path17.default.dirname(dir);
13162
13836
  if (parent === dir) break;
13163
13837
  dir = parent;
13164
13838
  }
@@ -13167,8 +13841,8 @@ function resolveRepoFile(rel) {
13167
13841
  function findGitRoot(startDir) {
13168
13842
  let dir = startDir;
13169
13843
  for (let i = 0; i < 12; i++) {
13170
- if ((0, import_node_fs15.existsSync)(import_node_path15.default.join(dir, ".git"))) return dir;
13171
- const parent = import_node_path15.default.dirname(dir);
13844
+ if ((0, import_node_fs18.existsSync)(import_node_path17.default.join(dir, ".git"))) return dir;
13845
+ const parent = import_node_path17.default.dirname(dir);
13172
13846
  if (parent === dir) break;
13173
13847
  dir = parent;
13174
13848
  }