Files
ableton-mcp-ai/validate_senior.py
OpenCode Agent 5ce8187c65 feat: Implement senior audio injection with 5 fallback methods
- Add _cmd_create_arrangement_audio_pattern with 5-method fallback chain
- Method 1: track.insert_arrangement_clip() [Live 12+]
- Method 2: track.create_audio_clip() [Live 11+]
- Method 3: arrangement_clips.add_new_clip() [Live 12+]
- Method 4: Session->duplicate_clip_to_arrangement [Legacy]
- Method 5: Session->Recording [Universal]

- Add _cmd_duplicate_clip_to_arrangement for session-to-arrangement workflow
- Update skills documentation
- Verified: 3 clips created at positions [0, 4, 8] in Arrangement View

Closes: Audio injection in Arrangement View
2026-04-12 14:02:32 -03:00

549 lines
20 KiB
Python

#!/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())