数据截至 (上游 commit 0004b748b71c)
落到磁盘:九级降级匹配与影子 git 快照
30 秒导读: 模型说"把这段旧代码换成新代码",可它手里的旧代码是从上下文里回忆出来的,跟磁盘上的字节几乎从不一致。这一章讲 Kilo Code 怎么用九级由严到宽的降级匹配把这段话精确落到真实文件上,怎么用唯一性闸门做到"宁可报错也不改错地方",以及改错了之后怎么靠一个与工作区分离的影子 git 仓库一键退回。
这是整个 Kilo Code 里工程含量最高的一段。前面几章讲的是"模型怎么被调起来"(02-agent-loop.md)、"它的手能伸到哪"(03-tools-and-permission.md);这一章讲的是手落下去的那一瞬间。
本章不讲权限闸门(那在 03),也不讲上下文怎么裁剪(那在 05-context-management.md)。
1. 这一章要解决的那个问题
1.1 场景
模型发出一次 edit 工具调用,参数就三个:
filePath—— 改哪个文件oldString—— 要被替换掉的那段旧代码newString—— 换成什么
参数定义见 packages/opencode/src/tool/edit.ts:74(Parameters)。
听起来这就是一次 String.replace。问题出在 oldString 的来源:它不是程序从磁盘现读的字节,而是模型从对话上下文里回忆出来的一段文本。回忆会失真。
1.2 三种翻车方式
| 翻车方式 | 具体表现 | 对策 |
|---|---|---|
| 改不了 | oldString 一个字节都对不上,indexOf 返回 -1 | 九级降级匹配(replacer 级联) |
| 改错地方 | oldString 在文件里出现多次,替换到了错误的那一处 | 唯一性闸门:不唯一就报错,绝不猜 |
| 改坏了 | 位置对、替换成功,但新代码本身是错的 | 影子 git 快照 + 回退链路 + LSP 诊断回灌 |
1.3 三道防线怎么串起来
怎么读这张图:从上往下是一次 edit 调用的完整生命周期,左侧是三道防线各自负责的段落。
模型给出 (filePath, oldString, newString)
|
防线一 ─────────────────────────v──────────────────────────
九级降级匹配 replace() 逐级放宽,命中即停
容忍失真 |
找不到 → 报错,本次编辑作废
|
防线二 ─────────────────────────v──────────────────────────
唯一性闸门 命中的那段在全文中唯一吗?
宁可报错 |
不唯一 → 报错, 让模型补上下文
|
------ 写盘 ------
|
防线三 ─────────────────────────v──────────────────────────
影子 git 快照 每轮开始前已提交一次 tree
后悔药 改坏了 → checkout 那个 hash 退回
|
LSP 诊断回灌进工具输出
2. 为什么 oldString 几乎从不逐字节相同
这一节先把"失真"拆开,因为后面九级降级匹配的每一级,都是在对付其中某一类失真。