Spec prefix: SWR-IDE-*
Status: Draft
IDE extensions using Shipwright must install with minimal friction while guaranteeing that the matching binaries are present and compatible. A user should normally install one VSIX, JetBrains plugin, Zed extension, or package-manager entry and get the right language server, MCP server, CLI, sidecar, and helper tools without manual setup.
For VS Code extensions with native binaries, the Microsoft platform-specific sample is authoritative: the installed extension package is the runtime source. The extension MUST execute the binary from the unpacked extension directory, not from PATH or a package-manager global install.
Every IDE extension MUST validate required binaries before reporting itself ready.
Startup sequence:
- Load
shipwright.jsonfrom the package. - Resolve every required component for the current platform.
- Run the component version check or protocol handshake check.
- Compare the reported version with the manifest
expectedVersion. - Start the LSP, MCP, sidecar, helper, or CLI integration only after all required checks pass.
- Surface a clear error if any required component is missing or mismatched.
The extension MUST NOT silently continue with a missing or incompatible binary.
VSIX and JetBrains plugin packages MUST bundle required runtime binaries by default.
Directory names under bin/ are vsceTarget values from schemas/platforms.json (e.g. darwin-arm64, win32-x64). The full VSIX packaging pipeline — build matrix, binary staging, vsce package --target, content verification, and Marketplace publish — is specified in vsix-platform-bundling.md.
VS Code MUST use the files unpacked by VS Code into the extension directory as-is. It MUST NOT copy bundled binaries to another runtime directory during installation or activation, and it MUST NOT create a per-user binary cache for bundled VSIX contents.
Normative layout:
bin/
darwin-arm64/
product-lsp
product-mcp
darwin-x64/
product-lsp
linux-x64/
product-lsp
linux-arm64/
product-lsp
win32-x64/
product-lsp.exe
win32-arm64/
product-lsp.exe
all/
tmc-server ← platform-agnostic (pure-Node) binary
shipwright.json
Zed extensions may download and cache release assets when marketplace packaging prevents bundling native binaries. In that case, the extension MUST validate the server version from LSP initialize before enabling product features. The canonical model — resolution order, integrity, and publishing — is [SWR-IDE-ZED].
Extension tests MUST prove the installed-package path, not a developer machine fallback.
Required test behavior:
- Stage required binaries inside the extension package or extension development directory before activation.
- Clear binary override environment variables.
- Remove known locally installed product binaries from PATH before tests.
- Fail before activation if a required product binary still resolves on PATH.
- Assert that the accepted resolver source for every bundled required component is
bundled.
Tests MUST NOT point an IDE extension at target/release, ~/.cargo/bin, Homebrew, Scoop, npm-global, dotnet tools, or any other external install. A missing or stale extension bundle must fail the test instead of being masked by a binary installed on the developer or CI runner machine.
Every IDE extension MUST provide a user setting for external binaries.
Supported forms:
- A binary directory containing all required components.
- A per-component absolute path.
The override is a resolver input, not a bypass. If a user points to a mismatched binary, the extension MUST stop startup and report the expected and found versions.
Suggested setting names:
| Host | Setting |
|---|---|
| VS Code | <product>.binaries.path for a directory, <product>.binaries.<componentId> for per-component paths |
| JetBrains | Settings → Tools → <Product> → Binary directory / component paths |
| Zed | LSP setting binary.path where supported |
For each component, resolvers SHOULD use this order:
- Explicit per-component path.
- Explicit binary directory.
- Explicit environment override, if the host documents one.
- Bundled package binary.
- Managed extension download/cache only where marketplace packaging prevents bundling.
VS Code native-binary extensions MUST stop at the bundled package binary unless the user explicitly
configured an absolute override. They MUST NOT inspect PATH, prepend or append to PATH, shell out to
which/where, or use Homebrew, Scoop, npm-global, Cargo, dotnet tools, or any other global package
manager as a normal startup source.
If the user explicitly configured a path and it mismatches, do not fall back. The user needs a precise error so they can fix the configured path.
Zed extensions are WebAssembly published through a human-reviewed PR to the zed-industries/extensions
registry; they cannot bundle a native binary (see [SWR-COMPAT-HOST-REQ] in the binary version contract).
Every Shipwright product's Zed extension MUST follow one model so the user experience matches the
bundled-VSIX flow: install the extension, get the matching binary, no manual steps.
Resolution order (in language_server_command, via shipwright-zed on the pure shipwright-host
resolver):
- Explicit user override — the Zed LSP
binary.pathsetting (or a documented env override). - A previously cached download in the extension's version-stamped work directory.
- A verified
github-releasedownload:latest_github_release(pinned repo, resolved tag — never/latestdrift) →download_filefor the current platform → SHA-256 digest verified againstSHA256SUMS→make_file_executable.
Then validate the running server's version from the LSP initialize serverInfo before enabling
product features; a mismatch stops startup with a precise error ([SWR-IDE-ERROR]). Zed cannot preflight
--version.
Forbidden as silent defaults. ~/.cargo/bin, a bare command name on PATH, and worktree.which
MUST NOT be the unconfigured startup source. A preinstalled/PATH binary is honored only when the user
explicitly opts into it (equivalent to an override), never as the default — the default is the verified
github-release download.
Anti-pattern — dead download (FAIL). A github-release download branch that is unreachable because
it is gated behind a condition that never occurs (e.g. only when $HOME is unset), leaving
cargo install / ~/.cargo/bin or another package-manager path as the de-facto default, is a release
blocker. The audit MUST confirm the download branch is actually reachable on a normal machine.
Integrity. The digest is verified in-extension; the cosign signature over SHA256SUMS is verified at
the release/CI boundary, not in the WASM sandbox ([SWR-SEC-CHECKSUM]).
Publishing. Reviewed PR to zed-industries/extensions: add the extension as a git submodule under
extensions/<id>, add an extensions.toml entry (submodule, version, optional path), and run
pnpm sort-extensions. The .wasm is built reproducibly in CI and is never committed.
Startup errors MUST include:
- Product and extension version.
- Component id.
- Expected version.
- Found version or
not found. - Source path or resolver source.
- Next action: reinstall extension, rebuild local binary, update package manager tool, or fix setting.
Example:
my-tool cannot start: my-tool-lsp version mismatch.
Expected 1.2.3 from the extension manifest.
Found 1.1.0 at /usr/local/bin/my-tool-lsp.
Reinstall the extension or update your configured binary path.
Any VS Code extension that bundles framework-dependent .NET sidecars (i.e. components with "language": "dotnet" and "bundlePath": "bin/all/..." in shipwright.json) MUST acquire the .NET runtime via Microsoft's .NET Install Tool extension — not by requiring the user to pre-install .NET, not by crashing with a "missing .NET" error, and not by hand-rolling a download.
-
Declare the dependency in
package.json:"extensionDependencies": ["ms-dotnettools.vscode-dotnet-runtime"]
VS Code installs this extension automatically when the user installs your extension. This is the same pattern used by C# Dev Kit, the C# extension, .NET MAUI, Unity, CMake, and Bicep.
-
Acquire the runtime on activation using the
dotnet.findPaththendotnet.acquirecommand sequence:// Step 1: check if a suitable runtime already exists const found = await vscode.commands.executeCommand('dotnet.findPath', { acquireContext: { version: '10.0', mode: 'runtime', requestingExtensionId: 'your-publisher.your-extension', }, versionSpecRequirement: 'greater_than_or_equal', }); // Step 2: acquire if not found — shows a non-interactive toast with spinner if (!found) { await vscode.window.withProgress( { location: vscode.ProgressLocation.Notification, title: 'MyProduct: Installing .NET runtime', cancellable: false }, () => vscode.commands.executeCommand('dotnet.acquire', { version: '10.0', mode: 'runtime', requestingExtensionId: 'your-publisher.your-extension', }) ); }
-
Set
DOTNET_ROOTin the environment passed to the Rust LSP host process so the .NET apphost can locate the runtime:const dotnetDir = path.dirname(dotnetPath); env['DOTNET_ROOT'] = dotnetDir;
The Rust sidecar spawn inherits this env and the framework-dependent apphost resolves the runtime correctly.
-
On acquisition failure — show a non-modal error notification with
[Open dot.net]and[Show log]buttons. Enter a degraded state. Do NOT block the editor or throw unhandled exceptions. Register a<product>.retryDotnetAcquisitioncommand so the user can retry without reloading the window.
- No
dotnet tool installfor sidecar distribution. Sidecars ship inside the VSIX atbin/all/. - No crashing activation when .NET is missing. Acquire it instead.
- No modal/blocking UI during acquisition. Progress notifications only.
- No hand-rolled runtime download logic. The
.NET Install Toolhandles this. - No
SelfContained=truepublish. Sidecars are framework-dependent; the runtime is acquired via the Install Tool.
This is the authoritative pattern from Microsoft. Downstream products MUST follow it exactly:
.NET Install Toolextension: https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.vscode-dotnet-runtime- C# Dev Kit
extensionDependenciesdeclaration is the canonical reference implementation.
Package managers can be used as repair flows, but not as hidden startup side effects.
Allowed repair flows:
- VS Code command:
<Product>: Install or Repair Binaries. - JetBrains notification action:
Install matching binaries. - CLI command:
shipwright repair.
The extension MUST ask before running Homebrew, Scoop, npm, cargo install, or dotnet tool commands.
Every host resolver library MUST provide:
- Platform id normalization.
- Manifest loading.
- Binary path resolution.
--versionexecution where host permits.- Protocol handshake fallback where host cannot execute preflight commands.
- Consistent diagnostic objects.
- Tests for env override, configured path, bundled binary, missing binary, mismatch, and rejection of PATH/global-install fallback during normal startup.