zen_apropos
Apropos-style search engine for rake tasks.
zen_apropos scans your .rake files, indexes task names, descriptions, source code, and structured annotations, then lets you search across all of them from the command line or Rails console.
Installation
Add to your Gemfile:
gem 'zen_apropos'Or install directly:
gem install zen_aproposThen run bundle install.
CLI Usage
Search
zen_apropos <query> # interactive rich output
zen_apropos <query> --plain # machine-readable, no colorsIn rich mode, results are numbered. You can type a number to view the full source of that task, enter a new query to search again, or q to quit.
Plain mode outputs pipe-separated columns (name | description | safety | team) suitable for piping to other tools.
Lint
zen_apropos lint # check all rake files
zen_apropos lint --changed-only # only files changed in the current branchThe linter checks that every desc/task pair has annotation tags or uses zen_desc. Exits with code 0 when all tasks are annotated, 1 when any are missing.
Output looks like:
⚠ lib/tasks/new_feature.rake:12 -- task "new_feature:run" has no @zen_desc annotations
2 task(s) missing annotations. Consider adding @zen_desc tags or using zen_desc.
Help
zen_apropos --helpQuery Syntax
Free text matches against task name, description, keywords, and source code (case-insensitive substring match). You can also use structured filters:
| Filter | Example | Matches |
|---|---|---|
team: |
team:search |
Tasks owned by the "search" team |
safety: |
safety:destructive |
Tasks marked as destructive |
namespace: |
namespace:indexer |
Tasks in the indexer namespace |
keyword: |
keyword:elasticsearch |
Tasks tagged with that keyword |
Filters can be combined with each other and with free text. Multiple filters use implicit AND:
zen_apropos "employee team:search"
zen_apropos "team:finance safety:destructive"
zen_apropos "keyword:elasticsearch"Annotation System
Every rake task should be annotated with team ownership, safety level, and keywords. There are two equivalent ways to do this.
Option A: Comment Tags
Place annotation lines immediately before the desc line. The default tag is @zen_desc:
namespace :indexer do
# @zen_desc team: search
# @zen_desc safety: safe
# @zen_desc keywords: elasticsearch, reindex
desc 'Reindex all employees'
task employees: :environment do
Employee.reindex
end
endOption B: zen_desc DSL
Replace desc with zen_desc and pass metadata as keyword arguments:
zen_desc 'Run database backup',
team: 'devops',
safety: :safe,
keywords: %w[backup database postgres]
task db_backup: :environment do
system('pg_dump ...')
endzen_desc calls desc under the hood, so rake -T keeps working.
Note: zen_desc is opt-in. You must explicitly require it:
require 'zen_apropos/zen_desc'Supported Fields
| Field | Type | Values |
|---|---|---|
team |
String | Any team name (e.g. search, hr-platform, devops) |
safety |
String/Symbol |
safe, caution, destructive
|
keywords |
Array/CSV | Comma-separated in comment tags, %w[] array in zen_desc
|
Custom Tag Prefix
By default, zen_apropos recognizes # @zen_desc comment tags. If you want a different prefix for your organization:
Project setup (recommended)
Run init to generate a binstub with your tag pre-configured:
bundle exec zen_apropos init --tag myappThis creates bin/zen_apropos in your project. From then on:
bin/zen_apropos "employee" # uses @myapp automatically
bin/zen_apropos lint # lints for @myapp tagsPer-command flag
Pass --tag to any command:
zen_apropos --tag myapp "employee"
zen_apropos lint --tag myapp --changed-onlyRuby configuration
In an initializer or setup file:
ZenApropos.configure do |config|
config.tag = 'myapp'
config.glob_patterns = ['lib/tasks/**/*.rake', 'packages/**/lib/tasks/**/*.rake']
endThen annotate with your custom tag
# @myapp team: payments
# @myapp safety: destructive
# @myapp keywords: refund, stripe
desc 'Process pending refunds'
task process_refunds: :environment do
# ...
endThe linter, CLI, and console helper all respect the configured tag.
Migrating from
@zen: If you previously used# @zentags, either rename them to# @zen_descor setconfig.tag = 'zen'to keep the old prefix.
Rails Console Integration
You can define an apropos helper for quick searching in the Rails console. Example initializer:
# config/initializers/zen_apropos.rb
return unless Rails.env.development?
require 'zen_apropos'
require 'zen_apropos/zen_desc'
ZenApropos.configure do |config|
config.tag = 'myapp' # recognizes # @myapp instead of # @zen_desc
end
RAKE_PATHS = ['lib/tasks/**/*.rake'].freeze
def apropos(query = nil, plain: false)
engine = ZenApropos::Engine.new(plain: plain, glob_patterns: RAKE_PATHS)
if query.nil? || query.strip.empty?
puts engine.help
return
end
puts engine.search(query)
endThen in the console:
apropos "employee"
apropos "team:finance safety:destructive"
apropos "reindex", plain: true
apropos # shows helpCustom Glob Patterns
By default, zen_apropos scans lib/tasks/**/*.rake. For monorepos or apps with tasks in non-standard locations, configure custom patterns:
ZenApropos.configure do |config|
config.glob_patterns = ['lib/tasks/**/*.rake', 'packages/**/lib/tasks/**/*.rake']
endThis works in binstubs, initializers, and anywhere you call ZenApropos.configure. The CLI and linter both respect it. You can also pass patterns directly to the engine:
engine = ZenApropos::Engine.new(
glob_patterns: ['lib/tasks/**/*.rake', 'packages/**/lib/tasks/**/*.rake']
)Running Tests
bundle exec rake testOr simply:
bundle exec rakeTests use Minitest and run against fixture rake files in test/fixtures/.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/ZenHR/zen_apropos. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
License
The gem is available as open source under the terms of the MIT License.