feat(engine): harden review pipeline with strict scope, build retry, and E2E tests

Improve the plan→review→approve→merge agent pipeline:

- Harden verdict extraction with JSON block parsing and anchored regexes
- Consolidate legacy/new merger conflict APIs into thin deprecated wrappers
- Add configurable strict scope enforcement (strictScopeEnforcement setting)
- Add build retry with timeout to merger (buildRetryCount, buildTimeoutMs)
- Add handleChangesRequested to PrCommentHandler for review feedback loop
- Remove dead code: handleFsChange, processTaskChange, unused imports/fields
- Add E2E multi-verdict sequence tests for the full review pipeline
- Fix unused parameter warnings across engine and core packages

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-04-04 00:29:34 -07:00
parent a2e67ce0f6
commit eef15f077c
11 changed files with 427 additions and 357 deletions

View File

@@ -267,7 +267,7 @@ function buildReviewRequest(
stepName: string,
reviewType: ReviewType,
promptContent: string,
cwd: string,
_cwd: string,
baseline?: string,
): string {
const parts = [
@@ -338,22 +338,34 @@ function buildReviewRequest(
}
function extractVerdict(review: string): ReviewVerdict {
// Look for "### Verdict: APPROVE" or "**Verdict: REVISE**" or similar
const verdictMatch = review.match(
/(?:###?\s*|[*_]{1,2})Verdict[:\s]*[*_]{0,2}\s*(APPROVE|REVISE|RETHINK)/i,
// Strategy 1: Look for a JSON verdict block (structured output)
// Matches: ```json\n{"verdict": "APPROVE"}\n``` or inline {"verdict":"REVISE"}
const jsonMatch = review.match(
/\{\s*"verdict"\s*:\s*"(APPROVE|REVISE|RETHINK)"\s*\}/i,
);
if (verdictMatch) {
return verdictMatch[1].toUpperCase() as ReviewVerdict;
if (jsonMatch) {
reviewerLog.log(`Verdict extracted via JSON block: ${jsonMatch[1].toUpperCase()}`);
return jsonMatch[1].toUpperCase() as ReviewVerdict;
}
// Fallback: look for a standalone verdict line like "Verdict: APPROVE"
// Strategy 2: Look for verdict in a heading line (### Verdict: APPROVE, **Verdict: REVISE**)
// Only match lines that START with a verdict pattern to avoid matching keywords in body text
const headingMatch = review.match(
/^[>\s]*(?:###?\s*|[*_]{1,2})Verdict[:\s]*[*_]{0,2}\s*(APPROVE|REVISE|RETHINK)\b/im,
);
if (headingMatch) {
return headingMatch[1].toUpperCase() as ReviewVerdict;
}
// Strategy 3: Standalone verdict line like "Verdict: APPROVE" or "Decision: REVISE"
const lineFallback = review.match(
/^[>\s]*(?:verdict|decision)[:\s]+(APPROVE|REVISE|RETHINK)\b/im,
/^[>\s]*(?:verdict|decision)\s*[-:]\s*(APPROVE|REVISE|RETHINK)\b/im,
);
if (lineFallback) {
return lineFallback[1].toUpperCase() as ReviewVerdict;
}
reviewerLog.warn(`Could not extract verdict from review (${review.length} chars). Returning UNAVAILABLE.`);
return "UNAVAILABLE";
}