关于 wiki-export
wiki-export 是用于导出 Obsidian 知识库 wikilink 图谱的技能,输出可供图谱工具、数据库和浏览器可视化使用的格式。支持按项目或可见性筛选,也可另行导出 Markdown 文档包。
主要功能
- 01导出 JSON、GraphML、Cypher、SQL 和 HTML 图谱
- 02提取 wikilink 与类型化关系
- 03按标签为页面划分社群
- 04按项目或可见性筛选
- 05可选导出 OKF Markdown 文档包
适用场景
- 01将知识库页面和链接载入 Neo4j 或 Postgres
- 02在 Gephi 或浏览器中探索知识库图谱
- 03导出指定项目或面向公众的页面
- 04生成供内容复用的 Markdown 文档包
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ ar9av/ obsidian-wiki --skill wiki-export安装后在 Claude Code 中输入/wiki-export确认已加载。
详细介绍
Wiki 导出 — 知识图谱导出
将 Wiki 的 wikilink 图导出为结构化格式,以便在外部工具(Gephi、Neo4j、自定义脚本、浏览器可视化)中使用。
开始之前
- 解析配置 — 按照
llm-wiki/SKILL.md中的配置解析协议操作(内联@name覆盖 → 沿 CWD 向上查找.env→ 全局配置 → 提示设置)。这会提供OBSIDIAN_VAULT_PATH - 确认保管库中有可供导出的页面 — 如果页面少于 5 个,请警告用户并停止
项目筛选(可选)
如果用户的调用中包含项目名称 — 例如 /wiki-export prismor、"export the prismor project"、"export project:security" — 启用项目筛选模式:
- 从参数或短语中提取项目名称。标准化处理:转换为小写,并去掉单词 "project"。
- 仅保留符合任一条件的页面:
- 页面
id以projects/<name>/开头(基于路径匹配) - 页面的
tags数组包含<name>(基于标签匹配)
- 页面
- 删除任一端点已被排除的边。
- 在摘要中注明筛选条件:
(filtered: project:<name> — X of Y pages) - 在 JSON 输出中设置
graph.graph.filter = "project:<name>"。
如果项目筛选和可见性筛选都已启用,则同时应用两者(先应用项目筛选,再对剩余页面应用可见性筛选)。
可见性筛选(可选)
默认情况下,导出所有页面,不受可见性标签影响。这样可以保持现有行为。
如果用户请求筛选导出 — 例如 "public export"、"user-facing export"、"exclude internal"、"no internal pages" — 启用可见性筛选模式:
- 构建一个屏蔽标签集合:
{visibility/internal, visibility/pii} - 构建节点列表时,跳过 frontmatter 标签中包含屏蔽标签的页面
- 跳过任一端点已被排除的边
- 在摘要中注明筛选条件:
(filtered: visibility/internal, visibility/pii excluded)
没有 visibility/ 标签或带有 visibility/public 标签的页面始终会被包含。
步骤 1:构建节点列表和边列表
遍历 vault 中所有 .md 文件(排除 _archives/、_raw/、_readouts/、.obsidian/、index.md、log.md、_insights.md)。收集完整文件列表后,再应用所有有效筛选条件(项目和/或可见性)。
对于每个页面,从 frontmatter 中提取:
id— 相对于 vault 根目录的路径,不带.md扩展名(例如concepts/transformers)label— frontmatter 中的title字段;如果缺失,则使用文件名category— 目录前缀(concepts、entities、skills、references、synthesis、projects或journal)tags— frontmattertags字段中的数组summary— 存在时使用 frontmatter 的summary字段
这就是你的节点列表。
对于每个页面,在正文中搜索 \[\[.*?\]\],提取所有 wikilink:
- 解析每个
[[target]]或[[target|display]]— 仅使用 target 部分 - 将 target 解析为节点 id(规范化:转为小写,将空格替换为连字符,移除
.md) - 跳过指向节点列表之外的链接(断开的链接)
- 每个解析成功的链接都会成为一条边:
{source: page_id, target: linked_id, relation: "wikilink", confidence: "EXTRACTED"} - 如果链接所在句子以
^[inferred]或^[ambiguous]结尾,则相应覆盖confidence
类型化边增强: 构建完 wikilink 边列表后,读取每个页面 frontmatter 中的 relationships: 块。对于每个 {target, type} 条目:
targetYAML 值是带引号的 wikilink 字符串,例如"[[concepts/lstm]]"。去掉外围的[[和]]字符,然后按相同规则规范化(转为小写,将空格替换为连字符,移除.md),得到节点 id。- 跳过解析后的目标不在节点列表中的条目(断开的链接)
- 如果此
(source, target)对应的边已存在,则将其relation字段覆盖为类型值(例如"contradicts"),并设置typed: true - 如果此对的边尚不存在,则添加一条:
{source: page_id, target: target_id, relation: <type>, confidence: "EXTRACTED", typed: true}
这意味着,relation: "wikilink" 是普通未类型化链接的默认值;relationships: 条目会将其升级为具名语义类型。同时由正文 wikilink 和 relationships: 条目共同产生的边只保留一条记录 — 以类型化版本为准。
这就是你的边列表。
步骤 2:分配社区 ID
按标签聚类将页面分组到不同社区:
- 具有相同主导标签的页面属于同一个社区
- 主导标签 = 页面 frontmatter
tags数组中的第一个标签 - 没有标签的页面,其社区 id 为
null - 社区编号从 0 开始,按大小降序排列(最大社区 = 0)
这可用于 HTML 可视化中的社区着色,以及 Gephi 等工具。
步骤 3:编写输出文件
如果 vault 根目录下不存在 wiki-export/,则创建该目录。写入以下全部五个文件:
3a. graph.json
NetworkX node_link 格式 — 图工具和脚本的标准格式:
{
"directed": false,
"multigraph": false,
"graph": {
"exported_at": "<ISO timestamp>",
"vault": "<OBSIDIAN_VAULT_PATH>",
"total_nodes": N,
"total_edges": M
},
"nodes": [
{
"id": "concepts/transformers",
"label": "Transformer Architecture",
"category": "concepts",
"tags": ["ml", "architecture"],
"summary": "The attention-based architecture introduced in Attention Is All You Need.",
"community": 0
}
],
"links": [
{
"source": "concepts/transformers",
"target": "entities/vaswani",
"relation": "wikilink",
"confidence": "EXTRACTED"
},
{
"source": "concepts/transformers",
"target": "concepts/lstm",
"relation": "contradicts",
"confidence": "EXTRACTED",
"typed": true
}
]
}
3b. graph.graphml
GraphML XML 格式 — 可在 Gephi、yEd 和 Cytoscape 中加载:
<?xml version="1.0" encoding="UTF-8"?>
<graphml xmlns="http://graphml.graphdrawing.org/graphml">
<key id="label" for="node" attr.name="label" attr.type="string"/>
<key id="category" for="node" attr.name="category" attr.type="string"/>
<key id="tags" for="node" attr.name="tags" attr.type="string"/>
<key id="community" for="node" attr.name="community" attr.type="int"/>
<key id="relation" for="edge" attr.name="relation" attr.type="string"/>
<key id="type" for="edge" attr.name="type" attr.type="string"/>
<key id="confidence" for="edge" attr.name="confidence" attr.type="string"/>
<graph id="wiki" edgedefault="undirected">
<node id="concepts/transformers">
<data key="label">Transformer Architecture</data>
<data key="category">concepts</data>
<data key="tags">ml, architecture</data>
<data key="community">0</data>
</node>
<edge source="concepts/transformers" target="entities/vaswani">
<data key="relation">wikilink</data>
<data key="confidence">EXTRACTED</data>
</edge>
<edge source="concepts/transformers" target="concepts/lstm">
<data key="relation">contradicts</data>
<data key="type">contradicts</data>
<data key="confidence">EXTRACTED</data>
</edge>
</graph>
</graphml>
每个页面写入一个 <node>,每条链接写入一个 <edge>。对于类型化边(边列表中 typed: true 的边),同时输出带有语义类型值的 <data key="relation"> 和具有相同值的 <data key="type"> — 这样既能让已使用 relation 的工具继续读取,也能让支持类型的工具通过专用 type 键进行筛选。未类型化的 wikilink 完全省略 <data key="type"> 元素。
3c. cypher.txt
Neo4j Cypher MERGE 语句 — 粘贴到 Neo4j Browser 中,或通过 cypher-shell 运行:
// Wiki knowledge graph export — <TIMESTAMP>
// Load with: cypher-shell -u neo4j -p password < cypher.txt
// Nodes
MERGE (n:Page {id: "concepts/transformers"}) SET n.label = "Transformer Architecture", n.category = "concepts", n.tags = ["ml","architecture"], n.community = 0;
MERGE (n:Page {id: "entities/vaswani"}) SET n.label = "Ashish Vaswani", n.category = "entities", n.tags = ["person","ml"], n.community = 0;
MERGE (n:Page {id: "concepts/lstm"}) SET n.label = "LSTM", n.category = "concepts", n.tags = ["ml","rnn"], n.community = 0;
// Relationships
// Untyped wikilinks use [:WIKILINK]
MATCH (a:Page {id: "concepts/transformers"}), (b:Page {id: "entities/vaswani"}) MERGE (a)-[:WIKILINK {relation: "wikilink", confidence: "EXTRACTED"}]->(b);
// Typed edges use the relationship type as the label (UPPERCASE)
MATCH (a:Page {id: "concepts/transformers"}), (b:Page {id: "concepts/lstm"}) MERGE (a)-[:CONTRADICTS {relation: "contradicts", confidence: "EXTRACTED"}]->(b);
每个页面写入一条节点 MERGE 语句,然后为每条边写入一条关系 MATCH/MERGE 语句。对于类型化边,将 type 值转为大写,作为 Cypher 关系标签(例如,contradicts → [:CONTRADICTS],derived_from → [:DERIVED_FROM])。未类型化的 wikilink 始终使用 [:WIKILINK]。
3d. postgres.sql
纯 SQL——可通过 psql -f postgres.sql 或迁移运行器加载到任意 Postgres 数据库(本地、Supabase、RDS、Neon 等)。包含两张表:wiki_pages(节点)和 wiki_edges(链接),并使用 ON CONFLICT upsert,因此重新运行导出安全且幂等,与 cypher.txt 的 MERGE 语义一致。
-- Wiki knowledge graph export — <TIMESTAMP>
-- Load with: psql -d yourdb -f postgres.sql
CREATE TABLE IF NOT EXISTS wiki_pages (
id TEXT PRIMARY KEY,
label TEXT NOT NULL,
category TEXT,
tags JSONB NOT NULL DEFAULT '[]'::jsonb,
summary TEXT,
community INT
);
CREATE TABLE IF NOT EXISTS wiki_edges (
source TEXT NOT NULL REFERENCES wiki_pages(id) ON DELETE CASCADE,
target TEXT NOT NULL REFERENCES wiki_pages(id) ON DELETE CASCADE,
relation TEXT NOT NULL DEFAULT 'wikilink',
confidence TEXT,
typed BOOLEAN NOT NULL DEFAULT false,
PRIMARY KEY (source, target, relation)
);
CREATE INDEX IF NOT EXISTS wiki_edges_source_idx ON wiki_edges(source);
CREATE INDEX IF NOT EXISTS wiki_edges_target_idx ON wiki_edges(target);
-- Nodes
INSERT INTO wiki_pages (id, label, category, tags, summary, community)
VALUES ('concepts/transformers', 'Transformer Architecture', 'concepts', '["ml","architecture"]'::jsonb, 'The attention-based architecture introduced in Attention Is All You Need.', 0)
ON CONFLICT (id) DO UPDATE SET
label = EXCLUDED.label, category = EXCLUDED.category, tags = EXCLUDED.tags,
summary = EXCLUDED.summary, community = EXCLUDED.community;
-- Edges
-- Untyped wikilink
INSERT INTO wiki_edges (source, target, relation, confidence, typed)
VALUES ('concepts/transformers', 'entities/vaswani', 'wikilink', 'EXTRACTED', false)
ON CONFLICT (source, target, relation) DO UPDATE SET confidence = EXCLUDED.confidence, typed = EXCLUDED.typed;
-- Typed edge from relationships: block
INSERT INTO wiki_edges (source, target, relation, confidence, typed)
VALUES ('concepts/transformers', 'concepts/lstm', 'contradicts', 'EXTRACTED', true)
ON CONFLICT (source, target, relation) DO UPDATE SET confidence = EXCLUDED.confidence, typed = EXCLUDED.typed;
每个页面写入一条 INSERT ... ON CONFLICT (id) DO UPDATE 语句(对值进行转义:单引号加倍,tags 序列化为 JSON 数组字面量并转换为 jsonb),然后每条边写入一条 INSERT ... ON CONFLICT (source, target, relation) DO UPDATE 语句。这里的 relation 保持小写(不同于大写的 Cypher 关系标签),因为它是普通列值,而不是架构标识符——这样可以直接用 WHERE relation = 'contradicts' 进行筛选,或直接联接,无需统一大小写。只有由 relationships: frontmatter 条目提升的边,其 typed 才为 true;普通 wikilink 保持 false。
如果某个页面的 id 仅在先前运行触发 ON CONFLICT 子句后发生冲突,则跳过该页面——不要尝试对合成多重边去重(例如,同一 source/target 同时存在 wikilink 和有类型的关系),因为复合主键 (source, target, relation) 已将它们保留为不同的行,这与 graph.json/graph.graphml 在查询层的“有类型版本优先”合并行为一致(SELECT * FROM wiki_edges WHERE source=$1 AND target=$2 ORDER BY typed DESC LIMIT 1)。
3e. graph.html
使用 vis.js CDN 的独立交互式可视化(无本地依赖)。用户可在任意浏览器中打开此文件——无需服务器。
按以下步骤构建 HTML 文件:
- 为 vis.js 生成节点对象的 JSON 数组:
{id: "concepts/transformers", label: "Transformer Architecture", color: {background: "#4E79A7"}, size: <degree * 3 + 8>, title: "concepts | #ml #architecture", community: 0}
- 按 community 着色(循环使用:
#4E79A7、#F28E2B、#E15759、#76B7B2、#59A14F、#EDC948、#B07AA1、#FF9DA7、#9C755F、#BAB0AC) - 按度数(入链数 + 出链数)设置大小:
size = degree * 3 + 8,最大为 60 title= 悬停时显示的工具提示文本:category、tags、summary(如有)
- 为 vis.js 生成边对象的 JSON 数组:
// Untyped wikilink
{from: "concepts/transformers", to: "entities/vaswani", dashes: false, width: 1, color: {color: "#666", opacity: 0.6}, title: "wikilink"}
// Typed edge
{from: "concepts/transformers", to: "concepts/lstm", dashes: false, width: 2, color: {color: "#E15759", opacity: 0.8}, label: "contradicts", font: {size: 9, color: "#ccc"}, title: "contradicts"}
- 对 INFERRED 边设置
dashes: true - 对 AMBIGUOUS 边设置
dashes: [4,8] - 有类型的边(
typed: true):设置width: 2,添加显示类型的label字段,并应用特定类型的颜色:
| 类型 | 边颜色 |
|---|---|
extends | #59A14F(绿色) |
implements | #4E79A7(蓝色) |
contradicts | #E15759(红色) |
derived_from | #F28E2B(橙色) |
uses | #76B7B2(青绿色) |
replaces | #B07AA1(紫色) |
related_to | #BAB0AC(灰色——与无类型边相同) |
无类型的 wikilink 边保留现有的 #666 灰色,且不添加标签。
- 编写完整的 HTML 文件:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Wiki Knowledge Graph</title>
<script src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { background: #0f0f1a; color: #e0e0e0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; display: flex; height: 100vh; }
#graph { flex: 1; }
#sidebar { width: 260px; background: #1a1a2e; border-left: 1px solid #2a2a4e; padding: 14px; overflow-y: auto; font-size: 13px; }
#sidebar h3 { color: #aaa; font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; margin: 0 0 10px; }
#info { margin-bottom: 16px; line-height: 1.6; color: #ccc; }
.legend-item { display: flex; align-items: center; gap: 8px; padding: 3px 0; font-size: 12px; }
.dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
#stats { margin-top: 16px; color: #555; font-size: 11px; }
</style>
</head>
<body>
<div id="graph"></div>
<div id="sidebar">
<h3>Wiki Knowledge Graph</h3>
<div id="info">Click a node to see details.</div>
<h3 style="margin-top:12px">Communities</h3>
<div id="legend"></div>
<div id="stats"></div>
</div>
<script>
const NODES_DATA = /* NODES_JSON */;
const EDGES_DATA = /* EDGES_JSON */;
const COMMUNITY_COLORS = ["#4E79A7","#F28E2B","#E15759","#76B7B2","#59A14F","#EDC948","#B07AA1","#FF9DA7","#9C755F","#BAB0AC"];
const nodes = new vis.DataSet(NODES_DATA);
const edges = new vis.DataSet(EDGES_DATA);
const network = new vis.Network(document.getElementById('graph'), {nodes, edges}, {
physics: { solver: 'forceAtlas2Based', forceAtlas2Based: { gravitationalConstant: -60, springLength: 120 }, stabilization: { iterations: 200 } },
interaction: { hover: true, tooltipDelay: 100 },
nodes: { shape: 'dot', borderWidth: 1.5 },
edges: { smooth: { type: 'continuous' }, arrows: { to: { enabled: true, scaleFactor: 0.4 } } }
});
network.once('stabilizationIterationsDone', () => network.setOptions({ physics: { enabled: false } }));
network.on('click', ({nodes: sel}) => {
if (!sel.length) return;
const n = NODES_DATA.find(x => x.id === sel[0]);
if (!n) return;
document.getElementById('info').innerHTML = `<b>${n.label}</b><br>Category: ${n.category||'—'}<br>Tags: ${n.tags||'—'}<br>${n.summary ? '<br>'+n.summary : ''}`;
});
// Build legend
const communities = {};
NODES_DATA.forEach(n => { if (n.community != null) communities[n.community] = (communities[n.community]||0)+1; });
const leg = document.getElementById('legend');
Object.entries(communities).sort((a,b)=>b[1]-a[1]).forEach(([cid, count]) => {
const color = COMMUNITY_COLORS[cid % COMMUNITY_COLORS.length];
leg.innerHTML += `<div class="legend-item"><div class="dot" style="background:${color}"></div>Community ${cid} (${count})</div>`;
});
document.getElementById('stats').textContent = `${NODES_DATA.length} pages · ${EDGES_DATA.length} links`;
</script>
</body>
</html>
将 /* NODES_JSON */ 和 /* EDGES_JSON */ 替换为第 1 步中生成的实际 JSON 数组。
第 3.5 步:OKF Bundle 导出(可选)
仅当用户要求 OKF / markdown bundle 时才运行此步骤(例如“导出为 OKF”“OKF bundle”“open knowledge format”“导出为 markdown bundle”)。此步骤是附加操作——始终生成上述五个图文件;此外还会写入一个保留完整内容的 markdown bundle。
五个图文件是有损投影(仅包含图的骨架)。OKF bundle 包含实际页面正文,因此通过 OKF 执行导出→wiki-import 往返可保留完整内容,并且 bundle 可直接用于 MkDocs、Notion、Hugo、GitHub 渲染器,或任何 Open Knowledge Format 消费端。
规范化 frontmatter 映射(obsidian-wiki ⇄ OKF)
此表是映射的唯一真实来源;wiki-import 会引用此表来处理反向映射。
| OKF 键 | ← 导出自 | → 导入到 | 备注 |
|---|---|---|---|
type(必填) | category,首字母大写 | category(全小写) | concepts→Concept,entities→Entity,skills→Skill,references→Reference,synthesis→Synthesis,projects→Project,journal→Journal。OKF 要求提供 type;使用方可以接受任意字符串。 |
title | title | title | 原样保留。 |
description | summary | summary | 我们单行的 summary: 就是 OKF 的 description(用于 index.md 条目)。 |
tags | tags | tags | 列表原样保留。visibility/* 系统标签原样传递。 |
generated | updated → generated.at;by 是生成方 | updated ← generated.at | 写入 generated: { by: obsidian-wiki/<version>, at: <datetime> },其中 <version> 取自 obsidian-wiki --version(如果未安装 CLI,则使用纯 obsidian-wiki)。OKF §5 要求每个时间戳都是带有明确偏移量的日期时间,因此日期格式的 updated: 2026-04-12 会转换为 2026-04-12T00:00:00Z。这取代了 v0.1 的 timestamp,后者不再写入。 |
sources | sources(字符串列表) | sources(字符串列表) | OKF 的 sources 是对象列表,每个对象都必须包含 resource,因此每个原生字符串都会转换为 - resource: <string>。conversation:2026-04-12 这类不透明字符串有效:§5.1 允许使用方无法跟踪的范围描述符。每个条目不输出其他内容,以便导入时还原出完全一致的字符串。 |
status | lifecycle | —(原生 lifecycle 会保留) | draft → draft;reviewed、verified、disputed → stable;archived → deprecated。如果没有 lifecycle,则省略 status(在 OKF 中,缺省表示 stable)。 |
verified | _meta/trust-ledger.json 中对应页面的条目 | —(原样保留) | 仅当页面有账本条目时写入:verified: { by: human:vault-owner, at: <entry reviewed_at> }。trust-record 只会写入经人工使用 --approved 批准的条目,因此 human: 操作者准确无误,并赋予页面 OKF 的 人工审核 级别(§5.3)。绝不能仅根据 lifecycle 推导 verified。 |
resource | 首个 sources: 条目,当且仅当它是 http(s):// URL | — | 可选;没有来源 URL 时省略。大多数页面描述的是抽象知识,并无来源 URL。 |
| (扩展字段) | category、created、updated、relationships、lifecycle、lifecycle_changed、tier、base_confidence、… | 原样保留 | OKF §4.1 要求使用方保留未知键。将我们的原生键写作 OKF 扩展 frontmatter,才能实现无损往返——导入时,优先使用保留的 category/created/updated/lifecycle,而不是从 type/generated/status 重新推导。(由于 generated.at 必须是完整的日期时间,updated 会一并保留;否则仅包含日期的 updated 导入后会变成 UTC 午夜。)sources 不再是扩展字段:它是规范字段(见上方行)。 |
步骤
复用步骤 1 中的节点列表(包括已应用的所有活动项目/可见性筛选条件)。在 wiki-export/okf/ 下写入目录树:
-
每个范围内的页面对应一个文件。 对每个页面解析其 frontmatter,根据上表构建 OKF frontmatter(依次为必填的
type、title、description、tags、generated、status、verified、sources、可选的resource,然后是保留的扩展键),转换正文链接(见下文),并写入wiki-export/okf/<category>/<slug>.md——使用页面在知识库中的相同相对路径。 -
正文链接转换(
[[wikilinks]]→ 标准 Markdown 链接):[[concepts/transformers]]→[<target title>](<file-relative path>.md),例如,从entities/foo.md链接到concepts/transformers,会变为[Transformer Architecture](../concepts/transformers.md)。链接文本 = 目标页面的title(如果未知,则使用目标 id)。[[target|display]]→[display](<rel path>.md)。- 使用相对于文件的路径(
../concepts/x.md),绝不使用以/开头的绝对路径——以/开头的链接会导致 GitHub 渲染失败。(这与 knowledge-catalog 自身的生产 Agent 一致。) - 根据目标文件路径计算相对路径,而不是根据页面 id:将 wikilink 目标规范化为页面 id,追加
.md,然后计算relpath(<target-file>, <source-file-dir>)。调用relpath()前,绝不要忘记先追加.md。 - 对于常见的“文件夹笔记”布局,此步骤必不可少:页面 id 既是文件,也是目录前缀,例如
projects/social-twitter.md和projects/social-twitter/...。从projects/social-twitter/concepts/mem0-memory-analysis.md链接到[[projects/social-twitter]]时,必须导出为../../social-twitter.md,而不是...md。 - 使用与步骤 1 相同的规范化方式解析链接目标(转为小写,将空格替换为连字符,去掉
.md)。根据形式处理未解析的目标,以保留前向引用,确保往返转换不丢失内容:- 解析为范围内的页面 → 转换为指向该页面的相对 Markdown 链接。
- 路径形式的目标(包含
/,例如[[concepts/attention-mechanism]])尚无对应页面,且未被活动筛选器排除 → 仍然生成相对 Markdown 链接。OKF §11 禁止因损坏的交叉链接而拒绝整个数据包,保留链接可在重新导入时无损保留用户的前向引用。(已在 st3ve 上验证:静默丢弃这些链接会删除真实的[[wikilinks]]。) - 被活动项目/可见性筛选器排除 → 转为纯文本。不要生成指向已筛除内容的路径。
- 裸标题目标没有匹配项(例如范围内不存在对应页面时的
[[tractorex]])→ 转为纯文本;没有可靠的路径可供生成。
- 保留现有的外部
http(s)://链接和# Citations部分,不作更改。
-
生成
index.md文件(OKF §8 渐进式披露;这些文件不包含每个条目的 frontmatter):- 数据包根目录
wiki-export/okf/index.md— 包含一个# Subdirectories部分,列出每个类别文件夹:* [<category>](<category>/index.md) - <one-line description of the category>。这是唯一允许包含 frontmatter 的索引文件:添加一个键okf_version: "0.2"(OKF §8、§12)。 - 每个类别文件夹中各有一个
index.md,列出该类别的页面:* [<title>](<slug>.md) - <description from the page's summary>。
- 数据包根目录
-
从知识库根目录的
log.md生成log.md,并按 OKF §9 调整格式。该规范要求使用## YYYY-MM-DD日期标题,日期从新到旧排列。我们的日志是扁平的、按时间从早到晚排列的- [<timestamp>] VERB key=value …行,因此:以# Wiki Update Log开头;按<timestamp>的日期部分对行分组;日期组按从新到旧排列;每行格式为* **<Verb>**: <rest of line>,并将动词转换为标题大小写(INGEST→Ingest)。不符合此模式的行归入上一行所属日期,并在*后原样保留。wiki-import会忽略log.md,因此这不会影响往返转换。 -
**筛选器。**遵循与图谱导出相同的项目/可见性筛选器——从数据包中省略已筛除的页面,并按步骤 2 将指向这些页面的入站链接转为纯文本。
从数据包中排除(与图谱导出相同):_archives/、_raw/、_readouts/、.obsidian/、_insights.md、_meta/*.base,以及知识库根目录的 index.md(将在上文重新生成)。
步骤 4:打印摘要
Wiki export complete → wiki-export/
graph.json — N nodes, M edges (NetworkX node_link format)
graph.graphml — N nodes, M edges (Gephi / yEd / Cytoscape)
cypher.txt — N MERGE nodes + M MERGE relationships (Neo4j)
postgres.sql — N upsert rows (wiki_pages) + M upsert rows (wiki_edges) (any Postgres)
graph.html — interactive browser visualization (open in any browser)
仅当已生成 OKF 数据包(步骤 3.5)时,追加此行:
okf/ — OKF v0.2 markdown bundle (N pages, lossless; import via wiki-import)
启用筛选器时,追加筛选说明:
(filtered: project:prismor — 19 of 67 pages)
(filtered: X of Y pages excluded — visibility/internal, visibility/pii)
仅包含实际应用的筛选器对应的行。
备注
- 可安全地重复运行 — 每次运行都会覆盖所有输出文件(以及
okf/文件包) - 跳过失效的 wikilink — 仅导出指向库中现有页面的边;在 OKF 文件包中,指向缺失或已筛除页面的 wikilink 会降级为纯文本
- OKF 是无损格式 — 导入时,
graph.json仅重建存根,而okf/文件包会保留完整的页面正文。库与库之间的传输以及外部 Markdown 工具(MkDocs、Notion、GitHub)请使用 OKF;分析工具(Gephi、Neo4j)请使用图文件 - 如果库受版本控制,应将
wiki-export/目录加入 gitignore — 这些是派生文件 graph.json是主要格式 — 其他格式均由它派生。如果未来有工具原生支持图查询,请将其指向graph.json