Keela ð
Your Ruby codebase's forensic investigator.
Like her namesake â the famous springer spaniel who helped solve cases by finding microscopic traces that eluded forensic teams â Keela sniffs out the dead code that grep missed.
ð 6 strategies: methods, scopes, constants, delegates, attrs, i18n
ðŊ Baseline mode â only bark at NEW dead code
ð JSON output for CI pipelines
Why Remove Unused Code?
Dead code isn't harmless â it's actively costly:
- Cognitive overhead: Developers read and try to understand code that doesn't matter, slowing down onboarding and feature work
- CI minutes: Tests for unused methods still run, burning compute time on every pipeline
- False confidence: Test coverage metrics include dead code, masking gaps in the code that actually runs
- Refactoring friction: Unused code creates dependencies that make refactoring harder ("wait, is this called somewhere?")
- Security surface: More code means more potential vulnerabilities, even in paths users never hit
Most codebases accumulate dead code gradually â a feature flag that's always on, a method replaced but never deleted, a scope that lost its last caller. Keela helps you find it and clean it up.
Installation
gem install keela
Or add to your Gemfile:
gem 'keela', group: :development
Quick Start
# First time: generate a baseline of current unused code
keela --update-baseline
# This creates .keela_baseline.yml in your project root
# From now on, just run:
keela
# Keela will fail if:
# - NEW unused code is detected (someone added dead code)
# - Previously unused code was REMOVED (time to update the baseline!)
How It Works
Keela operates in two modes:
Baseline Mode (Default)
If a .keela_baseline.yml file exists, Keela compares the current scan against it:
| Scenario | Result |
|---|---|
| No changes from baseline | â Pass (silent, exit 0) |
| NEW unused code detected | â Fail (shows new items) |
| Code REMOVED from baseline | â Fail (prompts to update baseline) |
This lets you gradually pay down tech debt while preventing new dead code from sneaking in.
Report Mode
If no baseline exists (or you use --report), Keela shows all unused code:
keela --report
Command Line Options
# Scan for all unused code (methods, scopes, constants, delegations, attributes)
keela
# Scan for specific types
keela --type methods
keela --type scopes
# Combine multiple types
keela --type methods,scopes,constants
# Force report mode (ignore baseline)
keela --report
# Update the baseline file
keela --update-baseline
# Use a custom baseline path
keela --baseline config/unused_baseline.yml
# Specify excluded items file
keela --excluded config/keela_excluded.yml
# Custom file extensions
keela --extensions rb,rake,haml
# Exclude files matching patterns
keela --exclude 'vendor/**/*' --exclude 'tmp/**/*'
# Include additional directories (adds to defaults)
keela --include 'engines/**/*.rb' --include 'custom/**/*.rb'
# Use a custom config file
keela --config path/to/keela.yml
# Output as JSON (for CI integrations)
keela --format json
# Suppress progress bar (useful for CI and scripting)
keela --quiet
keela -q
# Show version
keela --version
Detection Strategies
Keela detects several types of unused code:
| Strategy | Detects | Example |
|---|---|---|
| methods | Unused method definitions | def unused_method |
| scopes | Unused ActiveRecord scopes | scope :unused_scope, -> { } |
| constants | Unused constants | UNUSED_CONSTANT = 'value' |
| delegations | Unused delegate declarations | delegate :unused, to: :target |
| attributes | Unused attr_* declarations | attr_accessor :unused_attr |
| i18n_keys | Unused translation keys | en.users.unused_key in locale YAML |
Run all strategies (default) or target specific ones with --type.
Note: The i18n_keys strategy is not included in --type all because it requires scanning YAML locale files. Run it explicitly with --type i18n_keys.
I18n Keys (Beta)
The i18n_keys strategy is beta and may produce false positives. It cannot detect:
- Lazy lookup -
t('.title')in views resolves based on the view path - Dynamic keys -
t("users.#{action}.title")with interpolated segments - Model translations -
User.human_attribute_name(:email)andUser.model_name.human - Pluralization siblings - If
one:is used,other:may appear unused
Review results carefully and use the exclusion file for known false positives.
Configuration File
Create a keela.yml or .keela.yml in your project root:
# keela.yml
extensions:
- rb
- haml
- erb
exclude_patterns:
- "vendor/**/*"
- "tmp/**/*"
include_patterns:
- "engines/**/*.%<ext>s"
excluded_path: ".keela_excluded.yml"
baseline_path: ".keela_baseline.yml"
Keela automatically loads keela.yml or .keela.yml from the current directory. Use --config to specify a different path.
Customizing Which Files to Scan
There are two approaches:
1. Tweak the defaults with --include and --exclude (or include_patterns/exclude_patterns in config):
# Add engines/ to the default app/, lib/, config/ directories
keela --include 'engines/**/*.rb'
# Exclude vendor files from scanning
keela --exclude 'vendor/**/*'
2. Full control with directory_patterns - replaces the defaults entirely:
# keela.yml - scan ONLY these directories
directory_patterns:
- "src/**/*.%<ext>s"
- "custom/**/*.%<ext>s"
Use directory_patterns when you need complete control. Use --include/--exclude when you just want to tweak the defaults.
Note: Mixing both approaches raises a ConfigurationError. Choose one or the other.
Available options:
| Key | Description | Default |
|---|---|---|
directory_patterns |
Glob patterns for files to scan (replaces defaults) | app/, lib/, config/ |
extensions |
File extensions to scan | rb, haml, erb |
include_patterns |
Additional patterns to scan (added to defaults) | [] |
exclude_patterns |
Patterns for files to exclude | [] |
excluded_path |
Path to YAML file of excluded items | nil |
baseline_path |
Path to baseline YAML file | .keela_baseline.yml |
required_directory |
Directory that must exist for scanning to proceed | nil |
CI Integration
Keela is designed for CI pipelines. Add it to catch dead code before it merges:
# .gitlab-ci.yml
unused_code:
script:
- bundle exec keela
rules:
- if: $CI_MERGE_REQUEST_IID
# .github/workflows/ci.yml
- name: Check for unused code
run: bundle exec keela
The workflow:
- Initial setup: Run
keela --update-baselineand commit.keela_baseline.yml - CI runs:
keelacompares against baseline, fails on new dead code - After cleanup: Run
keela --update-baselineto update the baseline
JSON Output
Use --format json for machine-readable output:
keela --format json --report
{
"strategies": ["methods", "scopes"],
"unused": {
"methods": {
"app/models/user.rb": ["unused_method", "old_helper"]
},
"scopes": {
"app/models/post.rb": ["inactive"]
}
},
"summary": {
"total": 3,
"by_strategy": {
"methods": 2,
"scopes": 1
}
}
}
This is useful for integrating with other tools, generating reports, or processing results programmatically.
Exclusion File
Some code appears unused but is actually called dynamically. Exclude it:
# .keela_excluded.yml
app/models/user.rb:
- legacy_method: "Called via metaprogramming"
- callback_method: "Used as ActiveRecord callback"
app/helpers/application_helper.rb:
- helper_method: "Called from views dynamically"
Then run with:
keela --excluded .keela_excluded.yml
Ruby API
require 'keela'
# Configure Keela
Keela.configure do |config|
config.extensions = %w[rb haml erb]
config.directory_patterns = %w[
app/**/*.%<ext>s
lib/**/*.%<ext>s
]
config.include_patterns = %w[engines/**/*.%<ext>s]
config.exclude_patterns = %w[vendor/**/* tmp/**/*]
config.excluded_path = '.keela_excluded.yml'
config.baseline_path = '.keela_baseline.yml'
end
# Run a scan
strategy = Keela::Strategies::Methods.new
scanner = Keela::Scanner.new(strategy: strategy)
success = scanner.run
# Access results
scanner.unused_collection # Hash of file => [unused_names]
scanner.new_unused # Items not in baseline
scanner.removed # Items in baseline but no longer unused
Custom Strategies
Detect other patterns by creating your own strategy:
class CallbackStrategy < Keela::Strategy
def name
"callbacks"
end
def definition_file_pattern
%r{app/models}
end
def extract_definition(line)
# Match: before_save :do_something
line =~ /(?:before|after|around)_\w+\s+:(\w+)/ ? Regexp.last_match(1) : nil
end
def usage_regex(name)
/def #{Regexp.quote(name)}\b/
end
def skip_comments?
true
end
end
scanner = Keela::Scanner.new(strategy: CallbackStrategy.new)
scanner.run(force_report: true)
About the Name
Keela was a famous English Springer Spaniel known as the "CSI dog." She could detect microscopic traces of blood that other forensic methods missed, helping solve cases that had gone cold for decades. Her nose was so sensitive she found evidence that luminol and DNA testing couldn't.
Like her namesake, this gem finds the dead code that grep and your IDE missed.
Good girl, Keela. ðĶī
License
MIT License. See LICENSE.txt.