runtimeanalyz.ing ● early access · sign-up open

← All capabilities

coupling_map

CLI · MCP Pro

Coupling from real load. Module and namespace coupling drawn from real production calls. Which parts of your app actually talk, and how much.

Billing
debit
4.1M calls
Finance::Accounts
unlock_paid_books
3.9M calls · not on the wiki
Curriculum
charge
4.1M calls
Gateway
after_commit callback
1.2M calls · cycle
Enrollment
Reporting
monthly_totals
96 calls · month-end
Billing
Namespace edges weighted by pooled production calls. The heavy edge nobody drew and the cycle through a callback are where an extraction starts; the month-end edge can wait.

The problem

You want to pull billing out of the monolith. The diagram on the wiki says Billing talks to Accounts and nothing else. The code says otherwise, but only if you read all of it:

module Billing
  class Checkout
    def complete(order)
      Gateway.new.charge(order.total)
      Finance::Accounts.debit(order.account, order.total)
      Curriculum.for(order.student).unlock_paid_books
    end
  end
end

There’s a call into Curriculum in there, and a static dependency graph would find it. What it won’t tell you is how much it matters. Is Billing → Curriculum one call a month, or on every checkout? And a static graph misses the calls made through send, callbacks and ActiveSupport::Notifications, which is where the surprising coupling usually hides.

The fix

Draw the map from production traffic instead:

$ ra coupling_map --namespace Billing

pooled from 214 deploys · 91 days

Billing → Finance::Accounts     4.1M calls   every checkout
Billing → Curriculum            3.9M calls   every checkout   ⚠ not on the wiki
Billing → Gateway               4.1M calls   every checkout
Reporting → Billing               96 calls   month-end only
Billing ⇄ Enrollment            1.2M calls   cycle: via after_commit callback

Now the extraction plan starts from the real edges. Billing → Curriculum fires on every checkout, so it becomes an event rather than a direct call. Reporting → Billing is 96 calls a month and can move to a nightly export. And the cycle through an after_commit callback is the edge to cut first:

module Billing
  class Checkout
    def complete(order)
      Gateway.new.charge(order.total)
      Finance::Accounts.debit(order.account, order.total)
      Billing.events.publish(:paid, order:)
    end
  end
end

How it works

Every recorded call has a caller and a receiver, and each lives in a file and a namespace. The hosted graph rolls those calls up from methods to classes to namespaces, counting them as it goes, across every deploy you record.

The result is a weighted graph of your app’s modules, where each edge’s weight is real production traffic. It’s rendered as a map you can open in the browser, and served over MCP so your agent can plan a refactor from it. break_cycle and stabilize_dep run locally on your suite’s recording; coupling_map is the same idea, weighted by what your users actually do.

Limits

  • Traffic, not importance. A rarely called edge can still be critical: the month-end report runs 96 times, and the business cares about every one.
  • Only what you record. Services and workers you didn’t instrument don’t appear.
  • Namespaces are your namespaces. The map follows your module structure. If everything lives in app/models with no namespaces, roll it up by directory instead.

break_cycle

CLI · MCP free

Cut a dependency cycle. Files or namespaces caught in a runtime dependency cycle, flagged with the single lowest-traffic edge to sever to break the loop.

See more →

stabilize_dep

CLI · MCP free

Point dependencies at stability. A stable module, with many callers and few dependencies, reaching into a less stable one. Measured from real call traffic, so you can depend toward stability or put an abstraction between them.

See more →

split_package

CLI · MCP free

Split a package used in parts. Consumers that pull in a whole namespace but touch disjoint pieces of it. Split it so nobody takes a dependency on code they never call.

See more →