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

# @usenarrator/core

> createNarrator, dependency manifests, lines, markup, diffs and the contracts every language and locale implements.

The core package has no dependencies and knows nothing about any particular programming language. It defines the contracts, creates narrators and builds diff reports.

## createNarrator

```ts theme={null}
function createNarrator(options: { languages: SourceLanguage[]; locale: Locale; dependencies?: DependencyIndex }): Narrator;
```

<ParamField path="languages" type="SourceLanguage[]" required>
  The source languages this narrator can read. For each file, the first language whose `handles(path)` returns `true` is used.
</ParamField>

<ParamField path="locale" type="Locale" required>
  The output language, for example `en` from `@usenarrator/locale-en`.
</ParamField>

<ParamField path="dependencies" type="DependencyIndex">
  The repository's dependency manifests, from `createDependencyIndex` or `loadDependencyIndex` in `@usenarrator/node`. For each file, the narrator passes its language the dependencies of the file's package in that language's `ecosystems`. Without it, plugins only see imports. See [Dependency detection](/concepts/dependency-detection).
</ParamField>

### Narrator

```ts theme={null}
type Narrator = {
  locale: Locale;
  canNarrate(path: string): boolean;
  narrate(src: string, path: string): Narration;
  fileNarrator(path: string): FileNarrator;
  diff(files: ChangedFile[], opts?: { resolve?: Resolver; includeGenerated?: boolean }): FileJson[];
  diffFiles(files: ChangedFile[], opts?: { includeGenerated?: boolean }): FileDiff[];
};

type Narration = FileNarration & { problems: NarrationProblem[] };
```

<ResponseField name="canNarrate(path)" type="boolean">
  Whether any configured language handles this path.
</ResponseField>

<ResponseField name="narrate(src, path)" type="Narration">
  Narrates one file and returns `{ lines, errors, stats, problems }`. `errors` counts syntax errors, and `problems` describes them along with failed plugin rules. If the language itself throws, `narrate` returns no lines and one `narrator` problem instead of throwing. It throws only when no language handles the path, so check `canNarrate` first when paths come from users.
</ResponseField>

<ResponseField name="fileNarrator(path)" type="FileNarrator">
  A fresh narrator for one file, with access to `units(src, path)` for diffing.
</ResponseField>

<ResponseField name="diff(files, opts)" type="FileJson[]">
  Narrates both sides of each changed file, diffs them by unit, detects moves and renames across files, and returns HTML-ready rows. Files no language handles are skipped, and files whose English didn't change are left out. `opts.resolve` turns function and value references into links. See [Units and diffs](/concepts/units-and-diffs).

  Generated, vendored, minified and build files are not narrated. They are returned with `skipped` set to the reason and no units, so a report can still list them. Pass `includeGenerated: true` to narrate them anyway. A file that can't be narrated is returned with no units and a `narrator` problem, and the rest of the change set is unaffected. When the new side has a syntax error, declarations that only seem to be missing past the error are not reported as removed.
</ResponseField>

<ResponseField name="diffFiles(files)" type="FileDiff[]">
  The same change set as `diff`, as structured units whose rows hold `Line` objects rather than HTML, with the same `skipped` and `problems` fields. Render it with `renderDiffText`.
</ResponseField>

```ts theme={null}
type ChangedFile = {
  path: string; // relative to the repository root
  oldPath: string | null;
  status: "added" | "deleted" | "renamed" | "modified";
  oldSrc: string;
  newSrc: string;
  skipped?: SkipReason; // set it when you already know, e.g. from .gitattributes; the sources may then be empty
};

type FileDiff = {
  path: string;
  oldPath: string | null;
  status: ChangedFile["status"];
  adds: number;
  dels: number;
  units: UnitDiff[];
  skipped?: SkipReason;
  problems: NarrationProblem[];
};

type Resolver = (payload: string, kind: string) => string | null;
```

### renderDiffText

```ts theme={null}
function renderDiffText(options: { files: FileDiff[]; format?: (s: string) => string }): string;
```

Prints structured diffs as indented text: a header per file, a line per changed unit with its status, then each sentence marked `-`, `+` or left unmarked when unchanged. Skipped files get a single header line such as `dist/app.js [modified, skipped: build output]`, and problems are printed under their file's header with a leading `!`. `format` defaults to `plain`. Pass `toAnsi` for colour.

## Problems

### NarrationProblem

```ts theme={null}
type NarrationProblem = {
  path: string;
  message: string;
  severity: "error" | "warning";
  source: "parser" | "plugin" | "narrator";
  line?: number;
  plugin?: string;
  rule?: string;
};

function describeProblem(problem: NarrationProblem): string;
```

| `source` | Meaning |
| - | - |
| `parser` | A syntax error. The output holds whatever the parser could still read. `line` is set when the parser reports a position. |
| `plugin` | A plugin rule threw. The statement was read without that rule, and `plugin` and `rule` name the culprit. |
| `narrator` | The file couldn't be narrated at all, so it has no lines. Other files in the same call are not affected. |

`severity` is `error` when part of the file is missing from the output and `warning` when a fallback reading was used. `describeProblem` turns a problem into one English sentence, the way the CLI prints it.

## Generated files

```ts theme={null}
type SkipReason = "generated" | "vendored" | "build output" | "dependency" | "lockfile" | "minified";

function skipReason(options: { path: string; src?: string }): SkipReason | null;
function pathSkipReason(path: string): SkipReason | null;
function contentSkipReason(src: string): SkipReason | null;
```

`narrator.diff` and `narrator.diffFiles` use these to decide what to skip. Pass paths relative to the project root, since a folder named `build` above the project would otherwise count.

| Check | Skips |
| - | - |
| Path | Anything under `node_modules`, `dist`, `.next`, `.nuxt`, `.output`, `.svelte-kit`, `.vercel`, `.turbo`, `.cache`, `coverage`, `vendor` or `.yarn`; JavaScript under `build` or `out`; lockfiles; `.pnp.cjs`; `*.min.js`, `*.bundle.js` and `*.chunk.js`. |
| Content | A banner comment in the first five lines containing `@generated`, `DO NOT EDIT` or `generated by`, and minified code, meaning lines that average more than 160 characters or any line over 10,000 characters. Only the first 64 KB are read, so huge files cost almost nothing. |

`.gitattributes` needs file system access, so it lives in `@usenarrator/node` as `gitAttributeSkips` and `changedFilesFromGit`.

## Dependencies

```ts theme={null}
type Dependencies = Record<string, string>; // package name -> version range

type ManifestReader = {
  ecosystem: string;
  matches: (path: string) => boolean;
  parse: (text: string) => Dependencies;
};

type DependencyIndex = {
  manifestPaths(paths: string[]): string[];
  forFile(path: string, ecosystem: string): Dependencies;
};

function createDependencyIndex(options: { manifests: Map<string, string>; readers?: ManifestReader[] }): DependencyIndex;

const packageJson: ManifestReader;
const defaultManifestReaders: ManifestReader[]; // [packageJson]
```

| Member | Description |
| - | - |
| `createDependencyIndex` | Builds an index from repository-relative manifest paths and their contents. Manifests that fail to parse are skipped. |
| `manifestPaths(paths)` | Filters a list of repository paths down to the manifests the readers recognise, so a host knows what to fetch. |
| `forFile(path, ecosystem)` | The dependencies of the nearest manifest above `path`, merged over the repository root's. |
| `packageJson` | Reads `dependencies`, `devDependencies`, `peerDependencies` and `optionalDependencies`, ignoring manifests under `node_modules`, `dist`, `vendor` and similar folders. |

See [Dependency detection](/concepts/dependency-detection).

## Lines

```ts theme={null}
type LineKind = "head" | "stmt" | "note" | "bullet" | "raw" | "blank";
type Line = { d: number; text: string; start: number; end: number; kind: LineKind };
type Doc = { t: string; kids: Line[] };
```

`Line.d` is the indent depth. `start` and `end` are source offsets, or `-1` for lines that don't map to code. `text` contains [markup](/concepts/markup).

`Doc` is what rules return: inline text `t` and the block lines `kids` it introduces. Create one with `D(t, kids?)`, which also strips a trailing colon from `t` when there are kids, because the colon is added when the block is emitted.

| Function | Description |
| - | - |
| `renderText(lines, fmt?)` | Joins lines with two spaces of indent per level. `fmt` defaults to `plain`. Pass `toAnsi` for terminal colours. |
| `D(t, kids?)` | Creates a `Doc`. |
| `capFirst(s)`, `lowerFirst(s)` | Change the case of the first visible letter, skipping over markup. |

## Markup

| Function | Description |
| - | - |
| `mk(kind, text, payload?)` | Creates a token. `kind` is `"v"`, `"f"`, `"t"`, `"l"` or `"c"`. |
| `plain(s)` | Removes markup. Type names are wrapped in parentheses. |
| `toHtml(s, resolve)` | Wraps tokens in spans and, where `resolve` returns a URL, links. |
| `toAnsi(s)` | Colours tokens for a terminal. |
| `vlen(s)` | The visible length of a string, ignoring markup. |

## Contracts

```ts theme={null}
interface SourceLanguage {
  id: string;
  handles(path: string): boolean;
  ecosystems?: string[];
  create(locale: Locale, ctx?: { dependencies?: (path: string) => Dependencies }): FileNarrator;
}

interface FileNarrator {
  narrate(src: string, path: string): FileNarration;
  units(src: string, path: string): Unit[];
  problems?(): NarrationProblem[]; // from the latest units() call
}

type FileNarration = { lines: Line[]; errors: number; stats: NarrationStats; problems?: NarrationProblem[] };
type NarrationStats = { nodes: number; fallbacks: number; fallbackTypes: Record<string, number>; ruleErrors?: RuleError[] };
type RuleError = { plugin: string; rule: string; message: string; count: number; line?: number };
```

See [Adding a source language](/guides/source-languages) and the [Locale reference](/reference/locale).

## Diff primitives

These are what `narrator.diff` and `narrator.diffFiles` are built on.

```ts theme={null}
type Unit = { key: string; title: string; start: number; end: number; lines: Line[] };

function diffUnits(before: Unit[], after: Unit[]): UnitDiff[];
function diffLines(a: Line[], b: Line[], context?: number): Row[];
function detectMoves(files: { path: string; units: UnitDiff[] }[]): void;
function bodySimilarity(a: Line[], b: Line[]): number;
function diffFiles(options: { files: ChangedFile[]; narratorFor: (path: string) => FileNarrator; includeGenerated?: boolean }): FileDiff[];
function buildDiffReport(options: {
  files: ChangedFile[];
  narratorFor: (path: string) => FileNarrator;
  resolve: Resolver;
  includeGenerated?: boolean;
}): FileJson[];
```

`detectMoves` updates the unit diffs in place, marking matched pairs as `moved`, `renamed` or `moved-away`.

## Lexicon

`Lexicon` reads identifiers into words. Each engine has one, extended by the `vocabulary` of every plugin.

```ts theme={null}
type Vocabulary = {
  glossary?: Record<string, string>;
  acronyms?: string[];
  verbs?: string[];
  predicatePrefixes?: string[];
};
```

| Member | Example |
| - | - |
| `humanize(id)` | `cusProductIds` becomes "customer product IDs" |
| `words(id)` | `isTrialing` becomes `["is", "trialing"]` |
| `isPredicateName(id)` | `true` for `isTrialing`, `hasSeats` |
| `startsWithVerb(id)` | `true` for `buildInvoice` |
| `converterParts(id)` | `invoiceToPdf` becomes `[["invoice"], ["pdf"]]` |
| `extend(vocabulary)` | Adds glossary entries, acronyms, verbs and predicate prefixes |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.