--- description: An alias owns its target (folder index.ts = folder and below, any other file = that file); no overlap; outside an area only the exact alias reaches it; ../ is banned in every file; locally, aliases target only direct src/ subfolders --- ## `#` aliases own their targets Shared code is imported through Node subpath imports declared in `package.json` `imports`. Node, TypeScript, Vite, Vite+'s config bundler, and Oxlint's plugin loader all resolve them natively, so `.ts` source runs unbuilt. `vp pack` inlines them, so they never reach package consumers. One rule for every file, never tied to folder or file names (user rule 2026-10-17; it replaces the earlier "aliases only point at direct `src/` subfolders" rule as the general rule, and the "import-structure plugin checks only aliased folders" scope; this repo keeps the stricter rule as a local convention, see the last section): - **An alias may point anywhere** and owns its target. An alias to a folder's `index.ts` owns that folder and everything below it; an alias to any other file owns only that file. - **No overlap.** No file may be owned by 2 aliases: no alias inside another alias's area, and no 2 aliases with the same target. The import-structure plugin's `enforce-layer-order` reports it once at the top of every file 2 aliases claim. Fix by merging the aliases or moving files apart. - **Outside an area, only the exact alias reaches it.** A deeper alias specifier (`#a/one.ts`, `no-barrel-bypass`) and a relative path into the area (`./a/one.ts`, `no-relative-bypass`; `./b/f.ts` to a file alias, `prefer-alias-import`, fixed to the alias) are bypasses. - **Inside an area, relative imports of siblings and subfolders are free.** - **A `../` import is banned in every file**, owned or not (`no-parent-import`). Runtime file URLs (`new URL("../prompt.md", import.meta.url)`) are not imports and are allowed. Anything above a file needs an alias. - **Files no alias owns** need no `index.ts` and no alias, and import `./` siblings and subfolders freely. - Unchanged: no `/*` subpath patterns, the key order of `imports` is the layer order `enforce-layer-order` checks (add a new alias at the right layer), and entry files are `index.ts` only. ```jsonc "imports": { "#a": "./a/index.ts", // owns a/** "#f": "./b/f.ts", // owns only b/f.ts "#a-sub": "./a/sub/index.ts", // no: inside #a "#a2": "./a/index.ts" // no: same target as #a } ``` ```ts // in main.ts, which no alias owns import { a } from "#a"; // yes import { one } from "#a/one.ts"; // no: deeper alias specifier import { one } from "./a/one.ts"; // no: relative path into #a import { f } from "#f"; // yes import { f } from "./b/f.ts"; // no: relative path to a file alias import { g } from "./b/g.ts"; // yes: unowned // in a/one.ts, owned by #a import { two } from "./two.ts"; // yes: sibling import { deep } from "./sub/deep.ts"; // yes: subfolder of the same area import { x } from "../x.ts"; // no: no ../ anywhere ``` - Aliases point at `.ts` files only. There is no `#package` alias ([[ts-only-files-and-imports]]). - Lint `jsPlugins` entries in `vite.config.ts` stay path strings, even when the file has an alias. - Keep test-only helpers in their own alias (e.g. `#testing`) so they never land in a bundle. ## Barrels - An area used from outside exposes its API through its alias target, and callers import the alias (`#ui`), not files inside it. - Barrels are also the standard fix for `import/max-dependencies`: one barrel import replaces many direct ones ([[lint-overrides-policy]]). - `import/no-cycle` is on; a barrel must not create a cycle. Relative imports are written with the `.ts` extension. ## `scripts/` is flat (user rule, 2026-10-16) - `scripts/` has no subfolders and no `index.ts`. - Each tool is one file, `scripts/.ts`, imported directly by file: `import projectLint from "./scripts/project-lint.ts";` in `vite.config.ts`. No alias owns `scripts/`, so these `./` imports are free. - Code in a former `scripts//` folder is merged or renamed into flat `scripts/.ts` files. Non-TS assets (HTML, images, fixtures) move to where they belong (next to the tool's output, `.agents/`, or a gitignored scratch dir). A subfolder that is really an app moves under an alias. ## Local convention: aliases target direct `src/` subfolders This is a convention of this repo only, stricter than the general rule above (which the published import-structure plugin checks and which allows more). It is not a plugin rule. - Every `package.json` `imports` alias points only at the `index.ts` of a direct subfolder of `src/`, e.g. `"#helpers": "./src/helpers/index.ts"`. No alias to a single file, a deeper folder, or anything outside `src/`. Here the aliases are `#prepare`, `#run`, `#post`, `#agents`, `#processes`, `#runtime`, `#profiles`, `#common`, and `#testing`, in that layer order. - Code outside `src/` (`scripts/`, `tests/`, `vite.config.ts`) gets no alias of its own and imports its own files by `./` relative paths. It reaches `src/` only through the aliases, since `../` is banned. - No lint rule checks this; check `imports` by hand when adding an alias.