一、背景
接入 Codex / Claude Code 这类 Agent 后,规则文件会逐渐分散在多个位置:
text
# 项目目录
AGENTS.md
CLAUDE.md
references/
# 家目录
~/.codex/AGENTS.md
~/.claude/CLAUDE.md
这些文件有几个明显问题:
-
不想提交到业务仓库
规则文件主要影响 AI 行为,其中一部分还带有个人工作习惯,不一定属于业务代码。直接提交到业务仓库,会污染业务仓库的提交记录。
-
加入
.gitignore又无处追踪规则文件虽然不适合进入业务仓库,但它本身仍然需要版本历史。否则换机器、回滚规则、复用配置都会变成手工操作。
-
文件位置比较分散
项目规则在项目目录,全局规则在家目录。多个项目之间还可能复用一部分规则,手工复制很容易漂移。
所以这里真正要解决的是:
规则文件需要被 Git 追踪,但不应该进入业务仓库。
二、目标
| 目标 | 说明 |
|---|---|
| 单一真相源 | 规则文件只在一个仓库中维护,避免多处复制后漂移 |
| 不污染业务仓库 | 业务项目的 git status 不出现这些规则文件 |
| 支持多 scope | 同时管理项目规则和 ~/.codex / ~/.claude 全局规则 |
| 换机器可复现 | clone 规则仓库后执行同步脚本即可恢复 |
| 编辑零感知 | 在业务项目或规则仓库里编辑,改的都是同一份文件 |
三、方案:独立仓库 + symlink + 声明式清单
核心思路:
规则文件的真实位置在
code-rules/仓库,业务项目和家目录里只放指向它的 symlink。
目录结构如下:
text
code-rules/
├── sync-project-a.sh
├── sync-global.sh
├── project-a/
│ ├── links.txt
│ ├── AGENTS.md
│ ├── CLAUDE.md
│ └── references/
└── global/
├── links.txt
├── codex/
│ └── AGENTS.md
└── claude/
└── CLAUDE.md
这里按 scope 拆分:
project-a/:项目级规则global/:全局规则links.txt:声明当前 scope 下哪些文件要链接到哪里sync-*.sh:读取links.txt并创建 symlink
仓库里用非隐藏目录 codex/、claude/,目标位置再链接到 ~/.codex/、~/.claude/。这样仓库端更容易查看和编辑,系统端仍然符合 Agent 默认路径约定。
四、工作流程
AGENTS.md / CLAUDE.md
references/ ..."] LINKS["links.txt
声明式清单"] SCRIPT["sync-*.sh
读清单建 symlink"] LINKS --> SCRIPT end subgraph SYS["系统中的目标位置"] direction TB PROJ["项目目录
~/Desktop/project-a/"] GLOBAL["全局配置
~/.codex/ ~/.claude/"] end EDIT(["日常改规则"]) -->|"直接编辑"| SRC NEW(["新增规则文件"]) -->|"1. 创建文件"| SRC NEW -->|"2. 登记"| LINKS NEW -->|"3. 执行"| SCRIPT SRC -.->|"symlink 透明访问"| PROJ SRC -.->|"symlink 透明访问"| GLOBAL SCRIPT -->|"建立 / 修正 symlink"| PROJ SCRIPT -->|"建立 / 修正 symlink"| GLOBAL SRC -->|"git commit"| HISTORY["版本历史
在 code-rules 中追踪"] style REPO fill:#dbeafe,stroke:#2563eb style SYS fill:#f3e8ff,stroke:#9333ea style SRC fill:#fff,stroke:#2563eb style LINKS fill:#fff,stroke:#2563eb style SCRIPT fill:#fff,stroke:#2563eb style PROJ fill:#fff,stroke:#9333ea style GLOBAL fill:#fff,stroke:#9333ea style HISTORY fill:#dcfce7,stroke:#16a34a style EDIT fill:#fef9c3,stroke:#ca8a04 style NEW fill:#fef9c3,stroke:#ca8a04
核心心智模型:
- 规则文件的唯一真相源在
code-rules/ - 系统中的对应路径都是指向它的 symlink
- 日常修改规则时,任意一端编辑都可以,不需要额外同步
- 新增规则文件时,才需要创建文件、登记
links.txt、执行同步脚本 - 版本历史只在
code-rules/中追踪,业务仓库不受影响
五、关键实现
5.1 声明式清单 links.txt
每个 scope 下维护一份 links.txt。
格式:
text
<源相对路径> => <目标绝对路径>
项目规则示例:
text
AGENTS.md => ~/Desktop/project-a/AGENTS.md
CLAUDE.md => ~/Desktop/project-a/CLAUDE.md
references => ~/Desktop/project-a/references
全局规则示例:
text
codex/AGENTS.md => ~/.codex/AGENTS.md
claude/CLAUDE.md => ~/.claude/CLAUDE.md
links.txt 的作用是把"链什么"和"怎么链"拆开。
新增规则文件时,只需要改清单,不需要改脚本。行首加 # 可以暂停某条同步。
5.2 同步脚本 sync-*.sh
同步脚本只做一件事:读取 links.txt,按行创建 symlink。
核心逻辑可以简化成下面这样:
bash
#!/usr/bin/env bash
set -euo pipefail
REPO_DIR="$(cd "$(dirname "$0")" && pwd)"
SCOPE_DIR="$REPO_DIR/project-a"
LINKS_FILE="$SCOPE_DIR/links.txt"
while IFS= read -r raw || [ -n "$raw" ]; do
line="$(printf '%s' "$raw" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
[ -z "$line" ] && continue
case "$line" in \#*) continue ;; esac
src_rel="$(printf '%s' "${line%%=>*}" | sed -e 's/[[:space:]]*$//')"
dst_raw="$(printf '%s' "${line#*=>}" | sed -e 's/^[[:space:]]*//')"
dst="${dst_raw/#\~/$HOME}"
dst="${dst//\$HOME/$HOME}"
src="$SCOPE_DIR/$src_rel"
[ ! -e "$src" ] && {
echo "源缺失: $src_rel"
continue
}
mkdir -p "$(dirname "$dst")"
if [ -L "$dst" ]; then
current="$(readlink "$dst")"
if [ "$current" = "$src" ]; then
echo "已链接: $src_rel => $dst"
continue
fi
rm "$dst"
ln -s "$src" "$dst"
echo "修正: $src_rel => $dst"
continue
fi
if [ -e "$dst" ]; then
echo "冲突: $dst 已是真实文件或目录,未覆盖"
continue
fi
ln -s "$src" "$dst"
echo "新建: $src_rel => $dst"
done < "$LINKS_FILE"
脚本设计保持保守:
| 场景 | 行为 |
|---|---|
| 目标不存在 | 创建 symlink |
| 目标已经是正确 symlink | 跳过 |
| 目标是错误 symlink | 删除后重新创建 |
| 目标是真实文件或目录 | 报冲突,不覆盖 |
| 源文件不存在 | 报错 |
| 清单行被注释 | 跳过 |
这里不自动覆盖真实文件,也不自动清理已存在的 symlink。同步脚本只负责"建立或修正链接",不替用户做删除决策。
5.3 按 scope 拆同步脚本
sync-project-a.sh 和 sync-global.sh 分开维护。
好处是:
- 只想同步项目规则时,不影响全局规则
- 只想同步全局规则时,不影响项目目录
- 后续新增项目 scope 时,只需要新增一个目录和一份同步脚本
六、使用方式
6.1 日常修改规则
可以直接编辑规则仓库里的文件:
bash
vim ~/Desktop/code-rules/project-a/AGENTS.md
也可以编辑业务项目中的 symlink 文件:
bash
vim ~/Desktop/project-a/AGENTS.md
两种方式改的是同一个文件。
修改后只需要在 code-rules 仓库中提交:
bash
git add project-a/AGENTS.md
git commit -m "chore: update agent rules"
6.2 新增规则文件
新增文件时才需要执行同步流程:
bash
# 1. 在对应 scope 下创建文件
mkdir -p project-a/references
# 2. 在 project-a/links.txt 中登记
# references => ~/Desktop/project-a/references
# 3. 执行同步脚本
./sync-project-a.sh
# 4. 提交到规则仓库
git add project-a/links.txt project-a/references
git commit -m "chore: add project references"
6.3 暂停或恢复同步
在 links.txt 中注释对应行即可暂停:
text
# references => ~/Desktop/project-a/references
恢复时去掉 #。
需要注意:注释或删除清单行,不会自动删除系统中已经存在的 symlink。想彻底断开时,手动删除目标 symlink。
6.4 换机器部署
bash
git clone <远程仓库> ~/Desktop/code-rules
cd ~/Desktop/code-rules
./sync-project-a.sh
./sync-global.sh
执行后,业务项目目录和家目录中的 Agent 规则入口就会重新指向 code-rules 中的规则文件。
七、为什么用 symlink
几个备选方案对比:
| 方案 | 问题 |
|---|---|
| 直接提交到业务仓库 | 规则文件进入业务代码历史,边界不清 |
放业务仓库但加入 .gitignore |
规则文件失去版本追踪 |
| 独立仓库 + 手工复制 | 两边容易漂移,编辑时还要确认哪份是最新 |
| 独立仓库 + rsync | 本质仍是复制,需要同步动作,仍可能漂移 |
| 独立仓库 + symlink | 文件系统层面保持单一真相源,编辑零感知 |
symlink 的优势是简单:
- 没有运行时依赖
- 不需要复制文件
- 不需要区分"源文件"和"同步后的文件"
- 任何位置编辑,实际修改的都是同一份内容
同步脚本的职责也很小,只负责把链接建好。
八、小结
这套方案解决的是一个很具体的问题:
Agent 规则文件不想进入业务仓库,但又需要被版本追踪,并且不想分散在多个目录里手工维护。
最终结构是:
text
code-rules 负责追踪
links.txt 负责声明
sync-*.sh 负责建链
业务项目和家目录只负责使用
规则文件的真实内容集中在 code-rules/,业务项目和 Agent 默认目录通过 symlink 访问它。
这样既保留了 Git 版本历史,又避免污染业务仓库;同时项目规则、全局规则也能放在同一个地方统一管理。