[This article was first published on rOpenSci - open tools for open science, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.The goodpractice package has been recommended by rOpenSci since it was first started just over 10 years ago by Gábor Csárdi.We used to ask our editors to manually run goodpractice on all packages submitted to software peer-review, and then to ask authors to fix any notable issues flagged by the package.It is now integrated within our own pkgcheck system, and used to automatically identify any goodpractice issues with all new submissions.The package changed maintainers several times before the previous maintainers gave the green light for us to take over maintenance of the package two years ago (28th May 2024).We’re really pleased to share that we’ve recently rolled out a host of updates and extensions to the package.These make it both easier to use, and more powerful.This was a collaborative effort between new package author, Athanasia Mo Mowinckel, current maintainer, Mark Padgham, and the generative AI tool Claude.We describe the process at the end of this tech note.Easier control of checksThe goodpractice package is a convenience wrapper around several other packages including rcmdcheck, lintr, cyclocomp, and desc, along with a few hand-coded checks within the package itself.The previous version of goodpractice had a total of 230 checks, generally prefixed with the name of the package which defined each check.Checks were identifiable with the single function, all_checks().The only way to control which checks were run was to pass a checks parameter to the main function as a character vector of the names of checks you wanted to run.So, for example, if you wanted to skip the rcmdcheck checks, you had to do something like this:mychecks ── It is good practice to ─────────────────────────────────────────────────────────────────────────────────────────────────────────────#>#> ✖ remove or use internal functions that are defined but never called. Dead code increases maintenance burden.#>#> R/utils.R:3#> R/utils.R:73#>#> ✖ define exported (user-facing) functions before internal helper functions within each R source file.#>#> R/api.R:85#>#> ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────Notes on the development processFrom Mo (Athanasia)I’ll be honest with you: a lot of this contribution was done with an AI assistant (Claude Code using Opus 4.5) at my elbow.I want to say something about that, because I think the honest version is more useful than the marketing version.The way Mark approached reviewing this work was very thoughtful and helpful.Based on his feedback and how it was structured, I’ve learned a lot about what a good code review looks like, and it’s been absolutely wonderful.As a person who has mainly worked on their own code-bases alone, I have adopted a style of working that often creates diff monsters (hundreds of files changed), which can make reviewing very hard.Indeed, my first PR to this project was such a monster, and Mark pushed back asking for several smaller PRs he could actually manage to review.This is also where I learned about git worktrees (Thank you Maëlle!) and asking Claude to split the work up into several worktrees for easier review, was actually the first substantial change to how I now approach working with an AI assistant.Through this collaboration I learned a lot about splitting my work into better sizes and chunks.The shape of it was something like this:I’d locate an issue I thought was meaningful to tackle, and provide Claude Code with the issue context and possibly an idea of how to solve it.Once it had a solution, I’d review it, push back where it felt off, and we’d iterate.Mark would then review the PR and point out the things I’d missed.Three-way collaboration, more or less, with the AI doing the typing and me doing the deciding.What worked well was scaffolding.Drafting a new check, wiring up the prep step that goes with it, generating the test cases for the edge paths I’d otherwise forget — that kind of work compresses really nicely with an AI.I would often ask it to generate some tests first, then create code that would pass the tests.This way we had a clear idea of what we wanted the new code to do, and then solve it.What worked less well was anything involving judgement I hadn’t articulated yet.The first few times Claude opened a PR for me, it left the test-plan checkboxes unticked — even when the tests already passed.That’s not wrong exactly, but it’s misleading to a reviewer.I had to say “no, check the box if the test exists and passes; an unchecked box reads as TODO.”Similarly, when Mark left inline code suggestions on a PR, Claude tried to helpfully re-implement them locally — which would have stripped Mark’s attribution off his own contribution.We had to agree: suggestions get accepted on GitHub, not retyped.And then there’s the unglamorous part.It would often forget instructions, despite having them documented in agents.md and local memory for the project.These were mostly trivial, but at times fairly bad.Some of the first PRs we made, Mark pointed out that the solutions were using regexp rather than AST (Abstrat Syntax Trees).I have to admit, I didn’t really know about AST before starting this project, and I am so glad I now know of it.Claude, despite being told very clearly that we wanted AST solutions, would often forget and implement regexp based solutions.It would also often revert to using for-loops rather than vectorization, and creating nested for-loops into the 3rd or 4th level — which is just horrible to follow as a human.If you take one thing from this aside, take this: AI-assisted contribution didn’t mean handing the package over to a machine.It meant I could move faster on the parts I already understood, explore more confidently on the parts I didn’t, and spend my actual attention on the decisions that mattered — what to check for, how to group it, what to call it, and what to leave out.The friction taught me where to watch closely.The speed-ups taught me where I could trust the loop.It’s a different way of working, and I’m still figuring out the shape of it, but it has enabled me to contribute to this project despite some very severe health issues making it hard for me to work as normal.And one thing Claude always does better than me: writing good commit messages.I have found myself writing my own code, but asking Claude to commit them (making it look like Claude wrote the code, but I don’t care) because the commit messages are just so much better than what I write.From MarkThe entire idea for this major update was initiated and driven by Mo.It was also my first real foray into using generative AI tools directly in rOpenSci packages, and I learnt a lot.After I rejected the initial monster PR, things settled down into much smaller and manageable PRs that were always focussed on one specific thing.From that point, Mo was effectively the coder (assisted by Claude), and I was the reviewer.I never knew how much of the actual code was typed versus machine-generated, but was generally pleasantly surprised that I felt no need to inquire.Nearly every PR was obviously going to improve the package, and many of them were genuinely creative ideas.(That’s where I disagree slightly with Mo’s general description of the process: I am convinced a lot of the work behind this update came directly from her brilliant insights and experience, rather than her passive description of looking over pre-existing PRs.)The process started in Feb 2026, and involved 70 pull requests.So many of these really were independent and new ideas.That enabled me to view each one with fresh eyes, and to think about whether I might approach anything in different ways.Over so many PRs, there was a lot of back-and-forth, from pushing back on Claude insisting on nested loops, to figuring out whether code was best parsed and analysed as text, or as a syntax tree.As Mo indicated, a lot of those discussions ultimately converged towards us bringing the power of treesitter into the package, and even helping to replace previous text-based code checks with more efficient and accurate AST approaches.Our collaboration was very pleasant and enriching, and really felt like a direct cooperation between Mo as the ideas factory, me as the reviewer, and Claude as nothing more than a mediator of our ideas.The key role of Claude throughout the process was in handling all of the fiddly, technical details.We both looked carefully at every single line of code, but the use of Claude enabled our conversations to remain at higher, conceptual levels than what would have happened if we had to actually do all the technical implementation ourselves.I think that is the aspect that I found most surprising: That the use of Claude made our collaboration feel less technical, and therefore somehow even more human.And that gave us the ability to work though 70 pull requests representing over 100 new checks, all ready for everybody to use.Let us know what you thinkLike all rOpenSci packages, goodpractice is a community effort that lives through community use and feedback.We’d love to hear what you think, via email, or through issues on the GitHub repository.To leave a comment for the author, please follow the link and comment on their blog: rOpenSci - open tools for open science.R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.Continue reading: Our goodpractice Package Has New Superpowers