Obsidian 项目同步 是做什么的?
仓库自我描述。 项目在哪、已完成的去哪、钩子日志放哪、以及哪条笔记声明仓库的约定:所有这些都来自仓库根目录的 .claude-vault.json,并通过 scan 返回。此技能不假设任何文件夹名称。
仓库不只是项目, 其约定笔记说明了每个文件夹的用途。在决定往哪里放新内容时请记住这一点:一个影响多个项目的决定或事实不应放在单个项目的笔记中。
先查本地地图
在任何其他操作之前,先查找此技能的本地地图:即用户常设指令(Claude Code 中的 CLAUDE.md 文件,从磁盘读取而非上下文中的副本——上下文副本可能早于编辑;或 Project 指令及 claude.ai 中的用户偏好)中名为 obsidian-project-sync local map 的部分。名为 此安装的本地地图 的部分(当此技能为一个 claude.ai 账号打包时添加在此 SKILL.md 内部)也算在内;但说了其他内容的常设指令优先。该地图包含只有该设置才知道的内容:仓库路径、用户基于仓库约定的自定义约定(每个项目携带的伴随笔记、标注样式)、以及指向仓库且必须随之更改的其他文件。按照它说的做;它会覆盖此处的默认值。
脚本和仓库
脚本是 scripts/vault.py,与此 SKILL.md 位于同一目录:从技能自己的文件夹构建其路径(Claude Code 在加载技能时会声明基础目录)。下面的每个示例都将其写为 vault.py。它需要 Python 3.8 或更高版本;如果找不到 python,请尝试 python3,然后 py。
仓库路径按以下顺序确定:来自本地地图、来自 OBSIDIAN_VAULT 环境变量、或向用户询问。使用 --vault 传递它,在子命令之前或之后。该脚本有意没有内置默认值:如果没有仓库,它会停止并告知用户。
总是先看后写
python vault.py scan --vault "<vault>"
若不使用 --project,它会返回仓库的清单:有哪些项目、在哪个区域以及什么 status。statuses_in_use 列出了用户已使用的值;选择其中之一,不要发明新标签。
然后是项目本身:
python vault.py scan --vault "<vault>" --project "<folder name>"
它返回文件夹、主笔记、其 frontmatter、其标题的精确列表、文件夹中的其他文件、会话日志(以及读取的日志文件,在 session_log_file 中)、以及 sessions_since_note_changed:比笔记上次磁盘修改更新的已记录会话。这最后一个列表是“笔记中尚未包含的内容”的起点,并不是其证据:在声称任何内容缺失之前,请对照笔记进行检查。
--project 接受仓库中项目文件夹的名称,匹配时不区分大小写和变音符号。当从代码仓库工作时,该文件夹通常具有仓库的名称;如果不是,请运行不带 --project 的 scan,并通过主笔记的 frontmatter(如果仓库使用 path 或 url 键)进行匹配,如果没有匹配则询问。
归档区域中的项目是已完成的。不要将其重新创建到活跃项目之间,或未经询问就进行更新。
第二步:阅读仓库的约定
scan 返回一个 config 块:
"config": {
"source": ".claude-vault.json",
"projects": "Projects",
"archive": "Archive",
"logs": "Toolbox/Claude Sync",
"conventions": "/path/to/vault/Meta/Conventions.md"
}
conventions 是支配一切的笔记。 在写入任何其他笔记之前先阅读它:它声明了 frontmatter 模式、该仓库中笔记的写作方式、哪些可以不经询问更改、哪些不能。此技能故意不携带其副本:副本会过时,而该技能的早期版本曾指向一个不再存在的文件夹。
如果 conventions 为 null,则该仓库未声明任何约定。在写入任何笔记前先询问。
同一个块说明了文件夹的来源。如果 source 是 defaults,则仓库尚未描述自身,您正在基于假设工作:请说明这一点,并提供写入 .claude-vault.json(格式见 references/mechanics.md)。如果出现 warning,则配置已损坏,需要修复才能继续。
每个项目文件夹都不同:有些只包含笔记和画布,其他的则带有零散笔记。相信 scan 的结果,不要相信上次看到的内容。
在笔记中写入
笔记是手写的。没有模板,没有隐藏标记,也不会添加。替换特定部分的内容:
python vault.py section --vault "<vault>" --note "<path>" --heading "Deploy" --content "<file>" --dry-run
标题的匹配忽略表情符号并允许部分匹配,因此 Deploy 会找到 ## 🚀 Deploy。如果没有匹配,脚本会列出存在的标题,而不是凭空制造一个。
--content 是一个包含新 markdown 的文件。将其写入临时文件夹(会话的暂存区或系统临时文件夹),绝不要写入仓库或代码仓库内,并在之后将其删除。
真正重要的规则:
- 始终先执行
--dry-run,并且在用户同意后才能执行真正的写入。 向用户展示 dry-run 的差异,说明每个更改是事实性的还是散文性的,然后等待。仅在他们同意后,才运行不带--dry-run的相同命令;一次同意仅涵盖他们看到的那些差异,不包括其他。 --content包含整个部分,而不仅仅是更改的部分。replace会替换该标题下的整个内容。仅发送一个表格,其下方的散文就会消失。读取当前部分,将更改融入其中,并发送完整结果。- 笔记的最后一部分受到保护。 页脚链接通常位于其下方,不属于任何部分,会被丢失。脚本会以退出码 2 停止并说明原因。使用
--mode append在不删除的情况下追加,这对于诸如 最近更改 的运行日志几乎总是正确的。 append模式尊重标注。 当某部分是围栏标注(```ad-note,来自 Admonition 插件)时,新文本会进入围栏内部,在其结束行之前;当它是原生标注(> [!note])时,新行会被引用以保持在其内部。无论哪种方式,页脚始终保持在最后。- 模仿已有的内容。 语言、语气、散文或列表、长度:仓库的约定笔记声明了它,而您即将触碰的笔记确认了它。在起草前阅读它。
- 在声称之前先检查。 写入笔记的计数、版本或状态必须来自您在本会话中读取或运行的内容。如果现在无法检查,请省略它或说明未经核实。
添加新部分需要说明它的放置位置:--heading "Testing" --after "Gotchas"。请先询问:新部分会改变用户编写的文档结构。
您可以不经询问更改的内容
仓库声明了它, 在其约定笔记中:声明哪些是事实性的且可检查的(因此可以不经询问进行修正),哪些是散文,未经许可不得触碰。
在您阅读它之前,将一切视为散文并询问。
当新信息不适合任何现有部分时,提供两种选择让用户决定:扩展一个部分,或创建一个单独的笔记并从主笔记链接。不要自行决定。
Frontmatter
模式属于每个仓库,其约定笔记声明了它。scan 返回每条笔记的实际 frontmatter,这是查看它的另一种方式。
python vault.py frontmatter --vault "<vault>" --note "<path>" --set updated=2026-09-13 --dry-run
仅标量键。脚本刻意拒绝块列表:更改笔记的主题是作者的决定。section --touch-updated 已经设置了日期,因此很少需要单独使用此功能。
由插件拥有的字段(如贴纸、图标)永远不会被触碰。
画布
.canvas 文件是包含 nodes 和 edges 的 JSON(JSON Canvas 1.0)。布局是手动制作的,包含有目的的分组和连接。
python vault.py canvas --vault "<vault>" --file "<path>.canvas" --report
它提供节点、边、组名、链接的笔记以及是否有任何指向不再存在的文件。
以及 state_outside_its_group,这是真正需要关注的字段。 一个按其 state 对笔记进行分组的画布将该信息存储两次:在 frontmatter 中以及在节点的位置和颜色中。更改 state,将卡片留在原位,画布不会损坏;它仍然指向一个存在的文件,并且仍然显示旧颜色。它开始撒谎,却没有任何警告。
因此,每当您更改 state 时,请检查该项目或文件夹是否有按状态分组的画布,并运行报告。 反之亦然:创建笔记后,检查它是否有一个卡片。不在画板上的笔记对于阅读画板的人来说是不存在的。
修复它意味着移动节点并重新着色,这是手动编辑:脚本只报告,不重新排列。请说明这一点并提出更改建议。
python vault.py canvas --file "<path>.canvas" --add-file "Relative/Path/Note.md" --color 6 --dry-run
新节点会落在现有所有内容下方的空白处:已有的任何内容都不会移动。将它们放置在组内是用户的手动工作,而且理应如此。一旦用户同意,再次运行而不带 --dry-run。
如果报告显示链接损坏,请说明并停止。不要删除节点。
会话日志
一个 SessionEnd 钩子将每个 Claude Code 会话的原始数据记录在仓库声明的日志文件夹(config.logs)中,每个仓库一个 JSON 文件:日期、分支、自上次以来的新提交、触碰的文件以及是否有未提交的内容。它不会写入任何笔记。
这是原始材料。当用户要求更新时,scan --project 会将其已读取的内容带到 session_log 和 sessions_since_note_changed 中。将这些提交与笔记所说的内容进行对比,并提出哪些部分已经过时。
如果钩子尚未安装,请阅读 references/hook-install.md。
来自 claude.ai 对话的笔记
claude.ai 项目不在磁盘上,因此那里没有脚本。在一个代码块中编写完整的 markdown,包含遵循用户仓库模式的 frontmatter(如果您不知道模式,请询问用户,或要求提供一个现有笔记用于复制),并说明将其保存到何处。如果项目已有笔记,请说明应将其粘贴到哪个部分,而不是创建一个独立的文件。
当事情失败时
python无法识别:尝试python3,然后py。如果都不行,则没有 Python;请说明并提议使用文件工具手动编辑,这更慢且风险更高。- “未提供仓库”:本地地图没有仓库路径,且
OBSIDIAN_VAULT未设置。向用户询问路径,并提议将其添加到本地地图。 - 脚本以退出码 2 停止:这是最后部分保护在起作用。切换到
--mode append。 - 差异显示删除的内容比预期的多:不要写入。这几乎总是因为
--content不完整。 - 项目的文件夹不存在:询问。不要自行在项目区域创建新文件夹。
参考资料
references/hook-install.md:在 Windows、macOS 和 Linux 上安装会话记录钩子。references/mechanics.md:脚本的工作原理及其保证:配置文件、如何找到标题、保护措施、日志格式。
写作约定不在此处。 它们存在于仓库中,存在于 scan 在 config.conventions 中返回的笔记中。