From b5ac84084ad3d38110b840779955b14d2b387a11 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Tue, 22 Sep 2026 16:09:01 +1200 Subject: [PATCH 1/4] Ignore bot authors in copyright attributions. --- lib/bake/modernize/license.rb | 2 ++ test/bake/modernize/license.rb | 28 ++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/lib/bake/modernize/license.rb b/lib/bake/modernize/license.rb index bf3d1bfd..b7e71051 100644 --- a/lib/bake/modernize/license.rb +++ b/lib/bake/modernize/license.rb @@ -229,6 +229,8 @@ def initialize # Add a modification to the authorship. def add(path, author, time, id = nil) + return if author[:name].end_with?("[bot]") + modification = Modification.new(author, time, path, id) @commits[modification.key] << modification diff --git a/test/bake/modernize/license.rb b/test/bake/modernize/license.rb index 96ae3576..8313f3f4 100644 --- a/test/bake/modernize/license.rb +++ b/test/bake/modernize/license.rb @@ -262,6 +262,34 @@ def write_commit(repository, path, content, author:, message: "Commit.") expect(authorship.copyrights_for_path("lib/example.rb").map(&:statement)).to be == ["Copyright, 2026, by Samuel Williams."] end + it "ignores bot modifications in copyrights and gem authors" do + authorship.add("lib/example.rb", author, time) + authorship.add("lib/example.rb", {name: "github-actions[bot]"}, time) + authorship.add("lib/example.rb", {name: "dependabot[bot]"}, time) + + expect(authorship.sorted_authors).to be == ["Samuel Williams"] + expect(authorship.copyrights.map(&:author)).to be == ["Samuel Williams"] + expect(authorship.copyrights_for_path("lib/example.rb").map(&:author)).to be == ["Samuel Williams"] + end + + it "ignores bots from imported contributors and preserves attribution through bot renames" do + File.write(File.join(root, ".contributors.yaml"), [{ + author: {name: "dependabot[bot]", email: "bot@example.com"}, + time: time, + path: "lib/example.rb", + }].to_yaml) + + repository = Rugged::Repository.init_at(root) + write_commit(repository, "lib/example.rb", "example\n", author: author.merge(time: time)) + FileUtils.mv(File.join(root, "lib/example.rb"), File.join(root, "lib/renamed.rb")) + write_commit(repository, "lib/renamed.rb", "example\n", author: {name: "github-actions[bot]", email: "bot@example.com", time: time}) + + authorship.extract(root) + + expect(authorship.sorted_authors).to be == ["Samuel Williams"] + expect(authorship.copyrights_for_path("lib/renamed.rb").map(&:author)).to be == ["Samuel Williams"] + end + it "extracts authorship from contributors and git history" do File.write(File.join(root, ".contributors.yaml"), [{ author: {name: "Contributor Name", email: "contributor@example.com"}, From e2fcd1d2cec73b00e7a823300b6b12c71548937b Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Tue, 22 Sep 2026 16:09:01 +1200 Subject: [PATCH 2/4] Add opt-in migration to bake-gem-github releases. --- bake-modernize.gemspec | 1 + bake/modernize/contributing.rb | 8 ++- bake/modernize/releases.rb | 35 +++++++++- lib/bake/modernize.rb | 15 +++++ template/releases/{bake.rb => bake.rb.erb} | 2 + test/bake/modernize.rb | 32 +++++++++ test/bake/modernize/contributing.rb | 52 +++++++++++++++ test/bake/modernize/releases.rb | 45 ++++++++++++- test/bake/modernize/releases/github.rb | 77 ++++++++++++++++++++++ 9 files changed, 263 insertions(+), 4 deletions(-) rename template/releases/{bake.rb => bake.rb.erb} (93%) create mode 100644 test/bake/modernize/contributing.rb create mode 100644 test/bake/modernize/releases/github.rb diff --git a/bake-modernize.gemspec b/bake-modernize.gemspec index e2849302..5ad0ac73 100644 --- a/bake-modernize.gemspec +++ b/bake-modernize.gemspec @@ -31,6 +31,7 @@ Gem::Specification.new do |spec| spec.add_dependency "async-ollama", "~> 0.10" spec.add_dependency "bake" spec.add_dependency "build-files", "~> 1.6" + spec.add_dependency "erb" spec.add_dependency "markly", "~> 0.13" spec.add_dependency "rugged" end diff --git a/bake/modernize/contributing.rb b/bake/modernize/contributing.rb index 61f9674d..a687b0a8 100644 --- a/bake/modernize/contributing.rb +++ b/bake/modernize/contributing.rb @@ -55,7 +55,13 @@ def contributing def update_contributing(readme_path) root = Markly.parse(File.read(readme_path)) - replacement = Markly.parse(DEFAULT_CONTRIBUTING) + contributing = DEFAULT_CONTRIBUTING + if Bake::Modernize.gem_dependencies(File.dirname(readme_path)).include?("bake-gem-github") + contributing = contributing.sub("gem:release:patch", "gem:github:release:patch") + contributing = contributing.sub("### Developer Certificate of Origin", "See [bake-gem-github](https://github.com/socketry/bake-gem-github) for setup and release instructions.\n\n### Developer Certificate of Origin") + end + + replacement = Markly.parse(contributing) return unless node = root.find_header("Contributing") diff --git a/bake/modernize/releases.rb b/bake/modernize/releases.rb index 6cb37461..d205f771 100644 --- a/bake/modernize/releases.rb +++ b/bake/modernize/releases.rb @@ -5,6 +5,7 @@ require "bake/modernize" require "markly" +require "erb" # Update the project to use bake-releases for release notes. # @@ -17,6 +18,28 @@ def releases(root: Dir.pwd) update_bake(root) end +# Migrate release dependencies and hooks to bake-gem-github. Configure its workflows with gem:github:setup afterwards. +# +# @parameter root [String] The root directory of the project. +def github(root: Dir.pwd) + require "bundler" + + dependencies = Bake::Modernize.gem_dependencies(root) + environment = Bundler.unbundled_env + + unless dependencies.include?("bake-gem-github") + system(environment, "bundle", "add", "bake-gem-github", "--group", "maintenance", chdir: root, unsetenv_others: true, exception: true) + end + + if dependencies.include?("bake-gem") + system(environment, "bundle", "remove", "bake-gem", chdir: root, unsetenv_others: true, exception: true) + end + + update_releases(File.join(root, "readme.md")) + update_releases_md(File.join(root, "releases.md")) + update_bake(root) +end + private DEFAULT_CONTRIBUTING = <<~MARKDOWN @@ -58,13 +81,21 @@ def update_bake(root) require "async/ollama" bake_path = File.join(root, "bake.rb") - template = File.read(RELEASES_TEMPLATE_ROOT + "bake.rb") + github_releases = Bake::Modernize.gem_dependencies(root).include?("bake-gem-github") + template = ERB.new(File.read(RELEASES_TEMPLATE_ROOT + "bake.rb.erb"), trim_mode: "-").result(binding) + instruction = "Merge the template into the existing file. Add any missing methods and update existing method bodies to include any missing calls shown in the template." + + if github_releases + instruction += " Remove the releases:github:release call from after_gem_release, because bake-gem-github publishes the GitHub release. Remove that method and its documentation if it becomes empty. Do not add an after_gem_release hook. Preserve all other methods and calls." + else + instruction += " Do not remove any existing calls." + end if File.exist?(bake_path) existing = File.read(bake_path) updated = Async::Ollama::Transform.call(existing, model: "qwen3-coder:latest", - instruction: "Merge the template into the existing file. Add any missing methods and update existing method bodies to include any missing calls shown in the template. Do not remove any existing calls.", + instruction: instruction, template: template, ) File.write(bake_path, updated) diff --git a/lib/bake/modernize.rb b/lib/bake/modernize.rb index 135e4042..4af3759d 100644 --- a/lib/bake/modernize.rb +++ b/lib/bake/modernize.rb @@ -18,6 +18,21 @@ module Modernize TEMPLATE_ROOT = Build::Files::Path.new(ROOT) + "template" + # Read the gem dependencies declared by the target project, including optional groups. + # @parameter root [String] The root directory of the project. + # @returns [Array(String)] The declared dependency names. + def self.gem_dependencies(root) + require "bundler" + + if path = ["gems.rb", "Gemfile"].map{|name| File.expand_path(name, root)}.find{|path| File.file?(path)} + dsl = Bundler::Dsl.new + dsl.eval_gemfile(path) + return dsl.dependencies.map(&:name) + end + + return [] + end + # Compute the template root path relative to the gem root. def self.template_path_for(path) TEMPLATE_ROOT + path diff --git a/template/releases/bake.rb b/template/releases/bake.rb.erb similarity index 93% rename from template/releases/bake.rb rename to template/releases/bake.rb.erb index 94412110..2f4e84db 100644 --- a/template/releases/bake.rb +++ b/template/releases/bake.rb.erb @@ -10,6 +10,7 @@ def after_gem_release_version_increment(version) context["releases:update"].call(version) context["utopia:project:update"].call end +<% unless github_releases -%> # Create a GitHub release for the given tag. # @@ -17,3 +18,4 @@ def after_gem_release_version_increment(version) def after_gem_release(tag:, **options) context["releases:github:release"].call(tag) end +<% end -%> diff --git a/test/bake/modernize.rb b/test/bake/modernize.rb index 8840e0f8..73169d4f 100644 --- a/test/bake/modernize.rb +++ b/test/bake/modernize.rb @@ -32,6 +32,38 @@ task.call expect(calls.index("modernize:gemfile")).to be < calls.index("modernize:actions") + expect(calls).not.to be(:include?, "modernize:releases:github") + end + + with ".gem_dependencies" do + it "reads optional groups without matching comments or similar gem names" do + File.write(File.join(root, "gems.rb"), <<~RUBY) + # gem "bake-gem" + group :maintenance, optional: true do + gem "bake-gem-github", "~> 0.4.0" + end + RUBY + + expect(subject.gem_dependencies(root)).to be == ["bake-gem-github"] + end + + it "supports Gemfile and included manifests" do + File.write(File.join(root, "Gemfile"), 'eval_gemfile "maintenance.rb"') + File.write(File.join(root, "maintenance.rb"), 'gem "bake-gem"') + + expect(subject.gem_dependencies(root)).to be == ["bake-gem"] + end + + it "prefers gems.rb when both manifests exist" do + File.write(File.join(root, "gems.rb"), 'gem "bake-gem-github"') + File.write(File.join(root, "Gemfile"), 'gem "bake-gem"') + + expect(subject.gem_dependencies(root)).to be == ["bake-gem-github"] + end + + it "returns no dependencies without a manifest" do + expect(subject.gem_dependencies(root)).to be == [] + end end it "detects stale files when the destination exists" do diff --git a/test/bake/modernize/contributing.rb b/test/bake/modernize/contributing.rb new file mode 100644 index 00000000..a784b7a5 --- /dev/null +++ b/test/bake/modernize/contributing.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2026, by Samuel Williams. + +require "bake/context" +require "sus/fixtures/isolated_ruby_context" +require "sus/fixtures/temporary_directory_context" + +describe "modernize:contributing" do + include Sus::Fixtures::TemporaryDirectoryContext + include Sus::Fixtures::IsolatedRubyContext + + let(:recipe) {Bake::Context.load.lookup("modernize:contributing").instance} + let(:readme_path) {File.join(root, "readme.md")} + + before do + File.write(readme_path, "# Example\n\n## Contributing\n\nExisting instructions.\n") + end + + it "documents release PRs when the project uses bake-gem-github" do + File.write(File.join(root, "gems.rb"), 'gem "bake-gem-github"') + + recipe.send(:update_contributing, readme_path) + + expect(File.read(readme_path)).to be(:include?, "bake gem:github:release:patch") + expect(File.read(readme_path)).to be(:include?, "https://github.com/socketry/bake-gem-github") + end + + it "keeps local release instructions for other projects" do + File.write(File.join(root, "gems.rb"), 'gem "bake-gem"') + + recipe.send(:update_contributing, readme_path) + + expect(File.read(readme_path)).to be(:include?, "bake gem:release:patch") + expect(File.read(readme_path)).not.to be(:include?, "gem:github:release") + end + + it "updates the project from its working directory" do + File.write(File.join(root, "conduct.md"), "Old guidelines.\n") + File.write(File.join(root, "gems.rb"), 'gem "bake-gem-github"') + + isolated_ruby(<<~RUBY, chdir: root) + require "bake/context" + Bake::Context.load.call("modernize:contributing") + nil + RUBY + + expect(File).not.to be(:exist?, File.join(root, "conduct.md")) + expect(File.read(readme_path)).to be(:include?, "gem:github:release:patch") + end +end diff --git a/test/bake/modernize/releases.rb b/test/bake/modernize/releases.rb index 6a13d9f3..a093bfa0 100644 --- a/test/bake/modernize/releases.rb +++ b/test/bake/modernize/releases.rb @@ -91,7 +91,50 @@ recipe.send(:update_bake, root) expect(File.exist?(bake_path)).to be_truthy - expect(File.read(bake_path)).to be =~ /after_gem_release/ + expect(File.read(bake_path)).to be =~ /def after_gem_release\(/ + end + + it "only creates the version increment hook for bake-gem-github" do + File.write(File.join(root, "gems.rb"), <<~RUBY) + group :maintenance, optional: true do + gem "bake-gem-github" + end + RUBY + + recipe.send(:update_bake, root) + + scope = Module.new + scope.module_eval(File.read(bake_path), bake_path) + expect(scope.instance_methods(false)).to be == [:after_gem_release_version_increment] + expect(File.read(bake_path)).to be(:include?, 'context["releases:update"].call(version)') + expect(File.read(bake_path)).to be(:include?, 'context["utopia:project:update"].call') + end + + it "removes obsolete publishing calls while preserving custom hooks when merging" do + File.write(File.join(root, "gems.rb"), 'gem "bake-gem-github"') + existing = <<~RUBY + def after_gem_release(tag:, **options) + context["releases:github:release"].call(tag) + context["custom:notify"].call(tag) + end + RUBY + File.write(bake_path, existing) + updated = existing.lines.reject{|line| line.include?("releases:github:release")}.join + + mock(Async::Ollama::Transform) do |mock| + mock.replace(:call) do |content, model:, instruction:, template:| + expect(content).to be == existing + expect(instruction).to be(:include?, "Remove the releases:github:release call") + expect(instruction).to be(:include?, "Remove that method and its documentation if it becomes empty") + expect(instruction).to be(:include?, "Preserve all other methods and calls") + expect(template).not.to be =~ /def after_gem_release\(/ + updated + end + end + + recipe.send(:update_bake, root) + + expect(File.read(bake_path)).to be == updated end it "merges release hooks into an existing bake.rb using AI" do diff --git a/test/bake/modernize/releases/github.rb b/test/bake/modernize/releases/github.rb new file mode 100644 index 00000000..95c1cedd --- /dev/null +++ b/test/bake/modernize/releases/github.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2026, by Samuel Williams. + +require "bake/context" +require "sus/fixtures/temporary_directory_context" + +describe "modernize:releases:github" do + include Sus::Fixtures::TemporaryDirectoryContext + + let(:task) {Bake::Context.load.lookup("modernize:releases:github")} + let(:recipe) {task.instance} + let(:gems_path) {File.join(root, "gems.rb")} + let(:bake_path) {File.join(root, "bake.rb")} + + before do + File.write(File.join(root, "readme.md"), "# Example\n") + end + + it "replaces bake-gem and generates preparation hooks without publishing hooks" do + File.write(gems_path, "gem \"bake-gem\"\n") + calls = [] + + mock(recipe) do |mock| + mock.replace(:system) do |environment, *arguments, **options| + expect(environment).not.to have_keys("BUNDLE_GEMFILE") + expect(options).to be == {chdir: root, unsetenv_others: true, exception: true} + calls << arguments + case arguments + when ["bundle", "add", "bake-gem-github", "--group", "maintenance"] + File.write(gems_path, "gem \"bake-gem-github\"\n", mode: "a") + when ["bundle", "remove", "bake-gem"] + File.write(gems_path, File.read(gems_path).sub("gem \"bake-gem\"\n", "")) + end + true + end + end + + task.call(root: root) + + expect(calls).to be == [ + ["bundle", "add", "bake-gem-github", "--group", "maintenance"], + ["bundle", "remove", "bake-gem"], + ] + expect(Bake::Modernize.gem_dependencies(root)).to be == ["bake-gem-github"] + expect(File.read(bake_path)).to be(:include?, "def after_gem_release_version_increment") + expect(File.read(bake_path)).not.to be =~ /def after_gem_release\(/ + expect(File.read(File.join(root, "releases.md"))).to be(:include?, "Unreleased") + end + + it "does not reinstall an existing dependency or remove absent bake-gem" do + File.write(gems_path, 'gem "bake-gem-github", "~> 0.4.0"') + mock(recipe) do |mock| + mock.replace(:system){raise "Unexpected dependency change."} + end + + task.call(root: root) + + expect(File.read(gems_path)).to be == 'gem "bake-gem-github", "~> 0.4.0"' + expect(File.read(bake_path)).not.to be =~ /def after_gem_release\(/ + end + + it "stops before removing bake-gem or changing hooks when installation fails" do + File.write(gems_path, 'gem "bake-gem"') + mock(recipe) do |mock| + mock.replace(:system) do |environment, *arguments, **options| + expect(arguments).to be == ["bundle", "add", "bake-gem-github", "--group", "maintenance"] + raise "Installation failed." + end + end + + expect{task.call(root: root)}.to raise_exception(RuntimeError, message: be == "Installation failed.") + expect(File.read(gems_path)).to be == 'gem "bake-gem"' + expect(File).not.to be(:exist?, bake_path) + end +end From d5cd5db7fd87cf884fff1cdc83630ac7e5a944f8 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Tue, 22 Sep 2026 16:09:01 +1200 Subject: [PATCH 3/4] Provide agent guidance for updating gems. --- bake-modernize.gemspec | 2 +- context/getting-started.md | 85 ++++++++++++++++++++++++++++++++ context/index.yaml | 15 ++++++ gems.rb | 1 + guides/getting-started/readme.md | 85 ++++++++++++++++++++++++++++++++ guides/links.yaml | 2 + readme.md | 8 +++ releases.md | 6 +++ 8 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 context/getting-started.md create mode 100644 context/index.yaml create mode 100644 guides/getting-started/readme.md create mode 100644 guides/links.yaml diff --git a/bake-modernize.gemspec b/bake-modernize.gemspec index 5ad0ac73..2e9e512c 100644 --- a/bake-modernize.gemspec +++ b/bake-modernize.gemspec @@ -23,7 +23,7 @@ Gem::Specification.new do |spec| "source_code_uri" => "https://github.com/ioquatix/bake-modernize.git", } - spec.files = Dir.glob(["{bake,lib,template}/**/*", "*.md", "release.cert"], File::FNM_DOTMATCH, base: __dir__) + spec.files = Dir.glob(["{bake,context,lib,template}/**/*", "*.md", "release.cert"], File::FNM_DOTMATCH, base: __dir__) spec.required_ruby_version = ">= 3.3" diff --git a/context/getting-started.md b/context/getting-started.md new file mode 100644 index 00000000..8db73331 --- /dev/null +++ b/context/getting-started.md @@ -0,0 +1,85 @@ +# Getting Started + +This guide explains how to update a Ruby gem with `bake-modernize`, review the generated changes, and optionally migrate to releases through GitHub. + +## Installation + +Add the tools to the project's maintenance dependencies, preserving the existing groups and version requirements: + +``` ruby +group :maintenance, optional: true do + gem "agent-context" + gem "bake-modernize" +end +``` + +Install the maintenance group and its agent guidance: + +``` bash +bundle config set --local with maintenance +bundle install +bundle exec bake agent:context:install +``` + +If `bake-modernize` is already installed, use `bundle update bake-modernize --conservative` to update it, then reinstall the agent context. Read the project's instructions and relevant dependency guides before making changes. Generated `agents.md` and installed context are local working files; do not commit them. + +## Update a gem + +Start from an up-to-date checkout with a clean working tree and create a branch for the modernization. Check the gem's supported Ruby versions, CI jobs, release process, signing configuration, and custom Bake tasks before updating them. + +List the available tasks, then run the default modernization from the gem's root directory: + +``` bash +bundle exec bake list modernize +bundle exec bake modernize +git diff +``` + +The default task updates standard files including CI workflows, dependency manifests, the gemspec, copyright attributions, contributing instructions, and release hooks. It modifies the working tree directly. Review the diff and selectively keep appropriate changes; generated changes are suggestions, and repository customizations can be intentional. Some tasks install gems, inspect GitHub URLs, or use Ollama with `qwen3-coder:latest` to merge existing files. Those merges need a running Ollama service with that model available. + +You can run individual tasks when only part of the project needs updating: + +``` bash +bundle exec bake modernize:actions +bundle exec bake modernize:license +bundle exec bake modernize:releases +``` + +Check the resulting Ruby compatibility requirements, gem dependencies, workflow names and required status checks, gemspec file list, signing paths, and release hooks. Preserve custom tasks and project-specific CI. Copyright generation ignores author names ending in `[bot]`; `.mailmap`, `.contributors.yaml`, and human authorship through file renames remain supported. + +Run the project's checks before opening a PR. For gems using the standard Sus, RuboCop, and Decode tasks: + +``` bash +bundle exec bake test +bundle exec rubocop +bundle exec bake decode:index:coverage lib +``` + +Add user-visible changes under `## Unreleased` in `releases.md`. If guides or project documentation changed, run `bundle exec bake utopia:project:update` and review its output. Commit the selected changes and describe any remaining manual setup in the PR. + +## Opt in to GitHub releases + +The default `modernize` task does not migrate the publishing process. To adopt reviewed release PRs explicitly: + +``` bash +bundle exec bake modernize:releases:github +``` + +This task adds `bake-gem-github` to maintenance dependencies, removes the direct `bake-gem` dependency when present, and updates release notes and Bake hooks. `bake-gem-github` supplies `bake-gem` and `bake-releases` as dependencies. Existing `bake-gem-github` version requirements are preserved. + +Keep `after_gem_release_version_increment`, which updates release notes and project documentation. GitHub publishing replaces the `after_gem_release` call to `releases:github:release`. When merging an existing `bake.rb`, review the generated edit to ensure it removes that call while preserving unrelated custom behavior. Subsequent `modernize:releases` runs select the same template based on the target project's dependencies and do not add the publishing hook back. Contributing instructions also use `gem:github:release:patch` for projects that have adopted it. + +Configure the release workflows separately using the installed gem's setup task. Pass the actual required CI job names; for example, a repository using the standard Ruby matrix and coverage workflows might use: + +``` bash +bundle exec bake gem:github:setup checks="3.3 on ubuntu,3.3 on macos,3.4 on ubuntu,3.4 on macos,4.0 on ubuntu,4.0 on macos,check,ruby on ubuntu,ruby on macos,validate" +bundle exec bake gem:github:setup:plan +``` + +For an existing `config/release.yaml`, use `bundle exec bake gem:github:setup:update` to regenerate managed files instead. Review and commit the generated workflows, rulesets, configuration, and readme changes. Keep the readme's `Making Releases` section short: a release command and a link to [bake-gem-github](https://github.com/socketry/bake-gem-github). + +Complete the setup described by [bake-gem-github](https://github.com/socketry/bake-gem-github): configure the RubyGems Trusted Publisher for `release-publish.yaml` and the `rubygems` environment, restrict that environment to the default branch, and install a matching signing key if signing is enabled. Select environment reviewers per repository when a separate publishing approval is wanted, for example `reviewers: [socketry/managers]`. Omitting reviewers leaves the environment's existing reviewer configuration unchanged. + +After the setup PR is merged and the required CI jobs are available, an administrator reviews the plan and runs `bundle exec bake gem:github:setup:apply`. Generating files or upgrading gems does not apply remote settings. Workflow execution approvals, PR reviews, and publishing environment approvals are separate controls. + +Once setup is complete, prepare a release with `bundle exec bake gem:github:release:patch` (or `minor` or `major`), or dispatch `release-prepare.yaml` remotely. Merge the reviewed release PR to start publishing. diff --git a/context/index.yaml b/context/index.yaml new file mode 100644 index 00000000..c19af913 --- /dev/null +++ b/context/index.yaml @@ -0,0 +1,15 @@ +# Automatically generated context index for Utopia::Project guides. +# Do not edit then files in this directory directly, instead edit the guides and then run `bake utopia:project:agent:context:update`. +--- +description: Automatically modernize parts of your project/gem. +metadata: + bug_tracker_uri: https://github.com/ioquatix/bake-modernize/issues + changelog_uri: https://github.com/ioquatix/bake-modernize/blob/main/releases.md + documentation_uri: https://ioquatix.github.io/bake-modernize/ + funding_uri: https://github.com/sponsors/ioquatix/ + source_code_uri: https://github.com/ioquatix/bake-modernize.git +files: +- path: getting-started.md + title: Getting Started + description: This guide explains how to update a Ruby gem with `bake-modernize`, + review the generated changes, and optionally migrate to releases through GitHub. diff --git a/gems.rb b/gems.rb index 7ae730ee..a1d116dc 100644 --- a/gems.rb +++ b/gems.rb @@ -8,6 +8,7 @@ gemspec group :maintenance, optional: true do + gem "agent-context" gem "bake-gem-github", "~> 0.4.0" gem "bake-releases" diff --git a/guides/getting-started/readme.md b/guides/getting-started/readme.md new file mode 100644 index 00000000..8db73331 --- /dev/null +++ b/guides/getting-started/readme.md @@ -0,0 +1,85 @@ +# Getting Started + +This guide explains how to update a Ruby gem with `bake-modernize`, review the generated changes, and optionally migrate to releases through GitHub. + +## Installation + +Add the tools to the project's maintenance dependencies, preserving the existing groups and version requirements: + +``` ruby +group :maintenance, optional: true do + gem "agent-context" + gem "bake-modernize" +end +``` + +Install the maintenance group and its agent guidance: + +``` bash +bundle config set --local with maintenance +bundle install +bundle exec bake agent:context:install +``` + +If `bake-modernize` is already installed, use `bundle update bake-modernize --conservative` to update it, then reinstall the agent context. Read the project's instructions and relevant dependency guides before making changes. Generated `agents.md` and installed context are local working files; do not commit them. + +## Update a gem + +Start from an up-to-date checkout with a clean working tree and create a branch for the modernization. Check the gem's supported Ruby versions, CI jobs, release process, signing configuration, and custom Bake tasks before updating them. + +List the available tasks, then run the default modernization from the gem's root directory: + +``` bash +bundle exec bake list modernize +bundle exec bake modernize +git diff +``` + +The default task updates standard files including CI workflows, dependency manifests, the gemspec, copyright attributions, contributing instructions, and release hooks. It modifies the working tree directly. Review the diff and selectively keep appropriate changes; generated changes are suggestions, and repository customizations can be intentional. Some tasks install gems, inspect GitHub URLs, or use Ollama with `qwen3-coder:latest` to merge existing files. Those merges need a running Ollama service with that model available. + +You can run individual tasks when only part of the project needs updating: + +``` bash +bundle exec bake modernize:actions +bundle exec bake modernize:license +bundle exec bake modernize:releases +``` + +Check the resulting Ruby compatibility requirements, gem dependencies, workflow names and required status checks, gemspec file list, signing paths, and release hooks. Preserve custom tasks and project-specific CI. Copyright generation ignores author names ending in `[bot]`; `.mailmap`, `.contributors.yaml`, and human authorship through file renames remain supported. + +Run the project's checks before opening a PR. For gems using the standard Sus, RuboCop, and Decode tasks: + +``` bash +bundle exec bake test +bundle exec rubocop +bundle exec bake decode:index:coverage lib +``` + +Add user-visible changes under `## Unreleased` in `releases.md`. If guides or project documentation changed, run `bundle exec bake utopia:project:update` and review its output. Commit the selected changes and describe any remaining manual setup in the PR. + +## Opt in to GitHub releases + +The default `modernize` task does not migrate the publishing process. To adopt reviewed release PRs explicitly: + +``` bash +bundle exec bake modernize:releases:github +``` + +This task adds `bake-gem-github` to maintenance dependencies, removes the direct `bake-gem` dependency when present, and updates release notes and Bake hooks. `bake-gem-github` supplies `bake-gem` and `bake-releases` as dependencies. Existing `bake-gem-github` version requirements are preserved. + +Keep `after_gem_release_version_increment`, which updates release notes and project documentation. GitHub publishing replaces the `after_gem_release` call to `releases:github:release`. When merging an existing `bake.rb`, review the generated edit to ensure it removes that call while preserving unrelated custom behavior. Subsequent `modernize:releases` runs select the same template based on the target project's dependencies and do not add the publishing hook back. Contributing instructions also use `gem:github:release:patch` for projects that have adopted it. + +Configure the release workflows separately using the installed gem's setup task. Pass the actual required CI job names; for example, a repository using the standard Ruby matrix and coverage workflows might use: + +``` bash +bundle exec bake gem:github:setup checks="3.3 on ubuntu,3.3 on macos,3.4 on ubuntu,3.4 on macos,4.0 on ubuntu,4.0 on macos,check,ruby on ubuntu,ruby on macos,validate" +bundle exec bake gem:github:setup:plan +``` + +For an existing `config/release.yaml`, use `bundle exec bake gem:github:setup:update` to regenerate managed files instead. Review and commit the generated workflows, rulesets, configuration, and readme changes. Keep the readme's `Making Releases` section short: a release command and a link to [bake-gem-github](https://github.com/socketry/bake-gem-github). + +Complete the setup described by [bake-gem-github](https://github.com/socketry/bake-gem-github): configure the RubyGems Trusted Publisher for `release-publish.yaml` and the `rubygems` environment, restrict that environment to the default branch, and install a matching signing key if signing is enabled. Select environment reviewers per repository when a separate publishing approval is wanted, for example `reviewers: [socketry/managers]`. Omitting reviewers leaves the environment's existing reviewer configuration unchanged. + +After the setup PR is merged and the required CI jobs are available, an administrator reviews the plan and runs `bundle exec bake gem:github:setup:apply`. Generating files or upgrading gems does not apply remote settings. Workflow execution approvals, PR reviews, and publishing environment approvals are separate controls. + +Once setup is complete, prepare a release with `bundle exec bake gem:github:release:patch` (or `minor` or `major`), or dispatch `release-prepare.yaml` remotely. Merge the reviewed release PR to start publishing. diff --git a/guides/links.yaml b/guides/links.yaml new file mode 100644 index 00000000..7f527b02 --- /dev/null +++ b/guides/links.yaml @@ -0,0 +1,2 @@ +getting-started: + order: 1 diff --git a/readme.md b/readme.md index 4bbdf188..e8b27d42 100644 --- a/readme.md +++ b/readme.md @@ -8,10 +8,18 @@ A gem for modernizing files in your Ruby project. Please see the [project documentation](https://ioquatix.github.io/bake-modernize/) for more details. + - [Getting Started](https://ioquatix.github.io/bake-modernize/guides/getting-started/index) - This guide explains how to update a Ruby gem with `bake-modernize`, review the generated changes, and optionally migrate to releases through GitHub. + ## Releases Please see the [project releases](https://ioquatix.github.io/bake-modernize/releases/index) for all releases. +### Unreleased + + - Provide agent context for updating gems and configuring GitHub releases. + - Ignore `[bot]` authors in copyright attributions and gem authors. + - Add opt-in `modernize:releases:github` migration and omit redundant publishing hooks for gems using `bake-gem-github`. + ### v0.60.0 - Ignore `/vendor/bundle` by default for dependencies installed by GitHub Actions' Bundler cache. diff --git a/releases.md b/releases.md index 4c238eca..9565fe8b 100644 --- a/releases.md +++ b/releases.md @@ -1,5 +1,11 @@ # Releases +## Unreleased + + - Provide agent context for updating gems and configuring GitHub releases. + - Ignore `[bot]` authors in copyright attributions and gem authors. + - Add opt-in `modernize:releases:github` migration and omit redundant publishing hooks for gems using `bake-gem-github`. + ## v0.60.0 - Ignore `/vendor/bundle` by default for dependencies installed by GitHub Actions' Bundler cache. From 820c2d890b15bd521b36370b6521f4066786afe3 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Tue, 22 Sep 2026 21:52:34 +1200 Subject: [PATCH 4/4] Adopt bake-gem-github 0.5 for rebase releases --- .github/release-rules/reviews.json | 3 ++- .github/workflows/release-validate.yaml | 2 +- gems.rb | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/release-rules/reviews.json b/.github/release-rules/reviews.json index a6bda35f..b36fd664 100644 --- a/.github/release-rules/reviews.json +++ b/.github/release-rules/reviews.json @@ -28,7 +28,8 @@ "require_code_owner_review": false, "allowed_merge_methods": [ "merge", - "squash" + "squash", + "rebase" ] } } diff --git a/.github/workflows/release-validate.yaml b/.github/workflows/release-validate.yaml index 0f91f8a6..5d6c9f3b 100644 --- a/.github/workflows/release-validate.yaml +++ b/.github/workflows/release-validate.yaml @@ -27,6 +27,6 @@ jobs: - name: Regenerate release content env: RELEASE_BASE: ${{ github.event.pull_request.base.sha }} - run: bundle exec bake gem:release:validate "base=$RELEASE_BASE" optional=true + run: bundle exec bake gem:github:release:validate "base=$RELEASE_BASE" - name: Build unsigned package run: bundle exec bake gem:build signing_key=false diff --git a/gems.rb b/gems.rb index a1d116dc..b2a767bf 100644 --- a/gems.rb +++ b/gems.rb @@ -9,7 +9,7 @@ group :maintenance, optional: true do gem "agent-context" - gem "bake-gem-github", "~> 0.4.0" + gem "bake-gem-github", "~> 0.5.0" gem "bake-releases" gem "decode"