Initial commit: nvim-agentic-companion plugin

Adds a Claude Code agent and three skills for collaborating with the
user's running Neovim instance via mcp-neovim-server and the official
coder/claudecode.nvim plugin:

- agents/companion.md — the nvim companion identity, prefers runtime
  introspection over training-data guessing
- skills/editor-introspect — read-only queries against the live editor
- skills/editor-act — safe driving (open, jump, toggle, run); no buffer edits
- skills/claude-code-handoff — delegate buffer edits to the in-editor
  Claude Code session so users get a diff to accept or reject
This commit is contained in:
oleks
2026-05-21 00:22:05 +03:00
commit acf207e53b
8 changed files with 542 additions and 0 deletions
+106
View File
@@ -0,0 +1,106 @@
---
name: claude-code-handoff
description: |
Delegate buffer-editing work to the in-editor Claude Code session
managed by `coder/claudecode.nvim`. Use whenever a request would
*change code in a buffer*: refactor, generate, explain-and-edit,
fix a diagnostic. Opens (or focuses) the Claude Code split, attaches
the relevant buffer/selection, sends an instruction, and tells the
user how to accept/reject the resulting diff. Avoids duplicating
Claude Code's diff UX from outside the editor. Trigger on
"refactor this", "fix this in nvim", "generate the function in this
buffer", "have Claude edit my file", "send this to Claude in nvim",
"let Claude in the editor handle it".
disable-model-invocation: false
allowed-tools: Bash, Read, Skill, AskUserQuestion
---
# claude-code-handoff — let the in-editor Claude do the editing
Owner: `nvim-agentic-companion:companion`. This skill is the bridge
between *you* (the outer Claude Code session) and the **inner**
Claude Code session running in the user's nvim via
`coder/claudecode.nvim`. The inner session has the buffer, selection,
diagnostics, project root, and the diff-accept/reject UI. It is the
right tool for any edit that the user should review before it lands.
## When this skill fires
- The user wants buffer contents *modified*: refactor, rename, fix,
generate, complete, restructure.
- The change is bigger than a one-line vim substitute the user could
do themselves.
- The user wants a diff to look at before committing.
## When this skill does *not* fire
- Pure navigation (`editor-act`).
- Pure questions about state (`editor-introspect`).
- Edits to *config* files via the declarative NixVim path (those are
yours to edit with the `Edit` tool, then run `nix run .#deploy`).
## The configured keymaps (current NixVim setup)
These are defined under `<leader>a*` in
`/home/oleks/projects/servers/emmett/nixos/neovim.nix`:
| Mode | Keys | Command | Purpose |
| ---- | -------------- | ----------------------------- | ----------------------------- |
| n | `<leader>ac` | `:ClaudeCode` | Toggle the Claude Code split |
| n | `<leader>af` | `:ClaudeCodeFocus` | Move cursor to the split |
| n | `<leader>ar` | `:ClaudeCode --resume` | Resume previous session |
| n | `<leader>aC` | `:ClaudeCode --continue` | Continue last session |
| n | `<leader>ab` | `:ClaudeCodeAdd %` | Add current buffer to context |
| v | `<leader>as` | `:ClaudeCodeSend` | Send visual selection |
| n | `<leader>aa` | `:ClaudeCodeDiffAccept` | Accept the proposed diff |
| n | `<leader>ad` | `:ClaudeCodeDiffDeny` | Reject the proposed diff |
If the user remaps these, re-introspect via `editor-introspect` before
quoting them.
## Handoff procedure
1. **Confirm the split is open.** Either ask `editor-introspect` to
check for a Claude Code buffer/window, or just run
`:ClaudeCode` via `mcp__neovim__nvim_command` — the command is
idempotent (toggles, but if already visible the user sees no
surprise).
2. **Attach the right context.**
- If a buffer-level change: `:ClaudeCodeAdd %` (current buffer)
or `:ClaudeCodeAdd <path>` (a specific file).
- If a selection-level change: the user must have a visual
selection. If they don't, ask them to make one, *or* offer
`editor-act` to highlight the relevant range first.
3. **Send the instruction.** Phrase it as the user would speak to an
agent that already has the file open. Example: instead of "in
neovim.nix add a keymap for ...", say "add a `<leader>tw` keymap
that toggles wrap, next to the existing wrap-toggle block."
Send via `:ClaudeCodeSend` (after selection) or by typing the
prompt into the Claude Code buffer (`mcp__neovim__nvim_command`
plus an explicit feedkeys is overkill — usually the user can type
it themselves once focus is in the split).
4. **Brief the user.** One sentence: where the conversation is
happening (the split), what to expect (a proposed diff), and the
accept/reject keys. Example:
> Sent to the Claude Code split. You'll see a proposed diff —
> `<leader>aa` to accept, `<leader>ad` to reject.
5. **Stop.** Don't try to mirror or predict the inner session's
output. The user will come back with the result, and you can
resume from there.
## Avoiding double-work
If the user is already inside the Claude Code split typing to the
inner session, **do not also try to do the edit yourself**. Either:
- Stay quiet on the editing question and only handle introspection
/ navigation side requests, or
- Acknowledge: "the inner Claude is on it — ping me back if it
stalls or you want a second opinion."
The whole point of the handoff is to avoid two agents editing the
same buffer with different mental models.
+125
View File
@@ -0,0 +1,125 @@
---
name: editor-act
description: |
Drive the user's running Neovim instance — open files, run user
commands, trigger keymaps, jump to LSP locations, toggle UI,
evaluate small lua snippets. Uses `mcp__neovim__nvim_command` /
`nvim_eval` against the live instance. Prefers the user's own
commands and keymaps over teaching new vim syntax. Will *not* edit
buffer contents — that work is handed off via the
`claude-code-handoff` skill. Trigger on "open <file> in nvim",
"jump to definition of", "show file finder", "split this", "toggle
neo-tree", "run this command in my nvim", "go to next diagnostic",
"save my buffer".
disable-model-invocation: false
allowed-tools: Bash, Read, Skill, AskUserQuestion
---
# editor-act — do things in the user's nvim
Owner: `nvim-agentic-companion:companion`. This skill is for *acting*
on the running editor in ways that **do not modify buffer contents**.
For buffer edits, invoke `claude-code-handoff` instead.
## Preconditions
`mcp__neovim__*` tools must be present. If they aren't, stop and tell
the user to restart nvim + Claude Code (see `editor-introspect` for
the recovery message).
## The action hierarchy
When you need to do X, pick the lowest-impact form that works:
1. **An existing user command** the user already has — `:Telescope find_files`,
`:Neotree`, `:Trouble`, `:GitSigns ...`. These reflect how the user
already navigates and produce no surprises.
2. **A user keymap** — when the user asks "open the file finder," running
their `<leader>ff` is preferable to running `:Telescope find_files`
directly: same result, but if they later remap it you also get the
new behavior. Use `vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("<leader>ff", true, false, true), "n", false)`.
3. **A built-in vim command**`:edit`, `:vsplit`, `:tabnew`, `:write`,
`:bnext` — fine for navigation when no plugin command applies.
4. **Lua eval** — last resort, for things with no command surface:
`lua vim.diagnostic.goto_next()` or `lua vim.lsp.buf.definition()`.
Keep snippets short and obvious. Never paste multi-line scripts.
## Safe actions you can do without asking
- Open a file (`:edit`, `:vsplit`, `:tabnew <path>`).
- Jump to a location, definition, or reference (`vim.lsp.buf.*`,
`vim.diagnostic.goto_*`).
- Trigger a user command/keymap they already have.
- Toggle a UI element (file tree, trouble, bufferline state).
- Run `:write` *if* the buffer's only change is one you just made via a
user-blessed command (e.g. they asked you to format and you ran
`:lua vim.lsp.buf.format()`).
## Actions that require a confirmation first
`AskUserQuestion` before:
- Closing windows or buffers (`:q`, `:bd`, `:tabclose`, `:close`).
- Writing files the user did *not* just ask you to write.
- Touching files outside the current project root.
- Changing global state: colorscheme, leader, options that affect every
buffer.
- Running anything destructive against the filesystem from inside lua
(`os.remove`, `vim.fn.delete`, etc.).
## Actions you do not do
- **Editing buffer contents.** If the user wants code changed, that's
`claude-code-handoff`'s job. Do not type characters into a buffer
via `feedkeys`, do not `nvim_buf_set_text`, do not paste into a buffer.
The reason: changes should land as a *diff* the user can review via
claudecode.nvim's accept/reject UI, not as silent mutations.
- **Quitting nvim** (`:qa`, `:wqa`).
- **Source new lua / vimscript files** the user didn't approve. The
declarative config is the source of truth.
## After each action
Leave one short trail sentence in your response: what you did, and what
the user should see. Examples:
- "Ran `<leader>ff` — telescope file picker is open in your nvim window."
- "Jumped to `lualine.nvim/lua/lualine/init.lua:312` via `vim.lsp.buf.definition()`."
- "Opened `neovim.nix` in a vsplit on your right pane."
If the action didn't produce visible feedback (e.g. you only saved a
buffer), say so — the user can't tell from the screen alone.
## Common patterns
**Open a file from a path you just produced:**
```
:e /home/oleks/projects/servers/emmett/nixos/neovim.nix
```
**Open at a specific line:**
```
:e +199 /home/oleks/projects/servers/emmett/nixos/neovim.nix
```
**Find what the user has mapped, then run their mapping:**
```
:verbose nmap <leader>ff " confirm what's there
" then feed the keys so their setup runs end-to-end
:lua vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("<leader>ff", true, false, true), "n", false)
```
**Move to next LSP diagnostic in current buffer:**
```
:lua vim.diagnostic.goto_next()
```
**List loaded LSP clients for the current buffer:**
```
:lua print(vim.inspect(vim.lsp.get_clients({ bufnr = 0 })))
```
+110
View File
@@ -0,0 +1,110 @@
---
name: editor-introspect
description: |
Read-only inspection of the user's running Neovim instance via
`mcp__neovim__*` tools. Use whenever a question depends on the live
editor state: what is mapped to a key, which buffers are open, what
the cursor is on, what diagnostics exist, which plugins are loaded,
what the messages buffer says. Returns concrete facts grounded in
the running instance — never guesses from training data.
Trigger on "what's bound to", "what's mapped to", "what buffers",
"show me the keymaps", "is X loaded", "what plugins are active",
"current selection", "what's the cursor on", "any diagnostics",
"lualine theme actually applied".
disable-model-invocation: false
allowed-tools: Bash, Read
---
# editor-introspect — read the live nvim instance
Owner: `nvim-agentic-companion:companion`. This skill is for *reading*
the running editor. Never use it to mutate state — use `editor-act`
for that.
## Preconditions
The user's nvim must be running with an RPC socket reachable by the
`mcp-neovim-server` MCP server. If `mcp__neovim__*` tools are not
present in this session, **stop and tell the user**:
> The neovim MCP server isn't connected to this Claude Code session.
> Start (or restart) nvim so it creates the socket at `{{nvim_socket}}`,
> then restart Claude Code.
Do not try to fake introspection from the config alone.
## The introspection vocabulary
These are the queries you should reach for first. All are runnable
via `mcp__neovim__nvim_command` (returns the rendered command output)
or `mcp__neovim__nvim_eval` (returns a value).
### Keymaps
- `verbose nmap <leader>ff` — what (and where in lua) is `<leader>ff`?
- `verbose imap <C-Space>` — same for insert mode.
- `nmap <leader>` — all normal-mode mappings starting with leader.
- For machine-readable form prefer:
`lua print(vim.inspect(vim.api.nvim_get_keymap("n")))`
…then grep client-side. `nvim_get_keymap` returns the LHS, RHS, mode,
desc, and the file/line set when known.
### Buffers, windows, tabs
- `ls!` — list buffers, including hidden.
- `lua print(vim.inspect(vim.api.nvim_list_bufs()))`
- `lua print(vim.api.nvim_buf_get_name(0))` — current buffer path.
- `lua print(vim.inspect(vim.api.nvim_tabpage_list_wins(0)))`
- For the *visible* layout the user is staring at, prefer windows over buffers.
### Cursor, selection, mode
- `lua print(vim.inspect(vim.api.nvim_win_get_cursor(0)))``[row, col]`.
- `lua print(vim.fn.mode())``n`, `i`, `v`, `V`, `^V`, etc.
- For the current visual selection text:
`lua print(vim.fn.getregion(vim.fn.getpos("v"), vim.fn.getpos("."), {type=vim.fn.mode()}))`
(Neovim ≥ 0.10).
### Options
- `set option?` — value of a single option (`set number?`).
- `verbose set option?`*and* the file that last set it.
- `lua print(vim.bo.filetype)` / `vim.wo.wrap` / `vim.o.background`.
### Diagnostics
- `lua print(vim.inspect(vim.diagnostic.get(0)))` — current buffer's diagnostics.
- `lua print(vim.inspect(vim.diagnostic.count()))` — counts by severity.
- `lua print(vim.lsp.get_active_clients() | vim.iter ... | ...)`
which LSPs are attached.
### Plugins / runtime
- `lua print(vim.inspect(package.loaded["lualine"]) ~= nil)` — is X loaded?
- `messages` — startup warnings, last few notifications.
- `checkhealth <module>` — for a single module; the long-form output
is huge, so prefer specific modules (e.g. `:checkhealth lualine`) over
`:checkhealth` whole.
- For the lualine theme actually in use:
`lua print(require("lualine").get_config().options.theme)`
### "Why is X happening?"
When the user reports a symptom, the introspection script is:
1. `messages` — did something fail at startup?
2. `:checkhealth <suspected-module>` — does the module itself complain?
3. `verbose set <option>?` or `verbose nmap <key>` — is something overriding?
4. `vim.lsp.get_active_clients()` / `vim.diagnostic.get(0)` — is LSP behaving?
Stop as soon as you have the answer. Do not run a sweep when one query is enough.
## How to report findings
- Quote the **exact** runtime output you got, not a paraphrase.
- When citing config provenance, give `file_path:line_number` from
`{{config_path}}` (default `/home/oleks/projects/servers/emmett/nixos/neovim.nix`).
- If runtime disagrees with config, say so explicitly:
"Config sets X but the loaded value is Y — likely a stale build / missing reload."
- For long outputs, summarize and offer to drill in. Don't paste a 200-line
`:messages` dump verbatim unless the user asks.