/** * executionPlanBlock.ts — S495: extracted from agentLoop.ts * * Wrapper per il blocco Execution Plan (S414/S421). * "Pensa prima di ogni azione" a livello macro — genera un piano strutturato * (auth → db → api → ui con checkpoint) PRIMA del primo LLM call. * * Trigger: _isCodeTask=true + ≥2 requisiti strutturati rilevati. * Output: { plan, planShort, planFull, epOrder } o null. * Mai blocca: tutto wrapped in try/catch (best-effort). * * @module executionPlanBlock */ import type { AgentStep } from "../storage"; import type { ExecutionPlan, ExecutionStep } from "../executionPlanner"; // S554: tipi espliciti export interface ExecutionPlanResult { plan: ExecutionPlan; // S554: was any — ExecutionPlan è esportato da executionPlanner planShort: string; planFull: string; epOrder: string[]; } /** * Tenta di generare un piano strutturato per il task di codice. * Restituisce null se il task è troppo semplice (< 2 requisiti) o in caso di errore. * * @param task — testo del task/messaggio utente * @param opts — opzioni: onStatus, onSteps, steps (per aggiungere __plan__ step) * @returns — ExecutionPlanResult o null */ export async function runExecutionPlanIfNeeded( task: string, opts?: { onStatus?: (msg: string) => void; onSteps?: (steps: AgentStep[]) => void; steps?: AgentStep[]; }, ): Promise { try { const { decomposeGoal } = await import("../requirementDecomposer"); const { planArchitecture } = await import("../architecturePlanner"); const { buildExecutionPlan, formatExecutionPlan, formatExecutionPlanCompact } = await import("../executionPlanner"); const _epSpec = decomposeGoal(task); if (_epSpec.requirements.length < 2) return null; // GAP-3: recupera i file rilevanti dal VFS PRIMA della pianificazione architetturale. // Permette a planArchitecture di rilevare lo stack tecnologico già in uso e pianificare // in modo coerente con il codebase esistente (es. non suggerire di aggiungere React // se .tsx già presenti, non suggerire un nuovo backend se .py già ci sono). // topK=8 / minScore=0.06: rete larga — vogliamo vedere l'architettura generale, non solo i file // più rilevanti per la query specifica. let _existingFiles: Array<{ path: string; lang: string }> = []; try { const { getRelevantFiles } = await import("../context/repoMap"); const _rf = await getRelevantFiles(task, 8, 0.06); _existingFiles = _rf.map(f => ({ path: f.path, lang: f.lang })); } catch { /* non-blocking — planner funziona anche senza contesto file */ } const _epArch = planArchitecture(task, _epSpec.requirements, _existingFiles); const _epPlan = buildExecutionPlan(task, _epSpec, _epArch); if (_epPlan.isMinimal) return null; const planFull = formatExecutionPlan(_epPlan); const planShort = formatExecutionPlanCompact(_epPlan); // Step visibile in UI: piano compatto con ordine moduli if (opts?.steps && opts.onSteps) { opts.steps.push({ tool: "__plan__", args: {}, result: planShort, status: "done", at: Date.now(), } as AgentStep); opts.onSteps([...opts.steps]); } opts?.onStatus?.(`Ho un piano, parto con ${_epPlan.steps[0]?.name ?? "l'analisi"}…`); // Fix 7 (S421): sincronizza ordine con GraphOrchestrator // S554: usa filesToCreate — il campo corretto di ExecutionStep (bug fix) const epOrder: string[] = _epPlan.steps.flatMap((s: ExecutionStep) => s.filesToCreate ?? []); if (epOrder.length > 0) { try { const { setEpPlanOrder } = await import("../agent/GraphOrchestrator"); setEpPlanOrder(epOrder); } catch { /* non-blocking */ } } return { plan: _epPlan, planShort, planFull, epOrder }; } catch { // Non-blocking — piano non disponibile, loop prosegue senza return null; } }