Setup TS deep modules
Contract
| Field | Bound contract |
|---|---|
| Trigger | User asks to enforce package boundaries, set up deep modules, stop deep imports, import only entry points, or mutation-prove that boundary rules catch forbidden imports. |
| Authority | Reversible-local: writes .dependency-cruiser.cjs, package.json, package scripts, and an optional example package and README; refuses to overwrite existing config or touch tsconfig. |
| Side effect | Installs dependency-cruiser as a devDependency; writes .dependency-cruiser.cjs, lint:boundaries script, example package, packages README, and AGENTS.md or CLAUDE.md pointer. |
| Done | lint:boundaries passes on the clean example, fails on an injected deep import, and passes after the deep import is reverted. |
Inputs
Required: a TypeScript monorepo with a packages root (src/packages/ or packages/).
Optional: the preferred packages root if the repo has a different convention.
Procedure
-
Detect the environment. Identify the package manager:
pnpm-lock.yamlleads to pnpm,yarn.lockto yarn,bun.lockorbun.lockbto bun, else npm. Identify the packages root: usesrc/packagesifsrc/exists, elsepackages. Check for an existing.dependency-cruiser.*config file. Done when: package manager, packages root, and existing-config status are recorded. -
Install or verify dependency-cruiser. If
dependency-cruiseris not already a devDependency, add it using the detected package manager. If it is already installed, verify with the package manager's exec command (e.g.,pnpm exec depcruise --version) and record the version. Done when: dependency-cruiser is installed and its version is recorded. -
Write the config. Write
.dependency-cruiser.cjsto the repo root with these five error-level forbidden rules (seereferences/dependency-cruiser.config.cjsfor the full annotated config):entrypoint-boundary-from-app: importers outside any package may not reach package subfolder internals.entrypoint-boundary-across-packages: importers inside a package but outside itstests/folder may not reach another package's subfolder internals; same-package internals remain allowed.tests-through-entrypoints: importers in a package'stests/may reach subfolder internals of any package except their owntests/fixtures.tests-folder-is-private: a package'stests/folder is reachable only from tests.no-circular: no dependency cycles.
Set
PACKAGES_ROOTto the detected root. Use.cjs(not.js) for compatibility with"type": "module"repos. If an existing config is present, merge these rules into it and report what was added. Done when: the config file is written with all five rules. -
Wire the lint script. Add a
lint:boundariesnpm script:depcruise <packages-root>. Fold it into the existing umbrella check command (e.g.,check,ci,validate). Do not touch tsconfig or add path aliases. If no umbrella script exists, addlint:boundariesand instruct the user to include it in CI. Done when: the lint script is added and folded into the umbrella check or the user is instructed. -
Scaffold the example package. Create
<packages-root>/example/containing:index.ts, which exports a function that delegates to an internal file;lib/impl.ts, imported byindex.ts; andtests/example.test.ts, which imports only../indexand asserts on the public function. Mark it as a copy-me template. Done when: the example package exists with all three files. -
Prove the rules bite with mutation verification. Run
lint:boundaries: it must pass. Inject a forbidden deep import: temporarily addimport { thing } from "../lib/impl"totests/example.test.ts. Runlint:boundaries: it must fail with a boundary violation. Revert the deep import. Runlint:boundariesonce more: it must pass. If the injected violation does not trigger a failure, the rules are not wired correctly; fix the config before finishing. Do not leave the proof artifact in the repo. Done when: the pass, fail, pass sequence is confirmed. -
Document the convention. Write
<packages-root>/README.mdcovering: the<name>/layout (entry points at root,lib/for implementation,tests/for tests), the five boundary rules, how to runlint:boundaries, and an explicit warning against barrel files. Add one line to the repo'sCLAUDE.mdorAGENTS.md(create if absent):Packages are deep modules: see [src/packages/README.md](./src/packages/README.md) before adding or importing one.Done when: the README and steering-file pointer are written.
Failure and recovery
| Failure class | Condition | Result |
|---|---|---|
| existing-config | .dependency-cruiser.* already exists | Merge the five forbidden rules and options into it; report what was added. |
| package-manager-unknown | No lockfile detected and no explicit preference | Ask the user which package manager to use. |
| mutation-does-not-fail | Injected deep import does not trigger a lint:boundaries error | Config rules are incorrect; inspect and fix the regex patterns before continuing. |
| example-does-not-pass | Clean example produces lint errors | Package layout or config is wrong; do not proceed until the clean example passes. |
| revert-does-not-pass | lint:boundaries fails after reverting the injected import | The violation artifact was not fully removed; clean up manually and re-run. |
Output
.dependency-cruiser.cjs with five forbidden rules; lint:boundaries script in package.json folded into the umbrella check; <packages-root>/example/ as a starter template; <packages-root>/README.md and AGENTS.md or CLAUDE.md pointer; done predicate confirmed via the pass, fail, pass mutation-verification sequence.