rubocop-hash_inspect
A RuboCop extension gem that provides a single, conservative cop flagging Ruby code that relies on the legacy (Ruby ≤ 3.3) Hash#inspect output format. Ruby 3.4 (Dec 2024) changed Hash#inspect from {:x=>1, "baz"=>3} to {x: 1, "baz" => 3} (Ruby Bug #20433), and that format carries into Puppet 9's Ruby 4 runtime. Code that hardcodes the old format — most commonly test contracts comparing against literal strings like eq("{:a=>1}") — breaks silently on Puppet 9. The cop catches this statically during pdk validate so Puppet module authors can fix it before upgrading.
This gem is the deliverable for CAT-2635 (parent epic CAT-2630 — PDK | Puppet 9 Compatibility Testing).
Core Value: pdk validate flags reliance on legacy Hash#inspect output with actionable, low-false-positive output, so module authors fix it before Puppet 9 / Ruby 4 breaks them — with zero behavior change for Puppet 8 (Ruby 3.2) validation.
- Tech stack: Ruby gem, RuboCop extension API (cop + spec + config); RSpec for cop specs using RuboCop's cop test helpers.
- Compatibility: Must be additive — no false positives on Puppet 8 / Ruby 3.2 module code; no change to existing
pdk validatebehavior when the cop is disabled. - Quality bar: Heuristic by nature — success criterion relaxed (per CAT-2635 investigation) from "zero false positives" to no false positives on the curated clean-module baseline.
- Dependencies: Two-repo delivery — the gem itself, plus a
pdk-templates.rubocop.ymlPR to wire it intopdk validate. The template PR depends on a published/loadable gem. - Integration: Loadable as a RuboCop extension via
require:in.rubocop.yml; configurable like any standard cop.
| Technology | Version | Purpose | Why Recommended |
|---|---|---|---|
| rubocop | >= 1.72.2 (current: 1.87.0) |
Provides Cop::Base, AST helpers, plugin machinery, rspec/support test harness |
The minimum for the new plugin API; current as of May 2026. Pin with < 2.0 to avoid breaking changes from the upcoming major. |
| rubocop-ast | >= 1.45.1, < 2.0 (current: 1.49.1) |
NodePattern, Node — the DSL used to match AST subtrees inside cops |
Shipped as a separate gem since RuboCop 0.x; always needed explicitly in a gem that uses def_node_matcher / def_node_search. Lower bound tracks rubocop-performance's own floor. |
| lint_roller | ~> 1.1 (current: 1.1.0) |
Provides LintRoller::Plugin base class for the modern plugin registration system |
Required for the plugins: directive in .rubocop.yml. A transitive dep of rubocop itself but must be listed explicitly in the gemspec so bundler can resolve it without an implicit assumption. |
| Ruby | >= 2.7.0 |
Runtime | Matches rubocop's own minimum. Puppet 8 ships Ruby 3.2; Puppet 9 ships Ruby 4. Supporting >= 2.7 keeps the gem compatible with both without extra effort. |
| Library | Version | Purpose | When to Use |
|---|---|---|---|
| rspec | ~> 3.13 (current: 3.13.2) |
Test framework; cop specs use expect_offense/expect_no_offenses |
Always — the rubocop project's own test harness is RSpec-only |
| rake | ~> 13.4 (current: 13.4.2) |
new_cop Rake task (generator), spec task |
Always — convention in rubocop extension gems; the extension generator wires both |
| rubocop-internal_affairs | (latest via rubocop plugin) | Lints your own cop code; catches e.g. missing on_csend alongside on_send |
Use in .rubocop.yml for the gem's own source quality gate — it's free: just list it under plugins: |
| Tool | Purpose | Notes |
|---|---|---|
rubocop-extension-generator |
Generates the full gem skeleton from a single command | Run once: gem install rubocop-extension-generator && rubocop-extension-generator rubocop-hash_inspect. The generator's output is the canonical skeleton (it is maintained by the RuboCop core team). |
bundle gem |
Alternative lower-level scaffold | The extension generator calls bundle gem internally, then layers on RuboCop-specific files. Don't use bundle gem alone — you'll miss plugin.rb, config/default.yml, and the Rake integration. |
bundle exec rake 'new_cop[HashInspect/LegacyHashInspectFormat]' |
Generates the cop skeleton + spec skeleton and wires them into the cops require file and config/default.yml |
Run after initial generator setup; the only safe way to add a cop because it also injects the require_relative entry and a stub config/default.yml block. |
Enabled: true— ship enabled; the cop is additive and non-blockingSeverity: convention— matches the PROJECT.md requirement ("convention/refactor severity")SafeAutoCorrect: false— correct; a heuristic cop cannot provide a safe autocorrect since it cannot know what the correct replacement isSafe: true— the offense detection itself is not side-effectfulVersionAdded— required field; set to'1.0'for the initial release- No
AutoCorrectkey needed whenSafeAutoCorrect: falseand no corrector is defined
| Recommended | Alternative | When to Use Alternative |
|---|---|---|
plugins: (lint_roller) |
require: in .rubocop.yml |
Internal/private projects where backward-compat with RuboCop < 1.72 is required. For a public PDK gem shipped 2026+, plugins: is correct. |
Cop::Base |
Cop::Cop (deprecated) |
Never — Cop::Cop is explicitly marked deprecated in RuboCop docs and will be removed in RuboCop 2.0 |
rubocop-extension-generator |
Handcrafted skeleton | Only if the generator itself has a bug or the skeleton needs heavy deviation. The generator is maintained by the RuboCop core team and produces the exact structure verified here. |
SafeAutoCorrect: false + no corrector |
Provide an autocorrect | This cop is heuristic — it cannot know the correct replacement. Providing an autocorrect would be unsafe and misleading. |
Severity: convention |
warning or error |
error would block CI on potentially false positives; warning is less visible. convention matches the PROJECT.md requirement and appears in pdk validate output without being blocking. |
| Avoid | Why | Use Instead |
|---|---|---|
RuboCop::Cop::Cop base class |
Explicitly deprecated; removed in RuboCop 2.0; InheritDeprecatedCopClass internal affairs cop will flag it |
RuboCop::Cop::Base |
require: in user-facing .rubocop.yml |
Deprecated for published gems since 1.72; emits warnings | plugins: |
Inject.defaults! / manual config injection |
The pre-plugin era approach; superseded by Plugin#rules returning a LintRoller::Rules path |
Plugin#rules returning config/default.yml path |
bundle gem alone (without extension generator) |
Produces a bare gem skeleton missing plugin.rb, config/default.yml, and the Rake cop generator task |
rubocop-extension-generator |
AutoCorrect: disabled in config |
Old config key; replaced by SafeAutoCorrect: false |
SafeAutoCorrect: false |
| Package | Version Constraint | Notes |
|---|---|---|
| rubocop | >= 1.72.2, < 2.0 |
1.72 = plugin API introduction; < 2.0 guards against breaking API changes |
| rubocop-ast | >= 1.45.1, < 2.0 |
Tracks rubocop-performance floor; current 1.49.1 |
| lint_roller | ~> 1.1 |
Only version available; ~> 1.1 is the standard constraint used by rubocop-performance and rubocop-rails |
| Ruby | >= 2.7.0 |
Matches rubocop's own floor; Puppet 8 ships 3.2, Puppet 9 ships Ruby 4 — both clear this bar |
| rspec | ~> 3.13 (dev only) |
Current stable 3.13.2; rspec 4 beta exists but is not production-ready |
| rake | ~> 13.4 (dev only) |
Current 13.4.2 |
- Puppet 8 → Ruby 3.2 (no Hash#inspect change; cop is additive)
- Puppet 9 / Ruby 4 → Ruby 3.4+ format change is present; cop fires correctly
rubocop/rubocop(Context7:/rubocop/rubocop) — plugin migration guide, development guide,support.rbsource- RuboCop Plugins Docs — verified plugin API,
plugins:directive, lint_roller dependency - RuboCop Plugin Migration Guide — gemspec metadata,
LintRoller::Pluginclass structure rubocop/rubocop-extension-generatorgenerator source (generator.rb) — exact generated file structure, gemspec patches, dependency versions (HIGH confidence: first-party source)rubocop/rubocop-performancegemspec,plugin.rb,spec_helper.rb— canonical real-world reference implementation (HIGH confidence)- rubocop-ast on RubyGems — current version 1.49.1, released 2026-03-20
- rubocop 1.87.0 on GitHub — current version, released 2026-05-30
- lint_roller 1.1.0 on RubyGems — current version
- rspec 3.13.2 on RubyGems — current stable, released 2025-10-21
- rake 13.4.2 on RubyGems — current version, released 2026-04-16
- Evil Martians: Writing custom RuboCop rules in 2026 —
rubocop-internal_affairsrecommendation,Cop::BasevsCop::Cop
Conventions not yet established. Will populate as patterns emerge during development.
Architecture not yet mapped. Follow existing patterns found in the codebase.
No project skills found. Add skills to any of: .claude/skills/, .agents/skills/, .cursor/skills/, .github/skills/, or .codex/skills/ with a SKILL.md index file.
- At the start of a coding session, review the repository structure and any relevant README or documentation files to understand the area you are working in.
- Always read the files relevant to the task before suggesting or making a change.
- Never merge a pull request.
- Never work directly on the
mainormasterbranch. - Never push a branch without explicit instruction.
- Never delete a file without permission — this applies even after a blanket "yes to all".
- Never output, log, save, or hardcode security-sensitive values — this includes passwords, tokens, API keys, private keys, secrets, and credentials of any kind. Do not write them to files, include them in commit messages, or print them in responses.
These rules are enforced by PreToolUse Bash hooks in .claude/ (no-pr-merge.sh, no-main-commits.sh, no-push.sh, no-rm.sh), which block the matching commands rather than relying on the model to comply.
Before using Edit, Write, or other file-changing tools, start work through a GSD command so planning artifacts and execution context stay in sync.
Use these entry points:
/gsd-quickfor small fixes, doc updates, and ad-hoc tasks/gsd-debugfor investigation and bug fixing/gsd-execute-phasefor planned phase work
Do not make direct repo edits outside a GSD workflow unless the user explicitly asks to bypass it.
Profile not yet configured. Run
/gsd-profile-userto generate your developer profile. This section is managed bygenerate-claude-profile-- do not edit manually.