@aiready/consistency 0.21.14 → 0.21.16

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.
@@ -0,0 +1,1237 @@
1
+ // src/analyzers/naming-constants.ts
2
+ var VAGUE_NAMES = /* @__PURE__ */ new Set([
3
+ "data",
4
+ "info",
5
+ "item",
6
+ "obj",
7
+ "val",
8
+ "tmp",
9
+ "temp",
10
+ "thing",
11
+ "stuff",
12
+ "res",
13
+ "ret",
14
+ "result",
15
+ "output",
16
+ "input",
17
+ "params",
18
+ "args",
19
+ "props",
20
+ "state",
21
+ "value",
22
+ "list",
23
+ "array",
24
+ "map",
25
+ "set",
26
+ "manager",
27
+ "handler",
28
+ "helper",
29
+ "util",
30
+ "common"
31
+ ]);
32
+ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
33
+ // Standard identifiers
34
+ "id",
35
+ "uid",
36
+ "gid",
37
+ "pid",
38
+ // Loop counters and iterators
39
+ "i",
40
+ "j",
41
+ "k",
42
+ "n",
43
+ "m",
44
+ // Web/Network
45
+ "url",
46
+ "uri",
47
+ "api",
48
+ "cdn",
49
+ "dns",
50
+ "ip",
51
+ "tcp",
52
+ "udp",
53
+ "http",
54
+ "ssl",
55
+ "tls",
56
+ "utm",
57
+ "seo",
58
+ "rss",
59
+ "xhr",
60
+ "ajax",
61
+ "cors",
62
+ "ws",
63
+ "wss",
64
+ // Data formats
65
+ "json",
66
+ "xml",
67
+ "yaml",
68
+ "csv",
69
+ "html",
70
+ "css",
71
+ "svg",
72
+ "pdf",
73
+ // File types & extensions
74
+ "img",
75
+ "txt",
76
+ "doc",
77
+ "docx",
78
+ "xlsx",
79
+ "ppt",
80
+ "md",
81
+ "rst",
82
+ "jpg",
83
+ "png",
84
+ "gif",
85
+ // Databases
86
+ "db",
87
+ "sql",
88
+ "orm",
89
+ "dao",
90
+ "dto",
91
+ "ddb",
92
+ "rds",
93
+ "nosql",
94
+ // File system
95
+ "fs",
96
+ "dir",
97
+ "tmp",
98
+ "src",
99
+ "dst",
100
+ "bin",
101
+ "lib",
102
+ "pkg",
103
+ // Operating system
104
+ "os",
105
+ "env",
106
+ "arg",
107
+ "cli",
108
+ "cmd",
109
+ "exe",
110
+ "cwd",
111
+ "pwd",
112
+ // UI/UX
113
+ "ui",
114
+ "ux",
115
+ "gui",
116
+ "dom",
117
+ "ref",
118
+ // Request/Response
119
+ "req",
120
+ "res",
121
+ "ctx",
122
+ "err",
123
+ "msg",
124
+ "auth",
125
+ // Mathematics/Computing
126
+ "max",
127
+ "min",
128
+ "avg",
129
+ "sum",
130
+ "abs",
131
+ "cos",
132
+ "sin",
133
+ "tan",
134
+ "log",
135
+ "exp",
136
+ "pow",
137
+ "sqrt",
138
+ "std",
139
+ "var",
140
+ "int",
141
+ "num",
142
+ "idx",
143
+ // Time
144
+ "now",
145
+ "utc",
146
+ "tz",
147
+ "ms",
148
+ "sec",
149
+ "hr",
150
+ "min",
151
+ "yr",
152
+ "mo",
153
+ // Common patterns
154
+ "app",
155
+ "cfg",
156
+ "config",
157
+ "init",
158
+ "len",
159
+ "val",
160
+ "str",
161
+ "obj",
162
+ "arr",
163
+ "gen",
164
+ "def",
165
+ "raw",
166
+ "new",
167
+ "old",
168
+ "pre",
169
+ "post",
170
+ "sub",
171
+ "pub",
172
+ // Programming/Framework specific
173
+ "ts",
174
+ "js",
175
+ "jsx",
176
+ "tsx",
177
+ "py",
178
+ "rb",
179
+ "vue",
180
+ "re",
181
+ "fn",
182
+ "fns",
183
+ "mod",
184
+ "opts",
185
+ "dev",
186
+ // Cloud/Infrastructure
187
+ "s3",
188
+ "ec2",
189
+ "sqs",
190
+ "sns",
191
+ "vpc",
192
+ "ami",
193
+ "iam",
194
+ "acl",
195
+ "elb",
196
+ "alb",
197
+ "nlb",
198
+ "aws",
199
+ "ses",
200
+ "gst",
201
+ "cdk",
202
+ "btn",
203
+ "buf",
204
+ "agg",
205
+ "ocr",
206
+ "ai",
207
+ "cf",
208
+ "cfn",
209
+ "ga",
210
+ // Metrics/Performance
211
+ "fcp",
212
+ "lcp",
213
+ "cls",
214
+ "ttfb",
215
+ "tti",
216
+ "fid",
217
+ "fps",
218
+ "qps",
219
+ "rps",
220
+ "tps",
221
+ "wpm",
222
+ // Testing & i18n
223
+ "po",
224
+ "e2e",
225
+ "a11y",
226
+ "i18n",
227
+ "l10n",
228
+ "spy",
229
+ // Domain-specific abbreviations (context-aware)
230
+ "sk",
231
+ "fy",
232
+ "faq",
233
+ "og",
234
+ "seo",
235
+ "cta",
236
+ "roi",
237
+ "kpi",
238
+ "ttl",
239
+ "pct",
240
+ // Technical abbreviations
241
+ "mac",
242
+ "hex",
243
+ "esm",
244
+ "git",
245
+ "rec",
246
+ "loc",
247
+ "dup",
248
+ // Boolean helpers (these are intentional short names)
249
+ "is",
250
+ "has",
251
+ "can",
252
+ "did",
253
+ "was",
254
+ "are",
255
+ // Date/Time context (when in date contexts)
256
+ "d",
257
+ "t",
258
+ "dt",
259
+ // Coverage metrics (industry standard: statements/branches/functions/lines)
260
+ "s",
261
+ "b",
262
+ "f",
263
+ "l",
264
+ // Common media/content abbreviations
265
+ "vid",
266
+ "pic",
267
+ "img",
268
+ "doc",
269
+ "msg"
270
+ ]);
271
+ function detectNamingConventions(files, allIssues) {
272
+ const camelCaseCount = allIssues.filter(
273
+ (i) => i.type === "convention-mix"
274
+ ).length;
275
+ const totalChecks = files.length * 10;
276
+ if (camelCaseCount / totalChecks > 0.3) {
277
+ return { dominantConvention: "mixed", conventionScore: 0.5 };
278
+ }
279
+ return { dominantConvention: "camelCase", conventionScore: 0.9 };
280
+ }
281
+
282
+ // src/analyzers/naming-ast.ts
283
+ import { Severity as Severity2 } from "@aiready/core";
284
+
285
+ // src/utils/ast-parser.ts
286
+ import { parse } from "@typescript-eslint/typescript-estree";
287
+ import { readFileSync } from "fs";
288
+ function parseFile(filePath, content) {
289
+ try {
290
+ const code = content ?? readFileSync(filePath, "utf-8");
291
+ const isTypeScript = filePath.match(/\.tsx?$/);
292
+ return parse(code, {
293
+ jsx: filePath.match(/\.[jt]sx$/i) !== null,
294
+ loc: true,
295
+ range: true,
296
+ comment: false,
297
+ tokens: false,
298
+ // Relaxed parsing for JavaScript files
299
+ sourceType: "module",
300
+ ecmaVersion: "latest",
301
+ // Only use TypeScript parser features for .ts/.tsx files
302
+ filePath: isTypeScript ? filePath : void 0
303
+ });
304
+ } catch (error) {
305
+ void error;
306
+ return null;
307
+ }
308
+ }
309
+ function traverseAST(node, visitor, parent = null) {
310
+ if (!node) return;
311
+ visitor.enter?.(node, parent);
312
+ for (const key of Object.keys(node)) {
313
+ const value = node[key];
314
+ if (Array.isArray(value)) {
315
+ for (const child of value) {
316
+ if (child && typeof child === "object" && "type" in child) {
317
+ traverseAST(child, visitor, node);
318
+ }
319
+ }
320
+ } else if (value && typeof value === "object" && "type" in value) {
321
+ traverseAST(value, visitor, node);
322
+ }
323
+ }
324
+ visitor.leave?.(node, parent);
325
+ }
326
+ function isLoopStatement(node) {
327
+ return [
328
+ "ForStatement",
329
+ "ForInStatement",
330
+ "ForOfStatement",
331
+ "WhileStatement",
332
+ "DoWhileStatement"
333
+ ].includes(node.type);
334
+ }
335
+ function getLineNumber(node) {
336
+ return node.loc?.start.line ?? 0;
337
+ }
338
+
339
+ // src/utils/context-detector.ts
340
+ import { Severity } from "@aiready/core";
341
+ function detectFileType(filePath, ast) {
342
+ void ast;
343
+ const path = filePath.toLowerCase();
344
+ if (path.match(/\.(test|spec)\.(ts|tsx|js|jsx)$/) || path.includes("__tests__")) {
345
+ return "test";
346
+ }
347
+ if (path.endsWith(".d.ts") || path.includes("types")) {
348
+ return "types";
349
+ }
350
+ if (path.match(/config|\.config\.|rc\.|setup/) || path.includes("configuration")) {
351
+ return "config";
352
+ }
353
+ return "production";
354
+ }
355
+ function detectCodeLayer(ast) {
356
+ let hasAPIIndicators = 0;
357
+ let hasBusinessIndicators = 0;
358
+ let hasDataIndicators = 0;
359
+ let hasUtilityIndicators = 0;
360
+ traverseAST(ast, {
361
+ enter: (node) => {
362
+ if (node.type === "ImportDeclaration") {
363
+ const source = node.source.value;
364
+ if (source.match(/express|fastify|koa|@nestjs|axios|fetch|http/i)) {
365
+ hasAPIIndicators++;
366
+ }
367
+ if (source.match(/database|prisma|typeorm|sequelize|mongoose|pg|mysql/i)) {
368
+ hasDataIndicators++;
369
+ }
370
+ }
371
+ if (node.type === "FunctionDeclaration" && node.id) {
372
+ const name = node.id.name;
373
+ if (name.match(
374
+ /^(get|post|put|delete|patch|handle|api|route|controller)/i
375
+ )) {
376
+ hasAPIIndicators++;
377
+ }
378
+ if (name.match(/^(calculate|process|validate|transform|compute|analyze)/i)) {
379
+ hasBusinessIndicators++;
380
+ }
381
+ if (name.match(/^(find|create|update|delete|save|fetch|query|insert)/i)) {
382
+ hasDataIndicators++;
383
+ }
384
+ if (name.match(
385
+ /^(format|parse|convert|normalize|sanitize|encode|decode)/i
386
+ )) {
387
+ hasUtilityIndicators++;
388
+ }
389
+ }
390
+ if (node.type === "ExportNamedDeclaration" || node.type === "ExportDefaultDeclaration") {
391
+ if (node.type === "ExportNamedDeclaration" && node.declaration) {
392
+ if (node.declaration.type === "FunctionDeclaration" && node.declaration.id) {
393
+ const name = node.declaration.id.name;
394
+ if (name.match(/handler|route|api|controller/i)) {
395
+ hasAPIIndicators += 2;
396
+ }
397
+ }
398
+ }
399
+ }
400
+ }
401
+ });
402
+ const scores = {
403
+ api: hasAPIIndicators,
404
+ business: hasBusinessIndicators,
405
+ data: hasDataIndicators,
406
+ utility: hasUtilityIndicators
407
+ };
408
+ const maxScore = Math.max(...Object.values(scores));
409
+ if (maxScore === 0) {
410
+ return "unknown";
411
+ }
412
+ if (scores.api === maxScore) return "api";
413
+ if (scores.data === maxScore) return "data";
414
+ if (scores.business === maxScore) return "business";
415
+ if (scores.utility === maxScore) return "utility";
416
+ return "unknown";
417
+ }
418
+ function calculateComplexity(node) {
419
+ let complexity = 1;
420
+ traverseAST(node, {
421
+ enter: (childNode) => {
422
+ switch (childNode.type) {
423
+ case "IfStatement":
424
+ case "ConditionalExpression":
425
+ // ternary
426
+ case "SwitchCase":
427
+ case "ForStatement":
428
+ case "ForInStatement":
429
+ case "ForOfStatement":
430
+ case "WhileStatement":
431
+ case "DoWhileStatement":
432
+ case "CatchClause":
433
+ complexity++;
434
+ break;
435
+ case "LogicalExpression":
436
+ if (childNode.operator === "&&" || childNode.operator === "||") {
437
+ complexity++;
438
+ }
439
+ break;
440
+ }
441
+ }
442
+ });
443
+ return complexity;
444
+ }
445
+ function buildCodeContext(filePath, ast) {
446
+ const fileType = detectFileType(filePath, ast);
447
+ const codeLayer = detectCodeLayer(ast);
448
+ let totalComplexity = 0;
449
+ let functionCount = 0;
450
+ traverseAST(ast, {
451
+ enter: (node) => {
452
+ if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") {
453
+ totalComplexity += calculateComplexity(node);
454
+ functionCount++;
455
+ }
456
+ }
457
+ });
458
+ const avgComplexity = functionCount > 0 ? totalComplexity / functionCount : 1;
459
+ return {
460
+ fileType,
461
+ codeLayer,
462
+ complexity: Math.round(avgComplexity),
463
+ isTestFile: fileType === "test",
464
+ isTypeDefinition: fileType === "types"
465
+ };
466
+ }
467
+ function adjustSeverity(baseSeverity, context, issueType) {
468
+ const getEnum = (s) => {
469
+ if (s === Severity.Critical || s === "critical") return Severity.Critical;
470
+ if (s === Severity.Major || s === "major") return Severity.Major;
471
+ if (s === Severity.Minor || s === "minor") return Severity.Minor;
472
+ return Severity.Info;
473
+ };
474
+ let currentSev = getEnum(baseSeverity);
475
+ if (context.isTestFile) {
476
+ if (currentSev === Severity.Minor) currentSev = Severity.Info;
477
+ if (currentSev === Severity.Major) currentSev = Severity.Minor;
478
+ }
479
+ if (context.isTypeDefinition) {
480
+ if (currentSev === Severity.Minor) currentSev = Severity.Info;
481
+ }
482
+ if (context.codeLayer === "api") {
483
+ if (currentSev === Severity.Info && issueType === "unclear")
484
+ currentSev = Severity.Minor;
485
+ if (currentSev === Severity.Minor && issueType === "unclear")
486
+ currentSev = Severity.Major;
487
+ }
488
+ if (context.complexity > 10) {
489
+ if (currentSev === Severity.Info) currentSev = Severity.Minor;
490
+ }
491
+ if (context.codeLayer === "utility") {
492
+ if (currentSev === Severity.Minor && issueType === "abbreviation")
493
+ currentSev = Severity.Info;
494
+ }
495
+ return currentSev;
496
+ }
497
+ function isAcceptableInContext(name, context, options) {
498
+ if (options.isLoopVariable && ["i", "j", "k", "l", "n", "m"].includes(name)) {
499
+ return true;
500
+ }
501
+ if (context.isTestFile) {
502
+ if (["a", "b", "c", "x", "y", "z"].includes(name) && options.isParameter) {
503
+ return true;
504
+ }
505
+ }
506
+ if (context.codeLayer === "utility" && ["x", "y", "z"].includes(name)) {
507
+ return true;
508
+ }
509
+ if (options.isDestructured) {
510
+ if (["s", "b", "f", "l"].includes(name)) {
511
+ return true;
512
+ }
513
+ }
514
+ if (options.isParameter && (options.complexity ?? context.complexity) < 3) {
515
+ if (name.length >= 2) {
516
+ return true;
517
+ }
518
+ }
519
+ return false;
520
+ }
521
+
522
+ // src/analyzers/naming-ast.ts
523
+ var KNOWN_FONT_FAMILIES = /* @__PURE__ */ new Set([
524
+ "Geist_Mono",
525
+ "Geist_Sans",
526
+ "JetBrains_Mono",
527
+ "Fira_Code",
528
+ "Source_Code_Pro",
529
+ "IBM_Plex_Mono",
530
+ "Space_Mono",
531
+ "Roboto_Mono",
532
+ "Ubuntu_Mono",
533
+ "Inconsolata",
534
+ "Cousine",
535
+ "Anonymous_Pro",
536
+ "Arimo",
537
+ "Tinos",
538
+ "Cabin",
539
+ "Cardo",
540
+ "Gentium",
541
+ "Libre_Baskerville",
542
+ "Lora",
543
+ "Merriweather",
544
+ "Noto_Sans",
545
+ "Noto_Serif",
546
+ "Open_Sans",
547
+ "Playfair_Display",
548
+ "PT_Sans",
549
+ "PT_Serif",
550
+ "Raleway",
551
+ "Roboto",
552
+ "Slabo",
553
+ "Source_Sans_Pro",
554
+ "Source_Serif_Pro",
555
+ "Spectral",
556
+ "Titillium_Web",
557
+ "Ubuntu"
558
+ ]);
559
+ async function analyzeNamingAST(filePaths) {
560
+ const allIssues = [];
561
+ for (const filePath of filePaths) {
562
+ try {
563
+ const ast = parseFile(filePath);
564
+ if (!ast) continue;
565
+ const context = buildCodeContext(filePath, ast);
566
+ const issues = analyzeIdentifiers(ast, filePath, context);
567
+ allIssues.push(...issues);
568
+ } catch (err) {
569
+ void err;
570
+ }
571
+ }
572
+ return allIssues;
573
+ }
574
+ function analyzeIdentifiers(ast, filePath, context) {
575
+ const issues = [];
576
+ const scopeTracker = new ScopeTracker();
577
+ traverseAST(ast, {
578
+ enter: (node) => {
579
+ if (node.type === "VariableDeclarator" && node.id.type === "Identifier") {
580
+ const isParameter = false;
581
+ const isLoopVariable = isLoopStatement(node.parent?.parent);
582
+ scopeTracker.declareVariable(
583
+ node.id.name,
584
+ node.id,
585
+ getLineNumber(node.id),
586
+ { isParameter, isLoopVariable }
587
+ );
588
+ }
589
+ if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") {
590
+ const isArrowParameter = node.type === "ArrowFunctionExpression";
591
+ node.params.forEach((param) => {
592
+ if (param.type === "Identifier") {
593
+ scopeTracker.declareVariable(
594
+ param.name,
595
+ param,
596
+ getLineNumber(param),
597
+ { isParameter: true, isArrowParameter }
598
+ );
599
+ } else if (param.type === "ObjectPattern") {
600
+ extractDestructuredIdentifiers(param, scopeTracker, {
601
+ isParameter: true,
602
+ isArrowParameter
603
+ });
604
+ }
605
+ });
606
+ }
607
+ if ((node.type === "ClassDeclaration" || node.type === "TSInterfaceDeclaration" || node.type === "TSTypeAliasDeclaration") && node.id) {
608
+ checkNamingConvention(
609
+ node.id.name,
610
+ "PascalCase",
611
+ node.id,
612
+ filePath,
613
+ issues,
614
+ context
615
+ );
616
+ }
617
+ }
618
+ });
619
+ for (const varInfo of scopeTracker.getVariables()) {
620
+ checkVariableNaming(varInfo, filePath, issues, context);
621
+ }
622
+ return issues;
623
+ }
624
+ function checkNamingConvention(name, convention, node, file, issues, context) {
625
+ if (KNOWN_FONT_FAMILIES.has(name)) {
626
+ return;
627
+ }
628
+ let isValid = true;
629
+ if (convention === "PascalCase") {
630
+ isValid = /^[A-Z][a-zA-Z0-9]*$/.test(name);
631
+ } else if (convention === "camelCase") {
632
+ isValid = /^[a-z][a-zA-Z0-9]*$/.test(name);
633
+ } else if (convention === "UPPER_CASE") {
634
+ isValid = /^[A-Z][A-Z0-9_]*$/.test(name);
635
+ }
636
+ if (!isValid) {
637
+ const severity = adjustSeverity(Severity2.Info, context, "convention-mix");
638
+ issues.push({
639
+ file,
640
+ line: getLineNumber(node),
641
+ type: "convention-mix",
642
+ identifier: name,
643
+ severity,
644
+ suggestion: `Follow ${convention} for this identifier`
645
+ });
646
+ }
647
+ }
648
+ function checkVariableNaming(varInfo, file, issues, context) {
649
+ const { name, line, options } = varInfo;
650
+ const nameLower = name.toLowerCase();
651
+ if (isAcceptableInContext(name, context, options)) {
652
+ return;
653
+ }
654
+ if (name.length === 1 && !options.isLoopVariable && !options.isArrowParameter && !ACCEPTABLE_ABBREVIATIONS.has(nameLower)) {
655
+ const severity = adjustSeverity(Severity2.Minor, context, "poor-naming");
656
+ issues.push({
657
+ file,
658
+ line,
659
+ type: "poor-naming",
660
+ identifier: name,
661
+ severity,
662
+ suggestion: "Use a more descriptive name than a single letter"
663
+ });
664
+ }
665
+ if (VAGUE_NAMES.has(nameLower)) {
666
+ const severity = adjustSeverity(Severity2.Minor, context, "poor-naming");
667
+ issues.push({
668
+ file,
669
+ line,
670
+ type: "poor-naming",
671
+ identifier: name,
672
+ severity,
673
+ suggestion: `Avoid vague names like '${name}'. What does this data represent?`
674
+ });
675
+ }
676
+ if (name.length > 1 && name.length <= 3 && !options.isLoopVariable && !ACCEPTABLE_ABBREVIATIONS.has(nameLower)) {
677
+ const severity = adjustSeverity(Severity2.Info, context, "abbreviation");
678
+ issues.push({
679
+ file,
680
+ line,
681
+ type: "abbreviation",
682
+ identifier: name,
683
+ severity,
684
+ suggestion: "Avoid non-standard abbreviations"
685
+ });
686
+ }
687
+ }
688
+ var ScopeTracker = class {
689
+ constructor() {
690
+ this.variables = [];
691
+ }
692
+ declareVariable(name, node, line, options = {}) {
693
+ this.variables.push({ name, node, line, options });
694
+ }
695
+ getVariables() {
696
+ return this.variables;
697
+ }
698
+ };
699
+ function extractDestructuredIdentifiers(node, scopeTracker, options = {}) {
700
+ const { isParameter = false, isArrowParameter = false } = options;
701
+ if (node.type === "ObjectPattern") {
702
+ node.properties.forEach((prop) => {
703
+ if (prop.type === "Property" && prop.value.type === "Identifier") {
704
+ scopeTracker.declareVariable(
705
+ prop.value.name,
706
+ prop.value,
707
+ getLineNumber(prop.value),
708
+ {
709
+ isParameter,
710
+ isDestructured: true,
711
+ isArrowParameter
712
+ }
713
+ );
714
+ }
715
+ });
716
+ } else if (node.type === "ArrayPattern") {
717
+ for (const element of node.elements) {
718
+ if (element?.type === "Identifier") {
719
+ scopeTracker.declareVariable(
720
+ element.name,
721
+ element,
722
+ getLineNumber(element),
723
+ {
724
+ isParameter,
725
+ isDestructured: true,
726
+ isArrowParameter
727
+ }
728
+ );
729
+ }
730
+ }
731
+ }
732
+ }
733
+
734
+ // src/analyzers/patterns.ts
735
+ import { readFileSync as readFileSync2 } from "fs";
736
+ import { Severity as Severity3 } from "@aiready/core";
737
+ async function analyzePatterns(filePaths) {
738
+ const issues = [];
739
+ const contents = /* @__PURE__ */ new Map();
740
+ const tryCatchPattern = /try\s*\{/g;
741
+ const styleStats = {
742
+ tryCatch: 0,
743
+ thenCatch: 0,
744
+ asyncAwait: 0,
745
+ commonJs: 0,
746
+ esm: 0
747
+ };
748
+ for (const filePath of filePaths) {
749
+ try {
750
+ const content = readFileSync2(filePath, "utf-8");
751
+ contents.set(filePath, content);
752
+ if (content.match(tryCatchPattern)) styleStats.tryCatch++;
753
+ if (content.match(/\.catch\s*\(/)) styleStats.thenCatch++;
754
+ if (content.match(/\bawait\b/)) styleStats.asyncAwait++;
755
+ if (content.match(/\brequire\s*\(/)) styleStats.commonJs++;
756
+ if (content.match(/\bimport\b.*\bfrom\b/)) styleStats.esm++;
757
+ } catch (err) {
758
+ void err;
759
+ }
760
+ }
761
+ if (styleStats.tryCatch > 0 && styleStats.thenCatch > 0) {
762
+ const dominant = styleStats.tryCatch >= styleStats.thenCatch ? "try-catch" : ".catch()";
763
+ const minority = dominant === "try-catch" ? ".catch()" : "try-catch";
764
+ issues.push({
765
+ files: filePaths.filter((f) => {
766
+ const c = contents.get(f) || "";
767
+ return minority === "try-catch" ? c.match(tryCatchPattern) : c.match(/\.catch\s*\(/);
768
+ }),
769
+ type: "pattern-inconsistency",
770
+ description: `Mixed error handling styles: codebase primarily uses ${dominant}, but found ${minority} in some files.`,
771
+ examples: [dominant, minority],
772
+ severity: Severity3.Minor
773
+ });
774
+ }
775
+ if (styleStats.commonJs > 0 && styleStats.esm > 0) {
776
+ const minority = styleStats.esm >= styleStats.commonJs ? "CommonJS (require)" : "ESM (import)";
777
+ issues.push({
778
+ files: filePaths.filter((f) => {
779
+ const c = contents.get(f) || "";
780
+ return minority === "CommonJS (require)" ? c.match(/\brequire\s*\(/) : c.match(/\bimport\b/);
781
+ }),
782
+ type: "pattern-inconsistency",
783
+ description: `Mixed module systems: found both ESM and CommonJS.`,
784
+ examples: ['import X from "y"', 'const X = require("y")'],
785
+ severity: Severity3.Major
786
+ });
787
+ }
788
+ return issues;
789
+ }
790
+
791
+ // src/scoring.ts
792
+ import { calculateProductivityImpact, ToolName } from "@aiready/core";
793
+ function calculateConsistencyScore(issues, totalFilesAnalyzed, costConfig) {
794
+ void costConfig;
795
+ if (!Array.isArray(issues)) {
796
+ return {
797
+ toolName: ToolName.NamingConsistency,
798
+ score: 100,
799
+ rawMetrics: {
800
+ totalIssues: 0,
801
+ criticalIssues: 0,
802
+ majorIssues: 0,
803
+ minorIssues: 0,
804
+ issuesPerFile: 0,
805
+ avgWeightedIssuesPerFile: 0,
806
+ estimatedDeveloperHours: 0
807
+ },
808
+ factors: [],
809
+ recommendations: []
810
+ };
811
+ }
812
+ const criticalIssues = issues.filter((i) => i.severity === "critical").length;
813
+ const majorIssues = issues.filter((i) => i.severity === "major").length;
814
+ const minorIssues = issues.filter((i) => i.severity === "minor").length;
815
+ const totalIssues = issues.length;
816
+ const issuesPerFile = totalFilesAnalyzed > 0 ? totalIssues / totalFilesAnalyzed : 0;
817
+ const densityPenalty = Math.min(50, issuesPerFile * 15);
818
+ const weightedCount = criticalIssues * 10 + majorIssues * 3 + minorIssues * 0.5;
819
+ const avgWeightedIssuesPerFile = totalFilesAnalyzed > 0 ? weightedCount / totalFilesAnalyzed : 0;
820
+ const severityPenalty = Math.min(50, avgWeightedIssuesPerFile * 2);
821
+ const rawScore = 100 - densityPenalty - severityPenalty;
822
+ const score = Math.max(0, Math.min(100, Math.round(rawScore)));
823
+ const factors = [
824
+ {
825
+ name: "Issue Density",
826
+ impact: -Math.round(densityPenalty),
827
+ description: `${issuesPerFile.toFixed(2)} issues per file ${issuesPerFile < 1 ? "(excellent)" : issuesPerFile < 3 ? "(acceptable)" : "(high)"}`
828
+ }
829
+ ];
830
+ if (criticalIssues > 0) {
831
+ const criticalImpact = Math.min(30, criticalIssues * 10);
832
+ factors.push({
833
+ name: "Critical Issues",
834
+ impact: -criticalImpact,
835
+ description: `${criticalIssues} critical consistency issue${criticalIssues > 1 ? "s" : ""} (high AI confusion risk)`
836
+ });
837
+ }
838
+ if (majorIssues > 0) {
839
+ const majorImpact = Math.min(20, Math.round(majorIssues * 3));
840
+ factors.push({
841
+ name: "Major Issues",
842
+ impact: -majorImpact,
843
+ description: `${majorIssues} major issue${majorIssues > 1 ? "s" : ""} (moderate AI confusion risk)`
844
+ });
845
+ }
846
+ if (minorIssues > 0 && minorIssues >= totalFilesAnalyzed) {
847
+ const minorImpact = -Math.round(minorIssues * 0.5);
848
+ factors.push({
849
+ name: "Minor Issues",
850
+ impact: minorImpact,
851
+ description: `${minorIssues} minor issue${minorIssues > 1 ? "s" : ""} (slight AI confusion risk)`
852
+ });
853
+ }
854
+ const recommendations = [];
855
+ if (criticalIssues > 0) {
856
+ const estimatedImpact = Math.min(30, criticalIssues * 10);
857
+ recommendations.push({
858
+ action: "Fix critical naming/pattern inconsistencies (highest AI confusion risk)",
859
+ estimatedImpact,
860
+ priority: "high"
861
+ });
862
+ }
863
+ if (majorIssues > 5) {
864
+ const estimatedImpact = Math.min(15, Math.round(majorIssues / 2));
865
+ recommendations.push({
866
+ action: "Standardize naming conventions across codebase",
867
+ estimatedImpact,
868
+ priority: "medium"
869
+ });
870
+ }
871
+ if (issuesPerFile > 3) {
872
+ recommendations.push({
873
+ action: "Establish and enforce coding style guide to reduce inconsistencies",
874
+ estimatedImpact: 12,
875
+ priority: "medium"
876
+ });
877
+ }
878
+ if (totalIssues > 20 && minorIssues / totalIssues > 0.7) {
879
+ recommendations.push({
880
+ action: "Enable linter/formatter to automatically fix minor style issues",
881
+ estimatedImpact: 8,
882
+ priority: "low"
883
+ });
884
+ }
885
+ const productivityImpact = calculateProductivityImpact(issues);
886
+ return {
887
+ toolName: ToolName.NamingConsistency,
888
+ score,
889
+ rawMetrics: {
890
+ totalIssues,
891
+ criticalIssues,
892
+ majorIssues,
893
+ minorIssues,
894
+ issuesPerFile: Math.round(issuesPerFile * 100) / 100,
895
+ avgWeightedIssuesPerFile: Math.round(avgWeightedIssuesPerFile * 100) / 100,
896
+ // Business value metrics
897
+ estimatedDeveloperHours: productivityImpact.totalHours
898
+ },
899
+ factors,
900
+ recommendations
901
+ };
902
+ }
903
+
904
+ // src/analyzer.ts
905
+ import {
906
+ scanFiles,
907
+ Severity as Severity5,
908
+ IssueType,
909
+ getSeverityLevel
910
+ } from "@aiready/core";
911
+
912
+ // src/analyzers/naming-generalized.ts
913
+ import { getParser, Severity as Severity4 } from "@aiready/core";
914
+ import { readFileSync as readFileSync3 } from "fs";
915
+ var COMMON_ABBREVIATIONS = /* @__PURE__ */ new Set([
916
+ "id",
917
+ "db",
918
+ "fs",
919
+ "os",
920
+ "ip",
921
+ "ui",
922
+ "ux",
923
+ "api",
924
+ "env",
925
+ "url",
926
+ "req",
927
+ "res",
928
+ "err",
929
+ "ctx",
930
+ "cb",
931
+ "idx",
932
+ "src",
933
+ "dir",
934
+ "app",
935
+ "dev",
936
+ "qa",
937
+ "dto",
938
+ "dao",
939
+ "ref",
940
+ "ast",
941
+ "dom",
942
+ "log",
943
+ "msg",
944
+ "pkg",
945
+ "css",
946
+ "html",
947
+ "xml",
948
+ "jsx",
949
+ "tsx",
950
+ "ts",
951
+ "js"
952
+ ]);
953
+ var FONT_FAMILIES = /* @__PURE__ */ new Set([
954
+ "Geist_Mono",
955
+ "Geist_Sans",
956
+ "JetBrains_Mono",
957
+ "Fira_Code",
958
+ "Source_Code_Pro",
959
+ "IBM_Plex_Mono",
960
+ "Space_Mono",
961
+ "Roboto_Mono",
962
+ "Ubuntu_Mono",
963
+ "Inconsolata",
964
+ "Cousine",
965
+ "Anonymous_Pro",
966
+ "Arimo",
967
+ "Tinos",
968
+ "Cabin",
969
+ "Cardo",
970
+ "Gentium",
971
+ "Libre_Baskerville",
972
+ "Lora",
973
+ "Merriweather",
974
+ "Noto_Sans",
975
+ "Noto_Serif",
976
+ "Open_Sans",
977
+ "Playfair_Display",
978
+ "PT_Sans",
979
+ "PT_Serif",
980
+ "Raleway",
981
+ "Roboto",
982
+ "Slabo",
983
+ "Source_Sans_Pro",
984
+ "Source_Serif_Pro",
985
+ "Spectral",
986
+ "Titillium_Web",
987
+ "Ubuntu"
988
+ ]);
989
+ async function analyzeNamingGeneralized(files) {
990
+ const issues = [];
991
+ for (const file of files) {
992
+ const parser = await getParser(file);
993
+ if (!parser) continue;
994
+ try {
995
+ const code = readFileSync3(file, "utf-8");
996
+ if (!code.trim()) continue;
997
+ await parser.initialize();
998
+ const result = parser.parse(code, file);
999
+ const conventions = parser.getNamingConventions();
1000
+ const exceptions = new Set(conventions.exceptions || []);
1001
+ for (const exp of result.exports) {
1002
+ if (!exp.name || exp.name === "default") continue;
1003
+ if (exceptions.has(exp.name)) continue;
1004
+ if (COMMON_ABBREVIATIONS.has(exp.name.toLowerCase())) continue;
1005
+ let pattern;
1006
+ if (exp.type === "class") {
1007
+ pattern = conventions.classPattern;
1008
+ } else if (exp.type === "interface" && conventions.interfacePattern) {
1009
+ pattern = conventions.interfacePattern;
1010
+ } else if (exp.type === "type" && conventions.typePattern) {
1011
+ pattern = conventions.typePattern;
1012
+ } else if (exp.type === "function") {
1013
+ if (/^[A-Z][a-zA-Z0-9]*$/.test(exp.name) || /^[A-Z][A-Z0-9_]*$/.test(exp.name)) {
1014
+ continue;
1015
+ }
1016
+ pattern = conventions.functionPattern;
1017
+ } else if (exp.type === "const") {
1018
+ if ([
1019
+ "handler",
1020
+ "GET",
1021
+ "POST",
1022
+ "PUT",
1023
+ "DELETE",
1024
+ "PATCH",
1025
+ "OPTIONS",
1026
+ "HEAD"
1027
+ ].includes(exp.name))
1028
+ continue;
1029
+ if (conventions.constantPattern.test(exp.name) || conventions.variablePattern.test(exp.name) || /^[A-Z][a-zA-Z0-9]*$/.test(exp.name)) {
1030
+ continue;
1031
+ }
1032
+ pattern = conventions.constantPattern;
1033
+ } else {
1034
+ pattern = conventions.variablePattern;
1035
+ }
1036
+ if (pattern && !pattern.test(exp.name)) {
1037
+ issues.push({
1038
+ type: "naming-inconsistency",
1039
+ identifier: exp.name,
1040
+ file,
1041
+ line: exp.loc?.start.line || 1,
1042
+ column: exp.loc?.start.column || 0,
1043
+ // Recalibrate naming issues to Minor to differentiate from structural/architectural issues
1044
+ severity: Severity4.Minor,
1045
+ category: "naming",
1046
+ suggestion: `Follow ${parser.language} ${exp.type} naming convention: ${pattern.toString()}`
1047
+ });
1048
+ }
1049
+ }
1050
+ for (const imp of result.imports) {
1051
+ for (const spec of imp.specifiers) {
1052
+ if (!spec || spec === "*" || spec === "default") continue;
1053
+ if (exceptions.has(spec)) continue;
1054
+ if (COMMON_ABBREVIATIONS.has(spec.toLowerCase())) continue;
1055
+ if (FONT_FAMILIES.has(spec)) continue;
1056
+ if (spec.includes(".")) continue;
1057
+ if (!conventions.variablePattern.test(spec) && !conventions.classPattern.test(spec) && (!conventions.constantPattern || !conventions.constantPattern.test(spec)) && (!conventions.typePattern || !conventions.typePattern.test(spec)) && (!conventions.interfacePattern || !conventions.interfacePattern.test(spec)) && !/^[A-Z][A-Z0-9_]*$/.test(spec)) {
1058
+ issues.push({
1059
+ type: "naming-inconsistency",
1060
+ identifier: spec,
1061
+ file,
1062
+ line: imp.loc?.start.line || 1,
1063
+ column: imp.loc?.start.column || 0,
1064
+ severity: Severity4.Info,
1065
+ // Reduced from Minor to Info for imports
1066
+ category: "naming",
1067
+ suggestion: `Imported identifier '${spec}' may not follow standard conventions for this language.`
1068
+ });
1069
+ }
1070
+ }
1071
+ }
1072
+ } catch (error) {
1073
+ const errorMessage = error instanceof Error ? error.message : String(error);
1074
+ console.debug(
1075
+ `Consistency: Skipping unparseable file ${file}: ${errorMessage.split("\\n")[0]}`
1076
+ );
1077
+ }
1078
+ }
1079
+ return issues;
1080
+ }
1081
+
1082
+ // src/analyzer.ts
1083
+ async function analyzeConsistency(options) {
1084
+ const {
1085
+ checkNaming = true,
1086
+ checkPatterns = true,
1087
+ checkArchitecture = false,
1088
+ // Not implemented yet
1089
+ minSeverity = Severity5.Info,
1090
+ ...scanOptions
1091
+ } = options;
1092
+ void checkArchitecture;
1093
+ const filePaths = await scanFiles(scanOptions);
1094
+ let namingIssues = [];
1095
+ if (checkNaming) {
1096
+ namingIssues = await analyzeNamingGeneralized(filePaths);
1097
+ const tsJsFiles = filePaths.filter((f) => /\.(ts|tsx|js|jsx)$/i.test(f));
1098
+ if (tsJsFiles.length > 0) {
1099
+ const deepTsIssues = await analyzeNamingAST(tsJsFiles);
1100
+ namingIssues = [...namingIssues, ...deepTsIssues];
1101
+ }
1102
+ }
1103
+ const patternIssues = checkPatterns ? await analyzePatterns(filePaths) : [];
1104
+ const results = [];
1105
+ const fileIssuesMap = /* @__PURE__ */ new Map();
1106
+ for (const issue of namingIssues) {
1107
+ if (!shouldIncludeSeverity(issue.severity, minSeverity)) continue;
1108
+ const fileName = issue.fileName || issue.file || issue.filePath || "unknown";
1109
+ if (!fileIssuesMap.has(fileName)) fileIssuesMap.set(fileName, []);
1110
+ fileIssuesMap.get(fileName).push(issue);
1111
+ }
1112
+ for (const issue of patternIssues) {
1113
+ if (!shouldIncludeSeverity(issue.severity, minSeverity)) continue;
1114
+ const anyIssue = issue;
1115
+ const fileName = anyIssue.fileName || anyIssue.file || anyIssue.filePath || (Array.isArray(issue.files) ? issue.files[0] : "unknown");
1116
+ if (!fileIssuesMap.has(fileName)) fileIssuesMap.set(fileName, []);
1117
+ fileIssuesMap.get(fileName).push(issue);
1118
+ }
1119
+ for (const [fileName, issues] of fileIssuesMap.entries()) {
1120
+ const scoreResult = calculateConsistencyScore(
1121
+ issues,
1122
+ filePaths.length
1123
+ );
1124
+ results.push({
1125
+ fileName,
1126
+ issues: issues.map((i) => transformToIssue(i)),
1127
+ metrics: {
1128
+ consistencyScore: scoreResult.score / 100
1129
+ }
1130
+ });
1131
+ }
1132
+ const recommendations = [];
1133
+ if (namingIssues.length > 0) {
1134
+ recommendations.push("Standardize naming conventions across the codebase");
1135
+ }
1136
+ if (patternIssues.length > 0) {
1137
+ recommendations.push("Consolidate repetitive implementation patterns");
1138
+ }
1139
+ if (results.some((r) => (r.metrics?.consistencyScore ?? 1) < 0.8)) {
1140
+ recommendations.push(
1141
+ "Improve cross-module consistency to reduce AI confusion"
1142
+ );
1143
+ }
1144
+ return {
1145
+ results,
1146
+ summary: {
1147
+ filesAnalyzed: filePaths.length,
1148
+ totalIssues: results.reduce((acc, r) => acc + r.issues.length, 0),
1149
+ namingIssues: namingIssues.length,
1150
+ patternIssues: patternIssues.length,
1151
+ architectureIssues: 0
1152
+ },
1153
+ recommendations,
1154
+ metadata: {
1155
+ toolName: "naming-consistency",
1156
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1157
+ }
1158
+ };
1159
+ }
1160
+ function shouldIncludeSeverity(severity, minSeverity) {
1161
+ return getSeverityLevel(severity) >= getSeverityLevel(minSeverity);
1162
+ }
1163
+ function getIssueType(type) {
1164
+ if (!type) return IssueType.NamingInconsistency;
1165
+ const typeMap = {
1166
+ "naming-inconsistency": IssueType.NamingInconsistency,
1167
+ "naming-quality": IssueType.NamingQuality,
1168
+ "pattern-inconsistency": IssueType.PatternInconsistency,
1169
+ "architecture-inconsistency": IssueType.ArchitectureInconsistency,
1170
+ "error-handling": IssueType.PatternInconsistency,
1171
+ "async-style": IssueType.PatternInconsistency,
1172
+ "import-style": IssueType.PatternInconsistency,
1173
+ "api-design": IssueType.PatternInconsistency
1174
+ };
1175
+ return typeMap[type] || IssueType.NamingInconsistency;
1176
+ }
1177
+ function transformToIssue(i) {
1178
+ if (i.message && i.location) {
1179
+ return {
1180
+ type: getIssueType(i.type),
1181
+ severity: i.severity,
1182
+ message: i.message,
1183
+ location: i.location,
1184
+ suggestion: i.suggestion
1185
+ };
1186
+ }
1187
+ if (i.identifier || i.type) {
1188
+ const line = i.line || 1;
1189
+ const column = i.column || 1;
1190
+ return {
1191
+ type: getIssueType(i.type),
1192
+ severity: i.severity,
1193
+ message: i.suggestion ? `Naming issue: ${i.suggestion}` : `Naming issue for '${i.identifier || "unknown"}'`,
1194
+ location: {
1195
+ file: i.file || i.fileName || "",
1196
+ line,
1197
+ column,
1198
+ endLine: line,
1199
+ endColumn: column + (i.identifier?.length || 10)
1200
+ },
1201
+ suggestion: i.suggestion
1202
+ };
1203
+ }
1204
+ if (i.description || i.files) {
1205
+ const fileName = Array.isArray(i.files) ? i.files[0] : i.file || "";
1206
+ return {
1207
+ type: getIssueType(i.type),
1208
+ severity: i.severity,
1209
+ message: i.description || "Pattern inconsistency found",
1210
+ location: {
1211
+ file: fileName,
1212
+ line: 1,
1213
+ column: 1,
1214
+ endLine: 1,
1215
+ endColumn: 10
1216
+ },
1217
+ suggestion: i.examples?.[0]
1218
+ };
1219
+ }
1220
+ return {
1221
+ type: getIssueType(i.type),
1222
+ severity: i.severity,
1223
+ message: i.message || "Unknown issue",
1224
+ location: i.location || { file: "", line: 1, column: 1 },
1225
+ suggestion: i.suggestion
1226
+ };
1227
+ }
1228
+
1229
+ export {
1230
+ VAGUE_NAMES,
1231
+ ACCEPTABLE_ABBREVIATIONS,
1232
+ detectNamingConventions,
1233
+ analyzeNamingAST,
1234
+ analyzePatterns,
1235
+ calculateConsistencyScore,
1236
+ analyzeConsistency
1237
+ };