Agent Skills: Neovim `nvim.pack` Architecture Expert

Auto-apply when working on the Neovim configuration in the nvim.pack/ directory. Trigger this skill when the user asks to add, modify, or debug Neovim plugins, keymaps, or the core.pack loading engine.

UncategorizedID: plutowang/term.conf/nvim-pack

Install this agent skill to your local

pnpm dlx add-skill https://github.com/plutowang/term.conf/tree/HEAD/.opencode/skills/nvim-pack

Skill Files

Browse the full folder contents for nvim-pack.

Download Skill

Loading file tree…

.opencode/skills/nvim-pack/SKILL.md

Skill Metadata

Name
nvim-pack
Description
Auto-apply when working on the Neovim configuration in the nvim.pack/ directory. Trigger this skill when the user asks to add, modify, or debug Neovim plugins, keymaps, or the core.pack loading engine.

Neovim nvim.pack Architecture Expert

You are an expert in the custom nvim.pack Neovim configuration architecture. This configuration explicitly bypasses traditional plugin managers (like lazy.nvim or packer.nvim) in favor of Neovim 0.12+'s native vim.pack package management, orchestrated by a custom, declarative loading engine (core.pack).

Rule #1: Never suggest using lazy.nvim, packer, or paq commands. All plugin management is handled via vim.pack.add and the core.pack registry.

1. Architecture Overview

The configuration is built on three pillars:

  1. Entry Point (init.lua): Sets core options, keymaps, autocmds, disables built-ins, and requires the plugin registry.
  2. Plugin Declarations (lua/plugins/init.lua): Uses vim.pack.add({ ... }, { load = function() end }) to download plugins without adding them to the runtimepath or sourcing them automatically.
  3. Loading Engine (lua/core/pack.lua): A declarative registry that dictates when and how plugins are loaded (via :packadd and require).

2. Directory Structure

The configuration is strictly organized into domains to minimize file count:

nvim.pack/
├── init.lua                 # Entry point
├── lua/
│   ├── core/
│   │   ├── autocmds.lua     # Global autocmds
│   │   ├── keymaps.lua      # Global keymaps
│   │   ├── options.lua      # Global options
│   │   └── pack.lua         # The loading engine
│   └── plugins/
│       ├── init.lua         # Plugin declarations & loading registry
│       ├── catppuccin.lua   # Theme (loaded immediately)
│       ├── completion.lua   # blink.cmp, LuaSnip
│       ├── debugging.lua    # DAP ecosystem
│       ├── deferred.lua     # markdown, colorizer, guess-indent, todo-comments
│       ├── editing.lua      # autopairs, surround, conform, folds, etc.
│       ├── git.lua          # gitsigns, diffview
│       ├── lsp.lua          # LSP, Mason, lazydev
│       ├── lualine.lua      # Statusline
│       ├── navigation.lua   # neo-tree, flash, spider
│       ├── telescope.lua    # Fuzzy finder
│       ├── tools.lua        # testing, database, diagnostics, productivity
│       ├── treesitter.lua   # TS core, context, textobjects
│       └── ui.lua           # which-key, snacks, buffers

3. The Loading Engine (core.pack)

The engine (lua/core/pack.lua) processes a registry array. Each entry defines a module and its loading trigger.

Registry Entry Schema

{
  mod     = 'domain_file', -- e.g., 'ui' (maps to lua/plugins/ui.lua)
  fn      = 'function',    -- (Optional) e.g., 'which_key' (calls M.which_key())
  packadd = { 'plugin' },  -- Array of plugin dir names to :packadd before loading
  -- Trigger (choose ONE):
  event   = 'UIEnter',     -- Autocmd event(s) (string or array)
  keys    = { ... },       -- Array of { '<leader>x', desc = '...', mode = 'n' }
  defer   = 1,             -- Milliseconds to delay via vim.defer_fn
  -- If no trigger is provided, the module loads immediately (synchronously).
}

Loading Triggers & Timeline

  1. Immediate (Startup): No trigger specified. Used only for the colorscheme (catppuccin) to prevent flashing.
  2. UIEnter Event: Non-blocking. Loads immediately after the first frame renders. Used for UI components (lualine, which_key, snacks, neo-tree).
  3. BufReadPre / BufNewFile Events: Core file-level features. Used for treesitter, lsp, gitsigns.
  4. InsertEnter / CmdlineEnter Events: Used for completion (blink.cmp).
  5. BufWritePre Event: Used for formatting (conform).
  6. Keymaps (keys): Sets a temporary keymap. On first press, it deletes the temp keymap, loads the plugin, and replays the keypress. Used for telescope, debugging, testing.
  7. Deferred (defer): Idle loading after N milliseconds. Used for low-priority visual enhancements (colorizer, render-markdown).

4. Plugin File Patterns

Domain files in lua/plugins/ (like ui.lua or editing.lua) export multiple setup functions to allow independent loading of related plugins.

Example: lua/plugins/ui.lua

local M = {}

function M.which_key()
  require('which-key').setup({ ... })
end

function M.snacks()
  require('snacks').setup({ ... })
end

return M

Corresponding Registry Entries (lua/plugins/init.lua):

{ mod = 'ui', fn = 'which_key', event = 'UIEnter', packadd = { 'which-key.nvim' } },
{ mod = 'ui', fn = 'snacks',    event = 'UIEnter', packadd = { 'snacks.nvim' } },

If a file only configures one plugin (like lualine.lua), it can self-configure on require and omit the fn field in the registry.

5. Adding or Modifying Plugins

To add a new plugin

  1. Declare it: Add the source to the vim.pack.add list in lua/plugins/init.lua.
  2. Configure it: Add a setup function to the appropriate domain file in lua/plugins/ (e.g., add to tools.lua).
  3. Register it: Add an entry to the pack.setup registry in lua/plugins/init.lua, specifying the mod, fn, packadd dependencies, and the loading trigger.

To modify an existing plugin

  1. Locate its domain file in lua/plugins/.
  2. Modify the specific exported function (e.g., M.format() in editing.lua).

6. Known Pitfalls & Critical Rules

  1. packadd Dependencies: If a plugin requires another plugin to function (e.g., lsp keymaps require telescope), you MUST include the dependency in the packadd array of the registry entry.
    • Example: { mod = 'lsp', event = 'BufReadPre', packadd = { 'nvim-lspconfig', 'telescope.nvim', 'plenary.nvim' } }
  2. vim.ui.select Overrides: Plugins that override core Neovim functions (like telescope-ui-select) MUST be loaded early (e.g., on UIEnter), even if the main plugin (Telescope) is loaded lazily via keymaps. Otherwise, the override won't be active when other plugins (like LSP code actions) try to use it.
  3. Headless Mode: The core.pack engine does NOT bypass lazy loading in headless mode. If you need a plugin to load during a headless script, you must trigger its specific event or keymap.
  4. Augroup Collisions: The engine creates augroups named 'pack-' .. entry.mod .. '-' .. entry.fn. Never manually create augroups with this naming scheme.
  5. Keymap Replay: The engine's keymap trigger uses nvim_feedkeys to replay the initial keypress. Ensure the plugin's actual keymap exactly matches the trigger keymap, or the replay will fall through to default Neovim behavior.

7. Verification & Debugging

Always verify changes using these commands:

  1. Syntax Check: luac -p lua/**/*.lua (Must pass with no errors).
  2. Headless Startup: nvim --headless +quit (Must exit cleanly with code 0).
  3. Startup Benchmark: zig run eval_startuptime.zig (Target is < 35ms median).
  4. Loaded Modules: Inside Neovim, run :lua print(vim.inspect(require('core.pack').loaded())) to see which modules have been initialized.