| Title: | Context Engineering for Large Language Model Agents |
|---|---|
| Description: | Context-engineering primitives for Artificial Intelligence (AI) coding agents working in R. Assembles agent context from memory and instruction files ('AGENTS.md', 'CLAUDE.md'), traces function call blast radius across projects, generates project briefings, parses source into Abstract Syntax Tree (AST) symbol indices, discovers dependency graphs, and introspects installed packages. Zero dependencies. |
| 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.7.2.2 |
| Built: | 2026-07-20 20:57:18 UTC |
| Source: | https://github.com/cornball-ai/saber |
Returns assembled context (memory, project/global instructions, agent identity files) tailored to a specific consumer. Files autoloaded by the agent natively are skipped to avoid duplication.
agent_context(agent = NULL, project_dir = getwd(), workspace_dir = NULL, memory_base = file.path(path.expand("~"), ".claude", "projects"), claude_global_path = file.path(path.expand("~"), ".claude", "CLAUDE.md"), include_memory = NULL, include_project = NULL, include_global = NULL, include_soul = NULL, max_memory_lines = 100L)agent_context(agent = NULL, project_dir = getwd(), workspace_dir = NULL, memory_base = file.path(path.expand("~"), ".claude", "projects"), claude_global_path = file.path(path.expand("~"), ".claude", "CLAUDE.md"), include_memory = NULL, include_project = NULL, include_global = NULL, include_soul = NULL, max_memory_lines = 100L)
agent |
Consumer identifier: |
project_dir |
Project directory to scan for CLAUDE.md / AGENTS.md. |
workspace_dir |
Optional directory containing SOUL.md and USER.md
(e.g. |
memory_base |
Base directory for 'Claude Code' project memory files. |
claude_global_path |
Path to the global 'Claude Code' instructions
file. Defaults to |
include_memory |
Override default for memory inclusion. Use
|
include_project |
Override default for project instructions (CLAUDE.md / AGENTS.md). |
include_global |
Override default for global instructions (~/.claude/CLAUDE.md / USER.md). |
include_soul |
Override default for SOUL.md inclusion. |
max_memory_lines |
Maximum lines to include from each memory source. |
Defaults per agent:
"claude" - skips Claude Code project memory and CLAUDE.md
files (autoloaded by 'Claude Code'). Loads Codex memories, AGENTS.md,
USER.md, and SOUL.md when present.
"codex" - skips AGENTS.md and Codex memories (autoloaded by
Codex). Loads Claude Code project memory, CLAUDE.md, USER.md, and
SOUL.md when present.
"corteza" or NULL - loads everything available.
Project and global instructions are resolved by trying both naming conventions and picking the file relevant to the consumer:
Project: CLAUDE.md or AGENTS.md
Global: ~/.claude/CLAUDE.md or
<workspace_dir>/USER.md
Override the defaults with the include_* parameters.
Character string of assembled context, or empty string if no context applies.
# Codex agent in current project saber::agent_context(agent = "codex") # Corteza with workspace files saber::agent_context(agent = "corteza", workspace_dir = "~/.corteza/workspace") # Force-include memory regardless of agent default saber::agent_context(agent = "claude", include_memory = TRUE)# Codex agent in current project saber::agent_context(agent = "codex") # Corteza with workspace files saber::agent_context(agent = "corteza", workspace_dir = "~/.corteza/workspace") # Force-include memory regardless of agent default saber::agent_context(agent = "claude", include_memory = TRUE)
Given a function name and project, finds all internal callers within that project and all callers in downstream projects (projects whose DESCRIPTION lists this one in Depends, Imports, or LinkingTo).
blast_radius(fn, project = NULL, include = "r", scan_dir = path.expand("~"), cache_dir = file.path(tools::R_user_dir("saber", "cache"), "symbols"), exclude = default_exclude())blast_radius(fn, project = NULL, include = "r", scan_dir = path.expand("~"), cache_dir = file.path(tools::R_user_dir("saber", "cache"), "symbols"), exclude = default_exclude())
fn |
Character. Function name to search for. |
project |
Character. Project name (or path to project directory). |
include |
Character vector. Any of |
scan_dir |
Directory to scan for downstream projects. |
cache_dir |
Directory for symbol cache files. |
exclude |
Character vector of directory basenames to skip when scanning for downstream projects. |
With include = c("r", "examples", "vignettes") the search can be
extended to references in the target project's roxygen @examples
blocks and vignette code chunks (Rmd, qmd, Rnw). Documentation scanning is
target-project only; it does not walk downstream projects' docs.
A data.frame with columns: caller, project, file, line, source.
source is one of "r", "example", "vignette".
# Create a minimal project d <- file.path(tempdir(), "blastpkg") dir.create(file.path(d, "R"), recursive = TRUE, showWarnings = FALSE) writeLines("helper <- function(x) x + 1", file.path(d, "R", "helper.R")) writeLines("main <- function(x) helper(x * 2)", file.path(d, "R", "main.R")) # Find all callers of helper() blast_radius("helper", project = d, scan_dir = tempdir(), cache_dir = tempdir()) # Include roxygen @examples and vignettes from the target project blast_radius("helper", project = d, include = c("r", "examples", "vignettes"), scan_dir = tempdir(), cache_dir = tempdir())# Create a minimal project d <- file.path(tempdir(), "blastpkg") dir.create(file.path(d, "R"), recursive = TRUE, showWarnings = FALSE) writeLines("helper <- function(x) x + 1", file.path(d, "R", "helper.R")) writeLines("main <- function(x) helper(x * 2)", file.path(d, "R", "main.R")) # Find all callers of helper() blast_radius("helper", project = d, scan_dir = tempdir(), cache_dir = tempdir()) # Include roxygen @examples and vignettes from the target project blast_radius("helper", project = d, include = c("r", "examples", "vignettes"), scan_dir = tempdir(), cache_dir = tempdir())
Produces a concise markdown briefing combining DESCRIPTION metadata, downstream dependents, and recent git commits. Written to the user cache directory so both the agent and user see the same context.
briefing(project = NULL, scan_dir = path.expand("~"), briefs_dir = file.path(tools::R_user_dir("saber", "cache"), "briefs"))briefing(project = NULL, scan_dir = path.expand("~"), briefs_dir = file.path(tools::R_user_dir("saber", "cache"), "briefs"))
project |
Project name. If NULL, inferred from the current working directory basename. |
scan_dir |
Directory to scan for project directories. |
briefs_dir |
Directory to write briefing markdown files. |
For runtime context (memory, identity files, project instructions),
see agent_context.
The briefing text (character string), returned invisibly. Emitted
via message() and written to briefs_dir/{project}.md.
d <- file.path(tempdir(), "briefpkg") dir.create(file.path(d, "R"), recursive = TRUE, showWarnings = FALSE) writeLines(c("Package: briefpkg", "Title: Demo", "Version: 0.1.0"), file.path(d, "DESCRIPTION")) briefing("briefpkg", scan_dir = tempdir(), briefs_dir = file.path(tempdir(), "briefs"))d <- file.path(tempdir(), "briefpkg") dir.create(file.path(d, "R"), recursive = TRUE, showWarnings = FALSE) writeLines(c("Package: briefpkg", "Title: Demo", "Version: 0.1.0"), file.path(d, "DESCRIPTION")) briefing("briefpkg", scan_dir = tempdir(), briefs_dir = file.path(tempdir(), "briefs"))
Returns a character vector of directory basenames that are skipped
when scanning for downstream projects. Override by passing a custom
exclude vector to blast_radius.
default_exclude()default_exclude()
Character vector of directory basenames.
default_exclude()default_exclude()
Scans DESCRIPTION files in project directories under scan_dir
for Depends, Imports, or LinkingTo fields that reference package.
find_downstream(package, scan_dir = path.expand("~"), exclude = default_exclude())find_downstream(package, scan_dir = path.expand("~"), exclude = default_exclude())
package |
Character. Package name to search for. |
scan_dir |
Directory to scan for project directories. |
exclude |
Character vector of directory basenames to skip. |
Character vector of project names that depend on package.
d <- file.path(tempdir(), "dsdir") dir.create(d, showWarnings = FALSE) pkg <- file.path(d, "child") dir.create(pkg, showWarnings = FALSE) writeLines(c("Package: child", "Version: 0.1.0", "Imports: parent"), file.path(pkg, "DESCRIPTION")) find_downstream("parent", scan_dir = d)d <- file.path(tempdir(), "dsdir") dir.create(d, showWarnings = FALSE) pkg <- file.path(d, "child") dir.create(pkg, showWarnings = FALSE) writeLines(c("Package: child", "Version: 0.1.0", "Imports: parent"), file.path(pkg, "DESCRIPTION")) find_downstream("parent", scan_dir = d)
Pulls the AST symbol index via symbols and renders
internal function-to-function call edges as SVG. External calls
(into other packages) are dropped by default.
fn_graph(project_dir, include_external = FALSE, ..., cache_dir = file.path(tools::R_user_dir("saber", "cache"), "symbols"))fn_graph(project_dir, include_external = FALSE, ..., cache_dir = file.path(tools::R_user_dir("saber", "cache"), "symbols"))
project_dir |
Path to the project directory (an R package root). |
include_external |
If |
... |
Passed through to |
cache_dir |
Directory for the underlying |
Character vector of SVG lines. Write with writeLines().
d <- file.path(tempdir(), "fngdemo") dir.create(file.path(d, "R"), recursive = TRUE, showWarnings = FALSE) writeLines(c("Package: demo", "Version: 0.1.0"), file.path(d, "DESCRIPTION")) writeLines("add <- function(x, y) x + y", file.path(d, "R", "add.R")) writeLines("double <- function(x) add(x, x)", file.path(d, "R", "double.R")) svg <- fn_graph(d, cache_dir = tempdir()) writeLines(svg, tempfile(fileext = ".svg"))d <- file.path(tempdir(), "fngdemo") dir.create(file.path(d, "R"), recursive = TRUE, showWarnings = FALSE) writeLines(c("Package: demo", "Version: 0.1.0"), file.path(d, "DESCRIPTION")) writeLines("add <- function(x, y) x + y", file.path(d, "R", "add.R")) writeLines("double <- function(x) add(x, x)", file.path(d, "R", "double.R")) svg <- fn_graph(d, cache_dir = tempdir()) writeLines(svg, tempfile(fileext = ".svg"))
Returns the number of commits on HEAD dated on or after
since_date, and 0L for anything that is not a git
working tree (plain directories, missing paths). Worktrees and
dubious-ownership repositories are handled: a git rev-parse
check runs first, and git's non-zero exits never leak as warnings.
The counting primitive behind heartbeat, exported for
downstream tooling that composes its own activity reports.
git_commit_count_since(repo_dir, since_date)git_commit_count_since(repo_dir, since_date)
repo_dir |
Path to a repository. |
since_date |
A |
Integer commit count.
# A plain directory is not a repository: counts as zero git_commit_count_since(tempdir(), Sys.Date() - 7)# A plain directory is not a repository: counts as zero git_commit_count_since(tempdir(), Sys.Date() - 7)
Returns up to n commit lines from HEAD, newest first.
With format = "oneline" each line is git's --oneline
(short hash and subject); with format = "iso" each line is
tab-separated ISO-local timestamp, short hash, and subject. A
NULL since_date means no lower bound. Anything that is
not a git working tree returns character(0). The log
primitive behind heartbeat, exported for downstream
tooling that composes its own activity reports.
git_log_since(repo_dir, since_date = NULL, n = 5L, format = c("oneline", "iso"))git_log_since(repo_dir, since_date = NULL, n = 5L, format = c("oneline", "iso"))
repo_dir |
Path to a repository. |
since_date |
A |
n |
Integer. Maximum commits to return. |
format |
|
Character vector of commit lines (possibly empty).
# A plain directory is not a repository: empty log git_log_since(tempdir(), Sys.Date() - 7)# A plain directory is not a repository: empty log git_log_since(tempdir(), Sys.Date() - 7)
Runs a Fruchterman-Reingold force simulation to lay out nodes, then
emits SVG with baked coordinates. Output is interactive via native
browser features: hover tooltips from <title> elements, click
navigation from <a xlink:href> wrappers, CSS :hover
highlighting. No JavaScript.
graph_svg(edges, nodes = NULL, width = 1200L, height = 900L, iterations = 50L, seed = 1L)graph_svg(edges, nodes = NULL, width = 1200L, height = 900L, iterations = 50L, seed = 1L)
edges |
Data frame with |
nodes |
Optional data frame with |
width |
SVG viewport width in pixels. |
height |
SVG viewport height in pixels. |
iterations |
Force-simulation steps. 50 is usually enough. |
seed |
Integer seed for the random initial layout (output is deterministic given the same seed). |
Suitable for graphs up to roughly a few hundred nodes. The repulsion
step is vectorized via outer() but allocates an
n x n matrix per iteration; larger graphs should pre-filter.
Character vector, one SVG element per line. Write with
writeLines().
edges <- data.frame(from = c("a", "a", "b"), to = c("b", "c", "c")) svg <- graph_svg(edges) writeLines(svg, tempfile(fileext = ".svg"))edges <- data.frame(from = c("a", "a", "b"), to = c("b", "c", "c")) svg <- graph_svg(edges) writeLines(svg, tempfile(fileext = ".svg"))
Scans every git repository directly under scan_dir, counts
commits inside the lookback window, and produces a markdown summary of
the active projects with their most recent commits, busiest first. The
complement to briefing: briefing() is one project
in depth, heartbeat() is every project at a glance.
heartbeat(days = 7L, scan_dir = path.expand("~"), n = 5L, exclude = default_exclude(), briefs_dir = file.path(tools::R_user_dir("saber", "cache"), "briefs"))heartbeat(days = 7L, scan_dir = path.expand("~"), n = 5L, exclude = default_exclude(), briefs_dir = file.path(tools::R_user_dir("saber", "cache"), "briefs"))
days |
Integer. Lookback window in days. |
scan_dir |
Directory whose immediate subdirectories are scanned. |
n |
Integer. Maximum commits to list per active project. |
exclude |
Character vector of directory basenames to skip. |
briefs_dir |
Directory to write the heartbeat markdown file. |
The heartbeat text (character string), returned invisibly.
Emitted via message() and written to
briefs_dir/_heartbeat.md.
root <- file.path(tempdir(), "hbscan") dir.create(root, showWarnings = FALSE) heartbeat(days = 7, scan_dir = root, briefs_dir = file.path(tempdir(), "briefs"))root <- file.path(tempdir(), "hbscan") dir.create(root, showWarnings = FALSE) heartbeat(days = 7, scan_dir = root, briefs_dir = file.path(tempdir(), "briefs"))
Returns a data.frame of exported functions with their argument signatures.
pkg_exports(package, pattern = NULL)pkg_exports(package, pattern = NULL)
package |
Character. Package name. |
pattern |
Optional regex to filter function names. |
A data.frame with columns: name, args.
pkg_exports("tools") pkg_exports("tools", pattern = "^Rd")pkg_exports("tools") pkg_exports("tools", pattern = "^Rd")
Discovers R packages under scan_dir via projects,
parses each one's Depends and Imports fields, and
renders edges between packages that both live in scan_dir.
External CRAN dependencies are dropped.
pkg_graph(scan_dir = path.expand("~"), packages = NULL, include_suggests = FALSE, ...)pkg_graph(scan_dir = path.expand("~"), packages = NULL, include_suggests = FALSE, ...)
scan_dir |
Directory to scan for project directories. |
packages |
Optional character vector limiting the graph to these packages. |
include_suggests |
If |
... |
Passed through to |
Character vector of SVG lines. Write with writeLines().
d <- file.path(tempdir(), "pkgdemo") dir.create(file.path(d, "parent"), recursive = TRUE, showWarnings = FALSE) dir.create(file.path(d, "child"), showWarnings = FALSE) writeLines(c("Package: parent", "Title: P", "Version: 0.1.0"), file.path(d, "parent", "DESCRIPTION")) writeLines(c("Package: child", "Title: C", "Version: 0.1.0", "Imports: parent"), file.path(d, "child", "DESCRIPTION")) svg <- pkg_graph(scan_dir = d) writeLines(svg, tempfile(fileext = ".svg"))d <- file.path(tempdir(), "pkgdemo") dir.create(file.path(d, "parent"), recursive = TRUE, showWarnings = FALSE) dir.create(file.path(d, "child"), showWarnings = FALSE) writeLines(c("Package: parent", "Title: P", "Version: 0.1.0"), file.path(d, "parent", "DESCRIPTION")) writeLines(c("Package: child", "Title: C", "Version: 0.1.0", "Imports: parent"), file.path(d, "child", "DESCRIPTION")) svg <- pkg_graph(scan_dir = d) writeLines(svg, tempfile(fileext = ".svg"))
Extracts help documentation and converts it to clean markdown.
pkg_help(topic, package, format = c("md", "hugo"))pkg_help(topic, package, format = c("md", "hugo"))
topic |
Character. The help topic name. |
package |
Character. Package name. |
format |
Character. Output format: |
Character string of markdown help text.
cat(pkg_help("md5sum", "tools"))cat(pkg_help("md5sum", "tools"))
Returns functions defined in a package namespace but not exported.
pkg_internals(package, pattern = NULL)pkg_internals(package, pattern = NULL)
package |
Character. Package name. |
pattern |
Optional regex to filter function names. |
A data.frame with columns: name, args.
pkg_internals("tools", pattern = "^check")pkg_internals("tools", pattern = "^check")
Scans a directory for subdirectories containing a DESCRIPTION file and returns their metadata.
projects(scan_dir = path.expand("~"), exclude = default_exclude())projects(scan_dir = path.expand("~"), exclude = default_exclude())
scan_dir |
Directory to scan for project directories. |
exclude |
Character vector of directory basenames to skip. |
A data.frame with columns: package, title, version, path, depends, imports.
d <- file.path(tempdir(), "scandir") dir.create(d, showWarnings = FALSE) pkg <- file.path(d, "mypkg") dir.create(pkg, showWarnings = FALSE) writeLines(c("Package: mypkg", "Title: Demo", "Version: 0.1.0"), file.path(pkg, "DESCRIPTION")) projects(scan_dir = d)d <- file.path(tempdir(), "scandir") dir.create(d, showWarnings = FALSE) pkg <- file.path(d, "mypkg") dir.create(pkg, showWarnings = FALSE) writeLines(c("Package: mypkg", "Title: Demo", "Version: 0.1.0"), file.path(pkg, "DESCRIPTION")) projects(scan_dir = d)
Parses all R/*.R files in a project directory using
getParseData() to extract function definitions and function calls.
Results are cached as RDS in the user cache directory.
symbols(project_dir, cache_dir = file.path(tools::R_user_dir("saber", "cache"), "symbols"))symbols(project_dir, cache_dir = file.path(tools::R_user_dir("saber", "cache"), "symbols"))
project_dir |
Path to the project directory. |
cache_dir |
Directory for symbol cache files. |
A list with components:
data.frame(name, file, line, exported)
data.frame(caller, callee, file, line)
# Create a minimal project with R source files d <- file.path(tempdir(), "demopkg") dir.create(file.path(d, "R"), recursive = TRUE, showWarnings = FALSE) writeLines("add <- function(x, y) x + y", file.path(d, "R", "add.R")) writeLines("double <- function(x) add(x, x)", file.path(d, "R", "double.R")) idx <- symbols(d, cache_dir = tempdir()) idx$defs # function definitions idx$calls # call relationships (double calls add)# Create a minimal project with R source files d <- file.path(tempdir(), "demopkg") dir.create(file.path(d, "R"), recursive = TRUE, showWarnings = FALSE) writeLines("add <- function(x, y) x + y", file.path(d, "R", "add.R")) writeLines("double <- function(x) add(x, x)", file.path(d, "R", "double.R")) idx <- symbols(d, cache_dir = tempdir()) idx$defs # function definitions idx$calls # call relationships (double calls add)