mcpField
M
merge-change

merge-change

github.com/jan-jan开发140 次安装4 天前更新
代码仓库

关于 merge-change

引导开发者将已完成的变更集成到基准分支的技能,规定了预检、独立审查、验证记录、测试门禁及签名压缩合并的交接流程。

主要功能

  1. 01同步基准分支并处理冲突
  2. 02审查前后执行预检
  3. 03独立审查并处置问题
  4. 04编写验证记录并运行测试门禁
  5. 05交接签名压缩合并

适用场景

  1. 01集成已审查的变更
  2. 02在工作树中解决合并冲突
  3. 03合并前记录测试证据

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/jan-jan/guardrails --skill merge-change

安装后在 Claude Code 中输入/merge-change确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
cleanup-rejections.md
multi-unit.md
rationale.md
review-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。

  1. 将最新的基础分支合并到工作树分支:

    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 步要重新审查整个变更,即使已完成最后一轮审查也不例外。否则,重新运行门禁即可覆盖。
  2. 在审查前运行预检:

    sh .guardrails/scripts/merge-preflight.sh --before-review <change-branch>
    

    如果第 1 步将远程排除在范围之外,则在此处和第 6c 步都传入 --local-base。如果某一轮不派发审查者,则跳过此步骤:第 6c 步会运行所有此处运行的检查。

    阅读通过的检查输出的警告。第一个失败的检查会终止运行,并输出其工具的结果和一行 fix <CHECK>::退出码 1 表示检查失败;退出码 2 表示预检或其运行的工具发生用法或环境错误。遵循 fix 行的指示,但第 3 步另有规定的情况除外。

  3. 处理预检无法判定的事项。

    • MALFORMED-ID:使用 new-id.sh 为该项分配 ID。绝不能扩大匹配模式以接受该 token。
    • 如果 DRAFT-FILE 指向的路径并非此变更新增,则表示另一个变更遗留了草稿名称,应由单独的变更重命名。
  4. 独立审查——派发一位新的子 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 review
    
    • remove 会拒绝含有提交的审查分支。将每个提交记录为一条发现,然后运行 task-worktree.sh discard review;绝不要合并它们。
    • 并非每一轮都会创建工作树:人工审查者会使用自己的检出。对于从未启动的标签,remove 会失败,因此跳过。
    • remove 既会拒绝未跟踪文件,也会拒绝已修改文件。其中不包含任何被 git 忽略的文件。将值得保留的内容复制到记录中,然后阅读临时文件、将其删除,再次运行 remove。

    将每项修复派遣到 .worktrees/<change-branch>-<tag>。A 类可跳过此步骤;B 类需要一位审查者;C 类需要彻底审查;对于 C 类,可考虑为关键条目安排两位独立审查者。

  5. 验证记录——编写记录,将 .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。

评论

支持 Markdown