Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
*.so
*.dll
misc/
*.Rproj
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## What This Is

saber is a zero-dependency R package for code analysis and project context. It parses R source into symbol indices, traces callers across projects, discovers package metadata, generates project briefings, and inspects installed packages.
saber is a zero-dependency R package for context engineering in R. It assembles agent context from memory and instruction files, traces function call blast radius across projects, generates project briefings, parses R source into structured symbol indices, discovers dependency graphs, and introspects installed packages.

## Working Rules

Expand Down
20 changes: 12 additions & 8 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
Package: saber
Type: Package
Title: Code Analysis and Project Context for R
Title: Context Engineering for R
Version: 0.7.0
Authors@R: person("Troy", "Hernandez", role = c("aut", "cre"),
email = "troy@cornball.ai",
comment = c(ORCID = "0009-0005-4248-604X"))
Description: Parses R source files into Abstract Syntax Tree (AST) symbol indices, traces function
callers across projects, discovers project dependency graphs, generates
project briefings, and provides package introspection tools. Designed
for AI coding agents that need structured code understanding.
Authors@R: c(
person("Troy", "Hernandez", role = c("aut", "cre"),
email = "troy@cornball.ai",
comment = c(ORCID = "0009-0005-4248-604X")),
person("cornball.ai", role = "cph"))
Description: Context-engineering primitives for 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 structured AST symbol indices,
discovers dependency graphs, and introspects installed packages.
Zero dependencies.
License: Apache License (>= 2)
URL: https://github.com/cornball-ai/saber
BugReports: https://github.com/cornball-ai/saber/issues
Expand Down
5 changes: 3 additions & 2 deletions R/agent_context.R
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#' @title Agent context loading
#' @description Load standard context files for AI coding agents.
#' @title Agent context assembly
#' @description Assemble context from memory, instructions, and identity files
#' for AI coding agents.

#' Load context files for an AI coding agent
#'
Expand Down
5 changes: 3 additions & 2 deletions R/fn_graph.R
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#' @title Function call graph
#' @description Render a package's internal function call graph.
#' @title Code intelligence — function call graph
#' @description Render a project's internal function call graph as interactive
#' SVG.

#' Render a function call graph for an R project
#'
Expand Down
2 changes: 1 addition & 1 deletion R/graph_svg.R
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#' @title Force-directed graph rendering
#' @description Render a graph as static SVG using a base R
#' @description Render a graph as static, interactive SVG using a base R
#' Fruchterman-Reingold force simulation.

#' Render a graph as static SVG
Expand Down
3 changes: 2 additions & 1 deletion R/pkg.R
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#' @title Package introspection
#' @description Query installed R packages for exports, internals, and help.
#' @description Query installed R packages for exported functions, internal
#' functions, and help documentation.

#' List exported functions of a package
#'
Expand Down
5 changes: 3 additions & 2 deletions R/pkg_graph.R
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#' @title Package dependency graph
#' @description Render the dependency graph across a set of R packages.
#' @title Project discovery — package dependency graph
#' @description Render the dependency graph across a set of R packages as
#' interactive SVG.

#' Render a package-level dependency graph
#'
Expand Down
3 changes: 2 additions & 1 deletion R/projects.R
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#' @title Project discovery
#' @description Discover R package projects and their dependency relationships.
#' @description Discover R package projects and map their dependency
#' relationships.

#' Discover R package projects
#'
Expand Down
5 changes: 3 additions & 2 deletions R/symbols.R
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#' @title AST symbol index
#' @description Parse R source files to extract function definitions and calls.
#' @title Code intelligence — AST symbol index
#' @description Parse R source files into structured function definitions and
#' call relationships.
#' @importFrom utils getParseData

#' Build a symbol index for a project
Expand Down
97 changes: 69 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# saber

Code analysis and project context for R.
**Context engineering for R.**

saber ("to know" in Spanish, pronounced [sah-BEHR](https://www.youtube.com/watch?v=m3WBocsw9lw)) parses R source into structured symbol indices, traces function callers across projects, discovers dependency graphs, generates project briefings, and cracks open installed packages for introspection. Built for AI coding agents that need to understand R code without guessing.
saber ("to know" in Spanish, pronounced [sah-BEHR](https://www.youtube.com/watch?v=m3WBocsw9lw)) assembles agent context, traces blast radius across projects, and introspects packages so AI coding agents don't have to guess.

## Install

Expand All @@ -14,22 +14,70 @@ remotes::install_github("cornball-ai/saber")

## What it does

**9 exported functions.**
**13 exported functions.**

### Agent context

| Function | What it does |
|---|---|
| `symbols()` | Parse R source files into function defs and calls via `getParseData()` |
| `agent_context()` | Assemble memory, identity, and instruction files for an agent |
| `briefing()` | Generate a project briefing (metadata, dependents, git log) |

### Code intelligence

| Function | What it does |
|---|---|
| `symbols()` | Parse R source into function defs and calls via `getParseData()` |
| `blast_radius()` | Find every caller of a function, across projects |
| `find_downstream()` | Find all projects that depend on a given package |
| `fn_graph()` | Render a project's internal function call graph as SVG |
| `pkg_graph()` | Render a package dependency graph as SVG |
| `graph_svg()` | Force-directed graph renderer (used by `fn_graph` and `pkg_graph`) |

### Project discovery

| Function | What it does |
|---|---|
| `projects()` | Discover R package projects and their metadata |
| `briefing()` | Generate a project context briefing (metadata, dependents, memory, git log) |
| `find_downstream()` | Find all projects that depend on a given package |
| `default_exclude()` | Default directories to skip when scanning |

### Package introspection

| Function | What it does |
|---|---|
| `pkg_exports()` | List exported functions with argument signatures |
| `pkg_internals()` | List internal (non-exported) functions |
| `pkg_help()` | Pull help documentation as markdown |
| `default_exclude()` | Default directories to skip when scanning |

## Examples

Assemble agent context from project and workspace files:

```r
# Claude Code agent in current project
saber::agent_context(agent = "claude")

# Codex agent with workspace identity
saber::agent_context(agent = "codex", workspace_dir = "~/.codex/workspace")
```

Generate a project briefing:

```r
saber::briefing("saber")
#> # Briefing: saber
#> _Generated 2026-03-25 00:30_
#>
#> ## Package
#> - **Name**: saber
#> - **Title**: Context Engineering for R
#> - **Version**: 0.7.0
#>
#> ## Recent commits
#> - 7983478 Add r-ci GitHub Actions workflow
#> - ...
```

Index all function definitions and calls in a project:

```r
Expand All @@ -52,46 +100,38 @@ Discover projects and their dependencies:
```r
saber::projects()
#> package title version path depends imports
#> saber Code Analysis for R 0.2.0 /home/troy/saber ...
#> saber Context Engineering 0.7.0 /home/troy/saber ...

saber::find_downstream("jsonlite")
#> [1] "chatterbox" "cornfab" "diffuseR" "llamaR" "llm.api"
#> [6] "safetensors" "stt.api" "torch" "tts.api" "tuber" "whisper"
```

Generate a project briefing for an AI agent:
Inspect any installed package:

```r
saber::briefing("saber")
#> # Briefing: saber
#> _Generated 2026-03-25 00:30_
#>
#> ## Package
#> - **Name**: saber
#> - **Title**: Code Analysis and Project Context for R
#> - **Version**: 0.2.0
#>
#> ## Recent commits
#> - 7983478 Add r-ci GitHub Actions workflow
#> - ...
saber::pkg_exports("saber")
saber::pkg_help("symbols", "saber")
```

Inspect any installed package:
Render a call graph:

```r
saber::pkg_exports("saber")
saber::pkg_help("symbols", "saber")
svg <- saber::fn_graph("~/myproject")
writeLines(svg, "~/callgraph.svg")
```

## How it works

`symbols()` runs `getParseData()` on every `R/*.R` file in a project, extracts function definitions and call sites, and caches the results as RDS in the user cache directory. Cache invalidates on file content changes (MD5).
`agent_context()` loads standard context files for AI coding agents: project instructions (AGENTS.md / CLAUDE.md), Claude Code memory files, global instructions, and agent identity files (SOUL.md). It skips files the agent already autoloads to avoid duplication.

`briefing()` assembles project context from DESCRIPTION metadata, downstream dependents, and recent git commits. It writes the markdown to the user cache directory so both the agent and user see the same context.

`blast_radius()` builds on top of `symbols()`. It finds internal callers, then scans `~/` for any project whose DESCRIPTION declares a dependency on the target package. Traces the call graph across all of them.
`symbols()` runs `getParseData()` on every `R/*.R` file in a project, extracts function definitions and call sites, and caches the results as RDS. Cache invalidates on file content changes (MD5).

`projects()` scans for directories containing DESCRIPTION files and reads their metadata. `find_downstream()` does the same scan but filters to projects that depend on a specific package.
`blast_radius()` builds on `symbols()`. It finds internal callers, then scans `~/` for any project whose DESCRIPTION declares a dependency on the target package. Traces the call graph across all of them. With `include = c("r", "examples", "vignettes")` it also flags references in roxygen `@examples` blocks and vignette code chunks.

`briefing()` assembles project context from DESCRIPTION metadata, downstream dependents, Claude Code memory files, and recent git commits. It prints the briefing to stdout, returns the same text invisibly, and writes the markdown to the user cache directory so both the agent and user see the same context. An `agent` parameter controls memory inclusion: `agent = "claude"` skips Claude Code memory (since Claude Code autoloads it), while other values include it.
`fn_graph()` and `pkg_graph()` render force-directed SVG graphs via a base R Fruchterman-Reingold simulation. No JavaScript — tooltips and links work via native SVG features.

## Codex integration

Expand Down Expand Up @@ -159,6 +199,7 @@ project briefing. Set `AGENTS_GLOBAL_MD` if you want a different path.
Every new Codex session starts with the project's metadata, downstream dependents, Claude Code memory (if available), recent git commits, and optional global preferences already in context.

## Claude Code integration

Add the following to your `~/.claude/CLAUDE.md` to teach Claude Code how to use saber:

```markdown
Expand Down
5 changes: 3 additions & 2 deletions man/agent_context.Rd
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
% tinyrox says don't edit this manually, but it can't stop you!
\name{agent_context}
\alias{agent_context}
\title{Agent context loading}
\title{Agent context assembly}
\usage{
agent_context(agent = NULL, project_dir = getwd(), workspace_dir = NULL,
memory_base = file.path(path.expand("~"), ".claude", "projects"),
Expand Down Expand Up @@ -43,7 +43,8 @@ Character string of assembled context, or empty string if no
context applies.
}
\description{
Load standard context files for AI coding agents.
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
Expand Down
5 changes: 3 additions & 2 deletions man/fn_graph.Rd
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
% tinyrox says don't edit this manually, but it can't stop you!
\name{fn_graph}
\alias{fn_graph}
\title{Function call graph}
\title{Code intelligence — function call graph}
\usage{
fn_graph(project_dir, include_external = FALSE, ...)
}
Expand All @@ -18,7 +18,8 @@ functions called from other packages. Default \code{FALSE}.}
Character vector of SVG lines. Write with \code{writeLines()}.
}
\description{
Render a package's internal function call graph.
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 \code{\link{symbols}} and renders
Expand Down
2 changes: 1 addition & 1 deletion man/graph_svg.Rd
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Character vector, one SVG element per line. Write with
\code{writeLines()}.
}
\description{
Render a graph as static SVG using a base R
Render a graph as static, interactive SVG using a base R
Fruchterman-Reingold force simulation.
Render a graph as static SVG

Expand Down
3 changes: 2 additions & 1 deletion man/pkg_exports.Rd
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ pkg_exports(package, pattern = NULL)
A data.frame with columns: name, args.
}
\description{
Query installed R packages for exports, internals, and help.
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.
Expand Down
5 changes: 3 additions & 2 deletions man/pkg_graph.Rd
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
% tinyrox says don't edit this manually, but it can't stop you!
\name{pkg_graph}
\alias{pkg_graph}
\title{Package dependency graph}
\title{Project discovery — package dependency graph}
\usage{
pkg_graph(scan_dir = path.expand("~"), packages = NULL,
include_suggests = FALSE, ...)
Expand All @@ -22,7 +22,8 @@ packages in each project's \code{Suggests} field. Default
Character vector of SVG lines. Write with \code{writeLines()}.
}
\description{
Render the dependency graph across a set of R packages.
Render the dependency graph across a set of R packages as
interactive SVG.
Render a package-level dependency graph

Discovers R packages under \code{scan_dir} via \code{\link{projects}},
Expand Down
3 changes: 2 additions & 1 deletion man/projects.Rd
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ A data.frame with columns: package, title, version, path, depends,
imports.
}
\description{
Discover R package projects and their dependency relationships.
Discover R package projects and map their dependency
relationships.
Discover R package projects

Scans a directory for subdirectories containing a DESCRIPTION file and
Expand Down
5 changes: 3 additions & 2 deletions man/symbols.Rd
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
% tinyrox says don't edit this manually, but it can't stop you!
\name{symbols}
\alias{symbols}
\title{AST symbol index}
\title{Code intelligence — AST symbol index}
\usage{
symbols(project_dir,
cache_dir = file.path(tools::R_user_dir("saber", "cache"), "symbols"))
Expand All @@ -19,7 +19,8 @@ A list with components:
}
}
\description{
Parse R source files to extract function definitions and calls.
Parse R source files into structured function definitions and
call relationships.
}
\examples{
# Create a minimal project with R source files
Expand Down