github.com/Panevschi-Ruslan/flowatlas command reference in CI

flowatlas · a walkthrough in ten steps

One map of a project that lives in several repositories

A command-line tool that reads several TypeScript repositories with the compiler's own checker, without running them, and joins them into one graph you can query β€” from a terminal or from a coding agent. It knows NestJS and Angular, Telegraf and Hono, TypeORM, Prisma, Mongo, Redis, Kafka and RabbitMQ.

A request made in one service is matched to the route that answers it in another, a message published in one to whatever handles it, a button in a browser to the endpoint it calls. madge, dependency-cruiser and nx graph draw the imports inside one repository, which is what a compiler already sees. This joins the repositories. If your project is one repository, you do not need it.

npm install -g @flowatlas/cli
flowatlas init --dir .   # find the repositories under here
flowatlas build          # read them all and join them

measured on a real project

Five repositories that ship as one product: three NestJS services and two Angular frontends, with a shared package of types between them. Read in full, from cold, in about five seconds. It is the project this was built against β€” reproducible, and from one codebase whose author also wrote the tool.

251,056lines of TypeScript and templates, over 1,443 files
11,239nodes, joined by 19,710 edges
376of those edges cross a repository boundary
5.3sto read all five; 0.6s when nothing changed

The third figure is the one that matters. Three hundred and seventy six connections that no compiler in any of those five checkouts can see, because each one only ever reads its own. 19,325 of the edges were read from the code and 385 were inferred and marked heuristic; none needed an annotation. Every edge says which of the three it is.

ways in

HTTP routes615
bot commands, callbacks and events65
routes something in the project reaches353
routes nothing it can see calls262

joined β€” first build, then after two settings

browser requests matched to their route333 β†’ 333 / 356
calls between services matched0 β†’ 43 / 55
routes something reaches322 β†’ 353
message channels with a handler0 β†’ 2 / 12

found once they were joined

contract errors, over 351 boundaries107
names declared more than one way55
channels handled nowhere10
routes claimed by two handlers4

what it says it cannot see

findings to act on40
places static reading cannot reach750
rows they are folded into19
edges inferred, and marked so385

The first column is what a first build gives you. The two settings behind the second say which settings key addresses a service and where a frontend's key points: a string in one repository and a route in another are joined by a fact only the person who deployed them knows, and flowatlas doctor names both with the line that wants them.

Nothing in that last table is dropped or rounded away. Each row carries a file, a line, a reason and the one thing to change. flowatlas stats prints these figures for your own project, and flowatlas doctor prints the list behind them.

The rest of this page is a walkthrough, recorded against fixtures/multi-repo rather than the project above: four services that do not share a repository, small enough to read in a sitting, and already carrying the same kinds of problem. A scene is a shell script, so what is on screen is what actually ran.

01Point it at the repositories

Put yourself in a directory with the repositories under it, or beside them, and run init. It reads each manifest, works out the name and the kind, and writes flowatlas.config.json. It also registers the graph server in every repository, so an agent working in any one of them can ask about all of them.

npm install -g @flowatlas/cli

flowatlas init          # find the repositories and ask
flowatlas init --dir .  # look under this one, not beside it

flowatlas init with no --dir scans the parent directory, which is what you want when the configuration lives inside one of the repositories, and not what you want when it lives above them.

02Build

build reads every repository in its own process and joins the readings. A first build is rarely all green, and it is not supposed to be.

lsflowatlas init --dir . --yesflowatlas build
flowatlas init finding four repositories, then flowatlas build joining them

Four requests leave the gateway for an address flowatlas cannot attribute to any service, and only one of six browser requests found its route. Nothing was guessed; all of it was recorded, with a file and a line.

03Ask what it could not read

doctor turns those counts into a list of things to do. Every row names the reason, the file, the line, the call, and what to change.

flowatlas doctor --section desync --service gateway
flowatlas doctor naming four calls rooted at a settings key no service claims

All four rows here say the same thing: a request is rooted at a settings key, and no service in the configuration claims that key.

flowatlas doctor --section unresolved  # what could not be read
flowatlas doctor --section desync      # calls matching no route
flowatlas doctor --section markers     # annotations gone stale
flowatlas doctor --strict              # exit 1 on a problem

04Answer it in the configuration

Two settings close four of the eleven findings, and three of the five browser requests that had nowhere to go.

baseUrlEnv is what turns a request into an edge: a service declares the settings keys other services use to address it. Without it, a request is recorded and joined to nothing. apiTarget does the same for a browser, saying which service a frontend's settings key points at.

jq -c '.services[]' flowatlas.config.jsonflowatlas build
the services array before and after two settings, and the build that follows
first buildafter two settings
calls between services linked12
browser requests joined to a route13
routes something reaches24
rows left unread117

What survives is no longer missing configuration. It is the project disagreeing with itself, which is the thing worth knowing.

05Follow one request

flow takes any way in and follows it until it stops.

flowatlas flow 'ui_action:web#…' --format tree --depth 20flowatlas config 'ui_action:web#…'
a click in an Angular template followed through the gateway into the orders service

The first trace starts at a (click) in an Angular template and ends at a row in a table in a different repository, crossing two service boundaries on the way, with unresolved on this path: 0 to say nothing along it was guessed.

The second shows the other case. BillingClient builds its address at run time from a registry, so nothing can be read from the source; an annotation on the method declares where it goes, and the edge is recorded with confidence: marker rather than static, so it is always clear which edges were told rather than found.

An entry can be named however you would say it aloud. These three are the same route:

flowatlas flow "POST /orders/12345"
flowatlas flow "POST /orders/:param"
flowatlas flow "entry:orders:http:POST:/orders/:param"

06Ask the other direction

impact starts at a symbol and walks backwards to every way in that reaches it. Give it a table and it tells you which routes would have to be retested.

flowatlas impact 'table:orders#Order' --format treeflowatlas hotspots --type entry --max 5flowatlas channel order.created --format tree
impact walking backwards from a table, hotspots, and both ends of a channel

hotspots ranks what the most things point at, and channel shows both ends of a message channel β€” here, one with a handler and no publisher anywhere in the project.

07Compare what crosses a boundary

Two services agree about a shape until one of them changes. contracts compares what each side of every crossing declares, field by field.

flowatlas contracts --severity error --max-nodes 4flowatlas types --drift --format tree
contract errors between the gateway and the web app, and two types declared twice

The frontend declares an OrderDto with four required fields, and the gateway answers that route with none of them: it sends a wrapper whose only field is data, of an unreadable shape. Nobody wrote that down anywhere and no test covers it, because the two sides live in different repositories.

The unchecked list matters as much as the findings. It says which boundaries could not be compared and what to do about each, rather than reporting them as agreement.

08See the shape of the whole thing

flowatlas stats --format treeflowatlas dead --kind channelsflowatlas cycles
what the graph is made of, what nothing reaches, and no cycles

dead is the one to read carefully. It says so itself: nothing in it is proof, every row is a heuristic with the reason it fired attached.

visualise writes one self-contained page: no server, nothing to fetch, nothing to install. It opens on the reconciliation, lists every way in, and follows any one of them across service boundaries, marking each crossing with what it was joined by and how much to trust it.

flowatlas visualise --title "flowatlas demo"
the generated page, following a route from the gateway into the orders service

It is a report you can click, not a viewer you keep running. Attach it to a review, or keep it beside a decision.

09Ask what a branch changes

diff builds the graph at two revisions and reports what moved between them.

git -C orders diff -U0flowatlas diff HEAD
one renamed route, and the gateway entry and browser button that depended on it

One route is renamed in the orders service. flowatlas names the gateway route that called it and the browser button above that, in two repositories the change does not touch. The output is markdown because its destination is a pull request comment; --fail-on-contract-break makes it a build failure instead.

10Give it to an agent

init already registered the server in each repository. An agent in any one of them gets the whole project over the Model Context Protocol.

cat orders/.mcp.jsontools/listfind_symbolimpact
the registered server, its ten tools, and two of them answering

Ten tools, and only one of them returns source code. A trace never drags in the body of every method along it; the agent asks the graph which name to look at, then asks for that one name.

flowatlas mcp --install   # register everywhere, merging
flowatlas mcp --install --dry-run

After that, claude mcp list shows flowatlas. docs/mcp.md is the whole thing: setup for Claude Code, Cursor, VS Code and Claude Desktop, what each tool takes, the order to call them in, and what to check when a client will not list the server.