- Product name:
patchmarks.nvim - Primary goal: review changed files in a Git worktree and attach line-based annotations that can be exported back to an LLM coding agent.
Patchmarks is a file-oriented review plugin for Neovim.
It starts a review session for the current Git change set and attaches a local annotation layer to eligible normal file buffers. Users may optionally open a native quickfix list for changed-file navigation.
Patchmarks does not implement Git diff rendering itself. It is designed to coexist with existing diff plugins such as gitsigns.nvim or mini.diff. Those plugins remain responsible for signs, inline hunk preview, staging, unstaging, and other Git-facing visuals. Patchmarks only owns:
- review session lifecycle
- changed-file discovery
- file navigation
- annotation storage
- annotation rendering
- annotation export
Patchmarks follows a platform-first, Unix-style philosophy.
It should lean on:
- Neovim built-ins before custom infrastructure
- Git CLI before custom repository logic
- existing diff plugins before custom diff UI
- optional normal quickfix behavior before custom list UIs
Patchmarks should only implement the narrow layer that is actually unique:
- review-session orchestration
- annotation UX
- annotation persistence
- compact export for agent feedback
- Hunk-level review UI
- Replacing
gitsigns.nvim,mini.diff,fugitive, or similar tools - Managing Git staging/unstaging directly
- Persisting annotations across review rounds after the user starts a new round
- Supporting deleted tracked files in v1
- Tracked modified files
- Tracked added files
- Renamed files, using the new path
- Copied files, using the new path
- Untracked files
- Line annotations on a single line
- Line-range annotations across multiple lines
- Deleted tracked files
- Column-precise annotations
- Overlapping annotations
- Multiple annotations covering the same line range
- Automatic carry-forward of annotations into a new review round
- Direct integration with any specific agent CLI
Patchmarks is centered on a review session.
When the user starts a session:
- Patchmarks finds the Git repo root.
- It computes the current changed-file set.
- It creates or restores the persisted session.
- It attaches Patchmarks keymaps and annotation extmarks to the current buffer if that buffer is an eligible source buffer.
- It registers buffer-entry hooks so eligible source buffers attach when the user opens them normally.
- It does not open quickfix, jump files, or move the cursor.
During the session:
- the user opens and moves between files however they normally do
- the user may run
:PatchmarksFileswhen they want a native quickfix list of changed files - the user uses Patchmarks mappings to annotate line ranges
- the user may keep using their existing diff plugin to inspect or stage hunks
- Patchmarks only refreshes when the user explicitly asks it to
At export time:
- Patchmarks produces a compact text block
- it copies that block to Neovim registers and the system clipboard when available
- it keeps the current session intact until the user explicitly starts a new round or discards it
- if the review was exported and Git later changes, starting Patchmarks starts a fresh round automatically
The source buffer must remain the real file buffer so the user gets:
- normal syntax highlighting
- their usual motions and text objects
- existing Git diff signs and inline hunk preview from external plugins
This removes the need for Patchmarks to synthesize diff buffers or build its own diff renderer.
Required:
- Neovim
>= 0.10 - Git available on
$PATH
Optional:
lewis6991/gitsigns.nvimechasnovski/mini.diff
Patchmarks has no hard dependency on either optional plugin.
Patchmarks exposes a small Lua setup surface:
require("patchmarks").setup({
preview = {
trigger = "manual", -- or "cursorhold"
width = 72,
height = 6,
},
})v1 intentionally keeps configuration narrow.
preview.trigger"manual"by default"cursorhold"enables preview-on-hover for annotations in attached session buffers
preview.width- preview float width
preview.height- preview float height
:PatchmarksStart- Start the current review session if one exists.
- Otherwise create a new session from the current Git state.
- Do not open quickfix, switch buffers, or move the cursor.
:PatchmarksFiles- Populate and open a native quickfix list for the active session's changed files.
:PatchmarksNew- Discard any existing session and start a fresh round from the current Git state.
:PatchmarksRefresh- Refresh changed-file metadata without discarding annotations.
:PatchmarksExport- Export the current annotations to registers and clipboard.
:PatchmarksStop- Stop Patchmarks UI state but keep the session persisted.
:PatchmarksDiscard- Delete the active session and all annotations for it.
- Only one active Patchmarks session exists per Git repo.
- Session state is persisted under
.git/patchmarks/current.json. - Starting a new round with
:PatchmarksNewdiscards old annotations entirely. - Exporting does not clear the session automatically.
- If Neovim crashes or closes, restarting with
:PatchmarksStartrestores the session. - If the session was exported and Git has changed since that export,
:PatchmarksStartstarts a fresh round instead of restoring old annotations.
Patchmarks uses:
git rev-parse --show-toplevelgit status --porcelain=v1 -z --untracked-files=all
Include files with statuses equivalent to:
- modified
- added
- renamed
- copied
- untracked
- mixed staged/unstaged variants of the above
- deleted tracked files
- For renames and copies, Patchmarks uses the destination path.
- Paths are stored relative to repo root.
- Quickfix items use absolute paths internally and repo-relative paths for display/export.
Patchmarks still computes the first changed line for each tracked file so the initial cursor placement is useful.
For tracked existing files:
- use
git diff --unified=0 --no-ext-diff HEAD -- <path> - parse the first hunk header
- use the new-side start line as the target line
For untracked files:
- target line is
1
For files where no diff hunk can be parsed but the file exists:
- target line is
1
Patchmarks can use Neovim's normal built-in quickfix list when explicitly requested.
It does not implement a custom quickfix UI.
Patchmarks only:
- populates the list with changed files via
setqflist() - opens it from
:PatchmarksFiles - updates the list title and item text as session metadata changes
Each quickfix item represents one file, not one hunk.
filename: absolute file pathlnum: first changed line or1col:1text: compact label with Git status and annotation countuser_data: internal file metadata
Format:
Patchmarks: <repo-name> (<file-count> files, <annotation-count> notes)
Format:
[<status>] <relative-path> (<note-count>)
Examples:
[M] lua/patchmarks/session.lua (2)[??] notes/scratch.md (0)[R] lua/old.lua -> lua/new.lua (1)
Default order:
- Git status order
- files with stale annotations remain at the end
The quickfix list should stay stable while the user is actively reviewing.
Patchmarks uses the real file buffer.
- source buffers remain ordinary editable file buffers
- Patchmarks only attaches to buffers with
buftype == "" - Patchmarks only attaches when the normalized file path belongs to the active session
- no synthetic diff text is inserted into the buffer
- existing syntax highlighting remains untouched
Patchmarks is inactive in synthetic or plugin-owned buffers:
- quickfix
- help
- terminal
- prompt
nofileacwrite- Fugitive status/diff/index buffers
- other plugin-owned buffers without a stable real file identity
Annotations target source files, not Git control surfaces or synthetic views.
Patchmarks does not render or manage hunks.
If another plugin is attached to the buffer, its gutter and inline diff UI are allowed to remain active. Patchmarks avoids owning the sign column to reduce conflicts.
Annotations are line-based, not column-based.
- A normal-mode annotation targets the current line.
- A visual-mode annotation targets the selected line range.
- Characterwise and blockwise selections are normalized to full-line ranges.
- Range endpoints are inclusive.
- Annotations may not overlap.
- Creating a new annotation on a range that overlaps an existing annotation opens the existing annotation for editing instead of creating a second one.
Each annotation stores:
idpathstart_lnumend_lnumbodycreated_atupdated_atexcerpt
excerpt is a short snapshot of the annotated lines used for resilience and diagnostics after reload.
Inside an active Neovim session, annotations are anchored with extmarks.
- one extmark per annotation
- extmark starts on
start_lnum - extmark range covers
start_lnum..end_lnum - extmarks are only runtime anchors and are rebuilt from persisted data on restore
Persisted JSON stores line ranges, not extmark ids.
On restore:
- reopen the file
- place the extmark back on the stored line range
- if the file is shorter than before, clamp the range into valid bounds
Patchmarks renders annotations in the source buffer without taking over the gutter.
- subtle highlight over the full annotated line range
- compact end-of-line marker on the first line of the range
- marker only by default
- no inline annotation text preview by default
This keeps the buffer quiet and avoids competing with diff overlays from other plugins.
Preview is separate from editing.
- preview is manual by default
- buffer-local mapping:
<localleader>p - if the same annotation is already previewed, Patchmarks should avoid reopening the float unnecessarily
- config option
preview.trigger = "cursorhold" - when enabled, Patchmarks opens the preview float on
CursorHoldfor the annotation under the cursor
Manual preview avoids:
- hover flicker
- accidental popup churn while navigating
- interference with existing
CursorHoldworkflows
The preview float is read-only.
- anchored near the cursor
- fixed size
- wrapped text
- non-focusable
- auto-closes on cursor move, buffer leave, or explicit close
- width:
72 - height:
6
- title:
<relative-path>:<start>-<end> - body: full annotation text
Annotation creation and editing use a real floating editor buffer.
- anchored near the cursor position of the source buffer
- focusable
- fixed width and height
- multiline by default
- scrollable
- wraps long lines
- behaves like a temporary Vim buffer, not a modal form
- uses a custom-write scratch buffer
- source buffer remains visible behind it
- width:
72 - height:
8
- anchor to the cursor line of the source buffer
- prefer opening below the cursor
- if there is insufficient room below, open above
Format:
Patchmarks: <relative-path>:<start>-<end>
<Esc>in insert mode returns to normal mode as usual- normal-mode motions, scrolling, search, and text objects should work normally
:wsaves and keeps the editor open:wqandZZsave and close:qcloses only when there are no unsaved changes:q!andZQdiscard unsaved changes and close
- trim trailing blank lines
- on
:w, save in place and redraw annotation extmarks without closing - on
:wqorZZ, save and close - saving an empty new annotation closes without creating anything
- quitting an empty existing annotation with
:qkeeps the original annotation unchanged - saving an existing annotation as empty asks for deletion confirmation and deletes only on acceptance
These mappings exist only in normal file buffers that belong to the active Patchmarks session.
<localleader>a- Add annotation on current line or visual selection.
<localleader>e- Edit annotation under cursor.
<localleader>d- Delete annotation under cursor.
<localleader>p- Preview annotation under cursor.
]a- Jump to next annotation in the current file, wrapping to the first annotation at the end.
[a- Jump to previous annotation in the current file, wrapping to the last annotation at the start.
<localleader>x- Export current session.
<localleader>r- Refresh Patchmarks metadata.
<localleader>R- Start a new round and discard the current session.
[aand]aonly traverse annotations in the current file- navigation wraps within the current file
- if no annotation exists in the current file, Patchmarks shows a short notification and does nothing
Patchmarks does not own file traversal by default. Users open files however they normally do.
When requested, :PatchmarksFiles provides a native quickfix list for changed-file traversal.
Patchmarks should prefer Neovim 0.10+ built-ins over homegrown helpers whenever possible.
Examples:
vim.fsfor path operationsvim.system()for Git subprocessesvim.iterwhere it meaningfully simplifies traversalvim.api.nvim_create_autocmd()for lifecycle hooksvim.api.nvim_open_win()for preview/editor floatssetqflist()andgetqflist()for quickfix management- extmarks for runtime annotation anchors
Avoid introducing utility wrappers unless they reduce real complexity.
Patchmarks refresh is metadata-oriented, not destructive.
- refresh is explicit only
- there is no automatic refresh on focus, buffer entry, or window entry
- changed-file set from Git
- first changed line per file
- quickfix title and item labels if the Patchmarks file list is active
- it does not discard annotations
- it does not start a new round
- it does not auto-resolve or auto-carry annotations
If a file already has annotations and later leaves the live Git change set during the same session:
- keep it in the quickfix list
- mark it internally as
stale - keep its annotations intact
This prevents silent data loss.
Starting a new round is explicit.
:PatchmarksNew<localleader>R
- delete current session data
- rebuild changed-file set from current Git state
- create a brand-new annotation set
Patchmarks treats export as the handoff boundary to the agent.
If the current session was exported and Git later changes:
- starting with
:PatchmarksStartstarts a fresh round automatically - old annotations from the exported round are not restored
- Patchmarks shows a short notice explaining why a fresh round was started
If the session was exported but Git is unchanged:
:PatchmarksStartrestores the existing session normally
There is no automatic annotation carry-forward in v1.
Export is optimized for compactness and agent usefulness.
Always write to:
- unnamed register
"
Also write to these when available:
+*
Patchmarks should detect clipboard support instead of assuming Neovim is already synced to the system clipboard.
Use a compact plain-text block.
Default format:
PATCHMARKS REVIEW
repo: <repo-name>
files: <annotated-file-count>
notes: <annotation-count>
[<relative-path>:<start>-<end>]
<annotation body>
[<relative-path>:<start>-<end>]
<annotation body>
- files in session order
- annotations in ascending line order within each file
Do not include:
- code excerpts
- Git diff text
- hunk metadata
- timestamps
This keeps the payload token-efficient and assumes the agent can inspect the repo directly.
Patchmarks coexists with external diff plugins but does not control them.
- Patchmarks does not toggle diff overlays, inline previews, or hunk popups automatically
- Patchmarks does not wrap provider-specific commands behind its own keymaps
- Patchmarks does not try to normalize provider UX across
mini.diff,gitsigns.nvim, or any other plugin - users keep using their existing diff plugin exactly how they already prefer
Patchmarks should avoid interfering with provider signs, overlays, or buffer attachments.
{
"version": 1,
"repo_root": "/abs/path/to/repo",
"created_at": "2026-04-16T16:00:00Z",
"exported_at": null,
"files": {
"lua/patchmarks/session.lua": {
"status": "M",
"first_changed_line": 42,
"stale": false,
"annotations": [
{
"id": "ann_001",
"start_lnum": 42,
"end_lnum": 45,
"body": "Collapse these two refresh paths into one debounced entry point.",
"excerpt": "function Session.refresh(...)",
"created_at": "2026-04-16T16:02:00Z",
"updated_at": "2026-04-16T16:03:00Z"
}
]
}
}
}- fail with a clear message
- do not open Patchmarks UI
- open nothing
- show a short message:
Patchmarks: no changed files
- still write to unnamed register
- show a short message that system clipboard was unavailable
- if the file no longer exists, keep the annotation data in the session
- file-list entry remains if
:PatchmarksFilesis used, but opening it shows a clear error
- Git status refresh should avoid reopening all buffers unnecessarily
- annotation rendering should only update the current buffer when possible
- refresh should be debounced
- export should be linear in annotation count
Expected scale for v1:
- tens to low hundreds of changed files
- tens of annotations
Recommended Lua module split:
patchmarks.initpatchmarks.configpatchmarks.gitpatchmarks.sessionpatchmarks.annotationspatchmarks.renderpatchmarks.floatpatchmarks.exportpatchmarks.commandspatchmarks.keymaps
Implementation should proceed slice by slice.
- each slice must be coherent and usable on its own
- each slice must be large enough to exercise a real user workflow
- each slice must remain small enough to test confidently
- after each slice, implementation must be reviewed against both the slice plan and this spec
- after each slice, headless tests must pass before handing off for manual testing
- no commit should be made until manual testing for that slice is complete
Favor vertical slices over layer-only slices.
Good slices combine enough command, state, rendering, and test coverage to let the feature be used end to end.
- canonical name:
patchmarks.nvim - optional file-oriented quickfix list
- real source buffer, editable
- optional coexistence with
gitsigns.nvimormini.diff - line-based non-overlapping annotations
- manual annotation preview by default
- optional
CursorHoldpreview mode - floating multiline editor anchored to cursor
- autosaved session under
.git/patchmarks/current.json - no deleted tracked files in v1
- no cross-round annotation carry-forward in v1
- compact plain-text clipboard export