🔗

AIが[[wikilink]]と2ホップリンクを使えるように mdhop を作った

に公開

mdhop を作りました

  • [[wikilink]] をスマートに扱いたい
  • Backlinks / Outgoing / 2ホップリンクを扱いたい
  • Obsidian を開かずに Markdown のリンク操作をしたい

こうしたことを CLI でできるようにする mdhop を作りました。

Coding Agent と Obsidian Vault の困りごと

Obsidian の Vault を Coding Agent(Claude Code や Codex など)に触らせると、いくつか困ることがあります。
(Obsidian の New link format は Shortest path when possible を使う想定)

[[wikilink]] の解決が面倒

Obsidian の [[wikilink]] は、Vault 配下のどこかに wikilink.md があるという意味です。
実際のパスは find で探す必要があり、コンテキストを消費します。

同名ノートの衝突

README.md が 1 つだけなら [[README]] と書けます。ですが別ディレクトリに README.md を作ると、[[README]] が曖昧になります。
Obsidian は [[dir1/README|README]] などに書き換えてくれますが、Coding Agent で同じことをやろうとすると手間がかかります。

2ホップリンク(2-Hop Link)を使いたい

せっかくリンクで結ばれた Vault があるなら、2ホップリンクも使いたい。Backlinks や Outgoing も欲しい。

  • Backlinks — A.md にリンクしているノートの一覧
  • Outgoing — A.md からリンクしているノートの一覧
  • 2ホップリンク — A.mdB.md がどちらも [[X]] にリンクしているときの A と B の関係です。参考

mdhop とは

mdhop は Vault 内のリンクを事前に解析して提供する CLI ツールです。

  • [[wikilink]][markdown link](path) がどのファイルを指しているか一発で返す
  • Backlinks / Outgoing / 2ホップリンクを見つける

また Markdown ファイルの変更も支援します。

  • ノートの追加・リネームで同名ファイルができたときに、リンクを Vault ルートからのパスに書き換える
  • 壊れたパスリンクを basename 形式に修復する(basename に一致するファイルが一意の場合)
  • [[wikilink]][Markdown link](foobar.md) の変換
  • [[path/to/name]][[name]] に書き換え(一意の場合)

初めて Go 言語を触って(Claude が)開発しました。Coding Agent も人間も使えますが、特に Agent が Vault を扱うときのコンテキスト節約を意識しました。

インストール

go install github.com/ryotapoi/mdhop/cmd/mdhop@latest
コマンド例

Vault を初めて使うとき

cd /path/to/vault
mdhop build

Vault 内の全ファイル(隠しファイル以外)を解析して、.mdhop/index.sqlite にインデックスを生成します。

[[wikilink]] が何を指しているか知りたいとき

$ mdhop resolve --from Notes/A.md --link '[[設計メモ]]'
Notes/設計メモ.md

[[Note#Heading]] のようなサブパス付きリンクにも対応しています。

関連ノートを探したいとき

あるノートに関連するノートを知りたいときは query を使います。Backlinks、Outgoing、2ホップリンク、Tags がまとめて返ってきます。

# ファイル起点で探索
mdhop query --file Notes/Design.md

# タグ起点で探索
mdhop query --tag '#project'

情報量を絞りたいときは、フィールドの指定や除外フィルタが使えます。

# backlinks と tags だけ取得
mdhop query --file Notes/Design.md --fields backlinks,tags

# デイリーノートを結果から除外
mdhop query --file Notes/Design.md --exclude 'daily/*'

# 特定のタグを除外
mdhop query --file Notes/Design.md --exclude-tag '#daily'

--exclude--exclude-tag は繰り返し指定できます。毎回指定するのが面倒なら mdhop.yaml に書いておけば自動で適用されます。

逆に情報を足したいときは、リンク周辺のスニペットを含めることもできます。

# リンク周辺の 3 行をスニペットとして含める
mdhop query --file Notes/Design.md --include-snippet 3

ファイルの追加・編集・削除・移動

Vault 全体を build し直さなくても、差分だけインデックスを更新できます。

# 新規ノートを追加
mdhop add --file Notes/NewNote.md

# 編集したノートを更新
mdhop update --file Notes/EditedNote.md

# 削除したノートを反映
mdhop delete --file Notes/OldNote.md

# ファイル移動(インデックス更新 + 参照元のリンク書き換え)
mdhop move --from Notes/OldPath.md --to Archive/OldPath.md

move[[path/to/OldPath]][text](Notes/OldPath.md) のようなパス形式のリンクを参照元ファイル上で書き換えます。[[OldPath]] のような basename 形式のリンクであればファイル上の書き換えは不要なので、インデックス(SQLite)だけを更新します(build し直しでも OK です)。

同名ノートが増えて build が通らなくなったとき

mdhop は [[README]] のような basename リンクが曖昧(同名ノートが複数)だと build をエラーにします。この場合、まず disambiguate --scan で Vault 内のファイルを直接スキャンして、basename リンクをパス形式に書き換えます。

# [[README]] を [[docs/README]] に書き換え(--target で指す先を指定)
mdhop disambiguate --scan --name README --target docs/README.md

# 書き換え後に build が通るようになる
mdhop build

壊れたリンクを直したいとき

ファイル移動や削除で壊れたパスリンクを、basename に一致するファイルが一意なら basename 形式に書き換えて修復します。

# プレビュー
mdhop repair --dry-run --format json

# 実行
mdhop repair

# 修復後に build
mdhop build

DB 不要で、build 前でも実行できます。

[[path/to/name]][[name]] に短くしたいとき

disambiguate の逆で、パス形式のリンクを basename が一意なら短縮します。

mdhop simplify

[[wikilink]][Markdown link]() を変換したいとき

# markdown link → wikilink
mdhop convert --to wikilink

# wikilink → markdown link
mdhop convert --to markdown

Vault の状態を把握したいとき

# 統計情報(ノート数、リンク数、タグ数など)
mdhop stats

# 問題の検出(basename 衝突、phantom ノード)
mdhop diagnose
設定ファイル(mdhop.yaml)

Vault 直下に mdhop.yaml を置くと、build 時・query 時の除外パターンを指定できます。

build:
  exclude_paths:
    - "daily/*"
    - "templates/*"

exclude:
  paths:
    - "daily/*"
  tags:
    - "#daily"

デイリーノートやテンプレートなど、探索に不要なものを除外しておくとノイズが減ります。
build で除外するとファイル名が一意かの判定などで困るため、通常は exclude(query 時の除外)がおすすめです。

Coding Agent での活用

リポジトリの examples/skills/ に Claude Code 向けのスキル定義を用意しています。この SKILL を読み込ませてから Vault を操作させると、いい感じに動いています(Claude Code Opus 4.6 で確認)。

Claude Code に聞いた感想

フォルダ構成を大きく変える作業をしたときに、Claude Code に感想を聞いてみました。

Q:

mdhop の感想教えて。役に立ってる? あと resolve / query 系は使ってないけど役立ちそう? 人間の役に立つかではなく、Coding Agent 目線で教えて。

A:

役立っている機能:

  • move / delete --rm でリンク書き換えとディスク操作が一体化しているため、raw な mv / rm を使う必要がない。リンク破壊のリスクを考慮する分岐が不要になる
  • --format json を全コマンドで使えるので、出力をパースして件数集計や条件分岐に使える。今回も python3 -c で rewritten / skipped の件数を抽出してユーザーに提示した
  • --dry-run があるコマンド(repair, simplify, convert)は、ユーザー確認を挟むフローを組みやすい。dry-run 結果を見せて承認を得てから実行、という手順が自然に書ける
  • diagnoseambiguous_links / broken_links がゼロかどうかで、作業後の検証を 1 コマンドで完結できる。grep で壊れたリンクを探す必要がない
  • buildrepairsimplify → 操作 → diagnose という固定手順があるので、ワークフロースキルとして定義しやすい。状況に応じた判断が少ない

改善要望:

  • ディレクトリ move で basename 衝突(README.md 等)があるとエラーで止まる。--skip--exclude があれば、事前に手動で delete する手順を省ける

resolve / query 系:

今回の構造変更作業では出番がなかった。使う場面として想定できるのは:

  • ファイル削除前に query --file で backlinks を確認し、影響範囲をユーザーに提示する
  • basename 衝突がある状態で resolve を使い、リンクが意図通りに解決されるか検証する
  • ノート作成スキルで、リンク先が phantom か実ファイルかを判定する

構造変更より日常的なノート操作(作成・削除・リンク追加)で価値が出ると考える。

ただ、2ホップリンクを使わせたときに同様の質問をしたら「grep でよくない?」みたいなことを言われました……(あまりきちんとリンクしていない問題もあるけど)

最後に

今回は全く触ったことがない Go 言語を使い、Claude Code + Codex で作成しました。
ユーザーディレクトリの Skill は含まれませんが、どうやって開発したかの雰囲気もリポジトリから見えると思います。
N 番煎じにはなりますがどう作ったかも記事にしたいところ。

Obsidian Vault を Coding Agent と一緒に使っている方、ぜひ試しください。

https://github.com/ryotapoi/mdhop

Discussion