@aiready/consistency 0.20.2 → 0.20.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/src/analyzer.ts CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  scanFiles,
3
3
  Severity,
4
4
  IssueType,
5
- GLOBAL_SCAN_OPTIONS,
5
+ getSeverityLevel,
6
6
  } from '@aiready/core';
7
7
  import type { AnalysisResult, Issue } from '@aiready/core';
8
8
  import type {
@@ -58,149 +58,70 @@ export async function analyzeConsistency(
58
58
 
59
59
  // Process naming issues
60
60
  for (const issue of namingIssues) {
61
- if (!shouldIncludeSeverity(issue.severity, minSeverity)) {
62
- continue;
63
- }
64
-
65
- const consistencyIssue: ConsistencyIssue = {
66
- type:
67
- issue.type === 'convention-mix'
68
- ? IssueType.NamingInconsistency
69
- : IssueType.NamingQuality,
70
- category: 'naming',
71
- severity: getSeverityEnum(issue.severity),
72
- message: `${issue.type}: ${issue.identifier}`,
73
- location: {
74
- file: issue.file,
75
- line: issue.line,
76
- column: issue.column,
77
- },
78
- suggestion: issue.suggestion,
79
- };
80
-
81
- if (!fileIssuesMap.has(issue.file)) {
82
- fileIssuesMap.set(issue.file, []);
83
- }
84
- fileIssuesMap.get(issue.file)!.push(consistencyIssue);
61
+ if (!shouldIncludeSeverity(issue.severity, minSeverity)) continue;
62
+
63
+ const fileName =
64
+ (issue as any).fileName ||
65
+ (issue as any).file ||
66
+ (issue as any).filePath ||
67
+ 'unknown';
68
+ if (!fileIssuesMap.has(fileName)) fileIssuesMap.set(fileName, []);
69
+ fileIssuesMap.get(fileName)!.push(issue as unknown as ConsistencyIssue);
85
70
  }
86
71
 
87
72
  // Process pattern issues
88
73
  for (const issue of patternIssues) {
89
- if (!shouldIncludeSeverity(issue.severity, minSeverity)) {
90
- continue;
91
- }
92
-
93
- const consistencyIssue: ConsistencyIssue = {
94
- type: IssueType.PatternInconsistency,
95
- category: 'patterns',
96
- severity: getSeverityEnum(issue.severity),
97
- message: issue.description,
98
- location: {
99
- file: issue.files[0] || 'multiple files',
100
- line: 1,
101
- },
102
- examples: issue.examples,
103
- suggestion: `Standardize ${issue.type} patterns across ${issue.files.length} files`,
104
- };
105
-
106
- // Add to first file in the pattern
107
- const firstFile = issue.files[0];
108
- if (firstFile && !fileIssuesMap.has(firstFile)) {
109
- fileIssuesMap.set(firstFile, []);
110
- }
111
- if (firstFile) {
112
- fileIssuesMap.get(firstFile)!.push(consistencyIssue);
113
- }
74
+ if (!shouldIncludeSeverity(issue.severity, minSeverity)) continue;
75
+
76
+ const fileName =
77
+ (issue as any).fileName ||
78
+ (issue as any).file ||
79
+ (issue as any).filePath ||
80
+ (Array.isArray((issue as any).files)
81
+ ? (issue as any).files[0]
82
+ : 'unknown');
83
+ if (!fileIssuesMap.has(fileName)) fileIssuesMap.set(fileName, []);
84
+ fileIssuesMap.get(fileName)!.push(issue as unknown as ConsistencyIssue);
114
85
  }
115
86
 
116
- // Convert to AnalysisResult format
117
- for (const [fileName, issues] of fileIssuesMap) {
87
+ // Build final results
88
+ for (const [fileName, issues] of fileIssuesMap.entries()) {
118
89
  results.push({
119
90
  fileName,
120
- issues: issues as Issue[],
91
+ issues: issues.map((i) => transformToIssue(i)),
121
92
  metrics: {
122
93
  consistencyScore: calculateConsistencyScore(issues),
123
94
  },
124
95
  });
125
96
  }
126
97
 
127
- // Sort results by severity first, then by issue count per file
128
- results.sort((fileResultA, fileResultB) => {
129
- // Get highest severity in each file
130
- const maxSeverityA = Math.min(
131
- ...fileResultA.issues.map((i) => {
132
- const val = getSeverityLevel((i as ConsistencyIssue).severity);
133
- // Map 4->0, 3->1, 2->2, 1->3
134
- return val === 4 ? 0 : val === 3 ? 1 : val === 2 ? 2 : 3;
135
- })
136
- );
137
- const maxSeverityB = Math.min(
138
- ...fileResultB.issues.map((i) => {
139
- const val = getSeverityLevel((i as ConsistencyIssue).severity);
140
- return val === 4 ? 0 : val === 3 ? 1 : val === 2 ? 2 : 3;
141
- })
142
- );
143
-
144
- // Sort by severity first
145
- if (maxSeverityA !== maxSeverityB) {
146
- return maxSeverityA - maxSeverityB;
147
- }
148
-
149
- // Then by issue count (descending)
150
- return fileResultB.issues.length - fileResultA.issues.length;
151
- });
152
-
153
- // Generate recommendations
154
- const recommendations = generateRecommendations(namingIssues, patternIssues);
155
-
156
- // Compute filtered counts (respecting minSeverity) to report accurate summary
157
- const namingCountFiltered = namingIssues.filter((i) =>
158
- shouldIncludeSeverity(i.severity, minSeverity)
159
- ).length;
160
- const patternCountFiltered = patternIssues.filter((i) =>
161
- shouldIncludeSeverity(i.severity, minSeverity)
162
- ).length;
98
+ // Generate high-level recommendations
99
+ const recommendations: string[] = [];
100
+ if (namingIssues.length > 0) {
101
+ recommendations.push('Standardize naming conventions across the codebase');
102
+ }
103
+ if (patternIssues.length > 0) {
104
+ recommendations.push('Consolidate repetitive implementation patterns');
105
+ }
106
+ if (results.some((r) => (r.metrics?.consistencyScore ?? 1) < 0.8)) {
107
+ recommendations.push('Improve cross-module consistency to reduce AI confusion');
108
+ }
163
109
 
164
110
  return {
111
+ results,
165
112
  summary: {
166
- totalIssues: namingCountFiltered + patternCountFiltered,
167
- namingIssues: namingCountFiltered,
168
- patternIssues: patternCountFiltered,
169
- architectureIssues: 0,
170
113
  filesAnalyzed: filePaths.length,
171
- config: Object.fromEntries(
172
- Object.entries(options).filter(
173
- ([key]) => !GLOBAL_SCAN_OPTIONS.includes(key) || key === 'rootDir'
174
- )
175
- ),
114
+ totalIssues: results.reduce((acc, r) => acc + r.issues.length, 0),
115
+ namingIssues: namingIssues.length,
116
+ patternIssues: patternIssues.length,
117
+ architectureIssues: 0,
176
118
  },
177
- results,
178
119
  recommendations,
179
- };
180
- }
181
-
182
- function getSeverityLevel(s: any): number {
183
- if (s === Severity.Critical || s === 'critical') return 4;
184
- if (s === Severity.Major || s === 'major') return 3;
185
- if (s === Severity.Minor || s === 'minor') return 2;
186
- if (s === Severity.Info || s === 'info') return 1;
187
- return 0;
188
- }
189
-
190
- function getSeverityEnum(s: any): Severity {
191
- const val = getSeverityLevel(s);
192
- switch (val) {
193
- case 4:
194
- return Severity.Critical;
195
- case 3:
196
- return Severity.Major;
197
- case 2:
198
- return Severity.Minor;
199
- case 1:
200
- return Severity.Info;
201
- default:
202
- return Severity.Info;
203
- }
120
+ metadata: {
121
+ toolName: 'naming-consistency',
122
+ timestamp: new Date().toISOString(),
123
+ },
124
+ } as unknown as ConsistencyReport;
204
125
  }
205
126
 
206
127
  function shouldIncludeSeverity(
@@ -210,6 +131,91 @@ function shouldIncludeSeverity(
210
131
  return getSeverityLevel(severity) >= getSeverityLevel(minSeverity);
211
132
  }
212
133
 
134
+ /**
135
+ * Map string type to IssueType enum value
136
+ */
137
+ function getIssueType(type: string | undefined): IssueType {
138
+ if (!type) return IssueType.NamingInconsistency;
139
+
140
+ // Map string values to enum
141
+ const typeMap: Record<string, IssueType> = {
142
+ 'naming-inconsistency': IssueType.NamingInconsistency,
143
+ 'naming-quality': IssueType.NamingQuality,
144
+ 'pattern-inconsistency': IssueType.PatternInconsistency,
145
+ 'architecture-inconsistency': IssueType.ArchitectureInconsistency,
146
+ 'error-handling': IssueType.PatternInconsistency,
147
+ 'async-style': IssueType.PatternInconsistency,
148
+ 'import-style': IssueType.PatternInconsistency,
149
+ 'api-design': IssueType.PatternInconsistency,
150
+ };
151
+
152
+ return typeMap[type] || IssueType.NamingInconsistency;
153
+ }
154
+
155
+ /**
156
+ * Transform NamingIssue or PatternIssue to the required Issue format
157
+ */
158
+ function transformToIssue(i: any): Issue {
159
+ // If already has message and location, return as is
160
+ if (i.message && i.location) {
161
+ return {
162
+ type: getIssueType(i.type),
163
+ severity: i.severity as Severity,
164
+ message: i.message,
165
+ location: i.location,
166
+ suggestion: i.suggestion,
167
+ };
168
+ }
169
+
170
+ // Handle NamingIssue format (has file, line, column, identifier, suggestion)
171
+ if (i.identifier || i.type) {
172
+ const line = i.line || 1;
173
+ const column = i.column || 1;
174
+ return {
175
+ type: getIssueType(i.type),
176
+ severity: i.severity as Severity,
177
+ message: i.suggestion
178
+ ? `Naming issue: ${i.suggestion}`
179
+ : `Naming issue for '${i.identifier || 'unknown'}'`,
180
+ location: {
181
+ file: i.file || i.fileName || '',
182
+ line,
183
+ column,
184
+ endLine: line,
185
+ endColumn: column + (i.identifier?.length || 10),
186
+ },
187
+ suggestion: i.suggestion,
188
+ };
189
+ }
190
+
191
+ // Handle PatternIssue format (has description, files)
192
+ if (i.description || i.files) {
193
+ const fileName = Array.isArray(i.files) ? i.files[0] : i.file || '';
194
+ return {
195
+ type: getIssueType(i.type),
196
+ severity: i.severity as Severity,
197
+ message: i.description || 'Pattern inconsistency found',
198
+ location: {
199
+ file: fileName,
200
+ line: 1,
201
+ column: 1,
202
+ endLine: 1,
203
+ endColumn: 10,
204
+ },
205
+ suggestion: i.examples?.[0],
206
+ };
207
+ }
208
+
209
+ // Fallback
210
+ return {
211
+ type: getIssueType(i.type),
212
+ severity: i.severity as Severity,
213
+ message: i.message || 'Unknown issue',
214
+ location: i.location || { file: '', line: 1, column: 1 },
215
+ suggestion: i.suggestion,
216
+ };
217
+ }
218
+
213
219
  function calculateConsistencyScore(issues: ConsistencyIssue[]): number {
214
220
  let totalWeight = 0;
215
221
  for (const issue of issues) {
@@ -234,63 +240,3 @@ function calculateConsistencyScore(issues: ConsistencyIssue[]): number {
234
240
  // Score from 0-1, where 1 is perfect
235
241
  return Math.max(0, 1 - totalWeight / 100);
236
242
  }
237
-
238
- function generateRecommendations(
239
- namingIssues: any[],
240
- patternIssues: any[]
241
- ): string[] {
242
- const recommendations: string[] = [];
243
-
244
- if (namingIssues.length > 0) {
245
- const conventionMixCount = namingIssues.filter(
246
- (i) => i.type === 'convention-mix'
247
- ).length;
248
- if (conventionMixCount > 0) {
249
- recommendations.push(
250
- `Standardize naming conventions: Found ${conventionMixCount} snake_case variables in TypeScript/JavaScript (use camelCase)`
251
- );
252
- }
253
-
254
- const poorNamingCount = namingIssues.filter(
255
- (i) => i.type === 'poor-naming'
256
- ).length;
257
- if (poorNamingCount > 0) {
258
- recommendations.push(
259
- `Improve variable naming: Found ${poorNamingCount} single-letter or unclear variable names`
260
- );
261
- }
262
- }
263
-
264
- if (patternIssues.length > 0) {
265
- const errorHandlingIssues = patternIssues.filter(
266
- (i) => i.type === 'error-handling'
267
- );
268
- if (errorHandlingIssues.length > 0) {
269
- recommendations.push(
270
- 'Standardize error handling strategy across the codebase (prefer try-catch with typed errors)'
271
- );
272
- }
273
-
274
- const asyncIssues = patternIssues.filter((i) => i.type === 'async-style');
275
- if (asyncIssues.length > 0) {
276
- recommendations.push(
277
- 'Use async/await consistently instead of mixing with promise chains or callbacks'
278
- );
279
- }
280
-
281
- const importIssues = patternIssues.filter((i) => i.type === 'import-style');
282
- if (importIssues.length > 0) {
283
- recommendations.push(
284
- 'Use ES modules consistently across the project (avoid mixing with CommonJS)'
285
- );
286
- }
287
- }
288
-
289
- if (recommendations.length === 0) {
290
- recommendations.push(
291
- 'No major consistency issues found! Your codebase follows good practices.'
292
- );
293
- }
294
-
295
- return recommendations;
296
- }
@@ -4,9 +4,7 @@ import type { NamingIssue } from '../types';
4
4
  import {
5
5
  parseFile,
6
6
  traverseAST,
7
- getFunctionName,
8
7
  getLineNumber,
9
- isCoverageContext,
10
8
  isLoopStatement,
11
9
  } from '../utils/ast-parser';
12
10
  import {
@@ -154,7 +152,7 @@ function checkVariableNaming(
154
152
  issues: NamingIssue[],
155
153
  context: any
156
154
  ) {
157
- const { name, node, line, options } = varInfo;
155
+ const { name, line, options } = varInfo;
158
156
 
159
157
  // Skip very common small names if they are in acceptable context
160
158
  if (isAcceptableInContext(name, context, options)) {
@@ -253,6 +251,13 @@ class ScopeTracker {
253
251
  }
254
252
  }
255
253
 
254
+ /**
255
+ * Extracts identifiers from destructured patterns (object or array destructuring)
256
+ * and registers them in the scope tracker.
257
+ * @param node - The AST node representing the destructured pattern
258
+ * @param isParameter - When true, indicates the destructured variable is a function parameter; when false, it's a local variable
259
+ * @param scopeTracker - The scope tracker to register variables with
260
+ */
256
261
  function extractDestructuredIdentifiers(
257
262
  node: TSESTree.ObjectPattern | TSESTree.ArrayPattern,
258
263
  isParameter: boolean,
@@ -5,7 +5,7 @@
5
5
  * naming conventions across all supported languages.
6
6
  */
7
7
 
8
- import { getParser, Severity, IssueType } from '@aiready/core';
8
+ import { getParser, Severity } from '@aiready/core';
9
9
  import type { NamingIssue } from '../types';
10
10
  import { readFileSync } from 'fs';
11
11
 
@@ -31,7 +31,6 @@ export async function analyzeNamingGeneralized(
31
31
  // 1. Check Exports
32
32
  for (const exp of result.exports) {
33
33
  let pattern: RegExp | undefined;
34
- const typeName = exp.type;
35
34
 
36
35
  if (exp.type === 'class') {
37
36
  pattern = conventions.classPattern;
@@ -49,7 +48,7 @@ export async function analyzeNamingGeneralized(
49
48
 
50
49
  if (pattern && !pattern.test(exp.name)) {
51
50
  issues.push({
52
- type: 'poor-naming',
51
+ type: 'naming-inconsistency',
53
52
  identifier: exp.name,
54
53
  file,
55
54
  line: exp.loc?.start.line || 1,
@@ -72,7 +71,7 @@ export async function analyzeNamingGeneralized(
72
71
  ) {
73
72
  // This is often a 'convention-mix' issue (e.g. importing snake_case into camelCase project)
74
73
  issues.push({
75
- type: 'convention-mix',
74
+ type: 'naming-inconsistency',
76
75
  identifier: spec,
77
76
  file,
78
77
  line: imp.loc?.start.line || 1,
@@ -5,6 +5,8 @@ import type { NamingIssue } from '../types';
5
5
  /**
6
6
  * Legacy regex-based naming analyzer
7
7
  * (Used as fallback or for languages without AST support)
8
+ * @param filePaths - Array of file paths to analyze
9
+ * @returns Array of naming issues found
8
10
  */
9
11
  export async function analyzeNaming(
10
12
  filePaths: string[]
@@ -13,7 +13,6 @@ export async function analyzePatterns(
13
13
 
14
14
  // 1. Error handling style
15
15
  const tryCatchPattern = /try\s*\{/g;
16
- const catchPattern = /catch\s*\(\s*(\w+)\s*\)/g;
17
16
 
18
17
  const styleStats = {
19
18
  tryCatch: 0,
@@ -39,8 +38,6 @@ export async function analyzePatterns(
39
38
  }
40
39
  }
41
40
 
42
- const totalFiles = filePaths.length;
43
-
44
41
  // Report inconsistencies if there's a significant mix
45
42
  if (styleStats.tryCatch > 0 && styleStats.thenCatch > 0) {
46
43
  const dominant =
@@ -54,7 +51,7 @@ export async function analyzePatterns(
54
51
  ? c.match(tryCatchPattern)
55
52
  : c.match(/\.catch\s*\(/);
56
53
  }),
57
- type: 'error-handling',
54
+ type: 'pattern-inconsistency',
58
55
  description: `Mixed error handling styles: codebase primarily uses ${dominant}, but found ${minority} in some files.`,
59
56
  examples: [dominant, minority],
60
57
  severity: Severity.Minor,
@@ -73,7 +70,7 @@ export async function analyzePatterns(
73
70
  ? c.match(/\brequire\s*\(/)
74
71
  : c.match(/\bimport\b/);
75
72
  }),
76
- type: 'import-style',
73
+ type: 'pattern-inconsistency',
77
74
  description: `Mixed module systems: found both ESM and CommonJS.`,
78
75
  examples: ['import X from "y"', 'const X = require("y")'],
79
76
  severity: Severity.Major,
package/src/types.ts CHANGED
@@ -43,7 +43,12 @@ export interface NamingIssue {
43
43
 
44
44
  export interface PatternIssue {
45
45
  files: string[];
46
- type: 'error-handling' | 'async-style' | 'import-style' | 'api-design';
46
+ type:
47
+ | 'error-handling'
48
+ | 'async-style'
49
+ | 'import-style'
50
+ | 'api-design'
51
+ | 'pattern-inconsistency';
47
52
  description: string;
48
53
  examples: string[];
49
54
  severity: Severity;