# 📦 知识库构建模块

> 从属于 AHKB-CPS 统一系统。从文档构建基于 Obsidian 格式的知识库。交互菜单驱动，无需记忆参数。
> 🔴 **交互规则**：所有选项必须用数字编号展示，用户输入数字选择，**严禁使用 AskUserQuestion 工具或任何图形/卡片式选择菜单**（如客户端弹出的选项卡片）。
> 🔴🔴 **进度透明规则**：处理文档时每处理完一个文件必须在对话中报告进度（如 `[3/7] 处理完成：xxx.pptx`），让用户知道当前走到哪一步。**禁止静默处理，禁止连续处理多个文件后一次性报告。**
>
> 🔴🔴 **进入即展示菜单（★ 铁律）**：加载完本模块后，必须立即完整输出下方的「知识库构建模块菜单」，**禁止跳过菜单直接询问用户要做什么**。用户看到菜单后再选择。
>

## 模块概述

从文档构建基于 Obsidian 格式的知识库。交互菜单驱动，无需记忆参数。

### 📋 总体流程

1. **第一步：拆解文档** — 扫描**整个工作空间**中的所有文档，逐个提取知识元和资源
2. **第二步：链接成网** — 在各个知识元之间以及各种资源中建立交叉链接
3. **第三步：生成地图** — 生成知识地图（包括 Obsidian 格式和 HTML 版）

### 核心设计原则

> 🔴🔴🔴 **概念独立性原则（★ 最高优先级，知识元颗粒度的唯一标准）**
> 一个候选内容是否成为知识元，**唯一判据是"概念是否独立"**——该概念脱离上下文后，含义是否仍完整、可独立成立。
> - ✅ **独立** → **必须**抽取成知识元（该抽的必抽；不得因篇幅小、数量多而漏建）。
> - ❌ **不独立**（仅属某概念的组成部分 / 举例 / 补充 / 过渡）→ **不得**强行抽取：合入父级知识元展开，或弃置。**绝不凑数，绝不因"最少知识元数""字数底线""chunk/单元比"等数量指标而硬拆。**
> - 该原则**高于一切数量指标**。后者（字数底线、最少单元数、chunk/单元比）仅作为"是否真独立"的**辅助提示**：当候选偏短或某文档单元偏少时，先回头复核"是否确有独立概念被遗漏"；若复核确认概念本就少且独立，则保持原样，不强行拆分凑数。
> - 🔴🔴 **并行抽取（模式 2）特别约束**：每个子代理独立处理单文件、无全局视野，最易为"达标"把概念切得过碎。因此并行模式下更要克制——只把真正独立的概念抽成元，宁可单元数偏少也绝不凑数。

1. **原子性**：每个知识元只包含一个独立概念/观点/定义/事实（遵循上方"概念独立性原则"——只有独立的概念才成元）
2. **资源附属**：图片/视频/音频是知识元的附属资源，通过 `.ctx` 文件关联
3. **双向关联**：`.ctx` ↔ 知识元确定性挂接；知识元之间 `[[链接]]` 语义关联
4. **全局统一（★ 核心）**：同一概念在不同文档中只创建一个知识元
5. **🔴🔴 两阶段隔离（★ 最高优先级强制规则）**：Phase 1 只生产知识元，Phase 2 只做关联。**两个阶段之间有一道不可逾越的墙**
6. **🔴 全部处理原则（★ 强制）**：所有原始文件必须逐个处理完毕
7. **🔴🔴 自动审查原则（★ 强制）**：D.5 `verify-units` + F `mark-processed` 硬关卡双重保险，AI 读取 JSON 自动决策"通过/补做/回头"，**无需用户干预**。阈值随 `cGranularity` 参数自动调整。

### 🔴 绝对禁止规则

- **禁止将 skill 安装目录作为知识库工作空间**
- **禁止在 A→F 流程中生成 `[[链接]]`**
- **禁止将资源文件当知识元**
- **🔴🔴 临时文件 / 临时脚本一律写入 `临时工作文件/` 子目录，严禁在工作空间根目录、skill 安装目录、用户目录下裸写任何临时 .py / 中间文件。**
  - 任何核验、调试、批量生成脚本（如核查知识元、写知识元、清理脚本）必须显式写成 `临时工作文件/<name>.py` 并在其中使用相对/绝对路径指向工作空间，**不得直接落到工作空间根目录**。
  - 处理流程结束后必须对 `临时工作文件/` 做清理：移除本流程产生的临时脚本/中间文件（保留技能自身产出如 `concept_index.json`、`_maintain_completed.marker` 等）。
  - 根目录只允许出现技能规定的正式产物（知识元/、图片及其他资源/、chunks/、系统设置/、产品成果/、回收站/、`<知识库名称>.md`、`<知识库名称>-知识地图.html`、`_processed_docs.json`）；**任何 `_` 开头或 `.py` 散落根目录都视为违规残留，应立即移入 `临时工作文件/` 或回收站**。
- **🔴🔴 删除自动执行铁律（★ 2026-08-13 新增，任何删除都不准打扰用户）**：
  - 一切删除操作（清理临时文件、移除旧产物、清隔离目录等）**一律走统一删除命令**：
    ```bash
    python {skill_dir}/kb/scripts/ahkb_trash.py <路径> --workspace <工作空间绝对路径> [--recreate-dir]
    ```
    它优先 `os.rename` 把目标移入工作空间「回收站/」（同卷改名，**不触发 safe-delete、不向用户确认**）；rename 失败（跨卷/异常）时**静默直接删除，也不提示**。
  - 🔴 **严禁用 Bash `rm` / `rmdir` 删除任何文件或目录**——WorkBuddy 对删除命令有交互权限确认（"允许 / 始终允许 / 拒绝"），会打断全自动流程、反复打扰用户。也**严禁**直接调用 `os.remove` / `Path.unlink` / `shutil.rmtree`（沙箱 safe-delete 会拦截导致静默失败、残留垃圾）。
  - 需要"删除文件后保留原位目录结构"时加 `--recreate-dir`；需要彻底移除（不保留空目录）时不加。
  - 子代理 / 主代理 / 任何脚本一律遵守本铁律；清理 `临时工作文件/` 时同样用上述命令，**禁止任何形式的交互式删除确认**。
- **🔴 批次处理必须完成所有批次**
- **🔴 重建知识库时必须重新处理全部原始文件**
- **🔴🔴 严禁提前执行 crosslink / maintain**
- **🔴🔴 互动添加（菜单 3）严禁自动执行 maintain / crosslink**：用户选择这个菜单就是为了一步一步手工操作。每完成一批 A→F 后，只能询问用户"是否继续添加其他文档？"（🔴 用数字编号列表：1. 是 / 2. 否，严禁使用 AskUserQuestion 工具或任何图形/卡片式选择菜单），**绝对禁止自动触发 maintain**。即使用户一次性添加了所有未处理文档，也必须等用户通过菜单 6 或 7 手动启动后续关联。

### 工作空间结构

```
<工作空间根目录>/
├── 原始文件/               ← 📥 已处理文档归档目录
├── 知识元/                 ← 生成的 .md 知识单元
├── 图片及其他资源/{images,videos,audios,others}/
├── chunks/                 ← 原始文档分块存储 + index.json
├── 临时工作文件/           ← 临时脚本、中间文件
├── 产品成果/               ← AI 生成的产品成果文件
├── <知识库名称>.md         ← 🏆 总纲根节点
├── <知识库名称>-知识地图.html
└── 系统设置/               ← project_settings.json 等
```

### 🔖 知识库名称管理（全局统一规则）

> 🔴 知识库名称是知识库的"身份证"——根节点文件 `<名称>(根).md` 与 HTML 知识地图 `<名称>-知识地图.html` 的文件名均由它派生。菜单 0（从0开始）与菜单 1（重建）必须遵循下方统一规则，避免重复询问或无名构建。

- **存储位置**：`系统设置/project_settings.json` 的 `kb_name` 字段（同时镜像到 `_processed_docs.json` 的 `knowledge_base`）。
- **📌 系统缺省占位符「知识库」**：当 `init` 未携带 `--name` 时，系统自动把 `kb_name` 写成字面量 `"知识库"`（见 ahkb.py `cmd_init`：`kb_name or "知识库"`）；`get_kb_name` 在无名时同样回退 `"知识库"`。**该值只是"尚未命名"的占位符，绝对不能当作已设定的真实名称。** 🔴 因此：检测到的名称若等于 `"知识库"`，一律视同「名称缺失 / 未命名」。
- **设定命令**：
  - 首次初始化时一并写入：`init --name "<名称>"`（该命令同时创建全部目录结构）。
  - 随时修改：`set-kb-name "<名称>"`。
- **查询是否已命名**：读取 `系统设置/project_settings.json` 的 `kb_name`；**非空且 ≠ 缺省占位符 `"知识库"`** 才算已命名。满足以下任一即视为「未命名，必须询问用户」：
  1. `kb_name` 字段缺失 / 为空；或
  2. `kb_name` 等于 `"知识库"`（系统缺省占位符）。
- **📌 回退约定**：在「未命名」分支，若用户始终未提供新名称（直接回车 / 留空），允许回退使用缺省三字 `"知识库"`（系统会自动以它生成 `<知识库>(根).md` 与 `<知识库>-知识地图.html`），但**首次询问环节不得跳过**——必须先把命名机会交给用户。
- **📌 两菜单协同**：
  - **菜单 0（从0开始）**：进入时先执行 `init` 创建目录；随后判断——若 `kb_name` **真实已命名**（非空且 ≠「知识库」），询问「保留原名 / 修改」二选一；若**未命名**（缺失或 =「知识库」），**必须直接要求用户给出名称**，不要呈现"保留原名称「知识库」"这种选项（那不是真实名称）。详见 menu0.md 开头「初始化与命名」步骤。
  - **菜单 1（重建）**：破坏性清库**之前**（决策前置阶段）判断——`kb_name` 真实已命名时询问是否修改；若 `kb_name` 为缺省「知识库」，同样视作未命名、必须要求用户命名。详见 menu1.md Step 4.5 与 Step 6。

### 快速参考

```bash
python {skill_dir}/kb/scripts/ahkb.py <command> [--workspace <vault-path>]

# 常用命令：
#   scan                  扫描新文档
#   maintain --detached   启动知识链关联（detached 模式）
#   check-maintain-status 查询 maintain 状态（LLM 轮询专用）
#   build-graph           构建根节点
#   build-kg-html         生成 HTML 知识地图
```

> 🔴 **路径约定**：本文中所有路径均相对于 AHKB-CPS 技能目录（即 `{skill_dir}`），而非工作空间。

---

## 首次运行流程

### Step 1 — 检查初始化状态

```bash
python {skill_dir}/kb/scripts/ahkb.py find-root
```

- `found: false` → **首次运行**，先执行下方「Step 1.5 — 文档导入检测」
- `found: true` → **已有知识库**，直接进入主菜单

### Step 1.5 — 文档导入检测

1. **递归检查工作空间下所有文档**（子目录中的文档也会被检测到，排除 `.obsidian`、`.claude` 等隐藏目录）
2. **有文档** → 告知用户"检测到工作空间下有文档（含子目录）。这些文档会留在原位置，由后续菜单 0 扫描时逐个处理。"
3. **无文档** → 告知用户"当前工作空间没有可处理的文档。请把你的原始文档拷贝到当前目录或子目录下，然后重新运行菜单 0。"

### Step 2 — 设置权限

```bash
python {skill_dir}/kb/scripts/ahkb.py setup-permissions
```

### Step 3 — 初始化工作空间

首次运行时请用户选择（输入数字 1 或 2）：

1. ✅ 是，初始化工作空间
2. ➡️ 否，跳过

> ⚠️ **🔴 关键：选择创建后，必须直接问用户「请输入知识库名称」**

```bash
python {skill_dir}/kb/scripts/ahkb.py init --name <用户输入的知识库名称>
```

### Step 4 — 创建根节点

在工作空间根目录创建根节点 .md 文件，文件名 = `{{知识库名称}}(根).md`（如"大系统观知识库(根).md"）。

> 🔴 `build-graph` 已自动创建/更新根节点 .md，AI 无需手工操作。步骤 1 完成后根节点即就绪。

---

## 主菜单

### 📋 总体流程说明

每次显示主菜单前，先展示三步流程说明：

```
═══════════════════════════════════════════════════════
📋 本模块总体流程：
   第一步：拆解文档  → 扫描文档，提取知识元和资源
   第二步：链接成网  → 建立交叉链接，形成知识链网
   第三步：生成地图  → 生成Obsidian格式 + HTML格式的知识地图
═══════════════════════════════════════════════════════
```

### 📋 菜单

```
═══════════════════════════════════════════════════════
        AHKB-CPS：知识库构建模块菜单
═══════════════════════════════════════════════════════
  0. 🚀 从0开始全自动构建知识库与知识地图    — 建议初次使用时选择
  1. 🔨 全自动完全重建知识库       — 清除全部知识元和资源，重新构建
  2. 📦 全自动批量添加新文档         — 处理全部新增文档（先把新文档拷贝到工作目录，系统自动扫描）
  3. 📋 互动扫描添加新文档         — 手工模式：逐步挑选文档、逐个处理，不自动关联（先把新文档拷贝到工作目录）
  4. 🔄 重新重点处理某一文档       — 觉得AI处理某文档不够好时重新处理
  5. 🖐 手动编辑维护知识库         — 启动图形界面管理，手工修改知识元和资源，提高准确性
  6. 🛠 自动整理知识库并更新知识链  — 修复错误格式并刷新关联
  7. 🏗 重建知识地图+HTML          — 更新根节点、生成知识地图
  8. 🌐 生成HTML版知识地图         — 从现有根节点md文件生成
  9. 📊 知识库统计                — 查看统计数据
  s. ⚙ 设置                       — 调整颗粒度、链接权重等
  e. ↩️ 返回主菜单                — 返回AHKB-CPS统一主菜单
═══════════════════════════════════════════════════════
请回复数字 0~9、s 或 e 选择操作：
```

### 🔴 菜单循环规则

1. **只有用户选择「返回主菜单」才能返回统一主菜单**
2. **每完成一项操作后自动重新显示模块主菜单**
3. 每次返回模块主菜单时提醒用户：
   > 💡 AHKB-CPS 还提供以下模块：
   >   · 技能向导 — 帮你找到该用的模块
   >   · 文章生成 — 生成结构化文章
   >   · 幻灯片生成 — 基于知识库生成专业演示文稿
   >   · 全息脑图生成 — 生成阿色全息脑图
4. 🔴 **每一次轮询都必须向用户展示原始结果**（如 `⏳ [5/12] 关键词匹配，已用时 2min`），不得只写"正在处理"。用户看到进度才不焦虑。

### 🔴 全自动流水线 loop 执行（★ 全局规则，菜单 0/1/2 强制，2026-08-13 新增）

> 🔴🔴 **目标**：三个全自动菜单（0/1/2）启动后**自主循环（loop）直到全部完成**：阶段间自动衔接、
> 失败自动重试、中断自动续跑——**全程不向用户下指令、不等待确认**。判定交给 Python
> （`pipeline-status`），主代理只负责执行动作（沿用"循环判定不进 LLM"铁律，见「③ 脚本化等待门控」）。
> 中断后重新调用技能时**自动续跑**（不清库、不重做已完成阶段）。

**① 断点状态文件：`系统设置/_pipeline_state.json`**（跨会话 / 重启有效）
- 字段：`pipeline`（menu0/1/2）、`mode`（sequential/parallel，B0 选择，**续跑时复用、不再询问**）、
  `phase1/phase2/phase3` 各含 `{complete, retry}`、`all_complete`、`last_error`。

**② 判定命令 `pipeline-status`**（★ 判定节点，主代理不自己猜流程走到哪）
- **完成度以"实测硬证据"为准**（manifest / marker / HTML，不信任主观标记）：
  - Phase1 = 无未处理文档（manifest + 扫描）；Phase2 = maintain 完成标记
    `临时工作文件/_maintain_completed.marker`；Phase3 = `<知识库名称>-知识地图.html` 已生成。
  - 🔴 因此 `pipeline-mark done` 只负责**清零重试计数与留档**，阶段是否完成由实测决定——**任何"标记了却无硬证据"的状态都会被 pipeline-status 判为未完成，杜绝假完成**。
- 返回 `next_action`：`phase1` / `phase2` / `phase3` / `done`（全部完成）/ `error`（某阶段连续失败 ≥5 次）/ **`phase1_wait_required`（★ 2026-08-14 新增守卫：Phase1 未完成 + `phase1_dispatch` 锚点存在 + wait 进程未存活 + 距派发已超 2 分钟 → 主代理 loop 顶部发现后必须立即补执行 `phase1-wait`，不得忽略）**。
- 用法：`python {skill_dir}/kb/scripts/ahkb.py pipeline-status --workspace <工作空间绝对路径>`

**③ 写状态命令 `pipeline-mark`**（★ 写节点，阶段成功/失败后调用）
- 全新开始初始化：`python {skill_dir}/kb/scripts/ahkb.py pipeline-mark start --pipeline <menu0|menu1|menu2> --mode <sequential|parallel> --workspace <工作空间绝对路径>`
- 阶段成功：`python {skill_dir}/kb/scripts/ahkb.py pipeline-mark done --phase <phase1|phase2|phase3> --workspace <工作空间绝对路径>`
- 阶段失败：`python {skill_dir}/kb/scripts/ahkb.py pipeline-mark fail --phase <phase1|phase2|phase3> --reason "<原因>" --workspace <工作空间绝对路径>`
- 全部完成：`python {skill_dir}/kb/scripts/ahkb.py pipeline-mark finish --workspace <工作空间绝对路径>`

**④ 主代理 loop 规则（★ 强制，菜单 0/1/2 全自动阶段统一执行）：**
```
# ── 进入判定（自动续跑 vs 全新开始）──
st = pipeline-status
if st.next_action in ("phase1", "phase2", "phase3"):
    告知用户"检测到上次构建未完成（<stage>），自动从断点继续"
    直接进入下方循环（跳过初始化/清库/扫描/B0/顺序等交互，mode 复用状态文件记录值）
else:
    执行"全新开始"准备（初始化与命名 / 清库 / 扫描 / B0 抽取模式 / 处理顺序等，按菜单语义）
    pipeline-mark start --pipeline <本菜单> --mode <用户所选 sequential|parallel>
    进入下方循环

# ── 循环（直到 done 或 error，★ 阶段间自动衔接，不询问用户）──
loop:
    st = pipeline-status
    if st.next_action == "done":   报告完成（各阶段汇总），pipeline-mark finish，退出循环
    if st.next_action == "error":  停止；把 last_error 与重试次数报告用户（唯一停下等用户的情形）
    if st.next_action == "phase1_wait_required":  立即补执行 phase1-wait（守卫触发，不得忽略），回到 loop 顶部
    if st.next_action == "phase1": 执行 Phase 1 抽取（顺序：主代理 A→F；并行：phase1-group → phase1-dispatch --auto-wait（锚点+自动拉起 wait）→ 派发子代理 → phase1-peek 播报循环）
        成功 → pipeline-mark done --phase phase1
        失败 → pipeline-mark fail --phase phase1 --reason "<原因>"（自动重试，回到 loop 顶部）
    if st.next_action == "phase2": 执行 Phase 2 关联（maintain --detached + check-maintain-status 轮询）
        成功 → pipeline-mark done --phase phase2
        失败 → pipeline-mark fail --phase phase2 --reason "<原因>"
    if st.next_action == "phase3": 执行 Phase 3 出图（build-graph + build-kg-html + set-graph-colors + 打开浏览器）
        成功 → pipeline-mark done --phase phase3
        失败 → pipeline-mark fail --phase phase3 --reason "<原因>"
```

**⑤ 防死循环**：每阶段连续失败 ≥5 次 → `pipeline-status` 返回 `next_action=error`，主代理停止并报告用户。
**同一阶段的自动重试无需用户确认**（fail 后回到 loop 顶部再次执行）；只有 `error` 才停下等用户。

**⑥ 中断恢复（物理边界）**：若 WorkBuddy / 电脑完全关闭（进程已死），必须由用户重新打开并调用技能
**一次**才能继续——但**那一次调用由"进入判定"的自动续跑直接接管**：读 `_pipeline_state.json` → 未完成
→ 自动从断点阶段续跑，**不再询问、不再清库**。会话内的一切中断（阶段失败/卡死/超时）均由 loop 自动处理。

**⑦ 阶段成功/失败判定标准（主代理执行时对照）：**
- Phase1 成功 = `pipeline-status` 显示 `phase1_complete:true`；失败 = extract 系统性失败 / 并行子代理 stuck 重派仍失败 / mark-processed 反复被拒等。
- Phase2 成功 = `check-maintain-status` 返回 `status:"complete"`（写入完成标记）；失败 = `error` / `stale` / `hung` / `cancelled`。
- Phase3 成功 = `build-kg-html` 正常返回且 HTML 生成；失败 = 命令报错 / 产物缺失。

> 🔴 **本小节是菜单 0/1/2 全自动阶段的统一驱动**；各菜单的"阶段内部执行细节"（顺序 A→F 步骤、并行
> phase1-group/wait、maintain 轮询、build 命令序列）见各菜单 Step 详情。二者配合：**loop 决定"做什么阶段"，
> 菜单 Step 决定"该阶段怎么做"**。

---

## 文档处理核心流程（两阶段法）

### 📂 文档处理优先级

`.md` → `.pdf` → `.docx` → `.pptx` → `.xlsx` → `.html/htm` → `.txt` → 其他格式

### 第一阶段：批量处理所有文档（A→F，🔴 严格禁止做任何关联）

#### 📌 大文档分批策略

超大数据时：按照 **20~30 个 chunk/批** 的规模分批处理。

> 抽取采用何种模式（顺序单线程 / 并行子代理），由 **B0 抽取模式选择** 步骤交由用户决定，本系统不再强制单线程。

### ⛔ Phase 1 / Phase 2 分界线

```
┌─────────────────────────────────────────────────────────┐
│  🔴🔴 警告：此处是第一阶段和第二阶段之间不可逾越的墙 🔴🔴 │
│                                                         │
│  第二阶段必须在【所有 N 个文件全部完成 A→F +            │
│  mark-processed】后才能执行。                            │
│                                                         │
│  进入第二阶段前，强制自检：                                │
│  □ 所有文件都 mark-processed 了吗？（是 / 否）            │
│  □ 待处理文件数 = 0 吗？（是 / 否）                       │
│  □ 所有文件的 D.5 verify-units 已通过？（是 / 否）        │
│  → 三项全部为"是"才允许进入第二阶段                       │
│                                                         │
│  🔴🔴 脚本级硬关卡：maintain 命令内置了 Phase 1 完成度   │
│  检查。如果还有未处理文件，maintain 会直接拒绝执行并     │
│  返回 status: "phase1_incomplete"。不可绕过。             │
└─────────────────────────────────────────────────────────┘
```

### 第二阶段：统一关联（全部生成后再做）

**前置条件：全部 N 个文件已完成 A→F（含 D.5 verify-units 验证通过）并 mark-processed。**

通过 `maintain` 命令完成三类关联：
- **知识元↔知识元** `[[链接]]`
- **知识元↔原始文件** 关联
- **知识元↔资源**双向挂接

> ⚠️ maintain **必须弹出图形窗口（tkinter GUI）**，绝不允许隐藏或后台运行。
> 🔴 **严禁**任何绕过 GUI 的行为。
> 🔴🔴 **AI 调用铁律**：AI 只能以 `maintain --detached` / `cross-link --detached` 触发关联（独立 GUI 子进程，1 秒返回，AI 用 `check-maintain-status` 轮询并向用户逐回合播报）。**绝对禁止**用 `run_in_background` 后台跑、调用 `run_crosslink.py` 等无头驱动脚本、设置 `_GUI_AUTHORIZED=True` 进程内直跑、或使用 `--headless`。若环境无任何 tkinter 解释器，程序会**明确报错中止**而绝不转为隐藏后台运行。GUI 执行完自动关闭（10 秒倒计时），AI 随即自动继续 build-graph / build-kg-html，不因等用户而中断。

---

### B0. 抽取模式选择（★ 用户决策步骤）

> 🔴 **每次进入 Phase 1 前，必须向用户提问二选一，严格按用户选择执行；不得自行默认。**

请选择抽取模式（🔴 必须输入数字 1 或 2，严禁使用图形/卡片式选择菜单，包括 AskUserQuestion 工具）：

1. 顺序单线程（推荐） — 逐文件 / 逐批由主 agent 亲自顺序处理；跨批一致性最好（概念索引顺序去重天然稳），速度不变。
2. 并行子代理抽取 — 先用 `phase1-group` 按预估工作量**均衡分组（默认上限 4 组）**：大文件独占一组、小文件打包给同一子代理顺序处理；每组由一个子代理并发处理，速度近似线性提升；靠「🔴 写前查盘合并（★ 强制）」规则在落盘前合并同名知识元，无需事后全库去重。

你的选择（输入数字 1 或 2）：

用户选定后：
- 选 **1** → 按「大文档分批策略」（20~30 chunk/批）由主 agent 亲自顺序处理：每批分析 chunk → 创建知识元 → 执行 `update-concept-index`；下一批加载最新索引去重后创建。
- 选 **2** → 主代理先对全部待处理文件跑一次 `extract`（确定性、拿准确工作量）→ 运行 `phase1-group` 按预估工作量**均衡分组（≤4 组）**→ 每组派发**一个**子代理顺序处理组内全部文件；每个子代理写知识元前必须执行「🔴 写前查盘合并（★ 强制）」规则；所有文件完成 A→F + mark-processed 后进入 Phase 2 关联（maintain），无需事后全库去重。

> 🔴🔴 **适用范围与落地（★ 强制）**：本 B0 模式选择**适用于全部全自动菜单（菜单 0 / 1 / 2）**，必须作为进入 Phase 1 前的交互断点执行，不可省略、不可默认；菜单 0 的 Step 1.4、菜单 1 的 Step 4、菜单 2 的 Step 1.4 均已落实此提问。任何菜单若声称"唯一交互断点"却只问处理顺序、漏问抽取模式，均属违规，须立即补齐。**菜单 3（互动扫描添加）与菜单 4（重新重点处理某一文档）均不适用 B0、属设计性豁免而非遗漏**：菜单 3 为手工驱动模式、Step 3 天生「逐个顺序处理」且严禁自动 maintain；菜单 4 仅重处理单文档、Step 3 已明令「禁止使用子代理」，二者与并行子代理的后台派发/轮询范式冲突，故锁定为顺序单线程、不呈现 B0 二选一（已分别在 menu3.md / menu4.md 注明）。**

#### 🔴 并行模式（选项 2）的核心契约与完成门控（★ 强制，解决"主代理傻等"问题）

**根因**：并行派发后台子代理后，主代理不能依赖"后台 agent 完成通知"来判定结束——实测中该通知不可靠，导致知识元都已写完、主过程却一直空等。解决方案：**以 `原始文件/_processed_docs.json`（manifest）为唯一真相源，主代理自己轮询完成状态。** 卡死判定采用**方案 C 心跳静默窗口**：与文档总时长解耦，大文档只要持续心跳就永不误判。

**① 子代理契约（★ 2026-08-13 起为"分组"契约：一个子代理处理一组多个文件）：**
> 🔴 **分组派发（方案 B）**：主代理派发前先批量 `extract`（确定性，把 `chunks/index.json` 建立起来），再运行 `phase1-group --from-scan --max-groups 4` 得到 ≤4 组；每组派发**一个**子代理，由它**按列表顺序逐个处理组内文件**（每个文件独立跑完 A→F + 收尾，跑完一个再开始下一个）。`phase1-dispatch --files <组内全部文件>` 的派发锚点必须包含组内所有文件——子代理对组内任一文件的心跳/标记都会命中锚点。
>
> 子代理提示词必须明确要求：组内每个文件的 A→F 全流程**在任务返回前跑完**，临时脚本/日志一律写入 `临时工作文件/`、严禁落根目录，每个文件最后一步强制执行
```bash
python {skill_dir}/kb/scripts/ahkb.py mark-processed "原始文件/<该文件相对路径>"
```
并**读取其返回 JSON 自校验**：
- 返回 `ok:true` → 该文件完成，子代理正常返回。
- 返回 `warning:true`（如零知识元被拒 `no_knowledge_units`，**现同时返回 `ok:false`**）→ 子代理**必须回到 [C][D] 回补知识元后重新执行 `mark-processed`**，直到 `ok:true` 才返回。**严禁"写完知识元就走人、不跑 mark-processed"。**
- 🔴 **心跳（★ 强制）**：子代理**每处理完一个 chunk** 立即执行一次心跳（针对**当前正在处理的文件**）；若单个 chunk 处理预计超过 30 秒，则必须**每 30 秒额外心跳一次**（更新该文件的 `last_heartbeat` 时间戳）：
```bash
python {skill_dir}/kb/scripts/ahkb.py phase1-heartbeat "原始文件/<该文件相对路径>"
```
  心跳频率天然等于 chunk 节奏（约 0.5~2 分钟/次），对于大 chunk 再通过每 30 秒定时心跳兜底，足以区分"正在慢慢干"与"真卡死"。即使偶发漏调一次，只要下一 chunk 或下一个 30 秒补上即可；连续超过 `HEARTBEAT_SUSPECT_MINUTES`(4 分钟) 无心跳才进入预警，超过 `HEARTBEAT_SILENCE_MINUTES`(10 分钟) 才判 stuck，留有远大于单 chunk 耗时的余量，不会误伤大块处理。

  **🔴 子代理提示词模板（分组版，★ 2026-08-13 起取代单文件版；★ 2026-08-14 起含"用户抽取要求"注入；派发时直接套用，★ 强制包含心跳契约）：**
  主代理为每个后台子代理注入下方提示词（将 `<文件1相对路径>...<文件M相对路径>` / `<工作空间绝对路径>` / `<skill_dir>` / `<cGranularity模式>` / `<cTextAmount模式>` / `<抽取要求>` 替换为实际值；`<cGranularity模式>` 与 `<cTextAmount模式>` 从本工作空间 `系统设置/project_settings.json` 的 `cGranularity` / `cTextAmount` 取值后译为「精细/均衡/粗粒」「摘要/适中/详细」填入；**`<抽取要求>` 从同一文件的 `extraction_requirements` 字段读取后填入，无则写「无」**）。该模板已内置"每 chunk 心跳 + 长 chunk 定时心跳""跑完 mark-processed 才返回""写前查盘合并""Phase1 禁止 [[链接]]""按文件逐个收尾""用户抽取要求优先"等硬约束，缺任一条即违反并行契约：
  ```
  你负责处理知识库构建中的【一组】原始文件（共 <M> 个）。请按下方文件列表**逐个文件**独立完成
  A→F 全流程并跑完收尾校验——**跑完一个文件的全部步骤（含 mark-processed 返回 ok:true）再开始下一个**，
  组内全部文件都完成前，你的任务不允许结束。

  工作空间：<工作空间绝对路径>
  技能脚本目录：<skill_dir>
  待处理文件列表（按此顺序逐个处理）：
    1. 原始文件/<文件1相对路径>
    2. 原始文件/<文件2相对路径>
    ...
    M. 原始文件/<文件M相对路径>

  🔴🔴 用户本次抽取的额外要求（★ 最高优先级，必须严格遵守；与默认参数冲突时以本条为准）：
  <抽取要求>

  ── 你的任务（对列表中的【每一个文件】重复执行以下 A→F）──
  A. 提取内容：python <skill_dir>/kb/scripts/ahkb.py extract "原始文件/<该文件相对路径>" --workspace <工作空间绝对路径>
     （若该文件的 `chunks/<文件>.json` 已存在——主代理已批量预抽取——可直接跳到 B，复用已有 chunks，无需重复 extract。）
     🔴 **抽取结果 ok 闸门**：extract 返回的 JSON 含 `ok` 字段。
       - `ok:true` 且给出 `chunks_path` → 进入 B/C（以该 chunk JSON 为唯一输入）。
       - `ok:false`（含 `chunks_error` / `extract_error`）→ **立即停止并上报主代理，严禁把失败当成功继续**。
  B. 分块：extract 已自动落盘 `chunks/<文件>.json`，以它为唯一输入，**严禁为生成知识元而重读整篇原始文档**。
  C. 创建知识元：逐个 chunk 分析，遵守下方命名/原子性/格式铁律（按 `<cGranularity模式>` / `<cTextAmount模式>` 把握粗细与展开深度）：
     🔴🔴 **概念独立性原则（★ 唯一标准）**：只把"脱离上下文含义仍完整"的独立概念抽成知识元；
       不独立的组成部分（子步骤、举例、补充、过渡）合入父级或弃置，**绝不凑数、绝不硬拆**。
       数量/字数指标只是"是否真独立"的提示，绝不是强制标准。
     🔴🔴 **Phase 1 禁止写入任何 `[[链接]]`（wiki-link）**：本阶段（A→F）绝不可在知识元中写 `[[...]]`。
       关联由第二阶段 `maintain` 统一添加；任何 `[[链接]]` 写入都属违规，会污染知识库、破坏两阶段契约。
     🔴 **强制独立**：人物、定义必须独立成元（任何参数下不得合并）。
     🔴 **命名规则（★ 写前查盘合并依赖此约定，务必严格遵守）**：
       - 文件名（不含 `.md`）**必须等于 H1 标题，一字不差**；简短，1~12 字。
       - 名称必须是规范概念/术语；严禁：章节编号（`1.1 概述` / `第3章` / `§2.3` / `2、主打产品`）、
         编号前缀（`KD-001-xxx` / `A-耦合度` / `(1)工具` / `[1] 引言`）、括号/方括号整体包裹
         （`[参考文献]` / `(工具)` / `【系统哲学】`）、纯数字或带括号纯数字（`123` / `(28)` / `[40]`）、
         装饰符（`→→→` / `★★` / `===`）、字符画（`╔══╗`）。
       - 起名前**强制消噪**：剥离 chunk 标题里的章节编号/括号/方括号，只取真实概念名（如 `(1)工具` → `工具`、`[参考文献]` → `参考文献`）。
       - 形如 `(28)` / `[40]` 的纯数字/页码**不是概念名**，不得建元；若正文无独立概念，应合入上级或弃置。
     🔴🔴 **写前查盘合并（★ 强制）**：准备写入 `<概念名>.md` 前，先检查 `知识元/` 目录是否已存在同名文件：
       - **不存在** → 正常新建；
       - **已存在** → **合并，严禁覆盖 / 严禁跳过 / 严禁新建同名文件**：
         ① 正文：保留原元正文原样不动，其后追加 `## <来源文档短标识>视角` 的新来源内容；
         ② `source` 字段：把新来源路径追加进保留元的 `source`（多来源用 `, ` 分隔的引号列表，如 `"a.md", "b.pptx"`）；
         ③ frontmatter：只保留一份；`updated` 改为当天；类型标签取其一（优先"概念"）；
         ④ 严禁产生重复 `# <概念名>` 标题、重复 frontmatter 字段，或把旧内容整段丢弃。
  D. 写入知识元文件（markdown，**Phase 1 字段顺序固定为 tags → source → unit_id → created → updated → summary**）：
     ~~~
     ---
     tags: [领域标签, 概念标签, 类型标签]   # 最后一个必须是类型标签（概念/方法论/人物/定义/原理），名称本身不得作标签
     source: "原始文件/<该文件相对路径>"
     unit_id: KD-YYYYMMDD-NNN
     created: YYYY-MM-DD
     updated: YYYY-MM-DD
     summary: 一句话摘要
     related_files:
       - file: "原始文件/<该文件相对路径>"
         score: 1.0
     ---

     # <知识元名>（= 文件名，一字不差）

     正文内容...（不得为空；须有原文依据，禁止编造）
     ~~~
     🔴 **Phase 1 禁止写入** `resources`、关联知识元、关联资源、关联原始文件字段（这些由第二阶段 `maintain` 维护）。
     🔴🔴 **source 字段格式（★ 收尾能否通过的关键，2026-08-13 实战教训）**：
       - 单来源：`source: "原始文件/<该文件相对路径>"`（带引号）；
       - 多来源（写前查盘合并后）：`source: "a.md", "b.pptx"`（**逗号分隔的引号列表**，引号完整保留）；
       - 🔴 **严禁 YAML 数组格式**（`source: ["a.md", "b.pptx"]`）——数组格式会让 `verify-units` / `mark-processed` 的 source 匹配失败、计数为 0，子代理收尾永远无法通过（正是 2026-08-13 东油-第2讲 66 个单元卡死的直接原因）。多来源合并时逐来源追加引号项即可，切勿改写成数组。
     🔴 正文不得为空；所有内容须有原文依据，禁止编造、推测。
     E. 资源归档：extract 已自动生成 `.ctx` 初始文件；请为每个 `.ctx` 填写 `belongs_to` 指向所属知识元。
        🔴 **禁止把资源文件当作知识元**（资源不是 `.md` 知识元，不得为其建 `.md`）。
     E.5 更新概念索引：
        python <skill_dir>/kb/scripts/ahkb.py update-concept-index --workspace <工作空间绝对路径>
  （C/D/E 的完整阈值与铁律亦见 `<skill_dir>/kb/extraction.md`；二者冲突时以本提示内联铁律为准。）

  ── ★ 强制心跳契约（缺此即违约）──
  🔴 **心跳按"当前正在处理的文件"执行**——处理到列表中的哪个文件，心跳就针对哪个文件：
  1. 每处理完【一个 chunk】并落盘该 chunk 对应的全部知识元后，立即执行一次心跳（用**当前文件**的相对路径）：
        python <skill_dir>/kb/scripts/ahkb.py phase1-heartbeat "原始文件/<该文件相对路径>" --workspace <工作空间绝对路径>
  2. 🔴 **长 chunk 定时心跳（★ 2026-08-12 新增）**：若某个 chunk 的分析、写入过程预计会超过 30 秒，
     则必须**每 30 秒**调用一次上述 `phase1-heartbeat`，即使该 chunk 尚未处理完毕。
     这是为了不让主代理在两个 chunk 之间长时间看不到心跳而误判卡死。宁可多心跳，不可漏心跳。
  心跳用于告知主代理"我还在干活"。即使偶发漏一次也无妨，下一 chunk 或下一个 30 秒补上即可；
  但不要连续超过 4 分钟不心跳，否则主代理会误判你卡死而重派。
  🔴 心跳返回值必须检查：只有 `ok:true` **且** `updated:true` 才算成功。出现以下任一即视为
     心跳未生效，**立即停上并上报主代理，不得当作成功继续**：
       - `ok:false` 且 `anchor_missing:true`：主代理尚未建立 `phase1_dispatch` 锚点（顺序错误）；
       - `ok:false` 且 `not_in_set:true`：本文件不在派发集合，心跳实际未记录（派发漏文件或 rel_path 不一致）；
       - `updated:false`（即便 `ok:true` 也是假成功）：心跳未落地、主代理永远看不到。
     正常情况应返回 `ok:true, updated:true, last_heartbeat=<当前时间>`。

  ── ★ 临时文件铁律（缺此即违约）──
  你产生的任何临时脚本、调试日志、中间文件（如 `_xxx.py`、`_xxx.log`、`_xxx.txt`、
  `extract_*.txt`、`mark_*.log` 等）**一律写入 `<工作空间绝对路径>/临时工作文件/`，
  严禁直接落在工作空间根目录**。
  - 需要写调试脚本时，路径必须写成 `<工作空间绝对路径>/临时工作文件/_<文件名>.py`，
    并在脚本内部使用 `--workspace <工作空间绝对路径>` 指向工作空间。
  - 流程结束时，清理你在 `临时工作文件/` 下产生的、不再需要的中间文件；
    若发现根目录已有此类散落文件，**立即将它们移入 `临时工作文件/` 或上报主代理**。
  - 🔴 **清理删除一律用**：`python <skill_dir>/kb/scripts/ahkb_trash.py <路径> --workspace <工作空间绝对路径>`
    （自动移入回收站，失败静默直删）；**严禁 Bash `rm` / os.remove / unlink / rmtree**——否则会触发
    权限确认打扰用户、或删除失败残留垃圾。

  ── ★ 强制收尾校验（缺此即违约）──
  🔴 **收尾按"当前正在处理的文件"逐个执行**——每完成一个文件的 A→F，就对该文件跑完下面整套闸门，
  全部通过（mark-processed ok:true）后才开始列表中的下一个文件：
  A→F 全部跑完后、最后的 `mark-processed` **之前**，必须依次执行两道闸门（均带 `--workspace <工作空间绝对路径>`）：
  1. 🔴 产出自检（轻量脚本终检）：
        python <skill_dir>/kb/scripts/ahkb.py verify-units "原始文件/<该文件相对路径>" --workspace <工作空间绝对路径>
     读取返回的 `ok` / `status` 自校验：
       - `ok:true`（status=pass）→ 通过，继续。
       - `ok:false` 且 status=warning → 检查 `warnings` 列表，回到 C 补做遗漏 chunk 对应的知识元后重跑本步。
       - `ok:false` 且 status=fail（知识元数为 0 或严重不足）→ 必须回到 C 重新逐段分析，不得继续。
  2. 🔴 命名质量硬关卡（全局命令，跑一次即可）：
        python <skill_dir>/kb/scripts/ahkb.py validate-units --quarantine --workspace <工作空间绝对路径>
     把 `bad_name`（名称含编号/括号/纯数字等）与 `empty_body` 单元移入 `知识元/_quarantine/`；
     若 quarantine 出大量名称违规，必须回到 C 重新起名并修复后再继续。
  最后执行：
     python <skill_dir>/kb/scripts/ahkb.py mark-processed "原始文件/<该文件相对路径>" --workspace <工作空间绝对路径>
  读取返回 JSON 自校验：
    - ok:true → 该文件完成，继续列表中的下一个文件。
    - ok:false 且 warning:true（如 no_knowledge_units）→ 回到 C/D 回补知识元后重新执行
      mark-processed，直到 ok:true 才返回。严禁"写完知识元就走人、不跑 mark-processed"。
  组内全部文件都 mark-processed 返回 ok:true 之前，你的任务不允许结束。
  ```

**② 🔴 主代理派发【之前】必须先建立派发锚点（供心跳静默探测）—— 顺序铁律，不可颠倒：**
> 🔴🔴 **`phase1-dispatch` 必须在派发子代理【之前】完成。否则子代理从第一 chunk 起调用的 `phase1-heartbeat` 因找不到 `phase1_dispatch` 锚点而静默失效（旧版 `updated:false` 且无任何报错），主代理的 `phase1-status` 也因无锚点而无法判定 `suspect`/`stuck`，整条心跳监控形同虚设。** 此坑已在 2026-08-10 实战中踩中（先派发、事后才补 `--from-scan`，当时文件已处理完导致捕获为空、锚点未写入）。

派发子代理【之前】，主代理先运行（二选一）：
```bash
# 推荐（★ 2026-08-14 起标准姿势）：锚点 + 自动拉起 phase1-wait 一步完成（防"派发后空转"）
python {skill_dir}/kb/scripts/ahkb.py phase1-dispatch --files "a.pptx" "b.docx" "c.md" --auto-wait [--timeout <秒>] --workspace <工作空间绝对路径>
# 或自动以当前未处理文件集合作为派发集合（仅在派发前、所有文件尚未 mark-processed 时调用才有效）
python {skill_dir}/kb/scripts/ahkb.py phase1-dispatch --from-scan --auto-wait [--timeout <秒>] --workspace <工作空间绝对路径>
```
该命令在 manifest 写入 `phase1_dispatch`（本次派发文件集合，每个文件含 `dispatched_at` 与初始 `last_heartbeat`）。**锚点建立后**，再按 `phase1-group` 的分组派发 ≤4 个后台子代理（每组一个）；此后子代理每处理完一个 chunk 调 `phase1-heartbeat` 才会真正落地。**注意**：若顺序颠倒（先派发、事后才 `--from-scan`），此时文件可能已被 mark-processed 导致捕获为空、`dispatch` 字段不写入，子代理全部心跳静默失败——`phase1-heartbeat` 现在会返回 `ok:false, anchor_missing:true` 明确报错，但根本解法仍是【先 dispatch 再派发】。

> 🔴🔴 **`--auto-wait` 是 loop 最强化标准姿势（★ 2026-08-14）**：建立派发锚点后**自动拉起 `phase1-wait` 子进程**（返回 JSON 含 `wait.status=started` 与 PID），主代理随即进入"每 2~3 分钟 `phase1-peek` 播报"节奏，**无需再单独执行 phase1-wait**。若返回 `wait.status=already_running`（已有 wait 在跑），直接继续播报即可。**严禁**派发后不启动 wait 就结束回合或空转等待后台通知。

**③ 🔴 脚本化等待门控（phase1-wait）—— 根治"主代理轮询卡死"，不可跳过：**
> 🔴🔴 **2026-08-13 根治**：旧契约要求主代理"每 10~15 秒轮询 `phase1-status` 直到完成"（1 小时 ≈ 150 次 LLM API 往返），任一次请求挂起即整体停摆（实战：80 分钟静默卡死、需重启恢复）。**新契约把等待循环交给 Python 进程（`phase1-wait`），主代理只做两个短动作：启动等待 + 按退出码处置。** 主代理**严禁**再以 10~15 秒频率轮询 `phase1-status`（该命令保留用于诊断与断点恢复）。

> 🔴🔴🔴 **派发即等待（★ 2026-08-14 新增铁律，loop 最强化核心）**：主代理派发完子代理后，**唯一合法的下一步动作就是启动 `phase1-wait`**。**严禁**以下任何行为：
> - 派发后结束回合、空转等待"后台 agent 完成通知"（该通知不可靠，正是 2026-08-13 卡死的直接原因）；
> - 派发后直接进入其他阶段或向用户汇报"已派发请等待"后无后续动作；
> - 派发后不启动 wait 就执行 build-graph / build-kg-html 等其他命令。
> **程序级硬保障（2026-08-14 新增）**：
> 1. `phase1-dispatch --auto-wait`：锚点建立后**自动拉起 phase1-wait 子进程**，把"派发即等待"变成一条命令自动完成（推荐标准姿势）。
> 2. `pipeline-status` 的 **`phase1_wait_required` 守卫**：若 Phase1 未完成 + dispatch 锚点存在 + wait 未在运行 + 距派发已超 2 分钟 → 返回 `next_action: "phase1_wait_required"`，主代理必须立即补执行 `phase1-wait`。
> 主代理每次 loop 循环顶部调用 `pipeline-status` 时，**必须检查返回值是否为 `phase1_wait_required`**——若是，立即补执行 wait 再继续，不得忽略。

dispatch 记录完成（且子代理已派发）后，主代理执行一次：
```bash
python {skill_dir}/kb/scripts/ahkb.py phase1-wait [--timeout <秒>] [--interval 15] --workspace <工作空间绝对路径>
```
该命令 1 秒内返回（启动独立子进程 `phase1-wait --child`），子进程在**进程内**每 `--interval` 秒检测一次完成度（判定规则与 `phase1-status` 完全一致：`last_heartbeat` 基准 + 服务端活跃度交叉校验 `busy_but_silent`），直至出现三种终态之一（写 `临时工作文件/_phase1_result.json` 后退出，exit 0/2/3）：
- **`complete`（exit 0）** → 全部完成，**运行 `phase1-dispatch --clear` 清除派发状态**后进入 Phase 2（maintain）。
- **`stuck`（exit 2）** → 子进程检测到 stuck 即提前返回（附 stuck 清单），主代理按下方「stuck 处置规则」重派 / 补收尾后，**再次运行 phase1-wait** 继续等待。
- **`timeout`（exit 3）** → 超过 `--timeout`（默认 7200 秒，主代理应按 scan 的 `estimated_minutes` 上限设置）仍未完成，主代理诊断后处置（重派 stuck / 补收尾 / 汇报用户）。

主代理查询进度的**唯一轻量方式**：`phase1-peek`（读进度/结果文件，零状态计算）：
```bash
python {skill_dir}/kb/scripts/ahkb.py phase1-peek --workspace <工作空间绝对路径>
```
返回 `complete` / `stuck` / `timeout` / `running` / `error` / `not_started` 六态，`_display` 可直接呈现给用户。

**stuck 处置规则（phase1-wait 返回 stuck 或 peek 显示 stuck 时执行）：**
- 对该文件重新派发一个子代理（或主 agent 亲自降级补完 A→F + mark-processed）；
- 🔴 **亲自补完 ≠ 直接补标记**：补完必须包含「🔴 `verify-units` + 🔴 `validate-units --quarantine`」两道质量闸门（命令见子代理模板收尾校验节）。若 `verify-units` 报告 `unit_count=0` 或发现 `source` 字段格式错误（YAML 数组 `source: ["..."]`），**说明子代理产出的知识元格式不合规，必须回到 [C] 重新分析该文件并修复 source 字段后再标记**，严禁"仅修格式 / 直接补标记"糊弄过去；
- 处置完成后重新运行 `phase1-wait` 继续等待，不阻塞其他已完成的文件。

**④ 主代理可见进度反馈（★ 2026-08-12 设计、2026-08-13 改版为轻量播报，不可省略）：**
> 🔴🔴 **并行模式 B 下，子代理由大模型逐块处理，速度较慢（单 chunk 常需 1~2 分钟，大文档整体可达十几分钟）。主代理在 phase1-wait 等待期间必须每 2~3 分钟向用户轻量播报一次进度，屏幕不得长时间空白。** 规则：
> 1. **每 2~3 分钟**运行一次 `phase1-peek`，把返回的 `_display` 原文打印给用户（running 态自带 `[已标记/共]` 总进度与逐文件状态摘要）。
> 2. **终态即行动**：`phase1-peek` 返回 `complete` / `stuck` / `timeout` → 立即按 ③ 处置（收尾 / 重派 / 诊断），无需继续等待。
> 3. **派发即告知**：派发完子代理、启动 `phase1-wait` **之前**，先打印一次 `phase1-status` 的 `_display`（或 phase1-wait 返回的 started 信息），明确告诉用户"已并行派发 N 个子代理开始处理，请耐心等待，期间无需任何操作"。
> 4. 顺序模式（A）虽由主 agent 亲历亲为、天然有过程输出，但也建议每完成一个文件/阶段后同步进度，避免长文档处理期间用户无感知。
>
> 本要求适用于 **菜单 0 / 1 / 2（全自动从零 / 完全重建 / 批量添加）+ 并行模式 B**；菜单 3、4 为设计性顺序豁免，不适用此并行等待，但同样应保持过程可见。

**⑤ 最后防线：** `maintain` 仍内置 `phase1_incomplete` 硬关卡——即便等待/判定有误，未完成 mark-processed 也绝不会进入关联阶段。进入 Phase 2 前主代理也应再次以 `maintain` 的 `phase1_incomplete` 检查做最终确认。

**⑥ 🔴 断点恢复 / 自动续跑（重启 / 中断 / 新会话续跑）—— ★ 2026-08-13 升级：由「全自动流水线 loop」统一接管：**
> 🔴🔴 **背景（2026-08-13 实战踩坑）**：并行抽取中途电脑/WorkBuddy 重启后，`_processed_docs.json` 若显示全部文件已标记，新会话主代理会误以为 Phase 1 已完成而直接进入 `maintain`（cross link），但实际有文件是"子代理已写盘知识元、收尾（mark-processed）却没跑完"就中断的——它们的心跳静默、质量闸门未过，却被"标记即完成"模型放行了。**根因：技能此前未定义"重启后如何恢复"，主代理只信 manifest 而漏了 `phase1-status` / `pipeline-status`。**

1. 🔴 **状态持久化事实**：`phase1_dispatch` 锚点与 `processed` 清单持久化在 `原始文件/_processed_docs.json`；**流水线断点状态持久化在 `系统设置/_pipeline_state.json`**（均跨重启 / 跨会话有效）。重启后**必须**先运行 `pipeline-status --workspace <工作空间>` 恢复对"上次构建到哪一步"的认知，**严禁只凭 `_processed_docs.json` 是否全标记就判定完成**。
2. 🔴 **自动续跑（★ 全自动菜单 0/2 的进入判定）**：`pipeline-status` 返回 `next_action` ∈ phase1/phase2/phase3 → **自动从断点阶段续跑**（不清库、不重做已完成阶段、不询问用户）；返回 `done` / `error` 才走全新开始或停下报告。**menu1（完全重建）例外**：用户选本菜单即明确要重来，总是清库重建。
3. 🔴 **Phase1 内部恢复细节**：续跑进入 phase1 时，对未收尾文件执行 `③` 的 stuck 处置（重派子代理或主代理亲自补完——**补完必须含 verify-units + validate-units 两道闸门**），直到 `pipeline-status` 显示 `phase1_complete:true`；对"全标记但未跑过质量闸门"的文件一律以 `verify-units --pending` 复查。
4. 🔴 **source 字段格式错误是收尾卡死的头号原因**（2026-08-13 实战：66 个知识元被写成 `source: ["..."]` 数组格式，导致 `verify-units`/`mark-processed` 匹配为 0、子代理永远无法收尾）。恢复排查时**先查 source 格式**：规范为引号列表（单来源 `source: "原始文件/xxx.pdf"`；多来源 `source: "a.md", "b.pptx"`），严禁 YAML 数组 `source: ["..."]`。新版 `mark-processed`/`verify-units` 已对数组格式容错匹配并输出格式告警，但**告警 ≠ 放任**——发现后仍须回 [C] 修复，不得直接补标记了事。

> 🔴 框架的后台 agent "完成通知"仅作**辅助唤醒**；`pipeline-status`（流水线判定）+ `phase1-wait`（脚本等待）+ `phase1-status`（诊断）才是确定性门控，二者冲突时以 manifest + 流水线状态为准。

---

## 📂 按需加载说明（菜单 / 抽取 / 参考）

本模块采用**按需加载**，以降低上下文占用、避免无关菜单细节干扰执行：

- 进入模块时已加载本文件（含：全局铁律、工作空间结构、首次运行流程、**完整主菜单**、两阶段法契约）。
- 🔴 无论用户选数字还是自然语言路由，加载后**必须先展示上方完整主菜单**，再读取对应菜单文件。
- 用户选定某菜单后，请读取对应文件并按其步骤执行：
  - `kb/menus/menu0.md` … `menu9.md`；菜单 `s`（设置）对应 `kb/menus/menu_s.md`。
- 执行涉及**文档抽取（A–F 循环）**的菜单（0 / 1 / 2 / 3 / 4）时，另需读取 `kb/extraction.md` 获取 A–F 具体操作。
- 需要**命令参数说明**或**错误处理**（含 `e` 返回主菜单）时，读取 `kb/reference.md`。

> 以上子文档均为原 `kb.md` 的按需拆分，内容与原指令完全一致；全局铁律与契约始终保留在本文件中。

## 📌 已知限制（随版本发布，使用前请知悉）

以下限制为当前版本（v0.1.1）的已知边界，预期在后续版本修复；在手动维护场景下一般可接受，但自动化流水线需注意：

1. **知识元改名不联动更新跨引用链接**：在资源管理界面（菜单 5 / 知识元编辑器）对一个知识元执行「改名」时，系统会将其 `.md` 文件安全移入回收站并写入新文件，但**不会自动更新其它知识元正文中对它的 `[[旧名]]` 双链引用**。改名后若存在指向旧名的链接，会变成悬空链接。
   - 应对：手动批量改名前，建议先以全文检索确认没有（或很少）其它知识元引用该名；改名后可用菜单 6「自动整理并更新知识链」重新建立关联，但 `[[wiki 链接]]` 文本本身仍需手动或借助外部工具替换。
   - 后续计划：在改名流程中增加「同步更新全部 `[[链接]]`」选项。
2. **`.ctx` 媒体伴随文件**：媒体资源（图片/音视频）的元数据存于同名 `.ctx` 文件，知识元（`.md`）本身无 `.ctx`；改名/移动资源时已通过本地回收站安全迁移，但同样的跨引用同步限制（见上）也适用于资源。

> 上述限制不影响「概念独立性原则（唯一成元标准）」的正确性，仅涉及改名后的链接一致性维护。
