#
tokens: 46690/50000 18/195 files (page 3/4)
lines: off (toggle) GitHub
raw markdown copy
This is page 3 of 4. Use http://codebase.md/stefan-nitu/mcp-xcode-server?lines=false&page={x} to view the full context.

# Directory Structure

```
├── .claude
│   └── settings.local.json
├── .github
│   └── workflows
│       └── ci.yml
├── .gitignore
├── .vscode
│   └── settings.json
├── CLAUDE.md
├── CONTRIBUTING.md
├── docs
│   ├── ARCHITECTURE.md
│   ├── ERROR-HANDLING.md
│   └── TESTING-PHILOSOPHY.md
├── examples
│   └── screenshot-demo.js
├── jest.config.cjs
├── jest.e2e.config.cjs
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── scripts
│   └── xcode-sync.swift
├── src
│   ├── application
│   │   └── ports
│   │       ├── ArtifactPorts.ts
│   │       ├── BuildPorts.ts
│   │       ├── CommandPorts.ts
│   │       ├── ConfigPorts.ts
│   │       ├── LoggingPorts.ts
│   │       ├── MappingPorts.ts
│   │       ├── OutputFormatterPorts.ts
│   │       ├── OutputParserPorts.ts
│   │       └── SimulatorPorts.ts
│   ├── cli.ts
│   ├── config.ts
│   ├── domain
│   │   ├── errors
│   │   │   └── DomainError.ts
│   │   ├── services
│   │   │   └── PlatformDetector.ts
│   │   ├── shared
│   │   │   └── Result.ts
│   │   └── tests
│   │       └── unit
│   │           └── PlatformDetector.unit.test.ts
│   ├── features
│   │   ├── app-management
│   │   │   ├── controllers
│   │   │   │   └── InstallAppController.ts
│   │   │   ├── domain
│   │   │   │   ├── InstallRequest.ts
│   │   │   │   └── InstallResult.ts
│   │   │   ├── factories
│   │   │   │   └── InstallAppControllerFactory.ts
│   │   │   ├── index.ts
│   │   │   ├── infrastructure
│   │   │   │   └── AppInstallerAdapter.ts
│   │   │   ├── tests
│   │   │   │   ├── e2e
│   │   │   │   │   ├── InstallAppController.e2e.test.ts
│   │   │   │   │   └── InstallAppMCP.e2e.test.ts
│   │   │   │   ├── integration
│   │   │   │   │   └── InstallAppController.integration.test.ts
│   │   │   │   └── unit
│   │   │   │       ├── AppInstallerAdapter.unit.test.ts
│   │   │   │       ├── InstallAppController.unit.test.ts
│   │   │   │       ├── InstallAppUseCase.unit.test.ts
│   │   │   │       ├── InstallRequest.unit.test.ts
│   │   │   │       └── InstallResult.unit.test.ts
│   │   │   └── use-cases
│   │   │       └── InstallAppUseCase.ts
│   │   ├── build
│   │   │   ├── controllers
│   │   │   │   └── BuildXcodeController.ts
│   │   │   ├── domain
│   │   │   │   ├── BuildDestination.ts
│   │   │   │   ├── BuildIssue.ts
│   │   │   │   ├── BuildRequest.ts
│   │   │   │   ├── BuildResult.ts
│   │   │   │   └── PlatformInfo.ts
│   │   │   ├── factories
│   │   │   │   └── BuildXcodeControllerFactory.ts
│   │   │   ├── index.ts
│   │   │   ├── infrastructure
│   │   │   │   ├── BuildArtifactLocatorAdapter.ts
│   │   │   │   ├── BuildDestinationMapperAdapter.ts
│   │   │   │   ├── XcbeautifyFormatterAdapter.ts
│   │   │   │   ├── XcbeautifyOutputParserAdapter.ts
│   │   │   │   └── XcodeBuildCommandAdapter.ts
│   │   │   ├── tests
│   │   │   │   ├── e2e
│   │   │   │   │   ├── BuildXcodeController.e2e.test.ts
│   │   │   │   │   └── BuildXcodeMCP.e2e.test.ts
│   │   │   │   ├── integration
│   │   │   │   │   └── BuildXcodeController.integration.test.ts
│   │   │   │   └── unit
│   │   │   │       ├── BuildArtifactLocatorAdapter.unit.test.ts
│   │   │   │       ├── BuildDestinationMapperAdapter.unit.test.ts
│   │   │   │       ├── BuildIssue.unit.test.ts
│   │   │   │       ├── BuildProjectUseCase.unit.test.ts
│   │   │   │       ├── BuildRequest.unit.test.ts
│   │   │   │       ├── BuildResult.unit.test.ts
│   │   │   │       ├── BuildXcodeController.unit.test.ts
│   │   │   │       ├── BuildXcodePresenter.unit.test.ts
│   │   │   │       ├── PlatformInfo.unit.test.ts
│   │   │   │       ├── XcbeautifyFormatterAdapter.unit.test.ts
│   │   │   │       ├── XcbeautifyOutputParserAdapter.unit.test.ts
│   │   │   │       └── XcodeBuildCommandAdapter.unit.test.ts
│   │   │   └── use-cases
│   │   │       └── BuildProjectUseCase.ts
│   │   └── simulator
│   │       ├── controllers
│   │       │   ├── BootSimulatorController.ts
│   │       │   ├── ListSimulatorsController.ts
│   │       │   └── ShutdownSimulatorController.ts
│   │       ├── domain
│   │       │   ├── BootRequest.ts
│   │       │   ├── BootResult.ts
│   │       │   ├── ListSimulatorsRequest.ts
│   │       │   ├── ListSimulatorsResult.ts
│   │       │   ├── ShutdownRequest.ts
│   │       │   ├── ShutdownResult.ts
│   │       │   └── SimulatorState.ts
│   │       ├── factories
│   │       │   ├── BootSimulatorControllerFactory.ts
│   │       │   ├── ListSimulatorsControllerFactory.ts
│   │       │   └── ShutdownSimulatorControllerFactory.ts
│   │       ├── index.ts
│   │       ├── infrastructure
│   │       │   ├── SimulatorControlAdapter.ts
│   │       │   └── SimulatorLocatorAdapter.ts
│   │       ├── tests
│   │       │   ├── e2e
│   │       │   │   ├── BootSimulatorController.e2e.test.ts
│   │       │   │   ├── BootSimulatorMCP.e2e.test.ts
│   │       │   │   ├── ListSimulatorsController.e2e.test.ts
│   │       │   │   ├── ListSimulatorsMCP.e2e.test.ts
│   │       │   │   ├── ShutdownSimulatorController.e2e.test.ts
│   │       │   │   └── ShutdownSimulatorMCP.e2e.test.ts
│   │       │   ├── integration
│   │       │   │   ├── BootSimulatorController.integration.test.ts
│   │       │   │   ├── ListSimulatorsController.integration.test.ts
│   │       │   │   └── ShutdownSimulatorController.integration.test.ts
│   │       │   └── unit
│   │       │       ├── BootRequest.unit.test.ts
│   │       │       ├── BootResult.unit.test.ts
│   │       │       ├── BootSimulatorController.unit.test.ts
│   │       │       ├── BootSimulatorUseCase.unit.test.ts
│   │       │       ├── ListSimulatorsController.unit.test.ts
│   │       │       ├── ListSimulatorsUseCase.unit.test.ts
│   │       │       ├── ShutdownRequest.unit.test.ts
│   │       │       ├── ShutdownResult.unit.test.ts
│   │       │       ├── ShutdownSimulatorUseCase.unit.test.ts
│   │       │       ├── SimulatorControlAdapter.unit.test.ts
│   │       │       └── SimulatorLocatorAdapter.unit.test.ts
│   │       └── use-cases
│   │           ├── BootSimulatorUseCase.ts
│   │           ├── ListSimulatorsUseCase.ts
│   │           └── ShutdownSimulatorUseCase.ts
│   ├── index.ts
│   ├── infrastructure
│   │   ├── repositories
│   │   │   └── DeviceRepository.ts
│   │   ├── services
│   │   │   └── DependencyChecker.ts
│   │   └── tests
│   │       └── unit
│   │           ├── DependencyChecker.unit.test.ts
│   │           └── DeviceRepository.unit.test.ts
│   ├── logger.ts
│   ├── presentation
│   │   ├── decorators
│   │   │   └── DependencyCheckingDecorator.ts
│   │   ├── formatters
│   │   │   ├── ErrorFormatter.ts
│   │   │   └── strategies
│   │   │       ├── BuildIssuesStrategy.ts
│   │   │       ├── DefaultErrorStrategy.ts
│   │   │       ├── ErrorFormattingStrategy.ts
│   │   │       └── OutputFormatterErrorStrategy.ts
│   │   ├── interfaces
│   │   │   ├── IDependencyChecker.ts
│   │   │   ├── MCPController.ts
│   │   │   └── MCPResponse.ts
│   │   ├── presenters
│   │   │   └── BuildXcodePresenter.ts
│   │   └── tests
│   │       └── unit
│   │           ├── BuildIssuesStrategy.unit.test.ts
│   │           ├── DefaultErrorStrategy.unit.test.ts
│   │           ├── DependencyCheckingDecorator.unit.test.ts
│   │           └── ErrorFormatter.unit.test.ts
│   ├── shared
│   │   ├── domain
│   │   │   ├── AppPath.ts
│   │   │   ├── DeviceId.ts
│   │   │   ├── Platform.ts
│   │   │   └── ProjectPath.ts
│   │   ├── index.ts
│   │   ├── infrastructure
│   │   │   ├── ConfigProviderAdapter.ts
│   │   │   └── ShellCommandExecutorAdapter.ts
│   │   └── tests
│   │       ├── mocks
│   │       │   ├── promisifyExec.ts
│   │       │   ├── selectiveExecMock.ts
│   │       │   └── xcodebuildHelpers.ts
│   │       ├── skipped
│   │       │   ├── cli.e2e.test.skip
│   │       │   ├── hook-e2e.test.skip
│   │       │   ├── hook-path.e2e.test.skip
│   │       │   └── hook.test.skip
│   │       ├── types
│   │       │   └── execTypes.ts
│   │       ├── unit
│   │       │   ├── AppPath.unit.test.ts
│   │       │   ├── ConfigProviderAdapter.unit.test.ts
│   │       │   ├── ProjectPath.unit.test.ts
│   │       │   └── ShellCommandExecutorAdapter.unit.test.ts
│   │       └── utils
│   │           ├── gitResetTestArtifacts.ts
│   │           ├── mockHelpers.ts
│   │           ├── TestEnvironmentCleaner.ts
│   │           ├── TestErrorInjector.ts
│   │           ├── testHelpers.ts
│   │           ├── TestProjectManager.ts
│   │           └── TestSimulatorManager.ts
│   ├── types.ts
│   ├── utils
│   │   ├── devices
│   │   │   ├── Devices.ts
│   │   │   ├── SimulatorApps.ts
│   │   │   ├── SimulatorBoot.ts
│   │   │   ├── SimulatorDevice.ts
│   │   │   ├── SimulatorInfo.ts
│   │   │   ├── SimulatorReset.ts
│   │   │   └── SimulatorUI.ts
│   │   ├── errors
│   │   │   ├── index.ts
│   │   │   └── xcbeautify-parser.ts
│   │   ├── index.ts
│   │   ├── LogManager.ts
│   │   ├── LogManagerInstance.ts
│   │   └── projects
│   │       ├── SwiftBuild.ts
│   │       ├── SwiftPackage.ts
│   │       ├── SwiftPackageInfo.ts
│   │       ├── Xcode.ts
│   │       ├── XcodeArchive.ts
│   │       ├── XcodeBuild.ts
│   │       ├── XcodeErrors.ts
│   │       ├── XcodeInfo.ts
│   │       └── XcodeProject.ts
│   └── utils.ts
├── test_artifacts
│   ├── Test.xcworkspace
│   │   ├── contents.xcworkspacedata
│   │   └── xcuserdata
│   │       └── stefan.xcuserdatad
│   │           └── UserInterfaceState.xcuserstate
│   ├── TestProjectSwiftTesting
│   │   ├── TestProjectSwiftTesting
│   │   │   ├── Assets.xcassets
│   │   │   │   ├── AccentColor.colorset
│   │   │   │   │   └── Contents.json
│   │   │   │   ├── AppIcon.appiconset
│   │   │   │   │   └── Contents.json
│   │   │   │   └── Contents.json
│   │   │   ├── ContentView.swift
│   │   │   ├── Item.swift
│   │   │   ├── TestProjectSwiftTesting.entitlements
│   │   │   └── TestProjectSwiftTestingApp.swift
│   │   ├── TestProjectSwiftTesting.xcodeproj
│   │   │   ├── project.pbxproj
│   │   │   ├── project.xcworkspace
│   │   │   │   ├── contents.xcworkspacedata
│   │   │   │   └── xcuserdata
│   │   │   │       └── stefan.xcuserdatad
│   │   │   │           └── UserInterfaceState.xcuserstate
│   │   │   └── xcuserdata
│   │   │       └── stefan.xcuserdatad
│   │   │           └── xcschemes
│   │   │               └── xcschememanagement.plist
│   │   ├── TestProjectSwiftTestingTests
│   │   │   └── TestProjectSwiftTestingTests.swift
│   │   └── TestProjectSwiftTestingUITests
│   │       ├── TestProjectSwiftTestingUITests.swift
│   │       └── TestProjectSwiftTestingUITestsLaunchTests.swift
│   ├── TestProjectWatchOS
│   │   ├── TestProjectWatchOS
│   │   │   ├── Assets.xcassets
│   │   │   │   ├── AccentColor.colorset
│   │   │   │   │   └── Contents.json
│   │   │   │   ├── AppIcon.appiconset
│   │   │   │   │   └── Contents.json
│   │   │   │   └── Contents.json
│   │   │   ├── ContentView.swift
│   │   │   └── TestProjectWatchOSApp.swift
│   │   ├── TestProjectWatchOS Watch App
│   │   │   ├── Assets.xcassets
│   │   │   │   ├── AccentColor.colorset
│   │   │   │   │   └── Contents.json
│   │   │   │   ├── AppIcon.appiconset
│   │   │   │   │   └── Contents.json
│   │   │   │   └── Contents.json
│   │   │   ├── ContentView.swift
│   │   │   └── TestProjectWatchOSApp.swift
│   │   ├── TestProjectWatchOS Watch AppTests
│   │   │   └── TestProjectWatchOS_Watch_AppTests.swift
│   │   ├── TestProjectWatchOS Watch AppUITests
│   │   │   ├── TestProjectWatchOS_Watch_AppUITests.swift
│   │   │   └── TestProjectWatchOS_Watch_AppUITestsLaunchTests.swift
│   │   ├── TestProjectWatchOS.xcodeproj
│   │   │   ├── project.pbxproj
│   │   │   └── project.xcworkspace
│   │   │       └── contents.xcworkspacedata
│   │   ├── TestProjectWatchOSTests
│   │   │   └── TestProjectWatchOSTests.swift
│   │   └── TestProjectWatchOSUITests
│   │       ├── TestProjectWatchOSUITests.swift
│   │       └── TestProjectWatchOSUITestsLaunchTests.swift
│   ├── TestProjectXCTest
│   │   ├── TestProjectXCTest
│   │   │   ├── Assets.xcassets
│   │   │   │   ├── AccentColor.colorset
│   │   │   │   │   └── Contents.json
│   │   │   │   ├── AppIcon.appiconset
│   │   │   │   │   └── Contents.json
│   │   │   │   └── Contents.json
│   │   │   ├── ContentView.swift
│   │   │   ├── Item.swift
│   │   │   ├── TestProjectXCTest.entitlements
│   │   │   └── TestProjectXCTestApp.swift
│   │   ├── TestProjectXCTest.xcodeproj
│   │   │   ├── project.pbxproj
│   │   │   ├── project.xcworkspace
│   │   │   │   ├── contents.xcworkspacedata
│   │   │   │   └── xcuserdata
│   │   │   │       └── stefan.xcuserdatad
│   │   │   │           └── UserInterfaceState.xcuserstate
│   │   │   └── xcuserdata
│   │   │       └── stefan.xcuserdatad
│   │   │           └── xcschemes
│   │   │               └── xcschememanagement.plist
│   │   ├── TestProjectXCTestTests
│   │   │   └── TestProjectXCTestTests.swift
│   │   └── TestProjectXCTestUITests
│   │       ├── TestProjectXCTestUITests.swift
│   │       └── TestProjectXCTestUITestsLaunchTests.swift
│   ├── TestSwiftPackageSwiftTesting
│   │   ├── .gitignore
│   │   ├── Package.swift
│   │   ├── Sources
│   │   │   ├── TestSwiftPackageSwiftTesting
│   │   │   │   └── TestSwiftPackageSwiftTesting.swift
│   │   │   └── TestSwiftPackageSwiftTestingExecutable
│   │   │       └── main.swift
│   │   └── Tests
│   │       └── TestSwiftPackageSwiftTestingTests
│   │           └── TestSwiftPackageSwiftTestingTests.swift
│   └── TestSwiftPackageXCTest
│       ├── .gitignore
│       ├── Package.swift
│       ├── Sources
│       │   ├── TestSwiftPackageXCTest
│       │   │   └── TestSwiftPackageXCTest.swift
│       │   └── TestSwiftPackageXCTestExecutable
│       │       └── main.swift
│       └── Tests
│           └── TestSwiftPackageXCTestTests
│               └── TestSwiftPackageXCTestTests.swift
├── tsconfig.json
└── XcodeProjectModifier
    ├── Package.resolved
    ├── Package.swift
    └── Sources
        └── XcodeProjectModifier
            └── main.swift
```

# Files

--------------------------------------------------------------------------------
/docs/ERROR-HANDLING.md:
--------------------------------------------------------------------------------

```markdown
# Error Handling and Presentation Patterns

## Overview

This document describes the error handling patterns and presentation conventions used across the MCP Xcode Server codebase. Following these patterns ensures consistent user experience and maintainable error handling.

## Core Principles

### 1. Separation of Concerns
- **Domain Layer**: Creates typed error objects with data only (no formatting)
- **Use Cases**: Return domain errors without formatting messages
- **Presentation Layer**: Formats errors for user display with consistent styling

### 2. Typed Domain Errors
Each domain has its own error types that extend a base error class:

```typescript
// Base class for domain-specific errors
export abstract class BootError extends Error {}

// Specific error types with relevant data
export class SimulatorNotFoundError extends BootError {
  constructor(public readonly deviceId: string) {
    super(deviceId); // Just store the data, no formatting
    this.name = 'SimulatorNotFoundError';
  }
}

export class BootCommandFailedError extends BootError {
  constructor(public readonly stderr: string) {
    super(stderr); // Just store the stderr output
    this.name = 'BootCommandFailedError';
  }
}
```

**Important**: Domain errors should NEVER contain user-facing messages. They should only contain data. The presentation layer is responsible for formatting messages based on the error type and data.

### 3. Error Type Checking
Use `instanceof` to check error types in the presentation layer:

```typescript
if (error instanceof SimulatorNotFoundError) {
  return `❌ Simulator not found: ${error.deviceId}`;
}

if (error instanceof BootCommandFailedError) {
  return `❌ ${ErrorFormatter.format(error)}`;
}
```

## Presentation Patterns

### Visual Indicators (Emojis)

All tools use consistent emoji prefixes for different outcomes:

- **✅ Success**: Successful operations
- **❌ Error**: Failed operations  
- **⚠️ Warning**: Operations with warnings
- **📁 Info**: Additional information (like log paths)

Examples:
```
✅ Successfully booted simulator: iPhone 15 (ABC123)
✅ Build succeeded: MyApp

❌ Simulator not found: iPhone-16
❌ Build failed

⚠️ Warnings (3):
  • Deprecated API usage
  • Unused variable 'x'
  
📁 Full logs saved to: /path/to/logs
```

### Error Message Format

1. **Simple Errors**: Direct message with emoji
   ```
   ❌ Simulator not found: iPhone-16
   ❌ Unable to boot device
   ```

2. **Complex Errors** (builds, tests): Structured format
   ```
   ❌ Build failed: MyApp
   Platform: iOS
   Configuration: Debug
   
   ❌ Errors (3):
     • /path/file.swift:10: Cannot find type 'Foo'
     • /path/file.swift:20: Missing return statement
   ```

### ErrorFormatter Usage

The `ErrorFormatter` class provides consistent error formatting across all tools:

```typescript
import { ErrorFormatter } from '../formatters/ErrorFormatter.js';

// In controller or presenter
const message = ErrorFormatter.format(error);
return `❌ ${message}`;
```

The ErrorFormatter:
- Formats domain validation errors
- Formats build issues
- Cleans up common error prefixes
- Provides fallback for unknown errors

## Implementation Guidelines

### Controllers

Controllers should format results consistently:

```typescript
private formatResult(result: DomainResult): string {
  switch (result.outcome) {
    case Outcome.Success:
      return `✅ Successfully completed: ${result.name}`;
      
    case Outcome.Failed:
      if (result.error instanceof SpecificError) {
        return `❌ Specific error: ${result.error.details}`;
      }
      return `❌ ${ErrorFormatter.format(result.error)}`;
  }
}
```

### Use Cases

Use cases should NOT format error messages:

```typescript
// ❌ BAD: Formatting in use case
return Result.failed(
  `Simulator not found: ${deviceId}` // Don't format here!
);

// ✅ GOOD: Return typed error
return Result.failed(
  new SimulatorNotFoundError(deviceId) // Just the error object
);
```

### Presenters

For complex formatting (like build results), use a dedicated presenter:

```typescript
export class BuildXcodePresenter {
  presentError(error: Error): MCPResponse {
    const message = ErrorFormatter.format(error);
    return {
      content: [{
        type: 'text',
        text: `❌ ${message}`
      }]
    };
  }
}
```

## Testing Error Handling

### Unit Tests

Test that controllers format errors correctly:

```typescript
it('should handle boot failure', async () => {
  // Arrange
  const error = new BootCommandFailedError('Device is locked');
  const result = BootResult.failed('123', 'iPhone', error);
  
  // Act
  const response = controller.execute({ deviceId: 'iPhone' });
  
  // Assert - Check for emoji and message
  expect(response.text).toBe('❌ Device is locked');
});
```

### Integration Tests

Test behavior, not specific formatting:

```typescript
it('should handle simulator not found', async () => {
  // Act
  const result = await controller.execute({ deviceId: 'NonExistent' });
  
  // Assert - Test behavior: error message shown
  expect(result.content[0].text).toContain('❌');
  expect(result.content[0].text).toContain('not found');
});
```

## Common Error Scenarios

### 1. Resource Not Found
```typescript
export class ResourceNotFoundError extends DomainError {
  constructor(public readonly resourceId: string) {
    super(resourceId);
  }
}

// Presentation
`❌ Resource not found: ${error.resourceId}`
```

### 2. Command Execution Failed
```typescript
export class CommandFailedError extends DomainError {
  constructor(public readonly stderr: string, public readonly exitCode?: number) {
    super(stderr);
  }
}

// Presentation
`❌ Command failed: ${error.stderr}`
```

### 3. Resource Busy/State Conflicts
```typescript
export class SimulatorBusyError extends DomainError {
  constructor(public readonly currentState: string) {
    super(currentState); // Just store the state, no message
  }
}

// Presentation layer formats the message
`❌ Cannot boot simulator: currently ${error.currentState.toLowerCase()}`
```

### 4. Validation Failed
Domain value objects handle their own validation with consistent error types:

```typescript
// Domain value objects validate themselves
export class AppPath {
  static create(path: unknown): AppPath {
    // Check for missing field
    if (path === undefined || path === null) {
      throw new AppPath.RequiredError(); // "App path is required"
    }

    // Check type
    if (typeof path !== 'string') {
      throw new AppPath.InvalidTypeError(path); // "App path must be a string"
    }

    // Check empty
    if (path.trim() === '') {
      throw new AppPath.EmptyError(path); // "App path cannot be empty"
    }

    // Check format
    if (!path.endsWith('.app')) {
      throw new AppPath.InvalidFormatError(path); // "App path must end with .app"
    }

    return new AppPath(path);
  }
}
```

#### Validation Error Hierarchy
Each value object follows this consistent validation order:
1. **Required**: `undefined`/`null` → "X is required"
2. **Type**: Wrong type → "X must be a {type}"
3. **Empty**: Empty string → "X cannot be empty"
4. **Format**: Invalid format → Specific format message

```typescript
// Consistent error base classes
export abstract class DomainRequiredError extends DomainError {
  constructor(fieldDisplayName: string) {
    super(`${fieldDisplayName} is required`);
  }
}

export abstract class DomainEmptyError extends DomainError {
  constructor(fieldDisplayName: string) {
    super(`${fieldDisplayName} cannot be empty`);
  }
}

export abstract class DomainInvalidTypeError extends DomainError {
  constructor(fieldDisplayName: string, expectedType: string) {
    super(`${fieldDisplayName} must be a ${expectedType}`);
  }
}
```

## Benefits

1. **Consistency**: Users see consistent error formatting across all tools
2. **Maintainability**: Error formatting logic is centralized
3. **Testability**: Domain logic doesn't depend on presentation
4. **Flexibility**: Easy to change formatting without touching business logic
5. **Type Safety**: TypeScript ensures error types are handled correctly
```

--------------------------------------------------------------------------------
/src/shared/tests/utils/TestErrorInjector.ts:
--------------------------------------------------------------------------------

```typescript
import { readFileSync, writeFileSync, existsSync } from 'fs';
import { join } from 'path';
import { createModuleLogger } from '../../../logger';
import { gitResetTestArtifacts, gitResetFile } from './gitResetTestArtifacts';

const logger = createModuleLogger('TestErrorInjector');

/**
 * Helper class to inject specific error conditions into test projects
 * for testing error handling and display
 */
export class TestErrorInjector {
  private originalFiles: Map<string, string> = new Map();

  /**
   * Inject a compile error into a Swift file
   */
  injectCompileError(filePath: string, errorType: 'type-mismatch' | 'syntax' | 'missing-member' = 'type-mismatch') {
    this.backupFile(filePath);
    
    let content = readFileSync(filePath, 'utf8');
    
    switch (errorType) {
      case 'type-mismatch':
        // Add a type mismatch error
        content = content.replace(
          'let newItem = Item(timestamp: Date())',
          'let x: String = 123  // Type mismatch error\n        let newItem = Item(timestamp: Date())'
        );
        break;
      
      case 'syntax':
        // Add a syntax error
        content = content.replace(
          'import SwiftUI',
          'import SwiftUI\nlet incomplete = // Syntax error'
        );
        break;
      
      case 'missing-member':
        // Reference a non-existent property
        content = content.replace(
          'modelContext.insert(newItem)',
          'modelContext.insert(newItem)\n        let _ = newItem.nonExistentProperty // Missing member error'
        );
        break;
    }
    
    writeFileSync(filePath, content);
    logger.debug({ filePath, errorType }, 'Injected compile error');
  }

  /**
   * Inject multiple compile errors into a file
   */
  injectMultipleCompileErrors(filePath: string) {
    if (!existsSync(filePath)) {
      throw new Error(`File not found: ${filePath}`);
    }
    
    this.backupFile(filePath);
    
    let content = readFileSync(filePath, 'utf8');
    
    // Add multiple different types of errors
    content = content.replace(
      'let newItem = Item(timestamp: Date())',
      `let x: String = 123  // Type mismatch error 1
        let y: Int = "hello"  // Type mismatch error 2
        let z = nonExistentFunction()  // Undefined function error
        let newItem = Item(timestamp: Date())`
    );
    
    writeFileSync(filePath, content);
    logger.debug({ filePath }, 'Injected multiple compile errors');
  }

  /**
   * Remove code signing from a project to trigger signing errors
   */
  injectCodeSigningError(projectPath: string) {
    const pbxprojPath = join(projectPath, 'project.pbxproj');
    if (!existsSync(pbxprojPath)) {
      throw new Error(`Project file not found: ${pbxprojPath}`);
    }
    
    this.backupFile(pbxprojPath);
    
    let content = readFileSync(pbxprojPath, 'utf8');
    
    // Change code signing settings to trigger errors
    content = content.replace(
      /CODE_SIGN_STYLE = Automatic;/g,
      'CODE_SIGN_STYLE = Manual;'
    );
    content = content.replace(
      /DEVELOPMENT_TEAM = [A-Z0-9]+;/g,
      'DEVELOPMENT_TEAM = "";'
    );
    
    writeFileSync(pbxprojPath, content);
    logger.debug({ projectPath }, 'Injected code signing error');
  }

  /**
   * Inject a provisioning profile error by requiring a non-existent profile
   */
  injectProvisioningError(projectPath: string) {
    const pbxprojPath = join(projectPath, 'project.pbxproj');
    if (!existsSync(pbxprojPath)) {
      throw new Error(`Project file not found: ${pbxprojPath}`);
    }
    
    this.backupFile(pbxprojPath);
    
    let content = readFileSync(pbxprojPath, 'utf8');
    
    // Add a non-existent provisioning profile requirement
    content = content.replace(
      /PRODUCT_BUNDLE_IDENTIFIER = /g,
      'PROVISIONING_PROFILE_SPECIFIER = "NonExistent Profile";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = '
    );
    
    writeFileSync(pbxprojPath, content);
    logger.debug({ projectPath }, 'Injected provisioning profile error');
  }

  /**
   * Inject a missing dependency error into a Swift package
   */
  injectMissingDependency(packagePath: string) {
    const packageSwiftPath = join(packagePath, 'Package.swift');
    if (!existsSync(packageSwiftPath)) {
      throw new Error(`Package.swift not found: ${packageSwiftPath}`);
    }
    
    this.backupFile(packageSwiftPath);
    
    let content = readFileSync(packageSwiftPath, 'utf8');
    
    // In Swift Package Manager, the Package initializer arguments must be in order:
    // name, platforms?, products, dependencies?, targets
    // We need to insert dependencies after products but before targets
    
    // Find the end of products array and beginning of targets
    // Use [\s\S]*? for non-greedy multiline matching
    const regex = /(products:\s*\[[\s\S]*?\])(,\s*)(targets:)/;
    const match = content.match(regex);
    
    if (match) {
      // Insert dependencies between products and targets
      // Use a non-existent package to trigger dependency resolution error
      content = content.replace(
        regex,
        `$1,\n    dependencies: [\n        .package(url: "https://github.com/nonexistent-org/nonexistent-package.git", from: "1.0.0")\n    ]$2$3`
      );
    }
    
    // Now add the dependency to a target so it tries to use it
    // Find the first target that doesn't have dependencies yet
    const targetRegex = /\.target\(\s*name:\s*"([^"]+)"\s*\)/;
    const targetMatch = content.match(targetRegex);
    
    if (targetMatch) {
      const targetName = targetMatch[1];
      // Replace the target to add dependencies
      content = content.replace(
        targetMatch[0],
        `.target(\n            name: "${targetName}",\n            dependencies: [\n                .product(name: "NonExistentPackage", package: "nonexistent-package")\n            ])`
      );
      
      // Also add import to a source file to trigger the error at compile time
      const sourcePath = join(packagePath, 'Sources', targetName);
      if (existsSync(sourcePath)) {
        const sourceFiles = require('fs').readdirSync(sourcePath, { recursive: true })
          .filter((f: string) => f.endsWith('.swift'));
        
        if (sourceFiles.length > 0) {
          const sourceFile = join(sourcePath, sourceFiles[0]);
          this.backupFile(sourceFile);
          let sourceContent = readFileSync(sourceFile, 'utf8');
          sourceContent = 'import NonExistentPackage\n' + sourceContent;
          writeFileSync(sourceFile, sourceContent);
        }
      }
    }
    
    writeFileSync(packageSwiftPath, content);
    logger.debug({ packagePath }, 'Injected missing dependency error');
  }

  /**
   * Inject a platform compatibility error
   */
  injectPlatformError(projectPath: string) {
    const pbxprojPath = join(projectPath, 'project.pbxproj');
    if (!existsSync(pbxprojPath)) {
      throw new Error(`Project file not found: ${pbxprojPath}`);
    }
    
    this.backupFile(pbxprojPath);
    
    let content = readFileSync(pbxprojPath, 'utf8');
    
    // Set incompatible deployment targets
    content = content.replace(
      /IPHONEOS_DEPLOYMENT_TARGET = \d+\.\d+;/g,
      'IPHONEOS_DEPLOYMENT_TARGET = 99.0;' // Impossible iOS version
    );
    
    writeFileSync(pbxprojPath, content);
    logger.debug({ projectPath }, 'Injected platform compatibility error');
  }

  /**
   * Backup a file before modifying it
   */
  private backupFile(filePath: string) {
    if (!this.originalFiles.has(filePath)) {
      const content = readFileSync(filePath, 'utf8');
      this.originalFiles.set(filePath, content);
      logger.debug({ filePath }, 'Backed up original file');
    }
  }

  /**
   * Restore all modified files to their original state
   */
  restoreAll() {
    // Use git to reset all test_artifacts
    gitResetTestArtifacts();
    // Clear our tracking
    this.originalFiles.clear();
  }

  /**
   * Restore a specific file
   */
  restoreFile(filePath: string) {
    // Use git to reset the specific file
    gitResetFile(filePath);
    // Remove from our tracking
    this.originalFiles.delete(filePath);
  }
}
```

--------------------------------------------------------------------------------
/src/presentation/tests/unit/DefaultErrorStrategy.unit.test.ts:
--------------------------------------------------------------------------------

```typescript
import { DefaultErrorStrategy } from '../../formatters/strategies/DefaultErrorStrategy.js';

describe('DefaultErrorStrategy', () => {
  function createSUT(): DefaultErrorStrategy {
    return new DefaultErrorStrategy();
  }

  describe('canFormat', () => {
    it('should always return true as the fallback strategy', () => {
      const sut = createSUT();
      
      expect(sut.canFormat(new Error('Any error'))).toBe(true);
      expect(sut.canFormat({ message: 'Plain object' })).toBe(true);
      expect(sut.canFormat('String error')).toBe(true);
      expect(sut.canFormat(123)).toBe(true);
      expect(sut.canFormat(null)).toBe(true);
      expect(sut.canFormat(undefined)).toBe(true);
      expect(sut.canFormat({})).toBe(true);
      expect(sut.canFormat([])).toBe(true);
    });
  });

  describe('format', () => {
    describe('when formatting errors with messages', () => {
      it('should return plain message without modification', () => {
        const sut = createSUT();
        const error = new Error('Simple error message');
        
        const result = sut.format(error);
        
        expect(result).toBe('Simple error message');
      });

      it('should preserve message with special characters', () => {
        const sut = createSUT();
        const error = { message: 'Error: with @#$% special chars!' };
        
        const result = sut.format(error);
        
        expect(result).toBe('with @#$% special chars!');
      });

      it('should handle multiline messages', () => {
        const sut = createSUT();
        const error = new Error('First line\nSecond line\nThird line');
        
        const result = sut.format(error);
        
        expect(result).toBe('First line\nSecond line\nThird line');
      });
    });

    describe('when cleaning common prefixes', () => {
      it('should remove "Error:" prefix (case insensitive)', () => {
        const sut = createSUT();
        
        expect(sut.format(new Error('Error: Something went wrong'))).toBe('Something went wrong');
        expect(sut.format(new Error('error: lowercase prefix'))).toBe('lowercase prefix');
        expect(sut.format(new Error('ERROR: uppercase prefix'))).toBe('uppercase prefix');
        expect(sut.format(new Error('ErRoR: mixed case'))).toBe('mixed case');
      });

      it('should remove "Invalid arguments:" prefix (case insensitive)', () => {
        const sut = createSUT();
        
        expect(sut.format(new Error('Invalid arguments: Missing required field'))).toBe('Missing required field');
        expect(sut.format(new Error('invalid arguments: lowercase'))).toBe('lowercase');
        expect(sut.format(new Error('INVALID ARGUMENTS: uppercase'))).toBe('uppercase');
      });

      it('should remove "Validation failed:" prefix (case insensitive)', () => {
        const sut = createSUT();
        
        expect(sut.format(new Error('Validation failed: Bad input'))).toBe('Bad input');
        expect(sut.format(new Error('validation failed: lowercase'))).toBe('lowercase');
        expect(sut.format(new Error('VALIDATION FAILED: uppercase'))).toBe('uppercase');
      });

      it('should handle multiple spaces after prefix', () => {
        const sut = createSUT();
        
        expect(sut.format(new Error('Error:     Multiple spaces'))).toBe('Multiple spaces');
        expect(sut.format(new Error('Invalid arguments:   Extra spaces'))).toBe('Extra spaces');
      });

      it('should only remove prefix at start of message', () => {
        const sut = createSUT();
        const error = new Error('Something Error: in the middle');
        
        const result = sut.format(error);
        
        expect(result).toBe('Something Error: in the middle');
      });

      it('should handle messages that are only the prefix', () => {
        const sut = createSUT();
        
        expect(sut.format(new Error('Error:'))).toBe('');
        expect(sut.format(new Error('Error: '))).toBe('');
        expect(sut.format(new Error('Invalid arguments:'))).toBe('');
        expect(sut.format(new Error('Validation failed:'))).toBe('');
      });

      it('should clean all matching prefixes', () => {
        const sut = createSUT();
        const error = new Error('Error: Invalid arguments: Something');
        
        const result = sut.format(error);
        
        // Both "Error:" and "Invalid arguments:" are cleaned
        expect(result).toBe('Something');
      });
    });

    describe('when handling errors without messages', () => {
      it('should return default message for error without message property', () => {
        const sut = createSUT();
        const error = {};
        
        const result = sut.format(error);
        
        expect(result).toBe('An error occurred');
      });

      it('should return default message for null error', () => {
        const sut = createSUT();
        
        const result = sut.format(null);
        
        expect(result).toBe('An error occurred');
      });

      it('should return default message for undefined error', () => {
        const sut = createSUT();
        
        const result = sut.format(undefined);
        
        expect(result).toBe('An error occurred');
      });

      it('should return default message for non-object errors', () => {
        const sut = createSUT();
        
        expect(sut.format('string error')).toBe('An error occurred');
        expect(sut.format(123)).toBe('An error occurred');
        expect(sut.format(true)).toBe('An error occurred');
        expect(sut.format([])).toBe('An error occurred');
      });

      it('should handle error with null message', () => {
        const sut = createSUT();
        const error = { message: null };
        
        const result = sut.format(error);
        
        expect(result).toBe('An error occurred');
      });

      it('should handle error with undefined message', () => {
        const sut = createSUT();
        const error = { message: undefined };
        
        const result = sut.format(error);
        
        expect(result).toBe('An error occurred');
      });

      it('should handle error with empty string message', () => {
        const sut = createSUT();
        const error = { message: '' };
        
        const result = sut.format(error);
        
        expect(result).toBe('An error occurred');
      });

      it('should handle error with whitespace-only message', () => {
        const sut = createSUT();
        const error = { message: '   ' };
        
        const result = sut.format(error);
        
        expect(result).toBe('   '); // Preserves whitespace as it's truthy
      });
    });

    describe('when handling edge cases', () => {
      it('should handle very long messages', () => {
        const sut = createSUT();
        const longMessage = 'A'.repeat(10000);
        const error = new Error(longMessage);
        
        const result = sut.format(error);
        
        expect(result).toBe(longMessage);
      });

      it('should preserve unicode and emoji', () => {
        const sut = createSUT();
        const error = new Error('Error: Failed to process 你好 🚫');
        
        const result = sut.format(error);
        
        expect(result).toBe('Failed to process 你好 🚫');
      });

      it('should handle messages with only special characters', () => {
        const sut = createSUT();
        const error = new Error('@#$%^&*()');
        
        const result = sut.format(error);
        
        expect(result).toBe('@#$%^&*()');
      });

      it('should handle error-like objects with toString', () => {
        const sut = createSUT();
        const error = {
          message: 'Custom error',
          toString: () => 'ToString output'
        };
        
        const result = sut.format(error);
        
        expect(result).toBe('Custom error'); // Prefers message over toString
      });

      it('should not modify messages without known prefixes', () => {
        const sut = createSUT();
        const messages = [
          'Unknown prefix: Something',
          'Warning: Something else',
          'Notice: Important info',
          'Failed: Operation incomplete'
        ];
        
        messages.forEach(msg => {
          const error = new Error(msg);
          expect(sut.format(error)).toBe(msg);
        });
      });
    });
  });
});
```

--------------------------------------------------------------------------------
/src/utils/projects/Xcode.ts:
--------------------------------------------------------------------------------

```typescript
import { XcodeProject } from './XcodeProject.js';
import { SwiftPackage } from './SwiftPackage.js';
import { XcodeError, XcodeErrorType } from './XcodeErrors.js';
import { createModuleLogger } from '../../logger.js';
import { existsSync } from 'fs';
import { readdir } from 'fs/promises';
import path from 'path';

const logger = createModuleLogger('Xcode');

/**
 * Xcode project discovery and management.
 * Provides methods to find and open Xcode projects and Swift packages.
 */
export class Xcode {
  /**
   * Open a project at the specified path.
   * Automatically detects whether it's an Xcode project or Swift package.
   * @param projectPath Path to the project
   * @param expectedType Optional type to expect ('xcode' | 'swift-package' | 'auto')
   */
  async open(projectPath: string, expectedType: 'xcode' | 'swift-package' | 'auto' = 'auto'): Promise<XcodeProject | SwiftPackage> {
    // If expecting Swift package specifically, only look for Package.swift
    if (expectedType === 'swift-package') {
      // Check if it's a Package.swift file directly
      if (projectPath.endsWith('Package.swift')) {
        if (!existsSync(projectPath)) {
          throw new Error(`No Package.swift found at: ${projectPath}`);
        }
        const packageDir = path.dirname(projectPath);
        logger.debug({ packageDir }, 'Opening Swift package from Package.swift');
        return new SwiftPackage(packageDir);
      }
      
      // Check if directory contains Package.swift
      if (existsSync(projectPath)) {
        const packageSwiftPath = path.join(projectPath, 'Package.swift');
        if (existsSync(packageSwiftPath)) {
          logger.debug({ projectPath }, 'Found Package.swift in directory');
          return new SwiftPackage(projectPath);
        }
      }
      
      throw new Error(`No Package.swift found at: ${projectPath}`);
    }
    
    // If expecting Xcode project specifically, only look for .xcodeproj/.xcworkspace
    if (expectedType === 'xcode') {
      // Check if it's an Xcode project or workspace
      if (projectPath.endsWith('.xcodeproj') || projectPath.endsWith('.xcworkspace')) {
        if (!existsSync(projectPath)) {
          throw new Error(`No Xcode project found at: ${projectPath}`);
        }
        const type = projectPath.endsWith('.xcworkspace') ? 'workspace' : 'project';
        logger.debug({ projectPath, type }, 'Opening Xcode project');
        return new XcodeProject(projectPath, type);
      }
      
      // Check directory for Xcode projects
      if (existsSync(projectPath)) {
        const files = await readdir(projectPath);
        
        const workspace = files.find(f => f.endsWith('.xcworkspace'));
        if (workspace) {
          const workspacePath = path.join(projectPath, workspace);
          logger.debug({ workspacePath }, 'Found workspace in directory');
          return new XcodeProject(workspacePath, 'workspace');
        }
        
        const xcodeproj = files.find(f => f.endsWith('.xcodeproj'));
        if (xcodeproj) {
          const xcodeprojPath = path.join(projectPath, xcodeproj);
          logger.debug({ xcodeprojPath }, 'Found Xcode project in directory');
          return new XcodeProject(xcodeprojPath, 'project');
        }
      }
      
      throw new Error(`No Xcode project found at: ${projectPath}`);
    }
    
    // Auto mode - original behavior
    // Check if it's an Xcode project or workspace
    if (projectPath.endsWith('.xcodeproj') || projectPath.endsWith('.xcworkspace')) {
      if (!existsSync(projectPath)) {
        throw new Error(`Xcode project not found at: ${projectPath}`);
      }
      const type = projectPath.endsWith('.xcworkspace') ? 'workspace' : 'project';
      logger.debug({ projectPath, type }, 'Opening Xcode project');
      return new XcodeProject(projectPath, type);
    }
    
    // Check if it's a Swift package (directory containing Package.swift)
    const packagePath = path.join(projectPath, 'Package.swift');
    if (existsSync(packagePath)) {
      logger.debug({ projectPath }, 'Opening Swift package');
      return new SwiftPackage(projectPath);
    }
    
    // If it's a Package.swift file directly
    if (projectPath.endsWith('Package.swift') && existsSync(projectPath)) {
      const packageDir = path.dirname(projectPath);
      logger.debug({ packageDir }, 'Opening Swift package from Package.swift');
      return new SwiftPackage(packageDir);
    }
    
    // Try to auto-detect in the directory
    if (existsSync(projectPath)) {
      // Look for .xcworkspace first (higher priority)
      const files = await readdir(projectPath);
      
      const workspace = files.find(f => f.endsWith('.xcworkspace'));
      if (workspace) {
        const workspacePath = path.join(projectPath, workspace);
        logger.debug({ workspacePath }, 'Found workspace in directory');
        return new XcodeProject(workspacePath, 'workspace');
      }
      
      const xcodeproj = files.find(f => f.endsWith('.xcodeproj'));
      if (xcodeproj) {
        const xcodeprojPath = path.join(projectPath, xcodeproj);
        logger.debug({ xcodeprojPath }, 'Found Xcode project in directory');
        return new XcodeProject(xcodeprojPath, 'project');
      }
      
      // Check for Package.swift
      if (files.includes('Package.swift')) {
        logger.debug({ projectPath }, 'Found Package.swift in directory');
        return new SwiftPackage(projectPath);
      }
    }
    
    throw new XcodeError(XcodeErrorType.ProjectNotFound, projectPath);
  }
  
  /**
   * Find all Xcode projects in a directory
   */
  async findProjects(directory: string): Promise<XcodeProject[]> {
    const projects: XcodeProject[] = [];
    
    try {
      const files = await readdir(directory, { withFileTypes: true });
      
      for (const file of files) {
        const fullPath = path.join(directory, file.name);
        
        if (file.name.endsWith('.xcworkspace')) {
          projects.push(new XcodeProject(fullPath, 'workspace'));
        } else if (file.name.endsWith('.xcodeproj')) {
          // Only add if there's no workspace (workspace takes precedence)
          const workspaceName = file.name.replace('.xcodeproj', '.xcworkspace');
          const hasWorkspace = files.some(f => f.name === workspaceName);
          if (!hasWorkspace) {
            projects.push(new XcodeProject(fullPath, 'project'));
          }
        } else if (file.isDirectory() && !file.name.startsWith('.')) {
          // Recursively search subdirectories
          const subProjects = await this.findProjects(fullPath);
          projects.push(...subProjects);
        }
      }
    } catch (error: any) {
      logger.error({ error: error.message, directory }, 'Failed to find projects');
      throw new Error(`Failed to find projects in ${directory}: ${error.message}`);
    }
    
    logger.debug({ count: projects.length, directory }, 'Found Xcode projects');
    return projects;
  }
  
  /**
   * Find all Swift packages in a directory
   */
  async findPackages(directory: string): Promise<SwiftPackage[]> {
    const packages: SwiftPackage[] = [];
    
    try {
      const files = await readdir(directory, { withFileTypes: true });
      
      // Check if this directory itself is a package
      if (files.some(f => f.name === 'Package.swift')) {
        packages.push(new SwiftPackage(directory));
      }
      
      // Search subdirectories
      for (const file of files) {
        if (file.isDirectory() && !file.name.startsWith('.')) {
          const fullPath = path.join(directory, file.name);
          const subPackages = await this.findPackages(fullPath);
          packages.push(...subPackages);
        }
      }
    } catch (error: any) {
      logger.error({ error: error.message, directory }, 'Failed to find packages');
      throw new Error(`Failed to find packages in ${directory}: ${error.message}`);
    }
    
    logger.debug({ count: packages.length, directory }, 'Found Swift packages');
    return packages;
  }
  
  /**
   * Find all projects and packages in a directory
   */
  async findAll(directory: string): Promise<(XcodeProject | SwiftPackage)[]> {
    const [projects, packages] = await Promise.all([
      this.findProjects(directory),
      this.findPackages(directory)
    ]);
    
    const all = [...projects, ...packages];
    logger.debug({ 
      totalCount: all.length, 
      projectCount: projects.length, 
      packageCount: packages.length,
      directory 
    }, 'Found all projects and packages');
    
    return all;
  }
}

// Export a default instance for convenience
export const xcode = new Xcode();
```

--------------------------------------------------------------------------------
/src/features/simulator/tests/unit/ShutdownSimulatorUseCase.unit.test.ts:
--------------------------------------------------------------------------------

```typescript
import { describe, it, expect, jest, beforeEach } from '@jest/globals';
import { ShutdownSimulatorUseCase } from '../../use-cases/ShutdownSimulatorUseCase.js';
import { ShutdownRequest } from '../../domain/ShutdownRequest.js';
import { DeviceId } from '../../../../shared/domain/DeviceId.js';
import { ShutdownResult, ShutdownOutcome, SimulatorNotFoundError, ShutdownCommandFailedError } from '../../domain/ShutdownResult.js';
import { SimulatorState } from '../../domain/SimulatorState.js';
import { ISimulatorLocator, ISimulatorControl, SimulatorInfo } from '../../../../application/ports/SimulatorPorts.js';

describe('ShutdownSimulatorUseCase', () => {
  let useCase: ShutdownSimulatorUseCase;
  let mockLocator: jest.Mocked<ISimulatorLocator>;
  let mockControl: jest.Mocked<ISimulatorControl>;

  beforeEach(() => {
    jest.clearAllMocks();
    
    mockLocator = {
      findSimulator: jest.fn<ISimulatorLocator['findSimulator']>(),
      findBootedSimulator: jest.fn<ISimulatorLocator['findBootedSimulator']>()
    };
    
    mockControl = {
      boot: jest.fn<ISimulatorControl['boot']>(),
      shutdown: jest.fn<ISimulatorControl['shutdown']>()
    };
    
    useCase = new ShutdownSimulatorUseCase(mockLocator, mockControl);
  });

  describe('execute', () => {
    it('should shutdown a booted simulator', async () => {
      // Arrange
      const request = ShutdownRequest.create(DeviceId.create('iPhone-15'));
      const simulatorInfo: SimulatorInfo = {
        id: 'ABC123',
        name: 'iPhone 15',
        state: SimulatorState.Booted,
        platform: 'iOS',
        runtime: 'iOS-17.0'
      };
      
      mockLocator.findSimulator.mockResolvedValue(simulatorInfo);
      mockControl.shutdown.mockResolvedValue(undefined);

      // Act
      const result = await useCase.execute(request);

      // Assert
      expect(mockLocator.findSimulator).toHaveBeenCalledWith('iPhone-15');
      expect(mockControl.shutdown).toHaveBeenCalledWith('ABC123');
      expect(result.outcome).toBe(ShutdownOutcome.Shutdown);
      expect(result.diagnostics.simulatorId).toBe('ABC123');
      expect(result.diagnostics.simulatorName).toBe('iPhone 15');
    });

    it('should handle already shutdown simulator', async () => {
      // Arrange
      const request = ShutdownRequest.create(DeviceId.create('iPhone-15'));
      const simulatorInfo: SimulatorInfo = {
        id: 'ABC123',
        name: 'iPhone 15',
        state: SimulatorState.Shutdown,
        platform: 'iOS',
        runtime: 'iOS-17.0'
      };
      
      mockLocator.findSimulator.mockResolvedValue(simulatorInfo);

      // Act
      const result = await useCase.execute(request);

      // Assert
      expect(mockControl.shutdown).not.toHaveBeenCalled();
      expect(result.outcome).toBe(ShutdownOutcome.AlreadyShutdown);
      expect(result.diagnostics.simulatorId).toBe('ABC123');
      expect(result.diagnostics.simulatorName).toBe('iPhone 15');
    });

    it('should shutdown a simulator in Booting state', async () => {
      // Arrange
      const request = ShutdownRequest.create(DeviceId.create('iPhone-15'));
      const simulatorInfo: SimulatorInfo = {
        id: 'ABC123',
        name: 'iPhone 15',
        state: SimulatorState.Booting,
        platform: 'iOS',
        runtime: 'iOS-17.0'
      };
      
      mockLocator.findSimulator.mockResolvedValue(simulatorInfo);
      mockControl.shutdown.mockResolvedValue(undefined);

      // Act
      const result = await useCase.execute(request);

      // Assert
      expect(mockControl.shutdown).toHaveBeenCalledWith('ABC123');
      expect(result.outcome).toBe(ShutdownOutcome.Shutdown);
    });

    it('should handle simulator in ShuttingDown state as already shutdown', async () => {
      // Arrange
      const request = ShutdownRequest.create(DeviceId.create('iPhone-15'));
      const simulatorInfo: SimulatorInfo = {
        id: 'ABC123',
        name: 'iPhone 15',
        state: SimulatorState.ShuttingDown,
        platform: 'iOS',
        runtime: 'iOS-17.0'
      };
      
      mockLocator.findSimulator.mockResolvedValue(simulatorInfo);

      // Act
      const result = await useCase.execute(request);

      // Assert
      expect(mockControl.shutdown).not.toHaveBeenCalled();
      expect(result.outcome).toBe(ShutdownOutcome.AlreadyShutdown);
      expect(result.diagnostics.simulatorId).toBe('ABC123');
      expect(result.diagnostics.simulatorName).toBe('iPhone 15');
    });

    it('should return failure when simulator not found', async () => {
      // Arrange
      const request = ShutdownRequest.create(DeviceId.create('non-existent'));
      mockLocator.findSimulator.mockResolvedValue(null);

      // Act
      const result = await useCase.execute(request);
      
      // Assert - Test behavior: simulator not found error
      expect(mockControl.shutdown).not.toHaveBeenCalled();
      expect(result.outcome).toBe(ShutdownOutcome.Failed);
      expect(result.diagnostics.error).toBeInstanceOf(SimulatorNotFoundError);
      expect((result.diagnostics.error as SimulatorNotFoundError).deviceId).toBe('non-existent');
    });

    it('should return failure on shutdown error', async () => {
      // Arrange
      const request = ShutdownRequest.create(DeviceId.create('iPhone-15'));
      const simulatorInfo: SimulatorInfo = {
        id: 'ABC123',
        name: 'iPhone 15',
        state: SimulatorState.Booted,
        platform: 'iOS',
        runtime: 'iOS-17.0'
      };
      
      const shutdownError = new Error('Device is busy');
      (shutdownError as any).stderr = 'Device is busy';
      
      mockLocator.findSimulator.mockResolvedValue(simulatorInfo);
      mockControl.shutdown.mockRejectedValue(shutdownError);

      // Act
      const result = await useCase.execute(request);

      // Assert
      expect(result.outcome).toBe(ShutdownOutcome.Failed);
      expect(result.diagnostics.error).toBeInstanceOf(ShutdownCommandFailedError);
      expect((result.diagnostics.error as ShutdownCommandFailedError).stderr).toBe('Device is busy');
      expect(result.diagnostics.simulatorId).toBe('ABC123');
      expect(result.diagnostics.simulatorName).toBe('iPhone 15');
    });

    it('should handle shutdown error without stderr', async () => {
      // Arrange
      const request = ShutdownRequest.create(DeviceId.create('iPhone-15'));
      const simulatorInfo: SimulatorInfo = {
        id: 'ABC123',
        name: 'iPhone 15',
        state: SimulatorState.Booted,
        platform: 'iOS',
        runtime: 'iOS-17.0'
      };
      
      const shutdownError = new Error('Unknown error');
      
      mockLocator.findSimulator.mockResolvedValue(simulatorInfo);
      mockControl.shutdown.mockRejectedValue(shutdownError);

      // Act
      const result = await useCase.execute(request);

      // Assert
      expect(result.outcome).toBe(ShutdownOutcome.Failed);
      expect(result.diagnostics.error).toBeInstanceOf(ShutdownCommandFailedError);
      expect((result.diagnostics.error as ShutdownCommandFailedError).stderr).toBe('Unknown error');
    });

    it('should handle shutdown error with empty message', async () => {
      // Arrange
      const request = ShutdownRequest.create(DeviceId.create('iPhone-15'));
      const simulatorInfo: SimulatorInfo = {
        id: 'ABC123',
        name: 'iPhone 15',
        state: SimulatorState.Booted,
        platform: 'iOS',
        runtime: 'iOS-17.0'
      };
      
      const shutdownError = {};
      
      mockLocator.findSimulator.mockResolvedValue(simulatorInfo);
      mockControl.shutdown.mockRejectedValue(shutdownError);

      // Act
      const result = await useCase.execute(request);

      // Assert
      expect(result.outcome).toBe(ShutdownOutcome.Failed);
      expect(result.diagnostics.error).toBeInstanceOf(ShutdownCommandFailedError);
      expect((result.diagnostics.error as ShutdownCommandFailedError).stderr).toBe('');
    });

    it('should shutdown simulator by UUID', async () => {
      // Arrange
      const uuid = '550e8400-e29b-41d4-a716-446655440000';
      const request = ShutdownRequest.create(DeviceId.create(uuid));
      const simulatorInfo: SimulatorInfo = {
        id: uuid,
        name: 'iPhone 15 Pro',
        state: SimulatorState.Booted,
        platform: 'iOS',
        runtime: 'iOS-17.0'
      };
      
      mockLocator.findSimulator.mockResolvedValue(simulatorInfo);
      mockControl.shutdown.mockResolvedValue(undefined);

      // Act
      const result = await useCase.execute(request);

      // Assert
      expect(mockLocator.findSimulator).toHaveBeenCalledWith(uuid);
      expect(mockControl.shutdown).toHaveBeenCalledWith(uuid);
      expect(result.outcome).toBe(ShutdownOutcome.Shutdown);
      expect(result.diagnostics.simulatorId).toBe(uuid);
    });
  });
});
```

--------------------------------------------------------------------------------
/src/presentation/tests/unit/BuildIssuesStrategy.unit.test.ts:
--------------------------------------------------------------------------------

```typescript
import { BuildIssuesStrategy } from '../../formatters/strategies/BuildIssuesStrategy.js';
import { BuildIssue } from '../../../features/build/domain/BuildIssue.js';

describe('BuildIssuesStrategy', () => {
  function createSUT(): BuildIssuesStrategy {
    return new BuildIssuesStrategy();
  }

  function createErrorWithIssues(issues: BuildIssue[], message?: string) {
    return { issues, message };
  }

  describe('canFormat', () => {
    it('should return true for error with BuildIssue array', () => {
      const sut = createSUT();
      const error = createErrorWithIssues([
        BuildIssue.error('Test error')
      ]);
      
      const result = sut.canFormat(error);
      
      expect(result).toBe(true);
    });

    it('should return true when at least one issue is BuildIssue instance', () => {
      const sut = createSUT();
      const error = {
        issues: [
          BuildIssue.error('Real issue'),
          { message: 'Not a BuildIssue' }
        ]
      };
      
      const result = sut.canFormat(error);
      
      expect(result).toBe(true);
    });

    it('should return false for error without issues property', () => {
      const sut = createSUT();
      const error = { message: 'Plain error' };
      
      const result = sut.canFormat(error);
      
      expect(result).toBe(false);
    });

    it('should return false when issues is not an array', () => {
      const sut = createSUT();
      const error = { issues: 'not an array' };
      
      const result = sut.canFormat(error);
      
      expect(result).toBe(false);
    });

    it('should return false when issues array is empty', () => {
      const sut = createSUT();
      const error = { issues: [] };
      
      const result = sut.canFormat(error);
      
      expect(result).toBe(false);
    });

    it('should return false when no issues are BuildIssue instances', () => {
      const sut = createSUT();
      const error = {
        issues: [
          { message: 'Plain object 1' },
          { message: 'Plain object 2' }
        ]
      };
      
      const result = sut.canFormat(error);
      
      expect(result).toBe(false);
    });
  });

  describe('format', () => {
    describe('when formatting errors only', () => {
      it('should format single error correctly', () => {
        const sut = createSUT();
        const error = createErrorWithIssues([
          BuildIssue.error('Cannot find module', 'src/main.ts', 10, 5)
        ]);
        
        const result = sut.format(error);
        
        const expected = 
`❌ Errors (1):
  • src/main.ts:10:5: Cannot find module`;
        
        expect(result).toBe(expected);
      });

      it('should format multiple errors with file information', () => {
        const sut = createSUT();
        const error = createErrorWithIssues([
          BuildIssue.error('Cannot find module', 'src/main.ts', 10, 5),
          BuildIssue.error('Type mismatch', 'src/utils.ts', 20, 3)
        ]);
        
        const result = sut.format(error);
        
        const expected = 
`❌ Errors (2):
  • src/main.ts:10:5: Cannot find module
  • src/utils.ts:20:3: Type mismatch`;
        
        expect(result).toBe(expected);
      });

      it('should limit to 5 errors and show count for more', () => {
        const sut = createSUT();
        const issues = Array.from({ length: 10 }, (_, i) => 
          BuildIssue.error(`Error ${i + 1}`, `file${i}.ts`)
        );
        const error = createErrorWithIssues(issues);
        
        const result = sut.format(error);
        
        expect(result).toContain('❌ Errors (10):');
        expect(result).toContain('file0.ts: Error 1');
        expect(result).toContain('file4.ts: Error 5');
        expect(result).not.toContain('file5.ts: Error 6');
        expect(result).toContain('... and 5 more errors');
      });

      it('should handle errors without file information', () => {
        const sut = createSUT();
        const error = createErrorWithIssues([
          BuildIssue.error('General build error'),
          BuildIssue.error('Another error without file')
        ]);
        
        const result = sut.format(error);
        
        const expected = 
`❌ Errors (2):
  • General build error
  • Another error without file`;
        
        expect(result).toBe(expected);
      });
    });

    describe('when formatting warnings only', () => {
      it('should format single warning correctly', () => {
        const sut = createSUT();
        const error = createErrorWithIssues([
          BuildIssue.warning('Deprecated API usage', 'src/legacy.ts', 15)
        ]);
        
        const result = sut.format(error);
        
        const expected = 
`⚠️ Warnings (1):
  • src/legacy.ts:15: Deprecated API usage`;
        
        expect(result).toBe(expected);
      });

      it('should limit to 3 warnings and show count for more', () => {
        const sut = createSUT();
        const issues = Array.from({ length: 6 }, (_, i) => 
          BuildIssue.warning(`Warning ${i + 1}`, `file${i}.ts`)
        );
        const error = createErrorWithIssues(issues);
        
        const result = sut.format(error);
        
        expect(result).toContain('⚠️ Warnings (6):');
        expect(result).toContain('file0.ts: Warning 1');
        expect(result).toContain('file2.ts: Warning 3');
        expect(result).not.toContain('file3.ts: Warning 4');
        expect(result).toContain('... and 3 more warnings');
      });
    });

    describe('when formatting mixed errors and warnings', () => {
      it('should show both sections separated by blank line', () => {
        const sut = createSUT();
        const error = createErrorWithIssues([
          BuildIssue.error('Error message', 'error.ts'),
          BuildIssue.warning('Warning message', 'warning.ts')
        ]);
        
        const result = sut.format(error);
        
        const expected = 
`❌ Errors (1):
  • error.ts: Error message

⚠️ Warnings (1):
  • warning.ts: Warning message`;
        
        expect(result).toBe(expected);
      });

      it('should handle many mixed issues correctly', () => {
        const sut = createSUT();
        const issues = [
          ...Array.from({ length: 7 }, (_, i) => 
            BuildIssue.error(`Error ${i + 1}`, `error${i}.ts`)
          ),
          ...Array.from({ length: 5 }, (_, i) => 
            BuildIssue.warning(`Warning ${i + 1}`, `warn${i}.ts`)
          )
        ];
        const error = createErrorWithIssues(issues);
        
        const result = sut.format(error);
        
        expect(result).toContain('❌ Errors (7):');
        expect(result).toContain('... and 2 more errors');
        expect(result).toContain('⚠️ Warnings (5):');
        expect(result).toContain('... and 2 more warnings');
        expect(result.split('\n\n')).toHaveLength(2); // Two sections
      });
    });

    describe('when handling edge cases', () => {
      it('should return fallback message when no issues', () => {
        const sut = createSUT();
        const error = createErrorWithIssues([]);
        
        const result = sut.format(error);
        
        expect(result).toBe('Build failed');
      });

      it('should use provided message as fallback when no issues', () => {
        const sut = createSUT();
        const error = createErrorWithIssues([], 'Custom build failure');
        
        const result = sut.format(error);
        
        expect(result).toBe('Custom build failure');
      });

      it('should handle mix of BuildIssue and non-BuildIssue objects', () => {
        const sut = createSUT();
        const error = {
          issues: [
            BuildIssue.error('Real error'),
            { type: 'error', message: 'Not a BuildIssue' }, // Will be filtered out
            BuildIssue.warning('Real warning')
          ]
        };
        
        const result = sut.format(error);
        
        // Only real BuildIssues should be processed
        expect(result).toContain('❌ Errors (1):');
        expect(result).toContain('Real error');
        expect(result).toContain('⚠️ Warnings (1):');
        expect(result).toContain('Real warning');
      });

      it('should handle issues with unknown types gracefully', () => {
        const sut = createSUT();
        const issues = [
          BuildIssue.error('Error'),
          BuildIssue.warning('Warning'),
          Object.assign(BuildIssue.error('Info'), { type: 'info' as any }) // Unknown type
        ];
        const error = createErrorWithIssues(issues);
        
        const result = sut.format(error);
        
        // Unknown type should be ignored
        expect(result).toContain('❌ Errors (1):');
        expect(result).toContain('⚠️ Warnings (1):');
        expect(result).not.toContain('info');
      });
    });
  });
});
```

--------------------------------------------------------------------------------
/src/utils/devices/Devices.ts:
--------------------------------------------------------------------------------

```typescript
import { execAsync } from '../../utils.js';
import { SimulatorDevice } from './SimulatorDevice.js';
import { createModuleLogger } from '../../logger.js';
import { Platform } from '../../types.js';

const logger = createModuleLogger('Devices');

/**
 * Device discovery and management.
 * Provides methods to find and list simulator devices.
 * Future-ready for physical device support.
 */
export class Devices {
  /**
   * Find a device by name or UDID
   */
  async find(nameOrId: string): Promise<SimulatorDevice | null> {
    try {
      const { stdout } = await execAsync('xcrun simctl list devices --json');
      const data = JSON.parse(stdout);
      
      // Collect all matching devices with their raw data for sorting
      const matchingDevices: Array<{device: SimulatorDevice, state: string, isAvailable: boolean}> = [];
      
      for (const [runtime, deviceList] of Object.entries(data.devices)) {
        for (const device of deviceList as any[]) {
          if (device.udid === nameOrId || device.name === nameOrId) {
            matchingDevices.push({
              device: new SimulatorDevice(
                device.udid,
                device.name,
                this.extractPlatformFromRuntime(runtime),
                runtime
              ),
              state: device.state,
              isAvailable: device.isAvailable
            });
          }
        }
      }
      
      if (matchingDevices.length === 0) {
        logger.debug({ nameOrId }, 'Device not found');
        return null;
      }
      
      // Sort to prefer available, booted, and newer devices
      this.sortDevices(matchingDevices);
      
      const selected = matchingDevices[0];
      
      // Warn if selected device is not available
      if (!selected.isAvailable) {
        logger.warn({ 
          nameOrId, 
          deviceId: selected.device.id,
          runtime: selected.device.runtime 
        }, 'Selected device is not available - may fail to boot');
      }
      
      return selected.device;
    } catch (error: any) {
      logger.error({ error: error.message }, 'Failed to find device');
      throw new Error(`Failed to find device: ${error.message}`);
    }
  }

  /**
   * List all available simulators, optionally filtered by platform
   */
  async listSimulators(platform?: Platform): Promise<SimulatorDevice[]> {
    try {
      const { stdout } = await execAsync('xcrun simctl list devices --json');
      const data = JSON.parse(stdout);
      const devices: SimulatorDevice[] = [];
      
      for (const [runtime, deviceList] of Object.entries(data.devices)) {
        const extractedPlatform = this.extractPlatformFromRuntime(runtime);
        
        // Filter by platform if specified
        if (platform && !this.matchesPlatform(extractedPlatform, platform)) {
          continue;
        }
        
        for (const device of deviceList as any[]) {
          if (device.isAvailable) {
            devices.push(new SimulatorDevice(
              device.udid,
              device.name,
              extractedPlatform,
              runtime
            ));
          }
        }
      }
      
      return devices;
    } catch (error: any) {
      logger.error({ error: error.message }, 'Failed to list simulators');
      throw new Error(`Failed to list simulators: ${error.message}`);
    }
  }

  /**
   * Get the currently booted simulator, if any
   */
  async getBooted(): Promise<SimulatorDevice | null> {
    try {
      const { stdout } = await execAsync('xcrun simctl list devices --json');
      const data = JSON.parse(stdout);
      
      for (const [runtime, deviceList] of Object.entries(data.devices)) {
        for (const device of deviceList as any[]) {
          if (device.state === 'Booted' && device.isAvailable) {
            return new SimulatorDevice(
              device.udid,
              device.name,
              this.extractPlatformFromRuntime(runtime),
              runtime
            );
          }
        }
      }
      
      logger.debug('No booted simulator found');
      return null;
    } catch (error: any) {
      logger.error({ error: error.message }, 'Failed to get booted device');
      throw new Error(`Failed to get booted device: ${error.message}`);
    }
  }

  /**
   * Find the first available device for a platform
   */
  async findFirstAvailable(platform: Platform): Promise<SimulatorDevice | null> {
    const devices = await this.listSimulators(platform);
    
    // First, look for an already booted device
    const booted = devices.find((d: SimulatorDevice) => d.isBooted());
    if (booted) {
      logger.debug({ device: booted.name, id: booted.id }, 'Using already booted device');
      return booted;
    }
    
    // Otherwise, return the first available device
    const available = devices[0];
    if (available) {
      logger.debug({ device: available.name, id: available.id }, 'Found available device');
      return available;
    }
    
    logger.debug({ platform }, 'No available devices for platform');
    return null;
  }

  /**
   * Extract platform from runtime string
   */
  private extractPlatformFromRuntime(runtime: string): string {
    const runtimeLower = runtime.toLowerCase();
    
    if (runtimeLower.includes('ios')) return 'iOS';
    if (runtimeLower.includes('tvos')) return 'tvOS';
    if (runtimeLower.includes('watchos')) return 'watchOS';
    if (runtimeLower.includes('xros') || runtimeLower.includes('visionos')) return 'visionOS';
    
    // Default fallback
    return 'iOS';
  }

  /**
   * Extract version number from runtime string
   */
  private getVersionFromRuntime(runtime: string): number {
    const match = runtime.match(/(\d+)[.-](\d+)/);
    return match ? parseFloat(`${match[1]}.${match[2]}`) : 0;
  }

  /**
   * Sort devices preferring: available > booted > newer iOS versions
   */
  private sortDevices(devices: Array<{device: SimulatorDevice, state: string, isAvailable: boolean}>): void {
    devices.sort((a, b) => {
      if (a.isAvailable !== b.isAvailable) return a.isAvailable ? -1 : 1;
      if (a.state === 'Booted' !== (b.state === 'Booted')) return a.state === 'Booted' ? -1 : 1;
      return this.getVersionFromRuntime(b.device.runtime) - this.getVersionFromRuntime(a.device.runtime);
    });
  }

  /**
   * Check if a runtime matches a platform
   */
  private matchesPlatform(extractedPlatform: string, targetPlatform: Platform): boolean {
    const extractedLower = extractedPlatform.toLowerCase();
    const targetLower = targetPlatform.toLowerCase();
    
    // Handle visionOS special case (internally called xrOS)
    if (targetLower === 'visionos') {
      return extractedLower === 'visionos' || extractedLower === 'xros';
    }
    
    return extractedLower === targetLower;
  }

  /**
   * Find an available device for a specific platform
   * Returns the first available device, preferring already booted ones
   */
  async findForPlatform(platform: Platform): Promise<SimulatorDevice | null> {
    logger.debug({ platform }, 'Finding device for platform');
    
    try {
      const devices = await this.listSimulators();
      
      // Filter devices for the requested platform
      const platformDevices = devices.filter((device: SimulatorDevice) => 
        this.matchesPlatform(this.extractPlatformFromRuntime(device.runtime), platform)
      );
      
      if (platformDevices.length === 0) {
        logger.warn({ platform }, 'No devices found for platform');
        return null;
      }
      
      // Try to find a booted device first
      const booted = await this.getBooted();
      if (booted && platformDevices.some(d => d.id === booted.id)) {
        logger.debug({ 
          device: booted.name, 
          id: booted.id
        }, 'Selected already booted device for platform');
        return booted;
      }
      
      // Sort by runtime version (prefer newer) and return the first
      platformDevices.sort((a, b) => 
        this.getVersionFromRuntime(b.runtime) - this.getVersionFromRuntime(a.runtime)
      );
      
      const selected = platformDevices[0];
      logger.debug({ 
        device: selected.name, 
        id: selected.id
      }, 'Selected device for platform');
      
      return selected;
    } catch (error: any) {
      logger.error({ error: error.message, platform }, 'Failed to find device for platform');
      throw new Error(`Failed to find device for platform ${platform}: ${error.message}`);
    }
  }

  /**
   * Future: List physical devices connected to the system
   * Currently returns empty array as physical device support is not yet implemented
   */
  async listPhysical(): Promise<any[]> {
    // Future implementation for physical devices
    // Would use xcrun devicectl or ios-deploy
    return [];
  }
}

// Export a default instance for convenience
export const devices = new Devices();
```

--------------------------------------------------------------------------------
/src/features/app-management/tests/e2e/InstallAppController.e2e.test.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * E2E Test for InstallAppController
 * 
 * Tests CRITICAL USER PATH with REAL simulators:
 * - Can the controller actually install apps on real simulators?
 * - Does it properly validate inputs through Clean Architecture layers?
 * - Does error handling work with real simulator failures?
 * 
 * NO MOCKS - uses real simulators and real test apps
 * This is an E2E test (10% of test suite) for critical user journeys
 * 
 * NOTE: This test requires Xcode and iOS simulators to be installed
 * It may be skipped in CI environments without proper setup
 */

import { describe, it, expect, beforeAll, afterAll, beforeEach } from '@jest/globals';
import { MCPController } from '../../../../presentation/interfaces/MCPController.js';
import { InstallAppControllerFactory } from '../../factories/InstallAppControllerFactory.js';
import { TestProjectManager } from '../../../../shared/tests/utils/TestProjectManager.js';
import { exec } from 'child_process';
import { promisify } from 'util';
import * as fs from 'fs';
import { SimulatorState } from '../../../simulator/domain/SimulatorState.js';
import { bootAndWaitForSimulator } from '../../../../shared/tests/utils/testHelpers.js';

const execAsync = promisify(exec);

describe('InstallAppController E2E', () => {
  let controller: MCPController;
  let testManager: TestProjectManager;
  let testDeviceId: string;
  let testAppPath: string;
  
  beforeAll(async () => {
    // Set up test project with built app
    testManager = new TestProjectManager();
    await testManager.setup();
    
    // Build the test app using TestProjectManager
    testAppPath = await testManager.buildApp('xcodeProject');
    
    // Get the latest iOS runtime
    const runtimesResult = await execAsync('xcrun simctl list runtimes --json');
    const runtimes = JSON.parse(runtimesResult.stdout);
    const iosRuntime = runtimes.runtimes.find((r: { platform: string }) => r.platform === 'iOS');
    
    if (!iosRuntime) {
      throw new Error('No iOS runtime found. Please install an iOS simulator runtime.');
    }
    
    // Create and boot a test simulator
    const createResult = await execAsync(
      `xcrun simctl create "TestSimulator-InstallApp" "iPhone 15" "${iosRuntime.identifier}"`
    );
    testDeviceId = createResult.stdout.trim();
    
    // Boot the simulator and wait for it to be ready
    await bootAndWaitForSimulator(testDeviceId, 30);
  });
  
  afterAll(async () => {
    // Clean up simulator
    if (testDeviceId) {
      try {
        await execAsync(`xcrun simctl shutdown "${testDeviceId}"`);
        await execAsync(`xcrun simctl delete "${testDeviceId}"`);
      } catch (error) {
        // Ignore cleanup errors
      }
    }
    
    // Clean up test project
    await testManager.cleanup();
  });
  
  beforeEach(() => {
    // Create controller with all real dependencies
    controller = InstallAppControllerFactory.create();
  });

  describe('install real apps on simulators', () => {
    it('should successfully install app on booted simulator', async () => {
      // Arrange - simulator is already booted from beforeAll
      
      // Act
      const result = await controller.execute({
        appPath: testAppPath,
        simulatorId: testDeviceId
      });

      // Assert
      expect(result).toMatchObject({
        content: expect.arrayContaining([
          expect.objectContaining({
            type: 'text',
            text: expect.stringContaining('Successfully installed')
          })
        ])
      });
      
      // Verify app is actually installed
      const listAppsResult = await execAsync(
        `xcrun simctl listapps "${testDeviceId}" | grep -i test || true`
      );
      expect(listAppsResult.stdout).toBeTruthy();
    });

    it('should install app on booted simulator when no ID specified', async () => {
      // Arrange - ensure our test simulator is the only booted one
      const devicesResult = await execAsync('xcrun simctl list devices --json');
      const devices = JSON.parse(devicesResult.stdout);
      
      // Shutdown all other booted simulators
      interface Device {
        state: string;
        udid: string;
      }
      for (const runtime of Object.values(devices.devices) as Device[][]) {
        for (const device of runtime) {
          if (device.state === SimulatorState.Booted && device.udid !== testDeviceId) {
            await execAsync(`xcrun simctl shutdown "${device.udid}"`);
          }
        }
      }
      
      // Act - install without specifying simulator ID
      const result = await controller.execute({
        appPath: testAppPath
      });

      // Assert
      expect(result).toMatchObject({
        content: expect.arrayContaining([
          expect.objectContaining({
            type: 'text',
            text: expect.stringContaining('Successfully installed')
          })
        ])
      });
    });

    it('should boot and install when simulator is shutdown', async () => {
      // Arrange - get iOS runtime for creating simulator
      const runtimesResult = await execAsync('xcrun simctl list runtimes --json');
      const runtimes = JSON.parse(runtimesResult.stdout);
      const iosRuntime = runtimes.runtimes.find((r: { platform: string }) => r.platform === 'iOS');

      // Create a new shutdown simulator
      const createResult = await execAsync(
        `xcrun simctl create "TestSimulator-Shutdown" "iPhone 14" "${iosRuntime.identifier}"`
      );
      const shutdownSimId = createResult.stdout.trim();

      try {
        // Act
        const result = await controller.execute({
          appPath: testAppPath,
          simulatorId: shutdownSimId
        });

        // Assert
        expect(result).toMatchObject({
          content: expect.arrayContaining([
            expect.objectContaining({
              type: 'text',
              text: expect.stringContaining('Successfully installed')
            })
          ])
        });

        // Verify simulator was booted
        const stateResult = await execAsync(
          `xcrun simctl list devices --json | jq -r '.devices[][] | select(.udid=="${shutdownSimId}") | .state'`
        );
        expect(stateResult.stdout.trim()).toBe(SimulatorState.Booted);
      } finally {
        // Clean up
        await execAsync(`xcrun simctl shutdown "${shutdownSimId}" || true`);
        await execAsync(`xcrun simctl delete "${shutdownSimId}"`);
      }
    }, 300000);
  });

  describe('error handling with real simulators', () => {
    it('should fail when app path does not exist', async () => {
      // Arrange
      const nonExistentPath = '/path/that/does/not/exist.app';
      
      // Act
      const result = await controller.execute({
        appPath: nonExistentPath,
        simulatorId: testDeviceId
      });
      
      // Assert - error message from xcrun simctl install (multi-line in real E2E)
      expect(result.content[0].text).toContain('❌');
      expect(result.content[0].text).toContain('No such file or directory');  
    });

    it('should fail when app path is not an app bundle', async () => {
      // Arrange - use a regular file instead of .app
      const invalidAppPath = testManager.paths.xcodeProjectXCTestPath;
      
      // Act
      const result = await controller.execute({
        appPath: invalidAppPath,
        simulatorId: testDeviceId
      });
      
      // Assert - validation error formatted with ❌
      expect(result.content[0].text).toBe('❌ App path must end with .app');
    });

    it('should fail when simulator does not exist', async () => {
      // Arrange
      const nonExistentSimulator = 'non-existent-simulator-id';
      
      // Act
      const result = await controller.execute({
        appPath: testAppPath,
        simulatorId: nonExistentSimulator
      });
      
      // Assert
      expect(result.content[0].text).toBe('❌ Simulator not found: non-existent-simulator-id');
    });

    it('should fail when no booted simulator and no ID specified', async () => {
      // Arrange - shutdown all simulators
      await execAsync('xcrun simctl shutdown all');
      
      try {
        // Act
        const result = await controller.execute({
          appPath: testAppPath
        });
        
        // Assert
        expect(result.content[0].text).toBe('❌ No booted simulator found. Please boot a simulator first or specify a simulator ID.');
      } finally {
        // Re-boot our test simulator for other tests
        await execAsync(`xcrun simctl boot "${testDeviceId}"`);
        await new Promise(resolve => setTimeout(resolve, 3000));
      }
    });
  });

  describe('simulator name handling', () => {
    it('should handle simulator specified by name', async () => {
      // Act - use simulator name instead of UUID
      const result = await controller.execute({
        appPath: testAppPath,
        simulatorId: 'TestSimulator-InstallApp'
      });

      // Assert
      expect(result).toMatchObject({
        content: expect.arrayContaining([
          expect.objectContaining({
            type: 'text',
            text: expect.stringContaining('Successfully installed')
          })
        ])
      });
    });
  });
});
```

--------------------------------------------------------------------------------
/src/shared/tests/utils/mockHelpers.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Mock helpers for unit testing
 * Provides utilities to mock subprocess execution, filesystem operations, and MCP interactions
 */

import { jest } from '@jest/globals';
import type { ExecSyncOptions } from 'child_process';

/**
 * Mock response builder for subprocess commands
 */
export class SubprocessMock {
  private responses = new Map<string, { stdout?: string; stderr?: string; error?: Error }>();

  /**
   * Register a mock response for a command pattern
   */
  mockCommand(pattern: string | RegExp, response: { stdout?: string; stderr?: string; error?: Error }) {
    const key = pattern instanceof RegExp ? pattern.source : pattern;
    this.responses.set(key, response);
  }

  /**
   * Get mock implementation for execSync
   */
  getExecSyncMock() {
    return jest.fn((command: string, options?: ExecSyncOptions) => {
      // Find matching response
      for (const [pattern, response] of this.responses) {
        const regex = new RegExp(pattern);
        if (regex.test(command)) {
          if (response.error) {
            throw response.error;
          }
          return response.stdout || '';
        }
      }
      throw new Error(`No mock defined for command: ${command}`);
    });
  }

  /**
   * Get mock implementation for spawn
   */
  getSpawnMock() {
    return jest.fn((command: string, args: string[], options?: any) => {
      const fullCommand = `${command} ${args.join(' ')}`;
      
      // Find matching response
      for (const [pattern, response] of this.responses) {
        const regex = new RegExp(pattern);
        if (regex.test(fullCommand)) {
          return {
            stdout: {
              on: jest.fn((event: string, cb: Function) => {
                if (event === 'data' && response.stdout) {
                  cb(Buffer.from(response.stdout));
                }
              })
            },
            stderr: {
              on: jest.fn((event: string, cb: Function) => {
                if (event === 'data' && response.stderr) {
                  cb(Buffer.from(response.stderr));
                }
              })
            },
            on: jest.fn((event: string, cb: Function) => {
              if (event === 'close') {
                cb(response.error ? 1 : 0);
              }
              if (event === 'error' && response.error) {
                cb(response.error);
              }
            }),
            kill: jest.fn()
          };
        }
      }
      
      throw new Error(`No mock defined for command: ${fullCommand}`);
    });
  }

  /**
   * Clear all mocked responses
   */
  clear() {
    this.responses.clear();
  }
}

/**
 * Mock filesystem operations
 */
export class FilesystemMock {
  private files = new Map<string, string | Buffer>();
  private directories = new Set<string>();

  /**
   * Mock a file with content
   */
  mockFile(path: string, content: string | Buffer) {
    this.files.set(path, content);
    // Also add parent directories
    const parts = path.split('/');
    for (let i = 1; i < parts.length; i++) {
      this.directories.add(parts.slice(0, i).join('/'));
    }
  }

  /**
   * Mock a directory
   */
  mockDirectory(path: string) {
    this.directories.add(path);
  }

  /**
   * Get mock for existsSync
   */
  getExistsSyncMock() {
    return jest.fn((path: string) => {
      return this.files.has(path) || this.directories.has(path);
    });
  }

  /**
   * Get mock for readFileSync
   */
  getReadFileSyncMock() {
    return jest.fn((path: string, encoding?: BufferEncoding) => {
      if (!this.files.has(path)) {
        const error: any = new Error(`ENOENT: no such file or directory, open '${path}'`);
        error.code = 'ENOENT';
        throw error;
      }
      const content = this.files.get(path)!;
      return encoding && content instanceof Buffer ? content.toString(encoding) : content;
    });
  }

  /**
   * Get mock for readdirSync
   */
  getReaddirSyncMock() {
    return jest.fn((path: string) => {
      if (!this.directories.has(path)) {
        const error: any = new Error(`ENOENT: no such file or directory, scandir '${path}'`);
        error.code = 'ENOENT';
        throw error;
      }
      
      // Return files and subdirectories in this directory
      const items = new Set<string>();
      const pathWithSlash = path.endsWith('/') ? path : `${path}/`;
      
      for (const file of this.files.keys()) {
        if (file.startsWith(pathWithSlash)) {
          const relative = file.slice(pathWithSlash.length);
          const firstPart = relative.split('/')[0];
          items.add(firstPart);
        }
      }
      
      for (const dir of this.directories) {
        if (dir.startsWith(pathWithSlash) && dir !== path) {
          const relative = dir.slice(pathWithSlash.length);
          const firstPart = relative.split('/')[0];
          items.add(firstPart);
        }
      }
      
      return Array.from(items);
    });
  }

  /**
   * Clear all mocked files and directories
   */
  clear() {
    this.files.clear();
    this.directories.clear();
  }
}

/**
 * Common mock responses for Xcode/simulator commands
 */
export const commonMockResponses = {
  /**
   * Mock successful xcodebuild
   */
  xcodebuildSuccess: (scheme: string = 'TestApp') => ({
    stdout: `Build succeeded\nScheme: ${scheme}\n** BUILD SUCCEEDED **`,
    stderr: ''
  }),

  /**
   * Mock xcodebuild failure
   */
  xcodebuildFailure: (error: string = 'Build failed') => ({
    stdout: '',
    stderr: `error: ${error}\n** BUILD FAILED **`,
    error: new Error(`Command failed: xcodebuild\n${error}`)
  }),

  /**
   * Mock scheme not found error
   */
  schemeNotFound: (scheme: string) => ({
    stdout: '',
    stderr: `xcodebuild: error: The project does not contain a scheme named "${scheme}".`,
    error: new Error(`xcodebuild: error: The project does not contain a scheme named "${scheme}".`)
  }),

  /**
   * Mock simulator list
   */
  simulatorList: (devices: Array<{ name: string; udid: string; state: string }> = []) => ({
    stdout: JSON.stringify({
      devices: {
        'com.apple.CoreSimulator.SimRuntime.iOS-17-0': devices.map(d => ({
          ...d,
          isAvailable: true,
          deviceTypeIdentifier: 'com.apple.CoreSimulator.SimDeviceType.iPhone-15'
        }))
      }
    }),
    stderr: ''
  }),

  /**
   * Mock simulator boot success
   */
  simulatorBootSuccess: (deviceId: string) => ({
    stdout: `Device ${deviceId} booted successfully`,
    stderr: ''
  }),

  /**
   * Mock simulator already booted
   */
  simulatorAlreadyBooted: (deviceId: string) => ({
    stdout: '',
    stderr: `Device ${deviceId} is already booted`,
    error: new Error(`Device ${deviceId} is already booted`)
  }),

  /**
   * Mock app installation success
   */
  appInstallSuccess: (appPath: string, deviceId: string) => ({
    stdout: `Successfully installed ${appPath} on ${deviceId}`,
    stderr: ''
  }),

  /**
   * Mock list schemes
   */
  schemesList: (schemes: string[] = ['TestApp', 'TestAppTests']) => ({
    stdout: JSON.stringify({ project: { schemes: schemes } }),
    stderr: ''
  }),

  /**
   * Mock swift build success
   */
  swiftBuildSuccess: () => ({
    stdout: 'Building for debugging...\nBuild complete!',
    stderr: ''
  }),

  /**
   * Mock swift test success
   */
  swiftTestSuccess: (passed: number = 10, failed: number = 0) => ({
    stdout: `Test Suite 'All tests' passed at 2024-01-01\nExecuted ${passed + failed} tests, with ${failed} failures`,
    stderr: ''
  })
};

/**
 * Create a mock MCP client for testing
 */
export function createMockMCPClient() {
  return {
    request: jest.fn(),
    notify: jest.fn(),
    close: jest.fn(),
    on: jest.fn(),
    off: jest.fn()
  };
}

/**
 * Helper to setup common mocks for a test
 */
export function setupCommonMocks() {
  const subprocess = new SubprocessMock();
  const filesystem = new FilesystemMock();
  
  // Mock child_process
  jest.mock('child_process', () => ({
    execSync: subprocess.getExecSyncMock(),
    spawn: subprocess.getSpawnMock()
  }));
  
  // Mock fs
  jest.mock('fs', () => ({
    existsSync: filesystem.getExistsSyncMock(),
    readFileSync: filesystem.getReadFileSyncMock(),
    readdirSync: filesystem.getReaddirSyncMock()
  }));
  
  return { subprocess, filesystem };
}

/**
 * Helper to create a mock Xcode instance
 */
export function createMockXcode() {
  return {
    open: jest.fn().mockReturnValue({
      buildWithConfiguration: jest.fn<() => Promise<any>>().mockResolvedValue({
        success: true,
        stdout: 'Build succeeded',
        stderr: ''
      }),
      test: jest.fn<() => Promise<any>>().mockResolvedValue({
        success: true,
        stdout: 'Test succeeded',
        stderr: ''
      }),
      run: jest.fn<() => Promise<any>>().mockResolvedValue({
        success: true,
        stdout: 'Run succeeded',
        stderr: ''
      }),
      clean: jest.fn<() => Promise<any>>().mockResolvedValue({
        success: true,
        stdout: 'Clean succeeded',
        stderr: ''
      }),
      archive: jest.fn<() => Promise<any>>().mockResolvedValue({
        success: true,
        stdout: 'Archive succeeded',
        stderr: ''
      })
    })
  };
}
```

--------------------------------------------------------------------------------
/src/shared/tests/utils/TestProjectManager.ts:
--------------------------------------------------------------------------------

```typescript
import { existsSync, rmSync, readdirSync, statSync } from 'fs';
import { join, resolve } from 'path';
import { exec } from 'child_process';
import { promisify } from 'util';
import { createModuleLogger } from '../../../logger';
import { config } from '../../../config';
import { TestEnvironmentCleaner } from './TestEnvironmentCleaner';
import { gitResetTestArtifacts } from './gitResetTestArtifacts';

const execAsync = promisify(exec);

const logger = createModuleLogger('TestProjectManager');

export class TestProjectManager {
  private testArtifactsDir: string;
  private xcodeProjectPath: string;
  private xcodeProjectSwiftTestingPath: string;
  private swiftPackageXCTestPath: string;
  private swiftPackageSwiftTestingPath: string;
  private workspacePath: string;
  private watchOSProjectPath: string;

  constructor() {
    // Use the actual test artifacts directory
    this.testArtifactsDir = resolve(process.cwd(), 'test_artifacts');
    
    // Set up paths to real test projects
    // Xcode projects
    this.xcodeProjectPath = join(this.testArtifactsDir, 'TestProjectXCTest', 'TestProjectXCTest.xcodeproj');
    this.xcodeProjectSwiftTestingPath = join(this.testArtifactsDir, 'TestProjectSwiftTesting', 'TestProjectSwiftTesting.xcodeproj');
    
    // Swift packages
    this.swiftPackageXCTestPath = join(this.testArtifactsDir, 'TestSwiftPackageXCTest');
    this.swiftPackageSwiftTestingPath = join(this.testArtifactsDir, 'TestSwiftPackageSwiftTesting');
    
    // Workspace and other projects
    this.workspacePath = join(this.testArtifactsDir, 'Test.xcworkspace');
    this.watchOSProjectPath = join(this.testArtifactsDir, 'TestProjectWatchOS', 'TestProjectWatchOS.xcodeproj');
  }

  get paths() {
    return {
      testProjectDir: this.testArtifactsDir,
      // Xcode projects
      xcodeProjectXCTestDir: join(this.testArtifactsDir, 'TestProjectXCTest'),
      xcodeProjectXCTestPath: this.xcodeProjectPath,
      xcodeProjectSwiftTestingDir: join(this.testArtifactsDir, 'TestProjectSwiftTesting'),
      xcodeProjectSwiftTestingPath: this.xcodeProjectSwiftTestingPath,
      // Swift packages
      swiftPackageXCTestDir: this.swiftPackageXCTestPath, // Default to XCTest for backward compat
      swiftPackageSwiftTestingDir: this.swiftPackageSwiftTestingPath,
      // Other
      workspaceDir: this.testArtifactsDir,
      derivedDataPath: join(this.testArtifactsDir, 'DerivedData'),
      workspacePath: this.workspacePath,
      watchOSProjectPath: this.watchOSProjectPath,
      watchOSProjectDir: join(this.testArtifactsDir, 'TestProjectWatchOS')
    };
  }

  get schemes() {
    return {
      xcodeProject: 'TestProjectXCTest',
      xcodeProjectSwiftTesting: 'TestProjectSwiftTesting',
      workspace: 'TestProjectXCTest',  // The workspace uses the same scheme
      swiftPackageXCTest: 'TestSwiftPackageXCTest',
      swiftPackageSwiftTesting: 'TestSwiftPackageSwiftTesting',
      watchOSProject: 'TestProjectWatchOS Watch App'  // The watchOS app scheme
    };
  }

  get targets() {
    return {
      xcodeProject: {
        app: 'TestProjectXCTest',
        unitTests: 'TestProjectXCTestTests',
        uiTests: 'TestProjectXCTestUITests'
      },
      xcodeProjectSwiftTesting: {
        app: 'TestProjectSwiftTesting',
        unitTests: 'TestProjectSwiftTestingTests',
        uiTests: 'TestProjectSwiftTestingUITests'
      },
      watchOSProject: {
        app: 'TestProjectWatchOS Watch App',
        tests: 'TestProjectWatchOS Watch AppTests'
      }
    };
  }

  async setup() {
    // Clean up any leftover build artifacts before starting
    this.cleanBuildArtifacts();
  }

  /**
   * Build a test app for simulator testing
   * Uses optimized settings to avoid hanging on code signing or large output
   * @param projectType Which test project to build (defaults to 'xcodeProject')
   * @returns Path to the built .app bundle
   */
  async buildApp(projectType: 'xcodeProject' | 'xcodeProjectSwiftTesting' | 'watchOSProject' = 'xcodeProject'): Promise<string> {
    let projectPath: string;
    let scheme: string;

    switch (projectType) {
      case 'xcodeProject':
        projectPath = this.xcodeProjectPath;
        scheme = this.schemes.xcodeProject;
        break;
      case 'xcodeProjectSwiftTesting':
        projectPath = this.xcodeProjectSwiftTestingPath;
        scheme = this.schemes.xcodeProjectSwiftTesting;
        break;
      case 'watchOSProject':
        projectPath = this.watchOSProjectPath;
        scheme = this.schemes.watchOSProject;
        break;
    }

    // Build with optimized settings for testing
    // Use generic/platform but with ONLY_ACTIVE_ARCH to build for current architecture only
    await execAsync(
      `xcodebuild -project "${projectPath}" ` +
      `-scheme "${scheme}" ` +
      `-configuration Debug ` +
      `-destination 'generic/platform=iOS Simulator' ` +
      `-derivedDataPath "${this.paths.derivedDataPath}" ` +
      `ONLY_ACTIVE_ARCH=YES ` +
      `CODE_SIGNING_ALLOWED=NO ` +
      `CODE_SIGNING_REQUIRED=NO ` +
      `build`,
      { maxBuffer: 50 * 1024 * 1024 }
    );

    // Find the built app
    const findResult = await execAsync(
      `find "${this.paths.derivedDataPath}" -name "*.app" -type d | head -1`
    );
    const appPath = findResult.stdout.trim();

    if (!appPath || !existsSync(appPath)) {
      throw new Error('Failed to find built app');
    }

    return appPath;
  }

  private cleanBuildArtifacts() {
    // Clean DerivedData
    TestEnvironmentCleaner.cleanupTestEnvironment()
    
    // Clean .build directories (for SPM)
    const buildDirs = [
      join(this.swiftPackageXCTestPath, '.build'),
      join(this.swiftPackageSwiftTestingPath, '.build'),
      join(this.testArtifactsDir, '.build')
    ];

    buildDirs.forEach(dir => {
      if (existsSync(dir)) {
        rmSync(dir, { recursive: true, force: true });
      }
    });

    // Clean xcresult bundles (test results)
    this.cleanTestResults();

    // Clean any .swiftpm directories
    const swiftpmDirs = this.findDirectories(this.testArtifactsDir, '.swiftpm');
    swiftpmDirs.forEach(dir => {
      rmSync(dir, { recursive: true, force: true });
    });

    // Clean build folders in Xcode projects
    const xcodeProjects = [
      join(this.testArtifactsDir, 'TestProjectXCTest'),
      join(this.testArtifactsDir, 'TestProjectSwiftTesting'),
      join(this.testArtifactsDir, 'TestProjectWatchOS')
    ];

    xcodeProjects.forEach(projectDir => {
      const buildDir = join(projectDir, 'build');
      if (existsSync(buildDir)) {
        rmSync(buildDir, { recursive: true, force: true });
      }
    });
  }

  cleanTestResults() {
    // Find and remove all .xcresult bundles
    const xcresultFiles = this.findFiles(this.testArtifactsDir, '.xcresult');
    xcresultFiles.forEach(file => {
      rmSync(file, { recursive: true, force: true });
    });

    // Clean test output files
    const testOutputFiles = [
      join(this.swiftPackageXCTestPath, 'test-output.txt'),
      join(this.swiftPackageSwiftTestingPath, 'test-output.txt'),
      join(this.testArtifactsDir, 'test-results.json')
    ];

    testOutputFiles.forEach(file => {
      if (existsSync(file)) {
        rmSync(file, { force: true });
      }
    });
  }

  cleanup() {
    // Use git to restore test_artifacts to pristine state
    gitResetTestArtifacts();
    
    // ALWAYS clean build artifacts including MCP-Xcode DerivedData
    this.cleanBuildArtifacts();
    
    // Also clean DerivedData in project root
    const projectDerivedData = join(process.cwd(), 'DerivedData');
    if (existsSync(projectDerivedData)) {
      rmSync(projectDerivedData, { recursive: true, force: true });
    }
  }

  private findFiles(dir: string, extension: string): string[] {
    const results: string[] = [];
    
    if (!existsSync(dir)) {
      return results;
    }

    try {
      const files = readdirSync(dir);
      
      for (const file of files) {
        const fullPath = join(dir, file);
        const stat = statSync(fullPath);
        
        if (stat.isDirectory()) {
          // Skip hidden directories and node_modules
          if (!file.startsWith('.') && file !== 'node_modules') {
            results.push(...this.findFiles(fullPath, extension));
          }
        } else if (file.endsWith(extension)) {
          results.push(fullPath);
        }
      }
    } catch (error) {
      logger.error({ error, dir }, 'Error scanning directory');
    }
    
    return results;
  }

  private findDirectories(dir: string, name: string): string[] {
    const results: string[] = [];
    
    if (!existsSync(dir)) {
      return results;
    }

    try {
      const files = readdirSync(dir);
      
      for (const file of files) {
        const fullPath = join(dir, file);
        const stat = statSync(fullPath);
        
        if (stat.isDirectory()) {
          if (file === name) {
            results.push(fullPath);
          } else if (!file.startsWith('.') && file !== 'node_modules') {
            // Recursively search subdirectories
            results.push(...this.findDirectories(fullPath, name));
          }
        }
      }
    } catch (error) {
      logger.error({ error, dir }, 'Error scanning directory');
    }
    
    return results;
  }
}
```

--------------------------------------------------------------------------------
/src/features/simulator/tests/unit/SimulatorLocatorAdapter.unit.test.ts:
--------------------------------------------------------------------------------

```typescript
import { describe, it, expect, jest, beforeEach } from '@jest/globals';
import { SimulatorLocatorAdapter } from '../../infrastructure/SimulatorLocatorAdapter.js';
import { ICommandExecutor } from '../../../../application/ports/CommandPorts.js';
import { SimulatorState } from '../../domain/SimulatorState.js';

describe('SimulatorLocatorAdapter', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  function createSUT() {
    const mockExecute = jest.fn<ICommandExecutor['execute']>();
    const mockExecutor: ICommandExecutor = {
      execute: mockExecute
    };
    const sut = new SimulatorLocatorAdapter(mockExecutor);
    return { sut, mockExecute };
  }

  function createDeviceListOutput(devices: any = {}) {
    return JSON.stringify({ devices });
  }

  describe('findSimulator', () => {
    describe('finding by UUID', () => {
      it('should find simulator by exact UUID match', async () => {
        // Arrange
        const { sut, mockExecute } = createSUT();
        const deviceList = {
          'com.apple.CoreSimulator.SimRuntime.iOS-17-0': [{
            udid: 'ABC-123-EXACT',
            name: 'iPhone 15',
            state: 'Shutdown',
            isAvailable: true
          }]
        };
        mockExecute.mockResolvedValue({
          stdout: createDeviceListOutput(deviceList),
          stderr: '',
          exitCode: 0
        });

        // Act
        const result = await sut.findSimulator('ABC-123-EXACT');

        // Assert
        expect(result).toEqual({
          id: 'ABC-123-EXACT',
          name: 'iPhone 15',
          state: SimulatorState.Shutdown,
          platform: 'iOS',
          runtime: 'com.apple.CoreSimulator.SimRuntime.iOS-17-0'
        });
      });
    });

    describe('finding by name with multiple matches', () => {
      it('should prefer booted device when multiple devices have same name', async () => {
        // Arrange
        const { sut, mockExecute } = createSUT();
        const deviceList = {
          'com.apple.CoreSimulator.SimRuntime.iOS-16-0': [{
            udid: 'OLD-123',
            name: 'iPhone 15 Pro',
            state: 'Shutdown',
            isAvailable: true
          }],
          'com.apple.CoreSimulator.SimRuntime.iOS-17-0': [{
            udid: 'NEW-456',
            name: 'iPhone 15 Pro',
            state: 'Booted',
            isAvailable: true
          }]
        };
        mockExecute.mockResolvedValue({
          stdout: createDeviceListOutput(deviceList),
          stderr: '',
          exitCode: 0
        });

        // Act
        const result = await sut.findSimulator('iPhone 15 Pro');

        // Assert
        expect(result?.id).toBe('NEW-456'); // Should pick booted one
      });

      it('should prefer newer runtime when multiple shutdown devices have same name', async () => {
        // Arrange
        const { sut, mockExecute } = createSUT();
        const deviceList = {
          'com.apple.CoreSimulator.SimRuntime.iOS-16-4': [{
            udid: 'OLD-123',
            name: 'iPhone 14',
            state: 'Shutdown',
            isAvailable: true
          }],
          'com.apple.CoreSimulator.SimRuntime.iOS-17-2': [{
            udid: 'NEW-456',
            name: 'iPhone 14',
            state: 'Shutdown',
            isAvailable: true
          }],
          'com.apple.CoreSimulator.SimRuntime.iOS-15-0': [{
            udid: 'OLDER-789',
            name: 'iPhone 14',
            state: 'Shutdown',
            isAvailable: true
          }]
        };
        mockExecute.mockResolvedValue({
          stdout: createDeviceListOutput(deviceList),
          stderr: '',
          exitCode: 0
        });

        // Act
        const result = await sut.findSimulator('iPhone 14');

        // Assert
        expect(result?.id).toBe('NEW-456'); // Should pick iOS 17.2
        expect(result?.runtime).toContain('iOS-17-2');
      });
    });

    describe('availability handling', () => {
      it('should skip unavailable devices', async () => {
        // Arrange
        const { sut, mockExecute } = createSUT();
        const deviceList = {
          'com.apple.CoreSimulator.SimRuntime.iOS-17-0': [{
            udid: 'UNAVAIL-123',
            name: 'iPhone 15',
            state: 'Shutdown',
            isAvailable: false
          }]
        };
        mockExecute.mockResolvedValue({
          stdout: createDeviceListOutput(deviceList),
          stderr: '',
          exitCode: 0
        });

        // Act
        const result = await sut.findSimulator('iPhone 15');

        // Assert
        expect(result).toBeNull();
      });
    });

    describe('platform extraction', () => {
      it('should correctly identify iOS platform', async () => {
        // Arrange
        const { sut, mockExecute } = createSUT();
        const deviceList = {
          'com.apple.CoreSimulator.SimRuntime.iOS-17-0': [{
            udid: 'IOS-123',
            name: 'iPhone 15',
            state: 'Shutdown',
            isAvailable: true
          }]
        };
        mockExecute.mockResolvedValue({
          stdout: createDeviceListOutput(deviceList),
          stderr: '',
          exitCode: 0
        });

        // Act
        const result = await sut.findSimulator('IOS-123');

        // Assert
        expect(result?.platform).toBe('iOS');
      });

      it('should correctly identify tvOS platform', async () => {
        // Arrange
        const { sut, mockExecute } = createSUT();
        const deviceList = {
          'com.apple.CoreSimulator.SimRuntime.tvOS-17-0': [{
            udid: 'TV-123',
            name: 'Apple TV',
            state: 'Shutdown',
            isAvailable: true
          }]
        };
        mockExecute.mockResolvedValue({
          stdout: createDeviceListOutput(deviceList),
          stderr: '',
          exitCode: 0
        });

        // Act
        const result = await sut.findSimulator('TV-123');

        // Assert
        expect(result?.platform).toBe('tvOS');
      });

      it('should correctly identify visionOS platform', async () => {
        // Arrange
        const { sut, mockExecute } = createSUT();
        const deviceList = {
          'com.apple.CoreSimulator.SimRuntime.xrOS-1-0': [{
            udid: 'VISION-123',
            name: 'Apple Vision Pro',
            state: 'Shutdown',
            isAvailable: true
          }]
        };
        mockExecute.mockResolvedValue({
          stdout: createDeviceListOutput(deviceList),
          stderr: '',
          exitCode: 0
        });

        // Act
        const result = await sut.findSimulator('VISION-123');

        // Assert
        expect(result?.platform).toBe('visionOS');
      });
    });
  });

  describe('findBootedSimulator', () => {
    describe('with single booted simulator', () => {
      it('should return the booted simulator', async () => {
        // Arrange
        const { sut, mockExecute } = createSUT();
        const deviceList = {
          'com.apple.CoreSimulator.SimRuntime.iOS-17-0': [
            {
              udid: 'SHUT-123',
              name: 'iPhone 15',
              state: 'Shutdown',
              isAvailable: true
            },
            {
              udid: 'BOOT-456',
              name: 'iPhone 14',
              state: 'Booted',
              isAvailable: true
            }
          ]
        };
        mockExecute.mockResolvedValue({
          stdout: createDeviceListOutput(deviceList),
          stderr: '',
          exitCode: 0
        });

        // Act
        const result = await sut.findBootedSimulator();

        // Assert
        expect(result).toEqual({
          id: 'BOOT-456',
          name: 'iPhone 14',
          state: SimulatorState.Booted,
          platform: 'iOS',
          runtime: 'com.apple.CoreSimulator.SimRuntime.iOS-17-0'
        });
      });
    });

    describe('with multiple booted simulators', () => {
      it('should throw error indicating multiple booted simulators', async () => {
        // Arrange
        const { sut, mockExecute } = createSUT();
        const deviceList = {
          'com.apple.CoreSimulator.SimRuntime.iOS-17-0': [
            {
              udid: 'BOOT-1',
              name: 'iPhone 15',
              state: 'Booted',
              isAvailable: true
            },
            {
              udid: 'BOOT-2',
              name: 'iPhone 14',
              state: 'Booted',
              isAvailable: true
            }
          ]
        };
        mockExecute.mockResolvedValue({
          stdout: createDeviceListOutput(deviceList),
          stderr: '',
          exitCode: 0
        });

        // Act & Assert
        await expect(sut.findBootedSimulator())
          .rejects.toThrow('Multiple booted simulators found (2). Please specify a simulator ID.');
      });
    });

    describe('with no booted simulators', () => {
      it('should return null', async () => {
        // Arrange
        const { sut, mockExecute } = createSUT();
        const deviceList = {
          'com.apple.CoreSimulator.SimRuntime.iOS-17-0': [{
            udid: 'SHUT-123',
            name: 'iPhone 15',
            state: 'Shutdown',
            isAvailable: true
          }]
        };
        mockExecute.mockResolvedValue({
          stdout: createDeviceListOutput(deviceList),
          stderr: '',
          exitCode: 0
        });

        // Act
        const result = await sut.findBootedSimulator();

        // Assert
        expect(result).toBeNull();
      });
    });

    describe('with unavailable booted device', () => {
      it('should skip unavailable devices even if booted', async () => {
        // Arrange
        const { sut, mockExecute } = createSUT();
        const deviceList = {
          'com.apple.CoreSimulator.SimRuntime.iOS-17-0': [{
            udid: 'BOOT-123',
            name: 'iPhone 15',
            state: 'Booted',
            isAvailable: false
          }]
        };
        mockExecute.mockResolvedValue({
          stdout: createDeviceListOutput(deviceList),
          stderr: '',
          exitCode: 0
        });

        // Act
        const result = await sut.findBootedSimulator();

        // Assert
        expect(result).toBeNull();
      });
    });
  });
});
```

--------------------------------------------------------------------------------
/src/features/simulator/tests/integration/BootSimulatorController.integration.test.ts:
--------------------------------------------------------------------------------

```typescript
import { describe, it, expect, jest, beforeEach } from '@jest/globals';
import { MCPController } from '../../../../presentation/interfaces/MCPController.js';
import { BootSimulatorControllerFactory } from '../../factories/BootSimulatorControllerFactory.js';
import { SimulatorState } from '../../domain/SimulatorState.js';
import { exec } from 'child_process';
import type { NodeExecError } from '../../../../shared/tests/types/execTypes.js';

// Mock ONLY external boundaries
jest.mock('child_process');

// Mock promisify to return {stdout, stderr} for exec (as node's promisify does)
jest.mock('util', () => {
  const actualUtil = jest.requireActual('util') as typeof import('util');
  const { createPromisifiedExec } = require('../../../../shared/tests/mocks/promisifyExec');

  return {
    ...actualUtil,
    promisify: (fn: Function) =>
      fn?.name === 'exec' ? createPromisifiedExec(fn) : actualUtil.promisify(fn)
  };
});

// Mock DependencyChecker to always report dependencies are available in tests
jest.mock('../../../../infrastructure/services/DependencyChecker', () => ({
  DependencyChecker: jest.fn().mockImplementation(() => ({
    check: jest.fn<() => Promise<[]>>().mockResolvedValue([]) // No missing dependencies
  }))
}));

const mockExec = exec as jest.MockedFunction<typeof exec>;

/**
 * Integration tests for BootSimulatorController
 * 
 * Tests the integration between:
 * - Controller → Use Case → Adapters
 * - Input validation → Domain logic → Output formatting
 * 
 * Mocks only external boundaries (shell commands)
 * Tests behavior, not implementation details
 */
describe('BootSimulatorController Integration', () => {
  let controller: MCPController;
  let execCallIndex: number;
  let execMockResponses: Array<{ stdout: string; stderr: string; error?: NodeExecError }>;

  beforeEach(() => {
    jest.clearAllMocks();
    execCallIndex = 0;
    execMockResponses = [];
    
    // Setup exec mock to return responses sequentially
    mockExec.mockImplementation(((
      _cmd: string, 
      _options: any, 
      callback: (error: Error | null, stdout: string, stderr: string) => void
    ) => {
      const response = execMockResponses[execCallIndex++] || { stdout: '', stderr: '' };
      if (response.error) {
        callback(response.error, response.stdout, response.stderr);
      } else {
        callback(null, response.stdout, response.stderr);
      }
    }) as any);
    
    // Create controller with REAL components using factory
    controller = BootSimulatorControllerFactory.create();
  });

  describe('boot simulator workflow', () => {
    it('should boot a shutdown simulator', async () => {
      // Arrange
      const simulatorData = {
        devices: {
          'com.apple.CoreSimulator.SimRuntime.iOS-17-0': [{
            udid: 'ABC123',
            name: 'iPhone 15',
            state: SimulatorState.Shutdown,
            isAvailable: true,
            deviceTypeIdentifier: 'com.apple.CoreSimulator.SimDeviceType.iPhone-15'
          }]
        }
      };
      
      execMockResponses = [
        { stdout: JSON.stringify(simulatorData), stderr: '' },  // list devices
        { stdout: '', stderr: '' }  // boot command succeeds
      ];
      
      // Act
      const result = await controller.execute({ deviceId: 'iPhone 15' });
      
      // Assert - Test behavior: simulator was successfully booted
      expect(result.content[0].text).toBe('✅ Successfully booted simulator: iPhone 15 (ABC123)');
    });

    it('should handle already booted simulator', async () => {
      // Arrange
      const simulatorData = {
        devices: {
          'com.apple.CoreSimulator.SimRuntime.iOS-17-0': [{
            udid: 'ABC123',
            name: 'iPhone 15',
            state: SimulatorState.Booted,
            isAvailable: true,
            deviceTypeIdentifier: 'com.apple.CoreSimulator.SimDeviceType.iPhone-15'
          }]
        }
      };
      
      execMockResponses = [
        { stdout: JSON.stringify(simulatorData), stderr: '' }  // list devices - already booted
      ];
      
      // Act
      const result = await controller.execute({ deviceId: 'iPhone 15' });
      
      // Assert - Test behavior: reports simulator is already running
      expect(result.content[0].text).toBe('✅ Simulator already booted: iPhone 15 (ABC123)');
    });

    it('should boot simulator by UUID', async () => {
      // Arrange
      const uuid = '550e8400-e29b-41d4-a716-446655440000';
      const simulatorData = {
        devices: {
          'com.apple.CoreSimulator.SimRuntime.iOS-17-0': [{
            udid: uuid,
            name: 'iPhone 15 Pro',
            state: SimulatorState.Shutdown,
            isAvailable: true,
            deviceTypeIdentifier: 'com.apple.CoreSimulator.SimDeviceType.iPhone-15-Pro'
          }]
        }
      };
      
      execMockResponses = [
        { stdout: JSON.stringify(simulatorData), stderr: '' },  // list devices
        { stdout: '', stderr: '' }  // boot command succeeds
      ];
      
      // Act
      const result = await controller.execute({ deviceId: uuid });
      
      // Assert - Test behavior: simulator was booted using UUID
      expect(result.content[0].text).toBe(`✅ Successfully booted simulator: iPhone 15 Pro (${uuid})`);
    });
  });

  describe('error handling', () => {
    it('should handle simulator not found', async () => {
      // Arrange
      execMockResponses = [
        { stdout: JSON.stringify({ devices: {} }), stderr: '' }  // empty device list
      ];
      
      // Act
      const result = await controller.execute({ deviceId: 'NonExistent' });
      
      // Assert - Test behavior: appropriate error message shown
      expect(result.content[0].text).toBe('❌ Simulator not found: NonExistent');
    });

    it('should handle boot command failure', async () => {
      // Arrange
      const simulatorData = {
        devices: {
          'com.apple.CoreSimulator.SimRuntime.iOS-17-0': [{
            udid: 'ABC123',
            name: 'iPhone 15',
            state: SimulatorState.Shutdown,
            isAvailable: true,
            deviceTypeIdentifier: 'com.apple.CoreSimulator.SimDeviceType.iPhone-15'
          }]
        }
      };
      
      const bootError: NodeExecError = new Error('Command failed') as NodeExecError;
      bootError.code = 1;
      bootError.stdout = '';
      bootError.stderr = 'Unable to boot device';
      
      execMockResponses = [
        { stdout: JSON.stringify(simulatorData), stderr: '' },  // list devices
        { stdout: '', stderr: 'Unable to boot device', error: bootError }  // boot fails
      ];
      
      // Act
      const result = await controller.execute({ deviceId: 'iPhone 15' });
      
      // Assert - Test behavior: error message includes context for found simulator
      expect(result.content[0].text).toBe('❌ iPhone 15 (ABC123) - Unable to boot device');
    });
  });

  describe('validation', () => {
    it('should validate required deviceId', async () => {
      // Act
      const result = await controller.execute({} as any);
      
      // Assert
      expect(result.content[0].text).toBe('❌ Device ID is required');
    });

    it('should validate empty deviceId', async () => {
      // Act
      const result = await controller.execute({ deviceId: '' });
      
      // Assert
      expect(result.content[0].text).toBe('❌ Device ID cannot be empty');
    });

    it('should validate whitespace-only deviceId', async () => {
      // Act
      const result = await controller.execute({ deviceId: '   ' });
      
      // Assert
      expect(result.content[0].text).toBe('❌ Device ID cannot be whitespace only');
    });
  });

  describe('complex scenarios', () => {
    it('should boot specific simulator when multiple exist with similar names', async () => {
      // Arrange
      const simulatorData = {
        devices: {
          'com.apple.CoreSimulator.SimRuntime.iOS-17-0': [
            {
              udid: 'AAA111',
              name: 'iPhone 15',
              state: SimulatorState.Shutdown,
              isAvailable: true,
              deviceTypeIdentifier: 'com.apple.CoreSimulator.SimDeviceType.iPhone-15'
            },
            {
              udid: 'BBB222',
              name: 'iPhone 15 Pro',
              state: SimulatorState.Shutdown,
              isAvailable: true,
              deviceTypeIdentifier: 'com.apple.CoreSimulator.SimDeviceType.iPhone-15-Pro'
            },
            {
              udid: 'CCC333',
              name: 'iPhone 15 Pro Max',
              state: SimulatorState.Shutdown,
              isAvailable: true,
              deviceTypeIdentifier: 'com.apple.CoreSimulator.SimDeviceType.iPhone-15-Pro-Max'
            }
          ]
        }
      };
      
      execMockResponses = [
        { stdout: JSON.stringify(simulatorData), stderr: '' },  // list devices
        { stdout: '', stderr: '' }  // boot command succeeds
      ];
      
      // Act
      const result = await controller.execute({ deviceId: 'iPhone 15 Pro' });
      
      // Assert - Test behavior: correct simulator was booted
      expect(result.content[0].text).toBe('✅ Successfully booted simulator: iPhone 15 Pro (BBB222)');
    });

    it('should handle mixed state simulators across runtimes', async () => {
      // Arrange
      const simulatorData = {
        devices: {
          'com.apple.CoreSimulator.SimRuntime.iOS-16-0': [{
            udid: 'OLD123',
            name: 'iPhone 14',
            state: SimulatorState.Booted,
            isAvailable: true,
            deviceTypeIdentifier: 'com.apple.CoreSimulator.SimDeviceType.iPhone-14'
          }],
          'com.apple.CoreSimulator.SimRuntime.iOS-17-0': [{
            udid: 'NEW456',
            name: 'iPhone 14',
            state: SimulatorState.Shutdown,
            isAvailable: true,
            deviceTypeIdentifier: 'com.apple.CoreSimulator.SimDeviceType.iPhone-14'
          }]
        }
      };
      
      execMockResponses = [
        { stdout: JSON.stringify(simulatorData), stderr: '' }  // list shows iOS 16 one is booted
      ];
      
      // Act - should find the first matching by name regardless of runtime
      const result = await controller.execute({ deviceId: 'iPhone 14' });
      
      // Assert - Test behavior: finds already booted simulator from any runtime
      expect(result.content[0].text).toBe('✅ Simulator already booted: iPhone 14 (OLD123)');
    });
  });
});
```

--------------------------------------------------------------------------------
/src/features/app-management/tests/unit/InstallAppUseCase.unit.test.ts:
--------------------------------------------------------------------------------

```typescript
import { InstallResult, InstallOutcome, InstallCommandFailedError, SimulatorNotFoundError, NoBootedSimulatorError } from '../../domain/InstallResult.js';
import { describe, it, expect, jest, beforeEach } from '@jest/globals';
import { InstallAppUseCase } from '../../use-cases/InstallAppUseCase.js';
import { InstallRequest } from '../../domain/InstallRequest.js';
import { SimulatorState } from '../../../simulator/domain/SimulatorState.js';
import { 
  ISimulatorLocator,
  ISimulatorControl,
  IAppInstaller, 
  SimulatorInfo 
} from '../../../../application/ports/SimulatorPorts.js';
import { ILogManager } from '../../../../application/ports/LoggingPorts.js';

describe('InstallAppUseCase', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  function createSUT() {
    const mockFindSimulator = jest.fn<ISimulatorLocator['findSimulator']>();
    const mockFindBootedSimulator = jest.fn<ISimulatorLocator['findBootedSimulator']>();
    const mockSimulatorLocator: ISimulatorLocator = {
      findSimulator: mockFindSimulator,
      findBootedSimulator: mockFindBootedSimulator
    };

    const mockBoot = jest.fn<ISimulatorControl['boot']>();
    const mockShutdown = jest.fn<ISimulatorControl['shutdown']>();
    const mockSimulatorControl: ISimulatorControl = {
      boot: mockBoot,
      shutdown: mockShutdown
    };

    const mockInstallApp = jest.fn<IAppInstaller['installApp']>();
    const mockAppInstaller: IAppInstaller = {
      installApp: mockInstallApp
    };

    const mockSaveDebugData = jest.fn<ILogManager['saveDebugData']>();
    const mockSaveLog = jest.fn<ILogManager['saveLog']>();
    const mockLogManager: ILogManager = {
      saveDebugData: mockSaveDebugData,
      saveLog: mockSaveLog
    };

    const sut = new InstallAppUseCase(
      mockSimulatorLocator,
      mockSimulatorControl,
      mockAppInstaller,
      mockLogManager
    );

    return {
      sut,
      mockFindSimulator,
      mockFindBootedSimulator,
      mockBoot,
      mockInstallApp,
      mockSaveDebugData,
      mockSaveLog
    };
  }

  function createTestSimulator(state: SimulatorState = SimulatorState.Booted): SimulatorInfo {
    return {
      id: 'test-simulator-id',
      name: 'iPhone 15',
      state,
      platform: 'iOS',
      runtime: 'iOS 17.0'
    };
  }

  describe('when installing with specific simulator ID', () => {
    it('should install app on already booted simulator', async () => {
      // Arrange
      const { sut, mockFindSimulator, mockInstallApp } = createSUT();
      const request = InstallRequest.create('/path/to/MyApp.app', 'test-simulator-id');
      const simulator = createTestSimulator(SimulatorState.Booted);
      
      mockFindSimulator.mockResolvedValue(simulator);
      mockInstallApp.mockResolvedValue(undefined);

      // Act
      const result = await sut.execute(request);

      // Assert
      expect(result.outcome).toBe(InstallOutcome.Succeeded);
      expect(result.diagnostics.bundleId).toBe('MyApp.app');
      expect(result.diagnostics.simulatorId?.toString()).toBe('test-simulator-id');
      expect(mockInstallApp).toHaveBeenCalledWith('/path/to/MyApp.app', 'test-simulator-id');
    });

    it('should auto-boot shutdown simulator before installing', async () => {
      // Arrange
      const { sut, mockFindSimulator, mockBoot, mockInstallApp } = createSUT();
      const request = InstallRequest.create('/path/to/MyApp.app', 'test-simulator-id');
      const simulator = createTestSimulator(SimulatorState.Shutdown);
      
      mockFindSimulator.mockResolvedValue(simulator);
      mockBoot.mockResolvedValue(undefined);
      mockInstallApp.mockResolvedValue(undefined);

      // Act
      const result = await sut.execute(request);

      // Assert
      expect(mockBoot).toHaveBeenCalledWith('test-simulator-id');
      expect(mockInstallApp).toHaveBeenCalledWith('/path/to/MyApp.app', 'test-simulator-id');
      expect(result.outcome).toBe(InstallOutcome.Succeeded);
    });

    it('should return failure when simulator not found', async () => {
      // Arrange
      const { sut, mockFindSimulator, mockSaveDebugData } = createSUT();
      const request = InstallRequest.create('/path/to/MyApp.app', 'non-existent-id');
      
      mockFindSimulator.mockResolvedValue(null);

      // Act
      const result = await sut.execute(request);

      // Assert
      expect(result.outcome).toBe(InstallOutcome.Failed);
      expect(result.diagnostics.error).toBeInstanceOf(SimulatorNotFoundError);
      expect((result.diagnostics.error as SimulatorNotFoundError).simulatorId.toString()).toBe('non-existent-id');
      expect(mockSaveDebugData).toHaveBeenCalledWith(
        'install-app-failed',
        expect.objectContaining({ reason: 'simulator_not_found' }),
        'MyApp.app'
      );
    });

    it('should return failure when boot fails', async () => {
      // Arrange  
      const { sut, mockFindSimulator, mockBoot, mockSaveDebugData } = createSUT();
      const request = InstallRequest.create('/path/to/MyApp.app', 'test-simulator-id');
      const simulator = createTestSimulator(SimulatorState.Shutdown);
      
      mockFindSimulator.mockResolvedValue(simulator);
      mockBoot.mockRejectedValue(new Error('Boot failed'));

      // Act
      const result = await sut.execute(request);

      // Assert
      expect(result.outcome).toBe(InstallOutcome.Failed);
      expect(result.diagnostics.error).toBeInstanceOf(InstallCommandFailedError);
      expect((result.diagnostics.error as InstallCommandFailedError).stderr).toBe('Boot failed');
      expect(mockSaveDebugData).toHaveBeenCalledWith(
        'simulator-boot-failed',
        expect.objectContaining({ error: 'Boot failed' }),
        'MyApp.app'
      );
    });
  });

  describe('when installing without simulator ID', () => {
    it('should use booted simulator', async () => {
      // Arrange
      const { sut, mockFindBootedSimulator, mockInstallApp } = createSUT();
      const request = InstallRequest.create('/path/to/MyApp.app');
      const simulator = createTestSimulator(SimulatorState.Booted);
      
      mockFindBootedSimulator.mockResolvedValue(simulator);
      mockInstallApp.mockResolvedValue(undefined);

      // Act
      const result = await sut.execute(request);

      // Assert
      expect(result.outcome).toBe(InstallOutcome.Succeeded);
      expect(result.diagnostics.simulatorId?.toString()).toBe('test-simulator-id');
      expect(mockFindBootedSimulator).toHaveBeenCalled();
      expect(mockInstallApp).toHaveBeenCalledWith('/path/to/MyApp.app', 'test-simulator-id');
    });

    it('should return failure when no booted simulator found', async () => {
      // Arrange
      const { sut, mockFindBootedSimulator, mockSaveDebugData } = createSUT();
      const request = InstallRequest.create('/path/to/MyApp.app');
      
      mockFindBootedSimulator.mockResolvedValue(null);

      // Act
      const result = await sut.execute(request);

      // Assert
      expect(result.outcome).toBe(InstallOutcome.Failed);
      expect(result.diagnostics.error).toBeInstanceOf(NoBootedSimulatorError);
      expect(mockSaveDebugData).toHaveBeenCalledWith(
        'install-app-failed',
        expect.objectContaining({ reason: 'simulator_not_found' }),
        'MyApp.app'
      );
    });
  });

  describe('when installation fails', () => {
    it('should return failure with error message', async () => {
      // Arrange
      const { sut, mockFindSimulator, mockInstallApp, mockSaveDebugData } = createSUT();
      const request = InstallRequest.create('/path/to/MyApp.app', 'test-simulator-id');
      const simulator = createTestSimulator(SimulatorState.Booted);
      
      mockFindSimulator.mockResolvedValue(simulator);
      mockInstallApp.mockRejectedValue(new Error('Code signing error'));

      // Act
      const result = await sut.execute(request);

      // Assert
      expect(result.outcome).toBe(InstallOutcome.Failed);
      expect(result.diagnostics.error).toBeInstanceOf(InstallCommandFailedError);
      expect((result.diagnostics.error as InstallCommandFailedError).stderr).toBe('Code signing error');
      expect(mockSaveDebugData).toHaveBeenCalledWith(
        'install-app-error',
        expect.objectContaining({ error: 'Code signing error' }),
        'MyApp.app'
      );
    });

    it('should handle generic error', async () => {
      // Arrange
      const { sut, mockFindSimulator, mockInstallApp } = createSUT();
      const request = InstallRequest.create('/path/to/MyApp.app', 'test-simulator-id');
      const simulator = createTestSimulator(SimulatorState.Booted);
      
      mockFindSimulator.mockResolvedValue(simulator);
      mockInstallApp.mockRejectedValue('String error');

      // Act
      const result = await sut.execute(request);

      // Assert
      expect(result.outcome).toBe(InstallOutcome.Failed);
      expect(result.diagnostics.error).toBeInstanceOf(InstallCommandFailedError);
      expect((result.diagnostics.error as InstallCommandFailedError).stderr).toBe('String error');
    });
  });

  describe('debug data logging', () => {
    it('should log success with app name and simulator info', async () => {
      // Arrange
      const { sut, mockFindSimulator, mockInstallApp, mockSaveDebugData } = createSUT();
      const request = InstallRequest.create('/path/to/MyApp.app', 'test-simulator-id');
      const simulator = createTestSimulator(SimulatorState.Booted);
      
      mockFindSimulator.mockResolvedValue(simulator);
      mockInstallApp.mockResolvedValue(undefined);

      // Act
      await sut.execute(request);

      // Assert
      expect(mockSaveDebugData).toHaveBeenCalledWith(
        'install-app-success',
        expect.objectContaining({
          simulator: 'iPhone 15',
          simulatorId: 'test-simulator-id',
          app: 'MyApp.app'
        }),
        'MyApp.app'
      );
    });

    it('should log auto-boot event', async () => {
      // Arrange
      const { sut, mockFindSimulator, mockBoot, mockInstallApp, mockSaveDebugData } = createSUT();
      const request = InstallRequest.create('/path/to/MyApp.app', 'test-simulator-id');
      const simulator = createTestSimulator(SimulatorState.Shutdown);
      
      mockFindSimulator.mockResolvedValue(simulator);
      mockBoot.mockResolvedValue(undefined);
      mockInstallApp.mockResolvedValue(undefined);

      // Act
      await sut.execute(request);

      // Assert
      expect(mockSaveDebugData).toHaveBeenCalledWith(
        'simulator-auto-booted',
        expect.objectContaining({
          simulatorId: 'test-simulator-id',
          simulatorName: 'iPhone 15'
        }),
        'MyApp.app'
      );
    });
  });
});
```

--------------------------------------------------------------------------------
/src/features/simulator/tests/integration/ShutdownSimulatorController.integration.test.ts:
--------------------------------------------------------------------------------

```typescript
import { describe, it, expect, jest, beforeEach } from '@jest/globals';
import { MCPController } from '../../../../presentation/interfaces/MCPController.js';
import { ShutdownSimulatorControllerFactory } from '../../factories/ShutdownSimulatorControllerFactory.js';
import { SimulatorState } from '../../domain/SimulatorState.js';
import { exec } from 'child_process';
import type { NodeExecError } from '../../../../shared/tests/types/execTypes.js';

// Mock ONLY external boundaries
jest.mock('child_process');

// Mock promisify to return {stdout, stderr} for exec (as node's promisify does)
jest.mock('util', () => {
  const actualUtil = jest.requireActual('util') as typeof import('util');
  const { createPromisifiedExec } = require('../../../../shared/tests/mocks/promisifyExec');

  return {
    ...actualUtil,
    promisify: (fn: Function) =>
      fn?.name === 'exec' ? createPromisifiedExec(fn) : actualUtil.promisify(fn)
  };
});

// Mock DependencyChecker to always report dependencies are available in tests
jest.mock('../../../../infrastructure/services/DependencyChecker', () => ({
  DependencyChecker: jest.fn().mockImplementation(() => ({
    check: jest.fn<() => Promise<[]>>().mockResolvedValue([]) // No missing dependencies
  }))
}));

const mockExec = exec as jest.MockedFunction<typeof exec>;

/**
 * Integration tests for ShutdownSimulatorController
 * 
 * Tests the integration between:
 * - Controller → Use Case → Adapters
 * - Input validation → Domain logic → Output formatting
 * 
 * Mocks only external boundaries (shell commands)
 * Tests behavior, not implementation details
 */
describe('ShutdownSimulatorController Integration', () => {
  let controller: MCPController;
  let execCallIndex: number;
  let execMockResponses: Array<{ stdout: string; stderr: string; error?: NodeExecError }>;

  beforeEach(() => {
    jest.clearAllMocks();
    execCallIndex = 0;
    execMockResponses = [];
    
    // Setup exec mock to return responses sequentially
    mockExec.mockImplementation(((
      _cmd: string, 
      _options: any, 
      callback: (error: Error | null, stdout: string, stderr: string) => void
    ) => {
      const response = execMockResponses[execCallIndex++] || { stdout: '', stderr: '' };
      if (response.error) {
        callback(response.error, response.stdout, response.stderr);
      } else {
        callback(null, response.stdout, response.stderr);
      }
    }) as any);
    
    // Create controller with REAL components using factory
    controller = ShutdownSimulatorControllerFactory.create();
  });

  describe('shutdown simulator workflow', () => {
    it('should shutdown a booted simulator', async () => {
      // Arrange
      const simulatorData = {
        devices: {
          'com.apple.CoreSimulator.SimRuntime.iOS-17-0': [{
            udid: 'ABC123',
            name: 'iPhone 15',
            state: SimulatorState.Booted,
            isAvailable: true,
            deviceTypeIdentifier: 'com.apple.CoreSimulator.SimDeviceType.iPhone-15'
          }]
        }
      };
      
      execMockResponses = [
        { stdout: JSON.stringify(simulatorData), stderr: '' },  // list devices
        { stdout: '', stderr: '' }  // shutdown command succeeds
      ];
      
      // Act
      const result = await controller.execute({ deviceId: 'iPhone 15' });
      
      // Assert - Test behavior: simulator was successfully shutdown
      expect(result.content[0].text).toBe('✅ Successfully shutdown simulator: iPhone 15 (ABC123)');
    });

    it('should handle already shutdown simulator', async () => {
      // Arrange
      const simulatorData = {
        devices: {
          'com.apple.CoreSimulator.SimRuntime.iOS-17-0': [{
            udid: 'ABC123',
            name: 'iPhone 15',
            state: SimulatorState.Shutdown,
            isAvailable: true,
            deviceTypeIdentifier: 'com.apple.CoreSimulator.SimDeviceType.iPhone-15'
          }]
        }
      };
      
      execMockResponses = [
        { stdout: JSON.stringify(simulatorData), stderr: '' }  // list devices - already shutdown
      ];
      
      // Act
      const result = await controller.execute({ deviceId: 'iPhone 15' });
      
      // Assert - Test behavior: reports simulator is already shutdown
      expect(result.content[0].text).toBe('✅ Simulator already shutdown: iPhone 15 (ABC123)');
    });

    it('should shutdown simulator by UUID', async () => {
      // Arrange
      const uuid = '550e8400-e29b-41d4-a716-446655440000';
      const simulatorData = {
        devices: {
          'com.apple.CoreSimulator.SimRuntime.iOS-17-0': [{
            udid: uuid,
            name: 'iPhone 15 Pro',
            state: SimulatorState.Booted,
            isAvailable: true,
            deviceTypeIdentifier: 'com.apple.CoreSimulator.SimDeviceType.iPhone-15-Pro'
          }]
        }
      };
      
      execMockResponses = [
        { stdout: JSON.stringify(simulatorData), stderr: '' },  // list devices
        { stdout: '', stderr: '' }  // shutdown command succeeds
      ];
      
      // Act
      const result = await controller.execute({ deviceId: uuid });
      
      // Assert - Test behavior: simulator was shutdown using UUID
      expect(result.content[0].text).toBe(`✅ Successfully shutdown simulator: iPhone 15 Pro (${uuid})`);
    });
  });

  describe('error handling', () => {
    it('should handle simulator not found', async () => {
      // Arrange
      execMockResponses = [
        { stdout: JSON.stringify({ devices: {} }), stderr: '' }  // empty device list
      ];
      
      // Act
      const result = await controller.execute({ deviceId: 'NonExistent' });
      
      // Assert - Test behavior: appropriate error message shown
      expect(result.content[0].text).toBe('❌ Simulator not found: NonExistent');
    });

    it('should handle shutdown command failure', async () => {
      // Arrange
      const simulatorData = {
        devices: {
          'com.apple.CoreSimulator.SimRuntime.iOS-17-0': [{
            udid: 'ABC123',
            name: 'iPhone 15',
            state: SimulatorState.Booted,
            isAvailable: true,
            deviceTypeIdentifier: 'com.apple.CoreSimulator.SimDeviceType.iPhone-15'
          }]
        }
      };
      
      const shutdownError: NodeExecError = new Error('Command failed') as NodeExecError;
      shutdownError.code = 1;
      shutdownError.stdout = '';
      shutdownError.stderr = 'Unable to shutdown device';
      
      execMockResponses = [
        { stdout: JSON.stringify(simulatorData), stderr: '' },  // list devices
        { stdout: '', stderr: 'Unable to shutdown device', error: shutdownError }  // shutdown fails
      ];
      
      // Act
      const result = await controller.execute({ deviceId: 'iPhone 15' });
      
      // Assert - Test behavior: error message includes context for found simulator
      expect(result.content[0].text).toBe('❌ iPhone 15 (ABC123) - Unable to shutdown device');
    });
  });

  describe('validation', () => {
    it('should validate required deviceId', async () => {
      // Act
      const result = await controller.execute({} as any);
      
      // Assert
      expect(result.content[0].text).toBe('❌ Device ID is required');
    });

    it('should validate empty deviceId', async () => {
      // Act
      const result = await controller.execute({ deviceId: '' });
      
      // Assert
      expect(result.content[0].text).toBe('❌ Device ID cannot be empty');
    });

    it('should validate whitespace-only deviceId', async () => {
      // Act
      const result = await controller.execute({ deviceId: '   ' });
      
      // Assert
      expect(result.content[0].text).toBe('❌ Device ID cannot be whitespace only');
    });
  });

  describe('complex scenarios', () => {
    it('should shutdown specific simulator when multiple exist with similar names', async () => {
      // Arrange
      const simulatorData = {
        devices: {
          'com.apple.CoreSimulator.SimRuntime.iOS-17-0': [
            {
              udid: 'AAA111',
              name: 'iPhone 15',
              state: SimulatorState.Booted,
              isAvailable: true,
              deviceTypeIdentifier: 'com.apple.CoreSimulator.SimDeviceType.iPhone-15'
            },
            {
              udid: 'BBB222',
              name: 'iPhone 15 Pro',
              state: SimulatorState.Booted,
              isAvailable: true,
              deviceTypeIdentifier: 'com.apple.CoreSimulator.SimDeviceType.iPhone-15-Pro'
            },
            {
              udid: 'CCC333',
              name: 'iPhone 15 Pro Max',
              state: SimulatorState.Shutdown,
              isAvailable: true,
              deviceTypeIdentifier: 'com.apple.CoreSimulator.SimDeviceType.iPhone-15-Pro-Max'
            }
          ]
        }
      };
      
      execMockResponses = [
        { stdout: JSON.stringify(simulatorData), stderr: '' },  // list devices
        { stdout: '', stderr: '' }  // shutdown command succeeds
      ];
      
      // Act
      const result = await controller.execute({ deviceId: 'iPhone 15 Pro' });
      
      // Assert - Test behavior: correct simulator was shutdown
      expect(result.content[0].text).toBe('✅ Successfully shutdown simulator: iPhone 15 Pro (BBB222)');
    });

    it('should handle mixed state simulators across runtimes', async () => {
      // Arrange
      const simulatorData = {
        devices: {
          'com.apple.CoreSimulator.SimRuntime.iOS-16-0': [{
            udid: 'OLD123',
            name: 'iPhone 14',
            state: SimulatorState.Shutdown,
            isAvailable: true,
            deviceTypeIdentifier: 'com.apple.CoreSimulator.SimDeviceType.iPhone-14'
          }],
          'com.apple.CoreSimulator.SimRuntime.iOS-17-0': [{
            udid: 'NEW456',
            name: 'iPhone 14',
            state: SimulatorState.Booted,
            isAvailable: true,
            deviceTypeIdentifier: 'com.apple.CoreSimulator.SimDeviceType.iPhone-14'
          }]
        }
      };
      
      execMockResponses = [
        { stdout: JSON.stringify(simulatorData), stderr: '' },  // list shows iOS 17 one is booted
        { stdout: '', stderr: '' }  // shutdown succeeds
      ];
      
      // Act - should find the first matching by name (prioritizes newer runtime)
      const result = await controller.execute({ deviceId: 'iPhone 14' });
      
      // Assert - Test behavior: finds and shuts down the iOS 17 device (newer runtime)
      expect(result.content[0].text).toBe('✅ Successfully shutdown simulator: iPhone 14 (NEW456)');
    });

    it('should shutdown simulator in Booting state', async () => {
      // Arrange
      const simulatorData = {
        devices: {
          'com.apple.CoreSimulator.SimRuntime.iOS-17-0': [{
            udid: 'BOOT123',
            name: 'iPhone 15',
            state: SimulatorState.Booting,
            isAvailable: true,
            deviceTypeIdentifier: 'com.apple.CoreSimulator.SimDeviceType.iPhone-15'
          }]
        }
      };
      
      execMockResponses = [
        { stdout: JSON.stringify(simulatorData), stderr: '' },  // list devices - in Booting state
        { stdout: '', stderr: '' }  // shutdown command succeeds
      ];
      
      // Act
      const result = await controller.execute({ deviceId: 'iPhone 15' });
      
      // Assert - Test behavior: can shutdown a simulator that's booting
      expect(result.content[0].text).toBe('✅ Successfully shutdown simulator: iPhone 15 (BOOT123)');
    });
  });
});
```

--------------------------------------------------------------------------------
/src/features/app-management/tests/integration/InstallAppController.integration.test.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Integration Test for InstallAppController
 * 
 * Tests the controller with REAL use case, presenter, and adapters
 * but MOCKS external boundaries (filesystem, subprocess).
 * 
 * Following testing philosophy:
 * - Integration tests (60% of suite) test component interactions
 * - Mock only external boundaries
 * - Test behavior, not implementation
 */

import { describe, it, expect, jest, beforeEach } from '@jest/globals';
import { MCPController } from '../../../../presentation/interfaces/MCPController.js';
import { InstallAppControllerFactory } from '../../factories/InstallAppControllerFactory.js';
import { exec } from 'child_process';
import { existsSync, statSync } from 'fs';
import type { NodeExecError } from '../../../../shared/tests/types/execTypes.js';

// Mock ONLY external boundaries
jest.mock('child_process');
jest.mock('fs');

// Mock promisify to return {stdout, stderr} for exec (as node's promisify does)
jest.mock('util', () => {
  const actualUtil = jest.requireActual('util') as typeof import('util');
  const { createPromisifiedExec } = require('../../../../shared/tests/mocks/promisifyExec');

  return {
    ...actualUtil,
    promisify: (fn: Function) =>
      fn?.name === 'exec' ? createPromisifiedExec(fn) : actualUtil.promisify(fn)
  };
});

// Mock DependencyChecker to always report dependencies are available in tests
jest.mock('../../../../infrastructure/services/DependencyChecker', () => ({
  DependencyChecker: jest.fn().mockImplementation(() => ({
    check: jest.fn<() => Promise<[]>>().mockResolvedValue([]) // No missing dependencies
  }))
}));

const mockExec = exec as jest.MockedFunction<typeof exec>;
const mockExistsSync = existsSync as jest.MockedFunction<typeof existsSync>;
const mockStatSync = statSync as jest.MockedFunction<typeof statSync>;

describe('InstallAppController Integration', () => {
  let controller: MCPController;
  let execCallIndex: number;
  let execMockResponses: Array<{ stdout: string; stderr: string; error?: NodeExecError }>;
  
  // Helper to create device list JSON response
  const createDeviceListResponse = (devices: Array<{udid: string, name: string, state: string}>) => ({
    stdout: JSON.stringify({
      devices: {
        'com.apple.CoreSimulator.SimRuntime.iOS-17-0': devices.map(d => ({
          ...d,
          isAvailable: true
        }))
      }
    }),
    stderr: ''
  });

  beforeEach(() => {
    jest.clearAllMocks();
    execCallIndex = 0;
    execMockResponses = [];
    
    // Setup selective exec mock for xcrun simctl commands
    const actualExec = (jest.requireActual('child_process') as typeof import('child_process')).exec;
    const { createSelectiveExecMock } = require('../../../../shared/tests/mocks/selectiveExecMock');
    
    const isSimctlCommand = (cmd: string) => 
      cmd.includes('xcrun simctl');
    
    mockExec.mockImplementation(
      createSelectiveExecMock(
        isSimctlCommand,
        () => execMockResponses[execCallIndex++],
        actualExec
      )
    );
    
    // Default filesystem mocks
    mockExistsSync.mockImplementation((path) => {
      const pathStr = String(path);
      return pathStr.endsWith('.app');
    });
    
    mockStatSync.mockImplementation((path) => ({
      isDirectory: () => String(path).endsWith('.app'),
      isFile: () => false,
      // Add other stat properties as needed
    } as any));
    
    // Create controller with REAL components using factory
    controller = InstallAppControllerFactory.create();
  });

  describe('successful app installation', () => {
    it('should install app on booted simulator', async () => {
      // Arrange
      const appPath = '/Users/dev/MyApp.app';
      const simulatorId = 'test-simulator-id';
      
      execMockResponses = [
        // Find simulator
        createDeviceListResponse([
          { udid: simulatorId, name: 'iPhone 15', state: 'Booted' }
        ]),
        // Install app
        { stdout: '', stderr: '' }
      ];
      
      // Act
      const result = await controller.execute({
        appPath,
        simulatorId
      });
      
      // Assert
      expect(result).toMatchObject({
        content: expect.arrayContaining([
          expect.objectContaining({
            type: 'text',
            text: expect.stringContaining('Successfully installed')
          })
        ])
      });
      
    });

    it('should find and use booted simulator when no ID specified', async () => {
      // Arrange
      const appPath = '/Users/dev/MyApp.app';
      
      execMockResponses = [
        // xcrun simctl list devices --json (to find booted simulator)
        { 
          stdout: JSON.stringify({
            devices: {
              'com.apple.CoreSimulator.SimRuntime.iOS-17-0': [
                {
                  udid: 'booted-sim-id',
                  name: 'iPhone 15',
                  state: 'Booted',
                  isAvailable: true
                },
                {
                  udid: 'shutdown-sim-id',
                  name: 'iPhone 14',
                  state: 'Shutdown',
                  isAvailable: true
                }
              ]
            }
          }),
          stderr: ''
        },
        // xcrun simctl install command
        { stdout: '', stderr: '' }
      ];
      
      // Act
      const result = await controller.execute({
        appPath
      });
      
      // Assert
      expect(result).toMatchObject({
        content: expect.arrayContaining([
          expect.objectContaining({
            type: 'text',
            text: expect.stringContaining('Successfully installed')
          })
        ])
      });
      
    });

    it('should boot simulator if shutdown', async () => {
      // Arrange
      const appPath = '/Users/dev/MyApp.app';
      const simulatorId = 'shutdown-sim-id';
      
      execMockResponses = [
        // Find simulator
        {
          stdout: JSON.stringify({
            devices: {
              'com.apple.CoreSimulator.SimRuntime.iOS-17-0': [
                {
                  udid: simulatorId,
                  name: 'iPhone 15',
                  state: 'Shutdown',
                  isAvailable: true
                }
              ]
            }
          }),
          stderr: ''
        },
        // Boot simulator
        { stdout: '', stderr: '' },
        // Install app
        { stdout: '', stderr: '' }
      ];
      
      // Act
      const result = await controller.execute({
        appPath,
        simulatorId
      });
      
      // Assert
      expect(result).toMatchObject({
        content: expect.arrayContaining([
          expect.objectContaining({
            type: 'text',
            text: expect.stringContaining('Successfully installed')
          })
        ])
      });
      
    });
  });

  describe('error handling', () => {
    it('should fail when app path does not exist', async () => {
      // Arrange
      const nonExistentPath = '/path/that/does/not/exist.app';
      mockExistsSync.mockReturnValue(false);
      
      execMockResponses = [
        // Find simulator
        createDeviceListResponse([
          { udid: 'test-sim', name: 'iPhone 15', state: 'Booted' }
        ]),
        // Install command would fail with file not found
        {
          error: Object.assign(new Error('Failed to install app'), {
            code: 1,
            stdout: '',
            stderr: 'xcrun simctl install: No such file or directory'
          }),
          stdout: '',
          stderr: 'xcrun simctl install: No such file or directory'
        }
      ];
      
      // Act
      const result = await controller.execute({
        appPath: nonExistentPath,
        simulatorId: 'test-sim'
      });
      
      // Assert
      expect(result.content[0].text).toBe('❌ iPhone 15 (test-sim) - xcrun simctl install: No such file or directory');
    });

    it('should fail when app path is not an app bundle', async () => {
      // Arrange
      const invalidPath = '/Users/dev/file.txt';
      mockExistsSync.mockReturnValue(true);
      mockStatSync.mockReturnValue({
        isDirectory: () => false,
        isFile: () => true
      } as any);
      
      // Act
      const result = await controller.execute({
        appPath: invalidPath,
        simulatorId: 'test-sim'
      });
      
      // Assert
      expect(result.content[0].text).toBe('❌ App path must end with .app');
    });

    it('should fail when simulator does not exist', async () => {
      // Arrange
      const appPath = '/Users/dev/MyApp.app';
      const nonExistentSim = 'non-existent-id';
      
      execMockResponses = [
        // List devices - simulator not found
        {
          stdout: JSON.stringify({
            devices: {
              'com.apple.CoreSimulator.SimRuntime.iOS-17-0': []
            }
          }),
          stderr: ''
        }
      ];
      
      // Act
      const result = await controller.execute({
        appPath,
        simulatorId: nonExistentSim
      });
      
      // Assert
      expect(result.content[0].text).toBe(`❌ Simulator not found: ${nonExistentSim}`);
    });

    it('should fail when no booted simulator and no ID specified', async () => {
      // Arrange
      const appPath = '/Users/dev/MyApp.app';
      
      execMockResponses = [
        // List devices - no booted simulators
        {
          stdout: JSON.stringify({
            devices: {
              'com.apple.CoreSimulator.SimRuntime.iOS-17-0': [
                {
                  udid: 'shutdown-sim',
                  name: 'iPhone 15',
                  state: 'Shutdown',
                  isAvailable: true
                }
              ]
            }
          }),
          stderr: ''
        }
      ];
      
      // Act
      const result = await controller.execute({
        appPath
      });
      
      // Assert
      expect(result.content[0].text).toBe('❌ No booted simulator found. Please boot a simulator first or specify a simulator ID.');
    });

    it('should handle installation failure gracefully', async () => {
      // Arrange
      const appPath = '/Users/dev/MyApp.app';
      const simulatorId = 'test-sim';
      
      const error = new Error('Failed to install app: incompatible architecture') as NodeExecError;
      error.code = 1;
      error.stdout = '';
      error.stderr = 'Error: incompatible architecture';
      
      execMockResponses = [
        // Find simulator
        createDeviceListResponse([
          { udid: simulatorId, name: 'iPhone 15', state: 'Booted' }
        ]),
        // Install fails
        { error, stdout: '', stderr: error.stderr }
      ];
      
      // Act
      const result = await controller.execute({
        appPath,
        simulatorId
      });
      
      // Assert
      expect(result.content[0].text).toBe('❌ iPhone 15 (test-sim) - incompatible architecture');
    });
  });

  describe('input validation', () => {
    it('should accept simulator name instead of UUID', async () => {
      // Arrange
      const appPath = '/Users/dev/MyApp.app';
      const simulatorName = 'iPhone 15 Pro';
      
      execMockResponses = [
        // Find simulator by name
        createDeviceListResponse([
          { udid: 'sim-id-123', name: simulatorName, state: 'Booted' }
        ]),
        // Install succeeds
        { stdout: '', stderr: '' }
      ];
      
      // Act
      const result = await controller.execute({
        appPath,
        simulatorId: simulatorName
      });
      
      // Assert
      expect(result).toMatchObject({
        content: expect.arrayContaining([
          expect.objectContaining({
            type: 'text',
            text: expect.stringContaining('Successfully installed')
          })
        ])
      });
      // Should show both simulator name and ID in format: "name (id)"
      expect(result.content[0].text).toContain(`${simulatorName} (sim-id-123)`);
    });

    it('should handle paths with spaces', async () => {
      // Arrange
      const appPath = '/Users/dev/My iOS App/MyApp.app';
      const simulatorId = 'test-sim';
      
      execMockResponses = [
        // Find simulator
        createDeviceListResponse([
          { udid: simulatorId, name: 'iPhone 15', state: 'Booted' }
        ]),
        // Install app
        { stdout: '', stderr: '' }
      ];
      
      // Act
      const result = await controller.execute({
        appPath,
        simulatorId
      });
      
      // Assert
      expect(result).toMatchObject({
        content: expect.arrayContaining([
          expect.objectContaining({
            type: 'text',
            text: expect.stringContaining('Successfully installed')
          })
        ])
      });
      // Path with spaces should be handled correctly
      expect(result.content[0].text).toContain('iPhone 15 (test-sim)');
      
    });
  });
});
```

--------------------------------------------------------------------------------
/src/features/simulator/tests/integration/ListSimulatorsController.integration.test.ts:
--------------------------------------------------------------------------------

```typescript
import { describe, it, expect, jest, beforeEach } from '@jest/globals';
import { MCPController } from '../../../../presentation/interfaces/MCPController.js';
import { ListSimulatorsControllerFactory } from '../../factories/ListSimulatorsControllerFactory.js';
import { SimulatorState } from '../../domain/SimulatorState.js';
import { exec } from 'child_process';
import type { NodeExecError } from '../../../../shared/tests/types/execTypes.js';

// Mock ONLY external boundaries
jest.mock('child_process');

// Mock promisify to return {stdout, stderr} for exec (as node's promisify does)
jest.mock('util', () => {
  const actualUtil = jest.requireActual('util') as typeof import('util');
  const { createPromisifiedExec } = require('../../../../shared/tests/mocks/promisifyExec');

  return {
    ...actualUtil,
    promisify: (fn: Function) =>
      fn?.name === 'exec' ? createPromisifiedExec(fn) : actualUtil.promisify(fn)
  };
});

// Mock DependencyChecker to always report dependencies are available in tests
jest.mock('../../../../infrastructure/services/DependencyChecker', () => ({
  DependencyChecker: jest.fn().mockImplementation(() => ({
    check: jest.fn<() => Promise<[]>>().mockResolvedValue([]) // No missing dependencies
  }))
}));

const mockExec = exec as jest.MockedFunction<typeof exec>;

describe('ListSimulatorsController Integration', () => {
  let controller: MCPController;
  let execCallIndex: number;
  let execMockResponses: Array<{ stdout: string; stderr: string; error?: NodeExecError }>;

  beforeEach(() => {
    jest.clearAllMocks();
    execCallIndex = 0;
    execMockResponses = [];

    // Setup exec mock to return responses sequentially
    mockExec.mockImplementation(((
      _cmd: string,
      _options: any,
      callback: (error: Error | null, stdout: string, stderr: string) => void
    ) => {
      const response = execMockResponses[execCallIndex++] || { stdout: '', stderr: '' };
      if (response.error) {
        callback(response.error, response.stdout, response.stderr);
      } else {
        callback(null, response.stdout, response.stderr);
      }
    }) as any);

    // Create controller with REAL components using factory
    controller = ListSimulatorsControllerFactory.create();
  });

  describe('with mocked shell commands', () => {
    it('should list all simulators', async () => {
      // Arrange
      const mockDeviceList = {
        devices: {
          'com.apple.CoreSimulator.SimRuntime.iOS-17-0': [
            {
              dataPath: '/path/to/data',
              dataPathSize: 1000000,
              logPath: '/path/to/logs',
              udid: 'ABC123',
              isAvailable: true,
              deviceTypeIdentifier: 'com.apple.iPhone15',
              state: 'Booted',
              name: 'iPhone 15'
            },
            {
              dataPath: '/path/to/data2',
              dataPathSize: 2000000,
              logPath: '/path/to/logs2',
              udid: 'DEF456',
              isAvailable: true,
              deviceTypeIdentifier: 'com.apple.iPadPro',
              state: 'Shutdown',
              name: 'iPad Pro'
            }
          ],
          'com.apple.CoreSimulator.SimRuntime.tvOS-17-0': [
            {
              dataPath: '/path/to/data3',
              dataPathSize: 3000000,
              logPath: '/path/to/logs3',
              udid: 'GHI789',
              isAvailable: true,
              deviceTypeIdentifier: 'com.apple.AppleTV',
              state: 'Shutdown',
              name: 'Apple TV'
            }
          ]
        }
      };

      execMockResponses = [
        { stdout: JSON.stringify(mockDeviceList), stderr: '' }
      ];

      // Act
      const result = await controller.execute({});

      // Assert - Test behavior: lists all simulators
      expect(result.content[0].text).toContain('Found 3 simulators');
      expect(result.content[0].text).toContain('iPhone 15');
      expect(result.content[0].text).toContain('iPad Pro');
      expect(result.content[0].text).toContain('Apple TV');
    });

    it('should filter by iOS platform', async () => {
      // Arrange
      const mockDeviceList = {
        devices: {
          'com.apple.CoreSimulator.SimRuntime.iOS-17-0': [
            {
              udid: 'ABC123',
              isAvailable: true,
              state: 'Booted',
              name: 'iPhone 15'
            }
          ],
          'com.apple.CoreSimulator.SimRuntime.tvOS-17-0': [
            {
              udid: 'GHI789',
              isAvailable: true,
              state: 'Shutdown',
              name: 'Apple TV'
            }
          ]
        }
      };

      execMockResponses = [
        { stdout: JSON.stringify(mockDeviceList), stderr: '' }
      ];

      // Act
      const result = await controller.execute({ platform: 'iOS' });

      // Assert
      expect(result.content[0].text).toContain('Found 1 simulator');
      expect(result.content[0].text).toContain('iPhone 15');
      expect(result.content[0].text).not.toContain('Apple TV');
    });

    it('should filter by booted state', async () => {
      // Arrange
      const mockDeviceList = {
        devices: {
          'com.apple.CoreSimulator.SimRuntime.iOS-17-0': [
            {
              udid: 'ABC123',
              isAvailable: true,
              state: 'Booted',
              name: 'iPhone 15'
            },
            {
              udid: 'DEF456',
              isAvailable: true,
              state: 'Shutdown',
              name: 'iPad Pro'
            }
          ]
        }
      };

      execMockResponses = [
        { stdout: JSON.stringify(mockDeviceList), stderr: '' }
      ];

      // Act
      const result = await controller.execute({ state: 'Booted' });

      // Assert
      expect(result.content[0].text).toContain('Found 1 simulator');
      expect(result.content[0].text).toContain('iPhone 15');
      expect(result.content[0].text).not.toContain('iPad Pro');
    });

    it('should return error when command execution fails', async () => {
      // Arrange
      const error = new Error('xcrun not found') as NodeExecError;
      error.code = 1;
      execMockResponses = [
        { stdout: '', stderr: 'xcrun not found', error }
      ];

      // Act
      const result = await controller.execute({});

      // Assert
      expect(result.content[0].type).toBe('text');
      expect(result.content[0].text).toMatch(/^❌.*JSON/); // Error about JSON parsing
    });

    it('should show warning when no simulators exist', async () => {
      // Arrange
      execMockResponses = [
        { stdout: JSON.stringify({ devices: {} }), stderr: '' }
      ];

      // Act
      const result = await controller.execute({});

      // Assert
      expect(result.content[0].text).toBe('🔍 No simulators found');
    });

    it('should filter by multiple criteria', async () => {
      // Arrange
      const mockDeviceList = {
        devices: {
          'com.apple.CoreSimulator.SimRuntime.iOS-17-0': [
            {
              udid: 'ABC123',
              isAvailable: true,
              state: 'Booted',
              name: 'iPhone 15'
            },
            {
              udid: 'DEF456',
              isAvailable: true,
              state: 'Shutdown',
              name: 'iPad Pro'
            }
          ],
          'com.apple.CoreSimulator.SimRuntime.tvOS-17-0': [
            {
              udid: 'GHI789',
              isAvailable: true,
              state: 'Booted',
              name: 'Apple TV'
            }
          ]
        }
      };

      execMockResponses = [
        { stdout: JSON.stringify(mockDeviceList), stderr: '' }
      ];

      // Act
      const result = await controller.execute({
        platform: 'iOS',
        state: 'Booted'
      });

      // Assert
      expect(result.content[0].text).toContain('Found 1 simulator');
      expect(result.content[0].text).toContain('iPhone 15');
      expect(result.content[0].text).not.toContain('iPad Pro');
      expect(result.content[0].text).not.toContain('Apple TV');
    });

    it('should return JSON parse error for malformed response', async () => {
      // Arrange
      execMockResponses = [
        { stdout: 'not valid json', stderr: '' }
      ];

      // Act
      const result = await controller.execute({});

      // Assert
      expect(result.content[0].type).toBe('text');
      expect(result.content[0].text).toMatch(/^❌.*not valid JSON/);
    });

    it('should return error for invalid platform', async () => {
      // Arrange, Act, Assert
      const result = await controller.execute({
        platform: 'Android'
      });

      expect(result.content[0].text).toBe('❌ Invalid platform: Android. Valid values are: iOS, macOS, tvOS, watchOS, visionOS');
    });

    it('should return error for invalid state', async () => {
      // Arrange, Act, Assert
      const result = await controller.execute({
        state: 'Running'
      });

      expect(result.content[0].text).toBe('❌ Invalid simulator state: Running. Valid values are: Booted, Booting, Shutdown, Shutting Down');
    });

    it('should filter by device name with partial match', async () => {
      // Arrange
      const mockDeviceList = {
        devices: {
          'com.apple.CoreSimulator.SimRuntime.iOS-17-0': [
            {
              udid: 'ABC123',
              isAvailable: true,
              state: 'Booted',
              name: 'iPhone 15 Pro'
            },
            {
              udid: 'DEF456',
              isAvailable: true,
              state: 'Shutdown',
              name: 'iPhone 14'
            },
            {
              udid: 'GHI789',
              isAvailable: true,
              state: 'Shutdown',
              name: 'iPad Pro'
            }
          ]
        }
      };

      execMockResponses = [
        { stdout: JSON.stringify(mockDeviceList), stderr: '' }
      ];

      // Act
      const result = await controller.execute({ name: '15' });

      // Assert - Tests actual behavior: only devices with "15" in name
      expect(result.content[0].text).toContain('Found 1 simulator');
      expect(result.content[0].text).toContain('iPhone 15 Pro');
      expect(result.content[0].text).not.toContain('iPhone 14');
      expect(result.content[0].text).not.toContain('iPad Pro');
    });

    it('should filter by device name case-insensitive', async () => {
      // Arrange
      const mockDeviceList = {
        devices: {
          'com.apple.CoreSimulator.SimRuntime.iOS-17-0': [
            {
              udid: 'ABC123',
              isAvailable: true,
              state: 'Booted',
              name: 'iPhone 15 Pro'
            },
            {
              udid: 'DEF456',
              isAvailable: true,
              state: 'Shutdown',
              name: 'iPad Air'
            }
          ]
        }
      };

      execMockResponses = [
        { stdout: JSON.stringify(mockDeviceList), stderr: '' }
      ];

      // Act
      const result = await controller.execute({ name: 'iphone' });

      // Assert - Case-insensitive matching
      expect(result.content[0].text).toContain('Found 1 simulator');
      expect(result.content[0].text).toContain('iPhone 15 Pro');
      expect(result.content[0].text).not.toContain('iPad Air');
    });

    it('should combine all filters (platform, state, and name)', async () => {
      // Arrange
      const mockDeviceList = {
        devices: {
          'com.apple.CoreSimulator.SimRuntime.iOS-17-0': [
            {
              udid: 'ABC123',
              isAvailable: true,
              state: 'Booted',
              name: 'iPhone 15 Pro'
            },
            {
              udid: 'DEF456',
              isAvailable: true,
              state: 'Shutdown',
              name: 'iPhone 15'
            },
            {
              udid: 'GHI789',
              isAvailable: true,
              state: 'Booted',
              name: 'iPhone 14'
            }
          ],
          'com.apple.CoreSimulator.SimRuntime.tvOS-17-0': [
            {
              udid: 'TV123',
              isAvailable: true,
              state: 'Booted',
              name: 'Apple TV 15'
            }
          ]
        }
      };

      execMockResponses = [
        { stdout: JSON.stringify(mockDeviceList), stderr: '' }
      ];

      // Act
      const result = await controller.execute({
        platform: 'iOS',
        state: 'Booted',
        name: '15'
      });

      // Assert - All filters applied together
      expect(result.content[0].text).toContain('Found 1 simulator');
      expect(result.content[0].text).toContain('iPhone 15 Pro');
      expect(result.content[0].text).not.toContain('iPhone 14'); // Wrong name
      expect(result.content[0].text).not.toContain('Apple TV'); // Wrong platform
    });

    it('should show no simulators when name filter matches nothing', async () => {
      // Arrange
      const mockDeviceList = {
        devices: {
          'com.apple.CoreSimulator.SimRuntime.iOS-17-0': [
            {
              udid: 'ABC123',
              isAvailable: true,
              state: 'Booted',
              name: 'iPhone 15 Pro'
            }
          ]
        }
      };

      execMockResponses = [
        { stdout: JSON.stringify(mockDeviceList), stderr: '' }
      ];

      // Act
      const result = await controller.execute({ name: 'Galaxy' });

      // Assert
      expect(result.content[0].text).toBe('🔍 No simulators found');
    });
  });
});
```

--------------------------------------------------------------------------------
/src/features/simulator/tests/unit/ListSimulatorsUseCase.unit.test.ts:
--------------------------------------------------------------------------------

```typescript
import { describe, it, expect, jest, beforeEach } from '@jest/globals';
import { ListSimulatorsUseCase } from '../../use-cases/ListSimulatorsUseCase.js';
import { DeviceRepository } from '../../../../infrastructure/repositories/DeviceRepository.js';
import { ListSimulatorsRequest } from '../../domain/ListSimulatorsRequest.js';
import { SimulatorListParseError } from '../../domain/ListSimulatorsResult.js';
import { Platform } from '../../../../shared/domain/Platform.js';
import { SimulatorState } from '../../domain/SimulatorState.js';

describe('ListSimulatorsUseCase', () => {
  let mockDeviceRepository: jest.Mocked<DeviceRepository>;
  let sut: ListSimulatorsUseCase;

  beforeEach(() => {
    mockDeviceRepository = {
      getAllDevices: jest.fn()
    } as any;

    sut = new ListSimulatorsUseCase(mockDeviceRepository);
  });

  describe('execute', () => {
    it('should return all available simulators when no filters', async () => {
      // Arrange
      mockDeviceRepository.getAllDevices.mockResolvedValue({
        'com.apple.CoreSimulator.SimRuntime.iOS-17-0': [
          {
            udid: 'ABC123',
            name: 'iPhone 15',
            state: 'Booted',
            isAvailable: true
          },
          {
            udid: 'DEF456',
            name: 'iPad Pro',
            state: 'Shutdown',
            isAvailable: true
          },
          {
            udid: 'NOTAVAIL',
            name: 'Old iPhone',
            state: 'Shutdown',
            isAvailable: false
          }
        ]
      });

      const request = ListSimulatorsRequest.create();

      // Act
      const result = await sut.execute(request);

      // Assert
      expect(result.isSuccess).toBe(true);
      expect(result.count).toBe(2); // Only available devices
      expect(result.simulators).toHaveLength(2);
      expect(result.simulators[0]).toMatchObject({
        udid: 'ABC123',
        name: 'iPhone 15',
        state: SimulatorState.Booted,
        platform: 'iOS',
        runtime: 'iOS 17.0'
      });
    });

    it('should filter by platform', async () => {
      // Arrange
      mockDeviceRepository.getAllDevices.mockResolvedValue({
        'com.apple.CoreSimulator.SimRuntime.iOS-17-0': [
          {
            udid: 'IOS1',
            name: 'iPhone 15',
            state: 'Booted',
            isAvailable: true
          }
        ],
        'com.apple.CoreSimulator.SimRuntime.tvOS-17-0': [
          {
            udid: 'TV1',
            name: 'Apple TV',
            state: 'Shutdown',
            isAvailable: true
          }
        ]
      });

      const request = ListSimulatorsRequest.create(Platform.iOS);

      // Act
      const result = await sut.execute(request);

      // Assert
      expect(result.isSuccess).toBe(true);
      expect(result.count).toBe(1);
      expect(result.simulators[0].udid).toBe('IOS1');
    });

    it('should filter by state', async () => {
      // Arrange
      mockDeviceRepository.getAllDevices.mockResolvedValue({
        'com.apple.CoreSimulator.SimRuntime.iOS-17-0': [
          {
            udid: 'BOOTED1',
            name: 'iPhone 15',
            state: 'Booted',
            isAvailable: true
          },
          {
            udid: 'SHUTDOWN1',
            name: 'iPad Pro',
            state: 'Shutdown',
            isAvailable: true
          }
        ]
      });

      const request = ListSimulatorsRequest.create(undefined, SimulatorState.Booted);

      // Act
      const result = await sut.execute(request);

      // Assert
      expect(result.isSuccess).toBe(true);
      expect(result.count).toBe(1);
      expect(result.simulators[0].udid).toBe('BOOTED1');
    });

    it('should handle watchOS platform detection', async () => {
      // Arrange
      mockDeviceRepository.getAllDevices.mockResolvedValue({
        'com.apple.CoreSimulator.SimRuntime.watchOS-10-0': [
          {
            udid: 'WATCH1',
            name: 'Apple Watch Series 9',
            state: 'Shutdown',
            isAvailable: true
          }
        ]
      });

      const request = ListSimulatorsRequest.create();

      // Act
      const result = await sut.execute(request);

      // Assert
      expect(result.isSuccess).toBe(true);
      expect(result.simulators[0].platform).toBe('watchOS');
      expect(result.simulators[0].runtime).toBe('watchOS 10.0');
    });

    it('should handle visionOS platform detection', async () => {
      // Arrange
      mockDeviceRepository.getAllDevices.mockResolvedValue({
        'com.apple.CoreSimulator.SimRuntime.visionOS-1-0': [
          {
            udid: 'VISION1',
            name: 'Apple Vision Pro',
            state: 'Shutdown',
            isAvailable: true
          }
        ]
      });

      const request = ListSimulatorsRequest.create();

      // Act
      const result = await sut.execute(request);

      // Assert
      expect(result.isSuccess).toBe(true);
      expect(result.simulators[0].platform).toBe('visionOS');
      expect(result.simulators[0].runtime).toBe('visionOS 1.0');
    });

    it('should handle xrOS platform detection (legacy name for visionOS)', async () => {
      // Arrange
      mockDeviceRepository.getAllDevices.mockResolvedValue({
        'com.apple.CoreSimulator.SimRuntime.xrOS-1-0': [
          {
            udid: 'XR1',
            name: 'Apple Vision Pro',
            state: 'Shutdown',
            isAvailable: true
          }
        ]
      });

      const request = ListSimulatorsRequest.create();

      // Act
      const result = await sut.execute(request);

      // Assert
      expect(result.isSuccess).toBe(true);
      expect(result.simulators[0].platform).toBe('visionOS');
      expect(result.simulators[0].runtime).toBe('visionOS 1.0');
    });

    it('should handle macOS platform detection', async () => {
      // Arrange
      mockDeviceRepository.getAllDevices.mockResolvedValue({
        'com.apple.CoreSimulator.SimRuntime.macOS-14-0': [
          {
            udid: 'MAC1',
            name: 'Mac',
            state: 'Shutdown',
            isAvailable: true
          }
        ]
      });

      const request = ListSimulatorsRequest.create();

      // Act
      const result = await sut.execute(request);

      // Assert
      expect(result.isSuccess).toBe(true);
      expect(result.simulators[0].platform).toBe('macOS');
      expect(result.simulators[0].runtime).toBe('macOS 14.0');
    });

    it('should handle unknown platform', async () => {
      // Arrange
      mockDeviceRepository.getAllDevices.mockResolvedValue({
        'com.apple.CoreSimulator.SimRuntime.unknown-1-0': [
          {
            udid: 'UNKNOWN1',
            name: 'Unknown Device',
            state: 'Shutdown',
            isAvailable: true
          }
        ]
      });

      const request = ListSimulatorsRequest.create();

      // Act
      const result = await sut.execute(request);

      // Assert
      expect(result.isSuccess).toBe(true);
      expect(result.simulators[0].platform).toBe('Unknown');
      expect(result.simulators[0].runtime).toBe('Unknown 1.0');
    });

    it('should handle Booting state', async () => {
      // Arrange
      mockDeviceRepository.getAllDevices.mockResolvedValue({
        'com.apple.CoreSimulator.SimRuntime.iOS-17-0': [
          {
            udid: 'BOOT1',
            name: 'iPhone 15',
            state: 'Booting',
            isAvailable: true
          }
        ]
      });

      const request = ListSimulatorsRequest.create();

      // Act
      const result = await sut.execute(request);

      // Assert
      expect(result.isSuccess).toBe(true);
      expect(result.simulators[0].state).toBe(SimulatorState.Booting);
    });

    it('should handle Shutting Down state', async () => {
      // Arrange
      mockDeviceRepository.getAllDevices.mockResolvedValue({
        'com.apple.CoreSimulator.SimRuntime.iOS-17-0': [
          {
            udid: 'SHUTTING1',
            name: 'iPhone 15',
            state: 'Shutting Down',
            isAvailable: true
          }
        ]
      });

      const request = ListSimulatorsRequest.create();

      // Act
      const result = await sut.execute(request);

      // Assert
      expect(result.isSuccess).toBe(true);
      expect(result.simulators[0].state).toBe(SimulatorState.ShuttingDown);
    });

    it('should handle unknown device state by throwing error', async () => {
      // Arrange
      mockDeviceRepository.getAllDevices.mockResolvedValue({
        'com.apple.CoreSimulator.SimRuntime.iOS-17-0': [
          {
            udid: 'WEIRD1',
            name: 'iPhone 15',
            state: 'WeirdState',
            isAvailable: true
          }
        ]
      });

      const request = ListSimulatorsRequest.create();

      // Act
      const result = await sut.execute(request);

      // Assert - should fail with error about unrecognized state
      expect(result.isSuccess).toBe(false);
      expect(result.error?.message).toContain('Invalid simulator state: WeirdState');
    });

    it('should handle runtime without version number', async () => {
      // Arrange
      mockDeviceRepository.getAllDevices.mockResolvedValue({
        'com.apple.CoreSimulator.SimRuntime.iOS': [
          {
            udid: 'NOVERSION1',
            name: 'iPhone',
            state: 'Shutdown',
            isAvailable: true
          }
        ]
      });

      const request = ListSimulatorsRequest.create();

      // Act
      const result = await sut.execute(request);

      // Assert
      expect(result.isSuccess).toBe(true);
      expect(result.simulators[0].runtime).toBe('iOS Unknown');
    });

    it('should handle repository errors', async () => {
      // Arrange
      const error = new Error('Repository failed');
      mockDeviceRepository.getAllDevices.mockRejectedValue(error);

      const request = ListSimulatorsRequest.create();

      // Act
      const result = await sut.execute(request);

      // Assert
      expect(result.isSuccess).toBe(false);
      expect(result.error).toBeInstanceOf(SimulatorListParseError);
      expect(result.error?.message).toBe('Failed to parse simulator list: not valid JSON');
    });

    it('should return empty list when no simulators available', async () => {
      // Arrange
      mockDeviceRepository.getAllDevices.mockResolvedValue({});

      const request = ListSimulatorsRequest.create();

      // Act
      const result = await sut.execute(request);

      // Assert
      expect(result.isSuccess).toBe(true);
      expect(result.count).toBe(0);
      expect(result.simulators).toHaveLength(0);
    });

    it('should filter by device name with partial match', async () => {
      // Arrange
      mockDeviceRepository.getAllDevices.mockResolvedValue({
        'com.apple.CoreSimulator.SimRuntime.iOS-17-0': [
          {
            udid: 'ABC123',
            name: 'iPhone 15 Pro',
            state: 'Booted',
            isAvailable: true
          },
          {
            udid: 'DEF456',
            name: 'iPhone 14',
            state: 'Shutdown',
            isAvailable: true
          },
          {
            udid: 'GHI789',
            name: 'iPad Pro',
            state: 'Shutdown',
            isAvailable: true
          }
        ]
      });

      const request = ListSimulatorsRequest.create(undefined, undefined, '15');

      // Act
      const result = await sut.execute(request);

      // Assert
      expect(result.isSuccess).toBe(true);
      expect(result.count).toBe(1);
      expect(result.simulators[0].name).toBe('iPhone 15 Pro');
    });

    it('should filter by device name case-insensitive', async () => {
      // Arrange
      mockDeviceRepository.getAllDevices.mockResolvedValue({
        'com.apple.CoreSimulator.SimRuntime.iOS-17-0': [
          {
            udid: 'ABC123',
            name: 'iPhone 15 Pro',
            state: 'Booted',
            isAvailable: true
          },
          {
            udid: 'DEF456',
            name: 'iPad Air',
            state: 'Shutdown',
            isAvailable: true
          }
        ]
      });

      const request = ListSimulatorsRequest.create(undefined, undefined, 'iphone');

      // Act
      const result = await sut.execute(request);

      // Assert
      expect(result.isSuccess).toBe(true);
      expect(result.count).toBe(1);
      expect(result.simulators[0].name).toBe('iPhone 15 Pro');
    });

    it('should combine all filters (platform, state, and name)', async () => {
      // Arrange
      mockDeviceRepository.getAllDevices.mockResolvedValue({
        'com.apple.CoreSimulator.SimRuntime.iOS-17-0': [
          {
            udid: 'ABC123',
            name: 'iPhone 15 Pro',
            state: 'Booted',
            isAvailable: true
          },
          {
            udid: 'DEF456',
            name: 'iPhone 15',
            state: 'Shutdown',
            isAvailable: true
          }
        ],
        'com.apple.CoreSimulator.SimRuntime.tvOS-17-0': [
          {
            udid: 'GHI789',
            name: 'Apple TV 15',
            state: 'Booted',
            isAvailable: true
          }
        ]
      });

      const request = ListSimulatorsRequest.create(Platform.iOS, SimulatorState.Booted, '15');

      // Act
      const result = await sut.execute(request);

      // Assert
      expect(result.isSuccess).toBe(true);
      expect(result.count).toBe(1);
      expect(result.simulators[0].name).toBe('iPhone 15 Pro');
      expect(result.simulators[0].platform).toBe('iOS');
      expect(result.simulators[0].state).toBe(SimulatorState.Booted);
    });
  });
});
```

--------------------------------------------------------------------------------
/src/cli.ts:
--------------------------------------------------------------------------------

```typescript
#!/usr/bin/env node

import { program } from 'commander';
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
import { join, resolve, dirname } from 'path';
import { fileURLToPath } from 'url';
import { homedir } from 'os';
import { execSync } from 'child_process';
import * as readline from 'readline/promises';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const PACKAGE_ROOT = resolve(__dirname, '..');

interface ClaudeConfig {
  mcpServers?: Record<string, any>;
  [key: string]: any;
}

interface ClaudeSettings {
  hooks?: any;
  model?: string;
  [key: string]: any;
}

class MCPXcodeSetup {
  private rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
  });

  async setup() {
    console.log('🔧 MCP Xcode Setup\n');
    
    // 1. Ask about MCP server
    console.log('📡 MCP Server Configuration');
    const setupMCP = await this.askYesNo('Would you like to install the MCP Xcode server?');
    let mcpScope: 'global' | 'project' | null = null;
    if (setupMCP) {
      mcpScope = await this.askMCPScope();
      await this.setupMCPServer(mcpScope);
    }
    
    // 2. Ask about hooks (independent of MCP choice)
    console.log('\n📝 Xcode Sync Hook Configuration');
    console.log('The hook will automatically sync file operations with Xcode projects.');
    console.log('It syncs when:');
    console.log('  - Files are created, modified, deleted, or moved');
    console.log('  - An .xcodeproj file exists in the parent directories');
    console.log('  - The project hasn\'t opted out (via .no-xcode-sync or .no-xcode-autoadd file)');
    
    const setupHooks = await this.askYesNo('\nWould you like to enable Xcode file sync?');
    let hookScope: 'global' | 'project' | null = null;
    if (setupHooks) {
      hookScope = await this.askHookScope();
      await this.setupHooks(hookScope);
    }
    
    // 3. Build helper tools if anything was installed
    if (setupMCP || setupHooks) {
      console.log('\n📦 Building helper tools...');
      await this.buildHelperTools();
    }
    
    // 4. Show completion message
    console.log('\n✅ Setup complete!');
    console.log('\nNext steps:');
    console.log('1. Restart Claude Code for changes to take effect');
    
    const hasProjectConfig = (mcpScope === 'project' || hookScope === 'project');
    if (hasProjectConfig) {
      console.log('2. Commit .claude/settings.json to share with your team');
    }
    
    this.rl.close();
  }

  private async askMCPScope(): Promise<'global' | 'project'> {
    const answer = await this.rl.question(
      'Where should the MCP server be installed?\n' +
      '1) Global (~/.claude.json)\n' +
      '2) Project (.claude/settings.json)\n' +
      'Choice (1 or 2): '
    );
    
    return answer === '2' ? 'project' : 'global';
  }

  private async askHookScope(): Promise<'global' | 'project'> {
    const answer = await this.rl.question(
      'Where should the Xcode sync hook be installed?\n' +
      '1) Global (~/.claude/settings.json)\n' +
      '2) Project (.claude/settings.json)\n' +
      'Choice (1 or 2): '
    );
    
    return answer === '2' ? 'project' : 'global';
  }

  private async askYesNo(question: string): Promise<boolean> {
    const answer = await this.rl.question(`${question} (y/n): `);
    return answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes';
  }

  private getMCPConfigPath(scope: 'global' | 'project'): string {
    if (scope === 'global') {
      // MCP servers go in ~/.claude.json for global
      return join(homedir(), '.claude.json');
    } else {
      // Project scope - everything in .claude/settings.json
      return join(process.cwd(), '.claude', 'settings.json');
    }
  }

  private getHooksConfigPath(scope: 'global' | 'project'): string {
    if (scope === 'global') {
      // Hooks go in ~/.claude/settings.json for global
      return join(homedir(), '.claude', 'settings.json');
    } else {
      // Project scope - everything in .claude/settings.json
      return join(process.cwd(), '.claude', 'settings.json');
    }
  }

  private loadConfig(path: string): any {
    if (existsSync(path)) {
      try {
        return JSON.parse(readFileSync(path, 'utf8'));
      } catch (error) {
        console.warn(`⚠️  Warning: Could not parse existing config at ${path}`);
        return {};
      }
    }
    return {};
  }

  private saveConfig(path: string, config: any) {
    const dir = dirname(path);
    if (!existsSync(dir)) {
      mkdirSync(dir, { recursive: true });
    }
    writeFileSync(path, JSON.stringify(config, null, 2), 'utf8');
  }

  private async setupMCPServer(scope: 'global' | 'project') {
    const configPath = this.getMCPConfigPath(scope);
    const config = this.loadConfig(configPath);
    
    // Determine the command based on installation type
    const isGlobalInstall = await this.checkGlobalInstall();
    const serverPath = isGlobalInstall 
      ? 'mcp-xcode-server'
      : resolve(PACKAGE_ROOT, 'dist', 'index.js');
    
    const serverConfig = {
      type: 'stdio',
      command: isGlobalInstall ? 'mcp-xcode-server' : 'node',
      args: isGlobalInstall ? ['serve'] : [serverPath],
      env: {}
    };
    
    // Add to mcpServers
    if (!config.mcpServers) {
      config.mcpServers = {};
    }
    
    if (config.mcpServers['mcp-xcode-server']) {
      const overwrite = await this.askYesNo('MCP Xcode server already configured. Overwrite?');
      if (!overwrite) {
        console.log('Skipping MCP server configuration.');
        return;
      }
    }
    
    config.mcpServers['mcp-xcode-server'] = serverConfig;
    
    this.saveConfig(configPath, config);
    console.log(`✅ MCP server configured in ${configPath}`);
  }

  private async setupHooks(scope: 'global' | 'project') {
    const configPath = this.getHooksConfigPath(scope);
    const config = this.loadConfig(configPath) as ClaudeSettings;
    
    const hookScriptPath = resolve(PACKAGE_ROOT, 'scripts', 'xcode-sync.swift');
    
    // Set up hooks using the correct Claude settings format
    if (!config.hooks) {
      config.hooks = {};
    }
    
    if (!config.hooks.PostToolUse) {
      config.hooks.PostToolUse = [];
    }
    
    // Check if hook already exists
    const existingHookIndex = config.hooks.PostToolUse.findIndex((hook: any) => 
      hook.matcher === 'Write|Edit|MultiEdit|Bash' && 
      (hook.hooks?.[0]?.command?.includes('xcode-sync.swift') || hook.hooks?.[0]?.command?.includes('xcode-sync.js'))
    );
    
    if (existingHookIndex >= 0) {
      const overwrite = await this.askYesNo('PostToolUse hook for Xcode sync already exists. Overwrite?');
      if (!overwrite) {
        console.log('Skipping hook configuration.');
        return;
      }
      // Remove existing hook
      config.hooks.PostToolUse.splice(existingHookIndex, 1);
    }
    
    // Add the new hook in Claude's expected format
    config.hooks.PostToolUse.push({
      matcher: 'Write|Edit|MultiEdit|Bash',
      hooks: [{
        type: 'command',
        command: hookScriptPath
      }]
    });
    
    this.saveConfig(configPath, config);
    console.log(`✅ Xcode sync hook configured in ${configPath}`);
  }

  private async checkGlobalInstall(): Promise<boolean> {
    try {
      execSync('which mcp-xcode-server', { stdio: 'ignore' });
      return true;
    } catch {
      return false;
    }
  }

  private async buildHelperTools() {
    try {
      // Build TypeScript
      console.log('  Building TypeScript...');
      execSync('npm run build', { 
        cwd: PACKAGE_ROOT,
        stdio: 'inherit' 
      });
      
      // Build XcodeProjectModifier for the sync hook
      console.log('  Building XcodeProjectModifier for sync hook...');
      await this.buildXcodeProjectModifier();
      
    } catch (error) {
      console.error('❌ Failed to build:', error);
      process.exit(1);
    }
  }
  
  private async buildXcodeProjectModifier() {
    const modifierDir = '/tmp/XcodeProjectModifier';
    const modifierBinary = join(modifierDir, '.build', 'release', 'XcodeProjectModifier');
    
    // Check if already built
    if (existsSync(modifierBinary)) {
      // Check if it's the real modifier or just a mock
      try {
        const output = execSync(`"${modifierBinary}" --help 2>&1 || true`, { encoding: 'utf8' });
        if (output.includes('Mock XcodeProjectModifier')) {
          console.log('    Detected mock modifier, rebuilding with real implementation...');
          // Remove the mock
          execSync(`rm -rf "${modifierDir}"`, { stdio: 'ignore' });
        } else {
          console.log('    XcodeProjectModifier already built');
          return;
        }
      } catch {
        // If --help fails, rebuild
        execSync(`rm -rf "${modifierDir}"`, { stdio: 'ignore' });
      }
    }
    
    console.log('    Creating XcodeProjectModifier...');
    
    // Create directory structure
    mkdirSync(join(modifierDir, 'Sources', 'XcodeProjectModifier'), { recursive: true });
    
    // Create Package.swift
    const packageSwift = `// swift-tools-version: 5.9
import PackageDescription

let package = Package(
    name: "XcodeProjectModifier",
    platforms: [.macOS(.v10_15)],
    dependencies: [
        .package(url: "https://github.com/tuist/XcodeProj.git", from: "8.0.0"),
        .package(url: "https://github.com/apple/swift-argument-parser", from: "1.0.0")
    ],
    targets: [
        .executableTarget(
            name: "XcodeProjectModifier",
            dependencies: [
                "XcodeProj",
                .product(name: "ArgumentParser", package: "swift-argument-parser")
            ]
        )
    ]
)`;
    
    writeFileSync(join(modifierDir, 'Package.swift'), packageSwift);
    
    // Create main.swift (simplified version for the hook)
    const mainSwift = `import Foundation
import XcodeProj
import ArgumentParser

struct XcodeProjectModifier: ParsableCommand {
    @Argument(help: "Path to the .xcodeproj file")
    var projectPath: String
    
    @Argument(help: "Action to perform: add or remove")
    var action: String
    
    @Argument(help: "Path to the file to add/remove")
    var filePath: String
    
    @Argument(help: "Target name")
    var targetName: String
    
    @Option(name: .long, help: "Group path for the file")
    var groupPath: String = ""
    
    func run() throws {
        let project = try XcodeProj(pathString: projectPath)
        let pbxproj = project.pbxproj
        
        guard let target = pbxproj.nativeTargets.first(where: { $0.name == targetName }) else {
            print("Error: Target '\\(targetName)' not found")
            throw ExitCode.failure
        }
        
        let fileName = URL(fileURLWithPath: filePath).lastPathComponent
        
        if action == "remove" {
            // Remove file reference
            if let fileRef = pbxproj.fileReferences.first(where: { $0.path == fileName || $0.path == filePath }) {
                pbxproj.delete(object: fileRef)
                print("Removed \\(fileName) from project")
            }
        } else if action == "add" {
            // Remove existing reference if it exists
            if let existingRef = pbxproj.fileReferences.first(where: { $0.path == fileName || $0.path == filePath }) {
                pbxproj.delete(object: existingRef)
            }
            
            // Add new file reference
            let fileRef = PBXFileReference(
                sourceTree: .group,
                name: fileName,
                path: filePath
            )
            pbxproj.add(object: fileRef)
            
            // Add to appropriate build phase based on file type
            let fileExtension = URL(fileURLWithPath: filePath).pathExtension.lowercased()
            
            if ["swift", "m", "mm", "c", "cpp", "cc", "cxx"].contains(fileExtension) {
                // Add to sources build phase
                if let sourcesBuildPhase = target.buildPhases.compactMap({ $0 as? PBXSourcesBuildPhase }).first {
                    let buildFile = PBXBuildFile(file: fileRef)
                    pbxproj.add(object: buildFile)
                    sourcesBuildPhase.files?.append(buildFile)
                }
            } else if ["png", "jpg", "jpeg", "gif", "pdf", "json", "plist", "xib", "storyboard", "xcassets"].contains(fileExtension) {
                // Add to resources build phase
                if let resourcesBuildPhase = target.buildPhases.compactMap({ $0 as? PBXResourcesBuildPhase }).first {
                    let buildFile = PBXBuildFile(file: fileRef)
                    pbxproj.add(object: buildFile)
                    resourcesBuildPhase.files?.append(buildFile)
                }
            }
            
            // Add to group
            if let mainGroup = try? pbxproj.rootProject()?.mainGroup {
                mainGroup.children.append(fileRef)
            }
            
            print("Added \\(fileName) to project")
        }
        
        try project.write(path: Path(projectPath))
    }
}

XcodeProjectModifier.main()`;
    
    writeFileSync(join(modifierDir, 'Sources', 'XcodeProjectModifier', 'main.swift'), mainSwift);
    
    // Build the tool
    console.log('    Building with Swift Package Manager...');
    try {
      execSync('swift build -c release', {
        cwd: modifierDir,
        stdio: 'pipe'
      });
      console.log('    ✅ XcodeProjectModifier built successfully');
    } catch (error) {
      console.warn('    ⚠️  Warning: Could not build XcodeProjectModifier. Sync hook may not work until first MCP server use.');
    }
  }
}

// CLI Commands
program
  .name('mcp-xcode-server')
  .description('MCP Xcode Server - Setup and management')
  .version('2.4.0');

program
  .command('setup')
  .description('Interactive setup for MCP Xcode server and hooks')
  .action(async () => {
    const setup = new MCPXcodeSetup();
    await setup.setup();
  });

program
  .command('serve')
  .description('Start the MCP server')
  .action(async () => {
    // Simply run the server
    await import('./index.js');
  });

// Parse command line arguments
program.parse();

// If no command specified, show help
if (!process.argv.slice(2).length) {
  program.outputHelp();
}
```
Page 3/4FirstPrevNextLast