eslint-plugin-ai-rules 0.2.1 → 0.3.1
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 +1154 -175
- package/dist/index.cjs +1644 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +74 -2
- package/dist/index.d.ts +74 -2
- package/dist/index.js +1640 -2
- package/dist/index.js.map +1 -1
- package/package.json +25 -13
package/README.md
CHANGED
|
@@ -2,61 +2,212 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/eslint-plugin-ai-rules)
|
|
4
4
|
[](LICENSE)
|
|
5
|
-
[](package.json)
|
|
6
|
+
[](https://eslint.org)
|
|
7
7
|
|
|
8
|
-
Flat-config
|
|
8
|
+
Flat-config-only ESLint plugin for AI-generated code. It catches filename/import drift and enforces a project structure you declare, with ready-to-use `frontend` and `backend` presets.
|
|
9
9
|
|
|
10
|
-
> **ESLint 9+ only.** Uses flat config (`eslint.config.{ts
|
|
10
|
+
> **ESLint 9.22+ or 10 only.** Uses flat config (`eslint.config.{js,mjs,ts}`). No legacy `.eslintrc` support.
|
|
11
11
|
|
|
12
|
-
##
|
|
12
|
+
## Start here
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
| If your project is... | Use |
|
|
15
|
+
|-----------------------|-----|
|
|
16
|
+
| Frontend only | `preset: 'frontend'` |
|
|
17
|
+
| Backend only | `preset: 'backend'` |
|
|
18
|
+
| Frontend + backend in one repo | two projects with different `rootFolder` |
|
|
19
|
+
| Neither frontend nor backend | `preset: 'other'` |
|
|
20
|
+
| Layered architecture | `relations` (controller → useCase → repository) |
|
|
18
21
|
|
|
19
|
-
|
|
22
|
+
Before installing, check:
|
|
20
23
|
|
|
21
|
-
- Node `>=
|
|
22
|
-
- ESLint `^9.
|
|
23
|
-
- TypeScript `>=5.0.0`
|
|
24
|
+
- Node.js `>= 20`
|
|
25
|
+
- ESLint `^9.22.0` or `^10.0.0`
|
|
26
|
+
- TypeScript `>= 5.0.0 < 6.1.0`
|
|
27
|
+
- `typescript-eslint` configured as parser
|
|
28
|
+
- `tsconfig.json` if you use aliases like `@/*`
|
|
29
|
+
- `eslint-import-resolver-typescript` installed for TS alias resolution
|
|
24
30
|
|
|
25
|
-
|
|
31
|
+
## Quick start
|
|
26
32
|
|
|
27
|
-
|
|
33
|
+
Install:
|
|
28
34
|
|
|
29
35
|
```bash
|
|
30
|
-
|
|
31
|
-
|
|
36
|
+
npm i -D eslint-plugin-ai-rules eslint typescript typescript-eslint eslint-import-resolver-typescript
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Configure the project structure and let the plugin apply its recommended rules:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { defineConfig } from 'eslint/config'
|
|
43
|
+
import aiRules from 'eslint-plugin-ai-rules'
|
|
44
|
+
import tseslint from 'typescript-eslint'
|
|
45
|
+
|
|
46
|
+
export default defineConfig([
|
|
47
|
+
...tseslint.configs.recommended,
|
|
48
|
+
...aiRules.configs.projects([
|
|
49
|
+
{ rootFolder: '.', preset: 'frontend' },
|
|
50
|
+
]),
|
|
51
|
+
])
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Monorepo with frontend and backend:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
export default defineConfig([
|
|
58
|
+
...tseslint.configs.recommended,
|
|
59
|
+
...aiRules.configs.projects([
|
|
60
|
+
{ rootFolder: 'apps/web', preset: 'frontend' },
|
|
61
|
+
{ rootFolder: 'apps/api', preset: 'backend' },
|
|
62
|
+
]),
|
|
63
|
+
])
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Project that is neither frontend nor backend:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
export default defineConfig([
|
|
70
|
+
...tseslint.configs.recommended,
|
|
71
|
+
...aiRules.configs.projects([
|
|
72
|
+
{ rootFolder: '.', preset: 'other' },
|
|
73
|
+
]),
|
|
74
|
+
])
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Run:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npx eslint .
|
|
81
|
+
npx eslint --fix .
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Contents
|
|
85
|
+
|
|
86
|
+
- [Configuration warning](#configuration-warning)
|
|
87
|
+
- [Features](#features)
|
|
88
|
+
- [Requirements](#requirements)
|
|
89
|
+
- [Project structure](#project-structure)
|
|
90
|
+
- [Examples](#examples)
|
|
91
|
+
- [Architecture relations](#architecture-relations)
|
|
92
|
+
- [Expected diagnostics](#expected-diagnostics)
|
|
93
|
+
- [Preset reference](#preset-reference)
|
|
94
|
+
- [Structure DSL](#structure-dsl)
|
|
95
|
+
- [Limitations](#limitations)
|
|
96
|
+
- [Rules](#rules)
|
|
97
|
+
- [What runs automatically](#what-runs-automatically)
|
|
98
|
+
- [Recommended preset](#recommended-preset)
|
|
99
|
+
- [Troubleshooting](#troubleshooting)
|
|
100
|
+
- [Common mistakes](#common-mistakes)
|
|
101
|
+
- [Migrating from 0.2.x](#migrating-from-02x)
|
|
102
|
+
- [Development](#development)
|
|
103
|
+
- [Publishing](#publishing)
|
|
104
|
+
- [Legacy](#legacy)
|
|
105
|
+
|
|
106
|
+
## Configuration warning
|
|
107
|
+
|
|
108
|
+
If you install the plugin and use `aiRules.configs.recommended` without configuring the project structure, the plugin prints a warning the first time it lints a file:
|
|
109
|
+
|
|
110
|
+
```text
|
|
111
|
+
[eslint-plugin-ai-rules] Project structure validation is not configured.
|
|
112
|
+
Configure it with aiRules.configs.projects([{ rootFolder: '.', preset: 'frontend' | 'backend' }]).
|
|
113
|
+
If this project is not a frontend/backend, use preset: 'other' to disable this warning.
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The warning exists only to make sure the new structure rules are actually configured. It is printed once per process.
|
|
117
|
+
|
|
118
|
+
It disappears when you provide a valid configuration:
|
|
119
|
+
|
|
120
|
+
- `preset: 'frontend'`
|
|
121
|
+
- `preset: 'backend'`
|
|
122
|
+
- `preset: 'other'`
|
|
123
|
+
- a custom non-empty `structure`
|
|
124
|
+
- a `relations` configuration
|
|
125
|
+
|
|
126
|
+
A project containing only `rootFolder` and an empty `structure` is still considered unconfigured and will warn.
|
|
127
|
+
|
|
128
|
+
For CI or temporary runs, silence the warning without changing the config:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
AI_RULES_SILENT=1 npx eslint .
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
The warning is still recommended as a setup check, so prefer `preset: 'other'` for projects that intentionally do not use structure validation.
|
|
32
135
|
|
|
33
|
-
|
|
34
|
-
|
|
136
|
+
## Features
|
|
137
|
+
|
|
138
|
+
- **Project structure validation** — folder hierarchy, dynamic folders, folder/file casing, prefixes, suffixes and regex patterns.
|
|
139
|
+
- **Frontend/backend presets** — ready-to-use structures and architecture relations based on real projects.
|
|
140
|
+
- **Custom structure** — declare exactly how your project must be organized.
|
|
141
|
+
- **Architecture relations** — enforce which layers can import which other layers, including global vs page-local scope.
|
|
142
|
+
- **Monorepo friendly** — one `rootFolder` per project, including nested roots.
|
|
143
|
+
- **Autofix** — `sort-imports` and `no-multiline-imports` are fixable.
|
|
144
|
+
- **Bundled rules** — ships with useful `import-x`, `@stylistic`, `boundaries` and `@typescript-eslint` rules.
|
|
145
|
+
- **ESM + CJS** — `dist/index.js` and `dist/index.cjs`, with correct type declarations for both.
|
|
146
|
+
|
|
147
|
+
## Requirements
|
|
148
|
+
|
|
149
|
+
| Tool | Version |
|
|
150
|
+
|------|---------|
|
|
151
|
+
| Node.js | `>= 20` |
|
|
152
|
+
| ESLint | `^9.22.0 || ^10.0.0` |
|
|
153
|
+
| TypeScript | `>= 5.0.0 < 6.1.0` |
|
|
154
|
+
| ESLint resolver | `eslint-import-resolver-typescript` `^4.4.5` |
|
|
155
|
+
|
|
156
|
+
Runtime dependencies are `@stylistic/eslint-plugin`, `eslint-plugin-import-x`, `eslint-plugin-boundaries` and `typescript-eslint`.
|
|
35
157
|
|
|
36
|
-
|
|
37
|
-
pnpm add -D eslint-plugin-ai-rules eslint typescript
|
|
158
|
+
`eslint-import-resolver-typescript` is a peer dependency used by `relations` to resolve TS aliases. npm installs peers automatically; with Yarn 1 or pnpm, install it explicitly.
|
|
38
159
|
|
|
39
|
-
|
|
40
|
-
|
|
160
|
+
## Project structure
|
|
161
|
+
|
|
162
|
+
Each project definition has this shape:
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
interface IProjectDefinition {
|
|
166
|
+
rootFolder: string
|
|
167
|
+
preset?: 'frontend' | 'backend' | 'other'
|
|
168
|
+
structure?: IProjectStructure
|
|
169
|
+
relations?: IRelationsConfig
|
|
170
|
+
}
|
|
41
171
|
```
|
|
42
172
|
|
|
43
|
-
|
|
173
|
+
- `rootFolder` is the project root, usually the folder containing `package.json`.
|
|
174
|
+
- `rootFolder` is relative to the current working directory.
|
|
175
|
+
- Absolute paths inside the current working directory are also accepted.
|
|
176
|
+
- Nested roots are supported; the most specific root wins.
|
|
177
|
+
- Duplicate roots are rejected.
|
|
178
|
+
- `preset` provides a base structure.
|
|
179
|
+
- `structure` is merged on top of the preset.
|
|
180
|
+
- `relations` enables architecture dependency policies with `eslint-plugin-boundaries`.
|
|
181
|
+
- `preset: 'other'` disables structure validation unless a custom `structure` is provided.
|
|
44
182
|
|
|
45
|
-
|
|
183
|
+
`aiRules.configs.projects([...])` generates one flat config per project. Each config:
|
|
46
184
|
|
|
47
|
-
|
|
185
|
+
- scopes `files` to the project root;
|
|
186
|
+
- sets `settings['ai-rules'].project`;
|
|
187
|
+
- enables `ai-rules/project-structure`;
|
|
188
|
+
- adds `ignores` from `ignoredPaths`;
|
|
189
|
+
- includes the recommended rules;
|
|
190
|
+
- disables `ai-rules/filename-pascal-case`, because file naming is handled by `project-structure`;
|
|
191
|
+
- when `relations` is present, registers `boundaries` and enables `boundaries/dependencies`.
|
|
192
|
+
|
|
193
|
+
## Examples
|
|
194
|
+
|
|
195
|
+
### Frontend only
|
|
48
196
|
|
|
49
197
|
```ts
|
|
198
|
+
import { defineConfig } from 'eslint/config'
|
|
50
199
|
import aiRules from 'eslint-plugin-ai-rules'
|
|
51
200
|
import tseslint from 'typescript-eslint'
|
|
52
201
|
|
|
53
|
-
export default [
|
|
202
|
+
export default defineConfig([
|
|
54
203
|
...tseslint.configs.recommended,
|
|
55
|
-
|
|
56
|
-
|
|
204
|
+
...aiRules.configs.projects([
|
|
205
|
+
{ rootFolder: '.', preset: 'frontend' },
|
|
206
|
+
]),
|
|
207
|
+
])
|
|
57
208
|
```
|
|
58
209
|
|
|
59
|
-
|
|
210
|
+
### Frontend and backend in the same repository
|
|
60
211
|
|
|
61
212
|
```ts
|
|
62
213
|
import { defineConfig } from 'eslint/config'
|
|
@@ -65,125 +216,768 @@ import tseslint from 'typescript-eslint'
|
|
|
65
216
|
|
|
66
217
|
export default defineConfig([
|
|
67
218
|
...tseslint.configs.recommended,
|
|
68
|
-
|
|
219
|
+
...aiRules.configs.projects([
|
|
220
|
+
{ rootFolder: 'packages/web', preset: 'frontend' },
|
|
221
|
+
{ rootFolder: 'packages/api', preset: 'backend' },
|
|
222
|
+
]),
|
|
69
223
|
])
|
|
70
224
|
```
|
|
71
225
|
|
|
72
|
-
###
|
|
226
|
+
### Backend with preset (copy-paste)
|
|
73
227
|
|
|
74
|
-
```
|
|
228
|
+
```ts
|
|
229
|
+
import { defineConfig } from 'eslint/config'
|
|
75
230
|
import aiRules from 'eslint-plugin-ai-rules'
|
|
76
231
|
import tseslint from 'typescript-eslint'
|
|
77
232
|
|
|
78
|
-
export default [
|
|
233
|
+
export default defineConfig([
|
|
79
234
|
...tseslint.configs.recommended,
|
|
80
|
-
|
|
81
|
-
|
|
235
|
+
...aiRules.configs.projects([
|
|
236
|
+
{
|
|
237
|
+
rootFolder: '.',
|
|
238
|
+
preset: 'backend',
|
|
239
|
+
},
|
|
240
|
+
]),
|
|
241
|
+
])
|
|
82
242
|
```
|
|
83
243
|
|
|
84
|
-
|
|
244
|
+
The backend preset includes strict architecture relations. With this config:
|
|
85
245
|
|
|
86
246
|
```ts
|
|
87
|
-
|
|
88
|
-
import {
|
|
247
|
+
// src/http/controllers/ListProducts.ts
|
|
248
|
+
import { listProducts } from '@/use-case/product/List' // allowed
|
|
249
|
+
import { productRepository } from '@/repositories/ProductRepository' // blocked
|
|
250
|
+
```
|
|
89
251
|
|
|
252
|
+
To override a preset relation, declare only the layer/rule you want to change:
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
relations: {
|
|
256
|
+
rules: [
|
|
257
|
+
{ from: 'service', allow: ['generated', 'env'], allowSameLayer: false },
|
|
258
|
+
],
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Opting out with `other`
|
|
263
|
+
|
|
264
|
+
```ts
|
|
90
265
|
export default defineConfig([
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
266
|
+
...tseslint.configs.recommended,
|
|
267
|
+
...aiRules.configs.projects([
|
|
268
|
+
{ rootFolder: '.', preset: 'other' },
|
|
269
|
+
]),
|
|
270
|
+
])
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Custom structure
|
|
274
|
+
|
|
275
|
+
```ts
|
|
276
|
+
export default defineConfig([
|
|
277
|
+
...tseslint.configs.recommended,
|
|
278
|
+
...aiRules.configs.projects([
|
|
279
|
+
{
|
|
280
|
+
rootFolder: 'apps/api',
|
|
281
|
+
preset: 'backend',
|
|
282
|
+
structure: {
|
|
283
|
+
folders: {
|
|
284
|
+
src: {
|
|
285
|
+
folders: {
|
|
286
|
+
'use-case': {
|
|
287
|
+
folders: {
|
|
288
|
+
'[feature]': {
|
|
289
|
+
files: [
|
|
290
|
+
{
|
|
291
|
+
case: 'PascalCase',
|
|
292
|
+
suffix: 'UseCase',
|
|
293
|
+
extensions: ['.ts'],
|
|
294
|
+
},
|
|
295
|
+
],
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
]),
|
|
305
|
+
])
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Use case with a suffix
|
|
309
|
+
|
|
310
|
+
```ts
|
|
311
|
+
structure: {
|
|
312
|
+
folders: {
|
|
313
|
+
src: {
|
|
314
|
+
folders: {
|
|
315
|
+
'use-case': {
|
|
316
|
+
folders: {
|
|
317
|
+
'[feature]': {
|
|
318
|
+
files: [
|
|
319
|
+
{
|
|
320
|
+
case: 'PascalCase',
|
|
321
|
+
suffix: 'UseCase',
|
|
322
|
+
extensions: ['.ts'],
|
|
323
|
+
},
|
|
324
|
+
],
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
},
|
|
97
329
|
},
|
|
98
330
|
},
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Controller with a suffix
|
|
335
|
+
|
|
336
|
+
```ts
|
|
337
|
+
structure: {
|
|
338
|
+
folders: {
|
|
339
|
+
src: {
|
|
340
|
+
folders: {
|
|
341
|
+
http: {
|
|
342
|
+
folders: {
|
|
343
|
+
controllers: {
|
|
344
|
+
folders: {
|
|
345
|
+
'[feature]': {
|
|
346
|
+
files: [
|
|
347
|
+
{
|
|
348
|
+
case: 'PascalCase',
|
|
349
|
+
suffix: 'Controller',
|
|
350
|
+
extensions: ['.ts'],
|
|
351
|
+
},
|
|
352
|
+
],
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
},
|
|
103
359
|
},
|
|
104
360
|
},
|
|
105
|
-
|
|
361
|
+
}
|
|
106
362
|
```
|
|
107
363
|
|
|
108
|
-
|
|
364
|
+
### Dynamic folder with prefix, suffix and regex
|
|
109
365
|
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
|
|
366
|
+
```ts
|
|
367
|
+
structure: {
|
|
368
|
+
folders: {
|
|
369
|
+
'[module]': {
|
|
370
|
+
name: {
|
|
371
|
+
prefix: 'feature-',
|
|
372
|
+
suffix: '-module',
|
|
373
|
+
pattern: '^feature-[a-z-]+-module$',
|
|
374
|
+
},
|
|
375
|
+
files: ['index.ts'],
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
}
|
|
113
379
|
```
|
|
114
380
|
|
|
115
|
-
|
|
381
|
+
Valid: `feature-users-module/index.ts`.
|
|
116
382
|
|
|
117
|
-
|
|
383
|
+
Invalid: `users-module/index.ts`, `feature-users/index.ts`, `feature-Users-module/index.ts`.
|
|
118
384
|
|
|
119
|
-
|
|
385
|
+
### Adding files to a preset
|
|
120
386
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
387
|
+
```ts
|
|
388
|
+
structure: {
|
|
389
|
+
folders: {
|
|
390
|
+
src: {
|
|
391
|
+
additionalFiles: [
|
|
392
|
+
{ case: 'PascalCase', suffix: 'Service', extensions: ['.ts'] },
|
|
393
|
+
],
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
}
|
|
397
|
+
```
|
|
124
398
|
|
|
125
|
-
|
|
126
|
-
|------|---------|-------|
|
|
127
|
-
| `ai-rules/filename-pascal-case` | `error` | File-agnostic: checks every file ESLint lints. See below |
|
|
128
|
-
| `ai-rules/sort-imports` | `error` | Groups + length desc, collapses multiline |
|
|
129
|
-
| `ai-rules/no-multiline-imports` | `error` | Single-line imports |
|
|
130
|
-
| `import-x/no-duplicates` | `error` | |
|
|
131
|
-
| `import-x/no-default-export` | `off` | |
|
|
132
|
-
| `max-len` | `off` | |
|
|
133
|
-
| `@stylistic/semi` | `['error','never']` | No semicolons |
|
|
134
|
-
| `@typescript-eslint/consistent-type-imports` | `['error',{prefer:'type-imports'}]` | |
|
|
135
|
-
| `@typescript-eslint/no-unused-vars` | `['error',{argsIgnorePattern:'^_',varsIgnorePattern:'^_'}]` | |
|
|
136
|
-
| `@typescript-eslint/naming-convention` | `['error', {interface:'PascalCase' prefix I}, {typeAlias:'PascalCase' prefix T}]` | Disable per-project for `**/*.d.ts` via your own override if needed |
|
|
399
|
+
### Replacing preset files
|
|
137
400
|
|
|
138
|
-
|
|
401
|
+
```ts
|
|
402
|
+
structure: {
|
|
403
|
+
folders: {
|
|
404
|
+
src: {
|
|
405
|
+
files: ['index.ts'],
|
|
406
|
+
},
|
|
407
|
+
},
|
|
408
|
+
}
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
### Ignoring a preset subtree
|
|
412
|
+
|
|
413
|
+
```ts
|
|
414
|
+
structure: {
|
|
415
|
+
folders: {
|
|
416
|
+
e2e: { ignore: true },
|
|
417
|
+
},
|
|
418
|
+
}
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### Ignoring paths globally
|
|
422
|
+
|
|
423
|
+
```ts
|
|
424
|
+
structure: {
|
|
425
|
+
ignoredPaths: ['dist/**', 'coverage/**', 'src/shared/services/generated/**'],
|
|
426
|
+
}
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### Manual settings without the factory
|
|
139
430
|
|
|
140
|
-
|
|
431
|
+
Advanced users can configure the rule directly:
|
|
141
432
|
|
|
142
433
|
```ts
|
|
143
434
|
import { plugin } from 'eslint-plugin-ai-rules'
|
|
144
|
-
import { defineConfig } from 'eslint/config'
|
|
145
435
|
|
|
146
|
-
export default
|
|
436
|
+
export default [
|
|
147
437
|
{
|
|
148
|
-
files: ['
|
|
438
|
+
files: ['src/**/*.ts'],
|
|
149
439
|
plugins: { 'ai-rules': plugin },
|
|
440
|
+
settings: {
|
|
441
|
+
'ai-rules': {
|
|
442
|
+
project: {
|
|
443
|
+
rootFolder: '.',
|
|
444
|
+
preset: 'other',
|
|
445
|
+
structure: {
|
|
446
|
+
folders: {
|
|
447
|
+
src: {
|
|
448
|
+
files: [{ case: 'PascalCase', extensions: ['.ts'] }],
|
|
449
|
+
},
|
|
450
|
+
},
|
|
451
|
+
},
|
|
452
|
+
},
|
|
453
|
+
},
|
|
454
|
+
},
|
|
150
455
|
rules: {
|
|
151
|
-
'ai-rules/
|
|
152
|
-
|
|
153
|
-
|
|
456
|
+
'ai-rules/project-structure': 'error',
|
|
457
|
+
},
|
|
458
|
+
},
|
|
459
|
+
]
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
Multiple projects can be passed directly through `settings['ai-rules'].projects`. The rule chooses the most specific root for each file.
|
|
463
|
+
|
|
464
|
+
## Architecture relations
|
|
465
|
+
|
|
466
|
+
`relations` enforces which layers can import which other layers. It complements `structure`: `structure` validates paths and names, `relations` validates import direction.
|
|
467
|
+
|
|
468
|
+
```ts
|
|
469
|
+
interface IRelationsRule {
|
|
470
|
+
from: string
|
|
471
|
+
allow?: string[]
|
|
472
|
+
deny?: string[]
|
|
473
|
+
allowSameLayer?: boolean
|
|
474
|
+
allowTypeImports?: boolean
|
|
475
|
+
message?: string
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
interface IRelationsScope {
|
|
479
|
+
capture: string
|
|
480
|
+
layers: string[]
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
interface IRelationsConfig {
|
|
484
|
+
layers: Record<string, string | string[]>
|
|
485
|
+
rules: IRelationsRule[]
|
|
486
|
+
allowTypeImports?: boolean
|
|
487
|
+
scope?: IRelationsScope
|
|
488
|
+
}
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
Example for a backend:
|
|
492
|
+
|
|
493
|
+
```ts
|
|
494
|
+
export default defineConfig([
|
|
495
|
+
...tseslint.configs.recommended,
|
|
496
|
+
...aiRules.configs.projects([
|
|
497
|
+
{
|
|
498
|
+
rootFolder: 'apps/backend',
|
|
499
|
+
preset: 'other',
|
|
500
|
+
relations: {
|
|
501
|
+
layers: {
|
|
502
|
+
controller: 'src/http/controllers',
|
|
503
|
+
useCase: 'src/use-case',
|
|
504
|
+
repository: 'src/repositories',
|
|
505
|
+
service: 'src/services',
|
|
506
|
+
helper: 'src/helpers',
|
|
507
|
+
},
|
|
508
|
+
rules: [
|
|
509
|
+
{ from: 'controller', allow: ['useCase', 'helper'] },
|
|
510
|
+
{ from: 'useCase', allow: ['repository', 'service', 'helper'] },
|
|
511
|
+
{ from: 'repository', allow: ['helper'] },
|
|
512
|
+
{ from: 'service', allow: ['repository', 'helper'] },
|
|
513
|
+
{ from: 'helper', allow: [] },
|
|
514
|
+
],
|
|
515
|
+
},
|
|
516
|
+
},
|
|
517
|
+
]),
|
|
518
|
+
])
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
Semantics:
|
|
522
|
+
|
|
523
|
+
- Layers are folder paths relative to `rootFolder`.
|
|
524
|
+
- `allow` means the layer can import only the listed layers.
|
|
525
|
+
- `deny` means the layer cannot import the listed layers.
|
|
526
|
+
- `allow` and `deny` are mutually exclusive per rule.
|
|
527
|
+
- `allowSameLayer` defaults to `true`. Set it to `false` to block imports within the same layer.
|
|
528
|
+
- External packages and Node core modules are allowed automatically.
|
|
529
|
+
- Local files that do not match any layer are ignored by default.
|
|
530
|
+
- Only one rule per `from` layer is allowed.
|
|
531
|
+
- `scope` allows comparing a captured value, such as the current page, to restrict local imports to the same page.
|
|
532
|
+
|
|
533
|
+
Presets already include relation rules. You only need to declare `relations` when you want to add or override layers and rules:
|
|
534
|
+
|
|
535
|
+
```ts
|
|
536
|
+
aiRules.configs.projects([
|
|
537
|
+
{
|
|
538
|
+
rootFolder: 'apps/backend',
|
|
539
|
+
preset: 'backend',
|
|
540
|
+
relations: {
|
|
541
|
+
rules: [
|
|
542
|
+
{ from: 'service', allow: ['generated', 'env'], allowSameLayer: false },
|
|
543
|
+
],
|
|
154
544
|
},
|
|
155
545
|
},
|
|
156
546
|
])
|
|
157
547
|
```
|
|
158
548
|
|
|
159
|
-
|
|
549
|
+
The preset backend is strict: controllers only use use-cases plus infrastructure, use-cases cannot access database/helpers directly, repositories cannot import repositories, and services cannot import services. Structural exceptions exist for `generated` and `database/schema`. Service subfolders are generic modules with their own scope.
|
|
550
|
+
|
|
551
|
+
The preset frontend uses global layers plus page-local scope. Global layers cannot import page-local files. Page-local files can import global layers and files from the same page only.
|
|
552
|
+
|
|
553
|
+
### Backend preset relations
|
|
554
|
+
|
|
555
|
+
| Layer | Can import | Same layer |
|
|
556
|
+
|-------|------------|------------|
|
|
557
|
+
| controller | useCase, http, generated, helper, env | no |
|
|
558
|
+
| http | useCase, helper, generated | no |
|
|
559
|
+
| useCase | repository, service, serviceModule, generated | yes |
|
|
560
|
+
| repository | database, databaseSchema | no |
|
|
561
|
+
| service | generated, env | no |
|
|
562
|
+
| generated | — | yes |
|
|
563
|
+
| serviceModule | generated, env | yes, within the same module |
|
|
564
|
+
| database | databaseSchema, env | no |
|
|
565
|
+
| databaseSchema | — | yes |
|
|
566
|
+
| helper | — | no |
|
|
567
|
+
| env | — | no |
|
|
568
|
+
| types | — | no |
|
|
569
|
+
|
|
570
|
+
`generated` and `databaseSchema` are structural exceptions because they contain internal files that import each other. Any `src/services/<service-kebab>/` folder is a generic service module: files inside the same module can import each other, different modules cannot.
|
|
571
|
+
|
|
572
|
+
The strict backend preset will report existing violations such as:
|
|
573
|
+
|
|
574
|
+
- use-cases importing `database` or `helpers`;
|
|
575
|
+
- services importing other services;
|
|
576
|
+
- repositories importing other repositories, including `Setup`.
|
|
577
|
+
|
|
578
|
+
These violations are intentional and should be fixed during migration.
|
|
579
|
+
|
|
580
|
+
### Frontend preset relations
|
|
581
|
+
|
|
582
|
+
All global frontend layers live under `src/shared` (for example `src/shared/components`). Page-local layers remain under `src/pages/*`.
|
|
583
|
+
|
|
584
|
+
| Layer | Can import | Same layer |
|
|
585
|
+
|-------|------------|------------|
|
|
586
|
+
| components | components, hooks, contexts, services, generated, utils, helpers, constants, env | yes |
|
|
587
|
+
| hooks | hooks, contexts, services, generated, utils, helpers, constants, env | yes |
|
|
588
|
+
| contexts | services, generated, utils, helpers, constants, env | no |
|
|
589
|
+
| services | generated, utils, helpers, constants, env | no |
|
|
590
|
+
| generated | — | yes |
|
|
591
|
+
| utils | helpers, constants, env | no |
|
|
592
|
+
| helpers | utils, constants, env | no |
|
|
593
|
+
| constants | — | no |
|
|
594
|
+
| env | — | no |
|
|
595
|
+
| page | globals + same-page locals | yes (same page) |
|
|
596
|
+
| pageComponent | globals + same-page locals (including page main) | yes |
|
|
597
|
+
| pageHook | hooks/contexts/services/generated/utils/helpers/constants/env + same-page local hooks/services/contexts/helpers | yes |
|
|
598
|
+
| pageService | services, generated, utils, helpers, constants, env + same-page pageHelper | no |
|
|
599
|
+
| pageContext | contexts, services, generated, utils, helpers, constants, env + same-page pageService/pageHelper | no |
|
|
600
|
+
| pageHelper | helpers, utils, constants, env | no |
|
|
601
|
+
|
|
602
|
+
Local layers can import global layers. Global layers cannot import page-local layers. `pageHook` cannot import `pageComponent`.
|
|
603
|
+
|
|
604
|
+
`utils` and `helpers` can import each other. If this creates undesirable cycles, remove one direction.
|
|
605
|
+
|
|
606
|
+
### Same-layer and page scope
|
|
160
607
|
|
|
161
608
|
```ts
|
|
162
|
-
|
|
609
|
+
relations: {
|
|
610
|
+
layers: {
|
|
611
|
+
pageComponent: 'src/pages/*/components',
|
|
612
|
+
pageHook: 'src/pages/*/hooks',
|
|
613
|
+
page: 'src/pages/*',
|
|
614
|
+
},
|
|
615
|
+
scope: {
|
|
616
|
+
capture: 'page',
|
|
617
|
+
layers: ['pageComponent', 'pageHook', 'page'],
|
|
618
|
+
},
|
|
619
|
+
rules: [
|
|
620
|
+
{
|
|
621
|
+
from: 'pageComponent',
|
|
622
|
+
allow: ['pageHook'],
|
|
623
|
+
allowSameLayer: true,
|
|
624
|
+
},
|
|
625
|
+
],
|
|
626
|
+
}
|
|
163
627
|
```
|
|
164
628
|
|
|
165
|
-
|
|
629
|
+
A file from `src/pages/orders` can import another file from `src/pages/orders`, but not from `src/pages/users`.
|
|
166
630
|
|
|
167
|
-
|
|
631
|
+
### Type-only imports
|
|
168
632
|
|
|
169
|
-
|
|
633
|
+
Type-only imports follow the same restrictions by default:
|
|
170
634
|
|
|
171
|
-
|
|
635
|
+
```ts
|
|
636
|
+
import type { TUser } from '@/repositories/UserRepository' // blocked
|
|
637
|
+
```
|
|
172
638
|
|
|
173
|
-
|
|
174
|
-
- **Ignores:** `index.{ts,tsx,js,jsx}`, `*.d.ts`, `*.css`, dotfiles
|
|
175
|
-
- **Checks:** `path.basename` from `getPhysicalFilename()` / `context.filename`, `^[A-Z][a-zA-Z0-9]*$`, `kebab/snake -> PascalCase` conversion
|
|
176
|
-
- **Options:**
|
|
177
|
-
- `ignore` — array of regex strings matched against the basename and the path relative to `cwd` (same idea as `unicorn/filename-case`). Use it for tool-mandated names like `*.config.*`, tests/specs, generated or vendored code.
|
|
178
|
-
- `ignoreMiddleExtensions` — `boolean`, default `true`. When `true`, only the base name before the first dot is validated, so `ValidationRanges.test.ts` is valid and `my-component.test.ts` is renamed to `MyComponent.test.ts`. When `false`, every dot-separated segment must be PascalCase, so `ValidationRanges.test.ts` is renamed to `ValidationRanges.Test.ts` (same idea as `unicorn/filename-case`).
|
|
639
|
+
Allow them globally:
|
|
179
640
|
|
|
180
641
|
```ts
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
642
|
+
relations: {
|
|
643
|
+
allowTypeImports: true,
|
|
644
|
+
layers: { /* ... */ },
|
|
645
|
+
rules: [/* ... */],
|
|
646
|
+
}
|
|
185
647
|
```
|
|
186
648
|
|
|
649
|
+
Or for a specific layer:
|
|
650
|
+
|
|
651
|
+
```ts
|
|
652
|
+
{
|
|
653
|
+
from: 'controller',
|
|
654
|
+
allow: ['useCase'],
|
|
655
|
+
allowTypeImports: true,
|
|
656
|
+
}
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
### Deny rules and messages
|
|
660
|
+
|
|
661
|
+
```ts
|
|
662
|
+
relations: {
|
|
663
|
+
layers: {
|
|
664
|
+
controller: 'src/http/controllers',
|
|
665
|
+
repository: 'src/repositories',
|
|
666
|
+
},
|
|
667
|
+
rules: [
|
|
668
|
+
{
|
|
669
|
+
from: 'controller',
|
|
670
|
+
deny: ['repository'],
|
|
671
|
+
message: 'Controllers must use use-cases instead of repositories.',
|
|
672
|
+
},
|
|
673
|
+
],
|
|
674
|
+
}
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
### Aliases and TypeScript
|
|
678
|
+
|
|
679
|
+
When the project root has a `tsconfig.json`, the generated config uses `eslint-import-resolver-typescript`, so aliases such as `@/repositories/UserRepository` are resolved automatically.
|
|
680
|
+
|
|
681
|
+
Make sure the resolver is installed at the project level:
|
|
682
|
+
|
|
683
|
+
```bash
|
|
684
|
+
npm i -D eslint-import-resolver-typescript
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
For JS projects without `tsconfig.json`, the Node resolver is used.
|
|
688
|
+
|
|
689
|
+
`relations` uses `eslint-plugin-boundaries` under the hood and enables `boundaries/dependencies`.
|
|
690
|
+
|
|
691
|
+
## Expected diagnostics
|
|
692
|
+
|
|
693
|
+
Configuration warning:
|
|
694
|
+
|
|
695
|
+
```text
|
|
696
|
+
[eslint-plugin-ai-rules] Project structure validation is not configured.
|
|
697
|
+
Configure it with aiRules.configs.projects([{ rootFolder: '.', preset: 'frontend' | 'backend' }]).
|
|
698
|
+
If this project is not a frontend/backend, use preset: 'other' to disable this warning.
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
Structure violation:
|
|
702
|
+
|
|
703
|
+
```text
|
|
704
|
+
src/pages/MyFeature/Dashboard.tsx
|
|
705
|
+
error Folder "MyFeature" inside "src/pages" must match kebab-case. ai-rules/project-structure
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
Relation violation:
|
|
709
|
+
|
|
710
|
+
```text
|
|
711
|
+
src/http/controllers/ListProducts.ts
|
|
712
|
+
error Dependencies to elements of type "repository" are not allowed in elements of type "controller". boundaries/dependencies
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
Custom relation message:
|
|
716
|
+
|
|
717
|
+
```ts
|
|
718
|
+
{
|
|
719
|
+
from: 'controller',
|
|
720
|
+
deny: ['repository'],
|
|
721
|
+
message: 'Controllers must use use-cases instead of repositories.',
|
|
722
|
+
}
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
## Preset reference
|
|
726
|
+
|
|
727
|
+
### Frontend
|
|
728
|
+
|
|
729
|
+
```text
|
|
730
|
+
<root>/
|
|
731
|
+
├── root files: package.json, lockfiles, *.config.{js,mjs,cjs,ts},
|
|
732
|
+
│ tsconfig*.json, .env*, Dockerfile, entrypoint.sh, nginx.conf,
|
|
733
|
+
│ index.html, AGENTS.md, README.md, LICENSE
|
|
734
|
+
├── src/
|
|
735
|
+
│ ├── App.tsx, AppRoutes.tsx, main.tsx, index.css, vite-env.d.ts
|
|
736
|
+
│ ├── shared/
|
|
737
|
+
│ │ ├── components/
|
|
738
|
+
│ │ │ ├── *.tsx
|
|
739
|
+
│ │ │ └── ui/*.{ts,tsx}
|
|
740
|
+
│ │ ├── constants/*.ts
|
|
741
|
+
│ │ ├── contexts/*.tsx
|
|
742
|
+
│ │ ├── env/index.ts
|
|
743
|
+
│ │ ├── helpers/*.ts
|
|
744
|
+
│ │ ├── hooks/*.ts
|
|
745
|
+
│ │ ├── services/
|
|
746
|
+
│ │ │ ├── generated/** (ignored)
|
|
747
|
+
│ │ │ └── utils/*.ts
|
|
748
|
+
│ │ └── utils/*.ts
|
|
749
|
+
│ └── pages/
|
|
750
|
+
│ ├── *.tsx
|
|
751
|
+
│ └── <feature-kebab>/
|
|
752
|
+
│ ├── *.tsx, helpers.ts, schema.ts, types.ts
|
|
753
|
+
│ ├── components/*.tsx
|
|
754
|
+
│ ├── hooks/*.ts
|
|
755
|
+
│ ├── services/*.ts
|
|
756
|
+
│ ├── contexts/*.tsx
|
|
757
|
+
│ ├── helpers/*.ts
|
|
758
|
+
│ └── tabs/<tab-kebab>/
|
|
759
|
+
│ ├── *.tsx
|
|
760
|
+
│ └── <section-kebab>/
|
|
761
|
+
│ ├── *.tsx, *.ts
|
|
762
|
+
│ ├── components/*.tsx
|
|
763
|
+
│ └── contents/
|
|
764
|
+
│ ├── *.tsx
|
|
765
|
+
│ └── components/*.tsx
|
|
766
|
+
├── e2e/
|
|
767
|
+
│ ├── .auth/** (ignored)
|
|
768
|
+
│ ├── fixtures/*
|
|
769
|
+
│ ├── pages/*.ts
|
|
770
|
+
│ ├── specs/*.spec.ts
|
|
771
|
+
│ └── support/*.ts
|
|
772
|
+
│ └── api/*.ts
|
|
773
|
+
├── scripts/*.ts
|
|
774
|
+
├── scripts/types/** (ignored)
|
|
775
|
+
├── public/** (ignored)
|
|
776
|
+
└── .github/**, .vscode/** (ignored)
|
|
777
|
+
```
|
|
778
|
+
|
|
779
|
+
### Backend
|
|
780
|
+
|
|
781
|
+
```text
|
|
782
|
+
<root>/
|
|
783
|
+
├── root files: package.json, lockfiles, *.config.{js,mjs,cjs,ts},
|
|
784
|
+
│ tsconfig*.json, .env*, Dockerfile, .dockerignore,
|
|
785
|
+
│ AGENTS.md, README.md, LICENSE
|
|
786
|
+
├── src/
|
|
787
|
+
│ ├── index.ts, Server.ts, Schemas.ts
|
|
788
|
+
│ ├── env/index.ts
|
|
789
|
+
│ ├── helpers/*.ts
|
|
790
|
+
│ ├── @types/*.d.ts
|
|
791
|
+
│ ├── database/
|
|
792
|
+
│ │ ├── Connection.ts
|
|
793
|
+
│ │ ├── schema/index.ts, *.ts
|
|
794
|
+
│ │ ├── seeds/index.ts
|
|
795
|
+
│ │ └── migrations/** (ignored)
|
|
796
|
+
│ ├── repositories/Setup.ts, *.ts
|
|
797
|
+
│ ├── http/
|
|
798
|
+
│ │ ├── Routes.ts
|
|
799
|
+
│ │ ├── controllers/Health.ts
|
|
800
|
+
│ │ ├── controllers/<feature-kebab>/
|
|
801
|
+
│ │ │ ├── *.ts
|
|
802
|
+
│ │ │ └── <resource-kebab>/*.ts
|
|
803
|
+
│ │ ├── hooks/*.ts
|
|
804
|
+
│ │ └── types/*.d.ts
|
|
805
|
+
│ ├── services/*.ts
|
|
806
|
+
│ ├── services/<service-kebab>/index.ts, *.ts
|
|
807
|
+
│ ├── services/generated/** (ignored)
|
|
808
|
+
│ └── use-case/
|
|
809
|
+
│ ├── Errors.ts
|
|
810
|
+
│ └── <feature-kebab>/
|
|
811
|
+
│ ├── *.ts
|
|
812
|
+
│ └── <resource-kebab>/*.ts
|
|
813
|
+
├── test/*.test.ts, GlobalSetup.ts, Setup.ts, ProvidedContext.d.ts
|
|
814
|
+
├── test/Helpers/*.ts
|
|
815
|
+
├── docs/** (ignored)
|
|
816
|
+
├── scripts/*.ts
|
|
817
|
+
├── uploads/** (ignored)
|
|
818
|
+
└── .github/**, .vscode/** (ignored)
|
|
819
|
+
```
|
|
820
|
+
|
|
821
|
+
### Other
|
|
822
|
+
|
|
823
|
+
`preset: 'other'` has an empty structure. It disables validation and suppresses the configuration warning. If you provide a `structure`, it validates exactly what you declared.
|
|
824
|
+
|
|
825
|
+
## Structure DSL
|
|
826
|
+
|
|
827
|
+
```ts
|
|
828
|
+
type TProjectPreset = 'frontend' | 'backend' | 'other'
|
|
829
|
+
|
|
830
|
+
type TNameCase = 'PascalCase' | 'camelCase' | 'kebab-case' | 'snake_case'
|
|
831
|
+
|
|
832
|
+
interface INameRule {
|
|
833
|
+
case?: TNameCase
|
|
834
|
+
prefix?: string
|
|
835
|
+
suffix?: string
|
|
836
|
+
pattern?: string
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
type TFileRule =
|
|
840
|
+
| string
|
|
841
|
+
| ({ extensions: string[] } & INameRule)
|
|
842
|
+
|
|
843
|
+
interface IStructureNode {
|
|
844
|
+
name?: INameRule
|
|
845
|
+
files?: TFileRule[]
|
|
846
|
+
additionalFiles?: TFileRule[]
|
|
847
|
+
folders?: Record<string, IStructureNode>
|
|
848
|
+
ignore?: boolean
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
interface IProjectStructure extends IStructureNode {
|
|
852
|
+
ignoredPaths?: string[]
|
|
853
|
+
}
|
|
854
|
+
```
|
|
855
|
+
|
|
856
|
+
### Folder keys
|
|
857
|
+
|
|
858
|
+
Literal keys match exactly:
|
|
859
|
+
|
|
860
|
+
```ts
|
|
861
|
+
folders: {
|
|
862
|
+
components: { files: ['*.tsx'] },
|
|
863
|
+
}
|
|
864
|
+
```
|
|
865
|
+
|
|
866
|
+
Dynamic keys use brackets:
|
|
867
|
+
|
|
868
|
+
```ts
|
|
869
|
+
folders: {
|
|
870
|
+
'[feature]': {
|
|
871
|
+
name: { case: 'kebab-case' },
|
|
872
|
+
files: ['*.tsx'],
|
|
873
|
+
},
|
|
874
|
+
}
|
|
875
|
+
```
|
|
876
|
+
|
|
877
|
+
- Only one dynamic folder is allowed per level.
|
|
878
|
+
- `name` applies to dynamic folders.
|
|
879
|
+
- A static folder key cannot have a `name` rule.
|
|
880
|
+
- A dynamic folder without `name` accepts any folder name.
|
|
881
|
+
|
|
882
|
+
### File rules
|
|
883
|
+
|
|
884
|
+
Strings match exact names or globs:
|
|
885
|
+
|
|
886
|
+
```ts
|
|
887
|
+
files: [
|
|
888
|
+
'index.ts',
|
|
889
|
+
'*.spec.ts',
|
|
890
|
+
'*.config.{js,mjs,cjs,ts}',
|
|
891
|
+
]
|
|
892
|
+
```
|
|
893
|
+
|
|
894
|
+
Supported glob syntax: `*`, `**`, `?` and `{a,b}`.
|
|
895
|
+
|
|
896
|
+
Object rules require `extensions` and validate the base name:
|
|
897
|
+
|
|
898
|
+
```ts
|
|
899
|
+
files: [
|
|
900
|
+
{
|
|
901
|
+
extensions: ['.ts', '.tsx'],
|
|
902
|
+
case: 'PascalCase',
|
|
903
|
+
},
|
|
904
|
+
{
|
|
905
|
+
extensions: ['.ts'],
|
|
906
|
+
case: 'PascalCase',
|
|
907
|
+
suffix: 'UseCase',
|
|
908
|
+
},
|
|
909
|
+
{
|
|
910
|
+
extensions: ['.ts'],
|
|
911
|
+
case: 'PascalCase',
|
|
912
|
+
prefix: 'Use',
|
|
913
|
+
},
|
|
914
|
+
{
|
|
915
|
+
extensions: ['.ts'],
|
|
916
|
+
pattern: '^(Create|Read|Update|Delete|List)[A-Z][A-Za-z0-9]*$',
|
|
917
|
+
},
|
|
918
|
+
]
|
|
919
|
+
```
|
|
920
|
+
|
|
921
|
+
- `extensions` also supports compound extensions: `.d.ts`, `.test.mjs`, `.spec.ts`.
|
|
922
|
+
- `case`, `prefix`, `suffix` and `pattern` apply to the base name after removing the extension.
|
|
923
|
+
- A file is valid when it matches at least one rule completely.
|
|
924
|
+
|
|
925
|
+
### Merge behavior
|
|
926
|
+
|
|
927
|
+
| Field | Behavior |
|
|
928
|
+
|-------|----------|
|
|
929
|
+
| `folders` | recursive merge by key; new keys are added |
|
|
930
|
+
| `files` | replaces the preset files at that node |
|
|
931
|
+
| `additionalFiles` | appends to the preset files |
|
|
932
|
+
| `name` | fields override the preset |
|
|
933
|
+
| `ignore` | overrides the preset |
|
|
934
|
+
| `ignoredPaths` | union of preset and custom paths |
|
|
935
|
+
|
|
936
|
+
## Limitations
|
|
937
|
+
|
|
938
|
+
- Structure validation only runs for files ESLint actually lints.
|
|
939
|
+
- Required empty folders are not detected.
|
|
940
|
+
- Glob matching supports `*`, `**`, `?` and `{a,b}` only.
|
|
941
|
+
- Nested roots are supported, but duplicate roots are rejected.
|
|
942
|
+
- Relation layers must point to folders, not individual files.
|
|
943
|
+
- Scoped layers must use patterns with a wildcard, such as `src/pages/*`.
|
|
944
|
+
- TypeScript aliases in `relations` require the project `tsconfig.json`.
|
|
945
|
+
- Type-only imports follow the same restrictions by default.
|
|
946
|
+
- The backend preset is strict and will flag existing imports that violate AGENTS.md.
|
|
947
|
+
- The frontend preset allows `utils` and `helpers` to import each other, which can create cycles.
|
|
948
|
+
- ESLint versions below 9.22 are not supported.
|
|
949
|
+
|
|
950
|
+
## Rules
|
|
951
|
+
|
|
952
|
+
### `ai-rules/project-structure`
|
|
953
|
+
|
|
954
|
+
Validates the configured hierarchy, folder names and file names.
|
|
955
|
+
|
|
956
|
+
- Type: `problem`
|
|
957
|
+
- No autofix
|
|
958
|
+
- Reads `settings['ai-rules'].project` or `settings['ai-rules'].projects`
|
|
959
|
+
- Messages: `folderNotAllowed`, `folderNameMismatch`, `fileNotAllowed`, `fileNameMismatch`
|
|
960
|
+
- Ignores files outside the project root, `ignoredPaths` and `ignore: true` subtrees
|
|
961
|
+
|
|
962
|
+
### `ai-rules/project-setup`
|
|
963
|
+
|
|
964
|
+
Prints the configuration warning described above.
|
|
965
|
+
|
|
966
|
+
- Type: `suggestion`
|
|
967
|
+
- Enabled as `warn` in `recommended`
|
|
968
|
+
- Does not report lint messages
|
|
969
|
+
- Warns once per process
|
|
970
|
+
- Suppressed by `preset: 'other'`, `frontend`, `backend` or a non-empty custom structure
|
|
971
|
+
|
|
972
|
+
### `ai-rules/filename-pascal-case`
|
|
973
|
+
|
|
974
|
+
Enforces PascalCase for file names.
|
|
975
|
+
|
|
976
|
+
- Ignores `index.{ts,tsx,js,jsx}`, `*.d.ts`, `*.css` and dotfiles.
|
|
977
|
+
- Options:
|
|
978
|
+
- `ignore`: regex strings matched against the basename and relative path.
|
|
979
|
+
- `ignoreMiddleExtensions`: defaults to `true`, so `ValidationRanges.test.ts` is valid.
|
|
980
|
+
|
|
187
981
|
```ts
|
|
188
982
|
'ai-rules/filename-pascal-case': [
|
|
189
983
|
'error',
|
|
@@ -194,86 +988,224 @@ Enforces `PascalCase` for file names. The rule is file-agnostic: it validates ev
|
|
|
194
988
|
]
|
|
195
989
|
```
|
|
196
990
|
|
|
197
|
-
|
|
991
|
+
`projects()` disables this rule because `project-structure` enforces file names.
|
|
992
|
+
|
|
993
|
+
### `ai-rules/no-multiline-imports`
|
|
994
|
+
|
|
995
|
+
Disallows multiline import declarations. Fixable.
|
|
198
996
|
|
|
199
997
|
```ts
|
|
200
|
-
|
|
201
|
-
import {
|
|
998
|
+
// before
|
|
999
|
+
import {
|
|
1000
|
+
b,
|
|
1001
|
+
a,
|
|
1002
|
+
} from 'lodash'
|
|
202
1003
|
|
|
1004
|
+
// after --fix
|
|
1005
|
+
import { b, a } from 'lodash'
|
|
1006
|
+
```
|
|
1007
|
+
|
|
1008
|
+
### `ai-rules/sort-imports`
|
|
1009
|
+
|
|
1010
|
+
Sorts imports by group and line length descending, collapsing multiline imports. Fixable.
|
|
1011
|
+
|
|
1012
|
+
Groups:
|
|
1013
|
+
|
|
1014
|
+
1. Directives (`'use client'`, `'use server'`, `'use strict'`)
|
|
1015
|
+
2. External packages (`react` first, then by line length desc)
|
|
1016
|
+
3. Internal imports (`.`, `@/`)
|
|
1017
|
+
4. Re-exports
|
|
1018
|
+
|
|
1019
|
+
## Bundled rules from other plugins
|
|
1020
|
+
|
|
1021
|
+
`recommended` also registers and applies these rules. They existed before project structure support and remain part of the default setup.
|
|
1022
|
+
|
|
1023
|
+
| Rule | Default | Purpose |
|
|
1024
|
+
|------|---------|---------|
|
|
1025
|
+
| `import-x/no-duplicates` | `error` | Prevent importing the same module twice |
|
|
1026
|
+
| `import-x/no-default-export` | `off` | Default exports are allowed |
|
|
1027
|
+
| `max-len` | `off` | No line length limit |
|
|
1028
|
+
| `@stylistic/semi` | `['error', 'never']` | No semicolons |
|
|
1029
|
+
| `@typescript-eslint/consistent-type-imports` | `['error', { prefer: 'type-imports' }]` | Prefer `import type` |
|
|
1030
|
+
| `@typescript-eslint/no-unused-vars` | `['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }]` | Ignore unused values prefixed with `_` |
|
|
1031
|
+
| `@typescript-eslint/naming-convention` | interfaces `I` prefix, type aliases `T` prefix | Enforce `IUser` and `TUser` style |
|
|
1032
|
+
|
|
1033
|
+
When `relations` is configured, `projects()` also registers `eslint-plugin-boundaries` and enables `boundaries/dependencies` with the generated layer policies.
|
|
1034
|
+
|
|
1035
|
+
The plugin also configures the `import-x` resolver with:
|
|
1036
|
+
|
|
1037
|
+
```ts
|
|
1038
|
+
extensions: ['.ts', '.tsx', '.d.ts', '.js', '.jsx']
|
|
1039
|
+
```
|
|
1040
|
+
|
|
1041
|
+
Override any bundled rule after the preset:
|
|
1042
|
+
|
|
1043
|
+
```ts
|
|
203
1044
|
export default defineConfig([
|
|
204
|
-
...
|
|
205
|
-
{
|
|
1045
|
+
...aiRules.configs.projects([{ rootFolder: '.', preset: 'frontend' }]),
|
|
1046
|
+
{
|
|
1047
|
+
rules: {
|
|
1048
|
+
'@stylistic/semi': ['error', 'always'],
|
|
1049
|
+
'import-x/no-default-export': 'off',
|
|
1050
|
+
},
|
|
1051
|
+
},
|
|
206
1052
|
])
|
|
207
1053
|
```
|
|
208
1054
|
|
|
209
|
-
|
|
1055
|
+
## What runs automatically
|
|
210
1056
|
|
|
211
|
-
|
|
1057
|
+
| You use | What is enabled |
|
|
1058
|
+
|---------|-----------------|
|
|
1059
|
+
| `aiRules.configs.recommended` | bundled rules + configuration warning |
|
|
1060
|
+
| `aiRules.configs.projects([...])` | recommended rules + `project-structure` |
|
|
1061
|
+
| `projects([...])` with `preset: 'frontend' \| 'backend'` | preset structure + preset relations |
|
|
1062
|
+
| `projects([...])` with `structure` | structure validation |
|
|
1063
|
+
| `projects([...])` with `relations` | `boundaries/dependencies` |
|
|
1064
|
+
| `projects([...])` with both | structure + relations |
|
|
212
1065
|
|
|
213
|
-
|
|
214
|
-
|
|
1066
|
+
`projects()` does **not** configure the TypeScript parser. Pair it with `typescript-eslint` as shown in the examples.
|
|
1067
|
+
|
|
1068
|
+
## Recommended preset
|
|
1069
|
+
|
|
1070
|
+
`aiRules.configs.recommended` is file-agnostic. It provides:
|
|
1071
|
+
|
|
1072
|
+
- plugins: `@stylistic`, `import-x`, `@typescript-eslint`, `ai-rules`
|
|
1073
|
+
- `settings['import-x/resolver-next']`
|
|
1074
|
+
- the rule set documented above
|
|
1075
|
+
|
|
1076
|
+
It does **not** configure:
|
|
1077
|
+
|
|
1078
|
+
- `files`
|
|
1079
|
+
- `ignores`
|
|
1080
|
+
- parser
|
|
1081
|
+
- language options
|
|
1082
|
+
- globals
|
|
1083
|
+
|
|
1084
|
+
Pair it with `typescript-eslint` or `@typescript-eslint/parser` and define `files` in your config. `aiRules.configs.projects([...])` already does the file scoping for you.
|
|
1085
|
+
|
|
1086
|
+
If you want the recommended rules without project structure validation, use `projects([{ rootFolder: '.', preset: 'other' }])`.
|
|
1087
|
+
|
|
1088
|
+
## Troubleshooting
|
|
1089
|
+
|
|
1090
|
+
### The configuration warning keeps appearing
|
|
1091
|
+
|
|
1092
|
+
Use a valid project configuration:
|
|
215
1093
|
|
|
216
1094
|
```ts
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
} from 'lodash'
|
|
1095
|
+
...aiRules.configs.projects([
|
|
1096
|
+
{ rootFolder: '.', preset: 'frontend' },
|
|
1097
|
+
])
|
|
1098
|
+
```
|
|
222
1099
|
|
|
223
|
-
|
|
224
|
-
|
|
1100
|
+
Or explicitly opt out:
|
|
1101
|
+
|
|
1102
|
+
```ts
|
|
1103
|
+
...aiRules.configs.projects([
|
|
1104
|
+
{ rootFolder: '.', preset: 'other' },
|
|
1105
|
+
])
|
|
225
1106
|
```
|
|
226
1107
|
|
|
227
|
-
|
|
1108
|
+
A project with only `rootFolder` and an empty `structure` still warns.
|
|
228
1109
|
|
|
229
|
-
|
|
1110
|
+
For temporary runs or CI, use:
|
|
230
1111
|
|
|
231
|
-
|
|
1112
|
+
```bash
|
|
1113
|
+
AI_RULES_SILENT=1 npx eslint .
|
|
1114
|
+
```
|
|
1115
|
+
|
|
1116
|
+
### `project-structure` is not running
|
|
1117
|
+
|
|
1118
|
+
Make sure you are using `aiRules.configs.projects([...])` or manually setting `settings['ai-rules'].project` and enabling `ai-rules/project-structure`.
|
|
232
1119
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
2. External (`react` first, then other externals sorted by `line.length` desc → `localeCompare`)
|
|
237
|
-
3. Internal (`.`, `@/` sorted same)
|
|
238
|
-
4. Re-exports (`export { ... } from`, `export * from` sorted same)
|
|
239
|
-
- **Rest:** code after imports, joined with `\n\n\n` (two blank lines between imports and code)
|
|
240
|
-
- **Directives:** preserved at top while `canAcceptDirectives` is true
|
|
1120
|
+
### Type-aware rule error
|
|
1121
|
+
|
|
1122
|
+
`@typescript-eslint/consistent-type-imports` needs the TypeScript parser. Pair the plugin with:
|
|
241
1123
|
|
|
242
1124
|
```ts
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
1125
|
+
...tseslint.configs.recommended
|
|
1126
|
+
```
|
|
1127
|
+
|
|
1128
|
+
### Missing `dist` before lint
|
|
247
1129
|
|
|
248
|
-
|
|
249
|
-
import React from 'react'
|
|
250
|
-
import { a } from 'lodash'
|
|
1130
|
+
This repository imports the local build from `dist`. Consumers should import the published package:
|
|
251
1131
|
|
|
252
|
-
|
|
1132
|
+
```ts
|
|
1133
|
+
import aiRules from 'eslint-plugin-ai-rules'
|
|
253
1134
|
```
|
|
254
1135
|
|
|
255
|
-
|
|
1136
|
+
### File ignored because outside of base path
|
|
256
1137
|
|
|
257
|
-
|
|
1138
|
+
Run ESLint from the project root, or configure the correct `basePath`.
|
|
258
1139
|
|
|
259
|
-
|
|
1140
|
+
### Flat config only
|
|
260
1141
|
|
|
261
|
-
|
|
1142
|
+
This plugin exports flat config arrays. It does not support legacy `.eslintrc`.
|
|
1143
|
+
|
|
1144
|
+
### Filename rule too strict
|
|
1145
|
+
|
|
1146
|
+
Use the `ignore` option or disable `ai-rules/filename-pascal-case` for tool-mandated files:
|
|
262
1147
|
|
|
263
|
-
|
|
1148
|
+
```ts
|
|
1149
|
+
{
|
|
1150
|
+
files: ['*.config.*', 'scripts/**/*'],
|
|
1151
|
+
rules: {
|
|
1152
|
+
'ai-rules/filename-pascal-case': 'off',
|
|
1153
|
+
},
|
|
1154
|
+
}
|
|
264
1155
|
```
|
|
265
|
-
|
|
1156
|
+
|
|
1157
|
+
## Common mistakes
|
|
1158
|
+
|
|
1159
|
+
### Using `recommended` without `projects()`
|
|
1160
|
+
|
|
1161
|
+
You get the configuration warning and no project structure validation. Use `projects([...])` or explicitly opt out with `preset: 'other'`.
|
|
1162
|
+
|
|
1163
|
+
### Forgetting the TypeScript parser
|
|
1164
|
+
|
|
1165
|
+
`aiRules.configs.recommended` does not configure `languageOptions.parser`. Pair it with `tseslint.configs.recommended` or a parser.
|
|
1166
|
+
|
|
1167
|
+
### Aliases are not enforced in relations
|
|
1168
|
+
|
|
1169
|
+
Make sure the project has `tsconfig.json` with `paths`, and install `eslint-import-resolver-typescript`.
|
|
1170
|
+
|
|
1171
|
+
### A relation layer does not match
|
|
1172
|
+
|
|
1173
|
+
Layers must point to folders, not files:
|
|
1174
|
+
|
|
1175
|
+
```ts
|
|
1176
|
+
layers: {
|
|
1177
|
+
controller: 'src/http/controllers', // correct
|
|
1178
|
+
// controller: 'src/http/controllers/*.ts', // avoid
|
|
1179
|
+
}
|
|
266
1180
|
```
|
|
267
|
-
Fix: `npm run build` first. This repo's `eslint.config.mjs` imports from `dist` (no `jiti`), so `prelint` runs `build` automatically (`package.json` `prelint`).
|
|
268
1181
|
|
|
269
|
-
|
|
270
|
-
|
|
1182
|
+
### Type-only imports are blocked
|
|
1183
|
+
|
|
1184
|
+
That is the default. Set `allowTypeImports: true` globally or per rule.
|
|
1185
|
+
|
|
1186
|
+
### An external package or unknown file is not blocked
|
|
1187
|
+
|
|
1188
|
+
`relations` only checks known local layers. External packages, Node core modules, same-layer imports and files outside any layer are allowed.
|
|
1189
|
+
|
|
1190
|
+
### Duplicate or overlapping roots
|
|
271
1191
|
|
|
272
|
-
|
|
273
|
-
This plugin exports flat config arrays. It does not support legacy `.eslintrc`. Use `eslint.config.{js,ts}`.
|
|
1192
|
+
Nested roots are supported, but duplicate roots are rejected. Use the most specific configuration for each project.
|
|
274
1193
|
|
|
275
|
-
|
|
276
|
-
|
|
1194
|
+
## Migrating from 0.2.x
|
|
1195
|
+
|
|
1196
|
+
1. Replace `aiRules.configs.recommended` with `aiRules.configs.projects([...])`.
|
|
1197
|
+
2. Choose a preset: `frontend`, `backend`, or `other`.
|
|
1198
|
+
3. Set `rootFolder` to the folder containing `package.json`.
|
|
1199
|
+
4. Add `relations` if you want layer boundaries.
|
|
1200
|
+
5. Keep pairing with `typescript-eslint`.
|
|
1201
|
+
|
|
1202
|
+
If you intentionally do not want structure validation:
|
|
1203
|
+
|
|
1204
|
+
```ts
|
|
1205
|
+
...aiRules.configs.projects([
|
|
1206
|
+
{ rootFolder: '.', preset: 'other' },
|
|
1207
|
+
])
|
|
1208
|
+
```
|
|
277
1209
|
|
|
278
1210
|
## Development
|
|
279
1211
|
|
|
@@ -282,60 +1214,107 @@ git clone <repo>
|
|
|
282
1214
|
cd eslint-ai-guard
|
|
283
1215
|
npm install
|
|
284
1216
|
|
|
285
|
-
npm run typecheck
|
|
286
|
-
npm test
|
|
287
|
-
npm run lint
|
|
1217
|
+
npm run typecheck # tsc --noEmit
|
|
1218
|
+
npm test # node:test against dist
|
|
1219
|
+
npm run lint # eslint .
|
|
288
1220
|
npm run lint -- --fix
|
|
289
|
-
npm run build
|
|
290
|
-
npm run dev
|
|
291
|
-
npm run verify
|
|
292
|
-
npm
|
|
1221
|
+
npm run build # tsup -> ESM + CJS + declarations
|
|
1222
|
+
npm run dev # tsup --watch
|
|
1223
|
+
npm run verify # typecheck + test + lint + pack
|
|
1224
|
+
npm run test:coverage # c8 coverage with thresholds
|
|
1225
|
+
npm run test:consumer # install packed tarball in a temp project
|
|
1226
|
+
npm run test:package # publint + arethetypeswrong
|
|
1227
|
+
npm run test:release # consumer + package checks
|
|
1228
|
+
npm pack --dry-run # verify published files
|
|
293
1229
|
```
|
|
294
1230
|
|
|
295
|
-
|
|
1231
|
+
`test:consumer` and `test:package` are release checks and are not part of `verify`. They may use the network.
|
|
1232
|
+
|
|
1233
|
+
The consumer smoke test covers structure validation, relations with aliases and type-only imports, warning/suppression, ESM and CJS. It accepts `ESLINT_VERSION`:
|
|
296
1234
|
|
|
1235
|
+
```bash
|
|
1236
|
+
ESLINT_VERSION=9.22.0 npm run test:consumer
|
|
1237
|
+
ESLINT_VERSION='^10.0.0' npm run test:consumer
|
|
297
1238
|
```
|
|
298
|
-
|
|
1239
|
+
|
|
1240
|
+
CI runs `verify`, coverage and consumer checks on Node 20, 22 and 24.
|
|
1241
|
+
|
|
1242
|
+
### Project structure
|
|
1243
|
+
|
|
1244
|
+
```text
|
|
1245
|
+
eslint-ai-guard/
|
|
299
1246
|
├── src/
|
|
300
|
-
│ ├── index.ts
|
|
301
|
-
│ ├── plugin.ts
|
|
1247
|
+
│ ├── index.ts
|
|
1248
|
+
│ ├── plugin.ts
|
|
302
1249
|
│ ├── configs/
|
|
303
|
-
│ │ ├── recommended.ts
|
|
1250
|
+
│ │ ├── recommended.ts
|
|
1251
|
+
│ │ ├── projects.ts
|
|
304
1252
|
│ │ └── index.ts
|
|
1253
|
+
│ ├── types/
|
|
1254
|
+
│ │ └── structure.ts
|
|
1255
|
+
│ ├── structures/
|
|
1256
|
+
│ │ ├── buildRelationsConfig.ts
|
|
1257
|
+
│ │ ├── hasStructureRules.ts
|
|
1258
|
+
│ │ ├── mergeRelations.ts
|
|
1259
|
+
│ │ ├── mergeStructure.ts
|
|
1260
|
+
│ │ ├── validateRelations.ts
|
|
1261
|
+
│ │ ├── validateStructure.ts
|
|
1262
|
+
│ │ └── presets/
|
|
1263
|
+
│ │ ├── frontend.ts
|
|
1264
|
+
│ │ ├── backend.ts
|
|
1265
|
+
│ │ ├── other.ts
|
|
1266
|
+
│ │ ├── helpers.ts
|
|
1267
|
+
│ │ └── index.ts
|
|
1268
|
+
│ ├── utils/
|
|
305
1269
|
│ └── rules/
|
|
306
1270
|
│ ├── FilenamePascalCase.ts
|
|
307
1271
|
│ ├── NoMultilineImports.ts
|
|
308
|
-
│ ├──
|
|
1272
|
+
│ ├── ProjectSetup.ts
|
|
1273
|
+
│ ├── ProjectStructure.ts
|
|
1274
|
+
│ ├── SortImports.ts
|
|
309
1275
|
│ └── index.ts
|
|
310
|
-
├── test/
|
|
311
|
-
├──
|
|
312
|
-
|
|
313
|
-
├──
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
1276
|
+
├── test/
|
|
1277
|
+
├── scripts/
|
|
1278
|
+
│ └── TestConsumer.mjs
|
|
1279
|
+
├── .github/
|
|
1280
|
+
│ └── workflows/
|
|
1281
|
+
│ └── ci.yml
|
|
1282
|
+
├── eslint/
|
|
1283
|
+
├── CHANGELOG.md
|
|
1284
|
+
├── eslint.config.mjs
|
|
1285
|
+
├── tsconfig.json
|
|
1286
|
+
├── tsup.config.ts
|
|
1287
|
+
├── package.json
|
|
1288
|
+
└── dist/
|
|
317
1289
|
```
|
|
318
1290
|
|
|
319
1291
|
### Adding a new rule
|
|
320
1292
|
|
|
321
|
-
1. Create `src/rules/MyRule.ts
|
|
322
|
-
2. Export it from `src/rules/index.ts
|
|
323
|
-
3. Register in `src/plugin.ts
|
|
324
|
-
4.
|
|
325
|
-
5. Add
|
|
326
|
-
6. `npm run verify
|
|
1293
|
+
1. Create `src/rules/MyRule.ts`.
|
|
1294
|
+
2. Export it from `src/rules/index.ts`.
|
|
1295
|
+
3. Register it in `src/plugin.ts`.
|
|
1296
|
+
4. Add it to `src/configs/recommended.ts` if it should be enabled by default.
|
|
1297
|
+
5. Add tests in `test/`.
|
|
1298
|
+
6. Run `npm run verify`.
|
|
1299
|
+
|
|
1300
|
+
## Publishing
|
|
1301
|
+
|
|
1302
|
+
1. Change `version` in `package.json`.
|
|
1303
|
+
2. Optionally run `yarn build` to check the build and generated version.
|
|
1304
|
+
3. Run `npm publish`.
|
|
1305
|
+
|
|
1306
|
+
`prepack` runs `yarn build` automatically, so `npm publish` always packs a fresh `dist`. The plugin version is injected from `package.json` during the build, so you do not need to edit `src/plugin.ts`.
|
|
327
1307
|
|
|
328
|
-
|
|
1308
|
+
For an extra check before publishing:
|
|
329
1309
|
|
|
330
1310
|
```bash
|
|
331
|
-
|
|
332
|
-
npm
|
|
333
|
-
# or manually: npm run verify && npm publish --access public
|
|
1311
|
+
yarn verify
|
|
1312
|
+
npm pack --dry-run
|
|
334
1313
|
```
|
|
335
1314
|
|
|
336
1315
|
## Legacy
|
|
337
1316
|
|
|
338
|
-
`eslint/` keeps the three original rule
|
|
1317
|
+
`eslint/` keeps the three original rule implementations before packaging. Source of truth is `src/rules/`.
|
|
339
1318
|
|
|
340
1319
|
## License
|
|
341
1320
|
|