#
tokens: 49278/50000 14/337 files (page 7/11)
lines: off (toggle) GitHub
raw markdown copy
This is page 7 of 11. Use http://codebase.md/cameroncooke/xcodebuildmcp?lines=false&page={x} to view the full context.

# Directory Structure

```
├── .axe-version
├── .claude
│   └── agents
│       └── xcodebuild-mcp-qa-tester.md
├── .cursor
│   ├── BUGBOT.md
│   └── environment.json
├── .cursorrules
├── .github
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   └── feature_request.yml
│   └── workflows
│       ├── ci.yml
│       ├── claude-code-review.yml
│       ├── claude-dispatch.yml
│       ├── claude.yml
│       ├── droid-code-review.yml
│       ├── README.md
│       ├── release.yml
│       └── sentry.yml
├── .gitignore
├── .prettierignore
├── .prettierrc.js
├── .vscode
│   ├── extensions.json
│   ├── launch.json
│   ├── mcp.json
│   ├── settings.json
│   └── tasks.json
├── AGENTS.md
├── banner.png
├── build-plugins
│   ├── plugin-discovery.js
│   ├── plugin-discovery.ts
│   └── tsconfig.json
├── CHANGELOG.md
├── CLAUDE.md
├── CODE_OF_CONDUCT.md
├── Dockerfile
├── docs
│   ├── ARCHITECTURE.md
│   ├── CODE_QUALITY.md
│   ├── CONTRIBUTING.md
│   ├── ESLINT_TYPE_SAFETY.md
│   ├── MANUAL_TESTING.md
│   ├── NODEJS_2025.md
│   ├── PLUGIN_DEVELOPMENT.md
│   ├── RELEASE_PROCESS.md
│   ├── RELOADEROO_FOR_XCODEBUILDMCP.md
│   ├── RELOADEROO_XCODEBUILDMCP_PRIMER.md
│   ├── RELOADEROO.md
│   ├── session_management_plan.md
│   ├── session-aware-migration-todo.md
│   ├── TEST_RUNNER_ENV_IMPLEMENTATION_PLAN.md
│   ├── TESTING.md
│   └── TOOLS.md
├── eslint.config.js
├── example_projects
│   ├── .vscode
│   │   └── launch.json
│   ├── iOS
│   │   ├── .cursor
│   │   │   └── rules
│   │   │       └── errors.mdc
│   │   ├── .vscode
│   │   │   └── settings.json
│   │   ├── Makefile
│   │   ├── MCPTest
│   │   │   ├── Assets.xcassets
│   │   │   │   ├── AccentColor.colorset
│   │   │   │   │   └── Contents.json
│   │   │   │   ├── AppIcon.appiconset
│   │   │   │   │   └── Contents.json
│   │   │   │   └── Contents.json
│   │   │   ├── ContentView.swift
│   │   │   ├── MCPTestApp.swift
│   │   │   └── Preview Content
│   │   │       └── Preview Assets.xcassets
│   │   │           └── Contents.json
│   │   ├── MCPTest.xcodeproj
│   │   │   ├── project.pbxproj
│   │   │   └── xcshareddata
│   │   │       └── xcschemes
│   │   │           └── MCPTest.xcscheme
│   │   └── MCPTestUITests
│   │       └── MCPTestUITests.swift
│   ├── iOS_Calculator
│   │   ├── CalculatorApp
│   │   │   ├── Assets.xcassets
│   │   │   │   ├── AccentColor.colorset
│   │   │   │   │   └── Contents.json
│   │   │   │   ├── AppIcon.appiconset
│   │   │   │   │   └── Contents.json
│   │   │   │   └── Contents.json
│   │   │   ├── CalculatorApp.swift
│   │   │   └── CalculatorApp.xctestplan
│   │   ├── CalculatorApp.xcodeproj
│   │   │   ├── project.pbxproj
│   │   │   └── xcshareddata
│   │   │       └── xcschemes
│   │   │           └── CalculatorApp.xcscheme
│   │   ├── CalculatorApp.xcworkspace
│   │   │   └── contents.xcworkspacedata
│   │   ├── CalculatorAppPackage
│   │   │   ├── .gitignore
│   │   │   ├── Package.swift
│   │   │   ├── Sources
│   │   │   │   └── CalculatorAppFeature
│   │   │   │       ├── BackgroundEffect.swift
│   │   │   │       ├── CalculatorButton.swift
│   │   │   │       ├── CalculatorDisplay.swift
│   │   │   │       ├── CalculatorInputHandler.swift
│   │   │   │       ├── CalculatorService.swift
│   │   │   │       └── ContentView.swift
│   │   │   └── Tests
│   │   │       └── CalculatorAppFeatureTests
│   │   │           └── CalculatorServiceTests.swift
│   │   ├── CalculatorAppTests
│   │   │   └── CalculatorAppTests.swift
│   │   └── Config
│   │       ├── Debug.xcconfig
│   │       ├── Release.xcconfig
│   │       ├── Shared.xcconfig
│   │       └── Tests.xcconfig
│   ├── macOS
│   │   ├── MCPTest
│   │   │   ├── Assets.xcassets
│   │   │   │   ├── AccentColor.colorset
│   │   │   │   │   └── Contents.json
│   │   │   │   ├── AppIcon.appiconset
│   │   │   │   │   └── Contents.json
│   │   │   │   └── Contents.json
│   │   │   ├── ContentView.swift
│   │   │   ├── MCPTest.entitlements
│   │   │   ├── MCPTestApp.swift
│   │   │   └── Preview Content
│   │   │       └── Preview Assets.xcassets
│   │   │           └── Contents.json
│   │   └── MCPTest.xcodeproj
│   │       ├── project.pbxproj
│   │       └── xcshareddata
│   │           └── xcschemes
│   │               └── MCPTest.xcscheme
│   └── spm
│       ├── .gitignore
│       ├── Package.resolved
│       ├── Package.swift
│       ├── Sources
│       │   ├── long-server
│       │   │   └── main.swift
│       │   ├── quick-task
│       │   │   └── main.swift
│       │   ├── spm
│       │   │   └── main.swift
│       │   └── TestLib
│       │       └── TaskManager.swift
│       └── Tests
│           └── TestLibTests
│               └── SimpleTests.swift
├── LICENSE
├── mcp-install-dark.png
├── package-lock.json
├── package.json
├── README.md
├── scripts
│   ├── analysis
│   │   └── tools-analysis.ts
│   ├── bundle-axe.sh
│   ├── check-code-patterns.js
│   ├── release.sh
│   ├── tools-cli.ts
│   └── update-tools-docs.ts
├── server.json
├── smithery.yaml
├── src
│   ├── core
│   │   ├── __tests__
│   │   │   └── resources.test.ts
│   │   ├── dynamic-tools.ts
│   │   ├── plugin-registry.ts
│   │   ├── plugin-types.ts
│   │   └── resources.ts
│   ├── doctor-cli.ts
│   ├── index.ts
│   ├── mcp
│   │   ├── resources
│   │   │   ├── __tests__
│   │   │   │   ├── devices.test.ts
│   │   │   │   ├── doctor.test.ts
│   │   │   │   └── simulators.test.ts
│   │   │   ├── devices.ts
│   │   │   ├── doctor.ts
│   │   │   └── simulators.ts
│   │   └── tools
│   │       ├── device
│   │       │   ├── __tests__
│   │       │   │   ├── build_device.test.ts
│   │       │   │   ├── get_device_app_path.test.ts
│   │       │   │   ├── index.test.ts
│   │       │   │   ├── install_app_device.test.ts
│   │       │   │   ├── launch_app_device.test.ts
│   │       │   │   ├── list_devices.test.ts
│   │       │   │   ├── re-exports.test.ts
│   │       │   │   ├── stop_app_device.test.ts
│   │       │   │   └── test_device.test.ts
│   │       │   ├── build_device.ts
│   │       │   ├── clean.ts
│   │       │   ├── discover_projs.ts
│   │       │   ├── get_app_bundle_id.ts
│   │       │   ├── get_device_app_path.ts
│   │       │   ├── index.ts
│   │       │   ├── install_app_device.ts
│   │       │   ├── launch_app_device.ts
│   │       │   ├── list_devices.ts
│   │       │   ├── list_schemes.ts
│   │       │   ├── show_build_settings.ts
│   │       │   ├── start_device_log_cap.ts
│   │       │   ├── stop_app_device.ts
│   │       │   ├── stop_device_log_cap.ts
│   │       │   └── test_device.ts
│   │       ├── discovery
│   │       │   ├── __tests__
│   │       │   │   └── discover_tools.test.ts
│   │       │   ├── discover_tools.ts
│   │       │   └── index.ts
│   │       ├── doctor
│   │       │   ├── __tests__
│   │       │   │   ├── doctor.test.ts
│   │       │   │   └── index.test.ts
│   │       │   ├── doctor.ts
│   │       │   ├── index.ts
│   │       │   └── lib
│   │       │       └── doctor.deps.ts
│   │       ├── logging
│   │       │   ├── __tests__
│   │       │   │   ├── index.test.ts
│   │       │   │   ├── start_device_log_cap.test.ts
│   │       │   │   ├── start_sim_log_cap.test.ts
│   │       │   │   ├── stop_device_log_cap.test.ts
│   │       │   │   └── stop_sim_log_cap.test.ts
│   │       │   ├── index.ts
│   │       │   ├── start_device_log_cap.ts
│   │       │   ├── start_sim_log_cap.ts
│   │       │   ├── stop_device_log_cap.ts
│   │       │   └── stop_sim_log_cap.ts
│   │       ├── macos
│   │       │   ├── __tests__
│   │       │   │   ├── build_macos.test.ts
│   │       │   │   ├── build_run_macos.test.ts
│   │       │   │   ├── get_mac_app_path.test.ts
│   │       │   │   ├── index.test.ts
│   │       │   │   ├── launch_mac_app.test.ts
│   │       │   │   ├── re-exports.test.ts
│   │       │   │   ├── stop_mac_app.test.ts
│   │       │   │   └── test_macos.test.ts
│   │       │   ├── build_macos.ts
│   │       │   ├── build_run_macos.ts
│   │       │   ├── clean.ts
│   │       │   ├── discover_projs.ts
│   │       │   ├── get_mac_app_path.ts
│   │       │   ├── get_mac_bundle_id.ts
│   │       │   ├── index.ts
│   │       │   ├── launch_mac_app.ts
│   │       │   ├── list_schemes.ts
│   │       │   ├── show_build_settings.ts
│   │       │   ├── stop_mac_app.ts
│   │       │   └── test_macos.ts
│   │       ├── project-discovery
│   │       │   ├── __tests__
│   │       │   │   ├── discover_projs.test.ts
│   │       │   │   ├── get_app_bundle_id.test.ts
│   │       │   │   ├── get_mac_bundle_id.test.ts
│   │       │   │   ├── index.test.ts
│   │       │   │   ├── list_schemes.test.ts
│   │       │   │   └── show_build_settings.test.ts
│   │       │   ├── discover_projs.ts
│   │       │   ├── get_app_bundle_id.ts
│   │       │   ├── get_mac_bundle_id.ts
│   │       │   ├── index.ts
│   │       │   ├── list_schemes.ts
│   │       │   └── show_build_settings.ts
│   │       ├── project-scaffolding
│   │       │   ├── __tests__
│   │       │   │   ├── index.test.ts
│   │       │   │   ├── scaffold_ios_project.test.ts
│   │       │   │   └── scaffold_macos_project.test.ts
│   │       │   ├── index.ts
│   │       │   ├── scaffold_ios_project.ts
│   │       │   └── scaffold_macos_project.ts
│   │       ├── session-management
│   │       │   ├── __tests__
│   │       │   │   ├── index.test.ts
│   │       │   │   ├── session_clear_defaults.test.ts
│   │       │   │   ├── session_set_defaults.test.ts
│   │       │   │   └── session_show_defaults.test.ts
│   │       │   ├── index.ts
│   │       │   ├── session_clear_defaults.ts
│   │       │   ├── session_set_defaults.ts
│   │       │   └── session_show_defaults.ts
│   │       ├── simulator
│   │       │   ├── __tests__
│   │       │   │   ├── boot_sim.test.ts
│   │       │   │   ├── build_run_sim.test.ts
│   │       │   │   ├── build_sim.test.ts
│   │       │   │   ├── get_sim_app_path.test.ts
│   │       │   │   ├── index.test.ts
│   │       │   │   ├── install_app_sim.test.ts
│   │       │   │   ├── launch_app_logs_sim.test.ts
│   │       │   │   ├── launch_app_sim.test.ts
│   │       │   │   ├── list_sims.test.ts
│   │       │   │   ├── open_sim.test.ts
│   │       │   │   ├── record_sim_video.test.ts
│   │       │   │   ├── screenshot.test.ts
│   │       │   │   ├── stop_app_sim.test.ts
│   │       │   │   └── test_sim.test.ts
│   │       │   ├── boot_sim.ts
│   │       │   ├── build_run_sim.ts
│   │       │   ├── build_sim.ts
│   │       │   ├── clean.ts
│   │       │   ├── describe_ui.ts
│   │       │   ├── discover_projs.ts
│   │       │   ├── get_app_bundle_id.ts
│   │       │   ├── get_sim_app_path.ts
│   │       │   ├── index.ts
│   │       │   ├── install_app_sim.ts
│   │       │   ├── launch_app_logs_sim.ts
│   │       │   ├── launch_app_sim.ts
│   │       │   ├── list_schemes.ts
│   │       │   ├── list_sims.ts
│   │       │   ├── open_sim.ts
│   │       │   ├── record_sim_video.ts
│   │       │   ├── screenshot.ts
│   │       │   ├── show_build_settings.ts
│   │       │   ├── stop_app_sim.ts
│   │       │   └── test_sim.ts
│   │       ├── simulator-management
│   │       │   ├── __tests__
│   │       │   │   ├── erase_sims.test.ts
│   │       │   │   ├── index.test.ts
│   │       │   │   ├── reset_sim_location.test.ts
│   │       │   │   ├── set_sim_appearance.test.ts
│   │       │   │   ├── set_sim_location.test.ts
│   │       │   │   └── sim_statusbar.test.ts
│   │       │   ├── boot_sim.ts
│   │       │   ├── erase_sims.ts
│   │       │   ├── index.ts
│   │       │   ├── list_sims.ts
│   │       │   ├── open_sim.ts
│   │       │   ├── reset_sim_location.ts
│   │       │   ├── set_sim_appearance.ts
│   │       │   ├── set_sim_location.ts
│   │       │   └── sim_statusbar.ts
│   │       ├── swift-package
│   │       │   ├── __tests__
│   │       │   │   ├── active-processes.test.ts
│   │       │   │   ├── index.test.ts
│   │       │   │   ├── swift_package_build.test.ts
│   │       │   │   ├── swift_package_clean.test.ts
│   │       │   │   ├── swift_package_list.test.ts
│   │       │   │   ├── swift_package_run.test.ts
│   │       │   │   ├── swift_package_stop.test.ts
│   │       │   │   └── swift_package_test.test.ts
│   │       │   ├── active-processes.ts
│   │       │   ├── index.ts
│   │       │   ├── swift_package_build.ts
│   │       │   ├── swift_package_clean.ts
│   │       │   ├── swift_package_list.ts
│   │       │   ├── swift_package_run.ts
│   │       │   ├── swift_package_stop.ts
│   │       │   └── swift_package_test.ts
│   │       ├── ui-testing
│   │       │   ├── __tests__
│   │       │   │   ├── button.test.ts
│   │       │   │   ├── describe_ui.test.ts
│   │       │   │   ├── gesture.test.ts
│   │       │   │   ├── index.test.ts
│   │       │   │   ├── key_press.test.ts
│   │       │   │   ├── key_sequence.test.ts
│   │       │   │   ├── long_press.test.ts
│   │       │   │   ├── screenshot.test.ts
│   │       │   │   ├── swipe.test.ts
│   │       │   │   ├── tap.test.ts
│   │       │   │   ├── touch.test.ts
│   │       │   │   └── type_text.test.ts
│   │       │   ├── button.ts
│   │       │   ├── describe_ui.ts
│   │       │   ├── gesture.ts
│   │       │   ├── index.ts
│   │       │   ├── key_press.ts
│   │       │   ├── key_sequence.ts
│   │       │   ├── long_press.ts
│   │       │   ├── screenshot.ts
│   │       │   ├── swipe.ts
│   │       │   ├── tap.ts
│   │       │   ├── touch.ts
│   │       │   └── type_text.ts
│   │       └── utilities
│   │           ├── __tests__
│   │           │   ├── clean.test.ts
│   │           │   └── index.test.ts
│   │           ├── clean.ts
│   │           └── index.ts
│   ├── server
│   │   └── server.ts
│   ├── test-utils
│   │   └── mock-executors.ts
│   ├── types
│   │   └── common.ts
│   └── utils
│       ├── __tests__
│       │   ├── build-utils.test.ts
│       │   ├── environment.test.ts
│       │   ├── session-aware-tool-factory.test.ts
│       │   ├── session-store.test.ts
│       │   ├── simulator-utils.test.ts
│       │   ├── test-runner-env-integration.test.ts
│       │   └── typed-tool-factory.test.ts
│       ├── axe
│       │   └── index.ts
│       ├── axe-helpers.ts
│       ├── build
│       │   └── index.ts
│       ├── build-utils.ts
│       ├── capabilities.ts
│       ├── command.ts
│       ├── CommandExecutor.ts
│       ├── environment.ts
│       ├── errors.ts
│       ├── execution
│       │   └── index.ts
│       ├── FileSystemExecutor.ts
│       ├── log_capture.ts
│       ├── log-capture
│       │   └── index.ts
│       ├── logger.ts
│       ├── logging
│       │   └── index.ts
│       ├── plugin-registry
│       │   └── index.ts
│       ├── responses
│       │   └── index.ts
│       ├── schema-helpers.ts
│       ├── sentry.ts
│       ├── session-store.ts
│       ├── simulator-utils.ts
│       ├── template
│       │   └── index.ts
│       ├── template-manager.ts
│       ├── test
│       │   └── index.ts
│       ├── test-common.ts
│       ├── tool-registry.ts
│       ├── typed-tool-factory.ts
│       ├── validation
│       │   └── index.ts
│       ├── validation.ts
│       ├── version
│       │   └── index.ts
│       ├── video_capture.ts
│       ├── video-capture
│       │   └── index.ts
│       ├── xcode.ts
│       ├── xcodemake
│       │   └── index.ts
│       └── xcodemake.ts
├── tsconfig.json
├── tsconfig.test.json
├── tsup.config.ts
└── vitest.config.ts
```

# Files

--------------------------------------------------------------------------------
/src/mcp/tools/device/__tests__/get_device_app_path.test.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Tests for get_device_app_path plugin (unified)
 * Following CLAUDE.md testing standards with literal validation
 * Using dependency injection for deterministic testing
 */

import { describe, it, expect, beforeEach } from 'vitest';
import { z } from 'zod';
import { createMockExecutor } from '../../../../test-utils/mock-executors.ts';
import getDeviceAppPath, { get_device_app_pathLogic } from '../get_device_app_path.ts';
import { sessionStore } from '../../../../utils/session-store.ts';

describe('get_device_app_path plugin', () => {
  beforeEach(() => {
    sessionStore.clear();
  });

  describe('Export Field Validation (Literal)', () => {
    it('should have correct name', () => {
      expect(getDeviceAppPath.name).toBe('get_device_app_path');
    });

    it('should have correct description', () => {
      expect(getDeviceAppPath.description).toBe(
        'Retrieves the built app path for a connected device.',
      );
    });

    it('should have handler function', () => {
      expect(typeof getDeviceAppPath.handler).toBe('function');
    });

    it('should expose only platform in public schema', () => {
      const schema = z.object(getDeviceAppPath.schema).strict();
      expect(schema.safeParse({}).success).toBe(true);
      expect(schema.safeParse({ platform: 'iOS' }).success).toBe(true);
      expect(schema.safeParse({ projectPath: '/path/to/project.xcodeproj' }).success).toBe(false);

      const schemaKeys = Object.keys(getDeviceAppPath.schema).sort();
      expect(schemaKeys).toEqual(['platform']);
    });
  });

  describe('XOR Validation', () => {
    it('should error when neither projectPath nor workspacePath provided', async () => {
      const result = await getDeviceAppPath.handler({
        scheme: 'MyScheme',
      });
      expect(result.isError).toBe(true);
      expect(result.content[0].text).toContain('Missing required session defaults');
      expect(result.content[0].text).toContain('Provide a project or workspace');
    });

    it('should error when both projectPath and workspacePath provided', async () => {
      const result = await getDeviceAppPath.handler({
        projectPath: '/path/to/project.xcodeproj',
        workspacePath: '/path/to/workspace.xcworkspace',
        scheme: 'MyScheme',
      });
      expect(result.isError).toBe(true);
      expect(result.content[0].text).toContain('Parameter validation failed');
      expect(result.content[0].text).toContain('Mutually exclusive parameters provided');
    });
  });

  describe('Handler Requirements', () => {
    it('should require scheme when missing', async () => {
      const result = await getDeviceAppPath.handler({
        projectPath: '/path/to/project.xcodeproj',
      });
      expect(result.isError).toBe(true);
      expect(result.content[0].text).toContain('Missing required session defaults');
      expect(result.content[0].text).toContain('scheme is required');
    });

    it('should require project or workspace when scheme default exists', async () => {
      sessionStore.setDefaults({ scheme: 'MyScheme' });

      const result = await getDeviceAppPath.handler({});
      expect(result.isError).toBe(true);
      expect(result.content[0].text).toContain('Provide a project or workspace');
    });
  });

  describe('Handler Behavior (Complete Literal Returns)', () => {
    // Note: Parameter validation is now handled by Zod schema validation in createTypedTool,
    // so invalid parameters never reach the logic function. Schema validation is tested above.

    it('should generate correct xcodebuild command for iOS', async () => {
      const calls: Array<{
        args: any[];
        description: string;
        suppressErrors: boolean;
        workingDirectory: string | undefined;
      }> = [];

      const mockExecutor = (
        args: any[],
        description: string,
        suppressErrors: boolean,
        workingDirectory: string | undefined,
      ) => {
        calls.push({ args, description, suppressErrors, workingDirectory });
        return Promise.resolve({
          success: true,
          output:
            'Build settings for scheme "MyScheme"\n\nBUILT_PRODUCTS_DIR = /path/to/build/Debug-iphoneos\nFULL_PRODUCT_NAME = MyApp.app\n',
          error: undefined,
          process: { pid: 12345 },
        });
      };

      await get_device_app_pathLogic(
        {
          projectPath: '/path/to/project.xcodeproj',
          scheme: 'MyScheme',
        },
        mockExecutor,
      );

      expect(calls).toHaveLength(1);
      expect(calls[0]).toEqual({
        args: [
          'xcodebuild',
          '-showBuildSettings',
          '-project',
          '/path/to/project.xcodeproj',
          '-scheme',
          'MyScheme',
          '-configuration',
          'Debug',
          '-destination',
          'generic/platform=iOS',
        ],
        description: 'Get App Path',
        suppressErrors: true,
        workingDirectory: undefined,
      });
    });

    it('should generate correct xcodebuild command for watchOS', async () => {
      const calls: Array<{
        args: any[];
        description: string;
        suppressErrors: boolean;
        workingDirectory: string | undefined;
      }> = [];

      const mockExecutor = (
        args: any[],
        description: string,
        suppressErrors: boolean,
        workingDirectory: string | undefined,
      ) => {
        calls.push({ args, description, suppressErrors, workingDirectory });
        return Promise.resolve({
          success: true,
          output:
            'Build settings for scheme "MyScheme"\n\nBUILT_PRODUCTS_DIR = /path/to/build/Debug-watchos\nFULL_PRODUCT_NAME = MyApp.app\n',
          error: undefined,
          process: { pid: 12345 },
        });
      };

      await get_device_app_pathLogic(
        {
          projectPath: '/path/to/project.xcodeproj',
          scheme: 'MyScheme',
          platform: 'watchOS',
        },
        mockExecutor,
      );

      expect(calls).toHaveLength(1);
      expect(calls[0]).toEqual({
        args: [
          'xcodebuild',
          '-showBuildSettings',
          '-project',
          '/path/to/project.xcodeproj',
          '-scheme',
          'MyScheme',
          '-configuration',
          'Debug',
          '-destination',
          'generic/platform=watchOS',
        ],
        description: 'Get App Path',
        suppressErrors: true,
        workingDirectory: undefined,
      });
    });

    it('should generate correct xcodebuild command for workspace with iOS', async () => {
      const calls: Array<{
        args: any[];
        description: string;
        suppressErrors: boolean;
        workingDirectory: string | undefined;
      }> = [];

      const mockExecutor = (
        args: any[],
        description: string,
        suppressErrors: boolean,
        workingDirectory: string | undefined,
      ) => {
        calls.push({ args, description, suppressErrors, workingDirectory });
        return Promise.resolve({
          success: true,
          output:
            'Build settings for scheme "MyScheme"\n\nBUILT_PRODUCTS_DIR = /path/to/build/Debug-iphoneos\nFULL_PRODUCT_NAME = MyApp.app\n',
          error: undefined,
          process: { pid: 12345 },
        });
      };

      await get_device_app_pathLogic(
        {
          workspacePath: '/path/to/workspace.xcworkspace',
          scheme: 'MyScheme',
        },
        mockExecutor,
      );

      expect(calls).toHaveLength(1);
      expect(calls[0]).toEqual({
        args: [
          'xcodebuild',
          '-showBuildSettings',
          '-workspace',
          '/path/to/workspace.xcworkspace',
          '-scheme',
          'MyScheme',
          '-configuration',
          'Debug',
          '-destination',
          'generic/platform=iOS',
        ],
        description: 'Get App Path',
        suppressErrors: true,
        workingDirectory: undefined,
      });
    });

    it('should return exact successful app path retrieval response', async () => {
      const mockExecutor = createMockExecutor({
        success: true,
        output:
          'Build settings for scheme "MyScheme"\n\nBUILT_PRODUCTS_DIR = /path/to/build/Debug-iphoneos\nFULL_PRODUCT_NAME = MyApp.app\n',
      });

      const result = await get_device_app_pathLogic(
        {
          projectPath: '/path/to/project.xcodeproj',
          scheme: 'MyScheme',
        },
        mockExecutor,
      );

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: '✅ App path retrieved successfully: /path/to/build/Debug-iphoneos/MyApp.app',
          },
          {
            type: 'text',
            text: 'Next Steps:\n1. Get bundle ID: get_app_bundle_id({ appPath: "/path/to/build/Debug-iphoneos/MyApp.app" })\n2. Install app on device: install_app_device({ deviceId: "DEVICE_UDID", appPath: "/path/to/build/Debug-iphoneos/MyApp.app" })\n3. Launch app on device: launch_app_device({ deviceId: "DEVICE_UDID", bundleId: "BUNDLE_ID" })',
          },
        ],
      });
    });

    it('should return exact command failure response', async () => {
      const mockExecutor = createMockExecutor({
        success: false,
        error: 'xcodebuild: error: The project does not exist.',
      });

      const result = await get_device_app_pathLogic(
        {
          projectPath: '/path/to/nonexistent.xcodeproj',
          scheme: 'MyScheme',
        },
        mockExecutor,
      );

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: 'Failed to get app path: xcodebuild: error: The project does not exist.',
          },
        ],
        isError: true,
      });
    });

    it('should return exact parse failure response', async () => {
      const mockExecutor = createMockExecutor({
        success: true,
        output: 'Build settings without required fields',
      });

      const result = await get_device_app_pathLogic(
        {
          projectPath: '/path/to/project.xcodeproj',
          scheme: 'MyScheme',
        },
        mockExecutor,
      );

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: 'Failed to extract app path from build settings. Make sure the app has been built first.',
          },
        ],
        isError: true,
      });
    });

    it('should include optional configuration parameter in command', async () => {
      const calls: Array<{
        args: any[];
        description: string;
        suppressErrors: boolean;
        workingDirectory: string | undefined;
      }> = [];

      const mockExecutor = (
        args: any[],
        description: string,
        suppressErrors: boolean,
        workingDirectory: string | undefined,
      ) => {
        calls.push({ args, description, suppressErrors, workingDirectory });
        return Promise.resolve({
          success: true,
          output:
            'Build settings for scheme "MyScheme"\n\nBUILT_PRODUCTS_DIR = /path/to/build/Release-iphoneos\nFULL_PRODUCT_NAME = MyApp.app\n',
          error: undefined,
          process: { pid: 12345 },
        });
      };

      await get_device_app_pathLogic(
        {
          projectPath: '/path/to/project.xcodeproj',
          scheme: 'MyScheme',
          configuration: 'Release',
        },
        mockExecutor,
      );

      expect(calls).toHaveLength(1);
      expect(calls[0]).toEqual({
        args: [
          'xcodebuild',
          '-showBuildSettings',
          '-project',
          '/path/to/project.xcodeproj',
          '-scheme',
          'MyScheme',
          '-configuration',
          'Release',
          '-destination',
          'generic/platform=iOS',
        ],
        description: 'Get App Path',
        suppressErrors: true,
        workingDirectory: undefined,
      });
    });

    it('should return exact exception handling response', async () => {
      const mockExecutor = () => {
        return Promise.reject(new Error('Network error'));
      };

      const result = await get_device_app_pathLogic(
        {
          projectPath: '/path/to/project.xcodeproj',
          scheme: 'MyScheme',
        },
        mockExecutor,
      );

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: 'Error retrieving app path: Network error',
          },
        ],
        isError: true,
      });
    });

    it('should return exact string error handling response', async () => {
      const mockExecutor = () => {
        return Promise.reject('String error');
      };

      const result = await get_device_app_pathLogic(
        {
          projectPath: '/path/to/project.xcodeproj',
          scheme: 'MyScheme',
        },
        mockExecutor,
      );

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: 'Error retrieving app path: String error',
          },
        ],
        isError: true,
      });
    });
  });
});

```

--------------------------------------------------------------------------------
/src/mcp/tools/swift-package/__tests__/swift_package_stop.test.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Tests for swift_package_stop plugin
 * Following CLAUDE.md testing standards with pure dependency injection
 * No vitest mocking - using dependency injection pattern
 */

import { describe, it, expect } from 'vitest';
import { z } from 'zod';
import swiftPackageStop, {
  createMockProcessManager,
  swift_package_stopLogic,
  type ProcessManager,
} from '../swift_package_stop.ts';

/**
 * Mock process implementation for testing
 */
class MockProcess {
  public killed = false;
  public killSignal: string | undefined;
  public exitCallback: (() => void) | undefined;
  public shouldThrowOnKill = false;
  public killError: Error | string | undefined;
  public pid: number;

  constructor(pid: number) {
    this.pid = pid;
  }

  kill(signal?: string): void {
    if (this.shouldThrowOnKill) {
      throw this.killError ?? new Error('Process kill failed');
    }
    this.killed = true;
    this.killSignal = signal;
  }

  on(event: string, callback: () => void): void {
    if (event === 'exit') {
      this.exitCallback = callback;
    }
  }

  // Simulate immediate exit for test control
  simulateExit(): void {
    if (this.exitCallback) {
      this.exitCallback();
    }
  }
}

describe('swift_package_stop plugin', () => {
  describe('Export Field Validation (Literal)', () => {
    it('should have correct name', () => {
      expect(swiftPackageStop.name).toBe('swift_package_stop');
    });

    it('should have correct description', () => {
      expect(swiftPackageStop.description).toBe(
        'Stops a running Swift Package executable started with swift_package_run',
      );
    });

    it('should have handler function', () => {
      expect(typeof swiftPackageStop.handler).toBe('function');
    });

    it('should validate schema correctly', () => {
      // Test valid inputs
      expect(swiftPackageStop.schema.pid.safeParse(12345).success).toBe(true);
      expect(swiftPackageStop.schema.pid.safeParse(0).success).toBe(true);
      expect(swiftPackageStop.schema.pid.safeParse(-1).success).toBe(true);

      // Test invalid inputs
      expect(swiftPackageStop.schema.pid.safeParse('not-a-number').success).toBe(false);
      expect(swiftPackageStop.schema.pid.safeParse(null).success).toBe(false);
      expect(swiftPackageStop.schema.pid.safeParse(undefined).success).toBe(false);
      expect(swiftPackageStop.schema.pid.safeParse({}).success).toBe(false);
      expect(swiftPackageStop.schema.pid.safeParse([]).success).toBe(false);
    });
  });

  describe('Handler Behavior (Complete Literal Returns)', () => {
    it('should return exact error for process not found', async () => {
      const mockProcessManager = createMockProcessManager({
        getProcess: () => undefined,
      });

      const result = await swift_package_stopLogic({ pid: 99999 }, mockProcessManager);

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: '⚠️ No running process found with PID 99999. Use swift_package_run to check active processes.',
          },
        ],
        isError: true,
      });
    });

    it('should successfully stop a process that exits gracefully', async () => {
      const mockProcess = new MockProcess(12345);
      const startedAt = new Date('2023-01-01T10:00:00.000Z');

      const mockProcessManager = createMockProcessManager({
        getProcess: (pid: number) =>
          pid === 12345
            ? {
                process: mockProcess,
                startedAt: startedAt,
              }
            : undefined,
        removeProcess: () => true,
      });

      // Set up the process to exit immediately when exit handler is registered
      const originalOn = mockProcess.on.bind(mockProcess);
      mockProcess.on = (event: string, callback: () => void) => {
        originalOn(event, callback);
        if (event === 'exit') {
          // Simulate immediate graceful exit
          setImmediate(() => callback());
        }
      };

      const result = await swift_package_stopLogic(
        { pid: 12345 },
        mockProcessManager,
        10, // Very short timeout for testing
      );

      expect(mockProcess.killed).toBe(true);
      expect(mockProcess.killSignal).toBe('SIGTERM');
      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: '✅ Stopped executable (was running since 2023-01-01T10:00:00.000Z)',
          },
          {
            type: 'text',
            text: '💡 Process terminated. You can now run swift_package_run again if needed.',
          },
        ],
      });
    });

    it('should force kill process if graceful termination fails', async () => {
      const mockProcess = new MockProcess(67890);
      const startedAt = new Date('2023-02-15T14:30:00.000Z');

      const mockProcessManager = createMockProcessManager({
        getProcess: (pid: number) =>
          pid === 67890
            ? {
                process: mockProcess,
                startedAt: startedAt,
              }
            : undefined,
        removeProcess: () => true,
      });

      // Mock the process to NOT exit gracefully (no callback invocation)
      const killCalls: string[] = [];
      const originalKill = mockProcess.kill.bind(mockProcess);
      mockProcess.kill = (signal?: string) => {
        killCalls.push(signal ?? 'default');
        originalKill(signal);
      };

      // Set up timeout to trigger SIGKILL after SIGTERM
      const originalOn = mockProcess.on.bind(mockProcess);
      mockProcess.on = (event: string, callback: () => void) => {
        originalOn(event, callback);
        // Do NOT call the callback to simulate hanging process
      };

      const result = await swift_package_stopLogic(
        { pid: 67890 },
        mockProcessManager,
        10, // Very short timeout for testing
      );

      expect(killCalls).toEqual(['SIGTERM', 'SIGKILL']);
      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: '✅ Stopped executable (was running since 2023-02-15T14:30:00.000Z)',
          },
          {
            type: 'text',
            text: '💡 Process terminated. You can now run swift_package_run again if needed.',
          },
        ],
      });
    });

    it('should handle process kill error and return error response', async () => {
      const mockProcess = new MockProcess(54321);
      const startedAt = new Date('2023-03-20T09:15:00.000Z');

      // Configure process to throw error on kill
      mockProcess.shouldThrowOnKill = true;
      mockProcess.killError = new Error('ESRCH: No such process');

      const mockProcessManager = createMockProcessManager({
        getProcess: (pid: number) =>
          pid === 54321
            ? {
                process: mockProcess,
                startedAt: startedAt,
              }
            : undefined,
      });

      const result = await swift_package_stopLogic({ pid: 54321 }, mockProcessManager);

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: 'Error: Failed to stop process\nDetails: ESRCH: No such process',
          },
        ],
        isError: true,
      });
    });

    it('should handle non-Error exception in catch block', async () => {
      const mockProcess = new MockProcess(11111);
      const startedAt = new Date('2023-04-10T16:45:00.000Z');

      // Configure process to throw non-Error object
      mockProcess.shouldThrowOnKill = true;
      mockProcess.killError = 'Process termination failed';

      const mockProcessManager = createMockProcessManager({
        getProcess: (pid: number) =>
          pid === 11111
            ? {
                process: mockProcess,
                startedAt: startedAt,
              }
            : undefined,
      });

      const result = await swift_package_stopLogic({ pid: 11111 }, mockProcessManager);

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: 'Error: Failed to stop process\nDetails: Process termination failed',
          },
        ],
        isError: true,
      });
    });

    it('should handle process found but exit event never fires and timeout occurs', async () => {
      const mockProcess = new MockProcess(22222);
      const startedAt = new Date('2023-05-05T12:00:00.000Z');

      const mockProcessManager = createMockProcessManager({
        getProcess: (pid: number) =>
          pid === 22222
            ? {
                process: mockProcess,
                startedAt: startedAt,
              }
            : undefined,
        removeProcess: () => true,
      });

      const killCalls: string[] = [];
      const originalKill = mockProcess.kill.bind(mockProcess);
      mockProcess.kill = (signal?: string) => {
        killCalls.push(signal ?? 'default');
        originalKill(signal);
      };

      // Mock process.on to register the exit handler but never call it (timeout scenario)
      const originalOn = mockProcess.on.bind(mockProcess);
      mockProcess.on = (event: string, callback: () => void) => {
        originalOn(event, callback);
        // Handler is registered but callback never called (simulates hanging process)
      };

      const result = await swift_package_stopLogic(
        { pid: 22222 },
        mockProcessManager,
        10, // Very short timeout for testing
      );

      expect(killCalls).toEqual(['SIGTERM', 'SIGKILL']);
      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: '✅ Stopped executable (was running since 2023-05-05T12:00:00.000Z)',
          },
          {
            type: 'text',
            text: '💡 Process terminated. You can now run swift_package_run again if needed.',
          },
        ],
      });
    });

    it('should handle edge case with pid 0', async () => {
      const mockProcessManager = createMockProcessManager({
        getProcess: () => undefined,
      });

      const result = await swift_package_stopLogic({ pid: 0 }, mockProcessManager);

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: '⚠️ No running process found with PID 0. Use swift_package_run to check active processes.',
          },
        ],
        isError: true,
      });
    });

    it('should handle edge case with negative pid', async () => {
      const mockProcessManager = createMockProcessManager({
        getProcess: () => undefined,
      });

      const result = await swift_package_stopLogic({ pid: -1 }, mockProcessManager);

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: '⚠️ No running process found with PID -1. Use swift_package_run to check active processes.',
          },
        ],
        isError: true,
      });
    });

    it('should handle process that exits after first SIGTERM call', async () => {
      const mockProcess = new MockProcess(33333);
      const startedAt = new Date('2023-06-01T08:30:00.000Z');

      const mockProcessManager = createMockProcessManager({
        getProcess: (pid: number) =>
          pid === 33333
            ? {
                process: mockProcess,
                startedAt: startedAt,
              }
            : undefined,
        removeProcess: () => true,
      });

      const killCalls: string[] = [];
      const originalKill = mockProcess.kill.bind(mockProcess);
      mockProcess.kill = (signal?: string) => {
        killCalls.push(signal ?? 'default');
        originalKill(signal);
      };

      // Set up the process to exit immediately when exit handler is registered
      const originalOn = mockProcess.on.bind(mockProcess);
      mockProcess.on = (event: string, callback: () => void) => {
        originalOn(event, callback);
        if (event === 'exit') {
          // Simulate immediate graceful exit
          setImmediate(() => callback());
        }
      };

      const result = await swift_package_stopLogic(
        { pid: 33333 },
        mockProcessManager,
        10, // Very short timeout for testing
      );

      expect(killCalls).toEqual(['SIGTERM']); // Should not call SIGKILL
      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: '✅ Stopped executable (was running since 2023-06-01T08:30:00.000Z)',
          },
          {
            type: 'text',
            text: '💡 Process terminated. You can now run swift_package_run again if needed.',
          },
        ],
      });
    });

    it('should handle undefined pid parameter', async () => {
      const mockProcessManager = createMockProcessManager({
        getProcess: () => undefined,
      });

      const result = await swift_package_stopLogic({} as any, mockProcessManager);

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: '⚠️ No running process found with PID undefined. Use swift_package_run to check active processes.',
          },
        ],
        isError: true,
      });
    });
  });
});

```

--------------------------------------------------------------------------------
/src/mcp/tools/ui-testing/__tests__/screenshot.test.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Tests for screenshot tool plugin
 */

import { describe, it, expect, beforeEach } from 'vitest';
import { z } from 'zod';
import {
  createMockExecutor,
  createMockFileSystemExecutor,
  createNoopExecutor,
} from '../../../../test-utils/mock-executors.ts';
import { SystemError } from '../../../../utils/responses/index.ts';
import screenshotPlugin, { screenshotLogic } from '../screenshot.ts';

describe('Screenshot Plugin', () => {
  describe('Export Field Validation (Literal)', () => {
    it('should have correct name', () => {
      expect(screenshotPlugin.name).toBe('screenshot');
    });

    it('should have correct description', () => {
      expect(screenshotPlugin.description).toBe(
        "Captures screenshot for visual verification. For UI coordinates, use describe_ui instead (don't determine coordinates from screenshots).",
      );
    });

    it('should have handler function', () => {
      expect(typeof screenshotPlugin.handler).toBe('function');
    });

    it('should validate schema fields with safeParse', () => {
      const schema = z.object(screenshotPlugin.schema);

      // Valid case
      expect(
        schema.safeParse({
          simulatorUuid: '12345678-1234-1234-1234-123456789012',
        }).success,
      ).toBe(true);

      // Invalid simulatorUuid
      expect(
        schema.safeParse({
          simulatorUuid: 'invalid-uuid',
        }).success,
      ).toBe(false);

      // Missing simulatorUuid
      expect(schema.safeParse({}).success).toBe(false);
    });
  });

  describe('Plugin Handler Validation', () => {
    it('should return Zod validation error for missing simulatorUuid', async () => {
      const result = await screenshotPlugin.handler({});

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: 'Error: Parameter validation failed\nDetails: Invalid parameters:\nsimulatorUuid: Required',
          },
        ],
        isError: true,
      });
    });

    it('should return Zod validation error for invalid UUID format', async () => {
      const result = await screenshotPlugin.handler({
        simulatorUuid: 'invalid-uuid',
      });

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: 'Error: Parameter validation failed\nDetails: Invalid parameters:\nsimulatorUuid: Invalid Simulator UUID format',
          },
        ],
        isError: true,
      });
    });
  });

  describe('Command Generation', () => {
    it('should generate correct xcrun simctl command for basic screenshot', async () => {
      const capturedCommands: string[][] = [];
      const trackingExecutor = async (command: string[]) => {
        capturedCommands.push(command);
        return {
          success: true,
          output: 'Screenshot saved',
          error: undefined,
          process: { pid: 12345 },
        };
      };

      const mockImageBuffer = Buffer.from('fake-image-data', 'utf8');
      const mockFileSystemExecutor = createMockFileSystemExecutor({
        readFile: async () => mockImageBuffer.toString('utf8'),
      });

      await screenshotLogic(
        {
          simulatorUuid: '12345678-1234-1234-1234-123456789012',
        },
        trackingExecutor,
        mockFileSystemExecutor,
        { tmpdir: () => '/tmp', join: (...paths) => paths.join('/') },
        { v4: () => 'test-uuid' },
      );

      // Should capture the screenshot command first
      expect(capturedCommands[0]).toEqual([
        'xcrun',
        'simctl',
        'io',
        '12345678-1234-1234-1234-123456789012',
        'screenshot',
        '/tmp/screenshot_test-uuid.png',
      ]);
    });

    it('should generate correct xcrun simctl command with different simulator UUID', async () => {
      const capturedCommands: string[][] = [];
      const trackingExecutor = async (command: string[]) => {
        capturedCommands.push(command);
        return {
          success: true,
          output: 'Screenshot saved',
          error: undefined,
          process: { pid: 12345 },
        };
      };

      const mockImageBuffer = Buffer.from('fake-image-data', 'utf8');
      const mockFileSystemExecutor = createMockFileSystemExecutor({
        readFile: async () => mockImageBuffer.toString('utf8'),
      });

      await screenshotLogic(
        {
          simulatorUuid: 'ABCDEF12-3456-7890-ABCD-ABCDEFABCDEF',
        },
        trackingExecutor,
        mockFileSystemExecutor,
        { tmpdir: () => '/var/tmp', join: (...paths) => paths.join('/') },
        { v4: () => 'another-uuid' },
      );

      expect(capturedCommands[0]).toEqual([
        'xcrun',
        'simctl',
        'io',
        'ABCDEF12-3456-7890-ABCD-ABCDEFABCDEF',
        'screenshot',
        '/var/tmp/screenshot_another-uuid.png',
      ]);
    });

    it('should generate correct xcrun simctl command with custom path dependencies', async () => {
      const capturedCommands: string[][] = [];
      const trackingExecutor = async (command: string[]) => {
        capturedCommands.push(command);
        return {
          success: true,
          output: 'Screenshot saved',
          error: undefined,
          process: { pid: 12345 },
        };
      };

      const mockImageBuffer = Buffer.from('fake-image-data', 'utf8');
      const mockFileSystemExecutor = createMockFileSystemExecutor({
        readFile: async () => mockImageBuffer.toString('utf8'),
      });

      await screenshotLogic(
        {
          simulatorUuid: '98765432-1098-7654-3210-987654321098',
        },
        trackingExecutor,
        mockFileSystemExecutor,
        {
          tmpdir: () => '/custom/temp/dir',
          join: (...paths) => paths.join('\\'), // Windows-style path joining
        },
        { v4: () => 'custom-uuid' },
      );

      expect(capturedCommands[0]).toEqual([
        'xcrun',
        'simctl',
        'io',
        '98765432-1098-7654-3210-987654321098',
        'screenshot',
        '/custom/temp/dir\\screenshot_custom-uuid.png',
      ]);
    });

    it('should generate correct xcrun simctl command with generated UUID when no UUID deps provided', async () => {
      const capturedCommands: string[][] = [];
      const trackingExecutor = async (command: string[]) => {
        capturedCommands.push(command);
        return {
          success: true,
          output: 'Screenshot saved',
          error: undefined,
          process: { pid: 12345 },
        };
      };

      const mockImageBuffer = Buffer.from('fake-image-data', 'utf8');
      const mockFileSystemExecutor = createMockFileSystemExecutor({
        readFile: async () => mockImageBuffer.toString('utf8'),
      });

      await screenshotLogic(
        {
          simulatorUuid: '12345678-1234-1234-1234-123456789012',
        },
        trackingExecutor,
        mockFileSystemExecutor,
        { tmpdir: () => '/tmp', join: (...paths) => paths.join('/') },
        // No UUID deps provided - should use real uuidv4()
      );

      // Verify the command structure but not the exact UUID since it's generated
      expect(capturedCommands[0].slice(0, 5)).toEqual([
        'xcrun',
        'simctl',
        'io',
        '12345678-1234-1234-1234-123456789012',
        'screenshot',
      ]);
      expect(capturedCommands[0][5]).toMatch(/^\/tmp\/screenshot_[a-f0-9-]+\.png$/);
    });
  });

  describe('Handler Behavior (Complete Literal Returns)', () => {
    it('should handle parameter validation via plugin handler (not logic function)', async () => {
      // Note: With Zod validation in createTypedTool, the screenshotLogic function
      // will never receive invalid parameters - validation happens at the handler level.
      // This test documents that screenshotLogic assumes valid parameters.
      const result = await screenshotLogic(
        {
          simulatorUuid: '12345678-1234-1234-1234-123456789012',
        },
        createMockExecutor({
          success: true,
          output: 'Screenshot saved',
          error: undefined,
        }),
        createMockFileSystemExecutor({
          readFile: async () => Buffer.from('fake-image-data', 'utf8').toString('utf8'),
        }),
      );

      expect(result.isError).toBe(false);
      expect(result.content[0].type).toBe('image');
    });

    it('should return success for valid screenshot capture', async () => {
      const mockImageBuffer = Buffer.from('fake-image-data', 'utf8');

      const mockExecutor = createMockExecutor({
        success: true,
        output: 'Screenshot saved',
        error: undefined,
      });

      const mockFileSystemExecutor = createMockFileSystemExecutor({
        readFile: async () => mockImageBuffer.toString('utf8'),
      });

      const result = await screenshotLogic(
        {
          simulatorUuid: '12345678-1234-1234-1234-123456789012',
        },
        mockExecutor,
        mockFileSystemExecutor,
      );

      expect(result).toEqual({
        content: [
          {
            type: 'image',
            data: 'fake-image-data',
            mimeType: 'image/jpeg',
          },
        ],
        isError: false,
      });
    });

    it('should handle command execution failure', async () => {
      const mockExecutor = createMockExecutor({
        success: false,
        output: '',
        error: 'Simulator not found',
      });

      const result = await screenshotLogic(
        {
          simulatorUuid: '12345678-1234-1234-1234-123456789012',
        },
        mockExecutor,
        createMockFileSystemExecutor(),
      );

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: 'Error: System error executing screenshot: Failed to capture screenshot: Simulator not found',
          },
        ],
        isError: true,
      });
    });

    it('should handle file reading errors', async () => {
      const mockExecutor = createMockExecutor({
        success: true,
        output: 'Screenshot saved',
        error: undefined,
      });

      const mockFileSystemExecutor = createMockFileSystemExecutor({
        readFile: async () => {
          throw new Error('File not found');
        },
      });

      const result = await screenshotLogic(
        {
          simulatorUuid: '12345678-1234-1234-1234-123456789012',
        },
        mockExecutor,
        mockFileSystemExecutor,
      );

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: 'Error: Screenshot captured but failed to process image file: File not found',
          },
        ],
        isError: true,
      });
    });

    it('should handle file cleanup errors gracefully', async () => {
      const mockImageBuffer = Buffer.from('fake-image-data', 'utf8');

      const mockExecutor = createMockExecutor({
        success: true,
        output: 'Screenshot saved',
        error: undefined,
      });

      const mockFileSystemExecutor = createMockFileSystemExecutor({
        readFile: async () => mockImageBuffer.toString('utf8'),
        // unlink method is not overridden, so it will use the default (no-op)
        // which simulates the cleanup failure being caught and logged
      });

      const result = await screenshotLogic(
        {
          simulatorUuid: '12345678-1234-1234-1234-123456789012',
        },
        mockExecutor,
        mockFileSystemExecutor,
      );

      // Should still return successful result despite cleanup failure
      expect(result).toEqual({
        content: [
          {
            type: 'image',
            data: 'fake-image-data',
            mimeType: 'image/jpeg',
          },
        ],
        isError: false,
      });
    });

    it('should handle SystemError from command execution', async () => {
      const mockExecutor = async () => {
        throw new SystemError('System error occurred');
      };

      const result = await screenshotLogic(
        {
          simulatorUuid: '12345678-1234-1234-1234-123456789012',
        },
        mockExecutor,
        createMockFileSystemExecutor(),
      );

      expect(result).toEqual({
        content: [
          { type: 'text', text: 'Error: System error executing screenshot: System error occurred' },
        ],
        isError: true,
      });
    });

    it('should handle unexpected Error objects', async () => {
      const mockExecutor = async () => {
        throw new Error('Unexpected error');
      };

      const result = await screenshotLogic(
        {
          simulatorUuid: '12345678-1234-1234-1234-123456789012',
        },
        mockExecutor,
        createMockFileSystemExecutor(),
      );

      expect(result).toEqual({
        content: [{ type: 'text', text: 'Error: An unexpected error occurred: Unexpected error' }],
        isError: true,
      });
    });

    it('should handle unexpected string errors', async () => {
      const mockExecutor = async () => {
        throw 'String error';
      };

      const result = await screenshotLogic(
        {
          simulatorUuid: '12345678-1234-1234-1234-123456789012',
        },
        mockExecutor,
        createMockFileSystemExecutor(),
      );

      expect(result).toEqual({
        content: [{ type: 'text', text: 'Error: An unexpected error occurred: String error' }],
        isError: true,
      });
    });
  });
});

```

--------------------------------------------------------------------------------
/src/utils/build-utils.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Build Utilities - Higher-level abstractions for Xcode build operations
 *
 * This utility module provides specialized functions for build-related operations
 * across different platforms (macOS, iOS, watchOS, etc.). It serves as a higher-level
 * abstraction layer on top of the core Xcode utilities.
 *
 * Responsibilities:
 * - Providing a unified interface (executeXcodeBuild) for all build operations
 * - Handling build-specific parameter formatting and validation
 * - Standardizing response formatting for build results
 * - Managing build-specific error handling and reporting
 * - Supporting various build actions (build, clean, showBuildSettings, etc.)
 * - Supporting xcodemake as an alternative build strategy for faster incremental builds
 *
 * This file depends on the lower-level utilities in xcode.ts for command execution
 * while adding build-specific behavior, formatting, and error handling.
 */

import { log } from './logger.ts';
import { XcodePlatform, constructDestinationString } from './xcode.ts';
import { CommandExecutor, CommandExecOptions } from './command.ts';
import { ToolResponse, SharedBuildParams, PlatformBuildOptions } from '../types/common.ts';
import { createTextResponse, consolidateContentForClaudeCode } from './validation.ts';
import {
  isXcodemakeEnabled,
  isXcodemakeAvailable,
  executeXcodemakeCommand,
  executeMakeCommand,
  doesMakefileExist,
  doesMakeLogFileExist,
} from './xcodemake.ts';
import path from 'path';

/**
 * Common function to execute an Xcode build command across platforms
 * @param params Common build parameters
 * @param platformOptions Platform-specific options
 * @param preferXcodebuild Whether to prefer xcodebuild over xcodemake, useful for if xcodemake is failing
 * @param buildAction The xcodebuild action to perform (e.g., 'build', 'clean', 'test')
 * @param executor Optional command executor for dependency injection (used for testing)
 * @returns Promise resolving to tool response
 */
export async function executeXcodeBuildCommand(
  params: SharedBuildParams,
  platformOptions: PlatformBuildOptions,
  preferXcodebuild: boolean = false,
  buildAction: string = 'build',
  executor: CommandExecutor,
  execOpts?: CommandExecOptions,
): Promise<ToolResponse> {
  // Collect warnings, errors, and stderr messages from the build output
  const buildMessages: { type: 'text'; text: string }[] = [];
  function grepWarningsAndErrors(text: string): { type: 'warning' | 'error'; content: string }[] {
    return text
      .split('\n')
      .map((content) => {
        if (/warning:/i.test(content)) return { type: 'warning', content };
        if (/error:/i.test(content)) return { type: 'error', content };
        return null;
      })
      .filter(Boolean) as { type: 'warning' | 'error'; content: string }[];
  }

  log('info', `Starting ${platformOptions.logPrefix} ${buildAction} for scheme ${params.scheme}`);

  // Check if xcodemake is enabled and available
  const isXcodemakeEnabledFlag = isXcodemakeEnabled();
  let xcodemakeAvailableFlag = false;

  if (isXcodemakeEnabledFlag && buildAction === 'build') {
    xcodemakeAvailableFlag = await isXcodemakeAvailable();

    if (xcodemakeAvailableFlag && preferXcodebuild) {
      log(
        'info',
        'xcodemake is enabled but preferXcodebuild is set to true. Falling back to xcodebuild.',
      );
      buildMessages.push({
        type: 'text',
        text: '⚠️ incremental build support is enabled but preferXcodebuild is set to true. Falling back to xcodebuild.',
      });
    } else if (!xcodemakeAvailableFlag) {
      buildMessages.push({
        type: 'text',
        text: '⚠️ xcodemake is enabled but not available. Falling back to xcodebuild.',
      });
      log('info', 'xcodemake is enabled but not available. Falling back to xcodebuild.');
    } else {
      log('info', 'xcodemake is enabled and available, using it for incremental builds.');
      buildMessages.push({
        type: 'text',
        text: 'ℹ️ xcodemake is enabled and available, using it for incremental builds.',
      });
    }
  }

  try {
    const command = ['xcodebuild'];

    let projectDir = '';
    if (params.workspacePath) {
      projectDir = path.dirname(params.workspacePath);
      command.push('-workspace', params.workspacePath);
    } else if (params.projectPath) {
      projectDir = path.dirname(params.projectPath);
      command.push('-project', params.projectPath);
    }

    command.push('-scheme', params.scheme);
    command.push('-configuration', params.configuration);
    command.push('-skipMacroValidation');

    // Construct destination string based on platform
    let destinationString: string;
    const isSimulatorPlatform = [
      XcodePlatform.iOSSimulator,
      XcodePlatform.watchOSSimulator,
      XcodePlatform.tvOSSimulator,
      XcodePlatform.visionOSSimulator,
    ].includes(platformOptions.platform);

    if (isSimulatorPlatform) {
      if (platformOptions.simulatorId) {
        destinationString = constructDestinationString(
          platformOptions.platform,
          undefined,
          platformOptions.simulatorId,
        );
      } else if (platformOptions.simulatorName) {
        destinationString = constructDestinationString(
          platformOptions.platform,
          platformOptions.simulatorName,
          undefined,
          platformOptions.useLatestOS,
        );
      } else {
        return createTextResponse(
          `For ${platformOptions.platform} platform, either simulatorId or simulatorName must be provided`,
          true,
        );
      }
    } else if (platformOptions.platform === XcodePlatform.macOS) {
      destinationString = constructDestinationString(
        platformOptions.platform,
        undefined,
        undefined,
        false,
        platformOptions.arch,
      );
    } else if (platformOptions.platform === XcodePlatform.iOS) {
      if (platformOptions.deviceId) {
        destinationString = `platform=iOS,id=${platformOptions.deviceId}`;
      } else {
        destinationString = 'generic/platform=iOS';
      }
    } else if (platformOptions.platform === XcodePlatform.watchOS) {
      if (platformOptions.deviceId) {
        destinationString = `platform=watchOS,id=${platformOptions.deviceId}`;
      } else {
        destinationString = 'generic/platform=watchOS';
      }
    } else if (platformOptions.platform === XcodePlatform.tvOS) {
      if (platformOptions.deviceId) {
        destinationString = `platform=tvOS,id=${platformOptions.deviceId}`;
      } else {
        destinationString = 'generic/platform=tvOS';
      }
    } else if (platformOptions.platform === XcodePlatform.visionOS) {
      if (platformOptions.deviceId) {
        destinationString = `platform=visionOS,id=${platformOptions.deviceId}`;
      } else {
        destinationString = 'generic/platform=visionOS';
      }
    } else {
      return createTextResponse(`Unsupported platform: ${platformOptions.platform}`, true);
    }

    command.push('-destination', destinationString);

    if (params.derivedDataPath) {
      command.push('-derivedDataPath', params.derivedDataPath);
    }

    if (params.extraArgs && params.extraArgs.length > 0) {
      command.push(...params.extraArgs);
    }

    command.push(buildAction);

    // Execute the command using xcodemake or xcodebuild
    let result;
    if (
      isXcodemakeEnabledFlag &&
      xcodemakeAvailableFlag &&
      buildAction === 'build' &&
      !preferXcodebuild
    ) {
      // Check if Makefile already exists
      const makefileExists = doesMakefileExist(projectDir);
      log('debug', 'Makefile exists: ' + makefileExists);

      // Check if Makefile log already exists
      const makeLogFileExists = doesMakeLogFileExist(projectDir, command);
      log('debug', 'Makefile log exists: ' + makeLogFileExists);

      if (makefileExists && makeLogFileExists) {
        // Use make for incremental builds
        buildMessages.push({
          type: 'text',
          text: 'ℹ️ Using make for incremental build',
        });
        result = await executeMakeCommand(projectDir, platformOptions.logPrefix);
      } else {
        // Generate Makefile using xcodemake
        buildMessages.push({
          type: 'text',
          text: 'ℹ️ Generating Makefile with xcodemake (first build may take longer)',
        });
        // Remove 'xcodebuild' from the command array before passing to executeXcodemakeCommand
        result = await executeXcodemakeCommand(
          projectDir,
          command.slice(1),
          platformOptions.logPrefix,
        );
      }
    } else {
      // Use standard xcodebuild
      result = await executor(command, platformOptions.logPrefix, true, execOpts);
    }

    // Grep warnings and errors from stdout (build output)
    const warningOrErrorLines = grepWarningsAndErrors(result.output);
    warningOrErrorLines.forEach(({ type, content }) => {
      buildMessages.push({
        type: 'text',
        text: type === 'warning' ? `⚠️ Warning: ${content}` : `❌ Error: ${content}`,
      });
    });

    // Include all stderr lines as errors
    if (result.error) {
      result.error.split('\n').forEach((content) => {
        if (content.trim()) {
          buildMessages.push({ type: 'text', text: `❌ [stderr] ${content}` });
        }
      });
    }

    if (!result.success) {
      const isMcpError = result.exitCode === 64;

      log(
        isMcpError ? 'error' : 'warning',
        `${platformOptions.logPrefix} ${buildAction} failed: ${result.error}`,
        { sentry: isMcpError },
      );
      const errorResponse = createTextResponse(
        `❌ ${platformOptions.logPrefix} ${buildAction} failed for scheme ${params.scheme}.`,
        true,
      );

      if (buildMessages.length > 0 && errorResponse.content) {
        errorResponse.content.unshift(...buildMessages);
      }

      // If using xcodemake and build failed but no compiling errors, suggest using xcodebuild
      if (
        warningOrErrorLines.length == 0 &&
        isXcodemakeEnabledFlag &&
        xcodemakeAvailableFlag &&
        buildAction === 'build' &&
        !preferXcodebuild
      ) {
        errorResponse.content.push({
          type: 'text',
          text: `💡 Incremental build using xcodemake failed, suggest using preferXcodebuild option to try build again using slower xcodebuild command.`,
        });
      }

      return consolidateContentForClaudeCode(errorResponse);
    }

    log('info', `✅ ${platformOptions.logPrefix} ${buildAction} succeeded.`);

    // Create additional info based on platform and action
    let additionalInfo = '';

    // Add xcodemake info if relevant
    if (
      isXcodemakeEnabledFlag &&
      xcodemakeAvailableFlag &&
      buildAction === 'build' &&
      !preferXcodebuild
    ) {
      additionalInfo += `xcodemake: Using faster incremental builds with xcodemake. 
Future builds will use the generated Makefile for improved performance.

`;
    }

    // Only show next steps for 'build' action
    if (buildAction === 'build') {
      if (platformOptions.platform === XcodePlatform.macOS) {
        additionalInfo = `Next Steps:
1. Get app path: get_mac_app_path({ scheme: '${params.scheme}' })
2. Get bundle ID: get_mac_bundle_id({ appPath: 'PATH_FROM_STEP_1' })
3. Launch: launch_mac_app({ appPath: 'PATH_FROM_STEP_1' })`;
      } else if (platformOptions.platform === XcodePlatform.iOS) {
        additionalInfo = `Next Steps:
1. Get app path: get_device_app_path({ scheme: '${params.scheme}' })
2. Get bundle ID: get_app_bundle_id({ appPath: 'PATH_FROM_STEP_1' })
3. Launch: launch_app_device({ bundleId: 'BUNDLE_ID_FROM_STEP_2' })`;
      } else if (isSimulatorPlatform) {
        const simIdParam = platformOptions.simulatorId ? 'simulatorId' : 'simulatorName';
        const simIdValue = platformOptions.simulatorId ?? platformOptions.simulatorName;

        additionalInfo = `Next Steps:
1. Get app path: get_sim_app_path({ ${simIdParam}: '${simIdValue}', scheme: '${params.scheme}', platform: 'iOS Simulator' })
2. Get bundle ID: get_app_bundle_id({ appPath: 'PATH_FROM_STEP_1' })
3. Launch: launch_app_sim({ ${simIdParam}: '${simIdValue}', bundleId: 'BUNDLE_ID_FROM_STEP_2' })
   Or with logs: launch_app_logs_sim({ ${simIdParam}: '${simIdValue}', bundleId: 'BUNDLE_ID_FROM_STEP_2' })`;
      }
    }

    const successResponse: ToolResponse = {
      content: [
        ...buildMessages,
        {
          type: 'text',
          text: `✅ ${platformOptions.logPrefix} ${buildAction} succeeded for scheme ${params.scheme}.`,
        },
      ],
    };

    // Only add additional info if we have any
    if (additionalInfo) {
      successResponse.content.push({
        type: 'text',
        text: additionalInfo,
      });
    }

    return consolidateContentForClaudeCode(successResponse);
  } catch (error) {
    const errorMessage = error instanceof Error ? error.message : String(error);

    const isSpawnError =
      error instanceof Error &&
      'code' in error &&
      ['ENOENT', 'EACCES', 'EPERM'].includes((error as NodeJS.ErrnoException).code ?? '');

    log('error', `Error during ${platformOptions.logPrefix} ${buildAction}: ${errorMessage}`, {
      sentry: !isSpawnError,
    });

    return consolidateContentForClaudeCode(
      createTextResponse(
        `Error during ${platformOptions.logPrefix} ${buildAction}: ${errorMessage}`,
        true,
      ),
    );
  }
}

```

--------------------------------------------------------------------------------
/src/mcp/tools/swift-package/__tests__/swift_package_list.test.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Tests for swift_package_list plugin
 * Following CLAUDE.md testing standards with literal validation
 * Using pure dependency injection for deterministic testing
 */

import { describe, it, expect, beforeEach } from 'vitest';
import swiftPackageList, { swift_package_listLogic } from '../swift_package_list.ts';

describe('swift_package_list plugin', () => {
  // No mocks to clear with pure dependency injection

  describe('Export Field Validation (Literal)', () => {
    it('should have correct name', () => {
      expect(swiftPackageList.name).toBe('swift_package_list');
    });

    it('should have correct description', () => {
      expect(swiftPackageList.description).toBe('Lists currently running Swift Package processes');
    });

    it('should have handler function', () => {
      expect(typeof swiftPackageList.handler).toBe('function');
    });

    it('should validate schema correctly', () => {
      // The schema is an empty object, so any input should be valid
      expect(typeof swiftPackageList.schema).toBe('object');
      expect(Object.keys(swiftPackageList.schema)).toEqual([]);
    });
  });

  describe('Handler Behavior (Complete Literal Returns)', () => {
    it('should return empty list when no processes are running', async () => {
      // Create empty mock process map
      const mockProcessMap = new Map();

      // Use pure dependency injection with stub functions
      const mockArrayFrom = () => [];
      const mockDateNow = () => Date.now();

      const result = await swift_package_listLogic(
        {},
        {
          processMap: mockProcessMap,
          arrayFrom: mockArrayFrom,
          dateNow: mockDateNow,
        },
      );

      expect(result).toEqual({
        content: [
          { type: 'text', text: 'ℹ️ No Swift Package processes currently running.' },
          { type: 'text', text: '💡 Use swift_package_run to start an executable.' },
        ],
      });
    });

    it('should handle empty args object', async () => {
      // Create empty mock process map
      const mockProcessMap = new Map();

      // Use pure dependency injection with stub functions
      const mockArrayFrom = () => [];
      const mockDateNow = () => Date.now();

      const result = await swift_package_listLogic(
        {},
        {
          processMap: mockProcessMap,
          arrayFrom: mockArrayFrom,
          dateNow: mockDateNow,
        },
      );

      expect(result).toEqual({
        content: [
          { type: 'text', text: 'ℹ️ No Swift Package processes currently running.' },
          { type: 'text', text: '💡 Use swift_package_run to start an executable.' },
        ],
      });
    });

    it('should handle null args', async () => {
      // Create empty mock process map
      const mockProcessMap = new Map();

      // Use pure dependency injection with stub functions
      const mockArrayFrom = () => [];
      const mockDateNow = () => Date.now();

      const result = await swift_package_listLogic(null, {
        processMap: mockProcessMap,
        arrayFrom: mockArrayFrom,
        dateNow: mockDateNow,
      });

      expect(result).toEqual({
        content: [
          { type: 'text', text: 'ℹ️ No Swift Package processes currently running.' },
          { type: 'text', text: '💡 Use swift_package_run to start an executable.' },
        ],
      });
    });

    it('should handle undefined args', async () => {
      // Create empty mock process map
      const mockProcessMap = new Map();

      // Use pure dependency injection with stub functions
      const mockArrayFrom = () => [];
      const mockDateNow = () => Date.now();

      const result = await swift_package_listLogic(undefined, {
        processMap: mockProcessMap,
        arrayFrom: mockArrayFrom,
        dateNow: mockDateNow,
      });

      expect(result).toEqual({
        content: [
          { type: 'text', text: 'ℹ️ No Swift Package processes currently running.' },
          { type: 'text', text: '💡 Use swift_package_run to start an executable.' },
        ],
      });
    });

    it('should handle args with extra properties', async () => {
      // Create empty mock process map
      const mockProcessMap = new Map();

      // Use pure dependency injection with stub functions
      const mockArrayFrom = () => [];
      const mockDateNow = () => Date.now();

      const result = await swift_package_listLogic(
        {
          extraProperty: 'value',
          anotherProperty: 123,
        },
        {
          processMap: mockProcessMap,
          arrayFrom: mockArrayFrom,
          dateNow: mockDateNow,
        },
      );

      expect(result).toEqual({
        content: [
          { type: 'text', text: 'ℹ️ No Swift Package processes currently running.' },
          { type: 'text', text: '💡 Use swift_package_run to start an executable.' },
        ],
      });
    });

    it('should return single process when one process is running', async () => {
      const startedAt = new Date('2023-01-01T10:00:00.000Z');
      const mockProcess = {
        executableName: 'MyApp',
        packagePath: '/test/package',
        startedAt: startedAt,
      };

      // Create mock process map with one process
      const mockProcessMap = new Map([[12345, mockProcess]]);

      // Use pure dependency injection with stub functions
      const mockArrayFrom = (mapEntries: any) => Array.from(mapEntries);
      const mockDateNow = () => startedAt.getTime() + 5000; // 5 seconds after start

      const result = await swift_package_listLogic(
        {},
        {
          processMap: mockProcessMap,
          arrayFrom: mockArrayFrom,
          dateNow: mockDateNow,
        },
      );

      expect(result).toEqual({
        content: [
          { type: 'text', text: '📋 Active Swift Package processes (1):' },
          { type: 'text', text: '  • PID 12345: MyApp (/test/package) - running 5s' },
          { type: 'text', text: '💡 Use swift_package_stop with a PID to terminate a process.' },
        ],
      });
    });

    it('should return multiple processes when several are running', async () => {
      const startedAt1 = new Date('2023-01-01T10:00:00.000Z');
      const startedAt2 = new Date('2023-01-01T10:00:07.000Z');

      const mockProcess1 = {
        executableName: 'MyApp',
        packagePath: '/test/package1',
        startedAt: startedAt1,
      };

      const mockProcess2 = {
        executableName: undefined, // Test default executable name
        packagePath: '/test/package2',
        startedAt: startedAt2,
      };

      // Create mock process map with multiple processes
      const mockProcessMap = new Map([
        [12345, mockProcess1],
        [12346, mockProcess2],
      ]);

      // Use pure dependency injection with stub functions
      const mockArrayFrom = (mapEntries: any) => Array.from(mapEntries);
      const mockDateNow = () => startedAt1.getTime() + 10000; // 10 seconds after first start

      const result = await swift_package_listLogic(
        {},
        {
          processMap: mockProcessMap,
          arrayFrom: mockArrayFrom,
          dateNow: mockDateNow,
        },
      );

      expect(result).toEqual({
        content: [
          { type: 'text', text: '📋 Active Swift Package processes (2):' },
          { type: 'text', text: '  • PID 12345: MyApp (/test/package1) - running 10s' },
          { type: 'text', text: '  • PID 12346: default (/test/package2) - running 3s' },
          { type: 'text', text: '💡 Use swift_package_stop with a PID to terminate a process.' },
        ],
      });
    });

    it('should handle process with null executableName', async () => {
      const startedAt = new Date('2023-01-01T10:00:00.000Z');
      const mockProcess = {
        executableName: null, // Test null executable name
        packagePath: '/test/package',
        startedAt: startedAt,
      };

      // Create mock process map with one process
      const mockProcessMap = new Map([[12345, mockProcess]]);

      // Use pure dependency injection with stub functions
      const mockArrayFrom = (mapEntries: any) => Array.from(mapEntries);
      const mockDateNow = () => startedAt.getTime() + 1000; // 1 second after start

      const result = await swift_package_listLogic(
        {},
        {
          processMap: mockProcessMap,
          arrayFrom: mockArrayFrom,
          dateNow: mockDateNow,
        },
      );

      expect(result).toEqual({
        content: [
          { type: 'text', text: '📋 Active Swift Package processes (1):' },
          { type: 'text', text: '  • PID 12345: default (/test/package) - running 1s' },
          { type: 'text', text: '💡 Use swift_package_stop with a PID to terminate a process.' },
        ],
      });
    });

    it('should handle process with empty string executableName', async () => {
      const startedAt = new Date('2023-01-01T10:00:00.000Z');
      const mockProcess = {
        executableName: '', // Test empty string executable name
        packagePath: '/test/package',
        startedAt: startedAt,
      };

      // Create mock process map with one process
      const mockProcessMap = new Map([[12345, mockProcess]]);

      // Use pure dependency injection with stub functions
      const mockArrayFrom = (mapEntries: any) => Array.from(mapEntries);
      const mockDateNow = () => startedAt.getTime() + 2000; // 2 seconds after start

      const result = await swift_package_listLogic(
        {},
        {
          processMap: mockProcessMap,
          arrayFrom: mockArrayFrom,
          dateNow: mockDateNow,
        },
      );

      expect(result).toEqual({
        content: [
          { type: 'text', text: '📋 Active Swift Package processes (1):' },
          { type: 'text', text: '  • PID 12345: default (/test/package) - running 2s' },
          { type: 'text', text: '💡 Use swift_package_stop with a PID to terminate a process.' },
        ],
      });
    });

    it('should handle very recent process (less than 1 second)', async () => {
      const startedAt = new Date('2023-01-01T10:00:00.000Z');
      const mockProcess = {
        executableName: 'FastApp',
        packagePath: '/test/package',
        startedAt: startedAt,
      };

      // Create mock process map with one process
      const mockProcessMap = new Map([[12345, mockProcess]]);

      // Use pure dependency injection with stub functions
      const mockArrayFrom = (mapEntries: any) => Array.from(mapEntries);
      const mockDateNow = () => startedAt.getTime() + 500; // 500ms after start

      const result = await swift_package_listLogic(
        {},
        {
          processMap: mockProcessMap,
          arrayFrom: mockArrayFrom,
          dateNow: mockDateNow,
        },
      );

      expect(result).toEqual({
        content: [
          { type: 'text', text: '📋 Active Swift Package processes (1):' },
          { type: 'text', text: '  • PID 12345: FastApp (/test/package) - running 1s' },
          { type: 'text', text: '💡 Use swift_package_stop with a PID to terminate a process.' },
        ],
      });
    });

    it('should handle process running for exactly 0 milliseconds', async () => {
      const startedAt = new Date('2023-01-01T10:00:00.000Z');
      const mockProcess = {
        executableName: 'InstantApp',
        packagePath: '/test/package',
        startedAt: startedAt,
      };

      // Create mock process map with one process
      const mockProcessMap = new Map([[12345, mockProcess]]);

      // Use pure dependency injection with stub functions
      const mockArrayFrom = (mapEntries: any) => Array.from(mapEntries);
      const mockDateNow = () => startedAt.getTime(); // Same time as start

      const result = await swift_package_listLogic(
        {},
        {
          processMap: mockProcessMap,
          arrayFrom: mockArrayFrom,
          dateNow: mockDateNow,
        },
      );

      expect(result).toEqual({
        content: [
          { type: 'text', text: '📋 Active Swift Package processes (1):' },
          { type: 'text', text: '  • PID 12345: InstantApp (/test/package) - running 1s' },
          { type: 'text', text: '💡 Use swift_package_stop with a PID to terminate a process.' },
        ],
      });
    });

    it('should handle process running for a long time', async () => {
      const startedAt = new Date('2023-01-01T10:00:00.000Z');
      const mockProcess = {
        executableName: 'LongRunningApp',
        packagePath: '/test/package',
        startedAt: startedAt,
      };

      // Create mock process map with one process
      const mockProcessMap = new Map([[12345, mockProcess]]);

      // Use pure dependency injection with stub functions
      const mockArrayFrom = (mapEntries: any) => Array.from(mapEntries);
      const mockDateNow = () => startedAt.getTime() + 7200000; // 2 hours later

      const result = await swift_package_listLogic(
        {},
        {
          processMap: mockProcessMap,
          arrayFrom: mockArrayFrom,
          dateNow: mockDateNow,
        },
      );

      expect(result).toEqual({
        content: [
          { type: 'text', text: '📋 Active Swift Package processes (1):' },
          { type: 'text', text: '  • PID 12345: LongRunningApp (/test/package) - running 7200s' },
          { type: 'text', text: '💡 Use swift_package_stop with a PID to terminate a process.' },
        ],
      });
    });
  });
});

```

--------------------------------------------------------------------------------
/src/mcp/tools/project-scaffolding/scaffold_macos_project.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Utilities Plugin: Scaffold macOS Project
 *
 * Scaffold a new macOS project from templates.
 */

import { z } from 'zod';
import { join, dirname, basename } from 'path';
import { log } from '../../../utils/logging/index.ts';
import { ValidationError } from '../../../utils/responses/index.ts';
import { TemplateManager } from '../../../utils/template/index.ts';
import { ToolResponse } from '../../../types/common.ts';
import {
  CommandExecutor,
  getDefaultCommandExecutor,
  getDefaultFileSystemExecutor,
} from '../../../utils/command.ts';
import { FileSystemExecutor } from '../../../utils/FileSystemExecutor.ts';

// Common base schema for both iOS and macOS
const BaseScaffoldSchema = z.object({
  projectName: z.string().min(1).describe('Name of the new project'),
  outputPath: z.string().describe('Path where the project should be created'),
  bundleIdentifier: z
    .string()
    .optional()
    .describe(
      'Bundle identifier (e.g., com.example.myapp). If not provided, will use com.example.projectname',
    ),
  displayName: z
    .string()
    .optional()
    .describe(
      'App display name (shown on home screen/dock). If not provided, will use projectName',
    ),
  marketingVersion: z
    .string()
    .optional()
    .describe('Marketing version (e.g., 1.0, 2.1.3). If not provided, will use 1.0'),
  currentProjectVersion: z
    .string()
    .optional()
    .describe('Build number (e.g., 1, 42, 100). If not provided, will use 1'),
  customizeNames: z
    .boolean()
    .default(true)
    .describe('Whether to customize project names and identifiers. Default is true.'),
});

// macOS-specific schema
const ScaffoldmacOSProjectSchema = BaseScaffoldSchema.extend({
  deploymentTarget: z
    .string()
    .optional()
    .describe('macOS deployment target (e.g., 15.4, 14.0). If not provided, will use 15.4'),
});

// Use z.infer for type safety
type ScaffoldMacOSProjectParams = z.infer<typeof ScaffoldmacOSProjectSchema>;

/**
 * Update Package.swift file with deployment target
 */
function updatePackageSwiftFile(
  content: string,
  params: ScaffoldMacOSProjectParams & { platform: string },
): string {
  let result = content;

  // Update ALL target name references in Package.swift
  const featureName = `${params.projectName}Feature`;
  const testName = `${params.projectName}FeatureTests`;

  // Replace ALL occurrences of MyProjectFeatureTests first (more specific)
  result = result.replace(/MyProjectFeatureTests/g, testName);
  // Then replace ALL occurrences of MyProjectFeature (less specific, so comes after)
  result = result.replace(/MyProjectFeature/g, featureName);

  // Update deployment targets based on platform
  if (params.platform === 'macOS') {
    if (params.deploymentTarget) {
      // Extract major version (e.g., "14.0" -> "14")
      const majorVersion = params.deploymentTarget.split('.')[0];
      result = result.replace(/\.macOS\(\.v\d+\)/, `.macOS(.v${majorVersion})`);
    }
  }

  return result;
}

/**
 * Update XCConfig file with scaffold parameters
 */
function updateXCConfigFile(
  content: string,
  params: ScaffoldMacOSProjectParams & { platform: string },
): string {
  let result = content;

  // Update project identity settings
  result = result.replace(/PRODUCT_NAME = .+/g, `PRODUCT_NAME = ${params.projectName}`);
  result = result.replace(
    /PRODUCT_DISPLAY_NAME = .+/g,
    `PRODUCT_DISPLAY_NAME = ${params.displayName ?? params.projectName}`,
  );
  result = result.replace(
    /PRODUCT_BUNDLE_IDENTIFIER = .+/g,
    `PRODUCT_BUNDLE_IDENTIFIER = ${params.bundleIdentifier ?? `com.example.${params.projectName.toLowerCase().replace(/[^a-z0-9]/g, '')}`}`,
  );
  result = result.replace(
    /MARKETING_VERSION = .+/g,
    `MARKETING_VERSION = ${params.marketingVersion ?? '1.0'}`,
  );
  result = result.replace(
    /CURRENT_PROJECT_VERSION = .+/g,
    `CURRENT_PROJECT_VERSION = ${params.currentProjectVersion ?? '1'}`,
  );

  // Platform-specific updates
  if (params.platform === 'macOS') {
    // macOS deployment target
    if (params.deploymentTarget) {
      result = result.replace(
        /MACOSX_DEPLOYMENT_TARGET = .+/g,
        `MACOSX_DEPLOYMENT_TARGET = ${params.deploymentTarget}`,
      );
    }

    // Update entitlements path for macOS
    result = result.replace(
      /CODE_SIGN_ENTITLEMENTS = .+/g,
      `CODE_SIGN_ENTITLEMENTS = Config/${params.projectName}.entitlements`,
    );
  }

  // Update test bundle identifier and target name
  result = result.replace(/TEST_TARGET_NAME = .+/g, `TEST_TARGET_NAME = ${params.projectName}`);

  // Update comments that reference MyProject in entitlements paths
  result = result.replace(
    /Config\/MyProject\.entitlements/g,
    `Config/${params.projectName}.entitlements`,
  );

  return result;
}

/**
 * Replace placeholders in a string (for non-XCConfig files)
 */
function replacePlaceholders(
  content: string,
  projectName: string,
  bundleIdentifier: string,
): string {
  let result = content;

  // Replace project name
  result = result.replace(/MyProject/g, projectName);

  // Replace bundle identifier - check for both patterns used in templates
  if (bundleIdentifier) {
    result = result.replace(/com\.example\.MyProject/g, bundleIdentifier);
    result = result.replace(/com\.mycompany\.MyProject/g, bundleIdentifier);
  }

  return result;
}

/**
 * Process a single file, replacing placeholders if it's a text file
 */
async function processFile(
  sourcePath: string,
  destPath: string,
  params: ScaffoldMacOSProjectParams & { platform: string },
  fileSystemExecutor: FileSystemExecutor,
): Promise<void> {
  // Determine the destination file path
  let finalDestPath = destPath;
  if (params.customizeNames) {
    // Replace MyProject in file/directory names
    const fileName = basename(destPath);
    const dirName = dirname(destPath);
    const newFileName = fileName.replace(/MyProject/g, params.projectName);
    finalDestPath = join(dirName, newFileName);
  }

  // Text file extensions that should be processed
  const textExtensions = [
    '.swift',
    '.h',
    '.m',
    '.mm',
    '.cpp',
    '.c',
    '.pbxproj',
    '.plist',
    '.xcscheme',
    '.xctestplan',
    '.xcworkspacedata',
    '.xcconfig',
    '.json',
    '.xml',
    '.entitlements',
    '.storyboard',
    '.xib',
    '.md',
  ];

  const ext = sourcePath.toLowerCase();
  const isTextFile = textExtensions.some((textExt) => ext.endsWith(textExt));
  const isXCConfig = sourcePath.endsWith('.xcconfig');
  const isPackageSwift = sourcePath.endsWith('Package.swift');

  if (isTextFile && params.customizeNames) {
    // Read the file content
    const content = await fileSystemExecutor.readFile(sourcePath, 'utf-8');

    let processedContent;

    if (isXCConfig) {
      // Use special XCConfig processing
      processedContent = updateXCConfigFile(content, params);
    } else if (isPackageSwift) {
      // Use special Package.swift processing
      processedContent = updatePackageSwiftFile(content, params);
    } else {
      // Use standard placeholder replacement
      const bundleIdentifier =
        params.bundleIdentifier ??
        `com.example.${params.projectName.toLowerCase().replace(/[^a-z0-9]/g, '')}`;
      processedContent = replacePlaceholders(content, params.projectName, bundleIdentifier);
    }

    await fileSystemExecutor.mkdir(dirname(finalDestPath), { recursive: true });
    await fileSystemExecutor.writeFile(finalDestPath, processedContent, 'utf-8');
  } else {
    // Copy binary files as-is
    await fileSystemExecutor.mkdir(dirname(finalDestPath), { recursive: true });
    await fileSystemExecutor.cp(sourcePath, finalDestPath, { recursive: true });
  }
}

/**
 * Recursively process a directory
 */
async function processDirectory(
  sourceDir: string,
  destDir: string,
  params: ScaffoldMacOSProjectParams & { platform: string },
  fileSystemExecutor: FileSystemExecutor,
): Promise<void> {
  const entries = await fileSystemExecutor.readdir(sourceDir, { withFileTypes: true });

  for (const entry of entries) {
    const dirent = entry as { isDirectory(): boolean; isFile(): boolean; name: string };
    const sourcePath = join(sourceDir, dirent.name);
    let destName = dirent.name;

    if (params.customizeNames) {
      // Replace MyProject in directory names
      destName = destName.replace(/MyProject/g, params.projectName);
    }

    const destPath = join(destDir, destName);

    if (dirent.isDirectory()) {
      // Skip certain directories
      if (dirent.name === '.git' || dirent.name === 'xcuserdata') {
        continue;
      }
      await fileSystemExecutor.mkdir(destPath, { recursive: true });
      await processDirectory(sourcePath, destPath, params, fileSystemExecutor);
    } else if (dirent.isFile()) {
      // Skip certain files
      if (dirent.name === '.DS_Store' || dirent.name.endsWith('.xcuserstate')) {
        continue;
      }
      await processFile(sourcePath, destPath, params, fileSystemExecutor);
    }
  }
}

/**
 * Scaffold a new iOS or macOS project
 */
async function scaffoldProject(
  params: ScaffoldMacOSProjectParams & { platform: string },
  commandExecutor: CommandExecutor,
  fileSystemExecutor: FileSystemExecutor,
): Promise<string> {
  const projectName = params.projectName;
  const outputPath = params.outputPath;
  const platform = params.platform;
  const customizeNames = params.customizeNames ?? true;

  log('info', `Scaffolding project: ${projectName} (${platform}) at ${outputPath}`);

  // Validate project name
  if (!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(projectName)) {
    throw new ValidationError(
      'Project name must start with a letter and contain only letters, numbers, and underscores',
    );
  }

  // Get template path from TemplateManager
  let templatePath;
  try {
    templatePath = await TemplateManager.getTemplatePath(
      platform as 'macOS' | 'iOS',
      commandExecutor,
      fileSystemExecutor,
    );
  } catch (error) {
    throw new ValidationError(
      `Failed to get template for ${platform}: ${error instanceof Error ? error.message : String(error)}`,
    );
  }

  // Use outputPath directly as the destination
  const projectPath = outputPath;

  // Check if the output directory already has Xcode project files
  const xcworkspaceExists = fileSystemExecutor.existsSync(
    join(projectPath, `${customizeNames ? projectName : 'MyProject'}.xcworkspace`),
  );
  const xcodeprojExists = fileSystemExecutor.existsSync(
    join(projectPath, `${customizeNames ? projectName : 'MyProject'}.xcodeproj`),
  );

  if (xcworkspaceExists || xcodeprojExists) {
    throw new ValidationError(`Xcode project files already exist in ${projectPath}`);
  }

  try {
    // Process the template directly into the output path
    await processDirectory(templatePath, projectPath, params, fileSystemExecutor);

    return projectPath;
  } finally {
    // Clean up downloaded template if needed
    await TemplateManager.cleanup(templatePath, fileSystemExecutor);
  }
}

/**
 * Business logic for scaffolding macOS projects
 * Extracted for testability and Separation of Concerns
 */
export async function scaffold_macos_projectLogic(
  params: ScaffoldMacOSProjectParams,
  commandExecutor: CommandExecutor,
  fileSystemExecutor: FileSystemExecutor = getDefaultFileSystemExecutor(),
): Promise<ToolResponse> {
  try {
    const projectParams = { ...params, platform: 'macOS' as const };
    const projectPath = await scaffoldProject(projectParams, commandExecutor, fileSystemExecutor);

    const response = {
      success: true,
      projectPath,
      platform: 'macOS',
      message: `Successfully scaffolded macOS project "${params.projectName}" in ${projectPath}`,
      nextSteps: [
        `Important: Before working on the project make sure to read the README.md file in the workspace root directory.`,
        `Build for macOS: build_macos({ workspacePath: "${projectPath}/${params.customizeNames ? params.projectName : 'MyProject'}.xcworkspace", scheme: "${params.customizeNames ? params.projectName : 'MyProject'}" })`,
        `Build & Run on macOS: build_run_macos({ workspacePath: "${projectPath}/${params.customizeNames ? params.projectName : 'MyProject'}.xcworkspace", scheme: "${params.customizeNames ? params.projectName : 'MyProject'}" })`,
      ],
    };

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(response, null, 2),
        },
      ],
    };
  } catch (error) {
    log(
      'error',
      `Failed to scaffold macOS project: ${error instanceof Error ? error.message : String(error)}`,
    );

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(
            {
              success: false,
              error: error instanceof Error ? error.message : 'Unknown error occurred',
            },
            null,
            2,
          ),
        },
      ],
      isError: true,
    };
  }
}

export default {
  name: 'scaffold_macos_project',
  description:
    'Scaffold a new macOS project from templates. Creates a modern Xcode project with workspace structure, SPM package for features, and proper macOS configuration.',
  schema: ScaffoldmacOSProjectSchema.shape,
  async handler(args: Record<string, unknown>): Promise<ToolResponse> {
    // Validate the arguments against the schema before processing
    const validatedArgs = ScaffoldmacOSProjectSchema.parse(args);
    return scaffold_macos_projectLogic(
      validatedArgs,
      getDefaultCommandExecutor(),
      getDefaultFileSystemExecutor(),
    );
  },
};

```

--------------------------------------------------------------------------------
/docs/NODEJS_2025.md:
--------------------------------------------------------------------------------

```markdown
# Modern Node.js Development Guide

This guide provides actionable instructions for AI agents to apply modern Node.js patterns when the scenarios are applicable. Use these patterns when creating or modifying Node.js code that fits these use cases.

## Core Principles

**WHEN APPLICABLE** apply these modern patterns:

1. **Use ES Modules** with `node:` prefix for built-in modules
2. **Leverage built-in APIs** over external dependencies when the functionality matches
3. **Use top-level await** instead of IIFE patterns when initialization is needed
4. **Implement structured error handling** with proper context when handling application errors
5. **Use built-in testing** over external test frameworks when adding tests
6. **Apply modern async patterns** for better performance when dealing with async operations

## 1. Module System Patterns

### WHEN USING MODULES: ES Modules with node: Prefix

**✅ DO THIS:**
```javascript
// Use ES modules with node: prefix for built-ins
import { readFile } from 'node:fs/promises';
import { createServer } from 'node:http';
import { EventEmitter } from 'node:events';

export function myFunction() {
  return 'modern code';
}
```

**❌ AVOID:**
```javascript
// Don't use CommonJS or bare imports for built-ins
const fs = require('fs');
const { readFile } = require('fs/promises');
import { readFile } from 'fs/promises'; // Missing node: prefix
```

### WHEN INITIALIZING: Top-Level Await

**✅ DO THIS:**
```javascript
// Use top-level await for initialization
import { readFile } from 'node:fs/promises';

const config = JSON.parse(await readFile('config.json', 'utf8'));
const server = createServer(/* ... */);

console.log('App started with config:', config.appName);
```

**❌ AVOID:**
```javascript
// Don't wrap in IIFE
(async () => {
  const config = JSON.parse(await readFile('config.json', 'utf8'));
  // ...
})();
```

### WHEN USING ES MODULES: Package.json Settings

**✅ ENSURE package.json includes:**
```json
{
  "type": "module",
  "engines": {
    "node": ">=20.0.0"
  }
}
```

## 2. HTTP and Network Patterns

### WHEN MAKING HTTP REQUESTS: Use Built-in fetch

**✅ DO THIS:**
```javascript
// Use built-in fetch with AbortSignal.timeout
async function fetchData(url) {
  try {
    const response = await fetch(url, {
      signal: AbortSignal.timeout(5000)
    });

    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }

    return await response.json();
  } catch (error) {
    if (error.name === 'TimeoutError') {
      throw new Error('Request timed out');
    }
    throw error;
  }
}
```

**❌ AVOID:**
```javascript
// Don't add axios, node-fetch, or similar dependencies
const axios = require('axios');
const response = await axios.get(url);
```

### WHEN NEEDING CANCELLATION: AbortController Pattern

**✅ DO THIS:**
```javascript
// Implement proper cancellation
const controller = new AbortController();
setTimeout(() => controller.abort(), 10000);

try {
  const data = await fetch(url, { signal: controller.signal });
  console.log('Data received:', data);
} catch (error) {
  if (error.name === 'AbortError') {
    console.log('Request was cancelled');
  } else {
    console.error('Unexpected error:', error);
  }
}
```

## 3. Testing Patterns

### WHEN ADDING TESTS: Use Built-in Test Runner

**✅ DO THIS:**
```javascript
// Use node:test instead of external frameworks
import { test, describe } from 'node:test';
import assert from 'node:assert';

describe('My Module', () => {
  test('should work correctly', () => {
    assert.strictEqual(myFunction(), 'expected');
  });

  test('should handle async operations', async () => {
    const result = await myAsyncFunction();
    assert.strictEqual(result, 'expected');
  });

  test('should throw on invalid input', () => {
    assert.throws(() => myFunction('invalid'), /Expected error/);
  });
});
```

**✅ RECOMMENDED package.json scripts:**
```json
{
  "scripts": {
    "test": "node --test",
    "test:watch": "node --test --watch",
    "test:coverage": "node --test --experimental-test-coverage"
  }
}
```

**❌ AVOID:**
```javascript
// Don't add Jest, Mocha, or other test frameworks unless specifically required
```

## 4. Async Pattern Recommendations

### WHEN HANDLING MULTIPLE ASYNC OPERATIONS: Parallel Execution with Promise.all

**✅ DO THIS:**
```javascript
// Execute independent operations in parallel
async function processData() {
  try {
    const [config, userData] = await Promise.all([
      readFile('config.json', 'utf8'),
      fetch('/api/user').then(r => r.json())
    ]);

    const processed = processUserData(userData, JSON.parse(config));
    await writeFile('output.json', JSON.stringify(processed, null, 2));

    return processed;
  } catch (error) {
    console.error('Processing failed:', {
      error: error.message,
      stack: error.stack,
      timestamp: new Date().toISOString()
    });
    throw error;
  }
}
```

### WHEN PROCESSING EVENT STREAMS: AsyncIterators Pattern

**✅ DO THIS:**
```javascript
// Use async iterators for event processing
import { EventEmitter } from 'node:events';

class DataProcessor extends EventEmitter {
  async *processStream() {
    for (let i = 0; i < 10; i++) {
      this.emit('data', `chunk-${i}`);
      yield `processed-${i}`;
      await new Promise(resolve => setTimeout(resolve, 100));
    }
    this.emit('end');
  }
}

// Consume with for-await-of
const processor = new DataProcessor();
for await (const result of processor.processStream()) {
  console.log('Processed:', result);
}
```

## 5. Stream Processing Patterns

### WHEN PROCESSING STREAMS: Use pipeline with Promises

**✅ DO THIS:**
```javascript
import { pipeline } from 'node:stream/promises';
import { createReadStream, createWriteStream } from 'node:fs';
import { Transform } from 'node:stream';

// Always use pipeline for stream processing
async function processFile(inputFile, outputFile) {
  try {
    await pipeline(
      createReadStream(inputFile),
      new Transform({
        transform(chunk, encoding, callback) {
          this.push(chunk.toString().toUpperCase());
          callback();
        }
      }),
      createWriteStream(outputFile)
    );
    console.log('File processed successfully');
  } catch (error) {
    console.error('Pipeline failed:', error);
    throw error;
  }
}
```

### WHEN NEEDING BROWSER COMPATIBILITY: Web Streams

**✅ DO THIS:**
```javascript
import { Readable } from 'node:stream';

// Convert between Web Streams and Node streams when needed
const webReadable = new ReadableStream({
  start(controller) {
    controller.enqueue('Hello ');
    controller.enqueue('World!');
    controller.close();
  }
});

const nodeStream = Readable.fromWeb(webReadable);
```

## 6. CPU-Intensive Task Patterns

### WHEN DOING HEAVY COMPUTATION: Worker Threads

**✅ DO THIS:**
```javascript
// worker.js - Separate file for CPU-intensive tasks
import { parentPort, workerData } from 'node:worker_threads';

function heavyComputation(data) {
  // CPU-intensive work here
  return processedData;
}

const result = heavyComputation(workerData);
parentPort.postMessage(result);
```

```javascript
// main.js - Delegate to worker
import { Worker } from 'node:worker_threads';
import { fileURLToPath } from 'node:url';

async function processHeavyTask(data) {
  return new Promise((resolve, reject) => {
    const worker = new Worker(
      fileURLToPath(new URL('./worker.js', import.meta.url)),
      { workerData: data }
    );

    worker.on('message', resolve);
    worker.on('error', reject);
    worker.on('exit', (code) => {
      if (code !== 0) {
        reject(new Error(`Worker stopped with exit code ${code}`));
      }
    });
  });
}
```

## 7. Development Configuration Patterns

### FOR NEW PROJECTS: Modern package.json

**✅ RECOMMENDED for new projects:**
```json
{
  "name": "modern-node-app",
  "type": "module",
  "engines": {
    "node": ">=20.0.0"
  },
  "scripts": {
    "dev": "node --watch --env-file=.env app.js",
    "test": "node --test --watch",
    "start": "node app.js"
  }
}
```

### WHEN LOADING ENVIRONMENT VARIABLES: Built-in Support

**✅ DO THIS:**
```javascript
// Use --env-file flag instead of dotenv package
// Environment variables are automatically available
console.log('Database URL:', process.env.DATABASE_URL);
console.log('API Key loaded:', process.env.API_KEY ? 'Yes' : 'No');
```

**❌ AVOID:**
```javascript
// Don't add dotenv dependency
require('dotenv').config();
```

## 8. Error Handling Patterns

### WHEN CREATING CUSTOM ERRORS: Structured Error Classes

**✅ DO THIS:**
```javascript
class AppError extends Error {
  constructor(message, code, statusCode = 500, context = {}) {
    super(message);
    this.name = 'AppError';
    this.code = code;
    this.statusCode = statusCode;
    this.context = context;
    this.timestamp = new Date().toISOString();
  }

  toJSON() {
    return {
      name: this.name,
      message: this.message,
      code: this.code,
      statusCode: this.statusCode,
      context: this.context,
      timestamp: this.timestamp,
      stack: this.stack
    };
  }
}

// Usage with rich context
throw new AppError(
  'Database connection failed',
  'DB_CONNECTION_ERROR',
  503,
  { host: 'localhost', port: 5432, retryAttempt: 3 }
);
```

## 9. Performance Monitoring Patterns

### WHEN MONITORING PERFORMANCE: Built-in Performance APIs

**✅ DO THIS:**
```javascript
import { PerformanceObserver, performance } from 'node:perf_hooks';

// Set up performance monitoring
const obs = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (entry.duration > 100) {
      console.log(`Slow operation: ${entry.name} took ${entry.duration}ms`);
    }
  }
});
obs.observe({ entryTypes: ['function', 'http', 'dns'] });

// Instrument operations
async function processLargeDataset(data) {
  performance.mark('processing-start');
  
  const result = await heavyProcessing(data);
  
  performance.mark('processing-end');
  performance.measure('data-processing', 'processing-start', 'processing-end');
  
  return result;
}
```

## 10. Module Organization Patterns

### WHEN ORGANIZING INTERNAL MODULES: Import Maps

**✅ DO THIS in package.json:**
```json
{
  "imports": {
    "#config": "./src/config/index.js",
    "#utils/*": "./src/utils/*.js",
    "#db": "./src/database/connection.js"
  }
}
```

**✅ Use in code:**
```javascript
// Clean internal imports
import config from '#config';
import { logger, validator } from '#utils/common';
import db from '#db';
```

### WHEN LOADING CONDITIONALLY: Dynamic Imports

**✅ DO THIS:**
```javascript
// Load features based on environment
async function loadDatabaseAdapter() {
  const dbType = process.env.DATABASE_TYPE || 'sqlite';
  
  try {
    const adapter = await import(`#db/adapters/${dbType}`);
    return adapter.default;
  } catch (error) {
    console.warn(`Database adapter ${dbType} not available, falling back to sqlite`);
    const fallback = await import('#db/adapters/sqlite');
    return fallback.default;
  }
}
```

## 11. Diagnostic Patterns

### WHEN ADDING OBSERVABILITY: Diagnostic Channels

**✅ DO THIS:**
```javascript
import diagnostics_channel from 'node:diagnostics_channel';

// Create diagnostic channels
const dbChannel = diagnostics_channel.channel('app:database');

// Subscribe to events
dbChannel.subscribe((message) => {
  console.log('Database operation:', {
    operation: message.operation,
    duration: message.duration,
    query: message.query
  });
});

// Publish diagnostic information
async function queryDatabase(sql, params) {
  const start = performance.now();
  
  try {
    const result = await db.query(sql, params);
    
    dbChannel.publish({
      operation: 'query',
      sql,
      params,
      duration: performance.now() - start,
      success: true
    });
    
    return result;
  } catch (error) {
    dbChannel.publish({
      operation: 'query',
      sql,
      params,
      duration: performance.now() - start,
      success: false,
      error: error.message
    });
    throw error;
  }
}
```

## Modernization Checklist

When working with Node.js code, consider applying these patterns where applicable:

- [ ] `"type": "module"` in package.json
- [ ] `"engines": {"node": ">=20.0.0"}` specified
- [ ] All built-in imports use `node:` prefix
- [ ] Using `fetch()` instead of HTTP libraries
- [ ] Using `node --test` instead of external test frameworks
- [ ] Using `--watch` and `--env-file` flags
- [ ] Implementing structured error handling
- [ ] Using `Promise.all()` for parallel operations
- [ ] Using `pipeline()` for stream processing
- [ ] Implementing performance monitoring where appropriate
- [ ] Using worker threads for CPU-intensive tasks
- [ ] Using import maps for internal modules

## Dependencies to Remove

When modernizing, remove these dependencies if present:

- `axios`, `node-fetch`, `got` → Use built-in `fetch()`
- `jest`, `mocha`, `ava` → Use `node:test`
- `nodemon` → Use `node --watch`
- `dotenv` → Use `--env-file`
- `cross-env` → Use native environment handling

## Security Patterns

**WHEN SECURITY IS A CONCERN** apply these practices:

```bash
# Use permission model for enhanced security
node --experimental-permission --allow-fs-read=./data --allow-fs-write=./logs app.js

# Network restrictions
node --experimental-permission --allow-net=api.example.com app.js
```

This guide provides modern Node.js patterns to apply when the specific scenarios are encountered, ensuring code follows 2025 best practices for performance, security, and maintainability without forcing unnecessary changes.
```

--------------------------------------------------------------------------------
/src/mcp/tools/ui-testing/__tests__/type_text.test.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Tests for type_text plugin
 */

import { describe, it, expect } from 'vitest';
import { z } from 'zod';
import {
  createMockExecutor,
  createMockFileSystemExecutor,
  createNoopExecutor,
} from '../../../../test-utils/mock-executors.ts';
import typeTextPlugin, { type_textLogic } from '../type_text.ts';

// Mock axe helpers for dependency injection
function createMockAxeHelpers(
  overrides: {
    getAxePathReturn?: string | null;
    getBundledAxeEnvironmentReturn?: Record<string, string>;
  } = {},
) {
  return {
    getAxePath: () => {
      return Object.prototype.hasOwnProperty.call(overrides, 'getAxePathReturn')
        ? overrides.getAxePathReturn
        : '/usr/local/bin/axe';
    },
    getBundledAxeEnvironment: () => overrides.getBundledAxeEnvironmentReturn ?? {},
  };
}

// Mock executor that tracks rejections for testing
function createRejectingExecutor(error: any) {
  return async () => {
    throw error;
  };
}

describe('Type Text Plugin', () => {
  describe('Export Field Validation (Literal)', () => {
    it('should have correct name', () => {
      expect(typeTextPlugin.name).toBe('type_text');
    });

    it('should have correct description', () => {
      expect(typeTextPlugin.description).toBe(
        'Type text (supports US keyboard characters). Use describe_ui to find text field, tap to focus, then type.',
      );
    });

    it('should have handler function', () => {
      expect(typeof typeTextPlugin.handler).toBe('function');
    });

    it('should validate schema fields with safeParse', () => {
      const schema = z.object(typeTextPlugin.schema);

      // Valid case
      expect(
        schema.safeParse({
          simulatorUuid: '12345678-1234-1234-1234-123456789012',
          text: 'Hello World',
        }).success,
      ).toBe(true);

      // Invalid simulatorUuid
      expect(
        schema.safeParse({
          simulatorUuid: 'invalid-uuid',
          text: 'Hello World',
        }).success,
      ).toBe(false);

      // Invalid text - empty string
      expect(
        schema.safeParse({
          simulatorUuid: '12345678-1234-1234-1234-123456789012',
          text: '',
        }).success,
      ).toBe(false);

      // Invalid text - non-string
      expect(
        schema.safeParse({
          simulatorUuid: '12345678-1234-1234-1234-123456789012',
          text: 123,
        }).success,
      ).toBe(false);

      // Missing required fields
      expect(schema.safeParse({}).success).toBe(false);
    });
  });

  describe('Command Generation', () => {
    it('should generate correct axe command for basic text typing', async () => {
      let capturedCommand: string[] = [];
      const trackingExecutor = async (command: string[]) => {
        capturedCommand = command;
        return {
          success: true,
          output: 'Text typed successfully',
          error: undefined,
          process: { pid: 12345 },
        };
      };

      const mockAxeHelpers = createMockAxeHelpers({
        getAxePathReturn: '/usr/local/bin/axe',
        getBundledAxeEnvironmentReturn: {},
      });

      await type_textLogic(
        {
          simulatorUuid: '12345678-1234-1234-1234-123456789012',
          text: 'Hello World',
        },
        trackingExecutor,
        mockAxeHelpers,
      );

      expect(capturedCommand).toEqual([
        '/usr/local/bin/axe',
        'type',
        'Hello World',
        '--udid',
        '12345678-1234-1234-1234-123456789012',
      ]);
    });

    it('should generate correct axe command for text with special characters', async () => {
      let capturedCommand: string[] = [];
      const trackingExecutor = async (command: string[]) => {
        capturedCommand = command;
        return {
          success: true,
          output: 'Text typed successfully',
          error: undefined,
          process: { pid: 12345 },
        };
      };

      const mockAxeHelpers = createMockAxeHelpers({
        getAxePathReturn: '/usr/local/bin/axe',
        getBundledAxeEnvironmentReturn: {},
      });

      await type_textLogic(
        {
          simulatorUuid: '12345678-1234-1234-1234-123456789012',
          text: '[email protected]',
        },
        trackingExecutor,
        mockAxeHelpers,
      );

      expect(capturedCommand).toEqual([
        '/usr/local/bin/axe',
        'type',
        '[email protected]',
        '--udid',
        '12345678-1234-1234-1234-123456789012',
      ]);
    });

    it('should generate correct axe command for text with numbers and symbols', async () => {
      let capturedCommand: string[] = [];
      const trackingExecutor = async (command: string[]) => {
        capturedCommand = command;
        return {
          success: true,
          output: 'Text typed successfully',
          error: undefined,
          process: { pid: 12345 },
        };
      };

      const mockAxeHelpers = createMockAxeHelpers({
        getAxePathReturn: '/usr/local/bin/axe',
        getBundledAxeEnvironmentReturn: {},
      });

      await type_textLogic(
        {
          simulatorUuid: '12345678-1234-1234-1234-123456789012',
          text: 'Password123!@#',
        },
        trackingExecutor,
        mockAxeHelpers,
      );

      expect(capturedCommand).toEqual([
        '/usr/local/bin/axe',
        'type',
        'Password123!@#',
        '--udid',
        '12345678-1234-1234-1234-123456789012',
      ]);
    });

    it('should generate correct axe command for long text', async () => {
      let capturedCommand: string[] = [];
      const trackingExecutor = async (command: string[]) => {
        capturedCommand = command;
        return {
          success: true,
          output: 'Text typed successfully',
          error: undefined,
          process: { pid: 12345 },
        };
      };

      const mockAxeHelpers = createMockAxeHelpers({
        getAxePathReturn: '/usr/local/bin/axe',
        getBundledAxeEnvironmentReturn: {},
      });

      const longText =
        'This is a very long text that needs to be typed into the simulator for testing purposes.';

      await type_textLogic(
        {
          simulatorUuid: '12345678-1234-1234-1234-123456789012',
          text: longText,
        },
        trackingExecutor,
        mockAxeHelpers,
      );

      expect(capturedCommand).toEqual([
        '/usr/local/bin/axe',
        'type',
        longText,
        '--udid',
        '12345678-1234-1234-1234-123456789012',
      ]);
    });

    it('should generate correct axe command with bundled axe path', async () => {
      let capturedCommand: string[] = [];
      const trackingExecutor = async (command: string[]) => {
        capturedCommand = command;
        return {
          success: true,
          output: 'Text typed successfully',
          error: undefined,
          process: { pid: 12345 },
        };
      };

      const mockAxeHelpers = createMockAxeHelpers({
        getAxePathReturn: '/path/to/bundled/axe',
        getBundledAxeEnvironmentReturn: { AXE_PATH: '/some/path' },
      });

      await type_textLogic(
        {
          simulatorUuid: 'ABCDEF12-3456-7890-ABCD-ABCDEFABCDEF',
          text: 'Test message',
        },
        trackingExecutor,
        mockAxeHelpers,
      );

      expect(capturedCommand).toEqual([
        '/path/to/bundled/axe',
        'type',
        'Test message',
        '--udid',
        'ABCDEF12-3456-7890-ABCD-ABCDEFABCDEF',
      ]);
    });
  });

  describe('Handler Behavior (Complete Literal Returns)', () => {
    it('should handle axe dependency error', async () => {
      const mockAxeHelpers = createMockAxeHelpers({
        getAxePathReturn: null,
      });

      const result = await type_textLogic(
        {
          simulatorUuid: '12345678-1234-1234-1234-123456789012',
          text: 'Hello World',
        },
        createNoopExecutor(),
        mockAxeHelpers,
      );

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: 'Bundled axe tool not found. UI automation features are not available.\n\nThis is likely an installation issue with the npm package.\nPlease reinstall xcodebuildmcp or report this issue.',
          },
        ],
        isError: true,
      });
    });

    it('should successfully type text', async () => {
      const mockAxeHelpers = createMockAxeHelpers({
        getAxePathReturn: '/usr/local/bin/axe',
        getBundledAxeEnvironmentReturn: {},
      });
      const mockExecutor = createMockExecutor({
        success: true,
        output: 'Text typed successfully',
        error: undefined,
      });

      const result = await type_textLogic(
        {
          simulatorUuid: '12345678-1234-1234-1234-123456789012',
          text: 'Hello World',
        },
        mockExecutor,
        mockAxeHelpers,
      );

      expect(result).toEqual({
        content: [{ type: 'text', text: 'Text typing simulated successfully.' }],
        isError: false,
      });
    });

    it('should return success for valid text typing', async () => {
      const mockAxeHelpers = createMockAxeHelpers({
        getAxePathReturn: '/usr/local/bin/axe',
        getBundledAxeEnvironmentReturn: {},
      });

      const mockExecutor = createMockExecutor({
        success: true,
        output: 'Text typed successfully',
        error: undefined,
      });

      const result = await type_textLogic(
        {
          simulatorUuid: '12345678-1234-1234-1234-123456789012',
          text: 'Hello World',
        },
        mockExecutor,
        mockAxeHelpers,
      );

      expect(result).toEqual({
        content: [{ type: 'text', text: 'Text typing simulated successfully.' }],
        isError: false,
      });
    });

    it('should handle DependencyError when axe binary not found', async () => {
      const mockAxeHelpers = createMockAxeHelpers({
        getAxePathReturn: null,
      });

      const result = await type_textLogic(
        {
          simulatorUuid: '12345678-1234-1234-1234-123456789012',
          text: 'Hello World',
        },
        createNoopExecutor(),
        mockAxeHelpers,
      );

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: 'Bundled axe tool not found. UI automation features are not available.\n\nThis is likely an installation issue with the npm package.\nPlease reinstall xcodebuildmcp or report this issue.',
          },
        ],
        isError: true,
      });
    });

    it('should handle AxeError from command execution', async () => {
      const mockAxeHelpers = createMockAxeHelpers({
        getAxePathReturn: '/usr/local/bin/axe',
        getBundledAxeEnvironmentReturn: {},
      });

      const mockExecutor = createMockExecutor({
        success: false,
        output: '',
        error: 'Text field not found',
      });

      const result = await type_textLogic(
        {
          simulatorUuid: '12345678-1234-1234-1234-123456789012',
          text: 'Hello World',
        },
        mockExecutor,
        mockAxeHelpers,
      );

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: "Error: Failed to simulate text typing: axe command 'type' failed.\nDetails: Text field not found",
          },
        ],
        isError: true,
      });
    });

    it('should handle SystemError from command execution', async () => {
      const mockAxeHelpers = createMockAxeHelpers({
        getAxePathReturn: '/usr/local/bin/axe',
        getBundledAxeEnvironmentReturn: {},
      });

      const mockExecutor = createRejectingExecutor(new Error('ENOENT: no such file or directory'));

      const result = await type_textLogic(
        {
          simulatorUuid: '12345678-1234-1234-1234-123456789012',
          text: 'Hello World',
        },
        mockExecutor,
        mockAxeHelpers,
      );

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: expect.stringContaining(
              'Error: System error executing axe: Failed to execute axe command: ENOENT: no such file or directory',
            ),
          },
        ],
        isError: true,
      });
    });

    it('should handle unexpected Error objects', async () => {
      const mockAxeHelpers = createMockAxeHelpers({
        getAxePathReturn: '/usr/local/bin/axe',
        getBundledAxeEnvironmentReturn: {},
      });

      const mockExecutor = createRejectingExecutor(new Error('Unexpected error'));

      const result = await type_textLogic(
        {
          simulatorUuid: '12345678-1234-1234-1234-123456789012',
          text: 'Hello World',
        },
        mockExecutor,
        mockAxeHelpers,
      );

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: expect.stringContaining(
              'Error: System error executing axe: Failed to execute axe command: Unexpected error',
            ),
          },
        ],
        isError: true,
      });
    });

    it('should handle unexpected string errors', async () => {
      const mockAxeHelpers = createMockAxeHelpers({
        getAxePathReturn: '/usr/local/bin/axe',
        getBundledAxeEnvironmentReturn: {},
      });

      const mockExecutor = createRejectingExecutor('String error');

      const result = await type_textLogic(
        {
          simulatorUuid: '12345678-1234-1234-1234-123456789012',
          text: 'Hello World',
        },
        mockExecutor,
        mockAxeHelpers,
      );

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: 'Error: System error executing axe: Failed to execute axe command: String error',
          },
        ],
        isError: true,
      });
    });
  });
});

```

--------------------------------------------------------------------------------
/docs/TEST_RUNNER_ENV_IMPLEMENTATION_PLAN.md:
--------------------------------------------------------------------------------

```markdown
# TEST_RUNNER_ Environment Variables Implementation Plan

## Problem Statement

**GitHub Issue**: [#101 - Support TEST_RUNNER_ prefixed env vars](https://github.com/cameroncooke/XcodeBuildMCP/issues/101)

**Core Need**: Enable conditional test behavior by passing TEST_RUNNER_ prefixed environment variables from MCP client configurations to xcodebuild test processes. This addresses the specific use case of disabling `runsForEachTargetApplicationUIConfiguration` for faster development testing.

## Background Context

### xcodebuild Environment Variable Support

From the xcodebuild man page:
```
TEST_RUNNER_<VAR>   Set an environment variable whose name is prefixed
                    with TEST_RUNNER_ to have that variable passed, with
                    its prefix stripped, to all test runner processes
                    launched during a test action. For example,
                    TEST_RUNNER_Foo=Bar xcodebuild test ... sets the
                    environment variable Foo=Bar in the test runner's
                    environment.
```

### User Requirements

Users want to configure their MCP server with TEST_RUNNER_ prefixed environment variables:

```json
{
  "mcpServers": {
    "XcodeBuildMCP": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "xcodebuildmcp@latest"],
      "env": {
        "TEST_RUNNER_USE_DEV_MODE": "YES"
      }
    }
  }
}
```

And have tests that can conditionally execute based on these variables:

```swift
func testFoo() throws {
  let useDevMode = ProcessInfo.processInfo.environment["USE_DEV_MODE"] == "YES"
  guard useDevMode else {
    XCTFail("Test requires USE_DEV_MODE to be true")
    return
  }
  // Test logic here...
}
```

## Current Architecture Analysis

### XcodeBuildMCP Execution Flow
1. All Xcode commands flow through `executeXcodeBuildCommand()` function
2. Generic `CommandExecutor` interface handles all command execution
3. Test tools exist for device/simulator/macOS platforms
4. Zod schemas provide parameter validation and type safety

### Key Files in Current Architecture
- `src/utils/CommandExecutor.ts` - Command execution interface
- `src/utils/build-utils.ts` - Contains `executeXcodeBuildCommand`
- `src/mcp/tools/device/test_device.ts` - Device testing tool
- `src/mcp/tools/simulator/test_sim.ts` - Simulator testing tool  
- `src/mcp/tools/macos/test_macos.ts` - macOS testing tool
- `src/utils/test/index.ts` - Shared test logic for simulator

## Solution Analysis

### Design Options Considered

1. **Automatic Detection** (❌ Rejected)
   - Scan `process.env` for TEST_RUNNER_ variables and always pass them
   - **Issue**: Security risk of environment variable leakage
   - **Issue**: Unpredictable behavior based on server environment

2. **Explicit Parameter** (✅ Chosen)
   - Add `testRunnerEnv` parameter to test tools
   - Users explicitly specify which variables to pass
   - **Benefits**: Secure, predictable, well-validated

3. **Hybrid Approach** (🤔 Future Enhancement)
   - Both automatic + explicit with explicit overriding
   - **Issue**: Adds complexity, deferred for future consideration

### Expert Analysis Summary

**RepoPrompt Analysis**: Comprehensive architectural plan emphasizing security, type safety, and integration with existing patterns.

**Gemini Analysis**: Confirmed explicit approach as optimal, highlighting:
- Security benefits of explicit allow-list approach
- Architectural soundness of extending CommandExecutor
- Recommendation for automatic prefix handling for better UX

## Recommended Solution: Explicit Parameter with Automatic Prefix Handling

### Key Design Decisions

1. **Security-First**: Only explicitly provided variables are passed (no automatic process.env scanning)
2. **User Experience**: Automatic prefix handling - users provide unprefixed keys
3. **Architecture**: Extend execution layer generically for future extensibility  
4. **Validation**: Zod schema enforcement with proper type safety

### User Experience Design

**Input** (what users specify):
```json
{
  "testRunnerEnv": {
    "USE_DEV_MODE": "YES",
    "runsForEachTargetApplicationUIConfiguration": "NO"
  }
}
```

**Output** (what gets passed to xcodebuild):
```bash
TEST_RUNNER_USE_DEV_MODE=YES \
TEST_RUNNER_runsForEachTargetApplicationUIConfiguration=NO \
xcodebuild test ...
```

## Implementation Plan

### Phase 0: Test-Driven Development Setup

**Objective**: Create reproduction test to validate issue and later prove fix works

#### Tasks:
- [ ] Create test in `example_projects/iOS/MCPTest` that checks for environment variable
- [ ] Run current test tools to demonstrate limitation (test should fail)
- [ ] Document baseline behavior

**Test Code Example**:
```swift
func testEnvironmentVariablePassthrough() throws {
  let useDevMode = ProcessInfo.processInfo.environment["USE_DEV_MODE"] == "YES"
  guard useDevMode else {
    XCTFail("Test requires USE_DEV_MODE=YES via TEST_RUNNER_USE_DEV_MODE")
    return
  }
  XCTAssertTrue(true, "Environment variable successfully passed through")
}
```

### Phase 1: Core Infrastructure Updates

**Objective**: Extend CommandExecutor and build utilities to support environment variables

#### 1.1 Update CommandExecutor Interface

**File**: `src/utils/CommandExecutor.ts`

**Changes**:
- Add `CommandExecOptions` type for execution options
- Update `CommandExecutor` type signature to accept optional execution options

```typescript
export type CommandExecOptions = {
  cwd?: string;
  env?: Record<string, string | undefined>;
};

export type CommandExecutor = (
  args: string[],
  description?: string,
  quiet?: boolean,
  opts?: CommandExecOptions
) => Promise<CommandResponse>;
```

#### 1.2 Update Execution Facade

**File**: `src/utils/execution/index.ts`

**Changes**:
- Re-export `CommandExecOptions` type

```typescript
export type { CommandExecutor, CommandResponse, CommandExecOptions } from '../CommandExecutor.js';
```

#### 1.3 Update Default Command Executor

**File**: `src/utils/command.ts`

**Changes**:
- Modify `getDefaultCommandExecutor` to merge `opts.env` with `process.env` when spawning

```typescript
// In the returned function:
const env = { ...process.env, ...(opts?.env ?? {}) };
// Pass env and opts?.cwd to spawn/exec call
```

#### 1.4 Create Environment Variable Utility

**File**: `src/utils/environment.ts`

**Changes**:
- Add `normalizeTestRunnerEnv` function

```typescript
export function normalizeTestRunnerEnv(
  userVars?: Record<string, string | undefined>
): Record<string, string> {
  const result: Record<string, string> = {};
  if (userVars) {
    for (const [key, value] of Object.entries(userVars)) {
      if (value !== undefined) {
        result[`TEST_RUNNER_${key}`] = value;
      }
    }
  }
  return result;
}
```

#### 1.5 Update executeXcodeBuildCommand

**File**: `src/utils/build-utils.ts`

**Changes**:
- Add optional `execOpts?: CommandExecOptions` parameter (6th parameter)
- Pass execution options through to `CommandExecutor` calls

```typescript
export async function executeXcodeBuildCommand(
  build: { /* existing fields */ },
  runtime: { /* existing fields */ },
  preferXcodebuild = false,
  action: 'build' | 'test' | 'archive' | 'analyze' | string,
  executor: CommandExecutor = getDefaultCommandExecutor(),
  execOpts?: CommandExecOptions, // NEW
): Promise<ToolResponse>
```

### Phase 2: Test Tool Integration

**Objective**: Add `testRunnerEnv` parameter to all test tools and wire through execution

#### 2.1 Update Device Test Tool

**File**: `src/mcp/tools/device/test_device.ts`

**Changes**:
- Add `testRunnerEnv` to Zod schema with validation
- Import and use `normalizeTestRunnerEnv`
- Pass execution options to `executeXcodeBuildCommand`

**Schema Addition**:
```typescript
testRunnerEnv: z
  .record(z.string(), z.string().optional())
  .optional()
  .describe('Test runner environment variables (TEST_RUNNER_ prefix added automatically)')
```

**Usage**:
```typescript
const execEnv = normalizeTestRunnerEnv(params.testRunnerEnv);
const testResult = await executeXcodeBuildCommand(
  { /* build params */ },
  { /* runtime params */ },
  params.preferXcodebuild ?? false,
  'test',
  executor,
  { env: execEnv } // NEW
);
```

#### 2.2 Update macOS Test Tool

**File**: `src/mcp/tools/macos/test_macos.ts`

**Changes**: Same pattern as device test tool
- Schema addition for `testRunnerEnv`
- Import `normalizeTestRunnerEnv` 
- Pass execution options to `executeXcodeBuildCommand`

#### 2.3 Update Simulator Test Tool and Logic

**File**: `src/mcp/tools/simulator/test_sim.ts`

**Changes**:
- Add `testRunnerEnv` to schema
- Pass through to `handleTestLogic`

**File**: `src/utils/test/index.ts`

**Changes**:
- Update `handleTestLogic` signature to accept `testRunnerEnv?: Record<string, string | undefined>`
- Import and use `normalizeTestRunnerEnv`
- Pass execution options to `executeXcodeBuildCommand`

### Phase 3: Testing and Validation

**Objective**: Comprehensive testing coverage for new functionality

#### 3.1 Unit Tests

**File**: `src/utils/__tests__/environment.test.ts`

**Tests**:
- Test `normalizeTestRunnerEnv` with various inputs
- Verify prefix addition
- Verify undefined filtering
- Verify empty input handling

#### 3.2 Integration Tests  

**Files**: Update existing test files for test tools

**Tests**:
- Verify `testRunnerEnv` parameter is properly validated
- Verify environment variables are passed through `CommandExecutor`
- Mock executor to verify correct env object construction

#### 3.3 Tool Export Validation

**Files**: Test files in each tool directory

**Tests**:
- Verify schema exports include new `testRunnerEnv` field
- Verify parameter typing is correct

### Phase 4: End-to-End Validation

**Objective**: Prove the fix works with real xcodebuild scenarios

#### 4.1 Reproduction Test Validation

**Tasks**:
- Run reproduction test from Phase 0 with new `testRunnerEnv` parameter
- Verify test passes (proving env var was successfully passed)
- Document the before/after behavior

#### 4.2 Real-World Scenario Testing

**Tasks**:
- Test with actual iOS project using `runsForEachTargetApplicationUIConfiguration`
- Verify performance difference when variable is set
- Test with multiple environment variables
- Test edge cases (empty values, special characters)

## Security Considerations

### Security Benefits
- **No Environment Leakage**: Only explicit user-provided variables are passed
- **Command Injection Prevention**: Environment variables passed as separate object, not interpolated into command string
- **Input Validation**: Zod schemas prevent malformed inputs
- **Prefix Enforcement**: Only TEST_RUNNER_ prefixed variables can be set

### Security Best Practices
- Never log environment variable values (keys only for debugging)
- Filter out undefined values to prevent accidental exposure
- Validate all user inputs through Zod schemas
- Document supported TEST_RUNNER_ variables from Apple's documentation

## Architectural Benefits

### Clean Integration
- Extends existing `CommandExecutor` pattern generically
- Maintains backward compatibility (all existing calls remain valid)
- Follows established Zod validation patterns
- Consistent API across all test tools

### Future Extensibility  
- `CommandExecOptions` can support additional execution options (timeout, cwd, etc.)
- Pattern can be extended to other tools that need environment variables
- Generic approach allows for non-TEST_RUNNER_ use cases in the future

## File Modification Summary

### New Files
- `src/utils/__tests__/environment.test.ts` - Unit tests for environment utilities

### Modified Files
- `src/utils/CommandExecutor.ts` - Add execution options types
- `src/utils/execution/index.ts` - Re-export new types  
- `src/utils/command.ts` - Update default executor to handle env
- `src/utils/environment.ts` - Add `normalizeTestRunnerEnv` utility
- `src/utils/build-utils.ts` - Update `executeXcodeBuildCommand` signature
- `src/mcp/tools/device/test_device.ts` - Add schema and integration
- `src/mcp/tools/macos/test_macos.ts` - Add schema and integration
- `src/mcp/tools/simulator/test_sim.ts` - Add schema and pass-through
- `src/utils/test/index.ts` - Update `handleTestLogic` for simulator path
- Test files for each modified tool - Add validation tests

## Success Criteria

1. **Functionality**: Users can pass `testRunnerEnv` parameter to test tools and have variables appear in test runner environment
2. **Security**: No unintended environment variable leakage from server process
3. **Usability**: Users specify unprefixed variable names for better UX
4. **Compatibility**: All existing test tool calls continue to work unchanged
5. **Validation**: Comprehensive test coverage proves the feature works end-to-end

## Future Enhancements (Out of Scope)

1. **Configuration Profiles**: Allow users to define common TEST_RUNNER_ variable sets in config files
2. **Variable Discovery**: Help users discover available TEST_RUNNER_ variables
3. **Build Tool Support**: Extend to build tools if Apple adds similar BUILD_RUNNER_ support
4. **Performance Monitoring**: Track impact of environment variable passing on build times

## Implementation Timeline

- **Phase 0**: 1-2 hours (reproduction test setup)
- **Phase 1**: 4-6 hours (infrastructure changes)
- **Phase 2**: 3-4 hours (tool integration)
- **Phase 3**: 4-5 hours (testing)  
- **Phase 4**: 2-3 hours (validation)

**Total Estimated Time**: 14-20 hours

## Conclusion

This implementation plan provides a secure, user-friendly, and architecturally sound solution for TEST_RUNNER_ environment variable support. The explicit parameter approach with automatic prefix handling balances security concerns with user experience, while the test-driven development approach ensures we can prove the solution works as intended.

The plan leverages XcodeBuildMCP's existing patterns and provides a foundation for future environment variable needs across the tool ecosystem.
```

--------------------------------------------------------------------------------
/src/mcp/tools/ui-testing/__tests__/long_press.test.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Tests for long_press tool plugin
 */

import { describe, it, expect } from 'vitest';
import { z } from 'zod';
import { createMockExecutor } from '../../../../test-utils/mock-executors.ts';
import longPressPlugin, { long_pressLogic } from '../long_press.ts';

describe('Long Press Plugin', () => {
  // Setup for each test - no vitest mocks to clear

  describe('Export Field Validation (Literal)', () => {
    it('should have correct name', () => {
      expect(longPressPlugin.name).toBe('long_press');
    });

    it('should have correct description', () => {
      expect(longPressPlugin.description).toBe(
        "Long press at specific coordinates for given duration (ms). Use describe_ui for precise coordinates (don't guess from screenshots).",
      );
    });

    it('should have handler function', () => {
      expect(typeof longPressPlugin.handler).toBe('function');
    });

    it('should validate schema fields with safeParse', () => {
      const schema = z.object(longPressPlugin.schema);

      // Valid case
      expect(
        schema.safeParse({
          simulatorUuid: '12345678-1234-1234-1234-123456789012',
          x: 100,
          y: 200,
          duration: 1500,
        }).success,
      ).toBe(true);

      // Invalid simulatorUuid
      expect(
        schema.safeParse({
          simulatorUuid: 'invalid-uuid',
          x: 100,
          y: 200,
          duration: 1500,
        }).success,
      ).toBe(false);

      // Invalid x (not integer)
      expect(
        schema.safeParse({
          simulatorUuid: '12345678-1234-1234-1234-123456789012',
          x: 100.5,
          y: 200,
          duration: 1500,
        }).success,
      ).toBe(false);

      // Invalid y (not integer)
      expect(
        schema.safeParse({
          simulatorUuid: '12345678-1234-1234-1234-123456789012',
          x: 100,
          y: 200.5,
          duration: 1500,
        }).success,
      ).toBe(false);

      // Invalid duration (not positive)
      expect(
        schema.safeParse({
          simulatorUuid: '12345678-1234-1234-1234-123456789012',
          x: 100,
          y: 200,
          duration: 0,
        }).success,
      ).toBe(false);

      // Invalid duration (negative)
      expect(
        schema.safeParse({
          simulatorUuid: '12345678-1234-1234-1234-123456789012',
          x: 100,
          y: 200,
          duration: -100,
        }).success,
      ).toBe(false);
    });
  });

  describe('Command Generation', () => {
    it('should generate correct axe command for basic long press', async () => {
      let capturedCommand: string[] = [];
      const trackingExecutor = async (command: string[]) => {
        capturedCommand = command;
        return {
          success: true,
          output: 'long press completed',
          error: undefined,
          process: { pid: 12345 },
        };
      };

      const mockAxeHelpers = {
        getAxePath: () => '/usr/local/bin/axe',
        getBundledAxeEnvironment: () => ({}),
        createAxeNotAvailableResponse: () => ({
          content: [{ type: 'text', text: 'Mock axe not available' }],
          isError: true,
        }),
      };

      await long_pressLogic(
        {
          simulatorUuid: '12345678-1234-1234-1234-123456789012',
          x: 100,
          y: 200,
          duration: 1500,
        },
        trackingExecutor,
        mockAxeHelpers,
      );

      expect(capturedCommand).toEqual([
        '/usr/local/bin/axe',
        'touch',
        '-x',
        '100',
        '-y',
        '200',
        '--down',
        '--up',
        '--delay',
        '1.5',
        '--udid',
        '12345678-1234-1234-1234-123456789012',
      ]);
    });

    it('should generate correct axe command for long press with different coordinates', async () => {
      let capturedCommand: string[] = [];
      const trackingExecutor = async (command: string[]) => {
        capturedCommand = command;
        return {
          success: true,
          output: 'long press completed',
          error: undefined,
          process: { pid: 12345 },
        };
      };

      const mockAxeHelpers = {
        getAxePath: () => '/usr/local/bin/axe',
        getBundledAxeEnvironment: () => ({}),
        createAxeNotAvailableResponse: () => ({
          content: [{ type: 'text', text: 'Mock axe not available' }],
          isError: true,
        }),
      };

      await long_pressLogic(
        {
          simulatorUuid: '12345678-1234-1234-1234-123456789012',
          x: 50,
          y: 75,
          duration: 2000,
        },
        trackingExecutor,
        mockAxeHelpers,
      );

      expect(capturedCommand).toEqual([
        '/usr/local/bin/axe',
        'touch',
        '-x',
        '50',
        '-y',
        '75',
        '--down',
        '--up',
        '--delay',
        '2',
        '--udid',
        '12345678-1234-1234-1234-123456789012',
      ]);
    });

    it('should generate correct axe command for short duration long press', async () => {
      let capturedCommand: string[] = [];
      const trackingExecutor = async (command: string[]) => {
        capturedCommand = command;
        return {
          success: true,
          output: 'long press completed',
          error: undefined,
          process: { pid: 12345 },
        };
      };

      const mockAxeHelpers = {
        getAxePath: () => '/usr/local/bin/axe',
        getBundledAxeEnvironment: () => ({}),
        createAxeNotAvailableResponse: () => ({
          content: [{ type: 'text', text: 'Mock axe not available' }],
          isError: true,
        }),
      };

      await long_pressLogic(
        {
          simulatorUuid: '12345678-1234-1234-1234-123456789012',
          x: 300,
          y: 400,
          duration: 500,
        },
        trackingExecutor,
        mockAxeHelpers,
      );

      expect(capturedCommand).toEqual([
        '/usr/local/bin/axe',
        'touch',
        '-x',
        '300',
        '-y',
        '400',
        '--down',
        '--up',
        '--delay',
        '0.5',
        '--udid',
        '12345678-1234-1234-1234-123456789012',
      ]);
    });

    it('should generate correct axe command with bundled axe path', async () => {
      let capturedCommand: string[] = [];
      const trackingExecutor = async (command: string[]) => {
        capturedCommand = command;
        return {
          success: true,
          output: 'long press completed',
          error: undefined,
          process: { pid: 12345 },
        };
      };

      const mockAxeHelpers = {
        getAxePath: () => '/path/to/bundled/axe',
        getBundledAxeEnvironment: () => ({ AXE_PATH: '/some/path' }),
        createAxeNotAvailableResponse: () => ({
          content: [{ type: 'text', text: 'Mock axe not available' }],
          isError: true,
        }),
      };

      await long_pressLogic(
        {
          simulatorUuid: '12345678-1234-1234-1234-123456789012',
          x: 150,
          y: 250,
          duration: 3000,
        },
        trackingExecutor,
        mockAxeHelpers,
      );

      expect(capturedCommand).toEqual([
        '/path/to/bundled/axe',
        'touch',
        '-x',
        '150',
        '-y',
        '250',
        '--down',
        '--up',
        '--delay',
        '3',
        '--udid',
        '12345678-1234-1234-1234-123456789012',
      ]);
    });
  });

  describe('Handler Behavior (Complete Literal Returns)', () => {
    it('should return success for valid long press execution', async () => {
      const mockExecutor = createMockExecutor({
        success: true,
        output: 'long press completed',
        error: '',
      });

      const mockAxeHelpers = {
        getAxePath: () => '/usr/local/bin/axe',
        getBundledAxeEnvironment: () => ({}),
        createAxeNotAvailableResponse: () => ({
          content: [{ type: 'text', text: 'Mock axe not available' }],
          isError: true,
        }),
      };

      const result = await long_pressLogic(
        {
          simulatorUuid: '12345678-1234-1234-1234-123456789012',
          x: 100,
          y: 200,
          duration: 1500,
        },
        mockExecutor,
        mockAxeHelpers,
      );

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: 'Long press at (100, 200) for 1500ms simulated successfully.\n\nWarning: describe_ui has not been called yet. Consider using describe_ui for precise coordinates instead of guessing from screenshots.',
          },
        ],
        isError: false,
      });
    });

    it('should handle DependencyError when axe is not available', async () => {
      const mockExecutor = createMockExecutor({
        success: true,
        output: '',
        error: undefined,
        process: { pid: 12345 },
      });

      const mockAxeHelpers = {
        getAxePath: () => null, // Mock axe not found
        getBundledAxeEnvironment: () => ({}),
        createAxeNotAvailableResponse: () => ({
          content: [
            {
              type: 'text',
              text: 'Bundled axe tool not found. UI automation features are not available.\n\nThis is likely an installation issue with the npm package.\nPlease reinstall xcodebuildmcp or report this issue.',
            },
          ],
          isError: true,
        }),
      };

      const result = await long_pressLogic(
        {
          simulatorUuid: '12345678-1234-1234-1234-123456789012',
          x: 100,
          y: 200,
          duration: 1500,
        },
        mockExecutor,
        mockAxeHelpers,
      );

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: 'Bundled axe tool not found. UI automation features are not available.\n\nThis is likely an installation issue with the npm package.\nPlease reinstall xcodebuildmcp or report this issue.',
          },
        ],
        isError: true,
      });
    });

    it('should handle AxeError from failed command execution', async () => {
      const mockExecutor = createMockExecutor({
        success: false,
        output: '',
        error: 'axe command failed',
        process: { pid: 12345 },
      });

      const mockAxeHelpers = {
        getAxePath: () => '/usr/local/bin/axe',
        getBundledAxeEnvironment: () => ({}),
        createAxeNotAvailableResponse: () => ({
          content: [{ type: 'text', text: 'Mock axe not available' }],
          isError: true,
        }),
      };

      const result = await long_pressLogic(
        {
          simulatorUuid: '12345678-1234-1234-1234-123456789012',
          x: 100,
          y: 200,
          duration: 1500,
        },
        mockExecutor,
        mockAxeHelpers,
      );

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: "Error: Failed to simulate long press at (100, 200): axe command 'touch' failed.\nDetails: axe command failed",
          },
        ],
        isError: true,
      });
    });

    it('should handle SystemError from command execution', async () => {
      const mockExecutor = () => {
        throw new Error('ENOENT: no such file or directory');
      };

      const mockAxeHelpers = {
        getAxePath: () => '/usr/local/bin/axe',
        getBundledAxeEnvironment: () => ({}),
        createAxeNotAvailableResponse: () => ({
          content: [{ type: 'text', text: 'Mock axe not available' }],
          isError: true,
        }),
      };

      const result = await long_pressLogic(
        {
          simulatorUuid: '12345678-1234-1234-1234-123456789012',
          x: 100,
          y: 200,
          duration: 1500,
        },
        mockExecutor,
        mockAxeHelpers,
      );

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: expect.stringContaining(
              'Error: System error executing axe: Failed to execute axe command: ENOENT: no such file or directory',
            ),
          },
        ],
        isError: true,
      });
    });

    it('should handle unexpected Error objects', async () => {
      const mockExecutor = () => {
        throw new Error('Unexpected error');
      };

      const mockAxeHelpers = {
        getAxePath: () => '/usr/local/bin/axe',
        getBundledAxeEnvironment: () => ({}),
        createAxeNotAvailableResponse: () => ({
          content: [{ type: 'text', text: 'Mock axe not available' }],
          isError: true,
        }),
      };

      const result = await long_pressLogic(
        {
          simulatorUuid: '12345678-1234-1234-1234-123456789012',
          x: 100,
          y: 200,
          duration: 1500,
        },
        mockExecutor,
        mockAxeHelpers,
      );

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: expect.stringContaining(
              'Error: System error executing axe: Failed to execute axe command: Unexpected error',
            ),
          },
        ],
        isError: true,
      });
    });

    it('should handle unexpected string errors', async () => {
      const mockExecutor = () => {
        throw 'String error';
      };

      const mockAxeHelpers = {
        getAxePath: () => '/usr/local/bin/axe',
        getBundledAxeEnvironment: () => ({}),
        createAxeNotAvailableResponse: () => ({
          content: [{ type: 'text', text: 'Mock axe not available' }],
          isError: true,
        }),
      };

      const result = await long_pressLogic(
        {
          simulatorUuid: '12345678-1234-1234-1234-123456789012',
          x: 100,
          y: 200,
          duration: 1500,
        },
        mockExecutor,
        mockAxeHelpers,
      );

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: 'Error: System error executing axe: Failed to execute axe command: String error',
          },
        ],
        isError: true,
      });
    });
  });
});

```

--------------------------------------------------------------------------------
/docs/RELOADEROO.md:
--------------------------------------------------------------------------------

```markdown
# Reloaderoo Integration Guide

This guide explains how to use Reloaderoo v1.1.2+ for testing and developing XcodeBuildMCP with both CLI inspection tools and transparent proxy capabilities.

## Overview

**Reloaderoo** is a dual-mode MCP development tool that operates as both a CLI inspection tool and a transparent proxy server for the Model Context Protocol (MCP). It provides two distinct operational modes for different development workflows.

## Installation

Reloaderoo is available via npm and can be used with npx for universal compatibility.

```bash
# Use npx to run reloaderoo (works on any system)
npx reloaderoo@latest --help

# Or install globally if preferred
npm install -g reloaderoo
reloaderoo --help
```

## Two Operational Modes

### 🔍 **CLI Mode** (Inspection & Testing)

Direct command-line access to MCP servers without client setup - perfect for testing and debugging:

**Key Benefits:**
- ✅ **One-shot commands** - Test tools, list resources, get server info
- ✅ **No MCP client required** - Perfect for testing and debugging
- ✅ **Raw JSON output** - Ideal for scripts and automation  
- ✅ **8 inspection commands** - Complete MCP protocol coverage
- ✅ **AI agent friendly** - Designed for terminal-based AI development workflows

**Basic Commands:**

```bash
# List all available tools
npx reloaderoo@latest inspect list-tools -- node build/index.js

# Call any tool with parameters  
npx reloaderoo@latest inspect call-tool <tool_name> --params '<json>' -- node build/index.js

# Get server information
npx reloaderoo@latest inspect server-info -- node build/index.js

# List available resources
npx reloaderoo@latest inspect list-resources -- node build/index.js

# Read a specific resource
npx reloaderoo@latest inspect read-resource "<uri>" -- node build/index.js

# List available prompts
npx reloaderoo@latest inspect list-prompts -- node build/index.js

# Get a specific prompt
npx reloaderoo@latest inspect get-prompt <name> --args '<json>' -- node build/index.js

# Check server connectivity
npx reloaderoo@latest inspect ping -- node build/index.js
```

**Example Tool Calls:**

```bash
# List connected devices
npx reloaderoo@latest inspect call-tool list_devices --params '{}' -- node build/index.js

# Get doctor information
npx reloaderoo@latest inspect call-tool doctor --params '{}' -- node build/index.js

# List iOS simulators
npx reloaderoo@latest inspect call-tool list_sims --params '{}' -- node build/index.js

# Read devices resource
npx reloaderoo@latest inspect read-resource "xcodebuildmcp://devices" -- node build/index.js
```

### 🔄 **Proxy Mode** (Hot-Reload Development)

Transparent MCP proxy server that enables seamless hot-reloading during development:

**Key Benefits:**
- ✅ **Hot-reload MCP servers** without disconnecting your AI client
- ✅ **Session persistence** - Keep your development context intact
- ✅ **Automatic `restart_server` tool** - AI agents can restart servers on demand
- ✅ **Transparent forwarding** - Full MCP protocol passthrough
- ✅ **Process management** - Spawns, monitors, and restarts your server process

**Usage:**

```bash
# Start proxy mode (your AI client connects to this)
npx reloaderoo@latest proxy -- node build/index.js

# With debug logging
npx reloaderoo@latest proxy --log-level debug -- node build/index.js

# Then in your AI session, request:
# "Please restart the MCP server to load my latest changes"
```

The AI agent will automatically call the `restart_server` tool, preserving your session while reloading code changes.

## MCP Inspection Server Mode

Start CLI mode as a persistent MCP server for interactive debugging through MCP clients:

```bash
# Start reloaderoo in CLI mode as an MCP server
npx reloaderoo@latest inspect mcp -- node build/index.js
```

This runs CLI mode as a persistent MCP server, exposing 8 debug tools through the MCP protocol:
- `list_tools` - List all server tools
- `call_tool` - Call any server tool
- `list_resources` - List all server resources  
- `read_resource` - Read any server resource
- `list_prompts` - List all server prompts
- `get_prompt` - Get any server prompt
- `get_server_info` - Get comprehensive server information
- `ping` - Test server connectivity

## Claude Code Compatibility

When running under Claude Code, XcodeBuildMCP automatically detects the environment and consolidates multiple content blocks into single responses with `---` separators.

**Automatic Detection Methods:**
1. **Environment Variables**: `CLAUDECODE=1` or `CLAUDE_CODE_ENTRYPOINT=cli`
2. **Parent Process Analysis**: Checks if parent process contains 'claude'
3. **Graceful Fallback**: Falls back to environment variables if process detection fails

**No Configuration Required**: The consolidation happens automatically when Claude Code is detected.

## Command Reference

### Command Structure

```bash
npx reloaderoo@latest [options] [command]

Two modes, one tool:
• Proxy MCP server that adds support for hot-reloading MCP servers.
• CLI tool for inspecting MCP servers.

Global Options:
  -V, --version    Output the version number
  -h, --help       Display help for command

Commands:
  proxy [options]  🔄 Run as MCP proxy server (default behavior)
  inspect          🔍 Inspect and debug MCP servers
  info [options]   📊 Display version and configuration information
  help [command]   ❓ Display help for command
```

### 🔄 **Proxy Mode Commands**

```bash
npx reloaderoo@latest proxy [options] -- <child-command> [child-args...]

Options:
  -w, --working-dir <directory>    Working directory for the child process
  -l, --log-level <level>          Log level (debug, info, notice, warning, error, critical)
  -f, --log-file <path>            Custom log file path (logs to stderr by default)
  -t, --restart-timeout <ms>       Timeout for restart operations (default: 30000ms)
  -m, --max-restarts <number>      Maximum restart attempts (0-10, default: 3)
  -d, --restart-delay <ms>         Delay between restart attempts (default: 1000ms)
  -q, --quiet                      Suppress non-essential output
  --no-auto-restart                Disable automatic restart on crashes
  --debug                          Enable debug mode with verbose logging
  --dry-run                        Validate configuration without starting proxy

Examples:
  npx reloaderoo proxy -- node build/index.js
  npx reloaderoo -- node build/index.js                    # Same as above (proxy is default)
  npx reloaderoo proxy --log-level debug -- node build/index.js
```

### 🔍 **CLI Mode Commands**

```bash
npx reloaderoo@latest inspect [subcommand] [options] -- <child-command> [child-args...]

Subcommands:
  server-info [options]            Get server information and capabilities
  list-tools [options]             List all available tools
  call-tool [options] <name>       Call a specific tool
  list-resources [options]         List all available resources
  read-resource [options] <uri>    Read a specific resource
  list-prompts [options]           List all available prompts
  get-prompt [options] <name>      Get a specific prompt
  ping [options]                   Check server connectivity

Examples:
  npx reloaderoo@latest inspect list-tools -- node build/index.js
  npx reloaderoo@latest inspect call-tool list_devices --params '{}' -- node build/index.js
  npx reloaderoo@latest inspect server-info -- node build/index.js
```

### **Info Command**

```bash
npx reloaderoo@latest info [options]

Options:
  -v, --verbose                    Show detailed information
  -h, --help                       Display help for command
  
Examples:
  npx reloaderoo@latest info              # Show basic system information
  npx reloaderoo@latest info --verbose    # Show detailed system information
```

### Response Format

All CLI commands return structured JSON:

```json
{
  "success": true,
  "data": {
    // Command-specific response data
  },
  "metadata": {
    "command": "call-tool:list_devices",
    "timestamp": "2025-07-25T08:32:47.042Z",
    "duration": 1782
  }
}
```

### Error Handling

When commands fail, you'll receive:

```json
{
  "success": false,
  "error": {
    "message": "Error description",
    "code": "ERROR_CODE"
  },
  "metadata": {
    "command": "failed-command",
    "timestamp": "2025-07-25T08:32:47.042Z",
    "duration": 100
  }
}
```

## Development Workflow

### 🔍 **CLI Mode Workflow** (Testing & Debugging)

Perfect for testing individual tools or debugging server issues without MCP client setup:

```bash
# 1. Build XcodeBuildMCP
npm run build

# 2. Test your server quickly
npx reloaderoo@latest inspect list-tools -- node build/index.js

# 3. Call specific tools to verify behavior
npx reloaderoo@latest inspect call-tool list_devices --params '{}' -- node build/index.js

# 4. Check server health and resources
npx reloaderoo@latest inspect ping -- node build/index.js
npx reloaderoo@latest inspect list-resources -- node build/index.js
```

### 🔄 **Proxy Mode Workflow** (Hot-Reload Development)

For full development sessions with AI clients that need persistent connections:

#### 1. **Start Development Session**
Configure your AI client to connect to reloaderoo proxy instead of your server directly:
```bash
npx reloaderoo@latest proxy -- node build/index.js
# or with debug logging:
npx reloaderoo@latest proxy --log-level debug -- node build/index.js
```

#### 2. **Develop Your MCP Server**
Work on your XcodeBuildMCP code as usual - make changes, add tools, modify functionality.

#### 3. **Test Changes Instantly**
```bash
# Rebuild your changes
npm run build

# Then ask your AI agent to restart the server:
# "Please restart the MCP server to load my latest changes"
```

The agent will call the `restart_server` tool automatically. Your new capabilities are immediately available!

#### 4. **Continue Development**
Your AI session continues with the updated server capabilities. No connection loss, no context reset.

### 🛠️ **MCP Inspection Server** (Interactive CLI Debugging)

For interactive debugging through MCP clients:

```bash
# Start reloaderoo CLI mode as an MCP server
npx reloaderoo@latest inspect mcp -- node build/index.js

# Then connect with an MCP client to access debug tools
# Available tools: list_tools, call_tool, list_resources, etc.
```

## Troubleshooting

### 🔄 **Proxy Mode Issues**

**Server won't start in proxy mode:**
```bash
# Check if XcodeBuildMCP runs independently first
node build/index.js

# Then try with reloaderoo proxy to validate configuration
npx reloaderoo@latest proxy -- node build/index.js
```

**Connection problems with MCP clients:**
```bash
# Enable debug logging to see what's happening
npx reloaderoo@latest proxy --log-level debug -- node build/index.js

# Check system info and configuration
npx reloaderoo@latest info --verbose
```

**Restart failures in proxy mode:**
```bash
# Increase restart timeout
npx reloaderoo@latest proxy --restart-timeout 60000 -- node build/index.js

# Check restart limits  
npx reloaderoo@latest proxy --max-restarts 5 -- node build/index.js
```

### 🔍 **CLI Mode Issues**

**CLI commands failing:**
```bash
# Test basic connectivity first
npx reloaderoo@latest inspect ping -- node build/index.js

# Enable debug logging for CLI commands (via proxy debug mode)
npx reloaderoo@latest proxy --log-level debug -- node build/index.js
```

**JSON parsing errors:**
```bash
# Check server information for troubleshooting
npx reloaderoo@latest inspect server-info -- node build/index.js

# Ensure your server outputs valid JSON
node build/index.js | head -10
```

### **General Issues**

**Command not found:**
```bash
# Ensure npx can find reloaderoo
npx reloaderoo@latest --help

# If that fails, try installing globally
npm install -g reloaderoo
```

**Parameter validation:**
```bash
# Ensure JSON parameters are properly quoted
npx reloaderoo@latest inspect call-tool list_devices --params '{}' -- node build/index.js
```

### **General Debug Mode**

```bash
# Get detailed information about what's happening
npx reloaderoo@latest proxy --debug -- node build/index.js  # For proxy mode
npx reloaderoo@latest proxy --log-level debug -- node build/index.js  # For detailed proxy logging

# View system information
npx reloaderoo@latest info --verbose
```

### Debug Tips

1. **Always build first**: Run `npm run build` before testing
2. **Check tool names**: Use `inspect list-tools` to see exact tool names
3. **Validate JSON**: Ensure parameters are valid JSON strings
4. **Enable debug logging**: Use `--log-level debug` or `--debug` for verbose output
5. **Test connectivity**: Use `inspect ping` to verify server communication

## Advanced Usage

### Environment Variables

Configure reloaderoo behavior via environment variables:

```bash
# Logging Configuration
export MCPDEV_PROXY_LOG_LEVEL=debug           # Log level (debug, info, notice, warning, error, critical)
export MCPDEV_PROXY_LOG_FILE=/path/to/log     # Custom log file path (default: stderr)
export MCPDEV_PROXY_DEBUG_MODE=true           # Enable debug mode (true/false)

# Process Management
export MCPDEV_PROXY_RESTART_LIMIT=5           # Maximum restart attempts (0-10, default: 3)
export MCPDEV_PROXY_AUTO_RESTART=true         # Enable/disable auto-restart (true/false)
export MCPDEV_PROXY_TIMEOUT=30000             # Operation timeout in milliseconds
export MCPDEV_PROXY_RESTART_DELAY=1000        # Delay between restart attempts in milliseconds
export MCPDEV_PROXY_CWD=/path/to/directory     # Default working directory
```

### Custom Working Directory

```bash
npx reloaderoo@latest proxy --working-dir /custom/path -- node build/index.js
npx reloaderoo@latest inspect list-tools --working-dir /custom/path -- node build/index.js
```

### Timeout Configuration

```bash
npx reloaderoo@latest proxy --restart-timeout 60000 -- node build/index.js
```

## Integration with XcodeBuildMCP

Reloaderoo is specifically configured to work with XcodeBuildMCP's:

- **84+ Tools**: All workflow groups accessible via CLI
- **4 Resources**: Direct access to devices, simulators, environment, swift-packages
- **Dynamic Tool Discovery**: Compatible with `discover_tools` functionality
- **Claude Code Detection**: Automatic consolidation of multiple content blocks
- **Hot-Reload Support**: Seamless development workflow with `restart_server`

For more information about XcodeBuildMCP's architecture and capabilities, see:
- [Architecture Guide](ARCHITECTURE.md)
- [Plugin Development Guide](PLUGIN_DEVELOPMENT.md)
- [Testing Guide](TESTING.md)
```

--------------------------------------------------------------------------------
/example_projects/iOS_Calculator/CalculatorAppPackage/Tests/CalculatorAppFeatureTests/CalculatorServiceTests.swift:
--------------------------------------------------------------------------------

```swift
import Testing
import Foundation
@testable import CalculatorAppFeature

// MARK: - Calculator Basic Tests
@Suite("Calculator Basic Functionality")
struct CalculatorBasicTests {
    
    @Test("Calculator initializes with correct default values")
    func testInitialState() {
        let calculator = CalculatorService()
        #expect(calculator.display == "0")
        #expect(calculator.currentValue == 0)
        #expect(calculator.previousValue == 0)
        #expect(calculator.currentOperation == nil)
        #expect(calculator.willResetDisplay == false)
    }
    
    @Test("Clear function resets calculator to initial state")
    func testClear() {
        let calculator = CalculatorService()
        calculator.inputNumber("5")
        calculator.setOperation(.add)
        calculator.inputNumber("3")
        
        calculator.clear()
        
        #expect(calculator.display == "0")
        #expect(calculator.currentValue == 0)
        #expect(calculator.previousValue == 0)
    }
    
    @Test("This test should fail to verify error reporting")
    func testIntentionalFailure() {
        let calculator = CalculatorService()
        // This test is designed to fail to test error reporting
        #expect(calculator.display == "999", "This should fail - display should be 0, not 999")
        #expect(calculator.currentOperation == nil)
        #expect(calculator.willResetDisplay == false)
    }
}

// MARK: - Number Input Tests
@Suite("Number Input")
struct NumberInputTests {
    
    @Test("Adding single digit numbers")
    func testSingleDigitInput() {
        let calculator = CalculatorService()
        
        calculator.inputNumber("5")
        #expect(calculator.display == "5")
        #expect(calculator.currentValue == 5)
    }
    
    @Test("Adding multiple digit numbers")
    func testMultipleDigitInput() {
        let calculator = CalculatorService()
        
        calculator.inputNumber("1")
        calculator.inputNumber("2")
        calculator.inputNumber("3")
        
        #expect(calculator.display == "123")
        #expect(calculator.currentValue == 123)
    }
    
    @Test("Adding decimal numbers")
    func testDecimalInput() {
        let calculator = CalculatorService()
        
        calculator.inputNumber("1")
        calculator.inputDecimal()
        calculator.inputNumber("5")
        
        #expect(calculator.display == "1.5")
        #expect(calculator.currentValue == 1.5)
    }
    
    @Test("Multiple decimal points should be ignored")
    func testMultipleDecimalPoints() {
        let calculator = CalculatorService()
        
        calculator.inputNumber("1")
        calculator.inputDecimal()
        calculator.inputNumber("5")
        calculator.inputDecimal() // This should be ignored
        calculator.inputNumber("2")
        
        #expect(calculator.display == "1.52")
        #expect(calculator.currentValue == 1.52)
    }
    
    @Test("Decimal point at start creates 0.")
    func testDecimalAtStart() {
        let calculator = CalculatorService()
        
        calculator.inputDecimal()
        calculator.inputNumber("5")
        
        #expect(calculator.display == "0.5")
        #expect(calculator.currentValue == 0.5)
    }
}

// MARK: - Operation Tests
@Suite("Mathematical Operations")
struct OperationTests {
    
    @Test("Addition operation", arguments: [
        (5.0, 3.0, 8.0),
        (10.0, -2.0, 8.0),
        (0.0, 5.0, 5.0),
        (-3.0, -7.0, -10.0)
    ])
    func testAddition(a: Double, b: Double, expected: Double) {
        let result = CalculatorService.Operation.add.calculate(a, b)
        #expect(result == expected)
    }
    
    @Test("Subtraction operation", arguments: [
        (10.0, 3.0, 7.0),
        (5.0, 8.0, -3.0),
        (0.0, 5.0, -5.0),
        (-3.0, -7.0, 4.0)
    ])
    func testSubtraction(a: Double, b: Double, expected: Double) {
        let result = CalculatorService.Operation.subtract.calculate(a, b)
        #expect(result == expected)
    }
    
    @Test("Multiplication operation", arguments: [
        (5.0, 3.0, 15.0),
        (4.0, -2.0, -8.0),
        (0.0, 5.0, 0.0),
        (-3.0, -7.0, 21.0)
    ])
    func testMultiplication(a: Double, b: Double, expected: Double) {
        let result = CalculatorService.Operation.multiply.calculate(a, b)
        #expect(result == expected)
    }
    
    @Test("Division operation", arguments: [
        (10.0, 2.0, 5.0),
        (15.0, 3.0, 5.0),
        (-8.0, 2.0, -4.0),
        (7.0, 2.0, 3.5)
    ])
    func testDivision(a: Double, b: Double, expected: Double) {
        let result = CalculatorService.Operation.divide.calculate(a, b)
        #expect(result == expected)
    }
    
    @Test("Division by zero returns zero")
    func testDivisionByZero() {
        let result = CalculatorService.Operation.divide.calculate(10.0, 0.0)
        #expect(result == 0.0)
    }
}

// MARK: - Calculator Integration Tests
@Suite("Calculator Integration Tests")
struct CalculatorIntegrationTests {
    
    @Test("Simple addition calculation")
    func testSimpleAddition() {
        let calculator = CalculatorService()
        
        calculator.inputNumber("5")
        calculator.setOperation(.add)
        calculator.inputNumber("3")
        calculator.calculate()
        
        #expect(calculator.display == "8")
        #expect(calculator.currentValue == 8)
    }
    
    @Test("Chain calculations")
    func testChainCalculations() {
        let calculator = CalculatorService()
        
        calculator.inputNumber("5")
        calculator.setOperation(.add)
        calculator.inputNumber("3")
        calculator.setOperation(.multiply) // Should calculate 5+3=8 first
        calculator.inputNumber("2")
        calculator.calculate()
        
        #expect(calculator.currentValue == 16) // (5+3) * 2 = 16
    }
    
    @Test("Complex calculation sequence")
    func testComplexCalculation() {
        let calculator = CalculatorService()
        
        // Calculate: 10 + 5 * 2 - 3
        calculator.inputNumber("1")
        calculator.inputNumber("0")
        calculator.setOperation(.add)
        calculator.inputNumber("5")
        calculator.setOperation(.multiply)
        calculator.inputNumber("2")
        calculator.setOperation(.subtract)
        calculator.inputNumber("3")
        calculator.calculate()
        
        #expect(calculator.currentValue == 27) // ((10+5)*2)-3 = 27
    }

    @Test("Repetitive equals press repeats last operation")
    func testRepetitiveEquals() {
        let calculator = CalculatorService()

        calculator.inputNumber("5")
        calculator.setOperation(.add)
        calculator.inputNumber("3")
        calculator.calculate() // 5 + 3 = 8

        #expect(calculator.currentValue == 8)

        calculator.calculate() // Should be 8 + 3 = 11
        #expect(calculator.currentValue == 11)

        calculator.calculate() // Should be 11 + 3 = 14
        #expect(calculator.currentValue == 14)
    }

    @Test("Expression display updates correctly")
    func testExpressionDisplay() {
        let calculator = CalculatorService()

        calculator.inputNumber("1")
        calculator.inputNumber("2")
        #expect(calculator.expressionDisplay == "")

        calculator.setOperation(.add)
        #expect(calculator.expressionDisplay == "12 +")

        calculator.inputNumber("3")
        #expect(calculator.expressionDisplay == "12 +") 

        calculator.calculate()
        #expect(calculator.expressionDisplay == "12 + 3 =")
    }
}

// MARK: - Special Functions Tests
@Suite("Special Functions")
struct SpecialFunctionsTests {
    
    @Test("Toggle sign on positive number")
    func testToggleSignPositive() {
        let calculator = CalculatorService()
        
        calculator.inputNumber("5")
        calculator.toggleSign()
        
        #expect(calculator.display == "-5")
        #expect(calculator.currentValue == -5)
    }
    
    @Test("Toggle sign on negative number")
    func testToggleSignNegative() {
        let calculator = CalculatorService()
        
        calculator.inputNumber("5")
        calculator.toggleSign()
        calculator.toggleSign()
        
        #expect(calculator.display == "5")
        #expect(calculator.currentValue == 5)
    }
    
    @Test("Toggle sign on zero has no effect")
    func testToggleSignZero() {
        let calculator = CalculatorService()
        
        calculator.toggleSign()
        
        #expect(calculator.display == "0")
        #expect(calculator.currentValue == 0)
    }
    
    @Test("Percentage calculation", arguments: [
        ("100", 1.0),
        ("50", 0.5),
        ("25", 0.25),
        ("200", 2.0)
    ])
    func testPercentage(input: String, expected: Double) {
        let calculator = CalculatorService()
        
        calculator.inputNumber(input)
        calculator.percentage()
        
        #expect(calculator.currentValue == expected)
    }
}

// MARK: - Input Handler Tests
@Suite("Input Handler Integration")
struct InputHandlerTests {
    
    @Test("Number input through handler")
    func testNumberInputThroughHandler() {
        let calculator = CalculatorService()
        let handler = CalculatorInputHandler(service: calculator)
        
        handler.handleInput("1")
        handler.handleInput("2")
        handler.handleInput("3")
        
        #expect(calculator.display == "123")
    }
    
    @Test("Operation input through handler")
    func testOperationInputThroughHandler() {
        let calculator = CalculatorService()
        let handler = CalculatorInputHandler(service: calculator)
        
        handler.handleInput("5")
        handler.handleInput("+")
        handler.handleInput("3")
        handler.handleInput("=")
        
        #expect(calculator.currentValue == 8)
    }
    
    @Test("Clear input through handler")
    func testClearInputThroughHandler() {
        let calculator = CalculatorService()
        let handler = CalculatorInputHandler(service: calculator)
        
        handler.handleInput("5")
        handler.handleInput("+")
        handler.handleInput("3")
        handler.handleInput("C")
        
        #expect(calculator.display == "0")
        #expect(calculator.currentValue == 0)
    }
    
    @Test("Decimal input through handler")
    func testDecimalInputThroughHandler() {
        let calculator = CalculatorService()
        let handler = CalculatorInputHandler(service: calculator)
        
        handler.handleInput("1")
        handler.handleInput(".")
        handler.handleInput("5")
        
        #expect(calculator.display == "1.5")
    }
}

// MARK: - Edge Cases Tests
@Suite("Edge Cases")
struct EdgeCaseTests {
    
    @Test("Calculate without setting operation")
    func testCalculateWithoutOperation() {
        let calculator = CalculatorService()
        
        calculator.inputNumber("5")
        calculator.calculate()
        
        #expect(calculator.currentValue == 5) // Should remain unchanged
    }
    
    @Test("Setting operation without previous number")
    func testOperationWithoutPreviousNumber() {
        let calculator = CalculatorService()
        
        calculator.setOperation(.add)
        calculator.inputNumber("5")
        calculator.calculate()
        
        #expect(calculator.currentValue == 5) // 0 + 5 = 5
    }
    
    @Test("Multiple equals presses")
    func testMultipleEquals() {
        let calculator = CalculatorService()
        
        calculator.inputNumber("5")
        calculator.setOperation(.add)
        calculator.inputNumber("3")
        calculator.calculate()
        
        let firstResult = calculator.currentValue
        calculator.calculate() // Second equals press
        
        #expect(firstResult == 8)
        #expect(calculator.currentValue == 11) // Should repeat last operation: 8 + 3 = 11
    }
}

// MARK: - Error Handling Tests
@Suite("Error Handling")
struct ErrorHandlingTests {
    
    @Test("Calculator handles invalid input gracefully")
    func testInvalidInputHandling() {
        let calculator = CalculatorService()
        let handler = CalculatorInputHandler(service: calculator)
        
        // Test pressing operation without any number
        handler.handleInput("+")
        handler.handleInput("5")
        handler.handleInput("=")
        
        #expect(calculator.currentValue == 5) // Should be 0 + 5 = 5
    }
    
    @Test("Calculator state after multiple clears")
    func testMultipleClearOperations() {
        let calculator = CalculatorService()
        
        calculator.inputNumber("123")
        calculator.setOperation(.add)
        calculator.inputNumber("456")
        
        // Multiple clear operations
        calculator.clear()
        calculator.clear()
        calculator.clear()
        
        #expect(calculator.display == "0")
        #expect(calculator.currentValue == 0)
        #expect(calculator.currentOperation == nil)
    }
    
    @Test("Large number error handling")
    func testLargeNumberError() {
        let calculator = CalculatorService()
        calculator.inputNumber("1000000000000") // 1e12
        calculator.setOperation(.multiply)
        calculator.inputNumber("2")
        calculator.calculate()

        #expect(calculator.hasError == true)
        #expect(calculator.display == "Error")
        #expect(calculator.expressionDisplay == "Number too large")
    }
}

// MARK: - Decimal Edge Cases
@Suite("Decimal Edge Cases")
struct DecimalEdgeCaseTests {
    
    @Test("Very small decimal numbers")
    func testVerySmallDecimals() {
        let calculator = CalculatorService()
        
        calculator.inputNumber("0")
        calculator.inputDecimal()
        calculator.inputNumber("0")
        calculator.inputNumber("0")
        calculator.inputNumber("1")
        
        #expect(calculator.display == "0.001")
        #expect(calculator.currentValue == 0.001)
    }
    
    @Test("Decimal operations precision")
    func testDecimalPrecision() {
        let calculator = CalculatorService()
        
        calculator.inputNumber("0")
        calculator.inputDecimal()
        calculator.inputNumber("1")
        calculator.setOperation(.add)
        calculator.inputNumber("0")
        calculator.inputDecimal()
        calculator.inputNumber("2")
        calculator.calculate()
        
        // 0.1 + 0.2 should equal 0.3 (within floating point precision)
        #expect(abs(calculator.currentValue - 0.3) < 0.0001)
    }
}

```

--------------------------------------------------------------------------------
/scripts/analysis/tools-analysis.ts:
--------------------------------------------------------------------------------

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

/**
 * XcodeBuildMCP Tools Analysis
 *
 * Core TypeScript module for analyzing XcodeBuildMCP tools using AST parsing.
 * Provides reliable extraction of tool information without fallback strategies.
 */

import {
  createSourceFile,
  forEachChild,
  isExportAssignment,
  isIdentifier,
  isNoSubstitutionTemplateLiteral,
  isObjectLiteralExpression,
  isPropertyAssignment,
  isStringLiteral,
  isTemplateExpression,
  isVariableDeclaration,
  isVariableStatement,
  type Node,
  type ObjectLiteralExpression,
  ScriptTarget,
  type SourceFile,
  SyntaxKind,
} from 'typescript';
import * as fs from 'fs';
import * as path from 'path';
import { glob } from 'glob';
import { fileURLToPath } from 'url';

// Get project root
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const projectRoot = path.resolve(__dirname, '..', '..');
const toolsDir = path.join(projectRoot, 'src', 'mcp', 'tools');

export interface ToolInfo {
  name: string;
  workflow: string;
  path: string;
  relativePath: string;
  description: string;
  isCanonical: boolean;
}

export interface WorkflowInfo {
  name: string;
  displayName: string;
  description: string;
  tools: ToolInfo[];
  toolCount: number;
  canonicalCount: number;
  reExportCount: number;
}

export interface AnalysisStats {
  totalTools: number;
  canonicalTools: number;
  reExportTools: number;
  workflowCount: number;
}

export interface StaticAnalysisResult {
  workflows: WorkflowInfo[];
  tools: ToolInfo[];
  stats: AnalysisStats;
}

/**
 * Extract the description from a tool's default export using TypeScript AST
 */
function extractToolDescription(sourceFile: SourceFile): string {
  let description: string | null = null;

  function visit(node: Node): void {
    let objectExpression: ObjectLiteralExpression | null = null;

    // Look for export default { ... } - the standard TypeScript pattern
    // isExportEquals is undefined for `export default` and true for `export = `
    if (isExportAssignment(node) && !node.isExportEquals) {
      if (isObjectLiteralExpression(node.expression)) {
        objectExpression = node.expression;
      }
    }

    if (objectExpression) {
      // Found export default { ... }, now look for description property
      for (const property of objectExpression.properties) {
        if (
          isPropertyAssignment(property) &&
          isIdentifier(property.name) &&
          property.name.text === 'description'
        ) {
          // Extract the description value
          if (isStringLiteral(property.initializer)) {
            // This is the most common case - simple string literal
            description = property.initializer.text;
          } else if (
            isTemplateExpression(property.initializer) ||
            isNoSubstitutionTemplateLiteral(property.initializer)
          ) {
            // Handle template literals - get the raw text and clean it
            description = property.initializer.getFullText(sourceFile).trim();
            // Remove surrounding backticks
            if (description.startsWith('`') && description.endsWith('`')) {
              description = description.slice(1, -1);
            }
          } else {
            // Handle any other expression (multiline strings, computed values)
            const fullText = property.initializer.getFullText(sourceFile).trim();
            // This covers cases where the description spans multiple lines
            // Remove surrounding quotes and normalize whitespace
            let cleaned = fullText;
            if (
              (cleaned.startsWith('"') && cleaned.endsWith('"')) ||
              (cleaned.startsWith("'") && cleaned.endsWith("'"))
            ) {
              cleaned = cleaned.slice(1, -1);
            }
            // Collapse multiple whitespaces and newlines into single spaces
            description = cleaned.replace(/\s+/g, ' ').trim();
          }
          return; // Found description, stop looking
        }
      }
    }

    forEachChild(node, visit);
  }

  visit(sourceFile);

  if (description === null) {
    throw new Error('Could not extract description from tool export default object');
  }

  return description;
}

/**
 * Check if a file is a re-export by examining its content
 */
function isReExportFile(filePath: string): boolean {
  const content = fs.readFileSync(filePath, 'utf-8');

  // Remove comments and empty lines, then check for re-export pattern
  // First remove multi-line comments
  const contentWithoutBlockComments = content.replace(/\/\*[\s\S]*?\*\//g, '');

  const cleanedLines = contentWithoutBlockComments
    .split('\n')
    .map((line) => {
      // Remove inline comments but preserve the code before them
      const codeBeforeComment = line.split('//')[0].trim();
      return codeBeforeComment;
    })
    .filter((line) => line.length > 0);

  // Should have exactly one line: export { default } from '...';
  if (cleanedLines.length !== 1) {
    return false;
  }

  const exportLine = cleanedLines[0];
  return /^export\s*{\s*default\s*}\s*from\s*['"][^'"]+['"];?\s*$/.test(exportLine);
}

/**
 * Get workflow metadata from index.ts file if it exists
 */
async function getWorkflowMetadata(
  workflowDir: string,
): Promise<{ displayName: string; description: string } | null> {
  const indexPath = path.join(toolsDir, workflowDir, 'index.ts');

  if (!fs.existsSync(indexPath)) {
    return null;
  }

  try {
    const content = fs.readFileSync(indexPath, 'utf-8');
    const sourceFile = createSourceFile(indexPath, content, ScriptTarget.Latest, true);

    const workflowExport: { name?: string; description?: string } = {};

    function visit(node: Node): void {
      // Look for: export const workflow = { ... }
      if (
        isVariableStatement(node) &&
        node.modifiers?.some((mod) => mod.kind === SyntaxKind.ExportKeyword)
      ) {
        for (const declaration of node.declarationList.declarations) {
          if (
            isVariableDeclaration(declaration) &&
            isIdentifier(declaration.name) &&
            declaration.name.text === 'workflow' &&
            declaration.initializer &&
            isObjectLiteralExpression(declaration.initializer)
          ) {
            // Extract name and description properties
            for (const property of declaration.initializer.properties) {
              if (isPropertyAssignment(property) && isIdentifier(property.name)) {
                const propertyName = property.name.text;

                if (propertyName === 'name' && isStringLiteral(property.initializer)) {
                  workflowExport.name = property.initializer.text;
                } else if (
                  propertyName === 'description' &&
                  isStringLiteral(property.initializer)
                ) {
                  workflowExport.description = property.initializer.text;
                }
              }
            }
          }
        }
      }

      forEachChild(node, visit);
    }

    visit(sourceFile);

    if (workflowExport.name && workflowExport.description) {
      return {
        displayName: workflowExport.name,
        description: workflowExport.description,
      };
    }
  } catch (error) {
    console.error(`Warning: Could not parse workflow metadata from ${indexPath}: ${error}`);
  }

  return null;
}

/**
 * Get a human-readable workflow name from directory name
 */
function getWorkflowDisplayName(workflowDir: string): string {
  const displayNames: Record<string, string> = {
    device: 'iOS Device Development',
    discovery: 'Dynamic Tool Discovery',
    doctor: 'System Doctor',
    logging: 'Logging & Monitoring',
    macos: 'macOS Development',
    'project-discovery': 'Project Discovery',
    'project-scaffolding': 'Project Scaffolding',
    simulator: 'iOS Simulator Development',
    'simulator-management': 'Simulator Management',
    'swift-package': 'Swift Package Manager',
    'ui-testing': 'UI Testing & Automation',
    utilities: 'Utilities',
  };

  return displayNames[workflowDir] || workflowDir;
}

/**
 * Get workflow description
 */
function getWorkflowDescription(workflowDir: string): string {
  const descriptions: Record<string, string> = {
    device: 'Physical device development, testing, and deployment',
    discovery: 'Intelligent workflow enablement based on task descriptions',
    doctor: 'System health checks and environment validation',
    logging: 'Log capture and monitoring across platforms',
    macos: 'Native macOS application development and testing',
    'project-discovery': 'Project analysis and information gathering',
    'project-scaffolding': 'Create new projects from templates',
    simulator: 'Simulator-based development, testing, and deployment',
    'simulator-management': 'Simulator environment and configuration management',
    'swift-package': 'Swift Package development and testing',
    'ui-testing': 'Automated UI interaction and testing',
    utilities: 'General utility operations',
  };

  return descriptions[workflowDir] || `${workflowDir} related tools`;
}

/**
 * Perform static analysis of all tools in the project
 */
export async function getStaticToolAnalysis(): Promise<StaticAnalysisResult> {
  // Find all workflow directories
  const workflowDirs = fs
    .readdirSync(toolsDir, { withFileTypes: true })
    .filter((dirent) => dirent.isDirectory())
    .map((dirent) => dirent.name)
    .sort();

  // Find all tool files
  const files = await glob('**/*.ts', {
    cwd: toolsDir,
    ignore: [
      '**/__tests__/**',
      '**/index.ts',
      '**/*.test.ts',
      '**/lib/**',
      '**/*-processes.ts', // Process management utilities
      '**/*.deps.ts', // Dependency files
      '**/*-utils.ts', // Utility files
      '**/*-common.ts', // Common/shared code
      '**/*-types.ts', // Type definition files
    ],
    absolute: true,
  });

  const allTools: ToolInfo[] = [];
  const workflowMap = new Map<string, ToolInfo[]>();

  let canonicalCount = 0;
  let reExportCount = 0;

  // Initialize workflow map
  for (const workflowDir of workflowDirs) {
    workflowMap.set(workflowDir, []);
  }

  // Process each tool file
  for (const filePath of files) {
    const toolName = path.basename(filePath, '.ts');
    const workflowDir = path.basename(path.dirname(filePath));
    const relativePath = path.relative(projectRoot, filePath);

    const isReExport = isReExportFile(filePath);

    let description = '';

    if (!isReExport) {
      // Extract description from canonical tool using AST
      try {
        const content = fs.readFileSync(filePath, 'utf-8');
        const sourceFile = createSourceFile(filePath, content, ScriptTarget.Latest, true);

        description = extractToolDescription(sourceFile);
        canonicalCount++;
      } catch (error) {
        throw new Error(`Failed to extract description from ${relativePath}: ${error}`);
      }
    } else {
      description = '(Re-exported from shared workflow)';
      reExportCount++;
    }

    const toolInfo: ToolInfo = {
      name: toolName,
      workflow: workflowDir,
      path: filePath,
      relativePath,
      description,
      isCanonical: !isReExport,
    };

    allTools.push(toolInfo);

    const workflowTools = workflowMap.get(workflowDir);
    if (workflowTools) {
      workflowTools.push(toolInfo);
    }
  }

  // Build workflow information
  const workflows: WorkflowInfo[] = [];

  for (const workflowDir of workflowDirs) {
    const workflowTools = workflowMap.get(workflowDir) ?? [];
    const canonicalTools = workflowTools.filter((t) => t.isCanonical);
    const reExportTools = workflowTools.filter((t) => !t.isCanonical);

    // Try to get metadata from index.ts, fall back to hardcoded names/descriptions
    const metadata = await getWorkflowMetadata(workflowDir);

    const workflowInfo: WorkflowInfo = {
      name: workflowDir,
      displayName: metadata?.displayName ?? getWorkflowDisplayName(workflowDir),
      description: metadata?.description ?? getWorkflowDescription(workflowDir),
      tools: workflowTools.sort((a, b) => a.name.localeCompare(b.name)),
      toolCount: workflowTools.length,
      canonicalCount: canonicalTools.length,
      reExportCount: reExportTools.length,
    };

    workflows.push(workflowInfo);
  }

  const stats: AnalysisStats = {
    totalTools: allTools.length,
    canonicalTools: canonicalCount,
    reExportTools: reExportCount,
    workflowCount: workflows.length,
  };

  return {
    workflows: workflows.sort((a, b) => a.displayName.localeCompare(b.displayName)),
    tools: allTools.sort((a, b) => a.name.localeCompare(b.name)),
    stats,
  };
}

/**
 * Get only canonical tools (excluding re-exports) for documentation generation
 */
export async function getCanonicalTools(): Promise<ToolInfo[]> {
  const analysis = await getStaticToolAnalysis();
  return analysis.tools.filter((tool) => tool.isCanonical);
}

/**
 * Get tools grouped by workflow for documentation generation
 */
export async function getToolsByWorkflow(): Promise<Map<string, ToolInfo[]>> {
  const analysis = await getStaticToolAnalysis();
  const workflowMap = new Map<string, ToolInfo[]>();

  for (const workflow of analysis.workflows) {
    // Only include canonical tools for documentation
    const canonicalTools = workflow.tools.filter((tool) => tool.isCanonical);
    if (canonicalTools.length > 0) {
      workflowMap.set(workflow.name, canonicalTools);
    }
  }

  return workflowMap;
}

// CLI support - if run directly, perform analysis and output results
if (import.meta.url === `file://${process.argv[1]}`) {
  async function main(): Promise<void> {
    try {
      console.log('🔍 Performing static analysis...');
      const analysis = await getStaticToolAnalysis();

      console.log('\n📊 Analysis Results:');
      console.log(`   Workflows: ${analysis.stats.workflowCount}`);
      console.log(`   Total tools: ${analysis.stats.totalTools}`);
      console.log(`   Canonical tools: ${analysis.stats.canonicalTools}`);
      console.log(`   Re-export tools: ${analysis.stats.reExportTools}`);

      if (process.argv.includes('--json')) {
        console.log('\n' + JSON.stringify(analysis, null, 2));
      } else {
        console.log('\n📂 Workflows:');
        for (const workflow of analysis.workflows) {
          console.log(
            `   • ${workflow.displayName} (${workflow.canonicalCount} canonical, ${workflow.reExportCount} re-exports)`,
          );
        }
      }
    } catch (error) {
      console.error('❌ Analysis failed:', error);
      process.exit(1);
    }
  }

  main();
}

```

--------------------------------------------------------------------------------
/src/mcp/tools/project-scaffolding/__tests__/scaffold_macos_project.test.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Test for scaffold_macos_project plugin - Dependency Injection Architecture
 *
 * Tests the plugin structure and exported components for scaffold_macos_project tool.
 * Uses pure dependency injection with createMockFileSystemExecutor.
 * NO VITEST MOCKING ALLOWED - Only createMockExecutor/createMockFileSystemExecutor
 *
 * Plugin location: plugins/utilities/scaffold_macos_project.js
 */

import { describe, it, expect, beforeEach } from 'vitest';
import { z } from 'zod';
import {
  createMockFileSystemExecutor,
  createNoopExecutor,
  createMockExecutor,
} from '../../../../test-utils/mock-executors.ts';
import plugin, { scaffold_macos_projectLogic } from '../scaffold_macos_project.ts';
import { TemplateManager } from '../../../../utils/template/index.ts';

// ONLY ALLOWED MOCKING: createMockFileSystemExecutor

describe('scaffold_macos_project plugin', () => {
  let mockFileSystemExecutor: ReturnType<typeof createMockFileSystemExecutor>;
  let templateManagerStub: {
    getTemplatePath: (
      platform: string,
      commandExecutor?: unknown,
      fileSystemExecutor?: unknown,
    ) => Promise<string>;
    cleanup: (path: string) => Promise<void>;
    setError: (error: Error | string | null) => void;
    getCalls: () => string;
    resetCalls: () => void;
  };

  beforeEach(async () => {
    // Create template manager stub using pure JavaScript approach
    let templateManagerCall = '';
    let templateManagerError: Error | string | null = null;

    templateManagerStub = {
      getTemplatePath: async (
        platform: string,
        commandExecutor?: unknown,
        fileSystemExecutor?: unknown,
      ) => {
        templateManagerCall = `getTemplatePath(${platform})`;
        if (templateManagerError) {
          throw templateManagerError;
        }
        return '/tmp/test-templates/macos';
      },
      cleanup: async (path: string) => {
        templateManagerCall += `,cleanup(${path})`;
        return undefined;
      },
      // Test helpers
      setError: (error: Error | string | null) => {
        templateManagerError = error;
      },
      getCalls: () => templateManagerCall,
      resetCalls: () => {
        templateManagerCall = '';
      },
    };

    // Create fresh mock file system executor for each test
    mockFileSystemExecutor = createMockFileSystemExecutor({
      existsSync: () => false,
      mkdir: async () => {},
      cp: async () => {},
      readFile: async () => 'template content with MyProject placeholder',
      writeFile: async () => {},
      readdir: async () => [
        { name: 'Package.swift', isDirectory: () => false, isFile: () => true },
        { name: 'MyProject.swift', isDirectory: () => false, isFile: () => true },
      ],
    });

    // Replace the real TemplateManager with our stub for most tests
    (TemplateManager as any).getTemplatePath = templateManagerStub.getTemplatePath;
    (TemplateManager as any).cleanup = templateManagerStub.cleanup;
  });

  describe('Export Field Validation (Literal)', () => {
    it('should have correct name field', () => {
      expect(plugin.name).toBe('scaffold_macos_project');
    });

    it('should have correct description field', () => {
      expect(plugin.description).toBe(
        'Scaffold a new macOS project from templates. Creates a modern Xcode project with workspace structure, SPM package for features, and proper macOS configuration.',
      );
    });

    it('should have handler as function', () => {
      expect(typeof plugin.handler).toBe('function');
    });

    it('should have valid schema with required fields', () => {
      // Test the schema object exists
      expect(plugin.schema).toBeDefined();
      expect(plugin.schema.projectName).toBeDefined();
      expect(plugin.schema.outputPath).toBeDefined();
      expect(plugin.schema.bundleIdentifier).toBeDefined();
      expect(plugin.schema.customizeNames).toBeDefined();
      expect(plugin.schema.deploymentTarget).toBeDefined();
    });
  });

  describe('Command Generation', () => {
    it('should generate correct curl command for macOS template download', async () => {
      // This test validates that the curl command would be generated correctly
      // by verifying the URL construction logic
      const expectedUrl =
        'https://github.com/cameroncooke/XcodeBuildMCP-macOS-Template/releases/download/';

      // The curl command should be structured correctly for macOS template
      expect(expectedUrl).toContain('XcodeBuildMCP-macOS-Template');
      expect(expectedUrl).toContain('releases/download');

      // The template zip file should follow the expected pattern
      const expectedFilename = 'template.zip';
      expect(expectedFilename).toMatch(/template\.zip$/);

      // The curl command flags should be correct
      const expectedCurlFlags = ['-L', '-f', '-o'];
      expect(expectedCurlFlags).toContain('-L'); // Follow redirects
      expect(expectedCurlFlags).toContain('-f'); // Fail on HTTP errors
      expect(expectedCurlFlags).toContain('-o'); // Output to file
    });

    it('should generate correct unzip command for template extraction', async () => {
      // This test validates that the unzip command would be generated correctly
      // by verifying the command structure
      const expectedUnzipCommand = ['unzip', '-q', 'template.zip'];

      // The unzip command should use the quiet flag
      expect(expectedUnzipCommand).toContain('-q');

      // The unzip command should target the template zip file
      expect(expectedUnzipCommand).toContain('template.zip');

      // The unzip command should be structured correctly
      expect(expectedUnzipCommand[0]).toBe('unzip');
      expect(expectedUnzipCommand[1]).toBe('-q');
      expect(expectedUnzipCommand[2]).toMatch(/template\.zip$/);
    });

    it('should generate correct commands for template with version', async () => {
      // This test validates that the curl command would be generated correctly with version
      const testVersion = 'v1.0.0';
      const expectedUrlWithVersion = `https://github.com/cameroncooke/XcodeBuildMCP-macOS-Template/releases/download/${testVersion}/`;

      // The URL should contain the specific version
      expect(expectedUrlWithVersion).toContain(testVersion);
      expect(expectedUrlWithVersion).toContain('XcodeBuildMCP-macOS-Template');
      expect(expectedUrlWithVersion).toContain('releases/download');

      // The version should be in the correct format
      expect(testVersion).toMatch(/^v\d+\.\d+\.\d+$/);

      // The full URL should be correctly constructed
      expect(expectedUrlWithVersion).toBe(
        `https://github.com/cameroncooke/XcodeBuildMCP-macOS-Template/releases/download/${testVersion}/`,
      );
    });

    it('should not generate commands when using local template path', async () => {
      let capturedCommands: string[][] = [];
      const trackingExecutor = async (command: string[]) => {
        capturedCommands.push(command);
        return {
          success: true,
          output: 'Command successful',
          error: undefined,
          process: { pid: 12345 },
        };
      };

      // Store original environment variable
      const originalEnv = process.env.XCODEBUILDMCP_MACOS_TEMPLATE_PATH;

      // Mock local template path exists
      mockFileSystemExecutor.existsSync = (path: string) => {
        return path === '/local/template/path' || path === '/local/template/path/template';
      };

      // Set environment variable for local template path
      process.env.XCODEBUILDMCP_MACOS_TEMPLATE_PATH = '/local/template/path';

      // Restore original TemplateManager for command generation tests
      const { TemplateManager: OriginalTemplateManager } = await import(
        '../../../../utils/template/index.ts'
      );
      (TemplateManager as any).getTemplatePath = OriginalTemplateManager.getTemplatePath;
      (TemplateManager as any).cleanup = OriginalTemplateManager.cleanup;

      await scaffold_macos_projectLogic(
        {
          projectName: 'TestMacApp',
          outputPath: '/tmp/test-projects',
        },
        trackingExecutor,
        mockFileSystemExecutor,
      );

      // Should not generate any curl or unzip commands when using local template
      expect(capturedCommands).not.toContainEqual(
        expect.arrayContaining(['curl', expect.anything(), expect.anything()]),
      );
      expect(capturedCommands).not.toContainEqual(
        expect.arrayContaining(['unzip', expect.anything(), expect.anything()]),
      );

      // Clean up environment variable
      process.env.XCODEBUILDMCP_MACOS_TEMPLATE_PATH = originalEnv;

      // Restore stub after test
      (TemplateManager as any).getTemplatePath = templateManagerStub.getTemplatePath;
      (TemplateManager as any).cleanup = templateManagerStub.cleanup;
    });
  });

  describe('Handler Behavior (Complete Literal Returns)', () => {
    it('should return success response for valid scaffold macOS project request', async () => {
      const result = await scaffold_macos_projectLogic(
        {
          projectName: 'TestMacApp',
          outputPath: '/tmp/test-projects',
          bundleIdentifier: 'com.test.macapp',
          customizeNames: false,
        },
        createNoopExecutor(),
        mockFileSystemExecutor,
      );

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: JSON.stringify(
              {
                success: true,
                projectPath: '/tmp/test-projects',
                platform: 'macOS',
                message: 'Successfully scaffolded macOS project "TestMacApp" in /tmp/test-projects',
                nextSteps: [
                  'Important: Before working on the project make sure to read the README.md file in the workspace root directory.',
                  'Build for macOS: build_macos({ workspacePath: "/tmp/test-projects/MyProject.xcworkspace", scheme: "MyProject" })',
                  'Build & Run on macOS: build_run_macos({ workspacePath: "/tmp/test-projects/MyProject.xcworkspace", scheme: "MyProject" })',
                ],
              },
              null,
              2,
            ),
          },
        ],
      });

      // Verify template manager calls using manual tracking
      expect(templateManagerStub.getCalls()).toBe(
        'getTemplatePath(macOS),cleanup(/tmp/test-templates/macos)',
      );
    });

    it('should return success response with customizeNames false', async () => {
      const result = await scaffold_macos_projectLogic(
        {
          projectName: 'TestMacApp',
          outputPath: '/tmp/test-projects',
          customizeNames: false,
        },
        createNoopExecutor(),
        mockFileSystemExecutor,
      );

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: JSON.stringify(
              {
                success: true,
                projectPath: '/tmp/test-projects',
                platform: 'macOS',
                message: 'Successfully scaffolded macOS project "TestMacApp" in /tmp/test-projects',
                nextSteps: [
                  'Important: Before working on the project make sure to read the README.md file in the workspace root directory.',
                  'Build for macOS: build_macos({ workspacePath: "/tmp/test-projects/MyProject.xcworkspace", scheme: "MyProject" })',
                  'Build & Run on macOS: build_run_macos({ workspacePath: "/tmp/test-projects/MyProject.xcworkspace", scheme: "MyProject" })',
                ],
              },
              null,
              2,
            ),
          },
        ],
      });
    });

    it('should return error response for invalid project name', async () => {
      const result = await scaffold_macos_projectLogic(
        {
          projectName: '123InvalidName',
          outputPath: '/tmp/test-projects',
        },
        createNoopExecutor(),
        mockFileSystemExecutor,
      );

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: JSON.stringify(
              {
                success: false,
                error:
                  'Project name must start with a letter and contain only letters, numbers, and underscores',
              },
              null,
              2,
            ),
          },
        ],
        isError: true,
      });
    });

    it('should return error response for existing project files', async () => {
      // Override existsSync to return true for workspace file
      mockFileSystemExecutor.existsSync = () => true;

      const result = await scaffold_macos_projectLogic(
        {
          projectName: 'TestMacApp',
          outputPath: '/tmp/test-projects',
        },
        createNoopExecutor(),
        mockFileSystemExecutor,
      );

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: JSON.stringify(
              {
                success: false,
                error: 'Xcode project files already exist in /tmp/test-projects',
              },
              null,
              2,
            ),
          },
        ],
        isError: true,
      });
    });

    it('should return error response for template manager failure', async () => {
      templateManagerStub.setError(new Error('Template not found'));

      const result = await scaffold_macos_projectLogic(
        {
          projectName: 'TestMacApp',
          outputPath: '/tmp/test-projects',
        },
        createNoopExecutor(),
        mockFileSystemExecutor,
      );

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: JSON.stringify(
              {
                success: false,
                error: 'Failed to get template for macOS: Template not found',
              },
              null,
              2,
            ),
          },
        ],
        isError: true,
      });
    });
  });

  describe('File System Operations', () => {
    it('should create directories and process files correctly', async () => {
      await scaffold_macos_projectLogic(
        {
          projectName: 'TestApp',
          outputPath: '/tmp/test',
          customizeNames: true,
        },
        createNoopExecutor(),
        mockFileSystemExecutor,
      );

      // Verify template manager calls using manual tracking
      expect(templateManagerStub.getCalls()).toBe(
        'getTemplatePath(macOS),cleanup(/tmp/test-templates/macos)',
      );

      // File system operations are called by the mock implementation
      // but we can't verify them without vitest mocking patterns
      // This test validates the integration works correctly
    });
  });
});

```
Page 7/11FirstPrevNextLast