CLI LSP setup
The Pullminder CLI ships with a built-in Language Server Protocol server. Once configured, your editor surfaces Pullminder rule violations as inline diagnostics — squiggly underlines, hover descriptions, and quick-fix actions — the same way it shows compiler errors and lint warnings.
Prerequisites:
- Install the Pullminder CLI and confirm
pullminder --versionworks. - Add a
.pullminder.ymlat the root of your repository. The LSP server reads the same configuration aspullminder check.
The server runs over stdio by default, which is what every editor on this page expects. If you need to run it over TCP (remote development, dev containers), see the TCP transport section at the bottom.
VS Code
Section titled “VS Code”Create a local extension — no marketplace publish needed. VS Code loads extensions from a user-level directory, so the snippet below works on any machine without a publisher ID.
1. Create the extension directory
Section titled “1. Create the extension directory”# macOS / Linuxmkdir -p ~/.vscode/extensions/pullminder-lsp
# Windows (PowerShell)New-Item -ItemType Directory -Force "$env:USERPROFILE\.vscode\extensions\pullminder-lsp"2. Add package.json
Section titled “2. Add package.json”Save this as ~/.vscode/extensions/pullminder-lsp/package.json:
{ "name": "pullminder-lsp", "displayName": "Pullminder LSP", "description": "Pullminder language server — inline diagnostics for security, compliance, and code quality rules.", "version": "0.1.0", "publisher": "local", "license": "MIT", "engines": { "vscode": "^1.75.0" }, "activationEvents": [ "onLanguage:yaml", "onLanguage:json", "onLanguage:markdown", "onLanguage:go", "onLanguage:typescript", "onLanguage:javascript", "onLanguage:python", "onLanguage:dockerfile", "onLanguage:terraform", "onLanguage:plaintext" ], "main": "./extension.js", "contributes": { "languages": [ { "id": "yaml", "extensions": [".yml", ".yaml"] } ] }}activationEvents lists the language IDs the server wakes up for. Add or remove entries to match the file types in your projects. VS Code language identifiers are listed in the VS Code docs.
3. Add extension.js
Section titled “3. Add extension.js”Save this as ~/.vscode/extensions/pullminder-lsp/extension.js:
const { LanguageClient, TransportKind } = require("vscode-languageclient/node");
/** @type {LanguageClient} */let client;
/** @param {import("vscode").ExtensionContext} context */function activate(context) { const serverOptions = { command: "pullminder", args: ["lsp"], transport: TransportKind.stdio, };
const clientOptions = { documentSelector: [ { scheme: "file", language: "yaml" }, { scheme: "file", language: "json" }, { scheme: "file", language: "markdown" }, { scheme: "file", language: "go" }, { scheme: "file", language: "typescript" }, { scheme: "file", language: "javascript" }, { scheme: "file", language: "python" }, { scheme: "file", language: "dockerfile" }, { scheme: "file", language: "terraform" }, { scheme: "file", language: "plaintext" }, ], };
client = new LanguageClient( "pullminder-lsp", "Pullminder LSP", serverOptions, clientOptions, );
context.subscriptions.push(client.start());}
function deactivate() { if (client) { return client.stop(); }}
module.exports = { activate, deactivate };4. Install the client dependency
Section titled “4. Install the client dependency”cd ~/.vscode/extensions/pullminder-lspnpm init -ynpm install vscode-languageclient@95. Reload VS Code
Section titled “5. Reload VS Code”Run the Developer: Reload Window command from the command palette (Ctrl+Shift+P / Cmd+Shift+P). Open any file type listed in documentSelector — Pullminder diagnostics appear inline.
Troubleshooting: Check the extension host log at View → Output and select Pullminder LSP from the dropdown. If pullminder: command not found appears, VS Code’s PATH doesn’t include the binary. Either start VS Code from a terminal where pullminder resolves, or set the absolute path in serverOptions.command (run which pullminder to find it).
Neovim
Section titled “Neovim”Two recipes: the built-in LSP client (Neovim 0.10+, no plugins) and nvim-lspconfig (broader compatibility).
Built-in LSP client (vim.lsp.start())
Section titled “Built-in LSP client (vim.lsp.start())”Add this to your init.lua (or a file sourced from it). The snippet creates an autocmd that starts the Pullminder server for common file types:
-- Requires Neovim 0.10+
local function start_pullminder(bufnr) vim.lsp.start({ name = "pullminder-lsp", cmd = { "pullminder", "lsp" }, root_dir = vim.fs.dirname(vim.fs.find(".git", { upward = true, path = vim.fn.getcwd() })[1]), filetypes = { "yaml", "json", "markdown", "go", "typescript", "javascript", "python", "dockerfile", "terraform", "sh", "bash", "zsh", }, }, { bufnr = bufnr })end
vim.api.nvim_create_autocmd("FileType", { pattern = { "yaml", "json", "markdown", "go", "typescript", "javascript", "python", "dockerfile", "terraform", "sh", "bash", "zsh", }, callback = function(args) -- avoid attaching twice when multiple buffers share a filetype local clients = vim.lsp.get_clients({ name = "pullminder-lsp", bufnr = args.buf }) if #clients == 0 then start_pullminder(args.buf) end end,})What root_dir does: The snippet walks upward from the file’s directory until it finds a .git folder, then uses that directory as the workspace root. The Pullminder LSP reads .pullminder.yml from this directory. If your repos live outside .git trees or you use a monorepo layout, replace root_dir with vim.fn.getcwd() to always use the current working directory.
nvim-lspconfig recipe
Section titled “nvim-lspconfig recipe”For users who already have nvim-lspconfig installed:
local lspconfig = require("lspconfig")
lspconfig.pullminder_lsp = { default_config = { name = "pullminder-lsp", cmd = { "pullminder", "lsp" }, filetypes = { "yaml", "json", "markdown", "go", "typescript", "javascript", "python", "dockerfile", "terraform", "sh", "bash", "zsh", }, },}
lspconfig.pullminder_lsp.setup({})Because Pullminder is not a built-in lspconfig server, the snippet registers it manually before calling .setup({}). This works with any lspconfig version.
Verify it works
Section titled “Verify it works”- Open a file that matches one of the configured
filetypesin a repository that has a.pullminder.yml. - Run
:LspInfo— you should seepullminder-lspin the attached client list. - Introduce a known-bad pattern (e.g. a hardcoded secret in a YAML file) — Pullminder diagnostics appear as virtual text and in the
:LspLog.
Add a pullminder language server entry in your languages.toml (typically ~/.config/helix/languages.toml):
[language-server.pullminder-lsp]command = "pullminder"args = ["lsp"]
# Register the server for every language you want Pullminder to scan.# Helix requires an explicit language entry per file type.[[language]]name = "yaml"language-servers = ["pullminder-lsp"]
[[language]]name = "json"language-servers = ["pullminder-lsp"]
[[language]]name = "markdown"language-servers = ["pullminder-lsp"]
[[language]]name = "go"language-servers = ["pullminder-lsp"]
[[language]]name = "typescript"language-servers = ["pullminder-lsp"]
[[language]]name = "javascript"language-servers = ["pullminder-lsp"]
[[language]]name = "python"language-servers = ["pullminder-lsp"]
[[language]]name = "dockerfile"language-servers = ["pullminder-lsp"]
[[language]]name = "hcl"language-servers = ["pullminder-lsp"]
[[language]]name = "bash"language-servers = ["pullminder-lsp"]Helix does not support wildcard or “all” language entries — repeat a [[language]] block for each file type you want scanned. The name values must match Helix language identifiers (run helix --health to list available languages).
Restart Helix after editing languages.toml. Open any configured file type to see diagnostics.
Zed uses JSON extension manifests to define language servers. Create ~/.local/share/zed/extensions/pullminder/extension.json (macOS/Linux) or %LOCALAPPDATA%\Zed\extensions\pullminder\extension.json (Windows):
{ "name": "Pullminder LSP", "version": "0.1.0", "language_servers": { "pullminder-lsp": { "name": "pullminder-lsp", "command": "pullminder", "args": ["lsp"], "languages": [ "YAML", "JSON", "Markdown", "Go", "TypeScript", "JavaScript", "Python", "Dockerfile", "HCL", "Shell Script" ] } }}Zed language identifiers are case-sensitive display names. A full list is available at Zed Languages.
This is a sketch — Zed’s extension system is still evolving. For the latest extension manifest format, check the Zed extension docs.
TCP transport
Section titled “TCP transport”The default stdio transport works for local development. If your editor runs in a separate container or remote machine, use TCP mode instead:
pullminder lsp --tcp --port 9099Then configure your editor to connect to localhost:9099 instead of launching the pullminder lsp command:
Neovim (vim.lsp.start):
vim.lsp.start({ name = "pullminder-lsp", cmd = { "pullminder", "lsp", "--tcp", "--port", "9099" }, -- ... same root_dir and filetypes as above})Helix (languages.toml):
[language-server.pullminder-lsp]command = "pullminder"args = ["lsp", "--tcp", "--port", "9099"]The --listen flag gives you finer control over the bind address:
pullminder lsp --listen 127.0.0.1:9099 # localhost onlypullminder lsp --listen :9099 # all interfacesOnly one editor can connect at a time over TCP. For multi-editor setups, start separate servers on different ports.
Next steps
Section titled “Next steps”- Configuration reference — every
.pullminder.ymloption, including file patterns, severity thresholds, and rule overrides. - Command reference — full list of
pullmindersubcommands and flags. - CI integration — run Pullminder in GitHub Actions and other pipelines.