Predicates are the assertions inside a convention's must or mustNot block. Each predicate checks one structural property of the matched path. Listing multiple predicates in the same must is equivalent to AND — they all must pass. Listing predicates in mustNot reverses the result — a matched path fails when it satisfies one of those predicates.
The full machine-readable schema lives at node_modules/konsistent/konsistent.schema.json.
- Filesystem predicates
- Declaration predicates
- Export predicates
- Import predicates
- Structural predicates
All predicates support template substitutions in their string values — see path-patterns.md for the full case-transformation catalog.
mustNot accepts only the object form:
"mustNot": {
"exportConstants": ["debug"]
}It does not accept MustBlock[] or reusable-convention references.
Assert that the matched path is a file or a directory.
"must": { "haveType": "directory" }"must": { "haveType": "file" }Values: "file" or "directory".
Use this when a glob pattern could match either (e.g., packages/{name} could match a file or directory) and you want to be explicit.
Assert that specific files exist within the matched path. Used with directory paths.
{
"paths": "packages/{providerId}",
"must": {
"haveType": "directory",
"haveFiles": ["src/index.ts", "src/${providerId}-provider.ts"]
}
}For packages/openai, this requires both packages/openai/src/index.ts and packages/openai/src/openai-provider.ts to exist. Templates resolve from the parent path placeholders.
haveFiles paths are relative to the matched directory and may contain forward slashes for nested paths.
Declaration predicates assert local declarations that must not be exported. They mirror the kind-specific export predicates, but expect declarations such as const value = ... or function createValue() {} instead of export const value = ....
All declaration predicates accept an array of bare strings or objects with a name field. The string form is shorthand for { "name": "<value>" }.
Assert local type declarations. Interfaces and type aliases qualify. An object
entry can use schema to validate the local definition using the same supported
shapes and declaration semantics as exportTypes.
"must": {
"declareTypes": [
{
"name": "InternalConfig",
"schema": {
"type": "object",
"properties": { "enabled": { "type": "boolean" } }
}
}
]
}Assert local const declarations. Optionally validate an explicit type
annotation using the same schema field as exportConstants.
"must": {
"declareConstants": [
{ "name": "DEFAULT_PORT", "schema": { "type": "number" } }
]
}Assert local function declarations. Optionally validate parameters and return type, using the same fields as exportFunctions.
"must": {
"declareFunctions": [
{
"name": "createInternalClient",
"receiveParamsOfTypes": ["ClientConfig"],
"returnValueOfType": "Client"
}
]
}Assert local interface declarations. Optionally validate extends, using the same fields as exportInterfaces.
"must": {
"declareInterfaces": [
{ "name": "InternalClient", "extend": "BaseClient" }
]
}Assert local class declarations. Optionally validate extends and implements, using the same fields as exportClasses.
"must": {
"declareClasses": [
{
"name": "InternalClient",
"extend": "BaseClient",
"implement": ["Disposable"]
}
]
}All export predicates accept an array of either:
- A bare string (the expected export name), or
- An object with a
namefield plus optional metadata.
The string form is shorthand for { "name": "<value>" }.
Assert named value exports — anything that is not a type-only export. Functions, classes, constants, and re-exported values all qualify.
"must": { "export": ["myFunction", "${name}"] }"must": {
"export": [
{ "name": "${providerId}", "from": "./${providerId}-provider" }
]
}| Field | Type | Description |
|---|---|---|
name |
string | The export name. Templates allowed. |
from |
string | Optional. If set, the export must come from a re-export pointing at this module specifier. |
When from is omitted, only the export's existence is checked. When from is set, the export must be a re-export from that source — useful for enforcing barrel-file structure.
Assert type-only exports. Exported type aliases and interfaces qualify. Use
from to require a re-export, or use schema to validate a locally defined
exported type.
"must": {
"exportTypes": ["${name.toPascalCase()}Config"]
}"must": {
"exportTypes": [
{
"name": "${providerId.toPascalCase()}Provider",
"from": "./${providerId}-provider"
}
]
}"must": {
"exportTypes": [
{
"name": "ModuleSettings",
"schema": {
"type": "object",
"properties": {
"model": { "type": "string" },
"timeout": { "type": "number" }
}
}
}
]
}| Field | Type | Description |
|---|---|---|
name |
string | The type name. Templates allowed. |
from |
string | Optional. Require a type re-export from this module specifier. |
schema |
object | Optional. Validate a locally declared type definition. |
from and schema are mutually exclusive. Schema validation never resolves a
type definition from another file. The schema field supports the same forms
and declaration semantics documented under exportConstants.
Assert const exports specifically. Stricter than export — a function or let with the right name will not satisfy this predicate. An object entry can use schema to validate the constant's explicit type annotation.
"must": {
"exportConstants": [
"pluginId",
{
"name": "mode",
"schema": {
"type": "string",
"enum": ["development", "production"]
}
},
{
"name": "tags",
"schema": {
"type": "array",
"items": { "type": "string" }
}
},
{
"name": "options",
"schema": {
"type": "object",
"properties": {
"endpoint": { "type": "string" },
"metadata": {}
},
"required": ["endpoint"],
"additionalProperties": false
}
}
]
}| Field | Type | Description |
|---|---|---|
name |
string | The constant name. Templates allowed. |
schema |
object | Optional. Supported JSON Schema subset for the constant's explicit type annotation. |
The supported schema forms are:
- Scalar:
{ "type": "string" }, usingstring,number,boolean, ornull. - Enum:
{ "type": "string", "enum": ["a", "b"] }. The annotation must contain exactly the configured literal values, although their order does not matter. - Array:
{ "type": "array", "items": { "type": "string" } }. Items must use a scalar schema.T[],Array<T>,readonly T[], andReadonlyArray<T>annotations qualify. - Object:
{ "type": "object", "properties": { ... }, "required": [...], "additionalProperties": false }. Every configured property must be declared. An empty property schema ({}) checks its presence and optionality without constraining its type; a scalar schema also checks its type.
Object schemas describe TypeScript declaration shapes rather than ordinary JSON Schema instances. Every name in properties must exist in the annotation or definition. Names listed in required must be non-optional (name: Type); all other configured names must be optional (name?: Type). required defaults to an empty array. additionalProperties defaults to true, allowing unconfigured TypeScript properties; set it to false to reject them.
This is deliberately a strict subset of JSON Schema. Unsupported keywords and shapes are rejected during configuration validation, including integer, $ref, combinators, nested object or array schemas, tuple schemas, enum array items, schema-valued additionalProperties, and constraints such as minItems.
Constant schema checks require an explicit annotation on a locally declared constant. Type schema checks require a local type alias or interface definition; interfaces with extends are unsupported. Inferred types, named type references, tuples, intersections, index signatures, methods, computed properties, nested types, inherited properties, and cross-file re-exports are not resolved. Enum values and property names inside schema are literal data and do not expand placeholders.
Assert function exports. Optionally validate parameters and return type.
"must": {
"exportFunctions": [
{
"name": "create${serviceName.toPascalCase()}Service",
"receiveParamsOfTypes": ["${serviceName.toPascalCase()}Config"],
"returnValueOfType": "${serviceName.toPascalCase()}Service"
}
]
}| Field | Type | Description |
|---|---|---|
name |
string | The function name. Templates allowed. |
receiveParamsOfTypes |
string[] | Optional. Ordered parameter types to enforce by index. Extra function parameters are allowed. |
receiveParamOfType |
string | Deprecated. Optional type at least one parameter must have. Use receiveParamsOfTypes instead. |
returnValueOfType |
string | Optional. Type the return value must have. |
The bare-string form ("exportFunctions": ["myFunction"]) checks existence only.
Assert interface exports. Optionally validate the extends clause.
"must": {
"exportInterfaces": [
{ "name": "${providerId.toPascalCase()}Provider", "extend": "ProviderV1" }
]
}"must": {
"exportInterfaces": [
{
"name": "${providerId.toPascalCase()}Provider",
"extend": { "type": "ProviderV1", "allowOmissions": true }
}
]
}| Field | Type | Description |
|---|---|---|
name |
string | The interface name. Templates allowed. |
extend |
string | { type, allowOmissions? } |
Optional. Base interface the interface must extend. |
When extend is the object form with allowOmissions: true, the interface satisfies the rule even if it extends a generic variant or partial of the base type — useful when implementations omit optional members.
Assert class exports. Optionally validate extends and implements clauses.
"must": {
"exportClasses": [
{
"name": "${adapterName.toPascalCase()}Adapter",
"extend": "BaseAdapter",
"implement": ["Connectable", "Disposable"]
}
]
}| Field | Type | Description |
|---|---|---|
name |
string | The class name. Templates allowed. |
extend |
string | { type, allowOmissions? } |
Optional. Base class the class must extend. |
implement |
array of (string | { type, allowOmissions? }) |
Optional. Interfaces the class must implement. |
allowOmissions works the same as in exportInterfaces — relax the contract when the class extends or implements a generic variant.
Assert named value imports.
"must": {
"import": [
{ "name": "useState", "from": "react" }
]
}| Field | Type | Description |
|---|---|---|
name |
string | The imported binding. Templates allowed. |
from |
string | Optional. Module specifier the import must come from. |
Bare-string form ("import": ["useState"]) checks the binding regardless of source.
Assert that the file has at least one import statement from a specific module specifier.
"must": { "importFrom": "react" }"must": { "importFrom": ["./setup", "package/*"] }The value is a string or an array of strings. When an array is used in must, every entry must be satisfied.
By default, entries match exactly. For example, "package" only matches import ... from "package" and does not match import ... from "package/v4".
Use a trailing /* to match subpaths under a prefix. For example, "package/*" matches "package/v4", and "@scope/pkg/*" matches "@scope/pkg/subpath". The wildcard does not match the root itself, so "package/*" does not match "package".
Both value imports and type-only imports count because this predicate checks import statements by source. Side-effect imports such as import "./setup" also count.
Assert type-only imports (import type { ... } from).
"must": {
"importTypes": [
{ "name": "ProviderV1", "from": "@ai-toolkit/core" }
]
}Same shape as import. Useful for enforcing dependency direction — every adapter's implementation file must import type its base from a specific module.
Assert whether the file has value imports from the current directory via ./....
"must": { "importFromCurrentDir": true }true requires at least one non-type import whose module specifier is "." or starts with "./". false forbids those imports. Type-only imports are ignored. Side-effect imports such as import "./setup" count as value imports.
Assert whether the file has value imports from parent directories via ../....
"must": { "importFromParents": false }true requires at least one non-type import whose module specifier is ".." or starts with "../". false forbids those imports. Type-only imports are ignored.
Assert whether the file has value imports from external module specifiers.
"must": { "importFromExternals": true }true requires at least one non-type import that is not ./... or ../.... false forbids those imports. Scoped packages, node: modules, and unresolved path aliases such as @/utils count as external because konsistent does not resolve module aliases. Type-only imports are ignored.
Assert whether the file has type-only imports from the current directory via ./....
"must": { "importTypesFromCurrentDir": true }true requires at least one type import whose module specifier is "." or starts with "./". false forbids those imports. Value imports are ignored.
Assert whether the file has type-only imports from parent directories via ../....
"must": { "importTypesFromParents": false }true requires at least one type import whose module specifier is ".." or starts with "../". false forbids those imports. Value imports are ignored.
Assert whether the file has type-only imports from external module specifiers.
"must": { "importTypesFromExternals": true }true requires at least one type import that is not ./... or ../.... false forbids those imports. Scoped packages, node: modules, and unresolved path aliases such as @/utils count as external because konsistent does not resolve module aliases. Value imports are ignored.
Assert relative order for selected symbols.
"must": {
"useDeclarationOrder": ["schema", "parseInput", "formatOutput"]
}The configured array is the expected order. Only symbols that are present in the file are checked; missing symbols do not produce diagnostics. Local declarations and named exports/re-exports are considered. Default exports and export * from ... are ignored.
Assert that the matched files are pure barrel files: every top-level statement must be a re-export of another module, not a local declaration.
{
"paths": "src/{moduleName}",
"must": [
{
"for": { "files": "index.ts" },
"must": { "areBarrelFiles": true }
}
]
}A file passes when every top-level statement is one of:
- An
importdeclaration of any form, including bare side-effect imports (import "./polyfill"). - A re-export with a module specifier:
export * from "./x",export * as ns from "./x",export { a, b as c } from "./x",export { default } from "./x",export { default as Foo } from "./x". export { a, b as c }where every specifier references an identifier brought in by animportstatement in the same file. Aliasing (as) is allowed because it is a pure re-export.export default <Identifier>where the identifier was brought in by animportstatement in the same file.
Every other top-level construct produces a diagnostic:
| Violation kind | Example | Message |
|---|---|---|
declaration |
const x = 1;, function f() {}, class C {}, enum E {}, type T = ..., interface I {} |
Barrel file must not contain declarations |
expression |
doSomething(); |
Barrel file must not contain top-level expression statements |
default-expression |
export default { a: 1 };, export default 42; |
Barrel file default export must re-export an imported identifier |
named-export-local |
const x = 1; export { x }; |
Barrel file must only re-export imported identifiers |
export-equals |
export = x; |
Barrel file must not use `export =` |
The predicate takes a bare boolean (true enables, false/omitted disables). It does not enforce a filename — pair it with a for block or a paths pattern that targets the files you consider barrels.
Multiple predicates in the same must are AND-ed:
"must": {
"haveType": "file",
"export": ["createService"],
"exportTypes": ["ServiceConfig"],
"importTypes": [{ "name": "ServiceBase", "from": "../base" }]
}For OR-style logic (apply different predicates to different files), use conditional rules — the array form of must with if/for blocks.
Note that reusable conventions (those published via @konsistent/convention and consumed via conventionSources) are restricted to flat object-form must and mustNot predicates — the MustBlock[] form is unavailable on the author side. Hand-written conventions in your own konsistent.json can still use MustBlock[] in must. See reusable-conventions.md.