Skip to content

Commit 1240292

Browse files
author
alpsla
committed
rex(#2): Implement generateBatchFix Callback
1 parent 8208835 commit 1240292

3 files changed

Lines changed: 321 additions & 4 deletions

File tree

packages/agents/src/fix-agent/agents/ai-fixer-agent.ts

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1340,6 +1340,296 @@ ${instruction}`;
13401340
const registry = getFixPatternRegistry();
13411341
return registry.getAIFixerStats();
13421342
}
1343+
1344+
// ==========================================================================
1345+
// SESSION 89: Batch Fix Generation
1346+
// ==========================================================================
1347+
1348+
/**
1349+
* Generate fixes for multiple issues in a single AI call (batch fixing)
1350+
*
1351+
* SESSION 89: Implements the generateBatchFix callback for PatternAwareFixService
1352+
*
1353+
* Benefits:
1354+
* - Reduced API calls (1 instead of N)
1355+
* - Reduced total latency (180s → 75s for 3 issues)
1356+
* - AI can see all issues at once for better context
1357+
* - Single prompt/response round-trip
1358+
*
1359+
* @param issues Array of issues to fix in batch
1360+
* @param model Optional model override (for complexity routing)
1361+
* @returns Array of fixes corresponding to each input issue
1362+
*/
1363+
async generateBatchFix(
1364+
issues: AIFixerIssue[],
1365+
model?: string
1366+
): Promise<{
1367+
fixes: Array<{ fixCode: string; confidence: number }>;
1368+
totalConfidence: number;
1369+
cost?: number;
1370+
usage?: { promptTokens: number; completionTokens: number; totalTokens: number };
1371+
}> {
1372+
if (issues.length === 0) {
1373+
return { fixes: [], totalConfidence: 0 };
1374+
}
1375+
1376+
// Use provided model or get from language config
1377+
const selectedModel = model || await this.getModelForLanguage(issues[0].language);
1378+
1379+
console.log(`[AI-Fixer] 📦 Batch fix: ${issues.length} issues, model: ${selectedModel}`);
1380+
1381+
// Fetch KB guidance for the primary rule
1382+
let knowledgeBaseGuidance = '';
1383+
const primaryRule = issues[0].ruleId;
1384+
try {
1385+
knowledgeBaseGuidance = await formatGuidanceForPrompt(
1386+
primaryRule,
1387+
issues[0].language,
1388+
issues[0].validatorToolId
1389+
);
1390+
if (knowledgeBaseGuidance) {
1391+
console.log(`[AI-Fixer] Found KB guidance for batch primary rule: ${primaryRule}`);
1392+
}
1393+
} catch (e: any) {
1394+
// Continue without KB guidance
1395+
}
1396+
1397+
// Build batch prompt
1398+
const { systemPrompt, userPrompt } = this.buildBatchPrompt(issues, knowledgeBaseGuidance);
1399+
1400+
try {
1401+
const response = await this.executeOpenRouterCall(async (client) => {
1402+
return client.chat.completions.create({
1403+
model: selectedModel,
1404+
messages: [
1405+
{ role: 'system', content: systemPrompt },
1406+
{ role: 'user', content: userPrompt },
1407+
],
1408+
temperature: 0.2,
1409+
max_tokens: 4000 + (issues.length * 500), // Scale tokens with issue count
1410+
});
1411+
});
1412+
1413+
const content = response.choices[0]?.message?.content || '';
1414+
1415+
// Check for corrupted response
1416+
if (isCorruptedResponse(content)) {
1417+
console.warn(`[AI-Fixer] Batch fix returned corrupted response`);
1418+
// Return empty fixes to trigger fallback
1419+
return {
1420+
fixes: issues.map(() => ({ fixCode: '', confidence: 0 })),
1421+
totalConfidence: 0,
1422+
};
1423+
}
1424+
1425+
// Parse batch response
1426+
const fixes = this.parseBatchResponse(content, issues);
1427+
1428+
// Calculate usage and cost
1429+
const usage = {
1430+
promptTokens: response.usage?.prompt_tokens || 0,
1431+
completionTokens: response.usage?.completion_tokens || 0,
1432+
totalTokens: response.usage?.total_tokens || 0,
1433+
};
1434+
const cost = this.estimateCost(selectedModel, usage);
1435+
1436+
// Calculate total confidence
1437+
const totalConfidence = fixes.length > 0
1438+
? Math.round(fixes.reduce((sum, f) => sum + f.confidence, 0) / fixes.length)
1439+
: 0;
1440+
1441+
console.log(`[AI-Fixer] 📦 Batch fix complete: ${fixes.filter(f => f.confidence > 0).length}/${issues.length} fixes generated, avg confidence: ${totalConfidence}%`);
1442+
1443+
return { fixes, totalConfidence, cost, usage };
1444+
1445+
} catch (error: any) {
1446+
console.error(`[AI-Fixer] Batch fix error: ${error.message}`);
1447+
// Return empty fixes to trigger fallback to sequential
1448+
return {
1449+
fixes: issues.map(() => ({ fixCode: '', confidence: 0 })),
1450+
totalConfidence: 0,
1451+
};
1452+
}
1453+
}
1454+
1455+
/**
1456+
* Build system and user prompts for batch fix generation
1457+
*
1458+
* SESSION 89: Creates a structured prompt that asks AI to fix multiple issues
1459+
* and return fixes in a parseable JSON array format
1460+
*/
1461+
private buildBatchPrompt(
1462+
issues: AIFixerIssue[],
1463+
knowledgeBaseGuidance: string
1464+
): { systemPrompt: string; userPrompt: string } {
1465+
const language = issues[0]?.language || 'unknown';
1466+
const tool = issues[0]?.validatorToolId || 'unknown';
1467+
1468+
// Group issues by file for better context
1469+
const issuesByFile = new Map<string, AIFixerIssue[]>();
1470+
for (const issue of issues) {
1471+
const existing = issuesByFile.get(issue.file) || [];
1472+
existing.push(issue);
1473+
issuesByFile.set(issue.file, existing);
1474+
}
1475+
1476+
const systemPrompt = `You are an expert code fixer. Generate precise, compilable fixes for MULTIPLE code issues in a single response.
1477+
1478+
CRITICAL REQUIREMENTS:
1479+
- You MUST fix ALL ${issues.length} issues listed below
1480+
- Return fixes in the EXACT JSON format specified
1481+
- Each fix must be complete and compilable
1482+
- Never ask for more context - work with what's provided
1483+
- Do NOT add comments explaining fixes unless they add value
1484+
1485+
LANGUAGE: ${language}
1486+
TOOL: ${tool}
1487+
1488+
${knowledgeBaseGuidance ? `KNOWLEDGE BASE GUIDANCE (MUST follow):\n${knowledgeBaseGuidance}\n` : ''}
1489+
1490+
OUTPUT FORMAT - Return a JSON array with exactly ${issues.length} objects:
1491+
{
1492+
"fixes": [
1493+
{
1494+
"issueIndex": 0,
1495+
"fixCode": "The complete fixed code snippet",
1496+
"explanation": "Brief explanation of the fix"
1497+
},
1498+
{
1499+
"issueIndex": 1,
1500+
"fixCode": "The complete fixed code snippet",
1501+
"explanation": "Brief explanation of the fix"
1502+
}
1503+
// ... one entry for each issue
1504+
]
1505+
}
1506+
1507+
CRITICAL: Output ONLY valid JSON. No markdown code blocks, no extra text.`;
1508+
1509+
// Build user prompt with all issues
1510+
let userPrompt = `Fix the following ${issues.length} code issues:\n\n`;
1511+
1512+
let issueIndex = 0;
1513+
Array.from(issuesByFile.entries()).forEach(([file, fileIssues]) => {
1514+
userPrompt += `=== FILE: ${file} ===\n`;
1515+
1516+
for (const issue of fileIssues) {
1517+
userPrompt += `
1518+
--- ISSUE ${issueIndex} ---
1519+
RULE: ${issue.ruleId}
1520+
LINE: ${issue.line}
1521+
SEVERITY: ${issue.severity}
1522+
MESSAGE: ${issue.message}
1523+
${issue.codeContext ? `CODE CONTEXT:\n\`\`\`${language}\n${issue.codeContext}\n\`\`\`` : '(No code context available)'}
1524+
${issue.toolContext?.toolSuggestion ? `TOOL SUGGESTION: ${issue.toolContext.toolSuggestion}` : ''}
1525+
1526+
`;
1527+
issueIndex++;
1528+
}
1529+
});
1530+
1531+
userPrompt += `\nProvide fixes for all ${issues.length} issues in the JSON format specified.`;
1532+
1533+
return { systemPrompt, userPrompt };
1534+
}
1535+
1536+
/**
1537+
* Parse AI response from batch fix generation
1538+
*
1539+
* SESSION 89: Extracts individual fixes from the batch response JSON
1540+
* Handles various response formats and provides fallback for partial responses
1541+
*/
1542+
private parseBatchResponse(
1543+
content: string,
1544+
issues: AIFixerIssue[]
1545+
): Array<{ fixCode: string; confidence: number }> {
1546+
// Initialize result array with empty fixes
1547+
const results: Array<{ fixCode: string; confidence: number }> =
1548+
issues.map(() => ({ fixCode: '', confidence: 0 }));
1549+
1550+
// Clean response
1551+
let cleaned = content.trim();
1552+
cleaned = cleaned.replace(/```json\s*/gi, '');
1553+
cleaned = cleaned.replace(/```\s*/gi, '');
1554+
1555+
try {
1556+
// Find JSON object in response
1557+
const jsonMatch = cleaned.match(/\{[\s\S]*\}/);
1558+
if (!jsonMatch) {
1559+
console.warn(`[AI-Fixer] Batch response: No JSON found`);
1560+
return results;
1561+
}
1562+
1563+
const parsed = JSON.parse(jsonMatch[0]);
1564+
1565+
// Handle response format
1566+
const fixesArray = parsed.fixes || parsed.results || parsed;
1567+
1568+
if (!Array.isArray(fixesArray)) {
1569+
console.warn(`[AI-Fixer] Batch response: Expected array, got ${typeof fixesArray}`);
1570+
return results;
1571+
}
1572+
1573+
// Map fixes to issues
1574+
for (const fix of fixesArray) {
1575+
// Determine which issue this fix corresponds to
1576+
let index = -1;
1577+
1578+
if (typeof fix.issueIndex === 'number') {
1579+
index = fix.issueIndex;
1580+
} else if (typeof fix.index === 'number') {
1581+
index = fix.index;
1582+
} else if (typeof fix.id === 'string') {
1583+
// Try to match by issue ID
1584+
index = issues.findIndex(i => i.id === fix.id);
1585+
} else if (typeof fix.ruleId === 'string') {
1586+
// Try to match by ruleId and line
1587+
index = issues.findIndex(i =>
1588+
i.ruleId === fix.ruleId &&
1589+
(fix.line === undefined || i.line === fix.line)
1590+
);
1591+
}
1592+
1593+
// If still no match, try positional (assume fixes are in order)
1594+
if (index === -1) {
1595+
const nextEmpty = results.findIndex(r => r.fixCode === '');
1596+
if (nextEmpty !== -1) {
1597+
index = nextEmpty;
1598+
}
1599+
}
1600+
1601+
if (index >= 0 && index < results.length) {
1602+
const fixCode = fix.fixCode || fix.correctedCode || fix.code || fix.fix || '';
1603+
1604+
if (fixCode && fixCode.length > 0) {
1605+
// Calculate confidence based on response quality
1606+
let confidence = 75; // Base confidence for batch fix
1607+
1608+
// Boost if we have explanation
1609+
if (fix.explanation && fix.explanation.length > 10) {
1610+
confidence += 5;
1611+
}
1612+
1613+
// Boost if fix differs from original
1614+
const originalCode = issues[index]?.codeContext || '';
1615+
if (fixCode !== originalCode && fixCode.length > 5) {
1616+
confidence += 10;
1617+
}
1618+
1619+
results[index] = { fixCode, confidence: Math.min(90, confidence) };
1620+
}
1621+
}
1622+
}
1623+
1624+
const successCount = results.filter(r => r.confidence > 0).length;
1625+
console.log(`[AI-Fixer] Batch parse: ${successCount}/${issues.length} fixes extracted`);
1626+
1627+
} catch (e: any) {
1628+
console.warn(`[AI-Fixer] Batch response parse error: ${e.message}`);
1629+
}
1630+
1631+
return results;
1632+
}
13431633
}
13441634

13451635
// ============================================================================

rex-progress.txt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,21 @@
1818
- KB success rate >= 80% overrides unknown rules to simple
1919
- Batch fixing uses 1 API call for N issues (saves N-1 calls)
2020

21+
### Task 2: Implement generateBatchFix Callback
22+
- Session 89: Implemented batch fixing callback in AIFixerAgent
23+
- Key methods added:
24+
- `generateBatchFix()` - main callback for PatternAwareFixService
25+
- `buildBatchPrompt()` - creates system/user prompts for batch AI call
26+
- `parseBatchResponse()` - extracts individual fixes from JSON response
27+
- Implementation patterns:
28+
- Groups issues by file for better AI context
29+
- JSON output format with `issueIndex` for reliable parsing
30+
- Multiple fallback strategies for parsing (index, id, ruleId, positional)
31+
- Confidence scoring based on explanation and fix quality
32+
- Scales max_tokens with issue count: 4000 + (issues * 500)
33+
- TypeScript note: Use `Array.from(map.entries()).forEach()` for Map iteration
34+
to avoid downlevelIteration issues in ES5 target builds
35+
2136
---
2237

2338
=== Task #1 (2026-01-16 08:02:58) ===
@@ -31,3 +46,15 @@ Title: Test Complexity Detection & Batch Fixing
3146
Status: FAILED
3247
Reason: Validation failed
3348

49+
50+
=== Task #1 (2026-01-16 08:13:37) ===
51+
Title: Test Complexity Detection & Batch Fixing
52+
Status: COMPLETE
53+
Commit: [main 82088355] rex(#1): Test Complexity Detection & Batch Fixing
54+
4 files changed, 971 insertions(+)
55+
create mode 100644 packages/agents/src/fix-agent/state/__tests__/batch-fixing.test.ts
56+
create mode 100644 packages/agents/src/fix-agent/state/__tests__/complexity-detection.test.ts
57+
create mode 100644 rex-progress.txt
58+
create mode 100644 rex-tasks.json
59+
82088355
60+

rex-tasks.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"source": "docs/NEXT_SESSION_89.md",
33
"createdAt": "2025-01-16T08:00:00Z",
44
"maxIterations": 20,
5-
"currentIteration": 1,
5+
"currentIteration": 2,
66
"status": "ready",
77
"validation": {
88
"type": "turbo",
@@ -24,11 +24,11 @@
2424
"packages/agents/src/fix-agent/state/__tests__/batch-fixing.test.ts"
2525
],
2626
"priority": "high",
27-
"passes": false,
27+
"passes": true,
2828
"attempts": 0,
2929
"lastError": null,
30-
"completedAt": null,
31-
"commitHash": null
30+
"completedAt": "2026-01-16T13:13:37Z",
31+
"commitHash": "[main 82088355] rex(#1): Test Complexity Detection & Batch Fixing\n 4 files changed, 971 insertions(+)\n create mode 100644 packages/agents/src/fix-agent/state/__tests__/batch-fixing.test.ts\n create mode 100644 packages/agents/src/fix-agent/state/__tests__/complexity-detection.test.ts\n create mode 100644 rex-progress.txt\n create mode 100644 rex-tasks.json\n82088355"
3232
},
3333
{
3434
"id": 2,

0 commit comments

Comments
 (0)