From c56d76a77f620e14731a43e8060d845348b804fa Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Tue, 19 May 2026 15:15:14 +0200 Subject: [PATCH] T-131: wrap T-99 with governance amendments and shell-usage docs D-56 and D-68 gain dated implementation-notes amendments pointing at the eight T-99 slices (T-124 server through T-131 wrap-up) and the D-70/71/72/73 records the work spawned. D-68's main Decision text swaps "Transport deferred to Q-33" for the closed reference to D-73. CONTRIBUTING.md gains a "Running clide from the shell" section covering the verb surface, exit-code contract per D-68, and `/ide` MCP discovery via `~/.claude/ide/.lock`. governance/ README.md picks up the D-73 entry it was missing. T-119 moves out of backlog (its IPC dispatch path now exists); T-120 gains a 2026-05-19 re-scope note pointing the typed schema framework at the now-real argv grammar / wire contract. T-99 itself transitions to done. Co-Authored-By: Claude --- .pql/changelog/ticket_history/2026-05.sql | 25 ++++++++++++ .pql/changelog/tickets/2026-05.sql | 38 ++++++++++++++++++ CHANGELOG.md | 6 +++ CONTRIBUTING.md | 48 +++++++++++++++++++++++ governance/README.md | 1 + governance/decisions/architecture.md | 6 ++- 6 files changed, 122 insertions(+), 2 deletions(-) diff --git a/.pql/changelog/ticket_history/2026-05.sql b/.pql/changelog/ticket_history/2026-05.sql index e65b9e46..dd42cdc3 100644 --- a/.pql/changelog/ticket_history/2026-05.sql +++ b/.pql/changelog/ticket_history/2026-05.sql @@ -1866,3 +1866,28 @@ INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-129', 'status', 'in_progress', 'done', NULL, '2026-05-19 12:19:51', '2026-05-19 12:19:51', '2026-05-19 12:19:51', NULL, 'e9bf50652357521c7ee74bd6577e8108', 1) ON CONFLICT(hash) DO NOTHING; INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-130', 'status', 'backlog', 'in_progress', NULL, '2026-05-19 12:24:44', '2026-05-19 12:24:44', '2026-05-19 12:24:44', NULL, '63dd7e0c4346a5d3cdd81c9c477339c3', 1) ON CONFLICT(hash) DO NOTHING; INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-130', 'status', 'in_progress', 'done', NULL, '2026-05-19 12:33:52', '2026-05-19 12:33:52', '2026-05-19 12:33:52', NULL, 'f3060e086423af96f99684e3718cac45', 1) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-131', 'status', 'backlog', 'in_progress', NULL, '2026-05-19 13:10:27', '2026-05-19 13:10:27', '2026-05-19 13:10:27', NULL, 'c49e76c430de0cc42d7919ade9c5cfa5', 1) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-119', 'status', 'backlog', 'ready', NULL, '2026-05-19 13:14:01', '2026-05-19 13:14:01', '2026-05-19 13:14:01', NULL, '726631190b62bd38fb34f7201d2f27ee', 1) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-120', 'description', 'T-104 covered the spot-fixes (argv-injection rejection, size/count caps on specific commands). What remains is the framework piece: a typed schema per IPC command — branch/remote/path/etc. with regex/charset constraints — applied at DaemonDispatcher dispatch time rather than scattered through individual handlers. + +This needs design before code: +- Where the schema lives (per-handler? a central registry?) +- How it composes with the IpcRequest envelope +- Whether it generates the existing handler boilerplate or wraps it +- Coordination with T-99''s IPC architecture decision (if we go socket-server, the schema becomes the wire contract) + +Source: consultants.md "Security — Findings — [Major]" item 1.', 'T-104 covered the spot-fixes (argv-injection rejection, size/count caps on specific commands). What remains is the framework piece: a typed schema per IPC command — branch/remote/path/etc. with regex/charset constraints — applied at DaemonDispatcher dispatch time rather than scattered through individual handlers. + +Re-scope (2026-05-19, post T-99): the wire contract is now real — IpcRequest envelopes flow over the unix socket (per D-70/71/72) and through the C clide client which serialises raw argv under the `_argv` sentinel (per D-72/D-6). The argv parser (lib/src/cli/argv_to_request.dart) is the de-facto schema today; T-120 should formalise it by: + +- Lifting the per-cmd grammar from argv_to_request.dart into a typed registry keyed by cmd, sharing it with both the argv parser and the dispatcher. +- Using the same registry to validate inbound IpcRequests at dispatch time (regex/charset constraints on branch/remote/path args). +- Exposing the registry to the MCP server (T-130) so the `tools/list` payload is generated rather than hand-rolled. + +Design still open: +- Where the schema lives (per-handler? central registry?). +- Whether it generates handler boilerplate or wraps existing handlers. + +Source: consultants.md "Security — Findings — [Major]" item 1.', NULL, '2026-05-19 13:14:12', '2026-05-19 13:14:12', '2026-05-19 13:14:12', NULL, '3511c5e7731f568de7af33c1970ad97e', 1) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-99', 'status', 'backlog', 'done', NULL, '2026-05-19 13:14:41', '2026-05-19 13:14:41', '2026-05-19 13:14:41', NULL, '5ff47ab0fc7fb8cb8e9e039be48ada2a', 1) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-131', 'status', 'in_progress', 'done', NULL, '2026-05-19 13:14:41', '2026-05-19 13:14:41', '2026-05-19 13:14:41', NULL, 'a57b3fbe4e6815fcbb28691a08130801', 1) ON CONFLICT(hash) DO NOTHING; diff --git a/.pql/changelog/tickets/2026-05.sql b/.pql/changelog/tickets/2026-05.sql index b5d1b5f1..9cdeaa11 100644 --- a/.pql/changelog/tickets/2026-05.sql +++ b/.pql/changelog/tickets/2026-05.sql @@ -2180,3 +2180,41 @@ Acceptance: 4. Q-32 + Q-33 closed (either as decisions or with resolutions written into the ticket). Source: T-99 sketch. Depends on T-124 (server foundation must exist). Can land in parallel with T-127 / T-128 / T-129.', 'done', 'medium', NULL, NULL, 'D-68', '2026-05-18 11:59:17', '2026-05-19 12:33:52', NULL, '0d03fa2376bf179665ee817dc363e186', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-119', 'task', 'T-99', 'clide panel resize CLI verb (split from T-111)', 'Once T-99 lands an IPC dispatch path, register a ''panel.resize'' command that takes and args and calls LayoutArrangement.setSize / setEditorRatio. Keyboard parity already landed in T-111; this completes user/Claude parity per D-6.', 'ready', 'low', NULL, NULL, NULL, '2026-05-18 07:43:50', '2026-05-19 13:14:01', NULL, '08bd7d35f6ac787f1895d92a23a65cec', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-120', 'task', 'T-97', 'typed IPC command schema framework (split from T-104)', 'T-104 covered the spot-fixes (argv-injection rejection, size/count caps on specific commands). What remains is the framework piece: a typed schema per IPC command — branch/remote/path/etc. with regex/charset constraints — applied at DaemonDispatcher dispatch time rather than scattered through individual handlers. + +Re-scope (2026-05-19, post T-99): the wire contract is now real — IpcRequest envelopes flow over the unix socket (per D-70/71/72) and through the C clide client which serialises raw argv under the `_argv` sentinel (per D-72/D-6). The argv parser (lib/src/cli/argv_to_request.dart) is the de-facto schema today; T-120 should formalise it by: + +- Lifting the per-cmd grammar from argv_to_request.dart into a typed registry keyed by cmd, sharing it with both the argv parser and the dispatcher. +- Using the same registry to validate inbound IpcRequests at dispatch time (regex/charset constraints on branch/remote/path args). +- Exposing the registry to the MCP server (T-130) so the `tools/list` payload is generated rather than hand-rolled. + +Design still open: +- Where the schema lives (per-handler? central registry?). +- Whether it generates handler boilerplate or wraps existing handlers. + +Source: consultants.md "Security — Findings — [Major]" item 1.', 'backlog', 'medium', NULL, NULL, NULL, '2026-05-18 07:53:08', '2026-05-19 13:14:12', NULL, 'cac26b3ebcf14b056fdd178d4c60c721', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-131', 'task', 'T-99', 'T-99 wrap-up: governance + docs + unblock T-119 / T-120', 'Eighth and final slice of T-99(a). Bookkeeping once the substantive work lands. + +Acceptance: +1. D-56 gets an amendment recording that the unix socket server + C client + MCP server all landed (with the dated amendment line format the rest of the testing.md / architecture.md decisions use). +2. D-68 referenced as implemented in its own record. +3. CONTRIBUTING.md gets a ''Running clide from the shell'' section (`clide status`, `clide tail --events`, etc.). +4. T-119 (`clide panel resize` CLI verb) moved out of backlog into ready — its IPC dispatch path now exists. +5. T-120 (typed IPC schema framework) re-scoped if needed; the wire contract is now real. +6. T-99 itself transitions to done. + +Source: T-99 sketch. Depends on T-124 + T-125 + T-126 + T-127 + T-128 (+T-129, +T-130 if landed).', 'done', 'medium', NULL, NULL, NULL, '2026-05-18 11:59:23', '2026-05-19 13:14:41', NULL, '2acd8d20756c1615238472fedfe0d6df', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-99', 'task', 'T-97', 'implement (or amend) the IPC socket server per D-1/D-6/D-56/D-68', 'D-56 specifies the Flutter app hosts an in-process IPC server reachable by a thin C client over a Unix socket; D-1/D-6 commit to CLI-first. Today no `ServerSocket` for unix-domain exists anywhere in `lib/`. Three IPC clients coexist (`DaemonClient` socket, `InProcessClient`, `IsolateClient` + `Backend`/`backend_entry.dart`); two service-wiring sites duplicate registration. The load-bearing CLI-first guardrail has no runtime path. D-68 (filed in this session) commits to the dual-surface plan. + +**Pick one path:** +- **(a)** Implement the socket server per D-56 + the C `clide` client. Delete `InProcessClient` and `IsolateClient` + `backend_entry.dart`. Wire MCP server alongside per D-68. +- **(b)** Amend D-56 to declare in-process direct dispatch as the design; delete `DaemonClient`''s socket code, `IsolateClient`, `Backend`, and `backend_entry.dart`. Re-evaluate D-1''s CLI-first claim and D-68''s dual-surface plan. + +**Acceptance:** +1. Exactly one IPC model exists in code. +2. `clide ` shells from a terminal can drive a running clide app (or the contract is formally rescinded). +3. Service registration happens in one place. +4. D-records updated to match reality. + +Source: consultants.md "Architecture — Findings — [Critical] No IPC socket server exists".', 'done', 'high', NULL, NULL, NULL, '2026-05-17 18:47:18', '2026-05-19 13:14:41', NULL, '2ad1fbf0b1b6f5b9e46d4a31d1ad830c', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a5b241d..43209a6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,9 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit. "always open this project on launch" checkbox in welcome's RECENT list sets a sticky-startup flag; if exactly one project has it, that one opens directly. Two or more, or none ⇒ picker (T-115). +- CONTRIBUTING.md "Running clide from the shell" section — documents + the shell verbs, exit-code contract per D-68, and Claude Code + `/ide` MCP discovery via `~/.claude/ide/.lock` (T-131). ### Changed @@ -60,6 +63,9 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit. Flutter UI's `DaemonClient` now talks to its own `IpcServer` over the same per-workspace Unix socket the C `clide` client uses — one transport, one contract. +- D-56 / D-68 amended with implementation notes — both decisions now + link out to T-99's eight slices (T-124–T-131) and the D-70/71/72/73 + records they spawned (T-131). ### Deprecated diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 704ef8bb..7978179d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -82,6 +82,54 @@ FNV-1a vectors against the reference, and [`test/cli/clide_cli_e2e_test.dart`](test/cli/clide_cli_e2e_test.dart) compiles the C client and exercises the full round-trip. +## Running clide from the shell + +Once the desktop app is running and `clide` is on your `$PATH`, every +shell inside the workspace can drive it. The argv grammar is fixed by +[D-6](governance/decisions/architecture.md#d-6-cli-and-event-surface-contract) +— every UI affordance has a CLI counterpart, and every CLI verb has a +UI affordance. + +```sh +clide status # one-shot snapshot of pane state +clide ping # round-trip health check +clide tail --events # stream every subsystem event +clide tail --events --filter git # stream a single subsystem +clide pane focus editor # drive the running app +clide panel toggle git # show/hide a panel +``` + +Output is JSON on stdout, one envelope per line. Streaming verbs +(`tail --events`) keep the connection open and emit one event line per +push; Ctrl-C cleanly closes the socket. Exit codes follow the pql +convention ([D-68](governance/decisions/architecture.md#d-68-cli-and-mcp-surface-contracts)): + +| Code | Meaning | +|------|------------------------------------------------------| +| 0 | success | +| 64 | user error — bad argv, unknown verb, malformed flag | +| 65 | data error — request well-formed but rejected | +| 69 | service unavailable — no running clide for this repo | +| 70 | internal error — dispatcher threw | + +If `clide` exits 69, the desktop app isn't running for this workspace +— start it with `make run` (during development) or launch the +installed app pointed at this repo. + +### Claude Code's `/ide` and the MCP server + +The desktop app also runs an MCP companion server on a random +localhost port (HTTP + Server-Sent Events, per +[D-73](governance/decisions/architecture.md#d-73-mcp-server-transport-over-http-sse)). +Discovery follows the Claude Code `/ide` contract: clide writes a +JSON descriptor to `~/.claude/ide/.lock` containing the chosen +port, the workspace root, and the protocol version. Claude Code's +`/ide` command picks the lock for the workspace it's running in and +connects automatically — no manual configuration. The lock is removed +on graceful shutdown; stale locks from crashed processes are reaped +on the next start. The implementation lives in +[`lib/src/ipc/mcp_server.dart`](lib/src/ipc/mcp_server.dart). + ## Decisions, questions, rejected (DQR) clide tracks architectural commitments as durable records under diff --git a/governance/README.md b/governance/README.md index 90be5422..693ef4be 100644 --- a/governance/README.md +++ b/governance/README.md @@ -114,6 +114,7 @@ You might also want, project-permitting: - [D-70: IPC socket path is per-workspace, deterministic](decisions/architecture.md#d-70-ipc-socket-path-is-per-workspace-deterministic) — _architecture_ - [D-71: IPC socket access gated by chmod 0600 on socket + parent](decisions/architecture.md#d-71-ipc-socket-access-gated-by-chmod-0600-on-socket--parent) — _architecture_ - [D-72: IPC server is multi-connection with serial dispatch on the main isolate](decisions/architecture.md#d-72-ipc-server-is-multi-connection-with-serial-dispatch-on-the-main-isolate) — _architecture_ +- [D-73: MCP transport for /ide is SSE over HTTP](decisions/architecture.md#d-73-mcp-transport-for-ide-is-sse-over-http) — _architecture_ ## Open questions diff --git a/governance/decisions/architecture.md b/governance/decisions/architecture.md index b4be21be..3c0c1bc0 100644 --- a/governance/decisions/architecture.md +++ b/governance/decisions/architecture.md @@ -226,7 +226,8 @@ Core, rendering, IPC, kernel, panel manager. - `/decisions/`, `/docs/`, `/legacy/` — unchanged - **Rationale:** One package means one `pubspec.yaml`, one `flutter analyze`, one `flutter test`, no `cd` gymnastics, no cross-package import barriers. The IPC server running in-process eliminates the daemon lifecycle (start, stop, reconnect, pid file). If the app crashes, tmux sessions survive; the app re-attaches on restart. The CLI client in C is ~100 lines (socket connect + JSON exchange) with the same contract as pql. - **Cost:** If the Flutter app is not running, Claude's `clide` commands fail. In practice this is acceptable — the IDE being closed means the user isn't working. A future "headless mode" could start the Flutter engine without a window if needed. -- **Cross-reference:** [D-5](#d-5-dart-core-sidecar-dissolved-ptyc-as-pql-peer) (amended), [D-41](#d-41-claude-panes-one-primary-per-repo-tmux-backed) (tmux persistence), [D-1](#d-1-cli-first-not-mcp) (CLI-first surface preserved via C client). +- **Amendment (2026-05-19):** Implemented in T-99 across eight slices (T-124 server, T-125 argv translator, T-126 C client, T-127 socket loopback replacing InProcessClient, T-128 legacy-IPC cleanup, T-129 event streaming, T-130 MCP companion, T-131 this wrap-up). Per-workspace unix socket at the D-70 path; D-71 chmod gate; D-72 multi-connection serial dispatch; D-73 MCP transport. The C `clide` client lives at `native/clide-cli/clide.c` and ships with `make clide-cli`. Only the socket IPC model survives — InProcessClient + IsolateClient + Backend gone. +- **Cross-reference:** [D-5](#d-5-dart-core-sidecar-dissolved-ptyc-as-pql-peer) (amended), [D-41](#d-41-claude-panes-one-primary-per-repo-tmux-backed) (tmux persistence), [D-1](#d-1-cli-first-not-mcp) (CLI-first surface preserved via C client), [D-70](#d-70-ipc-socket-path-is-per-workspace-deterministic) / [D-71](#d-71-ipc-socket-access-gated-by-chmod-0600-on-socket--parent) / [D-72](#d-72-ipc-server-is-multi-connection-with-serial-dispatch-on-the-main-isolate) (implementation contracts). - **Raised by:** 2026-04-23 architectural simplification. ### D-57: Frameless custom chrome with per-column 24px hats @@ -248,7 +249,8 @@ Core, rendering, IPC, kernel, panel manager. ### D-68: Dual integration surface — Bash CLI primary, MCP secondary - **Date:** 2026-05-15 -- **Decision:** clide exposes two integration surfaces over the same in-process `DaemonDispatcher`. **(1) Bash CLI over Unix socket — primary.** Per [D-1](#d-1-cli-first-not-mcp) and [D-56](#d-56-dissolve-daemon-process-flutter-app-hosts-ipc-server), a thin C client (`clide …`) connects to a per-user Unix socket served in-process by the Flutter app, exchanges JSON-lines, and exits. This is the surface Claude-Code-in-a-pane uses; it is also the surface for human shell use, scripts, and external editor integrations. Full action surface — `pane.*`, `files.*`, `editor.*`, `git.*`, `pql.*`, …. **(2) `/ide`-compatible MCP server — secondary.** clide additionally serves an MCP endpoint compatible with Claude Code's `/ide` integration (the same protocol VS Code and JetBrains plugins serve). Minimum tools: `mcp__ide__getDiagnostics`, `mcp__ide__executeCode`. Optional `mcp__clide__*` namespace exposing high-leverage clide tools is deferred to [Q-32](../questions/architecture.md#q-32-mcp-tool-surface-minimum-slash-ide-or-extended-clide-tools). Transport choice deferred to [Q-33](../questions/architecture.md#q-33-mcp-transport-sse-websocket-stdio-or-all). The MCP server wraps the *same* `DaemonDispatcher`; there is no second source of truth. +- **Amendment (2026-05-19):** Both surfaces implemented in T-99. CLI lands as `IpcServer` over the unix socket (T-124, per D-70/71/72) with the C client at `native/clide-cli/clide.c` (T-126); the argv grammar lives in `lib/src/cli/argv_to_request.dart` (T-125). MCP lands as `McpServer` over HTTP+SSE (T-130, per D-73) — the transport choice closed Q-33. The two `/ide` minimum tools ship as stubs; real implementations follow as Q-32 resolves the broader tool-surface question. +- **Decision:** clide exposes two integration surfaces over the same in-process `DaemonDispatcher`. **(1) Bash CLI over Unix socket — primary.** Per [D-1](#d-1-cli-first-not-mcp) and [D-56](#d-56-dissolve-daemon-process-flutter-app-hosts-ipc-server), a thin C client (`clide …`) connects to a per-user Unix socket served in-process by the Flutter app, exchanges JSON-lines, and exits. This is the surface Claude-Code-in-a-pane uses; it is also the surface for human shell use, scripts, and external editor integrations. Full action surface — `pane.*`, `files.*`, `editor.*`, `git.*`, `pql.*`, …. **(2) `/ide`-compatible MCP server — secondary.** clide additionally serves an MCP endpoint compatible with Claude Code's `/ide` integration (the same protocol VS Code and JetBrains plugins serve). Minimum tools: `mcp__ide__getDiagnostics`, `mcp__ide__executeCode`. Optional `mcp__clide__*` namespace exposing high-leverage clide tools is deferred to [Q-32](../questions/architecture.md#q-32-mcp-tool-surface-minimum-slash-ide-or-extended-clide-tools). Transport closed by [D-73](#d-73-mcp-transport-for-ide-is-sse-over-http) — SSE over HTTP. The MCP server wraps the *same* `DaemonDispatcher`; there is no second source of truth. - **Context:** [D-1](#d-1-cli-first-not-mcp) chose CLI-first over MCP-only because MCP alone doesn't cover the action surface clide needs — Claude Code's `/ide` MCP exposes only two narrow tools (`getDiagnostics`, `executeCode`), enough for Claude to read diagnostics and run Jupyter cells but not enough to *drive* an IDE. The CLI surface gives full reach. But for users who run Claude Code *outside* clide and connect via `/ide`, MCP is the only path Claude Code knows; not serving it means clide is invisible to that workflow. The two surfaces are complementary, not alternatives. Reinforced by the [2026-05-14 consultant review](../../consultants.md): the architect flagged the absent socket server as the most critical drift; user confirmed the socket server (D-56 path a) plus an MCP companion. - **Rationale:** Both surfaces wrap the same dispatcher, so neither becomes a second source of truth. CLI remains the contract user/Claude parity ([D-6](#d-6-cli-and-event-surface-contract)) is enforced against. MCP is added because the `/ide` ecosystem is real and growing — VS Code, JetBrains, Cursor, Windsurf all serve compatible MCP — and clide should be a peer there. The implementation cost is a protocol adapter + tool definitions, not duplicate business logic. - **Cost:** Two transports to maintain. Mitigated by both wrapping the same dispatcher: the MCP adapter is the only thing that has to track `/ide` protocol evolution. If `mcp__clide__*` tools are added (pending Q-32), surface bloat is the obvious risk — every CLI verb invites an MCP twin; resist by default, justify on user need.