关于 AI 写代码的讨论很多,我不太吃两种调调:一种把 AI 当演示道具,另一种把能力吹到天上、顺便贩卖焦虑。

我更在意一个朴素问题:在真实工程里,它能不能稳定帮上忙。能写代码只是起点;场景要真实,结果要可验证,做错了要发现得了、回得去。离开这些,再漂亮的生成结果也只是 demo。

过去一段时间,我在 Knife4j Next 上试的就是这类场景:让 AI 参与维护一个已经停更、但仍有人在用的开源项目,而不是从零造一个新产品。

为什么先盯上“停更但仍有人用”的项目

Knife4j 是很多 Java 团队用过的 API 文档工具,入口常常是熟悉的 /doc.html。官网在 doc.xiaominfo.com/knife4j。维护节奏慢下来之后,用户需求并不会一起消失:页面要能开,调试不能莫名其妙挂掉,发布说明和实际产物还要对得上。

Knife4j Next 是它的社区维护 fork。我对这类项目的定位一直很克制:稳住现有体验,修回归和兼容,发布可重复,前端增量演进。目标是把项目继续养着,不是重写一个「更现代」的产品。

它适合拿来练 AI 协作,主要是因为足够务实:

  • 有真实用户和明确 issue
  • 日常工作大多是 bugfix、兼容补丁、文档和发布流程
  • 结果能用测试、构建、截图、日志或 CI 核对
  • 改错了通常回得去,损失可控

反过来,方向天天变的新产品、核心金融/权限链路、几乎没有验证手段、或者 issue 全是「感觉不好用」「能不能做成某某那样」的项目,我会谨慎得多。AI 当然可以帮忙调研、对比方案、写局部实现,但不适合在这些地方「自治推进」。

先选项目,再谈 AI。低风险、需求清楚、容易验证,比模型本身更决定成败。

不要一上来就让 AI “接管仓库”

刚开始用 Coding Agent 时,很容易冲动:issue 丢过去,让它自己读代码、自己改、自己交。

偶尔能成,但不稳。

真实项目里有大量隐性规则:哪些模块能改,哪些只做兼容维护;什么叫做完,什么只是本地看着过了;什么时候开 PR、什么时候等 CI;哪些事可以自己推,哪些必须先问人。这些如果只活在维护者脑子里,AI 只能猜。

我后来做的第一件事是把规则写进仓库,而不是继续抠 prompt。Knife4j Next 里大致有这些文件:

文件 干什么
AGENTS.md 总规则:读什么、硬约束、默认工作方式
.agent/PROJECT.md 目标、非目标、模块边界(比如 OAS3 主线 / OAS2 只兼容)
.agent/AUTONOMY_POLICY.md 什么能直接做,什么必须问人
.agent/PLAYBOOK.md 维护者在场时的完整工作循环
.agent/RUNBOOK.md 不同改动跑什么验证;bug 必须先复现
.agent/COORDINATION.md 什么时候才值得拆 worker / reviewer
.agent/KNOWN_PITFALLS.md 踩过的坑和反例

我不太喜欢把它们叫「给 AI 的操作系统」——听起来太玄。它们更像是把本来该写进 onboarding 的东西写清楚了。人来了要看,AI 来了也要看。规则在仓库里,就不依赖某段聊天记录,也不靠我每次口头提醒「别扩大范围」「先跑这个脚本」「结论写回 issue」。

AI 协作最容易出事的,往往是边界不清楚:写出一堆看起来合理、事后很难收拾的改动。代码写不出来,反而还好处理。

状态放在 Issue 里,不要放在聊天里

另一个很快暴露的问题:聊天窗口当不了任务系统。

今天这个会话还记得「继续处理这个 issue」;明天换会话、换工具、换模型,上下文就没了。分支开了没有、PR 卡在 CI 还是卡在 review、某个问题是暂时不做还是确认不存在——这些如果只散落在对话里,迟早乱。

所以任务状态我放到了 GitHub Issues + Labels:

  • agent-task:适合交给 agent 处理
  • status:ready:已细化,可以开干
  • status:in-progress:进行中
  • status:review:本地验证、审查、PR CI 都过了,等人 merge
  • status:blocked:卡在信息、环境或决策上
  • area:*:模块分区

进度写在 issue / PR,不另建一套影子看板。

默认流转很短:readyin-progress → 分支上做最小改动 → 跑对应验证 → 开 PR 等 CI → 审查通过后进 review → merge 后关 issue。

这里有个容易被吹过头的点:这套东西不是为了「无人值守 24 小时自动修仓库」准备的。我现在的默认假设是维护者在场。Issue 状态的价值,首先是让人和 AI 在同一套事实上对齐,而不是让模型在暗处自己排队干活。

默认路径:一个人 + 一个 agent 端到端

多 Agent 很能吸引眼球,但我越来越不把它当默认解。

早期我也研究过 coordinator / worker / reviewer。名字听起来像组织架构,实际用途其实很窄:

  • worker:实现或探索会把主会话上下文撑爆时,拆出去做窄范围工作
  • reviewer:高风险 diff,又希望另一份独立上下文先扫一遍时再用
  • 维护者人工 review:完全可以当合法的第二意见,不必为了「角色齐全」硬开 reviewer agent

仓库里现在写得很直白:维护者在场时,单 agent 端到端就够用。Bug 类任务就是:复现并贴证据 → 实现 → ./tools/test-* → 开 PR → 等 CI。只有上下文真的要爆,或者需要独立第二意见时,才按 .agent/COORDINATION.md 拆。

我也见过一种很热闹的反面:把 agent 包装成「虚拟公司」,产品经理、架构师、开发、测试轮流传文档。演示好看,工程上常常不稳——角色是演出来的,信息却在交接里被压缩,最后每个节点都「合理」,整体已经偏了。这块我另有一篇整理:《三省六部幻觉》

所以 Knife4j Next 里的多 Agent,不是编制,是可选工具。平时别为了像个团队而拆团队。

什么样的任务适合丢给 AI

项目选对了,也不是所有事都该自治推进。

我现在判断任务适不适合,不太问「难不难」,更问「能不能客观验收」。适合的通常长这样:

  • 明确 bug:能复现,能补回归
  • 前端显示问题:构建或截图能对上
  • 文档和代码不一致:改完能核对
  • CI / 发布脚本:失败条件变成明确报错
  • 小范围清理、补测、把手动检查脚本化

不适合直接丢出去的也很清楚:要不要转型、功能该怎么设计才「最好」、顺便现代化架构、把历史模块重写掉、笼统地「优化体验」。AI 可以先调研和对比,但这类开放题必须先有人把边界收窄,再让它改仓库。

后来我给自己定了个门槛:交给 AI 推进的任务,至少要有目标模块、预期行为变化、验证命令和完成条件。 缺一项,先补 issue,别急着写代码。

Bug 先复现,别先“防御性”补丁

维护停更项目时,issue 经常来自上游或老用户反馈。标题一句报错、截图一段堆栈,看起来方向很明显;真实原因可能藏在评论、版本组合、配置或调用链里。

仓库里现在有一条很硬的规则:bug / 回归先复现,再写修复。 复现不到,就不要写「看起来更稳」的 iftry-catch 或 fallback——没有测试约束时,这类补丁经常只是把真错误吞掉。证据要写进 issue;复现失败就 blocked 或说明原因后关闭,而不是假装修了。

正确顺序很无聊,但管用:

  1. 读完正文、堆栈和评论
  2. 在未打补丁的基线上复现,留下可核对证据
  3. 能复现再修;修后同一证据变绿或现象消失
  4. 复现不到,把尝试过的条件写回去

AI 默认倾向是「给一个方案」。维护项目时,先证明你和 issue 说的是同一件事更重要。KNOWN_PITFALLS 里也记过反例:有的 upstream 问题在本 fork 已经不存在,未复现就补丁,只会制造噪音。

发布也别靠人脑记账

小型开源项目里,发布经常比写代码还磨人。

tag 推了算不算完?CI 绿了算不算?Maven 包能下了算不算?GitHub Release 和文档站 release note 对得上吗?这些如果只靠记忆,迟早漏。

所以发布也被写成可核对条件:tag、workflow、公开构件、GitHub Release、release note 与文档站对应小节。缺 GitHub Release,就不能报「发布完成」。

思路比某个具体脚本更重要:完成条件写进仓库,别只活在某个人脑子里。 这样 AI 才能帮你检查和汇报,而不是甩一句「应该发好了」。

一个真实小例子:全局参数 loading 转圈

原则说完了,看一个具体 issue。

issue #379 反馈:在 doc.html 右上角设置里添加全局参数时,按钮 loading 不消失,控制台报 TypeError: crypto.randomUUID is not a function。标签是 agent-taskarea:ui-react,边界很清楚——React UI 的兼容 bug,不是产品重设计。

实际处理并没有演成「coordinator 派工、worker 实现、reviewer 背书」的三人戏。维护者确认复现后,交互式 agent 直接做了窄范围修复:

  1. 定位到添加参数时直接调用了 crypto.randomUUID()
  2. 抽了 createClientId():优先 randomUUID,没有就用 getRandomValues,再不行才本地临时 ID
  3. 补了三种环境下的单测
  4. 跑项目认可的前端验证,开 PR #382 合并

这个修复本身不炫。它说明的是一条更无聊、也更可靠的链路:真实用户问题 → 模块边界清楚 → 先复现 → 小改动 → 补测试 → 用仓库规定的命令验收 → 结论回到 issue / PR。

质量往往卡在有没有把这条链路走完,而不是一次生成了多少行代码。

落到仓库里的,其实是一套很土的东西

折腾下来,我觉得 AI 维护开源项目真正缺的,是一堆很土的工程安排,而不是什么神奇 prompt:

  • 项目边界写清楚
  • 任务状态只有 Issue / PR 这一处真相
  • 一个分支只做一件可独立验证的事
  • 不同类型改动有对应验证入口(./tools/test-java.shtest-front-core.shtest-vue3.sh 等)
  • bug 先复现
  • 高风险才要第二意见;维护者人工审也算
  • 发布完成条件可脚本核对
  • 重要结论写回 issue、PR 或仓库文档,不留在聊天里

没有这些时,AI 越积极,越容易把伤害扩大;有了这些,它才像一个可控的协作者,而不是会写代码的不确定因素。

结语

很多人讨论 AI 写代码,爱问它能不能从零做出完整产品。

我反而觉得,开源维护里有个更现实的切口:停更了、需求还在、风险不高、验证路径清楚的小型项目。它们往往不缺愿景,缺的是持续处理 issue、补测试、修文档、打 release、收尾 PR 的耐心。AI 在这里能帮上忙的,是把这些具体、可验证、可回滚的活往前推,而不是替你宣布产品方向。

如果只留一句实践建议,我会说:

别急着让 AI 接管项目。先把任务、边界、验证和发布条件写进仓库;维护时人在场,默认一个 agent 做完一件小事。

流程站得住,AI 才有机会长期参与维护,而不是偶尔蹦出一段代码的玩具。