AUDIT / src /lib /agentLoop /codeVerificationBlock.ts
Arypulka98's picture
feat(audit): deploy full backend cluster node (part 2)
cc11e77 verified
Raw
History Blame Contribute Delete
16.6 kB
/**
* 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 };
}