Check 1 — Package boundaries
Question: are packages split along real seams, and is each package’s public API respected?
Inputs: workspace.json, scan.json. Output: findings/boundaries.json.
| id | severity | detect | evidence |
|---|---|---|---|
boundaries/oversized-package |
low | scan.packages[p].sourceFiles > 500 or loc > 50000 (generated files are already excluded by scan) and at least 2 other workspace packages import it (scan.imports with target === p). Apps and leaf packages with no workspace consumers are never flagged: splitting them adds export maps and tags with no consumer that benefits. |
counts + the consumers and which subpaths/symbols each uses |
boundaries/deep-import |
medium | each scan.imports entry with deep && (declared || fromSource === "project.json") (undeclared package.json importers are reported by ci-coverage instead) |
file:line + import specifier + target’s exports keys |
boundaries/unlisted-dependency |
medium | knip unlisted entries |
knip output line |
boundaries/unused-dependency |
low | knip dependencies / devDependencies entries |
knip output line |
boundaries/nx-missing-module-boundaries-rule |
medium | Nx detected and no ESLint config in the root mentions @nx/enforce-module-boundaries |
list of ESLint config files checked |
boundaries/nx-permissive-module-boundaries |
low | the rule exists but depConstraints contains { sourceTag: '*', onlyDependOnLibsWithTags: ['*'] } |
the config snippet |
boundaries/nx-untagged-project |
low | Nx detected, rule absent or permissive, and a project has empty tags |
project.json path |
boundaries/turbo-boundaries-violation |
medium | each violation printed by turbo boundaries |
output line |
Group deep-import findings: one finding per (from, target) pair, listing up to 5 locations in evidence; location is the first occurrence.
- Load
workspace.jsonandscan.json. Apply the size and deep-import rules directly. - knip — only if
hasNodeModules:Parse stdout as JSON. Its shape varies by version; look for per-fileTerminal window W="$(node -p 'require("os").tmpdir()')/monorepo-doctor"npx --yes knip@6.41.0 --reporter json --include dependencies,devDependencies,unlisted --no-progress --no-exit-codedependencies,devDependenciesandunlistedarrays of{ name }. Map the file to its package viaworkspace.json. If knip fails or the output can’t be parsed, mark the checkpartialwith reasonknip unavailable: <first line of error>. Do not reportunused-dependencyfor@types/*packages or for tools only referenced in scripts. - Nx — only if Nx is an orchestrator: read ESLint configs in the root (
eslint.config.{js,mjs,cjs,ts},.eslintrc{,.json,.js,.cjs}) and every package dir. Apply the threenx-*rules. A config that contains@nx/enforce-module-boundariesor legacy@nrwl/nx/enforce-module-boundariesanywhere counts as present. Groupnx-untagged-projectconsistently as one finding per untagged project found from the root and package-dir project configs. - Turborepo — only if turbo is an orchestrator and installed:
One finding per reported violation. If the command errors with an unknown-command message (turbo < 2.4), skip this sub-step and note it in
Terminal window W="$(node -p 'require("os").tmpdir()')/monorepo-doctor"TURBO_TELEMETRY_DISABLED=1 npx turbo boundariesreason. - Write
findings/boundaries.json.
Fix guidance
Section titled “Fix guidance”- deep-import: add the subpath to the target’s
exports, or re-export from its entry point, then import from the package root. - oversized-package: identify clusters by which consumers import which files (
scan.importssubpaths); propose 2–3 splits along clusters used by different consumers. If every consumer uses the same parts, don’t emit the finding. - nx-missing-module-boundaries-rule: add
@nx/enforce-module-boundarieswithdepConstraintsbased onscope:*/type:*tags.