feat: Add pu diff command to show changes across agent worktrees#85
feat: Add pu diff command to show changes across agent worktrees#852witstudios merged 3 commits intomainfrom
pu diff command to show changes across agent worktrees#85Conversation
After agents finish work, you need to see what code they changed. This adds `pu diff` which computes git diffs for each worktree against its base branch — the missing step between `pu status` and reviewing/merging agent work. Usage: pu diff # diffs for all active worktrees pu diff --worktree X # diff a specific worktree pu diff --stat # file summary instead of full diff pu diff --json # machine-readable output Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughAdds a new diff feature: CLI subcommand, protocol Request/Response types, engine handling to compute per-worktree diffs, and git integration to produce diff text and statistics; includes output rendering and tests. Changes
Sequence DiagramsequenceDiagram
participant CLI as CLI User
participant Cmd as diff::run()
participant Client as Daemon Client
participant Engine as Engine
participant Git as git::diff_worktree()
CLI->>Cmd: invoke diff (worktree, stat, json)
activate Cmd
Cmd->>Cmd: ensure daemon running\nbuild project_root
Cmd->>Client: send Request::Diff(project_root, worktree_id, stat)
deactivate Cmd
activate Engine
Client->>Engine: deliver Request::Diff
Engine->>Engine: read manifest\nselect worktrees
loop per worktree
Engine->>Git: diff_worktree(path, base, stat)
activate Git
Git->>Git: run git diff / merge-base\ncompute --stat & parse
Git-->>Engine: return DiffOutput (diff, files, ins, del)
deactivate Git
end
Engine->>Engine: aggregate DiffResult
Engine-->>Client: send Response::DiffResult
deactivate Engine
activate Cmd
Client-->>Cmd: receive Response::DiffResult
Cmd->>Cmd: validate response\nprint formatted output
Cmd-->>CLI: display results
deactivate Cmd
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@crates/pu-core/src/protocol.rs`:
- Around line 238-244: The tagged IPC wire-format for Request::Diff and
Response::DiffResult changed but the protocol version was not bumped; update the
shared protocol version constant (e.g., PROTOCOL_VERSION or equivalent in
protocol.rs) to a new integer, add a runtime check where messages are
deserialized (before dispatching diff handling) to compare the incoming
message's protocol version against the compiled/expected version, and if they
mismatch immediately return an explicit error/fail-fast path that prevents
dispatching into Diff-related handlers; ensure the version bump is applied
consistently for both request and response handling code paths so older
daemons/clients detect incompatibility early.
- Around line 535-546: WorktreeDiffEntry currently lacks a structured
status/error field so engine.rs stuffs git failures into diff_output; add a new
field (e.g., pub status: WorktreeDiffStatus or pub error: Option<String>) to
WorktreeDiffEntry and define a small enum/option (e.g., WorktreeDiffStatus { Ok,
Error } with an optional error message or Option<String> error_message) that is
serde-serializable (keep rename_all = "snake_case"), then update
crates/pu-engine/src/engine.rs to set this new field on failure instead of
serializing errors into diff_output and reserve diff_output for actual diff text
(set diff_output to "" or None when error), and ensure
files_changed/insertions/deletions are set appropriately for error cases so JSON
clients can distinguish real empty diffs from failures.
In `@crates/pu-engine/src/engine.rs`:
- Around line 2975-2978: The code currently treats a missing worktree directory
(wt_path from wt.path) as “no diffs” by using `continue`, which hides deleted
worktrees; instead, when processing a specific worktree (e.g., the branch
handling `pu diff --worktree <id>`), return an error for that targeted worktree
(propagate Err or add an explicit error entry into the response) so callers can
distinguish a deleted worktree from a clean one; keep best-effort skipping only
for bulk/partial-result paths by detecting that mode and retaining the original
`continue` behavior there.
In `@crates/pu-engine/src/git.rs`:
- Around line 60-82: The diff_worktree function currently compares the worktree
against the tip of base (via run_git_allow_empty using "diff <base>|HEAD"),
which can include upstream commits as base advances and also omits untracked
files; update diff_worktree to (1) compute an explicit merge base using git
merge-base HEAD <base> and use that SHA as the comparison target when a base is
provided (call git via run_git_allow_empty to get the merge-base SHA), (2) if
callers expect uncommitted/untracked files to be included, add the flags
--others --exclude-standard (or document the exclusion) when invoking git diff,
and (3) update the function docstring to state the chosen semantic (merge-base
vs tip-of-base) and whether untracked files are included; keep parse_diff_stat
usage and stat calculation unchanged.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 6a8f27e9-0f8f-4b7f-9e1d-83b82576d3fb
📒 Files selected for processing (7)
crates/pu-cli/src/commands/diff.rscrates/pu-cli/src/commands/mod.rscrates/pu-cli/src/main.rscrates/pu-cli/src/output.rscrates/pu-core/src/protocol.rscrates/pu-engine/src/engine.rscrates/pu-engine/src/git.rs
- Bump PROTOCOL_VERSION to 2 for the new Diff wire-format variants - Add `error: Option<String>` to WorktreeDiffEntry so JSON clients can distinguish git failures from legitimate empty diffs - Use `git merge-base HEAD <base>` instead of diffing against the tip of the base branch, so diffs show only the worktree's own changes - Return an error entry (not silent skip) when `--worktree <id>` targets a missing directory; keep best-effort skipping for bulk queries - Display error entries in human-readable output Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
crates/pu-engine/src/git.rs (1)
57-90:⚠️ Potential issue | 🟠 MajorUntracked files are still invisible to
pu diff.A brand-new file will not appear until it is staged, so
pu diffcan still report “No changes” even when an agent created files in the worktree. The new doc comment documents the limitation, but the feature contract here is to surface worktree changes, not just tracked ones. Consider foldinggit ls-files --others --exclude-standardinto both the rendered diff and the summary counts.To verify, create an untracked file in a throwaway repo. Expected result:
git diff HEADstays empty whilegit ls-files --others --exclude-standardreports the new path.#!/bin/bash set -euo pipefail tmp="$(mktemp -d)" trap 'rm -rf "$tmp"' EXIT git -C "$tmp" init -q git -C "$tmp" config user.name Test git -C "$tmp" config user.email test@example.com printf 'tracked\n' > "$tmp/readme.txt" git -C "$tmp" add readme.txt git -C "$tmp" commit -qm init printf 'new file\n' > "$tmp/new_file.rs" echo '--- git diff HEAD --stat ---' git -C "$tmp" diff HEAD --stat echo '--- git diff HEAD ---' git -C "$tmp" diff HEAD echo '--- untracked files ---' git -C "$tmp" ls-files --others --exclude-standard🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/pu-engine/src/git.rs` around lines 57 - 90, diff_worktree currently ignores untracked files; call run_git_allow_empty with ["ls-files", "--others", "--exclude-standard"] to get untracked paths, update parse_diff_stat usage to include those counts (increment files_changed and insertions/deletions as appropriate or at least files_changed += untracked.len()), and then fold the untracked information into both outputs: for stat mode, append a summary line for untracked files to stat_output before returning; for full-diff mode, append a simple rendered block (e.g. a header and newline-separated untracked paths) to the diff string returned by the run_git_allow_empty(["diff", target], ...) call. Use the existing functions run_git_allow_empty, parse_diff_stat and the diff_worktree return flow to locate where to inject the ls-files call and how to merge the results.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@crates/pu-engine/src/git.rs`:
- Around line 57-82: The bug is that worktrees are being created with the
literal "HEAD" as their stored base_branch which makes diff_worktree (and its
merge-base logic using run_git_allow_empty and target computed from
merge_base/as_deref()) compare the worktree against its own tip and hide
commits; fix by resolving and storing a stable commit SHA (e.g., run git
rev-parse HEAD at creation time) or an explicit upstream branch name when
creating the worktree in the engine (references: the base_branch assignment in
engine.rs and diff_worktree in git.rs) so merge-base(HEAD, base) will compute
correctly, then update creation code to call git rev-parse and persist that SHA
instead of the literal "HEAD", and add a regression test that creates a
worktree, makes commits on the worktree branch, and asserts diff_worktree shows
those commits.
---
Duplicate comments:
In `@crates/pu-engine/src/git.rs`:
- Around line 57-90: diff_worktree currently ignores untracked files; call
run_git_allow_empty with ["ls-files", "--others", "--exclude-standard"] to get
untracked paths, update parse_diff_stat usage to include those counts (increment
files_changed and insertions/deletions as appropriate or at least files_changed
+= untracked.len()), and then fold the untracked information into both outputs:
for stat mode, append a summary line for untracked files to stat_output before
returning; for full-diff mode, append a simple rendered block (e.g. a header and
newline-separated untracked paths) to the diff string returned by the
run_git_allow_empty(["diff", target], ...) call. Use the existing functions
run_git_allow_empty, parse_diff_stat and the diff_worktree return flow to locate
where to inject the ls-files call and how to merge the results.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: b9455366-9595-45fd-b6d9-d9a7d7f2b532
📒 Files selected for processing (4)
crates/pu-cli/src/output.rscrates/pu-core/src/protocol.rscrates/pu-engine/src/engine.rscrates/pu-engine/src/git.rs
When no base is supplied, worktrees were storing the literal "HEAD" as base_branch. After introducing merge-base semantics in diff_worktree, this caused `git merge-base HEAD HEAD` to return the worktree's own tip, hiding all committed changes. Now resolves HEAD to the actual branch name (e.g. "main") at worktree creation time via `git symbolic-ref --short HEAD`, falling back to a SHA for detached HEAD. This ensures diff_worktree correctly computes the fork point and shows only the worktree's own changes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
pu diff— shows what code agents have changed across worktrees by computing git diffs against each worktree's base branchpu spawn→pu status→pu diff→ review → mergeUsage
Human-readable output shows each worktree with its branch, base, change counts, and the full diff:
What's included
pu-core)Request::Diff,Response::DiffResult,WorktreeDiffEntrystruct witherrorfield,PROTOCOL_VERSIONbumped to 2pu-engine)handle_diffhandler,diff_worktreewith merge-base semantics,resolve_base_reffor stable base storagepu-cli)pu diffcommand with--worktree,--stat,--jsonflagsReview feedback addressed
All CodeRabbit review comments have been addressed:
PROTOCOL_VERSIONbumped from 1 → 2 for the new wire-format variantserror: Option<String>toWorktreeDiffEntryso JSON clients can distinguish failures from empty diffs--worktree <id>) now report errors for missing directories; bulk queries skip gracefullydiff_worktreeusesgit merge-base HEAD <base>instead of tip-of-base, showing only the worktree's own changesHEADto actual branch name (e.g.main) to preventmerge-base HEAD HEADfrom returning the worktree's own tipTest plan
Diffrequest with defaults and explicit fieldsWorktreeDiffEntryserialization round-trip (including newerrorfield)parse_diff_stathandles: full stat, insertions-only, deletions-only, emptydiff_worktreeintegration tests: changes present, no changes, stat mode🤖 Generated with Claude Code