crosscheck-mcp 0.2.14 → 0.2.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser-ext.cjs +154 -74
- package/dist/browser-ext.cjs.map +1 -1
- package/dist/browser-ext.js +154 -74
- package/dist/browser-ext.js.map +1 -1
- package/dist/node-stdio.cjs +573 -268
- package/dist/node-stdio.cjs.map +1 -1
- package/dist/node-stdio.d.cts +9 -0
- package/dist/node-stdio.d.ts +9 -0
- package/dist/node-stdio.js +569 -264
- package/dist/node-stdio.js.map +1 -1
- package/package.json +1 -1
package/dist/node-stdio.js
CHANGED
|
@@ -934,8 +934,8 @@ var init_better_sqlite3 = __esm({
|
|
|
934
934
|
|
|
935
935
|
// src/entrypoints/node-stdio.ts
|
|
936
936
|
init_esm_shims();
|
|
937
|
-
import { existsSync as
|
|
938
|
-
import
|
|
937
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync10 } from "fs";
|
|
938
|
+
import path15 from "path";
|
|
939
939
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
940
940
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
941
941
|
|
|
@@ -952,10 +952,10 @@ var StderrEmitter = class {
|
|
|
952
952
|
}
|
|
953
953
|
};
|
|
954
954
|
var FileEmitter = class {
|
|
955
|
-
constructor(
|
|
956
|
-
this.path =
|
|
955
|
+
constructor(path16) {
|
|
956
|
+
this.path = path16;
|
|
957
957
|
try {
|
|
958
|
-
mkdirSync(dirname(
|
|
958
|
+
mkdirSync(dirname(path16), { recursive: true });
|
|
959
959
|
} catch {
|
|
960
960
|
}
|
|
961
961
|
}
|
|
@@ -988,13 +988,13 @@ var NullEmitter = class {
|
|
|
988
988
|
function buildDefaultEmitter(env) {
|
|
989
989
|
const mode = (env["CROSSCHECK_EVENTS"] ?? "stderr").toLowerCase();
|
|
990
990
|
if (mode === "off") return new NullEmitter();
|
|
991
|
-
const
|
|
991
|
+
const path16 = env["CROSSCHECK_EVENTS_PATH"];
|
|
992
992
|
if (mode === "file") {
|
|
993
|
-
return
|
|
993
|
+
return path16 ? new FileEmitter(path16) : new NullEmitter();
|
|
994
994
|
}
|
|
995
995
|
if (mode === "both") {
|
|
996
996
|
const ems = [new StderrEmitter()];
|
|
997
|
-
if (
|
|
997
|
+
if (path16) ems.push(new FileEmitter(path16));
|
|
998
998
|
return new TeeEmitter(ems);
|
|
999
999
|
}
|
|
1000
1000
|
return new StderrEmitter();
|
|
@@ -1043,10 +1043,10 @@ function envelopeBytes(v) {
|
|
|
1043
1043
|
// src/core/pricing.ts
|
|
1044
1044
|
init_esm_shims();
|
|
1045
1045
|
import { readFileSync } from "fs";
|
|
1046
|
-
function loadPricing(
|
|
1046
|
+
function loadPricing(path16) {
|
|
1047
1047
|
let raw;
|
|
1048
1048
|
try {
|
|
1049
|
-
raw = readFileSync(
|
|
1049
|
+
raw = readFileSync(path16, "utf8");
|
|
1050
1050
|
} catch {
|
|
1051
1051
|
return {};
|
|
1052
1052
|
}
|
|
@@ -1218,11 +1218,18 @@ var ProviderError = class extends Error {
|
|
|
1218
1218
|
status;
|
|
1219
1219
|
transient;
|
|
1220
1220
|
retryAfterS;
|
|
1221
|
+
/** True specifically for "the key is fine but this model isn't
|
|
1222
|
+
* available to this account/project" (see http-errors.ts's
|
|
1223
|
+
* isModelAccessFailure) — the one failure a model fallback chain can
|
|
1224
|
+
* actually fix. Distinct from `transient`: this is never worth
|
|
1225
|
+
* retrying the SAME model, but IS worth trying a different one. */
|
|
1226
|
+
modelAccessFailure;
|
|
1221
1227
|
constructor(kind, message, opts) {
|
|
1222
1228
|
super(message);
|
|
1223
1229
|
this.kind = kind;
|
|
1224
1230
|
this.status = opts?.status;
|
|
1225
1231
|
this.transient = opts?.transient ?? defaultTransient(kind);
|
|
1232
|
+
this.modelAccessFailure = opts?.modelAccessFailure ?? false;
|
|
1226
1233
|
if (opts?.retryAfterS !== void 0) {
|
|
1227
1234
|
this.retryAfterS = opts.retryAfterS;
|
|
1228
1235
|
}
|
|
@@ -1254,7 +1261,7 @@ function httpFailureToProviderError(provider, status, bodyText, retryAfterS, mod
|
|
|
1254
1261
|
return new ProviderError(
|
|
1255
1262
|
"client",
|
|
1256
1263
|
`${provider}: your account/project doesn't have access to ${modelRef} (HTTP ${status}). This is a model-access problem, not a key problem \u2014 your ${provider} API key is fine, ${modelRef} just isn't enabled for this account/project. Fix: run \`crosscheck models set ${provider} <a-model-you-have-access-to>\` (or set ${provider.toUpperCase()}_MODEL in your .env until that's available). Detail: ${detail}`,
|
|
1257
|
-
{ status }
|
|
1264
|
+
{ status, modelAccessFailure: true }
|
|
1258
1265
|
);
|
|
1259
1266
|
}
|
|
1260
1267
|
if (isCreditsFailure(status, bodyText)) {
|
|
@@ -1295,8 +1302,8 @@ function httpFailureToProviderError(provider, status, bodyText, retryAfterS, mod
|
|
|
1295
1302
|
// src/providers/retry.ts
|
|
1296
1303
|
init_esm_shims();
|
|
1297
1304
|
var override = null;
|
|
1298
|
-
function setRetryConfig(
|
|
1299
|
-
override =
|
|
1305
|
+
function setRetryConfig(cfg2) {
|
|
1306
|
+
override = cfg2;
|
|
1300
1307
|
}
|
|
1301
1308
|
function resolveRetryConfig() {
|
|
1302
1309
|
const envInt = (k, d) => {
|
|
@@ -1358,21 +1365,21 @@ function defaultSleep(ms, signal) {
|
|
|
1358
1365
|
signal?.addEventListener?.("abort", onAbort);
|
|
1359
1366
|
});
|
|
1360
1367
|
}
|
|
1361
|
-
async function sendWithRetry(attemptOnce,
|
|
1368
|
+
async function sendWithRetry(attemptOnce, cfg2, signal, deps) {
|
|
1362
1369
|
const sleep = deps?.sleepImpl ?? ((ms) => defaultSleep(ms, signal));
|
|
1363
1370
|
const rand = deps?.random ?? Math.random;
|
|
1364
1371
|
let lastErr;
|
|
1365
|
-
for (let attempt = 1; attempt <=
|
|
1372
|
+
for (let attempt = 1; attempt <= cfg2.maxAttempts; attempt += 1) {
|
|
1366
1373
|
try {
|
|
1367
1374
|
const result = await attemptOnce();
|
|
1368
1375
|
return { ...result, attempts: attempt };
|
|
1369
1376
|
} catch (e) {
|
|
1370
1377
|
lastErr = e;
|
|
1371
1378
|
const pe = e instanceof ProviderError ? e : null;
|
|
1372
|
-
if (pe?.transient !== true || attempt >=
|
|
1373
|
-
let backoffMs = pe.retryAfterS !== void 0 ? pe.retryAfterS * 1e3 :
|
|
1374
|
-
backoffMs += rand() *
|
|
1375
|
-
backoffMs = Math.min(backoffMs,
|
|
1379
|
+
if (pe?.transient !== true || attempt >= cfg2.maxAttempts) throw e;
|
|
1380
|
+
let backoffMs = pe.retryAfterS !== void 0 ? pe.retryAfterS * 1e3 : cfg2.backoffBaseMs * 2 ** (attempt - 1);
|
|
1381
|
+
backoffMs += rand() * cfg2.backoffBaseMs;
|
|
1382
|
+
backoffMs = Math.min(backoffMs, cfg2.maxBackoffMs);
|
|
1376
1383
|
await sleep(backoffMs);
|
|
1377
1384
|
}
|
|
1378
1385
|
}
|
|
@@ -1384,8 +1391,8 @@ init_esm_shims();
|
|
|
1384
1391
|
var DEFAULT_SPEC = { capacity: 5, refillPerSec: 5 };
|
|
1385
1392
|
var MAX_WAIT_SLICE_MS = 250;
|
|
1386
1393
|
var override2 = null;
|
|
1387
|
-
function setRateLimitConfig(
|
|
1388
|
-
override2 =
|
|
1394
|
+
function setRateLimitConfig(cfg2) {
|
|
1395
|
+
override2 = cfg2;
|
|
1389
1396
|
buckets.clear();
|
|
1390
1397
|
}
|
|
1391
1398
|
function disabled() {
|
|
@@ -1470,6 +1477,14 @@ function buildAnthropicRequest(opts) {
|
|
|
1470
1477
|
if (system !== void 0) {
|
|
1471
1478
|
body.system = system;
|
|
1472
1479
|
}
|
|
1480
|
+
if (isReasoningModel("anthropic", opts.model)) {
|
|
1481
|
+
if (opts.model.toLowerCase().startsWith("claude-fable-5")) {
|
|
1482
|
+
body.thinking = { type: "adaptive" };
|
|
1483
|
+
body.output_config = { effort: "low" };
|
|
1484
|
+
} else {
|
|
1485
|
+
body.thinking = { type: "disabled" };
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1473
1488
|
if (opts.jsonSchema) {
|
|
1474
1489
|
body.tools = [{
|
|
1475
1490
|
name: ANTHROPIC_STRUCTURED_TOOL_NAME,
|
|
@@ -1537,7 +1552,12 @@ function parseAnthropicResponse(opts) {
|
|
|
1537
1552
|
purpose: opts.purpose
|
|
1538
1553
|
};
|
|
1539
1554
|
usage.total_tokens = usage.prompt_tokens + usage.completion_tokens;
|
|
1540
|
-
|
|
1555
|
+
const stopReasonRaw = r["stop_reason"];
|
|
1556
|
+
const stopReason = typeof stopReasonRaw === "string" ? stopReasonRaw : void 0;
|
|
1557
|
+
return { text, usage, ...stopReason !== void 0 ? { stopReason } : {} };
|
|
1558
|
+
}
|
|
1559
|
+
function isEmptyDueToMaxTokens(text, stopReason) {
|
|
1560
|
+
return text.trim() === "" && stopReason === "max_tokens";
|
|
1541
1561
|
}
|
|
1542
1562
|
function applyPricing(usage, pricing) {
|
|
1543
1563
|
const { cost_usd, estimated } = calculateCost(
|
|
@@ -1555,49 +1575,64 @@ function applyPricing(usage, pricing) {
|
|
|
1555
1575
|
estimated: usage.estimated || estimated
|
|
1556
1576
|
};
|
|
1557
1577
|
}
|
|
1578
|
+
var MAX_TOKENS_ESCALATIONS = 2;
|
|
1579
|
+
var ESCALATION_MULTIPLIER = 2;
|
|
1558
1580
|
async function sendAnthropic(args) {
|
|
1559
|
-
const { url, headers, body } = buildAnthropicRequest({
|
|
1560
|
-
model: args.model,
|
|
1561
|
-
apiKey: args.apiKey,
|
|
1562
|
-
messages: args.messages,
|
|
1563
|
-
maxTokens: args.maxTokens,
|
|
1564
|
-
temperature: args.temperature,
|
|
1565
|
-
...args.jsonSchema ? { jsonSchema: args.jsonSchema } : {}
|
|
1566
|
-
});
|
|
1567
1581
|
const doFetch = args.fetchImpl ?? globalThis.fetch;
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1582
|
+
let maxTokens = args.maxTokens;
|
|
1583
|
+
let result = null;
|
|
1584
|
+
let totalAttempts = 0;
|
|
1585
|
+
for (let escalation = 0; escalation <= MAX_TOKENS_ESCALATIONS; escalation += 1) {
|
|
1586
|
+
const { url, headers, body } = buildAnthropicRequest({
|
|
1587
|
+
model: args.model,
|
|
1588
|
+
apiKey: args.apiKey,
|
|
1589
|
+
messages: args.messages,
|
|
1590
|
+
maxTokens,
|
|
1591
|
+
temperature: args.temperature,
|
|
1592
|
+
...args.jsonSchema ? { jsonSchema: args.jsonSchema } : {}
|
|
1593
|
+
});
|
|
1594
|
+
const init = {
|
|
1595
|
+
method: "POST",
|
|
1596
|
+
headers,
|
|
1597
|
+
body: JSON.stringify(body)
|
|
1598
|
+
};
|
|
1599
|
+
if (args.signal) init.signal = args.signal;
|
|
1600
|
+
let stopReason;
|
|
1601
|
+
const attemptOnce = async () => {
|
|
1602
|
+
let respLike;
|
|
1584
1603
|
try {
|
|
1585
|
-
|
|
1604
|
+
respLike = await doFetch(url, init);
|
|
1586
1605
|
} catch (e) {
|
|
1587
|
-
throw new ProviderError("
|
|
1606
|
+
throw new ProviderError("network", `anthropic: fetch failed: ${e.message}`);
|
|
1588
1607
|
}
|
|
1589
|
-
const
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1608
|
+
const status = respLike.status;
|
|
1609
|
+
if (status >= 200 && status < 300) {
|
|
1610
|
+
let parsed;
|
|
1611
|
+
try {
|
|
1612
|
+
parsed = await respLike.json();
|
|
1613
|
+
} catch (e) {
|
|
1614
|
+
throw new ProviderError("parse", `anthropic: response body not JSON: ${e.message}`);
|
|
1615
|
+
}
|
|
1616
|
+
const { text, usage, stopReason: sr } = parseAnthropicResponse({
|
|
1617
|
+
resp: parsed,
|
|
1618
|
+
model: args.model,
|
|
1619
|
+
purpose: args.purpose ?? "worker"
|
|
1620
|
+
});
|
|
1621
|
+
stopReason = sr;
|
|
1622
|
+
return { text, attempts: 1, usage: applyPricing(usage, args.pricing) };
|
|
1623
|
+
}
|
|
1624
|
+
const bodyText = await respLike.text().catch(() => "");
|
|
1625
|
+
throw httpFailureToProviderError("anthropic", status, bodyText, parseRetryAfter(respLike), args.model);
|
|
1626
|
+
};
|
|
1627
|
+
await acquireRateLimit("anthropic", args.signal ? { signal: args.signal } : void 0);
|
|
1628
|
+
result = await sendWithRetry(attemptOnce, resolveRetryConfig(), args.signal);
|
|
1629
|
+
totalAttempts += result.attempts;
|
|
1630
|
+
if (escalation >= MAX_TOKENS_ESCALATIONS || !isEmptyDueToMaxTokens(result.text, stopReason)) {
|
|
1631
|
+
break;
|
|
1595
1632
|
}
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
};
|
|
1599
|
-
await acquireRateLimit("anthropic", args.signal ? { signal: args.signal } : void 0);
|
|
1600
|
-
return sendWithRetry(attemptOnce, resolveRetryConfig(), args.signal);
|
|
1633
|
+
maxTokens *= ESCALATION_MULTIPLIER;
|
|
1634
|
+
}
|
|
1635
|
+
return { ...result, attempts: totalAttempts };
|
|
1601
1636
|
}
|
|
1602
1637
|
|
|
1603
1638
|
// src/providers/gemini.ts
|
|
@@ -1983,19 +2018,27 @@ function detectStalePins(env) {
|
|
|
1983
2018
|
const out = [];
|
|
1984
2019
|
for (const [provider, envVar] of Object.entries(MODEL_ENV_VARS)) {
|
|
1985
2020
|
const pinned = env[envVar];
|
|
1986
|
-
const
|
|
1987
|
-
if (pinned &&
|
|
1988
|
-
out.push({ provider, envVar, pinned, current });
|
|
2021
|
+
const current2 = DEFAULT_MODELS[provider];
|
|
2022
|
+
if (pinned && current2 && pinned !== current2) {
|
|
2023
|
+
out.push({ provider, envVar, pinned, current: current2 });
|
|
1989
2024
|
}
|
|
1990
2025
|
}
|
|
1991
2026
|
return out;
|
|
1992
2027
|
}
|
|
2028
|
+
function parseFallbackModels(raw) {
|
|
2029
|
+
if (!raw) return [];
|
|
2030
|
+
return raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
2031
|
+
}
|
|
2032
|
+
function fallbackModelsFor(env, provider) {
|
|
2033
|
+
const envVar = MODEL_ENV_VARS[provider];
|
|
2034
|
+
return envVar ? parseFallbackModels(env[`${envVar}_FALLBACKS`]) : [];
|
|
2035
|
+
}
|
|
1993
2036
|
function buildProviders(opts) {
|
|
1994
2037
|
const out = {};
|
|
1995
2038
|
const anthropicKey = opts.env["ANTHROPIC_API_KEY"];
|
|
1996
2039
|
if (anthropicKey) {
|
|
1997
2040
|
const model = opts.env["ANTHROPIC_MODEL"] ?? DEFAULT_MODELS.anthropic;
|
|
1998
|
-
out["anthropic"] = makeAnthropicProvider(model, anthropicKey, opts);
|
|
2041
|
+
out["anthropic"] = makeAnthropicProvider(model, anthropicKey, opts, fallbackModelsFor(opts.env, "anthropic"));
|
|
1999
2042
|
}
|
|
2000
2043
|
const openAiCompatSpec = [
|
|
2001
2044
|
{ name: "openai", keyEnv: "OPENAI_API_KEY", modelEnv: "OPENAI_MODEL", defaultModel: DEFAULT_MODELS["openai"] },
|
|
@@ -2010,19 +2053,20 @@ function buildProviders(opts) {
|
|
|
2010
2053
|
const apiKey = opts.env[s.keyEnv];
|
|
2011
2054
|
if (!apiKey) continue;
|
|
2012
2055
|
const model = opts.env[s.modelEnv] ?? s.defaultModel;
|
|
2013
|
-
out[s.name] = makeOpenAICompatibleProvider(s.name, model, apiKey, opts);
|
|
2056
|
+
out[s.name] = makeOpenAICompatibleProvider(s.name, model, apiKey, opts, fallbackModelsFor(opts.env, s.name));
|
|
2014
2057
|
}
|
|
2015
2058
|
const geminiKey = opts.env["GEMINI_API_KEY"];
|
|
2016
2059
|
if (geminiKey) {
|
|
2017
2060
|
const model = opts.env["GEMINI_MODEL"] ?? DEFAULT_MODELS.gemini;
|
|
2018
|
-
out["gemini"] = makeGeminiProvider(model, geminiKey, opts);
|
|
2061
|
+
out["gemini"] = makeGeminiProvider(model, geminiKey, opts, fallbackModelsFor(opts.env, "gemini"));
|
|
2019
2062
|
}
|
|
2020
2063
|
return out;
|
|
2021
2064
|
}
|
|
2022
|
-
function makeAnthropicProvider(model, apiKey, opts) {
|
|
2065
|
+
function makeAnthropicProvider(model, apiKey, opts, fallbackModels = []) {
|
|
2023
2066
|
return {
|
|
2024
2067
|
name: "anthropic",
|
|
2025
2068
|
model,
|
|
2069
|
+
fallbackModels,
|
|
2026
2070
|
send: async (args) => {
|
|
2027
2071
|
const sendOpts = {
|
|
2028
2072
|
...args,
|
|
@@ -2035,11 +2079,12 @@ function makeAnthropicProvider(model, apiKey, opts) {
|
|
|
2035
2079
|
}
|
|
2036
2080
|
};
|
|
2037
2081
|
}
|
|
2038
|
-
function makeOpenAICompatibleProvider(name, model, apiKey, opts) {
|
|
2082
|
+
function makeOpenAICompatibleProvider(name, model, apiKey, opts, fallbackModels = []) {
|
|
2039
2083
|
const url = OPENAI_COMPAT_DEFAULT_URLS[name];
|
|
2040
2084
|
return {
|
|
2041
2085
|
name,
|
|
2042
2086
|
model,
|
|
2087
|
+
fallbackModels,
|
|
2043
2088
|
send: async (args) => {
|
|
2044
2089
|
const sendOpts = {
|
|
2045
2090
|
...args,
|
|
@@ -2054,10 +2099,11 @@ function makeOpenAICompatibleProvider(name, model, apiKey, opts) {
|
|
|
2054
2099
|
}
|
|
2055
2100
|
};
|
|
2056
2101
|
}
|
|
2057
|
-
function makeGeminiProvider(model, apiKey, opts) {
|
|
2102
|
+
function makeGeminiProvider(model, apiKey, opts, fallbackModels = []) {
|
|
2058
2103
|
return {
|
|
2059
2104
|
name: "gemini",
|
|
2060
2105
|
model,
|
|
2106
|
+
fallbackModels,
|
|
2061
2107
|
send: async (args) => {
|
|
2062
2108
|
const sendOpts = {
|
|
2063
2109
|
...args,
|
|
@@ -2208,7 +2254,7 @@ import { z } from "zod";
|
|
|
2208
2254
|
// src/server-meta.ts
|
|
2209
2255
|
init_esm_shims();
|
|
2210
2256
|
var SERVER_NAME = "crosscheck-agent";
|
|
2211
|
-
var SERVER_VERSION = true ? "0.2.
|
|
2257
|
+
var SERVER_VERSION = true ? "0.2.16" : "0.0.0-dev";
|
|
2212
2258
|
|
|
2213
2259
|
// src/tools/audit.ts
|
|
2214
2260
|
init_esm_shims();
|
|
@@ -2275,27 +2321,27 @@ function extractJson(text) {
|
|
|
2275
2321
|
|
|
2276
2322
|
// src/core/json-schema.ts
|
|
2277
2323
|
init_esm_shims();
|
|
2278
|
-
function validateSchema(value, schema,
|
|
2324
|
+
function validateSchema(value, schema, path16 = "") {
|
|
2279
2325
|
const errs = [];
|
|
2280
2326
|
if ("anyOf" in schema) {
|
|
2281
2327
|
const subs = schema["anyOf"].map(
|
|
2282
|
-
(s) => validateSchema(value, s,
|
|
2328
|
+
(s) => validateSchema(value, s, path16)
|
|
2283
2329
|
);
|
|
2284
2330
|
if (!subs.some((e) => e.length === 0)) {
|
|
2285
|
-
errs.push(`${
|
|
2331
|
+
errs.push(`${path16 || "<root>"}: did not match anyOf`);
|
|
2286
2332
|
}
|
|
2287
2333
|
return errs;
|
|
2288
2334
|
}
|
|
2289
2335
|
if ("oneOf" in schema) {
|
|
2290
|
-
const passed = schema["oneOf"].filter((s) => validateSchema(value, s,
|
|
2336
|
+
const passed = schema["oneOf"].filter((s) => validateSchema(value, s, path16).length === 0).length;
|
|
2291
2337
|
if (passed !== 1) {
|
|
2292
|
-
errs.push(`${
|
|
2338
|
+
errs.push(`${path16 || "<root>"}: matched ${passed} of oneOf, expected 1`);
|
|
2293
2339
|
}
|
|
2294
2340
|
return errs;
|
|
2295
2341
|
}
|
|
2296
2342
|
if ("const" in schema && !deepEqual(value, schema["const"])) {
|
|
2297
2343
|
errs.push(
|
|
2298
|
-
`${
|
|
2344
|
+
`${path16 || "<root>"}: expected const ${pyRepr(schema["const"])}, got ${pyRepr(value)}`
|
|
2299
2345
|
);
|
|
2300
2346
|
return errs;
|
|
2301
2347
|
}
|
|
@@ -2305,7 +2351,7 @@ function validateSchema(value, schema, path15 = "") {
|
|
|
2305
2351
|
const ok = types.some((tt) => matchesType(value, String(tt)));
|
|
2306
2352
|
if (!ok) {
|
|
2307
2353
|
errs.push(
|
|
2308
|
-
`${
|
|
2354
|
+
`${path16 || "<root>"}: expected type ${pyReprType(t)}, got ${pyTypeName(value)}`
|
|
2309
2355
|
);
|
|
2310
2356
|
return errs;
|
|
2311
2357
|
}
|
|
@@ -2314,29 +2360,29 @@ function validateSchema(value, schema, path15 = "") {
|
|
|
2314
2360
|
if ("enum" in schema) {
|
|
2315
2361
|
const en = schema["enum"];
|
|
2316
2362
|
if (!en.some((x) => deepEqual(x, value))) {
|
|
2317
|
-
errs.push(`${
|
|
2363
|
+
errs.push(`${path16 || "<root>"}: value ${pyRepr(value)} not in enum`);
|
|
2318
2364
|
}
|
|
2319
2365
|
}
|
|
2320
2366
|
if ("minLength" in schema && value.length < schema["minLength"]) {
|
|
2321
|
-
errs.push(`${
|
|
2367
|
+
errs.push(`${path16 || "<root>"}: shorter than minLength ${schema["minLength"]}`);
|
|
2322
2368
|
}
|
|
2323
2369
|
}
|
|
2324
2370
|
if (typeof value === "number" && !Number.isNaN(value)) {
|
|
2325
2371
|
if ("minimum" in schema && value < schema["minimum"]) {
|
|
2326
|
-
errs.push(`${
|
|
2372
|
+
errs.push(`${path16 || "<root>"}: ${value} < minimum ${schema["minimum"]}`);
|
|
2327
2373
|
}
|
|
2328
2374
|
if ("maximum" in schema && value > schema["maximum"]) {
|
|
2329
|
-
errs.push(`${
|
|
2375
|
+
errs.push(`${path16 || "<root>"}: ${value} > maximum ${schema["maximum"]}`);
|
|
2330
2376
|
}
|
|
2331
2377
|
}
|
|
2332
2378
|
if (Array.isArray(value)) {
|
|
2333
2379
|
if ("minItems" in schema && value.length < schema["minItems"]) {
|
|
2334
|
-
errs.push(`${
|
|
2380
|
+
errs.push(`${path16 || "<root>"}: fewer items than minItems ${schema["minItems"]}`);
|
|
2335
2381
|
}
|
|
2336
2382
|
const itemSchema = schema["items"];
|
|
2337
2383
|
if (isObj(itemSchema)) {
|
|
2338
2384
|
for (let i = 0; i < value.length; i++) {
|
|
2339
|
-
errs.push(...validateSchema(value[i], itemSchema, `${
|
|
2385
|
+
errs.push(...validateSchema(value[i], itemSchema, `${path16}[${i}]`));
|
|
2340
2386
|
}
|
|
2341
2387
|
}
|
|
2342
2388
|
}
|
|
@@ -2345,19 +2391,19 @@ function validateSchema(value, schema, path15 = "") {
|
|
|
2345
2391
|
const required = schema["required"] ?? [];
|
|
2346
2392
|
for (const r of required) {
|
|
2347
2393
|
if (!(r in value)) {
|
|
2348
|
-
errs.push(`${
|
|
2394
|
+
errs.push(`${path16 || "<root>"}: missing required key ${pyRepr(r)}`);
|
|
2349
2395
|
}
|
|
2350
2396
|
}
|
|
2351
2397
|
if (schema["additionalProperties"] === false) {
|
|
2352
2398
|
for (const k of Object.keys(value)) {
|
|
2353
2399
|
if (!(k in props)) {
|
|
2354
|
-
errs.push(`${
|
|
2400
|
+
errs.push(`${path16 || "<root>"}: unknown key ${pyRepr(k)}`);
|
|
2355
2401
|
}
|
|
2356
2402
|
}
|
|
2357
2403
|
}
|
|
2358
2404
|
for (const [k, v] of Object.entries(value)) {
|
|
2359
2405
|
const ps = props[k];
|
|
2360
|
-
if (ps) errs.push(...validateSchema(v, ps,
|
|
2406
|
+
if (ps) errs.push(...validateSchema(v, ps, path16 ? `${path16}.${k}` : k));
|
|
2361
2407
|
}
|
|
2362
2408
|
}
|
|
2363
2409
|
return errs;
|
|
@@ -2858,8 +2904,8 @@ async function recordSessionCall(storage, sessionId, answers, wallMs, cpuMs, now
|
|
|
2858
2904
|
// src/core/budgets.ts
|
|
2859
2905
|
init_esm_shims();
|
|
2860
2906
|
var operatorBudgets = null;
|
|
2861
|
-
function setOperatorBudgets(
|
|
2862
|
-
operatorBudgets =
|
|
2907
|
+
function setOperatorBudgets(cfg2) {
|
|
2908
|
+
operatorBudgets = cfg2;
|
|
2863
2909
|
}
|
|
2864
2910
|
function operatorCeiling(purpose, provider) {
|
|
2865
2911
|
const direct = operatorBudgets?.token_budgets?.[purpose];
|
|
@@ -2870,6 +2916,55 @@ function operatorCeiling(purpose, provider) {
|
|
|
2870
2916
|
return null;
|
|
2871
2917
|
}
|
|
2872
2918
|
|
|
2919
|
+
// src/core/model-fallback.ts
|
|
2920
|
+
init_esm_shims();
|
|
2921
|
+
|
|
2922
|
+
// src/core/retarget.ts
|
|
2923
|
+
init_esm_shims();
|
|
2924
|
+
function retargetProvider(p, newModel) {
|
|
2925
|
+
if (p.model === newModel) return p;
|
|
2926
|
+
return {
|
|
2927
|
+
name: p.name,
|
|
2928
|
+
model: newModel,
|
|
2929
|
+
send: (args) => p.send({ ...args, modelOverride: newModel })
|
|
2930
|
+
};
|
|
2931
|
+
}
|
|
2932
|
+
async function loadProviderWeights(storage, names) {
|
|
2933
|
+
const out = {};
|
|
2934
|
+
for (const raw of names) {
|
|
2935
|
+
const name = raw.toLowerCase();
|
|
2936
|
+
if (name in out) continue;
|
|
2937
|
+
const row = await storage.getProviderStats(name);
|
|
2938
|
+
if (!row) {
|
|
2939
|
+
out[name] = 0.5;
|
|
2940
|
+
continue;
|
|
2941
|
+
}
|
|
2942
|
+
const total = row.wins + row.losses + row.abstains;
|
|
2943
|
+
out[name] = total <= 0 ? 0.5 : (row.wins + 0.5 * row.abstains) / total;
|
|
2944
|
+
}
|
|
2945
|
+
return out;
|
|
2946
|
+
}
|
|
2947
|
+
|
|
2948
|
+
// src/core/model-fallback.ts
|
|
2949
|
+
async function sendWithModelFallback(provider, args) {
|
|
2950
|
+
const chain = [provider.model, ...provider.fallbackModels ?? []];
|
|
2951
|
+
let lastErr;
|
|
2952
|
+
for (let i = 0; i < chain.length; i += 1) {
|
|
2953
|
+
const candidate = chain[i];
|
|
2954
|
+
const p = i === 0 ? provider : retargetProvider(provider, candidate);
|
|
2955
|
+
try {
|
|
2956
|
+
const result = await p.send(args);
|
|
2957
|
+
return { result, modelUsed: candidate };
|
|
2958
|
+
} catch (e) {
|
|
2959
|
+
lastErr = e;
|
|
2960
|
+
const isModelAccessFailure2 = e instanceof ProviderError && e.modelAccessFailure;
|
|
2961
|
+
const hasMoreCandidates = i < chain.length - 1;
|
|
2962
|
+
if (!isModelAccessFailure2 || !hasMoreCandidates) throw e;
|
|
2963
|
+
}
|
|
2964
|
+
}
|
|
2965
|
+
throw lastErr;
|
|
2966
|
+
}
|
|
2967
|
+
|
|
2873
2968
|
// src/core/structured.ts
|
|
2874
2969
|
async function requestStructured(provider, baseMessages, schema, opts) {
|
|
2875
2970
|
const maxRetries = opts.maxRetries ?? 1;
|
|
@@ -2948,7 +3043,7 @@ async function askOne(provider, messages, opts) {
|
|
|
2948
3043
|
const ceiling = operatorCeiling(opts.purpose, provider.name);
|
|
2949
3044
|
if (ceiling !== null && ceiling < maxTokens) maxTokens = ceiling;
|
|
2950
3045
|
try {
|
|
2951
|
-
const r = await provider
|
|
3046
|
+
const { result: r, modelUsed } = await sendWithModelFallback(provider, {
|
|
2952
3047
|
messages,
|
|
2953
3048
|
maxTokens,
|
|
2954
3049
|
temperature: opts.temperature,
|
|
@@ -2961,7 +3056,10 @@ async function askOne(provider, messages, opts) {
|
|
|
2961
3056
|
const cpuMs = Math.trunc((cpu.user + cpu.system) / 1e3);
|
|
2962
3057
|
return {
|
|
2963
3058
|
provider: provider.name,
|
|
2964
|
-
model
|
|
3059
|
+
// modelUsed reflects whichever model actually answered — the
|
|
3060
|
+
// primary, or a fallback if one was needed. Equivalent to the old
|
|
3061
|
+
// `provider.model` whenever no fallback occurred.
|
|
3062
|
+
model: modelUsed,
|
|
2965
3063
|
response: r.text,
|
|
2966
3064
|
attempts: r.attempts,
|
|
2967
3065
|
usage: r.usage,
|
|
@@ -3008,8 +3106,8 @@ function isPersonaId(s) {
|
|
|
3008
3106
|
return PERSONA_IDS.includes(s);
|
|
3009
3107
|
}
|
|
3010
3108
|
var personaConfig = null;
|
|
3011
|
-
function setPersonaConfig(
|
|
3012
|
-
personaConfig =
|
|
3109
|
+
function setPersonaConfig(cfg2) {
|
|
3110
|
+
personaConfig = cfg2;
|
|
3013
3111
|
}
|
|
3014
3112
|
var processFirstCallConsumed = false;
|
|
3015
3113
|
var sessionsSeen = /* @__PURE__ */ new Set();
|
|
@@ -3076,8 +3174,8 @@ function routePersona(prompt, toolName) {
|
|
|
3076
3174
|
return { id: "engineering", scores };
|
|
3077
3175
|
}
|
|
3078
3176
|
function resolvePersona(opts) {
|
|
3079
|
-
const
|
|
3080
|
-
if (
|
|
3177
|
+
const cfg2 = opts.config ?? personaConfig;
|
|
3178
|
+
if (cfg2?.enabled === false) return { id: null, source: "disabled", bypassed: true };
|
|
3081
3179
|
const explicit = opts.explicit?.trim().toLowerCase();
|
|
3082
3180
|
if (explicit === "none" || explicit === "off") {
|
|
3083
3181
|
return { id: null, source: "none", bypassed: true };
|
|
@@ -3123,12 +3221,12 @@ function readIfExists(p) {
|
|
|
3123
3221
|
}
|
|
3124
3222
|
}
|
|
3125
3223
|
function loadPersonaBody(id, opts) {
|
|
3126
|
-
const
|
|
3127
|
-
const cacheKey = `${id}|${opts?.repoRoot ?? ""}|${
|
|
3224
|
+
const cfg2 = opts?.config ?? personaConfig;
|
|
3225
|
+
const cacheKey = `${id}|${opts?.repoRoot ?? ""}|${cfg2?.dir ?? ""}`;
|
|
3128
3226
|
const cached = bodyCache.get(cacheKey);
|
|
3129
3227
|
if (cached !== void 0) return cached;
|
|
3130
3228
|
const candidates = [];
|
|
3131
|
-
if (
|
|
3229
|
+
if (cfg2?.dir) candidates.push(path2.join(cfg2.dir, `${id}.md`));
|
|
3132
3230
|
if (opts?.repoRoot) candidates.push(path2.join(opts.repoRoot, ".crosscheck", "personas", `${id}.md`));
|
|
3133
3231
|
if (id === "engineering" && opts?.repoRoot) candidates.push(path2.join(opts.repoRoot, "CLAUDE.md"));
|
|
3134
3232
|
const bundled = bundledPersonasDir();
|
|
@@ -3178,9 +3276,9 @@ function buildPersonaInjection(opts) {
|
|
|
3178
3276
|
|
|
3179
3277
|
// src/core/utils.ts
|
|
3180
3278
|
init_esm_shims();
|
|
3181
|
-
function checkSessionBreakers(session,
|
|
3279
|
+
function checkSessionBreakers(session, cfg2) {
|
|
3182
3280
|
if (!session || typeof session !== "object") return null;
|
|
3183
|
-
const c =
|
|
3281
|
+
const c = cfg2 ?? {};
|
|
3184
3282
|
const costCap = Number(c.max_session_cost_usd ?? 0);
|
|
3185
3283
|
if (costCap > 0 && Number(session.total_cost_usd ?? 0) >= costCap) {
|
|
3186
3284
|
return {
|
|
@@ -3204,10 +3302,10 @@ function checkSessionBreakers(session, cfg) {
|
|
|
3204
3302
|
}
|
|
3205
3303
|
return null;
|
|
3206
3304
|
}
|
|
3207
|
-
function checkDagBreakers(dag,
|
|
3305
|
+
function checkDagBreakers(dag, cfg2) {
|
|
3208
3306
|
if (!dag || typeof dag !== "object") return null;
|
|
3209
3307
|
const nodes = Array.isArray(dag.nodes) ? dag.nodes : [];
|
|
3210
|
-
const c =
|
|
3308
|
+
const c = cfg2 ?? {};
|
|
3211
3309
|
const maxNodes = Math.trunc(Number(c.max_dag_nodes ?? 0));
|
|
3212
3310
|
if (maxNodes > 0 && nodes.length > maxNodes) {
|
|
3213
3311
|
return {
|
|
@@ -3292,8 +3390,8 @@ function breakerEnvelope(toolName, trip, sessionId) {
|
|
|
3292
3390
|
transient: false
|
|
3293
3391
|
};
|
|
3294
3392
|
}
|
|
3295
|
-
async function maybeBreakerEnvelope(storage, sessionId, toolName,
|
|
3296
|
-
if (!sessionId || !storage || !
|
|
3393
|
+
async function maybeBreakerEnvelope(storage, sessionId, toolName, cfg2, extras) {
|
|
3394
|
+
if (!sessionId || !storage || !cfg2) return null;
|
|
3297
3395
|
let session;
|
|
3298
3396
|
try {
|
|
3299
3397
|
session = await storage.getSession(sessionId) ?? null;
|
|
@@ -3301,13 +3399,13 @@ async function maybeBreakerEnvelope(storage, sessionId, toolName, cfg, extras) {
|
|
|
3301
3399
|
return null;
|
|
3302
3400
|
}
|
|
3303
3401
|
const target = extras && extras.length > 0 ? projectSessionWithAnswers(session, extras) : session;
|
|
3304
|
-
const trip = checkSessionBreakers(target,
|
|
3402
|
+
const trip = checkSessionBreakers(target, cfg2);
|
|
3305
3403
|
if (!trip) return null;
|
|
3306
3404
|
return breakerEnvelope(toolName, trip, sessionId);
|
|
3307
3405
|
}
|
|
3308
|
-
function maybeDagBreakerEnvelope(dag, toolName,
|
|
3309
|
-
if (!
|
|
3310
|
-
const trip = checkDagBreakers(dag ?? null,
|
|
3406
|
+
function maybeDagBreakerEnvelope(dag, toolName, cfg2, sessionId) {
|
|
3407
|
+
if (!cfg2) return null;
|
|
3408
|
+
const trip = checkDagBreakers(dag ?? null, cfg2);
|
|
3311
3409
|
if (!trip) return null;
|
|
3312
3410
|
return breakerEnvelope(toolName, trip, sessionId);
|
|
3313
3411
|
}
|
|
@@ -3330,9 +3428,9 @@ function builtinRedactionRules() {
|
|
|
3330
3428
|
{ pattern: /\b(?:\d{4}[ -]?){3}\d{4}\b/g, label: "CARD", prefix_group: false }
|
|
3331
3429
|
];
|
|
3332
3430
|
}
|
|
3333
|
-
function compileRules(
|
|
3431
|
+
function compileRules(cfg2) {
|
|
3334
3432
|
const rules = builtinRedactionRules();
|
|
3335
|
-
const extras =
|
|
3433
|
+
const extras = cfg2?.patterns_extra ?? [];
|
|
3336
3434
|
for (const src of extras) {
|
|
3337
3435
|
try {
|
|
3338
3436
|
rules.push({ pattern: new RegExp(src, "g"), label: "EXTRA", prefix_group: false });
|
|
@@ -3341,25 +3439,25 @@ function compileRules(cfg) {
|
|
|
3341
3439
|
}
|
|
3342
3440
|
return { rules };
|
|
3343
3441
|
}
|
|
3344
|
-
function hmacSuffix(
|
|
3345
|
-
const secret =
|
|
3346
|
-
const sid =
|
|
3442
|
+
function hmacSuffix(cfg2, label, value) {
|
|
3443
|
+
const secret = cfg2.hmac_secret ?? Buffer.alloc(0);
|
|
3444
|
+
const sid = cfg2.session_id ?? "";
|
|
3347
3445
|
const sidBytes = Buffer.from(sid, "utf8");
|
|
3348
3446
|
const key = Buffer.concat([Buffer.from(secret), sidBytes]);
|
|
3349
3447
|
const digest = createHmac("sha256", key).update(`${label}:${value}`, "utf8").digest("hex");
|
|
3350
3448
|
return digest.slice(0, 8);
|
|
3351
3449
|
}
|
|
3352
|
-
function redactText(s,
|
|
3450
|
+
function redactText(s, cfg2) {
|
|
3353
3451
|
if (typeof s !== "string" || s.length === 0) return s;
|
|
3354
|
-
if (
|
|
3355
|
-
const { rules } = compileRules(
|
|
3356
|
-
const hmacMode = Boolean(
|
|
3452
|
+
if (cfg2 && cfg2.enabled === false) return s;
|
|
3453
|
+
const { rules } = compileRules(cfg2);
|
|
3454
|
+
const hmacMode = Boolean(cfg2?.hmac_tokens);
|
|
3357
3455
|
let out = s;
|
|
3358
3456
|
for (const { pattern, label, prefix_group } of rules) {
|
|
3359
3457
|
pattern.lastIndex = 0;
|
|
3360
3458
|
if (hmacMode) {
|
|
3361
3459
|
out = out.replace(pattern, (match, ...args) => {
|
|
3362
|
-
const suffix = hmacSuffix(
|
|
3460
|
+
const suffix = hmacSuffix(cfg2 ?? {}, label, match);
|
|
3363
3461
|
const token = `[REDACTED_${label}:${suffix}]`;
|
|
3364
3462
|
if (prefix_group) {
|
|
3365
3463
|
const g1 = args[0];
|
|
@@ -3379,23 +3477,23 @@ function redactText(s, cfg) {
|
|
|
3379
3477
|
}
|
|
3380
3478
|
return out;
|
|
3381
3479
|
}
|
|
3382
|
-
function redactObj(obj,
|
|
3383
|
-
if (typeof obj === "string") return redactText(obj,
|
|
3480
|
+
function redactObj(obj, cfg2) {
|
|
3481
|
+
if (typeof obj === "string") return redactText(obj, cfg2);
|
|
3384
3482
|
if (Array.isArray(obj)) {
|
|
3385
|
-
return obj.map((v) => redactObj(v,
|
|
3483
|
+
return obj.map((v) => redactObj(v, cfg2));
|
|
3386
3484
|
}
|
|
3387
3485
|
if (obj && typeof obj === "object") {
|
|
3388
3486
|
const out = {};
|
|
3389
3487
|
for (const [k, v] of Object.entries(obj)) {
|
|
3390
|
-
out[k] = redactObj(v,
|
|
3488
|
+
out[k] = redactObj(v, cfg2);
|
|
3391
3489
|
}
|
|
3392
3490
|
return out;
|
|
3393
3491
|
}
|
|
3394
3492
|
return obj;
|
|
3395
3493
|
}
|
|
3396
3494
|
var globalRedaction = null;
|
|
3397
|
-
function setRedactionConfig(
|
|
3398
|
-
globalRedaction =
|
|
3495
|
+
function setRedactionConfig(cfg2) {
|
|
3496
|
+
globalRedaction = cfg2;
|
|
3399
3497
|
}
|
|
3400
3498
|
function getRedactionConfig() {
|
|
3401
3499
|
return globalRedaction ?? void 0;
|
|
@@ -3682,32 +3780,6 @@ function numberOrNull(v) {
|
|
|
3682
3780
|
// src/tools/audit.ts
|
|
3683
3781
|
import { performance as performance2 } from "perf_hooks";
|
|
3684
3782
|
|
|
3685
|
-
// src/core/retarget.ts
|
|
3686
|
-
init_esm_shims();
|
|
3687
|
-
function retargetProvider(p, newModel) {
|
|
3688
|
-
if (p.model === newModel) return p;
|
|
3689
|
-
return {
|
|
3690
|
-
name: p.name,
|
|
3691
|
-
model: newModel,
|
|
3692
|
-
send: (args) => p.send({ ...args, modelOverride: newModel })
|
|
3693
|
-
};
|
|
3694
|
-
}
|
|
3695
|
-
async function loadProviderWeights(storage, names) {
|
|
3696
|
-
const out = {};
|
|
3697
|
-
for (const raw of names) {
|
|
3698
|
-
const name = raw.toLowerCase();
|
|
3699
|
-
if (name in out) continue;
|
|
3700
|
-
const row = await storage.getProviderStats(name);
|
|
3701
|
-
if (!row) {
|
|
3702
|
-
out[name] = 0.5;
|
|
3703
|
-
continue;
|
|
3704
|
-
}
|
|
3705
|
-
const total = row.wins + row.losses + row.abstains;
|
|
3706
|
-
out[name] = total <= 0 ? 0.5 : (row.wins + 0.5 * row.abstains) / total;
|
|
3707
|
-
}
|
|
3708
|
-
return out;
|
|
3709
|
-
}
|
|
3710
|
-
|
|
3711
3783
|
// src/core/co-reason.ts
|
|
3712
3784
|
init_esm_shims();
|
|
3713
3785
|
var CO_REASON_MODEL = "gpt-5.6";
|
|
@@ -4671,8 +4743,8 @@ function neutralizeInjection(s) {
|
|
|
4671
4743
|
|
|
4672
4744
|
// src/core/worker.ts
|
|
4673
4745
|
var WORKER_TOOL_COST_CAP_MODES = ["warn", "enforce", "off"];
|
|
4674
|
-
function workerToolCostCapDefaults(callerCapUsd, callerMode,
|
|
4675
|
-
const cfgObj =
|
|
4746
|
+
function workerToolCostCapDefaults(callerCapUsd, callerMode, cfg2) {
|
|
4747
|
+
const cfgObj = cfg2 ?? {};
|
|
4676
4748
|
let capRaw = callerCapUsd !== void 0 && callerCapUsd !== null ? callerCapUsd : cfgObj.cost_cap_usd;
|
|
4677
4749
|
let cap;
|
|
4678
4750
|
if (capRaw === void 0 || capRaw === null) {
|
|
@@ -6283,7 +6355,7 @@ function tryKill(child, signal, isUnix) {
|
|
|
6283
6355
|
init_esm_shims();
|
|
6284
6356
|
function shlexSplit(s) {
|
|
6285
6357
|
const out = [];
|
|
6286
|
-
let
|
|
6358
|
+
let current2 = "";
|
|
6287
6359
|
let hasCurrent = false;
|
|
6288
6360
|
let mode = "normal";
|
|
6289
6361
|
for (let i = 0; i < s.length; i++) {
|
|
@@ -6301,7 +6373,7 @@ function shlexSplit(s) {
|
|
|
6301
6373
|
}
|
|
6302
6374
|
if (ch === "\\") {
|
|
6303
6375
|
if (i + 1 < s.length) {
|
|
6304
|
-
|
|
6376
|
+
current2 += s[i + 1];
|
|
6305
6377
|
i++;
|
|
6306
6378
|
hasCurrent = true;
|
|
6307
6379
|
}
|
|
@@ -6309,13 +6381,13 @@ function shlexSplit(s) {
|
|
|
6309
6381
|
}
|
|
6310
6382
|
if (ch === " " || ch === " " || ch === "\n" || ch === "\r") {
|
|
6311
6383
|
if (hasCurrent) {
|
|
6312
|
-
out.push(
|
|
6313
|
-
|
|
6384
|
+
out.push(current2);
|
|
6385
|
+
current2 = "";
|
|
6314
6386
|
hasCurrent = false;
|
|
6315
6387
|
}
|
|
6316
6388
|
continue;
|
|
6317
6389
|
}
|
|
6318
|
-
|
|
6390
|
+
current2 += ch;
|
|
6319
6391
|
hasCurrent = true;
|
|
6320
6392
|
continue;
|
|
6321
6393
|
}
|
|
@@ -6324,7 +6396,7 @@ function shlexSplit(s) {
|
|
|
6324
6396
|
mode = "normal";
|
|
6325
6397
|
continue;
|
|
6326
6398
|
}
|
|
6327
|
-
|
|
6399
|
+
current2 += ch;
|
|
6328
6400
|
continue;
|
|
6329
6401
|
}
|
|
6330
6402
|
if (mode === "double") {
|
|
@@ -6335,21 +6407,21 @@ function shlexSplit(s) {
|
|
|
6335
6407
|
if (ch === "\\") {
|
|
6336
6408
|
const nxt = s[i + 1];
|
|
6337
6409
|
if (nxt === "$" || nxt === "`" || nxt === '"' || nxt === "\\" || nxt === "\n") {
|
|
6338
|
-
|
|
6410
|
+
current2 += nxt;
|
|
6339
6411
|
i++;
|
|
6340
6412
|
continue;
|
|
6341
6413
|
}
|
|
6342
|
-
|
|
6414
|
+
current2 += "\\";
|
|
6343
6415
|
continue;
|
|
6344
6416
|
}
|
|
6345
|
-
|
|
6417
|
+
current2 += ch;
|
|
6346
6418
|
continue;
|
|
6347
6419
|
}
|
|
6348
6420
|
}
|
|
6349
6421
|
if (mode !== "normal") {
|
|
6350
6422
|
throw new Error("unmatched quote");
|
|
6351
6423
|
}
|
|
6352
|
-
if (hasCurrent) out.push(
|
|
6424
|
+
if (hasCurrent) out.push(current2);
|
|
6353
6425
|
return out;
|
|
6354
6426
|
}
|
|
6355
6427
|
|
|
@@ -7064,16 +7136,16 @@ function computeDrift(opts) {
|
|
|
7064
7136
|
const absPath = resolvePinPath(opts);
|
|
7065
7137
|
const doc = loadDoc(absPath);
|
|
7066
7138
|
const pinned = doc && typeof doc === "object" && doc["pins"] && typeof doc["pins"] === "object" ? doc["pins"] : {};
|
|
7067
|
-
const
|
|
7139
|
+
const current2 = computeCurrent(opts);
|
|
7068
7140
|
const drift = [];
|
|
7069
7141
|
for (const [p, h] of Object.entries(pinned)) {
|
|
7070
|
-
if (
|
|
7142
|
+
if (current2[p] && current2[p] !== h) drift.push(p);
|
|
7071
7143
|
}
|
|
7072
|
-
const missingFromPin = Object.keys(
|
|
7073
|
-
const missingFiles = Object.keys(pinned).filter((p) => !(p in
|
|
7144
|
+
const missingFromPin = Object.keys(current2).filter((p) => !(p in pinned));
|
|
7145
|
+
const missingFiles = Object.keys(pinned).filter((p) => !(p in current2));
|
|
7074
7146
|
return {
|
|
7075
7147
|
pinned,
|
|
7076
|
-
current,
|
|
7148
|
+
current: current2,
|
|
7077
7149
|
drift: drift.sort(),
|
|
7078
7150
|
missing_from_pin: missingFromPin.sort(),
|
|
7079
7151
|
missing_files: missingFiles.sort(),
|
|
@@ -7368,19 +7440,19 @@ function sortKeyJsonifyTopLevel(json) {
|
|
|
7368
7440
|
for (const k of Object.keys(obj).sort()) sorted[k] = obj[k];
|
|
7369
7441
|
return JSON.stringify(sorted);
|
|
7370
7442
|
}
|
|
7371
|
-
function resolveCacheDir(
|
|
7372
|
-
const raw =
|
|
7443
|
+
function resolveCacheDir(cfg2, repoRoot) {
|
|
7444
|
+
const raw = cfg2.dir ?? ".crosscheck/node_cache";
|
|
7373
7445
|
if (isAbsolute(raw)) return raw;
|
|
7374
7446
|
return resolve(repoRoot ?? process.cwd(), raw);
|
|
7375
7447
|
}
|
|
7376
|
-
function cachePath(
|
|
7377
|
-
return join3(resolveCacheDir(
|
|
7448
|
+
function cachePath(cfg2, repoRoot, key) {
|
|
7449
|
+
return join3(resolveCacheDir(cfg2, repoRoot), key.slice(0, 2), `${key}.json`);
|
|
7378
7450
|
}
|
|
7379
|
-
function getNodeCache(
|
|
7380
|
-
if (!
|
|
7381
|
-
const p = cachePath(
|
|
7451
|
+
function getNodeCache(cfg2, repoRoot, key, nowMs) {
|
|
7452
|
+
if (!cfg2 || cfg2.enabled === false) return null;
|
|
7453
|
+
const p = cachePath(cfg2, repoRoot, key);
|
|
7382
7454
|
if (!existsSync4(p)) return null;
|
|
7383
|
-
const ttlS =
|
|
7455
|
+
const ttlS = cfg2.ttl_seconds ?? 604800;
|
|
7384
7456
|
try {
|
|
7385
7457
|
const st = statSync4(p);
|
|
7386
7458
|
if (ttlS > 0) {
|
|
@@ -7398,21 +7470,21 @@ function getNodeCache(cfg, repoRoot, key, nowMs) {
|
|
|
7398
7470
|
return null;
|
|
7399
7471
|
}
|
|
7400
7472
|
}
|
|
7401
|
-
function atomicWriteJson(
|
|
7402
|
-
mkdirSync4(dirname2(
|
|
7403
|
-
const tmp = `${
|
|
7473
|
+
function atomicWriteJson(path16, payload) {
|
|
7474
|
+
mkdirSync4(dirname2(path16), { recursive: true });
|
|
7475
|
+
const tmp = `${path16}.${process.pid}.${Date.now()}.tmp`;
|
|
7404
7476
|
writeFileSync3(tmp, JSON.stringify(payload), "utf8");
|
|
7405
|
-
renameSync(tmp,
|
|
7477
|
+
renameSync(tmp, path16);
|
|
7406
7478
|
}
|
|
7407
|
-
function putNodeCache(
|
|
7408
|
-
if (!
|
|
7479
|
+
function putNodeCache(cfg2, repoRoot, key, value) {
|
|
7480
|
+
if (!cfg2 || cfg2.enabled === false) return;
|
|
7409
7481
|
try {
|
|
7410
|
-
const p = cachePath(
|
|
7482
|
+
const p = cachePath(cfg2, repoRoot, key);
|
|
7411
7483
|
atomicWriteJson(p, value);
|
|
7412
|
-
const maxEntries =
|
|
7413
|
-
const maxBytes =
|
|
7484
|
+
const maxEntries = cfg2.max_entries ?? 2e3;
|
|
7485
|
+
const maxBytes = cfg2.max_cache_bytes ?? 100 * 1024 * 1024;
|
|
7414
7486
|
if (maxEntries <= 0 && maxBytes <= 0) return;
|
|
7415
|
-
const base = resolveCacheDir(
|
|
7487
|
+
const base = resolveCacheDir(cfg2, repoRoot);
|
|
7416
7488
|
if (!existsSync4(base)) return;
|
|
7417
7489
|
const entries = [];
|
|
7418
7490
|
let totalBytes = 0;
|
|
@@ -8530,31 +8602,31 @@ async function runFetch(args, opts) {
|
|
|
8530
8602
|
const url = String(args["url"] ?? "");
|
|
8531
8603
|
const force = Boolean(args["force_refresh"]);
|
|
8532
8604
|
const sessionId = typeof args["session_id"] === "string" ? args["session_id"] : null;
|
|
8533
|
-
const
|
|
8605
|
+
const cfg2 = { ...DEFAULT_CONFIG, ...opts.config ?? {} };
|
|
8534
8606
|
const base = { tool: "fetch", url };
|
|
8535
|
-
if (!
|
|
8607
|
+
if (!cfg2.enabled) {
|
|
8536
8608
|
return { ...base, accepted: false, reason: "fetch is disabled" };
|
|
8537
8609
|
}
|
|
8538
8610
|
if (!(url.startsWith("https://") || url.startsWith("http://"))) {
|
|
8539
8611
|
return { ...base, accepted: false, reason: "only http/https schemes are supported" };
|
|
8540
8612
|
}
|
|
8541
|
-
if (
|
|
8613
|
+
if (cfg2.url_allowlist.length === 0) {
|
|
8542
8614
|
return {
|
|
8543
8615
|
...base,
|
|
8544
8616
|
accepted: false,
|
|
8545
8617
|
reason: "fetch.url_allowlist is empty; no URLs may be fetched"
|
|
8546
8618
|
};
|
|
8547
8619
|
}
|
|
8548
|
-
if (!isUrlAllowed(url,
|
|
8620
|
+
if (!isUrlAllowed(url, cfg2.url_allowlist)) {
|
|
8549
8621
|
return {
|
|
8550
8622
|
...base,
|
|
8551
8623
|
accepted: false,
|
|
8552
8624
|
reason: "url is not covered by fetch.url_allowlist",
|
|
8553
|
-
allowlist:
|
|
8625
|
+
allowlist: cfg2.url_allowlist
|
|
8554
8626
|
};
|
|
8555
8627
|
}
|
|
8556
|
-
const maxBytesSession = Math.max(0,
|
|
8557
|
-
const maxUniqueHosts = Math.max(0,
|
|
8628
|
+
const maxBytesSession = Math.max(0, cfg2.max_bytes_per_session);
|
|
8629
|
+
const maxUniqueHosts = Math.max(0, cfg2.max_unique_hosts_per_session);
|
|
8558
8630
|
const host = extractHost(url);
|
|
8559
8631
|
if (sessionId && opts.storage && (maxBytesSession > 0 || maxUniqueHosts > 0)) {
|
|
8560
8632
|
const { total_bytes, unique_hosts } = await opts.storage.getFetchEgressTotals(sessionId);
|
|
@@ -8594,7 +8666,7 @@ async function runFetch(args, opts) {
|
|
|
8594
8666
|
}
|
|
8595
8667
|
}
|
|
8596
8668
|
}
|
|
8597
|
-
const evDir = resolveEvidenceDir(
|
|
8669
|
+
const evDir = resolveEvidenceDir(cfg2.evidence_dir, opts.repoRoot);
|
|
8598
8670
|
const urlHash16 = sha256Hex2(url).slice(0, 16);
|
|
8599
8671
|
const metaPath = path6.join(evDir, `by-url-${urlHash16}.json`);
|
|
8600
8672
|
if (existsSync5(metaPath) && !force) {
|
|
@@ -8616,7 +8688,7 @@ async function runFetch(args, opts) {
|
|
|
8616
8688
|
try {
|
|
8617
8689
|
response = await withTimeout(
|
|
8618
8690
|
fetchFn(url, { headers: { "User-Agent": "crosscheck-agent/0.1" } }),
|
|
8619
|
-
|
|
8691
|
+
cfg2.timeout_s * 1e3
|
|
8620
8692
|
);
|
|
8621
8693
|
} catch (e) {
|
|
8622
8694
|
return {
|
|
@@ -8642,8 +8714,8 @@ async function runFetch(args, opts) {
|
|
|
8642
8714
|
const reader = response.body?.getReader?.();
|
|
8643
8715
|
if (!reader) {
|
|
8644
8716
|
const buf = await response.arrayBuffer();
|
|
8645
|
-
if (buf.byteLength >
|
|
8646
|
-
return { ...base, accepted: false, reason: `response exceeds max_bytes=${
|
|
8717
|
+
if (buf.byteLength > cfg2.max_bytes) {
|
|
8718
|
+
return { ...base, accepted: false, reason: `response exceeds max_bytes=${cfg2.max_bytes}` };
|
|
8647
8719
|
}
|
|
8648
8720
|
data = new Uint8Array(buf);
|
|
8649
8721
|
} else {
|
|
@@ -8655,7 +8727,7 @@ async function runFetch(args, opts) {
|
|
|
8655
8727
|
if (done) break;
|
|
8656
8728
|
if (value) {
|
|
8657
8729
|
total += value.byteLength;
|
|
8658
|
-
if (total >
|
|
8730
|
+
if (total > cfg2.max_bytes) {
|
|
8659
8731
|
exceeded = true;
|
|
8660
8732
|
await reader.cancel().catch(() => {
|
|
8661
8733
|
});
|
|
@@ -8665,7 +8737,7 @@ async function runFetch(args, opts) {
|
|
|
8665
8737
|
}
|
|
8666
8738
|
}
|
|
8667
8739
|
if (exceeded) {
|
|
8668
|
-
return { ...base, accepted: false, reason: `response exceeds max_bytes=${
|
|
8740
|
+
return { ...base, accepted: false, reason: `response exceeds max_bytes=${cfg2.max_bytes}` };
|
|
8669
8741
|
}
|
|
8670
8742
|
data = new Uint8Array(total);
|
|
8671
8743
|
let offset = 0;
|
|
@@ -12519,15 +12591,15 @@ var CHECK_INTERVAL_SECONDS = 3 * 24 * 60 * 60;
|
|
|
12519
12591
|
var DEFAULT_PACKAGE = "crosscheck-cli";
|
|
12520
12592
|
var FETCH_TIMEOUT_MS = 3e3;
|
|
12521
12593
|
function engineVersion() {
|
|
12522
|
-
return true ? "0.2.
|
|
12594
|
+
return true ? "0.2.16" : "0.0.0-dev";
|
|
12523
12595
|
}
|
|
12524
12596
|
function defaultUpdateCachePath() {
|
|
12525
12597
|
const base = process.env["CROSSCHECK_DATA_DIR"] || path10.join(os.homedir() || os.tmpdir(), ".crosscheck");
|
|
12526
12598
|
return path10.join(base, "update_check.json");
|
|
12527
12599
|
}
|
|
12528
|
-
function isNewer(latest,
|
|
12600
|
+
function isNewer(latest, current2) {
|
|
12529
12601
|
const a = parseTriple(latest);
|
|
12530
|
-
const b = parseTriple(
|
|
12602
|
+
const b = parseTriple(current2);
|
|
12531
12603
|
for (let i = 0; i < 3; i += 1) {
|
|
12532
12604
|
if (a[i] !== b[i]) return a[i] > b[i];
|
|
12533
12605
|
}
|
|
@@ -12537,13 +12609,13 @@ function parseTriple(v) {
|
|
|
12537
12609
|
const parts = (v.split("-")[0] ?? "").split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
12538
12610
|
return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];
|
|
12539
12611
|
}
|
|
12540
|
-
function buildNotice(pkg,
|
|
12612
|
+
function buildNotice(pkg, current2, latest) {
|
|
12541
12613
|
return {
|
|
12542
12614
|
update_available: true,
|
|
12543
12615
|
package: pkg,
|
|
12544
|
-
current_version:
|
|
12616
|
+
current_version: current2,
|
|
12545
12617
|
latest_version: latest,
|
|
12546
|
-
message: `crosscheck ${latest} is available \u2014 you're on ${
|
|
12618
|
+
message: `crosscheck ${latest} is available \u2014 you're on ${current2}. To upgrade, run:
|
|
12547
12619
|
|
|
12548
12620
|
npm install -g crosscheck-cli@latest
|
|
12549
12621
|
|
|
@@ -14230,13 +14302,214 @@ function attachUpdateNotice(result, toolName, opts) {
|
|
|
14230
14302
|
|
|
14231
14303
|
// src/core/telemetry.ts
|
|
14232
14304
|
init_esm_shims();
|
|
14305
|
+
import crypto4 from "crypto";
|
|
14306
|
+
|
|
14307
|
+
// src/core/segments.ts
|
|
14308
|
+
init_esm_shims();
|
|
14309
|
+
import crypto3 from "crypto";
|
|
14310
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync9, readFileSync as readFileSync14, writeFileSync as writeFileSync8 } from "fs";
|
|
14311
|
+
import path13 from "path";
|
|
14312
|
+
|
|
14313
|
+
// src/core/git-context.ts
|
|
14314
|
+
init_esm_shims();
|
|
14315
|
+
import { execFileSync } from "child_process";
|
|
14233
14316
|
import crypto2 from "crypto";
|
|
14317
|
+
var TRUNK_BRANCHES = /* @__PURE__ */ new Set([
|
|
14318
|
+
"main",
|
|
14319
|
+
"master",
|
|
14320
|
+
"dev",
|
|
14321
|
+
"develop",
|
|
14322
|
+
"trunk"
|
|
14323
|
+
]);
|
|
14324
|
+
function git(args, cwd) {
|
|
14325
|
+
try {
|
|
14326
|
+
return execFileSync("git", args, {
|
|
14327
|
+
cwd,
|
|
14328
|
+
encoding: "utf8",
|
|
14329
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
14330
|
+
timeout: 2e3
|
|
14331
|
+
}).trim();
|
|
14332
|
+
} catch {
|
|
14333
|
+
return null;
|
|
14334
|
+
}
|
|
14335
|
+
}
|
|
14336
|
+
function repoIdentity(cwd) {
|
|
14337
|
+
const root = git(["rev-list", "--max-parents=0", "HEAD"], cwd);
|
|
14338
|
+
if (root) return `root:${root.split("\n").pop().trim()}`;
|
|
14339
|
+
const remote = git(["config", "--get", "remote.origin.url"], cwd);
|
|
14340
|
+
if (remote) return `remote:${remote}`;
|
|
14341
|
+
const top = git(["rev-parse", "--show-toplevel"], cwd);
|
|
14342
|
+
return top ? `path:${top}` : null;
|
|
14343
|
+
}
|
|
14344
|
+
function fingerprintOf(seatKey, parts) {
|
|
14345
|
+
return crypto2.createHmac("sha256", seatKey).update(parts.join("\0")).digest("hex");
|
|
14346
|
+
}
|
|
14347
|
+
function readGitContext(seatKey, cwd) {
|
|
14348
|
+
try {
|
|
14349
|
+
const inside = git(["rev-parse", "--is-inside-work-tree"], cwd);
|
|
14350
|
+
if (inside !== "true") return { signal: "no_repo" };
|
|
14351
|
+
const repo = repoIdentity(cwd);
|
|
14352
|
+
if (!repo) return { signal: "no_repo" };
|
|
14353
|
+
const branch = git(["rev-parse", "--abbrev-ref", "HEAD"], cwd);
|
|
14354
|
+
if (!branch || branch === "HEAD") {
|
|
14355
|
+
const wt = git(["rev-parse", "--show-toplevel"], cwd) ?? cwd;
|
|
14356
|
+
return {
|
|
14357
|
+
fingerprint: fingerprintOf(seatKey, [repo, "worktree", wt]),
|
|
14358
|
+
signal: "worktree",
|
|
14359
|
+
localLabel: `worktree:${wt}`
|
|
14360
|
+
};
|
|
14361
|
+
}
|
|
14362
|
+
if (TRUNK_BRANCHES.has(branch)) {
|
|
14363
|
+
return {
|
|
14364
|
+
fingerprint: fingerprintOf(seatKey, [repo, "trunk"]),
|
|
14365
|
+
signal: "trunk",
|
|
14366
|
+
localLabel: `${branch} (maintenance)`
|
|
14367
|
+
};
|
|
14368
|
+
}
|
|
14369
|
+
return {
|
|
14370
|
+
fingerprint: fingerprintOf(seatKey, [repo, "branch", branch]),
|
|
14371
|
+
signal: "branch",
|
|
14372
|
+
localLabel: branch
|
|
14373
|
+
};
|
|
14374
|
+
} catch {
|
|
14375
|
+
return { signal: "no_repo" };
|
|
14376
|
+
}
|
|
14377
|
+
}
|
|
14378
|
+
function sameContext(a, b) {
|
|
14379
|
+
return a.fingerprint === b.fingerprint && a.signal === b.signal;
|
|
14380
|
+
}
|
|
14381
|
+
|
|
14382
|
+
// src/core/segments.ts
|
|
14383
|
+
var POST_TIMEOUT_MS = 5e3;
|
|
14384
|
+
function canonicalSegmentBody(s) {
|
|
14385
|
+
return [
|
|
14386
|
+
s.segmentId,
|
|
14387
|
+
s.seatId,
|
|
14388
|
+
s.orgId,
|
|
14389
|
+
s.sessionId,
|
|
14390
|
+
String(s.seq),
|
|
14391
|
+
s.fingerprint ?? "",
|
|
14392
|
+
s.signal,
|
|
14393
|
+
s.startedAt,
|
|
14394
|
+
s.endedAt ?? ""
|
|
14395
|
+
].join("|");
|
|
14396
|
+
}
|
|
14397
|
+
function signSegment(seatKey, s) {
|
|
14398
|
+
return crypto3.createHmac("sha256", seatKey).update(canonicalSegmentBody(s)).digest("hex");
|
|
14399
|
+
}
|
|
14400
|
+
var cfgResolved = false;
|
|
14401
|
+
var cfg = null;
|
|
14234
14402
|
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
14403
|
+
function getConfig() {
|
|
14404
|
+
if (cfgResolved) return cfg;
|
|
14405
|
+
cfgResolved = true;
|
|
14406
|
+
if (process.env["CROSSCHECK_TELEMETRY"] === "off") return cfg = null;
|
|
14407
|
+
const base = process.env["CROSSCHECK_TELEMETRY_URL"];
|
|
14408
|
+
const seatId = process.env["CROSSCHECK_SEAT_ID"];
|
|
14409
|
+
const orgId = process.env["CROSSCHECK_ORG_ID"];
|
|
14410
|
+
const seatKey = process.env["CROSSCHECK_SEAT_HMAC_KEY"];
|
|
14411
|
+
if (!base || !seatId || !orgId || !seatKey) return cfg = null;
|
|
14412
|
+
if (!UUID_RE.test(seatId) || !UUID_RE.test(orgId)) return cfg = null;
|
|
14413
|
+
const sessionId = process.env["CLAUDE_SESSION_ID"] || process.env["CLAUDE_CODE_SESSION_ID"] || `engine-${crypto3.randomUUID()}`;
|
|
14414
|
+
cfg = {
|
|
14415
|
+
url: base.replace(/\/telemetry\/?$/, "/segments"),
|
|
14416
|
+
seatId,
|
|
14417
|
+
orgId,
|
|
14418
|
+
seatKey,
|
|
14419
|
+
sessionId
|
|
14420
|
+
};
|
|
14421
|
+
return cfg;
|
|
14422
|
+
}
|
|
14423
|
+
var current = null;
|
|
14424
|
+
var nextSeq = 0;
|
|
14425
|
+
function currentFingerprint() {
|
|
14426
|
+
return current?.context.fingerprint;
|
|
14427
|
+
}
|
|
14428
|
+
function rememberLabel(context) {
|
|
14429
|
+
if (!context.fingerprint || !context.localLabel) return;
|
|
14430
|
+
try {
|
|
14431
|
+
const dir = process.env["CROSSCHECK_DATA_DIR"] || path13.join(process.env["HOME"] ?? process.env["USERPROFILE"] ?? ".", ".crosscheck");
|
|
14432
|
+
const file = path13.join(dir, "features.json");
|
|
14433
|
+
let map = {};
|
|
14434
|
+
if (existsSync12(file)) {
|
|
14435
|
+
try {
|
|
14436
|
+
const parsed = JSON.parse(readFileSync14(file, "utf8"));
|
|
14437
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) map = parsed;
|
|
14438
|
+
} catch {
|
|
14439
|
+
}
|
|
14440
|
+
}
|
|
14441
|
+
map[context.fingerprint] = {
|
|
14442
|
+
label: context.localLabel,
|
|
14443
|
+
lastSeen: (/* @__PURE__ */ new Date()).toISOString()
|
|
14444
|
+
};
|
|
14445
|
+
mkdirSync9(dir, { recursive: true });
|
|
14446
|
+
writeFileSync8(file, `${JSON.stringify(map, null, 2)}
|
|
14447
|
+
`, "utf8");
|
|
14448
|
+
} catch {
|
|
14449
|
+
}
|
|
14450
|
+
}
|
|
14451
|
+
async function post(url, segments) {
|
|
14452
|
+
try {
|
|
14453
|
+
await fetch(url, {
|
|
14454
|
+
method: "POST",
|
|
14455
|
+
headers: { "content-type": "application/json" },
|
|
14456
|
+
body: JSON.stringify({ segments }),
|
|
14457
|
+
signal: AbortSignal.timeout(POST_TIMEOUT_MS)
|
|
14458
|
+
});
|
|
14459
|
+
} catch {
|
|
14460
|
+
}
|
|
14461
|
+
}
|
|
14462
|
+
function emit(c, seg, endedAt) {
|
|
14463
|
+
const payload = {
|
|
14464
|
+
segmentId: seg.segmentId,
|
|
14465
|
+
seatId: c.seatId,
|
|
14466
|
+
orgId: c.orgId,
|
|
14467
|
+
sessionId: c.sessionId,
|
|
14468
|
+
seq: seg.seq,
|
|
14469
|
+
...seg.context.fingerprint ? { fingerprint: seg.context.fingerprint } : {},
|
|
14470
|
+
signal: seg.context.signal,
|
|
14471
|
+
startedAt: seg.startedAt,
|
|
14472
|
+
...endedAt ? { endedAt } : {}
|
|
14473
|
+
};
|
|
14474
|
+
const signatureHmac = signSegment(c.seatKey, payload);
|
|
14475
|
+
void post(c.url, [{ ...payload, signatureHmac }]);
|
|
14476
|
+
}
|
|
14477
|
+
function syncSegment(cwd) {
|
|
14478
|
+
try {
|
|
14479
|
+
const c = getConfig();
|
|
14480
|
+
if (!c) return;
|
|
14481
|
+
const context = readGitContext(c.seatKey, cwd ?? process.cwd());
|
|
14482
|
+
if (current && sameContext(current.context, context)) return;
|
|
14483
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
14484
|
+
if (current) emit(c, current, now);
|
|
14485
|
+
current = {
|
|
14486
|
+
segmentId: crypto3.randomUUID(),
|
|
14487
|
+
seq: nextSeq++,
|
|
14488
|
+
context,
|
|
14489
|
+
startedAt: now
|
|
14490
|
+
};
|
|
14491
|
+
rememberLabel(context);
|
|
14492
|
+
emit(c, current);
|
|
14493
|
+
} catch {
|
|
14494
|
+
}
|
|
14495
|
+
}
|
|
14496
|
+
function closeSegment() {
|
|
14497
|
+
try {
|
|
14498
|
+
const c = getConfig();
|
|
14499
|
+
if (!c || !current) return;
|
|
14500
|
+
emit(c, current, (/* @__PURE__ */ new Date()).toISOString());
|
|
14501
|
+
current = null;
|
|
14502
|
+
} catch {
|
|
14503
|
+
}
|
|
14504
|
+
}
|
|
14505
|
+
|
|
14506
|
+
// src/core/telemetry.ts
|
|
14507
|
+
var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
14235
14508
|
var BATCH_MAX = 100;
|
|
14236
14509
|
var FLUSH_BATCH_AT = 25;
|
|
14237
14510
|
var FLUSH_INTERVAL_MS = 15e3;
|
|
14238
14511
|
var MAX_QUEUE = 500;
|
|
14239
|
-
var
|
|
14512
|
+
var POST_TIMEOUT_MS2 = 5e3;
|
|
14240
14513
|
var PROVIDER_TO_SCHEMA = {
|
|
14241
14514
|
anthropic: "anthropic",
|
|
14242
14515
|
openai: "openai",
|
|
@@ -14266,7 +14539,7 @@ function toolToPattern(toolName) {
|
|
|
14266
14539
|
return TOOL_TO_PATTERN[String(toolName).toLowerCase()] ?? "other";
|
|
14267
14540
|
}
|
|
14268
14541
|
function canonicalEventBody(e) {
|
|
14269
|
-
|
|
14542
|
+
const base = [
|
|
14270
14543
|
e.eventId,
|
|
14271
14544
|
e.seatId,
|
|
14272
14545
|
e.orgId,
|
|
@@ -14281,13 +14554,22 @@ function canonicalEventBody(e) {
|
|
|
14281
14554
|
e.status,
|
|
14282
14555
|
e.errorClass ?? ""
|
|
14283
14556
|
].join("|");
|
|
14557
|
+
if (e.runId === void 0 && e.purpose === void 0 && e.fingerprint === void 0) {
|
|
14558
|
+
return base;
|
|
14559
|
+
}
|
|
14560
|
+
return [base, e.runId ?? "", e.purpose ?? "", e.fingerprint ?? ""].join("|");
|
|
14561
|
+
}
|
|
14562
|
+
function normalizePurpose(v) {
|
|
14563
|
+
if (typeof v !== "string") return void 0;
|
|
14564
|
+
const slug = v.trim().toLowerCase().replace(/[^a-z0-9_-]/g, "-").slice(0, 32);
|
|
14565
|
+
return /^[a-z0-9][a-z0-9_-]*$/.test(slug) ? slug : void 0;
|
|
14284
14566
|
}
|
|
14285
14567
|
function signEvent(seatSigningKey, e) {
|
|
14286
|
-
return
|
|
14568
|
+
return crypto4.createHmac("sha256", seatSigningKey).update(canonicalEventBody(e)).digest("hex");
|
|
14287
14569
|
}
|
|
14288
14570
|
var configResolved = false;
|
|
14289
14571
|
var config = null;
|
|
14290
|
-
function
|
|
14572
|
+
function getConfig2() {
|
|
14291
14573
|
if (configResolved) return config;
|
|
14292
14574
|
configResolved = true;
|
|
14293
14575
|
if (process.env["CROSSCHECK_TELEMETRY"] === "off") return config = null;
|
|
@@ -14296,7 +14578,7 @@ function getConfig() {
|
|
|
14296
14578
|
const orgId = process.env["CROSSCHECK_ORG_ID"];
|
|
14297
14579
|
const seatKey = process.env["CROSSCHECK_SEAT_HMAC_KEY"];
|
|
14298
14580
|
if (!url || !seatId || !orgId || !seatKey) return config = null;
|
|
14299
|
-
if (!
|
|
14581
|
+
if (!UUID_RE2.test(seatId) || !UUID_RE2.test(orgId)) return config = null;
|
|
14300
14582
|
config = { url, seatId, orgId, seatKey };
|
|
14301
14583
|
return config;
|
|
14302
14584
|
}
|
|
@@ -14331,7 +14613,10 @@ function usageEventsFromEnvelope(out, toolName) {
|
|
|
14331
14613
|
completionTokens: intNonNeg(u.completion_tokens),
|
|
14332
14614
|
costUsdEstimate: cost,
|
|
14333
14615
|
latencyMs,
|
|
14334
|
-
status: "ok"
|
|
14616
|
+
status: "ok",
|
|
14617
|
+
// Envelopes have carried `purpose` per call all along (debate /
|
|
14618
|
+
// debate-coreason / synth / worker); it just was never forwarded.
|
|
14619
|
+
purpose: normalizePurpose(u.purpose)
|
|
14335
14620
|
});
|
|
14336
14621
|
}
|
|
14337
14622
|
return events;
|
|
@@ -14346,11 +14631,11 @@ function scheduleFlush() {
|
|
|
14346
14631
|
}, FLUSH_INTERVAL_MS);
|
|
14347
14632
|
if (typeof flushTimer.unref === "function") flushTimer.unref();
|
|
14348
14633
|
}
|
|
14349
|
-
function enqueue(
|
|
14634
|
+
function enqueue(cfg2, input, runId, fingerprint) {
|
|
14350
14635
|
const base = {
|
|
14351
|
-
eventId:
|
|
14352
|
-
seatId:
|
|
14353
|
-
orgId:
|
|
14636
|
+
eventId: crypto4.randomUUID(),
|
|
14637
|
+
seatId: cfg2.seatId,
|
|
14638
|
+
orgId: cfg2.orgId,
|
|
14354
14639
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14355
14640
|
provider: input.provider,
|
|
14356
14641
|
model: input.model,
|
|
@@ -14359,9 +14644,17 @@ function enqueue(cfg, input) {
|
|
|
14359
14644
|
completionTokens: input.completionTokens,
|
|
14360
14645
|
costUsdEstimate: input.costUsdEstimate,
|
|
14361
14646
|
latencyMs: input.latencyMs,
|
|
14362
|
-
status: input.status
|
|
14363
|
-
|
|
14364
|
-
|
|
14647
|
+
status: input.status,
|
|
14648
|
+
runId,
|
|
14649
|
+
// Omit entirely (not "") when unknown, so the canonical body stays
|
|
14650
|
+
// consistent with what the server reconstructs from the JSON.
|
|
14651
|
+
...input.purpose === void 0 ? {} : { purpose: input.purpose },
|
|
14652
|
+
// Panel attribution is EXACT: we know the active git segment at emit
|
|
14653
|
+
// time, so there is no time-join and no ambiguity window — unlike host
|
|
14654
|
+
// tokens, which can only be matched by session and timestamp.
|
|
14655
|
+
...fingerprint === void 0 ? {} : { fingerprint }
|
|
14656
|
+
};
|
|
14657
|
+
const signatureHmac = signEvent(cfg2.seatKey, base);
|
|
14365
14658
|
queue.push({ ...base, signatureHmac });
|
|
14366
14659
|
if (queue.length > MAX_QUEUE) queue.splice(0, queue.length - MAX_QUEUE);
|
|
14367
14660
|
if (queue.length >= FLUSH_BATCH_AT) void flush();
|
|
@@ -14369,10 +14662,14 @@ function enqueue(cfg, input) {
|
|
|
14369
14662
|
}
|
|
14370
14663
|
function recordUsage(out, toolName) {
|
|
14371
14664
|
try {
|
|
14372
|
-
const
|
|
14373
|
-
if (!
|
|
14665
|
+
const cfg2 = getConfig2();
|
|
14666
|
+
if (!cfg2) return;
|
|
14374
14667
|
if (toolName === "update_crosscheck") return;
|
|
14375
|
-
|
|
14668
|
+
const runId = crypto4.randomUUID();
|
|
14669
|
+
const fingerprint = currentFingerprint();
|
|
14670
|
+
for (const ev of usageEventsFromEnvelope(out, toolName)) {
|
|
14671
|
+
enqueue(cfg2, ev, runId, fingerprint);
|
|
14672
|
+
}
|
|
14376
14673
|
} catch {
|
|
14377
14674
|
}
|
|
14378
14675
|
}
|
|
@@ -14383,7 +14680,7 @@ async function postBatch(url, batch) {
|
|
|
14383
14680
|
method: "POST",
|
|
14384
14681
|
headers: { "content-type": "application/json" },
|
|
14385
14682
|
body: JSON.stringify({ events: batch }),
|
|
14386
|
-
signal: AbortSignal.timeout(
|
|
14683
|
+
signal: AbortSignal.timeout(POST_TIMEOUT_MS2)
|
|
14387
14684
|
});
|
|
14388
14685
|
} catch {
|
|
14389
14686
|
queue.unshift(...batch);
|
|
@@ -14405,15 +14702,15 @@ async function postBatch(url, batch) {
|
|
|
14405
14702
|
if (queue.length > MAX_QUEUE) queue.splice(0, queue.length - MAX_QUEUE);
|
|
14406
14703
|
}
|
|
14407
14704
|
async function flush() {
|
|
14408
|
-
const
|
|
14409
|
-
if (!
|
|
14705
|
+
const cfg2 = getConfig2();
|
|
14706
|
+
if (!cfg2) return;
|
|
14410
14707
|
if (flushTimer) {
|
|
14411
14708
|
clearTimeout(flushTimer);
|
|
14412
14709
|
flushTimer = null;
|
|
14413
14710
|
}
|
|
14414
14711
|
while (queue.length > 0) {
|
|
14415
14712
|
const batch = queue.splice(0, BATCH_MAX);
|
|
14416
|
-
await postBatch(
|
|
14713
|
+
await postBatch(cfg2.url, batch);
|
|
14417
14714
|
if (queue.length > 0 && queue[0] === batch[0]) break;
|
|
14418
14715
|
}
|
|
14419
14716
|
}
|
|
@@ -14481,6 +14778,7 @@ function createServer(opts = {}) {
|
|
|
14481
14778
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
14482
14779
|
const startedHr = process.hrtime.bigint();
|
|
14483
14780
|
const id = nextEventId();
|
|
14781
|
+
syncSegment(opts.repoRoot);
|
|
14484
14782
|
const gateBlock = configPinGate(name, {
|
|
14485
14783
|
...opts.repoRoot ? { repoRoot: opts.repoRoot } : {},
|
|
14486
14784
|
...opts.configPinning ? { config: opts.configPinning } : {},
|
|
@@ -14562,6 +14860,13 @@ function createServer(opts = {}) {
|
|
|
14562
14860
|
async function connectAndServe(transport, opts = {}) {
|
|
14563
14861
|
const server = createServer(opts);
|
|
14564
14862
|
await server.connect(transport);
|
|
14863
|
+
for (const sig of ["SIGINT", "SIGTERM"]) {
|
|
14864
|
+
process.once(sig, () => {
|
|
14865
|
+
closeSegment();
|
|
14866
|
+
process.exit(0);
|
|
14867
|
+
});
|
|
14868
|
+
}
|
|
14869
|
+
process.once("beforeExit", () => closeSegment());
|
|
14565
14870
|
const cliVersion = process.env["CROSSCHECK_CLI_VERSION"];
|
|
14566
14871
|
void maybeCheckForUpdate({
|
|
14567
14872
|
currentVersion: cliVersion ?? SERVER_VERSION,
|
|
@@ -14599,8 +14904,8 @@ function buildToolRegistry(opts) {
|
|
|
14599
14904
|
|
|
14600
14905
|
// src/core/config-file.ts
|
|
14601
14906
|
init_esm_shims();
|
|
14602
|
-
import { existsSync as
|
|
14603
|
-
import
|
|
14907
|
+
import { existsSync as existsSync13, readFileSync as readFileSync15 } from "fs";
|
|
14908
|
+
import path14 from "path";
|
|
14604
14909
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
14605
14910
|
function asObj(v) {
|
|
14606
14911
|
return v && typeof v === "object" && !Array.isArray(v) ? v : void 0;
|
|
@@ -14628,18 +14933,18 @@ function pickNums(src, keys) {
|
|
|
14628
14933
|
}
|
|
14629
14934
|
function findConfigFile(opts) {
|
|
14630
14935
|
const direct = opts.explicitPath ?? process.env["CROSSCHECK_CONFIG_PATH"];
|
|
14631
|
-
if (direct) return
|
|
14936
|
+
if (direct) return existsSync13(direct) ? direct : void 0;
|
|
14632
14937
|
const candidates = [];
|
|
14633
14938
|
let dir = opts.startDir;
|
|
14634
14939
|
for (let i = 0; dir && i < 8; i += 1) {
|
|
14635
|
-
candidates.push(
|
|
14636
|
-
const parent =
|
|
14940
|
+
candidates.push(path14.join(dir, "crosscheck.config.json"));
|
|
14941
|
+
const parent = path14.dirname(dir);
|
|
14637
14942
|
if (parent === dir) break;
|
|
14638
14943
|
dir = parent;
|
|
14639
14944
|
}
|
|
14640
|
-
if (opts.repoRoot) candidates.push(
|
|
14641
|
-
if (opts.dataDir) candidates.push(
|
|
14642
|
-
return candidates.find((p) =>
|
|
14945
|
+
if (opts.repoRoot) candidates.push(path14.join(opts.repoRoot, "crosscheck.config.json"));
|
|
14946
|
+
if (opts.dataDir) candidates.push(path14.join(opts.dataDir, "crosscheck.config.json"));
|
|
14947
|
+
return candidates.find((p) => existsSync13(p));
|
|
14643
14948
|
}
|
|
14644
14949
|
function mapConfig(raw, sourcePath) {
|
|
14645
14950
|
const result = { sourcePath };
|
|
@@ -14656,16 +14961,16 @@ function mapConfig(raw, sourcePath) {
|
|
|
14656
14961
|
}
|
|
14657
14962
|
const nc = asObj(raw["node_cache"]);
|
|
14658
14963
|
if (nc) {
|
|
14659
|
-
const
|
|
14964
|
+
const cfg2 = {
|
|
14660
14965
|
...pickNums(nc, ["ttl_seconds", "max_entries", "max_cache_bytes"])
|
|
14661
14966
|
};
|
|
14662
|
-
if (bool(nc["enabled"]) !== void 0)
|
|
14663
|
-
if (str(nc["dir"]) !== void 0)
|
|
14664
|
-
result.nodeCache =
|
|
14967
|
+
if (bool(nc["enabled"]) !== void 0) cfg2.enabled = bool(nc["enabled"]);
|
|
14968
|
+
if (str(nc["dir"]) !== void 0) cfg2.dir = str(nc["dir"]);
|
|
14969
|
+
result.nodeCache = cfg2;
|
|
14665
14970
|
}
|
|
14666
14971
|
const f = asObj(raw["fetch"]);
|
|
14667
14972
|
if (f) {
|
|
14668
|
-
const
|
|
14973
|
+
const cfg2 = {
|
|
14669
14974
|
...pickNums(f, [
|
|
14670
14975
|
"max_bytes",
|
|
14671
14976
|
"max_bytes_per_session",
|
|
@@ -14673,10 +14978,10 @@ function mapConfig(raw, sourcePath) {
|
|
|
14673
14978
|
"timeout_s"
|
|
14674
14979
|
])
|
|
14675
14980
|
};
|
|
14676
|
-
if (bool(f["enabled"]) !== void 0)
|
|
14677
|
-
if (strArr(f["url_allowlist"]) !== void 0)
|
|
14678
|
-
if (str(f["evidence_dir"]) !== void 0)
|
|
14679
|
-
result.fetchConfig =
|
|
14981
|
+
if (bool(f["enabled"]) !== void 0) cfg2.enabled = bool(f["enabled"]);
|
|
14982
|
+
if (strArr(f["url_allowlist"]) !== void 0) cfg2.url_allowlist = strArr(f["url_allowlist"]);
|
|
14983
|
+
if (str(f["evidence_dir"]) !== void 0) cfg2.evidence_dir = str(f["evidence_dir"]);
|
|
14984
|
+
result.fetchConfig = cfg2;
|
|
14680
14985
|
}
|
|
14681
14986
|
if ("provider_allowlist" in raw) {
|
|
14682
14987
|
const pa = raw["provider_allowlist"];
|
|
@@ -14700,10 +15005,10 @@ function mapConfig(raw, sourcePath) {
|
|
|
14700
15005
|
if (retries) {
|
|
14701
15006
|
const maxAttempts = num(retries["max_attempts"]);
|
|
14702
15007
|
const baseS = num(retries["backoff_base_s"]);
|
|
14703
|
-
const
|
|
14704
|
-
if (maxAttempts !== void 0)
|
|
14705
|
-
if (baseS !== void 0)
|
|
14706
|
-
if (Object.keys(
|
|
15008
|
+
const cfg2 = {};
|
|
15009
|
+
if (maxAttempts !== void 0) cfg2.maxAttempts = Math.max(1, Math.trunc(maxAttempts));
|
|
15010
|
+
if (baseS !== void 0) cfg2.backoffBaseMs = Math.round(baseS * 1e3);
|
|
15011
|
+
if (Object.keys(cfg2).length > 0) setRetryConfig(cfg2);
|
|
14707
15012
|
}
|
|
14708
15013
|
const rl = asObj(raw["rate_limits"]);
|
|
14709
15014
|
if (rl) {
|
|
@@ -14729,22 +15034,22 @@ function mapConfig(raw, sourcePath) {
|
|
|
14729
15034
|
}
|
|
14730
15035
|
const red = asObj(raw["redaction"]);
|
|
14731
15036
|
if (red) {
|
|
14732
|
-
const
|
|
14733
|
-
if (bool(red["enabled"]) !== void 0)
|
|
15037
|
+
const cfg2 = {};
|
|
15038
|
+
if (bool(red["enabled"]) !== void 0) cfg2.enabled = bool(red["enabled"]);
|
|
14734
15039
|
const extras = strArr(red["patterns_extra"]);
|
|
14735
|
-
if (extras && extras.length > 0)
|
|
15040
|
+
if (extras && extras.length > 0) cfg2.patterns_extra = extras;
|
|
14736
15041
|
if (bool(red["hmac_tokens"]) === true) {
|
|
14737
|
-
|
|
14738
|
-
|
|
15042
|
+
cfg2.hmac_tokens = true;
|
|
15043
|
+
cfg2.hmac_secret = randomBytes2(32);
|
|
14739
15044
|
}
|
|
14740
|
-
if (Object.keys(
|
|
15045
|
+
if (Object.keys(cfg2).length > 0) setRedactionConfig(cfg2);
|
|
14741
15046
|
}
|
|
14742
15047
|
const personas = asObj(raw["personas"]);
|
|
14743
15048
|
if (personas) {
|
|
14744
|
-
const
|
|
14745
|
-
if (bool(personas["enabled"]) !== void 0)
|
|
14746
|
-
if (str(personas["dir"]) !== void 0)
|
|
14747
|
-
if (Object.keys(
|
|
15049
|
+
const cfg2 = {};
|
|
15050
|
+
if (bool(personas["enabled"]) !== void 0) cfg2.enabled = bool(personas["enabled"]);
|
|
15051
|
+
if (str(personas["dir"]) !== void 0) cfg2.dir = str(personas["dir"]);
|
|
15052
|
+
if (Object.keys(cfg2).length > 0) setPersonaConfig(cfg2);
|
|
14748
15053
|
}
|
|
14749
15054
|
return result;
|
|
14750
15055
|
}
|
|
@@ -14753,7 +15058,7 @@ function loadConfigFile(opts) {
|
|
|
14753
15058
|
if (!file) return null;
|
|
14754
15059
|
let raw;
|
|
14755
15060
|
try {
|
|
14756
|
-
raw = JSON.parse(
|
|
15061
|
+
raw = JSON.parse(readFileSync15(file, "utf8"));
|
|
14757
15062
|
} catch (e) {
|
|
14758
15063
|
try {
|
|
14759
15064
|
process.stderr.write(
|
|
@@ -14773,7 +15078,7 @@ function loadConfigFile(opts) {
|
|
|
14773
15078
|
var SHUTDOWN_TIMEOUT_MS = 2e3;
|
|
14774
15079
|
async function main() {
|
|
14775
15080
|
enforceLicenseOrExit();
|
|
14776
|
-
const cfgDataDir = process.env["CROSSCHECK_DATA_DIR"] ||
|
|
15081
|
+
const cfgDataDir = process.env["CROSSCHECK_DATA_DIR"] || path15.join(process.env["HOME"] ?? process.env["USERPROFILE"] ?? "", ".crosscheck");
|
|
14777
15082
|
const configFile = loadConfigFile({
|
|
14778
15083
|
startDir: process.cwd(),
|
|
14779
15084
|
repoRoot: findGitRoot(__dirname),
|
|
@@ -14798,7 +15103,7 @@ async function main() {
|
|
|
14798
15103
|
installShutdownHandlers();
|
|
14799
15104
|
const bundledPricing = (() => {
|
|
14800
15105
|
try {
|
|
14801
|
-
return
|
|
15106
|
+
return path15.join(path15.dirname(fileURLToPath3(import.meta.url)), "pricing.json");
|
|
14802
15107
|
} catch {
|
|
14803
15108
|
return void 0;
|
|
14804
15109
|
}
|
|
@@ -14807,7 +15112,7 @@ async function main() {
|
|
|
14807
15112
|
process.env["CROSSCHECK_PRICING_PATH"],
|
|
14808
15113
|
resolveRepoFile("config/pricing.json"),
|
|
14809
15114
|
bundledPricing
|
|
14810
|
-
].find((p) => p &&
|
|
15115
|
+
].find((p) => p && existsSync14(p));
|
|
14811
15116
|
const pricing = pricingPath ? loadPricing(pricingPath) : {};
|
|
14812
15117
|
const providers = buildProviders({ env: process.env, pricing });
|
|
14813
15118
|
if (Object.keys(providers).length > 0) {
|
|
@@ -14825,10 +15130,10 @@ async function main() {
|
|
|
14825
15130
|
}
|
|
14826
15131
|
let storage;
|
|
14827
15132
|
if (process.env["CROSSCHECK_DB_DISABLED"] !== "1") {
|
|
14828
|
-
const dbPath = process.env["CROSSCHECK_DB_PATH"] ?? resolveRepoFile(".crosscheck/db.sqlite") ??
|
|
15133
|
+
const dbPath = process.env["CROSSCHECK_DB_PATH"] ?? resolveRepoFile(".crosscheck/db.sqlite") ?? path15.join(cfgDataDir, "db.sqlite");
|
|
14829
15134
|
if (dbPath) {
|
|
14830
15135
|
try {
|
|
14831
|
-
|
|
15136
|
+
mkdirSync10(path15.dirname(dbPath), { recursive: true });
|
|
14832
15137
|
const { openBetterSqliteStorage: openBetterSqliteStorage2 } = await Promise.resolve().then(() => (init_better_sqlite3(), better_sqlite3_exports));
|
|
14833
15138
|
storage = openBetterSqliteStorage2({ path: dbPath });
|
|
14834
15139
|
await storage.migrate();
|
|
@@ -14896,9 +15201,9 @@ function installShutdownHandlers() {
|
|
|
14896
15201
|
function resolveRepoFile(rel) {
|
|
14897
15202
|
let dir = __dirname;
|
|
14898
15203
|
for (let i = 0; i < 8; i++) {
|
|
14899
|
-
const candidate =
|
|
14900
|
-
if (
|
|
14901
|
-
const parent =
|
|
15204
|
+
const candidate = path15.join(dir, rel);
|
|
15205
|
+
if (existsSync14(candidate)) return candidate;
|
|
15206
|
+
const parent = path15.dirname(dir);
|
|
14902
15207
|
if (parent === dir) break;
|
|
14903
15208
|
dir = parent;
|
|
14904
15209
|
}
|
|
@@ -14907,8 +15212,8 @@ function resolveRepoFile(rel) {
|
|
|
14907
15212
|
function findGitRoot(startDir) {
|
|
14908
15213
|
let dir = startDir;
|
|
14909
15214
|
for (let i = 0; i < 12; i++) {
|
|
14910
|
-
if (
|
|
14911
|
-
const parent =
|
|
15215
|
+
if (existsSync14(path15.join(dir, ".git"))) return dir;
|
|
15216
|
+
const parent = path15.dirname(dir);
|
|
14912
15217
|
if (parent === dir) break;
|
|
14913
15218
|
dir = parent;
|
|
14914
15219
|
}
|