Skip to content

Compare a turn's patch with the working tree, file by file (CF-5) - #302

Merged
BunsDev merged 4 commits into
cf5-memory-citationsfrom
cf5-patch-state
Oct 18, 2026
Merged

BunsDev merged 4 commits into
cf5-memory-citationsfrom
cf5-patch-state

Conversation

@BunsDev

@BunsDev BunsDev commented Oct 18, 2026

Copy link
Copy Markdown
Contributor

Part of #278 (CF-5), tracker #273. Stacked on #299: it adds one command to #299's EvidenceCommands, so it targets cf5-memory-citations and will retarget to main when #299 merges. The claim is here.

This takes the CF-5 open item "Turn patches compared against the current working tree, beyond the label". The acceptance line is: captured turn patch and later working-tree changes cannot be confused; stale revisions are visibly identified.

What changes

A patch row (apply_patch, Edit, MultiEdit, Write, NotebookEdit) still carries the label "The patch as this turn applied it…". A patch the CLI reported as applied now also has Compare with working tree, then Check again, with the time of each check. Each file it touched gets one verdict:

Verdict When
As this turn left it The file is exactly what a Write or a Codex add wrote, or is still gone after a Codex delete.
Still has this turn's edit The text an Edit/MultiEdit put in is still there, or every Codex hunk's new lines are still there, in order. The rest of the file is not known, and the label doesn't claim it.
Changed since this turn It no longer matches. A moved file also reads as changed if a file is back at its old path.
Deleted since this turn It's gone.
Not compared, with the reason Outside the thread's folder; a link out of it; a directory; over 8 MiB; an edit that only removed text; a notebook edit; a Codex patch saved before this change.

Where it reads. Main does the comparison (main/engine/patch-state.ts, command evidence:patch).

  • It takes the patch from the thread's own record in main, not from the window.
  • It reads only the files the patch names, only inside the thread's folder. The path must resolve inside both by name and through realpath, and the final open uses O_NOFOLLOW.
  • Only verdicts reach the window, never file contents.

What stays still. Opening a row reads nothing. Comparing writes nothing, runs nothing and fetches nothing. A denied, failed, stopped, still-running or unknown-outcome patch isn't offered and is refused in main. That rule is in one shared function, appliedPatch.

Paired desktops can compare too. They learn less than files:read already gives them. The offline Store demo refuses the command like its other system commands.

The Codex record

From local codex-cli 0.163.0 sessions and Modex's own thread records on this Mac (8 threads: 10 updates, 6 adds):

  • Each file of a fileChange has an absolute path.
  • An update's diff is hunks only (@@ -a,b +c,d @@, no ---/+++ header).
  • An add carries the whole file.

Modex joined those diffs into args.patch and dropped the paths. args.files now keeps each file's path, kind, movePath, and lines: how many lines of patch are that file's diff. Each diff stays stored once, and args.patch is unchanged for every reader. This is one line at the fileChange start in codex.ts, outside the regions #293 changes, with an inline type import so it stays clear of #293's import hunk.

No session content was copied into the repository; fixtures are synthetic.

Tests, red first

  • test/patch-state.test.ts, 14 tests. It failed to compile before the module existed (TS2307).
    • Write: as left, changed, deleted.
    • Edit: still has its edit, even after unrelated lines change; changed once its text is gone; removal-only edits are not compared.
    • MultiEdit: every new text must be present.
    • Codex add, update and delete; hunks without headers, including \ No newline at end of file; a move judged at its new path; a file back at the old path; a move from outside the folder.
    • Legacy Codex items are not compared.
    • Absolute paths, .. and links out are not read.
    • Directories and files over 8 MiB are not compared; notebooks are listed but not compared.
    • Only applied patches are compared, with the legacy ok path covered.
    • The check's time is reported.
    • The "move from outside" case came from self-review: it failed (changed instead of has-edit) before its fix.
  • test/codex-backend.test.ts: a fileChange keeps each file's path, kind, move target and line share, and splitting patch by lines gives back each diff exactly. It failed with args.files undefined before the change.
  • e2e/turn-patch-state.spec.ts, 3 tests, through the real app, main and the claude -p adapter. A fake Claude CLI really edits greeting.txt, writes notes.txt, and writes a file outside the project.
    • Opening a row shows the button and reads nothing.
    • The edit reads "Still has this turn's edit", the write "As this turn left it", and the outside file is not read.
    • After the test changes and deletes files on disk, Check again says "Changed since this turn" and "Deleted since this turn". File bytes are unchanged by comparing, and the fixture's edit count stays 1.
    • Before the UI existed: 3 failed at the missing button. The fixture's first version also re-ran its edits for Modex's thread-title turn; it now answers that prompt without touching files.

Checks

Three signed commits:

macOS (Apple Silicon), Node 24.18.1, pnpm 10.34.5. The load average ran from 30 to 164 during these runs.

On 3afdc1e (the PR head):

Command Result
pnpm build passed
pnpm typecheck passed
pnpm --filter @modex/core test 16/16
pnpm --filter @modex/desktop test 1,546 passed, 16 skipped of 1,562 (existing skips); second stage 21/21
pnpm --filter @modex/site test 10/10
pnpm --filter @modex/store-desktop test 16/16
pnpm run test:worktrees 109/109
e2e: turn-patch-state, conversation-evidence, conversation-fidelity, typed-turns, event-journal, transcript-measure 22/22
pnpm --filter @modex/site test:e2e 7/7

On f9b30b8, before the merge: a full pnpm build, pnpm typecheck, pnpm test and pnpm test:e2e.

  • Build and typecheck passed.
  • pnpm test stopped at its first stage. Three tests in scripts/test/worktree-entry.test.mjs saw a child exit status of null, which is the script's own 20-second spawnSync timeout killing the child, at a load average up to 164. This PR does not touch scripts/, and that stage passed 109/109 on 3afdc1e. Because the chain stops at the first failure, the package unit suites ran only on 3afdc1e, above.
  • Desktop e2e: 336 passed, 3 skipped, 1 failed: event-journal.spec.ts:9 timed out at 90 s. It passed in the targeted run on 3afdc1e.
  • The new spec passed 3/3 repeats (--repeat-each=3, 9/9). In an earlier repeat, every test body passed but one afterAll app.close() hung under the same load. Six neighbouring transcript specs passed 20/20.

CI decides.

Still open on #278

codex-cli 0.163.0 sends a fileChange's files with absolute paths and
each file's diff separately. Modex joined the diffs into args.patch and
dropped the paths, so a turn's patch could not be checked file by file.

args.files now keeps each file's path, kind, move target and how many
lines of args.patch are its diff, so the diffs are not stored twice.

Coven-Harness: Claude Code / claude-opus-5-5
A patch tool was labelled as the turn's, but nothing said whether the
files still matched it. A patch the CLI reported as applied now has
Compare with working tree. Main takes the patch from the thread's own
record and reads only the files it names, inside the thread's folder,
and answers per file: as this turn left it, still has this turn's edit,
changed since, deleted since, or not compared with the reason.

Only verdicts reach the window. Opening a row reads nothing; comparing
writes and runs nothing. Links out of the folder, directories, files
over 8 MiB, removal-only edits, notebook edits and Codex patches saved
before per-file records are not compared, and say why.

Part of #278 (CF-5), tracker #273.

Coven-Harness: Claude Code / claude-opus-5-5
Coven-Harness: Claude Code / claude-opus-5-5
@vercel

vercel Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
modex Ready Ready Preview Oct 18, 2026 7:30pm UTC

Request Review

@github-code-quality

github-code-quality Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

Code coverage overview

Languages: TypeScript

TypeScript / code-coverage-agent

cf5-memory-citations a3c62e6 cf5-patch-state 4fd4af8 +/-
63% 63% 0%
View coverage of the 10 most impacted files.
File cf5-memory-citations a3c62e6 cf5-patch-state 4fd4af8 +/-
apps/desktop/sr...ngine/runner.ts 99% 99% 0%
apps/desktop/sr...ckends/codex.ts 97% 97% 0%
apps/store-desk...rc/main/demo.ts 95% 95% 0%
apps/desktop/sr...provals/gate.ts 100% 100% 0%
apps/desktop/sr...ovals/policy.ts 100% 100% 0%
apps/desktop/sr...ckends/types.ts 100% 100% 0%
apps/desktop/sr...sktop-client.ts 99% 99% 0%
apps/desktop/sr...kends/claude.ts 97% 98% +1%
apps/desktop/sr.../patch-state.ts 0% 98% +98%
apps/desktop/sr...ry-citations.ts 0% 100% +100%

ℹ️ Calculations represent line coverage. Results may change as calculations are updated.


Updated October 18, 2026 20:44 UTC

Coven-Harness: Claude Code / claude-opus-5-5
@BunsDev
BunsDev merged commit eda8445 into cf5-memory-citations Oct 18, 2026
6 checks passed
@BunsDev
BunsDev deleted the cf5-patch-state branch October 18, 2026 21:03
BunsDev added a commit that referenced this pull request Oct 19, 2026
* Show and copy the lines a Codex memory citation points to

A reply's "From Codex memory" citation was display-only. Each one now
has Show lines and Copy, which ask main (evidence:memory) for the cited
lines. main/engine/memory-citations.ts reads only inside Codex's
memories folder ($CODEX_HOME/memories, else ~/.codex/memories) and
refuses an absolute path, any "..", a symlink that resolves outside, a
directory, a file over 8 MiB, and a range past the end. It returns the
1-based, inclusive lines, cut to 64 KiB, and opens, runs and fetches
nothing. The command is local-only: a paired desktop never reads this
Mac's memory files. Streamer Mode masks the shown lines.

Local codex-cli 0.163.0 sessions cite paths relative to the memories
folder (MEMORY.md), and a one-line citation's note matched its line
counted from 1, not 0.

Tests first: memory-citations.test.ts (7) and two e2e cases in
conversation-evidence.spec.ts (show/copy exact lines with nothing
started; an escaping citation refused), red before the code.

Part of #278.

Coven-Harness: Claude Code / claude-opus-5-5

* Compare a turn's patch with the working tree, file by file (CF-5) (#302)

* Keep each Codex file change's path, kind and share of the patch

codex-cli 0.163.0 sends a fileChange's files with absolute paths and
each file's diff separately. Modex joined the diffs into args.patch and
dropped the paths, so a turn's patch could not be checked file by file.

args.files now keeps each file's path, kind, move target and how many
lines of args.patch are its diff, so the diffs are not stored twice.

Coven-Harness: Claude Code / claude-opus-5-5

* Compare a turn's patch with the working tree, file by file

A patch tool was labelled as the turn's, but nothing said whether the
files still matched it. A patch the CLI reported as applied now has
Compare with working tree. Main takes the patch from the thread's own
record and reads only the files it names, inside the thread's folder,
and answers per file: as this turn left it, still has this turn's edit,
changed since, deleted since, or not compared with the reason.

Only verdicts reach the window. Opening a row reads nothing; comparing
writes and runs nothing. Links out of the folder, directories, files
over 8 MiB, removal-only edits, notebook edits and Codex patches saved
before per-file records are not compared, and say why.

Part of #278 (CF-5), tracker #273.

Coven-Harness: Claude Code / claude-opus-5-5
BunsDev added a commit that referenced this pull request Oct 19, 2026
…5) (#311)

* Show and copy the lines a Codex memory citation points to

A reply's "From Codex memory" citation was display-only. Each one now
has Show lines and Copy, which ask main (evidence:memory) for the cited
lines. main/engine/memory-citations.ts reads only inside Codex's
memories folder ($CODEX_HOME/memories, else ~/.codex/memories) and
refuses an absolute path, any "..", a symlink that resolves outside, a
directory, a file over 8 MiB, and a range past the end. It returns the
1-based, inclusive lines, cut to 64 KiB, and opens, runs and fetches
nothing. The command is local-only: a paired desktop never reads this
Mac's memory files. Streamer Mode masks the shown lines.

Local codex-cli 0.163.0 sessions cite paths relative to the memories
folder (MEMORY.md), and a one-line citation's note matched its line
counted from 1, not 0.

Tests first: memory-citations.test.ts (7) and two e2e cases in
conversation-evidence.spec.ts (show/copy exact lines with nothing
started; an escaping citation refused), red before the code.

Part of #278.

Coven-Harness: Claude Code / claude-opus-5-5

* Compare a turn's patch with the working tree, file by file (CF-5) (#302)

* Keep each Codex file change's path, kind and share of the patch

codex-cli 0.163.0 sends a fileChange's files with absolute paths and
each file's diff separately. Modex joined the diffs into args.patch and
dropped the paths, so a turn's patch could not be checked file by file.

args.files now keeps each file's path, kind, move target and how many
lines of args.patch are its diff, so the diffs are not stored twice.

Coven-Harness: Claude Code / claude-opus-5-5

* Compare a turn's patch with the working tree, file by file

A patch tool was labelled as the turn's, but nothing said whether the
files still matched it. A patch the CLI reported as applied now has
Compare with working tree. Main takes the patch from the thread's own
record and reads only the files it names, inside the thread's folder,
and answers per file: as this turn left it, still has this turn's edit,
changed since, deleted since, or not compared with the reason.

Only verdicts reach the window. Opening a row reads nothing; comparing
writes and runs nothing. Links out of the folder, directories, files
over 8 MiB, removal-only edits, notebook edits and Codex patches saved
before per-file records are not compared, and say why.

Part of #278 (CF-5), tracker #273.

Coven-Harness: Claude Code / claude-opus-5-5

* Keep a long tool's raw output in main, from its first byte

The transcript keeps a tool's last 256 KiB; the rest of a long Codex
command's output was dropped. Codex streams every chunk
(item/commandExecution/outputDelta), so its backend now hands each raw
chunk to an optional main-only sink hook, TurnSink.output. The runner
maps it to the tool's transcript item and ToolLogStore keeps it.

Output that fits in the preview is never written. Past that, the store
keeps up to 10 MiB from the first byte in one file per item under
app/tool-logs/<thread>/, records the total when the tool finishes,
reads back 256 KiB pages ending on character boundaries, and is removed
with its thread. Thread ids are checked and item ids are hex-encoded,
so no id becomes a path. Nothing raw is journaled or sent to the window.

Claude Code 2.1.302 hands Claude at most bashOutputMaxChars (default
30,000, up to 128,000 characters) and keeps the rest in its own file,
so a Claude result never outgrows the preview and its backend does not
call the hook.

Part of #278 (CF-5), tracker #273.

Coven-Harness: Claude Code / claude-opus-5-5

* Add the full-log e2e test (red until the UI lands)

Coven-Harness: Claude Code / claude-opus-5-5

* Show a long tool's full log a page at a time

A tool whose output passed the 256 KB preview now has Show full log
under its truncation notice. It asks main (evidence:tool-log) for the
kept output from the first byte, 256 KB per page, with Earlier, Later,
Copy page and Hide. The status says whether all of it was kept, the
first part of how much, or that the tool never finished. Streamer Mode
masks the page; nothing is read until asked, and reading runs nothing.

The page shape, sizes and byte formatter move to shared/evidence.ts.
The offline Store demo refuses the command like its other evidence reads.

Part of #278 (CF-5), tracker #273.

Coven-Harness: Claude Code / claude-opus-5-5

* Document the full log: what main keeps, how it reads back, and why Claude needs none

Coven-Harness: Claude Code / claude-opus-5-5

This branch was successfully deployed

1 active deployment
Preview — 4fd4af8a Deployed Oct 18, 2026 by vercel[bot]
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