Skip to content

Ruby/Rails autoloader-aware import resolution — graph is ~95% edge-starved on Rails projects #463

Description

@kimadactyl

Summary

On Rails (and any Zeitwerk-autoloaded Ruby project) the import resolver finds almost nothing, which collapses the entire downstream graph: cross-file imports/depends_on edges are missing, layer assignment falls back to path heuristics, and the LLM batch agents have no neighborMap data to work with. Run /understand on a non-trivial Rails monolith and you'll see roughly one edge per node — a fraction of the real connection density.

Repro

Run /understand on any Rails 6/7/8 app. I tested on a ~1,200-file Rails 8 codebase (PlaceCal):

Metric Value
Files analyzed 1,158
imports edges emitted 22
calls edges emitted 4
inherits edges emitted 68 (before LLM review pass)
Total non-contains/exports edges ~150

By comparison, the same project's actual ActiveRecord association graph alone has hundreds of cross-file relationships.

Root cause

packages/core/src/languages/configs/ruby.js defines the Ruby language config but provides no resolver for class-name → file-path mapping. The structural extractor reads tree-sitter output and looks for explicit require/require_relative statements, of which a Rails app has almost none — Zeitwerk autoloads everything.

So when a controller references a constant:

class PartnersController < ApplicationController
  def show
    @partner = Partner.find(params[:id])  # <-- this is a cross-file reference
  end
end

the scanner sees Partner as a free identifier and emits no edge. The downstream effect is that batchImportData is empty for ~99% of Ruby files, so file-analyzer subagents have no inter-batch signal and the architecture-analyzer falls back to path patterns.

Proposal

A Rails-aware (or more generally, Zeitwerk-aware) resolver bundled with the Ruby plugin. Two concrete pieces:

  1. Constant → file resolver. At scan time, build an index of ClassName::ChildClass → app/models/class_name/child_class.rb using Zeitwerk's standard inflection rules (app/, lib/, plus configurable load paths). When tree-sitter sees a constant reference, look it up in the index and emit an imports (or depends_on) edge. The lookup is fast and entirely deterministic.

  2. ActiveRecord DSL recognizer. belongs_to, has_many, has_one, has_and_belongs_to_many are first-class structural facts. A small tree-sitter query over each model file can emit them as depends_on (or a new associates edge type) to the singularized target. Similarly validates_* and scope are noise; delegate :foo, to: :bar is signal worth surfacing.

Stretch:

  1. config/routes.rb resolver. Parse the routes DSL and emit routes edges from URL patterns to controller actions. This is the single biggest grouping signal in any Rails app and is currently invisible.
  2. RSpec convention pairings. spec/models/partner_spec.rb tests app/models/partner.rb. Filename mirroring is a strong enough heuristic to emit tested_by edges without LLM intervention — the LLM review pass currently does this manually 60+ times per project.

Workarounds I've tried

  • /understand --review (the LLM graph-reviewer pass) recovered ~270 edges by reading whole files and pattern-matching, which is the right shape but the wrong layer — this should be a deterministic scanner job, not an LLM tax paid on every run.
  • Tightening .understandignore to pack each batch with more related files helps the in-batch LLM but does nothing for cross-batch edges.

Why this matters

Rails is one of the most-deployed server frameworks in the world and the second-most-common language in your languages/configs/ directory. The current output for Rails projects is misleading enough that the dashboard and tour underrepresent how the app actually fits together — and the gap isn't visible to users unless they happen to know what edges should be there.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions