# 不只是模型问题：AI 辅助开发的三重资源分配

## 为什么 Opus 发现的问题 Sonnet 找不到？

这不是个例。每个用 Claude Code 做过复杂项目的人都有这个体验：

> 让 Sonnet 写代码、审查、说「没问题」。换 Opus 再看一遍，找出一堆问题。把 Opus 修好的代码拿回来，Sonnet 继续写下一个 Phase，又写出类似的问题。

直觉告诉我们「那下次直接换 Opus 写」。但让 Opus 写完了再让 Sonnet 审，Sonnet 也审不出什么。看起来像「能力差距」，但两个模型训练数据几乎一样，架构无本质差别——一个能发现的错误另一个为什么不行？

答案不在「能力」里——在**搜索策略**里。

## 搜索深度 > 模型能力

想象两位医生看同一张 CT 片：

- **实习医生**：扫一眼，「没看到明显异常」
- **主任医师**：按固定流程——纵隔→肺门→肺实质→胸膜→骨窗→……在肺实质第三象限发现一个 3mm 微小结节

主任医师不是眼睛更好——是**他有流程**。

Opus 也一样。它审查更仔细不是因为「更聪明」，而是它的搜索策略更系统化——主动走每一个条件分支、构造边界测试、怀疑自己遗漏了什么。

这引出一个关键问题：**模型的注意力和人类一样是稀缺资源。怎么分配它，比单纯追求「更强模型」更值得思考。**

这个话题可以拆成两个独立的问题：**什么时候查**、**给模型看什么**。第三个问题更具元属性：**这些规则写在哪里才不被平台锁定？**

---

## 一、注意力分配：GC 式审计路由

### 为什么写代码的不该是审查代码的

写代码的注意力路径和审查代码的注意力路径是一样的。你沿着 A→B→C 写了一遍，审的时候还会沿 A→B→C 走。漏掉的 D 分支，写的时候没看，审的时候也不会想到看——这叫**同模型自审盲区**。

模型也一样。让同一个模型写完代码再审查，它相当于把自己刚走过的路又走了一遍。它不知道自己不知道什么。

一个简单的解法是「换更强的模型来审」。但这等于承认「每次都要花两倍推理成本」，而且没有解决根因——问题不是审查不够，是**审查没有策略**。

### GC 分区的启示

JVM 垃圾回收有一个关键设计：不是所有对象都需要同等频率的扫描。刚分配的对象（Eden 区）最容易变成垃圾，需要频繁扫描；熬过足够多轮的对象（老年代）证明了自己稳定，可以降低扫描频率。

代码审查同理——不是所有代码变更都需要全量回归。

| 分区 | 开发对应 | 审查策略 |
|------|---------|---------|
| **永久代** | CLAUDE.md、Skill 定义、Spec | 每次变更加全量审查 |
| **老年代** | 稳定 Phase（经过 N 个后续 Phase 未改过） | 低频率，只做回归验证 |
| **新生代** | 最近 1-2 个 Phase | 高频率，每次新 Phase 完都查 |
| **Eden** | 刚写完的代码 | 写完后立刻全维度审查 |

实现这个策略需要两样东西。

**变更跟踪卡（Card Table）**。JVM 用 Card Table 标记老年代对象被跨代修改——很小的元数据告诉 GC「这里变了，该扫了」。每个 Phase 完成后自动输出一张卡片：

```
Phase N 变更卡
  ├─ 改接口: userService.getProfile() — 返回类型变了
  ├─ 改文件: src/services/profile.ts
  ├─ 改了全局状态？是/否
  └─ 依赖方: Phase 1（调用 getProfile）
```

这张卡决定了审计路由：

```
影响范围 = 0              → 跳过
影响范围 ≤ 2 个 Phase     → Minor GC（自审 + 审直接依赖方）
影响范围 ≤ 5 个 Phase     → Major GC（所有受影响 Phase 全审）
改了全局状态              → Full GC（全量回归）
```

**假设注册表**。每个 Phase 完成时强制记录：

```
1. 我写了什么「我假设它不会发生」的事情？
2. 如果这个假设被打破，哪些地方会坏？
3. 哪些接口/状态/行为被我改变了？
```

后续 Phase 写代码前先读假设注册表——如果新功能打破了旧假设，必须主动处理冲突，不能静默覆盖。这是「自我反思」的工程化，不是靠模型不自觉，而是靠硬性的注册和检查。

---

## 二、样本分配：2.5 层锚点

注意力分配解决的是「什么时候查」。更根本的问题是：**给模型看什么？**

### 禁令为什么无效

传统 prompt 工程的核心是**禁令**：「不要用标准 markdown」「不要忘记边界情况」「不要创建重复代码」。

但模型是模式匹配系统，不是指令执行系统。读「不要用 X」时，X 的 pattern 反而被激活了。禁令数量跟模型遵守意愿成反比——十条禁令之后，每条都打折扣。

### 锚点代替禁令

2.5 层的核心命题是：**用锚点代替禁令。**

不告诉模型「别用标准 markdown」——给一份正确的 Obsidian markdown 示例文件。不告诉模型「要检查边界情况」——让模型写代码之前先填一张真值表。

Obsidian CEO Steph Ango 的 `obsidian-skills` 项目就是这个方法最干净的公众示例（33K star）。他没有写「不要用 `[]()` 格式的链接」——他放了一份 `.md` 文件，模型读了自然就学会了。锚点比规则便宜且更有效。

### Forge 自己的实践

在 ReqForge 中，这个思想体现在几个地方：

最典型的是 **UI-Spec.md**——它是 design-maker 自动生成的中间文件，不是给人读的（太细），也不是给最终渲染引擎读的。它是给 **dev-builder 模型**读的。告诉它「这些页面有什么组件、什么状态、什么响应式规则」。用了它之后，dev-builder 猜 UI 参数的频率明显下降——不是靠禁令「不要猜 UI 参数」，而是靠一份精确的结构锚点。

另一个是 **first-principles.md 的行动摘要**——它放在文档末尾，利用 recency bias 让模型执行完主流程后自然读到。不靠「一定要看这个」，靠位置触发注意力。

这些实践背后都是同一个逻辑：**不控制模型不做什么，通过控制输入分布来控制模型做什么。**

---

## 三、这一切写在哪里？

前两层（什么时候查、给模型看什么）定义了策略。但策略写在哪里决定了它能不能存活。

一个容易踩的坑是：把审计路由或者对照清单写在 `workflow.md` 里。但 `workflow.md` 是 Claude Code 的插件——按需读取，换了 OpenCode、Cursor、Gemini CLI 就断了。

解是**把决策表写进平台无关的 reference 文档**。

```
workflow.md（平台特定）:
  "Phase 完成 → 读 gc-audit-routing.md → 按影响范围执行审计"

core/skills/dev-builder/references/gc-audit-routing.md（平台无关）:
  只定义「什么情况查什么」，不写 agent() 调用
```

这个模式可以扩展成一个原则：**任何决定怎么做决策的逻辑，都不应该写在工作流脚本里。** 工作流脚本只负责「按顺序做什么」，不负责「判断怎么做」。后者是 reference 文档的职责。

---

## 四、归根结底

把前两层串联起来看，共同点很清楚：

| 问题 | 资源 | 常见做法 | 更好的做法 |
|------|------|---------|-----------|
| 什么时候查？| 注意力 | 均匀分配或换更强模型 | GC 分区（按影响范围分配注意力）|
| 给模型看什么？| 样本 | 禁令堆叠 | 锚点代替规则（控制输入分布）|

两个问题共享一个前提：**模型的运算是有限资源，要做的是优化分配，不是期待无限供应。**

这不是道德主张——是工程事实。一次代码审查最多耗费几千 token 的推理量。在预算固定的前提下，把它均匀摊在所有文件上，不如集中在 Eden 区和跨代引用上。把一篇字节流塞进上下文，不如把结构锚点放在突出位置。

认识到这一点之后，很多工程决策就自然有方向了：
- 不是用更强模型堵漏洞——是把注意力花在最可能出问题的地方
- 不是写更多 prompt 规则——是给模型更好的例子
- 不是在每个平台上重写审查逻辑——是把决策表放中间，平台只负责执行

这也是为什么 ReqForge 的架构导向「可进化的框架」而不是「全能的模型」：模型每年在变，但注意力分配和样本分配的原则不会变。

---

*这篇文章基于 ReqForge 框架的开发实践提炼。ReqForge 是一个开源的 AI 辅助开发框架，上述的 GC 审计路由和平台无关决策表已作为 feature proposal 提交。项目地址：https://github.com/zxpmail/ReqForge*
