Spaces:
Sleeping
Sleeping
| /** | |
| * diffPipeline.ts — GAP-6: Pipeline end-to-end per applicazione sicura di diff | |
| * | |
| * Esegue la catena completa: | |
| * 1. Pre-check sintassi diff (validateDiffSyntax) | |
| * 2. Apply in-process (applySearchReplace per SEARCH/REPLACE) | |
| * 3. Post-apply validation (validatePostApply — syntax balance, shrinkage) | |
| * 4. Build check opzionale (callback esterno — typecheck/lint) | |
| * | |
| * Rollback implicito: se success=false → result = originalContent. | |
| * Il chiamante NON deve mai fare rollback manuale. | |
| * | |
| * Unified diff: non applicabile in-process — la pipeline esegue solo | |
| * pre/post validation; l'apply è delegato al backend (toolExecutor). | |
| * | |
| * Zero side-effects, async per il build check — Safari-safe. | |
| * | |
| * @module diffPipeline | |
| */ | |
| import { | |
| validateDiffSyntax, | |
| applySearchReplace, | |
| validatePostApply, | |
| detectDiffFormat, | |
| type DiffValidationResult, | |
| type PostApplyValidation, | |
| } from "./diffValidator"; | |
| // ─── Tipi ──────────────────────────────────────────────────────────────────── | |
| export interface DiffPipelineParams { | |
| /** Contenuto originale del file PRIMA del patch */ | |
| originalContent: string; | |
| /** Diff da applicare (SEARCH/REPLACE o unified) */ | |
| diff: string; | |
| /** Path del file — usato per syntax check specifico per linguaggio */ | |
| filePath: string; | |
| /** | |
| * Callback opzionale per build/typecheck post-apply. | |
| * Riceve il contenuto patchato, deve restituire true se il build è OK. | |
| * Non-blocking: se crasha → ignorato (buildCheck = skipped). | |
| */ | |
| runBuildCheck?: (patchedContent: string) => Promise<boolean>; | |
| } | |
| export type PipelineStage = "pre" | "apply" | "post" | "build" | "ok"; | |
| export interface DiffPipelineResult { | |
| success: boolean; | |
| /** Contenuto finale: originalContent se fallito, patchedContent se ok */ | |
| result: string; | |
| /** true se il rollback è stato applicato (apply OK ma post/build falliti) */ | |
| rolledBack: boolean; | |
| preValidation: DiffValidationResult | null; | |
| postValidation: PostApplyValidation | null; | |
| /** Fase in cui si è fermata la pipeline */ | |
| stage: PipelineStage; | |
| /** Messaggio descrittivo — iniettabile nel tool result */ | |
| message: string; | |
| } | |
| // ─── Pipeline ──────────────────────────────────────────────────────────────── | |
| /** | |
| * Applica un diff con validazione completa pre/post e rollback automatico. | |
| * | |
| * @example | |
| * const result = await applyDiffSafe({ originalContent, diff, filePath }); | |
| * if (!result.success) { | |
| * // usa result.result (= originalContent) — rollback già applicato | |
| * console.warn(result.message); | |
| * } | |
| */ | |
| export async function applyDiffSafe(params: DiffPipelineParams): Promise<DiffPipelineResult> { | |
| const { originalContent, diff, filePath, runBuildCheck } = params; | |
| // ── Fase 1: Pre-check sintassi diff ────────────────────────────────────── | |
| let preValidation: DiffValidationResult; | |
| try { | |
| preValidation = validateDiffSyntax(diff); | |
| } catch { | |
| return _fail("pre", "Errore interno validateDiffSyntax — diff non applicato", null, null, originalContent); | |
| } | |
| if (!preValidation.valid) { | |
| return _fail( | |
| "pre", | |
| `Pre-validazione fallita: ${preValidation.issues.join("; ")}`, | |
| preValidation, null, | |
| originalContent, | |
| false, // non c'è stato rollback — patch mai applicata | |
| ); | |
| } | |
| // ── Fase 2: Apply ───────────────────────────────────────────────────────── | |
| const format = detectDiffFormat(diff); | |
| let applied: string; | |
| if (format === "search_replace") { | |
| let _applied: string | null = null; | |
| try { _applied = applySearchReplace(originalContent, diff); } catch { /* handled below */ } | |
| if (_applied === null) { | |
| return _fail("apply", "SEARCH block non trovato nel file — diff non applicabile", preValidation, null, originalContent, false); | |
| } | |
| applied = _applied; | |
| } else { | |
| // Unified diff: apply delegato al backend; qui eseguiamo solo pre-validazione | |
| // Restituiamo success=true con il contenuto originale non toccato | |
| // (il chiamante userà il backend per apply effettivo) | |
| return { | |
| success: true, | |
| result: originalContent, // NON patchato — apply via backend | |
| rolledBack: false, | |
| preValidation, | |
| postValidation: null, | |
| stage: "ok", | |
| message: "Unified diff: pre-validazione OK — apply delegato al backend", | |
| }; | |
| } | |
| // ── Fase 3: Post-apply validation ───────────────────────────────────────── | |
| let postValidation: PostApplyValidation; | |
| try { | |
| postValidation = validatePostApply(originalContent, applied, filePath); | |
| } catch { | |
| // Non-blocking: se post-validation crasha, accetta il patch con warning | |
| return { | |
| success: true, result: applied, rolledBack: false, | |
| preValidation, postValidation: null, stage: "ok", | |
| message: "⚠️ Post-validation skipped (errore interno) — patch applicato comunque", | |
| }; | |
| } | |
| if (!postValidation.syntaxOk && postValidation.syntaxError) { | |
| return _fail( | |
| "post", | |
| `Post-apply syntax error: ${postValidation.syntaxError}`, | |
| preValidation, postValidation, | |
| originalContent, | |
| true, // rollback: non usiamo applied | |
| ); | |
| } | |
| // ── Fase 4: Build check (opzionale) ────────────────────────────────────── | |
| if (runBuildCheck) { | |
| let buildOk = false; | |
| try { buildOk = await runBuildCheck(applied); } catch { /* non-blocking — skip check */ } | |
| if (!buildOk) { | |
| return _fail( | |
| "build", | |
| "Build/typecheck fallito dopo apply — rollback applicato automaticamente", | |
| preValidation, postValidation, | |
| originalContent, | |
| true, | |
| ); | |
| } | |
| } | |
| // ── Successo ────────────────────────────────────────────────────────────── | |
| const delta = postValidation.lengthDelta; | |
| return { | |
| success: true, | |
| result: applied, | |
| rolledBack: false, | |
| preValidation, | |
| postValidation, | |
| stage: "ok", | |
| message: `Diff applicato: ${delta >= 0 ? "+" : ""}${delta} chars${postValidation.warnings.length > 0 ? " ⚠️ " + postValidation.warnings[0] : ""}`, | |
| }; | |
| } | |
| // ─── Helper ─────────────────────────────────────────────────────────────────── | |
| function _fail( | |
| stage: PipelineStage, | |
| message: string, | |
| preValidation: DiffValidationResult | null, | |
| postValidation: PostApplyValidation | null, | |
| originalContent: string, | |
| rolledBack = false, | |
| ): DiffPipelineResult { | |
| return { success: false, result: originalContent, rolledBack, preValidation, postValidation, stage, message }; | |
| } | |