# 10 [done] 当前 mnote 项目 AI / Page Aggregate 定向 Review v1

> 更新时间：2026-05-17
>
> 执行状态：`done`
>
> 范围：当前主线中 Page Aggregate 单一真源、页面 AI 快速编辑、`mnote.doc.markdown_edit` 与 ACP / Hermes runtime 的源码级定向审查。
>
> 2026-06-01 口径补充，2026-06-06 收紧：本文是 2026-05-17 的历史 review 快照。文中“`mnote.doc.markdown_edit` 是简单正文编辑主路径”的结论已被后续 local-first agent 文件编辑控制面覆盖；当前 local-first 普通 Markdown 编辑主路径以 `AGENTS.md`、`ARCHITECTURE.md` 与 `design/07-ai/process/7-18-local-first-agent-file-editing-control-plane-v1.md` 为准，即授权文件引用 + allowed roots/files + agent 原生 patch/diff + watcher/BufferStore/Page Aggregate 同步。`mnote.doc.markdown_edit` 只保留为历史 online/cloud/compat 回归和结构校验对照，不作为 local-first 新 agent run 的推荐 fallback。

## 1. 本轮结论

本轮没有发现 Page Aggregate 读取主链重新回退到 Next compat builder 的证据。后续修复已进一步收口：AI context 的 page subtree 只读 Rust Page Aggregate projection，不再由前端本地构造第二份 page tree 真相。

本轮识别出的 `07-ai` markdown 编辑 P0 风险已修复：

- `mnote.doc.markdown_edit` 是简单正文编辑主路径，并被 `/api/page-ai/block-edit-workflow` 通过统一 mnote tool executor 调用。
- 中文归一化匹配不再把 UTF-8 byte index 当 char index。
- 同一块内多次 markdown_edit 会合并到最终 markdown 结果后再映射写回，避免后续 block op 覆盖前序修改。
- 在线写回在无法安全映射最终 markdown 时明确拒绝，避免 `ok=true` 伪成功。
- Hermes guidance、manifest、tool toggle、dryRun / idempotency、revision / conflictDetectionKey 与 Reasonix ACP payload 已同步到当前工具合同。

剩余长期方向仍是 Phase C review session / streaming apply；当前设计冻结，不属于本轮 bug 修复范围。

## 2. 发现的问题

### R-01 P0：中文归一化匹配使用 byte index 当 char index

关联缺陷：

- `bugs/07-ai/done/7-16-markdown-edit-normalized-search-byte-index-v1.md`

证据：

- `rust/crates/mnote-web/src/hermes_tools/doc.rs:920-936`

`search_replace` 的 Level 2 先在 `norm_line` 上用 `find()` 得到 UTF-8 byte offset，却把这个 offset 传给 `line.char_indices().nth(start)` 当作字符序号使用。中文、多字节标点或中英混排时，替换范围会偏移，可能替错片段或生成错误内容。

影响：

- 页面 AI 对中文正文的“忽略空白 / 全半角差异”替换不可靠。
- 该问题位于 `mnote.doc.markdown_edit` 的基础 search/replace 层，会同时影响在线文档和本地 `.md` 文件。

### R-02 P0：同一块内多次 markdown_edit 会被后续 block op 覆盖

关联缺陷：

- `bugs/07-ai/done/7-17-markdown-edit-same-block-multi-op-overwrite-v1.md`

证据：

- `rust/crates/mnote-web/src/hermes_tools/doc.rs:779-824`
- `rust/crates/mnote-web/src/hermes_tools/doc.rs:854-878`
- `rust/crates/mnote-web/src/hermes_tools/doc.rs:976-1008`

`doc_markdown_edit` 已经在 `md` 变量中顺序应用了所有 search/replace，但 Convex 写入时没有使用这个最终 markdown。它重新读取原始 Page Aggregate blocks，并对每个 operation 用原始 block 文本生成一个 replace block op。

如果两个 operation 命中同一个 block，第二个 block op 的 `content` 仍从原始文本计算，会覆盖第一个 operation 的结果。也就是说，markdown 层算出的最终结果与实际落库结果可能不一致。

影响：

- AI 一次请求中常见的“把同一段里的 A 改成 B，同时把 C 改成 D”可能只保留最后一次修改。
- 前端快路径仍可能显示“已通过页面 markdown 编辑快路径完成写入”，但正文只部分生效。

## 3. 修复状态

- `mnote.doc.markdown_edit` manifest 已补齐 required / write contract。
- 本地 `.md` 写入已尊重 dryRun / idempotency。
- 在线写回已使用最终 markdown 作为真源进行映射；无法安全映射时返回明确错误。
- `mnote.doc.apply_block_ops` 批量写入已补 revision / conflictDetectionKey 前置校验。
- `/api/page-ai/block-edit-workflow` 已从绕过 executor 改为复用统一 Hermes mnote tool executor。
- ACP Reasonix wrapper 已透传 actor/session/run/toolCall/trace/dryRun/idempotency/workspace/document 等上下文字段。

## 4. 后续边界

1. Phase C 的 review session / streaming apply 仍冻结，当前不扩新 AI 功能。
2. 复杂结构编辑继续走 `apply_block_ops` / `mnote.block.*`，不要把正文 search/replace 回退到结构性块工具。
3. Page Aggregate 仍需后续推进 EditorBlockDocument 原生落库真相。

## 5. 本轮验证

静态检查范围：

```bash
rg -n "markdown_edit|apply_block_ops|block-edit-workflow|local_rule|page_ai_workflow|mnote\\.doc\\.fetch" rust wolai-frontend scripts design/07-ai design/10-review --glob '!rust/target/**' --glob '!node_modules/**'
rg -n "page-aggregate|pageAggregate|PageAggregate|blockDocument|projectionSource|documents/save|documents/options|/api/page-aggregate" rust/crates/mnote-web rust/crates/bridge-runtime wolai-frontend/src scripts --glob '!rust/target/**' --glob '!node_modules/**'
```

修复后定向验证：

```bash
cargo test --manifest-path rust/Cargo.toml -p mnote-web markdown_edit -- --nocapture
cargo test --manifest-path rust/Cargo.toml -p mnote-web page_ai_workflow -- --nocapture
cargo test --manifest-path rust/Cargo.toml -p mnote-web acp_client -- --nocapture
cargo test --manifest-path rust/Cargo.toml -p mnote-web acp_session_manager -- --nocapture
node --check scripts/reasonix-acp-wrapper.mjs
MNOTE_REASONIX_ACP_SELFTEST=1 node scripts/reasonix-acp-wrapper.mjs
```

结果：上述验证均通过。
