groups = $this->loadGroups(); $this->workflows = $this->loadWorkflows(); $this->commandGroupMapping = $this->buildCommandGroupMapping(); } /** * Get all command groups */ public function getGroups(): array { return $this->groups; } /** * Get group by name */ public function getGroup(string $name): ?array { return $this->groups[$name] ?? null; } /** * Get all workflows */ public function getWorkflows(): array { return $this->workflows; } /** * Get workflow by name */ public function getWorkflow(string $name): ?array { return $this->workflows[$name] ?? null; } /** * Get command group mapping */ public function getCommandGroupMapping(): array { return $this->commandGroupMapping; } /** * Get commands for a group */ public function getCommandsForGroup(string $groupName): array { $commands = []; $allCommands = $this->discoveryRegistry->attributes()->get(ConsoleCommand::class); foreach ($allCommands as $commandData) { $commandGroup = $this->getCommandGroup($commandData); if ($commandGroup === $groupName) { $commands[] = $commandData; } } return $commands; } /** * Get organized command structure */ public function getOrganizedCommands(): array { $organized = []; $allCommands = $this->discoveryRegistry->attributes()->get(ConsoleCommand::class); // Group commands by their groups foreach ($allCommands as $commandData) { $groupName = $this->getCommandGroup($commandData); $group = $this->getGroup($groupName); if (! isset($organized[$groupName])) { $organized[$groupName] = [ 'name' => $groupName, 'description' => $group['description'] ?? '', 'icon' => $group['icon'] ?? $this->inferIcon($groupName), 'priority' => $group['priority'] ?? 0, 'commands' => [], ]; } $organized[$groupName]['commands'][] = $commandData; } // Sort by priority uasort($organized, fn ($a, $b) => $b['priority'] <=> $a['priority']); // Convert to numeric array for TUI navigation (TuiState expects numeric indices) return array_values($organized); } /** * Check if command has explicit group */ public function hasExplicitGroup(array $commandData): bool { return $this->getExplicitGroup($commandData) !== null; } /** * Load groups from discovery */ private function loadGroups(): array { $groups = []; $results = $this->discoveryRegistry->attributes()->get(CommandGroup::class); foreach ($results as $result) { $group = $result->createAttributeInstance(); if ($group === null) { continue; } $groups[$group->name] = [ 'name' => $group->name, 'description' => $group->description, 'icon' => $group->icon, 'priority' => $group->priority, 'dependencies' => $group->dependencies, 'tags' => $group->tags, 'hidden' => $group->hidden, 'class' => $result->className->getFullyQualified(), ]; } return $groups; } /** * Load workflows from discovery */ private function loadWorkflows(): array { $workflows = []; $results = $this->discoveryRegistry->attributes()->get(CommandWorkflow::class); foreach ($results as $result) { $workflow = $result->createAttributeInstance(); if ($workflow === null) { continue; } $workflows[$workflow->name] = [ 'name' => $workflow->name, 'description' => $workflow->description, 'steps' => array_map( fn ($step) => is_array($step) ? WorkflowStep::fromArray($step) : $step, $workflow->steps ), 'prerequisites' => $workflow->prerequisites, 'stopOnError' => $workflow->stopOnError, 'rollbackSteps' => $workflow->rollbackSteps, 'timeoutSeconds' => $workflow->timeoutSeconds, 'environment' => $workflow->environment, 'class' => $result->className->getFullyQualified(), 'method' => $result->methodName?->toString(), ]; } return $workflows; } /** * Build command to group mapping */ private function buildCommandGroupMapping(): array { $mapping = []; $allCommands = $this->discoveryRegistry->attributes()->get(ConsoleCommand::class); foreach ($allCommands as $commandData) { $attribute = $commandData->createAttributeInstance(); if ($attribute === null) { continue; } $commandName = $attribute->name; $groupName = $this->getCommandGroup($commandData); $mapping[$commandName] = $groupName; } return $mapping; } /** * Get command group (explicit or inferred) */ private function getCommandGroup($commandData): string { // Check for explicit group attribute $explicitGroup = $this->getExplicitGroup($commandData); if ($explicitGroup !== null) { return $explicitGroup; } // Fall back to inference - get command name from attribute instance $attribute = $commandData->createAttributeInstance(); if ($attribute === null) { return 'General'; } return $this->inferCategory($attribute->name); } /** * Get explicit group from command */ private function getExplicitGroup($commandData): ?string { $class = $commandData->className->getFullyQualified(); $method = $commandData->methodName?->toString(); // Check method-level group attribute if ($method) { $reflection = new \ReflectionMethod($class, $method); $attributes = $reflection->getAttributes(CommandGroup::class); if (! empty($attributes)) { return $attributes[0]->newInstance()->name; } } // Check class-level group attribute $reflection = new \ReflectionClass($class); $attributes = $reflection->getAttributes(CommandGroup::class); if (! empty($attributes)) { return $attributes[0]->newInstance()->name; } return null; } /** * Infer category from command name (fallback) */ private function inferCategory(string $commandName): string { if (str_contains($commandName, ':')) { return ucfirst(explode(':', $commandName)[0]); } return match (true) { str_starts_with($commandName, 'test') => 'Testing', str_starts_with($commandName, 'demo') => 'Demo', str_starts_with($commandName, 'make') => 'Generator', str_starts_with($commandName, 'db') => 'Database', str_starts_with($commandName, 'mcp') => 'MCP', default => 'General' }; } /** * Infer icon for group */ private function inferIcon(string $groupName): string { return match (strtolower($groupName)) { 'testing' => '๐Ÿงช', 'demo' => '๐ŸŽฎ', 'generator' => 'โš™๏ธ', 'database' => '๐Ÿ—„๏ธ', 'mcp' => '๐Ÿค–', 'workflow' => '๐Ÿ”„', 'system' => 'โš™๏ธ', 'security' => '๐Ÿ›ก๏ธ', 'general' => '๐Ÿ“‚', default => '๐Ÿ“' }; } }