# memory-sync

**Identity:** `ai.stravica.ops.memory-sync`

Bidirectional mirror between Claude Code's per-project memory cache and the repo-tracked `operator/memory/` store. Installed as SessionStart / SessionEnd hooks so every session boots with the latest memories and every session persists any newly-saved ones back to git.

## Status

Implemented:

- `pull`; mirror `<repo>/operator/memory/` into `~/.claude/projects/<sanitised>/memory/`
- `push`; mirror the local cache back into `<repo>/operator/memory/`
- Worktree detection; commands no-op when run from a worktree (primary checkout is the sync authority)

Deferred: dry-run mode, selective filters, conflict flagging.

## Install / invoke

Invoked via the `memory-sync` shim:

```
node <repo>/core/tools/memory-sync/bin/memory-sync <verb>
```

Hooks in `.claude/settings.json` call this directly. No PATH install is required because the hook already knows the absolute path through `$CLAUDE_PROJECT_DIR`.

## Commands

```
memory-sync pull          Mirror repo memory dir -> local Claude Code cache
memory-sync push          Mirror local Claude Code cache -> repo memory dir
memory-sync -h, --help    Show usage
```

Both verbs walk up from the current working directory to find the git repo root (a directory containing `.git`). When invoked inside a git worktree (`.git` is a regular file, not a directory) the command logs a skip notice and exits `0`; the primary checkout is the source of truth for memory sync.

Mirroring is "source wins, full": every file in the source tree overwrites its counterpart in the destination, and every file in the destination that does not exist in the source is deleted. Empty directories left over after deletion are pruned.

## Exit codes

| Code | Meaning |
|---|---|
| `0` | OK (including skipped-inside-worktree) |
| `1` | Usage error (bad verb, extra arguments, missing verb) |
| `2` | Source dir missing (not in a repo, repo memory dir missing on pull, cache missing on pull/push) |
| `3` | IO failure (copy / delete / mkdir failed) |

The ladder matches the tooling standard (see [`core/knowledge/standards/tooling.md`](../../knowledge/standards/tooling.md) §6) with `2` overloaded to cover the shell-era semantics "the thing you asked to sync is not where it should be" for wire compatibility with the previous `sync.sh` behaviour.

## Operational discipline rules

- **Run only from the primary checkout.** Worktrees are explicitly skipped. If a session inside a worktree silently synced, it could race with the primary checkout and overwrite fresh memories with stale ones.
- **Source wins, unconditionally.** There is no merge. Whichever direction you run, the source side is authoritative and the destination is made to match. Conflict resolution lives outside this tool (use git on the repo side).
- **The `mirror.mjs` module is the sole filesystem chokepoint.** Every byte written or deleted on behalf of the tool passes through it. Commands may not open their own copy loops.

## Project layout

```
core/tools/memory-sync/
  bin/memory-sync         # node shebang shim; one line
  package.json            # name, version, type: module, zero dependencies
  README.md               # this file
  src/
    cli.mjs               # verb routing, exit-code translation
    cli-support.mjs       # ExitError + log() used by cli + commands
    repo.mjs              # findRepoRoot, cacheDirFor; pure helpers
    mirror.mjs            # THE chokepoint; all FS mutation lives here
    commands/
      pull.mjs            # repo -> cache
      push.mjs            # cache -> repo
  test/
    cli.test.mjs          # end-to-end over a temp repo tree
    repo.test.mjs         # repo discovery + cache path sanitisation
    mirror.test.mjs       # chokepoint behaviour against fixtures
```

## Tests

```
node --test core/tools/memory-sync/test/*.test.mjs
```

All tests use `mkdtemp()` under `os.tmpdir()` and clean up afterwards. No test touches the real cache, the real repo's `operator/memory/` tree, or the real `$HOME`. The CLI tests override `HOME` around the call so `os.homedir()` resolves to a fixture-scoped path.

## The tooling standard

This tool follows [`core/knowledge/standards/tooling.md`](../../knowledge/standards/tooling.md). In particular:

- §2 Node-only; no shell implementation remains.
- §3 Zero npm dependencies; only `node:*` imports.
- §4 Layout; bin / src / commands / test as described.
- §6 Exit codes; `0/1/2/3` ladder.
- §7 Chokepoint pattern; `mirror.mjs` is the single FS-mutation seam.
- §11 camelCase JSON; no JSON on the wire yet; the rule still holds when it appears.

## Changelog

- **1.0.0**; Rewritten from `sync.sh` to Node. Behaviour preserved (pull/push verbs, exit-code ladder, worktree skip, source-wins mirror semantics). Drops the rsync dependency.
