44
55import sys
66from pathlib import Path
7- from typing import Optional
87
98import typer
109
1716@app .callback (invoke_without_command = True )
1817def migrate (
1918 source : Path = typer .Argument (..., help = "Path to the ROS1 package directory." ),
20- output : Optional [ Path ] = typer .Option (
19+ output : Path | None = typer .Option (
2120 None , "--output" , "-o" , help = "Output directory for the migrated package."
2221 ),
2322 engine : str = typer .Option (
2423 "claude" , "--engine" , "-e" , help = "AI engine to use (claude / gemini / openai)."
2524 ),
26- mode : str = typer .Option (
27- "cli" , "--mode" , "-m" , help = "Engine mode: cli or api."
28- ),
25+ mode : str = typer .Option ("cli" , "--mode" , "-m" , help = "Engine mode: cli or api." ),
2926 target_distro : str = typer .Option (
3027 "humble" , "--distro" , "-d" , help = "Target ROS2 distribution (humble / iron / jazzy)."
3128 ),
32- verbose : bool = typer .Option (
33- False , "--verbose" , "-v" , help = "Enable verbose output."
34- ),
29+ verbose : bool = typer .Option (False , "--verbose" , "-v" , help = "Enable verbose output." ),
3530 max_fix_attempts : int = typer .Option (
36- 0 , "--max-fix-attempts" , help = "Maximum number of auto-fix attempts after build failure (0 = disabled)."
31+ 0 ,
32+ "--max-fix-attempts" ,
33+ help = "Maximum number of auto-fix attempts after build failure (0 = disabled)." ,
3734 ),
38- rules : Optional [ Path ] = typer .Option (
35+ rules : Path | None = typer .Option (
3936 None , "--rules" , help = "Path to a YAML file with custom transformation rules."
4037 ),
41- yes : bool = typer .Option (
42- False , "--yes" , "-y" , help = "Auto-proceed past cost estimate prompt."
43- ),
38+ yes : bool = typer .Option (False , "--yes" , "-y" , help = "Auto-proceed past cost estimate prompt." ),
4439 interactive : bool = typer .Option (
4540 False , "--interactive" , "-i" , help = "Interactively review each transformed file."
4641 ),
@@ -64,7 +59,8 @@ def migrate(
6459 custom_rules = None
6560 if rules is not None :
6661 try :
67- from rosforge .knowledge .custom_rules import load_custom_rules # noqa: PLC0415
62+ from rosforge .knowledge .custom_rules import load_custom_rules
63+
6864 custom_rules = load_custom_rules (rules )
6965 rule_count = (
7066 len (custom_rules .cpp_mappings )
@@ -75,13 +71,13 @@ def migrate(
7571 print_info (f"Custom rules loaded: [bold]{ rule_count } [/bold] overrides" )
7672 except FileNotFoundError as exc :
7773 print_error (str (exc ))
78- raise typer .Exit (code = 1 )
74+ raise typer .Exit (code = 1 ) from exc
7975 except ValueError as exc :
8076 print_error (f"Invalid custom rules file: { exc } " )
81- raise typer .Exit (code = 1 )
77+ raise typer .Exit (code = 1 ) from exc
8278 except ImportError as exc :
8379 print_error (str (exc ))
84- raise typer .Exit (code = 1 )
80+ raise typer .Exit (code = 1 ) from exc
8581
8682 # ── Load config and apply CLI overrides ──────────────────────────────
8783 cfg_manager = ConfigManager ()
@@ -102,14 +98,14 @@ def migrate(
10298
10399 # ── Import pipeline components ────────────────────────────────────────
104100 try :
105- from rosforge .pipeline .runner import PipelineContext , PipelineRunner # noqa: PLC0415
106- from rosforge .pipeline .ingest import IngestStage # noqa: PLC0415
107- from rosforge .pipeline .analyze import AnalyzeStage # noqa: PLC0415
108- from rosforge .pipeline .transform import TransformStage # noqa: PLC0415
109- from rosforge .pipeline .report import ReportStage # noqa: PLC0415
101+ from rosforge .pipeline .analyze import AnalyzeStage
102+ from rosforge .pipeline .ingest import IngestStage
103+ from rosforge .pipeline .report import ReportStage
104+ from rosforge .pipeline .runner import PipelineContext , PipelineRunner
105+ from rosforge .pipeline .transform import TransformStage
110106 except ImportError as exc :
111107 print_error (f"Pipeline not available: { exc } " )
112- raise typer .Exit (code = 1 )
108+ raise typer .Exit (code = 1 ) from exc
113109
114110 # ── Instantiate engine for fix loop (if needed) ───────────────────────
115111 pipeline_engine = None
@@ -118,22 +114,25 @@ def migrate(
118114
119115 if fix_loop_enabled :
120116 try :
121- from rosforge .engine .base import EngineRegistry # noqa: PLC0415
117+ from rosforge .engine .base import EngineRegistry
118+
122119 pipeline_engine = EngineRegistry .get (engine_key , config .engine )
123120 except KeyError as exc :
124121 print_error (f"Engine not available for fix loop: { exc } " )
125- raise typer .Exit (code = 1 )
122+ raise typer .Exit (code = 1 ) from exc
126123
127124 # ── Build stage list ──────────────────────────────────────────────────
128125 stages = [IngestStage (), AnalyzeStage (), TransformStage ()]
129126
130127 if interactive :
131- from rosforge .pipeline .interactive import InteractiveReviewStage # noqa: PLC0415
128+ from rosforge .pipeline .interactive import InteractiveReviewStage
129+
132130 config = cfg_manager .set (config , "migration.interactive" , True )
133131 stages .append (InteractiveReviewStage ())
134132
135133 if fix_loop_enabled :
136- from rosforge .pipeline .validate_fix_loop import ValidateFixLoopStage # noqa: PLC0415
134+ from rosforge .pipeline .validate_fix_loop import ValidateFixLoopStage
135+
137136 effective_max_attempts = max (max_fix_attempts , config .validation .max_fix_attempts )
138137 stages .append (ValidateFixLoopStage (max_attempts = effective_max_attempts ))
139138
@@ -188,11 +187,11 @@ def _after_stage(stage_name: str, pipeline_ctx: PipelineContext) -> None:
188187
189188 try :
190189 ctx = runner .run (ctx )
191- except Exception as exc : # noqa: BLE001
190+ except Exception as exc :
192191 print_error (f"Migration failed: { exc } " )
193192 if verbose :
194193 console .print_exception ()
195- raise typer .Exit (code = 1 )
194+ raise typer .Exit (code = 1 ) from exc
196195
197196 # ── Check if user declined after cost estimate ────────────────────────
198197 if not _proceed [0 ]:
@@ -223,17 +222,13 @@ def _after_stage(stage_name: str, pipeline_ctx: PipelineContext) -> None:
223222 report_path = output .resolve () / "migration_report.md"
224223 console .print ()
225224 console .print (
226- f"[bold green]Migration complete.[/bold green] "
227- f"Report: [cyan]{ report_path } [/cyan]"
225+ f"[bold green]Migration complete.[/bold green] Report: [cyan]{ report_path } [/cyan]"
228226 )
229227
230228 transformed = ctx .transformed_files
231229 rule_count_files = sum (1 for t in transformed if t .strategy_used == "rule_based" )
232230 ai_count = sum (1 for t in transformed if t .strategy_used == "ai_driven" )
233- conf_avg = (
234- sum (t .confidence for t in transformed ) / len (transformed )
235- if transformed else 0.0
236- )
231+ conf_avg = sum (t .confidence for t in transformed ) / len (transformed ) if transformed else 0.0
237232
238233 console .print (
239234 f" Files transformed: [bold]{ len (transformed )} [/bold] "
@@ -257,7 +252,9 @@ def _after_stage(stage_name: str, pipeline_ctx: PipelineContext) -> None:
257252 if low_conf > 0 :
258253 low_files = [t .target_path for t in transformed if t .confidence < 0.5 ]
259254 console .print ()
260- console .print ("[bold yellow]Low-confidence files (manual review recommended):[/bold yellow]" )
255+ console .print (
256+ "[bold yellow]Low-confidence files (manual review recommended):[/bold yellow]"
257+ )
261258 for lf in low_files :
262259 console .print (f" [yellow]•[/yellow] { lf } " )
263260
@@ -267,8 +264,7 @@ def _after_stage(stage_name: str, pipeline_ctx: PipelineContext) -> None:
267264 build_status = "[green]PASS[/green]" if build_ok else "[red]FAIL[/red]"
268265 console .print ()
269266 console .print (
270- f" Fix loop: [bold]{ ctx .fix_attempts } [/bold] attempt(s), "
271- f"final build: { build_status } "
267+ f" Fix loop: [bold]{ ctx .fix_attempts } [/bold] attempt(s), final build: { build_status } "
272268 )
273269
274270 recoverable_warnings = [e for e in ctx .errors if e .recoverable ]
0 commit comments