Skip to content

fix: migrate-root merges memory files by record id instead of skipping them (GIT-100) - #43

Merged
1 commit merged into
mainfrom
bugfix/GIT-100-migrate-root-merge
Sep 22, 2026
Merged

1 commit merged into
mainfrom
bugfix/GIT-100-migrate-root-merge

Conversation

@nTEG-dev

Copy link
Copy Markdown
Member

What

migrate-root merges the memory files instead of skipping them (GIT-100).

  • learnings.json, threads.json, decisions.json, sessions.json are merged by record id:
    • new ids are added and destination-only ids are kept;
    • where both sides hold a record and they differ, the newer one wins, dated by updated_at, then resolved_at, last_touched_at, created_at. On a tie, or when neither side is dated, the destination wins, since it's the live store.
  • Every loser is logged to <dest>/migrate-root-conflicts.json, with the id, which side was kept, both timestamps and the full losing record. Entries are deduplicated, so a re-run adds nothing.
  • The destination's memory files are backed up first, to <dest>/backups/migrate-root-<timestamp>/, before the first write. Nothing is backed up when nothing changes.
  • Idempotent: a re-run changes no file, makes no backup and logs no conflict.
  • A memory file is never listed under "skipped". One that isn't a readable list of records is reported as NOT merged: …, left untouched, and the command exits 1.
  • Everything else keeps copy-if-absent. backups/ and the conflicts log are never migrated out of a source.

Why

The four memory files were skipped as "already exists", so a destination with any local memory received none of the project store's. The report then listed the user's scars under "skipped":

0 file(s) copied.
1 skipped:
  learnings.json — already exists in destination      ← origin/main; project-only scar "a" is not in the result

How to test

CLI end to end (bin/gitmem.js migrate-root --from …, GITMEM_HOME pointing at a temp home, run twice). Project has a (project-only) and b (newer); home has b (older) and c:

run 1:  1 file(s) copied.
        1 memory file(s) merged by record id:
          learnings.json — 1 added, 1 updated from the source, 0 unchanged, 0 kept from the destination, 1 conflict(s) logged
        Destination memory files backed up to <home>/.gitmem/backups/migrate-root-2026-09-22T03-46-43-651Z
        Conflicting records (the version not kept) are in <home>/.gitmem/migrate-root-conflicts.json
run 2:  0 file(s) copied. learnings.json — 0 added, 0 updated, 2 unchanged …   (no backup, nothing logged)
result: b "project newer", c "home-only", a "project-only scar"

Venue: not applicable. migrate-root reads and writes only local .gitmem stores, never Supabase.

Unit tests: 1285/1285 pass (+11). They cover: no memory file under skipped; add, keep, newer-wins in both directions; key-order-independent equality; a source-only memory file is copied; undated conflict keeps the destination; every loser logged with the kept side; backup taken before write; idempotent re-run (byte-identical files, no backup, 0 new conflicts); source intact; dry-run writes nothing and reports the same merge; unreadable file → NOT merged, untouched, not skipped; backups and the conflicts log aren't migrated.

Existing tests that encoded the old behaviour (changed, not deleted)

tests/unit/commands/migrate-root.test.ts:

  • "never overwrites a file that already exists in the destination" used learnings.json and asserted the destination was unchanged and the file listed under skipped. That's exactly the GIT-100 bug: the project store's memory was dropped. The test now uses config.json, keeping the copy-if-absent rule for non-memory files. The memory case is covered by the new merge tests.
  • "reports why each file was skipped" used learnings.json as its example of a skip. It now uses config.json, since a memory file is never skipped.

Checklist

  • npm run test:unit passes (1285)
  • New tests added
  • No schema change
  • Docs: migrate-root --help output is unchanged; the README doesn't describe the merge

Breaking changes

migrate-root now writes into existing destination memory files (after backing them up), where it used to leave them alone. That's the fix.

🤖 Generated with Claude Code

…g them (GIT-100)

learnings.json, threads.json, decisions.json and sessions.json were skipped
as "already exists", so a destination holding any local memory received none
of the project store's, and the report listed the user's scars under
"skipped".

- Those four are merged by record id: new ids are added; where both sides
  hold a record the newer wins (updated_at, else resolved_at, last_touched_at,
  created_at), the destination on a tie or when neither is dated.
- Every loser is written to <dest>/migrate-root-conflicts.json with which side
  was kept, deduplicated so a re-run logs nothing new.
- The destination's memory files are backed up to <dest>/backups/ before the
  first write; nothing is backed up when nothing changes.
- Idempotent: a re-run changes nothing.
- A memory file that is not a readable list of records is reported as NOT
  merged (exit 1) and left untouched; never listed as skipped.
- Other files keep copy-if-absent; backups/ and the conflicts log are never
  migrated out of a source.

migrate-root.test.ts: "never overwrites a file that already exists" and
"reports why each file was skipped" used learnings.json and asserted it was
skipped, i.e. the old behaviour. They now use config.json to keep testing the
non-memory rule. +11 tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@nTEG-dev nTEG-dev closed this pull request by merging all changes into main in f93c3a3 Sep 22, 2026
neoneye pushed a commit to agent-memory-atlas-archive/gitmem-dev--gitmem that referenced this pull request Sep 22, 2026
Conflicts (blank-supabase.mjs): all result fields and all checks kept.
migrate-root.ts auto-merged (gitmem-dev#43's merge + gitmem-dev#45's sameDirectory). tsc clean.
@nTEG-dev
nTEG-dev deleted the bugfix/GIT-100-migrate-root-merge branch September 23, 2026 05:49
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