# Worker B:7-27 代码/测试缺口审查 > 审查时间:2026-05-20 > 审查范围:`doc.rs` + `hermes_tools.rs` + `manifest.rs` > 分级:🟢 已满足 / 🟡 可作为后续增强 / 🔴 必须修复才能归档 --- ## 检查点 1:`blocks_to_markdown` 是否能从 legacy `content`/`contentNodes` 读取文本 **结论:🟢 已满足** `legacy_block_text()` 函数 (`doc.rs:973-986`) 逐级回退: ```rust fn legacy_block_text(block: &Value) -> String { if let Some(text) = block.get("text").and_then(Value::as_str) { ... } if let Some(content) = block.get("content") { return inline_text_value(content); } if let Some(content_nodes) = block.get("contentNodes") { return inline_text_value(content_nodes); } } ``` `block_text()` (`doc.rs:1265-1270`) 只读 `text` 字段用于 markdown 序列化,但 `build_page_content` 中的 `update_legacy_block_text_for_markdown_edit` 正确使用了 `legacy_block_text` 做比较,且写回时通过 `replace_single_inline_text_value` 递归处理 `content`/`contentNodes` 中的 `text` 字段。 **证据**: `inline_text_value()` (`doc.rs:988-1000`) 递归处理 Array/Object/String 三种形态。 --- ## 检查点 2:注释格式是否与 7-27 文档一致,是否保留 `revisionRef` **结论:🟡 可作为后续增强** 当前格式: ``` (heading 块) ``` 7-27 §3.3.1 设计为: ``` (with level) ``` **差异**:`revisionRef` 不写入注释,而是从原始 `block.revisionRef` 读取。解析器 `extract_block_comment()` 虽然支持 `type:rev` 格式(`splitn(3, ':')` 后 `parts[2]` 可以包含 rev),但 `blocks_to_markdown` 写回时不输出 rev。 **影响**: 低。`revisionRef` 在整个 `build_page_content` 路径中被正确继承(`doc.rs:685-690`),只是注释里看不到。不阻碍归档,可后续增强。 --- ## 检查点 3:`full_content` 是否不再被拒绝,是否保留复杂块 **结论:🟢 已满足** - `doc_markdown_edit` 中 `full_content` 转为 `{search: current_md.trim(), replace: full.trim()}` 单个 operation,进入新路径 - `parse_final_markdown_to_blocks` 对无注释行创建 `is_new=true` 的 paragraph 块 - `build_page_content` 追加新块并保留原始复杂块(`is_complex_markdown_original_block`) - 测试 `test_build_page_content_full_content_replaces_old_text_blocks_but_keeps_complex_blocks` 验证复杂块保留 - 集成测试 `hermes_tools_markdown_edit_online_full_content_rejects_unsafe_block_mapping` 注释确认:"7-27 修复后:full_content 不再被拒绝" --- ## 检查点 4:`dryRun` 是否不写入 **结论:🟢 已满足** `doc_markdown_edit` 中: ```rust if input.dry_run == Some(true) { return Ok(json!({ "written": false, "dryRun": true, "diff": changed_blocks })); } ``` 本地文件路径也有独立的 `dryRun` 分支只返回预览。 测试 `hermes_tools_markdown_edit_local_dry_run_does_not_write` 验证。 --- ## 检查点 5:selection / allowedTargetBlockIds 是否阻止越界写入 **结论:🟢 已满足** `ensure_markdown_changed_blocks_within_allowed()` (`doc.rs:1050-1073`) 在写回前检查每个 `changedBlocks` 中的 `blockId` 是否在 `allowed_block_ids` 中。越界时返回 `mnote_markdown_edit_target_out_of_scope` 错误。 测试 `hermes_tools_markdown_edit_rejects_selection_out_of_scope` 验证。 `selected_block_ids()` (`doc.rs:1075-1094`) 从 `selectedBlockIds` / `allowedTargetBlockIds` / `selectedBlockId` / `blockId` 四个参数中收集。 --- ## 检查点 6:旧 `build_block_ops_from_markdown_edit` 推导路径 **结论:🟢 已满足(已退役)** `doc.rs:1911` 末尾注释: ```rust // 退役:7-27 改为 final_md → blocks → execute_page_body_save_from_aggregate 直接写回 ``` 函数体仍保留但不再被任何调用者引用(`doc.rs` 内只有一处调用已在步骤 4 中被替换)。可用 `cargo +nightly deadlinks` 确认死代码状态,但已足够安全。 --- ## 检查点 7:测试覆盖是否足以归档 **结论:🟢 已满足** ### 单元测试覆盖(doc.rs 内) | 测试 | 验证点 | |------|--------| | `test_search_replace_exact` | 精确匹配替换 | | `test_search_replace_normalized_chinese_byte_boundaries` | 中文忽略空白 + byte 边界保留 | | `test_search_replace_full_content` | 全文替换 = 全文精确匹配 | | `test_blocks_to_markdown_with_ids` | 块 ID 注释输出 | | `test_blocks_to_markdown_heading` | heading 类型 + level 注释 | | `test_blocks_to_markdown_complex_block_uses_raw_marker` | 复杂块 raw marker | | `test_build_page_content_full_content_replaces_old_text_blocks_but_keeps_complex_blocks` | full_content 新块 + 复杂块保留 | | `test_build_page_content_preserves_missing_original_text_block_when_some_ids_remain` | ID 消失时保留原始块 | | `test_build_page_content_preserves_unchanged_inline_content_nodes` | 行内 marks 保留(不变时) | | `test_build_page_content_preserves_single_inline_node_marks_when_text_changes` | 行内 marks 保留(文本改变时) | ### 集成测试覆盖(hermes_tools.rs) | 测试 | 验证点 | |------|--------| | `hermes_tools_markdown_edit_online_full_content_rejects_unsafe_block_mapping` | full_content 不再拒绝 | | `hermes_tools_markdown_edit_merges_same_block_operations` | 同块多次操作累积 | | `hermes_tools_markdown_edit_maps_normalized_search_to_block` | 非精确匹配 + changedBlocks 验证 | | `hermes_tools_markdown_edit_online_page_body_save_carries_revision_conflict_key` | revision/conflict key 传递 | | `hermes_tools_markdown_edit_rejects_no_applied_operations` | 空 operations 拒绝 | | `hermes_tools_markdown_edit_rejects_selection_out_of_scope` | selection 越界拒绝 | | `hermes_tools_markdown_edit_reports_empty_block_mapping_before_apply` | 空 block mapping 错误报告 | | `hermes_tools_markdown_edit_shared_read_is_forbidden` | 共享文档只读拒绝 | | `hermes_tools_markdown_edit_local_dry_run_does_not_write` | local dryRun | | `hermes_tools_markdown_edit_local_folder_writes_same_markdown_file` | local folder 写入 | | `hermes_tools_markdown_edit_local_requires_write_contract` | local write contract 要求 | ### 缺口:建议后续增强(非阻塞) 1. **无块注释的多段 markdown 还原**:当前行级解析器假设每行一个块;多段文本(如代码块)需 GFM fallback(§5.3 有分析但未实现) 2. **`revisionRef` 在注释中可见**:当前只在内存中保留,注释格式未输出 rev(§3.3.1 设计差异) 3. **`build_block_ops_from_markdown_edit` 函数体删除**:当前已退役但函数体仍在文件中 --- ## 总结 | 分级 | 数量 | 项目 | |------|------|------| | 🔴 必须修复才能归档 | 0 | — | | 🟡 后续增强 | 3 | 注释写 rev, GFM fallback 多段文本, 死代码体删除 | | 🟢 已满足 | 7/7 | 全部检查点通过 |