12 KiB
并行深读流水线(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保留作独立校验兜底(引擎不可用时的替代),两者判定逻辑一致。