Graph
Every other family judges one file, or one edge leaving it. graph judges the whole
repository resolved at once: whether two files import each other, whether anything imports
a file at all, and whether one tier can reach another through any number of intermediate
hops. No single file can answer those, and a rule about one edge cannot: a barrel hop
between an adapter and a live implementation satisfies every per-edge allowlist and still
puts the adapter one import away from the thing it was never supposed to touch.
graph: cycles: - name: no-cycles message: "These files import each other, directly or through others." within: "packages/*/src/**" withinNot: "**/*.test.ts" orphans: - name: no-dead-modules message: "Nothing imports this file. Register it, or delete it." within: "packages/*/src/**" withinNot: "**/*.test.ts" entry: ["packages/*/src/index.ts", "packages/*/src/main.ts"] reach: - name: adapters-through-ports message: "An adapter reaches infrastructure only through a port." from: "packages/*/src/adapters/**" to: "packages/*/src/infrastructure/**" via: "packages/*/src/ports/**"graph is a top-level section, not a node: its rules are statements about the repository,
and every glob in it is expanded through aliases like the rest of the manifest.
cycles
Section titled “cycles”A strongly connected component — two or more files that can each reach the other, or a file
that imports itself — among the files within (and not withinNot). Each component is
reported once, and the violation’s subject is the component as a sorted set, so its
baseline entry survives an edge added
or removed elsewhere in the same cycle.
A cycle that runs through a file outside the scope is not seen: withinNot: "**/*.test.ts"
means a test importing the module it tests, which imports a helper the test also imports, is
not a cycle the rule speaks to.
orphans
Section titled “orphans”A file within scope that nothing imports and that is not an entry. An importer counts
from anywhere — a fake imported only by a test is not an orphan, because the test imports it.
The files that are imported by nothing by design are the program’s entry points, and
entry is where a policy lists them.
A file matching from that can reach a file matching to through any number of hops
without passing through a via file. via is the tier that was supposed to mediate: a
path that steps onto one is fine, and only a path that avoids every via is the violation.
Omit via for a plain “must never reach, by any route”.
A violation is reported per origin and target, and the route that reached it is appended to the message:
packages/server/src/adapters/http/todos.ts [adapters-through-ports] An adapter reaches infrastructure only through a port. (route: packages/server/src/adapters/http/todos.ts → packages/server/src/shared/db.ts → packages/server/src/infrastructure/pg-live.ts)The route is in the message and not the subject, so the fingerprint is (origin, target)
and survives the route changing underneath it.
What the graph is
Section titled “What the graph is”Every file the CLI walked, and for each the walked files it resolves to. An external package,
a builtin, a file outside the walked roots, and an edge nobody can resolve are not in it —
the first three are not the repository’s shape, and the last is reported by
check on its own. A type-only import is
an edge like any other: it is still a dependency between the two files.
CLI only
Section titled “CLI only”Graph rules are evaluated by architecture check and not by the oxlint plugin. The plugin
sees one file at a time, with no view of the others, and cannot answer “does anything import
this?”. Both adapters compile and probe
the rules at load — a via that covers its own target fails oxlint too — but only the CLI
has the graph to evaluate them against. See
the CLI for what that means for
how the two are run.