From d4876bb9b8f854e5fb531cdc04bb059f2ad6ef93 Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Mon, 15 Jun 2026 15:25:51 +0000 Subject: [PATCH 1/2] Optimize deep review no-issue paths --- .../builtinWorkflows/deep-review-workflow.js | 139 +++++++- .../builtInWorkflowContent.generated.ts | 2 +- .../builtInWorkflowDefinitions.test.ts | 322 +++++++++++++++++- 3 files changed, 442 insertions(+), 21 deletions(-) diff --git a/src/node/builtinWorkflows/deep-review-workflow.js b/src/node/builtinWorkflows/deep-review-workflow.js index af625c59af..ee7b05b245 100644 --- a/src/node/builtinWorkflows/deep-review-workflow.js +++ b/src/node/builtinWorkflows/deep-review-workflow.js @@ -169,6 +169,33 @@ function runDeepReviewPass(context) { withLoopIteration({ count: laneIssues.length }, context.iteration) ); + if (laneIssues.length === 0) { + context.log( + "No candidate issues from review lanes; skipping triage, verification, and final synthesis", + withLoopIteration( + { skippedPhases: ["triage-dedupe", "adversarial-verification", "final-synthesis"] }, + context.iteration + ) + ); + const reviewResult = buildNoVerifiedIssuesReviewResult({ + input: input, + scope: scope.structuredOutput, + laneIssues: laneIssues, + triagedIssues: [], + verifications: [], + discardedIssueCount: 0, + }); + return finishDeepReviewPass({ + context: context, + input: input, + reviewResult: reviewResult, + gitContext: gitContext, + candidates: [], + verifications: [], + final: reviewResult.structuredOutput.final, + }); + } + context.phase( "triage-dedupe", withLoopIteration({ candidateCount: laneIssues.length }, context.iteration) @@ -206,6 +233,33 @@ function runDeepReviewPass(context) { ) ); + if (candidates.length === 0) { + context.log( + "No candidate issues after triage; skipping verification and final synthesis", + withLoopIteration( + { skippedPhases: ["adversarial-verification", "final-synthesis"] }, + context.iteration + ) + ); + const reviewResult = buildNoVerifiedIssuesReviewResult({ + input: input, + scope: scope.structuredOutput, + laneIssues: laneIssues, + triagedIssues: [], + verifications: [], + discardedIssueCount: laneIssues.length, + }); + return finishDeepReviewPass({ + context: context, + input: input, + reviewResult: reviewResult, + gitContext: gitContext, + candidates: [], + verifications: [], + final: reviewResult.structuredOutput.final, + }); + } + context.phase( "adversarial-verification", withLoopIteration({ candidateCount: candidates.length }, context.iteration) @@ -239,6 +293,30 @@ function runDeepReviewPass(context) { withLoopIteration({ count: verifications.length }, context.iteration) ); + if (!hasValidVerification(verifications)) { + context.log( + "No verifier upheld a candidate issue; skipping final synthesis", + withLoopIteration({ skippedPhases: ["final-synthesis"] }, context.iteration) + ); + const reviewResult = buildNoVerifiedIssuesReviewResult({ + input: input, + scope: scope.structuredOutput, + laneIssues: laneIssues, + triagedIssues: candidates, + verifications: verifications, + discardedIssueCount: candidates.length, + }); + return finishDeepReviewPass({ + context: context, + input: input, + reviewResult: reviewResult, + gitContext: gitContext, + candidates: candidates, + verifications: verifications, + final: reviewResult.structuredOutput.final, + }); + } + context.phase( "final-synthesis", withLoopIteration( @@ -276,30 +354,71 @@ function runDeepReviewPass(context) { final: final.structuredOutput, }, }; - if (!input.fix) return { reviewResult: reviewResult, gitContext: gitContext }; - if (context.skipFixWhenNoVerifiedIssues && !hasVerifiedIssues(final.structuredOutput)) { - return { reviewResult: reviewResult, gitContext: gitContext }; + return finishDeepReviewPass({ + context: context, + input: input, + reviewResult: reviewResult, + gitContext: gitContext, + candidates: candidates, + verifications: verifications, + final: final.structuredOutput, + }); +} + +function buildNoVerifiedIssuesReviewResult(options) { + return { + reportMarkdown: "# Deep Review\n\nNo verified issues.", + structuredOutput: { + target: options.input.target, + scope: options.scope, + laneIssues: options.laneIssues, + triagedIssues: options.triagedIssues, + verification: options.verifications, + final: { + verifiedIssueCount: 0, + verifiedIssueIds: [], + risk: "low", + validationPlan: [], + discardedIssueCount: options.discardedIssueCount, + }, + }, + }; +} + +function hasValidVerification(verifications) { + for (const verification of verifications) { + if (verification && verification.verdict === "valid") return true; + } + return false; +} + +function finishDeepReviewPass(options) { + const context = options.context; + const reviewResult = options.reviewResult; + if (!options.input.fix) return { reviewResult: reviewResult, gitContext: options.gitContext }; + if (context.skipFixWhenNoVerifiedIssues && !hasVerifiedIssues(options.final)) { + return { reviewResult: reviewResult, gitContext: options.gitContext }; } context.phase("fix-preflight", withLoopIteration({ requested: true }, context.iteration)); const fixResult = runDeepReviewFix({ - input: input, + input: options.input, action: context.action, log: context.log, agent: context.agent, parallelAgents: context.parallelAgents, applyPatch: context.applyPatch, - candidates: candidates, - verifications: verifications, - final: final.structuredOutput, - gitContext: gitContext, + candidates: options.candidates, + verifications: options.verifications, + final: options.final, + gitContext: options.gitContext, exploreAgentId: context.exploreAgentId, reasoningAgentId: context.reasoningAgentId, stepSuffix: context.stepSuffix, }); reviewResult.structuredOutput.fix = fixResult; - reviewResult.reportMarkdown = final.reportMarkdown + renderFixMarkdown(fixResult); - return { reviewResult: reviewResult, gitContext: gitContext }; + reviewResult.reportMarkdown = reviewResult.reportMarkdown + renderFixMarkdown(fixResult); + return { reviewResult: reviewResult, gitContext: options.gitContext }; } function cloneDeepReviewInput(input) { diff --git a/src/node/services/workflows/builtInWorkflowContent.generated.ts b/src/node/services/workflows/builtInWorkflowContent.generated.ts index 37a749dc2f..aaa4c9d8cf 100644 --- a/src/node/services/workflows/builtInWorkflowContent.generated.ts +++ b/src/node/services/workflows/builtInWorkflowContent.generated.ts @@ -20,7 +20,7 @@ export const BUILTIN_WORKFLOW_CONTENT: readonly BuiltInWorkflowContentEntry[] = description: "Coordinate adversarial review agents to find, verify, and synthesize code review findings.", source: - '// description: Coordinate adversarial review agents to find, verify, and synthesize code review findings.\n//\n// Keep the lightweight /deep-review skill; this workflow is the heavier structured path with\n// adversarial verification for review findings.\n\n// Verification/fixer fan-out scales with maxCandidates/maxFixes (clamped at 20);\n// cap live agents so raising those budgets queues work instead of launching one\n// wave of 20 concurrent agents. Matches deep-research\'s smart-mode verifier cap.\nconst MAX_PARALLEL_AGENTS = 12;\nexport default function deepReviewWorkflow({\n args,\n phase,\n log,\n agent,\n action,\n parallelAgents,\n applyPatch,\n}) {\n const exploreAgentId = "explore";\n const reasoningAgentId = "exec";\n // Scope discovery stays on Explore; review judgment uses Exec for users with fast Explore defaults.\n const readOnlyReviewPrompt =\n "This is a read-only deep code review task. Do not edit files, create commits, apply patches, push branches, or open PRs. Inspect repository evidence only as needed and report findings.\\n\\n";\n const input = normalizeDeepReviewArgs(args);\n if (input.loop && !input.fix) {\n throw new Error("--loop requires --fix for deep-review-workflow");\n }\n if (input.loop) {\n return runDeepReviewLoop({\n input: input,\n phase: phase,\n log: log,\n agent: agent,\n action: action,\n parallelAgents: parallelAgents,\n applyPatch: applyPatch,\n exploreAgentId: exploreAgentId,\n reasoningAgentId: reasoningAgentId,\n readOnlyReviewPrompt: readOnlyReviewPrompt,\n });\n }\n return runDeepReviewPass({\n input: input,\n phase: phase,\n log: log,\n agent: agent,\n action: action,\n parallelAgents: parallelAgents,\n applyPatch: applyPatch,\n exploreAgentId: exploreAgentId,\n reasoningAgentId: reasoningAgentId,\n readOnlyReviewPrompt: readOnlyReviewPrompt,\n stepSuffix: "",\n iteration: 0,\n skipFixWhenNoVerifiedIssues: false,\n }).reviewResult;\n}\n\nfunction runDeepReviewLoop(context) {\n const passes = [];\n let stopReason = "";\n let remainingFixBudget = context.input.maxFixes;\n let loopHeadRef = context.input.headRef;\n for (let iteration = 1; iteration <= context.input.maxLoopIterations; iteration += 1) {\n context.phase("loop-iteration", {\n iteration: iteration,\n maxIterations: context.input.maxLoopIterations,\n remainingFixBudget: remainingFixBudget,\n });\n const budgetExhaustedReadOnlyCheck = remainingFixBudget <= 0;\n const iterationInput = cloneDeepReviewInput(context.input);\n iterationInput.headRef = loopHeadRef;\n iterationInput.maxFixes = remainingFixBudget;\n if (budgetExhaustedReadOnlyCheck) iterationInput.fix = false;\n const pass = runDeepReviewPass({\n input: iterationInput,\n phase: context.phase,\n log: context.log,\n agent: context.agent,\n action: context.action,\n parallelAgents: context.parallelAgents,\n applyPatch: context.applyPatch,\n exploreAgentId: context.exploreAgentId,\n reasoningAgentId: context.reasoningAgentId,\n readOnlyReviewPrompt: context.readOnlyReviewPrompt,\n stepSuffix: "loop-" + iteration,\n iteration: iteration,\n skipFixWhenNoVerifiedIssues: true,\n });\n passes.push(pass.reviewResult);\n if (budgetExhaustedReadOnlyCheck) {\n stopReason = hasVerifiedIssues(pass.reviewResult.structuredOutput.final)\n ? "fix-budget-exhausted"\n : "no-verified-issues";\n return buildLoopResult(context.input, passes, stopReason, remainingFixBudget);\n }\n const fixProgress = reviewResultHasFixProgress(pass.reviewResult);\n remainingFixBudget = Math.max(0, remainingFixBudget - countSelectedFixes(pass.reviewResult));\n if (fixProgress) loopHeadRef = "";\n stopReason = getLoopStopReason(pass.reviewResult, remainingFixBudget);\n if (stopReason === "fix-budget-exhausted" && iteration < context.input.maxLoopIterations)\n continue;\n if (stopReason) {\n return buildLoopResult(context.input, passes, stopReason, remainingFixBudget);\n }\n }\n return buildLoopResult(context.input, passes, "max-iterations", remainingFixBudget);\n}\n\nfunction runDeepReviewPass(context) {\n const input = cloneDeepReviewInput(context.input);\n const gitContext = shouldCollectGitReviewContext(input)\n ? collectGitReviewContext(context.action, input, context.log, context.stepSuffix)\n : null;\n applyGitContextToReviewInput(input, gitContext);\n const maxCandidates = input.maxCandidates;\n\n context.phase(\n "scope",\n withLoopIteration(\n {\n target: input.target,\n fileCount: input.files.length,\n hasDiffSnapshot: input.diff.length > 0,\n hasGitSnapshot: input.gitSnapshot.length > 0,\n },\n context.iteration\n )\n );\n const scope = context.agent({\n id: workflowStepId("scope-review-surface", context.stepSuffix),\n title: "Scope review surface",\n agentId: context.exploreAgentId,\n prompt:\n "Scope this code review. Identify changed files, likely intent, touched layers, highest-risk areas, and which review lanes should run. Use repository evidence; do not assume the diff is complete if refs are provided.\\n\\n" +\n renderReviewInput(input),\n outputSchema: scopeSchema(),\n });\n\n const lanes = selectReviewLanes(scope.structuredOutput.lanes);\n context.log("Selected deep review lanes", withLoopIteration({ lanes: lanes }, context.iteration));\n\n context.phase("lane-review", withLoopIteration({ lanes: lanes }, context.iteration));\n const laneReviews = context.parallelAgents(\n lanes.map(function (lane) {\n return {\n id: workflowStepId("review-" + lane, context.stepSuffix),\n title: "Review lane: " + lane,\n agentId: context.reasoningAgentId,\n prompt:\n context.readOnlyReviewPrompt +\n lanePrompt(lane) +\n "\\n\\nReview target:\\n" +\n renderReviewInput(input) +\n "\\n\\nScoped review surface:\\n" +\n JSON.stringify(scope.structuredOutput, null, 2) +\n "\\n\\nReturn only concrete, actionable findings with file paths and evidence. Prefer an empty issues array over speculative feedback.",\n outputSchema: issueListSchema(),\n };\n })\n );\n const laneIssues = flatten(\n laneReviews.map(function (review) {\n return review.structuredOutput.issues || [];\n })\n );\n context.log(\n "Lane review produced candidate issues",\n withLoopIteration({ count: laneIssues.length }, context.iteration)\n );\n\n context.phase(\n "triage-dedupe",\n withLoopIteration({ candidateCount: laneIssues.length }, context.iteration)\n );\n const triage = context.agent({\n id: workflowStepId("triage-candidate-issues", context.stepSuffix),\n title: "Triage and dedupe review findings",\n agentId: context.reasoningAgentId,\n prompt:\n context.readOnlyReviewPrompt +\n "Deduplicate and consolidate these candidate code review findings. Merge duplicates across lanes (combining their evidence), normalize severity, and drop only clearly non-actionable items; keep borderline findings so adversarial verification can make the validity call. Order the issues by severity and impact, most severe first — only the first " +\n maxCandidates +\n " survive the candidate budget.\\n\\n" +\n "Review target:\\n" +\n renderReviewInput(input) +\n "\\n\\nCandidate issues:\\n" +\n JSON.stringify(laneIssues, null, 2),\n outputSchema: issueListSchema(),\n });\n // Triage is prompted to emit issues most-severe-first, but re-sort defensively so a\n // high-severity finding emitted past the budget cutoff is never silently dropped.\n const rankedIssues = sortIssuesBySeverity(triage.structuredOutput.issues || []);\n const candidates = rankedIssues.slice(0, maxCandidates);\n context.log(\n "Triaged candidate issues",\n withLoopIteration(\n {\n candidateCount: rankedIssues.length,\n selectedCount: candidates.length,\n droppedByBudget: rankedIssues.slice(candidates.length).map(function (issue, index) {\n return stableIssueId(issue, candidates.length + index);\n }),\n },\n context.iteration\n )\n );\n\n context.phase(\n "adversarial-verification",\n withLoopIteration({ candidateCount: candidates.length }, context.iteration)\n );\n const verificationResults =\n candidates.length > 0\n ? context.parallelAgents(\n candidates.map(function (issue, index) {\n return {\n id: workflowStepId("verify-issue-" + index, context.stepSuffix),\n title: "Verify review finding " + (index + 1),\n agentId: context.reasoningAgentId,\n prompt:\n context.readOnlyReviewPrompt +\n "Adversarially verify this code review finding. Try to disprove it. Inspect relevant code paths and tests. Decide whether it is valid, duplicate, overstated, not reproducible, or needs more information.\\n\\n" +\n "Review target:\\n" +\n renderReviewInput(input) +\n "\\n\\nFinding:\\n" +\n JSON.stringify(issue, null, 2),\n outputSchema: verificationSchema(),\n };\n }),\n { maxParallel: MAX_PARALLEL_AGENTS }\n )\n : [];\n const verifications = verificationResults.map(function (verification) {\n return verification.structuredOutput;\n });\n context.log(\n "Verified candidate issues",\n withLoopIteration({ count: verifications.length }, context.iteration)\n );\n\n context.phase(\n "final-synthesis",\n withLoopIteration(\n {\n candidateCount: candidates.length,\n verificationCount: verifications.length,\n },\n context.iteration\n )\n );\n const final = context.agent({\n id: workflowStepId("synthesize-review", context.stepSuffix),\n title: "Synthesize final deep review",\n agentId: context.reasoningAgentId,\n prompt:\n context.readOnlyReviewPrompt +\n "Write the final code review. Include only findings that remain actionable after adversarial verification. Use severity P0-P4, file paths, issue IDs, and concrete evidence. If there are no verified issues, say so clearly. Include questions and a validation plan. Set verifiedIssueIds to the issue IDs included in the final review when any are verified.\\n\\n" +\n "Scoped review surface:\\n" +\n JSON.stringify(scope.structuredOutput, null, 2) +\n "\\n\\nTriaged issues:\\n" +\n JSON.stringify(candidates, null, 2) +\n "\\n\\nVerification results:\\n" +\n JSON.stringify(verifications, null, 2),\n outputSchema: finalSynthesisSchema(),\n });\n\n const reviewResult = {\n reportMarkdown: final.reportMarkdown,\n structuredOutput: {\n target: input.target,\n scope: scope.structuredOutput,\n laneIssues: laneIssues,\n triagedIssues: candidates,\n verification: verifications,\n final: final.structuredOutput,\n },\n };\n if (!input.fix) return { reviewResult: reviewResult, gitContext: gitContext };\n if (context.skipFixWhenNoVerifiedIssues && !hasVerifiedIssues(final.structuredOutput)) {\n return { reviewResult: reviewResult, gitContext: gitContext };\n }\n\n context.phase("fix-preflight", withLoopIteration({ requested: true }, context.iteration));\n const fixResult = runDeepReviewFix({\n input: input,\n action: context.action,\n log: context.log,\n agent: context.agent,\n parallelAgents: context.parallelAgents,\n applyPatch: context.applyPatch,\n candidates: candidates,\n verifications: verifications,\n final: final.structuredOutput,\n gitContext: gitContext,\n exploreAgentId: context.exploreAgentId,\n reasoningAgentId: context.reasoningAgentId,\n stepSuffix: context.stepSuffix,\n });\n reviewResult.structuredOutput.fix = fixResult;\n reviewResult.reportMarkdown = final.reportMarkdown + renderFixMarkdown(fixResult);\n return { reviewResult: reviewResult, gitContext: gitContext };\n}\n\nfunction cloneDeepReviewInput(input) {\n return {\n target: input.target,\n baseRef: input.baseRef,\n headRef: input.headRef,\n diff: input.diff,\n files: input.files.slice(),\n instructions: input.instructions,\n gitSnapshot: input.gitSnapshot,\n explicitDiff: input.explicitDiff,\n explicitFiles: input.explicitFiles,\n includeGitContext: input.includeGitContext,\n maxCandidates: input.maxCandidates,\n fix: input.fix,\n loop: input.loop,\n maxFixes: input.maxFixes,\n maxLoopIterations: input.maxLoopIterations,\n fixIssueIds: input.fixIssueIds.slice(),\n };\n}\n\nfunction workflowStepId(baseId, suffix) {\n return suffix ? baseId + "-" + suffix : baseId;\n}\n\nfunction withLoopIteration(details, iteration) {\n if (iteration) details.iteration = iteration;\n return details;\n}\n\n// Stable sort: P0 first, unknown/invalid severity last, ties keep triage emit order.\nfunction sortIssuesBySeverity(issues) {\n return issues\n .map(function (issue, index) {\n return { issue: issue, index: index };\n })\n .sort(function (a, b) {\n const rankDelta = issueSeverityRank(a.issue) - issueSeverityRank(b.issue);\n return rankDelta !== 0 ? rankDelta : a.index - b.index;\n })\n .map(function (entry) {\n return entry.issue;\n });\n}\n\nfunction issueSeverityRank(issue) {\n const match =\n isObject(issue) && typeof issue.severity === "string"\n ? /^P([0-4])$/.exec(issue.severity)\n : null;\n return match ? Number(match[1]) : 5;\n}\n\nfunction hasVerifiedIssues(final) {\n if (!final) return false;\n if (typeof final.verifiedIssueCount === "number") return final.verifiedIssueCount > 0;\n return Array.isArray(final.verifiedIssueIds) && final.verifiedIssueIds.length > 0;\n}\n\nfunction reviewResultHasFixProgress(reviewResult) {\n const output = reviewResult && reviewResult.structuredOutput ? reviewResult.structuredOutput : {};\n const fix = output.fix;\n return Boolean(fix && fixHasProgress(fix));\n}\n\nfunction countSelectedFixes(reviewResult) {\n const output = reviewResult && reviewResult.structuredOutput ? reviewResult.structuredOutput : {};\n const fix = output.fix;\n return fix && Array.isArray(fix.selectedIssues) ? fix.selectedIssues.length : 0;\n}\n\nfunction getLoopStopReason(reviewResult, remainingFixBudget) {\n const output = reviewResult && reviewResult.structuredOutput ? reviewResult.structuredOutput : {};\n if (!hasVerifiedIssues(output.final)) return "no-verified-issues";\n const fix = output.fix;\n if (!fix) return "no-fix-attempted";\n if (fix.skippedReason) return "fix-skipped";\n if (!fix.selectedIssues || fix.selectedIssues.length === 0) return "no-fixable-issues";\n if (!fixHasProgress(fix)) return "no-fix-progress";\n const validationStatus = fix.validation ? fix.validation.status : "not-run";\n if (validationStatus === "failed") return "validation-failed";\n if (validationStatus !== "passed") return "validation-not-run";\n if (remainingFixBudget <= 0) return "fix-budget-exhausted";\n return "";\n}\n\nfunction fixHasProgress(fix) {\n return countStatus(fix.applications, "applied") > 0;\n}\n\nfunction buildLoopResult(input, passes, stopReason, remainingFixBudget) {\n const loop = {\n requested: true,\n completed: stopReason === "no-verified-issues",\n iterations: passes.length,\n maxIterations: input.maxLoopIterations,\n remainingFixBudget: remainingFixBudget,\n stopReason: stopReason,\n };\n const structuredOutput = {\n target: input.target,\n loop: loop,\n passes: passes.map(function (pass, index) {\n return {\n iteration: index + 1,\n result: pass.structuredOutput,\n };\n }),\n };\n if (passes.length > 0) {\n const latest = passes[passes.length - 1].structuredOutput;\n structuredOutput.latest = latest;\n structuredOutput.final = latest.final || null;\n if (latest.fix) structuredOutput.fix = latest.fix;\n }\n return {\n reportMarkdown: renderLoopMarkdown(passes, loop),\n structuredOutput: structuredOutput,\n };\n}\n\nfunction renderLoopMarkdown(passes, loop) {\n let markdown = "# Deep Review Loop\\n\\n";\n markdown += "- Iterations: " + loop.iterations + " / " + loop.maxIterations + "\\n";\n markdown += "- Stop reason: " + loop.stopReason + "\\n";\n markdown += "- Completed: " + (loop.completed ? "yes" : "no") + "\\n";\n for (let index = 0; index < passes.length; index += 1) {\n markdown += "\\n\\n---\\n\\n## Loop iteration " + (index + 1) + "\\n\\n";\n markdown += passes[index].reportMarkdown || "";\n }\n return markdown;\n}\n\nfunction runDeepReviewFix(context) {\n const input = context.input;\n const baseFix = {\n requested: true,\n selectedIssues: [],\n attempts: [],\n applications: [],\n resolutions: [],\n unresolved: [],\n };\n const preflight = collectFixPreflight(\n context.action,\n context.log,\n input,\n context.gitContext,\n context.stepSuffix\n );\n if (preflight.skippedReason) {\n baseFix.skippedReason = preflight.skippedReason;\n return baseFix;\n }\n let expectedHeadSha = preflight.expectedHeadSha;\n\n const selected = selectFixIssues(context.candidates, context.verifications, input, context.final);\n baseFix.selectedIssues = selected.map(function (item) {\n return summarizeFixIssue(item.issueId, item.issue);\n });\n context.log("Selected deep review fixes", {\n selectedCount: selected.length,\n skippedCount: context.candidates.length - selected.length,\n });\n if (selected.length === 0) return baseFix;\n\n const fixerResults = context.parallelAgents(\n selected.map(function (item, index) {\n return {\n id: workflowStepId("fix-issue-" + index, context.stepSuffix),\n title: "Fix verified review finding " + (index + 1),\n agentId: context.reasoningAgentId,\n prompt: buildFixPrompt(input, item),\n outputSchema: fixAttemptSchema(),\n };\n }),\n { maxParallel: MAX_PARALLEL_AGENTS }\n );\n\n const integratedIssues = [];\n for (let index = 0; index < selected.length; index += 1) {\n const item = selected[index];\n const fixerResult = fixerResults[index];\n const attempt = summarizeFixAttempt(item.issueId, fixerResult);\n baseFix.attempts.push(attempt);\n const attemptOutput =\n fixerResult && fixerResult.structuredOutput ? fixerResult.structuredOutput : {};\n if (!matchesReportedIssueId(attemptOutput, item.issueId)) {\n baseFix.unresolved.push({\n issueId: item.issueId,\n reason: issueIdMismatchReason("fixer", item.issueId, attemptOutput),\n });\n continue;\n }\n if (attemptOutput.status !== "fixed" || attemptOutput.commitCreated !== true) {\n if (attemptOutput.status !== "already-fixed") {\n baseFix.unresolved.push({\n issueId: item.issueId,\n reason: attemptOutput.status || "not-fixed",\n });\n }\n continue;\n }\n\n const application = safeApplyPatch(\n context.applyPatch,\n workflowStepId("apply-fix-" + index, context.stepSuffix),\n fixerResult,\n expectedHeadSha\n );\n application.issueId = item.issueId;\n baseFix.applications.push(application);\n if (application.status === "applied") {\n expectedHeadSha = getAppliedHeadCommitSha(application) || expectedHeadSha;\n integratedIssues.push(item.issueId);\n continue;\n }\n if (application.status !== "conflict") {\n baseFix.unresolved.push({\n issueId: item.issueId,\n reason: application.error || application.status,\n });\n continue;\n }\n\n const resolver = context.agent({\n id: workflowStepId("resolve-fix-" + index + "-conflict", context.stepSuffix),\n title: "Resolve auto-fix conflict " + (index + 1),\n agentId: context.reasoningAgentId,\n prompt: buildResolverPrompt(input, item, fixerResult, application, integratedIssues),\n outputSchema: fixResolverSchema(),\n });\n const resolution = summarizeResolution(item.issueId, resolver);\n baseFix.resolutions.push(resolution);\n const resolutionOutput = resolver && resolver.structuredOutput ? resolver.structuredOutput : {};\n if (!matchesReportedIssueId(resolutionOutput, item.issueId)) {\n baseFix.unresolved.push({\n issueId: item.issueId,\n reason: issueIdMismatchReason("resolver", item.issueId, resolutionOutput),\n });\n continue;\n }\n if (resolutionOutput.status === "already-resolved") {\n integratedIssues.push(item.issueId);\n continue;\n }\n if (resolutionOutput.status === "resolved" && resolutionOutput.commitCreated === true) {\n const resolvedApplication = safeApplyPatch(\n context.applyPatch,\n workflowStepId("apply-resolved-fix-" + index, context.stepSuffix),\n resolver,\n expectedHeadSha\n );\n resolvedApplication.issueId = item.issueId;\n resolution.applyStatus = resolvedApplication.status;\n baseFix.applications.push(resolvedApplication);\n if (resolvedApplication.status === "applied") {\n expectedHeadSha = getAppliedHeadCommitSha(resolvedApplication) || expectedHeadSha;\n integratedIssues.push(item.issueId);\n } else {\n baseFix.unresolved.push({\n issueId: item.issueId,\n reason: resolvedApplication.error || resolvedApplication.status,\n });\n }\n } else {\n baseFix.unresolved.push({\n issueId: item.issueId,\n reason: resolutionOutput.status || "unresolved-conflict",\n });\n }\n }\n\n if (integratedIssues.length > 0) {\n const validation = context.agent({\n id: workflowStepId("validate-auto-fixes", context.stepSuffix),\n title: "Validate applied auto-fixes",\n agentId: context.exploreAgentId,\n prompt: buildValidationPrompt(input, context.final, baseFix),\n outputSchema: fixValidationSchema(),\n });\n baseFix.validation = validation.structuredOutput;\n }\n return baseFix;\n}\n\nfunction collectFixPreflight(action, log, input, gitContext, stepSuffix) {\n if (input.explicitDiff)\n return {\n skippedReason: "auto-fix requires a local current workspace target, not an explicit diff",\n };\n if (looksNonLocalTarget(input.target))\n return { skippedReason: "auto-fix requires a local current workspace target" };\n let status = null;\n try {\n status = action.git.status({\n id: workflowStepId("fix-git-status", stepSuffix),\n input: { includeIgnored: false, head: input.headRef || "HEAD" },\n builtInOnly: true,\n cache: false,\n }).output;\n } catch (error) {\n const message = formatError(error);\n log("Git status unavailable for auto-fix preflight", { error: message });\n return { skippedReason: "auto-fix requires a fresh local Git status" };\n }\n if (!isObject(status)) return { skippedReason: "auto-fix requires a fresh local Git status" };\n if (!isCurrentReviewHead(input, status)) {\n return {\n skippedReason: "auto-fix requires the reviewed head ref to be the current checked-out branch",\n };\n }\n const reviewedSnapshot = getReviewedGitSnapshot(gitContext);\n if (!reviewedSnapshot) {\n return { skippedReason: "auto-fix requires a reviewed Git branch and HEAD snapshot" };\n }\n if (!matchesReviewedGitBranch(reviewedSnapshot, status)) {\n return {\n skippedReason: "auto-fix requires the current Git branch to match the reviewed snapshot",\n };\n }\n if (\n arrayLength(status.staged) > 0 ||\n arrayLength(status.unstaged) > 0 ||\n arrayLength(status.untracked) > 0\n ) {\n return { skippedReason: "auto-fix requires a clean committed local worktree" };\n }\n return { status: status, expectedHeadSha: reviewedSnapshot.headSha };\n}\n\nfunction isCurrentReviewHead(input, status) {\n if (!input.headRef) return true;\n const headRef = String(input.headRef).trim();\n if (!headRef || headRef === "HEAD") return true;\n const branch = normalizedGitBranch(status.branch);\n const currentBranchRef = branch ? "refs/heads/" + branch : "";\n if (branch && (headRef === branch || headRef === currentBranchRef)) return true;\n const requestedHeadRef =\n typeof status.requestedHeadRef === "string" ? status.requestedHeadRef : "";\n if (requestedHeadRef.length > 0) return false;\n if (!isExplicitCommitShaRef(headRef)) return false;\n const headSha = typeof status.headSha === "string" ? status.headSha : "";\n const requestedHeadSha =\n typeof status.requestedHeadSha === "string" ? status.requestedHeadSha : "";\n return Boolean(headSha && requestedHeadSha && headSha === requestedHeadSha);\n}\n\nfunction isExplicitCommitShaRef(headRef) {\n return /^[0-9a-fA-F]{7,64}$/.test(headRef);\n}\n\nfunction getReviewedGitSnapshot(gitContext) {\n const reviewedStatus = gitContext && isObject(gitContext.status) ? gitContext.status : null;\n if (!reviewedStatus) return null;\n const branch = normalizedGitBranch(reviewedStatus.branch);\n const headSha = typeof reviewedStatus.headSha === "string" ? reviewedStatus.headSha : "";\n if (!branch || !headSha) return null;\n return { branch: branch, headSha: headSha };\n}\n\nfunction matchesReviewedGitBranch(reviewedSnapshot, status) {\n const currentBranch = normalizedGitBranch(status.branch);\n return currentBranch.length > 0 && currentBranch === reviewedSnapshot.branch;\n}\n\nfunction normalizedGitBranch(branch) {\n if (typeof branch !== "string") return "";\n const trimmed = branch.trim();\n if (!trimmed || trimmed === "HEAD (no branch)") return "";\n return trimmed;\n}\n\nfunction looksNonLocalTarget(target) {\n const text = String(target || "").toLowerCase();\n return (\n text.indexOf("http://") !== -1 ||\n text.indexOf("https://") !== -1 ||\n /(^|\\s)(pr|pull request)\\s*#?\\d+/.test(text)\n );\n}\n\nfunction selectFixIssues(candidates, verifications, input, final) {\n const selected = [];\n const allowedIds = input.fixIssueIds || [];\n const finalFilter = getFinalIssueFilter(final);\n if (finalFilter.kind === "none") return selected;\n for (let index = 0; index < candidates.length; index += 1) {\n const issue = candidates[index];\n const issueId = stableIssueId(issue, index);\n if (finalFilter.kind === "ids" && finalFilter.ids[issueId] !== true) continue;\n if (allowedIds.length > 0 && allowedIds.indexOf(issueId) === -1) continue;\n const verification = findVerificationForIssue(issue, issueId, index, verifications);\n if (!verification || verification.verdict !== "valid" || verification.confidence === "low")\n continue;\n selected.push({ issueId: issueId, issue: issue, verification: verification, index: index });\n if (selected.length >= input.maxFixes) break;\n }\n return selected;\n}\n\nfunction getFinalIssueFilter(final) {\n if (final && final.verifiedIssueCount === 0) return { kind: "none" };\n if (final && Array.isArray(final.verifiedIssueIds)) {\n const ids = sanitizeStringArray(final.verifiedIssueIds);\n if (ids.length === 0) return { kind: "none" };\n const map = {};\n for (const id of ids) map[id] = true;\n return { kind: "ids", ids: map };\n }\n return { kind: "none" };\n}\n\nfunction stableIssueId(issue, index) {\n return issue && typeof issue.id === "string" && issue.id.trim()\n ? issue.id.trim()\n : "triaged-" + index;\n}\n\nfunction findVerificationForIssue(issue, issueId, index, verifications) {\n for (const verification of verifications) {\n if (!hasNonEmptyIssueId(verification)) continue;\n if (verification.issueId === issueId) return verification;\n if (issue && typeof issue.id === "string" && verification.issueId === issue.id)\n return verification;\n }\n return null;\n}\n\nfunction hasNonEmptyIssueId(verification) {\n return (\n verification &&\n typeof verification.issueId === "string" &&\n verification.issueId.trim().length > 0\n );\n}\n\nfunction matchesReportedIssueId(output, expectedIssueId) {\n return output && output.issueId === expectedIssueId;\n}\n\nfunction issueIdMismatchReason(source, expectedIssueId, output) {\n const reported =\n output && typeof output.issueId === "string" && output.issueId.length > 0\n ? output.issueId\n : "";\n return source + " reported issueId " + reported + " for " + expectedIssueId;\n}\n\nfunction summarizeFixIssue(issueId, issue) {\n return {\n issueId: issueId,\n severity: issue && issue.severity ? issue.severity : "",\n title: issue && issue.title ? issue.title : "",\n filePaths: issue && Array.isArray(issue.filePaths) ? issue.filePaths : [],\n };\n}\n\nfunction summarizeFixAttempt(issueId, result) {\n const output = result && result.structuredOutput ? result.structuredOutput : {};\n return {\n issueId: issueId,\n taskId: result ? result.taskId : undefined,\n status: output.status || "unknown",\n summary: output.summary || "",\n validation: Array.isArray(output.validation) ? output.validation : [],\n };\n}\n\nfunction summarizeResolution(issueId, result) {\n const output = result && result.structuredOutput ? result.structuredOutput : {};\n return {\n issueId: issueId,\n resolverTaskId: result ? result.taskId : undefined,\n status: output.status || "unknown",\n summary: output.summary || "",\n };\n}\n\nfunction safeApplyPatch(applyPatch, id, source, expectedHeadSha) {\n try {\n const spec = { id: id, source: source, target: "parent", onConflict: "return" };\n if (expectedHeadSha) spec.expectedHeadSha = expectedHeadSha;\n const result = applyPatch(spec);\n return normalizePatchApplication(source, result);\n } catch (error) {\n return {\n sourceTaskId: source ? source.taskId : undefined,\n status: "failed",\n error: formatError(error),\n };\n }\n}\n\nfunction normalizePatchApplication(source, result) {\n const status =\n result && result.status\n ? result.status\n : result && result.success === true\n ? "applied"\n : "failed";\n return {\n sourceTaskId: source ? source.taskId : undefined,\n status: status,\n appliedCommits: result ? result.appliedCommits : undefined,\n headCommitSha: result ? result.headCommitSha : undefined,\n conflictPaths: result ? result.conflictPaths : undefined,\n failedPatchSubject: result ? result.failedPatchSubject : undefined,\n error: result ? result.error : undefined,\n projectResults: result ? result.projectResults : undefined,\n };\n}\n\nfunction getAppliedHeadCommitSha(application) {\n if (\n application &&\n typeof application.headCommitSha === "string" &&\n application.headCommitSha.length > 0\n ) {\n return application.headCommitSha;\n }\n const projectResults =\n application && Array.isArray(application.projectResults) ? application.projectResults : [];\n for (let index = projectResults.length - 1; index >= 0; index -= 1) {\n const projectResult = projectResults[index];\n if (\n projectResult &&\n typeof projectResult.headCommitSha === "string" &&\n projectResult.headCommitSha.length > 0\n ) {\n return projectResult.headCommitSha;\n }\n }\n return "";\n}\n\nfunction buildFixPrompt(input, item) {\n return (\n "Fix exactly one verified deep-review finding. Make minimal code changes, add or update behavioral tests when appropriate, run targeted validation when practical, and create one or more git commits before reporting if code changed. If already fixed, not fixable, or more information is needed, report that status instead of inventing a patch. Do not push, open PRs, or perform unrelated cleanup.\\n\\n" +\n "Review target:\\n" +\n renderReviewInput(input) +\n "\\n\\nIssue ID: " +\n item.issueId +\n "\\nFinding:\\n" +\n JSON.stringify(item.issue, null, 2) +\n "\\n\\nVerification:\\n" +\n JSON.stringify(item.verification, null, 2)\n );\n}\n\nfunction buildResolverPrompt(input, item, fixerResult, application, integratedIssues) {\n return (\n "Resolve the git-am conflict for one auto-fix patch. Preserve the original issue intent and avoid unrelated changes. Replay the original patch with task_apply_git_patch in your workspace using dry_run false, resolve conflicts, git add, git am --continue, and commit follow-up resolved changes if needed. If earlier fixes already solved the issue, report already-resolved without inventing changes. Do not push or open PRs.\\n\\n" +\n "Review target:\\n" +\n renderReviewInput(input) +\n "\\n\\nIssue:\\n" +\n JSON.stringify(item.issue, null, 2) +\n "\\n\\nVerification:\\n" +\n JSON.stringify(item.verification, null, 2) +\n "\\n\\nFailing fixer task ID: " +\n (fixerResult ? fixerResult.taskId : "unknown") +\n "\\nApply conflict:\\n" +\n JSON.stringify(application, null, 2) +\n "\\nAlready integrated issue IDs:\\n" +\n JSON.stringify(integratedIssues, null, 2)\n );\n}\n\nfunction buildValidationPrompt(input, final, fixResult) {\n return (\n "Validate the auto-fixes now integrated in the parent workspace. Run the review validation plan and targeted tests/checks relevant to applied fixes. Do not edit files, create commits, apply patches, push, or open PRs. Report pass, fail, or not-run with commands and key failures.\\n\\n" +\n "Review target:\\n" +\n renderReviewInput(input) +\n "\\n\\nReview validation plan:\\n" +\n JSON.stringify(final.validationPlan || [], null, 2) +\n "\\n\\nAuto-fix result so far:\\n" +\n JSON.stringify(fixResult, null, 2)\n );\n}\n\nfunction renderFixMarkdown(fix) {\n const appliedCount =\n countStatus(fix.applications, "applied") +\n countStatus(fix.attempts, "already-fixed") +\n countStatus(fix.resolutions, "already-resolved");\n const conflictResolvedCount = countResolvedConflicts(fix.resolutions);\n const validationStatus = fix.validation ? fix.validation.status : "not-run";\n let markdown = "\\n\\n---\\n\\n## Auto-fix results\\n\\n";\n if (fix.skippedReason) {\n markdown += "- Skipped: " + fix.skippedReason + "\\n";\n return markdown;\n }\n markdown += "- Selected: " + fix.selectedIssues.length + " verified findings\\n";\n markdown += "- Fixed/applied: " + appliedCount + "\\n";\n markdown +=\n "- Already fixed/resolved: " +\n (countStatus(fix.attempts, "already-fixed") +\n countStatus(fix.resolutions, "already-resolved")) +\n "\\n";\n markdown += "- Not fixed: " + fix.unresolved.length + "\\n";\n markdown += "- Conflicts resolved: " + conflictResolvedCount + "\\n";\n markdown += "- Validation: " + validationStatus + "\\n";\n markdown += renderFixFailureDetails(fix);\n return markdown;\n}\n\nfunction countResolvedConflicts(resolutions) {\n let count = 0;\n for (const resolution of resolutions || []) {\n if (!resolution) continue;\n if (resolution.status === "already-resolved") count += 1;\n else if (resolution.status === "resolved" && resolution.applyStatus === "applied") count += 1;\n }\n return count;\n}\n\nfunction renderFixFailureDetails(fix) {\n let markdown = "";\n if (fix.unresolved && fix.unresolved.length > 0) {\n markdown += "\\nUnresolved issues:\\n";\n for (const unresolved of fix.unresolved) {\n markdown +=\n "- " + renderFixIssueLabel(fix, unresolved.issueId) + ": " + unresolved.reason + "\\n";\n }\n }\n const failedApplications = (fix.applications || []).filter(function (application) {\n return application && application.status !== "applied";\n });\n if (failedApplications.length > 0) {\n markdown += "\\nPatch application details:\\n";\n for (const application of failedApplications) {\n const details = renderPatchApplicationDetails(application);\n markdown +=\n "- " +\n renderFixIssueLabel(fix, application.issueId) +\n ": " +\n application.status +\n (details ? " — " + details : "") +\n "\\n";\n }\n }\n if (\n fix.validation &&\n Array.isArray(fix.validation.failures) &&\n fix.validation.failures.length > 0\n ) {\n markdown += "\\nValidation failures:\\n";\n for (const failure of fix.validation.failures) {\n markdown += "- " + failure + "\\n";\n }\n }\n return markdown;\n}\n\nfunction renderPatchApplicationDetails(application) {\n const details = [];\n if (application.conflictPaths && application.conflictPaths.length > 0)\n details.push("conflicts: " + application.conflictPaths.join(", "));\n if (application.failedPatchSubject)\n details.push("failed patch: " + application.failedPatchSubject);\n if (application.error) details.push(application.error);\n return details.join("; ");\n}\n\nfunction renderFixIssueLabel(fix, issueId) {\n for (const issue of fix.selectedIssues || []) {\n if (issue && issue.issueId === issueId && issue.title)\n return issueId + " (" + issue.title + ")";\n }\n return issueId;\n}\n\nfunction countStatus(items, status) {\n let count = 0;\n for (const item of items || []) {\n if (item && item.status === status) count += 1;\n }\n return count;\n}\n\nfunction fixAttemptSchema() {\n return {\n type: "object",\n required: ["issueId", "status", "summary", "validation", "commitCreated"],\n additionalProperties: false,\n properties: {\n issueId: { type: "string" },\n status: { type: "string", enum: ["fixed", "already-fixed", "not-fixable", "needs-info"] },\n summary: { type: "string" },\n validation: { type: "array", items: { type: "string" } },\n commitCreated: { type: "boolean" },\n },\n };\n}\n\nfunction fixResolverSchema() {\n return {\n type: "object",\n required: ["issueId", "status", "summary", "validation", "commitCreated"],\n additionalProperties: false,\n properties: {\n issueId: { type: "string" },\n status: { type: "string", enum: ["resolved", "already-resolved", "unresolved"] },\n summary: { type: "string" },\n validation: { type: "array", items: { type: "string" } },\n commitCreated: { type: "boolean" },\n },\n };\n}\n\nfunction fixValidationSchema() {\n return {\n type: "object",\n required: ["status", "commands", "summary", "failures"],\n additionalProperties: false,\n properties: {\n status: { type: "string", enum: ["passed", "failed", "not-run"] },\n commands: { type: "array", items: { type: "string" } },\n summary: { type: "string" },\n failures: { type: "array", items: { type: "string" } },\n },\n };\n}\n\nfunction normalizeDeepReviewArgs(args) {\n const normalized = {\n target: "current workspace changes",\n baseRef: "",\n headRef: "",\n diff: "",\n files: [],\n instructions: "",\n gitSnapshot: "",\n explicitDiff: false,\n explicitFiles: false,\n includeGitContext: false,\n maxCandidates: 12,\n fix: false,\n loop: false,\n maxFixes: 5,\n maxLoopIterations: 5,\n fixIssueIds: [],\n };\n\n if (typeof args === "string" && args.trim()) {\n applyFixFlags(normalized, args.trim());\n return normalized;\n }\n\n if (!args || typeof args !== "object") {\n return normalized;\n }\n\n let textualTarget = "";\n if (typeof args.target === "string" && args.target.trim()) textualTarget = args.target.trim();\n else if (typeof args.input === "string" && args.input.trim()) textualTarget = args.input.trim();\n else if (typeof args.pr === "string" && args.pr.trim()) textualTarget = args.pr.trim();\n else if (typeof args.branch === "string" && args.branch.trim())\n textualTarget = args.branch.trim();\n if (textualTarget) applyFixFlags(normalized, textualTarget);\n\n if (typeof args.baseRef === "string") normalized.baseRef = args.baseRef.trim();\n else if (typeof args.base === "string") normalized.baseRef = args.base.trim();\n\n if (typeof args.headRef === "string") normalized.headRef = args.headRef.trim();\n else if (typeof args.head === "string") normalized.headRef = args.head.trim();\n\n if (typeof args.diff === "string" && args.diff.trim().length > 0) {\n normalized.diff = args.diff;\n normalized.explicitDiff = true;\n }\n if (typeof args.instructions === "string") normalized.instructions = args.instructions.trim();\n else if (typeof args.notes === "string") normalized.instructions = args.notes.trim();\n\n if (Array.isArray(args.files)) {\n normalized.files = args.files\n .filter(function (file) {\n return typeof file === "string" && file.trim().length > 0;\n })\n .map(function (file) {\n return file.trim();\n });\n normalized.explicitFiles = normalized.files.length > 0;\n }\n\n if (typeof args.includeGitContext === "boolean") {\n normalized.includeGitContext = args.includeGitContext;\n }\n\n if (typeof args.maxCandidates === "number" && args.maxCandidates > 0) {\n normalized.maxCandidates = Math.min(20, Math.max(1, Math.floor(args.maxCandidates)));\n }\n\n if (typeof args.maxFixes === "number" && args.maxFixes > 0) {\n normalized.maxFixes = Math.min(20, Math.max(1, Math.floor(args.maxFixes)));\n }\n if (typeof args.maxLoopIterations === "number" && args.maxLoopIterations > 0) {\n normalized.maxLoopIterations = Math.min(10, Math.max(1, Math.floor(args.maxLoopIterations)));\n }\n if (Array.isArray(args.fixIssueIds)) {\n normalized.fixIssueIds = sanitizeStringArray(args.fixIssueIds);\n }\n if (typeof args.fix === "boolean") {\n normalized.fix = args.fix;\n }\n if (typeof args.loop === "boolean") {\n normalized.loop = args.loop;\n }\n\n return normalized;\n}\n\nfunction applyFixFlags(normalized, text) {\n const parsed = parseTrailingFixFlags(text);\n for (const flag of parsed.flags) {\n if (flag === "--fix") normalized.fix = true;\n else if (flag === "--no-fix") normalized.fix = false;\n else if (flag === "--loop") normalized.loop = true;\n else if (flag === "--no-loop") normalized.loop = false;\n }\n const target = parsed.target.trim().split(/ +/).join(" ");\n if (target) normalized.target = target;\n}\n\nfunction parseTrailingFixFlags(text) {\n const parts = String(text || "")\n .trim()\n .split(/ +/)\n .filter(Boolean);\n const flags = [];\n while (parts.length > 0) {\n const last = parts[parts.length - 1];\n if (last !== "--fix" && last !== "--no-fix" && last !== "--loop" && last !== "--no-loop") break;\n flags.unshift(last);\n parts.pop();\n }\n return { target: parts.join(" "), flags: flags };\n}\n\nfunction sanitizeStringArray(values) {\n const result = [];\n for (const value of values) {\n if (typeof value !== "string") continue;\n const trimmed = value.trim();\n if (trimmed && result.indexOf(trimmed) === -1) result.push(trimmed);\n }\n return result;\n}\n\nfunction shouldCollectGitReviewContext(input) {\n return input.includeGitContext || (!input.explicitFiles && !input.explicitDiff);\n}\n\nfunction collectGitReviewContext(action, input, log, stepSuffix) {\n const gitInput = buildGitActionInput(input);\n const failures = [];\n const builtInOnly = true;\n const status = tryGitAction(log, failures, "git.status", function () {\n return action.git.status({\n id: workflowStepId("git-status", stepSuffix),\n input: { includeIgnored: false },\n builtInOnly,\n }).output;\n });\n const changedFiles = tryGitAction(log, failures, "git.changedFiles", function () {\n return action.git.changedFiles({\n id: workflowStepId("git-changed-files", stepSuffix),\n input: gitInput,\n builtInOnly,\n }).output;\n });\n const diffStat = tryGitAction(log, failures, "git.diffStat", function () {\n return action.git.diffStat({\n id: workflowStepId("git-diff-stat", stepSuffix),\n input: gitInput,\n builtInOnly,\n }).output;\n });\n const diff = tryGitAction(log, failures, "git.diff", function () {\n return action.git.diff({\n id: workflowStepId("git-diff", stepSuffix),\n input: gitInput,\n builtInOnly,\n }).output;\n });\n const commits = tryGitAction(log, failures, "git.commitsBetween", function () {\n const commitsInput = copyGitActionInput(gitInput);\n commitsInput.limit = 20;\n return action.git.commitsBetween({\n id: workflowStepId("git-commits-between", stepSuffix),\n input: commitsInput,\n builtInOnly,\n }).output;\n });\n const context = {\n status: isObject(status) ? status : null,\n changedFiles: isObject(changedFiles) ? changedFiles : null,\n diffStat: isObject(diffStat) ? diffStat : null,\n diff: isObject(diff) ? diff : null,\n commits: isObject(commits) ? commits : null,\n failures: failures,\n explicitRefs: Boolean(input.baseRef || input.headRef),\n };\n if (!hasAnyGitContext(context)) {\n log("Git workflow actions unavailable; continuing with caller-provided review context", {\n failures: failures,\n });\n return null;\n }\n const files = collectGitFiles(context);\n log("Captured Git review context", {\n branch: context.status ? context.status.branch : "unknown",\n fileCount: files.length,\n hasDiff:\n context.diff != null &&\n (hasText(context.diff.branch) ||\n hasText(context.diff.staged) ||\n hasText(context.diff.unstaged)),\n failureCount: failures.length,\n });\n return context;\n}\n\nfunction tryGitAction(log, failures, name, fn) {\n try {\n return fn();\n } catch (error) {\n const failure = { action: name, error: formatError(error) };\n failures.push(failure);\n log("Git workflow action failed; continuing with partial review context", failure);\n return null;\n }\n}\n\nfunction hasAnyGitContext(gitContext) {\n return Boolean(\n gitContext.status ||\n gitContext.changedFiles ||\n gitContext.diffStat ||\n gitContext.diff ||\n gitContext.commits\n );\n}\n\nfunction hasResolvedBranchContext(gitContext) {\n return Boolean(\n hasResolvedGitRefContext(gitContext.changedFiles) ||\n hasResolvedGitRefContext(gitContext.diffStat) ||\n hasResolvedGitRefContext(gitContext.diff) ||\n hasResolvedGitRefContext(gitContext.commits)\n );\n}\n\nfunction hasResolvedGitRefContext(value) {\n return isObject(value) && hasText(value.base) && hasText(value.head) && hasText(value.mergeBase);\n}\n\nfunction isObject(value) {\n return value != null && typeof value === "object" && !Array.isArray(value);\n}\n\nfunction buildGitActionInput(input) {\n const gitInput = {};\n if (input.baseRef) gitInput.base = input.baseRef;\n if (input.headRef) gitInput.head = input.headRef;\n return gitInput;\n}\n\nfunction copyGitActionInput(input) {\n const copy = {};\n if (input.base) copy.base = input.base;\n if (input.head) copy.head = input.head;\n return copy;\n}\n\nfunction applyGitContextToReviewInput(input, gitContext) {\n if (gitContext == null) return;\n if ((input.explicitFiles || input.explicitDiff) && !input.includeGitContext) return;\n if (!input.explicitFiles) {\n const files = collectGitFiles(gitContext);\n if (files.length > 0) input.files = files;\n }\n if (!input.explicitDiff && gitContext.diff != null) {\n const diff = renderGitDiff(gitContext.diff);\n if (diff.length > 0) input.diff = truncateText(diff, 60000);\n }\n input.gitSnapshot = truncateText(renderGitSnapshot(gitContext), 20000);\n}\n\nfunction collectGitFiles(gitContext) {\n const files = [];\n if (gitContext == null) return files;\n if (gitContext.changedFiles != null) {\n addFileEntries(files, gitContext.changedFiles.branch);\n addFileEntries(files, gitContext.changedFiles.staged);\n addFileEntries(files, gitContext.changedFiles.unstaged);\n addFilePaths(files, gitContext.changedFiles.untracked);\n }\n if (gitContext.status != null) {\n addFileEntries(files, gitContext.status.staged);\n addFileEntries(files, gitContext.status.unstaged);\n addFilePaths(files, gitContext.status.untracked);\n }\n return files;\n}\n\nfunction addFileEntries(files, entries) {\n if (!Array.isArray(entries)) return;\n for (const entry of entries) {\n if (entry && typeof entry === "object") {\n addFilePath(files, entry.path);\n addFilePath(files, entry.oldPath);\n }\n }\n}\n\nfunction addFilePaths(files, paths) {\n if (!Array.isArray(paths)) return;\n for (const path of paths) {\n addFilePath(files, path);\n }\n}\n\nfunction addFilePath(files, path) {\n if (typeof path !== "string") return;\n const trimmed = path.trim();\n if (trimmed.length === 0 || files.indexOf(trimmed) !== -1) return;\n files.push(trimmed);\n}\n\nfunction renderGitSnapshot(gitContext) {\n const sections = [];\n if (gitContext.status != null) {\n const status = gitContext.status;\n sections.push(\n "Repository status: branch " +\n valueOrUnknown(status.branch) +\n (status.upstream ? " tracking " + status.upstream : "") +\n "; staged " +\n arrayLength(status.staged) +\n "; unstaged " +\n arrayLength(status.unstaged) +\n "; untracked " +\n arrayLength(status.untracked)\n );\n }\n if (gitContext.explicitRefs && !hasResolvedBranchContext(gitContext)) {\n sections.push(\n "WARNING: Requested base/head refs could not be resolved for automatic branch diff and commit capture; Git context may include only repository status or working-tree changes."\n );\n }\n if (Array.isArray(gitContext.failures) && gitContext.failures.length > 0) {\n sections.push(\n "Git context warnings:\\n" +\n gitContext.failures\n .map(function (failure) {\n return "- " + valueOrUnknown(failure.action) + ": " + valueOrUnknown(failure.error);\n })\n .join("\\n")\n );\n }\n const files = collectGitFiles(gitContext);\n if (files.length > 0) {\n sections.push("Changed files from parent workspace Git snapshot: " + files.join(", "));\n }\n if (gitContext.commits != null && Array.isArray(gitContext.commits.commits)) {\n const commits = gitContext.commits.commits;\n if (commits.length > 0) {\n sections.push(\n "Commits since " +\n valueOrUnknown(gitContext.commits.base) +\n ":\\n" +\n commits\n .map(function (commit) {\n return "- " + valueOrUnknown(commit.shortHash) + " " + valueOrUnknown(commit.subject);\n })\n .join("\\n")\n );\n }\n }\n if (gitContext.diffStat != null) {\n const statSections = [];\n if (hasText(gitContext.diffStat.branch))\n statSections.push("Branch diff stat:\\n" + gitContext.diffStat.branch);\n if (hasText(gitContext.diffStat.staged))\n statSections.push("Staged diff stat:\\n" + gitContext.diffStat.staged);\n if (hasText(gitContext.diffStat.unstaged))\n statSections.push("Unstaged diff stat:\\n" + gitContext.diffStat.unstaged);\n if (statSections.length > 0) sections.push(statSections.join("\\n\\n"));\n }\n if (gitContext.status != null && arrayLength(gitContext.status.untracked) > 0) {\n sections.push(\n "Untracked file contents are not included in the automatic diff snapshot; review agents only receive their paths unless the caller supplied args.diff."\n );\n }\n if (gitContext.diff != null && isDiffTruncated(gitContext.diff)) {\n sections.push(\n "One or more automatic diff sections were truncated by workflow action output limits."\n );\n }\n return sections.join("\\n\\n");\n}\n\nfunction renderGitDiff(diff) {\n const parts = [];\n if (hasText(diff.branch)) {\n parts.push(\n "Branch diff (" +\n valueOrUnknown(diff.base) +\n ".." +\n valueOrUnknown(diff.head) +\n ")\\n" +\n diff.branch\n );\n }\n if (hasText(diff.staged)) {\n parts.push("Staged diff\\n" + diff.staged);\n }\n if (hasText(diff.unstaged)) {\n parts.push("Unstaged diff\\n" + diff.unstaged);\n }\n if (isDiffTruncated(diff)) {\n parts.push("NOTE: one or more diff sections were truncated by workflow action output limits.");\n }\n return parts.join("\\n\\n");\n}\n\nfunction isDiffTruncated(diff) {\n return Boolean(\n diff &&\n diff.truncated &&\n (diff.truncated.branch || diff.truncated.staged || diff.truncated.unstaged)\n );\n}\n\nfunction truncateText(text, maxLength) {\n if (text.length <= maxLength) return text;\n return (\n text.slice(0, maxLength) +\n "\\n[truncated by deep-review-workflow after " +\n maxLength +\n " characters]"\n );\n}\n\nfunction hasText(value) {\n return typeof value === "string" && value.trim().length > 0;\n}\n\nfunction arrayLength(value) {\n return Array.isArray(value) ? value.length : 0;\n}\n\nfunction valueOrUnknown(value) {\n return typeof value === "string" && value.length > 0 ? value : "unknown";\n}\n\nfunction formatError(error) {\n return error && typeof error.message === "string" ? error.message : String(error);\n}\n\nfunction renderReviewInput(input) {\n return [\n "Target: " + input.target,\n input.baseRef ? "Base ref: " + input.baseRef : "",\n input.headRef ? "Head ref: " + input.headRef : "",\n input.files.length > 0 ? "Files: " + input.files.join(", ") : "",\n input.gitSnapshot ? "Git snapshot:\\n" + input.gitSnapshot : "",\n input.instructions ? "Reviewer instructions: " + input.instructions : "",\n input.diff ? "Diff snapshot:\\n~~~diff\\n" + input.diff + "\\n~~~" : "",\n ]\n .filter(Boolean)\n .join("\\n");\n}\n\nfunction selectReviewLanes(lanes) {\n const defaults = ["correctness", "tests", "architecture"];\n const allowed = {\n correctness: true,\n tests: true,\n architecture: true,\n "security-reliability": true,\n "ux-a11y": true,\n "docs-dx": true,\n };\n const requested = Array.isArray(lanes) && lanes.length > 0 ? lanes : defaults;\n const result = [];\n for (const lane of requested) {\n if (allowed[lane] && result.indexOf(lane) === -1) {\n result.push(lane);\n }\n }\n for (const fallback of defaults) {\n if (result.indexOf(fallback) === -1) {\n result.push(fallback);\n }\n }\n return result.slice(0, 6);\n}\n\nfunction lanePrompt(lane) {\n const prompts = {\n correctness:\n "Review for logic bugs, edge cases, races, state-machine violations, and broken invariants.",\n tests: "Review test coverage, determinism, missing regression tests, and validation commands.",\n architecture:\n "Review consistency with existing architecture, boundaries, naming, abstractions, and maintainability.",\n "security-reliability":\n "Review security, trust boundaries, path traversal, injection, data corruption, reliability, and performance risks.",\n "ux-a11y":\n "Review user-facing behavior, accessibility, keyboard flow, visual consistency, and empty/loading/error states.",\n "docs-dx":\n "Review documentation, developer experience, scripts, public API clarity, and migration concerns.",\n };\n return prompts[lane] || prompts.correctness;\n}\n\nfunction issueListSchema() {\n return {\n type: "object",\n required: ["issues"],\n additionalProperties: false,\n properties: {\n issues: {\n type: "array",\n items: issueSchema(),\n },\n },\n };\n}\n\nfunction issueSchema() {\n return {\n type: "object",\n required: [\n "id",\n "severity",\n "category",\n "title",\n "rationale",\n "evidence",\n "filePaths",\n "confidence",\n ],\n additionalProperties: false,\n properties: {\n id: { type: "string" },\n severity: { type: "string", enum: ["P0", "P1", "P2", "P3", "P4"] },\n category: { type: "string" },\n title: { type: "string" },\n rationale: { type: "string" },\n evidence: { type: "string" },\n filePaths: { type: "array", items: { type: "string" } },\n suggestedFix: { type: "string" },\n validation: { type: "string" },\n confidence: { type: "string", enum: ["low", "medium", "high"] },\n },\n };\n}\n\nfunction scopeSchema() {\n return {\n type: "object",\n required: ["summary", "files", "riskAreas", "lanes"],\n additionalProperties: false,\n properties: {\n summary: { type: "string" },\n files: { type: "array", items: { type: "string" } },\n riskAreas: { type: "array", items: { type: "string" } },\n lanes: {\n type: "array",\n items: {\n type: "string",\n enum: [\n "correctness",\n "tests",\n "architecture",\n "security-reliability",\n "ux-a11y",\n "docs-dx",\n ],\n },\n },\n },\n };\n}\n\nfunction verificationSchema() {\n return {\n type: "object",\n required: ["issueId", "verdict", "confidence", "rationale"],\n additionalProperties: false,\n properties: {\n issueId: { type: "string" },\n verdict: {\n type: "string",\n enum: ["valid", "duplicate", "overstated", "not-repro", "needs-info"],\n },\n confidence: { type: "string", enum: ["low", "medium", "high"] },\n rationale: { type: "string" },\n evidence: { type: "string" },\n suggestedSeverity: { type: "string", enum: ["P0", "P1", "P2", "P3", "P4"] },\n },\n };\n}\n\nfunction finalSynthesisSchema() {\n return {\n type: "object",\n required: ["verifiedIssueCount", "verifiedIssueIds", "risk", "validationPlan"],\n additionalProperties: false,\n properties: {\n verifiedIssueCount: { type: "number" },\n risk: { type: "string", enum: ["low", "medium", "high"] },\n validationPlan: { type: "array", items: { type: "string" } },\n verifiedIssueIds: { type: "array", items: { type: "string" } },\n discardedIssueCount: { type: "number" },\n },\n };\n}\n\nfunction flatten(arrays) {\n const out = [];\n for (const array of arrays) {\n for (const item of array) {\n out.push(item);\n }\n }\n return out;\n}\n', + '// description: Coordinate adversarial review agents to find, verify, and synthesize code review findings.\n//\n// Keep the lightweight /deep-review skill; this workflow is the heavier structured path with\n// adversarial verification for review findings.\n\n// Verification/fixer fan-out scales with maxCandidates/maxFixes (clamped at 20);\n// cap live agents so raising those budgets queues work instead of launching one\n// wave of 20 concurrent agents. Matches deep-research\'s smart-mode verifier cap.\nconst MAX_PARALLEL_AGENTS = 12;\nexport default function deepReviewWorkflow({\n args,\n phase,\n log,\n agent,\n action,\n parallelAgents,\n applyPatch,\n}) {\n const exploreAgentId = "explore";\n const reasoningAgentId = "exec";\n // Scope discovery stays on Explore; review judgment uses Exec for users with fast Explore defaults.\n const readOnlyReviewPrompt =\n "This is a read-only deep code review task. Do not edit files, create commits, apply patches, push branches, or open PRs. Inspect repository evidence only as needed and report findings.\\n\\n";\n const input = normalizeDeepReviewArgs(args);\n if (input.loop && !input.fix) {\n throw new Error("--loop requires --fix for deep-review-workflow");\n }\n if (input.loop) {\n return runDeepReviewLoop({\n input: input,\n phase: phase,\n log: log,\n agent: agent,\n action: action,\n parallelAgents: parallelAgents,\n applyPatch: applyPatch,\n exploreAgentId: exploreAgentId,\n reasoningAgentId: reasoningAgentId,\n readOnlyReviewPrompt: readOnlyReviewPrompt,\n });\n }\n return runDeepReviewPass({\n input: input,\n phase: phase,\n log: log,\n agent: agent,\n action: action,\n parallelAgents: parallelAgents,\n applyPatch: applyPatch,\n exploreAgentId: exploreAgentId,\n reasoningAgentId: reasoningAgentId,\n readOnlyReviewPrompt: readOnlyReviewPrompt,\n stepSuffix: "",\n iteration: 0,\n skipFixWhenNoVerifiedIssues: false,\n }).reviewResult;\n}\n\nfunction runDeepReviewLoop(context) {\n const passes = [];\n let stopReason = "";\n let remainingFixBudget = context.input.maxFixes;\n let loopHeadRef = context.input.headRef;\n for (let iteration = 1; iteration <= context.input.maxLoopIterations; iteration += 1) {\n context.phase("loop-iteration", {\n iteration: iteration,\n maxIterations: context.input.maxLoopIterations,\n remainingFixBudget: remainingFixBudget,\n });\n const budgetExhaustedReadOnlyCheck = remainingFixBudget <= 0;\n const iterationInput = cloneDeepReviewInput(context.input);\n iterationInput.headRef = loopHeadRef;\n iterationInput.maxFixes = remainingFixBudget;\n if (budgetExhaustedReadOnlyCheck) iterationInput.fix = false;\n const pass = runDeepReviewPass({\n input: iterationInput,\n phase: context.phase,\n log: context.log,\n agent: context.agent,\n action: context.action,\n parallelAgents: context.parallelAgents,\n applyPatch: context.applyPatch,\n exploreAgentId: context.exploreAgentId,\n reasoningAgentId: context.reasoningAgentId,\n readOnlyReviewPrompt: context.readOnlyReviewPrompt,\n stepSuffix: "loop-" + iteration,\n iteration: iteration,\n skipFixWhenNoVerifiedIssues: true,\n });\n passes.push(pass.reviewResult);\n if (budgetExhaustedReadOnlyCheck) {\n stopReason = hasVerifiedIssues(pass.reviewResult.structuredOutput.final)\n ? "fix-budget-exhausted"\n : "no-verified-issues";\n return buildLoopResult(context.input, passes, stopReason, remainingFixBudget);\n }\n const fixProgress = reviewResultHasFixProgress(pass.reviewResult);\n remainingFixBudget = Math.max(0, remainingFixBudget - countSelectedFixes(pass.reviewResult));\n if (fixProgress) loopHeadRef = "";\n stopReason = getLoopStopReason(pass.reviewResult, remainingFixBudget);\n if (stopReason === "fix-budget-exhausted" && iteration < context.input.maxLoopIterations)\n continue;\n if (stopReason) {\n return buildLoopResult(context.input, passes, stopReason, remainingFixBudget);\n }\n }\n return buildLoopResult(context.input, passes, "max-iterations", remainingFixBudget);\n}\n\nfunction runDeepReviewPass(context) {\n const input = cloneDeepReviewInput(context.input);\n const gitContext = shouldCollectGitReviewContext(input)\n ? collectGitReviewContext(context.action, input, context.log, context.stepSuffix)\n : null;\n applyGitContextToReviewInput(input, gitContext);\n const maxCandidates = input.maxCandidates;\n\n context.phase(\n "scope",\n withLoopIteration(\n {\n target: input.target,\n fileCount: input.files.length,\n hasDiffSnapshot: input.diff.length > 0,\n hasGitSnapshot: input.gitSnapshot.length > 0,\n },\n context.iteration\n )\n );\n const scope = context.agent({\n id: workflowStepId("scope-review-surface", context.stepSuffix),\n title: "Scope review surface",\n agentId: context.exploreAgentId,\n prompt:\n "Scope this code review. Identify changed files, likely intent, touched layers, highest-risk areas, and which review lanes should run. Use repository evidence; do not assume the diff is complete if refs are provided.\\n\\n" +\n renderReviewInput(input),\n outputSchema: scopeSchema(),\n });\n\n const lanes = selectReviewLanes(scope.structuredOutput.lanes);\n context.log("Selected deep review lanes", withLoopIteration({ lanes: lanes }, context.iteration));\n\n context.phase("lane-review", withLoopIteration({ lanes: lanes }, context.iteration));\n const laneReviews = context.parallelAgents(\n lanes.map(function (lane) {\n return {\n id: workflowStepId("review-" + lane, context.stepSuffix),\n title: "Review lane: " + lane,\n agentId: context.reasoningAgentId,\n prompt:\n context.readOnlyReviewPrompt +\n lanePrompt(lane) +\n "\\n\\nReview target:\\n" +\n renderReviewInput(input) +\n "\\n\\nScoped review surface:\\n" +\n JSON.stringify(scope.structuredOutput, null, 2) +\n "\\n\\nReturn only concrete, actionable findings with file paths and evidence. Prefer an empty issues array over speculative feedback.",\n outputSchema: issueListSchema(),\n };\n })\n );\n const laneIssues = flatten(\n laneReviews.map(function (review) {\n return review.structuredOutput.issues || [];\n })\n );\n context.log(\n "Lane review produced candidate issues",\n withLoopIteration({ count: laneIssues.length }, context.iteration)\n );\n\n if (laneIssues.length === 0) {\n context.log(\n "No candidate issues from review lanes; skipping triage, verification, and final synthesis",\n withLoopIteration(\n { skippedPhases: ["triage-dedupe", "adversarial-verification", "final-synthesis"] },\n context.iteration\n )\n );\n const reviewResult = buildNoVerifiedIssuesReviewResult({\n input: input,\n scope: scope.structuredOutput,\n laneIssues: laneIssues,\n triagedIssues: [],\n verifications: [],\n discardedIssueCount: 0,\n });\n return finishDeepReviewPass({\n context: context,\n input: input,\n reviewResult: reviewResult,\n gitContext: gitContext,\n candidates: [],\n verifications: [],\n final: reviewResult.structuredOutput.final,\n });\n }\n\n context.phase(\n "triage-dedupe",\n withLoopIteration({ candidateCount: laneIssues.length }, context.iteration)\n );\n const triage = context.agent({\n id: workflowStepId("triage-candidate-issues", context.stepSuffix),\n title: "Triage and dedupe review findings",\n agentId: context.reasoningAgentId,\n prompt:\n context.readOnlyReviewPrompt +\n "Deduplicate and consolidate these candidate code review findings. Merge duplicates across lanes (combining their evidence), normalize severity, and drop only clearly non-actionable items; keep borderline findings so adversarial verification can make the validity call. Order the issues by severity and impact, most severe first — only the first " +\n maxCandidates +\n " survive the candidate budget.\\n\\n" +\n "Review target:\\n" +\n renderReviewInput(input) +\n "\\n\\nCandidate issues:\\n" +\n JSON.stringify(laneIssues, null, 2),\n outputSchema: issueListSchema(),\n });\n // Triage is prompted to emit issues most-severe-first, but re-sort defensively so a\n // high-severity finding emitted past the budget cutoff is never silently dropped.\n const rankedIssues = sortIssuesBySeverity(triage.structuredOutput.issues || []);\n const candidates = rankedIssues.slice(0, maxCandidates);\n context.log(\n "Triaged candidate issues",\n withLoopIteration(\n {\n candidateCount: rankedIssues.length,\n selectedCount: candidates.length,\n droppedByBudget: rankedIssues.slice(candidates.length).map(function (issue, index) {\n return stableIssueId(issue, candidates.length + index);\n }),\n },\n context.iteration\n )\n );\n\n if (candidates.length === 0) {\n context.log(\n "No candidate issues after triage; skipping verification and final synthesis",\n withLoopIteration(\n { skippedPhases: ["adversarial-verification", "final-synthesis"] },\n context.iteration\n )\n );\n const reviewResult = buildNoVerifiedIssuesReviewResult({\n input: input,\n scope: scope.structuredOutput,\n laneIssues: laneIssues,\n triagedIssues: [],\n verifications: [],\n discardedIssueCount: laneIssues.length,\n });\n return finishDeepReviewPass({\n context: context,\n input: input,\n reviewResult: reviewResult,\n gitContext: gitContext,\n candidates: [],\n verifications: [],\n final: reviewResult.structuredOutput.final,\n });\n }\n\n context.phase(\n "adversarial-verification",\n withLoopIteration({ candidateCount: candidates.length }, context.iteration)\n );\n const verificationResults =\n candidates.length > 0\n ? context.parallelAgents(\n candidates.map(function (issue, index) {\n return {\n id: workflowStepId("verify-issue-" + index, context.stepSuffix),\n title: "Verify review finding " + (index + 1),\n agentId: context.reasoningAgentId,\n prompt:\n context.readOnlyReviewPrompt +\n "Adversarially verify this code review finding. Try to disprove it. Inspect relevant code paths and tests. Decide whether it is valid, duplicate, overstated, not reproducible, or needs more information.\\n\\n" +\n "Review target:\\n" +\n renderReviewInput(input) +\n "\\n\\nFinding:\\n" +\n JSON.stringify(issue, null, 2),\n outputSchema: verificationSchema(),\n };\n }),\n { maxParallel: MAX_PARALLEL_AGENTS }\n )\n : [];\n const verifications = verificationResults.map(function (verification) {\n return verification.structuredOutput;\n });\n context.log(\n "Verified candidate issues",\n withLoopIteration({ count: verifications.length }, context.iteration)\n );\n\n if (!hasValidVerification(verifications)) {\n context.log(\n "No verifier upheld a candidate issue; skipping final synthesis",\n withLoopIteration({ skippedPhases: ["final-synthesis"] }, context.iteration)\n );\n const reviewResult = buildNoVerifiedIssuesReviewResult({\n input: input,\n scope: scope.structuredOutput,\n laneIssues: laneIssues,\n triagedIssues: candidates,\n verifications: verifications,\n discardedIssueCount: candidates.length,\n });\n return finishDeepReviewPass({\n context: context,\n input: input,\n reviewResult: reviewResult,\n gitContext: gitContext,\n candidates: candidates,\n verifications: verifications,\n final: reviewResult.structuredOutput.final,\n });\n }\n\n context.phase(\n "final-synthesis",\n withLoopIteration(\n {\n candidateCount: candidates.length,\n verificationCount: verifications.length,\n },\n context.iteration\n )\n );\n const final = context.agent({\n id: workflowStepId("synthesize-review", context.stepSuffix),\n title: "Synthesize final deep review",\n agentId: context.reasoningAgentId,\n prompt:\n context.readOnlyReviewPrompt +\n "Write the final code review. Include only findings that remain actionable after adversarial verification. Use severity P0-P4, file paths, issue IDs, and concrete evidence. If there are no verified issues, say so clearly. Include questions and a validation plan. Set verifiedIssueIds to the issue IDs included in the final review when any are verified.\\n\\n" +\n "Scoped review surface:\\n" +\n JSON.stringify(scope.structuredOutput, null, 2) +\n "\\n\\nTriaged issues:\\n" +\n JSON.stringify(candidates, null, 2) +\n "\\n\\nVerification results:\\n" +\n JSON.stringify(verifications, null, 2),\n outputSchema: finalSynthesisSchema(),\n });\n\n const reviewResult = {\n reportMarkdown: final.reportMarkdown,\n structuredOutput: {\n target: input.target,\n scope: scope.structuredOutput,\n laneIssues: laneIssues,\n triagedIssues: candidates,\n verification: verifications,\n final: final.structuredOutput,\n },\n };\n return finishDeepReviewPass({\n context: context,\n input: input,\n reviewResult: reviewResult,\n gitContext: gitContext,\n candidates: candidates,\n verifications: verifications,\n final: final.structuredOutput,\n });\n}\n\nfunction buildNoVerifiedIssuesReviewResult(options) {\n return {\n reportMarkdown: "# Deep Review\\n\\nNo verified issues.",\n structuredOutput: {\n target: options.input.target,\n scope: options.scope,\n laneIssues: options.laneIssues,\n triagedIssues: options.triagedIssues,\n verification: options.verifications,\n final: {\n verifiedIssueCount: 0,\n verifiedIssueIds: [],\n risk: "low",\n validationPlan: [],\n discardedIssueCount: options.discardedIssueCount,\n },\n },\n };\n}\n\nfunction hasValidVerification(verifications) {\n for (const verification of verifications) {\n if (verification && verification.verdict === "valid") return true;\n }\n return false;\n}\n\nfunction finishDeepReviewPass(options) {\n const context = options.context;\n const reviewResult = options.reviewResult;\n if (!options.input.fix) return { reviewResult: reviewResult, gitContext: options.gitContext };\n if (context.skipFixWhenNoVerifiedIssues && !hasVerifiedIssues(options.final)) {\n return { reviewResult: reviewResult, gitContext: options.gitContext };\n }\n\n context.phase("fix-preflight", withLoopIteration({ requested: true }, context.iteration));\n const fixResult = runDeepReviewFix({\n input: options.input,\n action: context.action,\n log: context.log,\n agent: context.agent,\n parallelAgents: context.parallelAgents,\n applyPatch: context.applyPatch,\n candidates: options.candidates,\n verifications: options.verifications,\n final: options.final,\n gitContext: options.gitContext,\n exploreAgentId: context.exploreAgentId,\n reasoningAgentId: context.reasoningAgentId,\n stepSuffix: context.stepSuffix,\n });\n reviewResult.structuredOutput.fix = fixResult;\n reviewResult.reportMarkdown = reviewResult.reportMarkdown + renderFixMarkdown(fixResult);\n return { reviewResult: reviewResult, gitContext: options.gitContext };\n}\n\nfunction cloneDeepReviewInput(input) {\n return {\n target: input.target,\n baseRef: input.baseRef,\n headRef: input.headRef,\n diff: input.diff,\n files: input.files.slice(),\n instructions: input.instructions,\n gitSnapshot: input.gitSnapshot,\n explicitDiff: input.explicitDiff,\n explicitFiles: input.explicitFiles,\n includeGitContext: input.includeGitContext,\n maxCandidates: input.maxCandidates,\n fix: input.fix,\n loop: input.loop,\n maxFixes: input.maxFixes,\n maxLoopIterations: input.maxLoopIterations,\n fixIssueIds: input.fixIssueIds.slice(),\n };\n}\n\nfunction workflowStepId(baseId, suffix) {\n return suffix ? baseId + "-" + suffix : baseId;\n}\n\nfunction withLoopIteration(details, iteration) {\n if (iteration) details.iteration = iteration;\n return details;\n}\n\n// Stable sort: P0 first, unknown/invalid severity last, ties keep triage emit order.\nfunction sortIssuesBySeverity(issues) {\n return issues\n .map(function (issue, index) {\n return { issue: issue, index: index };\n })\n .sort(function (a, b) {\n const rankDelta = issueSeverityRank(a.issue) - issueSeverityRank(b.issue);\n return rankDelta !== 0 ? rankDelta : a.index - b.index;\n })\n .map(function (entry) {\n return entry.issue;\n });\n}\n\nfunction issueSeverityRank(issue) {\n const match =\n isObject(issue) && typeof issue.severity === "string"\n ? /^P([0-4])$/.exec(issue.severity)\n : null;\n return match ? Number(match[1]) : 5;\n}\n\nfunction hasVerifiedIssues(final) {\n if (!final) return false;\n if (typeof final.verifiedIssueCount === "number") return final.verifiedIssueCount > 0;\n return Array.isArray(final.verifiedIssueIds) && final.verifiedIssueIds.length > 0;\n}\n\nfunction reviewResultHasFixProgress(reviewResult) {\n const output = reviewResult && reviewResult.structuredOutput ? reviewResult.structuredOutput : {};\n const fix = output.fix;\n return Boolean(fix && fixHasProgress(fix));\n}\n\nfunction countSelectedFixes(reviewResult) {\n const output = reviewResult && reviewResult.structuredOutput ? reviewResult.structuredOutput : {};\n const fix = output.fix;\n return fix && Array.isArray(fix.selectedIssues) ? fix.selectedIssues.length : 0;\n}\n\nfunction getLoopStopReason(reviewResult, remainingFixBudget) {\n const output = reviewResult && reviewResult.structuredOutput ? reviewResult.structuredOutput : {};\n if (!hasVerifiedIssues(output.final)) return "no-verified-issues";\n const fix = output.fix;\n if (!fix) return "no-fix-attempted";\n if (fix.skippedReason) return "fix-skipped";\n if (!fix.selectedIssues || fix.selectedIssues.length === 0) return "no-fixable-issues";\n if (!fixHasProgress(fix)) return "no-fix-progress";\n const validationStatus = fix.validation ? fix.validation.status : "not-run";\n if (validationStatus === "failed") return "validation-failed";\n if (validationStatus !== "passed") return "validation-not-run";\n if (remainingFixBudget <= 0) return "fix-budget-exhausted";\n return "";\n}\n\nfunction fixHasProgress(fix) {\n return countStatus(fix.applications, "applied") > 0;\n}\n\nfunction buildLoopResult(input, passes, stopReason, remainingFixBudget) {\n const loop = {\n requested: true,\n completed: stopReason === "no-verified-issues",\n iterations: passes.length,\n maxIterations: input.maxLoopIterations,\n remainingFixBudget: remainingFixBudget,\n stopReason: stopReason,\n };\n const structuredOutput = {\n target: input.target,\n loop: loop,\n passes: passes.map(function (pass, index) {\n return {\n iteration: index + 1,\n result: pass.structuredOutput,\n };\n }),\n };\n if (passes.length > 0) {\n const latest = passes[passes.length - 1].structuredOutput;\n structuredOutput.latest = latest;\n structuredOutput.final = latest.final || null;\n if (latest.fix) structuredOutput.fix = latest.fix;\n }\n return {\n reportMarkdown: renderLoopMarkdown(passes, loop),\n structuredOutput: structuredOutput,\n };\n}\n\nfunction renderLoopMarkdown(passes, loop) {\n let markdown = "# Deep Review Loop\\n\\n";\n markdown += "- Iterations: " + loop.iterations + " / " + loop.maxIterations + "\\n";\n markdown += "- Stop reason: " + loop.stopReason + "\\n";\n markdown += "- Completed: " + (loop.completed ? "yes" : "no") + "\\n";\n for (let index = 0; index < passes.length; index += 1) {\n markdown += "\\n\\n---\\n\\n## Loop iteration " + (index + 1) + "\\n\\n";\n markdown += passes[index].reportMarkdown || "";\n }\n return markdown;\n}\n\nfunction runDeepReviewFix(context) {\n const input = context.input;\n const baseFix = {\n requested: true,\n selectedIssues: [],\n attempts: [],\n applications: [],\n resolutions: [],\n unresolved: [],\n };\n const preflight = collectFixPreflight(\n context.action,\n context.log,\n input,\n context.gitContext,\n context.stepSuffix\n );\n if (preflight.skippedReason) {\n baseFix.skippedReason = preflight.skippedReason;\n return baseFix;\n }\n let expectedHeadSha = preflight.expectedHeadSha;\n\n const selected = selectFixIssues(context.candidates, context.verifications, input, context.final);\n baseFix.selectedIssues = selected.map(function (item) {\n return summarizeFixIssue(item.issueId, item.issue);\n });\n context.log("Selected deep review fixes", {\n selectedCount: selected.length,\n skippedCount: context.candidates.length - selected.length,\n });\n if (selected.length === 0) return baseFix;\n\n const fixerResults = context.parallelAgents(\n selected.map(function (item, index) {\n return {\n id: workflowStepId("fix-issue-" + index, context.stepSuffix),\n title: "Fix verified review finding " + (index + 1),\n agentId: context.reasoningAgentId,\n prompt: buildFixPrompt(input, item),\n outputSchema: fixAttemptSchema(),\n };\n }),\n { maxParallel: MAX_PARALLEL_AGENTS }\n );\n\n const integratedIssues = [];\n for (let index = 0; index < selected.length; index += 1) {\n const item = selected[index];\n const fixerResult = fixerResults[index];\n const attempt = summarizeFixAttempt(item.issueId, fixerResult);\n baseFix.attempts.push(attempt);\n const attemptOutput =\n fixerResult && fixerResult.structuredOutput ? fixerResult.structuredOutput : {};\n if (!matchesReportedIssueId(attemptOutput, item.issueId)) {\n baseFix.unresolved.push({\n issueId: item.issueId,\n reason: issueIdMismatchReason("fixer", item.issueId, attemptOutput),\n });\n continue;\n }\n if (attemptOutput.status !== "fixed" || attemptOutput.commitCreated !== true) {\n if (attemptOutput.status !== "already-fixed") {\n baseFix.unresolved.push({\n issueId: item.issueId,\n reason: attemptOutput.status || "not-fixed",\n });\n }\n continue;\n }\n\n const application = safeApplyPatch(\n context.applyPatch,\n workflowStepId("apply-fix-" + index, context.stepSuffix),\n fixerResult,\n expectedHeadSha\n );\n application.issueId = item.issueId;\n baseFix.applications.push(application);\n if (application.status === "applied") {\n expectedHeadSha = getAppliedHeadCommitSha(application) || expectedHeadSha;\n integratedIssues.push(item.issueId);\n continue;\n }\n if (application.status !== "conflict") {\n baseFix.unresolved.push({\n issueId: item.issueId,\n reason: application.error || application.status,\n });\n continue;\n }\n\n const resolver = context.agent({\n id: workflowStepId("resolve-fix-" + index + "-conflict", context.stepSuffix),\n title: "Resolve auto-fix conflict " + (index + 1),\n agentId: context.reasoningAgentId,\n prompt: buildResolverPrompt(input, item, fixerResult, application, integratedIssues),\n outputSchema: fixResolverSchema(),\n });\n const resolution = summarizeResolution(item.issueId, resolver);\n baseFix.resolutions.push(resolution);\n const resolutionOutput = resolver && resolver.structuredOutput ? resolver.structuredOutput : {};\n if (!matchesReportedIssueId(resolutionOutput, item.issueId)) {\n baseFix.unresolved.push({\n issueId: item.issueId,\n reason: issueIdMismatchReason("resolver", item.issueId, resolutionOutput),\n });\n continue;\n }\n if (resolutionOutput.status === "already-resolved") {\n integratedIssues.push(item.issueId);\n continue;\n }\n if (resolutionOutput.status === "resolved" && resolutionOutput.commitCreated === true) {\n const resolvedApplication = safeApplyPatch(\n context.applyPatch,\n workflowStepId("apply-resolved-fix-" + index, context.stepSuffix),\n resolver,\n expectedHeadSha\n );\n resolvedApplication.issueId = item.issueId;\n resolution.applyStatus = resolvedApplication.status;\n baseFix.applications.push(resolvedApplication);\n if (resolvedApplication.status === "applied") {\n expectedHeadSha = getAppliedHeadCommitSha(resolvedApplication) || expectedHeadSha;\n integratedIssues.push(item.issueId);\n } else {\n baseFix.unresolved.push({\n issueId: item.issueId,\n reason: resolvedApplication.error || resolvedApplication.status,\n });\n }\n } else {\n baseFix.unresolved.push({\n issueId: item.issueId,\n reason: resolutionOutput.status || "unresolved-conflict",\n });\n }\n }\n\n if (integratedIssues.length > 0) {\n const validation = context.agent({\n id: workflowStepId("validate-auto-fixes", context.stepSuffix),\n title: "Validate applied auto-fixes",\n agentId: context.exploreAgentId,\n prompt: buildValidationPrompt(input, context.final, baseFix),\n outputSchema: fixValidationSchema(),\n });\n baseFix.validation = validation.structuredOutput;\n }\n return baseFix;\n}\n\nfunction collectFixPreflight(action, log, input, gitContext, stepSuffix) {\n if (input.explicitDiff)\n return {\n skippedReason: "auto-fix requires a local current workspace target, not an explicit diff",\n };\n if (looksNonLocalTarget(input.target))\n return { skippedReason: "auto-fix requires a local current workspace target" };\n let status = null;\n try {\n status = action.git.status({\n id: workflowStepId("fix-git-status", stepSuffix),\n input: { includeIgnored: false, head: input.headRef || "HEAD" },\n builtInOnly: true,\n cache: false,\n }).output;\n } catch (error) {\n const message = formatError(error);\n log("Git status unavailable for auto-fix preflight", { error: message });\n return { skippedReason: "auto-fix requires a fresh local Git status" };\n }\n if (!isObject(status)) return { skippedReason: "auto-fix requires a fresh local Git status" };\n if (!isCurrentReviewHead(input, status)) {\n return {\n skippedReason: "auto-fix requires the reviewed head ref to be the current checked-out branch",\n };\n }\n const reviewedSnapshot = getReviewedGitSnapshot(gitContext);\n if (!reviewedSnapshot) {\n return { skippedReason: "auto-fix requires a reviewed Git branch and HEAD snapshot" };\n }\n if (!matchesReviewedGitBranch(reviewedSnapshot, status)) {\n return {\n skippedReason: "auto-fix requires the current Git branch to match the reviewed snapshot",\n };\n }\n if (\n arrayLength(status.staged) > 0 ||\n arrayLength(status.unstaged) > 0 ||\n arrayLength(status.untracked) > 0\n ) {\n return { skippedReason: "auto-fix requires a clean committed local worktree" };\n }\n return { status: status, expectedHeadSha: reviewedSnapshot.headSha };\n}\n\nfunction isCurrentReviewHead(input, status) {\n if (!input.headRef) return true;\n const headRef = String(input.headRef).trim();\n if (!headRef || headRef === "HEAD") return true;\n const branch = normalizedGitBranch(status.branch);\n const currentBranchRef = branch ? "refs/heads/" + branch : "";\n if (branch && (headRef === branch || headRef === currentBranchRef)) return true;\n const requestedHeadRef =\n typeof status.requestedHeadRef === "string" ? status.requestedHeadRef : "";\n if (requestedHeadRef.length > 0) return false;\n if (!isExplicitCommitShaRef(headRef)) return false;\n const headSha = typeof status.headSha === "string" ? status.headSha : "";\n const requestedHeadSha =\n typeof status.requestedHeadSha === "string" ? status.requestedHeadSha : "";\n return Boolean(headSha && requestedHeadSha && headSha === requestedHeadSha);\n}\n\nfunction isExplicitCommitShaRef(headRef) {\n return /^[0-9a-fA-F]{7,64}$/.test(headRef);\n}\n\nfunction getReviewedGitSnapshot(gitContext) {\n const reviewedStatus = gitContext && isObject(gitContext.status) ? gitContext.status : null;\n if (!reviewedStatus) return null;\n const branch = normalizedGitBranch(reviewedStatus.branch);\n const headSha = typeof reviewedStatus.headSha === "string" ? reviewedStatus.headSha : "";\n if (!branch || !headSha) return null;\n return { branch: branch, headSha: headSha };\n}\n\nfunction matchesReviewedGitBranch(reviewedSnapshot, status) {\n const currentBranch = normalizedGitBranch(status.branch);\n return currentBranch.length > 0 && currentBranch === reviewedSnapshot.branch;\n}\n\nfunction normalizedGitBranch(branch) {\n if (typeof branch !== "string") return "";\n const trimmed = branch.trim();\n if (!trimmed || trimmed === "HEAD (no branch)") return "";\n return trimmed;\n}\n\nfunction looksNonLocalTarget(target) {\n const text = String(target || "").toLowerCase();\n return (\n text.indexOf("http://") !== -1 ||\n text.indexOf("https://") !== -1 ||\n /(^|\\s)(pr|pull request)\\s*#?\\d+/.test(text)\n );\n}\n\nfunction selectFixIssues(candidates, verifications, input, final) {\n const selected = [];\n const allowedIds = input.fixIssueIds || [];\n const finalFilter = getFinalIssueFilter(final);\n if (finalFilter.kind === "none") return selected;\n for (let index = 0; index < candidates.length; index += 1) {\n const issue = candidates[index];\n const issueId = stableIssueId(issue, index);\n if (finalFilter.kind === "ids" && finalFilter.ids[issueId] !== true) continue;\n if (allowedIds.length > 0 && allowedIds.indexOf(issueId) === -1) continue;\n const verification = findVerificationForIssue(issue, issueId, index, verifications);\n if (!verification || verification.verdict !== "valid" || verification.confidence === "low")\n continue;\n selected.push({ issueId: issueId, issue: issue, verification: verification, index: index });\n if (selected.length >= input.maxFixes) break;\n }\n return selected;\n}\n\nfunction getFinalIssueFilter(final) {\n if (final && final.verifiedIssueCount === 0) return { kind: "none" };\n if (final && Array.isArray(final.verifiedIssueIds)) {\n const ids = sanitizeStringArray(final.verifiedIssueIds);\n if (ids.length === 0) return { kind: "none" };\n const map = {};\n for (const id of ids) map[id] = true;\n return { kind: "ids", ids: map };\n }\n return { kind: "none" };\n}\n\nfunction stableIssueId(issue, index) {\n return issue && typeof issue.id === "string" && issue.id.trim()\n ? issue.id.trim()\n : "triaged-" + index;\n}\n\nfunction findVerificationForIssue(issue, issueId, index, verifications) {\n for (const verification of verifications) {\n if (!hasNonEmptyIssueId(verification)) continue;\n if (verification.issueId === issueId) return verification;\n if (issue && typeof issue.id === "string" && verification.issueId === issue.id)\n return verification;\n }\n return null;\n}\n\nfunction hasNonEmptyIssueId(verification) {\n return (\n verification &&\n typeof verification.issueId === "string" &&\n verification.issueId.trim().length > 0\n );\n}\n\nfunction matchesReportedIssueId(output, expectedIssueId) {\n return output && output.issueId === expectedIssueId;\n}\n\nfunction issueIdMismatchReason(source, expectedIssueId, output) {\n const reported =\n output && typeof output.issueId === "string" && output.issueId.length > 0\n ? output.issueId\n : "";\n return source + " reported issueId " + reported + " for " + expectedIssueId;\n}\n\nfunction summarizeFixIssue(issueId, issue) {\n return {\n issueId: issueId,\n severity: issue && issue.severity ? issue.severity : "",\n title: issue && issue.title ? issue.title : "",\n filePaths: issue && Array.isArray(issue.filePaths) ? issue.filePaths : [],\n };\n}\n\nfunction summarizeFixAttempt(issueId, result) {\n const output = result && result.structuredOutput ? result.structuredOutput : {};\n return {\n issueId: issueId,\n taskId: result ? result.taskId : undefined,\n status: output.status || "unknown",\n summary: output.summary || "",\n validation: Array.isArray(output.validation) ? output.validation : [],\n };\n}\n\nfunction summarizeResolution(issueId, result) {\n const output = result && result.structuredOutput ? result.structuredOutput : {};\n return {\n issueId: issueId,\n resolverTaskId: result ? result.taskId : undefined,\n status: output.status || "unknown",\n summary: output.summary || "",\n };\n}\n\nfunction safeApplyPatch(applyPatch, id, source, expectedHeadSha) {\n try {\n const spec = { id: id, source: source, target: "parent", onConflict: "return" };\n if (expectedHeadSha) spec.expectedHeadSha = expectedHeadSha;\n const result = applyPatch(spec);\n return normalizePatchApplication(source, result);\n } catch (error) {\n return {\n sourceTaskId: source ? source.taskId : undefined,\n status: "failed",\n error: formatError(error),\n };\n }\n}\n\nfunction normalizePatchApplication(source, result) {\n const status =\n result && result.status\n ? result.status\n : result && result.success === true\n ? "applied"\n : "failed";\n return {\n sourceTaskId: source ? source.taskId : undefined,\n status: status,\n appliedCommits: result ? result.appliedCommits : undefined,\n headCommitSha: result ? result.headCommitSha : undefined,\n conflictPaths: result ? result.conflictPaths : undefined,\n failedPatchSubject: result ? result.failedPatchSubject : undefined,\n error: result ? result.error : undefined,\n projectResults: result ? result.projectResults : undefined,\n };\n}\n\nfunction getAppliedHeadCommitSha(application) {\n if (\n application &&\n typeof application.headCommitSha === "string" &&\n application.headCommitSha.length > 0\n ) {\n return application.headCommitSha;\n }\n const projectResults =\n application && Array.isArray(application.projectResults) ? application.projectResults : [];\n for (let index = projectResults.length - 1; index >= 0; index -= 1) {\n const projectResult = projectResults[index];\n if (\n projectResult &&\n typeof projectResult.headCommitSha === "string" &&\n projectResult.headCommitSha.length > 0\n ) {\n return projectResult.headCommitSha;\n }\n }\n return "";\n}\n\nfunction buildFixPrompt(input, item) {\n return (\n "Fix exactly one verified deep-review finding. Make minimal code changes, add or update behavioral tests when appropriate, run targeted validation when practical, and create one or more git commits before reporting if code changed. If already fixed, not fixable, or more information is needed, report that status instead of inventing a patch. Do not push, open PRs, or perform unrelated cleanup.\\n\\n" +\n "Review target:\\n" +\n renderReviewInput(input) +\n "\\n\\nIssue ID: " +\n item.issueId +\n "\\nFinding:\\n" +\n JSON.stringify(item.issue, null, 2) +\n "\\n\\nVerification:\\n" +\n JSON.stringify(item.verification, null, 2)\n );\n}\n\nfunction buildResolverPrompt(input, item, fixerResult, application, integratedIssues) {\n return (\n "Resolve the git-am conflict for one auto-fix patch. Preserve the original issue intent and avoid unrelated changes. Replay the original patch with task_apply_git_patch in your workspace using dry_run false, resolve conflicts, git add, git am --continue, and commit follow-up resolved changes if needed. If earlier fixes already solved the issue, report already-resolved without inventing changes. Do not push or open PRs.\\n\\n" +\n "Review target:\\n" +\n renderReviewInput(input) +\n "\\n\\nIssue:\\n" +\n JSON.stringify(item.issue, null, 2) +\n "\\n\\nVerification:\\n" +\n JSON.stringify(item.verification, null, 2) +\n "\\n\\nFailing fixer task ID: " +\n (fixerResult ? fixerResult.taskId : "unknown") +\n "\\nApply conflict:\\n" +\n JSON.stringify(application, null, 2) +\n "\\nAlready integrated issue IDs:\\n" +\n JSON.stringify(integratedIssues, null, 2)\n );\n}\n\nfunction buildValidationPrompt(input, final, fixResult) {\n return (\n "Validate the auto-fixes now integrated in the parent workspace. Run the review validation plan and targeted tests/checks relevant to applied fixes. Do not edit files, create commits, apply patches, push, or open PRs. Report pass, fail, or not-run with commands and key failures.\\n\\n" +\n "Review target:\\n" +\n renderReviewInput(input) +\n "\\n\\nReview validation plan:\\n" +\n JSON.stringify(final.validationPlan || [], null, 2) +\n "\\n\\nAuto-fix result so far:\\n" +\n JSON.stringify(fixResult, null, 2)\n );\n}\n\nfunction renderFixMarkdown(fix) {\n const appliedCount =\n countStatus(fix.applications, "applied") +\n countStatus(fix.attempts, "already-fixed") +\n countStatus(fix.resolutions, "already-resolved");\n const conflictResolvedCount = countResolvedConflicts(fix.resolutions);\n const validationStatus = fix.validation ? fix.validation.status : "not-run";\n let markdown = "\\n\\n---\\n\\n## Auto-fix results\\n\\n";\n if (fix.skippedReason) {\n markdown += "- Skipped: " + fix.skippedReason + "\\n";\n return markdown;\n }\n markdown += "- Selected: " + fix.selectedIssues.length + " verified findings\\n";\n markdown += "- Fixed/applied: " + appliedCount + "\\n";\n markdown +=\n "- Already fixed/resolved: " +\n (countStatus(fix.attempts, "already-fixed") +\n countStatus(fix.resolutions, "already-resolved")) +\n "\\n";\n markdown += "- Not fixed: " + fix.unresolved.length + "\\n";\n markdown += "- Conflicts resolved: " + conflictResolvedCount + "\\n";\n markdown += "- Validation: " + validationStatus + "\\n";\n markdown += renderFixFailureDetails(fix);\n return markdown;\n}\n\nfunction countResolvedConflicts(resolutions) {\n let count = 0;\n for (const resolution of resolutions || []) {\n if (!resolution) continue;\n if (resolution.status === "already-resolved") count += 1;\n else if (resolution.status === "resolved" && resolution.applyStatus === "applied") count += 1;\n }\n return count;\n}\n\nfunction renderFixFailureDetails(fix) {\n let markdown = "";\n if (fix.unresolved && fix.unresolved.length > 0) {\n markdown += "\\nUnresolved issues:\\n";\n for (const unresolved of fix.unresolved) {\n markdown +=\n "- " + renderFixIssueLabel(fix, unresolved.issueId) + ": " + unresolved.reason + "\\n";\n }\n }\n const failedApplications = (fix.applications || []).filter(function (application) {\n return application && application.status !== "applied";\n });\n if (failedApplications.length > 0) {\n markdown += "\\nPatch application details:\\n";\n for (const application of failedApplications) {\n const details = renderPatchApplicationDetails(application);\n markdown +=\n "- " +\n renderFixIssueLabel(fix, application.issueId) +\n ": " +\n application.status +\n (details ? " — " + details : "") +\n "\\n";\n }\n }\n if (\n fix.validation &&\n Array.isArray(fix.validation.failures) &&\n fix.validation.failures.length > 0\n ) {\n markdown += "\\nValidation failures:\\n";\n for (const failure of fix.validation.failures) {\n markdown += "- " + failure + "\\n";\n }\n }\n return markdown;\n}\n\nfunction renderPatchApplicationDetails(application) {\n const details = [];\n if (application.conflictPaths && application.conflictPaths.length > 0)\n details.push("conflicts: " + application.conflictPaths.join(", "));\n if (application.failedPatchSubject)\n details.push("failed patch: " + application.failedPatchSubject);\n if (application.error) details.push(application.error);\n return details.join("; ");\n}\n\nfunction renderFixIssueLabel(fix, issueId) {\n for (const issue of fix.selectedIssues || []) {\n if (issue && issue.issueId === issueId && issue.title)\n return issueId + " (" + issue.title + ")";\n }\n return issueId;\n}\n\nfunction countStatus(items, status) {\n let count = 0;\n for (const item of items || []) {\n if (item && item.status === status) count += 1;\n }\n return count;\n}\n\nfunction fixAttemptSchema() {\n return {\n type: "object",\n required: ["issueId", "status", "summary", "validation", "commitCreated"],\n additionalProperties: false,\n properties: {\n issueId: { type: "string" },\n status: { type: "string", enum: ["fixed", "already-fixed", "not-fixable", "needs-info"] },\n summary: { type: "string" },\n validation: { type: "array", items: { type: "string" } },\n commitCreated: { type: "boolean" },\n },\n };\n}\n\nfunction fixResolverSchema() {\n return {\n type: "object",\n required: ["issueId", "status", "summary", "validation", "commitCreated"],\n additionalProperties: false,\n properties: {\n issueId: { type: "string" },\n status: { type: "string", enum: ["resolved", "already-resolved", "unresolved"] },\n summary: { type: "string" },\n validation: { type: "array", items: { type: "string" } },\n commitCreated: { type: "boolean" },\n },\n };\n}\n\nfunction fixValidationSchema() {\n return {\n type: "object",\n required: ["status", "commands", "summary", "failures"],\n additionalProperties: false,\n properties: {\n status: { type: "string", enum: ["passed", "failed", "not-run"] },\n commands: { type: "array", items: { type: "string" } },\n summary: { type: "string" },\n failures: { type: "array", items: { type: "string" } },\n },\n };\n}\n\nfunction normalizeDeepReviewArgs(args) {\n const normalized = {\n target: "current workspace changes",\n baseRef: "",\n headRef: "",\n diff: "",\n files: [],\n instructions: "",\n gitSnapshot: "",\n explicitDiff: false,\n explicitFiles: false,\n includeGitContext: false,\n maxCandidates: 12,\n fix: false,\n loop: false,\n maxFixes: 5,\n maxLoopIterations: 5,\n fixIssueIds: [],\n };\n\n if (typeof args === "string" && args.trim()) {\n applyFixFlags(normalized, args.trim());\n return normalized;\n }\n\n if (!args || typeof args !== "object") {\n return normalized;\n }\n\n let textualTarget = "";\n if (typeof args.target === "string" && args.target.trim()) textualTarget = args.target.trim();\n else if (typeof args.input === "string" && args.input.trim()) textualTarget = args.input.trim();\n else if (typeof args.pr === "string" && args.pr.trim()) textualTarget = args.pr.trim();\n else if (typeof args.branch === "string" && args.branch.trim())\n textualTarget = args.branch.trim();\n if (textualTarget) applyFixFlags(normalized, textualTarget);\n\n if (typeof args.baseRef === "string") normalized.baseRef = args.baseRef.trim();\n else if (typeof args.base === "string") normalized.baseRef = args.base.trim();\n\n if (typeof args.headRef === "string") normalized.headRef = args.headRef.trim();\n else if (typeof args.head === "string") normalized.headRef = args.head.trim();\n\n if (typeof args.diff === "string" && args.diff.trim().length > 0) {\n normalized.diff = args.diff;\n normalized.explicitDiff = true;\n }\n if (typeof args.instructions === "string") normalized.instructions = args.instructions.trim();\n else if (typeof args.notes === "string") normalized.instructions = args.notes.trim();\n\n if (Array.isArray(args.files)) {\n normalized.files = args.files\n .filter(function (file) {\n return typeof file === "string" && file.trim().length > 0;\n })\n .map(function (file) {\n return file.trim();\n });\n normalized.explicitFiles = normalized.files.length > 0;\n }\n\n if (typeof args.includeGitContext === "boolean") {\n normalized.includeGitContext = args.includeGitContext;\n }\n\n if (typeof args.maxCandidates === "number" && args.maxCandidates > 0) {\n normalized.maxCandidates = Math.min(20, Math.max(1, Math.floor(args.maxCandidates)));\n }\n\n if (typeof args.maxFixes === "number" && args.maxFixes > 0) {\n normalized.maxFixes = Math.min(20, Math.max(1, Math.floor(args.maxFixes)));\n }\n if (typeof args.maxLoopIterations === "number" && args.maxLoopIterations > 0) {\n normalized.maxLoopIterations = Math.min(10, Math.max(1, Math.floor(args.maxLoopIterations)));\n }\n if (Array.isArray(args.fixIssueIds)) {\n normalized.fixIssueIds = sanitizeStringArray(args.fixIssueIds);\n }\n if (typeof args.fix === "boolean") {\n normalized.fix = args.fix;\n }\n if (typeof args.loop === "boolean") {\n normalized.loop = args.loop;\n }\n\n return normalized;\n}\n\nfunction applyFixFlags(normalized, text) {\n const parsed = parseTrailingFixFlags(text);\n for (const flag of parsed.flags) {\n if (flag === "--fix") normalized.fix = true;\n else if (flag === "--no-fix") normalized.fix = false;\n else if (flag === "--loop") normalized.loop = true;\n else if (flag === "--no-loop") normalized.loop = false;\n }\n const target = parsed.target.trim().split(/ +/).join(" ");\n if (target) normalized.target = target;\n}\n\nfunction parseTrailingFixFlags(text) {\n const parts = String(text || "")\n .trim()\n .split(/ +/)\n .filter(Boolean);\n const flags = [];\n while (parts.length > 0) {\n const last = parts[parts.length - 1];\n if (last !== "--fix" && last !== "--no-fix" && last !== "--loop" && last !== "--no-loop") break;\n flags.unshift(last);\n parts.pop();\n }\n return { target: parts.join(" "), flags: flags };\n}\n\nfunction sanitizeStringArray(values) {\n const result = [];\n for (const value of values) {\n if (typeof value !== "string") continue;\n const trimmed = value.trim();\n if (trimmed && result.indexOf(trimmed) === -1) result.push(trimmed);\n }\n return result;\n}\n\nfunction shouldCollectGitReviewContext(input) {\n return input.includeGitContext || (!input.explicitFiles && !input.explicitDiff);\n}\n\nfunction collectGitReviewContext(action, input, log, stepSuffix) {\n const gitInput = buildGitActionInput(input);\n const failures = [];\n const builtInOnly = true;\n const status = tryGitAction(log, failures, "git.status", function () {\n return action.git.status({\n id: workflowStepId("git-status", stepSuffix),\n input: { includeIgnored: false },\n builtInOnly,\n }).output;\n });\n const changedFiles = tryGitAction(log, failures, "git.changedFiles", function () {\n return action.git.changedFiles({\n id: workflowStepId("git-changed-files", stepSuffix),\n input: gitInput,\n builtInOnly,\n }).output;\n });\n const diffStat = tryGitAction(log, failures, "git.diffStat", function () {\n return action.git.diffStat({\n id: workflowStepId("git-diff-stat", stepSuffix),\n input: gitInput,\n builtInOnly,\n }).output;\n });\n const diff = tryGitAction(log, failures, "git.diff", function () {\n return action.git.diff({\n id: workflowStepId("git-diff", stepSuffix),\n input: gitInput,\n builtInOnly,\n }).output;\n });\n const commits = tryGitAction(log, failures, "git.commitsBetween", function () {\n const commitsInput = copyGitActionInput(gitInput);\n commitsInput.limit = 20;\n return action.git.commitsBetween({\n id: workflowStepId("git-commits-between", stepSuffix),\n input: commitsInput,\n builtInOnly,\n }).output;\n });\n const context = {\n status: isObject(status) ? status : null,\n changedFiles: isObject(changedFiles) ? changedFiles : null,\n diffStat: isObject(diffStat) ? diffStat : null,\n diff: isObject(diff) ? diff : null,\n commits: isObject(commits) ? commits : null,\n failures: failures,\n explicitRefs: Boolean(input.baseRef || input.headRef),\n };\n if (!hasAnyGitContext(context)) {\n log("Git workflow actions unavailable; continuing with caller-provided review context", {\n failures: failures,\n });\n return null;\n }\n const files = collectGitFiles(context);\n log("Captured Git review context", {\n branch: context.status ? context.status.branch : "unknown",\n fileCount: files.length,\n hasDiff:\n context.diff != null &&\n (hasText(context.diff.branch) ||\n hasText(context.diff.staged) ||\n hasText(context.diff.unstaged)),\n failureCount: failures.length,\n });\n return context;\n}\n\nfunction tryGitAction(log, failures, name, fn) {\n try {\n return fn();\n } catch (error) {\n const failure = { action: name, error: formatError(error) };\n failures.push(failure);\n log("Git workflow action failed; continuing with partial review context", failure);\n return null;\n }\n}\n\nfunction hasAnyGitContext(gitContext) {\n return Boolean(\n gitContext.status ||\n gitContext.changedFiles ||\n gitContext.diffStat ||\n gitContext.diff ||\n gitContext.commits\n );\n}\n\nfunction hasResolvedBranchContext(gitContext) {\n return Boolean(\n hasResolvedGitRefContext(gitContext.changedFiles) ||\n hasResolvedGitRefContext(gitContext.diffStat) ||\n hasResolvedGitRefContext(gitContext.diff) ||\n hasResolvedGitRefContext(gitContext.commits)\n );\n}\n\nfunction hasResolvedGitRefContext(value) {\n return isObject(value) && hasText(value.base) && hasText(value.head) && hasText(value.mergeBase);\n}\n\nfunction isObject(value) {\n return value != null && typeof value === "object" && !Array.isArray(value);\n}\n\nfunction buildGitActionInput(input) {\n const gitInput = {};\n if (input.baseRef) gitInput.base = input.baseRef;\n if (input.headRef) gitInput.head = input.headRef;\n return gitInput;\n}\n\nfunction copyGitActionInput(input) {\n const copy = {};\n if (input.base) copy.base = input.base;\n if (input.head) copy.head = input.head;\n return copy;\n}\n\nfunction applyGitContextToReviewInput(input, gitContext) {\n if (gitContext == null) return;\n if ((input.explicitFiles || input.explicitDiff) && !input.includeGitContext) return;\n if (!input.explicitFiles) {\n const files = collectGitFiles(gitContext);\n if (files.length > 0) input.files = files;\n }\n if (!input.explicitDiff && gitContext.diff != null) {\n const diff = renderGitDiff(gitContext.diff);\n if (diff.length > 0) input.diff = truncateText(diff, 60000);\n }\n input.gitSnapshot = truncateText(renderGitSnapshot(gitContext), 20000);\n}\n\nfunction collectGitFiles(gitContext) {\n const files = [];\n if (gitContext == null) return files;\n if (gitContext.changedFiles != null) {\n addFileEntries(files, gitContext.changedFiles.branch);\n addFileEntries(files, gitContext.changedFiles.staged);\n addFileEntries(files, gitContext.changedFiles.unstaged);\n addFilePaths(files, gitContext.changedFiles.untracked);\n }\n if (gitContext.status != null) {\n addFileEntries(files, gitContext.status.staged);\n addFileEntries(files, gitContext.status.unstaged);\n addFilePaths(files, gitContext.status.untracked);\n }\n return files;\n}\n\nfunction addFileEntries(files, entries) {\n if (!Array.isArray(entries)) return;\n for (const entry of entries) {\n if (entry && typeof entry === "object") {\n addFilePath(files, entry.path);\n addFilePath(files, entry.oldPath);\n }\n }\n}\n\nfunction addFilePaths(files, paths) {\n if (!Array.isArray(paths)) return;\n for (const path of paths) {\n addFilePath(files, path);\n }\n}\n\nfunction addFilePath(files, path) {\n if (typeof path !== "string") return;\n const trimmed = path.trim();\n if (trimmed.length === 0 || files.indexOf(trimmed) !== -1) return;\n files.push(trimmed);\n}\n\nfunction renderGitSnapshot(gitContext) {\n const sections = [];\n if (gitContext.status != null) {\n const status = gitContext.status;\n sections.push(\n "Repository status: branch " +\n valueOrUnknown(status.branch) +\n (status.upstream ? " tracking " + status.upstream : "") +\n "; staged " +\n arrayLength(status.staged) +\n "; unstaged " +\n arrayLength(status.unstaged) +\n "; untracked " +\n arrayLength(status.untracked)\n );\n }\n if (gitContext.explicitRefs && !hasResolvedBranchContext(gitContext)) {\n sections.push(\n "WARNING: Requested base/head refs could not be resolved for automatic branch diff and commit capture; Git context may include only repository status or working-tree changes."\n );\n }\n if (Array.isArray(gitContext.failures) && gitContext.failures.length > 0) {\n sections.push(\n "Git context warnings:\\n" +\n gitContext.failures\n .map(function (failure) {\n return "- " + valueOrUnknown(failure.action) + ": " + valueOrUnknown(failure.error);\n })\n .join("\\n")\n );\n }\n const files = collectGitFiles(gitContext);\n if (files.length > 0) {\n sections.push("Changed files from parent workspace Git snapshot: " + files.join(", "));\n }\n if (gitContext.commits != null && Array.isArray(gitContext.commits.commits)) {\n const commits = gitContext.commits.commits;\n if (commits.length > 0) {\n sections.push(\n "Commits since " +\n valueOrUnknown(gitContext.commits.base) +\n ":\\n" +\n commits\n .map(function (commit) {\n return "- " + valueOrUnknown(commit.shortHash) + " " + valueOrUnknown(commit.subject);\n })\n .join("\\n")\n );\n }\n }\n if (gitContext.diffStat != null) {\n const statSections = [];\n if (hasText(gitContext.diffStat.branch))\n statSections.push("Branch diff stat:\\n" + gitContext.diffStat.branch);\n if (hasText(gitContext.diffStat.staged))\n statSections.push("Staged diff stat:\\n" + gitContext.diffStat.staged);\n if (hasText(gitContext.diffStat.unstaged))\n statSections.push("Unstaged diff stat:\\n" + gitContext.diffStat.unstaged);\n if (statSections.length > 0) sections.push(statSections.join("\\n\\n"));\n }\n if (gitContext.status != null && arrayLength(gitContext.status.untracked) > 0) {\n sections.push(\n "Untracked file contents are not included in the automatic diff snapshot; review agents only receive their paths unless the caller supplied args.diff."\n );\n }\n if (gitContext.diff != null && isDiffTruncated(gitContext.diff)) {\n sections.push(\n "One or more automatic diff sections were truncated by workflow action output limits."\n );\n }\n return sections.join("\\n\\n");\n}\n\nfunction renderGitDiff(diff) {\n const parts = [];\n if (hasText(diff.branch)) {\n parts.push(\n "Branch diff (" +\n valueOrUnknown(diff.base) +\n ".." +\n valueOrUnknown(diff.head) +\n ")\\n" +\n diff.branch\n );\n }\n if (hasText(diff.staged)) {\n parts.push("Staged diff\\n" + diff.staged);\n }\n if (hasText(diff.unstaged)) {\n parts.push("Unstaged diff\\n" + diff.unstaged);\n }\n if (isDiffTruncated(diff)) {\n parts.push("NOTE: one or more diff sections were truncated by workflow action output limits.");\n }\n return parts.join("\\n\\n");\n}\n\nfunction isDiffTruncated(diff) {\n return Boolean(\n diff &&\n diff.truncated &&\n (diff.truncated.branch || diff.truncated.staged || diff.truncated.unstaged)\n );\n}\n\nfunction truncateText(text, maxLength) {\n if (text.length <= maxLength) return text;\n return (\n text.slice(0, maxLength) +\n "\\n[truncated by deep-review-workflow after " +\n maxLength +\n " characters]"\n );\n}\n\nfunction hasText(value) {\n return typeof value === "string" && value.trim().length > 0;\n}\n\nfunction arrayLength(value) {\n return Array.isArray(value) ? value.length : 0;\n}\n\nfunction valueOrUnknown(value) {\n return typeof value === "string" && value.length > 0 ? value : "unknown";\n}\n\nfunction formatError(error) {\n return error && typeof error.message === "string" ? error.message : String(error);\n}\n\nfunction renderReviewInput(input) {\n return [\n "Target: " + input.target,\n input.baseRef ? "Base ref: " + input.baseRef : "",\n input.headRef ? "Head ref: " + input.headRef : "",\n input.files.length > 0 ? "Files: " + input.files.join(", ") : "",\n input.gitSnapshot ? "Git snapshot:\\n" + input.gitSnapshot : "",\n input.instructions ? "Reviewer instructions: " + input.instructions : "",\n input.diff ? "Diff snapshot:\\n~~~diff\\n" + input.diff + "\\n~~~" : "",\n ]\n .filter(Boolean)\n .join("\\n");\n}\n\nfunction selectReviewLanes(lanes) {\n const defaults = ["correctness", "tests", "architecture"];\n const allowed = {\n correctness: true,\n tests: true,\n architecture: true,\n "security-reliability": true,\n "ux-a11y": true,\n "docs-dx": true,\n };\n const requested = Array.isArray(lanes) && lanes.length > 0 ? lanes : defaults;\n const result = [];\n for (const lane of requested) {\n if (allowed[lane] && result.indexOf(lane) === -1) {\n result.push(lane);\n }\n }\n for (const fallback of defaults) {\n if (result.indexOf(fallback) === -1) {\n result.push(fallback);\n }\n }\n return result.slice(0, 6);\n}\n\nfunction lanePrompt(lane) {\n const prompts = {\n correctness:\n "Review for logic bugs, edge cases, races, state-machine violations, and broken invariants.",\n tests: "Review test coverage, determinism, missing regression tests, and validation commands.",\n architecture:\n "Review consistency with existing architecture, boundaries, naming, abstractions, and maintainability.",\n "security-reliability":\n "Review security, trust boundaries, path traversal, injection, data corruption, reliability, and performance risks.",\n "ux-a11y":\n "Review user-facing behavior, accessibility, keyboard flow, visual consistency, and empty/loading/error states.",\n "docs-dx":\n "Review documentation, developer experience, scripts, public API clarity, and migration concerns.",\n };\n return prompts[lane] || prompts.correctness;\n}\n\nfunction issueListSchema() {\n return {\n type: "object",\n required: ["issues"],\n additionalProperties: false,\n properties: {\n issues: {\n type: "array",\n items: issueSchema(),\n },\n },\n };\n}\n\nfunction issueSchema() {\n return {\n type: "object",\n required: [\n "id",\n "severity",\n "category",\n "title",\n "rationale",\n "evidence",\n "filePaths",\n "confidence",\n ],\n additionalProperties: false,\n properties: {\n id: { type: "string" },\n severity: { type: "string", enum: ["P0", "P1", "P2", "P3", "P4"] },\n category: { type: "string" },\n title: { type: "string" },\n rationale: { type: "string" },\n evidence: { type: "string" },\n filePaths: { type: "array", items: { type: "string" } },\n suggestedFix: { type: "string" },\n validation: { type: "string" },\n confidence: { type: "string", enum: ["low", "medium", "high"] },\n },\n };\n}\n\nfunction scopeSchema() {\n return {\n type: "object",\n required: ["summary", "files", "riskAreas", "lanes"],\n additionalProperties: false,\n properties: {\n summary: { type: "string" },\n files: { type: "array", items: { type: "string" } },\n riskAreas: { type: "array", items: { type: "string" } },\n lanes: {\n type: "array",\n items: {\n type: "string",\n enum: [\n "correctness",\n "tests",\n "architecture",\n "security-reliability",\n "ux-a11y",\n "docs-dx",\n ],\n },\n },\n },\n };\n}\n\nfunction verificationSchema() {\n return {\n type: "object",\n required: ["issueId", "verdict", "confidence", "rationale"],\n additionalProperties: false,\n properties: {\n issueId: { type: "string" },\n verdict: {\n type: "string",\n enum: ["valid", "duplicate", "overstated", "not-repro", "needs-info"],\n },\n confidence: { type: "string", enum: ["low", "medium", "high"] },\n rationale: { type: "string" },\n evidence: { type: "string" },\n suggestedSeverity: { type: "string", enum: ["P0", "P1", "P2", "P3", "P4"] },\n },\n };\n}\n\nfunction finalSynthesisSchema() {\n return {\n type: "object",\n required: ["verifiedIssueCount", "verifiedIssueIds", "risk", "validationPlan"],\n additionalProperties: false,\n properties: {\n verifiedIssueCount: { type: "number" },\n risk: { type: "string", enum: ["low", "medium", "high"] },\n validationPlan: { type: "array", items: { type: "string" } },\n verifiedIssueIds: { type: "array", items: { type: "string" } },\n discardedIssueCount: { type: "number" },\n },\n };\n}\n\nfunction flatten(arrays) {\n const out = [];\n for (const array of arrays) {\n for (const item of array) {\n out.push(item);\n }\n }\n return out;\n}\n', }, { name: "security-scan", diff --git a/src/node/services/workflows/builtInWorkflowDefinitions.test.ts b/src/node/services/workflows/builtInWorkflowDefinitions.test.ts index 0451fb0a87..7b492c536b 100644 --- a/src/node/services/workflows/builtInWorkflowDefinitions.test.ts +++ b/src/node/services/workflows/builtInWorkflowDefinitions.test.ts @@ -2423,6 +2423,315 @@ describe("built-in deep-review-workflow", () => { }); }, 10_000); + test("short-circuits when review lanes report no candidate issues", async () => { + if (!deepReviewWorkflow) { + throw new Error("Expected built-in deep-review-workflow workflow"); + } + using tmp = new DisposableTempDir("deep-review-workflow-no-issues-short-circuit"); + const runStore = new WorkflowRunStore({ + sessionDir: tmp.path, + staleLeaseMs: BUILT_IN_WORKFLOW_TEST_STALE_LEASE_MS, + }); + await runStore.createRun({ + id: "wfr_deep_review_no_issues_short_circuit", + workspaceId: "workspace-1", + definition: { + name: deepReviewWorkflow.name, + description: deepReviewWorkflow.description, + scope: "built-in", + executable: true, + }, + definitionSource: deepReviewWorkflow.source, + args: { input: "PR #123", files: ["src/service.ts"], maxCandidates: 1 }, + now: "2026-05-29T00:00:00.000Z", + }); + + const taskCalls: WorkflowAgentSpec[] = []; + const runner = new WorkflowRunner({ + runStore, + runtimeFactory: new QuickJSRuntimeFactory(), + taskAdapter: createNoIssueDeepReviewTaskAdapter(taskCalls), + runnerId: "runner-a", + clock: { + nowIso: () => "2026-05-29T00:00:01.000Z", + nowMs: () => 1_000, + }, + }); + + const result = await runner.run("wfr_deep_review_no_issues_short_circuit"); + const run = await runStore.getRun("wfr_deep_review_no_issues_short_circuit"); + + expect(taskCalls.map((call) => call.id)).toEqual([ + "scope-review-surface", + "review-correctness", + "review-tests", + "review-architecture", + ]); + expect(run.events.filter((event) => event.type === "phase").map((event) => event.name)).toEqual( + ["scope", "lane-review"] + ); + expect(result).toEqual({ + reportMarkdown: "# Deep Review\n\nNo verified issues.", + structuredOutput: { + target: "PR #123", + scope: { + summary: "Review target is scoped.", + files: [], + riskAreas: [], + lanes: ["correctness"], + }, + laneIssues: [], + triagedIssues: [], + verification: [], + final: { + verifiedIssueCount: 0, + verifiedIssueIds: [], + risk: "low", + validationPlan: [], + discardedIssueCount: 0, + }, + }, + }); + }, 10_000); + + test("short-circuits when triage drops all candidate issues", async () => { + if (!deepReviewWorkflow) { + throw new Error("Expected built-in deep-review-workflow workflow"); + } + using tmp = new DisposableTempDir("deep-review-workflow-empty-triage-short-circuit"); + const runStore = new WorkflowRunStore({ + sessionDir: tmp.path, + staleLeaseMs: BUILT_IN_WORKFLOW_TEST_STALE_LEASE_MS, + }); + await runStore.createRun({ + id: "wfr_deep_review_empty_triage_short_circuit", + workspaceId: "workspace-1", + definition: { + name: deepReviewWorkflow.name, + description: deepReviewWorkflow.description, + scope: "built-in", + executable: true, + }, + definitionSource: deepReviewWorkflow.source, + args: { input: "PR #123", files: ["src/service.ts"], maxCandidates: 1 }, + now: "2026-05-29T00:00:00.000Z", + }); + + const issue = { + id: "speculative-candidate", + severity: "P3", + category: "tests", + title: "Speculative candidate", + rationale: "The lane was unsure.", + evidence: "No concrete path evidence.", + filePaths: ["src/service.ts"], + suggestedFix: "No concrete fix.", + validation: "No validation.", + confidence: "low", + }; + const taskCalls: WorkflowAgentSpec[] = []; + const runner = new WorkflowRunner({ + runStore, + runtimeFactory: new QuickJSRuntimeFactory(), + taskAdapter: { + async runAgent(spec) { + taskCalls.push(spec); + switch (spec.id) { + case "scope-review-surface": + return { + taskId: "task_scope", + reportMarkdown: "Scoped review target.", + structuredOutput: { + summary: "Review target is scoped.", + files: [], + riskAreas: [], + lanes: ["correctness"], + }, + }; + case "review-correctness": + return { + taskId: "task_review_correctness", + reportMarkdown: "One speculative candidate.", + structuredOutput: { issues: [issue] }, + }; + case "review-tests": + case "review-architecture": + return { + taskId: `task_${spec.id}`, + reportMarkdown: "No findings.", + structuredOutput: { issues: [] }, + }; + case "triage-candidate-issues": + return { + taskId: "task_triage", + reportMarkdown: "Dropped speculative candidate.", + structuredOutput: { issues: [] }, + }; + default: + throw new Error(`Unexpected empty-triage deep-review step: ${spec.id}`); + } + }, + }, + runnerId: "runner-a", + clock: { + nowIso: () => "2026-05-29T00:00:01.000Z", + nowMs: () => 1_000, + }, + }); + + const result = await runner.run("wfr_deep_review_empty_triage_short_circuit"); + const run = await runStore.getRun("wfr_deep_review_empty_triage_short_circuit"); + + expect(taskCalls.map((call) => call.id)).toEqual([ + "scope-review-surface", + "review-correctness", + "review-tests", + "review-architecture", + "triage-candidate-issues", + ]); + expect(run.events.filter((event) => event.type === "phase").map((event) => event.name)).toEqual( + ["scope", "lane-review", "triage-dedupe"] + ); + expect(result).toMatchObject({ + structuredOutput: { + laneIssues: [issue], + triagedIssues: [], + verification: [], + final: { + verifiedIssueCount: 0, + verifiedIssueIds: [], + discardedIssueCount: 1, + }, + }, + }); + }, 10_000); + + test("short-circuits final synthesis when verification rejects every candidate", async () => { + if (!deepReviewWorkflow) { + throw new Error("Expected built-in deep-review-workflow workflow"); + } + using tmp = new DisposableTempDir("deep-review-workflow-rejected-verification-short-circuit"); + const runStore = new WorkflowRunStore({ + sessionDir: tmp.path, + staleLeaseMs: BUILT_IN_WORKFLOW_TEST_STALE_LEASE_MS, + }); + await runStore.createRun({ + id: "wfr_deep_review_rejected_verification_short_circuit", + workspaceId: "workspace-1", + definition: { + name: deepReviewWorkflow.name, + description: deepReviewWorkflow.description, + scope: "built-in", + executable: true, + }, + definitionSource: deepReviewWorkflow.source, + args: { input: "PR #123", files: ["src/service.ts"], maxCandidates: 1 }, + now: "2026-05-29T00:00:00.000Z", + }); + + const issue = { + id: "rejected-candidate", + severity: "P2", + category: "correctness", + title: "Rejected candidate", + rationale: "The lane suspected a bug.", + evidence: "Verifier will reject this evidence.", + filePaths: ["src/service.ts"], + suggestedFix: "No concrete fix.", + validation: "No validation.", + confidence: "medium", + }; + const rejection = { + issueId: "rejected-candidate", + verdict: "not-repro", + confidence: "high", + rationale: "The suspected path is unreachable.", + evidence: "Tests cover the path.", + suggestedSeverity: "P4", + }; + const taskCalls: WorkflowAgentSpec[] = []; + const runner = new WorkflowRunner({ + runStore, + runtimeFactory: new QuickJSRuntimeFactory(), + taskAdapter: { + async runAgent(spec) { + taskCalls.push(spec); + switch (spec.id) { + case "scope-review-surface": + return { + taskId: "task_scope", + reportMarkdown: "Scoped review target.", + structuredOutput: { + summary: "Review target is scoped.", + files: [], + riskAreas: [], + lanes: ["correctness"], + }, + }; + case "review-correctness": + return { + taskId: "task_review_correctness", + reportMarkdown: "One candidate.", + structuredOutput: { issues: [issue] }, + }; + case "review-tests": + case "review-architecture": + return { + taskId: `task_${spec.id}`, + reportMarkdown: "No findings.", + structuredOutput: { issues: [] }, + }; + case "triage-candidate-issues": + return { + taskId: "task_triage", + reportMarkdown: "Kept one candidate.", + structuredOutput: { issues: [issue] }, + }; + case "verify-issue-0": + return { + taskId: "task_verify", + reportMarkdown: "Rejected candidate.", + structuredOutput: rejection, + }; + default: + throw new Error(`Unexpected rejected-verification deep-review step: ${spec.id}`); + } + }, + }, + runnerId: "runner-a", + clock: { + nowIso: () => "2026-05-29T00:00:01.000Z", + nowMs: () => 1_000, + }, + }); + + const result = await runner.run("wfr_deep_review_rejected_verification_short_circuit"); + const run = await runStore.getRun("wfr_deep_review_rejected_verification_short_circuit"); + + expect(taskCalls.map((call) => call.id)).toEqual([ + "scope-review-surface", + "review-correctness", + "review-tests", + "review-architecture", + "triage-candidate-issues", + "verify-issue-0", + ]); + expect(run.events.filter((event) => event.type === "phase").map((event) => event.name)).toEqual( + ["scope", "lane-review", "triage-dedupe", "adversarial-verification"] + ); + expect(result).toMatchObject({ + structuredOutput: { + triagedIssues: [issue], + verification: [rejection], + final: { + verifiedIssueCount: 0, + verifiedIssueIds: [], + discardedIssueCount: 1, + }, + }, + }); + }, 10_000); + test("ranks triaged issues by severity before applying the candidate budget", async () => { if (!deepReviewWorkflow) { throw new Error("Expected built-in deep-review-workflow workflow"); @@ -3273,8 +3582,6 @@ describe("built-in deep-review-workflow", () => { "review-correctness-loop-2", "review-tests-loop-2", "review-architecture-loop-2", - "triage-candidate-issues-loop-2", - "synthesize-review-loop-2", ]); expect(applyCalls).toEqual([ expect.objectContaining({ @@ -3296,9 +3603,6 @@ describe("built-in deep-review-workflow", () => { "loop-iteration", "scope", "lane-review", - "triage-dedupe", - "adversarial-verification", - "final-synthesis", ] ); const completedActionStepIds = run.events.flatMap((event) => @@ -5095,7 +5399,7 @@ describe("built-in deep-review-workflow", () => { taskAdapter: { async runAgent(spec) { taskCalls.push(spec); - if (spec.id === "synthesize-review") { + if (spec.id === "review-correctness") { await runGit(repoRoot, ["checkout", "-b", "feature"]); } return createNoIssueDeepReviewTaskAdapter([]).runAgent(spec); @@ -6239,7 +6543,7 @@ describe("built-in deep-review-workflow", () => { expect(taskCalls.map((call) => call.id)).not.toContain("fix-issue-0"); expect(run.events.filter((event) => event.type === "phase").map((event) => event.name)).toEqual( - ["scope", "lane-review", "triage-dedupe", "adversarial-verification", "final-synthesis"] + ["scope", "lane-review"] ); const structuredOutput = result.structuredOutput; if ( @@ -6296,11 +6600,9 @@ describe("built-in deep-review-workflow", () => { "review-correctness", "review-tests", "review-architecture", - "triage-candidate-issues", - "synthesize-review", ]); expect(run.events.filter((event) => event.type === "phase").map((event) => event.name)).toEqual( - ["scope", "lane-review", "triage-dedupe", "adversarial-verification", "final-synthesis"] + ["scope", "lane-review"] ); expect(result).toEqual({ reportMarkdown: "# Deep Review\n\nNo verified issues.", From 54945030530a611ede8023d939e8e22f33af9457 Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Mon, 15 Jun 2026 15:33:14 +0000 Subject: [PATCH 2/2] Preserve overstated deep review findings --- .../builtinWorkflows/deep-review-workflow.js | 9 +- .../builtInWorkflowContent.generated.ts | 2 +- .../builtInWorkflowDefinitions.test.ts | 138 ++++++++++++++++++ 3 files changed, 144 insertions(+), 5 deletions(-) diff --git a/src/node/builtinWorkflows/deep-review-workflow.js b/src/node/builtinWorkflows/deep-review-workflow.js index ee7b05b245..a6d5608acb 100644 --- a/src/node/builtinWorkflows/deep-review-workflow.js +++ b/src/node/builtinWorkflows/deep-review-workflow.js @@ -293,9 +293,9 @@ function runDeepReviewPass(context) { withLoopIteration({ count: verifications.length }, context.iteration) ); - if (!hasValidVerification(verifications)) { + if (!hasActionableVerification(verifications)) { context.log( - "No verifier upheld a candidate issue; skipping final synthesis", + "No verifier upheld an actionable candidate issue; skipping final synthesis", withLoopIteration({ skippedPhases: ["final-synthesis"] }, context.iteration) ); const reviewResult = buildNoVerifiedIssuesReviewResult({ @@ -385,9 +385,10 @@ function buildNoVerifiedIssuesReviewResult(options) { }; } -function hasValidVerification(verifications) { +function hasActionableVerification(verifications) { for (const verification of verifications) { - if (verification && verification.verdict === "valid") return true; + if (!verification) continue; + if (verification.verdict === "valid" || verification.verdict === "overstated") return true; } return false; } diff --git a/src/node/services/workflows/builtInWorkflowContent.generated.ts b/src/node/services/workflows/builtInWorkflowContent.generated.ts index aaa4c9d8cf..ffbeb6d7d1 100644 --- a/src/node/services/workflows/builtInWorkflowContent.generated.ts +++ b/src/node/services/workflows/builtInWorkflowContent.generated.ts @@ -20,7 +20,7 @@ export const BUILTIN_WORKFLOW_CONTENT: readonly BuiltInWorkflowContentEntry[] = description: "Coordinate adversarial review agents to find, verify, and synthesize code review findings.", source: - '// description: Coordinate adversarial review agents to find, verify, and synthesize code review findings.\n//\n// Keep the lightweight /deep-review skill; this workflow is the heavier structured path with\n// adversarial verification for review findings.\n\n// Verification/fixer fan-out scales with maxCandidates/maxFixes (clamped at 20);\n// cap live agents so raising those budgets queues work instead of launching one\n// wave of 20 concurrent agents. Matches deep-research\'s smart-mode verifier cap.\nconst MAX_PARALLEL_AGENTS = 12;\nexport default function deepReviewWorkflow({\n args,\n phase,\n log,\n agent,\n action,\n parallelAgents,\n applyPatch,\n}) {\n const exploreAgentId = "explore";\n const reasoningAgentId = "exec";\n // Scope discovery stays on Explore; review judgment uses Exec for users with fast Explore defaults.\n const readOnlyReviewPrompt =\n "This is a read-only deep code review task. Do not edit files, create commits, apply patches, push branches, or open PRs. Inspect repository evidence only as needed and report findings.\\n\\n";\n const input = normalizeDeepReviewArgs(args);\n if (input.loop && !input.fix) {\n throw new Error("--loop requires --fix for deep-review-workflow");\n }\n if (input.loop) {\n return runDeepReviewLoop({\n input: input,\n phase: phase,\n log: log,\n agent: agent,\n action: action,\n parallelAgents: parallelAgents,\n applyPatch: applyPatch,\n exploreAgentId: exploreAgentId,\n reasoningAgentId: reasoningAgentId,\n readOnlyReviewPrompt: readOnlyReviewPrompt,\n });\n }\n return runDeepReviewPass({\n input: input,\n phase: phase,\n log: log,\n agent: agent,\n action: action,\n parallelAgents: parallelAgents,\n applyPatch: applyPatch,\n exploreAgentId: exploreAgentId,\n reasoningAgentId: reasoningAgentId,\n readOnlyReviewPrompt: readOnlyReviewPrompt,\n stepSuffix: "",\n iteration: 0,\n skipFixWhenNoVerifiedIssues: false,\n }).reviewResult;\n}\n\nfunction runDeepReviewLoop(context) {\n const passes = [];\n let stopReason = "";\n let remainingFixBudget = context.input.maxFixes;\n let loopHeadRef = context.input.headRef;\n for (let iteration = 1; iteration <= context.input.maxLoopIterations; iteration += 1) {\n context.phase("loop-iteration", {\n iteration: iteration,\n maxIterations: context.input.maxLoopIterations,\n remainingFixBudget: remainingFixBudget,\n });\n const budgetExhaustedReadOnlyCheck = remainingFixBudget <= 0;\n const iterationInput = cloneDeepReviewInput(context.input);\n iterationInput.headRef = loopHeadRef;\n iterationInput.maxFixes = remainingFixBudget;\n if (budgetExhaustedReadOnlyCheck) iterationInput.fix = false;\n const pass = runDeepReviewPass({\n input: iterationInput,\n phase: context.phase,\n log: context.log,\n agent: context.agent,\n action: context.action,\n parallelAgents: context.parallelAgents,\n applyPatch: context.applyPatch,\n exploreAgentId: context.exploreAgentId,\n reasoningAgentId: context.reasoningAgentId,\n readOnlyReviewPrompt: context.readOnlyReviewPrompt,\n stepSuffix: "loop-" + iteration,\n iteration: iteration,\n skipFixWhenNoVerifiedIssues: true,\n });\n passes.push(pass.reviewResult);\n if (budgetExhaustedReadOnlyCheck) {\n stopReason = hasVerifiedIssues(pass.reviewResult.structuredOutput.final)\n ? "fix-budget-exhausted"\n : "no-verified-issues";\n return buildLoopResult(context.input, passes, stopReason, remainingFixBudget);\n }\n const fixProgress = reviewResultHasFixProgress(pass.reviewResult);\n remainingFixBudget = Math.max(0, remainingFixBudget - countSelectedFixes(pass.reviewResult));\n if (fixProgress) loopHeadRef = "";\n stopReason = getLoopStopReason(pass.reviewResult, remainingFixBudget);\n if (stopReason === "fix-budget-exhausted" && iteration < context.input.maxLoopIterations)\n continue;\n if (stopReason) {\n return buildLoopResult(context.input, passes, stopReason, remainingFixBudget);\n }\n }\n return buildLoopResult(context.input, passes, "max-iterations", remainingFixBudget);\n}\n\nfunction runDeepReviewPass(context) {\n const input = cloneDeepReviewInput(context.input);\n const gitContext = shouldCollectGitReviewContext(input)\n ? collectGitReviewContext(context.action, input, context.log, context.stepSuffix)\n : null;\n applyGitContextToReviewInput(input, gitContext);\n const maxCandidates = input.maxCandidates;\n\n context.phase(\n "scope",\n withLoopIteration(\n {\n target: input.target,\n fileCount: input.files.length,\n hasDiffSnapshot: input.diff.length > 0,\n hasGitSnapshot: input.gitSnapshot.length > 0,\n },\n context.iteration\n )\n );\n const scope = context.agent({\n id: workflowStepId("scope-review-surface", context.stepSuffix),\n title: "Scope review surface",\n agentId: context.exploreAgentId,\n prompt:\n "Scope this code review. Identify changed files, likely intent, touched layers, highest-risk areas, and which review lanes should run. Use repository evidence; do not assume the diff is complete if refs are provided.\\n\\n" +\n renderReviewInput(input),\n outputSchema: scopeSchema(),\n });\n\n const lanes = selectReviewLanes(scope.structuredOutput.lanes);\n context.log("Selected deep review lanes", withLoopIteration({ lanes: lanes }, context.iteration));\n\n context.phase("lane-review", withLoopIteration({ lanes: lanes }, context.iteration));\n const laneReviews = context.parallelAgents(\n lanes.map(function (lane) {\n return {\n id: workflowStepId("review-" + lane, context.stepSuffix),\n title: "Review lane: " + lane,\n agentId: context.reasoningAgentId,\n prompt:\n context.readOnlyReviewPrompt +\n lanePrompt(lane) +\n "\\n\\nReview target:\\n" +\n renderReviewInput(input) +\n "\\n\\nScoped review surface:\\n" +\n JSON.stringify(scope.structuredOutput, null, 2) +\n "\\n\\nReturn only concrete, actionable findings with file paths and evidence. Prefer an empty issues array over speculative feedback.",\n outputSchema: issueListSchema(),\n };\n })\n );\n const laneIssues = flatten(\n laneReviews.map(function (review) {\n return review.structuredOutput.issues || [];\n })\n );\n context.log(\n "Lane review produced candidate issues",\n withLoopIteration({ count: laneIssues.length }, context.iteration)\n );\n\n if (laneIssues.length === 0) {\n context.log(\n "No candidate issues from review lanes; skipping triage, verification, and final synthesis",\n withLoopIteration(\n { skippedPhases: ["triage-dedupe", "adversarial-verification", "final-synthesis"] },\n context.iteration\n )\n );\n const reviewResult = buildNoVerifiedIssuesReviewResult({\n input: input,\n scope: scope.structuredOutput,\n laneIssues: laneIssues,\n triagedIssues: [],\n verifications: [],\n discardedIssueCount: 0,\n });\n return finishDeepReviewPass({\n context: context,\n input: input,\n reviewResult: reviewResult,\n gitContext: gitContext,\n candidates: [],\n verifications: [],\n final: reviewResult.structuredOutput.final,\n });\n }\n\n context.phase(\n "triage-dedupe",\n withLoopIteration({ candidateCount: laneIssues.length }, context.iteration)\n );\n const triage = context.agent({\n id: workflowStepId("triage-candidate-issues", context.stepSuffix),\n title: "Triage and dedupe review findings",\n agentId: context.reasoningAgentId,\n prompt:\n context.readOnlyReviewPrompt +\n "Deduplicate and consolidate these candidate code review findings. Merge duplicates across lanes (combining their evidence), normalize severity, and drop only clearly non-actionable items; keep borderline findings so adversarial verification can make the validity call. Order the issues by severity and impact, most severe first — only the first " +\n maxCandidates +\n " survive the candidate budget.\\n\\n" +\n "Review target:\\n" +\n renderReviewInput(input) +\n "\\n\\nCandidate issues:\\n" +\n JSON.stringify(laneIssues, null, 2),\n outputSchema: issueListSchema(),\n });\n // Triage is prompted to emit issues most-severe-first, but re-sort defensively so a\n // high-severity finding emitted past the budget cutoff is never silently dropped.\n const rankedIssues = sortIssuesBySeverity(triage.structuredOutput.issues || []);\n const candidates = rankedIssues.slice(0, maxCandidates);\n context.log(\n "Triaged candidate issues",\n withLoopIteration(\n {\n candidateCount: rankedIssues.length,\n selectedCount: candidates.length,\n droppedByBudget: rankedIssues.slice(candidates.length).map(function (issue, index) {\n return stableIssueId(issue, candidates.length + index);\n }),\n },\n context.iteration\n )\n );\n\n if (candidates.length === 0) {\n context.log(\n "No candidate issues after triage; skipping verification and final synthesis",\n withLoopIteration(\n { skippedPhases: ["adversarial-verification", "final-synthesis"] },\n context.iteration\n )\n );\n const reviewResult = buildNoVerifiedIssuesReviewResult({\n input: input,\n scope: scope.structuredOutput,\n laneIssues: laneIssues,\n triagedIssues: [],\n verifications: [],\n discardedIssueCount: laneIssues.length,\n });\n return finishDeepReviewPass({\n context: context,\n input: input,\n reviewResult: reviewResult,\n gitContext: gitContext,\n candidates: [],\n verifications: [],\n final: reviewResult.structuredOutput.final,\n });\n }\n\n context.phase(\n "adversarial-verification",\n withLoopIteration({ candidateCount: candidates.length }, context.iteration)\n );\n const verificationResults =\n candidates.length > 0\n ? context.parallelAgents(\n candidates.map(function (issue, index) {\n return {\n id: workflowStepId("verify-issue-" + index, context.stepSuffix),\n title: "Verify review finding " + (index + 1),\n agentId: context.reasoningAgentId,\n prompt:\n context.readOnlyReviewPrompt +\n "Adversarially verify this code review finding. Try to disprove it. Inspect relevant code paths and tests. Decide whether it is valid, duplicate, overstated, not reproducible, or needs more information.\\n\\n" +\n "Review target:\\n" +\n renderReviewInput(input) +\n "\\n\\nFinding:\\n" +\n JSON.stringify(issue, null, 2),\n outputSchema: verificationSchema(),\n };\n }),\n { maxParallel: MAX_PARALLEL_AGENTS }\n )\n : [];\n const verifications = verificationResults.map(function (verification) {\n return verification.structuredOutput;\n });\n context.log(\n "Verified candidate issues",\n withLoopIteration({ count: verifications.length }, context.iteration)\n );\n\n if (!hasValidVerification(verifications)) {\n context.log(\n "No verifier upheld a candidate issue; skipping final synthesis",\n withLoopIteration({ skippedPhases: ["final-synthesis"] }, context.iteration)\n );\n const reviewResult = buildNoVerifiedIssuesReviewResult({\n input: input,\n scope: scope.structuredOutput,\n laneIssues: laneIssues,\n triagedIssues: candidates,\n verifications: verifications,\n discardedIssueCount: candidates.length,\n });\n return finishDeepReviewPass({\n context: context,\n input: input,\n reviewResult: reviewResult,\n gitContext: gitContext,\n candidates: candidates,\n verifications: verifications,\n final: reviewResult.structuredOutput.final,\n });\n }\n\n context.phase(\n "final-synthesis",\n withLoopIteration(\n {\n candidateCount: candidates.length,\n verificationCount: verifications.length,\n },\n context.iteration\n )\n );\n const final = context.agent({\n id: workflowStepId("synthesize-review", context.stepSuffix),\n title: "Synthesize final deep review",\n agentId: context.reasoningAgentId,\n prompt:\n context.readOnlyReviewPrompt +\n "Write the final code review. Include only findings that remain actionable after adversarial verification. Use severity P0-P4, file paths, issue IDs, and concrete evidence. If there are no verified issues, say so clearly. Include questions and a validation plan. Set verifiedIssueIds to the issue IDs included in the final review when any are verified.\\n\\n" +\n "Scoped review surface:\\n" +\n JSON.stringify(scope.structuredOutput, null, 2) +\n "\\n\\nTriaged issues:\\n" +\n JSON.stringify(candidates, null, 2) +\n "\\n\\nVerification results:\\n" +\n JSON.stringify(verifications, null, 2),\n outputSchema: finalSynthesisSchema(),\n });\n\n const reviewResult = {\n reportMarkdown: final.reportMarkdown,\n structuredOutput: {\n target: input.target,\n scope: scope.structuredOutput,\n laneIssues: laneIssues,\n triagedIssues: candidates,\n verification: verifications,\n final: final.structuredOutput,\n },\n };\n return finishDeepReviewPass({\n context: context,\n input: input,\n reviewResult: reviewResult,\n gitContext: gitContext,\n candidates: candidates,\n verifications: verifications,\n final: final.structuredOutput,\n });\n}\n\nfunction buildNoVerifiedIssuesReviewResult(options) {\n return {\n reportMarkdown: "# Deep Review\\n\\nNo verified issues.",\n structuredOutput: {\n target: options.input.target,\n scope: options.scope,\n laneIssues: options.laneIssues,\n triagedIssues: options.triagedIssues,\n verification: options.verifications,\n final: {\n verifiedIssueCount: 0,\n verifiedIssueIds: [],\n risk: "low",\n validationPlan: [],\n discardedIssueCount: options.discardedIssueCount,\n },\n },\n };\n}\n\nfunction hasValidVerification(verifications) {\n for (const verification of verifications) {\n if (verification && verification.verdict === "valid") return true;\n }\n return false;\n}\n\nfunction finishDeepReviewPass(options) {\n const context = options.context;\n const reviewResult = options.reviewResult;\n if (!options.input.fix) return { reviewResult: reviewResult, gitContext: options.gitContext };\n if (context.skipFixWhenNoVerifiedIssues && !hasVerifiedIssues(options.final)) {\n return { reviewResult: reviewResult, gitContext: options.gitContext };\n }\n\n context.phase("fix-preflight", withLoopIteration({ requested: true }, context.iteration));\n const fixResult = runDeepReviewFix({\n input: options.input,\n action: context.action,\n log: context.log,\n agent: context.agent,\n parallelAgents: context.parallelAgents,\n applyPatch: context.applyPatch,\n candidates: options.candidates,\n verifications: options.verifications,\n final: options.final,\n gitContext: options.gitContext,\n exploreAgentId: context.exploreAgentId,\n reasoningAgentId: context.reasoningAgentId,\n stepSuffix: context.stepSuffix,\n });\n reviewResult.structuredOutput.fix = fixResult;\n reviewResult.reportMarkdown = reviewResult.reportMarkdown + renderFixMarkdown(fixResult);\n return { reviewResult: reviewResult, gitContext: options.gitContext };\n}\n\nfunction cloneDeepReviewInput(input) {\n return {\n target: input.target,\n baseRef: input.baseRef,\n headRef: input.headRef,\n diff: input.diff,\n files: input.files.slice(),\n instructions: input.instructions,\n gitSnapshot: input.gitSnapshot,\n explicitDiff: input.explicitDiff,\n explicitFiles: input.explicitFiles,\n includeGitContext: input.includeGitContext,\n maxCandidates: input.maxCandidates,\n fix: input.fix,\n loop: input.loop,\n maxFixes: input.maxFixes,\n maxLoopIterations: input.maxLoopIterations,\n fixIssueIds: input.fixIssueIds.slice(),\n };\n}\n\nfunction workflowStepId(baseId, suffix) {\n return suffix ? baseId + "-" + suffix : baseId;\n}\n\nfunction withLoopIteration(details, iteration) {\n if (iteration) details.iteration = iteration;\n return details;\n}\n\n// Stable sort: P0 first, unknown/invalid severity last, ties keep triage emit order.\nfunction sortIssuesBySeverity(issues) {\n return issues\n .map(function (issue, index) {\n return { issue: issue, index: index };\n })\n .sort(function (a, b) {\n const rankDelta = issueSeverityRank(a.issue) - issueSeverityRank(b.issue);\n return rankDelta !== 0 ? rankDelta : a.index - b.index;\n })\n .map(function (entry) {\n return entry.issue;\n });\n}\n\nfunction issueSeverityRank(issue) {\n const match =\n isObject(issue) && typeof issue.severity === "string"\n ? /^P([0-4])$/.exec(issue.severity)\n : null;\n return match ? Number(match[1]) : 5;\n}\n\nfunction hasVerifiedIssues(final) {\n if (!final) return false;\n if (typeof final.verifiedIssueCount === "number") return final.verifiedIssueCount > 0;\n return Array.isArray(final.verifiedIssueIds) && final.verifiedIssueIds.length > 0;\n}\n\nfunction reviewResultHasFixProgress(reviewResult) {\n const output = reviewResult && reviewResult.structuredOutput ? reviewResult.structuredOutput : {};\n const fix = output.fix;\n return Boolean(fix && fixHasProgress(fix));\n}\n\nfunction countSelectedFixes(reviewResult) {\n const output = reviewResult && reviewResult.structuredOutput ? reviewResult.structuredOutput : {};\n const fix = output.fix;\n return fix && Array.isArray(fix.selectedIssues) ? fix.selectedIssues.length : 0;\n}\n\nfunction getLoopStopReason(reviewResult, remainingFixBudget) {\n const output = reviewResult && reviewResult.structuredOutput ? reviewResult.structuredOutput : {};\n if (!hasVerifiedIssues(output.final)) return "no-verified-issues";\n const fix = output.fix;\n if (!fix) return "no-fix-attempted";\n if (fix.skippedReason) return "fix-skipped";\n if (!fix.selectedIssues || fix.selectedIssues.length === 0) return "no-fixable-issues";\n if (!fixHasProgress(fix)) return "no-fix-progress";\n const validationStatus = fix.validation ? fix.validation.status : "not-run";\n if (validationStatus === "failed") return "validation-failed";\n if (validationStatus !== "passed") return "validation-not-run";\n if (remainingFixBudget <= 0) return "fix-budget-exhausted";\n return "";\n}\n\nfunction fixHasProgress(fix) {\n return countStatus(fix.applications, "applied") > 0;\n}\n\nfunction buildLoopResult(input, passes, stopReason, remainingFixBudget) {\n const loop = {\n requested: true,\n completed: stopReason === "no-verified-issues",\n iterations: passes.length,\n maxIterations: input.maxLoopIterations,\n remainingFixBudget: remainingFixBudget,\n stopReason: stopReason,\n };\n const structuredOutput = {\n target: input.target,\n loop: loop,\n passes: passes.map(function (pass, index) {\n return {\n iteration: index + 1,\n result: pass.structuredOutput,\n };\n }),\n };\n if (passes.length > 0) {\n const latest = passes[passes.length - 1].structuredOutput;\n structuredOutput.latest = latest;\n structuredOutput.final = latest.final || null;\n if (latest.fix) structuredOutput.fix = latest.fix;\n }\n return {\n reportMarkdown: renderLoopMarkdown(passes, loop),\n structuredOutput: structuredOutput,\n };\n}\n\nfunction renderLoopMarkdown(passes, loop) {\n let markdown = "# Deep Review Loop\\n\\n";\n markdown += "- Iterations: " + loop.iterations + " / " + loop.maxIterations + "\\n";\n markdown += "- Stop reason: " + loop.stopReason + "\\n";\n markdown += "- Completed: " + (loop.completed ? "yes" : "no") + "\\n";\n for (let index = 0; index < passes.length; index += 1) {\n markdown += "\\n\\n---\\n\\n## Loop iteration " + (index + 1) + "\\n\\n";\n markdown += passes[index].reportMarkdown || "";\n }\n return markdown;\n}\n\nfunction runDeepReviewFix(context) {\n const input = context.input;\n const baseFix = {\n requested: true,\n selectedIssues: [],\n attempts: [],\n applications: [],\n resolutions: [],\n unresolved: [],\n };\n const preflight = collectFixPreflight(\n context.action,\n context.log,\n input,\n context.gitContext,\n context.stepSuffix\n );\n if (preflight.skippedReason) {\n baseFix.skippedReason = preflight.skippedReason;\n return baseFix;\n }\n let expectedHeadSha = preflight.expectedHeadSha;\n\n const selected = selectFixIssues(context.candidates, context.verifications, input, context.final);\n baseFix.selectedIssues = selected.map(function (item) {\n return summarizeFixIssue(item.issueId, item.issue);\n });\n context.log("Selected deep review fixes", {\n selectedCount: selected.length,\n skippedCount: context.candidates.length - selected.length,\n });\n if (selected.length === 0) return baseFix;\n\n const fixerResults = context.parallelAgents(\n selected.map(function (item, index) {\n return {\n id: workflowStepId("fix-issue-" + index, context.stepSuffix),\n title: "Fix verified review finding " + (index + 1),\n agentId: context.reasoningAgentId,\n prompt: buildFixPrompt(input, item),\n outputSchema: fixAttemptSchema(),\n };\n }),\n { maxParallel: MAX_PARALLEL_AGENTS }\n );\n\n const integratedIssues = [];\n for (let index = 0; index < selected.length; index += 1) {\n const item = selected[index];\n const fixerResult = fixerResults[index];\n const attempt = summarizeFixAttempt(item.issueId, fixerResult);\n baseFix.attempts.push(attempt);\n const attemptOutput =\n fixerResult && fixerResult.structuredOutput ? fixerResult.structuredOutput : {};\n if (!matchesReportedIssueId(attemptOutput, item.issueId)) {\n baseFix.unresolved.push({\n issueId: item.issueId,\n reason: issueIdMismatchReason("fixer", item.issueId, attemptOutput),\n });\n continue;\n }\n if (attemptOutput.status !== "fixed" || attemptOutput.commitCreated !== true) {\n if (attemptOutput.status !== "already-fixed") {\n baseFix.unresolved.push({\n issueId: item.issueId,\n reason: attemptOutput.status || "not-fixed",\n });\n }\n continue;\n }\n\n const application = safeApplyPatch(\n context.applyPatch,\n workflowStepId("apply-fix-" + index, context.stepSuffix),\n fixerResult,\n expectedHeadSha\n );\n application.issueId = item.issueId;\n baseFix.applications.push(application);\n if (application.status === "applied") {\n expectedHeadSha = getAppliedHeadCommitSha(application) || expectedHeadSha;\n integratedIssues.push(item.issueId);\n continue;\n }\n if (application.status !== "conflict") {\n baseFix.unresolved.push({\n issueId: item.issueId,\n reason: application.error || application.status,\n });\n continue;\n }\n\n const resolver = context.agent({\n id: workflowStepId("resolve-fix-" + index + "-conflict", context.stepSuffix),\n title: "Resolve auto-fix conflict " + (index + 1),\n agentId: context.reasoningAgentId,\n prompt: buildResolverPrompt(input, item, fixerResult, application, integratedIssues),\n outputSchema: fixResolverSchema(),\n });\n const resolution = summarizeResolution(item.issueId, resolver);\n baseFix.resolutions.push(resolution);\n const resolutionOutput = resolver && resolver.structuredOutput ? resolver.structuredOutput : {};\n if (!matchesReportedIssueId(resolutionOutput, item.issueId)) {\n baseFix.unresolved.push({\n issueId: item.issueId,\n reason: issueIdMismatchReason("resolver", item.issueId, resolutionOutput),\n });\n continue;\n }\n if (resolutionOutput.status === "already-resolved") {\n integratedIssues.push(item.issueId);\n continue;\n }\n if (resolutionOutput.status === "resolved" && resolutionOutput.commitCreated === true) {\n const resolvedApplication = safeApplyPatch(\n context.applyPatch,\n workflowStepId("apply-resolved-fix-" + index, context.stepSuffix),\n resolver,\n expectedHeadSha\n );\n resolvedApplication.issueId = item.issueId;\n resolution.applyStatus = resolvedApplication.status;\n baseFix.applications.push(resolvedApplication);\n if (resolvedApplication.status === "applied") {\n expectedHeadSha = getAppliedHeadCommitSha(resolvedApplication) || expectedHeadSha;\n integratedIssues.push(item.issueId);\n } else {\n baseFix.unresolved.push({\n issueId: item.issueId,\n reason: resolvedApplication.error || resolvedApplication.status,\n });\n }\n } else {\n baseFix.unresolved.push({\n issueId: item.issueId,\n reason: resolutionOutput.status || "unresolved-conflict",\n });\n }\n }\n\n if (integratedIssues.length > 0) {\n const validation = context.agent({\n id: workflowStepId("validate-auto-fixes", context.stepSuffix),\n title: "Validate applied auto-fixes",\n agentId: context.exploreAgentId,\n prompt: buildValidationPrompt(input, context.final, baseFix),\n outputSchema: fixValidationSchema(),\n });\n baseFix.validation = validation.structuredOutput;\n }\n return baseFix;\n}\n\nfunction collectFixPreflight(action, log, input, gitContext, stepSuffix) {\n if (input.explicitDiff)\n return {\n skippedReason: "auto-fix requires a local current workspace target, not an explicit diff",\n };\n if (looksNonLocalTarget(input.target))\n return { skippedReason: "auto-fix requires a local current workspace target" };\n let status = null;\n try {\n status = action.git.status({\n id: workflowStepId("fix-git-status", stepSuffix),\n input: { includeIgnored: false, head: input.headRef || "HEAD" },\n builtInOnly: true,\n cache: false,\n }).output;\n } catch (error) {\n const message = formatError(error);\n log("Git status unavailable for auto-fix preflight", { error: message });\n return { skippedReason: "auto-fix requires a fresh local Git status" };\n }\n if (!isObject(status)) return { skippedReason: "auto-fix requires a fresh local Git status" };\n if (!isCurrentReviewHead(input, status)) {\n return {\n skippedReason: "auto-fix requires the reviewed head ref to be the current checked-out branch",\n };\n }\n const reviewedSnapshot = getReviewedGitSnapshot(gitContext);\n if (!reviewedSnapshot) {\n return { skippedReason: "auto-fix requires a reviewed Git branch and HEAD snapshot" };\n }\n if (!matchesReviewedGitBranch(reviewedSnapshot, status)) {\n return {\n skippedReason: "auto-fix requires the current Git branch to match the reviewed snapshot",\n };\n }\n if (\n arrayLength(status.staged) > 0 ||\n arrayLength(status.unstaged) > 0 ||\n arrayLength(status.untracked) > 0\n ) {\n return { skippedReason: "auto-fix requires a clean committed local worktree" };\n }\n return { status: status, expectedHeadSha: reviewedSnapshot.headSha };\n}\n\nfunction isCurrentReviewHead(input, status) {\n if (!input.headRef) return true;\n const headRef = String(input.headRef).trim();\n if (!headRef || headRef === "HEAD") return true;\n const branch = normalizedGitBranch(status.branch);\n const currentBranchRef = branch ? "refs/heads/" + branch : "";\n if (branch && (headRef === branch || headRef === currentBranchRef)) return true;\n const requestedHeadRef =\n typeof status.requestedHeadRef === "string" ? status.requestedHeadRef : "";\n if (requestedHeadRef.length > 0) return false;\n if (!isExplicitCommitShaRef(headRef)) return false;\n const headSha = typeof status.headSha === "string" ? status.headSha : "";\n const requestedHeadSha =\n typeof status.requestedHeadSha === "string" ? status.requestedHeadSha : "";\n return Boolean(headSha && requestedHeadSha && headSha === requestedHeadSha);\n}\n\nfunction isExplicitCommitShaRef(headRef) {\n return /^[0-9a-fA-F]{7,64}$/.test(headRef);\n}\n\nfunction getReviewedGitSnapshot(gitContext) {\n const reviewedStatus = gitContext && isObject(gitContext.status) ? gitContext.status : null;\n if (!reviewedStatus) return null;\n const branch = normalizedGitBranch(reviewedStatus.branch);\n const headSha = typeof reviewedStatus.headSha === "string" ? reviewedStatus.headSha : "";\n if (!branch || !headSha) return null;\n return { branch: branch, headSha: headSha };\n}\n\nfunction matchesReviewedGitBranch(reviewedSnapshot, status) {\n const currentBranch = normalizedGitBranch(status.branch);\n return currentBranch.length > 0 && currentBranch === reviewedSnapshot.branch;\n}\n\nfunction normalizedGitBranch(branch) {\n if (typeof branch !== "string") return "";\n const trimmed = branch.trim();\n if (!trimmed || trimmed === "HEAD (no branch)") return "";\n return trimmed;\n}\n\nfunction looksNonLocalTarget(target) {\n const text = String(target || "").toLowerCase();\n return (\n text.indexOf("http://") !== -1 ||\n text.indexOf("https://") !== -1 ||\n /(^|\\s)(pr|pull request)\\s*#?\\d+/.test(text)\n );\n}\n\nfunction selectFixIssues(candidates, verifications, input, final) {\n const selected = [];\n const allowedIds = input.fixIssueIds || [];\n const finalFilter = getFinalIssueFilter(final);\n if (finalFilter.kind === "none") return selected;\n for (let index = 0; index < candidates.length; index += 1) {\n const issue = candidates[index];\n const issueId = stableIssueId(issue, index);\n if (finalFilter.kind === "ids" && finalFilter.ids[issueId] !== true) continue;\n if (allowedIds.length > 0 && allowedIds.indexOf(issueId) === -1) continue;\n const verification = findVerificationForIssue(issue, issueId, index, verifications);\n if (!verification || verification.verdict !== "valid" || verification.confidence === "low")\n continue;\n selected.push({ issueId: issueId, issue: issue, verification: verification, index: index });\n if (selected.length >= input.maxFixes) break;\n }\n return selected;\n}\n\nfunction getFinalIssueFilter(final) {\n if (final && final.verifiedIssueCount === 0) return { kind: "none" };\n if (final && Array.isArray(final.verifiedIssueIds)) {\n const ids = sanitizeStringArray(final.verifiedIssueIds);\n if (ids.length === 0) return { kind: "none" };\n const map = {};\n for (const id of ids) map[id] = true;\n return { kind: "ids", ids: map };\n }\n return { kind: "none" };\n}\n\nfunction stableIssueId(issue, index) {\n return issue && typeof issue.id === "string" && issue.id.trim()\n ? issue.id.trim()\n : "triaged-" + index;\n}\n\nfunction findVerificationForIssue(issue, issueId, index, verifications) {\n for (const verification of verifications) {\n if (!hasNonEmptyIssueId(verification)) continue;\n if (verification.issueId === issueId) return verification;\n if (issue && typeof issue.id === "string" && verification.issueId === issue.id)\n return verification;\n }\n return null;\n}\n\nfunction hasNonEmptyIssueId(verification) {\n return (\n verification &&\n typeof verification.issueId === "string" &&\n verification.issueId.trim().length > 0\n );\n}\n\nfunction matchesReportedIssueId(output, expectedIssueId) {\n return output && output.issueId === expectedIssueId;\n}\n\nfunction issueIdMismatchReason(source, expectedIssueId, output) {\n const reported =\n output && typeof output.issueId === "string" && output.issueId.length > 0\n ? output.issueId\n : "";\n return source + " reported issueId " + reported + " for " + expectedIssueId;\n}\n\nfunction summarizeFixIssue(issueId, issue) {\n return {\n issueId: issueId,\n severity: issue && issue.severity ? issue.severity : "",\n title: issue && issue.title ? issue.title : "",\n filePaths: issue && Array.isArray(issue.filePaths) ? issue.filePaths : [],\n };\n}\n\nfunction summarizeFixAttempt(issueId, result) {\n const output = result && result.structuredOutput ? result.structuredOutput : {};\n return {\n issueId: issueId,\n taskId: result ? result.taskId : undefined,\n status: output.status || "unknown",\n summary: output.summary || "",\n validation: Array.isArray(output.validation) ? output.validation : [],\n };\n}\n\nfunction summarizeResolution(issueId, result) {\n const output = result && result.structuredOutput ? result.structuredOutput : {};\n return {\n issueId: issueId,\n resolverTaskId: result ? result.taskId : undefined,\n status: output.status || "unknown",\n summary: output.summary || "",\n };\n}\n\nfunction safeApplyPatch(applyPatch, id, source, expectedHeadSha) {\n try {\n const spec = { id: id, source: source, target: "parent", onConflict: "return" };\n if (expectedHeadSha) spec.expectedHeadSha = expectedHeadSha;\n const result = applyPatch(spec);\n return normalizePatchApplication(source, result);\n } catch (error) {\n return {\n sourceTaskId: source ? source.taskId : undefined,\n status: "failed",\n error: formatError(error),\n };\n }\n}\n\nfunction normalizePatchApplication(source, result) {\n const status =\n result && result.status\n ? result.status\n : result && result.success === true\n ? "applied"\n : "failed";\n return {\n sourceTaskId: source ? source.taskId : undefined,\n status: status,\n appliedCommits: result ? result.appliedCommits : undefined,\n headCommitSha: result ? result.headCommitSha : undefined,\n conflictPaths: result ? result.conflictPaths : undefined,\n failedPatchSubject: result ? result.failedPatchSubject : undefined,\n error: result ? result.error : undefined,\n projectResults: result ? result.projectResults : undefined,\n };\n}\n\nfunction getAppliedHeadCommitSha(application) {\n if (\n application &&\n typeof application.headCommitSha === "string" &&\n application.headCommitSha.length > 0\n ) {\n return application.headCommitSha;\n }\n const projectResults =\n application && Array.isArray(application.projectResults) ? application.projectResults : [];\n for (let index = projectResults.length - 1; index >= 0; index -= 1) {\n const projectResult = projectResults[index];\n if (\n projectResult &&\n typeof projectResult.headCommitSha === "string" &&\n projectResult.headCommitSha.length > 0\n ) {\n return projectResult.headCommitSha;\n }\n }\n return "";\n}\n\nfunction buildFixPrompt(input, item) {\n return (\n "Fix exactly one verified deep-review finding. Make minimal code changes, add or update behavioral tests when appropriate, run targeted validation when practical, and create one or more git commits before reporting if code changed. If already fixed, not fixable, or more information is needed, report that status instead of inventing a patch. Do not push, open PRs, or perform unrelated cleanup.\\n\\n" +\n "Review target:\\n" +\n renderReviewInput(input) +\n "\\n\\nIssue ID: " +\n item.issueId +\n "\\nFinding:\\n" +\n JSON.stringify(item.issue, null, 2) +\n "\\n\\nVerification:\\n" +\n JSON.stringify(item.verification, null, 2)\n );\n}\n\nfunction buildResolverPrompt(input, item, fixerResult, application, integratedIssues) {\n return (\n "Resolve the git-am conflict for one auto-fix patch. Preserve the original issue intent and avoid unrelated changes. Replay the original patch with task_apply_git_patch in your workspace using dry_run false, resolve conflicts, git add, git am --continue, and commit follow-up resolved changes if needed. If earlier fixes already solved the issue, report already-resolved without inventing changes. Do not push or open PRs.\\n\\n" +\n "Review target:\\n" +\n renderReviewInput(input) +\n "\\n\\nIssue:\\n" +\n JSON.stringify(item.issue, null, 2) +\n "\\n\\nVerification:\\n" +\n JSON.stringify(item.verification, null, 2) +\n "\\n\\nFailing fixer task ID: " +\n (fixerResult ? fixerResult.taskId : "unknown") +\n "\\nApply conflict:\\n" +\n JSON.stringify(application, null, 2) +\n "\\nAlready integrated issue IDs:\\n" +\n JSON.stringify(integratedIssues, null, 2)\n );\n}\n\nfunction buildValidationPrompt(input, final, fixResult) {\n return (\n "Validate the auto-fixes now integrated in the parent workspace. Run the review validation plan and targeted tests/checks relevant to applied fixes. Do not edit files, create commits, apply patches, push, or open PRs. Report pass, fail, or not-run with commands and key failures.\\n\\n" +\n "Review target:\\n" +\n renderReviewInput(input) +\n "\\n\\nReview validation plan:\\n" +\n JSON.stringify(final.validationPlan || [], null, 2) +\n "\\n\\nAuto-fix result so far:\\n" +\n JSON.stringify(fixResult, null, 2)\n );\n}\n\nfunction renderFixMarkdown(fix) {\n const appliedCount =\n countStatus(fix.applications, "applied") +\n countStatus(fix.attempts, "already-fixed") +\n countStatus(fix.resolutions, "already-resolved");\n const conflictResolvedCount = countResolvedConflicts(fix.resolutions);\n const validationStatus = fix.validation ? fix.validation.status : "not-run";\n let markdown = "\\n\\n---\\n\\n## Auto-fix results\\n\\n";\n if (fix.skippedReason) {\n markdown += "- Skipped: " + fix.skippedReason + "\\n";\n return markdown;\n }\n markdown += "- Selected: " + fix.selectedIssues.length + " verified findings\\n";\n markdown += "- Fixed/applied: " + appliedCount + "\\n";\n markdown +=\n "- Already fixed/resolved: " +\n (countStatus(fix.attempts, "already-fixed") +\n countStatus(fix.resolutions, "already-resolved")) +\n "\\n";\n markdown += "- Not fixed: " + fix.unresolved.length + "\\n";\n markdown += "- Conflicts resolved: " + conflictResolvedCount + "\\n";\n markdown += "- Validation: " + validationStatus + "\\n";\n markdown += renderFixFailureDetails(fix);\n return markdown;\n}\n\nfunction countResolvedConflicts(resolutions) {\n let count = 0;\n for (const resolution of resolutions || []) {\n if (!resolution) continue;\n if (resolution.status === "already-resolved") count += 1;\n else if (resolution.status === "resolved" && resolution.applyStatus === "applied") count += 1;\n }\n return count;\n}\n\nfunction renderFixFailureDetails(fix) {\n let markdown = "";\n if (fix.unresolved && fix.unresolved.length > 0) {\n markdown += "\\nUnresolved issues:\\n";\n for (const unresolved of fix.unresolved) {\n markdown +=\n "- " + renderFixIssueLabel(fix, unresolved.issueId) + ": " + unresolved.reason + "\\n";\n }\n }\n const failedApplications = (fix.applications || []).filter(function (application) {\n return application && application.status !== "applied";\n });\n if (failedApplications.length > 0) {\n markdown += "\\nPatch application details:\\n";\n for (const application of failedApplications) {\n const details = renderPatchApplicationDetails(application);\n markdown +=\n "- " +\n renderFixIssueLabel(fix, application.issueId) +\n ": " +\n application.status +\n (details ? " — " + details : "") +\n "\\n";\n }\n }\n if (\n fix.validation &&\n Array.isArray(fix.validation.failures) &&\n fix.validation.failures.length > 0\n ) {\n markdown += "\\nValidation failures:\\n";\n for (const failure of fix.validation.failures) {\n markdown += "- " + failure + "\\n";\n }\n }\n return markdown;\n}\n\nfunction renderPatchApplicationDetails(application) {\n const details = [];\n if (application.conflictPaths && application.conflictPaths.length > 0)\n details.push("conflicts: " + application.conflictPaths.join(", "));\n if (application.failedPatchSubject)\n details.push("failed patch: " + application.failedPatchSubject);\n if (application.error) details.push(application.error);\n return details.join("; ");\n}\n\nfunction renderFixIssueLabel(fix, issueId) {\n for (const issue of fix.selectedIssues || []) {\n if (issue && issue.issueId === issueId && issue.title)\n return issueId + " (" + issue.title + ")";\n }\n return issueId;\n}\n\nfunction countStatus(items, status) {\n let count = 0;\n for (const item of items || []) {\n if (item && item.status === status) count += 1;\n }\n return count;\n}\n\nfunction fixAttemptSchema() {\n return {\n type: "object",\n required: ["issueId", "status", "summary", "validation", "commitCreated"],\n additionalProperties: false,\n properties: {\n issueId: { type: "string" },\n status: { type: "string", enum: ["fixed", "already-fixed", "not-fixable", "needs-info"] },\n summary: { type: "string" },\n validation: { type: "array", items: { type: "string" } },\n commitCreated: { type: "boolean" },\n },\n };\n}\n\nfunction fixResolverSchema() {\n return {\n type: "object",\n required: ["issueId", "status", "summary", "validation", "commitCreated"],\n additionalProperties: false,\n properties: {\n issueId: { type: "string" },\n status: { type: "string", enum: ["resolved", "already-resolved", "unresolved"] },\n summary: { type: "string" },\n validation: { type: "array", items: { type: "string" } },\n commitCreated: { type: "boolean" },\n },\n };\n}\n\nfunction fixValidationSchema() {\n return {\n type: "object",\n required: ["status", "commands", "summary", "failures"],\n additionalProperties: false,\n properties: {\n status: { type: "string", enum: ["passed", "failed", "not-run"] },\n commands: { type: "array", items: { type: "string" } },\n summary: { type: "string" },\n failures: { type: "array", items: { type: "string" } },\n },\n };\n}\n\nfunction normalizeDeepReviewArgs(args) {\n const normalized = {\n target: "current workspace changes",\n baseRef: "",\n headRef: "",\n diff: "",\n files: [],\n instructions: "",\n gitSnapshot: "",\n explicitDiff: false,\n explicitFiles: false,\n includeGitContext: false,\n maxCandidates: 12,\n fix: false,\n loop: false,\n maxFixes: 5,\n maxLoopIterations: 5,\n fixIssueIds: [],\n };\n\n if (typeof args === "string" && args.trim()) {\n applyFixFlags(normalized, args.trim());\n return normalized;\n }\n\n if (!args || typeof args !== "object") {\n return normalized;\n }\n\n let textualTarget = "";\n if (typeof args.target === "string" && args.target.trim()) textualTarget = args.target.trim();\n else if (typeof args.input === "string" && args.input.trim()) textualTarget = args.input.trim();\n else if (typeof args.pr === "string" && args.pr.trim()) textualTarget = args.pr.trim();\n else if (typeof args.branch === "string" && args.branch.trim())\n textualTarget = args.branch.trim();\n if (textualTarget) applyFixFlags(normalized, textualTarget);\n\n if (typeof args.baseRef === "string") normalized.baseRef = args.baseRef.trim();\n else if (typeof args.base === "string") normalized.baseRef = args.base.trim();\n\n if (typeof args.headRef === "string") normalized.headRef = args.headRef.trim();\n else if (typeof args.head === "string") normalized.headRef = args.head.trim();\n\n if (typeof args.diff === "string" && args.diff.trim().length > 0) {\n normalized.diff = args.diff;\n normalized.explicitDiff = true;\n }\n if (typeof args.instructions === "string") normalized.instructions = args.instructions.trim();\n else if (typeof args.notes === "string") normalized.instructions = args.notes.trim();\n\n if (Array.isArray(args.files)) {\n normalized.files = args.files\n .filter(function (file) {\n return typeof file === "string" && file.trim().length > 0;\n })\n .map(function (file) {\n return file.trim();\n });\n normalized.explicitFiles = normalized.files.length > 0;\n }\n\n if (typeof args.includeGitContext === "boolean") {\n normalized.includeGitContext = args.includeGitContext;\n }\n\n if (typeof args.maxCandidates === "number" && args.maxCandidates > 0) {\n normalized.maxCandidates = Math.min(20, Math.max(1, Math.floor(args.maxCandidates)));\n }\n\n if (typeof args.maxFixes === "number" && args.maxFixes > 0) {\n normalized.maxFixes = Math.min(20, Math.max(1, Math.floor(args.maxFixes)));\n }\n if (typeof args.maxLoopIterations === "number" && args.maxLoopIterations > 0) {\n normalized.maxLoopIterations = Math.min(10, Math.max(1, Math.floor(args.maxLoopIterations)));\n }\n if (Array.isArray(args.fixIssueIds)) {\n normalized.fixIssueIds = sanitizeStringArray(args.fixIssueIds);\n }\n if (typeof args.fix === "boolean") {\n normalized.fix = args.fix;\n }\n if (typeof args.loop === "boolean") {\n normalized.loop = args.loop;\n }\n\n return normalized;\n}\n\nfunction applyFixFlags(normalized, text) {\n const parsed = parseTrailingFixFlags(text);\n for (const flag of parsed.flags) {\n if (flag === "--fix") normalized.fix = true;\n else if (flag === "--no-fix") normalized.fix = false;\n else if (flag === "--loop") normalized.loop = true;\n else if (flag === "--no-loop") normalized.loop = false;\n }\n const target = parsed.target.trim().split(/ +/).join(" ");\n if (target) normalized.target = target;\n}\n\nfunction parseTrailingFixFlags(text) {\n const parts = String(text || "")\n .trim()\n .split(/ +/)\n .filter(Boolean);\n const flags = [];\n while (parts.length > 0) {\n const last = parts[parts.length - 1];\n if (last !== "--fix" && last !== "--no-fix" && last !== "--loop" && last !== "--no-loop") break;\n flags.unshift(last);\n parts.pop();\n }\n return { target: parts.join(" "), flags: flags };\n}\n\nfunction sanitizeStringArray(values) {\n const result = [];\n for (const value of values) {\n if (typeof value !== "string") continue;\n const trimmed = value.trim();\n if (trimmed && result.indexOf(trimmed) === -1) result.push(trimmed);\n }\n return result;\n}\n\nfunction shouldCollectGitReviewContext(input) {\n return input.includeGitContext || (!input.explicitFiles && !input.explicitDiff);\n}\n\nfunction collectGitReviewContext(action, input, log, stepSuffix) {\n const gitInput = buildGitActionInput(input);\n const failures = [];\n const builtInOnly = true;\n const status = tryGitAction(log, failures, "git.status", function () {\n return action.git.status({\n id: workflowStepId("git-status", stepSuffix),\n input: { includeIgnored: false },\n builtInOnly,\n }).output;\n });\n const changedFiles = tryGitAction(log, failures, "git.changedFiles", function () {\n return action.git.changedFiles({\n id: workflowStepId("git-changed-files", stepSuffix),\n input: gitInput,\n builtInOnly,\n }).output;\n });\n const diffStat = tryGitAction(log, failures, "git.diffStat", function () {\n return action.git.diffStat({\n id: workflowStepId("git-diff-stat", stepSuffix),\n input: gitInput,\n builtInOnly,\n }).output;\n });\n const diff = tryGitAction(log, failures, "git.diff", function () {\n return action.git.diff({\n id: workflowStepId("git-diff", stepSuffix),\n input: gitInput,\n builtInOnly,\n }).output;\n });\n const commits = tryGitAction(log, failures, "git.commitsBetween", function () {\n const commitsInput = copyGitActionInput(gitInput);\n commitsInput.limit = 20;\n return action.git.commitsBetween({\n id: workflowStepId("git-commits-between", stepSuffix),\n input: commitsInput,\n builtInOnly,\n }).output;\n });\n const context = {\n status: isObject(status) ? status : null,\n changedFiles: isObject(changedFiles) ? changedFiles : null,\n diffStat: isObject(diffStat) ? diffStat : null,\n diff: isObject(diff) ? diff : null,\n commits: isObject(commits) ? commits : null,\n failures: failures,\n explicitRefs: Boolean(input.baseRef || input.headRef),\n };\n if (!hasAnyGitContext(context)) {\n log("Git workflow actions unavailable; continuing with caller-provided review context", {\n failures: failures,\n });\n return null;\n }\n const files = collectGitFiles(context);\n log("Captured Git review context", {\n branch: context.status ? context.status.branch : "unknown",\n fileCount: files.length,\n hasDiff:\n context.diff != null &&\n (hasText(context.diff.branch) ||\n hasText(context.diff.staged) ||\n hasText(context.diff.unstaged)),\n failureCount: failures.length,\n });\n return context;\n}\n\nfunction tryGitAction(log, failures, name, fn) {\n try {\n return fn();\n } catch (error) {\n const failure = { action: name, error: formatError(error) };\n failures.push(failure);\n log("Git workflow action failed; continuing with partial review context", failure);\n return null;\n }\n}\n\nfunction hasAnyGitContext(gitContext) {\n return Boolean(\n gitContext.status ||\n gitContext.changedFiles ||\n gitContext.diffStat ||\n gitContext.diff ||\n gitContext.commits\n );\n}\n\nfunction hasResolvedBranchContext(gitContext) {\n return Boolean(\n hasResolvedGitRefContext(gitContext.changedFiles) ||\n hasResolvedGitRefContext(gitContext.diffStat) ||\n hasResolvedGitRefContext(gitContext.diff) ||\n hasResolvedGitRefContext(gitContext.commits)\n );\n}\n\nfunction hasResolvedGitRefContext(value) {\n return isObject(value) && hasText(value.base) && hasText(value.head) && hasText(value.mergeBase);\n}\n\nfunction isObject(value) {\n return value != null && typeof value === "object" && !Array.isArray(value);\n}\n\nfunction buildGitActionInput(input) {\n const gitInput = {};\n if (input.baseRef) gitInput.base = input.baseRef;\n if (input.headRef) gitInput.head = input.headRef;\n return gitInput;\n}\n\nfunction copyGitActionInput(input) {\n const copy = {};\n if (input.base) copy.base = input.base;\n if (input.head) copy.head = input.head;\n return copy;\n}\n\nfunction applyGitContextToReviewInput(input, gitContext) {\n if (gitContext == null) return;\n if ((input.explicitFiles || input.explicitDiff) && !input.includeGitContext) return;\n if (!input.explicitFiles) {\n const files = collectGitFiles(gitContext);\n if (files.length > 0) input.files = files;\n }\n if (!input.explicitDiff && gitContext.diff != null) {\n const diff = renderGitDiff(gitContext.diff);\n if (diff.length > 0) input.diff = truncateText(diff, 60000);\n }\n input.gitSnapshot = truncateText(renderGitSnapshot(gitContext), 20000);\n}\n\nfunction collectGitFiles(gitContext) {\n const files = [];\n if (gitContext == null) return files;\n if (gitContext.changedFiles != null) {\n addFileEntries(files, gitContext.changedFiles.branch);\n addFileEntries(files, gitContext.changedFiles.staged);\n addFileEntries(files, gitContext.changedFiles.unstaged);\n addFilePaths(files, gitContext.changedFiles.untracked);\n }\n if (gitContext.status != null) {\n addFileEntries(files, gitContext.status.staged);\n addFileEntries(files, gitContext.status.unstaged);\n addFilePaths(files, gitContext.status.untracked);\n }\n return files;\n}\n\nfunction addFileEntries(files, entries) {\n if (!Array.isArray(entries)) return;\n for (const entry of entries) {\n if (entry && typeof entry === "object") {\n addFilePath(files, entry.path);\n addFilePath(files, entry.oldPath);\n }\n }\n}\n\nfunction addFilePaths(files, paths) {\n if (!Array.isArray(paths)) return;\n for (const path of paths) {\n addFilePath(files, path);\n }\n}\n\nfunction addFilePath(files, path) {\n if (typeof path !== "string") return;\n const trimmed = path.trim();\n if (trimmed.length === 0 || files.indexOf(trimmed) !== -1) return;\n files.push(trimmed);\n}\n\nfunction renderGitSnapshot(gitContext) {\n const sections = [];\n if (gitContext.status != null) {\n const status = gitContext.status;\n sections.push(\n "Repository status: branch " +\n valueOrUnknown(status.branch) +\n (status.upstream ? " tracking " + status.upstream : "") +\n "; staged " +\n arrayLength(status.staged) +\n "; unstaged " +\n arrayLength(status.unstaged) +\n "; untracked " +\n arrayLength(status.untracked)\n );\n }\n if (gitContext.explicitRefs && !hasResolvedBranchContext(gitContext)) {\n sections.push(\n "WARNING: Requested base/head refs could not be resolved for automatic branch diff and commit capture; Git context may include only repository status or working-tree changes."\n );\n }\n if (Array.isArray(gitContext.failures) && gitContext.failures.length > 0) {\n sections.push(\n "Git context warnings:\\n" +\n gitContext.failures\n .map(function (failure) {\n return "- " + valueOrUnknown(failure.action) + ": " + valueOrUnknown(failure.error);\n })\n .join("\\n")\n );\n }\n const files = collectGitFiles(gitContext);\n if (files.length > 0) {\n sections.push("Changed files from parent workspace Git snapshot: " + files.join(", "));\n }\n if (gitContext.commits != null && Array.isArray(gitContext.commits.commits)) {\n const commits = gitContext.commits.commits;\n if (commits.length > 0) {\n sections.push(\n "Commits since " +\n valueOrUnknown(gitContext.commits.base) +\n ":\\n" +\n commits\n .map(function (commit) {\n return "- " + valueOrUnknown(commit.shortHash) + " " + valueOrUnknown(commit.subject);\n })\n .join("\\n")\n );\n }\n }\n if (gitContext.diffStat != null) {\n const statSections = [];\n if (hasText(gitContext.diffStat.branch))\n statSections.push("Branch diff stat:\\n" + gitContext.diffStat.branch);\n if (hasText(gitContext.diffStat.staged))\n statSections.push("Staged diff stat:\\n" + gitContext.diffStat.staged);\n if (hasText(gitContext.diffStat.unstaged))\n statSections.push("Unstaged diff stat:\\n" + gitContext.diffStat.unstaged);\n if (statSections.length > 0) sections.push(statSections.join("\\n\\n"));\n }\n if (gitContext.status != null && arrayLength(gitContext.status.untracked) > 0) {\n sections.push(\n "Untracked file contents are not included in the automatic diff snapshot; review agents only receive their paths unless the caller supplied args.diff."\n );\n }\n if (gitContext.diff != null && isDiffTruncated(gitContext.diff)) {\n sections.push(\n "One or more automatic diff sections were truncated by workflow action output limits."\n );\n }\n return sections.join("\\n\\n");\n}\n\nfunction renderGitDiff(diff) {\n const parts = [];\n if (hasText(diff.branch)) {\n parts.push(\n "Branch diff (" +\n valueOrUnknown(diff.base) +\n ".." +\n valueOrUnknown(diff.head) +\n ")\\n" +\n diff.branch\n );\n }\n if (hasText(diff.staged)) {\n parts.push("Staged diff\\n" + diff.staged);\n }\n if (hasText(diff.unstaged)) {\n parts.push("Unstaged diff\\n" + diff.unstaged);\n }\n if (isDiffTruncated(diff)) {\n parts.push("NOTE: one or more diff sections were truncated by workflow action output limits.");\n }\n return parts.join("\\n\\n");\n}\n\nfunction isDiffTruncated(diff) {\n return Boolean(\n diff &&\n diff.truncated &&\n (diff.truncated.branch || diff.truncated.staged || diff.truncated.unstaged)\n );\n}\n\nfunction truncateText(text, maxLength) {\n if (text.length <= maxLength) return text;\n return (\n text.slice(0, maxLength) +\n "\\n[truncated by deep-review-workflow after " +\n maxLength +\n " characters]"\n );\n}\n\nfunction hasText(value) {\n return typeof value === "string" && value.trim().length > 0;\n}\n\nfunction arrayLength(value) {\n return Array.isArray(value) ? value.length : 0;\n}\n\nfunction valueOrUnknown(value) {\n return typeof value === "string" && value.length > 0 ? value : "unknown";\n}\n\nfunction formatError(error) {\n return error && typeof error.message === "string" ? error.message : String(error);\n}\n\nfunction renderReviewInput(input) {\n return [\n "Target: " + input.target,\n input.baseRef ? "Base ref: " + input.baseRef : "",\n input.headRef ? "Head ref: " + input.headRef : "",\n input.files.length > 0 ? "Files: " + input.files.join(", ") : "",\n input.gitSnapshot ? "Git snapshot:\\n" + input.gitSnapshot : "",\n input.instructions ? "Reviewer instructions: " + input.instructions : "",\n input.diff ? "Diff snapshot:\\n~~~diff\\n" + input.diff + "\\n~~~" : "",\n ]\n .filter(Boolean)\n .join("\\n");\n}\n\nfunction selectReviewLanes(lanes) {\n const defaults = ["correctness", "tests", "architecture"];\n const allowed = {\n correctness: true,\n tests: true,\n architecture: true,\n "security-reliability": true,\n "ux-a11y": true,\n "docs-dx": true,\n };\n const requested = Array.isArray(lanes) && lanes.length > 0 ? lanes : defaults;\n const result = [];\n for (const lane of requested) {\n if (allowed[lane] && result.indexOf(lane) === -1) {\n result.push(lane);\n }\n }\n for (const fallback of defaults) {\n if (result.indexOf(fallback) === -1) {\n result.push(fallback);\n }\n }\n return result.slice(0, 6);\n}\n\nfunction lanePrompt(lane) {\n const prompts = {\n correctness:\n "Review for logic bugs, edge cases, races, state-machine violations, and broken invariants.",\n tests: "Review test coverage, determinism, missing regression tests, and validation commands.",\n architecture:\n "Review consistency with existing architecture, boundaries, naming, abstractions, and maintainability.",\n "security-reliability":\n "Review security, trust boundaries, path traversal, injection, data corruption, reliability, and performance risks.",\n "ux-a11y":\n "Review user-facing behavior, accessibility, keyboard flow, visual consistency, and empty/loading/error states.",\n "docs-dx":\n "Review documentation, developer experience, scripts, public API clarity, and migration concerns.",\n };\n return prompts[lane] || prompts.correctness;\n}\n\nfunction issueListSchema() {\n return {\n type: "object",\n required: ["issues"],\n additionalProperties: false,\n properties: {\n issues: {\n type: "array",\n items: issueSchema(),\n },\n },\n };\n}\n\nfunction issueSchema() {\n return {\n type: "object",\n required: [\n "id",\n "severity",\n "category",\n "title",\n "rationale",\n "evidence",\n "filePaths",\n "confidence",\n ],\n additionalProperties: false,\n properties: {\n id: { type: "string" },\n severity: { type: "string", enum: ["P0", "P1", "P2", "P3", "P4"] },\n category: { type: "string" },\n title: { type: "string" },\n rationale: { type: "string" },\n evidence: { type: "string" },\n filePaths: { type: "array", items: { type: "string" } },\n suggestedFix: { type: "string" },\n validation: { type: "string" },\n confidence: { type: "string", enum: ["low", "medium", "high"] },\n },\n };\n}\n\nfunction scopeSchema() {\n return {\n type: "object",\n required: ["summary", "files", "riskAreas", "lanes"],\n additionalProperties: false,\n properties: {\n summary: { type: "string" },\n files: { type: "array", items: { type: "string" } },\n riskAreas: { type: "array", items: { type: "string" } },\n lanes: {\n type: "array",\n items: {\n type: "string",\n enum: [\n "correctness",\n "tests",\n "architecture",\n "security-reliability",\n "ux-a11y",\n "docs-dx",\n ],\n },\n },\n },\n };\n}\n\nfunction verificationSchema() {\n return {\n type: "object",\n required: ["issueId", "verdict", "confidence", "rationale"],\n additionalProperties: false,\n properties: {\n issueId: { type: "string" },\n verdict: {\n type: "string",\n enum: ["valid", "duplicate", "overstated", "not-repro", "needs-info"],\n },\n confidence: { type: "string", enum: ["low", "medium", "high"] },\n rationale: { type: "string" },\n evidence: { type: "string" },\n suggestedSeverity: { type: "string", enum: ["P0", "P1", "P2", "P3", "P4"] },\n },\n };\n}\n\nfunction finalSynthesisSchema() {\n return {\n type: "object",\n required: ["verifiedIssueCount", "verifiedIssueIds", "risk", "validationPlan"],\n additionalProperties: false,\n properties: {\n verifiedIssueCount: { type: "number" },\n risk: { type: "string", enum: ["low", "medium", "high"] },\n validationPlan: { type: "array", items: { type: "string" } },\n verifiedIssueIds: { type: "array", items: { type: "string" } },\n discardedIssueCount: { type: "number" },\n },\n };\n}\n\nfunction flatten(arrays) {\n const out = [];\n for (const array of arrays) {\n for (const item of array) {\n out.push(item);\n }\n }\n return out;\n}\n', + '// description: Coordinate adversarial review agents to find, verify, and synthesize code review findings.\n//\n// Keep the lightweight /deep-review skill; this workflow is the heavier structured path with\n// adversarial verification for review findings.\n\n// Verification/fixer fan-out scales with maxCandidates/maxFixes (clamped at 20);\n// cap live agents so raising those budgets queues work instead of launching one\n// wave of 20 concurrent agents. Matches deep-research\'s smart-mode verifier cap.\nconst MAX_PARALLEL_AGENTS = 12;\nexport default function deepReviewWorkflow({\n args,\n phase,\n log,\n agent,\n action,\n parallelAgents,\n applyPatch,\n}) {\n const exploreAgentId = "explore";\n const reasoningAgentId = "exec";\n // Scope discovery stays on Explore; review judgment uses Exec for users with fast Explore defaults.\n const readOnlyReviewPrompt =\n "This is a read-only deep code review task. Do not edit files, create commits, apply patches, push branches, or open PRs. Inspect repository evidence only as needed and report findings.\\n\\n";\n const input = normalizeDeepReviewArgs(args);\n if (input.loop && !input.fix) {\n throw new Error("--loop requires --fix for deep-review-workflow");\n }\n if (input.loop) {\n return runDeepReviewLoop({\n input: input,\n phase: phase,\n log: log,\n agent: agent,\n action: action,\n parallelAgents: parallelAgents,\n applyPatch: applyPatch,\n exploreAgentId: exploreAgentId,\n reasoningAgentId: reasoningAgentId,\n readOnlyReviewPrompt: readOnlyReviewPrompt,\n });\n }\n return runDeepReviewPass({\n input: input,\n phase: phase,\n log: log,\n agent: agent,\n action: action,\n parallelAgents: parallelAgents,\n applyPatch: applyPatch,\n exploreAgentId: exploreAgentId,\n reasoningAgentId: reasoningAgentId,\n readOnlyReviewPrompt: readOnlyReviewPrompt,\n stepSuffix: "",\n iteration: 0,\n skipFixWhenNoVerifiedIssues: false,\n }).reviewResult;\n}\n\nfunction runDeepReviewLoop(context) {\n const passes = [];\n let stopReason = "";\n let remainingFixBudget = context.input.maxFixes;\n let loopHeadRef = context.input.headRef;\n for (let iteration = 1; iteration <= context.input.maxLoopIterations; iteration += 1) {\n context.phase("loop-iteration", {\n iteration: iteration,\n maxIterations: context.input.maxLoopIterations,\n remainingFixBudget: remainingFixBudget,\n });\n const budgetExhaustedReadOnlyCheck = remainingFixBudget <= 0;\n const iterationInput = cloneDeepReviewInput(context.input);\n iterationInput.headRef = loopHeadRef;\n iterationInput.maxFixes = remainingFixBudget;\n if (budgetExhaustedReadOnlyCheck) iterationInput.fix = false;\n const pass = runDeepReviewPass({\n input: iterationInput,\n phase: context.phase,\n log: context.log,\n agent: context.agent,\n action: context.action,\n parallelAgents: context.parallelAgents,\n applyPatch: context.applyPatch,\n exploreAgentId: context.exploreAgentId,\n reasoningAgentId: context.reasoningAgentId,\n readOnlyReviewPrompt: context.readOnlyReviewPrompt,\n stepSuffix: "loop-" + iteration,\n iteration: iteration,\n skipFixWhenNoVerifiedIssues: true,\n });\n passes.push(pass.reviewResult);\n if (budgetExhaustedReadOnlyCheck) {\n stopReason = hasVerifiedIssues(pass.reviewResult.structuredOutput.final)\n ? "fix-budget-exhausted"\n : "no-verified-issues";\n return buildLoopResult(context.input, passes, stopReason, remainingFixBudget);\n }\n const fixProgress = reviewResultHasFixProgress(pass.reviewResult);\n remainingFixBudget = Math.max(0, remainingFixBudget - countSelectedFixes(pass.reviewResult));\n if (fixProgress) loopHeadRef = "";\n stopReason = getLoopStopReason(pass.reviewResult, remainingFixBudget);\n if (stopReason === "fix-budget-exhausted" && iteration < context.input.maxLoopIterations)\n continue;\n if (stopReason) {\n return buildLoopResult(context.input, passes, stopReason, remainingFixBudget);\n }\n }\n return buildLoopResult(context.input, passes, "max-iterations", remainingFixBudget);\n}\n\nfunction runDeepReviewPass(context) {\n const input = cloneDeepReviewInput(context.input);\n const gitContext = shouldCollectGitReviewContext(input)\n ? collectGitReviewContext(context.action, input, context.log, context.stepSuffix)\n : null;\n applyGitContextToReviewInput(input, gitContext);\n const maxCandidates = input.maxCandidates;\n\n context.phase(\n "scope",\n withLoopIteration(\n {\n target: input.target,\n fileCount: input.files.length,\n hasDiffSnapshot: input.diff.length > 0,\n hasGitSnapshot: input.gitSnapshot.length > 0,\n },\n context.iteration\n )\n );\n const scope = context.agent({\n id: workflowStepId("scope-review-surface", context.stepSuffix),\n title: "Scope review surface",\n agentId: context.exploreAgentId,\n prompt:\n "Scope this code review. Identify changed files, likely intent, touched layers, highest-risk areas, and which review lanes should run. Use repository evidence; do not assume the diff is complete if refs are provided.\\n\\n" +\n renderReviewInput(input),\n outputSchema: scopeSchema(),\n });\n\n const lanes = selectReviewLanes(scope.structuredOutput.lanes);\n context.log("Selected deep review lanes", withLoopIteration({ lanes: lanes }, context.iteration));\n\n context.phase("lane-review", withLoopIteration({ lanes: lanes }, context.iteration));\n const laneReviews = context.parallelAgents(\n lanes.map(function (lane) {\n return {\n id: workflowStepId("review-" + lane, context.stepSuffix),\n title: "Review lane: " + lane,\n agentId: context.reasoningAgentId,\n prompt:\n context.readOnlyReviewPrompt +\n lanePrompt(lane) +\n "\\n\\nReview target:\\n" +\n renderReviewInput(input) +\n "\\n\\nScoped review surface:\\n" +\n JSON.stringify(scope.structuredOutput, null, 2) +\n "\\n\\nReturn only concrete, actionable findings with file paths and evidence. Prefer an empty issues array over speculative feedback.",\n outputSchema: issueListSchema(),\n };\n })\n );\n const laneIssues = flatten(\n laneReviews.map(function (review) {\n return review.structuredOutput.issues || [];\n })\n );\n context.log(\n "Lane review produced candidate issues",\n withLoopIteration({ count: laneIssues.length }, context.iteration)\n );\n\n if (laneIssues.length === 0) {\n context.log(\n "No candidate issues from review lanes; skipping triage, verification, and final synthesis",\n withLoopIteration(\n { skippedPhases: ["triage-dedupe", "adversarial-verification", "final-synthesis"] },\n context.iteration\n )\n );\n const reviewResult = buildNoVerifiedIssuesReviewResult({\n input: input,\n scope: scope.structuredOutput,\n laneIssues: laneIssues,\n triagedIssues: [],\n verifications: [],\n discardedIssueCount: 0,\n });\n return finishDeepReviewPass({\n context: context,\n input: input,\n reviewResult: reviewResult,\n gitContext: gitContext,\n candidates: [],\n verifications: [],\n final: reviewResult.structuredOutput.final,\n });\n }\n\n context.phase(\n "triage-dedupe",\n withLoopIteration({ candidateCount: laneIssues.length }, context.iteration)\n );\n const triage = context.agent({\n id: workflowStepId("triage-candidate-issues", context.stepSuffix),\n title: "Triage and dedupe review findings",\n agentId: context.reasoningAgentId,\n prompt:\n context.readOnlyReviewPrompt +\n "Deduplicate and consolidate these candidate code review findings. Merge duplicates across lanes (combining their evidence), normalize severity, and drop only clearly non-actionable items; keep borderline findings so adversarial verification can make the validity call. Order the issues by severity and impact, most severe first — only the first " +\n maxCandidates +\n " survive the candidate budget.\\n\\n" +\n "Review target:\\n" +\n renderReviewInput(input) +\n "\\n\\nCandidate issues:\\n" +\n JSON.stringify(laneIssues, null, 2),\n outputSchema: issueListSchema(),\n });\n // Triage is prompted to emit issues most-severe-first, but re-sort defensively so a\n // high-severity finding emitted past the budget cutoff is never silently dropped.\n const rankedIssues = sortIssuesBySeverity(triage.structuredOutput.issues || []);\n const candidates = rankedIssues.slice(0, maxCandidates);\n context.log(\n "Triaged candidate issues",\n withLoopIteration(\n {\n candidateCount: rankedIssues.length,\n selectedCount: candidates.length,\n droppedByBudget: rankedIssues.slice(candidates.length).map(function (issue, index) {\n return stableIssueId(issue, candidates.length + index);\n }),\n },\n context.iteration\n )\n );\n\n if (candidates.length === 0) {\n context.log(\n "No candidate issues after triage; skipping verification and final synthesis",\n withLoopIteration(\n { skippedPhases: ["adversarial-verification", "final-synthesis"] },\n context.iteration\n )\n );\n const reviewResult = buildNoVerifiedIssuesReviewResult({\n input: input,\n scope: scope.structuredOutput,\n laneIssues: laneIssues,\n triagedIssues: [],\n verifications: [],\n discardedIssueCount: laneIssues.length,\n });\n return finishDeepReviewPass({\n context: context,\n input: input,\n reviewResult: reviewResult,\n gitContext: gitContext,\n candidates: [],\n verifications: [],\n final: reviewResult.structuredOutput.final,\n });\n }\n\n context.phase(\n "adversarial-verification",\n withLoopIteration({ candidateCount: candidates.length }, context.iteration)\n );\n const verificationResults =\n candidates.length > 0\n ? context.parallelAgents(\n candidates.map(function (issue, index) {\n return {\n id: workflowStepId("verify-issue-" + index, context.stepSuffix),\n title: "Verify review finding " + (index + 1),\n agentId: context.reasoningAgentId,\n prompt:\n context.readOnlyReviewPrompt +\n "Adversarially verify this code review finding. Try to disprove it. Inspect relevant code paths and tests. Decide whether it is valid, duplicate, overstated, not reproducible, or needs more information.\\n\\n" +\n "Review target:\\n" +\n renderReviewInput(input) +\n "\\n\\nFinding:\\n" +\n JSON.stringify(issue, null, 2),\n outputSchema: verificationSchema(),\n };\n }),\n { maxParallel: MAX_PARALLEL_AGENTS }\n )\n : [];\n const verifications = verificationResults.map(function (verification) {\n return verification.structuredOutput;\n });\n context.log(\n "Verified candidate issues",\n withLoopIteration({ count: verifications.length }, context.iteration)\n );\n\n if (!hasActionableVerification(verifications)) {\n context.log(\n "No verifier upheld an actionable candidate issue; skipping final synthesis",\n withLoopIteration({ skippedPhases: ["final-synthesis"] }, context.iteration)\n );\n const reviewResult = buildNoVerifiedIssuesReviewResult({\n input: input,\n scope: scope.structuredOutput,\n laneIssues: laneIssues,\n triagedIssues: candidates,\n verifications: verifications,\n discardedIssueCount: candidates.length,\n });\n return finishDeepReviewPass({\n context: context,\n input: input,\n reviewResult: reviewResult,\n gitContext: gitContext,\n candidates: candidates,\n verifications: verifications,\n final: reviewResult.structuredOutput.final,\n });\n }\n\n context.phase(\n "final-synthesis",\n withLoopIteration(\n {\n candidateCount: candidates.length,\n verificationCount: verifications.length,\n },\n context.iteration\n )\n );\n const final = context.agent({\n id: workflowStepId("synthesize-review", context.stepSuffix),\n title: "Synthesize final deep review",\n agentId: context.reasoningAgentId,\n prompt:\n context.readOnlyReviewPrompt +\n "Write the final code review. Include only findings that remain actionable after adversarial verification. Use severity P0-P4, file paths, issue IDs, and concrete evidence. If there are no verified issues, say so clearly. Include questions and a validation plan. Set verifiedIssueIds to the issue IDs included in the final review when any are verified.\\n\\n" +\n "Scoped review surface:\\n" +\n JSON.stringify(scope.structuredOutput, null, 2) +\n "\\n\\nTriaged issues:\\n" +\n JSON.stringify(candidates, null, 2) +\n "\\n\\nVerification results:\\n" +\n JSON.stringify(verifications, null, 2),\n outputSchema: finalSynthesisSchema(),\n });\n\n const reviewResult = {\n reportMarkdown: final.reportMarkdown,\n structuredOutput: {\n target: input.target,\n scope: scope.structuredOutput,\n laneIssues: laneIssues,\n triagedIssues: candidates,\n verification: verifications,\n final: final.structuredOutput,\n },\n };\n return finishDeepReviewPass({\n context: context,\n input: input,\n reviewResult: reviewResult,\n gitContext: gitContext,\n candidates: candidates,\n verifications: verifications,\n final: final.structuredOutput,\n });\n}\n\nfunction buildNoVerifiedIssuesReviewResult(options) {\n return {\n reportMarkdown: "# Deep Review\\n\\nNo verified issues.",\n structuredOutput: {\n target: options.input.target,\n scope: options.scope,\n laneIssues: options.laneIssues,\n triagedIssues: options.triagedIssues,\n verification: options.verifications,\n final: {\n verifiedIssueCount: 0,\n verifiedIssueIds: [],\n risk: "low",\n validationPlan: [],\n discardedIssueCount: options.discardedIssueCount,\n },\n },\n };\n}\n\nfunction hasActionableVerification(verifications) {\n for (const verification of verifications) {\n if (!verification) continue;\n if (verification.verdict === "valid" || verification.verdict === "overstated") return true;\n }\n return false;\n}\n\nfunction finishDeepReviewPass(options) {\n const context = options.context;\n const reviewResult = options.reviewResult;\n if (!options.input.fix) return { reviewResult: reviewResult, gitContext: options.gitContext };\n if (context.skipFixWhenNoVerifiedIssues && !hasVerifiedIssues(options.final)) {\n return { reviewResult: reviewResult, gitContext: options.gitContext };\n }\n\n context.phase("fix-preflight", withLoopIteration({ requested: true }, context.iteration));\n const fixResult = runDeepReviewFix({\n input: options.input,\n action: context.action,\n log: context.log,\n agent: context.agent,\n parallelAgents: context.parallelAgents,\n applyPatch: context.applyPatch,\n candidates: options.candidates,\n verifications: options.verifications,\n final: options.final,\n gitContext: options.gitContext,\n exploreAgentId: context.exploreAgentId,\n reasoningAgentId: context.reasoningAgentId,\n stepSuffix: context.stepSuffix,\n });\n reviewResult.structuredOutput.fix = fixResult;\n reviewResult.reportMarkdown = reviewResult.reportMarkdown + renderFixMarkdown(fixResult);\n return { reviewResult: reviewResult, gitContext: options.gitContext };\n}\n\nfunction cloneDeepReviewInput(input) {\n return {\n target: input.target,\n baseRef: input.baseRef,\n headRef: input.headRef,\n diff: input.diff,\n files: input.files.slice(),\n instructions: input.instructions,\n gitSnapshot: input.gitSnapshot,\n explicitDiff: input.explicitDiff,\n explicitFiles: input.explicitFiles,\n includeGitContext: input.includeGitContext,\n maxCandidates: input.maxCandidates,\n fix: input.fix,\n loop: input.loop,\n maxFixes: input.maxFixes,\n maxLoopIterations: input.maxLoopIterations,\n fixIssueIds: input.fixIssueIds.slice(),\n };\n}\n\nfunction workflowStepId(baseId, suffix) {\n return suffix ? baseId + "-" + suffix : baseId;\n}\n\nfunction withLoopIteration(details, iteration) {\n if (iteration) details.iteration = iteration;\n return details;\n}\n\n// Stable sort: P0 first, unknown/invalid severity last, ties keep triage emit order.\nfunction sortIssuesBySeverity(issues) {\n return issues\n .map(function (issue, index) {\n return { issue: issue, index: index };\n })\n .sort(function (a, b) {\n const rankDelta = issueSeverityRank(a.issue) - issueSeverityRank(b.issue);\n return rankDelta !== 0 ? rankDelta : a.index - b.index;\n })\n .map(function (entry) {\n return entry.issue;\n });\n}\n\nfunction issueSeverityRank(issue) {\n const match =\n isObject(issue) && typeof issue.severity === "string"\n ? /^P([0-4])$/.exec(issue.severity)\n : null;\n return match ? Number(match[1]) : 5;\n}\n\nfunction hasVerifiedIssues(final) {\n if (!final) return false;\n if (typeof final.verifiedIssueCount === "number") return final.verifiedIssueCount > 0;\n return Array.isArray(final.verifiedIssueIds) && final.verifiedIssueIds.length > 0;\n}\n\nfunction reviewResultHasFixProgress(reviewResult) {\n const output = reviewResult && reviewResult.structuredOutput ? reviewResult.structuredOutput : {};\n const fix = output.fix;\n return Boolean(fix && fixHasProgress(fix));\n}\n\nfunction countSelectedFixes(reviewResult) {\n const output = reviewResult && reviewResult.structuredOutput ? reviewResult.structuredOutput : {};\n const fix = output.fix;\n return fix && Array.isArray(fix.selectedIssues) ? fix.selectedIssues.length : 0;\n}\n\nfunction getLoopStopReason(reviewResult, remainingFixBudget) {\n const output = reviewResult && reviewResult.structuredOutput ? reviewResult.structuredOutput : {};\n if (!hasVerifiedIssues(output.final)) return "no-verified-issues";\n const fix = output.fix;\n if (!fix) return "no-fix-attempted";\n if (fix.skippedReason) return "fix-skipped";\n if (!fix.selectedIssues || fix.selectedIssues.length === 0) return "no-fixable-issues";\n if (!fixHasProgress(fix)) return "no-fix-progress";\n const validationStatus = fix.validation ? fix.validation.status : "not-run";\n if (validationStatus === "failed") return "validation-failed";\n if (validationStatus !== "passed") return "validation-not-run";\n if (remainingFixBudget <= 0) return "fix-budget-exhausted";\n return "";\n}\n\nfunction fixHasProgress(fix) {\n return countStatus(fix.applications, "applied") > 0;\n}\n\nfunction buildLoopResult(input, passes, stopReason, remainingFixBudget) {\n const loop = {\n requested: true,\n completed: stopReason === "no-verified-issues",\n iterations: passes.length,\n maxIterations: input.maxLoopIterations,\n remainingFixBudget: remainingFixBudget,\n stopReason: stopReason,\n };\n const structuredOutput = {\n target: input.target,\n loop: loop,\n passes: passes.map(function (pass, index) {\n return {\n iteration: index + 1,\n result: pass.structuredOutput,\n };\n }),\n };\n if (passes.length > 0) {\n const latest = passes[passes.length - 1].structuredOutput;\n structuredOutput.latest = latest;\n structuredOutput.final = latest.final || null;\n if (latest.fix) structuredOutput.fix = latest.fix;\n }\n return {\n reportMarkdown: renderLoopMarkdown(passes, loop),\n structuredOutput: structuredOutput,\n };\n}\n\nfunction renderLoopMarkdown(passes, loop) {\n let markdown = "# Deep Review Loop\\n\\n";\n markdown += "- Iterations: " + loop.iterations + " / " + loop.maxIterations + "\\n";\n markdown += "- Stop reason: " + loop.stopReason + "\\n";\n markdown += "- Completed: " + (loop.completed ? "yes" : "no") + "\\n";\n for (let index = 0; index < passes.length; index += 1) {\n markdown += "\\n\\n---\\n\\n## Loop iteration " + (index + 1) + "\\n\\n";\n markdown += passes[index].reportMarkdown || "";\n }\n return markdown;\n}\n\nfunction runDeepReviewFix(context) {\n const input = context.input;\n const baseFix = {\n requested: true,\n selectedIssues: [],\n attempts: [],\n applications: [],\n resolutions: [],\n unresolved: [],\n };\n const preflight = collectFixPreflight(\n context.action,\n context.log,\n input,\n context.gitContext,\n context.stepSuffix\n );\n if (preflight.skippedReason) {\n baseFix.skippedReason = preflight.skippedReason;\n return baseFix;\n }\n let expectedHeadSha = preflight.expectedHeadSha;\n\n const selected = selectFixIssues(context.candidates, context.verifications, input, context.final);\n baseFix.selectedIssues = selected.map(function (item) {\n return summarizeFixIssue(item.issueId, item.issue);\n });\n context.log("Selected deep review fixes", {\n selectedCount: selected.length,\n skippedCount: context.candidates.length - selected.length,\n });\n if (selected.length === 0) return baseFix;\n\n const fixerResults = context.parallelAgents(\n selected.map(function (item, index) {\n return {\n id: workflowStepId("fix-issue-" + index, context.stepSuffix),\n title: "Fix verified review finding " + (index + 1),\n agentId: context.reasoningAgentId,\n prompt: buildFixPrompt(input, item),\n outputSchema: fixAttemptSchema(),\n };\n }),\n { maxParallel: MAX_PARALLEL_AGENTS }\n );\n\n const integratedIssues = [];\n for (let index = 0; index < selected.length; index += 1) {\n const item = selected[index];\n const fixerResult = fixerResults[index];\n const attempt = summarizeFixAttempt(item.issueId, fixerResult);\n baseFix.attempts.push(attempt);\n const attemptOutput =\n fixerResult && fixerResult.structuredOutput ? fixerResult.structuredOutput : {};\n if (!matchesReportedIssueId(attemptOutput, item.issueId)) {\n baseFix.unresolved.push({\n issueId: item.issueId,\n reason: issueIdMismatchReason("fixer", item.issueId, attemptOutput),\n });\n continue;\n }\n if (attemptOutput.status !== "fixed" || attemptOutput.commitCreated !== true) {\n if (attemptOutput.status !== "already-fixed") {\n baseFix.unresolved.push({\n issueId: item.issueId,\n reason: attemptOutput.status || "not-fixed",\n });\n }\n continue;\n }\n\n const application = safeApplyPatch(\n context.applyPatch,\n workflowStepId("apply-fix-" + index, context.stepSuffix),\n fixerResult,\n expectedHeadSha\n );\n application.issueId = item.issueId;\n baseFix.applications.push(application);\n if (application.status === "applied") {\n expectedHeadSha = getAppliedHeadCommitSha(application) || expectedHeadSha;\n integratedIssues.push(item.issueId);\n continue;\n }\n if (application.status !== "conflict") {\n baseFix.unresolved.push({\n issueId: item.issueId,\n reason: application.error || application.status,\n });\n continue;\n }\n\n const resolver = context.agent({\n id: workflowStepId("resolve-fix-" + index + "-conflict", context.stepSuffix),\n title: "Resolve auto-fix conflict " + (index + 1),\n agentId: context.reasoningAgentId,\n prompt: buildResolverPrompt(input, item, fixerResult, application, integratedIssues),\n outputSchema: fixResolverSchema(),\n });\n const resolution = summarizeResolution(item.issueId, resolver);\n baseFix.resolutions.push(resolution);\n const resolutionOutput = resolver && resolver.structuredOutput ? resolver.structuredOutput : {};\n if (!matchesReportedIssueId(resolutionOutput, item.issueId)) {\n baseFix.unresolved.push({\n issueId: item.issueId,\n reason: issueIdMismatchReason("resolver", item.issueId, resolutionOutput),\n });\n continue;\n }\n if (resolutionOutput.status === "already-resolved") {\n integratedIssues.push(item.issueId);\n continue;\n }\n if (resolutionOutput.status === "resolved" && resolutionOutput.commitCreated === true) {\n const resolvedApplication = safeApplyPatch(\n context.applyPatch,\n workflowStepId("apply-resolved-fix-" + index, context.stepSuffix),\n resolver,\n expectedHeadSha\n );\n resolvedApplication.issueId = item.issueId;\n resolution.applyStatus = resolvedApplication.status;\n baseFix.applications.push(resolvedApplication);\n if (resolvedApplication.status === "applied") {\n expectedHeadSha = getAppliedHeadCommitSha(resolvedApplication) || expectedHeadSha;\n integratedIssues.push(item.issueId);\n } else {\n baseFix.unresolved.push({\n issueId: item.issueId,\n reason: resolvedApplication.error || resolvedApplication.status,\n });\n }\n } else {\n baseFix.unresolved.push({\n issueId: item.issueId,\n reason: resolutionOutput.status || "unresolved-conflict",\n });\n }\n }\n\n if (integratedIssues.length > 0) {\n const validation = context.agent({\n id: workflowStepId("validate-auto-fixes", context.stepSuffix),\n title: "Validate applied auto-fixes",\n agentId: context.exploreAgentId,\n prompt: buildValidationPrompt(input, context.final, baseFix),\n outputSchema: fixValidationSchema(),\n });\n baseFix.validation = validation.structuredOutput;\n }\n return baseFix;\n}\n\nfunction collectFixPreflight(action, log, input, gitContext, stepSuffix) {\n if (input.explicitDiff)\n return {\n skippedReason: "auto-fix requires a local current workspace target, not an explicit diff",\n };\n if (looksNonLocalTarget(input.target))\n return { skippedReason: "auto-fix requires a local current workspace target" };\n let status = null;\n try {\n status = action.git.status({\n id: workflowStepId("fix-git-status", stepSuffix),\n input: { includeIgnored: false, head: input.headRef || "HEAD" },\n builtInOnly: true,\n cache: false,\n }).output;\n } catch (error) {\n const message = formatError(error);\n log("Git status unavailable for auto-fix preflight", { error: message });\n return { skippedReason: "auto-fix requires a fresh local Git status" };\n }\n if (!isObject(status)) return { skippedReason: "auto-fix requires a fresh local Git status" };\n if (!isCurrentReviewHead(input, status)) {\n return {\n skippedReason: "auto-fix requires the reviewed head ref to be the current checked-out branch",\n };\n }\n const reviewedSnapshot = getReviewedGitSnapshot(gitContext);\n if (!reviewedSnapshot) {\n return { skippedReason: "auto-fix requires a reviewed Git branch and HEAD snapshot" };\n }\n if (!matchesReviewedGitBranch(reviewedSnapshot, status)) {\n return {\n skippedReason: "auto-fix requires the current Git branch to match the reviewed snapshot",\n };\n }\n if (\n arrayLength(status.staged) > 0 ||\n arrayLength(status.unstaged) > 0 ||\n arrayLength(status.untracked) > 0\n ) {\n return { skippedReason: "auto-fix requires a clean committed local worktree" };\n }\n return { status: status, expectedHeadSha: reviewedSnapshot.headSha };\n}\n\nfunction isCurrentReviewHead(input, status) {\n if (!input.headRef) return true;\n const headRef = String(input.headRef).trim();\n if (!headRef || headRef === "HEAD") return true;\n const branch = normalizedGitBranch(status.branch);\n const currentBranchRef = branch ? "refs/heads/" + branch : "";\n if (branch && (headRef === branch || headRef === currentBranchRef)) return true;\n const requestedHeadRef =\n typeof status.requestedHeadRef === "string" ? status.requestedHeadRef : "";\n if (requestedHeadRef.length > 0) return false;\n if (!isExplicitCommitShaRef(headRef)) return false;\n const headSha = typeof status.headSha === "string" ? status.headSha : "";\n const requestedHeadSha =\n typeof status.requestedHeadSha === "string" ? status.requestedHeadSha : "";\n return Boolean(headSha && requestedHeadSha && headSha === requestedHeadSha);\n}\n\nfunction isExplicitCommitShaRef(headRef) {\n return /^[0-9a-fA-F]{7,64}$/.test(headRef);\n}\n\nfunction getReviewedGitSnapshot(gitContext) {\n const reviewedStatus = gitContext && isObject(gitContext.status) ? gitContext.status : null;\n if (!reviewedStatus) return null;\n const branch = normalizedGitBranch(reviewedStatus.branch);\n const headSha = typeof reviewedStatus.headSha === "string" ? reviewedStatus.headSha : "";\n if (!branch || !headSha) return null;\n return { branch: branch, headSha: headSha };\n}\n\nfunction matchesReviewedGitBranch(reviewedSnapshot, status) {\n const currentBranch = normalizedGitBranch(status.branch);\n return currentBranch.length > 0 && currentBranch === reviewedSnapshot.branch;\n}\n\nfunction normalizedGitBranch(branch) {\n if (typeof branch !== "string") return "";\n const trimmed = branch.trim();\n if (!trimmed || trimmed === "HEAD (no branch)") return "";\n return trimmed;\n}\n\nfunction looksNonLocalTarget(target) {\n const text = String(target || "").toLowerCase();\n return (\n text.indexOf("http://") !== -1 ||\n text.indexOf("https://") !== -1 ||\n /(^|\\s)(pr|pull request)\\s*#?\\d+/.test(text)\n );\n}\n\nfunction selectFixIssues(candidates, verifications, input, final) {\n const selected = [];\n const allowedIds = input.fixIssueIds || [];\n const finalFilter = getFinalIssueFilter(final);\n if (finalFilter.kind === "none") return selected;\n for (let index = 0; index < candidates.length; index += 1) {\n const issue = candidates[index];\n const issueId = stableIssueId(issue, index);\n if (finalFilter.kind === "ids" && finalFilter.ids[issueId] !== true) continue;\n if (allowedIds.length > 0 && allowedIds.indexOf(issueId) === -1) continue;\n const verification = findVerificationForIssue(issue, issueId, index, verifications);\n if (!verification || verification.verdict !== "valid" || verification.confidence === "low")\n continue;\n selected.push({ issueId: issueId, issue: issue, verification: verification, index: index });\n if (selected.length >= input.maxFixes) break;\n }\n return selected;\n}\n\nfunction getFinalIssueFilter(final) {\n if (final && final.verifiedIssueCount === 0) return { kind: "none" };\n if (final && Array.isArray(final.verifiedIssueIds)) {\n const ids = sanitizeStringArray(final.verifiedIssueIds);\n if (ids.length === 0) return { kind: "none" };\n const map = {};\n for (const id of ids) map[id] = true;\n return { kind: "ids", ids: map };\n }\n return { kind: "none" };\n}\n\nfunction stableIssueId(issue, index) {\n return issue && typeof issue.id === "string" && issue.id.trim()\n ? issue.id.trim()\n : "triaged-" + index;\n}\n\nfunction findVerificationForIssue(issue, issueId, index, verifications) {\n for (const verification of verifications) {\n if (!hasNonEmptyIssueId(verification)) continue;\n if (verification.issueId === issueId) return verification;\n if (issue && typeof issue.id === "string" && verification.issueId === issue.id)\n return verification;\n }\n return null;\n}\n\nfunction hasNonEmptyIssueId(verification) {\n return (\n verification &&\n typeof verification.issueId === "string" &&\n verification.issueId.trim().length > 0\n );\n}\n\nfunction matchesReportedIssueId(output, expectedIssueId) {\n return output && output.issueId === expectedIssueId;\n}\n\nfunction issueIdMismatchReason(source, expectedIssueId, output) {\n const reported =\n output && typeof output.issueId === "string" && output.issueId.length > 0\n ? output.issueId\n : "";\n return source + " reported issueId " + reported + " for " + expectedIssueId;\n}\n\nfunction summarizeFixIssue(issueId, issue) {\n return {\n issueId: issueId,\n severity: issue && issue.severity ? issue.severity : "",\n title: issue && issue.title ? issue.title : "",\n filePaths: issue && Array.isArray(issue.filePaths) ? issue.filePaths : [],\n };\n}\n\nfunction summarizeFixAttempt(issueId, result) {\n const output = result && result.structuredOutput ? result.structuredOutput : {};\n return {\n issueId: issueId,\n taskId: result ? result.taskId : undefined,\n status: output.status || "unknown",\n summary: output.summary || "",\n validation: Array.isArray(output.validation) ? output.validation : [],\n };\n}\n\nfunction summarizeResolution(issueId, result) {\n const output = result && result.structuredOutput ? result.structuredOutput : {};\n return {\n issueId: issueId,\n resolverTaskId: result ? result.taskId : undefined,\n status: output.status || "unknown",\n summary: output.summary || "",\n };\n}\n\nfunction safeApplyPatch(applyPatch, id, source, expectedHeadSha) {\n try {\n const spec = { id: id, source: source, target: "parent", onConflict: "return" };\n if (expectedHeadSha) spec.expectedHeadSha = expectedHeadSha;\n const result = applyPatch(spec);\n return normalizePatchApplication(source, result);\n } catch (error) {\n return {\n sourceTaskId: source ? source.taskId : undefined,\n status: "failed",\n error: formatError(error),\n };\n }\n}\n\nfunction normalizePatchApplication(source, result) {\n const status =\n result && result.status\n ? result.status\n : result && result.success === true\n ? "applied"\n : "failed";\n return {\n sourceTaskId: source ? source.taskId : undefined,\n status: status,\n appliedCommits: result ? result.appliedCommits : undefined,\n headCommitSha: result ? result.headCommitSha : undefined,\n conflictPaths: result ? result.conflictPaths : undefined,\n failedPatchSubject: result ? result.failedPatchSubject : undefined,\n error: result ? result.error : undefined,\n projectResults: result ? result.projectResults : undefined,\n };\n}\n\nfunction getAppliedHeadCommitSha(application) {\n if (\n application &&\n typeof application.headCommitSha === "string" &&\n application.headCommitSha.length > 0\n ) {\n return application.headCommitSha;\n }\n const projectResults =\n application && Array.isArray(application.projectResults) ? application.projectResults : [];\n for (let index = projectResults.length - 1; index >= 0; index -= 1) {\n const projectResult = projectResults[index];\n if (\n projectResult &&\n typeof projectResult.headCommitSha === "string" &&\n projectResult.headCommitSha.length > 0\n ) {\n return projectResult.headCommitSha;\n }\n }\n return "";\n}\n\nfunction buildFixPrompt(input, item) {\n return (\n "Fix exactly one verified deep-review finding. Make minimal code changes, add or update behavioral tests when appropriate, run targeted validation when practical, and create one or more git commits before reporting if code changed. If already fixed, not fixable, or more information is needed, report that status instead of inventing a patch. Do not push, open PRs, or perform unrelated cleanup.\\n\\n" +\n "Review target:\\n" +\n renderReviewInput(input) +\n "\\n\\nIssue ID: " +\n item.issueId +\n "\\nFinding:\\n" +\n JSON.stringify(item.issue, null, 2) +\n "\\n\\nVerification:\\n" +\n JSON.stringify(item.verification, null, 2)\n );\n}\n\nfunction buildResolverPrompt(input, item, fixerResult, application, integratedIssues) {\n return (\n "Resolve the git-am conflict for one auto-fix patch. Preserve the original issue intent and avoid unrelated changes. Replay the original patch with task_apply_git_patch in your workspace using dry_run false, resolve conflicts, git add, git am --continue, and commit follow-up resolved changes if needed. If earlier fixes already solved the issue, report already-resolved without inventing changes. Do not push or open PRs.\\n\\n" +\n "Review target:\\n" +\n renderReviewInput(input) +\n "\\n\\nIssue:\\n" +\n JSON.stringify(item.issue, null, 2) +\n "\\n\\nVerification:\\n" +\n JSON.stringify(item.verification, null, 2) +\n "\\n\\nFailing fixer task ID: " +\n (fixerResult ? fixerResult.taskId : "unknown") +\n "\\nApply conflict:\\n" +\n JSON.stringify(application, null, 2) +\n "\\nAlready integrated issue IDs:\\n" +\n JSON.stringify(integratedIssues, null, 2)\n );\n}\n\nfunction buildValidationPrompt(input, final, fixResult) {\n return (\n "Validate the auto-fixes now integrated in the parent workspace. Run the review validation plan and targeted tests/checks relevant to applied fixes. Do not edit files, create commits, apply patches, push, or open PRs. Report pass, fail, or not-run with commands and key failures.\\n\\n" +\n "Review target:\\n" +\n renderReviewInput(input) +\n "\\n\\nReview validation plan:\\n" +\n JSON.stringify(final.validationPlan || [], null, 2) +\n "\\n\\nAuto-fix result so far:\\n" +\n JSON.stringify(fixResult, null, 2)\n );\n}\n\nfunction renderFixMarkdown(fix) {\n const appliedCount =\n countStatus(fix.applications, "applied") +\n countStatus(fix.attempts, "already-fixed") +\n countStatus(fix.resolutions, "already-resolved");\n const conflictResolvedCount = countResolvedConflicts(fix.resolutions);\n const validationStatus = fix.validation ? fix.validation.status : "not-run";\n let markdown = "\\n\\n---\\n\\n## Auto-fix results\\n\\n";\n if (fix.skippedReason) {\n markdown += "- Skipped: " + fix.skippedReason + "\\n";\n return markdown;\n }\n markdown += "- Selected: " + fix.selectedIssues.length + " verified findings\\n";\n markdown += "- Fixed/applied: " + appliedCount + "\\n";\n markdown +=\n "- Already fixed/resolved: " +\n (countStatus(fix.attempts, "already-fixed") +\n countStatus(fix.resolutions, "already-resolved")) +\n "\\n";\n markdown += "- Not fixed: " + fix.unresolved.length + "\\n";\n markdown += "- Conflicts resolved: " + conflictResolvedCount + "\\n";\n markdown += "- Validation: " + validationStatus + "\\n";\n markdown += renderFixFailureDetails(fix);\n return markdown;\n}\n\nfunction countResolvedConflicts(resolutions) {\n let count = 0;\n for (const resolution of resolutions || []) {\n if (!resolution) continue;\n if (resolution.status === "already-resolved") count += 1;\n else if (resolution.status === "resolved" && resolution.applyStatus === "applied") count += 1;\n }\n return count;\n}\n\nfunction renderFixFailureDetails(fix) {\n let markdown = "";\n if (fix.unresolved && fix.unresolved.length > 0) {\n markdown += "\\nUnresolved issues:\\n";\n for (const unresolved of fix.unresolved) {\n markdown +=\n "- " + renderFixIssueLabel(fix, unresolved.issueId) + ": " + unresolved.reason + "\\n";\n }\n }\n const failedApplications = (fix.applications || []).filter(function (application) {\n return application && application.status !== "applied";\n });\n if (failedApplications.length > 0) {\n markdown += "\\nPatch application details:\\n";\n for (const application of failedApplications) {\n const details = renderPatchApplicationDetails(application);\n markdown +=\n "- " +\n renderFixIssueLabel(fix, application.issueId) +\n ": " +\n application.status +\n (details ? " — " + details : "") +\n "\\n";\n }\n }\n if (\n fix.validation &&\n Array.isArray(fix.validation.failures) &&\n fix.validation.failures.length > 0\n ) {\n markdown += "\\nValidation failures:\\n";\n for (const failure of fix.validation.failures) {\n markdown += "- " + failure + "\\n";\n }\n }\n return markdown;\n}\n\nfunction renderPatchApplicationDetails(application) {\n const details = [];\n if (application.conflictPaths && application.conflictPaths.length > 0)\n details.push("conflicts: " + application.conflictPaths.join(", "));\n if (application.failedPatchSubject)\n details.push("failed patch: " + application.failedPatchSubject);\n if (application.error) details.push(application.error);\n return details.join("; ");\n}\n\nfunction renderFixIssueLabel(fix, issueId) {\n for (const issue of fix.selectedIssues || []) {\n if (issue && issue.issueId === issueId && issue.title)\n return issueId + " (" + issue.title + ")";\n }\n return issueId;\n}\n\nfunction countStatus(items, status) {\n let count = 0;\n for (const item of items || []) {\n if (item && item.status === status) count += 1;\n }\n return count;\n}\n\nfunction fixAttemptSchema() {\n return {\n type: "object",\n required: ["issueId", "status", "summary", "validation", "commitCreated"],\n additionalProperties: false,\n properties: {\n issueId: { type: "string" },\n status: { type: "string", enum: ["fixed", "already-fixed", "not-fixable", "needs-info"] },\n summary: { type: "string" },\n validation: { type: "array", items: { type: "string" } },\n commitCreated: { type: "boolean" },\n },\n };\n}\n\nfunction fixResolverSchema() {\n return {\n type: "object",\n required: ["issueId", "status", "summary", "validation", "commitCreated"],\n additionalProperties: false,\n properties: {\n issueId: { type: "string" },\n status: { type: "string", enum: ["resolved", "already-resolved", "unresolved"] },\n summary: { type: "string" },\n validation: { type: "array", items: { type: "string" } },\n commitCreated: { type: "boolean" },\n },\n };\n}\n\nfunction fixValidationSchema() {\n return {\n type: "object",\n required: ["status", "commands", "summary", "failures"],\n additionalProperties: false,\n properties: {\n status: { type: "string", enum: ["passed", "failed", "not-run"] },\n commands: { type: "array", items: { type: "string" } },\n summary: { type: "string" },\n failures: { type: "array", items: { type: "string" } },\n },\n };\n}\n\nfunction normalizeDeepReviewArgs(args) {\n const normalized = {\n target: "current workspace changes",\n baseRef: "",\n headRef: "",\n diff: "",\n files: [],\n instructions: "",\n gitSnapshot: "",\n explicitDiff: false,\n explicitFiles: false,\n includeGitContext: false,\n maxCandidates: 12,\n fix: false,\n loop: false,\n maxFixes: 5,\n maxLoopIterations: 5,\n fixIssueIds: [],\n };\n\n if (typeof args === "string" && args.trim()) {\n applyFixFlags(normalized, args.trim());\n return normalized;\n }\n\n if (!args || typeof args !== "object") {\n return normalized;\n }\n\n let textualTarget = "";\n if (typeof args.target === "string" && args.target.trim()) textualTarget = args.target.trim();\n else if (typeof args.input === "string" && args.input.trim()) textualTarget = args.input.trim();\n else if (typeof args.pr === "string" && args.pr.trim()) textualTarget = args.pr.trim();\n else if (typeof args.branch === "string" && args.branch.trim())\n textualTarget = args.branch.trim();\n if (textualTarget) applyFixFlags(normalized, textualTarget);\n\n if (typeof args.baseRef === "string") normalized.baseRef = args.baseRef.trim();\n else if (typeof args.base === "string") normalized.baseRef = args.base.trim();\n\n if (typeof args.headRef === "string") normalized.headRef = args.headRef.trim();\n else if (typeof args.head === "string") normalized.headRef = args.head.trim();\n\n if (typeof args.diff === "string" && args.diff.trim().length > 0) {\n normalized.diff = args.diff;\n normalized.explicitDiff = true;\n }\n if (typeof args.instructions === "string") normalized.instructions = args.instructions.trim();\n else if (typeof args.notes === "string") normalized.instructions = args.notes.trim();\n\n if (Array.isArray(args.files)) {\n normalized.files = args.files\n .filter(function (file) {\n return typeof file === "string" && file.trim().length > 0;\n })\n .map(function (file) {\n return file.trim();\n });\n normalized.explicitFiles = normalized.files.length > 0;\n }\n\n if (typeof args.includeGitContext === "boolean") {\n normalized.includeGitContext = args.includeGitContext;\n }\n\n if (typeof args.maxCandidates === "number" && args.maxCandidates > 0) {\n normalized.maxCandidates = Math.min(20, Math.max(1, Math.floor(args.maxCandidates)));\n }\n\n if (typeof args.maxFixes === "number" && args.maxFixes > 0) {\n normalized.maxFixes = Math.min(20, Math.max(1, Math.floor(args.maxFixes)));\n }\n if (typeof args.maxLoopIterations === "number" && args.maxLoopIterations > 0) {\n normalized.maxLoopIterations = Math.min(10, Math.max(1, Math.floor(args.maxLoopIterations)));\n }\n if (Array.isArray(args.fixIssueIds)) {\n normalized.fixIssueIds = sanitizeStringArray(args.fixIssueIds);\n }\n if (typeof args.fix === "boolean") {\n normalized.fix = args.fix;\n }\n if (typeof args.loop === "boolean") {\n normalized.loop = args.loop;\n }\n\n return normalized;\n}\n\nfunction applyFixFlags(normalized, text) {\n const parsed = parseTrailingFixFlags(text);\n for (const flag of parsed.flags) {\n if (flag === "--fix") normalized.fix = true;\n else if (flag === "--no-fix") normalized.fix = false;\n else if (flag === "--loop") normalized.loop = true;\n else if (flag === "--no-loop") normalized.loop = false;\n }\n const target = parsed.target.trim().split(/ +/).join(" ");\n if (target) normalized.target = target;\n}\n\nfunction parseTrailingFixFlags(text) {\n const parts = String(text || "")\n .trim()\n .split(/ +/)\n .filter(Boolean);\n const flags = [];\n while (parts.length > 0) {\n const last = parts[parts.length - 1];\n if (last !== "--fix" && last !== "--no-fix" && last !== "--loop" && last !== "--no-loop") break;\n flags.unshift(last);\n parts.pop();\n }\n return { target: parts.join(" "), flags: flags };\n}\n\nfunction sanitizeStringArray(values) {\n const result = [];\n for (const value of values) {\n if (typeof value !== "string") continue;\n const trimmed = value.trim();\n if (trimmed && result.indexOf(trimmed) === -1) result.push(trimmed);\n }\n return result;\n}\n\nfunction shouldCollectGitReviewContext(input) {\n return input.includeGitContext || (!input.explicitFiles && !input.explicitDiff);\n}\n\nfunction collectGitReviewContext(action, input, log, stepSuffix) {\n const gitInput = buildGitActionInput(input);\n const failures = [];\n const builtInOnly = true;\n const status = tryGitAction(log, failures, "git.status", function () {\n return action.git.status({\n id: workflowStepId("git-status", stepSuffix),\n input: { includeIgnored: false },\n builtInOnly,\n }).output;\n });\n const changedFiles = tryGitAction(log, failures, "git.changedFiles", function () {\n return action.git.changedFiles({\n id: workflowStepId("git-changed-files", stepSuffix),\n input: gitInput,\n builtInOnly,\n }).output;\n });\n const diffStat = tryGitAction(log, failures, "git.diffStat", function () {\n return action.git.diffStat({\n id: workflowStepId("git-diff-stat", stepSuffix),\n input: gitInput,\n builtInOnly,\n }).output;\n });\n const diff = tryGitAction(log, failures, "git.diff", function () {\n return action.git.diff({\n id: workflowStepId("git-diff", stepSuffix),\n input: gitInput,\n builtInOnly,\n }).output;\n });\n const commits = tryGitAction(log, failures, "git.commitsBetween", function () {\n const commitsInput = copyGitActionInput(gitInput);\n commitsInput.limit = 20;\n return action.git.commitsBetween({\n id: workflowStepId("git-commits-between", stepSuffix),\n input: commitsInput,\n builtInOnly,\n }).output;\n });\n const context = {\n status: isObject(status) ? status : null,\n changedFiles: isObject(changedFiles) ? changedFiles : null,\n diffStat: isObject(diffStat) ? diffStat : null,\n diff: isObject(diff) ? diff : null,\n commits: isObject(commits) ? commits : null,\n failures: failures,\n explicitRefs: Boolean(input.baseRef || input.headRef),\n };\n if (!hasAnyGitContext(context)) {\n log("Git workflow actions unavailable; continuing with caller-provided review context", {\n failures: failures,\n });\n return null;\n }\n const files = collectGitFiles(context);\n log("Captured Git review context", {\n branch: context.status ? context.status.branch : "unknown",\n fileCount: files.length,\n hasDiff:\n context.diff != null &&\n (hasText(context.diff.branch) ||\n hasText(context.diff.staged) ||\n hasText(context.diff.unstaged)),\n failureCount: failures.length,\n });\n return context;\n}\n\nfunction tryGitAction(log, failures, name, fn) {\n try {\n return fn();\n } catch (error) {\n const failure = { action: name, error: formatError(error) };\n failures.push(failure);\n log("Git workflow action failed; continuing with partial review context", failure);\n return null;\n }\n}\n\nfunction hasAnyGitContext(gitContext) {\n return Boolean(\n gitContext.status ||\n gitContext.changedFiles ||\n gitContext.diffStat ||\n gitContext.diff ||\n gitContext.commits\n );\n}\n\nfunction hasResolvedBranchContext(gitContext) {\n return Boolean(\n hasResolvedGitRefContext(gitContext.changedFiles) ||\n hasResolvedGitRefContext(gitContext.diffStat) ||\n hasResolvedGitRefContext(gitContext.diff) ||\n hasResolvedGitRefContext(gitContext.commits)\n );\n}\n\nfunction hasResolvedGitRefContext(value) {\n return isObject(value) && hasText(value.base) && hasText(value.head) && hasText(value.mergeBase);\n}\n\nfunction isObject(value) {\n return value != null && typeof value === "object" && !Array.isArray(value);\n}\n\nfunction buildGitActionInput(input) {\n const gitInput = {};\n if (input.baseRef) gitInput.base = input.baseRef;\n if (input.headRef) gitInput.head = input.headRef;\n return gitInput;\n}\n\nfunction copyGitActionInput(input) {\n const copy = {};\n if (input.base) copy.base = input.base;\n if (input.head) copy.head = input.head;\n return copy;\n}\n\nfunction applyGitContextToReviewInput(input, gitContext) {\n if (gitContext == null) return;\n if ((input.explicitFiles || input.explicitDiff) && !input.includeGitContext) return;\n if (!input.explicitFiles) {\n const files = collectGitFiles(gitContext);\n if (files.length > 0) input.files = files;\n }\n if (!input.explicitDiff && gitContext.diff != null) {\n const diff = renderGitDiff(gitContext.diff);\n if (diff.length > 0) input.diff = truncateText(diff, 60000);\n }\n input.gitSnapshot = truncateText(renderGitSnapshot(gitContext), 20000);\n}\n\nfunction collectGitFiles(gitContext) {\n const files = [];\n if (gitContext == null) return files;\n if (gitContext.changedFiles != null) {\n addFileEntries(files, gitContext.changedFiles.branch);\n addFileEntries(files, gitContext.changedFiles.staged);\n addFileEntries(files, gitContext.changedFiles.unstaged);\n addFilePaths(files, gitContext.changedFiles.untracked);\n }\n if (gitContext.status != null) {\n addFileEntries(files, gitContext.status.staged);\n addFileEntries(files, gitContext.status.unstaged);\n addFilePaths(files, gitContext.status.untracked);\n }\n return files;\n}\n\nfunction addFileEntries(files, entries) {\n if (!Array.isArray(entries)) return;\n for (const entry of entries) {\n if (entry && typeof entry === "object") {\n addFilePath(files, entry.path);\n addFilePath(files, entry.oldPath);\n }\n }\n}\n\nfunction addFilePaths(files, paths) {\n if (!Array.isArray(paths)) return;\n for (const path of paths) {\n addFilePath(files, path);\n }\n}\n\nfunction addFilePath(files, path) {\n if (typeof path !== "string") return;\n const trimmed = path.trim();\n if (trimmed.length === 0 || files.indexOf(trimmed) !== -1) return;\n files.push(trimmed);\n}\n\nfunction renderGitSnapshot(gitContext) {\n const sections = [];\n if (gitContext.status != null) {\n const status = gitContext.status;\n sections.push(\n "Repository status: branch " +\n valueOrUnknown(status.branch) +\n (status.upstream ? " tracking " + status.upstream : "") +\n "; staged " +\n arrayLength(status.staged) +\n "; unstaged " +\n arrayLength(status.unstaged) +\n "; untracked " +\n arrayLength(status.untracked)\n );\n }\n if (gitContext.explicitRefs && !hasResolvedBranchContext(gitContext)) {\n sections.push(\n "WARNING: Requested base/head refs could not be resolved for automatic branch diff and commit capture; Git context may include only repository status or working-tree changes."\n );\n }\n if (Array.isArray(gitContext.failures) && gitContext.failures.length > 0) {\n sections.push(\n "Git context warnings:\\n" +\n gitContext.failures\n .map(function (failure) {\n return "- " + valueOrUnknown(failure.action) + ": " + valueOrUnknown(failure.error);\n })\n .join("\\n")\n );\n }\n const files = collectGitFiles(gitContext);\n if (files.length > 0) {\n sections.push("Changed files from parent workspace Git snapshot: " + files.join(", "));\n }\n if (gitContext.commits != null && Array.isArray(gitContext.commits.commits)) {\n const commits = gitContext.commits.commits;\n if (commits.length > 0) {\n sections.push(\n "Commits since " +\n valueOrUnknown(gitContext.commits.base) +\n ":\\n" +\n commits\n .map(function (commit) {\n return "- " + valueOrUnknown(commit.shortHash) + " " + valueOrUnknown(commit.subject);\n })\n .join("\\n")\n );\n }\n }\n if (gitContext.diffStat != null) {\n const statSections = [];\n if (hasText(gitContext.diffStat.branch))\n statSections.push("Branch diff stat:\\n" + gitContext.diffStat.branch);\n if (hasText(gitContext.diffStat.staged))\n statSections.push("Staged diff stat:\\n" + gitContext.diffStat.staged);\n if (hasText(gitContext.diffStat.unstaged))\n statSections.push("Unstaged diff stat:\\n" + gitContext.diffStat.unstaged);\n if (statSections.length > 0) sections.push(statSections.join("\\n\\n"));\n }\n if (gitContext.status != null && arrayLength(gitContext.status.untracked) > 0) {\n sections.push(\n "Untracked file contents are not included in the automatic diff snapshot; review agents only receive their paths unless the caller supplied args.diff."\n );\n }\n if (gitContext.diff != null && isDiffTruncated(gitContext.diff)) {\n sections.push(\n "One or more automatic diff sections were truncated by workflow action output limits."\n );\n }\n return sections.join("\\n\\n");\n}\n\nfunction renderGitDiff(diff) {\n const parts = [];\n if (hasText(diff.branch)) {\n parts.push(\n "Branch diff (" +\n valueOrUnknown(diff.base) +\n ".." +\n valueOrUnknown(diff.head) +\n ")\\n" +\n diff.branch\n );\n }\n if (hasText(diff.staged)) {\n parts.push("Staged diff\\n" + diff.staged);\n }\n if (hasText(diff.unstaged)) {\n parts.push("Unstaged diff\\n" + diff.unstaged);\n }\n if (isDiffTruncated(diff)) {\n parts.push("NOTE: one or more diff sections were truncated by workflow action output limits.");\n }\n return parts.join("\\n\\n");\n}\n\nfunction isDiffTruncated(diff) {\n return Boolean(\n diff &&\n diff.truncated &&\n (diff.truncated.branch || diff.truncated.staged || diff.truncated.unstaged)\n );\n}\n\nfunction truncateText(text, maxLength) {\n if (text.length <= maxLength) return text;\n return (\n text.slice(0, maxLength) +\n "\\n[truncated by deep-review-workflow after " +\n maxLength +\n " characters]"\n );\n}\n\nfunction hasText(value) {\n return typeof value === "string" && value.trim().length > 0;\n}\n\nfunction arrayLength(value) {\n return Array.isArray(value) ? value.length : 0;\n}\n\nfunction valueOrUnknown(value) {\n return typeof value === "string" && value.length > 0 ? value : "unknown";\n}\n\nfunction formatError(error) {\n return error && typeof error.message === "string" ? error.message : String(error);\n}\n\nfunction renderReviewInput(input) {\n return [\n "Target: " + input.target,\n input.baseRef ? "Base ref: " + input.baseRef : "",\n input.headRef ? "Head ref: " + input.headRef : "",\n input.files.length > 0 ? "Files: " + input.files.join(", ") : "",\n input.gitSnapshot ? "Git snapshot:\\n" + input.gitSnapshot : "",\n input.instructions ? "Reviewer instructions: " + input.instructions : "",\n input.diff ? "Diff snapshot:\\n~~~diff\\n" + input.diff + "\\n~~~" : "",\n ]\n .filter(Boolean)\n .join("\\n");\n}\n\nfunction selectReviewLanes(lanes) {\n const defaults = ["correctness", "tests", "architecture"];\n const allowed = {\n correctness: true,\n tests: true,\n architecture: true,\n "security-reliability": true,\n "ux-a11y": true,\n "docs-dx": true,\n };\n const requested = Array.isArray(lanes) && lanes.length > 0 ? lanes : defaults;\n const result = [];\n for (const lane of requested) {\n if (allowed[lane] && result.indexOf(lane) === -1) {\n result.push(lane);\n }\n }\n for (const fallback of defaults) {\n if (result.indexOf(fallback) === -1) {\n result.push(fallback);\n }\n }\n return result.slice(0, 6);\n}\n\nfunction lanePrompt(lane) {\n const prompts = {\n correctness:\n "Review for logic bugs, edge cases, races, state-machine violations, and broken invariants.",\n tests: "Review test coverage, determinism, missing regression tests, and validation commands.",\n architecture:\n "Review consistency with existing architecture, boundaries, naming, abstractions, and maintainability.",\n "security-reliability":\n "Review security, trust boundaries, path traversal, injection, data corruption, reliability, and performance risks.",\n "ux-a11y":\n "Review user-facing behavior, accessibility, keyboard flow, visual consistency, and empty/loading/error states.",\n "docs-dx":\n "Review documentation, developer experience, scripts, public API clarity, and migration concerns.",\n };\n return prompts[lane] || prompts.correctness;\n}\n\nfunction issueListSchema() {\n return {\n type: "object",\n required: ["issues"],\n additionalProperties: false,\n properties: {\n issues: {\n type: "array",\n items: issueSchema(),\n },\n },\n };\n}\n\nfunction issueSchema() {\n return {\n type: "object",\n required: [\n "id",\n "severity",\n "category",\n "title",\n "rationale",\n "evidence",\n "filePaths",\n "confidence",\n ],\n additionalProperties: false,\n properties: {\n id: { type: "string" },\n severity: { type: "string", enum: ["P0", "P1", "P2", "P3", "P4"] },\n category: { type: "string" },\n title: { type: "string" },\n rationale: { type: "string" },\n evidence: { type: "string" },\n filePaths: { type: "array", items: { type: "string" } },\n suggestedFix: { type: "string" },\n validation: { type: "string" },\n confidence: { type: "string", enum: ["low", "medium", "high"] },\n },\n };\n}\n\nfunction scopeSchema() {\n return {\n type: "object",\n required: ["summary", "files", "riskAreas", "lanes"],\n additionalProperties: false,\n properties: {\n summary: { type: "string" },\n files: { type: "array", items: { type: "string" } },\n riskAreas: { type: "array", items: { type: "string" } },\n lanes: {\n type: "array",\n items: {\n type: "string",\n enum: [\n "correctness",\n "tests",\n "architecture",\n "security-reliability",\n "ux-a11y",\n "docs-dx",\n ],\n },\n },\n },\n };\n}\n\nfunction verificationSchema() {\n return {\n type: "object",\n required: ["issueId", "verdict", "confidence", "rationale"],\n additionalProperties: false,\n properties: {\n issueId: { type: "string" },\n verdict: {\n type: "string",\n enum: ["valid", "duplicate", "overstated", "not-repro", "needs-info"],\n },\n confidence: { type: "string", enum: ["low", "medium", "high"] },\n rationale: { type: "string" },\n evidence: { type: "string" },\n suggestedSeverity: { type: "string", enum: ["P0", "P1", "P2", "P3", "P4"] },\n },\n };\n}\n\nfunction finalSynthesisSchema() {\n return {\n type: "object",\n required: ["verifiedIssueCount", "verifiedIssueIds", "risk", "validationPlan"],\n additionalProperties: false,\n properties: {\n verifiedIssueCount: { type: "number" },\n risk: { type: "string", enum: ["low", "medium", "high"] },\n validationPlan: { type: "array", items: { type: "string" } },\n verifiedIssueIds: { type: "array", items: { type: "string" } },\n discardedIssueCount: { type: "number" },\n },\n };\n}\n\nfunction flatten(arrays) {\n const out = [];\n for (const array of arrays) {\n for (const item of array) {\n out.push(item);\n }\n }\n return out;\n}\n', }, { name: "security-scan", diff --git a/src/node/services/workflows/builtInWorkflowDefinitions.test.ts b/src/node/services/workflows/builtInWorkflowDefinitions.test.ts index 7b492c536b..dcf616ff3a 100644 --- a/src/node/services/workflows/builtInWorkflowDefinitions.test.ts +++ b/src/node/services/workflows/builtInWorkflowDefinitions.test.ts @@ -2606,6 +2606,144 @@ describe("built-in deep-review-workflow", () => { }); }, 10_000); + test("keeps final synthesis when verification reports an overstated candidate", async () => { + if (!deepReviewWorkflow) { + throw new Error("Expected built-in deep-review-workflow workflow"); + } + using tmp = new DisposableTempDir("deep-review-workflow-overstated-verification-synthesis"); + const runStore = new WorkflowRunStore({ + sessionDir: tmp.path, + staleLeaseMs: BUILT_IN_WORKFLOW_TEST_STALE_LEASE_MS, + }); + await runStore.createRun({ + id: "wfr_deep_review_overstated_verification_synthesis", + workspaceId: "workspace-1", + definition: { + name: deepReviewWorkflow.name, + description: deepReviewWorkflow.description, + scope: "built-in", + executable: true, + }, + definitionSource: deepReviewWorkflow.source, + args: { input: "PR #123", files: ["src/service.ts"], maxCandidates: 1 }, + now: "2026-05-29T00:00:00.000Z", + }); + + const issue = { + id: "overstated-candidate", + severity: "P1", + category: "correctness", + title: "Overstated candidate", + rationale: "The lane overstated the impact.", + evidence: "Verifier will downgrade this finding.", + filePaths: ["src/service.ts"], + suggestedFix: "Handle the downgraded issue.", + validation: "Run targeted tests.", + confidence: "medium", + }; + const verification = { + issueId: "overstated-candidate", + verdict: "overstated", + confidence: "high", + rationale: "The issue is real but lower impact.", + evidence: "The affected path has a fallback.", + suggestedSeverity: "P3", + }; + const taskCalls: WorkflowAgentSpec[] = []; + const runner = new WorkflowRunner({ + runStore, + runtimeFactory: new QuickJSRuntimeFactory(), + taskAdapter: { + async runAgent(spec) { + taskCalls.push(spec); + switch (spec.id) { + case "scope-review-surface": + return { + taskId: "task_scope", + reportMarkdown: "Scoped review target.", + structuredOutput: { + summary: "Review target is scoped.", + files: [], + riskAreas: [], + lanes: ["correctness"], + }, + }; + case "review-correctness": + return { + taskId: "task_review_correctness", + reportMarkdown: "One candidate.", + structuredOutput: { issues: [issue] }, + }; + case "review-tests": + case "review-architecture": + return { + taskId: `task_${spec.id}`, + reportMarkdown: "No findings.", + structuredOutput: { issues: [] }, + }; + case "triage-candidate-issues": + return { + taskId: "task_triage", + reportMarkdown: "Kept one candidate.", + structuredOutput: { issues: [issue] }, + }; + case "verify-issue-0": + return { + taskId: "task_verify", + reportMarkdown: "Downgraded candidate.", + structuredOutput: verification, + }; + case "synthesize-review": + return { + taskId: "task_final", + reportMarkdown: "# Deep Review\n\n- P3 Overstated candidate remains actionable.", + structuredOutput: { + verifiedIssueCount: 1, + verifiedIssueIds: ["overstated-candidate"], + risk: "low", + validationPlan: ["Run targeted tests."], + discardedIssueCount: 0, + }, + }; + default: + throw new Error(`Unexpected overstated-verification deep-review step: ${spec.id}`); + } + }, + }, + runnerId: "runner-a", + clock: { + nowIso: () => "2026-05-29T00:00:01.000Z", + nowMs: () => 1_000, + }, + }); + + const result = await runner.run("wfr_deep_review_overstated_verification_synthesis"); + const run = await runStore.getRun("wfr_deep_review_overstated_verification_synthesis"); + + expect(taskCalls.map((call) => call.id)).toEqual([ + "scope-review-surface", + "review-correctness", + "review-tests", + "review-architecture", + "triage-candidate-issues", + "verify-issue-0", + "synthesize-review", + ]); + expect(run.events.filter((event) => event.type === "phase").map((event) => event.name)).toEqual( + ["scope", "lane-review", "triage-dedupe", "adversarial-verification", "final-synthesis"] + ); + expect(result).toMatchObject({ + structuredOutput: { + triagedIssues: [issue], + verification: [verification], + final: { + verifiedIssueCount: 1, + verifiedIssueIds: ["overstated-candidate"], + }, + }, + }); + }, 10_000); + test("short-circuits final synthesis when verification rejects every candidate", async () => { if (!deepReviewWorkflow) { throw new Error("Expected built-in deep-review-workflow workflow");