# System Commands Refactoring Specification v2

## Problem Statement

`kollabor/commands/system_commands.py` is a 3508-line monolithic file that violates single responsibility principle and has tech debt (backdoor `plugin.app = self` injection).

## Prerequisites: Service Registry Pattern

Before refactoring, implement a service registry on EventBus to eliminate backdoor injection.

### EventBus Service Registry

```python
# kollabor/events/bus.py - ADD to EventBus class

def __init__(self):
    self._services: Dict[str, Any] = {}
    # ... existing code

def register_service(self, name: str, service: Any) -> None:
    """Register a service for lookup by other components."""
    self._services[name] = service
    self.logger.debug(f"Service registered: {name}")

def get_service(self, name: str) -> Optional[Any]:
    """Get a registered service by name."""
    return self._services.get(name)

def has_service(self, name: str) -> bool:
    """Check if a service is registered."""
    return name in self._services
```

### Application Registration

```python
# kollabor/application.py - AFTER creating each service

# After permission_manager creation
self.event_bus.register_service("permission_manager", self.permission_manager)

# After llm_service creation
self.event_bus.register_service("llm_service", self.llm_service)

# After profile_manager creation
self.event_bus.register_service("profile_manager", self.profile_manager)

# After agent_manager creation
self.event_bus.register_service("agent_manager", self.agent_manager)

# REMOVE: self.system_commands.app = self  (the backdoor)
```

## Target Structure

```
kollabor/commands/
  system_commands/
    __init__.py                 # exports SystemCommandsPlugin
    plugin.py                   # facade (registration, routing)
    base.py                     # BaseCommandHandler
    handlers/
      __init__.py
      profile.py                # ProfileCommandHandler
      agent.py                  # AgentCommandHandler
      skill.py                  # SkillCommandHandler
      model.py                  # ModelCommandHandler
      directory.py              # DirectoryCommandHandler
      system.py                 # SystemCommandHandler
```

## Data Flow Contract

**CRITICAL: Handlers MUTATE the data dict in-place, not return new dicts.**

```python
async def handle_modal_action(self, action: str, data: Dict) -> Dict:
    """
    Contract:
    - MUTATE data dict in-place
    - Set data["display_messages"] for user feedback
    - Set data["show_modal"] to open another modal
    - Set data["close_modal"] = True to close current modal
    - Return the same data dict (for chaining)
    """
    data["display_messages"] = [("system", "message", {})]
    return data
```

## Cross-Handler Communication

Handlers that need to trigger cross-domain effects use the event bus:

```python
# Profile handler after switching profiles
llm_service = self.event_bus.get_service("llm_service")
if llm_service and hasattr(llm_service, 'api_service'):
    asyncio.create_task(llm_service.api_service.reinitialize_provider(profile))
    asyncio.create_task(llm_service._load_native_tools())
```

## BaseCommandHandler Design

```python
# kollabor/commands/system_commands/base.py

import logging
from typing import Any, Dict, Optional, Set

class BaseCommandHandler:
    """Base class for domain command handlers."""

    # Subclasses define which actions they handle
    MODAL_ACTIONS: Set[str] = set()

    def __init__(
        self,
        command_registry,
        event_bus,
        config_manager,
    ):
        """
        Minimal constructor - services accessed via event_bus.get_service().

        Args:
            command_registry: For registering slash commands
            event_bus: For hooks AND service lookup
            config_manager: For configuration access
        """
        self.command_registry = command_registry
        self.event_bus = event_bus
        self.config_manager = config_manager
        self.logger = logging.getLogger(self.__class__.__name__)

    # Service accessors (lazy lookup)
    @property
    def llm_service(self):
        return self.event_bus.get_service("llm_service")

    @property
    def profile_manager(self):
        return self.event_bus.get_service("profile_manager")

    @property
    def agent_manager(self):
        return self.event_bus.get_service("agent_manager")

    @property
    def permission_manager(self):
        return self.event_bus.get_service("permission_manager")

    def register_commands(self) -> None:
        """Override to register domain commands."""
        raise NotImplementedError

    async def handle_modal_action(self, action: str, data: Dict) -> Dict:
        """Override to handle domain-specific modal actions. MUTATE data in-place."""
        return data
```

## Handler Implementations

### ProfileCommandHandler

```python
# kollabor/commands/system_commands/handlers/profile.py

class ProfileCommandHandler(BaseCommandHandler):
    """Handles /profile command and profile modal actions."""

    MODAL_ACTIONS = {
        "select_profile",
        "edit_profile_prompt", "edit_profile_submit",
        "delete_profile_prompt", "delete_profile_confirm",
        "create_profile_prompt", "create_profile_submit",
        "save_profile_to_config",
    }

    def register_commands(self) -> None:
        """Register /profile command."""
        # ... command registration

    async def handle_profile(self, command) -> CommandResult:
        """Handle /profile slash command."""
        # ... implementation

    async def handle_modal_action(self, action: str, data: Dict) -> Dict:
        """Handle profile modal actions. MUTATE data in-place."""
        if action == "select_profile":
            return await self._handle_select_profile(data)
        elif action == "edit_profile_prompt":
            return await self._handle_edit_profile_prompt(data)
        # ... etc
        return data

    # Modal definitions
    def _get_profiles_modal_definition(self, skip_reload=False) -> Dict:
        """Build profile list modal."""
        # ...

    # Utilities
    def _mask_api_key_for_display(self, api_key: str) -> str:
        """Mask API key for display."""
        # ...
```

### SystemCommandHandler (includes permissions)

```python
# kollabor/commands/system_commands/handlers/system.py

class SystemCommandHandler(BaseCommandHandler):
    """Handles /help, /config, /status, /version, /permissions."""

    MODAL_ACTIONS = set()  # No modal actions for system commands

    async def handle_permissions(self, command) -> CommandResult:
        """Handle /permissions command."""
        # Uses self.permission_manager (via property on base class)
        if not self.permission_manager:
            return CommandResult(
                success=False,
                message="Permission system not initialized",
                display_type="error"
            )
        # ... rest of implementation
```

### DirectoryCommandHandler (has shared state)

```python
# kollabor/commands/system_commands/handlers/directory.py

class DirectoryCommandHandler(BaseCommandHandler):
    """Handles /cd command."""

    MODAL_ACTIONS = {"select_directory"}

    def __init__(self, command_registry, event_bus, config_manager):
        super().__init__(command_registry, event_bus, config_manager)
        self._previous_directory: Optional[str] = None  # Handler owns its state
```

## Plugin Facade

```python
# kollabor/commands/system_commands/plugin.py

class SystemCommandsPlugin:
    """Facade that delegates to domain handlers."""

    def __init__(self, command_registry, event_bus, config_manager, **kwargs):
        """
        Note: llm_service, profile_manager, agent_manager passed in kwargs
        for backward compat but NOT used - handlers use event_bus.get_service()
        """
        self.name = "system"
        self.event_bus = event_bus
        self.logger = logging.getLogger(__name__)

        # Create handlers (minimal deps - they use service registry)
        self.handlers = [
            ProfileCommandHandler(command_registry, event_bus, config_manager),
            AgentCommandHandler(command_registry, event_bus, config_manager),
            SkillCommandHandler(command_registry, event_bus, config_manager),
            ModelCommandHandler(command_registry, event_bus, config_manager),
            DirectoryCommandHandler(command_registry, event_bus, config_manager),
            SystemCommandHandler(command_registry, event_bus, config_manager),
        ]

        # Build action -> handler routing table
        self._action_handlers: Dict[str, BaseCommandHandler] = {}
        for handler in self.handlers:
            for action in handler.MODAL_ACTIONS:
                if action in self._action_handlers:
                    self.logger.warning(f"Action collision: {action}")
                self._action_handlers[action] = handler

    def register_commands(self) -> None:
        """Register all commands from all handlers."""
        for handler in self.handlers:
            handler.register_commands()
        self.logger.info("System commands registered")

    async def register_hooks(self) -> None:
        """Register modal command hook."""
        from ..events.models import Hook, EventType
        hook = Hook(
            name="system_modal_command",
            plugin_name="system",
            event_type=EventType.MODAL_COMMAND_SELECTED,
            priority=10,
            callback=self._handle_modal_command
        )
        await self.event_bus.register_hook(hook)
        self.logger.info("System modal command hook registered")

    async def _handle_modal_command(self, data: Dict, event) -> Dict:
        """Route modal actions to appropriate handler."""
        action = data.get("command", {}).get("action")

        handler = self._action_handlers.get(action)
        if handler:
            return await handler.handle_modal_action(action, data)

        self.logger.warning(f"Unknown modal action: {action}")
        return data
```

## Package __init__.py

```python
# kollabor/commands/system_commands/__init__.py

from .plugin import SystemCommandsPlugin

__all__ = ["SystemCommandsPlugin"]
```

## Implementation Phases

### Phase 0: Service Registry (do first)
1. Add service registry methods to EventBus
2. Register services in application.py
3. Remove `plugin.app = self` backdoor
4. Verify app still works

### Phase A: Foundation
1. Create `kollabor/commands/system_commands/` directory
2. Create `__init__.py` with export
3. Create `base.py` with BaseCommandHandler

### Phase B: Extract Handlers (one at a time, not parallel)
Order by complexity (simplest first):
1. Extract DirectoryCommandHandler (~150 lines)
2. Extract ModelCommandHandler (~150 lines)
3. Extract SystemCommandHandler (~350 lines)
4. Extract ProfileCommandHandler (~450 lines)
5. Extract AgentCommandHandler (~400 lines)
6. Extract SkillCommandHandler (~500 lines)

**After each extraction:**
- Run tests
- Test the command manually
- Commit

### Phase C: Create Facade
1. Create `plugin.py` with SystemCommandsPlugin
2. Wire up all handlers
3. Test action routing

### Phase D: Integration
1. Backup: `mv system_commands.py system_commands.py.backup`
2. Update imports (should be unchanged due to __init__.py)
3. Run full test suite
4. If pass: `rm system_commands.py.backup`
5. If fail: `mv system_commands.py.backup system_commands.py`

### Phase E: Cleanup
1. Update tests to use new structure where needed
2. Remove any remaining dead code
3. Final verification

## Modal Action Inventory

### Profile Actions (7)
- select_profile
- edit_profile_prompt, edit_profile_submit
- delete_profile_prompt, delete_profile_confirm
- create_profile_prompt, create_profile_submit
- save_profile_to_config

### Agent Actions (8)
- select_agent, clear_agent
- edit_agent_prompt, edit_agent_submit
- delete_agent_prompt, delete_agent_confirm
- create_agent_prompt, create_agent_submit
- toggle_project_default, toggle_global_default

### Skill Actions (10)
- load_skill, unload_skill
- toggle_default_skill, toggle_global_default_skill
- edit_skill_prompt, edit_skill_submit
- delete_skill_prompt, delete_skill_confirm
- create_skill_prompt, create_skill_submit

### Model Actions (1)
- select_model

### Directory Actions (1)
- select_directory

**Total: 27 actions**

## Success Criteria

- [ ] Service registry implemented and working
- [ ] No `plugin.app = self` backdoor
- [ ] All existing tests pass
- [ ] All commands work identically
- [ ] All modal actions work identically
- [ ] No file exceeds 600 lines
- [ ] Each handler independently testable

## Verification

```bash
# Phase 0: Service registry
python -c "from kollabor.events import EventBus; b = EventBus(); b.register_service('test', 1); assert b.get_service('test') == 1"

# After full refactor
python -m py_compile kollabor/commands/system_commands/__init__.py
python -m py_compile kollabor/commands/system_commands/plugin.py
python -m py_compile kollabor/commands/system_commands/base.py
python -m py_compile kollabor/commands/system_commands/handlers/*.py

python -c "from kollabor.commands.system_commands import SystemCommandsPlugin; print('Import OK')"

# Run tests
python tests/run_tests.py

# Manual test each command
python main.py
# /help, /profile, /agent, /skill, /model, /cd, /status, /version, /permissions
```
