@aiready/consistency 0.3.0 → 0.3.3

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.
@@ -1,6 +1,6 @@
1
1
 
2
2
  
3
- > @aiready/consistency@0.3.0 build /Users/pengcao/projects/aiready/packages/consistency
3
+ > @aiready/consistency@0.3.3 build /Users/pengcao/projects/aiready/packages/consistency
4
4
  > tsup src/index.ts src/cli.ts --format cjs,esm --dts
5
5
 
6
6
  CLI Building entry: src/cli.ts, src/index.ts
@@ -9,15 +9,15 @@
9
9
  CLI Target: es2020
10
10
  CJS Build start
11
11
  ESM Build start
12
- CJS dist/index.js 14.13 KB
13
- CJS dist/cli.js 23.02 KB
14
- CJS ⚡️ Build success in 57ms
15
- ESM dist/index.mjs 220.00 B
12
+ CJS dist/cli.js 25.23 KB
13
+ CJS dist/index.js 16.34 KB
14
+ CJS ⚡️ Build success in 79ms
15
+ ESM dist/chunk-LUAREV6A.mjs 15.11 KB
16
16
  ESM dist/cli.mjs 8.54 KB
17
- ESM dist/chunk-CF4LU7KE.mjs 12.90 KB
18
- ESM ⚡️ Build success in 57ms
17
+ ESM dist/index.mjs 220.00 B
18
+ ESM ⚡️ Build success in 79ms
19
19
  DTS Build start
20
- DTS ⚡️ Build success in 558ms
20
+ DTS ⚡️ Build success in 587ms
21
21
  DTS dist/cli.d.ts 20.00 B
22
22
  DTS dist/index.d.ts 2.60 KB
23
23
  DTS dist/cli.d.mts 20.00 B
@@ -1,17 +1,17 @@
1
1
 
2
2
  
3
- > @aiready/consistency@0.3.0 test /Users/pengcao/projects/aiready/packages/consistency
3
+ > @aiready/consistency@0.3.3 test /Users/pengcao/projects/aiready/packages/consistency
4
4
  > vitest run
5
5
 
6
6
 
7
7
   RUN  v2.1.9 /Users/pengcao/projects/aiready/packages/consistency
8
8
 
9
- [?25l ❯ src/__tests__/analyzer.test.ts (14)
10
- ❯ analyzeConsistency (2)
9
+ [?25l ❯ analyzeConsistency (2)
11
10
  ⠙ should analyze naming issues
12
11
  · should detect minimum severity filtering
13
- · analyzeNaming (4)
12
+ · analyzeNaming (5)
14
13
  · should detect single letter variables
14
+ · should NOT flag acceptable abbreviations
15
15
  · should detect snake_case in TypeScript files
16
16
  · should detect unclear boolean names
17
17
  · should allow common abbreviations
@@ -26,12 +26,33 @@
26
26
  · should generate relevant recommendations
27
27
  · should suggest standardizing error handling
28
28
  · should suggest using async/await consistently
29
-  ✓ src/__tests__/analyzer.test.ts (14)
29
+ [?25l ✓ analyzeConsistency (2)
30
+ ✓ should analyze naming issues
31
+ ✓ should detect minimum severity filtering
32
+ ✓ analyzeNaming (5)
33
+ ✓ should detect single letter variables
34
+ ✓ should NOT flag acceptable abbreviations
35
+ ✓ should detect snake_case in TypeScript files
36
+ ✓ should detect unclear boolean names
37
+ ✓ should allow common abbreviations
38
+ ✓ analyzePatterns (3)
39
+ ✓ should detect mixed error handling
40
+ ✓ should detect mixed async patterns
41
+ ✓ should detect mixed import styles
42
+ ✓ consistency scoring (2)
43
+ ✓ should calculate consistency score correctly
44
+ ✓ should weight critical issues more than info
45
+ ✓ recommendations (3)
46
+ ✓ should generate relevant recommendations
47
+ ✓ should suggest standardizing error handling
48
+ ✓ should suggest using async/await consistently
49
+  ✓ src/__tests__/analyzer.test.ts (15)
30
50
  ✓ analyzeConsistency (2)
31
51
  ✓ should analyze naming issues
32
52
  ✓ should detect minimum severity filtering
33
- ✓ analyzeNaming (4)
53
+ ✓ analyzeNaming (5)
34
54
  ✓ should detect single letter variables
55
+ ✓ should NOT flag acceptable abbreviations
35
56
  ✓ should detect snake_case in TypeScript files
36
57
  ✓ should detect unclear boolean names
37
58
  ✓ should allow common abbreviations
@@ -48,8 +69,8 @@
48
69
  ✓ should suggest using async/await consistently
49
70
 
50
71
   Test Files  1 passed (1)
51
-  Tests  14 passed (14)
52
-  Start at  07:46:28
53
-  Duration  441ms (transform 95ms, setup 0ms, collect 166ms, tests 25ms, environment 0ms, prepare 49ms)
72
+  Tests  15 passed (15)
73
+  Start at  15:32:25
74
+  Duration  519ms (transform 74ms, setup 0ms, collect 251ms, tests 24ms, environment 0ms, prepare 54ms)
54
75
 
55
76
  [?25h[?25h
package/README.md CHANGED
@@ -25,17 +25,24 @@ aiready-consistency ./src
25
25
  Inconsistent code patterns confuse AI models and reduce their effectiveness. This tool analyzes:
26
26
 
27
27
  ### 🏷️ Naming Quality & Conventions
28
- - Single-letter variables (except loop counters)
29
- - Unclear abbreviations
30
- - Mixed naming conventions (camelCase vs snake_case)
31
- - Boolean naming (should use is/has/can prefixes)
32
- - Function naming (should start with action verbs)
28
+ - **Single-letter variables** - Detects unclear variable names (skips common iterators: i, j, k, l, x, y, z in appropriate contexts)
29
+ - **Abbreviations** - Identifies unclear abbreviations while allowing 60+ standard ones (env, req, res, ctx, max, min, etc.)
30
+ - **Mixed naming conventions** - Detects snake_case in TypeScript/JavaScript projects (should use camelCase)
31
+ - **Boolean naming** - Ensures booleans use clear prefixes (is/has/can/should)
32
+ - **Function naming** - Checks for action verbs while allowing factory patterns and descriptive names
33
+
34
+ **Smart Detection:** The tool understands context and won't flag:
35
+ - Common abbreviations (env, api, url, max, min, now, etc.)
36
+ - Boolean prefixes (is, has, can used as variables)
37
+ - Loop iterators in appropriate contexts
38
+ - Factory/builder patterns
39
+ - Long descriptive function names
33
40
 
34
41
  ### 🔄 Pattern Consistency
35
- - Error handling strategies (try-catch vs returns)
36
- - Async patterns (async/await vs promises vs callbacks)
37
- - Import styles (ES modules vs CommonJS)
38
- - API design patterns
42
+ - **Error handling strategies** - Detects mixed approaches (try-catch vs returns vs throws)
43
+ - **Async patterns** - Identifies mixing of async/await, promises, and callbacks
44
+ - **Import styles** - Flags mixing of ES modules and CommonJS
45
+ - **API design patterns** - Ensures consistent patterns across endpoints
39
46
 
40
47
  ### 🏗️ Architectural Consistency *(coming soon)*
41
48
  - File organization patterns
@@ -149,6 +156,20 @@ Create `aiready.json` in your project root:
149
156
  | `checkPatterns` | boolean | `true` | Check code pattern consistency |
150
157
  | `minSeverity` | string | `'info'` | Filter: `'info'`, `'minor'`, `'major'`, `'critical'` |
151
158
 
159
+ ### Acceptable Abbreviations
160
+
161
+ The tool recognizes 60+ standard abbreviations and won't flag them:
162
+
163
+ **Web/Network:** url, uri, api, cdn, dns, ip, http, utm, seo, xhr
164
+ **Data:** json, xml, yaml, csv, html, css, svg, pdf, dto, dao
165
+ **System:** env, os, fs, cli, tmp, src, dst, bin, lib, pkg
166
+ **Request/Response:** req, res, ctx, err, msg
167
+ **Math:** max, min, avg, sum, abs, cos, sin, log, sqrt
168
+ **Time:** now, utc, ms, sec
169
+ **Common:** id, uid, db, sql, orm, ui, ux, dom, ref, val, str, obj, arr, cfg, init
170
+
171
+ See [naming.ts](src/analyzers/naming.ts) for the complete list.
172
+
152
173
  ## 🔧 Programmatic API
153
174
 
154
175
  ```typescript
@@ -0,0 +1,508 @@
1
+ // src/analyzers/naming.ts
2
+ import { readFileContent } from "@aiready/core";
3
+ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
4
+ // Standard identifiers
5
+ "id",
6
+ "uid",
7
+ "gid",
8
+ "pid",
9
+ // Web/Network
10
+ "url",
11
+ "uri",
12
+ "api",
13
+ "cdn",
14
+ "dns",
15
+ "ip",
16
+ "tcp",
17
+ "udp",
18
+ "http",
19
+ "ssl",
20
+ "tls",
21
+ "utm",
22
+ "seo",
23
+ "rss",
24
+ "xhr",
25
+ "ajax",
26
+ // Data formats
27
+ "json",
28
+ "xml",
29
+ "yaml",
30
+ "csv",
31
+ "html",
32
+ "css",
33
+ "svg",
34
+ "pdf",
35
+ // Databases
36
+ "db",
37
+ "sql",
38
+ "orm",
39
+ "dao",
40
+ "dto",
41
+ // File system
42
+ "fs",
43
+ "dir",
44
+ "tmp",
45
+ "src",
46
+ "dst",
47
+ "bin",
48
+ "lib",
49
+ "pkg",
50
+ // Operating system
51
+ "os",
52
+ "env",
53
+ "arg",
54
+ "cli",
55
+ "cmd",
56
+ "exe",
57
+ // UI/UX
58
+ "ui",
59
+ "ux",
60
+ "gui",
61
+ "dom",
62
+ "ref",
63
+ // Request/Response
64
+ "req",
65
+ "res",
66
+ "ctx",
67
+ "err",
68
+ "msg",
69
+ // Mathematics/Computing
70
+ "max",
71
+ "min",
72
+ "avg",
73
+ "sum",
74
+ "abs",
75
+ "cos",
76
+ "sin",
77
+ "tan",
78
+ "log",
79
+ "exp",
80
+ "pow",
81
+ "sqrt",
82
+ "std",
83
+ "var",
84
+ "int",
85
+ "num",
86
+ // Time
87
+ "now",
88
+ "utc",
89
+ "tz",
90
+ "ms",
91
+ "sec",
92
+ // Common patterns
93
+ "app",
94
+ "cfg",
95
+ "config",
96
+ "init",
97
+ "len",
98
+ "val",
99
+ "str",
100
+ "obj",
101
+ "arr",
102
+ "gen",
103
+ "def",
104
+ "raw",
105
+ "new",
106
+ "old",
107
+ "pre",
108
+ "post",
109
+ "sub",
110
+ "pub",
111
+ // Boolean helpers (these are intentional short names)
112
+ "is",
113
+ "has",
114
+ "can",
115
+ "did",
116
+ "was",
117
+ "are"
118
+ ]);
119
+ async function analyzeNaming(files) {
120
+ const issues = [];
121
+ for (const file of files) {
122
+ const content = await readFileContent(file);
123
+ const fileIssues = analyzeFileNaming(file, content);
124
+ issues.push(...fileIssues);
125
+ }
126
+ return issues;
127
+ }
128
+ function analyzeFileNaming(file, content) {
129
+ const issues = [];
130
+ const lines = content.split("\n");
131
+ lines.forEach((line, index) => {
132
+ const lineNumber = index + 1;
133
+ const singleLetterMatches = line.matchAll(/\b(?:const|let|var)\s+([a-hm-z])\s*=/gi);
134
+ for (const match of singleLetterMatches) {
135
+ const letter = match[1].toLowerCase();
136
+ const isInLoopContext = line.includes("for") || line.includes(".map") || line.includes(".filter") || line.includes(".forEach") || line.includes(".reduce");
137
+ if (!isInLoopContext && !["x", "y", "z", "i", "j", "k", "l", "n", "m"].includes(letter)) {
138
+ issues.push({
139
+ file,
140
+ line: lineNumber,
141
+ type: "poor-naming",
142
+ identifier: match[1],
143
+ severity: "minor",
144
+ suggestion: `Use descriptive variable name instead of single letter '${match[1]}'`
145
+ });
146
+ }
147
+ }
148
+ const abbreviationMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z]{1,3})(?=[A-Z]|_|\s*=)/g);
149
+ for (const match of abbreviationMatches) {
150
+ const abbrev = match[1].toLowerCase();
151
+ if (!ACCEPTABLE_ABBREVIATIONS.has(abbrev)) {
152
+ issues.push({
153
+ file,
154
+ line: lineNumber,
155
+ type: "abbreviation",
156
+ identifier: match[1],
157
+ severity: "info",
158
+ suggestion: `Consider using full word instead of abbreviation '${match[1]}'`
159
+ });
160
+ }
161
+ }
162
+ if (file.match(/\.(ts|tsx|js|jsx)$/)) {
163
+ const camelCaseVars = line.match(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*=/);
164
+ const snakeCaseVars = line.match(/\b(?:const|let|var)\s+([a-z][a-z0-9]*_[a-z0-9_]*)\s*=/);
165
+ if (snakeCaseVars) {
166
+ issues.push({
167
+ file,
168
+ line: lineNumber,
169
+ type: "convention-mix",
170
+ identifier: snakeCaseVars[1],
171
+ severity: "minor",
172
+ suggestion: `Use camelCase '${snakeCaseToCamelCase(snakeCaseVars[1])}' instead of snake_case in TypeScript/JavaScript`
173
+ });
174
+ }
175
+ }
176
+ const booleanMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*:\s*boolean/gi);
177
+ for (const match of booleanMatches) {
178
+ const name = match[1];
179
+ if (!name.match(/^(is|has|should|can|will|did)/i)) {
180
+ issues.push({
181
+ file,
182
+ line: lineNumber,
183
+ type: "unclear",
184
+ identifier: name,
185
+ severity: "info",
186
+ suggestion: `Boolean variable '${name}' should start with is/has/should/can for clarity`
187
+ });
188
+ }
189
+ }
190
+ const functionMatches = line.matchAll(/function\s+([a-z][a-zA-Z0-9]*)/g);
191
+ for (const match of functionMatches) {
192
+ const name = match[1];
193
+ const isFactoryPattern = name.match(/(Factory|Builder|Creator|Generator)$/);
194
+ const isEventHandler = name.match(/^on[A-Z]/);
195
+ const isDescriptiveLong = name.length > 20;
196
+ const hasActionVerb = name.match(/^(get|set|is|has|can|should|create|update|delete|fetch|load|save|process|handle|validate|check|find|search|filter|map|reduce|make|do|run|start|stop|build|parse|format|render|calculate|compute|generate|transform|convert|normalize|sanitize|encode|decode|compress|extract|merge|split|join|sort|compare|test|verify|ensure|apply|execute|invoke|call|emit|dispatch|trigger|listen|subscribe|unsubscribe|add|remove|clear|reset|toggle|enable|disable|open|close|connect|disconnect|send|receive|read|write|import|export|register|unregister|mount|unmount)/);
197
+ if (!hasActionVerb && !isFactoryPattern && !isEventHandler && !isDescriptiveLong) {
198
+ issues.push({
199
+ file,
200
+ line: lineNumber,
201
+ type: "unclear",
202
+ identifier: name,
203
+ severity: "info",
204
+ suggestion: `Function '${name}' should start with an action verb (get, set, create, etc.)`
205
+ });
206
+ }
207
+ }
208
+ });
209
+ return issues;
210
+ }
211
+ function snakeCaseToCamelCase(str) {
212
+ return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
213
+ }
214
+ function detectNamingConventions(files, allIssues) {
215
+ const camelCaseCount = allIssues.filter((i) => i.type === "convention-mix").length;
216
+ const totalChecks = files.length * 10;
217
+ if (camelCaseCount / totalChecks > 0.3) {
218
+ return { dominantConvention: "mixed", conventionScore: 0.5 };
219
+ }
220
+ return { dominantConvention: "camelCase", conventionScore: 0.9 };
221
+ }
222
+
223
+ // src/analyzers/patterns.ts
224
+ import { readFileContent as readFileContent2 } from "@aiready/core";
225
+ async function analyzePatterns(files) {
226
+ const issues = [];
227
+ const errorHandlingIssues = await analyzeErrorHandling(files);
228
+ issues.push(...errorHandlingIssues);
229
+ const asyncIssues = await analyzeAsyncPatterns(files);
230
+ issues.push(...asyncIssues);
231
+ const importIssues = await analyzeImportStyles(files);
232
+ issues.push(...importIssues);
233
+ return issues;
234
+ }
235
+ async function analyzeErrorHandling(files) {
236
+ const patterns = {
237
+ tryCatch: [],
238
+ throwsError: [],
239
+ returnsNull: [],
240
+ returnsError: []
241
+ };
242
+ for (const file of files) {
243
+ const content = await readFileContent2(file);
244
+ if (content.includes("try {") || content.includes("} catch")) {
245
+ patterns.tryCatch.push(file);
246
+ }
247
+ if (content.match(/throw new \w*Error/)) {
248
+ patterns.throwsError.push(file);
249
+ }
250
+ if (content.match(/return null/)) {
251
+ patterns.returnsNull.push(file);
252
+ }
253
+ if (content.match(/return \{ error:/)) {
254
+ patterns.returnsError.push(file);
255
+ }
256
+ }
257
+ const issues = [];
258
+ const strategiesUsed = Object.values(patterns).filter((p) => p.length > 0).length;
259
+ if (strategiesUsed > 2) {
260
+ issues.push({
261
+ files: [.../* @__PURE__ */ new Set([
262
+ ...patterns.tryCatch,
263
+ ...patterns.throwsError,
264
+ ...patterns.returnsNull,
265
+ ...patterns.returnsError
266
+ ])],
267
+ type: "error-handling",
268
+ description: "Inconsistent error handling strategies across codebase",
269
+ examples: [
270
+ patterns.tryCatch.length > 0 ? `Try-catch used in ${patterns.tryCatch.length} files` : "",
271
+ patterns.throwsError.length > 0 ? `Throws errors in ${patterns.throwsError.length} files` : "",
272
+ patterns.returnsNull.length > 0 ? `Returns null in ${patterns.returnsNull.length} files` : "",
273
+ patterns.returnsError.length > 0 ? `Returns error objects in ${patterns.returnsError.length} files` : ""
274
+ ].filter((e) => e),
275
+ severity: "major"
276
+ });
277
+ }
278
+ return issues;
279
+ }
280
+ async function analyzeAsyncPatterns(files) {
281
+ const patterns = {
282
+ asyncAwait: [],
283
+ promises: [],
284
+ callbacks: []
285
+ };
286
+ for (const file of files) {
287
+ const content = await readFileContent2(file);
288
+ if (content.match(/async\s+(function|\(|[a-zA-Z])/)) {
289
+ patterns.asyncAwait.push(file);
290
+ }
291
+ if (content.match(/\.then\(/) || content.match(/\.catch\(/)) {
292
+ patterns.promises.push(file);
293
+ }
294
+ if (content.match(/callback\s*\(/) || content.match(/\(\s*err\s*,/)) {
295
+ patterns.callbacks.push(file);
296
+ }
297
+ }
298
+ const issues = [];
299
+ if (patterns.callbacks.length > 0 && patterns.asyncAwait.length > 0) {
300
+ issues.push({
301
+ files: [...patterns.callbacks, ...patterns.asyncAwait],
302
+ type: "async-style",
303
+ description: "Mixed async patterns: callbacks and async/await",
304
+ examples: [
305
+ `Callbacks found in: ${patterns.callbacks.slice(0, 3).join(", ")}`,
306
+ `Async/await used in: ${patterns.asyncAwait.slice(0, 3).join(", ")}`
307
+ ],
308
+ severity: "minor"
309
+ });
310
+ }
311
+ if (patterns.promises.length > patterns.asyncAwait.length * 0.3 && patterns.asyncAwait.length > 0) {
312
+ issues.push({
313
+ files: patterns.promises,
314
+ type: "async-style",
315
+ description: "Consider using async/await instead of promise chains for consistency",
316
+ examples: patterns.promises.slice(0, 5),
317
+ severity: "info"
318
+ });
319
+ }
320
+ return issues;
321
+ }
322
+ async function analyzeImportStyles(files) {
323
+ const patterns = {
324
+ esModules: [],
325
+ commonJS: [],
326
+ mixed: []
327
+ };
328
+ for (const file of files) {
329
+ const content = await readFileContent2(file);
330
+ const hasESM = content.match(/^import\s+/m);
331
+ const hasCJS = content.match(/require\s*\(/);
332
+ if (hasESM && hasCJS) {
333
+ patterns.mixed.push(file);
334
+ } else if (hasESM) {
335
+ patterns.esModules.push(file);
336
+ } else if (hasCJS) {
337
+ patterns.commonJS.push(file);
338
+ }
339
+ }
340
+ const issues = [];
341
+ if (patterns.mixed.length > 0) {
342
+ issues.push({
343
+ files: patterns.mixed,
344
+ type: "import-style",
345
+ description: "Mixed ES modules and CommonJS imports in same files",
346
+ examples: patterns.mixed.slice(0, 5),
347
+ severity: "major"
348
+ });
349
+ }
350
+ if (patterns.esModules.length > 0 && patterns.commonJS.length > 0) {
351
+ const ratio = patterns.commonJS.length / (patterns.esModules.length + patterns.commonJS.length);
352
+ if (ratio > 0.2 && ratio < 0.8) {
353
+ issues.push({
354
+ files: [...patterns.esModules, ...patterns.commonJS],
355
+ type: "import-style",
356
+ description: "Inconsistent import styles across project",
357
+ examples: [
358
+ `ES modules: ${patterns.esModules.length} files`,
359
+ `CommonJS: ${patterns.commonJS.length} files`
360
+ ],
361
+ severity: "minor"
362
+ });
363
+ }
364
+ }
365
+ return issues;
366
+ }
367
+
368
+ // src/analyzer.ts
369
+ import { scanFiles } from "@aiready/core";
370
+ async function analyzeConsistency(options) {
371
+ const {
372
+ checkNaming = true,
373
+ checkPatterns = true,
374
+ checkArchitecture = false,
375
+ // Not implemented yet
376
+ minSeverity = "info",
377
+ ...scanOptions
378
+ } = options;
379
+ const filePaths = await scanFiles(scanOptions);
380
+ const namingIssues = checkNaming ? await analyzeNaming(filePaths) : [];
381
+ const patternIssues = checkPatterns ? await analyzePatterns(filePaths) : [];
382
+ const results = [];
383
+ const fileIssuesMap = /* @__PURE__ */ new Map();
384
+ for (const issue of namingIssues) {
385
+ if (!shouldIncludeSeverity(issue.severity, minSeverity)) {
386
+ continue;
387
+ }
388
+ const consistencyIssue = {
389
+ type: issue.type === "convention-mix" ? "naming-inconsistency" : "naming-quality",
390
+ category: "naming",
391
+ severity: issue.severity,
392
+ message: `${issue.type}: ${issue.identifier}`,
393
+ location: {
394
+ file: issue.file,
395
+ line: issue.line,
396
+ column: issue.column
397
+ },
398
+ suggestion: issue.suggestion
399
+ };
400
+ if (!fileIssuesMap.has(issue.file)) {
401
+ fileIssuesMap.set(issue.file, []);
402
+ }
403
+ fileIssuesMap.get(issue.file).push(consistencyIssue);
404
+ }
405
+ for (const issue of patternIssues) {
406
+ if (!shouldIncludeSeverity(issue.severity, minSeverity)) {
407
+ continue;
408
+ }
409
+ const consistencyIssue = {
410
+ type: "pattern-inconsistency",
411
+ category: "patterns",
412
+ severity: issue.severity,
413
+ message: issue.description,
414
+ location: {
415
+ file: issue.files[0] || "multiple files",
416
+ line: 1
417
+ },
418
+ examples: issue.examples,
419
+ suggestion: `Standardize ${issue.type} patterns across ${issue.files.length} files`
420
+ };
421
+ const firstFile = issue.files[0];
422
+ if (firstFile && !fileIssuesMap.has(firstFile)) {
423
+ fileIssuesMap.set(firstFile, []);
424
+ }
425
+ if (firstFile) {
426
+ fileIssuesMap.get(firstFile).push(consistencyIssue);
427
+ }
428
+ }
429
+ for (const [fileName, issues] of fileIssuesMap) {
430
+ results.push({
431
+ fileName,
432
+ issues,
433
+ metrics: {
434
+ consistencyScore: calculateConsistencyScore(issues)
435
+ }
436
+ });
437
+ }
438
+ const recommendations = generateRecommendations(namingIssues, patternIssues);
439
+ const conventionAnalysis = detectNamingConventions(filePaths, namingIssues);
440
+ return {
441
+ summary: {
442
+ totalIssues: namingIssues.length + patternIssues.length,
443
+ namingIssues: namingIssues.length,
444
+ patternIssues: patternIssues.length,
445
+ architectureIssues: 0,
446
+ filesAnalyzed: filePaths.length
447
+ },
448
+ results,
449
+ recommendations
450
+ };
451
+ }
452
+ function shouldIncludeSeverity(severity, minSeverity) {
453
+ const severityLevels = { info: 0, minor: 1, major: 2, critical: 3 };
454
+ return severityLevels[severity] >= severityLevels[minSeverity];
455
+ }
456
+ function calculateConsistencyScore(issues) {
457
+ const weights = { critical: 10, major: 5, minor: 2, info: 1 };
458
+ const totalWeight = issues.reduce((sum, issue) => sum + weights[issue.severity], 0);
459
+ return Math.max(0, 1 - totalWeight / 100);
460
+ }
461
+ function generateRecommendations(namingIssues, patternIssues) {
462
+ const recommendations = [];
463
+ if (namingIssues.length > 0) {
464
+ const conventionMixCount = namingIssues.filter((i) => i.type === "convention-mix").length;
465
+ if (conventionMixCount > 0) {
466
+ recommendations.push(
467
+ `Standardize naming conventions: Found ${conventionMixCount} snake_case variables in TypeScript/JavaScript (use camelCase)`
468
+ );
469
+ }
470
+ const poorNamingCount = namingIssues.filter((i) => i.type === "poor-naming").length;
471
+ if (poorNamingCount > 0) {
472
+ recommendations.push(
473
+ `Improve variable naming: Found ${poorNamingCount} single-letter or unclear variable names`
474
+ );
475
+ }
476
+ }
477
+ if (patternIssues.length > 0) {
478
+ const errorHandlingIssues = patternIssues.filter((i) => i.type === "error-handling");
479
+ if (errorHandlingIssues.length > 0) {
480
+ recommendations.push(
481
+ "Standardize error handling strategy across the codebase (prefer try-catch with typed errors)"
482
+ );
483
+ }
484
+ const asyncIssues = patternIssues.filter((i) => i.type === "async-style");
485
+ if (asyncIssues.length > 0) {
486
+ recommendations.push(
487
+ "Use async/await consistently instead of mixing with promise chains or callbacks"
488
+ );
489
+ }
490
+ const importIssues = patternIssues.filter((i) => i.type === "import-style");
491
+ if (importIssues.length > 0) {
492
+ recommendations.push(
493
+ "Use ES modules consistently across the project (avoid mixing with CommonJS)"
494
+ );
495
+ }
496
+ }
497
+ if (recommendations.length === 0) {
498
+ recommendations.push("No major consistency issues found! Your codebase follows good practices.");
499
+ }
500
+ return recommendations;
501
+ }
502
+
503
+ export {
504
+ analyzeNaming,
505
+ detectNamingConventions,
506
+ analyzePatterns,
507
+ analyzeConsistency
508
+ };
package/dist/cli.js CHANGED
@@ -31,6 +31,122 @@ var import_core3 = require("@aiready/core");
31
31
 
32
32
  // src/analyzers/naming.ts
33
33
  var import_core = require("@aiready/core");
34
+ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
35
+ // Standard identifiers
36
+ "id",
37
+ "uid",
38
+ "gid",
39
+ "pid",
40
+ // Web/Network
41
+ "url",
42
+ "uri",
43
+ "api",
44
+ "cdn",
45
+ "dns",
46
+ "ip",
47
+ "tcp",
48
+ "udp",
49
+ "http",
50
+ "ssl",
51
+ "tls",
52
+ "utm",
53
+ "seo",
54
+ "rss",
55
+ "xhr",
56
+ "ajax",
57
+ // Data formats
58
+ "json",
59
+ "xml",
60
+ "yaml",
61
+ "csv",
62
+ "html",
63
+ "css",
64
+ "svg",
65
+ "pdf",
66
+ // Databases
67
+ "db",
68
+ "sql",
69
+ "orm",
70
+ "dao",
71
+ "dto",
72
+ // File system
73
+ "fs",
74
+ "dir",
75
+ "tmp",
76
+ "src",
77
+ "dst",
78
+ "bin",
79
+ "lib",
80
+ "pkg",
81
+ // Operating system
82
+ "os",
83
+ "env",
84
+ "arg",
85
+ "cli",
86
+ "cmd",
87
+ "exe",
88
+ // UI/UX
89
+ "ui",
90
+ "ux",
91
+ "gui",
92
+ "dom",
93
+ "ref",
94
+ // Request/Response
95
+ "req",
96
+ "res",
97
+ "ctx",
98
+ "err",
99
+ "msg",
100
+ // Mathematics/Computing
101
+ "max",
102
+ "min",
103
+ "avg",
104
+ "sum",
105
+ "abs",
106
+ "cos",
107
+ "sin",
108
+ "tan",
109
+ "log",
110
+ "exp",
111
+ "pow",
112
+ "sqrt",
113
+ "std",
114
+ "var",
115
+ "int",
116
+ "num",
117
+ // Time
118
+ "now",
119
+ "utc",
120
+ "tz",
121
+ "ms",
122
+ "sec",
123
+ // Common patterns
124
+ "app",
125
+ "cfg",
126
+ "config",
127
+ "init",
128
+ "len",
129
+ "val",
130
+ "str",
131
+ "obj",
132
+ "arr",
133
+ "gen",
134
+ "def",
135
+ "raw",
136
+ "new",
137
+ "old",
138
+ "pre",
139
+ "post",
140
+ "sub",
141
+ "pub",
142
+ // Boolean helpers (these are intentional short names)
143
+ "is",
144
+ "has",
145
+ "can",
146
+ "did",
147
+ "was",
148
+ "are"
149
+ ]);
34
150
  async function analyzeNaming(files) {
35
151
  const issues = [];
36
152
  for (const file of files) {
@@ -47,26 +163,30 @@ function analyzeFileNaming(file, content) {
47
163
  const lineNumber = index + 1;
48
164
  const singleLetterMatches = line.matchAll(/\b(?:const|let|var)\s+([a-hm-z])\s*=/gi);
49
165
  for (const match of singleLetterMatches) {
50
- issues.push({
51
- file,
52
- line: lineNumber,
53
- type: "poor-naming",
54
- identifier: match[1],
55
- severity: "minor",
56
- suggestion: `Use descriptive variable name instead of single letter '${match[1]}'`
57
- });
166
+ const letter = match[1].toLowerCase();
167
+ const isInLoopContext = line.includes("for") || line.includes(".map") || line.includes(".filter") || line.includes(".forEach") || line.includes(".reduce");
168
+ if (!isInLoopContext && !["x", "y", "z", "i", "j", "k", "l", "n", "m"].includes(letter)) {
169
+ issues.push({
170
+ file,
171
+ line: lineNumber,
172
+ type: "poor-naming",
173
+ identifier: match[1],
174
+ severity: "minor",
175
+ suggestion: `Use descriptive variable name instead of single letter '${match[1]}'`
176
+ });
177
+ }
58
178
  }
59
179
  const abbreviationMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z]{1,3})(?=[A-Z]|_|\s*=)/g);
60
180
  for (const match of abbreviationMatches) {
61
- const abbrev = match[1];
62
- if (!["id", "url", "api", "db", "fs", "os", "ui"].includes(abbrev.toLowerCase())) {
181
+ const abbrev = match[1].toLowerCase();
182
+ if (!ACCEPTABLE_ABBREVIATIONS.has(abbrev)) {
63
183
  issues.push({
64
184
  file,
65
185
  line: lineNumber,
66
186
  type: "abbreviation",
67
- identifier: abbrev,
187
+ identifier: match[1],
68
188
  severity: "info",
69
- suggestion: `Consider using full word instead of abbreviation '${abbrev}'`
189
+ suggestion: `Consider using full word instead of abbreviation '${match[1]}'`
70
190
  });
71
191
  }
72
192
  }
@@ -101,7 +221,11 @@ function analyzeFileNaming(file, content) {
101
221
  const functionMatches = line.matchAll(/function\s+([a-z][a-zA-Z0-9]*)/g);
102
222
  for (const match of functionMatches) {
103
223
  const name = match[1];
104
- if (!name.match(/^(get|set|is|has|can|should|create|update|delete|fetch|load|save|process|handle|validate|check|find|search|filter|map|reduce)/)) {
224
+ const isFactoryPattern = name.match(/(Factory|Builder|Creator|Generator)$/);
225
+ const isEventHandler = name.match(/^on[A-Z]/);
226
+ const isDescriptiveLong = name.length > 20;
227
+ const hasActionVerb = name.match(/^(get|set|is|has|can|should|create|update|delete|fetch|load|save|process|handle|validate|check|find|search|filter|map|reduce|make|do|run|start|stop|build|parse|format|render|calculate|compute|generate|transform|convert|normalize|sanitize|encode|decode|compress|extract|merge|split|join|sort|compare|test|verify|ensure|apply|execute|invoke|call|emit|dispatch|trigger|listen|subscribe|unsubscribe|add|remove|clear|reset|toggle|enable|disable|open|close|connect|disconnect|send|receive|read|write|import|export|register|unregister|mount|unmount)/);
228
+ if (!hasActionVerb && !isFactoryPattern && !isEventHandler && !isDescriptiveLong) {
105
229
  issues.push({
106
230
  file,
107
231
  line: lineNumber,
package/dist/cli.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  analyzeConsistency
4
- } from "./chunk-CF4LU7KE.mjs";
4
+ } from "./chunk-LUAREV6A.mjs";
5
5
 
6
6
  // src/cli.ts
7
7
  import { Command } from "commander";
package/dist/index.js CHANGED
@@ -32,6 +32,122 @@ var import_core3 = require("@aiready/core");
32
32
 
33
33
  // src/analyzers/naming.ts
34
34
  var import_core = require("@aiready/core");
35
+ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
36
+ // Standard identifiers
37
+ "id",
38
+ "uid",
39
+ "gid",
40
+ "pid",
41
+ // Web/Network
42
+ "url",
43
+ "uri",
44
+ "api",
45
+ "cdn",
46
+ "dns",
47
+ "ip",
48
+ "tcp",
49
+ "udp",
50
+ "http",
51
+ "ssl",
52
+ "tls",
53
+ "utm",
54
+ "seo",
55
+ "rss",
56
+ "xhr",
57
+ "ajax",
58
+ // Data formats
59
+ "json",
60
+ "xml",
61
+ "yaml",
62
+ "csv",
63
+ "html",
64
+ "css",
65
+ "svg",
66
+ "pdf",
67
+ // Databases
68
+ "db",
69
+ "sql",
70
+ "orm",
71
+ "dao",
72
+ "dto",
73
+ // File system
74
+ "fs",
75
+ "dir",
76
+ "tmp",
77
+ "src",
78
+ "dst",
79
+ "bin",
80
+ "lib",
81
+ "pkg",
82
+ // Operating system
83
+ "os",
84
+ "env",
85
+ "arg",
86
+ "cli",
87
+ "cmd",
88
+ "exe",
89
+ // UI/UX
90
+ "ui",
91
+ "ux",
92
+ "gui",
93
+ "dom",
94
+ "ref",
95
+ // Request/Response
96
+ "req",
97
+ "res",
98
+ "ctx",
99
+ "err",
100
+ "msg",
101
+ // Mathematics/Computing
102
+ "max",
103
+ "min",
104
+ "avg",
105
+ "sum",
106
+ "abs",
107
+ "cos",
108
+ "sin",
109
+ "tan",
110
+ "log",
111
+ "exp",
112
+ "pow",
113
+ "sqrt",
114
+ "std",
115
+ "var",
116
+ "int",
117
+ "num",
118
+ // Time
119
+ "now",
120
+ "utc",
121
+ "tz",
122
+ "ms",
123
+ "sec",
124
+ // Common patterns
125
+ "app",
126
+ "cfg",
127
+ "config",
128
+ "init",
129
+ "len",
130
+ "val",
131
+ "str",
132
+ "obj",
133
+ "arr",
134
+ "gen",
135
+ "def",
136
+ "raw",
137
+ "new",
138
+ "old",
139
+ "pre",
140
+ "post",
141
+ "sub",
142
+ "pub",
143
+ // Boolean helpers (these are intentional short names)
144
+ "is",
145
+ "has",
146
+ "can",
147
+ "did",
148
+ "was",
149
+ "are"
150
+ ]);
35
151
  async function analyzeNaming(files) {
36
152
  const issues = [];
37
153
  for (const file of files) {
@@ -48,26 +164,30 @@ function analyzeFileNaming(file, content) {
48
164
  const lineNumber = index + 1;
49
165
  const singleLetterMatches = line.matchAll(/\b(?:const|let|var)\s+([a-hm-z])\s*=/gi);
50
166
  for (const match of singleLetterMatches) {
51
- issues.push({
52
- file,
53
- line: lineNumber,
54
- type: "poor-naming",
55
- identifier: match[1],
56
- severity: "minor",
57
- suggestion: `Use descriptive variable name instead of single letter '${match[1]}'`
58
- });
167
+ const letter = match[1].toLowerCase();
168
+ const isInLoopContext = line.includes("for") || line.includes(".map") || line.includes(".filter") || line.includes(".forEach") || line.includes(".reduce");
169
+ if (!isInLoopContext && !["x", "y", "z", "i", "j", "k", "l", "n", "m"].includes(letter)) {
170
+ issues.push({
171
+ file,
172
+ line: lineNumber,
173
+ type: "poor-naming",
174
+ identifier: match[1],
175
+ severity: "minor",
176
+ suggestion: `Use descriptive variable name instead of single letter '${match[1]}'`
177
+ });
178
+ }
59
179
  }
60
180
  const abbreviationMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z]{1,3})(?=[A-Z]|_|\s*=)/g);
61
181
  for (const match of abbreviationMatches) {
62
- const abbrev = match[1];
63
- if (!["id", "url", "api", "db", "fs", "os", "ui"].includes(abbrev.toLowerCase())) {
182
+ const abbrev = match[1].toLowerCase();
183
+ if (!ACCEPTABLE_ABBREVIATIONS.has(abbrev)) {
64
184
  issues.push({
65
185
  file,
66
186
  line: lineNumber,
67
187
  type: "abbreviation",
68
- identifier: abbrev,
188
+ identifier: match[1],
69
189
  severity: "info",
70
- suggestion: `Consider using full word instead of abbreviation '${abbrev}'`
190
+ suggestion: `Consider using full word instead of abbreviation '${match[1]}'`
71
191
  });
72
192
  }
73
193
  }
@@ -102,7 +222,11 @@ function analyzeFileNaming(file, content) {
102
222
  const functionMatches = line.matchAll(/function\s+([a-z][a-zA-Z0-9]*)/g);
103
223
  for (const match of functionMatches) {
104
224
  const name = match[1];
105
- if (!name.match(/^(get|set|is|has|can|should|create|update|delete|fetch|load|save|process|handle|validate|check|find|search|filter|map|reduce)/)) {
225
+ const isFactoryPattern = name.match(/(Factory|Builder|Creator|Generator)$/);
226
+ const isEventHandler = name.match(/^on[A-Z]/);
227
+ const isDescriptiveLong = name.length > 20;
228
+ const hasActionVerb = name.match(/^(get|set|is|has|can|should|create|update|delete|fetch|load|save|process|handle|validate|check|find|search|filter|map|reduce|make|do|run|start|stop|build|parse|format|render|calculate|compute|generate|transform|convert|normalize|sanitize|encode|decode|compress|extract|merge|split|join|sort|compare|test|verify|ensure|apply|execute|invoke|call|emit|dispatch|trigger|listen|subscribe|unsubscribe|add|remove|clear|reset|toggle|enable|disable|open|close|connect|disconnect|send|receive|read|write|import|export|register|unregister|mount|unmount)/);
229
+ if (!hasActionVerb && !isFactoryPattern && !isEventHandler && !isDescriptiveLong) {
106
230
  issues.push({
107
231
  file,
108
232
  line: lineNumber,
package/dist/index.mjs CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  analyzeNaming,
4
4
  analyzePatterns,
5
5
  detectNamingConventions
6
- } from "./chunk-CF4LU7KE.mjs";
6
+ } from "./chunk-LUAREV6A.mjs";
7
7
  export {
8
8
  analyzeConsistency,
9
9
  analyzeNaming,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiready/consistency",
3
- "version": "0.3.0",
3
+ "version": "0.3.3",
4
4
  "description": "Detects consistency issues in naming, patterns, and architecture that confuse AI models",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -41,7 +41,7 @@
41
41
  "dependencies": {
42
42
  "chalk": "^5.3.0",
43
43
  "commander": "^12.1.0",
44
- "@aiready/core": "0.5.6"
44
+ "@aiready/core": "0.7.0"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@types/node": "^22.10.5",
@@ -44,6 +44,17 @@ const result = x + y;
44
44
  expect(true).toBe(true);
45
45
  });
46
46
 
47
+ it('should NOT flag acceptable abbreviations', () => {
48
+ // These should all be acceptable and NOT flagged
49
+ const acceptableAbbreviations = [
50
+ 'env', 'req', 'res', 'ctx', 'err', 'api', 'url', 'id',
51
+ 'max', 'min', 'now', 'utm', 'has', 'is', 'can',
52
+ 'db', 'fs', 'os', 'ui', 'tmp', 'src', 'dst'
53
+ ];
54
+ // These abbreviations should not trigger warnings
55
+ expect(acceptableAbbreviations.length).toBeGreaterThan(0);
56
+ });
57
+
47
58
  it('should detect snake_case in TypeScript files', () => {
48
59
  const testCode = `
49
60
  const user_name = 'John';
@@ -1,6 +1,37 @@
1
1
  import { readFileContent } from '@aiready/core';
2
2
  import type { NamingIssue } from '../types';
3
3
 
4
+ // Comprehensive list of acceptable abbreviations and acronyms
5
+ const ACCEPTABLE_ABBREVIATIONS = new Set([
6
+ // Standard identifiers
7
+ 'id', 'uid', 'gid', 'pid',
8
+ // Web/Network
9
+ 'url', 'uri', 'api', 'cdn', 'dns', 'ip', 'tcp', 'udp', 'http', 'ssl', 'tls',
10
+ 'utm', 'seo', 'rss', 'xhr', 'ajax',
11
+ // Data formats
12
+ 'json', 'xml', 'yaml', 'csv', 'html', 'css', 'svg', 'pdf',
13
+ // Databases
14
+ 'db', 'sql', 'orm', 'dao', 'dto',
15
+ // File system
16
+ 'fs', 'dir', 'tmp', 'src', 'dst', 'bin', 'lib', 'pkg',
17
+ // Operating system
18
+ 'os', 'env', 'arg', 'cli', 'cmd', 'exe',
19
+ // UI/UX
20
+ 'ui', 'ux', 'gui', 'dom', 'ref',
21
+ // Request/Response
22
+ 'req', 'res', 'ctx', 'err', 'msg',
23
+ // Mathematics/Computing
24
+ 'max', 'min', 'avg', 'sum', 'abs', 'cos', 'sin', 'tan', 'log', 'exp',
25
+ 'pow', 'sqrt', 'std', 'var', 'int', 'num',
26
+ // Time
27
+ 'now', 'utc', 'tz', 'ms', 'sec',
28
+ // Common patterns
29
+ 'app', 'cfg', 'config', 'init', 'len', 'val', 'str', 'obj', 'arr',
30
+ 'gen', 'def', 'raw', 'new', 'old', 'pre', 'post', 'sub', 'pub',
31
+ // Boolean helpers (these are intentional short names)
32
+ 'is', 'has', 'can', 'did', 'was', 'are'
33
+ ]);
34
+
4
35
  /**
5
36
  * Analyzes naming conventions and quality
6
37
  */
@@ -26,32 +57,39 @@ function analyzeFileNaming(file: string, content: string): NamingIssue[] {
26
57
  lines.forEach((line, index) => {
27
58
  const lineNumber = index + 1;
28
59
 
29
- // Check for single letter variables (except i, j, k in loops)
60
+ // Check for single letter variables (except i, j, k, l in loops/common contexts)
30
61
  const singleLetterMatches = line.matchAll(/\b(?:const|let|var)\s+([a-hm-z])\s*=/gi);
31
62
  for (const match of singleLetterMatches) {
32
- issues.push({
33
- file,
34
- line: lineNumber,
35
- type: 'poor-naming',
36
- identifier: match[1],
37
- severity: 'minor',
38
- suggestion: `Use descriptive variable name instead of single letter '${match[1]}'`
39
- });
63
+ const letter = match[1].toLowerCase();
64
+ // Skip if it's in a loop context or common iterator
65
+ const isInLoopContext = line.includes('for') || line.includes('.map') ||
66
+ line.includes('.filter') || line.includes('.forEach') ||
67
+ line.includes('.reduce');
68
+ if (!isInLoopContext && !['x', 'y', 'z', 'i', 'j', 'k', 'l', 'n', 'm'].includes(letter)) {
69
+ issues.push({
70
+ file,
71
+ line: lineNumber,
72
+ type: 'poor-naming',
73
+ identifier: match[1],
74
+ severity: 'minor',
75
+ suggestion: `Use descriptive variable name instead of single letter '${match[1]}'`
76
+ });
77
+ }
40
78
  }
41
79
 
42
80
  // Check for overly abbreviated variables
43
81
  const abbreviationMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z]{1,3})(?=[A-Z]|_|\s*=)/g);
44
82
  for (const match of abbreviationMatches) {
45
- const abbrev = match[1];
46
- // Skip common acceptable abbreviations
47
- if (!['id', 'url', 'api', 'db', 'fs', 'os', 'ui'].includes(abbrev.toLowerCase())) {
83
+ const abbrev = match[1].toLowerCase();
84
+ // Skip acceptable abbreviations
85
+ if (!ACCEPTABLE_ABBREVIATIONS.has(abbrev)) {
48
86
  issues.push({
49
87
  file,
50
88
  line: lineNumber,
51
89
  type: 'abbreviation',
52
- identifier: abbrev,
90
+ identifier: match[1],
53
91
  severity: 'info',
54
- suggestion: `Consider using full word instead of abbreviation '${abbrev}'`
92
+ suggestion: `Consider using full word instead of abbreviation '${match[1]}'`
55
93
  });
56
94
  }
57
95
  }
@@ -93,8 +131,16 @@ function analyzeFileNaming(file: string, content: string): NamingIssue[] {
93
131
  const functionMatches = line.matchAll(/function\s+([a-z][a-zA-Z0-9]*)/g);
94
132
  for (const match of functionMatches) {
95
133
  const name = match[1];
96
- // Functions should typically start with verbs
97
- if (!name.match(/^(get|set|is|has|can|should|create|update|delete|fetch|load|save|process|handle|validate|check|find|search|filter|map|reduce)/)) {
134
+ // Functions should typically start with verbs, but allow:
135
+ // 1. Factory/builder patterns (ends with Factory, Builder, etc.)
136
+ // 2. Descriptive compound names that explain what they return
137
+ // 3. Event handlers (onClick, onSubmit, etc.)
138
+ const isFactoryPattern = name.match(/(Factory|Builder|Creator|Generator)$/);
139
+ const isEventHandler = name.match(/^on[A-Z]/);
140
+ const isDescriptiveLong = name.length > 20; // Long names are usually descriptive enough
141
+ const hasActionVerb = name.match(/^(get|set|is|has|can|should|create|update|delete|fetch|load|save|process|handle|validate|check|find|search|filter|map|reduce|make|do|run|start|stop|build|parse|format|render|calculate|compute|generate|transform|convert|normalize|sanitize|encode|decode|compress|extract|merge|split|join|sort|compare|test|verify|ensure|apply|execute|invoke|call|emit|dispatch|trigger|listen|subscribe|unsubscribe|add|remove|clear|reset|toggle|enable|disable|open|close|connect|disconnect|send|receive|read|write|import|export|register|unregister|mount|unmount)/);
142
+
143
+ if (!hasActionVerb && !isFactoryPattern && !isEventHandler && !isDescriptiveLong) {
98
144
  issues.push({
99
145
  file,
100
146
  line: lineNumber,