@aiready/consistency 0.16.0 → 0.16.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,157 @@ 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 = ["id", "db", "fs", "os", "ip", "ui", "ux", "api", "env", "url"];
420
+ return common.includes(name.toLowerCase());
421
+ }
422
+ var ScopeTracker = class {
423
+ constructor() {
424
+ this.variables = [];
425
+ }
426
+ declareVariable(name, node, line, options = {}) {
427
+ this.variables.push({ name, node, line, options });
428
+ }
429
+ getVariables() {
430
+ return this.variables;
431
+ }
432
+ };
433
+ function extractDestructuredIdentifiers(node, isParameter, scopeTracker) {
434
+ if (node.type === "ObjectPattern") {
435
+ node.properties.forEach((prop) => {
1119
436
  if (prop.type === "Property" && prop.value.type === "Identifier") {
1120
437
  scopeTracker.declareVariable(
1121
438
  prop.value.name,
@@ -1126,21 +443,11 @@ function extractIdentifiersFromPattern(pattern, scopeTracker, isParameter, ances
1126
443
  isDestructured: true
1127
444
  }
1128
445
  );
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
446
  }
1140
- }
1141
- } else if (pattern.type === "ArrayPattern") {
1142
- for (const element of pattern.elements) {
1143
- if (element && element.type === "Identifier") {
447
+ });
448
+ } else if (node.type === "ArrayPattern") {
449
+ for (const element of node.elements) {
450
+ if (element?.type === "Identifier") {
1144
451
  scopeTracker.declareVariable(
1145
452
  element.name,
1146
453
  element,
@@ -1156,10 +463,10 @@ function extractIdentifiersFromPattern(pattern, scopeTracker, isParameter, ances
1156
463
  }
1157
464
 
1158
465
  // src/analyzers/naming-python.ts
1159
- var import_core2 = require("@aiready/core");
466
+ var import_core3 = require("@aiready/core");
1160
467
  async function analyzePythonNaming(files) {
1161
468
  const issues = [];
1162
- const parser = (0, import_core2.getParser)("dummy.py");
469
+ const parser = (0, import_core3.getParser)("dummy.py");
1163
470
  if (!parser) {
1164
471
  console.warn("Python parser not available");
1165
472
  return issues;
@@ -1203,7 +510,7 @@ async function analyzePythonNaming(files) {
1203
510
  return issues;
1204
511
  }
1205
512
  function checkPythonNaming(identifier, type, file, line) {
1206
- const parser = (0, import_core2.getParser)("dummy.py");
513
+ const parser = (0, import_core3.getParser)("dummy.py");
1207
514
  const conventions = parser?.getNamingConventions();
1208
515
  if (!conventions) return null;
1209
516
  if (conventions.exceptions?.includes(identifier)) {
@@ -1278,162 +585,63 @@ function toPascalCase(str) {
1278
585
  }
1279
586
 
1280
587
  // src/analyzers/patterns.ts
1281
- var import_core3 = require("@aiready/core");
1282
- async function analyzePatterns(files) {
588
+ var import_fs2 = require("fs");
589
+ var import_core4 = require("@aiready/core");
590
+ async function analyzePatterns(filePaths) {
1283
591
  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: []
592
+ const contents = /* @__PURE__ */ new Map();
593
+ const tryCatchPattern = /try\s*\{/g;
594
+ const catchPattern = /catch\s*\(\s*(\w+)\s*\)/g;
595
+ const styleStats = {
596
+ tryCatch: 0,
597
+ thenCatch: 0,
598
+ asyncAwait: 0,
599
+ commonJs: 0,
600
+ esm: 0
1298
601
  };
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);
602
+ for (const filePath of filePaths) {
603
+ try {
604
+ const content = (0, import_fs2.readFileSync)(filePath, "utf-8");
605
+ contents.set(filePath, content);
606
+ if (content.match(tryCatchPattern)) styleStats.tryCatch++;
607
+ if (content.match(/\.catch\s*\(/)) styleStats.thenCatch++;
608
+ if (content.match(/\bawait\b/)) styleStats.asyncAwait++;
609
+ if (content.match(/\brequire\s*\(/)) styleStats.commonJs++;
610
+ if (content.match(/\bimport\b.*\bfrom\b/)) styleStats.esm++;
611
+ } catch (err) {
612
+ void err;
1312
613
  }
1313
614
  }
1314
- const issues = [];
1315
- const strategiesUsed = Object.values(patterns).filter(
1316
- (p) => p.length > 0
1317
- ).length;
1318
- if (strategiesUsed > 2) {
615
+ const totalFiles = filePaths.length;
616
+ if (styleStats.tryCatch > 0 && styleStats.thenCatch > 0) {
617
+ const dominant = styleStats.tryCatch >= styleStats.thenCatch ? "try-catch" : ".catch()";
618
+ const minority = dominant === "try-catch" ? ".catch()" : "try-catch";
1319
619
  issues.push({
1320
- files: [
1321
- .../* @__PURE__ */ new Set([
1322
- ...patterns.tryCatch,
1323
- ...patterns.throwsError,
1324
- ...patterns.returnsNull,
1325
- ...patterns.returnsError
1326
- ])
1327
- ],
620
+ files: filePaths.filter((f) => {
621
+ const c = contents.get(f) || "";
622
+ return minority === "try-catch" ? c.match(tryCatchPattern) : c.match(/\.catch\s*\(/);
623
+ }),
1328
624
  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"
625
+ description: `Mixed error handling styles: codebase primarily uses ${dominant}, but found ${minority} in some files.`,
626
+ examples: [dominant, minority],
627
+ severity: import_core4.Severity.Minor
1337
628
  });
1338
629
  }
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) {
1373
- 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) {
630
+ if (styleStats.commonJs > 0 && styleStats.esm > 0) {
631
+ const minority = styleStats.esm >= styleStats.commonJs ? "CommonJS (require)" : "ESM (import)";
1403
632
  issues.push({
1404
- files: patterns.mixed,
633
+ files: filePaths.filter((f) => {
634
+ const c = contents.get(f) || "";
635
+ return minority === "CommonJS (require)" ? c.match(/\brequire\s*\(/) : c.match(/\bimport\b/);
636
+ }),
1405
637
  type: "import-style",
1406
- description: "Mixed ES modules and CommonJS imports in same files",
1407
- examples: patterns.mixed.slice(0, 5),
1408
- severity: "major"
638
+ description: `Mixed module systems: found both ESM and CommonJS.`,
639
+ examples: ['import X from "y"', 'const X = require("y")'],
640
+ severity: import_core4.Severity.Major
1409
641
  });
1410
642
  }
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
643
  return issues;
1427
644
  }
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
645
 
1438
646
  // src/analyzer.ts
1439
647
  async function analyzeConsistency(options) {
@@ -1442,11 +650,11 @@ async function analyzeConsistency(options) {
1442
650
  checkPatterns = true,
1443
651
  checkArchitecture = false,
1444
652
  // Not implemented yet
1445
- minSeverity = "info",
653
+ minSeverity = import_core5.Severity.Info,
1446
654
  ...scanOptions
1447
655
  } = options;
1448
656
  void checkArchitecture;
1449
- const filePaths = await (0, import_core4.scanFiles)(scanOptions);
657
+ const filePaths = await (0, import_core5.scanFiles)(scanOptions);
1450
658
  const tsJsFiles = filePaths.filter((f) => /\.(ts|tsx|js|jsx)$/i.test(f));
1451
659
  const pythonFiles = filePaths.filter((f) => /\.py$/i.test(f));
1452
660
  let namingIssues = [];
@@ -1463,9 +671,9 @@ async function analyzeConsistency(options) {
1463
671
  continue;
1464
672
  }
1465
673
  const consistencyIssue = {
1466
- type: issue.type === "convention-mix" ? "naming-inconsistency" : "naming-quality",
674
+ type: issue.type === "convention-mix" ? import_core5.IssueType.NamingInconsistency : import_core5.IssueType.NamingQuality,
1467
675
  category: "naming",
1468
- severity: issue.severity,
676
+ severity: getSeverityEnum(issue.severity),
1469
677
  message: `${issue.type}: ${issue.identifier}`,
1470
678
  location: {
1471
679
  file: issue.file,
@@ -1484,9 +692,9 @@ async function analyzeConsistency(options) {
1484
692
  continue;
1485
693
  }
1486
694
  const consistencyIssue = {
1487
- type: "pattern-inconsistency",
695
+ type: import_core5.IssueType.PatternInconsistency,
1488
696
  category: "patterns",
1489
- severity: issue.severity,
697
+ severity: getSeverityEnum(issue.severity),
1490
698
  message: issue.description,
1491
699
  location: {
1492
700
  file: issue.files[0] || "multiple files",
@@ -1513,16 +721,17 @@ async function analyzeConsistency(options) {
1513
721
  });
1514
722
  }
1515
723
  results.sort((fileResultA, fileResultB) => {
1516
- const severityOrder = { critical: 0, major: 1, minor: 2, info: 3 };
1517
724
  const maxSeverityA = Math.min(
1518
- ...fileResultA.issues.map(
1519
- (i) => severityOrder[i.severity]
1520
- )
725
+ ...fileResultA.issues.map((i) => {
726
+ const val = getSeverityLevel(i.severity);
727
+ return val === 4 ? 0 : val === 3 ? 1 : val === 2 ? 2 : 3;
728
+ })
1521
729
  );
1522
730
  const maxSeverityB = Math.min(
1523
- ...fileResultB.issues.map(
1524
- (i) => severityOrder[i.severity]
1525
- )
731
+ ...fileResultB.issues.map((i) => {
732
+ const val = getSeverityLevel(i.severity);
733
+ return val === 4 ? 0 : val === 3 ? 1 : val === 2 ? 2 : 3;
734
+ })
1526
735
  );
1527
736
  if (maxSeverityA !== maxSeverityB) {
1528
737
  return maxSeverityA - maxSeverityB;
@@ -1548,16 +757,52 @@ async function analyzeConsistency(options) {
1548
757
  recommendations
1549
758
  };
1550
759
  }
760
+ function getSeverityLevel(s) {
761
+ if (s === import_core5.Severity.Critical || s === "critical") return 4;
762
+ if (s === import_core5.Severity.Major || s === "major") return 3;
763
+ if (s === import_core5.Severity.Minor || s === "minor") return 2;
764
+ if (s === import_core5.Severity.Info || s === "info") return 1;
765
+ return 0;
766
+ }
767
+ function getSeverityEnum(s) {
768
+ const val = getSeverityLevel(s);
769
+ switch (val) {
770
+ case 4:
771
+ return import_core5.Severity.Critical;
772
+ case 3:
773
+ return import_core5.Severity.Major;
774
+ case 2:
775
+ return import_core5.Severity.Minor;
776
+ case 1:
777
+ return import_core5.Severity.Info;
778
+ default:
779
+ return import_core5.Severity.Info;
780
+ }
781
+ }
1551
782
  function shouldIncludeSeverity(severity, minSeverity) {
1552
- const severityLevels = { info: 0, minor: 1, major: 2, critical: 3 };
1553
- return severityLevels[severity] >= severityLevels[minSeverity];
783
+ return getSeverityLevel(severity) >= getSeverityLevel(minSeverity);
1554
784
  }
1555
785
  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
- );
786
+ let totalWeight = 0;
787
+ for (const issue of issues) {
788
+ const val = getSeverityLevel(issue.severity);
789
+ switch (val) {
790
+ case 4:
791
+ totalWeight += 10;
792
+ break;
793
+ case 3:
794
+ totalWeight += 5;
795
+ break;
796
+ case 2:
797
+ totalWeight += 2;
798
+ break;
799
+ case 1:
800
+ totalWeight += 1;
801
+ break;
802
+ default:
803
+ totalWeight += 1;
804
+ }
805
+ }
1561
806
  return Math.max(0, 1 - totalWeight / 100);
1562
807
  }
1563
808
  function generateRecommendations(namingIssues, patternIssues) {
@@ -1611,243 +856,76 @@ function generateRecommendations(namingIssues, patternIssues) {
1611
856
  }
1612
857
 
1613
858
  // 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) {
859
+ var import_fs3 = require("fs");
860
+ var import_core6 = require("@aiready/core");
861
+ async function analyzeNaming(filePaths) {
1632
862
  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
- }
863
+ for (const filePath of filePaths) {
864
+ try {
865
+ const content = (0, import_fs3.readFileSync)(filePath, "utf-8");
866
+ const lines = content.split("\n");
867
+ lines.forEach((line, index) => {
868
+ const singleLetterMatch = line.match(/\b(const|let|var)\s+([a-hj-km-np-zA-Z])\s*=/);
869
+ if (singleLetterMatch) {
1686
870
  issues.push({
1687
- file,
1688
- line: lineNumber,
871
+ file: filePath,
872
+ line: index + 1,
1689
873
  type: "poor-naming",
1690
- identifier: match[1],
1691
- severity: "minor",
1692
- suggestion: `Use descriptive variable name instead of single letter '${match[1]}'`
874
+ identifier: singleLetterMatch[2],
875
+ severity: import_core6.Severity.Minor,
876
+ suggestion: "Use a more descriptive name than a single letter"
1693
877
  });
1694
878
  }
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;
879
+ if (filePath.match(/\.(ts|tsx|js|jsx)$/)) {
880
+ const snakeCaseMatch = line.match(/\b(const|let|var|function)\s+([a-z]+_[a-z0-9_]+)\b/);
881
+ if (snakeCaseMatch) {
882
+ issues.push({
883
+ file: filePath,
884
+ line: index + 1,
885
+ type: "convention-mix",
886
+ identifier: snakeCaseMatch[2],
887
+ severity: import_core6.Severity.Info,
888
+ suggestion: "Use camelCase instead of snake_case in TypeScript/JavaScript"
889
+ });
1727
890
  }
1728
891
  }
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)$/
1810
- );
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
- });
892
+ const shortNameMatch = line.match(/\b(const|let|var)\s+([a-zA-Z0-9]{2,3})\s*=/);
893
+ if (shortNameMatch) {
894
+ const name = shortNameMatch[2].toLowerCase();
895
+ const vagueNames = ["obj", "val", "tmp", "res", "ret", "data"];
896
+ if (vagueNames.includes(name)) {
897
+ issues.push({
898
+ file: filePath,
899
+ line: index + 1,
900
+ type: "poor-naming",
901
+ identifier: name,
902
+ severity: import_core6.Severity.Minor,
903
+ suggestion: `Avoid vague names like '${name}'`
904
+ });
905
+ }
1842
906
  }
1843
- }
907
+ });
908
+ } catch (err) {
909
+ void err;
1844
910
  }
1845
- });
911
+ }
1846
912
  return issues;
1847
913
  }
1848
914
 
915
+ // src/analyzers/naming-constants.ts
916
+ function detectNamingConventions(files, allIssues) {
917
+ const camelCaseCount = allIssues.filter(
918
+ (i) => i.type === "convention-mix"
919
+ ).length;
920
+ const totalChecks = files.length * 10;
921
+ if (camelCaseCount / totalChecks > 0.3) {
922
+ return { dominantConvention: "mixed", conventionScore: 0.5 };
923
+ }
924
+ return { dominantConvention: "camelCase", conventionScore: 0.9 };
925
+ }
926
+
1849
927
  // src/scoring.ts
1850
- var import_core6 = require("@aiready/core");
928
+ var import_core7 = require("@aiready/core");
1851
929
  function calculateConsistencyScore2(issues, totalFilesAnalyzed, costConfig) {
1852
930
  void costConfig;
1853
931
  const criticalIssues = issues.filter((i) => i.severity === "critical").length;
@@ -1923,7 +1001,7 @@ function calculateConsistencyScore2(issues, totalFilesAnalyzed, costConfig) {
1923
1001
  priority: "low"
1924
1002
  });
1925
1003
  }
1926
- const productivityImpact = (0, import_core6.calculateProductivityImpact)(issues);
1004
+ const productivityImpact = (0, import_core7.calculateProductivityImpact)(issues);
1927
1005
  return {
1928
1006
  toolName: "consistency",
1929
1007
  score,