@aiready/consistency 0.8.31 → 0.8.34

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.mjs CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  detectNamingConventions,
8
8
  loadNamingConfig,
9
9
  snakeCaseToCamelCase
10
- } from "./chunk-YEHXYHGY.mjs";
10
+ } from "./chunk-J5IFYDVU.mjs";
11
11
 
12
12
  // src/analyzers/naming.ts
13
13
  import { readFileContent } from "@aiready/core";
@@ -16,7 +16,13 @@ async function analyzeNaming(files) {
16
16
  const { customAbbreviations, customShortWords, disabledChecks } = await loadNamingConfig(files);
17
17
  for (const file of files) {
18
18
  const content = await readFileContent(file);
19
- const fileIssues = analyzeFileNaming(file, content, customAbbreviations, customShortWords, disabledChecks);
19
+ const fileIssues = analyzeFileNaming(
20
+ file,
21
+ content,
22
+ customAbbreviations,
23
+ customShortWords,
24
+ disabledChecks
25
+ );
20
26
  issues.push(...fileIssues);
21
27
  }
22
28
  return issues;
@@ -25,7 +31,10 @@ function analyzeFileNaming(file, content, customAbbreviations, customShortWords,
25
31
  const issues = [];
26
32
  const isTestFile = file.match(/\.(test|spec)\.(ts|tsx|js|jsx)$/);
27
33
  const lines = content.split("\n");
28
- const allAbbreviations = /* @__PURE__ */ new Set([...ACCEPTABLE_ABBREVIATIONS, ...customAbbreviations]);
34
+ const allAbbreviations = /* @__PURE__ */ new Set([
35
+ ...ACCEPTABLE_ABBREVIATIONS,
36
+ ...customAbbreviations
37
+ ]);
29
38
  const allShortWords = /* @__PURE__ */ new Set([...COMMON_SHORT_WORDS, ...customShortWords]);
30
39
  const getContextWindow = (index, windowSize = 3) => {
31
40
  const start = Math.max(0, index - windowSize);
@@ -49,7 +58,9 @@ function analyzeFileNaming(file, content, customAbbreviations, customShortWords,
49
58
  const lineNumber = index + 1;
50
59
  const contextWindow = getContextWindow(index);
51
60
  if (!disabledChecks.has("single-letter")) {
52
- const singleLetterMatches = line.matchAll(/\b(?:const|let|var)\s+([a-hm-z])\s*=/gi);
61
+ const singleLetterMatches = line.matchAll(
62
+ /\b(?:const|let|var)\s+([a-hm-z])\s*=/gi
63
+ );
53
64
  for (const match of singleLetterMatches) {
54
65
  const letter = match[1].toLowerCase();
55
66
  const isCoverageContext = /coverage|summary|metrics|pct|percent/i.test(line) || /\.(?:statements|branches|functions|lines)\.pct/i.test(line);
@@ -63,7 +74,9 @@ function analyzeFileNaming(file, content, customAbbreviations, customShortWords,
63
74
  /[a-z]\s*=>/.test(line) || // s => on same line
64
75
  // Multi-line arrow function detection: look for pattern in context window
65
76
  new RegExp(`\\b${letter}\\s*\\)\\s*$`).test(line) && /=>/.test(contextWindow) || // (s)\n =>
66
- new RegExp(`\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`).test(lines[index - 1] || "") && /=>/.test(contextWindow);
77
+ new RegExp(
78
+ `\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`
79
+ ).test(lines[index - 1] || "") && /=>/.test(contextWindow);
67
80
  const isShortLived = isShortLivedVariable(letter, index);
68
81
  if (!isInLoopContext && !isI18nContext && !isArrowFunctionParam && !isShortLived && !["x", "y", "z", "i", "j", "k", "l", "n", "m"].includes(letter)) {
69
82
  if (isTestFile && ["a", "b", "c", "d", "e", "f", "s"].includes(letter)) {
@@ -81,7 +94,9 @@ function analyzeFileNaming(file, content, customAbbreviations, customShortWords,
81
94
  }
82
95
  }
83
96
  if (!disabledChecks.has("abbreviation")) {
84
- const abbreviationMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z]{1,3})(?=[A-Z]|_|\s*=)/g);
97
+ const abbreviationMatches = line.matchAll(
98
+ /\b(?:const|let|var)\s+([a-z]{1,3})(?=[A-Z]|_|\s*=)/g
99
+ );
85
100
  for (const match of abbreviationMatches) {
86
101
  const abbrev = match[1].toLowerCase();
87
102
  if (allShortWords.has(abbrev)) {
@@ -94,7 +109,9 @@ function analyzeFileNaming(file, content, customAbbreviations, customShortWords,
94
109
  new RegExp(`\\b${abbrev}\\s*=>`).test(line) || // s => on same line
95
110
  // Multi-line arrow function: check context window
96
111
  new RegExp(`\\b${abbrev}\\s*\\)\\s*$`).test(line) && /=>/.test(contextWindow) || // (s)\n =>
97
- new RegExp(`\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`).test(lines[index - 1] || "") && new RegExp(`^\\s*${abbrev}\\s*=>`).test(line);
112
+ new RegExp(
113
+ `\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`
114
+ ).test(lines[index - 1] || "") && new RegExp(`^\\s*${abbrev}\\s*=>`).test(line);
98
115
  if (isArrowFunctionParam) {
99
116
  continue;
100
117
  }
@@ -119,8 +136,13 @@ function analyzeFileNaming(file, content, customAbbreviations, customShortWords,
119
136
  }
120
137
  }
121
138
  if (!disabledChecks.has("convention-mix") && file.match(/\.(ts|tsx|js|jsx)$/)) {
122
- const camelCaseVars = line.match(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*=/);
123
- const snakeCaseVars = line.match(/\b(?:const|let|var)\s+([a-z][a-z0-9]*_[a-z0-9_]*)\s*=/);
139
+ const camelCaseVars = line.match(
140
+ /\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*=/
141
+ );
142
+ void camelCaseVars;
143
+ const snakeCaseVars = line.match(
144
+ /\b(?:const|let|var)\s+([a-z][a-z0-9]*_[a-z0-9_]*)\s*=/
145
+ );
124
146
  if (snakeCaseVars) {
125
147
  issues.push({
126
148
  file,
@@ -133,7 +155,9 @@ function analyzeFileNaming(file, content, customAbbreviations, customShortWords,
133
155
  }
134
156
  }
135
157
  if (!disabledChecks.has("unclear")) {
136
- const booleanMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*:\s*boolean/gi);
158
+ const booleanMatches = line.matchAll(
159
+ /\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*:\s*boolean/gi
160
+ );
137
161
  for (const match of booleanMatches) {
138
162
  const name = match[1];
139
163
  if (!name.match(/^(is|has|should|can|will|did)/i)) {
@@ -152,25 +176,59 @@ function analyzeFileNaming(file, content, customAbbreviations, customShortWords,
152
176
  const functionMatches = line.matchAll(/function\s+([a-z][a-zA-Z0-9]*)/g);
153
177
  for (const match of functionMatches) {
154
178
  const name = match[1];
155
- const isKeyword = ["for", "if", "else", "while", "do", "switch", "case", "break", "continue", "return", "throw", "try", "catch", "finally", "with", "yield", "await"].includes(name);
179
+ const isKeyword = [
180
+ "for",
181
+ "if",
182
+ "else",
183
+ "while",
184
+ "do",
185
+ "switch",
186
+ "case",
187
+ "break",
188
+ "continue",
189
+ "return",
190
+ "throw",
191
+ "try",
192
+ "catch",
193
+ "finally",
194
+ "with",
195
+ "yield",
196
+ "await"
197
+ ].includes(name);
156
198
  if (isKeyword) {
157
199
  continue;
158
200
  }
159
- const isEntryPoint = ["main", "init", "setup", "bootstrap"].includes(name);
201
+ const isEntryPoint = ["main", "init", "setup", "bootstrap"].includes(
202
+ name
203
+ );
160
204
  if (isEntryPoint) {
161
205
  continue;
162
206
  }
163
- const isFactoryPattern = name.match(/(Factory|Builder|Creator|Generator|Provider|Adapter|Mock)$/);
207
+ const isFactoryPattern = name.match(
208
+ /(Factory|Builder|Creator|Generator|Provider|Adapter|Mock)$/
209
+ );
164
210
  const isEventHandler = name.match(/^on[A-Z]/);
165
211
  const isDescriptiveLong = name.length > 15;
166
212
  const isReactHook = name.match(/^use[A-Z]/);
167
- const isDescriptivePattern = name.match(/^(default|total|count|sum|avg|max|min|initial|current|previous|next)\w+/) || name.match(/\w+(Count|Total|Sum|Average|List|Map|Set|Config|Settings|Options|Props|Data|Info|Details|State|Status|Response|Result)$/);
213
+ const isDescriptivePattern = name.match(
214
+ /^(default|total|count|sum|avg|max|min|initial|current|previous|next)\w+/
215
+ ) || name.match(
216
+ /\w+(Count|Total|Sum|Average|List|Map|Set|Config|Settings|Options|Props|Data|Info|Details|State|Status|Response|Result)$/
217
+ );
168
218
  const isHelperPattern = name.match(/^(to|from|with|without|for|as|into)\w+/) || // toMetadata, withLogger, forPath
169
219
  name.match(/^\w+(To|From|With|Without|For|As|Into)\w*$/);
170
- const isUtilityName = ["cn", "proxy", "sitemap", "robots", "gtag"].includes(name);
220
+ const isUtilityName = [
221
+ "cn",
222
+ "proxy",
223
+ "sitemap",
224
+ "robots",
225
+ "gtag"
226
+ ].includes(name);
171
227
  const capitalCount = (name.match(/[A-Z]/g) || []).length;
172
228
  const isCompoundWord = capitalCount >= 3;
173
- const hasActionVerb = name.match(/^(get|set|is|has|can|should|create|update|delete|fetch|load|save|process|handle|validate|check|find|search|filter|map|reduce|make|do|run|start|stop|build|parse|format|render|calculate|compute|generate|transform|convert|normalize|sanitize|encode|decode|compress|extract|merge|split|join|sort|compare|test|verify|ensure|apply|execute|invoke|call|emit|dispatch|trigger|listen|subscribe|unsubscribe|add|remove|clear|reset|toggle|enable|disable|open|close|connect|disconnect|send|receive|read|write|import|export|register|unregister|mount|unmount|track|store|persist|upsert|derive|classify|combine|discover|activate|require|assert|expect|mask|escape|sign|put|list|complete|page|safe|mock|pick|pluralize|text)/);
229
+ const hasActionVerb = name.match(
230
+ /^(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)/
231
+ );
174
232
  if (!hasActionVerb && !isFactoryPattern && !isEventHandler && !isDescriptiveLong && !isDescriptivePattern && !isCompoundWord && !isHelperPattern && !isUtilityName && !isReactHook) {
175
233
  issues.push({
176
234
  file,
@@ -188,10 +246,9 @@ function analyzeFileNaming(file, content, customAbbreviations, customShortWords,
188
246
  }
189
247
 
190
248
  // src/scoring.ts
191
- import {
192
- calculateProductivityImpact
193
- } from "@aiready/core";
249
+ import { calculateProductivityImpact } from "@aiready/core";
194
250
  function calculateConsistencyScore(issues, totalFilesAnalyzed, costConfig) {
251
+ void costConfig;
195
252
  const criticalIssues = issues.filter((i) => i.severity === "critical").length;
196
253
  const majorIssues = issues.filter((i) => i.severity === "major").length;
197
254
  const minorIssues = issues.filter((i) => i.severity === "minor").length;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiready/consistency",
3
- "version": "0.8.31",
3
+ "version": "0.8.34",
4
4
  "description": "Detects consistency issues in naming, patterns, and architecture that confuse AI models",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -43,7 +43,7 @@
43
43
  "@typescript-eslint/typescript-estree": "^8.53.0",
44
44
  "chalk": "^5.3.0",
45
45
  "commander": "^14.0.0",
46
- "@aiready/core": "0.9.32"
46
+ "@aiready/core": "0.9.35"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/node": "^24.0.0",
@@ -1,6 +1,5 @@
1
1
  import { describe, it, expect } from 'vitest';
2
2
  import { analyzeConsistency } from '../analyzer';
3
- import { analyzeNaming } from '../analyzers/naming';
4
3
  import { analyzePatterns } from '../analyzers/patterns';
5
4
 
6
5
  describe('analyzeConsistency', () => {
@@ -39,6 +38,8 @@ const x = 10;
39
38
  const y = 20;
40
39
  const result = x + y;
41
40
  `;
41
+ void testCode;
42
+ void 0;
42
43
  // In a real test, we'd create temp files or mock file reading
43
44
  // For now, this is a placeholder structure
44
45
  expect(true).toBe(true);
@@ -47,12 +48,45 @@ const result = x + y;
47
48
  it('should NOT flag acceptable abbreviations', () => {
48
49
  // These should all be acceptable and NOT flagged
49
50
  const acceptableAbbreviations = [
50
- 'env', 'req', 'res', 'ctx', 'err', 'api', 'url', 'id',
51
- 'max', 'min', 'now', 'utm', 'has', 'is', 'can',
52
- 'db', 'fs', 'os', 'ui', 'tmp', 'src', 'dst',
51
+ 'env',
52
+ 'req',
53
+ 'res',
54
+ 'ctx',
55
+ 'err',
56
+ 'api',
57
+ 'url',
58
+ 'id',
59
+ 'max',
60
+ 'min',
61
+ 'now',
62
+ 'utm',
63
+ 'has',
64
+ 'is',
65
+ 'can',
66
+ 'db',
67
+ 'fs',
68
+ 'os',
69
+ 'ui',
70
+ 'tmp',
71
+ 'src',
72
+ 'dst',
53
73
  // New additions from Phase 1
54
- 'img', 'txt', 'doc', 'md', 'ts', 'js', 'ddb', 's3',
55
- 'fcp', 'lcp', 'fps', 'po', 'dto', 'e2e', 'a11y', 'i18n'
74
+ 'img',
75
+ 'txt',
76
+ 'doc',
77
+ 'md',
78
+ 'ts',
79
+ 'js',
80
+ 'ddb',
81
+ 's3',
82
+ 'fcp',
83
+ 'lcp',
84
+ 'fps',
85
+ 'po',
86
+ 'dto',
87
+ 'e2e',
88
+ 'a11y',
89
+ 'i18n',
56
90
  ];
57
91
  // These abbreviations should not trigger warnings
58
92
  expect(acceptableAbbreviations.length).toBeGreaterThan(0);
@@ -61,8 +95,24 @@ const result = x + y;
61
95
  it('should NOT flag common short English words', () => {
62
96
  // Full words, not abbreviations - should be accepted
63
97
  const commonWords = [
64
- 'day', 'key', 'net', 'to', 'go', 'for', 'not', 'new', 'old',
65
- 'top', 'end', 'run', 'try', 'use', 'get', 'set', 'add', 'put'
98
+ 'day',
99
+ 'key',
100
+ 'net',
101
+ 'to',
102
+ 'go',
103
+ 'for',
104
+ 'not',
105
+ 'new',
106
+ 'old',
107
+ 'top',
108
+ 'end',
109
+ 'run',
110
+ 'try',
111
+ 'use',
112
+ 'get',
113
+ 'set',
114
+ 'add',
115
+ 'put',
66
116
  ];
67
117
  // These are full words and should not be flagged as abbreviations
68
118
  expect(commonWords.length).toBeGreaterThan(0);
@@ -73,6 +123,7 @@ const result = x + y;
73
123
  const user_name = 'John';
74
124
  const user_id = 123;
75
125
  `;
126
+ void testCode;
76
127
  // Test would check for convention-mix issues
77
128
  expect(true).toBe(true);
78
129
  });
@@ -82,6 +133,7 @@ const user_id = 123;
82
133
  const enabled: boolean = true;
83
134
  const active: boolean = false;
84
135
  `;
136
+ void testCode;
85
137
  // Should suggest prefixes like isEnabled, isActive
86
138
  expect(true).toBe(true);
87
139
  });
@@ -92,6 +144,7 @@ const id = '123';
92
144
  const url = 'https://example.com';
93
145
  const api = new ApiClient();
94
146
  `;
147
+ void testCode;
95
148
  // Should not flag these as issues
96
149
  expect(true).toBe(true);
97
150
  });
@@ -108,6 +161,7 @@ items.filter(
108
161
  item.valid
109
162
  )
110
163
  `;
164
+ void multiLineArrowCode;
111
165
  // 's' and 'item' should not be flagged as poor naming
112
166
  expect(true).toBe(true);
113
167
  });
@@ -119,6 +173,7 @@ const a = obj1;
119
173
  const b = obj2;
120
174
  return compare(a, b);
121
175
  `;
176
+ void shortLivedCode;
122
177
  // 'a' and 'b' should not be flagged as they're short-lived
123
178
  expect(true).toBe(true);
124
179
  });
@@ -13,10 +13,10 @@ describe('Language File Filtering', () => {
13
13
  '/path/to/file.tsx',
14
14
  '/path/to/file.js',
15
15
  '/path/to/file.jsx',
16
- '/path/to/file.py', // Should be filtered out
17
- '/path/to/file.java', // Should be filtered out
18
- '/path/to/file.rb', // Should be filtered out
19
- '/path/to/file.go', // Should be filtered out
16
+ '/path/to/file.py', // Should be filtered out
17
+ '/path/to/file.java', // Should be filtered out
18
+ '/path/to/file.rb', // Should be filtered out
19
+ '/path/to/file.go', // Should be filtered out
20
20
  ];
21
21
 
22
22
  // This test verifies that the function doesn't crash on non-JS/TS files
@@ -26,10 +26,7 @@ describe('Language File Filtering', () => {
26
26
  });
27
27
 
28
28
  it('should filter out Python files before parsing', async () => {
29
- const files = [
30
- '/path/to/script.py',
31
- '/path/to/another.py',
32
- ];
29
+ const files = ['/path/to/script.py', '/path/to/another.py'];
33
30
 
34
31
  // Should not attempt to parse Python files
35
32
  const result = await analyzeNamingAST(files);
@@ -42,7 +39,7 @@ describe('Language File Filtering', () => {
42
39
  'test.jsx',
43
40
  'test.ts',
44
41
  'test.tsx',
45
- 'TEST.JS', // Test case-insensitive
42
+ 'TEST.JS', // Test case-insensitive
46
43
  'TEST.TS',
47
44
  ];
48
45
 
@@ -52,8 +52,12 @@ describe('Consistency Scoring', () => {
52
52
  // score = 100 - 3 - 4 = 93
53
53
  expect(result.score).toBe(93);
54
54
  expect(result.rawMetrics.criticalIssues).toBe(2);
55
- expect(result.factors.some(f => f.name === 'Critical Issues')).toBe(true);
56
- expect(result.recommendations.some(r => r.priority === 'high')).toBe(true);
55
+ expect(result.factors.some((f) => f.name === 'Critical Issues')).toBe(
56
+ true
57
+ );
58
+ expect(result.recommendations.some((r) => r.priority === 'high')).toBe(
59
+ true
60
+ );
57
61
  });
58
62
 
59
63
  it('should apply weighted severity penalties', () => {
@@ -84,8 +88,14 @@ describe('Consistency Scoring', () => {
84
88
 
85
89
  it('should generate appropriate recommendations', () => {
86
90
  const issues: ConsistencyIssue[] = [
87
- ...Array(3).fill({ severity: 'critical', category: 'naming' } as ConsistencyIssue),
88
- ...Array(10).fill({ severity: 'major', category: 'patterns' } as ConsistencyIssue),
91
+ ...Array(3).fill({
92
+ severity: 'critical',
93
+ category: 'naming',
94
+ } as ConsistencyIssue),
95
+ ...Array(10).fill({
96
+ severity: 'major',
97
+ category: 'patterns',
98
+ } as ConsistencyIssue),
89
99
  ];
90
100
 
91
101
  const result = calculateConsistencyScore(
@@ -98,8 +108,12 @@ describe('Consistency Scoring', () => {
98
108
  );
99
109
 
100
110
  expect(result.recommendations.length).toBeGreaterThan(0);
101
- expect(result.recommendations.some(r => r.action.includes('critical'))).toBe(true);
102
- expect(result.recommendations.some(r => r.action.includes('naming'))).toBe(true);
111
+ expect(
112
+ result.recommendations.some((r) => r.action.includes('critical'))
113
+ ).toBe(true);
114
+ expect(
115
+ result.recommendations.some((r) => r.action.includes('naming'))
116
+ ).toBe(true);
103
117
  });
104
118
 
105
119
  it('should handle edge case of zero files', () => {
@@ -121,14 +135,25 @@ describe('Consistency Scoring', () => {
121
135
 
122
136
  const result = calculateConsistencyScore(issues, 10);
123
137
 
124
- expect(result.recommendations.some(r => r.action.includes('linter'))).toBe(true);
138
+ expect(
139
+ result.recommendations.some((r) => r.action.includes('linter'))
140
+ ).toBe(true);
125
141
  });
126
142
 
127
143
  it('should combine all penalty types', () => {
128
144
  const issues: ConsistencyIssue[] = [
129
- ...Array(10).fill({ severity: 'critical', category: 'naming' } as ConsistencyIssue),
130
- ...Array(20).fill({ severity: 'major', category: 'patterns' } as ConsistencyIssue),
131
- ...Array(40).fill({ severity: 'minor', category: 'naming' } as ConsistencyIssue),
145
+ ...Array(10).fill({
146
+ severity: 'critical',
147
+ category: 'naming',
148
+ } as ConsistencyIssue),
149
+ ...Array(20).fill({
150
+ severity: 'major',
151
+ category: 'patterns',
152
+ } as ConsistencyIssue),
153
+ ...Array(40).fill({
154
+ severity: 'minor',
155
+ category: 'naming',
156
+ } as ConsistencyIssue),
132
157
  ];
133
158
 
134
159
  const result = calculateConsistencyScore(
package/src/analyzer.ts CHANGED
@@ -1,6 +1,10 @@
1
1
  import { scanFiles } from '@aiready/core';
2
2
  import type { AnalysisResult, Issue } from '@aiready/core';
3
- import type { ConsistencyOptions, ConsistencyReport, ConsistencyIssue } from './types';
3
+ import type {
4
+ ConsistencyOptions,
5
+ ConsistencyReport,
6
+ ConsistencyIssue,
7
+ } from './types';
4
8
  import { analyzeNamingAST } from './analyzers/naming-ast';
5
9
  import { analyzePythonNaming } from './analyzers/naming-python';
6
10
  import { analyzePatterns } from './analyzers/patterns';
@@ -20,21 +24,26 @@ export async function analyzeConsistency(
20
24
  ...scanOptions
21
25
  } = options;
22
26
 
27
+ // Mark intentionally-unused option to avoid lint warnings
28
+ void checkArchitecture;
29
+
23
30
  // Scan files
24
31
  const filePaths = await scanFiles(scanOptions);
25
32
 
26
33
  // Separate files by language
27
- const tsJsFiles = filePaths.filter(f => /\.(ts|tsx|js|jsx)$/i.test(f));
28
- const pythonFiles = filePaths.filter(f => /\.py$/i.test(f));
34
+ const tsJsFiles = filePaths.filter((f) => /\.(ts|tsx|js|jsx)$/i.test(f));
35
+ const pythonFiles = filePaths.filter((f) => /\.py$/i.test(f));
29
36
 
30
37
  // Collect issues by category - now handles multiple languages
31
38
  let namingIssues: any[] = [];
32
39
  if (checkNaming) {
33
- const tsJsNamingIssues = tsJsFiles.length > 0 ? await analyzeNamingAST(tsJsFiles) : [];
34
- const pythonNamingIssues = pythonFiles.length > 0 ? await analyzePythonNaming(pythonFiles) : [];
40
+ const tsJsNamingIssues =
41
+ tsJsFiles.length > 0 ? await analyzeNamingAST(tsJsFiles) : [];
42
+ const pythonNamingIssues =
43
+ pythonFiles.length > 0 ? await analyzePythonNaming(pythonFiles) : [];
35
44
  namingIssues = [...tsJsNamingIssues, ...pythonNamingIssues];
36
45
  }
37
-
46
+
38
47
  const patternIssues = checkPatterns ? await analyzePatterns(filePaths) : [];
39
48
 
40
49
  // Convert to AnalysisResult format
@@ -48,16 +57,19 @@ export async function analyzeConsistency(
48
57
  }
49
58
 
50
59
  const consistencyIssue: ConsistencyIssue = {
51
- type: issue.type === 'convention-mix' ? 'naming-inconsistency' : 'naming-quality',
60
+ type:
61
+ issue.type === 'convention-mix'
62
+ ? 'naming-inconsistency'
63
+ : 'naming-quality',
52
64
  category: 'naming',
53
65
  severity: issue.severity,
54
66
  message: `${issue.type}: ${issue.identifier}`,
55
67
  location: {
56
68
  file: issue.file,
57
69
  line: issue.line,
58
- column: issue.column
70
+ column: issue.column,
59
71
  },
60
- suggestion: issue.suggestion
72
+ suggestion: issue.suggestion,
61
73
  };
62
74
 
63
75
  if (!fileIssuesMap.has(issue.file)) {
@@ -79,10 +91,10 @@ export async function analyzeConsistency(
79
91
  message: issue.description,
80
92
  location: {
81
93
  file: issue.files[0] || 'multiple files',
82
- line: 1
94
+ line: 1,
83
95
  },
84
96
  examples: issue.examples,
85
- suggestion: `Standardize ${issue.type} patterns across ${issue.files.length} files`
97
+ suggestion: `Standardize ${issue.type} patterns across ${issue.files.length} files`,
86
98
  };
87
99
 
88
100
  // Add to first file in the pattern
@@ -101,28 +113,32 @@ export async function analyzeConsistency(
101
113
  fileName,
102
114
  issues: issues as Issue[],
103
115
  metrics: {
104
- consistencyScore: calculateConsistencyScore(issues)
105
- }
116
+ consistencyScore: calculateConsistencyScore(issues),
117
+ },
106
118
  });
107
119
  }
108
120
 
109
121
  // Sort results by severity first, then by issue count per file
110
122
  results.sort((fileResultA, fileResultB) => {
111
123
  const severityOrder = { critical: 0, major: 1, minor: 2, info: 3 };
112
-
124
+
113
125
  // Get highest severity in each file
114
126
  const maxSeverityA = Math.min(
115
- ...fileResultA.issues.map(i => severityOrder[(i as ConsistencyIssue).severity])
127
+ ...fileResultA.issues.map(
128
+ (i) => severityOrder[(i as ConsistencyIssue).severity]
129
+ )
116
130
  );
117
131
  const maxSeverityB = Math.min(
118
- ...fileResultB.issues.map(i => severityOrder[(i as ConsistencyIssue).severity])
132
+ ...fileResultB.issues.map(
133
+ (i) => severityOrder[(i as ConsistencyIssue).severity]
134
+ )
119
135
  );
120
-
136
+
121
137
  // Sort by severity first
122
138
  if (maxSeverityA !== maxSeverityB) {
123
139
  return maxSeverityA - maxSeverityB;
124
140
  }
125
-
141
+
126
142
  // Then by issue count (descending)
127
143
  return fileResultB.issues.length - fileResultA.issues.length;
128
144
  });
@@ -131,8 +147,12 @@ export async function analyzeConsistency(
131
147
  const recommendations = generateRecommendations(namingIssues, patternIssues);
132
148
 
133
149
  // Compute filtered counts (respecting minSeverity) to report accurate summary
134
- const namingCountFiltered = namingIssues.filter(i => shouldIncludeSeverity(i.severity, minSeverity)).length;
135
- const patternCountFiltered = patternIssues.filter(i => shouldIncludeSeverity(i.severity, minSeverity)).length;
150
+ const namingCountFiltered = namingIssues.filter((i) =>
151
+ shouldIncludeSeverity(i.severity, minSeverity)
152
+ ).length;
153
+ const patternCountFiltered = patternIssues.filter((i) =>
154
+ shouldIncludeSeverity(i.severity, minSeverity)
155
+ ).length;
136
156
 
137
157
  // Detect naming conventions (TODO: re-implement for AST version)
138
158
  // const conventionAnalysis = detectNamingConventions(filePaths, namingIssues);
@@ -143,10 +163,10 @@ export async function analyzeConsistency(
143
163
  namingIssues: namingCountFiltered,
144
164
  patternIssues: patternCountFiltered,
145
165
  architectureIssues: 0,
146
- filesAnalyzed: filePaths.length
166
+ filesAnalyzed: filePaths.length,
147
167
  },
148
168
  results,
149
- recommendations
169
+ recommendations,
150
170
  };
151
171
  }
152
172
 
@@ -161,7 +181,10 @@ function shouldIncludeSeverity(
161
181
  function calculateConsistencyScore(issues: ConsistencyIssue[]): number {
162
182
  // Higher score = more consistent (fewer issues)
163
183
  const weights = { critical: 10, major: 5, minor: 2, info: 1 };
164
- const totalWeight = issues.reduce((sum, issue) => sum + weights[issue.severity], 0);
184
+ const totalWeight = issues.reduce(
185
+ (sum, issue) => sum + weights[issue.severity],
186
+ 0
187
+ );
165
188
  // Score from 0-1, where 1 is perfect
166
189
  return Math.max(0, 1 - totalWeight / 100);
167
190
  }
@@ -173,14 +196,18 @@ function generateRecommendations(
173
196
  const recommendations: string[] = [];
174
197
 
175
198
  if (namingIssues.length > 0) {
176
- const conventionMixCount = namingIssues.filter(i => i.type === 'convention-mix').length;
199
+ const conventionMixCount = namingIssues.filter(
200
+ (i) => i.type === 'convention-mix'
201
+ ).length;
177
202
  if (conventionMixCount > 0) {
178
203
  recommendations.push(
179
204
  `Standardize naming conventions: Found ${conventionMixCount} snake_case variables in TypeScript/JavaScript (use camelCase)`
180
205
  );
181
206
  }
182
207
 
183
- const poorNamingCount = namingIssues.filter(i => i.type === 'poor-naming').length;
208
+ const poorNamingCount = namingIssues.filter(
209
+ (i) => i.type === 'poor-naming'
210
+ ).length;
184
211
  if (poorNamingCount > 0) {
185
212
  recommendations.push(
186
213
  `Improve variable naming: Found ${poorNamingCount} single-letter or unclear variable names`
@@ -189,21 +216,23 @@ function generateRecommendations(
189
216
  }
190
217
 
191
218
  if (patternIssues.length > 0) {
192
- const errorHandlingIssues = patternIssues.filter(i => i.type === 'error-handling');
219
+ const errorHandlingIssues = patternIssues.filter(
220
+ (i) => i.type === 'error-handling'
221
+ );
193
222
  if (errorHandlingIssues.length > 0) {
194
223
  recommendations.push(
195
224
  'Standardize error handling strategy across the codebase (prefer try-catch with typed errors)'
196
225
  );
197
226
  }
198
227
 
199
- const asyncIssues = patternIssues.filter(i => i.type === 'async-style');
228
+ const asyncIssues = patternIssues.filter((i) => i.type === 'async-style');
200
229
  if (asyncIssues.length > 0) {
201
230
  recommendations.push(
202
231
  'Use async/await consistently instead of mixing with promise chains or callbacks'
203
232
  );
204
233
  }
205
234
 
206
- const importIssues = patternIssues.filter(i => i.type === 'import-style');
235
+ const importIssues = patternIssues.filter((i) => i.type === 'import-style');
207
236
  if (importIssues.length > 0) {
208
237
  recommendations.push(
209
238
  'Use ES modules consistently across the project (avoid mixing with CommonJS)'
@@ -212,7 +241,9 @@ function generateRecommendations(
212
241
  }
213
242
 
214
243
  if (recommendations.length === 0) {
215
- recommendations.push('No major consistency issues found! Your codebase follows good practices.');
244
+ recommendations.push(
245
+ 'No major consistency issues found! Your codebase follows good practices.'
246
+ );
216
247
  }
217
248
 
218
249
  return recommendations;