Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
ecabb4b
feat: extend linter config and add auto-changelog workflow
bczech May 8, 2026
0e5ec6c
docs: add CONTRIBUTING.md with Conventional Commits guidelines and no…
bczech May 8, 2026
a32516e
chore: bump version to 1.11.3 and update NEWS.md
bczech May 8, 2026
5cdd0ca
docs: update style_guide.Rmd with new linting rules and fix SemVer de…
bczech May 8, 2026
a48c980
chore: remove CONTRIBUTING.md in favor of style_guide.Rmd vignette
bczech May 8, 2026
f4430e4
chore: update NEWS.md
bczech May 8, 2026
10b08ef
feat: add testthat linters and document all active linting rules in s…
bczech May 8, 2026
a5faedf
chore: simplify NEWS.md entry for 1.11.3
bczech May 8, 2026
cda7a66
refactor: unify linter config by bumping lintr requirement to >= 3.2.0
bczech May 9, 2026
d71fe13
fix: resolve shell injection in auto-changelog and stale CONTRIBUTING…
bczech May 9, 2026
ec809ed
fix: avoid f-string interpolation of diff content in auto-changelog
bczech May 11, 2026
6f8a853
feat: add lintNewsEntries to enforce NEWS.md entry style
bczech May 11, 2026
68a3d78
chore: revert version bump, add NEWS entry to 1.11.3
bczech May 11, 2026
8f773f5
feat: set max_chars=120 and add max_bullets=3 rule per NEWS section
bczech May 11, 2026
b60626b
feat: forbid Jira ticket references in NEWS.md entries
bczech May 11, 2026
68042cf
feat: forbid nrow/ncol in favor of NROW/NCOL
bczech May 11, 2026
9c87422
fix: add cyclocomp to Imports to ensure cyclocomp_linter works
bczech May 13, 2026
19dd60e
ci: trigger build
bczech May 14, 2026
754dcf2
fix: export lintNewsEntries, fix self-lint violations, allow <<-/:::
bczech May 15, 2026
4a4dba7
ci: trigger build
bczech May 18, 2026
15a61f5
chore: bump version and update NEWS.md
bczech May 18, 2026
d390c4d
ci: trigger build
bczech May 19, 2026
52c1b2a
refactor: revert cyclocomp refactoring, defer to separate PR
bczech May 20, 2026
38d0f32
docs: update NEWS.md entry wording
bczech May 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 154 additions & 0 deletions .github/workflows/auto-changelog.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
name: auto-changelog

on:
workflow_call:
secrets:
ANTHROPIC_API_KEY:
required: true
PRIVATE_ACCESS_TOKEN:
required: true

jobs:
auto-changelog:
runs-on: ubuntu-24.04
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.PRIVATE_ACCESS_TOKEN }}
ref: ${{ github.head_ref }}

- name: Check if version bump and NEWS.md entry are present
id: check
run: |
PKG_NAME=$(grep "^Package:" DESCRIPTION | sed 's/Package: //')
CURRENT_VERSION=$(grep "^Version:" DESCRIPTION | sed 's/Version: //')
MAIN_VERSION=$(git show origin/main:DESCRIPTION | grep "^Version:" | sed 's/Version: //')

echo "pkg_name=$PKG_NAME" >> "$GITHUB_OUTPUT"
echo "current_version=$CURRENT_VERSION" >> "$GITHUB_OUTPUT"
echo "main_version=$MAIN_VERSION" >> "$GITHUB_OUTPUT"

VERSION_BUMPED=false
NEWS_OK=false

if [ "$CURRENT_VERSION" != "$MAIN_VERSION" ]; then
VERSION_BUMPED=true
fi

if grep -q "^## $PKG_NAME $CURRENT_VERSION" NEWS.md; then
NEWS_OK=true
fi

if [ "$VERSION_BUMPED" = "true" ] && [ "$NEWS_OK" = "true" ]; then
echo "skip=true" >> "$GITHUB_OUTPUT"
else
echo "skip=false" >> "$GITHUB_OUTPUT"
fi

- name: Generate version bump and NEWS entry via Claude
if: steps.check.outputs.skip == 'false'
id: claude
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
PKG_NAME="${{ steps.check.outputs.pkg_name }}"
MAIN_VERSION="${{ steps.check.outputs.main_version }}"

# Limit diff to 6000 chars to stay within token limits; write to file to avoid shell injection
git diff origin/main...HEAD -- . \
':(exclude)NEWS.md' \
':(exclude)DESCRIPTION' \
':(exclude)*.lock' \
':(exclude)*.rdb' \
| head -c 6000 > /tmp/pr_diff.txt

PKG_NAME_ENV="$PKG_NAME" MAIN_VERSION_ENV="$MAIN_VERSION" python3 - <<PYEOF
import os, json, urllib.request

pkg_name = os.environ["PKG_NAME_ENV"]
main_version = os.environ["MAIN_VERSION_ENV"]

with open("/tmp/pr_diff.txt") as f:
diff = f.read()

prompt = (
f"You are a changelog writer for an R package called '{pkg_name}'"
f" (current version on main: {main_version}).\n\n"
"Based on the following git diff from a pull request, determine:\n"
"1. The version bump type: patch (bug fixes, docs, refactoring),"
" minor (new features), major (breaking changes)\n"
"2. One short, clear bullet point for NEWS.md describing what changed"
" (start with a verb, no period at end)\n\n"
'Respond ONLY with valid JSON, no other text:\n'
'{"bump_type": "patch|minor|major", "entry": "short description"}\n\n'
"Git diff:\n" + diff[:5000]
)

payload = json.dumps({
"model": "claude-haiku-4-5-20251001",
"max_tokens": 256,
"messages": [{"role": "user", "content": prompt}]
}).encode()

req = urllib.request.Request(
"https://api.anthropic.com/v1/messages",
data=payload,
headers={
"x-api-key": os.environ["ANTHROPIC_API_KEY"],
"anthropic-version": "2023-06-01",
"content-type": "application/json"
}
)

with urllib.request.urlopen(req) as resp:
result = json.loads(resp.read())

parsed = json.loads(result["content"][0]["text"])
bump_type = parsed["bump_type"]
entry = parsed["entry"]

with open(os.environ["GITHUB_OUTPUT"], "a") as f:
f.write(f"bump_type={bump_type}\n")
f.write(f"entry={entry}\n")

print(f"bump_type={bump_type}, entry={entry}")
PYEOF

- name: Bump version in DESCRIPTION and prepend NEWS.md entry
if: steps.check.outputs.skip == 'false'
run: |
PKG_NAME="${{ steps.check.outputs.pkg_name }}"
MAIN_VERSION="${{ steps.check.outputs.main_version }}"
BUMP_TYPE="${{ steps.claude.outputs.bump_type }}"
ENTRY="${{ steps.claude.outputs.entry }}"
DATE=$(date +%Y-%m-%d)

MAJOR=$(echo "$MAIN_VERSION" | cut -d. -f1)
MINOR=$(echo "$MAIN_VERSION" | cut -d. -f2)
PATCH=$(echo "$MAIN_VERSION" | cut -d. -f3)

if [ "$BUMP_TYPE" = "major" ]; then
NEW_VERSION="$((MAJOR + 1)).0.0"
elif [ "$BUMP_TYPE" = "minor" ]; then
NEW_VERSION="${MAJOR}.$((MINOR + 1)).0"
else
NEW_VERSION="${MAJOR}.${MINOR}.$((PATCH + 1))"
fi

sed -i "s/^Version: .*/Version: $NEW_VERSION/" DESCRIPTION
sed -i "s/^Date: .*/Date: $DATE/" DESCRIPTION

printf '## %s %s - %s\n* %s\n\n' "$PKG_NAME" "$NEW_VERSION" "$DATE" "$ENTRY" \
| cat - NEWS.md > NEWS.md.tmp && mv NEWS.md.tmp NEWS.md

- name: Commit and push
if: steps.check.outputs.skip == 'false'
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add DESCRIPTION NEWS.md
git commit -m "chore: bump version and update NEWS.md"
git push
23 changes: 23 additions & 0 deletions .github/workflows/conventional-commit-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: conventional-commit-check

on:
workflow_call:

jobs:
check-pr-title:
runs-on: ubuntu-24.04
steps:
- name: Check PR title format
env:
PR_TITLE: ${{ github.event.pull_request.title }}
run: |
PATTERN='^(feat|fix|refactor|docs|test|chore|ci|perf|build|revert)(\([^)]+\))?: .+'
if echo "$PR_TITLE" | grep -qE "$PATTERN"; then
echo "PR title follows Conventional Commits: '$PR_TITLE'"
else
echo "::warning::PR title '$PR_TITLE' does not follow Conventional Commits format."
echo "::warning::Expected: <type>: <description> (e.g. 'feat: add new linter')"
echo "::warning::Valid types: feat, fix, refactor, docs, test, chore, ci, perf, build, revert"
Comment thread
bczech marked this conversation as resolved.
echo "::warning::See the style guide vignette for details. This check is informational only."
fi
exit 0
7 changes: 4 additions & 3 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
Package: gDRstyle
Type: Package
Title: A package with style requirements for the gDR suite
Version: 1.11.2
Date: 2026-05-05
Version: 1.11.4
Date: 2026-05-08
Authors@R: c(
person("Allison", "Vuong", role=c("aut")),
person("Dariusz", "Scigocki", role=c("aut")),
Expand All @@ -27,7 +27,8 @@ Imports:
checkmate,
desc,
git2r,
lintr (>= 3.0.0),
cyclocomp,
lintr (>= 3.2.0),
rcmdcheck,
remotes,
yaml,
Expand Down
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export(checkDependencies)
export(checkPackage)
export(installAllDeps)
export(installLocalPackage)
export(lintNewsEntries)
export(lintPkgDirs)
export(roxygen_tag_linter)
importFrom(BiocStyle,html_document)
Expand Down
10 changes: 9 additions & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## gDRstyle 1.11.4 - 2026-05-18
* apply updated gDRstyle rules

## gDRstyle 1.11.3 - 2026-05-08
* extend linter config with additional rules and update style guide accordingly
* add auto-changelog GitHub Actions reusable workflow using Claude API
* add lintNewsEntries to enforce brevity and style in NEWS.md entries

## gDRstyle 1.11.2 - 2026-05-05
* support vector notation for `length` field in `note.json`

Expand Down Expand Up @@ -176,4 +184,4 @@
* fix WARNINGS and NOTES in check-

## gDRstyle 0.1.3.9 - 2023-01-05
* the note test is stricter
* the note test is stricter
32 changes: 16 additions & 16 deletions R/build_tools.R
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ setReposOpt <- function(additionalRepos = NULL) {
#' @return \code{NULL} or info about error
#' @keywords internal
#' @noRd
setTokenVar <- function(base_dir,
setTokenVar <- function(base_dir,
filename = ".github_access_token.txt") {
# Use GitHub access_token if available
gh_access_token_file <- file.path(base_dir, filename)
Expand Down Expand Up @@ -51,7 +51,7 @@ setTokenVar <- function(base_dir,
#' @return \code{NULL} or info about error
#' @keywords internal
#' @noRd
verify_version <- function(name,
verify_version <- function(name,
required_version) {
pkg_version <- utils::packageVersion(name)

Expand All @@ -67,24 +67,24 @@ verify_version <- function(name,

is_version_ok <- function(pkg_ver, req) {
valid_ver_regex <- .standard_regexps()$valid_numeric_version

req <- if (grepl(paste0("^", valid_ver_regex, "$"), req)) {
paste0("==", req)
} else {
req
}

fun <- trimws(sub("\\d.*", "", req))
req_ver <- trimws(sub(fun, "", req))

if (!grepl("==|<|>|<=|>=", fun)) stop("Invalid comparison operator")

get(fun, mode = "function")(pkg_ver, req_ver)
}

#' Create a new ssh key credential object
#'
#' @param use_ssh logical, if use ssh keys
#'
#' @param use_ssh logical, if use ssh keys
#'
#' @return A list of class cred_ssh_key
#' @keywords internal
Expand All @@ -108,7 +108,7 @@ getSshKeys <- function(use_ssh) {
#' installLocalPackage(system.file(
#' package = "gDRstyle", "tst_pkgs", "dummy_pkg"
#' ))
#'
#'
#' @return \code{NULL}
#' @keywords install
#' @export
Expand Down Expand Up @@ -189,7 +189,7 @@ installAllDeps <- function(additionalRepos = NULL,
#' @return \code{NULL}
#' @keywords internal
#' @noRd
install_cran <- function(name,
install_cran <- function(name,
pkg) {
if (is.null(pkg$repos)) {
pkg$repos <- getOption("repos")
Expand All @@ -211,7 +211,7 @@ install_cran <- function(name,
#' @return \code{NULL}
#' @keywords internal
#' @noRd
install_bioc <- function(name,
install_bioc <- function(name,
pkg) {
if (is.null(pkg$ver)) {
pkg$ver <- BiocManager::version()
Expand All @@ -232,18 +232,18 @@ install_bioc <- function(name,
#' @return \code{NULL}
#' @keywords internal
#' @noRd
install_github <- function(name,
install_github <- function(name,
pkg) {
if (is.null(pkg$ref)) {
pkg$ref <- "HEAD"
}

host_url <- if (!is.null(pkg$host)) {
pkg$host
} else {
"api.github.com"
}

remotes::install_github(
repo = pkg$url,
ref = pkg$ref,
Expand All @@ -264,8 +264,8 @@ install_github <- function(name,
#' @return \code{NULL}
#' @keywords internal
#' @noRd
install_git <- function(name,
pkg,
install_git <- function(name,
pkg,
keys) {
remotes::install_git(
url = pkg$url,
Expand All @@ -286,7 +286,7 @@ install_git <- function(name,
#' @return \code{NULL}
#' @keywords internal
#' @noRd
install_gitlab <- function(name,
install_gitlab <- function(name,
pkg) {
repo <- paste(tempdir(), "install_pkg_git", name, sep = .Platform$file.sep)
url <- if (!is.null(pkg$url) && grepl("code.roche.com", pkg$url)) {
Expand Down
3 changes: 2 additions & 1 deletion R/check.R
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ test_notes_check <- function(check_results,

if (!all(is_note_valid)) {
stop("Check found unexpected NOTEs: \n",
paste0(check_results$notes[!is_note_valid], collapse = " "))
paste(check_results$notes[!is_note_valid], collapse = " "))
}
}
}
Expand Down Expand Up @@ -233,6 +233,7 @@ checkPackage <- function(pkgName,
utils::timestamp()
with_shiny <- file.exists(file.path(pkgDir, "inst", "shiny"))
gDRstyle::lintPkgDirs(pkgDir, shiny = with_shiny)
gDRstyle::lintNewsEntries(pkgDir)
} else {
message("Lint skipped")
}
Expand Down
Loading
Loading