Skip to main content

ramify.ts

Multi-file hierarchical modules for TypeScript

TypeScript is modular at exactly one granularity: the file. Above it, every exported symbol is importable from every file in the codebase, and the architecture of an application - its domains, capabilities, boundaries - exists only by convention: nothing declares it, nothing checks it.

ramify.ts lets you declare groups of files as modules with a boundary and an interface, arrange them in a tree, and have the boundaries enforced. It is a toolkit to define, enforce, and visualize those modules, and to help humans and agents adhere to their rules.

Three main advantages:

  • Divide and conquer at every abstraction level. Splitting a complex system into smaller parts you can reason about separately is a foundation of software design, and TypeScript stops supporting it above the file. A module tree lets you make the same split at every level: a system into domains, a domain into capabilities, a capability into implementation pieces. Each level can only compose the one below it through public interfaces, with automated checks enforcing it. For humans and agents alike.
  • A manageable context for every agent task. An agent reasons worse as its context grows. In a module tree every task has a natural context: the module it works in, the interfaces it consumes, and the interface it owes its parent - nothing else. Splitting bigger tasks into smaller ones, involving just one or very few modules, ensures that context sizes stay manageable.
  • Human oversight at the level you care about. When agents write the code, reading every single file can become overwhelming. ramify's module dependency explorer shows your architecture at the module and interface level, so you can keep track of what agents are doing, and steer it, at whichever abstraction level you care about. Agents too can use its dependency analysis metrics to improve the architecture on their own.

Why hierarchical modularity makes the full case.

The ramify modularity model​

The modularity model is based on very few simple but powerful rules.

Every cross-module import is closed by default. A module shares a symbol through exactly two channels - to its parent, or to its descendants - and long reach only exists as a chain of such one-hop decisions. That is the whole model. The diagram below shows it at work: nine modules, four shared symbols, and every decision drawn as a dot.

The core model builds this picture up from scratch, one step at a time.

Traced symbols■ stopcomputeTotal - owner → parent → root → everywhereInvoiceModel - owner → parent → that subtreeoptimizeRoute - exposed to the parent, composed, stopped: parent onlyShipmentPlan - owner → descendants: never leaves the subtreecomputeTotalcomputeTotalcomputeTotalcomputeTotalInvoiceModelInvoiceModeloptimizeRouteappapp rootreceives▼computeTotalfrom globalLibraryglobalLibraryreceives▲computeTotalfrom moneyUtilsmoneyUtilsowns▲computeTotalinvoicingreceives▼InvoiceModelfrom invoicingLibrarycomputeTotalfrom appinvoicingLibraryowns▲InvoiceModelreceivescomputeTotalfrom appinvoiceComputationreceivescomputeTotalfrom appInvoiceModelfrom invoicinginvoicePDFreceivescomputeTotalfrom appInvoiceModelfrom invoicingshippingowns▼ShipmentPlanreceives·optimizeRoutefrom routingOptimizationcomputeTotalfrom approutingOptimizationowns▲optimizeRoutereceivescomputeTotalfrom appShipmentPlanfrom shippingNode▲exposed to parent▼exposed to descendantsThe three owners that exposed to their parent made the same decision; the three reaches were decided entirely above them.Exposing to descendants is final and bounded: one decision, whole subtree, no way out. Exposing to the parent cedes onward exposure.There is no sibling channel: exposing a symbol to the parent gives siblings nothing.Files inside one module import each other freely; those imports are not drawn.
drag to pan · scroll to zoom

The diagram is live: click a traced symbol to follow its chain.

Some needs require more than this: keeping test support out of production contracts, keeping UI and transport contracts out of the code that must not depend on them, and browser safety. ramify.ts handles them with tags and availability rules.

Module dependency explorer​

[TBD]

The module dependency explorer drilled into one domain module: its six sub-modules arranged in a circle with their dependency edges, and the selected module's path, description, and metrics in a details panel

Module dependency explorer - [TBD]

Formal definitions​

All vocabulary is defined in the Glossary.