From f633dc5ea2545bbe1285b6a43822fd14329a73e7 Mon Sep 17 00:00:00 2001 From: Douglas Ezra Morrison Date: Tue, 26 May 2026 19:09:02 -0700 Subject: [PATCH 1/6] feat: add reprexes skill (.claude/skills/reprexes) --- .claude/skills/reprexes/SKILL.md | 128 +++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 .claude/skills/reprexes/SKILL.md diff --git a/.claude/skills/reprexes/SKILL.md b/.claude/skills/reprexes/SKILL.md new file mode 100644 index 000000000..a669c034b --- /dev/null +++ b/.claude/skills/reprexes/SKILL.md @@ -0,0 +1,128 @@ +--- +name: reprexes +description: Isolate a technical problem into a minimal reproducible example ("reprex") and iterate fixes on that instead of inside the full application. Use when debugging a bug whose cause isn't obvious after a first look, when a failure only surfaces deep in a large pipeline / app / render, when the full-context test loop is slow, or before filing an upstream issue. Invoke explicitly with /reprexes. +user-invocable: true +allowed-tools: + - Bash + - Read + - Write + - Edit +--- + +# reprexes + +When a technical problem is hard to pin down, **don't debug it in the full +context of the motivating application.** Extract a minimal, self-contained +reproduction of the phenomenon, iterate candidate fixes on *that* (a fast, +clean loop), then port the working fix back to the real code. + +Reference: . The +payoff is real: ~80% of the time, the act of building a thorough reprex +surfaces the cause on its own — the noise you strip away was hiding it. + +## When this fires + +- A bug whose cause isn't obvious after a first read of the code. +- A failure that only shows up deep inside a large pipeline, app, or a full + `quarto render` — where each reproduction attempt is slow. +- Iterating on a fix that's expensive to test in the full context. +- Before asking a human for help or filing an upstream/package issue — a + reprex is what they'll ask for anyway. + +Don't bother for a one-line obvious fix, or a problem you can already see and +test cheaply in place. + +## The two requirements + +A reprex is only useful if it is BOTH: + +1. **Reproducible** — it captures *everything* needed to trigger the + phenomenon: every `library()` call, and code that creates every object it + uses. Someone (or a fresh session) can run it cold and see the same thing. +2. **Minimal** — everything unrelated to the phenomenon is stripped out: + small or built-in data instead of the real dataset, only the lines that + matter. + +The tension between these two is the whole game: include enough to reproduce, +but nothing more. + +## Procedure + +1. **Hypothesize the minimal trigger.** What is the smallest combination of + data + operation you believe causes the phenomenon? +2. **Create a standalone scratch file** outside the repo tree (e.g. + `/tmp/reprex.R`, or a tiny `/tmp/reprex.qmd` for a render bug). Put in it, + in order: + - the package loads (`library(...)`), + - the minimal data (see tactics below), + - the minimal code that triggers the phenomenon, with a comment marking + where it goes wrong. +3. **Run it in a clean session** and confirm it reproduces. For R, run with + `Rscript /tmp/reprex.R` (a fresh process — no stale `globalenv()` state + masking or faking the bug). For a Quarto page, render just that file: + `quarto render /tmp/reprex.qmd --to html`, not the whole site. +4. **Minimize.** Remove pieces until the phenomenon disappears — the last + removal that "fixes" it implicates that piece. (Or build up from nothing + until it appears.) Keep the data as small as it can be while still failing. +5. **Iterate fixes on the reprex**, not the full app. This is the fast loop + the whole technique exists to create. +6. **Port the fix back** to the real code and verify it there. +7. **Clean up.** Delete the scratch file (it lives in `/tmp`, so it never + touches the repo). If the bug was subtle, consider promoting the reprex + into a real regression test (`testthat`) instead of discarding it. + +## Minimizing the data + +- Prefer a **built-in dataset** (`mtcars`, `iris`, `mpg`) or a hand-built + tiny frame over the real data. +- If you must use a slice of real data, serialize the minimal slice with + `dput()` so the reprex recreates it inline — no external file dependency. +- Shrink to the fewest rows/columns that still show the phenomenon. + +## R / Quarto specifics + +- In R packages and Quarto projects, reprexes are usually short R snippets or + a single standalone `.qmd` page. Respect the repo's lint config + (`.lintr` / `.lintr.R`) if the reprex code will be ported back. +- The **`reprex` package** (tidyverse, ) + formats a reprex for sharing: it runs your code in a clean, separate R + session via `rmarkdown::render()` and emits code **plus actual output**. + Copy the code and call `reprex::reprex()` (reads the clipboard by default), + or point it at a file with `reprex(input = "/tmp/reprex.R")` — handy from a + non-interactive CLI session where there's no clipboard. Useful arguments: + - `venue =` — output format: `"gh"` (GitHub-flavored Markdown, default), + `"so"`/`"ds"` (Stack Overflow / Discourse), `"slack"`, `"R"` (runnable + script with commented output), `"html"`, `"rtf"`. + - `session_info = TRUE` — append `sessionInfo()` / `sessioninfo::session_info()`, + so versions travel with the reprex (set this when the bug may be + version-dependent). + - `std_out_err = TRUE` — capture stdout/stderr too (for output that doesn't + come back as normal R results). + - `wd =` — set the working directory when the code needs one. + - Use this when the reprex is destined for a PR comment or an upstream + issue. Companion helpers clean up "wild-caught" reprexes: `reprex_clean()` + (strip a GitHub/SO paste), `reprex_rescue()` (from console output), and + `reprex_invert()` (recover clean code from a rendered reprex). + - Validation bonus: because `reprex()` runs in a fresh session, if it errors + on a missing object or package, your example wasn't actually + self-contained — fix that before sharing. +- When the bug might be **version-dependent**, capture `sessionInfo()` (or + run `tidyverse_update()`) in the reprex so versions are part of the record. +- Build artifacts (`_site/`, `_freeze/`, `.quarto/`) are common confounders + for "it renders differently" bugs — a clean standalone render sidesteps + stale freeze caches. + +## Before declaring the reprex good + +- A fresh session runs it top-to-bottom and shows the phenomenon (and nothing + else breaks first). +- It references no object, file, or option it didn't create itself. +- It's as small as you can make it and still reproduce. + +## Don't + +- Don't paste the entire app/module — that's the opposite of a reprex. +- Don't commit scratch reprex files; keep them in `/tmp` or a gitignored + scratch path. +- Don't iterate fixes in the slow full-context loop once you have a reprex + that reproduces. From 0cc8e3ed72406b6b0ab44762feaf576c5f623b4b Mon Sep 17 00:00:00 2001 From: Douglas Ezra Morrison Date: Tue, 26 May 2026 21:06:11 -0700 Subject: [PATCH 2/6] reprexes skill: sync canonical (fold description; callr/tidyverse_update/lintr accuracy fixes) --- .claude/skills/reprexes/SKILL.md | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/.claude/skills/reprexes/SKILL.md b/.claude/skills/reprexes/SKILL.md index a669c034b..91eb513c0 100644 --- a/.claude/skills/reprexes/SKILL.md +++ b/.claude/skills/reprexes/SKILL.md @@ -1,6 +1,12 @@ --- name: reprexes -description: Isolate a technical problem into a minimal reproducible example ("reprex") and iterate fixes on that instead of inside the full application. Use when debugging a bug whose cause isn't obvious after a first look, when a failure only surfaces deep in a large pipeline / app / render, when the full-context test loop is slow, or before filing an upstream issue. Invoke explicitly with /reprexes. +description: > + Isolate a technical problem into a minimal reproducible example ("reprex") + and iterate fixes on that instead of inside the full application. Use when + debugging a bug whose cause isn't obvious after a first look, when a failure + only surfaces deep in a large pipeline / app / render, when the full-context + test loop is slow, or before filing an upstream issue. Invoke explicitly + with /reprexes. user-invocable: true allowed-tools: - Bash @@ -17,8 +23,8 @@ reproduction of the phenomenon, iterate candidate fixes on *that* (a fast, clean loop), then port the working fix back to the real code. Reference: . The -payoff is real: ~80% of the time, the act of building a thorough reprex -surfaces the cause on its own — the noise you strip away was hiding it. +payoff is real: often, the act of building a thorough reprex surfaces the +cause on its own — the noise you strip away was hiding it. ## When this fires @@ -73,7 +79,7 @@ but nothing more. ## Minimizing the data -- Prefer a **built-in dataset** (`mtcars`, `iris`, `mpg`) or a hand-built +- Prefer a **built-in dataset** (`mtcars`, `mpg`) or a hand-built tiny frame over the real data. - If you must use a slice of real data, serialize the minimal slice with `dput()` so the reprex recreates it inline — no external file dependency. @@ -82,11 +88,12 @@ but nothing more. ## R / Quarto specifics - In R packages and Quarto projects, reprexes are usually short R snippets or - a single standalone `.qmd` page. Respect the repo's lint config - (`.lintr` / `.lintr.R`) if the reprex code will be ported back. + a single standalone `.qmd` page. Respect the repo's lint config if the + reprex code will be ported back. - The **`reprex` package** (tidyverse, ) formats a reprex for sharing: it runs your code in a clean, separate R - session via `rmarkdown::render()` and emits code **plus actual output**. + session (via `callr` since reprex 2.0) and emits code **plus actual + output**. Copy the code and call `reprex::reprex()` (reads the clipboard by default), or point it at a file with `reprex(input = "/tmp/reprex.R")` — handy from a non-interactive CLI session where there's no clipboard. Useful arguments: @@ -106,8 +113,11 @@ but nothing more. - Validation bonus: because `reprex()` runs in a fresh session, if it errors on a missing object or package, your example wasn't actually self-contained — fix that before sharing. -- When the bug might be **version-dependent**, capture `sessionInfo()` (or - run `tidyverse_update()`) in the reprex so versions are part of the record. +- When the bug might be **version-dependent**, capture `sessionInfo()` (or set + `session_info = TRUE` above) in the reprex so versions are part of the + record. If you suspect *stale* packages are the cause, `tidyverse_update()` + outside the reprex can rule that out — but it updates packages, it doesn't + record versions, so don't put it in the reprex itself. - Build artifacts (`_site/`, `_freeze/`, `.quarto/`) are common confounders for "it renders differently" bugs — a clean standalone render sidesteps stale freeze caches. From 4a1647066b3a4c4dda9d743c9b96c29f078db093 Mon Sep 17 00:00:00 2001 From: Douglas Ezra Morrison Date: Tue, 26 May 2026 21:27:39 -0700 Subject: [PATCH 3/6] reprexes skill: sync canonical (venue bullets/r, helper+std_out_err wording, tempfile note) --- .claude/skills/reprexes/SKILL.md | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/.claude/skills/reprexes/SKILL.md b/.claude/skills/reprexes/SKILL.md index 91eb513c0..c04acb461 100644 --- a/.claude/skills/reprexes/SKILL.md +++ b/.claude/skills/reprexes/SKILL.md @@ -57,8 +57,9 @@ but nothing more. 1. **Hypothesize the minimal trigger.** What is the smallest combination of data + operation you believe causes the phenomenon? 2. **Create a standalone scratch file** outside the repo tree (e.g. - `/tmp/reprex.R`, or a tiny `/tmp/reprex.qmd` for a render bug). Put in it, - in order: + `/tmp/reprex.R`, or a tiny `/tmp/reprex.qmd` for a render bug; on a + non-Unix machine use `tempfile(fileext = ".R")` / `tempdir()` for a + portable path). Put in it, in order: - the package loads (`library(...)`), - the minimal data (see tactics below), - the minimal code that triggers the phenomenon, with a comment marking @@ -97,19 +98,24 @@ but nothing more. Copy the code and call `reprex::reprex()` (reads the clipboard by default), or point it at a file with `reprex(input = "/tmp/reprex.R")` — handy from a non-interactive CLI session where there's no clipboard. Useful arguments: - - `venue =` — output format: `"gh"` (GitHub-flavored Markdown, default), - `"so"`/`"ds"` (Stack Overflow / Discourse), `"slack"`, `"R"` (runnable - script with commented output), `"html"`, `"rtf"`. + - `venue =` — output format: + - `"gh"` — GitHub-flavored Markdown (default) + - `"so"` / `"ds"` — Stack Overflow / Discourse + - `"slack"` — Slack message + - `"r"` — runnable R script with commented output + - `"html"` — HTML + - `"rtf"` — rich text for presentations - `session_info = TRUE` — append `sessionInfo()` / `sessioninfo::session_info()`, so versions travel with the reprex (set this when the bug may be version-dependent). - - `std_out_err = TRUE` — capture stdout/stderr too (for output that doesn't - come back as normal R results). + - `std_out_err = TRUE` — capture stdout/stderr too (e.g. `system()` / + subprocess or C-level output that doesn't come back as normal R results). - `wd =` — set the working directory when the code needs one. - Use this when the reprex is destined for a PR comment or an upstream - issue. Companion helpers clean up "wild-caught" reprexes: `reprex_clean()` - (strip a GitHub/SO paste), `reprex_rescue()` (from console output), and - `reprex_invert()` (recover clean code from a rendered reprex). + issue. Companion helpers handle "wild-caught" reprexes: `reprex_clean()` + (strip the prompts/output from a copied reprex), `reprex_rescue()` + (recover code from R-console output with `>`/`+` prompts), and + `reprex_invert()` (turn a rendered reprex back into plain code). - Validation bonus: because `reprex()` runs in a fresh session, if it errors on a missing object or package, your example wasn't actually self-contained — fix that before sharing. From e31546ddc4de6af24fc8a4b2ef7c612dff781c50 Mon Sep 17 00:00:00 2001 From: Douglas Ezra Morrison Date: Tue, 26 May 2026 21:51:15 -0700 Subject: [PATCH 4/6] reprexes skill: sync canonical (one-sentence-per-line intro; tidyverse::tidyverse_update()) --- .claude/skills/reprexes/SKILL.md | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/.claude/skills/reprexes/SKILL.md b/.claude/skills/reprexes/SKILL.md index c04acb461..eee63143c 100644 --- a/.claude/skills/reprexes/SKILL.md +++ b/.claude/skills/reprexes/SKILL.md @@ -22,8 +22,8 @@ context of the motivating application.** Extract a minimal, self-contained reproduction of the phenomenon, iterate candidate fixes on *that* (a fast, clean loop), then port the working fix back to the real code. -Reference: . The -payoff is real: often, the act of building a thorough reprex surfaces the +Reference: . +The payoff is real: often, the act of building a thorough reprex surfaces the cause on its own — the noise you strip away was hiding it. ## When this fires @@ -96,7 +96,8 @@ but nothing more. session (via `callr` since reprex 2.0) and emits code **plus actual output**. Copy the code and call `reprex::reprex()` (reads the clipboard by default), - or point it at a file with `reprex(input = "/tmp/reprex.R")` — handy from a + or point it at a file with `reprex(input = "/tmp/reprex.R")` (or a + `tempfile(fileext = ".R")` path on non-Unix machines) — handy from a non-interactive CLI session where there's no clipboard. Useful arguments: - `venue =` — output format: - `"gh"` — GitHub-flavored Markdown (default) @@ -112,17 +113,20 @@ but nothing more. subprocess or C-level output that doesn't come back as normal R results). - `wd =` — set the working directory when the code needs one. - Use this when the reprex is destined for a PR comment or an upstream - issue. Companion helpers handle "wild-caught" reprexes: `reprex_clean()` - (strip the prompts/output from a copied reprex), `reprex_rescue()` + issue. Companion helpers handle "wild-caught" reprexes (all exported in + reprex 2.x): `reprex_clean()` (strip the `#>` output markers from a + rendered/pasted reprex, leaving runnable code), `reprex_rescue()` (recover code from R-console output with `>`/`+` prompts), and - `reprex_invert()` (turn a rendered reprex back into plain code). + `reprex_invert()` (the inverse of `reprex()` — recover the input code + from a rendered reprex). - Validation bonus: because `reprex()` runs in a fresh session, if it errors on a missing object or package, your example wasn't actually self-contained — fix that before sharing. - When the bug might be **version-dependent**, capture `sessionInfo()` (or set `session_info = TRUE` above) in the reprex so versions are part of the - record. If you suspect *stale* packages are the cause, `tidyverse_update()` - outside the reprex can rule that out — but it updates packages, it doesn't + record. If you suspect *stale* packages are the cause, + `tidyverse::tidyverse_update()` outside the reprex can rule that out — but it + updates packages, it doesn't record versions, so don't put it in the reprex itself. - Build artifacts (`_site/`, `_freeze/`, `.quarto/`) are common confounders for "it renders differently" bugs — a clean standalone render sidesteps @@ -138,7 +142,7 @@ but nothing more. ## Don't - Don't paste the entire app/module — that's the opposite of a reprex. -- Don't commit scratch reprex files; keep them in `/tmp` or a gitignored - scratch path. +- Don't commit scratch reprex files; keep them in `/tmp` (or `tempdir()` on + non-Unix machines) or a gitignored scratch path. - Don't iterate fixes in the slow full-context loop once you have a reprex that reproduces. From 8a4cb9bc245b0993b7baa8aea1e5b81301adfd78 Mon Sep 17 00:00:00 2001 From: Douglas Ezra Morrison Date: Tue, 26 May 2026 21:58:57 -0700 Subject: [PATCH 5/6] reprexes skill: sync canonical (usage note + helpers out of args list) --- .claude/skills/reprexes/SKILL.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.claude/skills/reprexes/SKILL.md b/.claude/skills/reprexes/SKILL.md index eee63143c..b376fb3fb 100644 --- a/.claude/skills/reprexes/SKILL.md +++ b/.claude/skills/reprexes/SKILL.md @@ -98,7 +98,8 @@ but nothing more. Copy the code and call `reprex::reprex()` (reads the clipboard by default), or point it at a file with `reprex(input = "/tmp/reprex.R")` (or a `tempfile(fileext = ".R")` path on non-Unix machines) — handy from a - non-interactive CLI session where there's no clipboard. Useful arguments: + non-interactive CLI session where there's no clipboard. Use it when the + output is destined for a PR comment or an upstream issue. Useful arguments: - `venue =` — output format: - `"gh"` — GitHub-flavored Markdown (default) - `"so"` / `"ds"` — Stack Overflow / Discourse @@ -112,16 +113,15 @@ but nothing more. - `std_out_err = TRUE` — capture stdout/stderr too (e.g. `system()` / subprocess or C-level output that doesn't come back as normal R results). - `wd =` — set the working directory when the code needs one. - - Use this when the reprex is destined for a PR comment or an upstream - issue. Companion helpers handle "wild-caught" reprexes (all exported in - reprex 2.x): `reprex_clean()` (strip the `#>` output markers from a - rendered/pasted reprex, leaving runnable code), `reprex_rescue()` - (recover code from R-console output with `>`/`+` prompts), and - `reprex_invert()` (the inverse of `reprex()` — recover the input code - from a rendered reprex). - Validation bonus: because `reprex()` runs in a fresh session, if it errors on a missing object or package, your example wasn't actually self-contained — fix that before sharing. + + Companion helpers handle "wild-caught" reprexes (all exported in reprex + 2.x): `reprex_clean()` (strip the `#>` output markers from a rendered/pasted + reprex, leaving runnable code), `reprex_rescue()` (recover code from + R-console output with `>`/`+` prompts), and `reprex_invert()` (the inverse + of `reprex()` — recover the input code from a rendered reprex). - When the bug might be **version-dependent**, capture `sessionInfo()` (or set `session_info = TRUE` above) in the reprex so versions are part of the record. If you suspect *stale* packages are the cause, From 525375d03ce293f68cf61f6ce4804537f6b66ae1 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Wed, 27 May 2026 07:31:40 +0000 Subject: [PATCH 6/6] fix: exclude .claude from build; bump version, NEWS, WORDLIST Add ^\.claude$ to .Rbuildignore so R CMD check (error-on note) no longer flags the skills directory. Bump dev version for the version-increment gate, add a NEWS.md entry for the changelog check, and add UCD/SeRG to inst/WORDLIST to clear the pre-existing spellcheck failure on NEWS.md. Co-authored-by: Douglas Ezra Morrison --- .Rbuildignore | 1 + DESCRIPTION | 2 +- NEWS.md | 5 +++++ inst/WORDLIST | 2 ++ 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.Rbuildignore b/.Rbuildignore index eaddfcf59..4bb835c33 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -29,3 +29,4 @@ ^_quarto\.yml$ ^\.quarto$ ^\.devcontainer$ +^\.claude$ diff --git a/DESCRIPTION b/DESCRIPTION index 62ceeb270..e17794e0b 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: serodynamics Title: What the Package Does (One Line, Title Case) -Version: 0.0.0.9054 +Version: 0.0.0.9055 Authors@R: c( person("Peter", "Teunis", , "p.teunis@emory.edu", role = c("aut", "cph"), comment = "Author of the method and original code."), diff --git a/NEWS.md b/NEWS.md index 75f93cb62..dac024f13 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,10 @@ # serodynamics (development version) +* Added a project-level `Claude Code` skill, `reprexes` + (`.claude/skills/reprexes`), capturing a workflow for isolating a problem + into a minimal reproducible example and iterating fixes on it before + porting them back. The `.claude` directory is excluded from the package + build via `.Rbuildignore`. * Expanded what the `Claude Code` (`@claude`) workflow can do: - Install the full R toolchain (R, JAGS, pandoc, the apt system libs mirrored from `copilot-setup-steps.yml`, plus `devtools`, `roxygen2`, diff --git a/inst/WORDLIST b/inst/WORDLIST index 8560c8a5f..0d654dda1 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -14,7 +14,9 @@ PR's Postprocess Rhat SHA +SeRG TSI +UCD Vectorization Wasserstein allowlist