I built a tool that draws architecture diagrams from a repository, where every edge cites the file, line and commit it came from. Then I ran it against thirteen repositories it had never seen, and every single one of them found something wrong with it.
There were thirteen. These are the ones worth writing down. The list says nothing about those codebases. It says something about testing: a tool that reads other people’s repositories has to be tested against other people’s repositories, and there is no substitute.
The rule the tool works by
Nothing is drawn that cannot be cited. Every edge in the output carries the file, the line and the commit that justifies it — click an arrow, see the import statement. If a reference cannot be resolved to something in the repository, it is not quietly dropped and it is not guessed at. It is reported as a gap.
That second half is what made these bugs findable. A tool that silently drops what it cannot resolve looks perfect and is useless. A tool that reports gaps by name and count tells you, loudly, every time it is confused.
Java: a library sharing your package prefix is not you
Guava declares com.google.common. Truth is a separate library, and it lives in com.google.common.truth.
My resolver matched on package prefixes, so Truth looked like Guava’s own code, and every reference to it became a gap against a package Guava does not contain. 834 false gaps — 28% of the repository.
The fix is to require the next path segment to look like a type before peeling, because com.google.common.truth.Truth peels to a package and com.google.common.collect.ImmutableList peels to a class, and those are different shapes.
Java: a file importing its own nested type
Java requires the import for a nested enum constant even inside the same file. Treating that as a dependency has you drawing an arrow from a file to itself. It accounted for all 137 remaining gaps on Spring Boot and all 34 on Guava.
Java: static imports point one segment too deep
import static com.google.gson.Foo.BAR names a member, not a package. Reading it as a package produced 106 facts asserting that gson depends on a published copy of gson. Nested types left another 84 with the same shape.
Never a dependency on a published copy of yourself. I would end up writing that sentence five times, once per language.
Go: the walker refused to look at directories named build
I skipped directories called build the way everyone skips node_modules. Moby has four Go packages named build. All 53 imports of them were reported as gaps against directories the walker had declined to open.
Rust: a crate does not necessarily live in src/
This was the big one. Deno’s manifest says [lib] path = "lib.rs". I had hardcoded src/.
3,896 gaps — 27% of every fact in the repository.
What makes this one worth writing down: the totals looked fine. Deno is a large repository and thousands of unresolved references still left a plausible-looking number of resolved ones. I found it by opening the diagram and seeing a shape that was wrong, not by reading a count.
** Rust: integration tests are their own crates**
Cargo compiles every direct child of tests/ as its own crate. So crate::util inside an integration test does not mean the library’s util — it means the one sitting beside the test.
Rust: use super::* in an inline test module
The commonest shape in Rust, and I resolved it to the crate root. That is a wrong answer rather than a gap, which is strictly worse: gaps get counted and reported, wrong answers just quietly inflate the fact count of every Rust repository that has tests.
Kotlin: fun interface
fun interface Dns { } is ordinary Kotlin, and fun was missing from my list of declaration modifiers. So okhttp3.Dns, okhttp3.Interceptor and LeakCanary’s EventListener were not in the type index at all.
Kotlin: a SCREAMING_SNAKE constant is not a type
USER_AGENT, TYPE_A, UTC — 31 more of OkHttp’s gaps.
Kotlin and Java are one namespace
A Kotlin file importing a Java type from the same repository is a real edge. An index built from one file extension cannot see the other, so 112 real edges in Spring Boot were reported as missing. The declaration index now spans both.
The four I would have missed
Here is the part I would rather not write.
I had verified the Rust adapter against Next.js and the Kotlin adapter against Spring Boot. Both repositories contain Rust and Kotlin. Both are overwhelmingly JavaScript and Java. Neither exercised a repository actually built in the language I was claiming to support.
Somebody asked me whether I had verified against repositories where those languages are the point. I had not. Deno, Tokio, OkHttp and LeakCanary went through next, and four more defects came out — including the 3,896-gap one, the largest in this list.
The lesson is not “test more”. It is that a test corpus where your target language is a rounding error will pass and prove nothing.
** What it does now**
Reads JavaScript, TypeScript, Python, Go, Java, Rust and Kotlin. Resolves against what a repository declares — the module path in go.mod, the package statements in .java files, the paths in Cargo.toml — rather than against directory conventions.
Across the thirteen repositories: 278,982 facts from 85,930 files. Four came out with zero unresolved references.
It does not read Ruby, C#, PHP or Swift. Point it at a Ruby repository today and it will draw nothing and tell you it read nothing — the honest answer, and not a useful one.
npx mirofy-cli map .
Enter fullscreen mode Exit fullscreen mode
MIT, zero runtime dependencies, output is one self-contained HTML file that opens from disk with no server.
https://github.com/Hasan-Laraib/Mirofy
If you run it on your own repository and the resolution is wrong, I want to know. That has been the most productive kind of message I get.