@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.
Files changed (308) hide show
  1. package/.oxlintrc.json +169 -0
  2. package/CHANGELOG.md +210 -0
  3. package/README.md +1 -1
  4. package/dist/cli/args.d.ts +6 -2
  5. package/dist/cli/args.d.ts.map +1 -1
  6. package/dist/cli/args.js +175 -207
  7. package/dist/cli/args.js.map +1 -1
  8. package/dist/cli/commands/add.d.ts.map +1 -1
  9. package/dist/cli/commands/add.js +16 -5
  10. package/dist/cli/commands/add.js.map +1 -1
  11. package/dist/cli/commands/build.d.ts +13 -0
  12. package/dist/cli/commands/build.d.ts.map +1 -0
  13. package/dist/cli/commands/build.js +384 -0
  14. package/dist/cli/commands/build.js.map +1 -0
  15. package/dist/cli/commands/init.d.ts.map +1 -1
  16. package/dist/cli/commands/init.js +10 -2
  17. package/dist/cli/commands/init.js.map +1 -1
  18. package/dist/cli/commands/runs.d.ts.map +1 -1
  19. package/dist/cli/commands/runs.js +12 -6
  20. package/dist/cli/commands/runs.js.map +1 -1
  21. package/dist/cli/commands/sync.d.ts.map +1 -1
  22. package/dist/cli/commands/sync.js +4 -1
  23. package/dist/cli/commands/sync.js.map +1 -1
  24. package/dist/cli/context.d.ts.map +1 -1
  25. package/dist/cli/context.js +8 -1
  26. package/dist/cli/context.js.map +1 -1
  27. package/dist/cli/files.d.ts.map +1 -1
  28. package/dist/cli/files.js +27 -9
  29. package/dist/cli/files.js.map +1 -1
  30. package/dist/cli/help.d.ts +4 -0
  31. package/dist/cli/help.d.ts.map +1 -0
  32. package/dist/cli/help.js +448 -0
  33. package/dist/cli/help.js.map +1 -0
  34. package/dist/cli/index.js +2 -13
  35. package/dist/cli/index.js.map +1 -1
  36. package/dist/cli/input.d.ts +12 -0
  37. package/dist/cli/input.d.ts.map +1 -1
  38. package/dist/cli/input.js +60 -0
  39. package/dist/cli/input.js.map +1 -1
  40. package/dist/cli/main.d.ts +39 -2
  41. package/dist/cli/main.d.ts.map +1 -1
  42. package/dist/cli/main.js +264 -74
  43. package/dist/cli/main.js.map +1 -1
  44. package/dist/cli/output/box.js +1 -1
  45. package/dist/cli/output/formatters.d.ts +10 -2
  46. package/dist/cli/output/formatters.d.ts.map +1 -1
  47. package/dist/cli/output/formatters.js +32 -9
  48. package/dist/cli/output/formatters.js.map +1 -1
  49. package/dist/cli/output/index.d.ts +1 -1
  50. package/dist/cli/output/index.d.ts.map +1 -1
  51. package/dist/cli/output/index.js +1 -1
  52. package/dist/cli/output/index.js.map +1 -1
  53. package/dist/cli/output/ink-runner.d.ts +3 -1
  54. package/dist/cli/output/ink-runner.d.ts.map +1 -1
  55. package/dist/cli/output/ink-runner.js +64 -28
  56. package/dist/cli/output/ink-runner.js.map +1 -1
  57. package/dist/cli/output/jsonl.d.ts +37 -0
  58. package/dist/cli/output/jsonl.d.ts.map +1 -1
  59. package/dist/cli/output/jsonl.js +6 -0
  60. package/dist/cli/output/jsonl.js.map +1 -1
  61. package/dist/cli/output/live-status.d.ts +48 -0
  62. package/dist/cli/output/live-status.d.ts.map +1 -0
  63. package/dist/cli/output/live-status.js +173 -0
  64. package/dist/cli/output/live-status.js.map +1 -0
  65. package/dist/cli/output/reporter.d.ts +4 -0
  66. package/dist/cli/output/reporter.d.ts.map +1 -1
  67. package/dist/cli/output/reporter.js +20 -3
  68. package/dist/cli/output/reporter.js.map +1 -1
  69. package/dist/cli/output/tasks.d.ts +8 -5
  70. package/dist/cli/output/tasks.d.ts.map +1 -1
  71. package/dist/cli/output/tasks.js +113 -71
  72. package/dist/cli/output/tasks.js.map +1 -1
  73. package/dist/cli/signals.d.ts +15 -0
  74. package/dist/cli/signals.d.ts.map +1 -0
  75. package/dist/cli/signals.js +26 -0
  76. package/dist/cli/signals.js.map +1 -0
  77. package/dist/config/loader.d.ts +48 -7
  78. package/dist/config/loader.d.ts.map +1 -1
  79. package/dist/config/loader.js +228 -32
  80. package/dist/config/loader.js.map +1 -1
  81. package/dist/config/schema.d.ts +55 -0
  82. package/dist/config/schema.d.ts.map +1 -1
  83. package/dist/config/schema.js +34 -2
  84. package/dist/config/schema.js.map +1 -1
  85. package/dist/diff/context.d.ts +9 -2
  86. package/dist/diff/context.d.ts.map +1 -1
  87. package/dist/diff/context.js +65 -17
  88. package/dist/diff/context.js.map +1 -1
  89. package/dist/evals/runner.d.ts.map +1 -1
  90. package/dist/evals/runner.js +16 -0
  91. package/dist/evals/runner.js.map +1 -1
  92. package/dist/event/context.d.ts.map +1 -1
  93. package/dist/event/context.js +1 -0
  94. package/dist/event/context.js.map +1 -1
  95. package/dist/event/schedule-context.d.ts.map +1 -1
  96. package/dist/event/schedule-context.js +1 -0
  97. package/dist/event/schedule-context.js.map +1 -1
  98. package/dist/index.d.ts +6 -6
  99. package/dist/index.d.ts.map +1 -1
  100. package/dist/index.js +3 -3
  101. package/dist/index.js.map +1 -1
  102. package/dist/output/dedup.d.ts +8 -11
  103. package/dist/output/dedup.d.ts.map +1 -1
  104. package/dist/output/dedup.js +39 -37
  105. package/dist/output/dedup.js.map +1 -1
  106. package/dist/output/github-checks.d.ts +1 -0
  107. package/dist/output/github-checks.d.ts.map +1 -1
  108. package/dist/output/github-checks.js +9 -7
  109. package/dist/output/github-checks.js.map +1 -1
  110. package/dist/sdk/analyze.d.ts.map +1 -1
  111. package/dist/sdk/analyze.js +147 -277
  112. package/dist/sdk/analyze.js.map +1 -1
  113. package/dist/sdk/circuit-breaker.d.ts +26 -0
  114. package/dist/sdk/circuit-breaker.d.ts.map +1 -0
  115. package/dist/sdk/circuit-breaker.js +53 -0
  116. package/dist/sdk/circuit-breaker.js.map +1 -0
  117. package/dist/sdk/errors.d.ts +5 -0
  118. package/dist/sdk/errors.d.ts.map +1 -1
  119. package/dist/sdk/errors.js +27 -0
  120. package/dist/sdk/errors.js.map +1 -1
  121. package/dist/sdk/extract.d.ts +18 -9
  122. package/dist/sdk/extract.d.ts.map +1 -1
  123. package/dist/sdk/extract.js +95 -87
  124. package/dist/sdk/extract.js.map +1 -1
  125. package/dist/sdk/fix-quality.d.ts +5 -0
  126. package/dist/sdk/fix-quality.d.ts.map +1 -1
  127. package/dist/sdk/fix-quality.js +42 -23
  128. package/dist/sdk/fix-quality.js.map +1 -1
  129. package/dist/sdk/haiku.d.ts +6 -0
  130. package/dist/sdk/haiku.d.ts.map +1 -1
  131. package/dist/sdk/haiku.js +75 -49
  132. package/dist/sdk/haiku.js.map +1 -1
  133. package/dist/sdk/json-output.d.ts +31 -0
  134. package/dist/sdk/json-output.d.ts.map +1 -0
  135. package/dist/sdk/json-output.js +98 -0
  136. package/dist/sdk/json-output.js.map +1 -0
  137. package/dist/sdk/model-pricing.json +56 -16
  138. package/dist/sdk/post-process.d.ts +29 -0
  139. package/dist/sdk/post-process.d.ts.map +1 -0
  140. package/dist/sdk/post-process.js +65 -0
  141. package/dist/sdk/post-process.js.map +1 -0
  142. package/dist/sdk/prepare.d.ts.map +1 -1
  143. package/dist/sdk/prepare.js +4 -1
  144. package/dist/sdk/prepare.js.map +1 -1
  145. package/dist/sdk/pricing.d.ts +18 -0
  146. package/dist/sdk/pricing.d.ts.map +1 -1
  147. package/dist/sdk/pricing.js +108 -3
  148. package/dist/sdk/pricing.js.map +1 -1
  149. package/dist/sdk/prompt-sections.d.ts +56 -0
  150. package/dist/sdk/prompt-sections.d.ts.map +1 -0
  151. package/dist/sdk/prompt-sections.js +118 -0
  152. package/dist/sdk/prompt-sections.js.map +1 -0
  153. package/dist/sdk/prompt.d.ts +2 -17
  154. package/dist/sdk/prompt.d.ts.map +1 -1
  155. package/dist/sdk/prompt.js +15 -37
  156. package/dist/sdk/prompt.js.map +1 -1
  157. package/dist/sdk/report-files.d.ts +15 -0
  158. package/dist/sdk/report-files.d.ts.map +1 -0
  159. package/dist/sdk/report-files.js +20 -0
  160. package/dist/sdk/report-files.js.map +1 -0
  161. package/dist/sdk/runner.d.ts +9 -1
  162. package/dist/sdk/runner.d.ts.map +1 -1
  163. package/dist/sdk/runner.js +6 -0
  164. package/dist/sdk/runner.js.map +1 -1
  165. package/dist/sdk/runtimes/claude.d.ts +3 -0
  166. package/dist/sdk/runtimes/claude.d.ts.map +1 -0
  167. package/dist/sdk/runtimes/claude.js +376 -0
  168. package/dist/sdk/runtimes/claude.js.map +1 -0
  169. package/dist/sdk/runtimes/index.d.ts +12 -0
  170. package/dist/sdk/runtimes/index.d.ts.map +1 -0
  171. package/dist/sdk/runtimes/index.js +22 -0
  172. package/dist/sdk/runtimes/index.js.map +1 -0
  173. package/dist/sdk/runtimes/types.d.ts +116 -0
  174. package/dist/sdk/runtimes/types.d.ts.map +1 -0
  175. package/dist/sdk/runtimes/types.js +17 -0
  176. package/dist/sdk/runtimes/types.js.map +1 -0
  177. package/dist/sdk/types.d.ts +23 -2
  178. package/dist/sdk/types.d.ts.map +1 -1
  179. package/dist/sdk/types.js.map +1 -1
  180. package/dist/sdk/usage.d.ts +21 -6
  181. package/dist/sdk/usage.d.ts.map +1 -1
  182. package/dist/sdk/usage.js +36 -23
  183. package/dist/sdk/usage.js.map +1 -1
  184. package/dist/sdk/verify.d.ts +25 -0
  185. package/dist/sdk/verify.d.ts.map +1 -0
  186. package/dist/sdk/verify.js +226 -0
  187. package/dist/sdk/verify.js.map +1 -0
  188. package/dist/skill-builder/agentic.d.ts +33 -0
  189. package/dist/skill-builder/agentic.d.ts.map +1 -0
  190. package/dist/skill-builder/agentic.js +240 -0
  191. package/dist/skill-builder/agentic.js.map +1 -0
  192. package/dist/skill-builder/authoring-provider.d.ts +5 -0
  193. package/dist/skill-builder/authoring-provider.d.ts.map +1 -0
  194. package/dist/skill-builder/authoring-provider.js +70 -0
  195. package/dist/skill-builder/authoring-provider.js.map +1 -0
  196. package/dist/skill-builder/definition.d.ts +46 -0
  197. package/dist/skill-builder/definition.d.ts.map +1 -0
  198. package/dist/skill-builder/definition.js +171 -0
  199. package/dist/skill-builder/definition.js.map +1 -0
  200. package/dist/skill-builder/outline-contract.d.ts +113 -0
  201. package/dist/skill-builder/outline-contract.d.ts.map +1 -0
  202. package/dist/skill-builder/outline-contract.js +73 -0
  203. package/dist/skill-builder/outline-contract.js.map +1 -0
  204. package/dist/skill-builder/outline-state.d.ts +158 -0
  205. package/dist/skill-builder/outline-state.d.ts.map +1 -0
  206. package/dist/skill-builder/outline-state.js +87 -0
  207. package/dist/skill-builder/outline-state.js.map +1 -0
  208. package/dist/skill-builder/outline.d.ts +36 -0
  209. package/dist/skill-builder/outline.d.ts.map +1 -0
  210. package/dist/skill-builder/outline.js +345 -0
  211. package/dist/skill-builder/outline.js.map +1 -0
  212. package/dist/skill-builder/skill-contract.d.ts +97 -0
  213. package/dist/skill-builder/skill-contract.d.ts.map +1 -0
  214. package/dist/skill-builder/skill-contract.js +58 -0
  215. package/dist/skill-builder/skill-contract.js.map +1 -0
  216. package/dist/skill-builder/skill-prompts.d.ts +61 -0
  217. package/dist/skill-builder/skill-prompts.d.ts.map +1 -0
  218. package/dist/skill-builder/skill-prompts.js +285 -0
  219. package/dist/skill-builder/skill-prompts.js.map +1 -0
  220. package/dist/skill-builder/skill.d.ts +23 -0
  221. package/dist/skill-builder/skill.d.ts.map +1 -0
  222. package/dist/skill-builder/skill.js +563 -0
  223. package/dist/skill-builder/skill.js.map +1 -0
  224. package/dist/skills/index.d.ts +2 -2
  225. package/dist/skills/index.d.ts.map +1 -1
  226. package/dist/skills/index.js +1 -1
  227. package/dist/skills/index.js.map +1 -1
  228. package/dist/skills/loader.d.ts +20 -6
  229. package/dist/skills/loader.d.ts.map +1 -1
  230. package/dist/skills/loader.js +88 -141
  231. package/dist/skills/loader.js.map +1 -1
  232. package/dist/skills/remote.d.ts +11 -7
  233. package/dist/skills/remote.d.ts.map +1 -1
  234. package/dist/skills/remote.js +62 -162
  235. package/dist/skills/remote.js.map +1 -1
  236. package/dist/triggers/matcher.d.ts.map +1 -1
  237. package/dist/triggers/matcher.js +28 -15
  238. package/dist/triggers/matcher.js.map +1 -1
  239. package/dist/types/index.d.ts +40 -0
  240. package/dist/types/index.d.ts.map +1 -1
  241. package/dist/types/index.js +11 -0
  242. package/dist/types/index.js.map +1 -1
  243. package/dist/utils/index.d.ts +1 -0
  244. package/dist/utils/index.d.ts.map +1 -1
  245. package/dist/utils/index.js +1 -0
  246. package/dist/utils/index.js.map +1 -1
  247. package/dist/utils/path.d.ts +17 -0
  248. package/dist/utils/path.d.ts.map +1 -0
  249. package/dist/utils/path.js +36 -0
  250. package/dist/utils/path.js.map +1 -0
  251. package/package.json +8 -8
  252. package/policies/README.md +22 -0
  253. package/policies/code-comments.md +20 -0
  254. package/policies/policy-template.md +14 -0
  255. package/skills/warden/SPEC.md +112 -0
  256. package/skills/warden/references/config-schema.md +29 -13
  257. package/skills/warden/references/configuration.md +20 -8
  258. package/skills/warden-sweep/SPEC.md +126 -0
  259. package/src/builtin-skills/security-review/SKILL.md +81 -0
  260. package/src/builtin-skills/security-review/SPEC.md +77 -0
  261. package/src/builtin-skills/security-review/references/github-workflows.md +186 -0
  262. package/src/builtin-skills/security-review/references/javascript-typescript.md +69 -0
  263. package/src/builtin-skills/security-review/references/python.md +70 -0
  264. package/src/internal-skills/skill-writer/EVAL.md +124 -0
  265. package/src/internal-skills/skill-writer/SKILL.md +159 -0
  266. package/src/internal-skills/skill-writer/SOURCES.md +94 -0
  267. package/src/internal-skills/skill-writer/SPEC.md +153 -0
  268. package/src/internal-skills/skill-writer/references/artifact-layouts/argument-driven-skill-layout.md +32 -0
  269. package/src/internal-skills/skill-writer/references/artifact-layouts/asset-template-skill-layout.md +30 -0
  270. package/src/internal-skills/skill-writer/references/artifact-layouts/inline-skill-layout.md +28 -0
  271. package/src/internal-skills/skill-writer/references/artifact-layouts/reference-backed-skill-layout.md +35 -0
  272. package/src/internal-skills/skill-writer/references/artifact-layouts/script-backed-skill-layout.md +31 -0
  273. package/src/internal-skills/skill-writer/references/authoring-path.md +91 -0
  274. package/src/internal-skills/skill-writer/references/claude-code/argument-substitutions.md +20 -0
  275. package/src/internal-skills/skill-writer/references/claude-code/dynamic-context.md +16 -0
  276. package/src/internal-skills/skill-writer/references/claude-code/frontmatter-and-invocation.md +33 -0
  277. package/src/internal-skills/skill-writer/references/claude-code/hook-backed-skills.md +28 -0
  278. package/src/internal-skills/skill-writer/references/claude-code/subagent-fork-skills.md +22 -0
  279. package/src/internal-skills/skill-writer/references/description-optimization.md +28 -0
  280. package/src/internal-skills/skill-writer/references/design-principles.md +74 -0
  281. package/src/internal-skills/skill-writer/references/evaluation-path.md +73 -0
  282. package/src/internal-skills/skill-writer/references/examples/documentation-skill.md +70 -0
  283. package/src/internal-skills/skill-writer/references/examples/evaluator-loop-skill.md +38 -0
  284. package/src/internal-skills/skill-writer/references/examples/hook-backed-skill.md +39 -0
  285. package/src/internal-skills/skill-writer/references/examples/router-skill.md +41 -0
  286. package/src/internal-skills/skill-writer/references/examples/security-review-skill.md +69 -0
  287. package/src/internal-skills/skill-writer/references/examples/subagent-fork-skill.md +39 -0
  288. package/src/internal-skills/skill-writer/references/examples/workflow-process-skill.md +65 -0
  289. package/src/internal-skills/skill-writer/references/execution-shapes.md +68 -0
  290. package/src/internal-skills/skill-writer/references/iteration-evidence.md +82 -0
  291. package/src/internal-skills/skill-writer/references/iteration-path.md +40 -0
  292. package/src/internal-skills/skill-writer/references/mode-selection.md +67 -0
  293. package/src/internal-skills/skill-writer/references/output-contracts.md +68 -0
  294. package/src/internal-skills/skill-writer/references/reference-architecture.md +71 -0
  295. package/src/internal-skills/skill-writer/references/registration-validation.md +67 -0
  296. package/src/internal-skills/skill-writer/references/source-discovery.md +60 -0
  297. package/src/internal-skills/skill-writer/references/spec-template.md +118 -0
  298. package/src/internal-skills/skill-writer/references/structure-troubleshooting.md +111 -0
  299. package/src/internal-skills/skill-writer/references/synthesis-path.md +119 -0
  300. package/src/internal-skills/skill-writer/references/workflow-mechanics/evaluator-loops.md +25 -0
  301. package/src/internal-skills/skill-writer/references/workflow-mechanics/orchestrator-workers.md +25 -0
  302. package/src/internal-skills/skill-writer/references/workflow-mechanics/parallel-workflows.md +21 -0
  303. package/src/internal-skills/skill-writer/references/workflow-mechanics/plan-validate-execute.md +26 -0
  304. package/src/internal-skills/skill-writer/references/workflow-mechanics/prompt-chaining.md +24 -0
  305. package/src/internal-skills/skill-writer/references/workflow-mechanics/routing-workflows.md +28 -0
  306. package/src/internal-skills/skill-writer/references/workflow-mechanics/validation-loops.md +26 -0
  307. package/src/internal-skills/skill-writer/scripts/quick_validate.py +158 -0
  308. package/src/internal-skills/skill-writer/scripts/quick_validate_test.py +79 -0
@@ -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, emitDedupMetrics, emitFixGateMetrics, logger } from '../sentry.js';
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 { extractUsage, aggregateUsage, emptyUsage, estimateTokens, aggregateAuxiliaryUsage } from './usage.js';
5
+ import { aggregateUsage, emptyUsage, estimateTokens, aggregateAuxiliaryUsage } from './usage.js';
7
6
  import { buildHunkSystemPrompt, buildHunkUserPrompt } from './prompt.js';
8
- import { extractFindingsJson, extractFindingsWithLLM, validateFindings, deduplicateFindings, mergeCrossLocationFindings } from './extract.js';
9
- import { sanitizeFindingsSuggestedFixes } from './fix-quality.js';
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, apiKey, auxiliaryMaxRetries) {
20
- if (result.subtype !== 'success') {
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.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.result, apiKey, auxiliaryMaxRetries);
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 { apiKey, abortController, retry } = options;
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
- if (callbacks?.onHunkFailed) {
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 { result: resultMessage, authError } = await executeQuery(systemPrompt, userPrompt, repoPath, options, skill.name);
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
- if (callbacks?.onHunkFailed) {
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 = extractUsage(resultMessage);
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.is_error || resultMessage.subtype !== 'success';
195
+ const isError = resultMessage.status !== 'success';
342
196
  if (isError) {
343
197
  // Extract error messages from SDK result
344
- const errorMessages = 'errors' in resultMessage ? resultMessage.errors : [];
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
- : `SDK error: ${resultMessage.subtype}`;
355
- if (callbacks?.onHunkFailed) {
356
- callbacks.onHunkFailed(callbacks.lineRange, `SDK execution failed: ${errorSummary}`);
357
- }
358
- else {
359
- console.error(`SDK execution failed: ${errorSummary}`);
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: resultMessage.subtype === 'error_max_turns' ? 'max_turns' : 'sdk_error',
367
- failureMessage: `SDK execution failed: ${errorSummary}`,
225
+ failureCode,
226
+ failureMessage,
368
227
  attempts: attempt + 1,
369
228
  };
370
229
  }
371
- const parseResult = await parseHunkOutput(resultMessage, hunkCtx.filename, apiKey, options.auxiliaryMaxRetries);
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
- if (!isRetryableError(error) || attempt >= retryConfig.maxRetries) {
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
- if (callbacks?.onHunkFailed) {
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
- if (callbacks?.onHunkFailed) {
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: retryMsg } = classifyError(lastError);
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
- // Deduplicate findings
792
- const uniqueFindings = deduplicateFindings(allFindings);
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
- maxRetries: options.auxiliaryMaxRetries,
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
- mergedFindings = sanitized.findings;
810
- if (sanitized.usage) {
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, mergedFindings);
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: mergedFindings,
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;