Skip to content

feat: review the change journal instead of git snapshots - #174

Closed
Waishnav wants to merge 5 commits into
feat/show-changes-hardeningfrom
feat/show-changes-journal
Closed

feat: review the change journal instead of git snapshots#174
Waishnav wants to merge 5 commits into
feat/show-changes-hardeningfrom
feat/show-changes-journal

Conversation

@Waishnav

@Waishnav Waishnav commented Aug 9, 2026

Copy link
Copy Markdown
Owner

Makes show_changes review the change journal instead of git snapshots.

What changed

  • New change_journal table (migration v5) records, per workspace session and path, the original content captured at first touch by the write, edit, and apply_patch tools, plus move provenance.
  • New change-journal.ts manager computes a net diff per path at review time: new files, edits, deletions, and moves are classified from journal facts (not diff heuristics), binary and oversized changes degrade to a file list (512 KB cap), and re-review re-baselines the journal so each review shows only changes since the last one.
  • show_changes diffs the journal by default. This works in non-git workspaces, needs no repository scanning, and survives restarts (SQLite).
  • The git-backed review remains available and unchanged via DEVSPACE_REVIEW_MODE=git.

Why

The git snapshot path failed in non-git workspaces, tied every review to a repo, and recomputed whole-snapshot diffs per call. The journal makes review a cheap, workspace-scoped read of what the model actually touched.

The middle PR of three; sits on #173 and is a dependency of #175.

@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 9, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 12d5fd3e-4aea-4611-b67d-33b9ccadca19

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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 feat/show changes journal feat: review the change journal instead of git snapshots Aug 9, 2026
@greptile-apps

greptile-apps Bot commented Aug 9, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds a persistent SQLite-backed change journal and uses it to implement show_changes for non-git workspaces.

  • Records write, edit, and apply_patch baselines and generates unified review patches.
  • Adds journal schema migration, review/re-baselining behavior, and server integration tests.
  • Exports the existing unified patch generator for journal reuse.

Confidence Score: 2/5

The PR should not merge until binary deletions are preserved and journal reads enforce canonical workspace containment.

The new review path can permanently omit recorded binary deletions and can read symlink targets outside the workspace into a user-visible patch; the multibyte size calculation also bypasses the intended diff cap.

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

Security Review

The journal validates paths lexically before following symbolic links, allowing an in-workspace symlink to capture and potentially expose text from outside the workspace. How this was verified: The recorded path passes a resolve-based containment check and is then read with readFile, which follows the symlink, before show_changes returns the generated patch.

Important Files Changed

Filename Overview
src/change-journal.ts Implements journal capture and review, but drops binary deletions, permits symlink-following reads outside the root, and inconsistently enforces the byte cap.
src/server.ts Integrates journal recording and review into file tools; recording before mutation makes journal path safety particularly important.
src/db/migrations.ts Adds the version-5 change_journal table with workspace-session cascade cleanup.
src/db/schema.ts Defines the Drizzle schema and inferred types corresponding to the new migration.
src/change-journal.test.ts Covers common journal behavior and binary modification, but not binary deletion or symlink targets.

Sequence Diagram

sequenceDiagram
    participant Tool as write/edit/apply_patch
    participant Journal as Change journal
    participant DB as SQLite
    participant FS as Workspace filesystem
    participant Review as show_changes
    Tool->>Journal: recordTouch(path)
    Journal->>FS: Read original content
    Journal->>DB: Persist first baseline
    Tool->>FS: Apply mutation
    Review->>Journal: "reviewChanges(markReviewed=true)"
    Journal->>DB: Load baselines
    Journal->>FS: Read current content
    Journal-->>Review: Summary, files, unified patch
    Journal->>DB: Re-baseline or remove rows
Loading

Reviews (1): Last reviewed commit: "test: cover the change journal and non-g..." | Re-trigger Greptile

Comment thread src/change-journal.ts Outdated
const { row, currentContent, currentBinary } = entry;

if (!row.previousPath) {
if (currentContent === null && row.originalContent === null) 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 Binary deletions disappear from reviews

When an existing binary file is deleted, its stored originalContent and current content are both null, so this branch skips the entry and re-baselining permanently removes it, causing show_changes to report no deletion.

Suggested change
if (currentContent === null && row.originalContent === null) continue;
if (currentContent === null && row.originalContent === null && !row.originalBinary) continue;

Comment thread src/change-journal.ts
Comment on lines +242 to +244
function assertJournalPath(root: string, path: string): void {
if (!isPathInsideRoot(join(root, path), root)) {
throw new Error(`Path is outside workspace root: ${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.

P1 security Symlinks escape journal containment

When a workspace-relative path is a symlink to an external text file, the lexical containment check accepts it and readFile follows the link, allowing show_changes to expose the external file's contents in its patch. How this was verified: The path passes the resolve-based containment check and is then read with symlink-following readFile before the generated patch is returned.

Comment thread src/change-journal.ts Outdated
const originalBytes = row.originalBinary
? 0
: Buffer.byteLength(row.originalContent ?? "");
const oversized = originalBytes + (currentContent?.length ?? 0) > MAX_JOURNAL_DIFF_BYTES;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Byte cap mixes measurement units

The size calculation measures the original with Buffer.byteLength but the current text in UTF-16 code units, so multibyte UTF-8 content can bypass the intended diff cap and produce an unnecessarily large patch.

Suggested change
const oversized = originalBytes + (currentContent?.length ?? 0) > MAX_JOURNAL_DIFF_BYTES;
const oversized = originalBytes + Buffer.byteLength(currentContent ?? "") > MAX_JOURNAL_DIFF_BYTES;

@Waishnav
Waishnav force-pushed the feat/show-changes-journal branch from 1760bb2 to e9b3308 Compare August 9, 2026 19:49
The git-backed review snapshots cannot see edits made through write,
edit, and apply_patch in workspaces that are not git repositories.

Add a change_journal table keyed by workspace session and path that
records the original content captured at first touch, so reviews can
diff current file state against what existed before this work started.
The journal manager captures original file content on first touch and
produces a net diff at review time. New files, deletions, and moves are
classified from journal facts instead of diff heuristics, and re-review
re-baselines the journal so each review shows only changes since the
last one. Binary and oversized changes degrade to a file list instead of
a text patch.

unifiedFilePatch is exported from apply-patch so the journal reuses the
same patch builder the patch tools use.
show_changes now diffs the change journal by default, so review works in
non-git workspaces and no longer pays per-call git snapshot costs. The
git-backed review stays available with DEVSPACE_REVIEW_MODE=git.

The write, edit, and apply_patch handlers record journal touches before
executing, gated on change-review widgets being enabled.
Manager tests exercise net diffs, revert-to-original, create-then-delete,
deletions, moves and re-baselining, binary and oversized degradation,
first-touch semantics, restart persistence, and path containment. The
server test drives show_changes end to end in a non-git workspace
through write and re-baseline.
A touched binary that was deleted never appeared in a review: its stored
original is null, so the revert-to-original check treated the deletion as
no change. Deleted binaries now surface as deletions once and are dropped
by re-baselining, and the revert check no longer applies to binaries whose
original content was never stored.

Journal reads now resolve symlinks and enforce containment on the resolved
path, so a symlink pointing outside the workspace cannot leak foreign file
content into a review patch. Symlinks that stay inside the workspace keep
working.

The diff size cap now compares bytes on both sides instead of UTF-16 code
units for the current content, matching the documented 512 KiB limit.
@Waishnav
Waishnav force-pushed the feat/show-changes-journal branch from e9b3308 to b8c2d40 Compare August 10, 2026 05:53
@Waishnav Waishnav closed this Aug 10, 2026
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