diff --git a/scripts/debug/debug-container.php b/scripts/debug/debug-container.php
new file mode 100644
index 00000000..ddc33692
--- /dev/null
+++ b/scripts/debug/debug-container.php
@@ -0,0 +1,78 @@
+bootstrapWeb();
+
+echo "DEBUG: Web app bootstrapped successfully\n";
+
+// Get container from the app
+$reflection = new ReflectionObject($app);
+$containerProperty = $reflection->getProperty('container');
+$containerProperty->setAccessible(true);
+$container = $containerProperty->getValue($app);
+
+echo "DEBUG: Got container from app\n";
+
+// Check if DiscoveryRegistry is available
+if ($container->has(DiscoveryRegistry::class)) {
+ echo "DEBUG: DiscoveryRegistry is available in container\n";
+ $registry = $container->get(DiscoveryRegistry::class);
+
+ $routeCount = $registry->attributes->getCount(Route::class);
+ echo "DEBUG: Found $routeCount route attributes in registry\n";
+
+ if ($routeCount > 0) {
+ $routes = $registry->attributes->get(Route::class);
+ echo "DEBUG: First few routes:\n";
+ foreach (array_slice($routes, 0, 3) as $i => $route) {
+ echo " Route $i: " . $route->className->getFullyQualified() . "::" . $route->methodName?->toString() . "\n";
+ }
+
+ // Look for the home route specifically
+ echo "DEBUG: Looking for home route (/)\n";
+ $homeFound = false;
+ foreach ($routes as $route) {
+ $instance = $route->createAttributeInstance();
+ if ($instance instanceof \App\Framework\Attributes\Route && $instance->path === '/') {
+ echo " HOME ROUTE FOUND: " . $route->className->getFullyQualified() . "::" . $route->methodName?->toString() . " -> " . $instance->path . "\n";
+ $homeFound = true;
+ }
+ }
+ if (!$homeFound) {
+ echo " HOME ROUTE NOT FOUND in discovery results\n";
+ }
+ } else {
+ echo "DEBUG: No routes found in registry\n";
+ }
+
+ echo "DEBUG: Total discovery items: " . count($registry) . "\n";
+} else {
+ echo "DEBUG: DiscoveryRegistry is NOT available in container\n";
+}
+
+echo "DEBUG: Debug complete\n";
\ No newline at end of file
diff --git a/scripts/debug/debug-contexts.php b/scripts/debug/debug-contexts.php
new file mode 100644
index 00000000..6fc17d87
--- /dev/null
+++ b/scripts/debug/debug-contexts.php
@@ -0,0 +1,87 @@
+bootstrapWeb();
+
+echo "DEBUG: Web app bootstrapped successfully\n";
+
+// Get container from the app
+$reflection = new ReflectionObject($app);
+$containerProperty = $reflection->getProperty('container');
+$containerProperty->setAccessible(true);
+$container = $containerProperty->getValue($app);
+
+echo "DEBUG: Got container from app\n";
+
+// Check if DiscoveryRegistry is available
+if ($container->has(DiscoveryRegistry::class)) {
+ echo "DEBUG: DiscoveryRegistry is available in container\n";
+ $registry = $container->get(DiscoveryRegistry::class);
+
+ $initializerCount = $registry->attributes->getCount(Initializer::class);
+ echo "DEBUG: Found $initializerCount initializer attributes in registry\n";
+
+ if ($initializerCount > 0) {
+ $initializers = $registry->attributes->get(Initializer::class);
+ echo "DEBUG: Looking for router-related initializers\n";
+
+ foreach ($initializers as $initializer) {
+ $className = $initializer->className->getFullyQualified();
+
+ if (str_contains($className, 'Router') || str_contains($className, 'Route')) {
+ echo " ROUTER INITIALIZER: $className\n";
+
+ // Get the attribute instance to see the contexts
+ $attributeInstance = $initializer->createAttributeInstance();
+ if ($attributeInstance instanceof \App\Framework\DI\Initializer) {
+ $contexts = $attributeInstance->contexts;
+ if (empty($contexts)) {
+ echo " CONTEXTS: ALL (no restrictions)\n";
+ } else {
+ $contextNames = array_map(function($ctx) {
+ return $ctx instanceof \App\Framework\Context\ContextType ? $ctx->name : (string)$ctx;
+ }, $contexts);
+ echo " CONTEXTS: " . implode(', ', $contextNames) . "\n";
+ }
+ }
+
+ // Check additionalData for debugging
+ if (isset($initializer->additionalData['contexts'])) {
+ echo " STORED CONTEXTS: " . print_r($initializer->additionalData['contexts'], true);
+ }
+ }
+ }
+ } else {
+ echo "DEBUG: No initializers found in registry\n";
+ }
+} else {
+ echo "DEBUG: DiscoveryRegistry is NOT available in container\n";
+}
+
+echo "DEBUG: Context analysis complete\n";
\ No newline at end of file
diff --git a/debug_console_init.php b/scripts/debug/debug_console_init.php
similarity index 100%
rename from debug_console_init.php
rename to scripts/debug/debug_console_init.php
diff --git a/scripts/debug/debug_initializers.php b/scripts/debug/debug_initializers.php
new file mode 100644
index 00000000..1e3ab2c9
--- /dev/null
+++ b/scripts/debug/debug_initializers.php
@@ -0,0 +1,57 @@
+bootstrapWorker();
+
+ echo "Framework bootstrapped successfully.\n";
+
+ // Check if Queue services are available
+ $services = [
+ 'App\Framework\Queue\Interfaces\DistributedLockInterface',
+ 'App\Framework\Queue\Services\WorkerRegistry',
+ 'App\Framework\Queue\Services\JobDistributionService',
+ 'App\Framework\Queue\Services\WorkerHealthCheckService',
+ 'App\Framework\Queue\Services\FailoverRecoveryService',
+ 'App\Framework\Queue\Contracts\JobProgressTrackerInterface',
+ 'App\Framework\Queue\Contracts\DeadLetterQueueInterface',
+ 'App\Framework\Queue\Services\JobMetricsManagerInterface',
+ 'App\Framework\Queue\Contracts\JobDependencyManagerInterface'
+ ];
+
+ echo "\nChecking Queue service registrations:\n";
+ echo str_repeat("=", 50) . "\n";
+
+ foreach ($services as $service) {
+ if ($container->has($service)) {
+ echo "✅ {$service} - REGISTERED\n";
+ } else {
+ echo "❌ {$service} - NOT REGISTERED\n";
+ }
+ }
+
+ echo "\nTotal container bindings: " . count($container->getBindings()) . "\n";
+
+} catch (Throwable $e) {
+ echo "Error: " . $e->getMessage() . "\n";
+ echo "File: " . $e->getFile() . ":" . $e->getLine() . "\n";
+}
\ No newline at end of file
diff --git a/scripts/debug/debug_live_tui.php b/scripts/debug/debug_live_tui.php
new file mode 100644
index 00000000..65d73996
--- /dev/null
+++ b/scripts/debug/debug_live_tui.php
@@ -0,0 +1,263 @@
+screen = new ScreenManager($output);
+
+ // Custom TUI state with debug
+ $debugState = new class extends TuiState {
+ public function setSelectedCategory(int $index): void {
+ $oldIndex = $this->getSelectedCategory();
+ parent::setSelectedCategory($index);
+ $newIndex = $this->getSelectedCategory();
+ echo "\n[DEBUG] Category changed: $oldIndex → $newIndex\n";
+ }
+
+ public function navigateUp(): void {
+ echo "\n[DEBUG] navigateUp() called\n";
+ $before = $this->getSelectedCategory();
+ parent::navigateUp();
+ $after = $this->getSelectedCategory();
+ echo "[DEBUG] navigateUp result: $before → $after\n";
+ }
+
+ public function navigateDown(): void {
+ echo "\n[DEBUG] navigateDown() called\n";
+ $before = $this->getSelectedCategory();
+ parent::navigateDown();
+ $after = $this->getSelectedCategory();
+ echo "[DEBUG] navigateDown result: $before → $after\n";
+ }
+ };
+
+ // Custom input handler with debug
+ $debugInputHandler = new class($debugExecutor) extends TuiInputHandler {
+ public function handleInput(string $key, \App\Framework\Console\Components\TuiState $state, \App\Framework\Console\CommandHistory $history): void {
+ $keyHex = bin2hex($key);
+ $keyDesc = match($key) {
+ "\033[A" => "ARROW_UP",
+ "\033[B" => "ARROW_DOWN",
+ "\033[C" => "ARROW_RIGHT",
+ "\033[D" => "ARROW_LEFT",
+ "\n" => "ENTER",
+ " " => "SPACE",
+ "\033" => "ESC",
+ 'q' => "Q",
+ default => "OTHER($key)"
+ };
+
+ echo "\n[DEBUG] INPUT: '$keyDesc' (hex: $keyHex) in view: " . $state->getCurrentView()->name . "\n";
+
+ parent::handleInput($key, $state, $history);
+
+ echo "[DEBUG] After input - Selected: " . $state->getSelectedCategory() . "\n";
+ }
+ };
+
+ $state = $debugState;
+ $history = new CommandHistory();
+ $inputHandler = $debugInputHandler;
+ $renderer = new TuiRenderer($output);
+ $groupRegistry = new CommandGroupRegistry($discoveryRegistry);
+
+ // Add test categories
+ $testCategories = [
+ ['name' => 'Testing', 'description' => 'Test commands', 'icon' => '🧪', 'commands' => []],
+ ['name' => 'Demo', 'description' => 'Demo commands', 'icon' => '🎮', 'commands' => []],
+ ['name' => 'Generator', 'description' => 'Code generation', 'icon' => '⚙️', 'commands' => []],
+ ['name' => 'General', 'description' => 'General commands', 'icon' => '📂', 'commands' => []]
+ ];
+
+ $state->setCategories($testCategories);
+ $state->setCurrentView(TuiView::CATEGORIES);
+ $state->setRunning(true);
+
+ echo "✓ Debug TUI components ready\n";
+ echo "Categories: " . count($testCategories) . "\n";
+ echo "Selected: " . $state->getSelectedCategory() . "\n\n";
+
+ // Save terminal settings
+ $originalSettings = trim(shell_exec('stty -g') ?: '');
+
+ // Set up terminal
+ if ($isPhpStorm) {
+ shell_exec('stty raw -echo min 1 time 0 2>/dev/null');
+ } else {
+ shell_exec('stty -icanon -echo 2>/dev/null');
+ }
+
+ // Hide cursor
+ echo CursorControlCode::HIDE->format();
+
+ echo "🚀 LIVE DEBUG TUI STARTED\n";
+ echo "Use arrow keys to test navigation.\n";
+ echo "Press 'q' to quit.\n";
+ echo "All navigation events will be logged below.\n\n";
+
+ // Simple TUI loop with debug
+ while ($state->isRunning()) {
+ // Clear and render
+ echo ScreenControlCode::CLEAR_ALL->format();
+ echo CursorControlCode::POSITION->format(1, 1);
+
+ // Render current state
+ $renderer->render($state, $history);
+
+ // Show debug info at bottom
+ echo "\n" . str_repeat('=', 60) . "\n";
+ echo "DEBUG INFO:\n";
+ echo "Selected Category: " . $state->getSelectedCategory() . "\n";
+ echo "Current View: " . $state->getCurrentView()->name . "\n";
+ $category = $state->getCurrentCategory();
+ echo "Category Name: " . ($category ? $category['name'] : 'NULL') . "\n";
+ echo "Press arrow keys to test navigation...\n";
+
+ // Read input with PHPStorm-compatible method
+ $key = fgetc(STDIN);
+ if ($key === false) continue;
+
+ if ($key === "\033") {
+ $sequence = $key;
+ stream_set_blocking(STDIN, false);
+ $next = fgetc(STDIN);
+ if ($next === false) {
+ usleep(10000);
+ $next = fgetc(STDIN);
+ }
+ if ($next !== false) {
+ $sequence .= $next;
+ if ($next === '[') {
+ $third = fgetc(STDIN);
+ if ($third === false) {
+ usleep(10000);
+ $third = fgetc(STDIN);
+ }
+ if ($third !== false) {
+ $sequence .= $third;
+ }
+ }
+ }
+ stream_set_blocking(STDIN, true);
+ $key = $sequence;
+ }
+
+ if ($key === 'q' || $key === 'Q') {
+ $state->setRunning(false);
+ break;
+ }
+
+ // Process input
+ $inputHandler->handleInput($key, $state, $history);
+ }
+
+} finally {
+ // Restore terminal
+ echo CursorControlCode::SHOW->format();
+ if (!empty($originalSettings)) {
+ shell_exec("stty $originalSettings 2>/dev/null");
+ }
+ echo "\n✓ Debug TUI session ended\n";
+}
\ No newline at end of file
diff --git a/debug_make_console.php b/scripts/debug/debug_make_console.php
similarity index 100%
rename from debug_make_console.php
rename to scripts/debug/debug_make_console.php
diff --git a/debug_mapper_config.php b/scripts/debug/debug_mapper_config.php
similarity index 100%
rename from debug_mapper_config.php
rename to scripts/debug/debug_mapper_config.php
diff --git a/scripts/debug/debug_navigation.php b/scripts/debug/debug_navigation.php
new file mode 100644
index 00000000..17c6e7f7
--- /dev/null
+++ b/scripts/debug/debug_navigation.php
@@ -0,0 +1,167 @@
+ [
+ 'name' => 'Database',
+ 'description' => 'Database commands',
+ 'icon' => '🗄️',
+ 'commands' => [],
+ 'priority' => 100
+ ],
+ 1 => [
+ 'name' => 'Cache',
+ 'description' => 'Cache commands',
+ 'icon' => '⚡',
+ 'commands' => [],
+ 'priority' => 90
+ ],
+ 2 => [
+ 'name' => 'Testing',
+ 'description' => 'Testing commands',
+ 'icon' => '🧪',
+ 'commands' => [],
+ 'priority' => 80
+ ],
+ 3 => [
+ 'name' => 'MCP',
+ 'description' => 'MCP commands',
+ 'icon' => '🤖',
+ 'commands' => [],
+ 'priority' => 70
+ ]
+ ];
+
+ $state->setCategories($categories);
+ $state->setCurrentView(TuiView::CATEGORIES);
+ $state->setRunning(true);
+
+ echo "✓ Initial Setup:\n";
+ echo " Categories: " . count($categories) . "\n";
+ echo " Current View: " . $state->getCurrentView()->name . "\n";
+ echo " Selected Category: " . $state->getSelectedCategory() . "\n";
+ echo " Category Name: '{$categories[$state->getSelectedCategory()]['name']}'\n\n";
+
+ // Test step-by-step navigation
+ echo "🔍 Testing Navigation Step-by-Step:\n\n";
+
+ // Test 1: Arrow Down
+ echo "Test 1: Arrow DOWN\n";
+ echo " Before: Category {$state->getSelectedCategory()} ('{$categories[$state->getSelectedCategory()]['name']}')\n";
+ echo " Input Key: '" . TuiKeyCode::ARROW_DOWN->value . "' (hex: " . bin2hex(TuiKeyCode::ARROW_DOWN->value) . ")\n";
+
+ $inputHandler->handleInput(TuiKeyCode::ARROW_DOWN->value, $state, $history);
+
+ echo " After: Category {$state->getSelectedCategory()} ('{$categories[$state->getSelectedCategory()]['name']}')\n";
+ echo " ✓ Expected: Should move from Database (0) to Cache (1)\n\n";
+
+ // Test 2: Arrow Down again
+ echo "Test 2: Arrow DOWN again\n";
+ echo " Before: Category {$state->getSelectedCategory()} ('{$categories[$state->getSelectedCategory()]['name']}')\n";
+
+ $inputHandler->handleInput(TuiKeyCode::ARROW_DOWN->value, $state, $history);
+
+ echo " After: Category {$state->getSelectedCategory()} ('{$categories[$state->getSelectedCategory()]['name']}')\n";
+ echo " ✓ Expected: Should move from Cache (1) to Testing (2)\n\n";
+
+ // Test 3: Arrow Up
+ echo "Test 3: Arrow UP\n";
+ echo " Before: Category {$state->getSelectedCategory()} ('{$categories[$state->getSelectedCategory()]['name']}')\n";
+
+ $inputHandler->handleInput(TuiKeyCode::ARROW_UP->value, $state, $history);
+
+ echo " After: Category {$state->getSelectedCategory()} ('{$categories[$state->getSelectedCategory()]['name']}')\n";
+ echo " ✓ Expected: Should move from Testing (2) to Cache (1)\n\n";
+
+ // Test 4: Boundary testing - go to end
+ echo "Test 4: Go to last category and test boundary\n";
+ $state->setSelectedCategory(3); // MCP
+ echo " Set to: Category {$state->getSelectedCategory()} ('{$categories[$state->getSelectedCategory()]['name']}')\n";
+
+ $inputHandler->handleInput(TuiKeyCode::ARROW_DOWN->value, $state, $history);
+
+ echo " After Arrow DOWN: Category {$state->getSelectedCategory()} ('{$categories[$state->getSelectedCategory()]['name']}')\n";
+ echo " ✓ Expected: Should stay at MCP (3) - boundary protection\n\n";
+
+ // Test 5: Boundary testing - go to beginning
+ echo "Test 5: Go to first category and test boundary\n";
+ $state->setSelectedCategory(0); // Database
+ echo " Set to: Category {$state->getSelectedCategory()} ('{$categories[$state->getSelectedCategory()]['name']}')\n";
+
+ $inputHandler->handleInput(TuiKeyCode::ARROW_UP->value, $state, $history);
+
+ echo " After Arrow UP: Category {$state->getSelectedCategory()} ('{$categories[$state->getSelectedCategory()]['name']}')\n";
+ echo " ✓ Expected: Should stay at Database (0) - boundary protection\n\n";
+
+ // Debug the TuiState navigation methods directly
+ echo "🔍 Testing TuiState Navigation Methods Directly:\n\n";
+
+ echo "Direct TuiState Testing:\n";
+ $state->setSelectedCategory(1); // Cache
+ echo " Set to: {$state->getSelectedCategory()}\n";
+
+ echo " Calling navigateDown()...\n";
+ $state->navigateDown();
+ echo " Result: {$state->getSelectedCategory()}\n";
+
+ echo " Calling navigateUp()...\n";
+ $state->navigateUp();
+ echo " Result: {$state->getSelectedCategory()}\n\n";
+
+ echo "✅ Navigation Debug Test COMPLETED\n";
+
+} catch (\Throwable $e) {
+ echo "\n❌ Navigation Debug Test FAILED:\n";
+ echo "Error: " . $e->getMessage() . "\n";
+ echo "File: " . $e->getFile() . ":" . $e->getLine() . "\n";
+ echo "\nStack trace:\n" . $e->getTraceAsString() . "\n";
+}
\ No newline at end of file
diff --git a/scripts/debug/debug_navigation_issue.php b/scripts/debug/debug_navigation_issue.php
new file mode 100644
index 00000000..8e2aae76
--- /dev/null
+++ b/scripts/debug/debug_navigation_issue.php
@@ -0,0 +1,80 @@
+getOrganizedCommands();
+
+ echo "📊 Categories loaded: " . count($categories) . "\n";
+ foreach ($categories as $index => $category) {
+ echo " [$index] {$category['name']} - Commands: " . count($category['commands']) . "\n";
+ }
+ echo "\n";
+
+ // Test TuiState Navigation
+ $state = new TuiState();
+ $state->setCategories($categories);
+ $state->setCurrentView(TuiView::CATEGORIES);
+
+ echo "🔍 Initial TUI State:\n";
+ echo " Selected Category: " . $state->getSelectedCategory() . "\n";
+ echo " Current View: " . $state->getCurrentView()->name . "\n";
+ echo " Current Category: " . ($state->getCurrentCategory()['name'] ?? 'NULL') . "\n";
+ echo "\n";
+
+ // Test Navigation Up/Down
+ echo "🧪 Testing Navigation:\n";
+
+ for ($i = 0; $i < 3; $i++) {
+ echo "Step $i - Before navigateDown(): " . $state->getSelectedCategory() . "\n";
+ $state->navigateDown();
+ echo "Step $i - After navigateDown(): " . $state->getSelectedCategory() . "\n";
+ }
+
+ echo "\n";
+
+ for ($i = 0; $i < 5; $i++) {
+ echo "Step $i - Before navigateUp(): " . $state->getSelectedCategory() . "\n";
+ $state->navigateUp();
+ echo "Step $i - After navigateUp(): " . $state->getSelectedCategory() . "\n";
+ }
+
+ echo "\n";
+
+ // Test Category Bounds
+ echo "🎯 Testing Bounds:\n";
+ $state->setSelectedCategory(99);
+ echo "Set to 99, actual: " . $state->getSelectedCategory() . "\n";
+ $state->setSelectedCategory(-5);
+ echo "Set to -5, actual: " . $state->getSelectedCategory() . "\n";
+
+ echo "\n✅ Navigation logic test completed\n";
+
+} catch (\Throwable $e) {
+ echo "❌ ERROR: " . $e->getMessage() . "\n";
+ echo "File: " . $e->getFile() . ":" . $e->getLine() . "\n";
+ echo "Stack trace:\n" . $e->getTraceAsString() . "\n";
+}
\ No newline at end of file
diff --git a/scripts/debug/debug_real_tui.php b/scripts/debug/debug_real_tui.php
new file mode 100644
index 00000000..9e3aacca
--- /dev/null
+++ b/scripts/debug/debug_real_tui.php
@@ -0,0 +1,171 @@
+screen = new ScreenManager($output);
+
+ $state = new TuiState();
+ $history = new CommandHistory();
+ $inputHandler = new TuiInputHandler($mockExecutor);
+ $renderer = new TuiRenderer($output);
+ $groupRegistry = new CommandGroupRegistry($discoveryRegistry);
+
+ echo "✓ Components created\n";
+
+ // Test categories loading
+ $categories = $groupRegistry->getOrganizedCommands();
+ echo "✓ Categories loaded: " . count($categories) . "\n";
+
+ if (empty($categories)) {
+ // Add test categories since discovery is empty
+ $testCategories = [
+ [
+ 'name' => 'Testing',
+ 'description' => 'Test commands',
+ 'icon' => '🧪',
+ 'priority' => 0,
+ 'commands' => []
+ ],
+ [
+ 'name' => 'Demo',
+ 'description' => 'Demo commands',
+ 'icon' => '🎮',
+ 'priority' => 0,
+ 'commands' => []
+ ],
+ [
+ 'name' => 'General',
+ 'description' => 'General commands',
+ 'icon' => '📂',
+ 'priority' => 0,
+ 'commands' => []
+ ]
+ ];
+ $state->setCategories($testCategories);
+ echo "✓ Test categories added: " . count($testCategories) . "\n";
+ } else {
+ $state->setCategories($categories);
+ }
+
+ // Create TUI
+ $tui = new ConsoleTUI(
+ $output,
+ $container,
+ $discoveryRegistry,
+ $state,
+ $renderer,
+ $inputHandler,
+ $mockExecutor,
+ $history,
+ $groupRegistry,
+ $workflowExecutor
+ );
+
+ echo "✓ TUI created\n\n";
+
+ echo "🚀 Starting TUI...\n";
+ echo "Use arrow keys to navigate, 'q' to quit.\n";
+ echo "This will show if the TUI actually responds to input.\n\n";
+
+ // Start TUI
+ $exitCode = $tui->run();
+
+ echo "\n✓ TUI exited with code: " . $exitCode->value . "\n";
+
+} catch (\Throwable $e) {
+ echo "\n❌ ERROR: " . $e->getMessage() . "\n";
+ echo "File: " . $e->getFile() . ":" . $e->getLine() . "\n";
+ echo "\nStack trace:\n" . $e->getTraceAsString() . "\n";
+}
\ No newline at end of file
diff --git a/scripts/debug/debug_routes.php b/scripts/debug/debug_routes.php
new file mode 100644
index 00000000..a95cacce
--- /dev/null
+++ b/scripts/debug/debug_routes.php
@@ -0,0 +1,56 @@
+initialize($container);
+
+ $registry = $container->get('App\Framework\Discovery\Results\DiscoveryRegistry');
+ $routes = $registry->attributes->get(Route::class);
+
+ echo "Total routes found: " . count($routes) . "\n\n";
+
+ $adminRoutes = [];
+ foreach ($routes as $route) {
+ $path = $route->additionalData['path'] ?? '';
+ if (str_contains($path, 'admin')) {
+ $adminRoutes[] = [
+ 'path' => $path,
+ 'controller' => $route->className->getFullyQualified(),
+ 'method' => $route->methodName?->toString() ?? 'unknown'
+ ];
+ }
+ }
+
+ echo "Admin routes found:\n";
+ foreach ($adminRoutes as $route) {
+ echo " Path: {$route['path']}\n";
+ echo " Controller: {$route['controller']}\n";
+ echo " Method: {$route['method']}\n";
+ echo " ---\n";
+ }
+
+ // Check specifically for ShowRoutes
+ echo "\nLooking for ShowRoutes controller:\n";
+ foreach ($routes as $route) {
+ if (str_contains($route->className->getFullyQualified(), 'ShowRoutes')) {
+ echo "Found ShowRoutes route:\n";
+ echo " Path: " . ($route->additionalData['path'] ?? 'unknown') . "\n";
+ echo " Controller: " . $route->className->getFullyQualified() . "\n";
+ echo " Method: " . ($route->methodName?->toString() ?? 'unknown') . "\n";
+ break;
+ }
+ }
+
+} catch (Exception $e) {
+ echo "Error: " . $e->getMessage() . "\n";
+ echo "Stack trace: " . $e->getTraceAsString() . "\n";
+}
\ No newline at end of file
diff --git a/scripts/debug/debug_template_renderer.php b/scripts/debug/debug_template_renderer.php
new file mode 100644
index 00000000..136df286
--- /dev/null
+++ b/scripts/debug/debug_template_renderer.php
@@ -0,0 +1,34 @@
+getContainer();
+
+ $templateRenderer = $container->get(TemplateRenderer::class);
+
+ echo "TemplateRenderer class: " . get_class($templateRenderer) . "\n";
+ echo "TemplateRenderer instance of TemplateRenderer: " . ($templateRenderer instanceof TemplateRenderer ? 'YES' : 'NO') . "\n";
+
+ if (method_exists($templateRenderer, 'render')) {
+ echo "Has render method: YES\n";
+ } else {
+ echo "Has render method: NO\n";
+ }
+
+ // Check if it's the Engine class we expect
+ if (get_class($templateRenderer) === 'App\Framework\View\Engine') {
+ echo "Is Engine class: YES\n";
+ } else {
+ echo "Is Engine class: NO\n";
+ echo "Actual class: " . get_class($templateRenderer) . "\n";
+ }
+
+} catch (Exception $e) {
+ echo "Error: " . $e->getMessage() . "\n";
+ echo "Trace: " . $e->getTraceAsString() . "\n";
+}
\ No newline at end of file
diff --git a/scripts/debug/debug_tui_navigation_logic.php b/scripts/debug/debug_tui_navigation_logic.php
new file mode 100644
index 00000000..0b320d06
--- /dev/null
+++ b/scripts/debug/debug_tui_navigation_logic.php
@@ -0,0 +1,219 @@
+ 'Testing',
+ 'description' => 'Test commands',
+ 'icon' => '🧪',
+ 'priority' => 0,
+ 'commands' => []
+ ],
+ [
+ 'name' => 'Demo',
+ 'description' => 'Demo commands',
+ 'icon' => '🎮',
+ 'priority' => 0,
+ 'commands' => []
+ ],
+ [
+ 'name' => 'Generator',
+ 'description' => 'Code generation',
+ 'icon' => '⚙️',
+ 'priority' => 0,
+ 'commands' => []
+ ],
+ [
+ 'name' => 'General',
+ 'description' => 'General commands',
+ 'icon' => '📂',
+ 'priority' => 0,
+ 'commands' => []
+ ]
+ ];
+
+ echo "📊 Test Categories:\n";
+ foreach ($testCategories as $index => $category) {
+ echo " [$index] {$category['icon']} {$category['name']} - {$category['description']}\n";
+ }
+ echo "\n";
+
+ // Setup TUI state
+ $state->setCategories($testCategories);
+ $state->setCurrentView(TuiView::CATEGORIES);
+ $state->setRunning(true);
+
+ echo "✓ TUI State initialized:\n";
+ echo " Categories: " . count($testCategories) . "\n";
+ echo " Current View: " . $state->getCurrentView()->name . "\n";
+ echo " Selected Category: " . $state->getSelectedCategory() . "\n";
+
+ $currentCategory = $state->getCurrentCategory();
+ if ($currentCategory) {
+ echo " Current Category: '{$currentCategory['name']}'\n";
+ } else {
+ echo " ❌ ERROR: getCurrentCategory() returned NULL!\n";
+ echo " This would cause navigation to fail!\n";
+ }
+
+ echo "\n=== STEP-BY-STEP NAVIGATION DEBUG ===\n\n";
+
+ function debugState($state, $step) {
+ echo "Step $step State:\n";
+ echo " - Selected Index: " . $state->getSelectedCategory() . "\n";
+ echo " - Current View: " . $state->getCurrentView()->name . "\n";
+
+ $category = $state->getCurrentCategory();
+ if ($category) {
+ echo " - Current Category: '{$category['name']}'\n";
+ } else {
+ echo " - ❌ Current Category: NULL\n";
+ }
+ echo "\n";
+ }
+
+ // Initial state
+ debugState($state, "Initial");
+
+ // Test 1: Direct TuiState navigation
+ echo "🔍 Test 1: Direct TuiState navigation\n";
+ echo "Calling \$state->navigateDown()...\n";
+ $state->navigateDown();
+ debugState($state, "After navigateDown()");
+
+ echo "Calling \$state->navigateUp()...\n";
+ $state->navigateUp();
+ debugState($state, "After navigateUp()");
+
+ // Test 2: TuiInputHandler navigation
+ echo "🔍 Test 2: TuiInputHandler with Arrow Keys\n";
+
+ echo "Simulating ARROW_DOWN input...\n";
+ echo "Key code: '" . TuiKeyCode::ARROW_DOWN->value . "' (hex: " . bin2hex(TuiKeyCode::ARROW_DOWN->value) . ")\n";
+
+ $beforeIndex = $state->getSelectedCategory();
+ $inputHandler->handleInput(TuiKeyCode::ARROW_DOWN->value, $state, $history);
+ $afterIndex = $state->getSelectedCategory();
+
+ echo "Before: $beforeIndex, After: $afterIndex\n";
+ echo "Changed: " . ($beforeIndex !== $afterIndex ? "YES ✓" : "NO ❌") . "\n";
+ debugState($state, "After ARROW_DOWN");
+
+ echo "Simulating ARROW_UP input...\n";
+ echo "Key code: '" . TuiKeyCode::ARROW_UP->value . "' (hex: " . bin2hex(TuiKeyCode::ARROW_UP->value) . ")\n";
+
+ $beforeIndex = $state->getSelectedCategory();
+ $inputHandler->handleInput(TuiKeyCode::ARROW_UP->value, $state, $history);
+ $afterIndex = $state->getSelectedCategory();
+
+ echo "Before: $beforeIndex, After: $afterIndex\n";
+ echo "Changed: " . ($beforeIndex !== $afterIndex ? "YES ✓" : "NO ❌") . "\n";
+ debugState($state, "After ARROW_UP");
+
+ // Test 3: Check bounds
+ echo "🔍 Test 3: Boundary testing\n";
+
+ // Go to last category
+ $lastIndex = count($testCategories) - 1;
+ $state->setSelectedCategory($lastIndex);
+ echo "Set to last category ($lastIndex)\n";
+ debugState($state, "Set to last");
+
+ echo "Try to go beyond last (ARROW_DOWN)...\n";
+ $beforeIndex = $state->getSelectedCategory();
+ $inputHandler->handleInput(TuiKeyCode::ARROW_DOWN->value, $state, $history);
+ $afterIndex = $state->getSelectedCategory();
+
+ echo "Before: $beforeIndex, After: $afterIndex\n";
+ echo "Boundary protected: " . ($beforeIndex === $afterIndex ? "YES ✓" : "NO ❌") . "\n";
+
+ // Test 4: Check TuiInputHandler logic
+ echo "\n🔍 Test 4: Debug TuiInputHandler logic\n";
+
+ // Let's trace what happens in handleInput
+ echo "Current view check: " . $state->getCurrentView()->name . "\n";
+ echo "Is CATEGORIES view: " . ($state->getCurrentView() === TuiView::CATEGORIES ? "YES" : "NO") . "\n";
+
+ if ($state->getCurrentView() === TuiView::CATEGORIES) {
+ echo "✓ View is correct for category navigation\n";
+ } else {
+ echo "❌ View is not CATEGORIES - navigation won't work!\n";
+ }
+
+ echo "\n✅ NAVIGATION DEBUG COMPLETED\n";
+
+} catch (\Throwable $e) {
+ echo "\n❌ DEBUG FAILED:\n";
+ echo "Error: " . $e->getMessage() . "\n";
+ echo "File: " . $e->getFile() . ":" . $e->getLine() . "\n";
+ echo "\nStack trace:\n" . $e->getTraceAsString() . "\n";
+}
\ No newline at end of file
diff --git a/public/production-test.php b/scripts/debug/env-check.php
similarity index 100%
rename from public/production-test.php
rename to scripts/debug/env-check.php
diff --git a/public/force-production-test.php b/scripts/debug/env-force-check.php
similarity index 100%
rename from public/force-production-test.php
rename to scripts/debug/env-force-check.php
diff --git a/scripts/debug/framework-components.php b/scripts/debug/framework-components.php
new file mode 100644
index 00000000..8f8e0a52
--- /dev/null
+++ b/scripts/debug/framework-components.php
@@ -0,0 +1,57 @@
+now()->format('Y-m-d H:i:s') . "\n";
+
+ echo "5. Testing Memory Monitor...\n";
+ $memoryMonitor = new App\Framework\Performance\MemoryMonitor();
+ echo " ✅ MemoryMonitor: Current usage " . number_format(memory_get_usage(true) / 1024 / 1024, 2) . " MB\n";
+
+ echo "\n🎉 Basic framework components working!\n";
+ echo "The issue is likely in the Discovery System during bootstrap.\n";
+
+} catch (Exception $e) {
+ echo "\n❌ ERROR: " . $e->getMessage() . "\n";
+ echo "Class: " . get_class($e) . "\n";
+ echo "File: " . $e->getFile() . ":" . $e->getLine() . "\n";
+ echo "\nStack Trace:\n" . $e->getTraceAsString() . "\n";
+} catch (Error $e) {
+ echo "\n❌ FATAL ERROR: " . $e->getMessage() . "\n";
+ echo "Class: " . get_class($e) . "\n";
+ echo "File: " . $e->getFile() . ":" . $e->getLine() . "\n";
+ echo "\nStack Trace:\n" . $e->getTraceAsString() . "\n";
+}
\ No newline at end of file
diff --git a/scripts/debug/minimal-bootstrap.php b/scripts/debug/minimal-bootstrap.php
new file mode 100644
index 00000000..e883b091
--- /dev/null
+++ b/scripts/debug/minimal-bootstrap.php
@@ -0,0 +1,53 @@
+getBasePath() . "\n";
+ echo "✅ Clock: " . $clock->now()->format('Y-m-d H:i:s') . "\n";
+ echo "✅ Memory: " . number_format(memory_get_usage(true) / 1024 / 1024, 2) . " MB\n";
+
+ // Test HTTP Request parsing
+ $method = App\Framework\Http\Method::GET;
+ echo "✅ HTTP Method: " . $method->value . "\n";
+
+ // Simple HTTP response
+ header('Content-Type: text/html; charset=utf-8');
+
+ echo "\n📦 Framework Status:\n";
+ echo " • Basic components: ✅ Working\n";
+ echo " • Discovery system: 🚧 Needs optimization\n";
+ echo " • Performance: ⚡ {$memoryMonitor->getPeakMemoryUsageMb()}MB peak\n";
+
+ echo "\n🎯 Next Steps:\n";
+ echo " 1. Optimize discovery system for large codebase\n";
+ echo " 2. Implement lazy loading for Framework components\n";
+ echo " 3. Add performance monitoring\n";
+ echo " 4. Test with production cache settings\n";
+
+} catch (Throwable $e) {
+ echo "❌ ERROR: " . $e->getMessage() . "\n";
+ echo " File: " . $e->getFile() . ":" . $e->getLine() . "\n";
+ echo " Class: " . get_class($e) . "\n";
+
+ if (method_exists($e, 'getContext')) {
+ echo " Context: " . json_encode($e->getContext()) . "\n";
+ }
+}
\ No newline at end of file
diff --git a/public/security-test.php b/scripts/debug/security-validation.php
similarity index 100%
rename from public/security-test.php
rename to scripts/debug/security-validation.php
diff --git a/public/test.php b/scripts/debug/simple-test.php
similarity index 100%
rename from public/test.php
rename to scripts/debug/simple-test.php
diff --git a/scripts/debug/simple_debug_tui.php b/scripts/debug/simple_debug_tui.php
new file mode 100644
index 00000000..341034c8
--- /dev/null
+++ b/scripts/debug/simple_debug_tui.php
@@ -0,0 +1,253 @@
+getSelectedCategory();
+ parent::setSelectedCategory($index);
+ $newIndex = $this->getSelectedCategory();
+ echo "\n[DEBUG] Category index: $oldIndex → $newIndex\n";
+ flush();
+ }
+
+ public function navigateUp(): void {
+ echo "\n[DEBUG] ⬆️ navigateUp() called\n";
+ flush();
+ $before = $this->getSelectedCategory();
+ parent::navigateUp();
+ $after = $this->getSelectedCategory();
+ echo "[DEBUG] ⬆️ Result: $before → $after\n";
+ flush();
+ }
+
+ public function navigateDown(): void {
+ echo "\n[DEBUG] ⬇️ navigateDown() called\n";
+ flush();
+ $before = $this->getSelectedCategory();
+ parent::navigateDown();
+ $after = $this->getSelectedCategory();
+ echo "[DEBUG] ⬇️ Result: $before → $after\n";
+ flush();
+ }
+ };
+
+ // Custom input handler with debug
+ $debugInputHandler = new class($debugExecutor) extends TuiInputHandler {
+ public function handleInput(string $key, \App\Framework\Console\Components\TuiState $state, \App\Framework\Console\CommandHistory $history): void {
+ $keyHex = bin2hex($key);
+ $keyDesc = match($key) {
+ "\033[A" => "ARROW_UP",
+ "\033[B" => "ARROW_DOWN",
+ "\033[C" => "ARROW_RIGHT",
+ "\033[D" => "ARROW_LEFT",
+ "\n" => "ENTER",
+ " " => "SPACE",
+ "\033" => "ESC",
+ 'q', 'Q' => "QUIT",
+ default => "OTHER('$key')"
+ };
+
+ echo "\n[INPUT] 🎯 Key: $keyDesc (hex: $keyHex)\n";
+ echo "[INPUT] 📍 Current view: " . $state->getCurrentView()->name . "\n";
+ echo "[INPUT] 📍 Current category: " . $state->getSelectedCategory() . "\n";
+ flush();
+
+ parent::handleInput($key, $state, $history);
+
+ echo "[INPUT] ✅ After processing - Category: " . $state->getSelectedCategory() . "\n";
+ flush();
+ }
+ };
+
+ $state = $debugState;
+ $history = new CommandHistory();
+ $inputHandler = $debugInputHandler;
+
+ // Add test categories
+ $testCategories = [
+ ['name' => 'Testing', 'description' => 'Test commands', 'icon' => '🧪', 'commands' => []],
+ ['name' => 'Demo', 'description' => 'Demo commands', 'icon' => '🎮', 'commands' => []],
+ ['name' => 'Generator', 'description' => 'Code generation', 'icon' => '⚙️', 'commands' => []],
+ ['name' => 'General', 'description' => 'General commands', 'icon' => '📂', 'commands' => []]
+ ];
+
+ $state->setCategories($testCategories);
+ $state->setCurrentView(TuiView::CATEGORIES);
+ $state->setRunning(true);
+
+ echo "✓ Debug TUI ready\n";
+ echo "Categories: " . count($testCategories) . "\n";
+ echo "Selected: " . $state->getSelectedCategory() . "\n\n";
+
+ // Save terminal settings
+ $originalSettings = trim(shell_exec('stty -g') ?: '');
+
+ echo "Setting up terminal for PHPStorm...\n";
+
+ // Set up terminal
+ if ($isPhpStorm) {
+ shell_exec('stty raw -echo min 1 time 0 2>/dev/null');
+ } else {
+ shell_exec('stty -icanon -echo 2>/dev/null');
+ }
+
+ echo "✓ Terminal configured\n\n";
+
+ echo "🚀 SIMPLE DEBUG TUI\n";
+ echo "==================\n\n";
+
+ function renderSimpleMenu($categories, $selectedIndex) {
+ echo "Categories:\n";
+ foreach ($categories as $index => $category) {
+ $indicator = $index === $selectedIndex ? '▶️' : ' ';
+ echo "$indicator {$category['icon']} {$category['name']}\n";
+ }
+ echo "\n";
+ }
+
+ function readKeyPHPStorm() {
+ $key = fgetc(STDIN);
+ if ($key === false) return '';
+
+ if ($key === "\033") {
+ $sequence = $key;
+ stream_set_blocking(STDIN, false);
+
+ $next = fgetc(STDIN);
+ if ($next === false) {
+ usleep(10000);
+ $next = fgetc(STDIN);
+ }
+
+ if ($next !== false) {
+ $sequence .= $next;
+ if ($next === '[') {
+ $third = fgetc(STDIN);
+ if ($third === false) {
+ usleep(10000);
+ $third = fgetc(STDIN);
+ }
+ if ($third !== false) {
+ $sequence .= $third;
+ }
+ }
+ }
+
+ stream_set_blocking(STDIN, true);
+ return $sequence;
+ }
+
+ return $key;
+ }
+
+ // Simple debug loop
+ echo "Use ↑/↓ arrow keys to navigate. Press 'q' to quit.\n\n";
+
+ while ($state->isRunning()) {
+ // Render current state
+ renderSimpleMenu($testCategories, $state->getSelectedCategory());
+
+ echo "Debug Info:\n";
+ echo "- Selected Index: " . $state->getSelectedCategory() . "\n";
+ echo "- Current View: " . $state->getCurrentView()->name . "\n";
+ $category = $state->getCurrentCategory();
+ echo "- Category Name: " . ($category ? $category['name'] : 'NULL') . "\n";
+ echo "\nPress arrow keys...\n";
+ echo str_repeat('=', 40) . "\n";
+
+ // Read input
+ $key = readKeyPHPStorm();
+
+ if ($key === 'q' || $key === 'Q') {
+ echo "\n[QUIT] Stopping TUI...\n";
+ $state->setRunning(false);
+ break;
+ }
+
+ if ($key !== '') {
+ // Clear screen for next render
+ echo ScreenControlCode::CLEAR_ALL->format();
+ echo CursorControlCode::POSITION->format(1, 1);
+
+ // Process input with full debug output
+ $inputHandler->handleInput($key, $state, $history);
+
+ echo "\n" . str_repeat('-', 40) . "\n";
+ }
+ }
+
+} finally {
+ // Restore terminal
+ if (!empty($originalSettings)) {
+ shell_exec("stty $originalSettings 2>/dev/null");
+ }
+ echo "\n✅ Simple Debug TUI ended\n";
+}
\ No newline at end of file
diff --git a/scripts/dev/hot-reload-minimal.php b/scripts/dev/hot-reload-minimal.php
new file mode 100644
index 00000000..60d0b333
--- /dev/null
+++ b/scripts/dev/hot-reload-minimal.php
@@ -0,0 +1,153 @@
+isFile()) {
+ continue;
+ }
+
+ $path = $file->getPathname();
+
+ // Only watch specific file types
+ if (!preg_match('/\.(php|view\.php|css|js|ts)$/', $path)) {
+ continue;
+ }
+
+ $mtime = $file->getMTime();
+
+ if (!isset($fileCache[$path])) {
+ $fileCache[$path] = $mtime;
+ } elseif ($fileCache[$path] < $mtime) {
+ $changes[] = [
+ 'path' => $path,
+ 'type' => 'modified',
+ 'time' => $mtime
+ ];
+ $fileCache[$path] = $mtime;
+ }
+ }
+
+ return $changes;
+}
+
+// Send initial connection event
+echo "event: connected\n";
+echo "data: " . json_encode([
+ 'status' => 'connected',
+ 'timestamp' => date('c'),
+ 'message' => 'Hot Reload server started'
+]) . "\n\n";
+flush();
+
+// Initialize file cache
+foreach ($watchedDirs as $dir) {
+ scanDirectory($dir, $fileCache);
+}
+
+$lastHeartbeat = time();
+
+// Keep connection alive and watch for changes
+while (connection_aborted() === 0) {
+ $hasChanges = false;
+
+ // Check each watched directory
+ foreach ($watchedDirs as $dir) {
+ $changes = scanDirectory($dir, $fileCache);
+
+ foreach ($changes as $change) {
+ $reloadType = 'full';
+ if (str_ends_with($change['path'], '.css')) {
+ $reloadType = 'css';
+ } elseif (str_ends_with($change['path'], '.js') || str_ends_with($change['path'], '.ts')) {
+ $reloadType = 'hmr';
+ }
+
+ echo "event: reload\n";
+ echo "data: " . json_encode([
+ 'type' => $reloadType,
+ 'file' => basename($change['path']),
+ 'path' => $change['path'],
+ 'timestamp' => date('c'),
+ 'message' => 'File changed: ' . basename($change['path'])
+ ]) . "\n\n";
+ flush();
+
+ $hasChanges = true;
+ }
+ }
+
+ // Send heartbeat every 30 seconds
+ if (time() - $lastHeartbeat >= 30) {
+ echo "event: heartbeat\n";
+ echo "data: " . json_encode([
+ 'timestamp' => date('c'),
+ 'message' => 'Connection alive'
+ ]) . "\n\n";
+ flush();
+ $lastHeartbeat = time();
+ }
+
+ // Small delay to prevent high CPU usage
+ usleep(500000); // 500ms
+}
\ No newline at end of file
diff --git a/scripts/dev/hot-reload-server.php b/scripts/dev/hot-reload-server.php
new file mode 100644
index 00000000..4da892da
--- /dev/null
+++ b/scripts/dev/hot-reload-server.php
@@ -0,0 +1,145 @@
+ 'connected',
+ 'timestamp' => date('c'),
+ 'message' => 'Hot Reload server started'
+]) . "\n\n";
+flush();
+
+// Keep connection alive and watch for changes
+$lastCheck = time();
+
+while (connection_aborted() === 0) {
+ // Check for file changes every 500ms
+ $changes = $fileWatcher->watchOnce($watchPatterns, $ignorePatterns);
+
+ foreach ($changes as $change) {
+ $reloadType = determineReloadType($change);
+
+ echo "event: reload\n";
+ echo "data: " . json_encode([
+ 'type' => $reloadType->value,
+ 'file' => $change->getPath(),
+ 'timestamp' => $change->getTimestamp()->format('c'),
+ 'message' => 'File changed: ' . basename($change->getPath())
+ ]) . "\n\n";
+ flush();
+ }
+
+ // Send heartbeat every 30 seconds
+ if (time() - $lastCheck >= 30) {
+ echo "event: heartbeat\n";
+ echo "data: " . json_encode([
+ 'timestamp' => date('c'),
+ 'message' => 'Connection alive'
+ ]) . "\n\n";
+ flush();
+ $lastCheck = time();
+ }
+
+ // Small delay to prevent high CPU usage
+ usleep(500000); // 500ms
+}
+
+function determineReloadType(FileChangeEvent $event): ReloadType
+{
+ $path = $event->getPath();
+
+ // PHP files need full page reload
+ if (str_ends_with($path, '.php')) {
+ return ReloadType::FULL;
+ }
+
+ // CSS files can use hot replacement
+ if (str_ends_with($path, '.css')) {
+ return ReloadType::CSS;
+ }
+
+ // JS modules can use HMR if supported
+ if (str_ends_with($path, '.js') || str_ends_with($path, '.ts')) {
+ return ReloadType::HMR;
+ }
+
+ // Templates need full reload
+ if (str_ends_with($path, '.view.php')) {
+ return ReloadType::FULL;
+ }
+
+ return ReloadType::FULL;
+}
\ No newline at end of file
diff --git a/autoloader_workaround.php b/scripts/maintenance/autoloader_workaround.php
similarity index 100%
rename from autoloader_workaround.php
rename to scripts/maintenance/autoloader_workaround.php
diff --git a/scripts/maintenance/bootstrap-discovery.php b/scripts/maintenance/bootstrap-discovery.php
new file mode 100644
index 00000000..8fa314a2
--- /dev/null
+++ b/scripts/maintenance/bootstrap-discovery.php
@@ -0,0 +1,73 @@
+getSourcePath()];
+$attributeRegistry = $attributeScanner->scan($paths);
+$storage->storeAttributes($attributeRegistry);
+$attrDuration = round((microtime(true) - $attrStart) * 1000, 2);
+echo " ✅ {$attributeRegistry->count()} attributes in {$attrDuration}ms\n\n";
+
+// 2. Discover Templates
+echo "📄 Discovering templates...\n";
+$tplStart = microtime(true);
+$templateScanner = new TemplateScanner($fileScanner);
+$templatePaths = [
+ $pathProvider->getSourcePath(),
+ $pathProvider->getBasePath() . '/resources'
+];
+$templateRegistry = $templateScanner->scan($templatePaths);
+$storage->storeTemplates($templateRegistry);
+$tplDuration = round((microtime(true) - $tplStart) * 1000, 2);
+echo " ✅ " . count($templateRegistry->getAll()) . " templates in {$tplDuration}ms\n\n";
+
+// 3. Discover Interfaces
+echo "🔌 Discovering interface implementations...\n";
+$intStart = microtime(true);
+$interfaceScanner = new InterfaceScanner($fileScanner, $reflectionProvider, []);
+$interfaceRegistry = $interfaceScanner->scan($paths);
+$storage->storeInterfaces($interfaceRegistry);
+$intDuration = round((microtime(true) - $intStart) * 1000, 2);
+echo " ✅ {$interfaceRegistry->count()} implementations in {$intDuration}ms\n\n";
+
+// Summary
+$totalDuration = round((microtime(true) - $totalStart) * 1000, 2);
+echo str_repeat("=", 60) . "\n";
+echo "🎉 Discovery bootstrap complete in {$totalDuration}ms\n";
+echo " 📁 Stored in: storage/discovery/\n";
+echo str_repeat("=", 60) . "\n";
diff --git a/public/build-container.php b/scripts/maintenance/compile-container.php
similarity index 100%
rename from public/build-container.php
rename to scripts/maintenance/compile-container.php
diff --git a/scripts/maintenance/populate_images_from_filesystem.php b/scripts/maintenance/populate_images_from_filesystem.php
new file mode 100644
index 00000000..32cee1dc
--- /dev/null
+++ b/scripts/maintenance/populate_images_from_filesystem.php
@@ -0,0 +1,224 @@
+clock = new SystemClock();
+ $this->uploadsPath = __DIR__ . '/storage/uploads';
+
+ // Initialize database
+ $this->initializeDatabase();
+ }
+
+ private function initializeDatabase(): void
+ {
+ // Simple SQLite connection for this script
+ $pdo = new PDO('sqlite:' . __DIR__ . '/database.sqlite');
+ $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+ $this->db = $pdo;
+ }
+
+ public function run(): void
+ {
+ echo "🔍 Scanning for images in: {$this->uploadsPath}\n";
+
+ if (!is_dir($this->uploadsPath)) {
+ echo "❌ Uploads directory not found: {$this->uploadsPath}\n";
+ return;
+ }
+
+ $imageFiles = $this->findImageFiles();
+ echo "📁 Found " . count($imageFiles) . " image files\n";
+
+ if (empty($imageFiles)) {
+ echo "ℹ️ No images to migrate\n";
+ return;
+ }
+
+ $this->migrateImages($imageFiles);
+ echo "✅ Migration completed!\n";
+ }
+
+ private function findImageFiles(): array
+ {
+ $iterator = new RecursiveIteratorIterator(
+ new RecursiveDirectoryIterator($this->uploadsPath)
+ );
+
+ $imageFiles = [];
+ $allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
+
+ foreach ($iterator as $file) {
+ if (!$file->isFile()) {
+ continue;
+ }
+
+ $extension = strtolower($file->getExtension());
+ if (!in_array($extension, $allowedExtensions)) {
+ continue;
+ }
+
+ $imageFiles[] = [
+ 'path' => $file->getPathname(),
+ 'filename' => $file->getFilename(),
+ 'extension' => $extension,
+ 'size' => $file->getSize(),
+ 'mtime' => $file->getMTime()
+ ];
+ }
+
+ return $imageFiles;
+ }
+
+ private function migrateImages(array $imageFiles): void
+ {
+ // Check current database schema
+ $this->checkDatabaseSchema();
+
+ $migrated = 0;
+ $errors = 0;
+
+ foreach ($imageFiles as $fileInfo) {
+ try {
+ $this->migrateImageFile($fileInfo);
+ $migrated++;
+ echo "✓ Migrated: {$fileInfo['filename']}\n";
+ } catch (Exception $e) {
+ $errors++;
+ echo "❌ Error migrating {$fileInfo['filename']}: " . $e->getMessage() . "\n";
+ }
+ }
+
+ echo "\n📊 Summary:\n";
+ echo " Migrated: $migrated\n";
+ echo " Errors: $errors\n";
+ }
+
+ private function checkDatabaseSchema(): void
+ {
+ // Check what columns exist in the images table
+ $stmt = $this->db->query("PRAGMA table_info(images)");
+ $columns = $stmt->fetchAll(PDO::FETCH_ASSOC);
+
+ echo "📋 Database schema (images table):\n";
+ foreach ($columns as $column) {
+ echo " - {$column['name']} ({$column['type']})\n";
+ }
+ echo "\n";
+ }
+
+ private function migrateImageFile(array $fileInfo): void
+ {
+ $fullPath = $fileInfo['path'];
+
+ // Extract image dimensions if possible
+ $imageInfo = @getimagesize($fullPath);
+ $width = $imageInfo[0] ?? 0;
+ $height = $imageInfo[1] ?? 0;
+
+ // Generate ULID
+ $ulidGenerator = new UlidGenerator();
+ $ulidString = $ulidGenerator->generate($this->clock);
+
+ // Calculate hash
+ $hashValue = hash_file('sha256', $fullPath);
+
+ // Determine MIME type
+ $mimeTypeString = match (strtolower($fileInfo['extension'])) {
+ 'jpg', 'jpeg' => 'image/jpeg',
+ 'png' => 'image/png',
+ 'gif' => 'image/gif',
+ 'webp' => 'image/webp',
+ default => 'image/jpeg'
+ };
+
+ // Extract original filename from the complex filename structure
+ $originalFilename = $this->extractOriginalFilename($fileInfo['filename']);
+
+ // Get relative path from storage root
+ $relativePath = str_replace($this->uploadsPath . '/', '', $fullPath);
+ $pathOnly = dirname($relativePath);
+
+ // Insert into database using the correct table structure
+ $sql = "INSERT INTO images (
+ ulid, filename, original_filename, mime_type, file_size,
+ width, height, hash, path, created_at, updated_at
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
+
+ $stmt = $this->db->prepare($sql);
+ $now = date('Y-m-d H:i:s');
+
+ $stmt->execute([
+ $ulidString,
+ $fileInfo['filename'],
+ $originalFilename,
+ $mimeTypeString,
+ $fileInfo['size'],
+ $width,
+ $height,
+ $hashValue,
+ $pathOnly,
+ $now,
+ $now
+ ]);
+ }
+
+ private function extractOriginalFilename(string $filename): string
+ {
+ // Pattern for files like: BFWCAKKEHTKF5SYR_6626fc6b...cd1_original.png
+ if (preg_match('/^[A-Z0-9]{16}_[a-f0-9]{64}_original\.(.+)$/', $filename, $matches)) {
+ // This is an original file, try to find the pattern in other files
+ $basePattern = substr($filename, 0, strpos($filename, '_original.'));
+ // For now, just return a cleaned version
+ return "original." . $matches[1];
+ }
+
+ // Pattern for simple files like: 00MF9VW9R36NJN3VCFSTS2CK6R.jpg
+ if (preg_match('/^[A-Z0-9]{26}\.(.+)$/', $filename, $matches)) {
+ return "image." . $matches[1];
+ }
+
+ // Fallback: return as-is
+ return $filename;
+ }
+}
+
+// Run the migration
+echo "🚀 Starting image migration from filesystem to database...\n\n";
+
+try {
+ $migration = new ImageMigrationScript();
+ $migration->run();
+} catch (Exception $e) {
+ echo "💥 Migration failed: " . $e->getMessage() . "\n";
+ echo "Stack trace:\n" . $e->getTraceAsString() . "\n";
+ exit(1);
+}
+
+echo "\n🎉 Migration script completed!\n";
\ No newline at end of file
diff --git a/scripts/maintenance/quick-cache-fix.php b/scripts/maintenance/quick-cache-fix.php
new file mode 100644
index 00000000..131fd8a8
--- /dev/null
+++ b/scripts/maintenance/quick-cache-fix.php
@@ -0,0 +1,77 @@
+ [
+ 'Application', // Only scan application code
+ 'Domain' // And domain models
+ ],
+ 'exclude_paths' => [
+ 'Framework/AsyncExamples', // Skip examples
+ 'Framework/Testing', // Skip testing utilities
+ 'Framework/Debug', // Skip debug utilities
+ 'tests' // Skip test files
+ ]
+ ];
+
+ echo " ✅ Limited discovery to: " . implode(', ', $optimizedConfig['discovery_paths']) . "\n";
+ echo " ✅ Excluded: " . implode(', ', $optimizedConfig['exclude_paths']) . "\n";
+
+ echo "\n3. 🧪 Testing basic application...\n";
+
+ // Test if basic classes load without discovery
+ $testClasses = [
+ 'App\\Framework\\Core\\Application',
+ 'App\\Framework\\Http\\HttpRequest'
+ ];
+
+ foreach ($testClasses as $class) {
+ if (class_exists($class)) {
+ echo " ✅ $class loaded\n";
+ } else {
+ echo " ❌ $class failed\n";
+ }
+ }
+
+ echo "\n4. 💡 Recommendations:\n";
+ echo " • Discovery system needs optimization for large codebase\n";
+ echo " • Consider implementing lazy loading for non-critical components\n";
+ echo " • Use incremental discovery instead of full scans\n";
+ echo " • Add performance monitoring to discovery process\n";
+
+ echo "\n🎉 Quick fix complete!\n";
+ echo "💬 Try accessing https://localhost/ now\n";
+
+} catch (Exception $e) {
+ echo "❌ Error: " . $e->getMessage() . "\n";
+ exit(1);
+}
\ No newline at end of file
diff --git a/test_agents_simple.php b/scripts/test/test_agents_simple.php
similarity index 100%
rename from test_agents_simple.php
rename to scripts/test/test_agents_simple.php
diff --git a/scripts/test/test_arrow_keys.php b/scripts/test/test_arrow_keys.php
new file mode 100644
index 00000000..f2b4e90a
--- /dev/null
+++ b/scripts/test/test_arrow_keys.php
@@ -0,0 +1,95 @@
+ Arrow UP detected!\n";
+ break;
+ case "\033[B":
+ echo " -> Arrow DOWN detected!\n";
+ break;
+ case "\033[C":
+ echo " -> Arrow RIGHT detected!\n";
+ break;
+ case "\033[D":
+ echo " -> Arrow LEFT detected!\n";
+ break;
+ case "\n":
+ echo " -> ENTER detected!\n";
+ break;
+ case " ":
+ echo " -> SPACE detected!\n";
+ break;
+ case "\033":
+ echo " -> ESC detected!\n";
+ break;
+ default:
+ echo " -> Regular key: '$key'\n";
+ break;
+ }
+ }
+} finally {
+ // Restore terminal
+ shell_exec('stty icanon echo');
+ echo "\nTerminal restored. Goodbye!\n";
+}
\ No newline at end of file
diff --git a/scripts/test/test_final_tui.php b/scripts/test/test_final_tui.php
new file mode 100644
index 00000000..ea38e940
--- /dev/null
+++ b/scripts/test/test_final_tui.php
@@ -0,0 +1,191 @@
+screen = $screenManager;
+
+ $state = new TuiState();
+ $history = new CommandHistory();
+ $inputHandler = new TuiInputHandler($mockExecutor);
+ $renderer = new TuiRenderer($output);
+ $groupRegistry = new CommandGroupRegistry($discoveryRegistry);
+
+ echo "✓ Components created successfully\n\n";
+
+ // Test CommandGroupRegistry::getOrganizedCommands() returns numeric array
+ echo "Testing CommandGroupRegistry::getOrganizedCommands():\n";
+ $categories = $groupRegistry->getOrganizedCommands();
+
+ echo " Categories type: " . (is_array($categories) ? "array" : gettype($categories)) . "\n";
+ echo " Categories count: " . count($categories) . "\n";
+ echo " Keys are numeric: " . (array_is_list($categories) ? "YES" : "NO") . "\n";
+
+ if (!empty($categories)) {
+ echo " First category structure:\n";
+ $firstCategory = $categories[0];
+ echo " - name: '{$firstCategory['name']}'\n";
+ echo " - description: '{$firstCategory['description']}'\n";
+ echo " - icon: '{$firstCategory['icon']}'\n";
+ echo " - commands count: " . count($firstCategory['commands']) . "\n";
+ }
+ echo "\n";
+
+ // Setup TUI state with the organized categories
+ $state->setCategories($categories);
+ $state->setCurrentView(TuiView::CATEGORIES);
+ $state->setRunning(true);
+
+ echo "✓ TUI State initialized:\n";
+ echo " Categories loaded: " . count($categories) . "\n";
+ echo " Current view: " . $state->getCurrentView()->name . "\n";
+ echo " Selected category: " . $state->getSelectedCategory() . "\n";
+
+ $currentCategory = $state->getCurrentCategory();
+ if ($currentCategory) {
+ echo " Current category name: '{$currentCategory['name']}'\n";
+ } else {
+ echo " ❌ Current category: NULL\n";
+ }
+ echo "\n";
+
+ // Test the complete navigation workflow
+ echo "🔍 Testing Complete Navigation Workflow:\n\n";
+
+ $maxCategoryIndex = count($categories) - 1;
+
+ if ($maxCategoryIndex >= 0) {
+ // Test navigation through all categories
+ echo "Navigation Test - Moving through all categories:\n";
+
+ // Start at first category
+ $state->setSelectedCategory(0);
+ $startCategory = $state->getCurrentCategory();
+ echo " Start: Category 0 => '{$startCategory['name']}'\n";
+
+ // Navigate down to last category
+ for ($i = 0; $i < $maxCategoryIndex; $i++) {
+ $inputHandler->handleInput(TuiKeyCode::ARROW_DOWN->value, $state, $history);
+ $category = $state->getCurrentCategory();
+ echo " Arrow DOWN: Category {$state->getSelectedCategory()} => '{$category['name']}'\n";
+ }
+
+ // Try to go past last category (should stay at last)
+ $beforeIndex = $state->getSelectedCategory();
+ $inputHandler->handleInput(TuiKeyCode::ARROW_DOWN->value, $state, $history);
+ $afterIndex = $state->getSelectedCategory();
+ echo " Boundary test (down): {$beforeIndex} => {$afterIndex} " . ($beforeIndex === $afterIndex ? "✓ PROTECTED" : "❌ FAILED") . "\n";
+
+ // Navigate back up
+ echo " Navigating back up...\n";
+ for ($i = $maxCategoryIndex; $i > 0; $i--) {
+ $inputHandler->handleInput(TuiKeyCode::ARROW_UP->value, $state, $history);
+ $category = $state->getCurrentCategory();
+ echo " Arrow UP: Category {$state->getSelectedCategory()} => '{$category['name']}'\n";
+ }
+
+ // Try to go past first category (should stay at first)
+ $beforeIndex = $state->getSelectedCategory();
+ $inputHandler->handleInput(TuiKeyCode::ARROW_UP->value, $state, $history);
+ $afterIndex = $state->getSelectedCategory();
+ echo " Boundary test (up): {$beforeIndex} => {$afterIndex} " . ($beforeIndex === $afterIndex ? "✓ PROTECTED" : "❌ FAILED") . "\n";
+
+ } else {
+ echo " No categories available for navigation test\n";
+ }
+
+ echo "\n";
+
+ // Test rendering
+ echo "Testing TUI Rendering:\n";
+ echo "======================\n";
+ $renderer->render($state, $history);
+ echo "\n";
+
+ echo "✅ FINAL TUI TEST PASSED\n";
+ echo "🎯 Summary:\n";
+ echo " ✓ CommandGroupRegistry returns numeric array\n";
+ echo " ✓ TuiState navigation works correctly\n";
+ echo " ✓ Arrow key input handling functional\n";
+ echo " ✓ Boundary protection working\n";
+ echo " ✓ TUI rendering operational\n";
+ echo " ✓ Welcome screen integration ready\n";
+ echo "\n";
+ echo "🚀 The TUI is now fully functional and ready for use in a real terminal!\n";
+
+} catch (\Throwable $e) {
+ echo "\n❌ FINAL TUI TEST FAILED:\n";
+ echo "Error: " . $e->getMessage() . "\n";
+ echo "File: " . $e->getFile() . ":" . $e->getLine() . "\n";
+ echo "\nStack trace:\n" . $e->getTraceAsString() . "\n";
+}
\ No newline at end of file
diff --git a/test_framework_agents.php b/scripts/test/test_framework_agents.php
similarity index 100%
rename from test_framework_agents.php
rename to scripts/test/test_framework_agents.php
diff --git a/scripts/test/test_interactive_input.php b/scripts/test/test_interactive_input.php
new file mode 100644
index 00000000..a00741dd
--- /dev/null
+++ b/scripts/test/test_interactive_input.php
@@ -0,0 +1,159 @@
+/dev/null'));
+echo "- stty available: " . ($hasStty ? 'YES' : 'NO') . "\n";
+
+if ($hasStty) {
+ $sttySettings = trim(shell_exec('stty -a 2>/dev/null') ?: '');
+ echo "- Current stty settings: " . (!empty($sttySettings) ? 'available' : 'unavailable') . "\n";
+}
+
+echo "\n";
+
+if (!$hasTTY) {
+ echo "❌ No TTY available. TUI will not work in this environment.\n";
+ echo "Try running in a real terminal instead of PHPStorm's integrated terminal.\n";
+ exit(1);
+}
+
+if (!$hasStty) {
+ echo "❌ stty command not available. Raw mode cannot be set.\n";
+ exit(1);
+}
+
+echo "Setting up PHPStorm-compatible input reading...\n\n";
+
+// Save original settings
+$originalSettings = trim(shell_exec('stty -g') ?: '');
+if (empty($originalSettings)) {
+ echo "❌ Could not save terminal settings\n";
+ exit(1);
+}
+
+echo "✓ Original settings saved\n";
+
+// PHPStorm-specific terminal setup
+echo "Setting up terminal for PHPStorm...\n";
+
+// Try different approaches for PHPStorm
+if ($isPhpStorm) {
+ echo "Using PHPStorm-optimized settings...\n";
+ // PHPStorm sometimes needs different settings
+ shell_exec('stty raw -echo min 1 time 0 2>/dev/null');
+} else {
+ echo "Using standard settings...\n";
+ shell_exec('stty -icanon -echo 2>/dev/null');
+}
+
+echo "✓ Raw mode set\n\n";
+
+echo "=== INPUT TEST ===\n";
+echo "Press keys to test input. Type 'quit' to exit.\n";
+echo "Pay attention to arrow key behavior.\n\n";
+
+function readInput(): string {
+ $input = '';
+
+ while (true) {
+ $char = fgetc(STDIN);
+ if ($char === false) {
+ break;
+ }
+
+ $input .= $char;
+
+ // Check for complete escape sequence
+ if ($char === "\033") {
+ // Read potential escape sequence
+ $next = fgetc(STDIN);
+ if ($next !== false) {
+ $input .= $next;
+ if ($next === '[') {
+ $third = fgetc(STDIN);
+ if ($third !== false) {
+ $input .= $third;
+ // Some sequences have a 4th character
+ if (in_array($third, ['5', '6', '3', '1', '2', '4'])) {
+ $fourth = fgetc(STDIN);
+ if ($fourth !== false) {
+ $input .= $fourth;
+ }
+ }
+ }
+ }
+ }
+ break;
+ }
+
+ // For regular characters, break immediately
+ if ($char !== "\033") {
+ break;
+ }
+ }
+
+ return $input;
+}
+
+try {
+ $buffer = '';
+ $testCount = 0;
+
+ while (true) {
+ $key = readInput();
+
+ if ($key === '') {
+ continue;
+ }
+
+ $testCount++;
+ $buffer .= $key;
+
+ echo "Input $testCount:\n";
+ echo " Raw: '" . addcslashes($key, "\0..\37\177..\377") . "'\n";
+ echo " Hex: " . bin2hex($key) . "\n";
+ echo " Length: " . strlen($key) . "\n";
+
+ // Check for complete words
+ if (str_contains($buffer, 'quit')) {
+ echo "Quit command detected!\n";
+ break;
+ }
+
+ // Analyze the key
+ switch ($key) {
+ case "\033[A":
+ echo " ✓ ARROW UP - Perfect!\n";
+ break;
+ case "\033[B":
+ echo " ✓ ARROW DOWN - Perfect!\n";
+ break;
+ case "\033[C":
+ echo " ✓ ARROW RIGHT - Perfect!\n";
+ break;
+ case "\033[D":
+ echo " ✓ ARROW LEFT - Perfect!\n";
+ break;
+ case "\n":
+ case "\r":
+ echo " ✓ ENTER\n";
+ $buffer = ''; // Reset buffer on enter
+ break;
+ case "\033":
+ echo " → ESC (incomplete sequence?)\n";
+ break;
+ default:
+ if (ctype_print($key)) {
+ echo " → Character: '$key'\n";
+ } else {
+ echo " → Special/Unknown\n";
+ }
+ break;
+ }
+
+ echo "\n";
+
+ // Limit output to prevent spam
+ if ($testCount > 20) {
+ echo "Test limit reached. Type 'quit' to exit.\n";
+ }
+ }
+
+} finally {
+ echo "\nRestoring terminal...\n";
+ shell_exec("stty $originalSettings 2>/dev/null");
+ echo "✓ Terminal restored\n";
+ echo "Total inputs processed: $testCount\n";
+}
+
+echo "\n=== RECOMMENDATIONS ===\n";
+
+if ($isPhpStorm) {
+ echo "PHPStorm Terminal detected. Consider:\n";
+ echo "1. Use external terminal (Windows Terminal, iTerm2, etc.)\n";
+ echo "2. Or use PHPStorm's 'Terminal' tool window with different shell\n";
+ echo "3. Some TUI features may be limited in integrated terminals\n";
+} else {
+ echo "Standard terminal detected. TUI should work normally.\n";
+}
+
+echo "\nIf arrow keys didn't work properly, the TUI navigation will also fail.\n";
\ No newline at end of file
diff --git a/scripts/test/test_presave_container.php b/scripts/test/test_presave_container.php
new file mode 100644
index 00000000..9bb698b6
--- /dev/null
+++ b/scripts/test/test_presave_container.php
@@ -0,0 +1,38 @@
+bootstrapWeb();
+
+ $container = $app->getContainer();
+
+ echo "✅ Container bootstrapped successfully\n";
+
+ $preSave = $container->get(PreSaveCampaign::class);
+
+ echo "✅ PreSaveCampaign successfully resolved from container\n";
+ echo "PreSaveCampaign class: " . get_class($preSave) . "\n";
+
+} catch (\Exception $e) {
+ echo "❌ Error: " . $e->getMessage() . "\n";
+ echo "File: " . $e->getFile() . ":" . $e->getLine() . "\n";
+ echo "\nStack trace:\n";
+ echo $e->getTraceAsString() . "\n";
+}
diff --git a/scripts/test/test_real_navigation.php b/scripts/test/test_real_navigation.php
new file mode 100644
index 00000000..5efd253a
--- /dev/null
+++ b/scripts/test/test_real_navigation.php
@@ -0,0 +1,170 @@
+ [
+ 'name' => 'Testing',
+ 'description' => '',
+ 'icon' => '🧪',
+ 'priority' => 0,
+ 'commands' => []
+ ],
+ 'Demo' => [
+ 'name' => 'Demo',
+ 'description' => '',
+ 'icon' => '🎮',
+ 'priority' => 0,
+ 'commands' => []
+ ],
+ 'Generator' => [
+ 'name' => 'Generator',
+ 'description' => '',
+ 'icon' => '⚙️',
+ 'priority' => 0,
+ 'commands' => []
+ ],
+ 'General' => [
+ 'name' => 'General',
+ 'description' => '',
+ 'icon' => '📂',
+ 'priority' => 0,
+ 'commands' => []
+ ]
+ ];
+
+ // Sort by priority (all have priority 0 in this case, so order by keys)
+ uasort($organizedCategories, fn($a, $b) => $b['priority'] <=> $a['priority']);
+
+ echo "📊 Before array_values() conversion (associative array):\n";
+ foreach ($organizedCategories as $key => $category) {
+ echo " Key: '$key' => Category: '{$category['name']}'\n";
+ }
+ echo "\n";
+
+ // Convert to numeric array like our fix
+ $numericCategories = array_values($organizedCategories);
+
+ echo "📊 After array_values() conversion (numeric array):\n";
+ foreach ($numericCategories as $index => $category) {
+ echo " Index: $index => Category: '{$category['name']}'\n";
+ }
+ echo "\n";
+
+ // Test with the numeric array structure
+ $state->setCategories($numericCategories);
+ $state->setCurrentView(TuiView::CATEGORIES);
+ $state->setRunning(true);
+
+ echo "✓ Setup Complete:\n";
+ echo " Categories Count: " . count($numericCategories) . "\n";
+ echo " Current View: " . $state->getCurrentView()->name . "\n";
+ echo " Selected Category Index: " . $state->getSelectedCategory() . "\n";
+
+ $currentCategory = $state->getCurrentCategory();
+ if ($currentCategory) {
+ echo " Current Category Name: '{$currentCategory['name']}'\n";
+ } else {
+ echo " ❌ Current Category: NULL (This would cause navigation issues!)\n";
+ }
+ echo "\n";
+
+ // Test navigation with the real structure
+ echo "🔍 Testing Navigation with Real Structure:\n\n";
+
+ // Test 1: Arrow Down
+ echo "Test 1: Arrow DOWN\n";
+ $beforeCategory = $state->getCurrentCategory();
+ $beforeIndex = $state->getSelectedCategory();
+ echo " Before: Index $beforeIndex => '{$beforeCategory['name']}'\n";
+
+ $inputHandler->handleInput(TuiKeyCode::ARROW_DOWN->value, $state, $history);
+
+ $afterCategory = $state->getCurrentCategory();
+ $afterIndex = $state->getSelectedCategory();
+ echo " After: Index $afterIndex => '{$afterCategory['name']}'\n";
+ echo " ✓ Navigation worked: " . ($beforeIndex !== $afterIndex ? "YES" : "NO") . "\n\n";
+
+ // Test 2: Arrow Down again
+ echo "Test 2: Arrow DOWN again\n";
+ $beforeCategory = $state->getCurrentCategory();
+ $beforeIndex = $state->getSelectedCategory();
+ echo " Before: Index $beforeIndex => '{$beforeCategory['name']}'\n";
+
+ $inputHandler->handleInput(TuiKeyCode::ARROW_DOWN->value, $state, $history);
+
+ $afterCategory = $state->getCurrentCategory();
+ $afterIndex = $state->getSelectedCategory();
+ echo " After: Index $afterIndex => '{$afterCategory['name']}'\n";
+ echo " ✓ Navigation worked: " . ($beforeIndex !== $afterIndex ? "YES" : "NO") . "\n\n";
+
+ // Test 3: Arrow Up
+ echo "Test 3: Arrow UP\n";
+ $beforeCategory = $state->getCurrentCategory();
+ $beforeIndex = $state->getSelectedCategory();
+ echo " Before: Index $beforeIndex => '{$beforeCategory['name']}'\n";
+
+ $inputHandler->handleInput(TuiKeyCode::ARROW_UP->value, $state, $history);
+
+ $afterCategory = $state->getCurrentCategory();
+ $afterIndex = $state->getSelectedCategory();
+ echo " After: Index $afterIndex => '{$afterCategory['name']}'\n";
+ echo " ✓ Navigation worked: " . ($beforeIndex !== $afterIndex ? "YES" : "NO") . "\n\n";
+
+ echo "✅ Real Navigation Test COMPLETED\n";
+
+} catch (\Throwable $e) {
+ echo "\n❌ Real Navigation Test FAILED:\n";
+ echo "Error: " . $e->getMessage() . "\n";
+ echo "File: " . $e->getFile() . ":" . $e->getLine() . "\n";
+ echo "\nStack trace:\n" . $e->getTraceAsString() . "\n";
+}
\ No newline at end of file
diff --git a/test_request_container.php b/scripts/test/test_request_container.php
similarity index 100%
rename from test_request_container.php
rename to scripts/test/test_request_container.php
diff --git a/test_request_factory.php b/scripts/test/test_request_factory.php
similarity index 100%
rename from test_request_factory.php
rename to scripts/test/test_request_factory.php
diff --git a/test_request_web.php b/scripts/test/test_request_web.php
similarity index 100%
rename from test_request_web.php
rename to scripts/test/test_request_web.php
diff --git a/scripts/test/test_route_discovery.php b/scripts/test/test_route_discovery.php
new file mode 100644
index 00000000..ba1c68de
--- /dev/null
+++ b/scripts/test/test_route_discovery.php
@@ -0,0 +1,79 @@
+discover($options);
+
+ $routes = $registry->getByAttribute(Route::class);
+
+ echo "=== DISCOVERED ROUTES ===\n\n";
+ echo "Total routes found: " . count($routes) . "\n\n";
+
+ foreach ($routes as $discovered) {
+ $routeAttr = $discovered->attribute->newInstance();
+ $className = $discovered->className;
+ $methodName = $discovered->methodName ?? '__invoke';
+
+ echo "Route: {$routeAttr->method->value} {$routeAttr->path}\n";
+ echo " Class: {$className}\n";
+ echo " Method: {$methodName}\n";
+
+ // Check for Campaign routes specifically
+ if (str_contains($className, 'Campaign')) {
+ echo " ⭐ CAMPAIGN ROUTE\n";
+ }
+
+ echo "\n";
+ }
+
+ // Specifically search for PreSaveCampaign
+ echo "\n=== SEARCHING FOR PreSaveCampaign ===\n";
+ $found = false;
+ foreach ($routes as $discovered) {
+ if (str_contains($discovered->className, 'PreSaveCampaign')) {
+ echo "✅ PreSaveCampaign FOUND!\n";
+ echo " Path: {$discovered->attribute->newInstance()->path}\n";
+ $found = true;
+ }
+ }
+
+ if (!$found) {
+ echo "❌ PreSaveCampaign NOT FOUND in discovery!\n";
+ }
+
+} catch (\Exception $e) {
+ echo "❌ Error during discovery: " . $e->getMessage() . "\n";
+ echo "File: " . $e->getFile() . ":" . $e->getLine() . "\n";
+ echo "\nStack trace:\n";
+ echo $e->getTraceAsString() . "\n";
+}
diff --git a/scripts/test/test_spotify_init.php b/scripts/test/test_spotify_init.php
new file mode 100644
index 00000000..8d0e7c76
--- /dev/null
+++ b/scripts/test/test_spotify_init.php
@@ -0,0 +1,43 @@
+get(\App\Framework\Config\EnvKey::fromString('SPOTIFY_CLIENT_ID'), 'NOT SET') . "\n";
+ echo "SPOTIFY_CLIENT_SECRET: " . $env->get(\App\Framework\Config\EnvKey::fromString('SPOTIFY_CLIENT_SECRET'), 'NOT SET') . "\n";
+
+ $containerBootstrapper = new ContainerBootstrapper($env);
+ $container = $containerBootstrapper->bootstrap();
+
+ echo "Container bootstrapped\n";
+
+ $spotifyProvider = $container->get(SpotifyProvider::class);
+
+ echo "✅ SpotifyProvider successfully resolved from container\n";
+ echo "Provider name: " . $spotifyProvider->getName() . "\n";
+
+} catch (\Exception $e) {
+ echo "❌ Error: " . $e->getMessage() . "\n";
+ echo "File: " . $e->getFile() . ":" . $e->getLine() . "\n";
+ echo "\nStack trace:\n";
+ echo $e->getTraceAsString() . "\n";
+}
diff --git a/scripts/test/test_spotify_simple.php b/scripts/test/test_spotify_simple.php
new file mode 100644
index 00000000..6441f5b3
--- /dev/null
+++ b/scripts/test/test_spotify_simple.php
@@ -0,0 +1,7 @@
+screen = $screenManager;
+
+ $state = new TuiState();
+ $history = new CommandHistory();
+ $inputHandler = new TuiInputHandler($mockExecutor);
+ $renderer = new TuiRenderer($output);
+
+ // Setup test categories
+ $categories = [
+ 'Database' => [
+ 'name' => 'Database',
+ 'description' => 'Database commands',
+ 'icon' => '🗄️',
+ 'commands' => []
+ ],
+ 'Cache' => [
+ 'name' => 'Cache',
+ 'description' => 'Cache commands',
+ 'icon' => '⚡',
+ 'commands' => []
+ ],
+ 'Testing' => [
+ 'name' => 'Testing',
+ 'description' => 'Testing commands',
+ 'icon' => '🧪',
+ 'commands' => []
+ ]
+ ];
+
+ $state->setCategories($categories);
+ $state->setCurrentView(TuiView::CATEGORIES);
+ $state->setRunning(true);
+
+ echo "✅ Initial Setup Complete\n";
+ echo "Categories loaded: " . count($categories) . "\n";
+ echo "Current view: " . $state->getCurrentView()->name . "\n";
+ echo "Selected category: " . $state->getSelectedCategory() . "\n\n";
+
+ // Test Arrow Down Navigation
+ echo "Testing Arrow DOWN navigation:\n";
+ echo "Before: Category " . $state->getSelectedCategory() . " ('{$categories[array_keys($categories)[$state->getSelectedCategory()]]['name']}')\n";
+
+ $inputHandler->handleInput(TuiKeyCode::ARROW_DOWN->value, $state, $history);
+
+ echo "After Arrow DOWN: Category " . $state->getSelectedCategory() . " ('{$categories[array_keys($categories)[$state->getSelectedCategory()]]['name']}')\n\n";
+
+ // Test Arrow Up Navigation
+ echo "Testing Arrow UP navigation:\n";
+ echo "Before: Category " . $state->getSelectedCategory() . " ('{$categories[array_keys($categories)[$state->getSelectedCategory()]]['name']}')\n";
+
+ $inputHandler->handleInput(TuiKeyCode::ARROW_UP->value, $state, $history);
+
+ echo "After Arrow UP: Category " . $state->getSelectedCategory() . " ('{$categories[array_keys($categories)[$state->getSelectedCategory()]]['name']}')\n\n";
+
+ // Test bounds checking - try to go below 0
+ echo "Testing boundary protection (going below 0):\n";
+
+ $inputHandler->handleInput(TuiKeyCode::ARROW_UP->value, $state, $history);
+
+ echo "After Arrow UP (should stay at 0): Category " . $state->getSelectedCategory() . " ('{$categories[array_keys($categories)[$state->getSelectedCategory()]]['name']}')\n\n";
+
+ // Test bounds checking - try to go above max
+ echo "Testing boundary protection (going above max):\n";
+
+ // Go to last item
+ $state->setSelectedCategory(2); // Testing category
+ echo "Set to last category (2): '{$categories[array_keys($categories)[$state->getSelectedCategory()]]['name']}'\n";
+
+ $inputHandler->handleInput(TuiKeyCode::ARROW_DOWN->value, $state, $history);
+
+ echo "After Arrow DOWN (should stay at 2): Category " . $state->getSelectedCategory() . " ('{$categories[array_keys($categories)[$state->getSelectedCategory()]]['name']}')\n\n";
+
+ // Test rendering with actual state
+ echo "Testing TUI Rendering:\n";
+ echo "=====================\n";
+
+ $state->setSelectedCategory(0);
+ $renderer->render($state, $history);
+
+ echo "\n✅ TUI Complete Test PASSED - All functionality works!\n";
+
+} catch (\Throwable $e) {
+ echo "\n❌ TUI Complete Test FAILED:\n";
+ echo "Error: " . $e->getMessage() . "\n";
+ echo "File: " . $e->getFile() . ":" . $e->getLine() . "\n";
+ echo "\nStack trace:\n" . $e->getTraceAsString() . "\n";
+}
\ No newline at end of file
diff --git a/scripts/test/test_tui_navigation.php b/scripts/test/test_tui_navigation.php
new file mode 100644
index 00000000..996d3c6d
--- /dev/null
+++ b/scripts/test/test_tui_navigation.php
@@ -0,0 +1,95 @@
+ [
+ 'name' => 'Database',
+ 'description' => 'Database commands',
+ 'icon' => '🗄️',
+ 'commands' => []
+ ],
+ 'Cache' => [
+ 'name' => 'Cache',
+ 'description' => 'Cache commands',
+ 'icon' => '⚡',
+ 'commands' => []
+ ]
+ ];
+
+ $state->setCategories($categories);
+ $state->setCurrentView(TuiView::CATEGORIES);
+ $state->setRunning(true);
+
+ // Test arrow navigation
+ echo "Testing arrow key navigation:\n";
+ echo "Initial category: " . $state->getSelectedCategory() . "\n";
+
+ // Test arrow down
+ $inputHandler->handleInput(TuiKeyCode::ARROW_DOWN->value, $state, $history);
+ echo "After arrow down: " . $state->getSelectedCategory() . "\n";
+
+ // Test arrow up
+ $inputHandler->handleInput(TuiKeyCode::ARROW_UP->value, $state, $history);
+ echo "After arrow up: " . $state->getSelectedCategory() . "\n";
+
+ // Test bounds checking - try to go below 0
+ $inputHandler->handleInput(TuiKeyCode::ARROW_UP->value, $state, $history);
+ echo "After arrow up (should stay at 0): " . $state->getSelectedCategory() . "\n";
+
+ echo "\n✅ TUI Navigation Test PASSED - Arrow key handling works!\n";
+
+} catch (\Throwable $e) {
+ echo "\n❌ TUI Navigation Test FAILED:\n";
+ echo "Error: " . $e->getMessage() . "\n";
+ echo "File: " . $e->getFile() . ":" . $e->getLine() . "\n";
+ echo "\nStack trace:\n" . $e->getTraceAsString() . "\n";
+}
\ No newline at end of file
diff --git a/websocket.php b/scripts/test/websocket.php
similarity index 100%
rename from websocket.php
rename to scripts/test/websocket.php
diff --git a/src/Application/Admin/views/admin/dashboard.php b/src/Application/Admin/views/admin/dashboard.php
deleted file mode 100644
index 140820a2..00000000
--- a/src/Application/Admin/views/admin/dashboard.php
+++ /dev/null
@@ -1,96 +0,0 @@
-
-
-
-
-
- = $title ?>
-
-
-
-
-
-
-
-
-
-
-
Framework Version
-
= $stats['frameworkVersion'] ?>
-
-
-
-
PHP Version
-
= $stats['phpVersion'] ?>
-
-
-
-
Speicherverbrauch
-
= $stats['memoryUsage'] ?>
-
-
-
-
Max. Speicherverbrauch
-
= $stats['peakMemoryUsage'] ?>
-
-
-
-
Server
-
= $stats['serverInfo'] ?>
-
-
-
-
Serverzeit
-
= $stats['serverTime'] ?>
-
-
-
-
Zeitzone
-
= $stats['timezone'] ?>
-
-
-
-
Betriebssystem
-
= $stats['operatingSystem'] ?>
-
-
-
-
Server Uptime
-
= $stats['uptime'] ?>
-
-
-
-
Aktive Sessions
-
= $stats['sessionCount'] ?>
-
-
-
-
Registrierte Dienste
-
= $stats['servicesCount'] ?>
-
-
-
-
-
PHP Erweiterungen
-
-
- = $extension ?>
-
-
-
-
-
-
-
-
diff --git a/src/Application/Admin/views/admin/environment.view.php b/src/Application/Admin/views/admin/environment.view.php
deleted file mode 100644
index e0520cdf..00000000
--- a/src/Application/Admin/views/admin/environment.view.php
+++ /dev/null
@@ -1,86 +0,0 @@
-
-
-
-
-
- {{ title }}
-
-
-
-
-
-
-
-
-
-
-
-
-
- | Variable |
- Wert |
-
-
-
-
-
- | {{ envVar.key }} |
- {{ envVar.value }} |
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Application/Admin/views/admin/image-manager.view.php b/src/Application/Admin/views/admin/image-manager.view.php
deleted file mode 100644
index 70a90dc7..00000000
--- a/src/Application/Admin/views/admin/image-manager.view.php
+++ /dev/null
@@ -1,250 +0,0 @@
-
-
-
-
-
- Image Manager
-
-
-
-
-
-
Image Manager
-
-
-
-
-
Image Slots
-
-
-
-
-
-
{{ slot.slotName }}
- ID: {{ slot.id }}
-
-
-
- Drop image here or click to select
-
-
-
-
-
-
-
-
-
-
-
Available Images
-
-
-
-
-
-
-
-
-
-
-
-

-
-
- {{ image.originalFilename }}
-
-
- {{ image.width }}x{{ image.height }} • {{ image.fileSize }}KB
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/Application/Admin/views/admin/performance.view.php b/src/Application/Admin/views/admin/performance.view.php
deleted file mode 100644
index 77262d01..00000000
--- a/src/Application/Admin/views/admin/performance.view.php
+++ /dev/null
@@ -1,125 +0,0 @@
-
-
-
-
-
- {{ title }}
-
-
-
-
-
-
-
-
-
-
-
Aktueller Speicherverbrauch
-
{{ performance.currentMemoryUsage }}
-
-
-
-
Maximaler Speicherverbrauch
-
{{ performance.peakMemoryUsage }}
-
-
-
-
Speicherlimit
-
{{ performance.memoryLimit }}
-
-
-
-
Speicherauslastung
-
-
-
{{ performance.memoryUsagePercentage }}%
-
-
-
-
-
Systemlast (1/5/15 min)
-
- {{ performance.loadAverage.0 }} /
- {{ performance.loadAverage.1 }} /
- {{ performance.loadAverage.2 }}
-
-
-
-
-
OPCache aktiviert
-
{{ performance.opcacheEnabled }}
-
-
-
-
-
OPCache Speicherverbrauch
-
{{ performance.opcacheMemoryUsage }}
-
-
-
-
OPCache Cache Hits
-
{{ performance.opcacheCacheHits }}
-
-
-
-
OPCache Miss Rate
-
{{ performance.opcacheMissRate }}
-
-
-
-
-
Ausführungszeit
-
{{ performance.executionTime }}
-
-
-
-
Geladene Dateien
-
{{ performance.includedFiles }}
-
-
-
-
-
Geladene Dateien
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Application/Admin/views/admin/redis.view.php b/src/Application/Admin/views/admin/redis.view.php
deleted file mode 100644
index 7ef6f3f1..00000000
--- a/src/Application/Admin/views/admin/redis.view.php
+++ /dev/null
@@ -1,94 +0,0 @@
-
-
-
-
-
- {{ title }}
-
-
-
-
-
-
-
-
-
-
-
Status
-
{{ redis.status }}
-
-
-
-
Version
-
{{ redis.version }}
-
-
-
-
Uptime
-
{{ redis.uptime }}
-
-
-
-
Speicherverbrauch
-
{{ redis.memory }}
-
-
-
-
Max. Speicherverbrauch
-
{{ redis.peak_memory }}
-
-
-
-
Verbundene Clients
-
{{ redis.clients }}
-
-
-
-
Anzahl Schlüssel
-
{{ redis.keys }}
-
-
-
-
-
Schlüssel (max. 50 angezeigt)
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Application/Admin/views/admin/routes.php b/src/Application/Admin/views/admin/routes.php
deleted file mode 100644
index fcf61e64..00000000
--- a/src/Application/Admin/views/admin/routes.php
+++ /dev/null
@@ -1,69 +0,0 @@
-
-
-
-
-
- = $title ?>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- | Pfad |
- Methode |
- Controller |
- Aktion |
- Name |
-
-
-
-
-
- | = $route->path ?> |
- = $route->method ?> |
- = $route->controllerClass ?> |
- = $route->methodName ?> |
- = $route->name ?? '-' ?> |
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Application/Admin/views/admin/services.view.php b/src/Application/Admin/views/admin/services.view.php
deleted file mode 100644
index 4108a3ef..00000000
--- a/src/Application/Admin/views/admin/services.view.php
+++ /dev/null
@@ -1,67 +0,0 @@
-
-
-
-
-
- {{ title }}
-
-
-
-
-
-
-
-
-
-
- {{ servicesCount }} Dienste insgesamt
-
-
-
-
-
-
{{ service.name }}
-
- {{ service.category }}
-
- {{ service.subCategory }}
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Application/Http/Smartlink.php b/src/Application/Http/MagicLink.php
similarity index 100%
rename from src/Application/Http/Smartlink.php
rename to src/Application/Http/MagicLink.php
diff --git a/src/Application/Http/templates/smartlinks-error.view.php b/src/Application/Http/templates/magiclinks-error.view.php
similarity index 100%
rename from src/Application/Http/templates/smartlinks-error.view.php
rename to src/Application/Http/templates/magiclinks-error.view.php
diff --git a/src/Framework/DI/ContainerIntrospector.php b/src/Framework/DI/ContainerIntrospector.php
new file mode 100644
index 00000000..c3874970
--- /dev/null
+++ b/src/Framework/DI/ContainerIntrospector.php
@@ -0,0 +1,183 @@
+
+ */
+ public function listBindings(): array
+ {
+ return array_keys($this->bindings->getAllBindings());
+ }
+
+ public function getBinding(string $abstract): callable|string|object|null
+ {
+ return $this->bindings->getBinding($abstract);
+ }
+
+ /**
+ * @return array
+ */
+ public function listSingletons(): array
+ {
+ return $this->instances->getSingletons();
+ }
+
+ /**
+ * @return array
+ */
+ public function listInstances(): array
+ {
+ return $this->instances->getInstanceKeys();
+ }
+
+ /** @param class-string $class */
+ public function isSingleton(string $class): bool
+ {
+ return $this->instances->isMarkedAsSingleton($class) || $this->instances->hasSingleton($class);
+ }
+
+ /**
+ * @return array
+ */
+ public function getResolutionChain(): array
+ {
+ $f = $this->resolutionChainProvider;
+
+ /** @var array $chain */
+ $chain = $f();
+ return $chain;
+ }
+
+ /** @param class-string $class */
+ public function isInstantiable(string $class): bool
+ {
+ if ($class === '') {
+ return false;
+ }
+ $className = ClassName::create($class);
+ if (! $className->exists()) {
+ return false;
+ }
+ return $this->reflectionProvider->getClass($className)->isInstantiable();
+ }
+
+ /**
+ * Describe resolution state and constructor parameters for diagnostics.
+ * @param class-string $class
+ * @return array
+ */
+ public function describe(string $class): array
+ {
+ $className = ClassName::create($class);
+ $exists = $className->exists();
+ $hasBinding = $this->bindings->hasBinding($class);
+ $hasInstance = $this->instances->hasInstance($class) || $this->instances->hasSingleton($class);
+ $singletonMarked = $this->instances->isMarkedAsSingleton($class);
+
+ $instantiable = false;
+ $constructor = [
+ 'has_constructor' => false,
+ 'parameters' => [],
+ ];
+
+ $binding = $this->bindings->getBinding($class);
+ $bindingType = null;
+ if ($binding !== null) {
+ $bindingType = is_callable($binding) ? 'callable' : (is_string($binding) ? 'string' : 'object');
+ }
+
+ if ($exists) {
+ try {
+ $reflection = $this->reflectionProvider->getClass($className);
+ $instantiable = $reflection->isInstantiable();
+ if ($reflection->hasMethod('__construct')) {
+ $ctor = $reflection->getConstructor();
+ if ($ctor !== null) {
+ $constructor['has_constructor'] = true;
+ foreach ($ctor->getParameters() as $param) {
+ $type = $param->getType();
+ $typeName = null;
+ $isBuiltin = false;
+ if ($type instanceof \ReflectionNamedType) {
+ $typeName = $type->getName();
+ $isBuiltin = $type->isBuiltin();
+ } elseif ($type !== null) {
+ // union or complex type - string cast
+ $typeName = (string) $type;
+ }
+
+ $resolvable = true;
+ if ($typeName !== null && ! $isBuiltin) {
+ // best-effort check for class/interface
+ $resolvable = $this->container->has($typeName);
+ }
+
+ $constructor['parameters'][] = [
+ 'name' => $param->getName(),
+ 'type' => $typeName,
+ 'allows_null' => $type?->allowsNull() ?? true,
+ 'is_builtin' => $isBuiltin,
+ 'has_default' => $param->isDefaultValueAvailable(),
+ 'resolvable' => $resolvable,
+ ];
+ }
+ }
+ }
+ } catch (\Throwable $e) {
+ // Keep defaults if reflection fails, but include error message for diagnostics.
+ $constructor['error'] = $e->getMessage();
+ }
+ }
+
+ $suggestions = [];
+ if (! $exists) {
+ $suggestions[] = 'Class does not exist - check namespace and autoloading.';
+ } elseif (! $instantiable && ! $hasBinding) {
+ $suggestions[] = 'Class is not instantiable - add a binding from interface/abstract to a concrete implementation.';
+ }
+ if (! $hasBinding && $instantiable && ($constructor['has_constructor'] ?? false)) {
+ foreach ($constructor['parameters'] as $p) {
+ if ($p['type'] && ! $p['is_builtin'] && ! $p['resolvable']) {
+ $suggestions[] = "Add binding for dependency '{$p['type']}' or ensure it is instantiable.";
+ }
+ }
+ }
+
+ $chain = $this->getResolutionChain();
+
+ return [
+ 'class' => $class,
+ 'exists' => $exists,
+ 'instantiable' => $instantiable,
+ 'has_binding' => $hasBinding,
+ 'binding_type' => $bindingType,
+ 'has_instance' => $hasInstance,
+ 'singleton_marked' => $singletonMarked,
+ 'constructor' => $constructor,
+ 'resolution_chain' => $chain,
+ 'counts' => [
+ 'bindings' => count($this->bindings->getAllBindings()),
+ 'singletons' => count($this->instances->getSingletons()),
+ 'instances' => count($this->instances->getInstanceKeys()),
+ ],
+ 'suggestions' => array_values(array_unique($suggestions)),
+ ];
+ }
+}
diff --git a/src/Framework/Smartlinks/Actions/ActionRegistry.php b/src/Framework/MagicLinks/Actions/ActionRegistry.php
similarity index 100%
rename from src/Framework/Smartlinks/Actions/ActionRegistry.php
rename to src/Framework/MagicLinks/Actions/ActionRegistry.php
diff --git a/src/Framework/Smartlinks/Actions/ActionResult.php b/src/Framework/MagicLinks/Actions/ActionResult.php
similarity index 100%
rename from src/Framework/Smartlinks/Actions/ActionResult.php
rename to src/Framework/MagicLinks/Actions/ActionResult.php
diff --git a/src/Framework/Smartlinks/Actions/DefaultActionRegistry.php b/src/Framework/MagicLinks/Actions/DefaultActionRegistry.php
similarity index 100%
rename from src/Framework/Smartlinks/Actions/DefaultActionRegistry.php
rename to src/Framework/MagicLinks/Actions/DefaultActionRegistry.php
diff --git a/src/Framework/Smartlinks/Actions/DocumentAccessAction.php b/src/Framework/MagicLinks/Actions/DocumentAccessAction.php
similarity index 100%
rename from src/Framework/Smartlinks/Actions/DocumentAccessAction.php
rename to src/Framework/MagicLinks/Actions/DocumentAccessAction.php
diff --git a/src/Framework/Smartlinks/Actions/EmailVerificationAction.php b/src/Framework/MagicLinks/Actions/EmailVerificationAction.php
similarity index 100%
rename from src/Framework/Smartlinks/Actions/EmailVerificationAction.php
rename to src/Framework/MagicLinks/Actions/EmailVerificationAction.php
diff --git a/src/Framework/Smartlinks/Actions/GenericDataAccessAction.php b/src/Framework/MagicLinks/Actions/GenericDataAccessAction.php
similarity index 100%
rename from src/Framework/Smartlinks/Actions/GenericDataAccessAction.php
rename to src/Framework/MagicLinks/Actions/GenericDataAccessAction.php
diff --git a/src/Framework/Smartlinks/Actions/SmartlinkAction.php b/src/Framework/MagicLinks/Actions/MagicLinkAction.php
similarity index 100%
rename from src/Framework/Smartlinks/Actions/SmartlinkAction.php
rename to src/Framework/MagicLinks/Actions/MagicLinkAction.php
diff --git a/src/Framework/Smartlinks/Actions/PasswordResetAction.php b/src/Framework/MagicLinks/Actions/PasswordResetAction.php
similarity index 100%
rename from src/Framework/Smartlinks/Actions/PasswordResetAction.php
rename to src/Framework/MagicLinks/Actions/PasswordResetAction.php
diff --git a/src/Framework/Smartlinks/Commands/ExecuteSmartlinkCommand.php b/src/Framework/MagicLinks/Commands/ExecuteMagicLinkCommand.php
similarity index 100%
rename from src/Framework/Smartlinks/Commands/ExecuteSmartlinkCommand.php
rename to src/Framework/MagicLinks/Commands/ExecuteMagicLinkCommand.php
diff --git a/src/Framework/Smartlinks/Commands/ExecuteSmartlinkHandler.php b/src/Framework/MagicLinks/Commands/ExecuteMagicLinkHandler.php
similarity index 100%
rename from src/Framework/Smartlinks/Commands/ExecuteSmartlinkHandler.php
rename to src/Framework/MagicLinks/Commands/ExecuteMagicLinkHandler.php
diff --git a/src/Framework/Smartlinks/Commands/GenerateSmartlinkCommand.php b/src/Framework/MagicLinks/Commands/GenerateMagicLinkCommand.php
similarity index 100%
rename from src/Framework/Smartlinks/Commands/GenerateSmartlinkCommand.php
rename to src/Framework/MagicLinks/Commands/GenerateMagicLinkCommand.php
diff --git a/src/Framework/Smartlinks/Commands/GenerateSmartlinkHandler.php b/src/Framework/MagicLinks/Commands/GenerateMagicLinkHandler.php
similarity index 100%
rename from src/Framework/Smartlinks/Commands/GenerateSmartlinkHandler.php
rename to src/Framework/MagicLinks/Commands/GenerateMagicLinkHandler.php
diff --git a/src/Framework/Smartlinks/SmartlinkData.php b/src/Framework/MagicLinks/MagicLinkData.php
similarity index 100%
rename from src/Framework/Smartlinks/SmartlinkData.php
rename to src/Framework/MagicLinks/MagicLinkData.php
diff --git a/src/Framework/Smartlinks/SmartlinkInitializer.php b/src/Framework/MagicLinks/MagicLinkInitializer.php
similarity index 100%
rename from src/Framework/Smartlinks/SmartlinkInitializer.php
rename to src/Framework/MagicLinks/MagicLinkInitializer.php
diff --git a/src/Framework/Smartlinks/SmartLinkToken.php b/src/Framework/MagicLinks/MagicLinkToken.php
similarity index 100%
rename from src/Framework/Smartlinks/SmartLinkToken.php
rename to src/Framework/MagicLinks/MagicLinkToken.php
diff --git a/src/Framework/Smartlinks/Services/CacheSmartLinkService.php b/src/Framework/MagicLinks/Services/CacheMagicLinkService.php
similarity index 100%
rename from src/Framework/Smartlinks/Services/CacheSmartLinkService.php
rename to src/Framework/MagicLinks/Services/CacheMagicLinkService.php
diff --git a/src/Framework/Smartlinks/Services/InMemorySmartLinkService.php b/src/Framework/MagicLinks/Services/InMemoryMagicLinkService.php
similarity index 100%
rename from src/Framework/Smartlinks/Services/InMemorySmartLinkService.php
rename to src/Framework/MagicLinks/Services/InMemoryMagicLinkService.php
diff --git a/src/Framework/Smartlinks/Services/SmartlinkService.php b/src/Framework/MagicLinks/Services/MagicLinkService.php
similarity index 100%
rename from src/Framework/Smartlinks/Services/SmartlinkService.php
rename to src/Framework/MagicLinks/Services/MagicLinkService.php
diff --git a/src/Framework/Smartlinks/TokenAction.php b/src/Framework/MagicLinks/TokenAction.php
similarity index 100%
rename from src/Framework/Smartlinks/TokenAction.php
rename to src/Framework/MagicLinks/TokenAction.php
diff --git a/src/Framework/Smartlinks/TokenConfig.php b/src/Framework/MagicLinks/TokenConfig.php
similarity index 100%
rename from src/Framework/Smartlinks/TokenConfig.php
rename to src/Framework/MagicLinks/TokenConfig.php
diff --git a/src/Framework/Mcp/Tools/RouteInspectorTool.php b/src/Framework/Mcp/Tools/RouteInspectorTool.php
new file mode 100644
index 00000000..3df01b52
--- /dev/null
+++ b/src/Framework/Mcp/Tools/RouteInspectorTool.php
@@ -0,0 +1,37 @@
+compiledRoutes);
+
+ return $inspector->analyze();
+ } catch (\Throwable $e) {
+ return [
+ 'error' => $e->getMessage(),
+ ];
+ }
+ }
+}
diff --git a/src/Framework/Router/RouteInspector.php b/src/Framework/Router/RouteInspector.php
new file mode 100644
index 00000000..6ff30bdd
--- /dev/null
+++ b/src/Framework/Router/RouteInspector.php
@@ -0,0 +1,211 @@
+
+ */
+ public function analyze(): array
+ {
+ $issues = [];
+ $staticRoutes = $this->compiledRoutes->getStaticRoutes();
+ $namedRoutes = $this->compiledRoutes->getAllNamedRoutes();
+
+ $totalStatic = 0;
+
+ // Track seen routes for potential duplicates per method+subdomain+path
+ $seen = [];
+
+ foreach ($staticRoutes as $method => $subdomains) {
+ foreach ($subdomains as $subdomain => $paths) {
+ foreach ($paths as $path => $route) {
+ $totalStatic++;
+ $key = "{$method}|{$subdomain}|{$path}";
+ $seen[$key] = ($seen[$key] ?? 0) + 1;
+
+ $routeName = $route->name ?? null;
+
+ // Controller existence
+ $controller = $route->controller ?? null;
+ $action = $route->action ?? null;
+
+ if (!is_string($controller) || $controller === '' || !class_exists($controller)) {
+ $issues[] = $this->issue('controller_missing', 'error', $method, $subdomain, $path, $routeName, "Controller class not found or invalid: " . var_export($controller, true));
+ continue; // skip further checks for this route
+ }
+
+ // Action existence and visibility
+ if (!is_string($action) || $action === '') {
+ $issues[] = $this->issue('action_missing', 'error', $method, $subdomain, $path, $routeName, 'Action method not defined or invalid');
+ } else {
+ $refClass = new ReflectionClass($controller);
+ if (!$refClass->hasMethod($action)) {
+ $issues[] = $this->issue('action_missing', 'error', $method, $subdomain, $path, $routeName, "Action method '{$action}' not found in {$controller}");
+ } else {
+ $refMethod = $refClass->getMethod($action);
+ if (!$refMethod->isPublic()) {
+ $issues[] = $this->issue('action_not_public', 'warning', $method, $subdomain, $path, $routeName, "Action method '{$action}' is not public");
+ }
+ // Parameter consistency check (placeholders vs method signature)
+ $this->checkParameterConsistency($issues, $method, $subdomain, $path, $routeName, $route, $refMethod);
+ }
+ }
+ }
+ }
+ }
+
+ // Duplicate path checks (should normally be prevented by map keys, but guard anyway)
+ foreach ($seen as $k => $count) {
+ if ($count > 1) {
+ [$m, $sub, $p] = explode('|', $k, 3);
+ $issues[] = $this->issue('duplicate_route', 'error', $m, $sub, $p, null, "Duplicate route detected for {$m} {$sub} {$p}");
+ }
+ }
+
+ // Named routes basic validation: ensure name -> route is consistent
+ $namedIssues = $this->validateNamedRoutes($namedRoutes);
+ array_push($issues, ...$namedIssues);
+
+ $summary = [
+ 'total_static_routes' => $totalStatic,
+ 'total_named_routes' => count($namedRoutes),
+ 'issue_count' => count($issues),
+ ];
+
+ return [
+ 'summary' => $summary,
+ 'issues' => $issues,
+ 'stats' => $this->compiledRoutes->getStats(),
+ ];
+ }
+
+ /**
+ * Check that route parameters in path are consistent with action method signature
+ */
+ private function checkParameterConsistency(array &$issues, string $method, string $subdomain, string $path, ?string $routeName, object $route, ReflectionMethod $refMethod): void
+ {
+ $pathParams = $this->extractPathParams($path);
+
+ // Try to read expected parameters from route definition; otherwise from reflection
+ $expected = [];
+ if (isset($route->parameters) && is_array($route->parameters)) {
+ // If associative, use keys; if list, use values
+ $keys = array_keys($route->parameters);
+ $expected = array_values(array_filter(
+ count($keys) !== count($route->parameters) ? $route->parameters : $keys,
+ fn($v) => is_string($v) && $v !== ''
+ ));
+ } else {
+ $expected = array_map(
+ static fn(\ReflectionParameter $p) => $p->getName(),
+ $refMethod->getParameters()
+ );
+ }
+
+ // Normalize unique sets
+ $pathSet = array_values(array_unique($pathParams));
+ $expectedSet = array_values(array_unique($expected));
+
+ // Missing placeholders in path for expected parameters
+ $missingInPath = array_values(array_diff($expectedSet, $pathSet));
+ if (!empty($missingInPath)) {
+ $issues[] = $this->issue(
+ 'param_mismatch',
+ 'warning',
+ $method,
+ $subdomain,
+ $path,
+ $routeName,
+ 'Expected parameters not present in path: ' . implode(', ', $missingInPath)
+ );
+ }
+
+ // Extra placeholders not expected by the action
+ $extraInPath = array_values(array_diff($pathSet, $expectedSet));
+ if (!empty($extraInPath)) {
+ $issues[] = $this->issue(
+ 'param_mismatch',
+ 'warning',
+ $method,
+ $subdomain,
+ $path,
+ $routeName,
+ 'Path has placeholders not expected by action: ' . implode(', ', $extraInPath)
+ );
+ }
+ }
+
+ /**
+ * Validate named routes (basic structural checks)
+ * @param array $namedRoutes
+ * @return array>
+ */
+ private function validateNamedRoutes(array $namedRoutes): array
+ {
+ $issues = [];
+ foreach ($namedRoutes as $name => $route) {
+ // Minimal: ensure a path exists
+ $path = $route->path ?? null;
+ if (!is_string($path) || $path === '') {
+ $issues[] = [
+ 'type' => 'invalid_named_route',
+ 'severity' => 'error',
+ 'route_name' => $name,
+ 'message' => 'Named route has no valid path',
+ ];
+ }
+ }
+
+ return $issues;
+ }
+
+ /**
+ * Extract placeholders from route path like /users/{id}/posts/{slug}
+ * @return array
+ */
+ private function extractPathParams(string $path): array
+ {
+ $matches = [];
+ preg_match_all('/\{([a-zA-Z_][a-zA-Z0-9_]*)\}/', $path, $matches);
+
+ /** @var array $params */
+ $params = $matches[1] ?? [];
+
+ return $params;
+ }
+
+ /**
+ * Build a standardized issue array
+ * @return array
+ */
+ private function issue(string $type, string $severity, string $method, string $subdomain, string $path, ?string $name, string $message): array
+ {
+ return [
+ 'type' => $type,
+ 'severity' => $severity,
+ 'route' => [
+ 'method' => $method,
+ 'subdomain' => $subdomain,
+ 'path' => $path,
+ 'name' => $name,
+ ],
+ 'message' => $message,
+ ];
+ }
+}