From para-knowledge-base
Health check for the PARA Knowledge Base. Detects orphan documents, broken links, index drift, tag issues, and stale content. Run periodically or as part of weekly review.
How this skill is triggered — by the user, by Claude, or both
Slash command
/para-knowledge-base:kb-lintThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Audit the vault for structural issues, inconsistencies, and maintenance opportunities.
Audit the vault for structural issues, inconsistencies, and maintenance opportunities.
Token-efficient by design: prefer obsidian-cli calls (single command result) over reading full vault into context. CLI uses Obsidian's actual link resolution — much more accurate than naive grep matching.
/kb-lint # Full report (read-only)
/kb-lint --fix # Auto-fix safe issues
/kb-lint --category Projects # Lint only one category
Compare _index.md contents against actual files on disk.
| Issue | Meaning |
|---|---|
| MISSING | Listed in index but file not found |
| UNLISTED | File exists but not in index |
| STALE_COUNT | Count in top-level index doesn't match |
| STALE_INDEX | updated date > 7 days old |
CLI: obsidian files folder="1. Projects" for actual files; compare against _index.md lines.
Auto-fix: Add unlisted files, remove missing entries, update counts and dates.
Files with no incoming wikilinks.
With CLI (preferred): obsidian orphans — uses Obsidian's actual link graph.
Without CLI: comm -13 <(grep wikilinks sorted) <(find files sorted).
Exclude (false positives):
0. Common/daily/*, 0. Common/weekly/*, _index.md, CLAUDE.md, Dashboard.md0. ... 프로젝트 개요.md — referenced by folder name in indexes, not by full filenametemplates/*, _templates/* — Obsidian Templater templatestags containing project/topic tags (cross-folder reference is intentional)Report: List orphans with suggested action (link, tag, or archive).
| Issue | Rule |
|---|---|
| MISSING_PROJECT_TAG | File in 1. Projects/x/ lacks #proj/x |
| MISSING_TYPE_TAG | Paper file lacks #type/paper |
| BAD_TAG_FORMAT | Spaces or unexpected casing in tag |
| CROSS_REF_TAG | #proj/x tag on file outside Projects — note as intentional cross-reference |
CLI: obsidian tags to list all, obsidian tag name=<tag> to find files for a tag.
Auto-fix: Use obsidian property:set to add missing tags to frontmatter.
With CLI (preferred): obsidian unresolved — Obsidian's resolved link graph reports actual unresolvable wikilinks.
Without CLI: grep -rh -oE '\[\[[^]|#]+' . --include='*.md' | sed 's/^\[\[//' | sort -u then compare against find . -name '*.md' -exec basename {} .md \;.
False positive exclusion (apply to both CLI and grep results):
``` ... ```) ... ) — e.g., when `[[wikilink]]` is shown as documentation<% ... %> content---)./, /, http*:[[1. Projects/foo/]]) — Obsidian resolves these to foldersPre-filter command for grep fallback (mask false positives before extraction):
sed -E '/^---$/,/^---$/d; /^```/,/^```/d; s/`[^`]*`//g; s/<%[^%]*%>//g'
Severity tiers:
Check documents in PARA directories for required fields:
| Field | Required? |
|---|---|
title | Yes |
created | Yes |
tags | Recommended |
type | Recommended |
summary | Recommended — kb-ingest writes this on every new document; older documents fall back to their first paragraph in kb-query |
CLI: obsidian property:read name=title file=<name> to check; bulk via obsidian properties.
Report: Count of documents missing each field, list worst offenders.
| Issue | Condition |
|---|---|
| POSSIBLY_STALE | Project file not modified >30 days |
| SHOULD_ARCHIVE | Completed project still in 1. Projects/ |
| MISPLACED | Active project found in 4. Archive/ |
CLI: obsidian file path=<file> for mtime; find -mtime +30 as fallback.
# KB Lint Report — {date}
## Summary
- Passed: N checks
- Warnings: N items
- Errors: N items
- (CLI used: yes|no)
## Details
### Index Drift
...
### Orphan Documents
...
### Tag Issues
...
### Broken Links (real, after FP filter)
...
### Frontmatter Quality
...
### Stale Content
...
## Recommended Actions
1. {highest priority}
2. ...
| Level | Examples | Auto-fixable |
|---|---|---|
| Error | Broken links, missing files | Some (--fix) |
| Warning | Missing tags, orphans, stale content | Most (--fix) |
| Info | Optimization suggestions | No |
Always prefer CLI. A single obsidian unresolved call returns a few hundred bytes; running grep across hundreds of files into context costs 7K–10K tokens unnecessarily.
When CLI is unavailable:
find ... -size and find ... -mtime for stat-based checks (no read).grep -l first to locate files; only grep -h content from those.sed mask before extracting wikilinks.comm -23 / -13 on sorted unique sets (linear time, no nested loops).Avoid: reading every .md file into model context. The vault has 700+ files — that exceeds budget by an order of magnitude.
Append to 0. Common/log.md:
[date] lint | passed N | warnings N | errors N | fixed N | cli: yes|no
| Argument | Effect |
|---|---|
--fix | Apply safe-category fixes (see Auto-fix Philosophy below) |
--category <name> | Limit to one category: Projects, Areas, Resources, Archive |
--no-cli | Force grep fallback even if obsidian-cli is available (for debugging) |
Obsidian vaults can declare exclusion paths in .obsidian/app.json under userIgnoreFilters (used by Obsidian's search to hide certain folders — common examples: personal journals, templates, draft directories). The path list is vault-specific — no two vaults share the same set, so the plugin reads whatever the user has already configured rather than hardcoding any path.
Whether obsidian-cli orphans applies these filters to its output depends on the installed CLI version — do not assume either way. Verify with a quick smoke test (check whether a file inside a known userIgnoreFilters path is still listed in obsidian orphans) before deciding whether post-filtering is needed. If the CLI does not apply the filters, post-filter to respect the user's intent:
# Read whatever exclusion paths the user has set in Obsidian
USER_IGNORES=$(python3 -c "
import json
try:
d = json.load(open('.obsidian/app.json'))
for p in d.get('userIgnoreFilters', []):
print(p.rstrip('/'))
except: pass
")
# Apply to path-based results (orphan check)
obsidian orphans | grep -vFf <(echo "$USER_IGNORES") > orphans_filtered.txt
obsidian unresolved is target-based, not path-based, so userIgnoreFilters does not apply directly to its output. If a broken target string happens to overlap with an excluded prefix, you may filter as well, but typically unresolved is left raw.
Reporting: The plugin should output both raw orphan count and filtered orphan count (after userIgnoreFilters) so the user sees the effect. Effect varies per vault — typically 20-40% reduction when journals/templates are excluded, but a vault that doesn't use userIgnoreFilters shows zero change.
Why this matters: vault-specific exclusion intent honored without per-vault hardcoding inside the plugin. Each user's vault automatically benefits from whatever filters they've already set in Obsidian's GUI.
This plugin does not bundle vault-specific batch-fix subcommands (e.g. --batch-weekly-wikilink, --fix-moved-projects). Adding one option per pattern explodes the surface and forces every vault to inherit assumptions that may not match.
Instead, the plugin gives Claude:
CLAUDE.md: preserve links, update affected indexes, append one structural log entry)When /kb-lint --fix is invoked, Claude:
obsidian unresolved / orphans (or grep+sed fallback)sed/Edit calls Claude composes from primitives[date] lint --fix | summary | N files | counts before→after line to log.mdWhy this matters: each vault has unique conventions (Templater patterns, project naming, archive structure). Hardcoding a --batch-X for every pattern Claude could detect would freeze the plugin to one vault style. Letting Claude compose fixes from primitives keeps the plugin general while still automating the boring mechanical work.
obsidian unresolved counts distinct broken targets, not instances that reference them. If 300 daily files all reference [[2021-W44]], that's 1 unresolved count but 300 instances. After fixing the 300 instances, count drops by only 1 (sometimes 0 if other files still reference the same target). For meaningful "fix progress" measurement, also report instance count via grep -rln '\[\[<target>\]\]' . | wc -l.
When applying batch fixes, surface both numbers in the dry-run summary — count alone misleads.
npx claudepluginhub ernestolee13/para-knowledge-base --plugin para-knowledge-baseScan agent-maintained directories for health issues: orphan pages, broken wikilinks, stale content, frontmatter violations, tag taxonomy drift, oversized pages. Use this skill whenever the user wants to audit knowledge base quality, check for broken links, find stale or orphan pages, or says anything like "check my wiki", "are there any issues", "audit the knowledge base", "find broken links", or "what needs fixing".
Lints an Obsidian knowledge base for broken links, orphan pages, stale captures, and wikilink violations. Run as a periodic health check.
Audits and interactively cleans up Claude Code knowledge base: detects stale references, duplicates, orphaned files, frontmatter issues, and merge opportunities.