关于 merge-change
引导开发者将已完成的变更集成到基准分支的技能,规定了预检、独立审查、验证记录、测试门禁及签名压缩合并的交接流程。
主要功能
- 01同步基准分支并处理冲突
- 02审查前后执行预检
- 03独立审查并处置问题
- 04编写验证记录并运行测试门禁
- 05交接签名压缩合并
适用场景
- 01集成已审查的变更
- 02在工作树中解决合并冲突
- 03合并前记录测试证据
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jan-jan/ guardrails --skill merge-change安装后在 Claude Code 中输入/merge-change确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/cleanup-rejections.mdmulti-unit.mdrationale.mdreview-checklist.md详细介绍
合并变更
开始时宣布:“正在使用 merge-change 技能集成此变更。”
前提条件
-
develop-change已完成,其测试套件通过。 -
第 7 步之前的每一步都在变更工作树中运行。
-
基础分支是主检出目录的分支。只检测一次:
BASE=$(. .guardrails/scripts/lib.sh && gr_base_branch) && [ -n "$BASE" ]
步骤
任何一步失败都立即停止。将修复任务派发到嵌套任务工作树 .worktrees/<change-branch>-<tag>,其名称在提示词中给出,并通过 sh .guardrails/scripts/task-worktree.sh start <tag> 创建。
任务报告通过后,运行
sh .guardrails/scripts/task-worktree.sh merge <tag>,以合并任务分支、移除工作树并删除任务分支。然后从第 1 步重新运行。
每次派发都必须使用唯一的 tag。
-
将最新的基础分支合并到工作树分支:
primary=$(git worktree list --porcelain | sed -n '1s/^worktree //p') [ -n "$primary" ] || { echo "no primary checkout" >&2; exit 1; } git -C "$primary" diff --cached --quiet || case $? in (1) echo "the index of $primary is not empty" >&2; exit 1 ;; (*) echo "cannot read the index of $primary" >&2; exit 1 ;; esac if [ -n "$(git remote)" ]; then git fetch origin || { echo "fetch failed — fix it before merging" >&2; exit 1; } base_ref="origin/$BASE" else base_ref=$BASE fi reviewed=$(git rev-list -1 --grep='^review dispatched$' "$base_ref..HEAD") [ -z "$reviewed" ] || git diff --name-only --no-renames "$(git merge-base "$reviewed" "$base_ref")" "$base_ref" | grep -Fx "$(git diff --name-only --no-renames "$base_ref...HEAD")" | sed 's/^/shared: /' git merge "$base_ref"- 如果主检出目录中有暂存的 squash 操作,必须停止:告知用户;待用户签署或清除后,重新运行此步骤。
- 获取失败则必须停止。从具备权限的会话获取,或者合并本地引用并在第 7 步的
Base:trailer 中注明。 - 只有当项目的 AGENTS.md 和
Base:trailer 都说明原因时,项目才可以将远程排除在范围之外。切勿类推采用这一做法。 - 在此处解决冲突,绝不能在基础分支上解决,然后重新运行。
- 如果 ledger 文件发生 add/add 冲突:保留基础分支的文件,运行
git checkout --theirs -- <path>;先将此变更的版本保存为新的 slug,运行git show :2:<path> > <new path>。然后对两个文件运行git add,并在此变更引用该文件的所有位置写入新名称。 shared:行表示此变更编辑的文件在上次审查提交之后也被基础分支修改过:此时第 4 步要重新审查整个变更,即使已完成最后一轮审查也不例外。否则,重新运行门禁即可覆盖。
-
在审查前运行预检:
sh .guardrails/scripts/merge-preflight.sh --before-review <change-branch>如果第 1 步将远程排除在范围之外,则在此处和第 6c 步都传入
--local-base。如果某一轮不派发审查者,则跳过此步骤:第 6c 步会运行所有此处运行的检查。阅读通过的检查输出的警告。第一个失败的检查会终止运行,并输出其工具的结果和一行
fix <CHECK>::退出码 1 表示检查失败;退出码 2 表示预检或其运行的工具发生用法或环境错误。遵循fix行的指示,但第 3 步另有规定的情况除外。 -
处理预检无法判定的事项。
MALFORMED-ID:使用new-id.sh为该项分配 ID。绝不能扩大匹配模式以接受该 token。- 如果
DRAFT-FILE指向的路径并非此变更新增,则表示另一个变更遗留了草稿名称,应由单独的变更重命名。
-
独立审查——派发一位新的子 Agent(或在团队政策要求时派发人工审查者),提供 diff、计划,以及此变更声称或修订的每个 ID 的
.guardrails/scripts/find-items.sh show输出,不得提供实现说明或聊天记录。 在提示词中注明其工作树路径:.worktrees/<change-branch>-review,通过task-worktree.sh start review创建。先标记其审查的提交:git -c commit.gpgsign=false commit --allow-empty -m 'review dispatched'审查者需回答:代码是否满足每项声称的 REQ/LLR;如果
verifies:所指的行为失效,每项测试是否都会失败;每个声称的 ID 是否都包含异常输入用例(B/C 类);是否存在未要求的行为(未标记的派生工作)。每一轮都要向审查者提供
references/review-checklist.md。审查者只运行自己判定需要运行的测试,并报告测试数量:每个声称 ID 的
verifies:测试、diff 新增或修改的每个测试文件中的测试,或在测试运行器无法选择测试时运行整个测试套件。第 6a 步的门禁会运行整个测试套件。对于纯文档 diff,审查者不运行测试;记录中需注明这一点。 每条发现都以其标签开头——code、requirement或record: 实现有误时用code;条目、技能或模板对代码树的行为有误述或遗漏时用requirement;记录、计划或台账对正确工作的描述有误时用record。code或requirement标签后须跟严重程度:high、medium或low。 审查者返回一行结论。逐字复制发现内容。无论标签是什么,每条发现都会让流程退回第 1 步。 标签只决定是否派遣另一位审查者。 不在原处处理
record发现是有意为之。**如果某一轮的
code和requirement发现(如有)全是low,则该轮就是最后一轮审查。**修复每条修复方式明确的低严重程度发现,并将其余发现记录为未解决问题项; 不再派遣审查者。 后续轮次的审查者会阅读前几轮写下的记录。第 5 步移除计划后,使用git show <commit>^:<path>读取计划,其中 是git log -1 --format=%H --grep='^chore: drop plans$'。6a 门禁失败的修复会派遣审查者,除非它只涉及记录。
报告到达时,移除每一轮创建的审查工作树:
sh .guardrails/scripts/task-worktree.sh remove reviewremove会拒绝含有提交的审查分支。将每个提交记录为一条发现,然后运行task-worktree.sh discard review;绝不要合并它们。- 并非每一轮都会创建工作树:人工审查者会使用自己的检出。对于从未启动的标签,
remove会失败,因此跳过。 remove既会拒绝未跟踪文件,也会拒绝已修改文件。其中不包含任何被 git 忽略的文件。将值得保留的内容复制到记录中,然后阅读临时文件、将其删除,再次运行remove。
将每项修复派遣到
.worktrees/<change-branch>-<tag>。A 类可跳过此步骤;B 类需要一位审查者;C 类需要彻底审查;对于 C 类,可考虑为关键条目安排两位独立审查者。 -
验证记录——编写记录,将
.guardrails/templates/verification.md复制到docs/verification/<date>-<branch>.md,或在文件已存在时更新:只填写缺失内容(<…>占位符也算缺失),并提交但不签名:计划中的**Implements:**值(没有计划时用 ID)、 此更改解决、接受或新增的 ID、 计划中的red -> green:和所有inherited:行、其指南审查(## Guideline reviews)、审查者的结论以及每条发现及其处理结果。重新运行时,按第 4 步的查找方式读取计划。然后:sh .guardrails/scripts/drop-plans.sh --base "$BASE"它会根据本地基分支还原或删除此更改新增或编辑的每个计划;第 7 步会将此基分支压缩合并。
如果记录未包含全部四个且均有值的字段(
branch:、reviewer:、verdict:、reproduced:),6c 会拒绝该记录:INCOMPLETE-RECORD。
6a. 按 verify-before-merge 的说明派遣验证套件,并根据其返回的门禁摘要中的通过数和失败数读取结论。
记录摘要所描述的代码树,并将记录放在摘要旁边:
git rev-parse HEAD^{tree} # clean worktree, at dispatch
6b. 确认 6a 的摘要描述的是此代码树,收到摘要时执行:
git status --porcelain # must be empty
git rev-parse HEAD^{tree} # the step 6a hash
哈希相同且工作树干净:6a 的摘要有效。否则重新派遣验证套件。
6c. 再次运行预检,并执行审查检查:
sh .guardrails/scripts/merge-preflight.sh <change-branch>
按第 2 步的方式读取结果。绝不要通过删除发现来应对 REVIEW 失败。
6d. **移除预检列出的工作树。**按 NESTED-WORKTREE 失败列出的修复说明清理每个工作树:
task-worktree.sh remove <tag>;对于 remove 因提交而拒绝的工作树,先记录这些提交,再运行 discard <tag>;对于 task-worktree.sh 未创建的工作树,使用普通 git 命令。如果 remove 因工作树不干净而拒绝,按第 4 步给出的方式处理。
在其他位置注册的工作树不属于此步骤的处理范围。
7. 将更改压缩合并到基准分支,然后交由用户签名。 在主检出中,切换到基准分支:
git diff --cached --quiet && git merge --squash <branch>
git diff --cached --quiet <branch>
如果索引中的内容恰好与该分支的树一致,第二行会以状态码 0 退出: 继续写提交说明。否则:
- 第一行输出了文件“would be overwritten”:主检出存在未提交更改。停止并告知用户。
- 输出了其他内容:基准分支在步骤 1 之后发生了变动。运行
git reset --merge,然后从步骤 1 重新开始,作为一次基准变动轮次: 仅当第 2 至第 4 步处于shared:行时才执行;将其视为一次评审轮次,重新运行后进入第 5 步;然后执行第 6a 至第 7 步。 - 没有输出:已经暂存了压缩提交,可能属于其他更改,也可能是此更改的旧版本。停止并告知用户;用户签名或清除此内容后,从步骤 1 重新开始。
将提交说明写入仓库之外的文件:
<type>: <summary>
Implements: <final REQ/RC/SDD/LLR IDs>
Resolves: <PR IDs this change closes>
Opens: <PR IDs this change raises>
Verified: docs/verification/<record file>
Base: <base ref> at <its commit>
Gate-tree: <step 6a tree>
Gate: <verify_commands entry> <passed>/<total>
Gate: <coverage_command> <figure>
如果 Resolves: 或 Opens: 行为空,则省略该行。为每个
verify_commands 条目写一行 Gate:,并在已配置的情况下为
coverage_command 写一行。<its commit> 是 git rev-parse <base ref>。
所有尾注都放在最后一段,其中包括 Co-Authored-By:。
移交之前,将索引与步骤 6a 的树进行比较:
[ "$(git write-tree)" = <step 6a tree> ]
如果不一致:运行 git reset --merge,然后从步骤 1 重新开始。
将这条填好内容的命令交给用户,然后停止:
git commit -S -F /tmp/merge-<branch>.msg && sh .guardrails/scripts/finish-merge.sh <branch>
告诉用户签名时可能需要触碰硬件密钥。成功时会以三行
finish-merge: 结束;否则,让用户粘贴输出。
finish-merge.sh 会在删除任何内容之前验证五项内容。传入分支名,绝不要传入路径。如果工作树由测试框架创建,请使用测试框架工具保留或移除它。
如果该分支没有注册工作树,脚本仍会删除该分支。
如果因未配置密钥而导致签名失败,停止,并指引用户查看 ratchet 设置清单。不得使用无签名的替代方案。
8. 汇报。 用户告知成功后,从消息文件中写一行结束语:分支、ID、记录。
不运行任何 git 命令:finish-merge.sh 已验证签名。
建议在进行下一项更改前执行压缩;如果测试框架提供压缩步骤,请指出该步骤,方便用户运行。
如果用户报告问题,或 finish-merge.sh 以非零状态码退出,请阅读
references/cleanup-rejections.md。
修复原因后,仅重新运行该脚本,绝不要重新运行整个组合命令:
sh .guardrails/scripts/finish-merge.sh <branch>
警示信号
| 想法 | 实际情况 |
|---|---|
“我自己运行 git commit -S 就行了,只是一条命令” | 提交属于用户。交付组合命令后停止(步骤 7)。 |
| “脚本拒绝了清理操作;我手动移除工作树和分支” | --force 和 -D 会破坏防护机制拦截的内容。修复原因后重新运行脚本(步骤 8)。 |
完成条件
- 基准分支 HEAD 是本次更改对应的一条压缩提交,其提交说明
写明了记录和受门控的树,
并由
finish-merge.sh验证。 - 更改对应的工作树和分支已由
finish-merge.sh移除。 - 你已汇报合并结果并建议执行压缩。
参考资料
.guardrails/units.yaml存在时,在步骤 1 之前阅读references/multi-unit.md。- 步骤 4 调度评审者时,阅读
references/review-checklist.md。 - 步骤 7 或 8 中
finish-merge.sh以非零状态码退出时,阅读references/cleanup-rejections.md。 - 如果这里的规则看起来不合理,或准备提议修改规则,请阅读
references/rationale.md。