@@ -212,6 +212,63 @@ describe('integration', () => {
212212 expect ( result . filesChanged ) . toBeGreaterThanOrEqual ( 1 ) ;
213213 } ) ;
214214
215+ it ( 'rollback on transform error does not leak packages or diagnostics' , ( ) => {
216+ const dir = createTempDir ( ) ;
217+ writePkgJson ( dir , {
218+ dependencies : { '@modelcontextprotocol/sdk' : '^1.0.0' }
219+ } ) ;
220+
221+ const input = [
222+ `import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';` ,
223+ `const server = new McpServer({ name: 'test', version: '1.0' });` ,
224+ ``
225+ ] . join ( '\n' ) ;
226+ writeFileSync ( path . join ( dir , 'broken.ts' ) , input ) ;
227+
228+ const leakyTransform : Transform = {
229+ name : 'leaky' ,
230+ id : 'imports' ,
231+ apply ( sourceFile ) {
232+ return {
233+ changesCount : 1 ,
234+ diagnostics : [ { level : DiagnosticLevel . Warning , file : sourceFile . getFilePath ( ) , line : 1 , message : 'should not survive rollback' } ] ,
235+ usedPackages : new Set ( [ '@modelcontextprotocol/phantom-pkg' ] )
236+ } ;
237+ }
238+ } ;
239+
240+ const failingTransform : Transform = {
241+ name : 'failing' ,
242+ id : 'failing' ,
243+ apply ( ) {
244+ throw new Error ( 'boom' ) ;
245+ }
246+ } ;
247+
248+ const testMigration : Migration = {
249+ name : 'test-rollback' ,
250+ description : 'Tests that rollback cleans up all side effects' ,
251+ transforms : [ leakyTransform , failingTransform ]
252+ } ;
253+
254+ const result = run ( testMigration , { targetDir : dir } ) ;
255+
256+ // File should be rolled back
257+ const output = readFileSync ( path . join ( dir , 'broken.ts' ) , 'utf8' ) ;
258+ expect ( output ) . toBe ( input ) ;
259+
260+ // Only the error diagnostic should survive — not the warning from the reverted transform
261+ const warnings = result . diagnostics . filter ( d => d . level === DiagnosticLevel . Warning ) ;
262+ expect ( warnings ) . toHaveLength ( 0 ) ;
263+ const errors = result . diagnostics . filter ( d => d . level === DiagnosticLevel . Error ) ;
264+ expect ( errors ) . toHaveLength ( 1 ) ;
265+ expect ( errors [ 0 ] . message ) . toContain ( 'boom' ) ;
266+
267+ // Phantom package from the reverted transform should not leak into package.json
268+ expect ( result . packageJsonChanges ) . toBeDefined ( ) ;
269+ expect ( result . packageJsonChanges ! . added ) . not . toContain ( '@modelcontextprotocol/phantom-pkg' ) ;
270+ } ) ;
271+
215272 it ( 'respects transform filter option' , ( ) => {
216273 const dir = createTempDir ( ) ;
217274 const input = [
0 commit comments