首页
/ GSD 代码修复器 gsd-code-fixer 源码级解析:从 REVIEW 发现到原子提交的完整修复闭环

GSD 代码修复器 gsd-code-fixer 源码级解析:从 REVIEW 发现到原子提交的完整修复闭环

2026-09-07 16:09:40作者:卓艾滢Kingsley

在 TÂCHES 出品的 get-shit-done(GSD)规格驱动开发体系中,代码评审与自动修复是一对紧密配合的流水线环节:gsd-code-reviewer 评审后产出 REVIEW.md,而本指南的主角 gsd-code-fixer 负责把这些评审发现转译为安全的源码修复——它读取真实源码、智能适配而非盲目套用补丁、逐条原子提交,并最终产出 REVIEW-FIX.md 修复报告。本文将以 agents/gsd-code-fixer.md 这份可被运行时直接加载的 Agent 定义文档为骨架,结合 code-review-fix 工作流code-review 命令 及仓库中针对 #2839、#2990 的回归测试,从执行流程、隔离策略、验证体系到部分失败语义做源码级拆解。读完你将掌握:一套可复用的"评审→修复→验证→原子提交→事务性清理"的自动化修复工程范式,以及其中每个设计决策背后的 Bug 教训。

一、gsd-code-fixer 在流水线中的位置与启动方式

在 GSD 的 phase 式开发流程中,代码评审与修复的编排链如下:

  1. 用户执行 /gsd:code-review {phase} --fix(见 commands/gsd/code-review.md),该命令是"薄分发层",只做参数解析后委托给工作流;
  2. 工作流 code-review.md 完成 phase 校验、workflow.code_review 配置门禁、文件范围圈定后,spawn gsd-code-reviewer 产出 REVIEW.md
  3. 若带 --fix,则继续走 code-review-fix.md,由 orchestrator 以 Agent(subagent_type="gsd-code-fixer", ...) 方式拉起本文主角。

Agent 定义文件的 YAML 头(agents/gsd-code-fixer.md)给出了它的契约特征:

---
name: gsd-code-fixer
description: Applies fixes to code review findings from REVIEW.md. Reads source files, applies intelligent fixes, and commits each fix atomically. Spawned by /gsd:code-review --fix.
tools: Read, Edit, Write, Bash, Grep, Glob
color: "#10B981"
---

值得注意的关键契约点:

  • 工具集是受限白名单Read / Edit / Write / Bash / Grep / Glob。写文件优先走 Edit(目标化编辑,diff 可见性好),回滚则禁止Write(部分写入在工具失败时会让文件损坏且无恢复路径)。
  • 强制初始阅读:若 prompt 中含 <required_reading> 块,则必须先 Read 其中列出的每个文件,再执行任何其他动作——这是它感知"评审对象与项目状态"的主上下文(agents/gsd-code-fixer.md)。
  • 双项目上下文注入:开始修复前要读工作目录下的 ./CLAUDE.md 项目约定,并扫描 .claude/skills/.agents/skills/ 下的 SKILL.md(轻量索引,约 130 行),实现层面需要时再加载具体 rules/*.md,且刻意不加载体积巨大的完整 AGENTS.md(100KB+ 的上下文开销)。

从工作流侧看,orchestrator 传给 fixer 的 <config> 块(get-shit-done/workflows/code-review-fix.md)由五个字段构成,这就是 agent 需要解析的全部运行时参数:

<config>
phase_dir: ${PHASE_DIR}
padded_phase: ${PADDED_PHASE}
review_path: ${REVIEW_PATH}
fix_scope: ${FIX_SCOPE}
fix_report_path: ${FIX_REPORT_PATH}
iteration: 1
</config>

其中 fix_scope--all 标志决定:默认 critical_warning(只处理 CR-/BL-/WR-),--all 时变为 all(额外纳入 IN- Info 级发现)。

二、执行流程全景:setup → load → parse → fix → report

fixer 的执行被组织为五个有序 step,工作流层面的完整映射如下:

Step 职责 关键产物/动作
setup_worktree 任何文件改动前建立隔离 git worktree + 恢复哨兵 gsd-reviewfix/${padded_phase}-$$ 分支、.review-fix-recovery-pending.json
load_context 读必读文件、解析 config、读 REVIEW.md、解析 frontmatter phase_dir/padded_phase/review_path/fix_scope/fix_report_path
parse_findings 按 finding 结构解析并过滤、排序 id/severity/title/file/files/line/issue/fix 字段
apply_fixes 逐条读源文件→记录 touched→适配修复→三级验证→原子提交 每条 finding 一个 commit
write_fix_report 写 REVIEW-FIX.md(不提交,由 orchestrator 统一提交) YAML frontmatter + Fixed/Skipped 双段落

2.1 load_context 的早退分支

在读取 REVIEW.md 正文之前,先解析其 YAML frontmatter 的 status: 字段(agents/gsd-code-fixer.md):

  • status"clean""skipped":直接以 exit code 0 退出,提示 "No issues to fix",不创建 REVIEW-FIX.md(这不是错误,只是无事可做);
  • 工作流侧也做了同构的防御:code-review-fix.mdcheck_review_status 中若解析到 clean/skipped 同样直接退出,若解析到 unknown 则打警告继续尝试。

2.2 parse_findings 的解析规则

REVIEW.md 中每条 finding 以 ### {ID}: {Title} 开头,ID 形如 CR-\d+ / BL-\d+(Critical 级)、WR-\d+(Warning)、IN-\d+(Info)。提取规则(agents/gsd-code-fixer.md)要求:

  • File 行可能是 path/to/file.ext:42(带行号)或裸路径,行号缺失时置 line: null,优雅降级;
  • Fix 段落**Fix:** 起,到下一个 ### 标题或文件尾结束,内容可能是内联/围栏代码、多文件引用("在 fileA.ts 改 X、在 fileB.ts 改 Y"需把所有路径收集进 files 数组)、或纯散文意图描述(需要 agent 自行理解语义落地);
  • 代码围栏防误判:扫描 ### 边界时必须跟踪三重反引号的开闭状态,围栏内内容视为不透明——因为 Fix 示例代码里完全可能包含 Markdown 标题,那绝不是新 finding 的起点。

过滤按 fix_scope 执行,随后按严重级稳定排序(Critical/BL 优先,同级别维持文档顺序),保证最重要的修复先落地。

三、隔离机制:为何每个 fixer 都必须在独立 worktree 中运行

gsd-code-fixer 是一个后台进程式的提交者,而用户的前台 session 正在同一仓库上工作。若直接在主工作树提交,会与前台 session 竞争共享的 index、HEAD 与磁盘文件(#2686)。因此第一个 step setup_worktree 的硬性要求是:在触碰任何文件之前建立独立 worktree(agents/gsd-code-fixer.md)。

3.1 关键 Bash 序列

branch=$(git branch --show-current)
test -n "$branch" || { echo "Detached HEAD is not supported for review-fix (#2686)"; exit 1; }

sentinel="${phase_dir}/.review-fix-recovery-pending.json"

wt=$(mktemp -d "/tmp/sv-${padded_phase}-reviewfix-XXXXXX")
reviewfix_branch="gsd-reviewfix/${padded_phase}-$$"
git worktree add -b "$reviewfix_branch" "$wt" "$branch"

# sentinel 仅在 git worktree add 成功后写入
node -e '...fs.writeFileSync(sentinelPath, JSON.stringify({
  worktree_path, branch, reviewfix_branch, padded_phase, started_at
}, null, 2));' "$sentinel" "$wt" "$branch" "$reviewfix_branch" "$padded_phase"

cd "$wt"

3.2 分支模型的 Bug 教训(#2990)

最初的实现是 git worktree add "$wt" "$branch"——把 worktree 直接挂到用户当前已检出的分支上。但 git 默认禁止同一分支在两个 worktree 中被同时检出,导致 setup 在 agent 开工前就失败。修复方案:用 git worktree add -b "$reviewfix_branch" "$wt" "$branch" 新建一条从当前分支顶端出发的临时分支 gsd-reviewfix/${padded_phase}-$$,把 worktree 挂到新分支上:

  • mktemp 的随机后缀保证同一 phase 的并发运行不会碰撞;
  • 修复提交推进的是 $reviewfix_branch,用户分支在主仓库不受打扰;
  • 收尾时通过 git merge --ff-only 让用户分支快进到临时分支,从而"吸收" agent 的提交;
  • 若用户在同一时段也提交过导致分歧,--ff-only 会响亮失败并保留临时分支供用户手动合并,绝不静默改写历史。

3.3 事务性收尾与恢复哨兵(#2839)

曾经的真实事故(#2839):若进程在最后一次修复提交与 git worktree remove 之间被打断(系统重启、OOM kill),worktree 会遗留在 git worktree list 中成为孤儿,agent 的分支挂着未合并提交,而 STATE.md 从未推进——从 main 分支视角看 phase "看起来已经 ready",修复却悬在孤立分支上。这正是回归测试 bug-2839-review-fix-transactional-cleanup.test.cjs 断言要锁定的契约。

解决方案是严格有序的四步收尾(必须当作 finally 块无条件执行):

# Step 1: 快进用户分支(--ff-only,失败则保留临时分支)
main_repo="$(git worktree list --porcelain | awk '/^worktree / { sub(/^worktree /, ""); print; exit }')"
ff_status=0
if git -C "$main_repo" merge --ff-only "$reviewfix_branch" 2>&1; then
  ff_status=0
else
  ff_status=$?
  echo "WARN: could not fast-forward $branch to $reviewfix_branch (exit $ff_status)."
fi

# Step 2: 删除 worktree(在移除哨兵之前!)
git worktree remove "$wt" --force

# Step 3: 仅当快进成功才删除临时分支
if [ "$ff_status" -eq 0 ]; then
  git -C "$main_repo" branch -D "$reviewfix_branch" || true
fi

# Step 4: worktree remove 成功后才移除恢复哨兵
rm -f "$sentinel"

四条不可违背的排序约束:

  1. 哨兵只在 git worktree add 成功后写——否则哨兵会指向一个根本不存在的 worktree;
  2. 哨兵只在 git worktree remove 成功后删——若先删哨兵再删 worktree,两步之间被打断会留下"无哨兵 + 孤儿 worktree",正是 #2839 的原 Bug;
  3. 临时分支只在快进成功后删——失败时要留给用户手动 inspect/merge;
  4. 顺序颠倒会重现孤儿 worktree Bug。

哨兵 JSON 同时记录 worktree_pathreviewfix_branch(#3001 CR 强化)——因为如果上次运行死在 git worktree remove 之后、git branch -D 之前,孤立分支会永远污染 git branch 输出。因此下一次运行(或 /gsd:resume-work/gsd:progress)检测到哨兵存在时,会解析出两个字段,分别尽力执行 git worktree remove --forcegit branch -D,再删除陈旧哨兵,实现自愈式重跑

测试侧对这份契约做了机械性断言(bug-2990-code-fixer-worktree-branch.test.cjs):worktree-add 调用必须带 -b $reviewfix_branch;cleanup 中必须恰好一次 merge --ff-only $reviewfix_branch、恰好一次 branch -D $reviewfix_branch、且 merge 严格先于 branch 删除;哨兵 JSON 必须同时含 reviewfix_branchworktree_path 字段。

四、智能修复策略:建议是 G U I D A N C E,不是补丁

fixer 的核心价值主张在 <fix_strategy> 中一句话点透:REVIEW.md 的修复建议是指导(GUIDANCE),不是可盲目应用的补丁agents/gsd-code-fixer.md)。

对每条 finding 的标准动作序列:

  1. 读真实源码:定位 File 行引用的位置,至少带 ±10 行上下文;
  2. 核对当前代码状态:检查代码是否仍是评审者看到的样子;
  3. 适配修复建议:若代码已变化或与评审语境有出入,将建议适配到实际代码上;
  4. 应用修复:优先 Edit 做目标化修改,整文件重写才考虑 Write
  5. 验证:走三级验证策略。

两个显式的降级分支:

  • 若源文件已发生显著变化、建议无法干净落位:标记为 skipped: code context differs from review,记录原因后继续下一条;
  • 若一条 finding 的 Fix 段引用多个文件:全部收集、逐一修复,且把所有改动文件一起放进同一条原子提交。

五、每条 finding 的安全回滚协议

在编辑任何文件之前就要建立回滚能力(agents/gsd-code-fixer.md):

  1. 动手前把每个将修改的文件路径记入 touched_files
  2. 应用修复;
  3. 三级验证;
  4. 验证失败时:对 touched_files 中每个文件执行 git checkout -- {file}——因为此时修复尚未提交(提交只发生在验证通过之后),git checkout -- 只还原该文件未提交的进行中改动,不会影响之前 finding 已提交的内容;
  5. 回滚后重读文件确认已还原到修复前状态,标记 skipped: fix caused errors, rolled back,记录失败详情,继续下一条。

两条硬性红线:

  • 禁止用 Write 工具回滚——工具失败时的部分写入会让文件损坏且无恢复路径,git checkout -- 是原子的;
  • 回滚范围严格限于单条 finding:finding N 的回滚绝不影响 finding 1…N-1 已提交的改动,因为每条 finding 相互独立。

六、三级验证策略(3-Tier Verification)

每应用一个修复都要验证,但验证对象是单条 fix,不是整个 sessionagents/gsd-code-fixer.md)。

Tier 1 — 最低限度(每条修复强制):重读被修改的文件区段,确认修复文本在、周边代码完整无损坏。

Tier 2 — 语法/解析检查(有工具时首选):按语言选择检查命令。

语言 检查命令
JavaScript node -c {file}
TypeScript npx tsc --noEmit {file}(项目存在 tsconfig.json 时)
Python python -c "import ast; ast.parse(open('{file}').read())"
JSON node -e "JSON.parse(require('fs').readFileSync('{file}','utf-8'))"
其他 仅回退到 Tier 1

Tier 2 的判定矩阵需要细致理解:

  • 检查只对你改过的文件失败(且修复前不存在该错误)→ 立即触发回滚;
  • 检查失败但全是既有错误(修复前就存在)→ 继续提交,你的修复不是元凶;
  • 检查失败是因为工具不支持该文件类型(如 node -c 碰到 JSX / TS / 带裸说明符的 ESM)→ 回退到 Tier 1,不要回滚
  • 检查通过 → 进入提交。

Tier 3 — 兜底:文件类型无任何语法检查器(.md.sh、小众语言)时接受 Tier 1 结果,不得因"无语法检查器"而跳过修复。

明确不在范围内:各修复之间不跑完整测试套件(太慢)、不做端到端测试(留给后续 verifier phase)。

6.1 逻辑 Bug 的诚实标注

Tier 1/2 只能验证语法与结构,不能验证语义正确性。一个引入了错误条件、差一(off-by-one)或错误状态处理的修复会通过两层检查并被提交。因此对 REVIEW.md 判定为逻辑错误类(错误条件、错误算法、糟糕的状态处理)的 finding,提交状态在 REVIEW-FIX.md 中必须写 "fixed: requires human verification" 而非 "fixed"——把"人工复核逻辑"作为 flag 交给开发者,防止 phase 在逻辑错误未被确认时进入验证阶段。这是该文档中少见但至关重要的"自动化边界诚实性"设计。

七、原子提交:一个 finding 一条 commit

验证通过后,用 gsd-sdk query commit 按 conventional 格式提交(agents/gsd-code-fixer.md):

gsd-sdk query commit \
  "fix({padded_phase}): {finding_id} {short_description}" \
  --files \
  {all_modified_files}

典型消息:

  • fix(02): CR-01 fix SQL injection in auth.py
  • fix(03): WR-05 add null check before array access

多文件修复必须把所有改动文件以空格分隔列出在 --files 之后(commit 使用位置路径而非 --files 集合语义)。提交成功后提取短哈希:

COMMIT_HASH=$(git rev-parse --short HEAD)

若提交在成功编辑后失败:标记 skipped: commit failed,执行回滚恢复修复前状态,绝不留下未提交改动,把 commit 错误写入 skip 原因后继续下一条。

对计数器使用还强调了一个隐蔽的 shell 陷阱:必须用 FIXED_COUNT=$((FIXED_COUNT + 1))不是 ((FIXED_COUNT++))——后者在 set -e 下会失败并中断脚本(Codex CR-06 教训)。

八、REVIEW-FIX.md:修复结果的机器可读报告

fixer 的最终产物是 REVIEW-FIX.md(agents/gsd-code-fixer.md),路径为 fix_report_path(例如 .planning/phases/02-code-review-command/02-REVIEW-FIX.md)。

---
phase: {phase}
fixed_at: {ISO timestamp}
review_path: {path to source REVIEW.md}
iteration: {current iteration number, default 1}
findings_in_scope: {count}
fixed: {count}
skipped: {count}
status: all_fixed | partial | none_fixed
---

status 三态语义:all_fixed(范围内全部成功修复)、partial(部分修复部分跳过)、none_fixed(全部跳过、未应用任何修复)。正文本体含 "Fixed Issues" 与 "Skipped Issues" 两段:每个已修复项记录 Files modified / Commit / Applied fix;每个跳过项记录 File / Reason / Original issue

提交责任分离:fixer 只提交各条 fix(per-finding),绝不让 fixer 提交 REVIEW-FIX.md——它是文档产物,由 workflow 在全部迭代结束后一次性提交(code-review-fix.mdcommit_fix_report step:docs({padded_phase}): add code review fix report)。提交前还校验其 frontmatter 是否含合法 status: 字段,无效则不提交并提示人工复查。

九、部分失败语义:把崩溃也设计进系统

由于修复是 per-finding 提交的,部分失败是有意的设计而非异常(agents/gsd-code-fixer.md):

  • 运行中途崩溃:部分修复 commit 已经存在于 git 历史中——每条 commit 都自包含且正确(BY DESIGN),后续 recovery/重跑能接着做;
  • 写 REVIEW-FIX.md 前失败:workflow 检测到报告缺失,提示 "Agent failed. Some fix commits may already exist — check git log.",用户可 git log 检查后决定下一步(对应 code-review-fix.mdspawn_fixer 失败分支,该分支会检查 FIX_REPORT_PATH 是否存在以区分"部分成功"还是"零修复");
  • 幂等性:对同一 REVIEW.md 重跑可能因代码已变化而产生不同结果——这不是 Bug,fixer 适配的是当前代码状态而非历史评审语境;
  • 部分自动化:部分 finding 可自动修复、部分需要人类判断,skip-and-log 模式让自动化与人工介入平滑共存。

十、--auto 迭代循环:最多三回合的修复-复评收敛

当用户调用 /gsd:code-review {phase} --fix --auto 时,code-review-fix.md 会在首轮 fixer 之后进入迭代循环(上限 MAX_ITERATIONS=3,初始修复 pass 即 iteration 1):

  1. 原评审深度与文件范围重新 spawn gsd-code-reviewer 复评——文件范围优先取 REVIEW.md frontmatter 中持久化的 files_reviewed_list(避免 --auto 迭代间丢失范围),缺失才回退到整 phase 范围;
  2. 每轮迭代前把上一版 REVIEW.md / REVIEW-FIX.md 备份为 .iter{N}.md(供迭代退化时的事后分析),再由复评覆盖最新评审状态、fixer 覆盖最新修复报告——各产物体始终只有一份最终版,不做逐轮副本;
  3. 复评状态为 clean 即提前 break,否则再 spawn fixer;
  4. fixer 未产出报告则停止循环并告警;
  5. REVIEW-FIX.md 的文档提交仍只发生在全部迭代结束后一次,而非每轮一次。

十一、从文档即产品看工程质量约束

一个容易忽视的事实:在 GSD 体系里,gsd-code-fixer.md 这样的 Agent 定义文件本身就是运行时加载的产品——Claude 在修复时遵循的就是这份文档。正因如此,仓库用结构性测试直接对文档源码做机械断言(allow-test-rule: source-text-is-the-product),把"命令序列契约"当作可测试对象。这正是 bug-2839bug-2990 测试所做的事:解析 agent 文档中的 bash 代码块,断言 sentinel 写入时机、cleanup 顺序、分支删除条件等不可回退的运行时契约。

将成功标准归纳成一张可执行检查清单(来自文档 <success_criteria>):

  • 所有在范围 finding 均被尝试(修复或以原因跳过);
  • 每条修复以 fix({padded_phase}): {id} {description} 原子提交,多文件修复完整列出全部文件;
  • REVIEW-FIX.md 计数、状态、迭代号准确;
  • 无源码停留在损坏态(失败修复经 git checkout 回滚)、无遗留部分或未提交改动;
  • 每条修复都经过验证(最低:重读;首选:语法检查);
  • 回滚一律用原子的 git checkout -- {file} 而非 Write 工具;
  • 跳过项带有具体 skip 原因;修复过程遵守 CLAUDE.md 项目约定。

结语

gsd-code-fixer 用一套可运行的 Agent 定义,示范了"自动化修改他人代码"这类高风险任务应有的工程纪律:用隔离 worktree 规避并发竞争,用恢复哨兵把崩溃变成可自愈事件,用三级验证守住"语法正确"底线并对"逻辑正确"诚实标注,用 per-finding 原子提交保证任何时刻失败都不产生脏状态,用 REVIEW-FIX.md 让每一次修复决策都可追溯。对想要构建类似自动修复/自愈流水线的工程团队而言,这份定义文件连同 code-review-fix 工作流 与其 回归测试 本身就是一份高质量的参考实现:把"安全"作为第一优先级,把"自动化"建立在可回滚、可恢复、可解释的基础之上。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
858
1.35 K
docsdocs
暂无描述
Markdown
899
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
923
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.83 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
532
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.03 K
524
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
393