xcode-mcli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +92 -0
  2. package/bin/xcode-mcli.ts +16 -0
  3. package/config/mcporter.json +11 -0
  4. package/package.json +57 -0
  5. package/skill/SKILL.md +70 -0
  6. package/skill/agents/openai.yaml +4 -0
  7. package/skill/references/api-reference.md +409 -0
  8. package/skill/references/apple-xcode-26.3.surface.json +1466 -0
  9. package/skill/references/compatibility.md +91 -0
  10. package/skill/references/setup.md +120 -0
  11. package/skill/references/troubleshooting.md +160 -0
  12. package/src/commands/daemon-restart.ts +22 -0
  13. package/src/commands/daemon-start.ts +22 -0
  14. package/src/commands/daemon-status.ts +29 -0
  15. package/src/commands/daemon-stop.ts +22 -0
  16. package/src/commands/index.ts +26 -0
  17. package/src/commands/project-build.ts +50 -0
  18. package/src/commands/setup.ts +26 -0
  19. package/src/commands/surface-snapshot.ts +45 -0
  20. package/src/commands/surface-verify.ts +46 -0
  21. package/src/commands/tool-command.ts +150 -0
  22. package/src/commands/windows-list.ts +43 -0
  23. package/src/commands/windows-use.ts +30 -0
  24. package/src/commands/xcode-tool-commands.ts +460 -0
  25. package/src/constants.ts +3 -0
  26. package/src/core/command-definition.ts +131 -0
  27. package/src/core/command-dispatch.ts +97 -0
  28. package/src/core/contracts.ts +25 -0
  29. package/src/core/errors.ts +52 -0
  30. package/src/core/package-version.ts +30 -0
  31. package/src/runtime/daemon-host-entry.ts +8 -0
  32. package/src/runtime/daemon-host.ts +455 -0
  33. package/src/runtime/daemon-state.ts +75 -0
  34. package/src/runtime/env.ts +37 -0
  35. package/src/runtime/mcp-jsonrpc.ts +114 -0
  36. package/src/runtime/output.ts +128 -0
  37. package/src/runtime/tab-resolver.ts +44 -0
  38. package/src/runtime/xcode-client.ts +192 -0
  39. package/src/runtime/xcode-surface.ts +166 -0
  40. package/src/runtime/xcode-tool-definition.ts +14 -0
  41. package/src/runtime/xcode-windows.ts +65 -0
  42. package/src/runtime/xcrun.ts +39 -0
@@ -0,0 +1,91 @@
1
+ # Compatibility
2
+
3
+ ## Objective
4
+
5
+ `xcode-mcli` can verify its Xcode MCP compatibility by diffing the live discovered MCP surface against a pinned baseline snapshot.
6
+
7
+ The compatibility contract is the normalized result of:
8
+
9
+ - `initialize`
10
+ - `tools/list`
11
+ - `prompts/list`
12
+ - `resources/list`
13
+
14
+ The pinned Apple Xcode 26.3 baseline is:
15
+
16
+ ```text
17
+ skill/references/apple-xcode-26.3.surface.json
18
+ ```
19
+
20
+ That snapshot currently records:
21
+
22
+ - protocol version `2025-06-18`
23
+ - `20` tools
24
+ - `0` prompts
25
+ - `0` resources
26
+
27
+ ## Commands
28
+
29
+ Capture the current live surface:
30
+
31
+ ```bash
32
+ xcode-mcli surface snapshot
33
+ ```
34
+
35
+ Write a canonical snapshot file:
36
+
37
+ ```bash
38
+ xcode-mcli surface snapshot --output-file skill/references/apple-xcode-26.3.surface.json
39
+ ```
40
+
41
+ Verify the current Xcode MCP surface against the pinned 26.3 baseline:
42
+
43
+ ```bash
44
+ xcode-mcli surface verify --baseline-file skill/references/apple-xcode-26.3.surface.json
45
+ ```
46
+
47
+ Convenience npm scripts:
48
+
49
+ ```bash
50
+ npm run compat:xcode-mcp:snapshot
51
+ npm run compat:xcode-mcp:pin-26.3
52
+ npm run compat:xcode-mcp:verify
53
+ ```
54
+
55
+ ## Verification Behavior
56
+
57
+ `surface snapshot` normalizes the discovered surface before printing or writing it:
58
+
59
+ - sorts tools, prompts, and resources by `name`
60
+ - sorts object keys recursively
61
+ - preserves extra server metadata such as `title` and `outputSchema`
62
+
63
+ `surface verify` compares:
64
+
65
+ - protocol version
66
+ - tool additions, removals, and changed definitions
67
+ - prompt additions, removals, and changed definitions
68
+ - resource additions, removals, and changed definitions
69
+
70
+ An exact match exits successfully.
71
+
72
+ A mismatch exits with a runtime error. In `--json` mode, the error envelope includes structured `error.details` with the categorized diff.
73
+
74
+ ## Upgrade Flow
75
+
76
+ When a new Xcode release arrives:
77
+
78
+ 1. Run `npm run compat:xcode-mcp:verify`.
79
+ 2. If it passes, the pinned baseline still matches the live Xcode MCP contract.
80
+ 3. If it fails, inspect the categorized diff.
81
+ 4. Confirm whether the change is additive, behavioral, or breaking for the CLI.
82
+ 5. Update `xcode-mcli` if the wrapper needs to support the new contract.
83
+ 6. Re-pin the baseline with `npm run compat:xcode-mcp:pin-26.3` or a new versioned snapshot path.
84
+
85
+ For machine-readable inspection:
86
+
87
+ ```bash
88
+ xcode-mcli surface verify \
89
+ --baseline-file skill/references/apple-xcode-26.3.surface.json \
90
+ --json
91
+ ```
@@ -0,0 +1,120 @@
1
+ # Setup
2
+
3
+ ## Requirements
4
+
5
+ - macOS
6
+ - Xcode installed
7
+ - `Xcode Tools` enabled in `Settings > Intelligence`
8
+ - Node.js 25+
9
+
10
+ ## Install
11
+
12
+ From the repo:
13
+
14
+ ```bash
15
+ npm install
16
+ ```
17
+
18
+ Global install:
19
+
20
+ ```bash
21
+ npm install -g xcode-mcli
22
+ ```
23
+
24
+ ## First Run
25
+
26
+ Verify the bridge and initialize the local state root:
27
+
28
+ ```bash
29
+ xcode-mcli setup
30
+ ```
31
+
32
+ If `setup` reports `Failed to call xcrun mcpbridge --help.`, enable `Xcode Tools` in `Settings > Intelligence` and run `xcode-mcli setup` again.
33
+
34
+ Then confirm Xcode access:
35
+
36
+ ```bash
37
+ xcode-mcli windows list
38
+ ```
39
+
40
+ If exactly one Xcode workspace window is open, many tab-aware commands can resolve it automatically.
41
+
42
+ To pin the active tab explicitly:
43
+
44
+ ```bash
45
+ xcode-mcli windows use --tab-identifier windowtab1
46
+ ```
47
+
48
+ ## Daemon
49
+
50
+ The daemon auto-starts on the first daemon-backed command.
51
+
52
+ Explicit lifecycle commands:
53
+
54
+ ```bash
55
+ xcode-mcli daemon start
56
+ xcode-mcli daemon status
57
+ xcode-mcli daemon restart
58
+ xcode-mcli daemon stop
59
+ ```
60
+
61
+ ## State Root
62
+
63
+ The default state root is:
64
+
65
+ ```text
66
+ ~/Library/Application Support/xcode-mcli
67
+ ```
68
+
69
+ Files used there:
70
+
71
+ - `daemon.sock`
72
+ - `daemon.pid`
73
+ - `daemon.log`
74
+ - `state.json`
75
+
76
+ ## Common Commands
77
+
78
+ ```bash
79
+ xcode-mcli windows list
80
+ xcode-mcli files read --tab-identifier windowtab1 --file-path Path/To/File.swift
81
+ xcode-mcli build log --tab-identifier windowtab1
82
+ xcode-mcli project build --tab-identifier windowtab1
83
+ xcode-mcli tests list --tab-identifier windowtab1
84
+ ```
85
+
86
+ ## Machine Output
87
+
88
+ Use `--json` for a stable wrapper envelope:
89
+
90
+ ```bash
91
+ xcode-mcli --json windows list
92
+ ```
93
+
94
+ Use `--verbose` to print the exact Xcode MCP tool name to `stderr` for tool-backed commands:
95
+
96
+ ```bash
97
+ xcode-mcli --json --verbose windows list
98
+ ```
99
+
100
+ `--json`, `--verbose`, and `--tab-identifier <id>` can be passed before the command path or after the specific command.
101
+
102
+ ## Compatibility Check
103
+
104
+ Verify the pinned Apple Xcode 26.3 MCP surface:
105
+
106
+ ```bash
107
+ npm run compat:xcode-mcp:verify
108
+ ```
109
+
110
+ Capture the current live surface:
111
+
112
+ ```bash
113
+ npm run compat:xcode-mcp:snapshot
114
+ ```
115
+
116
+ Pin a canonical baseline file:
117
+
118
+ ```bash
119
+ npm run compat:xcode-mcp:pin-26.3
120
+ ```
@@ -0,0 +1,160 @@
1
+ # Troubleshooting
2
+
3
+ ## `setup` fails to locate `mcpbridge`
4
+
5
+ Run:
6
+
7
+ ```bash
8
+ xcode-mcli setup
9
+ ```
10
+
11
+ If it fails with `Failed to locate xcrun mcpbridge.`:
12
+
13
+ - confirm Xcode is installed
14
+ - confirm `Xcode Tools` is enabled in `Settings > Intelligence`
15
+ - confirm `xcrun` resolves from your shell
16
+
17
+ If it fails with `Failed to call xcrun mcpbridge --help.`:
18
+
19
+ - enable `Xcode Tools` in `Settings > Intelligence`
20
+ - run `xcode-mcli setup` again
21
+
22
+ ## `windows list` returns no useful workspace
23
+
24
+ Make sure Xcode is open with a workspace or project window.
25
+
26
+ Then run:
27
+
28
+ ```bash
29
+ xcode-mcli windows list
30
+ ```
31
+
32
+ ## A tab-aware command fails because no active window is selected
33
+
34
+ Use one of these paths:
35
+
36
+ ```bash
37
+ xcode-mcli windows list
38
+ xcode-mcli windows use --tab-identifier windowtab1
39
+ ```
40
+
41
+ Or pass the tab directly on the command:
42
+
43
+ ```bash
44
+ xcode-mcli files read --tab-identifier windowtab1 --file-path Path/To/File.swift
45
+ ```
46
+
47
+ ## Multiple Xcode windows are open
48
+
49
+ Pick one explicitly:
50
+
51
+ ```bash
52
+ xcode-mcli windows list
53
+ xcode-mcli windows use --tab-identifier windowtab1
54
+ ```
55
+
56
+ ## Xcode permission prompts appear
57
+
58
+ The first tool-backed command may trigger an Xcode permission prompt for the CLI process.
59
+
60
+ If you change terminals, shells, or Node installations, macOS may ask again because the calling process identity changed.
61
+
62
+ ## The daemon looks stale
63
+
64
+ Restart it:
65
+
66
+ ```bash
67
+ xcode-mcli daemon restart
68
+ ```
69
+
70
+ Or stop and start it:
71
+
72
+ ```bash
73
+ xcode-mcli daemon stop
74
+ xcode-mcli daemon start
75
+ ```
76
+
77
+ Inspect status:
78
+
79
+ ```bash
80
+ xcode-mcli daemon status
81
+ ```
82
+
83
+ ## JSON output is mixed with diagnostic text
84
+
85
+ `--json` writes the stable envelope to `stdout`.
86
+
87
+ For tool-backed commands, `--verbose` writes the tool name to `stderr`.
88
+
89
+ This keeps `stdout` parseable:
90
+
91
+ ```bash
92
+ xcode-mcli windows list --json --verbose
93
+ ```
94
+
95
+ ## Mutating commands fail with a confirmation error
96
+
97
+ These commands require `--yes`:
98
+
99
+ - `files rm`
100
+ - `files update`
101
+ - `files write`
102
+ - `files mv --overwrite-existing`
103
+
104
+ Example:
105
+
106
+ ```bash
107
+ xcode-mcli files write --tab-identifier windowtab1 --file-path Path/To/File.swift --content 'let x = 1' --yes
108
+ ```
109
+
110
+ ## `files mkdir` or other mutating commands fail with an Xcode tool error
111
+
112
+ Some Xcode mutating tools depend on project structure resolution inside the active workspace.
113
+
114
+ If `files mkdir` reports a message telling you to run `XcodeLS` first, warm the structure with:
115
+
116
+ ```bash
117
+ xcode-mcli files ls --path .
118
+ ```
119
+
120
+ Then retry the mutating command against a path inside an existing project-recognized subtree.
121
+
122
+ If Xcode returns a group-resolution failure for a path that is not already represented in the project structure, use a disposable path under an existing group such as a known source folder.
123
+
124
+ ## Inspecting daemon state
125
+
126
+ The default state root is:
127
+
128
+ ```text
129
+ ~/Library/Application Support/xcode-mcli
130
+ ```
131
+
132
+ Important files:
133
+
134
+ - `daemon.sock`
135
+ - `daemon.pid`
136
+ - `daemon.log`
137
+ - `state.json`
138
+
139
+ ## `surface verify` reports a mismatch
140
+
141
+ Run:
142
+
143
+ ```bash
144
+ npm run compat:xcode-mcp:verify
145
+ ```
146
+
147
+ If it fails, inspect the categorized diff with:
148
+
149
+ ```bash
150
+ xcode-mcli surface verify --baseline-file skill/references/apple-xcode-26.3.surface.json --json
151
+ ```
152
+
153
+ Review whether the live Xcode release changed:
154
+
155
+ - protocol version
156
+ - tool definitions
157
+ - prompt definitions
158
+ - resource definitions
159
+
160
+ If the live contract is the one you want to support, re-pin the baseline snapshot after updating the wrapper behavior that depends on it.
@@ -0,0 +1,22 @@
1
+ import { z } from "zod";
2
+
3
+ import { defineCommand } from "../core/command-definition.ts";
4
+ import { restartDaemon } from "../runtime/daemon-host.ts";
5
+ import { printCommandResult } from "../runtime/output.ts";
6
+
7
+ export const daemonRestartCommand = defineCommand({
8
+ path: ["daemon", "restart"],
9
+ description: "Restart the daemon.",
10
+ optionsSchema: z.object({}),
11
+ run: async ({ commandPath, globals }) => {
12
+ await restartDaemon();
13
+ printCommandResult({
14
+ commandPath,
15
+ globals,
16
+ text: "Daemon restarted.",
17
+ data: {
18
+ running: true,
19
+ },
20
+ });
21
+ },
22
+ });
@@ -0,0 +1,22 @@
1
+ import { z } from "zod";
2
+
3
+ import { defineCommand } from "../core/command-definition.ts";
4
+ import { startDaemon } from "../runtime/daemon-host.ts";
5
+ import { printCommandResult } from "../runtime/output.ts";
6
+
7
+ export const daemonStartCommand = defineCommand({
8
+ path: ["daemon", "start"],
9
+ description: "Start the daemon.",
10
+ optionsSchema: z.object({}),
11
+ run: async ({ commandPath, globals }) => {
12
+ await startDaemon();
13
+ printCommandResult({
14
+ commandPath,
15
+ globals,
16
+ text: "Daemon started.",
17
+ data: {
18
+ running: true,
19
+ },
20
+ });
21
+ },
22
+ });
@@ -0,0 +1,29 @@
1
+ import { z } from "zod";
2
+
3
+ import { defineCommand } from "../core/command-definition.ts";
4
+ import { readDaemonStatus } from "../runtime/daemon-host.ts";
5
+ import { printCommandResult } from "../runtime/output.ts";
6
+
7
+ export const daemonStatusCommand = defineCommand({
8
+ path: ["daemon", "status"],
9
+ description: "Show daemon status.",
10
+ optionsSchema: z.object({}),
11
+ run: async ({ commandPath, globals }) => {
12
+ const status = await readDaemonStatus();
13
+ printCommandResult({
14
+ commandPath,
15
+ globals,
16
+ text: status.running
17
+ ? `Daemon is running with PID ${status.pid}.`
18
+ : "Daemon is not running.",
19
+ data: status.running
20
+ ? {
21
+ running: true,
22
+ pid: status.pid,
23
+ }
24
+ : {
25
+ running: false,
26
+ },
27
+ });
28
+ },
29
+ });
@@ -0,0 +1,22 @@
1
+ import { z } from "zod";
2
+
3
+ import { defineCommand } from "../core/command-definition.ts";
4
+ import { stopDaemon } from "../runtime/daemon-host.ts";
5
+ import { printCommandResult } from "../runtime/output.ts";
6
+
7
+ export const daemonStopCommand = defineCommand({
8
+ path: ["daemon", "stop"],
9
+ description: "Stop the daemon.",
10
+ optionsSchema: z.object({}),
11
+ run: async ({ commandPath, globals }) => {
12
+ await stopDaemon();
13
+ printCommandResult({
14
+ commandPath,
15
+ globals,
16
+ text: "Daemon stopped.",
17
+ data: {
18
+ running: false,
19
+ },
20
+ });
21
+ },
22
+ });
@@ -0,0 +1,26 @@
1
+ import type { CommandDefinition } from "../core/contracts.ts";
2
+ import { daemonRestartCommand } from "./daemon-restart.ts";
3
+ import { daemonStartCommand } from "./daemon-start.ts";
4
+ import { daemonStatusCommand } from "./daemon-status.ts";
5
+ import { daemonStopCommand } from "./daemon-stop.ts";
6
+ import { projectBuildCommand } from "./project-build.ts";
7
+ import { setupCommand } from "./setup.ts";
8
+ import { surfaceSnapshotCommand } from "./surface-snapshot.ts";
9
+ import { surfaceVerifyCommand } from "./surface-verify.ts";
10
+ import { windowsListCommand } from "./windows-list.ts";
11
+ import { windowsUseCommand } from "./windows-use.ts";
12
+ import { xcodeToolCommandDefinitions } from "./xcode-tool-commands.ts";
13
+
14
+ export const commandDefinitions: CommandDefinition[] = [
15
+ setupCommand,
16
+ daemonStartCommand,
17
+ daemonRestartCommand,
18
+ daemonStatusCommand,
19
+ daemonStopCommand,
20
+ projectBuildCommand,
21
+ surfaceSnapshotCommand,
22
+ surfaceVerifyCommand,
23
+ windowsListCommand,
24
+ windowsUseCommand,
25
+ ...xcodeToolCommandDefinitions,
26
+ ];
@@ -0,0 +1,50 @@
1
+ import { z } from "zod";
2
+
3
+ import { defineCommand } from "../core/command-definition.ts";
4
+ import { callDaemonTool } from "../runtime/daemon-host.ts";
5
+ import {
6
+ printCommandResult,
7
+ printVerboseTool,
8
+ toCommandData,
9
+ } from "../runtime/output.ts";
10
+ import { resolveTabIdentifier } from "../runtime/tab-resolver.ts";
11
+
12
+ const projectBuildOptionsSchema = z.object({
13
+ tabIdentifier: z.string().trim().min(1).optional(),
14
+ });
15
+ const projectBuildResultSchema = z.object({
16
+ structuredContent: z.unknown().optional(),
17
+ text: z.string().default(""),
18
+ });
19
+
20
+ export const projectBuildCommand = defineCommand({
21
+ path: ["project", "build"],
22
+ description: "Build the active Xcode project.",
23
+ toolName: "BuildProject",
24
+ configure: (command) => {
25
+ command.option("--tab-identifier <id>", "Active Xcode window tab identifier.");
26
+ },
27
+ optionsSchema: projectBuildOptionsSchema,
28
+ run: async ({ commandPath, globals, options }) => {
29
+ const tabIdentifier = await resolveTabIdentifier({
30
+ explicitTabIdentifier: options.tabIdentifier,
31
+ });
32
+ printVerboseTool(globals, "BuildProject");
33
+ const result = projectBuildResultSchema.parse(
34
+ await callDaemonTool({
35
+ name: "BuildProject",
36
+ arguments: {
37
+ tabIdentifier,
38
+ },
39
+ }),
40
+ );
41
+ printCommandResult({
42
+ commandPath,
43
+ globals,
44
+ text: result.text.trimEnd(),
45
+ data: toCommandData(result),
46
+ tabIdentifier,
47
+ tool: "BuildProject",
48
+ });
49
+ },
50
+ });
@@ -0,0 +1,26 @@
1
+ import { mkdir } from "node:fs/promises";
2
+ import { z } from "zod";
3
+
4
+ import { defineCommand } from "../core/command-definition.ts";
5
+ import { resolveStateRoot } from "../runtime/env.ts";
6
+ import { printCommandResult } from "../runtime/output.ts";
7
+ import { findMcpbridgePath, verifyMcpbridgeHelp } from "../runtime/xcrun.ts";
8
+
9
+ export const setupCommand = defineCommand({
10
+ path: ["setup"],
11
+ description: "Prepare xcode-mcli for use with Xcode MCP.",
12
+ optionsSchema: z.object({}),
13
+ run: async ({ commandPath, globals }) => {
14
+ await mkdir(resolveStateRoot(), { recursive: true });
15
+ await findMcpbridgePath();
16
+ await verifyMcpbridgeHelp();
17
+ printCommandResult({
18
+ commandPath,
19
+ globals,
20
+ text: "xcode-mcli setup complete.",
21
+ data: {
22
+ message: "xcode-mcli setup complete.",
23
+ },
24
+ });
25
+ },
26
+ });
@@ -0,0 +1,45 @@
1
+ import { z } from "zod";
2
+
3
+ import { defineCommand } from "../core/command-definition.ts";
4
+ import { printCommandResult } from "../runtime/output.ts";
5
+ import {
6
+ captureXcodeMcpSurfaceSnapshot,
7
+ writeXcodeMcpSurfaceSnapshotFile,
8
+ } from "../runtime/xcode-surface.ts";
9
+
10
+ export const surfaceSnapshotCommand = defineCommand({
11
+ path: ["surface", "snapshot"],
12
+ description: "Capture the live Xcode MCP surface.",
13
+ optionsSchema: z.object({
14
+ outputFile: z.string().trim().min(1).optional(),
15
+ }),
16
+ configure: (command) => {
17
+ command.option("--output-file <path>", "Write the canonical snapshot to a file.");
18
+ },
19
+ run: async ({ commandPath, globals, options, projectDir }) => {
20
+ const snapshot = await captureXcodeMcpSurfaceSnapshot();
21
+ if (options.outputFile) {
22
+ const outputPath = await writeXcodeMcpSurfaceSnapshotFile({
23
+ projectDir,
24
+ filePath: options.outputFile,
25
+ snapshot,
26
+ });
27
+ printCommandResult({
28
+ commandPath,
29
+ globals,
30
+ text: `Wrote Xcode MCP surface snapshot to ${outputPath}`,
31
+ data: {
32
+ outputFile: outputPath,
33
+ snapshot,
34
+ },
35
+ });
36
+ return;
37
+ }
38
+ printCommandResult({
39
+ commandPath,
40
+ globals,
41
+ text: JSON.stringify(snapshot, null, 2),
42
+ data: snapshot,
43
+ });
44
+ },
45
+ });
@@ -0,0 +1,46 @@
1
+ import { z } from "zod";
2
+
3
+ import { defineCommand } from "../core/command-definition.ts";
4
+ import { runtimeError } from "../core/errors.ts";
5
+ import { printCommandResult } from "../runtime/output.ts";
6
+ import {
7
+ captureXcodeMcpSurfaceSnapshot,
8
+ diffXcodeMcpSurfaceSnapshots,
9
+ readXcodeMcpSurfaceSnapshotFile,
10
+ xcodeMcpSurfaceDiffHasChanges,
11
+ } from "../runtime/xcode-surface.ts";
12
+
13
+ export const surfaceVerifyCommand = defineCommand({
14
+ path: ["surface", "verify"],
15
+ description: "Compare the live Xcode MCP surface against a baseline snapshot.",
16
+ optionsSchema: z.object({
17
+ baselineFile: z.string().trim().min(1),
18
+ }),
19
+ configure: (command) => {
20
+ command.requiredOption(
21
+ "--baseline-file <path>",
22
+ "Path to a canonical Xcode MCP surface snapshot.",
23
+ );
24
+ },
25
+ run: async ({ commandPath, globals, options, projectDir }) => {
26
+ const baseline = await readXcodeMcpSurfaceSnapshotFile(projectDir, options.baselineFile);
27
+ const live = await captureXcodeMcpSurfaceSnapshot();
28
+ const diff = diffXcodeMcpSurfaceSnapshots({
29
+ baseline,
30
+ live,
31
+ });
32
+ if (xcodeMcpSurfaceDiffHasChanges(diff)) {
33
+ throw runtimeError("Xcode MCP surface does not match the baseline snapshot.", diff);
34
+ }
35
+ printCommandResult({
36
+ commandPath,
37
+ globals,
38
+ text: "Xcode MCP surface matches the baseline snapshot.",
39
+ data: {
40
+ baselineFile: options.baselineFile,
41
+ compatible: true,
42
+ diff,
43
+ },
44
+ });
45
+ },
46
+ });