🛠️ 第7章 Rules、Hooks 与 Approvals:先约束,再放权
📄 一句话概括

五条硬规则、三条软偏好:硬的全写成路径或动作,因为只有这两种形状能机器判。 同一套规则要写两遍:一遍给人看(AGENTS.md),一遍给机器执行(脚本)。 Demo 里五个动作被拦、三个放行——护栏不是把门关死。

📝 本章和前面几章的关系

这一章最后一篇,把前三篇收成一份能直接抄的模板。 「高风险仓库」指哪类? 有线上数据、有历史包袱、有多个团队在改的仓库。 这类仓库的共同点是:一次错误操作的代价,远大于十次低效。 Demo 把五条硬规则写成脚本,拿八个动作去撞。 结果拦下五个、放行三个——放行的三个恰好都是安全动作,这是设计出来的。

一、五条硬规则

五条覆盖四类路径加一种动作:

规则 形状 为什么是硬的
data/ 下不许改 路径 线上导出,改了没处找
锁文件不许手改 路径 手改会让依赖树和清单不一致
不许删测试 路径 删了等于关掉验证
不许新增依赖 路径 装进去的包很难清干净
不许强推 动作 覆盖别人的提交

五条全部能机器判。 这不是巧合——写硬规则的时候就要按「能不能判」来挑。 判不了的写进软偏好,别硬塞进硬规则。 「不许删测试」这条值得单独说。 它看起来像「洁癖」,其实是「不许关掉自己的验收」—— 一个能删测试的 Agent,遇到测不过的代码,最省事的做法就是删测试。

二、三条软规则

三条软偏好:注释用中文、提交信息写两段、函数不超过四十行。 共同点是「违反了也不出事,但会让代码难读」。 软规则的作用是「说出来」。 不说,它就按默认风格写;说了,大多数时候会照做。 但它不该被强制——强制执行的软偏好,本质上是你把口味当成了规矩。 软规则放哪儿? 如果全团队都认同,放 AGENTS.md 的软偏好段; 只有你自己在意,放个人配置(第 6.3 节)。 别把「我喜欢两个空格缩进」写进仓库。

三、本章小结

这一章四篇,收成一张表:

问题 答案在哪一节 一句话
为什么先控制 7.1 智能管怎么做,控制管能不能做
三道闸怎么分工 7.1 规则、钩子、审批接力
钩子怎么写 7.2 四类,只认退出码
审批点怎么设 7.3 两维打分,少问几次
从哪儿抄 7.4 五硬三软

下一章进第三件:Skill、Subagent 与 MCP。 这一章讲的是「不这么做就拦住」,下一章讲的是「让它做得更好」—— 顺序不能反:没有护栏的能力装配,是把更强的工具交给一个还没被约束住的手。 一句话记住这一章:先约束,再放权。 而约束要落在「机器能判」的地方,不然它只是愿望。

四、动手 Demo:高风险仓库的护栏模板:五硬三软

把五条硬规则写成脚本,拿八个动作去撞。 命中退出二,不命中退出零——就这一条语义。 八个动作里有三个安全的,看它们会不会被误拦——误拦率是护栏能不能长期用的关键。

HARD = [
    ("data/ 下不许改", "path", "data/"),
    ("锁文件不许手改", "path", "package-lock.json"),
    ("不许删测试", "path", "tests/"),
    ("不许新增依赖", "path", "requirements.txt"),
    ("不许强推", "verb", "push --force"),
]

def check(path, verb):
    hit = []
    for name, kind, val in HARD:
        if kind == "path" and path.startswith(val):
            hit.append(name)
    return hit

实跑输出:

--------------------------------------------------------------------
高风险仓库的护栏模板:五硬三软
--------------------------------------------------------------------


  ① 模板长什么样
  --------------------------------------------------------------------
  硬 1  data/ 下不许改
  硬 2  锁文件不许手改
  硬 3  不许删测试
  硬 4  不许新增依赖
  硬 5  不许强推
  软 1  注释用中文
  软 2  提交信息写两段
  软 3  函数不超过四十行
  ! 五条硬规则、三条软偏好。硬规则全部写成「路径」或「动作」,因为只有这两种形状能拿机器判。

  ② 拿八个动作去撞
  --------------------------------------------------------------------
| 动作 | 结果 |
|---|:---:|
| 改 src/emo.py 加一个分支 | 过 |
| 改 data/stopwords.txt | 拦 |
| 跑 tasks.py test | 过 |
| 新增依赖 requests | 拦 |
| 删 tests/test_main.py | 拦 |
| 改 README | 过 |
| 改 package-lock.json | 拦 |
| git push --force | 拦 |

  八个动作拦下 5 个,放行 3 个。
  ! 注意「跑 tasks.py test」和「改 README」都放行了——护栏不是把门关死。
  五条硬规则只针对四类路径加一种动作,其余全部放行。护栏太密,你会开始给它开口子。

  ③ 模板怎么落地
  --------------------------------------------------------------------
| 落点 | 放什么 | 谁维护 |
|---|---|---|
| `AGENTS.md` 禁区段 | 五条硬规则的原话 | 仓库负责人 |
| `hooks/guard.py` | 同一套规则的代码版 | 仓库负责人 |
| 审批策略 | 「禁止」档的动作 | 团队 |
| 个人配置 | 三条软偏好 | 每个人自己 |

  同一套规则要写两遍:一遍给人看(AGENTS.md),一遍给机器执行(guard.py)。
  两遍不一致的时候,以代码为准——因为代码真的会拦。
  ! 这一章的核心就一句:先约束,再放权。而约束要落在「机器能判」的地方,不然它只是愿望。

八个拦五个、放三个。 放行的三个是:改 src/emo.py、跑测试、改 README。 这三个都不会造成不可恢复的后果——所以护栏不该拦它们。 误拦率要盯。 五条硬规则里有四条是路径,只有一条是动作。 路径规则误伤少(因为路径明确),动作规则容易误伤—— 所以动作类规则要写得更具体:「不许强推」比「不许改历史」好判。 第三段那张表是说「同一套规则写两遍」。 给人看的在 AGENTS.md,给机器跑的在这个脚本。 两遍不一致时以代码为准——因为代码真的会拦,文档不会。 最后那句「不然它只是愿望」是这一章的全部。 写在提示词里的规则是愿望,写在钩子里的规则是约束。

30 秒实验

  1. 给你的仓库写五条硬规则,全部用路径或动作的形状。 写不出「能判」的,挪去软偏好。
  2. 拿最近十个改动撞一遍你的规则。 有没有误拦?误拦的那条,是规则写宽了还是写错了?
  3. 把 AGENTS.md 的禁区和钩子脚本对一遍。 文档里有、代码里没有的,就是假护栏。

常见误区

  1. 「规则越多越安全。」 规则多了误拦就多,然后你会给它开口子。 五条能长期活着,十五条活不过一个月。
  2. 「软规则不用写,大家都懂。」 不说它就按默认风格写。 说出来成本几乎为零,为什么不说。
  3. 「写进 AGENTS.md 就等于有护栏了。」 文档不会拦人。 只有退出码会。
自测三问(点开对答案) 1. 为什么硬规则都要写成路径或动作? —— **因为只有这两种形状能机器判。** **判不了的规则,写进硬规则也执行不了**——它只能靠自觉。 2. 「不许删测试」为什么值得当硬规则? —— **因为它等于「不许关掉自己的验收」。** **一个能删测试的 Agent,遇到测不过的代码,最省事的做法就是删测试。** 3. 同一套规则为什么要写两遍?不一致时以哪个为准? —— **一遍给人看、一遍给机器跑。** **以代码为准**——因为代码真的会拦,文档不会。

第 7 章到这里收住。下一章进第三件:Skill、Subagent 与 MCP。 这一章是「不这么做就拦住」,下一章是「让它做得更好」——顺序不能反。

相关笔记