Repository navigation
Key the engine's shown map by one canonical path - #54
Conversation
read_file dur.py, edit_file ./dur.py, read_file dur.py: the edit cleared the ./dur.py key and left dur.py marked as shown, so the re-read was answered "already shown, nothing has changed" while the transcript had elided the old copy. The model held no copy of the file it had edited. canonPath in transcript.ts is now the one normaliser for both the engine's shown map (set, read, delete) and transcript elision. It also folds sub/../x and, given the workspace root, absolute paths inside it.
Solvyx
left a comment
There was a problem hiding this comment.
Grok review of e0762f3, comment only, not merging.
Verdict: no blockers, fine to merge once CI is green (test-linux and typecheck-and-test were still pending when I looked; privsep-linux passed). The fix does what it says for the reported case. I checked out the head, ran typecheck and test/lean-fixes.test.ts locally (25/25 pass), and confirmed the engine and transcript can no longer split the way #51's item 2 described. canonPath-with-root depends only on posix.normalize of the input, so whenever the transcript treats a write and a read as one file, the engine does too. That means the transcript can't elide a copy the engine still calls "shown".
Pairing rules: clean. No subscription backends, no competitor names in copy, nothing touching fine-tune data, and it's on a claude/ branch.
Blocking
None.
Non-blocking
-
The transcript doesn't fold absolute paths, but the changelog and docstring say it does. (
src/transcript.ts:636,:640;CHANGELOG.md:159; confidence high, impact low.) The engine callscanonPath(path, this.cwd), while the transcript callscanonPath(path)with no root. Repro: read_file/ws/dur.py, then edit_filedur.py, thenelideSupersededReads()elides 0, so the stale pre-edit copy stays in context. The engine side is correct (both spellings map todur.py), so this is wasted tokens rather than a missing copy. But the CHANGELOG line ("the engine and the transcript now key files through one normaliser … an absolute path inside the workspace as the same file") and thecanonPathdoc ("have to agree on it") overstate it, and the absolute-path test attest/lean-fixes.test.ts:329only covers the engine. Fix: giveTranscript(orelideSupersededReads) the workspace root, or narrow the wording. -
On Windows, absolute paths still get the wrong key, so the original false "nothing has changed" comes back. (
src/transcript.ts:44-52; confidence medium.release.ymlships a win build, but CI only tests on Linux.)path.relative("C:\\ws", "C:\\ws\\src\\dur.py")returnssrc\dur.py, andposix.normalizeleaves the backslash, so the key doesn't match a read ofsrc/dur.py. Scenario: readsrc/dur.py, then editC:\ws\src\dur.py(which deletes the wrong key), then re-readsrc/dur.py. The re-read gets "already been shown … nothing has changed since", and since the transcript keys differ too, the stale copy is the only one in context. The!rel.startsWith("../")guard at:49also misses..\there. This isn't a regression (main was already wrong here), but the PR claims the absolute-path case is fixed. Suggest replacing\with/afterrelative()whensep === "\\", plus a unit case built onpath.win32. -
Follow-up idea: make "nothing has changed since" true by checking the file, not the tool name. (
src/engine.ts:5834; confidence high, pre-existing, out of scope.) As the PR says, onlywrite_file/edit_fileclearshown. So readdur.py, thenbash: sed -i … dur.py(or a test run that rewrites it, or anapply_patch-style shell edit), then re-read, and the model is still told nothing changed while it holds the old copy. Storing a size+mtime (or a short content hash) with eachshownentry and dropping its coverage when that changes would cover bash writes, renames, symlinked-cwd spellings (/varvs/private/varon macOS) and case-only spellings on case-insensitive filesystems all at once. It also fits the project's line about proving things against disk. Worth a separate issue rather than growing this PR.
Nits: none worth holding for.
…pace Review of #55 and #54: - The fingerprint is taken before and after read_file, and the "already shown" pointer is sent only when before, after and the stored value all match. A write landing during the read is no longer recorded as what the model was shown. - A null fingerprint is unknown and never matches. - Fingerprinting never throws: a worker helper that fails turns into an unknown fingerprint, not a failed step. - One fileFingerprint (src/files.ts), taken by the worker's fs-helper as a new op, so the 1 MB hash cap applies under privilege separation too. - The 1 MB-and-over stat-only limit is stated in the docs and CHANGELOG. - Transcript takes the workspace root, so its elision folds absolute paths the way the engine does. - canonPath treats backslashes as separators where sep is a backslash (and ..\ as outside); path module injectable, tested with path.win32.
Follow-up to the PR #51 re-review, non-blocking item 2.
Bug. The engine's
shownmap (which lines of a file the model has been shown) was keyed by the path as spelled.read_file dur.py, then a successfuledit_file ./dur.py, thenread_file dur.py: the edit deleted the./dur.pykey,dur.pystayed marked as shown, and the re-read was answered with the "already been shown … nothing has changed since" pointer, while the transcript (which strips./) had already elided the old copy. The model held no copy of the file it had just edited.Fix.
canonPathinsrc/transcript.tsis the one path normaliser. Transcript elision uses it (where it replaces the two inline^./strips), and so does the engine at every place theshownmap is read, set or deleted. It also foldssub/../dur.py, and with the workspace root, absolute paths inside it. Paths outside the workspace stay absolute. No other tool (bash, rename, delete) touchesshown.Repro, final tool results on the wire:
[molt: you have already been shown lines 1-2 of dur.py. Scroll up rather than reading it again — nothing has changed since. …]def parse(s):\n return 5400 # NEW_MARKTests.
test/lean-fixes.test.tshas engine-level regressions fordur.py/./dur.pyboth ways, an absolute path andsub/../dur.py. All four failed before the fix. There are alsocanonPathunit cases and a transcript elision case.Verified (
~/.local/binoff PATH):npm run check: 2439 tests, 2434 pass, 0 fail, 5 todo; e2e PASS, self-check PASS.