crosscheck-mcp 0.1.10 → 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
@@ -1815,6 +1829,11 @@ async function sendOpenAICompatible(args) {
1815
1829
  body.reasoning_effort = effort;
1816
1830
  }
1817
1831
  }
1832
+ if (isReasoningModel(args.provider, args.model) && typeof body.max_completion_tokens === "number") {
1833
+ const raw = Number(process.env["CROSSCHECK_OPENAI_REASONING_HEADROOM_TOKENS"]);
1834
+ const headroom = Number.isFinite(raw) && raw >= 0 ? Math.trunc(raw) : 25e3;
1835
+ body.max_completion_tokens += headroom;
1836
+ }
1818
1837
  const doFetch = args.fetchImpl ?? globalThis.fetch;
1819
1838
  const init = {
1820
1839
  method: "POST",
@@ -1822,30 +1841,34 @@ async function sendOpenAICompatible(args) {
1822
1841
  body: JSON.stringify(body)
1823
1842
  };
1824
1843
  if (args.signal) init.signal = args.signal;
1825
- let respLike;
1826
- try {
1827
- respLike = await doFetch(url, init);
1828
- } catch (e) {
1829
- throw new ProviderError("network", `${args.provider}: fetch failed: ${e.message}`);
1830
- }
1831
- const status = respLike.status;
1832
- if (status >= 200 && status < 300) {
1833
- let parsed;
1844
+ const attemptOnce = async () => {
1845
+ let respLike;
1834
1846
  try {
1835
- parsed = await respLike.json();
1847
+ respLike = await doFetch(url, init);
1836
1848
  } catch (e) {
1837
- throw new ProviderError("parse", `${args.provider}: response body not JSON: ${e.message}`);
1849
+ throw new ProviderError("network", `${args.provider}: fetch failed: ${e.message}`);
1838
1850
  }
1839
- const { text, usage } = parseOpenAICompatibleResponse({
1840
- resp: parsed,
1841
- provider: args.provider,
1842
- model: args.model,
1843
- purpose: args.purpose ?? "worker"
1844
- });
1845
- return { text, attempts: 1, usage: applyPricing3(usage, args.pricing) };
1846
- }
1847
- const bodyText = await respLike.text().catch(() => "");
1848
- 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);
1849
1872
  }
1850
1873
 
1851
1874
  // src/providers/registry.ts
@@ -1940,7 +1963,51 @@ function makeGeminiProvider(model, apiKey, opts) {
1940
1963
  // src/server.ts
1941
1964
  init_cjs_shims();
1942
1965
  var import_server2 = require("@modelcontextprotocol/sdk/server/index.js");
1943
- 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
+ }
1944
2011
 
1945
2012
  // src/instructions.ts
1946
2013
  init_cjs_shims();
@@ -2007,7 +2074,7 @@ var import_zod = require("zod");
2007
2074
 
2008
2075
  // src/tools/audit.ts
2009
2076
  init_cjs_shims();
2010
- var import_node_fs4 = require("fs");
2077
+ var import_node_fs5 = require("fs");
2011
2078
  var import_node_path4 = require("path");
2012
2079
 
2013
2080
  // src/core/structured.ts
@@ -2070,27 +2137,27 @@ function extractJson(text) {
2070
2137
 
2071
2138
  // src/core/json-schema.ts
2072
2139
  init_cjs_shims();
2073
- function validateSchema(value, schema, path12 = "") {
2140
+ function validateSchema(value, schema, path14 = "") {
2074
2141
  const errs = [];
2075
2142
  if ("anyOf" in schema) {
2076
2143
  const subs = schema["anyOf"].map(
2077
- (s) => validateSchema(value, s, path12)
2144
+ (s) => validateSchema(value, s, path14)
2078
2145
  );
2079
2146
  if (!subs.some((e) => e.length === 0)) {
2080
- errs.push(`${path12 || "<root>"}: did not match anyOf`);
2147
+ errs.push(`${path14 || "<root>"}: did not match anyOf`);
2081
2148
  }
2082
2149
  return errs;
2083
2150
  }
2084
2151
  if ("oneOf" in schema) {
2085
- 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;
2086
2153
  if (passed !== 1) {
2087
- errs.push(`${path12 || "<root>"}: matched ${passed} of oneOf, expected 1`);
2154
+ errs.push(`${path14 || "<root>"}: matched ${passed} of oneOf, expected 1`);
2088
2155
  }
2089
2156
  return errs;
2090
2157
  }
2091
2158
  if ("const" in schema && !deepEqual(value, schema["const"])) {
2092
2159
  errs.push(
2093
- `${path12 || "<root>"}: expected const ${pyRepr(schema["const"])}, got ${pyRepr(value)}`
2160
+ `${path14 || "<root>"}: expected const ${pyRepr(schema["const"])}, got ${pyRepr(value)}`
2094
2161
  );
2095
2162
  return errs;
2096
2163
  }
@@ -2100,7 +2167,7 @@ function validateSchema(value, schema, path12 = "") {
2100
2167
  const ok = types.some((tt) => matchesType(value, String(tt)));
2101
2168
  if (!ok) {
2102
2169
  errs.push(
2103
- `${path12 || "<root>"}: expected type ${pyReprType(t)}, got ${pyTypeName(value)}`
2170
+ `${path14 || "<root>"}: expected type ${pyReprType(t)}, got ${pyTypeName(value)}`
2104
2171
  );
2105
2172
  return errs;
2106
2173
  }
@@ -2109,29 +2176,29 @@ function validateSchema(value, schema, path12 = "") {
2109
2176
  if ("enum" in schema) {
2110
2177
  const en = schema["enum"];
2111
2178
  if (!en.some((x) => deepEqual(x, value))) {
2112
- errs.push(`${path12 || "<root>"}: value ${pyRepr(value)} not in enum`);
2179
+ errs.push(`${path14 || "<root>"}: value ${pyRepr(value)} not in enum`);
2113
2180
  }
2114
2181
  }
2115
2182
  if ("minLength" in schema && value.length < schema["minLength"]) {
2116
- errs.push(`${path12 || "<root>"}: shorter than minLength ${schema["minLength"]}`);
2183
+ errs.push(`${path14 || "<root>"}: shorter than minLength ${schema["minLength"]}`);
2117
2184
  }
2118
2185
  }
2119
2186
  if (typeof value === "number" && !Number.isNaN(value)) {
2120
2187
  if ("minimum" in schema && value < schema["minimum"]) {
2121
- errs.push(`${path12 || "<root>"}: ${value} < minimum ${schema["minimum"]}`);
2188
+ errs.push(`${path14 || "<root>"}: ${value} < minimum ${schema["minimum"]}`);
2122
2189
  }
2123
2190
  if ("maximum" in schema && value > schema["maximum"]) {
2124
- errs.push(`${path12 || "<root>"}: ${value} > maximum ${schema["maximum"]}`);
2191
+ errs.push(`${path14 || "<root>"}: ${value} > maximum ${schema["maximum"]}`);
2125
2192
  }
2126
2193
  }
2127
2194
  if (Array.isArray(value)) {
2128
2195
  if ("minItems" in schema && value.length < schema["minItems"]) {
2129
- errs.push(`${path12 || "<root>"}: fewer items than minItems ${schema["minItems"]}`);
2196
+ errs.push(`${path14 || "<root>"}: fewer items than minItems ${schema["minItems"]}`);
2130
2197
  }
2131
2198
  const itemSchema = schema["items"];
2132
2199
  if (isObj(itemSchema)) {
2133
2200
  for (let i = 0; i < value.length; i++) {
2134
- errs.push(...validateSchema(value[i], itemSchema, `${path12}[${i}]`));
2201
+ errs.push(...validateSchema(value[i], itemSchema, `${path14}[${i}]`));
2135
2202
  }
2136
2203
  }
2137
2204
  }
@@ -2140,19 +2207,19 @@ function validateSchema(value, schema, path12 = "") {
2140
2207
  const required = schema["required"] ?? [];
2141
2208
  for (const r of required) {
2142
2209
  if (!(r in value)) {
2143
- errs.push(`${path12 || "<root>"}: missing required key ${pyRepr(r)}`);
2210
+ errs.push(`${path14 || "<root>"}: missing required key ${pyRepr(r)}`);
2144
2211
  }
2145
2212
  }
2146
2213
  if (schema["additionalProperties"] === false) {
2147
2214
  for (const k of Object.keys(value)) {
2148
2215
  if (!(k in props)) {
2149
- errs.push(`${path12 || "<root>"}: unknown key ${pyRepr(k)}`);
2216
+ errs.push(`${path14 || "<root>"}: unknown key ${pyRepr(k)}`);
2150
2217
  }
2151
2218
  }
2152
2219
  }
2153
2220
  for (const [k, v] of Object.entries(value)) {
2154
2221
  const ps = props[k];
2155
- if (ps) errs.push(...validateSchema(v, ps, path12 ? `${path12}.${k}` : k));
2222
+ if (ps) errs.push(...validateSchema(v, ps, path14 ? `${path14}.${k}` : k));
2156
2223
  }
2157
2224
  }
2158
2225
  return errs;
@@ -2541,6 +2608,21 @@ async function recordSessionCall(storage, sessionId, answers, wallMs, cpuMs, now
2541
2608
  }
2542
2609
  }
2543
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
+
2544
2626
  // src/core/structured.ts
2545
2627
  async function requestStructured(provider, baseMessages, schema, opts) {
2546
2628
  const maxRetries = opts.maxRetries ?? 1;
@@ -2615,10 +2697,13 @@ ${schemaText}`;
2615
2697
  async function askOne(provider, messages, opts) {
2616
2698
  const startedWall = performance.now();
2617
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;
2618
2703
  try {
2619
2704
  const r = await provider.send({
2620
2705
  messages,
2621
- maxTokens: opts.maxTokens,
2706
+ maxTokens,
2622
2707
  temperature: opts.temperature,
2623
2708
  purpose: opts.purpose,
2624
2709
  ...opts.signal ? { signal: opts.signal } : {},
@@ -2659,6 +2744,191 @@ async function askOne(provider, messages, opts) {
2659
2744
  }
2660
2745
  }
2661
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
+
2662
2932
  // src/core/utils.ts
2663
2933
  init_cjs_shims();
2664
2934
  function checkSessionBreakers(session, cfg) {
@@ -2797,7 +3067,7 @@ function maybeDagBreakerEnvelope(dag, toolName, cfg, sessionId) {
2797
3067
 
2798
3068
  // src/core/transcripts.ts
2799
3069
  init_cjs_shims();
2800
- var import_node_fs3 = require("fs");
3070
+ var import_node_fs4 = require("fs");
2801
3071
  var import_node_path3 = __toESM(require("path"), 1);
2802
3072
 
2803
3073
  // src/core/redact.ts
@@ -2876,6 +3146,13 @@ function redactObj(obj, cfg) {
2876
3146
  }
2877
3147
  return obj;
2878
3148
  }
3149
+ var globalRedaction = null;
3150
+ function setRedactionConfig(cfg) {
3151
+ globalRedaction = cfg;
3152
+ }
3153
+ function getRedactionConfig() {
3154
+ return globalRedaction ?? void 0;
3155
+ }
2879
3156
 
2880
3157
  // src/core/transcripts.ts
2881
3158
  var FTS_INDEX_MAX_CHARS = 64 * 1024;
@@ -2933,12 +3210,12 @@ async function writeTranscript(opts) {
2933
3210
  const now = opts.nowMs ? opts.nowMs() : Date.now();
2934
3211
  const stampMs = Math.trunc(now);
2935
3212
  const fileName = `${stampMs}-${opts.kind}.json`;
2936
- const redacted = redactObj(opts.payload, opts.redact);
3213
+ const redacted = redactObj(opts.payload, opts.redact ?? getRedactionConfig());
2937
3214
  let absPath;
2938
3215
  try {
2939
- (0, import_node_fs3.mkdirSync)(opts.transcriptsDir, { recursive: true });
3216
+ (0, import_node_fs4.mkdirSync)(opts.transcriptsDir, { recursive: true });
2940
3217
  absPath = import_node_path3.default.resolve(opts.transcriptsDir, fileName);
2941
- (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");
2942
3219
  } catch {
2943
3220
  return null;
2944
3221
  }
@@ -3430,7 +3707,18 @@ async function runAudit(args, opts) {
3430
3707
  for (const r2 of DEFAULT_AUDIT_RUBRICS) rubricItems.push({ ...r2 });
3431
3708
  }
3432
3709
  const rubricText = rubricItems.map((it) => `- ${it.id} (severity=${it.severity}): ${it.description}`).join("\n");
3433
- 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;
3434
3722
  const userMsg = (userConstraints ? `USER CONSTRAINTS:
3435
3723
  ${userConstraints}
3436
3724
 
@@ -3527,6 +3815,7 @@ ${rubricText}`;
3527
3815
  audit_process_failure: flags.audit_process_failure,
3528
3816
  judges_stats: flags.judges_stats
3529
3817
  };
3818
+ if (persona.meta.used !== null) coalesceResult["persona"] = persona.meta;
3530
3819
  if (!allPass2 && opts.storage && typeof sessionId === "string" && sessionId) {
3531
3820
  const marked = await markStaleOnAuditFailure(
3532
3821
  opts.storage,
@@ -3607,6 +3896,7 @@ ${rubricText}`;
3607
3896
  overall_score: overall,
3608
3897
  passed: allPass
3609
3898
  };
3899
+ if (persona.meta.used !== null) result["persona"] = persona.meta;
3610
3900
  if (errs.length > 0) result["validation_errors"] = errs;
3611
3901
  if (!allPass && opts.storage && typeof sessionId === "string" && sessionId) {
3612
3902
  const marked = await markStaleOnAuditFailure(
@@ -3892,7 +4182,7 @@ function loadAuditTextFromSession(sessionId, transcriptsDir) {
3892
4182
  if (!transcriptsDir) return "";
3893
4183
  let entries;
3894
4184
  try {
3895
- entries = (0, import_node_fs4.readdirSync)(transcriptsDir);
4185
+ entries = (0, import_node_fs5.readdirSync)(transcriptsDir);
3896
4186
  } catch {
3897
4187
  return "";
3898
4188
  }
@@ -3903,7 +4193,7 @@ function loadAuditTextFromSession(sessionId, transcriptsDir) {
3903
4193
  const p = (0, import_node_path4.join)(transcriptsDir, name);
3904
4194
  let doc2;
3905
4195
  try {
3906
- doc2 = JSON.parse((0, import_node_fs4.readFileSync)(p, "utf8"));
4196
+ doc2 = JSON.parse((0, import_node_fs5.readFileSync)(p, "utf8"));
3907
4197
  } catch {
3908
4198
  continue;
3909
4199
  }
@@ -3911,7 +4201,7 @@ function loadAuditTextFromSession(sessionId, transcriptsDir) {
3911
4201
  if (sid !== sessionId) continue;
3912
4202
  let mtime;
3913
4203
  try {
3914
- mtime = (0, import_node_fs4.statSync)(p).mtimeMs;
4204
+ mtime = (0, import_node_fs5.statSync)(p).mtimeMs;
3915
4205
  } catch {
3916
4206
  continue;
3917
4207
  }
@@ -3923,7 +4213,7 @@ function loadAuditTextFromSession(sessionId, transcriptsDir) {
3923
4213
  if (bestPath === null) return "";
3924
4214
  let doc;
3925
4215
  try {
3926
- doc = JSON.parse((0, import_node_fs4.readFileSync)(bestPath, "utf8"));
4216
+ doc = JSON.parse((0, import_node_fs5.readFileSync)(bestPath, "utf8"));
3927
4217
  } catch {
3928
4218
  return "";
3929
4219
  }
@@ -3956,7 +4246,7 @@ function boolArg(v, defaultVal) {
3956
4246
 
3957
4247
  // src/tools/bench.ts
3958
4248
  init_cjs_shims();
3959
- var import_node_fs5 = require("fs");
4249
+ var import_node_fs6 = require("fs");
3960
4250
  var import_node_path6 = __toESM(require("path"), 1);
3961
4251
 
3962
4252
  // src/tools/confer.ts
@@ -4493,21 +4783,21 @@ function extractToolCall(text) {
4493
4783
  return { call, parseError: null };
4494
4784
  }
4495
4785
  function wrapToolResult(name, content) {
4496
- let str;
4497
- if (typeof content === "string") str = content;
4786
+ let str2;
4787
+ if (typeof content === "string") str2 = content;
4498
4788
  else {
4499
4789
  try {
4500
- str = JSON.stringify(content);
4790
+ str2 = JSON.stringify(content);
4501
4791
  } catch {
4502
- str = String(content);
4792
+ str2 = String(content);
4503
4793
  }
4504
4794
  }
4505
- if (str.length > WORKER_TOOLS_MAX_RESULT_CHARS) {
4506
- 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)";
4507
4797
  }
4508
4798
  return `<tool_result name="${name}">
4509
4799
  <untrusted_input>
4510
- ${neutralizeInjection(str)}
4800
+ ${neutralizeInjection(str2)}
4511
4801
  </untrusted_input>
4512
4802
  </tool_result>`;
4513
4803
  }
@@ -5072,8 +5362,18 @@ async function runConfer(args, opts) {
5072
5362
  const untrusted = Boolean(args["untrusted_input"]);
5073
5363
  const canary = untrusted ? mintCanary() : null;
5074
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.";
5075
- const sysMsg = untrusted ? `${baseSys}
5076
- ${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;
5077
5377
  const messages = [{ role: "system", content: sysMsg }];
5078
5378
  if (context) {
5079
5379
  const ctxBody = untrusted ? wrapUntrusted(context, canary) : context;
@@ -5187,6 +5487,7 @@ ${ctxBody}` });
5187
5487
  question,
5188
5488
  answers: sanitized
5189
5489
  };
5490
+ if (persona.meta.used !== null) result["persona"] = persona.meta;
5190
5491
  if (earlyStopped) {
5191
5492
  result["early_stopped"] = true;
5192
5493
  result["skipped_providers"] = skippedProviders;
@@ -5406,9 +5707,19 @@ async function runSandboxed(args) {
5406
5707
  }
5407
5708
  resolve2(result);
5408
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);
5409
5720
  let child;
5410
5721
  try {
5411
- child = (0, import_node_child_process.spawn)(args.cmd[0], args.cmd.slice(1), {
5722
+ child = (0, import_node_child_process.spawn)(spawnBin, spawnArgv, {
5412
5723
  cwd: tempCwd ?? process.cwd(),
5413
5724
  env,
5414
5725
  // detached: own process-group on Unix (equivalent to setsid).
@@ -6097,10 +6408,10 @@ async function runBench(args, opts) {
6097
6408
  return result;
6098
6409
  }
6099
6410
  function loadGoldens(dirPath, filter) {
6100
- if (!(0, import_node_fs5.existsSync)(dirPath)) return [];
6411
+ if (!(0, import_node_fs6.existsSync)(dirPath)) return [];
6101
6412
  let entries;
6102
6413
  try {
6103
- entries = (0, import_node_fs5.readdirSync)(dirPath);
6414
+ entries = (0, import_node_fs6.readdirSync)(dirPath);
6104
6415
  } catch {
6105
6416
  return [];
6106
6417
  }
@@ -6109,13 +6420,13 @@ function loadGoldens(dirPath, filter) {
6109
6420
  if (!name.endsWith(".json")) continue;
6110
6421
  const p = import_node_path6.default.join(dirPath, name);
6111
6422
  try {
6112
- (0, import_node_fs5.statSync)(p);
6423
+ (0, import_node_fs6.statSync)(p);
6113
6424
  } catch {
6114
6425
  continue;
6115
6426
  }
6116
6427
  let doc;
6117
6428
  try {
6118
- doc = JSON.parse((0, import_node_fs5.readFileSync)(p, "utf8"));
6429
+ doc = JSON.parse((0, import_node_fs6.readFileSync)(p, "utf8"));
6119
6430
  } catch {
6120
6431
  continue;
6121
6432
  }
@@ -6195,7 +6506,7 @@ function isObj4(v) {
6195
6506
  // src/tools/config-pin.ts
6196
6507
  init_cjs_shims();
6197
6508
  var import_node_crypto3 = require("crypto");
6198
- var import_node_fs6 = require("fs");
6509
+ var import_node_fs7 = require("fs");
6199
6510
  var import_node_path7 = __toESM(require("path"), 1);
6200
6511
  var DEFAULT_PIN_PATH = ".crosscheck/config_pins.json";
6201
6512
  var DEFAULT_TARGETS = ["crosscheck.config.json", "config/pricing.json"];
@@ -6268,10 +6579,10 @@ async function runConfigPin(args, opts) {
6268
6579
  };
6269
6580
  }
6270
6581
  const absPath = resolvePinPath(opts);
6271
- const existed = (0, import_node_fs6.existsSync)(absPath);
6582
+ const existed = (0, import_node_fs7.existsSync)(absPath);
6272
6583
  if (existed) {
6273
6584
  try {
6274
- (0, import_node_fs6.unlinkSync)(absPath);
6585
+ (0, import_node_fs7.unlinkSync)(absPath);
6275
6586
  } catch (e) {
6276
6587
  return errorEnvelope4(
6277
6588
  "CONFIG_PIN_CLEAR_FAILED",
@@ -6306,7 +6617,7 @@ function computeDrift(opts) {
6306
6617
  missing_files: missingFiles.sort(),
6307
6618
  pin_file: relPath(opts, absPath),
6308
6619
  pinned_at: Number(doc?.["pinned_at"] ?? 0) || 0,
6309
- has_pin_file: (0, import_node_fs6.existsSync)(absPath)
6620
+ has_pin_file: (0, import_node_fs7.existsSync)(absPath)
6310
6621
  };
6311
6622
  }
6312
6623
  function shouldBlock(opts, drift) {
@@ -6331,9 +6642,9 @@ function resolveTargets(opts) {
6331
6642
  const out = [];
6332
6643
  for (const p of list) {
6333
6644
  const abs = import_node_path7.default.isAbsolute(p) ? p : import_node_path7.default.join(opts.repoRoot, p);
6334
- if ((0, import_node_fs6.existsSync)(abs)) {
6645
+ if ((0, import_node_fs7.existsSync)(abs)) {
6335
6646
  try {
6336
- const st = (0, import_node_fs6.statSync)(abs);
6647
+ const st = (0, import_node_fs7.statSync)(abs);
6337
6648
  if (st.isFile()) out.push(abs);
6338
6649
  } catch {
6339
6650
  }
@@ -6344,7 +6655,7 @@ function resolveTargets(opts) {
6344
6655
  function hashFile(abs) {
6345
6656
  let raw;
6346
6657
  try {
6347
- raw = (0, import_node_fs6.readFileSync)(abs);
6658
+ raw = (0, import_node_fs7.readFileSync)(abs);
6348
6659
  } catch {
6349
6660
  return "";
6350
6661
  }
@@ -6364,9 +6675,9 @@ function resolvePinPath(opts) {
6364
6675
  return import_node_path7.default.isAbsolute(raw) ? raw : import_node_path7.default.join(opts.repoRoot, raw);
6365
6676
  }
6366
6677
  function loadDoc(absPath) {
6367
- if (!(0, import_node_fs6.existsSync)(absPath)) return null;
6678
+ if (!(0, import_node_fs7.existsSync)(absPath)) return null;
6368
6679
  try {
6369
- const txt = (0, import_node_fs6.readFileSync)(absPath, "utf8");
6680
+ const txt = (0, import_node_fs7.readFileSync)(absPath, "utf8");
6370
6681
  const doc = JSON.parse(txt);
6371
6682
  return doc && typeof doc === "object" && !Array.isArray(doc) ? doc : null;
6372
6683
  } catch {
@@ -6375,11 +6686,11 @@ function loadDoc(absPath) {
6375
6686
  }
6376
6687
  function saveDoc(opts, doc) {
6377
6688
  const absPath = resolvePinPath(opts);
6378
- (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 });
6379
6690
  const tmp = absPath + ".tmp";
6380
- (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");
6381
6692
  try {
6382
- (0, import_node_fs6.unlinkSync)(absPath);
6693
+ (0, import_node_fs7.unlinkSync)(absPath);
6383
6694
  } catch {
6384
6695
  }
6385
6696
  const { renameSync: renameSync2 } = require("fs");
@@ -6428,7 +6739,7 @@ function pyRepr2(v) {
6428
6739
  // src/tools/create.ts
6429
6740
  init_cjs_shims();
6430
6741
  var import_node_crypto6 = require("crypto");
6431
- var import_node_fs9 = require("fs");
6742
+ var import_node_fs10 = require("fs");
6432
6743
  var import_node_path10 = __toESM(require("path"), 1);
6433
6744
 
6434
6745
  // src/tools/orchestrate.ts
@@ -6531,7 +6842,7 @@ function isDeadModelError(err) {
6531
6842
  // src/core/node-cache.ts
6532
6843
  init_cjs_shims();
6533
6844
  var import_node_crypto4 = require("crypto");
6534
- var import_node_fs7 = require("fs");
6845
+ var import_node_fs8 = require("fs");
6535
6846
  var import_node_path8 = require("path");
6536
6847
  var NODE_CACHE_KEY_VERSION = "v2";
6537
6848
  var CANON_PATTERNS = [
@@ -6596,18 +6907,18 @@ function cachePath(cfg, repoRoot, key) {
6596
6907
  function getNodeCache(cfg, repoRoot, key, nowMs) {
6597
6908
  if (!cfg || cfg.enabled === false) return null;
6598
6909
  const p = cachePath(cfg, repoRoot, key);
6599
- if (!(0, import_node_fs7.existsSync)(p)) return null;
6910
+ if (!(0, import_node_fs8.existsSync)(p)) return null;
6600
6911
  const ttlS = cfg.ttl_seconds ?? 604800;
6601
6912
  try {
6602
- const st = (0, import_node_fs7.statSync)(p);
6913
+ const st = (0, import_node_fs8.statSync)(p);
6603
6914
  if (ttlS > 0) {
6604
6915
  const now = nowMs ?? Date.now();
6605
6916
  if ((now - st.mtimeMs) / 1e3 > ttlS) return null;
6606
6917
  }
6607
- const data = JSON.parse((0, import_node_fs7.readFileSync)(p, "utf8"));
6918
+ const data = JSON.parse((0, import_node_fs8.readFileSync)(p, "utf8"));
6608
6919
  try {
6609
6920
  const t = (nowMs ?? Date.now()) / 1e3;
6610
- (0, import_node_fs7.utimesSync)(p, t, t);
6921
+ (0, import_node_fs8.utimesSync)(p, t, t);
6611
6922
  } catch {
6612
6923
  }
6613
6924
  return data;
@@ -6615,11 +6926,11 @@ function getNodeCache(cfg, repoRoot, key, nowMs) {
6615
6926
  return null;
6616
6927
  }
6617
6928
  }
6618
- function atomicWriteJson(path12, payload) {
6619
- (0, import_node_fs7.mkdirSync)((0, import_node_path8.dirname)(path12), { recursive: true });
6620
- const tmp = `${path12}.${process.pid}.${Date.now()}.tmp`;
6621
- (0, import_node_fs7.writeFileSync)(tmp, JSON.stringify(payload), "utf8");
6622
- (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);
6623
6934
  }
6624
6935
  function putNodeCache(cfg, repoRoot, key, value) {
6625
6936
  if (!cfg || cfg.enabled === false) return;
@@ -6630,23 +6941,23 @@ function putNodeCache(cfg, repoRoot, key, value) {
6630
6941
  const maxBytes = cfg.max_cache_bytes ?? 100 * 1024 * 1024;
6631
6942
  if (maxEntries <= 0 && maxBytes <= 0) return;
6632
6943
  const base = resolveCacheDir(cfg, repoRoot);
6633
- if (!(0, import_node_fs7.existsSync)(base)) return;
6944
+ if (!(0, import_node_fs8.existsSync)(base)) return;
6634
6945
  const entries = [];
6635
6946
  let totalBytes = 0;
6636
- for (const shard of (0, import_node_fs7.readdirSync)(base)) {
6947
+ for (const shard of (0, import_node_fs8.readdirSync)(base)) {
6637
6948
  const shardDir = (0, import_node_path8.join)(base, shard);
6638
6949
  let stShard;
6639
6950
  try {
6640
- stShard = (0, import_node_fs7.statSync)(shardDir);
6951
+ stShard = (0, import_node_fs8.statSync)(shardDir);
6641
6952
  } catch {
6642
6953
  continue;
6643
6954
  }
6644
6955
  if (!stShard.isDirectory()) continue;
6645
- for (const f of (0, import_node_fs7.readdirSync)(shardDir)) {
6956
+ for (const f of (0, import_node_fs8.readdirSync)(shardDir)) {
6646
6957
  if (!f.endsWith(".json")) continue;
6647
6958
  const fp = (0, import_node_path8.join)(shardDir, f);
6648
6959
  try {
6649
- const st = (0, import_node_fs7.statSync)(fp);
6960
+ const st = (0, import_node_fs8.statSync)(fp);
6650
6961
  entries.push({ mtime: st.mtimeMs, size: st.size, path: fp });
6651
6962
  totalBytes += st.size;
6652
6963
  } catch {
@@ -6660,7 +6971,7 @@ function putNodeCache(cfg, repoRoot, key, value) {
6660
6971
  while (entries.length > 0 && (maxEntries > 0 && entries.length > maxEntries || maxBytes > 0 && totalBytes > maxBytes)) {
6661
6972
  const oldest = entries.shift();
6662
6973
  try {
6663
- (0, import_node_fs7.unlinkSync)(oldest.path);
6974
+ (0, import_node_fs8.unlinkSync)(oldest.path);
6664
6975
  totalBytes -= oldest.size;
6665
6976
  } catch {
6666
6977
  }
@@ -7222,8 +7533,19 @@ NODE OUTPUTS:
7222
7533
  ${recombineLines.join("\n\n")}
7223
7534
 
7224
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.";
7225
7545
  const recMsgs = [
7226
- { 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 },
7227
7549
  { role: "user", content: recombinePrompt }
7228
7550
  ];
7229
7551
  let synthProvider = moderator;
@@ -7296,6 +7618,7 @@ Synthesize the node outputs into a single coherent deliverable. Preserve any [MI
7296
7618
  fail_fast: failFast,
7297
7619
  cheap_mode: cheapMode
7298
7620
  };
7621
+ if (personaMeta.used !== null) result["persona"] = personaMeta;
7299
7622
  if (synthModel) result["synth_model"] = synthModel;
7300
7623
  if (synthErr) result["synth_error"] = synthErr;
7301
7624
  if (plannerErrors.length > 0) result["planner_errors"] = plannerErrors;
@@ -7687,7 +8010,7 @@ function pyRound6(x) {
7687
8010
  // src/tools/fetch.ts
7688
8011
  init_cjs_shims();
7689
8012
  var import_node_crypto5 = require("crypto");
7690
- var import_node_fs8 = require("fs");
8013
+ var import_node_fs9 = require("fs");
7691
8014
  var import_node_path9 = __toESM(require("path"), 1);
7692
8015
  var DEFAULT_CONFIG = {
7693
8016
  enabled: true,
@@ -7769,9 +8092,9 @@ async function runFetch(args, opts) {
7769
8092
  const evDir = resolveEvidenceDir(cfg.evidence_dir, opts.repoRoot);
7770
8093
  const urlHash16 = sha256Hex2(url).slice(0, 16);
7771
8094
  const metaPath = import_node_path9.default.join(evDir, `by-url-${urlHash16}.json`);
7772
- if ((0, import_node_fs8.existsSync)(metaPath) && !force) {
8095
+ if ((0, import_node_fs9.existsSync)(metaPath) && !force) {
7773
8096
  try {
7774
- const meta2 = JSON.parse((0, import_node_fs8.readFileSync)(metaPath, "utf8"));
8097
+ const meta2 = JSON.parse((0, import_node_fs9.readFileSync)(metaPath, "utf8"));
7775
8098
  return {
7776
8099
  ...base,
7777
8100
  accepted: true,
@@ -7811,15 +8134,41 @@ async function runFetch(args, opts) {
7811
8134
  }
7812
8135
  let data;
7813
8136
  try {
7814
- const buf = await response.arrayBuffer();
7815
- if (buf.byteLength > cfg.max_bytes) {
7816
- return {
7817
- ...base,
7818
- accepted: false,
7819
- reason: `response exceeds max_bytes=${cfg.max_bytes}`
7820
- };
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
+ }
7821
8171
  }
7822
- data = new Uint8Array(buf);
7823
8172
  } catch (e) {
7824
8173
  return {
7825
8174
  ...base,
@@ -7831,8 +8180,8 @@ async function runFetch(args, opts) {
7831
8180
  const status = response.status;
7832
8181
  const sha = sha256HexBytes(data);
7833
8182
  const bodyPath = import_node_path9.default.join(evDir, `${sha}.bin`);
7834
- (0, import_node_fs8.mkdirSync)(evDir, { recursive: true });
7835
- (0, import_node_fs8.writeFileSync)(bodyPath, data);
8183
+ (0, import_node_fs9.mkdirSync)(evDir, { recursive: true });
8184
+ (0, import_node_fs9.writeFileSync)(bodyPath, data);
7836
8185
  const relBody = makeRelative2(bodyPath, opts.repoRoot);
7837
8186
  const now = opts.nowEpochSeconds ? opts.nowEpochSeconds() : Math.floor(Date.now() / 1e3);
7838
8187
  const meta = {
@@ -7844,7 +8193,7 @@ async function runFetch(args, opts) {
7844
8193
  status,
7845
8194
  fetched_at: now
7846
8195
  };
7847
- (0, import_node_fs8.writeFileSync)(metaPath, JSON.stringify(meta, null, 2));
8196
+ (0, import_node_fs9.writeFileSync)(metaPath, JSON.stringify(meta, null, 2));
7848
8197
  if (sessionId && host && opts.storage) {
7849
8198
  try {
7850
8199
  await opts.storage.recordFetchEgress(sessionId, host, data.byteLength, now);
@@ -8127,8 +8476,8 @@ ${documentsPayload}`
8127
8476
  if (targetPath && !dryRun && typeof orchestration["final"] === "string" && orchestration["final"] && !blockWriteStatuses.has(status)) {
8128
8477
  try {
8129
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);
8130
- (0, import_node_fs9.mkdirSync)(import_node_path10.default.dirname(tp), { recursive: true });
8131
- (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");
8132
8481
  artifacts.push({ path: targetPath, bytes: orchestration["final"].length });
8133
8482
  if (status === "audit_failed" || status === "audit_inconclusive") {
8134
8483
  warnings.push(
@@ -8198,7 +8547,7 @@ async function ingestDocuments(documents, sessionId, opts) {
8198
8547
  if (typeof evidencePath === "string" && evidencePath) {
8199
8548
  const abs = import_node_path10.default.isAbsolute(evidencePath) ? evidencePath : opts.repoRoot ? import_node_path10.default.join(opts.repoRoot, evidencePath) : evidencePath;
8200
8549
  try {
8201
- text = (0, import_node_fs9.readFileSync)(abs, "utf8");
8550
+ text = (0, import_node_fs10.readFileSync)(abs, "utf8");
8202
8551
  } catch {
8203
8552
  text = "";
8204
8553
  }
@@ -8212,7 +8561,7 @@ async function ingestDocuments(documents, sessionId, opts) {
8212
8561
  }, text));
8213
8562
  } else {
8214
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);
8215
- if (!(0, import_node_fs9.existsSync)(abs)) {
8564
+ if (!(0, import_node_fs10.existsSync)(abs)) {
8216
8565
  out.push({
8217
8566
  source: ref,
8218
8567
  type: "file",
@@ -8224,7 +8573,7 @@ async function ingestDocuments(documents, sessionId, opts) {
8224
8573
  }
8225
8574
  let text = "";
8226
8575
  try {
8227
- text = (0, import_node_fs9.readFileSync)(abs, "utf8");
8576
+ text = (0, import_node_fs10.readFileSync)(abs, "utf8");
8228
8577
  } catch (e) {
8229
8578
  out.push({
8230
8579
  source: ref,
@@ -9239,15 +9588,27 @@ ${prior}` });
9239
9588
  let synthesis = null;
9240
9589
  let synthesisStructured = null;
9241
9590
  let synthesisErrors = [];
9591
+ let personaMeta = null;
9242
9592
  if (moderator) {
9243
9593
  const condensed = transcript.map(
9244
9594
  (e) => `[${e.provider} \u2014 round ${e.round}]
9245
9595
  ${e.response ?? "(error)"}`
9246
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.";
9247
9606
  const synthMessages = [
9248
9607
  {
9249
9608
  role: "system",
9250
- content: "You are the moderator. Synthesise the debate into a single grounded recommendation."
9609
+ content: persona.block ? `${persona.block}
9610
+
9611
+ ${synthSys}` : synthSys
9251
9612
  },
9252
9613
  {
9253
9614
  role: "user",
@@ -9299,6 +9660,7 @@ ${condensed}`
9299
9660
  transcript,
9300
9661
  synthesis
9301
9662
  };
9663
+ if (personaMeta && personaMeta.used !== null) result["persona"] = personaMeta;
9302
9664
  if (synthesisStructured !== null) {
9303
9665
  result["synthesis_structured"] = synthesisStructured;
9304
9666
  }
@@ -9613,7 +9975,7 @@ function pyListRepr2(xs) {
9613
9975
 
9614
9976
  // src/tools/explain.ts
9615
9977
  init_cjs_shims();
9616
- var import_node_fs10 = require("fs");
9978
+ var import_node_fs11 = require("fs");
9617
9979
  var import_node_path11 = __toESM(require("path"), 1);
9618
9980
  async function runExplain(args, opts) {
9619
9981
  const sessionId = args["session_id"];
@@ -9715,10 +10077,10 @@ async function runExplain(args, opts) {
9715
10077
  return result;
9716
10078
  }
9717
10079
  function loadTranscriptsForSession(dir, sessionId) {
9718
- if (!(0, import_node_fs10.existsSync)(dir)) return [];
10080
+ if (!(0, import_node_fs11.existsSync)(dir)) return [];
9719
10081
  let names;
9720
10082
  try {
9721
- names = (0, import_node_fs10.readdirSync)(dir);
10083
+ names = (0, import_node_fs11.readdirSync)(dir);
9722
10084
  } catch {
9723
10085
  return [];
9724
10086
  }
@@ -9728,13 +10090,13 @@ function loadTranscriptsForSession(dir, sessionId) {
9728
10090
  const p = import_node_path11.default.join(dir, name);
9729
10091
  let stat;
9730
10092
  try {
9731
- stat = (0, import_node_fs10.statSync)(p);
10093
+ stat = (0, import_node_fs11.statSync)(p);
9732
10094
  } catch {
9733
10095
  continue;
9734
10096
  }
9735
10097
  let doc;
9736
10098
  try {
9737
- doc = JSON.parse((0, import_node_fs10.readFileSync)(p, "utf8"));
10099
+ doc = JSON.parse((0, import_node_fs11.readFileSync)(p, "utf8"));
9738
10100
  } catch {
9739
10101
  continue;
9740
10102
  }
@@ -10590,7 +10952,7 @@ function toStringArray3(v) {
10590
10952
 
10591
10953
  // src/tools/scoreboard.ts
10592
10954
  init_cjs_shims();
10593
- var import_node_fs11 = require("fs");
10955
+ var import_node_fs12 = require("fs");
10594
10956
  async function runScoreboard(args, opts) {
10595
10957
  if (!opts.storage) {
10596
10958
  if (opts.bridge && opts.bridge.toolNames.has("scoreboard")) {
@@ -10673,9 +11035,9 @@ async function runScoreboard(args, opts) {
10673
11035
  }
10674
11036
  usageTotals.cost_usd = Number(usageTotals.cost_usd.toFixed(8));
10675
11037
  let recentEvents = [];
10676
- 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)) {
10677
11039
  try {
10678
- 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);
10679
11041
  const tail = lines.slice(-recentLimit);
10680
11042
  for (const ln of tail) {
10681
11043
  try {
@@ -10924,6 +11286,122 @@ function pyRepr6(v) {
10924
11286
  // src/tools/solve.ts
10925
11287
  init_cjs_shims();
10926
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
10927
11405
  async function runSolve(args, opts) {
10928
11406
  const problem = typeof args["problem"] === "string" ? args["problem"] : String(args["problem"] ?? "");
10929
11407
  if (!problem) {
@@ -11065,7 +11543,23 @@ Re-emit the FULL solution. No commentary.`
11065
11543
  const targetPath = args["target_path"];
11066
11544
  if (typeof targetPath === "string" && targetPath) {
11067
11545
  result["target_path"] = targetPath;
11068
- 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
+ }
11069
11563
  }
11070
11564
  if (unknownNames.length > 0) result["skipped_unknown_providers"] = unknownNames;
11071
11565
  if (blocked.length > 0) result["blocked_by_allowlist"] = blocked;
@@ -11151,6 +11645,8 @@ async function verifyProposalShell(verifier, proposal) {
11151
11645
  }
11152
11646
  const argv = cmd.map((x) => String(x));
11153
11647
  const timeoutS = numberArg2(verifier["timeout_s"], 10);
11648
+ const memoryMb = numberArg2(verifier["memory_mb"], 0);
11649
+ const cpuSeconds = numberArg2(verifier["cpu_seconds"], 0);
11154
11650
  const expectExit = pyIntCoerce2(verifier["expect_exit_code"], 0);
11155
11651
  const expectContains = typeof verifier["expect_stdout_contains"] === "string" ? verifier["expect_stdout_contains"] : null;
11156
11652
  const expectRegex = typeof verifier["expect_stdout_regex"] === "string" ? verifier["expect_stdout_regex"] : null;
@@ -11160,7 +11656,9 @@ async function verifyProposalShell(verifier, proposal) {
11160
11656
  input: proposal,
11161
11657
  // proposal piped to stdin
11162
11658
  stdoutTailBytes: 2048,
11163
- stderrTailBytes: 2048
11659
+ stderrTailBytes: 2048,
11660
+ ...memoryMb > 0 ? { memoryMb } : {},
11661
+ ...cpuSeconds > 0 ? { cpuSeconds } : {}
11164
11662
  });
11165
11663
  if (r.status === "timeout") {
11166
11664
  return {
@@ -11330,24 +11828,24 @@ function pyRepr7(v) {
11330
11828
  // src/tools/update-crosscheck.ts
11331
11829
  init_cjs_shims();
11332
11830
  var import_node_child_process2 = require("child_process");
11333
- var import_node_fs13 = require("fs");
11334
- var import_node_path13 = __toESM(require("path"), 1);
11831
+ var import_node_fs15 = require("fs");
11832
+ var import_node_path14 = __toESM(require("path"), 1);
11335
11833
 
11336
11834
  // src/core/update-notify.ts
11337
11835
  init_cjs_shims();
11338
- var import_node_fs12 = require("fs");
11836
+ var import_node_fs14 = require("fs");
11339
11837
  var import_node_os2 = __toESM(require("os"), 1);
11340
- var import_node_path12 = __toESM(require("path"), 1);
11838
+ var import_node_path13 = __toESM(require("path"), 1);
11341
11839
  var NPM_REGISTRY = "https://registry.npmjs.org";
11342
11840
  var CHECK_INTERVAL_SECONDS = 3 * 24 * 60 * 60;
11343
11841
  var DEFAULT_PACKAGE = "crosscheck-cli";
11344
11842
  var FETCH_TIMEOUT_MS = 3e3;
11345
11843
  function engineVersion() {
11346
- return true ? "0.1.10" : "0.0.0-dev";
11844
+ return true ? "0.1.12" : "0.0.0-dev";
11347
11845
  }
11348
11846
  function defaultUpdateCachePath() {
11349
- const base = process.env["CROSSCHECK_DATA_DIR"] || import_node_path12.default.join(import_node_os2.default.homedir() || import_node_os2.default.tmpdir(), ".crosscheck");
11350
- return import_node_path12.default.join(base, "update_check.json");
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");
11351
11849
  }
11352
11850
  function isNewer(latest, current) {
11353
11851
  const a = parseTriple(latest);
@@ -11377,8 +11875,8 @@ then reconnect the crosscheck MCP server (run \`/mcp\` in Claude Code, or restar
11377
11875
  }
11378
11876
  function readCache(cachePath2) {
11379
11877
  try {
11380
- if (!(0, import_node_fs12.existsSync)(cachePath2)) return null;
11381
- 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"));
11382
11880
  if (data && typeof data === "object" && !Array.isArray(data)) {
11383
11881
  return data;
11384
11882
  }
@@ -11389,8 +11887,8 @@ function readCache(cachePath2) {
11389
11887
  }
11390
11888
  function writeCache(cachePath2, record2) {
11391
11889
  try {
11392
- (0, import_node_fs12.mkdirSync)(import_node_path12.default.dirname(cachePath2), { recursive: true });
11393
- (0, import_node_fs12.writeFileSync)(cachePath2, JSON.stringify(record2, null, 2), "utf8");
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");
11394
11892
  } catch {
11395
11893
  }
11396
11894
  }
@@ -11443,7 +11941,7 @@ async function runUpdateCrosscheck(args, opts) {
11443
11941
  const gitRun = opts.gitRun ?? defaultGitRun(opts.repoRoot);
11444
11942
  const fetchFn = opts.httpFetch ?? globalThis.fetch;
11445
11943
  const now = opts.nowEpochSeconds ? opts.nowEpochSeconds() : Math.floor(Date.now() / 1e3);
11446
- 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");
11447
11945
  const local = readLocalSha(gitRun);
11448
11946
  if (!local) {
11449
11947
  return {
@@ -11615,8 +12113,8 @@ function gitRelationship(gitRun, local, remote) {
11615
12113
  }
11616
12114
  function writeUpdateCache(p, payload) {
11617
12115
  try {
11618
- (0, import_node_fs13.mkdirSync)(import_node_path13.default.dirname(p), { recursive: true });
11619
- (0, import_node_fs13.writeFileSync)(p, JSON.stringify(payload, null, 2), "utf8");
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");
11620
12118
  } catch {
11621
12119
  }
11622
12120
  }
@@ -12631,8 +13129,8 @@ function pingTool() {
12631
13129
 
12632
13130
  // src/core/wrappers.ts
12633
13131
  init_cjs_shims();
12634
- var import_node_fs14 = require("fs");
12635
- var import_node_path14 = __toESM(require("path"), 1);
13132
+ var import_node_fs16 = require("fs");
13133
+ var import_node_path15 = __toESM(require("path"), 1);
12636
13134
  function configPinGate(toolName, opts) {
12637
13135
  if (toolName === "config_pin") return null;
12638
13136
  if (!opts || !opts.repoRoot) return null;
@@ -12664,8 +13162,8 @@ function configPinGate(toolName, opts) {
12664
13162
  var cachedUpdateNotice = null;
12665
13163
  function readUpdateCache(cachePath2) {
12666
13164
  try {
12667
- if (!(0, import_node_fs14.existsSync)(cachePath2)) return null;
12668
- 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"));
12669
13167
  if (data && typeof data === "object" && !Array.isArray(data)) return data;
12670
13168
  return null;
12671
13169
  } catch {
@@ -12677,7 +13175,7 @@ function attachUpdateNotice(result, toolName, opts) {
12677
13175
  if (toolName === "update_crosscheck") return;
12678
13176
  if ("update_notice" in result) return;
12679
13177
  if (cachedUpdateNotice === null) {
12680
- 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());
12681
13179
  const cached = readUpdateCache(cachePath2);
12682
13180
  if (cached && cached.update_available && cached.notice && typeof cached.notice === "object") {
12683
13181
  cachedUpdateNotice = cached.notice;
@@ -12875,7 +13373,7 @@ async function flush() {
12875
13373
 
12876
13374
  // src/server.ts
12877
13375
  var SERVER_NAME = "crosscheck-agent";
12878
- var SERVER_VERSION = true ? "0.1.10" : "0.0.0-dev";
13376
+ var SERVER_VERSION = true ? "0.1.12" : "0.0.0-dev";
12879
13377
  function extractRunSummaryText(out) {
12880
13378
  if (out === null || typeof out !== "object" || Array.isArray(out)) return void 0;
12881
13379
  const rs = out["run_summary"];
@@ -12897,14 +13395,14 @@ function createServer(opts = {}) {
12897
13395
  }
12898
13396
  );
12899
13397
  const tools = buildToolRegistry(opts);
12900
- server.setRequestHandler(import_types6.ListToolsRequestSchema, async () => ({
13398
+ server.setRequestHandler(import_types8.ListToolsRequestSchema, async () => ({
12901
13399
  tools: Array.from(tools.values()).map((t) => ({
12902
13400
  name: t.name,
12903
13401
  description: t.description,
12904
13402
  inputSchema: t.inputSchema
12905
13403
  }))
12906
13404
  }));
12907
- server.setRequestHandler(import_types6.CallToolRequestSchema, async (req) => {
13405
+ server.setRequestHandler(import_types8.CallToolRequestSchema, async (req) => {
12908
13406
  const name = req.params.name;
12909
13407
  const tool = tools.get(name);
12910
13408
  if (!tool) {
@@ -13028,6 +13526,10 @@ function buildToolRegistry(opts) {
13028
13526
  if (opts.nodeCache) registerOpts.nodeCache = opts.nodeCache;
13029
13527
  if (opts.configPinning) registerOpts.configPinning = opts.configPinning;
13030
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;
13031
13533
  const tools = registerCoreTools(registerOpts);
13032
13534
  if (!opts.bridge) return tools;
13033
13535
  const proxies = buildPythonProxies(opts.bridge);
@@ -13037,29 +13539,207 @@ function buildToolRegistry(opts) {
13037
13539
  return tools;
13038
13540
  }
13039
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
+
13040
13714
  // src/entrypoints/node-stdio.ts
13041
13715
  var SHUTDOWN_TIMEOUT_MS = 2e3;
13042
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
+ }
13043
13733
  setEventEmitter(buildDefaultEmitter(process.env));
13044
- let bridge;
13045
13734
  if (process.env["CROSSCHECK_BRIDGE_PYTHON"] === "1") {
13046
- bridge = await spawnPythonBridge({
13047
- ...process.env["CROSSCHECK_PYTHON_PATH"] ? { pythonPath: process.env["CROSSCHECK_PYTHON_PATH"] } : {},
13048
- ...process.env["CROSSCHECK_PYTHON_SERVER"] ? { serverPath: process.env["CROSSCHECK_PYTHON_SERVER"] } : {}
13049
- });
13050
13735
  process.stderr.write(
13051
- `crosscheck-agent: bridge online (pid=${bridge.pid ?? "?"}); proxying ${bridge.toolNames.size} Python tool(s)
13052
- `
13053
- );
13054
- } else {
13055
- process.stderr.write(
13056
- "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"
13057
13737
  );
13058
13738
  }
13059
- installShutdownHandlers(bridge);
13739
+ installShutdownHandlers();
13060
13740
  const bundledPricing = (() => {
13061
13741
  try {
13062
- 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");
13063
13743
  } catch {
13064
13744
  return void 0;
13065
13745
  }
@@ -13068,7 +13748,7 @@ async function main() {
13068
13748
  process.env["CROSSCHECK_PRICING_PATH"],
13069
13749
  resolveRepoFile("config/pricing.json"),
13070
13750
  bundledPricing
13071
- ].find((p) => p && (0, import_node_fs15.existsSync)(p));
13751
+ ].find((p) => p && (0, import_node_fs18.existsSync)(p));
13072
13752
  const pricing = pricingPath ? loadPricing(pricingPath) : {};
13073
13753
  const providers = buildProviders({ env: process.env, pricing });
13074
13754
  if (Object.keys(providers).length > 0) {
@@ -13082,7 +13762,7 @@ async function main() {
13082
13762
  const dbPath = process.env["CROSSCHECK_DB_PATH"] ?? resolveRepoFile(".crosscheck/db.sqlite");
13083
13763
  if (dbPath) {
13084
13764
  try {
13085
- (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 });
13086
13766
  const { openBetterSqliteStorage: openBetterSqliteStorage2 } = await Promise.resolve().then(() => (init_better_sqlite3(), better_sqlite3_exports));
13087
13767
  storage = openBetterSqliteStorage2({ path: dbPath });
13088
13768
  await storage.migrate();
@@ -13099,33 +13779,32 @@ async function main() {
13099
13779
  }
13100
13780
  }
13101
13781
  }
13102
- 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");
13103
13783
  const repoRoot = process.env["CROSSCHECK_REPO_ROOT"] ?? findGitRoot(__dirname);
13104
- const transport = new import_stdio2.StdioServerTransport();
13784
+ const transport = new import_stdio.StdioServerTransport();
13105
13785
  const serverOpts = { providers };
13106
- if (bridge) serverOpts.bridge = bridge;
13107
13786
  if (storage) serverOpts.storage = storage;
13108
13787
  if (transcriptsDir) serverOpts.transcriptsDir = transcriptsDir;
13109
13788
  if (repoRoot) serverOpts.repoRoot = repoRoot;
13110
13789
  if (pricing && Object.keys(pricing).length > 0) {
13111
13790
  serverOpts.pricing = pricing;
13112
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;
13113
13801
  await connectAndServe(transport, serverOpts);
13114
13802
  }
13115
- function installShutdownHandlers(bridge) {
13803
+ function installShutdownHandlers() {
13116
13804
  let shuttingDown = false;
13117
13805
  const shutdown = async (reason) => {
13118
13806
  if (shuttingDown) return;
13119
13807
  shuttingDown = true;
13120
- if (bridge) {
13121
- await Promise.race([
13122
- bridge.close(),
13123
- new Promise(
13124
- (resolve2) => setTimeout(resolve2, SHUTDOWN_TIMEOUT_MS)
13125
- )
13126
- ]).catch(() => {
13127
- });
13128
- }
13129
13808
  await Promise.race([
13130
13809
  flush(),
13131
13810
  new Promise((resolve2) => setTimeout(resolve2, SHUTDOWN_TIMEOUT_MS))
@@ -13151,9 +13830,9 @@ function installShutdownHandlers(bridge) {
13151
13830
  function resolveRepoFile(rel) {
13152
13831
  let dir = __dirname;
13153
13832
  for (let i = 0; i < 8; i++) {
13154
- const candidate = import_node_path15.default.join(dir, rel);
13155
- if ((0, import_node_fs15.existsSync)(candidate)) return candidate;
13156
- const parent = import_node_path15.default.dirname(dir);
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);
13157
13836
  if (parent === dir) break;
13158
13837
  dir = parent;
13159
13838
  }
@@ -13162,8 +13841,8 @@ function resolveRepoFile(rel) {
13162
13841
  function findGitRoot(startDir) {
13163
13842
  let dir = startDir;
13164
13843
  for (let i = 0; i < 12; i++) {
13165
- if ((0, import_node_fs15.existsSync)(import_node_path15.default.join(dir, ".git"))) return dir;
13166
- const parent = import_node_path15.default.dirname(dir);
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);
13167
13846
  if (parent === dir) break;
13168
13847
  dir = parent;
13169
13848
  }