158 lines
6.9 KiB
Markdown
158 lines
6.9 KiB
Markdown
# 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`
|
|||
|
|
|
|||
|
|
**结论:🟡 可作为后续增强**
|
|||
|
|
|
|||
|
|
当前格式:
|
|||
|
|
```
|
|||
|
|
<!-- block:{id}:{type} -->
|
|||
|
|
<!-- block:{id}:{type}:level={level} --> (heading 块)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
7-27 §3.3.1 设计为:
|
|||
|
|
```
|
|||
|
|
<!-- block:{id}:{type}:{revision_ref} -->
|
|||
|
|
<!-- block:{id}:{type}:{revision_ref}:level={level} --> (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 | 全部检查点通过 |
|