Move Project
把项目迁到新目录,并同步所有"按项目绝对路径索引"的 per-project 数据。路径一变这些数据全部失配,本 skill 保证会话历史不丢、配置不错位、工具不跳错路径。
核心概念
许多工具按项目绝对路径索引数据,路径变了索引就断。迁移 = 搬项目 + 让所有索引跟上。
Claude Code 的索引规则是关键:绝对路径中的 / 全替换为 -。如 /Users/x/foo → -Users-x-foo。编码按字符串路径算、不解析软链——同一物理项目经两条路径访问会各产生一份编码目录与会话,互不相通。
claude-mem 的索引规则不同:它按 git toplevel 的 basename 索引 project。搬家不改名无需迁移;仅改目录名时才需归并 observations.project 与 sdk_sessions.project。
迁移前盘点
1. 确认真实物理路径
用 readlink -f 或比对 stat 的 inode 排除软链——"看似两个副本、实则同一物理目录"很常见,把软链当副本会迁错对象。以用户实际打开项目的路径作为编码基准,而非软链别名。
2. 调查目标环境
不要假设目标只是一个空目录。必须先摸清目标目录的约定,否则搬过去会破坏现有结构。
必查项:
- 目标路径是否已是 git 仓库或 git 仓库的子目录?运行
git rev-parse --show-toplevel。
- 目标目录中现有子项目是普通目录、git submodule、worktree 还是 subtree?
ls -la <target-parent>/*/.git:若出现 .git 文件且内容为 gitdir: ...,说明是 submodule。
cat <target-root>/.gitmodules:读取 submodule URL 与路径命名模式。
git ls-files <target-parent>/<existing-child>:若只返回目录本身,说明是 submodule gitlink。
- 目标目录的命名约定是什么?(前缀、日期格式、kebab-case 规则)
- 目标仓库是否有 README、CLAUDE.md、agents.md 或类似约定文档?优先读取这些文件,它们通常明确记录子项目组织方式、命名规范、remote 命名模式。
- 子项目是否必须拥有独立 remote?remote 命名模式是什么?(如
demo-<name>)
- 子项目默认 private 还是 public?是否需要用户明确可见性?
3. 排查路径索引数据
不要假设清单完备——逐项排查:
- Claude Code 会话历史:
~/.claude/projects/<编码>
- Claude Code 缓存:
~/Library/Caches/claude-cli-nodejs/<编码>
~/.claude.json:projects 对象,key 是绝对路径
- Claude Code 项目列表:
~/.config/projects.json(若已注册)
- 编辑器与工具的路径记录:VS Code Project Manager / Favorites / 最近工作区、zoxide 访问历史等
- claude-mem(
~/.claude-mem/claude-mem.db):按 git toplevel basename 索引
执行顺序
阶段 1:停占用进程
不是所有占用都必须停止。分级处理:
| 类型 |
示例 |
处理方式 |
| 必须自动停止 |
node dev server、git fsmonitor daemon、build watcher、测试进程 |
lsof +D <old-path> 定位后停止 |
| 需用户知情 |
VS Code、Cursor、JetBrains、Typora 等前台编辑器 |
提醒用户迁移后重新打开新路径,不强制关闭 |
| 可容忍 |
只读打开的文件、shell cwd |
mv 后句柄自然失效,无需预处理 |
迁移前用 lsof +D <old-path> 输出占用清单,明确告诉用户哪些需要其手动配合。
阶段 2:搬项目
同卷 mv 为原子操作,跨卷为复制。
- 普通目录:直接
mv <old> <new>。
- 保留 gitignore 的本地目录(如
zRefs、.output、dist)应一并迁移,除非用户明确不要。
node_modules、.pnpm-store 通常不迁移,目标位置重新安装。
- 若目标为 git submodule 集合,见下方"场景 A"。
阶段 3:同步索引
按顺序执行:
备份 ~/.claude.json、~/.config/projects.json、~/.claude-mem/claude-mem.db。
修改 ~/.claude.json 中 projects 的 key:旧路径 → 新路径。
修改 ~/.config/projects.json 中的 rootPath。
重命名 Claude Code 会话目录:~/.claude/projects/<旧编码> → ~/.claude/projects/<新编码>。
重命名 Claude Code 缓存目录:~/Library/Caches/claude-cli-nodejs/<旧编码> → ~/Library/Caches/claude-cli-nodejs/<新编码>。
替换会话历史与缓存中的旧绝对路径文本:
find ~/.claude/projects/<新编码> -type f \( -name '*.jsonl' -o -name '*.txt' \) -print0 \
| xargs -0 perl -pi -e 's|<old-path>|<new-path>|g'
find ~/Library/Caches/claude-cli-nodejs/<新编码> -type f \( -name '*.jsonl' -o -name '*.txt' -o -name '*.json' \) -print0 \
| xargs -0 perl -pi -e 's|<old-path>|<new-path>|g'
若目录名改变,归并 claude-mem:
sqlite3 ~/.claude-mem/claude-mem.db "
DROP TRIGGER observations_au;
UPDATE observations SET project = '<new-basename>' WHERE project = '<old-basename>';
UPDATE sdk_sessions SET project = '<new-basename>' WHERE project = '<old-basename>';
CREATE TRIGGER observations_au AFTER UPDATE ON observations BEGIN
INSERT INTO observations_fts(observations_fts, rowid, title, subtitle, narrative, text, facts, concepts)
VALUES('delete', old.id, old.title, old.subtitle, old.narrative, old.text, old.facts, old.concepts);
INSERT INTO observations_fts(rowid, title, subtitle, narrative, text, facts, concepts)
VALUES (new.id, new.title, new.subtitle, new.narrative, new.text, new.facts, new.concepts);
END;
"
清理 zoxide 等工具路径残留:zoxide remove <old-path>(如有记录)。
阶段 4:验证
常见场景处理
场景 A:目标为 git submodule 集合
若目标目录中现有子项目都是 submodule(.git 为文件、父仓库存在 .gitmodules),按此流程:
- 明确 remote 决策(需用户确认):
- 是否创建新 remote?
- remote 命名是否符合目标约定?
- 仓库可见性 private / public?
- 是否在本次迁移中执行
git push?
- 若需要,创建 GitHub/GitLab remote 并设为对应可见性。
- 在目标子目录内添加 remote:
git remote add origin <url>。
- 若用户同意本次 push:commit 必要变更(如
.gitignore)并 push。
- 在父仓库中注册 submodule:
- 优先使用
git submodule add <url> <path>。
- 若
<path> 已存在,先完整备份到独立位置,删除已存在目录,让 git submodule add 创建标准结构,再把本地工作目录内容合并回去。
- 禁止直接
mv <project>/.git <parent>/.git/modules/<path>,极易因目录已存在或嵌套而损坏。
- 若标准命令失败,使用
git update-index --add --cacheinfo 160000,<commit-sha>,<path> 手动写入 gitlink。
- 在父仓库中 stage 并 commit
.gitmodules 与 gitlink。
场景 B:目标为普通目录
直接 mv,然后按需创建或更新目标父仓库的 .gitignore,确保 node_modules、构建产物等不被意外提交。
场景 C:跨卷迁移
mv 会变成复制 + 删除。对大仓库建议用 rsync -aP --exclude=node_modules <old>/ <new>/,然后验证文件完整性再删除旧目录。
关键约束
- 配置文件改动前备份,写临时文件 + 断言(JSON 合法、目标 key 已迁移、其余字段未动)通过后才覆盖——这类文件常被运行时进程持有。
- 修改 .git 结构前必须备份 .git 目录:
cp -a <project>/.git /tmp/<project>-git-backup-$(date +%s)。
- 分阶段删除:不要
rm -rf 可能包含唯一数据的目录。先移动到临时区,验证新结构可用后再删除。
- 命名冲突:目标若已有同名或相关项目,先与用户确认并存还是取代。
- 每次以盘点为准:编码规则、缓存与配置路径因 OS 与工具版本而异,不照搬固定清单。
用户必须拍板的决策
以下选择会显著影响迁移结果,skill 不应替用户默认:
- 目标子项目命名(是否使用日期前缀、具体日期选择)。
- 是否创建 remote、remote 名称、可见性、是否立即 push。
- 是否迁移被 gitignore 的大目录(如
zRefs 可能很大)。
- 是否保留旧目录作为软链或备份。
- 如何处理目标仓库中已有的未提交改动。
- 是否同步迁移
~/.config/projects.json 等编辑器项目列表。
1---2name: move-project3description: 把项目迁移到新目录,并同步所有按项目绝对路径索引的数据(Claude Code 会话历史、缓存、配置,以及编辑器与工具的路径记录),避免迁移后会话丢失或工具跳错路径。4---56# Move Project78把项目迁到新目录,并同步所有"按项目绝对路径索引"的 per-project 数据。路径一变这些数据全部失配,本 skill 保证会话历史不丢、配置不错位、工具不跳错路径。910## 核心概念1112许多工具按**项目绝对路径**索引数据,路径变了索引就断。迁移 = 搬项目 + 让所有索引跟上。1314Claude Code 的索引规则是关键:绝对路径中的 `/` 全替换为 `-`。如 `/Users/x/foo` → `-Users-x-foo`。编码按**字符串路径**算、不解析软链——同一物理项目经两条路径访问会各产生一份编码目录与会话,互不相通。1516claude-mem 的索引规则不同:它按 **git toplevel 的 basename** 索引 project。搬家不改名无需迁移;仅改目录名时才需归并 `observations.project` 与 `sdk_sessions.project`。1718## 迁移前盘点1920### 1. 确认真实物理路径2122用 `readlink -f` 或比对 `stat` 的 inode 排除软链——"看似两个副本、实则同一物理目录"很常见,把软链当副本会迁错对象。以用户**实际打开项目**的路径作为编码基准,而非软链别名。2324### 2. 调查目标环境2526不要假设目标只是一个空目录。必须先摸清目标目录的约定,否则搬过去会破坏现有结构。2728必查项:2930- 目标路径是否已是 git 仓库或 git 仓库的子目录?运行 `git rev-parse --show-toplevel`。31- 目标目录中现有子项目是普通目录、git submodule、worktree 还是 subtree?32 - `ls -la <target-parent>/*/.git`:若出现 `.git` 文件且内容为 `gitdir: ...`,说明是 submodule。33 - `cat <target-root>/.gitmodules`:读取 submodule URL 与路径命名模式。34 - `git ls-files <target-parent>/<existing-child>`:若只返回目录本身,说明是 submodule gitlink。35- 目标目录的命名约定是什么?(前缀、日期格式、kebab-case 规则)36- 目标仓库是否有 README、CLAUDE.md、agents.md 或类似约定文档?**优先读取这些文件**,它们通常明确记录子项目组织方式、命名规范、remote 命名模式。37- 子项目是否必须拥有独立 remote?remote 命名模式是什么?(如 `demo-<name>`)38- 子项目默认 private 还是 public?是否需要用户明确可见性?3940### 3. 排查路径索引数据4142不要假设清单完备——逐项排查:4344- Claude Code 会话历史:`~/.claude/projects/<编码>`45- Claude Code 缓存:`~/Library/Caches/claude-cli-nodejs/<编码>`46- `~/.claude.json`:`projects` 对象,key 是绝对路径47- Claude Code 项目列表:`~/.config/projects.json`(若已注册)48- 编辑器与工具的路径记录:VS Code Project Manager / Favorites / 最近工作区、zoxide 访问历史等49- claude-mem(`~/.claude-mem/claude-mem.db`):按 git toplevel basename 索引5051## 执行顺序5253### 阶段 1:停占用进程5455不是所有占用都必须停止。分级处理:5657| 类型 | 示例 | 处理方式 |58|---|---|---|59| 必须自动停止 | node dev server、git fsmonitor daemon、build watcher、测试进程 | `lsof +D <old-path>` 定位后停止 |60| 需用户知情 | VS Code、Cursor、JetBrains、Typora 等前台编辑器 | 提醒用户迁移后重新打开新路径,不强制关闭 |61| 可容忍 | 只读打开的文件、shell cwd | `mv` 后句柄自然失效,无需预处理 |6263迁移前用 `lsof +D <old-path>` 输出占用清单,明确告诉用户哪些需要其手动配合。6465### 阶段 2:搬项目6667同卷 `mv` 为原子操作,跨卷为复制。6869- 普通目录:直接 `mv <old> <new>`。70- 保留 gitignore 的本地目录(如 `zRefs`、`.output`、`dist`)应一并迁移,除非用户明确不要。71- `node_modules`、`.pnpm-store` 通常不迁移,目标位置重新安装。72- 若目标为 git submodule 集合,见下方"场景 A"。7374### 阶段 3:同步索引7576按顺序执行:77781. 备份 `~/.claude.json`、`~/.config/projects.json`、`~/.claude-mem/claude-mem.db`。792. 修改 `~/.claude.json` 中 projects 的 key:旧路径 → 新路径。803. 修改 `~/.config/projects.json` 中的 `rootPath`。814. 重命名 Claude Code 会话目录:`~/.claude/projects/<旧编码>` → `~/.claude/projects/<新编码>`。825. 重命名 Claude Code 缓存目录:`~/Library/Caches/claude-cli-nodejs/<旧编码>` → `~/Library/Caches/claude-cli-nodejs/<新编码>`。836. 替换会话历史与缓存中的旧绝对路径文本:8485 ```bash86 find ~/.claude/projects/<新编码> -type f \( -name '*.jsonl' -o -name '*.txt' \) -print0 \87 | xargs -0 perl -pi -e 's|<old-path>|<new-path>|g'88 89 find ~/Library/Caches/claude-cli-nodejs/<新编码> -type f \( -name '*.jsonl' -o -name '*.txt' -o -name '*.json' \) -print0 \90 | xargs -0 perl -pi -e 's|<old-path>|<new-path>|g'91 ```92937. 若目录名改变,归并 claude-mem:9495 ```bash96 sqlite3 ~/.claude-mem/claude-mem.db "97 DROP TRIGGER observations_au;98 UPDATE observations SET project = '<new-basename>' WHERE project = '<old-basename>';99 UPDATE sdk_sessions SET project = '<new-basename>' WHERE project = '<old-basename>';100 CREATE TRIGGER observations_au AFTER UPDATE ON observations BEGIN101 INSERT INTO observations_fts(observations_fts, rowid, title, subtitle, narrative, text, facts, concepts)102 VALUES('delete', old.id, old.title, old.subtitle, old.narrative, old.text, old.facts, old.concepts);103 INSERT INTO observations_fts(rowid, title, subtitle, narrative, text, facts, concepts)104 VALUES (new.id, new.title, new.subtitle, new.narrative, new.text, new.facts, new.concepts);105 END;106 "107 ```1081098. 清理 zoxide 等工具路径残留:`zoxide remove <old-path>`(如有记录)。110111### 阶段 4:验证112113- [ ] 旧路径在 `~/.claude.json`、`~/.config/projects.json`、Claude 编码目录、缓存目录中无残留。114- [ ] 新路径在所有上述索引中正确存在。115- [ ] `git status`、`git log` 在新位置正常工作。116- [ ] 若为 submodule:`git submodule status` 显示正确的 gitlink 与 commit。117- [ ] 若为 submodule:`.gitmodules` 包含正确条目,父仓库 `.git/config` 同步。118- [ ] `git check-ignore` 验证 `zRefs`、`.output`、`dist`、`node_modules` 被正确忽略。119- [ ] 被保留的 gitignore 目录确实存在于新位置。120- [ ] claude-mem 中旧 project 名称为 0 条,新 project 名称数量正确。121- [ ] session 历史与缓存文件中无旧绝对路径文本残留。122- [ ] 旧目录已清空或按用户要求保留为软链/备份。123124## 常见场景处理125126### 场景 A:目标为 git submodule 集合127128若目标目录中现有子项目都是 submodule(`.git` 为文件、父仓库存在 `.gitmodules`),按此流程:1291301. 明确 remote 决策(需用户确认):131 - 是否创建新 remote?132 - remote 命名是否符合目标约定?133 - 仓库可见性 private / public?134 - 是否在本次迁移中执行 `git push`?1352. 若需要,创建 GitHub/GitLab remote 并设为对应可见性。1363. 在目标子目录内添加 remote:`git remote add origin <url>`。1374. 若用户同意本次 push:commit 必要变更(如 `.gitignore`)并 push。1385. 在父仓库中注册 submodule:139 - 优先使用 `git submodule add <url> <path>`。140 - 若 `<path>` 已存在,**先完整备份到独立位置**,删除已存在目录,让 `git submodule add` 创建标准结构,再把本地工作目录内容合并回去。141 - **禁止直接 `mv <project>/.git <parent>/.git/modules/<path>`**,极易因目录已存在或嵌套而损坏。1426. 若标准命令失败,使用 `git update-index --add --cacheinfo 160000,<commit-sha>,<path>` 手动写入 gitlink。1437. 在父仓库中 stage 并 commit `.gitmodules` 与 gitlink。144145### 场景 B:目标为普通目录146147直接 `mv`,然后按需创建或更新目标父仓库的 `.gitignore`,确保 `node_modules`、构建产物等不被意外提交。148149### 场景 C:跨卷迁移150151`mv` 会变成复制 + 删除。对大仓库建议用 `rsync -aP --exclude=node_modules <old>/ <new>/`,然后验证文件完整性再删除旧目录。152153## 关键约束154155- **配置文件改动前备份**,写临时文件 + 断言(JSON 合法、目标 key 已迁移、其余字段未动)通过后才覆盖——这类文件常被运行时进程持有。156- **修改 .git 结构前必须备份 .git 目录**:`cp -a <project>/.git /tmp/<project>-git-backup-$(date +%s)`。157- **分阶段删除**:不要 `rm -rf` 可能包含唯一数据的目录。先移动到临时区,验证新结构可用后再删除。158- **命名冲突**:目标若已有同名或相关项目,先与用户确认并存还是取代。159- **每次以盘点为准**:编码规则、缓存与配置路径因 OS 与工具版本而异,不照搬固定清单。160161## 用户必须拍板的决策162163以下选择会显著影响迁移结果,skill 不应替用户默认:1641651. 目标子项目命名(是否使用日期前缀、具体日期选择)。1662. 是否创建 remote、remote 名称、可见性、是否立即 push。1673. 是否迁移被 gitignore 的大目录(如 `zRefs` 可能很大)。1684. 是否保留旧目录作为软链或备份。1695. 如何处理目标仓库中已有的未提交改动。1706. 是否同步迁移 `~/.config/projects.json` 等编辑器项目列表。