name: r-lib-package-dev license: CC-BY-4.0 description: >- Orchestrates the full R package development lifecycle: project creation, directory structure, devtools workflow, documentation with roxygen2, code formatting with air, NEWS.md conventions, and CRAN readiness. Load this skill when the user is building, maintaining, or improving an R package. For deep-dive topics, this skill delegates to specialized siblings: r-lib-testing (testthat patterns), r-lib-cli (user-facing messages), r-lib-lifecycle (deprecation/versioning), and r-lib-cran-extrachecks (CRAN submission checklist). metadata: repo: https://github.com/nq-rdl/agent-skills
R Package Development -- Orchestration Guide
This skill covers end-to-end R package development using devtools, roxygen2, and modern tidyverse tooling. It is the starting point for any R package work. When a task falls squarely into a specialist domain, delegate to the appropriate sibling skill rather than duplicating guidance here.
When to Load This Skill vs a Specialist
| Situation | Skill to load |
|---|---|
| Creating a new package, adding files, general devtools workflow | r-lib-package-dev (this skill) |
| Writing, organizing, or debugging testthat tests | r-lib-testing |
| Formatting console output, errors, warnings, progress bars with cli | r-lib-cli |
| Deprecating, superseding, or marking functions experimental | r-lib-lifecycle |
| Preparing a package for CRAN submission or responding to reviewer feedback | r-lib-cran-extrachecks |
| General R language questions, base R idioms, vectorization | r-expert |
If a task spans multiple domains (e.g., "add a function, test it, and prepare for CRAN"), start here and load additional skills as needed.
Standard R Package Structure
A well-formed R package follows this layout:
pkg/
+-- DESCRIPTION # Package metadata, dependencies, version
+-- NAMESPACE # Export/import declarations (generated by roxygen2)
+-- LICENSE / LICENSE.md
+-- NEWS.md # User-facing changelog
+-- README.md / README.Rmd
+-- R/ # All R source files
| +-- pkg-package.R # Package-level documentation (@keywords internal)
| +-- feature.R # One file per logical unit
+-- man/ # Generated .Rd help files (never edit by hand)
+-- tests/
| +-- testthat.R # Test runner bootstrap
| +-- testthat/ # Test files: test-*.R
+-- vignettes/ # Long-form documentation (.Rmd)
+-- inst/ # Arbitrary files installed with the package
+-- src/ # Compiled code (C, C++, Rust)
+-- data/ # Exported datasets (.rda)
+-- data-raw/ # Scripts that produce exported datasets
+-- _pkgdown.yml # pkgdown site configuration
+-- .Rbuildignore # Patterns excluded from R CMD build
+-- .Rprofile # Project-level R startup (source("renv/activate.R"), etc.)
Key conventions:
- One logical unit per file in
R/. The file name should match the primary function it defines (e.g.,R/fit_model.Rcontainsfit_model()). man/is entirely generated. Never edit.Rdfiles directly; edit the roxygen2 comments inR/and rundevtools::document().tests/testthat/test-*.Rfiles mirrorR/*.Rfiles by name.NAMESPACEis generated by roxygen2@export/@importtags.
Creating a New Package
# Create the skeleton
usethis::create_package("path/to/pkgname")
# Add essential infrastructure
usethis::use_testthat(3) # testthat 3rd edition
usethis::use_mit_license() # or use_gpl3_license(), etc.
usethis::use_readme_rmd() # README with badges
usethis::use_news_md() # NEWS.md changelog
usethis::use_pkgdown() # pkgdown documentation site
usethis::use_github_actions() # CI via GitHub Actions
After creation, open the project and verify the directory structure matches the layout above.
Core devtools Commands
These are the commands you will run most often during development. Execute them
via Rscript -e "..." when working from the terminal.
# Load package code into the session (like library() but from source)
Rscript -e "devtools::load_all()"
# Run an expression using the dev version of the package
Rscript -e "devtools::load_all(); my_function(x)"
# Regenerate documentation (man/ and NAMESPACE) from roxygen2 comments
Rscript -e "devtools::document()"
# Run the full test suite
Rscript -e "devtools::test()"
# Run tests for files whose name matches a pattern
Rscript -e "devtools::test(filter = '^fit_model')"
# Run tests for a specific source file
Rscript -e "devtools::test_active_file('R/fit_model.R')"
# Run a single named test within a source file
Rscript -e "devtools::test_active_file('R/fit_model.R', desc = 'handles NA input')"
# Full R CMD check (runs tests, examples, vignettes, CRAN checks)
Rscript -e "devtools::check()"
# Verify pkgdown reference index is complete
Rscript -e "pkgdown::check_pkgdown()"
For detailed testing patterns and best practices, load the r-lib-testing skill.
Code Formatting with air
air is the R code formatter from Posit (formerly RStudio). It is analogous to gofmt, black, or prettier -- an opinionated, deterministic formatter that eliminates style debates.
# Format all R files in the current directory tree
air format .
# Format a single file
air format R/fit_model.R
Always run air format . after generating or modifying R code. This ensures
consistent style across the package without manual intervention.
Coding Conventions
- Use the base pipe
|>, not the magrittr pipe%>%. - Use
\(x) x + 1(lambda syntax) for single-expression anonymous functions. For multi-line bodies, usefunction(x) { ... }. - Follow tidyverse style as the baseline.
- Prefer vectorized operations over explicit loops.
- Use
cli_abort(),cli_warn(), andcli_inform()for user-facing conditions rather thanstop(),warning(), andmessage(). For guidance on cli formatting and markup, load the r-lib-cli skill.
Documentation with roxygen2
Exported Functions
Every exported function must have a complete roxygen2 block:
#' Fit a model to the data
#'
#' @param data A data frame containing the training data.
#' @param formula A formula specifying the model.
#'
#' @returns A fitted model object of class `pkg_model`.
#'
#' @export
#' @examples
#' fit_model(mtcars, mpg ~ wt)
fit_model <- function(data, formula) {
# ...
}
Internal Functions
Internal functions (not exported) should still have roxygen2 documentation when
they are non-trivial. CRAN requires that every .Rd file contains an
@returns tag (or @return), including those tagged @keywords internal.
Therefore, any internal function with a roxygen2 block must include @returns:
#' Validate input data
#'
#' @param data A data frame to validate.
#'
#' @returns `data` (invisibly), after validation. Throws an error if
#' validation fails.
#'
#' @keywords internal
validate_data <- function(data) {
# ...
}
Small, truly private helper functions (one or two lines, obvious purpose) do not
need roxygen2 documentation at all. The key rule: if a function has a roxygen2
block, that block must be complete -- including @returns.
General Documentation Rules
- Wrap roxygen2 comments at 80 characters.
- Always re-document after changing any roxygen2 comment:
Rscript -e "devtools::document()". - When you add a new exported documentation topic, also add the topic to
_pkgdown.ymlso the pkgdown reference index stays complete. - Run
pkgdown::check_pkgdown()to verify no topics are missing from the reference index. - Use
@inheritParamsto avoid duplicating parameter documentation across functions that share parameters.
NEWS.md Conventions
NEWS.md is the user-facing changelog. It is shown on CRAN and in pkgdown
sites. Follow these rules:
- Every user-facing change gets a bullet. Do not add bullets for internal refactors, minor documentation fixes, or test-only changes.
- Each bullet briefly describes the change from the user's perspective and
mentions the related issue or PR in parentheses:
(#123). - A bullet can consist of multiple sentences but must be a single line -- do not wrap or break across lines.
- If the change relates to a function, put the function name early in the bullet so it is easy to scan.
- Order bullets alphabetically by function name. Place bullets that do not mention a specific function at the top of the section.
Example:
# pkgname (development version)
* `fit_model()` now supports formula interface in addition to x/y syntax (#42).
* `predict()` method gains a `type` argument for class probabilities (#55).
* Fixed an issue where package failed to load on R 4.2 (#61).
When preparing a release, the heading changes from (development version) to
the version number and date, e.g., # pkgname 1.2.0 (2026-04-04).
DESCRIPTION File Essentials
Package: pkgname
Title: One-Line Title in Title Case (No Period)
Version: 0.1.0
Authors@R: person("First", "Last", email = "email@example.com",
role = c("aut", "cre"))
Description: A paragraph describing what the package does. Must be at
least one complete sentence. Indent continuation lines by four spaces.
License: MIT + file LICENSE
URL: https://github.com/org/pkgname, https://org.github.io/pkgname
BugReports: https://github.com/org/pkgname/issues
Encoding: UTF-8
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.3.2
Config/testthat/edition: 3
Key fields:
- Imports vs Suggests: Use
Importsfor packages required at runtime. UseSuggestsfor packages only needed in tests, vignettes, or examples. Add dependencies withusethis::use_package("dplyr")(Imports) orusethis::use_package("dplyr", type = "Suggests"). - Roxygen: Set
list(markdown = TRUE)to enable markdown syntax in roxygen2 comments. - Config/testthat/edition: Should be
3for modern testthat.
Development Cycle Quick Reference
This is the typical edit-test-document loop during active development:
1. EDIT Write or modify code in R/
|
2. FORMAT air format .
|
3. LOAD Rscript -e "devtools::load_all()"
Interactively test your changes
|
4. TEST Rscript -e "devtools::test()"
Fix any failures
|
5. DOCUMENT Rscript -e "devtools::document()"
Rscript -e "pkgdown::check_pkgdown()"
|
6. CHECK Rscript -e "devtools::check()"
Fix any NOTEs, WARNINGs, ERRORs
|
7. COMMIT Update NEWS.md if user-facing change
Commit with a descriptive message
Before releasing to CRAN, load the r-lib-cran-extrachecks skill and work through its full checklist.
For managing deprecations or lifecycle changes during a release, load the r-lib-lifecycle skill.
Adding Common Infrastructure
# Add a dependency
usethis::use_package("rlang")
usethis::use_package("withr", type = "Suggests")
# Create a new R file + matching test file
usethis::use_r("feature_name")
usethis::use_test("feature_name")
# Add a vignette
usethis::use_vignette("getting-started")
# Add package-level documentation
usethis::use_package_doc()
# Add a dataset
usethis::use_data_raw("dataset_name")
# Set up lifecycle infrastructure
usethis::use_lifecycle()
# Add GitHub Actions CI
usethis::use_github_action("check-standard")
Delegation Summary
This skill owns the overall workflow and conventions. Delegate to specialist skills for depth:
- r-lib-testing: Test organization, expectations, fixtures, snapshots,
mocking,
withrcleanup, testthat 3 edition patterns. - r-lib-cli:
cli_abort()/cli_warn()/cli_inform()formatting, inline markup, progress bars, themes, ANSI handling. - r-lib-lifecycle: Deprecation, superseding, experimental badges,
lifecycle::deprecate_warn(), renaming helpers. - r-lib-cran-extrachecks: CRAN submission checklist, reviewer feedback
responses, documentation requirements beyond
devtools::check(). - r-expert: Base R idioms, vectorization, performance, S3/S4/R6 class design, error handling patterns.