@morriconeluca/eslint-plugin-faf 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,66 +1,63 @@
1
1
  # @morriconeluca/eslint-plugin-faf
2
2
 
3
- An ESLint plugin to deterministically enforce the rules and constraints of the **[Fractal Architecture Framework (FAF)](https://github.com/morriconeluca/fractal-architecture-framework)**.
3
+ Official ESLint plugin for validating codebases against the **Fractal Architecture Framework (FAF)** specification.
4
4
 
5
- This plugin is designed for complex TypeScript/JavaScript codebases to ensure **predictability**, **testability**, and strict **isolation** of dependency flows. It is fully compatible with modern ESLint flat configurations (ESLint v9 and v10).
5
+ `eslint-plugin-faf` enforces strict structural, classification, symbol, dependency, and acyclicity invariants across codebases using deterministic static analysis.
6
6
 
7
7
  ---
8
8
 
9
- ## Features
9
+ ## Requirements
10
10
 
11
- - **Strict Enforcing of FAF Taxonomy**: Ensures your folder structure strictly maps to Layers, Categories, Fragments, and Fractal Branches.
12
- - **Law of Separation between Peers**: Prevents lateral dependency coupling between sibling modules without explicit hierarchy.
13
- - **Access Node Encapsulation**: Guarantees that internal Fragment files are never imported directly, enforcing consumption solely through the Fragment's barrel file (`index.ts`).
14
- - **High-Performance Architecture**: Features optimized caching (`dirCache`, `classifyCache`, `ancestorChainCache`, `rolesCache`) to minimize disk I/O during linting runs.
11
+ - **Node.js**: `>= 24.0.0` (required for native TypeScript type stripping)
12
+ - **ESLint**: `^9.0.0 || ^10.0.0` (flat configuration)
15
13
 
16
14
  ---
17
15
 
18
- ## Companion Tooling
19
-
20
- `eslint-plugin-faf` does not detect dependency cycles. FAF requires the dependency graph to be a Directed Acyclic Graph, but enforcing that is intentionally left to dedicated tools, configured separately in your project:
16
+ ## Installation
21
17
 
22
- - [`eslint-plugin-import-x`](https://github.com/un-ts/eslint-plugin-import-x)'s `import-x/no-cycle` rule.
23
- - [`dependency-cruiser`](https://github.com/sverweij/dependency-cruiser), for cycle detection and other custom dependency fitness functions.
18
+ ```bash
19
+ npm install --save-dev @morriconeluca/eslint-plugin-faf eslint
20
+ ```
24
21
 
25
22
  ---
26
23
 
27
- ## Installation
24
+ ## ESLint Flat Configuration
28
25
 
29
- Install the plugin along with its peer dependencies:
26
+ `eslint-plugin-faf` exposes a single, unified, zero-option rule: `faf/conformity`. FAF architectural constraints are non-negotiable and conjunctive: a codebase is either conformant or non-conformant. Granular rule toggles and severity demotions are not supported.
30
27
 
31
- ```bash
32
- npm install @morriconeluca/eslint-plugin-faf --save-dev
33
- # or
34
- yarn add @morriconeluca/eslint-plugin-faf --dev
35
- # or
36
- pnpm add @morriconeluca/eslint-plugin-faf -D
37
- ```
28
+ ### Recommended Setup
38
29
 
39
- ---
30
+ Add the plugin to your `eslint.config.js` or `eslint.config.ts`:
40
31
 
41
- ## Configuration
32
+ ```javascript
33
+ import faf from '@morriconeluca/eslint-plugin-faf';
42
34
 
43
- `@morriconeluca/eslint-plugin-faf` is configured via the ESLint flat config file (`eslint.config.ts` or `eslint.config.js`). All architectural settings are loaded from the global `settings.faf` object.
35
+ export default [
36
+ faf.configs.recommended,
37
+ // ...your other flat configs
38
+ ];
39
+ ```
44
40
 
45
- ### 1. Integration in `eslint.config.ts`
41
+ The `recommended` configuration automatically:
46
42
 
47
- For a clean setup, we recommend defining your FAF taxonomy settings in a separate file (e.g. `faf.config.ts`) and importing it into your ESLint configuration.
43
+ - Registers the `faf` plugin.
44
+ - Targets all lintable script and module extensions (`.js`, `.jsx`, `.mjs`, `.cjs`, `.ts`, `.tsx`, `.mts`, `.cts`).
45
+ - Enables `faf/conformity` with severity `error` (2).
48
46
 
49
- Since FAF is an **all-or-nothing architectural system**, all rules must be active as blocking errors to prevent architectural drift. The canonical way to integrate it is by extending `fafPlugin.configs.recommended` (which pre-configures all rules as `"error"`) and injecting your settings:
47
+ ### Manual Configuration
50
48
 
51
- ```typescript
52
- import fafPlugin from '@morriconeluca/eslint-plugin-faf';
53
- import fafSettings from './faf.config';
49
+ If you prefer to configure rules manually:
54
50
 
55
- export default [
56
- // 1. Extend the recommended FAF configuration (registers the plugin and enables all rules as errors)
57
- fafPlugin.configs.recommended,
51
+ ```javascript
52
+ import faf from '@morriconeluca/eslint-plugin-faf';
58
53
 
59
- // 2. Inject FAF taxonomy settings
54
+ export default [
60
55
  {
61
- files: ['**/*.{js,cjs,ts,cts,tsx}'],
62
- settings: {
63
- ...fafSettings,
56
+ plugins: {
57
+ faf,
58
+ },
59
+ rules: {
60
+ 'faf/conformity': 'error',
64
61
  },
65
62
  },
66
63
  ];
@@ -68,329 +65,255 @@ export default [
68
65
 
69
66
  ---
70
67
 
71
- ## Anatomy of `faf.config.ts`
68
+ ## The Configuration Contract: `fafconfig.ts`
72
69
 
73
- Here is a comprehensive configuration example mapping a typical project structure:
70
+ Every project governed by `eslint-plugin-faf` requires a configuration module named `fafconfig.ts` located at the root of the project.
71
+
72
+ ### Example Configuration
74
73
 
75
74
  ```typescript
75
+ import type { TFAFConfig } from '@morriconeluca/eslint-plugin-faf';
76
+
76
77
  export default {
77
78
  faf: {
78
- // 1. Path aliases mapping (resolves absolute imports)
79
- aliases: {
80
- '#/_apis@shared': 'src/_src@shared/_network/_apis/_apis@shared',
81
- '#/_app@shared': 'src/app/_app@shared',
82
- '#/_src@shared': 'src/_src@shared',
83
- },
84
-
85
- // 2. Tree configurations
86
- trees: [
79
+ locals: [
87
80
  {
88
- // Root path to apply these rules to
89
- includes: ['src'],
90
-
91
- // Paths to completely exclude from FAF validation (Foreign Domains)
92
- excludes: ['src/configs'],
93
-
94
- // Definition of organizational Categories (folders starting with "_")
95
- categories: [
96
- // name: Name of the folder
97
- // role: Associated architectural role
98
- // allowSingleFiles: Allow files (Logical Nodes) directly in the folder (defaults to false)
99
- // allowedExtensions: Limit file types for static assets (disables role suffix check)
100
- { name: '_apis', role: 'api' },
101
- { name: '_components', role: 'component' },
102
- { name: '_utils', role: 'util' },
103
- { name: '_hooks', role: 'hook' },
104
- { name: '_layouts', role: 'layout' },
105
- { name: '_pages', role: 'page' },
106
- { name: '_routes', role: 'route' },
107
- { name: '_recipes', role: 'recipe' },
108
- { name: '_classes', role: 'class' },
109
- { name: '_contexts', role: 'context' },
110
- { name: '_stores', role: 'store' },
111
- { name: '_factories', role: 'factory' },
112
- { name: '_instances', role: 'instance' },
113
- { name: '_boundaries', role: 'boundary' },
114
-
115
- // Categories that allow direct logical files (Logical Nodes)
116
- { allowSingleFiles: true, name: '_constants', role: 'constant' },
117
- { allowSingleFiles: true, name: '_types', role: 'type' },
118
- { allowSingleFiles: true, name: '_schemas', role: 'schema' },
119
- { allowSingleFiles: true, name: '_mocks', role: 'mock' },
120
-
121
- // Asset-only categories (no logical role required)
122
- { allowedExtensions: ['.css'], name: '_styles' },
123
- { allowedExtensions: ['.ttf', '.woff', '.woff2'], name: '_fonts' },
124
- {
125
- allowedExtensions: ['.png', '.jpg', '.jpeg', '.svg'],
126
- name: '_images',
127
- },
128
- ],
129
-
130
- // Global horizontal hierarchies: flow rules for peer imports
131
- globalHorizontalHierarchies: [
132
- [
133
- ['_atoms'],
134
- ['_molecules'],
135
- ['_organisms'],
136
- ['_templates'],
137
- ['_pages'],
138
- ],
139
- [
140
- ['_primitives'],
141
- ['_compounds'],
142
- ['_aggregates'],
143
- ['_systems'],
144
- ['_ecosystems'],
145
- ],
146
- ],
147
-
148
- // Local horizontal hierarchies: flow rules restricted to specific paths
149
- // Imports flow from left to right (elements on the right can import from the left)
150
- localHorizontalHierarchies: [
81
+ roles: [
151
82
  {
152
- hierarchies: [['_domain'], ['_network', '_state', '_ui']],
153
- paths: ['src/_src@shared'],
83
+ name: 'constant',
84
+ category: '_constants',
85
+ isCategoryNode: true,
86
+ allowMultipleSymbols: true,
154
87
  },
155
88
  {
156
- hierarchies: [['_enums'], ['_dtos']],
157
- paths: ['src/_src@shared/_domain/_schemas'],
89
+ name: 'type',
90
+ category: '_types',
91
+ isCategoryNode: true,
92
+ allowMultipleSymbols: true,
158
93
  },
159
94
  {
160
- hierarchies: [['_types'], ['_classes'], ['_utils'], ['_apis']],
161
- paths: ['src/_src@shared/_network'],
95
+ name: 'mock',
96
+ category: '_mocks',
97
+ isCategoryNode: true,
98
+ allowMultipleSymbols: true,
162
99
  },
163
100
  {
164
- hierarchies: [
165
- ['_constants'],
166
- ['_types'],
167
- ['_utils'],
168
- ['_recipes'],
169
- ['_components'],
170
- ['_pages'],
171
- ],
172
- paths: ['src/_src@shared/_ui'],
101
+ name: 'config',
102
+ category: '_configs',
103
+ isCategoryNode: true,
104
+ allowMultipleSymbols: true,
173
105
  },
106
+ { name: 'util', category: '_utils' },
107
+ { name: 'service', category: '_services' },
108
+ { name: 'component', category: '_components' },
174
109
  ],
175
-
176
- // Suffix hierarchy role scale for files inside a Fragment.
177
- // Files with roles at higher indices can import from roles at lower indices.
178
- roles: [
179
- ['constant'],
180
- ['schema'],
181
- ['type'],
182
- ['class'],
183
- ['config'],
184
- ['mock'],
185
- ['util'],
186
- ['style'],
187
- ['recipe'],
188
- ['factory'],
189
- ['instance'],
190
- ['hook'],
110
+ subGraphRoles: [{ name: 'spec' }],
111
+ horizontalHierarchies: [
191
112
  [
192
- 'api',
193
- 'boundary',
194
- 'component',
195
- 'context',
196
- 'layout',
197
- 'page',
198
- 'route',
199
- 'store',
113
+ ['_constants'],
114
+ ['_types'],
115
+ ['_mocks'],
116
+ ['_configs'],
117
+ ['_utils'],
118
+ ['_services'],
119
+ ['_components'],
200
120
  ],
201
- ['index', 'spec', 'story'],
202
- ],
203
-
204
- // Software entry points (Root Fragments) and their dependency rules
205
- rootFragments: [
206
- {
207
- paths: ['src'],
208
- // rootNodes specifies permitted layers of execution:
209
- // main.tsx (index 1) can import main.css and app/app.tsx (index 0), but not vice-versa
210
- rootNodes: [['main.css', 'app/app.tsx'], ['main.tsx']],
211
- subRootFragments: [
212
- {
213
- paths: ['src/app'],
214
- rootNodes: [['app.tsx']],
215
- },
216
- ],
217
- },
218
- ],
219
-
220
- // Route-based structure (HTTP APIs or Pages)
221
- routeHierarchies: [
222
- {
223
- // Subtree where intermediate folders are classified as organizational Layers,
224
- // and the terminal Fragment must contain a Master Node with the specified role (e.g. '.api.ts')
225
- // and a name following the "<method>-<object>" pattern (e.g. 'get-todo')
226
- paths: ['src/_src@shared/_network/_apis'],
227
- role: 'api',
228
- // Optional: restricts which HTTP methods are valid as the Fragment name prefix.
229
- // Defaults to all standard HTTP methods (get, post, put, patch, delete, head, options, connect, trace).
230
- httpMethods: ['get', 'post', 'put', 'patch', 'delete'],
231
- },
232
121
  ],
122
+ rootFragments: [{ path: 'src', rootNodes: [['main.ts']] }],
123
+ publicRootNodes: ['src/main.ts'],
124
+ externalDependencies: [{ from: ['src/main.ts'], to: ['package.json'] }],
233
125
  },
234
126
  ],
235
127
  },
236
- };
128
+ } satisfies TFAFConfig;
237
129
  ```
238
130
 
239
- ---
131
+ ### Configuration Structure: `common` and `locals`
132
+
133
+ The configuration object defines:
240
134
 
241
- ## Configuration Settings Reference
135
+ - **`locals`** (_required_, non-empty array): Declarations specific to each Fractal Tree within the workspace. Each element must designate either a `rootFragments` array or a `rootLayers` array defining tree origins.
136
+ - **`common`** (_optional_): Shared declarations (such as roles, sub-graph roles, foreign families, or aliases) inherited by all local trees unless explicitly overridden.
137
+ - **Whole-property replacement**: When a local tree defines a property that also exists in `common`, the local property completely replaces the common definition without shallow or deep merging. Defaults apply strictly after replacement.
242
138
 
243
- ### `aliases`
139
+ ### Native Type-Stripping and Execution Constraints
244
140
 
245
- Maps import aliases (e.g., `#/_src@shared`) to relative paths from the project root. This is critical for the linter to trace boundaries and verify import encapsulation.
141
+ `fafconfig.ts` is evaluated synchronously within an isolated Node.js worker thread leveraging Node's native type stripping. Because native type stripping strips type annotations without full AST transformation, configuration files must adhere to the following rules:
246
142
 
247
- **Single Source of Truth**: By default, `eslint-plugin-faf` automatically resolves your path aliases using the `"imports"` field in your `package.json`. If `"imports"` contains any keys mapped to flat path strings, it is treated as the exclusive source of truth, and any `aliases` configured in `faf.config.ts` will be ignored. If `"imports"` is not defined, empty, or consists only of conditional import objects (which are skipped by the linter for simplicity), the plugin falls back to using the `aliases` object defined in `faf.config.ts`.
143
+ 1. **Explicit File Extensions on Relative Value Imports**:
144
+ Any relative value import inside `fafconfig.ts` or helper modules must specify an explicit extension (for example, `./roles.js` or `./roles.ts`). Type-only imports (`import type ...`) are erased during stripping and are exempt.
145
+ 2. **Prohibition of Non-Strippable TypeScript Constructs**:
146
+ `enum`, runtime `namespace` declarations with executable values, and constructor parameter properties are forbidden in `fafconfig.ts` and its imported dependencies. Use union types and standard property definitions.
147
+ 3. **Module Packaging in `package.json`**:
148
+ The nearest `package.json` must not declare `"type": "commonjs"`. It should specify `"type": "module"` or omit the field.
149
+ 4. **Synchronous, Clonable Default Export**:
150
+ `fafconfig.ts` must export its configuration as a synchronous `export default`. It cannot be a function, cannot return a `Promise` (no top-level asynchronous configuration), and must produce a plain data object serializable via `structuredClone`.
248
151
 
249
- _Note: If your codebase uses conditional imports in `package.json` (e.g. mapping aliases to objects for ESM/CJS or types/default targets), you must define their flat equivalents in `faf.config.ts` `aliases` to ensure correct linting._
152
+ ### Configuration Discovery and Automatic Invalidation
250
153
 
251
- ### `excludes`
154
+ - **Discovery**: The plugin inspects `settings.faf.configFile` if provided in ESLint settings. If unset, it searches upward from the linter's current working directory for `fafconfig.ts`. If not found, it reports `configuration/missing-config-file` on all linted files.
155
+ - **Freshness & Invalidation**: During worker evaluation, the loader tracks all local files loaded by `fafconfig.ts`. On subsequent lint passes, the plugin compares `mtimeMs` and `size` for all tracked files. If any file changes, the configuration cache is immediately invalidated and reloaded in a fresh worker without restarting your editor or language server.
252
156
 
253
- Array of path strings relative to the project root. Any folder matching these paths (or their subfolders) will be completely ignored by the linter.
157
+ ---
254
158
 
255
- ### `categories`
159
+ ## Diagnostic Outcomes & Enforcement Model
256
160
 
257
- Configures taxomonic categories (`_` prefixed directories).
161
+ ### Uniform Blocking Severity
258
162
 
259
- - `role`: Maps the folder to an architectural role. It removes language plural heuristics (e.g., mapping `_components` to the `component` role suffix).
260
- - `allowSingleFiles`: Set to `true` to allow single files (`Logical Nodes`) directly in the category. If `false` or omitted, only subfolder `Fragments` are permitted, except for static assets matching the `allowedExtensions` whitelist.
261
- - `allowedExtensions`: Whitelists file extensions for asset categories. Matches are treated as static assets, skipping role suffix validation (e.g. `logo.png` inside `_images`).
163
+ In `eslint-plugin-faf`, all diagnostic outcomes have severity **`error` (2)**. There are no warnings, hints, or demoted diagnostics.
262
164
 
263
- ### `localHorizontalHierarchies` / `globalHorizontalHierarchies`
165
+ ### The 6 Outcome Families
264
166
 
265
- Determines horizontal import flow (Peer Separation). Each hierarchy is an array of string arrays.
167
+ Diagnostics belong to one of six discriminated outcome families, corresponding to message identifiers in the ESLint rule:
266
168
 
267
- - Sub-folders at index `i` can import from sub-folders at index `j` only if `j < i`.
268
- - Peer folders at the same index (e.g. `['_network', '_state', '_ui']`) cannot import from each other.
169
+ 1. **`configuration`**: Errors in `fafconfig.ts` loading, package configuration, schema validation, literal path grammar, foreign glob syntax, or declaration inconsistencies.
170
+ 2. **`classification`**: Violations of the FAF classification model, including containment matrix violations, fragment anatomy defects, missing master or access nodes, invalid horizontal hierarchies, and nomenclature non-conformities.
171
+ 3. **`symbol`**: Root-scope symbol violations, such as exceeding the single-symbol rule in strict nodes, missing master exports in access nodes, or illegal re-export of imported symbols.
172
+ 4. **`dependency`**: Prohibited edges or paths in the Dependency Graph, including peer separation violations, fragment encapsulation bypasses, private category or fractal branch leakage, tree opacity breaches, and unauthorized external or cross-tree dependencies.
173
+ 5. **`acyclicity`**: Cycle violations detected in the logical module dependency DAG or across the inter-tree authorization DAG.
174
+ 6. **`unverified`**: Incomplete checks that static analysis cannot conclusively prove (such as encountering symbolic links inside trees, unreadable origins or files, non-literal dynamic imports, unsupported syntax, or unexercised reciprocal cyclic authorizations).
269
175
 
270
- ---
176
+ ### Diagnostic Message Format
271
177
 
272
- ## Rules Reference
178
+ Every diagnostic outcome adheres to the canonical structure:
273
179
 
274
- ### 1. `faf/naming-conventions`
180
+ ```text
181
+ {{constraint}}: {{message}} ({{reference}}).
182
+ ```
275
183
 
276
- Verifies pure lexical FAF naming conventions:
184
+ For example:
277
185
 
278
- - Enforces `kebab-case` naming for all folders and files in the logical domain.
279
- - Requires an underscore prefix for Layers/Categories/Sub-Categories, and forbids it for Fragments/Sub-Fragments/Root Fragments.
280
- - Validates that a Fractal Branch's name matches its parent scope (`_<Scope>@shared`).
281
- - Restricts a Logical Node's name to a single Role segment, whether it is a Fragment Node or a loose file inside a Category.
282
- - For loose files inside a Category: validates the file extension against the category's whitelist, requires a role suffix on code files, and validates that the role matches the Category's configured role.
186
+ ```text
187
+ dependency/fragment-encapsulation: 'src/main.ts' imports 'src/_components/button/button.component.ts' through './_components/button/button.component.js': a Fragment is importable from outside only through its index.ts (FAF §6.1.1).
188
+ ```
283
189
 
284
- ### 2. `faf/logical-domain-placement`
190
+ ### Deterministic Outcome Anchoring
285
191
 
286
- Enforces FAF topological placement constraints between Logical Domains:
192
+ Because ESLint can only report diagnostics on files it is currently visiting, the plugin deterministically anchors outcomes to specific files:
287
193
 
288
- - Root Fragments can only reside directly in the Root Container or another Root Fragment.
289
- - Layers cannot reside inside Fragments, and cannot contain files or Fragments directly, except the terminal Layer of a configured Route Hierarchy.
290
- - Fragments cannot be placed directly inside other Fragments (they must be nested within a Private Category), Layers (unless terminating a Route Hierarchy), Fractal Branches, or nested Root Fragments.
291
- - Fractal Branches cannot be nested inside other Fractal Branches, and cannot contain files directly.
194
+ - **Module-Level Outcomes**: Reported directly on the affected TypeScript/JavaScript file at the exact statement or import declaration location.
195
+ - **Domain-Level & Missing-File Outcomes**: If an outcome pertains to an entire directory, a missing mandatory file (such as a missing `index.ts`), or a non-lintable asset, it is deterministically anchored to the first lintable file in lexicographical order among direct children (anchoring to the Master Node if present). If no direct child is lintable, it ascends to the nearest ancestor domain containing a lintable file.
196
+ - **Global Configuration Outcomes**: Global format errors or catalog conflicts are anchored to `fafconfig.ts`.
197
+ - **Missing Configuration File**: If `fafconfig.ts` cannot be discovered, `configuration/missing-config-file` is anchored to every file ESLint visits.
198
+ - **Partial Linting Scope Limitation**: ESLint only presents diagnostics for files included in its current invocation. Linting a single file opened in an editor might not display architectural errors anchored to other files. Comprehensive architectural conformity requires running ESLint across the entire project (e.g., in CI via `eslint .`).
292
199
 
293
- ### 3. `faf/enforce-access-node`
200
+ ---
294
201
 
295
- Enforces Access Node (`index.ts` / `index.js`) integrity:
202
+ ## Scope of Verification
296
203
 
297
- - Requires a directory that isn't a Category, Layer, Fractal Branch, or Root Fragment to have an Access Node.
298
- - Restricts Access Nodes to Fragments: an index file directly inside any other Logical Domain is forbidden.
299
- - Requires every Fragment to contain at least one Fragment Node (its Master Node).
300
- - Restricts the Access Node to import/export only its own sibling Fragment Nodes.
301
- - Prevents import of nested Private Categories, Fractal Branches, or unrelated external paths.
204
+ FAF defines both formal structural laws and qualitative design recommendations. Automated static analysis operates exclusively on deterministic source representations (ASTs, file inventories, and resolved import graphs).
302
205
 
303
- ### 4. `faf/fragment-master-node`
206
+ The following five properties are explicitly **out of scope** for automated verification:
304
207
 
305
- Enforces Fragment Node identity and Master Node integrity:
208
+ 1. **Symbol Coherence with Role** (FAF §5): Whether the semantic responsibility of an exported function or constant truthfully matches its declared Role (e.g., distinguishing a pure `util` from a `service` when syntax alone is identical).
209
+ 2. **Indivisibility of Symbols and Separation of Concerns** (FAF §6.1.2): Whether multiple Symbols grouped under an allowed multi-symbol catalog exemption are truly indivisible or constitute distinct architectural concerns.
210
+ 3. **Semantic Cohesion of a Fragment** (FAF §6.1): Whether all internal files of a Fragment conceptually collaborate toward a single cohesive business responsibility.
211
+ 4. **Semantic Fidelity of Mimetic Path Structures to External Addresses** (FAF §6.3.5): Whether declared Path Sub-Categories faithfully mirror real external HTTP routes or resource endpoints beyond syntactic validity.
212
+ 5. **Architectural Recommendations Introduced by _"it is recommended"_ / _"è raccomandato"_** (FAF §1.2, §8): Qualitative recommendations such as placing pages next to components, placing private details as close as possible to consumers, or avoiding singleton layers.
306
213
 
307
- - Validates that internal Fragment files share their parent Fragment's name prefix (e.g. files in folder `button/` must be named `button.<role>.<ext>`).
308
- - Requires a role suffix on every Fragment Node, and forbids two Fragment Nodes from sharing the same Role.
309
- - Assures a Fragment placed under a Category (or terminating a Route Hierarchy) contains a Master Node carrying the Role imposed by that Category/Route (e.g. a `.api.ts` file under `_apis`).
310
- - Assures route terminal Fragments follow the `<method>-<object>` naming pattern (e.g. `get-todo`), with the HTTP method restricted to `routeHierarchies[].httpMethods` or, by default, all standard HTTP methods.
214
+ The plugin emits **no diagnostic outcomes** and applies **no heuristics** for out-of-scope properties or recommendations.
311
215
 
312
- ### 5. `faf/no-direct-fragment-import`
216
+ **Conformity Certification**: The absence of diagnostic outcomes certifies architectural conformity for the entire verified scope.
313
217
 
314
- Applies **Fragment Encapsulation**:
218
+ ---
315
219
 
316
- - Prevents external files from directly importing a Fragment's internal files. All imports must pass through the Fragment's Access Node (`index.ts`).
220
+ ## Operational Boundaries and Limitations
317
221
 
318
- ### 6. `faf/no-private-category-leak`
222
+ ### Incompatibility with `eslint --cache`
319
223
 
320
- Applies **Private Category Encapsulation**:
224
+ `eslint-plugin-faf` is **fundamentally incompatible with `eslint --cache`**.
321
225
 
322
- - Restricts consumption of elements inside a Private Category (e.g. `_components/` nested inside a Fragment) to the direct child Fragment Nodes of the owning Fragment/Root Fragment, and to sibling nodes within the same immediate sub-domain of the Private Category. Access does not extend to other sub-domains, nor to a Fractal Branch nested inside the Private Category (governed instead by its own encapsulation rule).
226
+ ESLint's native `--cache` flag operates on per-file timestamps. It assumes that if file `A` has not changed on disk, its lint status remains identical to the previous run. However, architectural invariants are whole-graph properties: if file `B` changes its exports or moves to another directory, file `A` (which imports file `B`) may suddenly become non-conformant even though file `A` was never modified.
323
227
 
324
- ### 7. `faf/no-fractal-branch-leak`
228
+ **Do not run ESLint with `--cache` when validating FAF rules.**
325
229
 
326
- Applies **Fractal Branch Encapsulation**:
230
+ The plugin maintains its own internal, sound caching mechanisms (memoizing module records and dependency graphs based on directory fingerprinting, `package.json`, and `fafconfig.ts`), ensuring high performance on full project runs.
327
231
 
328
- - Fractal Branches named `_<Scope>@shared` are only importable by modules residing inside the parent Scope's subtree.
329
- - A Fractal Branch itself must never depend, not even indirectly, on the subtree it serves: importing anything from its owner's subtree that isn't part of the branch itself is forbidden.
232
+ ### Purely Diagnostic Paradigm — Prohibition of Autofix
330
233
 
331
- ### 8. `faf/no-peer-dependency`
234
+ `eslint-plugin-faf` is strictly **diagnostic-only**:
332
235
 
333
- Applies **Peer Isolation** via horizontal hierarchy resolution:
236
+ - It provides **zero automated fixes** (`fix` functions) and zero automated refactorings.
237
+ - It will never automatically move files, create directories, rewrite imports, or synthesize barrel files.
334
238
 
335
- - **Inside a Fragment**: Flow is governed by the `roles` array order.
336
- - **Between folders**: Flow is governed exclusively by `localHorizontalHierarchies` or `globalHorizontalHierarchies`; a sibling pair without a matching entry is denied in both directions.
239
+ Architectural corrections require intentional engineering judgment: deciding whether a private helper should be moved into a Private Category, extracted into a Sub-Fragment, or promoted to a Fractal Branch requires domain understanding that automated tools cannot possess.
337
240
 
338
- Foreign Domain imports and Root Node dependency direction are handled by dedicated rules (see below).
241
+ ### Static Analysis Capabilities
339
242
 
340
- ### 9. `faf/foreign-domain-isolation`
243
+ The plugin analyzes codebases using high-performance, deterministic static tooling:
341
244
 
342
- Applies **Foreign Domain isolation**:
245
+ - **TypeScript & JavaScript**: Parsed exclusively using `oxc-parser`. The plugin analyzes ESM static imports, dynamic imports with string literals (`import('...')`), CommonJS top-level `require()`, and TypeScript external references (`import x = require('...')`).
246
+ - **CSS Stylesheets**: Scanned with a dedicated parser for `@import` and `url()` references, establishing dependency edges to other stylesheets and static assets.
247
+ - **Binary Assets**: Recognized as terminal leaf nodes (sinks) with incoming edges but zero outgoing edges.
248
+ - **Boundary Resolution (`oxc-resolver`)**: Resolves module specifiers against your project's `tsconfig.json` (`paths`, `baseUrl`) and `package.json` (`imports`, `exports`). Workspace packages inside a monorepo are classified as local codebase files; external `node_modules` packages and Node.js built-ins (`node:*`) act as terminal sinks.
249
+ - **Incomplete Checks (`unverified`)**: Any unanalyzable construct (such as non-literal dynamic imports `import(variable)`, unreadable files, symbolic links inside trees, or unsupported syntax) emits a blocking `unverified/*` outcome. Incomplete checks are never treated as proof of conformity.
343
250
 
344
- - Prevents Logical Nodes from importing a file from a Foreign Domain (e.g. `src/configs`).
345
- - Prevents a Foreign Domain file from importing a Logical Node from the FAF tree.
251
+ ---
346
252
 
347
- ### 10. `faf/root-node-dependency-direction`
253
+ ## Migration from 0.5.x (Breaking Changes)
348
254
 
349
- Enforces explicit, non-Role-based dependency direction between **Root Nodes**:
255
+ Version `0.6.0` is a **complete ground-up rewrite** of `@morriconeluca/eslint-plugin-faf` centered around the normative Fractal Architecture Framework specification and `fafconfig.ts`. Nothing from version `0.5.x` is compatible.
350
256
 
351
- - Flow is governed exclusively by the defined index order in `rootFragments.rootNodes`. Root Nodes are exempt from the Role naming convention, so relationships between them (sibling or nested Root Fragments) never fall back to the `roles` scale: any relationship without a matching `rootNodes` entry is denied.
257
+ ### What Changed
352
258
 
353
- ### 11. `faf/category-mutually-exclusive`
259
+ - **Removed `settings.faf.trees`**: Tree origins, catalog roles, and hierarchies are no longer declared inside `eslint.config.js` settings. They are declared in `fafconfig.ts`.
260
+ - **Removed `faf.config.ts`**: Replaced by the standardized `fafconfig.ts` specification module.
261
+ - **Removed Granular Rules**: All legacy individual rules (`faf/no-cross-imports`, `faf/enforce-hierarchy`, `faf/symbol-rules`, etc.) have been removed. The plugin now exposes exactly one rule: `faf/conformity`.
262
+ - **Removed Legacy Configs**: Previous configurations (such as `faf.configs['flat/recommended']`) are replaced by `faf.configs.recommended`.
354
263
 
355
- Ensures category purity:
264
+ ### Step-by-Step Migration Guide
356
265
 
357
- - Blocks mixing single files (Logical Nodes) and subfolders (Fragments) in the same category.
358
- - Blocks direct file placement inside categories configured with `allowSingleFiles: false`.
266
+ 1. **Upgrade Dependencies**:
359
267
 
360
- ---
268
+ ```bash
269
+ npm install --save-dev @morriconeluca/eslint-plugin-faf@^0.6.0 eslint@^9.0.0
270
+ ```
361
271
 
362
- ## Migrating from 7 rules to 11
272
+ Ensure Node.js is `>= 24.0.0`.
363
273
 
364
- Starting with this release, five checks that used to live inside two large rules (`naming-conventions` and `no-peer-dependency`) have moved to dedicated rules with their own `id`. The underlying checks and their error messages are unchanged — only the `ruleId` reporting them is different. This is a breaking change for anyone who doesn't use `configs.recommended` (which picks up the new rules automatically) or who references a specific rule `id` directly, e.g. in `// eslint-disable-next-line faf/<id>` comments or a hand-written `rules` block.
274
+ 2. **Create `fafconfig.ts`**:
275
+ In your repository root, create `fafconfig.ts` implementing `TFAFConfig`:
365
276
 
366
- | Old rule id | Check moved to |
367
- | ------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------- |
368
- | `naming-conventions` — folder/file placement (Root Fragment, Layer, Fragment, Fractal Branch containment) | `logical-domain-placement` (new) |
369
- | `naming-conventions` — Access Node existence, Master Node existence | `enforce-access-node` |
370
- | `naming-conventions` — Fragment Node name prefix, role requirement/uniqueness, Category/Route Master Node role, route naming pattern | `fragment-master-node` (new) |
371
- | `naming-conventions` — kebab-case, underscore prefix, Fractal Branch naming, single-Role-segment, Category loose-file naming | unchanged, stays in `naming-conventions` |
372
- | `no-peer-dependency` — Foreign Domain imports (both directions) | `foreign-domain-isolation` (new) |
373
- | `no-peer-dependency` — Root Node dependency direction (`rootFragments.rootNodes`) | `root-node-dependency-direction` (new) |
374
- | `no-peer-dependency` — horizontal hierarchy resolution (Fragment Node roles, sibling folders) | unchanged, stays in `no-peer-dependency` |
277
+ ```typescript
278
+ import type { TFAFConfig } from '@morriconeluca/eslint-plugin-faf';
375
279
 
376
- `no-direct-fragment-import`, `no-private-category-leak`, `category-mutually-exclusive` are unaffected. `no-fractal-branch-leak` keeps its `id` and gains one new check: a Fractal Branch depending on the subtree it serves is now reported.
280
+ export default {
281
+ faf: {
282
+ locals: [
283
+ {
284
+ roles: [
285
+ { name: 'util', category: '_utils' },
286
+ { name: 'component', category: '_components' },
287
+ ],
288
+ subGraphRoles: [{ name: 'spec' }],
289
+ rootLayers: ['src'],
290
+ },
291
+ ],
292
+ },
293
+ } satisfies TFAFConfig;
294
+ ```
377
295
 
378
- ---
296
+ 3. **Update `eslint.config.js`**:
297
+ Remove any legacy rules and settings. Use the new recommended preset:
379
298
 
380
- ## Performance Optimizations
299
+ ```javascript
300
+ import faf from '@morriconeluca/eslint-plugin-faf';
381
301
 
382
- To prevent sluggish linting in large codebases, the core engine implements aggressive, state-cached optimizations:
302
+ export default [faf.configs.recommended];
303
+ ```
383
304
 
384
- - **Directory Cache (`dirCache`)**: Inspects the physical disk exactly once per folder, storing the contents in-memory. It is optimized to perform exactly one system call on cache misses by removing redundant `fs.existsSync` checks.
385
- - **Classification Cache (`classifyCache`)**: Memoizes the structural type (`FolderType`) of each directory for the lifetime of the ESLint run.
386
- - **Ancestor Chain Cache (`ancestorChainCache`)**: Memoizes the full chain of classified ancestors above a directory, so rules that walk upward from a file (folder taxonomy, topological placement) do so once per directory rather than once per file.
387
- - **Role Cache (`rolesCache`)**: Uses a `WeakMap` to cache the allowed roles per tree configuration as a native `Set`, avoiding memory leaks, redundant array flattening allocations, and speeding up role lookup checks to $O(1)$.
388
- - **Category Configuration Cache (`categoryConfigCache`)**: Memoizes resolved category configurations per folder path to eliminate repetitive directory walks and path parent traversals.
389
- - **Tree Configuration Cache (`treeConfigCache` / `treeConfigIncludingExcludedCache`)**: Memoizes resolved tree configurations per file path, reducing tree lookup checks to a simple map-lookup.
390
- - **Resolved Import Path Cache (`resolvedImportPathCache`)**: Caches project-root-relative resolved import paths to avoid repeating expensive path resolutions, alias matching, and package.json parsing on duplicate imports.
305
+ 4. **Update CI and Scripts**:
306
+ Remove `--cache` from your ESLint linting scripts:
307
+ ```json
308
+ {
309
+ "scripts": {
310
+ "lint": "eslint ."
311
+ }
312
+ }
313
+ ```
391
314
 
392
315
  ---
393
316
 
394
317
  ## License
395
318
 
396
- This project is licensed under the terms of the [MIT License](LICENSE).
319
+ MIT © [Luca Morricone](https://github.com/morriconeluca)