rubocop-kata
A RuboCop plugin that replaces a pile of linter gems and config with one dependency. Install it and your .rubocop.yml shrinks to project-specific overrides. Everything else comes from the gem: a curated stack of RuboCop extensions, opinionated style defaults, and fifteen house cops.
Kata/GoodMethodName asks for one word per method name. A second word is
allowed only when it is not hiding a missing object: a role prefix
(after_fork), a role suffix (file_of), or a compound noun you have added to
Terms on purpose. The wrong way to satisfy the cop is matching_ids →
matchingids: if you need two words, that is a modelling smell — the concept is
missing, or the method belongs on a different object. Kata/RealWords catches
that dodge with a shipped dictionary instead of a word list you maintain:
matchingids is not a word, so it fails with zero configuration — and so does
cfg.
- One dependency. Bundles rubocop-rspec, rubocop-performance, rubocop-elegant, rubocop-packaging, and rubocop-thread_safety behind a single gem.
- Opinionated defaults. Methods under 5 lines, classes under 100, 4 parameters max, 120-column lines, double quotes,
NewCops: enable. See config/default.yml. - Fifteen house cops. Naming, dependency discipline, and data honesty in the Elegant Objects spirit — from
Kata/AgentNountoKata/ClockDiscipline.
class PaymentProcessor # Kata/AgentNoun: `PaymentProcessor` names a doer; name the class
end # for the thing it is, not the work it does. Try `Payment`.
class Payment # OK
end
It suggests a name only when it can derive one: a compound minus its agent word,
or a regular -ator/-ector/-isor/-izer noun the shipped dictionary
confirms (Selector → Selection, Synthesizer → Synthesis).
Getting started
Add the gem to your Gemfile:
gem "rubocop-kata", group: :development, require: false
Install it and point .rubocop.yml at the plugin:
plugins:
- rubocop-kata
AllCops:
TargetRubyVersion: 3.4
bundle install
bundle exec rubocop
That's it. The plugin loads the bundled extensions and the shared defaults, so your .rubocop.yml keeps only what's specific to your project. Requires Ruby >= 3.4 and RuboCop ~> 1.75.
The bundled rubocop-elegant
The released rubocop-elegant 0.7.1 has four defects this project reported
upstream: NoRedundantVariable autocorrect corrupts Ruby 3.1 shorthand
(#74), ClassInModule
reports a class nested in a class as global
(#75),
PairedBrackets autocorrect drifts the indent
(#76), and the
test-file exclusions never match a Rails or RSpec suite
(#77).
The defaults here neutralise all four. Elegant/ClassInModule is off, because
its offences cannot be cleared: the cop it ships beside forbids the module it
asks for. The two broken correctors are set to report only, because their
offences can be cleared, by hand. The exclusions are widened. Nothing corrupts
your code out of the box, and nothing reports a defect you cannot fix.
A gemspec cannot name a git source, so to take the fixes themselves rather than
the workarounds, add this to your own Gemfile:
gem "rubocop-elegant", github: "giacope/rubocop-elegant", branch: "fixes"
That branch is the released gem plus the five pull requests, with the version pinned so Bundler resolves it. With it in place you can turn the two correctors back on:
Elegant/NoRedundantVariable:
AutoCorrect: true
Elegant/PairedBrackets:
AutoCorrect: true
Elegant/ClassInModule:
Enabled: true
Drop all three once upstream releases the fixes. On a Rails codebase, consider
leaving Elegant/ClassInModule off for good: it wants every class inside a
module, and Zeitwerk resolves class Account from app/models/account.rb as a
top-level constant by design.
The cops
| Cop | Default | What it enforces |
|---|---|---|
Kata/AgentNoun |
on | Classes named for what they are, not -er/-or doers. Suggests the better name when it can derive one. AllowedNames matches a whole name or a trailing segment, so Error covers UsageError. |
Kata/NoComments |
on | No prose comments; say it in the code. Magic comments, linter directives, and licence headers survive. Autocorrects. |
Kata/IoDiscipline |
on | No bare puts/warn/pp/p outside the test suite and the boot layer; write through an injected @io or an explicit receiver. |
Kata/ProsePlacement |
off | Sentence-length strings belong in the presentation layer. Enable with an Include/Exclude matching your layering. |
Kata/NoUtilName |
on | No junk-drawer names (Util, Helper, Manager, Service, …). Tune via BannedNames. |
Kata/RealWords |
on | Every name segment is a word the shipped dictionary knows — errorcount (smash) and cfg (abbreviation) both fail, with no word list to maintain. Tune via Terms/BannedWords/AllowedNames. |
Kata/GoodMethodName |
on | One word per method name; a second word needs a role prefix (after_fork), a role suffix (file_of), or a reviewed Terms entry. Tune via MaxWords/Prefixes/Suffixes/Terms/AllowedNames. |
Kata/GoodVariableName |
on | The same rule for locals, parameters, ivars, class variables, and globals; _name and @_name stay exempt. |
Kata/BuilderNoun |
on | Builders named for what they return: total, not calculate_total. Tune via BannedPrefixes/AllowedNames. |
Kata/NoBooleanFlag |
on | No positional boolean arguments; split the method or use a keyword. |
Kata/ConstructorDiscipline |
on | initialize assigns, raises, or freezes — never computes. |
Kata/NoClassMethodLogic |
on | Class methods construct (build, parse, of, from_*); instances do the work. |
Kata/NoHashAsObject |
on | A hash with MaxKeys+ keys (default 4) wants to be an object. Keyword-argument call sites exempt. |
Kata/ClockDiscipline |
on | No bare Time.now/Date.today/.current; inject a clock. |
Kata/EnvDiscipline |
on | ENV reads only in the boot layer (config/, db/seeds*, lib/tasks/, rake files, bin/, exe/) — or as a parameter default, which is the seam the cop asks for. |
Dead configuration
doctor reports configuration that no longer does anything: a .rubocop.yml
entry for a cop the inherited configuration disables, and a
rubocop:disable/enable comment naming a cop that is not enabled where the
comment sits. RuboCop reports neither.
bundle exec rubocop-kata doctor # or: doctor path/to/project
.rubocop.yml:20: `Elegant/GoodMethodName` is disabled by the configuration this project inherits; the entry does nothing.
lib/registry.rb:44: the directive names `Elegant/GoodMethodName`, which is not enabled here; the comment does nothing.
2 dead entries
It exits non-zero when it finds something, so it can gate CI.
Adoption order
Kata's structural cops create names and its naming cops charge for them, so a
structural refactor removes offenses and adds more. A single total cannot tell
progress from regression. plan buckets the backlog into the stages that cause
each other and names the one to take next.
bundle exec rubocop-kata plan # or: plan path/to/project
structure 184 Kata/ConstructorDiscipline 92, Kata/NoHashAsObject 48, Kata/EnvDiscipline 44
naming 263 Kata/RealWords 141, Kata/GoodVariableName 88, Kata/AgentNoun 34
prose 57 Kata/NoComments 57
rest 412 Elegant/PairedBrackets 300, Layout/LineLength 112
next: structure — 184 offenses in 61 files; these mint the names `naming` then prices, so take them first
densest: app/models/account.rb (14)
Structure before naming, because doing naming first means renaming things the structural pass is about to move.
The gem also ships an agent skill that runs this loop: it works the stage plan
names, checks every name it introduces against the same dictionary
Kata/RealWords reads, and reports removed and created separately instead of a
net total.
mkdir -p .claude/skills/rubocop-kata
bundle exec rubocop-kata skill > .claude/skills/rubocop-kata/SKILL.md
It writes to stdout, so the same command installs it anywhere an agent reads
skills from — a project, ~/.claude/skills/, or a plugin.
The defaults
Double-quoted strings, Metrics/MethodLength: 5, Metrics/ClassLength: 100,
Metrics/ParameterLists: 4, 120-column lines, endless methods on one line,
rescue => error, NewCops: enable, heredocs counted as one line in spec
examples, and no inline rubocop:disable comments
(Style/DisableCopsWithinSourceCodeDirective). See
config/default.yml.
License
The code is MIT — see LICENSE.txt. The shipped dictionaries are third-party data under their own terms:
data/words.txt.gz— a modified subset of SCOWL en-US size 60: filtered, deduplicated, and gzipped. SCOWL is the collective work of Kevin Atkinson and the contributors named in data/SCOWL-COPYRIGHT, which is distributed with this gem and reproduced verbatim. It includes, among others, Copyright 2000–2018 Kevin Atkinson; WordNet 1.6 Copyright 1997 by Princeton University, all rights reserved; and Copyright 1993 Geoff Kuenning, Granada Hills, CA, all rights reserved. Princeton University makes no representations or warranties, express or implied, as to this database, and its name may not be used in advertising or publicity pertaining to this distribution.data/software.txt.gz— built from thesoftware-terms,ruby, andshelldictionaries of cspell-dicts, each MIT-licensed. Copyright (c) 2017–2025 Street Side Software; the notice and permission text are reproduced in data/CSPELL-LICENSE.data/supplement.txt— this project's own additions, MIT.