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:
@@ -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.
|
||||
@@ -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 })))
|
||||
```
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user