/** * codeVerificationBlock.ts — S496: extracted from agentLoop.ts * * Pipeline di verifica codice pre-streaming (solo per task di codice): * * 1. Acceptance Gate (S411/S413): REQ-001..N pipeline strutturata * - Se requisiti critici falliscono + iter disponibili → continue * - Se tutti i REQ passano → prependi checklist al finalText * * 2. Runtime Verifier (S415): analisi statica codice VFS * - Errori critici + iter disponibili → continue (repair hint) * - Solo warning → prependi badge, non blocca * * 3. Shell Verifier (S415b): node --check / tsc --noEmit via LLM * - Shell check fallito → continue con repair hint * - Shell check non ancora eseguito → richiede all'LLM di eseguirlo → continue * * 4. Build Validator (S424): npm run build * - Build fallita → continue con repair hint * - Build non ancora eseguita → richiede all'LLM → continue * * NOTIF-V2: incident registry integrato — ogni failure registrato in incidentRegistry * (dedup 60s, getRegressionInfo() usabile per detection pattern ricorrenti). * * Restituisce { shouldContinue, finalText }. * steps e loopMessages vengono mutati direttamente (passed by reference). * * @module codeVerificationBlock */ import type { AgentStep } from "../storage"; import type { ApiMsg } from "./networkTools"; import type { GoalContract } from "../_goalVerifier"; import { registerIncident } from "../incidentRegistry"; // NOTIF-V2 export interface CodeVerifCtx { formalGoal: GoalContract | null; steps: AgentStep[]; iter: number; MAX_ITER: number; loopMessages: ApiMsg[]; onStatus: (msg: string) => void; onSteps: (steps: AgentStep[]) => void; signal?: AbortSignal; lastUserMsg: string; finalText: string; _isCodeTask: boolean; /** taskId opzionale — propagato all'incident registry per correlazione */ taskId?: string | null; } export interface CodeVerifResult { shouldContinue: boolean; finalText: string; } /** * Esegue la pipeline di verifica codice (acceptance + runtime + shell + build). * Restituisce { shouldContinue, finalText } — mai lancia. */ export async function runCodeVerificationBlock(ctx: CodeVerifCtx): Promise { const { formalGoal, steps, iter, MAX_ITER, loopMessages, onStatus, onSteps, signal, lastUserMsg, _isCodeTask, taskId } = ctx; let finalText = ctx.finalText; if (!_isCodeTask || !formalGoal || signal?.aborted) { return { shouldContinue: false, finalText }; } // ── P45: AST Check (step 0) ──────────────────────────────────────────────── // Verifica sintattica IMMEDIATA via TypeScript compiler API — zero shell. // Intercetta errori di sintassi prima che l'acceptance gate li valuti, // evitando cicli inutili di esecuzione su file già rotti. if (iter < MAX_ITER - 2 && !signal?.aborted) { try { const { vfsAsync: _acVfs } = await import("../vfsDb"); const { astCheck, buildAstRepairHint } = await import("../astCheck"); const _acRaw = await _acVfs.list(); const _acFiles = _acRaw.filter( (f: { path?: string; content?: unknown }) => f.path && typeof f.content === "string" && /\.[jt]sx?$|\.m[jt]s$/.test(f.path as string), ); // Controlla max 12 file — bilancia completezza e performance for (const _acF of _acFiles.slice(0, 12)) { if (signal?.aborted) break; const _acRes = await astCheck(_acF.content as string, _acF.path as string); if (!_acRes.ok && _acRes.errors.length > 0) { const _acHint = buildAstRepairHint(_acF.path as string, _acRes.errors, _acF.content as string); steps.push({ tool: "_ast_check", args: {}, result: `❌ AST: ${_acRes.errors.length} errore/i sintattico/i in \`${_acF.path}\` (riga ${_acRes.errors[0].line})`, status: "done", at: Date.now(), } as AgentStep); onSteps([...steps]); onStatus(`AST check: sintassi non valida in ${_acF.path} — correzione autonoma…`); try { registerIncident("ast_syntax_error", "tool", "error", { file: _acF.path, errors: _acRes.errors.slice(0, 3).map((e: { line: number; message: string }) => `L${e.line}: ${e.message.slice(0, 80)}`), iter, }, taskId ?? undefined); } catch { /* non-blocking */ } loopMessages.push({ role: "user", content: _acHint } as ApiMsg); return { shouldContinue: true, finalText }; } } } catch { /* AST check è best-effort — non blocca mai la risposta */ } } // ── S411/S413: Acceptance Gate ───────────────────────────────────────────── try { const { vfsAsync: _agVfs } = await import("../vfsDb"); const { runAcceptancePipeline: _agRap } = await import("../requirementDecomposer"); const { runAcceptanceTests: _agRat } = await import("../goalTestDeriver"); const _agRaw = await _agVfs.list(); const _agFiles = _agRaw .filter(f => f.path && f.content) .map(f => ({ path: f.path, content: f.content as string })); if (_agFiles.length > 0) { const _goalStr = (formalGoal as GoalContract).goal; const _agPipe = _agRap(_goalStr, _agFiles); const _agRep = _agRat(_goalStr, _agFiles); const _failing = _agPipe.total > 0 ? _agPipe.results.filter((r: { status: string; reqId: string; feature: string }) => r.status === "failed").map((r: { reqId: string; feature: string }) => `${r.reqId}(${r.feature})`) : _agRep.tests.filter((t: { pass: boolean; requirement: string }) => !t.pass).map((t: { requirement: string }) => t.requirement); if (!_agRep.allGreen && _agRep.total > 0 && _agRep.hint && iter < MAX_ITER - 2) { const _label = _agPipe.total > 0 ? `📋 ${_agPipe.passed}/${_agPipe.total} REQ` : `${_agRep.passed}/${_agRep.total}`; steps.push({ tool: "_acceptance_gate", args: {}, result: `${_label} — mancanti: ${(_failing as string[]).slice(0, 3).join("; ")}`, status: "done", at: Date.now(), } as AgentStep); onSteps([...steps]); onStatus(`Acceptance gate: ${_agPipe.total > 0 ? _agPipe.passed + "/" + _agPipe.total + " REQ" : _agRep.passed + "/" + _agRep.total} — completamento requisiti…`); // NOTIF-V2: acceptance failure → registry (dedup evita spam su loop rapidi) try { registerIncident("acceptance_fail", "validator", "warning", { failing: (_failing as string[]).slice(0, 5), passed: _agRep.passed, total: _agRep.total, iter, }, taskId ?? undefined); } catch { /* non-blocking */ } // S697: anti-loop — if same hint injected 2+ times → escalate to rewrite const _recentMsgs = loopMessages.filter(m => m.role === "user").slice(-4).map(m => String(m.content).slice(0, 100)); const _hintFp = (_agRep.hint ?? "").slice(0, 100); const _alreadyLooping = _recentMsgs.filter(h => h === _hintFp).length >= 2; if (_alreadyLooping && iter < MAX_ITER - 2) { const _escalHint = `I tentativi precedenti non hanno risolto: ${(_failing as string[]).slice(0, 3).join(", ")}. RISCRIVI dall'inizio solo le parti che falliscono, con un approccio completamente diverso.`; loopMessages.push({ role: "user", content: _escalHint } as ApiMsg); } else { loopMessages.push({ role: "user", content: _agRep.hint } as ApiMsg); } try { const { recordRuntimeRepair: _rrp } = await import("../repairAuditor"); _rrp(lastUserMsg, (_failing as string[]).slice(0, 5), _agRep.hint ?? ""); } catch { /* non-blocking */ } return { shouldContinue: true, finalText }; } if (_agPipe.total > 0 && _agPipe.allGreen && _agPipe.checklist) { steps.push({ tool: "_acceptance_gate", args: {}, result: `📋 Acceptance OK — ${_agPipe.passed}/${_agPipe.total} REQ soddisfatti`, status: "done", at: Date.now(), } as AgentStep); onSteps([...steps]); finalText = _agPipe.checklist + "\n\n" + finalText; } } } catch { /* acceptance gate è best-effort — non blocca mai la risposta */ } // ── S415: Runtime Verifier ───────────────────────────────────────────────── if (signal?.aborted) return { shouldContinue: false, finalText }; try { const { vfsAsync: _rvVfs } = await import("../vfsDb"); const { verifyRuntime: _rvCheck } = await import("../runtimeVerifier"); const _rvRaw = await _rvVfs.list(); const _rvFiles = _rvRaw .filter(f => f.path && f.content) .map(f => ({ path: f.path, content: f.content as string })); if (_rvFiles.length > 0) { const _rvResult = _rvCheck(_rvFiles, (formalGoal as GoalContract).goal); steps.push({ tool: "_runtime_verifier", args: {}, result: _rvResult.summary, status: "done", at: Date.now(), } as AgentStep); onSteps([...steps]); if (_rvResult.hasCritical && _rvResult.repairHint && iter < MAX_ITER - 2) { onStatus(`Runtime check: ${_rvResult.issues.filter((i: { severity: string }) => i.severity === "error").length} errore/i critico/i — correzione autonoma…`); // NOTIF-V2: runtime error critico → severity "error" → getRegressionInfo() lo conterà try { const _criticalMsgs = _rvResult.issues .filter((i: { severity: string }) => i.severity === "error") .map((i: { message: string }) => i.message.slice(0, 80)); registerIncident("runtime_error", "validator", "error", { issues: _criticalMsgs.slice(0, 5), iter, }, taskId ?? undefined); } catch { /* non-blocking */ } try { const { recordRuntimeRepair } = await import("../repairAuditor"); const _rvGoal = (formalGoal as GoalContract).goal; const _rvMsgs = _rvResult.issues .filter((i: { severity: string }) => i.severity === "error") .map((i: { message: string }) => i.message); recordRuntimeRepair(_rvGoal, _rvMsgs, _rvResult.repairHint); } catch { /* non-blocking */ } loopMessages.push({ role: "user", content: _rvResult.repairHint } as ApiMsg); return { shouldContinue: true, finalText }; } if (!_rvResult.hasCritical) { try { const { markRepairsSuccessful } = await import("../repairAuditor"); const _rvGoal = (formalGoal as GoalContract).goal; const _rvWarn = _rvResult.issues.filter((i: { severity: string }) => i.severity === "warning").map((i: { message: string }) => i.message); if (_rvWarn.length > 0 && iter > 1) markRepairsSuccessful(_rvGoal, _rvWarn); } catch { /* non-blocking */ } } // S415b: Shell Verifier if (!_rvResult.hasCritical && iter >= 1) { try { const { buildShellVerifyPrompt, parseShellCheckFromSteps, formatShellCheckRepairHint, detectEntryPoint } = await import("../shellVerifier"); const _scResult = parseShellCheckFromSteps(steps as { tool?: string; args?: Record; result?: string }[]); if (_scResult.checked && !_scResult.ok && _scResult.errors.length > 0 && iter < MAX_ITER - 2) { const _scEntry = detectEntryPoint(_rvFiles)?.path ?? "entry point"; const _scHint = formatShellCheckRepairHint(_scResult.errors, _scEntry, _scResult.cmd); steps.push({ tool: "_shell_verifier", args: {}, result: `❌ Shell check fallito: ${_scResult.errors.slice(0, 3).join(" | ")}`, status: "done", at: Date.now(), } as AgentStep); onSteps([...steps]); onStatus(`Shell check: ${_scResult.errors.length} errore/i — correzione autonoma…`); // NOTIF-V2: shell check failure → "error" severity try { registerIncident("shell_check_fail", "validator", "error", { cmd: _scResult.cmd ?? "", errors: _scResult.errors.slice(0, 5), iter, }, taskId ?? undefined); } catch { /* non-blocking */ } loopMessages.push({ role: "user", content: _scHint } as ApiMsg); return { shouldContinue: true, finalText }; } if (!_scResult.checked && iter < MAX_ITER - 2) { const _scPrompt = buildShellVerifyPrompt( _rvFiles, steps as { tool?: string; args?: Record; result?: string }[], iter, ); if (_scPrompt) { steps.push({ tool: "_shell_verifier", args: {}, result: "⏳ Shell check richiesto — in attesa di execute_shell…", status: "done", at: Date.now(), } as AgentStep); onSteps([...steps]); loopMessages.push({ role: "user", content: _scPrompt } as ApiMsg); return { shouldContinue: true, finalText }; } } } catch { /* non-blocking — shell verifier è best-effort */ } } // S424: Build Validator if (!_rvResult.hasCritical && iter >= 2) { try { const { buildBuildPrompt: _bbPrompt, parseBuildResultFromSteps: _bbParse, formatBuildRepairHint: _bbHint, } = await import("../buildRunner"); const _brResult = _bbParse(steps as { tool?: string; args?: Record; result?: string }[]); if (_brResult.checked && !_brResult.ok && _brResult.errors.length > 0 && iter < MAX_ITER - 2) { steps.push({ tool: "_build_validator", args: {}, result: `❌ Build fallita (${_brResult.cmd}): ${_brResult.errors.slice(0, 3).join(" | ")}`, status: "done", at: Date.now(), } as AgentStep); onSteps([...steps]); onStatus(`Build validator: ${_brResult.errors.length} errore/i — correzione autonoma…`); // NOTIF-V2: build failure → "error" severity (stessa event key per regressioni) try { registerIncident("build_fail", "validator", "error", { cmd: _brResult.cmd ?? "", errors: _brResult.errors.slice(0, 5), iter, }, taskId ?? undefined); } catch { /* non-blocking */ } loopMessages.push({ role: "user", content: _bbHint(_brResult.errors, _brResult.cmd) } as ApiMsg); return { shouldContinue: true, finalText }; } if (!_brResult.checked && iter < MAX_ITER - 2) { const _buildPrompt = _bbPrompt( _rvFiles, steps as { tool?: string; args?: Record; result?: string }[], iter, ); if (_buildPrompt) { steps.push({ tool: "_build_validator", args: {}, result: "⏳ Build validation richiesta — in attesa di execute_shell…", status: "done", at: Date.now(), } as AgentStep); onSteps([...steps]); loopMessages.push({ role: "user", content: _buildPrompt } as ApiMsg); return { shouldContinue: true, finalText }; } } if (_brResult.checked && _brResult.ok) { steps.push({ tool: "_build_validator", args: {}, result: "✅ Build completata con successo", status: "done", at: Date.now(), } as AgentStep); onSteps([...steps]); } } catch { /* non-blocking — build validator è best-effort */ } } } } catch { /* non-blocking — runtime verifier è best-effort */ } return { shouldContinue: false, finalText }; }