Skip to content

Members

members restricts the names appearing inside a file, in one of two positions: the names written in a declaration, and the names called. It is how a policy states a vocabulary — a set of names a stereotype is allowed to use — where the constraint is not about which modules are reached at all.

"*.repository.ts":
members:
- message: >-
A repository port method named "{name}" is a query in disguise. A port
loads and saves an aggregate by identity; anything keyed on another
column is a read model, and belongs in queries/.
subject: members
declares: [type, interface]
in: "*RepositoryShape"
match: "find*"
matchNot: [findOneById, findManyByIds]
Field Meaning
subject members or calls
declares for members, which declaration kinds: type, interface, class
in for members, globs against the declaration’s own name
match names the rule is about. Omit to mean every name in that position
matchNot names carved back out of match
allow names permitted; anything matched and not allowed is the violation
message why. {name} is substituted with the offending name
probe { source, name } — a snippet the rule must report name out of

match/matchNot narrow what the rule is about; allow says which of those are fine. Either side can carry the exemption — the find* example above uses matchNot, and a hook allowlist below uses allow — and which reads better depends on whether the exceptions are the interesting part.

The names written in a declaration — a type alias, an interface or a class body — with in selecting the declaration by name and declares by kind:

type UserRepositoryShape = {
readonly findOneById: (id: UserId) => Effect<UserRoot>;
readonly findOneByEmail: (email: string) => Effect<UserRoot>; // ← reported
};

A computed key is skipped: it is not a name a vocabulary rule can speak about. Neither is a class’s constructor or a private #name.

declares is what separates a rule about a port from a rule about the adapter that implements it. ["type", "interface"] is the port’s vocabulary; ["class"] reaches the class body; omitting it speaks to every declaration in the file.

Called identifiers and called members — useState(0) and React.useEffect(fn) both produce a site named for the callee:

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

The use[A-Z]* pattern is the reason character classes exist in the glob language: it means a hook, without also matching user or useful.

members reads the members written in a named declaration, under that declaration’s name — a type alias, an interface, or a class, and inside an alias, through intersections, unions and parentheses:

type Base = { save(): void };
type Port = Base & { findOneById(): void }; // Port.findOneById — and Base.save, under Base
interface Other extends Base {
findMany(): void;
} // Other.findMany
class Live implements Port {
findOneById() {} // Live.findOneById, declared as `class`
}

A reference is not followed. Base’s members are declared where Base is and are reported there under its own name; following the reference would report every member twice, under two names. So an in pattern must name the declaration where the member is actually written.

Not read: an anonymous class (export default class {}) or a class expression, a constructor, a private #name, index, call and construct signatures, mapped types, and computed keys.

A rule about a shape the extractor does not read would pass its generated probe while reporting nothing.

An authored probe closes that: the snippet is parsed at load, and the rule must report name out of what the parser read, or the policy refuses to load.

probe:
source: "export type TodosRepositoryShape = { findOneByEmail(): void };"
name: findOneByEmail

Write it in the shape your real ports take. See Probes.

members is an array, and each entry is compiled to its own rule with its own probe. A node that constrains both what it declares and what it calls states both, and a violation names which one fired.