| Title: | LLM Wiki Engine |
|---|---|
| Description: | Personal wiki engine with a large language model (LLM) as research assistant. Supports guided sessions through a 'Claude Code' <https://github.com/anthropics/claude-code> skill bundle and autonomous research runs from R via autoresearch(). Results land in a structured vault of markdown pages with 'YAML' frontmatter and wikilinks, ready for hand-editing in your favourite editor alongside the LLM. Vaults are seeded with 'CLAUDE.md' and 'AGENTS.md' so 'Claude Code', 'Codex' <https://github.com/openai/codex>, and other agents share the same operating instructions. Can adopt an existing 'Obsidian' <https://obsidian.md/> vault in place via init_vault(adopt = TRUE). |
| Authors: | Troy Hernandez [aut, cre] (ORCID: <https://orcid.org/0009-0005-4248-604X>), cornball.ai [cph] |
| Maintainer: | Troy Hernandez <[email protected]> |
| License: | Apache License (>= 2) |
| Version: | 0.6.3 |
| Built: | 2026-05-19 14:15:41 UTC |
| Source: | https://github.com/cran/pensar |
Runs a bounded, package-owned research workflow. R controls the loop, source ingestion, wiki writes, index refresh, and logging; model calls are limited to structured decisions such as query planning, source selection, evidence extraction, and page drafting.
The default search backend uses Tavily via TAVILY_API_KEY. The
default model backend uses llm.api when credentials are
available and otherwise falls back to deterministic heuristics. Tests
and integrations can pass fake search_backend,
fetch_backend, and model_backend functions.
autoresearch(topic, vault = default_vault(), search_backend = NULL, fetch_backend = NULL, model_backend = NULL, program = NULL, force = FALSE, overwrite = TRUE, update = TRUE, slug = NULL, provider = "auto", model = NULL, verbose = TRUE)autoresearch(topic, vault = default_vault(), search_backend = NULL, fetch_backend = NULL, model_backend = NULL, program = NULL, force = FALSE, overwrite = TRUE, update = TRUE, slug = NULL, provider = "auto", model = NULL, verbose = TRUE)
topic |
Character. Research topic, free-form string. |
vault |
Character. Vault path. |
search_backend |
Function with signature |
fetch_backend |
Function with signature |
model_backend |
Function with signature
|
program |
Optional list or YAML path overriding the default
autoresearch program. Vault-level |
force |
Logical. Allow writes into adopted vaults. |
overwrite |
Logical. Allow planned wiki pages to overwrite
existing wiki files. Set |
update |
Logical. When |
slug |
Optional character. When supplied, force the synthesis
row's slug to this value, bypassing the title-overlap collision
guard. Useful for explicitly amending an existing wiki page
( |
provider |
Provider for the default |
model |
Optional model name for the default |
verbose |
Logical. Print phase progress. |
A list of class pensar_research with topic, program,
queries, search results, filed sources, extracted claims, written
pages, synthesis metadata, and model usage.
## Not run: vault <- file.path(tempdir(), "ar-example") init_vault(vault, rproj = FALSE, agent_instructions = FALSE) use_vault(vault) Sys.setenv(TAVILY_API_KEY = "tvly-...") res <- autoresearch("transformer scaling laws") print(res) show_page(res$synthesis$slug) ## End(Not run)## Not run: vault <- file.path(tempdir(), "ar-example") init_vault(vault, rproj = FALSE, agent_instructions = FALSE) use_vault(vault) Sys.setenv(TAVILY_API_KEY = "tvly-...") res <- autoresearch("transformer scaling laws") print(res) show_page(res$synthesis$slug) ## End(Not run)
Find pages that link to a given page via wikilinks. Find backlinks to a page
Scans all markdown files in the vault for [[wikilinks]] that
reference the target page.
backlinks(page, vault = default_vault())backlinks(page, vault = default_vault())
page |
Page name (without |
vault |
Path to the vault directory. |
A data.frame with columns source (page name) and
file (path relative to the vault).
v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) ingest("See [[seed]] for context.", type = "articles", source = "demo", vault = v) backlinks("seed", vault = v) unlink(v, recursive = TRUE)v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) ingest("See [[seed]] for context.", type = "articles", source = "demo", vault = v) backlinks("seed", vault = v) unlink(v, recursive = TRUE)
Read-only audits that surface duplicate-looking pages
and tag-vocabulary drift. Both write proposals to
<vault>/_proposals/ for human review. Pensar never
auto-merges or auto-renames.
Find candidate duplicate pages
Compares every non-system page pair by Jaro-Winkler title similarity
and tag-set Jaccard overlap. Pairs whose combined score exceeds
threshold are written to _proposals/dedup.md for human
review.
The combined score weights title similarity at 0.6 and tag overlap
at 0.4 (0.6 * jw_sim + 0.4 * tag_jaccard). Title similarity
is computed on lowercased trimmed titles; pages with no title fall
back to node_id.
Pensar never auto-merges. The proposals file is for human review.
dedup(vault = default_vault(), threshold = 0.7)dedup(vault = default_vault(), threshold = 0.7)
vault |
Vault path. |
threshold |
Minimum combined score, in [0, 1]. Default 0.7. |
A data.frame of proposed pairs (invisibly):
page_a, page_b, title_similarity,
tag_overlap, combined_score. Sorted by combined
score descending.
v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) ingest("A.", type = "articles", source = "alpha-foo", vault = v) ingest("B.", type = "articles", source = "alpha-foos", vault = v) dedup(v) unlink(v, recursive = TRUE)v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) ingest("A.", type = "articles", source = "alpha-foo", vault = v) ingest("B.", type = "articles", source = "alpha-foos", vault = v) dedup(v) unlink(v, recursive = TRUE)
Ingest content into a pensar vault. Ingest content into the vault
Writes content to raw/{type}/, generates a filename from source
and date, adds YAML frontmatter, updates index.md, and appends
to log.md.
ingest(content, type = c("articles", "chats", "briefings", "matrix"), source, title = NULL, tags = NULL, vault = default_vault(), force = FALSE)ingest(content, type = c("articles", "chats", "briefings", "matrix"), source, title = NULL, tags = NULL, vault = default_vault(), force = FALSE)
content |
Character string or character vector (lines) of content. |
type |
Content type: |
source |
Short identifier for the content source (e.g., URL, session ID, project name). |
title |
Optional title. If |
tags |
Optional character vector of tags. |
vault |
Path to the vault directory. |
force |
In adopted vaults ( |
The path to the written file, invisibly.
v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) ingest("Hello, world.", type = "articles", source = "demo", vault = v) status(v) unlink(v, recursive = TRUE)v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) ingest("Hello, world.", type = "articles", source = "demo", vault = v) status(v) unlink(v, recursive = TRUE)
Thin wrapper that snapshots
saber::agent_context() into the vault as a raw/chats/
page so the live agent context (memory, instructions, identity)
becomes searchable across sessions.
Snapshot saber's assembled agent context into the vault
Calls saber::agent_context() to assemble the current
memory / project-instructions / global-instructions / identity
string for an agent, then writes it into the vault through the
existing ingest() pipeline (so the manifest, index, log,
and auto-commit all kick in).
Saber stays in pensar's Suggests: the wrapper guards with
requireNamespace("saber") and errors with an install hint
if the package isn't available. It also gates on the
agent_context() export specifically so the wrapper degrades
cleanly against older saber versions (pre-0.4) that don't ship the
function, instead of failing R CMD check's static analysis or
crashing at runtime. Users who don't want this wrapper pay no
mandatory dependency cost.
Returns silently with a message (and no write) when saber returns an empty context, so the vault doesn't accumulate empty snapshots.
ingest_agent_context(agent = c("claude", "codex", "corteza"), vault = default_vault(), project_dir = getwd(), workspace_dir = NULL, ...)ingest_agent_context(agent = c("claude", "codex", "corteza"), vault = default_vault(), project_dir = getwd(), workspace_dir = NULL, ...)
agent |
One of |
vault |
Vault path. |
project_dir |
Project directory passed to
|
workspace_dir |
Optional workspace dir
(e.g., |
... |
Forwarded to |
The relative path of the written page, invisibly. Returns
NULL invisibly when saber returns an empty context.
## Not run: v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) ingest_agent_context("claude", vault = v) unlink(v, recursive = TRUE) ## End(Not run)## Not run: v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) ingest_agent_context("claude", vault = v) unlink(v, recursive = TRUE) ## End(Not run)
Deprecated. Use ingest_repo() with the default
enrich = "auto" (which writes a briefing.md for R packages
under raw/repos/<name>/) instead. This function now delegates to
ingest_repo() after warning.
ingest_briefing(project = NULL, scan_dir = path.expand("~"), vault = default_vault())ingest_briefing(project = NULL, scan_dir = path.expand("~"), vault = default_vault())
project |
Project name. If |
scan_dir |
Directory to search for the project. Defaults to
|
vault |
Path to the vault directory. |
Invisibly, the path(s) written by ingest_repo().
## Not run: v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) ingest_briefing(project = "pensar", vault = v) ## End(Not run)## Not run: v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) ingest_briefing(project = "pensar", vault = v) ## End(Not run)
Ingest a git repo's metadata, briefing, and AST into the vault. Ingest a repository snapshot into the vault
Captures repo state pinned to a commit SHA so wiki claims can cite a
specific point in time. Writes one or more artifacts under
raw/repos/<name>/<artifact>.md:
type: repo-briefing – saber digest of HEAD
(regenerable). Requires the saber package.
type: repo-ast – exported and internal symbols
from saber::symbols() (regenerable). Requires saber.
type: repo-snapshot – commit-pinned
metadata: SHA, origin URL, branch, tracked file listing, recent
commits.
Re-running overwrites the artifact files in place; git tracks history.
This supersedes ingest_briefing(), which is now deprecated.
ingest_repo(path, name = NULL, ref = "HEAD", enrich = c("auto", "package", "none"), artifacts = c("briefing", "ast", "snapshot"), files = NULL, tags = NULL, vault = default_vault())ingest_repo(path, name = NULL, ref = "HEAD", enrich = c("auto", "package", "none"), artifacts = c("briefing", "ast", "snapshot"), files = NULL, tags = NULL, vault = default_vault())
path |
Path to a local git repo. Tilde-expanded. |
name |
Repo identifier used as the directory name under
|
ref |
Git ref to snapshot. Default |
enrich |
One of |
artifacts |
Subset of |
files |
Optional file globs (relative to the repo root) whose
tracked paths are listed in |
tags |
Optional character vector of tags applied to every written artifact. |
vault |
Path to the vault directory. |
Character vector of paths written, invisibly.
## Not run: v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) ingest_repo("~/corteza", vault = v) ## End(Not run)## Not run: v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) ingest_repo("~/corteza", vault = v) ## End(Not run)
Fetch a URL with curl, write the body into the
vault via ingest(), and record the source URL in the
manifest.
Ingest content from a URL
Fetches url (10s timeout), refuses non-2xx responses or
content types outside text/JSON/XML, and writes the body into the
vault through ingest(). If the manifest already records this
URL as a source, returns the existing page path without re-fetching.
For HTML responses the page's <title> is extracted and used
as the page title when title is not supplied.
ingest_url(url, vault = default_vault(), type = "articles", title = NULL, tags = NULL)ingest_url(url, vault = default_vault(), type = "articles", title = NULL, tags = NULL)
url |
URL to fetch. |
vault |
Vault path. |
type |
Ingest type. Default |
title |
Optional page title. If |
tags |
Optional character vector of tags. |
The relative path of the written (or existing) page, invisibly.
## Not run: v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) ingest_url("https://example.com", vault = v) unlink(v, recursive = TRUE) ## End(Not run)## Not run: v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) ingest_url("https://example.com", vault = v) unlink(v, recursive = TRUE) ## End(Not run)
Create and seed a pensar vault. Initialize a pensar vault
Creates the vault directory structure and seeds the control files:
schema.md, index.md, log.md, and (by default)
agent instruction files for Claude Code and Codex.
init_vault(path = default_vault(), rproj = TRUE, agent_instructions = TRUE, adopt = FALSE, commit = NULL, force = FALSE)init_vault(path = default_vault(), rproj = TRUE, agent_instructions = TRUE, adopt = FALSE, commit = NULL, force = FALSE)
path |
Path to the vault directory. No implicit default: pass
an explicit path, or configure one via |
rproj |
If |
agent_instructions |
If |
adopt |
Opt-in read-only adopt mode. When |
commit |
Auto-commit gate. |
force |
Write gate. |
The vault path, invisibly. Returns NULL invisibly when
the safety gate refused to scaffold.
v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) list.files(v, recursive = TRUE) unlink(v, recursive = TRUE)v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) list.files(v, recursive = TRUE) unlink(v, recursive = TRUE)
Health check for a pensar vault. Vault health check
Scans the vault for orphan pages (no incoming wikilinks), broken wikilinks (pointing to nonexistent pages), and tag clusters with no wiki synthesis.
lint(vault = default_vault(), min_cluster_size = 3L)lint(vault = default_vault(), min_cluster_size = 3L)
vault |
Path to the vault directory. |
min_cluster_size |
Minimum number of raw pages sharing a tag to suggest a wiki page. Default 3. |
A list with class pensar_lint.
v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) ingest("Refers to [[absent]].", type = "articles", source = "demo", vault = v) lint(v) unlink(v, recursive = TRUE)v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) ingest("Refers to [[absent]].", type = "articles", source = "demo", vault = v) lint(v) unlink(v, recursive = TRUE)
Append-only operation log for a pensar vault. Append a log entry
Appends a structured entry to log.md with timestamp, operation
type, and message.
log_entry(message, operation = "note", vault = default_vault())log_entry(message, operation = "note", vault = default_vault())
message |
Description of what happened. |
operation |
Operation type (e.g., |
vault |
Path to the vault directory. |
Invisible NULL.
v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) log_entry("Reviewed wiki/seed.md", operation = "review", vault = v) unlink(v, recursive = TRUE)v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) log_entry("Reviewed wiki/seed.md", operation = "review", vault = v) unlink(v, recursive = TRUE)
Pensar-owned bookkeeping at .pensar/manifest.yml:
per-source ingest provenance and an opt-in path -> page_uid
address map. Read by retrieval primitives that need delta info;
written by ingest() and ingest_repo() after a
successful page write. Read-only operations
(vault_registry(), update_index(), status())
never touch the manifest.
Canonical manifest path inside a vault
Returns <vault>/.pensar/manifest.yml. The directory is created
lazily by update_manifest() so simply asking for the path
doesn't materialize .pensar/.
manifest_path(vault)manifest_path(vault)
vault |
Vault path. |
Absolute path to the manifest file.
v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) manifest_path(v) unlink(v, recursive = TRUE)v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) manifest_path(v) unlink(v, recursive = TRUE)
Move legacy raw/briefings/ content into raw/repos/<repo>/. Migrate legacy briefings to the repo provenance layout
Walks raw/briefings/, classifies each file as a briefing or AST
artifact, maps the slug to a repo name, keeps the newest file per
(repo, artifact) pair, and moves it to
raw/repos/<repo>/<artifact>.md. Older duplicates are dropped if
drop_old = TRUE (git history retains them).
Wikilinks are rewritten in wiki/*.md only – raw/ is left
untouched, per the immutability rule. Aliases (e.g.
[[2026-04-30-corteza|corteza]]) are preserved.
Run with dry_run = TRUE (the default) first and review the
planned operations before committing.
migrate_briefings_to_repos(vault = default_vault(), dry_run = TRUE, drop_old = TRUE, rename_map = c(llamaR = "corteza", llamar = "corteza"))migrate_briefings_to_repos(vault = default_vault(), dry_run = TRUE, drop_old = TRUE, rename_map = c(llamaR = "corteza", llamar = "corteza"))
vault |
Path to the vault. |
dry_run |
If |
drop_old |
If |
rename_map |
Named character vector mapping legacy slug stems to
current repo names. Defaults handle the |
Invisibly, a data.frame with columns file,
repo, artifact, action, destination.
Find the pages a given page cites via wikilinks. Find outlinks from a page
Scans a single page for [[wikilinks]] and returns the targets.
Mirror of backlinks() in the forward direction.
outlinks(page, vault = default_vault())outlinks(page, vault = default_vault())
page |
Page name (without |
vault |
Path to the vault directory. |
A data.frame with columns target (page name) and
exists (logical: whether the target page exists in the vault).
v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) fp <- ingest("Cites [[seed]] and [[missing]].", type = "articles", source = "demo", vault = v) outlinks(tools::file_path_sans_ext(basename(fp)), vault = v) unlink(v, recursive = TRUE)v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) fp <- ingest("Cites [[seed]] and [[missing]].", type = "articles", source = "demo", vault = v) outlinks(tools::file_path_sans_ext(basename(fp)), vault = v) unlink(v, recursive = TRUE)
Returns the frontmatter, a short body head, the page's outlinks, and
its backlinks in one struct so callers don't have to call four
functions. Resolves name through the registry, so a query
like "Notes/Foo" works alongside the basename style.
page_context(name, vault = default_vault(), body_chars = 300L)page_context(name, vault = default_vault(), body_chars = 300L)
name |
Page name, path, alias, or page_uid. |
vault |
Vault path. |
body_chars |
Maximum chars of body to return. Default 300. |
A list with components path (relative path),
node_id, frontmatter (named list), body_head
(string), outlinks (data.frame), backlinks
(data.frame), class = "pensar_page_context".
v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) fp <- ingest("Body text here.", type = "articles", source = "demo", vault = v) ctx <- page_context(tools::file_path_sans_ext(basename(fp)), vault = v) names(ctx) unlink(v, recursive = TRUE)v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) fp <- ingest("Body text here.", type = "articles", source = "demo", vault = v) ctx <- page_context(tools::file_path_sans_ext(basename(fp)), vault = v) names(ctx) unlink(v, recursive = TRUE)
Helper to locate pensar's bundled agent skills. Locate pensar's bundled skill directory
Pensar ships markdown skill bundles under inst/skills/pensar/.
Returns the absolute path to the bundle root, or to a specific
skill when skill is given. Useful for symlinking pensar
skills into an agent's skill directory, e.g.
ln -s $(Rscript -e 'cat(pensar::pensar_skill_path())') \
~/.claude/skills/pensar.
pensar_skill_path(skill = NULL)pensar_skill_path(skill = NULL)
skill |
Optional skill name (e.g., |
Absolute path. Returns an empty string when the skill is
not installed (matching system.file() behavior).
pensar_skill_path() pensar_skill_path("autoresearch")pensar_skill_path() pensar_skill_path("autoresearch")
Print a pensar research session result
## S3 method for class 'pensar_research' print(x, ...)## S3 method for class 'pensar_research' print(x, ...)
x |
A |
... |
Unused. |
x, invisibly.
Returns a normalized list with version, created,
sources, and address_map fields. When the manifest
file is missing, returns a fresh empty struct with the current date
as created; the manifest file is not written.
Malformed YAML logs a warning and returns the empty struct so callers
can keep going.
read_manifest(vault)read_manifest(vault)
vault |
Vault path. |
A list with components version (integer),
created (YYYY-MM-DD string), sources
(named list of per-source records), and address_map
(named list mapping relative path to page_uid).
v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) read_manifest(v)$sources unlink(v, recursive = TRUE)v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) read_manifest(v)$sources unlink(v, recursive = TRUE)
Parses entries from log.md (the format written by
log_entry()). Returns entries from the last days days,
newest first.
recent_activity(vault = default_vault(), days = 7L)recent_activity(vault = default_vault(), days = 7L)
vault |
Vault path. |
days |
Window in days. Default 7. |
A data.frame with columns timestamp (POSIXct),
operation, message, sorted newest first.
v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) recent_activity(v, days = 30) unlink(v, recursive = TRUE)v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) recent_activity(v, days = 30) unlink(v, recursive = TRUE)
Read-only queries over a vault's page registry: search,
page context, related pages, and recent activity. Built on
vault_registry(); no disk writes.
Search pages by title, tags, aliases, or (optionally) body
Substring match (case-insensitive). Default scope is registry-only
fields: title, tags, and frontmatter aliases.
With in_body = TRUE the body of each page is scanned too;
that reads every file and is slower.
search_pages(query, vault = default_vault(), type = NULL, in_body = FALSE)search_pages(query, vault = default_vault(), type = NULL, in_body = FALSE)
query |
Substring to search for. |
vault |
Vault path. |
type |
Optional type filter. When supplied, only pages whose
registry |
in_body |
If |
A data.frame with columns path, node_id,
title, type, and matched_in (character
identifying where the substring was found: "title",
"tag:<tag>", "alias:<alias>", or "body").
v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) ingest("Body cites [[other]].", type = "articles", source = "demo-article", vault = v) search_pages("demo", vault = v) unlink(v, recursive = TRUE)v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) ingest("Body cites [[other]].", type = "articles", source = "demo-article", vault = v) search_pages("demo", vault = v) unlink(v, recursive = TRUE)
Drill down into a page: content, outlinks, and backlinks. Show a page with its connections
Returns the page content alongside its outgoing and incoming wikilinks. Use this when you need to review or edit a page: the outlinks show what raw sources the page cites; the backlinks show what depends on it.
show_page(page, vault = default_vault())show_page(page, vault = default_vault())
page |
Page name (without |
vault |
Path to the vault directory. |
A list with class pensar_page.
v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) fp <- ingest("Body text.", type = "articles", source = "demo", vault = v) show_page(tools::file_path_sans_ext(basename(fp)), vault = v) unlink(v, recursive = TRUE)v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) fp <- ingest("Body text.", type = "articles", source = "demo", vault = v) show_page(tools::file_path_sans_ext(basename(fp)), vault = v) unlink(v, recursive = TRUE)
Summary stats for a pensar vault. Vault status summary
Returns page counts by category, total pages, and wikilink count.
When vault is NULL (default), the vault is resolved
via PENSAR_VAULT, walk-up from getwd(), or
options("pensar.vault"), and the source of the match is
recorded for display.
status(vault = NULL)status(vault = NULL)
vault |
Path to the vault directory. |
A list with class pensar_status, including the
resolved vault path and a source label
("env", "walkup", "walkup-subdir",
"option", or "explicit").
v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) status(v) unlink(v, recursive = TRUE)v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) status(v) unlink(v, recursive = TRUE)
Reads every tag from the registry, optionally compares against a
taxonomy file (_meta/taxonomy.md, a markdown bullet list of
allowed tags), and writes a proposals report to
_proposals/tags.md. Unknown tags get near-miss suggestions
via Jaro-Winkler distance against the taxonomy.
Pensar never auto-renames. The proposals file is for human review.
tags(vault = default_vault(), taxonomy = NULL, near_miss_threshold = 0.15)tags(vault = default_vault(), taxonomy = NULL, near_miss_threshold = 0.15)
vault |
Vault path. |
taxonomy |
Optional path to a taxonomy file. Defaults to
|
near_miss_threshold |
Maximum Jaro-Winkler distance for an unknown tag to be suggested as a typo of a taxonomy entry. Default 0.15. |
A list with components used (data.frame of tag /
count), unknown (data.frame of unknown tags with optional
suggestions), and unused_taxonomy (character vector of
taxonomy entries with zero usage). Invisible.
v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) ingest("hi", type = "articles", source = "demo", tags = c("foo", "bar"), vault = v) tags(v) unlink(v, recursive = TRUE)v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) ingest("hi", type = "articles", source = "demo", tags = c("foo", "bar"), vault = v) tags(v) unlink(v, recursive = TRUE)
Regenerate the vault index as a markdown catalog. Update the vault index
Scans all markdown files in the vault and regenerates index.md
as a categorized catalog with wikilinks and titles.
update_index(vault = default_vault())update_index(vault = default_vault())
vault |
Path to the vault directory. |
The path to index.md, invisibly.
v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) ingest("Body.", type = "articles", source = "demo", vault = v) update_index(v) unlink(v, recursive = TRUE)v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) ingest("Body.", type = "articles", source = "demo", vault = v) update_index(v) unlink(v, recursive = TRUE)
Patches a manifest record for one page. Writes only
.pensar/manifest.yml; never edits other tools' manifest
formats (.manifest.json, .raw/.manifest.json).
source, hash, or ingested_at together write or
refresh a sources[path] record. page_uid (or
address, which is treated as an alias) writes an
address_map[path] record. A call with only path and
page_uid updates the address map without touching the
sources record.
update_manifest(vault, source = NULL, path = NULL, page_uid = NULL, address = NULL, hash = NULL, ingested_at = NULL)update_manifest(vault, source = NULL, path = NULL, page_uid = NULL, address = NULL, hash = NULL, ingested_at = NULL)
vault |
Vault path. |
source |
Source identifier (URL, session id, etc.). Optional. |
path |
Relative path inside the vault that this update is about. Required if any of the other fields are set. |
page_uid |
Stable page identity from frontmatter
|
address |
Alias for |
hash |
Content hash (typically |
ingested_at |
Timestamp string. Defaults to current time when any source-shaped field is set. |
The manifest path, invisibly.
v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) update_manifest(v, source = "demo", path = "raw/articles/demo.md", hash = "sha1:abc") read_manifest(v)$sources[["raw/articles/demo.md"]] unlink(v, recursive = TRUE)v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) update_manifest(v, source = "demo", path = "raw/articles/demo.md", hash = "sha1:abc") read_manifest(v)$sources[["raw/articles/demo.md"]] unlink(v, recursive = TRUE)
Sets options("pensar.vault") so subsequent pensar calls
resolve to path without repeating the argument. Persist by
adding pensar::use_vault("~/wiki") to ~/.Rprofile as
a global default. Both PENSAR_VAULT and a project-local
schema.md found via walk-up (in the current directory or a
vault/ subdir) will override this option (see
default_vault resolution order).
use_vault(path)use_vault(path)
path |
Path to your pensar vault directory. |
The resolved path, invisibly.
v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) use_vault(v) status() options(pensar.vault = NULL) unlink(v, recursive = TRUE)v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) use_vault(v) status() options(pensar.vault = NULL) unlink(v, recursive = TRUE)
Auto-commit and push for pensar vaults that are git repos. Commit vault changes to git
No-op if the vault is not a git repo or if there are no changes.
Stages all changes (respecting .gitignore), commits with the
given message, and optionally pushes to remotes.
Honors the PENSAR_AUTO_PUSH environment variable: if set to
"0" or "false" (case-insensitive), skips the push
step. Otherwise, pushes to every configured remote.
vault_commit(message, vault = default_vault(), push = NULL)vault_commit(message, vault = default_vault(), push = NULL)
message |
Commit message. |
vault |
Path to the vault directory. |
push |
If |
TRUE if a commit was made, FALSE otherwise
(invisibly).
v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) # Returns FALSE invisibly: no .git in this temp vault. vault_commit("noop", vault = v, push = FALSE) unlink(v, recursive = TRUE)v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) # Returns FALSE invisibly: no .git in this temp vault. vault_commit("noop", vault = v, push = FALSE) unlink(v, recursive = TRUE)
Render the vault to a directory of static HTML files. Export the vault to static HTML
Renders every markdown page in the vault to HTML, resolving
[[wikilinks]] to relative anchor tags. Output is a standalone
site that can be served from any static file server or opened via
file://.
No default out_dir: pass an explicit path or set the
PENSAR_SITE_DIR environment variable. Per CRAN policy
pensar will not silently render into a home-filespace location
(e.g., tools::R_user_dir()). PENSAR_SITE_DIR is the
recommended escape hatch – point it at a Syncthing folder so edits
propagate to other devices on export.
Requires the pandoc command-line tool to be available.
vault_export(vault = default_vault(), out_dir = default_site_dir())vault_export(vault = default_vault(), out_dir = default_site_dir())
vault |
Path to the vault directory. |
out_dir |
Destination directory. No default: pass an explicit
path or set |
The output directory path, invisibly.
if (nzchar(Sys.which("pandoc"))) { v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) ingest("Body.", type = "articles", source = "demo", vault = v) vault_export(v, out_dir = tempfile("site-")) unlink(v, recursive = TRUE) }if (nzchar(Sys.which("pandoc"))) { v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) ingest("Body.", type = "articles", source = "demo", vault = v) vault_export(v, out_dir = tempfile("site-")) unlink(v, recursive = TRUE) }
Render the vault's wikilink graph as SVG via saber. Render a vault's wikilink graph as SVG
Scans every markdown page in the vault (excluding control files),
extracts [[wikilinks]] as edges, and renders the result via
saber::graph_svg(). Node tooltips carry the page type, tags,
and date from YAML frontmatter; broken wikilinks (targets with no
matching page) appear as external nodes with a distinct tooltip.
vault_graph(vault = default_vault(), width = 1600L, height = 1200L, ...)vault_graph(vault = default_vault(), width = 1600L, height = 1200L, ...)
vault |
Path to the vault directory. |
... |
Passed through to |
width, height
|
Viewport in pixels. Defaults (1600 x 1200) are
larger than |
Character vector of SVG lines. Write with writeLines().
## Not run: # Requires a version of 'saber' that exports graph_svg(). v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) ingest("Cites [[other]].", type = "articles", source = "demo", vault = v) svg <- vault_graph(v) writeLines(svg, tempfile(fileext = ".svg")) ## End(Not run)## Not run: # Requires a version of 'saber' that exports graph_svg(). v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) ingest("Cites [[other]].", type = "articles", source = "demo", vault = v) svg <- vault_graph(v) writeLines(svg, tempfile(fileext = ".svg")) ## End(Not run)
Build a structured index of every page in a vault for link resolution, frontmatter querying, and downstream features (retrieval primitives, dedup audits, manifest sync). Build a structured registry of a vault's pages
Scans every .md file in vault and returns a data.frame
with one row per page. Used internally to resolve wikilinks and
query frontmatter without re-scanning the filesystem on every call.
Identity columns:
node_id is the current link-resolution identity
(path-aware basename via name_from_path()). This is what
[[page]] matches against.
page_uid is a stable identity sourced from frontmatter
id: or address:. NA if the page declares
neither. Stable identity is opt-in via frontmatter; pensar never
fabricates one from a path hash, because path hashes change on
rename.
Cache levels:
"session" (default): memoized in a package-level
environment, keyed by vault path. No disk write. Invalidates when
any .md file's mtime changes.
"user": persisted to
tools::R_user_dir("pensar", "cache"). CRAN-safe location;
never writes inside the vault itself (.pensar/ is reserved
for vault-owned state).
"none": rebuild on every call.
vault_registry(vault = default_vault(), cache = c("session", "user", "none"), refresh = FALSE)vault_registry(vault = default_vault(), cache = c("session", "user", "none"), refresh = FALSE)
vault |
Vault path. |
cache |
Cache policy: |
refresh |
If |
A data.frame with columns: path, node_id,
page_uid, title, aliases, type,
category, tags, sources, links_out,
system_file. Aliases / tags / links_out are list-columns.
The type and category fields come from frontmatter
verbatim; callers compute an effective type as type
when present, falling back to category otherwise.
v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) ingest("Body cites [[other]].", type = "articles", source = "demo", vault = v) reg <- vault_registry(v) nrow(reg) unlink(v, recursive = TRUE)v <- tempfile("vault-") init_vault(v, rproj = FALSE, agent_instructions = FALSE) ingest("Body cites [[other]].", type = "articles", source = "demo", vault = v) reg <- vault_registry(v) nrow(reg) unlink(v, recursive = TRUE)