Learn/Semantic · 6 min · updated 2026-08-03

A Merkle graph of your code, and what it makes cheap

Hash every node of the syntax tree from its children up, and comparison, caching and change detection all become cheap. The same trick Git uses on files, applied inside them.

A Merkle graph of code is a syntax tree where every node carries a hash computed from its own content and the hashes of its children. Two subtrees with the same hash are identical, which turns comparison into pointer equality and makes change detection almost free.

Git already does this to files

Git hashes file contents into blobs and directories into trees, so an unchanged directory has an unchanged hash and can be skipped whole. A code Merkle graph applies the same idea one level down: inside the file, at the level of functions, classes and statements. Change one function and its hash changes, along with the hashes of its ancestors. Every sibling keeps its hash, and so does every other file.

What that buys

  • Diffing. Comparing two versions skips every subtree whose hash matches, so the cost is proportional to the change rather than to the repository.
  • Caching. Analysis keyed by node hash stays valid as long as the node does. A function that has not changed does not need re-checking.
  • Identity across moves. A function that moved to another file keeps its hash, which is what makes a move recognisable as a move.
  • Verification. A hash can be signed. That is the basis of an audit record that cannot be quietly edited later.

The design decisions

DecisionTrade-off
Include whitespace in the hashExact, but reformatting invalidates everything
Normalise formatting firstStable across formatters, and two genuinely different layouts hash the same
Include the node's nameRenames become changes; without it, renames stay identities
Include the file pathMoves become changes; without it, moves are free

Aura normalises formatting and excludes the path, which is why a move with no edit shows as a move and a reformat shows as nothing at all. Names are kept outside the body hash, so a rename is reported as a rename with an unchanged body.

The graph part

A tree becomes a graph once you add edges that are not parent-child: calls, imports, implementations. Those edges are what make impact analysis possible, since the question "what does this change reach" is a traversal. The hashes make the traversal incremental, because a subgraph whose roots are unchanged does not need revisiting.

See also: semantic diff compared with git diff and the parser underneath it.

Questions

What is a Merkle graph of code?

A syntax tree where every node carries a hash computed from its own content and the hashes of its children. Identical code produces identical hashes wherever it appears, so comparing two versions becomes comparing two root hashes.

Why hash an abstract syntax tree?

It makes change detection cheap. If two subtrees hash the same they are identical and can be skipped entirely, so a comparison only walks the parts that actually differ — and a function that moved keeps its hash, so it is recognised rather than reported as deleted and re-added.

Is this the same thing Git does?

The same idea one level down. Git content-addresses files and directories; this content-addresses the constructs inside a file. Git can tell you a file changed. Hashing the tree tells you which function changed, and that the rest of the file did not.