关于 fix-pr-review
fix-pr-review 用于处理 GitHub 链接或本地文件中的 PR 审查意见。它对意见分类、应用经验证的修复并检查差异,也可回复和解决 GitHub 讨论串。
主要功能
- 01获取 GitHub 审查数据或读取本地文件
- 02按分类规则归类审查意见
- 03应用修复并进行针对性类型检查
- 04通过 /done 验证修复差异
- 05回复并解决 GitHub 讨论串
适用场景
- 01处理 CodeRabbit 或人工 PR 反馈
- 02根据审查导出文件应用选定修复
- 03修复后解决 GitHub 审查讨论串
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ bhagyamudgal/ skills --skill fix-pr-review安装后在 Claude Code 中输入/fix-pr-review确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/branch-safety.mdfetch-review-data.mdfinal-report.mdgithub-reply-resolve.mdneeds-input-triage.mdper-fix-loop.mdtriage-prompt.mdtriage-rubric.md详细介绍
/fix-pr-review:分类、修复并解决 PR 审查评论
接收来自 CodeRabbit、/review-pr 的 PR 审查结果,或粘贴的文本。对每条发现进行分类,应用经过验证的修复,运行 /done,然后在一个流程中回复并解决 GitHub 对话。
仅当运行仍需要用户做出决定时,才使用 AskUserQuestion:分支安全、暂存确认、有争议条目的确认、--interactive 逐项修复确认、类型检查失败的分类,以及完成后的后续操作。调用 /fix-pr-review 或明确要求修复审查发现,即表示授权执行经过验证的 FIX 计划。不要另行征求计划批准。将每个必需选项标记为具体且已考虑,并按优先级从高到低排列,最优选项后标注“(推荐)”。
快速参考
阶段概览
| 阶段 | 内容 | 关键输出 |
|---|---|---|
| 1 | 前置条件、输入检测、分支安全、基线类型检查 | BASE_SHA、repo_map、baseline_errors |
| 2 | 从 GitHub 或本地文件获取审查数据 | 统一的 Comment[] 数组 |
| 3 | 分类子 Agent:通过 R 规则对每条发现进行分类 | 分类计划(FIX/DISMISS/DEFER/DISAGREE/NEEDS-INPUT) |
| 4 | 计划执行关卡:验证并遵循调用意图 | 可执行计划 |
| 5 | 执行修复:按顺序编辑 + 逐文件窄范围类型检查 | 已修改的文件、每项的 fix_status |
| 5.5 | 收敛子 Agent:类别完整性、逆向风险、新的同类问题 | 每项修复的判定;应用遗漏位置 |
| 6 | /done 验收验证,范围限定为修复差异,不进行提交或发布交接 | 已验证的修复差异、done_verified_snapshot |
| 7 | 在 GitHub 上回复并解决(本地文件则跳过) | 已解决的对话 |
| 8 | 收尾:处理 NEEDS-INPUT、恢复暂存、报告、写入抑制项、后续操作 | 最终报告 |
R 规则摘要(阶段 3 步骤 4;按顺序匹配,命中第一条即止)
| 规则 | 分类 | 要求 |
|---|---|---|
| R1 | DISMISS:自相矛盾/错误 | 无需额外字段 |
| R2 | DISMISS:虚构的文件:行号 | 死链重新定位失败 |
| R4 | DISMISS:已修复 | prior_commit_sha |
| R5 | DISMISS:与 CLAUDE.md 矛盾 | claude_md_quote |
| R3 | DISMISS:纯样式/命名问题 | 未标记为可复用性问题 |
| R10 | NEEDS-INPUT:修复会改变基分支上可观察到的行为 | R10 格式中的 why_unclear |
| R6 | FIX:错误/安全/性能/正确性/可复用性问题 | 长度 ≥30 个字符的 fix_plan、change_class、test_scenario、inverse_risk、class_completeness(含 verdict) |
| R7 | DEFER:问题有效但超出范围 | 跟踪引用 |
| R8 | DISAGREE:存在合理的技术分歧 | disagree_rationale |
| R9 | NEEDS-INPUT:有歧义/需要用户知识 | why_unclear |
R4/R5 排在 R3 之前是有意设计,并非偶然;R10 排在 R6 之前,是为了避免技术上正确的发现仅凭正确性就进入修复流程。完整规则及其排序原因见 references/triage-rubric.md。
关键交叉引用
- R 规则定义:
references/triage-rubric.md,由分类子 Agent 在阶段 3 步骤 4 加载,而非由主 Agent 加载 - 计划验证规则:阶段 4(验证 R 规则所需字段)
- 修复执行路由:阶段 5(执行 R6 FIX 项)
- 收敛:阶段 5.5(使用计划中的
class_completeness+inverse_risk;为fix_status和逐项修复的convergence:报告行提供数据) - 回复验证器:
references/github-reply-resolve.md步骤 7b(GitHub 回复的格式规则) - 报告分组:阶段 8(按 R 分类分组)
- 抑制项:阶段 3“加载审查抑制项”(主 Agent 在派发前读取)+
references/triage-prompt.md(子 Agent 在分类前应用抑制项)+ 阶段 8(写入)
参考文件
每个文件的加载条件都写在其使用位置的指针中。需要时再加载,不要预先加载。
references/fetch-review-data.md:按输入类型获取 GraphQL/REST 数据、CodeRabbit 审查正文结构、Comment架构。主流程在 Phase 2 的 GitHub 获取步骤加载;如果本地文件路径导致未在此处读取,则在Comment架构规范化步骤再次加载。references/triage-prompt.md:完整的 Phase 3 子 Agent 提示词(STEP 0 → STEP 6 + 输出格式)。主流程在 Phase 3 读取,替换占位符后原样传递。references/triage-rubric.md:R1-R10 详情、NEEDS-INPUT 校准、change_class示例、回复格式。分诊 SUBAGENT 在 STEP 4 加载。references/github-reply-resolve.md:发布/解决的 Step 7a-7d 操作。主流程在 Phase 7 加载;本地文件输入时绝不加载。references/final-report.md:Phase 8 报告模板及其渲染规则。主流程在 Phase 8 打印前加载。references/branch-safety.md:包含脱离 HEAD 和不同分支问题的仓库及分支切换流程。对于 GitHub 输入,主流程在 Phase 1 加载;本地文件时跳过。references/per-fix-loop.md:逐项修复循环、交互确认、范围限定的类型检查和基线对比、重试/跳过/中止。主流程在 Phase 5 循环时加载。references/needs-input-triage.md:NEEDS-INPUT 的处理、逐项分诊、Phase 8 修复及验证、GitHub 回复前的预检。仅当数量不为零时,主流程在 Phase 8 第 1 步加载。
此处未附带一个参考文件:${CLAUDE_SKILL_DIR}/../review-pr/references/repo-map.md 包含 repo_map_files / repo_map_exports shell 脚本,是本技能与 /review-pr 共用的唯一副本。若存在 packages/ 或 apps/,主流程会在 Phase 1 加载。
用法
/fix-pr-review https://github.com/owner/repo/pull/123
/fix-pr-review https://github.com/owner/repo/pull/123#pullrequestreview-4089716169
/fix-pr-review https://github.com/owner/repo/pull/123#discussion_r3064352825
/fix-pr-review ./review.md # local output from /review-pr
/fix-pr-review /tmp/review-pr-123-findings.md # local review export
/fix-pr-review # no arg → ask user to paste
可选标志:
--dry-run:显示计划后停止,不执行--interactive:应用每个 FIX 项目前先询问--all-nitpicks:对所有 nitpick 进行完整分诊,而非默认忽略
哪些内容会被分诊
所有审查者的评论都经过相同的分诊流程:CodeRabbit、人类审查者和其他机器人均不例外。全文使用“CodeRabbit”的表述,是因为这是最常见的使用场景。
Phase 1:先决条件 + 输入检测 + 分支安全(主流程)
先决条件检查
command -v gh >/dev/null 2>&1 || { echo "Install gh CLI: https://cli.github.com"; exit 1; }
git rev-parse --is-inside-work-tree >/dev/null 2>&1 || { echo "Not inside a git repo. cd into your clone first."; exit 1; }
gh auth status 2>&1 | grep -q "Logged in" || { echo "Run 'gh auth login' first"; exit 1; }
根据参数检测输入类型
- 包含
#pullrequestreview-<id>→ 审查 URL - 包含
#discussion_r<id>→ 讨论 URL(单条评论 + 讨论串) - 以
.///开头,或以.md结尾 → 本地文件 - 匹配
https://github.com/<owner>/<repo>/pull/<num>且不含片段 → PR URL - 为空 → 请用户粘贴内容或提供 URL
记录执行授权
当原始请求明确调用 /fix-pr-review,或以祈使方式要求修复、处理、应用、解决审查发现的问题时,设置 EXECUTION_AUTHORIZED=true。仅提供 URL,或只要求检查、解释、审查、分诊的只读请求,将其设置为 false。将匹配的请求文本保留为 execution_authorization_evidence;Phase 4 可以用明确的执行选择替换它,Phase 7 会将最终证据传递给 preflight-mutations。
确保仓库和分支正确(仅限 GitHub 输入)
从 URL 中解析 owner、repo 和 number,并按照 ${CLAUDE_SKILL_DIR}/references/branch-safety.md 切换到 PR 分支。现在加载该文件。如果工作区正处于合并、变基或拣选提交过程中,请停止,并在进行任何操作前将冲突解决交由 resolving-merge-conflicts 处理。
自动暂存未提交的工作(分支安全)
此步骤适用于所有输入类型,包括 GitHub 和本地文件。修复绝不会直接覆盖未暂存的 WIP。
git status --porcelain
如果非空,请使用 AskUserQuestion:
问题: 标题:"暂存" 文本:"检测到未提交的更改。是否在应用修复前自动暂存?这些内容将在结束时恢复。" 选项: - 标签:"自动暂存" 描述:"立即暂存更改。运行完成后会恢复这些更改" - 标签:"中止" 描述:"停止。我会先手动提交或暂存工作"
选择“自动暂存”时,运行 git stash push -u -m "fix-pr-review auto-stash $(date +%s)"。仅在成功后,记录 STASH_OID=$(git rev-parse -q --verify refs/stash) 并设置 STASH_PUSHED=true,然后确认 git status --porcelain 为空。推送失败时不记录任何内容,并中止运行,而不是标记一个实际上并未创建的暂存。如果运行中止,用户可在 git stash list 中找到名为 fix-pr-review auto-stash <timestamp> 的工作。
选择“中止”时,打印 "Commit or stash your uncommitted work first." 并退出。
暂存恢复保护。 本次运行中的每次恢复操作(包括此处和每个提前退出处)都只能在 STASH_PUSHED=true 时执行:仅当栈顶与记录的 OID 相同时,才按 OID 应用暂存(git stash apply "$STASH_OID",绝不使用不带参数的 pop);仅在应用成功且栈顶仍相同时才删除暂存;出现任何不匹配时,保留所有条目不作改动,记录 stash_restored: foreign-top,打印 stash_restored: foreign-top, stash left untouched,并继续运行,不应用或删除任何内容。
计算合并基准(供后续检测是否已修复)
BASE_SHA=$(git merge-base "origin/$(gh pr view <url> --json baseRefName -q .baseRefName)" HEAD)
将其暂存为 BASE_SHA,供第 3 阶段的已修复检查使用。
修复前类型检查基线(窄范围类型检查的对照基准)
在第 5 阶段之前运行一次基线类型检查,并捕获已存在错误的文件集合:
bun turbo run check-types 2>&1 | tee /tmp/fix-pr-review-baseline-$$.log
为整个运行定义一个解析器。对于每条诊断,确定其文件,并在移除文件位置、行号和列号信息后,将 <diagnostic code> + <normalized message> 作为标识;重复标识须以多重集形式保留。使用此解析器将第 1 阶段的输出解析到 baseline_errors[path] 中。第 5 阶段和第 8 阶段的所有基线路径都必须使用相同的解析器和标识规范化方式。如果缺少类型检查工具(没有 turbo、tsc),则跳过基线,并将所有修复的窄范围类型检查标记为 skipped。
计算共享包仓库映射(供可复用性分类使用)
盘点共享包和应用,以便第 3 阶段的分类器可以根据现有内容交叉核验有关复用和提取的注释。同时扫描 packages/ 和 apps/。例如,在 NestJS 风格的单体仓库中,跨应用的辅助函数重复很常见,如 apps/backend/src/modules/v1/feature-a/helpers.ts 与 feature-b/helpers.ts,仅扫描 packages/ 会遗漏这类情况。
加载 ${CLAUDE_SKILL_DIR}/../review-pr/references/repo-map.md,并运行其中的 Local mode 块;这是与 /review-pr 共享的同一份 shell。它通过 bash -c 封装 glob,确保其在 zsh 中也能正常工作,并将每部分限制为 500 行,且会标记截断情况。如果存在 packages/ 或 apps/,则加载该文件;如果两者都不存在,则无需运行,改用下方的回退方案。
将两项输出分别暂存为 repo_map_files 和 repo_map_exports,供第 3 阶段的子 Agent 提示词使用。如果 packages/ 和 apps/ 均不存在(非单体仓库),则将两者都设为 N/A (not a monorepo),并将 IS_MONOREPO=false。分类器提示词会据此将 grep 的扫描范围改为 src/ 和仓库根目录。
第 2 阶段:获取评审数据(主流程)
/review-pr 发现的双路径输入
/review-pr 现在会将发现作为单独的行内评论发布,每条发现对应一行特定代码。这些评论会在 GitHub 上创建标准的 PullRequestReviewThread,与 CodeRabbit 线程完全相同。现有的下方 GraphQL 获取逻辑可以直接处理,无需任何特殊解析。
手动导出或旧版的 /review-pr 发现文件使用现有的本地文件路径。对于这些输入,Phase 7 会跳过 GitHub 操作。
从 GitHub 获取(PR URL / review URL / discussion URL)
Phase 1 检测到了这三种输入类型中的一种。现在加载 ${CLAUDE_SKILL_DIR}/references/fetch-review-data.md,并且只运行对应类型的部分。其中包含分页 GraphQL reviewThreads 查询及其 isResolved 过滤条件、/pulls/<num>/reviews/<review_id> 和 /pulls/comments/<comment_id> REST 端点、CodeRabbit review 正文结构,以及 🤖 Prompt for all review comments with AI agents 区块的解析方式。该区块是 nitpick 唯一出现的位置,因为它们不会创建行内线程。
如果获取过程报错,会显示错误并退出,因此不会在评论集不完整的情况下运行分类处理。这包括 GraphQL 速率限制、404、私有仓库,以及分页循环中单页获取失败。遇到 404 时,打印 Couldn't access PR. Check repo access and run 'gh auth refresh -s repo'.
对于本地文件(./review.md、/tmp/review-pr-*-findings.md 等)
解析 /review-pr 输出格式。从 ## Findings 部分提取发现,保留 Severity / File / Category / Issue / Why it matters / Suggested fix,以及存在时的 Rule-class / Enclosing-symbol / Inverse risk / Class-sites。/review-pr 会为每条发现输出这些字段;若丢弃它们,就会迫使此技能重新推导审核者已经完成的工作。
用作起点,而非重新推导:存在 Inverse risk: 时,用它为 STEP 5 的 inverse_risk 提供初始值,并根据代码进行验证(确认所述失效模式确实存在且仍然适用),而不是从头推导。存在 Class-sites: <A>/<N> 时,用其中的 N 个站点为 STEP 1.5 的 class_completeness 提供初始值,并通过自己的搜索核实数量。重新搜索只是为了发现审核者遗漏的站点,而不是重新构建列表。Rule-class 和 Enclosing-symbol 为类范围扫描提供 signature 初始值。如果初始值与代码内容矛盾,以代码为准,并在相应字段中记录差异。
严重性映射:/review-pr 使用 Critical | Serious | Moderate | Minor,而 CodeRabbit 使用 Critical | Major | Minor | Refactor | Nitpick。两者均有效。将其规范化为内部 Comment 模式,该模式接受任一约定。分类优先级映射:Critical = 最高,Serious/Major = 高,Moderate = 中,Minor/Refactor = 低,Nitpick = 默认忽略。
规范化为内部 Comment 列表
所有输入路径最终都会到达此处。Comment 模式(即 Phase 3-8 读取的确切字段名称)定义在 references/fetch-review-data.md 中。如果你选择的是本地文件路径且尚未读取该文件,现在就加载它。
提前结束的情况
- 列表为空(所有线程均已解决,或本地文件中没有发现):打印
Nothing to triage. No unresolved comments found.→ 在保护条件下恢复 stash → 以状态码 0 退出。 - 只剩 nitpick 且未设置
--all-nitpicks:打印Only nitpicks found (N). Pass --all-nitpicks to triage them, or ignore.→ 在保护条件下恢复 stash → 以状态码 0 退出。
Phase 3:分类子 Agent(general-purpose)
加载评审抑制规则(主 Agent,在分派之前)
分派子 Agent 之前,从基准修订版本加载 .claude/review-suppressions.yml,绝不能从工作树加载:已检出的 PR 不得抑制自身的分诊结果。对于 GitHub 输入,请在固定的基准 OID 处读取:本地执行 git show "$PINNED_BASE_OID:.claude/review-suppressions.yml"(对象不存在时获取一次 origin),或者在跨仓库模式下执行 gh api repos/<owner>/<repo>/contents/.claude/review-suppressions.yml?ref=$PINNED_BASE_OID。对于没有 PR 的本地文件输入,HEAD 本身可能就是正在分诊的变更。将可信基准解析为唯一一个包含所有其他候选项的候选合并基点:为每条主线各收集一个基点,优先选择远程别名而非对应的本地别名,因为本地主线本身可能就是正在分诊的变更;只有当某个基点是唯一一个包含其余所有基点的基点时,才采用它。若存在并列、互不可比的候选项或候选集为空,则停用抑制规则。
BASES=$(for remote in origin/main origin/master origin/develop; do
bare=${remote#origin/}
if git rev-parse --verify -q "$remote" >/dev/null 2>&1; then ref=$remote
elif git rev-parse --verify -q "$bare" >/dev/null 2>&1; then ref=$bare
else continue
fi
git merge-base HEAD "$ref" 2>/dev/null || continue
done | sort -u)
RESULT=""
if [ -n "$BASES" ]; then RESULT=$(printf '%s\n' "$BASES" | while IFS= read -r b; do
[ -n "$b" ] || continue
BAD=$(printf '%s\n' "$BASES" | grep -vx "$b" | while IFS= read -r o; do
git merge-base --is-ancestor "$o" "$b" 2>/dev/null || printf 'bad\n'
done)
[ -z "$BAD" ] && printf 'WINNER %s\n' "$b"
done); fi
读取胜出的提交中的文件,并记录来源。统计胜出项时,确保空输入也能成功,因为 grep -c 在计数为零时会以 1 退出:
WINNERS=$(printf '%s\n' "$RESULT" | grep -c WINNER || true)
仅当 WINNERS 等于 1 且胜出项不是 HEAD 本身时才采用它:若胜出项等于 HEAD,说明没有可供信任的更早基准。否则设置 SUPPRESSIONS = "":不使用策略进行分诊会增加噪声,信任正在评审的变更则会隐藏发现的问题。候选集为空时不会输出任何胜出项行。如果基准中没有此类文件,则设置 SUPPRESSIONS = "",并记录忽略了由 PR 添加的文件。
将已加载的抑制规则作为 ## Review suppressions 一节传入子 Agent 提示词(与 CLAUDE.md 内容、PR 差异和仓库映射采用相同方式;主 Agent 获取,子 Agent 将其作为上下文接收)。
分派
分派一个具有 Read、Grep 和 Bash 工具的 general-purpose 子 Agent。分诊计划只能来自这个子 Agent。如果它彻底失败,则中止运行并说明原因。跳过基础核实和类别扫描,直接分类会使整个计划失去依据。
重要:Bash 允许列表(git log/diff/blame/show/merge-base/rev-parse、grep、rg)是提示词级指令。Claude Code 的 Agent 工具不会按命令对 Bash 进行沙箱隔离。子 Agent 受信任,不会运行其他命令,但系统不会以机制阻止它这样做。
提示词模板
完整提示词位于 references/triage-prompt.md。现在读取它,替换其中的 <...> 占位符(<SKILL_DIR> = 此技能的绝对目录,另加 Phase 1 上下文值、git diff <BASE_SHA>...HEAD 输出、仓库映射、SUPPRESSIONS 和 Phase 2 的 Comment[] 数组),然后将结果原样传给子 Agent。它是子 Agent 要运行的字符串,不是要你遵循、总结或在此处改述的指令。
其中的 STEP 4 会让子 Agent 自行读取 references/triage-rubric.md。你不要读取该文件;R-Rubric Summary 表格是主 Agent 的视图。Phase 4 会验证返回的内容:每个 FIX 都会由提示词输出 fix_plan、change_class、test_scenario、inverse_risk: 和 class_completeness:;reusability_context: 则包含在每个条目中,而不只包含在 FIX 中。
Phase 4:计划执行门控(主流程)
根据 R-rubric 进行验证:摘要表格见上文,完整细节见 references/triage-rubric.md。各分类所需字段在该文件中定义。
计划验证(显示前)
向用户展示任何内容之前,先以机械方式验证分类器的输出:
- 每个
rubric: R5的 DISMISS 都必须有非空的claude_md_quote。 - 每个
rubric: R4的 DISMISS 都必须有非空的prior_commit_sha。 - 每个 DISAGREE 都必须有非空的
disagree_rationale(且不得仅仅表达风格偏好;检查是否出现“prefer”“cleaner”“nicer”等关键词,却没有具体的反驳理由)。 - 每个 FIX 的
fix_plan长度必须不少于 30 个字符。 - 每个 FIX 的
change_class必须准确设为hardening或logic-change(分类器采用的校准标准见references/triage-rubric.md;此项检查仅核对字面值)。 - 每个 FIX 都必须有非空的
test_scenario。对于change_class: hardening,该值必须准确为smoke test, happy path unchanged。对于change_class: logic-change,该值必须是一个具体复现步骤句(不能只是“验证其正常工作”)。 - 每个 FIX 都必须有非空的
inverse_risk,且要么指出具体的故障模式,要么准确为none, pure addition。含糊表达无法通过验证:空值,或诸如“可能有问题”“风险较小”“有些风险”“可能引入回归”之类的内容,或单独的none,均不合格。指出故障模式时,必须说明哪里会出什么问题。Phase 5.5 会使用此字段;未说明的风险无法在该阶段验证。 - 每个 FIX 都必须包含
class_completeness块,其中有非空的verdict,且以COMPLETE或INCOMPLETE开头。INCOMPLETE必须列出排除的代码位置,并逐一说明原因。没有逐项说明原因的INCOMPLETE判定无法通过验证。 - 每个
rubric: R10的 NEEDS-INPUT 都必须有why_unclear,其中包含current:、proposed:和intent evidence:三部分,且每部分都非空。具体格式见references/triage-rubric.md。found nothing是有效的证据值,空值则无效。 - 每个条目都必须有非空的
grounding_a和grounding_b。 - 每个条目都必须包含
reusability_context字段,即使其值只是{ flagged: false }也不例外。所有分类都适用,包括 NEEDS-INPUT。Phase 7 的回复验证器会根据此字段进行分支处理,因此缺少此字段会悄然禁用该回复的可复用性检查。 - 验证失败时:使用缺失字段的具体列表重新派发分类器。最多重试 1 次。第二次仍失败时 → 中止并打印验证错误。
计划展示
使用以下标题打印计划:
# Fix Plan, PR #<num>: <title>
# <N> findings triaged: <F> fix, <D> dismiss, <E> defer, <G> disagree, <I> needs-input, <n> nitpicks
突出显示基于 CLAUDE.md 的 DISMISS
如果存在任何 rubric: R5 的 DISMISS,则在主计划之前打印一个单独的重点部分:
## ⚠ Dismissed because they contradict CLAUDE.md: review and override if any are exceptions
[D<n>] <file:line>: <comment ask>
CLAUDE.md rule: "<verbatim quote>"
Reply will be: "<reply>"
If this rule has a legitimate exception in this case, select D<n>
in the contested-item confirmation to change it to FIX or NEEDS-INPUT.
有争议项目确认(multiSelect)
有争议的项目是指那些会发布回复并解决讨论串、但不会更改代码的项目:所有 DISMISS、DEFER 和 DISAGREE。错误分类会悄然结束审查者的对话。在 Phase 5/7 采取行动前,请确认分流结果。
如果没有有争议的项目,请跳过此步骤。否则,使用 AskUserQuestion:
问题: header: "分流" text: " 个项目将发布回复并解决讨论串,且不更改代码。选择要重新分类的项目。未选中的项目将按计划继续处理。" 选项:[每个 DISMISS/DEFER/DISAGREE 项目对应一个选项:"[] file:line:(),<reason,前约 60 个字符>"] multiSelect: true
如果有争议的项目超过选项数上限,请拆分为多个 multiSelect 问题。先处理 DISMISS(错误分类的代价最高)。
对于每个选中的项目,使用后续的 AskUserQuestion:
问题: header: "项目 " text: "file:line,当前分类为 :。重新分类为?" 选项: - label: "FIX(推荐)" description: "将其视为实际问题,并添加到 FIX 列表,附上修复计划" - label: "NEEDS-INPUT" description: "暂缓处理,不发布任何内容;此项目会显示在最终报告中" - label: "保持原样" description: "由于误选了此项目,保留原分类"
选择“FIX”时:仅针对该项目重新分派分类器,生成完整的 FIX 字段集:fix_plan、change_class、test_scenario、inverse_risk、class_completeness(包括类别扫描;重新分类的项目尚未经过扫描),以及 reusability_context(除非扫描结果改变该值,否则沿用有争议项目自身的值)。然后对更改后的项目重新运行计划验证。此处生成不完整的字段集将导致验证失败,并耗尽唯一一次重试机会。选择“NEEDS-INPUT”时:将其移至 NEEDS-INPUT,并设置 why_unclear: "user contested the <classification> classification",原样保留项目的 reusability_context。选择“保持原样”时:不做更改。选择“Other”时:将自由文本作为重新分类指令。
此步骤期间不会发布或解决任何内容。只有 Phase 7 会操作 GitHub,且仅处理通过此确认的项目。解决讨论串会在审查者的对话中留下不可逆的干扰:跳过此关卡的 DISMISS、DEFER 或 DISAGREE 项目会保持未解决且没有回复,直到通过此确认。
执行
如果设置了 --dry-run:打印计划,打印 dry run, not executing,在保护机制下恢复 stash,然后以状态码 0 退出。
如果 EXECUTION_AUTHORIZED=true,则直接进入 Phase 5。此次调用已授权执行所有通过验证的 FIX 项目。如果设置了 --interactive,则在 Phase 5 中逐项询问确认。这不会增加计划级别的确认。
否则,使用 AskUserQuestion:
问题: header: "执行" text: "请求未明确授权修改。是否执行已验证的计划? 个修复, 个忽略, 个延期, 个不同意, 个需要输入。" 选项: - label: "执行计划(推荐)" description: "按依赖顺序应用所有 FIX 项目" - label: "取消" description: "保持工作区不变,并恢复任何 stash"
选择“执行计划”时:设置 EXECUTION_AUTHORIZED=true,将此选择记录为 execution_authorization_evidence,然后进入 Phase 5。选择“取消”时:如果已推送 stash,则在保护机制下恢复 stash,打印 cancelled,然后以状态码 0 退出。
Phase 5:执行修复(主流程,顺序执行)
执行已验证计划中的 R6 FIX 项目(R6 标准见 R-Rubric Summary 表,验证要求见 Phase 4)。
依赖项解析
根据 dependencies: 字段通过简单的拓扑排序生成执行顺序。遇到循环依赖(A→B→A)时:中止,并提示 Cyclic fix dependencies detected. Correct the dependency fields and rerun /fix-pr-review <original-input>.。在保护机制下恢复暂存内容。以非零状态退出。
编辑前快照(还原机制;Edit 工具没有撤销功能)
在首次编辑前绑定 Phase 5 的状态来源:
| 上下文 | active_snapshot | active_baseline_errors | 中止范围 |
|---|---|---|---|
| 普通 Phase 5 | perfix_snapshot[idx] | Phase 1 baseline_errors | 当前修复 |
Phase 8 项 <idx> | phase8_item_snapshot[idx] | phase8_item_baseline_errors[idx] | 仅当前项 |
在普通 Phase 5 中,首次 Edit 在本次运行中的任何位置修改文件前,将其完整内容或权威的缺失状态缓存到运行级中止快照:
preedit_snapshot[path] = <full file content from Read>
紧接着每次普通修复之前,将所有声明路径的当时内容或权威的缺失状态捕获到 perfix_snapshot[idx];其中包含此前所有已落地修复的结果,并作为 Retry 和 Skip 的 active_snapshot。该修复的最后一次尝试成功后、任何后续编辑开始前,根据 perfix_snapshot[idx] 和当前声明路径生成精确的二进制安全正向补丁,包括新增和删除内容。将该补丁冻结到 perfix_forward_patch[idx],将当前的精确内容或权威的缺失状态冻结到 perfix_postimage[idx],并用该补丁和后像初始化 perfix_owned_components[idx]。以后绝不能根据汇总的工作树差异推断修复的所有权。preedit_snapshot 仅用于全部中止。在 Phase 8 上下文中,active_snapshot 必须已包含所有声明路径,并且任何嵌套分支都不得读取或写入普通快照。根据活动快照还原时,应写回记录的内容,并删除记录为权威缺失的路径。只有全部中止才会还原每个运行级 preedit_snapshot 条目;Phase 8 项仅还原 phase8_item_files[idx],保留此前已落地的修复。
单项修复循环
按拓扑顺序处理每个 FIX 项,并遵循 ${CLAUDE_SKILL_DIR}/references/per-fix-loop.md 中的循环流程。现在加载该文件。文件中包含交互式确认、带基线比较的窄范围类型检查,以及重试、跳过和中止分支;还包括症状修补规则:若再次重试会变成症状修补,则将该项交给 systematic-debugging,而不是再消耗一次重试。
修复执行跟踪
fix_status[idx] = ok | retried_ok | inconclusive | skipped | aborted | type_check_skipped
| reverted_inverse_risk | inverse_risk_applied | partial | restored_failed
landed_fix_statuses = {ok, retried_ok, inconclusive, type_check_skipped}
landed_fix_statuses 是后续阶段和最终报告所依据的权威已落地修复集。只有成功还原后,Phase 5.5 才会写入 reverted_inverse_risk;无法证明安全移除时,则写入 inverse_risk_applied。这两种状态以及 partial 都不属于已落地状态。restored_failed 表示 Phase 8 在失败后还原了该项的快照,始终不属于已落地状态。
阶段 5.5:收敛(子代理)
在所有修复都已应用后、/done 流程开始前运行。当每项修复都已完成类别范围内的全部修复、没有逆向风险且未产生新的同类问题时,本轮即告收敛。未达到这些条件的任何情况,都会在下一轮审查中被发现。
派发一个 general-purpose 子代理。向其提供 git diff HEAD,以及每项修复的 class_completeness 站点列表和 inverse_risk 字符串。对于针对 inverse_risk_applied 的阶段 8 补救,还要提供 phase8_remediation_kind、完整的先前自有组件清单,以及每个组件计划移除或替换的证据。让它自行获取所需的其他信息。将其保留为子代理:它会重新读取文件并在仓库中运行 grep,而主代理只需要处理判定结果。
For each fix below, verify against the working tree, not against the fix plan's claims.
1. CLASS COMPLETENESS: every site the class sweep marked `affected` in
`class_completeness.sites` must actually be changed. A fix that landed on 3 of 4
sites is INCOMPLETE, not done.
(Sites the plan's `verdict` deliberately excluded are not counted as unfixed.)
2. INVERSE RISK: the named failure mode must NOT be present in the applied code.
3. NEW SIBLINGS: did the fix itself introduce a new instance of the pattern it fixes,
or a new branch (error state, empty state, early return) that its siblings have but
this one lacks?
Report per fix, nothing else:
fix: <idx>
class_complete: yes | no, <unfixed site if no>
inverse_risk_present: no | yes, <file:line + one sentence>
new_siblings: none | <file:line + one sentence>
Evidence rules differ per check. "I lack evidence" is not an answer for check 1:
- Check 1 is decided MECHANICALLY by `git diff HEAD`. Each affected site either
appears in the diff or it does not; there is no undecidable state. Report `no`
with the unfixed site whenever a site is absent from the diff.
- Checks 2 and 3 default to `no` / `none` unless you can cite a concrete file:line
in the applied code. Do not speculate, do not re-review the PR, do not report
style issues.
对于阶段 8 的 removal,将检查 1 替换为机械式补救完整性检查:每个先前自有组件都必须在补救清单中恰好出现一次,并且最终内容必须证明该组件已不存在。不要要求原受影响站点仍出现在 git diff HEAD 中,也绝不要重新应用遭拒的修复来让该站点出现在其中。对于阶段 8 的 replacement,要求每个先前自有组件都已移除,或已被记录的替换字节取代。对于这两个分支,缺少组件证据都表示 class_complete: no;逆向风险检查必须基于完整组件清单提供肯定证据,而不能采用通常的默认判断。
处理方式:
class_complete: no→ 立即应用缺失站点,然后只重新验证一次。如果第二次检查仍报告no,则停止:将fix_status[idx] = partial,记录仍未修复的站点,并在阶段 8 中列出。不要进行第三次循环;与窄范围类型检查重试(最多 2 次)和自愈循环(最多 2 次)一样,此循环有次数上限。对于阶段 8 补救,只修正缺失的移除或替换证据;不要仅因某个原受影响站点未出现在差异中就应用它。- 将每项用于补全类别范围修复或处理新同类问题的纠正性编辑视为其所补全修复的一部分。捕获其编辑前内容,然后将其精确补丁和编辑后内容追加到该修复按顺序排列的
perfix_owned_components[idx]中;同时将其登记为每项其他较早修复的后续编辑证据。 inverse_risk_present: yes→ 该建议是错误的;照做会引入更严重的缺陷。在阶段 8 上下文中,从active_snapshot恢复所有已声明路径,包括权威性的缺失状态,然后仅中止当前条目。在普通上下文中,preedit_snapshot仅用于全部中止。只能使用perfix_owned_components[idx]和后续编辑证据来证明归属;绝不要根据git diff HEAD或其他聚合差异重建归属。候选逆向补丁应按相反顺序移除所有自有组件。只有当冻结的证据证明不存在独占范围重叠,且临时候选补丁保留了所有其他后续编辑时,才应用该补丁。仅当没有其他后续修复或收敛编辑触及任何已声明路径,且当前每条已声明路径都与最后一个自有组件的编辑后内容完全一致时,才恢复整个perfix_snapshot[idx]。否则保留该修复,将fix_status[idx] = inverse_risk_applied,记录revert: not attempted, exclusive ownership unproven,将其转交 NEEDS-INPUT,并在阶段 8 中说明该风险修复仍处于应用状态。 对于普通逆向补丁或快照恢复,将fix_status[idx]标记为reverted_inverse_risk并转交 NEEDS-INPUT。阶段 8 的活动快照恢复遵循其状态保留恢复规则,并以reverted_inverse_risk作为后备值。new_siblings→ 将其视为同一修复的一部分并立即处理。
将每项修复的结果记录为 convergence[idx];阶段 8 会输出该结果,以及本轮是否收敛的判定。如果子代理失败,则在行内运行这三项检查,并注明 convergence checked inline。
阶段 6:/done 验证(主要流程)
应用所有修复后,对待处理的修复差异(git diff HEAD)运行 /done。/done 是一个包含四个部分的验收工作流,而不是固定的三命令流水线:§1 绑定本次运行并选择验收通道,§2 在各通道的边界验证每项必需通道,§3 为每个请求项、通道和证据维度分配状态,§4 构建就绪卡。将最初的请求绑定到已验证的修复计划,并将所有检查范围限定为修复差异,而不是整个 PR。
此处始终适用 Code 通道。运行 /fix-ts-errors,直到检查通过(捕获逐文件窄范围检查无法发现的跨文件错误,并至少运行一次完整工作区检查);对修复差异运行 /parallel-review 并应用其 converge-reviews 结果;运行 /simplify,包括其阻塞性的新增注释扫描;并根据差异逐项核对每项修复。按照 /done §1 中的规则,选择修复实际涉及的所有其他通道:UI、文档、全局配置或技能、外部元数据或数据、发布或部署。将其余通道记录为 not-applicable,并注明排除原因。
不要执行 /done §4 中的交接。本技能负责发布:git-commit 和 file-pr 在阶段 6 中不可用,只有阶段 8 的完成后提示才能启动提交或推送。
自我修复循环(显式跟踪迭代)
self_heal_iter = 0
done_remaining = []
while self_heal_iter < 2:
findings = run_parallel_review(diff="git diff HEAD")
blockers = [f for f in findings if f.severity in ("Critical", "Serious")]
if not blockers:
break
# Dispatch fresh subagent per blocker with the finding + the diff
for f in blockers:
apply_subagent_corrective_edits(f)
run_fix_ts_errors()
self_heal_iter += 1
# After loop: record whatever blockers remain (if any)
done_remaining = blockers
如果经过 2 次迭代后 done_remaining 仍非空,将其记录到最终报告中,并继续进入阶段 7。用户会在阶段 8 查看剩余问题并作出决定。
中等/轻微问题不进行自我修复,记录在 done_remaining 中。用户会在提交时决定。
验证所有必需通道后,将确切的修复内容物化为 git-commit 的已验证内容快照契约所要求的 done_verified_snapshot。使用 /done §4 的路径范围快照流程(create_verified_snapshot)构建:将 HEAD 读入隔离的 GIT_INDEX_FILE,仅对声明的路径运行 git hash-object -w,使用 git update-index --add --cacheinfo <mode>,<blob>,<path> 暂存每个路径,对每个声明的删除运行 --force-remove,然后运行 git write-tree。此处的声明路径集合是所有已落地修复所声明路径的并集;自动暂存的 WIP 和任何其他脏路径都不包含在其中,也不会进入对象数据库。在运行级 stash 仍未改动时,记录快照树和所含路径清单。阶段 8 的修复只能在相应项目验证干净通过后,更新此快照中的声明路径;通过将先前快照而不是 HEAD 读入隔离索引,并使用相同的 update-index --cacheinfo 机制应用已验证的字节来重建树,绝不能重新捕获实时工作区。如果不存在有效的 done_verified_snapshot,则无法执行提交和推送。
阶段 7:在 GitHub 上回复并解决(主要流程)
回复格式规则见 references/github-reply-resolve.md,阶段 4 验证会引用这些规则。本技能不会读取或写入 /review-pr 的缓存(~/.claude/skills/review-pr/cache/)。线程解决操作在 GitHub 上进行。/review-pr 会通过 GraphQL 获取先前评审时间线,并在再次评审时纳入已解决的线程。
回复并解决线程是本阶段在 GitHub 上的全部操作。评审本身的 CHANGES_REQUESTED 状态保持 CodeRabbit 留下的原样。用户推送后,CodeRabbit 会在下一次自动重新评审时自行清除该状态。
定义 has_github_surface[idx] = thread_id != null AND (can_reply OR can_resolve)。对于没有此操作入口的每个项目,无论输入来源如何,都将其初始化为成对的 not-applicable 状态。对于 ./review.md 或任何其他本地文件输入,所有项目都满足此条件;初始化这些项目后,跳过本阶段的其余部分。阶段 8 仍要求最终报告为每个项目提供确定的状态。
回复机制
立即加载 ${CLAUDE_SKILL_DIR}/references/github-reply-resolve.md。该文件包含第 7a 步(根据实际的修复后差异重新生成每条 FIX 回复,绝不使用第 3 阶段的 reply_placeholder,以及哪些 fix_status 值禁止回复)、第 7b 步(机械式回复验证器:禁止使用的前缀、40 字符下限、必须包含的模式,以及由 reusability_context 控制的规则)、第 7c 步(addPullRequestReviewThreadReply 和 resolveReviewThread GraphQL 调用),以及第 7d 步(晋升的细枝末节问题没有需要关闭的线程)。
逐项状态跟踪
gh_status[idx] = {
reply_state: landed | verified-existing | confirmed-absent | reconcile-required | skipped | not-applicable,
reply_err: <error message if any>,
resolve_state: resolved | already-resolved | confirmed-open | reconcile-required | skipped | not-applicable,
resolve_err: <error message if any>
}
为现有最终报告渲染器推导 reply_ok=true:当 reply_state ∈ {landed, verified-existing, not-applicable} 时为真;推导 resolve_ok=true:当 resolve_state ∈ {resolved, already-resolved, not-applicable} 时为真。对于每个 has_github_surface=false 的项目,使用 not-applicable;从 GitHub 失败以及回复/解决部分中省略这些条目。仅当 has_github_surface=true 且未尝试适用的 GitHub 修改时,才使用 skipped。将 verified-existing 呈现为权威的现有回复,将 already-resolved 呈现为权威的无需操作的已解决状态。仅有解决状态绝不能证明回复已发送。处理完批次中的每个项目。 在确认任何失败或出现 reconcile-required 结果后,跳过依赖操作并退役批次卡片。只有在 preflight-mutations 返回一张排除了未解决目标的新 ready 卡片后,才继续处理独立的待处理项目。所有失败都要集中显示在最终报告的顶部。
第 8 阶段:收尾(主流程)
报告按 R 类别分组(见 R-Rubric Summary 表)。包含抑制项写入(学习循环)。
1. 处理 NEEDS-INPUT
在整个步骤中保持运行级 stash 不变。根据当前工作流状态构建 needs_input_items:包括所有仍分类为 NEEDS-INPUT 的项目,以及第 5 或 5.5 阶段路由至此的所有项目。不要根据渲染后的报告推导此数量。按照 ${CLAUDE_SKILL_DIR}/references/needs-input-triage.md 对每个 NEEDS-INPUT 项目进行分流;仅当数量非零时才加载该文件。只有在不再有待处理条目后,才能离开此步骤。
2. 恢复 WIP
仅在所有 NEEDS-INPUT 项目都已处理完毕后,若 STASH_PUSHED=true:
if [ "$(git rev-parse -q --verify refs/stash)" = "$STASH_OID" ]; then
if git stash apply "$STASH_OID"; then
[ "$(git rev-parse -q --verify refs/stash)" = "$STASH_OID" ] && git stash drop || echo "STASH_CHANGED_MID_RESTORE"
elif [ -n "$(git diff --name-only --diff-filter=U)" ]; then
echo "STASH_CONFLICT"
else
echo "STASH_APPLY_FAILED"
fi
else
echo "STASH_TOP_MISMATCH"
fi
遇到 STASH_TOP_MISMATCH 时,记录 stash_restored: foreign-top 并继续生成最终报告:现在有另一个 stash 位于我们的 stash 之上,因此没有应用任何内容,也没有丢弃任何内容。遇到 STASH_CONFLICT 时,保持所有冲突标记与应用操作完成时完全一致,并跳过丢弃操作:该条目仍保存在 stash 中,作为恢复来源。在最终报告中记录 stash_restored: conflict;如何处理用户的 WIP 由用户决定。遇到 STASH_APPLY_FAILED 时,应用操作在没有冲突标记的情况下报错,因此工作树状态未知:记录 stash_restored: failed,保持工作树和所有条目不变,然后继续生成报告。当丢弃操作报告 STASH_CHANGED_MID_RESTORE 时,内容已干净地应用,但 stash 栈先发生了变化:记录 stash_restored: foreign-top,保留已记录的条目供用户手动丢弃,然后继续生成报告。发生 conflict 或 failed 恢复后,不得运行任何编辑、类型检查、收敛检查、第 6 阶段检查、提交或推送操作。将 stash 恢复设为唯一依赖就绪的后续操作。
3. 仅输出一次最终报告
根据最终分类以及 fix_status、convergence、needs_input_status 和 gh_status 状态重新计算所有计数。现在加载 ${CLAUDE_SKILL_DIR}/references/final-report.md,并严格按照其内容输出一次。将 inverse_risk_applied 归入 跳过/未干净落地,计为未收敛,并注明 risky fix remains applied。其依赖就绪操作为:移除存在风险的自有组件或应用替代方案,然后重新运行 Phase 5.5 和 Phase 6,以重建 done_verified_snapshot。对于 reverted_inverse_risk,将任何遗留的“无安全回滚”规则解释为 inverse_risk_applied;reverted 状态始终表示修复已移除。报告的失败优先排序包括 Phase 7 和 Phase 8 的所有 GitHub 结果;其渲染规则决定每个 [F<n>] 所属的修复子节,以及 Test: 行的输出方式。
4. 提议写入抑制规则(学习循环)
最终报告输出后,收集所有当前的 DISMISS 和 DISAGREE 项目。如果存在任何项目,提议将其保存为抑制规则,以供后续审查使用。
使用 AskUserQuestion:
Question: header: "学习" text: "将这些已驳回/有异议的模式保存到 .claude/review-suppressions.yml,以便在后续审查中自动驳回吗?" options: [每个 DISMISS/DISAGREE 项目对应一个选项,显示模式 + 原因] multiSelect: true
对于每个选中的项目,追加到 .claude/review-suppressions.yml:
- pattern: "<normalized pattern from finding: key phrase, not full text>"
category: "<finding category if available>"
file: "<finding's file path. Include only if the rationale is specific to one file, omit for generic patterns>"
reason: "<dismiss/disagree rationale from triage>"
added: <today's date YYYY-MM-DD>
如果文件不存在,则使用以下内容创建:
suppressions:
- pattern: ...
如果没有选中任何项目,或用户选择“其他”以跳过,则不要写入任何内容。
以下情况完全跳过此步骤:
- 没有 DISMISS 或 DISAGREE 项目
- 输入是来自 Git 仓库外部的本地文件(没有可写入抑制规则的项目根目录)
stash_restored=conflict(冲突工作树仍为只读状态)stash_restored=failed(工作树状态未知)
5. 完成后的后续操作
输出最终报告,并在适用时完成抑制规则步骤后,从所有当前 fix_status 为 inverse_risk_applied 的项目中计算 inverse_risk_blockers,不论其最终分类或 needs_input_status 如何。若 stash_restored=conflict,则不显示提示,并将 Resolve stash conflicts 作为唯一的依赖就绪后续操作;Commit 和 Push 均不可用。若 stash_restored=foreign-top,则不显示提示,并将 Reconcile the stash stack: the recorded auto-stash entry is still stashed 作为唯一的依赖就绪后续操作;Commit 和 Push 均不可用。若 stash_restored=failed,则不显示提示,并将 Recover the worktree and stash by hand 作为唯一的依赖就绪后续操作;Commit 和 Push 均不可用。否则,若 inverse_risk_blockers 非空,则不显示提示,并报告第 3 步中的依赖就绪移除/替代操作;即使存在 done_verified_snapshot,Commit 和 Push 仍不可用。如果所有修复均已中止(未应用任何修复),则跳过提示;否则使用 AskUserQuestion。
Question:
header: "下一步"
text: "修复流程已完成。已应用 项修复,已跳过 项,需要输入 项。接下来要做什么?"
options:
- label: "提交更改"
description: "使用建议的提交消息暂存并提交"
- label: "推送到远程"
description: "提交并推送以更新 PR"
- label: "对剩余项重新运行"
description: "对已跳过/需要输入的项目再次运行 /fix-pr-review"
- label: "完成"
description: "退出。我会手动处理剩余事项"
对于“提交更改”或“推送到远程”,要求 inverse_risk_blockers 为空,stash_restored 不属于 conflict、foreign-top 或 failed,并且存在有效的 done_verified_snapshot。重新计算阻塞项集合,不要信任呈现的报告;集合非空时,停止该操作。以已验证内容快照封存索引模式调用 git-commit,并提供该快照;绝不暂存或重新暂存实时工作树。要求创建的提交树与快照树一致。普通的已恢复 WIP 保留在工作树中,不纳入候选提交。
对于“推送到远程”,先提交,然后要求提交树仍与快照一致,再冻结发布分支和推送尝试 SHA。对于 GitHub PR 输入,重新读取当前 PR 的 headRefName 以及 headRepository.id 和 nameWithOwner;冻结这些权威值,要求 Phase 1 中的预期 PR 分支和活动分支都与冻结的头部分支名称一致,并将 <exact-ref> 设为 refs/heads/<frozen-headRefName>。该头部仓库身份是预期发布目标,且独立于输入/基础仓库。对于本地文件或其他没有当前 PR 的输入,冻结活动分支,并将 <exact-ref> 派生为 refs/heads/<active-branch>。仅当活动符号引用等于 <exact-ref>,且该分支引用和 HEAD 解析为同一 SHA 时,才冻结推送尝试 SHA。绑定 <preflighted-remote> 前,先解析预期发布目标。对于 GitHub PR 输入,枚举已配置的远程,并验证每个远程中有序且完整的抓取/推送端点集合。自动选择所有端点均与 PR 头部身份匹配的唯一远程;若有多个匹配项,立即使用 AskUserQuestion,并提供具体的 <remote>: <nameWithOwner> (<id>) 选项,必要时分页;若没有匹配项,则停止并提示:Configure a remote whose complete endpoint sets resolve to the current PR head repository, then choose Push to remote again. 所选匹配项将成为 <preflighted-remote>。
对于本地文件或其他没有当前 PR 的输入,选择“推送到远程”后立即使用 AskUserQuestion。列出所有已配置的远程;在不打印的情况下捕获其有序且完整的抓取和推送端点集合,通过已认证的 gh repo view ... --json id,nameWithOwner 分别规范化并解析每个端点,只有当某远程的所有端点都解析为同一仓库 ID 和 nameWithOwner 时,才保留该远程。将每个保留的远程显示为具体选项,标签格式为 <remote>: <nameWithOwner> (<id>);列出被拒绝的远程及失败的端点或集合不匹配情况,但绝不将其设为可选项。如果选项数量上限无法容纳所有保留的远程,则将具体选项分页为互不重叠的集合,并提供 Show next remotes 选项,直到用户选定一个远程。该选择会冻结两个完整端点集合,设置 <preflighted-remote> 和明确的预期发布目标,并加入预检授权和防护措施。如果没有有效远程,则停止并提示:Configure a remote whose complete fetch and push endpoint sets resolve to one authenticated GitHub repository, then choose Push to remote again.
捕获 git remote get-url --all <preflighted-remote> 和 git remote get-url --push --all <preflighted-remote> 返回的有序完整端点集合,但不要打印;随后将每个端点规范化为不含凭据的 URL 标识。保留传输类别、主机、端口和所有者/仓库,同时将 scp 风格与 ssh://、默认端口、尾部斜杠和 .git 后缀等等效写法视为相同。通过经过身份验证的 gh repo view ... --json id,nameWithOwner,独立解析每个规范化端点;要求每个端点的 ID 和 nameWithOwner 都与预期发布目标一致。仅保留有序规范化集合或其摘要,以及解析出的 ID,不要保留凭据。记录远程名称、两个完整端点集合和预期目标;任何添加、移除、顺序变更、解析失败或仓库不匹配都属于 reconcile-required。仅当冻结的集合仍然匹配时,才可通过命名远程推送到其配置的完整推送集合。
查询 <exact-ref> 并记录其远程 SHA 或权威确认不存在的结果;如果存在,则要求其为冻结 SHA 的祖先。在调用 git push 前,立即使用确切的远程、两个有序完整端点集合和仓库 ID、预期发布目标、<exact-ref>、冻结的本地 SHA、预期远程 SHA 或不存在状态、上游状态、提交范围、依赖引用以及用户的选择,调用 preflight-mutations。如果存在 PR,则在其守卫和失效条件中包含 PR 基础分支、冻结的 headRefName、<exact-ref> 和头部仓库 ID。在这些守卫和失效条件中包含当前符号引用、分支引用 SHA、HEAD、冻结的发布分支和确切引用,以及完整的已选远程标识;随后重新枚举并比较两个端点集合,再运行确切的推送命令:
git push --force-with-lease="<exact-ref>:<expected-sha-or-empty>" "<preflighted-remote>" "<frozen-local-sha>:<exact-ref>"
git ls-remote --heads "<preflighted-remote>" "<exact-ref>"
每次尝试推送后(包括退出码非零或结果不明确的情况),都应先确认远程状态:重新枚举两个完整端点集合,并对 <exact-ref> 执行一次确切的 git ls-remote 回读。只有在权威远程 SHA 等于冻结 SHA、有序集合未变且每个端点仍与预期发布目标一致时,才记录推送已落地。旧 SHA 或意外 SHA、无法回读、端点添加、移除、顺序变更、无法解析或不匹配,都属于 reconcile-required;应停止操作,绝不能仅根据命令结果重试。
保存远程结果后,重新读取当前符号引用、冻结的发布分支引用 SHA 和 HEAD。只有符号引用等于 <exact-ref> 且两个本地 SHA 都等于冻结 SHA 时,才可继续。如果远程推送已落地,但任何本地值发生变化,则记录 remote landed / local publication moved,将整体发布状态设为 reconcile-required,并停止操作,不要重试推送。如果远程推送未落地,则保留观测到的远程状态,并无论本地状态如何都以 reconcile-required 停止。只有远程和本地状态都通过检查后,GitHub PR 输入才能读取 headRefName、headRefOid 和 headRepository.id;要求它们分别等于冻结的头部名称、冻结 SHA 和预期发布目标 ID。在“对剩余项重新运行”时:如果原始输入是本地文件,则将 /fix-pr-review <original-file-path> 限定为跳过/需要输入的项目;否则将 /fix-pr-review <url> 限定为剩余项目。在“完成”时:退出。
6. 退出
只有在上述完成后提示中明确选择“提交更改”或“推送到远程”时才提交。否则保持工作树原样。报告中建议的提交消息用于交接。