From learning-loop
Quick-checks vault hygiene: ghost duplicates, orphan notes, stale inbox, broken wikilinks, embedding gaps. Light mode by default, --deep for full note-scoring analysis, --auto for safe auto-fix.
How this skill is triggered — by the user, by Claude, or both
Slash command
/learning-loop:healthThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Quick-check command that surfaces vault hygiene issues: ghost duplicates, near-duplicate pairs, orphan notes, stale inbox entries, embedding gaps, and broken wikilinks. Fast by default, deep on demand, with optional auto-fix for safe operations.
Quick-check command that surfaces vault hygiene issues: ghost duplicates, near-duplicate pairs, orphan notes, stale inbox entries, embedding gaps, and broken wikilinks. Fast by default, deep on demand, with optional auto-fix for safe operations.
/health or /health --light: quick vault status check (default)/health --deep: full diagnostic with note-scorer analysis/health --auto: auto-fix safe issues (combinable with either mode)/reflect sessions/inbox to see what needs attention| Input | Mode | Auto-fix |
|---|---|---|
/health | light | no |
/health --light | light | no |
/health --deep | deep | no |
/health --auto | light | yes |
/health --deep --auto | deep | yes |
/health --provenance | provenance | no |
/health --librarian | librarian | no |
/health --nli-edges | nli-edges | no |
Use AskUserQuestion when no arguments are provided to help users discover modes.
No arguments (/health):
Run light mode immediately (fast, no prompting needed: it's the default and completes in seconds).
But after presenting results, if issues were found, mention available modes:
Found N issues. Options:
/health --deep: full analysis with note scoring/health --auto: auto-fix safe issues (ghost dupes, broken links)/health --provenance: pipeline observability (fabrication rates, agent stats)/health --deep --auto: both
This teaches the modes through use rather than upfront prompting.
If --provenance flag is present, skip all vault health checks and run:
node PLUGIN/scripts/provenance-report.mjs
Display the output directly.
If the report includes a Recommendations section, present each recommendation and ask:
Which of these would you like to act on? Options:
- "N" to act on recommendation N
- "pattern N" to create a learned pattern from recommendation N
- "all" to review all recommendations
- "done" to finish
When user selects "pattern N", draft a positive behavior-based pattern following the format in PLUGIN_DATA/provenance/learned-patterns.md (where PLUGIN_DATA = CLAUDE_PLUGIN_DATA env or ~/.claude/plugins/data/learning-loop) and present for approval before writing.
After the local report, check for peer provenance data:
PLUGIN_DATA/federation/provenance-peers.json (where PLUGIN_DATA = CLAUDE_PLUGIN_DATA env or ~/.claude/plugins/data/learning-loop)Network (last 7 days):
peer-a: 12 sessions, 47 notes, 3 fixes
peer-b: 5 sessions, 12 notes, 1 fix
vault-search.mjs sync to fetch."Then stop (do not proceed to Step 1).
--librarian)If --librarian flag is present, skip all vault health checks and enter librarian review mode.
Step L1: Load Queue
Read PLUGIN_DATA/librarian/queue.jsonl. Parse all lines. Filter to status === 'pending'. If no pending items, report "No pending librarian observations." and stop.
Step L2: Phase 1: Advisory Review
Group pending items into link suggestions, tag suggestions, voice flags, and duplicate flags. Each subsection is independent: present and resolve one at a time.
Link suggestions: Present in a table grouped by confidence:
High confidence (N):
Orphan → Suggested link Reason
3-permanent/cadences-are-harmonic... → 3-permanent/chord-progressions-are... Both discuss harmonic function
...
Review (N):
Orphan → Suggested link Reason
...
Ask user: "Apply approved links? Enter numbers to approve (e.g., '1,3,5'), 'all-high' for all high-confidence, or 'skip'."
For each approved link suggestion:
\n\n[[suggested-note-slug]] to the note body using Edit toolapprovedFor rejected items, update status to rejected.
Tag suggestions: Present as a table:
Tag suggestions (N):
Target Existing tags Suggested tags
3-permanent/ginkgo-biloba-acute-pk... nootropic pharmacology, neuroscience
...
Ask user: "Apply tag suggestions? Enter numbers, 'all', or 'skip'."
For each approved tag suggestion:
suggested_tags into the existing tags: list (dedupe)approvedFor rejected items, update status to rejected.
Voice flags: Present as a list:
Voice flags (N):
0-inbox/gmail-multi-daemon-pull-dedup... "gmail multi daemon pull deduplication": Names a topic without stating a claim
...
These are advisory: present them for awareness. Ask: "Acknowledge voice flags? (y/n)": on yes, update all to acknowledged.
Duplicate flags: Present as a list:
Duplicate flags (N):
1. 0-inbox/foo-claim.md ↔ 3-permanent/foo-claim-original.md (similarity 0.93)
...
For each, ask the user to choose one of: merge (read both, decide which to keep, the user does the merge), link (add a wikilink between them: drop a [[other]] reference into the newer note's body), dismiss. Update queue item status to merged, linked, or dismissed.
Step L3: Phase 2: Staleness Suspects
Present staleness suspects:
Staleness suspects (N):
3-permanent/react-compiler-memoizes... 90 days old, matched: v1.0, October 2025
...
Ask: "Investigate staleness suspects? Enter numbers (e.g., '1,2'), 'all', or 'skip'."
For each selected suspect, Claude (the active model) investigates using available tools:
After investigation, ask user what to do with each: "update", "dismiss", or "flag for /deepen".
Step L4: Cleanup
After both phases:
node -e "import('./scripts/lib/librarian-queue.mjs').then(m => m.expireStaleItems('VAULT_PATH'))"node -e "import('./scripts/lib/librarian-queue.mjs').then(m => m.resetState())"Then stop (do not proceed to Step 1).
--nli-edges)If --nli-edges flag is present, skip all vault health checks and run NLI tuning mode only:
1. Aggregate stats
Query edges.db:
SELECT * FROM edges WHERE source_graph='nli' ORDER BY created_at DESC
Report:
edge_type: two are written today — challenges_rebuttal (driven by p(contradiction) > LL_NLI_THRESHOLD, default 0.90) and nli_supports (driven by p(entailment) > LL_NLI_ENTAIL_THRESHOLD, default 0.75). Report counts of each separately.from_path values that also have a regex-classified challenges_* edge to the same to_path (overlap: where regex and NLI contradiction agreed)confidence_score < LL_NLI_THRESHOLD for challenges_rebuttal, or confidence_score < LL_NLI_ENTAIL_THRESHOLD for nli_supports (surfaces when thresholds were tuned down between writes — these exist in the table but may be excluded from the histogram below depending on the threshold)SELECT COUNT(*) FROM edges WHERE source_graph='nli' AND confidence_score IS NOT NULL AND confidence_score < 0.90
Show inline as Below floor: N rows.
2. Random sample (10 edges from last 7 days)
SELECT from_path, to_path, edge_type, confidence_score, created_at
FROM edges
WHERE source_graph='nli' AND created_at >= date('now', '-7 days')
ORDER BY RANDOM() LIMIT 10
Render as a table:
3. Threshold line
Current LL_NLI_THRESHOLD (contradiction): <process.env.LL_NLI_THRESHOLD || '0.90 (default)'>
Current LL_NLI_ENTAIL_THRESHOLD (entailment): <process.env.LL_NLI_ENTAIL_THRESHOLD || '0.75 (default)'>
Spec sync threshold (frontmatter): 0.95
3a. Schema-mismatch / daemon-error count (last 7 days)
The hook logs structured errors when the NLI binary returns an unexpected envelope shape or the UDS daemon misbehaves. A non-zero count means edge writes are silently being dropped — check the binary version vs the hook's expected schema_version.
Grep recent hook logs for these tags:
edge-infer.runNliBatch.schemaMismatch.daemonedge-infer.runNliBatch.schemaMismatch.subprocessedge-infer.runNliBatch.daemon.timeoutedge-infer.runNliBatch.daemon.idle-timeoutedge-infer.runNliBatch.daemon.parse-erroredge-infer.runNliBatch.subprocessReport inline as NLI errors (7d): N (most recent: <tag> at <timestamp>).
If the count is zero, render NLI errors (7d): 0 (healthy).
4. Confidence-score histogram (10 bins from 0.90 to 1.00):
Query:
SELECT
CAST(((confidence_score - 0.90) * 100) AS INTEGER) AS bin,
COUNT(*) AS n
FROM edges
WHERE source_graph = 'nli' AND confidence_score IS NOT NULL AND confidence_score >= 0.90
GROUP BY bin
ORDER BY bin;
The confidence_score >= 0.90 predicate ensures bin math stays in the rendered 0-9 range. If you tune LL_NLI_THRESHOLD below 0.90, rows between the tuned threshold and 0.90 are excluded from this histogram (they still exist in the table). Use a separate query to inspect those.
Render as a horizontal text histogram (one row per bin, block characters scaled to the max count):
0.90-0.91 ████████ 24
0.91-0.92 ████ 13
0.92-0.93 ███ 9
0.93-0.94 ██ 7
0.94-0.95 ██ 5
0.95-0.96 ████ 12
0.96-0.97 ███ 8
0.97-0.98 ██ 6
0.98-0.99 ███ 9
0.99-1.00 ██ 7
Useful for tuning LL_NLI_THRESHOLD and the sync threshold (0.95) per spec. Bins above 0.95 propagate to note frontmatter; bins below stay in the db only.
Then stop (do not proceed to Step 1).
Collect the raw data needed for all checks. Run these in parallel:
Glob pattern *.md in {{VAULT}}/0-inbox/Glob pattern *.md in {{VAULT}}/1-fleeting/Glob pattern *.md in {{VAULT}}/3-permanent/Glob pattern *.md in {{VAULT}}/2-literature/Glob pattern *.md in {{VAULT}}/_system/node PLUGIN/scripts/vault-search.mjs cluster --threshold 0.85node PLUGIN/scripts/vault-search.mjs listnode PLUGIN/scripts/check-deps.mjsll-search binary via node -e "import { binaryVersion } from 'PLUGIN/scripts/lib/binary.mjs'; console.log(binaryVersion());" -- returns version string or nullParse the check-deps output from Step 1. Each entry has a required boolean — partition into required vs optional, render separately, and use it to set urgency.
For each dependency:
versionConstraint, install commandversionConstraint, install commandThis check runs in both light and deep modes -- there's no deeper analysis needed.
Compare inbox filenames against filenames in 1-fleeting/ and 3-permanent/. A ghost duplicate exists when the same filename appears in inbox AND a promoted folder.
Light: List each ghost duplicate with its promoted location. Deep: Read both versions of each ghost duplicate. If content is identical or the inbox version is a subset, confirm as true duplicate. If content has diverged, flag as "diverged copy: review before deleting".
Parse the cluster output from Step 1. Filter to pairs with similarity > 0.85 that are NOT ghost duplicates (same filename in different folders: already caught in Step 2).
Light: List each pair with similarity score. Deep: Read both notes in each pair. Compare content. Recommend which to keep (prefer the more mature version) or merge.
For each note across all content folders (0-inbox, 1-fleeting, 3-permanent), grep for \[\[ outgoing wikilinks. Notes with zero outgoing links are orphans. Exclude _system/ and 2-literature/ from orphan checks (system docs and literature notes don't need outlinks).
Light: List orphan filenames with their folder.
Deep: For each orphan, run node PLUGIN/scripts/vault-search.mjs similar "<note-path>" --top 3 to suggest link targets.
For each inbox note, check file modification time using Bash: node -e "console.log(require('fs').statSync('FILE').mtimeMs)". Flag notes older than 14 days.
Light: List stale notes with age in days.
Deep: Launch note-scorer agent(s) with stale note paths. Report maturity tier and recommend action: promote (if deep/medium), /deepen (if shallow but promising), or delete candidate (if shallow and empty).
Batching: If > 10 stale notes, split into batches of ~10 and launch parallel note-scorer agents.
Compare the full vault file list (all .md files in content folders) against the output of vault-search.mjs list. Notes present in the vault but missing from the embedding index are stale.
Light and Deep: List missing notes. No difference between modes: there's nothing deeper to analyze.
Grep all \[\[...\]\] wikilink references across all vault notes. For each unique link target, check if a matching .md file exists anywhere in the vault (case-insensitive filename match). Broken links are references to non-existent notes.
Light: List each broken link with the source note that contains it. Deep: For each broken link, find the closest matching vault filename using fuzzy/substring match and suggest it as a correction.
Read PLUGIN_DATA/librarian/queue.jsonl (where PLUGIN_DATA = CLAUDE_PLUGIN_DATA env or ~/.claude/plugins/data/learning-loop). Parse each line as JSON. Filter to items where status === 'pending'. Also read PLUGIN_DATA/librarian/state.json for visited count.
If the queue file doesn't exist or is empty, skip this step silently.
Group pending items by task field:
link_suggestion: link suggestions (orphan notes that should be linked)tag_suggestion: tag suggestions (under-tagged notes with proposed vocabulary tags)voice_flag: voice flags (topic-style titles)duplicate_flag: duplicate flags (notes that make the same claim as a near-neighbour)staleness_suspect: staleness suspects (Claude investigates)Add to the dashboard output:
Librarian: N pending observations (visited M/T notes)
Link suggestions: N (X high, Y review)
Tag suggestions: N
Voice flags: N
Duplicate flags: N
Staleness suspects: N (for Claude to investigate)
Queue: P% full (N/CAP cap)
Where CAP is read from config.json's librarian.queue_cap (default 200).
If the queue has pending items, add recommendation:
Run
/health --librarianto review and act on librarian suggestions.
Output the summary dashboard:
Vault Health: YYYY-MM-DD
Binary: ll-search vX.Y.Z (installed) | not installed
Dependencies: N satisfied, M missing
Ghost dupes: N inbox notes already promoted
Near-dupes: N pairs (>0.85 similarity)
Orphans: N notes with no outlinks
Stale inbox: N notes older than 14 days
Embeddings: N notes not indexed
Broken links: N dead [[wikilinks]]
Status: [total] issues [run /health --deep for full analysis]
The "run --deep" hint only appears in light mode. In deep mode, replace with a summary of findings.
Then output per-category details:
If --auto flag is set:
Bash: rm {{VAULT}}/0-inbox/<filename>[[ and ]] brackets from broken wikilinks using Edit tool, leaving the display text as plain text (e.g., [[missing-note]] becomes missing-note, [[missing|displayed]] becomes displayed)If --auto flag is NOT set:
/inbox, /verify, /deepen, or "re-index in Obsidian")Output a one-line summary of actions taken:
Fixed: N ghost dupes removed, N broken links cleaned. Remaining: N issues: see recommendations above.
If nothing was fixed (no --auto, user declined, or nothing fixable):
No fixes applied. N issues found: see recommendations above.
Reuses the note-scorer agent. Only invoked for stale inbox notes in --deep mode.
Launch pattern:
Spawn note-scorer subagent(s) (subagent_type: learning-loop:note-scorer) with this prompt:
Score these notes for inbox staleness assessment.
notes: <file-path-1>, <file-path-2>, ...
vault_path: {{VAULT}}/
scope: stale inbox triage (health --deep)
Return per-note: dimension scores + maturity tier (shallow/medium/deep) + specific issues found.
Batching: One agent per ~10 notes. Parallel for larger sets.
Output contract: Each agent returns a list of objects:
- file: <path>
tier: shallow | medium | deep
issues: [<string>, ...]
gate: N/6
claim_specificity: 0-2
source_grounded: 0-2
Mapping gate pass count to summary labels (for dashboard display):
--deep, give them the full picture. Use note-scorer, read content, diff duplicates.--auto only touches ghost dupes (inbox copy of promoted note) and broken links (references to nothing). Never auto-merge, auto-delete non-duplicate notes, or auto-promote./verify, /inbox, or /deepen. Recommend the right tool for each issue.0-inbox/ without asking. Broken link fixes edit the source note, which may be in any folder: always ask unless --auto.npx claudepluginhub robinslange/learning-loop --plugin learning-loopRuns vault health diagnostics in 8 categories including schema compliance, orphans, links, three-space boundaries, stale notes, MOC coherence. Quick/full/three-space modes yield FAIL/WARN/PASS reports with fixes.
Performs extended vault cleanup: full audits, stale content scans, outdated references, content quality reviews, redundant tag removal, broken external link fixes, and template compliance checks.
Read-only health check for Obsidian vault: finds broken links, orphaned notes, tag inconsistencies, and wiki issues, then reports prioritized fixes.