用了一阵 coding agent 之后,我慢慢把它从"聊天窗口"挪到了真实工作流里:让它读我的代码、改我的文件、提 PR。这一步省事很多,但也要求我先把"它能动到哪里"这件事想清楚。否则第一次翻车会来得很突然。
一、第一步永远是只读
我现在养成一个习惯:任何新任务,先让它只搜不改。比如"先帮我找出所有调用 SendMail 的地方,列出来,不要改任何东西"。它列出结果,我确认范围没问题,再进入下一步。
这个习惯是被迫养成的。最早有一次我直接说"把 SendMail 的调用都换成新接口",它确实换了,但连带把一个我根本没想到的内部封装也改了,编译直接挂。从那以后,搜索和改动我一定分开两次做。现在回想,那次要是先让它只列调用点,我大概率会拦住它去碰那个封装,根本不会走到编译报错那步。
二、白名单目录,第一次没设的代价
最惨的一次事故就出在"没设白名单"。那次我想让它把 src/Reports/ 下的日志写法统一一下,没限定目录。它干完活,我准备提交,git status 一出来我愣了:除了 Reports,它还动了一个 appsettings.local.json。
原因是它觉得"日志要接一个新的 sink,得在配置里加一项",就自作主张把配置文件删了,然后补了一个它自己拼的版本。可那个文件里除了日志,还有我本地数据库的连接串和几个开关——它补的版本把那些全弄丢了,还写了些它以为对的默认值。
幸好 git 还在,我 git checkout 把配置文件救了回来,但那几分钟心跳很快。事后我复盘,根子不在它"聪明反被聪明误",而在我没告诉它边界在哪。从那以后,我的项目里加了一条硬规矩:
# 允许修改的目录(白名单)
allow: src/Reports/, src/Common/Logging/
deny: appsettings*.json, *.config, Migrations/,
这条规则写进 CLAUDE.md 之后,它再没碰过配置文件。我后来想,这种"它好心帮你做决定"的行为其实最危险——因为它看起来是对的,你容易疏于检查。
三、所有改动走 PR,不直推
现在我要求它改完只 commit 到本地分支,然后我自己开 PR、自己 review、自己合。绝不让它直接推 main。一来 PR 有 diff 可以逐行看,二来万一有问题,回滚就是一个按钮的事。
具体流程大概是(这套是被配置文件事件逼出来后慢慢固定的,不是一开始就懂):
- 它在一個
fix/ai-draft分支上改,不碰主分支; - 我本地
git diff main...过一遍; - 跑一遍
dotnet test,全绿才提 PR; - 合入之前再扫一眼它有没有顺手改了白名单外的文件。
说实话这套流程比"直接让它改"麻烦,但比半夜被配置丢失叫起来排查,麻烦那点时间完全值得。
agent 改完之后,git status 一定要扫一眼未被跟踪的新文件。我同事就遇到过它为了"临时验证"新建了一个脚本忘了删,最后跟着提交上去了。
四、一个让我比较放心的用法
说个正面的。上个月我想给 Reports 模块加一条统一的结构化日志,按白名单它只能动 src/Reports/ 和 src/Common/Logging/。我先把任务拆成两步:第一步只让它"列出要改哪几个文件、每处加什么",只读不改;我确认清单没问题,第二步才说"按清单改,不要超出这几个文件"。全程它没碰过白名单外的地方,git status 干干净净,连那个出过事的 appsettings.local.json 都没动。
这次顺利,关键不是它变乖了,是我把"范围"和"动作"分开声明了。先让它报计划、我拍板,再让它执行——这个节奏我现在基本固定下来,比一句"你去把日志加上"稳得多。说白了,它靠不住的地方,我用流程补;它靠谱的地方,我才放手。
还有个小细节:白名单我写的是目录而不是文件,这样它在一个文件里改不完、需要拆成两个时不会被卡住,同时又出不了那个目录。比逐文件列清单灵活,也比"整个仓库随便动"安全,目前看是平衡点。