> ## Documentation Index
> Fetch the complete documentation index at: https://differens.ossl.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# @ossl-dev/differens-core

> Tree matching and edit script API reference

# @ossl-dev/differens-core

This package contains the tree matching engine. It takes two node trees and produces a typed edit script. The algorithm follows the GumTree lineage: top-down isomorphic matching finds anchors, bottom-up container matching pairs the rest, and leaf recovery fills the gaps. The result is a typed edit script with a minimal Move set.

The package has **zero dependencies**. Tier adapters in `@ossl-dev/differens-tiers` parse source into `Node` trees and call `diffTrees`.

Install:

```bash theme={null}
npm install @ossl-dev/differens-core
```

## Exports

| Export          | Kind      | Description                                |
| --------------- | --------- | ------------------------------------------ |
| `diffTrees`     | function  | Match two trees and produce an edit script |
| `treeFromValue` | function  | Build a `Node` tree from a plain JS value  |
| `createNode`    | function  | Build one `Node`, computing its hashes     |
| `Node`          | interface | The tree node shape                        |
| `EditAction`    | union     | `Insert` / `Delete` / `Update` / `Move`    |
| `MatchOptions`  | interface | Matching knobs                             |
| `DiffResult`    | interface | Return shape of `diffTrees`                |

## diffTrees

```ts theme={null}
diffTrees(oldRoot: Node, newRoot: Node, options?: Partial<MatchOptions>): DiffResult
```

Returns `{ changes: EditAction[], nodeCount: number, fallback?: "lines" }`. When either tree exceeds `maxNodes`, `fallback` is `"lines"` and the change list is empty. Line-diff instead in that case.

### MatchOptions

| Option          | Default   | Meaning                                                                         |
| --------------- | --------- | ------------------------------------------------------------------------------- |
| `minHeight`     | `2`       | Below this height an ambiguous subtree only matches when its parent already did |
| `bottomUpRatio` | `0.5`     | Minimum Dice coefficient of matched descendants to pair two containers          |
| `maxNodes`      | `250_000` | Node-count valve: larger inputs fall back to a line diff                        |

## Node

| Field           | Type           | Meaning                                                     |
| --------------- | -------------- | ----------------------------------------------------------- |
| `kind`          | `string`       | Canonical concept, e.g. `"function"`                        |
| `label?`        | `string`       | Name, when the node has one                                 |
| `value?`        | `string`       | Source text or primitive value                              |
| `children`      | `Node[]`       | Child nodes                                                 |
| `byteRange`     | `[start, end]` | Source offsets                                              |
| `line?`         | `number`       | 1-based source line, when the adapter knows it              |
| `height`        | `number`       | Distance from the leaves                                    |
| `contentHash`   | `number`       | Merkle hash over kind + label + value + children            |
| `structureHash` | `number`       | Merkle hash over kind + children only (label/value ignored) |

Both hashes are 53-bit-safe folds of two 32-bit FNV-1a streams, so subtree equality is an integer compare.

## EditAction

| Type     | Fields                                                                    |
| -------- | ------------------------------------------------------------------------- |
| `Insert` | `node`, `parent`, `position`, `context`                                   |
| `Delete` | `node`, `context`                                                         |
| `Update` | `node`, `detail: RenameDetail \| ValueChangeDetail`, `context`            |
| `Move`   | `node`, `fromParent`, `toParent`, `fromPosition`, `toPosition`, `context` |

`context: NodeContext[]` is the containment chain, nearest ancestor first. Each entry is `{ kind, label? }`. It lets narration say "removed `parse` from class `Config`", and gives AI tooling the full chain without re-parsing.

`RenameDetail`: `{ kind: "Renamed", from, to }`.
`ValueChangeDetail`: `{ kind: "ValueChanged", from?, to? }`.

## Usage

<CodeGroup>
  ```ts Diff two value trees theme={null}
  import { diffTrees, treeFromValue } from "@ossl-dev/differens-core";

  const oldTree = treeFromValue({ host: "localhost", port: 8080 });
  const newTree = treeFromValue({ host: "0.0.0.0", port: 8080 });

  const { changes } = diffTrees(oldTree, newTree);

  for (const action of changes) {
    console.log(action.type, action.node.kind, action.node.label);
    // "Update" "leaf" "host"   (detail: ValueChanged)
  }
  ```

  ```ts Build a tree by hand theme={null}
  import { createNode } from "@ossl-dev/differens-core";

  const tree = createNode({
    kind: "function",
    label: "parseConfig",
    children: [createNode({ kind: "identifier", value: "config", byteRange: [0, 6] })],
    byteRange: [0, 50],
  });
  ```
</CodeGroup>
