@ossl-dev/differens-tiers
The parsing pipeline. A content router picks a tier by file extension and magic bytes. The tier adapter parses the file into aNode tree for @ossl-dev/differens-core. If a tier fails to parse, or its diff falls back, the pipeline drops to the next lower tier. Raw line diff is the floor.
Install:
Tiers
Exports
classifyFile
Tier.Raw, not prose: lines are structure in docs. CSV, TSV, GraphQL, and Dockerfiles are line-structured the same way, so they route to Tier.Raw too. Environment files (.env, .env.local) route to Tier.Data: KEY=VALUE lines parse as the TOML subset, so a changed value reports with its key path.
diffWithTier
- Entirely new or removed files become a single
Insert/Deleteof akind: "file"node. That is one fact, not a tree diff. - Identical sources short-circuit with no changes.
- A tier result is kept only if it parses and produces a usable tree diff; the core’s
fallbackverdict propagates instead of being reported as “no changes”. - On any failure the pipeline falls back to raw lines, with
fallback: "lines".
LanguageExtractor
Each code language is aLanguageExtractor: a few dozen match arms that map tree-sitter CST node types to canonical concepts. It is not a parser.
getExtractors() reports them as ExtractorInfo { language, level: "L6" | "L5", extensions }, where L6 extractors have semantic mappings and L5 are generic. Sixteen languages register extractors: TypeScript/JavaScript, Python, Rust, Go, C, C++, Java, Ruby, PHP, Swift, Kotlin, C#, Scala, Lua, and shell. A grammar registered without an extractor reports as L5.
parseCode keeps a content-addressed cache (64-entry LRU, keyed by content hash and extension), so the same file content reappearing in one run parses once. The cache is per-run and per-process by design; worker children build their own.
BinaryDiffPlugin
T0 format plugins upgrade binary files from the byte-delta report to a real diff:registerBinaryPlugin. The first non-declining answer wins; a throwing plugin is skipped; the default byte-delta report always remains as the floor. Dynamic plugin loading from disk is deliberately not included: that belongs to an ecosystem layer, not the diff pipeline.