基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
  • 知识卡片碎片时间过考点
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
    • AI 热点每日 AI 资讯与动向
AI 工作台
简历
旧版
AIHT
内容
精选全部 AI 动态热点榜AI 日报收藏
分类
模型行业编程产品技术观点开源
更多
Agent 接入
返回首页开发者导航
AIHT
精选 / 资讯详情
8.0
热点
AI SCORE
编程提效2026-11-06 11:29

给编程智能体做保留现场的撤销机制

dev.to · AI#AI编程#版本控制#检查点
Editor brief · 编辑速览

Git 回退难以完整恢复智能体执行前的工作目录,尤其涉及未提交修改、忽略文件和新增文件时。文章介绍写入前保存文件内容的检查点方案,并提供约 90 行 Python 实现、对照测试和真实项目计时。

文章思维导图
Knowledge map
拖拽缩放
AI deep read

AI 深度解读

AI 编程助手改坏了项目,执行 git checkout -- .,为什么还是回不到改动前?因为你想恢复的是「助手这一轮开始前的文件夹」,Git 常用命令恢复的却是提交或暂存快照。你没提交的修改、被忽略的 .env、助手新建的文件,都可能让两者对不上。

作者用一个约 90 行的 Python 实现,解释 osFoundry 的逐轮检查点设计,并在真实 TypeScript 源码目录上测了正确性和耗时。对程序员更有用的,是理解一个可靠的「撤销 AI 修改」按钮究竟需要记录什么,以及它仍然会漏掉什么。

为什么 Git 撤销后,文件夹还是不对

作者构造了一个小站点:仓库里有 index.html、212 行的 styles.css、app.js,以及被 .gitignore 排除的 .env。最后一次提交后,用户改了页面标题,还新建了未跟踪的 notes.md。

随后,助手把 CSS 改到只剩 41 行,让 HTML 加载新建的 theme.css,重写 JS,并把 .env 中的 API_URL 指向 localhost。目标是完整找回助手动手前的状态,包括用户尚未提交的工作。

每种撤销方式都在独立副本上运行,结果逐文件、逐字节比较:

checkpoint restore                 matches the state before the turn: True   differs: []
git checkout -- .                  matches the state before the turn: False  differs: ['.env', 'index.html', 'theme.css']
git stash -u                       matches the state before the turn: False  differs: ['.env', 'index.html', 'notes.md']
git checkout + git clean -fd       matches the state before the turn: False  differs: ['.env', 'index.html', 'notes.md']
git stash create before the turn   matches the state before the turn: False  differs: ['.env', 'theme.css']

看最后一行:即使提前执行 git add -A 和 git stash create,这套恢复流程仍没还原 .env 和 theme.css,而且会改变真实暂存区,把用户的工作一起暂存。

这里不能推导出「Git 做不到」。git stash -u 不包含忽略文件,-a 可以把它们纳入;但恢复提交、保存工作区、清理新增文件,仍需要组合处理。直接执行 git clean -fd,又可能把用户原有的 notes.md 一起删掉。

恢复上一次提交,与恢复助手动手前的现场,是两个不同的目标。

检查点该记什么,才能真正按轮撤销

osFoundry 在 Rust 模块 checkpoint_store.rs 中实现了内容寻址存储(content-addressed storage):助手写文件之前,先把旧内容按 SHA-256 保存到 <project>/.osfoundry/checkpoints/blobs/<sha256>,再记录这一轮涉及的路径。

大白话解释:内容相同就共用一份备份,轮次清单只保存「哪个文件原来对应哪份内容」。聊天里的 /rewind 据此恢复。

正确性取决于三个规则。

第一,同一轮内,一个路径只记录第一次写入前的内容。助手可能反复修同一个组件,撤销应该回到本轮开始前,而不是它第二次尝试之前。

第二,原本不存在的文件要记录为 absent。恢复时才能准确删除助手新增的 theme.css,同时保留助手没碰过的 notes.md。

第三,超过 8 MB,也就是代码中 8 * 1024 * 1024 字节的文件,记录为 skipped 并附上原因。它们不会被恢复,但用户能知道哪里失败,避免把部分恢复误认为全部成功。

跨轮恢复也有讲究。回退第 1 轮时,必须同时撤销后续第 2、3 轮;同一个文件被多轮修改,则取最早的旧内容。下面这段原始实现负责这件事:

    def restore(self, turn):
        target = next(c["at"] for c in self.index if c["turn"] == turn)
        affected = sorted((c for c in self.index if c["at"] >= target),
                          key=lambda c: c["at"])          # 从最早的检查点开始
        done, report = set(), {"restored": [], "deleted": [], "failed": []}
        for cp in affected:
            for f in cp["files"]:
                path, pre = f["path"], f["pre"]
                if path in done:
                    continue
                done.add(path)
                if not self._contained(path):            # 再次检查,索引只是普通文件
                    report["failed"].append((path, "outside the workspace"))
                elif pre["kind"] == "skipped":
                    report["failed"].append((path, pre["reason"]))
                elif pre["kind"] == "absent":
                    Path(path).unlink(missing_ok=True)
                    report["deleted"].append(path)
                else:
                    Path(path).parent.mkdir(parents=True, exist_ok=True)
                    Path(path).write_bytes((self.blobs / pre["sha256"]).read_bytes())
                    report["restored"].append(path)
        self.index = [c for c in self.index if c["at"] < target]
        self._save()
        return report

重点看 sorted(..., key=lambda c: c["at"]) 和 done:按时间正序遍历,每个路径只处理一次,才能选中最早的旧内容。作者说 Rust 实现最初写成倒序,后来被 rewinding_a_turn_also_undoes_later_turns 测试抓到。

恢复时还要重新检查路径是否位于工作区,因为 index.json 本身可以被修改,不能让其中的路径直接决定向磁盘哪里写入。完成后移除已回退轮次的索引,也避免重复回退同一位置时虚报成功。

几毫秒的检查点,遇到全仓修改会怎样

作者在真实 React、TypeScript 应用的 src 副本上测试:4,686 个文件,共 121.4 MB,没有文件超过 8 MB。环境是 Apple M1、8 GB 内存、macOS 15、APFS SSD、Python 3.9.6、git 2.39.5。随机选文件、捕获旧内容、追加一行,再恢复并比较,所有恢复结果都一致。

对照方案 git add -A 加 git stash create,无改动时耗时 61 到 68 ms,修改四个文件时为 50 到 67 ms。一次全量重写后,下一轮达到 1,241 ms,原因是时间戳变化触发了大量文件重新计算哈希。

检查点只读取即将被写入的文件,少量修改时成本更低。但「项目再大也只要几毫秒」不能当成无条件承诺,文件大小、存储设备和写入数量都会影响结果。

示例自身就暴露了瓶颈:每次捕获都会重写整个 index.json。全量操作时索引涨到 1.3 MB,前 100 个文件平均每次捕获 1.29 ms,最后 100 个升到 9.54 ms。作者把 _save 改成每次追加一行 JSON 后,同样 4,686 个文件总计 2.5 s,首尾每次捕获分别为 0.41 ms、0.61 ms。格式化全仓或批量重命名,应该考虑这种追加日志。

存储也不能只看「哈希去重」四个字。4,686 个文件有 4,677 种不同内容,全仓备份仍需 4,677 个 blob。一个 2 MB 文件连续十轮各追加一行,会保存 10 份旧内容,占 20.0 MB;如果每轮都写入相同内容,则只需 2 份,共 4.0 MB。

它去重的是完全相同的字节,不是相近版本。原始 blob 共 121.4 MB,用 zlib level 6 压缩后为 33.7 MB,约 3.6∶1;同一目录提交一次后,Git 对象存储为 41.0 MB。Git pack 的版本差量还能进一步节省追加修改的空间,但实现复杂度也更高。

我的判断

我认同它把「撤销一轮助手操作」单独建模。Git 适合管理版本历史,检查点适合保存操作现场,两者可以配合。原文给出的逐字节比较和索引改造前后数据,也比单纯宣称「更快、更安全」有说服力。

不过,这只是一个场景的正确性对照和一套机器上的性能测试,不能证明它全面优于所有 Git 快照方案。原文也没有比较经过专门设计的 Git 恢复流程。

更大的前提是写入入口。示例只捕获经过 write() 的操作,shell 中的 rm、sed -i 会直接绕过。osFoundry 把助手文件写入集中到一个函数,但原文没有证明所有 shell 副作用都被覆盖。因此,接入写文件工具,不等于给整个助手执行环境装上了撤销能力。

另外,回退会覆盖当前文件。用户在助手修改后又手动编辑,自己的新改动也会丢失。作者提出记录助手写入结果的哈希,恢复前检测变化,必要时先保存当前内容;这是改进建议,不能算示例已经具备的保护。

长期保留还需要轮次上限,以及清理不再被检查点引用的 blob。当前实现的价值是机制清楚,离完整的恢复产品仍有距离。

能拿来做什么

  • 使用 AI 修改前端项目时,先确认「撤销」针对的是提交、单次文件写入,还是完整一轮操作;尤其检查未提交组件、未跟踪笔记和被忽略的 .env 是否包含在内。
  • 给 Node 或编辑器助手实现文件工具时,可以在实际写入前记录旧内容,并按轮次去重;验收至少覆盖重复改同一文件、新增文件、跨轮回退和超限文件报告。
  • 让助手执行全仓格式化、批量重命名前,要确认这些命令是否经过检查点入口;如果依赖直接 shell 操作,就不能假设文件工具的备份覆盖了它们。
  • 设计回退按钮时,应明确提示后续手动修改的处理方式,并优先考虑恢复前检查哈希、保存当前内容,让误回退也有补救机会。
Original source

本文由 AI 通读原文后重新编写,观点部分为站内判断,细节以原文为准,来源 dev.to · AI,原文版权归原作者所有。

阅读原文
上一篇
运行编码 Agent 前,先声明权限边界
下一篇
Ripple推出65B开放权重编程模型

资讯速览

dev.to · AI · 2026-11-06 11:29

AI 评分 8.0 / 10

相关标签

#AI编程#版本控制#检查点

继续阅读

  • 代码搜索实测:混合检索胜过纯向量
  • 运行编码 Agent 前,先声明权限边界
  • Ripple推出65B开放权重编程模型
  • 语音房AI审核先测状态与权限边界
  • 预测准确率为何掩盖 AI 系统失效