> ## 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-narrate

> Narration engine API reference

# @ossl-dev/differens-narrate

The narration engine turns `EditAction`s into human-readable text. It is template-based, so the output is deterministic. It returns a `SemanticChange[]`, where each entry pairs the raw action with an English `description`.

Install:

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

## Exports

| Export          | Kind     | Description                                                    |
| --------------- | -------- | -------------------------------------------------------------- |
| `narrate`       | function | `EditAction[]` → `SemanticChange[]` with descriptions          |
| `narrateAction` | function | One action → one English sentence                              |
| `summarize`     | function | One summary sentence for a changeset ("3 additions, 1 move")   |
| `formatChanges` | function | Render `SemanticChange[]` in terminal/json/markdown/llm        |
| `humanizeKind`  | function | `"call_expression"` / `"CallExpression"` → `"call expression"` |
| `OutputFormat`  | type     | `"terminal" \| "json" \| "markdown" \| "llm"`                  |

## narrate

```ts theme={null}
narrate(actions: EditAction[], opts?: { filePath?: string }): SemanticChange[]
```

```ts theme={null}
// SemanticChange:
// { action: EditAction; description: string; filePath?: string;
//   fromFilePath?: string; toFilePath?: string }
```

Duplicate `Update`s for the same rename are collapsed. Renaming a function reports it once, with the named node winning, instead of once per identifier occurrence.

## Vocabulary

Kinds are canonicalized at the extractor layer. `humanizeKind` renders them for display, splitting camelCase and underscores and lowercasing. Language-specific node names map to the same canonical kind:

| Canonical  | Source node types (language-specific)                                                           |
| ---------- | ----------------------------------------------------------------------------------------------- |
| `function` | `fn` (Rust), `def` (Python), `func` (Go), `function_declaration` (TS/JS), `FunctionDeclaration` |
| `class`    | `class` (TS/JS/Python), `ClassDeclaration`, `type_declaration` (Rust)                           |
| `variable` | `let`/`const`/`var` (TS/JS), `variable_declarator`, `assignment`                                |
| `import`   | `import_statement`, `import_declaration`, `use_declaration` (Rust)                              |

Narration shapes:

* `added function \`parseConfig\`\`
* `removed class \`RetryPolicy\`\`
* `renamed function \`foo\` to \`bar\`\`
* `changed value of config key \`host\` from \`localhost\` to \`0.0.0.0\`\`
* `moved function \`parseConfig\` from utils.ts to config.ts\`
* Scoped: `removed variable \`timeout\` from function \`connect\`\`. The nearest named ancestor is included when it adds information.

## Usage

<CodeGroup>
  ```ts Narrate an edit script theme={null}
  import { narrate, formatChanges } from "@ossl-dev/differens-narrate";

  const changes = narrate(actions, { filePath: "src/checkout.ts" });

  for (const c of changes) {
    console.log(c.description);
    // "renamed function `computeTotal` to `calculateTotalAmount`"
  }

  console.log(formatChanges(changes, { format: "terminal" }));
  ```

  ```text Terminal output theme={null}
    ~ renamed function `computeTotal` to `calculateTotalAmount`
    + added function `applyDiscount` from class `Checkout`

  3 modifications, 1 addition
  ```

  ```text LLM output theme={null}
  differens/1 1 files 2 changes 2 named
  # src/checkout.ts
  ~ function computeTotal :12 < class Checkout computeTotal -> calculateTotalAmount
  + function applyDiscount :40
  ```
</CodeGroup>
