@aiready/consistency 0.18.11 → 0.18.14

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.18.10 build /Users/pengcao/projects/aiready/packages/consistency
3
+ > @aiready/consistency@0.18.13 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/cli.js 35.99 KB
13
- CJS dist/index.js 34.59 KB
14
- CJS ⚡️ Build success in 132ms
12
+ ESM dist/chunk-V2UPXL7L.mjs 25.18 KB
15
13
  ESM dist/cli.mjs 8.83 KB
16
14
  ESM dist/index.mjs 7.17 KB
17
- ESM dist/chunk-YCDCIOJN.mjs 25.13 KB
18
- ESM ⚡️ Build success in 134ms
15
+ ESM ⚡️ Build success in 52ms
16
+ CJS dist/index.js 34.67 KB
17
+ CJS dist/cli.js 36.07 KB
18
+ CJS ⚡️ Build success in 52ms
19
19
  DTS Build start
20
- DTS ⚡️ Build success in 4294ms
20
+ DTS ⚡️ Build success in 2525ms
21
21
  DTS dist/cli.d.ts 20.00 B
22
22
  DTS dist/index.d.ts 3.48 KB
23
23
  DTS dist/cli.d.mts 20.00 B
@@ -1,25 +1,26 @@
1
1
 
2
2
  
3
- > @aiready/consistency@0.18.10 test /Users/pengcao/projects/aiready/packages/consistency
3
+ > @aiready/consistency@0.18.13 test /Users/pengcao/projects/aiready/packages/consistency
4
4
  > vitest run
5
5
 
6
6
  [?25l
7
7
   RUN  v4.0.18 /Users/pengcao/projects/aiready/packages/consistency
8
8
 
9
- ✓ dist/__tests__/scoring.test.js (8 tests) 25ms
10
- ✓ dist/__tests__/language-filter.test.js (3 tests) 5ms
11
- ✓ src/__tests__/language-filter.test.ts (3 tests) 2ms
12
- ✓ src/__tests__/scoring.test.ts (8 tests) 38ms
9
+ ✓ dist/__tests__/scoring.test.js (8 tests) 4ms
10
+ ✓ dist/__tests__/language-filter.test.js (3 tests) 3ms
13
11
  stdout | src/__tests__/contract.test.ts
14
- [ToolRegistry#0.26162408949739646] Registering tool: naming-consistency (consistency, naming, standards)
12
+ [ToolRegistry#0.3102922585477208] Registering tool: naming-consistency (consistency, naming, standards)
15
13
 
16
- ✓ src/__tests__/contract.test.ts (1 test) 8ms
17
- ✓ src/__tests__/analyzer.test.ts (18 tests) 299ms
18
- ✓ dist/__tests__/analyzer.test.js (18 tests) 295ms
14
+ ✓ src/__tests__/contract.test.ts (1 test) 3ms
15
+ ✓ src/__tests__/scoring.test.ts (8 tests) 111ms
16
+ ✓ src/__tests__/language-filter.test.ts (3 tests) 3ms
17
+ ✓ src/__tests__/analyzer.test.ts (18 tests) 533ms
18
+ ✓ should analyze naming issues  332ms
19
+ ✓ dist/__tests__/analyzer.test.js (18 tests) 579ms
19
20
 
20
21
   Test Files  7 passed (7)
21
22
   Tests  59 passed (59)
22
-  Start at  15:49:36
23
-  Duration  2.69s (transform 2.55s, setup 0ms, import 9.85s, tests 672ms, environment 1ms)
23
+  Start at  00:03:48
24
+  Duration  1.84s (transform 918ms, setup 0ms, import 5.31s, tests 1.24s, environment 1ms)
24
25
 
25
26
  [?25h
@@ -0,0 +1,842 @@
1
+ // src/analyzers/naming-ast.ts
2
+ import { Severity as Severity2 } from "@aiready/core";
3
+
4
+ // src/utils/ast-parser.ts
5
+ import { parse } from "@typescript-eslint/typescript-estree";
6
+ import { readFileSync } from "fs";
7
+ function parseFile(filePath, content) {
8
+ try {
9
+ const code = content ?? readFileSync(filePath, "utf-8");
10
+ const isTypeScript = filePath.match(/\.tsx?$/);
11
+ return parse(code, {
12
+ jsx: filePath.match(/\.[jt]sx$/i) !== null,
13
+ loc: true,
14
+ range: true,
15
+ comment: false,
16
+ tokens: false,
17
+ // Relaxed parsing for JavaScript files
18
+ sourceType: "module",
19
+ ecmaVersion: "latest",
20
+ // Only use TypeScript parser features for .ts/.tsx files
21
+ filePath: isTypeScript ? filePath : void 0
22
+ });
23
+ } catch (error) {
24
+ void error;
25
+ return null;
26
+ }
27
+ }
28
+ function traverseAST(node, visitor, parent = null) {
29
+ if (!node) return;
30
+ visitor.enter?.(node, parent);
31
+ for (const key of Object.keys(node)) {
32
+ const value = node[key];
33
+ if (Array.isArray(value)) {
34
+ for (const child of value) {
35
+ if (child && typeof child === "object" && "type" in child) {
36
+ traverseAST(child, visitor, node);
37
+ }
38
+ }
39
+ } else if (value && typeof value === "object" && "type" in value) {
40
+ traverseAST(value, visitor, node);
41
+ }
42
+ }
43
+ visitor.leave?.(node, parent);
44
+ }
45
+ function isLoopStatement(node) {
46
+ return [
47
+ "ForStatement",
48
+ "ForInStatement",
49
+ "ForOfStatement",
50
+ "WhileStatement",
51
+ "DoWhileStatement"
52
+ ].includes(node.type);
53
+ }
54
+ function getLineNumber(node) {
55
+ return node.loc?.start.line ?? 0;
56
+ }
57
+
58
+ // src/utils/context-detector.ts
59
+ import { Severity } from "@aiready/core";
60
+ function detectFileType(filePath, ast) {
61
+ void ast;
62
+ const path = filePath.toLowerCase();
63
+ if (path.match(/\.(test|spec)\.(ts|tsx|js|jsx)$/) || path.includes("__tests__")) {
64
+ return "test";
65
+ }
66
+ if (path.endsWith(".d.ts") || path.includes("types")) {
67
+ return "types";
68
+ }
69
+ if (path.match(/config|\.config\.|rc\.|setup/) || path.includes("configuration")) {
70
+ return "config";
71
+ }
72
+ return "production";
73
+ }
74
+ function detectCodeLayer(ast) {
75
+ let hasAPIIndicators = 0;
76
+ let hasBusinessIndicators = 0;
77
+ let hasDataIndicators = 0;
78
+ let hasUtilityIndicators = 0;
79
+ traverseAST(ast, {
80
+ enter: (node) => {
81
+ if (node.type === "ImportDeclaration") {
82
+ const source = node.source.value;
83
+ if (source.match(/express|fastify|koa|@nestjs|axios|fetch|http/i)) {
84
+ hasAPIIndicators++;
85
+ }
86
+ if (source.match(/database|prisma|typeorm|sequelize|mongoose|pg|mysql/i)) {
87
+ hasDataIndicators++;
88
+ }
89
+ }
90
+ if (node.type === "FunctionDeclaration" && node.id) {
91
+ const name = node.id.name;
92
+ if (name.match(
93
+ /^(get|post|put|delete|patch|handle|api|route|controller)/i
94
+ )) {
95
+ hasAPIIndicators++;
96
+ }
97
+ if (name.match(/^(calculate|process|validate|transform|compute|analyze)/i)) {
98
+ hasBusinessIndicators++;
99
+ }
100
+ if (name.match(/^(find|create|update|delete|save|fetch|query|insert)/i)) {
101
+ hasDataIndicators++;
102
+ }
103
+ if (name.match(
104
+ /^(format|parse|convert|normalize|sanitize|encode|decode)/i
105
+ )) {
106
+ hasUtilityIndicators++;
107
+ }
108
+ }
109
+ if (node.type === "ExportNamedDeclaration" || node.type === "ExportDefaultDeclaration") {
110
+ if (node.type === "ExportNamedDeclaration" && node.declaration) {
111
+ if (node.declaration.type === "FunctionDeclaration" && node.declaration.id) {
112
+ const name = node.declaration.id.name;
113
+ if (name.match(/handler|route|api|controller/i)) {
114
+ hasAPIIndicators += 2;
115
+ }
116
+ }
117
+ }
118
+ }
119
+ }
120
+ });
121
+ const scores = {
122
+ api: hasAPIIndicators,
123
+ business: hasBusinessIndicators,
124
+ data: hasDataIndicators,
125
+ utility: hasUtilityIndicators
126
+ };
127
+ const maxScore = Math.max(...Object.values(scores));
128
+ if (maxScore === 0) {
129
+ return "unknown";
130
+ }
131
+ if (scores.api === maxScore) return "api";
132
+ if (scores.data === maxScore) return "data";
133
+ if (scores.business === maxScore) return "business";
134
+ if (scores.utility === maxScore) return "utility";
135
+ return "unknown";
136
+ }
137
+ function calculateComplexity(node) {
138
+ let complexity = 1;
139
+ traverseAST(node, {
140
+ enter: (childNode) => {
141
+ switch (childNode.type) {
142
+ case "IfStatement":
143
+ case "ConditionalExpression":
144
+ // ternary
145
+ case "SwitchCase":
146
+ case "ForStatement":
147
+ case "ForInStatement":
148
+ case "ForOfStatement":
149
+ case "WhileStatement":
150
+ case "DoWhileStatement":
151
+ case "CatchClause":
152
+ complexity++;
153
+ break;
154
+ case "LogicalExpression":
155
+ if (childNode.operator === "&&" || childNode.operator === "||") {
156
+ complexity++;
157
+ }
158
+ break;
159
+ }
160
+ }
161
+ });
162
+ return complexity;
163
+ }
164
+ function buildCodeContext(filePath, ast) {
165
+ const fileType = detectFileType(filePath, ast);
166
+ const codeLayer = detectCodeLayer(ast);
167
+ let totalComplexity = 0;
168
+ let functionCount = 0;
169
+ traverseAST(ast, {
170
+ enter: (node) => {
171
+ if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") {
172
+ totalComplexity += calculateComplexity(node);
173
+ functionCount++;
174
+ }
175
+ }
176
+ });
177
+ const avgComplexity = functionCount > 0 ? totalComplexity / functionCount : 1;
178
+ return {
179
+ fileType,
180
+ codeLayer,
181
+ complexity: Math.round(avgComplexity),
182
+ isTestFile: fileType === "test",
183
+ isTypeDefinition: fileType === "types"
184
+ };
185
+ }
186
+ function adjustSeverity(baseSeverity, context, issueType) {
187
+ const getEnum = (s) => {
188
+ if (s === Severity.Critical || s === "critical") return Severity.Critical;
189
+ if (s === Severity.Major || s === "major") return Severity.Major;
190
+ if (s === Severity.Minor || s === "minor") return Severity.Minor;
191
+ return Severity.Info;
192
+ };
193
+ let currentSev = getEnum(baseSeverity);
194
+ if (context.isTestFile) {
195
+ if (currentSev === Severity.Minor) currentSev = Severity.Info;
196
+ if (currentSev === Severity.Major) currentSev = Severity.Minor;
197
+ }
198
+ if (context.isTypeDefinition) {
199
+ if (currentSev === Severity.Minor) currentSev = Severity.Info;
200
+ }
201
+ if (context.codeLayer === "api") {
202
+ if (currentSev === Severity.Info && issueType === "unclear")
203
+ currentSev = Severity.Minor;
204
+ if (currentSev === Severity.Minor && issueType === "unclear")
205
+ currentSev = Severity.Major;
206
+ }
207
+ if (context.complexity > 10) {
208
+ if (currentSev === Severity.Info) currentSev = Severity.Minor;
209
+ }
210
+ if (context.codeLayer === "utility") {
211
+ if (currentSev === Severity.Minor && issueType === "abbreviation")
212
+ currentSev = Severity.Info;
213
+ }
214
+ return currentSev;
215
+ }
216
+ function isAcceptableInContext(name, context, options) {
217
+ if (options.isLoopVariable && ["i", "j", "k", "l", "n", "m"].includes(name)) {
218
+ return true;
219
+ }
220
+ if (context.isTestFile) {
221
+ if (["a", "b", "c", "x", "y", "z"].includes(name) && options.isParameter) {
222
+ return true;
223
+ }
224
+ }
225
+ if (context.codeLayer === "utility" && ["x", "y", "z"].includes(name)) {
226
+ return true;
227
+ }
228
+ if (options.isDestructured) {
229
+ if (["s", "b", "f", "l"].includes(name)) {
230
+ return true;
231
+ }
232
+ }
233
+ if (options.isParameter && (options.complexity ?? context.complexity) < 3) {
234
+ if (name.length >= 2) {
235
+ return true;
236
+ }
237
+ }
238
+ return false;
239
+ }
240
+
241
+ // src/analyzers/naming-ast.ts
242
+ async function analyzeNamingAST(filePaths) {
243
+ const allIssues = [];
244
+ for (const filePath of filePaths) {
245
+ try {
246
+ const ast = parseFile(filePath);
247
+ if (!ast) continue;
248
+ const context = buildCodeContext(filePath, ast);
249
+ const issues = analyzeIdentifiers(ast, filePath, context);
250
+ allIssues.push(...issues);
251
+ } catch (err) {
252
+ void err;
253
+ }
254
+ }
255
+ return allIssues;
256
+ }
257
+ function analyzeIdentifiers(ast, filePath, context) {
258
+ const issues = [];
259
+ const scopeTracker = new ScopeTracker();
260
+ traverseAST(ast, {
261
+ enter: (node) => {
262
+ if (node.type === "VariableDeclarator" && node.id.type === "Identifier") {
263
+ const isParameter = false;
264
+ const isLoopVariable = isLoopStatement(node.parent?.parent);
265
+ scopeTracker.declareVariable(
266
+ node.id.name,
267
+ node.id,
268
+ getLineNumber(node.id),
269
+ { isParameter, isLoopVariable }
270
+ );
271
+ }
272
+ if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") {
273
+ node.params.forEach((param) => {
274
+ if (param.type === "Identifier") {
275
+ scopeTracker.declareVariable(
276
+ param.name,
277
+ param,
278
+ getLineNumber(param),
279
+ { isParameter: true }
280
+ );
281
+ } else if (param.type === "ObjectPattern") {
282
+ extractDestructuredIdentifiers(param, true, scopeTracker);
283
+ }
284
+ });
285
+ }
286
+ if ((node.type === "ClassDeclaration" || node.type === "TSInterfaceDeclaration" || node.type === "TSTypeAliasDeclaration") && node.id) {
287
+ checkNamingConvention(
288
+ node.id.name,
289
+ "PascalCase",
290
+ node.id,
291
+ filePath,
292
+ issues,
293
+ context
294
+ );
295
+ }
296
+ }
297
+ });
298
+ for (const varInfo of scopeTracker.getVariables()) {
299
+ checkVariableNaming(varInfo, filePath, issues, context);
300
+ }
301
+ return issues;
302
+ }
303
+ function checkNamingConvention(name, convention, node, file, issues, context) {
304
+ let isValid = true;
305
+ if (convention === "PascalCase") {
306
+ isValid = /^[A-Z][a-zA-Z0-9]*$/.test(name);
307
+ } else if (convention === "camelCase") {
308
+ isValid = /^[a-z][a-zA-Z0-9]*$/.test(name);
309
+ } else if (convention === "UPPER_CASE") {
310
+ isValid = /^[A-Z][A-Z0-9_]*$/.test(name);
311
+ }
312
+ if (!isValid) {
313
+ const severity = adjustSeverity(Severity2.Info, context, "convention-mix");
314
+ issues.push({
315
+ file,
316
+ line: getLineNumber(node),
317
+ type: "convention-mix",
318
+ identifier: name,
319
+ severity,
320
+ suggestion: `Follow ${convention} for this identifier`
321
+ });
322
+ }
323
+ }
324
+ function checkVariableNaming(varInfo, file, issues, context) {
325
+ const { name, node, line, options } = varInfo;
326
+ if (isAcceptableInContext(name, context, options)) {
327
+ return;
328
+ }
329
+ if (name.length === 1 && !options.isLoopVariable) {
330
+ const severity = adjustSeverity(Severity2.Minor, context, "poor-naming");
331
+ issues.push({
332
+ file,
333
+ line,
334
+ type: "poor-naming",
335
+ identifier: name,
336
+ severity,
337
+ suggestion: "Use a more descriptive name than a single letter"
338
+ });
339
+ }
340
+ const vagueNames = [
341
+ "data",
342
+ "info",
343
+ "item",
344
+ "obj",
345
+ "val",
346
+ "tmp",
347
+ "temp",
348
+ "thing",
349
+ "stuff"
350
+ ];
351
+ if (vagueNames.includes(name.toLowerCase())) {
352
+ const severity = adjustSeverity(Severity2.Minor, context, "poor-naming");
353
+ issues.push({
354
+ file,
355
+ line,
356
+ type: "poor-naming",
357
+ identifier: name,
358
+ severity,
359
+ suggestion: `Avoid vague names like '${name}'. What does this data represent?`
360
+ });
361
+ }
362
+ if (name.length > 1 && name.length <= 3 && !options.isLoopVariable && !isCommonAbbreviation(name)) {
363
+ const severity = adjustSeverity(Severity2.Info, context, "abbreviation");
364
+ issues.push({
365
+ file,
366
+ line,
367
+ type: "abbreviation",
368
+ identifier: name,
369
+ severity,
370
+ suggestion: "Avoid non-standard abbreviations"
371
+ });
372
+ }
373
+ }
374
+ function isCommonAbbreviation(name) {
375
+ const common = [
376
+ "id",
377
+ "db",
378
+ "fs",
379
+ "os",
380
+ "ip",
381
+ "ui",
382
+ "ux",
383
+ "api",
384
+ "env",
385
+ "url"
386
+ ];
387
+ return common.includes(name.toLowerCase());
388
+ }
389
+ var ScopeTracker = class {
390
+ constructor() {
391
+ this.variables = [];
392
+ }
393
+ declareVariable(name, node, line, options = {}) {
394
+ this.variables.push({ name, node, line, options });
395
+ }
396
+ getVariables() {
397
+ return this.variables;
398
+ }
399
+ };
400
+ function extractDestructuredIdentifiers(node, isParameter, scopeTracker) {
401
+ if (node.type === "ObjectPattern") {
402
+ node.properties.forEach((prop) => {
403
+ if (prop.type === "Property" && prop.value.type === "Identifier") {
404
+ scopeTracker.declareVariable(
405
+ prop.value.name,
406
+ prop.value,
407
+ getLineNumber(prop.value),
408
+ {
409
+ isParameter,
410
+ isDestructured: true
411
+ }
412
+ );
413
+ }
414
+ });
415
+ } else if (node.type === "ArrayPattern") {
416
+ for (const element of node.elements) {
417
+ if (element?.type === "Identifier") {
418
+ scopeTracker.declareVariable(
419
+ element.name,
420
+ element,
421
+ getLineNumber(element),
422
+ {
423
+ isParameter,
424
+ isDestructured: true
425
+ }
426
+ );
427
+ }
428
+ }
429
+ }
430
+ }
431
+
432
+ // src/analyzers/patterns.ts
433
+ import { readFileSync as readFileSync2 } from "fs";
434
+ import { Severity as Severity3 } from "@aiready/core";
435
+ async function analyzePatterns(filePaths) {
436
+ const issues = [];
437
+ const contents = /* @__PURE__ */ new Map();
438
+ const tryCatchPattern = /try\s*\{/g;
439
+ const catchPattern = /catch\s*\(\s*(\w+)\s*\)/g;
440
+ const styleStats = {
441
+ tryCatch: 0,
442
+ thenCatch: 0,
443
+ asyncAwait: 0,
444
+ commonJs: 0,
445
+ esm: 0
446
+ };
447
+ for (const filePath of filePaths) {
448
+ try {
449
+ const content = readFileSync2(filePath, "utf-8");
450
+ contents.set(filePath, content);
451
+ if (content.match(tryCatchPattern)) styleStats.tryCatch++;
452
+ if (content.match(/\.catch\s*\(/)) styleStats.thenCatch++;
453
+ if (content.match(/\bawait\b/)) styleStats.asyncAwait++;
454
+ if (content.match(/\brequire\s*\(/)) styleStats.commonJs++;
455
+ if (content.match(/\bimport\b.*\bfrom\b/)) styleStats.esm++;
456
+ } catch (err) {
457
+ void err;
458
+ }
459
+ }
460
+ const totalFiles = filePaths.length;
461
+ if (styleStats.tryCatch > 0 && styleStats.thenCatch > 0) {
462
+ const dominant = styleStats.tryCatch >= styleStats.thenCatch ? "try-catch" : ".catch()";
463
+ const minority = dominant === "try-catch" ? ".catch()" : "try-catch";
464
+ issues.push({
465
+ files: filePaths.filter((f) => {
466
+ const c = contents.get(f) || "";
467
+ return minority === "try-catch" ? c.match(tryCatchPattern) : c.match(/\.catch\s*\(/);
468
+ }),
469
+ type: "error-handling",
470
+ description: `Mixed error handling styles: codebase primarily uses ${dominant}, but found ${minority} in some files.`,
471
+ examples: [dominant, minority],
472
+ severity: Severity3.Minor
473
+ });
474
+ }
475
+ if (styleStats.commonJs > 0 && styleStats.esm > 0) {
476
+ const minority = styleStats.esm >= styleStats.commonJs ? "CommonJS (require)" : "ESM (import)";
477
+ issues.push({
478
+ files: filePaths.filter((f) => {
479
+ const c = contents.get(f) || "";
480
+ return minority === "CommonJS (require)" ? c.match(/\brequire\s*\(/) : c.match(/\bimport\b/);
481
+ }),
482
+ type: "import-style",
483
+ description: `Mixed module systems: found both ESM and CommonJS.`,
484
+ examples: ['import X from "y"', 'const X = require("y")'],
485
+ severity: Severity3.Major
486
+ });
487
+ }
488
+ return issues;
489
+ }
490
+
491
+ // src/analyzer.ts
492
+ import {
493
+ scanFiles,
494
+ Severity as Severity5,
495
+ IssueType,
496
+ GLOBAL_SCAN_OPTIONS
497
+ } from "@aiready/core";
498
+
499
+ // src/analyzers/naming-python.ts
500
+ import { getParser, Severity as Severity4 } from "@aiready/core";
501
+ async function analyzePythonNaming(files) {
502
+ const issues = [];
503
+ const parser = getParser("dummy.py");
504
+ if (!parser) {
505
+ console.warn("Python parser not available");
506
+ return issues;
507
+ }
508
+ const pythonFiles = files.filter((f) => f.toLowerCase().endsWith(".py"));
509
+ for (const file of pythonFiles) {
510
+ try {
511
+ const fs = await import("fs");
512
+ const code = await fs.promises.readFile(file, "utf-8");
513
+ const result = parser.parse(code, file);
514
+ for (const exp of result.exports) {
515
+ const nameIssue = checkPythonNaming(
516
+ exp.name,
517
+ exp.type,
518
+ file,
519
+ exp.loc?.start.line || 0
520
+ );
521
+ if (nameIssue) {
522
+ issues.push(nameIssue);
523
+ }
524
+ }
525
+ for (const imp of result.imports) {
526
+ for (const spec of imp.specifiers) {
527
+ if (spec !== "*" && spec !== "default") {
528
+ const nameIssue = checkPythonNaming(
529
+ spec,
530
+ "variable",
531
+ file,
532
+ imp.loc?.start.line || 0
533
+ );
534
+ if (nameIssue) {
535
+ issues.push(nameIssue);
536
+ }
537
+ }
538
+ }
539
+ }
540
+ } catch (error) {
541
+ console.warn(`Skipping ${file} due to error:`, error);
542
+ }
543
+ }
544
+ return issues;
545
+ }
546
+ function checkPythonNaming(identifier, type, file, line) {
547
+ const parser = getParser("dummy.py");
548
+ const conventions = parser?.getNamingConventions();
549
+ if (!conventions) return null;
550
+ if (conventions.exceptions?.includes(identifier)) {
551
+ return null;
552
+ }
553
+ if (type === "class") {
554
+ if (!conventions.classPattern.test(identifier)) {
555
+ return {
556
+ type: "poor-naming",
557
+ identifier,
558
+ file,
559
+ line,
560
+ column: 0,
561
+ severity: Severity4.Major,
562
+ category: "naming",
563
+ suggestion: `Class names should use PascalCase (e.g., ${toPascalCase(identifier)})`
564
+ };
565
+ }
566
+ } else if (type === "function") {
567
+ if (!conventions.functionPattern.test(identifier)) {
568
+ if (/^[a-z][a-zA-Z0-9]*$/.test(identifier) && /[A-Z]/.test(identifier)) {
569
+ return {
570
+ type: "convention-mix",
571
+ identifier,
572
+ file,
573
+ line,
574
+ column: 0,
575
+ severity: Severity4.Major,
576
+ category: "naming",
577
+ suggestion: `Function names should use snake_case, not camelCase (e.g., ${toSnakeCase(identifier)})`
578
+ };
579
+ }
580
+ }
581
+ } else if (type === "const" || type === "variable") {
582
+ if (identifier === identifier.toUpperCase() && identifier.length > 1) {
583
+ if (!conventions.constantPattern.test(identifier)) {
584
+ return {
585
+ type: "poor-naming",
586
+ identifier,
587
+ file,
588
+ line,
589
+ column: 0,
590
+ severity: Severity4.Minor,
591
+ category: "naming",
592
+ suggestion: "Constants should use UPPER_CASE_WITH_UNDERSCORES"
593
+ };
594
+ }
595
+ } else {
596
+ if (!conventions.variablePattern.test(identifier)) {
597
+ if (/^[a-z][a-zA-Z0-9]*$/.test(identifier) && /[A-Z]/.test(identifier)) {
598
+ return {
599
+ type: "convention-mix",
600
+ identifier,
601
+ file,
602
+ line,
603
+ column: 0,
604
+ severity: Severity4.Major,
605
+ category: "naming",
606
+ suggestion: `Variable names should use snake_case, not camelCase (e.g., ${toSnakeCase(identifier)})`
607
+ };
608
+ }
609
+ }
610
+ }
611
+ }
612
+ return null;
613
+ }
614
+ function toSnakeCase(str) {
615
+ return str.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
616
+ }
617
+ function toPascalCase(str) {
618
+ return str.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
619
+ }
620
+
621
+ // src/analyzer.ts
622
+ async function analyzeConsistency(options) {
623
+ const {
624
+ checkNaming = true,
625
+ checkPatterns = true,
626
+ checkArchitecture = false,
627
+ // Not implemented yet
628
+ minSeverity = Severity5.Info,
629
+ ...scanOptions
630
+ } = options;
631
+ void checkArchitecture;
632
+ const filePaths = await scanFiles(scanOptions);
633
+ const tsJsFiles = filePaths.filter((f) => /\.(ts|tsx|js|jsx)$/i.test(f));
634
+ const pythonFiles = filePaths.filter((f) => /\.py$/i.test(f));
635
+ let namingIssues = [];
636
+ if (checkNaming) {
637
+ const tsJsNamingIssues = tsJsFiles.length > 0 ? await analyzeNamingAST(tsJsFiles) : [];
638
+ const pythonNamingIssues = pythonFiles.length > 0 ? await analyzePythonNaming(pythonFiles) : [];
639
+ namingIssues = [...tsJsNamingIssues, ...pythonNamingIssues];
640
+ }
641
+ const patternIssues = checkPatterns ? await analyzePatterns(filePaths) : [];
642
+ const results = [];
643
+ const fileIssuesMap = /* @__PURE__ */ new Map();
644
+ for (const issue of namingIssues) {
645
+ if (!shouldIncludeSeverity(issue.severity, minSeverity)) {
646
+ continue;
647
+ }
648
+ const consistencyIssue = {
649
+ type: issue.type === "convention-mix" ? IssueType.NamingInconsistency : IssueType.NamingQuality,
650
+ category: "naming",
651
+ severity: getSeverityEnum(issue.severity),
652
+ message: `${issue.type}: ${issue.identifier}`,
653
+ location: {
654
+ file: issue.file,
655
+ line: issue.line,
656
+ column: issue.column
657
+ },
658
+ suggestion: issue.suggestion
659
+ };
660
+ if (!fileIssuesMap.has(issue.file)) {
661
+ fileIssuesMap.set(issue.file, []);
662
+ }
663
+ fileIssuesMap.get(issue.file).push(consistencyIssue);
664
+ }
665
+ for (const issue of patternIssues) {
666
+ if (!shouldIncludeSeverity(issue.severity, minSeverity)) {
667
+ continue;
668
+ }
669
+ const consistencyIssue = {
670
+ type: IssueType.PatternInconsistency,
671
+ category: "patterns",
672
+ severity: getSeverityEnum(issue.severity),
673
+ message: issue.description,
674
+ location: {
675
+ file: issue.files[0] || "multiple files",
676
+ line: 1
677
+ },
678
+ examples: issue.examples,
679
+ suggestion: `Standardize ${issue.type} patterns across ${issue.files.length} files`
680
+ };
681
+ const firstFile = issue.files[0];
682
+ if (firstFile && !fileIssuesMap.has(firstFile)) {
683
+ fileIssuesMap.set(firstFile, []);
684
+ }
685
+ if (firstFile) {
686
+ fileIssuesMap.get(firstFile).push(consistencyIssue);
687
+ }
688
+ }
689
+ for (const [fileName, issues] of fileIssuesMap) {
690
+ results.push({
691
+ fileName,
692
+ issues,
693
+ metrics: {
694
+ consistencyScore: calculateConsistencyScore(issues)
695
+ }
696
+ });
697
+ }
698
+ results.sort((fileResultA, fileResultB) => {
699
+ const maxSeverityA = Math.min(
700
+ ...fileResultA.issues.map((i) => {
701
+ const val = getSeverityLevel(i.severity);
702
+ return val === 4 ? 0 : val === 3 ? 1 : val === 2 ? 2 : 3;
703
+ })
704
+ );
705
+ const maxSeverityB = Math.min(
706
+ ...fileResultB.issues.map((i) => {
707
+ const val = getSeverityLevel(i.severity);
708
+ return val === 4 ? 0 : val === 3 ? 1 : val === 2 ? 2 : 3;
709
+ })
710
+ );
711
+ if (maxSeverityA !== maxSeverityB) {
712
+ return maxSeverityA - maxSeverityB;
713
+ }
714
+ return fileResultB.issues.length - fileResultA.issues.length;
715
+ });
716
+ const recommendations = generateRecommendations(namingIssues, patternIssues);
717
+ const namingCountFiltered = namingIssues.filter(
718
+ (i) => shouldIncludeSeverity(i.severity, minSeverity)
719
+ ).length;
720
+ const patternCountFiltered = patternIssues.filter(
721
+ (i) => shouldIncludeSeverity(i.severity, minSeverity)
722
+ ).length;
723
+ return {
724
+ summary: {
725
+ totalIssues: namingCountFiltered + patternCountFiltered,
726
+ namingIssues: namingCountFiltered,
727
+ patternIssues: patternCountFiltered,
728
+ architectureIssues: 0,
729
+ filesAnalyzed: filePaths.length,
730
+ config: Object.fromEntries(
731
+ Object.entries(options).filter(
732
+ ([key]) => !GLOBAL_SCAN_OPTIONS.includes(key) || key === "rootDir"
733
+ )
734
+ )
735
+ },
736
+ results,
737
+ recommendations
738
+ };
739
+ }
740
+ function getSeverityLevel(s) {
741
+ if (s === Severity5.Critical || s === "critical") return 4;
742
+ if (s === Severity5.Major || s === "major") return 3;
743
+ if (s === Severity5.Minor || s === "minor") return 2;
744
+ if (s === Severity5.Info || s === "info") return 1;
745
+ return 0;
746
+ }
747
+ function getSeverityEnum(s) {
748
+ const val = getSeverityLevel(s);
749
+ switch (val) {
750
+ case 4:
751
+ return Severity5.Critical;
752
+ case 3:
753
+ return Severity5.Major;
754
+ case 2:
755
+ return Severity5.Minor;
756
+ case 1:
757
+ return Severity5.Info;
758
+ default:
759
+ return Severity5.Info;
760
+ }
761
+ }
762
+ function shouldIncludeSeverity(severity, minSeverity) {
763
+ return getSeverityLevel(severity) >= getSeverityLevel(minSeverity);
764
+ }
765
+ function calculateConsistencyScore(issues) {
766
+ let totalWeight = 0;
767
+ for (const issue of issues) {
768
+ const val = getSeverityLevel(issue.severity);
769
+ switch (val) {
770
+ case 4:
771
+ totalWeight += 10;
772
+ break;
773
+ case 3:
774
+ totalWeight += 5;
775
+ break;
776
+ case 2:
777
+ totalWeight += 2;
778
+ break;
779
+ case 1:
780
+ totalWeight += 1;
781
+ break;
782
+ default:
783
+ totalWeight += 1;
784
+ }
785
+ }
786
+ return Math.max(0, 1 - totalWeight / 100);
787
+ }
788
+ function generateRecommendations(namingIssues, patternIssues) {
789
+ const recommendations = [];
790
+ if (namingIssues.length > 0) {
791
+ const conventionMixCount = namingIssues.filter(
792
+ (i) => i.type === "convention-mix"
793
+ ).length;
794
+ if (conventionMixCount > 0) {
795
+ recommendations.push(
796
+ `Standardize naming conventions: Found ${conventionMixCount} snake_case variables in TypeScript/JavaScript (use camelCase)`
797
+ );
798
+ }
799
+ const poorNamingCount = namingIssues.filter(
800
+ (i) => i.type === "poor-naming"
801
+ ).length;
802
+ if (poorNamingCount > 0) {
803
+ recommendations.push(
804
+ `Improve variable naming: Found ${poorNamingCount} single-letter or unclear variable names`
805
+ );
806
+ }
807
+ }
808
+ if (patternIssues.length > 0) {
809
+ const errorHandlingIssues = patternIssues.filter(
810
+ (i) => i.type === "error-handling"
811
+ );
812
+ if (errorHandlingIssues.length > 0) {
813
+ recommendations.push(
814
+ "Standardize error handling strategy across the codebase (prefer try-catch with typed errors)"
815
+ );
816
+ }
817
+ const asyncIssues = patternIssues.filter((i) => i.type === "async-style");
818
+ if (asyncIssues.length > 0) {
819
+ recommendations.push(
820
+ "Use async/await consistently instead of mixing with promise chains or callbacks"
821
+ );
822
+ }
823
+ const importIssues = patternIssues.filter((i) => i.type === "import-style");
824
+ if (importIssues.length > 0) {
825
+ recommendations.push(
826
+ "Use ES modules consistently across the project (avoid mixing with CommonJS)"
827
+ );
828
+ }
829
+ }
830
+ if (recommendations.length === 0) {
831
+ recommendations.push(
832
+ "No major consistency issues found! Your codebase follows good practices."
833
+ );
834
+ }
835
+ return recommendations;
836
+ }
837
+
838
+ export {
839
+ analyzeNamingAST,
840
+ analyzePatterns,
841
+ analyzeConsistency
842
+ };
package/dist/cli.js CHANGED
@@ -522,7 +522,7 @@ function checkPythonNaming(identifier, type, file, line) {
522
522
  file,
523
523
  line,
524
524
  column: 0,
525
- severity: "major",
525
+ severity: import_core3.Severity.Major,
526
526
  category: "naming",
527
527
  suggestion: `Class names should use PascalCase (e.g., ${toPascalCase(identifier)})`
528
528
  };
@@ -536,7 +536,7 @@ function checkPythonNaming(identifier, type, file, line) {
536
536
  file,
537
537
  line,
538
538
  column: 0,
539
- severity: "major",
539
+ severity: import_core3.Severity.Major,
540
540
  category: "naming",
541
541
  suggestion: `Function names should use snake_case, not camelCase (e.g., ${toSnakeCase(identifier)})`
542
542
  };
@@ -551,7 +551,7 @@ function checkPythonNaming(identifier, type, file, line) {
551
551
  file,
552
552
  line,
553
553
  column: 0,
554
- severity: "minor",
554
+ severity: import_core3.Severity.Minor,
555
555
  category: "naming",
556
556
  suggestion: "Constants should use UPPER_CASE_WITH_UNDERSCORES"
557
557
  };
@@ -565,7 +565,7 @@ function checkPythonNaming(identifier, type, file, line) {
565
565
  file,
566
566
  line,
567
567
  column: 0,
568
- severity: "major",
568
+ severity: import_core3.Severity.Major,
569
569
  category: "naming",
570
570
  suggestion: `Variable names should use snake_case, not camelCase (e.g., ${toSnakeCase(identifier)})`
571
571
  };
package/dist/cli.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  analyzeConsistency
4
- } from "./chunk-YCDCIOJN.mjs";
4
+ } from "./chunk-V2UPXL7L.mjs";
5
5
 
6
6
  // src/cli.ts
7
7
  import { Command } from "commander";
package/dist/index.js CHANGED
@@ -540,7 +540,7 @@ function checkPythonNaming(identifier, type, file, line) {
540
540
  file,
541
541
  line,
542
542
  column: 0,
543
- severity: "major",
543
+ severity: import_core3.Severity.Major,
544
544
  category: "naming",
545
545
  suggestion: `Class names should use PascalCase (e.g., ${toPascalCase(identifier)})`
546
546
  };
@@ -554,7 +554,7 @@ function checkPythonNaming(identifier, type, file, line) {
554
554
  file,
555
555
  line,
556
556
  column: 0,
557
- severity: "major",
557
+ severity: import_core3.Severity.Major,
558
558
  category: "naming",
559
559
  suggestion: `Function names should use snake_case, not camelCase (e.g., ${toSnakeCase(identifier)})`
560
560
  };
@@ -569,7 +569,7 @@ function checkPythonNaming(identifier, type, file, line) {
569
569
  file,
570
570
  line,
571
571
  column: 0,
572
- severity: "minor",
572
+ severity: import_core3.Severity.Minor,
573
573
  category: "naming",
574
574
  suggestion: "Constants should use UPPER_CASE_WITH_UNDERSCORES"
575
575
  };
@@ -583,7 +583,7 @@ function checkPythonNaming(identifier, type, file, line) {
583
583
  file,
584
584
  line,
585
585
  column: 0,
586
- severity: "major",
586
+ severity: import_core3.Severity.Major,
587
587
  category: "naming",
588
588
  suggestion: `Variable names should use snake_case, not camelCase (e.g., ${toSnakeCase(identifier)})`
589
589
  };
package/dist/index.mjs CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  analyzeConsistency,
3
3
  analyzeNamingAST,
4
4
  analyzePatterns
5
- } from "./chunk-YCDCIOJN.mjs";
5
+ } from "./chunk-V2UPXL7L.mjs";
6
6
 
7
7
  // src/index.ts
8
8
  import { ToolRegistry } from "@aiready/core";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiready/consistency",
3
- "version": "0.18.11",
3
+ "version": "0.18.14",
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",
@@ -43,7 +43,7 @@
43
43
  "@typescript-eslint/typescript-estree": "^8.53.0",
44
44
  "chalk": "^5.3.0",
45
45
  "commander": "^14.0.0",
46
- "@aiready/core": "0.21.11"
46
+ "@aiready/core": "0.21.14"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/node": "^24.0.0",
@@ -1,4 +1,5 @@
1
1
  import { describe, it, expect } from 'vitest';
2
+ import { Severity } from '@aiready/core';
2
3
  import { analyzeConsistency } from '../analyzer';
3
4
  import { analyzePatterns } from '../analyzers/patterns';
4
5
 
@@ -19,7 +20,7 @@ describe('analyzeConsistency', () => {
19
20
  it('should detect minimum severity filtering', async () => {
20
21
  const report = await analyzeConsistency({
21
22
  rootDir: './src',
22
- minSeverity: 'major',
23
+ minSeverity: Severity.Major,
23
24
  });
24
25
 
25
26
  // All issues should be major or critical
@@ -5,7 +5,7 @@
5
5
  * https://peps.python.org/pep-0008/#naming-conventions
6
6
  */
7
7
 
8
- import { getParser } from '@aiready/core';
8
+ import { getParser, Severity } from '@aiready/core';
9
9
  import type { NamingIssue } from '../types';
10
10
 
11
11
  /**
@@ -97,7 +97,7 @@ function checkPythonNaming(
97
97
  file,
98
98
  line,
99
99
  column: 0,
100
- severity: 'major',
100
+ severity: Severity.Major,
101
101
  category: 'naming',
102
102
  suggestion: `Class names should use PascalCase (e.g., ${toPascalCase(identifier)})`,
103
103
  };
@@ -113,7 +113,7 @@ function checkPythonNaming(
113
113
  file,
114
114
  line,
115
115
  column: 0,
116
- severity: 'major',
116
+ severity: Severity.Major,
117
117
  category: 'naming',
118
118
  suggestion: `Function names should use snake_case, not camelCase (e.g., ${toSnakeCase(identifier)})`,
119
119
  };
@@ -130,7 +130,7 @@ function checkPythonNaming(
130
130
  file,
131
131
  line,
132
132
  column: 0,
133
- severity: 'minor',
133
+ severity: Severity.Minor,
134
134
  category: 'naming',
135
135
  suggestion: 'Constants should use UPPER_CASE_WITH_UNDERSCORES',
136
136
  };
@@ -149,7 +149,7 @@ function checkPythonNaming(
149
149
  file,
150
150
  line,
151
151
  column: 0,
152
- severity: 'major',
152
+ severity: Severity.Major,
153
153
  category: 'naming',
154
154
  suggestion: `Variable names should use snake_case, not camelCase (e.g., ${toSnakeCase(identifier)})`,
155
155
  };
@@ -31,10 +31,14 @@ export async function loadNamingConfig(files: string[]): Promise<NamingConfig> {
31
31
 
32
32
  // Extract custom configuration
33
33
  const customAbbreviations = new Set(
34
- consistencyConfig?.acceptedAbbreviations || []
34
+ (consistencyConfig?.acceptedAbbreviations as string[]) || []
35
+ );
36
+ const customShortWords = new Set(
37
+ (consistencyConfig?.shortWords as string[]) || []
38
+ );
39
+ const disabledChecks = new Set(
40
+ (consistencyConfig?.disableChecks as string[]) || []
35
41
  );
36
- const customShortWords = new Set(consistencyConfig?.shortWords || []);
37
- const disabledChecks = new Set(consistencyConfig?.disableChecks || []);
38
42
 
39
43
  // Merge with defaults
40
44
  const allAbbreviations = new Set([