# 7-14 [recycle][process] Local-first AI Markdown 编辑路径收敛 v2

> 创建时间：2026-05-16
>
> 更新时间：2026-05-22（recycle：本文仍受 CLI Main / tool-first 旧口径影响，已移入 old，不再作为当前 AI 编辑实现依据）
>
> 归档说明（2026-05-22）：本文试图从旧在线 / 本地收敛稿修正为 local-first，但仍把 CLI Main 与 `mnote.doc.*` 兼容工具放在过高位置，不符合当前 SQLite control-plane + local-first `.md` + agent 原生文件 patch/diff 主线。后续应新建更小的 active 设计稿，直接基于 SQLite、allowed roots、file refs、watcher、BufferStore、Page Aggregate 前台同步和审计链设计。
>
> 2026-05-18 口径补充：
> - local-first workspace 已成为早期产品默认形态；本地 `.md` 是默认 AI 编辑目标；旧 Convex 文档只作为历史迁移源或显式 compat/source replica 边界。
> - 本文早期把 `mnote.doc.markdown_edit` 描述为统一主路径；最新口径改为：local-first 普通 Markdown 编辑优先给 agent 授权文件引用，由 agent 使用自身成熟的 diff / apply_patch / 文件编辑能力完成；`mnote.doc.markdown_edit` 保留为显式 remote agent / compat fallback。
> - 页面内图片 / 附件上传已在 local source 下写入 `{mdBase}.assets/` 并保存相对 Markdown 路径，AI 后续处理附件引用时也应保留相对路径，不改写为历史 media asset。
>
> 2026-05-22 口径纠正：
> - 当前 Convex runtime / functions 已退役，不再作为当前正文主链、默认控制面、AI 会话主存储或新增能力目标。
> - 本文中的“在线文档”仅可理解为历史迁移源、显式 cloud compat / remote agent 边界或未来 sync replica，不再与 local-first `.md` 并列为当前主路径。
> - 当前主线应写成：本地 `.md` 文件 + Rust SQLite control-plane + allowed roots / file references + agent 原生 patch/diff + watcher / BufferStore / Page Aggregate 前台同步。
>
> 当前状态：`PROCESS`
>
> 本稿目的：
> 1. 纠正 7-9 / 7-10 / 7-12 / 7-13 中隐含的「块级编辑是 AI 唯一写入路径」假设
> 2. 基于 CLI Main 参考实现，确立 mnote 的「agent 原生文件 patch/diff 为 local-first 默认路径，MNote 文本级兼容工具 + 块级结构性操作为 fallback / 辅助」两层模型
> 3. 规划 BlockNote AI 流式/review 能力的远期方向（当前不实施）
> 4. 明确历史 cloud / remote / compat 文档边界，并把当前 AI 读取、权限、冲突与回读口径收口到 local-first `.md` 文件
>
> 关联文档：
> - `/mnt/Data1T/mnote/design/07-ai/done/7-9-page-block-ai-tooling-roadmap-v1.md`
> - `/mnt/Data1T/mnote/design/old/07-ai/process/7-10-page-block-ai-tooling-execution-checklist-v1.md`
> - `/mnt/Data1T/mnote/design/07-ai/reference/7-12-page-ai-hermes-tool-routing-and-review-surface-v1.md`
> - `/mnt/Data1T/mnote/design/07-ai/done/7-13-page-block-editor-runtime-actor-v1.md`
> - `/mnt/Data1T/mnote/design/03-rust-web/process/3-13-rust-web-local-markdown-gfm-ast-parser-migration-v1.md`
> - `/mnt/Data1T/mnote/design/04-tree-domain/done/4-22-local-folder-convex-unified-tree-execution-checklist-v1.md`
> - `/mnt/Data1T/mnote/bugs/07-ai/process/page-ai-block-edit-model-fallback-missing-content.md`
>
> 参考实现（已分析）：
> - `/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/cli-main/` — **主参考**：skill 系统、scope/detail 上下文、两层操作、工作流、参考文件模式、CI 校验
>   - `skills/lark-doc/SKILL.md` + `references/`
>   - `skill-template/`（master-skill-template.md、skill-template.md）
>   - `shortcuts/doc/`、`internal/skillscheck/`
> - `/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/blocknote-ai/packages/xl-ai/` — 远期参考：流式/review
> - `/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/tiptap-ai-autocomplete/` — 独立功能：内联补全
>
> 上位依据：
> - `/mnt/Data1T/mnote/ARCHITECTURE.md`
> - `/mnt/Data1T/mnote/design/01-05-current-priority-overview.md`

---

## 1. 结论

**当前事实源是 local-first `.md` 文件，Tiptap 只是块级 UI 表现层。** Convex 相关正文链已经退役为历史 / compat 边界，不能再和本地 `.md` 并列描述为当前主路径。既然默认对象已经是普通 Markdown 文件，就不需要再为常规正文编辑发明一套 MNote 专用块级工具。当前 07-ai 设计把 AI 编辑契约钉死在 `EditorBlockDocument` 的块级操作上，导致三个连锁问题：

1. **AI 被迫在块级操作**：对「改写这段话」「补充一段总结」「调整语气」「把所有 TODO 改成 DONE」等自然请求，AI 必须产出 `{ op: "replace", blockId: "block_1", ... }` 格式的块操作，而不能直接产出修改后的文本或搜索替换对。这增加了 AI 的认知负担和出错概率。

2. **旧块级工具不适合 local-first `.md`**：本地文件没有稳定的 `blockId`（每次解析重新分配），因此块级工具不能作为普通 Markdown 编辑默认路径。

3. **历史 compat 容易污染当前主线**：如果继续把 Convex / online 文档写成并列路径，后续实现会误把 `mnote.doc.*` 或 `documents.save` 当成新增能力入口，而不是 local-first 的 fallback / 迁移边界。

### 参考实现的验证

分析了三个参考实现后，确认 mnote 的方向是正确的：

| 参考实现 | AI 编辑模型 | mnote 采纳 |
|----------|-----------|-----------|
| **CLI Main (Lark Doc)** | 文本级 `str_replace` + 块级 `block_replace/insert_after/delete/move_after` 两层操作 | ✅ **主参考**：两层模型 + AI skill 合同设计 |
| **BlockNote AI** | 纯块级 `add/update/delete`（依赖 blockId），流式 apply + suggest/review | ✅ **远期参考**：流式/review 能力，Phase C 规划 |
| **Tiptap AI Autocomplete** | 纯文本补全，单句接龙，无工具 | ⏳ 独立功能：内联 AI 补全（非本文讨论范围） |

**CLI Main 最关键的设计决策**：它不把 AI 钉死在某一层。`str_replace` 用于文本级修改（不需要 blockId），`block_*` 用于精确块结构调整。格式上，**XML 用于精确编辑场景，Markdown 用于整篇导入/导出**。mnote 的不同之处在于：local-first 之后，默认对象就是本地 `.md` 文件，因此可以直接复用 Codex / Hermes / Reasonix 自身成熟的 diff、apply_patch、文件编辑能力；MNote 的职责收口为权限沙箱、文件引用解析、审计和刷新。

### 正确方向

> mnote 的 AI 编辑路线：**本地授权文件 + agent 原生 patch/diff 为主；MNote 兼容工具仅用于显式 cloud/remote compat 或结构化辅助；BlockNote AI 的流式/review 只作为远期交互参考。**

具体：

- **当前实施**（Phase A/B）：local-first 页面 AI 只传当前文件引用、可选 selection 和用户指令；MNote 校验 `AiAccessScope` 后让 agent 在受限目录中使用原生 patch/diff 编辑 `.md`。`mnote.doc.markdown_edit` 只作为显式 cloud / remote agent / compat fallback，`mnote.block.*` 只作为复杂结构辅助。
- **远期规划**（Phase C）：BlockNote AI 的流式增量 apply + suggest/review 层。当前先设计，不实施。

---

## 2. 参考实现详解

### 2.1 CLI Main（Lark Doc）— 两层操作模型

CLI Main 的 `skills/lark-doc/SKILL.md` 是飞书文档的 AI skill 定义。它把文档操作分成两层：

**文本级操作**（不需要 blockId）：

```
docs +update --command str_replace --search "原文片段" --replace "新文本"
```

AI 只需要匹配文本，不需要知道块在哪儿。适用于所有内容修改场景。

**块级操作**（需要 blockId）：

```
docs +update --command block_replace --block-id "block_1" --content "<xml>...</xml>"
docs +update --command block_insert_after --block-id "block_1" --content "<xml>...</xml>"
docs +update --command block_delete --block-id "block_1"
docs +update --command block_move_after --block-id "block_3" --target-block-id "block_1"
```

适用于精确块结构调整。AI 需要知道 `blockId`，因此依赖前置的 `docs +fetch` 返回带 id 的内容。

**格式选择规则**（CLI Main 的 skill 中内联）：

| 场景 | 格式 | 原因 |
|------|------|------|
| 精确块编辑（替换/插入/删除指定块） | XML | XML 保留块 id、类型、属性 |
| 整篇文档导入/导出 | Markdown | 人类可读，和 `.md` 文件互通 |
| AI 对话中的内容引用 | Markdown | 模型理解 markdown 远好于 XML |

**对 mnote 的启示**：

1. **两层操作模型直接适用**。mnote 的 `markdown_edit` = CLI Main 的 `str_replace`（文本级），`apply_block_ops` = CLI Main 的 `block_*`（块级）。
2. **但 mnote 的主格式是 markdown 而非 XML**。当前 local-first 正文事实源就是本地 `.md`，历史 cloud / compat 内容也只能先投影为 markdown 后进入兼容工具。所以我们不需要 XML 这一层——markdown 既是 AI 编辑格式，也是人类可读格式。
3. **CLI Main 的 skill 格式**（YAML frontmatter + Markdown body，定义 tool shortcuts + 上下文格式规范）值得学习。mnote 的 Hermes `mnote` plugin 可以用类似方式组织。

#### 2.1.1 Skill YAML 前端元数据

> 参考文件：`skill-template/master-skill-template.md`、`skill-template/skill-template.md`、`scripts/skill-format-check/index.js`

每个 skill 文件以 YAML frontmatter（`---` 包裹）开头：

```yaml
---
name: lark-doc
description: "飞书云文档 / Docx / 知识库 Wiki 文档（v2）：创建、打开、读取..."
metadata:
  requires:
    bins: ["lark-cli"]
  cliHelp: "lark-cli docs --api-version v2 --help; ..."
---
```

CI 自动化校验（`skill-format-check.yml` + `scripts/skill-format-check/index.js`）确保 `name` 和 `description` 必填。

**→ mnote 采纳**：Hermes 的 `mnote` plugin 已采用 YAML frontmatter（`/home/lix/.hermes/plugins/mnote/plugin.yaml`）。在此基础上规范化：
- `name`：`mnote`
- `description`：一段完整的 skill 描述，含覆盖的工具和适用场景
- `metadata.domain`：`mnote-doc`（文档域），后续可扩展 `mnote-tree`、`mnote-file`
- CI 校验：确保每个 plugin 的 `plugin.yaml` 含必填字段

#### 2.1.2 Scope / Detail 上下文控制

> 参考文件：`skills/lark-doc/references/lark-doc-fetch.md`、`shortcuts/doc/docs_fetch_v2.go`

CLI Main 的 `docs +fetch` 支持五个 `--scope` 级别，精确控制注入 AI 上下文的文档量：

| `--scope` | 返回内容 | AI 场景 |
|-----------|---------|--------|
| `outline` | 标题树（h1-hN）+ blockId | "给我看目录" |
| `section` | 指定标题下的完整节 | "精读第三章" |
| `range` | blockId 区间 | "给我 100-200 行" |
| `keyword` | 关键词周围最小片段 | "找提到 deployment 的地方" |
| `full`（默认） | 全文 | 最后手段 |

三个 `--detail` 级别控制元数据量：

| `--detail` | blockId | 样式属性 | AI 场景 |
|-----------|---------|---------|--------|
| `simple` | ❌ | ❌ | 只读、总结 |
| `with-ids` | ✅ | ❌ | 需要精确寻址时 |
| `full` | ✅ | ✅ | 即将编辑该节 |

片段包装：`<fragment requested-start="..." requested-end="...">` 和 `<excerpt top-block-id="..." parent-block-path="...">` 标记告知 AI"这是部分视图，非完整块"。

**→ mnote 采纳**（高优先级）：

```
mnote.doc.fetch({
  scope: "section" | "outline" | "keyword" | "full",
  detail: "simple" | "with_ids" | "full",
  format: "markdown",
  maxChars: 8000
})
```

- `scope` 的四级映射：outline（标题树）、section（当前节）、keyword（搜索关键词）、full（全文）
- `detail` 的三级映射：simple（纯文本）、with_ids（带块标记）、full（带属性）
- 片段包装：markdown 中用 `<!-- fragment: section "标题" -->` 注释标记部分视图边界
- 实现位置：`mnote-web` 的 `mnote.doc.fetch` handler

#### 2.1.3 更新工作流：Code-Act Loop

> 参考文件：`skills/lark-doc/references/style/lark-doc-update-workflow.md`

CLI Main 的文档编辑遵循 **Plan → Execute → Observe → Iterate** 四步循环：

```
1. Plan（先读后改）：
   docs +fetch --scope section --detail full
   → 分析当前状态 → 制定操作序列

2. Execute（精准手术，不全量覆盖）：
   默认用 str_replace / block_insert_after / block_delete
   block_move_after 用于重排
   overwrite 仅在有明确指令时使用
   append + block_delete 组合优于 overwrite

3. Observe（每次写后回读）：
   docs +fetch --scope section
   → 确认修改正确

4. Iterate（修复差异）：
   如发现偏差 → 回到 Plan
```

更新命令决策树：

```
需要修改文本内容（不改块结构）？
  → str_replace（文本级，不需要 blockId）
需要整段替换？
  → block_replace --block-id xxx（需要先 fetch --detail with_ids）
需要插入/删除？
  → block_insert_after / block_delete
需要重排结构？
  → block_move_after / block_copy_insert_after
```

**→ mnote 采纳**（当前 Hermes 的 `mnote` plugin 应内置此工作流）：

- Hermes `mnote` skill 的 `SKILL.md` 中内联 Code-Act Loop 指导
- `mnote.doc.markdown_edit`（文本级搜索替换）优先于 `mnote.doc.apply_block_ops`（块级精确操作）
- 模型 system prompt 追加："永远不要在不确定时使用全文覆盖；优先搜索替换；每次写入后回读确认"

#### 2.1.4 参考文件分离模式

> 参考文件：`skills/lark-doc/references/`（24 个独立 .md 文件）

CLI Main 把详细工具规格从主 `SKILL.md` 中分离到 `references/` 子目录：

```
skills/lark-doc/
  SKILL.md              ← 技能概述 + 决策表 + 路由规则
  references/
    lark-doc-fetch.md   ← fetch 工具详细规格
    lark-doc-update.md  ← update 工具详细规格
    lark-doc-xml.md     ← XML 块语法参考
    lark-doc-md.md      ← Markdown 格式规则
    style/
      lark-doc-update-workflow.md  ← 编辑工作流
      lark-doc-style.md            ← 写作风格指南
```

**→ mnote 采纳**：Hermes `mnote` plugin 应采用相同结构：

```
~/.hermes/plugins/mnote/
  plugin.yaml              ← YAML 前端元数据
  SKILL.md                 ← 技能概述 + 工具决策表
  references/
    mnote-doc-fetch.md     ← fetch 详细规格（scope/detail/format）
    mnote-doc-update.md    ← 更新命令规格（search/replace + block ops）
    mnote-doc-workflow.md  ← Code-Act Loop
    mnote-doc-context.md   ← 上下文冻结与格式化规则
```

#### 2.1.5 跨域资源路由

> 参考文件：`skills/lark-doc/SKILL.md`（嵌入式资源路由表）

CLI Main 的 lark-doc skill 定义了嵌入式资源的显式路由表：

```
| 嵌入标签                     | 提取字段                  | 代理 skill    |
|-----------------------------|--------------------------|---------------|
| <sheet token="..." ...>     | token → spreadsheet_token | lark-sheets   |
| <bitable token="..." ...>   | token → app_token        | lark-base     |
| <whiteboard token="...">    | board_token              | lark-whiteboard|
```

**→ mnote 采纳**（远期，当文档内嵌入其他资源类型时）：
- local-first Markdown 中嵌入的思维导图资源 → 路由到 `mnote-mindmap` skill
- 嵌入的附件/文件 → 路由到 `mnote-file` skill
- 当前 Phase A 不实施，预留路由表字段

#### 2.1.6 格式策略：XML vs Markdown

> 参考文件：`skills/lark-doc/references/lark-doc-xml.md`、`lark-doc-md.md`

CLI Main 的格式选择规则：

| 场景 | 格式 | 原因 |
|------|------|------|
| 精确块编辑 | XML（默认） | 保留 blockId、样式属性、结构 |
| 整篇导入/导出 | Markdown | 人类可读，和 `.md` 互通 |
| AI 对话引用 | Markdown | 模型理解 markdown 远好于 XML |

**→ mnote 采纳**：CLI Main 用 XML 做默认格式是因为飞书文档本身是 XML 存储。mnote 当前主存储是 local-first `.md` 文件，历史 cloud / compat 内容也必须先投影成 markdown 后再进入 AI 编辑面，因此 markdown 是 mnote 的默认和唯一 AI 格式。这是正确的差异化决策。

#### 2.1.7 Skill CI 校验

> 参考文件：`.github/workflows/skill-format-check.yml`、`scripts/skill-format-check/`

CLI Main 的 CI 自动检查每个 `SKILL.md`：
1. 以 `---\n` 开头
2. YAML frontmatter 语法有效
3. `name` 和 `description` 必填
4. `metadata` 缺失仅为警告

**→ mnote 采纳**：对 Hermes `mnote` plugin 和所有 `~/.hermes/skills/note-taking/mnote-*/SKILL.md` 执行同等校验。在 CI 中增加 `skill-format-check` 步骤。

### 2.2 BlockNote AI — 流式 apply + suggest/review

BlockNote AI 的 `StreamTool` 实现了一套完整的 AI 编辑体验闭环：

**核心机制**：

```
LLM 流式输出 partial JSON
  → StreamToolExecutor 逐 chunk 解析
  → 匹配 operation type → validate → execute
  → ProseMirror 事务逐条 apply（带延迟，模拟"AI 正在打字"）
  → suggestChanges 标记 AI 编辑为 suggestion（红绿对比）
  → 用户逐条 accept / reject
  → 确认后才写入持久层
```

**关键组件**：

| 组件 | 功能 | mnote 远期对标 |
|------|------|---------------|
| `StreamTool` | 单操作的定义：name + inputSchema + validate + execute | mnote 已有（`mnote.block.*` / `mnote.doc.*`），不需要重构 |
| `StreamToolExecutor` | 流式解析 partial JSON → 逐条 enqueue → 按顺序 execute | Phase C 新增：`StreamApplyController` |
| `suggestChanges` | ProseMirror suggestion marks：AI 编辑不直接落盘，先标记为待审阅 | Phase C 新增：local-first `ReviewSession` / 临时审阅层 |
| `RebaseTool` | 协作场景：用户同时在编辑 → AI 操作 rebase 到最新文档状态 | Phase C 考虑；local-first 下优先基于 file version / BufferStore 冲突模型 |
| `delayAgentStep` | 逐条 apply 之间加 50-200ms 延迟，给用户"AI 正在操作"的可见性 | Phase C 可选改善 |

**BlockNote AI 不适合直接搬的原因**：它的所有操作都通过 `id`（blockId）寻址。`add` 需要 `referenceId`、`update` 需要 `id`、`delete` 需要 `id`。这意味着：

- 本地 `.md` 文件无法使用（没有稳定 blockId）
- 跨块内容修改（"把所有 TODO 改成 DONE"）需要模型逐一产出 N 个带 blockId 的操作
- 这和 mnote 的"markdown 优先"方向背道而驰

**但我们可以在 markdown_edit 之上叠加流式/review**：Phase C 时，`mnote.doc.markdown_edit` 的 apply 过程可以流式化——逐条 search/replace 执行后推送 BlockDelta，编辑器逐条渲染，用户可逐条撤回。这和 BlockNote AI 的体验效果一致，但底层是文本级操作而非块级操作。

### 2.3 Tiptap AI Autocomplete — 内联补全（独立功能）

纯文本补全器：发送光标前文本 → 返回下一句。不是文档编辑方案。mnote 将来可以考虑作为独立的"内联 AI 补全"功能，和本文讨论的文档编辑工具分开设计。

---

## 3. 当前问题

### 3.1 块级编辑是 AI 的过度抽象

> 参考对照：CLI Main 证明 `str_replace` 足以覆盖大多数 AI 编辑场景，不需要强迫模型理解 blockId。

当前 AI 写入链路的问题不在于技术实现，而在于**模型必须理解 blockId 这个 UI 层的概念**：

```
用户: "把第一段改简洁一些"
  → 当前路径：模型 → { op: "replace", blockId: "block_1", content: "..." }
  → 期望路径：模型 → { search: "第一段原文", replace: "改写后文本" }
```

「把文章中所有 TODO 改成 DONE」这类跨块请求，当前需要逐一产出 N 个 `{ op: "replace", blockId: "..." }`。文本级 search/replace 只需一条：`{ search: "TODO", replace: "DONE" }`。

### 3.2 旧块级工具没有 local-first 默认写入语义

> 参考对照：CLI Main 的 skill 可以操作任何 `doc-token`（包括本地文件和云端文档），因为它用的是文本级 + XML 级工具，不依赖特定存储。

mnote 当前主线：页面已能定位 local folder `.md`，但普通 AI 正文编辑不能回到“必须构造完整 page context 或 block ops”的旧模型。AI 视角应看到受控文件引用、selection 和 allowed roots，而不是一份由前端拼出的第二事实源。

### 3.3 两套体系互不相认

| | 历史 cloud / compat 文档 | 当前 local-first `.md` 文件 |
|---|---|---|
| 存储 | 历史迁移源 / 显式 cloud compat / sync replica | 文件系统 `*.md` |
| AI 读取 | `mnote.doc.fetch` 兼容投影 | 当前文件引用 / Page Aggregate / local markdown |
| AI 写入 | `mnote.doc.*` 或 `mnote.block.*` 兼容 fallback | agent 原生 patch/diff + watcher 同步 |
| 写入粒度 | 工具代理，不能作为新增默认入口 | 文件级 patch + selection 辅助 |
| 设计定位 | 历史 / compat 边界 | 当前主路径 |

旧设计把 compat 文档和本地文件写成两套体系，容易让后续实现继续往旧 cloud / block 工具堆逻辑。当前必须收敛：**local-first `.md` 是默认事实源，markdown 是共同格式，compat 只服务迁移、显式 cloud 或 remote agent 边界。**

---

## 4. 设计原则

### 4.1 Markdown 是 AI 编辑的第一公民

> 参考对照：CLI Main 用 Markdown 做整篇导入/导出和对话引用，用 XML 做精确块编辑。mnote 直接用 Markdown 做所有 AI 操作，因为 mnote 没有 XML 存储层。

AI 最自然的编辑方式是对文本进行操作。块是 UI 概念，不是 AI 概念。当前 local-first 持久化格式就是 markdown；历史 cloud / compat 内容若需要进入 AI 编辑，也必须先投影成 markdown。

### 4.2 两层操作模型（兼容层）

| 层 | 工具 | 寻址方式 | 适用场景 | 占比 |
|----|------|---------|---------|------|
| **文件级（主）** | agent 原生 `diff/apply_patch/文件编辑` | 授权文件引用 / selection | local-first 普通 Markdown 改写 | 80%+ |
| **文本级（兼容）** | `mnote.doc.markdown_edit` | search/replace 文本对 | 显式 cloud / remote agent / 兼容旧页面 AI | 次要 |
| **块级（辅助）** | `mnote.doc.apply_block_ops` | blockId / matchText | "把第三块拖到第一块后面"、"精确删除引用块" | <20% |

### 4.3 local-first 主写入路径

```
页面 AI / ACP
  → resolve_current_file(documentId, sourceKind=local_folder, rootUri)
  → 传授权文件引用 / selection / allowed roots 给 agent runtime
  → agent 原生 patch/diff 写入文件
  → MNote 做权限 / 审计 / watcher refresh / BufferStore 冲突
  → 仅在显式 cloud / remote compat 时 fallback 到 mnote.doc.markdown_edit
```

当前执行层不再二选一。默认只有 local-first 授权文件路径；cloud 或受限 remote runtime 无法直接访问本地文件时，才显式走 `mnote.doc.markdown_edit` 代理。这和 CLI Main 的 `docs +update` 可以操作任何 `doc-token` 的设计一致，只是 mnote 进一步把“编辑算法”让渡给 agent runtime。

### 4.4 Diff 是内部实现细节

对兼容 `mnote.doc.markdown_edit` 来说，AI **不**产出 unified diff（行号/上下文极易出错），而是产出两种形式之一：

| 形式 | 适用场景 | AI 负担 |
|------|---------|---------|
| `operations: [{ search, replace }]` | 局部修改 | 低：只需找原文片段 |
| `full_content: "..."` | 小文档全文改写 | 低：直接写完整 markdown |

而在 local-first 主路径中，agent runtime 自己可以安全使用成熟的 diff / apply_patch / 直接文件编辑能力；MNote 只要求这些写入被限制在授权路径内，并把 changed files / diff 摘要收回审计。

---

## 5. 工具合同

### 5.1 `mnote.doc.fetch`（已有，增强）

```json
{
  "toolName": "mnote.doc.fetch",
  "args": {
    "documentId": "tree_xxx 或 /path/to/file.md",
    "format": "markdown",
    "scope": "full",
    "query": "TODO",
    "maxChars": 8000
  }
}
```

增强点：
- `format: "markdown"` — 兼容值。local-first 文档直接返回 `.md` 原文；历史 cloud / compat 文档必须经 Page Aggregate 投影成 markdown。
- `documentId` — 当前默认解析为 local-first 文档；只有显式 sourceKind / compat 标记允许进入 cloud adapter。

返回：

```json
{
  "ok": true,
  "source": "local_folder",
  "documentId": "local-md:README.md",
  "fileVersion": "local-md:README.md:...",
  "format": "markdown",
  "content": "# 标题\n\n段落内容...\n\n## 子标题\n\n...",
  "truncated": false,
  "charCount": 1234
}
```

### 5.2 `mnote.doc.markdown_edit`（新增，兼容 / fallback）

```json
{
  "toolName": "mnote.doc.markdown_edit",
  "args": {
    "documentId": "tree_xxx 或 /path/to/file.md",

    "operations": [
      { "search": "原文片段", "replace": "新文本" },
      { "search": "另一段", "replace": "改写后的内容" }
    ]
  }
}
```

服务端处理流程：

```
1. resolve_source(documentId, sourceKind, rootUri) → LocalFSAdapter | explicit CompatAdapter
2. 读取当前 markdown 全文
3. 逐条 search_replace（精确匹配 → fuzzy fallback）
4. 计算内部 diff（用于 BlockDelta 推送）
5. 写入目标
6. 返回结果
```

返回：

```json
{
  "ok": true,
  "source": "local_folder",
  "documentId": "local-md:README.md",
  "fileVersion": { "before": "local-md:README.md:...", "after": "local-md:README.md:..." },
  "operationsApplied": 2,
  "operationsFailed": 0,
  "failedOperations": [],
  "changedText": "已将「原文片段」替换为「新文本」\n已将「另一段」替换为「改写后的内容」",
  "blockDelta": {
    "documentId": "local-md:README.md",
    "fileVersion": "local-md:README.md:...",
    "operations": [...]
  }
}
```

### 5.3 `mnote.doc.apply_block_ops`（已有，辅助路径）

保留不变。用于精确块结构调整（拖拽排序、指定 blockId 的精确删除）。在 Hermes tool manifest 中 `markdown_edit` 排在 `apply_block_ops` 前面。

---

## 6. 搜索替换语义

> 参考对照：CLI Main 的 `str_replace` 使用精确子串匹配。mnote 增加 fuzzy fallback 以提高模型输出容错率。

| 优先级 | 策略 | 说明 |
|--------|------|------|
| 1 | 精确匹配 | 原文字串精确匹配，区分大小写 |
| 2 | 宽松匹配 | 忽略首尾空白、全角/半角差异后匹配 |
| 3 | 段落 fuzzy | 按换行分段，每段独立 fuzzy match（允许 30% 字符差异） |
| 4 | 失败 | 返回 `operationsFailed`，列出无法匹配的 search 和原因 |

多条 operation 按数组顺序串行执行。前一条的替换结果对后一条可见（和 sed 语义一致）。

**乐观锁**：local-first 下执行前用 file version / conflict detection key 检测。如果写入时文件版本已过期，返回冲突错误，让 AI 重新 fetch + edit；历史 cloud compat 才能使用旧 revision 语义。

---

## 7. 远期规划：流式 apply + suggest/review（Phase C，当前不实施）

> 参考对照：BlockNote AI 的 `StreamToolExecutor` + `suggestChanges` + `delayAgentStep`。本节仅做设计规划，不列入当前实施阶段。

### 7.1 目标

当用户通过页面 AI 面板发起编辑后，不是等所有操作完成后一次性刷新，而是：

1. AI 逐条产出 search/replace 对（流式）
2. 服务端逐条 apply 并推送 BlockDelta
3. 编辑器逐条渲染修改（带延迟，模拟"AI 正在编辑"）
4. 用户可逐条 accept / reject（suggestion 模式）
5. 确认后才最终写入本地 `.md` 或显式 compat 目标（review 模式）

### 7.2 与 BlockNote AI 的差异

| | BlockNote AI | mnote Phase C |
|---|---|---|
| 操作粒度 | 块级（add/update/delete block） | 文本级（search/replace 文本对） |
| blockId 依赖 | 必须 | 不需要（文本匹配） |
| 本地文件支持 | 不支持（无稳定 blockId） | 支持（文本匹配不依赖 blockId） |
| Suggestion 层 | ProseMirror `suggestChanges` marks | Tiptap 的 suggestion 扩展或自建 |
| 写入时机 | 用户 accept 后统一写入 | 同 |

### 7.3 组件设计（草图）

```
StreamApplyController（新增）
  输入：LLM 流式输出的 partial operations JSON
  处理：
    1. 解析 partial JSON → 提取已完成的 operation
    2. 执行 search_replace → 产生 BlockDelta
    3. 通过 SSE 推送 delta 到编辑器
    4. 可选：注入 delay（50-200ms）模拟人类编辑节奏
  输出：逐条 BlockDelta

ReviewSession（新增，local-first 审阅层）
  状态：pending → accepted | rejected
  存储：SQLite control-plane / 本地 transient review state；不依赖 Convex
  生命周期：
    - AI 编辑 → 创建 ReviewSession → 所有修改标记为 pending
    - 用户逐条操作 → accept/reject → 更新 session 状态
    - 全部处理或用户确认 → 最终写入 → 关闭 session

GhostTextOverlay（新增，编辑器）
  参考：Tiptap AI Autocomplete 的 ghost text 覆盖层
  用于：展示 AI 修改前后的 diff（红删绿增），用户 hover 查看详情
```

### 7.4 分阶段实施顺序

| 阶段 | 内容 | 依赖 |
|------|------|------|
| C-1 | `StreamApplyController`：流式 apply + SSE 逐条推送 delta | 7-13 EditorRuntimeActor delta 通道 |
| C-2 | `GhostTextOverlay`：编辑器内 diff 展示（红删绿增） | leptos-tiptap 的 decoration 能力 |
| C-3 | `ReviewSession`：local-first accept/reject session | SQLite control-plane / editor transient review state 设计 |
| C-4 | `delayAgentStep`：可选的编辑节奏模拟 | C-1 完成 |

**当前不做实施决策**。Phase C 的启动时机以 Phase A/B 完成后，编辑器 decoration 能力、BufferStore/file version 冲突模型和 SQLite control-plane review state 设计就绪为前置条件。

---

## 8. 实施阶段（当前）

### Phase A：文件引用主路径 + `mnote.doc.*` 兼容层

- [x] `mnote.doc.fetch` 增加 `format: "markdown"`（local-first `.md` / Page Aggregate markdown 投影）
- [x] `mnote.doc.fetch` 增加本地文件 source 路由（`local_folder` / `rootUri` / `local-md:*`）
- [x] 实现 `resolve_source(documentId, sourceKind, rootUri)` — 默认 `local_folder`；历史 cloud / compat 必须显式进入 compat adapter
- [ ] 页面 AI / ACP 普通正文编辑默认只传当前文件引用、可选 selection 和用户指令，不再默认构造完整 page context
- [ ] 本地 agent runtime 在 `allowed_roots / allowed_file_paths` 内执行 patch/diff，并把 changed files / diff 摘要回传 MNote
- [x] 实现 `search_replace(text, operations)` — 四级匹配策略（精确→宽松→段落 fuzzy→失败）
- [x] 历史 cloud / compat 写入 adapter 已降级为 fallback，不再作为 local-first 默认入口
- [x] 实现本地文件写入 adapter（`mnote.doc.markdown_edit` 检测到本地文件路径时直接 `fs::write` 写回，不经过 Convex）
- [ ] 内部 diff 生成 + BlockDelta 推送（复用 7-13 delta 基础设施，待 Phase C 实现）
- [x] Hermes tool manifest 注册 `mnote.doc.markdown_edit`
- [x] Hermes `mnote` plugin 更新：`mnote_doc_fetch` schema + `mnote_doc_markdown_edit` 新增
- [x] 乐观锁：local-first 使用 file version / conflict detection key；compat 路径才保留 revision 语义
- [x] 历史 browser smoke（cloud compat `format: "markdown"` + `markdown_edit` 搜索替换）仅作兼容证据，不代表当前主路径
- [x] 单元测试（`search_replace` 精确/失败/全文、`blocks_to_markdown` with_ids/heading 共 5 测试通过）
- [x] 浏览器 smoke（本地 `.md` 文件读取 `mnote_doc_fetch` + 写入 `mnote_doc_markdown_edit`，`full_content` 创建 + `operations` 搜索替换 + 回读验证全部通过）

### Phase B：`page_ai_workflow.rs` 收口

- [x] 退役 `direct_block_edit_operations`（正则抠「」的快路径，代码保留但路由跳过）
- [x] `/api/page-ai/block-edit-workflow` 不再被当作 local-first 主路径
- [x] 模型 system prompt 重构：从产块操作 JSON 改为产 search/replace 文本对
- [x] 补全 operation schema：`extract_markdown_operations_from_model_text` 处理新旧格式
- [x] 浏览器 smoke：`markdown_edit` 搜索替换通过，自然语言编辑路径可用

### Phase C：流式/review（规划中，不实施）

- [ ] 流式 apply（`StreamApplyController`）
- [ ] suggest/review（`ReviewSession` + `GhostTextOverlay`）
- [ ] `delayAgentStep` 编辑节奏模拟

> 见 §7。当前仅做设计规划，不实施。

---

## 9. 与参考实现和其他设计稿的关系

### 9.1 与 CLI Main 的关系

| CLI Main 概念 | mnote 对应 |
|--------------|-----------|
| `str_replace`（文本级） | `mnote.doc.markdown_edit`（兼容 / fallback） |
| `block_replace/insert_after/delete/move_after`（块级） | `mnote.doc.apply_block_ops`（辅助路径） |
| XML 用于精确编辑 | mnote 不用 XML（没有 XML 存储层），direct path 退役后全部走 markdown |
| Markdown 用于导入/导出/对话引用 | mnote 全部 AI 交互走 markdown |
| Skill 格式（YAML + Markdown + tool shortcuts） | Hermes `mnote` plugin（`plugin.yaml` + `SKILL.md` + tool manifest） |

### 9.2 与 BlockNote AI 的关系

| BlockNote AI 概念 | mnote 远期对应 | 状态 |
|-------------------|---------------|------|
| `StreamTool` + `StreamToolExecutor` | `StreamApplyController` | Phase C 规划 |
| `suggestChanges` + `AIExtension` state machine | `ReviewSession` + `GhostTextOverlay` | Phase C 规划 |
| `delayAgentStep` | 可选改善 | Phase C 规划 |
| `RebaseTool` | revision 乐观锁（已有） | 当前已覆盖 |
| 纯 blockId 寻址 | ❌ 不采用。mnote 用文本匹配 | — |

### 9.3 与其他设计稿的关系

| 设计稿 | 关系 | 修正状态 |
|--------|------|---------|
| 7-9 路线图 | 块级编辑降级为辅助，local-first 普通编辑改为授权文件 + agent patch/diff，markdown_edit 退到兼容层 | ✅ 已修正 |
| 7-10 checklist | 新增 Phase 9 markdown_edit | ✅ 已修正 |
| 7-12 工具路由 | PageAICommandRouter 主输出改为 markdown_edit | ✅ 已修正 |
| 7-13 EditorRuntimeActor | 补充 markdown_edit 的 delta 适配 | ✅ 已修正 |
| 3-13 本地 markdown | 新增 AI 工具接入章节 | ✅ 已修正 |

---

## 10. 禁止项

- 不删除 `mnote.block.*` 工具（保留为辅助路径）。
- 不强迫 local-first agent 产出 MNote 自定义 diff；Hermes / Reasonix 可使用自身成熟 patch / diff / apply_patch 能力，MNote 负责白名单权限、文件版本冲突和审计。
- 不要求本地文件有稳定的 `blockId`（本地文件没有 block identity）。
- 不在 markdown_edit 内部引入新的 AI 模型调用（diff 是确定性算法）。
- 不把 Convex / `documents:updateContent` 重新提升为当前正文主存储；`markdown_edit` 只在显式 cloud / compat 场景复用受控保存路径。
- 不把 `mnote.page.save` 重新描述为精确编辑主入口（它仍是兜底工具）。
- **不照搬 BlockNote AI 的纯 blockId 寻址模式**（与 mnote 的 markdown 优先策略冲突）。

---

## 11. 成功标准

- [x] `mnote.doc.fetch(documentId, format: "markdown")` 对历史 cloud / compat 文档返回正确 markdown（兼容证据）
- [ ] `mnote.doc.fetch(documentId, format: "markdown")` 对本地 `.md` 文件返回正确 markdown（兼容路径）
- [x] `mnote.doc.markdown_edit` 的简单搜索替换（1 条 operation）浏览器 smoke 通过
- [ ] `mnote.doc.markdown_edit` 的复杂改写（3+ 条 operations）成功率 > 80%
- [ ] 本地 `.md` 文件通过页面 AI 面板可被读取和写入，且默认通过 agent 原生 patch/diff 完成
- [x] 历史 cloud / compat markdown_edit 不增加额外 RTT（兼容证据，不代表当前主路径）
- [x] `direct_block_edit_operations` 已退役（路由跳过，代码保留）
- [x] `page_ai_workflow.rs` 的 system prompt 已补全 search/replace schema
- [x] Phase C（流式/review）的设计已冻结，不阻塞 A/B 实施

---

## 12. CLI Main 成熟度采纳清单

以下清单按优先级排列，标注当前状态和对应 CLI Main 参考位置。

### 12.1 当前 Phase A/B 必须完成的

| # | CLI Main 模式 | mnote 实施项 | 状态 | 参考文件 |
|---|-------------|-------------|------|---------|
| 1 | 两层操作模型 | local-first 默认 agent 原生 patch/diff；`markdown_edit`（compat fallback）+ `apply_block_ops`（结构辅助）已在 7-10 Phase 9 登记 | 📋 设计完成，已按 2-2 改口径 | `lark-doc-update.md` |
| 2 | Scope 四级控制 | `mnote.doc.fetch` 增加 `scope: full/section/outline/keyword` | 🔜 Phase A | `lark-doc-fetch.md` |
| 3 | Detail 三级控制 | `mnote.doc.fetch` 增加 `detail: simple/with_ids/full` | 🔜 Phase A | `lark-doc-fetch.md` |
| 4 | 片段包装 | fetch 返回中标记 `<!-- fragment -->` / `<!-- excerpt -->` 告知 AI 部分视图 | 🔜 Phase A | `lark-doc-fetch.md`（fragment/excerpt 模式） |
| 5 | Code-Act Loop | Hermes `mnote` plugin SKILL.md 中内联 Plan→Execute→Observe→Iterate 指导 | ✅ 已写入 `~/.hermes/skills/note-taking/mnote-block-ai/SKILL.md` | `lark-doc-update-workflow.md` |
| 6 | 更新命令决策树 | 模型 system prompt 追加"优先搜索替换，不全文覆盖"规则 | 🔜 Phase B | `lark-doc-update.md`（str_replace vs block_* 决策） |
| 7 | 写后回读 | Hermes 每次写操作结束后自动 `mnote.doc.fetch` 回读 | 🔜 Phase B | `lark-doc-update-workflow.md` |
| 8 | Markdown 优先格式策略 | mnote 默认和唯一 AI 格式为 markdown（不需要 XML） | ✅ 已确认 | `lark-doc-md.md`（对比 XML 路线） |

### 12.2 Phase A/B 完成后应补充的

| # | CLI Main 模式 | mnote 实施项 | 状态 | 参考文件 |
|---|-------------|-------------|------|---------|
| 9 | Skill YAML 规范化 | Hermes `mnote` plugin 的 `plugin.yaml` 增加 `description`、`metadata.domain` | ✅ 已更新 v0.2.0 | `master-skill-template.md` |
| 10 | 参考文件分离 | `~/.hermes/skills/note-taking/mnote-block-ai/references/` 目录已创建，`mnote-doc-fetch.md` 已写入 | ✅ 已创建（fetch），其余按需补充 | `skills/lark-doc/references/` |
| 11 | Skill CI 校验 | CI 步骤：检查所有 plugin.yaml 和 SKILL.md 的 YAML frontmatter 有效性 | 📋 待实施 | `skill-format-check.yml` |
| 12 | 格式规则文档 | `references/mnote-doc-md.md`：定义 markdown 中 AI 应理解的特殊语法（任务列表标记 `- [ ]`、附件链接 `[file:...]` 等） | 📋 待实施 | `lark-doc-md.md` |
| 13 | 上下文冻结规范 | `references/mnote-doc-context.md`：冻结时保留哪些字段、截断规则、revision 注入方式 | 📋 待实施 | `lark-doc-fetch.md`（fetch body 构建） |

### 12.3 远期规划（Phase C 及以后）

| # | CLI Main 模式 | mnote 实施项 | 状态 | 参考文件 |
|---|-------------|-------------|------|---------|
| 14 | 跨域资源路由 | 文档内嵌思维导图/附件时，路由到对应 skill（见 §2.1.5） | ⏳ Phase C 后 | `lark-doc/SKILL.md`（路由表） |
| 15 | 流式增量 apply | BlockNote AI 的 `StreamToolExecutor` 模式，见 §7 | ⏳ Phase C 规划 | `blocknote-ai/packages/xl-ai/` |
| 16 | suggest/review | BlockNote AI 的 `suggestChanges` 模式，见 §7 | ⏳ Phase C 规划 | `blocknote-ai/packages/xl-ai/` |

### 12.4 与 BlockNote AI / Tiptap AI Autocomplete 的采纳清单

| # | 参考模式 | mnote 采纳 | 时机 | 参考文件 |
|---|---------|-----------|------|---------|
| B1 | `StreamTool` 流式 apply | `StreamApplyController`（§7.3） | Phase C | `blocknote-ai/packages/xl-ai/src/streamTool/` |
| B2 | `suggestChanges` review 层 | `ReviewSession` + `GhostTextOverlay`（§7.3） | Phase C | `blocknote-ai/packages/xl-ai/src/AIExtension.ts` |
| B3 | `delayAgentStep` 编辑节奏 | 可选改善 | Phase C | `blocknote-ai/packages/xl-ai/src/streamTool/` |
| T1 | ghost text 内联补全 | 独立功能（非本文档范围） | 独立立项 | `tiptap-ai-autocomplete/src/` |

### 12.5 实施优先级总结

```
Phase A（当前立即）
  ├── #2 Scope 四级控制  ← mnote.doc.fetch 增强
  ├── #3 Detail 三级控制 ← mnote.doc.fetch 增强
  ├── #4 片段包装        ← fetch 返回值增强
  ├── #8 Markdown 优先   ← 已确认
  └── 授权文件引用 + agent 原生 patch/diff 主路径；mnote.doc.markdown_edit 作为 compat fallback

Phase B（Phase A 完成后）
  ├── #5 Code-Act Loop   ← Hermes plugin SKILL.md
  ├── #6 更新决策树      ← 模型 system prompt
  └── #7 写后回读        ← Hermes tool loop 逻辑

Phase A/B 完成后补充
  ├── #9-#13 Skill 规范化、参考文件分离、CI 校验、格式文档、上下文规范

Phase C（远期）
  ├── #14 跨域路由（Phase C 后）
  ├── #15 流式 apply（Phase C）
  └── #16 suggest/review（Phase C）
```
