Skip to content

fix(merge): preserve repeated H2 headings instead of dropping all but the last - #7

Open
JeePeeTee wants to merge 1 commit into
MarimerLLC:mainfrom
JeePeeTee:fix/merge-duplicate-headings
Open

fix(merge): preserve repeated H2 headings instead of dropping all but the last#7
JeePeeTee wants to merge 1 commit into
MarimerLLC:mainfrom
JeePeeTee:fix/merge-duplicate-headings

Conversation

@JeePeeTee

Copy link
Copy Markdown

Problem

blocksByKey builds a map keyed on the normalized heading, so a MEMORY.md that repeats an H2 — or has two that differ only in case, since keys are lowercased — collapses them into a single map entry. Merge then emits one block per key, so every section but the last is silently discarded.

This fires even when all three inputs are byte-identical. Merging a file that has two ## Notes sections against itself loses the first one's body. It reaches users through both callers: the claude-memmerge driver and Reconcile.

Solution

Parse disambiguates repeated headings by occurrence, so the Nth ## Notes in ours matches the Nth in theirs. The first occurrence keeps its plain key, so cross-document matching is unchanged and ## Deploy on one machine still matches ## deploy on another.

The separator is a NUL byte, which cannot occur in markdown, so a disambiguated key can never collide with a real heading's. Keys are internal to the merge algorithm and never appear in the output — only Block.Heading is written.

Verification

go build ./... && go vet ./... && gofmt -l . && go test ./... all clean.

Four new tests, each confirmed to fail when the fix is reverted:

  • distinct keys at parse time
  • both sections surviving an identical-input merge (got 1 in "# T\n\n## Notes\nsecond\n" before the fix)
  • case-variant headings in one file both kept
  • edits to different occurrences merging cleanly instead of one erasing the other

Found while reviewing the codebase; happy to adjust the approach if you'd prefer duplicates concatenated rather than kept separate.

… the last

blocksByKey builds a map keyed on the normalized heading, so a MEMORY.md that
repeats an H2 -- or has two that differ only in case, since keys are lowercased
-- collapsed them into one map entry. Merge then emits one block per key, so
every section but the last was silently discarded.

This fires even when all three inputs are byte-identical: merging a file with
two "## Notes" sections against itself loses the first one's body. It reaches
users through both callers, the claude-memmerge driver and Reconcile.

Parse now disambiguates repeated headings by occurrence, so the Nth "## Notes"
in ours matches the Nth in theirs. The first occurrence keeps its plain key, so
cross-document matching is unchanged and "## Deploy" on one machine still
matches "## deploy" on another. The separator is a NUL byte, which cannot occur
in markdown, so a disambiguated key can never collide with a real heading's;
keys are internal to the merge and never appear in the output.

Four tests cover it: distinct keys at parse time, both sections surviving an
identical-input merge, case-variant headings in one file, and edits to
different occurrences merging cleanly rather than one erasing the other.
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