rlsbl v0.92.0 /Dependency validation
On this page

Dependency validation checks for monorepo workspaces: unused deps, undeclared deps, dead modules, and circular dependency detection.

#Dependency validation

rlsbl validates dependencies at two levels: workspace-level (between monorepo packages) and file-level (within a single package). Four workspace checks detect mismatches between declared and actual dependencies across all 4 supported language ecosystems. Three intra-package checks detect dead code and import cycles using per-file BFS traversal and Tarjan's algorithm for strongly connected components.

#Workspace dependency checks

These four checks compare declared dependencies in workspace manifests against actual import usage discovered by scanning source code. They share a common import scan cache (ctx._dep_import_cache) to avoid redundant source tree walks, and all four run during rlsbl check --tag workspace.

#deps-unused

Detects workspace dependencies declared in the project manifest that are not actually imported anywhere in the project's source code. An unused dependency adds unnecessary coupling and bloats the install footprint for consumers.

  • Scans both lib and test code for imports matching workspace sibling names
  • A dep is unused if zero source files import it (regardless of context)
  • Supports overrides via .rlsbl-monorepo/dep-overrides.toml with mandatory reason field
  • Override format: [[unused_allowed]] entries with package, dep, and reason keys
  • Imports inside try/except ImportError blocks only count as usage for optional deps (scope dev/peer). A hard dependency (scope runtime/explicit) that is only imported inside a guard is flagged -- declaring a hard dependency but importing it conditionally is contradictory (declare it optional or import it unconditionally).
  • If the same dep is declared with multiple scopes, hard scopes take precedence: declared hard anywhere means treated as hard.

#deps-undeclared

Detects imports of workspace sibling packages that are not declared as dependencies in the project's manifest. Undeclared dependencies work locally because all packages share a repository, but break when the project is installed standalone.

  • Only checks production code (non-test context) -- test files have more lenient rules
  • Self-imports are excluded (a package importing its own submodules is fine)
  • Detected import must match a workspace sibling name to trigger

#deps-runtime-test-only

Detects runtime dependencies that are only imported in test code and never in production source files. These should be declared as dev dependencies instead, since shipping them as runtime dependencies forces consumers to install packages they will never use.

  • Checks deps with scope="runtime" in the workspace manifest
  • Flags any runtime dep that appears in test_imports but not in lib_imports
  • Helps maintain correct dependency scoping for published packages

#deps-dev-in-lib

Detects dev dependencies that are imported in production code, indicating they should be declared as runtime dependencies instead. When a dev dependency is used in library or application code, consumers who install the package will get import errors because dev dependencies are not installed transitively.

  • Checks deps with scope="dev" in the workspace manifest
  • Flags any dev dep that appears in lib_imports
  • Catches incorrect scoping that would break consumers at install time

#Intra-package checks

These checks build per-file import graphs within a single project to detect structural problems like unreachable code and circular dependencies. They operate at file granularity rather than package granularity and do not share the workspace import cache.

#dead-modules

Detects source files that are unreachable from any entry point via BFS on the file-level import graph. Dead modules are production code that can never be executed because no import chain connects them to the package's public API or executable entry points.

Algorithm:

  1. Identify entry points (language-specific, see table below)
  2. Build file-level import graph (each file maps to the set of files it imports)
  3. BFS from all entry points
  4. Report files in production code that are never reached

Exclusions (6 categories, common to all languages):

  • Test files (matching 7 patterns from _NON_PRODUCTION_PATTERNS)
  • .selfdoc directories
  • _build directories
  • Browser asset directories (static, public, assets -- 3 directory names)
  • Generated files (.g.dart for Dart)

Per-language behavior:

dead-modules
LanguageDetection functionEntry pointsScope
Pythonfind_dead_modules()__init__.py files + cross-reference via import prefix matchingAll production .py files
Gofind_dead_go_packages()Packages imported by any non-test file outside the packageOnly internal/ packages (Go enforces visibility elsewhere)
npmfind_dead_npm_modules()package.json exports/main/bin fields, resolved to sourceAll production .js/.ts/.mjs/.cjs/.tsx files
Dartfind_dead_dart_modules()lib/<name>.dart barrel file + bin/*.dart scriptsAll production .dart files

#circular-deps

Detects circular dependencies by computing strongly connected components using Tarjan's algorithm on the file-level import graph. Only cycles involving 2 or more distinct nodes are reported as violations; self-loops (a file importing itself) are not flagged since they are harmless in all supported languages.

Severity by language:

circular-deps
LanguageSeverityRationale
npm (JS/TS)error (fail)Circular imports cause runtime issues (undefined values, initialization order bugs)
PythonwarningPython handles circular imports at runtime but they indicate design problems
DartwarningDart handles cycles but they indicate poor layering
GoexcludedThe Go compiler rejects circular imports natively; rlsbl does not duplicate that check

#library-lint

Enforces quality constraints specific to library packages published for consumption by other projects. This check detects imports, I/O patterns, and platform-specific APIs that are inappropriate for reusable library code and would cause problems for downstream consumers:

  • Detects imports inappropriate for library code (e.g., dart:io in a pure Dart library)
  • Detects stdout/stderr writes in library code (libraries should not print directly)
  • Only applies to projects marked library = true in workspace.toml

#Dead workspace packages

The dead-workspace-packages check operates at the workspace level, identifying library packages that no workspace sibling imports. A library with zero importers may indicate abandoned code, an incomplete migration, or a package that is only consumed by external projects outside the monorepo.

Criteria:

  • Only checks projects with library = true (apps/CLIs are entry points, not consumed)
  • Skips dev_node = true projects
  • Checks both lib and test import contexts from all other workspace projects

Results:

Dead workspace packages
ConditionSeverityMessage
Library imported in production code by at least one siblingpass (alive)--
Library only imported in test codewarn"library 'X' is only imported in test code by workspace siblings (A, B)"
Library not imported by any workspace packagewarn"library 'X' is not imported by any workspace package"

Published libraries may still be consumed externally, so zero workspace importers is a warning, not an error.

#Language support matrix

The following matrix shows which dependency validation checks are supported for each of the 4 languages with import scanning (Python, Go, npm/JS/TS, and Dart). Not all checks apply to every language -- for example, Go's compiler natively rejects circular imports, so rlsbl skips that check for Go projects.

[selfdoc: custom directive 'table-feature-matrix' failed: No module named 'rlsbl']

#Configuration

#dep-overrides.toml

Located at .rlsbl-monorepo/dep-overrides.toml, this configuration file allows suppressing deps-unused errors for dependencies that are intentionally declared but not statically detectable by import scanning (e.g., dynamic imports, plugin loading, or runtime reflection).

TM toml
[[unused_allowed]]
package = "my-app"
dep = "my-lib"
reason = "Used via dynamic import at runtime, not statically detectable"

Every entry requires all three fields (package, dep, reason). Empty reason values are rejected. This ensures the override has a documented justification.

#batch_limits in config.json

Controls batch size validation for changelog entries, limiting how many commits a single entry can reference and how many entries can reference the same commit. While not directly related to dependency validation, these limits are part of the same check infrastructure and run alongside dep checks during rlsbl check:

batch_limits in config.json
KeyTypeDefaultDescription
max_commits_per_entryint5Maximum commit hashes allowed in a single JSONL entry
max_entries_per_commitint5Maximum JSONL entries that may reference the same commit
exclusionsarray[]Per-violation silencers, each with mandatory reason plus commits or entries

#Source modules

The dependency validation system is implemented in two core modules: dep_validation handles workspace-level checks (unused, undeclared, test-only, dev-in-lib, dead packages) while import_scanners provides the per-language AST-based import extraction that feeds all dependency analysis.

#rlsbl.dep_validation

Dependency validation for monorepo workspaces.

Checks for unused declared dependencies and undeclared imports across workspace projects. Uses import scanners to compare actual source imports against manifest-declared dependencies.

#load_dep_overrides

python
def load_dep_overrides(root: str) -> dict[tuple[str, str], str]

Load dep-overrides.toml from the monorepo config directory.

Returns a dict mapping (package, dep) to reason string. Raises ValueError if an entry is missing a required 'reason' field. Returns empty dict if the file does not exist.

#_get_imported_workspace_packages

python
def _get_imported_workspace_packages(project_dir: str, workspace_names: set[str], exclude_dirs: list[str] | None=None, *, module_path_map: dict[str, str] | None=None, namespace_map: dict[str, str] | None=None, import_names: dict[str, str] | None=None, jvm_package_map: dict[str, str] | None=None) -> tuple[set[str], set[str], set[str]]

Scan a project for workspace imports, split by context.

Args:

  • project_dir: absolute path to the project root.
  • workspace_names: set of all workspace member package names.
  • exclude_dirs: directory paths to skip during the walk.
  • module_path_map: mapping of workspace project name to its Go

module path (from go.mod). Passed through to GoImportScanner.

  • namespace_map: mapping of namespace-qualified import paths to

workspace project names. Passed through to PythonImportScanner.

  • import_names: mapping of project_name -> import_name from workspace

config. Passed through to PythonImportScanner.

  • jvm_package_map: mapping of dotted package prefix to workspace

project name. Passed through to JavaImportScanner and KotlinImportScanner.

Returns (lib_imports, test_imports, guarded_imports) where each is a set of workspace package names. Guarded imports are those inside try/except ImportError blocks -- they count as "used" (not unused) but should not trigger undeclared-dep errors.

#check_unused_deps

python
def check_unused_deps(project_name: str, project_dir: str, manifest_deps_with_scope: dict[str, str], workspace_names: set[str], whitelist: dict[tuple[str, str], str], *, _cached_imports: tuple[set[str], set[str], set[str]] | None=None) -> list[str]

Check for declared workspace deps that no source file imports.

Args:

  • project_name: name of the project being checked.
  • project_dir: absolute path to the project directory.
  • manifest_deps_with_scope: mapping of declared intra-workspace

dependency name -> scope ("runtime", "dev", "peer", "explicit").

  • workspace_names: set of all workspace member package names.
  • whitelist: mapping of (package, dep) -> reason for allowed unused deps.
  • _cached_imports: optional pre-computed (lib_imports, test_imports,

guarded_imports) tuple to avoid redundant scans when multiple checks share the same project.

Returns:

  • list of error strings (empty means all good).

#check_undeclared_deps

python
def check_undeclared_deps(project_name: str, project_dir: str, manifest_deps: set[str], workspace_names: set[str], *, _cached_imports: tuple[set[str], set[str], set[str]] | None=None) -> list[str]

Check for imports from workspace packages not declared as deps.

Only checks lib/ imports (non-test context) against declared dependencies. Test files have more lenient rules and are skipped. Guarded imports (try/except ImportError) are excluded -- optional imports don't need to be declared as dependencies.

Args:

  • project_name: name of the project being checked.
  • project_dir: absolute path to the project directory.
  • manifest_deps: set of declared intra-workspace dependency names.
  • workspace_names: set of all workspace member package names.
  • _cached_imports: optional pre-computed (lib_imports, test_imports,

guarded_imports) tuple to avoid redundant scans when multiple checks share the same project.

Returns:

  • list of error strings (empty means all good).

#check_runtime_test_only

python
def check_runtime_test_only(manifest_deps_with_scope: dict[str, str], lib_imports: set[str], test_imports: set[str]) -> list[str]

Find runtime deps that are only used in test code.

For each dependency where scope="runtime": if it appears in test_imports but NOT in lib_imports, it is flagged.

Args:

  • manifest_deps_with_scope: mapping of dep name -> scope string.
  • lib_imports: workspace package names found in production code.
  • test_imports: workspace package names found in test code.

Returns:

  • list of flagged dependency names.

#check_dev_in_lib

python
def check_dev_in_lib(manifest_deps_with_scope: dict[str, str], lib_imports: set[str]) -> list[str]

Find dev deps that are imported in production code.

For each dependency where scope="dev": if it appears in lib_imports, it is flagged.

Args:

  • manifest_deps_with_scope: mapping of dep name -> scope string.
  • lib_imports: workspace package names found in production code.

Returns:

  • list of flagged dependency names.

#_is_non_production_path

python
def _is_non_production_path(filepath: str, project_dir: str) -> bool

Check if a file path is in a non-production context.

Delegates to _is_test_context from import_scanners.py to detect test directories, example directories, and test file patterns.

#_python_module_name

python
def _python_module_name(filepath: str, project_dir: str) -> str | None

Derive the dotted module name from a Python file path.

Returns None for files that cannot be mapped to a module name (e.g. files outside a package structure).

#_collect_python_imports

python
def _collect_python_imports(filepath: str, project_dir: str) -> set[str]

Collect all import targets from a Python file.

Returns a set of dotted module names that are imported. Handles absolute imports (import foo, from foo.bar import baz) and relative imports (from .utils import helper, from ..core import x). Relative imports are resolved to absolute dotted paths using the file's position within the project directory.

#_collect_init_exports

python
def _collect_init_exports(filepath: str) -> set[str]

Collect names exported from an __init__.py file.

Looks for __all__ definitions and import statements. Returns module names that are imported by the __init__.py.

#find_dead_modules

python
def find_dead_modules(project_dir: str, exclude_dirs: list[str] | None=None) -> list[str]

Find Python modules not referenced by any other module in the project.

A module is considered dead if:

  1. No other module in the project imports it (by any prefix match)
  2. It is not listed in any __init__.py's __all__ or imported by

any __init__.py

Only checks Python projects. Non-production files (tests, examples) are excluded from the scan.

Args:

  • project_dir: absolute path to the project root.
  • exclude_dirs: directory paths to skip during the walk

(relative to project_dir or absolute).

Returns:

  • list of relative paths of dead modules (e.g. ["mylib/unused.py"]).

#_go_package_dir

python
def _go_package_dir(filepath: str) -> str

Return the directory of a Go file (its package directory).

#find_dead_go_packages

python
def find_dead_go_packages(project_dir: str, exclude_dirs: list[str] | None=None) -> list[str]

Find Go internal packages not referenced by any non-test code.

A Go internal package is dead if no non-test .go file outside that package imports it. Only packages under internal/ subdirectories are checked, since those are the packages with restricted visibility in Go's module system.

Args:

  • project_dir: absolute path to the Go project root (where go.mod lives).
  • exclude_dirs: directory paths to skip during the walk

(relative to project_dir or absolute).

Returns:

  • list of relative paths of dead internal packages
  • (e.g. ["internal/unused"]).

#_resolve_npm_file

python
def _resolve_npm_file(path: str) -> str | None

Resolve a single path to an existing file using npm conventions.

Tries the exact path, then with each extension appended, then as a directory with index files. Also handles .js -> .ts mapping for TypeScript projects.

Returns the absolute file path if found, None otherwise.

#_collect_export_paths

python
def _collect_export_paths(value: object) -> list[str]

Recursively collect all file path strings from a package.json exports value.

The exports field can be:

  • A string: "./dist/index.js"
  • A dict with condition keys: {"import": "./dist/index.mjs", "require": "./dist/index.cjs"}
  • A nested subpath map: {".": {"import": "..."}, "./sub": "..."}
  • A list (rarely): ["./a.js", "./b.js"]

Collects all string values that look like file paths (start with ".").

#_resolve_npm_entry_points

python
def _resolve_npm_entry_points(project_dir: str) -> set[str]

Extract and resolve entry point file paths from package.json.

Parses exports, main, and bin fields. Resolves each declared path to an absolute filesystem path, handling .js -> .ts mapping and directory -> index file resolution.

Returns a set of absolute file paths. Missing files are skipped.

#_build_npm_import_graph

python
def _build_npm_import_graph(project_dir: str, exclude_dirs: list[str] | None=None) -> dict[str, set[str]]

Build a file-level import graph for an npm project.

Uses NpmAstLinter.scan_imports() to collect all imports, then resolves relative imports to absolute file paths.

Returns a dict mapping each source file's absolute path to a set of absolute paths it imports (only resolved relative imports).

#_is_inside_python_package

python
def _is_inside_python_package(filepath: str, project_dir: str) -> bool

Check if a file is inside a directory containing __init__.py.

Walks up from the file's directory looking for __init__.py, stopping at project_dir. If any directory from the file's parent up to (but not above) project_dir contains __init__.py, the file is considered a Python data resource, not an npm module.

#find_dead_npm_modules

python
def find_dead_npm_modules(project_dir: str, exclude_dirs: list[str] | None=None) -> list[str]

Find npm source files unreachable from any package.json entry point.

A source file is "dead" if there is no path through the import graph from any declared entry point (exports, main, bin) to that file.

Args:

  • project_dir: absolute path to the npm project root.
  • exclude_dirs: directory paths to skip during the walk

(relative to project_dir or absolute).

Returns:

  • sorted list of relative paths of dead source files.

#_read_dart_package_name

python
def _read_dart_package_name(project_dir: str) -> str | None

Read the package name from pubspec.yaml.

Returns None if pubspec.yaml does not exist or has no 'name' field.

#_resolve_dart_entry_points

python
def _resolve_dart_entry_points(project_dir: str) -> set[str]

Determine Dart entry point files for reachability analysis.

Entry points are:

  • lib/.dart (barrel file / main library entry)
  • bin/*.dart (executable scripts)

Returns a set of absolute file paths.

#_resolve_dart_import

python
def _resolve_dart_import(specifier: str, importing_file: str, project_dir: str, package_name: str | None) -> str | None

Resolve a Dart import specifier to an absolute file path.

Handles:

  • Relative imports: 'src/foo.dart', '../utils.dart'
  • Self-package imports: 'package:mylib/src/foo.dart' -> lib/src/foo.dart

Returns absolute path if resolved, None otherwise. Skips dart: imports and external package: imports.

#_build_dart_import_graph

python
def _build_dart_import_graph(project_dir: str, exclude_dirs: list[str] | None=None) -> dict[str, set[str]]

Build a file-level import graph for a Dart project.

Walks all .dart files, extracts import/export statements via regex, and resolves relative and self-package imports to absolute paths.

Returns a dict mapping each source file's absolute path to a set of absolute paths it imports/exports (only resolved intra-package refs).

#find_dead_dart_modules

python
def find_dead_dart_modules(project_dir: str, exclude_dirs: list[str] | None=None) -> list[str]

Find Dart source files unreachable from any entry point.

A .dart file is "dead" if there is no path through the import/export graph from any entry point (barrel file, bin scripts) to that file.

Test files (test/, *_test.dart) are excluded from the scan.

Args:

  • project_dir: absolute path to the Dart project root.
  • exclude_dirs: directory paths to skip during the walk

(relative to project_dir or absolute).

Returns:

  • sorted list of relative paths of dead source files.

#DeadWorkspacePackage

A workspace package with no workspace importers.

#find_dead_workspace_packages

python
def find_dead_workspace_packages(projects: list[dict], import_cache: dict[str, tuple[set[str], set[str]]]) -> list[DeadWorkspacePackage]

Find library packages that no other workspace package imports.

A library package is "dead" at the workspace level if its name does not appear in any other project's lib_imports or test_imports sets.

Args:

  • projects: list of workspace project dicts (must have "name",

and optionally "library" and "dev_node" keys).

  • import_cache: mapping of project name to (lib_imports, test_imports,

guarded_imports) as produced by _build_dep_import_cache in rlsbl/checks/_common.py.

Returns:

  • list of DeadWorkspacePackage for packages with no workspace importers.

#find_circular_deps

python
def find_circular_deps(import_graph: dict[str, set[str]]) -> list[list[str]]

Find circular dependencies in a file-level import graph using Tarjan's SCC.

Args:

  • import_graph: mapping of file path to the set of file paths it imports.

Returns:

  • list of cycles, where each cycle is a list of file paths forming
  • the strongly connected component. Only SCCs with 2+ nodes are
  • returned (self-loops are not interesting).

#_build_python_import_graph

python
def _build_python_import_graph(project_dir: str, exclude_dirs: list[str] | None=None) -> dict[str, set[str]]

Build a file-level import graph for a Python project.

Uses _collect_python_imports to get dotted module names, then resolves them to file paths via a module-name-to-file mapping.

Returns a dict mapping each source file's relative path to a set of relative paths it imports (only intra-project imports that resolve to actual files).

#find_circular_python_deps

python
def find_circular_python_deps(project_dir: str, exclude_dirs: list[str] | None=None) -> list[list[str]]

Find circular dependencies in a Python project.

Builds a file-level import graph from Python source files and runs Tarjan's SCC algorithm to detect cycles.

Args:

  • project_dir: absolute path to the project root.
  • exclude_dirs: directory paths to skip during the walk.

Returns:

  • list of cycles, each a sorted list of relative file paths.

#find_circular_npm_deps

python
def find_circular_npm_deps(project_dir: str, exclude_dirs: list[str] | None=None) -> list[list[str]]

Find circular dependencies in an npm project.

Reuses _build_npm_import_graph() and runs Tarjan's SCC algorithm.

Args:

  • project_dir: absolute path to the project root.
  • exclude_dirs: directory paths to skip during the walk.

Returns:

  • list of cycles, each a sorted list of relative file paths.

#find_circular_dart_deps

python
def find_circular_dart_deps(project_dir: str, exclude_dirs: list[str] | None=None) -> list[list[str]]

Find circular dependencies in a Dart project.

Builds a file-level import graph from Dart source files using regex to extract relative imports, then runs Tarjan's SCC algorithm.

Args:

  • project_dir: absolute path to the project root.
  • exclude_dirs: directory paths to skip during the walk.

Returns:

  • list of cycles, each a sorted list of relative file paths.

#rlsbl.import_scanners

Python, Dart, npm, Go, Java, and Kotlin import scanners for dependency-import validation.

Filters raw import data to workspace-relevant imports, handles language-specific edge cases, and distinguishes lib/ vs test/ contexts.

#ImportInfo

A single workspace-relevant import detected in a source file.

#_is_test_context

python
def _is_test_context(filepath: str, project_path: str) -> bool

Determine whether a file is in a non-production context.

Uses a layered approach to avoid false positives for production paths that happen to contain directory names like "test":

Layer 1 -- Unconditional directories (match at any depth): __tests__/, testdata/

Layer 2 -- Root-relative directories (match only as first component): test/, tests/, example/, examples/, integration_test/

Layer 3 -- File name patterns (checked against basename): test_.py, _test.py, _test.go, _test.dart, .test.[jt]sx?, .spec.[jt]sx?, conftest.py

#build_namespace_map

python
def build_namespace_map(projects, workspace_root: str) -> dict[str, str]

Map namespace-qualified import paths to workspace project names.

For a project named 'protocols' at 'protocols/src/orxt/protocols/', returns {'orxt.protocols': 'protocols'}.

Algorithm:

  1. For each project, call detect_python_package_root() to get the

package root (e.g., 'src/orxt')

  1. The namespace is the package root's leaf directory name (e.g., 'orxt')
  2. Walk subdirectories of the package root looking for the project's

directory name

  1. If src/orxt/protocols/ exists and project name is 'protocols',

map 'orxt.protocols' -> 'protocols'

#PythonImportScanner

Scan Python source files for workspace-relevant imports.

Uses the AST-based scanner from the lint system, then post-processes to filter out stdlib, relative imports, and non-workspace packages. Supports namespace package detection via namespace_map and import_names.

#scan

python
def scan(self, project_path: str, workspace_names: set[str], exclude_dirs: list[str] | None=None, *, namespace_map: dict[str, str] | None=None, import_names: dict[str, str] | None=None) -> list[ImportInfo]

Scan project_path for Python imports matching workspace members.

Args:

  • project_path: absolute path to the project root.
  • workspace_names: set of workspace member package names

(as they appear in pyproject.toml, e.g. "my-lib").

  • exclude_dirs: directory paths to skip during the walk

(relative to project_path or absolute).

  • namespace_map: mapping of namespace-qualified import paths

to workspace project names (e.g., {'orxt.protocols': 'protocols'}). Built by build_namespace_map().

  • import_names: mapping of project_name -> import_name from workspace

config. Used for explicit import_name overrides.

Returns:

  • list of ImportInfo for imports that match workspace members.

#DartImportScanner

Scan Dart source files for workspace-relevant package imports.

Uses regex to extract package names from import/export statements. Checks for missing generated (.g.dart) files when build_runner is configured.

#scan

python
def scan(self, project_path: str, workspace_names: set[str], exclude_dirs: list[str] | None=None) -> list[ImportInfo]

Scan project_path for Dart imports matching workspace members.

Args:

  • project_path: absolute path to the project root.
  • workspace_names: set of workspace member package names

(as they appear in pubspec.yaml).

  • exclude_dirs: directory paths to skip during the walk

(relative to project_path or absolute).

Returns:

  • list of ImportInfo for imports that match workspace members.

Raises:

  • RuntimeError: if build.yaml exists but no .g.dart files

are found in the project (missing code generation).

#_check_generated_files

python
def _check_generated_files(self, project_path: str) -> None

Raise RuntimeError if build_runner is configured but no .g.dart files exist.

#_extract_npm_bare_name

python
def _extract_npm_bare_name(specifier: str) -> str | None

Extract bare package name from an npm import specifier.

Returns None for relative imports, Node.js builtins, and node:-prefixed builtins. For scoped packages (@scope/pkg/foo), returns @scope/pkg. For unscoped (pkg/foo), returns pkg.

#NpmImportScanner

Scan JS/TS source files for workspace-relevant imports.

Uses the AST-based scanner from the npm lint system, then post-processes to filter out relative imports, Node.js builtins, and non-workspace packages.

#scan

python
def scan(self, project_path: str, workspace_names: set[str], exclude_dirs: list[str] | None=None) -> list[ImportInfo]

Scan project_path for JS/TS imports matching workspace members.

Args:

  • project_path: absolute path to the project root.
  • workspace_names: set of workspace member package names

(as they appear in package.json, e.g. "@scope/my-lib").

  • exclude_dirs: directory paths to skip during the walk

(relative to project_path or absolute).

Returns:

  • list of ImportInfo for imports that match workspace members.

#GoImportScanner

Scan Go source files for workspace-relevant imports.

Uses the tree-sitter-based scanner from the Go lint system, then post-processes to filter to imports matching other workspace projects' Go module paths.

#scan

python
def scan(self, project_path: str, workspace_names: set[str], exclude_dirs: list[str] | None=None, *, module_path_map: dict[str, str] | None=None) -> list[ImportInfo]

Scan project_path for Go imports matching workspace members.

Args:

  • project_path: absolute path to the project root.
  • workspace_names: set of workspace member package names.
  • exclude_dirs: directory paths to skip during the walk

(relative to project_path or absolute).

  • module_path_map: mapping of workspace project name to its

Go module path (from go.mod). Only Go projects appear in this map. Required for Go import detection.

Returns:

  • list of ImportInfo for imports that match workspace members.

#_match_workspace_import

python
def _match_workspace_import(import_path: str, module_to_name: dict[str, str]) -> str | None

Check if an import path belongs to a workspace sibling.

An import matches a workspace module if the import path equals the module path or starts with it followed by '/'.

#build_jvm_package_map

python
def build_jvm_package_map(projects: list, workspace_root: str) -> dict[str, str]

Map Java/Kotlin package prefixes to workspace project names.

For each workspace project with a pom.xml or build.gradle(.kts), reads the groupId (from POM) or group (from Gradle) and maps it to the project name. This allows import scanning to determine which workspace project an import like com.example.foo.Bar belongs to.

Args:

  • projects: list of workspace project dicts/objects with name

and path attributes.

  • workspace_root: absolute path to the workspace root.

Returns:

  • dict mapping dotted package prefix to workspace project name.
  • E.g. {"com.example.foo": "foo-lib"}

#_JvmImportScannerBase

Base class for Java and Kotlin import scanners.

Scans source files for import statements matching workspace projects via a package prefix map. Subclasses specify which file extensions to scan.

#scan

python
def scan(self, project_path: str, workspace_names: set[str], exclude_dirs: list[str] | None=None, *, package_map: dict[str, str] | None=None) -> list[ImportInfo]

Scan project_path for JVM imports matching workspace members.

Args:

  • project_path: absolute path to the project root.
  • workspace_names: set of workspace member package names.
  • exclude_dirs: directory paths to skip during the walk

(relative to project_path or absolute).

  • package_map: mapping of dotted package prefix to workspace

project name. Built by build_jvm_package_map(). Required for JVM import detection.

Returns:

  • list of ImportInfo for imports that match workspace members.

#JavaImportScanner

Scan Java source files for workspace-relevant imports.

Uses regex to extract import statements from .java files, then matches against the workspace package prefix map.

#KotlinImportScanner

Scan Kotlin source files for workspace-relevant imports.

Uses regex to extract import statements from .kt and .kts files, then matches against the workspace package prefix map.