Before writing a konsistent.json, explore the codebase to identify structural patterns that are worth enforcing. This guide explains what to look for and how to distinguish real patterns from coincidence.
The objective is not to write a config that produces zero violations on day one. The objective is to encode the conventions the codebase already follows (or wants to follow), even when 100% adherence hasn't happened yet. konsistent will then surface the deviations.
- Start by understanding the top-level directory layout (
ls,tree, or globbing). - Look for parallel directory structures — directories that contain similarly-shaped contents (e.g.,
packages/*,modules/*,providers/*,adapters/*). - Within parallel structures, examine multiple entries (at least 3–5) to see what's consistent and what varies.
- Only codify a pattern if it appears 3 or more times. Two occurrences are a weak signal — note them but don't focus on them unless nothing stronger exists. A single occurrence is never a pattern.
For each parallel directory group, check which files appear in most or all entries:
# See what files each package contains
ls packages/*/src/
ls modules/*/Look for:
- Barrel files (
index.ts) that appear in every entry. - Implementation files whose names follow the directory name (e.g.
packages/foo/src/foo-provider.ts). - Supporting files like
README.md,package.json, config files, test files, story files.
If most directories contain the same set of files, that's a haveFiles pattern.
Open the barrel files (typically index.ts) across multiple parallel directories and compare:
- Do they all export a value whose name derives from the directory name? (e.g.,
packages/openai/src/index.tsexportsopenai.) - Do they all export types following the same naming convention? (e.g.,
FooProvider,BarProvider→${name.toPascalCase()}Provider.) - Do they export factory functions following a pattern? (e.g.,
createFooService,createBarService→create${name.toPascalCase()}Service.) - Do they export constants with fixed names? (e.g., every plugin exports
pluginId.)
For each export you find, classify it:
- Templated: name derives from the directory/file name via a case transformation (these become
${placeholder.toPascalCase()}etc.). - Fixed: same literal name everywhere (these become plain string values like
"activate").
Search for class declarations across parallel structures:
grep -r "export class" adapters/Check:
- Do class names follow a pattern based on the directory or file name? (e.g.,
FooAdapter,BarAdapter.) - Do they extend a common base class? (e.g.,
extends BaseAdapter.) - Do they implement common interfaces? (e.g.,
implements Connectable.)
Search for interface declarations the same way:
- Do interface names follow a pattern? (e.g.,
${name.toPascalCase()}Provider.) - Do they extend a shared base interface? (e.g.,
extends ProviderV1.)
When you find consistent exports, also check the imports:
- Do files consistently import from the same module? (e.g., every provider imports
ProviderV1from@ai-toolkit/core.) - Do type-only imports follow a pattern?
Import checks are most useful when they validate a dependency relationship — every adapter must import its base class from a specific module, not from a local copy.
For exported functions that follow a naming pattern, check their signatures:
- Do they consistently receive a parameter of a specific type? (e.g.,
config: ${name.toPascalCase()}Config.) - Do they consistently return a specific type? (e.g.,
${name.toPascalCase()}Service.)
Check if barrel files re-export from specific submodules:
- Do
index.tsfiles consistently re-export from a file whose name follows the directory name? - Do they re-export from a fixed set of submodules?
This is the from field on export and exportTypes.
Some files exist only in some entries (test files, story files). When they exist, do they follow conventions that the test framework or type checker can't enforce? These become if / for blocks:
- If
.stories.tsxfiles exist, do they always export ametaconstant? (Storybook needs it; the type checker doesn't enforce.) - If a
.test.tsxfile exists, does it import the project's custom render or test-context helper? (Project convention; lint rules don't know about it.) - If a route file exists, does it export the expected HTTP method handlers (
GET,POST, …)?
Avoid encoding rules that the test framework, bundler, or type checker would already reject — those are wasted CPU. Konsistent is for the structural conventions that nothing else catches.
A structural convention must appear at least 3 times to be considered a pattern. This is the minimum threshold.
Patterns may not be adhered to 100% across the codebase — that's exactly what konsistent is for. When you see variation:
- 8 files follow pattern A, 3 follow pattern B → This is ONE pattern (A). The 3 outliers are likely violations that
konsistentshould flag. - 5 files follow pattern A, 4 follow pattern B → Ambiguous. Decide which convention to enforce, or skip it.
- 10 files follow a pattern, 1 doesn't → Strong pattern. The outlier is a violation.
Always align with the majority. Don't try to make the config accommodate every variant — the goal is to identify the intended convention, not to achieve zero errors.
When examining names, look past minor inconsistencies to see the underlying pattern:
createOpenAIProvider,createAnthropicProvider,createMistralProvider→ pattern iscreate${name.toPascalCase()}Provider. The casing variation for acronyms is whatkebabToPascalMapis for.foo-service.ts,bar-service.ts,baz-svc.ts→ the majority says${name}-service.ts; the third is likely a violation.
If a group of directories genuinely has no structural consistency, don't invent conventions. konsistent should encode patterns that actually exist (or should exist), not impose arbitrary structure.
Use this as a systematic walkthrough:
- Map the layout: What are the top-level directories? Which contain parallel entries?
- For each parallel group:
- How many entries are there? (Need 3+ for a pattern.)
- What files does each entry contain? Which are consistent?
- Open 3–5 barrel files (
index.ts). What do they export? Are names templated or fixed? - Search for
export class— any shared base classes or interfaces? - Search for
export interface— any shared base interfaces? - Search for
export functionorexport const ... =with arrow functions — any naming patterns or signature patterns? - Check imports — any consistent dependencies?
- Check for acronyms/special casing: Do any identifiers use acronyms (AI, API, DB, URL) that need
kebabToPascalMaporkebabToCamelMapoverrides? - Prioritize: Start with the strongest patterns (most occurrences, clearest naming). Add weaker patterns only if they still meet the 3+ threshold.
For every pattern you identify:
- Path → glob with placeholders for the parts that vary.
- Required files →
haveFiles. - Local declarations →
declareTypes,declareConstants,declareFunctions,declareInterfaces,declareClasses. - Exports →
export,exportTypes,exportConstants,exportFunctions,exportInterfaces,exportClasses. - Imports →
import,importFrom,importTypes,importFromCurrentDir,importFromParents,importFromExternals,importTypesFromCurrentDir,importTypesFromParents,importTypesFromExternals. - Optional file conditions →
if.hasFileblocks. - Subset-only rules →
if.placeholderSatisfieswith amatchesorsegmentsconstraint.
See examples.md for ready-made templates of each shape, and predicates.md for the full predicate catalog.