Install
$ agentstack add skill-jojoprison-mnemo-vault-health ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ● Filesystem access Used
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
mnemo:health — Vault Health Check & Analytics
Run a comprehensive health check on the Obsidian vault: orphans, broken links, missing sections, stale notes, and growth statistics.
Prerequisites & config
Obsidian must be open; obsidian CLI on PATH. Config at ~/.mnemo/config.json (required fields: vault, taxonomy, links_section) — schema in ${CLAUDE_PLUGIN_ROOT}/references/config-schema.md.
Workflow
Steps 1-4 run in parallel — single assistant message batching the independent CLI reads (orphans, unresolved, tag counts; Step 4 reuses Step 3's tag output, no extra call). These are independent queries against the same indexed vault, ~180ms total vs ~720ms sequential.
Step 0: claude-mem Sanity Check (optional, ~20ms)
Surface two common gotchas if claude-mem plugin is installed and enabled. If cascade.claude_mem.enabled is false, skip this section silently — many users intentionally disable claude-mem for CPU/RAM reasons.
bash "${CLAUDE_PLUGIN_ROOT}/scripts/check-cm-version.sh"
# Or from source: plugins/mnemo/scripts/check-cm-version.sh
Script emits three lines: version: X, stale: N, path: .... Interpret:
stale > 0→ warn: "claude-mem has {stale} old version folder(s) cached. Restart all Claude windows — old Stop hooks point to a path that no longer exists."- `version {const u=app.metadataCache.unresolvedLinks;const f={};Object.values(u).forEach(l=>Object.keys(l).forEach(t=>f[t]=(f[t]||0)+1));return JSON.stringify(Object.entries(f).sort((a,b)=>b[1]-a[1]).slice(0,10));})()" vault="{vault}"
A short name with many refs (e.g. `[[Diadoc]]` ×30) = create a hub note `Diadoc.md` → `[[MOC — …]]` so all those links resolve (alias doesn't work for bare links — by design).
### Step 3: Tag Distribution
```bash
obsidian tags counts sort=count vault="{vault}"
Show top 15 tags. Flag tags used only once (potential typos).
Step 4: Notes by Type
Reuse the obsidian tags counts output from Step 3 — do not call it again (the tag index is one query; Steps 3 and 4 are two views of the same result). From it, extract counts for taxonomy tags: #atom, #molecule, #source, #session, #moc. These correspond to config.taxonomy.*.tag values.
Total notes count:
obsidian files ext=md vault="{vault}" total
Step 5: Missing Links Section (batched grep — 3600x faster)
Do NOT loop obsidian read per file — on a 1000-note vault that's ~180s. Use a single filesystem grep against the vault directory.
VAULT_PATH=$(bash "${CLAUDE_PLUGIN_ROOT}/scripts/get-vault-path.sh" "{vault}")
# Single recursive grep -L: files NOT containing the links section heading.
# Filter to taxonomy-prefixed notes.
grep -rL --include="*.md" "{links_section}" "$VAULT_PATH" 2>/dev/null \
| grep -E "(Atom|Molecule|Source|Session|MOC) — "
Measured on 999-note vault: ~49ms vs ~180s serial — 3600x speedup. Safe to run always.
Report notes missing the section.
Step 6: Bad Filenames (# in names → permanent orphans)
VAULT_PATH=$(bash "${CLAUDE_PLUGIN_ROOT}/scripts/get-vault-path.sh" "{vault}")
find "$VAULT_PATH" -name "*#*.md" -not -path "*/.obsidian/*" -not -path "*/.trash/*" 2>/dev/null | sed "s|$VAULT_PATH/||"
Files with # in the name are permanent orphans — [[Note #1]] parses as [[Note]] + heading anchor #1, so nothing resolves to them (even existing links). Flag for rename (# → — or drop the #). Same for . mid-name (breaks CLI create). See ${CLAUDE_PLUGIN_ROOT}/references/tool-routing.md (naming rules).
Step 7: Review Candidates (content-staleness, type-aware)
A temporal signal, distinct from orphans (Step 1, which is structural): notes untouched longer than the threshold for their type are candidates for a re-read. Threshold precedence: per-note ttl: → review.staleDays. → review.staleDays.default → 30 (legacy). Age is measured from the newest of date or reviewed — so stamping reviewed: {today} on a still-valid note resets its clock. That snooze is what stops a stale list from rotting into guilt-debt (the canonical failure mode of review dates — see Gotchas).
VAULT_PATH=$(bash "${CLAUDE_PLUGIN_ROOT}/scripts/get-vault-path.sh" "{vault}")
python3 "${CLAUDE_PLUGIN_ROOT}/scripts/review-candidates.py" "$VAULT_PATH" --limit 30
# Or from source: plugins/mnemo/scripts/review-candidates.py
Output: CANDIDATES\t{n}, then THRESHOLDS\t{json}, then one tab-separated row per note ({overdue_days} {type} {anchor_date} {anchor_src} {threshold_days} {relpath}), most-overdue first. {threshold_days} is the budget actually applied to that note (per-note ttl: if set, else its type's staleDays, else default) — show it as (type, Nd budget), never invent a ttl. Pure filesystem — independent of the obsidian CLI graph cache (no lag/lie risk). A missing review config section reproduces the legacy uniform 30-day behavior, so this is safe before any config migration.
Don't AND this with backlinks — a well-linked note can still hold outdated claims. Report candidates on their own; cross-reference Step 1 yourself if you specifically want "old and orphaned."
Step 7.5: Content Lint (optional deep pass — gated by review.lint.enabled)
Steps 1-7 are cheap and structural. This is the content pass — Karpathy's "lint": actually re-read the notes to judge whether claims have rotted, instead of trusting the calendar. Together the checks cover his four: orphans (Step 1) + concepts-mentioned-but-no-page (Step 2 unresolved links) + stale claims (Step 7) + contradictions (here). Gated by config.json → review.lint.enabled (default false) because it reads note bodies and costs tokens — skip this step silently when disabled.
When enabled, take the top review.lint.maxCandidates (default 15) candidates from Step 7 and have them read & judged on the model set by config.json → review.lint.model (default haiku; sonnet/opus for higher-quality verdicts). This health skill itself runs as a haiku fork and cannot upgrade its own model, so:
- If
review.lint.modelishaiku(or unset) → do the lint inline in this fork. - Otherwise → spawn one subagent on
model: {review.lint.model}(Claude Code: Task tool,subagent_type: Exploreor general; Codex:spawn_agent) that reads the candidate note bodies in one batched pass (filesystem read, not oneobsidian readper file) and returns the verdicts. Keeping the cheap Steps 1-7 onhaikuwhile the lint runs onopusis the whole point of the split. - Report the subagent's verdicts verbatim — never assume "all still-valid". The fork only aggregates what the subagent returned; it does not re-judge. The Step 9 Content-lint block and its count MUST reflect the subagent's actual breakdown: if the subagent returned 13 still-valid + 2 update-needed out of 15, the report says
13 still-valid, 2 update-needed, NOT15 still-valid. Defaulting the count to the candidate total (as if everything passed) is a reporting bug — wait for the subagent's verdicts before writing the report.
Emit a verdict per candidate:
- still-valid → close the loop: stamp
reviewed: {today}into the note's frontmatter viamcp__obsidian__str_replace— anchor on the frontmatter line inside the leading---block (not adate:/reviewed:mention in the body): replace the existingreviewed:value, or if absent insert afterdate:(date: {d}→date: {d}\nreviewed: {today}). A confirmed-valid note then stops resurfacing without a manual edit. This auto-stamp is on by default (config.json→review.lint.autoStampReviewed, default true); only if the user set it tofalsedo you just recommend the stamp and write nothing. When the lint runs in a spawned subagent (model ≠ haiku), that subagent does the stamping — it already holds the verdict and the note path. If a stamp write fails (Obsidian offline, ormcp__obsidian__str_replaceunavailable in the subagent context), don't drop it silently — collect those note paths and surface them under the Content lint report block so the user can stamp them manually. - update-needed → one line on what specifically looks outdated.
- contradicts [[Other Note]] → name the conflicting note; flag the older one against the newer.
Verdicts are triage, not truth — on haiku especially, expect false positives; even on opus, surface them as questions. The only write health ever performs is the reviewed: auto-stamp above, and only ever on a still-valid verdict — never content, never on update-needed/contradicts. It fires only inside this lint pass (which is itself off unless review.lint.enabled is true), and can be turned back to suggest-only with autoStampReviewed: false. The user stays in control.
Step 8: Top Hubs
Enumerate the MOC notes (by the moc taxonomy prefix), then count backlinks for each:
VAULT_PATH=$(bash "${CLAUDE_PLUGIN_ROOT}/scripts/get-vault-path.sh" "{vault}")
find "$VAULT_PATH" -name "{moc_prefix}*.md" -not -path "*/.obsidian/*" -not -path "*/.trash/*" 2>/dev/null | sed "s|.*/||;s|\.md$||" # MOC names
For each enumerated MOC name, count backlinks:
obsidian backlinks file="{moc_name}" vault="{vault}"
Sort by count, show top 5. Keep the enumerated MOC-name list — Step 8.5 reuses it to find topics that have no MOC.
Step 8.5: Research-Gap Candidates (report-only — where the vault wants to grow)
Karpathy's lint also proposes what to research next. Steps 1-8 already collected the raw signal — turn it into growth suggestions without any new CLI calls (reuse Step 2's obsidian eval top-10 unresolved targets, Step 3/4's tag counts, and the MOC-name list enumerated in Step 8). Two cheap, computable gap types:
- Topic cluster with no MOC — a non-taxonomy topic tag with ≥5 notes (Milo's "mental squeeze point", the same trigger
config-schema.mduses for creating a MOC) whose{moc_prefix}{Topic}is absent from Step 8's enumerated MOC-name list. Suggest: "12 notes tagged#auth, no MOC → createMOC — Auth?" - Recurring external with no Source note — a top unresolved target from Step 2's
obsidian evallist (the authoritative metadataCache top-10, not the CLIunresolvedoutput) cited ≥5 times that reads like an external tool/paper/vendor (not a short project name, which instead wants a hub note) and has noSource — …note. Suggest: "[[LangGraph]]cited ×9, no Source note → capture one?"
These are suggestions, never auto-created (same non-destructive stance as the rest of health). Skip a type silently when it yields nothing. Do not web-search to fill the gap — mnemo maintains a human-authored vault: it points at the gap, the user decides whether to fill it. (This is the on-philosophy half of Karpathy's "suggest new article candidates"; the auto-web-imputation half is deliberately omitted.)
Step 9: Output Report
📊 Vault Health Report ({date})
⚠️ claude-mem: {warning or "v12.3.9, clean"}
Total: {N} notes
Atoms: {N} | Molecules: {N} | Sources: {N}
Sessions: {N} | MOCs: {N} | Other: {N}
🔴 Orphans: {N}
- Note Name 1
- Note Name 2
🟡 Missing {links_section}: {N}
- Note Name 1
🚫 Bad filenames (`#`/`.`): {N} — permanent broken links, rename
- Atom — Foo (PR #12) → rename to "PR 12"
🔍 Top unresolved targets (missing hub notes?):
1. [[Diadoc]] ×34 → create hub note?
2. [[Python]] ×28
🔗 Unresolved wikilinks: {N} total
📏 Tags: {N} total, {N} used once
🏆 Top-5 Hubs (most backlinks):
1. MOC — Security (34)
2. MOC — AI ML Tools (28)
...
💤 Review candidates (stale by type-aware age): {N}
- Atom — API X gotcha — 45d overdue (atom, 60d budget)
- Source — vendor API pricing — 35d overdue (source, 180d budget)
(snooze a still-valid note: add `reviewed: {today}` to its frontmatter)
🔬 Content lint: {N judged} — {S} still-valid, {U} update-needed, {C} contradicts ← only when review.lint.enabled
(counts MUST equal the lint's actual verdicts — never default to all-still-valid; see Step 7.5)
- Atom — API X gotcha → UPDATE-NEEDED: superseded by [[Atom — API X v2]]
- Source — vendor API pricing → still-valid (stamped reviewed: {today})
🌱 Research-gap candidates (where the vault wants to grow): {N}
- #auth ×12 notes, no MOC → create MOC — Auth?
- [[LangGraph]] ×9, no Source note → capture one?
🧠 Claude memory/ index: {KB}KB / {lines} lines {✅ lean | ⚠️ bloated → autodream}
Omit the 🔬 Content lint block entirely when review.lint.enabled is false. Omit the 🌱 Research-gap candidates block when Step 8.5 found nothing. The still-valid line above shows the default (autoStampReviewed: true — the note was stamped); with autoStampReviewed: false render it as → still-valid (recommend reviewed: {today}) instead, since nothing was written.
Skip the ⚠️ claude-mem line entirely if Step 0 found nothing to warn about.
Step 10: Claude memory/ index health (autodream check)
Separate from the Obsidian vault, Claude Code keeps an always-loaded index at memory/MEMORY.md. Claude Code auto-memory hard-truncates this index at ~24.4 KB on load — beyond that, trailing rows silently vanish from Claude's context. So warn early (before the cliff), not at some lax size. Threshold is configurable via config.json → memory.indexWarnKB (default 22):
WARN=$(python3 -c "import json,os;print(json.load(open(os.path.expanduser('~/.mnemo/config.json'))).get('memory',{}).get('indexWarnKB',22))" 2>/dev/null || echo 22)
for f in "$HOME"/.claude/projects/-*/memory/MEMORY.md; do
[ -f "$f" ] || continue
kb=$(( $(wc -c ${WARN}KB warn (auto-memory truncates ~24.4KB) → run autodream (move sessions → MEMORY-archive-index.md, target <20KB)"
done
If flagged → recommend autodream (memory consolidation): slim the index into topic files + MEMORY-archive-index.md, no loss. Procedure: ~/.claude/memory/autodream-principles.md. This is the only memory/ check here — vault-health otherwise audits Obsidian, not Claude's memory/.
Gotchas
Common failures in ${CLAUDE_PLUGIN_ROOT}/references/gotchas.md. Skill-specific rules:
obsidian orphansmay return empty on small vaults — this is OK, not an error.- Reference notes (taxonomy docs, templates) aren't orphans even if few backlinks — they're meant to be lookups.
- Ghost notes (unresolved wikilinks) are a feature, not a bug — they enable entity discovery. Don't flag on raw count; instead surface the top targets (Step 2 eval) — frequent ones = missing hub notes (actionable).
- CLI graph queries cache & can lie —
orphans/unresolved/backlinkslag writes and have shown a note as resolved AND broken at once. For critical checks useobsidian evalonmetadataCache(see${CLAUDE_PLUGIN_ROOT}/references/gotchas.md). Treat counts as advisory if notes were created this session. - Do not auto-fix anything — only report. User decides what to clean up. The one exception is
review.lint.autoStampReviewed(default true): it lets the content-lint stampreviewed: {today}on a still-valid note (Step 7.5) to close the snooze loop. That is the sole frontmatter write health can make — only thereviewed:field of a confirmed-valid note, never content, never anything else. It fires only when the content lint itself is enabled (review.lint.enabled, default false), so a default install still writes nothing; setautoStampReviewed: falseto keep the lint suggest-only. - Step 5 uses filesystem grep (~3600x faster than per-file reads — 49ms vs 180s on a 999-note vault) — safe on any vault size.
- Review candidates (Step 7) are temporal, not structural — don't conflate with orphans. A note can be both, either, or neither. The script is age-only by design (cheap, no graph dependency).
- Content-lint verdicts (Step 7.5) are triage-grade — t
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: jojoprison
- Source: jojoprison/mnemo
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.