Skip to content

Patterns

Every pattern in a manifest is a glob over a repository-relative path, compiled to a regular expression. Globs rather than regexes because the manifest is meant to read like a filesystem; everything not listed below is literal, including the dots that make up a compound file suffix.

Token Matches
* part of one path segment
** any number of segments
? one character other than /
{name} one segment — a capture in a tree key, a back-reference elsewhere
[A-Z] a character class, passed through as written
a | b several patterns sharing one node (tree keys only)

A trailing /** means “…or nothing”, so @/domain/** matches the folder @/domain as well as everything under it. This is what makes an allowlist entry for a folder read the way you expect rather than silently excluding the folder’s own index file.

A {name} in a tree key declares a capture of one segment. Anywhere below that node it is a back-reference to what the key captured:

"@/modules/{module}/domain/":
imports:
# …the SAME module's domain, not any module's.
allow: ["@/modules/{module}/domain/**"]

Without the capture you would have to write one node per module, or write a rule that lets every module reach every other module’s internals — the two failure modes a capture exists to avoid.

importedBy.allow is matched against the importing file, while the capture was declared by this file’s path. There is no coherent binding between them, so a capture there would compile to an exemption that silently never matched. The compiler rejects it rather than emit it.

The one place a pattern says something about a single character, and the reason is React:

members:
- subject: calls
match: "use[A-Z]*"
allow: [useAtomValue, useAtomSet, useAtomSuspense, useId, useCallback]
message: "`{name}` puts state in the View. It belongs in the ViewModel."

use[A-Z]* is “a hook”useState, useEffect — without also matching user or useful. Classes pass through to the regular expression as written.

A tree key may hold several patterns separated by |, which is how several file stereotypes share one node without repeating its body:

children:
"*.aggregate-ops.ts | *.entity-ops.ts | *.value-object-ops.ts": { use: constituent-ops }

This is a tree-key feature. Inside a pattern used as a value — a match, an allow entry — | is a literal character. Where a value needs several patterns, every field that takes a glob also takes an array of them:

# wrong — matches a file literally named "a.ts | b.ts"
match: "**/*.test.ts | **/*.test.tsx"
# right
match: ["**/*.test.ts", "**/*.test.tsx"]

Every glob in a YAML manifest is quoted. A bare * opens an alias, a bare @ or backtick is reserved, a bare { opens a flow mapping, and # starts a comment — so a pattern left unquoted either fails to parse or means something else. Messages are quoted for the same reason: most start with a backtick or contain a colon.

allow: ["@/domain/**", "**/*.test.ts"] # quoted: `@` and `*`
match: "use[A-Z]*" # quoted: `*`
name: { regex: "^(?:Database|[a-z0-9]+(?:-[a-z0-9]+)*)$" } # single quotes: nothing is escaped

Double quotes process backslash escapes, so a regex with \d or \. goes in single quotes, where nothing is. A capture {name} and a character class [A-Z] are literal in either. Bare words are fine where the value is a plain name — subject: calls, kinds: [default], convention: kebab-case — and a bare yes, no, on or off reads as a string, since the loader speaks YAML 1.2; quote those anyway, for the sake of tools that still speak 1.1.

A multi-line value is a block scalar — | keeps the line breaks, >- folds them — which is how a long message or a probe source avoids string concatenation:

probe:
source: |
import { readFileSync } from "node:fs";
readFileSync("x");
name: readFileSync

Two substitutions are made outside the glob language itself:

  • {name} in a members message is the offending name.
  • {base} in a requires entry is the file’s own name minus its final extension, so "{base}.test.ts" beside user.root.ts means user.root.test.ts.

requires entries also resolve ../ against the node’s own folder, which is how a port in one folder demands an adapter in another.