init_vault(path, adopt = TRUE) lets pensar attach to an
existing Obsidian vault without rewriting its layout. Adoption is
in place: pensar adds three small files
(schema.md, index.md, log.md) to
the path you pass and flags them with adopted: true
frontmatter. Nothing else in the vault is touched. After adoption the
vault is read-first: ingest() and the wiki writer refuse to
write unless you pass force = TRUE.
This vignette runs adopt mode against six real Obsidian vault repos
collected at ~/pensar-test-vaults. The chunks evaluate only
when that directory exists; on CRAN the code blocks are rendered as-is
without output. The demos copy each vault to a tempdir before adopting
it so the source tree in ~/pensar-test-vaults stays
untouched; production users would adopt their actual vault in place.
library(pensar)
v <- tempfile("vault-")
dir.create(v)
writeLines("# Hello", file.path(v, "Hello.md"))
init_vault(v, adopt = TRUE, rproj = FALSE, agent_instructions = FALSE)
list.files(v)
#> [1] "Hello.md" "index.md" "log.md" "schema.md"
# Adopted vaults refuse ingest writes:
tryCatch(ingest("body", source = "test://1", title = "T", vault = v),
error = function(e) conditionMessage(e))
#> [1] "Adopt mode: read-first. ingest() refuses writes unless force = TRUE."
# Page-level reads still work:
reg <- vault_registry(vault = v, cache = "none", refresh = TRUE)
nrow(reg)
#> [1] 4 # schema.md + index.md + log.md + Hello.md
unlink(v, recursive = TRUE){r setup, eval = dir.exists("~/pensar-test-vaults"), echo = TRUE} library(pensar) vaults_dir <- "~/pensar-test-vaults" vaults <- list.dirs(vaults_dir, recursive = FALSE) basename(vaults)
```{r adopt_all, eval = dir.exists(“~/pensar-test-vaults”), echo = TRUE} summarize_adoption <- function(src) { parent <- tempfile(“adopt-”) dir.create(parent) file.copy(src, parent, recursive = TRUE) dest <- file.path(parent, basename(src)) init_vault(dest, adopt = TRUE, rproj = FALSE, agent_instructions = FALSE) reg <- suppressWarnings( vault_registry(vault = dest, cache = “none”, refresh = TRUE)) out <- data.frame( vault = basename(src), pages = nrow(reg), adopted = pensar:::vault_is_adopted(dest), stringsAsFactors = FALSE) unlink(parent, recursive = TRUE) out }
results <- do.call(rbind, lapply(vaults, summarize_adoption)) results
Notes on each vault:
- **bramses-highly-opinionated-vault-2023** (37M) — Zettelkasten with
date-stamped notes. Adopts cleanly.
- **claude-obsidian** (16M) — `AGENTS.md` / `CLAUDE.md` already
present; adopt mode leaves them alone.
- **dusk-obsidian-vault** (199M) — Distributed mostly as zip themes;
contains few `.md` files. Adopt mode handles "vault with no
markdown content" gracefully (no error, low page count).
- **kepano-obsidian** (1.1M) — Steph Ango's example vault. The
smallest of the six and the cleanest demo. A handful of pages
use bare integers in frontmatter (ISBNs) that overflow R's
integer range; pensar's YAML loader surfaces a warning and
continues.
- **Obsidian-Vault-Structure** (7.4M) — Templated layout with
`01 - Primary`, `02 - Secondary`, `03 - Content`, `04 - Templates`
directories. Adopts cleanly.
- **obsidian-wiki** (1.6M) — Already wiki-shaped. Adopts cleanly.
## Walkthrough: kepano-obsidian
```{r walkthrough, eval = dir.exists("~/pensar-test-vaults/kepano-obsidian"), echo = TRUE}
src <- "~/pensar-test-vaults/kepano-obsidian"
parent <- tempfile("kepano-")
dir.create(parent)
file.copy(src, parent, recursive = TRUE)
vault <- file.path(parent, basename(src))
init_vault(vault, adopt = TRUE, rproj = FALSE,
agent_instructions = FALSE)
# What got written?
new_files <- setdiff(list.files(vault),
list.files(src))
new_files
# Look at the adopted schema:
readLines(file.path(vault, "schema.md"))[1:6]
# Page breakdown:
status(vault)
unlink(parent, recursive = TRUE)
git status before committing.init_vault(adopt = TRUE) is one-way at the data level.
To revert, delete schema.md, index.md, and
log.md.NA..obsidian/ plugin
config to pensar. Plugins keep working on the Obsidian side.force = TRUE escape hatch is available on
ingest() and the wiki writer if you need to write into an
adopted vault. Use sparingly; the read-first default exists so an LLM
agent can’t surprise you with destructive writes.inst/tinytest/test_adopt_real.R exercises the same six
vaults with assertions; gated by tinytest::at_home() and
the PENSAR_TEST_VAULTS environment variable.inst/tinytest/test_adopt.R covers the adopt-mode
mechanism with synthetic directories and runs in every
R CMD check.