# Adopt an existing Obsidian vault

`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.

## What adopt mode does

    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)

## Coverage across six real Obsidian vaults

`{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)

## Caveats

- Adopt writes three small files to the vault root. Check the diff with `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`.
- Frontmatter quirks in the wild (ISBNs as bare integers, dates in non-ISO formats) surface as warnings, not errors. The page is still indexed; the offending field becomes `NA`.
- Adopt does not migrate Obsidian’s `.obsidian/` plugin config to pensar. Plugins keep working on the Obsidian side.
- The `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.

## Related

- `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`.