mcpField
W
wiki-export

wiki-export

github.com/ar9avデータ・データベース3.6k3.2k 回インストール1 日前に更新
リポジトリ

wiki-export について

wiki-exportは、Obsidianのwikilinkグラフをグラフツール、データベース、ブラウザー可視化向けに出力するスキルです。プロジェクトや可視性による絞り込みに対応し、Markdownバンドルも任意で出力できます。

主な機能

  1. 01JSON、GraphML、Cypher、SQL、HTMLでグラフを出力
  2. 02wikilinkと型付き関係を抽出
  3. 03タグに基づいてページをコミュニティ化
  4. 04プロジェクトまたは可視性で絞り込み
  5. 05任意でOKF Markdownバンドルを出力

活用シーン

  1. 01ページとリンクをNeo4jまたはPostgresに読み込む
  2. 02Gephiやブラウザーでナレッジグラフを探索
  3. 03指定プロジェクトや公開対象ページを出力
  4. 04再利用向けのMarkdownバンドルを作成

インストール

コマンドをコピーしてターミナルで実行します。

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

インストール後、Claude Code で/wiki-exportを入力して読み込まれたか確認します。

詳細

Wiki エクスポート — ナレッジグラフのエクスポート

Wiki のウィキリンクグラフを構造化形式でエクスポートし、外部ツール(Gephi、Neo4j、カスタムスクリプト、ブラウザーでの可視化)で利用できるようにします。

開始する前に

  1. 設定を解決する — llm-wiki/SKILL.md の設定解決プロトコルに従います(インラインの @name による上書き → CWD から .env を上方向にたどる → グローバル設定 → セットアップを促す)。これにより OBSIDIAN_VAULT_PATH が取得できます
  2. エクスポート対象のページが Vault にあることを確認します — ページが 5 件未満の場合はユーザーに警告し、停止します

プロジェクトフィルター(任意)

ユーザーの呼び出しにプロジェクト名が含まれている場合(例:/wiki-export prismor、"export the prismor project"、"export project:security")、プロジェクトフィルターモードを有効にします。

  1. 引数またはフレーズからプロジェクト名を抽出します。正規化:小文字にし、「project」という単語を削除します。
  2. 次のいずれかの条件を満たすページだけを残します。
    • ページの id が projects/<name>/ で始まる(パスベースの照合)
    • ページの tags 配列に <name> が含まれる(タグベースの照合)
  3. いずれかの端点が除外されたエッジをすべて削除します。
  4. 概要にフィルターを記載します:(filtered: project:<name> — X of Y pages)
  5. JSON 出力で graph.graph.filter = "project:<name>" を設定します。

プロジェクトフィルターと可視性フィルターの両方が有効な場合は、両方を適用します(最初にプロジェクトフィルターを適用し、残ったページに可視性フィルターを適用します)。

可視性フィルター(任意)

デフォルトでは、可視性タグに関係なくすべてのページがエクスポートされます。これにより、従来の動作が維持されます。

ユーザーが絞り込んだエクスポートをリクエストした場合(「public export」、「user-facing export」、「exclude internal」、「no internal pages」などのフレーズ)、可視性フィルターモードを有効にします。

  • ブロック対象タグセットを作成します:{visibility/internal, visibility/pii}
  • ノードリストの作成時に、フロントマターのタグにブロック対象タグが含まれるページを除外します
  • いずれかの端点が除外されたエッジをすべて除外します
  • 概要にフィルターを記載します:(filtered: visibility/internal, visibility/pii excluded)

visibility/ タグがないページ、または visibility/public が付与されたページは、常に含めます。

ステップ 1:ノードリストとエッジリストを作成する

Vault 内のすべての .md ファイルを Glob で検索します(_archives/、_raw/、_readouts/、.obsidian/、index.md、log.md、_insights.md は除外)。すべてのファイルリストを収集した後で、有効なフィルター(プロジェクトおよび/または表示設定)を適用します。

各ページのフロントマターから、次の情報を抽出します。

  • id — .md 拡張子を除いた、Vault ルートからの相対パス(例:concepts/transformers)
  • label — フロントマターの title フィールド。存在しない場合はファイル名
  • category — ディレクトリのプレフィックス(concepts、entities、skills、references、synthesis、projects、または journal)
  • tags — フロントマターの tags フィールドの配列
  • summary — 存在する場合はフロントマターの summary フィールド

これがノードリストです。

各ページの本文から \[\[.*?\]\] を Grep し、すべてのウィキリンクを抽出します。

  • 各 [[target]] または [[target|display]] を解析し、target 部分のみを使用します
  • target をノード ID に解決します(正規化:小文字に変換、スペースをハイフンに置換、.md を削除)
  • ノードリスト外を指すリンク(リンク切れ)はスキップします
  • 解決された各リンクをエッジとして追加します:{source: page_id, target: linked_id, relation: "wikilink", confidence: "EXTRACTED"}
  • リンクを含む文が ^[inferred] または ^[ambiguous] で終わる場合は、confidence をそれに応じて上書きします

型付きエッジの補完: ウィキリンクのエッジリストを作成した後、各ページのフロントマターにある relationships: ブロックを読み取ります。各 {target, type} エントリについて:

  • target の YAML 値は、"[[concepts/lstm]]" のような引用符付きのウィキリンク文字列です。前後の [[ と ]] を削除し、同じ正規化(小文字に変換、スペースをハイフンに置換、.md を削除)を行ってノード ID を取得します。
  • 解決された target がノードリストにないエントリ(リンク切れ)はスキップします
  • この (source, target) のペアのエッジがすでに存在する場合は、relation フィールドを型付きの値(例:"contradicts")で上書きし、typed: true を設定します
  • このペアのエッジがまだない場合は、次のエッジを追加します:{source: page_id, target: target_id, relation: <type>, confidence: "EXTRACTED", typed: true}

つまり、relation: "wikilink" は通常の型なしリンクのデフォルト値であり、relationships: エントリによって名前付きの意味的な型に昇格します。本文のウィキリンクと relationships: エントリの両方に由来するエッジは、単一のレコードを維持し、型付きの値が優先されます。

これがエッジリストです。

ステップ 2:コミュニティ ID を割り当てる

タグのクラスタリングによってページをコミュニティにグループ化します。

  • 同じ優先タグを持つページは、同じコミュニティに属します
  • 優先タグ = ページのフロントマターの tags 配列にある最初のタグ
  • タグがないページのコミュニティ ID は null です
  • コミュニティにはサイズの大きい順に 0 から番号を付けます(最大のコミュニティ = 0)

これにより、HTML ビジュアライゼーションや Gephi などのツールでコミュニティに基づく色分けが可能になります。

ステップ 3:出力ファイルを書き込む

Vault ルートに wiki-export/ が存在しない場合は作成します。次の 5 つのファイルをすべて書き込みます。


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> を 1 つ、リンクごとに <edge> を 1 つ書き込みます。型付きエッジ(エッジリストで typed: true のもの)では、意味的な型の値を持つ <data key="relation"> と、同じ値を持つ <data key="type"> の両方を出力します。これにより、すでに relation を使用しているツールで読み取り可能な状態を保ちながら、型を認識するツールが専用の type キーでフィルターできるようになります。型なしのウィキリンクでは、<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 ステートメントを 1 つ書き込み、その後、エッジごとにリレーションシップの MATCH/MERGE ステートメントを 1 つ書き込みます。型付きエッジでは、type の値を大文字にしたものを Cypher のリレーションシップラベルとして使用します(例:contradicts → [:CONTRADICTS]、derived_from → [:DERIVED_FROM])。型なしのウィキリンクには常に [:WIKILINK] を使用します。


3d. postgres.sql

プレーン SQL — psql -f postgres.sql またはマイグレーションランナーを使って、任意の Postgres データベース(ローカル、Supabase、RDS、Neon など)にロードできます。2 つのテーブル wiki_pages(ノード)と wiki_edges(リンク)があり、ON CONFLICT によるアップサートを備えているため、エクスポートの再実行は安全かつ冪等であり、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;

ページごとに 1 つの INSERT ... ON CONFLICT (id) DO UPDATE ステートメントを記述し(値はエスケープ済み: シングルクォートは二重化、tags は jsonb にキャストされた JSON 配列リテラルとしてシリアライズ)、次にエッジごとに 1 つの INSERT ... ON CONFLICT (source, target, relation) DO UPDATE を記述します。ここでは relation は小文字のままです(大文字化される Cypher のリレーションシップラベルとは異なります)。これはスキーマ識別子ではなく単なるカラム値であるためで、これにより WHERE relation = 'contradicts' で直接フィルタリングしたり、大文字小文字を変換せずに結合したりできます。typed は relationships: frontmatter エントリによって昇格されたエッジに対してのみ 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 の CDN を使用した自己完結型のインタラクティブな可視化です(ローカル依存関係はありません)。ユーザーはこのファイルを任意の環境で開くことができます — サーバーは不要です。

HTML ファイルをビルドする手順:

  1. vis.js 用のノードオブジェクトの JSON 配列を生成します:
{id: "concepts/transformers", label: "Transformer Architecture", color: {background: "#4E79A7"}, size: <degree * 3 + 8>, title: "concepts | #ml #architecture", community: 0}
  • コミュニティごとに色分けします(順番に使用: #4E79A7、#F28E2B、#E15759、#76B7B2、#59A14F、#EDC948、#B07AA1、#FF9DA7、#9C755F、#BAB0AC)
  • 次数(入リンク数 + 出リンク数)でサイズを決定します: size = degree * 3 + 8、上限は 60
  • title = ホバー時に表示されるツールチップテキスト: カテゴリ、タグ、サマリー(利用可能な場合)
  1. 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 グレー色を維持し、ラベルはありません。

  1. 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 配列に置き換えます。


Step 3.5: OKF バンドルエクスポート(オプション)

このステップは、ユーザーが OKF / markdown バンドルを要求した場合(「export to OKF」「OKF bundle」「open knowledge format」「export as markdown bundle」といったフレーズ)にのみ実行します。これは追加的なものであり — 上記の 5 つのグラフファイルは常に生成されます。このステップでは、完全な忠実度を持つ追加の markdown バンドルを書き出します。

5 つのグラフファイルは非可逆な射影です(グラフの骨組みのみ)。OKF バンドルは実際のページ本文であるため、OKF を経由したエクスポート→wiki-import のラウンドトリップで完全なコンテンツが保持され、バンドルは MkDocs、Notion、Hugo、GitHub のレンダラー、または任意の Open Knowledge Format コンシューマーにそのまま投入できます。

canonical frontmatter のマッピング(obsidian-wiki ⇄ OKF)

このテーブルがマッピングの唯一の信頼できる情報源です。wiki-import は逆方向のマッピングでこのテーブルを参照します。

OKF key← export from→ import toNotes
type (required)category、タイトルケースcategory(小文字)concepts→Concept、entities→Entity、skills→Skill、references→Reference、synthesis→Synthesis、projects→Project、journal→Journal。OKF では type が必須です。コンシューマーは任意の文字列を許容します。
titletitletitleそのまま。
descriptionsummarysummary1 行の summary: は、OKF の description と完全に同じです(index.md のエントリーで使用)。
tagstagstagsリストをそのまま使用。visibility/* システムタグは変更せずに引き継ぎます。
generatedupdated → generated.at;by は生成元updated ← generated.atgenerated: { by: obsidian-wiki/<version>, at: <datetime> } として書き込みます。<version> には obsidian-wiki --version の結果を使用します(CLI がインストールされていない場合はプレーンな obsidian-wiki)。OKF §5 では、すべてのタイムスタンプを明示的なオフセット付きの datetime にする必要があります。そのため、日付のみの updated: 2026-04-12 は 2026-04-12T00:00:00Z になります。書き込まれなくなった v0.1 の timestamp に代わるものです。
sourcessources(文字列のリスト)sources(文字列のリスト)OKF の sources は resource が必須のオブジェクトのリストです。そのため、ネイティブ形式の各文字列は - resource: <string> になります。conversation:2026-04-12 のような不透明な文字列も有効です。§5.1 では、コンシューマーがたどれないスコープ記述子も許可されています。インポート時に文字列を完全に復元できるよう、各エントリーにはこれ以外何も出力しません。
statuslifecycle—(ネイティブの 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 はありません。
(extensions)category、created、updated、relationships、lifecycle、lifecycle_changed、tier、base_confidence、…そのまま保持OKF §4.1 では、コンシューマーが未知のキーを保持する必要があります。ネイティブキーを OKF 拡張 frontmatter として書き込むことで、往復変換時の情報損失を防げます。インポート時には、保持された category/created/updated/lifecycle が、type/generated/status から再導出する値より優先されます。(generated.at は完全な datetime でなければならないため、日付のみの updated はそのまま戻すと UTC の午前 0 時になってしまいます。そのため updated も引き継ぎます。)sources は、もはや拡張ではなく、仕様のフィールドです(上の行を参照)。
手順

Step 1 のノードリストを再利用します(適用済みのアクティブなプロジェクト/可視性フィルターがあれば、そのまま適用します)。wiki-export/okf/ 以下にディレクトリツリーを書き込みます。

  1. スコープ内のページごとに 1 ファイル。 各ページの frontmatter を解析し、上記のマッピングテーブルを適用して OKF frontmatter を作成します(必須の type を先頭に置き、続いて title、description、tags、generated、status、verified、sources、任意の resource、最後に保持する拡張キーを並べます)。本文のリンクを変換し(以下を参照)、wiki-export/okf/<category>/<slug>.md に書き込みます。これは、ボルト内でページが存在する相対パスと同じです。

  2. 本文リンクの変換([[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 の本番エージェントと同じ動作です。)
    • 相対パスは、ページ ID ではなくリンク先ファイルのパスから計算します。wikilink のリンク先をページ ID に正規化し、.md を追加してから、relpath(<target-file>, <source-file-dir>) を計算します。.md を追加する前に ID に対して relpath() を呼び出してはいけません。
    • これは、ページ ID と同名のファイルとディレクトリプレフィックスが併存する一般的な「フォルダーノート」レイアウトで必要です。たとえば、projects/social-twitter.md と projects/social-twitter/... がある場合、projects/social-twitter/concepts/mem0-memory-analysis.md から [[projects/social-twitter]] へのリンクは、...md ではなく ../../social-twitter.md にエクスポートする必要があります。
    • Step 1 と同じ正規化(小文字化、スペースをハイフンに置換、.md の削除)を使ってリンク先を解決します。未解決のリンク先は形式に応じて処理し、前方参照が往復変換後も保持されるようにします。
      • 対象範囲内のページに解決される場合 → 相対 Markdown リンクにします。
      • パス形式のリンク先([[concepts/attention-mechanism]] のように / を含む)に対応するページがまだなく、有効なフィルターで除外されていない場合 → 相対 Markdown リンクを出力します。OKF §11 では、壊れたクロスリンクを理由にバンドルを拒否することは禁止されています。また、リンクを残すことで、再インポート時にユーザーの前方参照を損失なく維持できます。(st3ve で検証済み:これらを黙って削除すると、実際の [[wikilinks]] が失われました。)
      • 有効なプロジェクト/可視性フィルターで除外されている場合 → プレーンテキストにします。フィルターで除外されたコンテンツを指すパスを出力してはいけません。
      • 一致するページがないタイトルのみのリンク先(例:対象範囲に該当ページがない場合の [[tractorex]]) → プレーンテキストにします。出力すべき信頼できるパスがありません。
    • 既存の外部 http(s):// リンクと # Citations セクションは変更しません。
  3. index.md ファイルの生成(OKF §8 の段階的な情報開示。各項目のフロントマターは含めません):

    • バンドルルートの wiki-export/okf/index.md — 各カテゴリフォルダーを列挙する # Subdirectories セクションを作成します。形式は * [<category>](<category>/index.md) - <one-line description of the category> です。フロントマターが許可されるのはこのインデックスのみです。キーを okf_version: "0.2" ひとつだけ追加します(OKF §8、§12)。
    • カテゴリフォルダーごとに index.md を作成し、そのページを列挙します。形式は * [<title>](<slug>.md) - <description from the page's summary> です。
  4. log.md の作成:保管庫ルートの log.md を OKF §9 に準拠する形式に整えます。日付見出しは ## YYYY-MM-DD とし、新しい日付から順に並べます。ログは古い順のフラットなリストで、行は - [<timestamp>] VERB key=value … 形式です。次のように処理します。まず # Wiki Update Log を記述します。行を <timestamp> の日付部分ごとにまとめ、新しい日付から順に ## <date> の下に出力します。各行は、動詞をタイトルケース(INGEST → Ingest)にして * **<Verb>**: <rest of line> として出力します。この形式に一致しない行は、直前の行の日付に含め、内容をそのまま * の後ろに記述します。wiki-import は log.md を無視するため、往復変換には影響しません。

  5. フィルター。グラフのエクスポートと同じプロジェクト/可視性フィルターを適用します。フィルターされたページはバンドルから除外し、そこへのインバウンドリンクは Step 2 に従ってプレーンテキストにします。

バンドルから除外するもの(グラフのエクスポートと同じ):_archives/、_raw/、_readouts/、.obsidian/、_insights.md、_meta/*.base、および保管庫ルートの index.md(上記で再生成します)。


Step 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 バンドルが生成された場合(Step 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/ バンドル)が上書きされます
  • リンク切れのウィキリンクはスキップされます — エクスポートされるのは、Vault 内に存在するページへのエッジのみです。OKF バンドルでは、存在しないページまたはフィルターで除外されたページへのウィキリンクは、通常のテキストに変換されます
  • 損失のない形式は OKF です — インポート時に graph.json から復元されるのはスタブのみですが、okf/ バンドルにはページ本文全体が保持されます。Vault 間の転送や外部の Markdown ツール(MkDocs、Notion、GitHub)には OKF を使用し、分析ツール(Gephi、Neo4j)にはグラフファイルを使用してください
  • Vault がバージョン管理されている場合、wiki-export/ ディレクトリは gitignore の対象にしてください — これらは派生成果物です
  • 主となる形式は graph.json です — ほかの形式はすべて、これを基に生成されます。将来、グラフクエリをネイティブにサポートするツールが登場した場合は、graph.json を指定してください

コメント