# [LEARN-20260430-001] 文档审查应该用 4-lens 框架 + 前置项目侦察，否则会反复多轮发现新问题

## 触发场景

开发 `feature/ops-center-m365-dormant` 模块文档（PRD / data-model / API / UI 规格）。第一稿写完后，连续走了 **5 轮"再看一眼"** 审查，每轮都发现新问题：

| 轮次 | 发现的问题（节选） | 类别 |
|---|---|---|
| 1 | 列表列数 PRD vs UI 不一致 / running 期间列表行为缺失 / sync/latest 空批次返回不明 | 跨文档 diff |
| 2 | Graph Reports 脱敏 UPN 坑 / hasLicense 计算规则缺 / Reports 返 CSV 不是 JSON | 外部 API 对抗 |
| 3 | Prisma 不支持 partial unique index / UPN 大小写 JOIN 风险 / SubscribedSku 无 displayName | 实现陷阱 |
| 4 | 权限点 seed 缺失 / audit 模块用装饰器不是手写调用 / Reports 24-48h 延迟 | 项目惯例 |
| 5 | 权限码格式 `ops-center:m365-dormant:read` 三段错误（项目实际两段）/ schema 文件名命名（`platform_*` 前缀）/ `/403` 不存在 | 项目惯例 |

5 轮总耗时 ≈ 2.5 小时。如果第一轮就"应该发现的"问题没发现，反复迭代是巨大浪费。

## 根本原因

### 1. 第一稿写作时没做项目侦察（grounding）
我直接凭"项目惯例应该是什么"来写权限码 / schema 文件名 / 错误重定向，**全错**。所有 🔴 真坑都不是从"再看一眼文档"找出来的，而是 grep / read 出来的：
- 权限格式 → grep `@RequirePermissions` 才知道两段冒号
- 审计装饰器 → ls audit 模块才知道 `@Auditable()`
- schema 文件名 → ls schema 目录才知道 `platform_*` 前缀
- TanStack Query → grep package.json
- /403 路由 → find 才知道不存在

### 2. 审查靠 free-association，不是结构化 lens
每轮"再看一眼"都是脑内随机蹦角度（"看下 UX、看下安全"）。同一类问题（如外部 API 怪癖）分散在多轮才浮现。

### 3. 抽象层缠绕导致 cascade
PRD → API → data-model → Prisma → migration SQL，每往下一层暴露新约束（如"保留历史" → "diff 逻辑" → "lowercase JOIN" → "citext" → "extension 顺序"）。每跨一层都会触发回头改。

## 正确做法

### Phase 0 — 写文档前 30 分钟项目侦察

用 grep / ls / read 从代码里捞**项目惯例**，不靠记忆：

| 侦察项 | 命令 |
|---|---|
| 权限码格式 | `grep -rn "@RequirePermissions" backend/src \| head` |
| 审计模块用法 | `ls backend/src/.../audit/decorators/` + 看一个 controller 怎么用 |
| Schema 文件命名 | `ls backend/prisma/schema/` |
| env 变量复用 | `grep -E "AZURE_\|GRAPH" .env.example` |
| 同类模块代码 | `cat backend/src/modules/.../entra.service.ts`（找类似已有功能照抄结构） |
| 前端状态库 | `grep -E "tanstack\|swr\|axios" frontend/package.json` |
| 错误页 / 通用组件 | `find frontend -name '403*' -o -name 'NoAccess*'` |
| 同类 PRD 风格 | `cat docs/modules/{相邻模块}/01-prd.md`（吸收文档风格） |

### Phase 1 — 4-lens 批判审查（每个 lens 都有具体 prompt）

**Lens A — 实现者走查**
> "我是接手 PR 的初级 dev，照文档从 0 写代码。每一行该怎么写？哪里缺细节？"

逼出：`$select` 字段、分页 nextLink、SKU displayName 来源、CSV parser 选型、batch insert 大小、Content-Disposition、organizationId 来源、Prisma partial index 实施。

**Lens B — 外部 API 对抗**
> "把所有外部依赖（Graph / 第三方）的怪癖列一遍：返回格式、分页、限流、字段缺失、数据延迟、字符大小写、脱敏。"

逼出：obfuscated UPN、24-48h 延迟、UPN 大小写、CSV vs JSON、SubscribedSku 没 displayName、429 重试。

**Lens C — 失败模式走查**
> "每一步在最坏时刻崩溃（进程重启、DB 临时挂、上游半路 429、用户连点两次）。状态留下什么？怎么恢复？"

逼出：SYNC_INTERRUPTED、startup hook、try/catch 防御、并发互斥、partial 写入策略。

**Lens D — 跨文档字段 diff**
> "PRD 承诺的每个字段 / 操作机械对照到 API / data-model / UI / 测试。逐项打钩，缺失标记。"

逼出：列数不一致、license 字段缺失、错误码缺失、UI 状态规格缺失、URL state 缺失。

### 时间对比

| 模式 | 耗时 | 覆盖 |
|---|---|---|
| 5 轮 free-association | 2.5h | 100%（但拉着用户走 5 轮） |
| Phase 0 + 4 lens | 1.5h | ~95%（剩 5% 实现时自然暴露） |

**关键洞察**：换 lens > 加轮次。同一 lens 跑两次信号增量 ≈ 0。

## 何时应用

任何**写完文档后、开始动代码前**的场景。特别是：

- 新模块 PRD + data-model + API + UI 一整套
- 模块大重构（契约面变化）
- 跨多文档的功能（涉及 ≥ 3 份文档）

单字段 tweak 不需要走全 lens，照 docs-main 的 5-cell 矩阵跑就够了。

## 与现有 skill 的关系

- `docs-main` 现有 5-cell 矩阵聚焦"对不对"（一致性），需补 **Phase 0 项目侦察**
- `plan-review` 审"实施方案"（怎么做），不审"文档完整性"
- 缺一个 `doc-review` skill 专门做"够不够实现就绪"——本 learning 推动新建该 skill

## 后续行动

1. ✅ 沉淀此 learning
2. ⏳ 扩 `docs-main`：加 Phase 0 项目侦察清单 + 项目惯例自查表
3. ⏳ 新建 `doc-review` skill：4-lens 框架 + 输出格式 + 与 plan-review 边界声明
