背景
我用 ~/workspace/ 作为所有项目的统一工作目录。每个项目有独立的子目录加 .hermes-status/ 进度跟踪。这个结构用了一段时间,但有一个项目从最开始就放在了外面——公众号博客项目,在 ~/ruite-ai-practice/。
当初没想那么多,直接就在家目录下建了项目。后来 workspace 结构成熟了,就产生了一个问题:怎么把它移进去,同时确保所有引用旧路径的地方都跟着更新?
这个项目有大约 50 处硬编码路径引用,分散在外部脚本、项目内脚本、技能文档三个层级。手动改容易遗漏,少改一处就可能让某个服务在运行时找不到路径。
方法论:审计 → 分类 → 执行 → 验证
这次迁移不是 mv + sed 一键搞定,而是按一套方法分步做的。

第一步:审计——发现所有引用
不依赖「我记得有哪些文件」,而是系统化搜索。分五个维度搜索:
# 外部工具脚本
grep -r "ruite-ai-practice" ~/tools/
grep -r "ruite-ai-practice" ~/.hermes/scripts/
# 项目内部脚本
grep -rn "ruite-ai-practice" ~/ruite-ai-practice/scripts/ --include="*.py"
# 技能文档
grep -r "ruite-ai-practice" ~/.hermes/skills/ --include="*.md"
# 配置文件
grep -r "ruite-ai-practice" ~/.hermes/config.yaml
# 别忘了搜索绝对路径形式
grep -rn "/home/liu" ~/ruite-ai-practice/scripts/ --include="*.py"
搜索 ~/ruite-ai-practice/ 是不够的——有些地方写的是 /home/liu/ruite-ai-practice/,还有些是 Path.home() / "ruite-ai-practice"。三种形式都要搜。
第二步:分类——哪些需要改?
把所有引用列出来后,按类型判断是否需要修改:
- 使用
Path(__file__).resolve().parent.parent的相对路径 → 自动适配,不用改 Path.home() / "ruite-ai-practice"→ 需要改路径/home/liu/ruite-ai-practice/(绝对路径)→ 需要改- 项目内
.venv-pelican/、node_modules/→ 随目录移动,不用改 - Git remote、Cloudflare Pages → 远程配置,不变
分类的收益是很大的:你不需要在 50 处引用上花同样的精力。大约一半是随目录移动自动解决,剩下的一半才需要手动改。
第三步:按优先级执行
优先级按”改了之后会不会立即影响在跑的服务”来排:
- 核心运行脚本(改了立即影响服务)
- pipeline server:
WORK_DIR和PIPELINE_SCRIPT两处 - ntfy listener:
CREATE_PIPELINE和TOPIC_MANAGER两处 -
webhook 订阅:prompt 中的
cd路径 -
项目内脚本(改了影响下次构建)
create-pipeline.py中的REPO_DIRt5-update-feishu.py中的os.chdir()-
gen_sensenova_images.py中的base_dir -
技能文档(改了影响下次使用)
-
5 个技能文件 + references,约 30 处引用
-
Memory 和状态文档(冷了再改也不迟)
每改完一个阶段验证一次,不等到最后再统一验证。
第四步:验证
验证清单是逐层展开的:
# 1. 验证核心服务
curl http://localhost:8645/health
# → {"status": "ok"}
# 2. 验证所有对外引用已更新
grep "PIPELINE_SCRIPT\|WORK_DIR\|CREATE_PIPELINE\|TOPIC_MANAGER" \
~/tools/rhook-pipeline-server.py ~/.hermes/scripts/ntfy-pipeline-listener.py
# 3. 验证 Git 工作正常
git status
五个踩坑记录

1. 绝对路径不止一种写法
你以为搜 ruite-ai-practice 就能找到所有引用。但有些地方写的是 /home/liu/ruite-ai-practice/,有的是 Path.home() / "ruite-ai-practice"。只搜关键词会漏掉那些用了 Path.home() 或 os.path.expanduser() 的路径。
解决方法:结合两种搜索——既搜项目名,也搜 /home/liu。
2. 技能文档的引用模式不统一
同一个技能文档里,有的引用是 ~/ruite-ai-practice/,有的是 /home/liu/ruite-ai-practice/,有的是相对路径。直接搜索替换不够精确——每个引用需要审查上下文判断是否要改。
最稳妥的做法:列出所有文件,逐文件审查每个匹配,确认上下文后再改。
3. 入口脚本用绝对路径还是 Path.home()?
Path.home() 在不同环境(cron、SSH、agent)下的行为可能不同。如果你写的脚本可能被多种方式调用,用 Path.home() 更安全。之后如果再迁移,只需要改一处配置或环境变量。
这次 ntfy-pipeline-listener.py 就改成了 Path.home() / "workspace" / "ruite-ai-practice" 而非绝对路径,就是为了不同环境都能正确解析。
4. 运行时服务的中断
改完脚本不等于新路径生效。如果你改了正在运行的脚本,而没有重启进程,运行时仍在用旧路径。
迁移过程中 pipeline server 和 ntfy-bridge 需要重启。改完核心脚本后,要检查并重启相关进程:
ps aux | grep <进程名>
# 杀旧进程 → 启动新进程
5. 旧目录的删除时机
确定所有引用更新完毕、新路径稳定运行之前,不要删旧目录。mv 本身很快(同一文件系统上只是 inode 操作),但 CI/CD 和 cron 任务可能在移动期间执行。旧目录就是你的回退方案。
建议:新路径运行至少 24 小时,确认没有问题后,再清理旧目录。
这套方法不只是搬目录

回顾这次迁移,最有价值的不只是把项目移到了 workspace 下,而是沉淀了一套通用迁移方法:
- 系统化审计 — 不单纯搜索一个关键词,而是分维度搜索(脚本、配置、文档)
- 按风险分阶段 — 先改运行时关键路径,后改冷存储(文档)
- 验证链 — 每阶段完成后验证,不等到最后一并验证
- 回退方案 — 旧路径保留直到新路径稳定运行
这套方法可以用在任何需要全局更新的场景:域名更换、服务器迁移、证书更新、数据库连接串更换。任何涉及「多个分散引用指向同一个资源」的问题,都可以用这套流程。
下次遇到类似的迁移,不再需要从头想方法论了。