Spaces:
Sleeping
Sleeping
| /** | |
| * 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<CodeVerifResult> { | |
| 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<string, unknown>; 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<string, unknown>; 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<string, unknown>; 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<string, unknown>; 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 }; | |
| } | |