Skip to content

Add Ruby and Rails/ Zeitwerk support to the belief-map builder #2

Description

@dapi

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

  • .rb and .rake files appear in .belief_map.sexp with :lang rb
  • Ruby classes, modules, methods, inheritance, and mixins are queryable
  • Zeitwerk-resolvable local constants produce dependencies without requiring explicit require
  • rdeps identifies consumers of shared Rails models, services, and concerns
  • specs referencing a changed constant can be discovered as reverse dependencies
  • unresolved or ambiguous dynamic calls do not produce silently guessed edges
  • Rails DSL edges have explicit relation metadata
  • Ruby support has unit and integration fixtures
  • README, SKILL.md, dependency instructions, and output-notation documentation list Ruby support and its limitations
  • existing Python and TypeScript tests remain green

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions