nvim-plugin-maker
Skill2 starsChanged 10 days agoMITView on GitHub
Build, test, and release Neovim plugins in pure Lua. Use when creating or extending a Neovim plugin, setting up headless plenary tests or plugin CI, writing a health check or vimdoc, or writing vim.uv async code, autocmds, extmarks, or keymaps inside a plugin. Covers the plugin layout, the never-crash-Neovim rules for callbacks and fast events, and an API reference whose claims scripts/verify.lua checks against the running Neovim. Not for editing a personal init.lua.
In this repository5 other files it gives its agents.
- AGENTS.mdAGENTS.md
- claim-checkSkill
- pre-build-discoverySkill
- production-ops-disciplineSkill
- repo-state-authoritySkill
The file
We split this skill into the rules it gives an agent
7 rules. About 0 are common, 2 are distinctive, and 5 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
5
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 19
Not for: tuning a personal Neovim config.
- Very specific: line 23
Existing plugin?: Read what is there first (lua/, plugin/, tests/, Makefile, CI).
- Very specific: line 24
New plugin: create the layout below and the test harness (references/testing.md) before feature code.
- Very specific: line 69
When you support a version where an API was renamed, use the old name until you drop that version, and note the rename in a comment.
- Distinctive: line 83
Read the one the task needs, not all of them.
- Very specific: line 93
Old API names from training data: Check references/api.md before writing buffer =, vim.diff, vim.uri_from_fname, or ephemeral = true.
- Very specific: line 101
After a Neovim upgrade, or before trusting a reference claim: nvim --clean --headless -l scripts/verify.lua from this skill's folder.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Neovim Plugin Maker
Pure Lua plugins: correct layout, a headless test harness, callbacks that cannot take the editor down, and a clean release. No Node, no Rust, no runtime dependencies unless the user asks.
The references are the main value here. Neovim renames APIs between releases (0.12 renamed the buffer key to buf, vim.diff to vim.text.diff), and a model trained before a release writes the old names. Every version-sensitive claim in references/ carries the version it holds for, and scripts/verify.lua checks them on the Neovim you run.
When to Use
- Creating a plugin, or adding a feature, test, health check, vimdoc, or CI to one.
- Writing
vim.uvtimers, sockets, file watchers,vim.systemcallbacks, autocmds, extmarks, or buffer-local keymaps in plugin code. make testhangs, specs pass together but fail alone, orE5560shows up.
Not for: tuning a personal Neovim config. The layout and release rules below do not apply there.
Workflow
- Existing plugin? Read what is there first (
lua/,plugin/,tests/,Makefile, CI). Do not rescaffold or restyle working code. Do one vertical slice: the smallest requested change, its spec,make test, stop. - New plugin: create the layout below and the test harness (
references/testing.md) before feature code. Done whenmake testruns one passing spec. - Write tests where behavior can regress: async, IO, config merge, commands, anything parsing input. Skip them for one-line glue and for docs-only changes.
- Wire the surface:
setup(), user commands, health check (references/architecture.md). - Release: README, vimdoc, CI, tag (
references/release.md), only when the user is releasing.
Match ceremony to the change. A README fix does not need a CI run; a tested fix does not need a second full verification pass.
Layout
myplugin.nvim/
├── lua/myplugin/
│ ├── init.lua -- setup(), public API
│ ├── config.lua -- defaults, merge, get()
│ ├── health.lua -- :checkhealth myplugin
│ └── <feature>.lua -- one responsibility per file
├── plugin/myplugin.lua -- command registration only
├── doc/myplugin.txt -- vimdoc
├── tests/
│ ├── minimal_init.lua
│ └── <feature>_spec.lua
├── Makefile -- make test
├── stylua.toml
└── .luacheckrcplugin/*.luaruns at startup for every user. It only registers commands and autocmds whose callbacksrequire()the module on first use. Real work at startup is a bug users will file.- The plugin must work when
setup()is never called. Read options through aget()that falls back to defaults; lazy.nvim users often skipsetup(). - One file, one responsibility, small explicit interfaces between them. That is also what makes them testable headless.
P0: never crash or wedge Neovim
These are the failures that get a plugin uninstalled. scripts/verify.lua checks the fast-event and handle-close rules.
- Fast events cannot touch the editor.
vim.uvcallbacks (timers, sockets, fs events) andvim.systemon_exitcallbacks run in a fast event. Callingvim.api, most ofvim.fn, or reading buffers there fails withE5560: ... must not be called in a fast event context. Wrap the editor work invim.schedule()or the callback invim.schedule_wrap().jobstartcallbacks are not fast.vim.in_fast_event()tells you where you are. - Catch errors in every callback Neovim or libuv calls. An uncaught error there does not kill Neovim, but the user gets a stack trace, and the code after the error never runs, so the handle you meant to close stays open. Wrap the body in
pcall, notify once, log the detail. - Close what you open. Timers (
timer:stop(); timer:close()), sockets, fs watchers, temp files, autocmd groups, namespaces. A leaked handle hangsmake testand plugin reloads. Keep every handle reachable from one module table and release them all instop(). - Notify once, log the rest.
vim.notify_oncefor user-facing failures; detail goes to a log the user can open. Repeated notifications train users to uninstall. - Secrets and sockets: tokens from
vim.uv.random(), nevermath.random. Never log them. Bind listeners to127.0.0.1, never0.0.0.0. Cap the read buffer for any peer you do not control. - Paths:
vim.fn.fnameescape()before putting a path in an Ex command, or use the table formvim.cmd({ cmd = "edit", args = { path } }).
Versions
Declare the minimum Neovim version in the README and in health.lua, and test that exact version in CI next to stable and nightly. Do not polyfill older versions unless asked; each extra version doubles the test matrix.
When you support a version where an API was renamed, use the old name until you drop that version, and note the rename in a comment. The rename table is in references/api.md.
Dependencies
Builtins first. Add a dependency only when it removes real work.
plenary.nvim: test dependency by default (the busted runner). Runtime dependency only if shipped code needs its modules.nui.nvim: layouts and stateful UI. For one prompt or list,vim.ui.input()/vim.ui.select()first.telescope.nvim: only for Telescope pickers.nvim-web-devicons: icon-heavy lists.
A runtime dependency goes in the README install snippet with the reason it is needed.
References
Read the one the task needs, not all of them.
references/api.md: buffers, extmarks, autocmds, keymaps,vim.uv,vim.system, notify, options, and the rename table by version. Read before writing API calls.references/lua-version.md: Lua 5.1 as the interface, what LuaJIT adds, what is missing. Read when unsure whether a Lua feature exists.references/architecture.md: config merge, lazy commands, health check, logging, async patterns, atomic file writes, debounce, sockets. Read when wiring the plugin surface or async code.references/testing.md:minimal_init.lua, Makefile, spec isolation, async tests, whymake testhangs. Read when setting up or debugging tests.references/release.md: README, vimdoc, CI, tags. Read at release time.
Pitfalls
- Old API names from training data. Check
references/api.mdbefore writingbuffer =,vim.diff,vim.uri_from_fname, orephemeral = true. --nopluginwithout loading plenary. The test command fails withE492: Not an editor command: PlenaryBustedDirectoryand then hangs instead of exiting. Seereferences/testing.md.- Rewriting a working plugin to match this layout. The layout is for new plugins. In an existing one, follow its conventions.
Verification
make testexits 0 with zero failures and no error output.:checkhealth mypluginis accurate on a clean config.- After a Neovim upgrade, or before trusting a reference claim:
nvim --clean --headless -l scripts/verify.luafrom this skill's folder. AnyFAILline names the reference file to fix.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

