本 spec 由 #294 的讨论与设计共识综合而成,作为 agent 执行契约发布。来源 issue:#294(问题、价值与验证数据见该 issue)。
Problem Statement
在含 ignored 文件的仓库里——也就是所有有 node_modules、构建产物或 .env 的真实仓库——replay 机制本来就会因 ignored 检查失败而整体禁用,但每次 replay-safe 的 workflow agent() 调用(最热路径:主 agent 正在改代码、脏 diff 很大时 fan-out 的只读 explorer 子代理)仍然会为注定被丢弃的结果先跑一次全量 dirty diff(--binary,缓冲上限 32 MiB)及其余指纹命令。实测这类仓库 52% 的指纹耗时是纯浪费(32 次调用共 8568 ms,其中 4454 ms 白算),且全部同步阻塞在宿主事件循环上,最差单次约 274 ms。
Solution
把便宜的终止性检查挪到昂贵的 diff 之前:ignored 文件检查尽早失败,tracked symlink/gitlink 检查随后,之后才依次执行 HEAD 解析、全量 diff、untracked 收集与哈希。含 ignored 文件的仓库从每次 5–6 个 git 子进程降到约 2 个,完全不跑 diff。所有失败路径本就汇入同一个 catch → undefined,成功与失败路径的可观察行为均不变。
User Stories
- As a 在脏工作区 fan-out 只读子代理的 workflow 用户,I want replay 指纹对注定禁用 replay 的仓库尽早失败,so that 子代理启动延迟不随我的脏 diff 大小增长。
- As a 主 agent 正在改代码的用户,I want 每次 replay-safe 子代理调用不为注定丢弃的 diff 付费,so that fan-out 总耗时近乎减半(实测 8568 ms → 4114 ms)。
- As a TUI 操作员,I want 指纹失败时行为与从前完全一致(静默禁用 replay、调用照常执行),so that 这次优化不改变任何可观察行为。
- As a 干净仓库用户,I want replay 真正生效时指纹值与重排前逐位一致,so that 已 journal 的 replay 条目继续有效。
- As a 依赖事件循环响应性的宿主,I want 含 ignored 文件仓库的单次指纹耗时显著下降,so that 最差单次同步阻塞明显缩短。
- As a 维护者,I want 有回归测试锁死「存在 disqualifier(ignored 文件、tracked symlink/gitlink)的仓库绝不执行 diff」这一属性,so that 将来的重构不会悄悄把昂贵命令挪回前面。
- As a 维护者,I want git 执行器可注入且生产路径默认行为不变,so that 命令顺序可测而生产代码语义零改动。
- As a 维护者,I want 新增测试沿用现有指纹测试的真实临时 git 仓库 fixture 风格,so that 测试套件保持同质、易维护。
- As a reviewer,I want PR 附改动前后的实测基准数据与复现口径,so that 性能主张可独立验证。
- As a 后续要做异步化的贡献者,I want 本改动不触碰
createReplayIdentity() 的导出签名与调用点,so that 异步化仍可作为独立变更评审。
- As a Windows 用户,I want 优化在 Windows 10 / Node 22 / git 2.45(基准实测环境)上成立,so that 最热路径在该环境受益。
- As a CI 守门人,I want 全量 check 与全量测试套件在改动后保持绿色,so that 线性历史的合并前提不被破坏。
- As a replay journal 的下游消费者,I want 指纹的构造语义不变(相同输入收集到相同字段集合、相同哈希),so that 跨版本兼容性不受顺序重排影响。
Implementation Decisions
- 目标模块:workflows 扩展中负责 replay 安全的模块内的指纹函数
repositoryFingerprint()(未导出的私有函数,本改动将其导出)。
- 最终命令顺序(决定 A):仓库根解析(
rev-parse --show-toplevel + realpath 规范化)→ ignored 文件检查(ls-files --others --ignored --exclude-standard --directory,非空即 throw)→ HEAD 校验 → tracked 模式检查(ls-files -s,发现 symlink/gitlink 即 throw)→ 全量 dirty diff(--binary,32 MiB 缓冲上限)→ untracked 收集(lstat 校验:symlink/非文件/总量超限即 throw;随后哈希)。
- 顺序不变量(写进 PR Approach):所有 git 调用共享同一个 5 秒 deadline,调用集合不变则总和约束不变,git 调用之间的重排不改变任何输入下的成败;非 git 的无界工作(realpath、lstat、readFile 哈希)重排前后均全部位于 git 调用之后,不侵占 git 预算。因此对任何输入,重排前后指纹值与成败完全一致。选项 (b)(把 untracked 哈希也挪到 diff 前)被否决:它会把无界哈希工作挪进共享预算,使「重 untracked + 慢 diff」的仓库从可 replay 变为超时不可 replay,违反零行为变化承诺。
- 测试 seam(决定 B):
repositoryFingerprint(cwd, runGit?) 导出,runGit 缺省为现有绑定 deadline 的 git 执行器;createReplayIdentity() 的导出签名与行为不变。这是本 spec 唯一新增的 seam。
- 注释随代码块移动,保留各失败路径上原有注释的语义(symlink/gitlink、ignored 文件 fail-closed 的理由注释不得丢失)。
- 提交与交付:基于最新 origin/main 建 topic 分支;单个英文 conventional commit(
perf(workflows): 前缀);ready-for-review PR,英文,遵循仓库 PR 模板五段(Problem/Value/Approach/Validation/Impact),Fixes #294,Validation 附改动前后实测数据表。
- 基准数据:由本地基准脚本实测产出(脚本不入库,见 Out of Scope),PR 中给出环境口径(Windows 10 / Node v22.23.2 / git 2.45.1)。
Testing Decisions
- 好测试只测外部行为:现有全部指纹/identity 测试的断言一行不改、原样通过——这层证明「任何输入结果不变」。
- 两层测试结构:
- 行为层(现有 seam,最高):经
createReplayIdentity() 观察返回 identity 或 undefined 的外部行为。重排不得要求任何现有断言改动。
- 顺序层(新 seam,本 spec 唯一新 seam):注入记录型
runGit(包装真实 git 执行并记录参数),断言:(a) 含 ignored 文件的仓库记录中不出现 diff 命令;(b) 含 tracked symlink 的仓库记录中不出现 diff 命令;(c) 干净仓库按新顺序完整出现全部命令。命令顺序在行为层不可见(任何顺序产生相同结果),因此「性能属性」这一外部承诺只能落在此 seam 观测——这是引入新 seam 的唯一理由。
- Prior art:现有 replay-safety 测试以真实临时 git 仓库为 fixture(mkdtemp + init + commit + 场景文件布置),新测试沿用同一风格;测试运行器为 node:test(由仓库测试脚本统一调度,
--experimental-strip-types)。
- 验证命令(仓库契约):全量
bun run check + bun run test;单文件冒烟 bun test <replay-safety 测试文件> 可作为快速反馈,但不替代全量。
Out of Scope
createReplayIdentity() 异步化(execFileSync 阻塞事件循环的根治):涉及导出签名、两个调用点与 workspace lease 交错语义,应独立评审;不发 follow-up issue。
- 基准脚本入库:
benchmarks/ 的定位是回归测试脚本,性能微基准保留在本地(本地已有复现脚本),只有实测数据进 PR。
- fail-closed 语义的任何改动:ignored 文件可被只读子代理观察时仍然整体禁用 replay;本改动只解决「为注定被丢弃的工作付费」。
- worktree-handoff 的
--binary diff(有意为之,捕获 worktree patch,每次 handoff 一次)。
- deadline(5 秒)语义与
REPLAY_IDENTITY_TIMEOUT_MS 的任何改动。
Further Notes
- 顺序不变量的可证明性论证(共享 deadline 预算)已与作者达成共识,须完整写进 PR 的 Approach 段。
- 执行环境注意(本机):bun 未安装,需先安装
bun@1.3.14(钉住 packageManager 版本)并 bun install;测试套件实际运行在 Node test runner 上;git 远端操作需追加 -c http.sslBackend=schannel(本机存在 TLS 拦截代理,OpenSSL CA 链验证失败);GitHub CLI 已安装并登录(完整路径 C:\Program Files\GitHub CLI\gh.exe,不在 Git Bash PATH 中)。
- 基线:origin/main
865f66e(2026-08-30)。已核实目标模块及其测试文件在该基线上与来源 issue 引用的 efd804d 无差异;上游另 5 个新提交不触碰目标模块。
- 语言口径:commit 与 PR 英文(仓库惯例);来源 issue 与本 spec 为中文(作者工作语言),两者均为仓库既有先例。
Problem Statement
在含 ignored 文件的仓库里——也就是所有有
node_modules、构建产物或.env的真实仓库——replay 机制本来就会因 ignored 检查失败而整体禁用,但每次 replay-safe 的 workflowagent()调用(最热路径:主 agent 正在改代码、脏 diff 很大时 fan-out 的只读 explorer 子代理)仍然会为注定被丢弃的结果先跑一次全量 dirty diff(--binary,缓冲上限 32 MiB)及其余指纹命令。实测这类仓库 52% 的指纹耗时是纯浪费(32 次调用共 8568 ms,其中 4454 ms 白算),且全部同步阻塞在宿主事件循环上,最差单次约 274 ms。Solution
把便宜的终止性检查挪到昂贵的 diff 之前:ignored 文件检查尽早失败,tracked symlink/gitlink 检查随后,之后才依次执行 HEAD 解析、全量 diff、untracked 收集与哈希。含 ignored 文件的仓库从每次 5–6 个 git 子进程降到约 2 个,完全不跑 diff。所有失败路径本就汇入同一个
catch → undefined,成功与失败路径的可观察行为均不变。User Stories
createReplayIdentity()的导出签名与调用点,so that 异步化仍可作为独立变更评审。Implementation Decisions
repositoryFingerprint()(未导出的私有函数,本改动将其导出)。rev-parse --show-toplevel+ realpath 规范化)→ ignored 文件检查(ls-files --others --ignored --exclude-standard --directory,非空即 throw)→ HEAD 校验 → tracked 模式检查(ls-files -s,发现 symlink/gitlink 即 throw)→ 全量 dirty diff(--binary,32 MiB 缓冲上限)→ untracked 收集(lstat 校验:symlink/非文件/总量超限即 throw;随后哈希)。repositoryFingerprint(cwd, runGit?)导出,runGit缺省为现有绑定 deadline 的 git 执行器;createReplayIdentity()的导出签名与行为不变。这是本 spec 唯一新增的 seam。perf(workflows):前缀);ready-for-review PR,英文,遵循仓库 PR 模板五段(Problem/Value/Approach/Validation/Impact),Fixes #294,Validation 附改动前后实测数据表。Testing Decisions
createReplayIdentity()观察返回 identity 或 undefined 的外部行为。重排不得要求任何现有断言改动。runGit(包装真实 git 执行并记录参数),断言:(a) 含 ignored 文件的仓库记录中不出现 diff 命令;(b) 含 tracked symlink 的仓库记录中不出现 diff 命令;(c) 干净仓库按新顺序完整出现全部命令。命令顺序在行为层不可见(任何顺序产生相同结果),因此「性能属性」这一外部承诺只能落在此 seam 观测——这是引入新 seam 的唯一理由。--experimental-strip-types)。bun run check+bun run test;单文件冒烟bun test <replay-safety 测试文件>可作为快速反馈,但不替代全量。Out of Scope
createReplayIdentity()异步化(execFileSync阻塞事件循环的根治):涉及导出签名、两个调用点与 workspace lease 交错语义,应独立评审;不发 follow-up issue。benchmarks/的定位是回归测试脚本,性能微基准保留在本地(本地已有复现脚本),只有实测数据进 PR。--binarydiff(有意为之,捕获 worktree patch,每次 handoff 一次)。REPLAY_IDENTITY_TIMEOUT_MS的任何改动。Further Notes
bun@1.3.14(钉住 packageManager 版本)并bun install;测试套件实际运行在 Node test runner 上;git 远端操作需追加-c http.sslBackend=schannel(本机存在 TLS 拦截代理,OpenSSL CA 链验证失败);GitHub CLI 已安装并登录(完整路径C:\Program Files\GitHub CLI\gh.exe,不在 Git Bash PATH 中)。865f66e(2026-08-30)。已核实目标模块及其测试文件在该基线上与来源 issue 引用的efd804d无差异;上游另 5 个新提交不触碰目标模块。