An agent-driven guided code review tool for Neovim and OpenCode. An AI agent walks you through code changes hunk by hunk, showing side-by-side diffs in Neovim while discussing the changes in a separate OpenCode chat.
The plugin connects an OpenCode agent to Neovim's diffview.nvim via Neovim's built-in RPC socket. The agent can query all change hunks, reorder them for narrative coherence, open diffs, and navigate between hunks — all while the user interacts via the OpenCode chat.
┌─────────────────────────────┬───────────────────────────┐
│ │ │
│ Neovim showing diff of │ OpenCode agent: │
│ src/utils/validation.ts │ │
│ (item 3 of 12) │ "I added a null check │
│ │ before accessing the │
│ - old code in red │ user property, since │
│ + new code in green │ it can be undefined │
│ │ after logout..." │
│ │ │
│ │ Any questions about │
│ │ this change? │
└─────────────────────────────┴───────────────────────────┘
The review workflow:
- Agent queries all change hunks across all files
- Agent groups nearby hunks and decides a review order — reordering for narrative coherence (e.g., data model first, then API, then UI)
- Agent opens the diff view and begins the review walk-through
- For each item: agent explains the change, user asks questions or leaves feedback
- Agent notes feedback but does not edit files during the review
- After the last hunk, agent closes the diff view
- Agent proposes a commit message and commits the original work
- If feedback was left, agent applies it as a separate commit
- Optionally, a second review of just the feedback changes
The plugin has two parts that are installed separately:
A Lua module that:
- Registers global functions queryable via Neovim's RPC socket:
DiffviewState()— current diffview state (file, position, file list)DiffviewHunks(ref?)— all diff hunks as a flat array, parsed fromgit diffoutput using diffview.nvim's diff parserDiffviewGoTo(file, hunks)— navigate diffview to a specific file, position cursor at the first hunk, and fold everything else. Accepts a single hunk spec, an array of hunk specs (for grouped hunks), or a plain line number.DiffviewPluginVersion()— Neovim-side plugin version used to verify compatibility with the OpenCode plugin.
- Provides diffview.nvim hooks that clean up buffers when the diff view is closed (so reviewed files don't linger as open tabs)
An OpenCode plugin that registers a diff_review tool the AI agent uses to control the diff view. Actions:
| Action | Description |
|---|---|
get_hunks |
Get all diff hunks across all files as a flat array. Each hunk is self-contained with file path, status, and line ranges. Optionally scoped to specific files or a git ref. |
start_review |
Open the diff view and begin the review walk-through. Accepts an optional array of groups (each group is an array of hunks to show together). If omitted, uses natural hunk order with each hunk as its own item. |
next |
Navigate to the next item in the review queue. Prevents wrap-around at the last item. |
prev |
Navigate to the previous item in the review queue. If include_next expanded the view, first collapses back to the current item before going back. Prevents wrap-around at the first item. |
include_next |
Expand the visible area to also show the next item alongside the current one. Items must be in the same file. The pointer advances, so next after include_next skips the already-shown item. prev goes back and shows just that single item. |
status |
Get current position in the review queue without navigating. |
close |
Close the diff view and clear the review queue. |
plugin_version |
Return OpenCode-side and Neovim-side plugin versions for diagnostics, plus whether they match. |
- Neovim 0.9+
- diffview.nvim
- OpenCode 1.3+
Using lazy.nvim:
{
"talldan/nvim-diff-review-opencode-plugin",
dependencies = { "sindrets/diffview.nvim" },
config = function()
require("diff-review").setup()
end,
}This also installs diffview.nvim as a dependency if you don't already have it. You can configure diffview.nvim separately — the plugin merges its hooks with any existing diffview hooks you have.
Add the plugin to your opencode.json configuration:
{
"plugin": ["opencode-nvim-diff-review"]
}This can go in your global config (~/.config/opencode/opencode.json) or a project-level config (opencode.json in your project root).
Restart OpenCode to load the plugin. The diff_review tool will be available to the AI agent automatically.
The tool communicates with Neovim via its RPC socket. In most cases, no configuration is needed — the tool auto-discovers running Neovim instances.
The tool automatically finds Neovim by:
- Checking the
NVIM_SOCKETenvironment variable (if set, always used) - Scanning for Neovim sockets in standard locations (
$TMPDIRand/tmp) - Preferring the Neovim instance whose working directory matches the current project
- Falling back to the first live Neovim instance found
This means if you just run nvim in your project directory, OpenCode will find it automatically.
If auto-discovery doesn't work for your setup (e.g., multiple Neovim instances in the same directory), you can set the socket path explicitly:
export NVIM_SOCKET=/tmp/nvim.sock
nvim --listen $NVIM_SOCKETMake sure NVIM_SOCKET is set in the environment where OpenCode runs.
If you use CMUX, you can set this in your workspace configuration so both Neovim and OpenCode share the socket path automatically.
OpenCode (agent) Neovim (editor)
│ │
│ nvim --headless --server │
│ $NVIM_SOCKET --remote-expr │
│ "luaeval('...')" │
│ ─────────────────────────────────>│
│ │
│ JSON response │
│ <─────────────────────────────────│
│ │
The tool uses Neovim's --remote-expr to evaluate Lua expressions on the running Neovim instance. This is a standard Neovim feature that works in any terminal — no CMUX or specific terminal emulator required.
The review operates at the hunk level rather than the file level. This means:
- A file with 3 separate change regions is presented as 3 review items (or fewer if the agent groups them)
- The agent can group nearby hunks in the same file into a single review item — e.g., 3 small changes within one function become one item instead of three
- Grouping should account for both conceptual relationship and physical proximity. Distant helper definitions, call sites, and tests should usually be separate review items even when conceptually related.
- The agent can reorder hunks across files for narrative coherence (e.g., show a data model change in
model.tsbefore the API endpoint inapi.tsthat uses it, even if they're in different files) - The agent can filter out trivial hunks (e.g., import reordering) from the review
- During review,
include_nextcan dynamically expand the visible area to include the next item — useful when the agent realizes adjacent items should be reviewed together
Hunks are retrieved by running git diff -U0 and parsing the output with diffview.nvim's built-in unified diff parser (diffview.vcs.utils.parse_diff). The -U0 flag produces zero-context hunks, giving exact change boundaries. (Note: -U0 omits the count in hunk headers when it's 1, e.g., @@ -134 +134,4 @@. The plugin normalizes these to the N,M form before parsing.)
When navigating to a hunk (or group of hunks), the plugin folds all other regions of the file so only the target area is visible — similar to git add -p. This is achieved by:
- Switching both diff windows from
foldmethod=difftofoldmethod=manual - Computing the bounding box across all hunks in the group (from earliest start to latest end)
- Creating two manual folds: one above the bounding box and one below
- Showing 5 lines of context above and below the visible area
The fold highlight is overridden with a custom DiffviewDiffFoldedReview highlight group (linked to Comment by default) so fold lines look like muted separators rather than diff modifications. Users can override this highlight group in their colorscheme.
Line numbers are switched to absolute (number, no relativenumber) during hunk focus so they match the line ranges shown in the hunk headers.
If a review item groups hunks that span more than 200 lines, start_review emits a grouping warning. This nudges the agent to split distant helper definitions, call sites, or tests into separate review items before the review becomes unwieldy.
The review queue is managed on the TypeScript (OpenCode plugin) side. It holds:
- An ordered array of review items, where each item contains one or more hunks (set by
start_review) - The current position in the queue (advanced by
next/prev) - A visible range (which may span multiple queue items when
include_nextis used)
The Lua (Neovim) side is stateless — it provides functions to query hunks and navigate the diff view, but doesn't track review progress. This keeps the Neovim plugin simple and the state management in one place.
--headlessflag on remote calls: Prevents Neovim from emitting terminal escape sequences when invoked from a subprocess (e.g., Bun's shell). Without this, the JSON response gets polluted with control codes.- State queries via global Lua functions:
DiffviewState(),DiffviewHunks(), andDiffviewGoTo()are registered as globals so they can be called vialuaeval()from Neovim's--remote-exprwithout needing the module require path. - Wrap-around prevention: The tool checks queue bounds before navigating and refuses to advance past the first/last item.
- Buffer cleanup on close: diffview.nvim intentionally keeps local file buffers open after closing (so you can continue editing). The plugin tracks which buffers existed before the review and removes any new ones on close — unless they have unsaved edits.
- Async cursor positioning:
DiffviewGoTostores a pending target and applies it via aDiffviewDiffBufWinEnterautocmd +vim.defer_fn. This ensures the cursor is positioned after diffview's asyncset_filecompletes (which resets cursor to line 1 onfile_open_new). - Socket auto-discovery: When
NVIM_SOCKETis not set, the tool scans$TMPDIR/nvim.$USER/and/tmpfor Neovim socket files, verifies each is live, and useslsofto match the Neovim process's working directory against the current project. This allows zero-configuration usage in ad-hoc terminals — just runnvimand OpenCode will find it. - Line wrapping: Diff windows have
wrapenabled during hunk focus so long lines are fully visible without horizontal scrolling. - Version handshake:
plugin_versionreports both the OpenCode and Neovim plugin versions. Neovim-dependent actions preflight the required Lua globals and report clearly if the two sides are mismatched.
The tool description embeds detailed workflow instructions for the AI agent:
- Lint before review: The agent is told to fix lint/format issues before opening the diff, so the user only sees clean changes.
- Grouping: The agent groups nearby hunks in the same file (especially within the same function) into single review items, reducing a 60-hunk diff to ~15-20 items.
- Narrative ordering: The agent analyzes hunks and reorders them for coherent presentation — explaining foundational changes before dependent ones.
- No edits during review: The agent is explicitly instructed to never edit files during the review. Feedback is collected and applied afterward.
- Two-commit pattern: Original work is committed first, then feedback changes are committed separately. This gives clean git history and allows the second review to show only the feedback diff.
- Line-numbered explanations: The agent references relevant line numbers when explaining each review item, making it easy to map comments back to the visible diff.
- Interactive pacing: The agent explains each hunk, asks for feedback, and waits for the user's response before moving on.
Run the diagnostic action:
{ "action": "plugin_version" }Expected output should show matching versions:
OpenCode plugin: v0.6.1
Neovim plugin: v0.6.1
Compatible: yes
If OpenCode reports an older version, clear OpenCode's plugin cache and restart OpenCode:
rm -rf ~/.cache/opencode/packages/opencode-nvim-diff-review@latestIf Neovim reports an older version, update the Neovim plugin (for lazy.nvim, run :Lazy update) and restart Neovim.
The tool checks for DiffviewState, DiffviewHunks, and DiffviewGoTo before running Neovim-dependent actions. If these are missing, the Neovim plugin has not loaded or require("diff-review").setup() has not run in that Neovim session.
If the plugin is installed but not loaded, run this in Neovim:
:lua require("diff-review").setup()Then retry the tool action.
These were discussed during development but not yet implemented:
Show surrounding code context around the current hunk. Useful when the agent or user needs to see more of the file to understand a change. Would be implemented as an expand action.
Allow the user to accept or reject individual hunks from the diff view, similar to git add -p. This would integrate with diffview.nvim's staging capabilities.
Instead of using git diff to determine changed files, use OpenCode's /session/:id/diff API endpoint to get exactly which files the agent modified in the current session. This would avoid showing unrelated uncommitted changes.