Skip to content

washr 1.2.0: readiness report, dictionary updates, Zenodo metadata, values from DESCRIPTION - #136

Open
larnsce wants to merge 27 commits into
mainfrom
dev
Open

larnsce wants to merge 27 commits into
mainfrom
dev

Conversation

@larnsce

@larnsce larnsce commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

Summary

  • washr 1.2.0, the workflow automation and generalization release (plan in Create milestones v1.0.2, v1.1.0, v1.2.0 and assign the issue set #79, direction in Design note: washr as a deterministic engine with a Claude skill as driver (review of exports and open issues before the next step) #111).
  • Three new exports: check_publication_readiness() (a read-only report of what a package still lacks, consumed by pkgreview), update_dictionary() (keeps descriptions and extra columns after a data change) and update_zenodo_json() (files a release on Zenodo as a Dataset in the community, called by update_citation()).
  • Every function reports through cli and returns what it wrote, invisibly. options(washr.quiet = TRUE) silences the messages.
  • The values that were fixed to openwashdata are read from DESCRIPTION: organisation and repository from URL, license from License, and Config/washr/ fields for funding, analytics, DOI provider, Zenodo community and brand. Output for an openwashdata package is unchanged.
  • use_brand() installs the brand at its latest release tag and records it in DESCRIPTION.
  • A new workflow, scaffold-check, builds a fixture package with every step, runs the steps twice, and runs the pkgreview check script at the version in PKGREVIEW_FLOOR.
  • Nine defects found by an independent review of the candidate are fixed, each with a regression test.

Closes #84
Closes #82
Closes #81
Closes #128
Closes #13
Closes #56
Closes #129
Closes #131
Closes #133
Closes #134
Closes #122

Not in this PR: the washr skill (#115, #83) lives in the pkgreview plugin on its branch feat/washr-1.2.0, the guide rework (#85, #80) waits for the new guide name, and the multi dataset loop of #103 is in the skill while the vignette carries the note.

Test plan

  • devtools::test(): 640 pass, 0 fail
  • devtools::check() with the CRAN settings: 0 errors, 0 warnings, 0 notes
  • urlchecker::url_check(): all URLs correct
  • pkgdown::check_pkgdown(): no problems
  • Scaffold check locally: the fixture builds, the second run changes no file, the pkgreview check script reports 0 required and 0 advisory failures
  • R-CMD-check on the five runners and scaffold-check are green on this PR
  • The publishing guide is synced against the 1.2.0 API (release checklist step 6)
  • devtools::submit_cran() from main and the confirmation email (maintainer)
  • After acceptance: tag v1.2.0, usethis::use_dev_version() on dev, sync dev with main
  • After pkgreview 1.7.0 is released: raise PKGREVIEW_FLOOR

A package that republishes data from a published article lists the
article DOI in DESCRIPTION as X-schema.org-isBasedOn, next to the
keywords and coverage fields. update_citation() looks the DOI up at
doi.org and writes it as a CITATION.cff reference, with a message that
asks users to cite both the data package and the article. inst/CITATION
carries both entries under that message. The package stays the work
that GitHub's "Cite this repository" shows. update_metadata() writes the
same DOIs as schema.org isBasedOn.

The field replaces the Config/washr/derived-from proposal in the issue,
because the 2026-09-02 amendment of the canonical sources decision keeps
schema.org facts under the X-schema.org- prefix. References that do not
come from the field are dropped, so a rerun gives the same files. A DOI
that cannot be looked up keeps its entry from the existing CITATION.cff.

Closes #134

Assisted-by: Claude claude-fable-5-1
dev carries unreleased work for 1.2.0 (the source article citation,
#134) on top of the 1.1.1 release, and still said 1.1.1. The
development heading in NEWS.md is already in place.

Assisted-by: Claude claude-fable-5-1
The washr skill (#115) parses the output of every function it runs, so
messages need one structure and return values need to be real. Messages
now go through three internal helpers on top of cli: a success line for
a file written, an info line for something kept or skipped, an arrow
line for the next step left to the user. Errors use cli::cli_abort()
with a hint, and the three copies of the working directory check became
check_pkg_root(), which names the function the user called.

Every exported function returns what it wrote, invisibly, and @returns
says so. options(washr.quiet = TRUE) silences washr's messages and the
usethis helpers it calls, never warnings or errors.

The usethis::ui_*() calls are gone (75 of them). usethis stays in
Imports for the use_*() helpers.

Closes #84

Assisted-by: Claude claude-fable-5-1
use_brand() wrote _brand.yml and the logo directory at the top level
without adding them to .Rbuildignore, so the next R CMD check listed
both as non-standard top-level files. The directory names come from
the logo paths, in case the brand repository moves the files.

Closes #133

Assisted-by: Claude claude-fable-5-1
washr is listed in the openwashdata R-universe registry, which serves
a build of the development version that installs without a compiler
toolchain. The README and the vignette now say what CRAN, R-universe
and GitHub each give you.

Closes #131

Assisted-by: Claude claude-fable-5-1
Zenodo's GitHub integration reads only a few fields of CITATION.cff and
has no mapping for its type, so every data package release landed as
Software in no community, and the record had to be edited by hand. When
a repository carries a .zenodo.json, Zenodo uses that file for the
record in place of CITATION.cff.

update_zenodo_json() writes the file from the same DESCRIPTION the
citation files come from: title, description, upload_type dataset,
creators (aut and cre) with ORCID and affiliation, the license as
Zenodo's identifier, keywords, version, the community, and the source
articles of X-schema.org-isBasedOn as related identifiers with the
relation isDerivedFrom. update_citation() calls it, so one step writes
all three files and they cannot drift apart. The file is written as
bytes (UTF-8, LF), so a second run is identical on every platform.

The community is the first value read through washr_config(), the new
reader for Config/washr/<key> fields in DESCRIPTION (#81). It defaults
to openwashdata, takes several values, and "none" for no community.

The license URLs of the JSON-LD and the Zenodo identifiers now share one
table, license_map, so the two cannot disagree about which licenses
washr knows. An unknown license is left out of the file, with a note
that Zenodo then falls back to the license GitHub detects, or CC0 1.0
for a dataset.

Closes #56

Assisted-by: Claude claude-fable-5-1
Until now a changed dataset left two choices: edit dictionary.csv by
hand, or delete it and lose every description, because
setup_dictionary() refuses an existing file and fill_dictionary()
rebuilds the five columns from the data without reading what is there.

update_dictionary() reads the file, merges it with the variables in
data/ and writes only when the content differs. Rows are matched on
file_name and variable_name. A matched row keeps its description and
every other cell, including columns the user added (unit,
allowed_values), which is the round trip openwashdata/pkgreview#37
waits for. Only variable_type is refreshed. New variables get an empty
row, removed ones are named, and when a data object has both, the cells
written for the removed variable are printed, since a rename cannot be
told from a removal plus an addition.

The file is read cell by cell as text, so codes and the string NA come
back as typed, and it is written as UTF-8 bytes in the layout of
write.csv(), independent of the session encoding. A file whose content
is current is left untouched, whatever its quoting or line ends.

The "already exists" error of setup_dictionary() now names the new
function.

Closes #13

Assisted-by: Claude claude-fable-5-1
The wiring rewrote _pkgdown.yml through the yaml package, which dropped
the comments of the template and folded the funding text over two
lines. The pkgreview check script looks for that text verbatim, so a
freshly scaffolded package failed the funding item after use_brand().
The scaffold check found it on its first run.

The brand lines are now inserted under the template block and the rest
of the file stays as written. The yaml rewrite remains as the fallback
when the template block already carries bslib settings or the edited
file does not parse to the intended configuration.

Refs #129

Assisted-by: Claude claude-fable-5-1
Nothing tested the claim that a freshly scaffolded package passes the
required tier of the review standard, or that a second run of the
steps changes no file. The new workflow builds a fixture with every
washr step in vignette order, runs the re-runnable steps again and
compares the tree, then runs pkgreview-check.R at the tag recorded in
PKGREVIEW_FLOOR with the openwashdata profile.

dev/release-checklist.md writes down the release steps from #78 and
adds the floor bump.

Closes #129

Assisted-by: Claude claude-fable-5-1
NEWS entries, the reference index and the README function list for the
two functions merged from their feature branches. The scaffold check
runs update_dictionary() in its second pass.

Refs #13, #56

Assisted-by: Claude claude-fable-5-1
A skipped workflow step used to surface as an opaque error in a later
function, and the guide had to warn about it. The new function reads the
package and reports, item by item, what is in place, what is missing and
which step closes each gap. It writes nothing.

The items are the mechanical part of the openwashdata review standard:
the metadata, docs and tests lines that pkgreview froze in its check
script for this function (openwashdata/pkgreview#66), the dictionary
checks pkgreview offered to hand over, and three items of washr's own
(placeholder text in DESCRIPTION, data in data/, a help page per data
object). Wording and details follow the script, so pkgreview can swap
its frozen sections for one call and map the rows one to one. On the
pkgreview fixture, its nine cases and three published packages, every
mapped line agrees in status and detail.

The result is a data frame of class washr_readiness with one row per
item (id, area, check, status, detail, fix) and the attributes ready,
washr_version and package. An item that cannot be decided is "na" with
the reason, never a failure. The profile argument carries what an
organisation expects of the site (analytics, Pages URL pattern, funding
text, required keywords, brand), which pkgreview fills from its org
profile. The report goes through cli and respects washr.quiet.

Each item is one internal function, so a later step can run a single
gate. Wiring those gates into the workflow functions is left for a
follow-up.

Closes #82

Assisted-by: Claude claude-fable-5-1
Zenodo takes the record of a release from .zenodo.json, version
included, so a file that lags behind DESCRIPTION files the release
under old values. The readiness report gains an item that compares the
file with what DESCRIPTION gives now. The dictionary coverage item
points at update_dictionary().

NEWS, the reference index and the README name the function, and the
scaffold check stops when the report of the fixture has a failing item.

Refs #82, #56, #13

Assisted-by: Claude claude-fable-5-1
The first scaffold-check run stopped at devtools::build_readme(): the
README template loads dplyr, stringr, gt and kableExtra, which are not
dependencies of washr.

Refs #129

Assisted-by: Claude claude-fable-5-1
washr carried about thirty openwashdata facts in its functions and
templates, so a group outside openwashdata could not publish with it
without editing the output by hand. The organisation, the repository
and the license are now read from the standard DESCRIPTION fields, and
the values that have no standard field live in Config/washr fields
that update_description() writes once and never overwrites: funding,
analytics-domain, doi-provider, zenodo-community and brand-source,
plus pages-domain for a site outside <org>.github.io. A field set to
none switches its feature off. For a package under another
organisation the funding, analytics, community and brand fields are
written as none, because the openwashdata values would make false
statements there. Config/washr/version records the washr version.

The README and _pkgdown.yml templates, the DOI badge, .zenodo.json,
use_brand() and the default profile of check_publication_readiness()
read these values. For an openwashdata package the output is
unchanged; the scaffold check still passes the review standard.

use_brand() installs the brand at the latest release tag of the brand
repository and records it in Config/washr/brand, where it copied from
the main branch before. The tag comes from the redirect of the
releases page, because the GitHub API rate limit is exhausted quickly
on a shared network address.

The vignette gains a section for groups outside openwashdata and a
note on packages with more than one dataset.

Closes #81
Closes #128
Refs #103

Assisted-by: Claude claude-fable-5-1
utils::write.csv() writes CRLF line ends on Windows whatever eol says,
so the reference file of the byte comparison differed from the LF file
that update_dictionary() writes on every platform. R CMD check failed
on the Windows runner only.

Refs #13

Assisted-by: Claude claude-fable-5-1
An independent review of the 1.2.0 candidate confirmed nine defects by
running them. Each has a regression test in test_edge_cases.R.

- update_description(github_user = ) after a first run left the package
  under the old organisation. The repository in URL is now replaced and
  the Config/washr fields that still hold the old defaults follow.
- The usethis helpers wrote into the package of an earlier call when
  only the working directory had changed. Every exported function now
  sets the active project to the working directory first.
- The funding item of the readiness report compared raw lines, so a
  quoted or folded funding text failed. It now also reads the value as
  YAML does.
- The dictionary coverage item ignored the data object a variable
  belongs to.
- A renamed data object lost its descriptions without the rename hint.
- A repository URL with a .git suffix, a fragment, a trailing slash or
  www. gave a wrong bug report link or a second URL entry.
- setup_readme(force = TRUE) deleted README.Rmd before it knew it could
  write a new one.
- washr.quiet left the cffr messages of update_citation().
- update_description(file = "DESCRIPTION") skipped the license.

Also from the agents that built on the candidate: a subset of the
readiness report is a plain data frame (a column subset printed a wrong
verdict), text files with invalid UTF-8 bytes are still matched, and a
package of another organisation is held to its own license.

Refs #81, #82, #13

Assisted-by: Claude claude-fable-5-1
Version and date, the NEWS section grouped the way 1.1.0 was, and the
cran-comments section. The submission itself waits for the guide sync
and is done by the maintainer.

Assisted-by: Claude claude-fable-5-1

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment