---
paths:
  - "R/**/*.R"
  - "tests/**/*.R"
  - "man/**/*.Rd"
  - "vignettes/**"
  - "DESCRIPTION"
  - "NAMESPACE"
  - "NEWS.md"
---

# R Package Development Standards

**Standard:** A CRAN-ready package. `R CMD check --as-cran` returns 0 errors, 0 warnings, and only explained notes.

> **Scope:** This rule covers R **package source** (`R/`, `tests/`, `DESCRIPTION`, `NAMESPACE`, `man/`, vignettes). For one-off **analysis scripts**, simulations, and figure code, see [`r-code-conventions.md`](r-code-conventions.md) — those have different rules (a top-level `set.seed()`, `library()` at the top, relative output paths). Packages do the opposite: no `library()` in `R/`, namespaced imports, generated docs.

---

## 1. Package anatomy

```
pkg/
├── DESCRIPTION        # metadata + dependencies (hand-edited)
├── NAMESPACE          # GENERATED by roxygen — never hand-edit
├── R/                 # package code
├── man/               # GENERATED .Rd by roxygen — never hand-edit
├── tests/testthat/    # testthat tests
├── vignettes/         # long-form docs
└── NEWS.md            # user-facing changelog
```

**`man/` and `NAMESPACE` are generated.** Edit the roxygen blocks in `R/` and run `devtools::document()` — never hand-edit the `.Rd` files or `NAMESPACE`. A hand-edited `NAMESPACE` that disagrees with the roxygen blocks is a check failure waiting to happen.

## 2. Dependencies & DESCRIPTION

- **`Imports`** for packages used in `R/` code. **`Suggests`** for packages used only in tests, vignettes, or optional code paths (guard with `requireNamespace(..., quietly = TRUE)`). **`Depends`** only when the user genuinely needs the dependency attached (rare — prefer `Imports`).
- **Never `library()` / `require()` in `R/`.** Reference dependencies as `pkg::fun()` or import them with roxygen `@importFrom pkg fun`. `library()` in package code is a check NOTE and pollutes the user's search path.
- Add a **version floor** (`dplyr (>= 1.1.0)`) only when you rely on a feature introduced in that version.
- **`Authors@R`** with roles (`aut`, `cre`; add `cph`/`fnd` where applicable), a real `License` (+ `LICENSE` file if needed), and a one-paragraph `Description` with no "This package…" / "A package…" opener (a check NOTE).

## 3. Documentation (roxygen2)

- **Every exported function** documents **all** arguments (`@param`), the return value (`@return`), and has a **runnable** `@examples` block. Wrap slow/online/interactive examples in `\donttest{}` or `@examplesIf`; never use `\dontrun{}` to hide an example that actually errors.
- `@export` controls what lands in `NAMESPACE`. Internal helpers stay unexported (optionally `@keywords internal`).
- Document datasets (`@format`, `@source`) and add a package-level doc page (`"_PACKAGE"`).

## 4. Testing (testthat, edition 3)

- Tests live in `tests/testthat/`; set edition 3 (`Config/testthat/edition: 3`). Use `expect_*()` — every exported function has at least one test covering its contract plus edge cases (empty input, `NA`, boundary values).
- **No global-state leakage:** restore `options()`, `par()`, working dir, env vars, and RNG state with `withr::*` or `on.exit()`. A test that changes global state and doesn't restore it can break unrelated tests.
- `skip_on_cran()` / `skip_if_offline()` for slow or network-dependent tests. Use `expect_snapshot()` for rich printed output.

## 5. CRAN-readiness red flags

| Red flag | Why it fails | Do instead |
|---|---|---|
| `library()` / `require()` in `R/` | NOTE; pollutes search path | `@importFrom` or `pkg::fun()` |
| Writing outside `tempdir()` | Policy violation (writes to user library/home) | Write only to `tempdir()`; return paths |
| `<<-` to the global env | Policy violation | Return values; use environments deliberately |
| `print()` / `cat()` in functions | Uncontrollable output | `message()` / `warning()`; gate on `verbose` |
| `options()` / `par()` changed, not restored | Side effects | `on.exit(...)` or `withr::with_*` |
| `T` / `F` instead of `TRUE` / `FALSE` | Reassignable | Always `TRUE` / `FALSE` |
| Examples that error, or run long | ERROR if they error; timing NOTE if slow | Fix; wrap slow-but-correct in `\donttest{}` |
| `\dontrun{}` hiding failures | Masks broken examples | `\donttest{}` only for slow-but-correct |
| Unstated option args | Silent typos | `match.arg(method)` |

## 6. Versioning & NEWS

- Bump `Version` in `DESCRIPTION` per change (dev versions use a 4th component, e.g. `1.2.0.9000`). Follow semantic-ish versioning: major for breaking API changes.
- Every user-facing change gets a `NEWS.md` bullet under the target version. Deprecate gracefully (`lifecycle::deprecate_warn()`), don't remove exported functions without a deprecation cycle.

## 7. The release gate

Before any release or CRAN submission, run [`/r-package-check`](../skills/r-package-check/SKILL.md): it runs `devtools::document()`, `devtools::test()`, and `R CMD check --as-cran`, then triages every ERROR / WARNING / NOTE against CRAN policy. **0 errors, 0 warnings, and a written justification for each remaining note** is the bar.

## 8. Checklist

```
[ ] NAMESPACE + man/ regenerated via devtools::document() (not hand-edited)
[ ] No library()/require() in R/; deps via @importFrom or pkg::fun()
[ ] Imports/Suggests/Depends classified correctly; Authors@R + License set
[ ] Every exported fn: @param (all args) + @return + runnable @examples
[ ] testthat 3e; exported fns tested; global state restored (withr/on.exit)
[ ] No <<-, no writes outside tempdir(), no T/F, no print()/cat() in functions
[ ] Version bumped + NEWS.md entry
[ ] R CMD check --as-cran: 0 errors, 0 warnings, notes explained
```

## Cross-references

- [`r-code-conventions.md`](r-code-conventions.md) — analysis-script standards (the non-package counterpart).
- [`../agents/r-package-reviewer.md`](../agents/r-package-reviewer.md) — the agent that enforces this rule.
- [`../agents/r-reviewer.md`](../agents/r-reviewer.md) — general R numerical discipline (Cat 11), still applies inside package functions.
- [`replication-protocol.md`](replication-protocol.md) — when the package reproduces published results.
