diff --git a/README.md b/README.md index aa52729..4bd3d38 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,8 @@ standalone Bifrost language server. It contains the independent Rust the extension and its release workflows. The extension remains published as `brokk/bifrost-vscode` on Open VSX. -The first extension release from this repository is `0.12.0`, following the -currently published `0.11.4`. Extension versions, standalone server versions, +The current published extension release from this repository is `0.12.0`. +Extension versions, standalone server versions, and compatible Bifrost engine versions are intentionally independent. Release qualification injects the server version and SHA-256 hashes into the VSIX. Extension releases use self-describing @@ -16,6 +16,16 @@ exactly match the committed manifest. See [RELEASING.md](RELEASING.md) for qualification and external setup. +## Documentation + +- [LSP Server](docs/lsp.md): server releases, startup, and compatibility. +- [VS Code LSP](docs/vscode.md): install and configure the VS Code extension. +- [RQL in VS Code](docs/rql-vscode.md): write queries and policies in VS Code. +- [Zed LSP](docs/zed.md): status of Zed language-server support and the + unpublished scaffold in [`editors/zed`](editors/zed/). +- [Neovim and Vim LSP](docs/neovim.md): configure Neovim and Vim clients. +- [Helix LSP](docs/helix.md): configure the Helix language-server client. + Open semantic packs and policies come from [BrokkAi/bifrost-packs](https://github.com/BrokkAi/bifrost-packs). The server reports the exact linked engine through `pack-engine-profile` and the LSP diff --git a/docs/assets/bifrost-vscode-query-playground.gif b/docs/assets/bifrost-vscode-query-playground.gif new file mode 100644 index 0000000..fb758c9 Binary files /dev/null and b/docs/assets/bifrost-vscode-query-playground.gif differ diff --git a/docs/assets/rql-vscode-query-results.png b/docs/assets/rql-vscode-query-results.png new file mode 100644 index 0000000..f29ebcb Binary files /dev/null and b/docs/assets/rql-vscode-query-results.png differ diff --git a/docs/helix.md b/docs/helix.md new file mode 100644 index 0000000..ba63001 --- /dev/null +++ b/docs/helix.md @@ -0,0 +1,133 @@ +# Helix LSP + +Helix can start the Bifrost language server, `bifrost-lsp`, through its built-in language-server configuration. No Bifrost-specific Helix plugin is needed. + +> [!CAUTION] +> **bifrost-lsp v0.1.1 is released** +> Bifrost built after release 0.12.0 does not serve LSP: `bifrost --lsp` exits with an error. The language server is the separate `bifrost-lsp` program, now available as v0.1.1. The configuration below starts it with `bifrost-lsp --root .`. See [LSP Server](./lsp.md) for details. + +Put this in `~/.config/helix/languages.toml`, start Helix from the workspace root, and open a supported source file: + +```toml +[language-server.bifrost] +command = "bifrost-lsp" +args = ["--root", "."] + +[[language]] +name = "c" +language-servers = ["bifrost"] + +[[language]] +name = "cpp" +language-servers = ["bifrost"] + +[[language]] +name = "c-sharp" +language-servers = ["bifrost"] + +[[language]] +name = "go" +language-servers = ["bifrost"] + +[[language]] +name = "java" +language-servers = ["bifrost"] + +[[language]] +name = "javascript" +language-servers = ["bifrost"] + +[[language]] +name = "jsx" +language-servers = ["bifrost"] + +[[language]] +name = "typescript" +language-servers = ["bifrost"] + +[[language]] +name = "tsx" +language-servers = ["bifrost"] + +[[language]] +name = "php" +language-servers = ["bifrost"] + +[[language]] +name = "python" +language-servers = ["bifrost"] + +[[language]] +name = "ruby" +language-servers = ["bifrost"] + +[[language]] +name = "rust" +language-servers = ["bifrost"] + +[[language]] +name = "scala" +language-servers = ["bifrost"] + +[[language]] +name = "kotlin" +language-servers = ["bifrost"] +``` + +This assumes `bifrost-lsp` is on `PATH`. If it is not, set `command` to the absolute path of the binary. + +Bifrost handles only the languages that list `bifrost` in `language-servers`. Remove the entries for languages you do not want it to handle. + +## Workspace Roots + +`--root` is the fallback workspace root. Helix sends the current working directory as `rootPath`, so `args = ["--root", "."]` works when you start Helix from the repository root: + +```bash +cd /path/to/project +hx src/main/java/example/App.java +``` + +If you start Helix outside the project, pass an absolute root instead: + +```toml +[language-server.bifrost] +command = "bifrost-lsp" +args = ["--root", "/path/to/project"] +``` + +Helix supports multiple language servers per language. If you want Bifrost to coexist with a language-specific server, include both names in that language's list: + +```toml +[[language]] +name = "java" +language-servers = ["bifrost", "jdtls"] +``` + +Running multiple servers can be useful when another server provides formatting or diagnostics, but it can also produce duplicate or competing navigation results. Keep the `language-servers` list to Bifrost alone if you want Bifrost to be the only server Helix uses for that language. + +## Confirm Bifrost Is Running + +Check that Helix can find the configured Bifrost server: + +```bash +hx --health java +``` + +The language server section should list `bifrost` with a check mark and the command path Helix will run. + +To capture startup and request logs, launch Helix with an explicit log file: + +```bash +hx -vvv --log /tmp/helix-bifrost.log src/main/java/example/App.java +``` + +Open a supported file and use Helix's normal LSP navigation commands, such as `gd` for go to definition or `gr` for references. The log should show `initialize`, `textDocument/definition`, or `textDocument/references` messages for `bifrost`. + +For request timing from the server, set the two variables that the VS Code extension passes to `bifrost-lsp`. `BIFROST_LSP_DEBUG = "1"` logs every request and notification. `BIFROST_LSP_SLOW_MS` logs requests that take at least that many milliseconds; `0` logs all of them. The `bifrost` CLI does not read these variables. See [LSP Server](./lsp.md). + +```toml +[language-server.bifrost] +command = "bifrost-lsp" +args = ["--root", "."] +environment = { BIFROST_LSP_DEBUG = "1", BIFROST_LSP_SLOW_MS = "0" } +``` diff --git a/docs/lsp.md b/docs/lsp.md new file mode 100644 index 0000000..24e13dc --- /dev/null +++ b/docs/lsp.md @@ -0,0 +1,158 @@ +# LSP Server + +The `bifrost` command no longer serves the Language Server Protocol (LSP). The +language server is a separate program named `bifrost-lsp`. It comes from the +[BrokkAi/bifrost-lsp](https://github.com/BrokkAi/bifrost-lsp) repository. + +> [!CAUTION] +> **bifrost-lsp v0.1.1 was released on 2026-10-08** +> The VS Code extension source is configured to download and install the +> standalone `bifrost-lsp` server in editor global storage. See +> [VS Code LSP](./vscode.md) for its managed-server behavior. + +## Which Bifrost Versions Serve LSP + +| Bifrost build | `bifrost --lsp` | +| --- | --- | +| Released 0.12.0 and earlier | Serves LSP over stdio. | +| Built from later source, including development builds that still report version 0.12.0 | Prints `the LSP server has moved out of this repository` and exits with status 1. | + +`bifrost --server lsp` behaves the same way as `bifrost --lsp`. The released +`bifrost-lsp` server gives you two working options: + +- In VS Code or Cursor, use the extension's managed `bifrost-lsp` v0.1.1 + download when using extension version 0.12.0. See [VS Code LSP](./vscode.md). +- In another editor, start the `bifrost-lsp` v0.1.1 binary with + `bifrost-lsp --root `. + +The Claude Code agent plugin registers an LSP server that runs `bifrost +--lsp`. It works only with the plugin's pinned release, Bifrost 0.12.0. See +[Claude Code](https://bifrost.brokk.ai/claude-code/). + +## Launch Command + +Editors start the server as a child process and talk to it over stdin and +stdout: + +```bash +bifrost-lsp --root +``` + +`--root` names the fallback workspace root. The VS Code extension passes the +first workspace folder. It also appends the strings from its +`bifrost.extraArgs` setting after `--root`. The extension runs +`bifrost-lsp --version` to check a downloaded binary before it starts the +server. + +The public `bifrost-lsp --help` output lists the supported command-line forms: + +```text +bifrost-lsp [--root PATH] [--lsp | --server lsp] + +Utilities: + pack-engine-profile Print the exact linked engine profile as JSON + --version Print the standalone server version +``` + +`--lsp` is accepted as a compatibility spelling; the standalone server does +not need it when launched by an editor. + +### Environment Variables + +The VS Code extension sets these variables when it starts `bifrost-lsp`. The +`bifrost` CLI does not read them. + +| Variable | Value the extension sets | Meaning | +| --- | --- | --- | +| `BIFROST_LSP_DEBUG` | `1` when `bifrost.debug` is on; otherwise the inherited value, or `0` | Log the start and end of every LSP request and notification. | +| `BIFROST_LSP_SLOW_MS` | The `bifrost.slowRequestMs` setting; default `2000` | Log requests and notifications that take at least this many milliseconds. | +| `RUST_BACKTRACE` | The inherited value, or `1` | Print a backtrace if the server panics. | + +The VS Code extension copies the server's stderr to **Output > Bifrost**. In +other editors, look for stderr in the editor's language-server log. + +### Initialization Options + +The VS Code extension sends this object as the LSP `initializationOptions`. It +sends the same object when the server requests the `bifrost` section through +`workspace/configuration`. + +| Field | Type | Meaning | +| --- | --- | --- | +| `roots` | array of absolute paths | Directories to index instead of the whole workspace. An empty array means the whole workspace. | +| `exclude` | array of absolute paths | Files or directories to leave out of indexing and lookups. | +| `formatterCommands` | array of formatter rules | External formatters to run for document formatting. See [VS Code LSP](./vscode.md) for the rule fields. | +| `unrecognizedSymbolDiagnostics` | boolean | Report symbols and members that Bifrost cannot resolve. Experimental. Default `false`. | + +The extension turns relative paths in its settings into absolute paths before +it sends them. When you configure another editor, send absolute paths too. + +## Versions and the Compatibility Check + +Three version numbers are independent of each other: + +| Version | Where it comes from | Current value in the bifrost-lsp source | +| --- | --- | --- | +| Extension version | The VS Code extension's `version` | `0.12.0` | +| Server version | The `bifrost-lsp` release tag, `v` | Preferred `0.1.1`, minimum `0.1.1` | +| Engine version | The Bifrost analysis engine built into `bifrost-lsp` | The extension accepts `>=0.13.0 <0.14.0` | + +When the server starts, it must identify itself in its `initialize` result: + +```json +{ + "capabilities": { + "experimental": { + "bifrost": { + "protocolVersion": 1, + "engineVersion": "0.13.0" + } + } + } +} +``` + +`protocolVersion` must be `1`. `engineVersion` must be a three-part version +inside the extension's engine range. If either value is missing, malformed, or +out of range, the extension stops the server and shows the error in its status +bar item and output channel. Other editors do not run this check. + +### Release Files + +The `bifrost-lsp` GitHub release provides one archive and one SHA-256 file for +each supported platform: + +| Platform | Archive | +| --- | --- | +| macOS (Intel and Apple silicon) | `bifrost-lsp-v-universal-apple-darwin.tar.gz` | +| Linux x64 | `bifrost-lsp-v-x86_64-unknown-linux-gnu.tar.gz` | +| Linux arm64 | `bifrost-lsp-v-aarch64-unknown-linux-gnu.tar.gz` | +| Windows x64 | `bifrost-lsp-v-x86_64-pc-windows-msvc.zip` | +| Windows arm64 | `bifrost-lsp-v-aarch64-pc-windows-msvc.zip` | + +The checksum file has the archive name plus `.sha256`. The archive contains a +top-level directory with the archive's base name and the `bifrost-lsp` +executable (`bifrost-lsp.exe` on Windows) inside it. + +## Editor Setup + +- [VS Code LSP](./vscode.md) +- [Cursor](https://bifrost.brokk.ai/cursor/) +- [Neovim and Vim LSP](./neovim.md) +- [Helix LSP](./helix.md) +- [Zed LSP](./zed.md) +- [OpenCode](https://bifrost.brokk.ai/opencode/) +- [RQL in VS Code](./rql-vscode.md) + +## Use MCP for Agents + +Agents do not need the language server. Bifrost's code intelligence for agents +runs over the Model Context Protocol (MCP) from the `bifrost` command: + +```bash +bifrost --root /path/to/project --mcp searchtools +``` + +See [Claude Code](https://bifrost.brokk.ai/claude-code/) and [OpenCode](https://bifrost.brokk.ai/opencode/) for host setup, +and [Capabilities](https://bifrost.brokk.ai/capabilities/) for what the tools answer. For terminal +checks and scripts, use [one-shot CLI tool mode](https://bifrost.brokk.ai/cli/). diff --git a/docs/neovim.md b/docs/neovim.md new file mode 100644 index 0000000..1a32f33 --- /dev/null +++ b/docs/neovim.md @@ -0,0 +1,268 @@ +# Neovim and Vim LSP + +Neovim can start the Bifrost language server through its built-in LSP client. Classic Vim can use a generic LSP client such as [vim-lsp](https://github.com/prabirshrestha/vim-lsp), [coc.nvim](https://github.com/neoclide/coc.nvim), or [ALE](https://github.com/dense-analysis/ale). No Bifrost-specific plugin is needed. Every configuration on this page starts the same stdio server with `bifrost-lsp --root `. + +> [!CAUTION] +> **bifrost-lsp v0.1.1 is released** +> Bifrost built after release 0.12.0 does not serve LSP: `bifrost --lsp` exits with an error. The language server is the separate `bifrost-lsp` program, now available as v0.1.1. The configurations below start it with `bifrost-lsp --root `. See [LSP Server](./lsp.md) for details. + +## Neovim 0.11 or Newer (Built-In Client) + +Use Neovim 0.11 or newer for `vim.lsp.config`. Put this in `~/.config/nvim/after/plugin/bifrost.lua`, start Neovim from the workspace root, and open a supported source file: + +```lua +local root = vim.fn.getcwd() + +vim.lsp.config('bifrost', { + cmd = { 'bifrost-lsp', '--root', root }, + filetypes = { + 'c', + 'cpp', + 'cs', + 'go', + 'java', + 'javascript', + 'javascriptreact', + 'kotlin', + 'php', + 'python', + 'ruby', + 'rust', + 'scala', + 'typescript', + 'typescriptreact', + }, + root_dir = root, +}) + +vim.lsp.enable('bifrost') +``` + +This assumes `bifrost-lsp` is on `PATH`. If it is not, replace `'bifrost-lsp'` in `cmd` with the absolute path to the binary. + +## Large Workspaces + +To limit indexing, send the same LSP initialization options that the VS Code extension sends. The extension sends absolute paths, so build them from the root. See [LSP Server](./lsp.md) for all four options. + +```lua +local root = vim.fn.getcwd() + +vim.lsp.config('bifrost', { + cmd = { 'bifrost-lsp', '--root', root }, + filetypes = { + 'c', + 'cpp', + 'cs', + 'go', + 'java', + 'javascript', + 'javascriptreact', + 'kotlin', + 'php', + 'python', + 'ruby', + 'rust', + 'scala', + 'typescript', + 'typescriptreact', + }, + root_dir = root, + init_options = { + roots = { root .. '/src', root .. '/tests' }, + exclude = { root .. '/target', root .. '/vendor/generated' }, + }, +}) + +vim.lsp.enable('bifrost') +``` + +Use `roots` when a repository has a small set of directories that should be indexed. Use `exclude` for generated output, dependency caches, or other directories that should not participate in workspace symbols or document-level lookups. + +## Dynamic Roots + +If you do not always start Neovim from the workspace root, use an autocmd and `vim.lsp.start` so the Bifrost command can include the root found for each buffer: + +```lua +local bifrost = 'bifrost-lsp' +local filetypes = { + c = true, + cpp = true, + cs = true, + go = true, + java = true, + javascript = true, + javascriptreact = true, + kotlin = true, + php = true, + python = true, + ruby = true, + rust = true, + scala = true, + typescript = true, + typescriptreact = true, +} + +vim.api.nvim_create_autocmd('FileType', { + callback = function(args) + if not filetypes[vim.bo[args.buf].filetype] then + return + end + + local root = vim.fs.root(args.buf, { '.git' }) or vim.fn.getcwd() + + vim.lsp.start({ + name = 'bifrost', + cmd = { bifrost, '--root', root }, + root_dir = root, + init_options = { + roots = { root .. '/src', root .. '/tests' }, + exclude = { root .. '/target', root .. '/vendor/generated' }, + }, + }, { bufnr = args.buf }) + end, +}) +``` + +## Older Neovim with nvim-lspconfig + +On Neovim releases before 0.11, `vim.lsp.config` does not exist. Use [nvim-lspconfig](https://github.com/neovim/nvim-lspconfig) instead. It ships no Bifrost entry, so define the server before calling `setup`: + +```lua +local lspconfig = require('lspconfig') +local configs = require('lspconfig.configs') + +local root = vim.fn.getcwd() + +if not configs.bifrost then + configs.bifrost = { + default_config = { + cmd = { 'bifrost-lsp', '--root', root }, + filetypes = { + 'c', + 'cpp', + 'cs', + 'go', + 'java', + 'javascript', + 'javascriptreact', + 'kotlin', + 'php', + 'python', + 'ruby', + 'rust', + 'scala', + 'typescript', + 'typescriptreact', + }, + root_dir = lspconfig.util.root_pattern('.git'), + init_options = { + roots = { root .. '/src', root .. '/tests' }, + exclude = { root .. '/target', root .. '/vendor/generated' }, + }, + }, + } +end + +lspconfig.bifrost.setup({}) +``` + +`root_dir` finds the Git root per buffer, but `cmd` and `init_options` are fixed when the server starts. Start Neovim from the workspace root, or replace `vim.fn.getcwd()` with your project's absolute path. The `init_options` block is optional; drop it when you want the whole workspace indexed. + +## Classic Vim with vim-lsp + +Install [vim-lsp](https://github.com/prabirshrestha/vim-lsp) with your Vim plugin manager, then register Bifrost. The helper below resolves the nearest Git root, including worktrees, and falls back to the current directory outside Git: + +```vim +function! s:bifrost_lsp_root() abort + let l:root = lsp#utils#find_nearest_parent_file_directory(lsp#utils#get_buffer_path(), ['.git', '.git/']) + return empty(l:root) ? getcwd() : l:root +endfunction + +if executable('bifrost-lsp') + augroup BifrostLsp + autocmd! + autocmd User lsp_setup call lsp#register_server({ + \ 'name': 'bifrost', + \ 'cmd': {server_info->['bifrost-lsp', '--root', s:bifrost_lsp_root()]}, + \ 'root_uri': {server_info->lsp#utils#path_to_uri(s:bifrost_lsp_root())}, + \ 'allowlist': ['c', 'cpp', 'cs', 'go', 'java', 'javascript', 'javascriptreact', 'kotlin', 'php', 'python', 'ruby', 'rust', 'scala', 'typescript', 'typescriptreact'], + \ }) + augroup END +endif +``` + +Check the server with `:LspStatus` after opening a supported file. To limit indexing, add an `'initialization_options'` entry with absolute `roots` and `exclude` paths, as in [Large Workspaces](#large-workspaces). + +## Classic Vim or Neovim with coc.nvim + +Install [coc.nvim](https://github.com/neoclide/coc.nvim), then run `:CocConfig` and add a `bifrost` language server entry: + +```json +{ + "languageserver": { + "bifrost": { + "command": "bifrost-lsp", + "args": ["--root", "/path/to/project"], + "filetypes": ["c", "cpp", "cs", "go", "java", "javascript", "javascriptreact", "kotlin", "php", "python", "ruby", "rust", "scala", "typescript", "typescriptreact"], + "rootPatterns": [".git"], + "initializationOptions": { + "roots": ["/path/to/project/src", "/path/to/project/tests"], + "exclude": ["/path/to/project/target", "/path/to/project/vendor/generated"] + } + } + } +} +``` + +Replace `/path/to/project` with your workspace root. `--root` is the fallback root; coc.nvim also sends the workspace folder at initialization. The `initializationOptions` block is optional. Verify with `:CocInfo` and look for a running `bifrost` service after opening a supported file. + +## Classic Vim or Neovim with ALE + +[ALE](https://github.com/dense-analysis/ale) can start `bifrost-lsp` as a stdio LSP linter. This configuration derives both ALE's process directory and Bifrost's fallback root from the current buffer, so it works across repositories without a project list: + +```vim +function! s:bifrost_root(buffer) abort + let l:start = expand('#' . a:buffer . ':p:h') + let l:gitdir = finddir('.git', l:start . ';') + if empty(l:gitdir) + let l:gitdir = findfile('.git', l:start . ';') + endif + return empty(l:gitdir) ? getcwd() : fnamemodify(l:gitdir . '/..', ':p') +endfunction + +let s:bifrost_filetypes = ['c', 'cpp', 'cs', 'go', 'java', 'javascript', 'javascriptreact', 'kotlin', 'php', 'python', 'ruby', 'rust', 'scala', 'typescript', 'typescriptreact'] + +for s:ft in s:bifrost_filetypes + call ale#linter#Define(s:ft, { + \ 'name': 'bifrost', + \ 'lsp': 'stdio', + \ 'executable': 'bifrost-lsp', + \ 'command': 'bifrost-lsp --root .', + \ 'cwd': function('s:bifrost_root'), + \ 'project_root': function('s:bifrost_root'), + \ }) +endfor + +let g:ale_linters = get(g:, 'ale_linters', {}) +for s:ft in s:bifrost_filetypes + let g:ale_linters[s:ft] = ['bifrost'] +endfor +``` + +`project_root` finds the Git root per buffer and ALE sends it as the LSP workspace root. The `cwd` setting makes the relative `--root .` fallback resolve to that same directory. For completion, also set `let g:ale_completion_enabled = 1`, `let g:ale_completion_timeout = 10`, and `set omnifunc=ale#completion#OmniFunc`; trigger it manually with `CTRL-X CTRL-O` if your Vim does not show automatic suggestions. The longer timeout lets the first workspace index finish before ALE gives up. Navigation uses ALE's LSP commands, such as `:ALEGoToDefinition`, `:ALEFindReferences`, and `:ALEHover`. + +## Confirm Bifrost Is Running + +Open a supported file and run: + +```vim +:lua =vim.lsp.get_clients({ bufnr = 0, name = 'bifrost' }) +``` + +The result should contain one client named `bifrost`. To confirm Neovim is asking Bifrost for navigation, place the cursor on a reference and run `vim.lsp.buf.definition()` or `vim.lsp.buf.references()`. + +For deeper debugging, inspect Neovim's LSP log path: + +```vim +:lua print(vim.lsp.log.get_filename()) +``` diff --git a/docs/rql-vscode.md b/docs/rql-vscode.md new file mode 100644 index 0000000..0039965 --- /dev/null +++ b/docs/rql-vscode.md @@ -0,0 +1,181 @@ +# RQL in VS Code + +Write and run Rune Query Language queries and policies in VS Code, and navigate the results. + +The Bifrost VS Code extension recognizes `.rql` files as **Bifrost RQL** and +`.rqlp` files as **Bifrost RQL Policy**. RQL, the +[Rune Query Language](https://bifrost.brokk.ai/rune-query-language/), is Bifrost's S-expression +language for structural `query_code` searches. + +> [!CAUTION] +> **Most RQL features need the language server** +> Bifrost built after release 0.12.0 does not serve LSP, and the new `bifrost-lsp` +> server v0.1.1 is released. Extension version 0.12.0 downloads and runs it. +> See [LSP Server](./lsp.md) and +> [VS Code LSP](./vscode.md). + +## What Needs the Server + +| Feature | Needs a running server | +| --- | --- | +| Language association, syntax highlighting, and file icons for `.rql`, `.rqlp`, and `.rune` | No | +| Diagnostics for the current `.rql` or `.rqlp` text, updated 300 ms after you stop typing | Yes | +| Hover for `.rql` and `.rqlp` | Yes | +| **Format Document** for `.rql` and saved `.rune` files | Yes | +| Run a query with the Play button | Yes | +| Run a policy with the Play button | Yes | +| **Suppress finding...** | Yes | +| **Bifrost: Show Rune IR** | Yes | + +Opening a query file does not start the server or wait for indexing. Start the +server, wait until indexing finishes, and then run the query. If the server is +not ready, the extension shows a warning and does not run anything. + +Write queries in RQL text. The extension does not run JSON query files. To get +the canonical JSON form of a query, use the REPL `:json` command. + +## Run a Query + +Open a `.rql` file and click the Play button in the editor title. The extension +sends the current editor text, including unsaved edits, to the server. + +For example, this query finds the `main` function in `src/bin/bifrost.rs`: + +```lisp +(result-detail full + (where "src/bin/bifrost.rs" + (function :name "main"))) +``` + +Results appear in the **Bifrost Query Results** view in the Explorer. If a +normal query returns nothing, the extension says so in a notification. + +For `(explain QUERY)` and `(profile QUERY)`, the extension writes the report to +**Output > Bifrost**. A profile report includes the complete JSON telemetry. +The profiled query's ordinary results also appear in the results view. Explain +mode only plans the query, so it shows no "no results" message. See +[Explain and Profile CodeQuery](https://bifrost.brokk.ai/code-query-explain-profile/). + +![An RQL query in VS Code, grouped query results in Explorer, and the selected Rust match.](./assets/rql-vscode-query-results.png) + +The screenshot shows a query against an older Bifrost source tree. + +### Query Scope + +The query searches every root that the running server has indexed: + +- all VS Code workspace folders by default; or +- the directories in `bifrost.roots`, without the paths in `bifrost.exclude`. + +The `.rql` file itself can be outside the workspace. Only the code that the +query searches is limited to the indexed roots. + +### Results View + +**Bifrost Query Results** groups results by file. Select a result to open its +file: + +- A result with a source range opens the file and selects that range. +- A file result opens the file at its first line. +- A control edge shows both endpoint IDs and ranges. +- A typestate witness expands into its ordered steps. Each step with a source + location opens that location. + +The view shows each result type that `query_code` returns, including +structural matches, declarations, procedures, program points, control edges, +typestate and taint findings, flow endpoints, occurrences, lexical scopes, +bindings, resolution candidates, and reference edges. Pipeline forms such as +`enclosing-decl`, `typestate`, `occurrences-in`, `binding-of`, `binding-uses`, +`candidates-of`, `edges-of`, and `file-of` return the same row types, so their +results can be opened from the same view. See +[CodeQuery reference](https://bifrost.brokk.ai/reference/code-query/) for the result types and their +fields. + +Labels and tooltips show the evidence that each row carries. Some rows need +care when you read them: + +- Typestate findings show certainty, protocol, proof and completeness status, + and witness counts. They do not show a severity. +- The one lexical scope per file that has no AST node is labeled as the + synthesized whole-file scope. +- A resolution candidate without a recorded precedence tier is labeled + `unattributed`, not as the weakest tier. +- When a resolution trace is `selection_only`, a missing rejection row tells + you nothing. The tooltip says so. +- On a reference edge, an `unknown` owner relation is inconclusive, not + external. A `declaration_site` row is editor navigation, not a runtime use. + +## RQL Policy Documents + +A `.rqlp` file holds a `(policy ...)` or `(endpoint ...)` document. It is not an +ordinary query. Its Play button runs **Bifrost: Run RQL Policy**, not the query +command, and its results never go to **Bifrost Query Results**. The CLI +`--query-file` option does not accept it. + +The extension highlights nested RQL only inside `(rql ...)`. Diagnostics check +only the current text. They do not read endpoint directories, catalogs, or +files that `(rql-file ...)` names. The server resolves those when it runs the +policy. + +To run a policy, open the `.rqlp` file and click Play, or run **Bifrost: Run +RQL Policy** from the Command Palette. The extension sends: + +- the current editor text, including unsaved edits; +- the file's URI; and +- today's date in UTC as the evaluation date. + +The extension does not name a suppression file. An `(endpoint ...)` document +cannot be run. A progress notification lets you cancel the run. Starting a new +run cancels the previous one. + +Results appear in **Bifrost Policy Results**. For each policy, the view shows +its completion state and its active findings. Select a finding, or a step of +its display path, to open the source. Suppressed findings do not appear in the +active list. They appear under **Suppression audit**, which also marks +decisions that are expired, orphaned, policy-hash drifted, or whose result was +omitted. If you edit the policy or the workspace while results are shown, the +view marks them stale. **Bifrost: Clear Policy Results** clears the view. + +The extension reads policy report schema version 5. It shows an error for any +other version. + +When the date or the suppression file must be fixed, run the policy from the +CLI with `--evaluation-date` and `--suppressions-file`. See +[Static-Analysis Policies](https://bifrost.brokk.ai/static-analysis-policies/). + +### Suppress a Finding + +Right-click a finding in **Bifrost Policy Results** and choose **Bifrost: +Suppress finding...**. The command is available only for a current, active +finding with a strong identity. Keep the policy file that produced the finding +open. + +1. Choose where to write the suppression: + - **Public**: `.bifrost/suppressions.json`, shared with the project. + - **Private**: `.bifrost/suppressions.private.json`, for decisions you do + not publish. + - **Local**: `.bifrost/suppressions.local.json`, for your checkout only. +2. Enter a reason. If you leave it blank, the reason is `unspecified`. If + `bifrost.requireSuppressionReason` is `true`, a blank reason is an error. +3. The server prepares the edit. The extension creates the file and its + directory if needed, applies the edit, and saves the file. +4. The extension runs the policy again. + +If the suppression file or a related source file changes while the server +prepares the edit, the extension does not write anything. Run the command +again. + +## Agents and MCP + +These Play actions use the editor's language server. They do not start an MCP +server, and they do not show that an agent can run a query or policy. + +- For agent queries, configure a query-capable MCP toolset and pass a saved + workspace `.rql` file to `query_code` through `query_file`. MCP does not + accept unsaved editor text or inline RQL. +- For agent policy runs, call the `run_policy` MCP tool with workspace `.rqlp` + paths and an evaluation date. + +See [MCP query and RQL availability](https://bifrost.brokk.ai/mcp/#query-and-rql-availability). + +For RQL syntax and the REPL, see [Rune Query Language](https://bifrost.brokk.ai/rune-query-language/). diff --git a/docs/vscode.md b/docs/vscode.md new file mode 100644 index 0000000..d804a46 --- /dev/null +++ b/docs/vscode.md @@ -0,0 +1,214 @@ +# VS Code LSP + +Install the Bifrost VS Code extension, and look up its launch modes, settings, commands, and views. + +> [!CAUTION] +> **The language server moved to bifrost-lsp** +> Bifrost built after release 0.12.0 does not serve LSP: `bifrost --lsp` exits with an +> error. The separate `bifrost-lsp` v0.1.1 server is released. Extension +> version 0.12.0 downloads and manages that server. See +> [LSP Server](./lsp.md) for the full status. + +## Install + +Install **Bifrost: Multi-Language LSP & MCP Server** (`brokk.bifrost-vscode`) +from the +[Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=Brokk.bifrost-vscode) +or [Open VSX](https://open-vsx.org/extension/brokk/bifrost-vscode). The +extension needs VS Code 1.90 or newer. + +The current published extension version is 0.12.0. It uses standalone server +version 0.1.1. The rest of this page describes version 0.12.0. + +Version 0.12.0 also checks the server's identity after it starts. The server +must report LSP protocol `1` and a Bifrost engine version from `0.13.0` up to, +but not including, `0.14.0`. Otherwise the extension stops the server. See +[LSP Server](./lsp.md) for the check and the release files. + +The extension describes its editor features as definitions, references, +symbols, hierarchy, rename, diagnostics, completion, and hover. It starts the +server for files in these VS Code languages: `c`, `cpp`, `csharp`, `go`, +`java`, `javascript`, `javascriptreact`, `kotlin`, `php`, `python`, `rust`, +`scala`, `typescript`, and `typescriptreact`. It also activates for the three +Bifrost languages below. + +## Launch Modes + +`bifrost.launchMode` chooses the server binary. + +| Mode | Binary the extension starts | +| --- | --- | +| `auto` (default) | A configured `bifrost.serverPath` other than the default command names `bifrost` and `bifrost-lsp`; otherwise the managed binary, if available or installed; otherwise the newer local `bifrost-lsp` development build; otherwise `bifrost-lsp` on `PATH`. | +| `bundled` | The managed binary only. If no compatible managed binary is installed, the extension asks to install it and fails if you decline. | +| `path` | The exact value of `bifrost.serverPath`. It does not fall back to a managed binary or local development build; a command name is still resolved on `PATH`. | + +A local development build is `target/debug/bifrost-lsp` or +`target/release/bifrost-lsp` at the repository root, two directories above the +extension's own folder. The extension uses the newer of the two. This applies +only when you run the extension from a source checkout. + +In version 0.12.0, the manifest default for `bifrost.serverPath` is +`bifrost-lsp`. `auto` mode also treats the legacy `bifrost` value as a default +command name and selects the standalone server. In `path` mode, the extension +uses the configured value verbatim, so set `bifrost.serverPath` to the absolute +path or `PATH` command name of a `bifrost-lsp` binary. + +### Managed Binary + +In `auto` and `bundled` mode, the extension manages its own server binary: + +- It supports macOS (x64 and arm64), Linux (x64 and arm64), and Windows (x64 + and arm64). +- It asks before it downloads. You can choose **Update**, **Not Now** (skip + this version), or **Don't Ask Again**. +- It downloads the archive and its `.sha256` file from the GitHub release. It + checks that both match the SHA-256 value built into the extension package. +- It stores the binary in VS Code's global storage for the extension, under + `binaries//-/`. +- It runs the binary with `--version` before it uses it. +- It reuses a cached binary from the extension's compatible version range + without asking. After a new install, it deletes cached versions outside that + range. + +## Settings + +| Setting | Type | Default | Meaning | +| --- | --- | --- | --- | +| `bifrost.launchMode` | `auto`, `bundled`, or `path` | `auto` | How to choose the server binary. See [Launch Modes](#launch-modes). | +| `bifrost.serverPath` | string | `bifrost-lsp` | Path to the server binary, or a command name to find on `PATH`. In `path` mode, use `bifrost-lsp`; `auto` mode also recognizes the legacy `bifrost` value as a compatibility sentinel. | +| `bifrost.debug` | boolean | `false` | Log every LSP request and notification. The extension passes this to the server as `BIFROST_LSP_DEBUG`. | +| `bifrost.slowRequestMs` | number, minimum `0` | `2000` | Log LSP requests and notifications that take at least this many milliseconds. The extension passes this to the server as `BIFROST_LSP_SLOW_MS`. | +| `bifrost.extraArgs` | array of strings | `[]` | Extra command-line arguments, added after `--root `. Blank entries are dropped. | +| `bifrost.roots` | array of strings | `[]` | Directories to index instead of the whole workspace. Relative paths are resolved against the workspace root. Empty means all workspace folders. | +| `bifrost.exclude` | array of strings | `[]` | Files or directories to leave out of indexing and LSP lookups. Relative paths are resolved against the workspace root. | +| `bifrost.formatterCommands` | array of formatter rules | `[]` | External formatter rules, in order. The extension reads this setting from user settings only. See [Formatter Rules](#formatter-rules). | +| `bifrost.unrecognizedSymbolDiagnostics` | boolean | `false` | Report symbols and members that Bifrost cannot resolve. Experimental; it can report false positives. | +| `bifrost.requireSuppressionReason` | boolean | `false` | Require a reason when you suppress a policy finding. Set it in workspace settings to make it a project rule. | + +Changes to `bifrost.launchMode`, `bifrost.serverPath`, `bifrost.debug`, +`bifrost.slowRequestMs`, and `bifrost.extraArgs` need a server restart. The +extension asks whether to restart now. + +Changes to `bifrost.roots`, `bifrost.exclude`, `bifrost.formatterCommands`, +and `bifrost.unrecognizedSymbolDiagnostics` do not cause a restart prompt. The +extension gives the current values to the server whenever the server asks for +the `bifrost` configuration section. If a change does not take effect, run +**Bifrost: Restart Language Server**. + +For a large repository, limit indexing before you start the server: + +```json +{ + "bifrost.roots": ["src", "tests"], + "bifrost.exclude": ["target", "vendor/generated"] +} +``` + +### Formatter Rules + +Each entry in `bifrost.formatterCommands` is an object with these fields. Only +`command` is required. Rules run without a shell. The formatter receives the +document text on stdin and must write the formatted document to stdout. + +| Field | Type | Meaning | +| --- | --- | --- | +| `include` | array of strings | Workspace-relative glob patterns that this rule applies to. | +| `exclude` | array of strings | Workspace-relative glob patterns that this rule must not apply to. | +| `language` | string | Optional Bifrost language filter, such as `rust`, `go`, `typescript`, `java`, `csharp`, `php`, or `ruby`. | +| `command` | string | Executable name or path. It runs directly and is not parsed by a shell. | +| `args` | array of strings | Command arguments. Supports the placeholders `{file}`, `{relativeFile}`, `{workspaceRoot}`, and `{language}`. | +| `cwd` | string | Working directory for the formatter. A relative path is resolved against the workspace root. Supports the same placeholders. | + +The extension ignores formatter rules in workspace or folder settings, because +a workspace could otherwise run any program. It writes a message to **Output > +Bifrost** when it ignores them. + +## Commands + +| Command | Command ID | What it does | +| --- | --- | --- | +| Bifrost: Start Language Server | `bifrost.startServer` | Start the server. | +| Bifrost: Stop Language Server | `bifrost.stopServer` | Stop the server. | +| Bifrost: Restart Language Server | `bifrost.restartServer` | Stop and start the server. | +| Bifrost: Show Output | `bifrost.showOutput` | Open **Output > Bifrost**, which shows the launch command, downloads, and server stderr. | +| Bifrost: Open MCP Setup | `bifrost.openMcpSetup` | Choose an MCP setup action. See [MCP Setup](#mcp-setup). | +| Bifrost: Copy MCP Config | `bifrost.copyMcpConfig` | Copy a generic `mcp.json` entry to the clipboard. | +| Bifrost: Run RQL Query | `bifrost.runRqlQuery` | Run the current `.rql` editor text. Available from the Play button in the editor title. | +| Bifrost: Run RQL Policy | `bifrost.runRqlPolicy` | Run the current `.rqlp` editor text. Available from the Play button and from the Command Palette in a `.rqlp` editor. | +| Bifrost: Clear Policy Results | `bifrost.clearRqlPolicyResults` | Clear **Bifrost Policy Results**. Available from that view's title bar. | +| Bifrost: Suppress finding... | `bifrost.suppressRqlPolicyFinding` | Write a suppression for a policy finding. Available from the context menu of a finding in **Bifrost Policy Results**. | +| Bifrost: Show Rune IR | `bifrost.showRuneIr` | Open the [Rune IR](https://bifrost.brokk.ai/rune-ir/) of the current source file, or of the selection, in a new untitled editor in **Bifrost Rune IR** mode. Available from the editor context menu and Command Palette. | +| Bifrost: Open RQL Query Result | `bifrost.openRqlQueryResult` | Open the source of a query result. Used by the results view. | +| Bifrost: Open RQL Policy Finding | `bifrost.openRqlPolicyFinding` | Open the source of a policy finding. Used by the results view. | +| Bifrost: Open RQL Policy Display Step | `bifrost.openRqlPolicyDisplayStep` | Open the source of one step of a finding's display path. Used by the results view. | + +The last three commands are hidden from the Command Palette. The status bar +item shows the server state. Click it to start or restart the server. + +## Views and Languages + +The extension adds two views to the Explorer: + +| View | Contents | +| --- | --- | +| **Bifrost Query Results** | Results of the last RQL query run, grouped by file. | +| **Bifrost Policy Results** | Completion state, findings, and suppression audit of RQL policy runs. | + +It also adds three languages: + +| Language | File extension | Purpose | +| --- | --- | --- | +| **Bifrost RQL** | `.rql` | [Rune Query Language](https://bifrost.brokk.ai/rune-query-language/) queries. | +| **Bifrost RQL Policy** | `.rqlp` | [Static-analysis policies](https://bifrost.brokk.ai/static-analysis-policies/) and policy endpoints. | +| **Bifrost Rune IR** | `.rune` | [Rune IR](https://bifrost.brokk.ai/rune-ir/) previews. | + +Each language has syntax highlighting and its own file icon. The icon shows +when your icon theme has no more specific icon. If another extension claims +`.rql`, choose **Bifrost RQL** with VS Code's language-mode picker. + +See [RQL in VS Code](./rql-vscode.md) for running queries and policies, and for +the features that need a running server. + +## Workspace .gitignore + +In version 0.12.0, the extension checks the workspace `.gitignore` for a line +that ignores all of `.bifrost`. If it finds one, it offers to replace that line +with `.bifrost/cache/`, so that project files under `.bifrost/` can be +committed. You can choose **Replace**, **Ask Again Later**, or **Don't Ask +Again**. + +## MCP Setup + +**Bifrost: Open MCP Setup** offers four actions: + +- Copy a generic `mcp.json` entry. +- Copy a Codex CLI command: `codex mcp add bifrost -- `. +- Copy a Claude Code command: `claude mcp add --scope user bifrost -- `. +- Open the Bifrost MCP documentation. + +The copied entry starts a separate MCP process for the current workspace: + +```json +{ + "mcpServers": { + "bifrost": { + "command": "/path/to/bifrost", + "args": ["--root", "/path/to/workspace", "--mcp", "searchtools"] + } + } +} +``` + +The commands run only when you choose them. The extension does not change +other programs' configuration files. + +The extension fills in `command` from `bifrost.mcpServerPath`, which defaults +to `bifrost` on `PATH` and names a separately installed Bifrost CLI. The +managed `bifrost-lsp` binary supports LSP only. See +[Install](https://bifrost.brokk.ai/install/) for ways to install the CLI, and +[MCP](https://bifrost.brokk.ai/mcp/) for the toolsets. + +The editor's language server and an agent's MCP server are separate +processes. Do not point an MCP host at the extension's language server. +Installing the extension does not give an agent the `query_code` tool. See +[MCP query and RQL availability](https://bifrost.brokk.ai/mcp/#query-and-rql-availability). diff --git a/docs/zed.md b/docs/zed.md new file mode 100644 index 0000000..1897d1c --- /dev/null +++ b/docs/zed.md @@ -0,0 +1,24 @@ +# Zed LSP + +> [!CAUTION] +> **No released Zed LSP setup yet** +> The `bifrost-lsp` v0.1.1 server is released, but the Zed extension remains +> unpublished. Bifrost built after release 0.12.0 does not serve LSP: +> `bifrost --lsp` exits with an error. See [LSP Server](./lsp.md). + +Bifrost has no published Zed extension. This repository contains an +unpublished development scaffold for one in [`editors/zed`](../editors/zed/). It starts the language server with: + +```bash +bifrost-lsp --root +``` + +For local development, put `bifrost-lsp` on `PATH` so the extension adapter +starts it with the worktree root. A configured `binary.path` is a direct Zed +host override: it bypasses the extension adapter, so the host does not add +`--root `. If you use a direct override, provide the fallback +root yourself through that setting's `binary.arguments`. +It is not a supported integration. + +To use Bifrost tools from Zed's agent, configure MCP instead. MCP does not need +the language server. See [Zed MCP](https://bifrost.brokk.ai/zed-mcp/). diff --git a/editors/zed/.gitignore b/editors/zed/.gitignore new file mode 100644 index 0000000..9a3bdc6 --- /dev/null +++ b/editors/zed/.gitignore @@ -0,0 +1,3 @@ +target/ +Cargo.lock +extension.wasm diff --git a/editors/zed/Cargo.toml b/editors/zed/Cargo.toml new file mode 100644 index 0000000..b633b6d --- /dev/null +++ b/editors/zed/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "bifrost_zed" +version = "0.0.1" +edition = "2021" +publish = false +license = "Apache-2.0" + +[lib] +path = "src/lib.rs" +crate-type = ["cdylib"] + +[dependencies] +zed_extension_api = "0.7.0" diff --git a/editors/zed/README.md b/editors/zed/README.md new file mode 100644 index 0000000..1fc426b --- /dev/null +++ b/editors/zed/README.md @@ -0,0 +1,53 @@ +# Bifrost for Zed + +Local Zed extension scaffold for the `bifrost-lsp` language server in this +repository. It is unpublished and is not a supported integration. See +[Zed LSP](../../docs/zed.md) for status. + +## Development + +For local testing, put a `bifrost-lsp` binary on `PATH` so the extension +adapter supplies the worktree root. + +Open Zed, run `zed: install dev extension`, and select `editors/zed`. + +For the first smoke test, configure the language to use only the Bifrost +adapter: + +```json +{ + "languages": { + "Rust": { + "language_servers": ["bifrost-rust", "!rust-analyzer"] + } + } +} +``` + +Zed treats `lsp.bifrost-rust.binary.path` as a direct language-server binary +override and starts it without the extension adapter's +`--root ` argument. Use the `PATH`-based setup above for the +first smoke test. If you use a direct override, configure its arguments +explicitly when the server needs a fallback root: + +```json +{ + "lsp": { + "bifrost-rust": { + "binary": { + "path": "/path/to/bifrost-lsp", + "arguments": ["--root", "/path/to/worktree"] + } + } + } +} +``` + +The extension starts the language server with: + +```bash +bifrost-lsp --root +``` + +When the extension adapter is used, any `lsp.bifrost.binary.arguments` values +are appended after `--root` and can be used for local debugging flags. diff --git a/editors/zed/extension.toml b/editors/zed/extension.toml new file mode 100644 index 0000000..e46844a --- /dev/null +++ b/editors/zed/extension.toml @@ -0,0 +1,51 @@ +id = "bifrost" +name = "Bifrost" +description = "Bifrost multi-language code intelligence." +version = "0.0.1" +schema_version = 1 +authors = ["Brokk "] +repository = "https://github.com/BrokkAi/bifrost-lsp" + +[language_servers.bifrost-rust] +name = "Bifrost" +language = "Rust" + +[language_servers.bifrost-python] +name = "Bifrost" +language = "Python" + +[language_servers.bifrost-go] +name = "Bifrost" +language = "Go" + +[language_servers.bifrost-javascript] +name = "Bifrost" +language = "JavaScript" + +[language_servers.bifrost-typescript] +name = "Bifrost" +language = "TypeScript" + +[language_servers.bifrost-ruby] +name = "Bifrost" +language = "Ruby" + +[language_servers.bifrost-php] +name = "Bifrost" +language = "PHP" + +[language_servers.bifrost-csharp] +name = "Bifrost" +language = "C#" + +[language_servers.bifrost-scala] +name = "Bifrost" +language = "Scala" + +[language_servers.bifrost-kotlin] +name = "Bifrost" +language = "Kotlin" + +[language_servers.bifrost-java] +name = "Bifrost" +language = "Java" diff --git a/editors/zed/src/lib.rs b/editors/zed/src/lib.rs new file mode 100644 index 0000000..a42aec9 --- /dev/null +++ b/editors/zed/src/lib.rs @@ -0,0 +1,49 @@ +use zed_extension_api::{self as zed, settings::LspSettings, Result}; + +struct BifrostExtension; + +impl BifrostExtension { + fn server_command( + &self, + language_server_id: &zed::LanguageServerId, + worktree: &zed::Worktree, + ) -> Result { + let settings = LspSettings::for_worktree(language_server_id.as_ref(), worktree) + .or_else(|_| LspSettings::for_worktree("bifrost", worktree)) + .ok(); + let binary = settings + .as_ref() + .and_then(|settings| settings.binary.as_ref()); + let command = binary + .and_then(|binary| binary.path.clone()) + .or_else(|| worktree.which("bifrost-lsp")) + .unwrap_or_else(|| "bifrost-lsp".to_string()); + + let mut args = vec!["--root".to_string(), worktree.root_path()]; + if let Some(extra_args) = binary.and_then(|binary| binary.arguments.clone()) { + args.extend(extra_args); + } + + Ok(zed::Command { + command, + args, + env: worktree.shell_env(), + }) + } +} + +impl zed::Extension for BifrostExtension { + fn new() -> Self { + Self + } + + fn language_server_command( + &mut self, + language_server_id: &zed::LanguageServerId, + worktree: &zed::Worktree, + ) -> Result { + self.server_command(language_server_id, worktree) + } +} + +zed::register_extension!(BifrostExtension);