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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## 0.8.2 - Unreleased

- Fixed diff-scoped review, CI, and revalidation to include both paths of committed renames, preventing features mapped to the old path from being silently skipped.

## 0.8.1 - 2026-09-13

**Highlights:** Nested-project repairs stop tripping over sibling changes, and failed patch attempts keep the edits a provider already wrote.
Expand Down
7 changes: 4 additions & 3 deletions docs/code-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,10 @@ clawpatch review --feature-list /tmp/features.txt
### --since <ref>

Restrict review to features whose owned or context files have changed in
`git diff --name-only --relative <ref>...HEAD`. Paths are compared relative to
the selected project root, so `--root` may point at a subdirectory inside a
larger Git repository. Useful for CI:
`git diff --no-renames --name-only --relative <ref>...HEAD`. Committed renames
include both the old and new paths, so features mapped to either path remain
eligible. Paths are compared relative to the selected project root, so `--root`
may point at a subdirectory inside a larger Git repository. Useful for CI:

```bash
clawpatch review --since origin/main # review what this branch changed
Expand Down
33 changes: 32 additions & 1 deletion src/git.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { mkdtemp, rm, writeFile } from "node:fs/promises";
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, it } from "vitest";
Expand All @@ -12,6 +12,37 @@ afterEach(async () => {
});

describe("Git machine output", () => {
it.each(["true", "copies", "false"])(
"includes both committed rename paths with diff.renames=%s",
async (renames) => {
const root = await gitFixture();
await git(root, ["config", "diff.renames", renames]);
await git(root, ["mv", "base.txt", "renamed.txt"]);
await git(root, ["commit", "-qm", "rename"]);

expect(await changedFilesSince(root, "HEAD~1")).toEqual(new Set(["base.txt", "renamed.txt"]));
},
);

it("scopes both sides of committed renames to a nested project", async () => {
const root = await gitFixture();
const project = join(root, "app");
await mkdir(project);
await writeFile(join(project, "outgoing.txt"), "outgoing\n");
await writeFile(join(project, "before.txt"), "internal\n");
await git(root, ["add", "."]);
await git(root, ["commit", "-qm", "nested project"]);
await git(root, ["config", "diff.renames", "true"]);
await git(root, ["mv", "base.txt", "app/incoming.txt"]);
await git(root, ["mv", "app/outgoing.txt", "outside.txt"]);
await git(root, ["mv", "app/before.txt", "app/after.txt"]);
await git(root, ["commit", "-qm", "move across project boundaries"]);

expect(await changedFilesSince(project, "HEAD~1")).toEqual(
new Set(["incoming.txt", "outgoing.txt", "before.txt", "after.txt"]),
);
});

it.skipIf(process.platform === "win32")(
"preserves unusual filenames in committed and dirty sets",
async () => {
Expand Down
3 changes: 2 additions & 1 deletion src/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ export async function changedFilesSince(root: string, ref: string): Promise<Set<
validateGitRef(ref);
const result = await runCommandArgs(
"git",
["diff", "--name-only", "--relative", "-z", `${ref}...HEAD`, "--"],
// Rename summaries omit the old path, which can still own mapped features.
["diff", "--no-renames", "--name-only", "--relative", "-z", `${ref}...HEAD`, "--"],
root,
undefined,
{ trimOutput: false },
Expand Down
16 changes: 16 additions & 0 deletions src/workflow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -861,6 +861,22 @@ describe("workflow", () => {
});
});

it("selects existing review features when their owned file is renamed", async () => {
const root = await sinceFixture("clawpatch-since-rename-");
const context = await makeContext(testOptions(root));
await initCommand(context, {});
await mapCommand(context);
const features = await readFeatures(statePaths(join(root, ".clawpatch")));
await checkCommand(root, "git config diff.renames true");
await checkCommand(root, "git mv src/two.ts src/renamed.ts");
await commitAll(root, "rename two");

const reviewed = await reviewCommand(context, { since: "base", dryRun: true });
const expected = expectedFeatureIds(features, new Set(["src/two.ts"]), true);
expect(expected.length).toBeGreaterThan(0);
expect(reviewed).toMatchObject({ dryRun: true, featureIds: expected });
});

it("selects changed features regardless of their previous review status", async () => {
const root = await sinceFixture("clawpatch-since-status-");
const context = await makeContext(testOptions(root));
Expand Down