So a month ago I posted an experiment here: can you link a React frontend to a .NET backend with compiler accuracy? The answer back then was "probably" — a one-day throwaway script resolved 96.6% of frontend HTTP calls on a real codebase to backend route templates. This is the follow-up. The thing is built and shipped now, and it caught real production bugs on the way. It also caught three rounds of bugs in my own tool before launch, which is honestly the part I find more interesting.
Quick context if you missed the first post. I maintain slnmap, a Roslyn-based code graph for .NET. It exists because AI coding agents kept confidently lying to me about my own codebase — one told me a class had "zero usages, safe to delete" and that class was load-bearing middleware. The rule that came out of that pain: if the tool can't resolve something statically, it counts it and says so. It never guesses. Everything below follows from that one rule.
The goal was one graph an agent can walk end to end. The C# half already existed — HTTP endpoints as graph nodes, resolved through MapGroup prefixes, [controller] tokens, const patterns, overload resolution. What was missing was the frontend half: which React call site hits which endpoint.
Extractor first. slnmap-ts (on npm) walks a React/Next.js project with the real TypeScript Compiler API — the type checker, not regex. The checker folds literal types deterministically, so apiClient.get(API_ROUTES.VENDORS), where the URL is buried behind a barrel file three imports away, resolves to the exact string the same way the compiler would resolve it. On my reference codebase: 743 call sites, 91% resolved to exact route templates. The other 9% get counted with reasons — runtime-computed segments, dynamic imports, stuff that genuinely can't be known statically.
Then the linker. Frontend templates matched against backend endpoint templates, verb-exact, with precedence rules that mirror what ASP.NET actually does (a literal segment beats a parameter hole). When one call site can genuinely hit multiple endpoints at runtime, you get an honest "set edge" instead of a fake single answer. On the reference codebase, 716 of 743 call sites linked deterministically. The remaining 27 each get disclosed with a reason.
And here's the output the whole project exists for — impact_analysis on a C# command handler:
- Depth 1: HandledBy → POST api/TaskCenter/compliances/{taskId}/reminder
- Depth 2 (React callers of that endpoint):
POST src/.../taskCenterService.ts:33
POST src/.../useUserTaskCenter.ts:323 (shared hook — also targets 3 sibling endpoints)
Change the handler, see which React screens break. One graph, zero guessing. The shared generic hook shows up correctly as a multi-target caller instead of getting silently dropped or falsely pinned to one route.
The part I didn't expect: it finds bugs grep structurally can't. During the very first matching experiment, the harness flagged a live production dialog POSTing to /organizationusers. That endpoint doesn't exist — the backend only has a GET at that path. A real screen, quietly sending requests into the void, and nobody noticed because the two halves of the bug live in two different languages. That check is now a first-class tool, find_orphan_calls, and it splits results into "nothing exists at this path" vs "path exists but under a different verb" — the second usually means someone typed the wrong verb, which is the sharper signal. It found two more of these on my codebase that I didn't know about.
Now the part that kept me honest. Before announcing anything I ran the tool the way a stranger would — fresh machine, fresh install, real codebase. Three separate times. Every round caught a real bug.
Round one: false ambiguity in the route matcher. A call site's parameter hole was absorbing an endpoint's literal in one position, while the endpoint's hole absorbed the call site's literal in another — two unrelated routes "matching" through a criss-cross coincidence ASP.NET's runtime could never produce. impact_analysis was over-reporting blast radius on six call sites. Fixed before anyone saw it.
Round two: I pointed the tool at codebases nothing like mine — official Microsoft Blazor samples, a Turborepo monorepo, an Angular app. Found four cases where it silently dropped data while reporting clean success. Blazor markup composition hidden behind a "0 skipped" message. A call-site counter that could disagree with what actually got persisted. String-concatenated URLs vanishing without a trace. All four are exactly the defect class this project exists to kill — the confident lie — sitting inside my own tool. All four fixed or honestly disclosed within 48 hours, verified on both machines before this post.
I keep relearning the same lesson: the moment a static-analysis tool guesses to make its numbers look better, it's worthless, because someone will trust it. So now the tool tells you it can't see Blazor markup. It tells you a URL was built by concatenation and couldn't be resolved. Its reported counts provably equal what's in the graph. That's not modesty for marketing — a graph that guesses is worse than no graph.
What it doesn't do, so nobody's surprised: Blazor and Razor Pages are detected and disclosed but not modeled yet (issues are open). MediatR's sender.Send dispatch isn't traced — the chain stops at the handler. Angular/Vue detection is honest but mostly unresolved. Monorepo tsconfigs need per-package targeting.
Everything's free and MIT. Three commands:
- dotnet tool install --global slnmap
- slnmap analyze MySolution.sln
- slnmap analyze-ts ./frontend --db slnmap.db
- slnmap link --db slnmap.db
Then point any MCP client (Claude Code etc.) at slnmap serve — 15 tools including the cross-stack ones. Repo: github.com/EMahmoudNabil/slnmap
If you run it on your codebase, I'd genuinely like to hear what the orphan report finds. And if the linker refuses a routing shape you have, paste the --verbose output in an issue — that's immediately actionable.








