Skip to content

feat: add VS Code IDE bridge extension#213

Open
suger-m wants to merge 2 commits intoclaude-code-best:mainfrom
suger-m:feat/vscode-ide-bridge-pr
Open

feat: add VS Code IDE bridge extension#213
suger-m wants to merge 2 commits intoclaude-code-best:mainfrom
suger-m:feat/vscode-ide-bridge-pr

Conversation

@suger-m
Copy link
Copy Markdown
Contributor

@suger-m suger-m commented Apr 8, 2026

Summary

  • add a standalone VS Code IDE bridge extension package under packages/vscode-ide-bridge
  • wire up root and package-local VS Code launch/task workflows plus Chinese design docs
  • add packaging support so the bridge can be built into a .vsix

Verification

  • �un test packages/vscode-ide-bridge/test
  • �unx tsc -p packages/vscode-ide-bridge/tsconfig.json --pretty false
  • �un run package (in packages/vscode-ide-bridge)

Summary by CodeRabbit

  • New Features

    • Adds a local VS Code IDE Bridge extension for CLI integration, real-time selection publishing, authenticated WebSocket connections, diff preview/tab management, and IDE discovery lockfile generation.
    • Exposes restart and status commands within VS Code.
  • Documentation

    • Adds design spec, implementation plan, and package README with usage and packaging instructions.
  • Tests

    • Adds comprehensive tests covering server, transport, diff controller, lockfile, workspace helpers, and packaging/workflow.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 8, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4df51c36-fce6-4953-8156-1fb3b7155732

📥 Commits

Reviewing files that changed from the base of the PR and between 2248030 and ea344ad.

📒 Files selected for processing (1)
  • packages/vscode-ide-bridge/test/lockfile.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/vscode-ide-bridge/test/lockfile.test.ts

📝 Walkthrough

Walkthrough

Adds a new VS Code extension package packages/vscode-ide-bridge implementing a local WebSocket IDE bridge (lockfile discovery, auth), MCP server tools for diffs, diff session management, selection publishing, tooling/transport layers, TypeScript build config, workspace/debug tasks, docs, and comprehensive tests.

Changes

Cohort / File(s) Summary
Root workspace config
/.vscode/launch.json, /.vscode/tasks.json
Added workspace launch config "Run VSCode IDE Bridge" and tasks to build/test the bridge.
Design & plan docs
docs/superpowers/plans/...2026-04-07-vscode-ide-bridge.md, docs/superpowers/specs/...2026-04-07-vscode-ide-bridge-design.md
Added implementation plan and detailed design spec outlining lockfile format, WebSocket bridge, MCP tool contracts, diff/tab lifecycle, and verification strategy.
Package metadata & packaging
packages/vscode-ide-bridge/package.json, packages/vscode-ide-bridge/tsconfig.json, packages/vscode-ide-bridge/LICENSE.txt, packages/vscode-ide-bridge/.vscodeignore, packages/vscode-ide-bridge/README.md
Added package manifest, TS config, license, packaging ignore, and README documenting capabilities, usage, and packaging steps.
Package workspace config
packages/vscode-ide-bridge/.vscode/launch.json, packages/vscode-ide-bridge/.vscode/tasks.json
Added per-package VS Code launch and task entries for local extension debugging, build, test, and package tasks.
Extension entrypoint
packages/vscode-ide-bridge/src/extension.ts
Registers activation/deactivation, commands (restart, showStatus), and subscriptions to editor/workspace events to drive the bridge service.
MCP server & protocol
packages/vscode-ide-bridge/src/server/bridgeServer.ts, packages/vscode-ide-bridge/src/server/protocol.ts
Implements MCP server factory, tool discovery/handlers (openDiff, close_tab, closeAllDiffTabs), ide_connected notification handling, and Zod schemas/types.
Diff controller
packages/vscode-ide-bridge/src/server/diffController.ts
Implements virtual document provider, opens diffs, tracks sessions by tab_name, settles sessions via multiple paths (user action, save, tab/visibility close), and exposes control methods.
Service & lifecycle
packages/vscode-ide-bridge/src/server/localIdeBridgeService.ts
Manages WebSocket server startup on ephemeral port, lockfile write/remove, single active client lifecycle, auth, connection attach to MCP transport, selection publishing, and status reporting.
Transport & helpers
packages/vscode-ide-bridge/src/server/serverWebSocketTransport.ts, .../lockfile.ts, .../randomToken.ts, .../selectionPublisher.ts, .../terminalEnvironment.ts, .../workspaceInfo.ts
Added WebSocket JSON-RPC transport, lockfile builders and I/O, auth token generator, selection payload builder, terminal env var helpers, and workspace/selection snapshot helpers.
Type stubs
packages/vscode-ide-bridge/src/vscode.d.ts, packages/vscode-ide-bridge/src/ws.d.ts
Added minimal ambient module declarations to satisfy TypeScript imports for vscode and ws.
Tests
packages/vscode-ide-bridge/test/*
Comprehensive Bun-based tests for bridge server, transport, diff controller, lockfile helpers, selection publisher, terminal env, workspace info, packaging config, and workspace launch/tasks validation.

Sequence Diagram(s)

sequenceDiagram
    participant VSCode as VSCode
    participant Ext as Extension
    participant Service as LocalIdeBridgeService
    participant WS as WebSocketServer
    participant MCP as MCPServer
    participant CLI as ClaudeCode CLI
    participant Diff as DiffController

    VSCode->>Ext: activate()
    Ext->>Service: start()
    Service->>WS: listen on ephemeral port
    Service->>Service: write lockfile (port, auth token)
    VSCode->>Ext: editor selection/change
    Ext->>Service: publishActiveSelection()
    Service->>MCP: notifySelectionChanged()
    MCP-->>CLI: selection_changed notification

    CLI->>WS: connect (X-Claude-Code-Ide-Authorization header)
    WS->>Service: new connection
    Service->>Service: validate auth, attach transport
    Service->>MCP: attach transport -> MCP ready

    CLI->>MCP: CallTool openDiff
    MCP->>Diff: openDiff(args)
    Diff->>VSCode: show diff tab
    alt user saves file
      VSCode->>Diff: file saved -> settle (FILE_SAVED)
      Diff-->>MCP: tool result
    else user closes tab / closeTab call
      CLI->>MCP: closeTab
      MCP->>Diff: closeTab -> settle (SETTLED)
      Diff-->>MCP: tool result
    end

    VSCode->>Ext: deactivate()
    Ext->>Service: dispose()
    Service->>WS: close server
    Service->>Service: remove lockfile
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 Hopping bytes and websockets bright,

I stitch two worlds through day and night,
Lockfile beacon, diffs take wing,
Selections sing and MCPs ring,
A little bridge — code's delight!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add VS Code IDE bridge extension' accurately and concisely describes the main addition of a new VS Code extension package for IDE bridge functionality.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

🧹 Nitpick comments (7)
packages/vscode-ide-bridge/tsconfig.json (1)

12-14: Use official type packages instead of any stubs.

With only "bun" in types, this setup loses meaningful checking for VS Code and Node APIs and encourages ambient any declarations. Prefer adding official typings and listing them here.

💡 Proposed fix
   "types": [
-    "bun"
+    "bun",
+    "node",
+    "vscode"
   ]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/vscode-ide-bridge/tsconfig.json` around lines 12 - 14, The
tsconfig.json "types" array only includes "bun", which causes missing type
coverage for VS Code and Node APIs; update the "types" array in tsconfig.json to
include the official ambient packages such as "node" and "vscode" (and keep
"bun" if still needed) and ensure the corresponding packages are installed
(e.g., `@types/node` or the official vscode typings) so the project uses correct
official type definitions instead of ambient any stubs.
packages/vscode-ide-bridge/test/selectionPublisher.test.ts (1)

13-16: Strengthen selection assertions to cover all coordinates.

Current checks only validate start.line and end.character; regressions in start.character or end.line would slip through.

✅ Suggested test tightening
-    expect(params.selection?.start.line).toBe(10)
-    expect(params.selection?.end.character).toBe(17)
+    expect(params.selection).toEqual({
+      start: { line: 10, character: 2 },
+      end: { line: 10, character: 17 },
+    })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/vscode-ide-bridge/test/selectionPublisher.test.ts` around lines 13 -
16, The test in selectionPublisher.test.ts only asserts
params.selection?.start.line and params.selection?.end.character, leaving
start.character and end.line untested; update the assertions on the params
object to verify all four selection coordinates by adding checks for
params.selection?.start.character and params.selection?.end.line alongside the
existing expectations for start.line and end.character so the test validates
start.line, start.character, end.line, and end.character.
packages/vscode-ide-bridge/test/workspaceInfo.test.ts (1)

37-40: Assert the full selection payload, not just one field.

Only checking snapshot.selection?.start.line leaves start.character, end.line, and end.character unverified.

✅ Suggested assertion update
-    expect(snapshot.selection?.start.line).toBe(3)
+    expect(snapshot.selection).toEqual({
+      start: { line: 3, character: 1 },
+      end: { line: 5, character: 0 },
+    })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/vscode-ide-bridge/test/workspaceInfo.test.ts` around lines 37 - 40,
The test only asserts snapshot.selection?.start.line; update the assertion to
verify the entire selection payload by replacing the single-field check with an
equality check against an expected selection object (include start.line,
start.character, end.line, end.character). Locate the snapshot variable in
workspaceInfo.test.ts and use a deep equality assertion (e.g.,
expect(snapshot.selection).toEqual(expectedSelection)) or toMatchObject with an
explicit expectedSelection you construct with the correct numeric values.
packages/vscode-ide-bridge/test/bridgeServer.test.ts (1)

111-128: Guard notification wait with a timeout to avoid hung test runs.

If the notification is never emitted, this Promise can hang indefinitely. Wrapping it with a bounded timeout will make failures deterministic and easier to debug.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/vscode-ide-bridge/test/bridgeServer.test.ts` around lines 111 - 128,
The test's notificationPromise (created with client.setNotificationHandler and
SelectionChangedSchema) can hang forever if no notification arrives; wrap or
replace it with a timeout-guarded promise (e.g., race the handler promise with a
timer that rejects or fails after a short timeout) and then await that guarded
promise instead of notificationPromise; ensure the timeout includes a
descriptive error so bridge.notifySelectionChanged failures yield deterministic
test failures.
packages/vscode-ide-bridge/test/serverWebSocketTransport.test.ts (1)

21-71: Add unhappy-path transport tests for open-state and callback errors.

Current tests only validate the happy path. Please add coverage for: start() when readyState !== 1, send() callback errors, and close() cleanup/listener detachment to protect regressions in transport reliability paths.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/vscode-ide-bridge/test/serverWebSocketTransport.test.ts` around
lines 21 - 71, Add tests for ServerWebSocketTransport unhappy paths: 1) start()
when the FakeWebSocket.readyState is not 1 (e.g., 0 or 3) should reject or throw
— set socket.readyState accordingly before calling transport.start() and assert
it fails; 2) send() callback errors — make FakeWebSocket.send invoke its
callback with an Error and assert transport.send(...) rejects with that error;
3) close() cleanup/listener detachment — call transport.start(),
transport.close(), then emit a 'message' on the FakeWebSocket and assert
transport.onmessage is not invoked and socket listeners count is cleaned up.
Reference ServerWebSocketTransport.start, ServerWebSocketTransport.send,
ServerWebSocketTransport.close, and the FakeWebSocket test helper when adding
these tests.
packages/vscode-ide-bridge/src/server/localIdeBridgeService.ts (2)

71-75: restart() can resurrect a disposed service.

Setting this.disposed = false allows restart() to work even after dispose() has been called. If dispose() is meant to be a terminal operation (e.g., extension deactivation), this could lead to unexpected behavior where the service is resurrected.

Consider either:

  1. Checking if previously disposed and throwing/returning early
  2. Documenting that restart() intentionally re-enables a disposed service
💡 Option to prevent restart after dispose
 async restart(): Promise<void> {
+  if (this.disposed) {
+    throw new Error('Cannot restart a disposed service')
+  }
   await this.stop()
-  this.disposed = false
   await this.start()
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/vscode-ide-bridge/src/server/localIdeBridgeService.ts` around lines
71 - 75, The restart() method currently clears this.disposed and restarts the
service, which can resurrect a service after dispose(); update restart() to
first check this.disposed and either throw an error or return early if the
service was previously disposed, or explicitly document in the
LocalIdeBridgeService class that restart() intentionally re-enables a disposed
service; reference restart(), dispose(), and the this.disposed flag when making
the change so the guard is placed before calling stop() / start().

26-54: Consider adding explicit types to improve type safety.

The heavy use of any reduces type safety and IDE assistance:

  • socket: any → could use WebSocket from 'ws'
  • server: any → could use WebSocketServer from 'ws'
  • vscode: any → could use typeof import('vscode')

This would catch type errors at compile time and improve maintainability.

💡 Example type improvements
+import type { WebSocket, WebSocketServer } from 'ws'
+import type * as vscodeTypes from 'vscode'

 type ActiveConnection = {
-  socket: any
+  socket: WebSocket
   bridge: ReturnType<typeof createIdeBridgeServer>
   transport: ServerWebSocketTransport
 }

 export class LocalIdeBridgeService {
   private readonly diffController
   private readonly ideName = 'VS Code'
   private readonly runningInWindows = process.platform === 'win32'

-  private server: any | null = null
+  private server: WebSocketServer | null = null
   // ...

   constructor(
-    private readonly vscode: any,
-    private readonly outputChannel: any,
+    private readonly vscode: typeof vscodeTypes,
+    private readonly outputChannel: vscodeTypes.OutputChannel,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/vscode-ide-bridge/src/server/localIdeBridgeService.ts` around lines
26 - 54, The code uses many anys which reduces type safety—replace them with
concrete types: change ActiveConnection.socket from any to WebSocket (imported
from 'ws'), change LocalIdeBridgeService.server from any to WebSocketServer |
null (from 'ws'), and type the constructor parameter vscode as typeof
import('vscode'); also update ActiveConnection.bridge to the concrete return
type of createIdeBridgeServer if available (or a dedicated interface) and adjust
any usages of socket/server/ide bridge methods accordingly so TS catches
mismatches. Ensure you add the necessary imports (WebSocket, WebSocketServer)
and update the related property declarations (socket, server, vscode, and
ActiveConnection.bridge) throughout the class.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/superpowers/plans/2026-04-07-vscode-ide-bridge.md`:
- Line 15: The heading "### Task 1: 脚手架 VSCode 扩展包" should be promoted from H3
to H2 to fix the MD001 level-jump; replace the leading "###" with "##" for the
heading "Task 1: 脚手架 VSCode 扩展包" so task sections use H2 and restore proper
heading hierarchy.

In `@packages/vscode-ide-bridge/.vscodeignore`:
- Line 6: The .vscodeignore currently excludes the compiled runtime server files
via the pattern "dist/server/**", which prevents inclusion of runtime modules in
the VSIX and causes activation-time module-not-found errors; remove the
"dist/server/**" exclusion (or narrow it only to dev-only artifacts) so the
extension's runtime server code in dist/server is packaged, then rebuild the
VSIX and verify the extension activates without missing-module errors.

In `@packages/vscode-ide-bridge/src/server/diffController.ts`:
- Around line 297-317: The accept branch currently calls settleSession(...,
createTextResult('TAB_CLOSED'), true) which sets closeEditors=true and triggers
closeSessionEditors() that reverts dirty editors (dropping the accepted diff);
before settling on ACCEPT_LABEL, persist the right-hand diff editor instead of
immediately closing: locate the showInformationMessage handler in
diffController.ts and, when choice === ACCEPT_LABEL, ensure the matching dirty
TextEditor (the diff's right-hand buffer) is saved (e.g., call document.save()
on the editor for args.new_file_path or programmatically apply and save the
edit) and only after a successful save call settleSession(...,
createTextResult('TAB_CLOSED'), true); alternatively, if you intend a
non-committal action, change the closeEditors flag to false and use
createTextResult('DIFF_ACCEPTED') to reflect acceptance without saving.
- Around line 196-207: The current conditional in the diff-closing loop includes
a loose check using tab?.label === session.tabName which can match unrelated
files with the same basename; remove that label comparison and rely solely on
canonical identifiers (original/modified/uri strings and
tab?.input?.uri?.fsPath) to identify the session. Update the condition in the
loop inside diffController (the for-loops over vscode.window.tabGroups) to drop
the tab?.label === session.tabName clause so only original, modified,
uri.toString() or fsPath comparisons (session.leftUri, session.rightUri,
session.filePath) are used to find the matching tab.
- Around line 115-139: Replace the current visible-editors-based closure
detection in the visibilityDisposable listener with a tab-close-based listener:
remove or stop using vscode.window.onDidChangeVisibleTextEditors and instead
subscribe to vscode.window.tabGroups.onDidChangeTabs, inspect the event.tabs or
the event.change(s) to find tabs where change.kind === 'closed' (or
event.closed) and match those closed tabs to your session entries (use
session.leftUri/session.rightUri or session.tabName via the Tab input) and then
call settleSession(session.tabName, createTextResult('TAB_CLOSED'), false) for
matching sessions; keep session.hasBeenVisible logic if still needed but only
resolve sessions when a matching tab closure is detected to avoid treating mere
visibility changes as tab close.

In `@packages/vscode-ide-bridge/src/server/localIdeBridgeService.ts`:
- Around line 191-219: The handleConnection method can leave the socket open if
bridge.server.connect(transport) throws; wrap the connect call in try/catch so
that on any exception you close/cleanup the socket/transport and do not leak
resources, then rethrow or log the error. Specifically, after creating transport
= new ServerWebSocketTransport(socket) and before setting this.activeConnection,
add a try around await bridge.server.connect(transport) and in the catch/finally
ensure socket.close() (or equivalent) and any transport cleanup is performed if
connect fails; only set this.activeConnection (and call publishActiveSelection)
after a successful connect. Ensure references to handleConnection,
bridge.server.connect, transport, socket, and activeConnection are used to
locate the correct code.
- Around line 169-178: The connection handler currently calls void
this.handleConnection(socket) which discards the promise and can produce
unhandled rejections; change it to attach error handling to the returned promise
(e.g., this.handleConnection(socket).catch(err => {
this.outputChannel.appendLine(`[bridge] connection error: ${err?.message ||
err}`); try { socket.close(1011, 'internal error'); } catch (_) {} })) so any
rejection from handleConnection is logged and the socket is closed gracefully;
update the server.on('connection', ...) callback to use this pattern referencing
handleConnection, outputChannel, and socket.close.

In `@packages/vscode-ide-bridge/src/server/serverWebSocketTransport.ts`:
- Around line 60-66: The close() method currently calls cleanup() even when the
socket is in CLOSING state; change it so cleanup() is only invoked after the
socket is fully CLOSED. In practice, update close() to: if readyState ===
WS_OPEN call socket.close() and return; if readyState === WS_CLOSING simply
return (do not call cleanup or remove handlers); if readyState === WS_CLOSED
then call cleanup(). This preserves the handleClose listener so onclose runs
when the 'close' event fires (refer to close(), cleanup(),
this.socket.readyState, WS_OPEN, WS_CLOSING, handleClose and onclose).

In `@packages/vscode-ide-bridge/test/lockfile.test.ts`:
- Around line 28-30: The test in lockfile.test.ts hardcodes a Windows-style path
which breaks on CI; update the assertion to be OS-agnostic by constructing the
expected path with Node's path utilities and comparing normalized paths: build
the expected using path.join('D:', 'tmp', 'claude-config', 'ide', '4567.lock')
(or normalize the returned value with path.normalize/replace separators) and
assert equality against getLockfilePath(4567); reference getLockfilePath in the
test to locate where to change the assertion.

In `@packages/vscode-ide-bridge/test/package.test.ts`:
- Around line 1-2: Remove the node:fs and node:path imports and replace all
usages with Bun-native APIs: swap join(...) calls to Bun.path.join(...), replace
existsSync(...) with Bun.existsSync(...), and replace readFileSync(..., 'utf8')
with Bun.readFileSync(...) (or Bun.file(...) + .text() if you prefer a
File-based API); update any path construction to use Bun.path.join and ensure
encoding is passed to Bun.readFileSync when reading text. References: replace
the imports at the top and the functions join, existsSync, and readFileSync in
the test.

In `@packages/vscode-ide-bridge/test/packagePackaging.test.ts`:
- Around line 65-69: The test "keeps the packaged README free of local absolute
file links" is using a too-broad check expect(contents).not.toContain(':/')
which will flag valid URLs like https://; update the assertion to specifically
detect Markdown links with absolute POSIX or Windows file paths: remove the ':/`
check and instead assert that contents does not match a POSIX-absolute link
regex (e.g. /\]\(\/[^)]+\)/) and does not match a Windows-drive absolute link
regex (e.g. /\]\([A-Za-z]:[\\/][^)]+\)/); locate the test by its name and the
variables readmePath / contents and replace the second expect with these two
regex-based negative matches.

In `@packages/vscode-ide-bridge/test/packageWorkspaceWorkflow.test.ts`:
- Around line 1-2: Replace Node built-ins used at the top (existsSync,
readFileSync, join) with Bun-native APIs: remove imports of node:fs and
node:path, replace any existsSync(...) checks with a Bun.file(path) existence
check (e.g., try to access/read or check .size via Bun.file(path) in a
try/catch), replace readFileSync(...) with await Bun.file(path).text(), and
replace join(...) usage with simple string/path concatenation or URL-based
construction (e.g., new URL('./sub/dir', import.meta.url).pathname) consistent
with the test's runtime; update the test functions to be async where needed so
they can await Bun.file(...).text() and ensure error handling uses try/catch for
missing files instead of existsSync.

In `@packages/vscode-ide-bridge/test/serverWebSocketTransport.test.ts`:
- Around line 1-19: The test imports Node's EventEmitter and defines
FakeWebSocket; replace that with a Bun-native test double that implements only
the needed event APIs (on, off, emit) and the websocket shape used by
ServerWebSocketTransport: readyState property, send(data, callback?), close(),
sent array and closed flag; remove the import of 'node:events', implement the
lightweight FakeWebSocket class used in serverWebSocketTransport.test.ts so it
emits 'message', 'close', and 'error' correctly for tests against
ServerWebSocketTransport.

In `@packages/vscode-ide-bridge/test/vscodeWorkflow.test.ts`:
- Around line 1-2: The test imports Node built-ins readFileSync and join;
replace these by using Bun APIs and import.meta.dir: remove "readFileSync" and
"join" imports, change any readFileSync(...) calls to Bun.file(...) or
Bun.read(...)/Bun.readFile(...) as appropriate, and use import.meta.dir for path
resolution instead of join; update references in vscodeWorkflow.test.ts to call
Bun.file(path).text() or Bun.readFile(path) where the file contents are needed
and construct file paths with import.meta.dir + '/relative/path'.

---

Nitpick comments:
In `@packages/vscode-ide-bridge/src/server/localIdeBridgeService.ts`:
- Around line 71-75: The restart() method currently clears this.disposed and
restarts the service, which can resurrect a service after dispose(); update
restart() to first check this.disposed and either throw an error or return early
if the service was previously disposed, or explicitly document in the
LocalIdeBridgeService class that restart() intentionally re-enables a disposed
service; reference restart(), dispose(), and the this.disposed flag when making
the change so the guard is placed before calling stop() / start().
- Around line 26-54: The code uses many anys which reduces type safety—replace
them with concrete types: change ActiveConnection.socket from any to WebSocket
(imported from 'ws'), change LocalIdeBridgeService.server from any to
WebSocketServer | null (from 'ws'), and type the constructor parameter vscode as
typeof import('vscode'); also update ActiveConnection.bridge to the concrete
return type of createIdeBridgeServer if available (or a dedicated interface) and
adjust any usages of socket/server/ide bridge methods accordingly so TS catches
mismatches. Ensure you add the necessary imports (WebSocket, WebSocketServer)
and update the related property declarations (socket, server, vscode, and
ActiveConnection.bridge) throughout the class.

In `@packages/vscode-ide-bridge/test/bridgeServer.test.ts`:
- Around line 111-128: The test's notificationPromise (created with
client.setNotificationHandler and SelectionChangedSchema) can hang forever if no
notification arrives; wrap or replace it with a timeout-guarded promise (e.g.,
race the handler promise with a timer that rejects or fails after a short
timeout) and then await that guarded promise instead of notificationPromise;
ensure the timeout includes a descriptive error so bridge.notifySelectionChanged
failures yield deterministic test failures.

In `@packages/vscode-ide-bridge/test/selectionPublisher.test.ts`:
- Around line 13-16: The test in selectionPublisher.test.ts only asserts
params.selection?.start.line and params.selection?.end.character, leaving
start.character and end.line untested; update the assertions on the params
object to verify all four selection coordinates by adding checks for
params.selection?.start.character and params.selection?.end.line alongside the
existing expectations for start.line and end.character so the test validates
start.line, start.character, end.line, and end.character.

In `@packages/vscode-ide-bridge/test/serverWebSocketTransport.test.ts`:
- Around line 21-71: Add tests for ServerWebSocketTransport unhappy paths: 1)
start() when the FakeWebSocket.readyState is not 1 (e.g., 0 or 3) should reject
or throw — set socket.readyState accordingly before calling transport.start()
and assert it fails; 2) send() callback errors — make FakeWebSocket.send invoke
its callback with an Error and assert transport.send(...) rejects with that
error; 3) close() cleanup/listener detachment — call transport.start(),
transport.close(), then emit a 'message' on the FakeWebSocket and assert
transport.onmessage is not invoked and socket listeners count is cleaned up.
Reference ServerWebSocketTransport.start, ServerWebSocketTransport.send,
ServerWebSocketTransport.close, and the FakeWebSocket test helper when adding
these tests.

In `@packages/vscode-ide-bridge/test/workspaceInfo.test.ts`:
- Around line 37-40: The test only asserts snapshot.selection?.start.line;
update the assertion to verify the entire selection payload by replacing the
single-field check with an equality check against an expected selection object
(include start.line, start.character, end.line, end.character). Locate the
snapshot variable in workspaceInfo.test.ts and use a deep equality assertion
(e.g., expect(snapshot.selection).toEqual(expectedSelection)) or toMatchObject
with an explicit expectedSelection you construct with the correct numeric
values.

In `@packages/vscode-ide-bridge/tsconfig.json`:
- Around line 12-14: The tsconfig.json "types" array only includes "bun", which
causes missing type coverage for VS Code and Node APIs; update the "types" array
in tsconfig.json to include the official ambient packages such as "node" and
"vscode" (and keep "bun" if still needed) and ensure the corresponding packages
are installed (e.g., `@types/node` or the official vscode typings) so the project
uses correct official type definitions instead of ambient any stubs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e21ff602-977a-47a3-b06f-4d0205b272e4

📥 Commits

Reviewing files that changed from the base of the PR and between 7f69416 and 2248030.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (35)
  • .vscode/launch.json
  • .vscode/tasks.json
  • docs/superpowers/plans/2026-04-07-vscode-ide-bridge.md
  • docs/superpowers/specs/2026-04-07-vscode-ide-bridge-design.md
  • packages/vscode-ide-bridge/.vscode/launch.json
  • packages/vscode-ide-bridge/.vscode/tasks.json
  • packages/vscode-ide-bridge/.vscodeignore
  • packages/vscode-ide-bridge/LICENSE.txt
  • packages/vscode-ide-bridge/README.md
  • packages/vscode-ide-bridge/package.json
  • packages/vscode-ide-bridge/src/extension.ts
  • packages/vscode-ide-bridge/src/server/bridgeServer.ts
  • packages/vscode-ide-bridge/src/server/diffController.ts
  • packages/vscode-ide-bridge/src/server/localIdeBridgeService.ts
  • packages/vscode-ide-bridge/src/server/lockfile.ts
  • packages/vscode-ide-bridge/src/server/protocol.ts
  • packages/vscode-ide-bridge/src/server/randomToken.ts
  • packages/vscode-ide-bridge/src/server/selectionPublisher.ts
  • packages/vscode-ide-bridge/src/server/serverWebSocketTransport.ts
  • packages/vscode-ide-bridge/src/server/terminalEnvironment.ts
  • packages/vscode-ide-bridge/src/server/workspaceInfo.ts
  • packages/vscode-ide-bridge/src/vscode.d.ts
  • packages/vscode-ide-bridge/src/ws.d.ts
  • packages/vscode-ide-bridge/test/bridgeServer.test.ts
  • packages/vscode-ide-bridge/test/diffController.test.ts
  • packages/vscode-ide-bridge/test/lockfile.test.ts
  • packages/vscode-ide-bridge/test/package.test.ts
  • packages/vscode-ide-bridge/test/packagePackaging.test.ts
  • packages/vscode-ide-bridge/test/packageWorkspaceWorkflow.test.ts
  • packages/vscode-ide-bridge/test/selectionPublisher.test.ts
  • packages/vscode-ide-bridge/test/serverWebSocketTransport.test.ts
  • packages/vscode-ide-bridge/test/terminalEnvironment.test.ts
  • packages/vscode-ide-bridge/test/vscodeWorkflow.test.ts
  • packages/vscode-ide-bridge/test/workspaceInfo.test.ts
  • packages/vscode-ide-bridge/tsconfig.json


---

### Task 1: 脚手架 VSCode 扩展包
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix heading level jump at Line 15.

The heading hierarchy skips ## (H2) and jumps from H1 to H3, which triggers MD001. Use ## for task sections.

🧰 Tools
🪛 markdownlint-cli2 (0.22.0)

[warning] 15-15: Heading levels should only increment by one level at a time
Expected: h2; Actual: h3

(MD001, heading-increment)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/superpowers/plans/2026-04-07-vscode-ide-bridge.md` at line 15, The
heading "### Task 1: 脚手架 VSCode 扩展包" should be promoted from H3 to H2 to fix the
MD001 level-jump; replace the leading "###" with "##" for the heading "Task 1:
脚手架 VSCode 扩展包" so task sections use H2 and restore proper heading hierarchy.

.vscode/**
tsconfig.json
*.tsbuildinfo
dist/server/**
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Do not exclude runtime server modules from the VSIX.

Line 6 removes dist/server/**, but extension runtime code typically imports those compiled files from dist/. This can cause activation-time module not found failures in packaged installs.

💡 Proposed fix
 src/**
 test/**
 .vscode/**
 tsconfig.json
 *.tsbuildinfo
-dist/server/**
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/vscode-ide-bridge/.vscodeignore` at line 6, The .vscodeignore
currently excludes the compiled runtime server files via the pattern
"dist/server/**", which prevents inclusion of runtime modules in the VSIX and
causes activation-time module-not-found errors; remove the "dist/server/**"
exclusion (or narrow it only to dev-only artifacts) so the extension's runtime
server code in dist/server is packaged, then rebuild the VSIX and verify the
extension activates without missing-module errors.

Comment on lines +115 to +139
const visibilityDisposable = vscode.window.onDidChangeVisibleTextEditors(
(editors: any[]) => {
const visibleUris = new Set(
editors.map(editor => editor?.document?.uri?.toString?.()),
)

for (const session of sessions.values()) {
const leftVisible = visibleUris.has(session.leftUri.toString())
const rightVisible = visibleUris.has(session.rightUri.toString())

if (leftVisible || rightVisible) {
session.hasBeenVisible = true
continue
}

if (session.hasBeenVisible) {
void settleSession(
session.tabName,
createTextResult('TAB_CLOSED'),
false,
)
}
}
},
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

VS Code API: does window.visibleTextEditors/window.onDidChangeVisibleTextEditors include only editors currently visible on screen, or all open tabs? What event or API should be used to detect when a tab is actually closed?

💡 Result:

  • vscode.window.visibleTextEditors is only the editors currently visible in the UI (i.e., the editor panes that are showing content right now), not all open tabs. The API describes it as “The currently visible editors”. [1]
  • vscode.window.onDidChangeVisibleTextEditors fires when that visible editors array changes (e.g., you change editor layout, switch which editor is shown in a group, open/close an editor that affects what’s visible). It’s defined as firing when the “array of visible editors has changed”. [2]

If you need “all open tabs”

Use the Tabs API: vscode.window.tabGroups.all and each group’s tabs. [3]

Detect when a tab is actually closed

Use vscode.window.tabGroups.onDidChangeTabs and look at event.closed (the event explicitly includes “The tabs that have been closed”). [4]

Why workspace.onDidCloseTextDocument isn’t a reliable “tab closed” signal

workspace.onDidCloseTextDocument is about the document being disposed (or language id change), and the docs explicitly note there’s no guarantee it fires when an editor tab is closed. [2]

Sources
[1] VS Code API docs — window.visibleTextEditors (“currently visible editors”) https://code.visualstudio.com/api/references/vscode-api
[2] VS Code API docs — window.onDidChangeVisibleTextEditors; workspace.onDidCloseTextDocument notes https://code.visualstudio.com/api/references/vscode-api
[3] VS Code API docs — window.tabGroups / TabGroup.tabs https://code.visualstudio.com/api/references/vscode-api
[4] VS Code API docs — tabGroups.onDidChangeTabs / TabChangeEvent.closed https://code.visualstudio.com/api/references/vscode-api


Replace onDidChangeVisibleTextEditors with tabGroups.onDidChangeTabs to detect actual tab closure.

The code treats "not currently visible in the editor pane" as "tab closed," but onDidChangeVisibleTextEditors only fires when the set of visible editors changes (e.g., user switches to another tab in the same group). Both diff editors disappear from visibleTextEditors without being closed, so the session incorrectly resolves as TAB_CLOSED while the diff tab is still open, dropping it from tracking and preventing later save/reject actions.

Use vscode.window.tabGroups.onDidChangeTabs instead, checking event.closed to detect when tabs are actually closed (per VS Code API documentation).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/vscode-ide-bridge/src/server/diffController.ts` around lines 115 -
139, Replace the current visible-editors-based closure detection in the
visibilityDisposable listener with a tab-close-based listener: remove or stop
using vscode.window.onDidChangeVisibleTextEditors and instead subscribe to
vscode.window.tabGroups.onDidChangeTabs, inspect the event.tabs or the
event.change(s) to find tabs where change.kind === 'closed' (or event.closed)
and match those closed tabs to your session entries (use
session.leftUri/session.rightUri or session.tabName via the Tab input) and then
call settleSession(session.tabName, createTextResult('TAB_CLOSED'), false) for
matching sessions; keep session.hasBeenVisible logic if still needed but only
resolve sessions when a matching tab closure is detected to avoid treating mere
visibility changes as tab close.

Comment on lines +196 to +207
for (const group of vscode.window.tabGroups?.all ?? []) {
for (const tab of group.tabs ?? []) {
const original = tab?.input?.original?.toString?.()
const modified = tab?.input?.modified?.toString?.()
const uri = tab?.input?.uri?.toString?.()
if (
original === session.leftUri.toString() ||
modified === session.rightUri.toString() ||
uri === session.rightUri.toString() ||
tab?.input?.uri?.fsPath === session.filePath ||
tab?.label === session.tabName
) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Plain tab.label matching can close the wrong editor.

tab_name looks basename-like and user-controlled. Matching on tab?.label === session.tabName means closing sample.ts here can also close an unrelated sample.ts tab from another folder. Please restrict this to the session URIs / fsPath.

🧭 Minimal fix
         if (
           original === session.leftUri.toString() ||
           modified === session.rightUri.toString() ||
           uri === session.rightUri.toString() ||
-          tab?.input?.uri?.fsPath === session.filePath ||
-          tab?.label === session.tabName
+          tab?.input?.uri?.fsPath === session.filePath
         ) {
           matchedTabs.push(tab)
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for (const group of vscode.window.tabGroups?.all ?? []) {
for (const tab of group.tabs ?? []) {
const original = tab?.input?.original?.toString?.()
const modified = tab?.input?.modified?.toString?.()
const uri = tab?.input?.uri?.toString?.()
if (
original === session.leftUri.toString() ||
modified === session.rightUri.toString() ||
uri === session.rightUri.toString() ||
tab?.input?.uri?.fsPath === session.filePath ||
tab?.label === session.tabName
) {
for (const group of vscode.window.tabGroups?.all ?? []) {
for (const tab of group.tabs ?? []) {
const original = tab?.input?.original?.toString?.()
const modified = tab?.input?.modified?.toString?.()
const uri = tab?.input?.uri?.toString?.()
if (
original === session.leftUri.toString() ||
modified === session.rightUri.toString() ||
uri === session.rightUri.toString() ||
tab?.input?.uri?.fsPath === session.filePath
) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/vscode-ide-bridge/src/server/diffController.ts` around lines 196 -
207, The current conditional in the diff-closing loop includes a loose check
using tab?.label === session.tabName which can match unrelated files with the
same basename; remove that label comparison and rely solely on canonical
identifiers (original/modified/uri strings and tab?.input?.uri?.fsPath) to
identify the session. Update the condition in the loop inside diffController
(the for-loops over vscode.window.tabGroups) to drop the tab?.label ===
session.tabName clause so only original, modified, uri.toString() or fsPath
comparisons (session.leftUri, session.rightUri, session.filePath) are used to
find the matching tab.

Comment on lines +297 to +317
void vscode.window
.showInformationMessage(
`Claude Code 提议了对 ${args.new_file_path} 的修改`,
ACCEPT_LABEL,
REJECT_LABEL,
)
.then((choice: string | undefined) => {
if (choice === ACCEPT_LABEL) {
void settleSession(
args.tab_name,
createTextResult('TAB_CLOSED'),
true,
)
} else if (choice === REJECT_LABEL) {
void settleSession(
args.tab_name,
createTextResult('DIFF_REJECTED'),
true,
)
}
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

“接受” currently discards unsaved diff contents.

The accept branch settles with closeEditors = true. closeSessionEditors() then reverts any dirty matching editor at Lines 179-191 before closing it, so clicking 接受 without saving drops the proposed change instead of accepting it. Either save the right-hand document first or rename this action to something non-committal.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/vscode-ide-bridge/src/server/diffController.ts` around lines 297 -
317, The accept branch currently calls settleSession(...,
createTextResult('TAB_CLOSED'), true) which sets closeEditors=true and triggers
closeSessionEditors() that reverts dirty editors (dropping the accepted diff);
before settling on ACCEPT_LABEL, persist the right-hand diff editor instead of
immediately closing: locate the showInformationMessage handler in
diffController.ts and, when choice === ACCEPT_LABEL, ensure the matching dirty
TextEditor (the diff's right-hand buffer) is saved (e.g., call document.save()
on the editor for args.new_file_path or programmatically apply and save the
edit) and only after a successful save call settleSession(...,
createTextResult('TAB_CLOSED'), true); alternatively, if you intend a
non-committal action, change the closeEditors flag to false and use
createTextResult('DIFF_ACCEPTED') to reflect acceptance without saving.

Comment on lines +1 to +2
import { existsSync, readFileSync } from 'node:fs'
import { join } from 'node:path'
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify TS/JS files under vscode-ide-bridge do not use Node built-in imports.
rg -n --type=ts --type=tsx --type=js --type=jsx "from ['\"]node:"

Repository: claude-code-best/claude-code

Length of output: 99


🏁 Script executed:

#!/bin/bash
# Find the file and check its content
find . -path "*vscode-ide-bridge/test/package.test.ts" -type f

Repository: claude-code-best/claude-code

Length of output: 121


🏁 Script executed:

#!/bin/bash
# Check for node: imports with correct rg options
rg -n "from ['\"]node:" --type ts

Repository: claude-code-best/claude-code

Length of output: 2470


🏁 Script executed:

#!/bin/bash
# Read the actual test file to verify imports
cat -n packages/vscode-ide-bridge/test/package.test.ts

Repository: claude-code-best/claude-code

Length of output: 1431


Replace Node built-ins with Bun APIs in this test file.

Lines 1-2 import node:fs and node:path, which violate the repository's Bun-only TS/JS guideline. Additionally, join() on lines 5-6, existsSync() on line 10, and readFileSync() on lines 12-13 must be replaced with Bun equivalents.

🔁 Bun-native rewrite
-import { existsSync, readFileSync } from 'node:fs'
-import { join } from 'node:path'
 import { describe, expect, test } from 'bun:test'
 
-const packageRoot = join(import.meta.dir, '..')
-const packageJsonPath = join(packageRoot, 'package.json')
+const packageJsonPath = new URL('../package.json', import.meta.url)
 
 describe('vscode-ide-bridge package', () => {
-  test('declares a VSCode extension entry', () => {
-    expect(existsSync(packageJsonPath)).toBe(true)
+  test('declares a VSCode extension entry', async () => {
+    expect(await Bun.file(packageJsonPath).exists()).toBe(true)
 
-    const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')) as {
+    const packageJson = (await Bun.file(packageJsonPath).json()) as {
       main?: string
       engines?: { vscode?: string }
       activationEvents?: string[]
       dependencies?: Record<string, string>
       devDependencies?: Record<string, string>
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/vscode-ide-bridge/test/package.test.ts` around lines 1 - 2, Remove
the node:fs and node:path imports and replace all usages with Bun-native APIs:
swap join(...) calls to Bun.path.join(...), replace existsSync(...) with
Bun.existsSync(...), and replace readFileSync(..., 'utf8') with
Bun.readFileSync(...) (or Bun.file(...) + .text() if you prefer a File-based
API); update any path construction to use Bun.path.join and ensure encoding is
passed to Bun.readFileSync when reading text. References: replace the imports at
the top and the functions join, existsSync, and readFileSync in the test.

Comment on lines +65 to +69
test('keeps the packaged README free of local absolute file links', () => {
const contents = readFileSync(readmePath, 'utf8')

expect(contents).not.toContain('](/')
expect(contents).not.toContain(':/')
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Tighten README link check to avoid false failures on valid URLs.

not.toContain(':/') also matches valid https://... links, so this assertion can fail for legitimate README content.

Suggested fix
-    expect(contents).not.toContain(':/')
+    // Block local absolute paths only (POSIX + Windows), allow web URLs.
+    expect(contents).not.toMatch(/\]\(\/(?!\/)/)
+    expect(contents).not.toMatch(/\b[A-Za-z]:[\\/]/)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/vscode-ide-bridge/test/packagePackaging.test.ts` around lines 65 -
69, The test "keeps the packaged README free of local absolute file links" is
using a too-broad check expect(contents).not.toContain(':/') which will flag
valid URLs like https://; update the assertion to specifically detect Markdown
links with absolute POSIX or Windows file paths: remove the ':/` check and
instead assert that contents does not match a POSIX-absolute link regex (e.g.
/\]\(\/[^)]+\)/) and does not match a Windows-drive absolute link regex (e.g.
/\]\([A-Za-z]:[\\/][^)]+\)/); locate the test by its name and the variables
readmePath / contents and replace the second expect with these two regex-based
negative matches.

Comment on lines +1 to +2
import { existsSync, readFileSync } from 'node:fs'
import { join } from 'node:path'
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

git ls-files | head -20

Repository: claude-code-best/claude-code

Length of output: 460


🏁 Script executed:

find . -name "packageWorkspaceWorkflow.test.ts" -o -name "vscode-ide-bridge" -type d

Repository: claude-code-best/claude-code

Length of output: 167


🏁 Script executed:

fd "packageWorkspaceWorkflow" --type f

Repository: claude-code-best/claude-code

Length of output: 136


🏁 Script executed:

cat -n "packages/vscode-ide-bridge/test/packageWorkspaceWorkflow.test.ts" | head -50

Repository: claude-code-best/claude-code

Length of output: 1957


🏁 Script executed:

ls -la "packages/vscode-ide-bridge/"

Repository: claude-code-best/claude-code

Length of output: 655


🏁 Script executed:

cat -n "CLAUDE.md" | head -100

Repository: claude-code-best/claude-code

Length of output: 5768


🌐 Web query:

Bun fs path APIs built-in modules

💡 Result:

In Bun, filesystem + path work primarily through Node-compatible built-in modules, plus a couple of Bun-native file APIs:

  • node:fs / node:fs/promises: Bun’s Node fs implementation is “fully implemented” (and Bun recommends using it for directory ops like mkdir/readdir). [1][2]

    import { readdir, mkdir } from "node:fs/promises";
  • node:path (and usually path): Bun reports node:path as “fully implemented”. [1]

    import path from "node:path"; // or: import * as path from "node:path";
  • Bun-native file I/O (recommended for fast reads/writes):

    • Bun.file(pathOrUrlOrFd) for reading (Blob-like interface)
    • Bun.write(dest, data) for writing/copying
      [2]
  • Handy module utility for paths: import.meta.dir gives the absolute directory of the current file (useful with path.join). [3]

    const here = import.meta.dir;

Sources: Bun Node.js compatibility (built-ins) [1]; Bun File I/O docs [2]; Bun import.meta.dir guide [3].


🏁 Script executed:

cat "packages/vscode-ide-bridge/package.json"

Repository: claude-code-best/claude-code

Length of output: 1771


🏁 Script executed:

grep -n "vscode-ide-bridge" CLAUDE.md

Repository: claude-code-best/claude-code

Length of output: 54


Migrate this test off Node built-in module imports and use Bun-native APIs.

Lines 1-2 import node:fs and node:path; replace with Bun-native APIs:

  • existsSync() → check existence via Bun.file() or try-catch on read
  • readFileSync()await Bun.file().text()
  • join() from node:path → use string concatenation, URL API, or import a dedicated path utility

This aligns with the repository standard for Bun-only runtime environments.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/vscode-ide-bridge/test/packageWorkspaceWorkflow.test.ts` around
lines 1 - 2, Replace Node built-ins used at the top (existsSync, readFileSync,
join) with Bun-native APIs: remove imports of node:fs and node:path, replace any
existsSync(...) checks with a Bun.file(path) existence check (e.g., try to
access/read or check .size via Bun.file(path) in a try/catch), replace
readFileSync(...) with await Bun.file(path).text(), and replace join(...) usage
with simple string/path concatenation or URL-based construction (e.g., new
URL('./sub/dir', import.meta.url).pathname) consistent with the test's runtime;
update the test functions to be async where needed so they can await
Bun.file(...).text() and ensure error handling uses try/catch for missing files
instead of existsSync.

Comment on lines +1 to +19
import { EventEmitter } from 'node:events'
import { describe, expect, test } from 'bun:test'
import { ServerWebSocketTransport } from '../src/server/serverWebSocketTransport.js'

class FakeWebSocket extends EventEmitter {
readyState = 1
sent: string[] = []
closed = false

send(data: string, callback?: (error?: Error) => void) {
this.sent.push(data)
callback?.()
}

close() {
this.closed = true
this.emit('close')
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, check if the file exists and read its content
cat -n packages/vscode-ide-bridge/test/serverWebSocketTransport.test.ts | head -50

Repository: claude-code-best/claude-code

Length of output: 1584


🏁 Script executed:

# Check how FakeWebSocket is used in the test file
cat -n packages/vscode-ide-bridge/test/serverWebSocketTransport.test.ts

Repository: claude-code-best/claude-code

Length of output: 2203


🏁 Script executed:

# Check the ServerWebSocketTransport implementation to see what interface it expects
cat -n packages/vscode-ide-bridge/src/server/serverWebSocketTransport.ts | head -100

Repository: claude-code-best/claude-code

Length of output: 3312


Replace Node EventEmitter usage with a Bun-native test double.

Line 1 imports node:events, which violates the Bun-only runtime/import rule for TS/JS files in this repo. The custom EventEmitter implementation in the suggestion correctly covers all required methods (on, off, emit) for the 'message', 'close', and 'error' events expected by ServerWebSocketTransport.

Suggested refactor
-import { EventEmitter } from 'node:events'
 import { describe, expect, test } from 'bun:test'
 import { ServerWebSocketTransport } from '../src/server/serverWebSocketTransport.js'

-class FakeWebSocket extends EventEmitter {
+class FakeWebSocket {
   readyState = 1
   sent: string[] = []
   closed = false
+  private listeners: Record<string, Array<(...args: any[]) => void>> = {}

+  on(event: 'message' | 'close' | 'error', listener: (...args: any[]) => void) {
+    this.listeners[event] ??= []
+    this.listeners[event].push(listener)
+  }
+
+  off(event: 'message' | 'close' | 'error', listener: (...args: any[]) => void) {
+    this.listeners[event] = (this.listeners[event] ?? []).filter(l => l !== listener)
+  }
+
+  emit(event: 'message' | 'close' | 'error', payload?: unknown) {
+    for (const listener of this.listeners[event] ?? []) listener(payload)
+  }

   send(data: string, callback?: (error?: Error) => void) {
     this.sent.push(data)
     callback?.()
   }

   close() {
     this.closed = true
     this.emit('close')
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { EventEmitter } from 'node:events'
import { describe, expect, test } from 'bun:test'
import { ServerWebSocketTransport } from '../src/server/serverWebSocketTransport.js'
class FakeWebSocket extends EventEmitter {
readyState = 1
sent: string[] = []
closed = false
send(data: string, callback?: (error?: Error) => void) {
this.sent.push(data)
callback?.()
}
close() {
this.closed = true
this.emit('close')
}
}
import { describe, expect, test } from 'bun:test'
import { ServerWebSocketTransport } from '../src/server/serverWebSocketTransport.js'
class FakeWebSocket {
readyState = 1
sent: string[] = []
closed = false
private listeners: Record<string, Array<(...args: any[]) => void>> = {}
on(event: 'message' | 'close' | 'error', listener: (...args: any[]) => void) {
this.listeners[event] ??= []
this.listeners[event].push(listener)
}
off(event: 'message' | 'close' | 'error', listener: (...args: any[]) => void) {
this.listeners[event] = (this.listeners[event] ?? []).filter(l => l !== listener)
}
emit(event: 'message' | 'close' | 'error', payload?: unknown) {
for (const listener of this.listeners[event] ?? []) listener(payload)
}
send(data: string, callback?: (error?: Error) => void) {
this.sent.push(data)
callback?.()
}
close() {
this.closed = true
this.emit('close')
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/vscode-ide-bridge/test/serverWebSocketTransport.test.ts` around
lines 1 - 19, The test imports Node's EventEmitter and defines FakeWebSocket;
replace that with a Bun-native test double that implements only the needed event
APIs (on, off, emit) and the websocket shape used by ServerWebSocketTransport:
readyState property, send(data, callback?), close(), sent array and closed flag;
remove the import of 'node:events', implement the lightweight FakeWebSocket
class used in serverWebSocketTransport.test.ts so it emits 'message', 'close',
and 'error' correctly for tests against ServerWebSocketTransport.

Comment on lines +1 to +2
import { readFileSync } from 'node:fs'
import { join } from 'node:path'
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, check the file structure and see what packages/vscode-ide-bridge contains
fd -t f . packages/vscode-ide-bridge --max-results 20 | head -30

Repository: claude-code-best/claude-code

Length of output: 1090


🏁 Script executed:

# Check the actual content of the test file mentioned in the review
cat -n packages/vscode-ide-bridge/test/vscodeWorkflow.test.ts | head -50

Repository: claude-code-best/claude-code

Length of output: 1995


🏁 Script executed:

# Check if there's a package.json for vscode-ide-bridge that indicates its purpose
cat packages/vscode-ide-bridge/package.json 2>/dev/null || echo "Not found"

Repository: claude-code-best/claude-code

Length of output: 1771


🏁 Script executed:

# Check if there's documentation about vscode-ide-bridge
cat packages/vscode-ide-bridge/README.md 2>/dev/null || echo "Not found"

Repository: claude-code-best/claude-code

Length of output: 1643


🏁 Script executed:

# Look at the root package.json to understand the project structure
cat -n package.json | head -40

Repository: claude-code-best/claude-code

Length of output: 1287


🏁 Script executed:

# Check how other test files in vscode-ide-bridge handle imports
rg -n "import.*from.*['\"]node:" packages/vscode-ide-bridge/test/ -A 2

Repository: claude-code-best/claude-code

Length of output: 2397


🏁 Script executed:

# Check if any test files use Bun APIs for file operations
rg -n "Bun\." packages/vscode-ide-bridge/test/ | head -20

Repository: claude-code-best/claude-code

Length of output: 54


🏁 Script executed:

# Check CLAUDE.md or guidelines for vscode-ide-bridge mentions
find . -name "CLAUDE.md" -o -name "*.md" -path "*/guidelines*" | xargs grep -l "vscode" 2>/dev/null || true

Repository: claude-code-best/claude-code

Length of output: 54


🏁 Script executed:

# Check CLAUDE.md for exceptions to Bun API rule
cat CLAUDE.md 2>/dev/null | head -100

Repository: claude-code-best/claude-code

Length of output: 5068


🏁 Script executed:

# Check if there's any documented guidance on Node.js packages
rg "Node\.js" -A 3 -B 3 CLAUDE.md 2>/dev/null || true

Repository: claude-code-best/claude-code

Length of output: 1367


Use Bun APIs instead of Node.js built-ins in this test file.

Lines 1-2 import from node:fs and node:path. Replace with Bun equivalents: use Bun.file() or Bun.readFile() for file I/O and import.meta.dir for path resolution (already used on line 19).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/vscode-ide-bridge/test/vscodeWorkflow.test.ts` around lines 1 - 2,
The test imports Node built-ins readFileSync and join; replace these by using
Bun APIs and import.meta.dir: remove "readFileSync" and "join" imports, change
any readFileSync(...) calls to Bun.file(...) or Bun.read(...)/Bun.readFile(...)
as appropriate, and use import.meta.dir for path resolution instead of join;
update references in vscodeWorkflow.test.ts to call Bun.file(path).text() or
Bun.readFile(path) where the file contents are needed and construct file paths
with import.meta.dir + '/relative/path'.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant