# 代码审查功能文档 > 代码审查能力由**两个独立工作流**组成:`unified-review`(diff 审查)与 `project-review`(项目级审查)。本文聚焦**审查工作流**的操作细节(流程、覆盖度机制、客观指标、报告产物、实操踩坑)。 > > 📖 **文档分工**: > - **本文**(`CODE_REVIEW_GUIDE_ZH.md`)— 审查流程 / 指标解读 / 覆盖度机制 / 报告产物 / 踩坑记录 > - **工具总览**(`CODE_REVIEW_GRAPH_ZH.md`)— 工具定位、MCP 工具清单、CLI 命令、配置与部署、opencode 集成现状 --- ## 目录 1. [两种审查工作流](#1-两种审查工作流) 2. [unified-review:diff 代码审查](#2-unified-reviewdiff-代码审查) 3. [project-review:项目级代码审查](#3-project-review项目级代码审查) 4. [并行深读流水线](#36-并行深读流水线whole-project-强制) 5. [审查调用的 MCP 工具](#4-审查调用的-mcp-工具) 6. [5 项客观指标详解](#5-5-项客观指标详解) 7. [审查 Skill 体系](#6-审查-skill-体系) 8. [审查报告成果物](#7-审查报告成果物) 9. [快速上手](#8-快速上手) --- ## 1. 两种审查工作流 | 维度 | unified-review | project-review | | --- | --- | --- | | **审查对象** | git diff(默认 `HEAD~1..HEAD`)| 全部源文件 / 单个功能 | | **范围来源** | 自动检测 git diff | 由用户自然语言指令决定 | | **opencode 命令** | `/code-review-graph-unified-review` | `/code-review-graph-project-review` | | **MCP prompt** | `/code-review-graph:unified_review` | `/code-review-graph:project_review` | **共同特性**: - **只读**:不修改代码,所有修复由人工裁决 - **客观评分**:统一走 `score_review_tool` 的 5 项量化指标 - **去重合并**:统一走 `dedupe_findings_tool` - **双格式报告**:默认产出中文 HTML + Markdown - **验收门禁**:任一 🔴 blocker → 判定 `❌ FAIL` --- ## 2. unified-review:diff 代码审查 ### 2.1 执行流程 | 步骤 | 做了什么 | 起到什么功能 | | --- | --- | --- | | **Step 0 范围** | 固定 `standard` 档位(全层审查);检测语言/框架;判定 change/file/service/chain 级范围 | 决定审查深度与范围粒度,避免对简单改动做全量审查,对高风险模块自动升级审查强度 | | **Step 1 图谱上下文** | `build_or_update_graph` 确保图谱最新;`get_review_context` 拿 blast radius + 源码片段;`detect_changes` 拿风险分、测试缺口、受影响流 | 让 AI 只读变更波及的最小文件集,建立"改了什么、影响了谁"的全貌,token 高效 | | **Step 2 Layer 1 链路分解** | 八分类检查(接口/业务/数据/工具/错误处理/安全/性能/可观测性)+ gstack CRITICAL 五类(SQL 数据安全、竞态并发、LLM 信任边界、Shell 注入、枚举完整性) | 逐类排查 AI 易漏的高风险缺陷(注入、竞态、信任边界、枚举遗漏),建立分类清单 | | **Step 3 Layer 2 量化评分** | `score_review_tool` 计算 5 项客观指标(SQL 风险/异常分支/冗余率/高风险密度/漏洞启发式);需求覆盖、逻辑对齐等由 LLM 判断(`llm_judged` 标注) | 用数据支撑评分而非主观判断:指标带 good/warn/fail 分级与证据,明确哪些需 LLM 补判 | | **Step 4 specialist 派发** | diff ≥ 50 行时并行派发 testing/maintainability/security/performance/data-migration/api-contract 子代理,各自独立审查 | 用"多个专注专家并行独立审查"覆盖单一视角盲区,多专家确认的 finding 置信度更高 | | **Step 5 合并去重** | `dedupe_findings_tool` 按 `path:line:category` 指纹合并、多源确认置信 +1、低置信抑制、计算 PR 质量分 | 把主审查 + 各 specialist 的 findings 合并成唯一清单,去除重复噪音,量化整体质量 | | **Step 6 人工裁决** | **只读**。每条 finding 带 severity + 置信度 + file:line + 修复建议,按 severity 批量呈现,逐个决定修/不修 | 把关决策权交给人工,防止 AI 擅自改码;blocker 不可批量跳过,确保高风险项被确认 | | **Step 7 验收门禁** | 任一 🔴 blocker → verdict `❌ FAIL`;分类 Ready / Needs Fix / Unusable | 给出明确的合并/上线结论,任何阻塞项都阻止通过,避免带病合并 | | **Step 8 报告** | `generate_report_tool`(`format="both"`)产出中文 HTML + Markdown 报告 | 生成可分发、可存档的审查成果物,供 PR 描述、文档、评审留痕 | | **Step 9 持久化** | 若 `gstack-review-log` 可用则记录审查结果,否则静默跳过 | 供跨次审查去重抑制与 /ship 识别,形成审查历史 | ### 2.2 档位 审查默认运行在 `standard` 档位(全层审查:Layer 1 链路分解 + Layer 2 客观评分 + 报告)。`tier` 参数支持 `fast` / `standard` / `strict` 三档,其中 `fast` / `strict` 由 `.code-review.yaml` 配置或 MCP prompt 调用时指定;`/code-review-graph-unified-review` 命令固定 `standard`。覆盖度目标固定为双目标:全库 ≥85% **且** 高风险 ≥95%(project-review 三件套口径见 §3.5.4)。 --- ## 3. project-review:项目级代码审查 ### 3.1 范围解析(Step 0) | 用户指令 | 解析结果 | | --- | --- | | "对项目代码进行全面审查" / "全面审查" / "整个项目" | `scope="whole-project"` | | "对支付功能的代码进行审查" / "审查 auth 模块" | `scope="feature"`, `target=<关键词>` | ### 3.2 全项目流程(whole-project) | 步骤 | 做了什么 | 起到什么功能 | | --- | --- | --- | | **Step 0 范围解析** | 从用户指令解析出 `scope="whole-project"` | 明确本次审查是整个项目而非 diff/单功能 | | **Step 1 图谱就绪 + 最小上下文** | `build_or_update_graph_tool()` 确保图谱最新;`get_minimal_context_tool(task="project review")` 拿节点/边/社区/风险概览 | 保证后续查询基于最新代码结构;最小上下文把全库概览压缩为几百 token,避免主上下文被 500+ 文件淹没 | | **Step 2 架构全景** | `get_architecture_overview` + `list_communities`(模块地图) | 先建立全局模块结构认知,为后续定位高风险区域提供上下文 | | **Step 3 高风险定位** | `get_knowledge_gaps`(未测试热点/孤立节点)+ `get_hub_nodes`(热点)+ `get_bridge_nodes`(瓶颈)+ `find_large_functions`(超大函数)+ `get_surprising_connections`(异常耦合) | 图论分析(节点度、介数中心性、社区划分、测试覆盖边),不读代码内容——它能告诉你"这个函数被 20 处调用,改动影响大",但说不出代码写得如何。 | | **Step 4 全量客观评分** | `score_review_tool(all_files=True)` 评分全部源文件;`community_health_tool()` 前置检查图谱健康 | 对每个源文件跑 5 项客观指标,文本质量扫描(_iter_source_lines 读文件逐行匹配正则),不关心调用关系——它能告诉你"这个文件异常处理薄弱",但看不出它是架构瓶颈。社区归属率 <90% 需先 `postprocess` 重建,否则覆盖度失真。 | | **Step 5 并行深读流水线** | `deep_read_plan_tool(target_coverage=85, include_prior=True)` 生成风险加权分组 → 每次并行派发 4-6 个 explore 子代理分组深读(每组 ≤40 文件)→ 子代理返回 `deep_read_files` + findings(每条必须带 file:line 证据)→ `coverage_tool(deep_read_files=<全清单>, gate="both", include_prior=True)` 复算 | 主上下文不逐文件读 500+ 文件,改为子代理流水线分摊。**这是 whole-project 覆盖达标的唯一可行路径**。深读计划按风险权重贪心,85% 全库计划先选满全部高风险文件,天然逼近高风险 95%。详见 §3.6 | | **Step 6 覆盖度门禁与补轮** | 双目标未达标(全库 <85% 或 高风险 <95%)→ 按 `coverage_tool` 返回的 `priority_deep_read_files` 补派 1-3 个子代理;`silent_files` 随机抽 15% 深读(G2),发现 ≥1 major 则升级全量 | 可量化兜底"该读的都读了",防止审查深度由感觉决定 | | **Step 7 合并去重** | `dedupe_findings_tool` 合并去重 + PR 质量分 | 统一 findings 清单,量化整体质量 | | **Step 8 人工裁决 + 门禁** | 只读裁决;任一 🔴 blocker → `❌ FAIL` | 人工把关 + 明确的审查结论 | | **Step 9 报告 + 自检** | `generate_report_tool(review_data=..., output_path="docs/reviews/...")` 产出 HTML + Markdown;Step 9.5 打开生成的 md 自检(问题清单数、三要素、客观指标表);Step 9.6 跑 `verify-report.ps1` 命名自检 | 全项目体检报告,可分发存档;自检防"空报告/漏命名"回归 | | **Step 10 持久化** | `save_coverage_index_tool(deep_read_files=<本轮全量深读清单>)` 写跨轮覆盖索引(file + per-file SHA) | 下一轮 `include_prior=True` 自动复用未变更文件,多轮后增量归零即全覆盖 | ### 3.3 单功能流程(feature) | 步骤 | 做了什么 | 起到什么功能 | | --- | --- | --- | | **Step 0 范围解析** | 从用户指令解析出 `scope="feature"`, `target=<关键词>` | 明确只审查目标功能,缩小范围 | | **Step 1 图谱就绪** | `build_or_update_graph_tool()` + `get_minimal_context_tool` | 保证查询基于最新代码 | | **Step 2 定位功能代码** | `semantic_search_nodes(query=target)`(语义定位)+ `query_graph(children_of, target=<模块>)`(模块展开)→ 聚合功能涉及文件 `files=[...]` | 把"功能关键词"转化为具体文件清单,确定审查对象 | | **Step 3 影响面分析** | `get_impact_radius(changed_files=files)` | 找出该功能波及的调用方/依赖方,评估改动影响范围 | | **Step 4 客观评分** | `score_review_tool(changed_files=files + 影响文件)` | 对功能代码跑 5 项客观指标 | | **Step 5 链路分解 + 深读** | Layer 1 八分类 + CRITICAL 检查(聚焦功能文件);文件多时也可用子代理分组深读 | 逐类排查功能内的高风险缺陷 | | **Step 6 覆盖度计算** | `coverage_tool(deep_read_files=<功能文件>, gate="both")` 复算 | 功能级覆盖兜底 | | **Step 7 合并去重** | `dedupe_findings_tool` 合并去重 + PR 质量分 | 统一 findings 清单 | | **Step 8 人工裁决 + 门禁** | 只读裁决;任一 🔴 blocker → `❌ FAIL` | 人工把关 + 结论 | | **Step 9 报告 + 自检** | `generate_report_tool(format="both")`;md 自检 + verify-report.ps1 | 功能审查报告,防回归 | | **Step 10 持久化** | `save_coverage_index_tool` 写覆盖索引 | 跨轮增量复用 | ### 3.4 `all_files` 参数 `score_review_tool(all_files=True)` 评分图谱内**全部源文件**,忽略 `changed_files` 与 git diff,用于全项目审查。默认 `False`。 ### 3.5 覆盖度保障机制 > 目标:保证"该深读的都读了,不该漏的没漏",用可量化的方式兜底非热点文件。本节是 project-review 的增强约定,unified-review 可复用(把"深读名单"理解为"变更文件 + 高风险文件")。 #### 3.5.1 三层覆盖模型 | 层 | 对应步骤 | 覆盖范围 | 手段 | | --- | --- | --- | --- | | L1 全量机械扫描 | Step 4 `all_files=True` | **100% 文件** × 5 项指标 | 正则启发式,查机械性风险 | | L2 并集信号 → 深读名单 | Step 3 + 本节 | 任一信号点名的文件 | 多信号并集,无上限 | | L3 深度审查 | Step 5.5 子代理深读 | 名单内全部文件 | 逐文件通读(大文件分段),产出带 file:line 证据的 finding | #### 3.5.2 深读名单(并集信号,无上限) ```text 进名单 = 任一命中: ① 拓扑热点:hub / bridge / large_functions / knowledge_gaps 标记 ② Step 4 指标 fail 或 warn 的文件 ③ churn 热点(≥3 次提交) ④ 测试缺口热点(untested_hotspots) 任一命中 → 必须深读(无上限) 未被任何信号点名 → 记入"未深读文件清单",在报告中显式列出 ``` 关键原则:**不覆盖不可怕,不知道没覆盖才可怕**。报告必须附带"未深读文件清单",把覆盖边界显式化。 #### 3.5.3 静默文件抽检 - 从未被信号点名的文件("静默文件")随机抽 **15%** 深读 - **升级规则**:抽检中发现 ≥1 个 major → 该文件升级为全量深读,并触发同社区/同类文件追加抽检一轮 #### 3.5.4 覆盖度度量(文件数口径 + 三件套质量口径,`gate="both+line"`) **由引擎 `coverage_tool` 自动计算**(`code-review-graph` v2.5.0+,三件套口径 v2.5.1 起),审查代理只需传入 `deep_read_files`(本轮实际深读清单)、`gate` 与三件套数据(`file_read_ranges` / `file_semantic_units`)。 ```text 双重文件数口径: 全库覆盖 coverage_pct = 已深读文件数 / 全部源文件数 高风险覆盖 high_risk_coverage_pct = 已深读高风险文件数 / 信号点名文件数 三件套质量口径(gate="both+line" 时生效): ① 单元完整性 unit_coverage_pct = 语义单元无缺口的深读文件数 / 深读文件数 语义单元 = 图谱 Function/Class/Test 节点;子代理上报 semantic_units 与其差集必须为空 巨型文件豁免(最大单元行占比 >80%,如 migrations.rs run_migrations 98%)→ 仅按行覆盖校验 ② 行覆盖 line_coverage_pct = 行覆盖达标的深读文件数 / 深读文件数 分子 = union(read_ranges);分母 = 真实文件行数(非图谱 line_end,±1 偏差已修正) ③ 防伪抽验(主代理执行,非引擎) = 每组抽 2 文件 × 2-3 单元回读比对 note,每波 ≤40 次; 结果落盘 spot_check_*.json 并注入 review_data.spot_check(顶层字段), 由 verify-spot-check.ps1 强制校验"已执行"(引擎无法防假读,抽样是唯一手段) gate 取值: "overall" — 只看全库口径 "high_risk" — 只看高风险口径 "both" — 全库 + 高风险都要达标(保持原语义,向后兼容) "both+line" — both + 行覆盖 ≥95% + 单元完整性无缺口(whole-project 新默认,见下) ``` **目标值(固定 standard 档位)**:`gate="both+line"` 时要求**全库 ≥85% 且 高风险 ≥95% 且 行覆盖 ≥95% 且 单元完整性无缺口**(`line_target=95`,`unit_target=100` 差集为空)。`target_reached=false` → 报告顶部标 🔴 覆盖不足,并给出 `remaining_files_to_target`(还差几个文件)与 `priority_deep_read_files`(按风险权重降序的待深读清单)驱动补轮;`both+line` 额外返回 `line_gap_files` / `unit_gap_files` / `unit_exempt_files` 定位具体缺口文件。 **增量语义(include_prior=True)**:`save_coverage_index_tool` 在每轮报告后写 `.code-review-graph/coverage-index.json`(v2:相对路径 → per-file SHA + `ranges` 行区间)。SHA 来源为图谱 `nodes.file_hash`(一次 SQL 查询,零 subprocess——已修复 v2.5.0 逐文件 `git hash-object` 超时问题)。下一轮 `include_prior=True` 时自动复用 SHA 未变文件的深读状态**及行区间**,只要求重读新增/变更文件,多轮后增量归零即全覆盖。 > ⚠️ **图谱同 range 多节点噪声豁免**:TS/TSX 图谱可能对**同一行**解析出多个箭头函数节点(如 `const a = ..., x = ...` 各占一行但 range 相同),导致单元完整性差集永远非空(one-to-one 匹配下无解)。处理原则:**仅当该文件行覆盖已达 100%(或 ≥95%)**,判定为图谱解析噪声,将该文件从 `unit_gap` 中豁免并在报告显式标注"图谱同 range 多节点豁免",不要反复补读。`unit_gap_files` 的判定须结合具体 uncovered 单元名与行号人工确认是否噪声(对比源码该行是否有多个符号)。 > ⚠️ **0 字节空文件处理**:深读计划可能选中空文件(如 `web/src/components/evm/EvmConfigModal.tsx`,0 字节且无引用)。引擎对空文件报 `unreadable/empty file` 计入 `line_gap_files`。处理:验证文件字节数与引用情况,确认死文件后从深读清单移除,并作为一条 finding(死代码残留)记录在报告中。 #### 3.5.5 三道闸门 - **G1 名单完整性**:名单外文件须确认"被评估过"而非"被忽略",未被任何信号点名的文件记入**未深读文件清单**并在报告显式列出 - **G1.5 三件套质量门禁复核**(v2.5.1+):报告前复核三件套聚合结果——`line_gap_files ∪ unit_gap_files` 必须为空(已补读至空);**图谱同 range 多节点噪声豁免的文件须在报告中显式列出**(每文件注明"行覆盖已 100%,图谱同 range 多节点豁免"),0 字节空文件须从清单移除并单列 finding;`verified_files` 与 `coverage_tool` 的 `deep_read_files` 一致;防伪抽验记录(抽了几组/几个单元/有无假读)附入报告,并由 `verify-spot-check.ps1`(Step 8.8)强制校验 - **G2 抽检执行**:用 `coverage_tool` 返回的 `silent_files` 随机抽 15% 深读;抽检中发现 ≥1 个 major → 该文件升级为全量深读,并触发同社区/同类文件追加抽检一轮;抽检记录附入报告(抽了几份、发现几个 major、有无升级) - **G3 覆盖度收尾**:`coverage_tool` 自动计算,`gate="both+line"` 时全库 ≥85% 且 高风险 ≥95% 且 行覆盖 ≥95% 且 单元完整性无缺口才算达标,低于目标在报告顶部告警 #### 3.5.6 执行前的前置检查 运行覆盖度流程前先确认图谱健康——调用 `community_health_tool`: 1. `attribution_pct` = `nodes.community_id` 非空 / 非 File 节点数 的百分比 2. `needs_postprocess=true`(归属率 <90%)→ 先跑 `code-review-graph postprocess` 重建社区归属,再开始审查 > 已知问题:`incremental_detect_communities` 在 nodes 重建后可能因 `community_id` 全 NULL 而判定"无社区受影响"跳过写回,导致 `communities.size` 与 `nodes.community_id` 失同步。`community_health_tool` 可检出该状态,遇此情况用全量 `postprocess` 修复。 #### 3.5.7 报告覆盖度透传(防 0/0 渲染) `coverage_tool` 的返回值必须**完整透传**进 `generate_report_tool` 的 `review_data.coverage`(全部字段:`coverage_pct`/`high_risk_coverage_pct`/`grade`/`deep_read_count`/`total_files`/`high_risk_total_files`/`high_risk_deep_count`/`deep_read_weight`/`total_weight`/`target_reached`/`target`/`gate`/`remaining_files_to_target`/`remaining_weight_to_target`/`priority_deep_read_files`/`uncovered_files`/`silent_files`/`note`)。**不要手挑子集**——`build_report_data` 只读固定键名,缺字段会导致报告覆盖度区块渲染成 `0/0` 或 `N/A`。报告会自动渲染 `## 覆盖度` 区块。 --- ### 3.6 并行深读流水线(whole-project 强制) > 主上下文无法逐文件深读 500+ 文件;必须用并行 explore 子代理分组深读,否则全库覆盖永远卡在 2-5%。覆盖率为**文件数口径**,且每个深读文件需通过**三件套质量门禁**(单元完整性 / 行覆盖 / 防伪抽验,见 §3.5.4)。 ### 3.6.1 流程 ```text a. deep_read_plan_tool(target_coverage=85, include_prior=True) → 返回 groups[{name, weight, files[]}] + remaining_files (引擎按风险权重 + 目录贪心分组,每组 ≤40 文件;include_prior 排除上轮已深读未变更文件) b. 按组并行派发 explore 子代理(每次 4-6 个,分多批),每组深读全部文件,返回(落盘到临时目录): - outputs[]:每文件含 path / total_lines / read_ranges[[s,e]..] / semantic_units[{range,kind,name,note}] / findings[] - findings[]:每条含 severity/category/confidence/file:line/message/fix 【证据铁律】无 file:line 证据的条目视为未深读 → 该文件须重读 c. 每波子代理完成后跑三件套门禁(引擎原生支持): coverage_tool(deep_read_files=, gate="both+line", file_read_ranges=<{rel:[[s,e]..]}>, file_semantic_units=<{rel:[...]}>) → line_gap_files ∪ unit_gap_files → 补读队列;verified → 计入本轮 deep_read_files (引擎不可用时兜底:python skills/project-review/scripts/aggregate_deep_read.py <落盘目录>) d. 防伪抽验:每组抽 2 文件、该文件抽 2-3 个语义单元回读比对 note(每波 ≤40 次); 结果落盘 spot_check_.json,抽到假读 → 该组重读并升级抽验率 e. 主代理合并全部 verified_files(去重)→ coverage_tool(deep_read_files=<全清单>, gate="both+line", include_prior=True) 复算 (include_prior 自动复用跨轮索引中 SHA 未变文件及其 read_ranges) f. 未达标(文件数 <85%/<95% 或行覆盖 <95% 或单元有缺口)→ 对 priority_deep_read_files / line_gap_files / unit_gap_files 补派 1-3 个子代理 → 循环直至全达标 g. G2:对 silent_files 随机抽 15% 深读(见 §3.5.5) h. 报告生成前:聚合全部 spot_check_*.json → 注入 review_data.spot_check(顶层字段) i. 报告生成 + 命名自检后:verify-spot-check.ps1(Step 8.8)→ save_coverage_index_tool(deep_read_files=, file_read_ranges=) 写跨轮索引 ``` ### 3.6.2 子代理分组参考(AuraSpace 实测,约 508 文件) | # | 组 | 约文件数 | | --- | --- | --- | | 1-2 | `server/src/api`(1/2、2/2) | 60 | | 3-4 | `server/src/services`(1/2、2/2) | 66 | | 5 | `server/src/domain` + `deepwiki` + `infrastructure` | 88 | | 6 | `server/tests` + `bin` + `models` | 17 | | 7-8 | `web/src/views`(1/2、2/2) | 60 | | 9-11 | `web/src/components`(1/3、2/3、3/3) | 110 | | 12 | `web/src/store` + `utils` + `api` | 50 | | 13 | `web/tests` + `scripts` + SQL | 57 | > 实际分组以 `deep_read_plan_tool` 返回为准;每次并行 **4-6 个**子代理,其余排队,避免 MCP 并发压力与上下文风暴。 ### 3.6.3 质量控制与防伪 | 风险 | 对策 | | --- | --- | | 子代理"声称读了"但没真读 | 强制三件套字段(total_lines/read_ranges/semantic_units)+ findings 带行号证据;聚合脚本按①单元差集+②行并集双校验;主代理③抽样回读 | | 子代理漏读文件 | outputs 与分组清单 diff,漏读文件计入覆盖率缺口,触发补轮 | | 子代理宽 range 冒充全读 | 单元完整性 one-to-one 匹配:一个上报 range 只能覆盖一个单元,无法用整文件 range 覆盖所有单元 | | 各子代理口径不一 | 统一八分类 + CRITICAL 子轮 + severity/confidence 标准(子代理 prompt 模板) | | 增量掩盖新代码 | `include_prior=True` 按 per-file SHA 判定;变更文件自动失效重读 | | 并发压力 | 每批 4-6 个并行,其余排队;batch_size 40 控制单组体量 | | 主上下文被大 JSON 撑爆 | 落盘机制:子代理写临时文件,主代理只读聚合摘要 | ### 3.6.4 "深读"的含义边界 - **文件级保证(可量化)**:`deep_read_files` 是文件数口径,覆盖 85% 表示 85% 的文件被"读过"(高风险子集要求 95%) - **行级保证(v2.5.1 起可量化)**:`read_ranges` 的并集 / 真实文件行数 ≥95%;分母为真实文件行数(图谱 `line_end` 有 ±1 偏差,已弃用)。子代理对每个文件上报 `total_lines` + `read_ranges` + `semantic_units`,聚合脚本/引擎逐文件校验 - **单元级保证(v2.5.1 起可量化)**:`semantic_units` 与图谱 Function/Class/Test 节点差集必须为空(one-to-one 匹配,宽 range 不能冒充);巨型文件(最大单元行占比 >80%)豁免,仅按行覆盖校验 - **防伪机制**:findings 必须带 `file:line` 证据;主代理每轮 ≤15 次单元回读(引擎无法防假读,抽样是唯一手段);抽到假读 → 该组重读并升级抽验率 ### 3.6.5 跨轮增量(多轮累积) - 每轮报告后 `save_coverage_index_tool` 写 `.code-review-graph/coverage-index.json`(v2:相对路径 → per-file SHA + `ranges` 行区间) - SHA 来源为图谱 `nodes.file_hash`(一次 SQL 查询,零 subprocess;已修复 v2.5.0 逐文件 `git hash-object` 超时) - 下一轮 `deep_read_plan_tool` / `coverage_tool` 传 `include_prior=True` 自动复用 SHA 未变文件及其行区间 → 增量任务 = 新增文件 + 变更文件 - 多轮后增量归零即实现全库全覆盖,避免每轮从 2-5% 起步 ### 3.6.6 行级覆盖率执行步骤(新窗口 / 全新审查) > 本节给出"行级覆盖率如何落地到一次实际审查"的**分步操作**。三件套门禁中,行覆盖是唯一有精确数值的维度(`union(read_ranges) / 真实行数 ≥95%`),本节聚焦它的完整执行链路;单元完整性(差集=空)与防伪抽验在 §3.6.1/§3.6.3 已述。 **行级覆盖率的口径(引擎 `scoring.py::compute_coverage`)**: ```text 行覆盖率(每文件) = |union(read_ranges)| / len(Path(repo/f).read_text().splitlines()) 行覆盖达标 = 行覆盖率 >= 95% (line_target,可参数覆盖) line_coverage_pct = 行覆盖达标的深读文件数 / 深读文件数 line_gap_files = 行覆盖率 <95% 的文件 [path, coverage_pct, total_lines, covered_lines] ``` - **分母 = 真实文件行数**(读取源文件,`_real_line_count` 带缓存)——**不用图谱 `line_end`**(实测 ±1 偏差) - **分子 = `read_ranges` 并集**——子代理实际上报的读取区间(分段读即天然分段) - **无 ranges 上报的文件**:行覆盖视为通过(不设硬卡),仅依赖单元完整性 + 抽验 **执行步骤(主代理在全新会话中)**: ```text Step 0 前置(新窗口) - 确认无残留进程:Get-Process code-review-graph → 无输出 - 首次调用 get_minimal_context_tool 会拉起 MCP serve(加载新引擎,含 both+line) Step 1 建图 + 概览 - build_or_update_graph_tool() 图谱最新 - get_minimal_context_tool(task="project review") 全库概览 Step 2 架构 + 高风险扫描(同旧流程,产出风险权重信号) Step 3 客观评分 - score_review_tool(all_files=True) + community_health_tool(前置健康检查) Step 4 深读分组 - deep_read_plan_tool(target_coverage=85, include_prior=True) → groups[{name, weight, files[]}](≤40/组) Step 5 并行派发子代理(严格模式) - 每次 4-6 个 explore 子代理,prompt 见 references/deep-read-pipeline.md §二 - 每文件必须返回 total_lines / read_ranges[[s,e]..] / semantic_units[{range,kind,name,note}] - semantic_units 必须逐一列出图谱全部单元(含 Props/interface/小函数,严格模式) - 结果落盘到临时目录(勿回传大 JSON) Step 6 聚合脚本(每波后) - python skills/project-review/scripts/aggregate_deep_read.py <落盘目录> - 输出 verified_files / line_gap_files / unit_gap_files / unit_exempt_files - 行覆盖判定就在这里:line_gap_files 列出所有 <95% 的文件 Step 7 引擎门禁(行级覆盖率正式数值) - coverage_tool(deep_read_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 - ⚠️ 必须传 file_read_ranges/file_semantic_units,否则行/单元覆盖不计算 Step 8 补轮(禁止降级) - line_gap_files ∪ unit_gap_files 非空 → 对缺口文件重派子代理补报/重读 - 禁止回退 gate="both" 静默跳过三件套;必须补至空,或报告显式标注 🔴 Step 9 报告 - generate_report_tool(review_data.coverage = coverage_tool 全量透传) - "## 覆盖度" 区块须含行覆盖/单元覆盖(line_coverage_pct/unit_coverage_pct) Step 10 持久化 - save_coverage_index_tool(deep_read_files=, file_read_ranges=) - 写 v2 索引(sha + ranges,SHA 来自 nodes.file_hash,<1s) ``` **新窗口首轮验证(Step 0 后建议先跑一次冒烟)**: ```text coverage_tool(deep_read_files=["server/src/services/auth_service.rs"], gate="both+line", file_read_ranges={"server/src/services/auth_service.rs": [[1,66]]}, file_semantic_units={"server/src/services/auth_service.rs": [全部 6 单元]}) → 期望 line_coverage_pct=100, unit_coverage_pct=100, line_gap_files=[] (即 V2.1 E2E 验证通过,确认引擎加载了新代码) ``` **常见坑**: - 子代理漏 `read_ranges` → 该文件行覆盖不计算(不算 gap 但也不验证),依赖单元完整性兜底;严格模式应要求 read_ranges 覆盖整文件 - 大文件分段时 read_ranges 各段要有重叠边界或连续覆盖,避免 1 行缝隙拉低覆盖率 - 全库 508 文件行覆盖聚合约 1-2s(分母读取带缓存);`save_coverage_index` 用 file_hash 后 <1s(不再有逐文件 git subprocess 超时) ### 3.6.7 实测踩坑与绕行方案(2026-08-18 全库审查实战记录) > 本节是 508 文件全库审查(`full-project-review-2026-08-18-144500`)实测中暴露的、文档正文未覆盖的坑与对应解法。深读流水线各环节的坑按出现顺序记录。 | # | 环节 | 坑 | 症状 | 解法 | | --- | --- | --- | --- | --- | | 1 | 子代理输出落盘 | 子代理 JSON 是**容器格式** `{"group":..., "outputs":[{path, read_ranges, semantic_units, findings}]}`,而 `aggregate_deep_read.py` 期望**单文件记录**(每条记录的 `path` 在顶层) | 聚合脚本 `verified=0`、全部文件被跳过,且无任何报错 | 先**解包**:读容器 → 把 `outputs` 数组逐条拆成独立 JSON 文件(`batchN_idx_.json`)→ 再跑聚合脚本。可用一段 Python 脚本完成,见下 | | 2 | 聚合脚本 | 子代理用 `write` 写 JSON 可能带 **UTF-8 BOM** | 报错 `Unexpected UTF-8 BOM (decode using utf-8-sig)` | 聚合前批量剥 BOM:读字节,若前 3 字节为 `EF BB BF` 则写入 `bytes[3:]` | | 3 | 单元完整性补读 | 首轮聚合 `unit_gap` 可达 **92 个文件**(子代理漏报 serde `visit_*` 小方法、几行的 struct/Props interface) | `unit_gap_files` 非空 → 三件套门禁不通过 | **不要整文件重读**。生成 `gap_manifest.json`(含每文件 `uncovered` 单元及精确 range),派 1-2 个"补读子代理"按 range 精准读取缺失单元、产出 `supplement.json`,主代理按 `(path, range)` 合并回原 payload 后再聚合 | | 4 | 单元完整性 | 图谱对**同一行解析出多个箭头函数节点**(如 `AgentConfigPanel.tsx` 的 `a`/`x` 同 range 420;`DataPermissionManagement.tsx` 的 `d`/`childrenOf` 同 range 76) | 该文件 `total_units` 高但永远 `covered_units` 差 1-3 个,**one-to-one 匹配不可能满足**(一个 range 只能匹配一个单元) | 判断为图谱解析噪声:文件行覆盖已 100% 即视为已深读,在报告中显式标注"该文件存在图谱同 range 多节点,单元完整性豁免";不要反复重读 | | 5 | 深读清单 | 深读计划可能选中 **0 字节空文件**(如 `web/src/components/evm/EvmConfigModal.tsx`) | `line_gap_files` 报 `unreadable/empty file`,且该文件无任何引用(死文件残留) | 验证文件大小与引用:空文件 + 无引用 → 从深读清单移除,作为一条 finding(死代码)单独记录 | | 6 | 补读子代理 | 补读子代理若只返回"补了 X 个单元"的**一句话**而不落盘补充 JSON | 主代理拿不到补充的 semantic_units,无法合并 | 强制补读子代理也必须**落盘** `supplement*.json`(结构化 range+note),主代理脚本合并后重跑聚合 | | 7 | MCP 大 JSON | `dedupe_findings_tool` / `generate_report_tool` 直传 400+ 条 findings 时 **MCP JSON 解析失败**(`Invalid escape character` / `Expected '}'`) | 工具调用直接报 invalid input,报告无法生成 | **绕行**:直接在 Python 里调用引擎函数 `code_review_graph.tools.generate_report_func(review_data, output_path=...)` / `coverage_func(...)`(`sys.path.insert(0, )` 后 import),输出与 MCP 工具完全一致(见 §4.5) | | 8 | 报告统计 | MD 报告的"问题统计"只读 `counts` 的 **`critical` / `informational` 键**(不读 blocker/major/minor) | 传 `{"blocker":20,"major":141,"minor":315}` 时统计显示 "20 严重 · 0 次要",major 数丢失 | `counts` 传 `{"critical":, "major":, "informational":}` 才能正确渲染 | **解包 + 剥 BOM 参考脚本(主代理每轮可复用)**: ```python # 1) 剥 BOM for f in glob.glob(dir + "/*.json"): b = Path(f).read_bytes() if b[:3] == b"\xef\xbb\xbf": Path(f).write_bytes(b[3:]) # 2) 解包 outputs → 单文件记录 data = json.load(open(batch, encoding="utf-8")) for i, rec in enumerate(data["outputs"]): out = dir / f"{basename}_{i}_{rec['path'].replace('/','_')}.json" out.write_text(json.dumps(rec, ensure_ascii=False), encoding="utf-8") ``` **合并 supplement 参考脚本**: ```python bypath = defaultdict(list) # path -> [semantic_units...] for s in supp1 + supp2: bypath[s["path"]] += s["semantic_units"] for rec in payloads: # 每个单文件记录 if rec["path"] in bypath: have = {tuple(u["range"]): u for u in rec.get("semantic_units", [])} for u in bypath[rec["path"]]: have.setdefault(tuple(u["range"]), u) # 按 range 幂等合并 rec["semantic_units"] = list(have.values()) ``` **教训**:深读流水线的"三件套门禁"把质量控制在引擎侧,但**子代理与主代理之间的数据契约(容器格式、BOM、补读落盘)是纯人工编排**,任何一处契约不一致都会让 verified 计数归零或三件套不达标。建议每波子代理完成后先跑一次聚合脚本看 `verified` 数,而非等到全部结束才校验。 --- ## 4. 审查调用的 MCP 工具 ### 4.1 核心审查工具 | 工具 | 功能 | | --- | --- | | `score_review_tool` | **客观 Layer-2 审查指标**:SQL 风险、异常分支覆盖、代码冗余率、高风险场景密度、漏洞启发式 5 项,每项带 good/warn/fail 分级、阈值与证据。支持 `all_files` 全量评分、`include_churn`。`llm_judged` 列出需 LLM 补判的指标 | | `dedupe_findings_tool` | **Finding 合并去重**:按 `path:line:category` 指纹合并;多源确认置信 +1(上限 10);低置信移附录或抑制;计算 PR 质量分 `max(0, 10 - (critical×2 + informational×0.5))`;支持抑制历史已跳过项 | | `generate_report_tool` | **审查报告生成**:中文 HTML + Markdown(默认 `format="both"`);`output_path` 传归档目标文件名(含 `docs/reviews/` 前缀,不含扩展名) | | `coverage_tool` | **覆盖度计算**(v2.5.0 / 三件套 v2.5.1):传入 `deep_read_files` + `gate`(默认 `high_risk`,可选 `overall` / `both` / `both+line`),引擎自动算双重文件数口径(全库 / 高风险)、未深读清单、静默文件清单、达标状态与待补深读优先级。`gate="both+line"` 时额外支持 `file_read_ranges` / `file_semantic_units`,返回行/单元覆盖三件套(`line_coverage_pct` / `unit_coverage_pct` / `line_gap_files` / `unit_gap_files` / `unit_exempt_files`) | | `deep_read_plan_tool` | **深读分组规划**(v2.5.0):`deep_read_plan_tool(target_coverage=85, include_prior=True, batch_size=40)`(**无 `gate` 参数**),按风险权重 + 目录贪心生成 `groups[{name, weight, files[]}]` + `remaining_files`,主代理据此并行派发子代理 | | `save_coverage_index_tool` | **跨轮覆盖索引持久化**(v2.5.0 / v2 索引):把本轮深读清单(file + per-file SHA + 可选 `ranges` 行区间)写入 `.code-review-graph/coverage-index.json`,供下一轮 `include_prior=True` 增量复用。v2.5.1 起 SHA 来源为图谱 `nodes.file_hash`(零 subprocess,根治逐文件 `git hash-object` 超时) | | `community_health_tool` | **社区归属健康检查**(v2.4.0+):返回 `nodes.community_id` 归属率与 `needs_postprocess`,前置检查用 | ### 4.2 图谱上下文工具 | 工具 | 用途 | | --- | --- | | `build_or_update_graph_tool` | 确保图谱最新 | | `get_minimal_context_tool` | 全库概览压缩(节点/边/社区/风险/受影响的流),project-review 主上下文第一步 | | `get_review_context_tool` | 变更文件 + blast radius + 源码片段 + 审查指引 | | `detect_changes_tool` | 风险评分 + 变更函数 + 测试缺口 + 受影响流 | | `get_affected_flows_tool` | 受影响执行流 | | `query_graph_tool` | 调用方/被调用方/测试/继承查询 | ### 4.3 图谱全景工具(project-review 用) | 工具 | 用途 | | --- | --- | | `get_architecture_overview_tool` | 社区耦合总览 | | `list_communities_tool` | 模块清单 | | `get_knowledge_gaps_tool` | 结构弱点:孤立节点、未测试热点 | | `get_hub_nodes_tool` | 最连接节点(架构热点) | | `get_bridge_nodes_tool` | 架构瓶颈 | | `find_large_functions_tool` | 超大函数/类 | | `get_surprising_connections_tool` | 意外跨社区耦合 | | `semantic_search_nodes_tool` | 按关键词定位代码(单功能) | | `get_impact_radius_tool` | 单功能的 blast radius | ### 4.4 工具调用链 ```text unified-review(diff): build_or_update_graph → get_review_context → detect_changes → score_review_tool → [specialist 子代理] → dedupe_findings_tool → generate_report_tool project-review(全项目): build_or_update_graph → get_minimal_context → 架构全景 + 高风险定位(6 个图谱工具) → score_review_tool(all_files=True) + community_health_tool(前置检查) → deep_read_plan_tool → [4-6 并行 explore 子代理 × N 批] → coverage_tool(gate="both+line") → [未达标]补轮 → [G2]silent 抽检 15% → dedupe_findings_tool → generate_report_tool(coverage 完整透传) → verify-report.ps1(命名自检) → save_coverage_index_tool(跨轮索引) project-review(单功能): build_or_update_graph → get_minimal_context → semantic_search_nodes / query_graph 定位功能文件 → get_impact_radius → score_review_tool(changed_files=<功能文件>) → coverage_tool(deep_read_files=<功能文件>) → dedupe_findings_tool → generate_report_tool ``` ### 4.5 引擎函数直调绕行(MCP 大 JSON 失败时的兜底) `dedupe_findings_tool` / `generate_report_tool` / `coverage_tool` 通过 MCP 传输 JSON 参数,当 findings 超过数百条(如全库审查 476 条)时可能因 **MCP 参数解析失败**(`Invalid escape character` / `Expected '}'`)而无法调用。此时可**绕开 MCP 直调引擎 Python 函数**,输出与 MCP 工具完全一致: ```python import sys, json sys.path.insert(0, r"D:\code-review-graph\code-review-graph-main") # CRG 引擎本地路径 import code_review_graph.tools as t # 等价于 generate_report_tool res = t.generate_report_func( review_data, # 与 MCP 版同构的 review_data dict output_path=r"D:\AuraSpace\docs\reviews\full-project-review-2026-08-18-144500", repo_root=r"D:\AuraSpace", format="both", ) # 等价于 coverage_tool(gate="both+line") res = t.coverage_func( deep_read_files=paths, gate="both+line", file_read_ranges=ranges_map, # {rel: [[s,e]..]} file_semantic_units=units_map, # {rel: [{range,kind,name,note}..]} repo_root=r"D:\AuraSpace", ) ``` > 触发条件:工具调用返回 invalid input / JSON Parse error,且 payload 确认超过 MCP 承载上限(实测 ≥400 条 findings 时出现)。直调入口统一为 `code_review_graph.tools` 下的同名函数;`coverage_func` 的签名见 §3.6.6 的 `compute_coverage` 说明。 ### 4.6 深读子代理类型选择 深读子代理使用 **`explore` 类型**(与 project-review skill 及实际审查一致)。子代理需**写 JSON 落盘**回传三件套数据(容器格式 `{group, outputs[]}`,每条含 path/read_ranges/semantic_units/findings),因此派发前须确认子代理工具集包含 **write 落盘能力**。若子代理无法落盘,主代理拿不到三件套数据,verified 计数会归零。 --- ## 5. 5 项客观指标详解 `score_review_tool`(unified-review Step 3 / project-review Step 4)计算 5 项客观 Layer-2 指标。这些指标由**静态正则启发式**实现(源码:`code_review_graph/scoring.py`),扫描变更文件的行级文本(`_iter_source_lines`),不执行代码、不理解语义,因此**会产生误报和漏报,需人工核实后再行动**。阈值定义在 `scoring.py` 的 `THRESHOLDS`。 ### 5.1 指标总览 | 指标 | 值类型 | 评级方向 | good | warn | fail | | --- | --- | --- | --- | --- | --- | | `SQL 注入风险` | 命中行数 | 越低越好 | `= 0` | `1-2` | `≥ 3` | | `异常分支覆盖` | 异常行占比 % | 越高越好 | `≥ 50%` | `30%–50%` | `< 30%` | | `代码冗余率` | 重复行占比 % | 越低越好 | `≤ 10%` | `10%–20%` | `> 20%` | | `high_risk_density` | 覆盖行占比 % | 越高越好 | `≥ 90%` | `≥ 70%` | 其余;无高风险行则为 `na` | | `vulnerability_risk` | 命中行数 | 越低越好 | `= 0` | `1-2` | `≥ 2` | **整体客观评级取 5 项中最差项**(`worst`,任一 fail → 整体 FAIL;否则任一 warn → WARN;全 good → GOOD)。 ### 5.2 这些指标有什么用处 核心用途一句话:**把"凭感觉"的审查,变成"有证据、有优先级"的审查清单**。它们不是给代码打分的考试成绩,而是**审查者的导航仪**——告诉你这份代码里"哪些行值得停下来细看",并给这个判断一个可复现的数字证据。 | 指标 | 实际帮你做什么 | 没有它的代价 | | --- | --- | --- | | `SQL 注入风险` | 直接给出行号,逐个确认"这些是不是注入点",其余 SQL 不用看 | 要么漏看注入,要么浪费时间通读所有 SQL | | `异常分支覆盖` | 一眼知道错误处理厚薄,决定是否要重点查边界 | 不知道这模块错误处理是优是劣,全靠碰运气 | | `代码冗余率` | 告诉你有没有"复制粘贴三份"的代码,决定是否值得重构 | 靠肉眼扫重复,扫不全 | | `high_risk_density` | 标出所有含并发/事务/权限/缓存的行,提醒"这些别跳过" | 容易忽略数据一致性风险点 | | `vulnerability_risk` | 机械扫硬编码密钥/XSS/shell 特征,做安全快筛 | 靠人工记忆检查常见漏洞模式 | **为什么这个设计关键**:审查最贵的成本是**注意力**。一份 diff 或一个功能可能上千行,逐行细看不现实。这 5 项指标把注意力压缩到三件事: - **哪些行**(evidence 给出 file:line) - **怀疑什么**(sql/vulnerability 给出关键词模式) - **薄弱在哪里**(exception_coverage 低 = 错误处理该补查) **注意"触发器而非结论"**:命中 ≠ 缺陷。每个指标都带 `evidence` 供逐一人工核实,`score_review_tool` 的 note 反复强调这一点(如 `sql_risk=1` 可能是 JSX 误报)。 **与 git diff 无关**:指标扫描的是传入 `changed_files` 清单的**文件全文**,不依赖 git diff。unified-review 的清单来自 git diff,project-review 的清单来自用户指令 → `semantic_search`/`query_graph` 聚合(feature)或 `all_files=True`(whole-project),二者喂给指标的只是不同的文件来源,指标引擎同一套。project-review 的只读、不碰 git 约束天然成立。 **分工边界**:指标只覆盖"机械化风险",抓不住 IDOR、越权、竞态、LLM 信任边界等业务逻辑缺陷——那些由 Layer 1 人工链路分解 + specialist 子代理覆盖(见 §5.3 与 §5.2.5 注意)。 ### 5.3 各指标收集方式与评分标准 #### 5.3.1 `sql_risk` — SQL 注入风险 - **收集方式**:用 6 条正则扫描每行,匹配字符串插值/未参数化 SQL 特征:`"SELECT ..." + 变量`、`f"...{var}"` 含 SQL、`exec("SELECT...`、`WHERE x = '" + var`、`.format(...)` 含 SQL、`% "..." % (...)` 含 SQL 等。 - **评级**:计数命中行数,0 → good,1-2 → warn,≥3 → fail。 - **注意**:命中只是警告不是证据。前端 JSX/模板字符串含 SQL 字样易误报(如后端明明全部参数化绑定,报告仍可能报 1 处来自 `.tsx` 的"风险"),需逐一确认上下文,必要时跑 `EXPLAIN` 评估性能。 #### 5.3.2 `exception_coverage` — 异常分支覆盖 - **收集方式**:用 10 条正则统计"异常/错误路径"行数(`try`、`except`、`catch(`、`raise`、`throw`、`if err != nil`、`if ... error`、`.catch(`、`else { return None` 等)。 - **计算**:`异常行 / 总行 × 100%`。经验基线来自"每 2 条正常路径约有 1 条异常路径"。 - **评级**:≥50% → good,30%-50% → warn,<30% → fail。 - **注意**:阈值命名易误解——`warn_min=50` 实际是 good 的及格线,`good_min=30` 是 warn 的下限。该指标对 `?` 一元错误传播(Rust)、`Result` 链等"无显式异常关键字"的代码会低估,需人工补查。 #### 5.3.3 `redundancy_rate` — 代码冗余率 - **收集方式**:将每行标准化为签名(去空白、数字→`N`、字符串→`"s"`),统计**在 ≥3 处出现相同签名**的行数。 - **计算**:`重复行 / 总行 × 100%`。 - **评级**:≤10% → good,10%-20% → warn,>20% → fail。 - **注意**:签名长度 <24 字符的短行被忽略。重复的函数参数签名(如 `State(state)`/`Extension(claims)`)会高频命中,属结构性信号而非缺陷。 #### 5.3.4 `high_risk_density` — 高风险场景密度 - **收集方式**:用 7 条正则标记"高关注行":并发(async/await/thread/mutex)、事务(transaction/commit/rollback)、原子性(atomic/race)、SQL 关键字、锁、缓存失效(cache/invalidate/evict)。 - **计算**:`覆盖行 / 相关行 × 100%`——因所有相关行都被计入"覆盖",实际**恒为 100%**。无高风险行时返回 `na`(不适用),不计入整体评级。 - **评级**:≥90% → good,≥70% → warn,其余 fail。 - **重要**:这是**审查关注信号,不是正确性评分**——只提示"哪些行含并发/事务/安全模式需要人工验证",值高不表示代码好。 #### 5.3.5 `vulnerability_risk` — 漏洞风险 - **收集方式**:用 6 条正则扫描 OWASP/密钥特征:硬编码 `password/api_key/secret/token`、`eval/exec(`、`subprocess(shell=True)`、`innerHTML/dangerouslySetInnerHTML/v-html`、``、`