Important
Human review needed. This README is AI-generated and has not yet been
confirmed by a human. Once reviewed and confirmed, remove this notice. See
AI_POLICY.md (Principle 1). The human review badge
above shows how many files still carry this marker; just review lists them.
A production-grade Neovim 0.12 plugin template — strict types, zero deprecated APIs, batteries included.
https://user-images.githubusercontent.com/000000/000000-demo.mp4
Demo placeholder — record a short screencast of your plugin and drop the URL above.
- Requirements
- Installation
- Setup
- Commands
- Options
- API
- Project structure
- Architecture
- Conventions & rules
- Development
- Human review & AI policy
- Contributing
- Template usage
- License
- Neovim >= 0.12
- Optional (development only):
just,stylua,selene,lua-language-server— or use the bundled Nix flake (direnv allow).
lazy.nvim
{
'KangaZero/sidey.nvim',
opts = {}, -- calls require('sidey').setup({})
}vim.pack (built-in, 0.12+)
vim.pack.add({ 'https://github.com/KangaZero/sidey.nvim' })
require('sidey').setup()rocks.nvim / luarocks
:Rocks install sidey.nvimpacker.nvim
use({
'KangaZero/sidey.nvim',
config = function()
require('sidey').setup()
end,
})vim-plug
Plug 'KangaZero/sidey.nvim'
" then, in Lua:
lua require('sidey').setup()sidey is zero-config: the first require('sidey') auto-initialises
with the defaults, so calling setup() is optional. Call it only to override
defaults.
require('sidey').setup({
debug = true,
theme = 'dark',
})| Command | Arguments | Description |
|---|---|---|
:Sidey |
info |
Print version and resolved config. |
:Sidey |
sum <N> <N>… |
Sum the given numbers (demo action). |
Subcommands are completed on the command line.
The full set of options and their defaults. Pass any subset to setup();
omitted keys keep their default and unknown keys are rejected (typos fail loudly).
require('sidey').setup({
-- Enable verbose logging and a file sink under `stdpath('log')/sidey.log`.
---@type boolean
debug = false,
-- Minimum level a log message must meet to be emitted.
---@type "trace"|"debug"|"info"|"warn"|"error"
log_level = 'warn',
-- Example domain-restricted option (stands in for real plugin config).
---@type "auto"|"light"|"dark"
theme = 'auto',
})local sidey = require('sidey')
sidey.setup(opts) -- resolve config, build logger; returns the module
sidey.config -- resolved sidey.Config (available after init)
sidey.log.info('hi %s', x) -- leveled logger
sidey.util.add(2, 3) --> 5
sidey.util.sum({ 1, 2, 3 }) --> 6Health: :checkhealth sidey.
sidey.nvim/
├── lua/sidey/
│ ├── init.lua # entry: VERSION, setup(), lazy submodule access
│ ├── config.lua # typed defaults + deep-merge + vim.validate
│ ├── types.lua # ---@meta: all @alias / @class (literal unions)
│ ├── util.lua # example util module (the "generic example code")
│ ├── log.lua # leveled logger gated by config.debug / log_level
│ ├── commands.lua # :Sidey subcommand dispatcher + completion
│ └── health.lua # :checkhealth sidey
├── plugin/sidey.lua # version guard (>=0.12) + user-command registration
├── after/plugin/ # (empty) load-after-others hooks
├── ftplugin/ # (empty) per-filetype settings
├── tests/ # mini.test specs + headless minimal_init
├── scripts/ # deps bootstrap + review-markers reporter
├── doc/sidey.txt # generated from README by panvimdoc
├── .github/workflows/ # ci · docs · release · review
├── flake.nix + .envrc # Nix dev shell (direnv)
├── .luarc.json # LuaLS config (strict)
├── .stylua.toml # formatter config
├── selene.toml+vim.yml # linter config + Neovim stdlib
├── pkg.json # packspec metadata
├── sidey-scm-1.rockspec
├── justfile # task runner
└── generate.sh # one-time template rename
- Entry & lazy loading.
require('sidey')returns a table with a metatable__index. On first field access it auto-initialises with defaults (zero-config), then resolvesrequire('sidey').<name>to the submodulesidey.<name>and caches it — so submodules load only when used. - Config flow.
config.luaowns the fully-populateddefaults.config.resolve(opts)deep-merges user input, rejects unknown keys, and validates every field via the 0.12vim.validatesignature. The rest of the code only ever sees a complete, type-correctsidey.Config. - Types.
types.luais a---@metafile (never required at runtime). It is the single source of truth: literal-string unions (sidey.LogLevel,sidey.Theme), an(exact)resolvedsidey.Config, and the partialsidey.Optsinput type. plugin/is light. It only guards the Neovim version and registers the:Sideycommand; all real work is deferred behindrequire.
Contributions are expected to follow these (enforced in CI):
- Strict LuaCATS everywhere — literal unions over
string,(exact)config classes,---@param/---@returnon every function. Neverany. - No deprecated APIs — target Neovim 0.12 (
vim.uv,vim.system,vim.iter, positionalvim.validate,nvim_set_option_value, …). - Doc comments on every module (
---@class) and function. - Formatting/linting must pass
stylua --check .andselene .. - Tests (
mini.test) accompany behavioural changes.
See CONTRIBUTING.md for the full workflow and
AI_POLICY.md for the automation/AI rules.
A Nix flake provides every dev tool. With direnv:
direnv allow # loads the flake dev shell on cd (see flake.nix / .envrc)Or nix develop. Then:
just deps # vendor mini.nvim into deps/ for the test suite
just fmt # format with stylua
just lint # selene
just typecheck # lua-language-server --check
just test # run mini.test headlessly
just check # fmt-check + lint + typecheck + test (the full gate)
just review # list files still carrying a "Human review needed" markerThis template is generated with AI assistance. Per AI_POLICY.md,
AI-generated content carries a Human review needed marker until a human
confirms it, then the marker is removed. The human review badge at the top
reflects how many files are still pending; run just review to see which, and
delete each marker as you review its file.
See CONTRIBUTING.md and
CODE_OF_CONDUCT.md. Commits follow
Conventional Commits; disclose AI
assistance with an Assisted-by: trailer.
This repo is a template. After cloning:
./generate.sh # renames sidey -> your name, renames files, then self-deletes