@aiready/consistency 0.16.2 → 0.16.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -40,7 +40,10 @@ __export(index_exports, {
40
40
  module.exports = __toCommonJS(index_exports);
41
41
 
42
42
  // src/analyzer.ts
43
- var import_core4 = require("@aiready/core");
43
+ var import_core5 = require("@aiready/core");
44
+
45
+ // src/analyzers/naming-ast.ts
46
+ var import_core2 = require("@aiready/core");
44
47
 
45
48
  // src/utils/ast-parser.ts
46
49
  var import_typescript_estree = require("@typescript-eslint/typescript-estree");
@@ -92,214 +95,12 @@ function isLoopStatement(node) {
92
95
  "DoWhileStatement"
93
96
  ].includes(node.type);
94
97
  }
95
- function getFunctionName(node) {
96
- switch (node.type) {
97
- case "FunctionDeclaration":
98
- return node.id?.name ?? null;
99
- case "FunctionExpression":
100
- return node.id?.name ?? null;
101
- case "ArrowFunctionExpression":
102
- return null;
103
- // Arrow functions don't have names directly
104
- case "MethodDefinition":
105
- if (node.key.type === "Identifier") {
106
- return node.key.name;
107
- }
108
- return null;
109
- default:
110
- return null;
111
- }
112
- }
113
98
  function getLineNumber(node) {
114
99
  return node.loc?.start.line ?? 0;
115
100
  }
116
- function isCoverageContext(node, ancestors) {
117
- const coveragePatterns = /coverage|summary|metrics|pct|percent|statements|branches|functions|lines/i;
118
- if (node.type === "Identifier" && coveragePatterns.test(node.name)) {
119
- return true;
120
- }
121
- for (const ancestor of ancestors.slice(-3)) {
122
- if (ancestor.type === "MemberExpression") {
123
- const memberExpr = ancestor;
124
- if (memberExpr.object.type === "Identifier" && coveragePatterns.test(memberExpr.object.name)) {
125
- return true;
126
- }
127
- }
128
- if (ancestor.type === "ObjectPattern" || ancestor.type === "ObjectExpression") {
129
- const parent = ancestors[ancestors.indexOf(ancestor) - 1];
130
- if (parent?.type === "VariableDeclarator") {
131
- const varDecl = parent;
132
- if (varDecl.id.type === "Identifier" && coveragePatterns.test(varDecl.id.name)) {
133
- return true;
134
- }
135
- }
136
- }
137
- }
138
- return false;
139
- }
140
-
141
- // src/utils/scope-tracker.ts
142
- var ScopeTracker = class {
143
- constructor(rootNode) {
144
- this.allScopes = [];
145
- this.rootScope = {
146
- type: "global",
147
- node: rootNode,
148
- parent: null,
149
- children: [],
150
- variables: /* @__PURE__ */ new Map()
151
- };
152
- this.currentScope = this.rootScope;
153
- this.allScopes.push(this.rootScope);
154
- }
155
- /**
156
- * Enter a new scope
157
- */
158
- enterScope(type, node) {
159
- const newScope = {
160
- type,
161
- node,
162
- parent: this.currentScope,
163
- children: [],
164
- variables: /* @__PURE__ */ new Map()
165
- };
166
- this.currentScope.children.push(newScope);
167
- this.currentScope = newScope;
168
- this.allScopes.push(newScope);
169
- }
170
- /**
171
- * Exit current scope and return to parent
172
- */
173
- exitScope() {
174
- if (this.currentScope.parent) {
175
- this.currentScope = this.currentScope.parent;
176
- }
177
- }
178
- /**
179
- * Declare a variable in the current scope
180
- */
181
- declareVariable(name, node, line, options = {}) {
182
- const varInfo = {
183
- name,
184
- node,
185
- declarationLine: line,
186
- references: [],
187
- type: options.type,
188
- isParameter: options.isParameter ?? false,
189
- isDestructured: options.isDestructured ?? false,
190
- isLoopVariable: options.isLoopVariable ?? false
191
- };
192
- this.currentScope.variables.set(name, varInfo);
193
- }
194
- /**
195
- * Add a reference to a variable
196
- */
197
- addReference(name, node) {
198
- const varInfo = this.findVariable(name);
199
- if (varInfo) {
200
- varInfo.references.push(node);
201
- }
202
- }
203
- /**
204
- * Find a variable in current or parent scopes
205
- */
206
- findVariable(name) {
207
- let scope = this.currentScope;
208
- while (scope) {
209
- const varInfo = scope.variables.get(name);
210
- if (varInfo) {
211
- return varInfo;
212
- }
213
- scope = scope.parent;
214
- }
215
- return null;
216
- }
217
- /**
218
- * Get all variables in current scope (not including parent scopes)
219
- */
220
- getCurrentScopeVariables() {
221
- return Array.from(this.currentScope.variables.values());
222
- }
223
- /**
224
- * Get all variables across all scopes
225
- */
226
- getAllVariables() {
227
- const allVars = [];
228
- for (const scope of this.allScopes) {
229
- allVars.push(...Array.from(scope.variables.values()));
230
- }
231
- return allVars;
232
- }
233
- /**
234
- * Calculate actual usage count (references minus declaration)
235
- */
236
- getUsageCount(varInfo) {
237
- return varInfo.references.length;
238
- }
239
- /**
240
- * Check if a variable is short-lived (used within N lines)
241
- */
242
- isShortLived(varInfo, maxLines = 5) {
243
- if (varInfo.references.length === 0) {
244
- return false;
245
- }
246
- const declarationLine = varInfo.declarationLine;
247
- const maxUsageLine = Math.max(
248
- ...varInfo.references.map((ref) => ref.loc?.start.line ?? declarationLine)
249
- );
250
- return maxUsageLine - declarationLine <= maxLines;
251
- }
252
- /**
253
- * Check if a variable is used in a limited scope (e.g., only in one callback)
254
- */
255
- isLocallyScoped(varInfo) {
256
- if (varInfo.references.length === 0) return false;
257
- const lines = varInfo.references.map((ref) => ref.loc?.start.line ?? 0);
258
- const minLine = Math.min(...lines);
259
- const maxLine = Math.max(...lines);
260
- return maxLine - minLine <= 3;
261
- }
262
- /**
263
- * Get current scope type
264
- */
265
- getCurrentScopeType() {
266
- return this.currentScope.type;
267
- }
268
- /**
269
- * Check if currently in a loop scope
270
- */
271
- isInLoop() {
272
- let scope = this.currentScope;
273
- while (scope) {
274
- if (scope.type === "loop") {
275
- return true;
276
- }
277
- scope = scope.parent;
278
- }
279
- return false;
280
- }
281
- /**
282
- * Check if currently in a function scope
283
- */
284
- isInFunction() {
285
- let scope = this.currentScope;
286
- while (scope) {
287
- if (scope.type === "function") {
288
- return true;
289
- }
290
- scope = scope.parent;
291
- }
292
- return false;
293
- }
294
- /**
295
- * Get the root scope
296
- */
297
- getRootScope() {
298
- return this.rootScope;
299
- }
300
- };
301
101
 
302
102
  // src/utils/context-detector.ts
103
+ var import_core = require("@aiready/core");
303
104
  function detectFileType(filePath, ast) {
304
105
  void ast;
305
106
  const path = filePath.toLowerCase();
@@ -427,24 +228,34 @@ function buildCodeContext(filePath, ast) {
427
228
  };
428
229
  }
429
230
  function adjustSeverity(baseSeverity, context, issueType) {
231
+ const getEnum = (s) => {
232
+ if (s === import_core.Severity.Critical || s === "critical") return import_core.Severity.Critical;
233
+ if (s === import_core.Severity.Major || s === "major") return import_core.Severity.Major;
234
+ if (s === import_core.Severity.Minor || s === "minor") return import_core.Severity.Minor;
235
+ return import_core.Severity.Info;
236
+ };
237
+ let currentSev = getEnum(baseSeverity);
430
238
  if (context.isTestFile) {
431
- if (baseSeverity === "minor") return "info";
432
- if (baseSeverity === "major") return "minor";
239
+ if (currentSev === import_core.Severity.Minor) currentSev = import_core.Severity.Info;
240
+ if (currentSev === import_core.Severity.Major) currentSev = import_core.Severity.Minor;
433
241
  }
434
242
  if (context.isTypeDefinition) {
435
- if (baseSeverity === "minor") return "info";
243
+ if (currentSev === import_core.Severity.Minor) currentSev = import_core.Severity.Info;
436
244
  }
437
245
  if (context.codeLayer === "api") {
438
- if (baseSeverity === "info" && issueType === "unclear") return "minor";
439
- if (baseSeverity === "minor" && issueType === "unclear") return "major";
246
+ if (currentSev === import_core.Severity.Info && issueType === "unclear")
247
+ currentSev = import_core.Severity.Minor;
248
+ if (currentSev === import_core.Severity.Minor && issueType === "unclear")
249
+ currentSev = import_core.Severity.Major;
440
250
  }
441
251
  if (context.complexity > 10) {
442
- if (baseSeverity === "info") return "minor";
252
+ if (currentSev === import_core.Severity.Info) currentSev = import_core.Severity.Minor;
443
253
  }
444
254
  if (context.codeLayer === "utility") {
445
- if (baseSeverity === "minor" && issueType === "abbreviation") return "info";
255
+ if (currentSev === import_core.Severity.Minor && issueType === "abbreviation")
256
+ currentSev = import_core.Severity.Info;
446
257
  }
447
- return baseSeverity;
258
+ return currentSev;
448
259
  }
449
260
  function isAcceptableInContext(name, context, options) {
450
261
  if (options.isLoopVariable && ["i", "j", "k", "l", "n", "m"].includes(name)) {
@@ -471,651 +282,168 @@ function isAcceptableInContext(name, context, options) {
471
282
  return false;
472
283
  }
473
284
 
474
- // src/utils/config-loader.ts
475
- var import_core = require("@aiready/core");
476
- var import_path = require("path");
477
-
478
- // src/analyzers/naming-constants.ts
479
- var COMMON_SHORT_WORDS = /* @__PURE__ */ new Set([
480
- // Full English words (1-3 letters)
481
- "day",
482
- "key",
483
- "net",
484
- "to",
485
- "go",
486
- "for",
487
- "not",
488
- "new",
489
- "old",
490
- "top",
491
- "end",
492
- "run",
493
- "try",
494
- "use",
495
- "get",
496
- "set",
497
- "add",
498
- "put",
499
- "map",
500
- "log",
501
- "row",
502
- "col",
503
- "tab",
504
- "box",
505
- "div",
506
- "nav",
507
- "tag",
508
- "any",
509
- "all",
510
- "one",
511
- "two",
512
- "out",
513
- "off",
514
- "on",
515
- "yes",
516
- "no",
517
- "now",
518
- "max",
519
- "min",
520
- "sum",
521
- "avg",
522
- "ref",
523
- "src",
524
- "dst",
525
- "raw",
526
- "def",
527
- "sub",
528
- "pub",
529
- "pre",
530
- "mid",
531
- "alt",
532
- "opt",
533
- "tmp",
534
- "ext",
535
- "sep",
536
- // Prepositions and conjunctions
537
- "and",
538
- "from",
539
- "how",
540
- "pad",
541
- "bar",
542
- "non",
543
- // Additional full words commonly flagged
544
- "tax",
545
- "cat",
546
- "dog",
547
- "car",
548
- "bus",
549
- "web",
550
- "app",
551
- "war",
552
- "law",
553
- "pay",
554
- "buy",
555
- "win",
556
- "cut",
557
- "hit",
558
- "hot",
559
- "pop",
560
- "job",
561
- "age",
562
- "act",
563
- "let",
564
- "lot",
565
- "bad",
566
- "big",
567
- "far",
568
- "few",
569
- "own",
570
- "per",
571
- "red",
572
- "low",
573
- "see",
574
- "six",
575
- "ten",
576
- "way",
577
- "who",
578
- "why",
579
- "yet",
580
- "via",
581
- "due",
582
- "fee",
583
- "fun",
584
- "gas",
585
- "gay",
586
- "god",
587
- "gun",
588
- "guy",
589
- "ice",
590
- "ill",
591
- "kid",
592
- "mad",
593
- "man",
594
- "mix",
595
- "mom",
596
- "mrs",
597
- "nor",
598
- "odd",
599
- "oil",
600
- "pan",
601
- "pet",
602
- "pit",
603
- "pot",
604
- "pow",
605
- "pro",
606
- "raw",
607
- "rep",
608
- "rid",
609
- "sad",
610
- "sea",
611
- "sit",
612
- "sky",
613
- "son",
614
- "tea",
615
- "tie",
616
- "tip",
617
- "van",
618
- "war",
619
- "win",
620
- "won"
621
- ]);
622
- var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
623
- // Standard identifiers
624
- "id",
625
- "uid",
626
- "gid",
627
- "pid",
628
- // Loop counters and iterators
629
- "i",
630
- "j",
631
- "k",
632
- "n",
633
- "m",
634
- // Web/Network
635
- "url",
636
- "uri",
637
- "api",
638
- "cdn",
639
- "dns",
640
- "ip",
641
- "tcp",
642
- "udp",
643
- "http",
644
- "ssl",
645
- "tls",
646
- "utm",
647
- "seo",
648
- "rss",
649
- "xhr",
650
- "ajax",
651
- "cors",
652
- "ws",
653
- "wss",
654
- // Data formats
655
- "json",
656
- "xml",
657
- "yaml",
658
- "csv",
659
- "html",
660
- "css",
661
- "svg",
662
- "pdf",
663
- // File types & extensions
664
- "img",
665
- "txt",
666
- "doc",
667
- "docx",
668
- "xlsx",
669
- "ppt",
670
- "md",
671
- "rst",
672
- "jpg",
673
- "png",
674
- "gif",
675
- // Databases
676
- "db",
677
- "sql",
678
- "orm",
679
- "dao",
680
- "dto",
681
- "ddb",
682
- "rds",
683
- "nosql",
684
- // File system
685
- "fs",
686
- "dir",
687
- "tmp",
688
- "src",
689
- "dst",
690
- "bin",
691
- "lib",
692
- "pkg",
693
- // Operating system
694
- "os",
695
- "env",
696
- "arg",
697
- "cli",
698
- "cmd",
699
- "exe",
700
- "cwd",
701
- "pwd",
702
- // UI/UX
703
- "ui",
704
- "ux",
705
- "gui",
706
- "dom",
707
- "ref",
708
- // Request/Response
709
- "req",
710
- "res",
711
- "ctx",
712
- "err",
713
- "msg",
714
- "auth",
715
- // Mathematics/Computing
716
- "max",
717
- "min",
718
- "avg",
719
- "sum",
720
- "abs",
721
- "cos",
722
- "sin",
723
- "tan",
724
- "log",
725
- "exp",
726
- "pow",
727
- "sqrt",
728
- "std",
729
- "var",
730
- "int",
731
- "num",
732
- "idx",
733
- // Time
734
- "now",
735
- "utc",
736
- "tz",
737
- "ms",
738
- "sec",
739
- "hr",
740
- "min",
741
- "yr",
742
- "mo",
743
- // Common patterns
744
- "app",
745
- "cfg",
746
- "config",
747
- "init",
748
- "len",
749
- "val",
750
- "str",
751
- "obj",
752
- "arr",
753
- "gen",
754
- "def",
755
- "raw",
756
- "new",
757
- "old",
758
- "pre",
759
- "post",
760
- "sub",
761
- "pub",
762
- // Programming/Framework specific
763
- "ts",
764
- "js",
765
- "jsx",
766
- "tsx",
767
- "py",
768
- "rb",
769
- "vue",
770
- "re",
771
- "fn",
772
- "fns",
773
- "mod",
774
- "opts",
775
- "dev",
776
- // Cloud/Infrastructure
777
- "s3",
778
- "ec2",
779
- "sqs",
780
- "sns",
781
- "vpc",
782
- "ami",
783
- "iam",
784
- "acl",
785
- "elb",
786
- "alb",
787
- "nlb",
788
- "aws",
789
- "ses",
790
- "gst",
791
- "cdk",
792
- "btn",
793
- "buf",
794
- "agg",
795
- "ocr",
796
- "ai",
797
- "cf",
798
- "cfn",
799
- "ga",
800
- // Metrics/Performance
801
- "fcp",
802
- "lcp",
803
- "cls",
804
- "ttfb",
805
- "tti",
806
- "fid",
807
- "fps",
808
- "qps",
809
- "rps",
810
- "tps",
811
- "wpm",
812
- // Testing & i18n
813
- "po",
814
- "e2e",
815
- "a11y",
816
- "i18n",
817
- "l10n",
818
- "spy",
819
- // Domain-specific abbreviations (context-aware)
820
- "sk",
821
- "fy",
822
- "faq",
823
- "og",
824
- "seo",
825
- "cta",
826
- "roi",
827
- "kpi",
828
- "ttl",
829
- "pct",
830
- // Technical abbreviations
831
- "mac",
832
- "hex",
833
- "esm",
834
- "git",
835
- "rec",
836
- "loc",
837
- "dup",
838
- // Boolean helpers (these are intentional short names)
839
- "is",
840
- "has",
841
- "can",
842
- "did",
843
- "was",
844
- "are",
845
- // Date/Time context (when in date contexts)
846
- "d",
847
- "t",
848
- "dt",
849
- // Coverage metrics (industry standard: statements/branches/functions/lines)
850
- "s",
851
- "b",
852
- "f",
853
- "l",
854
- // Common media/content abbreviations
855
- "vid",
856
- "pic",
857
- "img",
858
- "doc",
859
- "msg"
860
- ]);
861
- function snakeCaseToCamelCase(str) {
862
- return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
863
- }
864
- function detectNamingConventions(files, allIssues) {
865
- const camelCaseCount = allIssues.filter(
866
- (i) => i.type === "convention-mix"
867
- ).length;
868
- const totalChecks = files.length * 10;
869
- if (camelCaseCount / totalChecks > 0.3) {
870
- return { dominantConvention: "mixed", conventionScore: 0.5 };
871
- }
872
- return { dominantConvention: "camelCase", conventionScore: 0.9 };
873
- }
874
-
875
- // src/utils/config-loader.ts
876
- async function loadNamingConfig(files) {
877
- const rootDir = files.length > 0 ? (0, import_path.dirname)(files[0]) : process.cwd();
878
- const config = await (0, import_core.loadConfig)(rootDir);
879
- const consistencyConfig = config?.tools?.["consistency"];
880
- const customAbbreviations = new Set(
881
- consistencyConfig?.acceptedAbbreviations || []
882
- );
883
- const customShortWords = new Set(consistencyConfig?.shortWords || []);
884
- const disabledChecks = new Set(consistencyConfig?.disableChecks || []);
885
- const allAbbreviations = /* @__PURE__ */ new Set([
886
- ...ACCEPTABLE_ABBREVIATIONS,
887
- ...customAbbreviations
888
- ]);
889
- const allShortWords = /* @__PURE__ */ new Set([...COMMON_SHORT_WORDS, ...customShortWords]);
890
- return {
891
- customAbbreviations,
892
- customShortWords,
893
- disabledChecks,
894
- allAbbreviations,
895
- allShortWords
896
- };
897
- }
898
-
899
285
  // src/analyzers/naming-ast.ts
900
- async function analyzeNamingAST(files) {
901
- const issues = [];
902
- const { allAbbreviations, allShortWords, disabledChecks } = await loadNamingConfig(files);
903
- const supportedFiles = files.filter(
904
- (file) => /\.(js|jsx|ts|tsx)$/i.test(file)
905
- );
906
- for (const file of supportedFiles) {
286
+ async function analyzeNamingAST(filePaths) {
287
+ const allIssues = [];
288
+ for (const filePath of filePaths) {
907
289
  try {
908
- const ast = parseFile(file);
290
+ const ast = parseFile(filePath);
909
291
  if (!ast) continue;
910
- const fileIssues = analyzeFileNamingAST(
911
- file,
912
- ast,
913
- allAbbreviations,
914
- allShortWords,
915
- disabledChecks
916
- );
917
- issues.push(...fileIssues);
918
- } catch (error) {
919
- console.warn(`Skipping ${file} due to parse error:`, error);
292
+ const context = buildCodeContext(filePath, ast);
293
+ const issues = analyzeIdentifiers(ast, filePath, context);
294
+ allIssues.push(...issues);
295
+ } catch (err) {
296
+ void err;
920
297
  }
921
298
  }
922
- return issues;
299
+ return allIssues;
923
300
  }
924
- function analyzeFileNamingAST(file, ast, allAbbreviations, allShortWords, disabledChecks) {
301
+ function analyzeIdentifiers(ast, filePath, context) {
925
302
  const issues = [];
926
- const scopeTracker = new ScopeTracker(ast);
927
- const context = buildCodeContext(file, ast);
928
- const ancestors = [];
303
+ const scopeTracker = new ScopeTracker();
929
304
  traverseAST(ast, {
930
- enter: (node, parent) => {
931
- ancestors.push(node);
932
- if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") {
933
- scopeTracker.enterScope("function", node);
934
- if ("params" in node) {
935
- for (const param of node.params) {
936
- if (param.type === "Identifier") {
937
- scopeTracker.declareVariable(
938
- param.name,
939
- param,
940
- getLineNumber(param),
941
- {
942
- isParameter: true
943
- }
944
- );
945
- } else if (param.type === "ObjectPattern" || param.type === "ArrayPattern") {
946
- extractIdentifiersFromPattern(param, scopeTracker, true);
947
- }
948
- }
949
- }
950
- } else if (node.type === "BlockStatement") {
951
- scopeTracker.enterScope("block", node);
952
- } else if (isLoopStatement(node)) {
953
- scopeTracker.enterScope("loop", node);
954
- } else if (node.type === "ClassDeclaration") {
955
- scopeTracker.enterScope("class", node);
956
- }
957
- if (node.type === "VariableDeclarator") {
958
- if (node.id.type === "Identifier") {
959
- void isCoverageContext(node, ancestors);
960
- scopeTracker.declareVariable(
961
- node.id.name,
962
- node.id,
963
- getLineNumber(node.id),
964
- {
965
- type: "typeAnnotation" in node.id ? node.id.typeAnnotation : null,
966
- isDestructured: false,
967
- isLoopVariable: scopeTracker.getCurrentScopeType() === "loop"
968
- }
969
- );
970
- } else if (node.id.type === "ObjectPattern" || node.id.type === "ArrayPattern") {
971
- extractIdentifiersFromPattern(
972
- node.id,
973
- scopeTracker,
974
- false,
975
- ancestors
976
- );
977
- }
305
+ enter: (node) => {
306
+ if (node.type === "VariableDeclarator" && node.id.type === "Identifier") {
307
+ const isParameter = false;
308
+ const isLoopVariable = isLoopStatement(node.parent?.parent);
309
+ scopeTracker.declareVariable(
310
+ node.id.name,
311
+ node.id,
312
+ getLineNumber(node.id),
313
+ { isParameter, isLoopVariable }
314
+ );
978
315
  }
979
- if (node.type === "Identifier" && parent) {
980
- if (parent.type !== "VariableDeclarator" || parent.id !== node) {
981
- if (parent.type !== "FunctionDeclaration" || parent.id !== node) {
982
- scopeTracker.addReference(node.name, node);
316
+ if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") {
317
+ node.params.forEach((param) => {
318
+ if (param.type === "Identifier") {
319
+ scopeTracker.declareVariable(
320
+ param.name,
321
+ param,
322
+ getLineNumber(param),
323
+ { isParameter: true }
324
+ );
325
+ } else if (param.type === "ObjectPattern") {
326
+ extractDestructuredIdentifiers(param, true, scopeTracker);
983
327
  }
984
- }
985
- }
986
- },
987
- leave: (node) => {
988
- ancestors.pop();
989
- if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression" || node.type === "BlockStatement" || isLoopStatement(node) || node.type === "ClassDeclaration") {
990
- scopeTracker.exitScope();
991
- }
992
- }
993
- });
994
- const allVariables = scopeTracker.getAllVariables();
995
- for (const varInfo of allVariables) {
996
- const name = varInfo.name;
997
- const line = varInfo.declarationLine;
998
- if (disabledChecks.has("single-letter") && name.length === 1) continue;
999
- if (disabledChecks.has("abbreviation") && name.length <= 3) continue;
1000
- const isInCoverage = ["s", "b", "f", "l"].includes(name) && varInfo.isDestructured;
1001
- if (isInCoverage) continue;
1002
- const functionComplexity = varInfo.node.type === "Identifier" && "parent" in varInfo.node ? calculateComplexity(varInfo.node) : context.complexity;
1003
- if (isAcceptableInContext(name, context, {
1004
- isLoopVariable: varInfo.isLoopVariable || allAbbreviations.has(name),
1005
- isParameter: varInfo.isParameter,
1006
- isDestructured: varInfo.isDestructured,
1007
- complexity: functionComplexity
1008
- })) {
1009
- continue;
1010
- }
1011
- if (name.length === 1 && !allAbbreviations.has(name) && !allShortWords.has(name)) {
1012
- const isShortLived = scopeTracker.isShortLived(varInfo, 5);
1013
- if (!isShortLived) {
1014
- issues.push({
1015
- file,
1016
- line,
1017
- type: "poor-naming",
1018
- identifier: name,
1019
- severity: adjustSeverity("minor", context, "poor-naming"),
1020
- suggestion: `Use descriptive variable name instead of single letter '${name}'`
1021
328
  });
1022
329
  }
1023
- continue;
1024
- }
1025
- if (name.length >= 2 && name.length <= 3) {
1026
- if (!allShortWords.has(name.toLowerCase()) && !allAbbreviations.has(name.toLowerCase())) {
1027
- const isShortLived = scopeTracker.isShortLived(varInfo, 5);
1028
- if (!isShortLived) {
1029
- issues.push({
1030
- file,
1031
- line,
1032
- type: "abbreviation",
1033
- identifier: name,
1034
- severity: adjustSeverity("info", context, "abbreviation"),
1035
- suggestion: `Consider using full word instead of abbreviation '${name}'`
1036
- });
1037
- }
1038
- }
1039
- continue;
1040
- }
1041
- if (!disabledChecks.has("convention-mix") && file.match(/\.(ts|tsx|js|jsx)$/)) {
1042
- if (name.includes("_") && !name.startsWith("_") && name.toLowerCase() === name) {
1043
- const camelCase = name.replace(
1044
- /_([a-z])/g,
1045
- (_, letter) => letter.toUpperCase()
330
+ if ((node.type === "ClassDeclaration" || node.type === "TSInterfaceDeclaration" || node.type === "TSTypeAliasDeclaration") && node.id) {
331
+ checkNamingConvention(
332
+ node.id.name,
333
+ "PascalCase",
334
+ node.id,
335
+ filePath,
336
+ issues,
337
+ context
1046
338
  );
1047
- issues.push({
1048
- file,
1049
- line,
1050
- type: "convention-mix",
1051
- identifier: name,
1052
- severity: adjustSeverity("minor", context, "convention-mix"),
1053
- suggestion: `Use camelCase '${camelCase}' instead of snake_case in TypeScript/JavaScript`
1054
- });
1055
339
  }
1056
340
  }
341
+ });
342
+ for (const varInfo of scopeTracker.getVariables()) {
343
+ checkVariableNaming(varInfo, filePath, issues, context);
1057
344
  }
1058
- if (!disabledChecks.has("unclear")) {
1059
- traverseAST(ast, {
1060
- enter: (node) => {
1061
- if (node.type === "FunctionDeclaration" || node.type === "MethodDefinition") {
1062
- const name = getFunctionName(node);
1063
- if (!name) return;
1064
- const line = getLineNumber(node);
1065
- if (["main", "init", "setup", "bootstrap"].includes(name)) return;
1066
- const hasActionVerb = name.match(
1067
- /^(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|track|store|persist|upsert|derive|classify|combine|discover|activate|require|assert|expect|mask|escape|sign|put|list|complete|page|safe|mock|pick|pluralize|text|count|detect|select)/
1068
- );
1069
- const isFactoryPattern = name.match(
1070
- /(Factory|Builder|Creator|Generator|Provider|Adapter|Mock)$/
1071
- );
1072
- const isEventHandler = name.match(/^on[A-Z]/);
1073
- const isDescriptiveLong = name.length > 15;
1074
- const isReactHook = name.match(/^use[A-Z]/);
1075
- const isHelperPattern = name.match(/^(to|from|with|without|for|as|into)\w+/) || name.match(/^\w+(To|From|With|Without|For|As|Into)\w*$/);
1076
- const isUtilityName = [
1077
- "cn",
1078
- "proxy",
1079
- "sitemap",
1080
- "robots",
1081
- "gtag"
1082
- ].includes(name);
1083
- const isLanguageKeyword = [
1084
- "constructor",
1085
- "toString",
1086
- "valueOf",
1087
- "toJSON"
1088
- ].includes(name);
1089
- const isFrameworkPattern = name.match(
1090
- /^(goto|fill|click|select|submit|wait|expect)\w*/
1091
- );
1092
- const isDescriptivePattern = name.match(
1093
- /^(default|total|count|sum|avg|max|min|initial|current|previous|next)\w+/
1094
- ) || name.match(
1095
- /\w+(Count|Total|Sum|Average|List|Map|Set|Config|Settings|Options|Props|Data|Info|Details|State|Status|Response|Result)$/
1096
- );
1097
- const capitalCount = (name.match(/[A-Z]/g) || []).length;
1098
- const isCompoundWord = capitalCount >= 3;
1099
- if (!hasActionVerb && !isFactoryPattern && !isEventHandler && !isDescriptiveLong && !isReactHook && !isHelperPattern && !isUtilityName && !isDescriptivePattern && !isCompoundWord && !isLanguageKeyword && !isFrameworkPattern) {
1100
- issues.push({
1101
- file,
1102
- line,
1103
- type: "unclear",
1104
- identifier: name,
1105
- severity: adjustSeverity("info", context, "unclear"),
1106
- suggestion: `Function '${name}' should start with an action verb (get, set, create, etc.)`
1107
- });
1108
- }
1109
- }
1110
- }
345
+ return issues;
346
+ }
347
+ function checkNamingConvention(name, convention, node, file, issues, context) {
348
+ let isValid = true;
349
+ if (convention === "PascalCase") {
350
+ isValid = /^[A-Z][a-zA-Z0-9]*$/.test(name);
351
+ } else if (convention === "camelCase") {
352
+ isValid = /^[a-z][a-zA-Z0-9]*$/.test(name);
353
+ } else if (convention === "UPPER_CASE") {
354
+ isValid = /^[A-Z][A-Z0-9_]*$/.test(name);
355
+ }
356
+ if (!isValid) {
357
+ const severity = adjustSeverity(import_core2.Severity.Info, context, "convention-mix");
358
+ issues.push({
359
+ file,
360
+ line: getLineNumber(node),
361
+ type: "convention-mix",
362
+ identifier: name,
363
+ severity,
364
+ suggestion: `Follow ${convention} for this identifier`
1111
365
  });
1112
366
  }
1113
- return issues;
1114
367
  }
1115
- function extractIdentifiersFromPattern(pattern, scopeTracker, isParameter, ancestors) {
1116
- void ancestors;
1117
- if (pattern.type === "ObjectPattern") {
1118
- for (const prop of pattern.properties) {
368
+ function checkVariableNaming(varInfo, file, issues, context) {
369
+ const { name, node, line, options } = varInfo;
370
+ if (isAcceptableInContext(name, context, options)) {
371
+ return;
372
+ }
373
+ if (name.length === 1 && !options.isLoopVariable) {
374
+ const severity = adjustSeverity(import_core2.Severity.Minor, context, "poor-naming");
375
+ issues.push({
376
+ file,
377
+ line,
378
+ type: "poor-naming",
379
+ identifier: name,
380
+ severity,
381
+ suggestion: "Use a more descriptive name than a single letter"
382
+ });
383
+ }
384
+ const vagueNames = [
385
+ "data",
386
+ "info",
387
+ "item",
388
+ "obj",
389
+ "val",
390
+ "tmp",
391
+ "temp",
392
+ "thing",
393
+ "stuff"
394
+ ];
395
+ if (vagueNames.includes(name.toLowerCase())) {
396
+ const severity = adjustSeverity(import_core2.Severity.Minor, context, "poor-naming");
397
+ issues.push({
398
+ file,
399
+ line,
400
+ type: "poor-naming",
401
+ identifier: name,
402
+ severity,
403
+ suggestion: `Avoid vague names like '${name}'. What does this data represent?`
404
+ });
405
+ }
406
+ if (name.length > 1 && name.length <= 3 && !options.isLoopVariable && !isCommonAbbreviation(name)) {
407
+ const severity = adjustSeverity(import_core2.Severity.Info, context, "abbreviation");
408
+ issues.push({
409
+ file,
410
+ line,
411
+ type: "abbreviation",
412
+ identifier: name,
413
+ severity,
414
+ suggestion: "Avoid non-standard abbreviations"
415
+ });
416
+ }
417
+ }
418
+ function isCommonAbbreviation(name) {
419
+ const common = [
420
+ "id",
421
+ "db",
422
+ "fs",
423
+ "os",
424
+ "ip",
425
+ "ui",
426
+ "ux",
427
+ "api",
428
+ "env",
429
+ "url"
430
+ ];
431
+ return common.includes(name.toLowerCase());
432
+ }
433
+ var ScopeTracker = class {
434
+ constructor() {
435
+ this.variables = [];
436
+ }
437
+ declareVariable(name, node, line, options = {}) {
438
+ this.variables.push({ name, node, line, options });
439
+ }
440
+ getVariables() {
441
+ return this.variables;
442
+ }
443
+ };
444
+ function extractDestructuredIdentifiers(node, isParameter, scopeTracker) {
445
+ if (node.type === "ObjectPattern") {
446
+ node.properties.forEach((prop) => {
1119
447
  if (prop.type === "Property" && prop.value.type === "Identifier") {
1120
448
  scopeTracker.declareVariable(
1121
449
  prop.value.name,
@@ -1126,21 +454,11 @@ function extractIdentifiersFromPattern(pattern, scopeTracker, isParameter, ances
1126
454
  isDestructured: true
1127
455
  }
1128
456
  );
1129
- } else if (prop.type === "RestElement" && prop.argument.type === "Identifier") {
1130
- scopeTracker.declareVariable(
1131
- prop.argument.name,
1132
- prop.argument,
1133
- getLineNumber(prop.argument),
1134
- {
1135
- isParameter,
1136
- isDestructured: true
1137
- }
1138
- );
1139
457
  }
1140
- }
1141
- } else if (pattern.type === "ArrayPattern") {
1142
- for (const element of pattern.elements) {
1143
- if (element && element.type === "Identifier") {
458
+ });
459
+ } else if (node.type === "ArrayPattern") {
460
+ for (const element of node.elements) {
461
+ if (element?.type === "Identifier") {
1144
462
  scopeTracker.declareVariable(
1145
463
  element.name,
1146
464
  element,
@@ -1156,10 +474,10 @@ function extractIdentifiersFromPattern(pattern, scopeTracker, isParameter, ances
1156
474
  }
1157
475
 
1158
476
  // src/analyzers/naming-python.ts
1159
- var import_core2 = require("@aiready/core");
477
+ var import_core3 = require("@aiready/core");
1160
478
  async function analyzePythonNaming(files) {
1161
479
  const issues = [];
1162
- const parser = (0, import_core2.getParser)("dummy.py");
480
+ const parser = (0, import_core3.getParser)("dummy.py");
1163
481
  if (!parser) {
1164
482
  console.warn("Python parser not available");
1165
483
  return issues;
@@ -1203,7 +521,7 @@ async function analyzePythonNaming(files) {
1203
521
  return issues;
1204
522
  }
1205
523
  function checkPythonNaming(identifier, type, file, line) {
1206
- const parser = (0, import_core2.getParser)("dummy.py");
524
+ const parser = (0, import_core3.getParser)("dummy.py");
1207
525
  const conventions = parser?.getNamingConventions();
1208
526
  if (!conventions) return null;
1209
527
  if (conventions.exceptions?.includes(identifier)) {
@@ -1278,162 +596,63 @@ function toPascalCase(str) {
1278
596
  }
1279
597
 
1280
598
  // src/analyzers/patterns.ts
1281
- var import_core3 = require("@aiready/core");
1282
- async function analyzePatterns(files) {
599
+ var import_fs2 = require("fs");
600
+ var import_core4 = require("@aiready/core");
601
+ async function analyzePatterns(filePaths) {
1283
602
  const issues = [];
1284
- const errorHandlingIssues = await analyzeErrorHandling(files);
1285
- issues.push(...errorHandlingIssues);
1286
- const asyncIssues = await analyzeAsyncPatterns(files);
1287
- issues.push(...asyncIssues);
1288
- const importIssues = await analyzeImportStyles(files);
1289
- issues.push(...importIssues);
1290
- return issues;
1291
- }
1292
- async function analyzeErrorHandling(files) {
1293
- const patterns = {
1294
- tryCatch: [],
1295
- throwsError: [],
1296
- returnsNull: [],
1297
- returnsError: []
603
+ const contents = /* @__PURE__ */ new Map();
604
+ const tryCatchPattern = /try\s*\{/g;
605
+ const catchPattern = /catch\s*\(\s*(\w+)\s*\)/g;
606
+ const styleStats = {
607
+ tryCatch: 0,
608
+ thenCatch: 0,
609
+ asyncAwait: 0,
610
+ commonJs: 0,
611
+ esm: 0
1298
612
  };
1299
- for (const file of files) {
1300
- const content = await (0, import_core3.readFileContent)(file);
1301
- if (content.includes("try {") || content.includes("} catch")) {
1302
- patterns.tryCatch.push(file);
1303
- }
1304
- if (content.match(/throw new \w*Error/)) {
1305
- patterns.throwsError.push(file);
1306
- }
1307
- if (content.match(/return null/)) {
1308
- patterns.returnsNull.push(file);
1309
- }
1310
- if (content.match(/return \{ error:/)) {
1311
- patterns.returnsError.push(file);
613
+ for (const filePath of filePaths) {
614
+ try {
615
+ const content = (0, import_fs2.readFileSync)(filePath, "utf-8");
616
+ contents.set(filePath, content);
617
+ if (content.match(tryCatchPattern)) styleStats.tryCatch++;
618
+ if (content.match(/\.catch\s*\(/)) styleStats.thenCatch++;
619
+ if (content.match(/\bawait\b/)) styleStats.asyncAwait++;
620
+ if (content.match(/\brequire\s*\(/)) styleStats.commonJs++;
621
+ if (content.match(/\bimport\b.*\bfrom\b/)) styleStats.esm++;
622
+ } catch (err) {
623
+ void err;
1312
624
  }
1313
625
  }
1314
- const issues = [];
1315
- const strategiesUsed = Object.values(patterns).filter(
1316
- (p) => p.length > 0
1317
- ).length;
1318
- if (strategiesUsed > 2) {
626
+ const totalFiles = filePaths.length;
627
+ if (styleStats.tryCatch > 0 && styleStats.thenCatch > 0) {
628
+ const dominant = styleStats.tryCatch >= styleStats.thenCatch ? "try-catch" : ".catch()";
629
+ const minority = dominant === "try-catch" ? ".catch()" : "try-catch";
1319
630
  issues.push({
1320
- files: [
1321
- .../* @__PURE__ */ new Set([
1322
- ...patterns.tryCatch,
1323
- ...patterns.throwsError,
1324
- ...patterns.returnsNull,
1325
- ...patterns.returnsError
1326
- ])
1327
- ],
631
+ files: filePaths.filter((f) => {
632
+ const c = contents.get(f) || "";
633
+ return minority === "try-catch" ? c.match(tryCatchPattern) : c.match(/\.catch\s*\(/);
634
+ }),
1328
635
  type: "error-handling",
1329
- description: "Inconsistent error handling strategies across codebase",
1330
- examples: [
1331
- patterns.tryCatch.length > 0 ? `Try-catch used in ${patterns.tryCatch.length} files` : "",
1332
- patterns.throwsError.length > 0 ? `Throws errors in ${patterns.throwsError.length} files` : "",
1333
- patterns.returnsNull.length > 0 ? `Returns null in ${patterns.returnsNull.length} files` : "",
1334
- patterns.returnsError.length > 0 ? `Returns error objects in ${patterns.returnsError.length} files` : ""
1335
- ].filter((e) => e),
1336
- severity: "major"
636
+ description: `Mixed error handling styles: codebase primarily uses ${dominant}, but found ${minority} in some files.`,
637
+ examples: [dominant, minority],
638
+ severity: import_core4.Severity.Minor
1337
639
  });
1338
640
  }
1339
- return issues;
1340
- }
1341
- async function analyzeAsyncPatterns(files) {
1342
- const patterns = {
1343
- asyncAwait: [],
1344
- promises: [],
1345
- callbacks: []
1346
- };
1347
- for (const file of files) {
1348
- const content = await (0, import_core3.readFileContent)(file);
1349
- if (content.match(/async\s+(function|\(|[a-zA-Z])/)) {
1350
- patterns.asyncAwait.push(file);
1351
- }
1352
- if (content.match(/\.then\(/) || content.match(/\.catch\(/)) {
1353
- patterns.promises.push(file);
1354
- }
1355
- if (content.match(/callback\s*\(/) || content.match(/\(\s*err\s*,/)) {
1356
- patterns.callbacks.push(file);
1357
- }
1358
- }
1359
- const issues = [];
1360
- if (patterns.callbacks.length > 0 && patterns.asyncAwait.length > 0) {
1361
- issues.push({
1362
- files: [...patterns.callbacks, ...patterns.asyncAwait],
1363
- type: "async-style",
1364
- description: "Mixed async patterns: callbacks and async/await",
1365
- examples: [
1366
- `Callbacks found in: ${patterns.callbacks.slice(0, 3).join(", ")}`,
1367
- `Async/await used in: ${patterns.asyncAwait.slice(0, 3).join(", ")}`
1368
- ],
1369
- severity: "minor"
1370
- });
1371
- }
1372
- if (patterns.promises.length > patterns.asyncAwait.length * 0.3 && patterns.asyncAwait.length > 0) {
641
+ if (styleStats.commonJs > 0 && styleStats.esm > 0) {
642
+ const minority = styleStats.esm >= styleStats.commonJs ? "CommonJS (require)" : "ESM (import)";
1373
643
  issues.push({
1374
- files: patterns.promises,
1375
- type: "async-style",
1376
- description: "Consider using async/await instead of promise chains for consistency",
1377
- examples: patterns.promises.slice(0, 5),
1378
- severity: "info"
1379
- });
1380
- }
1381
- return issues;
1382
- }
1383
- async function analyzeImportStyles(files) {
1384
- const patterns = {
1385
- esModules: [],
1386
- commonJS: [],
1387
- mixed: []
1388
- };
1389
- for (const file of files) {
1390
- const content = await (0, import_core3.readFileContent)(file);
1391
- const hasESM = content.match(/^import\s+/m);
1392
- const hasCJS = hasActualRequireCalls(content);
1393
- if (hasESM && hasCJS) {
1394
- patterns.mixed.push(file);
1395
- } else if (hasESM) {
1396
- patterns.esModules.push(file);
1397
- } else if (hasCJS) {
1398
- patterns.commonJS.push(file);
1399
- }
1400
- }
1401
- const issues = [];
1402
- if (patterns.mixed.length > 0) {
1403
- issues.push({
1404
- files: patterns.mixed,
644
+ files: filePaths.filter((f) => {
645
+ const c = contents.get(f) || "";
646
+ return minority === "CommonJS (require)" ? c.match(/\brequire\s*\(/) : c.match(/\bimport\b/);
647
+ }),
1405
648
  type: "import-style",
1406
- description: "Mixed ES modules and CommonJS imports in same files",
1407
- examples: patterns.mixed.slice(0, 5),
1408
- severity: "major"
649
+ description: `Mixed module systems: found both ESM and CommonJS.`,
650
+ examples: ['import X from "y"', 'const X = require("y")'],
651
+ severity: import_core4.Severity.Major
1409
652
  });
1410
653
  }
1411
- if (patterns.esModules.length > 0 && patterns.commonJS.length > 0) {
1412
- const ratio = patterns.commonJS.length / (patterns.esModules.length + patterns.commonJS.length);
1413
- if (ratio > 0.2 && ratio < 0.8) {
1414
- issues.push({
1415
- files: [...patterns.esModules, ...patterns.commonJS],
1416
- type: "import-style",
1417
- description: "Inconsistent import styles across project",
1418
- examples: [
1419
- `ES modules: ${patterns.esModules.length} files`,
1420
- `CommonJS: ${patterns.commonJS.length} files`
1421
- ],
1422
- severity: "minor"
1423
- });
1424
- }
1425
- }
1426
654
  return issues;
1427
655
  }
1428
- function hasActualRequireCalls(content) {
1429
- let cleaned = content.replace(/\/\/.*$/gm, "");
1430
- cleaned = cleaned.replace(/\/\*[\s\S]*?\*\//g, "");
1431
- cleaned = cleaned.replace(/"[^"\n]*"/g, '""');
1432
- cleaned = cleaned.replace(/'[^'\n]*'/g, "''");
1433
- cleaned = cleaned.replace(/`[^`]*`/g, "``");
1434
- cleaned = cleaned.replace(/\/[^/\n]*require[^/\n]*\/[gimsuvy]*/g, "");
1435
- return /require\s*\(/.test(cleaned);
1436
- }
1437
656
 
1438
657
  // src/analyzer.ts
1439
658
  async function analyzeConsistency(options) {
@@ -1442,11 +661,11 @@ async function analyzeConsistency(options) {
1442
661
  checkPatterns = true,
1443
662
  checkArchitecture = false,
1444
663
  // Not implemented yet
1445
- minSeverity = "info",
664
+ minSeverity = import_core5.Severity.Info,
1446
665
  ...scanOptions
1447
666
  } = options;
1448
667
  void checkArchitecture;
1449
- const filePaths = await (0, import_core4.scanFiles)(scanOptions);
668
+ const filePaths = await (0, import_core5.scanFiles)(scanOptions);
1450
669
  const tsJsFiles = filePaths.filter((f) => /\.(ts|tsx|js|jsx)$/i.test(f));
1451
670
  const pythonFiles = filePaths.filter((f) => /\.py$/i.test(f));
1452
671
  let namingIssues = [];
@@ -1463,9 +682,9 @@ async function analyzeConsistency(options) {
1463
682
  continue;
1464
683
  }
1465
684
  const consistencyIssue = {
1466
- type: issue.type === "convention-mix" ? "naming-inconsistency" : "naming-quality",
685
+ type: issue.type === "convention-mix" ? import_core5.IssueType.NamingInconsistency : import_core5.IssueType.NamingQuality,
1467
686
  category: "naming",
1468
- severity: issue.severity,
687
+ severity: getSeverityEnum(issue.severity),
1469
688
  message: `${issue.type}: ${issue.identifier}`,
1470
689
  location: {
1471
690
  file: issue.file,
@@ -1484,9 +703,9 @@ async function analyzeConsistency(options) {
1484
703
  continue;
1485
704
  }
1486
705
  const consistencyIssue = {
1487
- type: "pattern-inconsistency",
706
+ type: import_core5.IssueType.PatternInconsistency,
1488
707
  category: "patterns",
1489
- severity: issue.severity,
708
+ severity: getSeverityEnum(issue.severity),
1490
709
  message: issue.description,
1491
710
  location: {
1492
711
  file: issue.files[0] || "multiple files",
@@ -1513,16 +732,17 @@ async function analyzeConsistency(options) {
1513
732
  });
1514
733
  }
1515
734
  results.sort((fileResultA, fileResultB) => {
1516
- const severityOrder = { critical: 0, major: 1, minor: 2, info: 3 };
1517
735
  const maxSeverityA = Math.min(
1518
- ...fileResultA.issues.map(
1519
- (i) => severityOrder[i.severity]
1520
- )
736
+ ...fileResultA.issues.map((i) => {
737
+ const val = getSeverityLevel(i.severity);
738
+ return val === 4 ? 0 : val === 3 ? 1 : val === 2 ? 2 : 3;
739
+ })
1521
740
  );
1522
741
  const maxSeverityB = Math.min(
1523
- ...fileResultB.issues.map(
1524
- (i) => severityOrder[i.severity]
1525
- )
742
+ ...fileResultB.issues.map((i) => {
743
+ const val = getSeverityLevel(i.severity);
744
+ return val === 4 ? 0 : val === 3 ? 1 : val === 2 ? 2 : 3;
745
+ })
1526
746
  );
1527
747
  if (maxSeverityA !== maxSeverityB) {
1528
748
  return maxSeverityA - maxSeverityB;
@@ -1548,16 +768,52 @@ async function analyzeConsistency(options) {
1548
768
  recommendations
1549
769
  };
1550
770
  }
771
+ function getSeverityLevel(s) {
772
+ if (s === import_core5.Severity.Critical || s === "critical") return 4;
773
+ if (s === import_core5.Severity.Major || s === "major") return 3;
774
+ if (s === import_core5.Severity.Minor || s === "minor") return 2;
775
+ if (s === import_core5.Severity.Info || s === "info") return 1;
776
+ return 0;
777
+ }
778
+ function getSeverityEnum(s) {
779
+ const val = getSeverityLevel(s);
780
+ switch (val) {
781
+ case 4:
782
+ return import_core5.Severity.Critical;
783
+ case 3:
784
+ return import_core5.Severity.Major;
785
+ case 2:
786
+ return import_core5.Severity.Minor;
787
+ case 1:
788
+ return import_core5.Severity.Info;
789
+ default:
790
+ return import_core5.Severity.Info;
791
+ }
792
+ }
1551
793
  function shouldIncludeSeverity(severity, minSeverity) {
1552
- const severityLevels = { info: 0, minor: 1, major: 2, critical: 3 };
1553
- return severityLevels[severity] >= severityLevels[minSeverity];
794
+ return getSeverityLevel(severity) >= getSeverityLevel(minSeverity);
1554
795
  }
1555
796
  function calculateConsistencyScore(issues) {
1556
- const weights = { critical: 10, major: 5, minor: 2, info: 1 };
1557
- const totalWeight = issues.reduce(
1558
- (sum, issue) => sum + weights[issue.severity],
1559
- 0
1560
- );
797
+ let totalWeight = 0;
798
+ for (const issue of issues) {
799
+ const val = getSeverityLevel(issue.severity);
800
+ switch (val) {
801
+ case 4:
802
+ totalWeight += 10;
803
+ break;
804
+ case 3:
805
+ totalWeight += 5;
806
+ break;
807
+ case 2:
808
+ totalWeight += 2;
809
+ break;
810
+ case 1:
811
+ totalWeight += 1;
812
+ break;
813
+ default:
814
+ totalWeight += 1;
815
+ }
816
+ }
1561
817
  return Math.max(0, 1 - totalWeight / 100);
1562
818
  }
1563
819
  function generateRecommendations(namingIssues, patternIssues) {
@@ -1611,243 +867,82 @@ function generateRecommendations(namingIssues, patternIssues) {
1611
867
  }
1612
868
 
1613
869
  // src/analyzers/naming.ts
1614
- var import_core5 = require("@aiready/core");
1615
- async function analyzeNaming(files) {
1616
- const issues = [];
1617
- const { customAbbreviations, customShortWords, disabledChecks } = await loadNamingConfig(files);
1618
- for (const file of files) {
1619
- const content = await (0, import_core5.readFileContent)(file);
1620
- const fileIssues = analyzeFileNaming(
1621
- file,
1622
- content,
1623
- customAbbreviations,
1624
- customShortWords,
1625
- disabledChecks
1626
- );
1627
- issues.push(...fileIssues);
1628
- }
1629
- return issues;
1630
- }
1631
- function analyzeFileNaming(file, content, customAbbreviations, customShortWords, disabledChecks) {
870
+ var import_fs3 = require("fs");
871
+ var import_core6 = require("@aiready/core");
872
+ async function analyzeNaming(filePaths) {
1632
873
  const issues = [];
1633
- const isTestFile = file.match(/\.(test|spec)\.(ts|tsx|js|jsx)$/);
1634
- const lines = content.split("\n");
1635
- const allAbbreviations = /* @__PURE__ */ new Set([
1636
- ...ACCEPTABLE_ABBREVIATIONS,
1637
- ...customAbbreviations
1638
- ]);
1639
- const allShortWords = /* @__PURE__ */ new Set([...COMMON_SHORT_WORDS, ...customShortWords]);
1640
- const getContextWindow = (index, windowSize = 3) => {
1641
- const start = Math.max(0, index - windowSize);
1642
- const end = Math.min(lines.length, index + windowSize + 1);
1643
- return lines.slice(start, end).join("\n");
1644
- };
1645
- const isShortLivedVariable = (varName, declarationIndex) => {
1646
- const searchRange = 5;
1647
- const endIndex = Math.min(lines.length, declarationIndex + searchRange + 1);
1648
- let usageCount = 0;
1649
- for (let i = declarationIndex; i < endIndex; i++) {
1650
- const regex = new RegExp(`\\b${varName}\\b`, "g");
1651
- const matches = lines[i].match(regex);
1652
- if (matches) {
1653
- usageCount += matches.length;
1654
- }
1655
- }
1656
- return usageCount >= 2 && usageCount <= 3;
1657
- };
1658
- lines.forEach((line, index) => {
1659
- const lineNumber = index + 1;
1660
- const contextWindow = getContextWindow(index);
1661
- if (!disabledChecks.has("single-letter")) {
1662
- const singleLetterMatches = line.matchAll(
1663
- /\b(?:const|let|var)\s+([a-hm-z])\s*=/gi
1664
- );
1665
- for (const match of singleLetterMatches) {
1666
- const letter = match[1].toLowerCase();
1667
- const isCoverageContext2 = /coverage|summary|metrics|pct|percent/i.test(line) || /\.(?:statements|branches|functions|lines)\.pct/i.test(line);
1668
- if (isCoverageContext2 && ["s", "b", "f", "l"].includes(letter)) {
1669
- continue;
1670
- }
1671
- const isInLoopContext = line.includes("for") || /\.(map|filter|forEach|reduce|find|some|every)\s*\(/.test(line) || line.includes("=>") || // Arrow function
1672
- /\w+\s*=>\s*/.test(line);
1673
- const isI18nContext = line.includes("useTranslation") || line.includes("i18n.t") || /\bt\s*\(['"]/.test(line);
1674
- const isArrowFunctionParam = /\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
1675
- /[a-z]\s*=>/.test(line) || // s => on same line
1676
- // Multi-line arrow function detection: look for pattern in context window
1677
- new RegExp(`\\b${letter}\\s*\\)\\s*$`).test(line) && /=>/.test(contextWindow) || // (s)\n =>
1678
- new RegExp(
1679
- `\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`
1680
- ).test(lines[index - 1] || "") && /=>/.test(contextWindow);
1681
- const isShortLived = isShortLivedVariable(letter, index);
1682
- if (!isInLoopContext && !isI18nContext && !isArrowFunctionParam && !isShortLived && !["x", "y", "z", "i", "j", "k", "l", "n", "m"].includes(letter)) {
1683
- if (isTestFile && ["a", "b", "c", "d", "e", "f", "s"].includes(letter)) {
1684
- continue;
1685
- }
874
+ for (const filePath of filePaths) {
875
+ try {
876
+ const content = (0, import_fs3.readFileSync)(filePath, "utf-8");
877
+ const lines = content.split("\n");
878
+ lines.forEach((line, index) => {
879
+ const singleLetterMatch = line.match(
880
+ /\b(const|let|var)\s+([a-hj-km-np-zA-Z])\s*=/
881
+ );
882
+ if (singleLetterMatch) {
1686
883
  issues.push({
1687
- file,
1688
- line: lineNumber,
884
+ file: filePath,
885
+ line: index + 1,
1689
886
  type: "poor-naming",
1690
- identifier: match[1],
1691
- severity: "minor",
1692
- suggestion: `Use descriptive variable name instead of single letter '${match[1]}'`
887
+ identifier: singleLetterMatch[2],
888
+ severity: import_core6.Severity.Minor,
889
+ suggestion: "Use a more descriptive name than a single letter"
1693
890
  });
1694
891
  }
1695
- }
1696
- }
1697
- if (!disabledChecks.has("abbreviation")) {
1698
- const abbreviationMatches = line.matchAll(
1699
- /\b(?:const|let|var)\s+([a-z]{1,3})(?=[A-Z]|_|\s*=)/g
1700
- );
1701
- for (const match of abbreviationMatches) {
1702
- const abbrev = match[1].toLowerCase();
1703
- if (allShortWords.has(abbrev)) {
1704
- continue;
1705
- }
1706
- if (allAbbreviations.has(abbrev)) {
1707
- continue;
1708
- }
1709
- const isArrowFunctionParam = /\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
1710
- new RegExp(`\\b${abbrev}\\s*=>`).test(line) || // s => on same line
1711
- // Multi-line arrow function: check context window
1712
- new RegExp(`\\b${abbrev}\\s*\\)\\s*$`).test(line) && /=>/.test(contextWindow) || // (s)\n =>
1713
- new RegExp(
1714
- `\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`
1715
- ).test(lines[index - 1] || "") && new RegExp(`^\\s*${abbrev}\\s*=>`).test(line);
1716
- if (isArrowFunctionParam) {
1717
- continue;
1718
- }
1719
- if (abbrev.length <= 2) {
1720
- const isDateTimeContext = /date|time|day|hour|minute|second|timestamp/i.test(line);
1721
- if (isDateTimeContext && ["d", "t", "dt"].includes(abbrev)) {
1722
- continue;
1723
- }
1724
- const isUserContext = /user|auth|account/i.test(line);
1725
- if (isUserContext && abbrev === "u") {
1726
- continue;
892
+ if (filePath.match(/\.(ts|tsx|js|jsx)$/)) {
893
+ const snakeCaseMatch = line.match(
894
+ /\b(const|let|var|function)\s+([a-z]+_[a-z0-9_]+)\b/
895
+ );
896
+ if (snakeCaseMatch) {
897
+ issues.push({
898
+ file: filePath,
899
+ line: index + 1,
900
+ type: "convention-mix",
901
+ identifier: snakeCaseMatch[2],
902
+ severity: import_core6.Severity.Info,
903
+ suggestion: "Use camelCase instead of snake_case in TypeScript/JavaScript"
904
+ });
1727
905
  }
1728
906
  }
1729
- issues.push({
1730
- file,
1731
- line: lineNumber,
1732
- type: "abbreviation",
1733
- identifier: match[1],
1734
- severity: "info",
1735
- suggestion: `Consider using full word instead of abbreviation '${match[1]}'`
1736
- });
1737
- }
1738
- }
1739
- if (!disabledChecks.has("convention-mix") && file.match(/\.(ts|tsx|js|jsx)$/)) {
1740
- const camelCaseVars = line.match(
1741
- /\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*=/
1742
- );
1743
- void camelCaseVars;
1744
- const snakeCaseVars = line.match(
1745
- /\b(?:const|let|var)\s+([a-z][a-z0-9]*_[a-z0-9_]*)\s*=/
1746
- );
1747
- if (snakeCaseVars) {
1748
- issues.push({
1749
- file,
1750
- line: lineNumber,
1751
- type: "convention-mix",
1752
- identifier: snakeCaseVars[1],
1753
- severity: "minor",
1754
- suggestion: `Use camelCase '${snakeCaseToCamelCase(snakeCaseVars[1])}' instead of snake_case in TypeScript/JavaScript`
1755
- });
1756
- }
1757
- }
1758
- if (!disabledChecks.has("unclear")) {
1759
- const booleanMatches = line.matchAll(
1760
- /\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*:\s*boolean/gi
1761
- );
1762
- for (const match of booleanMatches) {
1763
- const name = match[1];
1764
- if (!name.match(/^(is|has|should|can|will|did)/i)) {
1765
- issues.push({
1766
- file,
1767
- line: lineNumber,
1768
- type: "unclear",
1769
- identifier: name,
1770
- severity: "info",
1771
- suggestion: `Boolean variable '${name}' should start with is/has/should/can for clarity`
1772
- });
1773
- }
1774
- }
1775
- }
1776
- if (!disabledChecks.has("unclear")) {
1777
- const functionMatches = line.matchAll(/function\s+([a-z][a-zA-Z0-9]*)/g);
1778
- for (const match of functionMatches) {
1779
- const name = match[1];
1780
- const isKeyword = [
1781
- "for",
1782
- "if",
1783
- "else",
1784
- "while",
1785
- "do",
1786
- "switch",
1787
- "case",
1788
- "break",
1789
- "continue",
1790
- "return",
1791
- "throw",
1792
- "try",
1793
- "catch",
1794
- "finally",
1795
- "with",
1796
- "yield",
1797
- "await"
1798
- ].includes(name);
1799
- if (isKeyword) {
1800
- continue;
1801
- }
1802
- const isEntryPoint = ["main", "init", "setup", "bootstrap"].includes(
1803
- name
1804
- );
1805
- if (isEntryPoint) {
1806
- continue;
1807
- }
1808
- const isFactoryPattern = name.match(
1809
- /(Factory|Builder|Creator|Generator|Provider|Adapter|Mock)$/
907
+ const shortNameMatch = line.match(
908
+ /\b(const|let|var)\s+([a-zA-Z0-9]{2,3})\s*=/
1810
909
  );
1811
- const isEventHandler = name.match(/^on[A-Z]/);
1812
- const isDescriptiveLong = name.length > 15;
1813
- const isReactHook = name.match(/^use[A-Z]/);
1814
- const isDescriptivePattern = name.match(
1815
- /^(default|total|count|sum|avg|max|min|initial|current|previous|next)\w+/
1816
- ) || name.match(
1817
- /\w+(Count|Total|Sum|Average|List|Map|Set|Config|Settings|Options|Props|Data|Info|Details|State|Status|Response|Result)$/
1818
- );
1819
- const isHelperPattern = name.match(/^(to|from|with|without|for|as|into)\w+/) || // toMetadata, withLogger, forPath
1820
- name.match(/^\w+(To|From|With|Without|For|As|Into)\w*$/);
1821
- const isUtilityName = [
1822
- "cn",
1823
- "proxy",
1824
- "sitemap",
1825
- "robots",
1826
- "gtag"
1827
- ].includes(name);
1828
- const capitalCount = (name.match(/[A-Z]/g) || []).length;
1829
- const isCompoundWord = capitalCount >= 3;
1830
- const hasActionVerb = name.match(
1831
- /^(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|track|store|persist|upsert|derive|classify|combine|discover|activate|require|assert|expect|mask|escape|sign|put|list|complete|page|safe|mock|pick|pluralize|text)/
1832
- );
1833
- if (!hasActionVerb && !isFactoryPattern && !isEventHandler && !isDescriptiveLong && !isDescriptivePattern && !isCompoundWord && !isHelperPattern && !isUtilityName && !isReactHook) {
1834
- issues.push({
1835
- file,
1836
- line: lineNumber,
1837
- type: "unclear",
1838
- identifier: name,
1839
- severity: "info",
1840
- suggestion: `Function '${name}' should start with an action verb (get, set, create, etc.)`
1841
- });
910
+ if (shortNameMatch) {
911
+ const name = shortNameMatch[2].toLowerCase();
912
+ const vagueNames = ["obj", "val", "tmp", "res", "ret", "data"];
913
+ if (vagueNames.includes(name)) {
914
+ issues.push({
915
+ file: filePath,
916
+ line: index + 1,
917
+ type: "poor-naming",
918
+ identifier: name,
919
+ severity: import_core6.Severity.Minor,
920
+ suggestion: `Avoid vague names like '${name}'`
921
+ });
922
+ }
1842
923
  }
1843
- }
924
+ });
925
+ } catch (err) {
926
+ void err;
1844
927
  }
1845
- });
928
+ }
1846
929
  return issues;
1847
930
  }
1848
931
 
932
+ // src/analyzers/naming-constants.ts
933
+ function detectNamingConventions(files, allIssues) {
934
+ const camelCaseCount = allIssues.filter(
935
+ (i) => i.type === "convention-mix"
936
+ ).length;
937
+ const totalChecks = files.length * 10;
938
+ if (camelCaseCount / totalChecks > 0.3) {
939
+ return { dominantConvention: "mixed", conventionScore: 0.5 };
940
+ }
941
+ return { dominantConvention: "camelCase", conventionScore: 0.9 };
942
+ }
943
+
1849
944
  // src/scoring.ts
1850
- var import_core6 = require("@aiready/core");
945
+ var import_core7 = require("@aiready/core");
1851
946
  function calculateConsistencyScore2(issues, totalFilesAnalyzed, costConfig) {
1852
947
  void costConfig;
1853
948
  const criticalIssues = issues.filter((i) => i.severity === "critical").length;
@@ -1923,7 +1018,7 @@ function calculateConsistencyScore2(issues, totalFilesAnalyzed, costConfig) {
1923
1018
  priority: "low"
1924
1019
  });
1925
1020
  }
1926
- const productivityImpact = (0, import_core6.calculateProductivityImpact)(issues);
1021
+ const productivityImpact = (0, import_core7.calculateProductivityImpact)(issues);
1927
1022
  return {
1928
1023
  toolName: "consistency",
1929
1024
  score,