Context Sync — Cross-Device + Cross-Tool
Two problems, one canonical source of truth:
Cross-device — Claude Code identifies projects by a hash of the absolute path, so context is lost between machines (
/home/alice/myapp→-home-alice-myapp≠/Users/john/myapp→-Users-john-myapp). Solved byexport/import.Cross-tool — You use Claude Code, Cursor, Codex CLI, Roo, Cline, Aider, or Windsurf — each has its own instruction/memory file (
CLAUDE.md,.cursorrules,AGENTS.md, ...). You re-explain your project to every one. Solved bytools push— one canonical memory file → adapter files for every detected tool.
The shared truth: .claude-context/MEMORY.md (+ memory/*.md), committed to git.
Every tool reads from a locally-generated adapter file pointing at that truth.
No SaaS. No proprietary memory broker. No vendor lock-in.
Commands
/sync export
Export current project context to .claude-context/ and commit.
Run:
python scripts/sync-export.py
What it does:
- Calculates the Claude path hash for the current directory
- Finds
~/.claude/projects/<hash>/— reads all.mdcontext files - Copies them to
.claude-context/in the project - Creates
.claude-context/sync-manifest.jsonwith device metadata - Runs
git add .claude-context/ && git commit -m "context-sync: export <timestamp>" - Prints: push reminder
Then push:
git push
What gets exported:
MEMORY.md— your project memory indexmemory/*.md— individual memory filesproject_*.md,feedback_*.md,user_*.md,reference_*.md— all context docs
What stays private (never exported):
*.jsonl— conversation history (too large, private)*.json— may contain API keys or settings
/sync import
Restore project context on this device from .claude-context/.
Run:
python scripts/sync-import.py
What it does:
- Runs
git pullto get latest.claude-context/ - Reads the manifest to confirm what was exported
- Calculates the path hash for this device
- Creates
~/.claude/projects/<this-hash>/if needed - Copies all files from
.claude-context/to the correct local target - Prints a summary of restored files
After import: restart Claude Code or open the project. Claude immediately has full context.
/sync status
Show what has changed between the exported context and the current local context.
Check manually:
# What's in the local Claude folder
ls -la ~/.claude/projects/$(python3 -c "import os; print(os.path.abspath('.').replace('/', '-').replace('', '-').replace(':', '-'))")/
# What's been exported
ls -la .claude-context/
# Diff a specific file
diff .claude-context/MEMORY.md ~/.claude/projects/$(python3 -c "import os; print(os.path.abspath('.').replace('/', '-').replace('', '-').replace(':', '-'))")/MEMORY.md
Or ask Claude directly to compare them and summarize what's new, changed, or missing.
/sync clean
Remove large files from the local Claude project folder to reduce size before export.
Run:
# Show what's in your Claude project folder
python3 -c "
import os
from pathlib import Path
key = os.path.abspath('.').replace('/', '-').replace('', '-').replace(':', '-')
folder = Path.home() / '.claude' / 'projects' / key
if folder.exists():
for f in sorted(folder.iterdir()):
print(f'{f.stat().st_size:>10,} {f.name}')
else:
print('No Claude project folder found')
"
# Remove conversation history (*.jsonl) to free space
python3 -c "
import os
from pathlib import Path
key = os.path.abspath('.').replace('/', '-').replace('', '-').replace(':', '-')
folder = Path.home() / '.claude' / 'projects' / key
removed = 0
for f in folder.glob('*.jsonl'):
size = f.stat().st_size
f.unlink()
print(f'Removed {f.name} ({size:,} bytes)')
removed += size
print(f'Freed {removed:,} bytes')
"
/sync tools detect
List which AI dev tools are present in this project — shows what tools push would write.
Run:
python scripts/sync-tools.py detect
Detection (no false positives — we only write where the tool already lives):
| Tool | Output adapter file | Detected by |
|---|---|---|
| Claude Code | CLAUDE.md |
always written (universal) |
| Cursor | .cursor/rules/memory.mdc |
.cursor/ or .cursorrules exists |
| Roo Code | .roo/rules/memory.md |
.roo/ or .rooignore exists |
| Cline | .clinerules/memory.md |
.clinerules exists |
| Codex CLI | AGENTS.md |
.codex/ or AGENTS.md exists |
| Aider | CONVENTIONS.md |
.aider/, CONVENTIONS.md, or .aider.conf.yml |
| Windsurf | .windsurfrules |
.windsurfrules or .windsurf/ exists |
/sync tools push
Generate adapter files from .claude-context/MEMORY.md for every detected tool.
Run:
python scripts/sync-tools.py push # write them
python scripts/sync-tools.py push --dry-run # show diff without writing
python scripts/sync-tools.py push --only cursor,claude # subset
What it does:
- Assembles the canonical memory (
MEMORY.md+ everymemory/*.md) into one block - For each detected tool, wraps it in the right format (e.g. Cursor
.mdcgets YAML frontmatter) with a clear "AUTO-GENERATED" header pointing back to the source - Writes
.claude-context/sync-tools-manifest.jsonlisting what was written
Why this is better than commercial cross-AI memory tools (Vilix, Mem0):
- No SaaS subscription
- Memory lives in your git repo, not a third-party DB
- One canonical file you can
git blame, diff, and review like code - New devs run
python scripts/sync-tools.py pushaftergit clone— every AI tool they use immediately has the project context - When you update
.claude-context/MEMORY.md, re-run push and every adapter regenerates
Why most adapter files are gitignored:
They're per-machine projections of the canonical source. Committing them would create merge conflicts every time someone with a different tool stack syncs. The source-of-truth (.claude-context/) is what's committed; the adapters are local rebuilds.
Phase 2 — live MCP sync (planned, contributions welcome): See PHASE2-MCP.md for the full spec. Adds a small MCP server alongside the file adapters so edits flow between tools without re-running push. Covers every MCP-capable tool (Claude Code, Cursor, Codex CLI, Windsurf, Roo, Cline). The file-based Phase 1 stays as the universal fallback and the source of truth.
How the Path Hash Works
Claude Code generates the project key from the absolute path:
abs_path = os.path.abspath('.')
# /home/alice/myapp
# → replace '/' with '-'
# → -home-alice-myapp
claude_key = abs_path.replace('/', '-').replace('', '-').replace(':', '-')
source = f"~/.claude/projects/{claude_key}/"
| Device | Path | Hash |
|---|---|---|
| Linux VPS | /home/alice/myapp |
-home-alice-myapp |
| Mac | /Users/john/myapp |
-Users-john-myapp |
| Windows | C:\Users\john\myapp |
-C--Users-john-myapp |
These are all the same project but Claude sees three different contexts. sync-import.py handles this automatically — it always installs to the correct hash for the current device, regardless of where it was exported from.
Full Cross-Device Workflow
Device A (VPS):
1. Open project in Claude Code
2. Work — Claude builds up memory, project context
3. /sync export → git push
Device B (Mac / laptop):
1. git clone <repo> (or git pull)
2. /sync import
3. Open project in Claude Code
4. Claude remembers everything from Device A
Keep in sync:
Device A: /sync export → git push (after significant sessions)
Device B: /sync import (after git pull)
Setup (one-time)
The .claude-context/ folder is already part of the repo (committed). The scripts are in scripts/. No dependencies — pure Python stdlib.
Make sure .claude-context/ is not in .gitignore (this is intentional — it's the sync mechanism). The *.jsonl exclusion in .gitignore prevents conversation history from being committed.
What Gets Synced
| File type | Synced | Why |
|---|---|---|
MEMORY.md |
✅ | Project memory index |
memory/*.md |
✅ | Individual memory entries |
project_*.md |
✅ | Project context docs |
feedback_*.md |
✅ | Behavioral feedback |
user_*.md |
✅ | User profile context |
reference_*.md |
✅ | Reference links |
*.jsonl |
❌ | Conversation history — private, too large |
*.json |
❌ | Settings — may contain keys |
Source: veekunth217/claude-scaffold-skill — distributed by TomeVault.