Stats
Actions
Tags
From ai-zettelkasten
Fix broken wiki-links in Zettelkasten notes by replacing them with semantically similar existing notes. Audits link integrity and repairs Related sections with valid references.
How this skill is triggered — by the user, by Claude, or both
Slash command
/ai-zettelkasten:zfix-linksThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Audit and repair broken wiki-links by replacing them with semantically similar existing notes.
Audit and repair broken wiki-links by replacing them with semantically similar existing notes.
/zfix-links # Fix all broken links
/zfix-links --audit # Audit only, don't fix
/zfix-links --path permanent # Fix links in specific directory
from pathlib import Path
vault_path = Path(os.environ.get("OBSIDIAN_VAULT", ""))
permanent_path = vault_path / "knowledge-base" / "permanent"
existing_notes = {
note.stem: note
for note in permanent_path.glob("*.md")
}
print(f"Found {len(existing_notes)} existing notes")
import re
def extract_links(content):
# Match [[link]] or [[link|alias]]
pattern = r'\[\[([^\]|]+)(?:\|[^\]]+)?\]\]'
return re.findall(pattern, content)
all_links = set()
for note in permanent_path.glob("*.md"):
content = note.read_text()
all_links.update(extract_links(content))
broken_links = []
for link in all_links:
# Skip vault paths (playbooks/, hubs/, etc.) - these are cross-folder refs
if "/" in link:
continue
# Check if target exists
if link not in existing_notes:
broken_links.append(link)
print(f"Found {len(broken_links)} broken links")
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🔗 Link Audit Results
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Total unique links: 504
Valid links: 276
Broken links: 228
Broken Link Categories:
• Concept placeholders: 35 (e.g., eslint-configuration)
• Vault paths: 45 (e.g., playbooks/..., hubs/...)
• Typos/variations: 148 (fixable)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
def find_semantic_match(broken_link, existing_notes):
"""Find most similar existing note for a broken link."""
# Convert link to searchable text
broken_text = broken_link.replace("-", " ")
# Get existing note names as text
existing_texts = [name.replace("-", " ") for name in existing_notes.keys()]
# Compute similarity
vectorizer = TfidfVectorizer()
all_texts = [broken_text] + existing_texts
tfidf = vectorizer.fit_transform(all_texts)
similarities = cosine_similarity(tfidf[0:1], tfidf[1:])[0]
# Return best match if similarity > threshold
best_idx = similarities.argmax()
if similarities[best_idx] > 0.3:
return list(existing_notes.keys())[best_idx]
return None
def fix_links_in_file(note_path, replacements):
content = note_path.read_text()
for broken, replacement in replacements.items():
# Replace [[broken]] with [[replacement]]
content = re.sub(
rf'\[\[{re.escape(broken)}(\|[^\]]+)?\]\]',
f'[[{replacement}]]',
content
)
note_path.write_text(content)
Options:
[k]eep as concept placeholder (future note)
[r]emove from Related section
[m]anually specify replacement
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ Link Repair Complete
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Fixed: 182 links across 145 files
Kept as placeholders: 35 concept links
Removed: 11 vault path links
Sample replacements:
[[ai-code-review]] → [[code-review-acceleration]]
[[context-optimization]] → [[context-window-management-pattern]]
[[strict-linters]] → [[linters-as-negotiation-layer]]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
npx claudepluginhub cajias/claude-skills --plugin ai-zettelkastenCreates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.