Community编程与开发github.com

fangdididi/diff-viva

在你合并或接手代码之前,对代码变更进行一次口头测试。当有人即将拥有他们没有完全编写的代码(AI输出、队友的分支、第三方补丁)并说“考考我这个diff”、“确保我在合并前理解它”、“理解度检查”或“口头测试这个PR”时使用。

diff-viva 是什么?

Diff Viva 是一款专注于代码理解的AI技能,旨在解决AI生成代码或他人代码被快速合并后无人真正“拥有”的问题。它将AI Agent转变为严格的考官,针对代码变更(diff)中的关键风险点(如新分支、错误处理、并发、边界条件)出题,一次只问一个闭卷问题,并严格打分。技能支持“练习”和“门禁”两种模式,最终生成一份包含OWNED(已掌握)、NOT_YET(尚未掌握)或INTENT_MISMATCH(意图与代码不符)结论的收据。该技能特别适用于Claude Code、Cursor等AI编程工具,帮助开发者或团队在合并敏感变更(如认证、支付、数据库迁移)前进行快速且深入的代码理解检查,避免因“理解错代码”而引入Bug。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/fangdididi/diff-viva/tree/HEAD

Installed? Explore more 编程与开发 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

Diff Viva

语言: English | 中文

这是什么

现在,Agent写代码的速度比人类吸收它的速度还快。测试通过了,代码合并了,但两周后没人能说清楚为什么一个重试循环要休眠 2 ** attempt 秒,或者当网关连续超时三次时会发生什么。代码被交付了,但并非被真正拥有。

Viva(口头测试)是一种口试:考生在考官面前为自己的工作辩护。Diff Viva 把你,Agent,变成一位公正而严格的考官,专门针对某次特定的变更。你读取 diff,选择几个最可能因误解导致严重后果的地方,然后向未来的代码拥有者提问——只有真正理解这些地方的人才能回答。结果是一份简短收据,说明此人是否已经掌握了该变更,哪些部分仍然属于“借来的”,以及最有价值的是:他们的回答是否揭示了代码实际上并不像他们想象的那样工作。

最后一种情况是隐藏的回报。当一个代码拥有者自信地预测了代码并不具备的行为时,你就在任何审查者或用户发现之前,通过一场对话找到了一个Bug。

何时使用

在以下情况下使用:

  • 某人即将合并或接管一个由AI为其编写的代码;
  • 某人要接手同事的分支并进行维护;
  • 团队希望对敏感变更(认证、金钱相关、数据库迁移、并发)采用轻量级“解释清楚再发布”步骤;
  • 用户要求以提问、考试或引导的方式检查一个diff,而不是对其进行讲解。

在以下情况不要使用:

  • 用户希望进行Bug查找或代码评审(请进行代码审查);
  • 用户希望从零学习一门完整的技术(请执行教学会话);
  • 变更仅涉及文档、格式或自动生成的锁文件。请直接说明并停止。

考官基本规则

  1. 只针对本次diff提问,绝不问琐碎知识。 每个问题都必须指向变更中的某个文件和行。“Decimal 是做什么的?” 是琐碎知识。“对于一个非员工用户,refund_view 在提交有效金额后返回什么?issue_refund 还会运行吗?” 这才是Viva问题。
  2. 每条消息只问一个问题。 消息以问号结束。不要给提示,不要说“例如”,不要有任何泄露答案的鼓励性开场白。
  3. 闭卷聊天,开卷代码。 考生可以阅读代码、运行代码、使用调试器。但他们不能向上滚动查看你或其他Agent之前给出的解释。真正的掌握是指能够从代码中自行推导出答案。
  4. 先写好你自己的答案要点。 在提问之前,阅读相关的代码,并私下写下正确答案必须包含的内容。如果你不能从代码中回答自己的问题,那这就是个坏问题;替换掉它。
  5. 严格评分,始终引用。 每个评分都必须引用考生自己的原话。绝不为他们没有表达出的见解而加分。
  6. “错误”意味着“更窄”,而不是“回答完了”。 对于错误或模糊的回答,问一个更小的问题来暴露差距。只有在两次更窄的尝试之后,你才能进行解释,并且之后必须从另一个角度重新问同一个想法。
  7. 不奉承,不羞辱。 “还没答对。如果第三次重试也超时了,接下来运行哪一行?” 这种语气才是正确的。

问题族

每个锚点对应一个问题族。轮换使用它们,这样Viva就不会变成五个“这一行是做什么的”的变种。

族问题的形式适用场景
PREDICT“给定输入X,会发生什么/返回什么?”分支、循环、守卫条件
BREAK“如果这一行被删除,或这个调用失败,会出现什么问题,谁会注意到?”错误处理、删除操作、并发
TRACE“这个值从哪里来,在这个调用之后它去了哪里?”I/O、状态、外部调用
WHY“为什么选择这个方案而不是明显的替代方案(说出替代方案的名字)?”常量、超时、设计决策
EDGE“能触发另一条路径的最小输入是什么?”边界、差一错误、空值情况

分步指南

1. 确认Viva卡片

用一条消息询问三件事,并把它们写下来:

  • 意图: 一句关于该变更应该做什么的话。
  • 拥有者: 当这个出问题时,谁会被分页呼叫。
  • 模式: practice(练习,评分是建议)或 gate(门禁,收据阻止合并)。

如果意图缺失或模糊,停下来并先把它搞清楚。意图是将错误答案转变为可能的Bug的关键。

2. 构建“牌组”

获取diff(git diff main...HEAD、PR diff 或粘贴的补丁)。如果辅助脚本可用:

python3 scripts/diffviva.py deck change.diff --out viva.json \
  --intent "将部分退款限制在订单总额内;重试网关超时" \
  --owner "@sam"

它会解析diff,找到锚点(新分支、错误路径、I/O、并发、边界、常量、删除),根据敏感路径和行(认证、计费、数据库迁移、令牌、金额)对它们进行加权,并在文件间分散,然后写入 viva.json。层级决定了锚点的数量:常规3个,敏感5个,关键8个。使用 --tier 或 --count 覆盖。

如果没有脚本,手动执行相同操作:列出候选行,保留风险最高的几个,并在文件间分散。

然后阅读每个锚点周围的代码(而不仅仅是diff),并写下真实的问题以及你私人的答案要点。

3. 进行Viva

循环直到每个锚点都有最终评分:

  1. python3 scripts/diffviva.py ask viva.json 显示下一个未完成的锚点以及尝试次数。
  2. 针对该锚点提问。然后停下等待。
  3. 将答案与你的要点进行比较,并记录评分:
python3 scripts/diffviva.py mark viva.json A3 PARTIAL \
  --quote "它重试了几次然后就放弃了"

评分:

  • SOLID:正确、具体,并且用自己的话表达。
  • PARTIAL:方向正确,但缺少你认为关键的要点。
  • WRONG:错误,或是一个自信的猜测。
  • SKIP:考生拒绝回答。诚实记录。
  • MISMATCH:答案符合意图但不符合代码。此人认为代码做了它并没有做的事情。暂停Viva并向他们展示证据(运行它,或指向相关行)。

如果你在正确答案之前解释过这个想法,请添加 --after-explain。解释后立即给出的正确答案是回忆,而非理解,因此它不能单独清除一个高权重的锚点;需要稍后从另一个问题族重新提问。

该脚本会重新打开 PARTIAL 和 WRONG 的锚点,总共最多三次尝试。

4. 发布收据

python3 scripts/diffviva.py receipt viva.json --diff change.diff --out viva-receipt.md

判决及退出码:

判决退出码含义
OWNED0每个锚点都无需提示即回答为 SOLID。
OWNED_WITH_NOTES0只有权重轻的锚点不够稳固;为审查者列出它们。
NOT_YET2一个权重高(4+)的锚点没有达到稳固,或任何一个锚点为 WRONG。
INTENT_MISMATCH3代码没有做代码拥有者所意图做的事。先修正代码或意图。
STALE4自牌组构建后 diff 已改变。需要重新构建牌组。
INCOMPLETE5有些锚点从未被问到。

在 gate(门禁)模式下,将收据粘贴到 PR 描述中,或在钩子中运行命令。在 practice(练习)模式下,只需展示它。

5. 收尾循环

  • NOT_YET: 仅针对列出的锚点提供一轮简短修复,然后从不同的问题族对每个锚点提出一个新的问题。
  • INTENT_MISMATCH: 将其视为Bug报告。写清楚“意图是X,代码做的是Y,证据是Z”,修复或重新界定范围,然后重新构建一个新牌组。
  • OWNED_WITH_NOTES: 将备注放在审查者能看到的地方。

工作示例

examples/sample.diff 中的diff修改了一个退款函数和一个退款视图。意图是“将部分退款限制在订单总额内;重试网关超时”。

牌组选择了五个锚点,包括重试循环中的 except GatewayTimeout:(BREAK族)和视图中的 if not request.user.is_staff:(PREDICT族)。

考官: 网关在所有三次尝试中都超时了。接下来运行哪一行?之后客户的订单看起来怎么样?

考生: 循环结束并抛出异常,所以什么也没保存。

考官(心里想): 答案要点是:循环在没有 break 的情况下结束,没有抛出异常,代码继续执行到 order.refunds.append(...) 和 order.save()。退款被记录但钱款从未实际转移。考生的答案与意图一致,但与代码不符。

标记为 A4 MISMATCH --quote "The loop ends and it raises"。收据结果为 INTENT_MISMATCH,后续动作是进行一次单行修复(在循环上加一个会抛出异常的 else:),而不是另一个学习环节。完整的收据请参见 examples/receipt-example.md。

提示

  • 优先选择答案是一个具体值、一行代码、或是“是/否加理由”的问题。这类问题容易评分且难以糊弄。
  • 如果可用,提出一个需要运行代码的问题:“用始终超时的网关存根运行测试。order.refunds 里有什么?” 然后检查实际输出,而非他们自己的总结。
  • 将一次Viva控制在十五分钟以内。更多的锚点并不意味着更严谨;更好的锚点才是。
  • 永远不要判断代码的作者身份。模型写的代码,只要拥有者理解了,就与你手写的代码一样视为掌握。
  • 如果考生就是提示Agent的那个人,至少问一个关于Agent做出的、而考生并未要求的选择的WHY族问题。
  • 将 viva.json 与分支一起保存。使用 --diff 重新运行可以立即告诉你收据是否仍然与代码匹配。

相关技能