Skip to main content
A plugin should only explain a call when it knows the call belongs to its library. Imports are the first clue, but plenty of files use a library without importing it: a route file that receives app from somewhere else, or a helper that gets a Stripe client passed in. So Narrator also reads the dependency manifests in your repository. A file belongs to the package whose manifest is nearest to it, and that package’s dependencies tell plugins what the file can use.

What plugins get

Inside a rule, the engine exposes two questions that combine both clues: Plugins use the first to stay quiet in files that don’t use their library, and the second to pick between readings when a library changed meaning across majors. See Plugin versioning. The raw data is also available as e.dependencies, a map from package name to version range.

An example

This monorepo has two packages, and only apps/api depends on Hono. The same file reads as a Hono route in that package and as a plain method call in the other one, even though neither file imports hono.
dependencies.ts
Output

How manifests are found

createDependencyIndex({ manifests, readers? }) from @usenarrator/core takes a map from repository paths to file contents and builds the lookup. For each file, forFile(path, ecosystem) returns the nearest manifest’s dependencies merged over the repository root’s, so workspace-wide tools declared at the root apply everywhere. You rarely build the map yourself:
  • On Node and Bun, loadDependencyIndex({ root }) from @usenarrator/node lists the repository’s files with git ls-files, falling back to a shallow directory walk outside git, and reads every manifest it finds. createNodeNarrator() does this for you, starting from the git checkout around the working directory. Manifests are read the first time a file is narrated.
  • The CLI builds the index for the repository that contains your files, and uses the same index to decide which plugins to load.
  • The browser extension lists the repository’s tree with one GitHub API call, then fetches only the manifests that sit in a changed file’s directory or one of its parents.
Pass the index to createNarrator({ languages, locale, dependencies }). Without it, plugins fall back to imports alone.

Manifests are language-agnostic

Dependency manifests are not tied to JavaScript. A ManifestReader describes one kind of manifest:
The built-in reader, packageJson, reads the dependencies, devDependencies, peerDependencies and optionalDependencies of every package.json outside node_modules, dist and similar folders. A source language lists the ecosystems its imports come from in SourceLanguage.ecosystems, so the TypeScript language asks for ["npm"], and a future Rust language would ask for ["crates"] with a Cargo.toml reader. See Adding a source language.