A docent walks you through the museum. This plugin walks you through the diff.
Turn any diff — branch, commit range, staged, working tree, or GitHub PR — into a guided review session: an AI agent pre-analyzes the change, produces a narrative walkthrough ordered the way the author would explain it, flags risks and its own review concerns, and hands you a checklist. You work through hunks in a GitHub-quality diff UI (side-by-side and inline), tick items off, select any code (added or removed) to ask the agent questions, and can dispatch it to deep-dive suspicious areas in a throwaway worktree.
Zero plugin dependencies: pure Lua + Neovim ≥ 0.10 built-ins + external CLIs
(git, gh, agent CLIs). Fully usable without AI — the diff UI and manual
checklist stand alone; analysis enriches asynchronously.
-- lazy.nvim
{ 'you/docent.nvim', opts = {} }
-- or plain
require('docent').setup({}):Docent origin/main " review your branch vs merge-base (PR semantics)
:Docent HEAD~1 " last commit
:Docent a..b / a...b " explicit range
:Docent --staged " the index vs HEAD
:Docent " working tree vs HEAD (staged + unstaged)
:Docent pr:123 " GitHub PR via gh (no checkout needed)
:Docent analyze " (re-)run agent analysis for the open session
:Docent export " markdown review doc from your verdicts
:Docent close
Opening a session shows the checklist panel (left) and diff view (right)
immediately, in manual mode — the agent only runs when you ask with
:Docent analyze (set auto_analyze = true to run it on open); the UI stays
fully usable while analysis streams in. Reviewer state — read/checked
hunks, verdicts, notes — persists in .docent/ (you'll be offered a .gitignore
entry) and survives restarts, re-analysis, and rebases via content-hash +
fuzzy re-anchoring; items whose code vanished are greyed [stale], never
dropped. Re-running analysis matches old concerns to new ones by content
(hunk overlap + title similarity), so your verdicts and notes follow the
concern even when the agent reuses or renames item ids — an unmatched verdict
is set aside with a warning rather than silently misattached.
Checklist panel:
| key | action |
|---|---|
<CR> |
jump diff to item's first hunk |
x |
toggle checked (walkthrough items) |
c / d / a |
concern verdicts: confirm / reject (asks reason) / needs-author |
f |
flag item |
n |
attach/edit a reviewer note (flows into the export) |
o |
cycle ordering: narrative → risk → file |
i |
expand narration inline (default: float on hover) |
K |
ask the agent about this item |
<leader>dd |
deep-dive this item in a throwaway worktree |
q |
close the session |
Diff view (both layouts):
| key | action |
|---|---|
]f / [f |
next / prev changed file |
]h / [h |
next / prev checklist hunk (ordering-aware, crosses files) |
gt |
toggle inline ↔ side-by-side, preserving position |
The inline (GitHub-style) view is the default; set view = 'side_by_side' to
open in the two-pane layout instead.
| ? (visual) | ask the agent about the selection (old-side selections are labeled pre-change) |
| ? (normal) | ask about the deletion block or hunk under the cursor |
| <leader>a (visual) | ask a custom question |
| <leader>dd | deep-dive the hunk or selection |
The inline view renders the new file as real lines (LSP/treesitter work), with
deletions as red virtual lines, GitHub-style word-level emphasis, grey @@
separators, and long unchanged runs folded behind · N unchanged lines ·
(expand with zo).
require('docent').setup({
default_backend = 'claude',
backends = {
claude = {
analyze = { cmd = { 'claude', '-p', '--output-format', 'json',
'--allowedTools', 'Read,Grep,Glob' } },
},
pi = {
analyze = { cmd = { 'pi', '-p' } }, -- file-protocol transport
chat = { cmd = { 'pi' } },
},
mycli = { -- escape hatch: full custom harness
analyze = function(prompt, ctx) --[[ write ctx.out_path yourself ]] end,
chat = { send = function(prompt, ctx) --[[ your terminal harness ]] end },
},
},
})Q&A continuity is first-class: with Claude Code, chat resumes the exact session
that analyzed the diff (claude --resume <id> — the agent already has the
context it gathered). Backends without resume get a fresh terminal seeded with
the analysis summary, reported as degraded by :checkhealth docent.
Analysis transport preference: stdout JSON, else the file protocol — the
prompt instructs the agent to write analysis.json into the session dir, which
the plugin watches (fs_event + poll fallback). Malformed JSON gets one
automatic repair round-trip before falling back to manual mode. The plugin —
not the agent — guarantees coverage: any hunk the walkthrough missed is
appended as a plain checklist item with an "uncovered" badge.
.docent/
sessions/<id>/ # id = hash(spec, base_sha)
session.json # meta (incl. agent session id)
analysis.json # agent-owned, replaced on re-analyze
state.json # reviewer-owned, never written by the agent
patch.diff # the reviewed patch
hunks.json # snapshot for re-anchoring
chat/<n>.md # Q&A transcripts
review.md # :Docent export output
worktrees/<id>/ # deep-dive sandboxes (cleaned on close)
nvim -l tests/run.lua # all specs
nvim -l tests/run.lua inline # filter by name
Pure-Lua modules (diff parser, hunk IDs, word-level LCS, anchoring, schema
validation) are unit-tested against real gnarly git patches in
fixtures/diffs/ (renames, mode changes, binary, CRLF, big hunks, spaces in
paths); the UI has headless smoke tests covering open → render → toggle →
analysis ingest → export → rebase survival.
:checkhealth docent validates git, backend CLIs on PATH, gh auth for pr:N
sources, pi trust notes, and whether the open session's chat is truly resumed
or running degraded.