@sentry/warden 0.22.0 → 0.23.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/.oxlintrc.json +169 -0
- package/CHANGELOG.md +210 -0
- package/README.md +1 -1
- package/dist/cli/args.d.ts +6 -2
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +175 -207
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/commands/add.d.ts.map +1 -1
- package/dist/cli/commands/add.js +16 -5
- package/dist/cli/commands/add.js.map +1 -1
- package/dist/cli/commands/build.d.ts +13 -0
- package/dist/cli/commands/build.d.ts.map +1 -0
- package/dist/cli/commands/build.js +384 -0
- package/dist/cli/commands/build.js.map +1 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +10 -2
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/runs.d.ts.map +1 -1
- package/dist/cli/commands/runs.js +12 -6
- package/dist/cli/commands/runs.js.map +1 -1
- package/dist/cli/commands/sync.d.ts.map +1 -1
- package/dist/cli/commands/sync.js +4 -1
- package/dist/cli/commands/sync.js.map +1 -1
- package/dist/cli/context.d.ts.map +1 -1
- package/dist/cli/context.js +8 -1
- package/dist/cli/context.js.map +1 -1
- package/dist/cli/files.d.ts.map +1 -1
- package/dist/cli/files.js +27 -9
- package/dist/cli/files.js.map +1 -1
- package/dist/cli/help.d.ts +4 -0
- package/dist/cli/help.d.ts.map +1 -0
- package/dist/cli/help.js +448 -0
- package/dist/cli/help.js.map +1 -0
- package/dist/cli/index.js +2 -13
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/input.d.ts +12 -0
- package/dist/cli/input.d.ts.map +1 -1
- package/dist/cli/input.js +60 -0
- package/dist/cli/input.js.map +1 -1
- package/dist/cli/main.d.ts +39 -2
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +264 -74
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/output/box.js +1 -1
- package/dist/cli/output/formatters.d.ts +10 -2
- package/dist/cli/output/formatters.d.ts.map +1 -1
- package/dist/cli/output/formatters.js +32 -9
- package/dist/cli/output/formatters.js.map +1 -1
- package/dist/cli/output/index.d.ts +1 -1
- package/dist/cli/output/index.d.ts.map +1 -1
- package/dist/cli/output/index.js +1 -1
- package/dist/cli/output/index.js.map +1 -1
- package/dist/cli/output/ink-runner.d.ts +3 -1
- package/dist/cli/output/ink-runner.d.ts.map +1 -1
- package/dist/cli/output/ink-runner.js +64 -28
- package/dist/cli/output/ink-runner.js.map +1 -1
- package/dist/cli/output/jsonl.d.ts +37 -0
- package/dist/cli/output/jsonl.d.ts.map +1 -1
- package/dist/cli/output/jsonl.js +6 -0
- package/dist/cli/output/jsonl.js.map +1 -1
- package/dist/cli/output/live-status.d.ts +48 -0
- package/dist/cli/output/live-status.d.ts.map +1 -0
- package/dist/cli/output/live-status.js +173 -0
- package/dist/cli/output/live-status.js.map +1 -0
- package/dist/cli/output/reporter.d.ts +4 -0
- package/dist/cli/output/reporter.d.ts.map +1 -1
- package/dist/cli/output/reporter.js +20 -3
- package/dist/cli/output/reporter.js.map +1 -1
- package/dist/cli/output/tasks.d.ts +8 -5
- package/dist/cli/output/tasks.d.ts.map +1 -1
- package/dist/cli/output/tasks.js +113 -71
- package/dist/cli/output/tasks.js.map +1 -1
- package/dist/cli/signals.d.ts +15 -0
- package/dist/cli/signals.d.ts.map +1 -0
- package/dist/cli/signals.js +26 -0
- package/dist/cli/signals.js.map +1 -0
- package/dist/config/loader.d.ts +48 -7
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +228 -32
- package/dist/config/loader.js.map +1 -1
- package/dist/config/schema.d.ts +55 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +34 -2
- package/dist/config/schema.js.map +1 -1
- package/dist/diff/context.d.ts +9 -2
- package/dist/diff/context.d.ts.map +1 -1
- package/dist/diff/context.js +65 -17
- package/dist/diff/context.js.map +1 -1
- package/dist/evals/runner.d.ts.map +1 -1
- package/dist/evals/runner.js +16 -0
- package/dist/evals/runner.js.map +1 -1
- package/dist/event/context.d.ts.map +1 -1
- package/dist/event/context.js +1 -0
- package/dist/event/context.js.map +1 -1
- package/dist/event/schedule-context.d.ts.map +1 -1
- package/dist/event/schedule-context.js +1 -0
- package/dist/event/schedule-context.js.map +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/output/dedup.d.ts +8 -11
- package/dist/output/dedup.d.ts.map +1 -1
- package/dist/output/dedup.js +39 -37
- package/dist/output/dedup.js.map +1 -1
- package/dist/output/github-checks.d.ts +1 -0
- package/dist/output/github-checks.d.ts.map +1 -1
- package/dist/output/github-checks.js +9 -7
- package/dist/output/github-checks.js.map +1 -1
- package/dist/sdk/analyze.d.ts.map +1 -1
- package/dist/sdk/analyze.js +147 -277
- package/dist/sdk/analyze.js.map +1 -1
- package/dist/sdk/circuit-breaker.d.ts +26 -0
- package/dist/sdk/circuit-breaker.d.ts.map +1 -0
- package/dist/sdk/circuit-breaker.js +53 -0
- package/dist/sdk/circuit-breaker.js.map +1 -0
- package/dist/sdk/errors.d.ts +5 -0
- package/dist/sdk/errors.d.ts.map +1 -1
- package/dist/sdk/errors.js +27 -0
- package/dist/sdk/errors.js.map +1 -1
- package/dist/sdk/extract.d.ts +18 -9
- package/dist/sdk/extract.d.ts.map +1 -1
- package/dist/sdk/extract.js +95 -87
- package/dist/sdk/extract.js.map +1 -1
- package/dist/sdk/fix-quality.d.ts +5 -0
- package/dist/sdk/fix-quality.d.ts.map +1 -1
- package/dist/sdk/fix-quality.js +42 -23
- package/dist/sdk/fix-quality.js.map +1 -1
- package/dist/sdk/haiku.d.ts +6 -0
- package/dist/sdk/haiku.d.ts.map +1 -1
- package/dist/sdk/haiku.js +75 -49
- package/dist/sdk/haiku.js.map +1 -1
- package/dist/sdk/json-output.d.ts +31 -0
- package/dist/sdk/json-output.d.ts.map +1 -0
- package/dist/sdk/json-output.js +98 -0
- package/dist/sdk/json-output.js.map +1 -0
- package/dist/sdk/model-pricing.json +56 -16
- package/dist/sdk/post-process.d.ts +29 -0
- package/dist/sdk/post-process.d.ts.map +1 -0
- package/dist/sdk/post-process.js +65 -0
- package/dist/sdk/post-process.js.map +1 -0
- package/dist/sdk/prepare.d.ts.map +1 -1
- package/dist/sdk/prepare.js +4 -1
- package/dist/sdk/prepare.js.map +1 -1
- package/dist/sdk/pricing.d.ts +18 -0
- package/dist/sdk/pricing.d.ts.map +1 -1
- package/dist/sdk/pricing.js +108 -3
- package/dist/sdk/pricing.js.map +1 -1
- package/dist/sdk/prompt-sections.d.ts +56 -0
- package/dist/sdk/prompt-sections.d.ts.map +1 -0
- package/dist/sdk/prompt-sections.js +118 -0
- package/dist/sdk/prompt-sections.js.map +1 -0
- package/dist/sdk/prompt.d.ts +2 -17
- package/dist/sdk/prompt.d.ts.map +1 -1
- package/dist/sdk/prompt.js +15 -37
- package/dist/sdk/prompt.js.map +1 -1
- package/dist/sdk/report-files.d.ts +15 -0
- package/dist/sdk/report-files.d.ts.map +1 -0
- package/dist/sdk/report-files.js +20 -0
- package/dist/sdk/report-files.js.map +1 -0
- package/dist/sdk/runner.d.ts +9 -1
- package/dist/sdk/runner.d.ts.map +1 -1
- package/dist/sdk/runner.js +6 -0
- package/dist/sdk/runner.js.map +1 -1
- package/dist/sdk/runtimes/claude.d.ts +3 -0
- package/dist/sdk/runtimes/claude.d.ts.map +1 -0
- package/dist/sdk/runtimes/claude.js +376 -0
- package/dist/sdk/runtimes/claude.js.map +1 -0
- package/dist/sdk/runtimes/index.d.ts +12 -0
- package/dist/sdk/runtimes/index.d.ts.map +1 -0
- package/dist/sdk/runtimes/index.js +22 -0
- package/dist/sdk/runtimes/index.js.map +1 -0
- package/dist/sdk/runtimes/types.d.ts +116 -0
- package/dist/sdk/runtimes/types.d.ts.map +1 -0
- package/dist/sdk/runtimes/types.js +17 -0
- package/dist/sdk/runtimes/types.js.map +1 -0
- package/dist/sdk/types.d.ts +23 -2
- package/dist/sdk/types.d.ts.map +1 -1
- package/dist/sdk/types.js.map +1 -1
- package/dist/sdk/usage.d.ts +21 -6
- package/dist/sdk/usage.d.ts.map +1 -1
- package/dist/sdk/usage.js +36 -23
- package/dist/sdk/usage.js.map +1 -1
- package/dist/sdk/verify.d.ts +25 -0
- package/dist/sdk/verify.d.ts.map +1 -0
- package/dist/sdk/verify.js +226 -0
- package/dist/sdk/verify.js.map +1 -0
- package/dist/skill-builder/agentic.d.ts +33 -0
- package/dist/skill-builder/agentic.d.ts.map +1 -0
- package/dist/skill-builder/agentic.js +240 -0
- package/dist/skill-builder/agentic.js.map +1 -0
- package/dist/skill-builder/authoring-provider.d.ts +5 -0
- package/dist/skill-builder/authoring-provider.d.ts.map +1 -0
- package/dist/skill-builder/authoring-provider.js +70 -0
- package/dist/skill-builder/authoring-provider.js.map +1 -0
- package/dist/skill-builder/definition.d.ts +46 -0
- package/dist/skill-builder/definition.d.ts.map +1 -0
- package/dist/skill-builder/definition.js +171 -0
- package/dist/skill-builder/definition.js.map +1 -0
- package/dist/skill-builder/outline-contract.d.ts +113 -0
- package/dist/skill-builder/outline-contract.d.ts.map +1 -0
- package/dist/skill-builder/outline-contract.js +73 -0
- package/dist/skill-builder/outline-contract.js.map +1 -0
- package/dist/skill-builder/outline-state.d.ts +158 -0
- package/dist/skill-builder/outline-state.d.ts.map +1 -0
- package/dist/skill-builder/outline-state.js +87 -0
- package/dist/skill-builder/outline-state.js.map +1 -0
- package/dist/skill-builder/outline.d.ts +36 -0
- package/dist/skill-builder/outline.d.ts.map +1 -0
- package/dist/skill-builder/outline.js +345 -0
- package/dist/skill-builder/outline.js.map +1 -0
- package/dist/skill-builder/skill-contract.d.ts +97 -0
- package/dist/skill-builder/skill-contract.d.ts.map +1 -0
- package/dist/skill-builder/skill-contract.js +58 -0
- package/dist/skill-builder/skill-contract.js.map +1 -0
- package/dist/skill-builder/skill-prompts.d.ts +61 -0
- package/dist/skill-builder/skill-prompts.d.ts.map +1 -0
- package/dist/skill-builder/skill-prompts.js +285 -0
- package/dist/skill-builder/skill-prompts.js.map +1 -0
- package/dist/skill-builder/skill.d.ts +23 -0
- package/dist/skill-builder/skill.d.ts.map +1 -0
- package/dist/skill-builder/skill.js +563 -0
- package/dist/skill-builder/skill.js.map +1 -0
- package/dist/skills/index.d.ts +2 -2
- package/dist/skills/index.d.ts.map +1 -1
- package/dist/skills/index.js +1 -1
- package/dist/skills/index.js.map +1 -1
- package/dist/skills/loader.d.ts +20 -6
- package/dist/skills/loader.d.ts.map +1 -1
- package/dist/skills/loader.js +88 -141
- package/dist/skills/loader.js.map +1 -1
- package/dist/skills/remote.d.ts +11 -7
- package/dist/skills/remote.d.ts.map +1 -1
- package/dist/skills/remote.js +62 -162
- package/dist/skills/remote.js.map +1 -1
- package/dist/triggers/matcher.d.ts.map +1 -1
- package/dist/triggers/matcher.js +28 -15
- package/dist/triggers/matcher.js.map +1 -1
- package/dist/types/index.d.ts +40 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +11 -0
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/path.d.ts +17 -0
- package/dist/utils/path.d.ts.map +1 -0
- package/dist/utils/path.js +36 -0
- package/dist/utils/path.js.map +1 -0
- package/package.json +8 -8
- package/policies/README.md +22 -0
- package/policies/code-comments.md +20 -0
- package/policies/policy-template.md +14 -0
- package/skills/warden/SPEC.md +112 -0
- package/skills/warden/references/config-schema.md +29 -13
- package/skills/warden/references/configuration.md +20 -8
- package/skills/warden-sweep/SPEC.md +126 -0
- package/src/builtin-skills/security-review/SKILL.md +81 -0
- package/src/builtin-skills/security-review/SPEC.md +77 -0
- package/src/builtin-skills/security-review/references/github-workflows.md +186 -0
- package/src/builtin-skills/security-review/references/javascript-typescript.md +69 -0
- package/src/builtin-skills/security-review/references/python.md +70 -0
- package/src/internal-skills/skill-writer/EVAL.md +124 -0
- package/src/internal-skills/skill-writer/SKILL.md +159 -0
- package/src/internal-skills/skill-writer/SOURCES.md +94 -0
- package/src/internal-skills/skill-writer/SPEC.md +153 -0
- package/src/internal-skills/skill-writer/references/artifact-layouts/argument-driven-skill-layout.md +32 -0
- package/src/internal-skills/skill-writer/references/artifact-layouts/asset-template-skill-layout.md +30 -0
- package/src/internal-skills/skill-writer/references/artifact-layouts/inline-skill-layout.md +28 -0
- package/src/internal-skills/skill-writer/references/artifact-layouts/reference-backed-skill-layout.md +35 -0
- package/src/internal-skills/skill-writer/references/artifact-layouts/script-backed-skill-layout.md +31 -0
- package/src/internal-skills/skill-writer/references/authoring-path.md +91 -0
- package/src/internal-skills/skill-writer/references/claude-code/argument-substitutions.md +20 -0
- package/src/internal-skills/skill-writer/references/claude-code/dynamic-context.md +16 -0
- package/src/internal-skills/skill-writer/references/claude-code/frontmatter-and-invocation.md +33 -0
- package/src/internal-skills/skill-writer/references/claude-code/hook-backed-skills.md +28 -0
- package/src/internal-skills/skill-writer/references/claude-code/subagent-fork-skills.md +22 -0
- package/src/internal-skills/skill-writer/references/description-optimization.md +28 -0
- package/src/internal-skills/skill-writer/references/design-principles.md +74 -0
- package/src/internal-skills/skill-writer/references/evaluation-path.md +73 -0
- package/src/internal-skills/skill-writer/references/examples/documentation-skill.md +70 -0
- package/src/internal-skills/skill-writer/references/examples/evaluator-loop-skill.md +38 -0
- package/src/internal-skills/skill-writer/references/examples/hook-backed-skill.md +39 -0
- package/src/internal-skills/skill-writer/references/examples/router-skill.md +41 -0
- package/src/internal-skills/skill-writer/references/examples/security-review-skill.md +69 -0
- package/src/internal-skills/skill-writer/references/examples/subagent-fork-skill.md +39 -0
- package/src/internal-skills/skill-writer/references/examples/workflow-process-skill.md +65 -0
- package/src/internal-skills/skill-writer/references/execution-shapes.md +68 -0
- package/src/internal-skills/skill-writer/references/iteration-evidence.md +82 -0
- package/src/internal-skills/skill-writer/references/iteration-path.md +40 -0
- package/src/internal-skills/skill-writer/references/mode-selection.md +67 -0
- package/src/internal-skills/skill-writer/references/output-contracts.md +68 -0
- package/src/internal-skills/skill-writer/references/reference-architecture.md +71 -0
- package/src/internal-skills/skill-writer/references/registration-validation.md +67 -0
- package/src/internal-skills/skill-writer/references/source-discovery.md +60 -0
- package/src/internal-skills/skill-writer/references/spec-template.md +118 -0
- package/src/internal-skills/skill-writer/references/structure-troubleshooting.md +111 -0
- package/src/internal-skills/skill-writer/references/synthesis-path.md +119 -0
- package/src/internal-skills/skill-writer/references/workflow-mechanics/evaluator-loops.md +25 -0
- package/src/internal-skills/skill-writer/references/workflow-mechanics/orchestrator-workers.md +25 -0
- package/src/internal-skills/skill-writer/references/workflow-mechanics/parallel-workflows.md +21 -0
- package/src/internal-skills/skill-writer/references/workflow-mechanics/plan-validate-execute.md +26 -0
- package/src/internal-skills/skill-writer/references/workflow-mechanics/prompt-chaining.md +24 -0
- package/src/internal-skills/skill-writer/references/workflow-mechanics/routing-workflows.md +28 -0
- package/src/internal-skills/skill-writer/references/workflow-mechanics/validation-loops.md +26 -0
- package/src/internal-skills/skill-writer/scripts/quick_validate.py +158 -0
- package/src/internal-skills/skill-writer/scripts/quick_validate_test.py +79 -0
package/dist/sdk/analyze.js
CHANGED
|
@@ -1,33 +1,69 @@
|
|
|
1
|
-
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
2
1
|
import { getHunkLineRange } from '../diff/index.js';
|
|
3
|
-
import { Sentry, emitExtractionMetrics, emitRetryMetric
|
|
4
|
-
import { SkillRunnerError, WardenAuthenticationError, isRetryableError, isAuthenticationError, isAuthenticationErrorMessage, isSubprocessError, classifyError, mapExtractionErrorCode } from './errors.js';
|
|
2
|
+
import { Sentry, emitExtractionMetrics, emitRetryMetric } from '../sentry.js';
|
|
3
|
+
import { SkillRunnerError, WardenAuthenticationError, isRetryableError, isAuthenticationError, isAuthenticationErrorMessage, isSubprocessError, classifyError, mapExtractionErrorCode, sanitizeErrorMessage } from './errors.js';
|
|
5
4
|
import { DEFAULT_RETRY_CONFIG, calculateRetryDelay, sleep } from './retry.js';
|
|
6
|
-
import {
|
|
5
|
+
import { aggregateUsage, emptyUsage, estimateTokens, aggregateAuxiliaryUsage } from './usage.js';
|
|
7
6
|
import { buildHunkSystemPrompt, buildHunkUserPrompt } from './prompt.js';
|
|
8
|
-
import { extractFindingsJson, extractFindingsWithLLM, validateFindings
|
|
9
|
-
import {
|
|
7
|
+
import { extractFindingsJson, extractFindingsWithLLM, validateFindings } from './extract.js';
|
|
8
|
+
import { postProcessFindings } from './post-process.js';
|
|
9
|
+
import { buildFileReports } from './report-files.js';
|
|
10
|
+
import { getRuntime, getRuntimeProviderOptions } from './runtimes/index.js';
|
|
10
11
|
import { LARGE_PROMPT_THRESHOLD_CHARS, DEFAULT_FILE_CONCURRENCY, } from './types.js';
|
|
11
12
|
import { prepareFiles } from './prepare.js';
|
|
12
13
|
import { runPool } from '../utils/index.js';
|
|
14
|
+
function notifyHunkFailed(callbacks, lineRange, message) {
|
|
15
|
+
if (callbacks) {
|
|
16
|
+
callbacks.onHunkFailed?.(lineRange, message);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
console.error(`Hunk analysis failed for ${lineRange}.`);
|
|
20
|
+
}
|
|
21
|
+
function isAbortRequested(error, abortController) {
|
|
22
|
+
return (abortController?.signal.aborted ?? false) || classifyError(error).code === 'aborted';
|
|
23
|
+
}
|
|
24
|
+
function isCircuitBreakerCode(code) {
|
|
25
|
+
return code === 'auth_failed' || code === 'provider_unavailable';
|
|
26
|
+
}
|
|
27
|
+
function hunkFailureFromCircuit(reason, usage, attempts) {
|
|
28
|
+
return {
|
|
29
|
+
findings: [],
|
|
30
|
+
usage: aggregateUsage(usage),
|
|
31
|
+
failed: true,
|
|
32
|
+
extractionFailed: false,
|
|
33
|
+
failureCode: reason.code,
|
|
34
|
+
failureMessage: reason.message,
|
|
35
|
+
attempts,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function recordCircuitFailure(options, code, message) {
|
|
39
|
+
if (!isCircuitBreakerCode(code))
|
|
40
|
+
return undefined;
|
|
41
|
+
options.circuitBreaker?.recordFailure(code, message);
|
|
42
|
+
return options.circuitBreaker?.reason;
|
|
43
|
+
}
|
|
13
44
|
/**
|
|
14
45
|
* Parse findings from a hunk analysis result.
|
|
15
46
|
* Uses a two-tier extraction strategy:
|
|
16
47
|
* 1. Regex-based extraction (fast, handles well-formed output)
|
|
17
48
|
* 2. LLM fallback using haiku (handles malformed output gracefully)
|
|
18
49
|
*/
|
|
19
|
-
async function parseHunkOutput(result, filename,
|
|
20
|
-
if (result.
|
|
50
|
+
async function parseHunkOutput(result, filename, options) {
|
|
51
|
+
if (result.status !== 'success') {
|
|
21
52
|
// SDK error - not an extraction failure, just no findings
|
|
22
53
|
return { findings: [], extractionFailed: false, extractionMethod: 'none' };
|
|
23
54
|
}
|
|
24
55
|
// Tier 1: Try regex-based extraction first (fast)
|
|
25
|
-
const extracted = extractFindingsJson(result.
|
|
56
|
+
const extracted = extractFindingsJson(result.text);
|
|
26
57
|
if (extracted.success) {
|
|
27
58
|
return { findings: validateFindings(extracted.findings, filename), extractionFailed: false, extractionMethod: 'regex' };
|
|
28
59
|
}
|
|
29
60
|
// Tier 2: Try LLM fallback for malformed output
|
|
30
|
-
const fallback = await extractFindingsWithLLM(result.
|
|
61
|
+
const fallback = await extractFindingsWithLLM(result.text, {
|
|
62
|
+
apiKey: options.apiKey,
|
|
63
|
+
runtime: options.runtime,
|
|
64
|
+
model: options.auxiliaryModel,
|
|
65
|
+
maxRetries: options.auxiliaryMaxRetries,
|
|
66
|
+
});
|
|
31
67
|
if (fallback.success) {
|
|
32
68
|
return { findings: validateFindings(fallback.findings, filename), extractionFailed: false, extractionMethod: 'llm', extractionUsage: fallback.usage };
|
|
33
69
|
}
|
|
@@ -64,201 +100,6 @@ export function filterOutOfRangeFindings(findings, hunkRange) {
|
|
|
64
100
|
}
|
|
65
101
|
return { filtered, dropped };
|
|
66
102
|
}
|
|
67
|
-
/**
|
|
68
|
-
* Execute a single SDK query attempt.
|
|
69
|
-
* Captures stderr for better error diagnostics when Claude Code fails.
|
|
70
|
-
*/
|
|
71
|
-
async function executeQuery(systemPrompt, userPrompt, repoPath, options, skillName) {
|
|
72
|
-
const { maxTurns = 50, model, abortController, pathToClaudeCodeExecutable } = options;
|
|
73
|
-
const modelId = model ?? 'unknown';
|
|
74
|
-
return Sentry.startSpan({
|
|
75
|
-
op: 'gen_ai.invoke_agent',
|
|
76
|
-
name: `invoke_agent ${skillName}`,
|
|
77
|
-
attributes: {
|
|
78
|
-
'gen_ai.operation.name': 'invoke_agent',
|
|
79
|
-
'gen_ai.provider.name': 'anthropic',
|
|
80
|
-
'gen_ai.agent.name': skillName,
|
|
81
|
-
'gen_ai.request.model': modelId,
|
|
82
|
-
'warden.request.max_turns': maxTurns,
|
|
83
|
-
},
|
|
84
|
-
}, async (span) => {
|
|
85
|
-
span.setAttribute('gen_ai.request.messages', JSON.stringify([
|
|
86
|
-
{ role: 'system', content: systemPrompt },
|
|
87
|
-
{ role: 'user', content: userPrompt },
|
|
88
|
-
]));
|
|
89
|
-
// Capture stderr output for better error diagnostics
|
|
90
|
-
const stderrChunks = [];
|
|
91
|
-
const stream = query({
|
|
92
|
-
prompt: userPrompt,
|
|
93
|
-
options: {
|
|
94
|
-
maxTurns,
|
|
95
|
-
cwd: repoPath,
|
|
96
|
-
systemPrompt,
|
|
97
|
-
// Only allow read-only tools - context is already provided in the prompt
|
|
98
|
-
allowedTools: ['Read', 'Grep', 'Glob'],
|
|
99
|
-
// Explicitly block modification/side-effect tools as defense-in-depth
|
|
100
|
-
disallowedTools: ['Write', 'Edit', 'Bash', 'WebFetch', 'WebSearch', 'Task', 'TodoWrite'],
|
|
101
|
-
permissionMode: 'bypassPermissions',
|
|
102
|
-
// Prevent SDK from writing session .jsonl files and polluting Claude Code's session index
|
|
103
|
-
persistSession: false,
|
|
104
|
-
model,
|
|
105
|
-
abortController,
|
|
106
|
-
pathToClaudeCodeExecutable,
|
|
107
|
-
stderr: (data) => {
|
|
108
|
-
stderrChunks.push(data);
|
|
109
|
-
},
|
|
110
|
-
},
|
|
111
|
-
});
|
|
112
|
-
let resultMessage;
|
|
113
|
-
let authError;
|
|
114
|
-
// Per-turn tracing: buffer assistant messages and tool progress to create
|
|
115
|
-
// child spans (gen_ai.chat + gen_ai.execute_tool) under the invoke_agent span.
|
|
116
|
-
// We flush the previous turn when a new assistant message or result arrives,
|
|
117
|
-
// ensuring tool_progress events are captured before span creation.
|
|
118
|
-
let turnCount = 0;
|
|
119
|
-
let pendingTurn = null;
|
|
120
|
-
const pendingToolProgress = new Map();
|
|
121
|
-
/** Flush buffered turn data into gen_ai.chat and gen_ai.execute_tool child spans. */
|
|
122
|
-
function flushPendingTurn() {
|
|
123
|
-
if (!pendingTurn)
|
|
124
|
-
return;
|
|
125
|
-
turnCount++;
|
|
126
|
-
const turn = pendingTurn;
|
|
127
|
-
const toolProgress = new Map(pendingToolProgress);
|
|
128
|
-
pendingTurn = null;
|
|
129
|
-
pendingToolProgress.clear();
|
|
130
|
-
try {
|
|
131
|
-
const totalInput = turn.inputTokens + turn.cacheRead + turn.cacheWrite;
|
|
132
|
-
Sentry.startSpan({
|
|
133
|
-
op: 'gen_ai.chat',
|
|
134
|
-
name: `chat ${skillName} turn ${turnCount}`,
|
|
135
|
-
attributes: {
|
|
136
|
-
'gen_ai.operation.name': 'chat',
|
|
137
|
-
'gen_ai.provider.name': 'anthropic',
|
|
138
|
-
'gen_ai.agent.name': skillName,
|
|
139
|
-
'gen_ai.request.model': modelId,
|
|
140
|
-
'gen_ai.response.model': turn.model,
|
|
141
|
-
'gen_ai.usage.input_tokens': totalInput,
|
|
142
|
-
'gen_ai.usage.output_tokens': turn.outputTokens,
|
|
143
|
-
'gen_ai.usage.input_tokens.cached': turn.cacheRead,
|
|
144
|
-
'gen_ai.usage.input_tokens.cache_write': turn.cacheWrite,
|
|
145
|
-
'gen_ai.usage.total_tokens': totalInput + turn.outputTokens,
|
|
146
|
-
'gen_ai.tool_use.count': turn.toolUses.length,
|
|
147
|
-
},
|
|
148
|
-
}, () => {
|
|
149
|
-
for (const toolUse of turn.toolUses) {
|
|
150
|
-
const elapsed = toolProgress.get(toolUse.id);
|
|
151
|
-
Sentry.startSpan({
|
|
152
|
-
op: 'gen_ai.execute_tool',
|
|
153
|
-
name: toolUse.name,
|
|
154
|
-
attributes: {
|
|
155
|
-
'gen_ai.tool.name': toolUse.name,
|
|
156
|
-
...(elapsed !== undefined && { 'tool.elapsed_seconds': elapsed }),
|
|
157
|
-
},
|
|
158
|
-
}, () => { });
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
catch {
|
|
163
|
-
// Telemetry should never break the workflow
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
try {
|
|
167
|
-
for await (const message of stream) {
|
|
168
|
-
if (message.type === 'assistant') {
|
|
169
|
-
flushPendingTurn();
|
|
170
|
-
const msg = message.message;
|
|
171
|
-
const toolUses = msg.content
|
|
172
|
-
.filter((block) => block.type === 'tool_use')
|
|
173
|
-
.map(({ id, name }) => ({ id, name }));
|
|
174
|
-
pendingTurn = {
|
|
175
|
-
toolUses,
|
|
176
|
-
inputTokens: msg.usage?.input_tokens ?? 0,
|
|
177
|
-
outputTokens: msg.usage?.output_tokens ?? 0,
|
|
178
|
-
cacheRead: msg.usage?.cache_read_input_tokens ?? 0,
|
|
179
|
-
cacheWrite: msg.usage?.cache_creation_input_tokens ?? 0,
|
|
180
|
-
model: msg.model,
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
else if (message.type === 'tool_progress') {
|
|
184
|
-
pendingToolProgress.set(message.tool_use_id, message.elapsed_time_seconds);
|
|
185
|
-
}
|
|
186
|
-
else if (message.type === 'result') {
|
|
187
|
-
flushPendingTurn();
|
|
188
|
-
resultMessage = message;
|
|
189
|
-
}
|
|
190
|
-
else if (message.type === 'auth_status' && message.error) {
|
|
191
|
-
authError = message.error;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
catch (error) {
|
|
196
|
-
// Re-throw with stderr info if available
|
|
197
|
-
const stderr = stderrChunks.join('').trim();
|
|
198
|
-
if (stderr) {
|
|
199
|
-
const originalMessage = error instanceof Error ? error.message : String(error);
|
|
200
|
-
const enhancedError = new Error(`${originalMessage}\nClaude Code stderr: ${stderr}`);
|
|
201
|
-
enhancedError.cause = error;
|
|
202
|
-
throw enhancedError;
|
|
203
|
-
}
|
|
204
|
-
throw error;
|
|
205
|
-
}
|
|
206
|
-
finally {
|
|
207
|
-
// Flush any pending turn data for trace completeness
|
|
208
|
-
flushPendingTurn();
|
|
209
|
-
}
|
|
210
|
-
// Set response attributes from SDK result
|
|
211
|
-
if (resultMessage) {
|
|
212
|
-
const usage = resultMessage.usage;
|
|
213
|
-
if (usage) {
|
|
214
|
-
const inputTokens = usage.input_tokens ?? 0;
|
|
215
|
-
const outputTokens = usage.output_tokens ?? 0;
|
|
216
|
-
const cacheRead = usage.cache_read_input_tokens ?? 0;
|
|
217
|
-
const cacheWrite = usage.cache_creation_input_tokens ?? 0;
|
|
218
|
-
// Anthropic API's input_tokens is only the non-cached portion.
|
|
219
|
-
// OpenTelemetry gen_ai.usage.input_tokens expects the total input tokens.
|
|
220
|
-
const totalInputTokens = inputTokens + cacheRead + cacheWrite;
|
|
221
|
-
span.setAttribute('gen_ai.usage.input_tokens', totalInputTokens);
|
|
222
|
-
span.setAttribute('gen_ai.usage.output_tokens', outputTokens);
|
|
223
|
-
span.setAttribute('gen_ai.usage.input_tokens.cached', cacheRead);
|
|
224
|
-
span.setAttribute('gen_ai.usage.input_tokens.cache_write', cacheWrite);
|
|
225
|
-
span.setAttribute('gen_ai.usage.total_tokens', totalInputTokens + outputTokens);
|
|
226
|
-
}
|
|
227
|
-
if (resultMessage.total_cost_usd !== undefined) {
|
|
228
|
-
span.setAttribute('gen_ai.cost.total_tokens', resultMessage.total_cost_usd);
|
|
229
|
-
}
|
|
230
|
-
if (resultMessage.uuid) {
|
|
231
|
-
span.setAttribute('gen_ai.response.id', resultMessage.uuid);
|
|
232
|
-
}
|
|
233
|
-
if (resultMessage.modelUsage) {
|
|
234
|
-
const models = Object.keys(resultMessage.modelUsage);
|
|
235
|
-
if (models.length === 1 && models[0]) {
|
|
236
|
-
// Single model: set per OTel spec (string, one model)
|
|
237
|
-
span.setAttribute('gen_ai.response.model', models[0]);
|
|
238
|
-
}
|
|
239
|
-
// Multiple models: don't set gen_ai.response.model on the parent.
|
|
240
|
-
// Per-turn gen_ai.chat child spans carry the correct model each.
|
|
241
|
-
}
|
|
242
|
-
if (resultMessage.subtype === 'success' && resultMessage.result) {
|
|
243
|
-
span.setAttribute('gen_ai.response.text', JSON.stringify([resultMessage.result]));
|
|
244
|
-
}
|
|
245
|
-
// Optional SDK metadata attributes
|
|
246
|
-
const optionalAttrs = {
|
|
247
|
-
'gen_ai.conversation.id': resultMessage.session_id,
|
|
248
|
-
'sdk.duration_ms': resultMessage.duration_ms,
|
|
249
|
-
'sdk.duration_api_ms': resultMessage.duration_api_ms,
|
|
250
|
-
'sdk.num_turns': resultMessage.num_turns,
|
|
251
|
-
};
|
|
252
|
-
for (const [key, value] of Object.entries(optionalAttrs)) {
|
|
253
|
-
if (value !== undefined) {
|
|
254
|
-
span.setAttribute(key, value);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
const stderr = stderrChunks.join('').trim() || undefined;
|
|
259
|
-
return { result: resultMessage, authError, stderr };
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
103
|
/**
|
|
263
104
|
* Analyze a single hunk with retry logic for transient failures.
|
|
264
105
|
*/
|
|
@@ -272,7 +113,7 @@ async function analyzeHunk(skill, hunkCtx, repoPath, options, callbacks, prConte
|
|
|
272
113
|
'hunk.line_range': lineRange,
|
|
273
114
|
},
|
|
274
115
|
}, async (span) => {
|
|
275
|
-
const {
|
|
116
|
+
const { abortController, retry } = options;
|
|
276
117
|
const systemPrompt = buildHunkSystemPrompt(skill);
|
|
277
118
|
const userPrompt = buildHunkUserPrompt(skill, hunkCtx, prContext);
|
|
278
119
|
// Report prompt size information
|
|
@@ -295,11 +136,13 @@ async function analyzeHunk(skill, hunkCtx, repoPath, options, callbacks, prConte
|
|
|
295
136
|
// Track accumulated usage across retry attempts for accurate cost reporting
|
|
296
137
|
const accumulatedUsage = [];
|
|
297
138
|
for (let attempt = 0; attempt <= retryConfig.maxRetries; attempt++) {
|
|
139
|
+
const circuitReason = options.circuitBreaker?.reason;
|
|
140
|
+
if (circuitReason) {
|
|
141
|
+
return hunkFailureFromCircuit(circuitReason, accumulatedUsage, attempt);
|
|
142
|
+
}
|
|
298
143
|
// Check for abort before each attempt
|
|
299
144
|
if (abortController?.signal.aborted) {
|
|
300
|
-
|
|
301
|
-
callbacks.onHunkFailed(callbacks.lineRange, 'Analysis aborted');
|
|
302
|
-
}
|
|
145
|
+
callbacks?.onHunkFailed?.(callbacks.lineRange, 'Analysis aborted');
|
|
303
146
|
return {
|
|
304
147
|
findings: [],
|
|
305
148
|
usage: aggregateUsage(accumulatedUsage),
|
|
@@ -311,19 +154,30 @@ async function analyzeHunk(skill, hunkCtx, repoPath, options, callbacks, prConte
|
|
|
311
154
|
};
|
|
312
155
|
}
|
|
313
156
|
try {
|
|
314
|
-
const
|
|
157
|
+
const runtimeName = options.runtime ?? 'claude';
|
|
158
|
+
const runtime = getRuntime(runtimeName);
|
|
159
|
+
const { result: resultMessage, authError } = await runtime.runSkill({
|
|
160
|
+
systemPrompt,
|
|
161
|
+
userPrompt,
|
|
162
|
+
repoPath,
|
|
163
|
+
skillName: skill.name,
|
|
164
|
+
tools: skill.tools,
|
|
165
|
+
options: {
|
|
166
|
+
maxTurns: options.maxTurns,
|
|
167
|
+
model: options.model,
|
|
168
|
+
abortController: options.abortController,
|
|
169
|
+
},
|
|
170
|
+
providerOptions: getRuntimeProviderOptions(runtimeName, {
|
|
171
|
+
pathToClaudeCodeExecutable: options.pathToClaudeCodeExecutable,
|
|
172
|
+
}),
|
|
173
|
+
});
|
|
315
174
|
// Check for authentication errors from auth_status messages
|
|
316
175
|
// auth_status errors are always auth-related - throw immediately
|
|
317
176
|
if (authError) {
|
|
318
177
|
throw new WardenAuthenticationError(authError);
|
|
319
178
|
}
|
|
320
179
|
if (!resultMessage) {
|
|
321
|
-
|
|
322
|
-
callbacks.onHunkFailed(callbacks.lineRange, 'SDK returned no result');
|
|
323
|
-
}
|
|
324
|
-
else {
|
|
325
|
-
console.error('SDK returned no result');
|
|
326
|
-
}
|
|
180
|
+
notifyHunkFailed(callbacks, callbacks?.lineRange ?? lineRange, 'SDK returned no result');
|
|
327
181
|
return {
|
|
328
182
|
findings: [],
|
|
329
183
|
usage: aggregateUsage(accumulatedUsage),
|
|
@@ -335,13 +189,13 @@ async function analyzeHunk(skill, hunkCtx, repoPath, options, callbacks, prConte
|
|
|
335
189
|
};
|
|
336
190
|
}
|
|
337
191
|
// Extract usage from the result, regardless of success/error status
|
|
338
|
-
const usage =
|
|
192
|
+
const usage = resultMessage.usage;
|
|
339
193
|
accumulatedUsage.push(usage);
|
|
340
194
|
// Check if the SDK returned an error result (e.g., max turns, budget exceeded)
|
|
341
|
-
const isError = resultMessage.
|
|
195
|
+
const isError = resultMessage.status !== 'success';
|
|
342
196
|
if (isError) {
|
|
343
197
|
// Extract error messages from SDK result
|
|
344
|
-
const errorMessages =
|
|
198
|
+
const errorMessages = resultMessage.errors;
|
|
345
199
|
// Check if any error indicates authentication failure
|
|
346
200
|
for (const err of errorMessages) {
|
|
347
201
|
if (isAuthenticationErrorMessage(err)) {
|
|
@@ -350,25 +204,31 @@ async function analyzeHunk(skill, hunkCtx, repoPath, options, callbacks, prConte
|
|
|
350
204
|
}
|
|
351
205
|
// SDK error - log and return failure with error details
|
|
352
206
|
const errorSummary = errorMessages.length > 0
|
|
353
|
-
? errorMessages.join('; ')
|
|
354
|
-
: `
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
207
|
+
? sanitizeErrorMessage(errorMessages.join('; '))
|
|
208
|
+
: `Runtime error: ${resultMessage.status}`;
|
|
209
|
+
const failureCode = resultMessage.status === 'turn_limit'
|
|
210
|
+
? 'max_turns'
|
|
211
|
+
: resultMessage.status === 'provider_error'
|
|
212
|
+
? 'provider_unavailable'
|
|
213
|
+
: 'sdk_error';
|
|
214
|
+
const failureMessage = `Runtime execution failed: ${errorSummary}`;
|
|
215
|
+
const openReason = recordCircuitFailure(options, failureCode, failureMessage);
|
|
216
|
+
notifyHunkFailed(callbacks, callbacks?.lineRange ?? lineRange, failureMessage);
|
|
217
|
+
if (openReason) {
|
|
218
|
+
return hunkFailureFromCircuit(openReason, accumulatedUsage, attempt + 1);
|
|
360
219
|
}
|
|
361
220
|
return {
|
|
362
221
|
findings: [],
|
|
363
222
|
usage: aggregateUsage(accumulatedUsage),
|
|
364
223
|
failed: true,
|
|
365
224
|
extractionFailed: false,
|
|
366
|
-
failureCode
|
|
367
|
-
failureMessage
|
|
225
|
+
failureCode,
|
|
226
|
+
failureMessage,
|
|
368
227
|
attempts: attempt + 1,
|
|
369
228
|
};
|
|
370
229
|
}
|
|
371
|
-
|
|
230
|
+
options.circuitBreaker?.recordSuccess();
|
|
231
|
+
const parseResult = await parseHunkOutput(resultMessage, hunkCtx.filename, options);
|
|
372
232
|
// Filter findings outside hunk line range (defense-in-depth)
|
|
373
233
|
const hunkRange = getHunkLineRange(hunkCtx.hunk);
|
|
374
234
|
const { filtered: filteredFindings, dropped } = filterOutOfRangeFindings(parseResult.findings, hunkRange);
|
|
@@ -409,28 +269,46 @@ async function analyzeHunk(skill, hunkCtx, repoPath, options, callbacks, prConte
|
|
|
409
269
|
}
|
|
410
270
|
catch (error) {
|
|
411
271
|
lastError = error;
|
|
272
|
+
if (isAbortRequested(error, abortController)) {
|
|
273
|
+
callbacks?.onHunkFailed?.(callbacks.lineRange, 'Analysis aborted');
|
|
274
|
+
return {
|
|
275
|
+
findings: [],
|
|
276
|
+
usage: aggregateUsage(accumulatedUsage),
|
|
277
|
+
failed: true,
|
|
278
|
+
extractionFailed: false,
|
|
279
|
+
failureCode: 'aborted',
|
|
280
|
+
failureMessage: 'Analysis aborted',
|
|
281
|
+
attempts: attempt + 1,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
412
284
|
// Re-throw authentication errors (they shouldn't be retried)
|
|
413
285
|
if (error instanceof WardenAuthenticationError) {
|
|
286
|
+
const message = sanitizeErrorMessage(error.message);
|
|
287
|
+
options.circuitBreaker?.recordFailure('auth_failed', message);
|
|
414
288
|
throw error;
|
|
415
289
|
}
|
|
416
290
|
// Subprocess IPC failures (EPIPE, ECONNRESET, etc.) indicate the Claude CLI
|
|
417
291
|
// can't communicate — surface as an auth error with actionable guidance
|
|
418
292
|
if (isSubprocessError(error)) {
|
|
419
293
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
294
|
+
options.circuitBreaker?.recordFailure('auth_failed', sanitizeErrorMessage(errorMessage));
|
|
420
295
|
throw new WardenAuthenticationError(`Claude Code subprocess failed (${errorMessage}).\n` +
|
|
421
296
|
`This usually means the claude CLI cannot run in this environment.`, { cause: error });
|
|
422
297
|
}
|
|
423
298
|
// Authentication errors should surface immediately with helpful guidance
|
|
424
299
|
if (isAuthenticationError(error)) {
|
|
300
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
301
|
+
options.circuitBreaker?.recordFailure('auth_failed', sanitizeErrorMessage(errorMessage));
|
|
425
302
|
throw new WardenAuthenticationError(undefined, { cause: error });
|
|
426
303
|
}
|
|
427
304
|
// Don't retry if not a retryable error or we've exhausted retries
|
|
428
|
-
|
|
305
|
+
const shouldRetry = isRetryableError(error) && attempt < retryConfig.maxRetries;
|
|
306
|
+
if (!shouldRetry) {
|
|
429
307
|
break;
|
|
430
308
|
}
|
|
431
309
|
// Calculate delay and wait before retry
|
|
432
310
|
const delayMs = calculateRetryDelay(attempt, retryConfig);
|
|
433
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
311
|
+
const errorMessage = sanitizeErrorMessage(error instanceof Error ? error.message : String(error));
|
|
434
312
|
Sentry.addBreadcrumb({
|
|
435
313
|
category: 'retry',
|
|
436
314
|
message: `Retrying hunk analysis`,
|
|
@@ -445,9 +323,7 @@ async function analyzeHunk(skill, hunkCtx, repoPath, options, callbacks, prConte
|
|
|
445
323
|
}
|
|
446
324
|
catch {
|
|
447
325
|
// Aborted during sleep
|
|
448
|
-
|
|
449
|
-
callbacks.onHunkFailed(callbacks.lineRange, 'Analysis aborted during retry delay');
|
|
450
|
-
}
|
|
326
|
+
callbacks?.onHunkFailed?.(callbacks.lineRange, 'Analysis aborted during retry delay');
|
|
451
327
|
return {
|
|
452
328
|
findings: [],
|
|
453
329
|
usage: aggregateUsage(accumulatedUsage),
|
|
@@ -461,15 +337,10 @@ async function analyzeHunk(skill, hunkCtx, repoPath, options, callbacks, prConte
|
|
|
461
337
|
}
|
|
462
338
|
}
|
|
463
339
|
// All attempts failed - return failure with any accumulated usage
|
|
464
|
-
const finalError = lastError instanceof Error ? lastError.message : String(lastError);
|
|
340
|
+
const finalError = sanitizeErrorMessage(lastError instanceof Error ? lastError.message : String(lastError));
|
|
465
341
|
// Log the final error
|
|
466
342
|
if (lastError) {
|
|
467
|
-
|
|
468
|
-
callbacks.onHunkFailed(callbacks.lineRange, `All retry attempts failed: ${finalError}`);
|
|
469
|
-
}
|
|
470
|
-
else {
|
|
471
|
-
console.error(`All retry attempts failed: ${finalError}`);
|
|
472
|
-
}
|
|
343
|
+
notifyHunkFailed(callbacks, callbacks?.lineRange ?? lineRange, `All retry attempts failed: ${finalError}`);
|
|
473
344
|
}
|
|
474
345
|
// Also notify via callback if verbose
|
|
475
346
|
if (options.verbose) {
|
|
@@ -477,7 +348,12 @@ async function analyzeHunk(skill, hunkCtx, repoPath, options, callbacks, prConte
|
|
|
477
348
|
}
|
|
478
349
|
span.setAttribute('hunk.failed', true);
|
|
479
350
|
span.setAttribute('finding.count', 0);
|
|
480
|
-
const { code: retryCode, message
|
|
351
|
+
const { code: retryCode, message } = classifyError(lastError);
|
|
352
|
+
const retryMsg = sanitizeErrorMessage(message);
|
|
353
|
+
const openReason = recordCircuitFailure(options, retryCode, retryMsg);
|
|
354
|
+
if (openReason) {
|
|
355
|
+
return hunkFailureFromCircuit(openReason, accumulatedUsage, retryConfig.maxRetries + 1);
|
|
356
|
+
}
|
|
481
357
|
return {
|
|
482
358
|
findings: [],
|
|
483
359
|
usage: aggregateUsage(accumulatedUsage),
|
|
@@ -551,7 +427,7 @@ export async function analyzeFile(skill, file, repoPath, options = {}, callbacks
|
|
|
551
427
|
// Use else-if so a future change that violates this invariant doesn't
|
|
552
428
|
// silently double-count (one hunk → two hunkFailures entries +
|
|
553
429
|
// failedHunks AND failedExtractions both incremented).
|
|
554
|
-
if (result.failed) {
|
|
430
|
+
if (result.failed && result.failureCode !== 'aborted') {
|
|
555
431
|
failedHunks++;
|
|
556
432
|
hunkFailures.push({
|
|
557
433
|
type: 'analysis',
|
|
@@ -584,7 +460,7 @@ export async function analyzeFile(skill, file, repoPath, options = {}, callbacks
|
|
|
584
460
|
findings: result.findings,
|
|
585
461
|
usage: result.usage,
|
|
586
462
|
durationMs: hunkDurationMs,
|
|
587
|
-
failed: result.failed,
|
|
463
|
+
failed: result.failed && result.failureCode !== 'aborted',
|
|
588
464
|
extractionFailed: result.extractionFailed,
|
|
589
465
|
failureCode: result.failureCode,
|
|
590
466
|
failureMessage: result.failureMessage,
|
|
@@ -783,59 +659,53 @@ export async function runSkill(skill, context, options = {}) {
|
|
|
783
659
|
// at most one (analyzeFile makes them mutually exclusive), and an
|
|
784
660
|
// extraction-only failure scenario would otherwise slip through silently.
|
|
785
661
|
const totalAttemptFailures = totalFailedHunks + totalFailedExtractions;
|
|
662
|
+
const circuitReason = options.circuitBreaker?.reason;
|
|
663
|
+
if (circuitReason && totalAttemptFailures > 0 && allFindings.length === 0) {
|
|
664
|
+
throw new SkillRunnerError(circuitReason.message, { code: circuitReason.code });
|
|
665
|
+
}
|
|
786
666
|
if (totalAttemptFailures > 0 && totalAttemptFailures === totalHunks && allFindings.length === 0) {
|
|
667
|
+
const analysisFailures = allHunkFailures.filter((failure) => failure.type === 'analysis');
|
|
668
|
+
if (analysisFailures.length > 0
|
|
669
|
+
&& analysisFailures.every((failure) => failure.code === 'provider_unavailable')) {
|
|
670
|
+
throw new SkillRunnerError(`Provider unavailable: all ${totalHunks} chunk${totalHunks === 1 ? '' : 's'} failed to analyze. Warden stopped early.`, { code: 'provider_unavailable' });
|
|
671
|
+
}
|
|
787
672
|
throw new SkillRunnerError(`All ${totalHunks} chunk${totalHunks === 1 ? '' : 's'} failed to analyze. ` +
|
|
788
673
|
`This usually indicates an authentication problem. ` +
|
|
789
674
|
`Verify WARDEN_ANTHROPIC_API_KEY is set correctly, or run 'claude login' if using Claude Code subscription.`, { code: 'all_hunks_failed' });
|
|
790
675
|
}
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
emitDedupMetrics(skill.name, allFindings.length, uniqueFindings.length);
|
|
794
|
-
// Merge findings that describe the same issue at different locations
|
|
795
|
-
const mergeResult = await mergeCrossLocationFindings(uniqueFindings, {
|
|
796
|
-
apiKey: options.apiKey,
|
|
797
|
-
repoPath: context.repoPath,
|
|
798
|
-
maxRetries: options.auxiliaryMaxRetries,
|
|
799
|
-
});
|
|
800
|
-
let mergedFindings = mergeResult.findings;
|
|
801
|
-
if (mergeResult.usage) {
|
|
802
|
-
allAuxiliaryUsage.push({ agent: 'merge', usage: mergeResult.usage });
|
|
803
|
-
}
|
|
804
|
-
const sanitized = await sanitizeFindingsSuggestedFixes(mergedFindings, {
|
|
676
|
+
const processed = await postProcessFindings(allFindings, {
|
|
677
|
+
skill,
|
|
805
678
|
repoPath: context.repoPath,
|
|
806
679
|
apiKey: options.apiKey,
|
|
807
|
-
|
|
680
|
+
runtime: options.runtime,
|
|
681
|
+
auxiliaryModel: options.auxiliaryModel,
|
|
682
|
+
synthesisModel: options.synthesisModel,
|
|
683
|
+
auxiliaryMaxRetries: options.auxiliaryMaxRetries,
|
|
684
|
+
verifyFindings: options.verifyFindings,
|
|
685
|
+
maxTurns: options.maxTurns,
|
|
686
|
+
abortController: options.abortController,
|
|
687
|
+
pathToClaudeCodeExecutable: options.pathToClaudeCodeExecutable,
|
|
688
|
+
prContext,
|
|
689
|
+
onFindingProcessing: options.callbacks?.onFindingProcessing,
|
|
808
690
|
});
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
allAuxiliaryUsage.push({ agent: 'fix_gate', usage: sanitized.usage });
|
|
812
|
-
}
|
|
813
|
-
emitFixGateMetrics(skill.name, sanitized.stats.checked, sanitized.stats.strippedDeterministic, sanitized.stats.strippedSemantic, sanitized.stats.semanticUnavailable);
|
|
814
|
-
if (sanitized.stats.checked > 0) {
|
|
815
|
-
logger.info('Suggested fix quality gate', {
|
|
816
|
-
'fix_gate.checked': sanitized.stats.checked,
|
|
817
|
-
'fix_gate.stripped_deterministic': sanitized.stats.strippedDeterministic,
|
|
818
|
-
'fix_gate.stripped_semantic': sanitized.stats.strippedSemantic,
|
|
819
|
-
'fix_gate.semantic_unavailable': sanitized.stats.semanticUnavailable,
|
|
820
|
-
});
|
|
821
|
-
}
|
|
691
|
+
const finalFindings = processed.findings;
|
|
692
|
+
allAuxiliaryUsage.push(...processed.auxiliaryUsage);
|
|
822
693
|
// Generate summary
|
|
823
|
-
const summary = generateSummary(skill.name,
|
|
694
|
+
const summary = generateSummary(skill.name, finalFindings);
|
|
824
695
|
// Aggregate usage across all hunks
|
|
825
696
|
const totalUsage = aggregateUsage(allUsage);
|
|
826
697
|
const report = {
|
|
827
698
|
skill: skill.name,
|
|
828
699
|
summary,
|
|
829
|
-
findings:
|
|
700
|
+
findings: finalFindings,
|
|
830
701
|
usage: totalUsage,
|
|
831
702
|
durationMs: Date.now() - startTime,
|
|
832
703
|
model: options.model,
|
|
833
|
-
files: fileResults.map((fr) => ({
|
|
704
|
+
files: buildFileReports(fileResults.map((fr) => ({
|
|
834
705
|
filename: fr.filename,
|
|
835
|
-
findings: fr.result.findings.length,
|
|
836
706
|
durationMs: fr.durationMs,
|
|
837
707
|
usage: fr.result.usage,
|
|
838
|
-
})),
|
|
708
|
+
})), finalFindings),
|
|
839
709
|
};
|
|
840
710
|
if (skippedFiles.length > 0) {
|
|
841
711
|
report.skippedFiles = skippedFiles;
|