aura diff

Show a semantic diff: logic nodes added, removed, modified, renamed, or moved.

Synopsis

aura diff [<ref-a>] [<ref-b>] [--semantic] [--textual] [--stat] [--name-only] [--paths <pathspec>...] [--format=text|json]

Description

aura diff is the semantic counterpart to git diff. It compares two states of the codebase and reports the differences in terms of logic nodes — functions, methods, classes, types, constants — rather than lines of text. A refactor that moves a function to a new file shows up as a single moved entry instead of a big block of deleted lines in one file and added lines in another. A rename shows as one renamed entry with the old and new names, not as the disappearance and reappearance of two unrelated functions. Whitespace, import reordering, and comment-only changes are ignored.

With no arguments, aura diff compares the working tree against the last save. With one argument, it compares the working tree against that ref. With two, it compares them to each other. A ref can be a Git commit, a branch name, an Aura save id, a snapshot id, or the keywords HEAD, SAVE, or MOTHERSHIP (the last-known teammate state).

Flags

| Flag | Description | | --- | --- | | --semantic | Default. AST-level diff. | | --textual | Fall back to a text diff with AST-aware hunk boundaries. | | --stat | Summary counts per file and per node kind. | | --name-only | Print only the qualified names of changed nodes. | | --paths <pathspec>... | Restrict the diff to the given paths. | | --format=text | Default, colored. | | --format=json | One change per line, machine-readable. | | --include-bodies | Inline full before/after bodies for modified nodes. Default shows unified hunks. | | --include-whitespace | Do not ignore whitespace-only changes. |

Output format

Each change entry carries an operation, a node identity, and a location. Operations are:

| Op | Meaning | | --- | --- | | added | Node exists in B but not in A. | | removed | Node exists in A but not in B. | | modified | Node exists in both with different bodies. | | renamed | Same identity, different name. | | moved | Same identity, different file. | | signature-changed | Name preserved, but parameter list or return type changed. | | visibility-changed | pub/private/export changed. | | body-only | Body changed but signature did not — a subset of modified, shown separately when useful. |

A typical text output:

aura diff  HEAD..SAVE

src/payments/gateway.rs
  modified            fn PaymentGateway::charge
                        +14 -2 lines; intent-linked: "Add retry_on_network_error"
  signature-changed   fn PaymentGateway::refund
                        (amount: Money)  ->  (amount: Money, reason: RefundReason)
  added               fn PaymentGateway::capture

src/payments/errors.rs  ->  src/payments/error.rs (file renamed)
  moved               enum PaymentError
  added               variant PaymentError::NetworkTimeout

src/legacy/cvv.rs
  removed             fn check_cvv
                        last seen in save sv_0198ff; restored available via `aura rewind check_cvv`

Summary:  +3 nodes, -1 node, ~3 modified, 1 renamed-or-moved across 3 files

Comparison to git diff

  • Scope. git diff shows text hunks grouped by file. aura diff shows operations grouped by logic node.
  • Rename detection. Git's -M is heuristic on file content similarity and only applies to files. Aura's rename tracking is identity-based and applies to functions.
  • Noise. A reformat, a rename-only change, or an import reorder produces zero entries in aura diff. The same change in git diff can be thousands of lines.
  • Merge previews. aura diff branch-a branch-b shows exactly which logic nodes conflict if you merge, which git diff cannot answer directly.
  • Intent linkage. Each modified node links to the save whose intent produced it; git diff has no equivalent.

aura diff --textual falls back to a text-line diff when you need to see exact formatting changes. The textual mode still uses AST-aware hunk boundaries, so a hunk never spans two unrelated functions.

Examples

1. What did I change since my last save?

aura diff

The default — working tree vs last save. The most common invocation.

2. What will my next save record?

aura diff --stat
aura diff HEAD..working

src/api/auth.rs         2 modified, 1 added
src/session/store.rs    1 modified
src/models/user.rs      1 renamed  (User -> Account)

Totals: +1 added  ~3 modified  1 renamed  0 removed

Compact preview before running aura save.

3. Compare two branches

aura diff main feat/account-rename --name-only
renamed   fn User::new           -> fn Account::new
renamed   fn User::validate      -> fn Account::validate
modified  fn SessionStore::get_user
modified  fn api::users::handler
added     fn api::accounts::handler

Exactly the node-level changes. No file-level noise.

4. Compare against teammates' work

aura diff MOTHERSHIP

Shows what has arrived from teammates via the Mothership but not yet been pulled into your working tree. Pair with aura pull.

5. Machine-readable diff for a bot

aura diff HEAD~5..HEAD --format=json
{"op":"modified","node":"fn PaymentGateway::charge","file":"src/payments/gateway.rs","save":"sv_019abc","intent":"Add retry_on_network_error"}
{"op":"added","node":"fn PaymentGateway::capture","file":"src/payments/gateway.rs","save":"sv_019abc"}
{"op":"removed","node":"fn check_cvv","file":"src/legacy/cvv.rs","last_save":"sv_0198ff"}
{"op":"renamed","from":"fn User::new","to":"fn Account::new","file":"src/models/user.rs","save":"sv_019a10"}

One change per line. Feed it into a code-review bot or a changelog generator.

Exit Codes

| Code | Meaning | | --- | --- | | 0 | Diff produced (may be empty). | | 1 | Generic failure. | | 2 | A ref could not be resolved. | | 3 | Both refs refer to the same state (nothing to diff) and --strict was set. |

Limits and gotchas

A semantic diff is only as good as the AST parser for the language. Languages with mature tree-sitter grammars (Rust, Python, TypeScript, Go, Java, C++, Ruby) produce excellent diffs. Languages without — or configuration files, templating languages embedded in strings, code generation output — fall back to textual diff with a warning. aura doctor --check=IDX003 shows which files are affected.

Macro-heavy code can appear to change semantically when only a macro's expansion differs; Aura parses the pre-expansion source, so macro internals that change are invisible to the diff. Usually this is what you want; occasionally it hides a real behavioral change. When in doubt, cross-check with --textual.

Reading a semantic diff in code review

In practice, aura diff changes what a code reviewer looks at. Instead of scrolling through hundreds of lines of re-indented imports to find the six lines that matter, the reviewer sees six logic-node entries. For each, they can expand the body with --include-bodies or drop into aura trace to understand the history of that function.

Reviewers also get the intent line attached to every modified node. A save is a named unit of reasoning; the node-level diff re-exposes that reasoning inline. This is particularly valuable for PRs produced by AI agents, where the difference between I changed this because X and I changed this and the commit message does not explain why is the difference between an approvable PR and one that requires another round.

Diffing with the Mothership view

aura diff MOTHERSHIP compares your working tree to the union of all pending teammate deltas. This is a preview of what the codebase is about to look like once you pull. Running it before aura pull tells you whether a teammate's pending change will affect anything you are currently touching.

Similarly, aura diff HEAD MOTHERSHIP shows only the teammate-originated changes — useful in standup or asynchronous review: "here is what the team did since my last commit."

Diffing across rewrites

When git rebase has rewritten commits, line-level diffs across the rewrite are unreliable. aura diff is stable across rewrites because identities are preserved: a rebase does not mint new logic-node identities, it only re-parents their saves. aura diff old-sha new-sha where new-sha is the rebased counterpart of old-sha shows the actual semantic change introduced by the rebase (often nothing, which is reassuring).

How the diff is computed

Computing an AST diff naively is quadratic in the number of nodes, which is too slow on large repositories. Aura uses a two-phase strategy.

The first phase matches by identity: for each node in A, look up the same identity in B's index. Matches pair off and their bodies are hash-compared. Unmatched nodes in A are candidates for deletion or rename; unmatched in B are candidates for addition or rename.

The second phase resolves rename candidates via a Hungarian-style assignment: for each unmatched A-node and each unmatched B-node, score the similarity of their bodies and signatures. Pairs above a threshold (default 0.75) are reported as renamed or moved; the rest as removed/added. Ties are broken by proximity (same file, same module).

This is fast in practice because the identity lookup handles the 99% case — most nodes do not change identity between saves — and the expensive second phase only runs on the small residual set.

Ignored changes

By design, the following are not reported:

  • Whitespace and indentation.
  • Import ordering and grouping.
  • Trailing commas where optional.
  • Doc-comment edits, unless --include-docs is set.
  • Changes in file encoding or line endings.

These categories can be individually re-enabled in aura.toml under [diff.include] when you need to audit them (for example in a code-style sweep).

See Also

  • aura trace — history of a single node
  • aura save — what aura diff with no args is previewing
  • aura mergeaura diff branch is a preview of what the merge will consider
  • aura status — summary of changes without the full diff