# Claude IDE Bridge — Project Instructions

## Documentation

Comply with all docs in `/documents/`. Consult before changes:

- **[documents/platform-docs.md](documents/platform-docs.md)** — Full feature reference (177 tools registered). Consult before adding/modifying features. Authoritative count: `node scripts/audit-lsp-tools.mjs` Stats line.
- **[documents/prompts-reference.md](documents/prompts-reference.md)** — All 36 MCP prompts reference.
- **[documents/styleguide.md](documents/styleguide.md)** — Code conventions, UI patterns, output formats. Follow for all new tools, handlers, responses.
- **[documents/roadmap.md](documents/roadmap.md)** — Development direction. Check before exploratory work.
- **[documents/data-reference.md](documents/data-reference.md)** — Data flows, state mgmt, protocol details. Consult before modifying connection/auth/state logic.
- **[documents/plugin-authoring.md](documents/plugin-authoring.md)** — Plugin manifest schema, entrypoint API, distribution.
- **[docs/adr/](docs/adr/)** — Architecture Decision Records. Read before touching version numbers, lock files, error codes, session mgmt, or reconnect logic.

> **Cowork (computer-use) sessions:** MCP bridge tools NOT available inside Cowork. Run `/mcp__bridge__cowork` in regular Desktop chat first to capture IDE context, then switch to Cowork. Cowork runs in isolated git worktree — output won't appear in `git status` on main until merged. (see [docs/cowork.md](docs/cowork.md))

### CLI Subcommands

- `init [--workspace <path>]` — One-command setup: install extension + write CLAUDE.md + print next steps
- `start-all` — Launch tmux session with bridge + extension watcher panes
- `install-extension` — Install companion VS Code extension
- `gen-claude-md` — Generate starter CLAUDE.md for current workspace
- `print-token [--port N]` — Print auth token from active lock file
- `gen-plugin-stub <dir> --name <org/name> --prefix <prefix> [--ts]` — Scaffold new plugin (add `--ts` for TypeScript variant)
- `quick-task <preset>` — Launch a context-aware Claude task from a preset (fixErrors, refactorFile, addTests, explainCode, optimizePerf, runTests, resumeLastCancelled). Same dispatch path as the sidebar. Requires `--driver subprocess`.
- `start-task "<description>"` — Enqueue a free-form Claude task; Claude gathers its own workspace context.
- `continue-handoff` — Resume from the stored handoff note (skips auto-snapshots).
- `halts [--window 1h|24h|overnight|7d|any] [--recipe <name>] [--json]` — One-screen morning summary of recent recipe halts. Discovers the running bridge via lock file, queries `/runs/halt-summary`, formats per category + 5 most-recent reasons. Default window is `overnight` (since 6pm yesterday local). `--recipe` filters to a single recipe by name. Composes the haltReason field + category aggregator shipped in #441/#444.
- `recipe new <name>` — Scaffold a recipe from a template (`minimal` | `daily` | `inbox`). Add `--interactive` (or `-i`) to drop into the connector-aware prompt tree instead: mode pick (Guided / Template / AI-suggest), then step-by-step build. Generated YAML includes the SchemaStore pragma and runs `validateRecipeDefinition` post-hoc as warnings. AI-suggest discovers the running bridge via `~/.claude/ide/*.lock` and POSTs the goal to `/recipes/generate`; raw response written to disk (no form normalization).
- `--watch` — Auto-restart supervisor with exponential backoff (2s → 30s). Safe for production.

#### Recipe verbs (beyond `recipe new`)

- `recipe list` — Print installed recipes from the active bridge.
- `recipe install <source>` — Install from `github:owner/repo[/path][@ref]`. Same shape the dashboard install panel posts.
- `recipe uninstall <name>` — Remove a locally installed recipe.
- `recipe enable <name>` / `recipe disable <name>` — Flip the per-recipe disabled marker so cron / file-watch triggers stop firing without uninstalling. Used by the dashboard's pause toggle.
- `recipe run <name> [--local --dry-run --step <id> --attempt <n> --ledger-dir <path> --var k=v]` — Manual run with overrides; `--local` skips the bridge API.
- `recipe lint <file.yaml>` — Lint a recipe YAML against the schema + best-practice rules.
- `recipe preflight <file.yaml>` — Connector preflight: list authorisations the recipe needs.
- `recipe fmt <file.yaml>` — Format a recipe YAML in place.
- `recipe record <name>` — Record a successful run as a learned trace.
- `recipe schema` — Print the active recipe JSON schema.

#### Operational commands

- `start [--port N] [--workspace <path>]` — Single-bridge start (no tmux). Pair with `--watch` for supervised mode.
- `status` — One-line bridge status: lock file, port, uptime, session count.
- `tools [--slim] [--json]` — List tools the bridge would register without starting it.
- `tools list [--json]` — Same as bare `tools`, kept for symmetry with `search`.
- `tools search <query> [--json]` — Filter the registered tools by name / description substring.
- `install <companion>` — Install one of the bundled MCP-companion server registrations into Claude Desktop or Claude Code config. Use `--target cli|desktop` to choose, `--env KEY=VAL` to pass per-companion env vars. Companions: `memory`, `superpowers`, `devtools`, `database`, `slack`, `playwright`, `codebase-memory`. Each is a documented server config; the command writes it into `~/.claude.json` (CLI) or the Claude Desktop config (desktop) atomically.
- `kill-switch engage|release|status [--reason <text>]` — Toggle the global write-disable gate (see ADR-0012).
- `analytics show|configure|clear|test` — Manage the opt-in telemetry collector config (endpoint + shared secret) at `~/.claude/ide/analytics-config.json` (mode 0600). Replaces the brittle pattern of putting the secret in a launchd plist. `configure --endpoint URL --key KEY` writes both atomically; `test` sends a tiny synthetic payload and reports the HTTP status; `show` prints active values and resolution source (env / config / default). Env vars still win for headless/CI.
- `panic` — Shortcut for `kill-switch engage --reason "manual panic"`.
- `judgments [--window 1h|24h|overnight|7d|any] [--recipe <name>] [--json]` — Recent judge-step verdicts (from recipe steps with `agent.kind: judge`) across runs. Discovers the running bridge via lock file, queries `/runs/judge-summary`, prints per-verdict counts + 5 most-recent. Sibling of `halts`; same window/filter shape.
- `suggest [--since-days N]` — Recipe co-occurrence + unused-tool suggestions from recent activity.
- `traces export [--passphrase <p>] [--mode keyed|public] > file.jsonl` — Export decision traces; `--mode keyed` encrypts with the passphrase.
- `traces import [--passphrase <p>] [--dry-run] < file.jsonl` — Restore traces from an export.
- `token-efficiency benchmark [...]` — Measure token cost across slim/full tool sets.
- `launchd install|uninstall|status` — Manage the macOS LaunchAgent for the bridge (auto-start at login).
- `orchestrator [--port N] [--workspace <path>]` — Start a multi-bridge orchestrator (parent/child topology).
- `notify <event> [...]` — Forward a Claude Code hook event to the bridge `/notify` endpoint (wired from `~/.claude/settings.json`).
- `shim` — stdio MCP bridge for Claude Desktop. Auto-invoked by Desktop's `claude_desktop_config.json`; not normally run by hand.

#### Environment variables

Most users don't need to touch these — CLI flags cover the common cases. Listed here so deployments and supervisors have a complete reference.

| Var | Effect |
|---|---|
| `CLAUDE_IDE_BRIDGE_TOKEN` | Override the auto-generated auth token in the lock file. Use with `--fixed-token` in deployments. |
| `CLAUDE_IDE_BRIDGE_CONFIG` | Path to a JSON config file the bridge reads at startup (alternative to CLI flags). |
| `CLAUDE_IDE_BRIDGE_GRACE_PERIOD` | ms (default 120000) — session-restore window after WebSocket disconnect. Equivalent to `--grace-period`. |
| `CLAUDE_IDE_BRIDGE_EDITOR` | Editor identity reported on `extension/hello` (default auto-detect). |
| `CLAUDE_IDE_BRIDGE_LINTERS` | Comma-separated linter binaries the bridge will probe for. |
| `CLAUDE_IDE_BRIDGE_TIMEOUT` | ms (default 30000) — tool execution timeout. |
| `CLAUDE_IDE_BRIDGE_MAX_RESULT_SIZE` | bytes — cap on tool result payload size before truncation. |
| `CLAUDE_IDE_BRIDGE_ISSUER_URL` | Equivalent to `--issuer-url`; activates OAuth 2.0 mode. |
| `CLAUDE_IDE_BRIDGE_CORS_ORIGINS` | Comma-separated CORS origins (alternative to repeated `--cors-origin`). |
| `CLAUDE_IDE_BRIDGE_TRUST_PROXY` | Truthy → trust `X-Forwarded-For` (set when running behind nginx/Caddy). |
| `CLAUDE_IDE_BRIDGE_INSTALL_ALLOWED_HOSTS` | Comma-separated hostnames for `/recipes/install` source allowlist (default `github.com`). |
| `CLAUDE_IDE_BRIDGE_RECIPE_TMP_JAIL` | Path override for recipe-runner temp directory. |
| `BRIDGE_WEBHOOK_SECRET` | Equivalent to `--webhook-secret`; HMAC-SHA256 auth on `POST /hooks/*`. |
| `PATCHWORK_HOME` | Override `~/.patchwork` workspace root. |
| `PATCHWORK_BRIDGE_URL` / `PATCHWORK_BRIDGE_PORT` | CLI subcommands use these to find the bridge instead of the lock file (useful in remote-bridge setups). |
| `PATCHWORK_DASHBOARD_URL` | Default dashboard URL used by CLI "open" actions. |
| `PATCHWORK_CLAUDE_BINARY` | Equivalent to `--claude-binary`; path to the `claude` CLI. |
| `PATCHWORK_RECIPE_REPO_ALLOWLIST` | Comma-separated `owner/repo` allowlist for recipe install sources. |
| `PATCHWORK_TOKEN_DIR` / `PATCHWORK_TOKEN_STORAGE_BACKEND` | Connector-token storage location + backend (`file` vs `keychain`). |
| `PATCHWORK_FLAG_KILL_SWITCH_WRITES` | Feature flag — gate write tools on kill-switch state. |
| `PATCHWORK_FLAG_UI_SCHEMA_LINT` | Feature flag — strict UI-schema linting in the recipe editor. |
| `LOCAL_MODEL` / `LOCAL_ENDPOINT` / `LOCAL_API_KEY` / `LOCAL_ENDPOINT_ALLOW_REMOTE` | Local-model driver config (Ollama / vLLM / OpenAI-compatible endpoint). |
| `OTEL_SERVICE_NAME` | Override the OTel service name (default `claude-ide-bridge`). |
| `PATCHWORK_ANALYTICS_ENDPOINT` | Override the opt-in telemetry collector URL (default `https://analytics.claude-ide-bridge.dev/v1/usage`). Must be `http(s)://`; invalid values fall back to default. Per-call resolution; precedence: env > config file > default. For CI/headless. Prefer `patchwork analytics configure` for persistent setups — keeps the secret out of launchd plists. |
| `PATCHWORK_ANALYTICS_KEY` | Shared-secret sent as `X-Analytics-Key` header on telemetry POSTs. Only meaningful when paired with a self-hosted endpoint that checks it. Same precedence as endpoint. |

##### Connector credential env vars

Per-connector env override for OAuth client credentials (when self-hosting OAuth apps) or for PAT-style direct-token connectors. The dashboard `/connections` UI is the recommended setup path — these env vars are for headless / CI / scripted deployments. Setting `*_CLIENT_ID` + `*_CLIENT_SECRET` lets the bridge use your own GitHub/Slack/etc. OAuth app instead of the public one.

| Var(s) | Connector | Type |
|---|---|---|
| `PATCHWORK_GITHUB_CLIENT_ID` / `PATCHWORK_GITHUB_CLIENT_SECRET` | GitHub | OAuth app override |
| `PATCHWORK_SLACK_CLIENT_ID` / `PATCHWORK_SLACK_CLIENT_SECRET` | Slack | OAuth app override |
| `GMAIL_CLIENT_ID` / `GMAIL_CLIENT_SECRET` | Gmail | OAuth app override |
| `GOOGLE_CALENDAR_CLIENT_ID` / `GOOGLE_CALENDAR_CLIENT_SECRET` | Google Calendar | OAuth app override |
| `GOOGLE_DRIVE_CLIENT_ID` / `GOOGLE_DRIVE_CLIENT_SECRET` | Google Drive | OAuth app override |
| `ASANA_CLIENT_ID` / `ASANA_CLIENT_SECRET` | Asana | OAuth app override |
| `DISCORD_CLIENT_ID` / `DISCORD_CLIENT_SECRET` | Discord | OAuth app override |
| `GITLAB_CLIENT_ID` / `GITLAB_CLIENT_SECRET` / `GITLAB_BASE_URL` | GitLab | OAuth app override + self-hosted base URL |
| `JIRA_API_TOKEN` / `JIRA_EMAIL` / `JIRA_INSTANCE_URL` | Jira | PAT-style token |
| `CONFLUENCE_API_TOKEN` / `CONFLUENCE_EMAIL` / `CONFLUENCE_INSTANCE_URL` | Confluence | PAT-style token (HTTPS atlassian.net only) |
| `LINEAR_API_KEY` | Linear (non-MCP fallback) | PAT-style token |
| `NOTION_TOKEN` | Notion | PAT-style token |
| `HUBSPOT_ACCESS_TOKEN` | HubSpot | PAT-style token |
| `INTERCOM_ACCESS_TOKEN` | Intercom | PAT-style token |
| `DATADOG_API_KEY` / `DATADOG_APP_KEY` / `DATADOG_SITE` | Datadog | PAT-style (`SITE` enum-allowlisted) |
| `PAGERDUTY_TOKEN` / `PAGERDUTY_FROM_EMAIL` | PagerDuty | PAT-style token |
| `ZENDESK_API_TOKEN` / `ZENDESK_EMAIL` / `ZENDESK_SUBDOMAIN` | Zendesk | PAT-style token |
| `SENTRY_AUTH_TOKEN` | Sentry (non-MCP fallback) | PAT-style token |

##### Dashboard env vars (Next.js side)

The dashboard reads these from `dashboard/.env.local` / `.env` at startup. Most users don't change them — defaults pick a free port and assume a single-user local install.

| Var | Effect |
|---|---|
| `DASHBOARD_PASSWORD` | Single-user password gate for the dashboard. Required for any non-local deployment. |
| `DASHBOARD_SESSION_SECRET` | Cookie-signing secret (random hex ≥ 32 chars). |
| `DASHBOARD_ALLOW_UNAUTHENTICATED` | `1` to bypass the password gate (local dev only). |
| `DASHBOARD_AUTH_FAILURE_WINDOW_MS` / `DASHBOARD_AUTH_MAX_FAILURES` / `DASHBOARD_AUTH_LOCKOUT_MS` | Brute-force lockout tuning for the login form. |
| `PATCHWORK_BRIDGE_TOKEN` | Bearer token the dashboard uses when forwarding to a remote bridge (paired with `PATCHWORK_BRIDGE_URL`). |
| `NEXT_PUBLIC_BASE_PATH` | basePath for mounted-prefix deployments (`/dashboard` under nginx, etc.). |
| `VAPID_PRIVATE_KEY` / `VAPID_SUBJECT` / `NEXT_PUBLIC_VAPID_PUBLIC_KEY` | Web Push (PWA notifications). |
| `PATCHWORK_PUSH_TOKEN` / `PATCHWORK_PUSH_URL` / `PATCHWORK_PUSH_BASE_URL` | Phone-path push relay credentials (used by the optional remote relay; see ADR-0006). |

## Bug Fix Protocol

When bug reported, do NOT fix first. Instead:
1. Write test that reproduces bug (test must fail)
2. Have subagents fix bug and prove it with passing test
3. Only then consider bug fixed

## Build & Test

```bash
# Bridge
npm run build          # TypeScript compilation (wipes dist/ first)
npm test               # vitest

# Extension
cd vscode-extension
npm run build          # esbuild bundle
npm run package        # create .vsix

# Always rebuild bridge + extension + VSIX before testing changes
```

**Extension versioning rule:** Windsurf caches `.vsix` files by version number and will silently reuse the old bundle if the version hasn't changed. **Always bump `vscode-extension/package.json` version before packaging** when the user will install the `.vsix` in Windsurf (or any VS Code fork). Patch bump (`1.4.2` → `1.4.3`) is sufficient. Never repackage without bumping — the user will install it and see no change.

**Local global install rule (macOS):** Never run `npm install -g .` from a workspace under `~/Documents/`, `~/Desktop/`, or `~/Downloads/`. npm creates a symlink from `/opt/homebrew/lib/node_modules/<pkg>` into the workspace, and macOS TCC then silently blocks launchd-spawned processes from following that symlink (EPERM). The LaunchAgent will appear to install correctly but fail on first reload with `code: 'EPERM'`. Use `npm run install:global` (wraps `npm pack` + `npm install -g <tgz>`) instead — produces a real-copy install outside TCC's protected directory tree.

Before staging, run `npx biome check --write <files>` on changed files. Fix before stage — don't wait for hook to fail.

**Release channels:**
- `latest` / `beta` — manual via `chore(release):` PRs, tagged `v*-beta*` → `publish-npm.yml`. Stable / human-curated.
- `canary` — automatic on every green main merge via `publish-canary.yml`. Version shape `<base>.canary.<runNumber>` (e.g. `0.2.0-beta.5.canary.42`). `npm install -g patchwork-os@canary` always tracks main. Use for dogfooding freshly-merged changes without waiting for the next release PR. Never default-installed.

## LSP Workflows

All LSP tools available in both slim and full mode (full is the default since v2.43.0).

**Adding a new tool**
1. `searchWorkspaceSymbols` — confirm similar tool doesn't exist
2. `getDocumentSymbols` on `src/extensionClient.ts` — see available methods
3. `getHover { filePath, line, column }` — verify method signature before writing
4. `getDiagnostics { uri: "src/tools/myTool.ts" }` — catch type errors before `npm run build`
5. `getDiagnostics { uri: "src/tools/index.ts" }` — confirm no import errors after registering

> `getDiagnostics` uses `uri`, not `filePath`. Wrong key silently returns all-workspace diagnostics.

**Code review**
1. `getCallHierarchy { direction: "incoming" }` on each changed symbol — blast radius
2. `findReferences` on changed interfaces/types — find all implementation sites including test mocks
3. `getCodeActions` on flagged ranges — surface language server suggestions
4. `setEditorDecorations { id: "code-review", style: "warning"|"error", hoverMessage, message }` — annotate inline
5. `clearEditorDecorations { id: "code-review" }` when done

**Refactoring**
1. `refactorAnalyze` — returns `risk` (low/medium/high), `referenceCount`, `callerCount`. High risk (>20 refs or >10 callers) → write tests first per Bug Fix Protocol.
2. `refactorPreview` — see exact edits before committing
3. `renameSymbol` — execute; or `refactorExtractFunction { file, ... }` (param is `file`, not `filePath`)
4. `getDiagnostics` with no `uri` — workspace-wide type check (uses CLI/tsc when extension not connected)

**Debugging**
1. `searchWorkspaceSymbols` — jump to symbol from stack trace
2. `getCallHierarchy { direction: "outgoing" }` on failing handler — trace data flow
3. `getDocumentSymbols` on `src/extensionClient.ts` vs test mock — catch interface drift
4. `setDebugBreakpoints` + `evaluateInDebugger` — inspect runtime state

**Onboarding to unfamiliar code**
1. `getDocumentSymbols` — instant file outline
2. `explainSymbol` on primary export — richer than hover alone
3. `getCallHierarchy { direction: "incoming" }` — what depends on this module
4. `getImportTree` — full downstream dependency chain

**Quick reference**

| Situation | Tool |
|---|---|
| Tool for X exist? | `searchWorkspaceSymbols` |
| Method accepts what? | `getHover` |
| Hover N symbols | `batchGetHover` |
| Definition N symbols | `batchGoToDefinition` |
| Implementations N symbols | `batchFindImplementations` |
| Change breaking? | `getChangeImpact` or `getDiagnostics` + `findReferences` |
| Caller count? | `getCallHierarchy { direction: "incoming" }` |
| Safe to rename? | `refactorAnalyze` → `refactorPreview` → `renameSymbol` |
| File exports? | `getDocumentSymbols` |
| File imports (signatures)? | `getImportedSignatures` |
| Links / file refs in doc? | `getDocumentLinks` |
| Code lens counts? | `getCodeLens` |

## Architecture Rules

- **Tools**: factory pattern `createXxxTool(deps)` returning `{ schema, handler }`. Register in `src/tools/index.ts`.
- **Extension handlers**: standalone async functions in `handlers` map. Register in `vscode-extension/src/handlers/index.ts`.
- **WebSocket safety**: all `ws.send()` calls must use `safeSend()` or readyState check + try-catch.
- **Extension dependency**: tools requiring extension must set `extensionRequired: true` in schema.
- **Tool names**: must match `/^[a-zA-Z0-9_]+$/`.
- **Error handling**: tool execution errors return `isError: true` in content (NOT JSON-RPC errors). JSON-RPC errors (`ErrorCodes`, -32xxx) for protocol issues only. See [ADR-0004](docs/adr/0004-tool-errors-as-content.md).
- **`extensionClient` shape validation**: `proxy<T>()` is blind TypeScript cast with no runtime validation — **do NOT use for new methods**. Eight latent shape-mismatch bugs (v2.25.18–v2.25.24) from this pattern. For new methods:
  - `tryRequest<T>(method, params, timeout, signal)` — auto-unwraps `{error}` / `{success: false, error}` to `null`. Use when success path is single T shape and caller doesn't need to distinguish error paths.
  - `validatedRequest<T>(method, params, validator)` — runtime shape predicate. Use when success path is object with specific required fields (e.g. `{items, count}` wrappers).
  - Direct `requestOrNull` + inline unwrap — when handler has rich contract (e.g. `{success: true/false, data, error}`) and caller needs structured error (see `closeTab`, `saveFile`). Do NOT use `tryRequest` — hides info caller needs.
  - When auditing: read handler in `vscode-extension/src/handlers/*.ts`, enumerate ALL return statements (success AND error paths) before choosing helper. Test mocks always lie — handler file is ground truth.
- **Automation DSL**: automation hooks compile to an `AutomationProgram` ADT (`src/fp/automationProgram.ts`) via `parsePolicy` (`src/fp/policyParser.ts`) and run through a single interpreter `executeAutomationPolicy` (`src/fp/automationInterpreter.ts`). Seven nodes: `Hook`, `Sequence`, `Parallel`, `WithCooldown`, `WithDedup`, `WithRateLimit`, `WithRetry`. Side effects isolated behind the `Backend` interface (`src/fp/interpreterContext.ts`): `VsCodeBackend` (prod) + `TestBackend` (collector with `reset()`). `AutomationHooks` holds one `AutomationState` value (`src/fp/automationState.ts`) — all state transitions go through pure functions. New hooks: extend `HookType` union, add parser case, wire handler to `_runInterpreter(hookType, eventData)`. Parallel branches merge via `mergeAutomationStates` (max timestamp per key).

## Testing Requirements

- New tools: unit tests in `src/tools/__tests__/`
- New extension handlers: tests in `vscode-extension/src/__tests__/handlers/`
- Use vitest for both bridge and extension tests
- Coverage gates: 75% lines, 70% branches, 75% functions
- Test circuit breaker and reconnect behavior for connection-related changes
- **outputSchema is mandatory** for all tools. `scripts/audit-lsp-tools.mjs` enforces per-schema-block (not per-file) — multi-tool files can't mask gaps. Exceptions go in `scripts/audit-output-schema-allowlist.json` with a reason; ratchet gate rejects new entries and stale ones.

## Plugin System

Plugins register additional MCP tools without forking bridge. Run in-process alongside built-in tools.

- **Scaffold**: `claude-ide-bridge gen-plugin-stub <dir> --name "org/name" --prefix "myPrefix"` (add `--ts` for TypeScript variant — adds tsconfig + build/dev scripts; compiled artifact lands at `index.mjs` so the manifest entrypoint stays the same)
- **Load**: `--plugin <path-or-npm-package>` (repeatable). `--plugin-watch` enables hot reload.
- **Manifest**: `claude-ide-bridge-plugin.json` with `schemaVersion: 1`. Tool names must start with `toolNamePrefix` (2-20 chars, `/^[a-zA-Z][a-zA-Z0-9_]{1,19}$/`).
- **Entrypoint**: exports `register(ctx)` where `ctx` provides `workspace`, `workspaceFolders`, `config`, `logger`.
- **Distribution**: publish to npm with keyword `claude-ide-bridge-plugin`; install via package name.
- **Lifecycle**: loaded after CLI probes, before sessions accepted. On hot-reload, tools re-registered atomically.
- **No symlinks**: Files in `claude-ide-bridge-plugin/` are standalone copies, not symlinks. After modifying plugin source, manually sync copies — they will NOT auto-update.

Full reference: [documents/plugin-authoring.md](documents/plugin-authoring.md)

## OAuth 2.0 Mode

For remote deployments where claude.ai custom connectors need authenticated access.

- **Activation**: `--issuer-url <public-https-url>` activates OAuth 2.0. `--cors-origin <origin>` (repeatable) sets `Access-Control-Allow-Origin` on all responses.
- **Endpoints**: `/.well-known/oauth-authorization-server` (RFC 8414), `/.well-known/oauth-protected-resource` (RFC 9728), `/oauth/register` (RFC 7591 dynamic client registration), `/oauth/authorize` (approval page), `/oauth/token`, `/oauth/revoke` (RFC 7009).
- **Design**: PKCE S256 mandatory. Auth codes single-use, 5-min TTL. Access tokens opaque base64url, 24-hour TTL. No refresh tokens — clients connecting TO the bridge re-authorize on expiry. Note: connector tokens (bridge connecting OUT to external services) DO use refresh tokens and auto-refresh on 401 via `baseConnector.refreshToken()`.
- **CIMD**: if `client_id` is an `https://` URL, bridge fetches the Client ID Metadata Document (RFC draft) to discover `redirect_uris`; 5-min cache, 8 KB max, SSRF-guarded.
- **Bridge token**: resource owner credential. Entered in `/oauth/authorize` approval page. All string comparisons timing-safe.
- **CORS env var**: `CLAUDE_IDE_BRIDGE_CORS_ORIGINS=https://claude.ai,https://other.example.com` (comma-separated alternative to `--cors-origin`).
- **Never** commit bridge token, `--fixed-token` values, real domain names, `--issuer-url` values, or `--cors-origin` values to version control.

## Remote Deployment

- **VPS flags**: `--bind 0.0.0.0` exposes to all interfaces. `--vps` expands command allowlist (adds curl, systemctl, docker, etc.). `--fixed-token <uuid>` prevents token rotation on restart.
- **Headless (no IDE)**: `print-token [--port N]` retrieves auth token from lock file. CLI tools work; LSP/debugger tools require VS Code extension.
- **VS Code Remote-SSH / Cursor SSH**: extension has `extensionKind: ["workspace"]` — loads on VPS side automatically. Full tool support.
- **Reverse proxy**: required for remote access (nginx or Caddy with TLS). See [docs/remote-access.md](docs/remote-access.md).
- **Systemd + deploy scripts**: `deploy/bootstrap-new-vps.sh` (full provisioning), `deploy/install-vps-service.sh` (idempotent service install). See [deploy/README.md](deploy/README.md).
- **Scheduled task templates not auto-installed**: Copy from `templates/scheduled-tasks/` to `~/.claude/scheduled-tasks/` manually, then restart Claude Desktop.

## Claude Orchestration

Bridge spawns Claude Code subprocesses as background tasks.

- **Activation**: `--driver subprocess` (or `api`). Default `none` (disabled).
- **Tools**: `runClaudeTask` (enqueue prompt), `getClaudeTaskStatus`, `cancelClaudeTask`, `listClaudeTasks`, `resumeClaudeTask`.
- **Task lifecycle**: `pending` → `running` → `done | error | cancelled | interrupted`. Output streams to VS Code output channel, capped at 50KB.
- **Binary**: `--claude-binary <path>` overrides Claude CLI path (default: `claude` on PATH).

Full reference: [documents/platform-docs.md](documents/platform-docs.md) (Claude orchestration section).

## Automation Policy

Event-driven hooks that trigger Claude tasks automatically.

- **Activation**: `--automation --automation-policy <path.json> --driver subprocess`
- **Hooks**:
  - `onDiagnosticsStateChange` (v2.43.0+) — unified diagnostics hook. `state: "error"` fires on new error/warning diagnostics (`{{file}}`, `{{diagnostics}}`, severity filter). `state: "cleared"` fires when errors/warnings drop to zero (`{{file}}`). Replaces deprecated `onDiagnosticsError` + `onDiagnosticsCleared`.
  - `onFileSave` — matching files saved. Minimatch glob patterns. Placeholder: `{{file}}`.
  - `onFileChanged` — matching files changed (buffer change, not save). Minimatch glob patterns. Placeholder: `{{file}}`.
  - `onRecipeSave` — fires when any `.yaml`/`.yml` file is saved. Placeholder: `{{file}}`. Default prompt (when no `prompt`/`promptName`) runs `patchwork recipe preflight {{file}}` and reports issues as a Claude task. Override with explicit prompt for custom behavior. Cooldown key: per-file, default 10 000 ms.
  - `onCompaction` (v2.43.0+) — unified hook. `phase: "pre"` fires before compaction (snapshot state); `phase: "post"` fires after (re-inject IDE state). Replaces the now-deprecated `onPreCompact` + `onPostCompact` pair; legacy names still work but emit a deprecation warning. Removed no earlier than 2026-09-01.
  - `onInstructionsLoaded` — fires at session start. Injects bridge status summary.
  - `onGitCommit` — fires after successful `gitCommit`. Placeholders: `{{hash}}`, `{{branch}}`, `{{message}}`, `{{count}}`, `{{files}}`.
  - `onGitPull` — fires after successful `gitPull`. Placeholders: `{{remote}}`, `{{branch}}`.
  - `onGitPush` — fires after successful `gitPush`. Placeholders: `{{remote}}`, `{{branch}}`, `{{hash}}`.
  - `onBranchCheckout` — fires after successful `gitCheckout`. Placeholders: `{{branch}}`, `{{previousBranch}}`, `{{created}}`.
  - `onPullRequest` — fires after successful `githubCreatePR`. Placeholders: `{{url}}`, `{{number}}`, `{{title}}`, `{{branch}}`.
  - `onTestRun` — fires after `runTests` completes. Placeholders: `{{runner}}`, `{{failed}}`, `{{passed}}`, `{{total}}`, `{{failures}}` (JSON array). Supports `filter: "any"|"failure"|"pass-after-fail"` (v2.43.0+). `"pass-after-fail"` replaces the deprecated separate `onTestPassAfterFailure` hook. Legacy `onFailureOnly` boolean still works but emits a deprecation warning.
  - `onTaskCreated` — fires on Claude Code TaskCreated hook (CC 2.1.84+). Placeholders: `{{taskId}}`, `{{prompt}}`.
  - `onTaskSuccess` — fires when orchestrator task completes successfully. Placeholders: `{{taskId}}`, `{{output}}`.
  - `onPermissionDenied` — fires on Claude Code PermissionDenied hook (CC 2.1.89+). Placeholders: `{{tool}}`, `{{reason}}`.
  - `onCwdChanged` — fires when Claude Code CWD changes (CC 2.1.83+). Placeholder: `{{cwd}}`.
  - `onDebugSession` (v2.43.0+) — unified debug-session hook. `phase: "start"` fires on session start (`{{sessionName}}`, `{{sessionType}}`, `{{breakpointCount}}`, `{{activeFile}}`). `phase: "end"` fires on termination (`{{sessionName}}`, `{{sessionType}}`). Replaces deprecated `onDebugSessionStart` + `onDebugSessionEnd`.
- **Shared options**: all hooks support inline `prompt` string or `promptName`/`promptArgs` named prompt references. All support `cooldownMs` (min 5000).
- **Cooldown**: min 5s between triggers for same file/event. Max prompt size: 32KB.
- **Webhook fan-out** (v1, `onCompaction` only — see [ADR-0009](docs/adr/0009-automation-webhook-fanout.md)) — any opted-in hook may add an optional `webhook: { url, method?, headers? }` config. When the hook fires, the interpreter POSTs (or PUT/PATCH) a JSON body to the URL AFTER the inline prompt enqueue. Body shape: `{ hookType, phase?, timestamp, ...eventData }`. 10s timeout; non-2xx and network errors are logged but never block other hooks. SSRF guard: loopback (127.0.0.0/8, ::1, localhost) and public hosts allowed; other private ranges blocked unless `--automation-allow-private-webhooks` is set. Both inline `prompt` AND `webhook` may be set on the same entry — they run sequentially: prompt first, then webhook. Hook entries with only a `webhook` (no `prompt` / `promptName`) are valid and skip the task enqueue. Example:
  ```json
  {
    "onCompaction": {
      "phase": "pre",
      "enabled": true,
      "cooldownMs": 5000,
      "webhook": {
        "url": "http://127.0.0.1:54321/hooks/compaction-snapshot-pre",
        "method": "POST",
        "headers": { "Content-Type": "application/json" }
      }
    }
  }
  ```
- **CC hook wiring** — hooks relying on Claude Code's hook system need MCP notify tools called from `settings.json`. Bridge registers these automatically when `--automation` active:

  | CC hook event | Shell command (settings.json) |
  |---|---|
  | `PreCompact` | `claude-ide-bridge notify PreCompact` |
  | `PostCompact` | `claude-ide-bridge notify PostCompact` |
  | `InstructionsLoaded` | `claude-ide-bridge notify InstructionsLoaded` |
  | `TaskCreated` | `claude-ide-bridge notify TaskCreated --taskId $TASK_ID --prompt $PROMPT` |
  | `PermissionDenied` | `claude-ide-bridge notify PermissionDenied --tool $TOOL --reason $REASON` |
  | `CwdChanged` | `claude-ide-bridge notify CwdChanged --cwd $CWD` |

  `notify` subcommand reads bridge lock file, looks up running port and auth token, POSTs to `/notify` HTTP endpoint. Bridge must be running.

  Example `~/.claude/settings.json` block (Claude Code requires `matcher` + `hooks` arrays):
  ```json
  "hooks": {
    "PreCompact": [
      { "matcher": "", "hooks": [{ "type": "command", "command": "claude-ide-bridge notify PreCompact" }] }
    ],
    "PostCompact": [
      { "matcher": "", "hooks": [{ "type": "command", "command": "claude-ide-bridge notify PostCompact" }] }
    ],
    "InstructionsLoaded": [
      { "matcher": "", "hooks": [{ "type": "command", "command": "claude-ide-bridge notify InstructionsLoaded" }] }
    ],
    "TaskCreated": [
      { "matcher": "", "hooks": [{ "type": "command", "command": "claude-ide-bridge notify TaskCreated --taskId $TASK_ID --prompt $PROMPT" }] }
    ],
    "PermissionDenied": [
      { "matcher": "", "hooks": [{ "type": "command", "command": "claude-ide-bridge notify PermissionDenied --tool $TOOL --reason $REASON" }] }
    ],
    "CwdChanged": [
      { "matcher": "", "hooks": [{ "type": "command", "command": "claude-ide-bridge notify CwdChanged --cwd $CWD" }] }
    ]
  }
  ```

## Transport & Session Model

| Transport | Client | Protocol |
|-----------|--------|----------|
| WebSocket | Claude Code CLI | `ws://127.0.0.1:<port>` with `x-claude-code-ide-authorization` header |
| stdio shim | Claude Desktop | stdin/stdout JSON-RPC, bridges to WebSocket internally |
| Streamable HTTP | Remote MCP clients (claude.ai, Codex CLI) | `POST/GET/DELETE /mcp` with Bearer token |

- **Lock file**: `~/.claude/ide/<port>.lock` — `{pid, workspace, authToken, isBridge: true, ...}`. Created with `O_EXCL` (prevents symlink attacks), permissions `0o600`. `isBridge: true` distinguishes bridge locks from IDE-owned locks. See [ADR-0003](docs/adr/0003-isbridge-lock-file-flag.md).
- **Auth**: token from lock file, validated with `crypto.timingSafeEqual`. Host header DNS rebinding defense rejects non-loopback hosts.
- **HTTP sessions**: max 5 concurrent, 2hr idle TTL, oldest idle (>60s) evicted on capacity. See [ADR-0005](docs/adr/0005-http-session-eviction.md).
- **Grace period**: `--grace-period <ms>` (default 120s) preserves session state across brief disconnects. Reconnecting client sending `X-Claude-Code-Session-Id` matching in-grace session is reattached (no new session, no re-initialization). stdio shim sends stable per-process UUID automatically.
- **Version numbers**: `BRIDGE_PROTOCOL_VERSION` (wire format, bump rarely) vs `PACKAGE_VERSION` (npm, every release). See [ADR-0001](docs/adr/0001-dual-version-numbers.md). Same dual-version applies to extension: `EXTENSION_PROTOCOL_VERSION` (wire compat, `"1.1.0"`) vs npm package version (`1.3.x`). `extension/hello` reports both — `protocolVersion` and `packageVersion`. Check both in bridge logs; `version=1.1.0` in logs is the wire version, not stale extension.
- **Generation guards**: every WebSocket callback checks `gen !== this.generation` to prevent stale callbacks corrupting new connection state. See [ADR-0002](docs/adr/0002-generation-guards-on-reconnect.md).

## Security Model

- **Command allowlist**: `runCommand` only executes allowlisted commands. Interpreter commands (node, python, bash, etc.) permanently blocked from `--allow-command`. Argument splitting prevents `--flag=value` injection.
- **SSRF defense** (`sendHttpRequest`): hostname blocklist for private/loopback ranges, DNS pre-resolution re-check, Host header override after user headers.
- **Path traversal** (`resolveFilePath` in `src/tools/utils.ts`): rejects null bytes, symlink escapes (ancestor chain walk), paths outside workspace.
- **Input validation**: AJV validates all tool arguments at transport layer before execution. `isValidRef` rejects leading-dash git refs. `searchAndReplace` rejects null bytes and `-`-prefixed globs. Clipboard enforces 1MB cap via `Buffer.byteLength`.
- **Rate limiting**: 200 requests/min (ring buffer), 500 notifications/min, per-session tool token bucket (default 60/min, configurable via `--tool-rate-limit`). Failed AJV validation does not consume rate limit tokens.
- **Error codes**: `ToolErrorCodes` (string codes in `isError: true` content blocks) for tool failures; `ErrorCodes` (JSON-RPC -32xxx) for protocol issues. Never mix. See [ADR-0004](docs/adr/0004-tool-errors-as-content.md).
- **Webhook HMAC auth** (`POST /hooks/*`): when started with `--webhook-secret <hex>` (or `BRIDGE_WEBHOOK_SECRET` env), requests carrying `X-Hub-Signature-256: sha256=<hex>` are authenticated via HMAC-SHA256 over the raw body (constant-time compare via `timingSafeEqual`). Bearer-token access still works — HMAC is additive. Without `--webhook-secret`, a request that presents `X-Hub-Signature-256` gets 401 `webhook_secret_not_configured` (must still pass Bearer gate to reach the handler); a missing/invalid signature with no Bearer returns 401 at the outer gate.

## Claude IDE Bridge

@import .claude/rules/bridge-tools.md

Bridge connected via MCP. Session-start hook reports connection status, tool count, and extension state automatically — check that summary before proceeding. If tools appear missing, call `getBridgeStatus` to diagnose.

> **MCP server name is `patchwork`** (project-level). Do NOT add a user-level `claude-ide-bridge` shim to `~/.claude.json` — it causes all bridge tool calls to stall. If you see `claude-ide-bridge · connecting…` in `/mcp`, remove it: `python3 -c "import json,pathlib; p=pathlib.Path('~/.claude.json').expanduser(); d=json.loads(p.read_text()); d['mcpServers'].pop('claude-ide-bridge',None); p.write_text(json.dumps(d,indent=2))"` then restart Claude Code.

### Bug fix methodology

When bug reported, do NOT fix first. Instead:
1. Write test that reproduces bug (must fail)
2. Fix bug, confirm test passes
3. Only then consider bug fixed

### Documentation & memory

- **After architectural changes** — update `CLAUDE.md` so future sessions have accurate context.
- **At end of work session** — save meaningful decisions to memory: *"Remember that we chose X approach because Y."*
- **Prune stale instructions** — remove/correct outdated guidance. Stale instructions cause confident mistakes.

### Modular rules (optional)

Move rules out of CLAUDE.md into scoped files under `.claude/rules/`:

```
.claude/rules/testing.md     — applies when working with test files
.claude/rules/security.md    — applies to auth, payments, sensitive modules
.claude/rules/typescript.md  — TypeScript-specific conventions
```

Reference from CLAUDE.md with:
```
@import .claude/rules/testing.md
```

Path globs on rule files mean Claude only loads them when working on matching files.

### Workflow rules

Bridge tool substitution rules in `.claude/rules/bridge-tools.md` (loaded above). Quick reference table below is summary.

### Quick reference

> Tools marked **[full]** are NOT available when the bridge was started with `--slim`. Full mode is the default (since v2.43.0); slim mode opts out to expose only IDE-exclusive tools (LSP, debugger, editor state). Call `getToolCapabilities` to confirm available tools.

| Task | Tool | Mode |
|---|---|---|
| Check errors / warnings | `getDiagnostics` | slim |
| Navigate to definition | `goToDefinition` | slim |
| Find all references | `findReferences` | slim |
| Call hierarchy | `getCallHierarchy` | slim |
| File symbols | `getDocumentSymbols` | slim |
| Interactive debug | `setDebugBreakpoints`, `startDebugging`, `evaluateInDebugger` | slim |
| Function signature at call site | `signatureHelp` | slim |
| Type hierarchy (supertypes/subtypes) | `getTypeHierarchy` | slim |
| Explain symbol (composite) | `explainSymbol` | slim |
| Inline type hints | `getInlayHints` | slim |
| Refactor safely | `refactorAnalyze` → `refactorPreview` → `renameSymbol` | slim |
| Extract function | `refactorExtractFunction` | slim |
| Bridge/extension health | `getBridgeStatus` | slim |
| Available tools? | `getToolCapabilities` | slim |
| Watch live diagnostics (long-poll) | `watchDiagnostics` | slim |
| Bundle editor context | `contextBundle` | slim |
| Stream recent activity events | `watchActivityLog` | slim |
| Screenshot | `captureScreenshot` | slim |
| List open editors | `getOpenEditors` | slim |
| Hover at cursor | `getHoverAtCursor` | slim |
| Go to declaration | `goToDeclaration` | slim |
| Go to type definition | `goToTypeDefinition` | slim |
| Find all implementations | `findImplementations` | slim |
| Batch find implementations | `batchFindImplementations` | slim |
| Selection range expand/shrink | `selectionRanges` | slim |
| Folding ranges | `foldingRanges` | slim |
| Preview code action | `previewCodeAction` | slim |
| Git status / diff | `getGitStatus`, `getGitDiff` | **[full]** |
| Stage, commit, push | `gitAdd`, `gitCommit`, `gitPush` | **[full]** |
| Open pull request | `githubCreatePR` | **[full]** |
| File tree | `getFileTree` | **[full]** |
| Run shell command | `runInTerminal`, `getTerminalOutput` | **[full]** |
| Edit file by line range | `editText` | **[full]** |
| Open file in editor | `openFile` | **[full]** |
| Find + replace across workspace | `searchAndReplace` | **[full]** |
| List VS Code tasks | `listVSCodeTasks` | **[full]** |
| Run VS Code task | `runVSCodeTask` | **[full]** |
| Project info (name, version, deps) | `getProjectInfo` | **[full]** |
| Enqueue Claude subprocess task | `runClaudeTask` | **[full]** |
| List Claude subprocess tasks | `listClaudeTasks` | **[full]** |
| Checkout branch | `gitCheckout` | **[full]** |
| Pull from remote | `gitPull` | **[full]** |
| List branches | `gitListBranches` | **[full]** |
| Blame file | `gitBlame` | **[full]** |
| Run VS Code command by ID | `executeVSCodeCommand` | **[full]** |
| Cross-session context | `setHandoffNote` / `getHandoffNote` | **[full]** |
| Lint / format | `fixAllLintErrors`, `formatDocument` | **[full]** |
| Security audit | `getSecurityAdvisories`, `auditDependencies` | **[full]** |
| Unused code | `detectUnusedCode` | **[full]** |
| Coverage report | `getCodeCoverage` | **[full]** |
| Change-heavy files | `getGitHotspots` | **[full]** |
| Scaffold tests | `generateTests` | **[full]** |
| PR description | `getPRTemplate` | **[full]** |
| Unified task context (issue/PR/commit) | `ctxGetTaskContext` | **[full]** |
| Query past decisions (approval/enrichment/recipe/agent) | `ctxQueryTraces` | **[full]** |
| Record a fix for future sessions | `ctxSaveTrace` | **[full]** |
| Enrich commit with linked issues | `enrichCommit` | **[full]** |
| Reverse: commits that touched an issue | `getCommitsForIssue` | **[full]** |
| Stack frame → introducing commit | `enrichStackTrace` | **[full]** |

### Patchwork context platform — when to call what

The bridge has a built-in cross-session memory layer. **Prefer these over raw `gh` / `git` tools** for task-context lookups:

- **Starting a task?** Call `ctxGetTaskContext(ref)` first. Accepts any ref shape (`#42`, `PR-42`, commit SHA). Returns issue/PR/commit + linked commits + reverse issue links in one call. Fail-soft — missing `gh` / git shows up in `warnings`, never throws.
- **Fixing a bug?** Pair with `enrichStackTrace(stackTrace)` on the failing trace — maps frames to the commit that likely introduced the bug.
- **Done resolving?** Call `ctxSaveTrace(ref, problem, solution, tags?)` with a one-line problem + one-line solution. Future sessions see it via the session-start digest and `ctxQueryTraces`.
- **Debugging oversight?** `ctxQueryTraces({traceType, key, since, limit})` reads across all four stores (approvals, enrichment links, recipe runs, decision traces). The dashboard `/traces` page is the human UI over the same data.

On every session connect, the bridge prepends a digest of the last 12h of decisions to its MCP instructions block (top 5, ≤2 KB). If you see a `RECENT DECISIONS` section in your system prompt, that's live context — use it before re-running `gh`.

### Dispatch prompts (mobile)

Terse messages via Claude Desktop Dispatch (phone/Siri) are auto-routed to bridge prompts. Invoke directly by name in any chat. Keep responses under 20 lines for Dispatch.

> Require full mode (the default). Not available when the bridge was started with `--slim`.

| Phone message | Prompt | Tools called |
|---|---|---|
| "How's the build?" | `project-status` | `getGitStatus`, `getDiagnostics` |
| "Review my changes" | `quick-review` | `getGitStatus`, `getGitDiff`, `getDiagnostics` |
| "Does it build?" | `build-check` | `getProjectInfo`, `getDiagnostics`, `runCommand` |
| "What changed?" | `recent-activity` | `getGitLog`, `getGitStatus` |

### Agent Teams & Scheduled Tasks

| Context | Prompt | What it does |
|---|---|---|
| Team lead checking parallel agents | `team-status` | Workspace state, active tasks, recent activity across sessions |
| Scheduled nightly/hourly health check | `health-check` | Tests + diagnostics + security advisories + git status |

> `team-status` requires multiple Claude Code sessions connected simultaneously.

> **Claude Code ≥ v2.1.77**: `SendMessage` auto-resumes stopped agents.

Scheduled task templates (nightly-review, health-check, dependency-audit) included with bridge package. Copy to `~/.claude/scheduled-tasks/` and restart Claude Desktop. Find in `$(npm root -g)/claude-ide-bridge/templates/scheduled-tasks/`.

### Cowork (computer-use)

**MCP bridge tools NOT available inside Cowork.** Run `/mcp__bridge__cowork` in regular Claude Code or Desktop chat first to gather context and write handoff note, then open Cowork.

Workflow:
1. Regular chat: run `/mcp__bridge__cowork` → Claude collects IDE state → calls `setHandoffNote`
2. Open Cowork (Cmd+2 on Mac) → Cowork reads handoff note for context

**If bridge tools missing inside Cowork:** wrong context. Exit, run prompt in regular chat, return.

Full details: [docs/cowork.md](docs/cowork.md)

**Cowork uses git worktrees:** Cowork operates in isolated git worktree, not main workspace root. Files land in worktree. Always add "write all files to workspace root, not subdirectory" as first instruction in CLAUDE.md when using Cowork with synced workspace. After Cowork finishes, review and merge worktree branch back to main.

### Session continuity

| Scenario | Action |
|---|---|
| Switching CLI → Desktop | Call `setHandoffNote` before switching; bridge auto-snapshots if note >5 min stale |
| Session just started | Call `getHandoffNote` to pick up prior context (workspace-scoped). **Caution:** `onInstructionsLoaded` hook may have auto-overwritten note at session start — if content looks generic/templated, treat as stale and consult persistent session log (e.g. `docs/session-log.md`). |
| Bridge restarted | First connected client receives "restored from checkpoint" notification |
| Preparing for Cowork | Run `/mcp__bridge__cowork` in regular chat first — Cowork has no MCP access |
| Multi-workspace | Notes are workspace-scoped; switching workspaces won't overwrite each other's notes |
