Summary
The graph's import resolver treats modules imported via an npm alias (the "alias-name": "npm:@scope/real-package@version" syntax in package.json) as external. As a result, importers_of / callers_of / impact_radius queries on files inside a locally-vendored package miss consumers that import via the alias — even when both the consumer and the library source are inside the graph's scanned tree.
Reproducer (generic)
- A multi-app repository with multiple consumer apps (
appA/, appB/) sharing a locally-developed library (sharedLib/) — a common pattern for monorepos that also publish the shared library to a private registry.
sharedLib/package.json declares "name": "@scope/shared".
- Each consumer
appX/package.json pins the library via the npm-alias syntax:
"dependencies": {
"sharedLib": "npm:@scope/shared@^1.0.0"
}
- Consumer source imports look like:
import foo from 'sharedLib/some/module';
- Build/update the graph on the repository root:
code-review-graph build .
- Query:
query_graph_tool pattern=importers_of target=sharedLib/some/module.js
Expected: returns appA/src/entry.js and similar consumers in appB.
Actual: returns 0 results. The alias-based imports are not reflected as edges in the graph.
The same gap shows up in get_impact_radius_tool, callers_of on exported functions, and any downstream feature that relies on IMPORTS_FROM edges between consumer modules and the aliased library.
Why it matters
Alias-based local vendoring is a common pattern in monorepos / multi-app repos:
- A shared UI or utility library is developed inside the repository.
- It's published to a private registry for production consumption.
- Consumer apps pin it via the
"alias": "npm:@scope/pkg@version" syntax so their original import paths (import X from 'alias/...') keep working after the publish-and-install cycle, without a repo-wide refactor.
For repos following this pattern, every cross-app / cross-module question about the shared library currently falls through the graph — users have to fall back to grep for any importers_of / callers_of query that crosses the alias boundary. That undermines the graph's main value proposition for exactly the codebases that would benefit most from structural analysis.
Proposed behaviour
When building/updating the graph:
- For each
package.json in the scanned tree, scan dependencies and devDependencies for entries whose value starts with npm:. Parse out the alias → real-package-name mapping.
- For each
npm: entry where the real-package-name is also present in the graph's scanned tree (as a local package.json with a matching name field), record a local alias mapping: alias-name → local-library-path.
- During import analysis, when the analyzer sees
import X from 'alias-name/<subpath>', resolve it against local-library-path/<subpath> rather than treating it as external. Emit the IMPORTS_FROM edge accordingly.
Smaller first step
If package.json scanning is too invasive for a first pass, an explicit config option would unblock users with this pattern:
# .code-review-graph.yml
alias_mappings:
- alias: "sharedLib"
local_path: "./packages/shared-library"
The resolver then treats imports of sharedLib/* as resolving inside ./packages/shared-library/*. Manual config gives users control today; auto-detection from package.json can come later.
Current workaround
Users of repos with this pattern fall back to grep -rnE "import.*['\"]alias-name/..." for alias-crossing queries, and use the graph only for intra-module traversal.
Environment
code-review-graph 2.3.2
- Installed via
uv tool install
- MCP server used via Claude Code
Happy to contribute a PR if the approach above sounds right. LMK which direction you'd prefer (package.json auto-detection vs explicit config first).
Summary
The graph's import resolver treats modules imported via an npm alias (the
"alias-name": "npm:@scope/real-package@version"syntax in package.json) as external. As a result,importers_of/callers_of/impact_radiusqueries on files inside a locally-vendored package miss consumers that import via the alias — even when both the consumer and the library source are inside the graph's scanned tree.Reproducer (generic)
appA/,appB/) sharing a locally-developed library (sharedLib/) — a common pattern for monorepos that also publish the shared library to a private registry.sharedLib/package.jsondeclares"name": "@scope/shared".appX/package.jsonpins the library via the npm-alias syntax:appA/src/entry.jsand similar consumers inappB.Actual: returns 0 results. The alias-based imports are not reflected as edges in the graph.
The same gap shows up in
get_impact_radius_tool,callers_ofon exported functions, and any downstream feature that relies onIMPORTS_FROMedges between consumer modules and the aliased library.Why it matters
Alias-based local vendoring is a common pattern in monorepos / multi-app repos:
"alias": "npm:@scope/pkg@version"syntax so their original import paths (import X from 'alias/...') keep working after the publish-and-install cycle, without a repo-wide refactor.For repos following this pattern, every cross-app / cross-module question about the shared library currently falls through the graph — users have to fall back to
grepfor anyimporters_of/callers_ofquery that crosses the alias boundary. That undermines the graph's main value proposition for exactly the codebases that would benefit most from structural analysis.Proposed behaviour
When building/updating the graph:
package.jsonin the scanned tree, scandependenciesanddevDependenciesfor entries whose value starts withnpm:. Parse out thealias → real-package-namemapping.npm:entry where thereal-package-nameis also present in the graph's scanned tree (as a localpackage.jsonwith a matchingnamefield), record a local alias mapping:alias-name → local-library-path.import X from 'alias-name/<subpath>', resolve it againstlocal-library-path/<subpath>rather than treating it as external. Emit theIMPORTS_FROMedge accordingly.Smaller first step
If package.json scanning is too invasive for a first pass, an explicit config option would unblock users with this pattern:
The resolver then treats imports of
sharedLib/*as resolving inside./packages/shared-library/*. Manual config gives users control today; auto-detection frompackage.jsoncan come later.Current workaround
Users of repos with this pattern fall back to
grep -rnE "import.*['\"]alias-name/..."for alias-crossing queries, and use the graph only for intra-module traversal.Environment
code-review-graph2.3.2uv tool installHappy to contribute a PR if the approach above sounds right. LMK which direction you'd prefer (package.json auto-detection vs explicit config first).