| name | glean-review |
| description | Reading and responding to comments in an open glean review (the neovim git diff reviewer). Use this when the user pastes a glean review snippet, asks you to answer review comments, or mentions a `Glean:` buffer. |
glean review comments
glean renders a git diff into a neovim buffer. The human leaves comments on
lines; you answer them. Drive it with the nvim_lua tool through
require("glean.api") — everything crosses the boundary as plain strings and
numbers, so each call is self-contained and there is no handle to keep.
The model
Comments live in the repository, not in the review, so every call below also
works with no review open — address the repo instead of a session.
A comment has exactly two slots:
text — the human's words. You never edit or delete this. You may leave
your own comment with add_comment, but a human's comment is theirs.
reply — your answer. At most one per comment, and replying again replaces
the previous one, so re-running over the same review is idempotent. reply
is nil when the comment is unanswered — that is your work queue.
Each comment also reports id, path, lnum (where it currently resolves),
side ("new" for adds/context, "old" when every captured line is a
deletion), content (the block of lines captured when the comment was written,
as a list), code (the same block joined), state, outdated and origin.
state is where the comment resolves right now:
"diff" — its lines are in the review's diff; lnum is the diff-side line.
"file" — its lines are not in the diff but are in the working-tree file;
lnum is the file line. The comment is live and correctly located, just on
lines this review does not touch. (Repo-mode calls, which have no diff,
report "file" for everything that still matches.)
"outdated" — the block matches neither, i.e. the code moved out from under
the comment. lnum is only the last place it was seen; say so in your reply
rather than guessing. outdated is the boolean form of this state.
origin is { sha, dirty }: which version of the file the human was looking at
when they wrote the comment. dirty = true means "that commit plus uncommitted
edits". When a comment is outdated, origin.sha is what to git show to see
the code they meant.
Recovering ids from a paste
Two ids address any comment, and both are visible in the review buffer, so a
pasted snippet is a sufficient address.
- Session id — in the buffer name:
Glean:g2 glean main [8df5247a..09997456]
→ the session is "g2". Pass nil when only one review is open.
- Comment id — the bracketed token that opens a comment row:
💬 [7] why is this needed? inline in the diff, or [7] L51 <code preview> in the
comments summary at the bottom → the comment is 7 (a number, not a string).
Your reply renders beneath the comment as an indented ↳ block.
Entry points
List the open reviews:
return require("glean.api").sessions()
List comments (session may be nil with one review open):
return require("glean.api").comments("g1")
Just the ones needing an answer, optionally scoped to a file:
return require("glean.api").comments("g1", { unanswered = true, path = "lua/glean/init.lua" })
Answer one, and clear an answer:
return require("glean.api").reply("g1", 7, "Yes — the guard is needed because ...")
return require("glean.api").unreply("g1", 7)
With no review open — or to address a repo rather than a review — pass
{ repo = "/path/to/repo" } in the session slot (repo defaults to the cwd):
local api = require("glean.api")
api.comments({ repo = "/path/to/repo", unanswered = true })
api.reply({ repo = "/path/to/repo" }, 7, "...")
api.unreply({ repo = "/path/to/repo" }, 7)
Leave your own comment on a file, with or without a review open. path is
repo-relative and lnum/end_lnum are working-tree line numbers, so comment on
what you just read off disk. The captured lines become the comment's identity,
so it follows the code as it moves. Returns the new comment id:
return require("glean.api").add_comment({
repo = "/path/to/repo", path = "lua/glean/init.lua", lnum = 51, end_lnum = 53,
text = "This retries forever if the remote is down.",
})
Your comment shows up as a 💬 sign in the human's file buffer and in any open
review. Use it sparingly, for findings they asked for — it is their review queue.
Replies are undoable (u in the review buffer), persist to glean's store, and
re-render immediately, so the human watches your answers land live.
Hunks
The same api exposes the review body, so you can read the diff and mark it seen
the way the human does with m.
return require("glean.api").hunks("g1", { mode = "combined", path = "lua/**/*.lua", limit = 20 })
mode — "combined" (default; the whole base..target diff, "what changed
overall") or "commits" (the same review commit by commit). Independent of
the scope the human is viewing, so asking for one does not disturb their
buffer.
path — a glob over the file path ("lua/**/*.lua", "*.txt").
seen — true / false to filter; omit for both.
limit (default 20) and cursor — paging. The result is
{ hunks, cursor, total }; pass the returned cursor back to get the next
page and stop when it comes back nil. total counts the matches after the
cursor, ignoring limit.
Each hunk is { id, mode, sha (commits mode), path, kind, header, old_start, old_count, new_start, new_count, seen, adds, dels, unseen_lines, lines }, and
each entry of lines is { i, kind, lnum, side, text, seen }. i is the line's
address within the hunk; seen is false for context lines, which are not
markable. seen on the hunk is true only when every changed line is seen.
id is positional ("b:000002:000001"), so it is only valid for the review as
it stands now: page and then act, don't stash ids. mark re-resolves and errors
rather than marking the wrong hunk.
Mark whole hunks, or specific lines within one:
local api = require("glean.api")
api.mark("g1", id)
api.mark("g1", { id = id, lines = { 3, 4 } })
api.mark("g1", { id1, id2 })
api.mark("g1", id, false)
Returns { hunks, lines } — selectors applied, and identities whose seen state
actually flipped (lines = 0 means it was already in that state). Marks are
undoable with u, persist, and re-render live, exactly like the human's.
Only mark when the human asks you to triage; seen-ness is their reading
progress, not yours.
Errors
Every call errors loudly rather than no-opping:
- no review open and no
repo → the call falls back to the repo containing the
cwd; if that is not a git work tree it errors. Do not open a review yourself —
ask the human, so you answer exactly what they are looking at.
- several reviews open and no
session → the error lists the candidate ids with
their repo and range; pick one.
- unknown comment id → re-list with
comments(); ids are per repo.
add_comment with an unknown path, an out-of-range line or empty text errors.
reply with an empty or non-string text errors; use unreply to clear.
Working a review
comments(session, { unanswered = true }) to get the queue.
- For each, read the code around
path:lnum before answering — code is
only the anchored block, not the surrounding context.
reply(session, id, ...) one at a time; keep answers short, since they
render inline in the diff.