Skip to content

Instantly share code, notes, and snippets.

@atondwal
Created June 19, 2026 09:26
Show Gist options
  • Select an option

  • Save atondwal/1caef6c20bf047e46f0b8dd8fab3c645 to your computer and use it in GitHub Desktop.

Select an option

Save atondwal/1caef6c20bf047e46f0b8dd8fab3c645 to your computer and use it in GitHub Desktop.
critique — review a stack of (AI-written) commits by editing the diff in your editor; inline edits + #notes flow back to the agent. Two phases: a rebase-i-style plan, then per-commit review.
#!/usr/bin/env bash
# Critique a commit stack in nvim. The shell only does mechanism: render the
# plan todo, render commits and chain them in a tmux pane, diff a commit's edit,
# resolve a commit's current hash. Claude reads the plan and the per-commit
# edits as *intent* and drives git (restructure, amend) itself — nothing here
# parses what the reviewer wrote.
set -euo pipefail
WORK="${CRITIQUE_DIR:-${TMPDIR:-/tmp}/claude-critique}"
die() { echo "critique: $*" >&2; exit 1; }
m() { awk -v k="$1" '$1==k{print $2; exit}' "$WORK/manifest"; }
base_ref() {
if [ -n "${1:-}" ]; then echo "$1"; return; fi
git rev-parse --abbrev-ref --symbolic-full-name '@{upstream}' 2>/dev/null && return
for b in main master; do
git rev-parse --verify -q "$b" >/dev/null && { echo "$b"; return; }
done
die "no base given and no @{upstream}/main/master found — pass a base ref"
}
render_one() {
local sha="$1" n="$2" total="$3"
printf '# critique: commit %s of %s %s\n' "$n" "$total" "$sha"
printf '# edit code/message inline; add notes on their own line starting with #\n'
printf '# :wq to send this commit and move to the next.\n\n'
git show -s --format='%B' "$sha"
git show -p --format='' "$sha"
}
# render the given commits (full shas, in order) and chain them in one pane
open_commits() {
local base="$1"; shift
local total=$#; [ "$total" -gt 0 ] || { echo "no commits to review"; return; }
mkdir -p "$WORK"; rm -f "$WORK"/c*.diff "$WORK"/c*.orig
{ echo "base $base"; echo "branch $(git rev-parse --abbrev-ref HEAD)"; } > "$WORK/manifest"
local n=0 nn pos chain="" sha
for sha in "$@"; do
sha="$(git rev-parse "$sha")"
n=$((n + 1)); nn="$(printf '%02d' "$n")"
pos="$(git rev-list --reverse "$base"..HEAD | grep -n "^$sha" | cut -d: -f1)"
render_one "$sha" "$n" "$total" > "$WORK/c$nn.diff"
cp "$WORK/c$nn.diff" "$WORK/c$nn.orig"
printf '%s %s %s\n' "$nn" "$pos" "$sha" >> "$WORK/manifest"
chain="$chain nvim '$WORK/c$nn.diff' +'setf diff'; tmux wait-for -S critique-$nn;"
done
tmux split-window -h "$chain true"
echo "opened $total commit(s) for review; channels critique-01 .. critique-$(printf '%02d' "$total")"
}
cmd_plan() {
[ -n "${TMUX:-}" ] || die "not inside tmux — start this from a tmuxified session"
git rev-parse --git-dir >/dev/null 2>&1 || die "not in a git repo"
local b; b="$(git rev-parse "$(base_ref "${1:-}")")"
local shas; shas="$(git rev-list --reverse "$b"..HEAD)"
[ -n "$shas" ] || die "no commits in $b..HEAD"
rm -rf "$WORK"; mkdir -p "$WORK"
{ echo "base $b"; echo "branch $(git rev-parse --abbrev-ref HEAD)"; } > "$WORK/manifest"
{
printf '# critique plan — %s onto %s\n' "$(git rev-parse --abbrev-ref HEAD)" "$(git rev-parse --short "$b")"
printf '# verbs: review (open & edit inline) pick (keep as-is) drop (remove)\n'
printf '# reorder by moving lines. delete a line to drop it too.\n'
printf '# anything else is free-form: notes, questions, "squash these", "split this" — Claude reads it.\n'
printf '# :wq to begin.\n'
for sha in $shas; do
printf 'review %s %s\n' "$(git rev-parse --short "$sha")" "$(git show -s --format='%s' "$sha")"
done
} > "$WORK/plan.txt"
cp "$WORK/plan.txt" "$WORK/plan.orig"
tmux split-window -h "nvim '$WORK/plan.txt'; tmux wait-for -S critique-plan"
echo "opened plan in a tmux pane; channel=critique-plan; read $WORK/plan.txt after :wq"
}
cmd_open() {
[ -n "${TMUX:-}" ] || die "not inside tmux"
git rev-parse --git-dir >/dev/null 2>&1 || die "not in a git repo"
local base; base="$(git rev-parse "$1")"; shift
open_commits "$base" "$@"
}
cmd_start() {
[ -n "${TMUX:-}" ] || die "not inside tmux"
git rev-parse --git-dir >/dev/null 2>&1 || die "not in a git repo"
local b; b="$(git rev-parse "$(base_ref "${1:-}")")"
local shas; shas="$(git rev-list --reverse "$b"..HEAD)"
[ -n "$shas" ] || die "no commits in $b..HEAD"
open_commits "$b" $shas
}
cmd_diff() {
local nn="$1"
[ -f "$WORK/c$nn.orig" ] || die "no commit $nn in this review"
diff -u "$WORK/c$nn.orig" "$WORK/c$nn.diff" || true
}
# current hash at the reviewed commit's stack position (earlier amends shift it)
cmd_resolve() {
local nn="$1" base pos
base="$(m base)"
pos="$(awk -v n="$nn" '$1==n{print $2; exit}' "$WORK/manifest")"
git rev-list --reverse "$base"..HEAD | sed -n "${pos}p"
}
case "${1:-}" in
plan) shift; cmd_plan "${1:-}";;
open) shift; cmd_open "$@";;
start) shift; cmd_start "${1:-}";;
diff) shift; cmd_diff "$1";;
resolve) shift; cmd_resolve "$1";;
*) die "usage: review.sh plan [base] | open BASE SHA... | start [base] | diff NN | resolve NN";;
esac
name critique
description Review a commit stack in nvim, rebase-i style — first a plan pass (review/pick/drop + reorder), then edit each chosen commit's diff inline while Claude amends it behind you. Use when the user wants to review a diff/stack in their editor, "critique" or "edit the patch", plan/reorder/drop commits before reviewing, give feedback by editing commits, or hand back review notes for Claude to apply.

Critique

Local Google-Critique loop in two phases. Plan first (what to review, drop, reorder), then review the survivors one commit at a time while Claude amends each behind you.

Two principles:

  • The edited file is a message, not a patch. Never git apply it. The user rewrites +/- lines, mangles hunks, and adds notes freely; line counts and hunk headers need not stay honest. Read the orig→edited delta as intent and implement it cleanly.
  • Structure is settled before code review. Drops and reorders happen in the plan pass, so the per-commit review runs over a stack that won't shift under the reviewer — no throwaway edits to commits that later get dropped or moved.

Arguments

/critique [base] [plan|no_plan] — both optional, in any order; parse them yourself:

  • base — any commit/ref to review onto (main, HEAD~5, a sha). Default: @{upstream}, then main/master. Pass it through to whichever review.sh command you run.
  • modeplan (default) runs the two-phase flow below. no_plan skips the plan pane: go straight to start [base] and continue from step 4. Use it for a small stack you just want to read top to bottom.

Procedure

  1. (plan mode) Open the plan:

    bash ~/.claude/skills/critique/review.sh plan [base]

    Opens a rebase--i-style todo in a tmux pane — one line per commit (oldest at top), each review by default. The user sets verbs (review = open & edit inline, pick = keep as-is, drop = remove) and reorders by moving lines. base defaults to @{upstream}, then main/master. Signals critique-plan on :wq.

  2. Wait for the plan as a background call: tmux wait-for critique-plan.

  3. Read the plan yourselfcat "$WORK/plan.txt" (diff against plan.orig to see just what changed). Nothing parses it; interpret it like any message:

    • verbs review/pick/drop, line order, and deleted lines give you the kept set and its order.
    • free-form lines — notes, questions, "squash these two", "split this", "reword: …" — are intent too. Act on them, including ops that have no verb.

    Restructure the stack to match by cherry-picking the kept commits onto base in plan order:

    base=$(awk '/^base/{print $2}' "$WORK/manifest"); branch=$(git branch --show-current)
    git checkout -q --detach "$base"
    git cherry-pick <kept-sha-in-plan-order>...     # drops omitted; squash/split by hand
    git branch -f "$branch" HEAD && git checkout -q "$branch"

    Resolve any cherry-pick conflict (dependent commits reordered); if you can't, git cherry-pick --abort, restore the branch, and tell the user. Verify intermediates still parse. Then open the commits marked review:

    bash ~/.claude/skills/critique/review.sh open "$base" <review-sha-in-stack-order>...

    (In no_plan mode you skip steps 1–3 and just start [base].)

  4. Pre-arm one background wait per review commit, all up front: tmux wait-for critique-01critique-NN.

  5. When critique-NN fires, read that commit and apply it (see Applying):

    bash ~/.claude/skills/critique/review.sh diff NN
    • any line the user added that starts with # (banner/headers aside) = a reviewer note. Act on it; answer questions rather than guessing.
    • changed/added +/- lines = the code they want. Implement cleanly, don't paste diff text.
    • edits in the message block = rewrite that commit's message.

    Apply in ascending order — amending NN rebases its descendants, so NN lands before NN+1.

  6. After the last commit, summarize per commit what changed and which notes you addressed.

Applying

Amend the commit the feedback targets — don't rebuild the branch:

branch=$(git branch --show-current)
C=$(bash ~/.claude/skills/critique/review.sh resolve NN)   # current hash at position NN
git checkout -q --detach "$C"
#   ...edit the working tree to that commit's desired content...
git add -A
git commit -q --amend -m "<message>"        # --amend keeps the original author + author-date
Cnew=$(git rev-parse HEAD)
git checkout -q "$branch"
git rebase -q --onto "$Cnew" "$C"           # replays descendants; their author-dates survive

resolve NN re-derives the hash each time because earlier amends shift it. If C is already the tip, just git commit --amend in place.

A note like "drop this" during review means remove the commit: tip → git reset --hard HEAD~1; otherwise git rebase -q --onto "$C^" "$C" "$branch".

Replay conflicts are expected when your edit and a descendant touch nearby lines. Resolve each file so the descendant's change sits on top of your amended version, git add, then git rebase --continue. Never leave a rebase in progress: if you can't resolve cleanly, git rebase --abort, leave the stack untouched, and tell the user which commit needs a manual call. Watch coupled changes — verify every intermediate commit still parses, not just the tip.

Notes

  • Requires a tmuxified session ($TMUX set) and a git repo.
  • Override the scratch dir with CRITIQUE_DIR (default $TMPDIR/claude-critique).
  • One review at a time (fixed channels + scratch dir; plan/start wipe it).
  • No squash/split/reword verbs — write them as free-form plan lines and Claude does the surgery by hand (combine, partition, or rewrite the message). The shell never parses the plan, so any wording works.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment