Terminal-first code review notes for Git repos.
remark renders diffs for your working tree and lets you attach line comments that are saved as git notes. It also generates a collated “review prompt” you can hand to an LLM agent (or a human) to implement the requested changes.
- Keep review feedback in the repo, versioned, branch-aware, and shareable via
refs/notes/*. - Review staged, unstaged, or base-branch diffs without shelling out to
git. - Stay in the terminal, with a UI inspired by GitHub/Forgejo review flows.
- Two-pane TUI: file list on the left, diff on the right.
- Views
all: HEAD → worktree (shows combined staged + unstaged result)staged: HEAD → indexunstaged: index → worktreebase: merge-base(base, HEAD) → HEAD
- Diff modes (cycle with
i)- Decorated (default): full file with git status markers (
+,-,) likebat/scat - Side-by-side (only for modified files; added/deleted fall back to unified)
- Unified ("stacked")
- Mode persists in
.git/configasremark.diffView
- Decorated (default): full file with git status markers (
- Per-file syntax highlighting on diff code lines using
verdant+palatelanguage detection. - Comment markers: unresolved comments show
💬and resolved comments show✓. - Resolve comments: resolve/unresolve individual comments so your prompt only contains actionable items.
- Git notes storage: comments are stored under a notes ref (default:
refs/notes/remark). - Reviewed files
- Toggle reviewed state per file; reviewed files are dimmed with a checkmark.
- Jump between unreviewed files with
Ctrl+N/Ctrl+Pin the diff pane.
- Prompt rendering
- In TUI: open a prompt editor, edit it, and copy with
Shift+Enter. - Headless:
remark prompt …prints (or copies) the prompt without launching the UI.
- In TUI: open a prompt editor, edit it, and copy with
- Clipboard support
- Desktop clipboard via
copypasta(Wayland supported) - OSC52 fallback for remote terminals
- Desktop clipboard via
This repo is a Rust binary crate.
cargo build --releaseThe resulting binary is:
target/release/remarkReleases are cut automatically after CI passes on pushes to main by the
Cut Release workflow. It:
- Updates
CHANGELOG.mdviagit-cliff - Runs
cargo releaseto bump the patch version and tag - Pushes the release commit and tag
Artifacts are built and published by the Release workflow, which is triggered
by the tag push.
Important: the tag push must be made with a token that can trigger workflows.
Set a repo secret named RELEASE_TOKEN (a PAT with contents write + workflow
permissions). Without it, the tag push will not trigger Release, and no
GitHub Release artifacts will be produced.
Run inside a git repository:
remarkOptional flags:
remark --ref refs/notes/remark --base refs/heads/main
remark --view staged
remark --file src/lib.rs --line 42 --side new--ref: which notes ref to store reviews under (default:refs/notes/remark)--base: base ref used by the “base” view (default:@{upstream}thenmain/masterheuristics)--view: start in viewall,unstaged,staged, orbase--file: preselect a file when launching the UI--line: preselect a 1-based line in the selected file (requires--file)--side: which side for line selection (oldornew, default:new)
Render the collated prompt from the stored per-file notes, without starting the TUI:
remark promptOptions:
remark prompt --filter all
remark prompt --filter staged
remark prompt --filter unstaged
remark prompt --filter base --base refs/heads/main
remark prompt --ref refs/notes/remark
remark prompt --copy# Resolve a file-level comment
remark resolve --file src/lib.rs --file-comment
# Resolve a line comment (default side is "new")
remark resolve --file src/lib.rs --line 42
# Resolve an "old" (deleted) line comment
remark resolve --file src/lib.rs --line 10 --side old
# Mark a comment as unresolved again
remark resolve --file src/lib.rs --line 42 --unresolveTwo configuration files give you a seamless flow: keybindings to open the draft, and language-server wiring so remark shows code actions and syncs on save.
In ~/.config/helix/config.toml:
[keys.normal]
A-h = ':hsplit .git/remark/draft.md'
A-v = ':vsplit .git/remark/draft.md'These bindings open the repo’s draft file in a split. For best results, start
Helix from the repo root so .git/remark/draft.md resolves correctly.
In ~/.config/helix/languages.toml:
[language-server.remark]
command = "remark"
args = ["lsp"]
required-root-patterns = [".git"]
# TypeScript / TSX
[[language]]
name = "typescript"
language-servers = ["remark", "typescript-language-server"]
[[language]]
name = "tsx"
language-servers = ["remark", "typescript-language-server"]
# Go
[[language]]
name = "go"
language-servers = ["remark", "gopls"]
# Python
[[language]]
name = "python"
language-servers = ["remark", "pyright"]
# Shell (pick what you use)
[[language]]
name = "bash"
language-servers = ["remark", "bash-language-server"]
[[language]]
name = "toml"
language-servers = [ "crates-lsp", "taplo" ]
formatter = { command = "taplo", args = ["fmt", "-"] }
[[language]]
name = "rust"
roots = ["Cargo.toml", "Cargo.lock"]
language-servers = [
"remark",
"rust-analyzer"
]Workflow:
- Use the remark code action (space+a) on a line or file header to seed a comment.
- Press
Alt-h/Alt-vto open the draft. - Edit and save; the LSP syncs draft changes back into notes.
Viewing comments:
To view a single comment in the editor you can move to the start of the line and the inlay hint will be shown. You can also use the hover action (space + k) to show the message diagnostics.
To view all the comments for a file you can just view the diagnostics (space + d). That should list all the LSP diagnostics and so you can review comments that way too.
Zed only starts custom language servers when they are registered by an extension.
Install remark-lsp from Zed's extensions UI.
Use the dev extension while iterating locally:
- In Zed, open the command palette and choose Extensions: Install Dev Extension.
- Select the folder
zed-extension/remark-lspin this repo.
Add remark-lsp to the languages you want, and keep defaults with ...:
{
"languages": {
"Rust": { "language_servers": ["remark-lsp", "..."] },
"Go": { "language_servers": ["remark-lsp", "..."] },
"TypeScript": { "language_servers": ["remark-lsp", "..."] },
"TSX": { "language_servers": ["remark-lsp", "..."] }
}
}The extension resolves the remark binary from your PATH, then runs remark lsp.
If Zed can't find it, make sure $HOME/.cargo/bin is in the PATH Zed sees.
You can pass extra LSP flags via REMARK_LSP_ARGS, for example:
REMARK_LSP_ARGS="--no-inlay-hints" zedZed doesn't yet support the LSP showDocument flow that remark uses to open the draft,
so use a task to open .git/remark/draft.md. First, use the code action to mark a line
or file for comment, then open the draft file via a task.
Create a project-local tasks file at .zed/tasks.json (in the repo root):
[
{
"label": "remark: open draft",
"command": "gitdir=$(git rev-parse --git-dir) && zed \"$gitdir/remark/draft.md\"",
"cwd": "$ZED_WORKTREE_ROOT"
}
]Notes:
- Requires the
zedCLI to be on your PATH. (you may need to symlink:sudo ln -sf /usr/bin/zeditor /usr/bin/zed) - The draft file is inside
.git, so it won't show up in git status.
To run the task, open the command palette and use task: spawn (Alt-Shift-T), then pick the
remark: open draft ... entry.
:notice: If the draft buffer has already been openened before, it won't pick up changes to the draft file. Use the command palette to reload the file: editor: reload file.
VS Code only starts language servers via extensions. This repo includes a VS
Code extension at vscode-extension/remark-lsp.
- Open
vscode-extension/remark-lspin VS Code. - Run
npm installandnpm run compile(ornpm run watch). - Press
F5to launch the Extension Development Host.
The extension starts remark lsp from your PATH by default. You can customize
it via settings:
remark.path: explicit path to theremarkbinary.remark.lspArgs: extra args passed toremark lsp(string or string array).remark.languages: language ids to attach to (default:["*"]for all).
You can also pass extra LSP flags via REMARK_LSP_ARGS, for example:
REMARK_LSP_ARGS="--no-inlay-hints" codeUse the light bulb (quick fixes) to add comments:
- Remark: Add line comment
- Remark: Add file comment
This opens a multi-line editor panel (no escaping needed). Line comments always
target the new side. The extension uses remark add so the CLI handles syncing.
Use the command palette for either:
- Remark: Open Draft (command), or
- Tasks: Run Task →
remark: open draft(task)
If you are not using the extension, create a project-local tasks file at
.vscode/tasks.json:
{
"version": "2.0.0",
"tasks": [
{
"label": "remark: open draft",
"type": "shell",
"command": "bash",
"args": ["-lc", "gitdir=$(git rev-parse --git-dir) && code \"$gitdir/remark/draft.md\""]
}
]
}Notes:
- Requires the
codeCLI to be on your PATH (extension tasks do not). - The draft file is inside
.git, so it won’t show up in git status.
Workflow:
- Add comments via the quick fix UI or
remark add. - Run Tasks: Run Task →
remark: open draft. - Edit and save;
remarkwill sync draft changes on the next command or prompt.
h/lor←/→: switch focus between Files and Diff1/2/3/4: switch view all / unstaged / staged / basei: cycle diff mode decorated → side-by-side → unifiedH: show/hide diff popup (unified hunk for current cursor position)R: reload file list↑/↓,j/k: move selection (focused pane)PgUp/PgDn,Ctrl+U/Ctrl+D: page up/down (focused pane)Ctrl+N/Ctrl+P: next/prev unreviewed file (diff pane)v: toggle reviewed (selected file)c: add/edit comment (file header or commentable line)d: delete comment (file header or commentable line)r: resolve/unresolve commentp: open prompt editorEsc: dismiss overlay or quit?: help (press?orEscagain to close)
Enter: newlineShift+Enter: accept comment and close (Ctrl+Sfallback)Esc: cancel editor
Enter: newlineShift+Enter: copy prompt and close (Ctrl+Sfallback)Esc: close prompt editor
Shift+Enter is only distinguishable if your terminal sends modified Enter. Enable the kitty
keyboard protocol in your terminal emulator (for WezTerm, set enable_kitty_keyboard = true)
and avoid overriding Shift+Enter with a raw SendString binding. Ctrl+S is supported as a
fallback for terminals that don't report modified Enter.
remark supports:
- File-level comments: select the file header row at the top of the diff and press
c. - Line comments:
- Added/context lines attach to new (right) line numbers.
- Removed lines attach to old (left) line numbers.
Reviews are stored as git notes under a configurable ref (default refs/notes/remark).
Notes are attached to synthetic object ids derived from:
- the current
HEADcommit id - the review mode (
allfor worktree reviews, orbase:<ref>for base comparisons) - the file path
This means each reviewed file gets its own note, and when you create a new commit you naturally get a fresh set of notes for that commit.
Each per-file note is markdown with an embedded JSON block:
- JSON contains:
- a file-level comment (optional)
- line comments keyed by
(old|new, 1-based line_number)
The LLM prompt is generated by collating all per-file notes for the current view.
Per-file notes also store a reviewed flag to persist the reviewed state in the file tree.
When copying a prompt (Shift+Enter in the prompt editor, or remark prompt --copy):
remarktries the desktop clipboard viacopypasta(works on Wayland/X11/macOS/Windows depending on environment).- If that fails (common in headless/SSH sessions), it falls back to OSC52, which asks your terminal emulator to place the text into your local clipboard.
- Comments are currently file-level + line-level only; no multi-line / hunk-level comments.
- Comments are anchored to old/new line numbers (they can drift as the diff changes).
- UI styling is intentionally simple (especially around file/hunk headers).
Core libraries used:
ratatui+crosstermfor TUI rendering/inputgixfor repository access (status, trees, objects, notes ref updates)gix-difffor unified diff generationverdant+verdant-parsers-git+verdant-themesfor syntax highlightinghyperpolyglotfor language detectioncopypasta(desktop clipboard) + OSC52 fallback (remote clipboard-friendly)
MIT (see LICENSE).