Skip to content

Getting Started

This takes you from a checkout to a computed code graph: build Muundo, analyse a tree from Python, then do the same from the shell. About five minutes.

Nothing here reaches the network, runs a build system, or executes the code it reads. Muundo parses files with tree-sitter and reports what is in them.

1. Build it

Muundo is not on a package index yet, so you build it. The Rust crates need a stable toolchain; the Python binding needs maturin.

cargo build --release --workspace   # muundo and muundo-server

For the Python module:

cd python
maturin build --release
pip install ../target/wheels/muundo-*.whl

If pyo3 says it cannot find a Python interpreter, point it at one explicitly — it otherwise resolves whatever a stale virtualenv on the path claims:

PYO3_PYTHON=$(which python3) maturin build --release

2. Analyse a tree from Python

import muundo

report = muundo.MuundoAnalyzer("./src", ["rust", "python"]).analyze()

print(len(report.entities), "entities")
for edge in report.call_edges[:5]:
    print(edge.caller, "->", edge.callee)

The parse is the expensive part, so it runs once per MuundoAnalyzer and is cached for that instance's lifetime. The options are fixed at construction: build a new analyzer for a fresh analysis, which makes cache invalidation a non-question.

Check is_complete before you trust a count. A graph that quietly drops the files it failed on produces confident numbers about a subset, and nothing tells the reader which subset.

Two fields say a report is partial, and checking one of them is not enough:

  • skipped_files — files that were never read. A parse failure, an undecodable byte, a path outside the root. Nothing about them is in the report: no entity, no call edge, no hash. Each entry carries a reason_code — a stable token such as file_too_large or containment_escape — beside its sentence, so a program branches on the code and a person reads the sentence.
  • partial_analysis — stages that stopped early on files that WERE read, because a configured limit was reached.

Neither implies the other. is_complete is both, so you do not have to remember.

if not report.is_complete:
    print("not read:", report.skipped_files)
    print("stopped early:", report.partial_analysis)

3. The same from the shell

The command is muundo, with one subcommand per thing it computes.

./target/release/muundo info

That prints the version and the language names this build accepts. Then:

./target/release/muundo analyze --root ./src --languages rust,python

Every subcommand answers a single JSON object with ok and either data or an error, so it pipes into jq without a mode flag.

4. Verify what an analysis read

An analysis records the hash of every file it parsed, and of the tsconfig.json / jsconfig.json files that decided how its imports resolved — plus, per source directory, where it looked for a nearer one and found nothing. verify re-reads all of them AND re-runs the analysis, then tells you whether the tree still matches:

./target/release/muundo analyze --root ./src > report.json
./target/release/muundo verify --report report.json --root ./src

verify does not only re-hash the files the report listed — it reproduces the report. It rebuilds the analysis from the report's own configuration and compares every source-derived field: entities, dependencies, metrics, scores, hotspots, snippets and the file set. So it catches what a hash cannot — a metric or snippet edited while the file's bytes were left alone, a source file added since (never hashed because it is in no manifest), or one removed while its claims remain. Any of those makes the verdict verified: false and names the fields that diverged. This is what lets a finding be attributed to a specific state of a tree rather than to "the code, at some point". Verify with the same engine build that produced the report — reproduction needs it.

Next steps

  • Reference — the languages, every field of a report, all six subcommands, the HTTP API and its limits
  • Design decisions — the deliberate "no"s: things that look like missing features and are refusals on purpose