From 23666e8302db0b04f1b987785afc81232ef3f616 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Mar 2026 01:22:11 +0000 Subject: [PATCH 1/2] Reorganize CLAUDE.md using .claude/rules/ for modular instructions Move architecture details and tool routing into path-scoped rule files under .claude/rules/ so they only load when working on relevant source files. Keep CLAUDE.md lean with just build commands and critical gotchas. Follows official Claude Code docs guidance: - CLAUDE.md: concise, universally-applicable instructions - .claude/rules/architecture.md: scoped to Sources/** and CCXcodeConnect/** - .claude/rules/tool-routing.md: scoped to MCPToolRouter and Tools/** https://claude.ai/code/session_015j35S7XTnDeexsyzXhPzZ6 --- .claude/rules/architecture.md | 41 ++++++++++++++++++++++++ .claude/rules/tool-routing.md | 24 ++++++++++++++ CLAUDE.md | 60 +---------------------------------- 3 files changed, 66 insertions(+), 59 deletions(-) create mode 100644 .claude/rules/architecture.md create mode 100644 .claude/rules/tool-routing.md diff --git a/.claude/rules/architecture.md b/.claude/rules/architecture.md new file mode 100644 index 0000000..f65deb5 --- /dev/null +++ b/.claude/rules/architecture.md @@ -0,0 +1,41 @@ +--- +paths: + - "Sources/XcodeConnectCore/**" + - "Sources/cc-xcode-connect/**" + - "CCXcodeConnect/**" +--- + +# Architecture + +Three targets managed via `Package.swift`: + +- **XcodeConnectCore** (library) — all core logic in `Sources/XcodeConnectCore/` +- **cc-xcode-connect** (CLI executable) — thin entry point in `Sources/cc-xcode-connect/` +- **CCXcodeConnect.app** (Xcode project) — thin menu bar wrapper in `CCXcodeConnect/`, references the local package + +## Multi-Workspace Support + +`AdapterSupervisor` monitors Xcode and creates one `AdapterServer` per open workspace, each with its own WebSocket port, lock file, and editor context — but all sharing a single `MCPBridgeClient` connection to `xcrun mcpbridge`. + +``` +AdapterSupervisor (XcodeMonitor + workspace polling) + ├── MCPBridgeClient → xcrun mcpbridge (shared, 1 process) + ├── AdapterServer(workspace: "/Users/x/ProjectA") + │ ├── WebSocketServer :54321 → Claude Code #1, #2, ... + │ ├── LockFile ~/.claude/ide/54321.lock + │ ├── MCPToolRouter (tabIdentifier=windowtab1) + │ └── EditorContext (filters files under /Users/x/ProjectA) + └── AdapterServer(workspace: "/Users/x/ProjectB") + ├── WebSocketServer :54322 → Claude Code #3 + ├── LockFile ~/.claude/ide/54322.lock + ├── MCPToolRouter (tabIdentifier=windowtab2) + └── EditorContext (filters files under /Users/x/ProjectB) +``` + +Each WebSocket server accepts multiple Claude Code clients simultaneously — notifications are broadcast to all, responses are routed back to the sender. + +The CLI also supports `--workspace ` for running a single targeted instance with its own bridge client. + +**Request flow**: WebSocket frame → `WebSocketServer.handleMessage` → JSON-RPC decode → `MCPToolRouter.callTool` → local IDE tool handler (some tools internally proxy to mcpbridge with per-workspace `tabIdentifier`). + +**Editor context**: `EditorContext` polls Xcode every 500ms via AppleScript (`osascript`) for active file path and selection range, sends `selection_changed` JSON-RPC notifications over WebSocket. Each worker filters events by its `workspaceFilter` path prefix. diff --git a/.claude/rules/tool-routing.md b/.claude/rules/tool-routing.md new file mode 100644 index 0000000..379611f --- /dev/null +++ b/.claude/rules/tool-routing.md @@ -0,0 +1,24 @@ +--- +paths: + - "Sources/XcodeConnectCore/MCPToolRouter.swift" + - "Sources/XcodeConnectCore/Tools/**" +--- + +# Tool Routing + +`MCPToolRouter` exposes 9 IDE-specific tools that conform to the IDE API: + +| IDE Tool | Implementation | +|----------|---------------| +| `openFile` | `xed --line N path` | +| `getDiagnostics` | Proxies to `XcodeListNavigatorIssues` with optional glob filter by file | +| `executeCode` | Proxies to `ExecuteSnippet` via mcpbridge | +| `getCurrentSelection` / `getLatestSelection` | Returns current editor selection from `EditorContext` | +| `getOpenEditors` | Lists open editors via AppleScript | +| `getWorkspaceFolders` | Returns detected workspace paths | +| `checkDocumentDirty` | Checks unsaved changes via AppleScript | +| `saveDocument` | Saves a document via AppleScript | + +Diff tools (`openDiff`, `closeDiff`, `closeAllDiffTabs`) are intentionally not exposed — Claude Code falls back to its built-in terminal diff view, which gives the user proper accept/reject control. + +Only `getDiagnostics` and `executeCode` proxy to mcpbridge internally; other Xcode MCP bridge tools are not exposed. Unknown tool calls return an error. diff --git a/CLAUDE.md b/CLAUDE.md index ca487f7..bf01f0f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,7 +1,5 @@ # CLAUDE.md -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - ## Build & Run ```bash @@ -16,66 +14,10 @@ Requires macOS 14+ and a running Xcode instance with `xcrun mcpbridge` available The .app registers as a login item via `SMAppService.mainApp` so it starts automatically at login. -## Architecture - -This repo has three targets managed via a `Package.swift` at the repo root: - -- **XcodeConnectCore** (library) — all core logic in `Sources/XcodeConnectCore/` -- **cc-xcode-connect** (CLI executable) — thin entry point in `Sources/cc-xcode-connect/` -- **CCXcodeConnect.app** (Xcode project) — thin menu bar wrapper in `CCXcodeConnect/`, references the local package - -### Multi-Workspace Support - -The adapter supports N simultaneous Xcode windows. An `AdapterSupervisor` monitors Xcode and creates one `AdapterServer` per open workspace, each with its own WebSocket port, lock file, and editor context — but all sharing a single `MCPBridgeClient` connection to `xcrun mcpbridge`. - -``` -AdapterSupervisor (XcodeMonitor + workspace polling) - ├── MCPBridgeClient → xcrun mcpbridge (shared, 1 process) - ├── AdapterServer(workspace: "/Users/x/ProjectA") - │ ├── WebSocketServer :54321 → Claude Code #1, #2, ... - │ ├── LockFile ~/.claude/ide/54321.lock - │ ├── MCPToolRouter (tabIdentifier=windowtab1) - │ └── EditorContext (filters files under /Users/x/ProjectA) - └── AdapterServer(workspace: "/Users/x/ProjectB") - ├── WebSocketServer :54322 → Claude Code #3 - ├── LockFile ~/.claude/ide/54322.lock - ├── MCPToolRouter (tabIdentifier=windowtab2) - └── EditorContext (filters files under /Users/x/ProjectB) -``` - -Each WebSocket server accepts multiple Claude Code clients simultaneously — notifications are broadcast to all, responses are routed back to the sender. - -The CLI also supports `--workspace ` for running a single targeted instance with its own bridge client. - -**Request flow**: WebSocket frame → `WebSocketServer.handleMessage` → JSON-RPC decode → `MCPToolRouter.callTool` → either local IDE tool handler or proxy to mcpbridge (with per-workspace `tabIdentifier`). - -**Editor context**: `EditorContext` polls Xcode every 500ms via AppleScript (`osascript`) for active file path and selection range, sends `selection_changed` JSON-RPC notifications over WebSocket. Each worker filters events by its `workspaceFilter` path prefix. - -## Key Implementation Details +## Gotchas - **NIO WebSocket handlers** must be created per-connection in `upgradePipelineHandler` (not shared). Use `handlerAdded` (not `channelActive`) to register the client channel since the channel is already active during HTTP→WS upgrade. - **Channel writes** (`writeAndFlush`) must happen on the NIO event loop via `channel.eventLoop.execute {}`. - **MCPBridgeClient** uses `NSLock.withLock` for thread-safe request tracking and `CheckedContinuation` to bridge callback-based STDIO I/O to async/await. - **mcpbridge response format**: Tool results come as `content[].text` containing JSON with a `message` field (e.g. `{"message":"* tabIdentifier: X, workspacePath: Y"}`), not as structured JSON arrays. - **Lock file cleanup**: `LockFileManager` removes stale `.lock` files from crashed instances on startup by checking PIDs with `kill(pid, 0)`. - -## Tool Routing - -`MCPToolRouter` exposes exactly 9 IDE-specific tools (it does NOT expose all mcpbridge tools): - -| IDE Tool | Implementation | -|----------|---------------| -| `openFile` | `xed --line N path` | -| `getDiagnostics` | Wraps `XcodeListNavigatorIssues` mcpbridge tool with optional glob filter by file | -| `executeCode` | Wraps `ExecuteSnippet` mcpbridge tool | -| `getCurrentSelection` / `getLatestSelection` | Returns current editor selection from `EditorContext` | -| `getOpenEditors` | Lists open editors via AppleScript | -| `getWorkspaceFolders` | Returns detected workspace paths | -| `checkDocumentDirty` | Checks unsaved changes via AppleScript | -| `saveDocument` | Saves a document via AppleScript | - -Only these 9 tools are exposed to Claude Code clients. Tool calls for any other tool name will fail with "Unknown tool" error. - -The `getDiagnostics` and `executeCode` tools internally call mcpbridge tools (`XcodeListNavigatorIssues` and `ExecuteSnippet` respectively) with `tabIdentifier` auto-injected, but no other mcpbridge tools are accessible. - -Diff tools (`openDiff`, `closeDiff`, `closeAllDiffTabs`) are intentionally not exposed — Claude Code falls back to its built-in terminal diff view, which gives the user proper accept/reject control. From 54f7fa98f37dd5abf05a2ad370c5f7b987368b46 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Mar 2026 01:22:29 +0000 Subject: [PATCH 2/2] Move CLAUDE.md into .claude/ directory Both ./CLAUDE.md and ./.claude/CLAUDE.md are valid project instruction locations per Claude Code docs. Moving into .claude/ keeps all Claude configuration together in one directory alongside rules/. https://claude.ai/code/session_015j35S7XTnDeexsyzXhPzZ6 --- CLAUDE.md => .claude/CLAUDE.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename CLAUDE.md => .claude/CLAUDE.md (100%) diff --git a/CLAUDE.md b/.claude/CLAUDE.md similarity index 100% rename from CLAUDE.md rename to .claude/CLAUDE.md