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 onlyapps/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/nodelists the repository’s files withgit 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.
createNarrator({ languages, locale, dependencies }). Without it, plugins fall back to imports alone.
Manifests are language-agnostic
Dependency manifests are not tied to JavaScript. AManifestReader describes one kind of manifest:
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.