| 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.1.1 |
| Built: | 2026-05-17 15:57:26 UTC |
| Source: | https://github.com/cornball-ai/saber |
Assemble context from memory, instructions, and identity files for AI coding agents. Load context files for an AI coding agent
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.
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.
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. |
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)
Find all callers of a function across projects. Find callers of a function across projects
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).
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.
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. |
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())
Generate project context for AI coding agents. Generate a project briefing
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.
For runtime context (memory, identity files, project instructions),
see agent_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. |
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)
Render a project's internal function call graph as interactive SVG. Render a function call graph for an R project
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"))
Render a graph as static, interactive SVG using a base R Fruchterman-Reingold force simulation. Render a graph as static SVG
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.
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.
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). |
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"))
Query installed R packages for exported functions, internal functions, and help documentation. List exported functions of a package
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")
Render the dependency graph across a set of R packages as interactive SVG. Render a package-level dependency graph
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")
Discover R package projects and map their dependency relationships. Discover R package projects
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)
Parse R source files into structured function definitions and call relationships.
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)