runtimeanalyz.ing ● early access · sign-up open

← All capabilities

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.

Checkout
Gateway#charge ( amount Money )
Receipt.for ( charge Charge )
Billing Payments
Gateway · Receipt · Card
Refunds
Gateway#refund ( charge Charge )
Card#token
Reporting::Report
Proration.for ( invoice Invoice )
TaxTable#rate_for ( region Region )
Invoicing
Proration · TaxTable
Every call into Billing from outside it, grouped by consumer. Payments code and reporting code reach disjoint halves, and no consumer reaches both: two packages sharing one name.

The problem

A namespace is a promise that its contents belong together. The common reuse principle says the test of that is its consumers: if you depend on one class in a package, you should need the rest of it too. Otherwise you’re taking on changes, reviews and releases for code you never call.

Billing started as one idea and grew into two:

app/billing/
  gateway.rb     Billing::Gateway     charges and refunds
  receipt.rb     Billing::Receipt     one per charge
  card.rb        Billing::Card        tokenised cards
  proration.rb   Billing::Proration   partial periods
  tax_table.rb   Billing::TaxTable    rates by region

Checkout and Refunds use the first three. Reporting::Report uses the last two. Nobody uses both halves. But every change to tax rates shows up in the payments team’s review queue, a packwerk package boundary around Billing would force both sides through it, and extracting payments into a gem means taking proration along for the ride. The split is real, and it’s only visible from the callers’ side.

The fix

Ask how a namespace is actually consumed:

$ ra split_package Billing

Billing · 2 consumer groups · 0 consumers reach both
  Checkout, Refunds       → Gateway, Receipt, Card
  Reporting::Report       → Proration, TaxTable
  split it in two along these lines                              CRP

Split it where its consumers already have:

app/billing/gateway.rb    →  app/billing/payments/gateway.rb     Billing::Payments::Gateway
app/billing/receipt.rb    →  app/billing/payments/receipt.rb     Billing::Payments::Receipt
app/billing/card.rb       →  app/billing/payments/card.rb        Billing::Payments::Card
app/billing/proration.rb  →  app/billing/invoicing/proration.rb  Billing::Invoicing::Proration
app/billing/tax_table.rb  →  app/billing/invoicing/tax_table.rb  Billing::Invoicing::TaxTable

Now a consumer depends on exactly the half it uses, and a change to tax rates is invisible to checkout. With packwerk, each half becomes its own package with its own public API.

How it works

  1. Consumers, not callers. From every recorded call edge, it keeps the ones that cross into the namespace from outside it. Calls within Billing are its own business and don’t count.
  2. Who uses what. Each outside namespace gets the set of classes it reached inside. That’s a map from consumers to the parts of the package they depend on.
  3. Find the seams. Consumers whose sets never overlap fall into separate groups, and each group’s classes are a candidate package. It’s split_interface one level up: roles for a namespace instead of a class.

Limits

  • Shared cores show as overlap. If both halves use Billing::Money, the groups aren’t disjoint. The report names the shared classes as a common core rather than forcing them into one side.
  • Names are yours. It finds the groups. What to call them, and where the files go, is a decision about your domain it can’t make.
  • Only recorded consumers count. A consumer that never ran isn’t in the grouping, so check it against the code before you split.

split_interface

CLI · LSP · MCP free

Split a fat interface. A class whose callers each use a different slice of it. It names the role interfaces to split it into, measured from who actually calls what.

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 →

relocate_file

CLI · MCP free

Which file should move where. Two files that lean on each other constantly but sit directories apart. The graph names the folder they belong in, weighed by real call traffic and the distance between them.

See more →