- Move 12 markdown files from root to docs/ subdirectories - Organize documentation by category: • docs/troubleshooting/ (1 file) - Technical troubleshooting guides • docs/deployment/ (4 files) - Deployment and security documentation • docs/guides/ (3 files) - Feature-specific guides • docs/planning/ (4 files) - Planning and improvement proposals Root directory cleanup: - Reduced from 16 to 4 markdown files in root - Only essential project files remain: • CLAUDE.md (AI instructions) • README.md (Main project readme) • CLEANUP_PLAN.md (Current cleanup plan) • SRC_STRUCTURE_IMPROVEMENTS.md (Structure improvements) This improves: ✅ Documentation discoverability ✅ Logical organization by purpose ✅ Clean root directory ✅ Better maintainability
294 lines
8.3 KiB
PHP
294 lines
8.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Console;
|
|
|
|
use App\Framework\Discovery\Results\DiscoveryRegistry;
|
|
|
|
/**
|
|
* Registry for command groups and workflows
|
|
*/
|
|
final readonly class CommandGroupRegistry
|
|
{
|
|
private array $groups;
|
|
|
|
private array $workflows;
|
|
|
|
private array $commandGroupMapping;
|
|
|
|
public function __construct(
|
|
private DiscoveryRegistry $discoveryRegistry
|
|
) {
|
|
$this->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 => '📁'
|
|
};
|
|
}
|
|
}
|