Rules Reference
Learn how to use boundaries in rules.
Boundaries are the foundation of Stricture’s architecture enforcement. They define the logical layers, modules, or regions in your codebase.
A boundary represents a group of files with a specific architectural role. For example:
Each boundary is defined by a glob pattern that matches files, plus tags that are referenced in rules.
interface BoundaryDefinition { name: string // Unique boundary name pattern: string // Glob pattern matching files mode: 'file' | 'folder' // How to match files tags?: string[] // Tags for rule matching exclude?: string[] // Patterns to exclude metadata?: { description?: string layer?: number // For layered architectures [key: string]: unknown }}{ "name": "domain", "pattern": "src/domain/**", "mode": "file", "tags": ["domain"]}This matches all files under src/domain/ recursively.
{ "name": "driving-adapters", "pattern": "src/adapters/driving/**", "mode": "file", "tags": ["adapters", "driving"]}Multiple tags allow rules at different specificity levels:
"adapters" match ALL adapters"driving" match ONLY driving adapters{ "name": "domain", "pattern": "src/domain/**", "mode": "file", "tags": ["domain"], "exclude": [ "**/*.test.ts", "**/__mocks__/**" ]}Exclude test files and mocks from the domain boundary.
Stricture uses micromatch for glob patterns.
| Pattern | Matches | Example Files |
|---|---|---|
src/domain/** | All files under src/domain/ recursively | src/domain/user.tssrc/domain/orders/order.ts |
src/domain/*.ts | Only top-level files in src/domain/ | src/domain/user.tsNOT src/domain/orders/order.ts |
**/*.test.ts | All test files anywhere | src/domain/user.test.tstests/integration.test.ts |
src/{domain,ports}/** | Files in multiple directories | src/domain/user.tssrc/ports/repository.ts |
app/**/page.tsx | All Next.js page files | app/page.tsxapp/users/page.tsx |
src/*/index.ts | All index files one level deep | src/domain/index.tsNOT src/domain/orders/index.ts |
{ "name": "controllers", "pattern": "src/controllers/**", "mode": "file", "tags": ["presentation"]}Matches:
src/controllers/user.controller.tssrc/controllers/api/product.controller.tssrc/services/user.service.ts{ "name": "core", "pattern": "src/{domain,ports,application}/**", "mode": "file", "tags": ["core"]}Matches:
src/domain/user.tssrc/ports/repository.tssrc/application/use-case.tssrc/adapters/postgres.ts{ "name": "config-files", "pattern": "**/*.config.{ts,js}", "mode": "file", "tags": ["config"]}Matches:
vite.config.tsjest.config.jssrc/module.config.tssrc/user.ts{ "name": "domain", "pattern": "src/domain/**", "mode": "file", "tags": ["domain"], "exclude": [ "**/*.test.ts", "**/*.spec.ts" ]}Matches:
src/domain/user.tssrc/domain/orders/order.tssrc/domain/user.test.tssrc/domain/user.spec.tsThe mode property determines how files are matched and grouped.
File mode ("mode": "file") matches individual files:
{ "name": "domain", "pattern": "src/domain/**", "mode": "file", "tags": ["domain"]}Use when:
Example:
src/domain/├── user.ts → domain boundary├── order.ts → domain boundary└── product.ts → domain boundaryEach file is independently in the domain boundary.
Folder mode ("mode": "folder") matches entire directories:
{ "name": "user-module", "pattern": "src/modules/user/**", "mode": "folder", "tags": ["module", "user"]}Use when:
Example:
src/modules/├── user/ → user-module boundary (entire folder)│ ├── controller.ts│ ├── service.ts│ └── repository.ts└── product/ → product-module boundary (entire folder) ├── controller.ts ├── service.ts └── repository.tsAll files in user/ are treated as one unit.
%%{init: {'theme':'base', 'themeVariables': {
'primaryColor':'#b3d9ff',
'primaryBorderColor':'#66b3ff',
'secondaryColor':'#b3ffcc',
'secondaryBorderColor':'#66cc99'
}}}%%
graph TB
subgraph FileMode["File Mode"]
F1["domain/user.ts
→ domain boundary"]
F2["domain/order.ts
→ domain boundary"]
F3["ports/repository.ts
→ ports boundary"]
end
subgraph FolderMode["Folder Mode"]
FO1["modules/user/**
→ user-module boundary"]
FO2["modules/product/**
→ product-module boundary"]
end
style FileMode fill:#b3d9ff,stroke:#1976d2,stroke-width:2px
style FolderMode fill:#b3ffcc,stroke:#2e7d32,stroke-width:2px
Tags are labels attached to boundaries for use in rules.
{ "name": "domain", "pattern": "src/domain/**", "mode": "file", "tags": ["domain"]}Rule can target with: { "tag": "domain" }
{ "name": "driving-adapters", "pattern": "src/adapters/driving/**", "mode": "file", "tags": ["adapters", "driving", "primary"]}Rules can target at different levels:
{ "tag": "adapters" } - Matches all adapters (driving + driven){ "tag": "driving" } - Matches only driving adapters{ "tag": "primary" } - Alternative name for driving adapters{ "boundaries": [ { "name": "driving-adapters", "pattern": "src/adapters/driving/**", "tags": ["adapters", "driving"] }, { "name": "driven-adapters", "pattern": "src/adapters/driven/**", "tags": ["adapters", "driven"] } ], "rules": [ { "id": "adapters-to-core", "from": { "tag": "adapters" }, // Matches BOTH driving and driven "to": { "tag": "core" }, "allowed": true }, { "id": "driving-to-driven", "from": { "tag": "driving" }, // Matches ONLY driving adapters "to": { "tag": "driven" }, "allowed": false } ]}Add descriptive metadata for documentation:
{ "name": "domain", "pattern": "src/core/domain/**", "mode": "file", "tags": ["core", "domain"], "metadata": { "description": "Pure business logic - entities, value objects, domain services", "layer": 0, "documentation": "https://wiki.company.com/architecture/domain", "owner": "domain-modeling-team" }}Metadata is ignored by validation but useful for:
{ "boundaries": [ { "name": "domain", "pattern": "src/core/domain/**", "mode": "file", "tags": ["core", "domain"], "metadata": { "description": "Pure business logic", "layer": 0 } }, { "name": "ports", "pattern": "src/core/ports/**", "mode": "file", "tags": ["core", "ports"], "metadata": { "description": "Interface definitions", "layer": 1 } }, { "name": "application", "pattern": "src/core/application/**", "mode": "file", "tags": ["core", "application"], "metadata": { "description": "Use cases", "layer": 2 } }, { "name": "driving-adapters", "pattern": "src/adapters/driving/**", "mode": "file", "tags": ["adapters", "driving"], "metadata": { "description": "Entry points (CLI, HTTP, GraphQL)", "layer": 3 } }, { "name": "driven-adapters", "pattern": "src/adapters/driven/**", "mode": "file", "tags": ["adapters", "driven"], "metadata": { "description": "Implementations (Repositories, APIs)", "layer": 3 } } ]}{ "boundaries": [ { "name": "presentation", "pattern": "src/presentation/**", "mode": "file", "tags": ["presentation"], "metadata": { "description": "Controllers, views, DTOs", "layer": 3 } }, { "name": "business", "pattern": "src/business/**", "mode": "file", "tags": ["business"], "metadata": { "description": "Business logic, services", "layer": 2 } }, { "name": "data", "pattern": "src/data/**", "mode": "file", "tags": ["data"], "metadata": { "description": "Data access, repositories", "layer": 1 } }, { "name": "infrastructure", "pattern": "src/infrastructure/**", "mode": "file", "tags": ["infrastructure"], "metadata": { "description": "Cross-cutting concerns", "layer": 0 } } ]}{ "boundaries": [ { "name": "user-module", "pattern": "src/modules/user/**", "mode": "folder", "tags": ["module", "user"], "metadata": { "description": "User management feature module" } }, { "name": "product-module", "pattern": "src/modules/product/**", "mode": "folder", "tags": ["module", "product"], "metadata": { "description": "Product catalog feature module" } }, { "name": "order-module", "pattern": "src/modules/order/**", "mode": "folder", "tags": ["module", "order"], "metadata": { "description": "Order processing feature module" } }, { "name": "shared", "pattern": "src/shared/**", "mode": "file", "tags": ["shared"], "metadata": { "description": "Shared utilities and types" } } ]}{ "boundaries": [ { "name": "server-components", "pattern": "app/**/{page,layout,loading,error}.tsx", "mode": "file", "tags": ["server", "react"], "exclude": ["**/*.client.tsx"] }, { "name": "client-components", "pattern": "app/**/*.client.tsx", "mode": "file", "tags": ["client", "react"] }, { "name": "server-actions", "pattern": "app/**/actions.ts", "mode": "file", "tags": ["server", "actions"] }, { "name": "api-routes", "pattern": "app/api/**/route.ts", "mode": "file", "tags": ["server", "api"] }, { "name": "components", "pattern": "components/**", "mode": "file", "tags": ["components"] }, { "name": "lib", "pattern": "lib/**", "mode": "file", "tags": ["utils"] } ]}Match files EXCEPT certain ones:
{ "name": "production-code", "pattern": "src/**", "mode": "file", "tags": ["production"], "exclude": [ "**/*.test.ts", "**/*.spec.ts", "**/__tests__/**", "**/__mocks__/**" ]}Match only specific filenames:
{ "name": "composition-root", "pattern": "src/index.ts", "mode": "file", "tags": ["composition-root"]}{ "name": "react-components", "pattern": "src/components/**/*.{tsx,jsx}", "mode": "file", "tags": ["react", "components"]}{ "boundaries": [ { "name": "shared-kernel", "pattern": "packages/shared/**", "mode": "file", "tags": ["shared"] }, { "name": "package-a", "pattern": "packages/package-a/src/**", "mode": "file", "tags": ["package-a"] }, { "name": "package-b", "pattern": "packages/package-b/src/**", "mode": "file", "tags": ["package-b"] } ]}// ✅ GOOD - Specific directory{ "name": "domain", "pattern": "src/core/domain/**"}
// ❌ BAD - Too broad{ "name": "domain", "pattern": "**/domain/**" // Matches test/domain/, docs/domain/, etc.}// ✅ GOOD - Clear, consistent names{ "boundaries": [ { "name": "domain", "tags": ["domain"] }, { "name": "ports", "tags": ["ports"] }, { "name": "application", "tags": ["application"] } ]}
// ❌ BAD - Inconsistent naming{ "boundaries": [ { "name": "dom", "tags": ["business-logic"] }, { "name": "interfaces", "tags": ["ports"] }, { "name": "useCases", "tags": ["app"] } ]}{ "name": "domain", "pattern": "src/domain/**", "mode": "file", "tags": ["domain"], "metadata": { "description": "Pure business logic - NO infrastructure, NO I/O, NO external dependencies", "examples": ["User.ts", "Order.ts", "Money.ts"], "rules": ["Must be testable without mocks", "Immutable where possible"] }}// ❌ PROBLEMATIC - Overlapping patterns{ "boundaries": [ { "name": "adapters", "pattern": "src/adapters/**" // Matches EVERYTHING under adapters/ }, { "name": "driving-adapters", "pattern": "src/adapters/driving/**" // Also matches driving, overlap! } ]}
// ✅ GOOD - Non-overlapping{ "boundaries": [ { "name": "driving-adapters", "pattern": "src/adapters/driving/**", "tags": ["adapters", "driving"] }, { "name": "driven-adapters", "pattern": "src/adapters/driven/**", "tags": ["adapters", "driven"] } ]}Use tags to create logical groupings, not overlapping patterns.
Problem: Rule not triggering for expected files
Debug:
exclude patterns aren’t filtering it outmode is correct (file vs folder)Test your pattern:
# Using glob command line toolnpx glob "src/domain/**" --cwd /path/to/projectProblem: File matches multiple boundaries
Solution: First matching boundary wins. Order matters!
{ "boundaries": [ { "name": "domain-entities", "pattern": "src/domain/entities/**" // More specific, put first }, { "name": "domain", "pattern": "src/domain/**" // More general, put second } ]}Problem: Rule with tag doesn’t match expected boundary
Debug:
tags arraytag (not pattern)// ✅ CORRECT{ "boundaries": [ { "name": "domain", "tags": ["domain"] } ], "rules": [ { "from": { "tag": "domain" } } // Matches boundary tag ]}Rules Reference
Learn how to use boundaries in rules.
Presets Reference
See how presets define boundaries.
Pattern Matching
Deep dive into glob patterns.