353 lines
11 KiB
Markdown
353 lines
11 KiB
Markdown
# 7-10 [process] 页面块 AI 工具执行 checklist v1
|
||
|
||
> 更新时间:2026-05-16
|
||
>
|
||
> 当前状态:`PROCESS`。
|
||
>
|
||
> 关联文档:
|
||
> - `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-13-page-block-identity-and-command-contract-v1.md`
|
||
> - `/mnt/Data1T/mnote/design/02-convex-rust-long-term-architecture/process/2-1-page-block-storage-projection-alignment-v1.md`
|
||
> - `/mnt/Data1T/mnote/design/07-ai/process/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`
|
||
|
||
---
|
||
|
||
## 1. 目的
|
||
|
||
本 checklist 把 `7-9` 的路线图拆成可验收执行链,避免页面块 AI 工具停留在“工具名已设计、真实页面不可回读”的状态。
|
||
|
||
执行顺序固定为:
|
||
|
||
```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:设计与基线冻结
|
||
|
||
- [ ] `5-13` 已冻结 block identity、command、Tiptap boundary。
|
||
- [ ] `2-1` 已冻结 `documents.content`、`blocks` 表、Page Aggregate、revision/conflict key 的关系。
|
||
- [ ] `7-9` 已更新 Tiptap AI Toolkit 对照,不再写成“Tiptap 没有官方 AI 文档工具”。
|
||
- [ ] `7-9` 明确 `mnote.page.save` 是粗粒度兜底,不是精确块工具。
|
||
- [ ] 当前 reference code 已在 `/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/tiptap-docs` 与 `tiptap-main` 可读。
|
||
|
||
验收:
|
||
|
||
- [ ] `rg -n "Tiptap AI Toolkit|tiptapRead|tiptapEdit|UniqueID|_hash|blockDocument" design/05-editor-mainline/process design/02-convex-rust-long-term-architecture/process design/07-ai/process` 能找到对应设计。
|
||
|
||
---
|
||
|
||
## 3. Phase 1:Page Aggregate Block Projection
|
||
|
||
目标:
|
||
|
||
> 每个可编辑块都能从 Page Aggregate 读到稳定身份和冲突辅助信息。
|
||
|
||
任务:
|
||
|
||
- [ ] `PageBody` 增加 `blockDocument` 或等价稳定字段。
|
||
- [ ] 从 `documents.content` 生成 `EditorBlockDocument`。
|
||
- [ ] 从 Tiptap JSON 生成 `EditorBlockDocument` 的 bridge 测试覆盖当前主类型。
|
||
- [ ] 每个块输出 `blockId/type/text/attrs/children/parentBlockId/order/path/depth/revisionRef/editable`。
|
||
- [ ] 对无 id legacy block 生成稳定迁移策略或 warning。
|
||
- [ ] 复杂块输出 `editable=false` 或受限能力。
|
||
|
||
验证命令:
|
||
|
||
```bash
|
||
cargo test -p core-protocol editor_tiptap_bridge
|
||
cargo test -p bridge-runtime editor_document
|
||
```
|
||
|
||
真实页面 smoke:
|
||
|
||
- [ ] 登录 `http://localhost:3000/auth` 测试账号。
|
||
- [ ] 新建页面 `TEST-AI-BLOCK-PROJECTION-<timestamp>`。
|
||
- [ ] 输入段落、标题、todo、列表、mindmap/resource 占位至少各一个。
|
||
- [ ] 调 `/api/page-aggregate/:id` 保存响应到 `tmp/hermes-tester/<run-id>/page-aggregate.json`。
|
||
- [ ] 断言所有普通可编辑块有 `blockId` 和 `revisionRef`。
|
||
|
||
通过标准:
|
||
|
||
- [ ] 页面刷新后 block ids 不变化。
|
||
- [ ] projection 中 `blockCount` 与页面可编辑块数量大体一致;复杂块允许受限但必须有 warning。
|
||
|
||
---
|
||
|
||
## 4. Phase 2:`mnote.doc.fetch` / `mnote.doc.find`
|
||
|
||
目标:
|
||
|
||
> AI 能先读取和定位,不需要猜整页 `content` shape。
|
||
|
||
任务:
|
||
|
||
- [ ] tool manifest 增加 `mnote.doc.fetch`。
|
||
- [ ] tool manifest 增加 `mnote.doc.find`。
|
||
- [ ] `doc.fetch` 支持 `scope=full/outline/keyword/block/selection`。
|
||
- [ ] `doc.fetch` 支持 `detail=simple/with_ids/full`。
|
||
- [ ] `doc.find` 支持按 text/type/blockId 查找。
|
||
- [ ] 返回 page `revision/conflictDetectionKey`。
|
||
- [ ] 返回可直接传入 `block.fetch/replace/insert_after` 的 `blockId`。
|
||
|
||
验证命令:
|
||
|
||
```bash
|
||
cargo test -p mnote-web hermes_tools
|
||
cargo test -p bridge-runtime doc_find
|
||
```
|
||
|
||
真实页面 smoke:
|
||
|
||
- [ ] 使用 `TEST-AI-BLOCK-FETCH-<timestamp>` 页面。
|
||
- [ ] `mnote.doc.fetch scope=full detail=with_ids` 读取整页。
|
||
- [ ] `mnote.doc.fetch scope=outline detail=with_ids` 只返回标题结构。
|
||
- [ ] `mnote.doc.find query=<唯一前缀>` 定位目标段落。
|
||
- [ ] 保存工具返回到 `tmp/hermes-tester/<run-id>/doc-fetch-find.json`。
|
||
|
||
通过标准:
|
||
|
||
- [ ] 不读取浏览器 DOM。
|
||
- [ ] `doc.find` 返回的 block id 能被 `block.fetch` 读取。
|
||
- [ ] 大页面默认分块或限制输出,不默认返回无限正文。
|
||
|
||
---
|
||
|
||
## 5. Phase 3:`mnote.block.fetch`
|
||
|
||
目标:
|
||
|
||
> AI 能读取单块、子块和同父级上下文,形成写入前确认。
|
||
|
||
任务:
|
||
|
||
- [ ] tool manifest 增加 `mnote.block.fetch`。
|
||
- [ ] 支持 `includeChildren`。
|
||
- [ ] 支持 `contextBefore/contextAfter`。
|
||
- [ ] 支持 `format=json/markdown/page_xml/text`。
|
||
- [ ] 返回 `revisionRef`、`editable`、`unsupportedReason`。
|
||
- [ ] 不存在 block 返回 `mnote_block_not_found`。
|
||
|
||
验证命令:
|
||
|
||
```bash
|
||
cargo test -p mnote-web block_fetch
|
||
```
|
||
|
||
真实页面 smoke:
|
||
|
||
- [ ] 从 `doc.find` 结果选择一个段落 block。
|
||
- [ ] 调 `mnote.block.fetch includeChildren=true contextBefore=1 contextAfter=1`。
|
||
- [ ] 断言 before/after 只来自同父级。
|
||
|
||
通过标准:
|
||
|
||
- [ ] block 文本与页面显示一致。
|
||
- [ ] `revisionRef` 可被后续 dry-run 使用。
|
||
- [ ] 复杂块不会伪装成完全可编辑。
|
||
|
||
---
|
||
|
||
## 6. Phase 4:`mnote.doc.plan_update`
|
||
|
||
目标:
|
||
|
||
> 所有写入先 dry-run,返回 diff、warnings、risk,不改变页面。
|
||
|
||
任务:
|
||
|
||
- [ ] tool manifest 增加 `mnote.doc.plan_update`。
|
||
- [ ] 支持 `command=block_replace`。
|
||
- [ ] 支持 `command=block_insert_after`。
|
||
- [ ] 支持 `command=block_move_after` dry-run。
|
||
- [ ] 支持 `command=str_replace` 且多重匹配阻断。
|
||
- [ ] 缺少 `revision/conflictDetectionKey` 时只允许 dry-run。
|
||
- [ ] 返回 `planId`、`diff`、`warnings`、`risk`、`blocked`。
|
||
|
||
验证命令:
|
||
|
||
```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:
|
||
|
||
- [ ] 对目标段落执行 `block_replace dryRun=true`。
|
||
- [ ] 对目标段落执行 `block_insert_after dryRun=true`。
|
||
- [ ] 对两个同父级普通块执行 `block_move_after dryRun=true`。
|
||
- [ ] dry-run 前后分别保存 `/api/page-aggregate/:id`,确认内容 hash 不变。
|
||
|
||
通过标准:
|
||
|
||
- [ ] dry-run 不写 Convex。
|
||
- [ ] plan 能解释 before/after。
|
||
- [ ] 不支持场景返回 `blocked=true`。
|
||
|
||
---
|
||
|
||
## 7. Phase 5:`mnote.block.replace`
|
||
|
||
目标:
|
||
|
||
> 第一条最小精确块写入闭环。
|
||
|
||
任务:
|
||
|
||
- [ ] tool manifest 增加 `mnote.block.replace`。
|
||
- [ ] 输入必须包含 `blockId/revision/conflictDetectionKey/idempotencyKey/dryRun`。
|
||
- [ ] `dryRun=true` 只返回 plan。
|
||
- [ ] `dryRun=false` 生成 `EditorCommand::ReplaceBlock`。
|
||
- [ ] Rust 应用命令生成 canonical content。
|
||
- [ ] 通过 `page.body.save -> documents:updateContent` 持久化。
|
||
- [ ] 返回新 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:
|
||
|
||
- [ ] 新建 `TEST-AI-BLOCK-REPLACE-<timestamp>` 页面。
|
||
- [ ] 写入两段不同文本。
|
||
- [ ] `doc.find` 找到第二段 block id。
|
||
- [ ] `block.replace dryRun=true` 查看 plan。
|
||
- [ ] `block.replace dryRun=false` 替换第二段。
|
||
- [ ] 页面截图证明只替换第二段。
|
||
- [ ] `/api/page-aggregate/:id` 回读证明持久化。
|
||
- [ ] `mnote.doc.fetch` 再次证明 AI 可读回。
|
||
|
||
通过标准:
|
||
|
||
- [ ] 相邻块不变化。
|
||
- [ ] 目标 block id 保持不变。
|
||
- [ ] 旧 revision 写入返回 conflict。
|
||
|
||
---
|
||
|
||
## 8. Phase 6:`mnote.block.insert_after`
|
||
|
||
目标:
|
||
|
||
> AI 能在指定块后插入新块,并拿到新 block id。
|
||
|
||
任务:
|
||
|
||
- [ ] tool manifest 增加 `mnote.block.insert_after`。
|
||
- [ ] 输入必须包含 `anchorBlockId/revision/conflictDetectionKey/idempotencyKey/dryRun`。
|
||
- [ ] 新块 id 由 Rust runtime 分配。
|
||
- [ ] 支持单块和最多 20 个普通块插入。
|
||
- [ ] 第一阶段支持 paragraph/heading/todo。
|
||
- [ ] 返回 inserted block ids 和新 revision。
|
||
|
||
验证命令:
|
||
|
||
```bash
|
||
cargo test -p mnote-web block_insert_after
|
||
cargo test -p bridge-runtime doc_insert_blocks_tool_emits_editor_commands
|
||
```
|
||
|
||
真实页面 smoke:
|
||
|
||
- [ ] 新建 `TEST-AI-BLOCK-INSERT-<timestamp>` 页面。
|
||
- [ ] 在第一段后插入 todo。
|
||
- [ ] 截图证明位置准确。
|
||
- [ ] 刷新页面后再次确认。
|
||
- [ ] `mnote.block.fetch` 能读取新 block id。
|
||
|
||
通过标准:
|
||
|
||
- [ ] 插入位置准确。
|
||
- [ ] 新 block id 不是 AI 自造未校验 id。
|
||
- [ ] 重复同一 `idempotencyKey` 不重复插入。
|
||
|
||
---
|
||
|
||
## 9. Phase 7:`mnote.block.move_after`
|
||
|
||
目标:
|
||
|
||
> 第一阶段只开放同父级普通叶子块移动。
|
||
|
||
任务:
|
||
|
||
- [ ] tool manifest 增加 `mnote.block.move_after`。
|
||
- [ ] `dryRun=true` 支持同父级叶子块 diff。
|
||
- [ ] `dryRun=false` 前先继续阻断所有复杂块。
|
||
- [ ] 检查 `blockRevisionRef` 与 `anchorRevisionRef`。
|
||
- [ ] 阻断移动到自身、移动到子树、跨页面移动。
|
||
- [ ] 返回 from/to parent/order。
|
||
|
||
验证命令:
|
||
|
||
```bash
|
||
cargo test -p mnote-web block_move_after
|
||
cargo test -p mnote-editor-core command_executor
|
||
```
|
||
|
||
真实页面 smoke:
|
||
|
||
- [ ] 新建 `TEST-AI-BLOCK-MOVE-<timestamp>` 页面,包含三段普通段落。
|
||
- [ ] dry-run 移动第三段到第一段后。
|
||
- [ ] 正式执行同父级移动。
|
||
- [ ] 截图证明顺序为第一段、第三段、第二段。
|
||
- [ ] `mnote.doc.fetch` 回读顺序一致。
|
||
- [ ] 对标题带子块、列表项、表格、mindmap 执行 move dry-run,必须返回 blocked。
|
||
|
||
通过标准:
|
||
|
||
- [ ] moving block id 保持不变。
|
||
- [ ] 同父级顺序正确。
|
||
- [ ] 复杂块不被误移动。
|
||
|
||
---
|
||
|
||
## 10. Phase 8:UI 与 Review Mode
|
||
|
||
目标:
|
||
|
||
> AI 写入可解释、可确认,不把 preview 当持久审阅事实。
|
||
|
||
任务:
|
||
|
||
- [ ] Hermes tool event UI 展示 `plan/diff/warnings/risk`。
|
||
- [ ] `page.save` 标记为粗粒度高风险兜底。
|
||
- [ ] `block.replace/insert_after/move_after` 展示 changedBlocks。
|
||
- [ ] 本地 preview/suggestion 与持久 comment/tracked-change 分开。
|
||
- [ ] 协作可见审阅必须另走正式 comment/history/tracked-change 设计。
|
||
|
||
通过标准:
|
||
|
||
- [ ] 用户能看到 AI 将改哪个 block。
|
||
- [ ] `blocked=true` 的工具调用不会出现写入按钮。
|
||
- [ ] preview 不写入正式 comment/history。
|
||
|
||
---
|
||
|
||
## 11. DONE 条件
|
||
|
||
本 checklist 不能移动到 `done/`,直到:
|
||
|
||
- [ ] Phase 1 到 Phase 6 全部完成。
|
||
- [ ] Phase 7 至少完成 dry-run 和阻断规则;若真实 move 未完成,`7-9` 必须仍标注受限。
|
||
- [ ] 每个写工具都有真实页面 smoke 证据。
|
||
- [ ] 失败项已经写入 `bugs/07-ai/process/` 或真实 owner 分类。
|
||
- [ ] `mnote.page.save` 不再被任何设计描述为精确块编辑主入口。
|