EGM 同步上游变更
将 marcus-darden/egm 的变更引入本移植项目,同时不丢失移植的重命名、Cursor 适配或移植独有文件。首先阅读仓库根目录下的 AGENTS.md:它是本技能应用的规则手册。本技能位于 .cursor/skills/ 目录下,因此是项目本地的;install.sh 永远不会安装它。
调用方式:/egm-sync-upstream [--dry-run] [upstream-ref]。upstream-ref 默认为上游的 main。使用 --dry-run 时,执行步骤 1 到 4 并打印计划,不进行任何更改。
硬性规则
- 绝不推送到
main。 所有工作在sync/upstream-<shortsha>分支上进行;由 Juan 合并 PR。 - 绝不覆盖移植独有文件(列表见
AGENTS.md)。如果上游更改了这些文件镜像的内容(例如上游 README 的安装部分),请在报告中描述而不是覆盖。 - 不要猜测。 新的上游技能或代理、已删除的上游文件、已重命名的上游文件以及合并冲突都应通过精确路径标记给 Juan,而不是通过发明名称或选择一方来解决。
- 保留归属。
LICENSE保留两行版权声明;UPSTREAM_COMMIT仅在分支实际包含上游变更时才移动。
辅助工具
<skill-dir> 是存放本文件的文件夹。
python3 <skill-dir>/scripts/port_names.py path <upstream-path>...将上游路径映射到移植路径,并说明某个路径是移植独有、未移植或新增。python3 <skill-dir>/scripts/port_names.py merge <clone> <old> <new> <upstream-path> [--write]对一个上游文件变更与其移植文件进行三方合并(基础版本和他们的版本先转换为移植名称,我们的版本是移植文件),从而保留未改动行中的 Cursor 适配。退出码 0 表示干净,1 表示冲突标记残留,3 表示无需操作(移植独有、未移植或新增)。python3 <skill-dir>/scripts/port_names.py rewrite <file>/check <file>...在文本中重写名称,或列出残留的旧斜杠名称。bash <skill-dir>/scripts/check_port.sh运行所有移植检查(步骤 7)。
工作流程
EGM 同步进度:
- [ ] 步骤 1:工作区干净,位于最新的 main 分支
- [ ] 步骤 2:上游仓库克隆到临时目录;old = UPSTREAM_COMMIT,new = 上游 HEAD
- [ ] 步骤 3:读取自 old 以来的 CHANGELOG 条目;列出变更文件及其状态
- [ ] 步骤 4:每个变更文件被映射并分类(合并 / 标记 / 跳过)
- [ ] 步骤 5:创建分支 sync/upstream-<shortsha>;应用合并;重新检查适配
- [ ] 步骤 6:更新 UPSTREAM_COMMIT
- [ ] 步骤 7:check_port.sh 通过
- [ ] 步骤 8:编写 SYNC-REPORT.md
- [ ] 步骤 9:推送分支;使用 gh 创建 PR;通知 Juan
步骤 1:干净的起点
git status --porcelain 必须为空,且当前分支为 main,与 origin/main 保持同步(git fetch origin && git status -sb)。如果不满足,停止并告诉 Juan 哪些文件是脏的。不要暂存他的工作。
步骤 2:获取上游
tmp="$(mktemp -d)"
git clone --quiet https://github.com/marcus-darden/egm.git "$tmp/upstream"
old="$(cat UPSTREAM_COMMIT)"; new="$(git -C "$tmp/upstream" rev-parse ${REF:-HEAD})"
如果 old == new,报告“上游自 以来没有新提交”并停止(不创建分支,不创建 PR)。如果 old 不是 new 的祖先(git -C "$tmp/upstream" merge-base --is-ancestor $old $new),上游已重写历史:停止并标记该情况。
步骤 3:读取变更内容
git -C "$tmp/upstream" log --oneline $old..$newgit -C "$tmp/upstream" diff --name-status -M $old $new- 新的
CHANGELOG.md条目(包含old的标签之后的所有标签)。CHANGELOG 解释了原因;在报告中引用相关行。
步骤 4:映射和分类每个文件
对每个变更的上游路径运行 port_names.py path,然后将其放入一个桶中:
| 上游状态和映射 | 桶 |
|---|---|
| M,映射到现有移植文件 | 合并 |
A,映射到已知的技能文件夹(例如 skills/goldfish/ 中的新资产) | 合并(创建文件) |
| A,新技能、代理或演练(“NEW” 标记) | 标记:Juan 决定 egm- 名称以及是否移植 |
| D(上游已删除) | 标记:不要删除移植文件 |
| R(上游重命名) | 标记:显示旧路径和新路径以及移植路径 |
| 移植独有或未移植标记 | 跳过(如果重要则描述变更,例如 README 安装文本) |
LICENSE | 合并,然后确认两行版权声明仍然存在 |
使用 --dry-run 时,打印此表格和提交列表,然后停止。
步骤 5:在分支上应用变更
git switch -c "sync/upstream-${new:0:7}"
python3 <skill-dir>/scripts/port_names.py merge "$tmp/upstream" $old $new <upstream-path> --write
对每个合并文件运行一次合并。如果退出码为 1,则将冲突标记排除在提交之外:恢复该文件(git checkout -- <port-path>),并在报告中记录冲突片段给 Juan。合并后,重新读取每个触及的文件中 AGENTS.md 列出的 Cursor 适配(Task-tool 生成文本、代理中的 readonly: true、CLAUDE.md 旁的 AGENTS.md、审查者忽略 AGENTS.md 和 .cursor/rules/、使用 /review 而不是 /code-review、runtimes.md 中的 Cursor 行)。如果上游添加了需要适配的新位置(例如新的 CLAUDE.md 查找、代理中新的 tools: 行、新的 Claude 专用生成指令),则应用相同的适配并将其列在“适配”部分。按逻辑分组提交,提交消息中引用上游提交。
步骤 6:移动指针
仅当每个合并文件都干净合并或冲突已被记录时,才将 new 写入 UPSTREAM_COMMIT。将其作为最后一个提交。
步骤 7:检查
bash <skill-dir>/scripts/check_port.sh。它检查名称是否等于文件夹名称、求值 JSON、check_gate.py(先 BLOCKED 后 GO)、残留的旧斜杠名称、以及对临时目录的测试安装(该目录不得包含本技能)。修复由合并引起的失败;其他任何问题都放入报告。
步骤 8:SYNC-REPORT.md
在分支的仓库根目录写入 SYNC-REPORT.md,并使用与 PR 主体相同的文本:
# 上游同步:<old7>..<new7>
上游提交:<列表>
CHANGELOG:<引用的行>
## 已应用
- <移植路径> <- <上游路径>:<一行摘要>
## 针对 Cursor 的适配
- <移植路径>:<哪种适配及原因>
## 已跳过
- <上游路径>:<移植独有 | 未移植>,<变更内容(如果重要)>
## 标记给 Juan
- <新技能/代理、删除、重命名、冲突>:<路径、片段、问题>
## 检查
<check_port.sh 摘要>
## 待解决问题
- <...>
步骤 9:推送并创建 PR
git push -u origin "sync/upstream-${new:0:7}"
gh pr create --base main --head "sync/upstream-${new:0:7}" --title "同步上游 marcus-darden/egm ${old:0:7}..${new:0:7}" --body-file SYNC-REPORT.md
告知 Juan PR 的 URL、计数(已应用、已适配、已跳过、已标记)以及标记的项目。不要合并 PR。删除临时克隆。