169 lines
12 KiB
Markdown
169 lines
12 KiB
Markdown
# 并行深读流水线(Step 5.5 操作手册)
|
||
|
||
> 目的:让 whole-project 审查达到全库覆盖 ≥85% 且高风险 ≥95%(双目标 gate="both",文件数口径)**且**每个深读文件通过三件套质量门禁(单元完整性 / 行覆盖 / 防伪抽验)。
|
||
> 核心:主上下文不逐文件深读,改用并行 explore 子代理分组深读,每个子代理返回
|
||
> 「深读确认清单 + semantic_units + read_ranges + findings(带 file:line 证据)」,结果**落盘**给聚合脚本校验。
|
||
|
||
## 〇、三件套质量门禁(V2.1,每个深读文件必须通过)
|
||
|
||
| # | 门禁 | 定义 | 阈值 | 角色 | 防伪性 |
|
||
|---|---|---|---|---|---|
|
||
| ① | **单元完整性** | `semantic_units` 与图谱单元节点差集 = 空 | **100%(硬)** | 防漏读(跳过后半段) | 引擎/脚本自动核验 |
|
||
| ② | **行覆盖** | `union(read_ranges)` / 真实行数 | **≥95%** | 防"每单元只读一行" | 低(自报粒度) |
|
||
| ③ | **防伪抽验** | 主代理回读抽样单元 | **每组 2 文件 × 2-3 单元/文件,每波 ≤40 次** | 防假读(抄清单不读) | 唯一防假读手段 |
|
||
|
||
判定规则:
|
||
- ① 单元被覆盖 = 图谱单元 `[ls,le]` 与上报 `semantic_units` 中某 range **精确匹配**(优先)或**重叠 ≥80%**(兜底),且该单元 ≥80% 行落在 `union(read_ranges)` 内;一个上报 range 只能匹配一个单元(one-to-one),宽 range 无法覆盖多个单元。
|
||
- 巨型文件豁免 ①:`单元数<3` 或 `最大单元行占比>80%`(如 migrations.rs run_migrations 6200/6331=98%)→ 该文件仅按 ② 行覆盖校验,引擎/脚本返回 `unit_exempt_files`。
|
||
- ② 分母 = **真实文件行数**(读取源文件,非图节点 line_end——已验证 ±1 偏差),行数缓存。
|
||
- ③ 预算:**每波 ≤40 次 read**(每波 4-6 组 × 2 文件 × 3 单元 ≈ 24-36,留余量);每组抽 2 文件、每文件抽 2-3 单元回读比对 note;抽到假读 → 该组重读并升级抽验率。结果**必须落盘 `spot_check_<batch>.json`** 并在 Step 8 聚合注入 `review_data.spot_check`(顶层字段),否则报告渲染"防伪抽验:未执行 🔴"且 verify-spot-check.ps1 exit 1。
|
||
|
||
> **诚实声明**:引擎无法防假读。① 防漏单元、② 防读不全、③(抽样)才是唯一防假读的手段,且为预算化而非全量。verify-spot-check.ps1 只能验证抽验**声明完整性**(抽了、单元数>0、range 落在 read_ranges 内),无法验证主代理是否真读了文件。
|
||
|
||
## 一、分组规则
|
||
|
||
以 AuraSpace 实测分组表为基准(约 508 源文件 → 14-16 组,每组 ≤40 文件):
|
||
|
||
| # | 组(目录) | 约文件数 | 子代理职责 |
|
||
|---|---|---|---|
|
||
| 1 | `server/src/api`(1/2) | 30 | 后端 API 层前半 |
|
||
| 2 | `server/src/api`(2/2) | 31 | 后端 API 层后半 |
|
||
| 3 | `server/src/services`(1/2) | 33 | 核心业务服务前半 |
|
||
| 4 | `server/src/services`(2/2) | 33 | 核心业务服务后半 |
|
||
| 5 | `server/src/domain` | 43 | 领域模型/DTO |
|
||
| 6 | `server/src/deepwiki` + `infrastructure` | 25 | DeepWiki + 基础设施 |
|
||
| 7 | `server/tests` + `bin` + `models` | 14 | 测试与工具二进制 |
|
||
| 8 | `web/src/views`(1/2) | 34 | 前端视图前半 |
|
||
| 9 | `web/src/views`(2/2) | 33 | 前端视图后半 |
|
||
| 10 | `web/src/components`(1/3) | 38 | 前端组件(issues/evm/ci) |
|
||
| 11 | `web/src/components`(2/3) | 38 | 前端组件(docs/wiki/time-log/settings) |
|
||
| 12 | `web/src/components`(3/3) | 37 | 前端组件(ppt/agent/opencode/common/ui) |
|
||
| 13 | `web/src/store` + `utils` + `api` | 31 | 状态管理/工具函数/API 客户端 |
|
||
| 14 | `web/src/hooks` + `services` + `generated` + `web/tests` | 大 | 前端其余(可分 2 组) |
|
||
|
||
> 实际以 `deep_read_plan_tool(gate="overall", target_coverage=85, include_prior=True)`
|
||
> 返回的 `groups` 为准(引擎按风险权重贪心 + 目录分组,数量随仓库变化)。
|
||
> 每次并行派发 **4-6 个子代理**,其余排队分批,避免 MCP 并发压力与上下文风暴。
|
||
|
||
## 二、子代理 Prompt 模板(V2.1,含三件套协议)
|
||
|
||
对每个组派发如下 prompt(替换 `<GROUP>` / `<FILE_LIST>` / `<输出目录>` / `<临时输出文件>`):
|
||
|
||
```
|
||
你是一个代码审查深读子代理。请深读以下 <GROUP> 组的全部文件:
|
||
|
||
<FILE_LIST(每行一个相对路径)>
|
||
|
||
要求:
|
||
1. 逐文件用 read 读取完整内容(文件大则分段读),**不要跳过任何文件**。
|
||
2. 对每个文件,从八个类别审视:interface / business / data / utility /
|
||
error handling / security / performance / observability,并叠加
|
||
CRITICAL 子轮:SQL 与数据安全、竞态条件、LLM 输出信任边界、Shell 注入、枚举完备性。
|
||
3. 只报告**真实问题**(严重度 blocker/major/minor,置信度 1-10)。
|
||
每条 finding 必须含 file:line 证据(实际读取到的行),没有证据视为未深读。
|
||
4. 良好实践、无问题的文件,在 deep_read 确认清单中标注,不产出 finding。
|
||
5. **每个文件必须填写质量字段**(这是硬性要求,缺失视为未深读):
|
||
- total_lines: 文件真实总行数(从 read 输出得知)
|
||
- read_ranges: 实际读取的行区间数组 [[s,e],...](分段读即天然区间),
|
||
闭区间,并集应覆盖你读过的每一行
|
||
- semantic_units: 你实际读过的每个语义单元(函数/类/接口)摘要,
|
||
{"range":[s,e],"kind":"Function|Class|Test","name":"<符号名>","note":"<≤60字摘要>"}
|
||
**严格模式(强制)**:必须**逐一列出该文件图谱中的全部语义单元**,
|
||
包括:
|
||
- 所有 Function(含私有/辅助小函数)
|
||
- 所有 Class / interface / Type(含 Props 接口、仅数行的小接口——如
|
||
`MermaidBlockProps`(3 行)、`NavEntry`(5 行)也必须单独列出)
|
||
- 不得把 Props interface / 小接口并入父组件或跳过
|
||
- 一个语义单元 = 一个 semantic_units 条目,range 取该符号的实际
|
||
[line_start, line_end]
|
||
跳过任一图谱单元 = 漏读,会被单元完整性差集校验检出(unit_gap)→ 该文件须补报重读
|
||
|
||
输出(结构化,仅此格式),写入 <临时输出文件>(JSON 文件,不要回传大文本):
|
||
{
|
||
"group": "<GROUP>",
|
||
"outputs": [
|
||
{
|
||
"path": "相对路径",
|
||
"total_lines": <int>,
|
||
"read_ranges": [[s,e], ...],
|
||
"semantic_units": [{"range":[s,e],"kind":"...","name":"...","note":"..."}],
|
||
"findings": [
|
||
{"path": "...", "line": <int>, "severity": "blocker|major|minor",
|
||
"category": "security|business|data|...", "confidence": <1-10>,
|
||
"message": "问题描述(含具体行内容证据)", "fix": "修复建议"}
|
||
]
|
||
}
|
||
],
|
||
"summary": "本组一句话结论(主要风险点)"
|
||
}
|
||
```
|
||
|
||
深读确认 = 该组文件全部出现在 outputs 中,且每个都有 `total_lines`/`read_ranges`/`semantic_units`。
|
||
|
||
> **禁止退化格式**:不得把子代理输出简化为 `DEEPREAD_CONFIRM: <路径|总行数|已读范围|单元数>` 单行文本。
|
||
> 那会使主代理拿不到分段 `read_ranges` 与逐单元 `semantic_units`,导致 coverage_tool 三件套门禁
|
||
> 无法执行、报告"无行覆盖"。子代理 prompt 必须使用本节模板原样复制。
|
||
|
||
## 三、输出 Schema(子代理 → 落盘 → 主代理)
|
||
|
||
- **落盘机制(强制)**:子代理把上述 JSON 写入主代理指定的临时目录(如
|
||
`C:\Users\ADMINI~1\AppData\Local\Temp\opencode\review\batchN.json`)。
|
||
**不要把 1-2MB JSON 回传主上下文**——主代理只读聚合结果摘要。
|
||
- 每波子代理完成后,主代理运行聚合脚本:
|
||
```
|
||
python skills/project-review/scripts/aggregate_deep_read.py <repo_root> <输出目录>
|
||
```
|
||
返回 `verified_files / line_gap_files / unit_gap_files / unit_exempt_files / summary`。
|
||
|
||
## 四、主代理收尾流程
|
||
|
||
```
|
||
1. 每波子代理完成后(不等全部结束):
|
||
aggregate_deep_read.py <repo_root> <落盘目录>
|
||
2. 汇总三件套判定:
|
||
- line_gap_files ∪ unit_gap_files → 补读队列(下波派发,禁止跳过)
|
||
- unit_exempt_files → 仅按行覆盖校验(已由脚本处理)
|
||
- verified_files → 计入本轮 deep_read_files
|
||
3. 防伪抽验(**强制,每波必做**):对每组抽 **2 文件**、每文件抽 2-3 个语义单元,
|
||
回读源文件对应行比对 semantic_units.note。每波 ≤40 次 read。抽到假读 → 该组重读并升级抽验率。
|
||
结果落盘 `spot_check_<batch>.json`(schema:groups_sampled / files_sampled / units_sampled /
|
||
fake_read_found / groups_rereread / samples[{group,file,unit,range,note_match,in_read_ranges}])。
|
||
4. 全部达标后,三件套数据传入引擎 coverage_tool(B 阶段引擎原生支持):
|
||
coverage_tool(deep_read_files=<verified_files>, gate="both+line",
|
||
file_read_ranges=<{rel:[[s,e]..]}>, file_semantic_units=<{rel:[...]}>)
|
||
→ 引擎返回 line_coverage_pct / unit_coverage_pct / line_gap_files / unit_gap_files / unit_exempt_files
|
||
⚠️ 禁止降级:unit_gap_files 或 line_gap_files 非空时,**不得**改回 gate="both" 静默跳过;
|
||
必须补轮重读至空,或在报告中显式标注"三件套未达标 🔴"并列出缺口文件。
|
||
5. 未达标(文件数 <85%/<95% 或行覆盖 <95% 或单元有缺口)→
|
||
按 priority_deep_read_files / line_gap_files / unit_gap_files 补一轮(可再派 1-3 个子代理)。
|
||
6. G2:对 silent_files 随机抽 15% 深读(本轮未覆盖的静默文件)。
|
||
7. 报告生成前:聚合全部 `spot_check_*.json` → 注入 `review_data.spot_check`(顶层字段)。
|
||
8. G3:三件套 + 文件数双口径达标 → 报告生成(覆盖度区块须含行/单元覆盖 + 防伪抽验)→
|
||
跑 verify-spot-check.ps1(Step 8.8)→ save_coverage_index_tool(deep_read_files=<verified_files>, file_read_ranges=<ranges>) 写 v2 索引。
|
||
```
|
||
|
||
## 五、质量控制与防伪
|
||
|
||
| 风险 | 对策 |
|
||
|---|---|
|
||
| 子代理"声称读了"但没真读 | 强制 `total_lines`/`read_ranges`/`semantic_units` 三字段 + findings 带行号;聚合脚本按①差集+②行并集双校验;主代理③抽样回读 |
|
||
| 子代理漏读文件 | outputs 与分组清单 diff,漏读计入覆盖率缺口,触发补轮 |
|
||
| 子代理宽 range 冒充全读 | ① one-to-one 匹配:一个上报 range 只能覆盖一个单元,无法用整文件 range 覆盖所有单元 |
|
||
| 子代理漏报小单元(Props interface / Type / 小函数) | 严格模式:semantic_units 必须逐一列出图谱全部单元(含 3-5 行的小接口);unit_gap 非空 → 该文件补报重读,不得视为已深读 |
|
||
| 子代理各自为政口径不一 | 统一八类 + CRITICAL 子轮 + severity/confidence 标准(见上模板) |
|
||
| 增量掩盖新代码 | `include_prior=True` 按 per-file SHA 判定;变更文件自动失效重读 |
|
||
| 并发压力 | 每批 4-6 个并行,其余排队;batch_size 40 控制单组体量 |
|
||
| 主上下文被大 JSON 撑爆 | 落盘机制:子代理写临时文件,主代理只读聚合摘要 |
|
||
|
||
## 六、跨轮增量(多轮累积)
|
||
|
||
- 每轮报告后 `save_coverage_index_tool` 写 `.code-review-graph/coverage-index.json`
|
||
(相对路径 → per-file SHA)。
|
||
- 下一轮 `deep_read_plan_tool(include_prior=True)` / `coverage_tool(include_prior=True)`
|
||
自动复用 SHA 未变文件 → 增量任务 = 新增文件 + 变更文件。
|
||
- 多轮后增量归零即实现全库全覆盖,避免每轮从 2-5% 起步。
|
||
- 引擎 B 阶段(`compute_coverage` 支持 `file_read_ranges`/`file_semantic_units`,`gate="both+line"`)**已落地**。主代理优先用
|
||
`coverage_tool(deep_read_files=..., gate="both+line", file_read_ranges=..., file_semantic_units=...)`
|
||
做三件套门禁(引擎返回 `line_coverage_pct`/`unit_coverage_pct`/`line_gap_files`/`unit_gap_files`/`unit_exempt_files`)。
|
||
A 阶段聚合脚本 `scripts/aggregate_deep_read.py` 保留作独立校验兜底(引擎不可用时的替代),两者判定逻辑一致。
|