JSON
Repository and directory comparisons return one object withperFile and
crossFileMoves arrays:
perFile entry is a SemanticChange: a description, file path, and typed
action. Its context lists enclosing nodes, nearest ancestor first. The action
types are Insert, Delete, Update, and Move. Cross-file summaries identify
the declaration or file, its source and destination, and whether it changed during
the move. JSON retains the original per-file actions even when a human report
combines them into a move.
Standalone file comparisons return a SemanticChange array rather than an
envelope. They use the old input path as filePath. Repository and directory
reports use the new path, or the old path for a deleted file. Those reports also
serialize update/move nodes and insertion parents with shallow metadata; additions
and deletions keep their subtrees. Standalone file actions retain full nodes.
An unchanged standalone comparison returns []. An unchanged repository or
directory comparison returns {"perFile":[],"crossFileMoves":[]}.
A complete standalone example
Save{"retries":3} as before.json and {"retries":5} as after.json, then run:
context
places retries inside the root object. Directory/repository mode wraps such
changes in perFile and can add cross-file summaries.
Fallback diagnostics
If semantic analysis falls back to lines, the CLI writes a note to stderr. Repository/directory JSON also includes afallbacks array of records with
filePath and fallback. NDJSON includes fallback on the affected file record.
Standalone JSON keeps its array shape, so its fallback note is on stderr only.
See troubleshooting for common causes.
LLM
--format=llm groups edits under named declarations and includes source lines
where the parser provides them. Unnamed edits, such as comments, appear as a
tally. The quickstart produces:
changes count records the underlying actions before presentation
grouping, with each cross-file move counted once. named counts the presented
major entries and cross-file moves. These counts can differ from each other and
from the number of body lines. Both endpoints of a cross-file move contribute to
the file count.
Updates and insertions use new-source locations; deletions use old-source
locations. Grouped edits refer to their enclosing declaration. A structural body
replacement still names its function or method, and Go methods include their
receiver, for example
basicWriter.Write.
You can pass the report to a model through a separate tool:
llm command in this example is an external tool. Differens itself runs the
matching and narration locally without a model.
For the modified function move in the
cross-file walkthrough,
differens old/ new/ --format=llm prints:
Markdown
--format=markdown uses file headings and bullets, with related edits grouped
under their declaration. The quickstart produces:
_no logical changes_.
Empty repository or range comparisons use a context message, such as
nothing changed or nothing changed in range: HEAD..HEAD.
NDJSON
--format=ndjson writes one JSON object per changed file, in input order, as each
bounded worker window completes. Each object has a filePath and a changes
array containing descriptions and actions. The path appears once on the file
record. Unchanged files produce no record, so an unchanged comparison produces
no stdout bytes.
To inspect file paths without loading one large JSON document:
differens before.json after.json --format=ndjson
prints one complete record:
select(.filePath) and the trailer with
select(.crossFileMoves). File actions have the same shape as JSON actions;
matching a cross-file move does not remove them from the stream.
The candidate cache holds 8 MiB before spilling to indexed temporary files.
Individual large files, temporary token sets, and the final move list can exceed
that cache budget. Streaming output does not guarantee constant memory use.
Source coordinates
RustNode::byte_range uses UTF-8 byte offsets. Serialized code-node byteRange
fields keep UTF-16 code-unit offsets for compatibility with earlier releases,
despite the field name. Tree-level source mapping converts code spans during
serialization, including emoji and combining text. Data adapters use synthetic
tree ranges, so those ranges cannot locate a scalar in its source file.
Lines are one-based when present. Hashes serialize as integers within the 53-bit
range. JSON has no separate schema-version field; the differens/1 prefix applies
to the LLM text format.