File size: 4,107 Bytes
95eb75a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
/**
 * 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<ExecutionPlanResult | null> {
  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;
  }
}