|
| 1 | +# Dependency Injection Refactoring Plan |
| 2 | + |
| 3 | +## Issue We're Fixing |
| 4 | + |
| 5 | +### Current Problems |
| 6 | +1. **Runtime Type Mismatches**: The `wrapHandlerWithExecutor` in `index.ts` uses string matching to determine which dependencies to inject, leading to runtime errors when the wrong number/type of parameters are passed. |
| 7 | +2. **Brittle String Matching**: Checking `handlerString.includes('fileSystemExecutor')` is error-prone and lacks type safety. |
| 8 | +3. **Complex Conditional Logic**: Overlapping conditions cause handlers to receive wrong parameters (e.g., CommandExecutor passed as FileSystemExecutor). |
| 9 | +4. **Testing Complexity**: Current approach mixes production and testing concerns in the same handler signature. |
| 10 | + |
| 11 | +### Root Cause |
| 12 | +MCP protocol requires handlers to accept only `(args: Record<string, unknown>)`, but we need dependency injection for testing. The current solution of adding optional parameters with defaults has proven unreliable. |
| 13 | + |
| 14 | +## Proposed Solution: Separation of Concerns Pattern |
| 15 | + |
| 16 | +### Pattern Overview |
| 17 | +```typescript |
| 18 | +// 1. Pure business logic function (exported for testing) |
| 19 | +export async function toolNameLogic( |
| 20 | + params: ParamType, |
| 21 | + executor: CommandExecutor, |
| 22 | + fs?: FileSystemExecutor |
| 23 | +): Promise<ToolResponse> { |
| 24 | + // All actual implementation here |
| 25 | +} |
| 26 | + |
| 27 | +// 2. MCP handler (thin wrapper) |
| 28 | +export default { |
| 29 | + name: 'tool_name', |
| 30 | + description: '...', |
| 31 | + schema: { ... }, |
| 32 | + handler: async (args: Record<string, unknown>) => { |
| 33 | + return toolNameLogic(args, getDefaultCommandExecutor(), getDefaultFileSystemExecutor()); |
| 34 | + } |
| 35 | +}; |
| 36 | + |
| 37 | +// 3. Tests call logic directly |
| 38 | +test('tool behavior', async () => { |
| 39 | + const result = await toolNameLogic(params, mockExecutor, mockFs); |
| 40 | + expect(result).toEqual(...); |
| 41 | +}); |
| 42 | +``` |
| 43 | + |
| 44 | +### Benefits |
| 45 | +- **Zero Magic**: No string matching, no wrappers |
| 46 | +- **100% Type Safe**: TypeScript validates all parameter types at compile time |
| 47 | +- **Clean Separation**: MCP concerns separated from business logic |
| 48 | +- **Direct Testing**: Tests call logic functions directly with mocks |
| 49 | +- **Backwards Compatible**: MCP still sees the same handler interface |
| 50 | + |
| 51 | +## Implementation Process |
| 52 | + |
| 53 | +### Phase 0: Validation Script Setup |
| 54 | +We've created `scripts/check-separation-of-concerns.js` that checks for: |
| 55 | +- Exported logic functions (e.g., `toolNameLogic`) |
| 56 | +- Handler functions without optional parameters |
| 57 | +- Handlers that are thin wrappers (≤5 lines) |
| 58 | + |
| 59 | +Run: `node scripts/check-separation-of-concerns.js` to see current violations. |
| 60 | + |
| 61 | +### Phase 1: Setup Infrastructure |
| 62 | +1. Remove `wrapHandlerWithExecutor` from `index.ts` |
| 63 | +2. Update plugin loading to use handlers directly |
| 64 | +3. Ensure all imports/exports work correctly |
| 65 | + |
| 66 | +### Phase 2: Tool Refactoring |
| 67 | + |
| 68 | +#### Refactoring Steps Per Tool |
| 69 | +1. Extract handler logic into a separate `{toolName}Logic` function |
| 70 | +2. Add proper TypeScript types for parameters |
| 71 | +3. Update handler to be a thin wrapper calling the logic function |
| 72 | +4. Update tests to call logic function directly |
| 73 | +5. Remove mock executor creation from tests (use imported ones) |
| 74 | +6. **Use targeted serial edits** - Never rewrite entire files |
| 75 | + - Use `Edit` tool with specific old_string/new_string |
| 76 | + - Make incremental changes to preserve existing code structure |
| 77 | + - Avoid using `Write` tool on existing files |
| 78 | +7. Verify tool works via MCP |
| 79 | +8. Run tests and ensure they pass |
| 80 | + |
| 81 | +### Phase 3: Categories of Tools |
| 82 | + |
| 83 | +#### Category A: CommandExecutor Only (~60 tools) |
| 84 | +- Pattern: `toolLogic(params, executor)` |
| 85 | +- Examples: build tools, simulator tools, device tools |
| 86 | + |
| 87 | +#### Category B: FileSystemExecutor Only (~10 tools) |
| 88 | +- Pattern: `toolLogic(params, fs)` |
| 89 | +- Examples: discover_projs, file operations |
| 90 | + |
| 91 | +#### Category C: Both Executors (~5 tools) |
| 92 | +- Pattern: `toolLogic(params, executor, fs)` |
| 93 | +- Examples: screenshot, scaffold tools |
| 94 | + |
| 95 | +#### Category D: Special Dependencies (~5 tools) |
| 96 | +- ProcessManager: Swift package tools |
| 97 | +- SyncExecutor: Bundle ID extraction tools |
| 98 | +- MockUtilities: Diagnostic tool |
| 99 | + |
| 100 | +## Execution Strategy |
| 101 | + |
| 102 | +### Main Agent Orchestration |
| 103 | +1. **Parallel Execution**: Launch 5-10 sub-agents simultaneously |
| 104 | +2. **File Assignment**: Each sub-agent assigned exactly ONE tool file |
| 105 | +3. **Independent Work**: Sub-agents work without conflicts |
| 106 | +4. **Atomic Commits**: Commit each file individually after validation |
| 107 | + |
| 108 | +### Sub-Agent Instructions |
| 109 | +``` |
| 110 | +You are Sub-Agent [N]. Your task: |
| 111 | +1. Refactor ONE tool: [tool_name.ts] |
| 112 | +2. Follow the Separation of Concerns pattern exactly |
| 113 | +3. Extract logic into exported {toolName}Logic function |
| 114 | +4. Update tests to call logic directly |
| 115 | +5. Run: npm test -- path/to/your/tests |
| 116 | +6. Report completion to main agent |
| 117 | +``` |
| 118 | + |
| 119 | +### Validation Per File |
| 120 | +1. **Separation Check**: `node scripts/check-separation-of-concerns.js` (ensure tool no longer shows violations) |
| 121 | +2. **Test Execution**: `npm test -- src/plugins/[category]/__tests__/[tool].test.ts` |
| 122 | +3. **Build Check**: `npm run build` (ensure no compilation errors) |
| 123 | +4. **MCP Test**: Use `mcp__XcodeBuildMCP__call_tool` to verify tool works |
| 124 | +5. **Commit**: Only after all validations pass |
| 125 | + |
| 126 | +### Commit Strategy |
| 127 | +```bash |
| 128 | +# After each tool is validated |
| 129 | +git add src/plugins/[category]/[tool].ts |
| 130 | +git add src/plugins/[category]/__tests__/[tool].test.ts |
| 131 | +git commit -m "refactor: apply separation of concerns to [tool] tool" |
| 132 | +``` |
| 133 | + |
| 134 | +## Progress Tracking |
| 135 | + |
| 136 | +### Tools to Refactor (Total: ~84) |
| 137 | + |
| 138 | +#### High Priority (Fix first) |
| 139 | +- [ ] discover_projs (known issue with executor) |
| 140 | +- [ ] diagnostic (mock utilities issue) |
| 141 | +- [ ] screenshot (both executors) |
| 142 | +- [ ] stop_device_log_cap (schema fix applied) |
| 143 | +- [ ] stop_sim_log_cap (schema fix applied) |
| 144 | +- [ ] start_device_log_cap (schema fix applied) |
| 145 | +- [ ] get_app_bundle_id (schema fix applied) |
| 146 | +- [ ] get_mac_bundle_id (schema fix applied) |
| 147 | + |
| 148 | +#### Device Project Tools (3) |
| 149 | +- [ ] build_dev_proj |
| 150 | +- [ ] get_device_app_path_proj |
| 151 | +- [ ] test_device_proj |
| 152 | + |
| 153 | +#### Device Shared Tools (4) |
| 154 | +- [ ] install_app_device |
| 155 | +- [ ] launch_app_device |
| 156 | +- [ ] list_devices |
| 157 | +- [ ] stop_app_device |
| 158 | + |
| 159 | +#### Device Workspace Tools (3) |
| 160 | +- [ ] build_dev_ws |
| 161 | +- [ ] get_device_app_path_ws |
| 162 | +- [ ] test_device_ws |
| 163 | + |
| 164 | +#### Diagnostics Tools (1) |
| 165 | +- [ ] diagnostic |
| 166 | + |
| 167 | +#### Discovery Tools (1) |
| 168 | +- [ ] discover_tools |
| 169 | + |
| 170 | +#### Logging Tools (4) |
| 171 | +- [ ] start_device_log_cap |
| 172 | +- [ ] start_sim_log_cap |
| 173 | +- [ ] stop_device_log_cap |
| 174 | +- [ ] stop_sim_log_cap |
| 175 | + |
| 176 | +#### macOS Project Tools (4) |
| 177 | +- [ ] build_mac_proj |
| 178 | +- [ ] build_run_mac_proj |
| 179 | +- [ ] get_mac_app_path_proj |
| 180 | +- [ ] test_macos_proj |
| 181 | + |
| 182 | +#### macOS Shared Tools (2) |
| 183 | +- [ ] launch_mac_app |
| 184 | +- [ ] stop_mac_app |
| 185 | + |
| 186 | +#### macOS Workspace Tools (4) |
| 187 | +- [ ] build_mac_ws |
| 188 | +- [ ] build_run_mac_ws |
| 189 | +- [ ] get_mac_app_path_ws |
| 190 | +- [ ] test_macos_ws |
| 191 | + |
| 192 | +#### Project Discovery Tools (7) |
| 193 | +- [ ] discover_projs |
| 194 | +- [ ] get_app_bundle_id |
| 195 | +- [ ] get_mac_bundle_id |
| 196 | +- [ ] list_schems_proj |
| 197 | +- [ ] list_schems_ws |
| 198 | +- [ ] show_build_set_proj |
| 199 | +- [ ] show_build_set_ws |
| 200 | + |
| 201 | +#### Simulator Project Tools (8) |
| 202 | +- [ ] build_run_sim_id_proj |
| 203 | +- [ ] build_run_sim_name_proj |
| 204 | +- [ ] build_sim_id_proj |
| 205 | +- [ ] build_sim_name_proj |
| 206 | +- [ ] get_sim_app_path_id_proj |
| 207 | +- [ ] get_sim_app_path_name_proj |
| 208 | +- [ ] test_sim_id_proj |
| 209 | +- [ ] test_sim_name_proj |
| 210 | + |
| 211 | +#### Simulator Shared Tools (12) |
| 212 | +- [ ] boot_sim |
| 213 | +- [ ] install_app_sim |
| 214 | +- [ ] launch_app_logs_sim |
| 215 | +- [ ] launch_app_sim |
| 216 | +- [ ] list_sims |
| 217 | +- [ ] open_sim |
| 218 | +- [ ] reset_network_condition |
| 219 | +- [ ] reset_simulator_location |
| 220 | +- [ ] set_network_condition |
| 221 | +- [ ] set_sim_appearance |
| 222 | +- [ ] set_simulator_location |
| 223 | +- [ ] stop_app_sim |
| 224 | + |
| 225 | +#### Simulator Workspace Tools (10) |
| 226 | +- [ ] build_run_sim_id_ws |
| 227 | +- [ ] build_run_sim_name_ws |
| 228 | +- [ ] build_sim_id_ws |
| 229 | +- [ ] build_sim_name_ws |
| 230 | +- [ ] get_sim_app_path_id_ws |
| 231 | +- [ ] get_sim_app_path_name_ws |
| 232 | +- [ ] launch_app_sim_name_ws |
| 233 | +- [ ] stop_app_sim_name_ws |
| 234 | +- [ ] test_sim_id_ws |
| 235 | +- [ ] test_sim_name_ws |
| 236 | + |
| 237 | +#### Swift Package Tools (6) |
| 238 | +- [ ] swift_package_build |
| 239 | +- [ ] swift_package_clean |
| 240 | +- [ ] swift_package_list |
| 241 | +- [ ] swift_package_run |
| 242 | +- [ ] swift_package_stop |
| 243 | +- [ ] swift_package_test |
| 244 | + |
| 245 | +#### UI Testing Tools (11) |
| 246 | +- [ ] button |
| 247 | +- [ ] describe_ui |
| 248 | +- [ ] gesture |
| 249 | +- [ ] key_press |
| 250 | +- [ ] key_sequence |
| 251 | +- [ ] long_press |
| 252 | +- [ ] screenshot |
| 253 | +- [ ] swipe |
| 254 | +- [ ] tap |
| 255 | +- [ ] touch |
| 256 | +- [ ] type_text |
| 257 | + |
| 258 | +#### Utilities Tools (4) |
| 259 | +- [ ] clean_proj |
| 260 | +- [ ] clean_ws |
| 261 | +- [ ] scaffold_ios_project |
| 262 | +- [ ] scaffold_macos_project |
| 263 | + |
| 264 | +## Success Criteria |
| 265 | + |
| 266 | +### Per Tool |
| 267 | +- [ ] Logic extracted into separate function |
| 268 | +- [ ] Handler is thin wrapper only |
| 269 | +- [ ] Tests call logic directly |
| 270 | +- [ ] No vitest mocking used |
| 271 | +- [ ] Tool works via MCP |
| 272 | +- [ ] All tests pass |
| 273 | + |
| 274 | +### Overall |
| 275 | +- [ ] Zero string matching for dependency injection |
| 276 | +- [ ] No wrapHandlerWithExecutor needed |
| 277 | +- [ ] 100% type safety |
| 278 | +- [ ] All 84 tools refactored |
| 279 | +- [ ] All tests passing |
| 280 | +- [ ] Build succeeds |
| 281 | +- [ ] MCP server starts without errors |
| 282 | + |
| 283 | +## Canonical Requirements |
| 284 | + |
| 285 | +1. **No Vitest Mocking**: Use only `createMockExecutor` and `createMockFileSystemExecutor` |
| 286 | +2. **MCP Compliance**: Handlers must accept only `(args: Record<string, unknown>)` |
| 287 | +3. **Type Safety**: All parameters must be properly typed |
| 288 | +4. **Test Coverage**: Maintain 95%+ coverage |
| 289 | +5. **Performance**: Tests must run in <100ms per test |
| 290 | +6. **Error Handling**: Return `{ isError: true }`, never throw |
| 291 | +7. **Literal Expectations**: Use exact strings in test assertions |
| 292 | + |
| 293 | +## Risk Mitigation |
| 294 | + |
| 295 | +1. **Incremental Commits**: Each tool committed separately |
| 296 | +2. **Parallel Safety**: No two agents work on same file |
| 297 | +3. **Rollback Plan**: Git history allows reverting individual tools |
| 298 | +4. **Continuous Validation**: Test after each change |
| 299 | + |
| 300 | +## Timeline Estimate |
| 301 | + |
| 302 | +- **Setup Phase**: 30 minutes |
| 303 | +- **Per Tool**: ~10-15 minutes |
| 304 | +- **Total Estimate**: 8-12 hours with parallel execution |
| 305 | + |
| 306 | +--- |
| 307 | + |
| 308 | +**Ready for Review?** This plan ensures systematic refactoring with zero downtime and maintains all our architectural principles. |
0 commit comments