# 7-10 [recycle] 页面块 AI 工具执行 checklist v1

> 更新时间：2026-05-16
>
> 当前状态：`recycle / old`。
>
> 归档说明（2026-05-22）：本文的块级 AI 执行路线已被 local-first Markdown 编辑收敛口径覆盖；当前 active AI 编辑入口以 `design/07-ai/process/7-14-online-local-ai-markdown-editing-convergence-v1.md` 为准。
>
> 关联文档：
> - `/mnt/Data1T/mnote/design/05-editor-mainline/done/5-13-page-block-identity-and-command-contract-v1.md`
> - `/mnt/Data1T/mnote/design/02-convex-rust-long-term-architecture/done/2-1-page-block-storage-projection-alignment-v1.md`
> - `/mnt/Data1T/mnote/design/07-ai/done/7-9-page-block-ai-tooling-roadmap-v1.md`
> - `/mnt/Data1T/mnote/design/07-ai/done/7-6-mnote-hermes-plugin-tool-contract-v1.md`
> - `/mnt/Data1T/mnote/design/07-ai/process/7-12-page-ai-hermes-tool-routing-and-review-surface-v1.md`

---

## 1. 目的

本 checklist 把 `7-9` 的路线图拆成可验收执行链，避免页面块 AI 工具停留在“工具名已设计、真实页面不可回读”的状态。

## 1.0 执行口径修正（2026-05-16）

本文继续作为页面块 AI 工具执行 checklist 保留在 `process/`，不移入 `old/`。但后续所有“页面 AI runtime / fast workflow / planner / apply controller”相关任务必须按 `7-12` 的边界解释：

- Hermes 继续是唯一页面 AI agent runtime。
- mnote 本地层只提供工具路由、工具提示、上下文冻结、dry-run/review、Rust 写入校验和 readback。
- `PageAIIntentParser` 后续读作 `PageAICommandRouter`，输出 `recommendedToolCall`，不维护独立对话 runtime。
- `PageAIOperationPlanner` 后续只构造 tool args 或 dry-run plan，不能绕过 Hermes tool manifest/profile toggle/audit。
- `PageAIOperationValidator` 继续有效，但归属 mnote Rust tool executor / projection validation。
- `PageAIApplyController` 后续应收口为 review session / tool executor / readback controller，不能成为第二套 agent 编排中心。
- 当前 `usedHermesRun=false` 的快路径只能理解为 deterministic shortcut，不代表 mnote 新建长期 agent runtime。

## 1.0.1 当前主路径修正（2026-05-19）

`2-2 local-first` 完成后，本文继续保留为页面块 / 结构性工具 checklist，但普通 Markdown 正文编辑主路径已经转为 VSCode-like 文件编辑：

- local-first 普通正文编辑默认给 Hermes / Reasonix 当前文件引用、selection 和 allowed roots，由 agent 使用自身 patch / diff / 文件编辑能力写本地 `.md`。
- `mnote.doc.markdown_edit` 是 cloud / remote agent / compat fallback，不再是 local-first 普通 Markdown 编辑唯一主路径。
- `/api/page-ai/block-edit-workflow` 只保留为兼容门面或非 local source 的快捷路径，不作为 local-first 默认主路径。
- `mnote.doc.apply_block_ops` / `mnote.block.*` 保留为结构性块操作辅助，例如块移动、资源块、复杂子块或必须按 blockId 精确处理的场景。
- Phase C 的 review session / streaming apply 仍冻结；本文只继续跟踪基础工具合同、上下文、冲突校验、幂等和审阅面边界。
- 当前 page AI runtime 口径以 `design/02-convex-rust-long-term-architecture/done/2-2-local-first-workspace-convex-control-plane-v1.md`、`design/07-ai/process/7-14-online-local-ai-markdown-editing-convergence-v1.md` 和 `design/07-ai/process/7-15-page-ai-acp-agent-runtime-unified-layer-v1.md` 为准。

## 1.1 当前执行状态（2026-05-16）

已完成并有代码/测试/smoke 证据：

- Page Aggregate 输出 `blockDocument/blockProjectionVersion/projectionSource`。
- `mnote.doc.fetch`、`mnote.doc.find`、`mnote.block.fetch` 已接入 Hermes tool manifest 与 dispatch。
- `mnote.doc.plan_update` 已提供块级 dry-run 计划和移动阻断诊断。
- `mnote.block.replace`、`mnote.block.insert_after`、`mnote.block.move_after` 已通过 Rust `EditorCommand` 生成 canonical content，再经 `page.body.save -> documents:updateContent` 持久化。
- `revision/conflictDetectionKey/revisionRef/idempotencyKey/dryRun` 写入前置约束已接入，并修复 `content_revision` 投影对齐。
- 真实 smoke：`/mnt/Data1T/mnote/scripts/task-page-block-ai-tools-smoke.js`，证据位于 `/mnt/Data1T/mnote/tmp/page-block-ai-tools-smoke/mp7xgyqs.json` 与同名截图。
- 页面 AI Runtime `mnote tools` 面板已读取真实 manifest，展示 13 个 mnote tools，并支持当前 Hermes profile 下开关工具。
- `/api/hermes/client/tools/toggle` 已持久化 `mnote.tools.disabled`；`/api/hermes/tools/mnote/call` 在执行前按 profile 拦截关闭工具，返回 `mnote_tool_disabled`。
- Hermes 外部 mnote plugin/skill 已完成，不改 Hermes 应用本体：
  - `/home/lix/.hermes/plugins/mnote/plugin.yaml`
  - `/home/lix/.hermes/plugins/mnote/__init__.py`
  - `/home/lix/.hermes/skills/note-taking/mnote-block-ai/SKILL.md`
- Hermes 外部 plugin schema 已对齐真实块工具参数：`mnote_doc_fetch(scope/detail/format/query/maxBlocks/blockId/selectedBlockIds/allowedTargetBlockIds)`、`mnote_doc_plan_update(command/blockId/anchorBlockId/content)`、`mnote_block_fetch(blockId/includeChildren/contextBefore/contextAfter/format)`。
- Hermes CLI 真实 plugin 块操作已通过：
  - 测试页：`workspaceId=tree_1777430834634_3`，`documentId=tree_1778915893346_1`，`suffix=mp80lbze`。
  - 工具链：`mnote_doc_fetch -> mnote_block_fetch -> mnote_doc_plan_update(dryRun block_replace) -> mnote_block_replace -> mnote_doc_plan_update(dryRun block_insert_after) -> mnote_block_insert_after -> mnote_doc_plan_update(dryRun block_move_after) -> mnote_block_move_after -> mnote_doc_fetch`。
  - 结果：`finalOrder=["p_1","p_3","ai_block_req_1778916033994_21","p_2"]`，`finalTexts` 分别为 `Hermes 块插件第一段 mp80lbze`、`Hermes 块插件第三段 mp80lbze`、`Hermes 插件插入段 mp80lbze`、`Hermes 插件替换第二段 mp80lbze`，`errors=[]`。
  - 修复点：Hermes 可能把 `doc.fetch` 返回的 projection `payload/contentNodes` 形状传回写工具，`rust/crates/mnote-web/src/hermes_tools/block.rs` 已补齐 `content_to_text` 解析并加单测，避免 replace/insert 写成空段。
- 浏览器回读验证已通过：打开 `http://127.0.0.1:3000/documents/tree_1778915893346_1?workspaceId=tree_1777430834634_3` 后四段目标文本可见，截图 `/mnt/Data1T/mnote/tmp/hermes-plugin-block-ai/mp80lbze-page.png`。
- 页面 AI 工具面板浏览器验证：3000 最新 `mnote-web` 进程下 Runtime 面板可见 13 个工具；关闭 `mnote.block.fetch` 后 `/api/hermes/client/tools` 显示 `enabled=false/status=disabled`，直接调用 `mnote.block.fetch` 返回 `mnote_tool_disabled`，随后已恢复开启；截图 `/mnt/Data1T/mnote/tmp/page-ai-tools-runtime/mnote-page-ai-tools-runtime-20260516.png`。
- 页面 AI context / format focused smoke 已完成：
  - 脚本：`/mnt/Data1T/mnote/scripts/task-page-block-ai-context-format-smoke.js`。
  - 证据：`/mnt/Data1T/mnote/tmp/page-block-ai-context-format-smoke/mp8ddr4n.json`，截图 `/mnt/Data1T/mnote/tmp/page-block-ai-context-format-smoke/mp8ddr4n-page.png`。
  - 覆盖：`mnote.doc.fetch scope=selection selectedBlockIds=["p_2"] format=page_xml/text`、`mnote.block.fetch format=page_xml/text`、manifest annotations、`mnote.page.save` destructive/yolo 粗粒度兜底定位、`mnote.doc.apply_block_ops allowedTargetBlockIds` 越界阻断 `mnote_block_target_out_of_scope`。
  - 边界：`doc.apply_block_ops` 与单个 `mnote.block.*` 写工具的 `allowedTargetBlockIds` selection scope guard 均已完成矩阵验证。
- 页面 AI 快速块编辑第一阶段已完成：
  - 新增 `/api/page-ai/block-edit-workflow`，简单块增删改不再默认进入 Hermes agent run。
  - 对明确中文指令 `把「A」替换为「B」/ 在「C」后插入「D」/ 删除「E」` 已先由 mnote 本地 planner 生成 `mnote.doc.apply_block_ops` operations；无法解析时才进入小模型 operations 路径。
  - 快路径失败时，除 `page_ai_workflow_not_block_edit` 外不再自动 fallback 到 `/api/hermes/client/runs`，避免一次请求叠加“快路径失败成本 + Hermes agent 成本”。
  - 真实浏览器 smoke：`/mnt/Data1T/mnote/scripts/task-page-ai-block-edit-workflow-smoke.js`，最新证据 `/mnt/Data1T/mnote/tmp/page-ai-block-edit-workflow-smoke/mp86uciu.json`。
  - 验证结果：`pageAiWriteVisible=788ms`、`usedFastWorkflow=true`、`usedHermesRun=false`；后端日志 `operation_source=local_rule`、`model_ms=0`、`apply_ms=42`、`total_ms=42`。
  - 详细 review 历史快照：`/mnt/Data1T/mnote/design/10-review/done/09-page-ai-fast-block-edit-runtime-review.md`。

仍未完成，本文继续留在 `process/`：

- 剩余真实页面 smoke 已由 `/mnt/Data1T/mnote/design/07-ai/done/7-16-page-block-ai-real-smoke-followup-matrix-v1.md` 完成并归档；本文只保留总 checklist、历史证据和 Phase 8 / Review Mode 冻结缺口。
- 复杂块移动阻断矩阵已补隔离 route smoke 覆盖标题带子块、列表项、表格、mindmap/resource；真实浏览器页面 smoke 仍未补。
- 持久审阅/preview UI 仍是后续可选项；当前默认 yolo 模式不做写入审批，`scope=selection` 与 `format=page_xml/text` 已进入工具与 PageAIContextBuilder 首版。
- PageAIIntentParser / PageAIOperationPlanner / PageAIOperationValidator / PageAIApplyController 仍需继续建设；当前本地 planner 只覆盖低歧义文本块增删改，不应被视为完整 AI 编辑 runtime。

执行顺序固定为：

```text
fetch/find
  -> block.fetch
  -> plan_update
  -> block.replace
  -> block.insert_after
  -> block.move_after
```

每一步都必须满足：

- Rust command 或 projection 有测试。
- Hermes tool 有结构化返回。
- 真实页面 smoke 通过。
- 写入后能通过 Page Aggregate 与 AI fetch 回读。

---

## 2. Phase 0：设计与基线冻结

- [x] `5-13` 已冻结 block identity、command、Tiptap boundary。
- [x] `2-1` 已冻结 `documents.content`、`blocks` 表、Page Aggregate、revision/conflict key 的关系。
- [x] `7-9` 已更新 Tiptap AI Toolkit 对照，不再写成“Tiptap 没有官方 AI 文档工具”。
- [x] `7-9` 明确 `mnote.page.save` 是粗粒度兜底，不是精确块工具。
- [x] 当前 reference code 已在 `/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/tiptap-docs` 与 `tiptap-main` 可读。

验收：

- [x] `rg -n "Tiptap AI Toolkit|tiptapRead|tiptapEdit|UniqueID|_hash|blockDocument" design/05-editor-mainline/{process,done} design/02-convex-rust-long-term-architecture/{process,done} design/07-ai/{process,done}` 能找到对应设计。

---

## 3. Phase 1：Page Aggregate Block Projection

目标：

> 每个可编辑块都能从 Page Aggregate 读到稳定身份和冲突辅助信息。

任务：

- [x] `PageBody` 增加 `blockDocument` 或等价稳定字段。
- [x] 从 `documents.content` 生成 `EditorBlockDocument`。
- [x] 从 Tiptap JSON 生成 `EditorBlockDocument` 的 bridge 测试覆盖当前主类型。
- [x] 每个块输出 `blockId/type/text/attrs/children/parentBlockId/order/path/depth/revisionRef/editable`。
- [x] 对无 id legacy block 生成稳定迁移策略或 warning。
- [x] 复杂块输出 `editable=false` 或受限能力。

验证命令：

```bash
cargo test -p core-protocol editor_tiptap_bridge
cargo test -p bridge-runtime editor_document
```

真实页面 smoke：

- [x] 登录测试账号并创建综合页面 `TEST-AI-BLOCK-TOOLS-<timestamp>`。
- [x] 写入段落、标题、todo、列表、mindmap/resource 占位。
- [x] 调 `/api/page-aggregate/:id` 保存响应到 `tmp/hermes-tester/page-block-ai-tools-<run-id>/page-aggregate.json`。
- [x] 断言所有普通可编辑块有 `blockId` 和 `revisionRef`。

通过标准：

- [x] 页面刷新后 block ids 不变化。
- [x] projection 中 `blockCount` 与页面块数量一致；复杂块返回 `editable=false` 与 `unsupportedReason`。

2026-05-18 补充真实页面 smoke 证据：

- 最新证据：`tmp/page-block-ai-tools-smoke/mpag4966.json`，Page Aggregate：`tmp/hermes-tester/page-block-ai-tools-mpag4966/page-aggregate.json`，截图：`tmp/page-block-ai-tools-smoke/mpag4966-page.png`。
- 覆盖：Page Aggregate `blockProjectionVersion=1`、`projectionSource=documents.content`、`aggregateBlockCount=10`，与 `mnote.doc.fetch` 块顺序一致；刷新后 `afterRefreshBlockIds` 保持最终顺序；`table_1/mindmap_1/resource_1` 返回 `editable=false` 与 `unsupportedReason=复杂块暂不开放 AI 精确写入`。

---

## 4. Phase 2：`mnote.doc.fetch` / `mnote.doc.find`

目标：

> AI 能先读取和定位，不需要猜整页 `content` shape。

任务：

- [x] tool manifest 增加 `mnote.doc.fetch`。
- [x] tool manifest 增加 `mnote.doc.find`。
- [x] `doc.fetch` 支持 `scope=full/outline/keyword/block/selection`。
- [x] `doc.fetch` 支持 `detail=simple/with_ids/full`。
- [x] `doc.find` 支持按 text/type/blockId 查找。
- [x] 返回 page `revision/conflictDetectionKey`。
- [x] 返回可直接传入 `block.fetch/replace/insert_after` 的 `blockId`。

验证命令：

```bash
cargo test -p mnote-web hermes_tools
cargo test -p bridge-runtime doc_find
```

真实页面 smoke：

- [x] 使用综合页面 `TEST-AI-BLOCK-TOOLS-<timestamp>`。
- [x] `mnote.doc.fetch scope=full detail=with_ids` 读取整页。
- [x] `mnote.doc.fetch scope=outline detail=with_ids` 只返回标题结构。
- [x] `mnote.doc.find query=<唯一前缀>` 定位目标段落。
- [x] 保存工具返回到 `tmp/hermes-tester/page-block-ai-tools-<run-id>/doc-fetch-find.json`。

补充 smoke 证据：

- [x] `mnote.doc.fetch scope=selection selectedBlockIds=["p_2"] format=page_xml` 读取真实页面选区上下文，只返回 `p_2`，返回 `schema=mnote.page_ai_context.v1`、`allowedTargetBlockIds=["p_2"]`、`revision/conflictDetectionKey` 和 block `revisionRef`。证据：`tmp/page-block-ai-context-format-smoke/mp8ddr4n.json`。
- [x] `mnote.doc.fetch scope=selection format=text` 只返回 `[p_2] 第二段 mp8ddr4n`，不包含未选中块。证据：`tmp/page-block-ai-context-format-smoke/mp8ddr4n.json`。

通过标准：

- [x] 不读取浏览器 DOM。
- [x] `doc.find` 返回的 block id 能被 `block.fetch` 读取。
- [x] 大页面默认分块或限制输出，不默认返回无限正文。

2026-05-18 补充证据：

- `mnote.doc.fetch` 从 `aggregate_value` / Page Aggregate block projection 构造上下文，不读取浏览器 DOM；代码入口：`rust/crates/mnote-web/src/hermes_tools/doc.rs`。
- `hermes_tools_doc_find_and_block_fetch_use_block_projection` 覆盖 `doc.find` 返回 `heading_1` 后继续用同一个 id 调 `mnote.block.fetch`。
- `mnote.doc.fetch` 默认 `maxBlocks=120`，运行时 clamp 到 `1..=240`；超限返回 `truncated/continuation/warnings`。
- 真实 3000 smoke：`tmp/page-block-ai-tools-smoke/mpaes2hg.json` 覆盖 `doc.fetch full` 读取 `heading_1/p_1/p_2/p_3`，`doc.fetch outline` 只返回 `heading_1`，`doc.find` 定位 `p_2`，`maxBlocks=2` 返回 `truncated=true`。

---

## 5. Phase 3：`mnote.block.fetch`

目标：

> AI 能读取单块、子块和同父级上下文，形成写入前确认。

任务：

- [x] tool manifest 增加 `mnote.block.fetch`。
- [x] 支持 `includeChildren`。
- [x] 支持 `contextBefore/contextAfter`。
- [x] 支持 `format=json/markdown/page_xml/text`。
- [x] 返回 `revisionRef`、`editable`、`unsupportedReason`。
- [x] 不存在 block 返回 `mnote_block_not_found`。

验证命令：

```bash
cargo test -p mnote-web block_fetch
```

真实页面 smoke：

- [x] 从 `doc.find` 结果选择一个段落 block。
- [x] 调 `mnote.block.fetch includeChildren=true contextBefore=1 contextAfter=1`。
- [x] 断言 before/after 只来自同父级。

补充 smoke 证据：

- [x] `mnote.block.fetch blockId=p_2 format=page_xml/text contextBefore=1 contextAfter=1` 返回目标块 `p_2`、`revisionRef` 与同父级 before/after `p_1/p_3`。证据：`tmp/page-block-ai-context-format-smoke/mp8ddr4n.json`。
- [x] `doc.find` 定位 `p_2` 后，`mnote.block.fetch includeChildren=true contextBefore=1 contextAfter=1` 回读 `p_2`，返回 `revisionRef`、同父级 before `p_1` 和 after `p_3`。证据：`tmp/page-block-ai-tools-smoke/mpag4966.json`。
- [x] `table_1/mindmap_1/resource_1` 复杂块在真实 3000 smoke 中返回 `editable=false` 与 `unsupportedReason=复杂块暂不开放 AI 精确写入`。证据：`tmp/page-block-ai-tools-smoke/mpag4966.json`。

通过标准：

- [x] block 文本与页面显示一致。
- [x] `revisionRef` 可被后续 dry-run 使用。
- [x] 复杂块不会伪装成完全可编辑。

---

## 6. Phase 4：`mnote.doc.plan_update`

目标：

> 所有写入先 dry-run，返回 diff、warnings、risk，不改变页面。

任务：

- [x] tool manifest 增加 `mnote.doc.plan_update`。
- [x] 支持 `command=block_replace`。
- [x] 支持 `command=block_insert_after`。
- [x] 支持 `command=block_move_after` dry-run。
- [x] `command=str_replace` 不再作为本 checklist 当前目标；cloud / remote / compat 的普通正文 search/replace 已收口到 `mnote.doc.markdown_edit`，多重匹配/无法安全映射阻断由 markdown_edit 合同覆盖。local-first 普通 Markdown 编辑默认走授权文件 + agent 原生 patch/diff。
- [x] 缺少 `revision/conflictDetectionKey` 时只允许 dry-run。
- [x] 返回 `planId`、`diff`、`warnings`、`risk`、`blocked`。

2026-05-19 口径修正：

- `7-18` 到 `7-25` 收口后，普通正文替换不再继续扩 `mnote.doc.plan_update command=str_replace`；compat 实现路径是 `page_ai_workflow.rs -> mnote.doc.markdown_edit`。
- `mnote.doc.markdown_edit` 已覆盖精确匹配、归一化匹配、多操作同块合并、无法安全映射时失败；相关测试由 `cargo test --manifest-path rust/Cargo.toml -p mnote-web hermes_tools -- --nocapture` 覆盖。它不再代表 local-first 普通 Markdown 编辑默认入口。

验证命令：

```bash
cargo test -p mnote-web plan_update
cargo test -p bridge-runtime doc_replace_range
cargo test -p bridge-runtime doc_insert_blocks
```

真实页面 smoke：

- [x] 对目标段落执行 `block_replace dryRun=true`。
- [x] 对目标段落执行 `block_insert_after dryRun=true`。
- [x] 对两个同父级普通块执行 `block_move_after dryRun=true`。
- [x] dry-run 前后分别读取 `/api/page-aggregate/:id` 与 `mnote.doc.fetch`，确认 revision / conflictDetectionKey / 正文文本不变。

通过标准：

- [x] dry-run 不写 Convex。
- [x] plan 能解释 before/after。
- [x] 不支持场景返回 `blocked=true`。

2026-05-18 补充真实页面 smoke 证据：

- 最新证据：`tmp/page-block-ai-tools-smoke/mpag4966.json`，截图：`tmp/page-block-ai-tools-smoke/mpag4966-page.png`。
- 覆盖：`plan_update.dry_run.replaceDiff.before/after`、`insertDiff.after/content`、`block_move_after p_3 -> p_1 dryRun` 不阻断、不支持 `p_3 -> p_3` 返回 `blocked=true`、dry-run 后 revision / Page Aggregate conflictDetectionKey / 正文文本不变。

---

## 7. Phase 5：`mnote.block.replace`

目标：

> 第一条最小精确块写入闭环。

任务：

- [x] tool manifest 增加 `mnote.block.replace`。
- [x] 输入必须包含 `blockId/revision/conflictDetectionKey/idempotencyKey/dryRun`。
- [x] `dryRun=true` 只返回 plan。
- [x] `dryRun=false` 生成 `EditorCommand::ReplaceBlock`。
- [x] Rust 应用命令生成 canonical content。
- [x] 通过 `page.body.save -> documents:updateContent` 持久化。
- [x] 返回新 revision、changedBlocks、audit。

验证命令：

```bash
cargo test -p mnote-web block_replace
cargo test -p bridge-runtime doc_replace_range_tool_executes_in_rust_runtime
```

真实页面 smoke：

- [x] 使用综合页面 `TEST-AI-BLOCK-TOOLS-<timestamp>`。
- [x] 写入两段不同文本。
- [x] `doc.find` 找到第二段 block id。
- [x] `block.replace dryRun=true` 查看 plan。
- [x] `block.replace dryRun=false` 替换第二段。
- [x] 页面截图和工具回读证明只替换第二段。
- [x] `/api/page-aggregate/:id` 回读证明持久化。
- [x] `mnote.doc.fetch` 再次证明 AI 可读回。

通过标准：

- [x] 相邻块不变化。
- [x] 目标 block id 保持不变。
- [x] 旧 revision 写入返回 conflict。

2026-05-18 补充真实页面 smoke 证据：

- 最新证据：`tmp/page-block-ai-tools-smoke/mpag4966.json`，截图：`tmp/page-block-ai-tools-smoke/mpag4966-page.png`。
- 覆盖：`block.replace.changedBlocks[0]={blockId:"p_2",op:"replace"}`，`targetBlockStillExists=true`，相邻块仍为 `第一段 mpag4966` / `第三段 mpag4966`，最终 `mnote.doc.fetch` 与 Page Aggregate 均回读到 `第二段已替换 mpag4966`，Page Aggregate `aggregateRevision=2`、`aggregateConflictDetectionKey=tree_1779062903017_3:2`。
- 冲突 / 幂等补充证据：`tmp/page-block-ai-conflict-idempotency-smoke/mpafrevs.json`，截图：`tmp/page-block-ai-conflict-idempotency-smoke/mpafrevs-page.png`。
- 覆盖：首次 `block.replace` 携带最新 `revision/conflictDetectionKey/blockRevisionRef/idempotencyKey` 成功写入；重复同一 `idempotencyKey` replay 同一 `commandId` 且 revision 不再次递增；旧 `revision/conflictDetectionKey` 与旧 `blockRevisionRef` 均返回 HTTP `400` / `mnote_tool_conflict`，正文保持首次替换结果。

---

## 8. Phase 6：`mnote.block.insert_after`

目标：

> AI 能在指定块后插入新块，并拿到新 block id。

任务：

- [x] tool manifest 增加 `mnote.block.insert_after`。
- [x] 输入必须包含 `anchorBlockId/revision/conflictDetectionKey/idempotencyKey/dryRun`。
- [x] 新块 id 由 Rust runtime 分配。
- [x] 支持单块和最多 20 个普通块插入。
- [x] 第一阶段支持 paragraph/heading/todo。
- [x] 返回 inserted block ids 和新 revision。

2026-05-18 补充证据：

- `rust/crates/mnote-web/src/hermes_tools/block.rs` 的 `mnote.block.insert_after` 已支持 `content` / `block` 单块兼容输入，以及 `blocks` 数组输入。
- `blocks` 运行时限制为 `1..=20`，超过 20 个返回 `mnote_tool_bad_request`；多块写入按输入顺序连续插在 anchor 后。
- `rust/crates/mnote-web/src/hermes_tools/manifest.rs` 的 manifest 已将 `content | block | blocks` 写为 `anyOf`，并声明 `blocks.minItems=1/maxItems=20`。
- 新增回归：`hermes_tools_block_insert_after_accepts_multiple_blocks_and_returns_ids`、`hermes_tools_block_insert_after_rejects_more_than_twenty_blocks`。

验证命令：

```bash
cargo test --manifest-path rust/Cargo.toml -p mnote-web hermes_tools_block_insert_after -- --nocapture
cargo test --manifest-path rust/Cargo.toml -p mnote-web hermes_tools -- --nocapture
cargo test -p bridge-runtime doc_insert_blocks_tool_emits_editor_commands
```

真实页面 smoke：

- [x] 使用综合页面 `TEST-AI-BLOCK-TOOLS-<timestamp>`。
- [x] 在第一段后插入 todo。
- [x] 截图证明位置准确。
- [x] 刷新页面后再次确认。
- [x] `mnote.block.fetch` 能读取新 block id。

通过标准：

- [x] 插入位置准确。
- [x] 新 block id 不是 AI 自造未校验 id。
- [x] 重复同一 `idempotencyKey` 不重复插入。

2026-05-18 补充证据：

- `mnote.block.insert_after` 运行时分配 `ai_block_{request_id}` 或多块 `ai_block_{request_id}_{index}`，不会采用模型输入中的未校验 id。
- `hermes_tools_block_insert_after_accepts_multiple_blocks_and_returns_ids` 断言返回的 `insertedBlockIds` 均以 `ai_block_` 开头。
- 真实 3000 smoke：`PLAYWRIGHT_CHROME_EXECUTABLE=/snap/bin/chromium MNOTE_UI_BASE_URL=http://127.0.0.1:3000 MNOTE_AUTH_BASE_URL=http://127.0.0.1:3000 node scripts/task-page-block-ai-tools-smoke.js` 通过。
- 最新证据：`tmp/page-block-ai-tools-smoke/mpag4966.json`，截图：`tmp/page-block-ai-tools-smoke/mpag4966-page.png`。
- 覆盖：`insert_after` 插入 `todo` 块、`orderAfterInsert=["heading_1","p_1","ai_block_req_1779061213631_259","p_2","p_3"]`、`block.fetch` 可读取新块、重复 `idempotencyKey` replay 同一 `commandId` 且 revision 不再次递增、刷新后插入文本仍可见。

---

## 9. Phase 7：`mnote.block.move_after`

目标：

> 第一阶段只开放同父级普通叶子块移动。

任务：

- [x] tool manifest 增加 `mnote.block.move_after`。
- [x] `dryRun=true` 支持同父级叶子块 diff。
- [x] `dryRun=false` 前先继续阻断所有复杂块。（已有同父级/叶子/类型/editable/self 阻断，复杂块矩阵 route smoke 已补。）
- [x] 检查 `blockRevisionRef` 与 `anchorRevisionRef`。
- [x] 阻断移动到自身、移动到子树、跨页面移动。
- [x] 返回 from/to parent/order。

2026-05-18 补充证据：

- `mnote.block.move_after` 保持同父级、叶子块、可移动类型、editable、自身移动阻断条件。
- 新增隔离 fixture route smoke：`hermes_tools_block_move_after_blocks_complex_and_nested_blocks`，覆盖标题带子块、列表项、表格、mindmap、resource，均返回 `blocked=true` 与 `block_move_after_blocked`。

验证命令：

```bash
cargo test --manifest-path rust/Cargo.toml -p mnote-web hermes_tools_block_move_after_blocks_complex_and_nested_blocks -- --nocapture
cargo test --manifest-path rust/Cargo.toml -p mnote-web hermes_tools -- --nocapture
cargo test -p mnote-editor-core command_executor
```

真实页面 smoke：

- [x] 使用综合页面 `TEST-AI-BLOCK-TOOLS-<timestamp>`，包含三段普通段落。
- [x] dry-run 移动第三段到第一段后。
- [x] 正式执行同父级移动。
- [x] 截图证明顺序为第一段、第三段、第二段。
- [x] `mnote.doc.fetch` 回读顺序一致。
- [x] 对标题带子块、列表项、表格、mindmap 执行 move dry-run，必须返回 blocked。

通过标准：

- [x] moving block id 保持不变。
- [x] 同父级顺序正确。
- [x] 复杂块不被误移动。

2026-05-18 补充真实页面 smoke 证据：

- 最新证据：`tmp/page-block-ai-tools-smoke/mpag4966.json`，截图：`tmp/page-block-ai-tools-smoke/mpag4966-page.png`。
- 覆盖：`heading_parent/list_item_1/table_1/mindmap_1/resource_1` 的 `plan_update block_move_after dryRun=true` 与正式 `mnote.block.move_after dryRun=false` 均返回 `blocked=true` / `block_move_after_blocked`，阻断后 `revisionAfterBlocked=1` 且正文不变化；普通 `block.move_after.dryRunBlocked=false`，正式写入后顺序为 `heading_1,p_1,p_3,ai_block_...,p_2,...`，`movingBlockStillExists=true`。

---

## 10. Phase 8：UI 与 Review Mode

目标：

> AI 写入可解释、可确认，不把 preview 当持久审阅事实。

任务：

- [ ] Hermes tool event UI 展示 `plan/diff/warnings/risk`。
- [x] `page.save` 标记为粗粒度高风险兜底。
- [x] `block.replace/insert_after/move_after` 展示 changedBlocks。
- [x] manifest annotations 能区分只读 / 粗粒度破坏性写入 / selectionEffect / runtimeOwner / writeOwner；`mnote.page.save` 在 manifest 中为 `destructive=true`、`approvalMode=yolo`，不作为精确块编辑主入口。证据：`tmp/page-block-ai-context-format-smoke/mp8ddr4n.json`。
- [ ] 本地 preview/suggestion 与持久 comment/tracked-change 分开。
- [ ] 协作可见审阅必须另走正式 comment/history/tracked-change 设计。

通过标准：

- [ ] 用户能看到 AI 将改哪个 block。
- [ ] `blocked=true` 的工具调用不会出现写入按钮。
- [ ] preview 不写入正式 comment/history。

---

## 11. DONE 条件

本 checklist 不能移动到 `done/`，直到：

- [x] Phase 1 到 Phase 6 全部完成。
- [x] Phase 7 至少完成 dry-run 和阻断规则；若真实 move 未完成，`7-9` 必须仍标注受限。
- [x] 每个写工具都有真实页面 smoke 证据。
- [x] 失败项已经写入 `bugs/07-ai/process/` 或真实 owner 分类。
- [x] `mnote.page.save` 不再被任何设计描述为精确块编辑主入口。
- [ ] Phase 8 UI / Review Mode 仍按 Phase C 边界冻结；若后续进入实施，需要另补 UI smoke。
