vault-link-index
Maintain the vault's link graph — find broken links, orphan notes, hub notes, and backlink gaps, and repair them with link-doctor. Use when the user says "broken links", "orphan notes", "fix links", "
SKILL.mdskills/vault-link-index/SKILL.md
GitHubVault Link Index & Link Doctor
This skill manages the backlink index and provides link health tools for the vault.
Files
- Script:
80_Code/scripts/generate-link-index.py - Index:
99_Attachments/link-index.json
Commands
Smart Index Update
Default behavior - only regenerates if stale (>24h old):
python "80_Code/scripts/generate-link-index.py"
Check Freshness Only
python "80_Code/scripts/generate-link-index.py" --check
Force Regenerate
python "80_Code/scripts/generate-link-index.py" --force
Find Broken Links (Link Doctor)
Show broken wikilinks with fuzzy-match suggestions:
python "80_Code/scripts/generate-link-index.py" --broken
Output example:
📄 20_Areas/Career/@data analyst.md
❌ [[&DataScienceHub 101]] → 💡 [[DataScienceHub 101]] (97% match)
Show Orphan Notes
List notes with no incoming links, grouped by folder:
python "80_Code/scripts/generate-link-index.py" --orphans
Link Doctor Operations
"Link Doctor" is not a separate script — it is generate-link-index.py --broken
(the detector) plus the repair rules below (the judgment). The detector emits each
broken link with fuzzy-match suggestions; you decide which class it falls into.
Repair classes — apply vs ask
Deterministic — apply directly, then report the batch. These have exactly one correct answer and no information is lost:
| Pattern | Cause | Fix |
|---|---|---|
[[&Note]] → [[Note]] |
Prefix typo | Strip the leading & |
[[note]] → [[Note]] |
Case mismatch | Match the existing file's stem case exactly |
[[Old Name]] where a rename map exists |
Renamed note | Rewrite to the new name |
Ambiguous — always ask first. More than one plausible target, or the fix destroys information:
| Situation | Why you must ask |
|---|---|
| Fuzzy match below high confidence, or 2+ candidate targets | Picking wrong silently rewires the graph |
[[2022-01-01]] missing daily note |
Create the note, or remove the link? Only the user knows |
| Broken link inside a quoted block or an archived note | The link may be a deliberate historical record |
| The "target" is a note the user just deleted | Deleting the link may be right — or the deletion was the mistake |
Applying a fix
- Read the source file (never edit blind from the index).
- Replace the broken link —
Editwith the exact old string. - Re-run
--brokento confirm the count dropped and nothing new broke.
# Deterministic class: prefix typo
old = "[[&DataScienceHub 101]]"
new = "[[DataScienceHub 101]]"
Never batch-apply an ambiguous class. Never "fix" a link by deleting it unless the user said so.
Obsidian CLI (Fast Queries) — with Fallbacks
Try CLI first for instant results. If Obsidian is not running, fall back to Python/Grep.
OBS="bash 80_Code/scripts/obs.sh"
# Check if Obsidian is available
$OBS --status
# Backlinks — what links TO this note?
$OBS backlinks file="Note Name"
$OBS backlinks file="Note Name" total # count only
# FALLBACK: Grep for '\[\[Note Name' across *.md files
# Outgoing links — what does this note link TO?
$OBS links file="Note Name"
# FALLBACK: Read the file, extract [[...]] wikilinks
# Orphan notes (no incoming links)
$OBS orphans total
# FALLBACK: python "80_Code/scripts/generate-link-index.py" --orphans
# Dead-end notes (no outgoing links)
$OBS deadends total
# FALLBACK: no equivalent (would need full scan)
# Unresolved (broken) links
$OBS unresolved total
$OBS unresolved verbose # with source files
# FALLBACK: python "80_Code/scripts/generate-link-index.py" --broken
When to use CLI vs Python script:
| Need | Tool | Fallback |
|---|---|---|
| Quick backlink lookup for 1 note | CLI backlinks |
Grep \[\[Note Name |
| Count orphans/broken links | CLI totals | Python --orphans / --broken |
| Before moving/renaming a note | CLI backlinks file=X |
Grep for wikilink |
| Broken links with fuzzy-match suggestions | Python --broken |
(no CLI equivalent) |
| Full JSON index for bulk analysis | Python script | (always use Python) |
| Hub notes (most-referenced) | Python top_linked |
(always use Python) |
Query Operations
Query Backlinks
Try CLI first (instant, live index):
bash "80_Code/scripts/obs.sh" backlinks file="Career-Development-Plan"
Fallback (if Obsidian not running):
- Read
99_Attachments/link-index.json→ look upbacklinks["Note Name"] - Or Grep for
\[\[Career-Development-Planacross vault*.mdfiles
Find Hub Notes
Read the Python index, report top_linked (notes with most backlinks).
Check Note Before Moving
- CLI
backlinks file=X totalfor instant count - If non-zero, show full backlink list
- Proceed only after user confirms
When to Use
| Situation | Action |
|---|---|
| User asks "fix broken links" | Python --broken (has fuzzy suggestions) |
| Before refactoring files | CLI backlinks for quick check |
| Looking for orphaned notes | CLI orphans total, then Python --orphans for grouped list |
| Understanding vault structure | Python index hub notes |
| Moving/renaming a note | CLI backlinks file=X (instant) |
| Weekly maintenance | CLI unresolved total + orphans total for quick health check |
| "What links to X?" | CLI backlinks file=X |
Index Structure
{
"generated": "ISO timestamp",
"stats": {
"total_files": 2379,
"total_links": 8808,
"orphan_count": 935,
"broken_count": 2514
},
"orphans": ["path/to/orphan.md", ...],
"top_linked": [["NoteName", 182], ...],
"backlinks": {"NoteName": ["linking/file.md", ...]},
"broken_links": {"source.md": ["BrokenTarget", ...]},
"file_paths": {"NoteName": "actual/path.md"}
}
Proactive Usage
Claude Code should invoke this skill:
- Before any batch file operations
- When user asks "what links to X?"
- When user asks about vault health/orphans/broken links
- Before archiving projects (check nothing links to them)
- After major file moves (run
--brokento check for issues)