Skip to content

feat(review): experiment with mutation-journal reviews - #179

Open
Waishnav wants to merge 8 commits into
fix/review-checkpoint-hardeningfrom
experiment/review-change-journal
Open

feat(review): experiment with mutation-journal reviews#179
Waishnav wants to merge 8 commits into
fix/review-checkpoint-hardeningfrom
experiment/review-change-journal

Conversation

@Waishnav

@Waishnav Waishnav commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Stacks on #178. This experiments with making successful DevSpace file-mutation tools define the reviewed file set instead of relying solely on Git working-tree provenance.

The journal captures each path's original state on first touch by write, edit, or apply_patch, then computes one net original-to-final diff at show_changes. Repeated edits collapse naturally, net-zero changes disappear, moves retain provenance, unrelated working-tree edits are excluded, and non-Git workspaces can produce review cards. Git checkpoints still advance in parallel as a synchronized fallback/comparison path, so this PR can evaluate journal semantics without removing the existing recovery mechanism yet.

No OS-level filesystem watcher or new user-facing review-mode switch is introduced.

Summary by CodeRabbit

  • New Features

    • Added workspace change tracking for more accurate review summaries.
    • Reviews now capture net file changes, including edits, additions, deletions, and renames.
    • Change reviews work in non-Git projects and can distinguish tracked changes from unrelated workspace updates.
    • Added file-level summaries with change counts and line additions/removals, including binary and empty files.
  • Bug Fixes

    • Repeated reviews now stay synchronized and avoid reporting already reviewed changes.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds a review change journal for workspace mutations, shared review-diff parsing, pre-write patch callbacks, and server integration. Tests cover net edits, renames, Git-independent reviews, empty files, and unrelated changes.

Changes

Review change journaling

Layer / File(s) Summary
Shared review diff contracts
src/review-diff.ts, src/review-checkpoints.ts
Review diff types, parsing, summaries, and file-type normalization now use a shared module.
Workspace mutation journal
src/review-change-journal.ts, src/review-change-journal.test.ts
The journal records original states, edits, renames, file modes, binary states, patches, review results, and reset state.
Pre-write patch capture
src/apply-patch.ts, src/apply-patch.test.ts
applyPatch accepts beforeApply and invokes it with staged paths and file metadata before filesystem writes.
Server mutation and review integration
src/server.ts, src/server.test.ts, package.json
Write, edit, and patch tools record successful mutations. show_changes reads journal results and retains checkpoint fallback behavior. The new journal tests run in the test script.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant WriteTool
  participant ReviewChangeJournal
  participant Workspace
  participant show_changes
  WriteTool->>ReviewChangeJournal: prepare mutation
  WriteTool->>Workspace: write or edit file
  WriteTool->>ReviewChangeJournal: commit successful mutation
  show_changes->>ReviewChangeJournal: review tracked mutations
  ReviewChangeJournal->>Workspace: read current file states
  ReviewChangeJournal-->>show_changes: return net changes and patch
Loading

Possibly related PRs

Poem

A rabbit watched the workspace change,
Journaling each file in range.
Edits, moves, and patches align,
Net results now clearly shine.
“Review them all!” the rabbit sings.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: experimenting with mutation-journal reviews.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch experiment/review-change-journal

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Waishnav Waishnav changed the title experiment/review change journal feat(review): experiment with mutation-journal reviews Aug 10, 2026
@greptile-apps

greptile-apps Bot commented Aug 10, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds an in-memory change journal so successful write, edit, and patch operations can be reviewed independently of Git, while retaining Git checkpoints as a fallback.

  • Adds mutation capture and net-diff generation, including move tracking.
  • Hooks write, edit, apply_patch, and show_changes into the journal.
  • Extracts shared review-diff parsing and adds journal/server coverage.

Confidence Score: 3/5

The PR should not merge until move journaling and failed checkpoint synchronization preserve accurate, monotonic review results.

Supported overwrite moves are rendered with the wrong file relationship, and a transient checkpoint failure can cause changes that were already shown to reappear on the next review.

Files Needing Attention: src/review-change-journal.ts, src/server.ts

Important Files Changed

Filename Overview
src/review-change-journal.ts Implements net-change tracking and diff generation, but its strict move-state validation misrepresents supported overwrite and compound move scenarios.
src/server.ts Integrates mutation journaling into tools and review output, but clears journal state even when fallback-checkpoint synchronization fails.
src/apply-patch.ts Adds a pre-apply callback after staging and before filesystem mutation, enabling accurate baseline capture.
src/review-diff.ts Extracts existing review-patch parsing and summary behavior without a detected contract change.
src/review-change-journal.test.ts Covers basic edits, net-zero changes, simple moves, review advancement, and empty additions, but not overwrite moves or synchronization failures.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  M[Write, edit, or apply_patch] --> P[Capture pre-mutation file states]
  P --> C[Commit successful mutation to journal]
  C --> S[show_changes]
  S -->|Journal has mutations| J[Render journal-owned net diff]
  J --> G[Advance Git checkpoint]
  G --> R[Clear journal baseline]
  S -->|No journal mutations| F[Render Git checkpoint diff]
Loading

Reviews (1): Last reviewed commit: "test(review): cover journal patch moves" | Re-trigger Greptile

Comment on lines +109 to +115
if (
beforeSource.kind !== "file" ||
beforeDestination.kind !== "missing" ||
afterSource.kind !== "missing" ||
afterDestination.kind !== "file"
) {
continue;

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 Overwrite moves lose rename metadata

When apply_patch moves a file onto an existing destination, this check rejects the recorded move because the destination baseline is a file rather than missing, causing show_changes to report an independent deletion and modification instead of the successful rename.

Comment thread src/server.ts
Comment on lines +1375 to +1381
} catch (error) {
logEvent(config.logging, "debug", "review_checkpoint_comparison_unavailable", {
workspaceId,
error: error instanceof Error ? error.message : String(error),
});
}
reviewJournal.markReviewed({ workspaceId, root: workspace.root });

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 Checkpoint failure clears journal state

If Git checkpoint creation or update fails, this catch suppresses the error and markReviewed still clears the journal, causing the next show_changes call to use the stale checkpoint and present already-reviewed changes again.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🧹 Nitpick comments (5)
src/apply-patch.test.ts (1)

66-92: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a test for a rejecting beforeApply.

The current test pins the happy path. It does not pin the failure contract: if beforeApply rejects, applyPatch must reject and must not write or remove any staged file. src/server.ts depends on that property, because a failed prepareReviewMutation must not leave the filesystem mutated with no journal baseline.

Add a case that throws from beforeApply and then asserts the source file is unchanged and the destination is absent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/apply-patch.test.ts` around lines 66 - 92, Extend the applyPatch tests
around the existing beforeApply callback to cover a callback that rejects.
Assert that applyPatch rejects, the original source file remains unchanged, and
the destination file is still absent, confirming no staged file is written or
removed when beforeApply fails.
src/server.test.ts (1)

128-184: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a test for a failed mutation.

src/server.ts calls prepareReviewMutation before the tool runs and commitReviewMutation only after the error branch returns. No test pins that ordering. A regression that moves commitReviewMutation above the response.isError check would make failed writes appear in show_changes, and the suite would stay green.

Add a case that triggers a failing edit call, for example with an oldText that does not match, then asserts show_changes reports no changes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server.test.ts` around lines 128 - 184, Add a test alongside the existing
show_changes review tests that creates a workspace, invokes the edit tool with
an oldText value that cannot match, then calls show_changes and asserts it
reports no changes. Use the existing fixture, callOpen, and response helpers so
the test verifies failed mutations are not committed to review state.
src/apply-patch.ts (1)

23-28: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The mutation-capture contract mixes absolute and workspace-relative paths without naming either form. The journal accepts absolute paths in prepareMutation.paths and workspace-relative paths in ReviewMove. applyPatch hands both forms to beforeApply in one payload. The wiring in src/server.ts is correct today only because applyPatch happens to return relative display paths. Nothing in the types states the rule, so a future caller can pass the wrong form and get a silently unmatched move entry.

  • src/apply-patch.ts#L23-L28: add a doc comment on ApplyPatchOptions.beforeApply that states paths are absolute and files[].path and files[].previousPath are workspace-relative.
  • src/review-change-journal.test.ts#L59-L65: document the same split on ReviewChangeJournal.prepareMutation and ReviewMove in src/review-change-journal.ts, which this test relies on when it passes "before.txt" and "after.txt" as move endpoints while passing absolute paths to prepareMutation.

As per coding guidelines: "Use glossary terms precisely in schemas, types, documentation, and errors" and "Represent important behavior through schemas, types, checks, or explicit tool results rather than hidden prompt conventions."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/apply-patch.ts` around lines 23 - 28, The mutation-capture path contract
must explicitly distinguish path forms: document ApplyPatchOptions.beforeApply
so paths are absolute while files[].path and files[].previousPath are
workspace-relative. Also document the same distinction for
ReviewChangeJournal.prepareMutation and ReviewMove in
src/review-change-journal.ts, as exercised by src/review-change-journal.test.ts
lines 59-65; no test logic change is required.

Source: Coding guidelines

src/review-change-journal.test.ts (1)

52-81: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for a move onto an existing file.

reviewChanges requires beforeDestination.kind === "missing" to report a rename. If the destination existed before the move, the rename branch calls continue, and the fall-through loop reports the source as deleted and the destination as change. That degradation is reasonable, but no test pins it. A later change to the rename conditions could alter it silently.

Do you want me to generate that test case?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/review-change-journal.test.ts` around lines 52 - 81, Add a test covering
a move onto an already existing destination, using createReviewChangeJournal and
reviewChanges. Assert the current fallback behavior: the original source is
reported as deleted and the pre-existing destination is reported as changed,
rather than as a rename. Keep the existing move-across-later-edits test
unchanged.
src/review-change-journal.ts (1)

47-91: 🩺 Stability & Availability | 🔵 Trivial | 🏗️ Heavy lift

Add a disposal path for journal workspace state.

states gains an entry per workspaceId and never loses one. markReviewed clears baselines and moves, but the WorkspaceJournalState entry stays for the process lifetime. createServer creates one journal and shares it across all MCP sessions, so the map grows for every workspace opened by the server.

baselines also retains the full original bytes of every mutated file until markReviewed runs. If a turn writes several large files and never calls show_changes, those buffers stay resident.

Consider adding a closeWorkspace(workspaceId) method and calling it when the workspace is closed, plus a byte budget above which the journal stores a marker instead of the full content.

As per coding guidelines: "Prefer explicit lifecycle and state over hidden autonomy; make tasks, inputs, outputs, failures, and ownership inspectable."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/review-change-journal.ts` around lines 47 - 91, Add an explicit
closeWorkspace(workspaceId) operation to the object returned by
createReviewChangeJournal, removing that workspace’s WorkspaceJournalState from
states when its lifecycle ends. Invoke this disposal from the server’s
workspace-close path, while preserving markReviewed behavior. Add a configured
byte budget for captured originals in prepareMutation/readState handling,
storing a non-content marker once the budget is exceeded instead of retaining
full file bytes.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/server.ts`:
- Around line 1279-1292: Move commitReviewMutation into a finally block
surrounding the applyPatch call so the captured reviewMutation is committed
whether applyPatch succeeds or throws during partial application. Preserve the
existing move-path mapping from applied.files on success, and ensure the failure
path still commits the captured baselines without attempting to access
unavailable applied results.

---

Nitpick comments:
In `@src/apply-patch.test.ts`:
- Around line 66-92: Extend the applyPatch tests around the existing beforeApply
callback to cover a callback that rejects. Assert that applyPatch rejects, the
original source file remains unchanged, and the destination file is still
absent, confirming no staged file is written or removed when beforeApply fails.

In `@src/apply-patch.ts`:
- Around line 23-28: The mutation-capture path contract must explicitly
distinguish path forms: document ApplyPatchOptions.beforeApply so paths are
absolute while files[].path and files[].previousPath are workspace-relative.
Also document the same distinction for ReviewChangeJournal.prepareMutation and
ReviewMove in src/review-change-journal.ts, as exercised by
src/review-change-journal.test.ts lines 59-65; no test logic change is required.

In `@src/review-change-journal.test.ts`:
- Around line 52-81: Add a test covering a move onto an already existing
destination, using createReviewChangeJournal and reviewChanges. Assert the
current fallback behavior: the original source is reported as deleted and the
pre-existing destination is reported as changed, rather than as a rename. Keep
the existing move-across-later-edits test unchanged.

In `@src/review-change-journal.ts`:
- Around line 47-91: Add an explicit closeWorkspace(workspaceId) operation to
the object returned by createReviewChangeJournal, removing that workspace’s
WorkspaceJournalState from states when its lifecycle ends. Invoke this disposal
from the server’s workspace-close path, while preserving markReviewed behavior.
Add a configured byte budget for captured originals in prepareMutation/readState
handling, storing a non-content marker once the budget is exceeded instead of
retaining full file bytes.

In `@src/server.test.ts`:
- Around line 128-184: Add a test alongside the existing show_changes review
tests that creates a workspace, invokes the edit tool with an oldText value that
cannot match, then calls show_changes and asserts it reports no changes. Use the
existing fixture, callOpen, and response helpers so the test verifies failed
mutations are not committed to review state.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 532ddf30-1e4b-4a91-ae15-97205c4befe1

📥 Commits

Reviewing files that changed from the base of the PR and between 5bd46ab and dde6e3a.

📒 Files selected for processing (9)
  • package.json
  • src/apply-patch.test.ts
  • src/apply-patch.ts
  • src/review-change-journal.test.ts
  • src/review-change-journal.ts
  • src/review-checkpoints.ts
  • src/review-diff.ts
  • src/server.test.ts
  • src/server.ts

Comment thread src/server.ts
Comment on lines +1279 to +1292
let reviewMutation: ReviewMutationCapture | undefined;
const applied = await applyPatch(workspace.root, patch, {
beforeApply: async ({ paths }) => {
reviewMutation = await prepareReviewMutation(workspaceId, workspace.root, paths);
},
});
commitReviewMutation(
reviewMutation,
applied.files.flatMap((file) =>
file.operation === "move" && file.previousPath
? [{ fromPath: file.previousPath, toPath: file.path }]
: [],
),
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

A partially applied patch is dropped from journal-backed reviews.

applyPatch is intentionally partial-apply: the write and remove loops run after beforeApply, and a failure in those loops leaves earlier files already mutated. When that happens, the exception propagates out of line 1280 and commitReviewMutation at line 1285 never runs. The captured baselines in reviewMutation are discarded.

The filesystem is now mutated with no journal baseline for those files. If any other tool call in the same turn already journaled a mutation, hasTrackedMutations returns true, show_changes takes the journal path, and the partially applied files are absent from the review. The Git checkpoint fallback does not run in that case, so the user sees an incomplete diff.

Commit the capture in a finally block so the baselines survive a partial apply.

🐛 Proposed fix
         let reviewMutation: ReviewMutationCapture | undefined;
-        const applied = await applyPatch(workspace.root, patch, {
-          beforeApply: async ({ paths }) => {
-            reviewMutation = await prepareReviewMutation(workspaceId, workspace.root, paths);
-          },
-        });
-        commitReviewMutation(
-          reviewMutation,
-          applied.files.flatMap((file) =>
-            file.operation === "move" && file.previousPath
-              ? [{ fromPath: file.previousPath, toPath: file.path }]
-              : [],
-          ),
-        );
+        let applied: Awaited<ReturnType<typeof applyPatch>>;
+        try {
+          applied = await applyPatch(workspace.root, patch, {
+            beforeApply: async ({ paths }) => {
+              reviewMutation = await prepareReviewMutation(workspaceId, workspace.root, paths);
+            },
+          });
+        } catch (error) {
+          // apply_patch is partial-apply; keep baselines for files already mutated.
+          commitReviewMutation(reviewMutation);
+          throw error;
+        }
+        commitReviewMutation(
+          reviewMutation,
+          applied.files.flatMap((file) =>
+            file.operation === "move" && file.previousPath
+              ? [{ fromPath: file.previousPath, toPath: file.path }]
+              : [],
+          ),
+        );

Committing on failure is safe. reviewChanges compares each baseline against the current filesystem state and skips paths whose state did not change, so baselines for files that were never written produce no review entry.

Based on learnings: "For DevSpace Codex mode, the apply_patch operation is intentionally partial-apply: earlier successful patch operations may remain even if a later patch action fails." This comment does not ask for atomic rollback. It asks that the journal record the files that partial-apply already mutated.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let reviewMutation: ReviewMutationCapture | undefined;
const applied = await applyPatch(workspace.root, patch, {
beforeApply: async ({ paths }) => {
reviewMutation = await prepareReviewMutation(workspaceId, workspace.root, paths);
},
});
commitReviewMutation(
reviewMutation,
applied.files.flatMap((file) =>
file.operation === "move" && file.previousPath
? [{ fromPath: file.previousPath, toPath: file.path }]
: [],
),
);
let reviewMutation: ReviewMutationCapture | undefined;
let applied: Awaited<ReturnType<typeof applyPatch>>;
try {
applied = await applyPatch(workspace.root, patch, {
beforeApply: async ({ paths }) => {
reviewMutation = await prepareReviewMutation(workspaceId, workspace.root, paths);
},
});
} catch (error) {
// apply_patch is partial-apply; keep baselines for files already mutated.
commitReviewMutation(reviewMutation);
throw error;
}
commitReviewMutation(
reviewMutation,
applied.files.flatMap((file) =>
file.operation === "move" && file.previousPath
? [{ fromPath: file.previousPath, toPath: file.path }]
: [],
),
);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server.ts` around lines 1279 - 1292, Move commitReviewMutation into a
finally block surrounding the applyPatch call so the captured reviewMutation is
committed whether applyPatch succeeds or throws during partial application.
Preserve the existing move-path mapping from applied.files on success, and
ensure the failure path still commits the captured baselines without attempting
to access unavailable applied results.

Source: Learnings

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