返回 AICAP-180
B18 · Day 177OSS 收口 + 英文 + 全局 SOTA 复核

真实 OSS PR 实现

B18 的主线是「把私有资产变成外部可验证的工件」,今天是这条主线上的关键一跳:

阶段: B18 · OSS 收口 + 英文 + 全局 SOTA 复核(Day 171-180) 标签: #oss-contribution #fix-plus-test #contributing-conventions #regression-test

今日导引(由浅入深)

B18 的主线是「把私有资产变成外部可验证的工件」,今天是这条主线上的关键一跳:

  • Day 176 把 agent-evals 切成了独立公开 repo(自己的代码、自己的 CI);
  • 今天更进一步——向别人的活跃 OSS 项目提交一个真实修复,这是「外部可验证」的最高形式:不是自己给自己 review,而是上游 maintainer 的红绿门和人工审查;
  • Day 174 已经在 smolagents / DeepEval / Inspect AI 三个 repo 各筛了一个 good-first-issue 并本地复现了其中一个;
  • 今天就针对那个选定 issue 写出 fix + 配 1 个单测,本地全套绿,push 到 fork branch;
  • 明天(Day 178)才是把它正式提成 PR 并用英文说服 reviewer。

今日最小可判定产出:fork branch 上一个带单测的 commit,本地全套测试绿,通过测试数记录在案。

1. 机理精读

「fix 配单测」是 OSS 接受率的硬门槛,不是礼貌。

  • 一个没有测试的纯逻辑改动 PR,几乎一定会被 reviewer 退回——maintainer 无法判断你的 fix 是否真修了 bug、是否引入回归。
  • 配套单测必须覆盖 bug 的触发路径:即一个在打 patch 前会失败、打 patch 后会通过的测试。
  • 这条测试本身就是「这个 bug 确实存在过」与「这个 fix 确实有效」的可执行证据。
  • 依据:目标 repo 的 CONTRIBUTING(2026)。

小而聚焦的 PR 接受率最高。

  • 一个 PR 只改一件事:修一个 bug、改一处错误信息、补一段文档。
  • 把多个无关改动塞进一个 PR 会大幅增加 review 成本与被要求拆分的概率。
  • Day 174 之所以优先选「文档修复 / 错误信息改进 / 边界 bug」类,正因它们改动面小、语义清晰、回归风险低——是 good-first-issue 的典型形态。

三件套:fix + test + changelog。

  • (1) fix 本身;(2) 覆盖触发路径的单测;(3) changelog 条目(很多 repo 要求每个面向用户的改动都登记一条)。
  • 还要符合该 repo 的 commit 规范(Conventional Commits、DCO Signed-off-by、或 CLA)。
  • 这些都在 CONTRIBUTING.md 里写明,Day 174 已读过——动手前先把硬约束列成清单。

为什么参照本仓已验证模式?

  • seed 让 fix 逻辑参照本仓 src/agent/runtime/resilience.ts 的测试写法。
  • 原因:resilience 这类「重试 / 退避 / 熔断」逻辑是 OSS 里 bug 高发区(边界多:attempt=0、max 截断、jitter 边界、连续失败计数、冷却恢复)。
  • 本仓已用「时钟 / sleep / rng 全部可注入」的方式把它做成无真实定时器、无 key 的确定性单测
  • 这套写法直接可迁移到上游 PR 的测试里,让测试快、稳、可重放。

与相邻概念的边界。

  • 今天是「提 PR」(那是 Day 178 的事,状态 open + 英文正文)。
  • 今天的产出止于「本地 push 到 fork branch」——一个可独立判定的中间里程碑:代码写完、单测写完、本地全套绿。
  • 是否被上游接受是后续不可控的外部事件,不在今天的判定范围内。

2. 代码走读:可类比的已测产物

seed 点名两个本仓「已测、可作为 fix 写法范式」的纯函数模块。逐一走读真实符号(均已 Read):

  • src/agent/runtime/resilience.ts(retry/backoff/熔断已测):
    • backoffDelay(attempt, opts) — 第 0-based attempt 的延迟:min(base·factor^attempt, max) 再叠 ±jitter 分数,Math.max(0, Math.round(...)) 兜底非负。典型边界 bug 区:attempt=0、命中 max 截断、jitter 把值推到负数(已被 Math.max(0, ...) 防住)。
    • retry(fn, opts) — 最多 retries(默认 3)次重试,shouldRetry(err) 决定是否继续,sleep 可注入(测试里换成立即 resolve,无真实定时器)。触发路径测试范式:注入一个前 N 次抛错、第 N+1 次成功的 fn,断言总调用次数与最终返回。
    • CircuitBreaker 类 — getState()open 且超 cooldownMs 后转 half-openexec(fn) 成功归零失败计数并关断、失败累加并在达 threshold(默认 5)时开断。now() 可注入 → 用假时钟测试冷却恢复,不靠真实时间。
    • withFallback(primary, fallback) — primary 抛错则跑 fallback,返回 {value, usedFallback}
    • 迁移要点:上游若有类似重试/退避 bug,照搬「把 sleep/now/rng 注入成确定性桩」的写法,测试就能稳定覆盖触发路径。
  • src/agent/train/grpo.ts(纯函数已测):
    • groupRelativeAdvantage(rewards, eps=1e-8) — 组内标准化 (r-mean)/(std+eps),无 critic。
    • exactMatchReward(output, target) / formatReward(output, pattern) / composeRewards(parts)(加权平均,totalW===0 返回 0)。
    • grpoScore(group) — 给每个样本挂 advantagereinforce = advantage>0
    • 迁移要点:这类纯数学函数是「输入→输出确定」的理想被测对象,fix + 单测一对一,最易被 reviewer 接受。

走读结论:上游 fix 若落在「重试/边界/纯计算」类,本仓 resilience.ts / grpo.ts 的「依赖全注入 + 触发路径单测」写法可直接迁移。

3. 今日实战

  1. 在 Day 174 选定的 issue 所属 repo(smolagents / DeepEval / Inspect AI 之一)的 fork 上开新 branch。
  2. 重读该 repo CONTRIBUTING.md,确认:测试目录约定、commit 规范(DCO/CLA/Conventional Commits)、changelog 是否必填。
  3. 实现 fix:聚焦单一改动,覆盖 Day 174 本地复现里「预期 vs 实际」的偏差点。
  4. 加 1 个单测,覆盖 bug 触发路径(patch 前红、patch 后绿)——写法参照 resilience.ts 的依赖注入式确定性测试。
  5. 若 repo 要求,补 changelog 条目。
  6. 本地跑该 repo 全套测试,确认绿;git push 到 fork branch。记录通过测试数。

4. 今日实测 / 产出

  • fork branch + 通过测试数 committed外部动作(seed 状态「外部动作 → 将产出 fork branch + 通过测试数 committed」)。未完成、不升级为已完成。注意 Day 174 已明确「merged OSS PR 不算已完成」,今天止步于 fork branch,更不算合入。
  • 本仓可类比的已测产物(可立即引用的硬资产):resilience.ts(retry/backoff/熔断已测)、grpo.tsgroupRelativeAdvantage/exactMatch/composeRewards/grpoScore 纯函数已测)。
  • Block 里程碑参照:上游 OSS PR = 外部动作(未完成,状态待 open/merged,正文须含 before/after eval 数字)。

5. 常见误区 / 陷阱

  • 无测试的纯逻辑 PR:最常见的退回理由。哪怕只改一行逻辑,也要附一个覆盖触发路径的测试。
  • PR 范围发散:顺手「再优化一下」其他代码 → review 成本飙升、被要求拆分。一个 PR 只做一件事。
  • 忽略 commit / 签署规范:DCO 缺 Signed-off-by、commit 不符 Conventional Commits、漏 CLA 签署,都会卡在 CI 或机器人检查上。动手前把 CONTRIBUTING.md 的硬约束列清单。
  • 测试依赖真实定时器/网络/key:会让上游 CI 变慢变 flaky。照搬本仓「sleep / now / rng / fetch 全注入」的确定性写法。
    • 具体到 resilience:测 retry 时把 sleep 注入成立即 resolve,测 CircuitBreaker 冷却时把 now 注入成假时钟,绝不靠真实时间。
  • 把 push 到 fork 当成「已贡献」:今天只是中间里程碑,merge 与否是 Day 178 之后不可控的外部事件。
  • 改动越界改了无关代码:顺手「格式化整个文件」会让 diff 噪声淹没真正的 fix,reviewer 难以聚焦——保持 diff 最小。

6. 学习资源(每条带 YYYY-MM)

  • 目标 repo(smolagents / DeepEval / Inspect AI)各自的 CONTRIBUTING.md,2026 当周版本需复验(测试要求/commit 规范可能更新)。
  • GitHub「Creating a pull request from a fork」官方文档,2026。
  • Conventional Commits 规范 v1.0.0(多数活跃 repo 采用),规范本身长青。
  • DCO(Developer Certificate of Origin)说明,linuxfoundation,长青。
  • 本仓 src/agent/runtime/resilience.tsbackoffDelay / retry / CircuitBreaker / withFallback,依赖注入式确定性测试范式,seed 依据)。
  • 本仓 src/agent/train/grpo.tsgroupRelativeAdvantage / exactMatchReward / composeRewards / grpoScore 纯函数,fix+test 一对一范式,seed 依据)。

SOTA检查 (2026-06 更新)

  • fix + test + changelog 三件套 为长青 OSS 规范,仍 SOTA。
  • 当前主流实践:小而聚焦 PR + 触发路径回归测试 + Conventional Commits / DCO 签署,是 2026 活跃 Python/TS repo 的通行门槛。
  • 过时黑名单
    • 避免无测试的纯逻辑改动 PR(会被 reviewer 退回);
    • 避免针对已 stale/锁定的旧 issue 提 fix(Day 174 已警示 good-first-issue 会被他人抢先)。
  • 当周需确认
    • 所选 repo 未冻结分支(freeze 期间不接 PR);
    • 目标 issue 仍 open 未被他人认领;
    • CONTRIBUTING.md 的测试/签署要求未变。
  • 下次复查点:Day 178 提 PR 前,再刷一次目标 repo 的 open PR 列表,确认没有人已提交同一 fix。

衔接

  • 昨天:Day 176 — 抽独立公开 repo 工程化(agent-evals 切成独立 repo,确定性 CI,≥18 passing 目标)
  • 今天:针对 D174 选定 issue 写 fix + 1 单测,本地全套绿,push fork branch
  • 明天:Day 178 — 提 PR + 英文沟通(向上游提 1 个 open PR,正文贴 before/after 可核验数字)