""" Milestone command group for TinyTorch CLI: capability-based learning progression. The milestone system transforms module completion into meaningful capability achievements. Instead of just finishing modules, students unlock epic milestones that represent real-world ML engineering skills. """ from argparse import ArgumentParser, Namespace from rich.panel import Panel from rich import box from rich.progress import Progress, BarColumn, TextColumn, SpinnerColumn, TimeElapsedColumn from rich.console import Console from rich.align import Align from rich.text import Text from rich.layout import Layout from rich.tree import Tree from rich.columns import Columns import sys import json import time import subprocess import yaml from datetime import datetime from pathlib import Path from .base import BaseCommand from .checkpoint import CheckpointSystem from ..core.console import print_ascii_logo from ..core.console import get_console # Milestone-to-script mapping for tito milestone run command MILESTONE_SCRIPTS = { "01": { "id": "01", "name": "Perceptron (1957)", "year": 1957, "title": "Frank Rosenblatt's First Neural Network", "script": "milestones/01_1957_perceptron/02_rosenblatt_trained.py", "required_modules": [1], "description": "Build the first trainable neural network", "historical_context": "Rosenblatt's perceptron proved machines could learn", "emoji": "๐Ÿง " }, "02": { "id": "02", "name": "XOR Crisis (1969)", "year": 1969, "title": "Solving the Problem That Stalled AI", "script": "milestones/02_1969_xor/02_xor_solved.py", "required_modules": [1, 2], "description": "Solve XOR with multi-layer networks", "historical_context": "Minsky & Papert showed single-layer limits", "emoji": "๐Ÿ”€" }, "03": { "id": "03", "name": "MLP Revival (1986)", "year": 1986, "title": "Backpropagation Breakthrough", "script": "milestones/03_1986_mlp/01_rumelhart_tinydigits.py", "required_modules": [1, 2, 3, 4, 5, 6, 7], "description": "Train deep networks on TinyDigits", "historical_context": "Rumelhart, Hinton & Williams (Nature, 1986)", "emoji": "๐ŸŽ“" }, "04": { "id": "04", "name": "CNN Revolution (1998)", "year": 1998, "title": "LeNet - Computer Vision Breakthrough", "script": "milestones/04_1998_cnn/01_lecun_tinydigits.py", "required_modules": [1, 2, 3, 4, 5, 6, 7, 8, 9], "description": "Build LeNet for digit recognition", "historical_context": "Yann LeCun's convolutional networks", "emoji": "๐Ÿ‘๏ธ" }, "05": { "id": "05", "name": "Transformer Era (2017)", "year": 2017, "title": "Attention is All You Need", "script": "milestones/05_2017_transformer/03_quickdemo.py", "required_modules": list(range(1, 14)), "description": "Build transformer with self-attention", "historical_context": "Vaswani et al. revolutionized NLP", "emoji": "๐Ÿค–" }, "06": { "id": "06", "name": "MLPerf Benchmarks (2018)", "year": 2018, "title": "Production ML Systems", "script": "milestones/06_2018_mlperf/02_compression.py", "required_modules": list(range(1, 20)), "description": "Optimize for production deployment", "historical_context": "MLPerf standardized ML benchmarks", "emoji": "๐Ÿ†" } } class MilestoneSystem: """Core milestone tracking and management system.""" def __init__(self, config): self.config = config self.console = get_console() # Load milestones from configuration file self.MILESTONES = self._load_milestones_config() def _load_milestones_config(self) -> dict: """Load milestone configuration from YAML files (main and era-specific).""" config_path = Path("milestones") / "milestones.yml" milestones = {} # Try to load main milestones.yml first if config_path.exists(): try: with open(config_path, 'r') as f: config = yaml.safe_load(f) # Convert to expected format for milestone_id, milestone_data in config['milestones'].items(): milestone_data['id'] = str(milestone_id) milestones[str(milestone_id)] = milestone_data except Exception as e: self.console.print(f"[yellow]Warning: Could not load main milestone config: {e}[/yellow]") # Also try to load era-specific configurations era_paths = [ Path("milestones") / "foundation" / "milestone.yml", Path("milestones") / "revolution" / "milestone.yml", Path("milestones") / "generation" / "milestone.yml" ] for era_path in era_paths: if era_path.exists(): try: with open(era_path, 'r') as f: era_config = yaml.safe_load(f) if 'milestone' in era_config: milestone_data = era_config['milestone'] milestone_id = milestone_data['id'] milestones[str(milestone_id)] = milestone_data except Exception as e: self.console.print(f"[yellow]Warning: Could not load era config {era_path}: {e}[/yellow]") # If no milestones loaded, return fallback if not milestones: return { "1": { "id": "1", "name": "Machines Can See", "title": "I taught a computer to recognize digits!", "trigger_module": "05_dense", "required_checkpoints": ["00", "01", "02", "03", "04"], "victory_condition": "85%+ MNIST accuracy with MLP", "capability": "I can create neural networks that recognize images!", "real_world_impact": "Foundation for any computer vision system", "emoji": "๐Ÿ‘๏ธ", "test_file": "milestone_01_machines_can_see.py" } } return milestones def get_milestone_status(self) -> dict: """Get current milestone progress status.""" checkpoint_system = CheckpointSystem(self.config) milestone_data = self._get_milestone_progress_data() status = { "milestones": {}, "overall_progress": 0, "total_unlocked": 0, "next_milestone": None } total_milestones = len(self.MILESTONES) unlocked_count = 0 for milestone_id, milestone in self.MILESTONES.items(): # Check if all required checkpoints are complete required_complete = all( checkpoint_system.get_checkpoint_status(cp_id).get("is_complete", False) for cp_id in milestone["required_checkpoints"] ) # Check if milestone is unlocked is_unlocked = milestone_id in milestone_data.get("unlocked_milestones", []) # Check if trigger module is completed trigger_complete = self._is_module_completed(milestone["trigger_module"]) milestone_status = { "id": milestone_id, "name": milestone["name"], "title": milestone["title"], "emoji": milestone["emoji"], "trigger_module": milestone["trigger_module"], "required_checkpoints": milestone["required_checkpoints"], "victory_condition": milestone["victory_condition"], "capability": milestone["capability"], "real_world_impact": milestone["real_world_impact"], "required_complete": required_complete, "trigger_complete": trigger_complete, "is_unlocked": is_unlocked, "can_unlock": required_complete and trigger_complete and not is_unlocked, "unlock_date": milestone_data.get("unlock_dates", {}).get(milestone_id) } status["milestones"][milestone_id] = milestone_status if is_unlocked: unlocked_count += 1 elif milestone_status["can_unlock"] and not status["next_milestone"]: status["next_milestone"] = milestone_id status["total_unlocked"] = unlocked_count status["overall_progress"] = (unlocked_count / total_milestones) * 100 return status def check_milestone_unlock(self, completed_module: str) -> dict: """Check if completing a module unlocks a milestone.""" result = { "milestone_unlocked": False, "milestone_id": None, "milestone_data": None, "celebration_needed": False } # Find milestone triggered by this module for milestone_id, milestone in self.MILESTONES.items(): if milestone["trigger_module"] == completed_module: status = self.get_milestone_status() milestone_status = status["milestones"][milestone_id] if milestone_status["can_unlock"]: # Unlock the milestone! self._unlock_milestone(milestone_id) result.update({ "milestone_unlocked": True, "milestone_id": milestone_id, "milestone_data": milestone, "celebration_needed": True }) break return result def run_milestone_test(self, milestone_id: str) -> dict: """Run tests to validate milestone achievement.""" if milestone_id not in self.MILESTONES: return {"success": False, "error": f"Milestone {milestone_id} not found"} milestone = self.MILESTONES[milestone_id] # Check all required checkpoints checkpoint_system = CheckpointSystem(self.config) failed_checkpoints = [] for cp_id in milestone["required_checkpoints"]: if not checkpoint_system.get_checkpoint_status(cp_id).get("is_complete", False): failed_checkpoints.append(cp_id) if failed_checkpoints: return { "success": False, "error": f"Required checkpoints not completed: {', '.join(failed_checkpoints)}", "milestone_name": milestone["name"] } # Check trigger module completion if not self._is_module_completed(milestone["trigger_module"]): return { "success": False, "error": f"Trigger module {milestone['trigger_module']} not completed", "milestone_name": milestone["name"] } # All tests passed return { "success": True, "milestone_id": milestone_id, "milestone_name": milestone["name"], "title": milestone["title"], "capability": milestone["capability"], "victory_condition": milestone["victory_condition"] } def _unlock_milestone(self, milestone_id: str) -> None: """Record milestone unlock in progress tracking.""" milestone_data = self._get_milestone_progress_data() if milestone_id not in milestone_data["unlocked_milestones"]: milestone_data["unlocked_milestones"].append(milestone_id) milestone_data["unlock_dates"][milestone_id] = datetime.now().isoformat() milestone_data["total_unlocked"] = len(milestone_data["unlocked_milestones"]) self._save_milestone_progress_data(milestone_data) def _is_module_completed(self, module_name: str) -> bool: """Check if a module has been completed.""" # Check module progress file progress_file = Path(".tito") / "progress.json" if progress_file.exists(): try: with open(progress_file, 'r') as f: progress_data = json.load(f) return module_name in progress_data.get("completed_modules", []) except (json.JSONDecodeError, IOError): pass return False def _get_milestone_progress_data(self) -> dict: """Get or create milestone progress data.""" progress_dir = Path(".tito") progress_file = progress_dir / "milestones.json" progress_dir.mkdir(exist_ok=True) if progress_file.exists(): try: with open(progress_file, 'r') as f: return json.load(f) except (json.JSONDecodeError, IOError): pass return { "unlocked_milestones": [], "unlock_dates": {}, "total_unlocked": 0, "achievements": [] } def _save_milestone_progress_data(self, milestone_data: dict) -> None: """Save milestone progress data.""" progress_dir = Path(".tito") progress_file = progress_dir / "milestones.json" progress_dir.mkdir(exist_ok=True) try: with open(progress_file, 'w') as f: json.dump(milestone_data, f, indent=2) except IOError: pass class MilestoneCommand(BaseCommand): @property def name(self) -> str: return "milestone" @property def description(self) -> str: return "Milestone achievement and capability unlock commands" def add_arguments(self, parser: ArgumentParser) -> None: subparsers = parser.add_subparsers( dest='milestone_command', help='Milestone subcommands', metavar='SUBCOMMAND' ) # List subcommand (NEW) list_parser = subparsers.add_parser( 'list', help='List available milestones and their status' ) list_parser.add_argument( '--simple', action='store_true', help='Show simple list (less detail)' ) # Run subcommand (NEW) run_parser = subparsers.add_parser( 'run', help='Run a milestone with prerequisite checking' ) run_parser.add_argument( 'milestone_id', help='Milestone ID to run (01-06)' ) run_parser.add_argument( '--skip-checks', action='store_true', help='Skip prerequisite checks (not recommended)' ) # Info subcommand (NEW) info_parser = subparsers.add_parser( 'info', help='Show detailed information about a milestone' ) info_parser.add_argument( 'milestone_id', help='Milestone ID to show info for (01-06)' ) # Status subcommand status_parser = subparsers.add_parser( 'status', help='View milestone progress and achievements' ) status_parser.add_argument( '--detailed', action='store_true', help='Show detailed milestone information' ) # Timeline subcommand timeline_parser = subparsers.add_parser( 'timeline', help='View milestone timeline and progression' ) timeline_parser.add_argument( '--horizontal', action='store_true', help='Show horizontal progress bar instead of tree' ) # Test subcommand test_parser = subparsers.add_parser( 'test', help='Test milestone achievement requirements' ) test_parser.add_argument( 'milestone_id', nargs='?', help='Milestone ID to test (1-5), or test next available' ) # Demo subcommand demo_parser = subparsers.add_parser( 'demo', help='Run milestone capability demonstration' ) demo_parser.add_argument( 'milestone_id', help='Milestone ID to demonstrate (1-5)' ) def run(self, args: Namespace) -> int: console = self.console if not hasattr(args, 'milestone_command') or not args.milestone_command: console.print(Panel( "[bold cyan]Milestone Commands[/bold cyan]\n\n" "Recreate ML history and achieve epic capabilities!\n\n" "Available subcommands:\n" " โ€ข [bold]list[/bold] - List available milestones\n" " โ€ข [bold]run[/bold] - Run a milestone (with prereq checks)\n" " โ€ข [bold]info[/bold] - Show detailed milestone information\n" " โ€ข [bold]status[/bold] - View progress and achievements\n" " โ€ข [bold]timeline[/bold] - View milestone timeline\n" " โ€ข [bold]test[/bold] - Test milestone requirements\n" " โ€ข [bold]demo[/bold] - Run capability demonstration\n\n" "[dim]Examples:[/dim]\n" "[dim] tito milestone list[/dim]\n" "[dim] tito milestone run 03[/dim]\n" "[dim] tito milestone info 03[/dim]\n" "[dim] tito milestone status --detailed[/dim]", title="๐Ÿ† Milestone System", border_style="bright_cyan" )) return 0 # Execute the appropriate subcommand if args.milestone_command == 'list': return self._handle_list_command(args) elif args.milestone_command == 'run': return self._handle_run_command(args) elif args.milestone_command == 'info': return self._handle_info_command(args) elif args.milestone_command == 'status': return self._handle_status_command(args) elif args.milestone_command == 'timeline': return self._handle_timeline_command(args) elif args.milestone_command == 'test': return self._handle_test_command(args) elif args.milestone_command == 'demo': return self._handle_demo_command(args) else: console.print(Panel( f"[red]Unknown milestone subcommand: {args.milestone_command}[/red]", title="Error", border_style="red" )) return 1 def _handle_status_command(self, args: Namespace) -> int: """Handle milestone status command.""" console = self.console milestone_system = MilestoneSystem(self.config) status = milestone_system.get_milestone_status() # Show header with overall progress total_milestones = len(milestone_system.MILESTONES) console.print(Panel( f"[bold cyan]๐ŸŽฎ TinyTorch Milestone Progress[/bold cyan]\n\n" f"[bold]Capabilities Unlocked:[/bold] {status['total_unlocked']}/{total_milestones} milestones\n" f"[bold]Overall Progress:[/bold] {status['overall_progress']:.0f}%\n\n" f"[dim]Transform from student to ML Systems Engineer![/dim]", title="๐Ÿš€ Your Epic Journey", border_style="bright_blue" )) # Show milestone status for milestone_id in sorted(milestone_system.MILESTONES.keys()): milestone = status["milestones"][milestone_id] self._show_milestone_status(milestone, args.detailed) # Show next steps if status["next_milestone"]: next_milestone = status["milestones"][status["next_milestone"]] console.print(Panel( f"[bold cyan]๐ŸŽฏ Next Achievement[/bold cyan]\n\n" f"[bold yellow]{next_milestone['emoji']} {next_milestone['title']}[/bold yellow]\n" f"[dim]{next_milestone['victory_condition']}[/dim]\n\n" f"[green]Ready to unlock![/green] Complete: {next_milestone['trigger_module']}\n" f"[dim]tito module complete {next_milestone['trigger_module']}[/dim]", title="Next Milestone", border_style="bright_green" )) elif status["total_unlocked"] == 5: console.print(Panel( f"[bold green]๐Ÿ† QUEST COMPLETE! ๐Ÿ†[/bold green]\n\n" f"[green]You've unlocked all 5 epic milestones![/green]\n" f"[bold white]You are now an ML Systems Engineer![/bold white]\n\n" f"[cyan]Share your achievement and inspire others![/cyan]", title="๐ŸŒŸ FULL MASTERY ACHIEVED", border_style="bright_green" )) return 0 def _show_milestone_status(self, milestone: dict, detailed: bool = False) -> None: """Show status for a single milestone.""" console = self.console # Status indicator if milestone["is_unlocked"]: status_icon = "๐Ÿ”“" status_color = "green" status_text = "UNLOCKED" elif milestone["can_unlock"]: status_icon = "โšก" status_color = "yellow" status_text = "READY TO UNLOCK" elif milestone["required_complete"] and not milestone["trigger_complete"]: status_icon = "๐Ÿ”’" status_color = "cyan" status_text = f"COMPLETE: {milestone['trigger_module']}" else: status_icon = "๐Ÿ”’" status_color = "dim" status_text = "LOCKED" # Basic display milestone_content = ( f"[{status_color}]{status_icon} {milestone['emoji']} {milestone['title']}[/{status_color}]\n" f"[dim]{milestone['victory_condition']}[/dim]" ) # Add detailed information if requested if detailed: req_status = "โœ…" if milestone["required_complete"] else "โŒ" trigger_status = "โœ…" if milestone["trigger_complete"] else "โŒ" milestone_content += ( f"\n\n[bold]Requirements:[/bold]\n" f" {req_status} Checkpoints: {', '.join(milestone['required_checkpoints'])}\n" f" {trigger_status} Module: {milestone['trigger_module']}\n" f"[bold]Capability:[/bold] {milestone['capability']}\n" f"[bold]Impact:[/bold] {milestone['real_world_impact']}" ) if milestone["is_unlocked"] and milestone.get("unlock_date"): unlock_date = datetime.fromisoformat(milestone["unlock_date"]).strftime("%Y-%m-%d") milestone_content += f"\n[dim]Unlocked: {unlock_date}[/dim]" console.print(Panel( milestone_content, title=f"Milestone {milestone['id']}", border_style=status_color )) def _handle_timeline_command(self, args: Namespace) -> int: """Handle milestone timeline command.""" console = self.console milestone_system = MilestoneSystem(self.config) status = milestone_system.get_milestone_status() if args.horizontal: self._show_horizontal_timeline(status, milestone_system) else: self._show_tree_timeline(status, milestone_system) return 0 def _show_horizontal_timeline(self, status: dict, milestone_system: MilestoneSystem) -> None: """Show horizontal progress bar timeline.""" console = self.console total_milestones = len(milestone_system.MILESTONES) console.print(Panel( f"[bold cyan]๐ŸŽฎ Milestone Timeline[/bold cyan]\n\n" f"[bold]Progress:[/bold] {status['total_unlocked']}/{total_milestones} milestones unlocked", title="Your Epic Journey", border_style="bright_blue" )) # Create progress bar progress_width = 50 total_milestones = len(milestone_system.MILESTONES) unlocked_width = int((status["total_unlocked"] / total_milestones) * progress_width) # Create milestone markers timeline = [] for milestone_id in sorted(milestone_system.MILESTONES.keys()): milestone = status["milestones"][milestone_id] if milestone["is_unlocked"]: marker = f"[green]{milestone['emoji']}[/green]" elif milestone["can_unlock"]: marker = f"[yellow blink]{milestone['emoji']}[/yellow blink]" else: marker = f"[dim]{milestone['emoji']}[/dim]" timeline.append(marker) # Show timeline console.print(f"\n{' '.join(timeline)}") # Progress bar filled = "โ–ˆ" * unlocked_width empty = "โ–‘" * (progress_width - unlocked_width) console.print(f"\n[green]{filled}[/green][dim]{empty}[/dim]") console.print(f"[dim]{status['overall_progress']:.0f}% complete[/dim]\n") def _show_tree_timeline(self, status: dict, milestone_system: MilestoneSystem) -> None: """Show tree-style milestone timeline.""" console = self.console console.print(Panel( f"[bold cyan]๐ŸŽฎ Milestone Progression Tree[/bold cyan]\n\n" f"[bold]Your journey from student to ML Systems Engineer[/bold]", title="Epic Timeline", border_style="bright_blue" )) # Create tree structure tree = Tree("๐Ÿš€ [bold]TinyTorch Mastery Journey[/bold]") for milestone_id in sorted(milestone_system.MILESTONES.keys()): milestone = status["milestones"][milestone_id] if milestone["is_unlocked"]: node_style = "green" icon = "โœ…" elif milestone["can_unlock"]: node_style = "yellow" icon = "โšก" else: node_style = "dim" icon = "๐Ÿ”’" branch = tree.add( f"[{node_style}]{icon} {milestone['emoji']} {milestone['title']}[/{node_style}]" ) # Add capability description branch.add(f"[dim]{milestone['capability']}[/dim]") # Add trigger module info if milestone["trigger_complete"]: branch.add(f"[green]โœ… {milestone['trigger_module']} completed[/green]") else: branch.add(f"[dim]๐ŸŽฏ Complete: {milestone['trigger_module']}[/dim]") console.print(tree) console.print() def _handle_test_command(self, args: Namespace) -> int: """Handle milestone test command.""" console = self.console milestone_system = MilestoneSystem(self.config) # Determine which milestone to test if args.milestone_id: milestone_id = args.milestone_id else: # Test next available milestone status = milestone_system.get_milestone_status() if status["next_milestone"]: milestone_id = status["next_milestone"] else: console.print(Panel( "[yellow]No milestone available to test.[/yellow]\n\n" "Either all milestones are unlocked or none are ready.\n" "Use [dim]tito milestone status[/dim] to see your progress.", title="No Test Available", border_style="yellow" )) return 0 # Validate milestone ID if milestone_id not in milestone_system.MILESTONES: console.print(Panel( f"[red]Invalid milestone ID: {milestone_id}[/red]\n\n" f"Valid milestone IDs: 1, 2, 3, 4, 5", title="Invalid Milestone", border_style="red" )) return 1 milestone = milestone_system.MILESTONES[milestone_id] console.print(Panel( f"[bold cyan]๐Ÿงช Testing Milestone {milestone_id}[/bold cyan]\n\n" f"[bold]{milestone['emoji']} {milestone['title']}[/bold]\n" f"[dim]{milestone['victory_condition']}[/dim]", title="Milestone Test", border_style="bright_cyan" )) # Run the test with progress animation with console.status(f"[bold green]Testing milestone requirements...", spinner="dots"): result = milestone_system.run_milestone_test(milestone_id) # Show results if result["success"]: console.print(Panel( f"[bold green]โœ… Milestone Test Passed![/bold green]\n\n" f"[green]All requirements met for {result['milestone_name']}[/green]\n" f"[cyan]Capability: {result['capability']}[/cyan]\n\n" f"[bold yellow]Complete the trigger module to unlock:[/bold yellow]\n" f"[dim]tito module complete {milestone['trigger_module']}[/dim]", title="๐ŸŽ‰ Ready to Unlock!", border_style="green" )) else: console.print(Panel( f"[bold yellow]โš ๏ธ Milestone Requirements Not Met[/bold yellow]\n\n" f"[yellow]Milestone: {result.get('milestone_name', 'Unknown')}[/yellow]\n" f"[red]Issue: {result.get('error', 'Unknown error')}[/red]\n\n" f"[cyan]Complete the required modules and try again.[/cyan]", title="Requirements Missing", border_style="yellow" )) return 0 def _handle_demo_command(self, args: Namespace) -> int: """Handle milestone demo command.""" console = self.console milestone_system = MilestoneSystem(self.config) milestone_id = args.milestone_id # Validate milestone ID if milestone_id not in milestone_system.MILESTONES: console.print(Panel( f"[red]Invalid milestone ID: {milestone_id}[/red]\n\n" f"Valid milestone IDs: 1, 2, 3, 4, 5", title="Invalid Milestone", border_style="red" )) return 1 milestone = milestone_system.MILESTONES[milestone_id] status = milestone_system.get_milestone_status() milestone_status = status["milestones"][milestone_id] # Check if milestone is unlocked if not milestone_status["is_unlocked"]: console.print(Panel( f"[yellow]Milestone {milestone_id} not yet unlocked.[/yellow]\n\n" f"[bold]{milestone['emoji']} {milestone['title']}[/bold]\n" f"[dim]{milestone['victory_condition']}[/dim]\n\n" f"[cyan]Complete the requirements first:[/cyan]\n" f"[dim]tito milestone test {milestone_id}[/dim]", title="Milestone Locked", border_style="yellow" )) return 0 # Check if demo file exists demo_path = Path("capabilities") / milestone["demo_file"] if not demo_path.exists(): console.print(Panel( f"[yellow]Demo not available for Milestone {milestone_id}[/yellow]\n\n" f"Demo file not found: {milestone['demo_file']}\n" f"[dim]This demo may be coming in a future update.[/dim]", title="Demo Unavailable", border_style="yellow" )) return 0 # Run the demo console.print(Panel( f"[bold cyan]๐ŸŽฌ Launching Milestone {milestone_id} Demo[/bold cyan]\n\n" f"[bold]{milestone['emoji']} {milestone['title']}[/bold]\n" f"[yellow]Watch your capability in action![/yellow]\n\n" f"[cyan]Demonstrating: {milestone['capability']}[/cyan]\n" f"[dim]Running: {milestone['demo_file']}[/dim]", title="Capability Demo", border_style="bright_cyan" )) try: result = subprocess.run( [sys.executable, str(demo_path)], capture_output=False, text=True ) if result.returncode == 0: console.print(Panel( f"[bold green]โœ… Demo completed successfully![/bold green]\n\n" f"[yellow]You've seen your {milestone['title']} capability in action![/yellow]\n" f"[cyan]Real-world impact: {milestone['real_world_impact']}[/cyan]", title="๐ŸŽ‰ Demo Complete", border_style="green" )) else: console.print(f"[yellow]โš ๏ธ Demo completed with status: {result.returncode}[/yellow]") except Exception as e: console.print(Panel( f"[red]โŒ Error running demo: {e}[/red]\n\n" f"[dim]You can manually run: python capabilities/{milestone['demo_file']}[/dim]", title="Demo Error", border_style="red" )) return 1 return 0 def _handle_list_command(self, args: Namespace) -> int: """Handle milestone list command - show available milestones.""" console = self.console console.print(Panel( "[bold cyan]๐Ÿ† TinyTorch Milestones[/bold cyan]\n\n" "[dim]Recreate ML history from 1957 to 2018[/dim]", title="Available Milestones", border_style="bright_cyan" )) # Check module completion status progress_file = Path(".tito") / "progress.json" completed_modules = [] if progress_file.exists(): try: with open(progress_file, 'r') as f: progress_data = json.load(f) completed_modules = progress_data.get("completed_modules", []) except (json.JSONDecodeError, IOError): pass # Check milestone completion milestone_progress = self._get_milestone_progress_data() completed_milestones = milestone_progress.get("completed_milestones", []) for milestone_id in sorted(MILESTONE_SCRIPTS.keys()): milestone = MILESTONE_SCRIPTS[milestone_id] # Check if prerequisites met prereqs_met = all(mod in completed_modules for mod in milestone["required_modules"]) is_complete = milestone_id in completed_milestones # Status indicator if is_complete: status_icon = "โœ…" status_color = "green" status_text = "COMPLETE" elif prereqs_met: status_icon = "๐ŸŽฏ" status_color = "yellow" status_text = "READY TO RUN" else: status_icon = "๐Ÿ”’" status_color = "dim" status_text = "LOCKED" # Build display if args.simple: console.print(f"[{status_color}]{status_icon} {milestone['id']} - {milestone['name']}[/{status_color}]") else: milestone_display = ( f"[{status_color}]{status_icon} {milestone['emoji']} {milestone['name']}[/{status_color}]\n" f"[bold]{milestone['title']}[/bold]\n" f"[dim]{milestone['description']}[/dim]\n" f"[dim]Historical: {milestone['historical_context']}[/dim]\n\n" ) if prereqs_met and not is_complete: milestone_display += f"[bold yellow]โ–ถ Run now:[/bold yellow] [cyan]tito milestone run {milestone_id}[/cyan]\n" elif not prereqs_met: missing = [str(m) for m in milestone["required_modules"] if m not in completed_modules] milestone_display += f"[dim]Required: Complete modules {', '.join(missing)}[/dim]\n" console.print(Panel( milestone_display.strip(), title=f"Milestone {milestone['id']} ({milestone['year']})", border_style=status_color )) return 0 def _handle_run_command(self, args: Namespace) -> int: """Handle milestone run command - run a milestone with checks.""" console = self.console milestone_id = args.milestone_id # Validate milestone ID if milestone_id not in MILESTONE_SCRIPTS: console.print(Panel( f"[red]Invalid milestone ID: {milestone_id}[/red]\n\n" f"Valid milestone IDs: {', '.join(sorted(MILESTONE_SCRIPTS.keys()))}", title="Invalid Milestone", border_style="red" )) return 1 milestone = MILESTONE_SCRIPTS[milestone_id] # Check if script exists script_path = Path(milestone["script"]) if not script_path.exists(): console.print(Panel( f"[red]Milestone script not found![/red]\n\n" f"Expected: {milestone['script']}\n" f"[dim]This milestone may not be implemented yet.[/dim]", title="Script Not Found", border_style="red" )) return 1 # Check prerequisites and validate exports/tests (unless skipped) if not args.skip_checks: console.print(f"\n[bold cyan]๐Ÿ” Checking prerequisites for Milestone {milestone_id}...[/bold cyan]\n") # Use unified progress tracker from ..core.progress_tracker import ProgressTracker from ..core.milestone_validator import MilestoneValidator from .export import ExportCommand from .test import TestCommand tracker = ProgressTracker(self.config.project_root) validator = MilestoneValidator(self.config.project_root, console) export_cmd = ExportCommand(self.config) test_cmd = TestCommand(self.config) # Check if modules are completed all_complete, missing_modules = validator.check_prerequisites(milestone_id, tracker) if not all_complete: console.print(Panel( f"[bold yellow]โŒ Missing Required Modules[/bold yellow]\n\n" f"[yellow]Milestone {milestone_id} requires modules: {', '.join(f'{m:02d}' for m in milestone['required_modules'])}[/yellow]\n" f"[red]Missing: {', '.join(f'{m:02d}' for m in missing_modules)}[/red]\n\n" f"[cyan]Complete the missing modules first:[/cyan]\n" + "\n".join(f"[dim] tito module complete {m:02d}[/dim]" for m in missing_modules[:3]), title="Prerequisites Not Met", border_style="yellow" )) return 1 console.print(f"[green]โœ… All required modules completed![/green]\n") # Validate that all modules are exported and tested console.print(f"[bold cyan]๐Ÿ”ง Validating exports and tests...[/bold cyan]\n") success, failed = validator.validate_and_export_modules(milestone_id, export_cmd, test_cmd) if not success: console.print(Panel( f"[bold red]โŒ Validation Failed[/bold red]\n\n" f"[yellow]Some required modules failed export or tests:[/yellow]\n" f"[red]{', '.join(failed)}[/red]\n\n" f"[cyan]Fix the issues and try again:[/cyan]\n" f"[dim] tito module complete XX[/dim] - Re-export and test modules", title="Validation Failed", border_style="red" )) return 1 console.print(f"\n[green]โœ… All modules exported and tested! Ready to run milestone.[/green]\n") # Test imports work console.print("[bold cyan]๐Ÿงช Testing YOUR implementations...[/bold cyan]\n") # Try importing key components (basic check) try: import sys as _sys _sys.path.insert(0, str(Path.cwd())) if 1 in milestone["required_modules"]: from tinytorch import Tensor console.print(" [green]โœ“[/green] Tensor import successful") if 2 in milestone["required_modules"]: from tinytorch import ReLU console.print(" [green]โœ“[/green] Activations import successful") if 3 in milestone["required_modules"]: from tinytorch import Linear console.print(" [green]โœ“[/green] Layers import successful") console.print(f"\n[green]โœ… YOUR TinyTorch is ready![/green]\n") except ImportError as e: console.print(Panel( f"[red]Import Error![/red]\n\n" f"[yellow]Error: {e}[/yellow]\n\n" f"[dim]Your modules may not be exported correctly.[/dim]\n" f"[dim]Try re-exporting: tito module complete XX[/dim]", title="Import Test Failed", border_style="red" )) return 1 # Show milestone banner console.print(Panel( f"[bold magenta]โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—[/bold magenta]\n" f"[bold magenta]โ•‘[/bold magenta] {milestone['emoji']} Milestone {milestone_id}: {milestone['name']:<30} [bold magenta]โ•‘[/bold magenta]\n" f"[bold magenta]โ•‘[/bold magenta] {milestone['title']:<44} [bold magenta]โ•‘[/bold magenta]\n" f"[bold magenta]โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•[/bold magenta]\n\n" f"[bold]๐Ÿ“š Historical Context:[/bold]\n" f"{milestone['historical_context']}\n\n" f"[bold]๐ŸŽฏ What You'll Do:[/bold]\n" f"{milestone['description']}\n\n" f"[bold]๐Ÿ“‚ Running:[/bold] {milestone['script']}\n\n" f"[dim]All code uses YOUR TinyTorch implementations![/dim]", title=f"๐Ÿ† Milestone {milestone_id} ({milestone['year']})", border_style="bright_magenta", padding=(1, 2) )) try: input("\n[yellow]Press Enter to begin...[/yellow] ") except EOFError: # Non-interactive mode, proceed automatically pass # Run the milestone script console.print(f"\n[bold green]๐Ÿš€ Starting Milestone {milestone_id}...[/bold green]\n") console.print("โ”" * 80 + "\n") try: result = subprocess.run( [sys.executable, str(script_path)], capture_output=False, text=True ) console.print("\n" + "โ”" * 80) if result.returncode == 0: # Success! Mark milestone as complete self._mark_milestone_complete(milestone_id) # Also update unified progress tracker try: from ..core.progress_tracker import ProgressTracker tracker = ProgressTracker(self.config.project_root) tracker.mark_milestone_completed(milestone_id) except Exception: pass # Non-critical console.print(Panel( f"[bold green]๐Ÿ† MILESTONE ACHIEVED![/bold green]\n\n" f"[green]You completed Milestone {milestone_id}: {milestone['name']}[/green]\n" f"[yellow]{milestone['title']}[/yellow]\n\n" f"[bold]What makes this special:[/bold]\n" f"โ€ข Every line of code: YOUR implementations\n" f"โ€ข Every tensor operation: YOUR Tensor class\n" f"โ€ข Every gradient: YOUR autograd\n\n" f"[cyan]Achievement saved to your progress![/cyan]", title="โœจ Achievement Unlocked โœจ", border_style="bright_green", padding=(1, 2) )) # Show next steps next_id = str(int(milestone_id) + 1).zfill(2) if next_id in MILESTONE_SCRIPTS: next_milestone = MILESTONE_SCRIPTS[next_id] console.print(f"\n[bold yellow]๐ŸŽฏ What's Next:[/bold yellow]") console.print(f"[dim]Milestone {next_id}: {next_milestone['name']} ({next_milestone['year']})[/dim]") # Get completed modules for checking next milestone progress_file = Path(".tito") / "progress.json" completed_modules = [] if progress_file.exists(): try: with open(progress_file, 'r') as f: progress_data = json.load(f) for mod in progress_data.get("completed_modules", []): try: completed_modules.append(int(mod.split("_")[0])) except (ValueError, IndexError): pass except (json.JSONDecodeError, IOError): pass # Check if unlocked missing = [m for m in next_milestone["required_modules"] if m not in completed_modules] if missing: console.print(f"[dim]Unlock by completing modules: {', '.join(f'{m:02d}' for m in missing[:3])}[/dim]") else: console.print(f"[green]Ready to run: tito milestone run {next_id}[/green]") return 0 else: console.print(f"[yellow]โš ๏ธ Milestone completed with errors (exit code: {result.returncode})[/yellow]") return result.returncode except KeyboardInterrupt: console.print(f"\n\n[yellow]โš ๏ธ Milestone interrupted by user[/yellow]") return 130 except Exception as e: console.print(Panel( f"[red]โŒ Error running milestone: {e}[/red]\n\n" f"[dim]You can try running manually:[/dim]\n" f"[dim]python {milestone['script']}[/dim]", title="Execution Error", border_style="red" )) return 1 def _handle_info_command(self, args: Namespace) -> int: """Handle milestone info command - show detailed information.""" console = self.console milestone_id = args.milestone_id if milestone_id not in MILESTONE_SCRIPTS: console.print(Panel( f"[red]Invalid milestone ID: {milestone_id}[/red]\n\n" f"Valid milestone IDs: {', '.join(sorted(MILESTONE_SCRIPTS.keys()))}", title="Invalid Milestone", border_style="red" )) return 1 milestone = MILESTONE_SCRIPTS[milestone_id] # Check status progress_file = Path(".tito") / "progress.json" completed_modules = [] if progress_file.exists(): try: with open(progress_file, 'r') as f: progress_data = json.load(f) completed_modules = progress_data.get("completed_modules", []) except: pass prereqs_met = all(m in completed_modules for m in milestone["required_modules"]) # Display detailed info info_text = ( f"[bold cyan]{milestone['emoji']} {milestone['name']} ({milestone['year']})[/bold cyan]\n\n" f"[bold]{milestone['title']}[/bold]\n\n" f"[yellow]๐Ÿ“š Historical Context:[/yellow]\n" f"{milestone['historical_context']}\n\n" f"[yellow]๐ŸŽฏ Description:[/yellow]\n" f"{milestone['description']}\n\n" f"[yellow]๐Ÿ“‹ Required Modules:[/yellow]\n" ) for mod in milestone["required_modules"]: if mod in completed_modules: info_text += f" [green]โœ“[/green] Module {mod:02d}\n" else: info_text += f" [red]โœ—[/red] Module {mod:02d}\n" info_text += f"\n[yellow]๐Ÿ“‚ Script:[/yellow] {milestone['script']}\n" if prereqs_met: info_text += f"\n[bold green]โœ… Ready to run![/bold green]\n[cyan]tito milestone run {milestone_id}[/cyan]" else: missing = [m for m in milestone["required_modules"] if m not in completed_modules] info_text += f"\n[bold yellow]๐Ÿ”’ Locked[/bold yellow]\nComplete modules: {', '.join(f'{m:02d}' for m in missing)}" console.print(Panel( info_text, title=f"Milestone {milestone_id} Information", border_style="bright_cyan", padding=(1, 2) )) return 0 def _mark_milestone_complete(self, milestone_id: str) -> None: """Mark a milestone as complete in progress tracking.""" progress = self._get_milestone_progress_data() if milestone_id not in progress.get("completed_milestones", []): if "completed_milestones" not in progress: progress["completed_milestones"] = [] progress["completed_milestones"].append(milestone_id) progress["completion_dates"] = progress.get("completion_dates", {}) progress["completion_dates"][milestone_id] = datetime.now().isoformat() self._save_milestone_progress_data(progress) def _get_milestone_progress_data(self) -> dict: """Get or create milestone progress data.""" progress_dir = Path(".tito") progress_file = progress_dir / "milestones.json" progress_dir.mkdir(exist_ok=True) if progress_file.exists(): try: with open(progress_file, 'r') as f: return json.load(f) except (json.JSONDecodeError, IOError): pass return { "completed_milestones": [], "completion_dates": {}, "unlocked_milestones": [], "unlock_dates": {}, "total_unlocked": 0, "achievements": [] } def _save_milestone_progress_data(self, milestone_data: dict) -> None: """Save milestone progress data.""" progress_dir = Path(".tito") progress_file = progress_dir / "milestones.json" progress_dir.mkdir(exist_ok=True) try: with open(progress_file, 'w') as f: json.dump(milestone_data, f, indent=2) except IOError: pass