<?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: Mahmoud Nabil</title>
    <description>The latest articles on DEV Community by Mahmoud Nabil (@emahmoudnabil).</description>
    <link>https://dev.to/emahmoudnabil</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%2F4049816%2Fc004ee48-481f-449f-904e-f281b2154521.jpg</url>
      <title>DEV Community: Mahmoud Nabil</title>
      <link>https://dev.to/emahmoudnabil</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/emahmoudnabil"/>
    <language>en</language>
    <item>
      <title>Can you link a React frontend to a .NET backend with compiler accuracy? I ran the experiment</title>
      <dc:creator>Mahmoud Nabil</dc:creator>
      <pubDate>Fri, 21 Aug 2026 19:45:29 +0000</pubDate>
      <link>https://dev.to/emahmoudnabil/can-you-link-a-react-frontend-to-a-net-backend-with-compiler-accuracy-i-ran-the-experiment-3m68</link>
      <guid>https://dev.to/emahmoudnabil/can-you-link-a-react-frontend-to-a-net-backend-with-compiler-accuracy-i-ran-the-experiment-3m68</guid>
      <description>&lt;p&gt;I maintain slnmap, a tool that gives AI coding agents a compiler-accurate graph of .NET codebases. It's built on Roslyn, so if the compiler sees a reference, the graph has the edge. But it stops at the C# boundary. The question I kept getting was: can it cross into the frontend? Can "what breaks if I change this handler" end with "these three React screens"?&lt;/p&gt;

&lt;p&gt;I used to think the honest answer was no. I actually had evidence — an earlier prototype of mine tried exactly this with heuristic matching. Name-equality joins, filename-as-truth, take-first guesses written into the graph as real edges. Its own docs ended up flagging the results as unreliable. A graph that guesses is worse than no graph, because someone eventually trusts it.&lt;/p&gt;

&lt;p&gt;So before building anything this time, I gave myself one day and one question: is cross-stack linking deterministic, or is it doomed to heuristics no matter what?&lt;/p&gt;

&lt;p&gt;The setup was a ~200-line throwaway script using the TypeScript Compiler API — ts.createProgram plus the type checker, the real one, not a regex parser. I pointed it at a production React/Next.js frontend, 2,570 files, and matched what it found against the route data my Roslyn side already extracts.&lt;/p&gt;

&lt;p&gt;Three things came out of it.&lt;/p&gt;

&lt;p&gt;The type checker solves the exact thing that killed the old prototype. The old attempt gave up on 23% of call sites as "variable URLs" — stuff like apiClient.get(API_ROUTES.VENDORS) where the URL lives in a constant behind a barrel file, three imports away. The checker's literal types fold those constants. Not "probably this string" — this string, resolved the same way the compiler resolves it. Most of that 23% bucket just evaporated.&lt;/p&gt;

&lt;p&gt;The numbers held up. 675 frontend HTTP call sites, 96.6% resolved to a deterministic route template. The remaining 3.4% (runtime-computed segments, env-dependent bases) are detectable and countable, so the tool can say "these 23 sites I can't resolve, here's why" instead of guessing. That's already the design rule for the whole tool: anything not statically resolvable gets counted and disclosed, never guessed.&lt;/p&gt;

&lt;p&gt;And my favorite part — the experiment caught a real bug before the feature even exists. While matching frontend calls against backend routes, the script found a live dialog POSTing to /organizationusers. That endpoint doesn't exist on the backend. A production screen quietly sending requests into the void. grep would never surface this, because the two halves of the bug live in different languages.&lt;/p&gt;

&lt;p&gt;Looking back, the old prototype wasn't wrong to try. It was wrong to disable the type checker (for speed) and then paper over the gap with heuristics. That lesson generalizes, I think: in static analysis, the moment you start guessing to improve coverage, you've traded away the only thing that made the tool worth trusting.&lt;/p&gt;

&lt;p&gt;So it's buildable. The C# half already shipped — HTTP endpoints are graph nodes as of v0.7/v0.8, Minimal APIs and attribute-routed controllers, 658/658 registrations resolved on my reference codebase. The TypeScript extractor is what I'm building now. When the two halves join, impact_analysis on a C# handler will end its chain at the React components that break.&lt;/p&gt;

&lt;p&gt;slnmap is free and MIT: dotnet tool install --global slnmap — repo at &lt;a href="https://github.com/EMahmoudNabil/slnmap" rel="noopener noreferrer"&gt;github.com/EMahmoudNabil/slnmap&lt;/a&gt;. If you've tried cross-stack analysis and hit the heuristics wall, I'd like to hear how far you got.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>typescript</category>
      <category>staticanalysis</category>
      <category>ai</category>
    </item>
    <item>
      <title>I built an interactive HTML export for .NET dependency graphs, because my AI agent kept guessing wrong</title>
      <dc:creator>Mahmoud Nabil</dc:creator>
      <pubDate>Mon, 27 Jul 2026 16:10:29 +0000</pubDate>
      <link>https://dev.to/emahmoudnabil/i-built-an-interactive-html-export-for-net-dependency-graphs-because-my-ai-agent-kept-guessing-2nd1</link>
      <guid>https://dev.to/emahmoudnabil/i-built-an-interactive-html-export-for-net-dependency-graphs-because-my-ai-agent-kept-guessing-2nd1</guid>
      <description>&lt;p&gt;Every time I asked Claude Code or Copilot something like &lt;em&gt;"what breaks if I rename this method"&lt;/em&gt; on a mid-sized .NET solution, it would grep through files, miss call sites that go through interfaces, and burn a lot of tokens getting a partial answer. On a solution with a few dozen projects, that's slow and often just wrong: the agent doesn't see the whole picture, so it can't reason about it correctly.&lt;/p&gt;

&lt;p&gt;That's the problem &lt;a href="https://github.com/EMahmoudNabil/slnmap" rel="noopener noreferrer"&gt;Slnmap&lt;/a&gt; is built to solve. It uses Roslyn (the actual C# compiler) to build a semantic graph of your solution, stores it locally in SQLite, and exposes it over MCP so any compatible agent can query it directly instead of guessing from whatever files happen to be open.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I actually use the most: &lt;code&gt;slnmap viz&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Tool calls are great for agents. They're not great for &lt;em&gt;me&lt;/em&gt;, sitting there wondering what an unfamiliar codebase actually looks like before I start asking an agent to change it.&lt;/p&gt;

&lt;p&gt;So Slnmap also ships a &lt;code&gt;viz&lt;/code&gt; command that exports the whole graph as a single self-contained HTML file. No server, no account, no CDN calls. Open it in a browser, pan and zoom, click a symbol, and see everything connected to it collapse into view.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;slnmap viz &lt;span class="nt"&gt;--project&lt;/span&gt; MyProject.csproj
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reason this matters more than it sounds: it's the one artifact in this whole workflow that isn't just for the agent. You can drop the HTML file in a PR description, or send it to a teammate who doesn't have the repo checked out, and they get the same interactive view you do. Every other Roslyn-based MCP server I looked at while building this outputs data for an agent to consume. None of them give you something to look at.&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%2Fw649c0laozawp6uobss4.png" 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%2Fw649c0laozawp6uobss4.png" alt="slnmap viz interactive HTML graph export showing a .NET solution's dependency structure" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A concrete example
&lt;/h2&gt;

&lt;p&gt;Say you're about to touch &lt;code&gt;IBasketService&lt;/code&gt;. Instead of grepping five files and hoping you found every caller, you ask the agent (or run the query yourself):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What breaks if I change IBasketService?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Slnmap follows both the interface's callers &lt;strong&gt;and&lt;/strong&gt; its concrete implementations, across every project in the solution, in one query. On &lt;a href="https://github.com/dotnet-architecture/eShopOnWeb" rel="noopener noreferrer"&gt;eShopOnWeb&lt;/a&gt; (10 projects), querying an interface with 18 dependents comes back in about &lt;strong&gt;270ms end-to-end over MCP&lt;/strong&gt; (median of 3 runs — full methodology is in &lt;a href="https://github.com/EMahmoudNabil/slnmap/blob/main/BENCHMARKS.md" rel="noopener noreferrer"&gt;BENCHMARKS.md&lt;/a&gt; if you want to check the setup yourself).&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the tool surface is small on purpose
&lt;/h2&gt;

&lt;p&gt;If you go looking, there are a few other Roslyn-based MCP servers out there, some with a much larger surface of tools than Slnmap's. I went the other way on purpose. Every tool Slnmap exposes is read-only and narrow — find a symbol, trace its callers, check impact, list implementations — on the bet that an agent (or a person) gets more value from a small set of tools that answer a specific question well than from a large API surface that tries to do everything.&lt;/p&gt;

&lt;p&gt;I'll be honest: I don't know yet whether that bet holds up as codebases and usage scale. It's a real open question, not a marketing line.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it doesn't do
&lt;/h2&gt;

&lt;p&gt;It doesn't call out anywhere. Slnmap reads your source with Roslyn and writes one local SQLite file — nothing else. It's open source now, so that's not just a claim: you can read the code or watch the process yourself and confirm nothing leaves the machine.&lt;/p&gt;

&lt;p&gt;It also doesn't guess. Every answer comes from the compiler's own understanding of your code, not from string matching or heuristics on the source text — which is the same reason it catches call sites that go through an interface instead of missing them like grep does.&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet tool &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--global&lt;/span&gt; Slnmap
slnmap analyze path/to/YourSolution.sln
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then point any MCP-compatible client at it (Claude Code, Cursor, etc.) — setup instructions are in the &lt;a href="https://github.com/EMahmoudNabil/slnmap" rel="noopener noreferrer"&gt;README&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/EMahmoudNabil/slnmap" rel="noopener noreferrer"&gt;https://github.com/EMahmoudNabil/slnmap&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;NuGet: &lt;a href="https://www.nuget.org/packages/Slnmap" rel="noopener noreferrer"&gt;https://www.nuget.org/packages/Slnmap&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Site: &lt;a href="https://slnmap.dev" rel="noopener noreferrer"&gt;https://slnmap.dev&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's MIT licensed. If you've hit the "grep vs. semantic index" tradeoff yourself, or tried the narrow-tools-vs-many-tools design in a different direction, I'd genuinely like to hear how you approached it.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>ai</category>
      <category>mcp</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
