Tier Pipeline
Differens parses files at six levels of abstraction, T0 through T5. Each tier is a parser that produces aNode tree. Each tier can fall back to the tier below it. The tier number is the abstraction ladder: T0 is bytes, T5 is code.
The tiers
T0: binary (hash-only)
Byte-level hash comparison. Used for images, fonts, archives, and anything else that fails the text sniff. Output is a singleUpdate when the hash changes, with the byte-size delta as context. T0 never attempts to parse.
T1: raw (LCS line diff)
The safety net: a line-level longest-common-subsequence diff over the raw text. It is the fallback for every tier above it. It is capped at 2,000 lines. Beyond that, T1 returns a whole-fileUpdate action instead of running an unbounded LCS.
T2: prose (word-level)
Word-level diff for plain text and logs. Paragraphs and sentences become tree nodes, so a moved paragraph is aMove instead of a delete-plus-insert. Punctuation and whitespace changes update leaf nodes rather than rewriting whole paragraphs.
T3: markup (lenient HTML/XML)
A lenient HTML/XML tokenizer that tolerates malformed documents: unclosed tags, misnested elements, unescaped entities. Element hierarchy becomes the node tree. Attribute order changes are reorders, not updates.T4: data (JSON / YAML / TOML)
Value trees for JSON, the YAML subset Differens supports, and the TOML subset. The diff is key-path based: nested objects become nested nodes, so a key moved between two objects reports as aMove with the key path in the label. Array reordering is a Reorder.
T5: code (tree-sitter)
A tree-sitter CST with per-language semantic extractors. The extractors map raw tree-sitter node types to canonical concepts such asFunction, Class, Method, and Import. The narration can then say “function” whether the source said fn, def, or func. Currently: TypeScript/JavaScript, Python, Rust, and Go.
Languages without an extractor still work at the generic level: structural diff with raw tree-sitter node type labels. The extractor only adds human-readable concept names.
Graceful degradation
The tiers form a chain. Each tier can hand off to the tier below it:- T5 parse fails (syntax error, unsupported construct) → fall back to a generic tree-sitter CST diff, still structural.
- Generic CST fails (no tree-sitter grammar, garbage input) → T1 line diff.
- T1 hits the 2,000-line cap → returns a whole-file
Updateaction.
Classification rules
The content router classifies by extension, then by content sniff:
Extension-based classification is the first cut; the content sniff is the backstop. A
.txt file that is actually minified JSON still gets a T2 word-level diff. That is acceptable, because the tier below can take over.
Tier summary
Adding a tier adapter: create the adapter in
packages/tiers/src/, produce a Node tree (the interface lives in packages/core/src/index.ts), register it in the diffWithTier() switch in packages/tiers/src/index.ts, and add extension rules to classifyFile(). The tier number should reflect its position on the abstraction ladder.