ccqa 1.42.2 → 1.43.0
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/bin/ccqa.mjs +66 -65
- package/dist/package.json +1 -1
- package/package.json +1 -1
package/dist/bin/ccqa.mjs
CHANGED
|
@@ -872,6 +872,35 @@ async function runPool(items, concurrency, fn, opts = {}) {
|
|
|
872
872
|
return results;
|
|
873
873
|
}
|
|
874
874
|
//#endregion
|
|
875
|
+
//#region src/claude/env-keys.ts
|
|
876
|
+
/**
|
|
877
|
+
* Variables that carry a credential the Claude Code process can use on its
|
|
878
|
+
* own, with no login on the host: an API key, a gateway bearer token, or a
|
|
879
|
+
* subscription token from `claude setup-token`.
|
|
880
|
+
*/
|
|
881
|
+
const CREDENTIAL_ENV_KEYS = [
|
|
882
|
+
"ANTHROPIC_API_KEY",
|
|
883
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
884
|
+
"CLAUDE_CODE_OAUTH_TOKEN"
|
|
885
|
+
];
|
|
886
|
+
/**
|
|
887
|
+
* Standard Claude Code environment variables that select the API endpoint and
|
|
888
|
+
* credentials. ccqa forwards whichever of these are set to the underlying
|
|
889
|
+
* Claude Code process; it does not read or interpret their values.
|
|
890
|
+
*
|
|
891
|
+
* - `ANTHROPIC_BASE_URL` — the API endpoint to send requests to.
|
|
892
|
+
* - `ANTHROPIC_AUTH_TOKEN` — sent as `Authorization: Bearer <token>`.
|
|
893
|
+
* - `ANTHROPIC_API_KEY` — API key, when used instead of a token.
|
|
894
|
+
* - `ANTHROPIC_CUSTOM_HEADERS` — extra request headers.
|
|
895
|
+
* - `CLAUDE_CODE_OAUTH_TOKEN` — long-lived subscription token from
|
|
896
|
+
* `claude setup-token`, the headless-CI counterpart of a login.
|
|
897
|
+
*/
|
|
898
|
+
const ENDPOINT_ENV_KEYS = [
|
|
899
|
+
"ANTHROPIC_BASE_URL",
|
|
900
|
+
"ANTHROPIC_CUSTOM_HEADERS",
|
|
901
|
+
...CREDENTIAL_ENV_KEYS
|
|
902
|
+
];
|
|
903
|
+
//#endregion
|
|
875
904
|
//#region src/drift/auth.ts
|
|
876
905
|
/**
|
|
877
906
|
* Claude Code can also run against AWS Bedrock / Google Vertex AI, selected by
|
|
@@ -889,21 +918,20 @@ function cloudProviderEnabled() {
|
|
|
889
918
|
}
|
|
890
919
|
/**
|
|
891
920
|
* Probe whether the host has any credential the Anthropic SDK can pick up:
|
|
892
|
-
*
|
|
893
|
-
*
|
|
894
|
-
*
|
|
895
|
-
*
|
|
896
|
-
*
|
|
897
|
-
*
|
|
898
|
-
*
|
|
899
|
-
* darwin stores the OAuth credentials in the Keychain, not on disk)
|
|
921
|
+
* - one of CREDENTIAL_ENV_KEYS (API key, gateway bearer token, or the
|
|
922
|
+
* subscription token from `claude setup-token`)
|
|
923
|
+
* - CLAUDE_CODE_USE_BEDROCK / CLAUDE_CODE_USE_VERTEX (cloud-provider
|
|
924
|
+
* endpoints authenticated by the cloud SDK's credential chain)
|
|
925
|
+
* - ~/.claude/.credentials.json (Claude Code login, file-based platforms)
|
|
926
|
+
* - macOS Keychain item "Claude Code-credentials" (Claude Code login on
|
|
927
|
+
* darwin stores the OAuth credentials in the Keychain, not on disk)
|
|
900
928
|
*
|
|
901
929
|
* Claude-driven hooks are opt-in, so the caller only consults this after the
|
|
902
930
|
* user has asked for analysis. We never throw — auth absence is a normal flow
|
|
903
931
|
* that surfaces as "analysis skipped".
|
|
904
932
|
*/
|
|
905
933
|
function driftAuthAvailable() {
|
|
906
|
-
for (const key of
|
|
934
|
+
for (const key of CREDENTIAL_ENV_KEYS) {
|
|
907
935
|
const value = process.env[key];
|
|
908
936
|
if (typeof value === "string" && value.length > 0) return { ok: true };
|
|
909
937
|
}
|
|
@@ -912,7 +940,7 @@ function driftAuthAvailable() {
|
|
|
912
940
|
if (process.platform === "darwin" && keychainHasClaudeCredentials()) return { ok: true };
|
|
913
941
|
return {
|
|
914
942
|
ok: false,
|
|
915
|
-
reason:
|
|
943
|
+
reason: `no ${CREDENTIAL_ENV_KEYS.join(" / ")} / Bedrock or Vertex env / claude login`
|
|
916
944
|
};
|
|
917
945
|
}
|
|
918
946
|
/**
|
|
@@ -1520,31 +1548,17 @@ function sum(costs) {
|
|
|
1520
1548
|
}
|
|
1521
1549
|
//#endregion
|
|
1522
1550
|
//#region src/claude/invoke.ts
|
|
1551
|
+
/** The built-in tools an allow-list names: `Bash(*)` is `Bash`; `mcp__*` are not built-ins. */
|
|
1552
|
+
function builtinToolNames(allowedTools) {
|
|
1553
|
+
const names = allowedTools.map((entry) => entry.replace(/\(.*\)$/, "")).filter((name) => !name.startsWith("mcp__"));
|
|
1554
|
+
return [...new Set(names)];
|
|
1555
|
+
}
|
|
1523
1556
|
function resolveModel(explicit) {
|
|
1524
1557
|
if (explicit) return explicit;
|
|
1525
1558
|
const envModel = process.env["CCQA_MODEL"];
|
|
1526
1559
|
return envModel && envModel.length > 0 ? envModel : void 0;
|
|
1527
1560
|
}
|
|
1528
1561
|
/**
|
|
1529
|
-
* Standard Claude Code environment variables that select the API endpoint and
|
|
1530
|
-
* credentials. ccqa forwards whichever of these are set to the underlying
|
|
1531
|
-
* Claude Code process; it does not read or interpret their values.
|
|
1532
|
-
*
|
|
1533
|
-
* - `ANTHROPIC_BASE_URL` — the API endpoint to send requests to.
|
|
1534
|
-
* - `ANTHROPIC_AUTH_TOKEN` — sent as `Authorization: Bearer <token>`.
|
|
1535
|
-
* - `ANTHROPIC_API_KEY` — API key, when used instead of a token.
|
|
1536
|
-
* - `ANTHROPIC_CUSTOM_HEADERS` — extra request headers.
|
|
1537
|
-
* - `CLAUDE_CODE_OAUTH_TOKEN` — long-lived subscription token from
|
|
1538
|
-
* `claude setup-token`, the headless-CI counterpart of a login.
|
|
1539
|
-
*/
|
|
1540
|
-
const ENDPOINT_ENV_KEYS = [
|
|
1541
|
-
"ANTHROPIC_BASE_URL",
|
|
1542
|
-
"ANTHROPIC_AUTH_TOKEN",
|
|
1543
|
-
"ANTHROPIC_API_KEY",
|
|
1544
|
-
"ANTHROPIC_CUSTOM_HEADERS",
|
|
1545
|
-
"CLAUDE_CODE_OAUTH_TOKEN"
|
|
1546
|
-
];
|
|
1547
|
-
/**
|
|
1548
1562
|
* When both credentials are present the OAuth token wins and the API key is
|
|
1549
1563
|
* dropped. Left to the CLI the API key would win, which makes "switch a CI
|
|
1550
1564
|
* job to the subscription token" require unwiring the key everywhere; with
|
|
@@ -1556,21 +1570,6 @@ function preferOauthToken(env) {
|
|
|
1556
1570
|
if (env["CLAUDE_CODE_OAUTH_TOKEN"]) delete env["ANTHROPIC_API_KEY"];
|
|
1557
1571
|
}
|
|
1558
1572
|
/**
|
|
1559
|
-
* Collects the endpoint/auth variables set in the current process environment
|
|
1560
|
-
* so they can be forwarded, verbatim, to every Claude Code invocation. Returns
|
|
1561
|
-
* only the keys that are actually set (non-empty), so unset variables never
|
|
1562
|
-
* override the SDK's own defaults. Credential precedence per preferOauthToken.
|
|
1563
|
-
*/
|
|
1564
|
-
function resolveEndpointEnv() {
|
|
1565
|
-
const endpointEnv = {};
|
|
1566
|
-
for (const key of ENDPOINT_ENV_KEYS) {
|
|
1567
|
-
const value = process.env[key];
|
|
1568
|
-
if (value && value.length > 0) endpointEnv[key] = value;
|
|
1569
|
-
}
|
|
1570
|
-
preferOauthToken(endpointEnv);
|
|
1571
|
-
return endpointEnv;
|
|
1572
|
-
}
|
|
1573
|
-
/**
|
|
1574
1573
|
* Drop endpoint variables that are present but empty, so an empty value never
|
|
1575
1574
|
* reaches the Claude Code process as an override. "Set to nothing" is how a
|
|
1576
1575
|
* caller that cannot omit the key says "use the default" — a CI job wiring
|
|
@@ -1591,18 +1590,14 @@ function withoutEmptyEndpointVars(env) {
|
|
|
1591
1590
|
* resolved view: left to the CLI the API key would win, silently moving every
|
|
1592
1591
|
* call from the subscription to metered billing when a CI job wires both
|
|
1593
1592
|
* (which is exactly what happened before this function existed).
|
|
1594
|
-
*
|
|
1595
|
-
* Returns undefined when no endpoint variable is set and the caller passes no
|
|
1596
|
-
* env, so the SDK keeps its own default environment.
|
|
1597
1593
|
*/
|
|
1598
1594
|
function buildInvocationEnv(env) {
|
|
1599
|
-
const hasEndpointEnv = Object.keys(resolveEndpointEnv()).length > 0;
|
|
1600
|
-
if (!env && !hasEndpointEnv) return void 0;
|
|
1601
1595
|
const merged = withoutEmptyEndpointVars({
|
|
1602
1596
|
...process.env,
|
|
1603
1597
|
...env
|
|
1604
1598
|
});
|
|
1605
1599
|
preferOauthToken(merged);
|
|
1600
|
+
merged["CLAUDE_CODE_DISABLE_AUTO_MEMORY"] = "1";
|
|
1606
1601
|
return merged;
|
|
1607
1602
|
}
|
|
1608
1603
|
let nativeBinaryWarned = false;
|
|
@@ -1624,7 +1619,7 @@ function formatDuration$1(ms) {
|
|
|
1624
1619
|
return `${minutes} minute${minutes === 1 ? "" : "s"}`;
|
|
1625
1620
|
}
|
|
1626
1621
|
async function invokeClaudeStreaming(options, onEvent) {
|
|
1627
|
-
const { prompt, systemPrompt, allowedTools,
|
|
1622
|
+
const { prompt, systemPrompt, allowedTools, disableThinking = false, mcpServers, maxTurns, timeoutMs, env, model, cwd, onAbAction, onAbActionFailed, silenceBashLog = false, envScrubMap = [], relaxAbConstraints = false } = options;
|
|
1628
1623
|
const resolvedModel = resolveModel(model);
|
|
1629
1624
|
const mergedEnv = buildInvocationEnv(env);
|
|
1630
1625
|
const abortController = new AbortController();
|
|
@@ -1637,15 +1632,17 @@ async function invokeClaudeStreaming(options, onEvent) {
|
|
|
1637
1632
|
const sdkOptions = {
|
|
1638
1633
|
systemPrompt,
|
|
1639
1634
|
maxTurns,
|
|
1640
|
-
allowedTools
|
|
1635
|
+
allowedTools,
|
|
1636
|
+
tools: builtinToolNames(allowedTools),
|
|
1637
|
+
strictMcpConfig: true,
|
|
1638
|
+
settingSources: [],
|
|
1641
1639
|
permissionMode: "bypassPermissions",
|
|
1642
1640
|
allowDangerouslySkipPermissions: true,
|
|
1643
1641
|
abortController,
|
|
1644
1642
|
...resolvedModel ? { model: resolvedModel } : {},
|
|
1645
1643
|
...cwd ? { cwd } : {},
|
|
1646
|
-
|
|
1644
|
+
env: mergedEnv,
|
|
1647
1645
|
...mcpServers ? { mcpServers } : {},
|
|
1648
|
-
...disableBuiltinTools ? { tools: [] } : {},
|
|
1649
1646
|
...disableThinking ? { thinking: { type: "disabled" } } : {},
|
|
1650
1647
|
hooks: onAbAction || onAbActionFailed ? {
|
|
1651
1648
|
PreToolUse: [{ hooks: [async (input) => {
|
|
@@ -1774,8 +1771,9 @@ function extractInvocationCost(msg) {
|
|
|
1774
1771
|
const usage = m["usage"];
|
|
1775
1772
|
const modelUsage = m["modelUsage"];
|
|
1776
1773
|
const num = (v) => typeof v === "number" && Number.isFinite(v) ? v : null;
|
|
1774
|
+
const models = modelUsage && typeof modelUsage === "object" ? Object.keys(modelUsage) : [];
|
|
1777
1775
|
return {
|
|
1778
|
-
totalCostUsd: num(m["total_cost_usd"]),
|
|
1776
|
+
totalCostUsd: pricedForClaude(models) ? num(m["total_cost_usd"]) : null,
|
|
1779
1777
|
durationMs: num(m["duration_ms"]),
|
|
1780
1778
|
durationApiMs: num(m["duration_api_ms"]),
|
|
1781
1779
|
numTurns: num(m["num_turns"]),
|
|
@@ -1783,9 +1781,16 @@ function extractInvocationCost(msg) {
|
|
|
1783
1781
|
cacheCreationInputTokens: num(usage?.["cache_creation_input_tokens"]),
|
|
1784
1782
|
cacheReadInputTokens: num(usage?.["cache_read_input_tokens"]),
|
|
1785
1783
|
outputTokens: num(usage?.["output_tokens"]),
|
|
1786
|
-
models
|
|
1784
|
+
models
|
|
1787
1785
|
};
|
|
1788
1786
|
}
|
|
1787
|
+
/**
|
|
1788
|
+
* The SDK prices an unknown model id at a default Claude rate rather than
|
|
1789
|
+
* returning null, so a self-hosted model would report dollars nobody is billed.
|
|
1790
|
+
*/
|
|
1791
|
+
function pricedForClaude(models) {
|
|
1792
|
+
return models.every((id) => /claude/i.test(id));
|
|
1793
|
+
}
|
|
1789
1794
|
const BLOCKED_AB_SUBCOMMANDS = new Set([
|
|
1790
1795
|
"eval",
|
|
1791
1796
|
"js",
|
|
@@ -5015,12 +5020,11 @@ const DraftNamingSchema = z.object({
|
|
|
5015
5020
|
* Returns null only when the invocation reported nothing at all (a mock run,
|
|
5016
5021
|
* an SDK error, or a command that never called a model).
|
|
5017
5022
|
*
|
|
5018
|
-
* The price is one segment among several, not a precondition.
|
|
5019
|
-
*
|
|
5020
|
-
*
|
|
5021
|
-
*
|
|
5022
|
-
*
|
|
5023
|
-
* and become the signal to read.
|
|
5023
|
+
* The price is one segment among several, not a precondition. A model that is
|
|
5024
|
+
* not a Claude model has no price (`extractInvocationCost` drops the SDK's
|
|
5025
|
+
* estimate), and dropping the whole line there would hide real consumption
|
|
5026
|
+
* behind silence. Tokens come from the API response rather than a price list,
|
|
5027
|
+
* so they survive that case and become the signal to read.
|
|
5024
5028
|
*
|
|
5025
5029
|
* `compact: false` (default for CLI logs) keeps raw numbers and adds a
|
|
5026
5030
|
* `model=...` segment. `compact: true` (HTML chip) thousand-separates fresh
|
|
@@ -13529,7 +13533,6 @@ async function runLiveExecutor(input) {
|
|
|
13529
13533
|
prompt: buildStepVerdictPrompt(step, transcript),
|
|
13530
13534
|
model: input.model,
|
|
13531
13535
|
allowedTools: [],
|
|
13532
|
-
disableBuiltinTools: true,
|
|
13533
13536
|
disableThinking: true,
|
|
13534
13537
|
maxTurns: 1,
|
|
13535
13538
|
timeoutMs: VERDICT_TIMEOUT_MS
|
|
@@ -14704,7 +14707,7 @@ async function cleanupActions$1(actions, model) {
|
|
|
14704
14707
|
try {
|
|
14705
14708
|
const { result, isError } = await invokeClaudeStreaming({
|
|
14706
14709
|
prompt: buildCleanupPrompt(actions),
|
|
14707
|
-
|
|
14710
|
+
allowedTools: [],
|
|
14708
14711
|
maxTurns: 1,
|
|
14709
14712
|
model
|
|
14710
14713
|
}, () => {});
|
|
@@ -16633,7 +16636,6 @@ async function updateAgentPrompt(args) {
|
|
|
16633
16636
|
prompt: userPrompt,
|
|
16634
16637
|
systemPrompt,
|
|
16635
16638
|
allowedTools: [],
|
|
16636
|
-
disableBuiltinTools: true,
|
|
16637
16639
|
disableThinking: true,
|
|
16638
16640
|
...model ? { model } : {}
|
|
16639
16641
|
}, () => {});
|
|
@@ -30613,7 +30615,6 @@ function createLearningWorker(deps) {
|
|
|
30613
30615
|
prompt: buildLearningUserPrompt(cases.slice(0, LEARNING_MAX_CASES)),
|
|
30614
30616
|
systemPrompt: LEARNING_SYSTEM_PROMPT,
|
|
30615
30617
|
allowedTools: [],
|
|
30616
|
-
disableBuiltinTools: true,
|
|
30617
30618
|
maxTurns: 1
|
|
30618
30619
|
}, () => {});
|
|
30619
30620
|
const guidance = result?.trim();
|
package/dist/package.json
CHANGED