diff --git a/.cursor/BUGBOT.md b/.cursor/BUGBOT.md
index ca6b9f3c9..c550cd536 100644
--- a/.cursor/BUGBOT.md
+++ b/.cursor/BUGBOT.md
@@ -5,7 +5,7 @@
XcodeBuildMCP is an MCP server exposing Xcode / Swift workflows as **tools** and **resources**.
Stack: TypeScript · Node.js · plugin-based auto-discovery (`src/mcp/tools`, `src/mcp/resources`).
-For full details see [README.md](README.md) and [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
+For full details see [README.md](README.md) and [Architecture](https://xcodebuildmcp.com/docs/architecture).
---
@@ -55,8 +55,9 @@ export const handler = (p: FooBarParams) => fooBarLogic(p);
## 4. Documentation Checklist
-* `docs/TOOLS.md` must exactly mirror the structure of `src/mcp/tools/**` (exclude `__tests__` and `*-shared`).
- *Diff heuristic*: if a PR adds/removes a tool but does **not** change `docs/TOOLS.md` ⇒ **warning**.
+* Tool manifests, schemas, and implementations must stay aligned when tools are added, removed, or renamed.
+ *Diff heuristic*: tool changes without corresponding manifest/schema/fixture updates ⇒ **warning**.
+* Public tool docs live at https://xcodebuildmcp.com/docs/tools and are synced from release data; do not require static in-repo tools-doc parity.
* Update public docs when CLI parameters or tool names change.
---
@@ -76,7 +77,7 @@ export const handler = (p: FooBarParams) => fooBarLogic(p);
1. **External-boundary violations**: confirm tests use injected executors/filesystem for external side effects.
2. **DI compliance**: check direct `child_process` / `fs` imports in MCP tool logic; standalone utilities with simple commands are acceptable.
-3. **Docs accuracy**: compare `docs/TOOLS.md` against `src/mcp/tools/**`.
+3. **Docs accuracy**: compare tool manifests, schemas, fixtures, and implementation changes; public tool docs are generated at https://xcodebuildmcp.com/docs/tools.
4. **Style**: ensure ESLint and Prettier pass (`npm run lint`, `npm run format:check`).
---
diff --git a/.github/workflows/tool-authoring-guidance.yml b/.github/workflows/tool-authoring-guidance.yml
index c2c4ec7c6..74c494268 100644
--- a/.github/workflows/tool-authoring-guidance.yml
+++ b/.github/workflows/tool-authoring-guidance.yml
@@ -76,15 +76,15 @@ jobs:
${fileList}${hiddenText}
- Please review the [Tool Authoring Cookbook](https://github.com/${owner}/${repo}/blob/main/docs/dev/TOOL_AUTHORING_COOKBOOK.md) before merging.
+ Please review the [Tool Authoring guide](https://xcodebuildmcp.com/docs/tool-authoring) before merging.
Checklist:
- Run \`npm run test:snapshots\` for any added, modified, or deleted tool.
- If fixtures need to change, regenerate them with \`npm run test:snapshots:update\` and review the diff.
- Add, update, or remove the matching MCP, CLI, and JSON fixtures for the changed tool surface.
- Run \`npm run test:schema-fixtures\` after changing structured output schemas or JSON fixtures.
- - Keep tool manifests, workflow manifests, output schemas, structured content, generated docs, and fixtures aligned.
- - If you changed tool metadata, run \`npm run docs:update\` and \`npm run docs:check\`.
+ - Keep tool manifests, workflow manifests, output schemas, structured content, and fixtures aligned.
+ - If you changed tool metadata, run \`npm run docs:check\`.
Snapshot tests are intentionally not a required PR gate because they are slow and environment-sensitive. This reminder exists so contributors know when they need to run them locally for tool additions, changes, and removals.`;
diff --git a/AGENTS.md b/AGENTS.md
index 3e0a930e8..a0e45a7a8 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -28,7 +28,6 @@ When reading issues:
- Technical prose only, be kind but direct (e.g., "Thanks @user" not "Thanks so much @user!")
## Docs
-- If modifying or adding/removing tools run `npm run docs:update` to update the TOOLS.md file, never edit this file directly.
-
### Changelog
Location: `CHANGELOG.md`
@@ -61,7 +60,7 @@ Use these sections under `## [Unreleased]`:
- Do NOT just kill the run — first inspect the process tree (`ps -ef | grep -E "vitest|xcodebuild|simctl|devicectl"`) to identify what's stuck.
- Common hang causes: locked physical device, stale simulator state, `devicectl diagnose` waiting for password, orphaned daemon process.
- Capture what you find before killing, so the root cause can be fixed rather than papered over.
-- If physical-device snapshot tests hang after the final test summary, the likely cause is Apple post-failure diagnostics invoking `devicectl diagnose`, which may prompt for a macOS password and wedge in automated runs; see `docs/dev/device-snapshot-password-hang-rca.md`.
+- If physical-device snapshot tests hang after the final test summary, the likely cause is Apple post-failure diagnostics invoking `devicectl diagnose`, which may prompt for a macOS password and wedge in automated runs.
## **CRITICAL** Tool Usage Rules **CRITICAL**
- NEVER use sed/cat to read a file or a range of a file. Always use the native read tool.
diff --git a/CLAUDE.md b/CLAUDE.md
index c1c6e9b05..74a52bc62 100755
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -28,7 +28,6 @@ When reading issues:
- Technical prose only, be kind but direct (e.g., "Thanks @user" not "Thanks so much @user!")
## Docs
-- If modifying or adding/removing tools run `npm run docs:update` to update the TOOLS.md file, never edit this file directly.
-
### Changelog
Location: `CHANGELOG.md`
diff --git a/NEXT_STEPS_MIGRATION_TODO.md b/NEXT_STEPS_MIGRATION_TODO.md
deleted file mode 100644
index abd76a080..000000000
--- a/NEXT_STEPS_MIGRATION_TODO.md
+++ /dev/null
@@ -1,27 +0,0 @@
-# Next Steps Migration TODO
-
-Generated: 2026-02-11 19:12:27 UTC
-
-## Remaining tool files with inline nextSteps
-- [x] src/mcp/tools/debugging/debug_attach_sim.ts:148
-- [x] src/mcp/tools/device/get_device_app_path.ts:140
-- [x] src/mcp/tools/device/launch_app_device.ts:135
-- [x] src/mcp/tools/device/list_devices.ts:391
-- [x] src/mcp/tools/logging/start_device_log_cap.ts:665
-- [x] src/mcp/tools/logging/start_sim_log_cap.ts:81
-- [x] src/mcp/tools/macos/get_mac_app_path.ts:167
-- [x] src/mcp/tools/project-discovery/get_app_bundle_id.ts:91
-- [x] src/mcp/tools/project-discovery/get_mac_bundle_id.ts:88
-- [x] src/mcp/tools/project-discovery/list_schemes.ts:83
-- [x] src/mcp/tools/project-discovery/show_build_settings.ts:88
-- [x] src/mcp/tools/project-scaffolding/scaffold_ios_project.ts:366
-- [x] src/mcp/tools/project-scaffolding/scaffold_macos_project.ts:340
-- [x] src/mcp/tools/simulator/boot_sim.ts:69
-- [x] src/mcp/tools/simulator/build_run_sim.ts:486
-- [x] src/mcp/tools/simulator/get_sim_app_path.ts:244
-- [x] src/mcp/tools/simulator/install_app_sim.ts:93
-- [x] src/mcp/tools/simulator/launch_app_logs_sim.ts:100
-- [x] src/mcp/tools/simulator/launch_app_sim.ts:127
-- [x] src/mcp/tools/simulator/list_sims.ts:196
-- [x] src/mcp/tools/simulator/open_sim.ts:42
-- [x] src/mcp/tools/simulator/record_sim_video.ts:130
diff --git a/README.md b/README.md
index 51870d199..0bc513ff4 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@ A Model Context Protocol (MCP) server and CLI that provides tools for agent use
## Installation
-XcodeBuildMCP ships as a single package with two modes: a **CLI** for direct terminal use and an **MCP server** for AI coding agents. Both installation methods give you both modes.
+XcodeBuildMCP ships as a single package with two modes: a **CLI** for direct terminal use and an **MCP server** for AI coding agents. Either install method gives you both.
### Option A — Homebrew
@@ -16,285 +16,20 @@ brew tap getsentry/xcodebuildmcp
brew install xcodebuildmcp
```
-Use the CLI:
-```bash
-xcodebuildmcp --help
-```
+### Option B — npm (Node.js 18+)
-MCP client config:
-```json
-"XcodeBuildMCP": {
- "command": "xcodebuildmcp",
- "args": ["mcp"]
-}
+```bash
+npm install -g xcodebuildmcp@latest
```
-Upgrade later with `brew update && brew upgrade xcodebuildmcp`.
-
-### Option B — npm / npx (Node.js 18+)
-
-**For CLI use**, install globally:
+Verify either install:
```bash
-npm install -g xcodebuildmcp@latest
xcodebuildmcp --help
```
-**For MCP server only**, no global install needed — add directly to your client config:
-```json
-"XcodeBuildMCP": {
- "command": "npx",
- "args": ["-y", "xcodebuildmcp@latest", "mcp"]
-}
-```
+### Connect your MCP client
-To pin a specific version, replace `@latest` with an exact version (e.g. `xcodebuildmcp@latest`).
-
-### Client-specific setup
-
-The examples below use npx (Option B). If you installed via Homebrew, replace the command with `"command": "xcodebuildmcp", "args": ["mcp"]` instead.
-
-
- Cursor
-
-
- Recommended (project-scoped): add `.cursor/mcp.json` in your workspace root:
- ```json
- {
- "mcpServers": {
- "XcodeBuildMCP": {
- "command": "npx",
- "args": ["-y", "xcodebuildmcp@latest", "mcp"]
- }
- }
- }
- ```
-
- For global Cursor config (`~/.cursor/mcp.json`), use this variant so startup is aligned with the active workspace:
- ```json
- {
- "mcpServers": {
- "XcodeBuildMCP": {
- "command": "/bin/zsh",
- "args": [
- "-lc",
- "cd \"${workspaceFolder}\" && exec npx -y xcodebuildmcp@latest mcp"
- ]
- }
- }
- }
- ```
-
- Or use the quick install link:
-
-[](https://cursor.com/en-US/install-mcp?name=XcodeBuildMCP&config=eyJjb21tYW5kIjoibnB4IC15IHhjb2RlYnVpbGRtY3BAbGF0ZXN0IG1jcCJ9)
-
-
-
-
- Claude Code
-
-
- Run:
- ```bash
- claude mcp add XcodeBuildMCP -- npx -y xcodebuildmcp@latest mcp
- ```
-
-
-
-
- Codex CLI
-
-
- Run:
- ```bash
- codex mcp add XcodeBuildMCP -- npx -y xcodebuildmcp@latest mcp
- ```
-
- Or add to `~/.codex/config.toml`:
- ```toml
- [mcp_servers.XcodeBuildMCP]
- command = "npx"
- args = ["-y", "xcodebuildmcp@latest", "mcp"]
- ```
-
-
-
-
- Claude Desktop
-
-
- Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
- ```json
- {
- "mcpServers": {
- "XcodeBuildMCP": {
- "command": "npx",
- "args": ["-y", "xcodebuildmcp@latest", "mcp"]
- }
- }
- }
- ```
-
-
-
-
- VS Code / VS Code Insiders
-
-
- Add to your VS Code settings JSON:
- ```json
- "mcp": {
- "servers": {
- "XcodeBuildMCP": {
- "command": "npx",
- "args": ["-y", "xcodebuildmcp@latest", "mcp"]
- }
- }
- }
- ```
-
- Or use the quick install links:
-
- [](vscode:mcp/install?%7B%22name%22%3A%22XcodeBuildMCP%22%2C%22type%22%3A%22stdio%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22xcodebuildmcp%40latest%22%2C%22mcp%22%5D%7D)
- [](vscode-insiders:mcp/install?%7B%22name%22%3A%22XcodeBuildMCP%22%2C%22type%22%3A%22stdio%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22xcodebuildmcp%40latest%22%2C%22mcp%22%5D%7D)
-
-
-
-
- Kiro / Kiro CLI
-
-
- **Workspace-level** (applies only to the current workspace): add `.kiro/settings/mcp.json` in your project root:
- ```json
- {
- "mcpServers": {
- "XcodeBuildMCP": {
- "command": "npx",
- "args": ["-y", "xcodebuildmcp@latest", "mcp"]
- }
- }
- }
- ```
-
- **User-level** (applies globally across all workspaces): add to `~/.kiro/settings/mcp.json`:
- ```json
- {
- "mcpServers": {
- "XcodeBuildMCP": {
- "command": "npx",
- "args": ["-y", "xcodebuildmcp@latest", "mcp"]
- }
- }
- }
- ```
-
-
-
-
- Windsurf
-
-
- Add to `~/.codeium/windsurf/mcp_config.json`:
- ```json
- {
- "mcpServers": {
- "XcodeBuildMCP": {
- "command": "npx",
- "args": ["-y", "xcodebuildmcp@latest", "mcp"]
- }
- }
- }
- ```
-
-
-
-
- Trae
-
-
- Add to `~/Library/Application Support/Trae/User/mcp.json`:
- ```json
- {
- "mcpServers": {
- "XcodeBuildMCP": {
- "command": "npx",
- "args": ["-y", "xcodebuildmcp@latest", "mcp"]
- }
- }
- }
- ```
-
-
-
-
- Xcode (Codex Agent)
-
-
- Requires Xcode 26.3 or later. Codex agent must be installed and configured in Xcode Settings -> Intelligence -> Open AI.
-
- The only way at the time of writing to add an MCP server is to use a project scoped `.codex/config.toml` file in the root of your project workspace:
- `/path/to/your/project/.codex/config.toml`
-
- ```toml
- [mcp_servers.XcodeBuildMCP]
- args = [
- "-lc",
- "PATH=/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin; export NVM_DIR=\"$HOME/.nvm\"; [ -s \"$NVM_DIR/nvm.sh\" ] && . \"$NVM_DIR/nvm.sh\"; nvm use --silent >/dev/null 2>&1 || true; npx -y xcodebuildmcp@latest mcp"
- ]
- command = "/bin/zsh"
- enabled = true
- tool_timeout_sec = 10000
- ```
-
- > **NOTE**:
- > Codex Agent when running in Xcode has a limited PATH by default. The above example should work for most users but if you find the server doesn't start or is not available, it's likely because npx is not found so you might have to adjust the above configuration accordingly.
-
-
-
-
-
- Xcode (Claude Code Agent)
-
-
- Requires Xcode 26.3 or later. Claude Code agent must be installed and configured in Xcode Settings -> Intelligence -> Anthropic.
-
- Add to the end or replace the existing `mcpServers` object in Xcode's Claude Code agent config at:
- `~/Library/Developer/Xcode/CodingAssistant/ClaudeAgentConfig/.claude.json`
-
- ```json
- // ... rest of file ...
- "mcpServers": {
- "XcodeBuildMCP": {
- "command": "/bin/zsh",
- "args": [
- "-lc",
- "PATH=/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin; export NVM_DIR=\"$HOME/.nvm\"; [ -s \"$NVM_DIR/nvm.sh\" ] && . \"$NVM_DIR/nvm.sh\"; nvm use --silent >/dev/null 2>&1 || true; npx -y xcodebuildmcp@latest mcp"
- ]
- }
- }
- }
- ```
-
- > **NOTE**:
- > Claude Code Agent when running in Xcode has a limited PATH by default. The above example should work for most users but if you find the server doesn't start or is not available, it's likely because npx is not found so you might have to adjust the above configuration accordingly.
-
-
-
-
-
- AdaL CLI
-
-
- Run the following command inside the AdaL CLI prompt:
- ```console
- /mcp add XcodeBuildMCP --command npx --args "-y,xcodebuildmcp@latest,mcp"
- ```
-
-
-
-
-
-For other installation options see [Getting Started](docs/GETTING_STARTED.md).
+Drop-in config snippets for Cursor, Claude Code, Codex, can be found in the official docs page [MCP Clients](https://xcodebuildmcp.com/docs/clients). Most clients can also run the MCP server on demand via `npx -y xcodebuildmcp@latest mcp` without a global install.
## Requirements
@@ -323,16 +58,16 @@ Or install directly via npx without a global install:
npx -y xcodebuildmcp@latest init
```
-For further information on installing skills, see: [docs/SKILLS.md](docs/SKILLS.md)
+For further information on installing skills, see [Agent Skills](https://xcodebuildmcp.com/docs/skills).
## Notes
- XcodeBuildMCP requests xcodebuild to skip macro validation to avoid errors when building projects that use Swift Macros.
-- Device tools require code signing to be configured in Xcode. See [docs/DEVICE_CODE_SIGNING.md](docs/DEVICE_CODE_SIGNING.md).
+- Device tools require code signing to be configured in Xcode. See [Device Code Signing](https://xcodebuildmcp.com/docs/device-signing).
## Privacy
-XcodeBuildMCP uses Sentry for internal runtime error telemetry only. For details and opt-out instructions, see [docs/PRIVACY.md](docs/PRIVACY.md).
+XcodeBuildMCP uses Sentry for internal runtime error telemetry only. For details and opt-out instructions, see [Privacy & Telemetry](https://xcodebuildmcp.com/docs/privacy).
## CLI
@@ -359,18 +94,20 @@ xcodebuildmcp upgrade --check
xcodebuildmcp upgrade --yes
```
-The CLI uses a per-workspace daemon for stateful operations (log capture, debugging, etc.) that auto-starts when needed. See [docs/CLI.md](docs/CLI.md) for full documentation.
+The CLI uses a per-workspace daemon for stateful operations (log capture, debugging, etc.) that auto-starts when needed. See the [CLI guide](https://xcodebuildmcp.com/docs/cli) for full documentation.
## Documentation
-- Getting started: [docs/GETTING_STARTED.md](docs/GETTING_STARTED.md)
-- CLI usage: [docs/CLI.md](docs/CLI.md)
-- Configuration and options: [docs/CONFIGURATION.md](docs/CONFIGURATION.md)
-- Tools reference: [docs/TOOLS.md](docs/TOOLS.md)
-- Troubleshooting: [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md)
-- Privacy: [docs/PRIVACY.md](docs/PRIVACY.md)
-- Skills: [docs/SKILLS.md](docs/SKILLS.md)
-- Contributing: [docs/dev/CONTRIBUTING.md](docs/dev/CONTRIBUTING.md)
+- Installation: [https://xcodebuildmcp.com/docs/installation](https://xcodebuildmcp.com/docs/installation)
+- Setup: [https://xcodebuildmcp.com/docs/setup](https://xcodebuildmcp.com/docs/setup)
+- MCP clients: [https://xcodebuildmcp.com/docs/clients](https://xcodebuildmcp.com/docs/clients)
+- CLI usage: [https://xcodebuildmcp.com/docs/cli](https://xcodebuildmcp.com/docs/cli)
+- Configuration and options: [https://xcodebuildmcp.com/docs/configuration](https://xcodebuildmcp.com/docs/configuration)
+- Tools reference: [https://xcodebuildmcp.com/docs/tools](https://xcodebuildmcp.com/docs/tools)
+- Troubleshooting: [https://xcodebuildmcp.com/docs/troubleshooting](https://xcodebuildmcp.com/docs/troubleshooting)
+- Privacy: [https://xcodebuildmcp.com/docs/privacy](https://xcodebuildmcp.com/docs/privacy)
+- Skills: [https://xcodebuildmcp.com/docs/skills](https://xcodebuildmcp.com/docs/skills)
+- Contributing: [https://xcodebuildmcp.com/docs/contributing](https://xcodebuildmcp.com/docs/contributing)
## Licence
diff --git a/docs/CLI.md b/docs/CLI.md
deleted file mode 100644
index 47ecac93d..000000000
--- a/docs/CLI.md
+++ /dev/null
@@ -1,339 +0,0 @@
-# XcodeBuildMCP CLI
-
-`xcodebuildmcp` is a unified command-line interface that provides both an MCP server and direct tool access via a first-class CLI.
-
-Use `xcodebuildmcp` CLI to invoke tools or start the MCP server by passing the `mcp` argument.
-
-## Installation
-
-```bash
-# Install globally
-npm install -g xcodebuildmcp@beta
-
-# Or run via npx
-npx xcodebuildmcp@beta --help
-```
-
-## Quick Start
-
-```bash
-# List available tools
-xcodebuildmcp tools
-
-# View CLI help
-xcodebuildmcp --help
-
-# View tool help
-xcodebuildmcp --help
-
-# Run interactive setup for .xcodebuildmcp/config.yaml
-xcodebuildmcp setup
-
-# Check for updates
-xcodebuildmcp upgrade --check
-```
-
-## Upgrade
-
-`xcodebuildmcp upgrade` checks for a newer release and optionally runs the upgrade.
-
-```bash
-# Check for updates without upgrading
-xcodebuildmcp upgrade --check
-
-# Upgrade automatically (skip confirmation prompt)
-xcodebuildmcp upgrade --yes
-```
-
-### Flags
-
-| Flag | Description |
-|------|-------------|
-| `--check` | Report the latest version and exit. Never prompts or runs an upgrade. |
-| `--yes` / `-y` | Skip the confirmation prompt and run the upgrade command automatically. |
-
-When both `--check` and `--yes` are supplied, `--check` wins.
-
-### Channel-aware version lookup
-
-The version check queries the source of truth for your install channel — `brew info` for Homebrew, `npm view` for npm/npx, or GitHub Releases for unknown installs. This avoids misleading results when release channels drift (e.g. GitHub may publish a version before the Homebrew tap bumps). If the channel-specific lookup fails, the command does not fall back to another source; it reports the error and exits 1.
-
-### Install method behavior
-
-The command detects how XcodeBuildMCP was installed and adapts accordingly:
-
-| Method | Auto-upgrade | Command |
-|--------|--------------|----------|
-| Homebrew | Yes | `brew update && brew upgrade xcodebuildmcp` |
-| npm global | Yes | `npm install -g xcodebuildmcp@latest` |
-| npx | No | npx resolves `@latest` on each run; update the pinned version in your client config if needed. |
-| Unknown | No | Manual instructions for all supported channels are shown. |
-
-### Non-interactive mode
-
-When stdin is not a TTY (CI, pipes, scripts):
-
-- `--check` works normally and exits 0.
-- `--yes` runs the upgrade for Homebrew and npm-global installs.
-- Without `--check` or `--yes`, the command prints the manual upgrade command and exits 1 (it cannot prompt for confirmation).
-
-### Lookup failures
-
-If the channel-specific version check fails (network error, rate limit, timeout, missing formula), the command prints the detected install method and manual upgrade instructions, then exits 1.
-
-## Tool Options
-
-Each tool supports `--help` for detailed options:
-
-```bash
-xcodebuildmcp simulator build --help
-```
-
-Common patterns:
-
-```bash
-# Pass options as flags
-xcodebuildmcp simulator build --scheme MyApp --project-path ./MyApp.xcodeproj
-
-# Pass complex options as JSON
-xcodebuildmcp simulator build --json '{"scheme": "MyApp", "projectPath": "./MyApp.xcodeproj"}'
-
-# Control output format
-xcodebuildmcp simulator list --output json
-```
-
-## Examples
-
-### Build and Run Workflow
-
-```bash
-# Discover projects
-xcodebuildmcp simulator discover-projects
-
-# List schemes
-xcodebuildmcp simulator list-schemes --project-path ./MyApp.xcodeproj
-
-# Build
-xcodebuildmcp simulator build --scheme MyApp --project-path ./MyApp.xcodeproj
-
-# Boot simulator
-xcodebuildmcp simulator boot --simulator-name "iPhone 17 Pro"
-
-# Install and launch
-xcodebuildmcp simulator install --simulator-id --app-path ./build/MyApp.app
-
-xcodebuildmcp simulator launch-app --simulator-id --bundle-id io.sentry.MyApp
-
-# Or... build and run in a single command
-xcodebuildmcp simulator build-and-run --scheme MyApp --project-path ./MyApp.xcodeproj
-```
-
-### Human-readable build-and-run output
-
-For xcodebuild-backed build-and-run tools:
-
-- CLI text mode prints a durable preflight block first
-- interactive terminals then show active phases as live replace-in-place updates
-- warnings, errors, failures, summaries, and next steps are durable output
-- success output order is: front matter -> runtime state/diagnostics -> summary -> execution-derived footer -> next steps
-- failed structured xcodebuild runs do not render next steps
-- compiler/build diagnostics should be grouped into a readable failure block before the failed summary
-- the final footer should only contain execution-derived values such as app path, bundle ID, app ID, or process ID
-- requested values like scheme, project/workspace, configuration, and platform stay in front matter and should not be repeated later
-- when the tool computes a concrete value during execution, prefer showing it directly in the footer instead of relegating it to a hint or redundant next step
-
-For example, a successful build-and-run footer should prefer:
-
-```text
-✅ Build & Run complete
-
- └ App Path: /tmp/.../MyApp.app
-```
-
-rather than forcing the user to run another command just to retrieve a value the tool already knows.
-
-MCP uses the same human-readable formatting semantics, but buffers the rendered output instead of streaming it to stdout live. It is the same section model and ordering, just a different sink.
-
-`--output json` is still streamed JSONL events, not the human-readable section format.
-
-### Testing
-
-```bash
-# Run all tests
-xcodebuildmcp simulator test --scheme MyAppTests --project-path ./MyApp.xcodeproj
-
-# Run with specific simulator
-xcodebuildmcp simulator test --scheme MyAppTests --simulator-name "iPhone 17 Pro"
-
-# Run with pre-resolved test discovery and live progress
-xcodebuildmcp simulator test --json '{"workspacePath":"./MyApp.xcworkspace","scheme":"MyApp","simulatorName":"iPhone 17 Pro","progress":true,"extraArgs":["-only-testing:MyAppTests"]}'
-```
-
-Simulator test output now pre-resolves concrete Swift XCTest and Swift Testing cases when it can, then streams filtered milestones for package resolution, compilation, and test execution plus a grouped failure summary instead of raw `xcodebuild` noise.
-
-For a full list of workflows and tools, see [TOOLS-CLI.md](TOOLS-CLI.md).
-
-## Configuration
-
-The CLI respects the same configuration as the MCP server:
-
-```yaml
-# .xcodebuildmcp/config.yaml
-sessionDefaults:
- scheme: MyApp
- projectPath: ./MyApp.xcodeproj
- simulatorName: iPhone 17 Pro
-
-enabledWorkflows:
- - simulator
- - project-discovery
-```
-
-See [CONFIGURATION.md](CONFIGURATION.md) for the full schema.
-
-To create/update config interactively, run `xcodebuildmcp setup`.
-
-## Environment Variables
-
-| Variable | Description |
-|----------|-------------|
-| `XCODEBUILDMCP_SOCKET` | Override socket path for all commands |
-| `XCODEBUILDMCP_DAEMON_IDLE_TIMEOUT_MS` | Daemon idle timeout in ms (default `600000`, set `0` to disable) |
-| `XCODEBUILDMCP_DISABLE_SESSION_DEFAULTS` | Disable session defaults |
-
-## CLI vs MCP Mode
-
-| Feature | CLI (`xcodebuildmcp `) | MCP (`xcodebuildmcp mcp`) |
-|---------|------------------------------|---------------------------|
-| Invocation | Direct terminal | MCP client (Claude, etc.) |
-| Session state | Stateless direct + daemon for stateful tools | In-process |
-| Use case | Scripts, CI, manual | AI-assisted development |
-| Configuration | Same config.yaml | Same config.yaml |
-
-Both share the same underlying tool implementations.
-
-## Per-Workspace Daemon
-
-The CLI uses a per-workspace daemon architecture only when needed:
-
-- Stateless tools run directly in the CLI process.
-- Stateful tools route through the daemon (auto-started as needed).
-- Dynamic `xcode-ide` bridge tools are a special-case daemon-backed path for persistent bridge sessions.
-
-### How It Works
-
-- **Workspace identity**: The workspace root is determined by the location of `.xcodebuildmcp/config.yaml`, or falls back to the current directory.
-- **Socket location**: Each daemon runs on a Unix socket at `~/.xcodebuildmcp/daemons//daemon.sock`
-- **Auto-start**: The daemon starts automatically when you invoke a stateful tool - no manual setup required.
-- **Auto-shutdown**: The daemon exits after 10 minutes of inactivity, but only when there are no active stateful sessions (log capture, debugging, video capture, background swift-package processes).
-
-### Daemon Commands
-
-```bash
-# Check daemon status for current workspace
-xcodebuildmcp daemon status
-
-# Manually start the daemon
-xcodebuildmcp daemon start
-
-# Stop the daemon
-xcodebuildmcp daemon stop
-
-# Restart the daemon
-xcodebuildmcp daemon restart
-
-# List all daemons across workspaces
-xcodebuildmcp daemon list
-
-# List in JSON format
-xcodebuildmcp daemon list --json
-```
-
-### Daemon Status Output
-
-```
-Daemon Status: Running
- PID: 12345
- Workspace: /Users/you/Projects/MyApp
- Socket: /Users/you/.xcodebuildmcp/daemons/c5da0cbe19a7/daemon.sock
- Started: 2024-01-15T10:30:00.000Z
- Tools: 94
- Workflows: (default)
-```
-
-### Daemon List Output
-
-```
-Daemons:
-
- [running] c5da0cbe19a7
- Workspace: /Users/you/Projects/MyApp
- PID: 12345
- Started: 2024-01-15T10:30:00.000Z
- Version: 1.15.0
-
- [stale] a1b2c3d4e5f6
- Workspace: /Users/you/Projects/OldProject
- PID: 99999
- Started: 2024-01-14T08:00:00.000Z
- Version: 1.14.0
-
-Total: 2 (1 running, 1 stale)
-```
-
-## Stateful vs Stateless Tools
-
-### Stateless Tools (run in-process)
-Most tools run directly without the daemon:
-- `build`, `test`, `clean`
-- `list`, `list-schemes`, `discover-projects`
-- `boot`, `install`, `launch-app` etc.
-
-### Stateful Tools (require daemon)
-Some tools maintain state and route through the daemon:
-- Video recording: `record-video`
-- Debugging: `attach`, `continue`, etc.
-- Background processes: `run`, `stop`
-
-When you invoke a stateful tool, the daemon auto-starts if needed.
-
-## Global Options
-
-| Option | Description |
-|--------|-------------|
-| `--socket ` | Override the daemon socket path (hidden) |
-| `-h, --help` | Show help |
-| `-v, --version` | Show version |
-
-## Troubleshooting
-
-### Daemon won't start
-
-```bash
-# Check for stale sockets
-xcodebuildmcp daemon list
-
-# Force restart
-xcodebuildmcp daemon restart
-
-# Run in foreground to see logs
-xcodebuildmcp daemon start --foreground
-```
-
-### Tool timeout
-
-Increase the daemon startup timeout:
-
-```bash
-# Default is 5 seconds
-export XCODEBUILDMCP_STARTUP_TIMEOUT_MS=10000
-```
-
-### Socket permission errors
-
-The socket directory (`~/.xcodebuildmcp/daemons/`) should have mode 0700. If you encounter permission issues:
-
-```bash
-chmod 700 ~/.xcodebuildmcp
-chmod -R 700 ~/.xcodebuildmcp/daemons
-```
diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md
deleted file mode 100644
index 412b24934..000000000
--- a/docs/CONFIGURATION.md
+++ /dev/null
@@ -1,442 +0,0 @@
-# Configuration
-
-XcodeBuildMCP reads configuration from environment variables and/or a project config file. Both are optional but provide deterministic behavior for every session.
-
-## Contents
-
-- [Config file](#config-file)
-- [Configuration layering](#configuration-layering)
-- [Environment variables](#environment-variables)
-- [Session defaults](#session-defaults)
-- [Workflow selection](#workflow-selection)
-- [Build settings](#build-settings)
-- [Debugging and logging](#debugging-and-logging)
-- [UI automation](#ui-automation)
-- [Templates](#templates)
-- [Telemetry](#telemetry)
-- [Quick reference](#quick-reference)
-
----
-
-## Config file
-
-The config file provides repo-scoped, version-controllable configuration. Create it at your workspace root:
-
-```
-/.xcodebuildmcp/config.yaml
-```
-
-Or run the interactive setup wizard:
-
-```bash
-xcodebuildmcp setup
-```
-
-Minimal example:
-
-```yaml
-schemaVersion: 1
-enabledWorkflows: ["simulator", "ui-automation", "debugging"]
-```
-
-Full example options:
-
-```yaml
-schemaVersion: 1
-
-# Workflow selection
-enabledWorkflows: ["simulator", "ui-automation", "debugging"]
-customWorkflows:
- my-workflow:
- - build_run_sim
- - record_sim_video
- - screenshot
-experimentalWorkflowDiscovery: false
-
-# Session defaults
-disableSessionDefaults: false
-sessionDefaults:
- projectPath: "./MyApp.xcodeproj"
- scheme: "MyApp"
- configuration: "Debug"
- simulatorName: "iPhone 17"
- platform: "iOS"
- useLatestOS: true
- arch: "arm64"
- suppressWarnings: true
- derivedDataPath: "./.derivedData"
- preferXcodebuild: false
- bundleId: "io.sentry.myapp"
-
-# Build settings
-incrementalBuildsEnabled: false
-
-# Debugging
-debug: false
-sentryDisabled: false
-debuggerBackend: "dap"
-dapRequestTimeoutMs: 30000
-dapLogEvents: false
-launchJsonWaitMs: 8000
-
-# UI automation
-uiDebuggerGuardMode: "error"
-axePath: "/opt/axe/bin/axe"
-
-# Templates
-iosTemplatePath: "/path/to/ios/templates"
-iosTemplateVersion: "v1.2.3"
-macosTemplatePath: "/path/to/macos/templates"
-macosTemplateVersion: "v1.2.3"
-```
-
-The `schemaVersion` field is required and currently only supports `1`.
-
----
-
-## Session defaults
-
-Session defaults allow you to set shared values once (project, scheme, simulator, etc.) that all tools reuse automatically. This reduces token usage and ensures consistent behavior.
-
-### Enabling and disabling
-
-Session defaults are enabled by default. To disable them:
-
-```yaml
-disableSessionDefaults: true
-```
-
-When disabled, agents must pass explicit parameters on every tool call (legacy behavior).
-
-### Setting defaults from config
-
-Seed defaults at server startup by adding a `sessionDefaults` block:
-
-```yaml
-sessionDefaults:
- projectPath: "./MyApp.xcodeproj"
- scheme: "MyApp"
- configuration: "Debug"
- simulatorName: "iPhone 17"
-```
-
-### Setting defaults from an agent
-
-Agents can call `session_set_defaults` at runtime. By default these are stored in memory only. To persist them to the config file, the agent sets `persist: true`.
-
-### Session defaults reference
-
-| Option | Type | Description |
-|--------|------|-------------|
-| `projectPath` | string | Path to `.xcodeproj` file. Mutually exclusive with `workspacePath`. |
-| `workspacePath` | string | Path to `.xcworkspace` file. Takes precedence if both are set. |
-| `scheme` | string | Build scheme name. |
-| `configuration` | string | Build configuration (e.g., `Debug`, `Release`). |
-| `simulatorName` | string | Simulator name (e.g., `iPhone 17`). Mutually exclusive with `simulatorId`. |
-| `simulatorId` | string | Simulator UUID. Takes precedence if both are set. |
-| `deviceId` | string | Physical device UUID. |
-| `platform` | string | Target platform (e.g., `iOS`, `macOS`, `watchOS`, `tvOS`, `visionOS`). |
-| `useLatestOS` | boolean | Use the latest available OS version for the simulator. |
-| `arch` | string | Build architecture (e.g., `arm64`, `x86_64`). |
-| `suppressWarnings` | boolean | Suppress compiler warnings in build output. |
-| `derivedDataPath` | string | Custom path for derived data. |
-| `preferXcodebuild` | boolean | Use `xcodebuild` instead of the experimental incremental build support (xcodemake). Only applies when incremental builds are enabled. Is designed for agent use to self correct after failed xcodemake build attempts. |
-| `bundleId` | string | App bundle identifier. |
-
-### Mutual exclusivity rules
-
-- If both `projectPath` and `workspacePath` are set, `workspacePath` wins.
-- If both `simulatorId` and `simulatorName` are set, `simulatorId` wins.
-
----
-
-## Workflow selection
-
-Workflows determine which tools are available. By default only the `simulator` workflow is loaded.
-
-```yaml
-enabledWorkflows: ["simulator", "ui-automation", "debugging"]
-```
-
-See [TOOLS.md](TOOLS.md) for available workflows and their tools.
-
-### Custom workflows
-
-You can define your own workflow names in config and reference them from `enabledWorkflows`.
-Each custom workflow is a list of tool names (MCP names), and only those tools are loaded for that workflow.
-
-```yaml
-enabledWorkflows: ["my-workflow"]
-customWorkflows:
- my-workflow:
- - build_run_sim
- - record_sim_video
- - screenshot
-```
-
-Notes:
-- Built-in implicit workflows are unchanged. Session-management tools are still auto-included, and the doctor workflow is still auto-included when `debug: true`.
-- Custom workflow names are normalized to lowercase.
-- Unknown tool names are ignored and logged as warnings.
-
-To access Xcode IDE tools (Xcode 26+ `xcrun mcpbridge`), enable `xcode-ide`. This workflow exposes `xcode_ide_list_tools` and `xcode_ide_call_tool` for MCP clients. See [XCODE_IDE_MCPBRIDGE.md](XCODE_IDE_MCPBRIDGE.md).
-
-### Experimental workflow discovery
-
-Enables a `manage-workflows` tool that agents can use to add/remove workflows at runtime.
-
-```yaml
-experimentalWorkflowDiscovery: true
-```
-
-> [!IMPORTANT]
-> Requires client support for tools changed notifications. At the time of writing, Cursor, Claude Code, and Codex do not support this.
-
----
-
-## Build settings
-
-### Incremental builds
-
-Enable incremental builds to speed up repeated builds:
-
-```yaml
-incrementalBuildsEnabled: true
-```
-
-> [!IMPORTANT]
-> Incremental builds are experimental and may not work for all projects. Agents can bypass this setting per-call if needed.
-
----
-
-## Debugging and logging
-
-### Debug logging
-
-Enable debug logging and the doctor diagnostic tool:
-
-```yaml
-debug: true
-```
-
-### Debugger backend
-
-Select the debugger backend:
-
-```yaml
-debuggerBackend: "dap" # or "lldb-cli"
-```
-
-Default is `dap`. Changing this is not generally recommended.
-
-### DAP settings
-
-Tune the DAP backend:
-
-```yaml
-dapRequestTimeoutMs: 30000 # default: 30000
-dapLogEvents: true # default: false
-```
-
-### Device log capture
-
-Control how long to wait for devicectl JSON output:
-
-```yaml
-launchJsonWaitMs: 8000 # default: 8000
-```
-
----
-
-## UI automation
-
-### Debugger guard
-
-Block UI automation tools when the debugger is paused to prevent failures:
-
-```yaml
-uiDebuggerGuardMode: "error" # "error" | "warn" | "off"
-```
-
-Default is `error`.
-
-### AXe binary
-
-UI automation and simulator video capture require AXe, which is bundled by default. To use a different version:
-
-```yaml
-axePath: "/opt/axe/bin/axe"
-```
-
-See the [AXe repository](https://github.com/cameroncooke/axe) for more information.
-
----
-
-## Templates
-
-The scaffold tools pull templates from GitHub. Override the default locations and versions:
-
-```yaml
-iosTemplatePath: "/path/to/ios/templates"
-iosTemplateVersion: "v1.2.3"
-macosTemplatePath: "/path/to/macos/templates"
-macosTemplateVersion: "v1.2.3"
-```
-
-Default templates:
-- iOS: [XcodeBuildMCP-iOS-Template](https://github.com/getsentry/XcodeBuildMCP-iOS-Template)
-- macOS: [XcodeBuildMCP-macOS-Template](https://github.com/getsentry/XcodeBuildMCP-macOS-Template)
-
----
-
-## Telemetry
-
-By default, only internal XcodeBuildMCP runtime failures are sent to Sentry. User-domain errors (such as project build/test/config failures) are not sent. To disable telemetry entirely:
-
-```yaml
-sentryDisabled: true
-```
-
-You can also disable telemetry via environment variable:
-
-```bash
-XCODEBUILDMCP_SENTRY_DISABLED=true
-```
-
-See [PRIVACY.md](PRIVACY.md) for more information.
-
-Notes:
-- Sentry SDK logging is enabled for internal observability.
-- Only explicitly opted-in internal logs are forwarded to Sentry; standard console logs are not auto-forwarded.
-- Tool arguments and tool outputs are not captured by MCP wrapper telemetry (`recordInputs: false`, `recordOutputs: false`).
-
----
-
-## Quick reference
-
-| Option | Type | Default |
-|--------|------|---------|
-| `schemaVersion` | number | Required (`1`) |
-| `enabledWorkflows` | string[] | `["simulator"]` |
-| `customWorkflows` | Record | `{}` |
-| `experimentalWorkflowDiscovery` | boolean | `false` |
-| `disableSessionDefaults` | boolean | `false` |
-| `sessionDefaults` | object | `{}` |
-| `incrementalBuildsEnabled` | boolean | `false` |
-| `debug` | boolean | `false` |
-| `sentryDisabled` | boolean | `false` |
-| `debuggerBackend` | string | `"dap"` |
-| `dapRequestTimeoutMs` | number | `30000` |
-| `dapLogEvents` | boolean | `false` |
-| `launchJsonWaitMs` | number | `8000` |
-| `uiDebuggerGuardMode` | string | `"error"` |
-| `axePath` | string | Bundled |
-| `iosTemplatePath` | string | GitHub default |
-| `iosTemplateVersion` | string | Bundled version |
-| `macosTemplatePath` | string | GitHub default |
-| `macosTemplateVersion` | string | Bundled version |
-
----
-
-## Configuration layering
-
-When multiple configuration sources are present, they are merged with clear precedence:
-
-1. **`session_set_defaults` tool** (highest) — agent runtime overrides, set during a session
-2. **Config file** — project-local config (`config.yaml`), committed to repo
-3. **Environment variables** (lowest) — MCP client integration, set in `mcp_config.json`
-
-This follows the same pattern as tools like `git config` (`--flag` > `--local` > `--global`). Each layer serves a different context:
-
-- **Config file** is the canonical home for structured, repo-scoped, version-controlled settings.
-- **Env vars** are best used to bootstrap flat startup defaults for MCP clients with limited workspace support.
-- **Tool calls** are for agent-driven runtime adjustments.
-
----
-
-## Environment variables
-
-Environment variables are supported for MCP client integration when a client cannot reliably provide workspace context to the server. Set them in the `env` field of your MCP client config (for example `mcp_config.json` for Windsurf, `.vscode/mcp.json` for VS Code, or `claude_desktop_config.json` for Claude Desktop).
-
-Use env vars for flat bootstrap values such as startup workflow selection, project path, scheme, simulator selector, or other scalar defaults. Keep structured project-owned configuration in `config.yaml`.
-
-### General settings
-
-| Config option | Environment variable |
-|---------------|---------------------|
-| `enabledWorkflows` | `XCODEBUILDMCP_ENABLED_WORKFLOWS` (comma-separated) |
-| `experimentalWorkflowDiscovery` | `XCODEBUILDMCP_EXPERIMENTAL_WORKFLOW_DISCOVERY` |
-| `disableSessionDefaults` | `XCODEBUILDMCP_DISABLE_SESSION_DEFAULTS` |
-| `disableXcodeAutoSync` | `XCODEBUILDMCP_DISABLE_XCODE_AUTO_SYNC` |
-| `incrementalBuildsEnabled` | `INCREMENTAL_BUILDS_ENABLED` |
-| `debug` | `XCODEBUILDMCP_DEBUG` |
-| `sentryDisabled` | `XCODEBUILDMCP_SENTRY_DISABLED` |
-| `debuggerBackend` | `XCODEBUILDMCP_DEBUGGER_BACKEND` |
-| `dapRequestTimeoutMs` | `XCODEBUILDMCP_DAP_REQUEST_TIMEOUT_MS` |
-| `dapLogEvents` | `XCODEBUILDMCP_DAP_LOG_EVENTS` |
-| `launchJsonWaitMs` | `XBMCP_LAUNCH_JSON_WAIT_MS` |
-| `uiDebuggerGuardMode` | `XCODEBUILDMCP_UI_DEBUGGER_GUARD_MODE` |
-| `axePath` | `XCODEBUILDMCP_AXE_PATH` |
-| `iosTemplatePath` | `XCODEBUILDMCP_IOS_TEMPLATE_PATH` |
-| `iosTemplateVersion` | `XCODEBUILD_MCP_IOS_TEMPLATE_VERSION` |
-| `macosTemplatePath` | `XCODEBUILDMCP_MACOS_TEMPLATE_PATH` |
-| `macosTemplateVersion` | `XCODEBUILD_MCP_MACOS_TEMPLATE_VERSION` |
-
-### Session default bootstrap values
-
-| Session default | Environment variable |
-|----------------|---------------------|
-| `workspacePath` | `XCODEBUILDMCP_WORKSPACE_PATH` |
-| `projectPath` | `XCODEBUILDMCP_PROJECT_PATH` |
-| `scheme` | `XCODEBUILDMCP_SCHEME` |
-| `configuration` | `XCODEBUILDMCP_CONFIGURATION` |
-| `simulatorName` | `XCODEBUILDMCP_SIMULATOR_NAME` |
-| `simulatorId` | `XCODEBUILDMCP_SIMULATOR_ID` |
-| `simulatorPlatform` | `XCODEBUILDMCP_SIMULATOR_PLATFORM` |
-| `deviceId` | `XCODEBUILDMCP_DEVICE_ID` |
-| `platform` | `XCODEBUILDMCP_PLATFORM` |
-| `useLatestOS` | `XCODEBUILDMCP_USE_LATEST_OS` |
-| `arch` | `XCODEBUILDMCP_ARCH` |
-| `suppressWarnings` | `XCODEBUILDMCP_SUPPRESS_WARNINGS` |
-| `derivedDataPath` | `XCODEBUILDMCP_DERIVED_DATA_PATH` |
-| `preferXcodebuild` | `XCODEBUILDMCP_PREFER_XCODEBUILD` |
-| `bundleId` | `XCODEBUILDMCP_BUNDLE_ID` |
-
-### Example MCP config
-
-Use this when your MCP client needs env-based bootstrap defaults:
-
-```json
-{
- "mcpServers": {
- "XcodeBuildMCP": {
- "command": "npx",
- "args": ["-y", "xcodebuildmcp@latest", "mcp"],
- "env": {
- "XCODEBUILDMCP_ENABLED_WORKFLOWS": "simulator,debugging,logging",
- "XCODEBUILDMCP_WORKSPACE_PATH": "/Users/me/MyApp/MyApp.xcworkspace",
- "XCODEBUILDMCP_SCHEME": "MyApp",
- "XCODEBUILDMCP_PLATFORM": "iOS Simulator",
- "XCODEBUILDMCP_SIMULATOR_NAME": "iPhone 16 Pro"
- }
- }
- }
-}
-```
-
-You can also export a bootstrap block interactively:
-
-```bash
-xcodebuildmcp setup --format mcp-json
-```
-
-That export is intended for MCP client bootstrap. It does not replace `config.yaml` as the canonical project configuration.
-
----
-
-## Related docs
-
-- Session defaults: [SESSION_DEFAULTS.md](SESSION_DEFAULTS.md)
-- Tools reference: [TOOLS.md](TOOLS.md)
-- Privacy and telemetry: [PRIVACY.md](PRIVACY.md)
-- Troubleshooting: [TROUBLESHOOTING.md](TROUBLESHOOTING.md)
diff --git a/docs/DAP_BACKEND_IMPLEMENTATION_PLAN.md b/docs/DAP_BACKEND_IMPLEMENTATION_PLAN.md
deleted file mode 100644
index 75b10f822..000000000
--- a/docs/DAP_BACKEND_IMPLEMENTATION_PLAN.md
+++ /dev/null
@@ -1,571 +0,0 @@
-## Goal & constraints (grounded in current code)
-
-Implement a real **`lldb-dap` Debug Adapter Protocol backend** that plugs into the existing debugger architecture without changing MCP tool names/schemas. The DAP backend remains **opt-in only** via `XCODEBUILDMCP_DEBUGGER_BACKEND=dap` (current selection logic in `src/utils/debugger/debugger-manager.ts`).
-
-Key integration points already in place:
-
-- **Backend contract**: `src/utils/debugger/backends/DebuggerBackend.ts`
-- **Backend selection & session lifecycle**: `src/utils/debugger/debugger-manager.ts`
-- **MCP tool surface area**: `src/mcp/tools/debugging/*` (attach, breakpoints, stack, variables, command, detach)
-- **Subprocess patterns**: `src/utils/execution/interactive-process.ts` (interactive, piped stdio, test-safe default spawner)
-- **DI/test safety**: defaults throw under Vitest (`getDefaultCommandExecutor`, `getDefaultInteractiveSpawner`)
-- **Docs baseline**: `docs/DAP_BACKEND_IMPLEMENTATION_PLAN.md`, `docs/DEBUGGING_ARCHITECTURE.md`
-
----
-
-## Implementation status (current)
-
-Implemented modules and behavior (as of this document):
-
-- DAP protocol and transport: `src/utils/debugger/dap/types.ts`, `src/utils/debugger/dap/transport.ts`
-- Adapter discovery: `src/utils/debugger/dap/adapter-discovery.ts`
-- Backend implementation: `src/utils/debugger/backends/dap-backend.ts`
-- Conditional breakpoints: backend-level support via `DebuggerBackend.addBreakpoint(..., { condition })`
-- Tool updates: `src/mcp/tools/debugging/debug_breakpoint_add.ts` passes conditions to backend
-- Health check: `doctor` now reports `lldb-dap` availability
-- Tests: DAP transport framing, backend mapping, and debugger manager selection tests
-
-### MCP tool → DAP request mapping (current)
-
-| MCP tool | DebuggerManager call | DAP requests |
-| --- | --- | --- |
-| `debug_attach_sim` | `createSession` → `attach` | `initialize` → `attach` → `configurationDone` |
-| `debug_lldb_command` | `runCommand` | `evaluate` (context: `repl`) |
-| `debug_stack` | `getStack` | `threads` → `stackTrace` |
-| `debug_variables` | `getVariables` | `threads` → `stackTrace` → `scopes` → `variables` |
-| `debug_breakpoint_add` | `addBreakpoint` | `setBreakpoints` / `setFunctionBreakpoints` |
-| `debug_breakpoint_remove` | `removeBreakpoint` | `setBreakpoints` / `setFunctionBreakpoints` |
-| `debug_detach` | `detach` | `disconnect` |
-
-### Breakpoint strategy (current)
-
-- Breakpoints are stateful: DAP removal re-applies `setBreakpoints`/`setFunctionBreakpoints` with the remaining list.
-- Conditions are passed as part of the breakpoint request in both backends:
- - DAP: `breakpoints[].condition` or `functionBreakpoints[].condition`
- - LLDB CLI: `breakpoint modify -c "" `
-
----
-
-## Architectural decisions to make (explicit)
-
-### 1) Spawn model: one `lldb-dap` process per debug session
-**Decision**: Each `DebuggerManager.createSession()` creates a new backend instance, which owns a single `lldb-dap` subprocess for the lifetime of that session.
-
-- Aligns with current LLDB CLI backend (one long-lived interactive `lldb` per session).
-- Keeps multi-session support (`DebuggerManager.sessions: Map`) straightforward.
-
-### 2) Transport abstraction: DAP framing + request correlation in a dedicated module
-**Decision**: Build a dedicated DAP transport that:
-- implements `Content-Length` framing
-- correlates requests/responses by `seq`
-- emits DAP events
-
-This keeps `DapBackend` focused on **mapping MCP tool operations → DAP requests**.
-
-### 3) Breakpoint conditions support: move condition handling into the backend API
-**Decision**: Extend internal debugger API to support conditional breakpoints *without relying on* “LLDB command follow-ups” (which are CLI-specific).
-
-This avoids depending on DAP `evaluate` for breakpoint modification and keeps semantics consistent across backends.
-
----
-
-## Implementation plan (by component / file)
-
-### A) Add DAP protocol & transport layer
-
-#### New files
-
-##### 1) `src/utils/debugger/dap/types.ts`
-Define minimal DAP types used by the backend (not a full spec).
-
-Example types (illustrative, not exhaustive):
-
-```ts
-export type DapRequest = {
- seq: number;
- type: 'request';
- command: string;
- arguments?: C;
-};
-
-export type DapResponse = {
- seq: number;
- type: 'response';
- request_seq: number;
- success: boolean;
- command: string;
- message?: string;
- body?: B;
-};
-
-export type DapEvent = {
- seq: number;
- type: 'event';
- event: string;
- body?: B;
-};
-```
-
-Also define bodies used in mapping:
-- `InitializeResponseBody` (capabilities)
-- `ThreadsResponseBody`
-- `StackTraceResponseBody`
-- `ScopesResponseBody`
-- `VariablesResponseBody`
-- `SetBreakpointsResponseBody`
-- `EvaluateResponseBody`
-- event bodies: `StoppedEventBody`, `OutputEventBody`, `TerminatedEventBody`
-
-**Side effects / impact**: none outside debugger subsystem; ensures type safety inside DAP modules.
-
----
-
-##### 2) `src/utils/debugger/dap/transport.ts`
-Implement DAP over stdio.
-
-**Dependencies / imports**
-- `node:events` (EventEmitter) or a small typed emitter pattern
-- `src/utils/execution/index.ts` for `InteractiveSpawner` and `InteractiveProcess` types
-- `src/utils/logging/index.ts` for `log`
-- `src/utils/CommandExecutor.ts` type (for adapter discovery helper if kept here)
-
-**Core responsibilities**
-- Spawn adapter process (or accept an already spawned `InteractiveProcess`)
-- Parse stdout stream into discrete DAP messages using `Content-Length` framing
-- Maintain:
- - `nextSeq: number`
- - `pending: Map` keyed by request `seq`
-- Expose:
- - `sendRequest(command, args, opts?) => Promise`
- - event subscription: `onEvent(handler)` or `on('event', ...)`
- - lifecycle: `dispose()` (must not throw)
-
-**Key function signatures**
-
-```ts
-export type DapTransportOptions = {
- spawner: InteractiveSpawner;
- adapterCommand: string[]; // e.g. ['xcrun', 'lldb-dap'] or [resolvedPath]
- env?: Record;
- cwd?: string;
- logPrefix?: string;
-};
-
-export class DapTransport {
- constructor(opts: DapTransportOptions);
-
- sendRequest(
- command: string,
- args?: A,
- opts?: { timeoutMs?: number },
- ): Promise;
-
- onEvent(handler: (evt: DapEvent) => void): () => void;
-
- dispose(): void; // best-effort, never throw
-}
-```
-
-**Framing logic**
-- Maintain an internal `Buffer`/string accumulator for stdout.
-- Repeatedly:
- - find `\r\n\r\n`
- - parse headers for `Content-Length`
- - wait until body bytes are available
- - `JSON.parse` body into `{ type: 'response' | 'event' | 'request' }`
-
-**Process failure handling**
-- On adapter `exit`/`error`, reject all pending requests with a clear error (and include exit detail).
-- Log stderr output at `debug` level; do **not** feed stderr into framing.
-
-**Concurrency**
-- Transport supports multiple in-flight requests concurrently (DAP allows it).
-- Backend may still serialize higher-level operations if stateful.
-
-**Side effects**
-- Add a long-lived child process per session.
-- Requires careful memory management in the framing buffer (ensure you slice consumed bytes).
-
----
-
-### B) Adapter discovery (`xcrun --find lldb-dap`)
-
-#### New helper (recommended)
-##### 3) `src/utils/debugger/dap/adapter-discovery.ts` (new)
-**Purpose**: centralize resolution and produce actionable errors when DAP is explicitly selected but unavailable.
-
-**Uses**
-- `CommandExecutor` to run `xcrun --find lldb-dap`
-- `log` for diagnostics
-- throw a `DependencyError` (from `src/utils/errors.ts`) or plain `Error` with a consistent message
-
-Example signature:
-
-```ts
-import type { CommandExecutor } from '../../execution/index.ts';
-
-export async function resolveLldbDapCommand(opts: {
- executor: CommandExecutor;
-}): Promise;
-// returns e.g. ['xcrun', 'lldb-dap'] OR [absolutePath]
-```
-
-**Design choice**
-- Returning `['xcrun','lldb-dap']` is simplest (no dependency on parsing).
-- Returning `[absolutePath]` provides a stronger “tool exists” guarantee.
-
-**Impact**
-- Enables a clean error message early in session creation.
-- Keeps `DapBackend` simpler.
-
----
-
-### C) Implement `DapBackend` (current)
-
-#### Modify file: `src/utils/debugger/backends/dap-backend.ts`
-
-**Implemented** as a real backend that:
-- discovers adapter (`resolveLldbDapCommand`)
-- creates `DapTransport`
-- performs DAP handshake (`initialize`)
-- attaches by PID (`attach`)
-- maps backend interface methods to DAP requests
-
-**Dependencies**
-- `DapTransport`
-- `resolveLldbDapCommand`
-- `getDefaultCommandExecutor` and `getDefaultInteractiveSpawner` (production defaults)
-- `log`
-- existing backend interface/types
-
-**Constructor / factory**
-Update `createDapBackend()` to accept injectable deps, mirroring the CLI backend’s injection style.
-
-```ts
-export async function createDapBackend(opts?: {
- executor?: CommandExecutor;
- spawner?: InteractiveSpawner;
- requestTimeoutMs?: number;
-}): Promise;
-```
-
-> This is critical for tests because defaults throw under Vitest.
-
-**Session state to maintain inside `DapBackend`**
-- `transport: DapTransport | null`
-- `attached: boolean`
-- `lastStoppedThreadId: number | null`
-- `cachedThreads: { id: number; name?: string }[] | null` (optional)
-- breakpoint registry:
- - `breakpointsById: Map`
- - for DAP “remove breakpoint”, you must re-issue `setBreakpoints`/`setFunctionBreakpoints` with the updated list, so also keep:
- - `fileLineBreakpointsByFile: Map>`
- - `functionBreakpoints: Array<{ name: string; condition?: string; id?: number }>`
-- optional cached stack frames from the last `stackTrace` call (for variables lookup)
-
-**Backend lifecycle mapping**
-- `attach()`:
- 1) spawn `lldb-dap`
- 2) `initialize`
- 3) `attach` with pid (+ waitFor mapping)
- 4) `configurationDone` if required by lldb-dap behavior (plan for it even if no-op)
- 5) mark attached
-
-- `detach()`
- - send `disconnect` with `terminateDebuggee: false` (do not kill app)
- - dispose transport / kill process
-
-- `dispose()`
- - best-effort cleanup; **must not throw** (important because `DebuggerManager.createSession` calls dispose to clean up on attach failure)
-
-**Method mappings (MCP tools → DebuggerManager → DapBackend)**
-
-1) `runCommand(command: string, opts?)`
-- Map to DAP `evaluate` with `context: 'repl'`
-- Return string output from `EvaluateResponse.body.result` and/or `body.output`
-- If adapter doesn’t support command-style repl evaluation, return a clear error message suggesting `lldb-cli` backend.
-
-2) `getStack(opts?: { threadIndex?: number; maxFrames?: number })`
-- DAP sequence:
- - `threads`
- - select thread:
- - if a `stopped` event has a `threadId`, prefer that when `threadIndex` is undefined
- - else map `threadIndex` to array index (document this)
- - `stackTrace({ threadId, startFrame: 0, levels: maxFrames })`
-- Format output as readable text (LLDB-like) to keep tool behavior familiar:
- - `frame #: at :`
-- If stackTrace fails due to running state, return a helpful error:
- - “Process is running; pause or hit a breakpoint to fetch stack.”
-
-3) `getVariables(opts?: { frameIndex?: number })`
-- DAP sequence:
- - resolve thread as above
- - `stackTrace` to get frames
- - choose frame by `frameIndex` (default 0)
- - `scopes({ frameId })`
- - for each scope: `variables({ variablesReference })`
-- Format output as text with sections per scope:
- - `Locals:\n x = 1\n y = ...`
-
-4) `addBreakpoint(spec: BreakpointSpec, opts?: { condition?: string })`
-- For `file-line`:
- - update `fileLineBreakpointsByFile[file]`
- - call `setBreakpoints({ source: { path: file }, breakpoints: [{ line, condition }] })`
- - parse returned `breakpoints[]` to find matching line and capture `id`
-- For `function`:
- - update `functionBreakpoints`
- - call `setFunctionBreakpoints({ breakpoints: [{ name, condition }] })`
-- Return `BreakpointInfo`:
- - `id` must be a number (from DAP breakpoint id; if missing, generate a synthetic id and store mapping, but prefer real id)
- - `rawOutput` can be a pretty JSON snippet or a short text summary
-
-5) `removeBreakpoint(id: number)`
-- Look up spec in `breakpointsById`
-- Remove it from the corresponding registry
-- Re-issue `setBreakpoints` or `setFunctionBreakpoints` with the remaining breakpoints
-- Return text confirmation
-
-**Important: DAP vs existing condition flow**
-- Today `debug_breakpoint_add` sets condition by issuing an LLDB command after creation.
-- With the above, condition becomes part of breakpoint creation and removal logic, backend-agnostic.
-
----
-
-### D) Internal API adjustment for conditional breakpoints (recommended)
-
-#### Modify: `src/utils/debugger/backends/DebuggerBackend.ts`
-Update signature:
-
-```ts
-addBreakpoint(spec: BreakpointSpec, opts?: { condition?: string }): Promise;
-```
-
-#### Modify: `src/utils/debugger/debugger-manager.ts`
-Update method:
-
-```ts
-async addBreakpoint(
- id: string | undefined,
- spec: BreakpointSpec,
- opts?: { condition?: string },
-): Promise
-```
-
-Pass `opts` through to `backend.addBreakpoint`.
-
-**Impact**
-- Requires updating both backends + the tool call site.
-- Improves cross-backend compatibility and avoids “DAP evaluate must support breakpoint modify”.
-
-#### Modify: `src/utils/debugger/backends/lldb-cli-backend.ts`
-Implement condition via LLDB command internally after breakpoint creation (current behavior, just moved):
-
-- after parsing breakpoint id:
- - if `opts?.condition`, run `breakpoint modify -c "" `
-
-This keeps condition support identical for LLDB CLI users.
-
----
-
-### E) Update MCP tool logic to use new breakpoint API
-
-#### Modify: `src/mcp/tools/debugging/debug_breakpoint_add.ts`
-Change logic to pass `condition` into `ctx.debugger.addBreakpoint(...)` and remove the follow-up `breakpoint modify ...` command.
-
-**Before**
-- call `addBreakpoint()`
-- if condition, call `runCommand("breakpoint modify ...")`
-
-**After**
-- call `addBreakpoint(sessionId, spec, { condition })`
-- no extra `runCommand` required
-
-**Impact / side effects**
-- Output remains the same shape, but the “rawOutput” content for DAP may differ (acceptable).
-- Improves backend portability.
-
----
-
-### F) Backend selection & opt-in behavior (already mostly correct)
-
-#### Modify (optional but recommended): `src/utils/debugger/debugger-manager.ts`
-Keep selection rules but improve failure clarity:
-
-- If backend kind is `dap`, and adapter discovery fails, throw an error like:
- - `DAP backend selected but lldb-dap not found. Ensure Xcode is installed and xcrun can locate lldb-dap, or set XCODEBUILDMCP_DEBUGGER_BACKEND=lldb-cli.`
-
-Also ensure that dispose failures do not mask attach failures:
-- in `createSession` catch, wrap `dispose()` in its own try/catch (even if backend should not throw).
-
----
-
-### G) Diagnostics / “doctor” integration (validation surface)
-
-#### Modify: `src/mcp/tools/doctor/doctor.ts` (not shown in provided contents)
-Add a DAP capability line:
-- `lldb-dap available: yes/no`
-- if env selects dap, include a prominent warning/error section when missing
-
-Implementation approach:
-- reuse `CommandExecutor` and call `xcrun --find lldb-dap`
-- do not fail doctor entirely if missing; just report
-
-**Side effects**
-- Improves discoverability and reduces “mystery failures” when users opt into dap.
-
----
-
-## Concurrency & state management plan
-
-### Transport-level
-- Fully concurrent in-flight DAP requests supported via:
- - `seq` generation
- - `pending` map keyed by `seq`
-- Each request can set its own timeout (`timeoutMs`).
-
-### Backend-level
-Use a serialized queue **only where state mutation occurs**, e.g.:
-- updating breakpoint registries
-- attach/detach transitions
-
-Pattern (same as LLDB CLI backend):
-
-```ts
-private queue: Promise = Promise.resolve();
-
-private enqueue(work: () => Promise): Promise { ... }
-```
-
-**Reasoning**
-- Prevent races such as:
- - addBreakpoint + removeBreakpoint in parallel, reissuing `setBreakpoints` inconsistently.
-
----
-
-## Error handling & logging strategy
-
-### Error taxonomy (pragmatic, consistent with current tools)
-- Backend throws `Error` with clear messages.
-- MCP tools already catch and wrap errors via `createErrorResponse(...)`.
-
-### Where to log
-- `DapTransport`:
- - `log('debug', ...)` for raw events (optionally gated by env)
- - `log('error', ...)` on process exit while requests are pending
-- `DapBackend`:
- - minimal `info` logs on attach/detach
- - `debug` logs for request mapping (command names, not full payloads unless opted in)
-
-### New optional env flags (config plan)
-Document these (no need to require them):
-- `XCODEBUILDMCP_DAP_REQUEST_TIMEOUT_MS` (default to 30_000)
-- `XCODEBUILDMCP_DAP_LOG_EVENTS=true` (default false)
-
----
-
-## Tests (architecture-aware, DI-compliant)
-
-Even though this is “testing”, it directly impacts design because default spawners/executors throw under Vitest.
-
-### 1) Add a first-class mock interactive spawner utility
-#### Modify: `src/test-utils/mock-executors.ts`
-Add:
-
-```ts
-export function createMockInteractiveSpawner(script: {
- // map writes -> stdout/stderr emissions, or a programmable fake
-}): InteractiveSpawner;
-```
-
-This avoids ad-hoc manual mocks and matches the project’s “approved mocks live in test-utils” philosophy.
-
-### 2) DAP framing tests
-New: `src/utils/debugger/dap/__tests__/transport-framing.test.ts`
-- Feed partial header/body chunks into the transport parser using `PassThrough` streams behind a mock InteractiveProcess.
-- Assert:
- - correct parsing across chunk boundaries
- - multiple messages in one chunk
- - invalid Content-Length handling
-
-### 3) Backend mapping tests (no real lldb-dap)
-New: `src/utils/debugger/backends/__tests__/dap-backend.test.ts`
-- Use `createMockExecutor()` to fake adapter discovery.
-- Use `createMockInteractiveSpawner()` to simulate an adapter that returns scripted DAP responses:
- - initialize → success
- - attach → success
- - threads/stackTrace/scopes/variables → stable fixtures
-- Validate:
- - `getStack()` formatting
- - `getVariables()` formatting
- - breakpoint add/remove registry behavior
- - `dispose()` never throws
-
-### 4) DebuggerManager selection test
-New: `src/utils/debugger/__tests__/debugger-manager-dap.test.ts`
-- Inject a custom `backendFactory` that returns a fake backend (or the scripted DAP backend) and verify:
- - env selection
- - attach failure triggers dispose
- - current session behavior unchanged
-
----
-
-## Docs updates (grounded in existing docs)
-
-### 1) Update `docs/DAP_BACKEND_IMPLEMENTATION_PLAN.md`
-Replace/extend the existing outline with the following:
-- finalized module list (`dap/types.ts`, `dap/transport.ts`, discovery helper)
-- breakpoint strategy (stateful re-issue `setBreakpoints`)
-- explicit mapping table per MCP tool
-
-### 2) Update `docs/DEBUGGING_ARCHITECTURE.md`
-Add a section “DAP Backend (lldb-dap)”:
-- how it’s selected (opt-in)
-- differences vs LLDB CLI (structured stack/variables, breakpoint reapplication)
-- note about process state (stack/variables usually require stopped context)
-- explain that conditional breakpoints are implemented backend-side
-
----
-
-## Configuration & validation steps (manual / operational)
-
-### Validation steps (local)
-1. Ensure `lldb-dap` is discoverable:
- - `xcrun --find lldb-dap`
-2. Run server with DAP enabled:
- - `XCODEBUILDMCP_DEBUGGER_BACKEND=dap node build/cli.js mcp`
-3. Use existing MCP tool flow:
- - `debug_attach_sim` (attach by PID or bundleId)
- - `debug_breakpoint_add` (with condition)
- - trigger breakpoint (or pause via `debug_lldb_command` if implemented via evaluate)
- - `debug_stack`, `debug_variables`
- - `debug_detach`
-
-### Expected behavioral constraints to document
-- If the target is running and no stop context exists, DAP `stackTrace`/`variables` may fail; return guidance in tool output (“pause or set breakpoint”).
-
----
-
-## Summary of files modified / added
-
-### Add
-- `src/utils/debugger/dap/types.ts`
-- `src/utils/debugger/dap/transport.ts`
-- `src/utils/debugger/dap/adapter-discovery.ts` (recommended)
-
-### Modify
-- `src/utils/debugger/backends/dap-backend.ts` (real implementation)
-- `src/utils/debugger/backends/DebuggerBackend.ts` (add breakpoint condition option)
-- `src/utils/debugger/backends/lldb-cli-backend.ts` (support condition via new opts)
-- `src/utils/debugger/debugger-manager.ts` (pass-through opts; optional improved error handling)
-- `src/mcp/tools/debugging/debug_breakpoint_add.ts` (use backend-level condition support)
-- `src/mcp/tools/doctor/doctor.ts` (report `lldb-dap` availability)
-- `docs/DAP_BACKEND_IMPLEMENTATION_PLAN.md`
-- `docs/DEBUGGING_ARCHITECTURE.md`
-- `src/test-utils/mock-executors.ts` (add mock interactive spawner)
-
----
-
-## Critical “don’t miss” requirements
-- `dispose()` in DAP backend and transport must be **best-effort and never throw** because `DebuggerManager.createSession()` will call dispose on attach failure.
-- Avoid any use of default executors/spawners in tests; ensure `createDapBackend()` accepts injected `executor` + `spawner`.
-- Breakpoint removal requires stateful re-application with `setBreakpoints` / `setFunctionBreakpoints`; plan for breakpoint registries from day one.
diff --git a/docs/DEBUGGING_ARCHITECTURE.md b/docs/DEBUGGING_ARCHITECTURE.md
deleted file mode 100644
index 88abb1fb5..000000000
--- a/docs/DEBUGGING_ARCHITECTURE.md
+++ /dev/null
@@ -1,276 +0,0 @@
-# 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.
-
-
-Sequence diagrams (Mermaid)
-
-```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
-```
-
-
-
-## 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 "`.
-- `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 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 "" ` 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`
diff --git a/docs/DEMOS.md b/docs/DEMOS.md
deleted file mode 100644
index 80bd1b124..000000000
--- a/docs/DEMOS.md
+++ /dev/null
@@ -1,15 +0,0 @@
-# Demos
-
-## Autonomously fixing build errors in Cursor
-
-
-## Utilising the new UI automation and screen capture features
-
-
-## Building and running iOS app in Claude Desktop
-https://github.com/user-attachments/assets/e3c08d75-8be6-4857-b4d0-9350b26ef086
-
-## Related docs
-- Getting started: [GETTING_STARTED.md](GETTING_STARTED.md)
-- Tools reference: [TOOLS.md](TOOLS.md)
-- Session defaults: [SESSION_DEFAULTS.md](SESSION_DEFAULTS.md)
diff --git a/docs/DEVICE_CODE_SIGNING.md b/docs/DEVICE_CODE_SIGNING.md
deleted file mode 100644
index f6bd72920..000000000
--- a/docs/DEVICE_CODE_SIGNING.md
+++ /dev/null
@@ -1,18 +0,0 @@
-# Device Code Signing
-
-For device deployment features to work, code signing must be configured in Xcode before using XcodeBuildMCP device tools.
-
-## One-time setup in Xcode
-1. Open your project in Xcode.
-2. Select your project target.
-3. Open the "Signing & Capabilities" tab.
-4. Enable "Automatically manage signing" and select your development team.
-5. Ensure a valid provisioning profile is selected.
-
-## What XcodeBuildMCP can and cannot do
-- Can build, install, launch, and test once signing is configured.
-- Cannot configure code signing automatically.
-
-## Related docs
-- Tools reference: [TOOLS.md](TOOLS.md)
-- Troubleshooting: [TROUBLESHOOTING.md](TROUBLESHOOTING.md)
diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md
deleted file mode 100644
index e03a396d9..000000000
--- a/docs/GETTING_STARTED.md
+++ /dev/null
@@ -1,165 +0,0 @@
-# Getting Started
-
-## Prerequisites
-- macOS 14.5 or later
-- Xcode 16.x or later
-- Node.js 18.x or later (not required for Homebrew installation)
-
-## Choose Your Interface
-
-XcodeBuildMCP provides a unified CLI with two modes:
-
-| Command | Use Case |
-|---------|----------|
-| `xcodebuildmcp mcp` | Start MCP server for AI-assisted development |
-| `xcodebuildmcp ` | Direct terminal usage, scripts, CI pipelines |
-
-Both share the same tools and configuration.
-
-## Installation
-
-Both methods give you the CLI and the MCP server.
-
-### Option A — Homebrew (no Node.js required)
-
-```bash
-brew tap getsentry/xcodebuildmcp
-brew install xcodebuildmcp
-```
-
-Use the CLI:
-```bash
-xcodebuildmcp --help
-```
-
-MCP client config:
-```json
-"XcodeBuildMCP": {
- "command": "xcodebuildmcp",
- "args": ["mcp"]
-}
-```
-
-Upgrade later with `brew update && brew upgrade xcodebuildmcp`.
-
-### Option B — npm / npx (Node.js 18+)
-
-**For CLI use**, install globally:
-```bash
-npm install -g xcodebuildmcp@latest
-xcodebuildmcp --help
-```
-
-**For MCP server only**, no global install needed — add directly to your client config:
-```json
-"XcodeBuildMCP": {
- "command": "npx",
- "args": ["-y", "xcodebuildmcp@latest", "mcp"]
-}
-```
-
-Using `@latest` ensures clients resolve the newest version on each run.
-
-See [CLI.md](CLI.md) for full CLI documentation.
-
-### Checking for updates
-
-After installing, check for newer releases at any time:
-
-```bash
-xcodebuildmcp upgrade --check
-```
-
-Homebrew and npm-global installs can auto-upgrade with `xcodebuildmcp upgrade --yes`. npx users don't need to upgrade explicitly — `@latest` resolves the newest version on each run. If you pinned a specific version in your MCP client config, update the version there instead.
-
-## Project config (optional)
-For deterministic session defaults and runtime configuration, add a config file at:
-
-```text
-/.xcodebuildmcp/config.yaml
-```
-
-Use the setup wizard to create or update this file interactively:
-
-```bash
-xcodebuildmcp setup
-```
-
-See [CONFIGURATION.md](CONFIGURATION.md) for the full schema and examples.
-
-## Client-specific configuration
-
-The examples below use npx (Option B). If you installed via Homebrew, replace the command with `"command": "xcodebuildmcp", "args": ["mcp"]` instead.
-
-### Cursor
-Recommended (project-scoped): create `.cursor/mcp.json` in your project root:
-
-```json
-{
- "mcpServers": {
- "XcodeBuildMCP": {
- "command": "npx",
- "args": ["-y", "xcodebuildmcp@latest", "mcp"]
- }
- }
-}
-```
-
-If you use a global Cursor config at `~/.cursor/mcp.json`, use this variant to align startup with the active workspace:
-
-```json
-{
- "mcpServers": {
- "XcodeBuildMCP": {
- "command": "/bin/zsh",
- "args": [
- "-lc",
- "cd \"${workspaceFolder}\" && exec npx -y xcodebuildmcp@latest mcp"
- ]
- }
- }
-}
-```
-
-### OpenAI Codex CLI
-Codex uses TOML for MCP configuration. Add this to `~/.codex/config.toml`:
-
-```toml
-[mcp_servers.XcodeBuildMCP]
-command = "npx"
-args = ["-y", "xcodebuildmcp@latest", "mcp"]
-env = { "XCODEBUILDMCP_SENTRY_DISABLED" = "false" }
-```
-
-If you see tool calls timing out (for example, `timed out awaiting tools/call after 60s`), increase the timeout:
-
-```toml
-tool_timeout_sec = 600
-```
-
-For more info see the OpenAI Codex configuration docs:
-https://github.com/openai/codex/blob/main/docs/config.md#connecting-to-mcp-servers
-
-### Claude Code CLI
-```bash
-# Add XcodeBuildMCP server to Claude Code
-claude mcp add XcodeBuildMCP -- npx -y xcodebuildmcp@latest mcp
-
-# Or with environment variables
-claude mcp add XcodeBuildMCP -e XCODEBUILDMCP_SENTRY_DISABLED=false -- npx -y xcodebuildmcp@latest mcp
-```
-
-Note: XcodeBuildMCP requests xcodebuild to skip macro validation to avoid Swift Macro build errors.
-
-### AdaL CLI
-Run the following command inside the AdaL CLI prompt:
-```console
-/mcp add XcodeBuildMCP --command npx --args "-y,xcodebuildmcp@latest,mcp"
-```
-
-## Next steps
-- Configuration options: [CONFIGURATION.md](CONFIGURATION.md)
-- Session defaults and opt-out: [SESSION_DEFAULTS.md](SESSION_DEFAULTS.md)
-- Tools reference: [TOOLS.md](TOOLS.md)
-- CLI guide: [CLI.md](CLI.md)
-- Troubleshooting: [TROUBLESHOOTING.md](TROUBLESHOOTING.md)
diff --git a/docs/MIGRATION_V2.md b/docs/MIGRATION_V2.md
deleted file mode 100644
index cd870f5f6..000000000
--- a/docs/MIGRATION_V2.md
+++ /dev/null
@@ -1,252 +0,0 @@
-# Migrating to XcodeBuildMCP v2.0.0
-
-This guide covers breaking changes and required actions when upgrading from v1.x to v2.0.0.
-
-## Quick migration
-
-There are two breaking changes. Most users only need to do step 1.
-
-**1. Append `mcp` to your server launch command.**
-
-The `xcodebuildmcp` binary is now a CLI. To start the MCP server you must pass the `mcp` subcommand. Without it the MCP client will fail to connect.
-
-```
-# v1.x
-npx -y xcodebuildmcp@latest
-
-# v2.0.0
-npx -y xcodebuildmcp@latest mcp
-```
-
-If you installed via a CLI command, remove and re-add:
-
-```bash
-# Claude Code
-claude mcp remove XcodeBuildMCP
-claude mcp add XcodeBuildMCP -- npx -y xcodebuildmcp@latest mcp
-
-# Codex CLI
-codex mcp remove XcodeBuildMCP
-codex mcp add XcodeBuildMCP -- npx -y xcodebuildmcp@latest mcp
-```
-
-If you manage a configuration file directly, add `"mcp"` as the last entry in the `args` array. See [section 1](#1-mcp-server-launch-requires-the-mcp-subcommand) for file locations and examples.
-
-**2. Check your workflow configuration (if needed).**
-
-v2.0.0 defaults to loading only the `simulator` workflow instead of all workflows. If you already set `enabledWorkflows` or `XCODEBUILDMCP_ENABLED_WORKFLOWS`, nothing changes for you.
-
-If you relied on the previous default and need additional workflows, add them to `.xcodebuildmcp/config.yaml`:
-
-```yaml
-schemaVersion: 1
-enabledWorkflows:
- - simulator
- - ui-automation
- - debugging
-```
-
-See [section 2](#2-default-workflows-changed) for the rationale and full list of available workflows.
-
----
-
-# Detailed reference
-
-## 1. MCP server launch requires the `mcp` subcommand
-
-The `xcodebuildmcp` binary is now a CLI first. To start the MCP server, you must pass the `mcp` subcommand at the end of the launch command. Without it, the binary enters CLI mode and the MCP client will fail to connect.
-
-Wherever your v1.x setup invoked `xcodebuildmcp` (or `npx -y xcodebuildmcp@latest`), append `mcp` so the final token of the command is `mcp`.
-
-### Option A: Remove and re-add via CLI
-
-If you originally installed using a CLI command (e.g. `claude mcp add`, `codex mcp add`), remove the existing entry and re-add with the updated command.
-
-**Claude Code:**
-
-```bash
-claude mcp remove XcodeBuildMCP
-claude mcp add XcodeBuildMCP -- npx -y xcodebuildmcp@latest mcp
-```
-
-**Codex CLI:**
-
-```bash
-codex mcp remove XcodeBuildMCP
-codex mcp add XcodeBuildMCP -- npx -y xcodebuildmcp@latest mcp
-```
-
-### Option B: Edit the configuration file directly
-
-If you manage MCP servers through a configuration file, open it and add `"mcp"` as the last entry in the `args` array.
-
-Common file locations:
-
-| Client | Configuration file |
-|--------|--------------------|
-| Claude Code | `~/.claude.json` |
-| Claude Desktop | `~/Library/Application Support/Claude/claude_desktop_config.json` |
-| Cursor (project) | `.cursor/mcp.json` |
-| Cursor (global) | `~/.cursor/mcp.json` |
-| VS Code (project) | `.vscode/mcp.json` |
-| VS Code (global) | `~/Library/Application Support/Code/User/mcp.json` |
-| Windsurf | `~/.codeium/windsurf/mcp_config.json` |
-| Trae | `~/Library/Application Support/Trae/User/mcp.json` |
-| Codex CLI | `~/.codex/config.toml` |
-
-JSON example:
-
-```json
-"XcodeBuildMCP": {
- "command": "npx",
- "args": ["-y", "xcodebuildmcp@latest", "mcp"]
-}
-```
-
-TOML example (Codex CLI):
-
-```toml
-[mcp_servers.XcodeBuildMCP]
-command = "npx"
-args = ["-y", "xcodebuildmcp@latest", "mcp"]
-```
-
-For the full set of client-specific examples, see the [README](../README.md#installation).
-
----
-
-## 2. Default workflows changed
-
-### Why this changed
-
-In v1.x, all workflows loaded by default, exposing 70+ tools. Every tool definition and its schema is sent to the LLM on each turn, consuming context window space throughout the session. While most LLM providers cache these tokens to reduce re-inference cost, they still occupy context that could otherwise be used for your code and conversation.
-
-v2.0.0 defaults to loading only the **`simulator` workflow** (21 tools). Simulator development is the most common use case, so this covers the majority of users while keeping the token footprint small.
-
-Other workflows -- `ui-automation`, `debugging`, `device`, `macos`, and more -- are now opt-in. This gives you direct control over the trade-off between tool breadth and token cost. Enable only what you need, when you need it.
-
-### Who is affected
-
-- **Already set** `enabledWorkflows` or `XCODEBUILDMCP_ENABLED_WORKFLOWS`? Nothing changes.
-- **Relied on the default** (all workflows)? You will now only see simulator tools until you opt in to additional workflows.
-
-### How to enable additional workflows
-
-#### Config file (recommended)
-
-Create or update `.xcodebuildmcp/config.yaml` in your workspace root:
-
-```yaml
-schemaVersion: 1
-enabledWorkflows:
- - simulator
- - device
- - macos
- - ui-automation
- - debugging
- - swift-package
-```
-
-#### Environment variable
-
-Set `XCODEBUILDMCP_ENABLED_WORKFLOWS` in your MCP client configuration:
-
-```json
-"XcodeBuildMCP": {
- "command": "npx",
- "args": ["-y", "xcodebuildmcp@latest", "mcp"],
- "env": {
- "XCODEBUILDMCP_ENABLED_WORKFLOWS": "simulator,device,macos,ui-automation,debugging,swift-package"
- }
-}
-```
-
-### Available workflows
-
-| Workflow ID | Description |
-|-------------|-------------|
-| `simulator` | iOS simulator build, run, test, screenshots, logs (default) |
-| `device` | Physical device build, deploy, test, logs |
-| `macos` | macOS build, run, test |
-| `swift-package` | Swift Package Manager build, test, run |
-| `ui-automation` | Tap, swipe, text input, UI hierarchy inspection |
-| `debugging` | LLDB attach, breakpoints, variable inspection |
-| `logging` | Simulator and device log capture |
-| `simulator-management` | Boot, list, open simulators |
-| `utilities` | Clean build products |
-| `project-discovery` | Discover projects and workspaces |
-| `project-scaffolding` | Create new projects from templates |
-| `session-management` | Session defaults management |
-| `doctor` | Diagnostic tool (requires debug mode) |
-| `workflow-discovery` | Runtime workflow management (experimental) |
-| `xcode-ide` | Xcode IDE MCP bridge proxy (Xcode 26.3+) |
-
-For full details on configuration options see [CONFIGURATION.md](CONFIGURATION.md). For session defaults (project, scheme, simulator, etc.) see [SESSION_DEFAULTS.md](SESSION_DEFAULTS.md).
-
----
-
-## 3. CLI and skills
-
-### xcodebuildmcp is now a CLI
-
-The `xcodebuildmcp` command can now be used directly in the terminal without an MCP client:
-
-```bash
-# Install globally
-npm install -g xcodebuildmcp@latest
-
-# List available tools
-xcodebuildmcp tools
-
-# Build and run on simulator
-xcodebuildmcp simulator build-and-run --scheme MyApp --project-path ./MyApp.xcodeproj
-
-# Take a screenshot
-xcodebuildmcp simulator screenshot
-
-# Tap a button
-xcodebuildmcp ui-automation tap --label "Submit"
-```
-
-See [CLI.md](CLI.md) for full documentation.
-
-### MCP vs CLI for coding agents
-
-**The MCP server is the recommended way to use XcodeBuildMCP with coding agents and will yield the best results.** The CLI is provided as an alternative and for scripting/CI use cases.
-
-Why MCP is preferred:
-
-- **Automatic tool discovery** -- tools are registered with the agent at session start, so the agent always knows what is available and how to call it.
-- **Session defaults** -- the MCP server maintains stateful defaults (project path, scheme, simulator, etc.) across tool calls, so the agent does not have to recall and re-supply project details on every invocation. This significantly reduces errors.
-- **Stateful operations** -- log capture, debugging sessions, and other long-running operations are fully managed by the server.
-
-The CLI avoids the per-turn context cost of MCP tool definitions since the agent invokes commands directly with no tool schemas to transmit. However, this comes with trade-offs: session defaults are not available in CLI mode, so the agent must pass all parameters explicitly on every call. Agents also tend to consume significant tokens repeatedly calling `--help` to re-discover commands and arguments. The CLI skill helps reduce this discovery overhead, but in practice MCP tools are almost always used more reliably by agents.
-
-### Agent skills (optional)
-
-v2.0.0 introduces optional skill files that prime your coding agent with usage instructions:
-
-- **CLI Skill** -- strongly recommended when using the CLI with a coding agent.
-- **MCP Skill** -- optional when using the MCP server; gives the agent better context on available tools.
-
-Install via the built-in CLI command:
-
-```bash
-xcodebuildmcp init
-```
-
-Or run it via npx without a global install:
-
-```bash
-npx -y xcodebuildmcp@latest init
-```
-
-See [SKILLS.md](SKILLS.md) for more details.
-
----
-
-## 4. New project-level configuration file
-
-v2.0.0 adds support for a YAML config file at `.xcodebuildmcp/config.yaml`. This replaces the need for environment variables and provides deterministic, repo-scoped behavior. Environment variables still work but the config file takes precedence.
-
-See [CONFIGURATION.md](CONFIGURATION.md) for the full reference.
diff --git a/docs/OVERVIEW.md b/docs/OVERVIEW.md
deleted file mode 100644
index 2e0262fe8..000000000
--- a/docs/OVERVIEW.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Overview
-
-XcodeBuildMCP is a Model Context Protocol (MCP) server that exposes Xcode operations as tools and resources for AI assistants and other MCP clients. It uses a plugin-based architecture with workflow groups so clients can access Xcode projects, simulators, devices, and Swift packages through a standard interface.
-
-## Why it exists
-- Standardizes Xcode interactions for AI agents instead of ad-hoc command lines.
-- Reduces configuration errors by providing purpose-built tools.
-- Enables agents to build, inspect errors, and iterate autonomously.
-
-## What it can do
-- Xcode project discovery, build, test, and clean.
-- Simulator and device app lifecycle management.
-- Swift Package Manager build, test, and run.
-- UI automation, screenshots, and video capture.
-- Log capture and system diagnostics.
-- Debugger attach, breakpoints, stack, variables, and LLDB command execution.
-
-See the full tool catalog in [TOOLS.md](TOOLS.md).
-
-## Next steps
-- Get started: [GETTING_STARTED.md](GETTING_STARTED.md)
-- Configure options: [CONFIGURATION.md](CONFIGURATION.md)
-- Tools reference: [TOOLS.md](TOOLS.md)
-- Troubleshooting: [TROUBLESHOOTING.md](TROUBLESHOOTING.md)
diff --git a/docs/PRIVACY.md b/docs/PRIVACY.md
deleted file mode 100644
index 56573dce2..000000000
--- a/docs/PRIVACY.md
+++ /dev/null
@@ -1,23 +0,0 @@
-# Privacy
-
-XcodeBuildMCP uses Sentry for error monitoring and diagnostics. This helps track crashes and unexpected errors to improve reliability.
-
-## What is sent to Sentry
-- Internal XcodeBuildMCP failures only (for example: daemon/MCP runtime faults and unexpected exceptions in server code).
-- User-domain errors are not sent (for example: project build/test failures, invalid user config, missing scheme/destination, simulator/app-state errors).
-- Tool inputs/outputs are not captured by default, and environment/system variables are not attached as telemetry tags.
-- Internal operational logs are sent only when explicitly marked for Sentry (`{ sentry: true }` in server code). Console logging is not automatically forwarded.
-- Event payloads are scrubbed before send to remove request/user context and redact user home paths (for example `/Users//...`).
-
-## Opting out
-To disable error telemetry, set:
-
-```json
-"env": {
- "XCODEBUILDMCP_SENTRY_DISABLED": "true"
-}
-```
-
-## Related docs
-- Configuration options: [CONFIGURATION.md](CONFIGURATION.md)
-- Troubleshooting: [TROUBLESHOOTING.md](TROUBLESHOOTING.md)
diff --git a/docs/README.md b/docs/README.md
deleted file mode 100644
index d1b2264de..000000000
--- a/docs/README.md
+++ /dev/null
@@ -1,27 +0,0 @@
-# XcodeBuildMCP Documentation
-
-## Start here
-- [Getting started](GETTING_STARTED.md)
-- [Configuration and options](CONFIGURATION.md)
-- [Tools reference (all workflows/tools)](TOOLS.md)
-- [Troubleshooting and doctor](TROUBLESHOOTING.md)
-- [Privacy and telemetry](PRIVACY.md)
-- [Demos](DEMOS.md)
-
-## User guides
-- [Product overview and rationale](OVERVIEW.md)
-- [Session defaults and opt-out](SESSION_DEFAULTS.md)
-- [Device code signing notes](DEVICE_CODE_SIGNING.md)
-- [CLI reference](CLI.md)
-
-## Developer docs
-- [Developer documentation index](dev/README.md)
-- [Contributing guide](dev/CONTRIBUTING.md)
-- [Architecture](dev/ARCHITECTURE.md)
-- [Testing](dev/TESTING.md)
-- [Code quality rules](dev/CODE_QUALITY.md)
-- [Plugin development](dev/PLUGIN_DEVELOPMENT.md)
-- [Release process](dev/RELEASE_PROCESS.md)
-
-## Generated docs
-- [TOOLS.md](TOOLS.md) is generated by `scripts/update-tools-docs.ts`.
diff --git a/docs/SCHEMA_VERSIONING.md b/docs/SCHEMA_VERSIONING.md
deleted file mode 100644
index 44b8ae696..000000000
--- a/docs/SCHEMA_VERSIONING.md
+++ /dev/null
@@ -1,217 +0,0 @@
-# Structured JSON Schema versioning and publishing
-
-This document defines how XcodeBuildMCP versions and publishes the JSON Schemas for
-structured output fixtures and runtime payloads.
-
-## Goals
-
-- Keep schema contracts stable and predictable for external consumers.
-- Make published schema URLs real, durable, and safe to reference.
-- Let the website serve schemas directly from `https://xcodebuildmcp.com/schemas/...`.
-- Avoid ambiguous compatibility rules.
-
-## Canonical schema identity
-
-Each schema has two stable identifiers:
-
-1. The payload metadata:
- ```json
- {
- "schema": "xcodebuildmcp.output.build-result",
- "schemaVersion": "1"
- }
- ```
-2. The published schema file path:
- ```text
- https://xcodebuildmcp.com/schemas/structured-output/xcodebuildmcp.output.build-result/1.schema.json
- ```
-
-The in-payload `schema` and `schemaVersion` values must always match the published
-schema document that validates that payload.
-
-## Version format
-
-`schemaVersion` uses integer strings only:
-
-- `"1"`
-- `"2"`
-- `"3"`
-
-Do not use semver-style schema versions such as `"1.1"` or `"2.0"`.
-
-The version number is a contract version, not a release number.
-
-## Versioning rules
-
-### Published versions are immutable
-
-Once a schema version is published, do not make breaking changes to that file.
-
-Breaking changes include:
-
-- removing a property
-- making an optional property required
-- narrowing allowed values or enums
-- changing object shape incompatibly
-- changing field meaning in a way that could break clients
-
-If any of those changes are needed, publish a new version instead:
-
-```text
-schemas/structured-output/xcodebuildmcp.output.build-result/2.schema.json
-```
-
-and emit:
-
-```json
-"schemaVersion": "2"
-```
-
-### Old versions remain available
-
-Previously published schema files must continue to be hosted.
-
-Do not remove old schema versions from the website once consumers may rely on them.
-
-### Additive changes
-
-Additive, optional fields can be compatible, but use caution.
-
-If a new field is truly optional and old clients can safely ignore it, it may remain
-within the same schema version. If there is any doubt about compatibility or meaning,
-bump the schema version.
-
-Bias toward a new version when the contract meaning changes.
-
-## Directory layout
-
-Source schemas in this repository live under:
-
-```text
-schemas/structured-output/
-```
-
-Published schemas on the website live under:
-
-```text
-public/schemas/structured-output/
-```
-
-A source file such as:
-
-```text
-schemas/structured-output/xcodebuildmcp.output.build-result/1.schema.json
-```
-
-is published to:
-
-```text
-public/schemas/structured-output/xcodebuildmcp.output.build-result/1.schema.json
-```
-
-which is then served at:
-
-```text
-https://xcodebuildmcp.com/schemas/structured-output/xcodebuildmcp.output.build-result/1.schema.json
-```
-
-## Publishing workflow
-
-Schema publishing is handled from this repository by a GitHub Actions workflow.
-
-Trigger conditions:
-
-- push to `main` when files under `schemas/**` change
-- manual `workflow_dispatch`
-
-Publishing steps:
-
-1. Check out this repository.
-2. Clone `getsentry/xcodebuildmcp.com` over SSH.
-3. Sync `schemas/structured-output/` from this repository into
- `public/schemas/structured-output/` in the website repository.
-4. Commit the website change if the published files changed.
-5. Push to the website repository `main` branch.
-6. Let Vercel deploy the website normally.
-
-This keeps schema authoring in the main project repository while using the website
-repository as the deployment surface.
-
-## Required secret
-
-The publishing workflow requires this repository secret:
-
-```text
-XCODEBUILDMCP_WEBSITE_DEPLOY_KEY
-```
-
-This secret must contain an SSH private key with write access to:
-
-```text
-git@github.com:getsentry/xcodebuildmcp.com.git
-```
-
-The corresponding public key should be installed as a deploy key on the website
-repository with write access.
-
-### Deploy key setup
-
-1. Generate a dedicated SSH key pair for schema publishing.
- ```bash
- ssh-keygen -t ed25519 -C "schema-publisher" -f ./xcodebuildmcp-website-deploy-key
- ```
-2. In `getsentry/xcodebuildmcp.com`, add the public key as a deploy key with write
- access.
- - GitHub, Settings, Deploy keys
- - Add `xcodebuildmcp-website-deploy-key.pub`
- - Enable write access
-3. In `getsentry/XcodeBuildMCP`, add the private key as an actions secret named:
- ```text
- XCODEBUILDMCP_WEBSITE_DEPLOY_KEY
- ```
-4. Trigger the `Publish Schemas` workflow manually once to verify SSH access and sync.
-5. Confirm that the website repository receives the commit and Vercel deploys it.
-6. Confirm a final URL resolves, for example:
- ```text
- https://xcodebuildmcp.com/schemas/structured-output/xcodebuildmcp.output.build-result/1.schema.json
- ```
-
-Use a dedicated deploy key for this workflow only. Do not reuse a personal SSH key.
-
-## Consumer guidance
-
-Consumers should branch on both `schema` and `schemaVersion`.
-
-Example:
-
-```ts
-switch (`${payload.schema}@${payload.schemaVersion}`) {
- case "xcodebuildmcp.output.build-result@1":
- // validate using v1 schema
- break
- case "xcodebuildmcp.output.build-result@2":
- // validate using v2 schema
- break
- default:
- throw new Error("Unsupported schema version")
-}
-```
-
-These JSON Schemas describe payload shapes. They are not an OpenAPI description by
-themselves. If an HTTP API is introduced later, OpenAPI should reference the schema
-files as component schemas instead of trying to infer endpoints from them.
-
-## Maintenance checklist
-
-When updating schemas:
-
-1. Decide whether the change is compatible or breaking.
-2. If breaking, add a new versioned schema file instead of changing the old one.
-3. Update fixture payloads to emit the correct `schemaVersion`.
-4. Run:
- ```bash
- npm run test:schema-fixtures
- ```
-5. Merge to `main`.
-6. Confirm the publish workflow updated the website repo.
-7. Confirm the final schema URL resolves on the website.
diff --git a/docs/SENTRY_AUDIT_REPORT_2026-02-11.md b/docs/SENTRY_AUDIT_REPORT_2026-02-11.md
deleted file mode 100644
index 4af47a895..000000000
--- a/docs/SENTRY_AUDIT_REPORT_2026-02-11.md
+++ /dev/null
@@ -1,184 +0,0 @@
-# Investigation: Sentry Telemetry Scope and Privacy Audit (#204)
-
-## Summary
-The current implementation captured too much data by default (including broad MCP instrumentation and PII-heavy tagging). The fix narrows telemetry to internal runtime failures only, keeps tracing enabled for observability, disables MCP input/output capture, and adds payload scrubbing/redaction.
-
-## Symptoms
-- Sentry was initialized with `sendDefaultPii: true` and `tracesSampleRate: 1.0`.
-- MCP server was wrapped with `wrapMcpServerWithSentry`, enabling broad per-call instrumentation.
-- Logger default sent all `error` logs to Sentry, including user-domain failures.
-- Sentry tags included sensitive environment/system values (HOME, USER, PATH, Xcode paths).
-- Privacy docs understated actual collection scope.
-
-## Investigation Log
-
-### Phase 1 - Issue and baseline audit
-**Hypothesis:** Telemetry defaults and wrapper behavior were broader than documented.
-**Findings:** Issue #204 correctly identified mismatch between docs and implementation.
-**Evidence:**
-- GitHub issue: https://github.com/getsentry/XcodeBuildMCP/issues/204
-- `/Users/cameroncooke/.codex/worktrees/a59a/XcodeBuildMCP/src/utils/sentry.ts`
-- `/Users/cameroncooke/.codex/worktrees/a59a/XcodeBuildMCP/src/server/server.ts`
-- `/Users/cameroncooke/.codex/worktrees/a59a/XcodeBuildMCP/src/utils/logger.ts`
-**Conclusion:** Confirmed.
-
-### Phase 2 - Runtime path tracing
-**Hypothesis:** User-domain tool failures were reaching Sentry through logger defaults.
-**Findings:** `log('error', ...)` implicitly captured to Sentry unless overridden; many tool/runtime paths emit user-domain errors at error level.
-**Evidence:**
-- `/Users/cameroncooke/.codex/worktrees/a59a/XcodeBuildMCP/src/utils/logger.ts`
-- `/Users/cameroncooke/.codex/worktrees/a59a/XcodeBuildMCP/src/utils/build-utils.ts`
-- `/Users/cameroncooke/.codex/worktrees/a59a/XcodeBuildMCP/src/runtime/tool-invoker.ts`
-**Conclusion:** Confirmed.
-
-### Phase 3 - Version and docs alignment
-**Hypothesis:** Sentry SDK was not at latest patch and docs were not aligned with behavior.
-**Findings:** `@sentry/node` moved from `^10.37.0` to latest `10.38.0` and docs were updated to match internal-only policy.
-**Evidence:**
-- Command: `npm view @sentry/node version` returned `10.38.0`
-- `/Users/cameroncooke/.codex/worktrees/a59a/XcodeBuildMCP/package.json`
-- `/Users/cameroncooke/.codex/worktrees/a59a/XcodeBuildMCP/docs/PRIVACY.md`
-- `/Users/cameroncooke/.codex/worktrees/a59a/XcodeBuildMCP/docs/CONFIGURATION.md`
-- `/Users/cameroncooke/.codex/worktrees/a59a/XcodeBuildMCP/README.md`
-**Conclusion:** Confirmed.
-
-## Root Cause
-Three defaults combined to over-collect telemetry:
-1. `sendDefaultPii: true` and tracing at 100% in Sentry init.
-2. `wrapMcpServerWithSentry` around the MCP server.
-3. Logger behavior that captured every `error` log to Sentry by default.
-
-This effectively blurred boundaries between internal platform failures and user-domain build/config/runtime failures, and increased risk of sensitive metadata leakage.
-
-## Changes Implemented
-1. Sentry initialization hardened (`sendDefaultPii: false`, `tracesSampleRate: 1.0`, breadcrumbs disabled, `beforeSend` scrubbing/redaction).
-2. Sensitive tags/context removed (no env dumps, no HOME/USER/PATH/Xcode path tagging).
-3. Restored MCP wrapper with explicit safe options (`recordInputs: false`, `recordOutputs: false`) to keep tool-level observability without payload capture.
-4. Logger changed to explicit opt-in capture only (`context.sentry === true`).
-5. Internal boundary capture retained only where appropriate (startup/shutdown/fatal daemon internal errors).
-6. Added tests for explicit capture policy and path redaction.
-7. Updated privacy/config/README/architecture docs and changelog.
-
-## Eliminated Hypotheses
-- "Only MCP-level faults are captured today": Eliminated (not true before this patch due to logger defaults and wrapper).
-- "Docs accurately reflected telemetry scope": Eliminated.
-
-## Recommendations
-1. Keep Sentry capture explicit and centralized to internal runtime boundaries.
-2. Avoid adding environment or filesystem metadata to telemetry tags.
-3. Preserve redaction tests to prevent regressions.
-4. Continue documenting telemetry scope in user-facing docs whenever behavior changes.
-
-## Preventive Measures
-- CI should keep redaction and logger policy tests running by default.
-- Any future telemetry additions should require explicit privacy review with docs update in same PR.
-
-## Validation
-All relevant quality checks were executed after changes:
-
-- `npm run format:check` ✅
-- `npm run lint` ✅
-- `npm run typecheck` ✅
-- `npm run build` ✅
-- `npm test` ✅ (117 test files passed; 1274 tests passed, 15 skipped)
-
-Notes:
-- Test output includes expected stderr from negative-path parser tests in `src/utils/__tests__/nskeyedarchiver-parser.test.ts`; test run still passed.
-
-## Addendum: MCP Wrapper Capture Semantics (verified against SDK 10.38.0)
-- `wrapMcpServerWithSentry` resolves options as:
- - `recordInputs: options?.recordInputs ?? sendDefaultPii`
- - `recordOutputs: options?.recordOutputs ?? sendDefaultPii`
-- For MCP request spans, `tools/call` and `prompts/get` arguments are added as `mcp.request.argument.*` attributes when `recordInputs=true`.
-- Tool/prompt results are added as `mcp.tool.result*` / `mcp.prompt.result*` attributes when `recordOutputs=true`.
-- Built-in MCP PII filtering in the SDK only strips network-level fields (`client.address`, `client.port`, `mcp.resource.uri`) when `sendDefaultPii=false`; it does not by itself scrub arbitrary tool argument/output payloads.
-
-Evidence (source-inspected package build):
-- `@sentry/core@10.38.0` `build/cjs/integrations/mcp-server/index.js`
-- `@sentry/core@10.38.0` `build/cjs/integrations/mcp-server/methodConfig.js`
-- `@sentry/core@10.38.0` `build/cjs/integrations/mcp-server/resultExtraction.js`
-- `@sentry/core@10.38.0` `build/cjs/integrations/mcp-server/piiFiltering.js`
-
-## Addendum: Live Validation (2026-02-12)
-
-### Findings
-- Runtime config/dependency tags are being attached to issues when events are captured after bootstrap context is set.
-- Example: issue `XCODEBUILDMCP-6` includes `runtime.mode`, `xcode.version`, `xcode.xcodebuild_path`, `axe.source`, and config tags.
-- Startup-phase config parse warnings can occur before full runtime context is attached, so those earlier events may not show the full tag set.
-- MCP wrapper instrumentation is active in-process:
- - Local debug output shows sampled MCP spans for `initialize`, `notifications/initialized`, and `tools/call session_show_defaults`.
- - Local exporter reports spans exported.
-- Despite local span export, Sentry project query for spans currently returns `count() = 0` in the tested time window.
-
-### Evidence
-- Local MCP call validation:
- - `session_show_defaults` invoked over stdio client; server started successfully.
-- Local in-memory instrumentation validation:
- - Debug logs show:
- - `Starting sampled root span op: mcp.server`
- - `Finishing ... tools/call session_show_defaults`
- - `SpanExporter exported 3 spans`
-- Sentry MCP queries:
- - Spans in last hour: `count() = 0`
- - Transactions in last hour: `count() = 0`
- - Trace for issue `XCODEBUILDMCP-6`: `Total Spans: 0, Errors: 1`
-
-## Addendum: MCP View + Logs Explorer Deep Dive (2026-02-12)
-
-### Scope
-Investigated two active symptoms:
-- MCP tools are not visible in Sentry MCP view.
-- Logs are not visible in Sentry Logs Explorer.
-
-### Key Findings
-- Error events are ingesting correctly in the target project.
- - Sentry query: errors in last 24h = `11`.
-- Logs and spans datasets remain empty in the same project/time windows.
- - Sentry query: logs in last 24h/7d = `0`.
- - Sentry query: spans in last 24h/14d = `0`.
- - Sentry query: transactions in last 14d/15m = `0`.
-- SDK-side emission is working for both logs and transactions.
- - Direct probe emitted:
- - `Sentry.logger.info('envelope logger probe ...')`
- - `Sentry.startSpan({ forceTransaction: true, ... })`
- - Runtime instrumentation confirmed envelope item types sent:
- - `ENVELOPE_ITEM_TYPES log`
- - `ENVELOPE_ITEM_TYPES transaction`
- - plus expected `event`/`session` items.
-- Despite emitted envelopes, Sentry queries still return zero logs/spans/transactions.
- - Strongly indicates an ingestion/storage/configuration issue outside current app code path.
-
-### Code-Path Validation
-- MCP wrapper is enabled with safe options:
- - `src/server/server.ts:72` uses `wrapMcpServerWithSentry(..., { recordInputs: false, recordOutputs: false })`.
-- Sentry logs pipeline is enabled:
- - `src/utils/sentry.ts:282` sets `enableLogs: true`.
- - `src/utils/sentry.ts:286` sets `beforeSendLog: redactLog`.
-- Logger forwards only explicit opt-in internal logs:
- - `src/utils/logger.ts:56` (`context?.sentry === true`).
- - `src/utils/logger.ts:236` fallback uses `captureMessage` only if logger method is unavailable.
-- Runtime split is real:
- - Daemon handles `tool.invoke` requests (`src/daemon/daemon-server.ts:117`), including `runtime: 'daemon'` (`src/daemon/daemon-server.ts:128`).
- - CLI paths route many invocations through daemon (`src/runtime/tool-invoker.ts:192`).
- - MCP wrapper only covers stdio MCP server runtime (`src/server/server.ts:72`).
-
-### Root Cause Assessment (Current Confidence)
-- Most likely primary blocker: Sentry-side configuration/entitlement/pipeline for traces and logs in this project/org (not client emission).
-- Secondary (not primary) code risk:
- - `process.exit(...)` without explicit `Sentry.flush/close` in shutdown paths can still drop buffered telemetry in some paths:
- - `src/server/start-mcp-server.ts:68`
- - `src/server/start-mcp-server.ts:83`
- - `src/daemon.ts:303`
- - `src/daemon.ts:310`
- - `src/daemon.ts:402`
-
-### Eliminated Hypotheses
-- "MCP wrapper is removed or disabled." Eliminated.
-- "Logs are not being captured by SDK at all." Eliminated (capture hooks + envelope inspection confirm capture/send).
-- "Transactions are not being created by SDK at all." Eliminated (manual forced transaction emitted and sent).
-
-### Recommended Next Steps
-1. Verify project-level traces/logs ingestion settings in Sentry (`sentry/xcodebuildmcp`) and any org-level sampling/filtering rules dropping transactions/logs.
-2. Verify account/product entitlement for Logs and Performance on this project.
-3. Add explicit shutdown drain in app code (`Sentry.flush`/`Sentry.close`) before `process.exit(...)` to reduce telemetry loss on fast shutdown.
-4. Keep MCP-view expectation scoped to MCP stdio runtime; add daemon-specific spans if daemon tool-call observability is required in traces.
diff --git a/docs/SESSION_DEFAULTS.md b/docs/SESSION_DEFAULTS.md
deleted file mode 100644
index 0331819e8..000000000
--- a/docs/SESSION_DEFAULTS.md
+++ /dev/null
@@ -1,73 +0,0 @@
-# Session Defaults
-
-By default, XcodeBuildMCP uses a session-aware mode. The client sets shared defaults once (simulator, device, project/workspace, scheme, etc.) and all tools reuse them. This reduces schema size and repeated payloads and ensures a more deterministic experience.
-
-## How it works
-- Agent calls `session_set_defaults` once at the start of a workflow.
-- Tools reuse those defaults automatically.
-- Agent can call `session_show_defaults` to inspect current values.
-- Agent can call `session_clear_defaults` to clear values when switching contexts.
-- Defaults can be seeded from `.xcodebuildmcp/config.yaml` at server startup.
-
-See the session-management tools in [TOOLS.md](TOOLS.md).
-
-## Opting out
-If you prefer explicit parameters on every tool call, set `disableSessionDefaults: true` in your `.xcodebuildmcp/config.yaml` file.
-
-This restores the legacy schemas with per-call parameters while still honoring any defaults you choose to set.
-
-See [CONFIGURATION.md](CONFIGURATION.md) for more information.
-
-## Persisting defaults
-Session defaults can be persisted between sessions by setting the `persist` flag to `true` on `session_set_defaults`. This writes to `.xcodebuildmcp/config.yaml` at the root of your workspace.
-
-The persistence is patch-only: only keys provided in that call are written (plus any removals needed for mutual exclusivity).
-
-You can also manually create the config file to essentially seed the defaults at startup; see [CONFIGURATION.md](CONFIGURATION.md) for more information.
-
-## Namespaced profiles
-Session defaults support named profiles so one workspace can keep separate defaults for iOS/watchOS/macOS (or any custom profile names).
-
-- Use `session_use_defaults_profile` to switch the active profile (existing profiles only).
-- Existing tools (`session_set_defaults`, `session_show_defaults`, build/test tools) use the active profile automatically.
-- `session_set_defaults` can also accept `profile` to switch and set in one call; use `createIfNotExists: true` to create a new profile intentionally.
-- Profiles are strictly isolated: values are not inherited from global defaults or other profiles.
-- The unnamed global defaults profile exists for backwards compatibility and is the default active profile when no named profile is selected.
-- There is always an active profile context: either a named profile or `global`.
-- Use `global: true` to switch back to the unnamed global profile.
-- Set `persist: true` on `session_use_defaults_profile` to write `activeSessionDefaultsProfile` in `.xcodebuildmcp/config.yaml`.
-
-## Recommended startup flow (monorepo / multi-target)
-Copy/paste this sequence when starting a new session:
-
-```json
-{"name":"session_use_defaults_profile","arguments":{"profile":"ios","persist":true}}
-{"name":"session_set_defaults","arguments":{
- "workspacePath":"/repo/MyApp.xcworkspace",
- "scheme":"MyApp-iOS",
- "simulatorName":"iPhone 17 Pro",
- "persist":true
-}}
-{"name":"session_show_defaults","arguments":{}}
-```
-
-Switch targets later in the same session:
-
-```json
-{"name":"session_use_defaults_profile","arguments":{"profile":"watch","persist":true}}
-{"name":"session_set_defaults","arguments":{
- "workspacePath":"/repo/MyApp.xcworkspace",
- "scheme":"MyApp-watchOS",
- "simulatorName":"Apple Watch Series 10 (45mm)",
- "persist":true
-}}
-```
-
-Isolation example:
-- Global profile has `workspacePath: /repo/MyApp.xcworkspace`
-- Active profile is `watch` with only `scheme` set
-- Result: `watch` does not see global `workspacePath` until you set it on `watch` or switch back to `global`
-
-## Related docs
-- Configuration options: [CONFIGURATION.md](CONFIGURATION.md)
-- Tools reference: [TOOLS.md](TOOLS.md)
diff --git a/docs/SKILLS.md b/docs/SKILLS.md
deleted file mode 100644
index 5e6351319..000000000
--- a/docs/SKILLS.md
+++ /dev/null
@@ -1,42 +0,0 @@
-# XcodeBuildMCP Skill
-
-XcodeBuildMCP includes two optional agent skills:
-
-- **MCP Skill**: Primes the agent with instructions on how to use the MCP server's tools (optional when using the MCP server).
-
-- **CLI Skill**: Primes the agent with instructions on how to navigate the CLI (recommended when using the CLI).
-
-## Install
-
-```bash
-xcodebuildmcp init
-```
-
-This auto-detects installed AI clients (Claude Code, Cursor, Codex) and installs the CLI skill.
-
-### Options
-
-```bash
-xcodebuildmcp init --skill cli # Install CLI skill (default)
-xcodebuildmcp init --skill mcp # Install MCP skill
-xcodebuildmcp init --client claude # Install to Claude only
-xcodebuildmcp init --dest /path/to/dir # Install to custom directory
-xcodebuildmcp init --force # Overwrite existing
-xcodebuildmcp init --remove-conflict # Auto-remove conflicting variant
-xcodebuildmcp init --uninstall # Remove installed skill
-```
-
-When installing `--skill mcp` in auto-detect mode, Claude Code targets are skipped because Claude already receives MCP guidance through server instructions. If you explicitly set `--client claude`, MCP skill installation is still allowed.
-
-## Unsupported Clients
-
-For clients without a skills directory, print the skill content and pipe it to a file or paste it into your client's instructions area:
-
-```bash
-xcodebuildmcp init --print
-xcodebuildmcp init --print --skill mcp > my-skill.md
-```
-
-## Skills
-
-To learn more about skills see: [https://agentskills.io/home](https://agentskills.io/home).
diff --git a/docs/TOOLS-CLI.md b/docs/TOOLS-CLI.md
deleted file mode 100644
index dcfdb27ce..000000000
--- a/docs/TOOLS-CLI.md
+++ /dev/null
@@ -1,194 +0,0 @@
-# XcodeBuildMCP CLI Tools Reference
-
-This document lists CLI tool names as exposed by `xcodebuildmcp `.
-
-XcodeBuildMCP provides 71 canonical tools organized into 13 workflow groups.
-
-## Workflow Groups
-
-### Build Utilities (`utilities`)
-**Purpose**: Utility tools for cleaning build products and managing build artifacts. (1 tools)
-
-- `clean` - Defined in iOS Device Development workflow.
-
-
-
-### Code Coverage (`coverage`)
-**Purpose**: View code coverage data from xcresult bundles produced by test runs. (2 tools)
-
-- `get-coverage-report` - Show per-target code coverage from an xcresult bundle.
-- `get-file-coverage` - Show function-level coverage and uncovered line ranges for a specific file.
-
-
-
-### iOS Device Development (`device`)
-**Purpose**: Complete iOS development workflow for physical devices (iPhone, iPad, Apple Watch, Apple TV, Apple Vision Pro). (15 tools)
-
-- `build` - Build for device.
-- `build-and-run` - Build, install, and launch on physical device. Runtime logs are captured automatically and the log file path is included in the response. Preferred single-step run tool when defaults are set.
-- `clean` - Clean build products.
-- `discover-projects` - Scans a directory (defaults to workspace root) to find Xcode project (.xcodeproj) and workspace (.xcworkspace) files. Use when project/workspace path is unknown.
-- `get-app-bundle-id` - Extract bundle id from .app.
-- `get-app-path` - Get device built app path.
-- `get-coverage-report` - Defined in Code Coverage workflow.
-- `get-file-coverage` - Defined in Code Coverage workflow.
-- `install` - Install app on device.
-- `launch` - Launch app on device.
-- `list` - List connected devices.
-- `list-schemes` - List Xcode schemes.
-- `show-build-settings` - Show build settings.
-- `stop` - Stop device app.
-- `test` - Test on device.
-
-
-
-### iOS Simulator Development (`simulator`)
-**Purpose**: Complete iOS development workflow for both .xcodeproj and .xcworkspace files targeting simulators. (20 tools)
-
-- `boot` - Defined in Simulator Management workflow.
-- `build` - Build for iOS sim (compile-only, no launch).
-- `build-and-run` - Build, install, and launch on iOS Simulator; boots simulator and attempts to open Simulator.app as needed. Runtime logs are captured automatically and the log file path is included in the response. Preferred single-step run tool when defaults are set.
-- `clean` - Defined in iOS Device Development workflow.
-- `discover-projects` - Defined in iOS Device Development workflow.
-- `get-app-bundle-id` - Defined in iOS Device Development workflow.
-- `get-app-path` - Get sim built app path.
-- `get-coverage-report` - Defined in Code Coverage workflow.
-- `get-file-coverage` - Defined in Code Coverage workflow.
-- `install` - Install app on sim.
-- `launch-app` - Launch app on simulator. Runtime logs are captured automatically and the log file path is included in the response.
-- `list` - Defined in Simulator Management workflow.
-- `list-schemes` - Defined in iOS Device Development workflow.
-- `open` - Defined in Simulator Management workflow.
-- `record-video` - Record sim video.
-- `screenshot` - Capture screenshot.
-- `show-build-settings` - Defined in iOS Device Development workflow.
-- `snapshot-ui` - Print view hierarchy with precise view coordinates (x, y, width, height) for visible elements.
-- `stop` - Stop sim app.
-- `test` - Test on iOS sim.
-
-
-
-### LLDB Debugging (`debugging`)
-**Purpose**: Attach LLDB debugger to simulator apps, set breakpoints, inspect variables and call stacks. (8 tools)
-
-- `add-breakpoint` - Add breakpoint.
-- `attach` - Attach LLDB to sim app.
-- `continue` - Continue debug session.
-- `detach` - Detach debugger.
-- `lldb-command` - Run LLDB command.
-- `remove-breakpoint` - Remove breakpoint.
-- `stack` - Get backtrace.
-- `variables` - Get frame variables.
-
-
-
-### macOS Development (`macos`)
-**Purpose**: Complete macOS development workflow for both .xcodeproj and .xcworkspace files. Build, test, deploy, and manage macOS applications. (13 tools)
-
-- `build` - Build macOS app.
-- `build-and-run` - Build and run macOS app.
-- `clean` - Defined in iOS Device Development workflow.
-- `discover-projects` - Defined in iOS Device Development workflow.
-- `get-app-path` - Get macOS built app path.
-- `get-coverage-report` - Defined in Code Coverage workflow.
-- `get-file-coverage` - Defined in Code Coverage workflow.
-- `get-macos-bundle-id` - Extract bundle id from macOS .app.
-- `launch` - Launch macOS app.
-- `list-schemes` - Defined in iOS Device Development workflow.
-- `show-build-settings` - Defined in iOS Device Development workflow.
-- `stop` - Stop macOS app.
-- `test` - Test macOS target.
-
-
-
-### MCP Doctor (`doctor`)
-**Purpose**: Diagnostic tool providing comprehensive information about the MCP server environment, dependencies, and configuration. (1 tools)
-
-- `doctor` - MCP environment info.
-
-
-
-### Project Discovery (`project-discovery`)
-**Purpose**: Discover and examine Xcode projects, workspaces, and Swift packages. Analyze project structure, schemes, build settings, and bundle information. (5 tools)
-
-- `discover-projects` - Defined in iOS Device Development workflow.
-- `get-app-bundle-id` - Defined in iOS Device Development workflow.
-- `get-macos-bundle-id` - Defined in macOS Development workflow.
-- `list-schemes` - Defined in iOS Device Development workflow.
-- `show-build-settings` - Defined in iOS Device Development workflow.
-
-
-
-### Project Scaffolding (`project-scaffolding`)
-**Purpose**: Scaffold new iOS and macOS projects from templates. (2 tools)
-
-- `scaffold-ios` - Scaffold iOS project.
-- `scaffold-macos` - Scaffold macOS project.
-
-
-
-### Simulator Management (`simulator-management`)
-**Purpose**: Tools for managing simulators from booting, opening simulators, listing simulators, stopping simulators, erasing simulator content and settings, and setting simulator environment options like location, network, statusbar and appearance. (8 tools)
-
-- `boot` - Boot iOS simulator for manual/non-build flows. Not required before simulator build-and-run (build_run_sim).
-- `erase` - Erase simulator.
-- `list` - List iOS simulators.
-- `open` - Open Simulator.app for visibility/manual workflows. Not required before simulator build-and-run (build_run_sim).
-- `reset-location` - Reset sim location.
-- `set-appearance` - Set sim appearance.
-- `set-location` - Set sim location.
-- `statusbar` - Set sim status bar network.
-
-
-
-### Swift Package Development (`swift-package`)
-**Purpose**: Build, test, run and manage Swift Package Manager projects. (8 tools)
-
-- `build` - swift package target build.
-- `clean` - swift package clean.
-- `get-coverage-report` - Defined in Code Coverage workflow.
-- `get-file-coverage` - Defined in Code Coverage workflow.
-- `list` - List SwiftPM processes.
-- `run` - swift package target run.
-- `stop` - Stop SwiftPM run.
-- `test` - Run swift package target tests.
-
-
-
-### UI Automation (`ui-automation`)
-**Purpose**: UI automation and accessibility testing tools for iOS simulators. Perform gestures, interactions, screenshots, and UI analysis for automated testing workflows. (11 tools)
-
-- `button` - Press simulator hardware button.
-- `gesture` - Simulator gesture preset.
-- `key-press` - Press key by keycode.
-- `key-sequence` - Press a sequence of keys by their keycodes.
-- `long-press` - Long press at coords.
-- `screenshot` - Defined in iOS Simulator Development workflow.
-- `snapshot-ui` - Defined in iOS Simulator Development workflow.
-- `swipe` - Swipe between points.
-- `tap` - Tap UI element by accessibility id/label (recommended) or coordinates as fallback.
-- `touch` - Touch down/up at coords.
-- `type-text` - Type text.
-
-
-
-### Xcode IDE Integration (`xcode-ide`)
-**Purpose**: Bridge tools for connecting to Xcode's built-in MCP server (mcpbridge) to access IDE-specific functionality. (5 tools)
-
-- `bridge-disconnect` - Disconnect bridge and unregister proxied `xcode_tools_*` tools.
-- `bridge-status` - Show xcrun mcpbridge availability and proxy tool sync status.
-- `bridge-sync` - One-shot connect + tools/list sync (manual retry; avoids background prompt spam).
-- `call-tool` - Call a remote Xcode IDE MCP tool.
-- `list-tools` - Lists Xcode-IDE-only MCP capabilities (Use for: SwiftUI previews image capture, code snippet execution, issue Navigator/build logs, and window/tab context).
-
-
-
-## Summary Statistics
-
-- **Canonical Tools**: 71
-- **Total Tools**: 99
-- **Workflow Groups**: 13
-
----
-
-*This documentation is automatically generated by `scripts/update-tools-docs.ts` from the tools manifest. Last updated: 2026-04-24T09:29:18.061Z UTC*
diff --git a/docs/TOOLS.md b/docs/TOOLS.md
deleted file mode 100644
index 533133e52..000000000
--- a/docs/TOOLS.md
+++ /dev/null
@@ -1,210 +0,0 @@
-# XcodeBuildMCP MCP Tools Reference
-
-This document lists MCP tool names as exposed to MCP clients. XcodeBuildMCP provides 77 canonical tools organized into 15 workflow groups for comprehensive Apple development workflows.
-
-## Workflow Groups
-
-### Build Utilities (`utilities`)
-**Purpose**: Utility tools for cleaning build products and managing build artifacts. (1 tools)
-
-- `clean` - Defined in iOS Device Development workflow.
-
-
-
-### Code Coverage (`coverage`)
-**Purpose**: View code coverage data from xcresult bundles produced by test runs. (2 tools)
-
-- `get_coverage_report` - Show per-target code coverage from an xcresult bundle.
-- `get_file_coverage` - Show function-level coverage and uncovered line ranges for a specific file.
-
-
-
-### iOS Device Development (`device`)
-**Purpose**: Complete iOS development workflow for physical devices (iPhone, iPad, Apple Watch, Apple TV, Apple Vision Pro). (15 tools)
-
-- `build_device` - Build for device.
-- `build_run_device` - Build, install, and launch on physical device. Runtime logs are captured automatically and the log file path is included in the response. Preferred single-step run tool when defaults are set.
-- `clean` - Clean build products.
-- `discover_projs` - Scans a directory (defaults to workspace root) to find Xcode project (.xcodeproj) and workspace (.xcworkspace) files. Use when project/workspace path is unknown.
-- `get_app_bundle_id` - Extract bundle id from .app.
-- `get_coverage_report` - Defined in Code Coverage workflow.
-- `get_device_app_path` - Get device built app path.
-- `get_file_coverage` - Defined in Code Coverage workflow.
-- `install_app_device` - Install app on device.
-- `launch_app_device` - Launch app on device.
-- `list_devices` - List connected devices.
-- `list_schemes` - List Xcode schemes.
-- `show_build_settings` - Show build settings.
-- `stop_app_device` - Stop device app.
-- `test_device` - Test on device.
-
-
-
-### iOS Simulator Development (`simulator`)
-**Purpose**: Complete iOS development workflow for both .xcodeproj and .xcworkspace files targeting simulators. (20 tools)
-
-- `boot_sim` - Defined in Simulator Management workflow.
-- `build_run_sim` - Build, install, and launch on iOS Simulator; boots simulator and attempts to open Simulator.app as needed. Runtime logs are captured automatically and the log file path is included in the response. Preferred single-step run tool when defaults are set.
-- `build_sim` - Build for iOS sim (compile-only, no launch).
-- `clean` - Defined in iOS Device Development workflow.
-- `discover_projs` - Defined in iOS Device Development workflow.
-- `get_app_bundle_id` - Defined in iOS Device Development workflow.
-- `get_coverage_report` - Defined in Code Coverage workflow.
-- `get_file_coverage` - Defined in Code Coverage workflow.
-- `get_sim_app_path` - Get sim built app path.
-- `install_app_sim` - Install app on sim.
-- `launch_app_sim` - Launch app on simulator. Runtime logs are captured automatically and the log file path is included in the response.
-- `list_schemes` - Defined in iOS Device Development workflow.
-- `list_sims` - Defined in Simulator Management workflow.
-- `open_sim` - Defined in Simulator Management workflow.
-- `record_sim_video` - Record sim video.
-- `screenshot` - Capture screenshot.
-- `show_build_settings` - Defined in iOS Device Development workflow.
-- `snapshot_ui` - Print view hierarchy with precise view coordinates (x, y, width, height) for visible elements.
-- `stop_app_sim` - Stop sim app.
-- `test_sim` - Test on iOS sim.
-
-
-
-### LLDB Debugging (`debugging`)
-**Purpose**: Attach LLDB debugger to simulator apps, set breakpoints, inspect variables and call stacks. (8 tools)
-
-- `debug_attach_sim` - Attach LLDB to sim app.
-- `debug_breakpoint_add` - Add breakpoint.
-- `debug_breakpoint_remove` - Remove breakpoint.
-- `debug_continue` - Continue debug session.
-- `debug_detach` - Detach debugger.
-- `debug_lldb_command` - Run LLDB command.
-- `debug_stack` - Get backtrace.
-- `debug_variables` - Get frame variables.
-
-
-
-### macOS Development (`macos`)
-**Purpose**: Complete macOS development workflow for both .xcodeproj and .xcworkspace files. Build, test, deploy, and manage macOS applications. (13 tools)
-
-- `build_macos` - Build macOS app.
-- `build_run_macos` - Build and run macOS app.
-- `clean` - Defined in iOS Device Development workflow.
-- `discover_projs` - Defined in iOS Device Development workflow.
-- `get_coverage_report` - Defined in Code Coverage workflow.
-- `get_file_coverage` - Defined in Code Coverage workflow.
-- `get_mac_app_path` - Get macOS built app path.
-- `get_mac_bundle_id` - Extract bundle id from macOS .app.
-- `launch_mac_app` - Launch macOS app.
-- `list_schemes` - Defined in iOS Device Development workflow.
-- `show_build_settings` - Defined in iOS Device Development workflow.
-- `stop_mac_app` - Stop macOS app.
-- `test_macos` - Test macOS target.
-
-
-
-### MCP Doctor (`doctor`)
-**Purpose**: Diagnostic tool providing comprehensive information about the MCP server environment, dependencies, and configuration. (1 tools)
-
-- `doctor` - MCP environment info.
-
-
-
-### Project Discovery (`project-discovery`)
-**Purpose**: Discover and examine Xcode projects, workspaces, and Swift packages. Analyze project structure, schemes, build settings, and bundle information. (5 tools)
-
-- `discover_projs` - Defined in iOS Device Development workflow.
-- `get_app_bundle_id` - Defined in iOS Device Development workflow.
-- `get_mac_bundle_id` - Defined in macOS Development workflow.
-- `list_schemes` - Defined in iOS Device Development workflow.
-- `show_build_settings` - Defined in iOS Device Development workflow.
-
-
-
-### Project Scaffolding (`project-scaffolding`)
-**Purpose**: Scaffold new iOS and macOS projects from templates. (2 tools)
-
-- `scaffold_ios_project` - Scaffold iOS project.
-- `scaffold_macos_project` - Scaffold macOS project.
-
-
-
-### Session Management (`session-management`)
-**Purpose**: Manage session defaults for project/workspace paths, scheme, configuration, simulator/device settings. (5 tools)
-
-- `session_clear_defaults` - Clear session defaults for the active profile or a specified profile.
-- `session_set_defaults` - Set session defaults for the active profile, or for a specified profile and make it active.
-- `session_show_defaults` - Show current active defaults. Required before your first build/run/test call in a session — do not assume defaults are configured.
-- `session_use_defaults_profile` - Switch the active session defaults profile.
-- `sync_xcode_defaults` - Sync session defaults (scheme, simulator) from Xcode's current IDE selection.
-
-
-
-### Simulator Management (`simulator-management`)
-**Purpose**: Tools for managing simulators from booting, opening simulators, listing simulators, stopping simulators, erasing simulator content and settings, and setting simulator environment options like location, network, statusbar and appearance. (8 tools)
-
-- `boot_sim` - Boot iOS simulator for manual/non-build flows. Not required before simulator build-and-run (build_run_sim).
-- `erase_sims` - Erase simulator.
-- `list_sims` - List iOS simulators.
-- `open_sim` - Open Simulator.app for visibility/manual workflows. Not required before simulator build-and-run (build_run_sim).
-- `reset_sim_location` - Reset sim location.
-- `set_sim_appearance` - Set sim appearance.
-- `set_sim_location` - Set sim location.
-- `sim_statusbar` - Set sim status bar network.
-
-
-
-### Swift Package Development (`swift-package`)
-**Purpose**: Build, test, run and manage Swift Package Manager projects. (8 tools)
-
-- `get_coverage_report` - Defined in Code Coverage workflow.
-- `get_file_coverage` - Defined in Code Coverage workflow.
-- `swift_package_build` - swift package target build.
-- `swift_package_clean` - swift package clean.
-- `swift_package_list` - List SwiftPM processes.
-- `swift_package_run` - swift package target run.
-- `swift_package_stop` - Stop SwiftPM run.
-- `swift_package_test` - Run swift package target tests.
-
-
-
-### UI Automation (`ui-automation`)
-**Purpose**: UI automation and accessibility testing tools for iOS simulators. Perform gestures, interactions, screenshots, and UI analysis for automated testing workflows. (11 tools)
-
-- `button` - Press simulator hardware button.
-- `gesture` - Simulator gesture preset.
-- `key_press` - Press key by keycode.
-- `key_sequence` - Press a sequence of keys by their keycodes.
-- `long_press` - Long press at coords.
-- `screenshot` - Defined in iOS Simulator Development workflow.
-- `snapshot_ui` - Defined in iOS Simulator Development workflow.
-- `swipe` - Swipe between points.
-- `tap` - Tap UI element by accessibility id/label (recommended) or coordinates as fallback.
-- `touch` - Touch down/up at coords.
-- `type_text` - Type text.
-
-
-
-### Workflow Discovery (`workflow-discovery`)
-**Purpose**: Manage enabled workflows at runtime. (1 tools)
-
-- `manage-workflows` - Workflows are groups of tools exposed by XcodeBuildMCP. By default, not all workflows (and therefore tools) are enabled; only simulator tools are enabled by default. Some workflows are mandatory and can't be disabled.
-
-
-
-### Xcode IDE Integration (`xcode-ide`)
-**Purpose**: Bridge tools for connecting to Xcode's built-in MCP server (mcpbridge) to access IDE-specific functionality. (5 tools)
-
-- `xcode_ide_call_tool` - Call a remote Xcode IDE MCP tool.
-- `xcode_ide_list_tools` - Lists Xcode-IDE-only MCP capabilities (Use for: SwiftUI previews image capture, code snippet execution, issue Navigator/build logs, and window/tab context).
-- `xcode_tools_bridge_disconnect` - Disconnect bridge and unregister proxied `xcode_tools_*` tools.
-- `xcode_tools_bridge_status` - Show xcrun mcpbridge availability and proxy tool sync status.
-- `xcode_tools_bridge_sync` - One-shot connect + tools/list sync (manual retry; avoids background prompt spam).
-
-
-
-## Summary Statistics
-
-- **Canonical Tools**: 77
-- **Total Tools**: 105
-- **Workflow Groups**: 15
-
----
-
-*This documentation is automatically generated by `scripts/update-tools-docs.ts` from the tools manifest. Last updated: 2026-04-24T09:29:18.061Z UTC*
diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md
deleted file mode 100644
index d24eed653..000000000
--- a/docs/TROUBLESHOOTING.md
+++ /dev/null
@@ -1,43 +0,0 @@
-# Troubleshooting
-
-## Quick triage
-- Run the doctor tool and include output when filing issues.
-- Confirm Xcode and Command Line Tools are installed.
-- Verify required workflows are enabled in configuration.
-- Check simulator/device availability and permissions.
-
-## Doctor tool
-The doctor tool checks system configuration and reports dependency/capability status for key XcodeBuildMCP workflows and runtime features.
-
-```bash
-npx --package xcodebuildmcp@latest xcodebuildmcp-doctor
-```
-
-It reports on:
-- System and Node.js environment
-- Xcode installation and configuration
-- Dependency and capability status (xcodebuild, AXe, debugger/backend requirements, etc.)
-- Environment variables affecting XcodeBuildMCP
-- Feature availability status
-
-> [!NOTE]
-> You can also ask you agent to run the doctor tool which will provide a more representative output.
-
-## Common issues
-
-### UI automation reports missing AXe
-UI automation (describe/tap/swipe/type) and simulator video capture require the AXe binary. If you see a missing AXe error:
-- Ensure `bundled/` artifacts exist in your installation.
-- Or set `XCODEBUILDMCP_AXE_PATH` to a known AXe binary path (preferred), or `AXE_PATH`.
-- Re-run the doctor tool to confirm AXe is detected.
-
-### Tool timeouts
-Some clients have short tool timeouts. If you see timeouts, increase the client timeout (for example, `tool_timeout_sec = 600` in Codex).
-
-### Missing tools
-If tools do not appear, verify `XCODEBUILDMCP_ENABLED_WORKFLOWS` includes the required workflow groups.
-
-## Related docs
-- Configuration options: [CONFIGURATION.md](CONFIGURATION.md)
-- Tools reference: [TOOLS.md](TOOLS.md)
-- Privacy and telemetry: [PRIVACY.md](PRIVACY.md)
diff --git a/docs/XCODE_IDE_MCPBRIDGE.md b/docs/XCODE_IDE_MCPBRIDGE.md
deleted file mode 100644
index 2b3b818df..000000000
--- a/docs/XCODE_IDE_MCPBRIDGE.md
+++ /dev/null
@@ -1,60 +0,0 @@
-# Xcode IDE MCP Bridge (`xcrun mcpbridge`)
-
-XcodeBuildMCP can optionally proxy Xcode’s built-in “Xcode Tools” MCP service via Xcode 26’s `xcrun mcpbridge`.
-
-This enables IDE-only tools (e.g. Preview rendering, Issue Navigator queries, documentation search, project navigator operations) that are not available via `xcodebuild`.
-
-## Enable
-
-Add `xcode-ide` to `enabledWorkflows` in `.xcodebuildmcp/config.yaml`:
-
-```yaml
-schemaVersion: 1
-enabledWorkflows: ["simulator", "debugging", "xcode-ide"]
-```
-
-If the workflow is not enabled, XcodeBuildMCP does not start the bridge.
-
-## MCP tools in `xcode-ide` workflow
-
-When `xcode-ide` is enabled and `mcpbridge` is available, XcodeBuildMCP exposes two gateway tools:
-
-- `xcode_ide_list_tools`: Lists remote tools from Xcode's MCP server (name, description, schemas).
-- `xcode_ide_call_tool`: Calls a remote Xcode tool by name with a JSON argument payload.
-
-These tools are stable and manifest-managed. They are shown when the `xcode-ide` workflow is enabled in MCP runtime; calls will fail until `mcpbridge` is available.
-
-CLI behavior is unchanged and continues to use dynamic `xcode_tools_*` proxy naming.
-
-## Bridge debug tools
-
-These tools are stable and do not depend on Xcode’s tool catalog, but they are intentionally hidden unless you enable debugging (`debug: true`) because they are primarily for troubleshooting:
-
-- `xcode_tools_bridge_status`: Shows `mcpbridge` availability, connection state, last error, and proxied tool count.
-- `xcode_tools_bridge_sync`: One-shot connect + re-sync (use this if Xcode prompts blocked startup sync).
-- `xcode_tools_bridge_disconnect`: Disconnect and unregister proxied `xcode_tools_*` tools.
-
-## Trust prompts / troubleshooting
-
-Xcode may show trust/allow prompts when the bridge connects and/or when tools are invoked.
-
-Recommended flow:
-
-1. Launch Xcode.
-2. Start XcodeBuildMCP with `xcode-ide` enabled.
-3. If `xcode_ide_list_tools` fails, temporarily set `debug: true` and call `xcode_tools_bridge_status` to inspect bridge health, then retry after approving prompts.
-
-## Targeting a specific Xcode instance (optional)
-
-If you need to scope the bridge to a specific Xcode instance, XcodeBuildMCP forwards these environment variables to the bridge:
-
-- `XCODEBUILDMCP_XCODE_PID` → `MCP_XCODE_PID`
-- `XCODEBUILDMCP_XCODE_SESSION_ID` → `MCP_XCODE_SESSION_ID`
-
-## Dev probe script
-
-For manual verification against a real Xcode install:
-
-```bash
-npx tsx scripts/probe-xcode-mcpbridge.ts
-```
diff --git a/docs/dev/ADDING_TOOLS_OUTPUT_MODEL.md b/docs/dev/ADDING_TOOLS_OUTPUT_MODEL.md
deleted file mode 100644
index a5c8241ee..000000000
--- a/docs/dev/ADDING_TOOLS_OUTPUT_MODEL.md
+++ /dev/null
@@ -1,389 +0,0 @@
-# Adding New Tools: Streaming vs Non-Streaming Output
-
-This repo uses a strict two-path output model for tools:
-
-1. **Streaming tools** emit fragments during execution **and** produce a final structured result.
-2. **Non-streaming tools** produce a final structured result only. They cannot emit fragments.
-
-The final structured result is always the canonical final output.
-
-## Quick decision rule
-
-Use a **non-streaming tool** when the tool computes a result and returns it once.
-
-Examples:
-- list simulators
-- show build settings
-- session defaults
-- clean
-- get coverage report
-
-Use a **streaming tool** when the tool has meaningful intermediate progress that should surface while the tool is running.
-
-Examples:
-- build
-- build and run
-- test
-- long-running process launch with phases/progress
-
-## The rule in one sentence
-
-- **Non-streaming**: `return result` — no execution context at all
-- **Streaming**: `emit fragments while running`, then `return result` — receives a `StreamingExecutionContext`
-
-A non-streaming tool cannot emit fragments and should not try.
-A streaming tool emits fragments through its execution context, not through the handler context.
-
-## Canonical ownership
-
-### Final structured result
-The final result must contain everything needed for final rendering.
-
-For build-like tools, that includes `request`.
-
-Example:
-
-```ts
-ctx.structuredOutput = {
- result: {
- kind: 'build-result',
- request: {
- scheme: params.scheme,
- projectPath: params.projectPath,
- configuration: 'Debug',
- platform: 'iOS Simulator',
- },
- didError: false,
- error: null,
- summary: { status: 'SUCCEEDED', durationMs: 3200 },
- artifacts: { buildLogPath: '/tmp/build.log' },
- diagnostics: { warnings: [], errors: [] },
- },
- schema: 'xcodebuildmcp.output.build-result',
- schemaVersion: '1',
-};
-```
-
-### Fragment stream
-Fragments are only for streaming tools. They surface live intermediate state such as:
-
-- invocation header
-- build stages
-- test discovery
-- test failures while running
-- phase transitions
-
-## Executor types
-
-Two executor shapes exist in `src/types/tool-execution.ts`:
-
-```ts
-// Non-streaming: accepts args, returns a result. No execution context.
-export type NonStreamingExecutor = (
- args: TArgs,
-) => Promise;
-
-// Streaming: accepts args and a StreamingExecutionContext for live fragment emission.
-export type StreamingExecutor = (
- args: TArgs,
- ctx: StreamingExecutionContext,
-) => Promise;
-```
-
-These are distinct type surfaces. Use the one that matches your tool.
-
-## Non-streaming tool pattern
-
-A non-streaming tool should:
-
-1. compute its result
-2. set `ctx.structuredOutput`
-3. return
-
-Non-streaming tools do **not** receive a `StreamingExecutionContext`.
-They cannot emit fragments at all.
-
-Minimal example:
-
-```ts
-export function createListThingsExecutor(
- executor: CommandExecutor,
-): NonStreamingExecutor {
- return async (params) => {
- const items = await loadThings(params.root, executor);
- return {
- kind: 'thing-list',
- didError: false,
- error: null,
- items,
- };
- };
-}
-
-export async function listThingsLogic(
- params: ListThingsParams,
- executor: CommandExecutor,
-): Promise {
- const ctx = getHandlerContext();
- const result = await createListThingsExecutor(executor)(params);
-
- ctx.structuredOutput = {
- result,
- schema: 'xcodebuildmcp.output.thing-list',
- schemaVersion: '1',
- };
-}
-```
-
-That is enough.
-
-Do **not** do this for a non-streaming tool:
-
-```ts
-ctx.emit(createBuildInvocationFragment(...)); // wrong: no streaming
-createStreamingExecutionContext(ctx); // wrong: no streaming
-```
-
-If the final text output needs header data, put that data on the final result.
-
-## Streaming tool pattern
-
-A streaming tool should:
-
-1. build a canonical invocation request
-2. emit the invocation fragment explicitly from the logic function via `ctx.emit(...)`
-3. create the execution context with `createStreamingExecutionContext(ctx)`
-4. stream progress fragments via `executionContext.emitFragment(...)` / pipeline
-5. set structured output with the final result
-
-Minimal example:
-
-```ts
-export async function buildThingLogic(
- params: BuildThingParams,
- executor: CommandExecutor,
-): Promise {
- const ctx = getHandlerContext();
- const request = createBuildThingRequest(params);
-
- ctx.emit(createBuildInvocationFragment('build-result', 'BUILD', request));
-
- const executionContext = createStreamingExecutionContext(ctx);
- const result = await createBuildThingExecutor(executor, request)(params, executionContext);
-
- setXcodebuildStructuredOutput(ctx, 'build-result', result);
-}
-```
-
-Example executor shape:
-
-```ts
-export function createBuildThingExecutor(
- executor: CommandExecutor,
- request: BuildInvocationRequest,
-): StreamingExecutor {
- return async (params, ctx) => {
- const started = createDomainStreamingPipeline('build_thing', 'BUILD', ctx, 'build-result');
-
- const commandResult = await runBuild(params, executor, started.pipeline);
-
- return createBuildDomainResult({
- started,
- succeeded: !commandResult.isError,
- target: 'simulator',
- artifacts: { buildLogPath: started.pipeline.logPath },
- fallbackErrorMessages: collectFallbackErrorMessages(started, [], commandResult.content),
- request,
- });
- };
-}
-```
-
-## Build-like tools must populate `request`
-
-For these result kinds, the final result must be self-sufficient:
-
-- `build-result`
-- `build-run-result`
-- `test-result`
-
-That means the final result must include:
-
-```ts
-request: BuildInvocationRequest
-```
-
-Do not rely on streamed invocation fragments to make final rendering work.
-
-## `ctx.emit(...)` vs `executionContext.emitFragment(...)`
-
-In streaming tools, two fragment emission sites exist:
-
-- `ctx.emit(...)` — called from the logic function for the invocation header
- fragment, before the executor runs. This appears as a durable fragment in
- the render session.
-- `executionContext.emitFragment(...)` — called from inside a streaming
- executor/pipeline for intermediate progress fragments (build stages, phase
- transitions, test progress, etc.).
-
-Non-streaming tools have no `executionContext`, so this question doesn't apply.
-
-## Anti-patterns
-
-Do not introduce these patterns.
-
-### 1. Calling `createStreamingExecutionContext(ctx)` from a non-streaming tool
-
-Bad:
-
-```ts
-export async function showBuildSettingsLogic(params, executor) {
- const ctx = getHandlerContext();
- const executionContext = createStreamingExecutionContext(ctx); // wrong
- const result = await runShowBuildSettings(params, executor);
- ctx.structuredOutput = { ... };
-}
-```
-
-Good:
-
-```ts
-export async function showBuildSettingsLogic(params, executor) {
- const ctx = getHandlerContext();
- const result = await createShowBuildSettingsExecutor(executor)(params);
- ctx.structuredOutput = { ... };
-}
-```
-
-### 2. Emitting invocation fragments from a non-streaming tool
-
-Bad:
-
-```ts
-ctx.emit(createBuildInvocationFragment(...)); // non-streaming tool
-```
-
-If the tool is static, the final rendering should derive everything from the
-structured result, not from a fragment stream.
-
-### 3. Post-hoc mutation of `result.request`
-
-Bad:
-
-```ts
-const result = createBuildRunDomainResult({ ... });
-result.request = invocationRequest;
-```
-
-Good:
-
-```ts
-const result = createBuildRunDomainResult({
- ...,
- request: invocationRequest,
-});
-```
-
-### 4. Adding a third output channel
-
-The only valid output channels are:
-
-- the fragment stream (streaming tools only)
-- `ctx.structuredOutput` (all tools)
-
-Do not add an `executionContext.emitResult(...)` side channel or similar.
-
-### 5. Using `NonStreamingExecutor` signature but emitting fragments inside
-
-A `NonStreamingExecutor` takes `(args) => Promise`.
-There is no execution context argument. If your executor needs to emit
-fragments, use `StreamingExecutor` instead.
-
-## Current approved patterns
-
-### Good: non-streaming tool
-
-```ts
-const result = await createFooExecutor(executor)(params);
-ctx.structuredOutput = {
- result,
- schema: 'xcodebuildmcp.output.foo',
- schemaVersion: '1',
-};
-```
-
-### Good: streaming build-like tool
-
-```ts
-ctx.emit(createBuildInvocationFragment('test-result', 'TEST', request));
-const executionContext = createStreamingExecutionContext(ctx);
-const result = await createTestExecutor(executor, request)(params, executionContext);
-setXcodebuildStructuredOutput(ctx, 'test-result', result);
-```
-
-### Good: progress emitted inside streaming executor/pipeline
-
-```ts
-ctx.emitFragment({
- kind: 'build-run-result',
- fragment: 'phase',
- phase: 'install-app',
- status: 'started',
-});
-```
-
-## Checklist for a new tool
-
-### If the tool is non-streaming
-- [ ] result contains everything needed for final rendering
-- [ ] no invocation fragment emitted
-- [ ] `ctx.structuredOutput` is set
-- [ ] executor uses `NonStreamingExecutor` signature
-- [ ] does not call `createStreamingExecutionContext(...)`
-
-### If the tool is streaming
-- [ ] build a canonical `request`
-- [ ] emit invocation fragment explicitly in the logic function via `ctx.emit(...)`
-- [ ] use `createStreamingExecutionContext(ctx)` to obtain a `StreamingExecutionContext`
-- [ ] executor uses `StreamingExecutor` signature
-- [ ] stream real progress fragments via the executor/pipeline
-- [ ] final result includes `request`
-- [ ] set structured output from the final result
-
-## Sanity checks after adding a tool
-
-Search for these smells:
-
-```text
-emitResult(
-pendingInvocationRequest
-result.request =
-```
-
-If you see them in new tool code, the implementation is probably drifting back toward the old hybrid model.
-
-Also verify that non-streaming tools do not reference `createStreamingExecutionContext` or `StreamingExecutor`.
-
-## Good reference files
-
-### Non-streaming tools
-- `src/mcp/tools/simulator/list_sims.ts`
-- `src/mcp/tools/project-discovery/discover_projs.ts`
-- `src/mcp/tools/project-discovery/show_build_settings.ts`
-- `src/mcp/tools/session-management/session_show_defaults.ts`
-- `src/mcp/tools/workflow-discovery/manage_workflows.ts`
-- `src/mcp/tools/swift-package/swift_package_clean.ts`
-
-### Streaming tools
-- `src/mcp/tools/simulator/build_sim.ts`
-- `src/mcp/tools/simulator/build_run_sim.ts`
-- `src/mcp/tools/simulator/test_sim.ts`
-- `src/mcp/tools/device/build_device.ts`
-- `src/mcp/tools/device/build_run_device.ts`
-- `src/mcp/tools/device/test_device.ts`
-- `src/mcp/tools/macos/build_macos.ts`
-- `src/mcp/tools/macos/build_run_macos.ts`
-- `src/mcp/tools/macos/test_macos.ts`
-- `src/mcp/tools/swift-package/swift_package_build.ts`
-- `src/mcp/tools/swift-package/swift_package_run.ts`
-- `src/mcp/tools/swift-package/swift_package_test.ts`
diff --git a/docs/dev/ARCHITECTURE.md b/docs/dev/ARCHITECTURE.md
deleted file mode 100644
index d3de9b5de..000000000
--- a/docs/dev/ARCHITECTURE.md
+++ /dev/null
@@ -1,575 +0,0 @@
-# XcodeBuildMCP Architecture
-
-## Table of Contents
-
-1. [Overview](#overview)
-2. [Core Architecture](#core-architecture)
-3. [Design Principles](#design-principles)
-4. [Component Details](#component-details)
-5. [Registration System](#registration-system)
-6. [Tool Naming Conventions & Glossary](#tool-naming-conventions--glossary)
-7. [Testing Architecture](#testing-architecture)
-8. [Build and Deployment](#build-and-deployment)
-9. [Extension Guidelines](#extension-guidelines)
-10. [Performance Considerations](#performance-considerations)
-11. [Security Considerations](#security-considerations)
-
-## Overview
-
-XcodeBuildMCP is a Model Context Protocol (MCP) server that exposes Xcode operations as tools for AI assistants. The architecture emphasizes modularity, type safety, and selective enablement to support diverse development workflows.
-
-### High-Level Objectives
-
-- Expose Xcode-related tools (build, test, deploy, UI automation, etc.) through MCP
-- Run as a long-lived stdio-based server for LLM agents, CLIs, or editors
-- Enable fine-grained, opt-in activation of individual tools or tool groups
-- Support incremental builds via experimental xcodemake with xcodebuild fallback
-
-## Core Architecture
-
-### Runtime Flow
-
-1. **Initialization**
- - The `xcodebuildmcp` executable, as defined in `package.json`, points to the compiled `build/cli.js` (CLI entrypoint from `src/cli.ts`); the MCP server starts via the `mcp` subcommand which invokes `src/index.ts`.
- - Sentry initialized for error tracking (optional)
- - Version information loaded from `package.json`
-
-2. **Server Creation**
- - MCP server created with stdio transport
- - Plugin discovery system initialized
-
-3. **Manifest-Driven Discovery**
- - YAML manifests in `manifests/tools/`, `manifests/workflows/`, and `manifests/resources/` define all metadata
- - `loadManifest()` reads and validates all YAML files at startup against Zod schemas
- - Tool and resource code modules are dynamically imported on demand
-
-4. **Tool & Resource Loading (Runtime)**
- - `registerWorkflowsFromManifest()` selects workflows based on config and predicate context, then dynamically imports tool modules
- - `registerResources()` loads resource manifests, filters by predicates, and dynamically imports resource modules
- - Both systems share the same `PredicateContext` for visibility filtering
- - If `XCODEBUILDMCP_ENABLED_WORKFLOWS` is set, only those workflows (plus `session-management`) are registered; `workflow-discovery` is only auto-included when `XCODEBUILDMCP_EXPERIMENTAL_WORKFLOW_DISCOVERY=true`
-
-5. **Tool & Resource Registration**
- - Tools are registered via `server.registerTool()` after manifest-driven workflow selection
- - Resources are registered via `server.resource()` after manifest-driven predicate filtering
- - Environment variables control workflow selection behavior
-
-5. **Request Handling**
- - MCP client calls tool → server routes to tool handler
- - Zod validates parameters before execution
- - Tool handler uses shared utilities (build, simctl, etc.)
- - Returns standardized `ToolResponse`
-
-6. **Response Streaming**
- - Server streams response back to client
- - Consistent error handling with `isError` flag
-
-## Design Principles
-
-### 1. **Plugin Autonomy**
-Tools are self-contained units that export a standardized interface. They don't know about the server implementation, ensuring loose coupling and high testability.
-
-### 2. **Pure Functions vs Stateful Components**
-- Most utilities are stateless pure functions
-- Stateful components (e.g., process tracking) isolated in specific tool modules
-- Clear separation between computation and side effects
-
-### 3. **Single Source of Truth**
-- Version from `package.json` drives all version references
-- Tool directory structure is authoritative tool source
-- Environment variables provide consistent configuration interface
-
-### 4. **Feature Isolation**
-- Experimental features behind environment flags
-- Optional dependencies (Sentry, xcodemake) gracefully degrade
-- Tool directory structure enables workflow-specific organization
-
-### 5. **Type Safety Throughout**
-- TypeScript strict mode enabled
-- Zod schemas for runtime validation
-- Generic type constraints ensure compile-time safety
-
-## Module Organization and Import Strategy
-
-### Focused Facades Pattern
-
-XcodeBuildMCP has migrated from a traditional "barrel file" export pattern (`src/utils/index.ts`) to a more structured **focused facades** pattern. Each distinct area of functionality within `src/utils` is exposed through its own `index.ts` file in a dedicated subdirectory.
-
-**Example Structure:**
-
-```
-src/utils/
-├── execution/
-│ └── index.ts # Facade for CommandExecutor, FileSystemExecutor
-├── logging/
-│ └── index.ts # Facade for the logger
-├── responses/
-│ └── index.ts # Facade for error types and response creators
-├── validation/
-│ └── index.ts # Facade for validation utilities
-├── axe/
-│ └── index.ts # Facade for axe UI automation helpers
-├── plugin-registry/
-│ └── index.ts # Facade for plugin system utilities
-├── xcodemake/
-│ └── index.ts # Facade for xcodemake utilities
-├── template/
-│ └── index.ts # Facade for template management utilities
-├── version/
-│ └── index.ts # Facade for version information
-├── test/
-│ └── index.ts # Facade for test utilities
-├── log-capture/
-│ └── index.ts # Facade for log capture utilities
-└── index.ts # Deprecated barrel file (legacy/external use only)
-```
-
-This approach offers several architectural benefits:
-
-- **Clear Dependencies**: It makes the dependency graph explicit. Importing from `utils/execution` clearly indicates a dependency on command execution logic
-- **Reduced Coupling**: Modules only import the functionality they need, reducing coupling between unrelated utility components
-- **Prevention of Circular Dependencies**: It's much harder to create circular dependencies, which were a risk with the large barrel file
-- **Improved Tree-Shaking**: Bundlers can more effectively eliminate unused code
-- **Performance**: Eliminates loading of unused modules, reducing startup time and memory usage
-
-### ESLint Enforcement
-
-To maintain this architecture, an ESLint rule in `eslint.config.js` explicitly forbids importing from the deprecated barrel file within the `src/` directory.
-
-**ESLint Rule Snippet** (`eslint.config.js`):
-
-```javascript
-'no-restricted-imports': ['error', {
- patterns: [{
- group: ['**/utils/index.js', '../utils/index.js', '../../utils/index.js', '../../../utils/index.js'],
- message: 'Barrel imports from utils/index.js are prohibited. Use focused facade imports instead (e.g., utils/logging/index.js, utils/execution/index.js).'
- }]
-}],
-```
-
-This rule prevents regression to the previous barrel import pattern and ensures all new code follows the focused facade architecture.
-
-## Component Details
-
-### Entry Points
-
-#### `src/index.ts`
-Main server entry point responsible for:
-- Sentry initialization (if enabled)
-- xcodemake availability check
-- Server creation and startup
-- Process lifecycle management (SIGTERM, SIGINT)
-- Error handling and logging
-
-#### `src/doctor-cli.ts`
-Standalone doctor tool for:
-- Environment validation
-- Dependency checking
-- Configuration verification
-- Troubleshooting assistance
-
-### Server Layer
-
-#### `src/server/server.ts`
-MCP server wrapper providing:
-- Server instance creation
-- stdio transport configuration
-- Request/response handling
-- Error boundary implementation
-
-### Tool Discovery System
-
-#### `src/core/plugin-registry.ts`
-Runtime plugin loading system that leverages build-time generated code:
-- Uses `WORKFLOW_LOADERS` and `WORKFLOW_METADATA` maps from the generated `src/core/generated-plugins.ts` file
-- `loadWorkflowGroups()` iterates through the loaders, dynamically importing each workflow module using `await loader()`
-- Validates that each imported module contains the required `workflow` metadata export
-- Aggregates all tools from the loaded workflows into a single map
-- This system eliminates runtime file system scanning, providing significant startup performance boost
-
-#### `src/core/plugin-types.ts`
-Plugin type definitions:
-- `PluginMeta` interface for plugin structure
-- `WorkflowMeta` interface for workflow metadata
-- `WorkflowGroup` interface for directory organization
-
-### Tool Implementation
-
-Each tool is implemented in TypeScript and follows a standardized pattern that separates the core business logic from the MCP handler boilerplate. This is achieved using the `createTypedTool` factory, which provides compile-time and runtime type safety.
-
-**Standard Tool Pattern** (`src/mcp/tools/some-workflow/some_tool.ts`):
-
-```typescript
-import { z } from 'zod';
-import { createTypedTool, getHandlerContext } from '../../../utils/typed-tool-factory.js';
-import type { CommandExecutor } from '../../../utils/execution/index.js';
-import { getDefaultCommandExecutor } from '../../../utils/execution/index.js';
-import { log } from '../../../utils/logging/index.js';
-import { withErrorHandling } from '../../../utils/tool-error-handling.js';
-import { header, statusLine } from '../../../utils/tool-event-builders.js';
-
-// 1. Define the Zod schema for parameters
-const someToolSchema = z.object({
- requiredParam: z.string().describe('Description for AI'),
- optionalParam: z.boolean().optional().describe('Optional parameter'),
-});
-
-// 2. Infer the parameter type from the schema
-type SomeToolParams = z.infer;
-
-// 3. Implement the core logic as an event-emitting function.
-// Handlers emit structured events via ctx.emit() instead of returning ToolResponse.
-export async function someToolLogic(
- params: SomeToolParams,
- executor: CommandExecutor,
-): Promise {
- const headerEvent = header('Some Tool', [
- { label: 'Param', value: params.requiredParam },
- ]);
- const ctx = getHandlerContext();
-
- return withErrorHandling(
- ctx,
- async () => {
- const result = await executor(['some', 'command'], 'Some Tool Operation');
-
- if (!result.success) {
- ctx.emit(headerEvent);
- ctx.emit(statusLine('error', `Operation failed: ${result.error}`));
- return;
- }
-
- ctx.emit(headerEvent);
- ctx.emit(statusLine('success', `Success: ${result.output}`));
- },
- {
- header: headerEvent,
- errorMessage: ({ message }) => `Tool execution failed: ${message}`,
- },
- );
-}
-
-// 4. Export schema shape and handler for manifest-driven auto-discovery
-export const schema = someToolSchema.shape;
-
-export const handler = createTypedTool(
- someToolSchema,
- someToolLogic,
- getDefaultCommandExecutor,
-);
-```
-
-This pattern ensures that:
-- The `someToolLogic` function is highly testable via dependency injection
-- Zod handles all runtime parameter validation automatically
-- The handler is type-safe, preventing unsafe access to parameters
-- Import paths use focused facades for clear dependency management
-```
-
-### Debugger Subsystem
-
-The debugging workflow relies on a long-lived, interactive LLDB subprocess. A `DebuggerManager` owns the session lifecycle and routes tool calls to a backend implementation. The default backend is the LLDB CLI (`xcrun lldb --no-lldbinit`) and configures a unique prompt sentinel to safely read command results. A stub DAP backend exists for future expansion.
-
-Key elements:
-- **Interactive execution**: Uses a dedicated interactive spawner with `stdin: 'pipe'` so LLDB commands can be streamed across multiple tool calls.
-- **Session manager**: Tracks debug session metadata (session id, simulator id, pid, timestamps) and maintains a “current” session.
-- **Backend abstraction**: `DebuggerBackend` keeps the tool contract stable while allowing future DAP support.
-
-### MCP Resources System
-
-XcodeBuildMCP provides dual interfaces: traditional MCP tools and efficient MCP resources for supported clients. Resources are located in `src/mcp/resources/` and are automatically discovered **at build time**. The build process generates `src/core/generated-resources.ts`, which contains dynamic loaders for each resource, improving startup performance. For more details on creating resources, see the [Plugin Development Guide](PLUGIN_DEVELOPMENT.md).
-
-#### Resource Architecture
-
-```
-src/mcp/resources/
-├── simulators.ts # Simulator data resource
-└── __tests__/ # Resource-specific tests
-```
-
-#### Client Capability Detection
-
-The system automatically detects client MCP capabilities:
-
-```typescript
-// src/core/resources.ts
-export function supportsResources(server?: unknown): boolean {
- // Detects client capabilities via getClientCapabilities()
- // Conservative fallback: assumes resource support
-}
-```
-
-#### Resource Implementation Pattern
-
-Resources can reuse existing tool logic for consistency:
-
-```typescript
-// src/mcp/resources/some_resource.ts
-import { log } from '../../utils/logging/index.js';
-import { getDefaultCommandExecutor, CommandExecutor } from '../../utils/execution/index.js';
-import { getSomeResourceLogic } from '../tools/some-workflow/get_some_resource.js';
-
-// Testable resource logic separated from MCP handler
-export async function someResourceResourceLogic(
- executor: CommandExecutor = getDefaultCommandExecutor(),
-): Promise<{ contents: Array<{ text: string }> }> {
- try {
- log('info', 'Processing some resource request');
-
- const result = await getSomeResourceLogic({}, executor);
-
- if (result.isError) {
- const errorText = result.content[0]?.text;
- throw new Error(
- typeof errorText === 'string' ? errorText : 'Failed to retrieve some resource data',
- );
- }
-
- return {
- contents: [
- {
- text:
- typeof result.content[0]?.text === 'string'
- ? result.content[0].text
- : 'No data for that resource is available',
- },
- ],
- };
- } catch (error) {
- const errorMessage = error instanceof Error ? error.message : String(error);
- log('error', `Error in some_resource resource handler: ${errorMessage}`);
-
- return {
- contents: [
- {
- text: `Error retrieving resource data: ${errorMessage}`,
- },
- ],
- };
- }
-}
-
-export default {
- uri: 'xcodebuildmcp://some_resource',
- name: 'some_resource',
- description: 'Returns some resource information',
- mimeType: 'text/plain',
- async handler(_uri: URL): Promise<{ contents: Array<{ text: string }> }> {
- return someResourceResourceLogic();
- },
-};
-```
-
-## Registration System
-
-XcodeBuildMCP registers tools at startup using the generated workflow loaders. Tool selection can be narrowed using the `XCODEBUILDMCP_ENABLED_WORKFLOWS` environment variable.
-
-### Full Registration (Default)
-
-- **Environment**: `XCODEBUILDMCP_ENABLED_WORKFLOWS` is not set.
-- **Behavior**: All available tools are loaded and registered with the MCP server at startup.
-- **Use Case**: Use this mode when you want the full suite of tools immediately available.
-
-### Selective Workflow Registration
-
-- **Environment**: `XCODEBUILDMCP_ENABLED_WORKFLOWS=simulator,device,project-discovery` (comma-separated)
-- **Behavior**: Only tools from the selected workflows are registered, plus the required `session-management` workflow.
-- **Use Case**: Use this mode to reduce tool surface area for focused workflows.
-
-## Tool Naming Conventions & Glossary
-
-Tools follow a consistent naming pattern to ensure predictability and clarity. Understanding this convention is crucial for both using and developing tools.
-
-### Naming Pattern
-
-The standard naming convention for tools is:
-
-`{action}_{target}_{specifier}_{projectType}`
-
-- **action**: The primary verb describing the tool's function (e.g., `build`, `test`, `get`, `list`).
-- **target**: The main subject of the action (e.g., `sim` for simulator, `dev` for device, `mac` for macOS).
-- **specifier**: A variant that specifies *how* the target is identified (e.g., `id` for UUID, `name` for by-name).
-- **projectType**: The type of Xcode project the tool operates on (e.g., `ws` for workspace, `proj` for project).
-
-Not all parts are required for every tool. For example, `swift_package_build` has an action and a target, but no specifier or project type.
-
-### Examples
-
-- `build_sim_id_ws`: **Build** for a **simulator** identified by its **ID (UUID)** from a **workspace**.
-- `test_dev_proj`: **Test** on a **device** from a **project**.
-- `get_mac_app_path_ws`: **Get** the app path for a **macOS** application from a **workspace**.
-- `list_sims`: **List** all **simulators**.
-
-### Glossary
-
-| Term/Abbreviation | Meaning | Description |
-|---|---|---|
-| `ws` | Workspace | Refers to an `.xcworkspace` file. Used for projects with multiple `.xcodeproj` files or dependencies managed by CocoaPods or SPM. |
-| `proj` | Project | Refers to an `.xcodeproj` file. Used for single-project setups. |
-| `sim` | Simulator | Refers to the iOS, watchOS, tvOS, or visionOS simulator. |
-| `dev` | Device | Refers to a physical Apple device (iPhone, iPad, etc.). |
-| `mac` | macOS | Refers to a native macOS application target. |
-| `id` | Identifier | Refers to the unique identifier (UUID/UDID) of a simulator or device. |
-| `name` | Name | Refers to the human-readable name of a simulator (e.g., "iPhone 15 Pro"). |
-| `cap` | Capture | Used in logging tools, e.g., `start_sim_log_cap`. |
-
-## Testing Architecture
-
-### Framework and Configuration
-
-- **Test Runner**: Vitest 3.x
-- **Environment**: Node.js
-- **Configuration**: `vitest.config.ts`
-- **Test Pattern**: `*.test.ts` files alongside implementation
-
-### Testing Principles
-
-XcodeBuildMCP uses a **Dependency Injection (DI)** pattern for MCP tool logic functions that orchestrate complex, long-running processes with sub-processes (e.g., `xcodebuild`), where standard vitest mocking produces race conditions. Standalone utility modules with simple commands may use direct `child_process`/`fs` imports and standard vitest mocking. Vitest mocking libraries (`vi.mock`, `vi.fn`, etc.) are also acceptable for internal collaborators.
-
-For detailed guidelines, see the [Testing Guide](TESTING.md).
-
-### Test Structure Example
-
-Tests for MCP tool logic inject mock "executors" for complex process orchestration (e.g., xcodebuild). This allows for deterministic testing without race conditions from non-deterministic sub-process ordering. The project provides helper functions like `createMockExecutor` and `createMockFileSystemExecutor` in `src/test-utils/mock-executors.ts`. Standalone utility modules with simple commands use standard vitest mocking.
-
-```typescript
-import { describe, it, expect } from 'vitest';
-import { someToolLogic } from '../tool-file.js'; // Import the logic function
-import { createMockExecutor } from '../../../test-utils/mock-executors.js';
-
-describe('Tool Name', () => {
- it('should execute successfully', async () => {
- // 1. Create a mock executor to simulate command-line results
- const mockExecutor = createMockExecutor({
- success: true,
- output: 'Command output'
- });
-
- // 2. Call the tool's logic function, injecting the mock executor
- const result = await someToolLogic({ requiredParam: 'value' }, mockExecutor);
-
- // 3. Assert the final result
- expect(result).toEqual({
- content: [{ type: 'text', text: 'Expected output' }],
- isError: false
- });
- });
-});
-```
-
-## Build and Deployment
-
-### Build Process
-
-1. **Version Generation**
- ```bash
- npm run build
- ```
- - Reads version from `package.json`
- - Generates `src/version.ts`
-
-2. **Plugin & Resource Loader Generation**
- - The `build-plugins/plugin-discovery.ts` script is executed
- - It scans `src/mcp/tools/` and `src/mcp/resources/` to find all workflows and resources
- - It generates `src/core/generated-plugins.ts` and `src/core/generated-resources.ts` with dynamic import maps
- - This eliminates runtime file system scanning and enables code-splitting
-
-3. **TypeScript Compilation**
- - `tsup` compiles the TypeScript source, including the newly generated files, into JavaScript
- - Compiles TypeScript with tsup
-
-4. **Build Configuration** (`tsup.config.ts`)
- - Entry points: `index.ts`, `doctor-cli.ts`
- - Output format: ESM
- - Target: Node 18+
- - Source maps enabled
-
-5. **Distribution Structure**
- ```
- build/
- ├── index.js # Main server executable
- ├── doctor-cli.js # Doctor tool
- └── *.js.map # Source maps
- ```
-
-### npm Package
-
-- **Name**: `xcodebuildmcp`
-- **Executables**:
- - `xcodebuildmcp` → Main server
- - `xcodebuildmcp-doctor` → Doctor tool
-- **Dependencies**: Minimal runtime dependencies
-- **Platform**: macOS only (due to Xcode requirement)
-
-### Bundled Resources
-
-```
-bundled/
-├── axe # UI automation binary
-└── Frameworks/ # Facebook device frameworks
- ├── FBControlCore.framework
- ├── FBDeviceControl.framework
- └── FBSimulatorControl.framework
-```
-
-## Extension Guidelines
-
-This project is designed to be extensible. For comprehensive instructions on creating new tools, workflow groups, and resources, please refer to the dedicated [**Plugin Development Guide**](PLUGIN_DEVELOPMENT.md).
-
-The guide covers:
-- The auto-discovery system architecture.
-- The dependency injection pattern required for all new tools.
-- How to organize tools into workflow groups.
-- Testing guidelines and patterns.
-
-## Performance Considerations
-
-### Startup Performance
-
-- **Build-Time Plugin Discovery**: The server avoids expensive and slow file system scans at startup by using pre-generated loader maps. This is the single most significant performance optimization
-- **Code-Splitting**: Workflow modules are loaded via dynamic imports when registration occurs, reducing the initial memory footprint and parse time
-- **Focused Facades**: Using targeted imports instead of a large barrel file improves module resolution speed for the Node.js runtime
-- **Lazy Loading**: Tools only initialized when registered
-- **Selective Registration**: Fewer tools = faster startup
-- **Minimal Dependencies**: Fast module resolution
-
-### Runtime Performance
-
-- **Stateless Operations**: Most tools complete quickly
-- **Process Management**: Long-running processes tracked separately
-- **Incremental Builds**: xcodemake provides significant speedup
-- **Parallel Execution**: Tools can run concurrently
-
-### Memory Management
-
-- **Process Cleanup**: Proper process termination handling
-- **Log Rotation**: Captured logs have size limits
-- **Resource Disposal**: Explicit cleanup in lifecycle hooks
-
-### Optimization Strategies
-
-1. **Use Tool Groups**: Enable only needed workflows
-2. **Enable Incremental Builds**: Set `INCREMENTAL_BUILDS_ENABLED=true`
-3. **Limit Log Capture**: Use structured logging when possible
-
-## Security Considerations
-
-### Input Validation
-
-- All tool inputs validated with Zod schemas
-- Command injection prevented via proper escaping
-- Path traversal protection in file operations
-
-### Process Isolation
-
-- Tools run with user permissions
-- No privilege escalation
-- Sandboxed execution environment
-
-### Error Handling
-
-- Sensitive information scrubbed from errors
-- Stack traces limited to application code
-- Sentry capture is explicit (`{ sentry: true }`) and limited to internal runtime failures
-- MCP wrapper auto-instrumentation is enabled for MCP observability, with tool input/output capture disabled (`recordInputs: false`, `recordOutputs: false`)
-- Request/user context and user home paths are scrubbed before telemetry is sent
diff --git a/docs/dev/CODE_QUALITY.md b/docs/dev/CODE_QUALITY.md
deleted file mode 100644
index 648c29043..000000000
--- a/docs/dev/CODE_QUALITY.md
+++ /dev/null
@@ -1,303 +0,0 @@
-# XcodeBuildMCP Code Quality Guide
-
-This guide consolidates all code quality, linting, and architectural compliance information for the XcodeBuildMCP project.
-
-## Table of Contents
-
-1. [Overview](#overview)
-2. [ESLint Configuration](#eslint-configuration)
-3. [Architectural Rules](#architectural-rules)
-4. [Development Scripts](#development-scripts)
-5. [Code Pattern Violations](#code-pattern-violations)
-6. [Type Safety Migration](#type-safety-migration)
-7. [Best Practices](#best-practices)
-
-## Overview
-
-XcodeBuildMCP enforces code quality through multiple layers:
-
-1. **ESLint**: Handles general code quality, TypeScript rules, and stylistic consistency
-2. **TypeScript**: Enforces type safety with strict mode
-3. **Pattern Checker**: Enforces XcodeBuildMCP-specific architectural rules
-4. **Migration Scripts**: Track progress on type safety improvements
-
-## ESLint Configuration
-
-### Current Configuration
-
-The project uses a comprehensive ESLint setup that covers:
-
-- TypeScript type safety rules
-- Code style consistency
-- Import ordering
-- Unused variable detection
-- Testing best practices
-
-### ESLint Rules
-
-For detailed ESLint rules and rationale, see [ESLINT_TYPE_SAFETY.md](ESLINT_TYPE_SAFETY.md).
-
-### Running ESLint
-
-```bash
-# Check for linting issues
-npm run lint
-
-# Auto-fix linting issues
-npm run lint:fix
-```
-
-## Architectural Rules
-
-XcodeBuildMCP enforces several architectural patterns that cannot be expressed through ESLint:
-
-### 1. Dependency Injection Pattern
-
-**Rule**: MCP tool logic functions that orchestrate complex, long-running processes with sub-processes (e.g., `xcodebuild`) must use dependency injection for external interactions. This is because standard vitest mocking produces race conditions when sub-process ordering is non-deterministic.
-
-Standalone utility modules that invoke simple, short-lived commands (e.g., `xcrun devicectl list`, `xcrun xcresulttool get`) may use direct `child_process`/`fs` imports and be tested with standard vitest mocking.
-
-✅ **Allowed**:
-- `createMockExecutor()` / `createMockFileSystemExecutor()` for complex process orchestration in tool logic
-- Logic functions accepting `executor?: CommandExecutor` parameter for xcodebuild and similar pipelines
-- Direct `child_process`/`fs` imports in standalone utility modules with simple commands, tested via vitest mocking
-
-❌ **Forbidden**:
-- Testing handler functions directly
-- Real external side effects in unit tests (real `xcodebuild`, `xcrun`, AXe, filesystem writes/reads outside test harness)
-
-### 2. Handler Signature Compliance
-
-**Rule**: MCP handlers must have exact signatures as required by the SDK.
-
-✅ **Tool Handler Signature**:
-```typescript
-async handler(args: Record): Promise
-```
-
-✅ **Resource Handler Signature**:
-```typescript
-async handler(uri: URL): Promise<{ contents: Array<{ text: string }> }>
-```
-
-❌ **Forbidden**:
-- Multiple parameters in handlers
-- Optional parameters
-- Dependency injection parameters in handlers
-
-### 3. Testing Architecture
-
-**Rule**: Tests must only call logic functions, never handlers directly.
-
-✅ **Correct Pattern**:
-```typescript
-const result = await myToolLogic(params, mockExecutor);
-```
-
-❌ **Forbidden Pattern**:
-```typescript
-const result = await myTool.handler(params);
-```
-
-### 4. Server Type Safety
-
-**Rule**: MCP server instances must use proper SDK types, not generic casts.
-
-✅ **Correct Pattern**:
-```typescript
-import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
-const server = (globalThis as { mcpServer?: McpServer }).mcpServer;
-server.server.createMessage({...});
-```
-
-❌ **Forbidden Pattern**:
-```typescript
-const server = (globalThis as { mcpServer?: Record }).mcpServer;
-const serverInstance = (server.server ?? server) as Record & {...};
-```
-
-## Development Scripts
-
-### Core Scripts
-
-```bash
-# Build the project
-npm run build
-
-# Run type checking
-npm run typecheck
-
-# Run tests
-npm run test
-
-# Check code patterns (architectural compliance)
-node scripts/check-code-patterns.js
-
-# Check type safety migration progress
-npm run check-migration
-```
-
-### Pattern Checker Usage
-
-The pattern checker enforces XcodeBuildMCP-specific architectural rules:
-
-```bash
-# Check all patterns
-node scripts/check-code-patterns.js
-
-# Check specific pattern type
-node scripts/check-code-patterns.js
-node scripts/check-code-patterns.js --pattern=execsync
-node scripts/check-code-patterns.js --pattern=handler
-node scripts/check-code-patterns.js --pattern=handler-testing
-node scripts/check-code-patterns.js --pattern=server-typing
-
-# Get help
-node scripts/check-code-patterns.js --help
-```
-
-### Tool Summary Scripts
-
-```bash
-# Show tool and resource summary
-npm run tools
-
-# List all tools
-npm run tools:list
-
-# List both tools and resources
-npm run tools:all
-```
-
-## Code Pattern Violations
-
-The pattern checker identifies the following violations:
-
-### 1. External-Boundary Mocking Violations
-
-**What**: Tests that mock external side effects without injected executors/filesystem dependencies
-**Why**: Breaks deterministic external-boundary testing
-**Fix**: Use `createMockExecutor()` / `createMockFileSystemExecutor()` for external dependencies
-### 2. ExecSync Violations
-
-**What**: Direct use of Node.js child_process functions in production code
-**Why**: Bypasses CommandExecutor dependency injection
-**Fix**: Accept `CommandExecutor` parameter and use it
-
-### 3. Handler Signature Violations
-
-**What**: Handlers with incorrect parameter signatures
-**Why**: MCP SDK requires exact signatures
-**Fix**: Move dependencies inside handler body
-
-### 4. Handler Testing Violations
-
-**What**: Tests calling `.handler()` directly
-**Why**: Violates dependency injection principle
-**Fix**: Test logic functions instead
-
-### 5. Improper Server Typing Violations
-
-**What**: Casting MCP server instances to `Record` or using custom interfaces instead of SDK types
-**Why**: Breaks type safety and prevents proper API usage
-**Fix**: Import `McpServer` from SDK and use proper typing instead of generic casts
-
-## Type Safety Migration
-
-The project is migrating to improved type safety using the `createTypedTool` factory:
-
-### Check Migration Status
-
-```bash
-# Show summary
-npm run check-migration
-
-# Show detailed analysis
-npm run check-migration:verbose
-
-# Show only unmigrated tools
-npm run check-migration:unfixed
-```
-
-### Migration Benefits
-
-1. **Compile-time type safety** for tool parameters
-2. **Automatic Zod schema validation**
-3. **Better IDE support** and autocomplete
-4. **Consistent error handling**
-
-## Best Practices
-
-### 1. Before Committing
-
-Always run these checks before committing:
-
-```bash
-npm run build # Ensure code compiles
-npm run typecheck # Check TypeScript types
-npm run lint # Check linting rules
-npm run test # Run tests
-node scripts/check-code-patterns.js # Check architectural compliance
-```
-
-### 2. Adding New Tools
-
-1. Use dependency injection pattern
-2. Follow handler signature requirements
-3. Create comprehensive tests (test logic, not handlers)
-4. Use `createTypedTool` factory for type safety
-5. Document parameter schemas clearly
-
-### 3. Writing Tests
-
-1. Import the logic function, not the default export
-2. Use `createMockExecutor()` / `createMockFileSystemExecutor()` for external side effects
-3. Test three dimensions: validation, command generation, output processing
-4. Never test handlers directly
-
-### 4. Code Organization
-
-1. Keep tools in appropriate workflow directories
-2. Share common tools via `-shared` directories
-3. Re-export shared tools, don't duplicate
-4. Follow naming conventions for tools
-
-## Automated Enforcement
-
-The project uses multiple layers of automated enforcement:
-
-1. **Pre-commit**: ESLint and TypeScript checks (if configured)
-2. **CI Pipeline**: All checks run on every PR
-3. **PR Blocking**: Checks must pass before merge
-4. **Code Review**: Automated and manual review processes
-
-## Troubleshooting
-
-### ESLint False Positives
-
-If ESLint reports false positives in test files, check that:
-1. Test files are properly configured in `.eslintrc.json`
-2. Test-specific rules are applied correctly
-3. File patterns match your test file locations
-
-### Pattern Checker Issues
-
-If the pattern checker reports unexpected violations:
-1. Check if it's a legitimate architectural violation
-2. Verify the file is in the correct directory
-3. Ensure you're using the latest pattern definitions
-
-### Type Safety Migration
-
-If migration tooling reports incorrect status:
-1. Ensure the tool exports follow standard patterns
-2. Check that schema definitions are properly typed
-3. Verify the handler uses the schema correctly
-
-## Future Improvements
-
-1. **Automated Fixes**: Add auto-fix capability to pattern checker
-2. **IDE Integration**: Create VS Code extension for real-time checking
-3. **Performance Metrics**: Add build and test performance tracking
-4. **Complexity Analysis**: Add code complexity metrics
-5. **Documentation Linting**: Add documentation quality checks
diff --git a/docs/dev/CONTRIBUTING.md b/docs/dev/CONTRIBUTING.md
deleted file mode 100644
index 9a09a4974..000000000
--- a/docs/dev/CONTRIBUTING.md
+++ /dev/null
@@ -1,418 +0,0 @@
-# Contributing
-
-Contributions are welcome! Here's how you can help improve XcodeBuildMCP.
-
-- [Local development setup](#local-development-setup)
- - [Prerequisites](#prerequisites)
- - [Optional: Enabling UI Automation](#optional-enabling-ui-automation)
- - [Installation](#installation)
- - [Configure your MCP client](#configure-your-mcp-client)
- - [Developing using VS Code](#developing-using-vs-code)
- - [Debugging](#debugging)
- - [MCP Inspector (Basic Debugging)](#mcp-inspector-basic-debugging)
- - [Reloaderoo (Advanced Debugging) - **RECOMMENDED**](#reloaderoo-advanced-debugging---recommended)
- - [1. Proxy Mode (Hot-Reloading)](#1-proxy-mode-hot-reloading)
- - [2. Inspection Mode (Raw MCP Debugging)](#2-inspection-mode-raw-mcp-debugging)
- - [Workflow Selection Testing](#workflow-selection-testing)
- - [Using XcodeBuildMCP doctor tool](#using-xcodebuildmcp-doctor-tool)
- - [Development Workflow with Reloaderoo](#development-workflow-with-reloaderoo)
-- [Architecture and Code Standards](#architecture-and-code-standards)
- - [Code Quality Requirements](#code-quality-requirements)
- - [Testing Standards](#testing-standards)
- - [Pre-Commit Checklist](#pre-commit-checklist)
-- [Making changes](#making-changes)
-- [Plugin Development](#plugin-development)
- - [Quick Plugin Development Checklist](#quick-plugin-development-checklist)
- - [Working with Project Templates](#working-with-project-templates)
- - [Template Repositories](#template-repositories)
- - [Local Template Development](#local-template-development)
- - [Template Versioning](#template-versioning)
- - [Testing Template Changes](#testing-template-changes)
-- [Testing](#testing)
-- [Submitting](#submitting)
-- [Code of Conduct](#code-of-conduct)
-
-## Local development setup
-
-### Prerequisites
-
-In addition to the prerequisites mentioned in the [Getting started](README.md/#getting-started) section of the README, you will also need:
-
-- Node.js (v18 or later)
-- npm
-
-#### Optional: Enabling UI Automation
-
-When running locally, you'll need to install AXe for UI automation:
-
-```bash
-# Install axe (required for UI automation)
-brew tap cameroncooke/axe
-brew install axe
-```
-
-#### Optional: Using a Local AXe Checkout for Bundling
-
-`npm run bundle:axe` defaults to downloading pinned AXe release artifacts from GitHub.
-
-To bundle from a local AXe source checkout instead:
-
-```bash
-AXE_USE_LOCAL=1 AXE_LOCAL_DIR=/absolute/path/to/AXe npm run bundle:axe
-```
-
-Rules:
-- Local mode is enabled only when `AXE_USE_LOCAL=1`.
-- `AXE_LOCAL_DIR` must point to a valid AXe repository (must contain `Package.swift`).
-- If `AXE_USE_LOCAL=1` and `AXE_LOCAL_DIR` is missing/invalid, bundling fails fast.
-- `AXE_FORCE_REMOTE=1` overrides local mode and forces remote artifact download.
-
-### Installation
-
-1. Clone the repository
-2. Install dependencies:
- ```
- npm install
- ```
-3. Install repository-managed git hooks:
- ```
- npm run hooks:install
- ```
- This configures `core.hooksPath` to `.githooks` so the shared pre-commit hook runs for this repository.
-4. Build the project:
- ```
- npm run build
- ```
-5. Start the server:
- ```
- node build/cli.js mcp
- ```
-
-### Configure your MCP client
-
-Most MCP clients (Cursor, VS Code, Windsurf, Claude Desktop etc) have standardised on the following JSON configuration format, just add the the following to your client's JSON configuration's `mcpServers` object:
-
-```json
-{
- "mcpServers": {
- "XcodeBuildMCP": {
- "command": "node",
- "args": [
- "/path_to/XcodeBuildMCP/build/cli.js",
- "mcp"
- ]
- }
- }
-}
-```
-
-### Developing using VS Code
-
-VS Code is especially good for developing XcodeBuildMCP as it has a built-in way to view MCP client/server logs as well as the ability to configure MCP servers at a project level. It probably has the most comprehensive support for MCP development.
-
-To make your development workflow in VS Code more efficient:
-
-1. **Start the MCP Server**: Open the `.vscode/mcp.json` file. You can start the `xcodebuildmcp-dev` server either by clicking the `Start` CodeLens that appears above the server definition, or by opening the Command Palette (`Cmd+Shift+P` or `Ctrl+Shift+P`), running `Mcp: List Servers`, selecting `xcodebuildmcp-dev`, and starting the server.
-2. **Launch the Debugger**: Press `F5` to attach the Node.js debugger.
-
-Once these steps are completed, you can utilize the tools from the MCP server you are developing within this repository in agent mode.
-For more details on how to work with MCP servers in VS Code see: https://code.visualstudio.com/docs/copilot/chat/mcp-servers
-
-### Debugging
-
-#### MCP Inspector (Basic Debugging)
-
-You can use MCP Inspector for basic debugging via:
-
-```bash
-npm run inspect
-```
-
-or if you prefer the explicit command:
-
-```bash
-npx @modelcontextprotocol/inspector node build/cli.js mcp
-```
-
-#### Reloaderoo (Advanced Debugging) - **RECOMMENDED**
-
-For development and debugging, we strongly recommend using **Reloaderoo**, which provides hot-reloading capabilities and advanced debugging features for MCP servers.
-
-Reloaderoo operates in two modes:
-
-##### 1. Proxy Mode (Hot-Reloading)
-Provides transparent hot-reloading without disconnecting your MCP client:
-
-```bash
-# Install reloaderoo globally
-npm install -g reloaderoo
-
-# Start XcodeBuildMCP through reloaderoo proxy
-reloaderoo -- node build/cli.js mcp
-```
-
-**Benefits**:
-- 🔄 Hot-reload server without restarting client
-- 🛠️ Automatic `restart_server` tool added to toolset
-- 🌊 Transparent MCP protocol forwarding
-- 📡 Full protocol support (tools, resources, prompts)
-
-**MCP Client Configuration for Proxy Mode**:
-```json
-"XcodeBuildMCP": {
- "command": "reloaderoo",
- "args": ["--", "node", "/path/to/XcodeBuildMCP/build/cli.js", "mcp"],
- "env": {
- "XCODEBUILDMCP_DEBUG": "true"
- }
-}
-```
-
-##### 2. Inspection Mode (Raw MCP Debugging)
-Exposes debug tools for making raw MCP protocol calls and inspecting server responses:
-
-```bash
-# Start reloaderoo in inspection mode
-reloaderoo inspect mcp -- node build/cli.js mcp
-```
-
-**Available Debug Tools**:
-- `list_tools` - List all server tools
-- `call_tool` - Execute any server tool with parameters
-- `list_resources` - List all server resources
-- `read_resource` - Read any server resource
-- `list_prompts` - List all server prompts
-- `get_prompt` - Get any server prompt
-- `get_server_info` - Get comprehensive server information
-- `ping` - Test server connectivity
-
-**MCP Client Configuration for Inspection Mode**:
-```json
-"XcodeBuildMCP": {
- "command": "node",
- "args": [
- "/path/to/reloaderoo/dist/bin/reloaderoo.js",
- "inspect", "mcp",
- "--working-dir", "/path/to/XcodeBuildMCP",
- "--",
- "node", "/path/to/XcodeBuildMCP/build/cli.js", "mcp"
- ],
- "env": {
- "XCODEBUILDMCP_DEBUG": "true"
- }
-}
-```
-
-#### Workflow Selection Testing
-
-Test full vs. selective workflow registration during development:
-
-```bash
-# Test full tool registration (default)
-reloaderoo inspect mcp -- node build/cli.js mcp
-
-# Test selective workflow registration
-XCODEBUILDMCP_ENABLED_WORKFLOWS=simulator,device reloaderoo inspect mcp -- node build/cli.js mcp
-```
-**Key Differences to Test**:
-- **Full Registration**: All tools are available immediately via `list_tools`
-- **Selective Registration**: Only tools from the selected workflows (plus `session-management`) are available
-
-#### Using XcodeBuildMCP doctor tool
-
-Running the XcodeBuildMCP server with the environmental variable `XCODEBUILDMCP_DEBUG=true` will expose a new doctor MCP tool called `doctor` which your agent can call to get information about the server's environment, available tools, and configuration status.
-
-> [!NOTE]
-> You can also call the doctor tool directly using the following command but be advised that the output may vary from that of the MCP tool call due to environmental differences:
-> ```bash
-> npm run doctor
-> ```
-
-#### Development Workflow with Reloaderoo
-
-1. **Start Development Session**:
- ```bash
- # Terminal 1: Start in hot-reload mode
- reloaderoo -- node build/cli.js mcp
-
- # Terminal 2: Start build watcher
- npm run build:watch
- ```
-
-2. **Make Changes**: Edit source code in `src/`
-
-3. **Test Changes**: Ask your AI client to restart the server:
- ```
- "Please restart the MCP server to load my changes"
- ```
- The AI will automatically call the `restart_server` tool provided by reloaderoo.
-
-4. **Verify Changes**: New functionality immediately available without reconnecting client
-
-## Architecture and Code Standards
-
-Before making changes, please familiarize yourself with:
-- [ARCHITECTURE.md](ARCHITECTURE.md) - Comprehensive architectural overview
-- [CLAUDE.md](../../CLAUDE.md) - AI assistant guidelines and testing principles
-- [TOOLS.md](../TOOLS.md) - Complete tool documentation
-- [CONFIGURATION.md](../CONFIGURATION.md) - Tool configuration options
-
-### Code Quality Requirements
-
-1. **Follow existing code patterns and structure**
-2. **Use TypeScript strictly** - no `any` types, proper typing throughout
-3. **Add proper error handling and logging** - all failures must set `isError: true`
-4. **Update documentation for new features**
-5. **Test with example projects before submitting**
-
-### Testing Standards
-
-All contributions must adhere to the testing standards outlined in the [**XcodeBuildMCP Plugin Testing Guidelines (TESTING.md)**](TESTING.md). This is the canonical source of truth for all testing practices.
-
-**Key Principles (Summary):**
-- **Dependency Injection for Complex Processes**: MCP tool logic functions that orchestrate complex, long-running processes with sub-processes (e.g., `xcodebuild`) must use injected `CommandExecutor` and `FileSystemExecutor` patterns. Standalone utility modules with simple commands may use direct imports and standard vitest mocking.
-- **Internal Mocking Is Allowed**: Vitest mocking (`vi.mock`, `vi.fn`, `vi.spyOn`, etc.) is acceptable for internal modules/collaborators.
-- **Test Production Code**: Tests must import and execute the actual tool logic, not mock implementations.
-- **Comprehensive Coverage**: Tests must cover input validation, command generation, and output processing.
-
-Please read [TESTING.md](TESTING.md) in its entirety before writing tests.
-
-### Pre-Commit Checklist
-
-**MANDATORY**: Run these commands before any commit and ensure they all pass:
-
-```bash
-# 1. Run linting (must pass with 0 errors)
-npm run lint:fix
-
-# 2. Run typechecker (must pass with 0 errors)
-npm run typecheck
-
-# 3. Run formatting (must format all files)
-npm run format
-
-# 4. Run build (must compile successfully)
-npm run build
-
-# 5. Validate docs CLI command references (requires built CLI artifact)
-npm run docs:check
-
-# 6. Run tests (all tests must pass)
-npm test
-```
-
-**NO EXCEPTIONS**: Code that fails any of these commands cannot be committed.
-
-The shared pre-commit hook installed via `npm run hooks:install` runs:
-- `npm run format:check`
-- `npm run lint`
-- `npm run build`
-- `npm run docs:check`
-
-## Making changes
-
-1. Fork the repository and create a new branch
-2. Follow the TypeScript best practices and existing code style
-3. Add proper parameter validation and error handling
-
-## Plugin Development
-
-For comprehensive instructions on creating new tools and workflow groups, see our dedicated [Plugin Development Guide](PLUGIN_DEVELOPMENT.md).
-
-The plugin development guide covers:
-- Auto-discovery system architecture
-- Tool creation with dependency injection patterns
-- Workflow group organization
-- Testing guidelines and patterns
-- Workflow registration and selection
-
-### Quick Plugin Development Checklist
-
-1. Choose appropriate workflow directory in `src/mcp/tools/`
-2. Follow naming conventions: `{action}_{target}_{specifier}_{projectType}`
-3. Use dependency injection pattern with separate logic functions
-4. Create comprehensive tests using `createMockExecutor()`
-5. Add workflow metadata if creating new workflow group
-
-See [PLUGIN_DEVELOPMENT.md](PLUGIN_DEVELOPMENT.md) for complete details.
-
-### Working with Project Templates
-
-XcodeBuildMCP uses external template repositories for the iOS and macOS project scaffolding features. These templates are maintained separately to allow independent versioning and updates.
-
-#### Template Repositories
-
-- **iOS Template**: [XcodeBuildMCP-iOS-Template](https://github.com/getsentry/XcodeBuildMCP-iOS-Template)
-- **macOS Template**: [XcodeBuildMCP-macOS-Template](https://github.com/getsentry/XcodeBuildMCP-macOS-Template)
-
-#### Local Template Development
-
-When developing or testing changes to the templates:
-
-1. Clone the template repository you want to work on:
- ```bash
- git clone https://github.com/getsentry/XcodeBuildMCP-iOS-Template.git
- git clone https://github.com/getsentry/XcodeBuildMCP-macOS-Template.git
- ```
-
-2. Set the appropriate environment variable to use your local template:
- ```bash
- # For iOS template development
- export XCODEBUILDMCP_IOS_TEMPLATE_PATH=/path/to/XcodeBuildMCP-iOS-Template
-
- # For macOS template development
- export XCODEBUILDMCP_MACOS_TEMPLATE_PATH=/path/to/XcodeBuildMCP-macOS-Template
- ```
-
-3. When using MCP clients, add these environment variables to your MCP configuration:
-```json
-"XcodeBuildMCP": {
- "command": "node",
- "args": ["/path_to/XcodeBuildMCP/build/cli.js", "mcp"],
- "env": {
- "XCODEBUILDMCP_IOS_TEMPLATE_PATH": "/path/to/XcodeBuildMCP-iOS-Template",
- "XCODEBUILDMCP_MACOS_TEMPLATE_PATH": "/path/to/XcodeBuildMCP-macOS-Template"
- }
-}
-```
-
-4. The scaffold tools will use your local templates instead of downloading from GitHub releases.
-
-#### Template Versioning
-
-- Templates are versioned independently from XcodeBuildMCP
-- The default template version is specified in `package.json` under `templateVersion`
-- You can override the template version with `XCODEBUILD_MCP_TEMPLATE_VERSION` environment variable
-- To update the default template version:
- 1. Update `templateVersion` in `package.json`
- 2. Run `npm run build` to regenerate version.ts
- 3. Create a new XcodeBuildMCP release
-
-#### Testing Template Changes
-
-1. Make changes to your local template
-2. Test scaffolding with your changes using the local override
-3. Verify the scaffolded project builds and runs correctly
-4. Once satisfied, create a PR in the template repository
-5. After merging, create a new release in the template repository using the release script
-
-## Testing
-
-1. Build the project with `npm run build`
-2. Test your changes with MCP Inspector
-3. Verify tools work correctly with different MCP clients
-
-## Submitting
-
-1. Run `npm run lint` to check for linting issues (use `npm run lint:fix` to auto-fix)
-2. Run `npm run format:check` to verify formatting (use `npm run format` to fix)
-3. Update documentation if you've added or modified features
-4. Add your changes to the CHANGELOG.md file
-5. Push your changes and create a pull request with a clear description
-6. Link any related issues
-
-For major changes or new features, please open an issue first to discuss your proposed changes.
-
-## Code of Conduct
-
-Please follow our [Code of Conduct](../../CODE_OF_CONDUCT.md) and community guidelines.
diff --git a/docs/dev/ESLINT_TYPE_SAFETY.md b/docs/dev/ESLINT_TYPE_SAFETY.md
deleted file mode 100644
index b0c4760c7..000000000
--- a/docs/dev/ESLINT_TYPE_SAFETY.md
+++ /dev/null
@@ -1,136 +0,0 @@
-# ESLint Type Safety Rules
-
-This document explains the ESLint rules added to prevent TypeScript anti-patterns and improve type safety.
-
-## Rules Added
-
-### Error-Level Rules (Block CI/Deployment)
-
-These rules prevent dangerous type casting patterns that can lead to runtime errors:
-
-#### `@typescript-eslint/consistent-type-assertions`
-- **Purpose**: Prevents dangerous object literal type assertions
-- **Example**: Prevents `{ foo: 'bar' } as ComplexType`
-- **Rationale**: Object literal assertions can hide missing properties
-
-#### `@typescript-eslint/no-unsafe-*` (5 rules)
-- **no-unsafe-argument**: Prevents passing `any` to typed parameters
-- **no-unsafe-assignment**: Prevents assigning `any` to typed variables
-- **no-unsafe-call**: Prevents calling `any` as a function
-- **no-unsafe-member-access**: Prevents accessing properties on `any`
-- **no-unsafe-return**: Prevents returning `any` from typed functions
-
-**Example of prevented anti-pattern:**
-```typescript
-// ❌ BAD - This would now be an ESLint error
-function handleParams(args: Record) {
- const typedParams = args as MyToolParams; // Unsafe casting
- return typedParams.someProperty as string; // Unsafe member access
-}
-
-// ✅ GOOD - Proper validation approach
-function handleParams(args: Record) {
- const typedParams = MyToolParamsSchema.parse(args); // Runtime validation
- return typedParams.someProperty; // Type-safe access
-}
-```
-
-#### `@typescript-eslint/ban-ts-comment`
-- **Purpose**: Prevents unsafe TypeScript comments
-- **Blocks**: `@ts-ignore`, `@ts-nocheck`
-- **Allows**: `@ts-expect-error` (with description)
-
-### Warning-Level Rules (Encourage Best Practices)
-
-These rules encourage modern TypeScript patterns but don't block builds:
-
-#### `@typescript-eslint/prefer-nullish-coalescing`
-- **Purpose**: Prefer `??` over `||` for default values
-- **Example**: `value ?? 'default'` instead of `value || 'default'`
-- **Rationale**: More precise handling of falsy values (0, '', false)
-
-#### `@typescript-eslint/prefer-optional-chain`
-- **Purpose**: Prefer `?.` for safe property access
-- **Example**: `obj?.prop` instead of `obj && obj.prop`
-- **Rationale**: More concise and readable
-
-#### `@typescript-eslint/prefer-as-const`
-- **Purpose**: Prefer `as const` for literal types
-- **Example**: `['a', 'b'] as const` instead of `['a', 'b'] as string[]`
-
-## Test File Exceptions
-
-Test files (`.test.ts`) have relaxed rules for flexibility:
-- All `no-unsafe-*` rules are disabled
-- `no-explicit-any` is disabled
-- Tests often need to test error conditions and edge cases
-
-## Impact on Codebase
-
-### Current Status (Post-Implementation)
-- **387 total issues detected**
- - **207 errors**: Require fixing for type safety
- - **180 warnings**: Can be gradually improved
-
-### Gradual Migration Strategy
-
-1. **Phase 1** (Immediate): Error-level rules prevent new anti-patterns
-2. **Phase 2** (Ongoing): Gradually fix warning-level violations
-3. **Phase 3** (Future): Consider promoting warnings to errors
-
-### Benefits
-
-1. **Prevents Regression**: New code can't introduce the anti-patterns we just fixed
-2. **Runtime Safety**: Catches potential runtime errors at compile time
-3. **Code Quality**: Encourages modern TypeScript best practices
-4. **Developer Experience**: Better IDE support and autocomplete
-
-## Related Issues Fixed
-
-These rules prevent the specific anti-patterns identified in PR review:
-
-1. **✅ Type Casting in Parameters**: `args as SomeType` patterns now flagged
-2. **✅ Unsafe Property Access**: `params.field as string` patterns prevented
-3. **✅ Missing Validation**: Encourages schema validation over casting
-4. **✅ Return Type Mismatches**: Function signature inconsistencies caught
-5. **✅ Nullish Coalescing**: Promotes safer default value handling
-
-## Agent Orchestration for ESLint Fixes
-
-### Parallel Agent Strategy
-
-When fixing ESLint issues across the codebase:
-
-1. **Deploy Multiple Agents**: Run agents in parallel on different files
-2. **Single File Focus**: Each agent works on ONE tool file at a time
-3. **Individual Linting**: Agents run `npm run lint path/to/single/file.ts` only
-4. **Immediate Commits**: Commit each agent's work as soon as they complete
-5. **Never Wait**: Don't wait for all agents to finish before committing
-6. **Avoid Full Linting**: Never run `npm run lint` without a file path (eats context)
-7. **Progress Tracking**: Update todo list and periodically check overall status
-8. **Loop Until Done**: Keep deploying agents until all issues are resolved
-
-### Example Commands for Agents
-
-```bash
-# Single file linting (what agents should run)
-npm run lint src/mcp/tools/device-project/test_device_proj.ts
-
-# NOT this (too much context)
-npm run lint
-```
-
-### Commit Strategy
-
-- **Individual commits**: One commit per agent completion
-- **Clear messages**: `fix: resolve ESLint errors in tool_name.ts`
-- **Never batch**: Don't wait to commit multiple files together
-- **Progress preservation**: Each fix is immediately saved
-
-## Future Improvements
-
-Consider adding these rules in future iterations:
-
-- `@typescript-eslint/strict-boolean-expressions`: Stricter boolean logic
-- `@typescript-eslint/prefer-reduce-type-parameter`: Better generic usage
-- `@typescript-eslint/switch-exhaustiveness-check`: Complete switch statements
\ No newline at end of file
diff --git a/docs/dev/FIXTURE_DESIGNS.md b/docs/dev/FIXTURE_DESIGNS.md
deleted file mode 100644
index 9397f11a0..000000000
--- a/docs/dev/FIXTURE_DESIGNS.md
+++ /dev/null
@@ -1,877 +0,0 @@
-# Snapshot test fixture designs
-
-Target UX for all tool output. This is the TDD reference — write fixtures first, then update rendering code until output matches.
-
-Delete this file once all fixtures are written and tests pass.
-
-## Output rhythm (all tools)
-
-```
-
-
- :
- :
-
-
-
-Next steps:
-1.
-```
-
-## Design principles
-
-1. No JSON output — all tools render structured data as human-readable text
-2. Every tool gets a header — emoji + operation name + indented params
-3. File paths always relative where possible (rendered by `displayPath`)
-4. Grouped/structured body — not raw command dumps. Focus on useful information
-5. Concise for AI agents — minimize tokens while maximizing signal
-6. Success + error + failure fixtures for every tool where appropriate (error = can't run; failure = ran, bad outcome)
-11. Error fixtures must test real executable errors — not just pre-call validation (file-exists checks, param validation). The fixture should exercise the underlying CLI/tool and capture how we handle its error response. Pre-call validation should be handled by yargs or input schemas, not tested as snapshot fixtures.
-7. Consistent icons — status emojis owned by renderer, not tools
-8. Consistent spacing — one blank line between sections, always
-9. No next steps on error paths
-10. Tree chars (├/└) for informational lists (paths, IDs, metadata) — not for result lists (errors, failures, test outcomes)
-
-### Error fixture policy
-
-Every error fixture must test a **real executable/CLI error** — not pre-call validation (file-exists checks, param validation). The fixture should exercise the underlying tool and capture how we handle its error response. Pre-call validation should be handled by yargs or input schemas, not tested as snapshot fixtures.
-
-One fixture per distinct CLI or output shape. The representative error fixtures cover all shapes:
-
-| CLI / Shape | Representative fixture |
-|---|---|
-| xcodebuild (wrong scheme) | `simulator/build--error-wrong-scheme` |
-| simctl terminate (bad bundle) | `simulator/stop--error-no-app` |
-| simctl boot (bad UUID) | `simulator-management/boot--error-invalid-id` |
-| open (invalid app) | `macos/launch--error-invalid-app` |
-| xcrun xccov (invalid bundle) | `coverage/get-coverage-report--error-invalid-bundle` |
-| swift build (bad path) | `swift-package/build--error-bad-path` |
-| AXe (bad simulator) | `ui-automation/tap--error-no-simulator` |
-| Internal: idempotency check | `project-scaffolding/scaffold-ios--error-existing` |
-| Internal: no active session | `debugging/continue--error-no-session` |
-| Internal: file coverage | `coverage/get-file-coverage--error-invalid-bundle` |
-
-## Tracking checklist
-
-### coverage
-- [x] `get-coverage-report--success.txt`
-- [x] `get-coverage-report--error-invalid-bundle.txt`
-- [x] `get-file-coverage--success.txt`
-- [x] `get-file-coverage--error-invalid-bundle.txt`
-- [ ] Code updated to match fixtures
-
-### session-management
-- [x] `session-set-defaults--success.txt`
-- [x] `session-show-defaults--success.txt`
-- [x] `session-clear-defaults--success.txt`
-- [ ] Code updated to match fixtures
-
-### simulator-management
-- [x] `list--success.txt`
-- [x] `boot--error-invalid-id.txt`
-- [x] `open--success.txt`
-- [x] `set-appearance--success.txt`
-- [x] `set-location--success.txt`
-- [x] `reset-location--success.txt`
-- [ ] Code updated to match fixtures
-
-### simulator
-- [x] `build--success.txt`
-- [x] `build--error-wrong-scheme.txt`
-- [x] `build--failure-compilation.txt`
-- [x] `build-and-run--success.txt`
-- [x] `test--success.txt`
-- [x] `test--failure.txt`
-- [x] `get-app-path--success.txt`
-- [x] `list--success.txt`
-- [x] `stop--error-no-app.txt`
-- [ ] Code updated to match fixtures
-
-### project-discovery
-- [x] `discover-projs--success.txt`
-- [x] `list-schemes--success.txt`
-- [x] `show-build-settings--success.txt`
-- [ ] Code updated to match fixtures
-
-### project-scaffolding
-- [x] `scaffold-ios--success.txt`
-- [x] `scaffold-ios--error-existing.txt`
-- [x] `scaffold-macos--success.txt`
-- [ ] Code updated to match fixtures
-
-### device
-- [x] `build--success.txt`
-- [x] `build--failure-compilation.txt`
-- [x] `get-app-path--success.txt`
-- [x] `list--success.txt`
-- [ ] Code updated to match fixtures
-
-### macos
-- [x] `build--success.txt`
-- [x] `build--failure-compilation.txt`
-- [x] `build-and-run--success.txt`
-- [x] `test--success.txt`
-- [x] `test--failure.txt`
-- [x] `get-app-path--success.txt`
-- [x] `launch--error-invalid-app.txt`
-- [ ] Code updated to match fixtures
-
-### swift-package
-- [x] `build--success.txt`
-- [x] `build--error-bad-path.txt`
-- [x] `build--failure-compilation.txt`
-- [x] `test--success.txt`
-- [x] `test--failure.txt`
-- [x] `clean--success.txt`
-- [x] `list--success.txt`
-- [x] `run--success.txt`
-- [ ] Code updated to match fixtures
-
-### debugging
-- [x] `attach--success.txt`
-- [x] `add-breakpoint--success.txt`
-- [x] `remove-breakpoint--success.txt`
-- [x] `continue--success.txt`
-- [x] `continue--error-no-session.txt`
-- [x] `detach--success.txt`
-- [x] `lldb-command--success.txt`
-- [x] `stack--success.txt`
-- [x] `variables--success.txt`
-- [ ] Code updated to match fixtures
-
-### ui-automation
-- [x] `snapshot-ui--success.txt`
-- [x] `tap--error-no-simulator.txt`
-- [ ] Code updated to match fixtures
-
-### utilities
-- [x] `clean--success.txt`
-- [ ] Code updated to match fixtures
-
----
-
-## Fixture designs by workflow
-
-### coverage
-
-**`get-coverage-report--success.txt`**:
-```
-📊 Coverage Report
-
- xcresult: /TestResults.xcresult
- Target Filter: CalculatorAppTests
-
-Overall: 94.9% (354/373 lines)
-
-Targets:
- CalculatorAppTests.xctest — 94.9% (354/373 lines)
-
-Next steps:
-1. View file-level coverage: xcodebuildmcp coverage get-file-coverage --xcresult-path "/TestResults.xcresult"
-```
-
-**`get-coverage-report--error-invalid-bundle.txt`** — real executable error (fake .xcresult dir passes file-exists check, xcrun xccov fails):
-```
-📊 Coverage Report
-
- xcresult: /invalid.xcresult
-
-❌ Failed to get coverage report: Failed to load result bundle.
-
-Hint: Run tests with coverage enabled (e.g., xcodebuild test -enableCodeCoverage YES).
-```
-
-**`get-file-coverage--success.txt`** — already updated, keep current content.
-
-**`get-file-coverage--error-invalid-bundle.txt`** — real executable error (fake .xcresult dir passes file-exists check, xcrun xccov fails):
-```
-📊 File Coverage
-
- xcresult: /invalid.xcresult
- File: SomeFile.swift
-
-❌ Failed to get file coverage: Failed to load result bundle.
-
-Hint: Make sure the xcresult bundle contains coverage data for "SomeFile.swift".
-```
-
----
-
-### session-management
-
-**`session-set-defaults--success.txt`**:
-```
-⚙️ Set Defaults
-
- Workspace: example_projects/iOS_Calculator/CalculatorApp.xcworkspace
- Scheme: CalculatorApp
-
-✅ Session defaults updated.
-```
-
-**`session-show-defaults--success.txt`**:
-```
-⚙️ Show Defaults
-
- Workspace: example_projects/iOS_Calculator/CalculatorApp.xcworkspace
- Scheme: CalculatorApp
-```
-
-**`session-clear-defaults--success.txt`**:
-```
-⚙️ Clear Defaults
-
-✅ Session defaults cleared.
-```
-
----
-
-### simulator-management
-
-**`list--success.txt`**:
-```
-📱 List Simulators
-
-iOS 26.2:
- iPhone 17 Pro Booted
- iPhone 17 Pro Max
- iPhone Air
- iPhone 17 Booted
- iPhone 16e
- iPad Pro 13-inch (M5)
- iPad Pro 11-inch (M5)
- iPad mini (A17 Pro)
- iPad (A16)
- iPad Air 13-inch (M3)
- iPad Air 11-inch (M3)
-
-watchOS 26.2:
- Apple Watch Series 11 (46mm)
- Apple Watch Series 11 (42mm)
- Apple Watch Ultra 3 (49mm)
- Apple Watch SE 3 (44mm)
- Apple Watch SE 3 (40mm)
-
-tvOS 26.2:
- Apple TV 4K (3rd generation)
- Apple TV 4K (3rd generation) (at 1080p)
- Apple TV
-
-xrOS 26.2:
- Apple Vision Pro
-
-Next steps:
-1. Boot simulator: xcodebuildmcp simulator-management boot --simulator-id "UUID_FROM_ABOVE"
-2. Open Simulator UI: xcodebuildmcp simulator-management open
-3. Build for simulator: xcodebuildmcp simulator build --scheme "YOUR_SCHEME" --simulator-id "UUID_FROM_ABOVE"
-4. Get app path: xcodebuildmcp simulator get-app-path --scheme "YOUR_SCHEME" --platform "iOS Simulator" --simulator-id "UUID_FROM_ABOVE"
-```
-
-Runtime names shortened from `com.apple.CoreSimulator.SimRuntime.iOS-26-2` to `iOS 26.2`. Tabular layout. Booted state shown inline.
-
-**`boot--error-invalid-id.txt`**:
-```
-🔌 Boot Simulator
-
- Simulator:
-
-❌ Failed to boot simulator: Invalid device or device pair:
-
-Next steps:
-1. Open Simulator UI: xcodebuildmcp simulator-management open
-2. Install app: xcodebuildmcp simulator install --simulator-id "SIMULATOR_UUID" --app-path "PATH_TO_YOUR_APP"
-3. Launch app: xcodebuildmcp simulator launch-app --simulator-id "SIMULATOR_UUID" --bundle-id "YOUR_APP_BUNDLE_ID"
-```
-
-**`open--success.txt`**:
-```
-📱 Open Simulator
-
-✅ Simulator app opened successfully.
-
-Next steps:
-1. Boot simulator: xcodebuildmcp simulator-management boot --simulator-id "UUID_FROM_LIST_SIMS"
-2. Start log capture: xcodebuildmcp logging start-simulator-log-capture --simulator-id "UUID" --bundle-id "YOUR_APP_BUNDLE_ID"
-3. Launch app with logs: xcodebuildmcp simulator launch-app-with-logs --simulator-id "UUID" --bundle-id "YOUR_APP_BUNDLE_ID"
-```
-
-**`set-appearance--success.txt`**:
-```
-🎨 Set Appearance
-
- Simulator:
- Mode: dark
-
-✅ Appearance set to dark mode.
-```
-
-**`set-location--success.txt`**:
-```
-📍 Set Location
-
- Simulator:
- Latitude: 37.7749
- Longitude: -122.4194
-
-✅ Location set to 37.7749, -122.4194.
-```
-
-**`reset-location--success.txt`**:
-```
-📍 Reset Location
-
- Simulator:
-
-✅ Location reset to default.
-```
-
----
-
-### simulator
-
-**`build--success.txt`** — pipeline-rendered, review for unified UX consistency.
-
-**`build--error-wrong-scheme.txt`** — pipeline-rendered, representative pipeline error fixture.
-
-**`build--failure-compilation.txt`** — build ran but failed with compiler errors (uses CompileError.fixture.swift injected into app target):
-```
-🔨 Build
-
- Scheme: CalculatorApp
- Workspace: example_projects/iOS_Calculator/CalculatorApp.xcworkspace
- Configuration: Debug
- Platform: iOS Simulator
- Simulator: iPhone 17
-
-Errors (1):
- ✗ CalculatorApp/CompileError.swift:3: Cannot convert value of type 'String' to specified type 'Int'
-
-❌ Build failed. (⏱️ )
-```
-
-**`build-and-run--success.txt`** — pipeline-rendered, review for consistency.
-
-**`test--success.txt`** — all tests pass:
-```
-🧪 Test
-
- Scheme: CalculatorApp
- Workspace: example_projects/iOS_Calculator/CalculatorApp.xcworkspace
- Configuration: Debug
- Platform: iOS Simulator
- Simulator: iPhone 17
-
-Resolved to test(s)
-
-✅ Test succeeded. (, ⏱️ )
-
-Next steps:
-1. View test coverage: xcodebuildmcp coverage get-coverage-report --xcresult-path "XCRESULT_PATH"
-```
-
-**`test--failure.txt`** — tests ran, assertion failures:
-```
-🧪 Test
-
- Scheme: CalculatorApp
- Workspace: example_projects/iOS_Calculator/CalculatorApp.xcworkspace
- Configuration: Debug
- Platform: iOS Simulator
- Simulator: iPhone 17
-
-Resolved to test(s)
-
-Failures (1):
- ✗ CalculatorAppTests.testCalculatorServiceFailure — XCTAssertEqual failed: ("0") is not equal to ("999")
-
-❌ Test failed. (, ⏱️ )
-```
-
-**`get-app-path--success.txt`**:
-```
-🔍 Get App Path
-
- Scheme: CalculatorApp
- Workspace: example_projects/iOS_Calculator/CalculatorApp.xcworkspace
- Configuration: Debug
- Platform: iOS Simulator
-
- └ App Path: /Library/Developer/Xcode/DerivedData/CalculatorApp-/Build/Products/Debug-iphonesimulator/CalculatorApp.app
-
-Next steps:
-1. Get bundle ID: xcodebuildmcp project-discovery get-app-bundle-id --app-path "/Library/Developer/Xcode/DerivedData/CalculatorApp-/Build/Products/Debug-iphonesimulator/CalculatorApp.app"
-2. Boot simulator: xcodebuildmcp simulator-management boot --simulator-id ""
-3. Install on simulator: xcodebuildmcp simulator install --simulator-id "" --app-path "/Library/Developer/Xcode/DerivedData/CalculatorApp-/Build/Products/Debug-iphonesimulator/CalculatorApp.app"
-4. Launch on simulator: xcodebuildmcp simulator launch-app --simulator-id "" --bundle-id "BUNDLE_ID"
-```
-
-**`list--success.txt`** — same as simulator-management/list--success.txt (shared tool).
-
-**`stop--error-no-app.txt`**:
-```
-🛑 Stop App
-
- Simulator:
- Bundle ID: com.nonexistent.app
-
-❌ Failed to stop app: An error was encountered processing the command (domain=com.apple.CoreSimulator.SimError, code=164): found nothing to terminate
-```
-
----
-
-### project-discovery
-
-**`discover-projs--success.txt`**:
-```
-🔍 Discover Projects
-
- Search Path: .
-
-Workspaces:
- example_projects/iOS_Calculator/CalculatorApp.xcworkspace
-
-Projects:
- example_projects/iOS_Calculator/CalculatorApp.xcodeproj
-
-Next steps:
-1. Build and run: xcodebuildmcp simulator build-and-run
-```
-
-**`list-schemes--success.txt`**:
-```
-🔍 List Schemes
-
- Workspace: example_projects/iOS_Calculator/CalculatorApp.xcworkspace
-
-Schemes:
- CalculatorApp
- CalculatorAppFeature
-
-Next steps:
-1. Build for macOS: xcodebuildmcp macos build --workspace-path "example_projects/iOS_Calculator/CalculatorApp.xcworkspace" --scheme "CalculatorApp"
-2. Build and run on simulator: xcodebuildmcp simulator build-and-run --workspace-path "example_projects/iOS_Calculator/CalculatorApp.xcworkspace" --scheme "CalculatorApp" --simulator-name "iPhone 17"
-3. Build for simulator: xcodebuildmcp simulator build --workspace-path "example_projects/iOS_Calculator/CalculatorApp.xcworkspace" --scheme "CalculatorApp" --simulator-name "iPhone 17"
-4. Show build settings: xcodebuildmcp device show-build-settings --workspace-path "example_projects/iOS_Calculator/CalculatorApp.xcworkspace" --scheme "CalculatorApp"
-```
-
-**`show-build-settings--success.txt`** — curated summary (full dump behind `--verbose` flag):
-```
-🔍 Show Build Settings
-
- Scheme: CalculatorApp
- Workspace: example_projects/iOS_Calculator/CalculatorApp.xcworkspace
-
-Key Settings:
- ├ PRODUCT_NAME: CalculatorApp
- ├ PRODUCT_BUNDLE_IDENTIFIER: io.sentry.calculatorapp
- ├ SDKROOT: iphoneos
- ├ SUPPORTED_PLATFORMS: iphonesimulator iphoneos
- ├ ARCHS: arm64
- ├ SWIFT_VERSION: 6.0
- ├ IPHONEOS_DEPLOYMENT_TARGET: 18.0
- ├ CODE_SIGNING_ALLOWED: YES
- ├ CODE_SIGN_IDENTITY: Apple Development
- ├ CONFIGURATION: Debug
- ├ BUILD_DIR: /Library/Developer/Xcode/DerivedData/CalculatorApp-/Build/Products
- └ BUILT_PRODUCTS_DIR: /Library/Developer/Xcode/DerivedData/CalculatorApp-/Build/Products/Debug-iphoneos
-
-Next steps:
-1. Build for simulator: xcodebuildmcp simulator build --workspace-path "example_projects/iOS_Calculator/CalculatorApp.xcworkspace" --scheme "CalculatorApp"
-```
-
----
-
-### project-scaffolding
-
-**`scaffold-ios--success.txt`**:
-```
-🏗️ Scaffold iOS Project
-
- Name: SnapshotTestApp
- Path: /ios
- Platform: iOS
-
-✅ Project scaffolded successfully.
-
-Next steps:
-1. Read the README.md in the workspace root directory before working on the project.
-2. Build for simulator: xcodebuildmcp simulator build --workspace-path "/ios/SnapshotTestApp.xcworkspace" --scheme "SnapshotTestApp" --simulator-name "iPhone 17"
-3. Build and run on simulator: xcodebuildmcp simulator build-and-run --workspace-path "/ios/SnapshotTestApp.xcworkspace" --scheme "SnapshotTestApp" --simulator-name "iPhone 17"
-```
-
-**`scaffold-ios--error-existing.txt`**:
-```
-🏗️ Scaffold iOS Project
-
- Path: /ios-existing
-
-❌ Xcode project files already exist in /ios-existing.
-```
-
-**`scaffold-macos--success.txt`**:
-```
-🏗️ Scaffold macOS Project
-
- Name: SnapshotTestApp
- Path: /macos
- Platform: macOS
-
-✅ Project scaffolded successfully.
-
-Next steps:
-1. Build for macOS: xcodebuildmcp macos build --project-path "/macos/SnapshotTestApp.xcodeproj" --scheme "SnapshotTestApp"
-2. Build and run on macOS: xcodebuildmcp macos build-and-run --project-path "/macos/SnapshotTestApp.xcodeproj" --scheme "SnapshotTestApp"
-```
-
----
-
-### device
-
-**`build--success.txt`** — pipeline-rendered, review for unified UX consistency.
-
-**`build--failure-compilation.txt`** — build ran but failed with compiler errors:
-```
-🔨 Build
-
- Scheme: CalculatorApp
- Workspace: example_projects/iOS_Calculator/CalculatorApp.xcworkspace
- Configuration: Debug
- Platform: iOS
-
-Errors (1):
- ✗ CalculatorApp/CompileError.swift:3: Cannot convert value of type 'String' to specified type 'Int'
-
-❌ Build failed. (⏱️ )
-```
-
-**`get-app-path--success.txt`**:
-```
-🔍 Get App Path
-
- Scheme: CalculatorApp
- Workspace: example_projects/iOS_Calculator/CalculatorApp.xcworkspace
- Configuration: Debug
- Platform: iOS
-
- └ App Path: /Library/Developer/Xcode/DerivedData/CalculatorApp-/Build/Products/Debug-iphoneos/CalculatorApp.app
-
-Next steps:
-1. Get bundle ID: xcodebuildmcp project-discovery get-app-bundle-id --app-path "..."
-2. Install on device: xcodebuildmcp device install --app-path "..."
-3. Launch on device: xcodebuildmcp device launch --bundle-id "BUNDLE_ID"
-```
-
-**`list--success.txt`**:
-```
-📱 List Devices
-
-✅ Available Devices:
-
- Cameron's Apple Watch
- ├ UDID:
- ├ Model: Watch4,2
- ├ Platform: Unknown 10.6.1
- ├ CPU: arm64_32
- └ Developer Mode: disabled
-
- Cameron's Apple Watch
- ├ UDID:
- ├ Model: Watch7,20
- ├ Platform: Unknown 26.1
- ├ CPU: arm64e
- ├ Connection: localNetwork
- └ Developer Mode: disabled
-
- Cameron's iPhone 16 Pro Max
- ├ UDID:
- ├ Model: iPhone17,2
- ├ Platform: Unknown 26.3.1
- ├ CPU: arm64e
- ├ Connection: localNetwork
- └ Developer Mode: enabled
-
- iPhone
- ├ UDID:
- ├ Model: iPhone99,11
- ├ Platform: Unknown 26.1
- └ CPU: arm64e
-
-Next steps:
-1. Build for device: xcodebuildmcp device build --scheme "SCHEME" --device-id "DEVICE_UDID"
-2. Run tests on device: xcodebuildmcp device test --scheme "SCHEME" --device-id "DEVICE_UDID"
-3. Get app path: xcodebuildmcp device get-app-path --scheme "SCHEME"
-```
-
----
-
-### macos
-
-**`build--success.txt`** — pipeline-rendered, review for unified UX consistency.
-
-**`build--failure-compilation.txt`** — build ran but failed with compiler errors:
-```
-🔨 Build
-
- Scheme: MCPTest
- Project: example_projects/macOS/MCPTest.xcodeproj
- Configuration: Debug
- Platform: macOS
-
-Errors (1):
- ✗ MCPTest/CompileError.swift:3: Cannot convert value of type 'String' to specified type 'Int'
-
-❌ Build failed. (⏱️ )
-```
-
-**`build-and-run--success.txt`** — pipeline-rendered, review for consistency.
-
-**`test--success.txt`** — all tests pass (MCPTest has only passing tests).
-
-**`test--failure.txt`** — tests ran, assertion failures (requires intentional failure in MCPTest):
-```
-🧪 Test
-
- Scheme: MCPTest
- Project: example_projects/macOS/MCPTest.xcodeproj
- Configuration: Debug
- Platform: macOS
-
-Resolved to test(s)
-
-Failures (1):
- ✗ MCPTestTests.testIntentionalFailure — Expectation failed
-
-❌ Test failed. (, ⏱️ )
-```
-
-**`get-app-path--success.txt`** — same pattern as simulator/device get-app-path.
-
-**`launch--error-invalid-app.txt`** — real `open` CLI error (fake .app dir passes file-exists, open fails):
-```
-🚀 Launch macOS App
-
- App: /Fake.app
-
-❌ Launch failed: The application cannot be opened because its executable is missing.
-```
-
----
-
-### swift-package
-
-**`build--success.txt`**:
-```
-📦 Swift Package Build
-
- Package: example_projects/SwiftPackage
-
-✅ Build succeeded. ()
-```
-
-**`build--error-bad-path.txt`** — real swift CLI error (swift build runs and fails on missing path):
-```
-📦 Swift Package Build
-
- Package: example_projects/NONEXISTENT
-
-❌ Build failed: No such file or directory: example_projects/NONEXISTENT
-```
-
-**`build--failure-compilation.txt`** — build ran but failed with compiler errors:
-```
-📦 Swift Package Build
-
- Package: example_projects/SwiftPackage
-
-Errors (1):
- ✗ Sources/CompileError.swift:3: Cannot convert value of type 'String' to specified type 'Int'
-
-❌ Build failed. ()
-```
-
-**`test--success.txt`**:
-```
-🧪 Swift Package Test
-
- Package: example_projects/SwiftPackage
-
-✅ All tests passed. (5 tests, )
-
-Tests:
- ✔ Array operations
- ✔ Basic math operations
- ✔ Basic truth assertions
- ✔ Optional handling
- ✔ String operations
-```
-
-**`test--failure.txt`** — tests ran, assertion failures (requires intentional failure in SPM example):
-```
-🧪 Swift Package Test
-
- Package: example_projects/SwiftPackage
-
-Failures (1):
- ✗ IntentionalFailureTests.testShouldFail — #expect failed
-
-❌ Tests failed. (1 failure, )
-```
-
-**`clean--success.txt`**:
-```
-🧹 Swift Package Clean
-
- Package: example_projects/SwiftPackage
-
-✅ Clean succeeded. Build artifacts removed.
-```
-
-**`list--success.txt`**:
-```
-📦 Swift Package List
-
-ℹ️ No Swift Package processes currently running.
-```
-
-**`run--success.txt`**:
-```
-📦 Swift Package Run
-
- Package: example_projects/SwiftPackage
-
-✅ Executable completed successfully.
-
-Output:
- Hello, world!
-```
-
----
-
-### debugging
-
-**`attach--success.txt`** — debugger attached to running simulator process:
-```
-🐛 Attach Debugger
-
- Simulator:
-
-✅ Attached LLDB to simulator process ().
-
- ├ Debug Session:
- └ Status: Execution resumed after attach.
-
-Next steps:
-1. Add breakpoint: xcodebuildmcp debugging add-breakpoint --file "..." --line 42
-2. View stack trace: xcodebuildmcp debugging stack
-3. View variables: xcodebuildmcp debugging variables
-```
-
-**`add-breakpoint--success.txt`** — breakpoint set at file:line:
-```
-🐛 Add Breakpoint
-
- File: ContentView.swift
- Line: 42
-
-✅ Breakpoint 1 set.
-
-Next steps:
-1. Continue execution: xcodebuildmcp debugging continue
-2. View stack trace: xcodebuildmcp debugging stack
-3. View variables: xcodebuildmcp debugging variables
-```
-
-**`remove-breakpoint--success.txt`**:
-```
-🐛 Remove Breakpoint
-
- Breakpoint: 1
-
-✅ Breakpoint 1 removed.
-```
-
-**`continue--success.txt`**:
-```
-🐛 Continue
-
-✅ Resumed debugger session.
-
-Next steps:
-1. View stack trace: xcodebuildmcp debugging stack
-2. View variables: xcodebuildmcp debugging variables
-```
-
-**`continue--error-no-session.txt`**:
-```
-🐛 Continue
-
-❌ No active debug session. Provide debugSessionId or attach first.
-```
-
-**`detach--success.txt`**:
-```
-🐛 Detach
-
-✅ Detached debugger session.
-```
-
-**`lldb-command--success.txt`** — raw LLDB output passed through:
-```
-🐛 LLDB Command
-
- Command: po self
-
-
-```
-
-**`stack--success.txt`** — stack trace from paused process:
-```
-🐛 Stack Trace
-
-* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
- * frame #0: CalculatorApp`ContentView.body.getter at ContentView.swift:42
- frame #1: SwiftUI`ViewGraph.updateOutputs()
- frame #2: SwiftUI`ViewRendererHost.render()
-```
-
-**`variables--success.txt`** — variable dump from current frame:
-```
-🐛 Variables
-
-(CalculatorService) self = {
- ├ display = "0"
- ├ expressionDisplay = ""
- ├ currentValue = 0
- ├ previousValue = 0
- └ currentOperation = nil
-}
-```
-
----
-
-### ui-automation
-
-**`snapshot-ui--success.txt`** — accessibility tree with header prepended:
-```
-🔍 Snapshot UI
-
- Simulator:
-
-
-```
-
-**`tap--error-no-simulator.txt`**:
-```
-👆 Tap
-
- Simulator:
- Position: (100, 100)
-
-❌ Failed to simulate tap: Simulator with UDID not found.
-```
-
----
-
-### utilities
-
-**`clean--success.txt`** — pipeline-rendered, review for unified UX consistency.
diff --git a/docs/dev/INVESTIGATION_OUTPUT_FORMATTING_CONSISTENCY.md b/docs/dev/INVESTIGATION_OUTPUT_FORMATTING_CONSISTENCY.md
deleted file mode 100644
index f13717022..000000000
--- a/docs/dev/INVESTIGATION_OUTPUT_FORMATTING_CONSISTENCY.md
+++ /dev/null
@@ -1,210 +0,0 @@
-# Investigation: Output Formatting Consistency
-
-## Summary
-
-Two follow-up questions answered: (1) The three-renderer architecture was the **original implementation choice from day one** — the plan's "one renderer, two sinks" vision was never attempted because interactive CLI behavior requires state-machine logic incompatible with "dumb pipe" sinks. (2) The 6 holdout tools match their fixtures because they were **actually migrated to the pipeline** in commit `ac33b97f`, then **deliberately reverted** in commit `c0693a1d` (WIP). Fixtures were updated to match the reverted format. The snapshot harness validates final text only, not pipeline provenance.
-
-## Symptoms
-
-- Three separate renderers exist instead of the plan's "one renderer, two sinks"
-- 6 tool files still manually construct ToolResponse objects
-- These holdout tools have passing snapshot fixtures despite bypassing the pipeline
-
-## Investigation Log
-
-### Phase 1 — Why Three Renderers Instead of One?
-
-**Hypothesis:** The implementation started with one renderer and was later split into three.
-
-**Findings:** **Eliminated.** Git archaeology shows the three-renderer pattern was the original design from the first commit:
-
-**Commit `1374d3c2` (March 21)** — "Unify pipeline architecture, rendering, and output formatting"
-- This is when `mcp-renderer.ts`, `cli-text-renderer.ts`, and `cli-jsonl-renderer.ts` were **first created** (confirmed via `git log --diff-filter=A`)
-- The initial `index.ts` already had `resolveRenderers()` returning multiple renderer instances
-- At this point, renderers only handled `XcodebuildEvent` types
-
-**Commit `ac33b97f` (March 25)** — "Migrate all tool output to unified PipelineEvent system"
-- Upgraded all three renderers from `XcodebuildEvent` to generic `PipelineEvent`
-- Added generic event type handlers (`header`, `status-line`, `section`, `detail-tree`, `table`, `file-ref`)
-- The three-renderer architecture was preserved and extended, not questioned
-
-**Evidence:** The plan document's "one renderer, two sinks" was written as a forward-looking vision. The implementer chose separate renderers from the start because:
-
-1. **CLI text renderer requires state-machine logic** that can't be a "dumb pipe":
- - Tracks `pendingTransientRuntimeLine` for spinner management (`cli-text-renderer.ts:46`)
- - Tracks `hasDurableRuntimeContent` for flush decisions (`cli-text-renderer.ts:47`)
- - Tracks `lastVisibleEventType` and `lastStatusLineLevel` for compact spacing (`cli-text-renderer.ts:48-49`)
- - Uses `createCliProgressReporter()` for Clack spinner integration (`cli-text-renderer.ts:45`)
- - Has conditional logic for `interactive` mode (`cli-text-renderer.ts:84-88` for `build-stage`, `cli-text-renderer.ts:93-105` for `status-line`)
-
-2. **MCP renderer has different semantics**:
- - Buffers text parts as strings, returns `ToolResponseContent[]` (`mcp-renderer.ts:36-37, 147-152`)
- - Applies session-level `suppressWarnings` (`mcp-renderer.ts:34`)
- - Different spacing rules (e.g., `section` uses `\n\n` prefix at `mcp-renderer.ts:68`, CLI uses `writeSection()` which adds `\n`)
-
-3. **JSONL renderer bypasses text rendering entirely**:
- - Serializes raw events as JSON: `process.stdout.write(JSON.stringify(event) + '\n')` (`cli-jsonl-renderer.ts`)
- - Making this a "sink" of a text renderer doesn't make architectural sense
-
-4. **Shared formatting layer already exists**:
- - `event-formatting.ts` contains all shared format functions (`formatHeaderEvent`, `formatStatusLineEvent`, `formatSectionEvent`, etc.)
- - Both text renderers call the same format functions
- - The difference is in orchestration (buffering vs streaming, transient handling, spacing), not in formatting
-
-**Conclusion:** The three-renderer architecture is a **deliberate pragmatic choice**. The "one renderer, two sinks" model from the plan was aspirational but not viable because CLI interactive behavior (spinners, transient lines, flush timing) requires active event-processing logic. The current design (shared formatters + separate renderers) is functionally equivalent to "shared formatting, runtime-specific orchestration."
-
----
-
-### Phase 2 — How Do Holdout Tools Match Fixtures?
-
-**Hypothesis:** The holdout tools were never migrated and their fixtures encode their original manual format.
-
-**Findings:** **Eliminated.** The holdout tools were actually migrated and then **reverted**. The git history tells the story clearly:
-
-#### Timeline
-
-1. **Commit `ac33b97f` (March 25)** — All tools migrated to pipeline:
- - `get_sim_app_path.ts` used `toolResponse()`, `header()`, `statusLine()`, `detailTree()`
- - `list_devices.ts` used pipeline events for all paths
- - All 74 fixtures regenerated with pipeline-formatted output
- - Fixture for `get-app-path--success.txt` had 2-space indented params, `detailTree` output, simple "App path resolved" message
-
-2. **Commit `c0693a1d` (March 28, WIP)** — Selective reversion:
- - `get_sim_app_path.ts`: Replaced `toolResponse`/`header`/`statusLine`/`detailTree` with `formatToolPreflight` + manual `content: [{type: 'text'}]`
- - `get_device_app_path.ts`: Same reversion pattern
- - `get_mac_app_path.ts`: Same reversion pattern
- - `list_devices.ts`: Added `renderGroupedDevices()` manual string builder
- - `session_show_defaults.ts`: Added emoji to section titles, manual tree connectors
- - `screenshot.ts`: Added manual content branches
- - **Fixtures simultaneously updated** to match new manual output
-
-**Evidence from fixture diffs:**
-
-`simulator/get-app-path--success.txt` changed FROM (pipeline):
-```
- Scheme: CalculatorApp (2-space indent, pipeline HeaderEvent)
- └ App Path: ... (detailTree event)
-✅ App path resolved (statusLine event)
-```
-
-TO (manual):
-```
- Scheme: CalculatorApp (3-space indent, formatToolPreflight)
-✅ Get app path successful (⏱️ ) (inline text with emoji)
- └ App Path: ... (manual tree connector)
-```
-
-`device/list--success.txt` changed FROM (pipeline):
-```
-🟢 Cameron's Apple Watch (per-device with detailTree)
- ├ UDID: ...
- ├ Model: Watch4,2
- ├ CPU Architecture: arm64_32
- └ Developer Mode: disabled
-```
-
-TO (manual):
-```
-watchOS Devices: (grouped by platform)
- ⌚️ [✓] Cameron's Apple Watch (emoji + availability marker)
- OS: 26.3
- UDID:
-```
-
-#### Why do fixtures still pass?
-
-The snapshot test harness at `src/snapshot-tests/harness.ts` validates **final text output, not pipeline provenance**:
-
-1. **CLI path** (`invokeCli`, line 124): Spawns `node CLI_PATH workflow tool --json args`, captures stdout
-2. **Direct path** (`invokeDirect`, line 141): Calls handler, extracts `ToolResponse.content` text
-
-For manual-text tools (not MCP-only, not stateful), the harness uses CLI invocation:
-- Tool returns `ToolResponse` with manual `content[].text`
-- `printToolResponse()` in `cli/output.ts` checks `isCompletePipelineStream(response)` — **false** for manual tools (no `_meta.pipelineStreamMode`)
-- Falls through to `printToolResponseText()` which writes `content[].text` to stdout
-- Harness captures stdout, normalizes via `normalize.ts`, compares to fixture via `expect(actual).toBe(expected)` in `fixture-io.ts`
-
-For pipeline tools:
-- CLI text renderer streams formatted output to stdout during execution
-- `printToolResponse()` sees `pipelineStreamMode: 'complete'` and **skips printing** (avoids double output)
-- Harness captures the already-streamed stdout
-
-Both paths produce stdout text that gets compared to the fixture. The fixture encodes whatever text was actually produced, regardless of whether it came from the pipeline.
-
-**Conclusion:** The holdout tools pass their fixtures because the fixtures were updated to match the reverted manual format. The snapshot suite is a **final output contract test**, not a pipeline provenance test.
-
----
-
-### Phase 3 — Why Were Tools Reverted?
-
-**Assessment by category:**
-
-#### `get_sim_app_path`, `get_device_app_path`, `get_mac_app_path` — Expedient compromise
-
-The pipeline has all the primitives needed for these tools (`HeaderEvent`, `StatusLineEvent`, `DetailTreeEvent`, `NextStepsEvent`). The reverted format is not something the pipeline can't express — it just uses `formatToolPreflight` (3-space indent) instead of pipeline `HeaderEvent` (2-space indent), and inline emoji instead of renderer-owned formatting.
-
-This reads as a "preserve exact legacy wording/spacing quickly" decision, not a fundamental pipeline limitation.
-
-#### `list_devices` — Deliberate UX preference
-
-This tool has a purpose-built `renderGroupedDevices()` function that produces a grouped-by-platform layout with platform-specific emojis (`📱`, `⌚️`, `📺`, `🥽`) and availability markers (`[✓]`/`[✗]`). The pipeline version showed flat per-device `detailTree` output with hardware details (Model, Product Type, CPU Architecture). The grouped format is arguably better UX for scanning.
-
-That said, the pipeline's `section()` + structured lines could still express this layout.
-
-#### `swift_package_run`, `swift_package_test` — Defensive escape hatch, not reverted UX
-
-These are pipeline-first tools. The manual `content` branch is only hit on command failure:
-```typescript
-const response: ToolResponse = result.success
- ? { content: [], isError: false }
- : { content: [{ type: 'text', text: result.error || ... }], isError: true };
-```
-
-And `errorFallbackPolicy: 'if-no-structured-diagnostics'` in `xcodebuild-output.ts` explicitly suppresses the raw fallback when structured diagnostics exist. The fixtures show pipeline-formatted output. These aren't really "holdouts" — they're pipeline tools with a safety net.
-
----
-
-## Root Cause
-
-### Q1: Three renderers
-The three-renderer architecture was the **pragmatic original design**, not a deviation from the plan. The plan's "one renderer, two sinks" model doesn't account for the interactive state-machine behavior required by the CLI text renderer (spinners, transient/durable line management, test progress updates). The actual architecture — shared formatting helpers + runtime-specific renderers — achieves the plan's goal of consistent formatting while accommodating runtime differences.
-
-### Q2: Fixture matching
-The holdout tools pass fixtures through a two-step mechanism:
-1. Tools were migrated to the pipeline, then reverted to manual text
-2. Fixtures were simultaneously updated to encode the manual output format
-3. The snapshot harness compares final stdout text, not pipeline provenance
-
-The reversion was the wrong approach. The fixtures define the target output contract — if the pipeline at migration time couldn't produce the desired format, the pipeline should have been extended (new event types, new formatting functions), not bypassed. The tools were hand-crafted to match the fixtures instead of the pipeline being updated to produce them.
-
-Designed fixtures exist in `__fixtures_designed__/` that show an earlier target format. The actual `__fixtures__/` files represent the current target. Both should be producible by the pipeline.
-
-## Recommendations
-
-### Principle: fixtures define the contract, the pipeline must produce it
-
-The fixtures in `__fixtures__/` define the correct target output. When the pipeline can't produce a fixture's format, **extend the pipeline** (new event types, new formatting functions) — do not bypass the pipeline with manual text construction.
-
-### Re-migrate reverted tools by extending the pipeline
-
-1. **`get_sim_app_path`, `get_device_app_path`, `get_mac_app_path`** — The fixture format (3-space indent header, timing display, success message wording) may require updates to `formatHeaderEvent` or a new formatting variant. Extend the formatting layer to produce the fixture output, then convert tools back to `toolResponse()` with events.
-
-2. **`list_devices` success path** — The fixture defines a grouped-by-platform layout with platform-specific emojis and `[✓]/[✗]` availability markers. This likely requires new event types or formatting capabilities (e.g., a grouped device list event, or enriching `SectionEvent` with platform/availability metadata). Extend the pipeline to support this, then re-migrate the tool.
-
-3. **`swift_package_run/test` error fallback** — Route the error fallback through pipeline events instead of raw `content`. The `errorFallbackPolicy` mechanism should remain, but the fallback itself should be event-shaped.
-
-4. **`session_show_defaults`** — Use `detailTree()` events instead of manual tree connectors. Remove emoji from section titles (renderer should own emoji).
-
-5. **`screenshot`** — Remove manual content branches. For mixed text + image responses, extend the pipeline if needed.
-
-### Fix presentation leakage in migrated tools
-
-6. **`list_sims`** — Remove inline emoji and `✓`/`✗` markers from section content lines. These should come from the formatting layer or event type metadata.
-
-### Documentation (done)
-
-7. **`STRUCTURED_XCODEBUILD_EVENTS_PLAN.md`** — Updated: replaced "one renderer, two sinks" with actual "shared formatters + runtime-specific renderers" architecture. Checked off completed items. Documented remaining work with correct framing.
-
-### Prevent future drift
-
-8. **Consider a lint/test guard** — Add a check that tool files under `src/mcp/tools/` don't directly construct `content: [{ type: 'text' }]` objects. This would catch future regressions where tools bypass the pipeline.
diff --git a/docs/dev/MANIFEST_FORMAT.md b/docs/dev/MANIFEST_FORMAT.md
deleted file mode 100644
index de8983cb2..000000000
--- a/docs/dev/MANIFEST_FORMAT.md
+++ /dev/null
@@ -1,552 +0,0 @@
-# Manifest Format Reference
-
-This document describes the YAML manifest format used to define tools and workflows in XcodeBuildMCP. Manifests are the single source of truth for tool/workflow metadata, visibility rules, and runtime behavior.
-
-## Overview
-
-Manifests are stored in the `manifests/` directory:
-
-```
-manifests/
-├── tools/ # Tool manifest files
-│ ├── build_sim.yaml
-│ ├── list_sims.yaml
-│ └── ...
-├── workflows/ # Workflow manifest files
-│ ├── simulator.yaml
-│ ├── device.yaml
-│ └── ...
-└── resources/ # Resource manifest files
- ├── simulators.yaml
- ├── devices.yaml
- └── ...
-```
-
-Each tool and workflow has its own YAML file. The manifest loader reads all files at startup and validates them against the schema.
-
-## Directory Structure
-
-Tool implementations live in `src/mcp/tools//`:
-
-```
-src/mcp/tools/
-├── simulator/
-│ ├── build_sim.ts # Tool implementation
-│ ├── build_run_sim.ts
-│ ├── list_sims.ts
-│ └── ...
-├── device/
-│ ├── build_device.ts
-│ └── ...
-└── ...
-```
-
-## Tool Manifest Format
-
-Tool manifests define individual tools and their metadata.
-
-### Schema
-
-```yaml
-# Required fields
-id: string # Unique tool identifier (must match filename without .yaml)
-module: string # Module path (see Module Path section)
-names:
- mcp: string # MCP tool name (globally unique, used in MCP protocol)
- cli: string # CLI command name (optional, derived from mcp if omitted)
-
-# Optional fields
-description: string # Tool description (shown in tool listings)
-availability: # Per-runtime availability flags
- mcp: boolean # Available via MCP server (default: true)
- cli: boolean # Available via CLI (default: true)
-predicates: string[] # Predicate names for visibility filtering (default: [])
-routing: # CLI daemon routing
- stateful: boolean # Tool maintains state (default: false)
-outputSchema: # MCP structured output schema advertised to clients
- schema: string # Schema name, e.g. xcodebuildmcp.output.build-result
- version: string # Schema version, e.g. "1"
-annotations: # MCP tool annotations (hints for clients)
- title: string # Human-readable title (optional)
- readOnlyHint: boolean # Tool only reads data (optional)
- destructiveHint: boolean # Tool may modify/delete data (optional)
- idempotentHint: boolean # Safe to retry (optional)
- openWorldHint: boolean # May access external resources (optional)
-```
-
-### Example: Basic Tool
-
-```yaml
-id: list_sims
-module: mcp/tools/simulator/list_sims
-names:
- mcp: list_sims
-description: "List available iOS simulators."
-outputSchema:
- schema: xcodebuildmcp.output.simulator-list
- version: "1"
-availability:
- mcp: true
- cli: true
-predicates: []
-annotations:
- title: "List Simulators"
- readOnlyHint: true
-```
-
-### Example: Tool with Predicates
-
-```yaml
-id: build_sim
-module: mcp/tools/simulator/build_sim
-names:
- mcp: build_sim
-description: "Build for iOS sim."
-availability:
- mcp: true
- cli: true
-predicates:
- - hideWhenXcodeAgentMode # Hidden when Xcode provides equivalent tool
-```
-
-### Example: MCP-Only Tool
-
-```yaml
-id: manage_workflows
-module: mcp/tools/workflow-discovery/manage_workflows
-names:
- mcp: manage-workflows # Note: MCP name uses hyphens
-description: "Manage enabled workflows at runtime."
-availability:
- mcp: true
- cli: false # Not available in CLI
-predicates:
- - experimentalWorkflowDiscoveryEnabled
-```
-
-## Workflow Manifest Format
-
-Workflow manifests define groups of related tools.
-
-### Schema
-
-```yaml
-# Required fields
-id: string # Unique workflow identifier (must match filename without .yaml)
-title: string # Display title
-description: string # Workflow description
-tools: string[] # Array of tool IDs belonging to this workflow
-
-# Optional fields
-availability: # Per-runtime availability flags
- mcp: boolean # Available via MCP server (default: true)
- cli: boolean # Available via CLI (default: true)
-selection: # MCP selection rules
- mcp:
- defaultEnabled: boolean # Enabled when config.enabledWorkflows is empty (default: false)
- autoInclude: boolean # Include when predicates pass, even if not requested (default: false)
-predicates: string[] # Predicate names for visibility filtering (default: [])
-```
-
-### Example: Default-Enabled Workflow
-
-```yaml
-id: simulator
-title: "iOS Simulator Development"
-description: "Complete iOS development workflow for simulators."
-availability:
- mcp: true
- cli: true
-selection:
- mcp:
- defaultEnabled: true # Enabled by default
- autoInclude: false
-predicates: []
-tools:
- - list_sims
- - boot_sim
- - build_sim
- - build_run_sim
- - test_sim
- # ... more tools
-```
-
-### Example: Auto-Include Workflow
-
-```yaml
-id: doctor
-title: "MCP Doctor"
-description: "Diagnostic tool for the MCP server environment."
-availability:
- mcp: true
- cli: true
-selection:
- mcp:
- defaultEnabled: false
- autoInclude: true # Auto-included when predicates pass
-predicates:
- - debugEnabled # Only shown in debug mode
-tools:
- - doctor
-```
-
-### Example: Conditional Workflow
-
-```yaml
-id: workflow-discovery
-title: "Workflow Discovery"
-description: "Manage enabled workflows at runtime."
-availability:
- mcp: true
- cli: false
-selection:
- mcp:
- defaultEnabled: false
- autoInclude: true
-predicates:
- - experimentalWorkflowDiscoveryEnabled # Feature flag
-tools:
- - manage_workflows
-```
-
-## Field Reference
-
-### Tool Fields
-
-| Field | Type | Required | Default | Description |
-|-------|------|----------|---------|-------------|
-| `id` | string | Yes | - | Unique identifier, must match filename |
-| `module` | string | Yes | - | Module path relative to `src/` (extensionless) |
-| `names.mcp` | string | Yes | - | MCP protocol tool name |
-| `names.cli` | string | No | Derived from MCP name | CLI command name |
-| `description` | string | No | - | Tool description |
-| `availability.mcp` | boolean | No | `true` | Available via MCP |
-| `availability.cli` | boolean | No | `true` | Available via CLI |
-| `predicates` | string[] | No | `[]` | Visibility predicates (all must pass) |
-| `routing.stateful` | boolean | No | `false` | Tool maintains state |
-| `outputSchema.schema` | string | No | - | Structured output schema advertised to MCP clients; must match `ctx.structuredOutput.schema` |
-| `outputSchema.version` | string | No | - | Structured output schema version advertised to MCP clients; must match `ctx.structuredOutput.schemaVersion` |
-| `annotations.title` | string | No | - | Human-readable title |
-| `annotations.readOnlyHint` | boolean | No | - | Tool only reads data |
-| `annotations.destructiveHint` | boolean | No | - | Tool may modify/delete data |
-| `annotations.idempotentHint` | boolean | No | - | Safe to retry |
-| `annotations.openWorldHint` | boolean | No | - | May access external resources |
-
-### Workflow Fields
-
-| Field | Type | Required | Default | Description |
-|-------|------|----------|---------|-------------|
-| `id` | string | Yes | - | Unique identifier, must match filename |
-| `title` | string | Yes | - | Display title |
-| `description` | string | Yes | - | Workflow description |
-| `tools` | string[] | Yes | - | Tool IDs in this workflow |
-| `availability.mcp` | boolean | No | `true` | Available via MCP |
-| `availability.cli` | boolean | No | `true` | Available via CLI |
-| `selection.mcp.defaultEnabled` | boolean | No | `false` | Enabled when no workflows configured |
-| `selection.mcp.autoInclude` | boolean | No | `false` | Auto-include when predicates pass |
-| `predicates` | string[] | No | `[]` | Visibility predicates (all must pass) |
-
-## Module Path
-
-The `module` field specifies where to find the tool implementation. It uses a package-relative path without file extension:
-
-```
-mcp/tools//
-```
-
-At runtime, this resolves to:
-```
-build/mcp/tools//.js
-```
-
-The module must export named exports: `{ schema, handler }`
-
-Note: `name`, `description`, and `annotations` are defined in the YAML manifest, not the module.
-
-Example module structure:
-```typescript
-// src/mcp/tools/simulator/build_sim.ts
-import { z } from 'zod';
-
-export const schema = z.object({
- projectPath: z.string().describe('Path to project'),
- // ...
-});
-
-export async function handler(params: z.infer) {
- // Implementation
-}
-```
-
-## Naming Conventions
-
-### Tool ID
-- Use `snake_case`: `build_sim`, `list_devices`
-- Must match the YAML filename (without `.yaml`)
-- Must be unique across all tools
-
-### MCP Name (`names.mcp`)
-- Use `snake_case` or `kebab-case` consistently
-- Must be globally unique across all tools
-- This is what LLMs see and call
-
-### CLI Name (`names.cli`)
-- Optional; if omitted, derived from MCP name
-- Derivation: `snake_case` → `kebab-case` (`build_sim` → `build-sim`)
-- Use `kebab-case` for explicit names
-
-### Workflow ID
-- Use `kebab-case`: `simulator`, `swift-package`, `ui-automation`
-- Must match the YAML filename (without `.yaml`)
-
-## Predicates
-
-Predicates control visibility based on runtime context. All predicates in the array must pass (AND logic) for the tool/workflow to be visible.
-
-### Available Predicates
-
-| Predicate | Description |
-|-----------|-------------|
-| `debugEnabled` | Show only when `config.debug` is `true` |
-| `experimentalWorkflowDiscoveryEnabled` | Show only when experimental workflow discovery is enabled |
-| `mcpRuntimeOnly` | Show only in MCP runtime (hide in CLI/daemon catalogs) |
-| `runningUnderXcodeAgent` | Show only when running under Xcode's coding agent |
-| `hideWhenXcodeAgentMode` | Hide when running inside Xcode's coding agent (tools conflict with Xcode's native equivalents) |
-| `xcodeAutoSyncDisabled` | Show only when running under Xcode and `config.disableXcodeAutoSync` is `true` |
-| `always` | Always visible (explicit documentation) |
-| `never` | Never visible (temporarily disable) |
-
-Notes:
-- Bridge availability/connection is handled at tool call time, not as a visibility predicate.
-- Prefer runtime/config predicates for deterministic tool exposure.
-
-### Predicate Context
-
-Predicates receive a context object:
-
-```typescript
-interface PredicateContext {
- runtime: 'cli' | 'mcp' | 'daemon';
- config: ResolvedRuntimeConfig;
- runningUnderXcode: boolean;
-}
-```
-
-### Adding New Predicates
-
-To add a new predicate, edit `src/visibility/predicate-registry.ts`:
-
-```typescript
-export const PREDICATES: Record = {
- // Existing predicates...
-
- myNewPredicate: (ctx: PredicateContext): boolean => {
- return ctx.config.someFlag === true;
- },
-};
-```
-
-## Workflow Selection Rules
-
-For MCP runtime, workflows are selected based on these rules (in order):
-
-1. **Auto-include workflows** (`autoInclude: true`) when their predicates pass
-2. **Explicitly requested workflows** from `config.enabledWorkflows`
-3. **Default workflows** (`defaultEnabled: true`) when `config.enabledWorkflows` is empty
-4. All selected workflows are filtered by availability + predicates
-
-### Selection Examples
-
-```yaml
-# Always included (autoInclude with no predicates = always passes)
-selection:
- mcp:
- autoInclude: true
-
-# Enabled by default when no workflows configured
-selection:
- mcp:
- defaultEnabled: true
-
-# MCP-only workflow/tool visibility
-predicates:
- - mcpRuntimeOnly
-
-# Auto-included only when predicates pass (e.g., debug mode)
-selection:
- mcp:
- autoInclude: true
-predicates:
- - debugEnabled
-
-# Show only when manual Xcode sync is needed
-predicates:
- - xcodeAutoSyncDisabled
-```
-
-## Tool Re-export
-
-A single tool can belong to multiple workflows. This is useful for shared utilities:
-
-```yaml
-# manifests/workflows/simulator.yaml
-tools:
- - clean # Shared tool
- - discover_projs # Shared tool
- - build_sim
-
-# manifests/workflows/device.yaml
-tools:
- - clean # Same tool, different workflow
- - discover_projs # Same tool, different workflow
- - build_device
-```
-
-The tool is defined once in `manifests/tools/clean.yaml` but referenced by both workflows.
-
-## Daemon Routing
-
-Daemon routing is intentionally simple:
-
-- **`routing.stateful: true`**: CLI routes this tool through the daemon.
-- **`routing` omitted or `stateful: false`**: CLI runs the tool directly.
-- **Special-case**: dynamic `xcode-ide` bridge tools use daemon-backed routing for bridge session persistence.
-
-## Validation
-
-Manifests are validated at load time against Zod schemas. Invalid manifests cause startup failures with descriptive error messages.
-
-The schema definitions are in `src/core/manifest/schema.ts`.
-
-## Runtime Tool Registration
-
-At startup, tools are registered dynamically from manifests:
-
-```
-1. loadManifest()
- └── Reads all YAML files from manifests/tools/ and manifests/workflows/
- └── Validates against Zod schemas
- └── Returns { tools: Map, workflows: Map }
-
-2. selectWorkflowsForMcp(workflows, requestedWorkflows, ctx)
- └── Filters workflows by availability (mcp: true)
- └── Applies selection rules (defaultEnabled, autoInclude)
- └── Evaluates predicates against context
-
-3. For each selected workflow:
- └── For each tool ID in workflow.tools:
- └── Look up tool manifest by ID
- └── Check tool availability and predicates
- └── importToolModule(module) → { schema, handler, annotations }
- └── Load outputSchema from schemas/structured-output//.schema.json
- └── server.registerTool(mcpName, { inputSchema, outputSchema, annotations }, handler)
-```
-
-Key files:
-- `src/core/manifest/load-manifest.ts` - Manifest loading and caching
-- `src/core/manifest/import-tool-module.ts` - Dynamic tool module imports
-- `src/core/manifest/import-resource-module.ts` - Dynamic resource module imports
-- `src/utils/tool-registry.ts` - MCP server tool registration
-- `src/core/resources.ts` - MCP server resource registration
-- `src/runtime/tool-catalog.ts` - CLI/daemon tool catalog building
-- `src/visibility/exposure.ts` - Workflow/tool/resource visibility filtering
-
-## Resource Manifest Format
-
-Resource manifests define MCP resources exposed by the server.
-
-### Schema
-
-```yaml
-# Required fields
-id: string # Unique resource identifier (must match filename without .yaml)
-module: string # Module path (see Module Path section)
-name: string # MCP resource name
-uri: string # Resource URI (e.g., xcodebuildmcp://simulators)
-description: string # Resource description
-mimeType: string # MIME type for the resource content
-
-# Optional fields
-availability: # Per-runtime availability flags
- mcp: boolean # Available via MCP server (default: true)
-predicates: string[] # Predicate names for visibility filtering (default: [])
-```
-
-### Example: Basic Resource
-
-```yaml
-id: simulators
-module: mcp/resources/simulators
-name: simulators
-uri: xcodebuildmcp://simulators
-description: Available iOS simulators with their UUIDs and states
-mimeType: text/plain
-```
-
-### Example: Predicate-Gated Resource
-
-```yaml
-id: xcode-ide-state
-module: mcp/resources/xcode-ide-state
-name: xcode-ide-state
-uri: xcodebuildmcp://xcode-ide-state
-description: "Current Xcode IDE selection (scheme and simulator) from Xcode's UI state"
-mimeType: application/json
-predicates:
- - runningUnderXcodeAgent # Only exposed when running under Xcode
-```
-
-### Resource Fields
-
-| Field | Type | Required | Default | Description |
-|-------|------|----------|---------|-------------|
-| `id` | string | Yes | - | Unique identifier, must match filename |
-| `module` | string | Yes | - | Module path relative to `src/` (extensionless) |
-| `name` | string | Yes | - | MCP resource name |
-| `uri` | string | Yes | - | Resource URI |
-| `description` | string | Yes | - | Resource description |
-| `mimeType` | string | Yes | - | Content MIME type |
-| `availability.mcp` | boolean | No | `true` | Available via MCP |
-| `predicates` | string[] | No | `[]` | Visibility predicates (all must pass) |
-
-### Resource Module Contract
-
-Resource modules must export a named `handler` function:
-
-```typescript
-// src/mcp/resources/simulators.ts
-export async function handler(uri: URL): Promise<{ contents: Array<{ text: string }> }> {
- // Implementation
-}
-```
-
-Metadata (name, description, URI, mimeType) is defined in the YAML manifest, not the module.
-
-## Creating a New Tool
-
-1. **Create the tool module** in `src/mcp/tools//.ts`
-2. **Create the manifest** in `manifests/tools/.yaml`
-3. **Add to workflow(s)** in `manifests/workflows/.yaml`
-4. **Run tests** to validate
-
-Example checklist:
-- [ ] Tool ID matches filename
-- [ ] Module path is correct
-- [ ] MCP name is unique
-- [ ] Tool is added to at least one workflow
-- [ ] `outputSchema` references the schema produced by `ctx.structuredOutput`, if the tool returns structured content
-- [ ] Predicates reference valid predicate names
-- [ ] Availability flags match intended runtimes
-
-## Creating a New Workflow
-
-1. **Create the manifest** in `manifests/workflows/.yaml`
-2. **Add tool references** (tools must already exist)
-3. **Configure selection rules** for MCP behavior
-4. **Run tests** to validate
-
-Example checklist:
-- [ ] Workflow ID matches filename
-- [ ] All referenced tool IDs exist
-- [ ] Selection rules are appropriate
-- [ ] Predicates reference valid predicate names
diff --git a/docs/dev/MANUAL_TESTING.md b/docs/dev/MANUAL_TESTING.md
deleted file mode 100644
index b4049e560..000000000
--- a/docs/dev/MANUAL_TESTING.md
+++ /dev/null
@@ -1,750 +0,0 @@
-# XcodeBuildMCP Manual Testing Guidelines
-
-This document provides comprehensive guidelines for manual black-box testing of XcodeBuildMCP using Reloaderoo inspect commands. This is the authoritative guide for validating all tools through the Model Context Protocol interface.
-
-## Table of Contents
-
-1. [Testing Philosophy](#testing-philosophy)
-2. [Black Box Testing via Reloaderoo](#black-box-testing-via-reloaderoo)
-3. [Testing Psychology & Bias Prevention](#testing-psychology--bias-prevention)
-4. [Tool Dependency Graph Testing Strategy](#tool-dependency-graph-testing-strategy)
-5. [Prerequisites](#prerequisites)
-6. [Step-by-Step Testing Process](#step-by-step-testing-process)
-7. [Error Testing](#error-testing)
-8. [Testing Report Generation](#testing-report-generation)
-9. [Troubleshooting](#troubleshooting)
-
-## Testing Philosophy
-
-### 🚨 CRITICAL: THOROUGHNESS OVER EFFICIENCY - NO SHORTCUTS ALLOWED
-
-**ABSOLUTE PRINCIPLE: EVERY TOOL MUST BE TESTED INDIVIDUALLY**
-
-**🚨 MANDATORY TESTING SCOPE - NO EXCEPTIONS:**
-- **EVERY SINGLE TOOL** - All tools must be tested individually, one by one
-- **NO REPRESENTATIVE SAMPLING** - Testing similar tools does NOT validate other tools
-- **NO PATTERN RECOGNITION SHORTCUTS** - Similar-looking tools may have different behaviors
-- **NO EFFICIENCY OPTIMIZATIONS** - Thoroughness is more important than speed
-- **NO TIME CONSTRAINTS** - This is a long-running task with no deadline pressure
-
-**❌ FORBIDDEN EFFICIENCY SHORTCUTS:**
-- **NEVER** assume testing `build_sim_id_proj` validates `build_sim_name_proj`
-- **NEVER** skip tools because they "look similar" to tested ones
-- **NEVER** use representative sampling instead of complete coverage
-- **NEVER** stop testing due to time concerns or perceived redundancy
-- **NEVER** group tools together for batch testing
-- **NEVER** make assumptions about untested tools based on tested patterns
-
-**✅ REQUIRED COMPREHENSIVE APPROACH:**
-1. **Individual Tool Testing**: Each tool gets its own dedicated test execution
-2. **Complete Documentation**: Every tool result must be recorded, regardless of outcome
-3. **Systematic Progress**: Use TodoWrite to track every single tool as tested/untested
-4. **Failure Documentation**: Test tools that cannot work and mark them as failed/blocked
-5. **No Assumptions**: Treat each tool as potentially unique requiring individual validation
-
-**TESTING COMPLETENESS VALIDATION:**
-- **Start Count**: Record exact number of tools discovered using `npm run tools`
-- **End Count**: Verify same number of tools have been individually tested
-- **Missing Tools = Testing Failure**: If any tools remain untested, the testing is incomplete
-- **TodoWrite Tracking**: Every tool must appear in todo list and be marked completed
-
-## Black Box Testing via Reloaderoo
-
-### 🚨 CRITICAL: Black Box Testing via Reloaderoo Inspect
-
-**DEFINITION: Black Box Testing**
-Black Box Testing means testing ONLY through external interfaces without any knowledge of internal implementation. For XcodeBuildMCP, this means testing exclusively through the Model Context Protocol (MCP) interface using Reloaderoo as the MCP client.
-
-**🚨 MANDATORY: RELOADEROO INSPECT IS THE ONLY ALLOWED TESTING METHOD**
-
-**ABSOLUTE TESTING RULES - NO EXCEPTIONS:**
-
-1. **✅ ONLY ALLOWED: Reloaderoo Inspect Commands**
- - `npx reloaderoo@latest inspect call-tool "TOOL_NAME" --params 'JSON' -- node build/cli.js mcp`
- - `npx reloaderoo@latest inspect list-tools -- node build/cli.js mcp`
- - `npx reloaderoo@latest inspect read-resource "URI" -- node build/cli.js mcp`
- - `npx reloaderoo@latest inspect server-info -- node build/cli.js mcp`
- - `npx reloaderoo@latest inspect ping -- node build/cli.js mcp`
-
-2. **❌ COMPLETELY FORBIDDEN ACTIONS:**
- - **NEVER** call `mcp__XcodeBuildMCP__tool_name()` functions directly
- - **NEVER** use MCP server tools as if they were native functions
- - **NEVER** access internal server functionality
- - **NEVER** read source code to understand how tools work
- - **NEVER** examine implementation files during testing
- - **NEVER** diagnose internal server issues or registration problems
- - **NEVER** suggest code fixes or implementation changes
-
-3. **🚨 CRITICAL VIOLATION EXAMPLES:**
- ```typescript
- // ❌ FORBIDDEN - Direct MCP tool calls
- await mcp__XcodeBuildMCP__list_devices();
- await mcp__XcodeBuildMCP__build_sim_id_proj({ ... });
-
- // ❌ FORBIDDEN - Using tools as native functions
- const devices = await list_devices();
- const result = await doctor();
-
- // ✅ CORRECT - Only through Reloaderoo inspect
- npx reloaderoo@latest inspect call-tool "list_devices" --params '{}' -- node build/cli.js mcp
- npx reloaderoo@latest inspect call-tool "doctor" --params '{}' -- node build/cli.js mcp
- ```
-
-**WHY RELOADEROO INSPECT IS MANDATORY:**
-- **Higher Fidelity**: Provides clear input/output visibility for each tool call
-- **Real-world Simulation**: Tests exactly how MCP clients interact with the server
-- **Interface Validation**: Ensures MCP protocol compliance and proper JSON formatting
-- **Black Box Enforcement**: Prevents accidental access to internal implementation details
-- **Clean State**: Each tool call runs with a fresh MCP server instance, preventing cross-contamination
-
-**IMPORTANT: STATEFUL TOOL LIMITATIONS**
-
-**Reloaderoo Inspect Behavior:**
-Reloaderoo starts a fresh MCP server instance for each individual tool call and terminates it immediately after the response. This ensures:
-- ✅ **Clean Testing Environment**: No state contamination between tool calls
-- ✅ **Isolated Testing**: Each tool test is independent and repeatable
-- ✅ **Real-world Accuracy**: Simulates how most MCP clients interact with servers
-
-**Expected False Negatives:**
-Some tools rely on in-memory state within the MCP server and will fail when tested via Reloaderoo inspect. These failures are **expected and acceptable** as false negatives:
-
-- **`swift_package_stop`** - Requires in-memory process tracking from `swift_package_run`
-- **`stop_app_device`** - Requires in-memory process tracking from `launch_app_device`
-- **`stop_app_sim`** - Requires in-memory process tracking from `launch_app_sim`
-- **`stop_device_log_cap`** - Requires in-memory session tracking from `start_device_log_cap`
-- **`stop_sim_log_cap`** - Requires in-memory session tracking from `start_sim_log_cap`
-- **`stop_mac_app`** - Requires in-memory process tracking from `launch_mac_app`
-
-**Testing Protocol for Stateful Tools:**
-1. **Test the tool anyway** - Execute the Reloaderoo inspect command
-2. **Expect failure** - Tool will likely fail due to missing state
-3. **Mark as false negative** - Document the failure as expected due to stateful limitations
-4. **Continue testing** - Do not attempt to fix or investigate the failure
-5. **Report as finding** - Note in testing report that stateful tools failed as expected
-
-**COMPLETE COVERAGE REQUIREMENTS:**
-- ✅ **Test ALL tools individually** - No exceptions, every tool gets manual verification
-- ✅ **Follow dependency graphs** - Test tools in correct order based on data dependencies
-- ✅ **Capture key outputs** - Record UUIDs, paths, schemes needed by dependent tools
-- ✅ **Test real workflows** - Complete end-to-end workflows from discovery to execution
-- ✅ **Use tool-summary.js script** - Accurate tool/resource counting and discovery
-- ✅ **Document all observations** - Record exactly what you see via testing
-- ✅ **Report discrepancies as findings** - Note unexpected results without investigation
-
-**MANDATORY INDIVIDUAL TOOL TESTING PROTOCOL:**
-
-**Step 1: Create Complete Tool Inventory**
-```bash
-# Use the official tool summary script to get accurate tool count and list
-npm run tools > /tmp/summary_output.txt
-TOTAL_TOOLS=$(grep "Tools:" /tmp/summary_output.txt | awk '{print $2}')
-echo "TOTAL TOOLS TO TEST: $TOTAL_TOOLS"
-
-# Generate detailed tool list and extract tool names
-npm run tools:list > /tmp/tools_detailed.txt
-grep "^ • " /tmp/tools_detailed.txt | sed 's/^ • //' > /tmp/tool_names.txt
-```
-
-**Step 2: Create TodoWrite Task List for Every Tool**
-```bash
-# Create individual todo items for each tool discovered
-# Use the actual tool count from step 1
-# Example for first few tools:
-# 1. [ ] Test tool: doctor
-# 2. [ ] Test tool: list_devices
-# 3. [ ] Test tool: list_sims
-# ... (continue for ALL $TOTAL_TOOLS tools)
-```
-
-**Step 3: Test Each Tool Individually**
-For EVERY tool in the list:
-```bash
-# Test each tool individually - NO BATCHING
-npx reloaderoo@latest inspect call-tool "TOOL_NAME" --params 'APPROPRIATE_PARAMS' -- node build/cli.js mcp
-
-# Mark tool as completed in TodoWrite IMMEDIATELY after testing
-# Record result (success/failure/blocked) for each tool
-```
-
-**Step 4: Validate Complete Coverage**
-```bash
-# Verify all tools tested
-COMPLETED_TOOLS=$(count completed todo items)
-if [ $COMPLETED_TOOLS -ne $TOTAL_TOOLS ]; then
- echo "ERROR: Testing incomplete. $COMPLETED_TOOLS/$TOTAL_TOOLS tested"
- exit 1
-fi
-```
-
-**CRITICAL: NO TOOL LEFT UNTESTED**
-- **Every tool name from the JSON list must be individually tested**
-- **Every tool must have a TodoWrite entry that gets marked completed**
-- **Tools that fail due to missing parameters should be tested anyway and marked as blocked**
-- **Tools that require setup (like running processes) should be tested and documented as requiring dependencies**
-- **NO ASSUMPTIONS**: Test tools even if they seem redundant or similar to others
-
-**BLACK BOX TESTING ENFORCEMENT:**
-- ✅ **Test only through Reloaderoo MCP interface** - Simulates real-world MCP client usage
-- ✅ **Use task lists** - Track progress with TodoWrite tool for every single tool
-- ✅ **Tick off each tool** - Mark completed in task list after manual verification
-- ✅ **Manual oversight** - Human verification of each tool's input and output
-- ❌ **Never examine source code** - No reading implementation files during testing
-- ❌ **Never diagnose internal issues** - No investigation of build processes or tool registration
-- ❌ **Never suggest implementation fixes** - Report issues as findings, don't solve them
-- ❌ **Never use scripts for tool testing** - Each tool must be manually executed and verified
-
-## Testing Psychology & Bias Prevention
-
-**COMMON ANTI-PATTERNS TO AVOID:**
-
-**1. Efficiency Bias (FORBIDDEN)**
-- **Symptom**: "These tools look similar, I'll test one to validate the others"
-- **Correction**: Every tool is unique and must be tested individually
-- **Enforcement**: Count tools at start, verify same count tested at end
-
-**2. Pattern Recognition Override (FORBIDDEN)**
-- **Symptom**: "I see the pattern, the rest will work the same way"
-- **Correction**: Patterns may hide edge cases, bugs, or different implementations
-- **Enforcement**: No assumptions allowed, test every tool regardless of apparent similarity
-
-**3. Time Pressure Shortcuts (FORBIDDEN)**
-- **Symptom**: "This is taking too long, let me speed up by sampling"
-- **Correction**: This is explicitly a long-running task with no time constraints
-- **Enforcement**: Thoroughness is the ONLY priority, efficiency is irrelevant
-
-**4. False Confidence (FORBIDDEN)**
-- **Symptom**: "The architecture is solid, so all tools must work"
-- **Correction**: Architecture validation does not guarantee individual tool functionality
-- **Enforcement**: Test tools to discover actual issues, not to confirm assumptions
-
-**MANDATORY MINDSET:**
-- **Every tool is potentially broken** until individually tested
-- **Every tool may have unique edge cases** not covered by similar tools
-- **Every tool deserves individual attention** regardless of apparent redundancy
-- **Testing completion means EVERY tool tested**, not "enough tools to validate patterns"
-- **The goal is discovering problems**, not confirming everything works
-
-**TESTING COMPLETENESS CHECKLIST:**
-- [ ] Generated complete tool list using `npm run tools:list`
-- [ ] Created TodoWrite entry for every single tool
-- [ ] Tested every tool individually via Reloaderoo inspect
-- [ ] Marked every tool as completed in TodoWrite
-- [ ] Verified tool count: tested_count == total_count
-- [ ] Documented all results, including failures and blocked tools
-- [ ] Created final report covering ALL tools, not just successful ones
-
-## Tool Dependency Graph Testing Strategy
-
-**CRITICAL: Tools must be tested in dependency order:**
-
-1. **Foundation Tools** (provide data for other tools):
- - `doctor` - System info
- - `list_devices` - Device UUIDs
- - `list_sims` - Simulator UUIDs
- - `discover_projs` - Project/workspace paths
-
-2. **Discovery Tools** (provide metadata for build tools):
- - `list_schemes` - Scheme names
- - `show_build_settings` - Build settings
-
-3. **Build Tools** (create artifacts for install tools):
- - `build_*` tools - Create app bundles
- - `get_*_app_path_*` tools - Locate built app bundles
- - `get_*_bundle_id` tools - Extract bundle IDs
-
-4. **Installation Tools** (depend on built artifacts):
- - `install_app_*` tools - Install built apps
- - `launch_app_*` tools - Launch installed apps
-
-5. **Testing Tools** (depend on projects/schemes):
- - `test_*` tools - Run test suites
-
-6. **UI Automation Tools** (depend on running apps):
- - `snapshot_ui`, `screenshot`, `tap`, etc.
-
-**MANDATORY: Record Key Outputs**
-
-Must capture and document these values for dependent tools:
-- **Device UUIDs** from `list_devices`
-- **Simulator UUIDs** from `list_sims`
-- **Project/workspace paths** from `discover_projs`
-- **Scheme names** from `list_schems_*`
-- **App bundle paths** from `get_*_app_path_*`
-- **Bundle IDs** from `get_*_bundle_id`
-
-## Prerequisites
-
-1. **Build the server**: `npm run build`
-2. **Install jq**: `brew install jq` (required for JSON parsing)
-3. **System Requirements**: macOS with Xcode installed, connected devices/simulators optional
-4. **AXe video capture (optional)**: run `npm run bundle:axe` before using `record_sim_video` in local tests (not required for unit tests)
-
-## Step-by-Step Testing Process
-
-**Note**: All tool and resource discovery now uses the official `tool-summary.js` script (available as `npm run tools`, `npm run tools:list`, and `npm run tools:all`) instead of direct reloaderoo calls. This ensures accurate counts and lists without hardcoded values.
-
-### Step 1: Programmatic Discovery and Official Testing Lists
-
-#### Generate Official Tool and Resource Lists using tool-summary.js
-
-```bash
-# Use the official tool summary script to get accurate counts and lists
-npm run tools > /tmp/summary_output.txt
-
-# Extract tool and resource counts from summary
-TOOL_COUNT=$(grep "Tools:" /tmp/summary_output.txt | awk '{print $2}')
-RESOURCE_COUNT=$(grep "Resources:" /tmp/summary_output.txt | awk '{print $2}')
-echo "Official tool count: $TOOL_COUNT"
-echo "Official resource count: $RESOURCE_COUNT"
-
-# Generate detailed tool list for testing checklist
-npm run tools:list > /tmp/tools_detailed.txt
-
-# Extract tool names from the detailed output
-grep "^ • " /tmp/tools_detailed.txt | sed 's/^ • //' > /tmp/tool_names.txt
-echo "Tool names saved to /tmp/tool_names.txt"
-
-# Generate detailed resource list for testing checklist
-npm run tools:all > /tmp/tools_and_resources.txt
-
-# Extract resource URIs from the detailed output
-sed -n '/📚 Available Resources:/,/✅ Tool summary complete!/p' /tmp/tools_and_resources.txt | grep "^ • " | sed 's/^ • //' | cut -d' ' -f1 > /tmp/resource_uris.txt
-echo "Resource URIs saved to /tmp/resource_uris.txt"
-```
-
-#### Create Tool Testing Checklist
-
-```bash
-# Generate markdown checklist from actual tool list
-echo "# Official Tool Testing Checklist" > /tmp/tool_testing_checklist.md
-echo "" >> /tmp/tool_testing_checklist.md
-echo "Total Tools: $TOOL_COUNT" >> /tmp/tool_testing_checklist.md
-echo "" >> /tmp/tool_testing_checklist.md
-
-# Add each tool as unchecked item
-while IFS= read -r tool_name; do
- echo "- [ ] $tool_name" >> /tmp/tool_testing_checklist.md
-done < /tmp/tool_names.txt
-
-echo "Tool testing checklist created at /tmp/tool_testing_checklist.md"
-```
-
-#### Create Resource Testing Checklist
-
-```bash
-# Generate markdown checklist from actual resource list
-echo "# Official Resource Testing Checklist" > /tmp/resource_testing_checklist.md
-echo "" >> /tmp/resource_testing_checklist.md
-echo "Total Resources: $RESOURCE_COUNT" >> /tmp/resource_testing_checklist.md
-echo "" >> /tmp/resource_testing_checklist.md
-
-# Add each resource as unchecked item
-while IFS= read -r resource_uri; do
- echo "- [ ] $resource_uri" >> /tmp/resource_testing_checklist.md
-done < /tmp/resource_uris.txt
-
-echo "Resource testing checklist created at /tmp/resource_testing_checklist.md"
-```
-
-### Step 2: Tool Schema Discovery for Parameter Testing
-
-#### Extract Tool Schema Information
-
-```bash
-# Get schema for specific tool to understand required parameters
-TOOL_NAME="list_devices"
-jq --arg tool "$TOOL_NAME" '.tools[] | select(.name == $tool) | .inputSchema' /tmp/tools.json
-
-# Get tool description for usage guidance
-jq --arg tool "$TOOL_NAME" '.tools[] | select(.name == $tool) | .description' /tmp/tools.json
-
-# Generate parameter template for tool testing
-jq --arg tool "$TOOL_NAME" '.tools[] | select(.name == $tool) | .inputSchema.properties // {}' /tmp/tools.json
-```
-
-#### Batch Schema Extraction
-
-```bash
-# Create schema reference file for all tools
-echo "# Tool Schema Reference" > /tmp/tool_schemas.md
-echo "" >> /tmp/tool_schemas.md
-
-while IFS= read -r tool_name; do
- echo "## $tool_name" >> /tmp/tool_schemas.md
- echo "" >> /tmp/tool_schemas.md
-
- # Get description
- description=$(jq -r --arg tool "$tool_name" '.tools[] | select(.name == $tool) | .description' /tmp/tools.json)
- echo "**Description:** $description" >> /tmp/tool_schemas.md
- echo "" >> /tmp/tool_schemas.md
-
- # Get required parameters
- required=$(jq -r --arg tool "$tool_name" '.tools[] | select(.name == $tool) | .inputSchema.required // [] | join(", ")' /tmp/tools.json)
- if [ "$required" != "" ]; then
- echo "**Required Parameters:** $required" >> /tmp/tool_schemas.md
- else
- echo "**Required Parameters:** None" >> /tmp/tool_schemas.md
- fi
- echo "" >> /tmp/tool_schemas.md
-
- # Get all parameters
- echo "**All Parameters:**" >> /tmp/tool_schemas.md
- jq --arg tool "$tool_name" '.tools[] | select(.name == $tool) | .inputSchema.properties // {} | keys[]' /tmp/tools.json | while read param; do
- echo "- $param" >> /tmp/tool_schemas.md
- done
- echo "" >> /tmp/tool_schemas.md
-
-done < /tmp/tool_names.txt
-
-echo "Tool schema reference created at /tmp/tool_schemas.md"
-```
-
-### Step 3: Manual Tool-by-Tool Testing
-
-#### 🚨 CRITICAL: STEP-BY-STEP BLACK BOX TESTING PROCESS
-
-**ABSOLUTE RULE: ALL TESTING MUST BE DONE MANUALLY, ONE TOOL AT A TIME USING RELOADEROO INSPECT**
-
-**SYSTEMATIC TESTING PROCESS:**
-
-1. **Create TodoWrite Task List**
- - Add all tools (from `npm run tools` count) to task list before starting
- - Mark each tool as "pending" initially
- - Update status to "in_progress" when testing begins
- - Mark "completed" only after manual verification
-
-2. **Test Each Tool Individually**
- - Execute ONLY via `npx reloaderoo@latest inspect call-tool "TOOL_NAME" --params 'JSON' -- node build/cli.js mcp`
- - Wait for complete response before proceeding to next tool
- - Read and verify each tool's output manually
- - Record key outputs (UUIDs, paths, schemes) for dependent tools
-
-3. **Manual Verification Requirements**
- - ✅ **Read each response** - Manually verify tool output makes sense
- - ✅ **Check for errors** - Identify any tool failures or unexpected responses
- - ✅ **Record UUIDs/paths** - Save outputs needed for dependent tools
- - ✅ **Update task list** - Mark each tool complete after verification
- - ✅ **Document issues** - Record any problems found during testing
-
-4. **FORBIDDEN SHORTCUTS:**
- - ❌ **NO SCRIPTS** - Scripts hide what's happening and prevent proper verification
- - ❌ **NO AUTOMATION** - Every tool call must be manually executed and verified
- - ❌ **NO BATCHING** - Cannot test multiple tools simultaneously
- - ❌ **NO MCP DIRECT CALLS** - Only Reloaderoo inspect commands allowed
-
-#### Phase 1: Infrastructure Validation
-
-**Manual Commands (execute individually):**
-
-```bash
-# Test server connectivity
-npx reloaderoo@latest inspect ping -- node build/cli.js mcp
-
-# Get server information
-npx reloaderoo@latest inspect server-info -- node build/cli.js mcp
-
-# Verify tool count manually
-npx reloaderoo@latest inspect list-tools -- node build/cli.js mcp 2>/dev/null | jq '.tools | length'
-
-# Verify resource count manually
-npx reloaderoo@latest inspect list-resources -- node build/cli.js mcp 2>/dev/null | jq '.resources | length'
-```
-
-#### Phase 2: Resource Testing
-
-```bash
-# Test each resource systematically
-while IFS= read -r resource_uri; do
- echo "Testing resource: $resource_uri"
- npx reloaderoo@latest inspect read-resource "$resource_uri" -- node build/cli.js mcp 2>/dev/null
- echo "---"
-done < /tmp/resource_uris.txt
-```
-
-#### Phase 3: Foundation Tools (Data Collection)
-
-**CRITICAL: Capture ALL key outputs for dependent tools**
-
-```bash
-echo "=== FOUNDATION TOOL TESTING & DATA COLLECTION ==="
-
-# 1. Test doctor (no dependencies)
-echo "Testing doctor..."
-npx reloaderoo@latest inspect call-tool "doctor" --params '{}' -- node build/cli.js mcp 2>/dev/null
-
-# 2. Collect device data
-echo "Collecting device UUIDs..."
-npx reloaderoo@latest inspect call-tool "list_devices" --params '{}' -- node build/cli.js mcp 2>/dev/null > /tmp/devices_output.json
-DEVICE_UUIDS=$(jq -r '.content[0].text' /tmp/devices_output.json | grep -E "UDID: [A-F0-9-]+" | sed 's/.*UDID: //' | head -2)
-echo "Device UUIDs captured: $DEVICE_UUIDS"
-
-# 3. Collect simulator data
-echo "Collecting simulator UUIDs..."
-npx reloaderoo@latest inspect call-tool "list_sims" --params '{}' -- node build/cli.js mcp 2>/dev/null > /tmp/sims_output.json
-SIMULATOR_UUIDS=$(jq -r '.content[0].text' /tmp/sims_output.json | grep -E "\([A-F0-9-]+\)" | sed 's/.*(\([A-F0-9-]*\)).*/\1/' | head -3)
-echo "Simulator UUIDs captured: $SIMULATOR_UUIDS"
-
-# 4. Collect project data
-echo "Collecting project paths..."
-npx reloaderoo@latest inspect call-tool "discover_projs" --params '{"workspaceRoot": "/Volumes/Developer/XcodeBuildMCP"}' -- node build/cli.js mcp 2>/dev/null > /tmp/projects_output.json
-PROJECT_PATHS=$(jq -r '.content[1].text' /tmp/projects_output.json | grep -E "\.xcodeproj$" | sed 's/.*- //' | head -3)
-WORKSPACE_PATHS=$(jq -r '.content[2].text' /tmp/projects_output.json | grep -E "\.xcworkspace$" | sed 's/.*- //' | head -2)
-echo "Project paths captured: $PROJECT_PATHS"
-echo "Workspace paths captured: $WORKSPACE_PATHS"
-
-# Save key data for dependent tools
-echo "$DEVICE_UUIDS" > /tmp/device_uuids.txt
-echo "$SIMULATOR_UUIDS" > /tmp/simulator_uuids.txt
-echo "$PROJECT_PATHS" > /tmp/project_paths.txt
-echo "$WORKSPACE_PATHS" > /tmp/workspace_paths.txt
-```
-
-#### Phase 4: Discovery Tools (Metadata Collection)
-
-```bash
-echo "=== DISCOVERY TOOL TESTING & METADATA COLLECTION ==="
-
-# Collect schemes for each project
-while IFS= read -r project_path; do
- if [ -n "$project_path" ]; then
- echo "Getting schemes for: $project_path"
- npx reloaderoo@latest inspect call-tool "list_schems_proj" --params "{\"projectPath\": \"$project_path\"}" -- node build/cli.js mcp 2>/dev/null > /tmp/schemes_$$.json
- SCHEMES=$(jq -r '.content[1].text' /tmp/schemes_$$.json 2>/dev/null || echo "NoScheme")
- echo "$project_path|$SCHEMES" >> /tmp/project_schemes.txt
- echo "Schemes captured for $project_path: $SCHEMES"
- fi
-done < /tmp/project_paths.txt
-
-# Collect schemes for each workspace
-while IFS= read -r workspace_path; do
- if [ -n "$workspace_path" ]; then
- echo "Getting schemes for: $workspace_path"
- npx reloaderoo@latest inspect call-tool "list_schemes" --params "{\"workspacePath\": \"$workspace_path\"}" -- node build/cli.js mcp 2>/dev/null > /tmp/ws_schemes_$$.json
- SCHEMES=$(jq -r '.content[1].text' /tmp/ws_schemes_$$.json 2>/dev/null || echo "NoScheme")
- echo "$workspace_path|$SCHEMES" >> /tmp/workspace_schemes.txt
- echo "Schemes captured for $workspace_path: $SCHEMES"
- fi
-done < /tmp/workspace_paths.txt
-```
-
-#### Phase 5: Manual Individual Tool Testing (All Tools)
-
-**CRITICAL: Test every single tool manually, one at a time**
-
-**Manual Testing Process:**
-
-1. **Create task list** with TodoWrite tool for all tools (using count from `npm run tools`)
-2. **Test each tool individually** with proper parameters
-3. **Mark each tool complete** in task list after manual verification
-4. **Record results** and observations for each tool
-5. **NO SCRIPTS** - Each command executed manually
-
-**STEP-BY-STEP MANUAL TESTING COMMANDS:**
-
-```bash
-# STEP 1: Test foundation tools (no parameters required)
-# Execute each command individually, wait for response, verify manually
-npx reloaderoo@latest inspect call-tool "doctor" --params '{}' -- node build/cli.js mcp
-# [Wait for response, read output, mark tool complete in task list]
-
-npx reloaderoo@latest inspect call-tool "list_devices" --params '{}' -- node build/cli.js mcp
-# [Record device UUIDs from response for dependent tools]
-
-npx reloaderoo@latest inspect call-tool "list_sims" --params '{}' -- node build/cli.js mcp
-# [Record simulator UUIDs from response for dependent tools]
-
-# STEP 2: Test project discovery (use discovered project paths)
-npx reloaderoo@latest inspect call-tool "list_schems_proj" --params '{"projectPath": "/actual/path/from/discover_projs.xcodeproj"}' -- node build/cli.js mcp
-# [Record scheme names from response for build tools]
-
-# STEP 3: Test workspace tools (use discovered workspace paths)
-npx reloaderoo@latest inspect call-tool "list_schemes" --params '{"workspacePath": "/actual/path/from/discover_projs.xcworkspace"}' -- node build/cli.js mcp
-# [Record scheme names from response for build tools]
-
-# STEP 4: Test simulator tools (use captured simulator UUIDs from step 1)
-npx reloaderoo@latest inspect call-tool "boot_sim" --params '{"simulatorUuid": "ACTUAL_UUID_FROM_LIST_SIMS"}' -- node build/cli.js mcp
-# [Verify simulator boots successfully]
-
-# STEP 5: Test build tools (requires project + scheme + simulator from previous steps)
-npx reloaderoo@latest inspect call-tool "build_sim_id_proj" --params '{"projectPath": "/actual/project.xcodeproj", "scheme": "ActualSchemeName", "simulatorId": "ACTUAL_SIMULATOR_UUID"}' -- node build/cli.js mcp
-# [Verify build succeeds and record app bundle path]
-```
-
-**CRITICAL: EACH COMMAND MUST BE:**
-1. **Executed individually** - One command at a time, manually typed or pasted
-2. **Verified manually** - Read the complete response before continuing
-3. **Tracked in task list** - Mark tool complete only after verification
-4. **Use real data** - Replace placeholder values with actual captured data
-5. **Wait for completion** - Allow each command to finish before proceeding
-
-### TESTING VIOLATIONS AND ENFORCEMENT
-
-**🚨 CRITICAL VIOLATIONS THAT WILL TERMINATE TESTING:**
-
-1. **Direct MCP Tool Usage Violation:**
- ```typescript
- // ❌ IMMEDIATE TERMINATION - Using MCP tools directly
- await mcp__XcodeBuildMCP__list_devices();
- const result = await list_sims();
- ```
-
-2. **Script-Based Testing Violation:**
- ```bash
- # ❌ IMMEDIATE TERMINATION - Using scripts to test tools
- for tool in $(cat tool_list.txt); do
- npx reloaderoo inspect call-tool "$tool" --params '{}' -- node build/cli.js mcp
- done
- ```
-
-3. **Batching/Automation Violation:**
- ```bash
- # ❌ IMMEDIATE TERMINATION - Testing multiple tools simultaneously
- npx reloaderoo inspect call-tool "list_devices" & npx reloaderoo inspect call-tool "list_sims" &
- ```
-
-4. **Source Code Examination Violation:**
- ```typescript
- // ❌ IMMEDIATE TERMINATION - Reading implementation during testing
- const toolImplementation = await Read('/src/mcp/tools/device-shared/list_devices.ts');
- ```
-
-**ENFORCEMENT PROCEDURE:**
-1. **First Violation**: Immediate correction and restart of testing process
-2. **Documentation Update**: Add explicit prohibition to prevent future violations
-3. **Method Validation**: Ensure all future testing uses only Reloaderoo inspect commands
-4. **Progress Reset**: Restart testing from foundation tools if direct MCP usage detected
-
-**VALID TESTING SEQUENCE EXAMPLE:**
-```bash
-# ✅ CORRECT - Step-by-step manual execution via Reloaderoo
-# Tool 1: Test doctor
-npx reloaderoo@latest inspect call-tool "doctor" --params '{}' -- node build/cli.js mcp
-# [Read response, verify, mark complete in TodoWrite]
-
-# Tool 2: Test list_devices
-npx reloaderoo@latest inspect call-tool "list_devices" --params '{}' -- node build/cli.js mcp
-# [Read response, capture UUIDs, mark complete in TodoWrite]
-
-# Tool 3: Test list_sims
-npx reloaderoo@latest inspect call-tool "list_sims" --params '{}' -- node build/cli.js mcp
-# [Read response, capture UUIDs, mark complete in TodoWrite]
-
-# Tool X: Test stateful tool (expected to fail)
-npx reloaderoo@latest inspect call-tool "swift_package_stop" --params '{"pid": 12345}' -- node build/cli.js mcp
-# [Tool fails as expected - no in-memory state available]
-# [Mark as "false negative - stateful tool limitation" in TodoWrite]
-# [Continue to next tool without investigation]
-
-# Continue individually for all tools (use count from npm run tools)...
-```
-
-**HANDLING STATEFUL TOOL FAILURES:**
-```bash
-# ✅ CORRECT Response to Expected Stateful Tool Failure
-# Tool fails with "No process found" or similar state-related error
-# Response: Mark tool as "tested - false negative (stateful)" in task list
-# Do NOT attempt to diagnose, fix, or investigate the failure
-# Continue immediately to next tool in sequence
-```
-
-## Error Testing
-
-```bash
-# Test error handling systematically
-echo "=== Error Testing ==="
-
-# Test with invalid JSON parameters
-echo "Testing invalid parameter types..."
-npx reloaderoo@latest inspect call-tool list_schems_proj --params '{"projectPath": 123}' -- node build/cli.js mcp 2>/dev/null
-
-# Test with non-existent paths
-echo "Testing non-existent paths..."
-npx reloaderoo@latest inspect call-tool list_schems_proj --params '{"projectPath": "/nonexistent/path.xcodeproj"}' -- node build/cli.js mcp 2>/dev/null
-
-# Test with invalid UUIDs
-echo "Testing invalid UUIDs..."
-npx reloaderoo@latest inspect call-tool boot_sim --params '{"simulatorUuid": "invalid-uuid"}' -- node build/cli.js mcp 2>/dev/null
-```
-
-## Testing Report Generation
-
-```bash
-# Create comprehensive testing session report
-cat > TESTING_SESSION_$(date +%Y-%m-%d).md << EOF
-# Manual Testing Session - $(date +%Y-%m-%d)
-
-## Environment
-- macOS Version: $(sw_vers -productVersion)
-- XcodeBuildMCP Version: $(jq -r '.version' package.json 2>/dev/null || echo "unknown")
-- Testing Method: Reloaderoo @latest via npx
-
-## Official Counts (Programmatically Verified)
-- Total Tools: $TOOL_COUNT
-- Total Resources: $RESOURCE_COUNT
-
-## Test Results
-[Document test results here]
-
-## Issues Found
-[Document any discrepancies or failures]
-
-## Performance Notes
-[Document response times and performance observations]
-EOF
-
-echo "Testing session template created: TESTING_SESSION_$(date +%Y-%m-%d).md"
-```
-
-### Key Commands Reference
-
-```bash
-# Essential testing commands
-npx reloaderoo@latest inspect ping -- node build/cli.js mcp
-npx reloaderoo@latest inspect server-info -- node build/cli.js mcp
-npx reloaderoo@latest inspect list-tools -- node build/cli.js mcp | jq '.tools | length'
-npx reloaderoo@latest inspect list-resources -- node build/cli.js mcp | jq '.resources | length'
-npx reloaderoo@latest inspect call-tool TOOL_NAME --params '{}' -- node build/cli.js mcp
-npx reloaderoo@latest inspect read-resource "xcodebuildmcp://RESOURCE" -- node build/cli.js mcp
-
-# Schema extraction
-jq --arg tool "TOOL_NAME" '.tools[] | select(.name == $tool) | .inputSchema' /tmp/tools.json
-jq --arg tool "TOOL_NAME" '.tools[] | select(.name == $tool) | .description' /tmp/tools.json
-```
-
-## Troubleshooting
-
-### Common Issues
-
-#### 1. Reloaderoo Command Timeouts
-**Symptoms**: Commands hang or timeout after extended periods
-**Cause**: Server startup issues or MCP protocol communication problems
-**Resolution**:
-- Verify server builds successfully: `npm run build`
-- Test direct server startup: `node build/cli.js mcp`
-- Check for TypeScript compilation errors
-
-#### 2. Tool Parameter Validation Errors
-**Symptoms**: Tools return parameter validation errors
-**Cause**: Missing or incorrect required parameters
-**Resolution**:
-- Check tool schema: `jq --arg tool "TOOL_NAME" '.tools[] | select(.name == $tool) | .inputSchema' /tmp/tools.json`
-- Verify parameter types and required fields
-- Use captured dependency data (UUIDs, paths, schemes)
-
-#### 3. "No Such Tool" Errors
-**Symptoms**: Reloaderoo reports tool not found
-**Cause**: Tool name mismatch or server registration issues
-**Resolution**:
-- Verify tool exists in list: `npx reloaderoo@latest inspect list-tools -- node build/cli.js mcp | jq '.tools[].name'`
-- Check exact tool name spelling and case sensitivity
-- Ensure server built successfully
-
-#### 4. Empty or Malformed Responses
-**Symptoms**: Tools return empty responses or JSON parsing errors
-**Cause**: Tool implementation issues or server errors
-**Resolution**:
-- Document as testing finding - do not investigate implementation
-- Mark tool as "failed - empty response" in task list
-- Continue with next tool in sequence
-
-This systematic approach ensures comprehensive, accurate testing using programmatic discovery and validation of all XcodeBuildMCP functionality through the MCP interface exclusively.
diff --git a/docs/dev/NODEJS_2025.md b/docs/dev/NODEJS_2025.md
deleted file mode 100644
index a80723b2c..000000000
--- a/docs/dev/NODEJS_2025.md
+++ /dev/null
@@ -1,550 +0,0 @@
-# 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.
\ No newline at end of file
diff --git a/docs/dev/QUERY_TOOL_FORMAT_SPEC.md b/docs/dev/QUERY_TOOL_FORMAT_SPEC.md
deleted file mode 100644
index 2f60e18a8..000000000
--- a/docs/dev/QUERY_TOOL_FORMAT_SPEC.md
+++ /dev/null
@@ -1,123 +0,0 @@
-# Query Tool Formatting Spec
-
-## Goal
-
-Make all xcodebuild query tools (list-schemes, show-build-settings, get-app-path variants) use the same visual UX as pipeline-backed build/test tools: front matter, structured errors, clean results, manifest-driven next steps.
-
-These tools do NOT need the full streaming pipeline (no parser, no run-state, no renderers). They run a single short-lived xcodebuild command and return a result. But they must share the same visual language.
-
-## Target output format
-
-### Happy path
-
-```
-🔍 List Schemes
-
- Workspace: example_projects/iOS_Calculator/CalculatorApp.xcworkspace
-
-Schemes:
- - CalculatorApp
- - CalculatorAppFeature
-
-Next steps:
-1. Build for simulator: xcodebuildmcp simulator build ...
-```
-
-```
-🔍 Show Build Settings
-
- Scheme: CalculatorApp
- Workspace: example_projects/iOS_Calculator/CalculatorApp.xcworkspace
-
-
-
-Next steps:
-1. Build for macOS: ...
-```
-
-```
-🔍 Get App Path
-
- Scheme: CalculatorApp
- Workspace: example_projects/iOS_Calculator/CalculatorApp.xcworkspace
- Platform: iOS Simulator
- Simulator: iPhone 17
-
- └ App Path: /path/to/CalculatorApp.app
-
-Next steps:
-1. Get bundle ID: ...
-```
-
-### Sad path
-
-```
-🔍 Get App Path
-
- Scheme: NONEXISTENT
- Workspace: example_projects/iOS_Calculator/CalculatorApp.xcworkspace
- Platform: iOS Simulator
-
-Errors (1):
-
- ✗ The workspace named "CalculatorApp" does not contain a scheme named "NONEXISTENT".
-
-❌ Query failed.
-```
-
-No raw xcodebuild noise (timestamps, PIDs, result bundle paths). No next steps on failure.
-
-## Implementation approach
-
-### Shared helper: `formatQueryPreflight`
-
-Extend `formatToolPreflight` in `src/utils/build-preflight.ts` to support query operations. Add operation types: `'List Schemes'`, `'Show Build Settings'`, `'Get App Path'`. Make `configuration` and `platform` optional (query tools may not have them).
-
-Use emoji `🔍` (U+1F50D) for all query operations.
-
-### Shared helper: `parseXcodebuildError`
-
-Create a small utility to extract clean error messages from raw xcodebuild stderr/output. Strip:
-- Timestamp lines (`2026-03-21 13:42:...`)
-- Result bundle lines (`Writing error result bundle to ...`)
-- PID noise
-
-Keep only `xcodebuild: error: ` lines, cleaned to just ``.
-
-### Error formatting
-
-Use the same `Errors (N):` grouped block format with `✗` prefix. Reuse `formatGroupedCompilerErrors` or a lightweight equivalent.
-
-### Result formatting
-
-- `list_schemes`: List schemes as ` - SchemeName` lines under a `Schemes:` heading
-- `show_build_settings`: Raw build settings output (already structured)
-- `get_*_app_path`: Use the tree format (`└ App Path: /path/to/app`) matching the build-run-result footer
-
-### Next steps
-
-Continue using `nextStepParams` and let `postProcessToolResponse` resolve manifest templates. No change needed.
-
-### Error response
-
-On failure, return `isError: true` with no next steps (consistent with pipeline tools).
-
-## Tools to migrate
-
-1. `src/mcp/tools/project-discovery/list_schemes.ts`
-2. `src/mcp/tools/project-discovery/show_build_settings.ts`
-3. `src/mcp/tools/simulator/get_sim_app_path.ts`
-4. `src/mcp/tools/macos/get_mac_app_path.ts`
-5. `src/mcp/tools/device/get_device_app_path.ts`
-
-## Rules
-
-- No full pipeline (no startBuildPipeline, no createPendingXcodebuildResponse)
-- Use formatToolPreflight (extended) for front matter
-- Parse xcodebuild errors cleanly
-- Strip raw xcodebuild noise from error output
-- Use `✗` grouped error block for failures
-- Use `❌ Query failed.` as the failure summary (not tool-specific messages)
-- Next steps only on success
-- Update existing tests to match new output format
-- All tests must pass, no regressions
diff --git a/docs/dev/README.md b/docs/dev/README.md
deleted file mode 100644
index 50d361197..000000000
--- a/docs/dev/README.md
+++ /dev/null
@@ -1,26 +0,0 @@
-# Developer Documentation
-
-## Getting started for contributors
-- [Contributing guide](CONTRIBUTING.md)
-- [Code quality standards](CODE_QUALITY.md)
-- [Testing guidelines](TESTING.md)
-- [Architecture overview](ARCHITECTURE.md)
-- [Tool authoring cookbook](TOOL_AUTHORING_COOKBOOK.md)
-
-## Build and tooling
-- [ESLint and type safety](ESLINT_TYPE_SAFETY.md)
-- [Node.js and runtime notes](NODEJS_2025.md)
-
-## Release and maintenance
-- [Release process](RELEASE_PROCESS.md)
-- [Manual testing](MANUAL_TESTING.md)
-
-## Deep dives and plans
-- [Plugin development](PLUGIN_DEVELOPMENT.md)
-- [Reloaderoo docs](RELOADEROO.md)
-- [Reloaderoo primer](RELOADEROO_XCODEBUILDMCP_PRIMER.md)
-- [Reloaderoo for XcodeBuildMCP](RELOADEROO_FOR_XCODEBUILDMCP.md)
-- [Zod migration guide](ZOD_MIGRATION_GUIDE.md)
-- [Test runner env plan](TEST_RUNNER_ENV_IMPLEMENTATION_PLAN.md)
-- [Session management plan](session_management_plan.md)
-- [Session-aware migration todo](session-aware-migration-todo.md)
diff --git a/docs/dev/RELEASE_PROCESS.md b/docs/dev/RELEASE_PROCESS.md
deleted file mode 100644
index b75e75ad8..000000000
--- a/docs/dev/RELEASE_PROCESS.md
+++ /dev/null
@@ -1,238 +0,0 @@
-# Release Process
-
-## GitHub Release Notes Source of Truth
-
-GitHub release descriptions are generated from the matching version section in `CHANGELOG.md`. The release process now enforces this in both local and CI flows:
-
-- `scripts/release.sh` validates release notes generation before tagging and pushing
-- `.github/workflows/release.yml` generates the final GitHub release body from `CHANGELOG.md`
-
-If the changelog section for the target version is missing or empty, release execution fails with a clear error.
-
-If the latest changelog section is `## [Unreleased]` and no matching version heading exists yet, `scripts/release.sh` automatically renames that heading to `## []` for the release. In `--dry-run`, this rename is performed only in a temporary file and does not modify `CHANGELOG.md`.
-
-Preview release notes locally:
-
-```bash
-node scripts/generate-github-release-notes.mjs --version 2.0.0
-```
-
-## Release Workflow Modes
-
-The release workflow (`.github/workflows/release.yml`) has two execution modes:
-
-### Tag push (`push` on `v*`)
-
-Production release behavior:
-- Publishes package to npm.
-- Creates GitHub release and uploads npm tarball.
-- Builds and verifies portable macOS artifacts (`arm64`, `x64`, `universal`).
-- Uploads portable artifacts to GitHub release assets.
-- Updates the Homebrew tap repository (`getsentry/homebrew-xcodebuildmcp`) directly when `HOMEBREW_TAP_TOKEN` is configured.
-- Attempts MCP Registry publish (best effort based on configured secrets).
-
-### Manual dispatch (`workflow_dispatch`)
-
-Validation behavior only (no production deployment):
-- Runs formatting/build/tests and packaging checks.
-- Runs npm publish in `--dry-run` mode.
-- Builds and verifies portable artifacts for release-pipeline validation.
-- Does **not** publish to npm.
-- Does **not** create GitHub release.
-- Does **not** upload portable assets to a release.
-- Does **not** update Homebrew tap.
-- Does **not** run MCP Registry publish job.
-
-## Step-by-Step Development Workflow
-
-### 1. Starting New Work
-
-**Always start by syncing with main:**
-```bash
-git checkout main
-git pull origin main
-```
-
-**Create feature branch using standardized naming convention:**
-```bash
-git checkout -b feature/issue-123-add-new-feature
-git checkout -b bugfix/issue-456-fix-simulator-crash
-```
-
-### 2. Development & Commits
-
-**Before committing, ALWAYS run quality checks:**
-```bash
-npm run build # Ensure code compiles
-npm run typecheck # MANDATORY: Fix all TypeScript errors
-npm run lint # Fix linting issues
-npm run test # Ensure tests pass
-```
-
-**🚨 CRITICAL: TypeScript errors are BLOCKING:**
-- **ZERO tolerance** for TypeScript errors in commits
-- The `npm run typecheck` command must pass with no errors
-- Fix all `ts(XXXX)` errors before committing
-- Do not ignore or suppress TypeScript errors without explicit approval
-
-**Make logical, atomic commits:**
-- Each commit should represent a single logical change
-- Write short, descriptive commit summaries
-- Commit frequently to your feature branch
-
-```bash
-# Always run quality checks first
-npm run typecheck && npm run lint && npm run test
-
-# Then commit your changes
-git add .
-git commit -m "feat: add simulator boot validation logic"
-git commit -m "fix: handle null response in device list parser"
-```
-
-### 3. Pushing Changes
-
-**🚨 CRITICAL: Always ask permission before pushing**
-- **NEVER push without explicit user permission**
-- **NEVER force push without explicit permission**
-- Pushing without permission is a fatal error resulting in termination
-
-```bash
-# Only after getting permission:
-git push origin feature/your-branch-name
-```
-
-### 4. Pull Request Creation
-
-**Use GitHub CLI tool exclusively:**
-```bash
-gh pr create --title "feat: add simulator boot validation" --body "$(cat <<'EOF'
-## Summary
-Brief description of what this PR does and why.
-
-## Background/Details
-### For New Features:
-- Detailed explanation of the new feature
-- Context and requirements that led to this implementation
-- Design decisions and approach taken
-
-### For Bug Fixes:
-- **Root Cause Analysis**: Detailed explanation of what caused the bug
-- Specific conditions that trigger the issue
-- Why the current code fails in these scenarios
-
-## Solution
-- How the root cause was addressed
-- Technical approach and implementation details
-- Key changes made to resolve the issue
-
-## Testing
-- **Reproduction Steps**: How to reproduce the original issue (for bugs)
-- **Validation Method**: How you verified the fix works
-- **Test Coverage**: What tests were added or modified
-- **Manual Testing**: Steps taken to validate the solution
-- **Edge Cases**: Additional scenarios tested
-
-## Notes
-- Any important considerations for reviewers
-- Potential impacts or side effects
-- Future improvements or technical debt
-- Deployment considerations
-EOF
-)"
-```
-
-**After PR creation, add automated review trigger:**
-```bash
-gh pr comment --body "Cursor review"
-```
-
-### 5. Branch Management & Rebasing
-
-**Keep branch up to date with main:**
-```bash
-git checkout main
-git pull origin main
-git checkout your-feature-branch
-git rebase main
-```
-
-**If rebase creates conflicts:**
-- Resolve conflicts manually
-- `git add .` resolved files
-- `git rebase --continue`
-- **Ask permission before force pushing rebased branch**
-
-### 6. Merge Process
-
-**Only merge via Pull Requests:**
-- No direct merges to `main`
-- Maintain linear commit history through rebasing
-- Use "Squash and merge" or "Rebase and merge" as appropriate
-- Delete feature branch after successful merge
-
-## Pull Request Template Structure
-
-Every PR must include these sections in order:
-
-1. **Summary**: Brief overview of changes and purpose
-2. **Background/Details**:
- - New Feature: Requirements, context, design decisions
- - Bug Fix: Detailed root cause analysis
-3. **Solution**: Technical approach and implementation details
-4. **Testing**: Reproduction steps, validation methods, test coverage
-5. **Notes**: Additional considerations, impacts, future work
-
-## Critical Rules
-
-### ❌ FATAL ERRORS (Result in Termination)
-- **NEVER push to `main` directly**
-- **NEVER push without explicit user permission**
-- **NEVER force push without explicit permission**
-- **NEVER commit code with TypeScript errors**
-
-### ✅ Required Practices
-- Always pull from `main` before creating branches
-- **MANDATORY: Run `npm run typecheck` before every commit**
-- **MANDATORY: Fix all TypeScript errors before committing**
-- Use `gh` CLI tool for all PR operations
-- Add "Cursor review" comment after PR creation
-- Maintain linear commit history via rebasing
-- Ask permission before any push operation
-- Use standardized branch naming conventions
-
-## Branch Naming Conventions
-
-- `feature/issue-xxx-description` - New features
-- `bugfix/issue-xxx-description` - Bug fixes
-- `hotfix/critical-issue-description` - Critical production fixes
-- `docs/update-readme` - Documentation updates
-- `refactor/improve-error-handling` - Code refactoring
-
-## Automated Quality Gates
-
-### CI/CD Pipeline
-Our GitHub Actions CI pipeline automatically enforces these quality checks:
-1. `npm run build` - Compilation check
-2. `npm run docs:check` - Validate CLI command references in consumer docs
-3. `npm run lint` - ESLint validation
-4. `npm run format:check` - Prettier formatting check
-5. `npm run typecheck` - **TypeScript error validation**
-6. `npm run test` - Test suite execution
-
-**All checks must pass before PR merge is allowed.**
-
-### Optional: Pre-commit Hook Setup
-To install the repository-managed pre-commit hook:
-
-```bash
-npm run hooks:install
-```
-
-This installs `.githooks/pre-commit` and configures `core.hooksPath` for this repository.
-
-The shared pre-commit hook runs:
-- `npm run format:check`
-- `npm run lint`
-- `npm run build`
-- `npm run docs:check`
diff --git a/docs/dev/RENDERING_PIPELINE.md b/docs/dev/RENDERING_PIPELINE.md
deleted file mode 100644
index de4272727..000000000
--- a/docs/dev/RENDERING_PIPELINE.md
+++ /dev/null
@@ -1,280 +0,0 @@
-# Rendering Pipeline
-
-All tool output flows through a unified event-based rendering pipeline. Tools produce `PipelineEvent` objects. Renderers consume events and produce output appropriate for the active runtime (CLI text, CLI JSON, MCP).
-
-## Core Principle
-
-```mermaid
-flowchart LR
- T[Tool Handler] -->|PipelineEvent array| R[Renderers]
- R -->|text| STDOUT[stdout]
- R -->|json| STDOUT
- R -->|content| MCP[ToolResponse.content]
-```
-
-Every piece of output — headers, status lines, detail trees, summaries, next-steps — is a pipeline event. Renderers are the **only** mechanism that produces output. There is no direct content mutation, text extraction, or replay.
-
-## Renderers
-
-Three renderers exist. Which are active depends on the runtime environment:
-
-| Renderer | Purpose | Writes to stdout? |
-|----------|---------|-------------------|
-| **MCP** | Accumulates formatted text into `ToolResponse.content` | No |
-| **CLI Text** | Writes formatted, colored text to stdout in real-time | Yes |
-| **CLI JSONL** | Writes one JSON object per event per line to stdout | Yes |
-
-### Renderer Activation
-
-Determined by `resolveRenderers()` in `src/utils/renderers/index.ts` based on environment variables set during bootstrap:
-
-```mermaid
-flowchart TD
- R[resolveRenderers] --> MCP[MCP Renderer
always created]
- R --> CHECK{RUNTIME == cli
AND VERBOSE != 1?}
- CHECK -->|no| DONE[Return MCP only]
- CHECK -->|yes| FMT{OUTPUT_FORMAT?}
- FMT -->|json| JSONL[+ CLI JSONL Renderer]
- FMT -->|text| TEXT[+ CLI Text Renderer]
-```
-
-| Context | `XCODEBUILDMCP_RUNTIME` | `..._CLI_OUTPUT_FORMAT` | Active Renderers |
-|---------|------------------------|------------------------|------------------|
-| MCP server | `mcp` | — | MCP only |
-| CLI `--output text` | `cli` | `text` | MCP + CLI Text |
-| CLI `--output json` | `cli` | `json` | MCP + CLI JSONL |
-| Daemon (internal) | `daemon` | — | MCP only |
-| Verbose / test | any | any + `VERBOSE=1` | MCP only |
-
-The MCP renderer is **always** active. CLI renderers are additive.
-
-## Pipeline Flows
-
-### Flow 1: Non-Xcodebuild Tools (Immediate)
-
-Most tools (simulator management, project discovery, coverage, UI automation, etc.) produce all their events at once and return immediately. No real-time streaming.
-
-```mermaid
-sequenceDiagram
- participant TH as Tool Handler
- participant TR as toolResponse()
- participant CLR as CLI Renderer
- participant MCR as MCP Renderer
- participant PP as postProcessToolResponse()
- participant PR as printToolResponse()
-
- TH->>TR: events[]
- loop Each event
- TR->>CLR: onEvent(event)
- CLR->>CLR: write to stdout
- TR->>MCR: onEvent(event)
- MCR->>MCR: accumulate text
- end
- TR->>CLR: finalize()
- TR->>MCR: finalize()
- TR-->>TH: ToolResponse
{content, _meta.events, streamedCounts}
-
- TH-->>PP: response (with nextStepParams)
- PP->>PP: resolve next-steps from manifest templates
- PP->>PP: create NextStepsEvent
-
- Note over PP,MCR: emitNextStepsEvent() — same renderer types
- PP->>CLR: onEvent(next-steps)
- CLR->>CLR: write to stdout
- PP->>MCR: onEvent(next-steps)
- MCR->>MCR: accumulate text
- PP->>CLR: finalize()
- PP->>MCR: finalize()
- PP->>PP: append MCP content + event to response
-
- PP-->>PR: final ToolResponse
- PR->>PR: emit any remaining delta
(usually nothing)
-```
-
-### Flow 2: Xcodebuild Tools (Streaming)
-
-Build, test, and build-and-run tools use a long-lived pipeline that streams events in real-time as xcodebuild produces output. The pipeline stays open during execution and is finalized after the build completes.
-
-#### Build Execution Phase
-
-```mermaid
-sequenceDiagram
- participant XC as xcodebuild process
- participant P as Event Parser
- participant RS as RunState
- participant CLR as CLI Renderer
- participant MCR as MCP Renderer
-
- Note over XC,MCR: startBuildPipeline() creates pipeline with renderers
-
- loop stdout/stderr chunks in real-time
- XC->>P: raw output
- P->>RS: parsed PipelineEvent
- RS->>CLR: onEvent(event)
- CLR->>CLR: write to stdout
(progress, stages, errors)
- RS->>MCR: onEvent(event)
- MCR->>MCR: accumulate text
- end
-
- Note over XC,MCR: xcodebuild exits — pipeline stays open
-```
-
-#### Finalization Phase
-
-```mermaid
-sequenceDiagram
- participant PP as postProcessToolResponse()
- participant FP as finalizePendingXcodebuildResponse()
- participant RS as RunState
- participant CLR as CLI Renderer
- participant MCR as MCP Renderer
- participant PR as printToolResponse()
-
- PP->>FP: isPendingXcodebuild? yes
- FP->>FP: create next-steps event
- FP->>RS: finalize(tailEvents incl. next-steps)
-
- RS->>CLR: onEvent(summary)
- CLR->>CLR: write summary to stdout
- RS->>MCR: onEvent(summary)
-
- RS->>CLR: onEvent(detail-tree)
- CLR->>CLR: write details to stdout
- RS->>MCR: onEvent(detail-tree)
-
- RS->>CLR: onEvent(next-steps)
- CLR->>CLR: write next-steps to stdout
- RS->>MCR: onEvent(next-steps)
-
- FP->>CLR: finalize()
- FP->>MCR: finalize()
- FP-->>PP: ToolResponse
{mcpContent, events, streamedCounts}
-
- PP-->>PR: final ToolResponse
- PR->>PR: emit any remaining delta
(usually nothing)
-```
-
-Key difference from immediate tools: the pipeline owns the renderer lifecycle. Events stream through renderers during execution. Next-steps are injected as tail events **before** finalization, so they flow through the same renderers in the same pass.
-
-### Flow 3: MCP Server Mode
-
-In MCP mode, only the MCP renderer is active. No CLI output.
-
-```mermaid
-sequenceDiagram
- participant AI as AI Model (MCP Client)
- participant S as MCP Server
- participant TH as Tool Handler
- participant MCR as MCP Renderer
-
- AI->>S: call_tool request
- S->>TH: handler(args)
- TH->>MCR: events via toolResponse()
- MCR->>MCR: accumulate text
- TH-->>S: ToolResponse
- S->>S: postProcessToolResponse()
emitNextStepsEvent() -> MCP renderer
- S-->>AI: ToolResponse.content
(formatted text)
-```
-
-Next-steps go through `emitNextStepsEvent()` which creates a fresh MCP renderer, formats the event, and appends the content. The MCP renderer uses function-call format for next-steps (e.g., `install_app_sim({ simulatorId: "..." })`).
-
-### Flow 4: Daemon Mode
-
-Stateful tools run on a background daemon process. The daemon uses MCP-only rendering (no CLI output). The response travels over a Unix socket to the CLI process, which handles CLI output.
-
-```mermaid
-sequenceDiagram
- participant CLI as CLI Process
- participant D as Daemon Process
- participant MCR as MCP Renderer
- participant PR as printToolResponse()
-
- CLI->>D: RPC request (Unix socket)
- D->>D: tool.handler(args)
- D->>MCR: events via toolResponse()
(MCP renderer only, no CLI)
- D->>D: postProcessToolResponse()
(next-steps resolved)
- D-->>CLI: ToolResponse (over socket)
-
- CLI->>CLI: postProcessToolResponse()
(applyTemplateNextSteps: false)
- CLI->>PR: printToolResponse()
- PR->>PR: print content to stdout
-```
-
-### Flow 5: CLI JSON Mode
-
-Events are emitted as JSONL (one JSON object per line) in real-time.
-
-```mermaid
-sequenceDiagram
- participant TH as Tool Handler
- participant JR as CLI JSONL Renderer
- participant MCR as MCP Renderer
-
- loop Each event from tool or xcodebuild
- TH->>JR: onEvent(event)
- JR->>JR: stdout.write(JSON.stringify(event) + newline)
- TH->>MCR: onEvent(event)
- end
-
- Note over TH,MCR: Next-steps appended via emitNextStepsEvent()
- TH->>JR: onEvent(next-steps)
- JR->>JR: stdout.write(JSON.stringify(nextStepsEvent) + newline)
-```
-
-Output example:
-```jsonl
-{"type":"header","timestamp":"...","operation":"Build","params":[...]}
-{"type":"build-stage","timestamp":"...","stage":"COMPILING"}
-{"type":"summary","timestamp":"...","status":"SUCCEEDED","durationMs":5200}
-{"type":"next-steps","timestamp":"...","steps":[{"tool":"launch_app_sim","params":{...}}]}
-```
-
-## Event Types
-
-All events implement `PipelineEvent` (see `src/types/pipeline-events.ts`):
-
-| Event Type | Purpose | Example |
-|-----------|---------|---------|
-| `header` | Operation banner with params | "Build", scheme, workspace, derived data |
-| `build-stage` | Build progress phase | Resolving packages, Compiling, Linking |
-| `status-line` | Success/error/warning/info status | "Build succeeded", "App launched" |
-| `section` | Titled block with detail lines | Failed test output, captured output |
-| `detail-tree` | Key-value tree with branch characters | App path, bundle ID, process ID |
-| `table` | Columnar data | Simulator list, device list |
-| `file-ref` | File path reference | Build log path, debug log |
-| `compiler-error` | Compiler diagnostic | Error message with file location |
-| `compiler-warning` | Compiler warning | Warning message with file location |
-| `test-failure` | Test failure diagnostic | Test name, assertion, location |
-| `test-discovery` | Discovered test list | Test names, count |
-| `test-progress` | Running test counts | Completed, failed, total |
-| `summary` | Final operation summary | Succeeded/failed, duration, test counts |
-| `next-steps` | Suggested follow-up actions | Tool names with params |
-
-## Key Files
-
-| File | Responsibility |
-|------|---------------|
-| `src/utils/tool-response.ts` | `toolResponse()` — streams events through renderers, returns response |
-| `src/utils/renderers/index.ts` | `resolveRenderers()` — decides which renderers are active |
-| `src/utils/renderers/mcp-renderer.ts` | Accumulates event text into `ToolResponse.content` |
-| `src/utils/renderers/cli-text-renderer.ts` | Writes formatted text to stdout (supports interactive progress) |
-| `src/utils/renderers/cli-jsonl-renderer.ts` | Writes JSON events to stdout |
-| `src/utils/renderers/event-formatting.ts` | Canonical formatters for each event type |
-| `src/utils/xcodebuild-pipeline.ts` | Long-lived pipeline for streaming builds |
-| `src/utils/xcodebuild-output.ts` | Pending response creation and finalization |
-| `src/utils/xcodebuild-run-state.ts` | Event ordering, deduplication, summary generation |
-| `src/runtime/tool-invoker.ts` | Post-processing: next-steps resolution, `emitNextStepsEvent()` |
-| `src/cli/output.ts` | `printToolResponse()` — prints remaining delta after renderers |
-| `src/utils/tool-event-builders.ts` | Factory functions for creating event objects |
-
-## Design Rules
-
-1. **Events are the model.** All output is represented as `PipelineEvent` objects. Renderers are the only mechanism that turns events into text/JSON.
-
-2. **Renderers produce output.** No direct `process.stdout.write()` outside renderers. No text content mutation after rendering.
-
-3. **One pass per event.** Each event goes through renderers exactly once. No replay, no extraction, no re-rendering.
-
-4. **Next-steps are events.** A `next-steps` event is treated identically to any other event — it flows through renderers which format it according to their strategy.
-
-5. **`printToolResponse()` handles the delta.** After renderers have written streamed output, `printToolResponse()` only prints content items or events that were appended after the initial streaming pass (tracked by `streamedEventCount` / `streamedContentCount`).
diff --git a/docs/dev/RENDERING_PIPELINE_REFACTOR.md b/docs/dev/RENDERING_PIPELINE_REFACTOR.md
deleted file mode 100644
index fb706dafc..000000000
--- a/docs/dev/RENDERING_PIPELINE_REFACTOR.md
+++ /dev/null
@@ -1,382 +0,0 @@
-# Rendering Pipeline Refactor Plan
-
-## Goal
-
-```
-events -> render(events, strategy) -> text -> output(target)
-```
-
-Three steps. Two render strategies (text, json). Two output targets (stdout, ToolResponse envelope). No special cases for next-steps. No `_meta` coordination. No replay.
-
-## Principles
-
-1. **Two render strategies**: text (human-readable) and json (JSONL). That's it.
-2. **Rendering is data in, text out.** A renderer takes events and produces strings. It doesn't know about stdout or ToolResponse.
-3. **Output target is post-render.** After rendering produces text, the caller decides: write to stdout (CLI) or wrap in ToolResponse (MCP).
-4. **Streaming is incremental rendering.** Same renderer, called event-by-event instead of all at once. The sink receives chunks progressively.
-5. **Daemon is lifecycle, not rendering.** Daemon keeps a process alive for stateful tools. It sends events over the wire. The CLI renders them locally.
-6. **ToolResponse is MCP transport only.** Internal code never constructs, inspects, or mutates ToolResponse. It's built once at the MCP boundary.
-7. **Next-steps are events.** They flow through the renderer like any other event. No second render pass.
-
-## Current State (problems)
-
-- Three "renderers" (MCP, CLI Text, CLI JSONL) when there should be two strategies
-- `mcp-renderer.ts` and `cli-text-renderer.ts` use the same formatters from `event-formatting.ts` — they're the same strategy with different sinks
-- `toolResponse()` renders AND constructs ToolResponse — mixing rendering with transport
-- `emitNextStepsEvent()` creates a second set of renderers for next-steps
-- `printToolResponse()` inspects `_meta`, calculates deltas, replays leftover output
-- `resolveRenderers()` always creates MCP renderer even in CLI mode
-- `ToolResponse` used as internal data structure throughout invoker, daemon, CLI
-- `_meta` used as undocumented coordination channel (events, streamed counts, pending state)
-
-## Design
-
-### Internal result type
-
-Tools return events. Not ToolResponse.
-
-```typescript
-// src/types/tool-result.ts
-
-interface ToolResult {
- events: PipelineEvent[];
- isError?: boolean;
- attachments?: ToolResponseContent[]; // non-event content (images only)
- nextSteps?: NextStep[];
- nextStepParams?: NextStepParamsMap;
-}
-
-interface PendingBuildResult {
- kind: 'pending-build';
- started: StartedPipeline;
- isError?: boolean;
- emitSummary: boolean;
- tailEvents: PipelineEvent[];
- fallbackContent: ToolResponseContent[];
- errorFallbackPolicy: 'always' | 'if-no-structured-diagnostics';
- includeBuildLogFileRef: boolean;
- includeParserDebugFileRef: boolean;
- meta?: Record;
-}
-
-type ToolExecutionResult = ToolResult | PendingBuildResult;
-```
-
-`ToolResponse` stays in `src/types/common.ts` as the MCP SDK type. Internal code stops using it.
-
-### Render function
-
-Pure function. Events in, text out.
-
-```typescript
-// src/rendering/render.ts
-
-type RenderStrategy = 'text' | 'json';
-
-// Batch render — all events at once, returns complete output
-function renderEvents(events: PipelineEvent[], strategy: RenderStrategy): string;
-
-// Incremental render — for streaming. Returns a session.
-interface RenderSession {
- push(event: PipelineEvent): string; // returns rendered text for this event
- finalize(): string; // returns any buffered text (grouped diagnostics, summary)
-}
-
-function createRenderSession(strategy: RenderStrategy): RenderSession;
-```
-
-**Text strategy**: reuses all existing formatters from `event-formatting.ts`. Handles diagnostic grouping, summary generation, transient/durable distinction. The `push()` return value is the rendered text for that event (may be empty for grouped events like compiler-error that are deferred until summary).
-
-**JSON strategy**: `push()` returns `JSON.stringify(event) + '\n'`. `finalize()` returns `''`.
-
-### Sink (output target)
-
-The caller decides what to do with the rendered text. This is not a class or interface — it's just what the boundary code does.
-
-**CLI text mode:**
-```typescript
-const session = createRenderSession('text');
-for (const event of result.events) {
- const text = session.push(event);
- if (text) process.stdout.write(formatCliTextLine(text) + '\n');
-}
-const final = session.finalize();
-if (final) process.stdout.write(final);
-```
-
-**CLI json mode:**
-```typescript
-const session = createRenderSession('json');
-for (const event of result.events) {
- process.stdout.write(session.push(event));
-}
-```
-
-**MCP boundary:**
-```typescript
-const text = renderEvents(result.events, 'text');
-const response: ToolResponse = {
- content: [
- { type: 'text', text },
- ...(result.attachments ?? []),
- ],
- isError: result.isError || undefined,
-};
-```
-
-**Streaming (xcodebuild CLI):**
-```typescript
-const session = createRenderSession('text');
-// During build execution, pipeline calls emitEvent for each parsed event:
-function emitEvent(event: PipelineEvent) {
- const text = session.push(event);
- if (text) process.stdout.write(formatCliTextLine(text) + '\n');
-}
-// After build completes and next-steps resolved:
-emitEvent(nextStepsEvent);
-const final = session.finalize();
-if (final) process.stdout.write(final);
-```
-
-### Interactive progress (CLI text)
-
-The CLI text renderer currently has spinner/transient line behavior for build stages and test progress. This stays in the text strategy but the `push()` return distinguishes durable vs transient text:
-
-```typescript
-interface TextRenderOp {
- text: string;
- transient?: boolean; // true = progress line that can be overwritten
-}
-```
-
-The CLI stdout sink handles transient lines using the existing `CliProgressReporter`. The MCP sink ignores transient ops. This is the only place where the sink needs to know more than "here's a string".
-
-### Tool handler changes
-
-`toolResponse()` becomes a pure data constructor:
-
-```typescript
-// src/utils/tool-response.ts
-function toolResponse(events: PipelineEvent[], options?): ToolResult {
- return {
- events,
- isError: detectError(events) || undefined,
- nextStepParams: options?.nextStepParams,
- };
-}
-```
-
-No rendering. No resolveRenderers(). No _meta.
-
-Handler signature in `src/runtime/types.ts`:
-```typescript
-handler: (params: Record) => Promise;
-```
-
-### Invoker flow
-
-```typescript
-// src/runtime/tool-invoker.ts — simplified executeTool
-
-async executeTool(tool, args, opts): Promise {
- const result = await tool.handler(args);
- return finalizeResult(tool, result, this.catalog);
-}
-```
-
-`finalizeResult()` replaces `postProcessToolResponse()`:
-1. If pending build: finalize pipeline, get events
-2. Resolve next-steps from manifest templates (existing logic, unchanged)
-3. Push next-steps event to events array
-4. Strip nextSteps/nextStepParams
-5. Return `ToolResult`
-
-No rendering. No emitNextStepsEvent(). No second renderer pass.
-
-### CLI entry point
-
-```typescript
-// src/cli/register-tool-commands.ts — simplified
-
-const strategy = outputFormat === 'json' ? 'json' : 'text';
-const session = createRenderSession(strategy);
-
-// For streaming tools, pass emitEvent into the invocation
-const emitEvent = (event: PipelineEvent) => {
- const rendered = session.push(event);
- if (rendered) writeToStdout(rendered, strategy);
-};
-
-const result = await invoker.invokeDirect(tool, args, {
- runtime: 'cli',
- emitEvent, // xcodebuild pipeline uses this for live streaming
-});
-
-// Finalize (flushes grouped diagnostics, summary)
-const finalText = session.finalize();
-if (finalText) writeToStdout(finalText, strategy);
-
-// Print non-event attachments (images)
-printAttachments(result.attachments);
-
-if (result.isError) process.exitCode = 1;
-```
-
-`printToolResponse()` is deleted. Its job is done by the boundary code above.
-
-### MCP entry point
-
-```typescript
-// src/utils/tool-registry.ts — simplified
-
-const result = await invoker.invoke(toolName, args, { runtime: 'mcp' });
-const text = renderEvents(result.events, 'text');
-return {
- content: [
- { type: 'text', text },
- ...(result.attachments ?? []),
- ],
- isError: result.isError || undefined,
-};
-```
-
-### Daemon flow
-
-Daemon doesn't render. It runs the tool, collects events, sends them to CLI.
-
-**Daemon server:**
-```typescript
-const result = await invoker.invoke(toolName, args, { runtime: 'daemon' });
-return { events: result.events, attachments: result.attachments, isError: result.isError };
-```
-
-**CLI after daemon response:**
-```typescript
-// Received events from daemon — render them locally
-const session = createRenderSession(strategy);
-for (const event of daemonResult.events) {
- const text = session.push(event);
- if (text) writeToStdout(text, strategy);
-}
-const final = session.finalize();
-if (final) writeToStdout(final, strategy);
-printAttachments(daemonResult.attachments);
-```
-
-Same rendering code path as direct CLI invocation. Daemon is just transport.
-
-### Xcodebuild streaming
-
-The pipeline stops owning renderers. It accepts an `emitEvent` callback.
-
-```typescript
-// src/utils/xcodebuild-pipeline.ts — key change
-
-interface PipelineOptions {
- operation: XcodebuildOperation;
- toolName: string;
- params: Record;
- minimumStage?: XcodebuildStage;
- emitEvent?: (event: PipelineEvent) => void; // NEW: live event sink
-}
-```
-
-When `emitEvent` is provided (CLI direct), events stream to stdout in real-time through the render session. When not provided (MCP, daemon), events are buffered and rendered after completion.
-
-Pipeline finalization returns events only:
-```typescript
-interface PipelineResult {
- state: XcodebuildRunState;
- events: PipelineEvent[];
-}
-```
-
-No `mcpContent`. No renderer finalization. The caller renders.
-
-### Next-steps format
-
-One canonical text format. No CLI-vs-MCP branching.
-
-Current MCP format is the canonical one:
-```
-Next steps:
-1. launch_app_sim({ simulatorId: "ABC-123", bundleId: "com.example.app" })
-2. stop_app_sim({ simulatorId: "ABC-123" })
-```
-
-CLI command format (`xcodebuildmcp simulator launch-app-sim --simulator-id "..."`) becomes a presentation concern in the CLI sink layer if desired, not a rendering concern. Initially, use the canonical format everywhere.
-
-## What Gets Deleted
-
-| File/Function | Reason |
-|---------------|--------|
-| `src/utils/renderers/mcp-renderer.ts` | Replaced by text strategy + MCP boundary wrapping |
-| `src/utils/renderers/cli-text-renderer.ts` | Replaced by text strategy + CLI stdout writing |
-| `src/utils/renderers/cli-jsonl-renderer.ts` | Replaced by json strategy + CLI stdout writing |
-| `src/utils/renderers/index.ts` (`resolveRenderers`) | No longer needed — strategy selected at boundary |
-| `emitNextStepsEvent()` in tool-invoker.ts | Next-steps pushed to events before render |
-| `printToolResponse()` complex logic | Boundary code handles output directly |
-| `_meta.events`, `_meta.streamedEventCount`, `_meta.streamedContentCount` | No coordination channel needed |
-| `_meta.pendingXcodebuild` | Typed `PendingBuildResult` instead |
-| `suppressCliStream` option | No CLI rendering in toolResponse() to suppress |
-
-## What Stays
-
-| Component | Why |
-|-----------|-----|
-| `event-formatting.ts` | Pure formatters, shared by text strategy |
-| `PipelineEvent` types | The event model is correct |
-| `tool-event-builders.ts` | Event factory functions |
-| `xcodebuild-event-parser.ts` | Parsing is not a rendering concern |
-| `xcodebuild-run-state.ts` | Event ordering/dedup is not a rendering concern |
-| `CliProgressReporter` | Interactive progress stays as a CLI sink concern |
-| `terminal-output.ts` | CLI text coloring stays as a CLI sink concern |
-| Next-step template resolution logic | Business logic, unchanged |
-
-## New Files
-
-| File | Purpose |
-|------|---------|
-| `src/types/tool-result.ts` | `ToolResult`, `PendingBuildResult`, `ToolExecutionResult` |
-| `src/rendering/render.ts` | `renderEvents()`, `createRenderSession()`, `RenderSession` |
-
-Two new files. That's it.
-
-## Migration Order
-
-1. **Add `ToolResult` type** — additive, no existing code changes
-2. **Add `renderEvents()` and `createRenderSession()`** — extract text strategy from existing `cli-text-renderer.ts` and `mcp-renderer.ts` (they use the same formatters). Add json strategy. Independently testable.
-3. **Change `toolResponse()` to return `ToolResult`** — stop rendering, just store events. Update all call sites (mechanical type change).
-4. **Change handler contract** to `Promise` in `types.ts` and `typed-tool-factory.ts`. Update tool modules.
-5. **Replace `postProcessToolResponse` with `finalizeResult`** — push next-steps to events. Delete `emitNextStepsEvent()`.
-6. **Refactor xcodebuild pipeline** — remove renderer ownership, accept `emitEvent` callback, return events only. Update pending result helpers. Update build/test tools.
-7. **Switch CLI boundary** — create render session, pass `emitEvent`, delete `printToolResponse()` complex logic.
-8. **Switch MCP boundary** — render at boundary, construct ToolResponse.
-9. **Switch daemon protocol** — send events over wire, render locally on CLI. Bump protocol version.
-10. **Delete old renderers** — `mcp-renderer.ts`, `cli-text-renderer.ts`, `cli-jsonl-renderer.ts`, `resolveRenderers()`.
-11. **Update docs and tests.**
-
-This should land as one atomic branch. Mixed old/new paths recreate the complexity.
-
-## Daemon Protocol
-
-Bump `DAEMON_PROTOCOL_VERSION` to 2. Wire payload changes from:
-```typescript
-{ response: ToolResponse }
-```
-to:
-```typescript
-{ events: PipelineEvent[], attachments?: ToolResponseContent[], isError?: boolean }
-```
-
-Old CLI + new daemon (or vice versa) fails fast with a restart instruction.
-
-## Risk
-
-- ~50 `toolResponse()` call sites need type changes (mechanical)
-- Handler contract change touches `types.ts`, `typed-tool-factory.ts`, `tool-registry.ts`, all tool modules
-- Daemon protocol bump requires atomic client+server update
-- Next-steps text format change is user-visible
-- Test churn is significant
-
-All of this is bounded and mechanical. The event model, parsing, formatting, and business logic are unchanged.
diff --git a/docs/dev/STRUCTURED_XCODEBUILD_EVENTS_PLAN.md b/docs/dev/STRUCTURED_XCODEBUILD_EVENTS_PLAN.md
deleted file mode 100644
index 3beed65e3..000000000
--- a/docs/dev/STRUCTURED_XCODEBUILD_EVENTS_PLAN.md
+++ /dev/null
@@ -1,723 +0,0 @@
-# Unified tool output pipeline
-
-## Goal
-
-Every tool in XcodeBuildMCP must produce its output through a single structured pipeline. No tool may construct its own formatted text. The pipeline owns all rendering, spacing, path formatting, and section structure.
-
-This applies to:
-
-- xcodebuild-backed tools (build, test, build & run, clean)
-- query tools (list simulators, list schemes, discover projects, show build settings)
-- action tools (set appearance, set location, boot simulator, install app)
-- coverage tools (coverage report, file coverage)
-- scaffolding tools (scaffold iOS project, scaffold macOS project)
-- logging tools (start/stop log capture)
-- debugging tools (attach, breakpoints, variables)
-- UI automation tools (tap, swipe, type text, screenshot, snapshot UI)
-- session tools (set defaults, show defaults, clear defaults)
-
-No exceptions. If a tool produces user-visible output, it goes through the pipeline.
-
-## Architecture principle
-
-Shared formatting, runtime-specific renderers.
-
-All renderers share a single set of formatting functions (`event-formatting.ts`) that define how each event type is converted to text. This is the single source of truth for output formatting. Each runtime has its own renderer that orchestrates those shared formatters according to its needs:
-
-- **MCP renderer** (`mcp-renderer.ts`): Buffers formatted text and returns it in `ToolResponse.content`. Applies session-level warning suppression.
-- **CLI text renderer** (`cli-text-renderer.ts`): Writes formatted text to stdout as events arrive. In interactive TTY mode, uses a Clack spinner for transient status updates (build stages, progress). Manages durable vs transient line state.
-- **CLI JSONL renderer** (`cli-jsonl-renderer.ts`): Serialises each event as one JSON line to stdout. Does not go through the text formatters.
-
-The renderers are not "dumb pipes" — the CLI text renderer in particular is a state machine that tracks transient lines, flush timing, and interactive spinner state. This is why the architecture uses separate renderer implementations rather than a single renderer with sink adapters.
-
-The key invariant is: **all text formatting lives in `event-formatting.ts`**. Renderers orchestrate when and how those formatters are called, but no renderer contains its own formatting logic.
-
-Runtime-specific rendering concerns:
-
-- CLI interactive mode: Clack spinner for transient status updates, durable flush rules before summary events
-- Next steps syntax: CLI renders `xcodebuildmcp workflow tool --flag "value"`, MCP renders `tool_name({ param: "value" })`. This is a single parameterised formatting function.
-- Warning suppression: session-level filter applied in MCP renderer before rendering.
-
-## Why this matters
-
-Without a unified pipeline, every tool re-invents:
-
-- spacing between sections (some add blank lines, some don't)
-- file path formatting (some call `displayPath`, some don't)
-- header/preflight structure (some use `formatToolPreflight`, some build strings manually)
-- error formatting (some use icons, some use `[NOT COVERED]`, some use bare text)
-- next steps rendering (some hardcode strings, some use the manifest)
-
-Every new tool or refactor re-introduces the same bugs. The pipeline makes these bugs structurally impossible.
-
-## Event model
-
-All tools emit structured events. The renderer converts events to formatted text. Tools never produce formatted text directly.
-
-### Generic tool events
-
-These events cover all non-xcodebuild tools:
-
-```ts
-type ToolEvent =
- | HeaderEvent // preflight block: operation name + params
- | SectionEvent // titled group of content lines
- | DetailTreeEvent // key/value pairs with tree connectors
- | StatusLineEvent // single status message (success, error, info)
- | FileRefEvent // a file path (always normalised)
- | TableEvent // rows of structured data
- | SummaryEvent // final outcome line
- | NextStepsEvent // suggested follow-up actions
- | XcodebuildEvent; // existing xcodebuild events (unchanged)
-```
-
-#### HeaderEvent
-
-Replaces `formatToolPreflight`. Every tool starts with a header.
-
-```ts
-interface HeaderEvent {
- type: 'header';
- operation: string; // e.g. 'File Coverage', 'List Simulators', 'Set Appearance'
- params: Array<{ // rendered as indented key: value lines
- label: string;
- value: string;
- }>;
- timestamp: string;
-}
-```
-
-The renderer owns:
-
-- the emoji (looked up from the operation name)
-- the blank line after the heading
-- the indentation of params
-- the trailing blank line after the params block
-
-Tools cannot get the spacing wrong because they never produce it.
-
-#### SectionEvent
-
-A titled group of content lines with an optional icon.
-
-```ts
-interface SectionEvent {
- type: 'section';
- title: string; // e.g. 'Not Covered (7 functions, 22 lines)'
- icon?: 'red-circle' | 'yellow-circle' | 'green-circle' | 'checkmark' | 'cross' | 'info';
- lines: string[]; // indented content lines
- timestamp: string;
-}
-```
-
-The renderer owns:
-
-- the icon-to-emoji mapping
-- the blank line before and after each section
-- the indentation of content lines
-
-#### DetailTreeEvent
-
-Key/value pairs rendered with tree connectors.
-
-```ts
-interface DetailTreeEvent {
- type: 'detail-tree';
- items: Array<{ label: string; value: string }>;
- timestamp: string;
-}
-```
-
-Rendered as:
-
-```text
- ├ App Path: /path/to/app
- └ Bundle ID: com.example.app
-```
-
-The renderer owns the connector characters and indentation.
-
-#### StatusLineEvent
-
-A single status message.
-
-```ts
-interface StatusLineEvent {
- type: 'status-line';
- level: 'success' | 'error' | 'info' | 'warning';
- message: string;
- timestamp: string;
-}
-```
-
-The renderer owns the emoji prefix based on level.
-
-#### FileRefEvent
-
-A file path that must be normalised.
-
-```ts
-interface FileRefEvent {
- type: 'file-ref';
- label?: string; // e.g. 'File' — rendered as "File: "
- path: string; // raw absolute path from the tool
- timestamp: string;
-}
-```
-
-The renderer always runs the path through `displayPath()` (relative if under cwd, absolute otherwise). Tools cannot bypass this.
-
-#### TableEvent
-
-Rows of structured data grouped under an optional heading.
-
-```ts
-interface TableEvent {
- type: 'table';
- heading?: string; // e.g. 'iOS 18.5'
- columns: string[]; // column names for alignment
- rows: Array>;
- timestamp: string;
-}
-```
-
-The renderer owns column alignment and indentation.
-
-#### SummaryEvent (generic)
-
-A final outcome line for non-xcodebuild tools. Different from the xcodebuild `SummaryEvent` which includes test counts and duration.
-
-```ts
-interface GenericSummaryEvent {
- type: 'generic-summary';
- level: 'success' | 'error';
- message: string;
- timestamp: string;
-}
-```
-
-#### NextStepsEvent
-
-Unchanged from the existing model. Parameterised rendering for CLI vs MCP syntax.
-
-### Xcodebuild events
-
-The existing `XcodebuildEvent` union type is unchanged. Xcodebuild-backed tools continue to use:
-
-- `start` (replaces `HeaderEvent` for xcodebuild tools — the start event already contains the preflight)
-- `status`, `warning`, `error`, `notice`
-- `test-discovery`, `test-progress`, `test-failure`
-- `summary`
-- `next-steps`
-
-The xcodebuild event parser feeds these into the same pipeline. The renderer handles both generic tool events and xcodebuild events.
-
-## Pipeline architecture
-
-### For xcodebuild-backed tools (existing, unchanged)
-
-```text
-tool logic
- -> startBuildPipeline(...)
- -> XcodebuildPipeline
- -> parser + run-state
- -> ordered XcodebuildEvent stream
- -> renderer -> sink (stdout or buffer)
-```
-
-This path remains as-is. The xcodebuild parser, run-state layer, and event types do not change.
-
-### For all other tools (new)
-
-```text
-tool logic
- -> emits ToolEvent[] (or streams them)
- -> renderer -> sink (stdout or buffer)
-```
-
-Simple tools emit events synchronously and return them. The pipeline renders them and routes to the appropriate sink.
-
-There is no parser or run-state layer for non-xcodebuild tools. They don't need one — they already have structured data. The pipeline is just: structured events -> renderer -> sink.
-
-### Mermaid diagram
-
-```mermaid
-flowchart LR
- subgraph "Xcodebuild tools"
- A[Tool logic] --> B[XcodebuildPipeline]
- B --> C[Event parser]
- B --> D[Run-state]
- C --> D
- D --> E[PipelineEvent stream]
- end
-
- subgraph "All other tools"
- F[Tool logic] --> G[PipelineEvent array]
- end
-
- E --> H[resolveRenderers]
- G --> I[toolResponse] --> H
-
- H --> J[MCP renderer]
- H --> K{CLI mode?}
-
- J --> L[Buffer → ToolResponse.content]
-
- K -->|text| M[CLI text renderer]
- K -->|json| N[CLI JSONL renderer]
-
- M --> O[stdout - streaming text]
- N --> P[stdout - streaming JSON]
-
- subgraph "Shared formatting"
- Q[event-formatting.ts]
- end
-
- J -.-> Q
- M -.-> Q
-```
-
-### Renderer behaviour
-
-#### MCP renderer
-
-- Buffers all formatted text parts
-- Returns as `ToolResponse.content` when the tool completes
-- Applies session-level warning suppression
-- Groups compiler errors, warnings, and test failures for batch rendering before summary
-
-#### CLI text renderer
-
-- Writes formatted text to stdout as events arrive
-- In interactive TTY mode: uses Clack spinner for transient status events, tracks durable vs transient line state
-- In non-interactive mode: writes all events as durable lines
-- Groups compiler errors, warnings, and test failures for batch rendering before summary
-- Tracks `lastVisibleEventType` for compact spacing between consecutive status lines
-
-#### CLI JSONL renderer
-
-- Serialises each event as one JSON line to stdout
-- Does not go through the text formatters
-- Available for all tools (events are the same union type)
-
-### Renderer resolution
-
-`resolveRenderers()` in `src/utils/renderers/index.ts` always creates the MCP renderer (for `ToolResponse.content`). If running in CLI mode, it also creates either the CLI text renderer or CLI JSONL renderer based on output format.
-
-`toolResponse()` in `src/utils/tool-response.ts` feeds events through all active renderers and extracts content from the MCP renderer.
-
-## Formatting contract
-
-One set of formatting functions. All renderers.
-
-```ts
-// src/utils/renderers/event-formatting.ts
-formatHeaderEvent(event: HeaderEvent): string;
-formatBuildStageEvent(event: BuildStageEvent): string;
-formatStatusLineEvent(event: StatusLineEvent): string;
-formatSectionEvent(event: SectionEvent): string;
-formatDetailTreeEvent(event: DetailTreeEvent): string;
-formatTableEvent(event: TableEvent): string;
-formatFileRefEvent(event: FileRefEvent): string;
-formatSummaryEvent(event: SummaryEvent): string;
-formatNextStepsEvent(event: NextStepsEvent, runtime: 'cli' | 'mcp'): string;
-```
-
-The formatting layer is the single source of truth for:
-
-- emoji selection per operation/level/icon
-- spacing between sections (always one blank line)
-- file path normalisation (always `displayPath()`)
-- indentation depth (always 2 spaces for params, content lines)
-- tree connector characters
-- next steps formatting (parameterised by runtime)
-- section ordering enforcement
-
-### Formatting rules enforced by the renderer
-
-These rules are not guidelines. They are enforced structurally because tools cannot produce formatted text.
-
-1. **Header always has a trailing blank line.** The renderer emits: blank line, emoji + operation, blank line, indented params, blank line. Every tool. No exceptions.
-
-2. **File paths are always normalised.** `FileRefEvent` paths always go through `displayPath()`. Xcodebuild diagnostic paths go through `formatDiagnosticFilePath()`. There is no code path where a raw absolute path reaches the output.
-
-3. **Sections are always separated by blank lines.** The renderer adds one blank line before each section. Tools cannot omit or double this.
-
-4. **Icons are always consistent.** The renderer maps `icon` enum values to emoji. Tools do not contain emoji characters.
-
-5. **Next steps are always last.** The renderer enforces ordering. Nothing renders after next steps.
-
-6. **Error messages follow the convention.** `Failed to : `. The renderer does not enforce this (it's a content concern), but the pipeline API makes it easy to follow.
-
-## How tools emit events
-
-### Simple action tools (e.g. set appearance)
-
-```ts
-return toolResponse([
- header('Set Appearance', [
- { label: 'Simulator', value: simulatorId },
- ]),
- statusLine('success', `Appearance set to ${mode} mode`),
-]);
-```
-
-### Query tools (e.g. list simulators)
-
-```ts
-return toolResponse([
- header('List Simulators'),
- ...grouped.map(([runtime, devices]) =>
- table(runtime, ['Name', 'UUID', 'State'],
- devices.map(d => ({ Name: d.name, UUID: d.udid, State: d.state }))
- )
- ),
- nextSteps([...]),
-]);
-```
-
-### Coverage tools (e.g. file coverage)
-
-```ts
-return toolResponse([
- header('File Coverage', [
- { label: 'xcresult', value: xcresultPath },
- { label: 'File', value: file },
- ]),
- fileRef('File', entry.filePath),
- statusLine('info', `Coverage: ${pct}% (${covered}/${total} lines)`),
- section('Not Covered', notCoveredLines, { icon: 'red-circle',
- title: `Not Covered (${count} functions, ${missedLines} lines)` }),
- section('Partial Coverage', partialLines, { icon: 'yellow-circle',
- title: `Partial Coverage (${count} functions)` }),
- section('Full Coverage', [`${fullCount} functions — all at 100%`], { icon: 'green-circle',
- title: `Full Coverage (${fullCount} functions) — all at 100%` }),
- nextSteps([...]),
-]);
-```
-
-### Xcodebuild tools
-
-These keep the existing parser and run-state layers (`startBuildPipeline()`, `executeXcodeBuildCommand()`, `createPendingXcodebuildResponse()`), but the run-state output gets mapped to `ToolEvent` types before reaching the renderer. The xcodebuild parser remains an ingestion layer — it just feeds into the unified event model instead of having its own rendering path. Streaming and Clack progress are preserved as CLI sink concerns.
-
-## Locked human-readable output contract
-
-The output structure for all tools follows the same rhythm:
-
-```text
-
-
- :
- :
-
-
-
-
-
-
-
-Next steps:
-1.
-2.
-```
-
-### For xcodebuild-backed tools
-
-The canonical examples are `build_run_macos` and `build_run_sim`. Their output contract is locked:
-
-Successful runs:
-
-1. front matter (header event / start event)
-2. runtime state and durable diagnostics
-3. summary
-4. execution-derived footer (detail tree)
-5. next steps
-
-Failed runs:
-
-1. front matter
-2. runtime state and/or grouped diagnostics
-3. summary
-
-Failed runs do not render next steps.
-
-### For non-xcodebuild tools
-
-Successful runs:
-
-1. header
-2. body (sections, tables, file refs, status lines — tool-specific)
-3. next steps (if applicable)
-
-Failed runs:
-
-1. header
-2. error status line
-3. no next steps
-
-### Example outputs
-
-#### Build (xcodebuild pipeline — existing)
-
-```text
-🔨 Build
-
- Scheme: CalculatorApp
- Workspace: example_projects/iOS_Calculator/CalculatorApp.xcworkspace
- Configuration: Debug
- Platform: iOS Simulator
- Simulator: iPhone 17
-
-✅ Build succeeded. (⏱️ 12.3s)
-
-Next steps:
-1. Get built app path: xcodebuildmcp simulator get-app-path --scheme "CalculatorApp"
-```
-
-#### File Coverage (generic pipeline — new)
-
-```text
-📊 File Coverage
-
- xcresult: /tmp/TestResults.xcresult
- File: CalculatorService.swift
-
-File: example_projects/.../CalculatorService.swift
-Coverage: 83.1% (157/189 lines)
-
-🔴 Not Covered (7 functions, 22 lines)
- L159 CalculatorService.deleteLastDigit() — 0/16 lines
- L58 implicit closure #2 in inputNumber(_:) — 0/1 lines
-
-🟡 Partial Coverage (4 functions)
- L184 updateExpressionDisplay() — 80.0% (8/10 lines)
- L195 formatNumber(_:) — 85.7% (18/21 lines)
-
-🟢 Full Coverage (28 functions) — all at 100%
-
-Next steps:
-1. View overall coverage: xcodebuildmcp coverage get-coverage-report --xcresult-path "/tmp/TestResults.xcresult"
-```
-
-#### List Simulators (generic pipeline — new)
-
-```text
-📱 List Simulators
-
-iOS 18.5:
- iPhone 16 Pro A1B2C3D4-... Booted
- iPhone 16 E5F6G7H8-... Shutdown
- iPad Pro 13" I9J0K1L2-... Shutdown
-
-iOS 17.5:
- iPhone 15 M3N4O5P6-... Shutdown
-
-Next steps:
-1. Boot simulator: xcodebuildmcp simulator-management boot --simulator-id "UUID"
-```
-
-#### Set Appearance (generic pipeline — new)
-
-```text
-🎨 Set Appearance
-
- Simulator: A1B2C3D4-E5F6-...
-
-✅ Appearance set to dark mode
-```
-
-#### Discover Projects (generic pipeline — new)
-
-```text
-🔍 Discover Projects
-
- Search Path: .
-
-Workspaces:
- example_projects/iOS_Calculator/CalculatorApp.xcworkspace
-
-Projects:
- example_projects/iOS_Calculator/CalculatorApp.xcodeproj
-
-Next steps:
-1. List schemes: xcodebuildmcp project-discovery list-schemes --workspace-path "example_projects/iOS_Calculator/CalculatorApp.xcworkspace"
-```
-
-## Xcodebuild pipeline specifics
-
-The existing xcodebuild pipeline architecture is preserved. This section documents it for reference.
-
-### Execution flow
-
-1. Tool calls `startBuildPipeline(...)` from `src/utils/xcodebuild-pipeline.ts`
-2. Pipeline creates parser and run-state, emits initial `start` event
-3. Raw stdout/stderr chunks feed into `createXcodebuildEventParser(...)`
-4. Parser emits structured events into `createXcodebuildRunState(...)`
-5. Tool-emitted events (post-build notices, errors) enter run-state through `pipeline.emitEvent(...)`
-6. Run-state dedupes, orders, aggregates, forwards to the unified renderer
-7. On finalize: summary + tail events + next-steps emitted in order
-
-### Canonical pattern
-
-```ts
-const started = startBuildPipeline({
- operation: 'BUILD',
- toolName: 'build_run_',
- params: { scheme, configuration, platform, preflight: preflightText },
- message: preflightText,
-});
-
-const buildResult = await executeXcodeBuildCommand(..., started.pipeline);
-if (buildResult.isError) {
- return createPendingXcodebuildResponse(started, buildResult, {
- errorFallbackPolicy: 'if-no-structured-diagnostics',
- });
-}
-
-// Post-build steps: emit notices for progress, errors for failures
-emitPipelineNotice(started, 'BUILD', 'Resolving app path', 'info', {
- code: 'build-run-step',
- data: { step: 'resolve-app-path', status: 'started' },
-});
-
-// ... resolve, boot, install, launch ...
-
-return createPendingXcodebuildResponse(
- started,
- { content: [], isError: false, nextStepParams: { ... } },
- {
- tailEvents: [{
- type: 'notice',
- timestamp: new Date().toISOString(),
- operation: 'BUILD',
- level: 'success',
- message: 'Build & Run complete',
- code: 'build-run-result',
- data: { scheme, platform, target, appPath, bundleId, launchState: 'requested' },
- }],
- },
-);
-```
-
-### Pending response lifecycle
-
-1. Tool returns `createPendingXcodebuildResponse(started, response, options)`
-2. `postProcessToolResponse` in `src/runtime/tool-invoker.ts` detects the pending state
-3. Resolves manifest-driven next-step templates against `nextStepParams`
-4. Calls `finalizePendingXcodebuildResponse` which finalizes the pipeline
-5. Finalized content becomes `ToolResponse.content`
-
-### Post-build step notices
-
-Post-build steps use `notice` events with `code: 'build-run-step'`:
-
-Available step names (defined in `BuildRunStepName` in `src/types/xcodebuild-events.ts`):
-
-- `resolve-app-path`
-- `resolve-simulator`
-- `boot-simulator`
-- `install-app`
-- `extract-bundle-id`
-- `launch-app`
-
-To add new steps: extend `BuildRunStepName` and add the label in `formatBuildRunStepLabel` in `src/utils/renderers/event-formatting.ts`.
-
-### Error message convention
-
-All post-build errors via `emitPipelineError` use: `Failed to : `
-
-### All errors get grouped rendering
-
-All error events are batched and rendered as a single grouped section before the summary:
-
-- If any error has a file location: `Compiler Errors (N):`
-- Otherwise: `Errors (N):`
-
-Each error: ` ✗ ` with optional ` ` and continuation lines.
-
-### Error event message field
-
-The `message` field must not include severity prefix. Correct: `"unterminated string literal"`. Wrong: `"error: unterminated string literal"`. The `rawLine` field preserves the original verbatim.
-
-## Implementation steps
-
-One canonical list. Checked items are done. Remaining items are work-in-progress.
-
-### Infrastructure (done)
-
-- [x] Define `PipelineEvent` union type in `src/types/pipeline-events.ts` (named `PipelineEvent`, not `ToolEvent`)
-- [x] Define `toolResponse()` builder + helper functions: `header()`, `section()`, `statusLine()`, `fileRef()`, `table()`, `detailTree()`, `nextSteps()` in `src/utils/tool-event-builders.ts`
-- [x] Build shared formatting layer in `src/utils/renderers/event-formatting.ts`
-- [x] Build MCP renderer (`src/utils/renderers/mcp-renderer.ts`) — buffers formatted text for `ToolResponse.content`
-- [x] Build CLI text renderer (`src/utils/renderers/cli-text-renderer.ts`) — streaming text to stdout with interactive spinner support
-- [x] Preserve CLI JSONL renderer (`src/utils/renderers/cli-jsonl-renderer.ts`) for machine-readable output
-- [x] Build `resolveRenderers()` orchestration in `src/utils/renderers/index.ts`
-- [x] Build `toolResponse()` entry point in `src/utils/tool-response.ts` that feeds events through renderers
-- [x] Migrate xcodebuild pipeline run-state to emit `PipelineEvent` types through renderers (preserve parser, run-state, streaming, Clack)
-- [x] Write designed fixtures for all tools (`__fixtures_designed__/`)
-
-### Tool migration (mostly done)
-
-- [x] Migrate xcodebuild tools: `build_sim`, `build_device`, `build_macos`, `build_run_sim`, `build_run_device`, `build_run_macos`
-- [x] Migrate simple action tools: `set_sim_appearance`, `set_sim_location`, `reset_sim_location`, `sim_statusbar`, `boot_sim`, `open_sim`, `stop_app_sim`, `stop_app_device`, `stop_mac_app`, `launch_app_sim`, `launch_app_device`, `launch_mac_app`, `install_app_sim`, `install_app_device`
-- [x] Migrate most query tools: `list_sims`, `discover_projs`, `list_schemes`, `show_build_settings`, `get_app_bundle_id`, `get_mac_bundle_id`
-- [x] Migrate coverage tools: `get_coverage_report`, `get_file_coverage`
-- [x] Migrate scaffolding tools: `scaffold_ios_project`, `scaffold_macos_project`
-- [x] Migrate session tools: `session_set_defaults`, `session_clear_defaults`, `session_use_defaults_profile`
-- [x] Migrate logging tools: `start_sim_log_cap`, `stop_sim_log_cap`, `start_device_log_cap`, `stop_device_log_cap`
-- [x] Migrate debugging tools: `debug_attach_sim`, `debug_breakpoint_add`, `debug_breakpoint_remove`, `debug_continue`, `debug_detach`, `debug_lldb_command`, `debug_stack`, `debug_variables`
-- [x] Migrate UI automation tools: `snapshot_ui`, `tap`, `type_text`, `button`, `gesture`, `key_press`, `key_sequence`, `long_press`, `swipe`, `touch`
-- [x] Migrate swift-package tools: `swift_package_build`, `swift_package_clean`, `swift_package_list`, `swift_package_stop`
-- [x] Migrate xcode-ide tools: `xcode_ide_call_tool`, `xcode_ide_list_tools`, `xcode_tools_bridge_disconnect`, `xcode_tools_bridge_status`, `xcode_tools_bridge_sync`, `sync_xcode_defaults`
-- [x] Migrate doctor tool
-
-### Remaining: tools that were migrated then reverted to manual text
-
-These tools were migrated to the pipeline in `ac33b97f` but reverted to manual `ToolResponse` construction in `c0693a1d`. The fixtures in `__fixtures__/` define the correct target output. The pipeline (renderers and/or event types) needs to be extended to produce that output — the tools should NOT hand-craft text to match fixtures.
-
-- [x] Re-migrate `get_sim_app_path` — extended `SectionEvent` with `blankLineAfterTitle`, added `extractQueryErrorMessages`, added `suppressCliStream` to `toolResponse()` for late-bound CLI next steps
-- [x] Re-migrate `get_device_app_path` — same approach
-- [x] Re-migrate `get_mac_app_path` — same approach
-- [x] Re-migrate `list_devices` success path — uses `blankLineAfterTitle` sections for grouped-by-platform layout
-- [x] Clean up `swift_package_run` error fallback — removed manual content, relies on pipeline-produced structured diagnostics
-- [x] Clean up `swift_package_test` error fallback — same
-- [ ] Re-migrate `session_show_defaults` — remove inline emoji from section titles, use `detailTree()` instead of manual tree connectors
-- [ ] Re-migrate `screenshot` — remove manual content branches for base64 fallback
-
-### Remaining: presentation leakage in migrated tools
-
-These tools use `toolResponse()` but embed presentation details in event payloads that should be owned by the renderer:
-
-- [ ] `list_sims` — remove inline emoji and `✓`/`✗` markers from section content; these should come from the renderer or event type metadata
-- [ ] `session_show_defaults` — use `detailTree()` events instead of `formatDetailLines()` manual tree connectors
-
-### Remaining: cleanup
-
-- [ ] Delete `formatToolPreflight` in `src/utils/build-preflight.ts` once all tools use pipeline `HeaderEvent`
-- [ ] All snapshot tests pass against `__fixtures__/` (target output)
-- [ ] Manual verification of CLI output for representative tools
-
-## Success criteria
-
-This work is successful when:
-
-- every tool emits structured events through the pipeline
-- shared formatting functions in `event-formatting.ts` produce all formatted output
-- CLI and MCP durable output are identical (CLI interactive mode may show transient spinner updates)
-- file paths are always normalised — no tool can produce a raw absolute path
-- spacing between sections is always correct — no tool can get it wrong
-- the only way to add a new tool's output is to emit events — there is no escape hatch
-- adding a new output format (e.g. markdown, HTML) requires only a new renderer, not touching any tool code
-- all `__fixtures__/` snapshot tests pass with output produced by the pipeline, not by manual text construction
-
-## Design constraints
-
-- all text formatting lives in `event-formatting.ts` — renderers orchestrate, they do not contain formatting logic
-- no formatted text construction inside tool logic
-- no emoji characters inside tool logic (formatting layer owns the mapping)
-- no `displayPath()` calls inside tool logic (formatting layer owns path normalisation)
-- no spacing/indentation decisions inside tool logic (formatting layer owns layout)
-- xcodebuild event parser and run-state layer are preserved — they work well and do not need to change
-- CLI JSONL mode is preserved for all tools
-- no attempt to make non-xcodebuild tools streamable initially — they complete fast enough that buffered rendering is fine
-- if the pipeline cannot produce a fixture's target output, extend the pipeline (new event types, new formatting functions) — do not bypass the pipeline to match fixtures manually
diff --git a/docs/dev/TESTING.md b/docs/dev/TESTING.md
deleted file mode 100644
index f83534302..000000000
--- a/docs/dev/TESTING.md
+++ /dev/null
@@ -1,1278 +0,0 @@
-# XcodeBuildMCP Plugin Testing Guidelines
-
-This document provides comprehensive testing guidelines for XcodeBuildMCP plugins, ensuring consistent, robust, and maintainable test coverage across the entire codebase.
-
-## Table of Contents
-
-1. [Testing Philosophy](#testing-philosophy)
-2. [Test Architecture](#test-architecture)
-3. [Dependency Injection Strategy](#dependency-injection-strategy)
-4. [Three-Dimensional Testing](#three-dimensional-testing)
-5. [Test Organization](#test-organization)
-6. [Test Patterns](#test-patterns)
-7. [Performance Requirements](#performance-requirements)
-8. [Coverage Standards](#coverage-standards)
-9. [Common Patterns](#common-patterns)
-10. [Manual Testing with Reloaderoo](#manual-testing-with-reloaderoo)
-11. [Troubleshooting](#troubleshooting)
-
-## Testing Philosophy
-
-### 🚨 CRITICAL: External Dependency Mocking Rules
-
-### When to use dependency-injection executors
-
-`CommandExecutor` / `FileSystemExecutor` DI is required for **MCP tool logic functions** that orchestrate complex, long-running processes with sub-processes (e.g., `xcodebuild`, multi-step build pipelines). Standard vitest mocking produces race conditions with these because sub-process ordering is non-deterministic.
-
-- `createMockExecutor()` / `createNoopExecutor()` for command execution in tool logic
-- `createMockFileSystemExecutor()` / `createNoopFileSystemExecutor()` for file system interactions in tool logic
-
-### When standard vitest mocking is fine
-
-Standalone utility modules that invoke simple, short-lived commands (e.g., `xcrun devicectl list`, `xcrun xcresulttool get`) may use direct `child_process`/`fs` imports and be tested with standard vitest mocking (`vi.fn`, `vi.mock`, `vi.spyOn`, etc.). This is simpler and perfectly adequate for deterministic, single-command utilities.
-
-### Internal mocking guidance:
-- Vitest mocking (`vi.fn`, `vi.mock`, `vi.spyOn`, `.mockResolvedValue`, etc.) is allowed for internal modules and in-memory collaborators
-- Prefer straightforward, readable test doubles over over-engineered mocks
-
-### Still forbidden:
-- Hitting real external systems in unit tests (real `xcodebuild`, `xcrun`, AXe, filesystem writes/reads outside test harness)
-
-### OUR CORE PRINCIPLE
-
-**Simple Rule**: Use dependency-injection mock executors for complex process orchestration in tool logic; use standard vitest mocking for simple utility modules and internal behavior.
-
-**Why This Rule Exists**:
-1. **Reliability**: Complex multi-process orchestration stays deterministic and hermetic via DI executors
-2. **Simplicity**: Simple utilities use standard vitest mocking without unnecessary abstraction
-3. **Architectural Enforcement**: External boundaries for complex processes are explicit in tool logic signatures
-4. **Maintainability**: Tests fail for behavior regressions, not incidental environment differences
-
-### Integration Testing with Dependency Injection
-
-XcodeBuildMCP follows a dependency-injection testing philosophy for external boundaries:
-
-- ✅ **Test plugin interfaces** (public API contracts)
-- ✅ **Test integration flows** (plugin → utilities → external tools)
-- ✅ **Use dependency injection** with createMockExecutor()/createMockFileSystemExecutor() for external dependencies
-- ✅ **Use Vitest mocking when needed** for internal modules and collaborators
-
-### Benefits
-
-1. **Implementation Independence**: Internal refactoring doesn't break tests
-2. **Real Coverage**: Tests verify actual user data flows
-3. **Maintainability**: No brittle vitest mocks that break on implementation changes
-4. **True Integration**: Catches integration bugs between layers
-5. **Test Safety**: A Vitest setup file installs blocking executor overrides for unit tests
-
-### Automated Violation Checking
-
-To enforce external-boundary testing policy, the project includes a script that checks for architectural test-pattern violations.
-
-```bash
-# Run the script to check for violations
-node scripts/check-code-patterns.js
-```
-
-This script is part of the standard development workflow and should be run before committing changes to ensure compliance with the testing standards.
-
-### What the Script Flags vs. What It Should NOT Flag
-
-#### ✅ LEGITIMATE VIOLATIONS (correctly flagged):
-- Manual mock executors: `const mockExecutor = async (...) => { ... }`
-- Manual filesystem mocks: `const mockFsDeps = { readFile: async () => ... }`
-- Manual server mocks: `const mockServer = { ... }`
-- External side-effect patterns that bypass injected executors/filesystem dependencies
-
-#### ❌ FALSE POSITIVES (should NOT be flagged):
-- Test data tracking: `commandCalls.push({ ... })` - This is just collecting test data, not mocking behavior
-- Regular variables: `const testData = { ... }` - Non-mocking object assignments
-- Test setup: Regular const assignments that don't implement mock behavior
-
-The script has been refined to minimize false positives while catching all legitimate violations of our core rule.
-
-## Test Architecture
-
-### Correct Test Flow
-```
-Test → Plugin Handler → utilities → [DEPENDENCY INJECTION] createMockExecutor()
-```
-
-### What Gets Tested
-- Plugin parameter validation
-- Business logic execution
-- Command generation
-- Response formatting
-- Error handling
-- Integration between layers
-
-### What Gets Mocked
-- Command execution via `createMockExecutor()`
-- File system operations via `createMockFileSystemExecutor()`
-- Internal modules can use Vitest mocks where appropriate
-
-## Dependency Injection Strategy
-
-### Handler Requirements
-
-MCP tool logic functions that orchestrate complex processes must support dependency injection:
-
-```typescript
-export function tool_nameLogic(
- args: Record,
- commandExecutor: CommandExecutor,
- fileSystemExecutor?: FileSystemExecutor
-): Promise {
- // Use injected executors
- const result = await executeCommand(['xcrun', 'simctl', 'list'], commandExecutor);
- return createTextResponse(result.output);
-}
-
-export default {
- name: 'tool_name',
- description: 'Tool description',
- schema: { /* zod schema */ },
- async handler(args: Record): Promise {
- return tool_nameLogic(args, getDefaultCommandExecutor(), getDefaultFileSystemExecutor());
- },
-};
-```
-
-**Important**: The dependency injection pattern applies to tool and resource handler logic that orchestrates complex, long-running processes (e.g., `xcodebuild`). Standalone utility modules with simple commands may use direct imports and standard vitest mocking.
-
-Always use default parameter values (e.g., `= getDefaultCommandExecutor()`) in tool logic to ensure production code works without explicit executor injection, while tests can override with mock executors.
-
-### Test Requirements
-
-All tests must explicitly provide mock executors:
-
-```typescript
-it('should handle successful command execution', async () => {
- const mockExecutor = createMockExecutor({
- success: true,
- output: 'BUILD SUCCEEDED'
- });
-
- const result = await tool_nameLogic(
- { projectPath: '/test.xcodeproj', scheme: 'MyApp' },
- mockExecutor
- );
-
- expect(result.content[0].text).toContain('Build succeeded');
-});
-```
-
-## Three-Dimensional Testing
-
-Every plugin test suite must validate three critical dimensions:
-
-### 1. Input Validation (Schema Testing)
-
-Test parameter validation and schema compliance:
-
-```typescript
-describe('Parameter Validation', () => {
- it('should accept valid parameters', () => {
- const schema = z.object(tool.schema);
- expect(schema.safeParse({
- projectPath: '/valid/path.xcodeproj',
- scheme: 'ValidScheme'
- }).success).toBe(true);
- });
-
- it('should reject invalid parameters', () => {
- const schema = z.object(tool.schema);
- expect(schema.safeParse({
- projectPath: 123, // Wrong type
- scheme: 'ValidScheme'
- }).success).toBe(false);
- });
-
- it('should handle missing required parameters', async () => {
- const mockExecutor = createMockExecutor({ success: true });
-
- const result = await tool.handler({ scheme: 'MyApp' }, mockExecutor); // Missing projectPath
-
- expect(result).toEqual({
- content: [{
- type: 'text',
- text: "Required parameter 'projectPath' is missing. Please provide a value for this parameter."
- }],
- isError: true
- });
- });
-});
-```
-
-### 2. Command Generation (CLI Testing)
-
-### CRITICAL: No command spying allowed. Test command generation through response validation.
-
-```typescript
-describe('Command Generation', () => {
- it('should execute correct command with minimal parameters', async () => {
- const mockExecutor = createMockExecutor({
- success: true,
- output: 'BUILD SUCCEEDED'
- });
-
- const result = await tool.handler({
- projectPath: '/test.xcodeproj',
- scheme: 'MyApp'
- }, mockExecutor);
-
- // Verify through successful response - command was executed correctly
- expect(result.content[0].text).toContain('Build succeeded');
- });
-
- it('should handle paths with spaces correctly', async () => {
- const mockExecutor = createMockExecutor({
- success: true,
- output: 'BUILD SUCCEEDED'
- });
-
- const result = await tool.handler({
- projectPath: '/Users/dev/My Project/app.xcodeproj',
- scheme: 'MyApp'
- }, mockExecutor);
-
- // Verify successful execution (proper path handling)
- expect(result.content[0].text).toContain('Build succeeded');
- });
-});
-```
-
-### 3. Output Processing (Response Testing)
-
-Test response formatting and error handling:
-
-```typescript
-describe('Response Processing', () => {
- it('should format successful response', async () => {
- const mockExecutor = createMockExecutor({
- success: true,
- output: 'BUILD SUCCEEDED'
- });
-
- const result = await tool.handler({ projectPath: '/test', scheme: 'MyApp' }, mockExecutor);
-
- expect(result).toEqual({
- content: [{ type: 'text', text: '✅ Build succeeded for scheme MyApp' }]
- });
- });
-
- it('should handle command failures', async () => {
- const mockExecutor = createMockExecutor({
- success: false,
- output: 'Build failed with errors',
- error: 'Compilation error'
- });
-
- const result = await tool.handler({ projectPath: '/test', scheme: 'MyApp' }, mockExecutor);
-
- expect(result.isError).toBe(true);
- expect(result.content[0].text).toContain('Build failed');
- });
-
- it('should handle executor errors', async () => {
- const mockExecutor = createMockExecutor(new Error('spawn xcodebuild ENOENT'));
-
- const result = await tool.handler({ projectPath: '/test', scheme: 'MyApp' }, mockExecutor);
-
- expect(result).toEqual({
- content: [{ type: 'text', text: 'Error during build: spawn xcodebuild ENOENT' }],
- isError: true
- });
- });
-});
-```
-
-## Test Organization
-
-### Directory Structure
-
-```
-src/plugins/[workflow-group]/
-├── __tests__/
-│ ├── index.test.ts # Workflow metadata tests (canonical groups only)
-│ ├── re-exports.test.ts # Re-export validation (project/workspace groups only)
-│ ├── tool1.test.ts # Individual tool tests
-│ ├── tool2.test.ts
-│ └── ...
-├── tool1.ts
-├── tool2.ts
-├── index.ts # Workflow metadata
-└── ...
-```
-
-### Test File Types
-
-#### 1. Tool Tests (`tool_name.test.ts`)
-Test individual plugin tools with full three-dimensional coverage.
-
-#### 2. Workflow Tests (`index.test.ts`)
-Test workflow metadata for canonical groups:
-
-```typescript
-describe('simulator-workspace workflow metadata', () => {
- it('should have correct workflow name', () => {
- expect(workflow.name).toBe('iOS Simulator Workspace Development');
- });
-
- it('should have correct description', () => {
- expect(workflow.description).toBe(
- 'Complete iOS development workflow for .xcworkspace files including build, test, deploy, and debug capabilities',
- );
- });
-});
-```
-
-#### 3. Re-export Tests (`re-exports.test.ts`)
-Test re-export integrity for project/workspace groups:
-
-```typescript
-describe('simulator-project re-exports', () => {
- it('should re-export boot_sim from simulator-shared', () => {
- expect(bootSim.name).toBe('boot_sim');
- expect(typeof bootSim.handler).toBe('function');
- });
-});
-```
-
-## Test Patterns
-
-### Standard Test Template
-
-```typescript
-import { vi, describe, it, expect, beforeEach } from 'vitest';
-import { z } from 'zod';
-
-// Use dependency-injection mocks for external boundaries.
-// Vitest mocks are acceptable for internal collaborators when needed.
-
-import tool from '../tool_name.ts';
-import { createMockExecutor } from '../../utils/command.js';
-
-describe('tool_name', () => {
-
- describe('Export Field Validation (Literal)', () => {
- it('should export correct name', () => {
- expect(tool.name).toBe('tool_name');
- });
-
- it('should export correct description', () => {
- expect(tool.description).toBe('Expected literal description');
- });
-
- it('should export handler function', () => {
- expect(typeof tool.handler).toBe('function');
- });
-
- // Schema validation tests...
- });
-
- describe('Command Generation', () => {
- it('should execute commands successfully', async () => {
- const mockExecutor = createMockExecutor({
- success: true,
- output: 'Expected output'
- });
-
- const result = await tool.handler(validParams, mockExecutor);
-
- expect(result.content[0].text).toContain('Expected result');
- });
- });
-
- describe('Response Processing', () => {
- // Output handling tests...
- });
-});
-```
-
-## Performance Requirements
-
-### Test Execution Speed
-
-- **Individual test**: < 100ms
-- **Test file**: < 5 seconds
-- **Full test suite**: < 20 seconds
-- **No real system calls**: Tests must use mocks
-
-### Performance Anti-Patterns
-
-❌ **Real command execution**:
-```
-[INFO] Executing command: xcodebuild -showBuildSettings...
-```
-
-❌ **Long timeouts** (indicates real calls)
-❌ **File system operations** (unless testing file utilities)
-❌ **Network requests** (unless testing network utilities)
-
-## Coverage Standards
-
-### Target Coverage
-- **Overall**: 95%+
-- **Plugin handlers**: 100%
-- **Command generation**: 100%
-- **Error paths**: 100%
-
-### Coverage Validation
-```bash
-# Check coverage for specific plugin group
-npm run test:coverage -- plugins/simulator-workspace/
-
-# Ensure all code paths are tested
-npm run test:coverage -- --reporter=lcov
-```
-
-### Required Test Paths
-
-Every plugin test must cover:
-
-- ✅ **Valid parameter combinations**
-- ✅ **Invalid parameter rejection**
-- ✅ **Missing required parameters**
-- ✅ **Successful command execution**
-- ✅ **Command failure scenarios**
-- ✅ **Executor error handling**
-- ✅ **Output parsing edge cases**
-
-## Common Patterns
-
-### Testing Parameter Defaults
-
-```typescript
-it('should use default configuration when not provided', async () => {
- const mockExecutor = createMockExecutor({
- success: true,
- output: 'BUILD SUCCEEDED'
- });
-
- const result = await tool.handler({
- projectPath: '/test.xcodeproj',
- scheme: 'MyApp'
- // configuration intentionally omitted
- }, mockExecutor);
-
- // Verify default behavior through successful response
- expect(result.content[0].text).toContain('Build succeeded');
-});
-```
-
-### Testing Complex Output Parsing
-
-```typescript
-it('should extract app path from build settings', async () => {
- const mockExecutor = createMockExecutor({
- success: true,
- output: `
- CONFIGURATION_BUILD_DIR = /path/to/build
- BUILT_PRODUCTS_DIR = /path/to/products
- FULL_PRODUCT_NAME = MyApp.app
- OTHER_SETTING = ignored_value
- `
- });
-
- const result = await tool.handler({ projectPath: '/test', scheme: 'MyApp' }, mockExecutor);
-
- expect(result.content[0].text).toContain('/path/to/products/MyApp.app');
-});
-```
-
-### Testing Error Message Formatting
-
-```typescript
-it('should format validation errors correctly', async () => {
- const mockExecutor = createMockExecutor({ success: true });
-
- const result = await tool.handler({}, mockExecutor); // Missing required params
-
- expect(result).toEqual({
- content: [{
- type: 'text',
- text: "Required parameter 'projectPath' is missing. Please provide a value for this parameter."
- }],
- isError: true
- });
-});
-```
-
-## Manual Testing with Reloaderoo
-
-### 🚨 CRITICAL: THOROUGHNESS OVER EFFICIENCY - NO SHORTCUTS ALLOWED
-
-### ABSOLUTE PRINCIPLE: EVERY TOOL MUST BE TESTED INDIVIDUALLY
-
-### 🚨 MANDATORY TESTING SCOPE - NO EXCEPTIONS
-- **EVERY SINGLE TOOL** - All 83+ tools must be tested individually, one by one
-- **NO REPRESENTATIVE SAMPLING** - Testing similar tools does NOT validate other tools
-- **NO PATTERN RECOGNITION SHORTCUTS** - Similar-looking tools may have different behaviors
-- **NO EFFICIENCY OPTIMIZATIONS** - Thoroughness is more important than speed
-- **NO TIME CONSTRAINTS** - This is a long-running task with no deadline pressure
-
-### ❌ FORBIDDEN EFFICIENCY SHORTCUTS
-- **NEVER** assume testing `build_sim_id_proj` validates `build_sim_name_proj`
-- **NEVER** skip tools because they "look similar" to tested ones
-- **NEVER** use representative sampling instead of complete coverage
-- **NEVER** stop testing due to time concerns or perceived redundancy
-- **NEVER** group tools together for batch testing
-- **NEVER** make assumptions about untested tools based on tested patterns
-
-### ✅ REQUIRED COMPREHENSIVE APPROACH
-1. **Individual Tool Testing**: Each tool gets its own dedicated test execution
-2. **Complete Documentation**: Every tool result must be recorded, regardless of outcome
-3. **Systematic Progress**: Use TodoWrite to track every single tool as tested/untested
-4. **Failure Documentation**: Test tools that cannot work and mark them as failed/blocked
-5. **No Assumptions**: Treat each tool as potentially unique requiring individual validation
-
-### TESTING COMPLETENESS VALIDATION
-- **Start Count**: Record exact number of tools discovered (e.g., 83 tools)
-- **End Count**: Verify same number of tools have been individually tested
-- **Missing Tools = Testing Failure**: If any tools remain untested, the testing is incomplete
-- **TodoWrite Tracking**: Every tool must appear in todo list and be marked completed
-
-### 🚨 CRITICAL: Black Box Testing via Reloaderoo Inspect
-
-### DEFINITION: Black Box Testing
-Black Box Testing means testing ONLY through external interfaces without any knowledge of internal implementation. For XcodeBuildMCP, this means testing exclusively through the Model Context Protocol (MCP) interface using Reloaderoo as the MCP client.
-
-### 🚨 MANDATORY: RELOADEROO INSPECT IS THE ONLY ALLOWED TESTING METHOD
-
-### ABSOLUTE TESTING RULES - NO EXCEPTIONS
-
-1. **✅ ONLY ALLOWED: Reloaderoo Inspect Commands**
- - `npx reloaderoo@latest inspect call-tool "TOOL_NAME" --params 'JSON' -- node build/cli.js mcp`
- - `npx reloaderoo@latest inspect list-tools -- node build/cli.js mcp`
- - `npx reloaderoo@latest inspect read-resource "URI" -- node build/cli.js mcp`
- - `npx reloaderoo@latest inspect server-info -- node build/cli.js mcp`
- - `npx reloaderoo@latest inspect ping -- node build/cli.js mcp`
-
-2. **❌ COMPLETELY FORBIDDEN ACTIONS:**
- - **NEVER** call `mcp__XcodeBuildMCP__tool_name()` functions directly
- - **NEVER** use MCP server tools as if they were native functions
- - **NEVER** access internal server functionality
- - **NEVER** read source code to understand how tools work
- - **NEVER** examine implementation files during testing
- - **NEVER** diagnose internal server issues or registration problems
- - **NEVER** suggest code fixes or implementation changes
-
-3. **🚨 CRITICAL VIOLATION EXAMPLES:**
- ```typescript
- // ❌ FORBIDDEN - Direct MCP tool calls
- await mcp__XcodeBuildMCP__list_devices();
- await mcp__XcodeBuildMCP__build_sim_id_proj({ ... });
-
- // ❌ FORBIDDEN - Using tools as native functions
- const devices = await list_devices();
- const result = await doctor();
-
- // ✅ CORRECT - Only through Reloaderoo inspect
- npx reloaderoo@latest inspect call-tool "list_devices" --params '{}' -- node build/cli.js mcp
- npx reloaderoo@latest inspect call-tool "doctor" --params '{}' -- node build/cli.js mcp
- ```
-
-### WHY RELOADEROO INSPECT IS MANDATORY
-- **Higher Fidelity**: Provides clear input/output visibility for each tool call
-- **Real-world Simulation**: Tests exactly how MCP clients interact with the server
-- **Interface Validation**: Ensures MCP protocol compliance and proper JSON formatting
-- **Black Box Enforcement**: Prevents accidental access to internal implementation details
-- **Clean State**: Each tool call runs with a fresh MCP server instance, preventing cross-contamination
-
-### IMPORTANT: STATEFUL TOOL LIMITATIONS
-
-#### Reloaderoo Inspect Behavior:
-Reloaderoo starts a fresh MCP server instance for each individual tool call and terminates it immediately after the response. This ensures:
-- ✅ **Clean Testing Environment**: No state contamination between tool calls
-- ✅ **Isolated Testing**: Each tool test is independent and repeatable
-- ✅ **Real-world Accuracy**: Simulates how most MCP clients interact with servers
-
-#### Expected False Negatives:
-Some tools rely on in-memory state within the MCP server and will fail when tested via Reloaderoo inspect. These failures are **expected and acceptable** as false negatives:
-
-- **`swift_package_stop`** - Requires in-memory process tracking from `swift_package_run`
-- **`stop_app_device`** - Requires in-memory process tracking from `launch_app_device`
-- **`stop_app_sim`** - Requires in-memory process tracking from `launch_app_sim`
-- **`stop_device_log_cap`** - Requires in-memory session tracking from `start_device_log_cap`
-- **`stop_sim_log_cap`** - Requires in-memory session tracking from `start_sim_log_cap`
-- **`stop_mac_app`** - Requires in-memory process tracking from `launch_mac_app`
-
-#### Testing Protocol for Stateful Tools:
-1. **Test the tool anyway** - Execute the Reloaderoo inspect command
-2. **Expect failure** - Tool will likely fail due to missing state
-3. **Mark as false negative** - Document the failure as expected due to stateful limitations
-4. **Continue testing** - Do not attempt to fix or investigate the failure
-5. **Report as finding** - Note in testing report that stateful tools failed as expected
-
-### COMPLETE COVERAGE REQUIREMENTS
-- ✅ **Test ALL 83+ tools individually** - No exceptions, every tool gets manual verification
-- ✅ **Follow dependency graphs** - Test tools in correct order based on data dependencies
-- ✅ **Capture key outputs** - Record UUIDs, paths, schemes needed by dependent tools
-- ✅ **Test real workflows** - Complete end-to-end workflows from discovery to execution
-- ✅ **Use programmatic JSON parsing** - Accurate tool/resource counting and discovery
-- ✅ **Document all observations** - Record exactly what you see via testing
-- ✅ **Report discrepancies as findings** - Note unexpected results without investigation
-
-### MANDATORY INDIVIDUAL TOOL TESTING PROTOCOL
-
-#### Step 1: Create Complete Tool Inventory
-```bash
-# Generate complete list of all tools
-npx reloaderoo@latest inspect list-tools -- node build/cli.js mcp > /tmp/all_tools.json
-TOTAL_TOOLS=$(jq '.tools | length' /tmp/all_tools.json)
-echo "TOTAL TOOLS TO TEST: $TOTAL_TOOLS"
-
-# Extract all tool names for systematic testing
-jq -r '.tools[].name' /tmp/all_tools.json > /tmp/tool_names.txt
-```
-
-#### Step 2: Create TodoWrite Task List for Every Tool
-```bash
-# Create individual todo items for each of the 83+ tools
-# Example for first few tools:
-# 1. [ ] Test tool: doctor
-# 2. [ ] Test tool: list_devices
-# 3. [ ] Test tool: list_sims
-# ... (continue for ALL 83+ tools)
-```
-
-#### Step 3: Test Each Tool Individually
-For EVERY tool in the list:
-```bash
-# Test each tool individually - NO BATCHING
-npx reloaderoo@latest inspect call-tool "TOOL_NAME" --params 'APPROPRIATE_PARAMS' -- node build/cli.js mcp
-
-# Mark tool as completed in TodoWrite IMMEDIATELY after testing
-# Record result (success/failure/blocked) for each tool
-```
-
-#### Step 4: Validate Complete Coverage
-```bash
-# Verify all tools tested
-COMPLETED_TOOLS=$(count completed todo items)
-if [ $COMPLETED_TOOLS -ne $TOTAL_TOOLS ]; then
- echo "ERROR: Testing incomplete. $COMPLETED_TOOLS/$TOTAL_TOOLS tested"
- exit 1
-fi
-```
-
-### CRITICAL: NO TOOL LEFT UNTESTED
-- **Every tool name from the JSON list must be individually tested**
-- **Every tool must have a TodoWrite entry that gets marked completed**
-- **Tools that fail due to missing parameters should be tested anyway and marked as blocked**
-- **Tools that require setup (like running processes) should be tested and documented as requiring dependencies**
-- **NO ASSUMPTIONS**: Test tools even if they seem redundant or similar to others
-
-### BLACK BOX TESTING ENFORCEMENT
-- ✅ **Test only through Reloaderoo MCP interface** - Simulates real-world MCP client usage
-- ✅ **Use task lists** - Track progress with TodoWrite tool for every single tool
-- ✅ **Tick off each tool** - Mark completed in task list after manual verification
-- ✅ **Manual oversight** - Human verification of each tool's input and output
-- ❌ **Never examine source code** - No reading implementation files during testing
-- ❌ **Never diagnose internal issues** - No investigation of build processes or tool registration
-- ❌ **Never suggest implementation fixes** - Report issues as findings, don't solve them
-- ❌ **Never use scripts for tool testing** - Each tool must be manually executed and verified
-
-### 🚨 TESTING PSYCHOLOGY & BIAS PREVENTION
-
-### COMMON ANTI-PATTERNS TO AVOID
-
-#### 1. Efficiency Bias (FORBIDDEN)
-- **Symptom**: "These tools look similar, I'll test one to validate the others"
-- **Correction**: Every tool is unique and must be tested individually
-- **Enforcement**: Count tools at start, verify same count tested at end
-
-#### 2. Pattern Recognition Override (FORBIDDEN)
-- **Symptom**: "I see the pattern, the rest will work the same way"
-- **Correction**: Patterns may hide edge cases, bugs, or different implementations
-- **Enforcement**: No assumptions allowed, test every tool regardless of apparent similarity
-
-#### 3. Time Pressure Shortcuts (FORBIDDEN)
-- **Symptom**: "This is taking too long, let me speed up by sampling"
-- **Correction**: This is explicitly a long-running task with no time constraints
-- **Enforcement**: Thoroughness is the ONLY priority, efficiency is irrelevant
-
-#### 4. False Confidence (FORBIDDEN)
-- **Symptom**: "The architecture is solid, so all tools must work"
-- **Correction**: Architecture validation does not guarantee individual tool functionality
-- **Enforcement**: Test tools to discover actual issues, not to confirm assumptions
-
-### MANDATORY MINDSET
-- **Every tool is potentially broken** until individually tested
-- **Every tool may have unique edge cases** not covered by similar tools
-- **Every tool deserves individual attention** regardless of apparent redundancy
-- **Testing completion means EVERY tool tested**, not "enough tools to validate patterns"
-- **The goal is discovering problems**, not confirming everything works
-
-### TESTING COMPLETENESS CHECKLIST
-- [ ] Generated complete tool list (83+ tools)
-- [ ] Created TodoWrite entry for every single tool
-- [ ] Tested every tool individually via Reloaderoo inspect
-- [ ] Marked every tool as completed in TodoWrite
-- [ ] Verified tool count: tested_count == total_count
-- [ ] Documented all results, including failures and blocked tools
-- [ ] Created final report covering ALL tools, not just successful ones
-
-### Tool Dependency Graph Testing Strategy
-
-**CRITICAL: Tools must be tested in dependency order:**
-
-1. **Foundation Tools** (provide data for other tools):
- - `doctor` - System info
- - `list_devices` - Device UUIDs
- - `list_sims` - Simulator UUIDs
- - `discover_projs` - Project/workspace paths
-
-2. **Discovery Tools** (provide metadata for build tools):
- - `list_schemes` - Scheme names
- - `show_build_settings` - Build settings
-
-3. **Build Tools** (create artifacts for install tools):
- - `build_*` tools - Create app bundles
- - `get_*_app_path_*` tools - Locate built app bundles
- - `get_*_bundle_id` tools - Extract bundle IDs
-
-4. **Installation Tools** (depend on built artifacts):
- - `install_app_*` tools - Install built apps
- - `launch_app_*` tools - Launch installed apps
-
-5. **Testing Tools** (depend on projects/schemes):
- - `test_*` tools - Run test suites
-
-6. **UI Automation Tools** (depend on running apps):
- - `snapshot_ui`, `screenshot`, `tap`, etc.
-
-### MANDATORY: Record Key Outputs
-
-Must capture and document these values for dependent tools:
-- **Device UUIDs** from `list_devices`
-- **Simulator UUIDs** from `list_sims`
-- **Project/workspace paths** from `discover_projs`
-- **Scheme names** from `list_schems_*`
-- **App bundle paths** from `get_*_app_path_*`
-- **Bundle IDs** from `get_*_bundle_id`
-
-### Prerequisites
-
-1. **Build the server**: `npm run build`
-2. **Install jq**: `brew install jq` (required for JSON parsing)
-3. **System Requirements**: macOS with Xcode installed, connected devices/simulators optional
-
-### Step 1: Programmatic Discovery and Official Testing Lists
-
-#### Generate Official Tool List
-
-```bash
-# Generate complete tool list with accurate count
-npx reloaderoo@latest inspect list-tools -- node build/cli.js mcp 2>/dev/null > /tmp/tools.json
-
-# Get accurate tool count
-TOOL_COUNT=$(jq '.tools | length' /tmp/tools.json)
-echo "Official tool count: $TOOL_COUNT"
-
-# Generate tool names list for testing checklist
-jq -r '.tools[] | .name' /tmp/tools.json > /tmp/tool_names.txt
-echo "Tool names saved to /tmp/tool_names.txt"
-```
-
-#### Generate Official Resource List
-
-```bash
-# Generate complete resource list
-npx reloaderoo@latest inspect list-resources -- node build/cli.js mcp 2>/dev/null > /tmp/resources.json
-
-# Get accurate resource count
-RESOURCE_COUNT=$(jq '.resources | length' /tmp/resources.json)
-echo "Official resource count: $RESOURCE_COUNT"
-
-# Generate resource URIs for testing checklist
-jq -r '.resources[] | .uri' /tmp/resources.json > /tmp/resource_uris.txt
-echo "Resource URIs saved to /tmp/resource_uris.txt"
-```
-
-#### Create Tool Testing Checklist
-
-```bash
-# Generate markdown checklist from actual tool list
-echo "# Official Tool Testing Checklist" > /tmp/tool_testing_checklist.md
-echo "" >> /tmp/tool_testing_checklist.md
-echo "Total Tools: $TOOL_COUNT" >> /tmp/tool_testing_checklist.md
-echo "" >> /tmp/tool_testing_checklist.md
-
-# Add each tool as unchecked item
-while IFS= read -r tool_name; do
- echo "- [ ] $tool_name" >> /tmp/tool_testing_checklist.md
-done < /tmp/tool_names.txt
-
-echo "Tool testing checklist created at /tmp/tool_testing_checklist.md"
-```
-
-#### Create Resource Testing Checklist
-
-```bash
-# Generate markdown checklist from actual resource list
-echo "# Official Resource Testing Checklist" > /tmp/resource_testing_checklist.md
-echo "" >> /tmp/resource_testing_checklist.md
-echo "Total Resources: $RESOURCE_COUNT" >> /tmp/resource_testing_checklist.md
-echo "" >> /tmp/resource_testing_checklist.md
-
-# Add each resource as unchecked item
-while IFS= read -r resource_uri; do
- echo "- [ ] $resource_uri" >> /tmp/resource_testing_checklist.md
-done < /tmp/resource_uris.txt
-
-echo "Resource testing checklist created at /tmp/resource_testing_checklist.md"
-```
-
-### Step 2: Tool Schema Discovery for Parameter Testing
-
-#### Extract Tool Schema Information
-
-```bash
-# Get schema for specific tool to understand required parameters
-TOOL_NAME="list_devices"
-jq --arg tool "$TOOL_NAME" '.tools[] | select(.name == $tool) | .inputSchema' /tmp/tools.json
-
-# Get tool description for usage guidance
-jq --arg tool "$TOOL_NAME" '.tools[] | select(.name == $tool) | .description' /tmp/tools.json
-
-# Generate parameter template for tool testing
-jq --arg tool "$TOOL_NAME" '.tools[] | select(.name == $tool) | .inputSchema.properties // {}' /tmp/tools.json
-```
-
-#### Batch Schema Extraction
-
-```bash
-# Create schema reference file for all tools
-echo "# Tool Schema Reference" > /tmp/tool_schemas.md
-echo "" >> /tmp/tool_schemas.md
-
-while IFS= read -r tool_name; do
- echo "## $tool_name" >> /tmp/tool_schemas.md
- echo "" >> /tmp/tool_schemas.md
-
- # Get description
- description=$(jq -r --arg tool "$tool_name" '.tools[] | select(.name == $tool) | .description' /tmp/tools.json)
- echo "**Description:** $description" >> /tmp/tool_schemas.md
- echo "" >> /tmp/tool_schemas.md
-
- # Get required parameters
- required=$(jq -r --arg tool "$tool_name" '.tools[] | select(.name == $tool) | .inputSchema.required // [] | join(", ")' /tmp/tools.json)
- if [ "$required" != "" ]; then
- echo "**Required Parameters:** $required" >> /tmp/tool_schemas.md
- else
- echo "**Required Parameters:** None" >> /tmp/tool_schemas.md
- fi
- echo "" >> /tmp/tool_schemas.md
-
- # Get all parameters
- echo "**All Parameters:**" >> /tmp/tool_schemas.md
- jq --arg tool "$tool_name" '.tools[] | select(.name == $tool) | .inputSchema.properties // {} | keys[]' /tmp/tools.json | while read param; do
- echo "- $param" >> /tmp/tool_schemas.md
- done
- echo "" >> /tmp/tool_schemas.md
-
-done < /tmp/tool_names.txt
-
-echo "Tool schema reference created at /tmp/tool_schemas.md"
-```
-
-### Step 3: Manual Tool-by-Tool Testing
-
-#### 🚨 CRITICAL: STEP-BY-STEP BLACK BOX TESTING PROCESS
-
-### ABSOLUTE RULE: ALL TESTING MUST BE DONE MANUALLY, ONE TOOL AT A TIME USING RELOADEROO INSPECT
-
-### SYSTEMATIC TESTING PROCESS
-
-1. **Create TodoWrite Task List**
- - Add all 83 tools to task list before starting
- - Mark each tool as "pending" initially
- - Update status to "in_progress" when testing begins
- - Mark "completed" only after manual verification
-
-2. **Test Each Tool Individually**
- - Execute ONLY via `npx reloaderoo@latest inspect call-tool "TOOL_NAME" --params 'JSON' -- node build/cli.js mcp`
- - Wait for complete response before proceeding to next tool
- - Read and verify each tool's output manually
- - Record key outputs (UUIDs, paths, schemes) for dependent tools
-
-3. **Manual Verification Requirements**
- - ✅ **Read each response** - Manually verify tool output makes sense
- - ✅ **Check for errors** - Identify any tool failures or unexpected responses
- - ✅ **Record UUIDs/paths** - Save outputs needed for dependent tools
- - ✅ **Update task list** - Mark each tool complete after verification
- - ✅ **Document issues** - Record any problems found during testing
-
-4. **FORBIDDEN SHORTCUTS:**
- - ❌ **NO SCRIPTS** - Scripts hide what's happening and prevent proper verification
- - ❌ **NO AUTOMATION** - Every tool call must be manually executed and verified
- - ❌ **NO BATCHING** - Cannot test multiple tools simultaneously
- - ❌ **NO MCP DIRECT CALLS** - Only Reloaderoo inspect commands allowed
-
-#### Phase 1: Infrastructure Validation
-
-#### Manual Commands (execute individually):
-
-```bash
-# Test server connectivity
-npx reloaderoo@latest inspect ping -- node build/cli.js mcp
-
-# Get server information
-npx reloaderoo@latest inspect server-info -- node build/cli.js mcp
-
-# Verify tool count manually
-npx reloaderoo@latest inspect list-tools -- node build/cli.js mcp 2>/dev/null | jq '.tools | length'
-
-# Verify resource count manually
-npx reloaderoo@latest inspect list-resources -- node build/cli.js mcp 2>/dev/null | jq '.resources | length'
-```
-
-#### Phase 2: Resource Testing
-
-```bash
-# Test each resource systematically
-while IFS= read -r resource_uri; do
- echo "Testing resource: $resource_uri"
- npx reloaderoo@latest inspect read-resource "$resource_uri" -- node build/cli.js mcp 2>/dev/null
- echo "---"
-done < /tmp/resource_uris.txt
-```
-
-#### Phase 3: Foundation Tools (Data Collection)
-
-### CRITICAL: Capture ALL key outputs for dependent tools
-
-```bash
-echo "=== FOUNDATION TOOL TESTING & DATA COLLECTION ==="
-
-# 1. Test doctor (no dependencies)
-echo "Testing doctor..."
-npx reloaderoo@latest inspect call-tool "doctor" --params '{}' -- node build/cli.js mcp 2>/dev/null
-
-# 2. Collect device data
-echo "Collecting device UUIDs..."
-npx reloaderoo@latest inspect call-tool "list_devices" --params '{}' -- node build/cli.js mcp 2>/dev/null > /tmp/devices_output.json
-DEVICE_UUIDS=$(jq -r '.content[0].text' /tmp/devices_output.json | grep -E "UDID: [A-F0-9-]+" | sed 's/.*UDID: //' | head -2)
-echo "Device UUIDs captured: $DEVICE_UUIDS"
-
-# 3. Collect simulator data
-echo "Collecting simulator UUIDs..."
-npx reloaderoo@latest inspect call-tool "list_sims" --params '{}' -- node build/cli.js mcp 2>/dev/null > /tmp/sims_output.json
-SIMULATOR_UUIDS=$(jq -r '.content[0].text' /tmp/sims_output.json | grep -E "\([A-F0-9-]+\)" | sed 's/.*(\([A-F0-9-]*\)).*/\1/' | head -3)
-echo "Simulator UUIDs captured: $SIMULATOR_UUIDS"
-
-# 4. Collect project data
-echo "Collecting project paths..."
-npx reloaderoo@latest inspect call-tool "discover_projs" --params '{"workspaceRoot": "/Volumes/Developer/XcodeBuildMCP"}' -- node build/cli.js mcp 2>/dev/null > /tmp/projects_output.json
-PROJECT_PATHS=$(jq -r '.content[1].text' /tmp/projects_output.json | grep -E "\.xcodeproj$" | sed 's/.*- //' | head -3)
-WORKSPACE_PATHS=$(jq -r '.content[2].text' /tmp/projects_output.json | grep -E "\.xcworkspace$" | sed 's/.*- //' | head -2)
-echo "Project paths captured: $PROJECT_PATHS"
-echo "Workspace paths captured: $WORKSPACE_PATHS"
-
-# Save key data for dependent tools
-echo "$DEVICE_UUIDS" > /tmp/device_uuids.txt
-echo "$SIMULATOR_UUIDS" > /tmp/simulator_uuids.txt
-echo "$PROJECT_PATHS" > /tmp/project_paths.txt
-echo "$WORKSPACE_PATHS" > /tmp/workspace_paths.txt
-```
-
-#### Phase 4: Discovery Tools (Metadata Collection)
-
-```bash
-echo "=== DISCOVERY TOOL TESTING & METADATA COLLECTION ==="
-
-# Collect schemes for each project
-while IFS= read -r project_path; do
- if [ -n "$project_path" ]; then
- echo "Getting schemes for: $project_path"
- npx reloaderoo@latest inspect call-tool "list_schems_proj" --params "{\"projectPath\": \"$project_path\"}" -- node build/cli.js mcp 2>/dev/null > /tmp/schemes_$$.json
- SCHEMES=$(jq -r '.content[1].text' /tmp/schemes_$$.json 2>/dev/null || echo "NoScheme")
- echo "$project_path|$SCHEMES" >> /tmp/project_schemes.txt
- echo "Schemes captured for $project_path: $SCHEMES"
- fi
-done < /tmp/project_paths.txt
-
-# Collect schemes for each workspace
-while IFS= read -r workspace_path; do
- if [ -n "$workspace_path" ]; then
- echo "Getting schemes for: $workspace_path"
- npx reloaderoo@latest inspect call-tool "list_schemes" --params "{\"workspacePath\": \"$workspace_path\"}" -- node build/cli.js mcp 2>/dev/null > /tmp/ws_schemes_$$.json
- SCHEMES=$(jq -r '.content[1].text' /tmp/ws_schemes_$$.json 2>/dev/null || echo "NoScheme")
- echo "$workspace_path|$SCHEMES" >> /tmp/workspace_schemes.txt
- echo "Schemes captured for $workspace_path: $SCHEMES"
- fi
-done < /tmp/workspace_paths.txt
-```
-
-#### Phase 5: Manual Individual Tool Testing (All 83 Tools)
-
-### CRITICAL: Test every single tool manually, one at a time
-
-#### Manual Testing Process:
-
-1. **Create task list** with TodoWrite tool for all 83 tools
-2. **Test each tool individually** with proper parameters
-3. **Mark each tool complete** in task list after manual verification
-4. **Record results** and observations for each tool
-5. **NO SCRIPTS** - Each command executed manually
-
-### STEP-BY-STEP MANUAL TESTING COMMANDS
-
-```bash
-# STEP 1: Test foundation tools (no parameters required)
-# Execute each command individually, wait for response, verify manually
-npx reloaderoo@latest inspect call-tool "doctor" --params '{}' -- node build/cli.js mcp
-# [Wait for response, read output, mark tool complete in task list]
-
-npx reloaderoo@latest inspect call-tool "list_devices" --params '{}' -- node build/cli.js mcp
-# [Record device UUIDs from response for dependent tools]
-
-npx reloaderoo@latest inspect call-tool "list_sims" --params '{}' -- node build/cli.js mcp
-# [Record simulator UUIDs from response for dependent tools]
-
-# STEP 2: Test project discovery (use discovered project paths)
-npx reloaderoo@latest inspect call-tool "list_schems_proj" --params '{"projectPath": "/actual/path/from/discover_projs.xcodeproj"}' -- node build/cli.js mcp
-# [Record scheme names from response for build tools]
-
-# STEP 3: Test workspace tools (use discovered workspace paths)
-npx reloaderoo@latest inspect call-tool "list_schemes" --params '{"workspacePath": "/actual/path/from/discover_projs.xcworkspace"}' -- node build/cli.js mcp
-# [Record scheme names from response for build tools]
-
-# STEP 4: Test simulator tools (use captured simulator UUIDs from step 1)
-npx reloaderoo@latest inspect call-tool "boot_sim" --params '{"simulatorUuid": "ACTUAL_UUID_FROM_LIST_SIMS"}' -- node build/cli.js mcp
-# [Verify simulator boots successfully]
-
-# STEP 5: Test build tools (requires project + scheme + simulator from previous steps)
-npx reloaderoo@latest inspect call-tool "build_sim_id_proj" --params '{"projectPath": "/actual/project.xcodeproj", "scheme": "ActualSchemeName", "simulatorId": "ACTUAL_SIMULATOR_UUID"}' -- node build/cli.js mcp
-# [Verify build succeeds and record app bundle path]
-```
-
-### CRITICAL: EACH COMMAND MUST BE
-1. **Executed individually** - One command at a time, manually typed or pasted
-2. **Verified manually** - Read the complete response before continuing
-3. **Tracked in task list** - Mark tool complete only after verification
-4. **Use real data** - Replace placeholder values with actual captured data
-5. **Wait for completion** - Allow each command to finish before proceeding
-
-### TESTING VIOLATIONS AND ENFORCEMENT
-
-### 🚨 CRITICAL VIOLATIONS THAT WILL TERMINATE TESTING
-
-1. **Direct MCP Tool Usage Violation:**
- ```typescript
- // ❌ IMMEDIATE TERMINATION - Using MCP tools directly
- await mcp__XcodeBuildMCP__list_devices();
- const result = await list_sims();
- ```
-
-2. **Script-Based Testing Violation:**
- ```bash
- # ❌ IMMEDIATE TERMINATION - Using scripts to test tools
- for tool in $(cat tool_list.txt); do
- npx reloaderoo inspect call-tool "$tool" --params '{}' -- node build/cli.js mcp
- done
- ```
-
-3. **Batching/Automation Violation:**
- ```bash
- # ❌ IMMEDIATE TERMINATION - Testing multiple tools simultaneously
- npx reloaderoo inspect call-tool "list_devices" & npx reloaderoo inspect call-tool "list_sims" &
- ```
-
-4. **Source Code Examination Violation:**
- ```typescript
- // ❌ IMMEDIATE TERMINATION - Reading implementation during testing
- const toolImplementation = await Read('/src/mcp/tools/device-shared/list_devices.ts');
- ```
-
-### ENFORCEMENT PROCEDURE
-1. **First Violation**: Immediate correction and restart of testing process
-2. **Documentation Update**: Add explicit prohibition to prevent future violations
-3. **Method Validation**: Ensure all future testing uses only Reloaderoo inspect commands
-4. **Progress Reset**: Restart testing from foundation tools if direct MCP usage detected
-
-### VALID TESTING SEQUENCE EXAMPLE
-```bash
-# ✅ CORRECT - Step-by-step manual execution via Reloaderoo
-# Tool 1: Test doctor
-npx reloaderoo@latest inspect call-tool "doctor" --params '{}' -- node build/cli.js mcp
-# [Read response, verify, mark complete in TodoWrite]
-
-# Tool 2: Test list_devices
-npx reloaderoo@latest inspect call-tool "list_devices" --params '{}' -- node build/cli.js mcp
-# [Read response, capture UUIDs, mark complete in TodoWrite]
-
-# Tool 3: Test list_sims
-npx reloaderoo@latest inspect call-tool "list_sims" --params '{}' -- node build/cli.js mcp
-# [Read response, capture UUIDs, mark complete in TodoWrite]
-
-# Tool X: Test stateful tool (expected to fail)
-npx reloaderoo@latest inspect call-tool "swift_package_stop" --params '{"pid": 12345}' -- node build/cli.js mcp
-# [Tool fails as expected - no in-memory state available]
-# [Mark as "false negative - stateful tool limitation" in TodoWrite]
-# [Continue to next tool without investigation]
-
-# Continue individually for all 83 tools...
-```
-
-### HANDLING STATEFUL TOOL FAILURES
-```bash
-# ✅ CORRECT Response to Expected Stateful Tool Failure
-# Tool fails with "No process found" or similar state-related error
-# Response: Mark tool as "tested - false negative (stateful)" in task list
-# Do NOT attempt to diagnose, fix, or investigate the failure
-# Continue immediately to next tool in sequence
-```
-
-### Step 4: Error Testing
-
-```bash
-# Test error handling systematically
-echo "=== Error Testing ==="
-
-# Test with invalid JSON parameters
-echo "Testing invalid parameter types..."
-npx reloaderoo@latest inspect call-tool list_schems_proj --params '{"projectPath": 123}' -- node build/cli.js mcp 2>/dev/null
-
-# Test with non-existent paths
-echo "Testing non-existent paths..."
-npx reloaderoo@latest inspect call-tool list_schems_proj --params '{"projectPath": "/nonexistent/path.xcodeproj"}' -- node build/cli.js mcp 2>/dev/null
-
-# Test with invalid UUIDs
-echo "Testing invalid UUIDs..."
-npx reloaderoo@latest inspect call-tool boot_sim --params '{"simulatorUuid": "invalid-uuid"}' -- node build/cli.js mcp 2>/dev/null
-```
-
-### Step 5: Generate Testing Report
-
-```bash
-# Create comprehensive testing session report
-cat > TESTING_SESSION_$(date +%Y-%m-%d).md << EOF
-# Manual Testing Session - $(date +%Y-%m-%d)
-
-## Environment
-- macOS Version: $(sw_vers -productVersion)
-- XcodeBuildMCP Version: $(jq -r '.version' package.json 2>/dev/null || echo "unknown")
-- Testing Method: Reloaderoo @latest via npx
-
-## Official Counts (Programmatically Verified)
-- Total Tools: $TOOL_COUNT
-- Total Resources: $RESOURCE_COUNT
-
-## Test Results
-[Document test results here]
-
-## Issues Found
-[Document any discrepancies or failures]
-
-## Performance Notes
-[Document response times and performance observations]
-EOF
-
-echo "Testing session template created: TESTING_SESSION_$(date +%Y-%m-%d).md"
-```
-
-### Key Commands Reference
-
-```bash
-# Essential testing commands
-npx reloaderoo@latest inspect ping -- node build/cli.js mcp
-npx reloaderoo@latest inspect server-info -- node build/cli.js mcp
-npx reloaderoo@latest inspect list-tools -- node build/cli.js mcp | jq '.tools | length'
-npx reloaderoo@latest inspect list-resources -- node build/cli.js mcp | jq '.resources | length'
-npx reloaderoo@latest inspect call-tool TOOL_NAME --params '{}' -- node build/cli.js mcp
-npx reloaderoo@latest inspect read-resource "xcodebuildmcp://RESOURCE" -- node build/cli.js mcp
-
-# Schema extraction
-jq --arg tool "TOOL_NAME" '.tools[] | select(.name == $tool) | .inputSchema' /tmp/tools.json
-jq --arg tool "TOOL_NAME" '.tools[] | select(.name == $tool) | .description' /tmp/tools.json
-```
-
-This systematic approach ensures comprehensive, accurate testing using programmatic discovery and validation of all XcodeBuildMCP functionality.
-
-## Troubleshooting
-
-### Common Issues
-
-#### 1. "Noop Executor Called" Error
-**Symptoms**: Test fails with `NOOP EXECUTOR CALLED` or `NOOP FILESYSTEM EXECUTOR CALLED`
-**Cause**: The Vitest unit setup (`src/test-utils/vitest-executor-safety.setup.ts`) installs
-blocking noop overrides for all unit tests. If a handler calls `getDefaultCommandExecutor()` or
-`getDefaultFileSystemExecutor()` without an explicit test override, the noop throws.
-**Fix**: Either inject a mock executor directly into the logic function, or use the override hooks:
-
-```typescript
-// Option A: Direct injection into the logic function
-const mockExecutor = createMockExecutor({ success: true });
-const result = await toolLogic(params, mockExecutor);
-
-// Option B: Override hooks (for handler-level tests)
-import { __setTestCommandExecutorOverride } from '../utils/command.ts';
-__setTestCommandExecutorOverride(createMockExecutor({ success: true }));
-const result = await handler(params);
-```
-
-**Note**: The setup file only applies to `vitest.config.ts` (unit tests). Snapshot and smoke
-tests use separate configs and are not affected.
-
-#### 2. "Noop Interactive Spawner Called" Error
-**Symptoms**: Test fails with `NOOP INTERACTIVE SPAWNER CALLED`
-**Cause**: Same mechanism as above but for `getDefaultInteractiveSpawner()`.
-**Fix**: Use `createMockInteractiveSpawner()` from `test-utils/mock-executors.ts`.
-
-#### 3. Handler Signature Errors
-**Symptoms**: TypeScript errors about handler parameters
-**Cause**: Handler doesn't support dependency injection
-**Fix**: Update handler signature:
-
-```typescript
-async handler(args: Record): Promise {
- return tool_nameLogic(args, getDefaultCommandExecutor(), getDefaultFileSystemExecutor());
-}
-```
-
-### Debug Commands
-
-```bash
-# Run specific test file
-npm test -- src/plugins/simulator-workspace/__tests__/tool_name.test.ts
-
-# Run with verbose output
-npm test -- --reporter=verbose
-
-# Check for banned patterns
-node scripts/check-code-patterns.js
-
-# Verify dependency injection compliance
-node scripts/audit-dependency-container.js
-
-# Coverage for specific directory
-npm run test:coverage -- src/plugins/simulator-workspace/
-```
-
-### Validation Scripts
-
-```bash
-# Check for architectural pattern violations
-node scripts/check-code-patterns.js
-
-# Check dependency injection compliance
-node scripts/audit-dependency-container.js
-
-# Both scripts must pass before committing
-```
-
-## Best Practices Summary
-
-1. **Dependency injection**: Always use createMockExecutor() and createMockFileSystemExecutor()
-2. **External boundaries via DI**: mock command execution/filesystem with injected executors
-3. **Three dimensions**: Test input validation, command execution, and output processing
-4. **Literal expectations**: Use exact strings in assertions to catch regressions
-5. **Performance**: Ensure fast execution through proper mocking
-6. **Coverage**: Aim for 95%+ with focus on error paths
-7. **Consistency**: Follow standard patterns across all plugin tests
-8. **Test safety**: Default executors prevent accidental real system calls
-
-This testing strategy ensures robust, maintainable tests that provide confidence in plugin functionality while remaining resilient to implementation changes and keeping external boundaries deterministic.
diff --git a/docs/dev/TOOL_AUTHORING_COOKBOOK.md b/docs/dev/TOOL_AUTHORING_COOKBOOK.md
deleted file mode 100644
index a9b9d9223..000000000
--- a/docs/dev/TOOL_AUTHORING_COOKBOOK.md
+++ /dev/null
@@ -1,383 +0,0 @@
-# Tool Authoring Cookbook
-
-This is the end-to-end contributor guide for creating, modifying, deleting, and validating XcodeBuildMCP tools.
-
-Use this cookbook when a change affects any tool's runtime behavior, MCP metadata, CLI surface, structured output, schemas, docs, or fixtures.
-
-Related references:
-- Manifest fields: `docs/dev/MANIFEST_FORMAT.md`
-- Streaming vs non-streaming output model: `docs/dev/ADDING_TOOLS_OUTPUT_MODEL.md`
-- Schema versioning: `docs/SCHEMA_VERSIONING.md`
-- Testing guidance: `docs/dev/TESTING.md`
-
-## Mental model
-
-A tool is defined across several layers:
-
-| Layer | Location | Purpose |
-|---|---|---|
-| Tool implementation | `src/mcp/tools//.ts` | Input validation, execution, final structured result |
-| Tool manifest | `manifests/tools/.yaml` | MCP/CLI names, description, annotations, output schema metadata, visibility |
-| Workflow manifest | `manifests/workflows/.yaml` | Which workflows expose the tool |
-| Structured output schema | `schemas/structured-output//1.schema.json` | Canonical JSON contract for `structuredContent` and CLI JSON fixtures |
-| Snapshot fixtures | `src/snapshot-tests/__fixtures__/{mcp,cli,json}/...` | Expected MCP text, CLI text, and structured JSON outputs |
-| Generated docs | `docs/TOOLS.md`, `docs/TOOLS-CLI.md` | User-facing tool reference generated from manifests |
-
-The final structured result is the canonical data contract. Text output is rendered from that result and runtime-specific presentation logic.
-
-## Before changing a tool
-
-1. Find the tool manifest in `manifests/tools/`.
-2. Find the implementation from the manifest `module` field.
-3. Find the workflow manifest(s) that include the tool ID.
-4. Find the output schema named by `outputSchema.schema` in the manifest.
-5. Find existing fixtures under:
- - `src/snapshot-tests/__fixtures__/mcp//`
- - `src/snapshot-tests/__fixtures__/cli//`
- - `src/snapshot-tests/__fixtures__/json//`
-
-Do not edit generated docs or fixtures blindly. Understand which runtime surface changed first.
-
-## Create a new tool
-
-### 1. Pick the tool shape
-
-Use a non-streaming tool when it computes a result and returns once.
-
-Examples:
-- list tools
-- query tools
-- metadata lookups
-- session defaults
-
-Use a streaming tool when users benefit from live progress.
-
-Examples:
-- build
-- build and run
-- test
-- long-running process launch
-
-See `docs/dev/ADDING_TOOLS_OUTPUT_MODEL.md` for the exact executor patterns.
-
-### 2. Create the implementation
-
-Create:
-
-```text
-src/mcp/tools//.ts
-```
-
-The module must export:
-
-```ts
-export const schema = schemaObject.shape;
-export const handler = createTypedTool(...);
-```
-
-or the session-aware equivalent when appropriate.
-
-The handler must set structured output:
-
-```ts
-ctx.structuredOutput = {
- result,
- schema: 'xcodebuildmcp.output.example-result',
- schemaVersion: '1',
-};
-```
-
-For build-like tools, use the existing helper:
-
-```ts
-setXcodebuildStructuredOutput(ctx, 'build-result', result);
-```
-
-Do not manually return MCP `content` text from normal tools. The renderer owns text output.
-
-### 3. Define the structured result
-
-Prefer an existing result kind when the shape already fits.
-
-Examples:
-- `xcodebuildmcp.output.build-result`
-- `xcodebuildmcp.output.app-path`
-- `xcodebuildmcp.output.simulator-action-result`
-
-Create a new schema only when no existing schema accurately describes the payload.
-
-If adding a new schema, create:
-
-```text
-schemas/structured-output/xcodebuildmcp.output./1.schema.json
-```
-
-The schema should validate the entire envelope:
-
-```json
-{
- "schema": "xcodebuildmcp.output.",
- "schemaVersion": "1",
- "didError": false,
- "error": null,
- "data": {}
-}
-```
-
-Keep root `$schema` and `$id`. Reuse shared definitions from:
-
-```text
-schemas/structured-output/_defs/common.schema.json
-```
-
-Canonical schemas may use absolute `$ref`s. MCP runtime bundles those refs into local `$defs` before advertising `outputSchema`.
-
-### 4. Create the tool manifest
-
-Create:
-
-```text
-manifests/tools/.yaml
-```
-
-Minimal shape:
-
-```yaml
-id: example_tool
-module: mcp/tools//example_tool
-names:
- mcp: example_tool
- cli: example-tool
-description: Do one clear thing.
-annotations:
- title: Example Tool
- readOnlyHint: true
- destructiveHint: false
- openWorldHint: false
-outputSchema:
- schema: xcodebuildmcp.output.example-result
- version: "1"
-```
-
-Use `outputSchema` for every tool that sets `ctx.structuredOutput`. The schema/version must match the values used by the tool's structured result.
-
-### 5. Add the tool to a workflow
-
-Edit:
-
-```text
-manifests/workflows/.yaml
-```
-
-Add the manifest `id` to `tools:`.
-
-A tool can be referenced by multiple workflows, but it should be defined once in `manifests/tools/`.
-
-### 6. Generate docs
-
-If you add, remove, or modify tool metadata, run:
-
-```bash
-npm run docs:update
-npm run docs:check
-```
-
-Do not hand-edit `docs/TOOLS.md` or `docs/TOOLS-CLI.md`.
-
-### 7. Add or update fixtures
-
-Add representative MCP, CLI, and JSON fixtures for the new behavior.
-
-Run snapshot updates with full output preserved:
-
-```bash
-npm run test:snapshots:update 2>&1 | tee /tmp/snapshot-update.txt
-```
-
-If running the full snapshot suite against a simulator/device, follow `docs/dev/TESTING.md` and preserve full logs. Snapshot tests are slow and environment-sensitive, so inspect failures before updating fixtures.
-
-### 8. Validate schemas and fixtures
-
-Run:
-
-```bash
-npm run test:schema-fixtures
-```
-
-This validates generated JSON fixtures in `src/snapshot-tests/__fixtures__/json/**` against the canonical schemas in `schemas/structured-output/**`.
-
-If it fails, fix the source of drift:
-- update the tool result if the payload is wrong
-- update the schema if the payload is now the intended contract
-- create a new schema version if the change is breaking for consumers
-
-## Modify an existing tool
-
-### Edit tool metadata
-
-Change the manifest first:
-
-```text
-manifests/tools/.yaml
-```
-
-Metadata includes:
-- `description`
-- `names.mcp`
-- `names.cli`
-- `annotations`
-- `availability`
-- `predicates`
-- `routing`
-- `outputSchema`
-
-Then run:
-
-```bash
-npm run docs:update
-npm run docs:check
-```
-
-If the MCP or CLI name changes, update tests, docs, fixtures, and any next-step references that call the old name.
-
-### Edit input parameters
-
-Input parameters live in the tool implementation's Zod schema.
-
-After changing parameters:
-
-1. Update descriptions on the Zod fields.
-2. Keep mutually exclusive parameters enforced in the schema or tool requirements.
-3. Update tests for valid and invalid inputs.
-4. Update fixtures if text or JSON output changes.
-5. Run docs generation because CLI/MCP docs are generated from tool schemas/manifests.
-
-Commands:
-
-```bash
-npm run docs:update
-npm run docs:check
-npm run typecheck
-npm run test:schema-fixtures
-```
-
-### Edit structured output
-
-Structured output changes must keep three things aligned:
-
-1. The tool's `ctx.structuredOutput` schema name/version.
-2. The manifest `outputSchema` metadata.
-3. The canonical schema file.
-
-For compatible additions, update the existing schema version and fixtures.
-
-For breaking changes, add a new schema version file:
-
-```text
-schemas/structured-output/xcodebuildmcp.output./2.schema.json
-```
-
-Then update the tool and manifest to emit:
-
-```ts
-schemaVersion: '2'
-```
-
-and:
-
-```yaml
-outputSchema:
- schema: xcodebuildmcp.output.
- version: "2"
-```
-
-Run:
-
-```bash
-npm run test:schema-fixtures
-npx vitest run src/core/__tests__/structured-output-schema.test.ts
-```
-
-The second command validates the MCP `outputSchema` bundling path.
-
-## Delete a tool
-
-1. Remove the tool ID from all workflow manifests.
-2. Delete `manifests/tools/.yaml`.
-3. Delete the implementation file if no longer used.
-4. Delete tests that only covered that tool.
-5. Delete fixtures for MCP, CLI, and JSON output.
-6. Run docs generation.
-
-Commands:
-
-```bash
-npm run docs:update
-npm run docs:check
-npm run typecheck
-npm test
-npm run test:schema-fixtures
-```
-
-Do not delete a shared schema just because one tool stopped using it. Schemas are published API; only remove unpublished or clearly unused files after checking consumers.
-
-## Maintain fixtures
-
-Fixtures are not golden decorations; they are the output contract.
-
-| Fixture tree | Validates |
-|---|---|
-| `__fixtures__/mcp` | Human-readable MCP `content[]` text |
-| `__fixtures__/cli` | CLI text output |
-| `__fixtures__/json` | Structured JSON envelope from MCP `structuredContent` |
-
-When behavior changes intentionally:
-
-1. Update the implementation.
-2. Regenerate fixtures.
-3. Review fixture diffs manually.
-4. Validate JSON fixtures against schemas.
-5. Commit implementation, schemas, fixtures, and generated docs together.
-
-Do not update snapshot fixtures just to make tests pass. If a fixture changes unexpectedly, assume code is wrong until proven otherwise.
-
-## Required checks before handoff
-
-For documentation-only changes, checks may be skipped if the change does not affect generated docs.
-
-For tool changes, run at least:
-
-```bash
-npm run docs:update
-npm run docs:check
-npm run format:check
-npm run lint
-npm run typecheck
-npm test
-npm run test:schema-fixtures
-```
-
-For output or fixture changes, also run the relevant snapshot suite/update flow and preserve full output in a log file:
-
-```bash
-npm run test:snapshots 2>&1 | tee /tmp/snapshot-results.txt
-npm run test:snapshots:update 2>&1 | tee /tmp/snapshot-update.txt
-```
-
-For MCP output schema changes, also run:
-
-```bash
-npx vitest run src/core/__tests__/structured-output-schema.test.ts
-npx vitest run --config vitest.smoke.config.ts src/smoke-tests/__tests__/e2e-mcp-discovery.test.ts
-```
-
-## Common mistakes
-
-- Adding a tool implementation but forgetting the manifest.
-- Adding a manifest but forgetting the workflow reference.
-- Setting `ctx.structuredOutput` but forgetting manifest `outputSchema`.
-- Changing JSON payload shape without updating schema and fixtures.
-- Updating fixtures without reviewing why they changed.
-- Hand-editing generated tool docs.
-- Relying on streamed fragments for final output data.
-- Adding fallback behavior instead of making the requested path canonical.
diff --git a/docs/dev/TOOL_DISCOVERY_LOGIC.md b/docs/dev/TOOL_DISCOVERY_LOGIC.md
deleted file mode 100644
index a6054c892..000000000
--- a/docs/dev/TOOL_DISCOVERY_LOGIC.md
+++ /dev/null
@@ -1,144 +0,0 @@
-# Tool Discovery & Visibility Logic (MCP server + CLI)
-
-This document describes how XcodeBuildMCP discovers workflows/tools and decides which ones are visible in:
-
-- the **MCP server** (`xcodebuildmcp mcp`), and
-- the **CLI** (`node build/cli.js` / `xcodebuildmcp ...`).
-
-It also documents the current and intended **visibility filtering** behavior (post workflow-selection filtering).
-
-## Terminology
-
-- **Workflow**: a manifest entry in `manifests/workflows/.yaml` referencing tool IDs.
-- **Tool**: a manifest entry in `manifests/tools/.yaml` with a module path; the module exports `{ schema, handler }`.
-- **Resource**: a manifest entry in `manifests/resources/.yaml` with a module path; the module exports `{ handler }`.
-- **Workflow selection**: picking which workflows are active (coarse-grained inclusion).
-- **Visibility filtering**: hiding specific tools/resources even if their workflow is enabled (fine-grained exclusion via predicates).
-- **Dynamic tools**: tools registered at runtime that do not come from static workflows (e.g. proxied Xcode Tools).
-
-## Where workflows/tools/resources come from (source of truth)
-
-YAML manifests in `manifests/` are the single source of truth for metadata:
-
-- `manifests/tools/*.yaml` define individual tools and their module paths
-- `manifests/workflows/*.yaml` define workflow groupings that reference tool IDs
-- `manifests/resources/*.yaml` define MCP resources and their module paths
-
-At runtime, `loadManifest()` reads all YAML files and returns a `ResolvedManifest` containing tools, workflows, and resources. Tool/resource code modules are dynamically imported via `importToolModule()` and `importResourceModule()`.
-
-Key properties of this design:
-
-- Workflows are discoverable by enumerating the manifest's workflow entries.
-- Tools within a workflow are listed by tool ID references.
-- A single tool can appear in multiple workflows (referenced by ID). This matters for workflow management and hiding.
-
-## MCP server: registration pipeline
-
-At MCP server startup:
-
-1) Runtime config is loaded (`bootstrapRuntime` → `initConfigStore`) from:
- - config file (project config),
- - env (`XCODEBUILDMCP_*`),
- - explicit overrides.
-
-2) Enabled workflows are taken from config (`enabledWorkflows`).
-
-3) `registerWorkflows(enabledWorkflows)` runs, which calls `applyWorkflowSelection(...)` (`src/utils/tool-registry.ts`).
-
-4) Workflow selection uses `selectWorkflowsForMcp(...)` (`src/visibility/exposure.ts`) which:
- - includes auto-include workflows whose predicates pass (e.g., `session-management` with no predicates is always included),
- - includes explicitly requested workflows from config,
- - defaults to `defaultEnabled: true` workflows (e.g., `simulator`) when `enabledWorkflows` is empty,
- - filters all selected workflows by availability + predicates.
-
-5) For each selected workflow, each tool is considered for registration, then filtered by `shouldExposeTool(workflowName, toolName)` (`src/utils/tool-registry.ts`).
-
-6) If visible, the tool is registered via `server.registerTool(...)`.
-
-### Runtime workflow management (MCP)
-
-The `manage-workflows` tool updates the enabled workflow list at runtime and re-applies workflow selection (`src/mcp/tools/workflow-discovery/manage_workflows.ts` → `applyWorkflowSelection(...)`).
-
-Important nuance:
-
-- Because tools can be re-exported across workflows, disabling one workflow may not remove a tool if another enabled workflow still provides that same tool name.
-- Visibility filtering operates on the tool name (and workflow name) at registration time; it is layered after workflow selection.
-
-## CLI: discovery and help output
-
-The CLI has two related but distinct concepts:
-
-1) **Command registration / `--help` tree** (yargs commands)
-2) **Tool listing** (`xcodebuildmcp tools`) which is driven by a manifest
-
-### CLI command registration (yargs)
-
-CLI mode builds a tool catalog using `buildCliToolCatalog()` which enables **all workflows** returned by `listWorkflowDirectoryNames()` except `session-management` and `workflow-discovery` (`src/cli/cli-tool-catalog.ts`).
-
-The catalog is built via `buildToolCatalog(...)` (`src/runtime/tool-catalog.ts`), which:
-
-- loads workflow groups via `loadWorkflowGroups()`,
-- resolves selected workflows (as above),
-- applies `shouldExposeTool(...)` as a per-tool visibility filter,
-- generates CLI names and disambiguates collisions.
-
-Yargs then registers workflow command groups and per-tool subcommands from the catalog (`src/cli/register-tool-commands.ts`).
-
-Additionally, the CLI registers workflow command groups from workflow metadata even if there are currently zero visible tools in that workflow (so the workflow still appears in `--help` output).
-
-### CLI `tools` command (manifest-driven)
-
-`xcodebuildmcp tools` reads `build/tools-manifest.json` (`src/cli/commands/tools.ts`) which is generated by `npm run generate:tools-manifest` (invoked by `npm run build:tsup`).
-
-Key implications:
-
-- The manifest is a **static analysis** snapshot (canonical source for docs/CLI listing).
-- Dynamic runtime tools (see below) are **not** represented in the manifest.
-
-## Dynamic tools: Xcode Tools via `xcrun mcpbridge`
-
-When the `xcode-ide` workflow is enabled, XcodeBuildMCP can proxy Xcode’s IDE tool service through `xcrun mcpbridge` and register proxied tools at runtime.
-
-Properties:
-
-- Proxied tools are registered dynamically and prefixed as `xcode_tools_` (see `src/integrations/xcode-tools-bridge/registry.ts`).
-- Proxied tools are not part of any static workflow and are not present in `build/tools-manifest.json`.
-- Proxied tool registration can change over time if Xcode’s tool catalog changes (the bridge supports `tools/listChanged`).
-
-## Visibility filtering (post-selection hiding)
-
-### Current behavior (implemented)
-
-`shouldExposeTool(...)` is the single, shared visibility gate for:
-
-- MCP server tool registration (`src/utils/tool-registry.ts`)
-- CLI catalog building (`src/runtime/tool-catalog.ts`)
-
-Currently it is used to hide Xcode IDE bridge **debug** tools unless debugging is enabled:
-
-- `xcode_tools_bridge_status`
-- `xcode_tools_bridge_sync`
-- `xcode_tools_bridge_disconnect`
-
-These tools are gated behind `debug: true` / `XCODEBUILDMCP_DEBUG=true` (`src/utils/tool-visibility.ts`).
-
-### Intended behavior (documented policy)
-
-We also want a broader “Xcode agent mode” visibility filter where specific XcodeBuildMCP tools are hidden when:
-
-- `runningUnderXcode === true`, AND
-- Xcode Tools MCP is enabled/available
-
-This is documented in `docs/dev/XCODE_IDE_TOOL_CONFLICTS.md`.
-
-Design principle:
-
-- Workflow selection stays the **primary** “what areas are enabled” control.
-- Visibility filtering is a **secondary** control to reduce confusion/duplication and align behavior with the environment (e.g. “inside Xcode, builds/tests should run inside Xcode”).
-
-## Layering summary (what wins)
-
-1) **Workflow selection** decides which workflows are in play.
-2) **Visibility filtering** can hide individual tools even within enabled workflows.
-3) **Dynamic tools** may appear even if there are no static tools in a workflow (e.g. proxied `xcode_tools_*`).
-
diff --git a/docs/dev/device-snapshot-investigation.md b/docs/dev/device-snapshot-investigation.md
deleted file mode 100644
index 92990c1ae..000000000
--- a/docs/dev/device-snapshot-investigation.md
+++ /dev/null
@@ -1,125 +0,0 @@
-# Investigation: Device snapshot regressions
-
-## Summary
-The strongest current evidence points to an Apple tooling issue in the post-test device diagnostics path, not a deterministic regression in XcodeBuildMCP’s shared device workflow code. After a failing physical-device `xcodebuild test`, Xcode launches `devicectl diagnose`; when that runs on a TTY it prints `Password:` and blocks on interactive input, and when Xcode launches it from a background TTY process group the subprocess gets wedged. In non-TTY mode, the same path does not hang and instead exits with a concrete `CoreDeviceCLISupport.DiagnoseError` / `No provider was found` failure.
-
-## Symptoms
-- CLI `device build-and-run` success case returned `isError === true` in the snapshot suite.
-- CLI `device test` intentional failure snapshot was truncated after discovery/build-stage output and did not include the expected failure footer.
-- MCP `device test` intentional failure timed out after 300s in the suite.
-
-## Investigation Log
-
-### Initial assessment
-**Hypothesis:** A refactor changed shared device workflow behavior, likely in long-running command/result streaming or failure parsing.
-**Findings:** Targeted device test still passes, which suggests device discovery/build bootstrapping still works. Failures are concentrated in `build-and-run` and full-suite `test` failure handling.
-**Evidence:** `XcodeBuildMCP/src/snapshot-tests/suites/device-suite.ts:13-211`
-**Conclusion:** Needed broader context and direct reproductions.
-
-### Broad code-path review
-**Hypothesis:** Shared test/build code or transport handling regressed.
-**Findings:** The device suite and entrypoint are effectively unchanged from `main`; the meaningful snapshot-harness changes vs `main` are timeout increases and normalization/MCP envelope handling, not device workflow logic.
-**Evidence:**
-- `src/snapshot-tests/suites/device-suite.ts` is unchanged in substance versus `main`.
-- `src/snapshot-tests/__tests__/device.snapshot.test.ts` is unchanged versus `main`.
-- `src/snapshot-tests/harness.ts:8-36` increased CLI snapshot timeout from `120000` to `300000`.
-- `src/snapshot-tests/mcp-harness.ts:9-13,93-105` increased MCP timeout from `120000` to `300000` and now prefers `structuredEnvelope.didError`.
-- `src/snapshot-tests/normalize.ts` changes are normalization-only.
-**Conclusion:** The reported failures are not explained by a direct diff in the device suite itself.
-
-### CLI `build-and-run` direct reproduction
-**Hypothesis:** CLI is falsely classifying a successful build-and-run as an error because of render-session error latching.
-**Findings:** Isolated `device build-and-run` really failed on the first run. The exit code was `1`, and the output contained a real `devicectl` install failure:
-- `Unable to Install “Calculator”`
-- `ApplicationVerificationFailed`
-- `No code signature found`
-A second immediate rerun succeeded with exit code `0`.
-**Evidence:**
-- Direct run exit code capture: `/tmp/build-run.rc` contained `1`.
-- Direct run output: `/tmp/build-run.out` contained the `devicectl` install failure text.
-- Immediate rerun: `/tmp/build-run-2.rc` contained `0` and `/tmp/build-run-2.out` showed a complete success transcript.
-- The failed build log still ended with successful signing and `** BUILD SUCCEEDED **`: `/Users/cameroncooke/Library/Developer/XcodeBuildMCP/logs/build_run_device_2026-04-16T19-59-14-443Z_pid38742.log`.
-**Conclusion:** This is not primarily a false CLI `isError` classification bug. The combined flow hit a genuine but flaky device-side install failure.
-
-### Build artifact validation after failed `build-and-run`
-**Hypothesis:** The refactor produced an actually unsigned app artifact.
-**Findings:** The app at the resolved path was signed correctly, and a direct `device install` of that same app path succeeded.
-**Evidence:**
-- `device get-app-path` succeeded and pointed to `~/Library/Developer/XcodeBuildMCP/DerivedData/Build/Products/Debug-iphoneos/CalculatorApp.app`.
-- `xcrun codesign -dvvv` against that app showed a valid Apple Development signature, team identifier, `_CodeSignature`, and `embedded.mobileprovision`.
-- Direct install succeeded with exit code `0` and output `✅ App installed successfully.` in `/tmp/install-device.out`.
-**Conclusion:** The artifact itself was valid. The first `build-and-run` failure is best explained as transient `devicectl` / physical-device install flakiness, not a deterministic signing regression in the build step.
-
-### Fresh DerivedData isolation
-**Hypothesis:** Shared default DerivedData corruption is causing the `build-and-run` failure deterministically.
-**Findings:** Running `device build-and-run` with a fresh temporary `derivedDataPath` succeeded on the first try.
-**Evidence:** `/tmp/fresh-build-run.rc` contained `0`; `/tmp/fresh-build-run.out` contained a full success transcript.
-**Conclusion:** There is no evidence of a deterministic combined-flow failure tied solely to the current implementation. The failure is transient/stateful.
-
-### Direct device-test reproductions
-**Hypothesis:** `test-common.ts`, parser/finalization code, or CLI/MCP transport handling regressed for failing device tests.
-**Findings:** The failing full-device test works normally in isolation and in focused back-to-back runs.
-**Evidence:**
-- Direct CLI full failing test exited `1` and completed in about 10s with the full footer in `/tmp/device-test.out`.
-- Back-to-back direct CLI runs (targeted pass, then full fail) completed normally: `targeted rc=0 dur=8.7s`, `fullfail rc=1 dur=6.8s`.
-- Back-to-back snapshot MCP harness calls also completed normally: targeted `isError:false` in ~9.2s, full fail `isError:true` in ~6.1s.
-**Conclusion:** The underlying device-test code path is not deterministically broken. The suite-only failure requires a broader state/order interaction.
-
-### Post-test diagnostics reverse engineering
-**Hypothesis:** The hang happens after test execution, in Apple’s diagnostics collection path rather than in XcodeBuildMCP parsing/rendering.
-**Findings:** That hypothesis is confirmed.
-**Evidence:**
-- Raw PTY-backed `xcodebuild test` reproduced the exact symptom and captured `Password:` immediately after the final XCTest summary in `/tmp/pty-xcodebuild-test.typescript`.
-- During that stall, the live child process was:
- - `/Library/Developer/PrivateFrameworks/CoreDevice.framework/Versions/A/Resources/bin/devicectl diagnose --devices 00008140-000278A438E3C01C --no-finder --archive-destination ... --timeout 600`
-- Process state showed `devicectl` was running in its own process group on the same TTY, but **not** the foreground TTY process group:
- - `pgid=15787`, `tpgid=15723`, `state=T`
-- That means the subprocess was stopped after attempting terminal interaction from a background TTY process group.
-- Running the same `xcodebuild test` command **without** a TTY did not hang. It completed and printed Xcode’s own diagnostic failure text:
- - `Failure collecting diagnostics from devices`
- - `No provider was found`
- - `CoreDeviceCLISupport.DiagnoseError error 0`
-- Running `devicectl diagnose` directly under a PTY, with no `xcodebuild` involved, reproduced the same interactive path in `/tmp/pty-devicectl-diagnose.typescript`:
- - provisioning/provider error text
- - Apple privacy notice
- - `Password:`
- - sending a blank line produced `Sorry, try again.` and another `Password:` prompt
-- Running `devicectl diagnose` directly **without** a TTY did not prompt; it failed fast and wrote a partial bundle.
-**Conclusion:** The bad path is in Apple’s `devicectl diagnose` TTY behavior. `xcodebuild` makes it worse by launching that interactive subprocess from a background terminal process group, which wedges the run.
-
-## Root Cause
-The root cause of the hang is Apple’s post-test diagnostics flow for failing physical-device tests:
-
-1. After the failing test summary, `xcodebuild test` launches `devicectl diagnose` to collect device diagnostics.
-2. On a TTY, `devicectl diagnose` enters an interactive authorization path and prints `Password:`.
-3. When launched by `xcodebuild`, that subprocess is in a background TTY process group, so terminal input cannot be handled normally and the subprocess gets wedged.
-4. In non-TTY mode, the same path does not block on a password prompt; it exits with the real underlying diagnostics failure (`No provider was found`, `CoreDeviceCLISupport.DiagnoseError error 0`).
-
-This means the snapshot hang is not primarily caused by:
-- `src/utils/test-common.ts`
-- `src/utils/xcresult-test-failures.ts`
-- `src/utils/xcodebuild-event-parser.ts`
-- `src/utils/renderers/cli-text-renderer.ts`
-- `src/utils/command.ts`
-- `src/snapshot-tests/mcp-harness.ts`
-
-The earlier transient `build-and-run` install failure is real, but it is a separate flaky physical-device issue, not the cause of the `Password:` hang.
-
-## Eliminated hypotheses
-- **Deterministic CLI error-latch bug for `build-and-run`** — ruled out by the captured real install failure text and exit code `1`.
-- **Deterministic parser/renderer regression dropping the test footer** — ruled out by successful isolated CLI and MCP failing-test runs.
-- **Deterministic MCP transport deadlock** — ruled out by successful focused MCP harness pass→fail reproduction.
-- **Deterministic signing regression in the built app artifact** — ruled out by successful codesign inspection and direct `device install` of the same app path.
-
-## Recommendations
-1. Treat the `Password:` hang as an Apple `devicectl diagnose` / Xcode physical-device diagnostics problem, not as evidence of a deterministic XcodeBuildMCP refactor regression.
-2. For XcodeBuildMCP’s automated/device-test paths, prefer **non-interactive process mode** and preserve full stderr/stdout so Xcode’s explicit diagnostics failure is surfaced instead of a hung terminal prompt.
-3. Add timeout diagnostics around failing physical-device test runs that explicitly note whether the process appears to be stuck in post-test diagnostics collection.
-4. Keep the timeout at `120_000`; increasing it just makes this Apple diagnostics wedge slower to fail.
-5. Separately from the hang, keep the earlier `build-and-run` flake in mind as a real but distinct physical-device reliability issue.
-
-## Preventive Measures
-- Keep physical-device snapshot tests minimal and isolated.
-- Avoid chaining many mutating device operations in one snapshot file.
-- Capture direct per-step logs/artifacts for device tests so transient `devicectl` failures are visible without rerunning the whole file.
-- Be careful about interpreting longer timeouts as fixes; here they mainly make the suite slower when the device gets wedged.
diff --git a/docs/dev/device-snapshot-password-hang-rca.md b/docs/dev/device-snapshot-password-hang-rca.md
deleted file mode 100644
index dd9405ec2..000000000
--- a/docs/dev/device-snapshot-password-hang-rca.md
+++ /dev/null
@@ -1,50 +0,0 @@
-# RCA: Physical-device snapshot test hang after failing test
-
-## Summary
-When a physical-device `xcodebuild test` run fails, Xcode launches `devicectl diagnose` to collect post-failure diagnostics. On an interactive terminal, `devicectl diagnose` prompts for a local macOS password before starting diagnostics collection. When Xcode launches that same interactive flow from the test process, it can wedge and make device snapshot tests appear to hang.
-
-## Symptoms
-- A physical-device snapshot test stalls after the final XCTest summary.
-- In a terminal/PTTY run, a transient or persistent `Password:` line appears after the failing test summary.
-- MCP/snapshot runs may sit until timeout instead of surfacing the real diagnostics failure.
-
-## Confirmed evidence
-- The hang happens after test execution, not during the app test run itself.
-- After a failing physical-device test, `xcodebuild` launches:
- ```bash
- /Library/Developer/PrivateFrameworks/CoreDevice.framework/Versions/A/Resources/bin/devicectl diagnose ...
- ```
-- Running `devicectl diagnose` directly on a terminal reproduces the password gate:
- - prints Apple privacy text
- - prompts with `Password:`
- - after local auth, proceeds with diagnostics collection and completes
-- Running the same `xcodebuild test` command without a TTY does not hang; instead it exits and prints the underlying diagnostics error:
- - `Failure collecting diagnostics from devices`
- - `No provider was found`
- - `CoreDeviceCLISupport.DiagnoseError error 0`
-
-## Root cause
-This is an Apple tooling issue in the post-failure device diagnostics path:
-
-1. A failing physical-device test triggers `devicectl diagnose`.
-2. `devicectl diagnose` can require interactive local macOS authentication.
-3. In the `xcodebuild`-launched context, that interactive auth path may not be able to complete cleanly, so the run wedges.
-
-## Scope
-This explains hangs after failing physical-device tests. It does not explain unrelated build/install flake in other device flows.
-
-## Practical guidance
-- If a physical-device snapshot test hangs after the final test summary, check for a `Password:` prompt.
-- Prefer non-interactive execution when automating this flow so the command fails fast instead of hanging.
-- Do not treat longer timeouts as a fix; they only make this Apple diagnostics wedge slower to fail.
-
-## Useful manual repro
-```bash
-/Library/Developer/PrivateFrameworks/CoreDevice.framework/Versions/A/Resources/bin/devicectl diagnose \
- --devices \
- --no-finder \
- --archive-destination /tmp/devicectl-live-sample.zip \
- --timeout 600
-```
-
-If prompted and local auth succeeds, the command proceeds with diagnostics collection and writes a zip archive.
diff --git a/docs/dev/doctor-representativeness-investigation.md b/docs/dev/doctor-representativeness-investigation.md
deleted file mode 100644
index ca20c45a8..000000000
--- a/docs/dev/doctor-representativeness-investigation.md
+++ /dev/null
@@ -1,129 +0,0 @@
-# Investigation: Doctor Tool Representativeness Audit
-
-## Summary
-`doctor` is partially representative, but it has material drift in dependency modeling, runtime/workflow accuracy, and messaging. The biggest issues are false-green/false-red states and one side-effecting check (`xcodemake`) inside a diagnostic command.
-
-## Symptoms
-- Concern that `doctor` output no longer matches current manifest-driven tool/workflow architecture.
-- Concern that workflow/environment/dependency checks are incomplete or misleading.
-
-## Investigation Log
-
-### Phase 1 - Initial assessment
-**Hypothesis:** Doctor may have drifted from implementation after manifest/runtime refactors.
-**Findings:** Doctor report is assembled from mixed sources (static manifest summaries + runtime registry snapshots + ad-hoc binary checks).
-**Evidence:** `src/mcp/tools/doctor/doctor.ts:102-141`, `src/mcp/tools/doctor/lib/doctor.deps.ts:217-251`
-**Conclusion:** Confirmed; this mixed model introduces representativeness gaps.
-
-### Phase 2 - Core doctor behavior audit
-**Hypothesis:** Doctor schema and output include stale/unused behavior.
-**Findings:** `enabled` exists in schema and tests but is not used by logic.
-**Evidence:** `src/mcp/tools/doctor/doctor.ts:24-26`, `src/mcp/tools/doctor/doctor.ts:102-137`, `src/mcp/tools/doctor/__tests__/doctor.test.ts:131-140`
-**Conclusion:** Confirmed stale surface area.
-
-### Phase 3 - Dependency and feature checks
-**Hypothesis:** Dependency checks are not aligned with runtime behavior.
-**Findings:**
-- Doctor hardcodes `['axe','xcodemake','mise']` for dependencies.
-- Doctor calls `isXcodemakeAvailable()` which can download/install xcodemake (side effect).
-- Doctor reports UI automation as supported if AXe exists, but video capture requires AXe >= 1.1.0.
-**Evidence:**
-- `src/mcp/tools/doctor/doctor.ts:107-111`
-- `src/mcp/tools/doctor/doctor.ts:127-130`
-- `src/utils/xcodemake.ts:90-105`, `src/utils/xcodemake.ts:156-157`
-- `src/mcp/tools/doctor/doctor.ts:288-292`
-- `src/mcp/tools/simulator/record_sim_video.ts:81-84`
-**Conclusion:** Confirmed false-green/false-red and side-effect risk.
-
-### Phase 4 - Workflow/runtime representativeness
-**Hypothesis:** Doctor's tool/workflow summaries do not match actual runtime exposure rules.
-**Findings:**
-- Plugin summary totals are derived from manifest workflow memberships, not runtime predicate-filtered exposure.
-- Runtime registration can be unavailable (doctor prints note) when registry wasn't initialized.
-- CLI doctor invokes `doctorLogic` directly without runtime bootstrap.
-**Evidence:**
-- `src/mcp/tools/doctor/lib/doctor.deps.ts:224-236`
-- `src/utils/tool-registry.ts:36-44`, `src/utils/tool-registry.ts:84-102`
-- `src/mcp/tools/doctor/doctor.ts:120-127`
-- `src/doctor-cli.ts:20-23`
-**Conclusion:** Confirmed reporting mismatch between manifest inventory and live exposure/registration.
-
-### Phase 5 - Bridge and docs consistency
-**Hypothesis:** Xcode IDE bridge docs and doctor/workflow behavior are inconsistent.
-**Findings:**
-- Docs claim gateway tools are shown only when `mcpbridge` is available.
-- Tool manifests only require `mcpRuntimeOnly` (not bridge availability).
-- Runtime actually fails at call-time when `mcpbridge` is missing.
-**Evidence:**
-- `docs/XCODE_IDE_MCPBRIDGE.md:25`
-- `manifests/tools/xcode_ide_list_tools.yaml:7-8`, `manifests/tools/xcode_ide_call_tool.yaml:7-8`
-- `src/integrations/xcode-tools-bridge/tool-service.ts:145-149`
-**Conclusion:** Confirmed docs/runtime mismatch.
-
-### Phase 6 - Environment and test coverage
-**Hypothesis:** Doctor env reporting and tests may not cover current config surface.
-**Findings:**
-- Doctor env list is partially hardcoded + `XCODEBUILDMCP_*` prefix scan.
-- Config also reads non-prefix env inputs (for example `AXE_PATH`, `XBMCP_LAUNCH_JSON_WAIT_MS`) not explicitly surfaced as first-class doctor checks.
-- Doctor tests mostly assert text existence/type instead of semantic correctness.
-**Evidence:**
-- `src/mcp/tools/doctor/lib/doctor.deps.ts:164-184`
-- `src/utils/config-store.ts:197-198`, `src/utils/config-store.ts:224`
-- `src/mcp/tools/doctor/__tests__/doctor.test.ts:152`, `:172`, `:191`, `:217`, `:283`
-**Conclusion:** Confirmed gaps in report fidelity and regression safety.
-
-### Phase 7 - History check
-**Hypothesis:** Recent architecture/tool changes may have outpaced doctor updates.
-**Findings:**
-- Large manifest/runtime refactor landed (`907cfe3c`, Feb 4, 2026).
-- xcode-ide/gating changes landed (`9b182d46`, Feb 5, 2026; `898819b9`, Feb 15, 2026).
-- Doctor changed during this period but still retains mismatches above.
-**Evidence:** Git history via `git log/show` on doctor, manifests, and xcode-ide files.
-**Conclusion:** Drift is plausible and observable post-refactor.
-
-## Root Cause
-Doctor currently blends multiple data models with different semantics:
-1. **Static manifest inventory** (workflow membership totals)
-2. **Live runtime registry snapshot** (only after registration runs)
-3. **Direct environment/binary probes** (including side-effecting xcodemake path)
-
-Because these sources are not normalized into one explicit capability model, the output can be internally inconsistent and not fully representative of what tools will actually work in the current runtime/context.
-
-## Eliminated Hypotheses
-- **"Doctor is totally disconnected from current manifests"**: eliminated. It does read manifests (`doctor.deps.ts:220-236`).
-- **"Doctor has no bridge visibility"**: eliminated. It does report bridge status and proxied count (`doctor.ts:329-340`).
-- **"No recent doctor maintenance"**: eliminated. Doctor-related files changed during Feb 2026 refactors.
-
-## Recommendations
-1. **Make doctor read-only (P0)**
- - Replace `isXcodemakeAvailable()` usage in doctor with side-effect-free `isXcodemakeBinaryAvailable()`.
- - Keep auto-install behavior in build execution paths, not diagnostics.
-
-2. **Separate inventory/exposure/registration views (P0)**
- - Report 3 explicit counts:
- - Manifest inventory
- - Exposed under current predicate context
- - Actually registered now
- - Avoid presenting manifest totals as runtime availability.
-
-3. **Capability-based dependency checks (P1)**
- - Gate checks by enabled workflows + selected backend/config.
- - Add AXe version capability check for video capture (`>=1.1.0`).
- - Add explicit bridge dependency health to gateway-tool readiness section.
-
-4. **Fix docs/runtime inconsistencies (P1)**
- - Correct `docs/XCODE_IDE_MCPBRIDGE.md` claim about visibility on `mcpbridge` availability, or add a predicate/system that enforces that claim.
- - Adjust `docs/TROUBLESHOOTING.md` wording (“all dependencies required”) to reflect scope-based checks.
-
-5. **Improve env and workflow-management visibility (P2)**
- - Surface key non-prefix env vars that materially alter runtime behavior (for example `AXE_PATH`, `XBMCP_LAUNCH_JSON_WAIT_MS`).
- - Include current predicate context summary (runtime/debug/runningUnderXcode) so workflow results are explainable.
-
-6. **Strengthen tests (P2)**
- - Add semantic assertions (specific sections/flags/warnings) instead of mostly `typeof text === 'string'`.
- - Add regression tests for false-green/false-red scenarios (AXe version mismatch, xcodemake unavailable but disabled, runtime registry missing).
-
-## Preventive Measures
-- Define a typed internal "doctor capability model" (single source for checks and statuses) and render output from that model only.
-- Add contract tests that compare doctor output against manifest/runtime predicate expectations for known fixture configs.
-- Add CI check to fail if docs claims about tool visibility/dependencies contradict manifest predicates or runtime gating behavior.
diff --git a/docs/dev/investigation-simulator-test-hang-2026-04-11.md b/docs/dev/investigation-simulator-test-hang-2026-04-11.md
deleted file mode 100644
index ada175d1f..000000000
--- a/docs/dev/investigation-simulator-test-hang-2026-04-11.md
+++ /dev/null
@@ -1,170 +0,0 @@
-# Investigation: Simulator test hang and EMFILE failures
-
-## Summary
-XcodeBuildMCP definitively leaks detached simulator OSLog stream processes from the simulator launch-with-logging path. Real-world verification showed that production code created orphaned `simctl spawn log stream ...` processes under PID 1, the normal `simulator stop` command did not clean them up, and repeated launches accumulated additional survivors. After clearing the leaked processes, the previously unhealthy simulator recovered: `xcrun simctl get_app_container ...` completed successfully in `259 ms`, and a real `node build/cli.js simulator test ...` run progressed through `test-without-building`, executed tests, and returned control to the terminal instead of hanging at `Process spawn via launchd failed`. That makes the leak the confirmed cause of the broken simulator state behind the original symptom.
-
-## Symptoms
-- `build-for-testing` succeeds.
-- `test-without-building` reports `Process spawn via launchd failed` and `Too many open files`.
-- `NSPOSIXErrorDomain Code 24` appears in Xcode output.
-- The failing `xcodebuild` process can remain alive after printing the error.
-- On the machine, dozens of orphaned `simctl spawn ... log stream ...` processes existed for the same simulator and bundle.
-
-## Investigation Log
-
-### Phase 1 - Initial machine assessment
-**Hypothesis:** The simulator/Xcode environment had accumulated leaked processes or descriptors from earlier E2E/manual work.
-**Findings:** The shell limit was high, but launchd soft maxfiles was low, and the machine had many orphaned simulator log-stream processes plus multiple stuck `xcodebuild` instances.
-**Evidence:**
-- `ulimit -n` returned `1048575`.
-- `launchctl limit maxfiles` returned soft `256`, hard `unlimited`.
-- `ps` showed **81** live `simctl spawn 01DA97D9-3856-46C5-A75E-DDD48100B2DB log stream --level=debug --predicate subsystem == "io.sentry.calculatorapp"` processes under PID 1.
-- `ps -p 46498,48422 -o pid,ppid,stat,etime,command` showed two stuck `xcodebuild ... test-without-building` processes still alive.
-- `lsof -p 46498 | wc -l` and `lsof -p 48422 | wc -l` each showed about 200 open fds.
-**Conclusion:** Confirmed degraded local environment. Needed to separate product leak from broader simulator/Xcode damage.
-
-### Phase 2 - Code path identification
-**Hypothesis:** Recent simulator launch work in XcodeBuildMCP explicitly creates detached OSLog stream processes and fails to manage their lifecycle.
-**Findings:** `launchSimulatorAppWithLogging()` starts an OSLog stream via `startOsLogStream()`. That helper spawns detached `xcrun simctl spawn ... log stream` children and immediately `unref()`s them. `stop_app_sim` only terminates the app and does not stop the OSLog stream. Session tracking only accounts for `activeLogSessions` from `log_capture.ts`, not these detached children.
-**Evidence:**
-- `src/utils/simulator-steps.ts:205` calls `startOsLogStream(...)`.
-- `src/utils/simulator-steps.ts:251` defines `startOsLogStream`.
-- `src/utils/simulator-steps.ts:278` sets `detached: true`.
-- `src/utils/simulator-steps.ts:281` calls `child.unref()`.
-- `src/mcp/tools/simulator/stop_app_sim.ts:58` only runs `['xcrun', 'simctl', 'terminate', simulatorId, params.bundleId]`.
-- `src/utils/log_capture.ts:60` stores tracked sessions in `activeLogSessions`.
-- `src/utils/log-capture/index.ts:10-11` lists only `activeLogSessions` ids.
-- `src/utils/session-status.ts:52` reports simulator active session ids from `listActiveSimulatorLogSessionIds()`.
-**Conclusion:** Confirmed product design bug: detached OSLog children are created outside the tracked log-session lifecycle.
-
-### Phase 3 - Real-world proof that production code creates orphaned OSLog children
-**Hypothesis:** The production simulator launch helper is capable of creating the exact orphaned `simctl spawn ... log stream ...` processes observed on the machine.
-**Findings:** After clearing all existing matching orphan processes, running the production `launchSimulatorAppWithLogging()` helper created a new detached `simctl spawn ... log stream ...` process under PID 1 for the exact simulator and bundle under investigation.
-**Evidence:**
-- Before the controlled run, matching process count was `0`.
-- Production helper invocation used built code:
- ```sh
- node --input-type=module - <<'NODE'
- import { launchSimulatorAppWithLogging } from './build/utils/simulator-steps.js';
- const fakeExecutor = async () => ({ success: true, output: 'io.sentry.calculatorapp: 123', process: { pid: 123 }, exitCode: 0 });
- const result = await launchSimulatorAppWithLogging(
- '01DA97D9-3856-46C5-A75E-DDD48100B2DB',
- 'io.sentry.calculatorapp',
- fakeExecutor,
- );
- console.log(JSON.stringify(result, null, 2));
- NODE
- ```
-- That helper returned success and produced an OSLog file:
- `/Users/cameroncooke/Library/Developer/XcodeBuildMCP/logs/io.sentry.calculatorapp_oslog_2026-04-11T08-46-40-929Z_pid62912.log`
-- Immediately afterward, process table showed:
- `62966 1 00:11 /Library/Developer/PrivateFrameworks/CoreSimulator.framework/Versions/A/Resources/bin/simctl spawn 01DA97D9-3856-46C5-A75E-DDD48100B2DB log stream --level=debug --predicate subsystem == "io.sentry.calculatorapp"`
-- The OSLog file contained runtime output from the launched app, including:
- `Calculator app launched`
-**Conclusion:** Definitively confirmed. The observed orphan command line is created by XcodeBuildMCP production code.
-
-### Phase 4 - Real-world proof that normal stop does not clean up the leaked child
-**Hypothesis:** The normal stop tool leaves the detached OSLog stream running.
-**Findings:** Running the normal stop command successfully terminated the app, but the detached log-stream process remained alive under PID 1.
-**Evidence:**
-- Stop command used:
- ```sh
- node build/cli.js simulator stop --simulator-id 01DA97D9-3856-46C5-A75E-DDD48100B2DB --bundle-id io.sentry.calculatorapp --output text
- ```
-- CLI output reported `App stopped successfully`.
-- After stop, process table still showed:
- `62966 1 00:20 /Library/Developer/PrivateFrameworks/CoreSimulator.framework/Versions/A/Resources/bin/simctl spawn 01DA97D9-3856-46C5-A75E-DDD48100B2DB log stream --level=debug --predicate subsystem == "io.sentry.calculatorapp"`
-- Matching process count remained `1`.
-**Conclusion:** Definitively confirmed. `simulator stop` does not stop the detached OSLog stream child created by launch-with-logging.
-
-### Phase 5 - Real-world proof of accumulation
-**Hypothesis:** Repeated launches can accumulate additional detached OSLog stream survivors.
-**Findings:** A second invocation of the same production helper created another survivor. Two distinct PIDs were alive concurrently under PID 1.
-**Evidence:**
-- Second production helper invocation returned success and wrote a second OSLog file:
- `/Users/cameroncooke/Library/Developer/XcodeBuildMCP/logs/io.sentry.calculatorapp_oslog_2026-04-11T08-47-12-997Z_pid63093.log`
-- Process table then showed both:
- - `62966 1 00:49 /Library/Developer/PrivateFrameworks/CoreSimulator.framework/Versions/A/Resources/bin/simctl spawn ...`
- - `63142 1 00:17 /Library/Developer/PrivateFrameworks/CoreSimulator.framework/Versions/A/Resources/bin/simctl spawn ...`
-- Matching process count was `2`.
-- The first OSLog file later contained output from both launches, while the second file also contained output from the second launch, showing overlapping capture behavior.
-**Conclusion:** Definitively confirmed. The leak accumulates across repeated launches.
-
-### Phase 6 - Short-timeout false positive and post-cleanup recovery
-**Hypothesis:** The earlier `get_app_container` timeout may have been a measurement artifact, and the real question is whether the simulator recovers once the leaked processes are removed.
-**Findings:** The earlier 5-second probe was too aggressive and not a reliable signal. After cleanup, rerunning the simulator metadata command with a realistic timeout completed successfully in `259 ms`. A real `simulator test` run then progressed through `build-for-testing`, entered `test-without-building`, executed tests, and returned control to the terminal. The failure mode changed from `Process spawn via launchd failed / Code 24 / blinking cursor` to ordinary test execution with the fixture project's expected intentional failures.
-**Evidence:**
-- Cleanup removed all matching orphan streams: count changed from `81` to `0`.
-- Post-cleanup verification command:
- ```sh
- python3 - <<'PY'
- import subprocess, time
- cmd = ['xcrun','simctl','get_app_container','01DA97D9-3856-46C5-A75E-DDD48100B2DB','io.sentry.calculatorapp','app']
- start = time.time()
- out = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
- print('STATUS:completed')
- print('RC:', out.returncode)
- print('ELAPSED_MS:', int((time.time()-start)*1000))
- print('STDOUT:', out.stdout.strip())
- PY
- ```
- returned `STATUS:completed`, `RC: 0`, and `ELAPSED_MS: 259` with a valid app-container path.
-- Real test command:
- ```sh
- node build/cli.js simulator test --workspace-path /Volumes/Developer/XcodeBuildMCP/example_projects/iOS_Calculator/CalculatorApp.xcworkspace --scheme CalculatorApp --simulator-id 01DA97D9-3856-46C5-A75E-DDD48100B2DB --output raw
- ```
- completed and returned exit code `1` only because the fixture suite contains intentional failures. Final output included:
- - `IDETestOperationsObserverDebug: 16.342 elapsed -- Testing started completed.`
- - `** TEST EXECUTE FAILED **`
- - result bundle path under `/Users/cameroncooke/Library/Developer/XcodeBuildMCP/DerivedData/Logs/Test/...`
-- Critically, the rerun did **not** reproduce `Process spawn via launchd failed`, `NSPOSIXErrorDomain Code 24`, or the terminal hang.
-**Conclusion:** Confirmed. Clearing the leaked OSLog stream processes restored simulator health sufficiently for the original test path to run normally. The leak is the verified cause of the broken simulator state behind the original symptom.
-
-### Phase 7 - Eliminated hypotheses
-**Hypothesis:** `test_sim` directly creates the leaked `simctl spawn ... log stream ...` children.
-**Findings:** `test_sim` does not call `launchSimulatorAppWithLogging()` or `startOsLogStream()`.
-**Evidence:**
-- `src/mcp/tools/simulator/test_sim.ts` routes into `src/utils/test-common.ts` and xcodebuild orchestration, not simulator launch-with-logging.
-- The actual spawn site is `src/utils/simulator-steps.ts:251-281`.
-**Conclusion:** Eliminated. `test_sim` is not the direct source of the observed orphan stream processes.
-
-## Root Cause
-There is a definitive product-side leak in XcodeBuildMCP’s simulator launch-with-logging path.
-
-`launchSimulatorAppWithLogging()` in `src/utils/simulator-steps.ts` starts OSLog capture by calling `startOsLogStream()` (`src/utils/simulator-steps.ts:205`). `startOsLogStream()` then launches:
-
-```ts
-xcrun simctl spawn log stream --level=debug --predicate subsystem == ""
-```
-
-using `detached: true` (`src/utils/simulator-steps.ts:278`) and immediately `child.unref()` (`src/utils/simulator-steps.ts:281`). The process handle is not stored in any registry, and the normal stop path only runs `simctl terminate` on the app (`src/mcp/tools/simulator/stop_app_sim.ts:58`). Therefore these OSLog stream children are not tied to app lifecycle, not visible in tracked simulator log-session state, and not cleaned up by the normal stop tool.
-
-This was verified with real production code and real process-table inspection:
-- the helper created the exact orphaned `simctl spawn ... log stream ...` process shape seen in the field,
-- the process lived under PID 1 after the parent exited,
-- `simulator stop` did not remove it,
-- and repeated launches accumulated multiple survivors.
-
-This was verified end-to-end with real recovery evidence: after removing the leaked stream processes, simulator metadata calls succeeded again and the original `simulator test` command stopped failing in the launch path. In other words, `Code 24` was Xcode/CoreSimulator reporting the downstream effect — an unhealthy simulator launch environment caused by the leaked detached helpers — rather than a separate root cause inside the test suite itself.
-
-## Recommendations
-1. Track OSLog stream children from `src/utils/simulator-steps.ts` in an explicit registry.
- - File: `src/utils/simulator-steps.ts`
- - Record PID/process handle, simulator id, bundle id, and log path.
-2. Stop tracked OSLog stream children when the app is stopped.
- - File: `src/mcp/tools/simulator/stop_app_sim.ts`
- - Extend stop flow to terminate the matching OSLog stream(s), not just the app.
-3. Integrate detached simulator OSLog stream cleanup into shutdown/session lifecycle.
- - Files: `src/server/mcp-shutdown.ts`, `src/server/mcp-lifecycle.ts`, `src/utils/session-status.ts`
- - Ensure status reflects these children and shutdown cleans them.
-4. Add regression tests for lifecycle, not just launch success.
- - Files: `src/mcp/tools/simulator/__tests__/launch_app_sim.test.ts`, `src/mcp/tools/simulator/__tests__/stop_app_sim.test.ts`, `src/utils/__tests__/simulator-steps-pid.test.ts`
- - Assert launch creates tracked OSLog capture and stop/shutdown removes it.
-5. Add a doctor/cleanup path for existing leaked simulator OSLog streams.
- - Detect orphaned `simctl spawn log stream ...` helpers and terminate them before they poison later runs.
-
-## Preventive Measures
-- Never start detached helper processes without a matching ownership and teardown model.
-- Surface all long-lived simulator-side helpers in session status.
-- Add an integration test that repeatedly launches/stops an app and asserts no monotonic growth in matching `simctl`/`xcodebuild` processes.
-- Add a cleanup command or doctor check that detects and reports orphaned simulator OSLog streams.
diff --git a/docs/dev/investigation-snapshot-failures-2026-04-16.md b/docs/dev/investigation-snapshot-failures-2026-04-16.md
deleted file mode 100644
index 48008bd4d..000000000
--- a/docs/dev/investigation-snapshot-failures-2026-04-16.md
+++ /dev/null
@@ -1,114 +0,0 @@
-# Investigation: Snapshot Test Failures Post-Refactor
-
-## Summary
-
-6 snapshot test failures traced to 3 root causes: (1) `ensureNamedSimulatorStates` added in commit `1e60b101` boots up to 5 named simulators with no teardown, causing resource exhaustion and cross-suite state contamination; (2) device test invocations stall because the host environment is saturated by booted simulators from prior suites; (3) swift-package JSON diagnostics may be empty in MCP path due to a difference in how structured output captures fallback errors.
-
-## Symptoms
-
-1. Device test CLI/MCP: output truncated after test discovery — missing test failures, summary, build log
-2. Device test JSON: 300s timeout
-3. Simulator-management open JSON: Launch Services -1712 error
-4. Simulator test JSON: parity mismatch
-5. Swift-package build error JSON: empty diagnostics.errors
-6. 5 simulators booted when only 1 should be
-
-## Root Cause 1: Simulator State Contamination (Failures #3, #4, #6)
-
-### Evidence
-
-`ensureNamedSimulatorStates` was added in commit `1e60b101` and did NOT exist pre-refactor:
-
-```
-git show 9fbdebf6:src/snapshot-tests/harness.ts | grep -c "ensureNamedSimulatorStates"
-# Returns: 0
-```
-
-Three suites now boot multiple named simulators:
-
-- `simulator-management-suite.ts` line 34: boots iPhone 17 Pro, 17 Pro Max, 17e, Air, 17
-- `simulator-suite.ts` line 182: boots iPhone 17 Pro, 17 Pro Max
-- `resources-suite.ts` line 38: boots ALL 5 (iPhone 17 Pro, 17 Pro Max, 17e, Air, 17)
-
-No suite restores baseline state. With `maxThreads: 1` in `vitest.snapshot.config.ts`, suites run sequentially and share host state.
-
-### Impact
-
-- **Failure #6**: 5 booted simulators is directly caused by resources-suite booting all 5
-- **Failure #3**: Launch Services -1712 for `open_sim` is caused by Simulator.app resource contention with 5 booted sims
-- **Failure #4**: Simulator test JSON mismatch because the test environment state differs from when the fixture was hand-crafted
-
-### Fix
-
-Remove `ensureNamedSimulatorStates` and all multi-simulator boot calls. Restore the pre-refactor approach:
-- Use `ensureSimulatorBooted('iPhone 17')` for the primary test simulator
-- Use `createTemporarySimulator` for throwaway tests (boot, erase)
-- List tests should use deterministic mock data or accept normalized output, not force real simulator states
-
-## Root Cause 2: Device Test Stall from Resource Exhaustion (Failures #1, #2)
-
-### Evidence
-
-Direct CLI run produces full correct output in 6-30 seconds:
-```
-node build/cli.js device test --json '{"workspacePath":"...","scheme":"CalculatorApp","deviceId":"33689F72-..."}' 2>/dev/null | wc -l
-# Returns: 33 (full output)
-```
-
-spawnSync simulation also produces full output:
-```
-STATUS: 1, SIGNAL: null, STDOUT LINES: 34
-```
-
-But the snapshot test run only gets output up to test discovery. This means the xcodebuild invocation stalls during execution — likely because:
-- Multiple booted simulators from earlier suites consume system resources
-- CoreSimulator is busy managing 5 active simulators
-- Device communication competes with simulator I/O
-
-The test discovery is emitted before `executeXcodeBuildCommand` starts the actual test run. The truncation happens because the test run itself stalls or takes too long.
-
-### Impact
-
-- **Failure #1**: CLI device test output truncated
-- **Failure #2**: MCP/JSON device test timeout at 300s
-
-### Fix
-
-Fixing Root Cause 1 (no multi-simulator booting) will likely resolve this. Only iPhone 17 should be booted for the primary test simulator. The device test should have ample resources.
-
-## Root Cause 3: Swift-Package JSON Diagnostics (Failure #5)
-
-### Evidence
-
-CLI `--output json` produces correct diagnostics:
-```json
-"diagnostics": {
- "errors": [{"message": "chdir error: No such file or directory (2): ..."}]
-}
-```
-
-**When run in isolation, the JSON parity test PASSES.** The failure only occurs in the full suite run. This confirms it's cross-suite state contamination, not a code bug.
-
-The MCP and CLI paths use the same tool handler, same executor, same domain result builder. The diagnostics fallback logic (`collectFallbackDiagnosticEntries`) is independent of `liveProgressEnabled`. Static code analysis confirmed no MCP-specific diagnostic loss path exists.
-
-The failure in the full suite is caused by daemon state contamination from earlier test suites — the same pattern as the swift-package `list` stale processes issue. When the daemon has stale state, the tool execution environment differs, potentially causing the xcodebuild command to produce different error output.
-
-### Fix
-
-Fixing Root Cause 1 (simulator state contamination + proper daemon cleanup between suites) will resolve this. No code change needed in the diagnostic extraction path.
-
-## Recommendations
-
-1. **Remove `ensureNamedSimulatorStates` and all multi-simulator boot orchestration** — revert to pre-refactor approach of single booted sim + temporary sims for throwaway tests
-2. **Remove `ensureSimulatorState` function** — it was not in the pre-refactor code
-3. **Keep `ensureSimulatorBooted`** but only for iPhone 17 (the primary test sim)
-4. **For list snapshot tests** — either accept that list output is environment-dependent and normalize it, or skip those tests (as they were pre-refactor)
-5. **Investigate swift-package MCP diagnostic capture** — may need to ensure fallback errors are captured in the MCP execution path
-6. **Add afterAll cleanup** to any suite that modifies simulator state
-
-## Preventive Measures
-
-- Never boot real named simulators in snapshot tests — use temporary simulators for isolation
-- Each suite should clean up any state it creates
-- Add a global afterAll that shuts down any simulators booted during the test run
-- Validate snapshot tests pass on a clean environment before merging
diff --git a/docs/dev/investigation-snapshot-parity-2026-04-12.md b/docs/dev/investigation-snapshot-parity-2026-04-12.md
deleted file mode 100644
index 377687278..000000000
--- a/docs/dev/investigation-snapshot-parity-2026-04-12.md
+++ /dev/null
@@ -1,155 +0,0 @@
-# Investigation: Snapshot CLI/MCP parity audit
-
-## Summary
-CLI vs MCP snapshot **scenario parity is mostly good**: there are no CLI fixture scenarios missing an MCP counterpart. The real problems are **transport fidelity** and one **runtime-state skew**. Specifically, CLI snapshot tests for some stateful tools are not exercising the real CLI path, and a few UI automation success cases run with different logical preconditions across runtimes.
-
-## Symptoms
-- MCP parity was previously incomplete and some MCP output drifted into CLI-style next steps.
-- Snapshot harness logic recently regressed around simulator lookup and broader snapshot refactors raised concern about parity drift.
-- We needed to verify that for every CLI snapshot scenario there is an equivalent MCP scenario, and that the captured command uses the correct runtime harness.
-
-## Investigation Log
-
-### Fixture corpus comparison
-**Hypothesis:** Some CLI fixture scenarios may be missing MCP equivalents.
-
-**Findings:** Programmatic fixture comparison shows **no CLI-only fixture files**. MCP has 7 additional fixtures, but all are intentional MCP-only cases.
-
-**Evidence:**
-- Programmatic comparison result:
- - `cliCount: 135`
- - `mcpCount: 142`
- - `onlyCli: []`
- - `onlyMcp:`
- - `resources/devices--success.txt`
- - `resources/doctor--success.txt`
- - `resources/session-status--success.txt`
- - `resources/simulators--success.txt`
- - `session-management/session-set-defaults--scheme.txt`
- - `session-management/session-show-defaults--empty.txt`
- - `simulator/build--error-missing-params.txt`
-- Explicit MCP-only suite branches:
- - `src/snapshot-tests/suites/session-management-suite.ts:92`
- - `src/snapshot-tests/suites/simulator-suite.ts:246`
-- Resources are MCP-only by test entry design; there is no CLI resources snapshot entry.
-
-**Conclusion:** Confirmed: **1:1 fixture scenario parity exists for all CLI scenarios**. The fixture-count mismatch is explained by intentional MCP-only additions.
-
-### Harness wiring audit
-**Hypothesis:** Some snapshot suites may capture output through the wrong runtime path.
-
-**Findings:** MCP harness wiring is correct, but the CLI harness has a direct-invoke fallback that bypasses the CLI subprocess path for `stateful` tools.
-
-**Evidence:**
-- `src/snapshot-tests/suites/helpers.ts:5-10`
- - `createHarnessForRuntime('mcp')` uses `createMcpSnapshotHarness()`
- - `createHarnessForRuntime('cli')` uses `createSnapshotHarness()`
-- `src/snapshot-tests/mcp-harness.ts:71-122`
- - MCP snapshots use `client.callTool(...)` over stdio MCP transport.
-- `src/snapshot-tests/harness.ts:89-101`
- - CLI harness resolves tool manifest and does:
- - `if (resolved.isMcpOnly || resolved.isStateful) { return invokeDirect(...) }`
-- `src/snapshot-tests/harness.ts:116-142`
- - `invokeDirect(...)` renders with `postProcessSession(... runtime: 'mcp' ...)`
-- `src/snapshot-tests/tool-manifest-resolver.ts:29-36`
- - `isStateful` is derived from `tool.routing?.stateful === true`
-
-**Conclusion:** Confirmed: **CLI stateful-tool snapshots are not true CLI transport tests**. They bypass CLI execution and are post-processed as MCP text.
-
-### Debugging workflow parity
-**Hypothesis:** Debugging CLI fixtures are likely contaminated by the direct-invoke fallback.
-
-**Findings:** The debugging suite is scenario-parity complete, but CLI fixtures prove MCP formatting leaked into CLI output.
-
-**Evidence:**
-- `manifests/tools/debug_attach_sim.yaml:7`
- - `routing.stateful: true`
-- `src/snapshot-tests/suites/debugging-suite.ts:104-116`
- - main command under test is captured with `harness.invoke('debugging', 'attach', ...)`
-- `src/snapshot-tests/__fixtures__/cli/debugging/attach--success.txt:9-11`
- - CLI fixture contains MCP-style next steps:
- - `debug_breakpoint_add({ ... })`
- - `debug_continue({ ... })`
- - `debug_stack({ ... })`
-
-**Conclusion:** Confirmed: debugging has **scenario parity**, but CLI capture fidelity is broken for stateful commands.
-
-### Swift package workflow parity
-**Hypothesis:** Swift package stateful commands may also bypass real CLI capture.
-
-**Findings:** The suite covers the same scenarios across runtimes, but several CLI scenarios are effectively in-process/stateful snapshots rather than real CLI transport tests.
-
-**Evidence:**
-- `manifests/tools/swift_package_run.yaml:7`
- - `routing.stateful: true`
-- `manifests/tools/swift_package_list.yaml:7`
- - `routing.stateful: true`
-- `manifests/tools/swift_package_stop.yaml:7`
- - `routing.stateful: true`
-- `src/snapshot-tests/suites/swift-package-suite.ts:2`
- - suite imports `clearAllProcesses` directly from the active-process registry
-- `src/snapshot-tests/suites/swift-package-suite.ts:102-136`
- - `list` and `stop` scenarios manipulate or rely on in-process process state
-- `src/snapshot-tests/__fixtures__/cli/swift-package/stop--error-no-process.txt:5`
- - CLI fixture says: `Use swift_package_list to check active processes.`
- - This is MCP tool naming leaking into CLI output.
-
-**Conclusion:** Confirmed: swift-package has **scenario parity**, but the CLI transport contract is wrong for stateful scenarios (`run`, `list`, `stop`).
-
-### UI automation logical parity
-**Hypothesis:** Some UI automation success cases may differ by runtime because MCP preserves session state while CLI does not.
-
-**Findings:** The captured commands use the correct runtime harnesses, but four success scenarios run with different logical preconditions across runtimes.
-
-**Evidence:**
-- `src/snapshot-tests/suites/ui-automation-suite.ts:17-28`
- - suite reuses one harness per runtime and calls `simulator build-and-run` once in `beforeAll`
-- `src/snapshot-tests/suites/ui-automation-suite.ts:31-46`
- - `snapshot-ui` runs early in the suite before coordinate-based actions
-- `src/snapshot-tests/__fixtures__/cli/ui-automation/tap--success.txt:7`
- - CLI fixture includes warning:
- - `snapshot_ui has not been called yet`
-- `src/snapshot-tests/__fixtures__/mcp/ui-automation/tap--success.txt:1-6`
- - MCP fixture does **not** include that warning
-- By identical suite order plus persistent MCP harness state (`src/snapshot-tests/mcp-harness.ts:71-122`), MCP retains prior `snapshot-ui` state while CLI subprocess invocation does not.
-
-**Conclusion:** Confirmed: `tap--success`, `touch--success`, `long-press--success`, and `swipe--success` are **not logically identical scenarios** across runtimes, even though both are captured through their intended harnesses.
-
-## Root Cause
-There are two distinct issues:
-
-1. **Transport fidelity bug in CLI snapshot harness**
- - `src/snapshot-tests/harness.ts:89-101` routes `stateful` CLI tools to `invokeDirect(...)` instead of the real CLI subprocess path.
- - `src/snapshot-tests/harness.ts:129-134` then post-processes those snapshots as `runtime: 'mcp'`.
- - This causes CLI fixtures for stateful tools to capture MCP-style output, especially visible in debugging and swift-package fixtures.
-
-2. **Runtime-state skew in UI automation suite ordering**
- - `src/snapshot-tests/suites/ui-automation-suite.ts` runs `snapshot-ui` before several coordinate-based action tests while reusing one harness per runtime.
- - Because the MCP harness is persistent and the CLI harness is subprocess-per-command, the same test order produces different preconditions.
-
-These are separate from fixture-count parity. Fixture parity itself is largely correct.
-
-## Recommendations
-1. **Fix CLI transport capture for stateful tools**
- - File: `src/snapshot-tests/harness.ts`
- - Change CLI snapshot behavior so CLI-available tools always go through `invokeCli(...)`, even when `stateful`.
- - Only use direct invoke for truly MCP-only/internal cases if needed.
-
-2. **Re-record affected CLI fixtures after transport fix**
- - At minimum:
- - `src/snapshot-tests/__fixtures__/cli/debugging/*` stateful scenarios
- - `src/snapshot-tests/__fixtures__/cli/swift-package/run--*.txt`
- - `src/snapshot-tests/__fixtures__/cli/swift-package/list--*.txt`
- - `src/snapshot-tests/__fixtures__/cli/swift-package/stop--error-no-process.txt`
-
-3. **Restore logical parity in UI automation success cases**
- - File: `src/snapshot-tests/suites/ui-automation-suite.ts`
- - Minimal fix: move `snapshot-ui` success later, or otherwise ensure the four coordinate-based success scenarios run with the same `snapshot-ui` precondition across runtimes.
-
-4. **Do not treat intentional MCP-only files as parity failures**
- - Resources workflow and the two explicit MCP-only suite branches are acceptable deltas.
-
-## Preventive Measures
-- Add a small assertion/helper in CLI snapshot harness tests that fails if a CLI fixture is generated through `invokeDirect(...)` for CLI-available tools.
-- Keep parity checks focused on **registered suite scenarios**, not raw total fixture counts.
-- When adding runtime-specific extras, keep them explicit in `if (runtime === 'mcp')` branches so parity audits remain simple.
diff --git a/docs/investigations/mcp-json-output-bug-2026-04-24.md b/docs/investigations/mcp-json-output-bug-2026-04-24.md
deleted file mode 100644
index ae91fcb1a..000000000
--- a/docs/investigations/mcp-json-output-bug-2026-04-24.md
+++ /dev/null
@@ -1,117 +0,0 @@
-# Investigation: MCP Client Returns JSON Output
-
-## Summary
-XcodeBuildMCP is returning a valid dual-channel MCP tool result: human-readable text in `content[]` plus a JSON envelope in top-level `structuredContent`. The fixtures pass because MCP snapshots validate the text channel and JSON snapshots validate the structured channel separately; a real MCP client that prefers `structuredContent` will show JSON even though the text channel is correct.
-
-## Symptoms
-- Snapshot fixtures for MCP, CLI, and JSON output are correct.
-- When running XcodeBuildMCP in a real MCP client, tool calls return JSON output instead of the expected MCP text output.
-
-## Background / Prior Research
-
-### Git archaeology: structured output changes
-
-- Recent structured-output work introduced a two-channel MCP response: human-readable `content[0].text` plus `structuredContent` JSON envelope.
-- The snapshot tiers appear to validate different response fields: MCP fixtures validate rendered text content; JSON fixtures validate `structuredContent`; CLI fixtures validate CLI text output.
-- Relevant commits surfaced by the probe:
- - `3a1f548f` introduced `StructuredOutputEnvelope`, `ToolDomainResult`, and MCP `structuredContent` response data.
- - `cef40735`, `ba182dbd`, and `3df874a5` stabilized rendering/fixture parity.
- - `b4faec8b` changed streaming output to the domain-fragment model.
- - `bb786ff6` fixed CLI JSON error envelopes when structured output is absent.
- - `cc8f8467` is a WIP two-path output simplification that makes MCP/JSON non-streaming behavior stricter.
-- Preliminary conclusion: fixtures can all be correct while a real MCP client shows JSON because fixtures may snapshot `content[0].text` while the client displays `structuredContent`.
-
-### MCP protocol / SDK behavior
-
-- The installed SDK version is `@modelcontextprotocol/sdk` `1.27.1`.
-- MCP tool results support both `content` and `structuredContent`.
-- SDK/spec behavior: tools with an `outputSchema` must provide `structuredContent`; tools returning structured content should also provide a text `content` block for backwards compatibility.
-- The SDK validates that `structuredContent` is present when an output schema exists.
-- Client behavior is not uniform. The probe found that VS Code currently prefers `structuredContent` and passes JSON to the model instead of the `content` text, while some other clients use `content`.
-- Relevant external references surfaced by the probe:
- - MCP tools spec: https://modelcontextprotocol.io/docs/concepts/tools
- - MCP discussion: https://github.com/modelcontextprotocol/modelcontextprotocol/discussions/1563
- - MCP issue: https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1624
- - VS Code issue: https://github.com/microsoft/vscode/issues/290063
-
-## Investigator Findings
-
-### 2026-04-24 - MCP dual-channel response flow
-
-**Finding:** The hypothesis is supported, with one nuance: the MCP snapshot harness does not blindly read `content[0].text`; it iterates the MCP `content[]` array and refuses multiple text blocks, so the stored MCP fixture is effectively the single text block while `structuredContent` is separately captured.
-
-**Runtime response shape evidence:**
-- `src/utils/tool-registry.ts:19-48` converts a render session into the MCP `ToolResponse`. It builds `content` from `session.finalize()` text plus image attachments, then conditionally adds top-level `structuredContent` when `session.getStructuredOutput?.()` returns a value.
-- `src/utils/tool-registry.ts:299-327` is the native MCP handler path: create a text render session, run the tool handler, copy `ctx.structuredOutput` into the session, post-process next steps, then return `sessionToToolResponse(session)`.
-- `src/rendering/render.ts:36-99` stores fragments, attachments, and structured output in the same render session. `setStructuredOutput` also marks the session as an error when the domain result has `didError`.
-- `src/utils/structured-output-envelope.ts:10-22` converts domain results into the public JSON envelope by hoisting `schema`, `schemaVersion`, `didError`, and `error`, and putting the remaining domain fields under `data`.
-- Representative tool path: `src/mcp/tools/project-discovery/show_build_settings.ts:107-113` sets `ctx.structuredOutput` to schema `xcodebuildmcp.output.build-settings`; `src/mcp/tools/project-discovery/show_build_settings.ts:163-166` calls that setter after execution. The matching text fixture is human-readable at `src/snapshot-tests/__fixtures__/mcp/project-discovery/show-build-settings--success.txt:1-30`, while the JSON fixture starts with the structured envelope at `src/snapshot-tests/__fixtures__/json/project-discovery/show-build-settings--success.json:1-11`.
-
-**Snapshot harness evidence:**
-- `src/snapshot-tests/mcp-harness.ts:36-65` extracts only text blocks from `result.content`, throws on invalid content, and throws if more than one text block is present. This validates the human-readable text channel, not the full MCP tool result.
-- `src/snapshot-tests/mcp-harness.ts:68-78` separately reads top-level `result.structuredContent` into `structuredEnvelope`.
-- `src/snapshot-tests/mcp-harness.ts:111-121` returns both `text` and `structuredEnvelope`, but snapshot suites generally compare the `text` field via `expectFixture`; for example `src/snapshot-tests/suites/project-discovery-suite.ts:78-83` compares `text` for the MCP/runtime fixture.
-- `src/snapshot-tests/json-harness.ts:5-24` does not exercise the CLI `--output json` path. It wraps the MCP harness, requires `result.structuredEnvelope`, then formats that envelope as the JSON fixture.
-- `src/snapshot-tests/harness.ts:25-66` is the CLI text snapshot path: it spawns `node build/cli.js --json ` without `--output json` and snapshots stdout text.
-
-**CLI JSON path separation evidence:**
-- `src/cli/register-tool-commands.ts:93-116` implements real CLI `--output json` by writing `handlerContext.structuredOutput` through `toStructuredEnvelope`, or a synthetic error envelope if no structured output exists.
-- `src/cli/register-tool-commands.ts:347-391` chooses the render session based on `--output`; for `json`, it runs the invocation and then writes only the JSON envelope instead of finalizing text output.
-- Therefore the three observed fixture tiers validate distinct surfaces: CLI text stdout, MCP text `content[]`, and MCP `structuredContent` formatted as JSON. They do not prove that a real MCP client will prefer the text channel when both MCP channels are present.
-
-**Tool registration / `outputSchema` evidence:**
-- Native MCP registration in `src/utils/tool-registry.ts:290-297` passes `description`, `inputSchema`, and `annotations` to `server.registerTool`; it does not advertise `outputSchema`.
-- `outputSchema` does not appear in `manifests/` tool definitions. The only source hits are bridge metadata/types and the investigation report.
-- Dynamic Xcode Tools bridge proxy registration in `src/integrations/xcode-tools-bridge/registry.ts:94-119` also registers only `description`, `inputSchema`, `annotations`, and `_meta`; it does not pass remote `outputSchema` to the SDK.
-- Bridge metadata still preserves remote output schema information for listing/fingerprinting: `src/integrations/xcode-tools-bridge/core.ts:21-38` serializes `outputSchema`, and `src/integrations/xcode-tools-bridge/registry.ts:126-134` includes it in the stable fingerprint.
-- Bridge call results preserve remote `structuredContent` when present: `src/integrations/xcode-tools-bridge/bridge-tool-result.ts:30-57` copies `result.structuredContent` into the bridge payload.
-
-**Eliminated hypotheses:**
-- The MCP runtime is not selecting the CLI JSON renderer for native tool calls. It creates `createRenderSession('text')` in `src/utils/tool-registry.ts:301` and returns text plus optional `structuredContent`.
-- MCP fixtures are not validating the full MCP result object. They validate the extracted text channel for MCP fixtures and only use `structuredContent` for the separate JSON fixture tier.
-- `outputSchema` is not the trigger for native tools because it is not advertised during native `server.registerTool` registration. The structured JSON appears because tool handlers set `ctx.structuredOutput`, not because the client was given an output schema.
-
-**Conclusion:** XcodeBuildMCP is returning a valid dual-channel MCP result for structured tools: human-readable text in `content[]` and a machine-readable JSON envelope in top-level `structuredContent`. Snapshot coverage can remain green because MCP text fixtures check the text channel, while JSON fixtures intentionally check the structured channel. A real MCP client that prefers or forwards `structuredContent` will surface JSON even though the text channel is correct. The likely issue is client display/forwarding preference interacting with XcodeBuildMCP's unconditional `structuredContent` emission for structured tools, not a broken text renderer or fixture mismatch.
-
-## Investigation Log
-
-### Phase 1 - Initial Assessment
-**Hypothesis:** The runtime MCP server path may be selecting the JSON renderer or returning structured JSON fields in a way that real clients prefer over textual MCP content, while snapshot fixtures validate a different path.
-**Findings:** Report created; external git/protocol facts will be gathered before broad workspace selection.
-**Evidence:** User symptom report; current branch has extensive output-formatting changes in progress.
-**Conclusion:** Confirmed by later phases: the issue is not an accidental CLI JSON renderer path; it is a full MCP response containing both text and structured JSON, combined with client-side preference for the structured channel.
-
-## Root Cause
-
-XcodeBuildMCP emits `structuredContent` whenever a tool produces structured output, while still emitting human-readable text in `content[]`.
-
-The key path is `src/utils/tool-registry.ts:19-48`: `sessionToToolResponse()` finalizes text into `content`, then conditionally adds top-level `structuredContent` from `session.getStructuredOutput()`. Native MCP tool calls reach that through `src/utils/tool-registry.ts:299-327`, which creates a text render session, runs the tool, copies `ctx.structuredOutput` into the session, and returns the combined response.
-
-The test harnesses validate these channels separately:
-
-- `src/snapshot-tests/mcp-harness.ts:36-65` extracts text blocks from `result.content` for MCP fixtures.
-- `src/snapshot-tests/mcp-harness.ts:68-78` separately extracts `result.structuredContent`.
-- `src/snapshot-tests/json-harness.ts:5-24` formats that MCP `structuredContent` as the JSON fixture.
-- `src/cli/register-tool-commands.ts:93-116` and `src/cli/register-tool-commands.ts:347-391` show CLI `--output json` is a separate path, not what the JSON fixture harness exercises.
-
-So the observed real-client behavior is explained by a client displaying or forwarding `structuredContent` instead of the text content. This is especially plausible for clients with a structured-output-first policy. The text renderer is not broken, and MCP mode is not accidentally using the CLI JSON renderer.
-
-One design concern remains: native static tools currently do not advertise `outputSchema` during `server.registerTool()` (`src/utils/tool-registry.ts:290-297`), even though they may return `structuredContent`. That is not the immediate cause, but it is a weak public contract if structured MCP output is intended to be supported API.
-
-## Recommendations
-
-1. Decide the MCP API contract before changing code:
- - If structured MCP output is public API, keep `structuredContent`, add/advertise native `outputSchema`, and document that some clients may show JSON.
- - If the primary product goal is LLM-readable tool output, make normal MCP responses text-only or make structured MCP output explicitly opt-in.
-2. Do not try to fix this in the text renderer; the renderer is already producing the expected human-readable content.
-3. Add a full MCP result-shape test for representative tools such as `list_sims`, `session_show_defaults`, and `show_build_settings`, asserting both `content` text and `structuredContent` presence/absence according to the chosen contract.
-4. Add a `listTools` contract test that locks whether native tools advertise `outputSchema`.
-5. Add an explicit CLI `--output json` regression test if CLI JSON is a supported external contract, because current JSON fixtures are derived from MCP `structuredContent`, not the CLI JSON path.
-6. If the bug report is specifically from VS Code or another named MCP client, capture that client's exact full `CallToolResult` and display policy before choosing between text-only, opt-in structured output, or documentation/client-side mitigation.
-
-## Preventive Measures
-
-- Snapshot the full normalized MCP `CallToolResult`, not only extracted text and separately formatted JSON.
-- Maintain a small client-policy simulation test: one text-first policy and one structured-first policy. This would expose when a valid dual-channel response produces poor user-facing behavior in structured-first clients.
-- Treat `structuredContent` as public API if emitted to MCP clients: either advertise schemas and document the contract, or keep it behind an explicit mode.
-- Keep CLI JSON, MCP text, and MCP structured output tests clearly named so future fixture parity work does not conflate the three surfaces.
diff --git a/knip.json b/knip.json
index e29304bf4..e56be3cf6 100644
--- a/knip.json
+++ b/knip.json
@@ -1,7 +1,6 @@
{
"$schema": "https://unpkg.com/knip@5/schema.json",
"entry": [
- "scripts/check-code-patterns.js",
"scripts/probe-xcode-mcpbridge.ts",
"scripts/repro-mcp-parent-exit-helper.mjs",
"src/integrations/xcode-tools-bridge/__tests__/fixtures/fake-xcode-tools-server.mjs"
diff --git a/package.json b/package.json
index 08eecf999..4f0331052 100644
--- a/package.json
+++ b/package.json
@@ -37,8 +37,6 @@
"typecheck:tests": "npx tsc -p tsconfig.test.json",
"inspect": "npx @modelcontextprotocol/inspector@latest node build/cli.js mcp",
"doctor": "node build/doctor-cli.js",
- "docs:update": "npx tsx scripts/update-tools-docs.ts",
- "docs:update:dry-run": "npx tsx scripts/update-tools-docs.ts --dry-run --verbose",
"docs:check": "node scripts/check-docs-cli-commands.js",
"bench:test-sim": "npx tsx scripts/benchmark-simulator-test.ts",
"capture:xcodebuild": "npx tsx scripts/capture-xcodebuild-wrapper.ts",
diff --git a/scripts/check-code-patterns.js b/scripts/check-code-patterns.js
deleted file mode 100755
index 67f2d6fa6..000000000
--- a/scripts/check-code-patterns.js
+++ /dev/null
@@ -1,961 +0,0 @@
-#!/usr/bin/env node
-
-/**
- * XcodeBuildMCP Code Pattern Violations Checker
- *
- * Validates that all code files follow XcodeBuildMCP-specific architectural patterns.
- * This script focuses on domain-specific rules that ESLint cannot express.
- *
- * USAGE:
- * node scripts/check-code-patterns.js [--pattern=vitest|execsync|handler|handler-testing|all]
- * node scripts/check-code-patterns.js --help
- *
- * ARCHITECTURAL RULES ENFORCED:
- * 1. External boundaries in tests should use dependency-injection utilities
- * 2. NO execSync usage in production code (use CommandExecutor instead)
- * 3. NO handler signature violations (handlers must have exact MCP SDK signatures)
- * 4. NO handler testing violations (test logic functions, not handlers directly)
- *
- * For comprehensive code quality documentation, see docs/dev/CODE_QUALITY.md
- *
- * Note: General code quality rules (TypeScript, ESLint) are handled by other tools.
- * This script only enforces XcodeBuildMCP-specific architectural patterns.
- */
-
-import { readFileSync, readdirSync, statSync } from 'fs';
-import { join, relative } from 'path';
-import { fileURLToPath } from 'url';
-import { dirname } from 'path';
-
-const __filename = fileURLToPath(import.meta.url);
-const __dirname = dirname(__filename);
-const projectRoot = join(__dirname, '..');
-
-// Parse command line arguments
-const args = process.argv.slice(2);
-const patternFilter = args.find((arg) => arg.startsWith('--pattern='))?.split('=')[1] || 'all';
-const showHelp = args.includes('--help') || args.includes('-h');
-
-if (showHelp) {
- console.log(`
-XcodeBuildMCP Code Pattern Violations Checker
-
-USAGE:
- node scripts/check-code-patterns.js [options]
-
-OPTIONS:
- --pattern=TYPE Check specific pattern type (vitest|execsync|handler|handler-testing|server-typing|all) [default: all]
- --help, -h Show this help message
-
-PATTERN TYPES:
- vitest Check only custom vitest policy violations (currently none)
- execsync Check only execSync usage in production code
- handler Check only handler signature violations
- handler-testing Check only handler testing violations (testing handlers instead of logic functions)
- server-typing Check only improper server typing violations (Record casts)
- all Check all pattern violations (default)
-
- Note: General code quality (TypeScript, etc.) is handled by ESLint
-
-EXAMPLES:
- node scripts/check-code-patterns.js
- node scripts/check-code-patterns.js --pattern=vitest
- node scripts/check-code-patterns.js --pattern=handler
- node scripts/check-code-patterns.js --pattern=handler-testing
- node scripts/check-code-patterns.js --pattern=server-typing
-`);
- process.exit(0);
-}
-
-// Patterns for execSync usage in production code (FORBIDDEN)
-// Note: execSync is allowed in test files for mocking, but not in production code
-const EXECSYNC_PATTERNS = [
- /\bexecSync\s*\(/, // Direct execSync usage
- /\bexecSyncFn\s*[=:]/, // execSyncFn parameter or assignment
- /^import\s+(?!type\s)[^}]*from\s+['"]child_process['"]/m, // Importing from child_process (except type-only imports)
- /^import\s+{[^}]*(?:exec|spawn|execSync)[^}]*}\s+from\s+['"](?:node:)?child_process['"]/m, // Named imports of functions
-];
-
-// Vitest mocking is allowed for internal collaborators.
-// Keep this list empty unless a specific project policy requires certain vitest patterns to be blocked.
-const VITEST_GENERIC_PATTERNS = [];
-
-// APPROVED mock utilities - ONLY these are allowed
-const APPROVED_MOCK_PATTERNS = [
- /\bcreateMockExecutor\b/,
- /\bcreateMockFileSystemExecutor\b/,
- /\bcreateNoopExecutor\b/,
- /\bcreateNoopFileSystemExecutor\b/,
- /\bcreateCommandMatchingMockExecutor\b/,
- /\bcreateMockEnvironmentDetector\b/,
-];
-
-// Custom vitest restrictions can be added here if needed.
-const UNAPPROVED_MOCK_PATTERNS = [];
-
-// Function to check if a line contains unapproved mock patterns
-function hasUnapprovedMockPattern(line) {
- // Skip lines that contain approved patterns
- const hasApprovedPattern = APPROVED_MOCK_PATTERNS.some((pattern) => pattern.test(line));
- if (hasApprovedPattern) {
- return false;
- }
-
- // Check for unapproved patterns
- return UNAPPROVED_MOCK_PATTERNS.some((pattern) => pattern.test(line));
-}
-
-// Combined pattern checker for backward compatibility
-const VITEST_MOCKING_PATTERNS = VITEST_GENERIC_PATTERNS;
-
-// CRITICAL: ARCHITECTURAL VIOLATIONS - Utilities bypassing CommandExecutor (BANNED)
-const UTILITY_BYPASS_PATTERNS = [
- /spawn\s*\(/, // Direct Node.js spawn usage in utilities - BANNED
- /exec\s*\(/, // Direct Node.js exec usage in utilities - BANNED
- /execSync\s*\(/, // Direct Node.js execSync usage in utilities - BANNED
- /child_process\./, // Direct child_process module usage in utilities - BANNED
-];
-
-// TypeScript patterns are now handled by ESLint - removed from domain-specific checks
-// ESLint has comprehensive TypeScript rules with proper test file exceptions
-
-// CRITICAL: HANDLER SIGNATURE VIOLATIONS ARE FORBIDDEN
-// MCP SDK requires handlers to have exact signatures:
-// Tools: (args: Record) => Promise
-// Resources: (uri: URL) => Promise<{ contents: Array<{ text: string }> }>
-const HANDLER_SIGNATURE_VIOLATIONS = [
- /async\s+handler\s*\([^)]*:\s*[^,)]+,\s*[^)]+\s*:/ms, // Handler with multiple parameters separated by comma - BANNED
- /async\s+handler\s*\(\s*args\?\s*:/ms, // Handler with optional args parameter - BANNED (should be required)
- /async\s+handler\s*\([^)]*,\s*[^)]*CommandExecutor/ms, // Handler with CommandExecutor parameter - BANNED
- /async\s+handler\s*\([^)]*,\s*[^)]*FileSystemExecutor/ms, // Handler with FileSystemExecutor parameter - BANNED
- /async\s+handler\s*\([^)]*,\s*[^)]*Dependencies/ms, // Handler with Dependencies parameter - BANNED
- /async\s+handler\s*\([^)]*,\s*[^)]*executor\s*:/ms, // Handler with executor parameter - BANNED
- /async\s+handler\s*\([^)]*,\s*[^)]*dependencies\s*:/ms, // Handler with dependencies parameter - BANNED
-];
-
-// CRITICAL: HANDLER TESTING IN TESTS IS FORBIDDEN
-// Tests must ONLY call logic functions with dependency injection, NEVER handlers directly
-// Handlers are thin wrappers for MCP SDK - testing them violates dependency injection architecture
-const HANDLER_TESTING_VIOLATIONS = [
- /\.handler\s*\(/, // Direct handler calls in tests - BANNED
- /await\s+\w+\.handler\s*\(/, // Awaited handler calls - BANNED
- /const\s+result\s*=\s*await\s+\w+\.handler/, // Handler result assignment - BANNED
- /expect\s*\(\s*await\s+\w+\.handler/, // Handler expectation calls - BANNED
-];
-
-// CRITICAL: IMPROPER SERVER TYPING PATTERNS ARE FORBIDDEN
-// Server instances must use proper MCP SDK types, not generic Record casts
-const IMPROPER_SERVER_TYPING_VIOLATIONS = [
- /as Record.*server/, // Casting server to Record - BANNED
- /server.*as Record/, // Casting server to Record - BANNED
- /mcpServer\?\s*:\s*Record/, // Typing server as Record - BANNED
- /server\.server\?\?\s*server.*as Record/, // Complex server casting - BANNED
- /interface\s+MCPServerInterface\s*{/, // Custom MCP interfaces when SDK types exist - BANNED
-];
-
-// ALLOWED PATTERNS for cleanup (not mocking)
-const ALLOWED_CLEANUP_PATTERNS = [
- // All cleanup patterns removed - no exceptions allowed
-];
-
-// Patterns that indicate TRUE dependency injection approach
-const DEPENDENCY_INJECTION_PATTERNS = [
- /createMockExecutor/, // createMockExecutor usage
- /createMockFileSystemExecutor/, // createMockFileSystemExecutor usage
- /executor\?\s*:\s*CommandExecutor/, // executor?: CommandExecutor parameter
-];
-
-function findTestFiles(dir) {
- const testFiles = [];
-
- function traverse(currentDir) {
- const items = readdirSync(currentDir);
-
- for (const item of items) {
- const fullPath = join(currentDir, item);
- const stat = statSync(fullPath);
-
- if (stat.isDirectory()) {
- // Skip node_modules and other non-relevant directories
- if (
- !item.startsWith('.') &&
- item !== 'node_modules' &&
- item !== 'dist' &&
- item !== 'build'
- ) {
- traverse(fullPath);
- }
- } else if (item.endsWith('.test.ts') || item.endsWith('.test.js')) {
- testFiles.push(fullPath);
- }
- }
- }
-
- traverse(dir);
- return testFiles;
-}
-
-function findToolAndResourceFiles(dir) {
- const toolFiles = [];
-
- function traverse(currentDir) {
- const items = readdirSync(currentDir);
-
- for (const item of items) {
- const fullPath = join(currentDir, item);
- const stat = statSync(fullPath);
-
- if (stat.isDirectory()) {
- // Skip test directories and other non-relevant directories
- if (
- !item.startsWith('.') &&
- item !== '__tests__' &&
- item !== 'node_modules' &&
- item !== 'dist' &&
- item !== 'build'
- ) {
- traverse(fullPath);
- }
- } else if (
- (item.endsWith('.ts') || item.endsWith('.js')) &&
- !item.includes('.test.') &&
- item !== 'index.ts' &&
- item !== 'index.js'
- ) {
- toolFiles.push(fullPath);
- }
- }
- }
-
- traverse(dir);
- return toolFiles;
-}
-
-function findUtilityFiles(dir) {
- const utilityFiles = [];
-
- function traverse(currentDir) {
- const items = readdirSync(currentDir);
-
- for (const item of items) {
- const fullPath = join(currentDir, item);
- const stat = statSync(fullPath);
-
- if (stat.isDirectory()) {
- // Skip test directories and other non-relevant directories
- if (
- !item.startsWith('.') &&
- item !== '__tests__' &&
- item !== 'node_modules' &&
- item !== 'dist' &&
- item !== 'build'
- ) {
- traverse(fullPath);
- }
- } else if (
- (item.endsWith('.ts') || item.endsWith('.js')) &&
- !item.includes('.test.') &&
- item !== 'index.ts' &&
- item !== 'index.js'
- ) {
- // Only include key utility files that should use CommandExecutor
- // Exclude command.ts itself as it's the core implementation that is allowed to use spawn()
- if (
- fullPath.includes('/utils/') &&
- (fullPath.includes('log_capture.ts') ||
- fullPath.includes('build.ts') ||
- fullPath.includes('simctl.ts')) &&
- !fullPath.includes('command.ts')
- ) {
- utilityFiles.push(fullPath);
- }
- }
- }
- }
-
- traverse(dir);
- return utilityFiles;
-}
-
-// Helper function to determine if a file is a test file
-function isTestFile(filePath) {
- return (
- filePath.includes('__tests__') || filePath.endsWith('.test.ts') || filePath.endsWith('.test.js')
- );
-}
-
-// Helper function to determine if a file is a production file
-function isProductionFile(filePath) {
- return !isTestFile(filePath) && (filePath.endsWith('.ts') || filePath.endsWith('.js'));
-}
-
-// Helper function to determine if a file is allowed to use child_process
-function isAllowedChildProcessFile(filePath) {
- // These files need direct child_process access for their core functionality
- return (
- filePath.includes('command.ts') || // Core CommandExecutor implementation
- filePath.includes('swift_package_run.ts')
- ); // Needs spawn for background process management
-}
-
-function analyzeTestFile(filePath) {
- try {
- const content = readFileSync(filePath, 'utf8');
- const relativePath = relative(projectRoot, filePath);
-
- // Check for vitest mocking patterns using new robust approach
- const vitestMockingDetails = [];
- const lines = content.split('\n');
-
- // 1. Check generic vi.* patterns (always violations)
- lines.forEach((line, index) => {
- VITEST_GENERIC_PATTERNS.forEach((pattern) => {
- if (pattern.test(line)) {
- vitestMockingDetails.push({
- line: index + 1,
- content: line.trim(),
- pattern: pattern.source,
- type: 'vitest-generic',
- });
- }
- });
-
- // 2. Check for unapproved mock patterns
- if (hasUnapprovedMockPattern(line)) {
- // Find which specific pattern matched for better reporting
- const matchedPattern = UNAPPROVED_MOCK_PATTERNS.find((pattern) => pattern.test(line));
- vitestMockingDetails.push({
- line: index + 1,
- content: line.trim(),
- pattern: matchedPattern ? matchedPattern.source : 'unapproved mock pattern',
- type: 'unapproved-mock',
- });
- }
- });
-
- const hasVitestMockingPatterns = vitestMockingDetails.length > 0;
-
- // TypeScript patterns now handled by ESLint
- const hasTypescriptAntipatterns = false;
-
- // Check for handler testing violations (FORBIDDEN - ARCHITECTURAL VIOLATION)
- const hasHandlerTestingViolations = HANDLER_TESTING_VIOLATIONS.some((pattern) =>
- pattern.test(content),
- );
-
- // Check for improper server typing violations (FORBIDDEN - ARCHITECTURAL VIOLATION)
- const hasImproperServerTypingViolations = IMPROPER_SERVER_TYPING_VIOLATIONS.some((pattern) =>
- pattern.test(content),
- );
-
- // Check for dependency injection patterns (TRUE DI)
- const hasDIPatterns = DEPENDENCY_INJECTION_PATTERNS.some((pattern) => pattern.test(content));
-
- // Extract specific pattern occurrences for details
- const execSyncDetails = []; // Not applicable to test files
- const typescriptAntipatternDetails = []; // Unused - TypeScript handled by ESLint
- const handlerTestingDetails = [];
- const improperServerTypingDetails = [];
-
- lines.forEach((line, index) => {
- // TypeScript anti-patterns now handled by ESLint - removed from domain checks
-
- HANDLER_TESTING_VIOLATIONS.forEach((pattern) => {
- if (pattern.test(line)) {
- handlerTestingDetails.push({
- line: index + 1,
- content: line.trim(),
- pattern: pattern.source,
- });
- }
- });
-
- IMPROPER_SERVER_TYPING_VIOLATIONS.forEach((pattern) => {
- if (pattern.test(line)) {
- improperServerTypingDetails.push({
- line: index + 1,
- content: line.trim(),
- pattern: pattern.source,
- });
- }
- });
- });
-
- return {
- filePath: relativePath,
- hasExecSyncPatterns: false, // Not applicable to test files
- hasVitestMockingPatterns,
- hasTypescriptAntipatterns,
- hasHandlerTestingViolations,
- hasImproperServerTypingViolations,
- hasDIPatterns,
- execSyncDetails,
- vitestMockingDetails,
- typescriptAntipatternDetails,
- handlerTestingDetails,
- improperServerTypingDetails,
- needsConversion:
- hasVitestMockingPatterns ||
- hasHandlerTestingViolations ||
- hasImproperServerTypingViolations,
- isConverted:
- hasDIPatterns &&
- !hasVitestMockingPatterns &&
- !hasHandlerTestingViolations &&
- !hasImproperServerTypingViolations,
- isMixed:
- (hasVitestMockingPatterns ||
- hasHandlerTestingViolations ||
- hasImproperServerTypingViolations) &&
- hasDIPatterns,
- };
- } catch (error) {
- console.error(`Error reading file ${filePath}: ${error.message}`);
- return null;
- }
-}
-
-function analyzeToolOrResourceFile(filePath) {
- try {
- const content = readFileSync(filePath, 'utf8');
- const relativePath = relative(projectRoot, filePath);
-
- // Check for execSync patterns in production code (excluding allowed files)
- const hasExecSyncPatterns =
- isProductionFile(filePath) &&
- !isAllowedChildProcessFile(filePath) &&
- EXECSYNC_PATTERNS.some((pattern) => pattern.test(content));
-
- // Check for vitest mocking patterns using new robust approach
- const vitestMockingDetails = [];
- const lines = content.split('\n');
-
- // 1. Check generic vi.* patterns (always violations)
- lines.forEach((line, index) => {
- VITEST_GENERIC_PATTERNS.forEach((pattern) => {
- if (pattern.test(line)) {
- vitestMockingDetails.push({
- line: index + 1,
- content: line.trim(),
- pattern: pattern.source,
- type: 'vitest-generic',
- });
- }
- });
-
- // 2. Check for unapproved mock patterns
- if (hasUnapprovedMockPattern(line)) {
- // Find which specific pattern matched for better reporting
- const matchedPattern = UNAPPROVED_MOCK_PATTERNS.find((pattern) => pattern.test(line));
- vitestMockingDetails.push({
- line: index + 1,
- content: line.trim(),
- pattern: matchedPattern ? matchedPattern.source : 'unapproved mock pattern',
- type: 'unapproved-mock',
- });
- }
- });
-
- const hasVitestMockingPatterns = vitestMockingDetails.length > 0;
-
- // TypeScript patterns now handled by ESLint
- const hasTypescriptAntipatterns = false;
-
- // Check for dependency injection patterns (TRUE DI)
- const hasDIPatterns = DEPENDENCY_INJECTION_PATTERNS.some((pattern) => pattern.test(content));
-
- // Check for handler signature violations (FORBIDDEN)
- const hasHandlerSignatureViolations = HANDLER_SIGNATURE_VIOLATIONS.some((pattern) =>
- pattern.test(content),
- );
-
- // Check for improper server typing violations (FORBIDDEN - ARCHITECTURAL VIOLATION)
- const hasImproperServerTypingViolations = IMPROPER_SERVER_TYPING_VIOLATIONS.some((pattern) =>
- pattern.test(content),
- );
-
- // Check for utility bypass patterns (ARCHITECTURAL VIOLATION)
- const hasUtilityBypassPatterns = UTILITY_BYPASS_PATTERNS.some((pattern) =>
- pattern.test(content),
- );
-
- // Extract specific pattern occurrences for details
- const execSyncDetails = [];
- const typescriptAntipatternDetails = []; // Unused - TypeScript handled by ESLint
- const handlerSignatureDetails = [];
- const improperServerTypingDetails = [];
- const utilityBypassDetails = [];
-
- lines.forEach((line, index) => {
- if (isProductionFile(filePath) && !isAllowedChildProcessFile(filePath)) {
- EXECSYNC_PATTERNS.forEach((pattern) => {
- if (pattern.test(line)) {
- execSyncDetails.push({
- line: index + 1,
- content: line.trim(),
- pattern: pattern.source,
- });
- }
- });
- }
-
- // TypeScript anti-patterns now handled by ESLint - removed from domain checks
-
- IMPROPER_SERVER_TYPING_VIOLATIONS.forEach((pattern) => {
- if (pattern.test(line)) {
- improperServerTypingDetails.push({
- line: index + 1,
- content: line.trim(),
- pattern: pattern.source,
- });
- }
- });
-
- UTILITY_BYPASS_PATTERNS.forEach((pattern) => {
- if (pattern.test(line)) {
- utilityBypassDetails.push({
- line: index + 1,
- content: line.trim(),
- pattern: pattern.source,
- });
- }
- });
- });
- if (hasHandlerSignatureViolations) {
- // Use regex to find the violation and its line number
- const lines = content.split('\n');
- const fullContent = content;
-
- HANDLER_SIGNATURE_VIOLATIONS.forEach((pattern) => {
- let match;
- const globalPattern = new RegExp(pattern.source, pattern.flags + 'g');
- while ((match = globalPattern.exec(fullContent)) !== null) {
- // Find which line this match is on
- const beforeMatch = fullContent.substring(0, match.index);
- const lineNumber = beforeMatch.split('\n').length;
-
- handlerSignatureDetails.push({
- line: lineNumber,
- content: match[0].replace(/\s+/g, ' ').trim(),
- pattern: pattern.source,
- });
- }
- });
- }
-
- return {
- filePath: relativePath,
- hasExecSyncPatterns,
- hasVitestMockingPatterns,
- hasTypescriptAntipatterns,
- hasDIPatterns,
- hasHandlerSignatureViolations,
- hasImproperServerTypingViolations,
- hasUtilityBypassPatterns,
- execSyncDetails,
- vitestMockingDetails,
- typescriptAntipatternDetails,
- handlerSignatureDetails,
- improperServerTypingDetails,
- utilityBypassDetails,
- needsConversion:
- hasExecSyncPatterns ||
- hasVitestMockingPatterns ||
- hasHandlerSignatureViolations ||
- hasImproperServerTypingViolations ||
- hasUtilityBypassPatterns,
- isConverted:
- hasDIPatterns &&
- !hasExecSyncPatterns &&
- !hasVitestMockingPatterns &&
- !hasHandlerSignatureViolations &&
- !hasImproperServerTypingViolations &&
- !hasUtilityBypassPatterns,
- isMixed:
- (hasExecSyncPatterns ||
- hasVitestMockingPatterns ||
- hasHandlerSignatureViolations ||
- hasImproperServerTypingViolations ||
- hasUtilityBypassPatterns) &&
- hasDIPatterns,
- };
- } catch (error) {
- console.error(`Error reading file ${filePath}: ${error.message}`);
- return null;
- }
-}
-
-function main() {
- console.log('🔍 XcodeBuildMCP Code Pattern Violations Checker\n');
- console.log(`🎯 Checking pattern type: ${patternFilter.toUpperCase()}\n`);
- console.log('CODE GUIDELINES ENFORCED:');
- console.log('✅ External boundaries in tests should use createMockExecutor()/createMockFileSystemExecutor()');
- console.log('✅ Vitest mocking is allowed for internal collaborators');
- console.log('❌ BANNED: execSync usage in production code (use CommandExecutor instead)');
- console.log('ℹ️ TypeScript patterns: Handled by ESLint with proper test exceptions');
- console.log(
- '❌ BANNED: handler signature violations (handlers must have exact MCP SDK signatures)',
- );
- console.log(
- '❌ BANNED: handler testing violations (test logic functions, not handlers directly)',
- );
- console.log(
- '❌ BANNED: improper server typing (use McpServer type, not Record)\n',
- );
-
- const testFiles = findTestFiles(join(projectRoot, 'src'));
- const testResults = testFiles.map(analyzeTestFile).filter(Boolean);
-
- // Also check tool and resource files for TypeScript anti-patterns AND handler signature violations
- const toolFiles = findToolAndResourceFiles(join(projectRoot, 'src', 'mcp', 'tools'));
- const resourceFiles = findToolAndResourceFiles(join(projectRoot, 'src', 'mcp', 'resources'));
- const allToolAndResourceFiles = [...toolFiles, ...resourceFiles];
- const toolResults = allToolAndResourceFiles.map(analyzeToolOrResourceFile).filter(Boolean);
-
- // Check utility files for architectural violations (bypassing CommandExecutor)
- const utilityFiles = findUtilityFiles(join(projectRoot, 'src'));
- const utilityResults = utilityFiles.map(analyzeToolOrResourceFile).filter(Boolean);
-
- // Combine test, tool, and utility file results for analysis
- const results = [...testResults, ...toolResults, ...utilityResults];
- const handlerResults = toolResults;
- const utilityBypassResults = utilityResults.filter((r) => r.hasUtilityBypassPatterns);
-
- // Filter results based on pattern type
- let filteredResults;
- let filteredHandlerResults = [];
-
- switch (patternFilter) {
- case 'vitest':
- filteredResults = results.filter((r) => r.hasVitestMockingPatterns);
- console.log(
- `Filtering to show only vitest mocking violations (${filteredResults.length} files)`,
- );
- break;
- case 'execsync':
- filteredResults = results.filter((r) => r.hasExecSyncPatterns);
- console.log(`Filtering to show only execSync violations (${filteredResults.length} files)`);
- break;
- // TypeScript case removed - now handled by ESLint
- case 'handler':
- filteredResults = [];
- filteredHandlerResults = handlerResults.filter((r) => r.hasHandlerSignatureViolations);
- console.log(
- `Filtering to show only handler signature violations (${filteredHandlerResults.length} files)`,
- );
- break;
- case 'handler-testing':
- filteredResults = results.filter((r) => r.hasHandlerTestingViolations);
- console.log(
- `Filtering to show only handler testing violations (${filteredResults.length} files)`,
- );
- break;
- case 'server-typing':
- filteredResults = results.filter((r) => r.hasImproperServerTypingViolations);
- console.log(
- `Filtering to show only improper server typing violations (${filteredResults.length} files)`,
- );
- break;
- case 'all':
- default:
- filteredResults = results.filter((r) => r.needsConversion);
- filteredHandlerResults = handlerResults.filter((r) => r.hasHandlerSignatureViolations);
- console.log(
- `Showing all pattern violations (${filteredResults.length} test files + ${filteredHandlerResults.length} handler files)`,
- );
- break;
- }
-
- const needsConversion = filteredResults;
- const converted = results.filter((r) => r.isConverted);
- const mixed = results.filter((r) => r.isMixed);
- const execSyncOnly = results.filter(
- (r) =>
- r.hasExecSyncPatterns &&
- !r.hasVitestMockingPatterns &&
- true &&
- !r.hasHandlerTestingViolations &&
- !r.hasImproperServerTypingViolations &&
- !r.hasDIPatterns,
- );
- const vitestMockingOnly = results.filter(
- (r) =>
- r.hasVitestMockingPatterns &&
- !r.hasExecSyncPatterns &&
- true &&
- !r.hasHandlerTestingViolations &&
- !r.hasImproperServerTypingViolations &&
- !r.hasDIPatterns,
- );
- const typescriptOnly = results.filter(
- (r) =>
- r.false &&
- !r.hasExecSyncPatterns &&
- !r.hasVitestMockingPatterns &&
- !r.hasHandlerTestingViolations &&
- !r.hasImproperServerTypingViolations &&
- !r.hasDIPatterns,
- );
- const handlerTestingOnly = results.filter(
- (r) =>
- r.hasHandlerTestingViolations &&
- !r.hasExecSyncPatterns &&
- !r.hasVitestMockingPatterns &&
- true &&
- !r.hasImproperServerTypingViolations &&
- !r.hasDIPatterns,
- );
- const improperServerTypingOnly = results.filter(
- (r) =>
- r.hasImproperServerTypingViolations &&
- !r.hasExecSyncPatterns &&
- !r.hasVitestMockingPatterns &&
- !r.hasHandlerTestingViolations &&
- !r.hasDIPatterns,
- );
- const noPatterns = results.filter(
- (r) =>
- !r.hasExecSyncPatterns &&
- !r.hasVitestMockingPatterns &&
- true &&
- !r.hasHandlerTestingViolations &&
- !r.hasImproperServerTypingViolations &&
- !r.hasDIPatterns,
- );
-
- console.log(`📊 CODE PATTERN VIOLATION ANALYSIS`);
- console.log(`=================================`);
- console.log(`Total files analyzed: ${results.length}`);
- console.log(`🚨 FILES WITH VIOLATIONS: ${needsConversion.length}`);
- console.log(` └─ execSync production violations: ${execSyncOnly.length}`);
- console.log(` └─ vitest mocking violations: ${vitestMockingOnly.length}`);
- // TypeScript anti-patterns now handled by ESLint
- console.log(` └─ handler testing violations: ${handlerTestingOnly.length}`);
- console.log(` └─ improper server typing violations: ${improperServerTypingOnly.length}`);
- console.log(`🚨 ARCHITECTURAL VIOLATIONS: ${utilityBypassResults.length}`);
- console.log(`✅ COMPLIANT (best practices): ${converted.length}`);
- console.log(`⚠️ MIXED VIOLATIONS: ${mixed.length}`);
- console.log(`📝 No patterns detected: ${noPatterns.length}`);
- console.log('');
-
- if (needsConversion.length > 0) {
- console.log(`❌ FILES THAT NEED CONVERSION (${needsConversion.length}):`);
- console.log(`=====================================`);
- needsConversion.forEach((result, index) => {
- console.log(`${index + 1}. ${result.filePath}`);
-
- if (result.execSyncDetails && result.execSyncDetails.length > 0) {
- console.log(` 🚨 EXECSYNC PATTERNS (${result.execSyncDetails.length}):`);
- result.execSyncDetails.slice(0, 2).forEach((detail) => {
- console.log(` Line ${detail.line}: ${detail.content}`);
- });
- if (result.execSyncDetails.length > 2) {
- console.log(` ... and ${result.execSyncDetails.length - 2} more execSync patterns`);
- }
- console.log(` 🔧 FIX: Replace execSync with CommandExecutor dependency injection`);
- }
-
- if (result.vitestMockingDetails.length > 0) {
- console.log(` 🧪 VITEST MOCKING PATTERNS (${result.vitestMockingDetails.length}):`);
- result.vitestMockingDetails.slice(0, 2).forEach((detail) => {
- console.log(` Line ${detail.line}: ${detail.content}`);
- });
- if (result.vitestMockingDetails.length > 2) {
- console.log(` ... and ${result.vitestMockingDetails.length - 2} more vitest patterns`);
- }
- }
-
- // TypeScript violations now handled by ESLint - removed from domain checks
-
- if (result.handlerTestingDetails && result.handlerTestingDetails.length > 0) {
- console.log(` 🚨 HANDLER TESTING VIOLATIONS (${result.handlerTestingDetails.length}):`);
- result.handlerTestingDetails.slice(0, 2).forEach((detail) => {
- console.log(` Line ${detail.line}: ${detail.content}`);
- });
- if (result.handlerTestingDetails.length > 2) {
- console.log(
- ` ... and ${result.handlerTestingDetails.length - 2} more handler testing violations`,
- );
- }
- console.log(
- ` 🔧 FIX: Replace handler calls with logic function calls using dependency injection`,
- );
- }
-
- if (result.improperServerTypingDetails && result.improperServerTypingDetails.length > 0) {
- console.log(
- ` 🔧 IMPROPER SERVER TYPING VIOLATIONS (${result.improperServerTypingDetails.length}):`,
- );
- result.improperServerTypingDetails.slice(0, 2).forEach((detail) => {
- console.log(` Line ${detail.line}: ${detail.content}`);
- });
- if (result.improperServerTypingDetails.length > 2) {
- console.log(
- ` ... and ${result.improperServerTypingDetails.length - 2} more server typing violations`,
- );
- }
- console.log(
- ` 🔧 FIX: Import McpServer from SDK and use proper typing instead of Record`,
- );
- }
-
- console.log('');
- });
- }
-
- // Utility bypass violations reporting
- if (utilityBypassResults.length > 0) {
- console.log(`🚨 CRITICAL: UTILITY ARCHITECTURAL VIOLATIONS (${utilityBypassResults.length}):`);
- console.log(`=======================================================`);
- console.log('⚠️ These utilities bypass CommandExecutor and break our testing architecture!');
- console.log('');
- utilityBypassResults.forEach((result, index) => {
- console.log(`${index + 1}. ${result.filePath}`);
-
- if (result.utilityBypassDetails.length > 0) {
- console.log(` 🚨 BYPASS PATTERNS (${result.utilityBypassDetails.length}):`);
- result.utilityBypassDetails.forEach((detail) => {
- console.log(` Line ${detail.line}: ${detail.content}`);
- });
- }
-
- console.log(
- ' 🔧 FIX: Refactor to accept CommandExecutor and use it instead of direct spawn/exec calls',
- );
- console.log('');
- });
- }
-
- // Handler signature violations reporting
- if (filteredHandlerResults.length > 0) {
- console.log(`🚨 HANDLER SIGNATURE VIOLATIONS (${filteredHandlerResults.length}):`);
- console.log(`===========================================`);
- filteredHandlerResults.forEach((result, index) => {
- console.log(`${index + 1}. ${result.filePath}`);
-
- if (result.handlerSignatureDetails.length > 0) {
- console.log(` 🛠️ HANDLER VIOLATIONS (${result.handlerSignatureDetails.length}):`);
- result.handlerSignatureDetails.forEach((detail) => {
- console.log(` Line ${detail.line}: ${detail.content}`);
- });
- }
-
- console.log('');
- });
- }
-
- if (mixed.length > 0) {
- console.log(`⚠️ FILES WITH MIXED PATTERNS (${mixed.length}):`);
- console.log(`===================================`);
- mixed.forEach((result, index) => {
- console.log(`${index + 1}. ${result.filePath}`);
- console.log(` ⚠️ Contains both setTimeout and dependency injection patterns`);
- console.log('');
- });
- }
-
- if (converted.length > 0) {
- console.log(`✅ SUCCESSFULLY CONVERTED FILES (${converted.length}):`);
- console.log(`====================================`);
- converted.forEach((result, index) => {
- console.log(`${index + 1}. ${result.filePath}`);
- });
- console.log('');
- }
-
- // Summary for next steps
- const hasViolations =
- needsConversion.length > 0 ||
- filteredHandlerResults.length > 0 ||
- utilityBypassResults.length > 0;
-
- if (needsConversion.length > 0) {
- console.log(`🚨 CRITICAL ACTION REQUIRED (TEST FILES):`);
- console.log(`=======================================`);
- console.log(`1. Fix architectural violations in ${needsConversion.length} files`);
- console.log(
- `2. BANNED: Testing handlers directly (.handler()) - test logic functions with dependency injection`,
- );
- console.log(`3. Use injected executors/filesystem mocks for external side effects`);
- console.log(`4. Update plugin implementations to accept executor?: CommandExecutor parameter`);
- console.log(`5. Run this script again after each fix to track progress`);
- console.log('');
-
- // Show top files by total violation count
- const sortedByPatterns = needsConversion
- .sort((a, b) => {
- const totalA =
- (a.execSyncDetails?.length || 0) +
- a.vitestMockingDetails.length +
- (a.handlerTestingDetails?.length || 0) +
- (a.improperServerTypingDetails?.length || 0);
- const totalB =
- (b.execSyncDetails?.length || 0) +
- b.vitestMockingDetails.length +
- (b.handlerTestingDetails?.length || 0) +
- (b.improperServerTypingDetails?.length || 0);
- return totalB - totalA;
- })
- .slice(0, 5);
-
- console.log(`🚨 TOP 5 FILES WITH MOST VIOLATIONS:`);
- sortedByPatterns.forEach((result, index) => {
- const totalPatterns =
- (result.execSyncDetails?.length || 0) +
- result.vitestMockingDetails.length +
- (result.handlerTestingDetails?.length || 0) +
- (result.improperServerTypingDetails?.length || 0);
- console.log(
- `${index + 1}. ${result.filePath} (${totalPatterns} violations: ${result.execSyncDetails?.length || 0} execSync + ${result.vitestMockingDetails.length} vitest + ${result.handlerTestingDetails?.length || 0} handler + ${result.improperServerTypingDetails?.length || 0} server)`,
- );
- });
- console.log('');
- }
-
- if (utilityBypassResults.length > 0) {
- console.log(`🚨 CRITICAL ACTION REQUIRED (UTILITY FILES):`);
- console.log(`==========================================`);
- console.log(
- `1. IMMEDIATELY fix ALL architectural violations in ${utilityBypassResults.length} files`,
- );
- console.log(`2. Refactor utilities to accept CommandExecutor parameter`);
- console.log(`3. Replace direct spawn/exec calls with executor calls`);
- console.log(`4. These violations break our entire testing strategy`);
- console.log(`5. Run this script again after each fix to track progress`);
- console.log('');
- }
-
- if (filteredHandlerResults.length > 0) {
- console.log(`🚨 CRITICAL ACTION REQUIRED (HANDLER FILES):`);
- console.log(`==========================================`);
- console.log(
- `1. IMMEDIATELY fix ALL handler signature violations in ${filteredHandlerResults.length} files`,
- );
- console.log(
- `2. Tools: Handler must be: async handler(args: Record): Promise`,
- );
- console.log(
- `3. Resources: Handler must be: async handler(uri: URL): Promise<{ contents: Array<{ text: string }> }>`,
- );
- console.log(
- `4. Inject dependencies INSIDE handler body: const executor = getDefaultCommandExecutor()`,
- );
- console.log(`5. Run this script again after each fix to track progress`);
- console.log('');
- }
-
- if (!hasViolations && mixed.length === 0) {
- console.log(`🎉 ALL FILES COMPLY WITH PROJECT STANDARDS!`);
- console.log(`==========================================`);
- console.log(`✅ External boundary tests use injected executors/filesystem dependencies`);
- console.log(`✅ All files follow TypeScript best practices (no unsafe casts)`);
- console.log(`✅ All handler signatures comply with MCP SDK requirements`);
- console.log(`✅ All utilities properly use CommandExecutor dependency injection`);
- console.log(`✅ No violations detected!`);
- }
-
- // Exit with appropriate code
- process.exit(hasViolations || mixed.length > 0 ? 1 : 0);
-}
-
-main();
diff --git a/scripts/check-docs-cli-commands.js b/scripts/check-docs-cli-commands.js
index 42a9222a6..95f44e902 100755
--- a/scripts/check-docs-cli-commands.js
+++ b/scripts/check-docs-cli-commands.js
@@ -1,7 +1,7 @@
#!/usr/bin/env node
import { spawnSync } from 'node:child_process';
-import { existsSync, readdirSync, readFileSync } from 'node:fs';
+import { existsSync, readFileSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
@@ -43,13 +43,7 @@ function loadToolCatalog() {
}
function getConsumerDocs() {
- const docsDir = path.join(repoRoot, 'docs');
- const docsFiles = readdirSync(docsDir, { withFileTypes: true })
- .filter((entry) => entry.isFile() && entry.name.endsWith('.md'))
- .map((entry) => path.join(docsDir, entry.name))
- .sort();
-
- return [path.join(repoRoot, 'README.md'), path.join(repoRoot, 'CHANGELOG.md'), ...docsFiles];
+ return [path.join(repoRoot, 'README.md'), path.join(repoRoot, 'CHANGELOG.md')];
}
function buildValidationSets(catalog) {
@@ -182,7 +176,7 @@ function main() {
);
}
- console.log('✅ Docs CLI command check passed (README.md + CHANGELOG.md + docs/*.md).');
+ console.log('✅ Docs CLI command check passed (README.md + CHANGELOG.md).');
}
main();
diff --git a/scripts/release.sh b/scripts/release.sh
index 87038c104..f2656e56f 100755
--- a/scripts/release.sh
+++ b/scripts/release.sh
@@ -330,8 +330,6 @@ RELEASE_MANAGED_FILES=(
"package.json"
"package-lock.json"
"README.md"
- "docs/SKILLS.md"
- "docs/GETTING_STARTED.md"
"server.json"
)
@@ -426,11 +424,10 @@ if [[ "$SKIP_VERSION_UPDATE" == "false" ]]; then
# README update
echo ""
- echo "📝 Updating install tags in README.md and docs/GETTING_STARTED.md..."
+ echo "📝 Updating install tags in README.md..."
README_AT_TAG_REGEX='xcodebuildmcp@([0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?|latest|beta|alpha)'
README_URLENCODED_AT_TAG_REGEX='xcodebuildmcp%40([0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?|latest|beta|alpha)'
run sed_inplace "s/${README_AT_TAG_REGEX}/xcodebuildmcp@${NPM_TAG}/g" README.md
- run sed_inplace "s/${README_AT_TAG_REGEX}/xcodebuildmcp@${NPM_TAG}/g" docs/GETTING_STARTED.md
run sed_inplace "s/${README_URLENCODED_AT_TAG_REGEX}/xcodebuildmcp%40${NPM_TAG}/g" README.md
echo "📝 Updating Cursor install link config in README.md..."
@@ -450,9 +447,9 @@ if [[ "$SKIP_VERSION_UPDATE" == "false" ]]; then
echo ""
echo "📦 Committing version changes..."
if [[ -f server.json ]]; then
- run git add package.json package-lock.json README.md docs/SKILLS.md docs/GETTING_STARTED.md CHANGELOG.md server.json
+ run git add package.json package-lock.json README.md CHANGELOG.md server.json
else
- run git add package.json package-lock.json README.md docs/SKILLS.md docs/GETTING_STARTED.md CHANGELOG.md
+ run git add package.json package-lock.json README.md CHANGELOG.md
fi
run git commit -m "Release v$VERSION"
else
diff --git a/scripts/update-tools-docs.ts b/scripts/update-tools-docs.ts
deleted file mode 100644
index a570e3417..000000000
--- a/scripts/update-tools-docs.ts
+++ /dev/null
@@ -1,505 +0,0 @@
-#!/usr/bin/env node
-
-/**
- * XcodeBuildMCP Tools Documentation Updater
- *
- * Automatically updates docs/TOOLS.md and docs/TOOLS-CLI.md with current tool and workflow information
- * using the build tools manifest.
- *
- * Usage:
- * npx tsx scripts/update-tools-docs.ts [--dry-run] [--verbose]
- *
- * Options:
- * --dry-run, -d Show what would be updated without making changes
- * --verbose, -v Show detailed information about the update process
- * --help, -h Show this help message
- */
-
-import * as fs from 'fs';
-import * as path from 'path';
-import { fileURLToPath } from 'url';
-import { loadManifest as loadYamlManifest } from '../src/core/manifest/load-manifest.ts';
-import { getEffectiveCliName } from '../src/core/manifest/schema.ts';
-
-// Get project paths
-const __filename = fileURLToPath(import.meta.url);
-const __dirname = path.dirname(__filename);
-const projectRoot = path.resolve(__dirname, '..');
-const docsPath = path.join(projectRoot, 'docs', 'TOOLS.md');
-const docsCliPath = path.join(projectRoot, 'docs', 'TOOLS-CLI.md');
-const cliExcludedWorkflows = new Set(['session-management', 'workflow-discovery']);
-
-type ToolsManifest = {
- generatedAt: string;
- stats: {
- totalTools: number;
- canonicalTools: number;
- reExportTools: number;
- workflowCount: number;
- };
- workflows: DocumentationWorkflow[];
- tools: DocumentationTool[];
-};
-
-// CLI options
-const args = process.argv.slice(2);
-const options = {
- dryRun: args.includes('--dry-run') || args.includes('-d'),
- verbose: args.includes('--verbose') || args.includes('-v'),
- help: args.includes('--help') || args.includes('-h'),
-};
-
-const colors = {
- reset: '\x1b[0m',
- bright: '\x1b[1m',
- red: '\x1b[31m',
- green: '\x1b[32m',
- yellow: '\x1b[33m',
- blue: '\x1b[34m',
- cyan: '\x1b[36m',
- magenta: '\x1b[35m',
-} as const;
-
-if (options.help) {
- console.log(`
-${colors.bright}${colors.blue}XcodeBuildMCP Tools Documentation Updater${colors.reset}
-
-Automatically updates docs/TOOLS.md and docs/TOOLS-CLI.md with current tool and workflow information.
-
-${colors.bright}Usage:${colors.reset}
- npx tsx scripts/update-tools-docs.ts [options]
-
-${colors.bright}Options:${colors.reset}
- --dry-run, -d Show what would be updated without making changes
- --verbose, -v Show detailed information about the update process
- --help, -h Show this help message
-
-${colors.bright}Examples:${colors.reset}
- ${colors.cyan}npx tsx scripts/update-tools-docs.ts${colors.reset} # Update docs/TOOLS.md + docs/TOOLS-CLI.md
- ${colors.cyan}npx tsx scripts/update-tools-docs.ts --dry-run${colors.reset} # Preview changes
- ${colors.cyan}npx tsx scripts/update-tools-docs.ts --verbose${colors.reset} # Show detailed progress
-`);
- process.exit(0);
-}
-
-/**
- * Generate the workflow section content
- */
-function cleanToolDescription(description: string | undefined): string {
- if (!description) {
- return 'No description available';
- }
-
- return description
- .replace(/IMPORTANT:.*?Example:.*?\)/g, '') // Remove IMPORTANT sections
- .replace(/\s+/g, ' ') // Normalize whitespace
- .trim();
-}
-
-type DocumentationTool = {
- name: string;
- description?: string;
- isCanonical?: boolean;
- originWorkflowDisplayName?: string;
- workflow?: string;
- cliName?: string;
- originWorkflow?: string;
-};
-
-type DocumentationWorkflow = {
- name: string;
- displayName: string;
- description: string;
-};
-
-function generateWorkflowSection(
- workflow: DocumentationWorkflow,
- tools: DocumentationTool[],
-): string {
- const toolCount = tools.length;
-
- let content = `### ${workflow.displayName} (\`${workflow.name}\`)\n`;
- content += `**Purpose**: ${workflow.description} (${toolCount} tools)\n\n`;
-
- // List each tool with its description
- const sortedTools = [...tools].sort((a, b) => a.name.localeCompare(b.name));
- for (const tool of sortedTools) {
- let description = tool.description;
- if (tool.isCanonical === false) {
- if (tool.originWorkflowDisplayName) {
- description = `Defined in ${tool.originWorkflowDisplayName} workflow.`;
- } else {
- description = 'Defined in another workflow.';
- }
- }
-
- const cleanDescription = cleanToolDescription(description);
- content += `- \`${tool.name}\` - ${cleanDescription}\n`;
- }
-
- content += '\n\n';
-
- return content;
-}
-
-function loadManifest(): ToolsManifest {
- const manifest = loadYamlManifest();
- const workflowList = Array.from(manifest.workflows.values());
- const firstWorkflowByToolId = new Map();
- const docsTools: DocumentationTool[] = [];
-
- for (const workflow of workflowList) {
- for (const toolId of workflow.tools) {
- if (!firstWorkflowByToolId.has(toolId)) {
- firstWorkflowByToolId.set(toolId, workflow.id);
- }
-
- const tool = manifest.tools.get(toolId);
- if (!tool) {
- continue;
- }
-
- const originWorkflow = firstWorkflowByToolId.get(toolId);
- docsTools.push({
- name: tool.names.mcp,
- description: tool.description,
- isCanonical: originWorkflow === workflow.id,
- originWorkflow,
- workflow: workflow.id,
- cliName: getEffectiveCliName(tool),
- });
- }
- }
-
- return {
- generatedAt: new Date().toISOString(),
- stats: {
- totalTools: docsTools.length,
- canonicalTools: manifest.tools.size,
- reExportTools: docsTools.length - manifest.tools.size,
- workflowCount: workflowList.length,
- },
- workflows: workflowList.map((workflow) => ({
- name: workflow.id,
- displayName: workflow.title,
- description: workflow.description,
- })),
- tools: docsTools,
- };
-}
-
-/**
- * Generate the complete TOOLS.md content
- */
-function generateToolsDocumentation(manifest: ToolsManifest): string {
- const { workflows, stats, tools } = manifest;
-
- // Sort workflows by display name for consistent ordering
- const sortedWorkflows = [...workflows].sort((a, b) => a.displayName.localeCompare(b.displayName));
- const workflowMeta = new Map(workflows.map((workflow) => [workflow.name, workflow]));
- const toolsByWorkflow = new Map();
- for (const tool of tools) {
- const workflowKey = tool.workflow ?? '';
- const workflowTools = toolsByWorkflow.get(workflowKey) ?? [];
- workflowTools.push(tool);
- toolsByWorkflow.set(workflowKey, workflowTools);
- }
- const workflowSections = sortedWorkflows
- .map((workflow) => {
- const workflowTools = toolsByWorkflow.get(workflow.name) ?? [];
- const docTools = workflowTools.map((tool) => {
- const originWorkflow = tool.originWorkflow
- ? (workflowMeta.get(tool.originWorkflow)?.displayName ?? tool.originWorkflow)
- : undefined;
-
- return {
- name: tool.name,
- description: tool.description,
- isCanonical: tool.isCanonical,
- originWorkflowDisplayName: originWorkflow,
- };
- });
-
- return generateWorkflowSection(
- {
- name: workflow.name,
- displayName: workflow.displayName,
- description: workflow.description,
- },
- docTools,
- );
- })
- .join('\n');
-
- const lastUpdated = `${new Date(manifest.generatedAt).toISOString()} UTC`;
-
- const content = `# XcodeBuildMCP MCP Tools Reference
-
-This document lists MCP tool names as exposed to MCP clients. XcodeBuildMCP provides ${stats.canonicalTools} canonical tools organized into ${stats.workflowCount} workflow groups for comprehensive Apple development workflows.
-
-## Workflow Groups
-
-${workflowSections}
-## Summary Statistics
-
-- **Canonical Tools**: ${stats.canonicalTools}
-- **Total Tools**: ${stats.totalTools}
-- **Workflow Groups**: ${stats.workflowCount}
-
----
-
-*This documentation is automatically generated by \`scripts/update-tools-docs.ts\` from the tools manifest. Last updated: ${lastUpdated}*
-`;
-
- return content;
-}
-
-/**
- * Generate CLI tools documentation content
- */
-type CliDocumentationStats = {
- toolCount: number;
- canonicalToolCount: number;
- workflowCount: number;
-};
-
-type CliDocumentationResult = {
- content: string;
- stats: CliDocumentationStats;
-};
-
-function generateCliToolsDocumentation(manifest: ToolsManifest): CliDocumentationResult {
- const workflowMeta = new Map(manifest.workflows.map((workflow) => [workflow.name, workflow]));
- const toolsByWorkflow = new Map();
- let canonicalToolCount = 0;
- for (const tool of manifest.tools) {
- if (cliExcludedWorkflows.has(tool.workflow)) {
- continue;
- }
-
- if (tool.isCanonical) {
- canonicalToolCount++;
- }
-
- const tools = toolsByWorkflow.get(tool.workflow) ?? [];
- const originWorkflow = tool.originWorkflow
- ? (workflowMeta.get(tool.originWorkflow)?.displayName ?? tool.originWorkflow)
- : undefined;
-
- tools.push({
- name: tool.cliName ?? tool.name,
- description: tool.description,
- isCanonical: tool.isCanonical,
- originWorkflowDisplayName: originWorkflow,
- });
- toolsByWorkflow.set(tool.workflow, tools);
- }
-
- const sortedWorkflows = [...manifest.workflows]
- .filter((workflow) => toolsByWorkflow.has(workflow.name))
- .filter((workflow) => !cliExcludedWorkflows.has(workflow.name))
- .sort((a, b) => a.displayName.localeCompare(b.displayName));
-
- const workflowSections = sortedWorkflows
- .map((workflow) => {
- const tools = toolsByWorkflow.get(workflow.name) ?? [];
- const meta = workflowMeta.get(workflow.name);
- return generateWorkflowSection(
- {
- name: workflow.name,
- displayName: meta?.displayName ?? workflow.name,
- description: meta?.description ?? `${workflow.name} related tools`,
- },
- tools,
- );
- })
- .join('\n');
-
- const workflowCount = sortedWorkflows.length;
- const totalTools = Array.from(toolsByWorkflow.values()).reduce(
- (sum, tools) => sum + tools.length,
- 0,
- );
-
- const lastUpdated = `${new Date(manifest.generatedAt).toISOString()} UTC`;
-
- const content = `# XcodeBuildMCP CLI Tools Reference
-
-This document lists CLI tool names as exposed by \`xcodebuildmcp \`.
-
-XcodeBuildMCP provides ${canonicalToolCount} canonical tools organized into ${workflowCount} workflow groups.
-
-## Workflow Groups
-
-${workflowSections}
-## Summary Statistics
-
-- **Canonical Tools**: ${canonicalToolCount}
-- **Total Tools**: ${totalTools}
-- **Workflow Groups**: ${workflowCount}
-
----
-
-*This documentation is automatically generated by \`scripts/update-tools-docs.ts\` from the tools manifest. Last updated: ${lastUpdated}*
-`;
-
- return {
- content,
- stats: {
- toolCount: totalTools,
- canonicalToolCount,
- workflowCount,
- },
- };
-}
-
-/**
- * Compare old and new content to show what changed
- */
-function showDiff(oldContent: string, newContent: string): void {
- if (!options.verbose) return;
-
- console.log(`${colors.bright}${colors.cyan}📄 Content Comparison:${colors.reset}`);
- console.log('─'.repeat(50));
-
- const oldLines = oldContent.split('\n');
- const newLines = newContent.split('\n');
-
- const maxLength = Math.max(oldLines.length, newLines.length);
- let changes = 0;
-
- for (let i = 0; i < maxLength; i++) {
- const oldLine = oldLines[i] || '';
- const newLine = newLines[i] || '';
-
- if (oldLine !== newLine) {
- changes++;
- if (changes <= 10) {
- // Show first 10 changes
- console.log(`${colors.red}- Line ${i + 1}: ${oldLine}${colors.reset}`);
- console.log(`${colors.green}+ Line ${i + 1}: ${newLine}${colors.reset}`);
- }
- }
- }
-
- if (changes > 10) {
- console.log(`${colors.yellow}... and ${changes - 10} more changes${colors.reset}`);
- }
-
- console.log(`${colors.blue}Total changes: ${changes} lines${colors.reset}\n`);
-}
-
-/**
- * Main execution function
- */
-async function main(): Promise {
- try {
- console.log(
- `${colors.bright}${colors.blue}🔧 XcodeBuildMCP Tools Documentation Updater${colors.reset}`,
- );
-
- if (options.dryRun) {
- console.log(
- `${colors.yellow}🔍 Running in dry-run mode - no files will be modified${colors.reset}`,
- );
- }
-
- console.log(`${colors.cyan}📊 Analyzing tools...${colors.reset}`);
-
- const manifest = loadManifest();
-
- if (options.verbose) {
- console.log(
- `${colors.green}✓ Found ${manifest.stats.canonicalTools} canonical tools in ${manifest.stats.workflowCount} workflows${colors.reset}`,
- );
- }
-
- // Generate new documentation content
- console.log(`${colors.cyan}📝 Generating documentation...${colors.reset}`);
- const mcpContent = generateToolsDocumentation(manifest);
- const cliDocumentation = generateCliToolsDocumentation(manifest);
- const cliContent = cliDocumentation.content;
- const cliStats = cliDocumentation.stats;
-
- const targets = [
- { label: 'MCP tools', path: docsPath, content: mcpContent },
- { label: 'CLI tools', path: docsCliPath, content: cliContent },
- ];
-
- const changes = targets.map((target) => {
- const existing = fs.existsSync(target.path) ? fs.readFileSync(target.path, 'utf-8') : '';
-
- const changed = existing !== target.content;
- return { ...target, existing, changed };
- });
-
- const changedTargets = changes.filter((target) => target.changed);
-
- // Check if content has changed
- if (changedTargets.length === 0) {
- console.log(`${colors.green}✅ Documentation is already up to date!${colors.reset}`);
- return;
- }
-
- // Show differences if verbose
- if (options.verbose) {
- for (const target of changedTargets) {
- if (target.existing) {
- console.log(
- `${colors.bright}${colors.magenta}📄 ${target.label} content comparison:${colors.reset}`,
- );
- showDiff(target.existing, target.content);
- }
- }
- }
-
- if (options.dryRun) {
- console.log(
- `${colors.yellow}📋 Dry run completed. Documentation would be updated with:${colors.reset}`,
- );
- for (const target of changedTargets) {
- console.log(` - ${path.relative(projectRoot, target.path)} (${target.label})`);
- }
- console.log(` - MCP tools: ${manifest.stats.canonicalTools} canonical tools`);
- console.log(
- ` - CLI tools: ${cliStats.toolCount} tools across ${cliStats.workflowCount} workflows`,
- );
- console.log(` - MCP lines: ${mcpContent.split('\n').length}`);
- console.log(` - CLI lines: ${cliContent.split('\n').length}`);
-
- if (!options.verbose) {
- console.log(`\n${colors.cyan}💡 Use --verbose to see detailed changes${colors.reset}`);
- }
-
- return;
- }
-
- // Write new content
- console.log(`${colors.cyan}✏️ Writing updated documentation...${colors.reset}`);
- for (const target of changedTargets) {
- fs.writeFileSync(target.path, target.content, 'utf-8');
- console.log(
- `${colors.green}✅ Successfully updated ${path.relative(projectRoot, target.path)}!${colors.reset}`,
- );
- }
-
- if (options.verbose) {
- console.log(`\n${colors.bright}📈 Update Summary:${colors.reset}`);
- console.log(
- ` MCP tools: ${manifest.stats.canonicalTools} canonical (${manifest.stats.totalTools} total)`,
- );
- console.log(` MCP workflows: ${manifest.stats.workflowCount}`);
- console.log(` CLI tools: ${cliStats.toolCount} across ${cliStats.workflowCount} workflows`);
- console.log(` MCP file size: ${(mcpContent.length / 1024).toFixed(1)}KB`);
- console.log(` CLI file size: ${(cliContent.length / 1024).toFixed(1)}KB`);
- console.log(` MCP lines: ${mcpContent.split('\n').length}`);
- console.log(` CLI lines: ${cliContent.split('\n').length}`);
- }
- } catch (error) {
- console.error(`${colors.red}❌ Error: ${(error as Error).message}${colors.reset}`);
- process.exit(1);
- }
-}
-
-// Run the updater
-main();
diff --git a/skills/xcodebuildmcp/SKILL.md b/skills/xcodebuildmcp/SKILL.md
index 86764097d..5fc47401f 100644
--- a/skills/xcodebuildmcp/SKILL.md
+++ b/skills/xcodebuildmcp/SKILL.md
@@ -19,7 +19,7 @@ Capabilities:
- SwiftPM: Build, run, test, and manage Swift Package Manager projects
- Project scaffolding: Generate new iOS/macOS project templates
-Only simulator workflow tools are enabled by default. If capabilities like device, macOS, debugging, or UI automation are not available, the user must configure XcodeBuildMCP to enable them. See https://github.com/getsentry/XcodeBuildMCP/blob/main/docs/CONFIGURATION.md for workflow configuration.
+Only simulator workflow tools are enabled by default. If capabilities like device, macOS, debugging, or UI automation are not available, the user must configure XcodeBuildMCP to enable them. See https://xcodebuildmcp.com/docs/configuration for workflow configuration.
## Step 1: Establish Session Context
diff --git a/src/server/server.ts b/src/server/server.ts
index b132b728c..7284eb480 100644
--- a/src/server/server.ts
+++ b/src/server/server.ts
@@ -42,7 +42,7 @@ Capabilities:
- SwiftPM: Build, run, test, and manage Swift Package Manager projects
- Project scaffolding: Generate new iOS/macOS project templates
-Only simulator workflow tools are enabled by default. If capabilities like device, macOS, debugging, or UI automation are not available, the user must configure XcodeBuildMCP to enable them. See https://github.com/getsentry/XcodeBuildMCP/blob/main/docs/CONFIGURATION.md for workflow configuration.
+Only simulator workflow tools are enabled by default. If capabilities like device, macOS, debugging, or UI automation are not available, the user must configure XcodeBuildMCP to enable them. See https://xcodebuildmcp.com/docs/configuration for workflow configuration.
Simulator run flow:
- Before your first build, run, or test call in a session, you MUST call session_show_defaults to verify the active project/workspace, scheme, and simulator. Do not assume defaults are configured. Only skip this if you have already called session_show_defaults earlier in the current session.