Help us improve
Share bugs, ideas, or general feedback.
From Obsidian Vault
Analyzes Obsidian vault citation and wikilink graphs using networkx. Builds citation networks, detects communities, computes connectivity metrics, and suggests graph improvements.
npx claudepluginhub luffysolution-svg/obsidian-vault-mcpHow this skill is triggered — by the user, by Claude, or both
Slash command
/obsidian-vault:obsidian-graphThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Use `obsidian_search` and `obsidian_read_file` to gather vault data, then run Python/networkx analysis via `Bash`.
Analyzes a vault's wikilink structure as a directed graph, computing PageRank, betweenness, orphan detection, cluster analysis, and missing-link discovery. Use for vault health and connection insights.
Builds Wikipedia-style Obsidian vaults from academic PDFs, extracting concepts into linked notes with atomic sentences and citations. Expands existing networks with new papers.
Explores knowledge vault on a topic using semantic search, graph neighborhood traversal, and gap analysis to map notes, connections, and missing knowledge before research or note creation.
Share bugs, ideas, or general feedback.
Use obsidian_search and obsidian_read_file to gather vault data, then run Python/networkx analysis via Bash.
Steps:
obsidian_search with query="", extensions=".md" — get all notes.obsidian_read_file and extract:
\[\[([^\]|#]+) on the file body.citekey field.Bash:import json, re, pathlib, sys
vault = sys.argv[1]
notes = {}
for md in pathlib.Path(vault).rglob("*.md"):
rel = str(md.relative_to(vault))
text = md.read_text(encoding="utf-8", errors="ignore")
links = re.findall(r'\[\[([^\]|#\n]+)', text)
notes[rel] = links
edges = []
for src, targets in notes.items():
for tgt in targets:
edges.append({"source": src, "target": tgt + ".md"})
print(json.dumps({"nodeCount": len(notes), "edgeCount": len(edges), "edges": edges[:100]}, indent=2))
Run: python graph_build.py "<vault_path>"
import networkx as nx, json, sys
data = json.loads(sys.argv[1])
G = nx.DiGraph()
for e in data["edges"]:
G.add_edge(e["source"], e["target"])
U = G.to_undirected()
communities = list(nx.community.greedy_modularity_communities(U))
result = [{"community": i, "nodes": list(c)[:10], "size": len(c)}
for i, c in enumerate(communities)]
print(json.dumps(result, indent=2))
From the directed graph, compute:
import networkx as nx
hubs = sorted(G.in_degree(), key=lambda x: x[1], reverse=True)[:10]
orphans = [n for n in G.nodes if G.in_degree(n) == 0 and G.out_degree(n) == 0]
components = list(nx.weakly_connected_components(G))
After computing metrics, suggest:
obsidian_write_file to create a stub.[text](file.md) patterns → suggest converting to [[file]].通过 obsidian_search + obsidian_read_file 收集 vault 数据,用 Bash 运行 Python/networkx 脚本完成引用网络构建、社区检测和连通性分析。分析结果转化为可操作的 obsidian_write_file 建议。
Run after building the graph (see "Citation Network" above). Identifies notes and clusters that are under-connected.
import networkx as nx
# 1. Isolated nodes — no edges at all
gaps = [n for n in G.nodes if G.degree(n) == 0]
print(f"Isolated notes ({len(gaps)}):", gaps[:20])
# 2. Sparse communities — single-node or two-node clusters after community detection
sparse = [list(c) for c in communities if len(c) <= 2]
print(f"Sparse communities ({len(sparse)}):", sparse)
# 3. Bridge nodes — their removal would disconnect the graph
bridges = list(nx.bridges(G.to_undirected()))
bridge_nodes = {u for u, v in bridges} | {v for u, v in bridges}
print(f"Bridge nodes ({len(bridge_nodes)}):", list(bridge_nodes)[:10])
Report to the user:
obsidian_search by tag overlap to find candidate notes to link, or ask whether to delete.obsidian_write_file.Run after community detection. Finds high-value cross-community edges that may represent unexpected interdisciplinary links.
# Map each node to its community index
node_to_community = {}
for i, c in enumerate(communities):
for n in c:
node_to_community[n] = i
# Cross-community edges
cross_edges = [
(u, v) for u, v in G.edges()
if node_to_community.get(u) != node_to_community.get(v)
]
# Rank by combined degree — high-degree cross-community links are most surprising
cross_edges.sort(
key=lambda e: G.degree(e[0]) + G.degree(e[1]),
reverse=True
)
print("Top surprising connections:")
for u, v in cross_edges[:10]:
print(f" [{node_to_community[u]}] {u} → [{node_to_community[v]}] {v}")
Report to the user: For each of the top 10 cross-community connections, describe which two thematic clusters they bridge (using community member note names as cluster labels). Suggest: