范围要写进任务里,不是写在心里。
detector 跑在提交之前,比跑在 review 里便宜一个量级。
新建文件的越界最隐蔽:不加 git add,它根本不出现在 diff 里。
上一篇把 bug 说清楚了。这一篇加一道闸:说清楚只准改哪儿。 9.4 讲过改动的三个数字,那是「改完怎么交」。 这一篇讲的是「改之前怎么锁」,以及「锁完怎么量」。 两篇不重复:一个管输出,一个管输入。 Demo 造三个版本:越界重构八个文件、只在范围内改两个、范围外多建一个。 再用二十行检测器把越界逐个点名。
一、把范围写进任务
「顺手」这件事,Agent 和人犯的是同一个错,但原因不同。 人顺手是因为看见了不舒服; Agent 顺手是因为它读到的上下文里,那些东西和目标是连在一起的——它不区分「相关」和「该动」。 所以任务描述里要有一句显式的话:只允许改哪几个文件。 这句话的作用不是「限制能力」,是「给它一个判据」—— 它看到一个变量名不好,本来会改;有了这句话,它会记下来。
| 任务写法 | 典型结果 |
|---|---|
| 修一下空名字导致的 500 | 顺手改了八个文件 |
| 修 500,只改 handler.py 和它的测试 | 改了两个 |
| 同上,加一句「其它发现记到 issue」 | 两个,另有三条待办 |
第三行那个「记到 issue」很关键。 不给出路,它就只能二选一:要么改,要么忘。 给了出路,它会把发现攒下来,而这份清单本身就是下一轮任务的输入。
二、用 diff 数字兜底
光写进任务不够,还要有一道能自动跑的闸。
Demo 里那个检测器二十行:把允许的文件列成集合,拿 git diff --name-only 跟它比,多了就报。
二十行,跑在提交之前,比跑在 review 里便宜一个量级。
review 里发现越界,你得评论、等回复、来回一轮;
提交前发现,改一下就好。
Demo 里三个版本:A 改了八个文件、越界六个;B 两个、越界零;C 两个文件但越界一个。
C 版那个最值得看。
它只碰了两个文件,看起来很小,但其中一个是你没批准的新文件。
而如果检测器跑在 git add 之前,用裸 git diff,这个新文件根本不会出现在列表里——
越界就这么漏过去了。
所以检测器有两个细节要写对:跑在 git add 之后,并且把「新增文件」和「修改文件」分开看。
新增文件的风险比修改更高——修改至少还在原来的调用链上,新增的没人知道它会被谁用。
三、发现该重构怎么办
| 发现 | 当次该做 | 什么时候做 |
|---|---|---|
| 这里应该抽个函数 | 记一笔,不动 | 换一个提交 |
| 这个变量名太糟 | 记一笔,不动 | 换一个提交 |
| 这行会崩 | 当场修 | 就是这次 |
判据只有一条:这件事跟「修好这个 bug」是不是同一件。 是,就做;不是,就记。 第三行是唯一的例外。 你路过一个会崩的地方,看见了不修,下次还得再来一轮,而且下次来的人可能不知道你也看见过。 但要在提交信息里明说「顺手修了另一个问题」,别让它伪装成这次修复的一部分。 「记一笔」要真记。 写进 issue 或者代码里的 TODO,别记在脑子里—— 脑子里那张单子从来不会变短,也从来没被谁看见过。 这条规矩对 Agent 同样适用。 任务里写清「其它发现列成清单交回来,不要动手改」,它就会给你一份清单;不写,它就会替你做决定。
四、动手 Demo:约束修改范围:越界的另一种查法
同一个 bug,三个版本各改一遍:越界重构、只在范围内改、范围外多建一个文件。
然后拿一份声明的范围当尺子,逐个点名越界。
三个版本都是真 git 仓库、真 git diff --name-only,文件列表是命令输出,不是我列的。
SCOPE = {"svc/handler.py", "tests/test_handler.py"}
# 必须在 git add 之后,否则新建文件不出现
changed = git(root, "diff", "--cached", "--name-only").split("\n")
extra = [f for f in changed if f and f not in SCOPE]
print("改了 %d 个,越界 %d 个" % (len([f for f in changed if f]), len(extra)))
for f in extra:
print(" 越界:", f)
实跑输出:
--------------------------------------------------------------------
约束修改范围:越界的另一种查法
--------------------------------------------------------------------
上一篇把 bug 说清楚了。这一篇把范围也写清楚,然后拿它当尺子量 diff。
任务里先声明范围:只允许改 svc/handler.py 和 tests/test_handler.py。
① 三个版本,各自改动
--------------------------------------------------------------------
| 版本 | 改了 | 范围内 | 越界 | 越界的文件 |
|---|---:|---:|---:|---|
| A | 8 | 2 | 6 | docs/CONVENTIONS.md、svc/routes.py、svc/store.py… |
| B | 2 | 2 | 0 | — |
| C | 2 | 1 | 1 | svc/guard.py |
A 越界 6 个,C 越界 1 个,B 越界 0 个。
! C 版那个越界很值得看:它是新建的文件。
检测器必须跑在 git add 之后——直接 git diff 不含未跟踪文件,新文件根本不出现,越界就漏了。
② 检测器二十行
--------------------------------------------------------------------
[越界检测 · 本沙箱真跑]
| SCOPE = {"svc/handler.py", "tests/test_handler.py"}
| changed = git(root, "diff", "--cached", "--name-only")
| extra = [f for f in changed if f not in SCOPE]
| if extra:
| print("越界:", extra)
跑在提交之前,比跑在 review 里便宜一个量级。
③ 发现该重构怎么办
--------------------------------------------------------------------
| 发现 | 当次该做 | 什么时候做 |
|---|---|---|
| 这里应该抽个函数 | 记一笔,不动 | 换一个提交 |
| 这个变量名太糟 | 记一笔,不动 | 换一个提交 |
| 这行会崩 | 当场修 | 就是这次 |
判据只有一条:这件事跟「修好这个 bug」是不是同一件。
第三行是例外——一个会崩的 bug 你看见了不修,下次还得再来一轮。
但要在提交信息里明说是顺手修的另一个问题。
! 「记一笔」要真记。
写进 issue 或 TODO,不然它永远只是「记在脑子里」,而脑子里那张单子从来不会变短。
A 版改了八个文件,越界六个;B 版两个,零;C 版两个,越界一个。
三个版本的 bug 修复部分完全一样——差别只在「多做了什么」。
A 版没有一处改动是错的。
函数改名、加类型别名、补一份命名约定文档,都是好事。
但它把「这次改了什么」这件事变得无法用一句话说清——
而「能用一句话说清」恰恰是提交信息的前提。
C 版是最像样的一次越界:只多了十行,还是个好抽象。
但它暴露了检测器的一个盲区——
如果跑在 git add 之前,这个新文件根本不在 git diff 里,越界就查不出来。
所以检测器的顺序是:add、再 diff、再比范围。
这三步倒过来,闸门就漏了。
30 秒实验
- 给你手上的项目写一份 SCOPE 例子:随便挑一个任务,写下「只准改哪几个文件」。 写不出来,说明这个任务还没拆到能派出去的程度。
- 跑一次
git diff --name-only,数一下上一次提交改了几个文件。 超过三而任务只有一件的,就是可以拆的。 - 在你的 AGENTS.md 里加一句「其它发现记清单,不要动手」。 下一次任务看它有没有真的交回一份清单。
常见误区
- 「限得太死会影响 Agent 发挥。」 限制的是「改哪儿」,不是「怎么改」。 范围内的解法它随便发挥,范围外的它记下来就好。
- 「越界改了也没坏处,反正测试会挂。」 测试只覆盖测过的地方。 顺手改的那八处,可能一处测试都没有。
- 「diff 数字看一眼就行,不用脚本。」 人看一眼会漏,尤其是新建文件。 二十行脚本跑一千次都不累,你看一千次会累。
自测三问(点开对答案)
1. 为什么要把范围写进任务里? —— **因为「相关」和「该动」是两件事。** **Agent 分不清,你不给它判据,它就会替你做决定。** 2. 检测器为什么必须跑在 `git add` 之后? —— **因为裸 `git diff` 不含未跟踪文件。** **新建文件的越界最隐蔽,也最危险。** 3. 哪种「顺手」是允许的? —— **只有「这行会崩」这一种。** **而且要写明是顺手修的另一件事,别伪装成这次修复的一部分。**下一篇讲测试本身:覆盖率百分之百的测试,可能一条 bug 都抓不住。
- ☐ (本篇完,下一篇 10.3 AI 生成测试:从表面达标到真正可靠)