Agent 一个回合改动几十个文件,逐行审查已不现实。pi-diff-risk 在每轮回复后自动扫描变更, 为每个文件计算量化风险分,输出四色标注目录树 —— 把「哪些改动值得看」从主观判断, 变成确定性的、可配置的决策产物。
过去写200 行代码要半天,审它只需十分钟。现在 Agent 三十秒产出 800 行 —— 审查成为了新的瓶颈。 而人的注意力并没有变多,于是大多数人选择了最糟的策略:不看。
面对几十个文件的 diff,要么硬着头皮翻完(成本高、注意力衰减、越往后越敷衍), 要么直接合并赌它没问题。前者不可持续,后者迟早出事 —— 尤其当 Agent 悄悄动了鉴权逻辑或支付流程时。
资深工程师看 diff 时其实有直觉:这块没问题、那块得仔细看。 本项目把这份直觉拆成可计算的信号:碰了敏感领域吗?测试跟上了吗?改动量异常吗? 然后用颜色把注意力精确导向真正危险的地方。
核心设计是把变更范围严格锁定在「这一轮 Agent 改了什么」,而不是累积改动 —— 所以基线快照必须卡在回合起点。
监听 agent_started,递归扫描项目,记录每个文件的 size / mtime,
并缓存 200KB 以内的文本内容。
完全不干预、不拦截、不阻塞。Agent 新建、修改、删除文件,一切照常。
监听 agent_settled,二次扫描后与基线对比,得出新增 / 修改 / 删除清单。
对修改文件用 LCS 算法算出 +N/-M,再跑三信号加权模型输出 0–100 分。
结果作为自定义卡片渲染在对话底部,不进消息历史,因此不消耗任何上下文。
权重不是均分的 —— 敏感领域出事的代价最大,所以给它最高权重; 改动量大不等于危险,所以给它最低权重。
它碰了要命的地方吗?对文件路径和变更内容双向匹配 8 类高危模式,命中越多分越高。
改完之后有人验证吗?源码变了但同批改动里没有对应测试文件,直接吃满惩罚。测试文件自身与文档类文件免检。
改动规模超出可审范围了吗?按增删行数分五档。大改不必然危险,但确实更难看懂 —— 所以权重最低。
// 最终得分:三信号加权求和,再乘以文件类型系数 riskScore = ( sensitivity × 0.40 + testGap × 0.35 + diffSize × 0.25 ) × typeMultiplier × 100 // 同样改 200 行,.ts 和 .md 的风险量级根本不同 typeMultiplier: .ts .py .go .rs .java = 1.0 │ .json .yaml .toml = 0.7 │ .css .html = 0.5 │ .md .txt = 0.2
三个真实场景,切换看看不同风险等级下的渲染效果。
~/projects/my-site/ ├── src/ │ ├── layouts/ │ │ └── Layout.astro[LOW risk=22] │ ├── pages/ │ │ ├── agents/ │ │ │ └── index.astro [LOW risk=20] │ │ ├── skills/ │ │ │ └── index.astro [LOW risk=20] │ │ ├── 404.astro [LOW risk=22] │ │ └── index.astro [LOW risk=31] │ ├── styles/ │ │ └── global.css [LOW risk=20] │ └── ui/ │ ├── command-dialog.tsx [LOW risk=28] │ └── copy-button.tsx [LOW risk=25] ├── DESIGN.md [LOW risk=1] └── package.json
~/projects/backend/ ├── src/ │ ├── auth/ │ │ ├── token.ts [MED risk=58] │ │ └── middleware.ts [MED risk=45] │ ├── api/ │ │ └── routes.ts [LOW risk=18] │ ├── config/ │ │ └── env.ts [LOW risk=12] │ └── utils/ │ ├── hash.ts [MED risk=42] │ └── logger.ts [LOW risk=8] ├── tests/ │ └── utils.test.ts [LOW risk=10] └── .env.example
src/auth/token.ts [58/100] +45/-12 sensitivity (×0.40): sensitive: auth, secrets test-gap (×0.35): no test file changed src/auth/middleware.ts [45/100] +30/-5 sensitivity (×0.40): sensitive: auth test-gap (×0.35): no test file changed src/utils/hash.ts [42/100] +25/-8 sensitivity (×0.40): sensitive: crypto
~/projects/payment-svc/ ├── src/ │ ├── billing/ │ │ └── checkout.ts [HIGH risk=85] │ ├── config/ │ │ └── stripe.ts [MED risk=48] │ └── models/ │ └── invoice.ts [LOW risk=15] └── package.json
src/billing/checkout.ts [85/100] +580/-120 sensitivity (×0.40): sensitive: payments, secrets test-gap (×0.35): no test file changed diff-size (×0.25): very large: +580/-120 src/config/stripe.ts [48/100] +35/-5 sensitivity (×0.40): sensitive: payments, secrets
下面这个计算器跑的就是项目里真实的评分逻辑。调一调参数,看分数和等级怎么变。
用户关心的是「这一轮改了什么」,不是仓库里累积的所有改动。
所以基线快照必须在 agent_started 拍 —— 只在结束时扫一次,就没有可比对的基准。
每轮结束后基线自动前移,天然实现增量视图。
LCS 是 O(m×n),几千行的文件跑满 DP 表会明显卡顿。
超过 2000 行时自动降级为集合匹配(Set 求交),
牺牲一点精度换取稳定的响应速度 —— 反正风险分只需要量级正确。
风险卡片走 registerEntryRenderer 渲染为自定义 entry,
不进入消息历史、不参与下一轮请求构造。
意味着无论展示多少次,都不会挤占对话窗口或增加 token 账单。
写死一套规则等于假设所有团队的风险容忍度相同。
三个信号权重、两个判级阈值均可通过 ~/.pi/agent/pi-diff-risk.json 调整 ——
严格的团队可以把 review 阈值下调到 25,让更多改动进入视野。