<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Lucca Gabriel</title>
    <description>The latest articles on DEV Community by Lucca Gabriel (@worstone0).</description>
    <link>https://dev.to/worstone0</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1747196%2Fb7bc1c2a-2a64-4d05-958f-87afa796bf44.jpeg</url>
      <title>DEV Community: Lucca Gabriel</title>
      <link>https://dev.to/worstone0</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/worstone0"/>
    <language>en</language>
    <item>
      <title>How to find every caller of a route across 20 repos, and keep the answer true</title>
      <dc:creator>Lucca Gabriel</dc:creator>
      <pubDate>Fri, 16 Oct 2026 03:01:48 +0000</pubDate>
      <link>https://dev.to/worstone0/how-to-find-every-caller-of-a-route-across-20-repos-and-keep-the-answer-true-4amg</link>
      <guid>https://dev.to/worstone0/how-to-find-every-caller-of-a-route-across-20-repos-and-keep-the-answer-true-4amg</guid>
      <description>&lt;p&gt;At my day job we have a dozen repositories: two web front ends, two Flutter apps, a handful of Express APIs and some RabbitMQ workers. One question kept coming up in code review: &lt;strong&gt;who calls &lt;code&gt;PUT /devices/:id&lt;/code&gt;?&lt;/strong&gt; Nobody could answer it without grepping every repo, and the architecture doc was two years out of date.&lt;/p&gt;

&lt;p&gt;So I built a scanner that answers it from the code, and kept going until it drew the whole map. This post is about the parts that turned out to be hard, and how I check that the answers are right.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzlrl38ovucvbzl3dzpuz.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzlrl38ovucvbzl3dzpuz.jpg" alt="The OpenTelemetry demo mapped by Kuuhaku: 23 services and every call between them" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why grep is not enough
&lt;/h2&gt;

&lt;p&gt;Searching for &lt;code&gt;/devices/&lt;/code&gt; finds some callers and misses most of them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The host lives somewhere else.&lt;/strong&gt; &lt;code&gt;axios.create({ baseURL: process.env.API_URL })&lt;/code&gt; in one file, &lt;code&gt;api.put(`/devices/${id}`)&lt;/code&gt; in another, and &lt;code&gt;API_URL=http://devices-api:3000&lt;/code&gt; in a &lt;code&gt;.env&lt;/code&gt; or a &lt;code&gt;docker-compose.yml&lt;/code&gt;. The call only means something once you join all three.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clients are wrapped.&lt;/strong&gt; Real code calls an &lt;code&gt;http(method, url)&lt;/code&gt; helper, a generated gRPC stub or a &lt;code&gt;Gateway&lt;/code&gt; class, rarely &lt;code&gt;fetch&lt;/code&gt; directly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Routes are mounted.&lt;/strong&gt; The handler says &lt;code&gt;router.put("/:id")&lt;/code&gt;; the &lt;code&gt;/devices&lt;/code&gt; prefix is added two files away with &lt;code&gt;app.use("/devices", router)&lt;/code&gt;, and a proxy (nginx, Envoy, Spring Cloud Gateway) may add another hop in front.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Paths are templates on one side and patterns on the other.&lt;/strong&gt; The caller builds &lt;code&gt;`/devices/${device.id}`&lt;/code&gt;; the route says &lt;code&gt;/devices/:id&lt;/code&gt;, or &lt;code&gt;{id}&lt;/code&gt;, or &lt;code&gt;&amp;lt;int:id&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Matching calls to routes
&lt;/h2&gt;

&lt;p&gt;The scanner is static analysis (tree-sitter for JavaScript and TypeScript, pattern readers per framework for the other languages, no LLM). For each repo it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;finds the &lt;strong&gt;routes&lt;/strong&gt; it serves, with their full mounted path;&lt;/li&gt;
&lt;li&gt;finds the &lt;strong&gt;call sites&lt;/strong&gt;: HTTP clients, gRPC stubs, queue publishers and consumers, database connections;&lt;/li&gt;
&lt;li&gt;works out &lt;strong&gt;where each call goes&lt;/strong&gt;: the base URL from env files, compose files, constants and proxy config, then the path, normalised so &lt;code&gt;/devices/${id}&lt;/code&gt; meets &lt;code&gt;/devices/:id&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then every call is matched against the routes of the service its host points at.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evidence instead of percentages
&lt;/h2&gt;

&lt;p&gt;Every connection on the map says why it is there. Real examples from the OpenTelemetry demo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;frontend → shipping&lt;/code&gt;, HTTP, &lt;strong&gt;config&lt;/strong&gt;: &lt;code&gt;SHIPPING_ADDR = http://shipping:50050&lt;/code&gt; in &lt;code&gt;compose.yaml&lt;/code&gt;, read in the same file.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;checkout → cart&lt;/code&gt;, gRPC, &lt;strong&gt;config&lt;/strong&gt;: &lt;code&gt;CART_ADDR = cart:7070&lt;/code&gt; in &lt;code&gt;compose.yaml&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;checkout → accounting&lt;/code&gt;, queue, &lt;strong&gt;inferred&lt;/strong&gt;: topic &lt;code&gt;orders&lt;/code&gt;, published at &lt;code&gt;main.go:671&lt;/code&gt;, consumed at &lt;code&gt;Consumer.cs:46&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are four tiers: &lt;strong&gt;config&lt;/strong&gt; (the address is written down somewhere), &lt;strong&gt;inferred&lt;/strong&gt; (the path matches and nothing contradicts it), &lt;strong&gt;declared&lt;/strong&gt; (a person confirmed it) and &lt;strong&gt;observed&lt;/strong&gt; (seen at runtime, on the roadmap). No "87% confident". When the scanner can't settle something, like a base URL assigned at runtime, it goes to a review inbox instead of becoming a guessed arrow.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I know it's right
&lt;/h2&gt;

&lt;p&gt;I keep a benchmark of 40 public systems (the OpenTelemetry demo, Online Boutique, Train Ticket, eShop, Sock Shop, Spring PetClinic microservices and more) with hand-checked answer keys. Every scanner change runs the whole set, and a change that lowers the score doesn't ship. Today it's F1 97 on connections, precision 99 and recall 96.&lt;/p&gt;

&lt;p&gt;That number is after fixing what those systems taught. The first run on 14 systems it had never seen scored F1 79, and every fix had to be a general rule, never a special case for one repo. New systems get scored once before any fix they prompt.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the map is for
&lt;/h2&gt;

&lt;p&gt;Once you have the graph, a few things get easy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A pull-request check:&lt;/strong&gt; "this PR removes &lt;code&gt;PUT /devices/:id&lt;/code&gt;, which &lt;code&gt;web&lt;/code&gt; calls at &lt;code&gt;src/api.js:42&lt;/code&gt;".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An MCP server&lt;/strong&gt;, so Claude Code or Cursor can ask what depends on a route before editing it: &lt;code&gt;claude mcp add kuuhaku -- npx kuuhaku mcp&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exports&lt;/strong&gt; to Mermaid, C4 (Structurizr), Backstage and a PDF report.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;Nine public systems are mapped and open without an account. The &lt;a href="https://app.kuuhaku.dev/explore/otel_demo?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=launch" rel="noopener noreferrer"&gt;OpenTelemetry demo&lt;/a&gt; has 23 services in 10 languages and 30 connections, and against its answer key the scanner finds 27 of 28 and invents none. All nine are at &lt;a href="https://kuuhaku.dev/maps/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=launch" rel="noopener noreferrer"&gt;kuuhaku.dev/maps&lt;/a&gt;. To map your own repos, the CLI runs on your machine and only the graph leaves it.&lt;/p&gt;

&lt;p&gt;It's called Kuuhaku (空白, "blank": it fills in the blanks of your architecture), and it's free in early access until November 14: &lt;a href="https://kuuhaku.dev/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=launch" rel="noopener noreferrer"&gt;kuuhaku.dev&lt;/a&gt;. I'd love to hear where it gets your stack wrong.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>architecture</category>
      <category>microservices</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
