File size: 16,626 Bytes
cc11e77
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
/**
 * 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 };
}