Skip to content

feat(review): review targets, merge-base resolution and a rubric - #529

Merged
emal-avala merged 7 commits into
mainfrom
feat/review-targets
Jul 27, 2026
Merged

feat(review): review targets, merge-base resolution and a rubric#529
emal-avala merged 7 commits into
mainfrom
feat/review-targets

Conversation

@emal-avala

Copy link
Copy Markdown
Member

First slice of #524.

Summary

/review sent one sentence into the running conversation:

Some("review") => CommandResult::Prompt(
    "Review the current git diff. Look for bugs, security issues, \
     code quality problems, and suggest improvements."
        .to_string(),
),

No target, no rubric, no way to say what it had reviewed.

Targets

uncommitted (the default — "review this" almost always means the working tree), base <branch>, commit <sha>, or free text. An unrecognised argument becomes instructions rather than an error, so /review the auth changes works instead of printing usage.

Prompts say how to find the change

The prompt does not embed a diff. It gives the reviewer the resolved merge-base SHA to diff against, or the commit to show. That is the whole point: the reviewer has tools, so it can open the code around a hunk, read the callers, and check whether a test exists — the difference between a real finding and a plausible one.

Merge-base resolution prefers the upstream

git merge-base HEAD main@{upstream} before git merge-base HEAD main. On a fork, main is often stale while origin/main is what the change will actually merge into — diffing against the stale one presents unrelated commits as if they were part of the change under review.

When git cannot answer at all, the prompt tells the reviewer to work the merge base out itself rather than failing. A review that runs with a vaguer instruction beats no review.

The rubric is mostly exclusions

What stops a reviewer being useful is volume, not missing checks — speculative findings, style nits and pre-existing issues drown the two that matter. So the rubric spends its length on what not to report:

  • only defects this change introduced
  • nothing that rests on unstated assumptions about intent
  • no rigour the surrounding code does not already have
  • "If nothing meets that bar, say so and report no findings"
  • name the code provably affected; "might break elsewhere" is not a finding
  • "Do not suggest a fix you have not reasoned through — an incorrect suggestion costs the author more than no suggestion"

It defers to AGENTS.md and scoped instruction files over generic advice, and asks for [P0][P3] priorities plus an overall verdict.

Overridable per project via .agent/review-rubric.md (wiring for the override is in a later slice; the constant is already the single source).

Verification

9 tests: target parsing including the free-text fallback and keyword-without-argument case; every prompt naming a runnable command and keeping the findings instruction; commit sha/title propagation; an unresolvable base still producing a usable prompt; a real base resolving to a concrete SHA; hint text; and the_rubric_states_its_exclusions, which fails if the rubric loses the guidance that makes it a rubric rather than a request.

cargo test --workspace --all-targets green apart from the 3 bwrap_* tests, which fail on this host with setting up uid map: Permission denied and pass in CI. clippy --all-targets -- -D warnings and fmt --check clean.

Still to come in #524

The isolated subagent with fresh history (the property that makes a review trustworthy), structured findings + JSON parsing, POST /review on agent serve, and the project rubric override.

/review sent one sentence — 'Review the current git diff. Look for bugs
...' — into the running conversation. It had no target, no rubric, and no
way to say what it had reviewed.

Targets: uncommitted (the default, since that is what 'review this'
usually means), base <branch>, commit <sha>, or free text. An
unrecognised argument becomes instructions rather than an error, so
'/review the auth changes' works.

Prompts tell the reviewer how to FIND the change rather than embedding a
diff: the resolved merge-base SHA to diff against, the commit to show.
That is what lets the reviewer open the code around a hunk and check the
callers, which is the difference between a real finding and a plausible
one.

Merge-base resolution prefers the base branch's upstream. On a fork
'main' is often stale while 'origin/main' is what the change will merge
into, and diffing the stale one presents unrelated commits as part of the
change. When git cannot answer, the prompt tells the reviewer to work the
merge base out itself — a review with a vaguer instruction beats no
review.

The rubric is mostly exclusions, because what stops a reviewer being
useful is volume rather than missing checks: only defects this change
introduced, nothing speculative, no rigour the surrounding code does not
already have, and 'if nothing meets the bar, report no findings'. It
defers to AGENTS.md and scoped instruction files over generic advice.

First slice of #524. The constrained subagent and structured findings are
separate pieces.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6d6f5345c5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/cli/src/commands/mod.rs Outdated
let target = review::parse_target(args);
let cwd = std::path::PathBuf::from(&engine.state().cwd);
let resolved = review::resolve(target, &cwd);
println!("Reviewing {}…", resolved.hint);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Surface the review target in the modern TUI

In the default modern TUI, slash-command stdout is captured, but the CommandResult::Prompt branch in ui/modern/run.rs enqueues only the prompt and discards the captured text. Consequently, Reviewing changes vs main… (and the equivalent commit/custom hint) is never shown to interactive users, defeating the new target feedback. Pass the hint through the prompt-result path or add it directly to the transcript rather than printing it here.

Useful? React with 👍 / 👎.

The modern TUI captured slash-command stdout but only replayed it for
CommandResult::Handled, so `/review`'s "Reviewing changes vs main…"
line — the only signal of which target was resolved — never reached
interactive users.

Emit the captured text for every outcome, before the turn is enqueued
so the note precedes the turn it explains.
@emal-avala

Copy link
Copy Markdown
Member Author

@codex review — P2 fixed in 76b341c: the modern TUI's slash bridge captured stdout but only replayed it on the Handled arm, so /review's Reviewing <target>… line was discarded on the Prompt path. Extracted apply_command_result and moved the captured-output replay ahead of the match, so every outcome surfaces what the command printed and the note lands before the turn it explains. Regression test a_prompt_returning_command_still_surfaces_what_it_printed drives the Prompt path end-to-end (verified failing without the fix) and asserts ordering plus ANSI stripping. Full gate green; only the 3 known environmental bwrap_* sandbox failures.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 76b341c815

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/lib/src/review/mod.rs Outdated
ReviewTarget::BaseBranch { base } => match merge_base(cwd, base) {
Some(sha) => format!(
"Review the changes this branch adds on top of '{base}'. The merge base is \
{sha}. Run `git diff {sha}` to see exactly what would merge into {base}, then \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Diff the merge base against HEAD

When /review base <branch> runs with staged or unstaged changes, git diff {sha} compares the merge base to the working tree, so the review includes local changes that would not merge with the branch and can produce findings outside the requested scope. This follows the one-commit form shown by git diff -h (git diff [<commit>]); use the two-commit form such as git diff {sha} HEAD to inspect only what the branch adds.

Useful? React with 👍 / 👎.

`git diff <merge-base>` is the one-commit form: it compares the base to
the working tree, so a `/review base <branch>` run with staged or
unstaged edits reviewed those too and reported findings outside the
requested scope. Use `git diff <merge-base> HEAD` in both the resolved
and the degraded prompt.
@emal-avala

Copy link
Copy Markdown
Member Author

@codex review — P1 fixed in bcf5165: git diff {sha} was the one-commit form, which diffs the merge base against the working tree, so a /review base <branch> run with staged or unstaged edits pulled those into scope. Both base-branch prompts now name two commits — resolved: git diff {sha} HEAD; degraded: git merge-base HEAD {base} then git diff <merge-base> HEAD. Regression test a_base_review_diffs_the_merge_base_against_head covers both paths and asserts the bare one-commit form is gone. Full gate green; only the 3 known environmental bwrap_* sandbox failures.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bcf5165e06

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +68 to +70
"commit" if !rest.is_empty() => ReviewTarget::Commit {
sha: rest.to_string(),
title: None,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate commit targets before building the command

When the argument begins with -, this accepts it as the SHA and emits an option-only command; for example, /review commit --no-patch produces git show --no-patch, which succeeds by showing HEAD without its diff, so the reviewer can inspect the wrong commit and return a false clean result. git show -h documents the command as git show [<options>] <object>..., confirming that the leading value is parsed as an option rather than an object. Resolve and validate the supplied value as exactly one commit before constructing the prompt, and report an invalid target instead of allowing Git options or additional arguments.

Useful? React with 👍 / 👎.

`git show [<options>] <object>...` parses a leading dash as an option,
so `/review commit --no-patch` became `git show --no-patch` — which
succeeds, showing HEAD with no diff. The reviewer then inspected a
commit nobody asked for and could return a confident clean result.

Reject anything that is not a single revision word, and resolve a
commit target through `git rev-parse --verify <rev>^{commit}` so an
unknown ref is an error rather than whatever git picks instead. The
merge base keeps its degraded path; a commit does not, since it is the
thing under review. `/review` reports the bad target instead of
spending a turn on it.
@emal-avala

Copy link
Copy Markdown
Member Author

@codex review — P2 fixed in d43c21d: commit targets are now validated before anything is interpolated into a command. validate_revision rejects a leading dash (--no-patch is a git option, not an object) and anything that is not exactly one revision word; validate_commit then resolves through git rev-parse --verify <rev>^{commit}, so an unknown ref is an error rather than a silent git show of HEAD. Fails closed: the merge base keeps its degraded path because it can still be found by the reviewer, but a commit is the thing under review, so /review reports the bad target and returns Handled instead of spending a turn. Base branches get the same single-revision check since they reach git merge-base. Regression test a_commit_target_that_is_not_one_commit_is_rejected covers --no-patch, -p, HEAD --stat and an unknown ref. Full gate green; only the 3 known environmental bwrap_* sandbox failures.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d43c21d079

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/lib/src/review/mod.rs Outdated
Comment on lines +201 to +202
untracked. Use `git status` and `git diff` (including `--staged`) to see them, then read the \
surrounding code as needed. Report prioritized, actionable findings.";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Tell the reviewer to inspect untracked file contents

When the default review contains a newly created, untracked file, these instructions do not expose its contents: git status only lists untracked paths, while neither git diff nor git diff --staged includes files outside the index. This is consistent with git status -h describing -u as “show untracked files” and git diff -h offering only commit/blob/path comparisons. The reviewer can therefore report no findings without reading code that ReviewTarget::Uncommitted explicitly includes; instruct it to enumerate and open every untracked file (or diff each against /dev/null) as well.

Useful? React with 👍 / 👎.

The default target promises staged, unstaged *and* untracked changes,
but no diff shows an untracked file: it is outside the index, so
`git diff` and `git diff --staged` both skip it and `git status` prints
only its path. A whole new file could come back reviewed and clean
without the reviewer opening it.

List them with `git ls-files --others --exclude-standard` and read each
one in full.
@emal-avala

Copy link
Copy Markdown
Member Author

@codex review — P2 fixed in 55aa688: the uncommitted prompt claimed untracked coverage that no command in it delivered — untracked files are outside the index, so git diff and git diff --staged both skip them and git status prints only paths. The prompt now splits the two cases explicitly: the diffs for tracked edits, then git ls-files --others --exclude-standard to enumerate untracked files and read each one in full. Regression test the_uncommitted_prompt_says_how_to_read_untracked_files pins both halves. Full gate green; only the 3 known environmental bwrap_* sandbox failures.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 55aa688b0d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/lib/src/review/mod.rs Outdated
Comment on lines +206 to +207
files. Untracked files appear in neither diff, so list them with `git ls-files --others \
--exclude-standard` and read each one in full. Then read the surrounding code as needed. \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Enumerate untracked files from the repository root

When the agent's working directory is a repository subdirectory, git ls-files --others --exclude-standard is scoped to that prefix, even though git status reports untracked files elsewhere as paths such as ../other/new.rs. Following this prompt therefore omits those files and can return a clean review without reading them. Resolve the repository top level first and enumerate/read untracked paths relative to it.

Useful? React with 👍 / 👎.

Comment thread crates/lib/src/review/mod.rs Outdated
_ => String::new(),
};
format!(
"Review the changes introduced by commit {sha}{named}. Run `git show {sha}` to \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Show merge commits against their first parent

When /review commit targets a two-parent merge commit, plain git show <sha> normally prints only the commit metadata and no patch, so the reviewer is given no changes to inspect. This is reproducible with Git 2.43.0: git show <merge> shows only the merge header, while git show --first-parent <merge> shows the net diff introduced relative to the branch receiving the merge. Use the latter form, or explicitly diff <sha>^1 against <sha>.

Useful? React with 👍 / 👎.

Two ways a review could come back clean without the reviewer seeing the
code:

`git show <merge>` prints a combined diff, which for a routine merge is
empty — a header and nothing to read. `--first-parent` gives the net
diff and is a no-op on ordinary and root commits.

`git ls-files` is scoped to the current prefix, so run from a
subdirectory it omitted untracked files elsewhere in the repository.
The `:/` pathspec covers the whole tree.
@emal-avala

Copy link
Copy Markdown
Member Author

@codex review — both P2s fixed in e7ad124, each verified against git 2.43.0 before changing anything.

Merge commits: on a real merge in this repo, git show <merge> produced 294 lines (header, empty combined diff) vs 3432 for git show --first-parent <merge>. The commit prompt now uses --first-parent, which is byte-identical output on ordinary and root commits (65/65 and 212/212 lines), so it is safe unconditionally.

Untracked files: reproduced the prefix scoping — from sub/deep, plain git ls-files --others --exclude-standard listed only the file in that directory, while -- :/ also listed the one at the repo root. The uncommitted prompt now carries the :/ pathspec.

Regression tests: a_commit_prompt_can_show_a_merge_commit pins the --first-parent form, and the_uncommitted_prompt_says_how_to_read_untracked_files now pins the -- :/ pathspec rather than the bare command. Full gate green; only the 3 known environmental bwrap_* sandbox failures.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e7ad124a61

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/lib/src/review/mod.rs Outdated
None => (args, ""),
};
match head {
"uncommitted" | "working" => ReviewTarget::Uncommitted,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve instructions beginning with the working-tree aliases

When free-form instructions begin with working or uncommitted—for example, /review working on authentication—this arm discards every word after the alias and silently turns the request into an unrestricted working-tree review. This conflicts with the command's free-text fallback and can omit the user's requested focus; recognize these aliases only when rest is empty, or reject trailing arguments instead of dropping them.

Useful? React with 👍 / 👎.

`/review working on authentication` matched the bare `working` alias
and discarded the rest, silently reviewing the whole tree instead of
the part asked about. Match the working-tree aliases only when nothing
follows; anything more falls through to the free-text path that already
exists for exactly this.
@emal-avala

Copy link
Copy Markdown
Member Author

@codex review — P2 fixed in be52032: the working-tree aliases now match only when nothing follows ("uncommitted" | "working" if rest.is_empty()), so /review working on authentication falls through to the free-text path that already exists for it instead of silently widening to the whole tree. Bare /review working and /review uncommitted are unchanged. Regression test a_working_tree_alias_with_more_words_stays_instructions covers both directions. Full gate green; only the 3 known environmental bwrap_* sandbox failures.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Swish!

Reviewed commit: be52032c40

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@emal-avala
emal-avala merged commit 24e982b into main Jul 27, 2026
14 checks passed
@emal-avala
emal-avala deleted the feat/review-targets branch July 27, 2026 08:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant