Claude Skill

lifecycle

Guidance for managing R package lifecycle according to tidyverse principles using the lifecycle package. Use when: (1) Setting up lifecycle infrastructure in a package, (2) Deprecating functions or arguments, (3) Renaming functions or arguments, (4) Superseding functions, (5) Mar

LLM Mart · 0 points · 16 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download posit-dev-skills-r-lib_lifecycle-b58a92e.zip · 3 KB
Part of posit-dev/skills — 23 skills

Install

skills CLI npx skills add https://github.com/posit-dev/skills/tree/main/r-lib/lifecycle
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install posit-dev-skills@llmmart
Git git clone https://github.com/posit-dev/skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole posit-dev/skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

R Package Lifecycle Management

Manage function and argument lifecycle using tidyverse conventions and the lifecycle package.

Setup

Check if lifecycle is configured by looking for lifecycle-*.svg files in man/figures/.

If not configured, run:

usethis::use_lifecycle()

This:

  • Adds lifecycle to Imports in DESCRIPTION
  • Adds @importFrom lifecycle deprecated to the package documentation file
  • Copies badge SVGs to man/figures/

Lifecycle Badges

Insert badges in roxygen2 documentation:

#' @description
#' `r lifecycle::badge("experimental")`
#' `r lifecycle::badge("deprecated")`
#' `r lifecycle::badge("superseded")`

For arguments:

#' @param old_arg `r lifecycle::badge("deprecated")` Use `new_arg` instead.

Only badge functions/arguments whose stage differs from the package's overall stage.

Deprecating a Function

  1. Add badge and explanation to @description:
#' Do something
#'
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' `old_fun()` was deprecated in mypkg 1.0.0. Use [new_fun()] instead.
#' @keywords internal
  1. Add deprecate_warn() as first line of function body:
old_fun <- function(x) {

lifecycle::deprecate_warn("1.0.0", "old_fun()", "new_fun()")
new_fun(x)
}
  1. Show migration in examples:
#' @examples
#' old_fun(x)
#' # ->
#' new_fun(x)

Deprecation Functions

Function When to Use
deprecate_soft() First stage; warns only direct users and during tests
deprecate_warn() Standard deprecation; warns once per 8 hours
deprecate_stop() Final stage before removal; errors with helpful message

Deprecation workflow for major releases:

  1. Search deprecate_stop() - consider removing function entirely
  2. Replace deprecate_warn() with deprecate_stop()
  3. Replace deprecate_soft() with deprecate_warn()

Renaming a Function

Move implementation to new name, call from old name with deprecation:

#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' `add_two()` was renamed to `number_add()` for API consistency.
#' @keywords internal
#' @export
add_two <- function(x, y) {
lifecycle::deprecate_warn("1.0.0", "add_two()", "number_add()")
number_add(x, y)
}

#' Add two numbers
#' @export
number_add <- function(x, y) {
x + y
}

Deprecating an Argument

Use deprecated() as default value with is_present() check:

#' @param path `r lifecycle::badge("deprecated")` Use `file` instead.
write_file <- function(x, file, path = deprecated()) {
  if (lifecycle::is_present(path)) {
    lifecycle::deprecate_warn("1.4.0", "write_file(path)", "write_file(file)")
    file <- path
  }
  # ... rest of function
}

Renaming an Argument

add_two <- function(x, y, na_rm = TRUE, na.rm = deprecated()) {
  if (lifecycle::is_present(na.rm)) {
    lifecycle::deprecate_warn("1.0.0", "add_two(na.rm)", "add_two(na_rm)")
    na_rm <- na.rm
  }
  sum(x, y, na.rm = na_rm)
}

Superseding a Function

For functions with better alternatives that shouldn't be removed:

#' Gather columns into key-value pairs
#'
#' @description
#' `r lifecycle::badge("superseded")`
#'
#' Development on `gather()` is complete. For new code, use [pivot_longer()].
#'
#' `df %>% gather("key", "value", x, y, z)` is equivalent to
#' `df %>% pivot_longer(c(x, y, z), names_to = "key", values_to = "value")`.

No warning needed - just document the preferred alternative.

Marking as Experimental

#' @description
#' `r lifecycle::badge("experimental")`
cool_function <- function() {
  lifecycle::signal_stage("experimental", "cool_function()")
  # ...
}

Testing Deprecations

Test that deprecated functions work and warn appropriately:

test_that("old_fun is deprecated", {
  expect_snapshot({
    x <- old_fun(1)
    expect_equal(x, expected_value)
  })
})

Suppress warnings in existing tests:

test_that("old_fun returns correct value", {
  withr::local_options(lifecycle_verbosity = "quiet")
  expect_equal(old_fun(1), expected_value)
})

Deprecation Helpers

For deprecations affecting many functions (e.g., removing a common argument), create an internal helper:

warn_for_verbose <- function(
  verbose = TRUE,
  env = rlang::caller_env(),
  user_env = rlang::caller_env(2)
) {
  if (!lifecycle::is_present(verbose) || isTRUE(verbose)) {
    return(invisible())
  }

  lifecycle::deprecate_warn(
    when = "2.0.0",
    what = I("The `verbose` argument"),
    details = c(
      "Set `options(mypkg_quiet = TRUE)` to suppress messages.",
      "The `verbose` argument will be removed in a future release."
    ),
    user_env = user_env
  )

  invisible()
}

Then use in affected functions:

my_function <- function(..., verbose = deprecated()) {
  warn_for_verbose(verbose)
  # ...
}

Custom Deprecation Messages

For non-standard deprecations, use I() to wrap custom text:

lifecycle::deprecate_warn(
  when = "1.0.0",
  what = I('Setting option "pkg.opt" to "foo"'),
  with = I('"pkg.new_opt"')
)

The what fragment must work with "was deprecated in..." appended.

Reference

See references/lifecycle-stages.md for detailed stage definitions and transitions.

Files (skills)
  • references
    • lifecycle-stages.md 2.2 KB
      # Lifecycle Stages Reference
      
      ## Table of Contents
      
      - [Stage Diagram](#stage-diagram)
      - [Stable](#stable)
      - [Experimental](#experimental)
      - [Deprecated](#deprecated)
      - [Superseded](#superseded)
      - [Retired Stages](#retired-stages)
      
      ## Stage Diagram
      
      ```
      experimental --> stable --> deprecated
                         |
                         +--> superseded
      ```
      
      Lifecycle stages apply to packages, functions, arguments, or specific argument values.
      
      ## Stable
      
      The default stage. Functions are stable when the author is satisfied with the interface.
      
      **Promises:**
      - Breaking changes avoided where possible
      - Breaking changes happen gradually through deprecation
      
      **Breaking changes** reduce working code: removing functions/arguments, narrowing valid inputs, or changing output types.
      
      **Not breaking changes:** Bug fixes that happen to break code relying on buggy behavior.
      
      ## Experimental
      
      For functions released without long-term stability promises. Author reserves the right to make breaking changes without deprecation cycle.
      
      **Guidelines:**
      - Packages with version < 1.0.0 are at least somewhat experimental
      - Non-CRAN packages require active relationship with maintainers
      
      ## Deprecated
      
      Function has a better alternative and is scheduled for removal.
      
      **Deprecation stages (in order):**
      
      1. **Soft deprecated** (`deprecate_soft()`) - Warns only:
         - Users calling from global environment
         - Developers running tests
         - Does NOT warn when called indirectly by another package
      
      2. **Deprecated** (`deprecate_warn()`) - Warns all users once per 8 hours
      
      3. **Defunct** (`deprecate_stop()`) - Errors with helpful message about replacement
      
      ## Superseded
      
      Function has a known better alternative but won't be removed. Receives only critical bug fixes.
      
      **Key difference from deprecated:**
      - No warning emitted
      - Documentation explains recommended alternative
      - Function preserved indefinitely (safer than stable in some ways)
      
      **Examples:** `tidyr::gather()` and `tidyr::spread()` superseded by `pivot_longer()` and `pivot_wider()`.
      
      ## Retired Stages
      
      No longer recommended:
      
      - **Questioning** - Author uncertain about function; not actionable for users
      - **Maturing** - Between experimental and stable; unclear actionable information
      
  • SKILL.md 5.8 KB
    ---
    name: lifecycle
    description: "Guidance for managing R package lifecycle according to tidyverse principles using the lifecycle package. Use when: (1) Setting up lifecycle infrastructure in a package, (2) Deprecating functions or arguments, (3) Renaming functions or arguments, (4) Superseding functions, (5) Marking functions as experimental, (6) Understanding lifecycle stages (stable, experimental, deprecated, superseded), or (7) Writing deprecation helpers for complex scenarios."
    metadata:
      author: Garrick Aden-Buie (@gadenbuie)
      version: "1.1"
    license: MIT
    ---
    
    # R Package Lifecycle Management
    
    Manage function and argument lifecycle using tidyverse conventions and the lifecycle package.
    
    ## Setup
    
    Check if lifecycle is configured by looking for `lifecycle-*.svg` files in `man/figures/`.
    
    If not configured, run:
    
    ```r
    usethis::use_lifecycle()
    ```
    
    This:
    
    - Adds lifecycle to `Imports` in DESCRIPTION
    - Adds `@importFrom lifecycle deprecated` to the package documentation file
    - Copies badge SVGs to `man/figures/`
    
    ## Lifecycle Badges
    
    Insert badges in roxygen2 documentation:
    
    ```r
    #' @description
    #' `r lifecycle::badge("experimental")`
    #' `r lifecycle::badge("deprecated")`
    #' `r lifecycle::badge("superseded")`
    ```
    
    For arguments:
    
    ```r
    #' @param old_arg `r lifecycle::badge("deprecated")` Use `new_arg` instead.
    ```
    
    Only badge functions/arguments whose stage differs from the package's overall stage.
    
    ## Deprecating a Function
    
    1. Add badge and explanation to `@description`:
    
    ```r
    #' Do something
    #'
    #' @description
    #' `r lifecycle::badge("deprecated")`
    #'
    #' `old_fun()` was deprecated in mypkg 1.0.0. Use [new_fun()] instead.
    #' @keywords internal
    ```
    
    2. Add `deprecate_warn()` as first line of function body:
    
    ```r
    old_fun <- function(x) {
    
    lifecycle::deprecate_warn("1.0.0", "old_fun()", "new_fun()")
    new_fun(x)
    }
    ```
    
    3. Show migration in examples:
    
    ```r
    #' @examples
    #' old_fun(x)
    #' # ->
    #' new_fun(x)
    ```
    
    ## Deprecation Functions
    
    | Function           | When to Use                                             |
    | ------------------ | ------------------------------------------------------- |
    | `deprecate_soft()` | First stage; warns only direct users and during tests   |
    | `deprecate_warn()` | Standard deprecation; warns once per 8 hours            |
    | `deprecate_stop()` | Final stage before removal; errors with helpful message |
    
    **Deprecation workflow for major releases:**
    
    1. Search `deprecate_stop()` - consider removing function entirely
    2. Replace `deprecate_warn()` with `deprecate_stop()`
    3. Replace `deprecate_soft()` with `deprecate_warn()`
    
    ## Renaming a Function
    
    Move implementation to new name, call from old name with deprecation:
    
    ```r
    #' @description
    #' `r lifecycle::badge("deprecated")`
    #'
    #' `add_two()` was renamed to `number_add()` for API consistency.
    #' @keywords internal
    #' @export
    add_two <- function(x, y) {
    lifecycle::deprecate_warn("1.0.0", "add_two()", "number_add()")
    number_add(x, y)
    }
    
    #' Add two numbers
    #' @export
    number_add <- function(x, y) {
    x + y
    }
    ```
    
    ## Deprecating an Argument
    
    Use `deprecated()` as default value with `is_present()` check:
    
    ```r
    #' @param path `r lifecycle::badge("deprecated")` Use `file` instead.
    write_file <- function(x, file, path = deprecated()) {
      if (lifecycle::is_present(path)) {
        lifecycle::deprecate_warn("1.4.0", "write_file(path)", "write_file(file)")
        file <- path
      }
      # ... rest of function
    }
    ```
    
    ## Renaming an Argument
    
    ```r
    add_two <- function(x, y, na_rm = TRUE, na.rm = deprecated()) {
      if (lifecycle::is_present(na.rm)) {
        lifecycle::deprecate_warn("1.0.0", "add_two(na.rm)", "add_two(na_rm)")
        na_rm <- na.rm
      }
      sum(x, y, na.rm = na_rm)
    }
    ```
    
    ## Superseding a Function
    
    For functions with better alternatives that shouldn't be removed:
    
    ```r
    #' Gather columns into key-value pairs
    #'
    #' @description
    #' `r lifecycle::badge("superseded")`
    #'
    #' Development on `gather()` is complete. For new code, use [pivot_longer()].
    #'
    #' `df %>% gather("key", "value", x, y, z)` is equivalent to
    #' `df %>% pivot_longer(c(x, y, z), names_to = "key", values_to = "value")`.
    ```
    
    No warning needed - just document the preferred alternative.
    
    ## Marking as Experimental
    
    ```r
    #' @description
    #' `r lifecycle::badge("experimental")`
    cool_function <- function() {
      lifecycle::signal_stage("experimental", "cool_function()")
      # ...
    }
    ```
    
    ## Testing Deprecations
    
    Test that deprecated functions work and warn appropriately:
    
    ```r
    test_that("old_fun is deprecated", {
      expect_snapshot({
        x <- old_fun(1)
        expect_equal(x, expected_value)
      })
    })
    ```
    
    Suppress warnings in existing tests:
    
    ```r
    test_that("old_fun returns correct value", {
      withr::local_options(lifecycle_verbosity = "quiet")
      expect_equal(old_fun(1), expected_value)
    })
    ```
    
    ## Deprecation Helpers
    
    For deprecations affecting many functions (e.g., removing a common argument), create an internal helper:
    
    ```r
    warn_for_verbose <- function(
      verbose = TRUE,
      env = rlang::caller_env(),
      user_env = rlang::caller_env(2)
    ) {
      if (!lifecycle::is_present(verbose) || isTRUE(verbose)) {
        return(invisible())
      }
    
      lifecycle::deprecate_warn(
        when = "2.0.0",
        what = I("The `verbose` argument"),
        details = c(
          "Set `options(mypkg_quiet = TRUE)` to suppress messages.",
          "The `verbose` argument will be removed in a future release."
        ),
        user_env = user_env
      )
    
      invisible()
    }
    ```
    
    Then use in affected functions:
    
    ```r
    my_function <- function(..., verbose = deprecated()) {
      warn_for_verbose(verbose)
      # ...
    }
    ```
    
    ## Custom Deprecation Messages
    
    For non-standard deprecations, use `I()` to wrap custom text:
    
    ```r
    lifecycle::deprecate_warn(
      when = "1.0.0",
      what = I('Setting option "pkg.opt" to "foo"'),
      with = I('"pkg.new_opt"')
    )
    ```
    
    The `what` fragment must work with "was deprecated in..." appended.
    
    ## Reference
    
    See `references/lifecycle-stages.md` for detailed stage definitions and transitions.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related