mirror of
https://github.com/MLSysBook/TinyTorch.git
synced 2026-06-01 05:35:50 -05:00
Implement clean start/resume/complete workflow - no overlaps!
PERFECT WORKFLOW: Clean lifecycle commands with distinct purposes New Commands (No Overlaps): ✅ tito module start 01 → Start working on module (first time only) ✅ tito module resume 01 → Resume working on module (continue work) ✅ tito module complete 01 → Complete module (test + export) ✅ tito module status → Show progress with 3 states Smart Features: ✅ State tracking: ⏳ not started → 🚀 in progress → ✅ completed ✅ Smart validation: start checks if already started, suggests resume ✅ Smart defaults: resume/complete work without module number ✅ Progress persistence: JSON file tracks started/completed modules ✅ Clear guidance: Always shows next logical step User Journey: 1. tito setup → Environment setup 2. tito module start 01 → Begin tensors (marks as started) 3. Work in Jupyter, save → Natural development 4. tito module complete 01 → Test, export, mark completed 5. tito module start 02 → Begin activations 6. tito module resume 02 → Continue activations later No command overlaps - each has distinct purpose and clear mental model!
This commit is contained in:
@@ -3,5 +3,9 @@
|
||||
"01"
|
||||
],
|
||||
"last_completed": "01",
|
||||
"last_updated": "2025-09-28T07:24:32.560451"
|
||||
"last_updated": "2025-09-28T07:57:44.694673",
|
||||
"started_modules": [
|
||||
"01"
|
||||
],
|
||||
"last_worked": "01"
|
||||
}
|
||||
@@ -36,20 +36,42 @@ class ModuleWorkflowCommand(BaseCommand):
|
||||
|
||||
def add_arguments(self, parser: ArgumentParser) -> None:
|
||||
"""Add module workflow arguments."""
|
||||
# Add subcommands first
|
||||
# Add subcommands - clean lifecycle workflow
|
||||
subparsers = parser.add_subparsers(
|
||||
dest='module_command',
|
||||
help='Module operations'
|
||||
help='Module lifecycle operations'
|
||||
)
|
||||
|
||||
# Complete command - the key workflow
|
||||
# START command - begin working on a module
|
||||
start_parser = subparsers.add_parser(
|
||||
'start',
|
||||
help='Start working on a module (first time)'
|
||||
)
|
||||
start_parser.add_argument(
|
||||
'module_number',
|
||||
help='Module number to start (01, 02, 03, etc.)'
|
||||
)
|
||||
|
||||
# RESUME command - continue working on a module
|
||||
resume_parser = subparsers.add_parser(
|
||||
'resume',
|
||||
help='Resume working on a module (continue previous work)'
|
||||
)
|
||||
resume_parser.add_argument(
|
||||
'module_number',
|
||||
nargs='?',
|
||||
help='Module number to resume (01, 02, 03, etc.) - defaults to last worked'
|
||||
)
|
||||
|
||||
# COMPLETE command - finish and validate a module
|
||||
complete_parser = subparsers.add_parser(
|
||||
'complete',
|
||||
help='Complete module: run tests, export if passing, update progress'
|
||||
)
|
||||
complete_parser.add_argument(
|
||||
'module_number',
|
||||
help='Module number to complete (01, 02, 03, etc.)'
|
||||
nargs='?',
|
||||
help='Module number to complete (01, 02, 03, etc.) - defaults to current'
|
||||
)
|
||||
complete_parser.add_argument(
|
||||
'--skip-tests',
|
||||
@@ -62,24 +84,11 @@ class ModuleWorkflowCommand(BaseCommand):
|
||||
help='Skip automatic export'
|
||||
)
|
||||
|
||||
# Status command
|
||||
# STATUS command - show progress
|
||||
status_parser = subparsers.add_parser(
|
||||
'status',
|
||||
help='Show module completion status'
|
||||
help='Show module completion status and progress'
|
||||
)
|
||||
|
||||
# Advanced commands (less commonly used)
|
||||
test_parser = subparsers.add_parser(
|
||||
'test',
|
||||
help='Run tests for specific module'
|
||||
)
|
||||
test_parser.add_argument('module_number', help='Module to test')
|
||||
|
||||
export_parser = subparsers.add_parser(
|
||||
'export',
|
||||
help='Export module to package'
|
||||
)
|
||||
export_parser.add_argument('module_number', help='Module to export')
|
||||
|
||||
def get_module_mapping(self) -> Dict[str, str]:
|
||||
"""Get mapping from numbers to module names."""
|
||||
@@ -113,8 +122,8 @@ class ModuleWorkflowCommand(BaseCommand):
|
||||
return f"{int(module_input):02d}"
|
||||
return module_input
|
||||
|
||||
def open_module(self, module_number: str) -> int:
|
||||
"""Open a module in Jupyter Lab."""
|
||||
def start_module(self, module_number: str) -> int:
|
||||
"""Start working on a module (first time)."""
|
||||
module_mapping = self.get_module_mapping()
|
||||
normalized = self.normalize_module_number(module_number)
|
||||
|
||||
@@ -125,10 +134,60 @@ class ModuleWorkflowCommand(BaseCommand):
|
||||
|
||||
module_name = module_mapping[normalized]
|
||||
|
||||
self.console.print(f"🚀 Opening Module {normalized}: {module_name}")
|
||||
# Check if already started
|
||||
if self.is_module_started(normalized):
|
||||
self.console.print(f"[yellow]⚠️ Module {normalized} already started[/yellow]")
|
||||
self.console.print(f"💡 Did you mean: [bold cyan]tito module resume {normalized}[/bold cyan]")
|
||||
return 1
|
||||
|
||||
# Mark as started
|
||||
self.mark_module_started(normalized)
|
||||
|
||||
self.console.print(f"🚀 Starting Module {normalized}: {module_name}")
|
||||
self.console.print("💡 Work in Jupyter, save your changes, then run:")
|
||||
self.console.print(f" [bold cyan]tito module complete {normalized}[/bold cyan]")
|
||||
|
||||
return self._open_jupyter(module_name)
|
||||
|
||||
def resume_module(self, module_number: Optional[str] = None) -> int:
|
||||
"""Resume working on a module (continue previous work)."""
|
||||
module_mapping = self.get_module_mapping()
|
||||
|
||||
# If no module specified, resume last worked
|
||||
if not module_number:
|
||||
last_worked = self.get_last_worked_module()
|
||||
if not last_worked:
|
||||
self.console.print("[yellow]⚠️ No module to resume[/yellow]")
|
||||
self.console.print("💡 Start with: [bold cyan]tito module start 01[/bold cyan]")
|
||||
return 1
|
||||
module_number = last_worked
|
||||
|
||||
normalized = self.normalize_module_number(module_number)
|
||||
|
||||
if normalized not in module_mapping:
|
||||
self.console.print(f"[red]❌ Module {normalized} not found[/red]")
|
||||
self.console.print("💡 Available modules: 01-21")
|
||||
return 1
|
||||
|
||||
module_name = module_mapping[normalized]
|
||||
|
||||
# Check if module was started
|
||||
if not self.is_module_started(normalized):
|
||||
self.console.print(f"[yellow]⚠️ Module {normalized} not started yet[/yellow]")
|
||||
self.console.print(f"💡 Start with: [bold cyan]tito module start {normalized}[/bold cyan]")
|
||||
return 1
|
||||
|
||||
# Update last worked
|
||||
self.update_last_worked(normalized)
|
||||
|
||||
self.console.print(f"🔄 Resuming Module {normalized}: {module_name}")
|
||||
self.console.print("💡 Continue your work, then run:")
|
||||
self.console.print(f" [bold cyan]tito module complete {normalized}[/bold cyan]")
|
||||
|
||||
return self._open_jupyter(module_name)
|
||||
|
||||
def _open_jupyter(self, module_name: str) -> int:
|
||||
"""Open Jupyter Lab for a module."""
|
||||
# Use the existing view command
|
||||
fake_args = Namespace()
|
||||
fake_args.module = module_name
|
||||
@@ -137,9 +196,19 @@ class ModuleWorkflowCommand(BaseCommand):
|
||||
view_command = ViewCommand(self.config)
|
||||
return view_command.run(fake_args)
|
||||
|
||||
def complete_module(self, module_number: str, skip_tests: bool = False, skip_export: bool = False) -> int:
|
||||
def complete_module(self, module_number: Optional[str] = None, skip_tests: bool = False, skip_export: bool = False) -> int:
|
||||
"""Complete a module with testing and export."""
|
||||
module_mapping = self.get_module_mapping()
|
||||
|
||||
# If no module specified, complete current/last worked
|
||||
if not module_number:
|
||||
last_worked = self.get_last_worked_module()
|
||||
if not last_worked:
|
||||
self.console.print("[yellow]⚠️ No module to complete[/yellow]")
|
||||
self.console.print("💡 Start with: [bold cyan]tito module start 01[/bold cyan]")
|
||||
return 1
|
||||
module_number = last_worked
|
||||
|
||||
normalized = self.normalize_module_number(module_number)
|
||||
|
||||
if normalized not in module_mapping:
|
||||
@@ -227,38 +296,89 @@ class ModuleWorkflowCommand(BaseCommand):
|
||||
self.console.print(f"[red]Error exporting module: {e}[/red]")
|
||||
return 1
|
||||
|
||||
def update_progress(self, module_number: str, module_name: str) -> None:
|
||||
"""Update user progress tracking."""
|
||||
def get_progress_data(self) -> dict:
|
||||
"""Get current progress data."""
|
||||
progress_file = self.config.project_root / "progress.json"
|
||||
|
||||
try:
|
||||
import json
|
||||
if progress_file.exists():
|
||||
with open(progress_file, 'r') as f:
|
||||
return json.load(f)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return {
|
||||
'started_modules': [],
|
||||
'completed_modules': [],
|
||||
'last_worked': None,
|
||||
'last_completed': None,
|
||||
'last_updated': None
|
||||
}
|
||||
|
||||
def save_progress_data(self, progress: dict) -> None:
|
||||
"""Save progress data."""
|
||||
progress_file = self.config.project_root / "progress.json"
|
||||
|
||||
try:
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
# Load existing progress
|
||||
progress = {}
|
||||
if progress_file.exists():
|
||||
with open(progress_file, 'r') as f:
|
||||
progress = json.load(f)
|
||||
|
||||
# Update progress
|
||||
if 'completed_modules' not in progress:
|
||||
progress['completed_modules'] = []
|
||||
|
||||
if module_number not in progress['completed_modules']:
|
||||
progress['completed_modules'].append(module_number)
|
||||
|
||||
progress['last_completed'] = module_number
|
||||
progress['last_updated'] = datetime.now().isoformat()
|
||||
|
||||
# Save progress
|
||||
with open(progress_file, 'w') as f:
|
||||
json.dump(progress, f, indent=2)
|
||||
|
||||
self.console.print(f"📈 Progress updated: {len(progress['completed_modules'])} modules completed")
|
||||
|
||||
except Exception as e:
|
||||
self.console.print(f"[yellow]⚠️ Could not update progress: {e}[/yellow]")
|
||||
self.console.print(f"[yellow]⚠️ Could not save progress: {e}[/yellow]")
|
||||
|
||||
def is_module_started(self, module_number: str) -> bool:
|
||||
"""Check if a module has been started."""
|
||||
progress = self.get_progress_data()
|
||||
return module_number in progress.get('started_modules', [])
|
||||
|
||||
def is_module_completed(self, module_number: str) -> bool:
|
||||
"""Check if a module has been completed."""
|
||||
progress = self.get_progress_data()
|
||||
return module_number in progress.get('completed_modules', [])
|
||||
|
||||
def mark_module_started(self, module_number: str) -> None:
|
||||
"""Mark a module as started."""
|
||||
progress = self.get_progress_data()
|
||||
|
||||
if 'started_modules' not in progress:
|
||||
progress['started_modules'] = []
|
||||
|
||||
if module_number not in progress['started_modules']:
|
||||
progress['started_modules'].append(module_number)
|
||||
|
||||
progress['last_worked'] = module_number
|
||||
self.save_progress_data(progress)
|
||||
|
||||
def update_last_worked(self, module_number: str) -> None:
|
||||
"""Update the last worked module."""
|
||||
progress = self.get_progress_data()
|
||||
progress['last_worked'] = module_number
|
||||
self.save_progress_data(progress)
|
||||
|
||||
def get_last_worked_module(self) -> Optional[str]:
|
||||
"""Get the last worked module."""
|
||||
progress = self.get_progress_data()
|
||||
return progress.get('last_worked')
|
||||
|
||||
def update_progress(self, module_number: str, module_name: str) -> None:
|
||||
"""Update user progress tracking."""
|
||||
progress = self.get_progress_data()
|
||||
|
||||
# Update completed modules
|
||||
if 'completed_modules' not in progress:
|
||||
progress['completed_modules'] = []
|
||||
|
||||
if module_number not in progress['completed_modules']:
|
||||
progress['completed_modules'].append(module_number)
|
||||
|
||||
progress['last_completed'] = module_number
|
||||
self.save_progress_data(progress)
|
||||
|
||||
self.console.print(f"📈 Progress updated: {len(progress['completed_modules'])} modules completed")
|
||||
|
||||
def show_next_steps(self, completed_module: str) -> None:
|
||||
"""Show next steps after completing a module."""
|
||||
@@ -287,88 +407,86 @@ class ModuleWorkflowCommand(BaseCommand):
|
||||
|
||||
def show_status(self) -> int:
|
||||
"""Show module completion status."""
|
||||
progress_file = self.config.project_root / "progress.json"
|
||||
module_mapping = self.get_module_mapping()
|
||||
progress = self.get_progress_data()
|
||||
|
||||
try:
|
||||
progress = {}
|
||||
if progress_file.exists():
|
||||
import json
|
||||
with open(progress_file, 'r') as f:
|
||||
progress = json.load(f)
|
||||
|
||||
completed = progress.get('completed_modules', [])
|
||||
|
||||
self.console.print(Panel(
|
||||
"📊 Module Completion Status",
|
||||
title="Your Progress",
|
||||
border_style="bright_blue"
|
||||
))
|
||||
|
||||
for num, name in module_mapping.items():
|
||||
status = "✅" if num in completed else "⏳"
|
||||
self.console.print(f" {status} Module {num}: {name}")
|
||||
|
||||
self.console.print(f"\n📈 Progress: {len(completed)}/{len(module_mapping)} modules completed")
|
||||
|
||||
if completed:
|
||||
last = progress.get('last_completed', completed[-1])
|
||||
next_num = f"{int(last) + 1:02d}"
|
||||
if next_num in module_mapping:
|
||||
self.console.print(f"💡 Next: [bold cyan]tito module {next_num}[/bold cyan]")
|
||||
started = progress.get('started_modules', [])
|
||||
completed = progress.get('completed_modules', [])
|
||||
last_worked = progress.get('last_worked')
|
||||
|
||||
self.console.print(Panel(
|
||||
"📊 Module Status & Progress",
|
||||
title="Your Learning Journey",
|
||||
border_style="bright_blue"
|
||||
))
|
||||
|
||||
for num, name in module_mapping.items():
|
||||
if num in completed:
|
||||
status = "✅"
|
||||
state = "completed"
|
||||
elif num in started:
|
||||
status = "🚀" if num == last_worked else "💻"
|
||||
state = "in progress" if num == last_worked else "started"
|
||||
else:
|
||||
self.console.print("💡 Start with: [bold cyan]tito module 01[/bold cyan]")
|
||||
status = "⏳"
|
||||
state = "not started"
|
||||
|
||||
return 0
|
||||
|
||||
except Exception as e:
|
||||
self.console.print(f"[red]Error reading progress: {e}[/red]")
|
||||
return 1
|
||||
marker = " ← current" if num == last_worked else ""
|
||||
self.console.print(f" {status} Module {num}: {name} ({state}){marker}")
|
||||
|
||||
# Summary
|
||||
self.console.print(f"\n📈 Progress: {len(completed)}/{len(module_mapping)} completed, {len(started)} started")
|
||||
|
||||
# Next steps
|
||||
if last_worked:
|
||||
if last_worked not in completed:
|
||||
self.console.print(f"💡 Continue: [bold cyan]tito module resume {last_worked}[/bold cyan]")
|
||||
self.console.print(f"💡 Or complete: [bold cyan]tito module complete {last_worked}[/bold cyan]")
|
||||
else:
|
||||
next_num = f"{int(last_worked) + 1:02d}"
|
||||
if next_num in module_mapping:
|
||||
self.console.print(f"💡 Next: [bold cyan]tito module start {next_num}[/bold cyan]")
|
||||
else:
|
||||
self.console.print("💡 Start with: [bold cyan]tito module start 01[/bold cyan]")
|
||||
|
||||
return 0
|
||||
|
||||
def run(self, args: Namespace) -> int:
|
||||
"""Execute the module workflow command."""
|
||||
# Handle subcommands
|
||||
if hasattr(args, 'module_command') and args.module_command:
|
||||
if args.module_command == 'complete':
|
||||
if args.module_command == 'start':
|
||||
return self.start_module(args.module_number)
|
||||
elif args.module_command == 'resume':
|
||||
return self.resume_module(getattr(args, 'module_number', None))
|
||||
elif args.module_command == 'complete':
|
||||
return self.complete_module(
|
||||
args.module_number,
|
||||
getattr(args, 'module_number', None),
|
||||
getattr(args, 'skip_tests', False),
|
||||
getattr(args, 'skip_export', False)
|
||||
)
|
||||
elif args.module_command == 'status':
|
||||
return self.show_status()
|
||||
elif args.module_command == 'test':
|
||||
module_mapping = self.get_module_mapping()
|
||||
normalized = self.normalize_module_number(args.module_number)
|
||||
if normalized in module_mapping:
|
||||
return self.run_module_tests(module_mapping[normalized])
|
||||
else:
|
||||
self.console.print(f"[red]❌ Module {normalized} not found[/red]")
|
||||
return 1
|
||||
elif args.module_command == 'export':
|
||||
module_mapping = self.get_module_mapping()
|
||||
normalized = self.normalize_module_number(args.module_number)
|
||||
if normalized in module_mapping:
|
||||
return self.export_module(module_mapping[normalized])
|
||||
else:
|
||||
self.console.print(f"[red]❌ Module {normalized} not found[/red]")
|
||||
return 1
|
||||
|
||||
# Show help if no valid command
|
||||
self.console.print(Panel(
|
||||
"[bold cyan]Module Workflow Commands[/bold cyan]\n\n"
|
||||
"[bold]Main Workflow:[/bold]\n"
|
||||
" [bold green]tito module 01[/bold green] - Open Module 01 in Jupyter Lab\n"
|
||||
"[bold cyan]Module Lifecycle Commands[/bold cyan]\n\n"
|
||||
"[bold]Core Workflow:[/bold]\n"
|
||||
" [bold green]tito module start 01[/bold green] - Start working on Module 01 (first time)\n"
|
||||
" [bold green]tito module resume 01[/bold green] - Resume working on Module 01 (continue)\n"
|
||||
" [bold green]tito module complete 01[/bold green] - Complete Module 01 (test + export)\n\n"
|
||||
"[bold]Status & Management:[/bold]\n"
|
||||
" [bold]tito module status[/bold] - Show completion progress\n"
|
||||
" [bold]tito module test 01[/bold] - Run tests for Module 01\n"
|
||||
" [bold]tito module export 01[/bold] - Export Module 01 to package\n\n"
|
||||
"[bold]Natural Workflow:[/bold]\n"
|
||||
" 1. [dim]tito module 01[/dim] → Open and work in Jupyter\n"
|
||||
" 2. [dim]Save your work[/dim] → Ctrl+S in Jupyter\n"
|
||||
"[bold]Smart Defaults:[/bold]\n"
|
||||
" [bold]tito module resume[/bold] - Resume last worked module\n"
|
||||
" [bold]tito module complete[/bold] - Complete current module\n"
|
||||
" [bold]tito module status[/bold] - Show progress with states\n\n"
|
||||
"[bold]Natural Learning Flow:[/bold]\n"
|
||||
" 1. [dim]tito module start 01[/dim] → Begin tensors (first time)\n"
|
||||
" 2. [dim]Work in Jupyter, save[/dim] → Ctrl+S to save progress\n"
|
||||
" 3. [dim]tito module complete 01[/dim] → Test, export, track progress\n"
|
||||
" 4. [dim]tito module 02[/dim] → Continue to next module",
|
||||
" 4. [dim]tito module start 02[/dim] → Begin activations\n"
|
||||
" 5. [dim]tito module resume 02[/dim] → Continue activations later\n\n"
|
||||
"[bold]Module States:[/bold]\n"
|
||||
" ⏳ Not started 🚀 In progress ✅ Completed",
|
||||
title="Module Development Workflow",
|
||||
border_style="bright_cyan"
|
||||
))
|
||||
|
||||
17
tito/main.py
17
tito/main.py
@@ -110,12 +110,12 @@ Convenience Commands:
|
||||
|
||||
Examples:
|
||||
tito setup First-time environment setup
|
||||
tito module 01 Open Module 01 in Jupyter Lab
|
||||
tito module start 01 Start Module 01 (tensors)
|
||||
tito module complete 01 Complete Module 01 (test + export)
|
||||
tito module resume 02 Resume working on Module 02
|
||||
tito module status View your learning progress
|
||||
tito system info Show system information
|
||||
tito checkpoint timeline Visual progress timeline
|
||||
tito leaderboard register Join the inclusive community
|
||||
tito book build Build the Jupyter Book locally
|
||||
"""
|
||||
)
|
||||
@@ -175,15 +175,6 @@ Examples:
|
||||
def run(self, args: Optional[List[str]] = None) -> int:
|
||||
"""Run the CLI application."""
|
||||
try:
|
||||
# Handle special case: tito module 01, tito module 02, etc.
|
||||
if args and len(args) >= 2 and args[0] == 'module':
|
||||
second_arg = args[1]
|
||||
if second_arg.isdigit() or (len(second_arg) == 2 and second_arg.isdigit()):
|
||||
# This is a direct module number, handle it specially
|
||||
from .commands.module_workflow import ModuleWorkflowCommand
|
||||
module_cmd = ModuleWorkflowCommand(self.config)
|
||||
return module_cmd.open_module(second_arg)
|
||||
|
||||
parser = self.create_parser()
|
||||
parsed_args = parser.parse_args(args)
|
||||
|
||||
@@ -235,9 +226,9 @@ Examples:
|
||||
" [bold green]logo[/bold green] - Learn about TinyTorch philosophy\n"
|
||||
"[bold]Quick Start:[/bold]\n"
|
||||
" [dim]tito setup[/dim] - First-time environment setup\n"
|
||||
" [dim]tito module 01[/dim] - Start building tensors in Jupyter Lab\n"
|
||||
" [dim]tito module start 01[/dim] - Start building tensors (first time)\n"
|
||||
" [dim]tito module complete 01[/dim] - Complete Module 01 (test + export)\n"
|
||||
" [dim]tito module 02[/dim] - Continue to activation functions\n"
|
||||
" [dim]tito module start 02[/dim] - Begin activation functions\n"
|
||||
" [dim]tito module status[/dim] - View your progress\n"
|
||||
"[bold]Get Help:[/bold]\n"
|
||||
" [dim]tito system[/dim] - Show system subcommands\n"
|
||||
|
||||
Reference in New Issue
Block a user