Summary
Add first-class Ruby support to the belief-map builder, followed by Rails-aware dependency resolution. Parsing .rb files alone is not sufficient for Rails applications because application dependencies are normally expressed through constants resolved by Zeitwerk rather than explicit require calls.
The desired result is useful search, analyze, deps, rdeps, flow, boundaries, and invariants output for large Rails monoliths.
Motivation and validation project
A concrete validation target is the Merchantly/Kiiiosk Rails monolith. Its current approximate source distribution is:
- 2,918 Ruby files total
- 414 files under
app/models
- 296 under
app/controllers
- 188 under
app/services
- 108 under
app/jobs
- 230 under
app/modules
- 189 under
app/api
- 570 specs
- 900+ JavaScript files, which can remain out of scope for this issue
The application contains many implicit relationships:
- more than 1,000
include SomeConstant occurrences
- Active Record associations and callbacks
- service objects and concerns
- Active Job and mailer dispatch
- Grape APIs
- many payment and delivery integration modules
- distinct SaaS-billing and customer-payment subsystems that should not be conflated
For this kind of codebase, the main value is reliable blast-radius analysis before changing a shared model, concern, payment contract, job, or service.
Proposed implementation
Phase 1: Ruby syntax support
Add Ruby as a source language in scripts/build_belief_map.py:
- discover
.rb and .rake files
- add a maintained Ruby parser, preferably
tree-sitter-ruby to match the existing builder architecture
- implement
parse_ruby(...)
- add
ruby/rb to language mappings, node ID extension stripping, counters, cache records, S-expression output, documentation, and supported-language messages
- extract:
- classes and modules, including nested and qualified constant names
- instance and singleton methods (
def call, def self.call)
- superclass relationships
include, prepend, and extend
require and require_relative
- constants and explicit constant-qualified calls
- top-level constants where useful
- preserve line numbers and the existing
FileResult/Entity contracts
Example facts:
(node app/services/orders/checkout :lang rb ...)
(entity class app/services/orders/checkout Orders::Checkout ...)
(entity method app/services/orders/checkout Orders::Checkout#call ...)
(extends app/models/order_payment app/models/application_record)
(imports app/services/orders/checkout app/models/order)
Phase 2: Rails and Zeitwerk resolution
Build a project-local constant index and resolve Ruby constant references to source files.
Required behavior:
- recognize Rails autoload roots, including custom directories below
app/
- map
OrderPayment to app/models/order_payment.rb
- map namespaced constants such as
Billing::Charge to their expected files
- account for
config.autoload_paths, config.autoload_once_paths, and lib where practical
- account for configured inflections/acronyms
- support concerns and qualified constant declarations
- handle class/module reopening conservatively
- retain explicit
require resolution for non-autoloaded Ruby
- distinguish unresolved, ambiguous, and external constants instead of creating guessed edges
A static filesystem convention pass can provide the baseline. Optional Rails runtime introspection may improve accuracy, but building the default map should not require booting the application.
Phase 3: Rails semantic edges
Add conservative extraction for common Rails DSL relationships:
belongs_to, has_one, has_many, and has_and_belongs_to_many
include, prepend, extend, and ActiveSupport::Concern
- validations and callbacks that reference local methods
- Active Job dispatch via
perform_later
- mailer dispatch via
deliver_later
- controller inheritance and action/view relationships where resolvable
render targets where statically resolvable
- Grape API class/module composition
- RSpec
describe SomeConstant and explicit constant references, to improve impacted-test discovery
These should use explicit relation metadata, for example :association, :concern, :job, :mailer, :callback, or :spec, rather than treating every edge as an ordinary import.
Phase 4: optional Ruby LSP enrichment
Investigate Ruby LSP as an optional enrichment source for definitions, references, workspace symbols, and Rails-specific information.
This should not be assumed to be a drop-in replacement for the existing TypeScript/Pyright call-hierarchy integration. Ruby's dynamic dispatch and current Ruby LSP capabilities mean that a complete precise call graph is not generally available. The base graph should therefore remain useful without --lsp, and LSP-derived facts should be marked explicitly.
Phase 5: configurable project architecture
The existing generic layer heuristics will not map cleanly to every Rails monolith. Add an optional repository-owned configuration, for example .belief-map.yml, that can define:
- source/autoload roots
- path-to-layer mappings
- allowed/forbidden dependency directions
- ignored paths and generated files
- project-specific inflections
Example use cases for Merchantly include separating:
- Admin and Operator surfaces
- public/system/operator APIs
- domain models and services
- SaaS billing
- customer payment integrations
- external delivery/payment gateways
The configuration mechanism should remain generic; Merchantly-specific rules should stay in Merchantly rather than this skill.
Tests
Add fixtures and regression coverage for:
.rb/.rake language detection and node IDs
- nested modules and
Foo::Bar declarations
- instance and singleton methods
- inheritance
include/prepend/extend
require and require_relative
- Zeitwerk constant-to-file resolution
- custom inflections/acronyms
- concerns
- Active Record associations
- callbacks and validations
- jobs and mailers
- STI inheritance
- RSpec-to-production-code references
- ambiguous and unresolved constants
- multiple constants or reopened classes/modules
- incremental rebuilds after Ruby files are added, renamed, or removed
Acceptance criteria
Suggested quality evaluation
Before calling Rails support complete, evaluate it on several known changes in a real Rails monolith and record:
- percentage of Ruby files successfully parsed
- percentage of local constant references resolved to exactly one file
- false-positive dependency rate from a reviewed sample
- whether
rdeps includes the known blast radius for shared concerns, base payment classes, services, jobs, and models
- build time and
.belief_map.sexp size for approximately 3,000 Ruby files
Non-goals for the first iteration
- perfectly resolving arbitrary Ruby metaprogramming
- a sound and complete method-level call graph
- booting Rails by default during every map build
- support for JavaScript, ERB, Slim, or runtime-only dependencies in the Ruby MVP
Summary
Add first-class Ruby support to the belief-map builder, followed by Rails-aware dependency resolution. Parsing
.rbfiles alone is not sufficient for Rails applications because application dependencies are normally expressed through constants resolved by Zeitwerk rather than explicitrequirecalls.The desired result is useful
search,analyze,deps,rdeps,flow,boundaries, andinvariantsoutput for large Rails monoliths.Motivation and validation project
A concrete validation target is the Merchantly/Kiiiosk Rails monolith. Its current approximate source distribution is:
app/modelsapp/controllersapp/servicesapp/jobsapp/modulesapp/apiThe application contains many implicit relationships:
include SomeConstantoccurrencesFor this kind of codebase, the main value is reliable blast-radius analysis before changing a shared model, concern, payment contract, job, or service.
Proposed implementation
Phase 1: Ruby syntax support
Add Ruby as a source language in
scripts/build_belief_map.py:.rband.rakefilestree-sitter-rubyto match the existing builder architectureparse_ruby(...)ruby/rbto language mappings, node ID extension stripping, counters, cache records, S-expression output, documentation, and supported-language messagesdef call,def self.call)include,prepend, andextendrequireandrequire_relativeFileResult/EntitycontractsExample facts:
Phase 2: Rails and Zeitwerk resolution
Build a project-local constant index and resolve Ruby constant references to source files.
Required behavior:
app/OrderPaymenttoapp/models/order_payment.rbBilling::Chargeto their expected filesconfig.autoload_paths,config.autoload_once_paths, andlibwhere practicalrequireresolution for non-autoloaded RubyA static filesystem convention pass can provide the baseline. Optional Rails runtime introspection may improve accuracy, but building the default map should not require booting the application.
Phase 3: Rails semantic edges
Add conservative extraction for common Rails DSL relationships:
belongs_to,has_one,has_many, andhas_and_belongs_to_manyinclude,prepend,extend, andActiveSupport::Concernperform_laterdeliver_laterrendertargets where statically resolvabledescribe SomeConstantand explicit constant references, to improve impacted-test discoveryThese should use explicit relation metadata, for example
:association,:concern,:job,:mailer,:callback, or:spec, rather than treating every edge as an ordinary import.Phase 4: optional Ruby LSP enrichment
Investigate Ruby LSP as an optional enrichment source for definitions, references, workspace symbols, and Rails-specific information.
This should not be assumed to be a drop-in replacement for the existing TypeScript/Pyright call-hierarchy integration. Ruby's dynamic dispatch and current Ruby LSP capabilities mean that a complete precise call graph is not generally available. The base graph should therefore remain useful without
--lsp, and LSP-derived facts should be marked explicitly.Phase 5: configurable project architecture
The existing generic layer heuristics will not map cleanly to every Rails monolith. Add an optional repository-owned configuration, for example
.belief-map.yml, that can define:Example use cases for Merchantly include separating:
The configuration mechanism should remain generic; Merchantly-specific rules should stay in Merchantly rather than this skill.
Tests
Add fixtures and regression coverage for:
.rb/.rakelanguage detection and node IDsFoo::Bardeclarationsinclude/prepend/extendrequireandrequire_relativeAcceptance criteria
.rband.rakefiles appear in.belief_map.sexpwith:lang rbrequirerdepsidentifies consumers of shared Rails models, services, and concernsSKILL.md, dependency instructions, and output-notation documentation list Ruby support and its limitationsSuggested quality evaluation
Before calling Rails support complete, evaluate it on several known changes in a real Rails monolith and record:
rdepsincludes the known blast radius for shared concerns, base payment classes, services, jobs, and models.belief_map.sexpsize for approximately 3,000 Ruby filesNon-goals for the first iteration