A Claude Code plugin for upgrading Ruby projects safely — including Ruby on Rails apps. Supports any Ruby version upgrade (2.7→3.x and beyond) and any Rails version upgrade (5→8), separately or together.
How It Works
The plugin gives Claude a structured, repeatable methodology for Ruby and Rails upgrades. The six commands compose — use as many or as few as you need. A sixth command — rules — is optional: it manages a project-local policy file (gem pins, gem swaps, Rails LTS / Sidekiq Pro substitutions, extra verification gates like Brakeman) that the other commands pick up automatically.
Mode 1: Fully automated
/ruby-upgrade-toolkit:upgrade ruby:X.Y.Z [rails:X.Y]
One command runs everything: detects versions, validates compatibility, computes the full upgrade path (including intermediate versions), creates a live task list, applies fixes phase by phase, verifies after each phase, and pauses with a recovery menu if anything fails.
Use this when you want to move quickly and trust Claude to sequence and execute the work.
Mode 2: Review first, then automate (recommended for first-time upgrades)
/ruby-upgrade-toolkit:audit ruby:X.Y.Z [rails:X.Y] # understand the scope
/ruby-upgrade-toolkit:plan ruby:X.Y.Z [rails:X.Y] # review the phase sequence
/ruby-upgrade-toolkit:upgrade ruby:X.Y.Z [rails:X.Y] # execute the plan
audit surfaces breaking changes and gives an effort estimate before touching any code. plan shows exactly which phases will run, in what order, and — in its Estimate Summary — the effort, risk, blast radius, and confidence for each phase so you can sanity-check scope and sequencing. Once you're comfortable with the plan, upgrade executes the same sequence automatically — no need to re-specify anything.
Use this when you're doing a major version jump, have a large codebase, or want to understand the scope before committing to execution.
Mode 3: Fully manual (maximum control)
/ruby-upgrade-toolkit:audit ruby:X.Y.Z [rails:X.Y] # read-only scan
/ruby-upgrade-toolkit:plan ruby:X.Y.Z [rails:X.Y] # roadmap + creates a task list
/ruby-upgrade-toolkit:fix next # apply next pending phase; repeat
plan creates a TodoWrite task list (one task per phase); fix next reads the list, picks the first pending task, applies it, verifies, prompts you for commit, and ticks it off. Iterate fix next until the list is empty. You can also pass explicit args to fix to jump to a specific phase — useful when resuming or working out of order.
Use this when you want to inspect and approve changes phase by phase, apply fixes to a specific scope (scope:path), or resume a partially completed upgrade.
Manual and automated share the same machinery. /upgrade is a loop of /fix next calls around the same TodoWrite task list that /plan creates, plus some pre-loop prerequisite checks (branch, Ruby installs) and a post-loop Final infra check (CI/CD, Dockerfiles). If you already ran /plan and did a few /fix next iterations manually, /upgrade reuses that list as-is (target-matching) and picks up where you left off — progress is preserved. If the invocation target differs or no list exists, upgrade regenerates the list via /plan. Either way, the per-phase work is byte-identical.
Why the order matters:
auditis read-only — zero risk, surfaces breaking changes and effort before touching anythingplansequences work correctly (Ruby phases before Rails phases, intermediate versions in the right order), quantifies each phase (effort, risk, blast radius, confidence), and creates the task list that drives the rest of the workflowfix nextconsumes the next pending task from the list; on successful commit it ticks the task off. Repeat until the list is empty.statusis an on-demand dashboard — useful for a full health snapshot, thoughfix's built-in verification is what gates the commit
Installation
Via Claude Code marketplace
Add the marketplace and install the plugin:
/plugin marketplace add dhruvasagar/ruby-upgrade-toolkit
/plugin install ruby-upgrade-toolkit@dhruvasagar
Or use the interactive UI: run /plugin, go to the Discover tab, search for ruby-upgrade-toolkit, and click Install.
Local development
git clone https://github.com/dhruvasagar/ruby-upgrade-toolkit
Then add the cloned directory as a local marketplace and install:
/plugin marketplace add /path/to/ruby-upgrade-toolkit
/plugin install ruby-upgrade-toolkit
/reload-plugins
Updating
Marketplace install
Uninstall and reinstall to get the latest version:
/plugin uninstall ruby-upgrade-toolkit@dhruvasagar
/plugin install ruby-upgrade-toolkit@dhruvasagar
Then reload without restarting Claude Code:
To enable auto-updates for this marketplace, run /plugin, go to the Marketplaces tab, select dhruvasagar, and toggle Enable auto-update. When an update is detected at startup, Claude Code will prompt you to run /reload-plugins.
Local install
Pull the latest changes and reload:
cd /path/to/ruby-upgrade-toolkit
git pullCommand Reference
All commands are namespaced under /ruby-upgrade-toolkit: to avoid conflicts with other plugins.
/ruby-upgrade-toolkit:upgrade ruby:X.Y.Z [rails:X.Y]
The recommended starting point. Runs the full upgrade pipeline automatically with per-phase commit checkpoints. Equivalent to running /plan once and then iterating /fix next until the task list is empty, with prerequisite and final infra checks bolted on either side.
What it does:
- Detects current versions, validates Ruby ↔ Rails compatibility, computes the full upgrade path
- Reuses an existing TodoWrite task list if one exists and its implied target matches the invocation args — preserving any progress from earlier
/fix nextiterations. Otherwise, delegates to/planto produce the roadmap + estimates and create a fresh list. - Checks all intermediate Ruby versions are installed (stops early if not)
- Confirms a baseline: test failures and RuboCop offenses before any changes
- Checks/creates an upgrade branch if needed
- Loops: invokes
/fix next— which picks the next pending task, applies changes, iterates to green, verifies, prompts for a git commit with a detailed message, and ticks the task off on commit - Auto-advances between phases once the commit lands — no "continue?" prompt
- After all apply-phase tasks are done, runs Final infra checks (CI/CD, Dockerfiles) and produces a summary
- On verification failure: pauses with full error output and three options — investigate+continue, retry the phase, or abort (no rollback; working tree holds the problematic changes)
# Automated Ruby-only upgrade /ruby-upgrade-toolkit:upgrade ruby:3.3.1 # Automated Ruby + Rails upgrade /ruby-upgrade-toolkit:upgrade ruby:3.3.1 rails:8.0
Intermediate Ruby versions must be installed via rbenv/rvm before running. The command will tell you exactly which ones are missing if any are absent.
/ruby-upgrade-toolkit:audit ruby:X.Y.Z [rails:X.Y]
Read-only pre-upgrade assessment. Run this first.
Surfaces: Ruby breaking changes for the target version, Rails deprecations (if Rails present), gem incompatibilities for both Ruby and Rails targets, migration safety issues, RuboCop TargetRubyVersion gap, and an effort estimate.
Never modifies any file.
# Ruby-only audit /ruby-upgrade-toolkit:audit ruby:3.3.1 # Combined Ruby + Rails audit /ruby-upgrade-toolkit:audit ruby:3.3.1 rails:8.0
/ruby-upgrade-toolkit:plan ruby:X.Y.Z [rails:X.Y]
Generate a phased, project-specific upgrade roadmap and create the TodoWrite task list that drives /fix next and /upgrade. Detects current versions automatically.
Produces:
- A Markdown plan with prerequisites, Ruby upgrade phases (one per intermediate version), Rails upgrade phases (if
rails:given), and a final verification checklist. - An Estimate Summary — per-phase effort (hours), risk (LOW/MED/HIGH), blast radius (files, call sites, gems touched), and confidence level. Numbers are derived from concrete grep counts and
bundle outdatedresults, never guessed; each total ships with the formula so you can audit or override it. - A TodoWrite task list — one task per apply-phase, in the canonical
Phase N — Ruby|Rails X.Y.Z: apply + verify + commitformat that/fix nextparses. Re-running/planoverwrites the list (current repo state is authoritative).
# Ruby-only plan /ruby-upgrade-toolkit:plan ruby:3.3.1 # Combined Ruby + Rails plan /ruby-upgrade-toolkit:plan ruby:3.3.1 rails:8.0
/ruby-upgrade-toolkit:fix next | ruby:X.Y.Z [rails:X.Y] [scope:path]
Apply one phase of upgrade changes. The primary execution command, usable in two modes:
/fix next — reads the task list created by /plan, picks the first pending task, parses the target from the task subject, and executes. On successful commit, ticks that task off the list. Best for iterating through a planned upgrade.
/fix ruby:X.Y.Z [rails:X.Y] [scope:path] — explicit target. Useful when resuming, jumping to a specific phase, or running fix without a plan. If the explicit args happen to match a pending task, fix still ticks it off on commit.
Either mode applies: .ruby-version and Gemfile pin updates, gem dependency updates, Ruby version-specific code fixes, Rails deprecation fixes (if rails: given), Rails config updates, iterative RSpec until green, iterative RuboCop until clean.
When the phase reaches GREEN (or YELLOW — tests pass, warnings remain), fix builds a detailed proposed commit message that itemises what changed (version pins, gem diffs, fix counts, verification results) and prompts you to yes / edit / no:
- yes — stages the tracked files and commits with the proposed message
- edit — you revise the message first, then it commits
- no — skips the commit; your working tree is left dirty for you to handle manually
On RED (new failures above baseline), fix exits without prompting and without committing — you inspect the failures and rerun.
The same prompt runs whether you call /fix directly or it's driven by /upgrade. When orchestrated by /upgrade, the post-commit auto-advance to the next phase is what differs; the commit confirmation itself is always shown.
Flags CI/CD pipeline files and Dockerfiles for manual update — never modifies them automatically.
# Typical manual flow — run /plan once, then iterate this: /ruby-upgrade-toolkit:fix next # Explicit target (ignores the task list) /ruby-upgrade-toolkit:fix ruby:3.3.1 # Explicit target, Ruby + Rails /ruby-upgrade-toolkit:fix ruby:3.3.1 rails:8.0 # Fix a single file only (gem/pin changes still apply project-wide) /ruby-upgrade-toolkit:fix ruby:3.3.1 rails:8.0 scope:app/models/user.rb # Fix a directory /ruby-upgrade-toolkit:fix ruby:3.3.1 rails:8.0 scope:app/controllers/
/ruby-upgrade-toolkit:status
Current upgrade health dashboard. No arguments.
Reports: current vs. target versions, test suite pass/fail, deprecation warning count, Ruby warning count, RuboCop offense count, Zeitwerk status (Rails), and overall RED/YELLOW/GREEN readiness.
fix runs this internally before prompting for its per-phase commit, so the same tier is already gating the commit. Call /status directly when you want an on-demand snapshot outside the fix flow — e.g., to check health mid-session or after a manual edit.
When a rules.yml is present with active verification-gate rules, status adds a Custom gates section listing each gate's latest result (PASS / FAIL / ADVISORY). Brakeman and Reek counts show up here the same way RuboCop offenses do.
/ruby-upgrade-toolkit:status
/ruby-upgrade-toolkit:rules [subcommand] [args]
Manage the project's custom rules — a YAML file at .ruby-upgrade-toolkit/rules.yml that declares project-specific upgrade policies: gem pins, gem swaps, private-source substitutions (Rails LTS, Sidekiq Pro), extra verification gates (Brakeman, Reek, custom scripts), and whitelisted policy overrides. Once the file is present, the other commands (audit, plan, fix, upgrade, status) pick it up automatically — no additional flags required.
With no rules.yml, every command behaves byte-identically to a version without this feature. The feature is strictly additive.
| Subcommand | What it does |
|---|---|
init |
Creates a starter rules.yml with one commented example per rule type (all disabled by default). No-op if the file already exists. |
validate |
Schema-checks the file. Reports unknown types, duplicate IDs, conflicts, missing credential env vars. Exits non-zero on error (CI-usable). |
list [--all] |
Shows active rules. --all includes disabled. Default when called with no subcommand. |
show <id> |
Full detail for one rule: raw YAML, computed effects, phases it will fire in, preflight status, conflicts. |
add <type> |
Interactive Q&A authoring — Claude asks class-specific questions, writes the YAML, validates, shows a diff, confirms. |
remove <id> |
Deletes a rule after a diff preview and confirmation. |
disable <id> / enable <id> |
Toggles the enabled flag without removing the rule. |
explain |
Dry-run against the current project: lists which rules will fire this run, which are no-ops, and why. |
Rule types (v1 vocabulary):
type |
Purpose |
|---|---|
gem-constraint |
Pin/cap/floor/forbid a gem (e.g., devise >= 4.9). |
gem-swap |
Replace gem X with gem Y — may include companion gems and code transforms (phantomjs → selenium-webdriver). |
target-substitute |
Redirect the upgrade target itself to an alternate gem/source (mainline Rails → Rails LTS). |
code-transform |
Pattern-based code rewrite run during each matching phase's apply step (literal default; regex opt-in). |
phase-inject |
Insert a shell command into a specific phase (before/after the built-in apply step). |
verification-gate |
Additional GREEN gate — Brakeman, Reek, bin/your-check.sh. Required gates block the commit; advisory gates just report. |
policy-override |
Tweak toolkit defaults from a whitelisted set (e.g., rubocop.enabled: false, require_zero_deprecations: true). |
intermediate-pin |
Pin a specific patch version during path computation (ruby: 3.2.6, not "latest 3.2"). |
Private gem sources (Rails LTS, Sidekiq Pro): declare credentials_env: YOUR_ENV_VAR in the rule. The toolkit reads the env var at preflight and blocks the phase with a clear error if unset. The secret itself is never stored in rules.yml — only the env var's name.
How active rules affect other commands:
auditadds a Custom Rules Impact section and flags any preflight failures (missing credentials, unreachable private sources).planannotates phase checklists inline: built-in steps are unmarked, rule-driven steps get a[rule: <id>]tag. The Estimate Summary adds a "Rules contrib" column. Atarget-substituteredirects the path (e.g., mainline Rails hops replaced by an LTS substitute phase).fixapplies rule transforms in a defined order (phase-inject-before → built-ins → code-transforms → gem-swaps → phase-inject-after), runs rule gates alongside RSpec/RuboCop, and itemizes each rule's outcome in the proposed commit message.upgradepreflights credentials for all private-source rules before the loop. The failure-recovery menu gains a fourth option:D) Disable rule <id> and retry.statusadds a Custom gates section listing eachverification-gatewith its latest result.
# Scaffold a rules file (all examples disabled by default) /ruby-upgrade-toolkit:rules init # Interactively author rules /ruby-upgrade-toolkit:rules add verification-gate /ruby-upgrade-toolkit:rules add gem-swap /ruby-upgrade-toolkit:rules add target-substitute # Inspect /ruby-upgrade-toolkit:rules list /ruby-upgrade-toolkit:rules show brakeman-gate /ruby-upgrade-toolkit:rules explain # Toggle without editing the file /ruby-upgrade-toolkit:rules disable reek-gate /ruby-upgrade-toolkit:rules enable reek-gate # CI-friendly validation /ruby-upgrade-toolkit:rules validate
Full schema and semantics: docs/superpowers/specs/2026-04-24-custom-rules-design.md. Scenario 4 below walks through a complete real-world example (Rails LTS + Brakeman gate + phantomjs swap).
Workflow Examples
Five complete walkthroughs below. Scenario 0 uses the automated upgrade command — the fastest path. Scenarios 1–3 use the manual audit → plan → fix → status loop for full control. Scenario 4 demonstrates custom rules (Rails LTS + Brakeman gate + gem swap).
Rule of thumb:
fixruns verification and prompts for a commit before the phase "completes" — so the commit itself is the checkpoint. Only decline or hit RED if something actually looks wrong. You can still run/statusany time for a full on-demand dashboard.
Scenario 0: Automated upgrade (Ruby 3.1 → 3.3, Rails 7.0 → 7.2)
Starting state: Ruby 3.1.4, Rails 7.0.8. Two Ruby minor hops (3.1 → 3.2 → 3.3), two Rails minor hops (7.0 → 7.1 → 7.2).
Install required Ruby versions first (the command will tell you exactly which are missing if any):
rbenv install 3.2.4 rbenv install 3.3.1
Then run one command:
/ruby-upgrade-toolkit:upgrade ruby:3.3.1 rails:7.2
Claude delegates task list creation to /plan, runs prerequisite checks, and then loops through the tasks:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Ruby Upgrade Orchestrator
Ruby: 3.1.4 → 3.3.1
Rails: 7.0.8 → 7.2
Path: Ruby: 3.1 → 3.2 → 3.3 | Rails: 7.0 → 7.1 → 7.2
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Task list created by /plan. Running prerequisites next.
☐ Phase 1 — Ruby 3.2.4: apply + verify + commit
☐ Phase 2 — Ruby 3.3.1: apply + verify + commit
☐ Phase 3 — Rails 7.1: apply + verify + commit
☐ Phase 4 — Rails 7.2: apply + verify + commit
☐ Final — Infra checks (CI/CD, Dockerfile) + checklist
Upgrade then loops /fix next — each invocation picks the first pending task, runs the full apply → verify → commit flow, and ticks that task off. As each phase completes, its task is ticked off. Each phase ends with fix's commit prompt:
━━━ Phase verification: GREEN ━━━
chore(upgrade): ruby 3.2.4 phase
...
Commit this now? [yes / edit / no]
Answer yes (or edit then provide your own message) and the phase's changes land as a reviewable commit. Upgrade immediately moves to the next phase — no separate "continue?" prompt.
If a phase fails verification, the upgrade pauses (no commit is made):
⛔ UPGRADE PAUSED — Phase 1b: Ruby 3.2.4 did not pass verification
Failures introduced by this phase: 2 (above the 0 pre-existing baseline)
1) UserSerializer#to_json raises on nil input
Failure/Error: UserSerializer.new(nil).to_json
NoMethodError: undefined method 'name' for nil
━━━ What you can do ━━━
A) Investigate and fix — resolve the failures yourself, then reply "continue"
B) Retry this phase — reply "retry phase 1b" to re-run phase 1b's fixes
C) Abort — reply "abort" to stop here (working tree preserved)
Waiting for your decision...
Fix the issue in app/serializers/user_serializer.rb, then reply continue. Claude re-runs verification and proceeds automatically if green — surfacing the commit prompt for your approval before moving on.
Scenario 1: Ruby-only upgrade (no Rails, 2.7 → 3.3)
Starting state: Ruby 2.7.8, no Rails, RSpec suite, 47 gems.
Step 1 — Audit (read-only, zero risk)
/ruby-upgrade-toolkit:audit ruby:3.3.1
Claude runs a full pre-upgrade scan and produces a findings report. Typical output:
# Ruby Upgrade Audit Report
Current: Ruby 2.7.8
Target: Ruby 3.3.1
Upgrade Path: 2.7 → 3.0 → 3.1 → 3.2 → 3.3 (4 phases required)
## Test Suite Baseline
- Status: PASSING
- 312 examples, 0 failures
## Critical Issues
### Keyword Argument Mismatches (Ruby 3.0)
- 18 methods with **kwargs or opts={} patterns
- 34 call sites with potential hash/keyword mismatch
- Preview warnings from Ruby 2.7: 22 unique warning sites
### Unsafe YAML.load calls
- 3 occurrences: lib/config_loader.rb, lib/serializer.rb, config/initializers/legacy.rb
## Effort Estimate
Overall: Medium
- Keyword arg fixes: ~34 sites (automated with review)
- YAML fixes: 3 files (automated)
- RuboCop TargetRubyVersion gap: 2.7 → 3.3 (expect new cops)
Read the report carefully before proceeding. The 2.7→3.0 keyword argument change is the most impactful — it is a hard break, not a deprecation warning.
Step 2 — Plan
/ruby-upgrade-toolkit:plan ruby:3.3.1
Claude generates a phased roadmap specific to your project. It sequences intermediate versions correctly — you cannot jump from 2.7 directly to 3.3 safely. The plan output:
## Upgrade Roadmap: Ruby 2.7.8 → 3.3.1
### Estimate Summary
| Phase | Effort | Risk | Blast radius | Confidence |
|-------------------|--------|------|------------------------|------------|
| Phase 1: 2.7→3.0 | ~2.5h | MED | 37 sites, 3 files | HIGH |
| Phase 2: 3.0→3.1 | ~1h | LOW | 0 sites | HIGH |
| Phase 3: 3.1→3.2 | ~1h | LOW | 0 sites | HIGH |
| Phase 4: 3.2→3.3 | ~1h | LOW | 0 sites | HIGH |
| **Total** | **~5.5h** | **MED** | 37 sites, 3 files | **HIGH** |
Formulas:
- Phase 1 = 34 kwarg × 2min + 3 YAML × 2min + 1 hop × 60min = 134min ≈ 2.5h
- Phases 2–4 = 1 hop × 60min each = 1h each
### Prerequisites
- [ ] Install Ruby 3.0.7: rbenv install 3.0.7
- [ ] Install Ruby 3.1.6: rbenv install 3.1.6
- [ ] Install Ruby 3.2.4: rbenv install 3.2.4
- [ ] Install Ruby 3.3.1: rbenv install 3.3.1
- [ ] Fix 0 pre-existing test failures (baseline is green — good)
### Phase 1: Ruby 2.7 → 3.0 (highest risk)
**Effort:** ~2.5h · **Risk:** MED · **Blast radius:** 37 sites, 3 files · **Confidence:** HIGH
- Fix keyword argument mismatches (34 sites)
- Fix YAML.load → YAML.safe_load (3 files)
- Update .ruby-version and Gemfile ruby pin
- Run RSpec until green
- Run RuboCop until clean
- Checkpoint: /ruby-upgrade-toolkit:status → GREEN required
### Phase 2: Ruby 3.0 → 3.1
**Effort:** ~1h · **Risk:** LOW · **Confidence:** HIGH
- Update .ruby-version and Gemfile ruby pin
- Address any Psych 4 YAML changes
- Run RSpec until green, RuboCop until clean
- Checkpoint: GREEN required
### Phase 3: Ruby 3.1 → 3.2
**Effort:** ~1h · **Risk:** LOW · **Confidence:** HIGH
- Update .ruby-version and Gemfile ruby pin
- Run RSpec until green, RuboCop until clean
- Checkpoint: GREEN required
### Phase 4: Ruby 3.2 → 3.3.1
**Effort:** ~1h · **Risk:** LOW · **Confidence:** HIGH
- Update .ruby-version and Gemfile ruby pin
- Check for `it` block parameter conflicts
- Run RSpec until green, RuboCop until clean
- Checkpoint: GREEN required (upgrade complete)
Install each intermediate Ruby version via rbenv/rvm before starting the fix phases.
Step 3a — Fix Phase 1: 2.7 → 3.0
/plan in Step 2 created the task list. You can either let /fix next resolve the target from that list, or pass an explicit target. Both produce the same result; next is ergonomic once a plan exists.
Activate Ruby 3.0.7 first: rbenv local 3.0.7
/ruby-upgrade-toolkit:fix next
# equivalent to:
# /ruby-upgrade-toolkit:fix ruby:3.0.7
Claude applies changes in this order:
- Updates
.ruby-versionto3.0.7, updatesGemfileruby pin to~> 3.0 - Runs
bundle install— updates any gems that require it - Fixes all 34 keyword argument sites (reads each file, applies Pattern A or B, runs the file's tests)
- Replaces
YAML.loadwithYAML.safe_loadin 3 files - Runs full RSpec suite — iterates on any failures until green
- Runs RuboCop (
-athen-A) — iterates on remaining offenses until clean - Runs verification (status) and, on GREEN/YELLOW, prompts you to commit with a detailed message:
━━━ Phase verification: GREEN ━━━
chore(upgrade): ruby 3.0.7 phase
Version pins:
- .ruby-version: 2.7.8 → 3.0.7
- Gemfile ruby directive: "~> 3.0"
Ruby code changes:
- Keyword argument fixes: 34 sites across 12 files
- YAML.load → YAML.safe_load: 3 occurrences
Verification:
- RSpec: 312 examples, 0 failures (0 pre-existing, 0 new)
- RuboCop: 0 offenses
- Deprecation warnings: 0
- Tier: GREEN
Commit this now? [yes / edit / no]
- On
yes, creates the commit. Onedit, lets you revise the message first. Onno, leaves the working tree dirty. Then produces a fix summary.
## Upgrade Fix Summary
Ruby: 2.7.8 → 3.0.7
Scope: full project
Commit: abc1234
### Ruby Code Changes
- Keyword argument fixes: 12 files, 34 occurrences
- YAML.load → safe_load: 3 occurrences
### RSpec
- Before: 0 failures After: 0 failures ✓
### RuboCop
- Before: 0 offenses After: 0 offenses ✓
### Manual Action Required
- .github/workflows/ci.yml: update ruby-version from 2.7 to 3.0
Checkpoint after Phase 1
The per-phase commit is the primary checkpoint, but status is still useful as a full dashboard (Zeitwerk, outdated gems, etc.):
/ruby-upgrade-toolkit:status
## Overall Readiness: GREEN
Ruby: 3.0.7 / Rails: not present
Tests: PASSING (312 examples, 0 failures)
Deprecation warnings: 0
RuboCop offenses: 0
GREEN — proceed to Phase 2.
Steps 3b–3d — Phases 2, 3, 4 (repeat the pattern)
Activate each intermediate Ruby, then run /fix next — it will resolve the target from whatever is still pending in the task list:
rbenv local 3.1.6
/ruby-upgrade-toolkit:fix next
rbenv local 3.2.4
/ruby-upgrade-toolkit:fix next
rbenv local 3.3.1
/ruby-upgrade-toolkit:fix next # last apply phase — upgrade complete
status is still available any time you want a full dashboard (/ruby-upgrade-toolkit:status), but fix's built-in verification has already gated each commit.
Phases 2–4 are typically much faster than Phase 1. The 2.7→3.0 step carries the bulk of the breaking changes.
Final status — upgrade complete
## Overall Readiness: GREEN
Ruby: 3.3.1 / Rails: not present
Tests: PASSING (312 examples, 0 failures)
Deprecation warnings: 0
Ruby warnings: 0
RuboCop offenses: 0
Suggested Next Step: Update CI/CD ruby-version and merge your upgrade branch.
Scenario 2: Coordinated Ruby + Rails upgrade (Ruby 2.7 → 3.3, Rails 6.1 → 8.0)
Starting state: Ruby 2.7.8, Rails 6.1.7, full Rails app with RSpec, PostgreSQL.
Tip: This scenario can be run automatically with
/ruby-upgrade-toolkit:upgrade ruby:3.3.1 rails:8.0. The manual walkthrough below is useful when you want to inspect each phase before proceeding.
Key rule: Always complete the full Ruby upgrade before starting the Rails upgrade. Ruby and Rails upgrades interact — attempting both simultaneously creates a debugging nightmare.
Step 1 — Audit
/ruby-upgrade-toolkit:audit ruby:3.3.1 rails:8.0
The audit covers both upgrade targets in one pass:
# Ruby Upgrade Audit Report
Current: Ruby 2.7.8 / Rails 6.1.7
Target: Ruby 3.3.1 / Rails 8.0
Upgrade Path: Ruby: 2.7→3.0→3.1→3.2→3.3 | Rails: 6.1→7.0→7.1→8.0
## Critical Issues
### Keyword Argument Mismatches (Ruby 3.0)
- 41 call sites with potential mismatch
## Rails Deprecations
### Dynamic Warnings (from test suite)
- 23 unique deprecation patterns
### Static Pattern Counts
| Pattern | Count |
|-------------------|-------|
| update_attributes | 8 |
| before_filter | 12 |
| redirect_to :back | 3 |
| old enum syntax | 6 |
## Gem Compatibility
### Must Update
| Gem | Current | Required |
|--------------|---------|----------|
| devise | 4.7 | >= 4.9 |
| sidekiq | 5.2 | >= 6.0 |
| ransack | 2.1 | >= 4.0 |
## Effort Estimate
Overall: High
- Ruby keyword arg fixes: ~41 sites
- Rails deprecation fixes: ~32 patterns across ~18 files
- Gem updates: 3 gems with significant version jumps
- Rails multi-step: 6.1 → 7.0 → 7.1 → 8.0 (3 Rails phases)
Step 2 — Plan
/ruby-upgrade-toolkit:plan ruby:3.3.1 rails:8.0
The plan sequences Ruby phases first, Rails phases second:
## Upgrade Roadmap: Ruby 2.7→3.3, Rails 6.1→8.0
### Estimate Summary
| Phase | Effort | Risk | Blast radius | Confidence |
|-----------------------|--------|------|------------------------------|------------|
| Ruby Phase 1: 2.7→3.0 | ~3h | MED | 41 sites, 5 files, 1 gem | HIGH |
| Ruby Phase 2: 3.0→3.1 | ~1h | LOW | 0 sites | HIGH |
| Ruby Phase 3: 3.1→3.2 | ~1h | LOW | 0 sites | HIGH |
| Ruby Phase 4: 3.2→3.3 | ~1h | LOW | 0 sites | HIGH |
| Rails Phase 1: 6.1→7.0| ~4h | HIGH | ~21 sites, 12 files, 2 gems | MED |
| Rails Phase 2: 7.0→7.1| ~2h | MED | ~8 sites, 4 files | MED |
| Rails Phase 3: 7.1→8.0| ~2.5h | MED | ~6 sites, 6 files, 1 gem | MED |
| Phase 3: Verification | ~1h | LOW | — | HIGH |
| **Total** | **~15.5h** | **HIGH** | ~76 sites, 27 files, 3 gems | **MED** |
Formulas:
- Ruby 2.7→3.0 = 41 kwarg × 2min + 1 gem × 30min + 1 hop × 60min = 172min ≈ 3h
- Rails 6.1→7.0 = 21 deprecations × 5min + 12 config × 10min + 2 gems × 30min + 1 hop × 60min = 345min ≈ 4h
- Rails 7.1→8.0 = 6 deprecations × 5min + 6 config × 10min + 1 gem × 30min + 1 hop × 60min = 180min ≈ 2.5h
- Risk escalated to HIGH because Rails hops > 1 and `devise` gem pin required native-ext spike
### Prerequisites
- [ ] Install Ruby 3.0.7, 3.1.6, 3.2.4, 3.3.1
### Ruby Phase 1: 2.7 → 3.0 (keyword args, YAML)
**Effort:** ~3h · **Risk:** MED · **Blast radius:** 41 sites, 5 files, 1 gem · **Confidence:** HIGH
### Ruby Phase 2: 3.0 → 3.1
### Ruby Phase 3: 3.1 → 3.2
### Ruby Phase 4: 3.2 → 3.3 ← Ruby upgrade complete here
### Rails Phase 1: 6.1 → 7.0 (enum syntax, Zeitwerk, filter→action)
**Effort:** ~4h · **Risk:** HIGH · **Blast radius:** 21 sites, 12 files, 2 gems · **Confidence:** MED
### Rails Phase 2: 7.0 → 7.1 (load_defaults, encryption)
### Rails Phase 3: 7.1 → 8.0 (config updates, gem updates)
### Final Verification
**Effort:** ~1h · **Risk:** LOW · **Confidence:** HIGH
- [ ] Full RSpec suite green
- [ ] 0 deprecation warnings
- [ ] 0 RuboCop offenses
- [ ] Update CI/CD and Dockerfiles manually
Step 3 — Ruby upgrade phases (same as Scenario 1)
Work through Phases 1–4 exactly as in Scenario 1. Activate each Ruby version before running its fix phase:
rbenv local 3.0.7
/ruby-upgrade-toolkit:fix ruby:3.0.7
/ruby-upgrade-toolkit:status # must be GREEN
rbenv local 3.1.6
/ruby-upgrade-toolkit:fix ruby:3.1.6
/ruby-upgrade-toolkit:status # must be GREEN
rbenv local 3.2.4
/ruby-upgrade-toolkit:fix ruby:3.2.4
/ruby-upgrade-toolkit:status # must be GREEN
rbenv local 3.3.1
/ruby-upgrade-toolkit:fix ruby:3.3.1
/ruby-upgrade-toolkit:status # must be GREEN
Do not start Rails phases until the final Ruby status is GREEN.
Step 4 — Rails Phase 1: 6.1 → 7.0
/ruby-upgrade-toolkit:fix ruby:3.3.1 rails:7.0
Claude applies:
- Updates
gem 'rails'pin to~> 7.0, runsbundle update rails - Runs
bin/rails app:update— reviews generated diffs - Updates
config.load_defaults 7.0 - Applies safe deprecation fixes:
update_attributes→update(8 occurrences),before_filter→before_action(12 occurrences),redirect_to :back→redirect_back(3 occurrences),enumsyntax rewrites (6 occurrences) - Updates gem versions for Rails 7.0 compatibility: devise, sidekiq, ransack
- Runs RSpec iteratively until green
- Runs RuboCop until clean
/ruby-upgrade-toolkit:status
Expected: GREEN before proceeding.
Step 5 — Rails Phase 2: 7.0 → 7.1
/ruby-upgrade-toolkit:fix ruby:3.3.1 rails:7.1
/ruby-upgrade-toolkit:status # GREEN required
Step 6 — Rails Phase 3: 7.1 → 8.0
/ruby-upgrade-toolkit:fix ruby:3.3.1 rails:8.0
/ruby-upgrade-toolkit:status # GREEN = upgrade complete
Final status
## Overall Readiness: GREEN
Ruby: 3.3.1 / Rails: 8.0.0
load_defaults: 8.0
Tests: PASSING (847 examples, 0 failures)
Deprecation warnings: 0
RuboCop offenses: 0
Suggested Next Step: Update CI/CD ruby-version and rails version, update Dockerfiles, merge.
Scenario 3: Rails-only upgrade (Ruby unchanged, Rails 7.0 → 8.0)
Starting state: Ruby 3.2.4 (staying on 3.2), Rails 7.0.8. No Ruby version change needed.
Tip: This scenario can be run automatically with
/ruby-upgrade-toolkit:upgrade ruby:3.2.4 rails:8.0. Pass the current Ruby version — the upgrade command detects no Ruby change is needed and skips Ruby phases entirely.
Pass the current Ruby version in all commands — the fix skill detects that it matches the active Ruby and skips Ruby-specific changes, applying only Rails fixes.
Step 1 — Audit
/ruby-upgrade-toolkit:audit ruby:3.2.4 rails:8.0
Since Ruby target equals current, the audit skips Ruby breaking changes and focuses on Rails:
# Ruby Upgrade Audit Report
Current: Ruby 3.2.4 / Rails 7.0.8
Target: Ruby 3.2.4 / Rails 8.0
Upgrade Path: Ruby: no change | Rails: 7.0 → 7.1 → 8.0
## Critical Issues
(none — Ruby version unchanged)
## Rails Deprecations
### Dynamic Warnings
- 18 unique deprecation patterns
### Static Pattern Counts
| Pattern | Count |
|-------------------|-------|
| update_attributes | 5 |
| old enum syntax | 3 |
| redirect_to :back | 1 |
## Gem Compatibility
### Must Update
| Gem | Current | Required |
|-----------|---------|----------|
| puma | 5.6 | >= 6.0 |
| nokogiri | 1.13 | >= 1.15 |
## Effort Estimate
Overall: Low–Medium
- No Ruby code changes required
- Rails deprecation fixes: ~9 patterns, ~7 files
- Rails: 2 upgrade steps (7.0→7.1→8.0)
Step 2 — Plan
/ruby-upgrade-toolkit:plan ruby:3.2.4 rails:8.0
## Upgrade Roadmap: Rails 7.0 → 8.0 (Ruby unchanged at 3.2.4)
### Estimate Summary
| Phase | Effort | Risk | Blast radius | Confidence |
|-----------------------|--------|------|---------------------------|------------|
| Rails Phase 1: 7.0→7.1| ~2h | LOW | 5 sites, 4 files | HIGH |
| Rails Phase 2: 7.1→8.0| ~2h | MED | 4 sites, 3 files, 1 gem | HIGH |
| Phase 3: Verification | ~1h | LOW | — | HIGH |
| **Total** | **~5h** | **MED** | 9 sites, 7 files, 1 gem | **HIGH** |
Formulas:
- Rails 7.0→7.1 = 5 deprecations × 5min + 4 config × 10min + 1 hop × 60min = 125min ≈ 2h
- Rails 7.1→8.0 = 4 deprecations × 5min + 3 config × 10min + 1 gem × 30min + 1 hop × 60min = 140min ≈ 2h
### Prerequisites
- [ ] Baseline: 0 test failures
### Rails Phase 1: 7.0 → 7.1
**Effort:** ~2h · **Risk:** LOW · **Blast radius:** 5 sites, 4 files · **Confidence:** HIGH
- Update rails gem pin
- Run bin/rails app:update, review diffs
- Set config.load_defaults 7.1
- Apply deprecation fixes
- RSpec green + RuboCop clean
- Checkpoint: status GREEN required
### Rails Phase 2: 7.1 → 8.0
**Effort:** ~2h · **Risk:** MED · **Blast radius:** 4 sites, 3 files, 1 gem · **Confidence:** HIGH
- Update rails gem pin
- Run bin/rails app:update, review diffs
- Set config.load_defaults 8.0
- Apply any remaining deprecation fixes
- RSpec green + RuboCop clean
- Checkpoint: status GREEN = done
Step 3 — Rails Phase 1: 7.0 → 7.1
/ruby-upgrade-toolkit:fix ruby:3.2.4 rails:7.1
Claude detects that Ruby target matches current and skips all Ruby-specific steps. It applies only:
- Rails gem pin update →
bundle update rails bin/rails app:updatediff reviewconfig.load_defaults 7.1- Deprecation fixes:
update_attributes→update, enum syntax,redirect_to :back - Gem updates: puma, nokogiri
- Iterative RSpec until green
- Iterative RuboCop until clean
/ruby-upgrade-toolkit:status
## Overall Readiness: GREEN
Ruby: 3.2.4 (unchanged) / Rails: 7.1.0
Tests: PASSING (634 examples, 0 failures)
Deprecation warnings: 0
RuboCop offenses: 0
Step 4 — Rails Phase 2: 7.1 → 8.0
/ruby-upgrade-toolkit:fix ruby:3.2.4 rails:8.0
/ruby-upgrade-toolkit:status
## Overall Readiness: GREEN
Ruby: 3.2.4 (unchanged) / Rails: 8.0.0
Tests: PASSING (634 examples, 0 failures)
Deprecation warnings: 0
RuboCop offenses: 0
Suggested Next Step: Update CI/CD rails version, update Dockerfiles, merge.
Scenario 4: Upgrade with custom rules (Rails LTS + Brakeman gate + phantomjs swap)
Starting state: Ruby 3.2.4, Rails 6.1.7. The team has three project-specific constraints the toolkit can't infer on its own:
- Paid Rails LTS subscription — they want to stay on Rails 6.1 with backported security patches instead of moving to mainline Rails 7+.
- Security policy — every upgrade phase must pass
bundle exec brakeman --no-pager --exit-on-warn; failures block the per-phase commit. - Legacy dependency —
phantomjsshould be replaced withselenium-webdriver + webdrivers, and the Capybara driver config should flip to headless chromium during the same phase.
Step 1 — Create a rules file
/ruby-upgrade-toolkit:rules init
This writes .ruby-upgrade-toolkit/rules.yml with one commented example per rule type, all enabled: false by default. Either edit the file to activate the examples you want, or author each rule interactively:
/ruby-upgrade-toolkit:rules add target-substitute
# target=rails, replacement gem=railslts-version, constraint='~> 6.1.7',
# source url=https://railslts.com, credentials_env=BUNDLE_RAILSLTS__COM
/ruby-upgrade-toolkit:rules add verification-gate
# command='bundle exec brakeman --no-pager --exit-on-warn',
# phases=all, timing=after, required=true, id=brakeman-gate
/ruby-upgrade-toolkit:rules add gem-swap
# from=phantomjs,
# to=[selenium-webdriver ~> 4.0, webdrivers ~> 5.0],
# code_transforms=[Capybara.javascript_driver = :poltergeist
# → Capybara.javascript_driver = :selenium_chrome_headless]
The resulting .ruby-upgrade-toolkit/rules.yml:
version: 1 rules: - id: rails-lts-substitute type: target-substitute target: rails replacement: gem: railslts-version constraint: '~> 6.1.7' source: url: https://railslts.com credentials_env: BUNDLE_RAILSLTS__COM description: "Use Rails LTS 6.1 (paid backports) instead of mainline Rails" - id: brakeman-gate type: verification-gate command: "bundle exec brakeman --no-pager --exit-on-warn" when: { phases: [all] } timing: after required: true description: "Every phase must pass Brakeman before GREEN" - id: phantomjs-to-selenium type: gem-swap from: phantomjs to: - { name: selenium-webdriver, constraint: '~> 4.0' } - { name: webdrivers, constraint: '~> 5.0' } code_transforms: - pattern: "Capybara.javascript_driver = :poltergeist" replacement: "Capybara.javascript_driver = :selenium_chrome_headless" description: "Replace phantomjs with selenium + headless chromium"
Step 2 — Set credentials (never stored in rules.yml)
export BUNDLE_RAILSLTS__COM='your:credentials' # equivalent: # bundle config railslts.com your:credentials
Only the env var name lives in rules.yml; the secret stays in your shell / bundler config.
Step 3 — Validate and dry-run
/ruby-upgrade-toolkit:rules validate
/ruby-upgrade-toolkit:rules explain
explain shows exactly which rules will fire against the current project and which will no-op, with reasons:
Current state: Ruby 3.2.4, Rails 6.1.7
Active rules (3):
[rails-lts-substitute] target-substitute
Will redirect: Rails 6.1 → Rails LTS 6.1 (mainline Rails hops skipped)
Preflight: BUNDLE_RAILSLTS__COM is set ✓
[brakeman-gate] verification-gate
Will fire: verify step of every apply phase
Binary check: OK (brakeman 6.1.2 found)
[phantomjs-to-selenium] gem-swap
Will fire: Ruby Phase 2 (3.3 phase — first apply phase)
Matched: phantomjs found in Gemfile; 2 Capybara config sites match
Step 4 — Audit and plan
/ruby-upgrade-toolkit:audit ruby:3.3.1 rails:6.1
/ruby-upgrade-toolkit:plan ruby:3.3.1 rails:6.1
The audit gains a Custom Rules Impact section listing each rule, its affected phases, and its incremental effort contribution. The plan annotates phase checklists inline — built-in steps remain untagged, rule-driven steps get a [rule: <id>] tag:
Ruby Phase 2: 3.2 → 3.3
- Update .ruby-version and Gemfile ruby pin
- [rule: phantomjs-to-selenium] Swap phantomjs → selenium-webdriver + webdrivers
- [rule: phantomjs-to-selenium] Rewrite Capybara driver config (2 sites)
- RSpec green + RuboCop clean
- [rule: brakeman-gate] Brakeman: no warnings (required)
- Checkpoint: status GREEN required
Rails Phase 1: 6.1 → Rails LTS 6.1 ← path redirected by rule: rails-lts-substitute
- [rule: rails-lts-substitute] Replace 'rails' gem with railslts-version from https://railslts.com
- Run bundle update rails
- RSpec green + RuboCop clean
- [rule: brakeman-gate] Brakeman: no warnings (required)
- Checkpoint: status GREEN = upgrade complete
The Estimate Summary adds a Rules contrib column so rule-driven effort is visible at a glance.
Step 5 — Run the upgrade
/ruby-upgrade-toolkit:upgrade ruby:3.3.1 rails:6.1
Upgrade's preflight verifies all rule credentials before the phase loop starts; missing credentials fail fast with a clear message. Each phase's commit message includes a Custom rules applied block, so rule impact is auditable in git history:
chore(upgrade): ruby 3.3.1 phase
Version pins:
- .ruby-version: 3.2.4 → 3.3.1
Custom rules applied:
- [phantomjs-to-selenium] Swapped phantomjs → selenium-webdriver, webdrivers. Rewrote 2 Capybara config sites.
- [brakeman-gate] Passed (0 warnings).
Verification:
- RSpec: 847 examples, 0 failures
- RuboCop: 0 offenses
- Custom gate brakeman-gate: PASS (required)
- Tier: GREEN
If Brakeman flags new warnings in any phase, the fix step exits without committing and the upgrade's recovery menu adds a D) Disable rule <id> and retry option so you can unblock the pipeline without editing rules.yml mid-run:
⛔ UPGRADE PAUSED — Phase [Rails LTS 6.1] did not pass verification
⛔ Required gate failed: brakeman-gate
[brakeman output]
━━━ What you can do ━━━
A) Investigate and fix
B) Retry this phase
C) Abort
D) Disable rule brakeman-gate and retry
Re-enable the gate later once the warnings are resolved:
/ruby-upgrade-toolkit:rules enable brakeman-gate
/ruby-upgrade-toolkit:status # gate re-runs on the next check
Takeaway. Custom rules are the extensibility point for project-specific policy. The toolkit's built-in behavior stays simple; teams enforce their own standards — LTS subscriptions, security gates, gem conventions — alongside it without forking the plugin.
Quick reference
# Full automated upgrade (recommended starting point) /ruby-upgrade-toolkit:upgrade ruby:3.3.1 /ruby-upgrade-toolkit:upgrade ruby:3.3.1 rails:8.0 # Read-only scan before touching anything /ruby-upgrade-toolkit:audit ruby:3.3.1 rails:8.0 # Generate a phased roadmap without executing it /ruby-upgrade-toolkit:plan ruby:3.3.1 rails:8.0 # Apply fixes for a specific phase manually /ruby-upgrade-toolkit:fix ruby:3.3.1 /ruby-upgrade-toolkit:fix ruby:3.3.1 rails:8.0 # Fix deprecations in a single file (gem/pin changes still apply project-wide) /ruby-upgrade-toolkit:fix ruby:3.3.1 rails:8.0 scope:app/models/order.rb # Fix an entire directory /ruby-upgrade-toolkit:fix ruby:3.3.1 rails:8.0 scope:app/controllers/ # Check upgrade health at any point without making changes /ruby-upgrade-toolkit:status # Manage project-specific custom rules (gem pins, swaps, gates, etc.) /ruby-upgrade-toolkit:rules init # scaffold with examples /ruby-upgrade-toolkit:rules add verification-gate # author a Brakeman/Reek gate /ruby-upgrade-toolkit:rules list # see active rules /ruby-upgrade-toolkit:rules explain # dry-run against current project /ruby-upgrade-toolkit:rules validate # CI-friendly schema check
Agents
Two agents activate automatically based on natural language context — no slash commands needed.
upgrade-auditor
Fires when you describe an upgrade intent:
- "I need to upgrade this app from Rails 7 to 8"
- "What would it take to get to Ruby 3.3?"
- "How bad is our deprecation situation?"
Detects whether the project is Rails or plain Ruby automatically. Produces the same findings report as /ruby-upgrade-toolkit:audit and ends with the recommended command sequence.
deprecation-fixer
Fires when you ask to fix deprecations in a specific file or directory:
- "Fix the deprecation warnings in app/models/user.rb"
- "Clear all deprecations from app/controllers/"
Reads each file, applies safe mechanical fixes automatically, presents complex fixes for confirmation, and runs the file's tests to verify.
Hooks
Three automatic hooks activate when the plugin is installed:
| Hook | Event | Behavior |
|---|---|---|
block-vendor |
PreToolUse (Write/Edit) | Blocks any write to vendor/ — use bundle update instead |
ruby-version-sync |
PostToolUse (Write/Edit) | Warns when .ruby-version and Gemfile ruby directive have different minor versions |
rubocop-fix |
PostToolUse (Write/Edit) | Opt-in — auto-corrects Style/Layout cops on edited .rb files |
Enable RuboCop auto-fix
touch .ruby-upgrade-toolkit-rubocop # enable rm .ruby-upgrade-toolkit-rubocop # disable
Requires rubocop in your Gemfile. Only corrects Style and Layout cops — does not touch Metrics or Lint.
Prerequisites
- Claude Code 1.x+
- Ruby project using Bundler
jqinstalled (required by hook scripts):brew install jq/apt install jq- RuboCop in Gemfile (for rubocop-fix hook and
fixcommand RuboCop step) - Target Ruby version installed via rbenv or rvm before running
fix
Ruby ↔ Rails Version Compatibility
| Target Ruby | Minimum Rails | Recommended Rails |
|---|---|---|
| 2.7 | 5.2 | 6.0–6.1 |
| 3.0 | 6.1 | 7.0 |
| 3.1 | 7.0 | 7.0–7.1 |
| 3.2 | 7.0.4 | 7.1 |
| 3.3 | 7.1 | 7.1–7.2 |
| 3.4 | 7.2 | 7.2–8.0 |
Always complete the Ruby upgrade before starting the Rails upgrade when doing both.
CI Template
A GitHub Actions workflow template is included at .github/workflows/ruby-upgrade-ci.yml. Copy it to your application's .github/workflows/ directory.
It provides three jobs:
| Job | When it runs | What it checks |
|---|---|---|
test |
Always | Ruby deprecation warnings + RSpec/rake test |
rails-test |
Rails projects only (config/application.rb present) |
DB setup, Zeitwerk, Rails deprecation count, RSpec |
migration-safety |
When db/migrate/ exists |
Risky migration pattern counts |
Adjust the ruby-version matrix to the versions relevant to your upgrade path.
Contributing
Issues and PRs welcome at github.com/dhruvasagar/ruby-upgrade-toolkit.
Adding a new Rails version guide
Add a file at skills/rails-upgrade-guide/references/rails-X-to-Y.md and reference it in skills/rails-upgrade-guide/SKILL.md.
Updating the gem compatibility matrix
Edit skills/rails-upgrade-guide/references/compatibility-matrix.md. Follow the existing table format.
Adding a new Ruby version's breaking changes
Add breaking change patterns to the relevant step in skills/audit/SKILL.md (Step 3) and skills/fix/SKILL.md (Step 4), scoped to the relevant version pair.
License
MIT