@@ -2322,37 +2322,15 @@ def integration_upgrade(
23222322 raise typer .Exit (1 )
23232323
23242324 # Phase 2: Remove stale files from old manifest that are not in the new one
2325- root = project_root .resolve ()
2326- old_files = set (old_manifest .files .keys ())
2327- new_files = set (new_manifest .files .keys ())
2328- stale_files = old_files - new_files
2329- stale_removed = 0
2330- for rel in stale_files :
2331- path = root / rel
2332- # Validate containment to prevent path traversal from tampered manifests
2333- try :
2334- normed = Path (os .path .normpath (path ))
2335- normed .relative_to (root )
2336- except (ValueError , OSError ):
2337- continue
2338- if path .is_symlink () or path .is_file ():
2339- try :
2340- path .unlink ()
2341- stale_removed += 1
2342- # Clean up empty parent directories up to project root
2343- parent = path .parent
2344- while parent != root :
2345- try :
2346- parent .rmdir ()
2347- except OSError :
2348- break
2349- parent = parent .parent
2350- except OSError :
2351- # Best-effort cleanup: if a stale file cannot be removed (e.g. permission/race),
2352- # continue upgrade flow without failing the command.
2353- pass
2354- if stale_removed :
2355- console .print (f" Removed { stale_removed } stale file(s) from previous install" )
2325+ old_files = old_manifest .files
2326+ new_files = new_manifest .files
2327+ stale_keys = set (old_files ) - set (new_files )
2328+ if stale_keys :
2329+ stale_manifest = IntegrationManifest (key , project_root , version = "stale-cleanup" )
2330+ stale_manifest ._files = {k : old_files [k ] for k in stale_keys }
2331+ stale_removed , _ = stale_manifest .uninstall (project_root , force = True )
2332+ if stale_removed :
2333+ console .print (f" Removed { len (stale_removed )} stale file(s) from previous install" )
23562334
23572335 name = (integration .config or {}).get ("name" , key )
23582336 console .print (f"\n [green]✓[/green] Integration '{ name } ' upgraded successfully" )
0 commit comments