Workspace package names (pnpm/npm/yarn workspaces) are never resolved — importers_of/impact_radius collapse at every package boundary
Summary
IMPORTS_FROM edges whose specifier is a package-manager workspace name (pnpm/npm/yarn workspaces — @core/policies, @acme/ui, …) are stored bare. TsconfigResolver only reads compilerOptions.paths; nothing reads the workspace's package.json name + exports/main. In a pnpm monorepo that is the primary import shape, so importers_of, impact_radius and references_to all collapse at every package boundary.
Measured (v2.3.8, pnpm monorepo, 1 491 files, 10 174 nodes)
Edge kinds by whether target_qualified resolved to a path:
| kind | resolved | bare |
|---|---|---|
| IMPORTS_FROM | 2 809 | 2 738 |
| REFERENCES | 3 534 | 1 343 |
| CALLS | 13 083 | 33 174 |
Top bare IMPORTS_FROM targets are all workspace names: @core/shared 237 files, @core/ui 135, @core/database/testing/ephemeral-pg 135, @core/database 109, @core/billing 67, @core/channels 55.
Concrete: importers_of("packages/policies/src/can.ts") → 1 result (its own unit test). grep -rl 'from "@core/policies' → 43 files. The same file is the authorization primitive every route in the app calls through.
Why tsconfig paths miss it: apps/web/tsconfig.json has only "@/*": ["./src/*"]. @core/* is not a path alias; each packages/*/package.json declares "name": "@core/policies" with "exports": { ".": "./src/index.ts" }, and pnpm links it. That is the standard monorepo layout (Turborepo, Nx, plain pnpm workspaces).
Where it lives
parser.py:13688 calls self._tsconfig_resolver.resolve_alias(module, file_path) and falls through to bare when it returns None. A WorkspaceResolver beside TsconfigResolver would close it:
- Find the workspace root (
pnpm-workspace.yaml, orpackage.jsonwithworkspaces). - Glob the declared package dirs, read each
package.json→name→ dir, plusexports(subpath map, conditions) /main/module/types. - On a bare specifier: longest-prefix match on package name; map the remainder through
exports(.for the bare name,./testing/ephemeral-pgfor@core/database/testing/ephemeral-pg); probe_PROBE_EXTENSIONSlike_probe_pathdoes. - Cache per workspace root; invalidate on
package.jsonchange (incremental already tracks file hashes).
Scope note: this makes importers_of("packages/policies/src/index.ts") correct. importers_of("…/can.ts") stays low because importers import the barrel — following export * from / export { x } from re-exports is a separate step, and worth naming in the same fix so the number is not read as complete.
Related: #969 (query-layer half of the same gap for references_to, merged via #988). This is the parser-layer half. I can send a PR if the shape above is acceptable — the repo I measured on is available for a before/after.
Source: tirth8205/code-review-graph