# CLAUDE.md: Neovim plugin (Lua)

## Commands
- Tests run headless: use the repo's harness (`make test`, `busted`, `nvim -l tests/minimal_init.lua`, plenary or mini.test). Never require a user config.
- Format with `stylua .`; lint with `selene` or `luacheck` if configured.
- Respect the minimum Neovim version in the README; check `vim.fn.has('nvim-0.x')` before using newer APIs (`vim.uv` vs `vim.loop`, `vim.system`, `vim.iter`).

## Layout
- `lua/<plugin>/init.lua` exposes `M.setup(opts)` and the public API; internals in `lua/<plugin>/*.lua`. `plugin/<plugin>.lua` only defines user commands and must load fast (no heavy `require` at startup).
- `doc/<plugin>.txt` vimdoc is updated with every option or command change.
- `lua/<plugin>/health.lua` implements `check()` using `vim.health.start/ok/warn/error` for dependencies and config problems.

## Code
- Merge config with `vim.tbl_deep_extend('force', defaults, opts or {})` and validate with `vim.validate`. Work with sensible defaults when `setup()` is never called, if the README says so.
- Autocommands always in a named group: `local group = vim.api.nvim_create_augroup('<plugin>', { clear = true })`. Keymaps via `vim.keymap.set` with `desc`; don't set global mappings by default—offer `<Plug>` mappings or options.
- No global variables or polluting `_G`. Module-local state only; buffer-local state in `vim.b[bufnr]` or tables keyed by buffer number, cleared on `BufWipeout`.
- Use `vim.api.nvim_*` functions over `vim.cmd` strings where an API exists. Always pass explicit buffer/window handles; don't assume the current one inside callbacks.
- Async work with `vim.system` or `vim.uv`; schedule UI updates back with `vim.schedule`. Never block the editor with synchronous shell calls.
- Report to users with `vim.notify(msg, vim.log.levels.WARN, { title = '<plugin>' })`; errors in callbacks are caught so one failure doesn't spam every keystroke.

## Done when
Tests pass headless, stylua clean, and `:checkhealth <plugin>` and vimdoc reflect any new options.
