Import & placement rules
These govern the dependency graph and where files live, the boundaries that keep a layered architecture from rotting.
denyImports / allowImports
{
name: 'ui-primitive',
files: 'components/ui/**/*.tsx',
denyImports: ['services/**', 'store/**'],
allowImports: ['components/ui/**', 'react'],
}Each import specifier is reduced to a governed path before matching:
| Specifier form | Reduced to | Treated as |
|---|---|---|
relative (./, ../) | resolved, relative to root | internal |
alias @/services/x | services/x | internal |
bare react, lodash | unchanged | external |
denyImportsmatches against both the governed path and the raw specifier, so['axios']catches a package and['services/**']catches a path.allowImportsgoverns internal imports only: an internal import that matches no allow glob is flagged. Bare packages are out of its scope.
Reported as ARCH_DENIED_IMPORT.
The alias
@/is treated as therootprefix out of the box. Configurable aliases are on the roadmap.
belongsHere: the inverse check
Every other rule validates files that are already in the governed folder. belongsHere catches a file that looks governed but lives in the wrong place, the way an agent evades a rule by writing elsewhere.
{
name: 'ui-component',
files: 'components/ui/**/*.tsx',
belongsHere: { exportsType: 'React.FC', importsNone: ['services/**'] },
}A file anywhere in the source universe that exports a React.FC and imports nothing from services/**, but is not under components/ui/, is flagged ARCH_MISPLACED_FILE and told to move.
| Field | Meaning |
|---|---|
exportsType | A top-level export must be annotated with this type (folded through sameType). |
importsNone | …and the file imports none of these globs. |
requires: mandatory siblings & companions
{ name: 'block', files: 'components/block/**/*.tsx', requires: ['index.ts'] }Each matched file must have the named siblings next to it, or ARCH_MISSING_SIBLING.
The token {stem} expands to the file's name before the first dot: the same stem definition naming.files and filename conventions use (orders.module.ts → orders, Button.tsx → Button). That's what dotted-role naming needs, "every feature folder holds its module, controller and service":
{
name: 'feature-module',
files: '*/*.module.ts',
// orders.module.ts → requires orders.controller.ts + orders.service.ts
requires: ['{stem}.controller.ts', '{stem}.service.ts'],
}For a plain (non-dotted) basename {stem} is the whole name, so it also covers the classic "every component has a matching story" case:
{
name: 'ui-component',
files: 'components/ui/**/*.tsx',
exclude: ['**/*.stories.tsx'], // don't apply the rule to the stories themselves
requires: ['{stem}.stories.tsx'], // Button.tsx → requires Button.stories.tsx
}Closing the escape hatch
Combine belongsHere + unmatched: 'error' and an agent cannot create an unchecked file: either it's under a governed glob (and must match that rule) or it's unmatched (an error) or it looks governed but is misplaced (also an error).