fix(config): stop disconnect destroying files the user wrote - #367
Open
fstubner wants to merge 1 commit into
Open
fix(config): stop disconnect destroying files the user wrote#367fstubner wants to merge 1 commit into
fstubner wants to merge 1 commit into
Conversation
Two ways it did, both found by driving a real setup/disconnect round trip rather than by reading the code, and both reproduced before and after. A memory file containing nothing but the author's own YAML frontmatter was DELETED. Removal asked "is what survives any frontmatter?" and deleted the file when it was — a question that cannot tell xtctx's own Cursor-rule prelude from a metadata stub someone wrote themselves. A `CLAUDE.md` holding `---\ntitle: My own notes\n---` did not exist after `disconnect`. It now compares against the exact prelude the writer prepends for that path, which is the only string removal is entitled to take back; the writer and the remover already promise in their own comments to agree byte-for-byte, and this is what that promise required. A file with one orphaned `begin` marker lost every line between it and the next block's `end`. The pattern was `begin[\s\S]*?end`, which pairs an opening marker with the nearest following close whatever lies between — so an end marker lost to a hand-edit, or a merge that kept half of one side, made removal swallow the user's content. Reproduced: a file with the author's heading and body came back as a single line. A `begin` that meets another `begin` before it meets an `end` is now not treated as an opening at all. It stays in the file as inert text, which `inspectManagedFile` already surfaces, and inert text is recoverable where deleted content is not. `countManagedBlocks` counts by the same rule, so status cannot report a block that removal refuses to touch — which would tell the user to run a repair that can never change anything. Both cases now round-trip byte-identical through setup and disconnect. The regression tests assert through `setupProject`/`disconnectProject` rather than through the splicing helpers, because that is the layer where the loss happened: the helpers were individually defensible and the round trip still ate the file. Verified by mutation — restoring either the old regex pairing or the loose frontmatter test turns them red. Kept: a file that held nothing but xtctx's own block is still removed rather than left as a stub, and the `--all` global blast radius, duplicate-block self-healing and comment preservation all still behave as before.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two ways
disconnectdestroyed user content. Both found by driving a real setup/disconnect round trip, not by reading the code — the splicing helpers were individually defensible and the round trip still ate the file.1. A file that was only the author's own frontmatter was deleted
Removal asked "is what survives any YAML frontmatter?" and deleted the file when it was. That cannot tell xtctx's own Cursor-rule prelude from a metadata stub someone wrote themselves.
Now it compares against the exact prelude the writer prepends for that path — the only string removal is entitled to take back. The writer and remover already promise in their own comments to agree byte-for-byte; this is what that promise required.
2. An orphaned
beginmarker ate everything to the next block'sendThe pattern was
begin[\s\S]*?end, which pairs an opening marker with the nearest following close whatever lies between. An end marker lost to a hand-edit, or a merge that kept half of one side, made removal swallow the user's content.A
beginthat meets anotherbeginbefore it meets anendis no longer treated as an opening. It stays as inert text — whichinspectManagedFilealready surfaces, and inert text is recoverable where deleted content is not.countManagedBlocksnow counts by the same rule, so status can't report a block removal refuses to touch and send the user to a repair that can never change anything.Verification
Both cases now round-trip byte-identical through a real setup/disconnect. Tests assert at that level, not at the helper level.
714 tests pass, typecheck and lint clean.
Deliberately unchanged
A file holding nothing but xtctx's own block is still removed rather than left as a stub. The
--allglobal blast radius, duplicate-block self-healing, and comment preservation all behave as before — each verified still passing.