hunk 追踪 · 怎么改文件而不踩掉你的改动

agent 在改文件,你可能同时也在改。grok 用 xai-hunk-tracker 做「行级、带归因」的改动追踪:把每处改动记成一个带作者标签的 hunk,拒绝时只回滚那几行、绝不用整份旧快照覆盖你的编辑。这一章拆它的 actor、归因与防覆盖。

作者 David更新于 第 21 篇(共 60 篇)

hunk 追踪 · 怎么改文件而不踩掉你的改动

一个会自己动手改代码的 agent,有个隐蔽的危险:它读了文件、想了半天、然后写回去——可这中间你也改了这个文件。它一写,你的改动就没了。grok 用 xai-hunk-tracker 专门防这件事,做法很讲究。

又一个 actor,追踪「hunk」

HunkTrackerActor(还是那套:独占 task + mpsc 命令 + oneshot 查询,无锁)追踪的单元叫 hunk——基线(git HEAD 内容)与当前内容之间一段连续的行级改动,由 diff.rs 基于 similar crate 算出。每个 hunk 带:

  • 一个稳定的 HunkId;
  • 一个 HunkSource ——这是灵魂:AgentEdit{prompt_index}(agent 改的)/ ExternalEditOnAgentFile(agent 碰过的文件被外部改了)/ External(纯外部)。

防覆盖:靠归因 + 行级写,不靠锁

grok 不锁文件。它的「不踩掉你改动」是一套行级、带归因的设计:

  1. 检测并发编辑:handle_file_change 读盘,发现一个 agent 文件被外部改了,就把那处记成 ExternalEditOnAgentFile 的 hunk——浮出来给你 review,而不是默默吸收或覆盖。
  2. 归因存续:recompute_hunks 里,新 hunk 会继承匹配的旧 hunk 的 HunkId;若「新 hunk 是外部的、但最佳匹配是 agent 的」,agent 归因存续下来,同时把触发者转发给 LOC 记账——既不冤枉人,也不丢历史。
  3. 拒绝时行级回滚(真正的防覆盖保证,actor/actions.rs):拒绝一处改动时,绝不写整份旧快照,而是 patch_lines 只针对那个 hunk 的行区间、打在刚读到的最新 current_content 上(按行倒序打避免偏移),再写盘。文件里别处你的改动纹丝不动。

对比一下天真的做法:「读文件 → 内存里改 → 整份写回」。只要你在它读之后、写之前动过这文件,整份写回就把你的改动抹了。grok 用「行级 patch 打在最新内容上」把这个窗口关掉了——这是生产级编辑工具和玩具编辑工具最实在的差距之一。

接受 / 拒绝的语义

  • 接受:基线在内存里打上这处改动、baseline_accepted=true、hunk 移除——不写盘(文件本来就是你要的样子)。
  • 拒绝:文件按基线回滚那几行;特例 RestoreDeleted / DeleteCreated(删掉新建的文件)。

避免「幻影 diff」与 Git 集成

不是所有文件都能 diff。符号链接(lstat)、Git-LFS 指针(认前缀)、二进制(前 8000 字节有 NUL)、超过 1 MiB 的 TooLarge——统统归为不可 diff,清掉 hunk、改查 git dirty 缓存。Git 这一侧用的是纯 Rust 的 gix(不是 git2),所有 gix 调用丢进 spawn_blocking,基线批量读用 JoinSet 并行(上限 64)。

LOC 归因:算清谁写了多少行

loc/ 这个 sink 把 hunk 事件转成「谁改了多少行」的 JSONL 记录(Agent vs Human)。妙在拒绝/被取代时会写一条负记录,让基于 SUM 的统计自动归零——这样「agent 写了又被拒」不会虚增它的产出。

和《从 LLM 到 Coding Agent》对照

教学最小实现里,「编辑文件」通常就是 write(path, new_content)。grok 的增量全在「多方并发改同一个文件」这个真实困境上:行级追踪、作者归因、拒绝时只回滚自己那几行。它回答的是一个玩具永远不会遇到、产品却天天遇到的问题——怎么让 AI 大胆改代码,又不让它把人的活儿冲掉。


上一篇:三种编辑格式 · 下一篇:Shell 执行与 PTY

本页目录