Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/clear-sides-travel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"diffhub": patch
---

Preserve whether each copied review comment refers to the old or new side of a
diff, so agent review prompts identify the intended version of the file.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ diffhub cmux --repo ~/Code/mblode/cmux

The default view is `touched`: `HEAD` versus the working tree, including untracked files. Merge-base scopes (`all`, `committed`) are available from the status bar, and `--base` still selects the comparison branch, preferring `origin/main` so unpushed commits show up. A watcher marks updates available; press `r` to refresh. `diffhub cmux` uses a repo-derived port in 20000–29999 instead of 2047.

For a complete review loop, see [Review agent-written code in cmux](apps/docs/review-with-codex.mdx).

## Commands

| Command | Description |
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/components/DiffApp.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@ describe("DiffApp review flow", () => {

await waitFor(() => {
expect(writeText).toHaveBeenCalledWith(
expect.stringContaining("- [must-fix] **src/b.ts:12**: Investigate this diff"),
expect.stringContaining("- [must-fix] **src/b.ts:12** (new side): Investigate this diff"),
);
});
expect(fetchMock).toHaveBeenCalledWith(
Expand Down
37 changes: 37 additions & 0 deletions apps/cli/lib/comments.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { join } from "node:path";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { addComment, deleteComment, readComments } from "./comments";

import { exportCommentsAsPrompt } from "./export-comments";

const tempPaths: string[] = [];

const createTempRepo = (): string => {
Expand Down Expand Up @@ -64,3 +66,38 @@ describe("comments store", () => {
expect(readComments()).toStrictEqual([secondComment]);
});
});

describe("review prompt export", () => {
it("distinguishes old and new code at the same file and line", () => {
const common = {
body: "Keep the validation",
createdAt: "2026-09-05",
file: "src/save.ts",
lineNumber: 12,
tag: "[must-fix]" as const,
};
const prompt = exportCommentsAsPrompt([
{ ...common, id: "old", side: "left" },
{ ...common, id: "new", side: "right" },
]);
expect(prompt).toContain("[must-fix] **src/save.ts:12** (old side): Keep the validation");
expect(prompt).toContain("[must-fix] **src/save.ts:12** (new side): Keep the validation");
});

it("keeps file comments free of a misleading line or side", () => {
expect(
exportCommentsAsPrompt([
{
body: "Split this module",
createdAt: "2026-09-05",
file: "src/save.ts",
id: "file",
lineNumber: 0,
side: "right",
tag: "",
},
]),
).toContain("**src/save.ts**: Split this module");
expect(exportCommentsAsPrompt([])).toBe("No comments.");
});
});
3 changes: 2 additions & 1 deletion apps/cli/lib/export-comments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ export const exportCommentsAsPrompt = (comments: Comment[]): string => {
const lines = comments.map((c) => {
const tag = c.tag ? `${c.tag} ` : "";
const loc = c.lineNumber > 0 ? `:${c.lineNumber}` : "";
return `- ${tag}**${c.file}${loc}**: ${c.body}`;
const side = c.lineNumber > 0 ? ` (${c.side === "left" ? "old" : "new"} side)` : "";
return `- ${tag}**${c.file}${loc}**${side}: ${c.body}`;
});
return `## Code Review Comments\n\nPlease address the following:\n\n${lines.join("\n")}`;
};
6 changes: 4 additions & 2 deletions apps/docs/features/comments.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ Click any line in the diff to open the comment input. Type your note and press E

## Copy as prompt

Click the copy button in the status bar to copy all comments as a formatted markdown prompt. This is useful when you want to hand review feedback to an agent or share it with a teammate.
Click the copy button in the status bar to copy all comments as a formatted markdown prompt. A successful copy clears the saved comments, so paste the prompt somewhere you can keep it before copying anything else. This is useful when you want to hand review feedback to an agent or share it with a teammate.

The prompt includes the file, line number and old or new side for each line comment. A comment on deleted code refers to the old side, so its line number may not exist in the current file. Ask the agent to inspect the diff before applying the feedback.

## Storage

Comments are stored in `.git/diffhub-comments.json` inside the target repository. The file is gitignored by default and stays there between sessions.
Comments are stored as `diffhub-comments.json` in the Git directory of the target checkout. A linked worktree has its own Git directory, so its comments stay separate from the main checkout. Comments persist between sessions and are not committed with your source files.
2 changes: 1 addition & 1 deletion apps/docs/meta.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"title": "Documentation",
"pages": ["index", "installation", "usage", "configuration"]
"pages": ["index", "usage", "review-with-codex", "features"]
}
55 changes: 55 additions & 0 deletions apps/docs/review-with-codex.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
title: Review agent-written code in cmux
description: Inspect a branch in DiffHub, give Codex precise line comments, and verify the revision before committing.
---

## Open the checkout the agent changed

Run this in the same Git checkout or worktree that Codex is editing:

```bash
npx diffhub@latest cmux
```

DiffHub opens a browser split in cmux. This integration requires cmux at `/Applications/cmux.app` on macOS. For another terminal, use `npx diffhub@latest` to open the viewer in your browser.

The default view shows changes against HEAD, including untracked files. If the agent already committed its work, choose the `all` or `committed` scope in the status bar. Otherwise a clean working tree can hide the very changes you came to review.

To compare with a particular branch, use its actual name:

```bash
npx diffhub@latest cmux --base main
```

Replace `main` if your repository uses another base. Select a merge-base scope in the viewer to review that comparison.

## Review one behaviour at a time

Start with the requirement. Follow the change through its caller, implementation and test. Use the file filter to narrow a large patch and `s` to switch between split and unified views.

Leave comments that describe the failure and the expected behaviour. For example:

> [must-fix] This removes the empty-input guard. Preserve the guard and add a regression for an empty submission.

A line number on the old side refers to the comparison version, not necessarily the file currently on disk. Include that context when handing off feedback, especially with older installed versions of DiffHub.

## Hand the review to Codex

Copy the comments as a prompt, then add this instruction:

```text
Address these review comments in this checkout. Inspect the diff and the
surrounding code first. Preserve unrelated changes. Run the relevant tests
and report what passed, what failed and any comment you could not resolve.
Do not commit, push or deploy.
```

Keep the request tied to the checkout you reviewed. A different worktree can have the same filename and different code.

## Verify the revision

When DiffHub reports updates, press `r` to refresh. Revisit each comment against the revised patch, then run the affected user journey and the repository's required checks.

An empty comment list or a successful test run alone does not prove that the original behaviour is fixed. Check the trigger that motivated the comment. Commit only after the revised behaviour and patch match your intent.

[Comment controls and storage](./features/comments) explain how review notes persist. [Usage](./usage) covers the comparison modes and CLI options.
Loading