| .github/workflows | ||
| docs | ||
| scripts | ||
| src | ||
| test | ||
| .gitignore | ||
| CHANGELOG.md | ||
| esbuild.config.mjs | ||
| LICENSE | ||
| manifest-beta.json | ||
| manifest.json | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| README.zh-TW.md | ||
| styles.css | ||
| tsconfig.json | ||
| vitest.config.ts | ||
Vault Curate
Find, connect, and rediscover your notes.
A local-first second brain for Obsidian — semantic search · relation graph · semantic paths · Hot/Cold rediscovery · strong Chinese/CJK · WebGPU on-device · no API keys
Why Vault Curate?
Finding a note is only the first step. The harder parts come after: seeing how notes connect — including the links you never drew — and not letting good notes sink out of sight. Obsidian's built-in search is literal (think "prayer", miss "devotional"), its graph shows only the links you made by hand, and old notes quietly fall off your radar.
Andrej Karpathy shared his vision of LLM-maintained knowledge bases — letting AI "compile" your notes into structured wikis. Compelling, but it asks you to hand over full editorial control. Vault Curate takes a different stance: AI should help you see, not think for you.
Three differentiators
| Feature | How it works |
|---|---|
| A closed loop: find → connect → rediscover | Semantic search (hybrid BM25 + embeddings + fuzzy) finds the note; a relation graph and semantic paths surface related notes you never linked; Hot/Cold tiering resurfaces ones you'd forgotten. Each exists elsewhere as a separate single-point plugin — combining all three is what makes this a second brain, not just a search box. |
| Helps you see, not think for you | No background LLM calls, no auto-rewriting your notes, no chatbot answering for you. AI curation (descriptions, grouped MOCs) is opt-in and explicit. You stay the editor of your own vault. |
| Local-first, strong on Chinese/CJK | Runs on-device via WebGPU (~110 MB model once, 5,004 chunks in about 1m23s; WASM fallback works too), no API keys. The built-in bge-small-zh-v1.5 gives Chinese names, religious terms, and colloquial phrases an edge generic multilingual models miss — switch to Ollama/OpenAI for other languages. |
How it works: find → connect → rediscover
Four layers form one closed loop. The first three are zero-config and work out of the box; the fourth (curation) is off by default and only runs when you explicitly turn it on.
🔍 Find: semantic search
Search by meaning, not just literal characters. Three signals fused via Reciprocal Rank Fusion (k=60):
| Path | Catches |
|---|---|
| BM25 (pure TS, CJK trigram) | Exact phrases, keyword combinations |
| Semantic embedding | Different wording, same meaning |
| Fuzzy title (Jaro–Winkler) | Typos, spelling variants |
- Cmd/Ctrl+P →
Vault Curate: Semantic search (modal)for a quick jump; the sidebar Search tab for persistent results. - Find similar notes: right-click any
.md→ VC: Find similar notes; results land in the sidebar and drag straight to Canvas. Similarity is template-resistant (since 1.2.0): markdown structure symbols are stripped from the embedding input and the frontmatterdescriptionblends into the ranking vector, so a person card finds that person's conversations, not its nine template siblings. Embedding input is additionally converted Traditional→Simplified (since 1.2.2; stored text, keyword search, and snippets stay Traditional) to sharpen ranking on Traditional-Chinese vaults.
🕸 See connections: relation graph / semantic path / expand in place
Search finds a single note; this layer shows how notes relate — including the links you never drew by hand.
Relation graph (Canvas) — generate an editable Obsidian Canvas around any note: the note in the center, its top-K semantic neighbors laid out radially, every edge labeled with its similarity score.
- Purple edges = semantically close but not yet linked — invisible connections the native graph view can't show you
- Gray edges (with direction arrows) = notes you've already wikilinked
- Cyan nodes = Cold notes
Entry points: the command palette, right-click VC: Generate relation graph, or the Graph button on the Discover sidebar. Each run writes a fresh timestamped .canvas into the folder set under Advanced → Relation graph folder (default Vault Curate Canvases) — your edited graphs are never overwritten.
Semantic path (Canvas) — pick any two notes and get the chain of stepping-stone notes that connects them: a widest-path search over the vault's semantic k-NN graph, judged by the chain's weakest hop so one far-fetched link can't hide behind strong ones. Run Generate semantic path (Canvas) on the start note, then pick the destination in the fuzzy picker. If no chain of consistently strong links exists, you get an honest "not connected" notice with the actual numbers — that's information, not an error. The underlying graph builds on demand (~4s on a 2.5k-note vault) and is reused for repeat queries until the index changes.
Expand in this graph — right-click any node inside a generated canvas → VC: Expand in this graph grows the graph in place: the clicked note's neighborhood slots into free space around it, notes already on the canvas get connecting edges instead of duplicates, and a note pointed at by two or more edges turns orange (several expansions independently converged on it, which usually means it matters). Your layout edits and manually applied colors are never touched.
♻️ Rediscover: Hot/Cold tiering + Discover
A good note shouldn't cease to exist just because you forgot it. Notes are auto-tiered by internal links + recency: Hot (linked to or recently touched), Cold (orphan or untouched for a while); the cutoff is tunable under Advanced → Hot window (days).
Discover works on notes, not query strings — it actively surfaces semantically related Cold notes you haven't touched recently:
- Current note: opening a file surfaces related notes, with Cold ones visually highlighted ("you haven't read this one")
- Global: the Cold notes most related to your entire Hot pool — intentional blind-spot mining
- Results export to a topic-grouped Map of Content via Generate MOC (falls back to a flat MOC when results are too few or too similar)
✨ Curate (optional, off by default)
Turn it on under Settings → AI Curation → Enable AI curation to unlock three actions — all manually triggered, never running in the background:
- Generate a description + tags into a single note's frontmatter
- Run description generation across the sidebar's search / discover results in a batch
- Generate a topic-grouped MOC via HDBSCAN clustering + LLM naming
The LLM provider is configured separately under Settings → AI Curation (local Ollama or any OpenAI-compatible endpoint).
Getting started
Requirements: Obsidian desktop (v1.0.0+). The advanced paths (Ollama / OpenAI-compatible) additionally need a local Ollama instance or any OpenAI-compatible server.
Installation
From Community plugins (recommended)
- Open Settings → Community plugins, make sure Restricted mode is off, and click Browse
- Search Vault Curate, then click Install → Enable
Via BRAT (optional, tracks GitHub releases)
- Install and enable BRAT from Community plugins
- Cmd/Ctrl+P →
BRAT: Add a beta plugin for testing→ enternotoriouslab/vault-curate, then enable - New releases are picked up automatically (or on demand via
BRAT: Check for updates to all beta plugins)
Manual install
- Download
main.js,manifest.json,styles.cssfrom Releases (the two.wasmruntimes are fetched automatically on first launch) - Copy them into
.obsidian/plugins/vault-curate/in your vault, then enable in Settings → Community plugins
Tip: If your vault is Git-tracked, add
.obsidian/plugins/*/data.jsonand.obsidian/plugins/*/index.sqliteto.gitignore.
First launch
The Welcome to Vault Curate modal opens automatically: under Embedding provider pick Built-in (on-device, WebGPU), then click Index my vault now. After the ~110 MB model download and WebGPU indexing finish, click the sidebar compass icon and start searching.
Reference
Commands
From the Command Palette (Cmd/Ctrl+P), type Vault Curate: to see them all.
| Command | What it does | Requires |
|---|---|---|
Semantic search (modal) |
Modal-style semantic search with quick jump | always available |
Open search panel |
Open the sidebar panel | always available |
Find similar notes |
Find semantically related notes to the active .md |
always available |
Rebuild index |
Wipe the existing index and re-index everything | always available |
Update index |
Incremental update (re-index files with newer mtime) | always available |
Discover related Cold notes |
Global discover: Cold notes most related to your Hot pool | always available |
Generate relation graph (Canvas) |
Editable Canvas of the active note's semantic neighborhood | always available |
Generate semantic path (Canvas) |
Widest-path chain between the active note and a picked destination | always available |
Generate description for active note |
LLM-write description + tags to the active file's frontmatter | AI curation on |
Generate descriptions for current results |
Batch description for the current sidebar results | AI curation on |
Generate MOC (topic-grouped) |
HDBSCAN cluster + LLM-name each group | AI curation on |
Right-click menus expose these directly on a note: VC: Find similar notes, VC: Generate relation graph, VC: Generate semantic path, VC: Expand in this graph (while a .canvas is open), and VC: Generate description (AI curation on).
Settings
| Section | Settings | Default |
|---|---|---|
| Quick setup | Embedding provider (Built-in / Ollama / OpenAI-compatible); excluded folders | Built-in; empty |
| AI Curation | Enable toggle; LLM provider; LLM model | off; Ollama; qwen3:1.7b |
| Advanced | top results, min score, relation graph folder, Hot window (days), default search scope, chunk size + overlap, synonym list, auto-index toggle, rebuild + update buttons, index stats | see panel |
Changing the embedding provider or model triggers a confirmation modal — the index is wiped and rebuilt.
Privacy & security
Three embedding modes, picked from Quick setup → Embedding provider:
| Mode | Where embeddings run | Where note text goes |
|---|---|---|
| Built-in | On-device WebGPU / WASM | Stays on your device |
| Ollama (local daemon) | Local Ollama daemon on 127.0.0.1 | Stays on your device |
| OpenAI-compatible API | Any endpoint you point it at — local (LM Studio, llama.cpp, …) or remote (OpenAI etc.) | Depends on the endpoint you choose; may leave your device |
The same applies to AI curation (description / MOC naming), which uses an independently-configured LLM endpoint.
No telemetry. No usage tracking. Nothing is sent to any server unless you configure a remote endpoint.
Audit disclosures
The Obsidian Developer Dashboard's automated audit may flag the following items on this plugin. They are intentional and disclosed here for transparency:
- Vault enumeration (
vault.getMarkdownFiles()): The indexer needs to walk the full list of markdown files in your vault to build the semantic index. The "excluded folders" setting (Settings → Advanced) lets you scope this — e.g. excluding_templates/,.trash/, or any folder you don't want indexed. No file is read until it's in the included set. - Dynamic code execution (
new Functionin bundled@huggingface/transformers): The Hugging Face Transformers library usesnew Functioninternally to create type-safe method dispatchers during model loading. Vault Curate's own source code contains zeroeval()ornew Function(). We bundle the upstream library as-is to avoid divergence; the dynamic dispatch happens only inside the embedding model's tokenizer/inference setup, not on any vault content. - Direct filesystem access: The bundled
sql.jsships an Emscripten output with a Node.js fallback path that importsnode:fs/node:crypto. These branches are dead code in Obsidian's renderer process (gated byprocess.type !== "renderer"). As of v1.0.3, the esbuild config strips thoserequire()strings from the released bundle so the audit no longer sees them.
🔒 About API key storage
Vault Curate, like every Obsidian plugin, stores its settings (including any OpenAI API key) as plain text in <vault>/.obsidian/plugins/vault-curate/data.json. This is Obsidian's plugin storage mechanism, not a vault-curate-specific design choice.
If your vault syncs to a cloud service (iCloud / Dropbox / Google Drive) or pushes to a public Git repository, you should:
- Add
.obsidian/plugins/vault-curate/data.jsonto your sync exclusion list or.gitignore - Or use the Built-in model / Ollama path — neither requires an API key
Tech Stack
- TypeScript + esbuild (two-stage bundle for worker + main)
- sql.js (SQLite via WASM) for the storage layer — replaces v0.x's
data.json/index.json - Pure-TS BM25+ (
src/storage/bm25.ts) for CJK-aware full-text search (no native FTS5 dependency) @huggingface/transformers+bge-small-zh-v1.5q8 (~110 MB, WebGPU/WASM) for on-device embeddingshdbscan-tsfor topic clustering (MOC)- Reciprocal Rank Fusion (k=60) combining BM25 + semantic + fuzzy
- Optional: Ollama / any OpenAI-compatible endpoint for higher-end embedding or LLM models
Development
git clone https://github.com/notoriouslab/vault-curate.git
cd vault-curate
npm install
npm run dev # watch mode
npm run build # production build
npm test # vitest unit tests




