mcpField
W
wiki-import

wiki-import

github.com/ar9av生产力3.6k992 次安装1 天前更新
代码仓库

关于 wiki-import

wiki-import 用于从 wiki-export 生成的 graph.json 或 OKF 包,将知识导入当前 Obsidian Vault。它支持导入图结构页面存根或保留正文的完整页面,并提供合并、跳过和覆盖冲突模式。

主要功能

  1. 01导入 graph.json 或 OKF 包
  2. 02从图数据重建页面存根和关系
  3. 03从 OKF 包还原完整页面
  4. 04支持合并、跳过和覆盖模式
  5. 05更新 Vault 元数据并转换页面链接

适用场景

  1. 01将 graph.json 导出的结构导入 Vault
  2. 02在 Vault 之间迁移完整 Markdown 页面
  3. 03按所选冲突模式合并导入页面

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/ar9av/obsidian-wiki --skill wiki-import

安装后在 Claude Code 中输入/wiki-import确认已加载。

详细介绍

Wiki 导入 — 从导出文件重建页面

你正在将某个知识库中的知识导入当前知识库,来源是 wiki-export 生成的以下两种文件之一:

  • graph.json — 图谱骨架。用于重建页面存根(frontmatter、带类型的关系和 ## Related 链接列表,不含正文)。会丢失部分信息。
  • OKF 捆绑包(wiki-export/okf/ 目录)— 实际的 Markdown 文件。用于重建包含原始正文的完整页面。无损。要在知识库之间进行真正的迁移,请使用此方式。

无论哪种方式,导入都会写入带有正确 frontmatter 和双链的页面,然后更新所有知识库元数据。第 2 步、第 3 步(仅图谱)和第 5 步通用;第 4 步根据来源类型分流。

开始之前

**写作配置:**在起草或改写自然语言 Markdown 之前,请阅读并应用 llm-wiki/SKILL.md 中的 Writing Profile Resolution 部分。框架架构、来源信息、安全和特定操作要求优先。 保留导入来源中的原文;仅对新生成的元数据或存根应用 WRITING.md 中的偏好。

  1. 解析配置 — 遵循 llm-wiki/SKILL.md 中的 Config Resolution Protocol(内联 @name 覆盖 → 从当前工作目录向上查找 .env → 全局配置 → 提示设置)。由此获得 OBSIDIAN_VAULT_PATH。
  2. 如果 $OBSIDIAN_VAULT_PATH/AGENTS.md 存在,则读取该文件,并遵循其中针对所有者的约定。

第 1 步:定位并检测来源类型

查找导入来源:

  • 如果用户提供了路径参数,请直接使用。
  • 否则按以下顺序自动检测:./wiki-export/okf/(目录)→ ./wiki-export/graph.json(文件)。
  • 如果两者都不存在,请向用户询问路径。

检测来源类型:

  • 路径是一个以 .json 结尾的文件 → graph.json 导入(先按下文验证,再执行第 3 步和第 4 步-Graph)。
  • 路径是一个包含 .md 文件的目录,这些文件带有 OKF frontmatter(含 type: 键),和/或根目录下有一个包含 okf_version 的 index.md → OKF 捆绑包导入(跳过第 3 步;转到第 4 步-OKF)。接受 okf_version 为 0.1 和 0.2 的捆绑包;第 4 步-OKF 可读取这两种版本。
  • 其他情况 → 报告问题并停止。

验证 graph.json 来源:

  • 必须是有效的 JSON
  • 顶层必须包含以下键:nodes(数组)、links(数组)、graph(对象)
  • 至少必须有 1 个节点

如果验证失败,请报告问题并停止。

验证 OKF 捆绑包来源:

  • 必须至少包含 1 个非保留 .md 文件(即不是 index.md/log.md),且该文件包含可解析的 YAML frontmatter,其中有非空的 type。
  • 没有 frontmatter 或没有 type 的 .md 文件会被跳过(并计数),不会导致失败 — OKF 使用方采取宽松策略(OKF §11)。

导入前显示预览:

graph.json:

Import preview (graph.json — stubs)
  Source: <path>  (exported at <graph.exported_at>)
  Nodes:  N total  (concepts: A, entities: B, skills: C, references: D, ...)
  Links:  M edges  (X typed, Y untyped)
  Target: $OBSIDIAN_VAULT_PATH

OKF 捆绑包:

Import preview (OKF bundle — full pages)
  Source: <dir>  (okf_version <ver if present>)
  Pages:  N total  (concepts: A, entities: B, skills: C, references: D, ...)
  Target: $OBSIDIAN_VAULT_PATH

第 2 步:确定冲突解决模式

根据用户的措辞确定模式。默认模式为 merge。

模式触发短语行为
merge(默认,无特殊措辞)对于现有页面:更新 frontmatter 标签/摘要/关系,并添加缺失的双链;对于新页面:创建存根。
skip“skip existing”、“don't overwrite”、“only new pages”完全不更改现有页面;仅创建尚不存在的页面。
overwrite“overwrite”、“replace existing”、“force import”无论现有内容如何,都用重新构建的存根替换所有匹配页面。

第 3 步:构建内部映射 (仅限 graph.json — OKF 捆绑包跳过)

写入任何内容之前,先从 links 数组构建两个映射:

邻接映射 — 对于每个节点 ID,收集所有邻居 ID(包括两个方向的边):

adjacency["concepts/transformers"] = ["entities/vaswani", "concepts/lstm", ...]

带类型的边映射 — 对于每个节点 ID,仅收集向外的带类型边(typed: true):

typed_edges["concepts/transformers"] = [
  {target: "concepts/lstm", relation: "contradicts"},
  ...
]

步骤 4-Graph:从 graph.json 重建页面 (仅限 graph.json 源)

记录计数:created = 0、skipped = 0、merged = 0。

对于 nodes 中的每个节点:

  1. 计算 page_path = $VAULT/<node.id>.md
  2. 确保父目录存在(例如 $VAULT/concepts/)
  3. 检查文件是否已存在:
    • 合并模式(默认)+ 文件存在 → 读取现有文件,应用合并逻辑(见下文),merged 加 1
    • 合并模式(默认)+ 文件不存在 → 创建存根,created 加 1
    • 跳过模式 + 文件存在 → skipped 加 1,继续处理下一个节点
    • 跳过模式 + 文件不存在 → 创建存根,created 加 1
    • 覆盖模式 + 文件存在 → 写入新的存根(覆盖),merged 加 1
    • 覆盖模式 + 文件不存在 → 创建存根,created 加 1
页面模板(新建或覆盖)
---
title: <node.label>
category: <node.category>
tags: <node.tags as YAML list>
sources:
  - "imported from <graph.json path>"
<if node.summary exists>
summary: "<node.summary>"
</if>
<if typed_edges[node.id] is non-empty>
relationships:
<for each {target, relation} in typed_edges[node.id]>
  - target: "[[<target>]]"
    type: <relation>
</for>
</if>
lifecycle: draft
lifecycle_changed: <today YYYY-MM-DD>
base_confidence: 0.5
tier: supporting
created: <ISO timestamp>
updated: <ISO timestamp>
---

# <node.label>

<node.summary paragraph if available, else omit>

## Related

<for each neighbour in adjacency[node.id], sorted alphabetically>
<if edge is typed>
- [[<neighbour>]] — <relation>
<else>
- [[<neighbour>]]
</if>
</for>

如果 adjacency[node.id] 为空,则完全省略 ## Related 部分。

合并逻辑(合并模式,页面已存在)
  1. 读取现有页面的 frontmatter。
  2. 标签:合并现有标签和 node.tags(去重,保留现有顺序,将新标签追加到末尾)。
  3. 摘要:如果现有页面没有 summary 字段,且 node.summary 存在,则添加该字段。
  4. 关系:合并现有的 relationships: 条目和 typed_edges[node.id],跳过已存在相同 (target, type) 对的条目。
  5. 更新时间:将 updated 设置为当前 ISO 时间戳。
  6. 正文:查找 ## Related 部分。如果存在,则追加 adjacency[node.id] 中尚未在正文任何位置链接的缺失维基链接。如果不存在,则追加一个包含缺失链接的部分。
  7. 保持正文的其余部分不变。

步骤 4-OKF:从 OKF 包重建页面 (仅限 OKF 源)

记录计数:created = 0、skipped = 0、merged = 0、unparseable = 0。

遍历包目录树。对于每个非保留文件(index.md、log.md)的 .md 文件:

  1. 解析 YAML frontmatter。如果没有 frontmatter 或没有非空的 type,则将 unparseable 加 1 并跳过该文件。
  2. 计算概念 ID = 文件相对于 bundle 根目录的路径,去掉 .md(例如,concepts/transformers.md → concepts/transformers)。目标页面为 $VAULT/<concept-id>.md。
  3. 反向映射 frontmatter(即 wiki-export 第 3.5 步中规范映射表的逆向操作):
    • title ← title。
    • category ← 如存在,则取保留的 category 扩展键;否则,将概念 ID 的目录前缀转为小写(concepts/… → concepts);否则,根据 type 推导(Concept→concepts,Entity→entities,Skill→skills,Reference→references,Synthesis→synthesis,Project→projects,Journal→journal)。
    • tags ← tags。
    • summary ← description。
    • updated ← 如存在,则取保留的 updated 扩展键;否则取 generated.at;否则取旧版 v0.1 的 timestamp;否则取当前时间。
    • created ← 如存在,则取保留的 created 扩展键;否则取当前时间。
    • sources ← OKF sources,作为字符串列表:对于每个映射条目,取其 resource;普通字符串保持原样(旧版导出生成的 v0.1 bundle 会以这种方式携带原生列表)。如果 bundle 没有 sources,则使用 ["imported from OKF bundle <bundle path>"]。如果存在顶层 resource URL,且它尚未在列表中,则将其添加进去。
    • lifecycle ← 如存在,则取保留的 lifecycle 扩展键;否则根据 status 设置:draft → draft,deprecated → archived,stable 或缺省 → 不设置 lifecycle。(OKF 没有与 reviewed/verified/disputed 等效的字段,因此只有通过保留键,才能恢复我们自己的导出内容。)
    • 原样保留其他任何扩展键(relationships、lifecycle_changed、tier、base_confidence 等),以及 OKF 专属键 generated、verified、stale_after 和 usage_window,以免后续导出时丢失。这些键可确保往返转换无损。将单独的 verified 映射视为单元素列表(OKF §5.2)。
    • 但排除由我们自己的导出器推导出的内容。 如果 generated.by 以 obsidian-wiki/ 开头,则丢弃 generated(它是根据 updated 计算得出的);丢弃 by 为 human:vault-owner 的 verified 条目(它们是根据源知识库的信任账本计算得出的,不会随内容传递)。保留这些内容会添加源页面中原本不存在的键。来自其他任何生成方的 generated 或 verified 块都应保留。
  4. 反向转换正文中的链接 — 将指向 .md 路径的 Markdown 链接转换为 wikilink(这会恢复真实的交叉链接,以及导出器根据 wiki-export 第 3.5 步保留的前向引用):
    • [text](../concepts/transformers.md) 或 [text](/concepts/transformers.md) → 将路径解析(相对于此文件所在目录;以 / 开头的绝对路径则相对于 bundle 根目录)为概念 ID → [[concepts/transformers]];如果 text 与目标标题不同,则使用 [[concepts/transformers|text]]。目标标题取自 bundle 中对应页面;如果页面不存在,导出器写入的链接文本就是目标 ID,因此当文本等于完整 ID(或其最后一个路径段)时,不添加别名。
    • 应首先将 Markdown 链接目标视为文件路径:相对于当前文件规范化 .md 路径,然后去掉解析后文件路径末尾的 .md,以还原页面 ID。不要在解析完整文件路径之前,尝试根据目录遍历片段推断 ID。这样可保留文件夹笔记布局的往返转换,例如 projects/social-twitter.md 加上 projects/social-twitter/...,其中 ../../social-twitter.md 必须还原为 projects/social-twitter。
    • 即使目标页面不在 bundle 中,也要应用此规则——指向路径的链接应还原为指向尚未创建页面的 [[wikilink]](Obsidian 支持这种链接;OKF §11 规定不得拒绝)。不要将其保留为 Markdown 链接。
    • 当配置中设置了 OBSIDIAN_LINK_FORMAT=markdown 时,保留 Markdown 链接(只需将路径改写为相对于知识库根目录的路径);不要转换为 wikilink。
    • 外部 http(s):// 链接和 # Citations 部分保持不变。
  5. 写入页面,采用第 2 步中的冲突模式:
    • merge + 存在 → 反向映射 frontmatter 并将其合并到现有页面(合并 tags;仅在缺失时填入 summary/sources;合并 relationships;刷新 updated)。对于正文,OKF 携带的是真实正文:仅当现有页面为存根(正文只有标题 + ## Related)时,才用 bundle 正文替换现有正文;否则保留现有正文,并追加尚未存在的 bundle # Citations / 新 ## 部分。将 merged 加 1。
    • merge + 不存在 → 写入完整页面(frontmatter + 完整 bundle 正文)。将 created 加 1。
    • skip + 存在 → 将 skipped 加 1,然后继续。
    • skip + 不存在 → 写入完整页面。将 created 加 1。
    • overwrite + 存在 → 写入完整页面,替换现有页面。将 merged 加 1(在摘要中标记为 Replaced)。
    • overwrite + 不存在 → 写入完整页面。将 created 加 1。
  6. 写入前确保父目录($VAULT/concepts/ 等)存在。 与 graph.json 路径不同,不要生成 ## Related 占位章节——bundle 正文中已包含真实的交叉链接。

第 5 步:更新 Vault 元数据

.manifest.json

添加一条新记录,以 graph.json 文件的规范路径为键:

"<absolute path to source>": {
  "ingested_at": "<ISO timestamp>",
  "source_type": "wiki-export",
  "pages_created": ["list/of/created/pages.md"],
  "pages_updated": ["list/of/merged/pages.md"]
}

对于 graph.json 导入,将 source_type 设为 "wiki-export";对于 OKF bundle 导入,则设为 "okf-bundle"。以来源的绝对路径作为键(graph.json 文件或 bundle 目录)。

同时递增:

  • stats.total_sources_ingested 加 1
  • stats.total_pages 加上实际创建的页面数(不包括跳过或合并的页面)

如果 .manifest.json 不存在,请按标准结构创建:

{
  "stats": {
    "total_sources_ingested": 1,
    "total_pages": <created count>
  },
  "<graph.json path>": { ... }
}
index.md、log.md 和 hot.md

一次加锁调用。索引会根据磁盘上的现有页面进行协调,因此每个创建或合并的页面都会归入相应类别,并使用文档规定的 ( #tag) 间距:

obsidian-wiki memory sync IMPORT \
  source="<bundle path>" \
  pages_created=<N> pages_skipped=<K> pages_merged=<M> \
  --takeaways "Imported <bundle>: <what it adds to the picture>"

如果导入没有改变整体情况,请省略 --takeaways;之前的要点会保留。切勿手动编辑 index.md、log.md 或 hot.md——该命令会获取锁,防止并行写入者丢失你的更新。

完整流程请参阅 .skills/llm-wiki/references/MEMORY.md。

第 6 步:打印摘要

Wiki import complete → $OBSIDIAN_VAULT_PATH
  Source:  <source path>  (<graph.json | OKF bundle>)
           <graph: exported at <graph.exported_at>, N nodes, M links | okf: N pages, okf_version <ver>>
  Created: <X> pages
  Merged:  <Z> pages  (existing pages updated)
  Skipped: <Y> pages  (only when --skip mode was used)

只有在明确请求 skip 模式时,才显示 Skipped 行。如果使用了 overwrite 模式,则将合并的页面标记为 Replaced。对于 OKF 导入,如果 U > 0,还需报告 Unparseable: <U> files (no frontmatter/type, skipped)。

备注

  • 占位页面与完整页面:graph.json 导入会生成占位页面——包含结构和 wikilink,但没有正文,可作为后续摄取的起点。OKF bundle 导入会生成带有真实正文的完整页面——这是无损且包含内容的路径,也是 vault 到 vault 传输的正确选择。
  • 可安全重复运行:默认的 merge 模式具有幂等性——对未更改的导出重复运行会更新时间戳,但不会破坏内容。只有在需要将页面完全重置为占位页面时,才使用 overwrite。
  • 创建目录:写入页面前,始终先创建缺失的类别目录。
  • 失效的 wikilink:由于这些页面是从同一导出中一起创建的,大多数链接都能解析。任何在 links 中引用、但不在 nodes 中的节点(原始导出中的失效链接)仍会显示为 wikilink——只是不会有对应的页面文件,这是有效的。
  • 经过筛选的导出:如果源 graph.json 是通过可见性筛选生成的(记录在 graph.metadata 中),导入的页面将只反映筛选后的集合。如果 graph.graph 包含 filtered 键,请在摘要中注明这一点。

评论

支持 Markdown