runtimeanalyz.ing

← All capabilities

relocate_file

CLI · MCP Pro

Which file should move where. Two files that call each other often and are directories apart. The report names the folder they belong in, weighted by recorded call traffic and the distance between them.

Invoice app/models/invoice.rb
def total = Billing::Proration
  .for(self).adjust(subtotal)
Proration.for ( self Invoice )
2,051 calls
Billing::Proration app/billing/proration.rb
Billing::Proration app/billing/proration.rb
plan
period
lines
2,051 calls
Invoice app/models/invoice.rb
move both into app/domains/billing · 3 directories apart → 0 · Proration has no other caller
Two files call each other thousands of times and call almost nothing else, and they are in different parts of the directory tree. By their calls they belong in one folder.

The problem

The common closure principle says that code that changes together should be kept together. A directory layout departs from it through decisions that each made sense when they were taken. In the example app Invoice went in app/models because it’s a model, and Proration went in app/billing because it’s billing. The two call each other on every total.

app/models/invoice.rb:35:

def total
  taxed = subtotal * (1 + rate_table.vat_for(region))
  Billing::Proration.for(self).adjust(taxed)
end

app/billing/billing/proration.rb:5:

def self.for(invoice)
  new(invoice.plan, invoice.period, invoice.lines)
end

A change to how proration reads an invoice means editing two directories and reviewing two areas of the tree, and it depends on whoever owns app/models noticing. The layout does not show that these two files work as one unit, and the source does not show how much they depend on each other compared with everything else.

The fix

ra relocate_file reports which file a given file belongs with:

$ ra relocate_file app/billing/billing/proration.rb
app/billing/billing/proration.rb
· talks to 5 files
  app/models/invoice.rb                     520  370
  app/models/money.rb                       740    0
  app/models/invoice_line.rb                147    0
  app/billing/billing/checkout.rb             0  126
  app/controllers/api/quotes_controller.rb    0   20
· app/billing/billing/proration.rb ⇄ app/models/invoice.rb
  890 calls between them, in both directions
  proration has 2 other callers, and invoice has 8 other callers
  3 directories apart: app/billing/billing ⇄ app/models
  → gather both into app/models   CCP

The two columns are the calls from proration.rb into each file and the calls back from it. money.rb gets more traffic, but all of it goes one way, because proration uses Money the way the rest of the app does. invoice.rb is the file that calls back, 370 calls to 520, so the tool pairs it with proration.rb. The suggested folder is the one that already has most of the pair’s combined traffic.

Move the file:

app/billing/billing/proration.rb  →  app/models/billing/proration.rb

With Zeitwerk the directory is the namespace, so Billing::Proration keeps its name. Its two other callers are in the report, and what_a_change_touches lists every call site to update in the same commit.

How it works

  1. The recording holds, for every call, the file its calling method was defined in and the file of the method it reached. Summed over every run, the calls form a weighted graph of files.
  2. For each pair it measures exclusivity: how much of each file’s traffic the other one accounts for, and how many other callers each side has. Two files that mostly call each other score high, and a utility that every file calls scores low.
  3. For distance, it counts the path segments that differ after the shared prefix. app/billing/billing and app/models share app and differ in three segments.
  4. It names the folder that puts the pair closest together while moving the least.

Run it on your app

The output on this page comes from our example Rails app, and it is regenerated whenever a tool changes. These are the commands behind it. On your own app, run ra record first, then put your own class, method or file in their place.

ra relocate_file app/billing/billing/proration.rb

The pair is app/models/invoice.rb:35 and app/billing/billing/proration.rb:5.

Reading the result

  • If you group by type (models, services, jobs) deliberately, it will keep suggesting that you group by domain, because common closure is the principle it measures. Read those suggestions as a description of the choice you have made.
  • It measures which files call each other. Which files change in the same commits is a different signal, available from git history, and you can check it alongside.
  • It does not move files. Moving a file renames constants and breaks references the tool cannot see, such as strings in config, so the move is yours or your agent’s to make.

Every tool reads the same recording. What gets recorded and how far to trust an answer are on the questions page.

break_cycle

CLI · MCP Pro

Cut a dependency cycle. The files or namespaces that form a dependency cycle at runtime. Each cycle is marked with its lowest-traffic edge, which is the cheapest one to cut.

See more →

split_package

CLI · MCP Pro

The parts of a namespace each consumer uses. Groups the classes in a namespace by which outside classes called them, and reports the groups that share no consumer as the lines to split the namespace along.

See more →

what_a_change_touches

CLI · MCP Pro

What a signature change affects. For each caller of a method, the argument classes it passed and the messages it sent to the return value, so you can see which callers a signature change affects before you make it.

See more →