Organize Vault

扫描 Markdown vault 中的笔记,按语义主题生成目录层级,将笔记移动到 对应目录,并在根目录和每个主题目录下创建 MOC.md。增量维护模式下检测 git 新增笔记, 移动归类并更新对应 MOC.md。笔记内容始终不变,git 是唯一回退机制。

Audits

Pass

Install

openclaw skills install organize-vault

organize-vault

知识库目录结构生成与 MOC 维护助手。

When to Use

当用户说以下内容时激活:

  • "整理一下 vault"、"把新笔记归类"、"更新 MOC"
  • "帮我初始化 vault 结构"、"生成目录层级"
  • "哪些笔记还没归类"
  • "organize my vault"、"update MOC"

Core Constraints

在整个执行过程中,以下约束不可违反:

  1. 永不修改笔记内容:笔记的 body 文本不可改动。可移动文件位置、创建 MOC.md、在 MOC.md 中追加 wikilink。
  2. 变更必须经用户确认:所有文件移动和写入在执行前展示完整清单,等待明确确认。
  3. 模糊性是正常状态:无法归类的笔记归入 misc/,标注"待整理",不强制归入不合适的主题。
  4. git 是唯一回退:所有变更受 git 追踪,执行前告知用户可通过 git revert 撤销。

目标结构

初始化完成后,vault 应呈现如下层级:

vault/
├── MOC.md                  # 根目录索引,链接所有主题目录
├── programming/
│   ├── MOC.md              # 本主题笔记列表(含描述)
│   ├── python-async.md
│   └── docker-compose.md
├── tools/
│   ├── MOC.md
│   └── vim-config.md
├── reading/
│   ├── MOC.md
│   └── deep-work.md
└── misc/
    ├── MOC.md              # 标注"待整理"
    └── random-idea.md

入口:检测场景

1. 若 vault_root 未知,询问用户 vault 的根目录路径

2. 检测 git 状态:
   git -C <vault_root> status --porcelain 2>&1
   - 若返回 "not a git repository":提示用户执行 git init,停止

3. 检测是否存在 MOC 文件(见 MOC 识别规则):
   - 若无 MOC 文件 → 走【场景 B:初次初始化】
   - 若有 MOC 文件 → 走【场景 A:增量维护】

MOC 识别规则

以下任一条件满足,则该文件被识别为 MOC,不参与归类:

  • 文件名为 MOC.md(大小写不敏感)
  • 文件顶部 frontmatter 中含 type: mocmoc: true

笔记描述提取规则

在读取笔记全文时,同步提取一句简短描述,用于写入 MOC.md。提取优先级:

  1. frontmatter 的 description 字段(直接使用)
  2. H1 标题后第一个非空段落的首句(截断到 50 字以内)
  3. 以上均无:不写描述,只写 - [[note-stem]]

描述只写入 MOC.md,不触碰原笔记文件。


场景 A:增量维护(已有 MOC)

Step 1 — 识别新增笔记

# 未提交的新增文件
git -C <vault_root> status --porcelain | grep '^?' | awk '{print $2}' | grep '\.md$'

# 或:相对上次 commit 新增的文件
git -C <vault_root> diff --name-only --diff-filter=A HEAD

过滤规则:

  • 只保留 .md 文件
  • 排除 MOC.md(按 MOC 识别规则)
  • 若结果为空:告知用户"未发现新笔记(相对 git 状态)",结束

Step 2 — 读取现有目录结构

用 find 找到所有 MOC.md 文件,逐一读取,理解各主题目录的内容范围。

在内存中构建映射:

{
  "programming/MOC.md": "涵盖编程语言和框架,已有:[[python-async]], [[docker-compose]]...",
  "tools/MOC.md": "开发工具配置,已有:[[vim-config]], [[git-tips]]..."
}

Step 3 — 归类决策

对每个新笔记,Read 其全文,然后:

  1. 按【笔记描述提取规则】提取一句描述
  2. 对比各主题目录,判断归属:
    • 高置信度:直接给出目标目录
    • 低置信度:列出候选目录,给出理由,让用户选择
    • 无法归类:归入 misc/,标注"待整理"
  3. 每篇笔记只移动到一个目录(主要主题);若同时匹配多个主题,在多个 MOC.md 中均追加 wikilink
  4. 检查目标 MOC.md 是否已含该笔记链接:若已含,跳过

Step 4 — 展示变更预览

待执行的操作:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  移动:python-async.md → programming/python-async.md
  更新:programming/MOC.md ← 追加
        [[python-async]] — Python asyncio 事件循环机制详解
  理由:笔记讨论 Python asyncio,归入 programming

  移动:vim-config.md → tools/vim-config.md
  更新:tools/MOC.md ← 追加
        [[vim-config]] — Vim 配置文件与插件管理
  理由:编辑器配置,归入 tools

无法归类(共 1 篇):
  random-idea.md → misc/random-idea.md(标注"待整理")
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

所有变更受 git 追踪,可通过 git revert 撤销。
确认执行?(可指定"全部"或逐条接受/拒绝)

Step 5 — 执行变更

对每个用户确认的归类:

  1. mv <vault_root>/<note>.md <vault_root>/<topic>/<note>.md
  2. Edit <topic>/MOC.md,在笔记列表末尾追加:
    • 有描述:- [[note-stem]] — {描述}
    • 无描述:- [[note-stem]]

若目标目录不存在,先 mkdir -p 创建,再在其中创建 MOC.md(格式见下文)。


场景 B:初次初始化(无 MOC)

分四步执行,避免一次读取大量笔记导致上下文过长。

Step 1 — 标题扫描

find <vault_root> -name "*.md" -not -path "*/.git/*" -not -name "MOC.md"

对每个文件,只提取:

  • 文件相对路径
  • H1 标题(首行 # ...)或文件名 stem(若无 H1)

不读取笔记 body。

Step 2 — 分组提案

基于文件路径(目录结构)+ 标题,推断主题分组,向用户提出候选结构:

建议生成以下目录结构(基于 47 篇笔记的标题和路径):

  programming/    — 约 18 篇(Python, TypeScript, 算法...)
  tools/          — 约 12 篇(Docker, Vim, Git...)
  reading/        — 约 9 篇(读书笔记, 摘录...)
  projects/       — 约 5 篇(项目记录...)
  misc/           — 约 3 篇(暂无明显归属)

是否采用这个结构?可以调整目录名、合并或拆分分组。

等待用户确认或修改。不执行任何文件操作。

Step 3 — 逐组细化

用户确认结构后,按分组逐批读取笔记全文(每批 10-15 篇),对每篇笔记:

  1. 按【笔记描述提取规则】提取一句描述
  2. 确认归属哪个目录:
    • 归属明确的:记录目标目录 + 描述
    • 跨主题的:移动到主要目录,在次要目录的 MOC.md 中也追加链接
    • 完全不匹配的:归入 misc/,标注"待整理"

记录所有归属决策(含描述),不写文件

Step 4 — 目录重组

汇总所有归属决策,展示完整预览:

即将执行以下操作(共 47 个笔记):

新建目录:
  programming/    tools/    reading/    projects/    misc/

移动文件(共 47 个):
  python-async.md          → programming/python-async.md
  docker-compose.md        → programming/docker-compose.md
  vim-config.md            → tools/vim-config.md
  ...(共 47 条,全部展示)

新建 MOC 文件(共 6 个):
  MOC.md                   (根目录索引,5 个主题)
  programming/MOC.md       (18 个笔记,含描述)
  tools/MOC.md             (12 个笔记,含描述)
  reading/MOC.md           (9 个笔记,含描述)
  projects/MOC.md          (5 个笔记,含描述)
  misc/MOC.md              (3 个笔记,标注"待整理")

清理空目录(若有):移走笔记后变空的原目录将被删除

所有变更受 git 追踪,可通过 git revert 撤销。
确认执行?

用户确认后,按以下顺序执行:

  1. mkdir -p 创建所有主题目录
  2. mv 移动所有笔记文件
  3. Write 创建每个 <topic>/MOC.md(含描述)
  4. Write 创建根目录 MOC.md
  5. rmdir 清理空目录(失败则静默跳过)

MOC 文件格式

主题目录 <topic>/MOC.md

# {主题名称}

## 笔记

- [[python-async]] — Python asyncio 事件循环机制详解
- [[docker-compose]] — 多容器应用编排配置示例
- [[algorithm-notes]]

(无描述的笔记直接写 - [[note-stem]],不强制加破折号)

根目录 MOC.md

# Vault Index

## 主题

- [[programming/MOC|Programming]]
- [[tools/MOC|Tools]]
- [[reading/MOC|Reading]]
- [[projects/MOC|Projects]]
- [[misc/MOC|Misc]]

边界情况处理

情况处理方式
vault 未初始化 git提示执行 git init && git add -A && git commit -m "init",停止
vault 无任何 .md 文件告知 vault 为空,结束
无法确定 vault 根目录询问用户,不猜测
根目录已有 MOC.md询问用户是覆盖还是追加,不默认覆盖
笔记同时匹配多个主题移动到主要目录,在次要目录 MOC.md 中追加跨目录 wikilink
移走笔记后原目录变空rmdir 删除,失败则静默跳过(可能含其他文件)
MOC.md 已含该笔记链接跳过,不重复添加
新笔记本身是 MOC.md跳过归类,告知用户
笔记无可提取描述只写 - [[note-stem]],不加破折号,不报错
大型 vault(> 100 篇笔记)初始化严格执行分步策略,每批不超过 15 篇全文读取