​​

请添加图片描述

🌈你好呀!我是 是Yu欸

🚀 感谢你的陪伴与支持~ 欢迎添加文末好友​​

🌌 在所有感兴趣的领域扩展知识,不定期掉落福利资讯(*^▽^*)


写在最前面

 版权声明:本文为原创,遵循 CC 4.0 BY-SA 协议。转载请注明出处。

图 1:RepoMemory Guard 把当前修改、仓库历史和模型判断放进同一条审查链。

一、代码能运行,测试也通过,为什么还是不敢合并?

1.1 AI Coding 让代码写得更快,也把压力推给了 Reviewer

现在用 Codex、Claude Code、GitHub Copilot,以及接入豆包 Seed、GLM、MiniMax、Kimi 等国内模型的 Coding Agent 写代码,已经不是什么新鲜事了。一个过去需要半天完成的跨文件重构,Agent 可能十几分钟就能改完,顺便补上测试、配置和 PR 描述。

问题是,代码写得更快,并不等于代码更容易相信。

Reviewer 经常面对这样的修改:逻辑更短了,类型检查通过了,现有测试也是绿色的,但没人说得清,被删掉的那几行代码当初为什么会存在。

在维护时间较长的仓库里,很多看起来不够漂亮的代码,其实都有来历:

  • 一个不起眼的 if,可能处理过某个很少出现的边界条件;

  • 一段锁或事务代码,可能来自一次并发故障;

  • 一个重试参数,可能专门绕过某种服务端限流行为;

  • 一条兼容分支,可能仍在照顾旧客户端;

  • 一个看似重复的测试,可能是过去某个回归问题留下的“报警器”。

普通代码问答能解释“这段代码做什么”,git blame 能告诉我们“最后是谁改的”,常见 Code Review 工具则更关注当前 Diff 有没有明显问题。

但在 AI 重构场景里,Reviewer 更关心另一件事:

当前 PR 改了什么 ↓ 这段代码最初为什么加入 ↓ 它过去解决过什么问题 ↓ 当前修改会不会把那个问题带回来 ↓ 相关测试还在不在 ↓ 下一步应该补什么证据

RepoMemory Guard 就是从这里开始的。

图 2:Agent 缩短了编码时间,但历史上下文和验证责任并不会自动消失。

1.2 从“代码考古”收敛到一个具体动作

我最早想做的是一个通用代码仓库考古仪:输入一段代码,展示它是什么时候加入的、经过哪些 PR、为什么会变成现在这样。

这个想法有意思,但使用频率不高。开发者通常不会为了好奇,专门打开一个工具浏览代码演化史。

后来我把入口放到了更具体的工作流里:

在合并 AI 生成的 PR 前,先检查它有没有删掉过去专门修过 Bug 的逻辑。

于是项目变成了 RepoMemory Guard。它不替 Reviewer 决定是否合并,也不申请 GitHub 写权限。它只读取公开仓库,把相关历史证据找出来,整理成一份能直接复核的报告。

1.3 哪些工作交给 Git,哪些工作交给 Seed Evolving?

这个项目不能只靠模型。

Git 和 GitHub API 更适合处理确定性事实,例如:

  • 某一行来自哪个 Commit;

  • Commit 是否关联某个 PR;

  • PR 描述里写了什么;

  • 当前 Diff 删除了哪些代码;

  • 代码和测试是否在同一次修改中一起变化。

模型更适合处理事实之间的关系:

  • 这次历史修改到底在保护什么行为;

  • 能否把它概括成一句可验证的约束;

  • 当前 Diff 与过去的修复是否冲突;

  • 还缺哪一种回归测试。

因此,系统的分工很明确:

Git / GitHub 提供事实 ↓ RepoMemory Guard 组织证据 ↓ Seed Evolving 提炼历史意图与约束 ↓ 输出冲突判断和复核建议

这次评测我直接订阅了火山引擎的 Agent Plan。订阅包里除了 Seed 系列,也提供 GLM、MiniMax、DeepSeek、Kimi 等国内常用模型,可以手动切换,也可以直接使用 Auto 模式调度。对这种需要反复跑代码、查历史、调用工具的任务来说,订阅制用起来更省事,也方便后续拿同一套证据包做模型对比。

项目当前使用:

ARK_MODEL=ark-code-latest ARK_BASE_URL=https://ark.cn-beijing.volces.com/api/plan/v3

ark-code-latest 使用 Auto 模式,由平台在效果和速度之间自动选择目标模型。调用走 OpenAI-compatible Responses API。后续若要固定测试某个模型,也可以直接在 Agent Plan 的模型列表中切换,再重复同一套分析流程。

API Key 只保存在本地 .env 中,不写入代码,也不会出现在导出的报告里。

1.4 1M 级上下文在这个场景里怎么用?

这里并没有一份需要硬塞进模型的超长文件。

PR 审查的上下文本来就很分散:

当前 Diff + Base Commit + 函数上下文 + 历史 Commit + PR 描述 + Issue 讨论 + Review 评论 + 相关测试 + 测试结果 + 用户当前需求

如果 Agent 参与的是一个持续数小时甚至数天的任务,它还要记住前面已经做过什么、用户否定过哪些方案、哪些测试跑过、后续计划有没有和早期约束打架。

所以,长上下文在这里更像一个工程工作区。它让模型有机会同时看到“现在要改什么”和“过去为什么这样写”,而不是每到一个新步骤就重新猜一遍背景。

图 3:上下文不是单一长文档,而是 Diff、历史、测试、用户需求和 Agent 状态的组合。

二、项目怎么控制分析成本?

2.1 不给整个仓库做无差别考古

如果每个 PR 都把所有修改文件、所有 Commit、所有 Issue 一股脑交给模型,成本高,结果也容易被无关历史淹没。

RepoMemory Guard 先做一轮轻量筛选:

Pull Request Diff ↓ 排除生成文件、构建产物等噪声 ↓ 识别修改语义 ↓ 定位到函数、方法或类 ↓ 检查生产代码和测试是否一起变化 ↓ 生成 HIGH / MEDIUM / LOW 优先级 ↓ 只对少量候选恢复完整历史 ↓ 调用 Seed Evolving 做证据推理

当前版本最多读取 20 个修改文件,最多挑出 5 个候选进入深度分析。这个边界足够完成演示,也避免把项目做成一套庞大的仓库知识平台。

图 4:先收缩范围,再把有限的分析预算留给更可能承载历史约束的修改。

2.2 系统会关注哪些修改?

第一版重点处理几类容易藏着历史原因的变化:

  • 锁、事务和并发控制被删除;

  • 重试、退避和限流逻辑变化;

  • 幂等、防重复和唯一性保护变化;

  • 权限和访问控制变化;

  • 输入校验、边界 Guard 或 assert 被删除;

  • except、finally、回滚和清理路径变化;

  • 默认值改变;

  • 对象或配置状态不再向后续对象传播;

  • 测试函数被删除;

  • 测试断言或参数化场景被弱化。

例如下面这行:

- respect_retry_after_header=self.respect_retry_after_header,

单纯看字符串,它只是一条参数复制;从修改语义看,它属于:

state_propagation_removed

也就是显式配置不再传给后续对象。这种变化即使没有明显报错,也值得查一下历史。

2.3 为什么“实现和测试一起删”特别值得警惕?

一次清理式重构里,生产逻辑和测试可能同时变少。

单看生产代码,像是在去掉重复逻辑;单看测试,像是在合并重复用例。可如果被删掉的测试恰好是那段逻辑唯一的回归保护,剩余测试就可能继续变绿。

RepoMemory Guard 会检查:

  • 生产文件和测试文件是否引用相似标识符;

  • 是否属于同一个模块;

  • 是否同时删除行为和断言;

  • 是否删掉了和历史修复直接对应的测试。

这种组合会显著提高考古优先级。

2.4 项目结构

项目使用 FastAPI 和原生前端,核心模块保持在一个较小范围内:

RepoMemoryGuard/ ├── app/ │ ├── analysis/ │ │ ├── triage/ │ │ │ ├── noise_filter.py │ │ │ ├── edit_classifier.py │ │ │ ├── symbol_mapper.py │ │ │ ├── test_coupling.py │ │ │ ├── priority.py │ │ │ └── pipeline.py │ │ ├── history.py │ │ ├── seed.py │ │ ├── reporting.py │ │ └── cases/ │ ├── github/ │ ├── git_history/ │ ├── domain/ │ ├── api/ │ ├── web/ │ └── main.py ├── docs/cases/ ├── examples/ ├── scripts/ ├── tests/ ├── README.md └── pyproject.toml

技术栈如下:

模块 技术
后端服务 FastAPI
数据模型 Pydantic
GitHub 数据 GitHub REST API
代码历史 git blame、Commit 元数据
Python 符号定位 AST
模型调用 Ark Responses API
模型 ark-code-latest
页面 HTML、CSS、JavaScript
报告 JSON、Markdown

三、从一个 PR 到一份历史审查报告

3.1 读取公开 PR

用户可以输入:

公开 GitHub 仓库地址 + PR 编号

也可以直接粘贴完整 PR URL:

https://github.com/owner/repo/pull/123

系统会读取 PR 标题、描述、Base SHA、Head SHA、修改文件和 Unified Diff。第一版只处理公开仓库,不安装 GitHub App,也不申请写权限。

3.2 过滤噪声

二进制文件、Minified 文件、自动生成文件、Vendored 代码、构建产物和部分依赖锁文件会被优先跳过。

测试、配置、Schema 和 Migration 不会被简单忽略,因为这些文件也可能记录重要约束。

3.3 把修改定位到具体符号

对于 Python 文件,系统会用 AST 把旧行号映射到函数、方法或类。例如:

文件:src/urllib3/util/retry.py 行号:283 符号:Retry.new

这样 Reviewer 看到的不只是“某个文件删了一行”,而是“Retry.new() 的状态复制行为发生了变化”。

3.4 生成可解释的优先级

系统内部使用 HIGH、MEDIUM、LOW 控制分析顺序,并保留触发原因。

urllib3 案例得到的是:

HIGH · Retry.new

原因包括:

deterministic_medium_risk sensitive_removal state_propagation_removed direct_regression_test_removed_or_weakened

这比一个孤立的“风险分 82”更容易复核。Reviewer 能看见,候选被选中是因为状态传播和直接测试同时被删,而不是仅仅因为变量名里出现了 retry。

3.5 沿代码行恢复历史

对 HIGH 和 MEDIUM 候选,系统执行:

当前旧代码行 → git blame → Commit → 关联 Pull Request → 关联 Issue → 历史问题描述

这些结果统一标记为 FACT。模型不能替系统编造不存在的 Issue、事故或测试。

3.6 让 Seed Evolving 在证据范围内判断

系统把当前 Diff、历史 Commit、PR 描述和测试变化整理成结构化输入,交给 ark-code-latest。

模型需要返回:

| "historical_intent": "历史代码保护的行为", "invariant_statement": "可验证的历史约束", "conflict_status": "possible_violation

no_clear_conflict | insufficient_evidence", "reasoning": "当前 Diff 与历史证据之间的关系", "missing_test_scenarios": [], "evidence_ids": [], "confidence": 0.0 | | --- |

项目会过滤不存在的证据 ID,并把结果分成三类:

  • FACT:来自 Git、PR、Issue、代码和测试;

  • INFERENCE:模型根据事实作出的判断;

  • UNKNOWN:证据不足,不能继续下结论。

图 5:模型负责解释证据,但不能补写仓库里没有发生过的历史。

3.7 生成 Reviewer 能直接使用的报告

结果页会集中展示:

  • 哪些修改值得考古;

  • 修改属于哪个符号;

  • 为什么优先级较高;

  • 关联的历史 Commit 和 PR;

  • 模型提炼出的历史约束;

  • 测试发生了什么变化;

  • Reviewer 下一步应该补什么证据。

报告可以在页面查看,也可以复制为 GitHub 风格评论,或下载为 Markdown 和 JSON。是否把评论发到 PR,由 Reviewer 自己决定。

图 6:确定性工具先恢复事实,模型只处理事实之间的关系。

四、真实案例:urllib3 中一行不起眼的参数复制

4.1 为什么选 urllib3?

第一个案例来自公开仓库 urllib3/urllib3,历史锚点是 PR #1607:

Improve implementation of respect_retry_after_header

这个 PR 修复的问题很具体:respect_retry_after_header 虽然可以传给 Retry,但过去没有被 Retry.new() 继续传播。第一次重试递增后,用户显式设置的值会丢失。

历史修复加入了一行:

respect_retry_after_header=self.respect_retry_after_header,

并加入直接回归测试:

def test_respect_retry_after_header_propagated(...): retry = Retry(respect_retry_after_header=False) new_retry = retry.new() assert new_retry.respect_retry_after_header is False

这段代码很适合做案例:只有一行,看起来像普通参数复制,删掉以后没有语法错误,问题只会在后续对象里出现,而且历史 PR 和回归测试都很完整。

4.2 构造一次本地清理回放

为了不向上游仓库提交无意义 PR,我在固定 Commit 上构造了一次本地修改:

- respect_retry_after_header=self.respect_retry_after_header,

同时删除直接回归测试:

- def test_respect_retry_after_header_propagated(...): - ...

这里需要明确区分两部分:urllib3 的仓库、历史 PR、Commit、代码和测试都是真实的;当前风险 Diff 是本地评测回放,没有提交到上游。

4.3 自动筛选和历史恢复结果

RepoMemory Guard 对这份 Diff 的筛选结果是:

HIGH · Retry.new

识别到的修改语义包括:

sensitive_removal state_propagation_removed test_removed test_assertion_weakened

关联测试文件为:

test/test_retry.py

随后系统沿第 283 行恢复出:

src/urllib3/util/retry.py:283 → Commit 728d9244665ef5b03103cb74d7b409ebe4f23b43 → Improve implementation of respect_retry_after_header (#1607) → PR #1607

图 7:从当前被删代码回到历史 Commit 和 PR,整条链都可以人工核对。

4.4 三阶段测试

这个案例没有停在模型评论上,我实际跑了代码和测试。

阶段 实际结果
原始 test/test_retry.py 44 passed, 18 skipped
只删除配置传播,保留回归测试 1 failed, 43 passed, 18 skipped
配置传播和直接测试一起删除 42 passed, 18 skipped
继续运行核心测试 1422 passed, 46 skipped, 6 warnings

只删除实现时,原有回归测试马上失败:

requested=False Retry.new() 后=True

把直接测试也删除后,剩余测试重新变绿。但运行时探针仍然返回:

{ "requested": false, "after_new": true, "regression_reproduced": true }

也就是说,历史问题已经回来,只是原来负责揭露它的测试也被删掉了。

图 8:保留回归测试时问题会暴露;测试一起删除后,测试结果恢复为绿色,但行为仍然错误。

需要补充一个边界:1422 passed 对应非 dummyserver、非 contrib 的核心测试集,不代表所有平台相关测试组全部通过。

4.5 ark-code-latest 的实时判断

接着,我把同一组 Diff、Commit、PR 和测试证据交给 ark-code-latest。

模型返回:

conflict_status = possible_violation

它提炼出的历史意图是:确保 respect_retry_after_header 在 Retry.new() 创建新实例时继续传播,避免用户的显式重试偏好在第一次递增后丢失。

对应的历史约束是:

任意 Retry 实例调用 new() 后,新实例的 respect_retry_after_header 应与原实例保持一致。

模型还建议补两类测试:单次 new() 后检查属性保持,以及多次重试递增过程中的持续保持。

这部分属于 INFERENCE。PR、Commit、代码和测试才是事实来源。

4.6 最终给 Reviewer 的信息

最后的报告会把结论压缩成几项可核对信息:

目标符号:Retry.new 考古优先级:HIGH 历史来源:Commit 728d924 / PR #1607 历史约束:显式配置必须跨后续 Retry 对象保持 当前变化:状态传播和直接回归测试同时删除 测试结果:1422 项核心测试通过,但行为探针仍失败 建议:恢复实现与回归测试,或明确说明兼容性变化

图 9:结果页把目标符号、历史来源、测试证据和下一步动作放在一起。

五、它是不是一个真实、常用、能落地的小工具?

5.1 真实

这个问题来自日常 AI Coding:Agent 很容易完成大范围清理,Reviewer 却没有时间逐条恢复仓库历史。旧代码看起来重复,测试看起来多余,但它们可能正好对应某次过去的问题。

urllib3 案例使用的是公开仓库和真实历史修复,不是完全虚构的业务故事。

5.2 使用频率

在经常使用 Coding Agent 的团队里,删除兼容分支、简化重试、调整默认值、缩小事务范围、合并测试等修改会反复出现。RepoMemory Guard 的使用时机也很清楚:重要 AI PR 合并前,先跑一次历史检查。

目前项目还没有真实团队使用数据,所以不能把它写成已经验证过的市场刚需。更准确的说法是,它面向一个高频工作流,而且入口足够明确。

5.3 可执行

报告不是给一个笼统风险分,而是告诉 Reviewer:应该打开哪个 PR、哪段代码来自哪个 Commit、哪个测试被删、还缺什么场景,以及作者需要解释什么。

这几项都可以直接转化为审查动作。

5.4 轻量

第一版只做以下范围:

只读公开仓库 单个 PR 最多 20 个修改文件 最多 5 个深度考古候选 Python 优先 无需 GitHub App 无需私有仓库权限 不自动修改代码 不自动阻止合并 本地导出 Markdown

这个规模适合本地运行、录屏和复现,也没有为了演示额外堆叠数据库、向量检索或微服务。

六、下一步:把它放进 Coding Agent 的执行过程

6.1 从 PR 审查前移到任务执行中

当前流程是:

Agent 完成代码 → 创建 PR → RepoMemory Guard 分析 → Reviewer 查看报告

后续我更想把它放进 Codex 等 Agent 的执行链:

用户提出任务 → Agent 制定计划 → Agent 修改代码 → RepoMemory Guard 检查当前步骤 → 没有冲突则继续 → 有冲突则询问用户 → 更新项目记忆 → Agent 继续剩余任务

这样它就不只是事后审查工具,而是任务过程中的一个 Memory Gate。

6.2 什么情况下应该打断用户?

不是所有历史差异都值得打断任务。只有当前计划准备推翻一个明确约束时,才需要停下来确认。

例如:

用户最初要求:继续兼容 Python 3.9 后续 Agent:准备删除 Python 3.9 兼容分支

或者:

历史 PR:明确禁止无限自动重试 当前 Agent:为了提高成功率准备移除重试上限

再比如:

前一步用户确认:只修改后端,不改数据库 Schema 后续 Agent:发现最简单方案需要新增数据库字段

这时 Agent 不应该替用户选,而应该问一个具体问题:

当前方案需要删除 Python 3.9 兼容逻辑,但项目之前要求继续支持 Python 3.9。你希望保留兼容性,还是允许提高最低版本?

6.3 用户回答后,记忆怎么更新?

用户的回答不能只停留在一轮对话里。它需要变成一条带来源、作用范围和替代关系的项目决策,例如:

memory_id: decision-python-min-version scope: project status: active decision: "最低 Python 版本提高到 3.11" replaces: - "继续支持 Python 3.9" reason: "用户确认旧环境已经下线" evidence: - user_confirmation:2026-07-24T20:10:00+08:00 affected_paths: - pyproject.toml - ci/** - compatibility/**

之后,旧约束被标记为 superseded,Agent 用新决策更新计划,再继续修改代码。

6.4 为什么不能让 Agent 自动选择?

很多工程冲突没有唯一正确答案。删除兼容分支,可能是因为旧客户已经下线,也可能是因为团队暂时忘了还有旧客户在用。仓库历史只能解释过去,不能自动证明过去的前提今天是否仍然成立。

所以 Memory Gate 的作用不是让 Agent 永远服从历史,而是在它准备推翻历史时,把决定交还给用户。

6.5 项目记忆应该存什么?

可以把长期记忆分成四类:

记忆类型 内容
Requirement 用户当前要求和验收条件
Historical Constraint 从代码历史中恢复出的设计约束
Decision 用户对冲突作出的明确选择
Verification 测试、CI 和运行结果

图 10:需求、历史约束、用户决策和验证结果共同组成项目记忆。

每完成一个高风险步骤,Agent 都可以检查:这次改了什么,是否触及历史约束,是否和本轮需求或前面的决策冲突,当前测试能否支撑结论。

没有冲突就继续;有明确冲突就停下来询问。用户确认后,更新记忆并重新规划后续步骤。

图 11:出现冲突时,Agent 先询问用户,再更新记忆和计划。

6.6 这个方向解决的不是“记不住聊天记录”

Coding Agent 的问题往往不是完全忘记前文,而是没有区分不同信息的效力:用户当前要求、旧 PR 的设计约束、一次临时讨论和已经通过测试验证的结论,重要程度并不一样。

Memory Gate 希望做的是把这些信息变成可追溯的项目状态。当后续步骤和前面冲突时,系统能指出冲突来自哪里,并要求一个明确的新决定。

七、目前做到什么程度?

当前版本已经完成:

  • Web 页面;

  • 公开 PR 读取;

  • 噪声过滤;

  • 修改语义分类;

  • Python 符号定位;

  • 实现与测试成对删除检测;

  • 考古优先级;

  • Git 历史恢复;

  • ark-code-latest 实时分析;

  • Markdown 和 JSON 报告;

  • urllib3 真实案例回放;

  • 39 项项目测试通过。

还没有解决的部分包括:

  1. 自动判断未修改测试文件中是否仍有等价覆盖;

  2. 多语言符号定位;

  3. 更多正例和负例;

  4. 与其他模型的公平对比;

  5. Codex 工作流中的自动 Memory Gate;

  6. 用户确认后,项目记忆的持久化和版本管理。

因此,当前版本更适合称为一个已经跑通主要链路的轻量 MVP。它可以录屏、复现,也能解释自己的边界,但还不是完整的企业级 Agent 记忆系统。

结语

这次实践没有再做一个“让模型多写一点代码”的 Demo,而是把注意力放到了代码生成之后:当修改越来越快,谁来解释旧代码为什么存在?

在 urllib3 案例中,本地重构只删了一行状态传播代码和一段直接测试。剩余 1422 项核心测试仍然通过,但运行时行为已经回到六年前修复前的状态。

RepoMemory Guard 沿着代码行找回了 Commit 和 PR,Seed Evolving 则把这些分散证据整理成一条可以复核的历史约束。它并不阻止旧代码被修改,只要求修改者回答几个具体问题:过去为什么这样做,过去的前提是否还成立,这次为什么要改变,以及新的决定由谁确认。

如果后续把这套逻辑放进 Codex 等 Agent 的执行流程,当计划和历史约束发生冲突时,Agent 就能先停下来问清楚,再更新项目记忆并继续开发。

这比单纯追求“更快生成代码”更接近长期维护真正需要解决的问题。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐