Carry project memory in the repo, with tools/memory-sync.sh
Claude Code keeps per-project memory at <home>/.claude/projects/<key>/,
where <key> is the project's absolute path with every separator and the
drive colon turned into a dash. That makes it both rename-fragile and
machine-specific, and this project has hit each of those once:
- D:\DEV\Schunix -> D:\DEV\SchOS changed the key, and 43 memories were
invisible until moved across by hand.
- Development happens from Windows and from Linux, and each looks in a
different place for the same checkout.
Committing .claude/memory/ fixes both -- git carries it across renames
and between hosts -- and `import` puts it wherever the local machine
expects. .claude/settings.local.json stays ignored: those are per-machine
permissions.
The colon maps to a dash rather than being dropped. Deleting it gives
D-DEV-SchOS, which matches nothing and silently finds zero memories; that
was the first attempt. On Git Bash the key comes from `cygpath -w`,
because Claude Code is a Windows program and sees D:\DEV\SchOS, not
/d/DEV/SchOS.
This commit is contained in:
parent
cda5908660
commit
3182da3965
47 changed files with 2810 additions and 0 deletions
111
tools/memory-sync.sh
Normal file
111
tools/memory-sync.sh
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
#!/bin/sh
|
||||
# Move Claude Code's project memory between this repo and the local machine.
|
||||
#
|
||||
# tools/memory-sync.sh export memory on this machine -> .claude/memory/
|
||||
# tools/memory-sync.sh import .claude/memory/ -> this machine
|
||||
# tools/memory-sync.sh status show both sides, change nothing
|
||||
#
|
||||
# Why this exists
|
||||
# ---------------
|
||||
# Claude Code keeps per-project memory outside the repo, under
|
||||
#
|
||||
# <home>/.claude/projects/<key>/memory/
|
||||
#
|
||||
# where <key> is the project's absolute path with the separators replaced by
|
||||
# dashes: D:\DEV\SchOS becomes D--DEV-SchOS, /home/me/dev/SchOS becomes
|
||||
# -home-me-dev-SchOS. Two consequences follow, and both have already bitten
|
||||
# this project once:
|
||||
#
|
||||
# * Renaming the directory orphans the memory. D:\DEV\Schunix -> D:\DEV\SchOS
|
||||
# changed the key, and everything written under the old one became
|
||||
# invisible until it was moved across by hand.
|
||||
# * The key is machine-specific, so memory written on Windows is not where
|
||||
# Linux looks for it, even for the same checkout.
|
||||
#
|
||||
# Committing the memory to the repo fixes both: git carries it between hosts
|
||||
# and across renames, and `import` puts it wherever this particular machine
|
||||
# expects to find it. Run `export` after a session that learned something and
|
||||
# `import` after cloning or pulling on another machine.
|
||||
|
||||
set -eu
|
||||
|
||||
usage() {
|
||||
echo "usage: $0 {export|import|status}" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
[ $# -eq 1 ] || usage
|
||||
|
||||
# Repo root: this script lives in tools/, so the parent of its directory.
|
||||
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||
repo=$(CDPATH= cd -- "$script_dir/.." && pwd)
|
||||
repo_mem="$repo/.claude/memory"
|
||||
|
||||
# The absolute path Claude Code itself sees. Under Git Bash `pwd` gives
|
||||
# /d/DEV/SchOS, but Claude Code is a Windows program and uses D:\DEV\SchOS, so
|
||||
# the key must be derived from the Windows form or it will not match.
|
||||
native_path="$repo"
|
||||
case "$(uname -s)" in
|
||||
MINGW* | MSYS* | CYGWIN*)
|
||||
if command -v cygpath >/dev/null 2>&1; then
|
||||
native_path=$(cygpath -w "$repo")
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
# Encode: every separator becomes a dash, and so does the drive colon.
|
||||
#
|
||||
# D:\DEV\SchOS -> D--DEV-SchOS
|
||||
# /home/me/dev/SchOS -> -home-me-dev-SchOS
|
||||
#
|
||||
# The colon maps to a dash rather than being deleted -- that is where the
|
||||
# doubled dash in the Windows form comes from, and dropping it instead yields
|
||||
# D-DEV-SchOS, which matches nothing.
|
||||
key=$(printf '%s' "$native_path" | tr '/\\:' '---')
|
||||
|
||||
home_dir=${HOME:-$USERPROFILE}
|
||||
host_mem="$home_dir/.claude/projects/$key/memory"
|
||||
|
||||
count() { [ -d "$1" ] && find "$1" -name '*.md' 2>/dev/null | wc -l | tr -d ' ' || echo 0; }
|
||||
|
||||
case "$1" in
|
||||
status)
|
||||
echo "repo: $repo"
|
||||
echo "as Claude sees it: $native_path"
|
||||
echo "project key: $key"
|
||||
echo
|
||||
echo "in repo: $repo_mem"
|
||||
echo " $(count "$repo_mem") memories"
|
||||
echo "on this host: $host_mem"
|
||||
echo " $(count "$host_mem") memories"
|
||||
;;
|
||||
|
||||
export)
|
||||
if [ ! -d "$host_mem" ]; then
|
||||
echo "memory-sync: nothing to export -- no memory at $host_mem" >&2
|
||||
echo " (is the project key right? run: $0 status)" >&2
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p "$repo_mem"
|
||||
# Copy rather than mirror: deleting from the repo is a git operation and
|
||||
# should be visible in a diff, not a side effect of running a sync script.
|
||||
cp "$host_mem"/*.md "$repo_mem"/ 2>/dev/null || true
|
||||
echo "exported $(count "$repo_mem") memories to .claude/memory/"
|
||||
echo "commit them to carry them to your other machines."
|
||||
;;
|
||||
|
||||
import)
|
||||
if [ ! -d "$repo_mem" ]; then
|
||||
echo "memory-sync: nothing to import -- no .claude/memory/ in this repo" >&2
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p "$host_mem"
|
||||
cp "$repo_mem"/*.md "$host_mem"/ 2>/dev/null || true
|
||||
echo "imported $(count "$host_mem") memories to:"
|
||||
echo " $host_mem"
|
||||
;;
|
||||
|
||||
*)
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
Loading…
Reference in a new issue