(args: z.infer<typeof MultiFileEditArgs>, _ctx: ToolContext)
| 95 | argsSchema = MultiFileEditArgs; |
| 96 | |
| 97 | async execute(args: z.infer<typeof MultiFileEditArgs>, _ctx: ToolContext): Promise<ToolResult> { |
| 98 | const plans: EditPlan[] = []; |
| 99 | const failures: string[] = []; |
| 100 | |
| 101 | // PASS 1: validate every file/edit without writing |
| 102 | for (const file of args.files) { |
| 103 | try { |
| 104 | const plan = await planFile(file.path, file.edits); |
| 105 | plans.push(plan); |
| 106 | } catch (e: any) { |
| 107 | failures.push(e?.message ?? String(e)); |
| 108 | } |
| 109 | } |
| 110 | |
| 111 | if (failures.length > 0) { |
| 112 | return { |
| 113 | content: `[MULTI_EDIT_REJECTED] Validation failed; NO files were modified:\n` + |
| 114 | failures.map(f => ' - ' + f).join('\n'), |
| 115 | isError: true, |
| 116 | }; |
| 117 | } |
| 118 | |
| 119 | if (args.dry_run) { |
| 120 | const summary = plans.map(p => { |
| 121 | const changedLines = countChangedLines(p.originalContent, p.finalContent); |
| 122 | return ` ${p.path} (${p.editCount} edit${p.editCount > 1 ? 's' : ''}, ${changedLines} line${changedLines !== 1 ? 's' : ''} would change)`; |
| 123 | }); |
| 124 | return { |
| 125 | content: `Dry run — ${plans.length} file(s) would be modified:\n${summary.join('\n')}\n\n` + |
| 126 | `Re-run with dry_run=false to apply.`, |
| 127 | }; |
| 128 | } |
| 129 | |
| 130 | // PASS 1.5: syntax-gate every final content BEFORE any write (this tool |
| 131 | // bypasses Transaction.write, so it runs the same gate explicitly). |
| 132 | // All-or-nothing is preserved: one syntactically broken plan rejects the batch. |
| 133 | { |
| 134 | const { checkSyntaxForWrite } = await import('../ast/syntax-check.js'); |
| 135 | const syntaxFailures: string[] = []; |
| 136 | for (const plan of plans) { |
| 137 | if (!plan.changed) continue; |
| 138 | const rejection = await checkSyntaxForWrite(plan.path, plan.originalContent, plan.finalContent); |
| 139 | if (rejection) syntaxFailures.push(rejection); |
| 140 | } |
| 141 | if (syntaxFailures.length > 0) { |
| 142 | return { |
| 143 | content: `[MULTI_EDIT_REJECTED] Syntax validation failed; NO files were modified:\n` + |
| 144 | syntaxFailures.map(f => ' - ' + f).join('\n'), |
| 145 | isError: true, |
| 146 | }; |
| 147 | } |
| 148 | } |
| 149 | |
| 150 | // PASS 2: write all the files |
| 151 | // We do NOT have transaction integration here yet (that would touch the |
| 152 | // transaction journal); for v0.9.2 we do sequential writes and report any |
| 153 | // partial failure clearly. Full transaction integration in v1.0.0. |
| 154 | const written: string[] = []; |
nothing calls this directly
no test coverage detected