Enrich citations: fill in every citation in your vault, and see each change first
One command fills every citation's missing title, author, date, and publisher, shows you each change, and writes only what you approve.
Enrich citations: fill in every citation in your vault, and see each change first
Why Care?
If you keep a citation file per source, you know how they drift. Some were saved with a title and a URL. Some were saved before the footnote had any text, so they're empty. Some hold whatever an older extraction scraped, and that can be an author of "3 minutes" or a title of "Page not found".
On 2026-10-06 we cleaned up the 75 citations in our own vault with throwaway scripts run outside Obsidian. Author coverage went from 43 to 70 of 70, and we filled 49 reference texts, 63 publishers, and dozens of logos and dates. It worked, but only someone at a terminal could run it. Now it's two commands in Cite Wide, and nothing is written until you've looked.
What's New?
Enrich all citations. Fills in what's missing in every citation file in your Citations folder.
Enrich this citation. The same, for the footnote under your cursor or the citation file you have open.
You see the plan first. A modal lists every citation with changes, old → new for each field, with a checkbox per citation. Close it and nothing changes.
It only fills gaps. A real title, author, or date you wrote is never overwritten. Junk is treated as a gap: error-page titles ("Page not found | MRU"), bot checks ("Just a moment…"), and non-names in the author field ("3 minutes", "Super User").
Links that survive renames.
filesUsedInis rewritten as[[full/path|Alias]]links to the notes that cite each source today, so a renamed note no longer makes a citation look orphaned.A report you can act on. Each run saves
Citations/_reports/Enrichment-<YYYY-MM-DD-HHmm>.md, listing what changed, dead or blocked links, citations no note defines, and fields still empty.Safe to re-run. An enriched vault proposes no changes, and no file is touched.
How it works
read the vault once ──► index every [^id] and [^id]: (symlinked folders included)
│
▼
for each citation, 4 at a time: footnote ─► page <meta> tags ─► Jina Reader (if gaps) ─► publisher homepage (brand)
│
▼
planCitationChanges(current, fetched, usage) → [{ key, from, to, reason }] ← pure, unit-tested
│
▼
review modal (old → new, checkbox per citation) ── Cancel: nothing written
│ Apply
▼
processFrontMatter, approved fields only ──► report note in _reports/ The fetching is the same pipeline Promote to canonical source already uses, unchanged. What's new is the orchestration and, above all, the planner: one pure function that decides, field by field, whether to fill, replace, or leave alone. Every rule in it comes from something that went wrong in the vault run.
| What went wrong in the vault | The rule now |
| Error pages stored as titles ("Page not found | MRU") | A junk title counts as empty, and the footnote's title wins |
What BMW’s Corporate VC… | Entity-encoded titles are decoded |
| 23 stored authors that weren't names | An author that isn't name-shaped is replaced, or emptied when no name is known |
21 filesUsedIn paths pointing at renamed notes | Rewritten as links to the notes that cite it today; kept when none do |
| The temptation to mark sources canonical in bulk | canonical is never set; promotion stays a deliberate act |
Under the Hood
Pure core, thin I/O.
src/services/enrichCitationsService.tsholdsbuildUsageIndex,planCitationChanges,applyFieldChanges, andrenderEnrichmentReport, with no Obsidian or network calls.src/services/enrichCitationsRunner.tsreads the vault, runs the worker pool, and writes.Writes go through
processFrontMatter. The one-off run had to write YAML line by line to avoid restyling files; inside Obsidian, the app's own frontmatter API does that job.applyFieldChangesalso skips any field whose value changed after it was planned, so an edit you make while the modal is open is never overwritten.The access date is not a change.
date_recently_accessedis refreshed alongside real changes, but a citation whose only change would be that date isn't listed or written. That's what makes a re-run touch zero files.Usage is indexed once per run, not once per citation, and the Citations folder itself (with its
_reports,_text, and_filesfolders) doesn't count as usage.Tests first. 21 new tests ran red before the services existed: every planner case from the spec's acceptance list, using the vault's real values; the usage index over a stub vault; the report as a pure function; and the runner end to end over a stub vault with
requestUrlstubbed, so no test touches the network.pnpm test: 115 pass. ESLint reports 0 errors and 0 warnings; no new dependencies.
What's Next
Operator check: run Enrich all citations on the lossless vault. It was already enriched by the one-off run, so the modal should propose little or nothing. Anything it does propose is either a gap the scripts missed or a planner rule worth a second look.
cited_in_fileson canonical sources is left as is, per the spec's "everything else untouched"; a later pass could keep it in step withfilesUsedIn.
References
Spec:
context-v/specs/Enrich-Citations.mdThe run it productizes:
context-v/issues/Enriching-the-Lossless-Vault-Citations.md