#!/usr/bin/env python3 """Final validation script for Senior Architecture. Validates: 1. All new modules import successfully 2. SQLite database is accessible 3. Metadata store works without numpy 4. ArrangementRecorder state machine functions 5. LiveBridge connects to Ableton 6. Integration coordinator initializes 7. End-to-end workflow executes """ import sys import os import json import traceback import argparse import tempfile import sqlite3 from datetime import datetime from pathlib import Path class ValidationRunner: """Runs all validations and generates report.""" def __init__(self, verbose=False): self.verbose = verbose self.results = [] self.errors = [] self.warnings = [] self.fix_suggestions = {} def run_all(self, selective=None): """Execute all validation checks.""" all_checks = [ ("Module Imports", self._check_imports), ("SQLite Database", self._check_database), ("Metadata Store", self._check_metadata_store), ("Numpy Independence", self._check_numpy_independence), ("ArrangementRecorder", self._check_arrangement_recorder), ("LiveBridge", self._check_live_bridge), ("Integration", self._check_integration), ("Ableton Connection", self._check_ableton_connection), ] # Filter if selective checks requested if selective: all_checks = [ (name, func) for name, func in all_checks if name.lower() in selective ] if not all_checks: print(f"Error: No checks match {selective}") return False for name, check_func in all_checks: if self.verbose: print(f"\nRunning: {name}...") try: result = check_func() self.results.append({ "name": name, "status": "PASS" if result else "FAIL", "timestamp": datetime.now().isoformat() }) if not result: self.fix_suggestions[name] = self._generate_fix_suggestion(name, Exception("Check returned False")) except Exception as e: self.results.append({ "name": name, "status": "ERROR", "error": str(e), "traceback": traceback.format_exc() }) self.errors.append((name, e)) self.fix_suggestions[name] = self._generate_fix_suggestion(name, e) return self._generate_report() def _check_imports(self): """Check all new modules import successfully.""" imports = [ 'mcp_server.engines.metadata_store', 'mcp_server.engines.abstract_analyzer', 'mcp_server.engines.arrangement_recorder', 'mcp_server.engines.live_bridge', 'mcp_server.integration', ] for module in imports: try: __import__(module) if self.verbose: print(f" [OK] Imported {module}") except ImportError as e: if self.verbose: print(f" [FAIL] Failed to import {module}: {e}") raise ImportError(f"Failed to import {module}: {e}") return True def _check_database(self): """Check SQLite database is accessible.""" try: # Try to create in-memory database conn = sqlite3.connect(':memory:') conn.execute('SELECT 1') conn.close() if self.verbose: print(" [OK] SQLite in-memory database created") return True except Exception as e: if self.verbose: print(f" [FAIL] SQLite error: {e}") raise def _check_metadata_store(self): """Check metadata store works without numpy.""" from mcp_server.engines.metadata_store import SampleMetadataStore, SampleFeatures # Create temp database fd, path = tempfile.mkstemp(suffix='.db') try: store = SampleMetadataStore(path) store.init_database() if self.verbose: print(f" [OK] Database initialized at {path}") # Save sample features features = SampleFeatures( path="/test/sample.wav", bpm=95.0, key="Am", duration=2.0, rms=-12.0, spectral_centroid=1000.0, spectral_rolloff=5000.0, zero_crossing_rate=0.1, mfcc_1=0.1, mfcc_2=0.1, mfcc_3=0.1, mfcc_4=0.1, mfcc_5=0.1, mfcc_6=0.1, mfcc_7=0.1, mfcc_8=0.1, mfcc_9=0.1, mfcc_10=0.1, mfcc_11=0.1, mfcc_12=0.1, mfcc_13=0.1 ) store.save_sample_features("/test/sample.wav", features) if self.verbose: print(" [OK] Sample features saved") # Retrieve retrieved = store.get_sample_features("/test/sample.wav") assert retrieved is not None, "Retrieved features should not be None" assert retrieved.bpm == 95.0, f"BPM should be 95.0, got {retrieved.bpm}" if self.verbose: print(" [OK] Sample features retrieved correctly") return True finally: try: os.close(fd) os.unlink(path) except: pass def _check_numpy_independence(self): """Verify core functionality works without numpy.""" # Check if numpy is available try: import numpy numpy_available = True except ImportError: numpy_available = False if not numpy_available: if self.verbose: print(" [INFO] Numpy not available - skipping independence test") return True # Already independent by absence # Temporarily hide numpy import sys numpy_backup = sys.modules.pop('numpy', None) try: # Re-import metadata store (should work without numpy) if 'mcp_server.engines.metadata_store' in sys.modules: del sys.modules['mcp_server.engines.metadata_store'] from mcp_server.engines.metadata_store import SampleMetadataStore if self.verbose: print(" [OK] Metadata store imports without numpy") return True finally: # Restore numpy if numpy_backup: sys.modules['numpy'] = numpy_backup def _check_arrangement_recorder(self): """Check ArrangementRecorder state machine.""" from mcp_server.engines.arrangement_recorder import ( ArrangementRecorder, RecordingState, RecordingConfig ) # Create mock objects class MockSong: def __init__(self): self.tempo = 95.0 self.current_song_time = 0.0 self.arrangement_overdub = False self.is_playing = False class MockConn: pass recorder = ArrangementRecorder(MockSong(), MockConn()) # Check initial state assert recorder.get_state() == RecordingState.IDLE, \ f"Initial state should be IDLE, got {recorder.get_state()}" if self.verbose: print(" [OK] Initial state is IDLE") # Check config can be created config = RecordingConfig( duration_bars=4.0, tempo=95.0, start_bar=0.0, scene_index=0 ) assert config.duration_bars == 4.0, \ f"Duration bars mismatch: expected 4.0, got {config.duration_bars}" assert config.tempo == 95.0, \ f"Tempo mismatch: expected 95.0, got {config.tempo}" if self.verbose: print(" [OK] RecordingConfig created successfully") print(" [OK] State transitions available:") for state in RecordingState: print(f" - {state.name}") return True def _check_live_bridge(self): """Check LiveBridge initializes.""" from mcp_server.engines.live_bridge import AbletonLiveBridge class MockSong: pass class MockConn: pass bridge = AbletonLiveBridge(MockSong(), MockConn()) assert bridge is not None, "LiveBridge should initialize" if self.verbose: print(" [OK] LiveBridge initialized") print(" [OK] Available methods:") methods = [m for m in dir(bridge) if not m.startswith('_')] for method in methods[:5]: print(f" - {method}") if len(methods) > 5: print(f" ... and {len(methods) - 5} more") return True def _check_integration(self): """Check integration coordinator.""" from mcp_server.integration import SeniorArchitectureCoordinator class MockSong: pass class MockConn: pass coord = SeniorArchitectureCoordinator(MockSong(), MockConn()) assert coord is not None, "Coordinator should initialize" if self.verbose: print(" [OK] SeniorArchitectureCoordinator initialized") return True def _check_ableton_connection(self): """Check Ableton Live is accessible.""" # Try to ping Ableton via existing wrapper try: import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(2) result = s.connect_ex(('127.0.0.1', 9877)) s.close() if result == 0: if self.verbose: print(" [OK] Ableton Live TCP server responding on port 9877") return True else: if self.verbose: print(f" [WARN] Ableton Live not available on port 9877 (error code: {result})") print(" [WARN] This is OK - Ableton may not be running") self.warnings.append("Ableton not running - some checks skipped") return False # Not a failure, just not available except Exception as e: if self.verbose: print(f" [WARN] Connection check error: {e}") self.warnings.append(f"Connection check: {e}") return False def _generate_fix_suggestion(self, check_name, error): """Generate fix suggestion for a failed check.""" suggestions = { "Module Imports": """ Fix: Ensure all new modules exist in mcp_server/engines/: 1. metadata_store.py - SQLite-based sample metadata 2. abstract_analyzer.py - Hybrid feature extraction 3. arrangement_recorder.py - Recording state machine 4. live_bridge.py - Direct Ableton API execution 5. integration.py - Coordinator Run: python -m py_compile on each file to check for syntax errors. """, "SQLite Database": """ Fix: SQLite is part of Python standard library. If this fails: 1. Check Python installation: python --version 2. Verify sqlite3 module: python -c "import sqlite3; print(sqlite3.version)" 3. Reinstall Python if necessary """, "Metadata Store": """ Fix: If metadata store fails: 1. Check database schema in metadata_store.py 2. Verify SampleFeatures dataclass definition 3. Check for SQL syntax errors in init_database() 4. Ensure proper error handling in save/get methods """, "Numpy Independence": """ Fix: If numpy independence fails: 1. Ensure metadata_store.py has no 'import numpy' at top level 2. Move numpy imports inside functions that need them 3. Use type checking (TYPE_CHECKING) for numpy type hints 4. Provide fallback implementations for numpy operations """, "ArrangementRecorder": """ Fix: If ArrangementRecorder fails: 1. Check RecordingState enum definition 2. Verify RecordingConfig dataclass 3. Ensure proper mock objects for testing 4. Check state transition logic """, "LiveBridge": """ Fix: If LiveBridge fails: 1. Check Ableton Live is running with Remote Script loaded 2. Verify TCP connection on port 9877 3. Check Live API access in __init__.py 4. Verify song and connection objects are properly passed """, "Integration": """ Fix: If Integration coordinator fails: 1. Check all dependencies are imported correctly 2. Verify mode detection logic (numpy/librosa availability) 3. Check for circular imports 4. Ensure proper initialization of sub-components """, "Ableton Connection": """ Fix: If Ableton connection fails: 1. Start Ableton Live 12 Suite 2. Verify AbletonMCP_AI is selected in Preferences > MIDI > Control Surface 3. Check that __init__.py is in correct location 4. Verify port 9877 is not blocked by firewall 5. Check Ableton log for errors """, } return suggestions.get(check_name, f""" Fix: General troubleshooting for {check_name}: 1. Check error traceback above 2. Verify file exists and has no syntax errors 3. Check import paths are correct 4. Run with --verbose for more details 5. Check AGENTS.md for architecture details """) def _generate_report(self): """Generate validation report.""" total = len(self.results) passed = sum(1 for r in self.results if r['status'] == 'PASS') failed = sum(1 for r in self.results if r['status'] == 'FAIL') errors = sum(1 for r in self.results if r['status'] == 'ERROR') report = { "timestamp": datetime.now().isoformat(), "summary": { "total": total, "passed": passed, "failed": failed, "errors": errors, "success_rate": passed / total if total > 0 else 0 }, "results": self.results, "warnings": self.warnings, "errors": [{"check": name, "error": str(e)} for name, e in self.errors] } # Print to console print("\n" + "="*60) print("SENIOR ARCHITECTURE VALIDATION REPORT") print("="*60) print(f"Timestamp: {report['timestamp']}") print(f"Passed: {passed}/{total}") print(f"Failed: {failed}/{total}") print(f"Errors: {errors}/{total}") print(f"Success Rate: {report['summary']['success_rate']:.1%}") if self.warnings: print(f"\nWarnings: {len(self.warnings)}") for warning in self.warnings: print(f" [WARN] {warning}") print("-"*60) for result in self.results: status_icon = "[PASS]" if result['status'] == 'PASS' else "[FAIL]" if result['status'] == 'FAIL' else "[WARN]" print(f"{status_icon} {result['name']}: {result['status']}") if self.verbose and 'error' in result and result['error']: print(f" Error: {result['error'][:100]}...") print("="*60) # Print fix suggestions for failed checks if self.fix_suggestions: print("\n" + "="*60) print("FIX SUGGESTIONS") print("="*60) for check_name, suggestion in self.fix_suggestions.items(): print(f"\n{check_name}:") print(suggestion) print("="*60) # Save JSON report report_path = "senior_validation_report.json" with open(report_path, 'w') as f: json.dump(report, f, indent=2) print(f"\nFull report saved to: {report_path}") # Also save fix suggestions if self.fix_suggestions: fixes_path = "senior_validation_fixes.txt" with open(fixes_path, 'w') as f: f.write("SENIOR ARCHITECTURE - FIX SUGGESTIONS\n") f.write("="*60 + "\n\n") for check_name, suggestion in self.fix_suggestions.items(): f.write(f"{check_name}:\n") f.write(suggestion + "\n") print(f"Fix suggestions saved to: {fixes_path}") return report['summary']['success_rate'] >= 0.8 # 80% pass threshold def main(): """Main entry point.""" parser = argparse.ArgumentParser( description="Validate Senior Architecture implementation", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: python validate_senior.py # Run all checks python validate_senior.py -v # Run with verbose output python validate_senior.py --list # List available checks python validate_senior.py -c imports integration # Run specific checks """ ) parser.add_argument( '-v', '--verbose', action='store_true', help='Enable verbose output with detailed information' ) parser.add_argument( '-c', '--checks', nargs='+', metavar='CHECK', help='Run only specific checks (space-separated names)' ) parser.add_argument( '--list', action='store_true', help='List all available checks and exit' ) parser.add_argument( '--threshold', type=float, default=0.8, help='Success rate threshold (default: 0.8 = 80 percent)' ) args = parser.parse_args() if args.list: print("Available validation checks:") checks = [ "Module Imports - Check all new modules import successfully", "SQLite Database - Check SQLite database is accessible", "Metadata Store - Check metadata store works without numpy", "Numpy Independence - Verify core functionality works without numpy", "ArrangementRecorder - Check ArrangementRecorder state machine", "LiveBridge - Check LiveBridge initializes", "Integration - Check integration coordinator", "Ableton Connection - Check Ableton Live is accessible", ] for check in checks: print(f" - {check}") return 0 # Normalize check names selective = None if args.checks: selective = [name.lower().replace("_", " ") for name in args.checks] print(f"Running selective validation: {selective}") runner = ValidationRunner(verbose=args.verbose) success = runner.run_all(selective=selective) # Apply custom threshold if selective: total = len(runner.results) passed = sum(1 for r in runner.results if r['status'] == 'PASS') success_rate = passed / total if total > 0 else 0 success = success_rate >= args.threshold if success: print("\n[PASS] Senior Architecture validation PASSED") print(f" Success rate meets {args.threshold:.0%} threshold") return 0 else: print("\n[FAIL] Senior Architecture validation FAILED") print(f" Success rate below {args.threshold:.0%} threshold") print("\nTo fix issues:") print(" 1. Check senior_validation_fixes.txt for suggestions") print(" 2. Run with --verbose to see detailed errors") print(" 3. Review AGENTS.md for architecture details") return 1 if __name__ == '__main__': sys.exit(main())