CoolFace
Apppublic

OnyxMunk/AudioForge

sourceHugging Facemitupdated 8mo agoView on Hugging Face
0likes
launch_verification.py933 linesDownload Raw Back to scripts
1#!/usr/bin/env python3
2"""
3AudioForge Launch Verification Script
4Systematically verifies all items in LAUNCH_CHECKLIST.md
5
6Usage:
7    python scripts/launch_verification.py [--section SECTION] [--fix]
8
9Options:
10    --section SECTION    Run specific section only (backend, frontend, security, etc.)
11    --fix               Attempt to auto-fix issues where possible
12    --verbose           Show detailed output
13"""
14
15import argparse
16import asyncio
17import json
18import os
19import subprocess
20import sys
21import time
22from dataclasses import dataclass, field
23from enum import Enum
24from pathlib import Path
25from typing import Dict, List, Optional, Tuple
26from urllib.parse import urlparse
27
28try:
29    import httpx
30    import psutil
31    from rich.console import Console
32    from rich.progress import Progress, SpinnerColumn, TextColumn
33    from rich.table import Table
34    from rich.panel import Panel
35except ImportError:
36    print("Installing required packages...")
37    subprocess.run([sys.executable, "-m", "pip", "install", "httpx", "psutil", "rich"], check=True)
38    import httpx
39    import psutil
40    from rich.console import Console
41    from rich.progress import Progress, SpinnerColumn, TextColumn
42    from rich.table import Table
43    from rich.panel import Panel
44
45
46class CheckStatus(Enum):
47    """Status of a verification check."""
48    PASS = "✅"
49    FAIL = "❌"
50    WARN = "⚠️"
51    SKIP = "⏭️"
52    INFO = "ℹ️"
53
54
55@dataclass
56class CheckResult:
57    """Result of a single verification check."""
58    name: str
59    status: CheckStatus
60    message: str
61    details: Optional[str] = None
62    fix_available: bool = False
63    fix_command: Optional[str] = None
64
65
66@dataclass
67class SectionResult:
68    """Result of a verification section."""
69    name: str
70    checks: List[CheckResult] = field(default_factory=list)
71    
72    @property
73    def passed(self) -> int:
74        return sum(1 for c in self.checks if c.status == CheckStatus.PASS)
75    
76    @property
77    def failed(self) -> int:
78        return sum(1 for c in self.checks if c.status == CheckStatus.FAIL)
79    
80    @property
81    def warned(self) -> int:
82        return sum(1 for c in self.checks if c.status == CheckStatus.WARN)
83    
84    @property
85    def total(self) -> int:
86        return len(self.checks)
87    
88    @property
89    def success_rate(self) -> float:
90        if self.total == 0:
91            return 0.0
92        return (self.passed / self.total) * 100
93
94
95class LaunchVerifier:
96    """Main verification orchestrator."""
97    
98    def __init__(self, console: Console, fix_mode: bool = False, verbose: bool = False):
99        self.console = console
100        self.fix_mode = fix_mode
101        self.verbose = verbose
102        self.root_path = Path(__file__).parent.parent
103        self.results: Dict[str, SectionResult] = {}
104        
105    async def verify_all(self) -> Dict[str, SectionResult]:
106        """Run all verification checks."""
107        sections = [
108            ("Backend", self.verify_backend),
109            ("Frontend", self.verify_frontend),
110            ("UI/UX", self.verify_ui_ux),
111            ("Integration", self.verify_integration),
112            ("Performance", self.verify_performance),
113            ("Security", self.verify_security),
114            ("Documentation", self.verify_documentation),
115        ]
116        
117        for section_name, verify_func in sections:
118            self.console.print(f"\n[bold cyan]═══ {section_name} Verification ═══[/bold cyan]\n")
119            result = await verify_func()
120            self.results[section_name] = result
121            self._print_section_summary(result)
122        
123        return self.results
124    
125    async def verify_backend(self) -> SectionResult:
126        """Verify backend setup and health."""
127        result = SectionResult(name="Backend")
128        
129        # Check Python version
130        python_version = sys.version_info
131        if python_version >= (3, 11):
132            result.checks.append(CheckResult(
133                name="Python Version",
134                status=CheckStatus.PASS,
135                message=f"Python {python_version.major}.{python_version.minor}.{python_version.micro}"
136            ))
137        else:
138            result.checks.append(CheckResult(
139                name="Python Version",
140                status=CheckStatus.FAIL,
141                message=f"Python 3.11+ required, found {python_version.major}.{python_version.minor}",
142                fix_available=False
143            ))
144        
145        # Check .env file
146        env_path = self.root_path / "backend" / ".env"
147        env_example = self.root_path / "backend" / ".env.example"
148        
149        if env_path.exists():
150            result.checks.append(CheckResult(
151                name="Environment File",
152                status=CheckStatus.PASS,
153                message=".env file exists"
154            ))
155        elif env_example.exists() and self.fix_mode:
156            import shutil
157            shutil.copy(env_example, env_path)
158            result.checks.append(CheckResult(
159                name="Environment File",
160                status=CheckStatus.PASS,
161                message=".env created from .env.example"
162            ))
163        else:
164            result.checks.append(CheckResult(
165                name="Environment File",
166                status=CheckStatus.FAIL,
167                message=".env file missing",
168                fix_available=True,
169                fix_command="cp backend/.env.example backend/.env"
170            ))
171        
172        # Check backend dependencies
173        backend_path = self.root_path / "backend"
174        pyproject_path = backend_path / "pyproject.toml"
175        
176        if pyproject_path.exists():
177            result.checks.append(CheckResult(
178                name="Backend Package Config",
179                status=CheckStatus.PASS,
180                message="pyproject.toml found"
181            ))
182        else:
183            result.checks.append(CheckResult(
184                name="Backend Package Config",
185                status=CheckStatus.FAIL,
186                message="pyproject.toml missing"
187            ))
188        
189        # Check if backend is running
190        try:
191            async with httpx.AsyncClient(timeout=5.0) as client:
192                response = await client.get("http://localhost:8000/health")
193                if response.status_code == 200:
194                    result.checks.append(CheckResult(
195                        name="Backend Health Check",
196                        status=CheckStatus.PASS,
197                        message="Backend responding on port 8000",
198                        details=f"Response: {response.json()}"
199                    ))
200                else:
201                    result.checks.append(CheckResult(
202                        name="Backend Health Check",
203                        status=CheckStatus.WARN,
204                        message=f"Backend returned status {response.status_code}"
205                    ))
206        except Exception as e:
207            result.checks.append(CheckResult(
208                name="Backend Health Check",
209                status=CheckStatus.WARN,
210                message="Backend not running or not accessible",
211                details=str(e),
212                fix_command="cd backend && uvicorn app.main:app --reload"
213            ))
214        
215        # Check API documentation
216        try:
217            async with httpx.AsyncClient(timeout=5.0) as client:
218                response = await client.get("http://localhost:8000/docs")
219                if response.status_code == 200:
220                    result.checks.append(CheckResult(
221                        name="API Documentation",
222                        status=CheckStatus.PASS,
223                        message="API docs accessible at /docs"
224                    ))
225                else:
226                    result.checks.append(CheckResult(
227                        name="API Documentation",
228                        status=CheckStatus.WARN,
229                        message="API docs not accessible"
230                    ))
231        except Exception:
232            result.checks.append(CheckResult(
233                name="API Documentation",
234                status=CheckStatus.SKIP,
235                message="Backend not running"
236            ))
237        
238        # Check storage directories
239        storage_path = backend_path / "storage" / "audio"
240        required_dirs = ["music", "vocals", "mixed", "mastered"]
241        missing_dirs = [d for d in required_dirs if not (storage_path / d).exists()]
242        
243        if not missing_dirs:
244            result.checks.append(CheckResult(
245                name="Storage Directories",
246                status=CheckStatus.PASS,
247                message="All storage directories exist"
248            ))
249        elif self.fix_mode:
250            for dir_name in missing_dirs:
251                (storage_path / dir_name).mkdir(parents=True, exist_ok=True)
252            result.checks.append(CheckResult(
253                name="Storage Directories",
254                status=CheckStatus.PASS,
255                message=f"Created missing directories: {', '.join(missing_dirs)}"
256            ))
257        else:
258            result.checks.append(CheckResult(
259                name="Storage Directories",
260                status=CheckStatus.WARN,
261                message=f"Missing directories: {', '.join(missing_dirs)}",
262                fix_available=True
263            ))
264        
265        return result
266    
267    async def verify_frontend(self) -> SectionResult:
268        """Verify frontend setup and build."""
269        result = SectionResult(name="Frontend")
270        frontend_path = self.root_path / "frontend"
271        
272        # Check package.json
273        package_json = frontend_path / "package.json"
274        if package_json.exists():
275            result.checks.append(CheckResult(
276                name="Package Configuration",
277                status=CheckStatus.PASS,
278                message="package.json found"
279            ))
280            
281            # Check if node_modules exists
282            node_modules = frontend_path / "node_modules"
283            if node_modules.exists():
284                result.checks.append(CheckResult(
285                    name="Dependencies Installed",
286                    status=CheckStatus.PASS,
287                    message="node_modules directory exists"
288                ))
289            else:
290                result.checks.append(CheckResult(
291                    name="Dependencies Installed",
292                    status=CheckStatus.FAIL,
293                    message="node_modules missing",
294                    fix_available=True,
295                    fix_command="cd frontend && pnpm install"
296                ))
297        else:
298            result.checks.append(CheckResult(
299                name="Package Configuration",
300                status=CheckStatus.FAIL,
301                message="package.json missing"
302            ))
303        
304        # Check .env.local
305        env_local = frontend_path / ".env.local"
306        if env_local.exists():
307            result.checks.append(CheckResult(
308                name="Environment Configuration",
309                status=CheckStatus.PASS,
310                message=".env.local exists"
311            ))
312        else:
313            result.checks.append(CheckResult(
314                name="Environment Configuration",
315                status=CheckStatus.WARN,
316                message=".env.local missing",
317                fix_available=True,
318                fix_command='echo "NEXT_PUBLIC_API_URL=http://localhost:8000" > frontend/.env.local'
319            ))
320        
321        # Check if frontend is running
322        try:
323            async with httpx.AsyncClient(timeout=5.0) as client:
324                response = await client.get("http://localhost:3000")
325                if response.status_code == 200:
326                    result.checks.append(CheckResult(
327                        name="Frontend Server",
328                        status=CheckStatus.PASS,
329                        message="Frontend responding on port 3000"
330                    ))
331                else:
332                    result.checks.append(CheckResult(
333                        name="Frontend Server",
334                        status=CheckStatus.WARN,
335                        message=f"Frontend returned status {response.status_code}"
336                    ))
337        except Exception as e:
338            result.checks.append(CheckResult(
339                name="Frontend Server",
340                status=CheckStatus.WARN,
341                message="Frontend not running",
342                details=str(e),
343                fix_command="cd frontend && pnpm dev"
344            ))
345        
346        # Check TypeScript configuration
347        tsconfig = frontend_path / "tsconfig.json"
348        if tsconfig.exists():
349            result.checks.append(CheckResult(
350                name="TypeScript Configuration",
351                status=CheckStatus.PASS,
352                message="tsconfig.json found"
353            ))
354        else:
355            result.checks.append(CheckResult(
356                name="TypeScript Configuration",
357                status=CheckStatus.FAIL,
358                message="tsconfig.json missing"
359            ))
360        
361        # Check for TypeScript errors (if tsc is available)
362        try:
363            proc = subprocess.run(
364                ["pnpm", "tsc", "--noEmit"],
365                cwd=frontend_path,
366                capture_output=True,
367                text=True,
368                timeout=30
369            )
370            if proc.returncode == 0:
371                result.checks.append(CheckResult(
372                    name="TypeScript Compilation",
373                    status=CheckStatus.PASS,
374                    message="No TypeScript errors"
375                ))
376            else:
377                error_lines = proc.stdout.count('\n')
378                result.checks.append(CheckResult(
379                    name="TypeScript Compilation",
380                    status=CheckStatus.FAIL,
381                    message=f"TypeScript errors found ({error_lines} lines)",
382                    details=proc.stdout[:500]
383                ))
384        except FileNotFoundError:
385            result.checks.append(CheckResult(
386                name="TypeScript Compilation",
387                status=CheckStatus.SKIP,
388                message="pnpm not found"
389            ))
390        except Exception as e:
391            result.checks.append(CheckResult(
392                name="TypeScript Compilation",
393                status=CheckStatus.SKIP,
394                message=f"Could not run tsc: {str(e)}"
395            ))
396        
397        return result
398    
399    async def verify_ui_ux(self) -> SectionResult:
400        """Verify UI/UX enhancements are working."""
401        result = SectionResult(name="UI/UX")
402        frontend_path = self.root_path / "frontend" / "src"
403        
404        # Check for new components
405        components_to_check = [
406            "sound-wave-background.tsx",
407            "floating-notes.tsx",
408            "prompt-suggestions.tsx",
409            "mini-visualizer.tsx",
410            "footer-stats.tsx",
411            "keyboard-shortcuts.tsx",
412            "confetti-effect.tsx",
413        ]
414        
415        components_path = frontend_path / "components"
416        missing_components = []
417        
418        for component in components_to_check:
419            if not (components_path / component).exists():
420                missing_components.append(component)
421        
422        if not missing_components:
423            result.checks.append(CheckResult(
424                name="Creative Components",
425                status=CheckStatus.PASS,
426                message=f"All {len(components_to_check)} creative components present"
427            ))
428        else:
429            result.checks.append(CheckResult(
430                name="Creative Components",
431                status=CheckStatus.WARN,
432                message=f"Missing {len(missing_components)} components",
433                details=", ".join(missing_components)
434            ))
435        
436        # Check globals.css for animations
437        globals_css = frontend_path / "app" / "globals.css"
438        if globals_css.exists():
439            content = globals_css.read_text()
440            animations = [
441                "fade-in",
442                "slide-in-left",
443                "slide-in-right",
444                "gradient",
445                "pulse-glow",
446                "bounce-subtle",
447                "float-up",
448                "confetti-fall"
449            ]
450            missing_animations = [a for a in animations if f"@keyframes {a}" not in content]
451            
452            if not missing_animations:
453                result.checks.append(CheckResult(
454                    name="CSS Animations",
455                    status=CheckStatus.PASS,
456                    message=f"All {len(animations)} animations defined"
457                ))
458            else:
459                result.checks.append(CheckResult(
460                    name="CSS Animations",
461                    status=CheckStatus.WARN,
462                    message=f"Missing {len(missing_animations)} animations",
463                    details=", ".join(missing_animations)
464                ))
465        else:
466            result.checks.append(CheckResult(
467                name="CSS Animations",
468                status=CheckStatus.FAIL,
469                message="globals.css not found"
470            ))
471        
472        # Check tailwind config for font support
473        tailwind_config = self.root_path / "frontend" / "tailwind.config.ts"
474        if tailwind_config.exists():
475            content = tailwind_config.read_text()
476            if "fontFamily" in content and "display" in content:
477                result.checks.append(CheckResult(
478                    name="Typography Configuration",
479                    status=CheckStatus.PASS,
480                    message="Display font configured in Tailwind"
481                ))
482            else:
483                result.checks.append(CheckResult(
484                    name="Typography Configuration",
485                    status=CheckStatus.WARN,
486                    message="Display font may not be configured"
487                ))
488        
489        return result
490    
491    async def verify_integration(self) -> SectionResult:
492        """Verify integration between frontend and backend."""
493        result = SectionResult(name="Integration")
494        
495        # Check if both services are running
496        backend_running = False
497        frontend_running = False
498        
499        try:
500            async with httpx.AsyncClient(timeout=5.0) as client:
501                await client.get("http://localhost:8000/health")
502                backend_running = True
503        except Exception:
504            pass
505        
506        try:
507            async with httpx.AsyncClient(timeout=5.0) as client:
508                await client.get("http://localhost:3000")
509                frontend_running = True
510        except Exception:
511            pass
512        
513        if backend_running and frontend_running:
514            result.checks.append(CheckResult(
515                name="Services Running",
516                status=CheckStatus.PASS,
517                message="Both frontend and backend are running"
518            ))
519            
520            # Test API endpoint from frontend perspective
521            try:
522                async with httpx.AsyncClient(timeout=10.0) as client:
523                    # Test generations list endpoint
524                    response = await client.get("http://localhost:8000/api/v1/generations")
525                    if response.status_code in [200, 404]:  # 404 is ok if no generations yet
526                        result.checks.append(CheckResult(
527                            name="API Endpoints",
528                            status=CheckStatus.PASS,
529                            message="Generations API endpoint accessible"
530                        ))
531                    else:
532                        result.checks.append(CheckResult(
533                            name="API Endpoints",
534                            status=CheckStatus.WARN,
535                            message=f"Unexpected status code: {response.status_code}"
536                        ))
537            except Exception as e:
538                result.checks.append(CheckResult(
539                    name="API Endpoints",
540                    status=CheckStatus.FAIL,
541                    message="Could not access API endpoints",
542                    details=str(e)
543                ))
544        else:
545            services_status = []
546            if not backend_running:
547                services_status.append("backend")
548            if not frontend_running:
549                services_status.append("frontend")
550            
551            result.checks.append(CheckResult(
552                name="Services Running",
553                status=CheckStatus.FAIL,
554                message=f"Services not running: {', '.join(services_status)}",
555                fix_command="docker-compose up -d"
556            ))
557        
558        return result
559    
560    async def verify_performance(self) -> SectionResult:
561        """Verify performance metrics."""
562        result = SectionResult(name="Performance")
563        
564        # Check backend response time
565        try:
566            async with httpx.AsyncClient(timeout=5.0) as client:
567                start = time.time()
568                response = await client.get("http://localhost:8000/health")
569                duration = (time.time() - start) * 1000  # Convert to ms
570                
571                if response.status_code == 200 and duration < 200:
572                    result.checks.append(CheckResult(
573                        name="Backend Response Time",
574                        status=CheckStatus.PASS,
575                        message=f"Health check: {duration:.0f}ms (< 200ms target)"
576                    ))
577                elif duration < 500:
578                    result.checks.append(CheckResult(
579                        name="Backend Response Time",
580                        status=CheckStatus.WARN,
581                        message=f"Health check: {duration:.0f}ms (target: < 200ms)"
582                    ))
583                else:
584                    result.checks.append(CheckResult(
585                        name="Backend Response Time",
586                        status=CheckStatus.FAIL,
587                        message=f"Health check: {duration:.0f}ms (too slow)"
588                    ))
589        except Exception:
590            result.checks.append(CheckResult(
591                name="Backend Response Time",
592                status=CheckStatus.SKIP,
593                message="Backend not running"
594            ))
595        
596        # Check system resources
597        cpu_percent = psutil.cpu_percent(interval=1)
598        memory = psutil.virtual_memory()
599        
600        if cpu_percent < 80:
601            result.checks.append(CheckResult(
602                name="CPU Usage",
603                status=CheckStatus.PASS,
604                message=f"CPU: {cpu_percent:.1f}% (healthy)"
605            ))
606        else:
607            result.checks.append(CheckResult(
608                name="CPU Usage",
609                status=CheckStatus.WARN,
610                message=f"CPU: {cpu_percent:.1f}% (high)"
611            ))
612        
613        if memory.percent < 80:
614            result.checks.append(CheckResult(
615                name="Memory Usage",
616                status=CheckStatus.PASS,
617                message=f"Memory: {memory.percent:.1f}% (healthy)"
618            ))
619        else:
620            result.checks.append(CheckResult(
621                name="Memory Usage",
622                status=CheckStatus.WARN,
623                message=f"Memory: {memory.percent:.1f}% (high)"
624            ))
625        
626        return result
627    
628    async def verify_security(self) -> SectionResult:
629        """Verify security configurations."""
630        result = SectionResult(name="Security")
631        
632        # Check for .env in .gitignore
633        gitignore = self.root_path / ".gitignore"
634        if gitignore.exists():
635            content = gitignore.read_text()
636            if ".env" in content:
637                result.checks.append(CheckResult(
638                    name="Environment Files Protected",
639                    status=CheckStatus.PASS,
640                    message=".env files in .gitignore"
641                ))
642            else:
643                result.checks.append(CheckResult(
644                    name="Environment Files Protected",
645                    status=CheckStatus.FAIL,
646                    message=".env not in .gitignore",
647                    fix_available=True
648                ))
649        
650        # Check for exposed secrets in frontend
651        frontend_env = self.root_path / "frontend" / ".env.local"
652        if frontend_env.exists():
653            content = frontend_env.read_text()
654            dangerous_keys = ["SECRET", "PRIVATE", "KEY", "PASSWORD"]
655            exposed = [key for key in dangerous_keys if key in content.upper() and "NEXT_PUBLIC" not in content]
656            
657            if not exposed:
658                result.checks.append(CheckResult(
659                    name="Frontend Secrets",
660                    status=CheckStatus.PASS,
661                    message="No exposed secrets in .env.local"
662                ))
663            else:
664                result.checks.append(CheckResult(
665                    name="Frontend Secrets",
666                    status=CheckStatus.WARN,
667                    message=f"Potential secrets found: {', '.join(exposed)}"
668                ))
669        
670        # Check CORS configuration (if backend is running)
671        try:
672            async with httpx.AsyncClient(timeout=5.0) as client:
673                response = await client.options(
674                    "http://localhost:8000/api/v1/generations",
675                    headers={"Origin": "http://localhost:3000"}
676                )
677                if "access-control-allow-origin" in response.headers:
678                    result.checks.append(CheckResult(
679                        name="CORS Configuration",
680                        status=CheckStatus.PASS,
681                        message="CORS headers present"
682                    ))
683                else:
684                    result.checks.append(CheckResult(
685                        name="CORS Configuration",
686                        status=CheckStatus.WARN,
687                        message="CORS headers not found"
688                    ))
689        except Exception:
690            result.checks.append(CheckResult(
691                name="CORS Configuration",
692                status=CheckStatus.SKIP,
693                message="Backend not running"
694            ))
695        
696        return result
697    
698    async def verify_documentation(self) -> SectionResult:
699        """Verify documentation completeness."""
700        result = SectionResult(name="Documentation")
701        
702        required_docs = {
703            "README.md": "Main documentation",
704            "SETUP.md": "Setup instructions",
705            "ARCHITECTURE.md": "Architecture overview",
706            "CONTRIBUTING.md": "Contribution guidelines",
707            "LAUNCH_CHECKLIST.md": "Launch checklist",
708        }
709        
710        missing_docs = []
711        for doc_file, description in required_docs.items():
712            doc_path = self.root_path / doc_file
713            if doc_path.exists():
714                size = doc_path.stat().st_size
715                if size > 100:  # At least 100 bytes
716                    continue
717            missing_docs.append(f"{doc_file} ({description})")
718        
719        if not missing_docs:
720            result.checks.append(CheckResult(
721                name="Required Documentation",
722                status=CheckStatus.PASS,
723                message=f"All {len(required_docs)} documentation files present"
724            ))
725        else:
726            result.checks.append(CheckResult(
727                name="Required Documentation",
728                status=CheckStatus.WARN,
729                message=f"Missing or incomplete: {len(missing_docs)} files",
730                details="\n".join(missing_docs)
731            ))
732        
733        # Check for LICENSE
734        license_file = self.root_path / "LICENSE"
735        if license_file.exists():
736            result.checks.append(CheckResult(
737                name="License File",
738                status=CheckStatus.PASS,
739                message="LICENSE file present"
740            ))
741        else:
742            result.checks.append(CheckResult(
743                name="License File",
744                status=CheckStatus.WARN,
745                message="LICENSE file missing"
746            ))
747        
748        return result
749    
750    def _print_section_summary(self, result: SectionResult):
751        """Print summary for a section."""
752        table = Table(show_header=True, header_style="bold magenta")
753        table.add_column("Check", style="cyan", width=30)
754        table.add_column("Status", justify="center", width=8)
755        table.add_column("Message", width=50)
756        
757        for check in result.checks:
758            status_str = check.status.value
759            message = check.message
760            if check.details and self.verbose:
761                message += f"\n[dim]{check.details}[/dim]"
762            if check.fix_available and check.fix_command:
763                message += f"\n[yellow]Fix: {check.fix_command}[/yellow]"
764            
765            table.add_row(check.name, status_str, message)
766        
767        self.console.print(table)
768        
769        # Print summary
770        summary = f"[bold]Summary:[/bold] {result.passed}/{result.total} passed"
771        if result.failed > 0:
772            summary += f", {result.failed} failed"
773        if result.warned > 0:
774            summary += f", {result.warned} warnings"
775        
776        success_rate = result.success_rate
777        if success_rate == 100:
778            color = "green"
779        elif success_rate >= 80:
780            color = "yellow"
781        else:
782            color = "red"
783        
784        self.console.print(f"\n{summary} ([{color}]{success_rate:.1f}% success rate[/{color}])\n")
785    
786    def print_final_report(self):
787        """Print final verification report."""
788        self.console.print("\n[bold cyan]═══ FINAL VERIFICATION REPORT ═══[/bold cyan]\n")
789        
790        table = Table(show_header=True, header_style="bold magenta")
791        table.add_column("Section", style="cyan", width=20)
792        table.add_column("Passed", justify="center", width=10)
793        table.add_column("Failed", justify="center", width=10)
794        table.add_column("Warnings", justify="center", width=10)
795        table.add_column("Success Rate", justify="center", width=15)
796        
797        total_passed = 0
798        total_failed = 0
799        total_warned = 0
800        total_checks = 0
801        
802        for section_name, result in self.results.items():
803            total_passed += result.passed
804            total_failed += result.failed
805            total_warned += result.warned
806            total_checks += result.total
807            
808            success_rate = result.success_rate
809            if success_rate == 100:
810                rate_str = f"[green]{success_rate:.1f}%[/green]"
811            elif success_rate >= 80:
812                rate_str = f"[yellow]{success_rate:.1f}%[/yellow]"
813            else:
814                rate_str = f"[red]{success_rate:.1f}%[/red]"
815            
816            table.add_row(
817                section_name,
818                str(result.passed),
819                str(result.failed) if result.failed > 0 else "-",
820                str(result.warned) if result.warned > 0 else "-",
821                rate_str
822            )
823        
824        self.console.print(table)
825        
826        # Overall summary
827        overall_rate = (total_passed / total_checks * 100) if total_checks > 0 else 0
828        
829        if overall_rate == 100:
830            status_emoji = "🎉"
831            status_msg = "[bold green]READY TO LAUNCH![/bold green]"
832        elif overall_rate >= 90:
833            status_emoji = "✅"
834            status_msg = "[bold yellow]ALMOST READY - Minor issues to fix[/bold yellow]"
835        elif overall_rate >= 70:
836            status_emoji = "⚠️"
837            status_msg = "[bold yellow]NOT READY - Several issues to address[/bold yellow]"
838        else:
839            status_emoji = "❌"
840            status_msg = "[bold red]NOT READY - Critical issues found[/bold red]"
841        
842        panel = Panel(
843            f"{status_emoji} {status_msg}\n\n"
844            f"Total Checks: {total_checks}\n"
845            f"Passed: {total_passed}\n"
846            f"Failed: {total_failed}\n"
847            f"Warnings: {total_warned}\n"
848            f"Overall Success Rate: {overall_rate:.1f}%",
849            title="[bold]Launch Status[/bold]",
850            border_style="cyan"
851        )
852        
853        self.console.print("\n", panel, "\n")
854        
855        # Print actionable items
856        if total_failed > 0 or total_warned > 0:
857            self.console.print("[bold yellow]Action Items:[/bold yellow]\n")
858            for section_name, result in self.results.items():
859                fixable = [c for c in result.checks if c.fix_available and c.fix_command]
860                if fixable:
861                    self.console.print(f"[cyan]{section_name}:[/cyan]")
862                    for check in fixable:
863                        self.console.print(f"  • {check.name}: [yellow]{check.fix_command}[/yellow]")
864                    self.console.print()
865
866
867async def main():
868    """Main entry point."""
869    parser = argparse.ArgumentParser(description="AudioForge Launch Verification")
870    parser.add_argument("--section", help="Run specific section only")
871    parser.add_argument("--fix", action="store_true", help="Attempt to auto-fix issues")
872    parser.add_argument("--verbose", action="store_true", help="Show detailed output")
873    parser.add_argument("--json", help="Output results to JSON file")
874    
875    args = parser.parse_args()
876    
877    console = Console()
878    console.print(Panel.fit(
879        "[bold cyan]AudioForge Launch Verification[/bold cyan]\n"
880        "Systematically verifying all launch checklist items",
881        border_style="cyan"
882    ))
883    
884    verifier = LaunchVerifier(console, fix_mode=args.fix, verbose=args.verbose)
885    
886    try:
887        results = await verifier.verify_all()
888        verifier.print_final_report()
889        
890        # Export to JSON if requested
891        if args.json:
892            output = {
893                section: {
894                    "passed": result.passed,
895                    "failed": result.failed,
896                    "warned": result.warned,
897                    "total": result.total,
898                    "success_rate": result.success_rate,
899                    "checks": [
900                        {
901                            "name": c.name,
902                            "status": c.status.name,
903                            "message": c.message,
904                            "details": c.details,
905                        }
906                        for c in result.checks
907                    ]
908                }
909                for section, result in results.items()
910            }
911            
912            with open(args.json, 'w') as f:
913                json.dump(output, f, indent=2)
914            console.print(f"\n[green]Results exported to {args.json}[/green]")
915        
916        # Exit code based on failures
917        total_failed = sum(r.failed for r in results.values())
918        sys.exit(0 if total_failed == 0 else 1)
919        
920    except KeyboardInterrupt:
921        console.print("\n[yellow]Verification cancelled by user[/yellow]")
922        sys.exit(130)
923    except Exception as e:
924        console.print(f"\n[red]Error during verification: {e}[/red]")
925        if args.verbose:
926            import traceback
927            console.print(traceback.format_exc())
928        sys.exit(1)
929
930
931if __name__ == "__main__":
932    asyncio.run(main())
933