Files
Vijay Janapa ReddiandClaude 8d3025afc5 Refactor CLI commands into hierarchical folder structure
Reorganize TinyTorch CLI from flat structure to hierarchical organization
with subfolders for complex commands with subcommands.

Changes:
- Create subfolders: module/, system/, package/
- Move module commands: module_workflow.py → module/workflow.py
- Move module_reset.py → module/reset.py
- Move system commands: system.py → system/system.py
- Move system subcommands: info.py, health.py, jupyter.py → system/
- Move package commands: package.py → package/package.py
- Move package helpers: reset.py, nbdev.py → package/
- Archive deprecated files: clean.py, help.py, notebooks.py, status.py
- Update all imports in moved files and main.py
- Add __init__.py exports for each subfolder
- Create comprehensive CLI test suite (52 tests)
  - test_cli_registry.py: Validate command registration
  - test_cli_execution.py: Smoke tests for all commands
  - test_cli_help_consistency.py: Help text validation
- Update tests to match new structure

Benefits:
- Clear ownership: Easy to see which helpers belong to which commands
- Better organization: Related files grouped together
- Scales cleanly: Adding subcommands is straightforward
- Zero user impact: All commands work exactly the same

All 52 tests passing 

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-28 23:42:03 +01:00

377 lines
14 KiB
Python

"""
Reset command for TinyTorch CLI: resets package and user data.
"""
import json
import shutil
from datetime import datetime
from argparse import ArgumentParser, Namespace
from pathlib import Path
from rich.panel import Panel
from rich.text import Text
from ..base import BaseCommand
class ResetCommand(BaseCommand):
@property
def name(self) -> str:
return "reset"
@property
def description(self) -> str:
return "Reset package files or user progress data"
def add_arguments(self, parser: ArgumentParser) -> None:
subparsers = parser.add_subparsers(
dest='reset_command',
help='Reset subcommands',
metavar='SUBCOMMAND'
)
# Package reset (original functionality)
package_parser = subparsers.add_parser(
'package',
help='Reset tinytorch package to clean state (remove exported files)'
)
package_parser.add_argument("--force", action="store_true", help="Skip confirmation prompt")
# All data reset
all_parser = subparsers.add_parser(
'all',
help='Reset all user progress (modules + milestones + config)'
)
all_parser.add_argument("--backup", action="store_true", help="Create backup before reset")
all_parser.add_argument("--force", action="store_true", help="Skip confirmation prompt")
# Progress reset
progress_parser = subparsers.add_parser(
'progress',
help='Reset module completion tracking only'
)
progress_parser.add_argument("--backup", action="store_true", help="Create backup before reset")
progress_parser.add_argument("--force", action="store_true", help="Skip confirmation prompt")
# Milestones reset
milestones_parser = subparsers.add_parser(
'milestones',
help='Reset milestone achievements only'
)
milestones_parser.add_argument("--backup", action="store_true", help="Create backup before reset")
milestones_parser.add_argument("--force", action="store_true", help="Skip confirmation prompt")
# Config reset
config_parser = subparsers.add_parser(
'config',
help='Reset configuration to defaults'
)
config_parser.add_argument("--force", action="store_true", help="Skip confirmation prompt")
def run(self, args: Namespace) -> int:
console = self.console
if not hasattr(args, 'reset_command') or not args.reset_command:
console.print(Panel(
"[bold cyan]Reset Commands[/bold cyan]\n\n"
"Available subcommands:\n"
" • [bold]package[/bold] - Reset tinytorch package (remove exported files)\n"
" • [bold]all[/bold] - Reset all user progress (modules + milestones + config)\n"
" • [bold]progress[/bold] - Reset module completion tracking only\n"
" • [bold]milestones[/bold] - Reset milestone achievements only\n"
" • [bold]config[/bold] - Reset configuration to defaults\n\n"
"[dim]Example: tito reset progress --backup[/dim]",
title="Reset Command Group",
border_style="bright_yellow"
))
return 0
# Execute the appropriate subcommand
if args.reset_command == 'package':
return self._reset_package(args)
elif args.reset_command == 'all':
return self._reset_all(args)
elif args.reset_command == 'progress':
return self._reset_progress(args)
elif args.reset_command == 'milestones':
return self._reset_milestones(args)
elif args.reset_command == 'config':
return self._reset_config(args)
else:
console.print(Panel(
f"[red]Unknown reset subcommand: {args.reset_command}[/red]",
title="Error",
border_style="red"
))
return 1
def _reset_package(self, args: Namespace) -> int:
"""Reset tinytorch package (original functionality)."""
console = self.console
console.print(Panel("🔄 Resetting TinyTorch Package",
title="Package Reset", border_style="bright_yellow"))
tinytorch_path = Path("tinytorch")
if not tinytorch_path.exists():
console.print(Panel("[yellow]⚠️ TinyTorch package directory not found. Nothing to reset.[/yellow]",
title="Nothing to Reset", border_style="yellow"))
return 0
# Ask for confirmation unless --force is used
if not args.force:
console.print("\n[yellow]This will remove all exported Python files from the tinytorch package.[/yellow]")
console.print("[yellow]Notebooks in modules/ will be preserved.[/yellow]\n")
try:
response = input("Are you sure you want to reset? (y/N): ").strip().lower()
if response not in ['y', 'yes']:
console.print(Panel("[cyan]Reset cancelled.[/cyan]",
title="Cancelled", border_style="cyan"))
return 0
except KeyboardInterrupt:
console.print(Panel("[cyan]Reset cancelled.[/cyan]",
title="Cancelled", border_style="cyan"))
return 0
reset_text = Text()
reset_text.append("🗑️ Removing all exported files:\n", style="bold red")
# Simple approach: remove all .py files except __init__.py
files_removed = 0
for py_file in tinytorch_path.rglob("*.py"):
if py_file.name != "__init__.py":
try:
rel_path = py_file.relative_to(tinytorch_path)
reset_text.append(f" 🗑️ tinytorch/{rel_path}\n", style="red")
py_file.unlink()
files_removed += 1
except Exception as e:
reset_text.append(f" ❌ Failed to remove {py_file}: {e}\n", style="red")
# Remove __pycache__ directories
for pycache in tinytorch_path.rglob("__pycache__"):
if pycache.is_dir():
reset_text.append(f" 🗑️ {pycache}/\n", style="red")
shutil.rmtree(pycache)
# Remove .pytest_cache if it exists
pytest_cache = Path(".pytest_cache")
if pytest_cache.exists():
reset_text.append(f" 🗑️ .pytest_cache/\n", style="red")
shutil.rmtree(pytest_cache)
if files_removed > 0:
reset_text.append(f"\n✅ Reset complete! Removed {files_removed} generated files.\n", style="bold green")
reset_text.append("\n💡 Next steps:\n", style="bold yellow")
reset_text.append(" • Run: tito module complete 01 - Re-export modules\n", style="white")
console.print(Panel(reset_text, title="Reset Complete", border_style="green"))
else:
console.print(Panel("[yellow]No generated files found to remove.[/yellow]",
title="Nothing to Reset", border_style="yellow"))
return 0
def _create_backup(self) -> Path:
"""Create timestamped backup of .tito folder."""
console = self.console
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
backup_dir = Path(f".tito_backup_{timestamp}")
tito_dir = Path(".tito")
if tito_dir.exists():
shutil.copytree(tito_dir, backup_dir)
console.print(f"[green]✅ Backup created: {backup_dir}[/green]")
return backup_dir
def _reset_all(self, args: Namespace) -> int:
"""Reset all user progress data."""
console = self.console
# Ask for confirmation
if not args.force:
console.print("\n[bold red]⚠️ Warning: This will reset ALL progress[/bold red]\n")
console.print("[yellow]This will clear:[/yellow]")
console.print(" • Module completion tracking")
console.print(" • Milestone achievements")
console.print(" • Configuration settings\n")
console.print("[dim]Your code in modules/ will NOT be deleted.[/dim]\n")
try:
response = input("Continue? (y/N): ").strip().lower()
if response not in ['y', 'yes']:
console.print(Panel("[cyan]Reset cancelled.[/cyan]",
title="Cancelled", border_style="cyan"))
return 0
except KeyboardInterrupt:
console.print(Panel("\n[cyan]Reset cancelled.[/cyan]",
title="Cancelled", border_style="cyan"))
return 0
# Create backup if requested
if args.backup:
self._create_backup()
# Reset all data files
tito_dir = Path(".tito")
tito_dir.mkdir(parents=True, exist_ok=True)
# Reset progress.json
progress_file = tito_dir / "progress.json"
progress_file.write_text(json.dumps({
"version": "1.0",
"completed_modules": [],
"completion_dates": {}
}, indent=2))
# Reset milestones.json
milestones_file = tito_dir / "milestones.json"
milestones_file.write_text(json.dumps({
"version": "1.0",
"completed_milestones": [],
"completion_dates": {}
}, indent=2))
# Reset config.json
config_file = tito_dir / "config.json"
config_file.write_text(json.dumps({
"logo_theme": "standard"
}, indent=2))
console.print(Panel(
"[green]✅ All progress reset![/green]\n\n"
"You're ready to start fresh.\\n"
"Run: [cyan]tito module start 01[/cyan]",
title="🔄 Reset Complete",
border_style="green"
))
return 0
def _reset_progress(self, args: Namespace) -> int:
"""Reset module completion tracking only."""
console = self.console
# Ask for confirmation
if not args.force:
console.print("\n[bold yellow]⚠️ This will reset module completion tracking[/bold yellow]\n")
console.print("[dim]Milestone achievements will be preserved.[/dim]\n")
try:
response = input("Continue? (y/N): ").strip().lower()
if response not in ['y', 'yes']:
console.print(Panel("[cyan]Reset cancelled.[/cyan]",
title="Cancelled", border_style="cyan"))
return 0
except KeyboardInterrupt:
console.print(Panel("\n[cyan]Reset cancelled.[/cyan]",
title="Cancelled", border_style="cyan"))
return 0
# Create backup if requested
if args.backup:
self._create_backup()
# Reset progress.json
tito_dir = Path(".tito")
tito_dir.mkdir(parents=True, exist_ok=True)
progress_file = tito_dir / "progress.json"
progress_file.write_text(json.dumps({
"version": "1.0",
"completed_modules": [],
"completion_dates": {}
}, indent=2))
console.print(Panel(
"[green]✅ Module progress reset![/green]\n\n"
"You can re-complete modules with:\\n"
"[cyan]tito module complete XX[/cyan]",
title="🔄 Progress Reset",
border_style="green"
))
return 0
def _reset_milestones(self, args: Namespace) -> int:
"""Reset milestone achievements only."""
console = self.console
# Ask for confirmation
if not args.force:
console.print("\n[bold yellow]⚠️ This will reset milestone achievements[/bold yellow]\n")
console.print("[dim]Module completion will be preserved.[/dim]\n")
try:
response = input("Continue? (y/N): ").strip().lower()
if response not in ['y', 'yes']:
console.print(Panel("[cyan]Reset cancelled.[/cyan]",
title="Cancelled", border_style="cyan"))
return 0
except KeyboardInterrupt:
console.print(Panel("\n[cyan]Reset cancelled.[/cyan]",
title="Cancelled", border_style="cyan"))
return 0
# Create backup if requested
if args.backup:
self._create_backup()
# Reset milestones.json
tito_dir = Path(".tito")
tito_dir.mkdir(parents=True, exist_ok=True)
milestones_file = tito_dir / "milestones.json"
milestones_file.write_text(json.dumps({
"version": "1.0",
"completed_milestones": [],
"completion_dates": {}
}, indent=2))
console.print(Panel(
"[green]✅ Milestone achievements reset![/green]\n\n"
"You can re-run milestones with:\\n"
"[cyan]tito milestone run XX[/cyan]",
title="🔄 Milestones Reset",
border_style="green"
))
return 0
def _reset_config(self, args: Namespace) -> int:
"""Reset configuration to defaults."""
console = self.console
# Ask for confirmation
if not args.force:
console.print("\n[bold yellow]⚠️ This will reset configuration to defaults[/bold yellow]\n")
try:
response = input("Continue? (y/N): ").strip().lower()
if response not in ['y', 'yes']:
console.print(Panel("[cyan]Reset cancelled.[/cyan]",
title="Cancelled", border_style="cyan"))
return 0
except KeyboardInterrupt:
console.print(Panel("\n[cyan]Reset cancelled.[/cyan]",
title="Cancelled", border_style="cyan"))
return 0
# Reset config.json
tito_dir = Path(".tito")
tito_dir.mkdir(parents=True, exist_ok=True)
config_file = tito_dir / "config.json"
config_file.write_text(json.dumps({
"logo_theme": "standard"
}, indent=2))
console.print(Panel(
"[green]✅ Configuration reset to defaults![/green]",
title="🔄 Config Reset",
border_style="green"
))
return 0