diff --git a/.Rbuildignore b/.Rbuildignore index d2f79f04f..84e3947c9 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -27,3 +27,4 @@ ^\.vscode$ ^[.]?air[.]toml$ ^scratch\.R$ +^\.claude$ diff --git a/.claude/.gitignore b/.claude/.gitignore new file mode 100644 index 000000000..93c0f73fa --- /dev/null +++ b/.claude/.gitignore @@ -0,0 +1 @@ +settings.local.json diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md new file mode 100644 index 000000000..2e4fbaa6e --- /dev/null +++ b/.claude/CLAUDE.md @@ -0,0 +1,60 @@ +## R package development + +### Key commands + +``` +# To run code +Rscript -e "devtools::load_all(); code" + +# To run all tests +Rscript -e "devtools::test()" + +# To run tests for R/{name.R} +Rscript -e "devtools::test(filter = '{name}')" + +# To document the package +Rscript -e "devtools::document()" + +# To check pkgdown documentation +Rscript -e "pkgdown::check_pkgdown()" + +# 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 dbplyr 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. + +### 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 documentation topic, also add the topic to `_pkgdown.yml`. +- Use `pkgdown::check_pkgdown()` to check that all topics are included in the reference index. + +### 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. diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 000000000..e1f575192 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,21 @@ +{ + + "permissions": { + "$schema": "https://json.schemastore.org/claude-code-settings.json", + "defaultMode": "acceptEdits", + "allow": [ + "Bash(find:*)", + "Bash(grep:*)", + "Bash(ls:*)", + "Bash(R:*)", + "Bash(Rscript:*)", + "Bash(rm:*)", + "Bash(air:*)", + "WebFetch(domain:github.com)" + ], + "deny": [ + "Read(.Renviron)", + "Read(.env)" + ] + } +} diff --git a/.claude/skills/tidy-deprecate-function/SKILL.md b/.claude/skills/tidy-deprecate-function/SKILL.md new file mode 100644 index 000000000..a9427f5ca --- /dev/null +++ b/.claude/skills/tidy-deprecate-function/SKILL.md @@ -0,0 +1,156 @@ +--- +name: tidy-deprecate-function +description: Guide for deprecating R functions/arguments. Use when a user asks to deprecate a function or parameter, including adding lifecycle warnings, updating documentation, adding NEWS entries, and updating tests. +--- + +# Deprecate functions and function arguments + +Use this skill when deprecating functions or function parameters in dbplyr. + +## Overview + +This skill guides you through the complete process of deprecating a function or parameter, ensuring all necessary changes are made consistently: + +1. Add deprecation warning using `lifecycle::deprecate_warn()`. +2. Silence deprecation warnings in existing tests. +3. Add lifecycle badge to documentation. +4. Add bullet point to NEWS.md. +5. Create test for deprecation warning. + +## Workflow + +### Step 1: Determine deprecation version + +Read the current version from DESCRIPTION and calculate the deprecation version: + +- Current version format: `MAJOR.MINOR.PATCH.9000` (development). +- Deprecation version: Next minor release `MAJOR.(MINOR+1).0`. +- Example: If current version is `2.5.1.9000`, deprecation version is `2.6.0`. + +### Step 2: Add `lifecycle::deprecate_warn()` call + +Add the deprecation warning to the function: + +```r +# For a deprecated function: +function_name <- function(...) { + lifecycle::deprecate_warn("X.Y.0", "function_name()", "replacement_function()") + # rest of function +} + +# For a deprecated parameter: +function_name <- function(param1, deprecated_param = deprecated()) { + if (lifecycle::is_present(deprecated_param)) { + lifecycle::deprecate_warn("X.Y.0", "function_name(deprecated_param)") + } + # rest of function +} +``` + +Key points: + +- First argument is the deprecation version string (e.g., "2.6.0"). +- Second argument describes what is deprecated (e.g., "function_name(param)"). +- Optional third argument suggests replacement. +- Use `lifecycle::is_present()` to check if a deprecated parameter was supplied. + +### Step 3: Update tests + +Find all existing tests that use the deprecated function or parameter and silence lifecycle warnings. Add at the beginning of test blocks that use the deprecated feature: + +```r +test_that("existing test with deprecated feature", { + withr::local_options(lifecycle_verbosity = "quiet") + + # existing test code +}) +``` + +Then add a new test to verify the deprecation message in the appropriate test file (usually `tests/testthat/test-{name}.R`): + +```r +test_that("function_name(deprecated_param) is deprecated", { + expect_snapshot(. <- function_name(deprecated_param = value)) +}) +``` + +You'll need to supply any additional arguments to create a valid call. + +Then run the tests and verify they pass. + +### Step 4: Update documentation + +For function deprecation, add to the description section: + +```r +#' @description +#' `r lifecycle::badge("deprecated")` +#' +#' This function is deprecated. Please use [replacement_function()] instead. +``` + +If the documentation does not already contain `@description`, you will need to add it. + +For argument deprecation, add to the appropriate `@param` tag: + +```r +#' @param deprecated_param `r lifecycle::badge("deprecated")` +``` + +When deprecating a function or parameter in favor of a replacement, add old/new examples to the `@examples` section to help users migrate. These should relace all existing examples. + +```r +#' @examples +#' # Old: +#' old_function(arg1, arg2) +#' # New: +#' replacement_function(arg1, arg2) +#' +#' # Old: +#' x <- "value" +#' old_function("prefix", x, "suffix") +#' # New: +#' replacement_function("prefix {x} suffix") +``` + +Key points: + +- Use "# Old:" and "# New:" comments to clearly show the transition. +- Include 2-3 practical examples covering common use cases. +- Make examples runnable and self-contained. +- Show how the new syntax differs from the old. + +Then re-document the package. + +### Step 5: Add NEWS entry + +Add a bullet point to the top of the "# dbplyr (development version)" section in NEWS.md: + +```markdown +# dbplyr (development version) + +* `function_name(parameter)` is deprecated and will be removed in a future + version. +* `function_name()` is deprecated. Use `replacement_function()` instead. +``` + +Place the entry: + +- In the lifecycle subsection if it exists, otherwise at the top level under development version. +- Include the replacement if known. +- Keep entries concise and actionable. + +## Implementation checklist + +When deprecating a function or parameter, ensure you: + +- [ ] Read DESCRIPTION to determine deprecation version. +- [ ] Add `lifecycle::deprecate_warn()` call in the function. +- [ ] Add `withr::local_options(lifecycle_verbosity = "quiet")` to existing tests. +- [ ] Create new test for deprecation warning using `expect_snapshot()`. +- [ ] Run tests to verify everything works. +- [ ] Add lifecycle badge to roxygen documentation. +- [ ] Add migration examples to `@examples` section (for function deprecation). +- [ ] Run `devtools::document()` to update documentation. +- [ ] Add bullet point to NEWS.md. +- [ ] Run `air format .` to format code. diff --git a/NAMESPACE b/NAMESPACE index 91a15d28e..3ec8b6f4b 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -99,6 +99,7 @@ export(use_ccby_license) export(use_circleci) export(use_circleci_badge) export(use_citation) +export(use_claude_code) export(use_code_of_conduct) export(use_conflicted) export(use_course) diff --git a/R/claude.R b/R/claude.R new file mode 100644 index 000000000..fca746c22 --- /dev/null +++ b/R/claude.R @@ -0,0 +1,51 @@ +#' Configure a project to use Claude Code +#' +#' @description +#' This function sets up a project to use +#' [Claude Code](https://docs.anthropic.com/en/docs/claude-code). +#' Specifically, it: +#' +#' - Creates a `.claude/` directory with a `CLAUDE.md` file containing +#' project-specific instructions for R package development. +#' +#' - Creates a `.claude/settings.json` configuration file with recommended +#' permissions for R package development, including the ability to run R, +#' format with [Air](https://posit-dev.github.io/air/), and run common +#' development tools. +#' +#' - Creates a `.claude/skills/` directory containing various skills found +#' useful by the tidyverse team. All skills have a `tidy-` prefix to avoid +#' clashing with skills that you might provide. +#' +#' - Updates `.claude/.gitignore` to ignore `settings.local.json` (for +#' user-specific settings). +#' +#' @export +#' @examples +#' \dontrun{ +#' use_claude_code() +#' } +use_claude_code <- function() { + use_directory(".claude", ignore = TRUE) + copy_claude_directory() + use_git_ignore("settings.local.json", directory = ".claude") + + invisible(TRUE) +} + +copy_claude_directory <- function() { + source_dir <- path_package("usethis", "claude") + dest_dir <- proj_path(".claude") + + source_dirs <- dir_ls(source_dir, recurse = TRUE, type = "directory") + dir_create(path(dest_dir, path_rel(source_dirs, source_dir))) + + source_files <- dir_ls(source_dir, recurse = TRUE, type = "file") + for (source_file in source_files) { + rel_path <- path_rel(source_file, source_dir) + dest_file <- path(dest_dir, rel_path) + + ui_bullets(c("v" = "Creating {.path {pth(dest_file)}}.")) + file_copy(source_file, dest_file, overwrite = TRUE) + } +} diff --git a/_pkgdown.yml b/_pkgdown.yml index 9e10f6596..1b7ac0be4 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -84,6 +84,7 @@ reference: - use_standalone - use_testthat - use_air + - use_claude_code - title: Package release contents: - use_cran_comments diff --git a/inst/claude/CLAUDE.md b/inst/claude/CLAUDE.md new file mode 100644 index 000000000..2e4fbaa6e --- /dev/null +++ b/inst/claude/CLAUDE.md @@ -0,0 +1,60 @@ +## R package development + +### Key commands + +``` +# To run code +Rscript -e "devtools::load_all(); code" + +# To run all tests +Rscript -e "devtools::test()" + +# To run tests for R/{name.R} +Rscript -e "devtools::test(filter = '{name}')" + +# To document the package +Rscript -e "devtools::document()" + +# To check pkgdown documentation +Rscript -e "pkgdown::check_pkgdown()" + +# 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 dbplyr 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. + +### 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 documentation topic, also add the topic to `_pkgdown.yml`. +- Use `pkgdown::check_pkgdown()` to check that all topics are included in the reference index. + +### 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. diff --git a/inst/claude/settings.json b/inst/claude/settings.json new file mode 100644 index 000000000..e1f575192 --- /dev/null +++ b/inst/claude/settings.json @@ -0,0 +1,21 @@ +{ + + "permissions": { + "$schema": "https://json.schemastore.org/claude-code-settings.json", + "defaultMode": "acceptEdits", + "allow": [ + "Bash(find:*)", + "Bash(grep:*)", + "Bash(ls:*)", + "Bash(R:*)", + "Bash(Rscript:*)", + "Bash(rm:*)", + "Bash(air:*)", + "WebFetch(domain:github.com)" + ], + "deny": [ + "Read(.Renviron)", + "Read(.env)" + ] + } +} diff --git a/inst/claude/skills/tidy-deprecate-function/SKILL.md b/inst/claude/skills/tidy-deprecate-function/SKILL.md new file mode 100644 index 000000000..a9427f5ca --- /dev/null +++ b/inst/claude/skills/tidy-deprecate-function/SKILL.md @@ -0,0 +1,156 @@ +--- +name: tidy-deprecate-function +description: Guide for deprecating R functions/arguments. Use when a user asks to deprecate a function or parameter, including adding lifecycle warnings, updating documentation, adding NEWS entries, and updating tests. +--- + +# Deprecate functions and function arguments + +Use this skill when deprecating functions or function parameters in dbplyr. + +## Overview + +This skill guides you through the complete process of deprecating a function or parameter, ensuring all necessary changes are made consistently: + +1. Add deprecation warning using `lifecycle::deprecate_warn()`. +2. Silence deprecation warnings in existing tests. +3. Add lifecycle badge to documentation. +4. Add bullet point to NEWS.md. +5. Create test for deprecation warning. + +## Workflow + +### Step 1: Determine deprecation version + +Read the current version from DESCRIPTION and calculate the deprecation version: + +- Current version format: `MAJOR.MINOR.PATCH.9000` (development). +- Deprecation version: Next minor release `MAJOR.(MINOR+1).0`. +- Example: If current version is `2.5.1.9000`, deprecation version is `2.6.0`. + +### Step 2: Add `lifecycle::deprecate_warn()` call + +Add the deprecation warning to the function: + +```r +# For a deprecated function: +function_name <- function(...) { + lifecycle::deprecate_warn("X.Y.0", "function_name()", "replacement_function()") + # rest of function +} + +# For a deprecated parameter: +function_name <- function(param1, deprecated_param = deprecated()) { + if (lifecycle::is_present(deprecated_param)) { + lifecycle::deprecate_warn("X.Y.0", "function_name(deprecated_param)") + } + # rest of function +} +``` + +Key points: + +- First argument is the deprecation version string (e.g., "2.6.0"). +- Second argument describes what is deprecated (e.g., "function_name(param)"). +- Optional third argument suggests replacement. +- Use `lifecycle::is_present()` to check if a deprecated parameter was supplied. + +### Step 3: Update tests + +Find all existing tests that use the deprecated function or parameter and silence lifecycle warnings. Add at the beginning of test blocks that use the deprecated feature: + +```r +test_that("existing test with deprecated feature", { + withr::local_options(lifecycle_verbosity = "quiet") + + # existing test code +}) +``` + +Then add a new test to verify the deprecation message in the appropriate test file (usually `tests/testthat/test-{name}.R`): + +```r +test_that("function_name(deprecated_param) is deprecated", { + expect_snapshot(. <- function_name(deprecated_param = value)) +}) +``` + +You'll need to supply any additional arguments to create a valid call. + +Then run the tests and verify they pass. + +### Step 4: Update documentation + +For function deprecation, add to the description section: + +```r +#' @description +#' `r lifecycle::badge("deprecated")` +#' +#' This function is deprecated. Please use [replacement_function()] instead. +``` + +If the documentation does not already contain `@description`, you will need to add it. + +For argument deprecation, add to the appropriate `@param` tag: + +```r +#' @param deprecated_param `r lifecycle::badge("deprecated")` +``` + +When deprecating a function or parameter in favor of a replacement, add old/new examples to the `@examples` section to help users migrate. These should relace all existing examples. + +```r +#' @examples +#' # Old: +#' old_function(arg1, arg2) +#' # New: +#' replacement_function(arg1, arg2) +#' +#' # Old: +#' x <- "value" +#' old_function("prefix", x, "suffix") +#' # New: +#' replacement_function("prefix {x} suffix") +``` + +Key points: + +- Use "# Old:" and "# New:" comments to clearly show the transition. +- Include 2-3 practical examples covering common use cases. +- Make examples runnable and self-contained. +- Show how the new syntax differs from the old. + +Then re-document the package. + +### Step 5: Add NEWS entry + +Add a bullet point to the top of the "# dbplyr (development version)" section in NEWS.md: + +```markdown +# dbplyr (development version) + +* `function_name(parameter)` is deprecated and will be removed in a future + version. +* `function_name()` is deprecated. Use `replacement_function()` instead. +``` + +Place the entry: + +- In the lifecycle subsection if it exists, otherwise at the top level under development version. +- Include the replacement if known. +- Keep entries concise and actionable. + +## Implementation checklist + +When deprecating a function or parameter, ensure you: + +- [ ] Read DESCRIPTION to determine deprecation version. +- [ ] Add `lifecycle::deprecate_warn()` call in the function. +- [ ] Add `withr::local_options(lifecycle_verbosity = "quiet")` to existing tests. +- [ ] Create new test for deprecation warning using `expect_snapshot()`. +- [ ] Run tests to verify everything works. +- [ ] Add lifecycle badge to roxygen documentation. +- [ ] Add migration examples to `@examples` section (for function deprecation). +- [ ] Run `devtools::document()` to update documentation. +- [ ] Add bullet point to NEWS.md. +- [ ] Run `air format .` to format code. diff --git a/man/use_claude_code.Rd b/man/use_claude_code.Rd new file mode 100644 index 000000000..162ff7935 --- /dev/null +++ b/man/use_claude_code.Rd @@ -0,0 +1,31 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/claude.R +\name{use_claude_code} +\alias{use_claude_code} +\title{Configure a project to use Claude Code} +\usage{ +use_claude_code() +} +\description{ +This function sets up a project to use +\href{https://docs.anthropic.com/en/docs/claude-code}{Claude Code}. +Specifically, it: +\itemize{ +\item Creates a \verb{.claude/} directory with a \code{CLAUDE.md} file containing +project-specific instructions for R package development. +\item Creates a \code{.claude/settings.json} configuration file with recommended +permissions for R package development, including the ability to run R, +format with \href{https://posit-dev.github.io/air/}{Air}, and run common +development tools. +\item Creates a \verb{.claude/skills/} directory containing various skills found +useful by the tidyverse team. All skills have a \verb{tidy-} prefix to avoid +clashing with skills that you might provide. +\item Updates \code{.claude/.gitignore} to ignore \code{settings.local.json} (for +user-specific settings). +} +} +\examples{ +\dontrun{ +use_claude_code() +} +}