v0.40.0 076e2bb

lsp.md RawHistoryFolder

LSP and linter support

zut exposes a built-in lsp tool for code intelligence and diagnostics. It communicates with stdio language servers through JSON-RPC and can also run configured command-line linters without a shell.

Configuration

zut loads JSON configuration from the following layers, in increasing precedence:

  • $ZUT_HOME/lsp.json
  • <cwd>/.pi/lsp.json
  • <cwd>/.omp/lsp.json
  • <cwd>/lsp.json, <cwd>/.lsp.json, and <cwd>/.zut/lsp.json

A file may put providers below servers, lspServers, or linters, use a flat provider map, or use the providers array:

{
  "servers": {
    "gopls": {
      "command": "gopls",
      "fileTypes": [".go"],
      "rootMarkers": ["go.mod", "go.work"]
    },
    "eslint": {
      "kind": "cli",
      "command": "eslint",
      "args": ["--format", "json"],
      "parser": "eslint-json",
      "mode": "files",
      "fileTypes": [".js", ".jsx", ".ts", ".tsx"],
      "rootMarkers": ["eslint.config.js", ".eslintrc"]
    }
  }
}

The providers array accepts built-in names and objects with id, kind, command, args, selectors, root markers, and nested lsp/cli options. Unknown provider metadata is ignored. Set autoDetect to false to keep only providers named or defined by the configuration file.

Server overrides merge onto zut's built-in definitions. Built-ins cover common Go, Rust, TypeScript/JavaScript, Python, C/C++, YAML, JSON, and shell servers, plus ESLint, golangci-lint, and Ruff CLI providers. A provider is selected when it is enabled and its file type/root markers match the workspace. Command lookup prefers project-local bin directories and then PATH; missing commands are reported by status or the request result. Set disabled to true to turn off a provider.

Useful server fields are:

Field Meaning
kind lsp (default) or cli
command, args Executable and arguments
fileTypes Extensions, basenames, or language IDs handled by the provider
languageId Optional LSP language ID sent on textDocument/didOpen; inferred when omitted
rootMarkers Files identifying a workspace root
isLinter LSP provider used for diagnostics, not navigation/refactors
parser CLI output parser: eslint-json, golangci-lint-json, ruff-json, sarif, or generic
mode CLI provider scope: files or workspace
settings Values returned for workspace/configuration requests
initializationOptions Values sent during LSP initialization
env Extra environment variables for the child process

CLI providers may use {files} as an argument or inside an argument. It is expanded to project-relative paths touched by the diagnostics request; an embedded placeholder produces one argument per path. CLI output is capped before parsing.

Tool actions

The model calls lsp with an action and optional arguments such as path, line, column, server, or query. Every action has an overall time budget: timeout_ms defaults to 30,000 milliseconds and is capped at 120,000 milliseconds.

  • diagnostics — merge LSP and CLI diagnostics for a file, glob, or the whole workspace. run_cli:false skips command-line linters.
  • definition, type_definition, implementation, references, hover — query a language server at a 1-based line and column.
  • symbols — return document symbols, or workspace symbols when query is supplied and path is omitted or *.
  • rename — request a WorkspaceEdit; applies it by default, or previews it with apply:false.
  • code_actions — list actions, or apply returned edits with apply:true.
  • status — show configured providers, running state, and diagnostic counts without starting servers.
  • reload — stop cached LSP processes and reload configuration.
  • capabilities — inspect initialize capabilities for one or all servers.
  • request — send a raw JSON-RPC method with JSON params.

--no-lsp disables the tool for one run. --tools lsp selects it when an explicit tool list is used.

Diagnostics and context limits

Diagnostics are deduplicated across providers using path, severity, code, start position, and normalized message. Results are ordered by severity and location. Repeated identical messages are represented as one occurrence group with example paths or lines. Cascades beneath missing-module errors are grouped as SECONDARY items so the root import problem remains visible without repeating a large error fan-out.

The model-facing summary has a hard diagnostic cap (50 by default, with a smaller eight-item summary used after writes and edits). Messages are whitespace-compacted and clipped before formatting. Slow write-time checks are bounded to a short two-second budget; a successful file write or edit is never turned into a failed mutation because an LSP or linter is unavailable.

Write and edit diagnostics are enabled by default when LSP is enabled. Applied LSP rename, code-action, and AST rewrite edits also run the same bounded checks for up to three modified files within one shared two-second budget. The persisted configuration keys are lsp_diagnostics_on_write and lsp_diagnostics_on_edit when an embedding needs to override the native file-tool defaults. Repeated post-write diagnostics are tracked per file by severity/code/message identity, so a line shift does not replay the same error. Clearing a file's diagnostics allows a later recurrence to be surfaced again.

Settings

/settings provides two independent switches, both enabled by default:

  • lsp in main session (lsp_enabled) controls the main agent and applies immediately to the live tool registry.
  • lsp in sub-agents (subagent_lsp_enabled) controls newly spawned subagent child processes.

The sub-agent setting does not grant access to a child whose profile or explicit tool list omits lsp. Both settings can also be controlled through $ZUT_HOME/config.json.

Safety and lifecycle

Language servers and linters run as local child processes. Project and global LSP configuration is executable local configuration: review configured commands, arguments, environment variables, and workspace-local binaries before using it. zut does not add a separate trust prompt or sandbox around those child processes; it does avoid shell expansion and keeps returned workspace edits inside the session workspace. The lsp tool never executes configured commands through a shell. Workspace edits returned by rename and code actions are restricted to the session workspace, reject resource operations, validate UTF-16 LSP positions, and write through a temporary file before replacement. Server-initiated workspace/applyEdit requests use the same checks.

LSP clients are cached per workspace and provider for the lifetime of the zut process. reload and process shutdown terminate cached children. Server stderr is kept out of model-facing tool results; diagnostics and protocol errors are returned through the established tool result channel.

source
1# LSP and linter support
2
3zut exposes a built-in `lsp` tool for code intelligence and diagnostics. It
4communicates with stdio language servers through JSON-RPC and can also run
5configured command-line linters without a shell.
6
7## Configuration
8
9zut loads JSON configuration from the following layers, in increasing
10precedence:
11
12- `$ZUT_HOME/lsp.json`
13- `<cwd>/.pi/lsp.json`
14- `<cwd>/.omp/lsp.json`
15- `<cwd>/lsp.json`, `<cwd>/.lsp.json`, and `<cwd>/.zut/lsp.json`
16
17A file may put providers below `servers`, `lspServers`, or `linters`, use a
18flat provider map, or use the `providers` array:
19
20```json
21{
22  "servers": {
23    "gopls": {
24      "command": "gopls",
25      "fileTypes": [".go"],
26      "rootMarkers": ["go.mod", "go.work"]
27    },
28    "eslint": {
29      "kind": "cli",
30      "command": "eslint",
31      "args": ["--format", "json"],
32      "parser": "eslint-json",
33      "mode": "files",
34      "fileTypes": [".js", ".jsx", ".ts", ".tsx"],
35      "rootMarkers": ["eslint.config.js", ".eslintrc"]
36    }
37  }
38}
39```
40
41The `providers` array accepts built-in names and objects with `id`, `kind`,
42`command`, `args`, selectors, root markers, and nested `lsp`/`cli` options.
43Unknown provider metadata is ignored. Set `autoDetect` to `false` to
44keep only providers named or defined by the configuration file.
45
46Server overrides merge onto zut's built-in definitions. Built-ins cover common
47Go, Rust, TypeScript/JavaScript, Python, C/C++, YAML, JSON, and shell servers,
48plus ESLint, golangci-lint, and Ruff CLI providers. A provider is selected
49when it is enabled and its file type/root markers match the workspace. Command
50lookup prefers project-local bin directories and then `PATH`; missing commands
51are reported by `status` or the request result. Set `disabled` to `true` to
52turn off a provider.
53
54Useful server fields are:
55
56| Field | Meaning |
57| --- | --- |
58| `kind` | `lsp` (default) or `cli` |
59| `command`, `args` | Executable and arguments |
60| `fileTypes` | Extensions, basenames, or language IDs handled by the provider |
61| `languageId` | Optional LSP language ID sent on `textDocument/didOpen`; inferred when omitted |
62| `rootMarkers` | Files identifying a workspace root |
63| `isLinter` | LSP provider used for diagnostics, not navigation/refactors |
64| `parser` | CLI output parser: `eslint-json`, `golangci-lint-json`, `ruff-json`, `sarif`, or generic |
65| `mode` | CLI provider scope: `files` or `workspace` |
66| `settings` | Values returned for `workspace/configuration` requests |
67| `initializationOptions` | Values sent during LSP initialization |
68| `env` | Extra environment variables for the child process |
69
70CLI providers may use `{files}` as an argument or inside an argument. It is
71expanded to project-relative paths touched by the diagnostics request; an
72embedded placeholder produces one argument per path. CLI output is capped
73before parsing.
74
75## Tool actions
76
77The model calls `lsp` with an `action` and optional arguments such as `path`,
78`line`, `column`, `server`, or `query`. Every action has an overall time budget:
79`timeout_ms` defaults to 30,000 milliseconds and is capped at 120,000 milliseconds.
80
81- `diagnostics` — merge LSP and CLI diagnostics for a file, glob, or the whole workspace. `run_cli:false` skips command-line linters.
82- `definition`, `type_definition`, `implementation`, `references`, `hover` — query a language server at a 1-based line and column.
83- `symbols` — return document symbols, or workspace symbols when `query` is supplied and `path` is omitted or `*`.
84- `rename` — request a `WorkspaceEdit`; applies it by default, or previews it with `apply:false`.
85- `code_actions` — list actions, or apply returned edits with `apply:true`.
86- `status` — show configured providers, running state, and diagnostic counts without starting servers.
87- `reload` — stop cached LSP processes and reload configuration.
88- `capabilities` — inspect initialize capabilities for one or all servers.
89- `request` — send a raw JSON-RPC method with JSON `params`.
90
91`--no-lsp` disables the tool for one run. `--tools lsp` selects it when an
92explicit tool list is used.
93
94## Diagnostics and context limits
95
96Diagnostics are deduplicated across providers using path, severity, code,
97start position, and normalized message. Results are ordered by severity and
98location. Repeated
99identical messages are represented as one occurrence group with example paths
100or lines. Cascades beneath missing-module errors are grouped as `SECONDARY`
101items so the root import problem remains visible without repeating a large
102error fan-out.
103
104The model-facing summary has a hard diagnostic cap (50 by default, with a
105smaller eight-item summary used after writes and edits). Messages are
106whitespace-compacted and clipped before formatting. Slow write-time checks are
107bounded to a short two-second budget; a successful file write or edit is never
108turned into a failed mutation because an LSP or linter is unavailable.
109
110Write and edit diagnostics are enabled by default when LSP is enabled. Applied
111LSP rename, code-action, and AST rewrite edits also run the same bounded checks
112for up to three modified files within one shared two-second budget. The persisted configuration keys are
113`lsp_diagnostics_on_write` and `lsp_diagnostics_on_edit` when an embedding
114needs to override the native file-tool defaults.
115Repeated post-write diagnostics are tracked per file by severity/code/message
116identity, so a line shift does not replay the same error. Clearing a file's
117diagnostics allows a later recurrence to be surfaced again.
118
119## Settings
120
121`/settings` provides two independent switches, both enabled by default:
122
123- **lsp in main session** (`lsp_enabled`) controls the main agent and applies
124  immediately to the live tool registry.
125- **lsp in sub-agents** (`subagent_lsp_enabled`) controls newly spawned subagent
126  child processes.
127
128The sub-agent setting does not grant access to a child whose profile or
129explicit tool list omits `lsp`. Both settings can also be controlled through
130`$ZUT_HOME/config.json`.
131
132## Safety and lifecycle
133
134Language servers and linters run as local child processes. Project and
135global LSP configuration is executable local configuration: review configured
136commands, arguments, environment variables, and workspace-local binaries before
137using it. zut does not add a separate trust prompt or sandbox around those child
138processes; it does avoid shell expansion and keeps returned workspace edits
139inside the session workspace. The `lsp` tool never executes configured commands
140through a shell. Workspace edits returned by
141rename and code actions are restricted to the session workspace, reject
142resource operations, validate UTF-16 LSP positions, and write through a
143temporary file before replacement. Server-initiated `workspace/applyEdit`
144requests use the same checks.
145
146LSP clients are cached per workspace and provider for the lifetime of the zut
147process. `reload` and process shutdown terminate cached children. Server
148stderr is kept out of model-facing tool results; diagnostics and protocol
149errors are returned through the established tool result channel.

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 476

↑↓ moveenter openesc close