--- sessionId: session-260910-075746-nw8q --- # Requirements ### Overview & Goals Refactor `CommandProcessor` to break its tightly coupled `dispatch()` method into composable, extensible pieces so that new preconditions, permission strategies, and result handlers can be added without modifying the processor itself. ### Scope **In Scope** - Decompose `CommandProcessor.dispatch()` into a pipeline of single-responsibility components - Eliminate `instanceof` checks for `InteractionContext` / `SlashCommandContext` inside the processor - Preserve all existing behavior and keep the existing test suite green **Out of Scope** - Changes to the `Command` interface or individual command implementations - Changes to `CommandContext` subclass hierarchy - Changes to `CommandContainer`, `CommandRegistry`, or `CommandExecutionResult` # Technical Design ### Current Implementation `CommandProcessor.dispatch()` (~80 lines, `CommandProcessor.java:48-129`) performs **five concerns** sequentially: 1. **Command lookup** — resolve command from registry 2. **Precondition checks** — guild-only, slash-only, via `instanceof` checks against `InteractionContext` 3. **Permission check** — `canExecute()` with `instanceof SlashCommandContext` 4. **Usage/argument validation** — `checkUsage()` + `checkOptions()`, again branching on `instanceof InteractionContext` 5. **Execution & result handling** — execute command, handle TASK/ERROR/WARN/NOACTION results, catch exceptions The `instanceof` checks (lines 66, 197, 220) tightly couple the processor to specific context subtypes, making it impossible to add new context types without modifying the processor. ### Key Decisions 1. **Introduce a `CommandPrecondition` interface** — each check (guild-only, slash-only, permissions) becomes a standalone, testable precondition that can be composed into an ordered list. This replaces the inline if-chains and eliminates instanceof checks from the processor. 2. **Move context-type-specific logic into the context itself** — instead of `instanceof` checks in the processor, add polymorphic methods to `CommandContext` (e.g., `supportsSlashOnly()`, `canSkipPermissionCheck()`, `validateOptions()`). Each context subclass overrides as needed. 3. **Extract result handling into a `ResultHandler`** — the if-chain for TASK/ERROR/WARN/NOACTION becomes a dedicated component, making it easy to add new result types. 4. **Keep changes incremental** — refactor in place without changing the public API of `CommandProcessor` (constructor + `dispatch()` remain the same). The pipeline is internal. ### Proposed Changes #### 1. `CommandPrecondition` interface ```java // New file: core/command/precondition/CommandPrecondition.java public interface CommandPrecondition { /** Returns null if passed, or an error message to reply with. */ String check(CommandContext context, Command command); } ``` Concrete implementations: - `GuildOnlyPrecondition` — replaces lines 60-64 - `SlashCommandOnlyPrecondition` — replaces lines 66-69, uses `context.supportsSlashOnly()` instead of instanceof - `PermissionPrecondition` — replaces `canExecute()` (lines 215-229), uses `context.canSkipPermissionCheck()` instead of instanceof #### 2. Polymorphic methods on `CommandContext` Add to `CommandContext`: - `boolean isInteraction()` — default `false`, overridden to `true` in `InteractionContext` - `boolean canSkipPermissionCheck()` — default `false`, overridden in `SlashCommandContext` This eliminates all `instanceof` checks from the processor. #### 3. `CommandResultHandler` ```java // New file: core/command/CommandResultHandler.java public class CommandResultHandler { void handle(CommandContext context, CommandExecutionResult result, TaskManager taskManager); } ``` Encapsulates the TASK/ERROR/WARN/NOACTION/message reply logic from lines 100-120. #### 4. Refactored `CommandProcessor.dispatch()` The method becomes: ```java public void dispatch() { CommandContainer container = resolveCommand(); if (container == null) return; Command command = container.getCommand(); context.setCommand(command); configureSentry(command); for (CommandPrecondition precondition : preconditions) { String error = precondition.check(context, command); if (error != null) { context.replyEphemeral(error); return; } } if (!usageValidator.validate(context, container)) return; try { CommandExecutionResult result = command.execute(context); resultHandler.handle(context, result, taskManager); } catch (...) { /* existing error handling */ } } ``` ### Architecture Diagram ```mermaid graph LR D[dispatch] --> R[resolveCommand] R --> P[Preconditions Loop] P --> GuildOnly P --> SlashOnly P --> Permission P --> UV[UsageValidator] UV --> E[command.execute] E --> RH[ResultHandler] ``` ### File Structure | File | Action | |------|--------| | `core/command/precondition/CommandPrecondition.java` | **New** — interface | | `core/command/precondition/GuildOnlyPrecondition.java` | **New** | | `core/command/precondition/SlashCommandOnlyPrecondition.java` | **New** | | `core/command/precondition/PermissionPrecondition.java` | **New** | | `core/command/CommandResultHandler.java` | **New** | | `core/command/UsageValidator.java` | **New** — extracted from `checkUsage()`/`checkOptions()` | | `core/command/CommandProcessor.java` | **Modified** — delegate to above components | | `core/context/CommandContext.java` | **Modified** — add `isInteraction()`, `canSkipPermissionCheck()` | | `core/context/InteractionContext.java` | **Modified** — override `isInteraction()` | | `context/command/SlashCommandContext.java` | **Modified** — override `canSkipPermissionCheck()` | | `CommandProcessorTest.java` | **Modified** — update construction, add unit tests for new components | ### Risks - **Behavioral regression** — the refactor must preserve exact reply messages and ordering. Mitigated by the existing 1000+ line test suite. - **Precondition ordering matters** — guild-only must run before permission check. The list order is explicit and documented. # Testing ### Validation Approach - All existing `CommandProcessorTest` tests must pass unchanged (behavior preservation). - New unit tests for each precondition, the result handler, and the usage validator in isolation. ### Key Scenarios - Preconditions short-circuit in order (guild-only before permissions) - Custom precondition can be added to the list and is invoked - ResultHandler correctly dispatches TASK to TaskManager, logs ERROR/WARN - UsageValidator handles all subcommand/group/option combinations ### Edge Cases - Null command from container - Empty precondition list (all checks pass) - InteractionContext skips option validation in UsageValidator # Delivery Steps ### ✓ Step 1: Add polymorphic methods to CommandContext hierarchy CommandContext subclasses expose their capabilities without requiring instanceof checks. - Add `isInteraction()` (default `false`) and `canSkipPermissionCheck()` (default `false`) to `CommandContext.java` - Override `isInteraction()` to return `true` in `InteractionContext.java` - Override `canSkipPermissionCheck()` to return `true` in `SlashCommandContext.java` - Verify existing tests still compile and pass ### ✓ Step 2: Extract preconditions into CommandPrecondition interface and implementations Precondition checks become standalone, composable components. - Create `CommandPrecondition` interface in `core/command/precondition/` - Implement `GuildOnlyPrecondition` — checks `command.isGuildOnly()` and `context.isFromGuild()` - Implement `SlashCommandOnlyPrecondition` — checks `command.isSlashCommandOnly()` and `context.isInteraction()` - Implement `PermissionPrecondition` — replaces `canExecute()`, uses `context.canSkipPermissionCheck()` instead of instanceof - Add unit tests for each precondition ### ✓ Step 3: Extract UsageValidator and CommandResultHandler Usage validation and result handling become dedicated classes. - Extract `checkUsage()` and `checkOptions()` into `UsageValidator.java`, replacing `instanceof InteractionContext` with `context.isInteraction()` - Extract result-handling if-chain (TASK/ERROR/WARN/NOACTION + reply) into `CommandResultHandler.java` - Add unit tests for both classes ### ✓ Step 4: Refactor CommandProcessor to use the new components CommandProcessor.dispatch() becomes a thin orchestrator delegating to the extracted components. - Replace inline precondition checks with a `List` loop - Replace `checkUsage()`/`checkOptions()` calls with `UsageValidator` - Replace result-handling if-chain with `CommandResultHandler` - Remove the now-unused private methods (`canExecute`, `checkUsage`, `checkOptions`) - Keep constructor backward-compatible or provide a builder/factory for custom precondition lists - Run full `CommandProcessorTest` suite to confirm behavioral equivalence