import aliases

rule

use ~/* for one package's authored source root: "~/*": ["./src/*"]. relative imports are for immediately neighboring files; package imports cross workspace boundaries.

why

an import's first character discloses its ownership boundary. one alias per package keeps that classification true everywhere, so moving a file or packaging code for independent use cannot hide a dependency behind an overlapping alias or a relative escape. the risk avoided: two paths to the same module that update independently and then diverge.

workflow

  1. configure the alias in the package tsconfig.json.
  2. configure the bundler or test resolver to match it.
  3. use ~/ for package-internal imports and @scope/package for public sibling-package APIs.

why one alias is enough

the alias answers exactly one question: "where is this package's authored source root?" it is not a second package manager or a way to hide a workspace boundary.

import formmeaningprohibited use
./ or ../immediately neighboring implementationclimbing across an architectural area to avoid a real module boundary.
~/authored source owned by this packagereaching into a sibling package.
@scope/packagepublic API of a sibling packageimporting its src, private output, or build internals.

failure mode of overlapping aliases or relative escapes: with both @app/utils and ~/utils pointing to nearby folders, a search for utils returns both. an agent moving formatDate updates one path, creating a duplicate; a later breaking change updates only one path, so one caller serves the old broken version while another serves the fixed one. a codemod or IDE refactor picks one path arbitrarily, silently creating divergence. a relative escape like ../../../services hides a package-boundary crossing — a future agent packaging the code for independent use misses the hidden dependency.

verification

resolve the alias from the compiler, the runtime bundler, and the test runner; all three must point at the same source root. after a workspace move, search for deep cross-package paths — they are a hidden alias that bypasses ownership.

do not

  • point ~ outside the package.
  • import another workspace's src directory.
  • add multiple aliases for the same source root.

good and bad

bad

import { readInvoice } from "@app/services/invoice";
import { InvoiceSchema } from "@shared/schemas/invoice";
import { parseCursor } from "../../../../shared/cursor";

the aliases overlap and the relative import escapes an architectural boundary, so import syntax no longer reveals ownership: the module is reachable through two independent paths that update separately — a fix or rename lands on one while the other keeps serving the old bytes.

good

import { readInvoice } from "~/repositories/billingRepository";
import { InvoiceSchema } from "~/schemas/billing/invoice";
import { parseCursor } from "@scope/base/cursor";

~/ stays inside the package; the scoped import crosses a published workspace boundary. the first character of every import classifies its ownership, so reviewers and agents audit dependency direction without resolving anything — file moves stay safe (the alias travels with the package), workspace extractions stay mechanical, and hidden cross-boundary dependencies cannot appear by accident.

search pages

go to any page