#
tokens: 47181/50000 14/393 files (page 7/12)
lines: off (toggle) GitHub
raw markdown copy
This is page 7 of 12. Use http://codebase.md/cameroncooke/xcodebuildmcp?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
│       ├── README.md
│       ├── release.yml
│       ├── sentry.yml
│       └── stale.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
├── docs
│   ├── CONFIGURATION.md
│   ├── DAP_BACKEND_IMPLEMENTATION_PLAN.md
│   ├── DEBUGGING_ARCHITECTURE.md
│   ├── DEMOS.md
│   ├── dev
│   │   ├── ARCHITECTURE.md
│   │   ├── CODE_QUALITY.md
│   │   ├── CONTRIBUTING.md
│   │   ├── ESLINT_TYPE_SAFETY.md
│   │   ├── MANUAL_TESTING.md
│   │   ├── NODEJS_2025.md
│   │   ├── PLUGIN_DEVELOPMENT.md
│   │   ├── README.md
│   │   ├── RELEASE_PROCESS.md
│   │   ├── RELOADEROO_FOR_XCODEBUILDMCP.md
│   │   ├── RELOADEROO_XCODEBUILDMCP_PRIMER.md
│   │   ├── RELOADEROO.md
│   │   ├── session_management_plan.md
│   │   ├── session-aware-migration-todo.md
│   │   ├── SMITHERY.md
│   │   ├── TEST_RUNNER_ENV_IMPLEMENTATION_PLAN.md
│   │   ├── TESTING.md
│   │   └── ZOD_MIGRATION_GUIDE.md
│   ├── DEVICE_CODE_SIGNING.md
│   ├── GETTING_STARTED.md
│   ├── investigations
│   │   ├── issue-154-screenshot-downscaling.md
│   │   ├── issue-163.md
│   │   ├── issue-debugger-attach-stopped.md
│   │   └── issue-describe-ui-empty-after-debugger-resume.md
│   ├── OVERVIEW.md
│   ├── PRIVACY.md
│   ├── README.md
│   ├── SESSION_DEFAULTS.md
│   ├── TOOLS.md
│   └── TROUBLESHOOTING.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
│   │   ├── .gitignore
│   │   ├── 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
│   │   └── MCPTestTests
│   │       └── MCPTestTests.swift
│   └── 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
│   ├── generate-loaders.ts
│   ├── generate-version.ts
│   ├── release.sh
│   ├── tools-cli.ts
│   ├── update-tools-docs.ts
│   └── verify-smithery-bundle.sh
├── server.json
├── smithery.config.js
├── smithery.yaml
├── src
│   ├── core
│   │   ├── __tests__
│   │   │   └── resources.test.ts
│   │   ├── generated-plugins.ts
│   │   ├── generated-resources.ts
│   │   ├── plugin-registry.ts
│   │   ├── plugin-types.ts
│   │   └── resources.ts
│   ├── doctor-cli.ts
│   ├── index.ts
│   ├── mcp
│   │   ├── resources
│   │   │   ├── __tests__
│   │   │   │   ├── devices.test.ts
│   │   │   │   ├── doctor.test.ts
│   │   │   │   ├── session-status.test.ts
│   │   │   │   └── simulators.test.ts
│   │   │   ├── devices.ts
│   │   │   ├── doctor.ts
│   │   │   ├── session-status.ts
│   │   │   └── simulators.ts
│   │   └── tools
│   │       ├── debugging
│   │       │   ├── debug_attach_sim.ts
│   │       │   ├── debug_breakpoint_add.ts
│   │       │   ├── debug_breakpoint_remove.ts
│   │       │   ├── debug_continue.ts
│   │       │   ├── debug_detach.ts
│   │       │   ├── debug_lldb_command.ts
│   │       │   ├── debug_stack.ts
│   │       │   ├── debug_variables.ts
│   │       │   └── index.ts
│   │       ├── 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
│   │       ├── 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
│   │   ├── bootstrap.ts
│   │   └── server.ts
│   ├── smithery.ts
│   ├── test-utils
│   │   └── mock-executors.ts
│   ├── types
│   │   └── common.ts
│   ├── utils
│   │   ├── __tests__
│   │   │   ├── build-utils-suppress-warnings.test.ts
│   │   │   ├── build-utils.test.ts
│   │   │   ├── debugger-simctl.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
│   │   │   └── workflow-selection.test.ts
│   │   ├── axe
│   │   │   └── index.ts
│   │   ├── axe-helpers.ts
│   │   ├── build
│   │   │   └── index.ts
│   │   ├── build-utils.ts
│   │   ├── capabilities.ts
│   │   ├── command.ts
│   │   ├── CommandExecutor.ts
│   │   ├── debugger
│   │   │   ├── __tests__
│   │   │   │   └── debugger-manager-dap.test.ts
│   │   │   ├── backends
│   │   │   │   ├── __tests__
│   │   │   │   │   └── dap-backend.test.ts
│   │   │   │   ├── dap-backend.ts
│   │   │   │   ├── DebuggerBackend.ts
│   │   │   │   └── lldb-cli-backend.ts
│   │   │   ├── dap
│   │   │   │   ├── __tests__
│   │   │   │   │   └── transport-framing.test.ts
│   │   │   │   ├── adapter-discovery.ts
│   │   │   │   ├── transport.ts
│   │   │   │   └── types.ts
│   │   │   ├── debugger-manager.ts
│   │   │   ├── index.ts
│   │   │   ├── simctl.ts
│   │   │   ├── tool-context.ts
│   │   │   ├── types.ts
│   │   │   └── ui-automation-guard.ts
│   │   ├── environment.ts
│   │   ├── errors.ts
│   │   ├── execution
│   │   │   ├── index.ts
│   │   │   └── interactive-process.ts
│   │   ├── FileSystemExecutor.ts
│   │   ├── log_capture.ts
│   │   ├── log-capture
│   │   │   ├── device-log-sessions.ts
│   │   │   └── index.ts
│   │   ├── logger.ts
│   │   ├── logging
│   │   │   └── index.ts
│   │   ├── plugin-registry
│   │   │   └── index.ts
│   │   ├── responses
│   │   │   └── index.ts
│   │   ├── runtime-registry.ts
│   │   ├── schema-helpers.ts
│   │   ├── sentry.ts
│   │   ├── session-status.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
│   │   ├── workflow-selection.ts
│   │   ├── xcode.ts
│   │   ├── xcodemake
│   │   │   └── index.ts
│   │   └── xcodemake.ts
│   └── version.ts
├── tsconfig.json
├── tsconfig.test.json
├── tsconfig.tests.json
├── tsup.config.ts
├── vitest.config.ts
└── XcodeBuildMCP.code-workspace
```

# Files

--------------------------------------------------------------------------------
/docs/DEBUGGING_ARCHITECTURE.md:
--------------------------------------------------------------------------------

```markdown
# Debugging Architecture

This document describes how the simulator debugging tools are wired, how sessions are managed,
and how external tools (simctl, Simulator, LLDB, xcodebuild) are invoked.

## Scope

- Tools: `src/mcp/tools/debugging/*`
- Debugger subsystem: `src/utils/debugger/*`
- Execution and tool wiring: `src/utils/typed-tool-factory.ts`, `src/utils/execution/*`
- External invocation: `xcrun simctl`, `xcrun lldb`, `xcodebuild`

## Registration and Wiring

- Workflow discovery is automatic: `src/core/plugin-registry.ts` loads debugging tools via the
  generated workflow loaders (`src/core/generated-plugins.ts`).
- Tool handlers are created with the typed tool factory:
  - `createTypedToolWithContext` for standard tools (Zod validation + dependency injection).
  - `createSessionAwareToolWithContext` for session-aware tools (merges session defaults and
    validates requirements).
- Debugging tools inject a `DebuggerToolContext` that provides:
  - `executor`: a `CommandExecutor` used for simctl and other command execution.
  - `debugger`: a shared `DebuggerManager` instance.

## Session Defaults and Validation

- Session defaults live in `src/utils/session-store.ts` and are merged with user args by the
  session-aware tool factory.
- `debug_attach_sim` is session-aware; it can omit `simulatorId`/`simulatorName` in the public
  schema and rely on session defaults.
- The `XCODEBUILDMCP_DISABLE_SESSION_DEFAULTS` env flag exposes legacy schemas that include all
  parameters (no session default hiding).

## Debug Session Lifecycle

`DebuggerManager` owns lifecycle, state, and backend routing:

Backend selection happens inside `DebuggerManager.createSession`:

- Selection order: explicit `backend` argument -> `XCODEBUILDMCP_DEBUGGER_BACKEND` -> default `lldb-cli`.
- Env values: `lldb-cli`/`lldb` -> `lldb-cli`, `dap` -> `dap`, anything else throws.
- Backend factory: `defaultBackendFactory` maps `lldb-cli` to `createLldbCliBackend` and `dap` to
  `createDapBackend`. A custom factory can be injected for tests or extensions.

1. `debug_attach_sim` resolves simulator UUID and PID, then calls
   `DebuggerManager.createSession`.
2. `DebuggerManager` creates a backend (default `lldb-cli`), attaches to the process, and stores
   session metadata (id, simulatorId, pid, timestamps).
3. Debugging tools (`debug_lldb_command`, `debug_stack`, `debug_variables`,
   `debug_breakpoint_add/remove`) look up the session (explicit id or current) and route commands
   to the backend.
4. `debug_detach` calls `DebuggerManager.detachSession` to detach and dispose the backend.

## Debug Session + Command Execution Flow

Session lifecycle flow (text):

1. Client calls `debug_attach_sim`.
2. `debug_attach_sim` resolves simulator UUID and PID, then calls `DebuggerManager.createSession`.
3. `DebuggerManager.createSession` resolves backend kind (explicit/env/default), instantiates the
   backend, and calls `backend.attach`.
4. Command tools (`debug_lldb_command`, `debug_stack`, `debug_variables`) call
   `DebuggerManager.runCommand`/`getStack`/`getVariables`, which route to the backend.
5. `debug_detach` calls `DebuggerManager.detachSession`, which invokes `backend.detach` and
   `backend.dispose`.

`LldbCliBackend.runCommand()` flow (text):

1. Enqueue the command to serialize LLDB access.
2. Await backend readiness (`initialize` completed).
3. Write the command to the interactive process.
4. Write `script print("__XCODEBUILDMCP_DONE__")` to emit the sentinel marker.
5. Buffer stdout/stderr until the sentinel is detected.
6. Trim the buffer to the next prompt, sanitize output, and return the result.

<details>
<summary>Sequence diagrams (Mermaid)</summary>

```mermaid
sequenceDiagram
  participant U as User/Client
  participant A as debug_attach_sim
  participant M as DebuggerManager
  participant F as backendFactory
  participant B as DebuggerBackend (lldb-cli|dap)
  participant L as LldbCliBackend
  participant P as InteractiveProcess (xcrun lldb)

  U->>A: debug_attach_sim(simulator*, bundleId|pid)
  A->>A: determineSimulatorUuid(...)
  A->>A: resolveSimulatorAppPid(...) (if bundleId)
  A->>M: createSession({simulatorId, pid, waitFor})
  M->>M: resolveBackendKind(explicit/env/default)
  M->>F: create backend(kind)
  F-->>M: backend instance
  M->>B: attach({pid, simulatorId, waitFor})
  alt kind == lldb-cli
    B-->>L: (is LldbCliBackend)
    L->>P: spawn xcrun lldb + initialize prompt/sentinel
  else kind == dap
    B-->>M: throws DAP_ERROR_MESSAGE
  end
  M-->>A: DebugSessionInfo {id, backend, ...}
  A->>M: setCurrentSession(id) (optional)
  U->>M: runCommand(id?, "thread backtrace")
  M->>B: runCommand(...)
  U->>M: detachSession(id?)
  M->>B: detach()
  M->>B: dispose()
```

```mermaid
sequenceDiagram
  participant T as debug_lldb_command
  participant M as DebuggerManager
  participant L as LldbCliBackend
  participant P as InteractiveProcess
  participant S as stdout/stderr buffer

  T->>M: runCommand(sessionId?, command, {timeoutMs?})
  M->>L: runCommand(command)
  L->>L: enqueue(work)
  L->>L: await ready (initialize())
  L->>P: write(command + "\n")
  L->>P: write('script print("__XCODEBUILDMCP_DONE__")\n')
  P-->>S: stdout/stderr chunks
  S-->>L: handleData() appends to buffer
  L->>L: checkPending() finds sentinel
  L->>L: slice output up to sentinel
  L->>L: trim buffer to next prompt (if present)
  L->>L: sanitizeOutput() + trimEnd()
  L-->>M: output string
  M-->>T: output string
```

</details>

## LLDB CLI Backend (Default)

- Backend implementation: `src/utils/debugger/backends/lldb-cli-backend.ts`.
- Uses `InteractiveSpawner` from `src/utils/execution/interactive-process.ts` to keep a single
  long-lived `xcrun lldb` process alive.
- Keeps LLDB state (breakpoints, selected frames, target) across tool calls without reattaching.

### Internals: interactive process model

- The backend spawns `xcrun lldb --no-lldbinit -o "settings set prompt <prompt>"`.
- `InteractiveProcess.write()` is used to send commands; stdout and stderr are merged into a single
  parse buffer.
- `InteractiveProcess.dispose()` closes stdin, removes listeners, and kills the process.

### Prompt and sentinel protocol

The backend uses a prompt + sentinel protocol to detect command completion reliably:

- `LLDB_PROMPT = "XCODEBUILDMCP_LLDB> "`
- `COMMAND_SENTINEL = "__XCODEBUILDMCP_DONE__"`

Definitions:

- Prompt: the LLDB REPL prompt string that indicates LLDB is ready to accept the next command.
- Sentinel: a unique marker explicitly printed after each command to mark the end of that
  command's output.

Protocol flow:

1. Startup: write `script print("__XCODEBUILDMCP_DONE__")` to prime the prompt parser.
2. For each command:
   - Write the command.
   - Write `script print("__XCODEBUILDMCP_DONE__")`.
   - Read until the sentinel is observed, then trim up to the next prompt.

The sentinel marks command completion, while the prompt indicates the REPL is ready for the next
command.

Why both a prompt and a sentinel?

- The sentinel is the explicit end-of-output marker; LLDB does not provide a reliable boundary for
  arbitrary command output otherwise.
- The prompt is used to cleanly align the buffer for the next command after the sentinel is seen.

Annotated example (simplified):

1. Backend writes:
   - `thread backtrace`
   - `script print("__XCODEBUILDMCP_DONE__")`
2. LLDB emits (illustrative):
   - `... thread backtrace output ...`
   - `__XCODEBUILDMCP_DONE__`
   - `XCODEBUILDMCP_LLDB> `
3. Parser behavior:
   - Sentinel marks the end of the command output payload.
   - Prompt is used to trim the buffer, so the next command starts cleanly.

### Output parsing and sanitization

- `handleData()` appends to an internal buffer, and `checkPending()` scans for the sentinel regex
  `/(^|\\r?\\n)__XCODEBUILDMCP_DONE__(\\r?\\n)/`.
- Output is the buffer up to the sentinel. The remainder is trimmed to the next prompt, if present.
- `sanitizeOutput()` removes prompt echoes, sentinel lines, the `script print(...)` lines, and empty
  lines, then `runCommand()` returns `trimEnd()` output.

### Concurrency model (queueing)

- Commands are serialized through a promise queue to avoid interleaved output.
- `waitForSentinel()` rejects if a pending command exists, acting as a safety check.

### Timeouts, errors, and disposal

- Startup timeout: `DEFAULT_STARTUP_TIMEOUT_MS = 10_000`.
- Per-command timeout: `DEFAULT_COMMAND_TIMEOUT_MS = 30_000` (override via `runCommand` opts).
- Timeout failure clears the pending command and rejects the promise.
- `assertNoLldbError()` throws if `/error:/i` appears in output (simple heuristic).
- Process exit triggers `failPending(new Error(...))` so in-flight calls fail promptly.
- `runCommand()` rejects immediately if the backend is already disposed.

### Testing and injection

`getDefaultInteractiveSpawner()` throws in test environments to prevent spawning real interactive
processes. Tests should inject a mock `InteractiveSpawner` into `createLldbCliBackend()` or a custom
`DebuggerManager` backend factory.

## DAP Backend (lldb-dap)

- Implementation: `src/utils/debugger/backends/dap-backend.ts`, with protocol support in
  `src/utils/debugger/dap/transport.ts`, `src/utils/debugger/dap/types.ts`, and adapter discovery in
  `src/utils/debugger/dap/adapter-discovery.ts`.
- Selected via backend selection (explicit `backend`, `XCODEBUILDMCP_DEBUGGER_BACKEND=dap`, or default when unset).
- Adapter discovery uses `xcrun --find lldb-dap`; missing adapters raise a clear dependency error.
- One `lldb-dap` process is spawned per session; DAP framing and request correlation are handled
  by `DapTransport`.
- Session handshake: `initialize` → `attach` → `configurationDone`.
- Breakpoints are stateful: adding/removing re-issues `setBreakpoints` or
  `setFunctionBreakpoints` with the remaining list. Conditions are passed in the request body.
- Stack/variables typically require a stopped thread; the backend returns guidance if the process
  is still running.

## External Tool Invocation

### simctl and Simulator

- Simulator UUID resolution uses `xcrun simctl list devices available -j`
  (`determineSimulatorUuid` in `src/utils/simulator-utils.ts`).
- PID lookup uses `xcrun simctl spawn <simulatorId> launchctl list`
  (`resolveSimulatorAppPid` in `src/utils/debugger/simctl.ts`).

### LLDB

- Attachment uses `xcrun lldb --no-lldbinit` in the interactive backend.
- Breakpoint conditions are applied internally by the LLDB CLI backend using
  `breakpoint modify -c "<condition>" <id>` after creation.

### xcodebuild (Build/Launch Context)

- Debugging assumes a running simulator app.
- The typical flow is to build and launch via simulator tools (for example `build_sim`),
  which uses `executeXcodeBuildCommand` to invoke `xcodebuild` (or `xcodemake` when enabled).
- After launch, `debug_attach_sim` attaches LLDB to the simulator process.

## Typical Tool Flow

1. Build and launch the app on a simulator (`build_sim`, `launch_app_sim`).
2. Attach LLDB (`debug_attach_sim`) using session defaults or explicit simulator + bundle ID.
3. Set breakpoints (`debug_breakpoint_add`), inspect stack/variables (`debug_stack`,
   `debug_variables`), and issue arbitrary LLDB commands (`debug_lldb_command`).
4. Detach when done (`debug_detach`).

## Integration Points Summary

- Tool entrypoints: `src/mcp/tools/debugging/*`
- Session defaults: `src/utils/session-store.ts`
- Debug session manager: `src/utils/debugger/debugger-manager.ts`
- Backends: `src/utils/debugger/backends/lldb-cli-backend.ts` (default),
  `src/utils/debugger/backends/dap-backend.ts`
- Interactive execution: `src/utils/execution/interactive-process.ts` (used by LLDB CLI backend)
- External commands: `xcrun simctl`, `xcrun lldb`, `xcodebuild`

```

--------------------------------------------------------------------------------
/src/mcp/tools/logging/__tests__/stop_sim_log_cap.test.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * stop_sim_log_cap Plugin Tests - Test coverage for stop_sim_log_cap plugin
 *
 * This test file provides complete coverage for the stop_sim_log_cap plugin:
 * - Plugin structure validation
 * - Handler functionality (stop log capture session and retrieve captured logs)
 * - Error handling for validation and log capture failures
 *
 * Tests follow the canonical testing patterns from CLAUDE.md with deterministic
 * response validation and comprehensive parameter testing.
 * Converted to pure dependency injection without vitest mocking.
 */

import { describe, it, expect, beforeEach } from 'vitest';
import * as z from 'zod';
import stopSimLogCap, { stop_sim_log_capLogic } from '../stop_sim_log_cap.ts';
import { activeLogSessions } from '../../../../utils/log_capture.ts';
import { createMockFileSystemExecutor } from '../../../../test-utils/mock-executors.ts';

describe('stop_sim_log_cap plugin', () => {
  beforeEach(() => {
    // Clear any active sessions before each test
    activeLogSessions.clear();
  });

  // Helper function to create a test log session
  async function createTestLogSession(sessionId: string, logContent: string = '') {
    const mockProcess = {
      pid: 12345,
      killed: false,
      exitCode: null,
      kill: () => {},
    };

    const logFilePath = `/tmp/xcodemcp_sim_log_test_${sessionId}.log`;
    const fileSystem = createMockFileSystemExecutor({
      existsSync: (path) => path === logFilePath,
      readFile: async (path, _encoding) => {
        if (path !== logFilePath) {
          throw new Error(`ENOENT: no such file or directory, open '${path}'`);
        }
        return logContent;
      },
    });

    activeLogSessions.set(sessionId, {
      processes: [mockProcess as any],
      logFilePath: logFilePath,
      simulatorUuid: 'test-simulator-uuid',
      bundleId: 'com.example.TestApp',
    });

    return { fileSystem, logFilePath };
  }

  describe('Export Field Validation (Literal)', () => {
    it('should have correct plugin structure', () => {
      expect(stopSimLogCap).toHaveProperty('name');
      expect(stopSimLogCap).toHaveProperty('description');
      expect(stopSimLogCap).toHaveProperty('schema');
      expect(stopSimLogCap).toHaveProperty('handler');

      expect(stopSimLogCap.name).toBe('stop_sim_log_cap');
      expect(stopSimLogCap.description).toBe(
        'Stops an active simulator log capture session and returns the captured logs.',
      );
      expect(typeof stopSimLogCap.handler).toBe('function');
      expect(typeof stopSimLogCap.schema).toBe('object');
    });

    it('should have correct schema structure', () => {
      // Schema should be a plain object for MCP protocol compliance
      expect(typeof stopSimLogCap.schema).toBe('object');
      expect(stopSimLogCap.schema).toHaveProperty('logSessionId');

      // Validate that schema fields are Zod types that can be used for validation
      const schema = z.object(stopSimLogCap.schema);
      expect(schema.safeParse({ logSessionId: 'test-session-id' }).success).toBe(true);
      expect(schema.safeParse({ logSessionId: 123 }).success).toBe(false);
    });

    it('should validate schema with valid parameters', () => {
      expect(stopSimLogCap.schema.logSessionId.safeParse('test-session-id').success).toBe(true);
    });

    it('should reject invalid schema parameters', () => {
      expect(stopSimLogCap.schema.logSessionId.safeParse(null).success).toBe(false);
      expect(stopSimLogCap.schema.logSessionId.safeParse(undefined).success).toBe(false);
      expect(stopSimLogCap.schema.logSessionId.safeParse(123).success).toBe(false);
      expect(stopSimLogCap.schema.logSessionId.safeParse(true).success).toBe(false);
    });
  });

  describe('Input Validation', () => {
    it('should handle null logSessionId (validation handled by framework)', async () => {
      // With typed tool factory, invalid params won't reach the logic function
      // This test now validates that the logic function works with valid empty strings
      const { fileSystem } = await createTestLogSession('', 'Log content for empty session');

      const result = await stop_sim_log_capLogic(
        {
          logSessionId: '',
        },
        fileSystem,
      );

      expect(result.isError).toBeUndefined();
      expect(result.content[0].text).toBe(
        'Log capture session  stopped successfully. Log content follows:\n\nLog content for empty session',
      );
    });

    it('should handle undefined logSessionId (validation handled by framework)', async () => {
      // With typed tool factory, invalid params won't reach the logic function
      // This test now validates that the logic function works with valid empty strings
      const { fileSystem } = await createTestLogSession('', 'Log content for empty session');

      const result = await stop_sim_log_capLogic(
        {
          logSessionId: '',
        },
        fileSystem,
      );

      expect(result.isError).toBeUndefined();
      expect(result.content[0].text).toBe(
        'Log capture session  stopped successfully. Log content follows:\n\nLog content for empty session',
      );
    });

    it('should handle empty string logSessionId', async () => {
      const { fileSystem } = await createTestLogSession('', 'Log content for empty session');

      const result = await stop_sim_log_capLogic(
        {
          logSessionId: '',
        },
        fileSystem,
      );

      expect(result.isError).toBeUndefined();
      expect(result.content[0].text).toBe(
        'Log capture session  stopped successfully. Log content follows:\n\nLog content for empty session',
      );
    });
  });

  describe('Function Call Generation', () => {
    it('should call stopLogCapture with correct parameters', async () => {
      const { fileSystem } = await createTestLogSession(
        'test-session-id',
        'Mock log content from file',
      );

      const result = await stop_sim_log_capLogic(
        {
          logSessionId: 'test-session-id',
        },
        fileSystem,
      );

      expect(result.isError).toBeUndefined();
      expect(result.content[0].text).toBe(
        'Log capture session test-session-id stopped successfully. Log content follows:\n\nMock log content from file',
      );
    });

    it('should call stopLogCapture with different session ID', async () => {
      const { fileSystem } = await createTestLogSession(
        'different-session-id',
        'Different log content',
      );

      const result = await stop_sim_log_capLogic(
        {
          logSessionId: 'different-session-id',
        },
        fileSystem,
      );

      expect(result.isError).toBeUndefined();
      expect(result.content[0].text).toBe(
        'Log capture session different-session-id stopped successfully. Log content follows:\n\nDifferent log content',
      );
    });
  });

  describe('Response Processing', () => {
    it('should handle successful log capture stop', async () => {
      const { fileSystem } = await createTestLogSession(
        'test-session-id',
        'Mock log content from file',
      );

      const result = await stop_sim_log_capLogic(
        {
          logSessionId: 'test-session-id',
        },
        fileSystem,
      );

      expect(result.isError).toBeUndefined();
      expect(result.content[0].text).toBe(
        'Log capture session test-session-id stopped successfully. Log content follows:\n\nMock log content from file',
      );
    });

    it('should handle empty log content', async () => {
      const { fileSystem } = await createTestLogSession('test-session-id', '');

      const result = await stop_sim_log_capLogic(
        {
          logSessionId: 'test-session-id',
        },
        fileSystem,
      );

      expect(result.isError).toBeUndefined();
      expect(result.content[0].text).toBe(
        'Log capture session test-session-id stopped successfully. Log content follows:\n\n',
      );
    });

    it('should handle multiline log content', async () => {
      const { fileSystem } = await createTestLogSession(
        'test-session-id',
        'Line 1\nLine 2\nLine 3',
      );

      const result = await stop_sim_log_capLogic(
        {
          logSessionId: 'test-session-id',
        },
        fileSystem,
      );

      expect(result.isError).toBeUndefined();
      expect(result.content[0].text).toBe(
        'Log capture session test-session-id stopped successfully. Log content follows:\n\nLine 1\nLine 2\nLine 3',
      );
    });

    it('should handle log capture stop errors for non-existent session', async () => {
      const result = await stop_sim_log_capLogic(
        {
          logSessionId: 'non-existent-session',
        },
        createMockFileSystemExecutor(),
      );

      expect(result.isError).toBe(true);
      expect(result.content[0].text).toBe(
        'Error stopping log capture session non-existent-session: Log capture session not found: non-existent-session',
      );
    });

    it('should handle file read errors', async () => {
      // Create session but make file reading fail in the log_capture utility
      const mockProcess = {
        pid: 12345,
        killed: false,
        exitCode: null,
        kill: () => {},
      };

      activeLogSessions.set('test-session-id', {
        processes: [mockProcess as any],
        logFilePath: `/tmp/test_file_not_found.log`,
        simulatorUuid: 'test-simulator-uuid',
        bundleId: 'com.example.TestApp',
      });

      const result = await stop_sim_log_capLogic(
        { logSessionId: 'test-session-id' },
        createMockFileSystemExecutor({
          existsSync: () => false,
        }),
      );

      expect(result.isError).toBe(true);
      expect(result.content[0].text).toContain(
        'Error stopping log capture session test-session-id:',
      );
    });

    it('should handle permission errors', async () => {
      // Create session but make file reading fail in the log_capture utility
      const mockProcess = {
        pid: 12345,
        killed: false,
        exitCode: null,
        kill: () => {},
      };

      activeLogSessions.set('test-session-id', {
        processes: [mockProcess as any],
        logFilePath: `/tmp/test_permission_denied.log`,
        simulatorUuid: 'test-simulator-uuid',
        bundleId: 'com.example.TestApp',
      });

      const result = await stop_sim_log_capLogic(
        { logSessionId: 'test-session-id' },
        createMockFileSystemExecutor({
          existsSync: () => true,
          readFile: async () => {
            throw new Error('Permission denied');
          },
        }),
      );

      expect(result.isError).toBe(true);
      expect(result.content[0].text).toContain(
        'Error stopping log capture session test-session-id:',
      );
    });

    it('should handle various error types', async () => {
      // Create session but make file reading fail in the log_capture utility
      const mockProcess = {
        pid: 12345,
        killed: false,
        exitCode: null,
        kill: () => {},
      };

      activeLogSessions.set('test-session-id', {
        processes: [mockProcess as any],
        logFilePath: `/tmp/test_generic_error.log`,
        simulatorUuid: 'test-simulator-uuid',
        bundleId: 'com.example.TestApp',
      });

      const result = await stop_sim_log_capLogic(
        { logSessionId: 'test-session-id' },
        createMockFileSystemExecutor({
          existsSync: () => true,
          readFile: async () => {
            throw new Error('Something went wrong');
          },
        }),
      );

      expect(result.isError).toBe(true);
      expect(result.content[0].text).toContain(
        'Error stopping log capture session test-session-id:',
      );
    });
  });
});

```

--------------------------------------------------------------------------------
/src/mcp/tools/project-discovery/__tests__/discover_projs.test.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Pure dependency injection test for discover_projs plugin
 *
 * Tests the plugin structure and project discovery functionality
 * including parameter validation, file system operations, and response formatting.
 *
 * Uses createMockFileSystemExecutor for file system operations.
 */

import { describe, it, expect, beforeEach } from 'vitest';
import * as z from 'zod';
import plugin, { discover_projsLogic } from '../discover_projs.ts';
import { createMockFileSystemExecutor } from '../../../../test-utils/mock-executors.ts';

describe('discover_projs plugin', () => {
  let mockFileSystemExecutor: any;

  // Create mock file system executor
  mockFileSystemExecutor = createMockFileSystemExecutor({
    stat: async () => ({ isDirectory: () => true, mtimeMs: 0 }),
    readdir: async () => [],
  });

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

    it('should have correct description', () => {
      expect(plugin.description).toBe(
        'Scans a directory (defaults to workspace root) to find Xcode project (.xcodeproj) and workspace (.xcworkspace) files.',
      );
    });

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

    it('should validate schema with valid inputs', () => {
      const schema = z.object(plugin.schema);
      expect(schema.safeParse({ workspaceRoot: '/path/to/workspace' }).success).toBe(true);
      expect(
        schema.safeParse({ workspaceRoot: '/path/to/workspace', scanPath: 'subdir' }).success,
      ).toBe(true);
      expect(schema.safeParse({ workspaceRoot: '/path/to/workspace', maxDepth: 3 }).success).toBe(
        true,
      );
      expect(
        schema.safeParse({
          workspaceRoot: '/path/to/workspace',
          scanPath: 'subdir',
          maxDepth: 5,
        }).success,
      ).toBe(true);
    });

    it('should validate schema with invalid inputs', () => {
      const schema = z.object(plugin.schema);
      expect(schema.safeParse({}).success).toBe(false);
      expect(schema.safeParse({ workspaceRoot: 123 }).success).toBe(false);
      expect(schema.safeParse({ workspaceRoot: '/path', scanPath: 123 }).success).toBe(false);
      expect(schema.safeParse({ workspaceRoot: '/path', maxDepth: 'invalid' }).success).toBe(false);
      expect(schema.safeParse({ workspaceRoot: '/path', maxDepth: -1 }).success).toBe(false);
      expect(schema.safeParse({ workspaceRoot: '/path', maxDepth: 1.5 }).success).toBe(false);
    });
  });

  describe('Handler Behavior (Complete Literal Returns)', () => {
    it('should handle workspaceRoot parameter correctly when provided', async () => {
      mockFileSystemExecutor.stat = async () => ({ isDirectory: () => true, mtimeMs: 0 });
      mockFileSystemExecutor.readdir = async () => [];

      const result = await discover_projsLogic(
        { workspaceRoot: '/workspace' },
        mockFileSystemExecutor,
      );

      expect(result).toEqual({
        content: [{ type: 'text', text: 'Discovery finished. Found 0 projects and 0 workspaces.' }],
        isError: false,
      });
    });

    it('should return error when scan path does not exist', async () => {
      mockFileSystemExecutor.stat = async () => {
        throw new Error('ENOENT: no such file or directory');
      };

      const result = await discover_projsLogic(
        {
          workspaceRoot: '/workspace',
          scanPath: '.',
          maxDepth: 5,
        },
        mockFileSystemExecutor,
      );

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: 'Failed to access scan path: /workspace. Error: ENOENT: no such file or directory',
          },
        ],
        isError: true,
      });
    });

    it('should return error when scan path is not a directory', async () => {
      mockFileSystemExecutor.stat = async () => ({ isDirectory: () => false, mtimeMs: 0 });

      const result = await discover_projsLogic(
        {
          workspaceRoot: '/workspace',
          scanPath: '.',
          maxDepth: 5,
        },
        mockFileSystemExecutor,
      );

      expect(result).toEqual({
        content: [{ type: 'text', text: 'Scan path is not a directory: /workspace' }],
        isError: true,
      });
    });

    it('should return success with no projects found', async () => {
      mockFileSystemExecutor.stat = async () => ({ isDirectory: () => true, mtimeMs: 0 });
      mockFileSystemExecutor.readdir = async () => [];

      const result = await discover_projsLogic(
        {
          workspaceRoot: '/workspace',
          scanPath: '.',
          maxDepth: 5,
        },
        mockFileSystemExecutor,
      );

      expect(result).toEqual({
        content: [{ type: 'text', text: 'Discovery finished. Found 0 projects and 0 workspaces.' }],
        isError: false,
      });
    });

    it('should return success with projects found', async () => {
      mockFileSystemExecutor.stat = async () => ({ isDirectory: () => true, mtimeMs: 0 });
      mockFileSystemExecutor.readdir = async () => [
        { name: 'MyApp.xcodeproj', isDirectory: () => true, isSymbolicLink: () => false },
        { name: 'MyWorkspace.xcworkspace', isDirectory: () => true, isSymbolicLink: () => false },
      ];

      const result = await discover_projsLogic(
        {
          workspaceRoot: '/workspace',
          scanPath: '.',
          maxDepth: 5,
        },
        mockFileSystemExecutor,
      );

      expect(result).toEqual({
        content: [
          { type: 'text', text: 'Discovery finished. Found 1 projects and 1 workspaces.' },
          { type: 'text', text: 'Projects found:\n - /workspace/MyApp.xcodeproj' },
          { type: 'text', text: 'Workspaces found:\n - /workspace/MyWorkspace.xcworkspace' },
          {
            type: 'text',
            text: "Hint: Save a default with session-set-defaults { projectPath: '...' } or { workspacePath: '...' }.",
          },
        ],
        isError: false,
      });
    });

    it('should handle fs error with code', async () => {
      const error = new Error('Permission denied');
      (error as any).code = 'EACCES';
      mockFileSystemExecutor.stat = async () => {
        throw error;
      };

      const result = await discover_projsLogic(
        {
          workspaceRoot: '/workspace',
          scanPath: '.',
          maxDepth: 5,
        },
        mockFileSystemExecutor,
      );

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: 'Failed to access scan path: /workspace. Error: Permission denied',
          },
        ],
        isError: true,
      });
    });

    it('should handle string error', async () => {
      mockFileSystemExecutor.stat = async () => {
        throw 'String error';
      };

      const result = await discover_projsLogic(
        {
          workspaceRoot: '/workspace',
          scanPath: '.',
          maxDepth: 5,
        },
        mockFileSystemExecutor,
      );

      expect(result).toEqual({
        content: [
          { type: 'text', text: 'Failed to access scan path: /workspace. Error: String error' },
        ],
        isError: true,
      });
    });

    it('should handle workspaceRoot parameter correctly', async () => {
      mockFileSystemExecutor.stat = async () => ({ isDirectory: () => true, mtimeMs: 0 });
      mockFileSystemExecutor.readdir = async () => [];

      const result = await discover_projsLogic(
        {
          workspaceRoot: '/workspace',
        },
        mockFileSystemExecutor,
      );

      expect(result).toEqual({
        content: [{ type: 'text', text: 'Discovery finished. Found 0 projects and 0 workspaces.' }],
        isError: false,
      });
    });

    it('should handle scan path outside workspace root', async () => {
      // Mock path normalization to simulate path outside workspace root
      mockFileSystemExecutor.stat = async () => ({ isDirectory: () => true, mtimeMs: 0 });
      mockFileSystemExecutor.readdir = async () => [];

      const result = await discover_projsLogic(
        {
          workspaceRoot: '/workspace',
          scanPath: '../outside',
          maxDepth: 5,
        },
        mockFileSystemExecutor,
      );

      expect(result).toEqual({
        content: [{ type: 'text', text: 'Discovery finished. Found 0 projects and 0 workspaces.' }],
        isError: false,
      });
    });

    it('should handle error with object containing message and code properties', async () => {
      const errorObject = {
        message: 'Access denied',
        code: 'EACCES',
      };
      mockFileSystemExecutor.stat = async () => {
        throw errorObject;
      };

      const result = await discover_projsLogic(
        {
          workspaceRoot: '/workspace',
          scanPath: '.',
          maxDepth: 5,
        },
        mockFileSystemExecutor,
      );

      expect(result).toEqual({
        content: [
          { type: 'text', text: 'Failed to access scan path: /workspace. Error: Access denied' },
        ],
        isError: true,
      });
    });

    it('should handle max depth reached during recursive scan', async () => {
      let readdirCallCount = 0;

      mockFileSystemExecutor.stat = async () => ({ isDirectory: () => true, mtimeMs: 0 });
      mockFileSystemExecutor.readdir = async () => {
        readdirCallCount++;
        if (readdirCallCount <= 3) {
          return [
            {
              name: `subdir${readdirCallCount}`,
              isDirectory: () => true,
              isSymbolicLink: () => false,
            },
          ];
        }
        return [];
      };

      const result = await discover_projsLogic(
        {
          workspaceRoot: '/workspace',
          scanPath: '.',
          maxDepth: 3,
        },
        mockFileSystemExecutor,
      );

      expect(result).toEqual({
        content: [{ type: 'text', text: 'Discovery finished. Found 0 projects and 0 workspaces.' }],
        isError: false,
      });
    });

    it('should handle skipped directory types during scan', async () => {
      mockFileSystemExecutor.stat = async () => ({ isDirectory: () => true, mtimeMs: 0 });
      mockFileSystemExecutor.readdir = async () => [
        { name: 'build', isDirectory: () => true, isSymbolicLink: () => false },
        { name: 'DerivedData', isDirectory: () => true, isSymbolicLink: () => false },
        { name: 'symlink', isDirectory: () => true, isSymbolicLink: () => true },
        { name: 'regular.txt', isDirectory: () => false, isSymbolicLink: () => false },
      ];

      const result = await discover_projsLogic(
        {
          workspaceRoot: '/workspace',
          scanPath: '.',
          maxDepth: 5,
        },
        mockFileSystemExecutor,
      );

      // Test that skipped directories and files are correctly filtered out
      expect(result).toEqual({
        content: [{ type: 'text', text: 'Discovery finished. Found 0 projects and 0 workspaces.' }],
        isError: false,
      });
    });

    it('should handle error during recursive directory reading', async () => {
      mockFileSystemExecutor.stat = async () => ({ isDirectory: () => true, mtimeMs: 0 });
      mockFileSystemExecutor.readdir = async () => {
        const readError = new Error('Permission denied');
        (readError as any).code = 'EACCES';
        throw readError;
      };

      const result = await discover_projsLogic(
        {
          workspaceRoot: '/workspace',
          scanPath: '.',
          maxDepth: 5,
        },
        mockFileSystemExecutor,
      );

      // The function should handle the error gracefully and continue
      expect(result).toEqual({
        content: [{ type: 'text', text: 'Discovery finished. Found 0 projects and 0 workspaces.' }],
        isError: false,
      });
    });
  });
});

```

--------------------------------------------------------------------------------
/src/mcp/tools/simulator/get_sim_app_path.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Simulator Get App Path Plugin: Get Simulator App Path (Unified)
 *
 * Gets the app bundle path for a simulator by UUID or name using either a project or workspace file.
 * Accepts mutually exclusive `projectPath` or `workspacePath`.
 * Accepts mutually exclusive `simulatorId` or `simulatorName`.
 */

import * as z from 'zod';
import { log } from '../../../utils/logging/index.ts';
import { createTextResponse } from '../../../utils/responses/index.ts';
import type { CommandExecutor } from '../../../utils/execution/index.ts';
import { getDefaultCommandExecutor } from '../../../utils/execution/index.ts';
import { ToolResponse } from '../../../types/common.ts';
import {
  createSessionAwareTool,
  getSessionAwareToolSchemaShape,
} from '../../../utils/typed-tool-factory.ts';
import { nullifyEmptyStrings } from '../../../utils/schema-helpers.ts';

const XcodePlatform = {
  macOS: 'macOS',
  iOS: 'iOS',
  iOSSimulator: 'iOS Simulator',
  watchOS: 'watchOS',
  watchOSSimulator: 'watchOS Simulator',
  tvOS: 'tvOS',
  tvOSSimulator: 'tvOS Simulator',
  visionOS: 'visionOS',
  visionOSSimulator: 'visionOS Simulator',
};

function constructDestinationString(
  platform: string,
  simulatorName: string,
  simulatorId: string,
  useLatest: boolean = true,
  arch?: string,
): string {
  const isSimulatorPlatform = [
    XcodePlatform.iOSSimulator,
    XcodePlatform.watchOSSimulator,
    XcodePlatform.tvOSSimulator,
    XcodePlatform.visionOSSimulator,
  ].includes(platform);

  // If ID is provided for a simulator, it takes precedence and uniquely identifies it.
  if (isSimulatorPlatform && simulatorId) {
    return `platform=${platform},id=${simulatorId}`;
  }

  // If name is provided for a simulator
  if (isSimulatorPlatform && simulatorName) {
    return `platform=${platform},name=${simulatorName}${useLatest ? ',OS=latest' : ''}`;
  }

  // If it's a simulator platform but neither ID nor name is provided (should be prevented by callers now)
  if (isSimulatorPlatform && !simulatorId && !simulatorName) {
    log(
      'warning',
      `Constructing generic destination for ${platform} without name or ID. This might not be specific enough.`,
    );
    throw new Error(`Simulator name or ID is required for specific ${platform} operations`);
  }

  // Handle non-simulator platforms
  switch (platform) {
    case XcodePlatform.macOS:
      return arch ? `platform=macOS,arch=${arch}` : 'platform=macOS';
    case XcodePlatform.iOS:
      return 'generic/platform=iOS';
    case XcodePlatform.watchOS:
      return 'generic/platform=watchOS';
    case XcodePlatform.tvOS:
      return 'generic/platform=tvOS';
    case XcodePlatform.visionOS:
      return 'generic/platform=visionOS';
  }
  // Fallback just in case (shouldn't be reached with enum)
  log('error', `Reached unexpected point in constructDestinationString for platform: ${platform}`);
  return `platform=${platform}`;
}

// Define base schema
const baseGetSimulatorAppPathSchema = z.object({
  projectPath: z
    .string()
    .optional()
    .describe('Path to .xcodeproj file. Provide EITHER this OR workspacePath, not both'),
  workspacePath: z
    .string()
    .optional()
    .describe('Path to .xcworkspace file. Provide EITHER this OR projectPath, not both'),
  scheme: z.string().describe('The scheme to use (Required)'),
  platform: z
    .enum(['iOS Simulator', 'watchOS Simulator', 'tvOS Simulator', 'visionOS Simulator'])
    .describe('Target simulator platform (Required)'),
  simulatorId: z
    .string()
    .optional()
    .describe(
      'UUID of the simulator (from list_sims). Provide EITHER this OR simulatorName, not both',
    ),
  simulatorName: z
    .string()
    .optional()
    .describe(
      "Name of the simulator (e.g., 'iPhone 16'). Provide EITHER this OR simulatorId, not both",
    ),
  configuration: z.string().optional().describe('Build configuration (Debug, Release, etc.)'),
  useLatestOS: z
    .boolean()
    .optional()
    .describe('Whether to use the latest OS version for the named simulator'),
  arch: z.string().optional().describe('Optional architecture'),
});

// Add XOR validation with preprocessing
const getSimulatorAppPathSchema = z.preprocess(
  nullifyEmptyStrings,
  baseGetSimulatorAppPathSchema
    .refine((val) => val.projectPath !== undefined || val.workspacePath !== undefined, {
      message: 'Either projectPath or workspacePath is required.',
    })
    .refine((val) => !(val.projectPath !== undefined && val.workspacePath !== undefined), {
      message: 'projectPath and workspacePath are mutually exclusive. Provide only one.',
    })
    .refine((val) => val.simulatorId !== undefined || val.simulatorName !== undefined, {
      message: 'Either simulatorId or simulatorName is required.',
    })
    .refine((val) => !(val.simulatorId !== undefined && val.simulatorName !== undefined), {
      message: 'simulatorId and simulatorName are mutually exclusive. Provide only one.',
    }),
);

// Use z.infer for type safety
type GetSimulatorAppPathParams = z.infer<typeof getSimulatorAppPathSchema>;

/**
 * Exported business logic function for getting app path
 */
export async function get_sim_app_pathLogic(
  params: GetSimulatorAppPathParams,
  executor: CommandExecutor,
): Promise<ToolResponse> {
  // Set defaults - Zod validation already ensures required params are present
  const projectPath = params.projectPath;
  const workspacePath = params.workspacePath;
  const scheme = params.scheme;
  const platform = params.platform;
  const simulatorId = params.simulatorId;
  const simulatorName = params.simulatorName;
  const configuration = params.configuration ?? 'Debug';
  const useLatestOS = params.useLatestOS ?? true;
  const arch = params.arch;

  // Log warning if useLatestOS is provided with simulatorId
  if (simulatorId && params.useLatestOS !== undefined) {
    log(
      'warning',
      `useLatestOS parameter is ignored when using simulatorId (UUID implies exact device/OS)`,
    );
  }

  log('info', `Getting app path for scheme ${scheme} on platform ${platform}`);

  try {
    // Create the command array for xcodebuild with -showBuildSettings option
    const command = ['xcodebuild', '-showBuildSettings'];

    // Add the workspace or project (XOR validation ensures exactly one is provided)
    if (workspacePath) {
      command.push('-workspace', workspacePath);
    } else if (projectPath) {
      command.push('-project', projectPath);
    }

    // Add the scheme and configuration
    command.push('-scheme', scheme);
    command.push('-configuration', configuration);

    // Handle destination based on platform
    const isSimulatorPlatform = [
      XcodePlatform.iOSSimulator,
      XcodePlatform.watchOSSimulator,
      XcodePlatform.tvOSSimulator,
      XcodePlatform.visionOSSimulator,
    ].includes(platform);

    let destinationString = '';

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

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

    // Execute the command directly
    const result = await executor(command, 'Get App Path', true, undefined);

    if (!result.success) {
      return createTextResponse(`Failed to get app path: ${result.error}`, true);
    }

    if (!result.output) {
      return createTextResponse('Failed to extract build settings output from the result.', true);
    }

    const buildSettingsOutput = result.output;
    const builtProductsDirMatch = buildSettingsOutput.match(/^\s*BUILT_PRODUCTS_DIR\s*=\s*(.+)$/m);
    const fullProductNameMatch = buildSettingsOutput.match(/^\s*FULL_PRODUCT_NAME\s*=\s*(.+)$/m);

    if (!builtProductsDirMatch || !fullProductNameMatch) {
      return createTextResponse(
        'Failed to extract app path from build settings. Make sure the app has been built first.',
        true,
      );
    }

    const builtProductsDir = builtProductsDirMatch[1].trim();
    const fullProductName = fullProductNameMatch[1].trim();
    const appPath = `${builtProductsDir}/${fullProductName}`;

    let nextStepsText = '';
    if (platform === XcodePlatform.macOS) {
      nextStepsText = `Next Steps:
1. Get bundle ID: get_mac_bundle_id({ appPath: "${appPath}" })
2. Launch the app: launch_mac_app({ appPath: "${appPath}" })`;
    } else if (isSimulatorPlatform) {
      nextStepsText = `Next Steps:
1. Get bundle ID: get_app_bundle_id({ appPath: "${appPath}" })
2. Boot simulator: boot_sim({ simulatorId: "SIMULATOR_UUID" })
3. Install app: install_app_sim({ simulatorId: "SIMULATOR_UUID", appPath: "${appPath}" })
4. Launch app: launch_app_sim({ simulatorId: "SIMULATOR_UUID", bundleId: "BUNDLE_ID" })`;
    } else if (
      [
        XcodePlatform.iOS,
        XcodePlatform.watchOS,
        XcodePlatform.tvOS,
        XcodePlatform.visionOS,
      ].includes(platform)
    ) {
      nextStepsText = `Next Steps:
1. Get bundle ID: get_app_bundle_id({ appPath: "${appPath}" })
2. Install app on device: install_app_device({ deviceId: "DEVICE_UDID", appPath: "${appPath}" })
3. Launch app on device: launch_app_device({ deviceId: "DEVICE_UDID", bundleId: "BUNDLE_ID" })`;
    } else {
      // For other platforms
      nextStepsText = `Next Steps:
1. The app has been built for ${platform}
2. Use platform-specific deployment tools to install and run the app`;
    }

    return {
      content: [
        {
          type: 'text',
          text: `✅ App path retrieved successfully: ${appPath}`,
        },
        {
          type: 'text',
          text: nextStepsText,
        },
      ],
      isError: false,
    };
  } catch (error) {
    const errorMessage = error instanceof Error ? error.message : String(error);
    log('error', `Error retrieving app path: ${errorMessage}`);
    return createTextResponse(`Error retrieving app path: ${errorMessage}`, true);
  }
}

const publicSchemaObject = baseGetSimulatorAppPathSchema.omit({
  projectPath: true,
  workspacePath: true,
  scheme: true,
  simulatorId: true,
  simulatorName: true,
  configuration: true,
  useLatestOS: true,
  arch: true,
} as const);

export default {
  name: 'get_sim_app_path',
  description: 'Retrieves the built app path for an iOS simulator.',
  schema: getSessionAwareToolSchemaShape({
    sessionAware: publicSchemaObject,
    legacy: baseGetSimulatorAppPathSchema,
  }),
  annotations: {
    title: 'Get Simulator App Path',
    readOnlyHint: true,
  },
  handler: createSessionAwareTool<GetSimulatorAppPathParams>({
    internalSchema: getSimulatorAppPathSchema as unknown as z.ZodType<GetSimulatorAppPathParams>,
    logicFunction: get_sim_app_pathLogic,
    getExecutor: getDefaultCommandExecutor,
    requirements: [
      { allOf: ['scheme'], message: 'scheme is required' },
      { oneOf: ['projectPath', 'workspacePath'], message: 'Provide a project or workspace' },
      { oneOf: ['simulatorId', 'simulatorName'], message: 'Provide simulatorId or simulatorName' },
    ],
    exclusivePairs: [
      ['projectPath', 'workspacePath'],
      ['simulatorId', 'simulatorName'],
    ],
  }),
};

```

--------------------------------------------------------------------------------
/src/mcp/tools/simulator/__tests__/launch_app_sim.test.ts:
--------------------------------------------------------------------------------

```typescript
import { describe, it, expect, beforeEach } from 'vitest';
import * as z from 'zod';
import { createMockExecutor } from '../../../../test-utils/mock-executors.ts';
import { sessionStore } from '../../../../utils/session-store.ts';
import launchAppSim, { launch_app_simLogic } from '../launch_app_sim.ts';

describe('launch_app_sim tool', () => {
  beforeEach(() => {
    sessionStore.clear();
  });

  describe('Export Field Validation (Literal)', () => {
    it('should expose correct name and description', () => {
      expect(launchAppSim.name).toBe('launch_app_sim');
      expect(launchAppSim.description).toBe('Launches an app in an iOS simulator.');
    });

    it('should expose only non-session fields in public schema', () => {
      const schema = z.object(launchAppSim.schema);

      expect(
        schema.safeParse({
          bundleId: 'com.example.testapp',
        }).success,
      ).toBe(true);

      expect(
        schema.safeParse({
          bundleId: 'com.example.testapp',
          args: ['--debug'],
        }).success,
      ).toBe(true);

      expect(schema.safeParse({}).success).toBe(false);
      expect(schema.safeParse({ bundleId: 123 }).success).toBe(false);
      expect(schema.safeParse({ args: ['--debug'] }).success).toBe(false);

      expect(Object.keys(launchAppSim.schema).sort()).toEqual(['args', 'bundleId'].sort());

      const withSimDefaults = schema.safeParse({
        simulatorId: 'sim-default',
        simulatorName: 'iPhone 16',
        bundleId: 'com.example.testapp',
      });
      expect(withSimDefaults.success).toBe(true);
      const parsed = withSimDefaults.data as Record<string, unknown>;
      expect(parsed.simulatorId).toBeUndefined();
      expect(parsed.simulatorName).toBeUndefined();
    });
  });

  describe('Handler Requirements', () => {
    it('should require simulator identifier when not provided', async () => {
      const result = await launchAppSim.handler({ bundleId: 'com.example.testapp' });

      expect(result.isError).toBe(true);
      expect(result.content[0].text).toContain('Missing required session defaults');
      expect(result.content[0].text).toContain('Provide simulatorId or simulatorName');
      expect(result.content[0].text).toContain('session-set-defaults');
    });

    it('should validate bundleId when simulatorId default exists', async () => {
      sessionStore.setDefaults({ simulatorId: 'SIM-UUID' });

      const result = await launchAppSim.handler({});

      expect(result.isError).toBe(true);
      expect(result.content[0].text).toContain('Parameter validation failed');
      expect(result.content[0].text).toContain(
        'bundleId: Invalid input: expected string, received undefined',
      );
    });

    it('should reject when both simulatorId and simulatorName provided explicitly', async () => {
      const result = await launchAppSim.handler({
        simulatorId: 'SIM-UUID',
        simulatorName: 'iPhone 16',
        bundleId: 'com.example.testapp',
      });

      expect(result.isError).toBe(true);
      expect(result.content[0].text).toContain('Mutually exclusive parameters provided');
      expect(result.content[0].text).toContain('simulatorId');
      expect(result.content[0].text).toContain('simulatorName');
    });
  });

  describe('Logic Behavior (Literal Returns)', () => {
    it('should launch app successfully with simulatorId', async () => {
      let callCount = 0;
      const sequencedExecutor = async (command: string[]) => {
        callCount++;
        if (callCount === 1) {
          return {
            success: true,
            output: '/path/to/app/container',
            error: '',
            process: {} as any,
          };
        }
        return {
          success: true,
          output: 'App launched successfully',
          error: '',
          process: {} as any,
        };
      };

      const result = await launch_app_simLogic(
        {
          simulatorId: 'test-uuid-123',
          bundleId: 'com.example.testapp',
        },
        sequencedExecutor,
      );

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: `✅ App launched successfully in simulator test-uuid-123.\n\nNext Steps:\n1. To see simulator: open_sim()\n2. Log capture: start_sim_log_cap({ simulatorId: "test-uuid-123", bundleId: "com.example.testapp" })\n   With console: start_sim_log_cap({ simulatorId: "test-uuid-123", bundleId: "com.example.testapp", captureConsole: true })\n3. Stop logs: stop_sim_log_cap({ logSessionId: 'SESSION_ID' })`,
          },
        ],
      });
    });

    it('should append additional arguments when provided', async () => {
      let callCount = 0;
      const commands: string[][] = [];

      const sequencedExecutor = async (command: string[]) => {
        callCount++;
        commands.push(command);
        if (callCount === 1) {
          return {
            success: true,
            output: '/path/to/app/container',
            error: '',
            process: {} as any,
          };
        }
        return {
          success: true,
          output: 'App launched successfully',
          error: '',
          process: {} as any,
        };
      };

      await launch_app_simLogic(
        {
          simulatorId: 'test-uuid-123',
          bundleId: 'com.example.testapp',
          args: ['--debug', '--verbose'],
        },
        sequencedExecutor,
      );

      expect(commands).toEqual([
        ['xcrun', 'simctl', 'get_app_container', 'test-uuid-123', 'com.example.testapp', 'app'],
        [
          'xcrun',
          'simctl',
          'launch',
          'test-uuid-123',
          'com.example.testapp',
          '--debug',
          '--verbose',
        ],
      ]);
    });

    it('should surface error when simulatorId missing after lookup', async () => {
      const result = await launch_app_simLogic(
        {
          simulatorId: undefined,
          bundleId: 'com.example.testapp',
        } as any,
        async () => ({
          success: true,
          output: '',
          error: '',
          process: {} as any,
        }),
      );

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: 'No simulator identifier provided',
          },
        ],
        isError: true,
      });
    });

    it('should detect missing app container on install check', async () => {
      const mockExecutor = async (command: string[]) => {
        if (command.includes('get_app_container')) {
          return {
            success: false,
            output: '',
            error: 'App container not found',
            process: {} as any,
          };
        }
        return {
          success: true,
          output: '',
          error: '',
          process: {} as any,
        };
      };

      const result = await launch_app_simLogic(
        {
          simulatorId: 'test-uuid-123',
          bundleId: 'com.example.testapp',
        },
        mockExecutor,
      );

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: `App is not installed on the simulator. Please use install_app_sim before launching.\n\nWorkflow: build → install → launch.`,
          },
        ],
        isError: true,
      });
    });

    it('should return error when install check throws', async () => {
      const mockExecutor = async (command: string[]) => {
        if (command.includes('get_app_container')) {
          throw new Error('Simctl command failed');
        }
        return {
          success: true,
          output: '',
          error: '',
          process: {} as any,
        };
      };

      const result = await launch_app_simLogic(
        {
          simulatorId: 'test-uuid-123',
          bundleId: 'com.example.testapp',
        },
        mockExecutor,
      );

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: `App is not installed on the simulator (check failed). Please use install_app_sim before launching.\n\nWorkflow: build → install → launch.`,
          },
        ],
        isError: true,
      });
    });

    it('should handle launch failure', async () => {
      let callCount = 0;
      const mockExecutor = async (command: string[]) => {
        callCount++;
        if (callCount === 1) {
          return {
            success: true,
            output: '/path/to/app/container',
            error: '',
            process: {} as any,
          };
        }
        return {
          success: false,
          output: '',
          error: 'Launch failed',
          process: {} as any,
        };
      };

      const result = await launch_app_simLogic(
        {
          simulatorId: 'test-uuid-123',
          bundleId: 'com.example.testapp',
        },
        mockExecutor,
      );

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: 'Launch app in simulator operation failed: Launch failed',
          },
        ],
      });
    });

    it('should launch using simulatorName by resolving UUID', async () => {
      let callCount = 0;
      const sequencedExecutor = async (command: string[]) => {
        callCount++;
        if (callCount === 1) {
          return {
            success: true,
            output: JSON.stringify({
              devices: {
                'iOS 17.0': [
                  {
                    name: 'iPhone 16',
                    udid: 'resolved-uuid',
                    isAvailable: true,
                    state: 'Shutdown',
                  },
                ],
              },
            }),
            error: '',
            process: {} as any,
          };
        }
        if (callCount === 2) {
          return {
            success: true,
            output: '/path/to/app/container',
            error: '',
            process: {} as any,
          };
        }
        return {
          success: true,
          output: 'App launched successfully',
          error: '',
          process: {} as any,
        };
      };

      const result = await launch_app_simLogic(
        {
          simulatorName: 'iPhone 16',
          bundleId: 'com.example.testapp',
        },
        sequencedExecutor,
      );

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: `✅ App launched successfully in simulator "iPhone 16" (resolved-uuid).\n\nNext Steps:\n1. To see simulator: open_sim()\n2. Log capture: start_sim_log_cap({ simulatorName: "iPhone 16", bundleId: "com.example.testapp" })\n   With console: start_sim_log_cap({ simulatorName: "iPhone 16", bundleId: "com.example.testapp", captureConsole: true })\n3. Stop logs: stop_sim_log_cap({ logSessionId: 'SESSION_ID' })`,
          },
        ],
      });
    });

    it('should return error when simulator name is not found', async () => {
      const mockListExecutor = async () => ({
        success: true,
        output: JSON.stringify({ devices: {} }),
        error: '',
        process: {} as any,
      });

      const result = await launch_app_simLogic(
        {
          simulatorName: 'Missing Simulator',
          bundleId: 'com.example.testapp',
        },
        mockListExecutor,
      );

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: 'Simulator named "Missing Simulator" not found. Use list_sims to see available simulators.',
          },
        ],
        isError: true,
      });
    });

    it('should return error when simctl list fails', async () => {
      const mockExecutor = createMockExecutor({
        success: false,
        output: '',
        error: 'simctl list failed',
      });

      const result = await launch_app_simLogic(
        {
          simulatorName: 'iPhone 16',
          bundleId: 'com.example.testapp',
        },
        mockExecutor,
      );

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: 'Failed to list simulators: simctl list failed',
          },
        ],
        isError: true,
      });
    });
  });
});

```

--------------------------------------------------------------------------------
/src/mcp/tools/macos/test_macos.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * macOS Shared Plugin: Test macOS (Unified)
 *
 * Runs tests for a macOS project or workspace using xcodebuild test and parses xcresult output.
 * Accepts mutually exclusive `projectPath` or `workspacePath`.
 */

import * as z from 'zod';
import { join } from 'path';
import { ToolResponse, XcodePlatform } from '../../../types/common.ts';
import { log } from '../../../utils/logging/index.ts';
import { executeXcodeBuildCommand } from '../../../utils/build/index.ts';
import { createTextResponse } from '../../../utils/responses/index.ts';
import { normalizeTestRunnerEnv } from '../../../utils/environment.ts';
import type {
  CommandExecutor,
  FileSystemExecutor,
  CommandExecOptions,
} from '../../../utils/execution/index.ts';
import {
  getDefaultCommandExecutor,
  getDefaultFileSystemExecutor,
} from '../../../utils/execution/index.ts';
import {
  createSessionAwareTool,
  getSessionAwareToolSchemaShape,
} from '../../../utils/typed-tool-factory.ts';
import { nullifyEmptyStrings } from '../../../utils/schema-helpers.ts';

// Unified schema: XOR between projectPath and workspacePath
const baseSchemaObject = z.object({
  projectPath: z.string().optional().describe('Path to the .xcodeproj file'),
  workspacePath: z.string().optional().describe('Path to the .xcworkspace file'),
  scheme: z.string().describe('The scheme to use'),
  configuration: z.string().optional().describe('Build configuration (Debug, Release, etc.)'),
  derivedDataPath: z
    .string()
    .optional()
    .describe('Path where build products and other derived data will go'),
  extraArgs: z.array(z.string()).optional().describe('Additional xcodebuild arguments'),
  preferXcodebuild: z
    .boolean()
    .optional()
    .describe('If true, prefers xcodebuild over the experimental incremental build system'),
  testRunnerEnv: z
    .record(z.string(), z.string())
    .optional()
    .describe(
      'Environment variables to pass to the test runner (TEST_RUNNER_ prefix added automatically)',
    ),
});

const publicSchemaObject = baseSchemaObject.omit({
  projectPath: true,
  workspacePath: true,
  scheme: true,
  configuration: true,
} as const);

const testMacosSchema = z.preprocess(
  nullifyEmptyStrings,
  baseSchemaObject
    .refine((val) => val.projectPath !== undefined || val.workspacePath !== undefined, {
      message: 'Either projectPath or workspacePath is required.',
    })
    .refine((val) => !(val.projectPath !== undefined && val.workspacePath !== undefined), {
      message: 'projectPath and workspacePath are mutually exclusive. Provide only one.',
    }),
);

export type TestMacosParams = z.infer<typeof testMacosSchema>;

/**
 * Type definition for test summary structure from xcresulttool
 * @typedef {Object} TestSummary
 * @property {string} [title]
 * @property {string} [result]
 * @property {number} [totalTestCount]
 * @property {number} [passedTests]
 * @property {number} [failedTests]
 * @property {number} [skippedTests]
 * @property {number} [expectedFailures]
 * @property {string} [environmentDescription]
 * @property {Array<Object>} [devicesAndConfigurations]
 * @property {Array<Object>} [testFailures]
 * @property {Array<Object>} [topInsights]
 */

/**
 * Parse xcresult bundle using xcrun xcresulttool
 */
async function parseXcresultBundle(
  resultBundlePath: string,
  executor: CommandExecutor = getDefaultCommandExecutor(),
): Promise<string> {
  try {
    const result = await executor(
      ['xcrun', 'xcresulttool', 'get', 'test-results', 'summary', '--path', resultBundlePath],
      'Parse xcresult bundle',
      true,
    );

    if (!result.success) {
      throw new Error(result.error ?? 'Failed to parse xcresult bundle');
    }

    // Parse JSON response and format as human-readable
    let summary: unknown;
    try {
      summary = JSON.parse(result.output || '{}');
    } catch (parseError) {
      throw new Error(`Failed to parse JSON output: ${parseError}`);
    }

    if (typeof summary !== 'object' || summary === null) {
      throw new Error('Invalid JSON output: expected object');
    }

    return formatTestSummary(summary as Record<string, unknown>);
  } catch (error) {
    const errorMessage = error instanceof Error ? error.message : String(error);
    log('error', `Error parsing xcresult bundle: ${errorMessage}`);
    throw error;
  }
}

/**
 * Format test summary JSON into human-readable text
 */
function formatTestSummary(summary: Record<string, unknown>): string {
  const lines = [];

  lines.push(`Test Summary: ${summary.title ?? 'Unknown'}`);
  lines.push(`Overall Result: ${summary.result ?? 'Unknown'}`);
  lines.push('');

  lines.push('Test Counts:');
  lines.push(`  Total: ${summary.totalTestCount ?? 0}`);
  lines.push(`  Passed: ${summary.passedTests ?? 0}`);
  lines.push(`  Failed: ${summary.failedTests ?? 0}`);
  lines.push(`  Skipped: ${summary.skippedTests ?? 0}`);
  lines.push(`  Expected Failures: ${summary.expectedFailures ?? 0}`);
  lines.push('');

  if (summary.environmentDescription) {
    lines.push(`Environment: ${summary.environmentDescription}`);
    lines.push('');
  }

  if (
    summary.devicesAndConfigurations &&
    Array.isArray(summary.devicesAndConfigurations) &&
    summary.devicesAndConfigurations.length > 0
  ) {
    const firstDeviceConfig: unknown = summary.devicesAndConfigurations[0];
    if (
      typeof firstDeviceConfig === 'object' &&
      firstDeviceConfig !== null &&
      'device' in firstDeviceConfig
    ) {
      const device: unknown = (firstDeviceConfig as Record<string, unknown>).device;
      if (typeof device === 'object' && device !== null) {
        const deviceRecord = device as Record<string, unknown>;
        const deviceName =
          'deviceName' in deviceRecord && typeof deviceRecord.deviceName === 'string'
            ? deviceRecord.deviceName
            : 'Unknown';
        const platform =
          'platform' in deviceRecord && typeof deviceRecord.platform === 'string'
            ? deviceRecord.platform
            : 'Unknown';
        const osVersion =
          'osVersion' in deviceRecord && typeof deviceRecord.osVersion === 'string'
            ? deviceRecord.osVersion
            : 'Unknown';

        lines.push(`Device: ${deviceName} (${platform} ${osVersion})`);
        lines.push('');
      }
    }
  }

  if (
    summary.testFailures &&
    Array.isArray(summary.testFailures) &&
    summary.testFailures.length > 0
  ) {
    lines.push('Test Failures:');
    summary.testFailures.forEach((failure: unknown, index: number) => {
      if (typeof failure === 'object' && failure !== null) {
        const failureRecord = failure as Record<string, unknown>;
        const testName =
          'testName' in failureRecord && typeof failureRecord.testName === 'string'
            ? failureRecord.testName
            : 'Unknown Test';
        const targetName =
          'targetName' in failureRecord && typeof failureRecord.targetName === 'string'
            ? failureRecord.targetName
            : 'Unknown Target';

        lines.push(`  ${index + 1}. ${testName} (${targetName})`);

        if ('failureText' in failureRecord && typeof failureRecord.failureText === 'string') {
          lines.push(`     ${failureRecord.failureText}`);
        }
      }
    });
    lines.push('');
  }

  if (summary.topInsights && Array.isArray(summary.topInsights) && summary.topInsights.length > 0) {
    lines.push('Insights:');
    summary.topInsights.forEach((insight: unknown, index: number) => {
      if (typeof insight === 'object' && insight !== null) {
        const insightRecord = insight as Record<string, unknown>;
        const impact =
          'impact' in insightRecord && typeof insightRecord.impact === 'string'
            ? insightRecord.impact
            : 'Unknown';
        const text =
          'text' in insightRecord && typeof insightRecord.text === 'string'
            ? insightRecord.text
            : 'No description';

        lines.push(`  ${index + 1}. [${impact}] ${text}`);
      }
    });
  }

  return lines.join('\n');
}

/**
 * Business logic for testing a macOS project or workspace.
 * Exported for direct testing and reuse.
 */
export async function testMacosLogic(
  params: TestMacosParams,
  executor: CommandExecutor = getDefaultCommandExecutor(),
  fileSystemExecutor: FileSystemExecutor = getDefaultFileSystemExecutor(),
): Promise<ToolResponse> {
  log('info', `Starting test run for scheme ${params.scheme} on platform macOS (internal)`);

  try {
    // Create temporary directory for xcresult bundle
    const tempDir = await fileSystemExecutor.mkdtemp(
      join(fileSystemExecutor.tmpdir(), 'xcodebuild-test-'),
    );
    const resultBundlePath = join(tempDir, 'TestResults.xcresult');

    // Add resultBundlePath to extraArgs
    const extraArgs = [...(params.extraArgs ?? []), `-resultBundlePath`, resultBundlePath];

    // Prepare execution options with TEST_RUNNER_ environment variables
    const execOpts: CommandExecOptions | undefined = params.testRunnerEnv
      ? { env: normalizeTestRunnerEnv(params.testRunnerEnv) }
      : undefined;

    // Run the test command
    const testResult = await executeXcodeBuildCommand(
      {
        projectPath: params.projectPath,
        workspacePath: params.workspacePath,
        scheme: params.scheme,
        configuration: params.configuration ?? 'Debug',
        derivedDataPath: params.derivedDataPath,
        extraArgs,
      },
      {
        platform: XcodePlatform.macOS,
        logPrefix: 'Test Run',
      },
      params.preferXcodebuild ?? false,
      'test',
      executor,
      execOpts,
    );

    // Parse xcresult bundle if it exists, regardless of whether tests passed or failed
    // Test failures are expected and should not prevent xcresult parsing
    try {
      log('info', `Attempting to parse xcresult bundle at: ${resultBundlePath}`);

      // Check if the file exists
      try {
        await fileSystemExecutor.stat(resultBundlePath);
        log('info', `xcresult bundle exists at: ${resultBundlePath}`);
      } catch {
        log('warn', `xcresult bundle does not exist at: ${resultBundlePath}`);
        throw new Error(`xcresult bundle not found at ${resultBundlePath}`);
      }

      const testSummary = await parseXcresultBundle(resultBundlePath, executor);
      log('info', 'Successfully parsed xcresult bundle');

      // Clean up temporary directory
      await fileSystemExecutor.rm(tempDir, { recursive: true, force: true });

      // Return combined result - preserve isError from testResult (test failures should be marked as errors)
      return {
        content: [
          ...(testResult.content ?? []),
          {
            type: 'text',
            text: '\nTest Results Summary:\n' + testSummary,
          },
        ],
        isError: testResult.isError,
      };
    } catch (parseError) {
      // If parsing fails, return original test result
      log('warn', `Failed to parse xcresult bundle: ${parseError}`);

      // Clean up temporary directory even if parsing fails
      try {
        await fileSystemExecutor.rm(tempDir, { recursive: true, force: true });
      } catch (cleanupError) {
        log('warn', `Failed to clean up temporary directory: ${cleanupError}`);
      }

      return testResult;
    }
  } catch (error) {
    const errorMessage = error instanceof Error ? error.message : String(error);
    log('error', `Error during test run: ${errorMessage}`);
    return createTextResponse(`Error during test run: ${errorMessage}`, true);
  }
}

export default {
  name: 'test_macos',
  description: 'Runs tests for a macOS target.',
  schema: getSessionAwareToolSchemaShape({
    sessionAware: publicSchemaObject,
    legacy: baseSchemaObject,
  }),
  annotations: {
    title: 'Test macOS',
    destructiveHint: true,
  },
  handler: createSessionAwareTool<TestMacosParams>({
    internalSchema: testMacosSchema as unknown as z.ZodType<TestMacosParams, unknown>,
    logicFunction: (params, executor) =>
      testMacosLogic(params, executor, getDefaultFileSystemExecutor()),
    getExecutor: getDefaultCommandExecutor,
    requirements: [
      { allOf: ['scheme'], message: 'scheme is required' },
      { oneOf: ['projectPath', 'workspacePath'], message: 'Provide a project or workspace' },
    ],
    exclusivePairs: [['projectPath', 'workspacePath']],
  }),
};

```

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

```typescript
/**
 * Tests for swift_package_run plugin
 * Following CLAUDE.md testing standards with literal validation
 * Integration tests using dependency injection for deterministic testing
 */

import { describe, it, expect, beforeEach } from 'vitest';
import * as z from 'zod';
import {
  createMockExecutor,
  createNoopExecutor,
  createMockCommandResponse,
} from '../../../../test-utils/mock-executors.ts';
import swiftPackageRun, { swift_package_runLogic } from '../swift_package_run.ts';
import type { CommandExecutor } from '../../../../utils/execution/index.ts';

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

    it('should have correct description', () => {
      expect(swiftPackageRun.description).toBe(
        'Runs an executable target from a Swift Package with swift run',
      );
    });

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

    it('should validate schema correctly', () => {
      // Test packagePath (required string)
      expect(swiftPackageRun.schema.packagePath.safeParse('valid/path').success).toBe(true);
      expect(swiftPackageRun.schema.packagePath.safeParse(null).success).toBe(false);

      // Test executableName (optional string)
      expect(swiftPackageRun.schema.executableName.safeParse('MyExecutable').success).toBe(true);
      expect(swiftPackageRun.schema.executableName.safeParse(undefined).success).toBe(true);
      expect(swiftPackageRun.schema.executableName.safeParse(123).success).toBe(false);

      // Test arguments (optional array of strings)
      expect(swiftPackageRun.schema.arguments.safeParse(['arg1', 'arg2']).success).toBe(true);
      expect(swiftPackageRun.schema.arguments.safeParse(undefined).success).toBe(true);
      expect(swiftPackageRun.schema.arguments.safeParse(['arg1', 123]).success).toBe(false);

      // Test configuration (optional enum)
      expect(swiftPackageRun.schema.configuration.safeParse('debug').success).toBe(true);
      expect(swiftPackageRun.schema.configuration.safeParse('release').success).toBe(true);
      expect(swiftPackageRun.schema.configuration.safeParse(undefined).success).toBe(true);
      expect(swiftPackageRun.schema.configuration.safeParse('invalid').success).toBe(false);

      // Test timeout (optional number)
      expect(swiftPackageRun.schema.timeout.safeParse(30).success).toBe(true);
      expect(swiftPackageRun.schema.timeout.safeParse(undefined).success).toBe(true);
      expect(swiftPackageRun.schema.timeout.safeParse('30').success).toBe(false);

      // Test background (optional boolean)
      expect(swiftPackageRun.schema.background.safeParse(true).success).toBe(true);
      expect(swiftPackageRun.schema.background.safeParse(false).success).toBe(true);
      expect(swiftPackageRun.schema.background.safeParse(undefined).success).toBe(true);
      expect(swiftPackageRun.schema.background.safeParse('true').success).toBe(false);

      // Test parseAsLibrary (optional boolean)
      expect(swiftPackageRun.schema.parseAsLibrary.safeParse(true).success).toBe(true);
      expect(swiftPackageRun.schema.parseAsLibrary.safeParse(false).success).toBe(true);
      expect(swiftPackageRun.schema.parseAsLibrary.safeParse(undefined).success).toBe(true);
      expect(swiftPackageRun.schema.parseAsLibrary.safeParse('true').success).toBe(false);
    });
  });

  let executorCalls: any[] = [];

  beforeEach(() => {
    executorCalls = [];
  });

  describe('Command Generation Testing', () => {
    it('should build correct command for basic run (foreground mode)', async () => {
      const mockExecutor: CommandExecutor = (command, logPrefix, useShell, opts) => {
        executorCalls.push({ command, logPrefix, useShell, opts });
        return Promise.resolve(
          createMockCommandResponse({
            success: true,
            output: 'Process completed',
            error: undefined,
          }),
        );
      };

      await swift_package_runLogic(
        {
          packagePath: '/test/package',
        },
        mockExecutor,
      );

      expect(executorCalls[0]).toEqual({
        command: ['swift', 'run', '--package-path', '/test/package'],
        logPrefix: 'Swift Package Run',
        useShell: true,
        opts: undefined,
      });
    });

    it('should build correct command with release configuration', async () => {
      const mockExecutor: CommandExecutor = (command, logPrefix, useShell, opts) => {
        executorCalls.push({ command, logPrefix, useShell, opts });
        return Promise.resolve(
          createMockCommandResponse({
            success: true,
            output: 'Process completed',
            error: undefined,
          }),
        );
      };

      await swift_package_runLogic(
        {
          packagePath: '/test/package',
          configuration: 'release',
        },
        mockExecutor,
      );

      expect(executorCalls[0]).toEqual({
        command: ['swift', 'run', '--package-path', '/test/package', '-c', 'release'],
        logPrefix: 'Swift Package Run',
        useShell: true,
        opts: undefined,
      });
    });

    it('should build correct command with executable name', async () => {
      const mockExecutor: CommandExecutor = (command, logPrefix, useShell, opts) => {
        executorCalls.push({ command, logPrefix, useShell, opts });
        return Promise.resolve(
          createMockCommandResponse({
            success: true,
            output: 'Process completed',
            error: undefined,
          }),
        );
      };

      await swift_package_runLogic(
        {
          packagePath: '/test/package',
          executableName: 'MyApp',
        },
        mockExecutor,
      );

      expect(executorCalls[0]).toEqual({
        command: ['swift', 'run', '--package-path', '/test/package', 'MyApp'],
        logPrefix: 'Swift Package Run',
        useShell: true,
        opts: undefined,
      });
    });

    it('should build correct command with arguments', async () => {
      const mockExecutor: CommandExecutor = (command, logPrefix, useShell, opts) => {
        executorCalls.push({ command, logPrefix, useShell, opts });
        return Promise.resolve(
          createMockCommandResponse({
            success: true,
            output: 'Process completed',
            error: undefined,
          }),
        );
      };

      await swift_package_runLogic(
        {
          packagePath: '/test/package',
          arguments: ['arg1', 'arg2'],
        },
        mockExecutor,
      );

      expect(executorCalls[0]).toEqual({
        command: ['swift', 'run', '--package-path', '/test/package', '--', 'arg1', 'arg2'],
        logPrefix: 'Swift Package Run',
        useShell: true,
        opts: undefined,
      });
    });

    it('should build correct command with parseAsLibrary flag', async () => {
      const mockExecutor: CommandExecutor = (command, logPrefix, useShell, opts) => {
        executorCalls.push({ command, logPrefix, useShell, opts });
        return Promise.resolve(
          createMockCommandResponse({
            success: true,
            output: 'Process completed',
            error: undefined,
          }),
        );
      };

      await swift_package_runLogic(
        {
          packagePath: '/test/package',
          parseAsLibrary: true,
        },
        mockExecutor,
      );

      expect(executorCalls[0]).toEqual({
        command: [
          'swift',
          'run',
          '--package-path',
          '/test/package',
          '-Xswiftc',
          '-parse-as-library',
        ],
        logPrefix: 'Swift Package Run',
        useShell: true,
        opts: undefined,
      });
    });

    it('should build correct command with all parameters', async () => {
      const mockExecutor: CommandExecutor = (command, logPrefix, useShell, opts) => {
        executorCalls.push({ command, logPrefix, useShell, opts });
        return Promise.resolve(
          createMockCommandResponse({
            success: true,
            output: 'Process completed',
            error: undefined,
          }),
        );
      };

      await swift_package_runLogic(
        {
          packagePath: '/test/package',
          executableName: 'MyApp',
          configuration: 'release',
          arguments: ['arg1'],
          parseAsLibrary: true,
        },
        mockExecutor,
      );

      expect(executorCalls[0]).toEqual({
        command: [
          'swift',
          'run',
          '--package-path',
          '/test/package',
          '-c',
          'release',
          '-Xswiftc',
          '-parse-as-library',
          'MyApp',
          '--',
          'arg1',
        ],
        logPrefix: 'Swift Package Run',
        useShell: true,
        opts: undefined,
      });
    });

    it('should not call executor for background mode', async () => {
      // For background mode, no executor should be called since it uses direct spawn
      const mockExecutor = createNoopExecutor();

      const result = await swift_package_runLogic(
        {
          packagePath: '/test/package',
          background: true,
        },
        mockExecutor,
      );

      // Should return success without calling executor
      expect(result.content[0].text).toContain('🚀 Started executable in background');
    });
  });

  describe('Response Logic Testing', () => {
    it('should return validation error for missing packagePath', async () => {
      // Since the tool now uses createTypedTool, Zod validation happens at the handler level
      // Test the handler directly to see Zod validation
      const result = await swiftPackageRun.handler({});

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: 'Error: Parameter validation failed\nDetails: Invalid parameters:\npackagePath: Invalid input: expected string, received undefined',
          },
        ],
        isError: true,
      });
    });

    it('should return success response for background mode', async () => {
      const mockExecutor = createNoopExecutor();
      const result = await swift_package_runLogic(
        {
          packagePath: '/test/package',
          background: true,
        },
        mockExecutor,
      );

      expect(result.content[0].text).toContain('🚀 Started executable in background');
      expect(result.content[0].text).toContain('💡 Process is running independently');
    });

    it('should return success response for successful execution', async () => {
      const mockExecutor = createMockExecutor({
        success: true,
        output: 'Hello, World!',
      });

      const result = await swift_package_runLogic(
        {
          packagePath: '/test/package',
        },
        mockExecutor,
      );

      expect(result).toEqual({
        content: [
          { type: 'text', text: '✅ Swift executable completed successfully.' },
          { type: 'text', text: '💡 Process finished cleanly. Check output for results.' },
          { type: 'text', text: 'Hello, World!' },
        ],
      });
    });

    it('should return error response for failed execution', async () => {
      const mockExecutor = createMockExecutor({
        success: false,
        output: '',
        error: 'Compilation failed',
      });

      const result = await swift_package_runLogic(
        {
          packagePath: '/test/package',
        },
        mockExecutor,
      );

      expect(result).toEqual({
        content: [
          { type: 'text', text: '❌ Swift executable failed.' },
          { type: 'text', text: '(no output)' },
          { type: 'text', text: 'Errors:\nCompilation failed' },
        ],
      });
    });

    it('should handle executor error', async () => {
      const mockExecutor = createMockExecutor(new Error('Command not found'));

      const result = await swift_package_runLogic(
        {
          packagePath: '/test/package',
        },
        mockExecutor,
      );

      expect(result).toEqual({
        content: [
          {
            type: 'text',
            text: 'Error: Failed to execute swift run\nDetails: Command not found',
          },
        ],
        isError: true,
      });
    });
  });
});

```

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

```typescript
/**
 * Tests for test_device plugin
 * Following CLAUDE.md testing standards with literal validation
 * Using pure dependency injection for deterministic testing
 * NO VITEST MOCKING ALLOWED - Only createMockExecutor and manual stubs
 */

import { describe, it, expect, beforeEach } from 'vitest';
import * as z from 'zod';
import {
  createMockCommandResponse,
  createMockExecutor,
  createMockFileSystemExecutor,
} from '../../../../test-utils/mock-executors.ts';
import testDevice, { testDeviceLogic } from '../test_device.ts';
import { sessionStore } from '../../../../utils/session-store.ts';

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

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

    it('should have correct description', () => {
      expect(testDevice.description).toBe('Runs tests on a physical Apple device.');
    });

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

    it('should expose only session-free fields in public schema', () => {
      const schema = z.strictObject(testDevice.schema);
      expect(
        schema.safeParse({
          derivedDataPath: '/path/to/derived-data',
          extraArgs: ['--arg1'],
          preferXcodebuild: true,
          platform: 'iOS',
          testRunnerEnv: { FOO: 'bar' },
        }).success,
      ).toBe(true);
      expect(schema.safeParse({}).success).toBe(true);
      expect(schema.safeParse({ projectPath: '/path/to/project.xcodeproj' }).success).toBe(false);

      const schemaKeys = Object.keys(testDevice.schema).sort();
      expect(schemaKeys).toEqual([
        'derivedDataPath',
        'extraArgs',
        'platform',
        'preferXcodebuild',
        'testRunnerEnv',
      ]);
    });

    it('should validate XOR between projectPath and workspacePath', async () => {
      // This would be validated at the schema level via createTypedTool
      // We test the schema validation through successful logic calls instead
      const mockExecutor = createMockExecutor({
        success: true,
        output: JSON.stringify({
          title: 'Test Schema',
          result: 'SUCCESS',
          totalTestCount: 1,
          passedTests: 1,
          failedTests: 0,
          skippedTests: 0,
          expectedFailures: 0,
        }),
      });

      // Valid: project path only
      const projectResult = await testDeviceLogic(
        {
          projectPath: '/path/to/project.xcodeproj',
          scheme: 'MyScheme',
          deviceId: 'test-device-123',
        },
        mockExecutor,
        createMockFileSystemExecutor({
          mkdtemp: async () => '/tmp/xcodebuild-test-123',
          tmpdir: () => '/tmp',
          stat: async () => ({ isDirectory: () => false, mtimeMs: 0 }),
          rm: async () => {},
        }),
      );
      expect(projectResult.isError).toBeFalsy();

      // Valid: workspace path only
      const workspaceResult = await testDeviceLogic(
        {
          workspacePath: '/path/to/workspace.xcworkspace',
          scheme: 'MyScheme',
          deviceId: 'test-device-123',
        },
        mockExecutor,
        createMockFileSystemExecutor({
          mkdtemp: async () => '/tmp/xcodebuild-test-456',
          tmpdir: () => '/tmp',
          stat: async () => ({ isDirectory: () => false, mtimeMs: 0 }),
          rm: async () => {},
        }),
      );
      expect(workspaceResult.isError).toBeFalsy();
    });
  });

  describe('Handler Requirements', () => {
    it('should require scheme and device defaults', async () => {
      const result = await testDevice.handler({});

      expect(result.isError).toBe(true);
      expect(result.content[0].text).toContain('Missing required session defaults');
      expect(result.content[0].text).toContain('Provide scheme and deviceId');
    });

    it('should require project or workspace when defaults provide scheme and device', async () => {
      sessionStore.setDefaults({ scheme: 'MyScheme', deviceId: 'test-device-123' });

      const result = await testDevice.handler({});

      expect(result.isError).toBe(true);
      expect(result.content[0].text).toContain('Provide a project or workspace');
    });

    it('should reject mutually exclusive project inputs when defaults satisfy requirements', async () => {
      sessionStore.setDefaults({ scheme: 'MyScheme', deviceId: 'test-device-123' });

      const result = await testDevice.handler({
        projectPath: '/path/to/project.xcodeproj',
        workspacePath: '/path/to/workspace.xcworkspace',
      });

      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 Behavior (Complete Literal Returns)', () => {
    beforeEach(() => {
      // Clean setup for standard testing pattern
    });

    it('should return successful test response with parsed results', async () => {
      // Mock xcresulttool output
      const mockExecutor = createMockExecutor({
        success: true,
        output: JSON.stringify({
          title: 'MyScheme Tests',
          result: 'SUCCESS',
          totalTestCount: 5,
          passedTests: 5,
          failedTests: 0,
          skippedTests: 0,
          expectedFailures: 0,
        }),
      });

      const result = await testDeviceLogic(
        {
          projectPath: '/path/to/project.xcodeproj',
          scheme: 'MyScheme',
          deviceId: 'test-device-123',
          configuration: 'Debug',
          preferXcodebuild: false,
          platform: 'iOS',
        },
        mockExecutor,
        createMockFileSystemExecutor({
          mkdtemp: async () => '/tmp/xcodebuild-test-123456',
          tmpdir: () => '/tmp',
          stat: async () => ({ isDirectory: () => false, mtimeMs: 0 }),
          rm: async () => {},
        }),
      );

      expect(result.content).toHaveLength(2);
      expect(result.content[0].text).toContain('✅');
      expect(result.content[1].text).toContain('Test Results Summary:');
      expect(result.content[1].text).toContain('MyScheme Tests');
    });

    it('should handle test failure scenarios', async () => {
      // Mock xcresulttool output for failed tests
      const mockExecutor = createMockExecutor({
        success: true,
        output: JSON.stringify({
          title: 'MyScheme Tests',
          result: 'FAILURE',
          totalTestCount: 5,
          passedTests: 3,
          failedTests: 2,
          skippedTests: 0,
          expectedFailures: 0,
          testFailures: [
            {
              testName: 'testExample',
              targetName: 'MyTarget',
              failureText: 'Expected true but was false',
            },
          ],
        }),
      });

      const result = await testDeviceLogic(
        {
          projectPath: '/path/to/project.xcodeproj',
          scheme: 'MyScheme',
          deviceId: 'test-device-123',
          configuration: 'Debug',
          preferXcodebuild: false,
          platform: 'iOS',
        },
        mockExecutor,
        createMockFileSystemExecutor({
          mkdtemp: async () => '/tmp/xcodebuild-test-123456',
          tmpdir: () => '/tmp',
          stat: async () => ({ isDirectory: () => false, mtimeMs: 0 }),
          rm: async () => {},
        }),
      );

      expect(result.content).toHaveLength(2);
      expect(result.content[1].text).toContain('Test Failures:');
      expect(result.content[1].text).toContain('testExample');
    });

    it('should handle xcresult parsing failures gracefully', async () => {
      // Create a multi-call mock that handles different commands
      let callCount = 0;
      const mockExecutor = async (
        _args: string[],
        _description?: string,
        _useShell?: boolean,
        _opts?: { cwd?: string },
        _detached?: boolean,
      ) => {
        callCount++;

        // First call is for xcodebuild test (successful)
        if (callCount === 1) {
          return createMockCommandResponse({ success: true, output: 'BUILD SUCCEEDED' });
        }

        // Second call is for xcresulttool (fails)
        return createMockCommandResponse({ success: false, error: 'xcresulttool failed' });
      };

      const result = await testDeviceLogic(
        {
          projectPath: '/path/to/project.xcodeproj',
          scheme: 'MyScheme',
          deviceId: 'test-device-123',
          configuration: 'Debug',
          preferXcodebuild: false,
          platform: 'iOS',
        },
        mockExecutor,
        createMockFileSystemExecutor({
          mkdtemp: async () => '/tmp/xcodebuild-test-123456',
          tmpdir: () => '/tmp',
          stat: async () => {
            throw new Error('File not found');
          },
          rm: async () => {},
        }),
      );

      // When xcresult parsing fails, it falls back to original test result only
      expect(result.content).toHaveLength(1);
      expect(result.content[0].text).toContain('✅');
    });

    it('should support different platforms', async () => {
      // Mock xcresulttool output
      const mockExecutor = createMockExecutor({
        success: true,
        output: JSON.stringify({
          title: 'WatchApp Tests',
          result: 'SUCCESS',
          totalTestCount: 3,
          passedTests: 3,
          failedTests: 0,
          skippedTests: 0,
          expectedFailures: 0,
        }),
      });

      const result = await testDeviceLogic(
        {
          projectPath: '/path/to/project.xcodeproj',
          scheme: 'WatchApp',
          deviceId: 'watch-device-456',
          configuration: 'Debug',
          preferXcodebuild: false,
          platform: 'watchOS',
        },
        mockExecutor,
        createMockFileSystemExecutor({
          mkdtemp: async () => '/tmp/xcodebuild-test-123456',
          tmpdir: () => '/tmp',
          stat: async () => ({ isDirectory: () => false, mtimeMs: 0 }),
          rm: async () => {},
        }),
      );

      expect(result.content).toHaveLength(2);
      expect(result.content[1].text).toContain('WatchApp Tests');
    });

    it('should handle optional parameters', async () => {
      // Mock xcresulttool output
      const mockExecutor = createMockExecutor({
        success: true,
        output: JSON.stringify({
          title: 'Tests',
          result: 'SUCCESS',
          totalTestCount: 1,
          passedTests: 1,
          failedTests: 0,
          skippedTests: 0,
          expectedFailures: 0,
        }),
      });

      const result = await testDeviceLogic(
        {
          projectPath: '/path/to/project.xcodeproj',
          scheme: 'MyScheme',
          deviceId: 'test-device-123',
          configuration: 'Release',
          derivedDataPath: '/tmp/derived-data',
          extraArgs: ['--verbose'],
          preferXcodebuild: false,
          platform: 'iOS',
        },
        mockExecutor,
        createMockFileSystemExecutor({
          mkdtemp: async () => '/tmp/xcodebuild-test-123456',
          tmpdir: () => '/tmp',
          stat: async () => ({ isDirectory: () => false, mtimeMs: 0 }),
          rm: async () => {},
        }),
      );

      expect(result.content).toHaveLength(2);
      expect(result.content[0].text).toContain('✅');
    });

    it('should handle workspace testing successfully', async () => {
      // Mock xcresulttool output
      const mockExecutor = createMockExecutor({
        success: true,
        output: JSON.stringify({
          title: 'WorkspaceScheme Tests',
          result: 'SUCCESS',
          totalTestCount: 10,
          passedTests: 10,
          failedTests: 0,
          skippedTests: 0,
          expectedFailures: 0,
        }),
      });

      const result = await testDeviceLogic(
        {
          workspacePath: '/path/to/workspace.xcworkspace',
          scheme: 'WorkspaceScheme',
          deviceId: 'test-device-456',
          configuration: 'Debug',
          preferXcodebuild: false,
          platform: 'iOS',
        },
        mockExecutor,
        createMockFileSystemExecutor({
          mkdtemp: async () => '/tmp/xcodebuild-test-workspace-123',
          tmpdir: () => '/tmp',
          stat: async () => ({ isDirectory: () => false, mtimeMs: 0 }),
          rm: async () => {},
        }),
      );

      expect(result.content).toHaveLength(2);
      expect(result.content[0].text).toContain('✅');
      expect(result.content[1].text).toContain('Test Results Summary:');
      expect(result.content[1].text).toContain('WorkspaceScheme Tests');
    });
  });
});

```

--------------------------------------------------------------------------------
/scripts/release.sh:
--------------------------------------------------------------------------------

```bash
#!/bin/bash
set -e

# GitHub Release Creation Script
# This script handles only the GitHub release creation.
# Building and NPM publishing are handled by GitHub workflows.
#
# Usage: ./scripts/release.sh [VERSION|BUMP_TYPE] [OPTIONS]
# Run with --help for detailed usage information
FIRST_ARG=$1
DRY_RUN=false
VERSION=""
BUMP_TYPE=""

# Function to show help
show_help() {
  cat << 'EOF'
📦 GitHub Release Creator

Creates releases with automatic semver bumping. Only handles GitHub release
creation - building and NPM publishing are handled by workflows.

USAGE:
    [VERSION|BUMP_TYPE] [OPTIONS]

ARGUMENTS:
    VERSION         Explicit version (e.g., 1.5.0, 2.0.0-beta.1)
    BUMP_TYPE       major | minor [default] | patch

OPTIONS:
    --dry-run       Preview without executing
    -h, --help      Show this help

EXAMPLES:
    (no args)       Interactive minor bump
    major           Interactive major bump
    1.5.0           Use specific version
    patch --dry-run Preview patch bump

EOF

  local highest_version=$(get_highest_version)
  if [[ -n "$highest_version" ]]; then
    echo "CURRENT: $highest_version"
    echo "NEXT: major=$(bump_version "$highest_version" "major") | minor=$(bump_version "$highest_version" "minor") | patch=$(bump_version "$highest_version" "patch")"
  else
    echo "No existing version tags found"
  fi
  echo ""
}

# Function to get the highest version from git tags
get_highest_version() {
  git tag | grep -E '^v?[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+\.[0-9]+)?$' | sed 's/^v//' | sort -V | tail -1
}

# Function to parse version components
parse_version() {
  local version=$1
  echo "$version" | sed -E 's/^([0-9]+)\.([0-9]+)\.([0-9]+)(-.*)?$/\1 \2 \3 \4/'
}

# Function to bump version based on type
bump_version() {
  local current_version=$1
  local bump_type=$2

  local parsed=($(parse_version "$current_version"))
  local major=${parsed[0]}
  local minor=${parsed[1]}
  local patch=${parsed[2]}
  local prerelease=${parsed[3]:-""}

  # Remove prerelease for stable version bumps
  case $bump_type in
    major)
      echo "$((major + 1)).0.0"
      ;;
    minor)
      echo "${major}.$((minor + 1)).0"
      ;;
    patch)
      echo "${major}.${minor}.$((patch + 1))"
      ;;
    *)
      echo "❌ Unknown bump type: $bump_type" >&2
      exit 1
      ;;
  esac
}

# Function to validate version format
validate_version() {
  local version=$1
  if ! [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+\.[0-9]+)?$ ]]; then
    echo "❌ Invalid version format: $version"
    echo "Version must be in format: x.y.z or x.y.z-tag.n (e.g., 1.4.0 or 1.4.0-beta.3)"
    return 1
  fi
  return 0
}

# Function to compare versions (returns 1 if first version is greater, 0 if equal, -1 if less)
compare_versions() {
  local version1=$1
  local version2=$2

  local v1_base=${version1%%-*}
  local v2_base=${version2%%-*}
  local v1_pre=""
  local v2_pre=""

  [[ "$version1" == *-* ]] && v1_pre=${version1#*-}
  [[ "$version2" == *-* ]] && v2_pre=${version2#*-}

  # When base versions match, a stable release outranks any prerelease
  if [[ "$v1_base" == "$v2_base" ]]; then
    if [[ -z "$v1_pre" && -n "$v2_pre" ]]; then
      echo 1
      return
    elif [[ -n "$v1_pre" && -z "$v2_pre" ]]; then
      echo -1
      return
    elif [[ "$version1" == "$version2" ]]; then
      echo 0
      return
    fi
  fi

  # Fallback to version sort for differing bases or two prereleases
  local sorted=$(printf "%s\n%s" "$version1" "$version2" | sort -V)
  if [[ "$(echo "$sorted" | head -1)" == "$version1" ]]; then
    echo -1
  else
    echo 1
  fi
}

# Function to ask for confirmation
ask_confirmation() {
  local suggested_version=$1
  echo ""
  echo "🚀 Suggested next version: $suggested_version"
  read -p "Do you want to use this version? (y/N): " -n 1 -r
  echo
  if [[ $REPLY =~ ^[Yy]$ ]]; then
    return 0
  else
    return 1
  fi
}

# Function to get version interactively
get_version_interactively() {
  echo ""
  echo "Please enter the version manually:"
  while true; do
    read -p "Version: " manual_version
    if validate_version "$manual_version"; then
      local highest_version=$(get_highest_version)
      if [[ -n "$highest_version" ]]; then
        local comparison=$(compare_versions "$manual_version" "$highest_version")
        if [[ $comparison -le 0 ]]; then
          echo "❌ Version $manual_version is not newer than the highest existing version $highest_version"
          continue
        fi
      fi
      VERSION="$manual_version"
      break
    fi
  done
}

# Check for help flags first
for arg in "$@"; do
  if [[ "$arg" == "-h" ]] || [[ "$arg" == "--help" ]]; then
    show_help
    exit 0
  fi
done

# Check for arguments and set flags
for arg in "$@"; do
  if [[ "$arg" == "--dry-run" ]]; then
    DRY_RUN=true
  fi
done

# Determine version or bump type (ignore --dry-run flag)
if [[ -z "$FIRST_ARG" ]] || [[ "$FIRST_ARG" == "--dry-run" ]]; then
  # No argument provided, default to minor bump
  BUMP_TYPE="minor"
elif [[ "$FIRST_ARG" == "major" ]] || [[ "$FIRST_ARG" == "minor" ]] || [[ "$FIRST_ARG" == "patch" ]]; then
  # Bump type provided
  BUMP_TYPE="$FIRST_ARG"
else
  # Version string provided
  if validate_version "$FIRST_ARG"; then
    VERSION="$FIRST_ARG"
  else
    exit 1
  fi
fi

# If bump type is set, calculate the suggested version
if [[ -n "$BUMP_TYPE" ]]; then
  HIGHEST_VERSION=$(get_highest_version)
  if [[ -z "$HIGHEST_VERSION" ]]; then
    echo "❌ No existing version tags found. Please provide a version manually."
    get_version_interactively
  else
    SUGGESTED_VERSION=$(bump_version "$HIGHEST_VERSION" "$BUMP_TYPE")

    if ask_confirmation "$SUGGESTED_VERSION"; then
      VERSION="$SUGGESTED_VERSION"
    else
      get_version_interactively
    fi
  fi
fi

# Final validation and version comparison
if [[ -z "$VERSION" ]]; then
  echo "❌ No version determined"
  exit 1
fi

HIGHEST_VERSION=$(get_highest_version)
if [[ -n "$HIGHEST_VERSION" ]]; then
  COMPARISON=$(compare_versions "$VERSION" "$HIGHEST_VERSION")
  if [[ $COMPARISON -le 0 ]]; then
    echo "❌ Version $VERSION is not newer than the highest existing version $HIGHEST_VERSION"
    exit 1
  fi
fi

# Detect current branch
BRANCH=$(git rev-parse --abbrev-ref HEAD)

# Enforce branch policy - only allow releases from main
if [[ "$BRANCH" != "main" ]]; then
  echo "❌ Error: Releases must be created from the main branch."
  echo "Current branch: $BRANCH"
  echo "Please switch to main and try again."
  exit 1
fi

run() {
  if $DRY_RUN; then
    echo "[dry-run] $*"
    return 0
  fi

  "$@"
}

# Portable in-place sed (BSD/macOS vs GNU/Linux)
# - macOS/BSD sed requires: sed -i '' -E 's/.../.../' file
# - GNU sed requires:       sed -i -E 's/.../.../' file
sed_inplace() {
  local expr="$1"
  local file="$2"

  if sed --version >/dev/null 2>&1; then
    # GNU sed
    sed -i -E "$expr" "$file"
  else
    # BSD/macOS sed
    sed -i '' -E "$expr" "$file"
  fi
}

# Ensure we're in the project root (parent of scripts directory)
cd "$(dirname "$0")/.."

# Check if working directory is clean (only enforced for real runs)
if ! $DRY_RUN; then
  if ! git diff-index --quiet HEAD --; then
    echo "❌ Error: Working directory is not clean."
    echo "Please commit or stash your changes before creating a release."
    exit 1
  fi
else
  if ! git diff-index --quiet HEAD --; then
    echo "⚠️  Dry-run: working directory is not clean (continuing)."
  fi
fi

# Check if package.json already has this version (from previous attempt)
CURRENT_PACKAGE_VERSION=$(node -p "require('./package.json').version")
if [[ "$CURRENT_PACKAGE_VERSION" == "$VERSION" ]]; then
  echo "📦 Version $VERSION already set in package.json"
  SKIP_VERSION_UPDATE=true
else
  SKIP_VERSION_UPDATE=false
fi

if [[ "$SKIP_VERSION_UPDATE" == "false" ]]; then
  # Version update
  echo ""
  echo "🔧 Setting version to $VERSION..."
  run npm version "$VERSION" --no-git-tag-version

  # README update
  echo ""
  echo "📝 Updating version in README.md..."
  # Update version references in code examples using extended regex for precise semver matching
  README_AT_SEMVER_REGEX='@[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+\.[0-9]+)?(-[a-zA-Z0-9]+\.[0-9]+)*(-[a-zA-Z0-9]+)?'
  run sed_inplace "s/${README_AT_SEMVER_REGEX}/@${VERSION}/g" README.md

  # Update URL-encoded version references in shield links
  echo "📝 Updating version in README.md shield links..."
  README_URLENCODED_NPM_AT_SEMVER_REGEX='npm%3Axcodebuildmcp%40[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+\.[0-9]+)?(-[a-zA-Z0-9]+\.[0-9]+)*(-[a-zA-Z0-9]+)?'
  run sed_inplace "s/${README_URLENCODED_NPM_AT_SEMVER_REGEX}/npm%3Axcodebuildmcp%40${VERSION}/g" README.md

  # server.json update
  echo ""
  if [[ -f server.json ]]; then
    echo "📝 Updating server.json version to $VERSION..."
    run node -e "const fs=require('fs');const f='server.json';const j=JSON.parse(fs.readFileSync(f,'utf8'));j.version='${VERSION}';if(Array.isArray(j.packages)){j.packages=j.packages.map(p=>({...p,version:'${VERSION}'}));}fs.writeFileSync(f,JSON.stringify(j,null,2)+'\n');"
  else
    echo "⚠️  server.json not found; skipping update"
  fi

  # Git operations
  echo ""
  echo "📦 Committing version changes..."
  if [[ -f server.json ]]; then
    run git add package.json package-lock.json README.md server.json
  else
    run git add package.json package-lock.json README.md
  fi
  run git commit -m "Release v$VERSION"
else
  echo "⏭️  Skipping version update (already done)"
  # Ensure server.json still matches the desired version (in case of a partial previous run)
  if [[ -f server.json ]]; then
    CURRENT_SERVER_VERSION=$(node -e "console.log(JSON.parse(require('fs').readFileSync('server.json','utf8')).version||'')")
    if [[ "$CURRENT_SERVER_VERSION" != "$VERSION" ]]; then
      echo "📝 Aligning server.json to $VERSION..."
      run node -e "const fs=require('fs');const f='server.json';const j=JSON.parse(fs.readFileSync(f,'utf8'));j.version='${VERSION}';if(Array.isArray(j.packages)){j.packages=j.packages.map(p=>({...p,version:'${VERSION}'}));}fs.writeFileSync(f,JSON.stringify(j,null,2)+'\n');"
      run git add server.json
      run git commit -m "Align server.json for v$VERSION"
    fi
  fi
fi

# Create or recreate tag at current HEAD
echo "🏷️  Creating tag v$VERSION..."
run git tag -f "v$VERSION"

echo ""
echo "🚀 Pushing to origin..."
run git push origin "$BRANCH" --tags

# In dry-run, stop here (don't monitor workflows, and don't claim a release happened).
if $DRY_RUN; then
  echo ""
  echo "ℹ️  Dry-run: skipping GitHub Actions workflow monitoring."
  exit 0
fi

# Monitor the workflow and handle failures
echo ""
echo "⏳ Monitoring GitHub Actions workflow..."
echo "This may take a few minutes..."

# Wait for workflow to start
sleep 5

# Get the workflow run ID for this tag
RUN_ID=$(gh run list --workflow=release.yml --limit=1 --json databaseId --jq '.[0].databaseId')

if [[ -n "$RUN_ID" ]]; then
  echo "📊 Workflow run ID: $RUN_ID"
  echo "🔍 Watching workflow progress..."
  echo "(Press Ctrl+C to detach and monitor manually)"
  echo ""

  # Watch the workflow with exit status
  if gh run watch "$RUN_ID" --exit-status; then
    echo ""
    echo "✅ Release v$VERSION completed successfully!"
    echo "📦 View on NPM: https://www.npmjs.com/package/xcodebuildmcp/v/$VERSION"
    echo "🎉 View release: https://github.com/cameroncooke/XcodeBuildMCP/releases/tag/v$VERSION"
    # MCP Registry verification link
    echo "🔎 Verify MCP Registry: https://registry.modelcontextprotocol.io/v0/servers?search=com.xcodebuildmcp/XcodeBuildMCP&version=latest"
  else
    echo ""
    echo "❌ CI workflow failed!"
    echo ""
    # Prefer job state: if the primary 'release' job succeeded, treat as success.
    RELEASE_JOB_CONCLUSION=$(gh run view "$RUN_ID" --json jobs --jq '.jobs[] | select(.name=="release") | .conclusion')
    if [ "$RELEASE_JOB_CONCLUSION" = "success" ]; then
      echo "⚠️ Workflow reported failure, but primary 'release' job concluded SUCCESS."
      echo "✅ Treating release as successful. Tag v$VERSION is kept."
      echo "📦 Verify on NPM: https://www.npmjs.com/package/xcodebuildmcp/v/$VERSION"
      exit 0
    fi
    echo "🧹 Cleaning up tags only (keeping version commit)..."

    # Delete remote tag
    echo "  - Deleting remote tag v$VERSION..."
    git push origin :refs/tags/v$VERSION 2>/dev/null || true

    # Delete local tag
    echo "  - Deleting local tag v$VERSION..."
    git tag -d v$VERSION

    echo ""
    echo "✅ Tag cleanup complete!"
    echo ""
    echo "ℹ️  The version commit remains in your history."
    echo "📝 To retry after fixing issues:"
    echo "   1. Fix the CI issues"
    echo "   2. Commit your fixes"
    echo "   3. Run: ./scripts/release.sh $VERSION"
    echo ""
    echo "🔍 To see what failed: gh run view $RUN_ID --log-failed"
    exit 1
  fi
else
  echo "⚠️  Could not find workflow run. Please check manually:"
  echo "https://github.com/cameroncooke/XcodeBuildMCP/actions"
fi

```

--------------------------------------------------------------------------------
/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 * as 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/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 * as z from 'zod';
import {
  createMockCommandResponse,
  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.strictObject(getDeviceAppPath.schema);
      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: string[];
        logPrefix?: string;
        useShell?: boolean;
        opts?: { cwd?: string };
      }> = [];

      const mockExecutor = (
        args: string[],
        logPrefix?: string,
        useShell?: boolean,
        opts?: { cwd?: string },
        _detached?: boolean,
      ) => {
        calls.push({ args, logPrefix, useShell, opts });
        return Promise.resolve(
          createMockCommandResponse({
            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,
          }),
        );
      };

      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',
        ],
        logPrefix: 'Get App Path',
        useShell: true,
        opts: undefined,
      });
    });

    it('should generate correct xcodebuild command for watchOS', async () => {
      const calls: Array<{
        args: string[];
        logPrefix?: string;
        useShell?: boolean;
        opts?: { cwd?: string };
      }> = [];

      const mockExecutor = (
        args: string[],
        logPrefix?: string,
        useShell?: boolean,
        opts?: { cwd?: string },
        _detached?: boolean,
      ) => {
        calls.push({ args, logPrefix, useShell, opts });
        return Promise.resolve(
          createMockCommandResponse({
            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,
          }),
        );
      };

      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',
        ],
        logPrefix: 'Get App Path',
        useShell: true,
        opts: undefined,
      });
    });

    it('should generate correct xcodebuild command for workspace with iOS', async () => {
      const calls: Array<{
        args: string[];
        logPrefix?: string;
        useShell?: boolean;
        opts?: { cwd?: string };
      }> = [];

      const mockExecutor = (
        args: string[],
        logPrefix?: string,
        useShell?: boolean,
        opts?: { cwd?: string },
        _detached?: boolean,
      ) => {
        calls.push({ args, logPrefix, useShell, opts });
        return Promise.resolve(
          createMockCommandResponse({
            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,
          }),
        );
      };

      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',
        ],
        logPrefix: 'Get App Path',
        useShell: true,
        opts: 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: string[];
        logPrefix?: string;
        useShell?: boolean;
        opts?: { cwd?: string };
      }> = [];

      const mockExecutor = (
        args: string[],
        logPrefix?: string,
        useShell?: boolean,
        opts?: { cwd?: string },
        _detached?: boolean,
      ) => {
        calls.push({ args, logPrefix, useShell, opts });
        return Promise.resolve(
          createMockCommandResponse({
            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,
          }),
        );
      };

      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',
        ],
        logPrefix: 'Get App Path',
        useShell: true,
        opts: undefined,
      });
    });

    it('should return exact exception handling response', async () => {
      const mockExecutor = (
        _args: string[],
        _logPrefix?: string,
        _useShell?: boolean,
        _opts?: { cwd?: string },
        _detached?: boolean,
      ) => {
        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/ui-testing/__tests__/screenshot.test.ts:
--------------------------------------------------------------------------------

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

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

describe('Screenshot Plugin', () => {
  beforeEach(() => {
    sessionStore.clear();
  });

  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);

      // Public schema is empty; ensure extra fields are stripped
      expect(schema.safeParse({}).success).toBe(true);

      const withSimId = schema.safeParse({
        simulatorId: '12345678-1234-4234-8234-123456789012',
      });
      expect(withSimId.success).toBe(true);
      expect('simulatorId' in (withSimId.data as Record<string, unknown>)).toBe(false);
    });
  });

  describe('Plugin Handler Validation', () => {
    it('should require simulatorId session default when not provided', async () => {
      const result = await screenshotPlugin.handler({});

      expect(result.isError).toBe(true);
      const message = result.content[0].text;
      expect(message).toContain('Missing required session defaults');
      expect(message).toContain('simulatorId is required');
      expect(message).toContain('session-set-defaults');
    });

    it('should validate inline simulatorId overrides', async () => {
      const result = await screenshotPlugin.handler({
        simulatorId: 'invalid-uuid',
      });

      expect(result.isError).toBe(true);
      const message = result.content[0].text;
      expect(message).toContain('Parameter validation failed');
      expect(message).toContain('simulatorId: Invalid Simulator UUID format');
    });
  });

  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: mockProcess,
        };
      };

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

      await screenshotLogic(
        {
          simulatorId: '12345678-1234-4234-8234-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-4234-8234-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: mockProcess,
        };
      };

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

      await screenshotLogic(
        {
          simulatorId: '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: mockProcess,
        };
      };

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

      await screenshotLogic(
        {
          simulatorId: '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: mockProcess,
        };
      };

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

      await screenshotLogic(
        {
          simulatorId: '12345678-1234-4234-8234-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-4234-8234-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(
        {
          simulatorId: '12345678-1234-4234-8234-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(
        {
          simulatorId: '12345678-1234-4234-8234-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(
        {
          simulatorId: '12345678-1234-4234-8234-123456789012',
        },
        mockExecutor,
        createMockFileSystemExecutor(),
      );

      expect(result).toEqual({
        content: [
          {
            type: 'text' as const,
            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(
        {
          simulatorId: '12345678-1234-4234-8234-123456789012',
        },
        mockExecutor,
        mockFileSystemExecutor,
      );

      expect(result).toEqual({
        content: [
          {
            type: 'text' as const,
            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(
        {
          simulatorId: '12345678-1234-4234-8234-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(
        {
          simulatorId: '12345678-1234-4234-8234-123456789012',
        },
        mockExecutor,
        createMockFileSystemExecutor(),
      );

      expect(result).toEqual({
        content: [
          {
            type: 'text' as const,
            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(
        {
          simulatorId: '12345678-1234-4234-8234-123456789012',
        },
        mockExecutor,
        createMockFileSystemExecutor(),
      );

      expect(result).toEqual({
        content: [
          { type: 'text' as const, 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(
        {
          simulatorId: '12345678-1234-4234-8234-123456789012',
        },
        mockExecutor,
        createMockFileSystemExecutor(),
      );

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

```

--------------------------------------------------------------------------------
/docs/dev/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/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<
        number,
        { executableName?: string; packagePath: string; startedAt: Date }
      >([
        [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 missing executableName', async () => {
      const startedAt = new Date('2023-01-01T10:00:00.000Z');
      const mockProcess = {
        executableName: undefined, // Test missing executable name
        packagePath: '/test/package',
        startedAt: startedAt,
      };

      // Create mock process map with one process
      const mockProcessMap = new Map<
        number,
        { executableName?: string; packagePath: string; startedAt: Date }
      >([[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.' },
        ],
      });
    });
  });
});

```
Page 7/12FirstPrevNextLast