diff --git a/.Rbuildignore b/.Rbuildignore index a30427bd..b4073423 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -17,3 +17,6 @@ ^\.git-blame-ignore-revs$ ^dev-notes$ ^\.claude$ +^[.]?air[.]toml$ +^\.vscode$ +^AGENTS\.md$ diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 33944ad7..dba71e97 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -1,98 +1 @@ -## R package development - -### Key commands - -```R -# To run code -devtools::load_all(); code - -# To run all tests -devtools::test() - -# To run all tests for files starting with {name} -devtools::test(filter = '^{name}') - -# To run all tests for R/{name}.R -devtools::test_active_file('R/{name}.R') - -# To run a single test "blah" for R/{name}.R -devtools::test_active_file('R/{name}.R', desc = 'blah') - -# To redocument the package -devtools::document() - -# To check pkgdown documentation -pkgdown::check_pkgdown() - -# To check the package with R CMD check -devtools::check() -``` - -You have two options to run R code: - -* `Rscript --no-environ -e "code"`. - - Without `--no-environ`, every R call fails with `"Fatal error: cannot create 'R_TempDir'"` because the sandbox blocks reads of `~/.Renviron`, which R reads during startup before creating `tempdir()`. - -* If the mcp-repl tool is available, you can use it instead. Note that its default sandbox blocks network requests. - -Other commands: - -``` -# To format code -air format . -``` - -### Coding - -* Always run `air format .` after generating code -* Use the base pipe operator (`|>`) not the magrittr pipe (`%>%`) -* Don't use `_$x` or `_$[["x"]]` since this package must work on R 4.1. -* Use `\() ...` for single-line anonymous functions. For all other cases, use `function() {...}` - -### Testing - -- Tests for `R/{name}.R` go in `tests/testthat/test-{name}.R`. -- All new code should have an accompanying test. -- If there are existing tests, place new tests next to similar existing tests. -- Strive to keep your tests minimal with few comments. -- Never put code in a `test-{name}.R` file outside of a `test_that()` block. Instead, use `tests/testthat/helper.R` or `tests/testthat/helper-{name}.R`. -- Avoid `expect_true()` and `expect_false()` in favour of a specific expectation which will give a better failure message. A few expectations in newer releases that you might not know about are `expect_all_true()`, `expect_all_equal()`, and `expect_r6_class()`. -- When testing errors and warnings, don't use `expect_error()` or `expect_warning()`. Instead, use `expect_snapshot(error = TRUE)` for errors and `expect_snapshot()` for warnings because these allow the user to review the full text of the output. -- Avoid the `.package` argument to `local_mocked_bindings()`; this modifies the namespace of another package which is not good practice. Instead create a mockable version of the function in the current package. See `?local_mocked_bindings` for more details. - -### Documentation - -- Every user-facing function should be exported and have roxygen2 documentation. -- Wrap roxygen comments at 80 characters. -- Internal functions should not have roxygen documentation. -- Whenever you add a new (non-internal) documentation topic, also add the topic to `_pkgdown.yml`. -- Always re-document the package after changing a roxygen2 comment. -- Use `pkgdown::check_pkgdown()` to check that all topics are included in the reference index. - -### `NEWS.md` - -- Every user-facing change should be given a bullet in `NEWS.md`. -- Changes that shouldn't get a bullet: - - Small documentation changes. - - Internal refactorings. - - Fixes to bugs introduced in the current dev version. -- Each bullet should briefly describe the change to the end user and mention the related issue in parentheses. -- A bullet can consist of multiple sentences but should not contain any new lines (i.e. DO NOT line wrap). -- If the change is related to a function, put the name of the function early in the bullet. -- Order bullets alphabetically by function name. Put all bullets that don't mention function names at the beginning. - -### Writing - -- Use sentence case for headings. -- Use US English. - -### Proofreading - -If the user asks you to proofread a file, act as an expert proofreader and editor with a deep understanding of clear, engaging, and well-structured writing. - -Work paragraph by paragraph, always starting by making a TODO list that includes individual items for each top-level heading. - -Fix spelling, grammar, and other minor problems without asking the user. Label any unclear, confusing, or ambiguous sentences with a FIXME comment. - -Only report what you have changed. +@../AGENTS.md diff --git a/.claude/settings.json b/.claude/settings.json index 805307d6..0ab263f3 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -1,28 +1,6 @@ { "$schema": "https://json.schemastore.org/claude-code-settings.json", "permissions": { - "defaultMode": "acceptEdits", - "allow": [ - "Bash(air:*)", - "Bash(cat:*)", - "Bash(find:*)", - "Bash(gh issue list:*)", - "Bash(gh issue view:*)", - "Bash(gh pr diff:*)", - "Bash(gh pr view:*)", - "Bash(git checkout:*)", - "Bash(git grep:*)", - "Bash(grep:*)", - "Bash(ls:*)", - "Bash(R:*)", - "Bash(rm:*)", - "Bash(Rscript:*)", - "Bash(sed:*)", - "Skill", - "WebFetch(domain:cran.r-project.org)", - "WebFetch(domain:github.com)", - "WebFetch(domain:raw.githubusercontent.com)" - ], "deny": [ "Read(.Renviron)", "Read(.env)" diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index e712a216..0ac7d7d6 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -8,9 +8,8 @@ on: push: branches: [main, master] pull_request: - branches: [main, master] -name: R-CMD-check +name: R-CMD-check.yaml permissions: read-all @@ -25,11 +24,7 @@ jobs: matrix: config: - {os: macos-latest, r: 'release'} - - {os: windows-latest, r: 'release'} - # use 4.1 to check with rtools40's older compiler - - {os: windows-latest, r: '4.1'} - - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} - {os: ubuntu-latest, r: 'release'} - {os: ubuntu-latest, r: 'oldrel-1'} @@ -42,7 +37,7 @@ jobs: R_KEEP_PKG_SOURCE: yes steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: r-lib/actions/setup-pandoc@v2 @@ -50,7 +45,6 @@ jobs: with: r-version: ${{ matrix.config.r }} http-user-agent: ${{ matrix.config.http-user-agent }} - use-public-rspm: true - uses: r-lib/actions/setup-r-dependencies@v2 with: diff --git a/.github/workflows/format-suggest.yaml b/.github/workflows/format-suggest.yaml new file mode 100644 index 00000000..8c4f117d --- /dev/null +++ b/.github/workflows/format-suggest.yaml @@ -0,0 +1,46 @@ +# Workflow derived from https://github.com/posit-dev/setup-air/tree/main/examples + +on: + # Using `pull_request_target` over `pull_request` for elevated `GITHUB_TOKEN` + # privileges, otherwise we can't set `pull-requests: write` when the pull + # request comes from a fork, which is our main use case (external contributors). + # + # `pull_request_target` runs in the context of the target branch (`main`, usually), + # rather than in the context of the pull request like `pull_request` does. Due + # to this, we must explicitly checkout `ref: ${{ github.event.pull_request.head.sha }}`. + # This is typically frowned upon by GitHub, as it exposes you to potentially running + # untrusted code in a context where you have elevated privileges, but they explicitly + # call out the use case of reformatting and committing back / commenting on the PR + # as a situation that should be safe (because we aren't actually running the untrusted + # code, we are just treating it as passive data). + # https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/ + pull_request_target: + +name: format-suggest.yaml + +jobs: + format-suggest: + name: format-suggest + runs-on: ubuntu-latest + + permissions: + # Required to push suggestion comments to the PR + pull-requests: write + + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + + - name: Install + uses: posit-dev/setup-air@v1 + + - name: Format + run: air format . + + - name: Suggest + uses: reviewdog/action-suggester@v1 + with: + level: error + fail_level: error + tool_name: air diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml index c9f0165d..16c6c8b8 100644 --- a/.github/workflows/pkgdown.yaml +++ b/.github/workflows/pkgdown.yaml @@ -4,12 +4,11 @@ on: push: branches: [main, master] pull_request: - branches: [main, master] release: types: [published] workflow_dispatch: -name: pkgdown +name: pkgdown.yaml permissions: read-all @@ -24,13 +23,11 @@ jobs: permissions: contents: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: r-lib/actions/setup-pandoc@v2 - uses: r-lib/actions/setup-r@v2 - with: - use-public-rspm: true - uses: r-lib/actions/setup-r-dependencies@v2 with: @@ -43,7 +40,7 @@ jobs: - name: Deploy to GitHub pages 🚀 if: github.event_name != 'pull_request' - uses: JamesIves/github-pages-deploy-action@v4.5.0 + uses: JamesIves/github-pages-deploy-action@d92aa235d04922e8f08b40ce78cc5442fcfbfa2f # v4.8.0 with: clean: false branch: gh-pages diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml index fefc52e2..5e820d39 100644 --- a/.github/workflows/test-coverage.yaml +++ b/.github/workflows/test-coverage.yaml @@ -4,9 +4,8 @@ on: push: branches: [main, master] pull_request: - branches: [main, master] -name: test-coverage +name: test-coverage.yaml permissions: read-all @@ -17,11 +16,9 @@ jobs: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: r-lib/actions/setup-r@v2 - with: - use-public-rspm: true - uses: r-lib/actions/setup-r-dependencies@v2 with: @@ -35,14 +32,16 @@ jobs: clean = FALSE, install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package") ) + print(cov) covr::to_cobertura(cov) shell: Rscript {0} - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7 with: - fail_ci_if_error: ${{ github.event_name != 'pull_request' && true || false }} - file: ./cobertura.xml - plugin: noop + # Fail if error if not on PR, or if on PR and token is given + fail_ci_if_error: ${{ github.event_name != 'pull_request' || secrets.CODECOV_TOKEN }} + files: ./cobertura.xml + plugins: noop disable_search: true token: ${{ secrets.CODECOV_TOKEN }} @@ -55,7 +54,7 @@ jobs: - name: Upload test results if: failure() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: coverage-test-failures path: ${{ runner.temp }}/package diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..344f76eb --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "Posit.air-vscode" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..f2d0b79d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "[r]": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "Posit.air-vscode" + } +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..5ea162b8 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,115 @@ +## This package + + + +## Package development + +### Key commands + +(All these functions have been optimized for agentic use, so they can be called directly without other arguments.) + +```R +# Executing code +devtools::load_all() +code + +# Tests +devtools::test() # all tests +devtools::test(filter = "^{name}") # tests for files starting with {name} +devtools::test_active_file("R/{name}.R") # tests for R/{name}.R +devtools::test_active_file("R/{name}.R", desc = 'blah') # single test with exact description "blah" (no regexp) + +# Test coverage +devtools::test_coverage() # all files +devtools::test_coverage_active_file("R/{name}.R") # coverage for R/{name}.R from tests in tests/testthat/test-{name}.R + +# Documentation +devtools::document() # redocument package +pkgdown::check_pkgdown() # check website + +# Run complete R CMD check +devtools::check() +``` + +### Running R + +There are three possible ways to run code, listed in rough order of desirability: + +- If you're running inside Posit Assistant or otherwise have an + `executeCode()` tool available, use it to run code in a session that the + user can also interact with. + +- Otherwise, if an R REPL (e.g. `mcp__r__repl` or `btw::run_r`) is + available, use that. Note that `mcp__r__repl` uses a sandbox that blocks + network requests and reads/writes outside of the current directory. + +- Otherwise, use `Rscript -e "code"`. + +### Code style + +- Follow the tidyverse style guide +- Always run `air format .` after generating code. +- Use the base pipe operator (`|>`), not the magrittr pipe (`%>%`). +- Use `\() ...` for single-line anonymous functions. For all other cases, use `function() {...}`. + +### Test style + +- Tests for `R/{name}.R` go in `tests/testthat/test-{name}.R`. +- All new code should have an accompanying test. +- If there are existing tests, place new tests next to similar existing tests. +- Strive to keep your tests minimal with few comments. +- Never put code in a `test-{name}.R` file outside of a `test_that()` block. Instead, use `tests/testthat/helper.R` or `tests/testthat/helper-{name}.R`. +- Avoid `expect_true()` and `expect_false()` in favor of a specific expectation with a better failure message. A few expectations in newer releases that you might not know about are `expect_all_true()`, `expect_all_equal()`, and `expect_r6_class()`. +- When testing errors and warnings: + - Only use `expect_error()` or `expect_warning()` if the error or warning has a known class. + - Generally, prefer `expect_snapshot(error = TRUE)` for errors and `expect_snapshot()` for warnings because these allow the user to review the full text of the output. +- Avoid the `.package` argument to `local_mocked_bindings()`; this modifies the namespace of another package, which is not good practice. Instead create a mockable version of the function in the current package. See `?local_mocked_bindings` for more details. + +### Documentation + +- Every user-facing function should be exported and have roxygen2 documentation. +- Internal functions should not have roxygen documentation. +- Wrap roxygen2 comments to 80 characters. +- Whenever you add a new (non-internal) documentation topic, also add the topic to `_pkgdown.yml`. +- Always re-document the package after changing a roxygen2 comment. +- Use `pkgdown::check_pkgdown()` to check that all topics are included in the reference index. + +### `NEWS.md` + +- Every user-facing change should be given a bullet in `NEWS.md`. +- Changes that shouldn't get a bullet: + - Small documentation changes. + - Internal refactorings. + - Fixes to bugs introduced in the current dev version. +- Each bullet should briefly describe the change to the end user and mention the related issue in parentheses. +- A bullet can consist of multiple sentences but should not contain any newlines (i.e. DO NOT line wrap). +- If the change is related to a function, put the name of the function early in the bullet. +- If the change is related to an issue, include the issue number in parentheses. +- Only include a GitHub username if the PR was created by someone who isn't an author. +- Order bullets alphabetically by function name. Put all bullets that don't mention function names at the beginning. + +## Specialized skills + +- Do you need to deprecate a function or argument? Read the output of `usethis::learn_tidy_skill("deprecate")`. +- Are you adding input checking to an existing function or writing a new exported function? Read the output of `usethis::learn_tidy_skill("arg-checking")`. + +## Git + +- If the user asks you to commit, use markdown in the commit message, and don't line wrap. +- If the commit fixes an issue, include `Fixes #num.` on its own line. +- Only push when the user explicitly requests it. + +## Writing + +- Use sentence case for headings. +- Use US English. + +### Proofreading + +If the user asks you to proofread a file, act as an expert proofreader and editor with a deep understanding of clear, engaging, and well-structured writing. + +Work paragraph by paragraph, always starting by making a TODO list that includes individual items for each top-level section. + +Fix spelling, grammar, and other minor problems without asking the user. Label any unclear, confusing, or ambiguous sentences with a FIXME comment. + +Only report what you have changed. diff --git a/DESCRIPTION b/DESCRIPTION index 3e3cf690..6881afc6 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -42,9 +42,9 @@ Suggests: usethis, withr Config/Needs/website: tidyverse/tidytemplate, ggplot2 +Config/roxygen2/version: 8.0.0 Config/testthat/edition: 3 Config/testthat/parallel: TRUE Config/testthat/start-first: dll Encoding: UTF-8 Roxygen: list(markdown = TRUE) -RoxygenNote: 7.3.2 diff --git a/README.md b/README.md index 04f8dc4b..0908f44b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # pkgload -[![Codecov test coverage](https://codecov.io/gh/r-lib/pkgload/branch/main/graph/badge.svg)](https://app.codecov.io/gh/r-lib/pkgload?branch=main) +[![Codecov test coverage](https://codecov.io/gh/r-lib/pkgload/graph/badge.svg)](https://app.codecov.io/gh/r-lib/pkgload) [![R-CMD-check](https://github.com/r-lib/pkgload/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/r-lib/pkgload/actions/workflows/R-CMD-check.yaml) diff --git a/man/dev_example.Rd b/man/dev_example.Rd index 4163241b..983bc997 100644 --- a/man/dev_example.Rd +++ b/man/dev_example.Rd @@ -33,7 +33,7 @@ run_example( \item{macros}{Custom macros to use to parse the \code{.Rd} file. See the \code{macros} argument of \code{\link[tools:parse_Rd]{tools::parse_Rd()}}. If \code{NULL}, then the -\code{\link[tools:Rd2HTML]{tools::Rd2ex()}} (and \code{\link[tools:parse_Rd]{tools::parse_Rd()}}) default is used.} +\code{\link[tools:Rd2ex]{tools::Rd2ex()}} (and \code{\link[tools:parse_Rd]{tools::parse_Rd()}}) default is used.} \item{run, test}{Deprecated, see \code{run_dontrun} and \code{run_donttest} above.} } diff --git a/man/help.Rd b/man/help.Rd index d77a3227..c82dbbe2 100644 --- a/man/help.Rd +++ b/man/help.Rd @@ -30,7 +30,7 @@ same name in the utils package. They are made available when a package is loaded with \code{\link[=load_all]{load_all()}}. } \details{ -The \verb{?} function is a replacement for \code{\link[utils:Question]{utils::?()}} from the +The \verb{?} function is a replacement for \code{\link[utils:?]{utils::?()}} from the utils package. It will search for help in devtools-loaded packages first, then in regular packages. diff --git a/man/pkgload-package.Rd b/man/pkgload-package.Rd index 6f9a366d..ab8255b5 100644 --- a/man/pkgload-package.Rd +++ b/man/pkgload-package.Rd @@ -22,6 +22,7 @@ Useful links: Authors: \itemize{ + \item Lionel Henry \email{lionel@posit.co} \item Hadley Wickham \item Winston Chang \item Jim Hester