@sentry/warden 0.21.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 +390 -0
- package/README.md +4 -4
- 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 +79 -15
- 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/log-cleanup.d.ts.map +1 -1
- package/dist/cli/log-cleanup.js +4 -0
- package/dist/cli/log-cleanup.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 +482 -100
- 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 +2 -2
- package/dist/cli/output/index.d.ts.map +1 -1
- package/dist/cli/output/index.js +2 -2
- 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 +79 -25
- package/dist/cli/output/ink-runner.js.map +1 -1
- package/dist/cli/output/jsonl-schema-gen.d.ts.map +1 -1
- package/dist/cli/output/jsonl-schema-gen.js +4 -2
- package/dist/cli/output/jsonl-schema-gen.js.map +1 -1
- package/dist/cli/output/jsonl.d.ts +147 -4
- package/dist/cli/output/jsonl.d.ts.map +1 -1
- package/dist/cli/output/jsonl.js +183 -7
- 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 +11 -5
- package/dist/cli/output/tasks.d.ts.map +1 -1
- package/dist/cli/output/tasks.js +126 -68
- 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 +166 -276
- 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 +42 -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 +41 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +24 -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/SKILL.md +5 -5
- package/skills/warden/SOURCES.md +39 -0
- package/skills/warden/SPEC.md +112 -0
- package/skills/warden/references/cli-reference.md +11 -4
- package/skills/warden/references/config-schema.md +38 -13
- package/skills/warden/references/configuration.md +29 -8
- package/skills/warden-sweep/SKILL.md +43 -365
- package/skills/warden-sweep/SOURCES.md +43 -0
- package/skills/warden-sweep/SPEC.md +126 -0
- package/skills/warden-sweep/references/issue-phase.md +24 -0
- package/skills/warden-sweep/references/organize-phase.md +31 -0
- package/skills/warden-sweep/references/patch-phase.md +142 -0
- package/skills/warden-sweep/references/resume-and-artifacts.md +51 -0
- package/skills/warden-sweep/references/scan-phase.md +46 -0
- package/skills/warden-sweep/references/script-interfaces.md +127 -0
- package/skills/warden-sweep/references/verify-phase.md +69 -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),
|
|
@@ -543,13 +419,15 @@ export async function analyzeFile(skill, file, repoPath, options = {}, callbacks
|
|
|
543
419
|
onHunkFailed: callbacks.onHunkFailed,
|
|
544
420
|
}
|
|
545
421
|
: undefined;
|
|
422
|
+
const hunkStartTime = Date.now();
|
|
546
423
|
const result = await analyzeHunk(skill, hunk, repoPath, options, hunkCallbacks, prContext);
|
|
424
|
+
const hunkDurationMs = Date.now() - hunkStartTime;
|
|
547
425
|
// `failed` and `extractionFailed` are conceptually mutually exclusive:
|
|
548
426
|
// if analysis failed (no output produced), there's nothing to extract.
|
|
549
427
|
// Use else-if so a future change that violates this invariant doesn't
|
|
550
428
|
// silently double-count (one hunk → two hunkFailures entries +
|
|
551
429
|
// failedHunks AND failedExtractions both incremented).
|
|
552
|
-
if (result.failed) {
|
|
430
|
+
if (result.failed && result.failureCode !== 'aborted') {
|
|
553
431
|
failedHunks++;
|
|
554
432
|
hunkFailures.push({
|
|
555
433
|
type: 'analysis',
|
|
@@ -573,6 +451,24 @@ export async function analyzeFile(skill, file, repoPath, options = {}, callbacks
|
|
|
573
451
|
}
|
|
574
452
|
attachElapsedTime(result.findings, callbacks?.skillStartTime);
|
|
575
453
|
callbacks?.onHunkComplete?.(hunkIndex + 1, result.findings, result.usage);
|
|
454
|
+
const chunkResult = {
|
|
455
|
+
filename: file.filename,
|
|
456
|
+
model: options.model,
|
|
457
|
+
index: hunkIndex + 1,
|
|
458
|
+
total: file.hunks.length,
|
|
459
|
+
lineRange,
|
|
460
|
+
findings: result.findings,
|
|
461
|
+
usage: result.usage,
|
|
462
|
+
durationMs: hunkDurationMs,
|
|
463
|
+
failed: result.failed && result.failureCode !== 'aborted',
|
|
464
|
+
extractionFailed: result.extractionFailed,
|
|
465
|
+
failureCode: result.failureCode,
|
|
466
|
+
failureMessage: result.failureMessage,
|
|
467
|
+
extractionError: result.extractionError,
|
|
468
|
+
extractionPreview: result.extractionPreview,
|
|
469
|
+
auxiliaryUsage: result.auxiliaryUsage,
|
|
470
|
+
};
|
|
471
|
+
callbacks?.onChunkComplete?.(chunkResult);
|
|
576
472
|
fileFindings.push(...result.findings);
|
|
577
473
|
fileUsage.push(result.usage);
|
|
578
474
|
if (result.auxiliaryUsage) {
|
|
@@ -763,59 +659,53 @@ export async function runSkill(skill, context, options = {}) {
|
|
|
763
659
|
// at most one (analyzeFile makes them mutually exclusive), and an
|
|
764
660
|
// extraction-only failure scenario would otherwise slip through silently.
|
|
765
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
|
+
}
|
|
766
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
|
+
}
|
|
767
672
|
throw new SkillRunnerError(`All ${totalHunks} chunk${totalHunks === 1 ? '' : 's'} failed to analyze. ` +
|
|
768
673
|
`This usually indicates an authentication problem. ` +
|
|
769
674
|
`Verify WARDEN_ANTHROPIC_API_KEY is set correctly, or run 'claude login' if using Claude Code subscription.`, { code: 'all_hunks_failed' });
|
|
770
675
|
}
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
emitDedupMetrics(skill.name, allFindings.length, uniqueFindings.length);
|
|
774
|
-
// Merge findings that describe the same issue at different locations
|
|
775
|
-
const mergeResult = await mergeCrossLocationFindings(uniqueFindings, {
|
|
776
|
-
apiKey: options.apiKey,
|
|
777
|
-
repoPath: context.repoPath,
|
|
778
|
-
maxRetries: options.auxiliaryMaxRetries,
|
|
779
|
-
});
|
|
780
|
-
let mergedFindings = mergeResult.findings;
|
|
781
|
-
if (mergeResult.usage) {
|
|
782
|
-
allAuxiliaryUsage.push({ agent: 'merge', usage: mergeResult.usage });
|
|
783
|
-
}
|
|
784
|
-
const sanitized = await sanitizeFindingsSuggestedFixes(mergedFindings, {
|
|
676
|
+
const processed = await postProcessFindings(allFindings, {
|
|
677
|
+
skill,
|
|
785
678
|
repoPath: context.repoPath,
|
|
786
679
|
apiKey: options.apiKey,
|
|
787
|
-
|
|
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,
|
|
788
690
|
});
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
allAuxiliaryUsage.push({ agent: 'fix_gate', usage: sanitized.usage });
|
|
792
|
-
}
|
|
793
|
-
emitFixGateMetrics(skill.name, sanitized.stats.checked, sanitized.stats.strippedDeterministic, sanitized.stats.strippedSemantic, sanitized.stats.semanticUnavailable);
|
|
794
|
-
if (sanitized.stats.checked > 0) {
|
|
795
|
-
logger.info('Suggested fix quality gate', {
|
|
796
|
-
'fix_gate.checked': sanitized.stats.checked,
|
|
797
|
-
'fix_gate.stripped_deterministic': sanitized.stats.strippedDeterministic,
|
|
798
|
-
'fix_gate.stripped_semantic': sanitized.stats.strippedSemantic,
|
|
799
|
-
'fix_gate.semantic_unavailable': sanitized.stats.semanticUnavailable,
|
|
800
|
-
});
|
|
801
|
-
}
|
|
691
|
+
const finalFindings = processed.findings;
|
|
692
|
+
allAuxiliaryUsage.push(...processed.auxiliaryUsage);
|
|
802
693
|
// Generate summary
|
|
803
|
-
const summary = generateSummary(skill.name,
|
|
694
|
+
const summary = generateSummary(skill.name, finalFindings);
|
|
804
695
|
// Aggregate usage across all hunks
|
|
805
696
|
const totalUsage = aggregateUsage(allUsage);
|
|
806
697
|
const report = {
|
|
807
698
|
skill: skill.name,
|
|
808
699
|
summary,
|
|
809
|
-
findings:
|
|
700
|
+
findings: finalFindings,
|
|
810
701
|
usage: totalUsage,
|
|
811
702
|
durationMs: Date.now() - startTime,
|
|
812
703
|
model: options.model,
|
|
813
|
-
files: fileResults.map((fr) => ({
|
|
704
|
+
files: buildFileReports(fileResults.map((fr) => ({
|
|
814
705
|
filename: fr.filename,
|
|
815
|
-
findings: fr.result.findings.length,
|
|
816
706
|
durationMs: fr.durationMs,
|
|
817
707
|
usage: fr.result.usage,
|
|
818
|
-
})),
|
|
708
|
+
})), finalFindings),
|
|
819
709
|
};
|
|
820
710
|
if (skippedFiles.length > 0) {
|
|
821
711
|
report.skippedFiles = skippedFiles;
|