Show HN: RealDiff – 针对拉取请求的运行时行为差异分析(支持六种语言)

Hacker News Top 工具

摘要

RealDiff 是一款开源工具,它通过比较测试输出来检测拉取请求中的运行时行为变更,甚至能在未直接编辑的文件中高亮显示非预期影响。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/08/27 15:24

issacnitin/RealDiff 来源:https://github.com/issacnitin/RealDiff

RealDiff CI (https://github.com/issacnitin/RealDiff/actions/workflows/ci.yml) Release (https://github.com/issacnitin/RealDiff/releases/tag/v0.4.0)

许可证:MIT
.NET 8 (https://dotnet.microsoft.com/download/dotnet/8.0)

RealDiff 能够发现普通源码审查遗漏的运行时行为变化。它会构建两个 Git 修订版本,观察其测试表现,基于三次基线运行学习噪声基线,并报告每个调用树中首次发生的行为变化。
源码差异告诉你修改了什么,而 RealDiff 告诉你这些修改产生了什么效果,包括影响了那些在拉取请求中未触及的文件。

为何使用它?

一个看似无害的重构可能改变远离被编辑文件的行为:

public static List<(int Priority, T Value)> ByPriority(
    this IEnumerable<(int Priority, T Value)> src)
-{
-    var list = src.ToList();
-    list.Sort((a, b) => a.Priority.CompareTo(b.Priority));
-    return list;
-}
+    this IEnumerable<(int Priority, T Value)> src) =>
+        src.OrderBy(item => item.Priority).ToList();

List.Sort 是不稳定排序;OrderBy 是稳定排序。在包含的演示中,这一行基础设施代码的修改改变了未被编辑的定价引擎:

RealDiff: 发现 1 处位于此差异之外的行为变化
DiscountEngine.SelectDiscount 原返回 "CLEARANCE_40",现返回 "SEASONAL_15"。
CheckoutTotals.Compute 原返回 60,现返回 85。
执行此逻辑的 3 个测试中,有 2 个未对变化添加断言。

被编辑的辅助方法位于 Infrastructure.Collections;观察到的效果出现在 Commerce.Pricing

运行以下包含的演示,或查看维护的公共 .NET 演示拉取请求 (https://github.com/issacnitin/realdiff-sort-dotnet/pull/1) 及其成功托管运行记录 (https://github.com/issacnitin/realdiff-sort-dotnet/actions/runs/33064892219)。

五分钟 .NET 演示

此 .NET 演示的前置要求:Git、.NET 8 SDK 和 PowerShell 7。
Java 分析额外需要 JDK 和仓库的 Maven/Gradle 包装器或相应系统工具;Node 分析需要 Node.js 及其锁文件指定的包管理器。

git clone https://github.com/issacnitin/RealDiff.git
cd RealDiff
dotnet build RealDiff.sln -c Release
pwsh -File tools/verify-diffs.ps1 -Mutate -Change sort

该证明会创建一个临时的提议变更树,仅修改 SortingExtensions.cs,运行两次基线测试加上一次变更测试,并写入 findings.json。它验证:

  • 被编辑的文件贡献了零个被追踪的成员;
  • 边界位于未编辑项目 Commerce.Pricing.DiscountEngine.SelectDiscount 中;
  • 两个调用点发生了变化但未有断言响应;
  • 五个分歧键合并为三个边界节点;
  • 等优先级选择在全新的进程中是确定性的。

运行所有维护的演示模式:

pwsh -File tools/verify-demo-fixtures.ps1

这涵盖了排序稳定性、重试策略和配置解析。

公开的可执行文件是一个轻量级的 Rust 启动器,负责参数路由、仓库配置加载和 detect。它会启动一个并行的自包含托管组件,用于引用解析、构建、缓存、插桩和发布。架构设计采用一个语言无关的追踪契约、每个运行时一个追踪器,以及一个单遍流式 Rust 差异、边界和发现引擎:

flowchart LR
L[Rust argv, config, detect] --> O[Managed orchestration]
O --> D
O --> J
O --> N
O --> G
O --> R
O --> P
D[.NET / Cecil] --> T[realdiff.trace/1]
J[Java / javaagent + ASM] --> T
N[Node / CJS + ESM + Babel] --> T
G[Go / stable AST rewrite] --> T
R[Rust / stable syn rewrite cache] --> T
P[Python / PEP 669 sys.monitoring] --> T
T --> E[Rust matching, noise, frontier, and findings]
E --> F[findings.json]
F --> P[GitHub, Azure DevOps, MCP]

TRACE-FORMAT.md 是追踪器与引擎之间的契约。维护的 .NET、Java、Node、Go、Rust 和 Python 网关应用相同的符合性规则:相同的方法集、每个键的事件计数和进入序号、源代码触发点、摘要证明以及零引擎分歧(对于非空运行)。

状态:早期预览版。
统一的 CLI 可从常规根标记检测 .NET、Maven/Gradle Java、npm/pnpm/Yarn/Bun Node、Go 模块、Cargo Rust 和 Python 3.12+ 仓库。

支持的语言

语言插桩方式测试/源码集成当前限制
.NET 8Mono.Cecil 构建时 IL 织入xUnit 和可移植 PDB属性、事件和运算符是策略排除项。类型初始化器在结构上不可观察,因为钩子在 CLR 类型初始化锁下运行,可能导致启动死锁。
Javajava.lang.instrument 代理配合 ASMMaven/Gradle、JUnit/TestNG 注解、推断或配置的源根Gradle 源集推断覆盖字面量 srcDir/srcDirs 声明;动态源集配置需要 source_roots。集合形状规则需要 java.util 模块访问。类初始化器在结构上不可观察,因为钩子在 JVM 类初始化锁下运行,可能导致启动死锁。
Node / TypeScriptCommonJS require hook 和 ESM 加载器配合 Babelnpm、pnpm、Yarn Classic/Berry、Bun、直接 JavaScript 位置、TypeScript 源映射、Jest/Vitest 适配器需要恰好一个支持的锁文件;工作线程超出范围;生成器和不支持的调用对象将被跳过。
Go稳定的模块感知 AST 重写至构建缓存go test、原始 .go 解析器位置动态接口/函数边界和未重写的 goroutine 边界是显式跳过的。
Rust稳定的 syn/quote 重写至 SHA-256 构建缓存cargo test、结构化 #[test] 根、原始 .rs 解析器位置宏展开、外部/常量调用对象、联合体、trait 对象和依赖拥有的值在结构上不可达,因为稳定的源码重写无法进入展开的/编译器拥有的代码或将读取器注入到依赖源中。MIR 原型发出零运行时事件。
Python 3.12+PEP 669 sys.monitoring 在进程启动时附加;无构建、字节码织入或 AST 重写pytest 和 unittest 结构根;co_filename/co_firstlineno;源码 AST 清单原生/C 调用对象在结构上不可观察,因为它们没有用于 sys.monitoring 的 Python 帧;没有仓库源码的合成代码以及模块/类设置主体是显式不支持的边界。Python 3.11 及更早版本被拒绝;没有 sys.settrace 回退。

未解析的 TypeScript 源映射不被视为追踪器限制:RealDiff 拒绝猜测原始路径,将源记录为未解析,并降低归属置信度而不是声称可能错误的文件。
每个追踪器发出相同的进程范围 NDJSON 契约和协调的覆盖清单。报告为已插桩的成员必须具备发出事件的能力,每个模块必须满足 discovered = instrumented + skipped 且零补丁失败。

Python 与编译型追踪器不同,因为没有构建命令可以注入。RealDiff 将其暂存的 sitecustomize.py 添加到 PYTHONPATH 前部,在目标导入之前附加 sys.monitoring,并运行仓库测试(不作更改)。一个无副作用的 AST 传递仅为覆盖清单盘点源码成员;运行时事件完全来自 PEP 669。

基线追踪缓存保持有效:其键包括暂存 Python 追踪器目录指纹、Python major.minor.micro、有效包含/排除范围和编辑配置。
对于源解析,仓库下方的真实 co_filenameco_firstlinenodebugInfo;缺失/合成文件名是 debugInfoMissing;不可规范化或外部的真实路径是 unresolved。Python 不需要 generatedState,因为挂起的调用对象保留其原始代码对象,并且没有安全的 declaringType 回退。

Python 值支持是显式的:

置信度形状
精确None、缺失值、布尔值、任意整数、浮点数(包括规范 NaN 和不同的 -0.0)、字符串、字节,以及精确的内置 listtupledictsetfrozenset;当没有其他状态通道存在时,完整的实例 __dict__ 状态。
部分属性、__slots____getattr__、覆盖的 __getattribute__、容器子类、不可读字段、深度/广度限制和显示截断。每个不可读区域发出一个计数的 标记。
UnsupportedShape没有 Python 代码对象的原生/C 调用对象、没有仓库源码的动态/合成代码,以及可执行的模块/类设置主体。

规范化器从不调用属性、描述符、用户迭代器、repr、相等性、哈希、格式化回调、__getattr__ 或覆盖的 __getattribute__。编辑在完整规范值被哈希后应用,匹配其他语言契约。

跨语言发布演示

v0.4.0 版本通过六个公共排序稳定性拉取请求进行演练。每个夹具恰好有三个测试,修改一个配置文件,两个广泛的断言通过,而一个精确的平局获胜者断言发生反应。在每个打包运行中,被编辑的文件贡献零个被追踪的成员,边界位于未编辑的定价代码中,至少一个调用点未被测试,且没有工具或清单噪声存在。

语言匹配的键数边界合并拉取请求托管工作流
.NET3199 到 3 (3.0x)realdiff-sort-dotnet#1 (https://github.com/issacnitin/realdiff-sort-dotnet/pull/1)workflow (https://github.com/issacnitin/realdiff-sort-dotnet/actions/workflows/realdiff.yml)
Node129117 到 3 (39.0x)realdiff-sort-node#1 (https://github.com/issacnitin/realdiff-sort-node/pull/1)workflow (https://github.com/issacnitin/realdiff-sort-node/actions/workflows/realdiff.yml)
Java132117 到 3 (39.0x)realdiff-sort-java#1 (https://github.com/issacnitin/realdiff-sort-java/pull/1)workflow (https://github.com/issacnitin/realdiff-sort-java/actions/workflows/realdiff.yml)
Go3159 到 3 (3.0x)realdiff-sort-go#1 (https://github.com/issacnitin/realdiff-sort-go/pull/1)workflow (https://github.com/issacnitin/realdiff-sort-go/actions/workflows/realdiff.yml)
Rust3129 到 3 (3.0x)realdiff-sort-rust#1 (https://github.com/issacnitin/realdiff-sort-rust/pull/1)workflow (https://github.com/issacnitin/realdiff-sort-rust/actions/workflows/realdiff.yml)
Python3106 到 3 (2.0x)realdiff-sort-python#1 (https://github.com/issacnitin/realdiff-sort-python/pull/1)workflow (https://github.com/issacnitin/realdiff-sort-python/actions/workflows/realdiff.yml)

每种语言的两次全新打包运行产生了相同的行为规范化产物。每个托管工作流也恰好发布了一条 RealDiff 拉取请求评论。

安装 CLI

使用全语言容器

已发布的 Linux 镜像包含 RealDiff CLI、默认的 Rust 差异引擎、.NET 8 SDK/追踪器、Java 17 代理、Node 24 追踪器、Go 重写器、稳定的 Rust 工具链/追踪器以及带有 pytest 的 Python 3.12 sys.monitoring 追踪器。主机只需要 Docker:

docker pull ghcr.io/issacnitin/realdiff:v0.4.0
docker run --rm \
    --volume "$PWD:/workspace" \
    ghcr.io/issacnitin/realdiff:v0.4.0 \
    /workspace --base origin/main --pr HEAD \
    --findings /workspace/.realdiff/artifacts/findings.json

常规镜像入口点是 realdiff;不涉及 PowerShell 包装器。
统一的 CLI 协调 .NET、Java、Node/TypeScript、Go、Rust 和 Python 仓库。当前本地验证的 Linux/amd64 镜像大小为 926,593,250 字节(通过 Docker 镜像检查),包含稳定的 Rust 1.98、带 pytest 的 Python 3.12 和本地链接器。容器工作流报告每个已发布构建的确切大小。

安装 GitHub 版本

从 v0.4.0 版本 (https://github.com/issacnitin/RealDiff/releases/tag/v0.4.0) 下载 linux-x64linux-arm64darwin-arm64darwin-x64win-x64 的归档文件。
对照 SHA256SUMS 验证,解压,并将解压后的目录放在 PATH 上。
可执行文件是自包含的;主机不需要 .NET 运行时。

sha256sum --check SHA256SUMS --ignore-missing
tar -xzf realdiff-v0.4.0-linux-x64.tar.gz -C "$HOME/.local/lib/realdiff"
ln -s "$HOME/.local/lib/realdiff/realdiff" "$HOME/.local/bin/realdiff"
realdiff --help
Get-FileHash .\realdiff-v0.4.0-win-x64.zip -Algorithm SHA256
Expand-Archive .\realdiff-v0.4.0-win-x64.zip "$env:LOCALAPPDATA\RealDiff"
& "$env:LOCALAPPDATA\RealDiff\realdiff.exe" --help

完整的解压目录必须保持在一起,因为 Rust realdiff 启动器会在旁边启动 realdiff-managed,且该目录还包含原生引擎、语言追踪器和单独启动的 .NET Weaver。

NuGet 工具包仍然作为框架依赖的托管兼容性分发可用:

dotnet tool install --global RealDiff.Tool --version 0.4.0 --add-source .
realdiff --help

从源码构建和安装

git clone https://github.com/issacnitin/RealDiff.git
cd RealDiff
pwsh -File tools/package-cli.ps1
dotnet tool install --global RealDiff.Tool `
    --add-source ./artifacts/packages
realdiff --help

打包包装器在打包工具之前,构建并暂存带阴影的 Java 代理、带生产依赖的 Node 追踪器、当前 RID 的稳定 Rust 追踪器以及当前主机的 Rust 差异引擎。普通的 dotnet build 仍然独立于 Maven、npm 和 Cargo。

要更新现有的源码安装:

dotnet tool update --global RealDiff.Tool `
    --add-source ./artifacts/packages

你也可以直接运行构建的 DLL:

dotnet build src/RealDiff.Cli/RealDiff.Cli.csproj -c Release
dotnet src/RealDiff.Cli/bin/Release/net8.0/realdiff.dll --help

运行分析

RealDiff 需要仓库路径和两个 Git 引用。目标仓库必须在当前环境中可以构建。

realdiff C:\src\my-service `
    --base origin/main `
    --pr HEAD `
    --findings C:\temp\realdiff\findings.json

有用的选项:

--work          覆盖临时工作目录
--findings      写入规范的机器可读发现结果
--cache-dir     覆盖本地基线追踪缓存目录
--cache-retention 在指定时间窗口后过期缓存的追踪,例如 12h 或 7d
--keep-traces   选择保留工作追踪一段指定时间
--keep          保留临时的 Git 工作树;追踪仍默认删除
--ci=github     从 GitHub pull_request 事件解析引用
--ci=azuredevops 从 Azure Pipelines 变量解析引用

流式 Rust 引擎实现追踪加载、匹配、噪声过滤、差异构建、边界检测、归属、基线抑制和发现生成。REALDIFF_RUST_ENGINE 可以覆盖打包的原生可执行文件用于开发诊断。

PR 评论默认使用高置信度策略。一个发现只有在以下情况下才是高置信度:其边界已验证、每个比较的摘要都是精确的、一个祖先或后代差异通过调用树将其连接到变更、并且相同的成员没有表现出基线运行或清单的不确定性。一个贡献零个被追踪成员的编辑文件本身并不会取消该发现的资格。

每个发现都保留在 findings.json 中,包含 confidenceconfidenceFactorsnondeterminismcommentSuppressionReasons;评论显示有多少较低置信度的发现在产物中被保留。传递 --strict 以将所有未抑制的发现包含在评论中。GitHub Action 通过 strict: 'true' 暴露相同的行为,Azure Pipelines 通过 behaviorDiffStrict: 'true'

退出代码:

代码含义
0分析完成;没有意外的行为变化
1分析完成;检测到意外的行为变化
2分析因错误或无效输入而失败

相似文章

Show HN: Codiff,本地差异审查工具

Hacker News Top

Codiff 是一款轻量级本地 diff 查看器,用于审查 Git 暂存和未暂存的更改,支持基于 LLM 的逐步讲解和内联审查评论。

DiffsHub

Lobsters Hottest

DiffsHub 是一款工具,通过在 URL 中将 github.com 替换为 diffshub.com,即可提供快速、虚拟化的界面来查看任何公共 GitHub 差异(PR、提交等)。

差异只是摘要,会话跟踪才是实际发生的过程。

Reddit r/AI_Agents

一篇文章强调,AI智能体会话跟踪揭示了最终PR差异中缺失的隐藏操作(例如未声明的密钥、范围蔓延),并警告大多数开发者只审查差异,而不审查完整跟踪,导致未审查的风险。

Linear Diffs

Product Hunt

Linear Diffs 是一项新功能,可让您直接在 Linear 中审查拉取请求。