A typed code graph
A structural claim about code has to be computed.
Muundo parses a source tree with tree-sitter and returns what is actually there: entities, the calls between them, per-entity metrics, dependencies and reachability. No language server, no build, no network — it reads the files.
Apache‑2.0 · thirteen grammars over twelve languages · nothing leaves the machine
What it is
A model answers. A graph shows.
Ask a model whether a function is recursive and it will tell you something. Ask the graph and it points at the edge.
That difference is the whole reason this exists. A finding that is going to be attributed to a clause of a standard, and shown to somebody who audits for a living, cannot rest on a plausible sentence. It has to rest on something a second party can recompute from the same files and get the same answer.
So Muundo computes. It parses each file with tree-sitter — the same parser an editor uses for syntax highlighting, which means no build system, no dependency resolution and no running of your code — and turns the trees into a report you can read, diff and store.
Muundo is Swahili for structure.
Entities and the calls between them
Every function, method, class and module the tree declares, each with its file and line, and an edge for every call that resolves.
Metrics per entity
Cyclomatic complexity, fan-in and fan-out, nesting depth — computed from the tree rather than estimated from line counts.
Reachability
Which entities are reachable from the entry points, and which are not. Dead code is a graph question, and this answers it as one.
What was read
The hash of every file the analysis parsed. verify re-reads the tree and says whether it still matches.
Languages
Thirteen grammars over twelve languages.
Ada and C are not decoration. They are what avionics, rail and medical device code is written in, and they are usually the languages an analysis tool skips.
Thirteen grammars, twelve languages: typescript and tsx are separate tree-sitter grammars for one language, which is why the two counts differ. A build can be compiled with fewer; muundo info prints the ones yours accepts.
Every field the report has is filled by every extractor whose language has the construct. That rule is checked by a fixture that writes one function in each language and reads what comes back — because an unfilled field and an absent construct look identical from the outside, and both are empty.
Use it
A library, a command, and an HTTP server.
It is not on a package index yet, so you build it. A stable Rust toolchain is enough for the command; the Python module needs maturin.
From the shell
cargo build --release --workspace
./target/release/muundo analyze --root ./src > report.json
./target/release/muundo verify --report report.json --root ./src
analyze, metrics, fragility, hotspots, verify and info.
From Python
report = muundo.MuundoAnalyzer(
"./src", ["c", "cpp"]
).analyze()
for edge in report.call_edges:
print(edge.caller, "->", edge.callee)
The parse is expensive, so it runs once per analyser and is cached for that instance's lifetime. Build a new one for a fresh analysis.
Completeness
Read these two fields before quoting a count.
A graph that quietly drops what it failed on produces confident numbers about a subset, and nothing tells the reader which subset.
A report is complete only when skipped_files and partial_analysis are both empty, and they mean different things. The first lists whole files that were never read. The second lists stages that stopped early on files that were. Either one on its own makes every number in the report a number over a subset.
if not report.is_complete:
print("not read:", report.skipped_files)
print("stopped early:", report.partial_analysis)
Nothing is dropped silently and nothing is rounded up. That is the claim worth checking, and it is the one the rest of the report depends on.
Limits
What it will not do.
Each of these looks like a missing feature and is a refusal on purpose. Doing better would mean either executing your code or building a full language-semantics engine, and both are somebody else's job.
It does not run your code
Everything comes from the parse tree. A call reached only through reflection, a dynamic import or a string dispatch is not an edge, because statically it is not one.
Imports resolve statically
A path a build system rewrites, an alias defined in a bundler config, a wildcard re-export — the resolver says what it can prove from the files and stops there.
Overloads stay merged
Two methods with one name are one entity. Splitting them needs type resolution, and a consumer that has types can split them itself.
It labels nothing as safe
There is no "this function sanitises" flag. Whether a function makes a value safe depends on where it is going, which the graph does not know.
The reasoning behind each one, and the ones that came and were reverted, is written down in the design decisions.
Licence
Apache-2.0. There is nothing to buy.
No licence key, no tier, no telemetry, no account. Read it, run it, fork it, ship it inside something you sell.
Questions and defects go to the issue tracker, which is where the answers are written down and stay readable. Muundo is one of several tools built here; the others are named below, and some of them are sold.