Tips & Tricks (更新: 2026/6/6)

Obsidian 里的笔记,靠 AI“自己长大”——打造第二大脑的机制

把散乱的 Obsidian 笔记交给 AI 读,半自动地加链接、做摘要、做整理。写入要谨慎、以读取为主,养出一个“自己长大的 Vault”的真实经历。

Obsidian 里的笔记,靠 AI“自己长大”——打造第二大脑的机制

凌晨 0 点,我对着 Obsidian 的搜索框敲下:“那条命令,叫啥来着。”

两年前记下的,区区三行 shell 命令。它在,这点我确定。可在超过 500 条笔记里,它到底埋在哪一条,我是怎么也想不起来了。那天最后我放弃了搜,又重新去谷歌了一遍。

这事,我估计不止我一个人遇上。

Obsidian(一款笔记 app),越用越顺手。但与此同时,它也一定会越来越乱。每日记录、读书笔记、代码片段、文章选题、会议纪要,全都以 Markdown 文件的形式堆着,攒上半年,就成了“一座只属于你、却又死活搜不到东西的杂物屋”。

于是我开始做这么件事:让 AI 去读我的 Vault(笔记的存放处),把整理这活儿替我分担一半。贴链接、做摘要、把走丢的笔记捞回来。每天这么一点点地跑,原本散成一盘沙的笔记,竟然开始自己串起来了,真有点“第二大脑”那味儿了。今天就聊这个。

话说回来,“笔记会长大”是啥意思?

普通笔记,写下来那一刻就是巅峰。往后就只剩被遗忘。

可 Obsidian 有个写法 [[笔记名]],能把笔记彼此连起来(这叫 Wikilink)。连得越多,整个东西就越像一张地图。从“TypeScript 的类型错误”那条笔记,能跳到“那天被 Docker 坑了一整天”,再从那儿连到“上个月那次故障复盘”。点连成线,线连成面,就是这种感觉。

问题在于,这种串联,要是人来做,麻烦到根本坚持不下去

这块就交给 AI。具体说,搭档是 Claude Code 这个工具。它本是写代码的 AI,但说白了,可以当成一个“能安全读写一整文件夹 Markdown 的作业员”来用。Obsidian 本地优先(文件就放在你自己电脑上)的好处原封不动留着,只把整理那份苦力交给 AI。这就是今天的作战计划。

不过有一条,我得先把丑话撂前头。把整个 Vault 的编辑权都交给 AI,这事绝对别干。

.obsidian/(设置文件夹)也好,已经发出去的文章也好,像合同那样的私人笔记、API key 也好,没有任何理由让 AI 去读。所以我的方针一直是:读取放宽点,写入超级谨慎。这跟我之前写的 给 AI 搭“脚手架”的做法 完全是同一套思路——说白了,就是先装好辅助轮,再让它跑。

这三个场景里它最管用(3 个)

光讲抽象的传不到位,挑三个我实打实感到“哦,这真行”的瞬间。

第一个,是早上的每日记录。 每天早上,让 AI 读昨天的笔记,吩咐它“把没做完的任务挪到今天,再把昨天的收获压成三行”。于是,昨天的自己随手撒下的烂摊子,就被整成今天的自己一上手就能动的样子端出来了。“啊,这就是昨天卡住的那个”,一瞬间就想起来。接班的人,是未来的自己。

第二个,是代码片段的救援。 就是刚才那个“两年前的命令”难题。把跑通过的命令往 snippets/ 文件夹一扔,AI 会自动给它补上“这是干嘛用的命令”的说明,再连上其他相关的笔记。一条光秃秃的命令,就这么摇身变成了“能被搜到的、像样的笔记”。

第三个,是文章选题的备料。 要写博客那天,把 content-ops/ 文件夹的选题本给 AI 看,问它“这篇文章,能往哪些旧文章上挂内链?”。它有时会把我自己都忘了的、半年前的某篇文章给翻出来,这一手是真不起眼地顶用。点和点连上的那一下,就是这个瞬间。

我亲手翻过的 3 次车

这块可能才是正题。最初那几周,我的 Vault 被 AI 祸害得不轻。我老实晒出来。

第一桩,一上来就让它“把整个 Vault 整理一下”。 这桩最惨。AI 连两年前那些不知所云的笔记都要“热心”地去整理,自作主张生出来一大堆标签和标题。Graph view(把笔记联系可视化的那个画面)成了一片满是陌生标签的丛林。旧笔记当初啥意图,AI 根本不懂,可它偏要靠猜来动手。从那以后,对象我必定只圈定在一个文件夹里。

第二桩,让 AI 擅自改了笔记名。 “标题给我弄得好认点。”——就这一句,[[旧笔记名]] 的链接齐刷刷全断了。Obsidian 里你自己改名,链接会跟着走;可外部的 AI 一口气把文件名改了,这种跟随就失灵了。断链尸横遍野。现在“重命名必须先问人”,是我立下的铁律。

第三桩,让它读了 private/ 这桩可不止后背发凉那么简单。一个装着合同金额笔记的文件夹,因为我偷懒没配权限,竟处在 AI 能读的状态。事故倒是没真出,可这无异于让一个新人光手去摸生产数据库。只要在 deny(拒绝清单)里老老实实写上,压根就没有出事的余地。教训就是:是我自己抠门没配设置,活该。

上手办法:只放三个文件

不难。在 Vault 根目录下,备齐三样就行。

先,把文件夹分一分。一开始就分得太细,必崩,所以粗粗地来就够。

# 在 Vault 根目录执行。只分出:每天用的地方、项目、公开作业、保护区
mkdir -p inbox daily projects content-ops snippets templates scripts archive private

各文件夹干啥的、AI 能管到哪,我是这么分派的。

文件夹角色交给 AI 的范围
inbox/未整理笔记、网页剪藏的接收盘读取与新建
daily/每日记录、工作笔记新建、追加、前一天的摘要
projects/进行中任务、交接新建、更新、整理未决事项
content-ops/文章草案、内链、发布检查整理草稿、确认链接
snippets/代码片段与用法补说明、整理标签
templates/Obsidian 模板原则上先确认再编辑
archive/已完成、已封存禁止编辑
private/个人信息、合同、机密连读取也禁止

接着,在 Vault 根目录放一个 CLAUDE.md。这是写给 AI 的“咱家规矩”那张纸。与其大谈长篇理念,不如把希望它守的输入输出规则写得短小,反而更见效。

# Obsidian Vault 的规矩

## 角色
- 这个 Vault 是放每日记录、项目交接、代码片段、文章草案的地方。
- 即便不读 private,也要能正常干活。

## 文件夹方针
- `daily/`: 用 `YYYY-MM-DD.md` 新建、更新每日笔记。
- `projects/`: 每个进行中的案子,维护一张交接笔记。
- `snippets/`: 把跑通的命令,连同来龙去脉、失败例一起留下。
- `templates/`: 读随便读。编辑之前必须先问。
- `archive/` 和 `private/`: 不编辑。private 的内容连摘要都不许做。

## 写法规矩
- 内链用 `[[项目名]]` 这种 Wikilink 写法。
- 笔记开头加上 YAML 属性(status 等)。
- 一段控制在 5 行以内。

## 安全规矩
- 现有笔记的重命名,必须先确认。
- 不许改写 `.obsidian/` 的设置。
- 批量编辑前,先把目标文件列出来,等批准。
- 编辑完务必跑一遍 `node scripts/audit-wikilinks.cjs .`。

最后这个最要紧。用 .claude/settings.json 把权限物理地锁死。CLAUDE.md 是“拜托你”,这个才是“物理锁”。是为了让第三桩翻车再也不重演的门卫。读取放宽,写入只限作业文件夹,危险操作用 deny 彻底封死。设置细节看官方权限文档

{
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
  "permissions": {
    "allow": [
      "Read(./CLAUDE.md)",
      "Read(./daily/**)",
      "Read(./projects/**)",
      "Read(./content-ops/**)",
      "Read(./snippets/**)",
      "Read(./templates/**)",
      "Read(./scripts/**)",
      "Edit(./daily/**)",
      "Edit(./projects/**)",
      "Edit(./content-ops/**)",
      "Edit(./snippets/**)",
      "Write(./inbox/**)",
      "Write(./daily/**)",
      "Write(./projects/**)",
      "Write(./content-ops/**)",
      "Write(./snippets/**)",
      "Bash(node scripts/audit-wikilinks.cjs .)"
    ],
    "ask": [
      "Edit(./templates/**)",
      "Bash(git *)"
    ],
    "deny": [
      "Read(./private/**)",
      "Read(./**/.env)",
      "Read(./**/.env.*)",
      "Edit(./.obsidian/**)",
      "Edit(./archive/**)",
      "Write(./archive/**)",
      "Bash(rm *)",
      "Bash(del *)"
    ]
  }
}

这三张放好,往后就只剩开口吩咐了。诀窍是别说“你整理整理”。范围、成品、禁止事项、验证,每回全给指明。

读一下 daily/2026-06-04.md 和 projects/site-refresh.md。
照着 templates/daily.md 做一份 daily/2026-06-05.md。
没做完的任务,只把负责人还是自己的接过来。
.obsidian/、archive/、private/ 都别碰。
编辑完跑一遍 node scripts/audit-wikilinks.cjs .,把结果报给我。

虽短,但输入、输出、禁区、验证命令全在里头。比起“整理整理”,可复现性高出好几倍。

收尾的门卫:用机器查死链

让外部的 AI 改文件,时不时就会把 Wikilink 改坏。人眼,会漏。所以最后,套一个脚本机械地把坏链都翻出来。把它存成 scripts/audit-wikilinks.cjs

#!/usr/bin/env node
// 把 Vault 内坏掉的 / 含糊的内链([[...]])翻出来的门卫脚本
const fs = require("node:fs");
const path = require("node:path");

const vaultRoot = path.resolve(process.argv[2] || ".");
const ignoredDirs = new Set([".git", ".obsidian", ".trash", "node_modules"]);
const allFiles = [];
const markdownFiles = [];

function walk(dir) {
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
    if (ignoredDirs.has(entry.name)) continue;
    const full = path.join(dir, entry.name);
    if (entry.isDirectory()) {
      walk(full);
    } else if (entry.isFile()) {
      allFiles.push(full);
      if (entry.name.toLowerCase().endsWith(".md")) markdownFiles.push(full);
    }
  }
}

function toPosix(filePath) {
  return filePath.split(path.sep).join("/");
}

function withoutMd(value) {
  return value.replace(/\.md$/i, "");
}

function stripTarget(raw) {
  return raw.split("|")[0].split("#")[0].split("^")[0].trim();
}

function safeDecode(value) {
  try {
    return decodeURIComponent(value);
  } catch {
    return value;
  }
}

function isExternal(target) {
  return /^(https?:|mailto:|obsidian:|#|\/)/i.test(target);
}

function candidateKeys(target, fromFile) {
  const clean = safeDecode(stripTarget(target));
  if (!clean) return [];
  const keys = new Set();
  const normalized = toPosix(path.normalize(clean));
  keys.add(normalized);
  keys.add(withoutMd(normalized));

  if (clean.includes("/") || clean.includes("\\")) {
    const fromDir = path.dirname(toPosix(path.relative(vaultRoot, fromFile)));
    const relative = toPosix(path.normalize(path.join(fromDir, clean)));
    keys.add(relative);
    keys.add(withoutMd(relative));
  } else {
    keys.add(withoutMd(path.posix.basename(normalized)));
    keys.add(path.posix.basename(normalized));
  }

  return [...keys].filter(Boolean);
}

walk(vaultRoot);

const byPath = new Map();
const byBase = new Map();

for (const file of allFiles) {
  const rel = toPosix(path.relative(vaultRoot, file));
  const lowerRel = rel.toLowerCase();
  byPath.set(lowerRel, file);
  if (rel.toLowerCase().endsWith(".md")) byPath.set(withoutMd(lowerRel), file);

  const base = rel.toLowerCase().endsWith(".md")
    ? withoutMd(path.posix.basename(rel)).toLowerCase()
    : path.posix.basename(rel).toLowerCase();
  const list = byBase.get(base) || [];
  list.push(file);
  byBase.set(base, list);
}

const problems = [];
const wikilink = /!?\[\[([^\]]+)\]\]/g;
const markdownLink = /!?\[[^\]]*\]\(([^)]+)\)/g;

for (const file of markdownFiles) {
  const text = fs.readFileSync(file, "utf8");
  const rel = toPosix(path.relative(vaultRoot, file));
  const targets = [];
  let match;

  while ((match = wikilink.exec(text))) targets.push(match[1]);
  while ((match = markdownLink.exec(text))) {
    const target = match[1].replace(/^<|>$/g, "").trim();
    if (!isExternal(target)) targets.push(target);
  }

  for (const target of targets) {
    const clean = stripTarget(target);
    if (!clean || isExternal(clean)) continue;
    const keys = candidateKeys(clean, file);
    const pathHit = keys.some((key) => byPath.has(key.toLowerCase()));
    const baseHits = keys.flatMap((key) => byBase.get(path.posix.basename(key).toLowerCase()) || []);

    if (!pathHit && baseHits.length === 0) {
      problems.push(`${rel} -> missing [[${clean}]]`);
    } else if (!pathHit && baseHits.length > 1) {
      problems.push(`${rel} -> ambiguous [[${clean}]] (${baseHits.length} matches)`);
    }
  }
}

if (problems.length) {
  console.error("发现坏掉/含糊的内链:");
  for (const problem of problems) console.error(`- ${problem}`);
  process.exit(1);
}

console.log(`OK: 已检查 ${vaultRoot} 下的 ${markdownFiles.length} 个 Markdown 文件`);

运行就这一行。

node scripts/audit-wikilinks.cjs .

链接全活着,就吐个 OK。有断的,就给你列出来:哪个文件的哪条链接死了,一目了然。AI 整理完立刻跑这一下,是我每天的收尾仪式。

另外,Obsidian 自身那套机制(Daily notes、Templates、Properties、内链)的准确规格,请去查Obsidian 官方帮助。交给 AI 之前,自己先把这艘母舰的规格摸清,就不至于下出些莫名其妙的指令。

我实际试下来的结果

用这套机制把自己的 Vault 跑了三个月。

最顶用的,果然还是“每日记录的交接”和“发布前的链接审计”这两样。一早让 AI 把前一天摘要一下,发动机明显热得快。“昨天的自己,原来卡在这儿了”,一眼就清楚,趁冲咖啡的工夫,脑子就切到今天的模式了。自从把链接审计嵌进流程,文章发出去之后再“啊,内链断了”而脸色发青的夜晚,归零了。拿数字说,审计前每月总有两三回带着断链就发出去,现在是零。

还有个没料到的副产品。有了“每天都让 AI 来碰”这个前提,我自己写笔记的方式也变了。一想到回头要给机器读,就自然而然会加上 status:,再潦草的笔记也会补一行交代上下文。本以为是在为 AI 整理,结果到头来读着最顺的,是人类的我自己。

反过来,最初一口气整理全 Vault 那回,明摆着是失败。AI 把旧笔记的意思猜过了头,标签和标题不减反增,更乱了。结论很简单:“许可范围划窄、模板做薄、最后用 Node 审计”,这三点守得越牢,就越顺。

有意思的是,半年前我打死也想不起来的、两年前那条命令,如今 5 秒就能摸到。AI 一点一点替我贴好的链接,我只管顺着走就行。笔记,是真的开始变成大脑了。

小结

“笔记自己长大”的真相,不是什么魔法。读取放宽、写入收窄、最后用机器核一遍。 不过是把这条天经地义的事,做成了机制而已。

要做的就三件:把文件夹分好,用 CLAUDE.md 写下规矩,用 .claude/settings.json 把危险操作物理封死。剩下就是别说“整理整理”,圈好范围再开口。今天先从 daily/ 一个文件夹起步。明天的自己,会轻松那么一点点。

想更细地搞懂权限和沙箱的思路,可以读 Claude Code 的审批与沙箱设计。能动手的模板、团队落地的咨询入口,都整理在 教程一览 里。

#claude-code #obsidian #pkm #markdown #automation #second-brain
免费

免费 PDF: Claude Code 速查表

输入邮箱即可获取一页 PDF,整理常用命令、审查习惯和安全工作流。

我们会妥善保护你的信息,不发送垃圾邮件。

让 Claude Code 真正进入可验证的工作流

先用免费 PDF 固定基础,再用 Gumroad 教材复用工作流;如果涉及团队导入、权限或收入路径,可以直接咨询。

Masa

关于作者

Masa

专注 Claude Code 实务流程、团队导入和内容转化的工程师。