NCI
Guides · Monorepo

Index a pnpm / nx / turbo monorepo

One database, one CLI, one MCP server. Multiple workspaces. Here is the recipe that keeps every package indexable while staying fast on subsequent runs.

Layout

my-monorepo/ apps/ web/ cli/ packages/ shared/ ui/ pnpm-workspace.yaml package.json nci.config.json

nci.config.json

{ "project_root": ".", "workspaces": ["apps/*", "packages/*"], "package_scope": ["dependencies", "dev_dependencies"], "max_hops": 10 }

That config does three things:

  1. Scans the root node_modules plus every workspace’s node_modules.
  2. Includes both dependencies and dev_dependencies so test/build types are indexed too.
  3. Lets you query across the whole tree from any package.

Index incrementally

nci·my-monorepo

--package is repeatable and accepts SQLite-style globs. Use it to re-index only the workspace packages you control after a refactor — third-party dependencies stay cached.

Skip the root install

If your monorepo hoists everything into node_modules at the workspaces but keeps the root empty:

nci·my-monorepo

That tells the scanner to ignore <project_root>/node_modules and only walk the listed workspaces. Conflicts with --include-root-workspace (the override that forces the root in even when nci.config.json excluded it).

Stub noisy dependencies

Some dependencies show up everywhere. zod lives in nearly every workspace package, and almost every file does import { z } from "zod". Its types are deep generic chains — z.object({...}).strict().refine(...) builds large inference trees that NCI has to walk on every consumer file. You almost never query zod’s internals; you just want NCI to know your packages reference it.

Tell NCI not to crawl into zod. Every consumer-side import collapses into a single stub edge:

nci·my-monorepo

The flag (short: -s) is repeatable and accepts bare names or scoped specifiers like @aws-sdk/client-s3. Each consumer-side import becomes an npm::<specifier>::<member> edge — e.g. import { z } from "zod" resolves to npm::zod::z. Fast to write, queryable as evidence, never parsed.

Make it sticky by setting dependency_stub_packages in nci.config.json:

{ "project_root": ".", "workspaces": ["apps/*", "packages/*"], "package_scope": ["dependencies", "dev_dependencies"], "dependency_stub_packages": [ "zod", "@aws-sdk/client-s3", "googleapis" ] }

The CLI value is unioned with the config — CI can add more stubs without rewriting the file. Drop a name from the config and the next index re-parses it in full.

Wire up the agent

Pair this walkthrough with any client setup page — Claude, Cursor, Codex, Antigravity, or OpenCode. The MCP server reads the same nci.config.json and resolves to the same nci.sqlite regardless of client.