@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/src/cli.ts CHANGED
@@ -5,16 +5,24 @@ import { analyzeConsistency } from './analyzer';
5
5
  import type { ConsistencyOptions } from './types';
6
6
  import chalk from 'chalk';
7
7
  import { writeFileSync, mkdirSync, existsSync } from 'fs';
8
- import { join, dirname } from 'path';
9
- import { loadConfig, mergeConfigWithDefaults, resolveOutputPath } from '@aiready/core';
8
+ import { dirname } from 'path';
9
+ import {
10
+ loadConfig,
11
+ mergeConfigWithDefaults,
12
+ resolveOutputPath,
13
+ } from '@aiready/core';
10
14
 
11
15
  const program = new Command();
12
16
 
13
17
  program
14
18
  .name('aiready-consistency')
15
- .description('Detect consistency patterns in naming, code structure, and architecture')
19
+ .description(
20
+ 'Detect consistency patterns in naming, code structure, and architecture'
21
+ )
16
22
  .version('0.1.0')
17
- .addHelpText('after', `
23
+ .addHelpText(
24
+ 'after',
25
+ `
18
26
  LANGUAGE SUPPORT:
19
27
  Supported: TypeScript (.ts, .tsx), JavaScript (.js, .jsx)
20
28
  Note: Python, Java, and other language files will be safely ignored
@@ -33,17 +41,28 @@ EXAMPLES:
33
41
  aiready-consistency . --no-naming # Skip naming checks
34
42
  aiready-consistency . --min-severity major # Only show major+ patterns
35
43
  aiready-consistency . --output json > report.json # JSON export
36
- `)
44
+ `
45
+ )
37
46
  .argument('<directory>', 'Directory to analyze')
38
47
  .option('--naming', 'Check naming conventions and quality (default: true)')
39
48
  .option('--no-naming', 'Skip naming analysis')
40
49
  .option('--patterns', 'Check code pattern consistency (default: true)')
41
50
  .option('--no-patterns', 'Skip pattern analysis')
42
- .option('--architecture', 'Check architectural consistency (not yet implemented)')
43
- .option('--min-severity <level>', 'Minimum severity: info|minor|major|critical. Default: info')
51
+ .option(
52
+ '--architecture',
53
+ 'Check architectural consistency (not yet implemented)'
54
+ )
55
+ .option(
56
+ '--min-severity <level>',
57
+ 'Minimum severity: info|minor|major|critical. Default: info'
58
+ )
44
59
  .option('--include <patterns>', 'File patterns to include (comma-separated)')
45
60
  .option('--exclude <patterns>', 'File patterns to exclude (comma-separated)')
46
- .option('-o, --output <format>', 'Output format: console|json|markdown', 'console')
61
+ .option(
62
+ '-o, --output <format>',
63
+ 'Output format: console|json|markdown',
64
+ 'console'
65
+ )
47
66
  .option('--output-file <path>', 'Output file path (for json/markdown)')
48
67
  .action(async (directory, options) => {
49
68
  console.log(chalk.blue('🔍 Analyzing consistency...\n'));
@@ -89,12 +108,12 @@ EXAMPLES:
89
108
  `consistency-report-${new Date().toISOString().split('T')[0]}.json`,
90
109
  directory
91
110
  );
92
-
111
+
93
112
  const dir = dirname(outputPath);
94
113
  if (!existsSync(dir)) {
95
114
  mkdirSync(dir, { recursive: true });
96
115
  }
97
-
116
+
98
117
  writeFileSync(outputPath, output);
99
118
  console.log(chalk.green(`✓ Report saved to ${outputPath}`));
100
119
  } else if (options.output === 'markdown') {
@@ -104,12 +123,12 @@ EXAMPLES:
104
123
  `consistency-report-${new Date().toISOString().split('T')[0]}.md`,
105
124
  directory
106
125
  );
107
-
126
+
108
127
  const dir = dirname(outputPath);
109
128
  if (!existsSync(dir)) {
110
129
  mkdirSync(dir, { recursive: true });
111
130
  }
112
-
131
+
113
132
  writeFileSync(outputPath, markdown);
114
133
  console.log(chalk.green(`✓ Report saved to ${outputPath}`));
115
134
  } else {
@@ -132,7 +151,11 @@ function displayConsoleReport(report: any, elapsedTime: string): void {
132
151
  console.log(`Analysis Time: ${chalk.gray(elapsedTime + 's')}\n`);
133
152
 
134
153
  if (summary.totalIssues === 0) {
135
- console.log(chalk.green('✨ No consistency patterns found! Your codebase is AI-friendly.\n'));
154
+ console.log(
155
+ chalk.green(
156
+ '✨ No consistency patterns found! Your codebase is AI-friendly.\n'
157
+ )
158
+ );
136
159
  return;
137
160
  }
138
161
 
@@ -174,10 +197,10 @@ function displayCategoryIssues(results: any[], maxToShow: number): void {
174
197
  issue.severity === 'critical'
175
198
  ? chalk.red
176
199
  : issue.severity === 'major'
177
- ? chalk.yellow
178
- : issue.severity === 'minor'
179
- ? chalk.blue
180
- : chalk.gray;
200
+ ? chalk.yellow
201
+ : issue.severity === 'minor'
202
+ ? chalk.blue
203
+ : chalk.gray;
181
204
 
182
205
  console.log(
183
206
  `${severityColor(issue.severity.toUpperCase())} ${chalk.dim(
@@ -193,7 +216,8 @@ function displayCategoryIssues(results: any[], maxToShow: number): void {
193
216
  }
194
217
  }
195
218
 
196
- const remaining = results.reduce((sum, r) => sum + r.issues.length, 0) - shown;
219
+ const remaining =
220
+ results.reduce((sum, r) => sum + r.issues.length, 0) - shown;
197
221
  if (remaining > 0) {
198
222
  console.log(chalk.dim(` ... and ${remaining} more patterns\n`));
199
223
  }
package/src/scoring.ts CHANGED
@@ -1,19 +1,15 @@
1
- import {
2
- calculateProductivityImpact,
3
- DEFAULT_COST_CONFIG,
4
- type CostConfig
5
- } from '@aiready/core';
6
- import type { ToolScoringOutput } from '@aiready/core';
1
+ import { calculateProductivityImpact } from '@aiready/core';
2
+ import type { ToolScoringOutput, CostConfig } from '@aiready/core';
7
3
  import type { ConsistencyIssue } from './types';
8
4
 
9
5
  /**
10
6
  * Calculate AI Readiness Score for code consistency (0-100)
11
- *
7
+ *
12
8
  * Based on:
13
9
  * - Issue density (issues per file)
14
10
  * - Weighted severity (critical: 10pts, major: 3pts, minor: 0.5pts)
15
11
  * - Pattern consistency across codebase
16
- *
12
+ *
17
13
  * Includes business value metrics:
18
14
  * - Estimated developer hours to fix consistency issues
19
15
  */
@@ -22,31 +18,36 @@ export function calculateConsistencyScore(
22
18
  totalFilesAnalyzed: number,
23
19
  costConfig?: Partial<CostConfig>
24
20
  ): ToolScoringOutput {
25
- const criticalIssues = issues.filter(i => i.severity === 'critical').length;
26
- const majorIssues = issues.filter(i => i.severity === 'major').length;
27
- const minorIssues = issues.filter(i => i.severity === 'minor').length;
21
+ // Parameter reserved for future configuration; reference to avoid lint warnings
22
+ void costConfig;
23
+ const criticalIssues = issues.filter((i) => i.severity === 'critical').length;
24
+ const majorIssues = issues.filter((i) => i.severity === 'major').length;
25
+ const minorIssues = issues.filter((i) => i.severity === 'minor').length;
28
26
  const totalIssues = issues.length;
29
-
27
+
30
28
  // Issue density penalty (0-50 points)
31
29
  // Ideal: 0 issues/file = 0 penalty
32
30
  // Acceptable: <1 issue/file = 10 penalty
33
31
  // High: 1-3 issues/file = 10-40 penalty
34
32
  // Critical: >3 issues/file = 40-50 penalty
35
- const issuesPerFile = totalFilesAnalyzed > 0 ? totalIssues / totalFilesAnalyzed : 0;
33
+ const issuesPerFile =
34
+ totalFilesAnalyzed > 0 ? totalIssues / totalFilesAnalyzed : 0;
36
35
  const densityPenalty = Math.min(50, issuesPerFile * 15);
37
-
36
+
38
37
  // Weighted severity penalty (0-50 points)
39
38
  // Each critical: 10 points
40
39
  // Each major: 3 points
41
40
  // Each minor: 0.5 points
42
- const weightedCount = (criticalIssues * 10) + (majorIssues * 3) + (minorIssues * 0.5);
43
- const avgWeightedIssuesPerFile = totalFilesAnalyzed > 0 ? weightedCount / totalFilesAnalyzed : 0;
41
+ const weightedCount =
42
+ criticalIssues * 10 + majorIssues * 3 + minorIssues * 0.5;
43
+ const avgWeightedIssuesPerFile =
44
+ totalFilesAnalyzed > 0 ? weightedCount / totalFilesAnalyzed : 0;
44
45
  const severityPenalty = Math.min(50, avgWeightedIssuesPerFile * 2);
45
-
46
+
46
47
  // Calculate final score
47
48
  const rawScore = 100 - densityPenalty - severityPenalty;
48
49
  const score = Math.max(0, Math.min(100, Math.round(rawScore)));
49
-
50
+
50
51
  // Build factors array
51
52
  const factors: ToolScoringOutput['factors'] = [
52
53
  {
@@ -55,7 +56,7 @@ export function calculateConsistencyScore(
55
56
  description: `${issuesPerFile.toFixed(2)} issues per file ${issuesPerFile < 1 ? '(excellent)' : issuesPerFile < 3 ? '(acceptable)' : '(high)'}`,
56
57
  },
57
58
  ];
58
-
59
+
59
60
  if (criticalIssues > 0) {
60
61
  const criticalImpact = Math.min(30, criticalIssues * 10);
61
62
  factors.push({
@@ -64,7 +65,7 @@ export function calculateConsistencyScore(
64
65
  description: `${criticalIssues} critical consistency issue${criticalIssues > 1 ? 's' : ''} (high AI confusion risk)`,
65
66
  });
66
67
  }
67
-
68
+
68
69
  if (majorIssues > 0) {
69
70
  const majorImpact = Math.min(20, Math.round(majorIssues * 3));
70
71
  factors.push({
@@ -73,7 +74,7 @@ export function calculateConsistencyScore(
73
74
  description: `${majorIssues} major issue${majorIssues > 1 ? 's' : ''} (moderate AI confusion risk)`,
74
75
  });
75
76
  }
76
-
77
+
77
78
  if (minorIssues > 0 && minorIssues >= totalFilesAnalyzed) {
78
79
  const minorImpact = -Math.round(minorIssues * 0.5);
79
80
  factors.push({
@@ -82,19 +83,20 @@ export function calculateConsistencyScore(
82
83
  description: `${minorIssues} minor issue${minorIssues > 1 ? 's' : ''} (slight AI confusion risk)`,
83
84
  });
84
85
  }
85
-
86
+
86
87
  // Generate recommendations
87
88
  const recommendations: ToolScoringOutput['recommendations'] = [];
88
-
89
+
89
90
  if (criticalIssues > 0) {
90
91
  const estimatedImpact = Math.min(30, criticalIssues * 10);
91
92
  recommendations.push({
92
- action: 'Fix critical naming/pattern inconsistencies (highest AI confusion risk)',
93
+ action:
94
+ 'Fix critical naming/pattern inconsistencies (highest AI confusion risk)',
93
95
  estimatedImpact,
94
96
  priority: 'high',
95
97
  });
96
98
  }
97
-
99
+
98
100
  if (majorIssues > 5) {
99
101
  const estimatedImpact = Math.min(15, Math.round(majorIssues / 2));
100
102
  recommendations.push({
@@ -103,15 +105,16 @@ export function calculateConsistencyScore(
103
105
  priority: 'medium',
104
106
  });
105
107
  }
106
-
108
+
107
109
  if (issuesPerFile > 3) {
108
110
  recommendations.push({
109
- action: 'Establish and enforce coding style guide to reduce inconsistencies',
111
+ action:
112
+ 'Establish and enforce coding style guide to reduce inconsistencies',
110
113
  estimatedImpact: 12,
111
114
  priority: 'medium',
112
115
  });
113
116
  }
114
-
117
+
115
118
  if (totalIssues > 20 && minorIssues / totalIssues > 0.7) {
116
119
  recommendations.push({
117
120
  action: 'Enable linter/formatter to automatically fix minor style issues',
@@ -119,10 +122,10 @@ export function calculateConsistencyScore(
119
122
  priority: 'low',
120
123
  });
121
124
  }
122
-
125
+
123
126
  // Calculate business value metrics
124
127
  const productivityImpact = calculateProductivityImpact(issues);
125
-
128
+
126
129
  return {
127
130
  toolName: 'consistency',
128
131
  score,
@@ -132,7 +135,8 @@ export function calculateConsistencyScore(
132
135
  majorIssues,
133
136
  minorIssues,
134
137
  issuesPerFile: Math.round(issuesPerFile * 100) / 100,
135
- avgWeightedIssuesPerFile: Math.round(avgWeightedIssuesPerFile * 100) / 100,
138
+ avgWeightedIssuesPerFile:
139
+ Math.round(avgWeightedIssuesPerFile * 100) / 100,
136
140
  // Business value metrics
137
141
  estimatedDeveloperHours: productivityImpact.totalHours,
138
142
  },
package/src/types.ts CHANGED
@@ -12,7 +12,7 @@ export interface ConsistencyOptions extends ScanOptions {
12
12
  }
13
13
 
14
14
  export interface ConsistencyIssue extends Issue {
15
- type:
15
+ type:
16
16
  | 'naming-inconsistency'
17
17
  | 'naming-quality'
18
18
  | 'pattern-inconsistency'
@@ -32,6 +32,7 @@ export interface NamingIssue {
32
32
  identifier: string;
33
33
  suggestion?: string;
34
34
  severity: 'critical' | 'major' | 'minor' | 'info';
35
+ category?: 'naming';
35
36
  }
36
37
 
37
38
  export interface PatternIssue {
@@ -5,11 +5,14 @@ import { readFileSync } from 'fs';
5
5
  * Parse a file into an AST
6
6
  * Only supports TypeScript/JavaScript files (.ts, .tsx, .js, .jsx)
7
7
  */
8
- export function parseFile(filePath: string, content?: string): TSESTree.Program | null {
8
+ export function parseFile(
9
+ filePath: string,
10
+ content?: string
11
+ ): TSESTree.Program | null {
9
12
  try {
10
13
  const code = content ?? readFileSync(filePath, 'utf-8');
11
14
  const isTypeScript = filePath.match(/\.tsx?$/);
12
-
15
+
13
16
  return parse(code, {
14
17
  jsx: filePath.match(/\.[jt]sx$/i) !== null,
15
18
  loc: true,
@@ -23,6 +26,7 @@ export function parseFile(filePath: string, content?: string): TSESTree.Program
23
26
  filePath: isTypeScript ? filePath : undefined,
24
27
  });
25
28
  } catch (error) {
29
+ void error;
26
30
  // Silently skip files that fail to parse (likely non-JS/TS or syntax errors)
27
31
  // Non-JS/TS files should be filtered before reaching this point
28
32
  return null;
@@ -47,7 +51,7 @@ export function traverseAST(
47
51
  // Visit children
48
52
  for (const key of Object.keys(node)) {
49
53
  const value = (node as any)[key];
50
-
54
+
51
55
  if (Array.isArray(value)) {
52
56
  for (const child of value) {
53
57
  if (child && typeof child === 'object' && 'type' in child) {
@@ -70,7 +74,7 @@ export function hasAncestor(
70
74
  ancestorTypes: string[],
71
75
  ancestors: TSESTree.Node[]
72
76
  ): boolean {
73
- return ancestors.some(ancestor => ancestorTypes.includes(ancestor.type));
77
+ return ancestors.some((ancestor) => ancestorTypes.includes(ancestor.type));
74
78
  }
75
79
 
76
80
  /**
@@ -135,7 +139,7 @@ export function getFunctionName(node: TSESTree.Node): string | null {
135
139
  */
136
140
  export function isInDestructuring(node: TSESTree.Node): boolean {
137
141
  if (!node) return false;
138
-
142
+
139
143
  return node.type === 'ObjectPattern' || node.type === 'ArrayPattern';
140
144
  }
141
145
 
@@ -149,34 +153,48 @@ export function getLineNumber(node: TSESTree.Node): number {
149
153
  /**
150
154
  * Check if a node represents a coverage metric context
151
155
  */
152
- export function isCoverageContext(node: TSESTree.Node, ancestors: TSESTree.Node[]): boolean {
156
+ export function isCoverageContext(
157
+ node: TSESTree.Node,
158
+ ancestors: TSESTree.Node[]
159
+ ): boolean {
153
160
  // Check if any ancestor or the node itself references coverage-related properties
154
- const coveragePatterns = /coverage|summary|metrics|pct|percent|statements|branches|functions|lines/i;
155
-
161
+ const coveragePatterns =
162
+ /coverage|summary|metrics|pct|percent|statements|branches|functions|lines/i;
163
+
156
164
  // Check variable name
157
165
  if (node.type === 'Identifier' && coveragePatterns.test(node.name)) {
158
166
  return true;
159
167
  }
160
-
168
+
161
169
  // Check if it's a property of something coverage-related
162
- for (const ancestor of ancestors.slice(-3)) { // Check last 3 ancestors
170
+ for (const ancestor of ancestors.slice(-3)) {
171
+ // Check last 3 ancestors
163
172
  if (ancestor.type === 'MemberExpression') {
164
173
  const memberExpr = ancestor as TSESTree.MemberExpression;
165
- if (memberExpr.object.type === 'Identifier' && coveragePatterns.test(memberExpr.object.name)) {
174
+ if (
175
+ memberExpr.object.type === 'Identifier' &&
176
+ coveragePatterns.test(memberExpr.object.name)
177
+ ) {
166
178
  return true;
167
179
  }
168
180
  }
169
- if (ancestor.type === 'ObjectPattern' || ancestor.type === 'ObjectExpression') {
181
+ if (
182
+ ancestor.type === 'ObjectPattern' ||
183
+ ancestor.type === 'ObjectExpression'
184
+ ) {
170
185
  // Check if parent variable has coverage-related name
171
186
  const parent = ancestors[ancestors.indexOf(ancestor) - 1];
172
187
  if (parent?.type === 'VariableDeclarator') {
173
188
  const varDecl = parent as TSESTree.VariableDeclarator;
174
- if (varDecl.id.type === 'Identifier' && coveragePatterns.test(varDecl.id.name)) {
189
+ if (
190
+ varDecl.id.type === 'Identifier' &&
191
+ coveragePatterns.test(varDecl.id.name)
192
+ ) {
175
193
  return true;
176
194
  }
177
195
  }
178
196
  }
179
197
  }
180
-
198
+
181
199
  return false;
182
200
  }
@@ -19,7 +19,7 @@ export interface NamingConfig {
19
19
  /**
20
20
  * Loads and merges naming configuration for consistency analyzers
21
21
  * Extracts common config loading logic used by both naming.ts and naming-ast.ts
22
- *
22
+ *
23
23
  * @param files - Array of files being analyzed (used to determine project root)
24
24
  * @returns Merged configuration with custom and default abbreviations/short words
25
25
  */
@@ -30,12 +30,17 @@ export async function loadNamingConfig(files: string[]): Promise<NamingConfig> {
30
30
  const consistencyConfig = config?.tools?.['consistency'];
31
31
 
32
32
  // Extract custom configuration
33
- const customAbbreviations = new Set(consistencyConfig?.acceptedAbbreviations || []);
33
+ const customAbbreviations = new Set(
34
+ consistencyConfig?.acceptedAbbreviations || []
35
+ );
34
36
  const customShortWords = new Set(consistencyConfig?.shortWords || []);
35
37
  const disabledChecks = new Set(consistencyConfig?.disableChecks || []);
36
38
 
37
39
  // Merge with defaults
38
- const allAbbreviations = new Set([...ACCEPTABLE_ABBREVIATIONS, ...customAbbreviations]);
40
+ const allAbbreviations = new Set([
41
+ ...ACCEPTABLE_ABBREVIATIONS,
42
+ ...customAbbreviations,
43
+ ]);
39
44
  const allShortWords = new Set([...COMMON_SHORT_WORDS, ...customShortWords]);
40
45
 
41
46
  return {