Skip to content

Read a function-level diff

A text diff tells you which lines moved. A semantic diff tells you what changed — which exported symbols were added, removed, or re-shaped, and whether a public interface broke. This guide shows you how to read one.

Built and default-on — optional and additive

tovio semantic diff is part of the optional, additive semantic layer, which is built and on by default — everything below works today. The layer never blocks a core operation, so a plain text tovio diff always works whether or not the index exists.

When to reach for it

Use a semantic diff when "12 lines changed" isn't the question you actually have. You want to know:

  • Did I break anyone? — whether an exported signature or supertype changed.
  • What's the blast radius of this refactor? — symbols touched, not lines touched.
  • Is this safe to land? — whether a public interface changed in a breaking way.

For a line-by-line view, keep using plain tovio diff. The semantic diff sits beside it, not instead of it.

Diff a change semantically

tovio semantic diff [A] [B] compares two commits' symbol graphs. Give it commit addresses (blake3:<hex> or bare hex); with no arguments it compares HEAD's first parent with HEAD — "what did my latest commit change":

$ tovio semantic diff
semantic diff blake3:3f9c1a2b7d… → blake3:8d41e07c93…
  Removed (breaking):
    - legacyVerify().
  Changed (breaking):
    ~ verifyToken(). (signature)
  Added:
    + assertNotExpired().
  → BREAKING interface change(s) detected (advisory).

Read it top to bottom:

  • ~ changed, + added, - removed — the same glyph grammar as the rest of TOVIO, applied to symbols instead of lines. Symbols are named by their descriptor moniker (verifyToken(). is a function; Claims# is a type).
  • (breaking) marks a change to an exported symbol — a removed export, or one whose signature or supertype changed (signature, supertype, or signature + supertype). File-local symbols never appear here.
  • Advisory means exactly that: the diff reports, it never blocks. The last line is the verdict — breaking changes detected, no interface changes, or additions only.

Two more sections appear when the graph carries the edges: Dependencies changed (a symbol now depends on, or no longer depends on, another) and Test coverage changed (a symbol gained or lost a covering test). Both are advisory context, not part of the breaking verdict.

Turn the verdict into a list

A breaking change tells you what moved. To see who is affected, query the symbol graph: tovio semantic impact with no arguments seeds itself from this change's removed and changed symbols and walks the recorded dependents, and tovio semantic callers '<moniker>' lists the direct callers of one symbol.

Get machine-readable output

Every semantic command honors the same --json contract as the rest of the CLI — structured, stable, and free of any human decoration. This is the surface agents and CI read.

$ tovio semantic diff --json
{
  "a": "blake3:3f9c1a2b7d4e6f0182a35c9b7e04d1f6a83c25be9701df4368ac52e1b0947dca",
  "b": "blake3:8d41e07c93b5a2f7160e4dc8395b7f02ae61c4d90837fb52e6a1c0d47983be25",
  "a_indexed": true,
  "b_indexed": true,
  "breaking": true,
  "added": ["assertNotExpired()."],
  "removed": ["legacyVerify()."],
  "changed": [
    { "moniker": "verifyToken().", "reason": "signature", "removed_supertypes": [] }
  ],
  "dependency_deltas": [],
  "coverage_deltas": []
}

a_indexed and b_indexed tell you whether each side actually carried a symbol graph; a side without one is treated as empty, so its symbols read as absent rather than unchanged.

The machine contract carries no warmth

--json and --quiet output contain no glyphs, color, or celebration — that's a hard rule across TOVIO, so an agent or pipeline parses exactly the same bytes every time.

When a file has no indexer

The index covers Rust, TypeScript/JavaScript, Python, and Go. A file in any other language is simply absent from the symbol graph: the semantic diff doesn't list it, nothing errors, and tovio diff still gives you the text view of that file. The same fallback applies to a file the indexer can't parse or that is too large to index — a single unindexable file never costs the rest of the graph, let alone the commit.

You always get a diff. You just don't get the symbol-level view for that file.

Make it a gate

The diff is advisory on its own. A protected lane can opt in to enforcing it: set semantic_check = required on the lane's protection entry in the policy manifest, and a land that would introduce a semantic conflict or a breaking interface change is refused with TVO-SEM-004 (or TVO-SEM-005 when the build carries no semantic layer at all — fail-closed, never waved through). tovio explain semantic [A] [B] --into <lane> shows the diff and evaluates that gate for a target lane without landing anything.

How it stays fast

The symbol graph updates incrementally as you commit — TOVIO re-indexes only the changed files, never the whole tree. Each file's facts live in a content-addressed symbol-shard; the commit's symbol-xref stitches them together and is what the diff compares. Because those are ordinary objects in your store, they sync and verify like any other object, and because the layer is additive, a slow or missing index can only ever cost you the enhanced view, never the commit itself.

Where to go next

Last reviewed September 9, 2026

Suggest an improvement to this page Not for security reports — see disclosure