@aiready/testability 0.1.4 → 0.1.6

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
@@ -7,15 +7,23 @@ import type { TestabilityOptions } from './types';
7
7
  import chalk from 'chalk';
8
8
  import { writeFileSync, mkdirSync, existsSync } from 'fs';
9
9
  import { dirname } from 'path';
10
- import { loadConfig, mergeConfigWithDefaults, resolveOutputPath } from '@aiready/core';
10
+ import {
11
+ loadConfig,
12
+ mergeConfigWithDefaults,
13
+ resolveOutputPath,
14
+ } from '@aiready/core';
11
15
 
12
16
  const program = new Command();
13
17
 
14
18
  program
15
19
  .name('aiready-testability')
16
- .description('Measure how safely AI-generated changes can be verified in your codebase')
20
+ .description(
21
+ 'Measure how safely AI-generated changes can be verified in your codebase'
22
+ )
17
23
  .version('0.1.0')
18
- .addHelpText('after', `
24
+ .addHelpText(
25
+ 'after',
26
+ `
19
27
  DIMENSIONS MEASURED:
20
28
  Test Coverage Ratio of test files to source files
21
29
  Function Purity Pure functions are trivially AI-testable
@@ -33,10 +41,18 @@ EXAMPLES:
33
41
  aiready-testability . # Full analysis
34
42
  aiready-testability src/ --output json # JSON report
35
43
  aiready-testability . --min-coverage 0.5 # Stricter 50% threshold
36
- `)
44
+ `
45
+ )
37
46
  .argument('<directory>', 'Directory to analyze')
38
- .option('--min-coverage <ratio>', 'Minimum acceptable test/source ratio (default: 0.3)', '0.3')
39
- .option('--test-patterns <patterns>', 'Additional test file patterns (comma-separated)')
47
+ .option(
48
+ '--min-coverage <ratio>',
49
+ 'Minimum acceptable test/source ratio (default: 0.3)',
50
+ '0.3'
51
+ )
52
+ .option(
53
+ '--test-patterns <patterns>',
54
+ 'Additional test file patterns (comma-separated)'
55
+ )
40
56
  .option('--include <patterns>', 'File patterns to include (comma-separated)')
41
57
  .option('--exclude <patterns>', 'File patterns to exclude (comma-separated)')
42
58
  .option('-o, --output <format>', 'Output format: console|json', 'console')
@@ -52,7 +68,9 @@ EXAMPLES:
52
68
 
53
69
  const finalOptions: TestabilityOptions = {
54
70
  rootDir: directory,
55
- minCoverageRatio: parseFloat(options.minCoverage ?? '0.3') || mergedConfig.minCoverageRatio,
71
+ minCoverageRatio:
72
+ parseFloat(options.minCoverage ?? '0.3') ||
73
+ mergedConfig.minCoverageRatio,
56
74
  testPatterns: options.testPatterns?.split(','),
57
75
  include: options.include?.split(','),
58
76
  exclude: options.exclude?.split(','),
@@ -67,7 +85,7 @@ EXAMPLES:
67
85
  const outputPath = resolveOutputPath(
68
86
  options.outputFile,
69
87
  `testability-report-${new Date().toISOString().split('T')[0]}.json`,
70
- directory,
88
+ directory
71
89
  );
72
90
  const dir = dirname(outputPath);
73
91
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
@@ -82,21 +100,31 @@ program.parse();
82
100
 
83
101
  function safetyColor(rating: string) {
84
102
  switch (rating) {
85
- case 'safe': return chalk.green;
86
- case 'moderate-risk': return chalk.yellow;
87
- case 'high-risk': return chalk.red;
88
- case 'blind-risk': return chalk.bgRed.white;
89
- default: return chalk.white;
103
+ case 'safe':
104
+ return chalk.green;
105
+ case 'moderate-risk':
106
+ return chalk.yellow;
107
+ case 'high-risk':
108
+ return chalk.red;
109
+ case 'blind-risk':
110
+ return chalk.bgRed.white;
111
+ default:
112
+ return chalk.white;
90
113
  }
91
114
  }
92
115
 
93
116
  function safetyIcon(rating: string) {
94
117
  switch (rating) {
95
- case 'safe': return '✅';
96
- case 'moderate-risk': return '⚠️ ';
97
- case 'high-risk': return '🔴';
98
- case 'blind-risk': return '💀';
99
- default: return '';
118
+ case 'safe':
119
+ return '';
120
+ case 'moderate-risk':
121
+ return '⚠️ ';
122
+ case 'high-risk':
123
+ return '🔴';
124
+ case 'blind-risk':
125
+ return '💀';
126
+ default:
127
+ return '❓';
100
128
  }
101
129
  }
102
130
 
@@ -112,19 +140,33 @@ function displayConsoleReport(report: any, scoring: any, elapsed: string) {
112
140
  console.log(chalk.bold('\n🧪 Testability Analysis\n'));
113
141
 
114
142
  if (safetyRating === 'blind-risk') {
115
- console.log(chalk.bgRed.white.bold(
116
- ' 💀 BLIND RISK — NO TESTS DETECTED. AI-GENERATED CHANGES CANNOT BE VERIFIED. '
117
- ));
143
+ console.log(
144
+ chalk.bgRed.white.bold(
145
+ ' 💀 BLIND RISK — NO TESTS DETECTED. AI-GENERATED CHANGES CANNOT BE VERIFIED. '
146
+ )
147
+ );
118
148
  console.log();
119
149
  } else if (safetyRating === 'high-risk') {
120
- console.log(chalk.red.bold(` 🔴 HIGH RISK — Insufficient test coverage. AI changes may introduce silent bugs.`));
150
+ console.log(
151
+ chalk.red.bold(
152
+ ` 🔴 HIGH RISK — Insufficient test coverage. AI changes may introduce silent bugs.`
153
+ )
154
+ );
121
155
  console.log();
122
156
  }
123
157
 
124
- console.log(`AI Change Safety: ${safetyColor(safetyRating)(`${safetyIcon(safetyRating)} ${safetyRating.toUpperCase()}`)}`);
125
- console.log(`Score: ${chalk.bold(summary.score + '/100')} (${summary.rating})`);
126
- console.log(`Source Files: ${chalk.cyan(rawData.sourceFiles)} Test Files: ${chalk.cyan(rawData.testFiles)}`);
127
- console.log(`Coverage Ratio: ${chalk.bold(Math.round(summary.coverageRatio * 100) + '%')}`);
158
+ console.log(
159
+ `AI Change Safety: ${safetyColor(safetyRating)(`${safetyIcon(safetyRating)} ${safetyRating.toUpperCase()}`)}`
160
+ );
161
+ console.log(
162
+ `Score: ${chalk.bold(summary.score + '/100')} (${summary.rating})`
163
+ );
164
+ console.log(
165
+ `Source Files: ${chalk.cyan(rawData.sourceFiles)} Test Files: ${chalk.cyan(rawData.testFiles)}`
166
+ );
167
+ console.log(
168
+ `Coverage Ratio: ${chalk.bold(Math.round(summary.coverageRatio * 100) + '%')}`
169
+ );
128
170
  console.log(`Analysis Time: ${chalk.gray(elapsed + 's')}\n`);
129
171
 
130
172
  console.log(chalk.bold('📐 Dimension Scores\n'));
@@ -136,16 +178,25 @@ function displayConsoleReport(report: any, scoring: any, elapsed: string) {
136
178
  ['Observability', summary.dimensions.observabilityScore],
137
179
  ];
138
180
  for (const [name, val] of dims) {
139
- const color = val >= 70 ? chalk.green : val >= 50 ? chalk.yellow : chalk.red;
181
+ const color =
182
+ val >= 70 ? chalk.green : val >= 50 ? chalk.yellow : chalk.red;
140
183
  console.log(` ${name.padEnd(22)} ${color(scoreBar(val))} ${val}/100`);
141
184
  }
142
185
 
143
186
  if (issues.length > 0) {
144
187
  console.log(chalk.bold('\n⚠️ Issues\n'));
145
188
  for (const issue of issues) {
146
- const sev = issue.severity === 'critical' ? chalk.red : issue.severity === 'major' ? chalk.yellow : chalk.blue;
189
+ const sev =
190
+ issue.severity === 'critical'
191
+ ? chalk.red
192
+ : issue.severity === 'major'
193
+ ? chalk.yellow
194
+ : chalk.blue;
147
195
  console.log(`${sev(issue.severity.toUpperCase())} ${issue.message}`);
148
- if (issue.suggestion) console.log(` ${chalk.dim('→')} ${chalk.italic(issue.suggestion)}`);
196
+ if (issue.suggestion)
197
+ console.log(
198
+ ` ${chalk.dim('→')} ${chalk.italic(issue.suggestion)}`
199
+ );
149
200
  console.log();
150
201
  }
151
202
  }
package/src/scoring.ts CHANGED
@@ -5,7 +5,9 @@ import type { TestabilityReport } from './types';
5
5
  /**
6
6
  * Convert testability report into a ToolScoringOutput for the unified score.
7
7
  */
8
- export function calculateTestabilityScore(report: TestabilityReport): ToolScoringOutput {
8
+ export function calculateTestabilityScore(
9
+ report: TestabilityReport
10
+ ): ToolScoringOutput {
9
11
  const { summary, rawData, recommendations } = report;
10
12
 
11
13
  const factors: ToolScoringOutput['factors'] = [
@@ -36,13 +38,17 @@ export function calculateTestabilityScore(report: TestabilityReport): ToolScorin
36
38
  },
37
39
  ];
38
40
 
39
- const recs: ToolScoringOutput['recommendations'] = recommendations.map(action => ({
40
- action,
41
- estimatedImpact: summary.aiChangeSafetyRating === 'blind-risk' ? 15 : 8,
42
- priority: summary.aiChangeSafetyRating === 'blind-risk' || summary.aiChangeSafetyRating === 'high-risk'
43
- ? 'high'
44
- : 'medium',
45
- }));
41
+ const recs: ToolScoringOutput['recommendations'] = recommendations.map(
42
+ (action) => ({
43
+ action,
44
+ estimatedImpact: summary.aiChangeSafetyRating === 'blind-risk' ? 15 : 8,
45
+ priority:
46
+ summary.aiChangeSafetyRating === 'blind-risk' ||
47
+ summary.aiChangeSafetyRating === 'high-risk'
48
+ ? 'high'
49
+ : 'medium',
50
+ })
51
+ );
46
52
 
47
53
  return {
48
54
  toolName: 'testability',
package/src/types.ts CHANGED
@@ -18,7 +18,13 @@ export interface TestabilityOptions {
18
18
  export interface TestabilityIssue extends Issue {
19
19
  type: 'low-testability';
20
20
  /** Category of testability barrier */
21
- dimension: 'test-coverage' | 'purity' | 'dependency-injection' | 'interface-focus' | 'observability' | 'framework';
21
+ dimension:
22
+ | 'test-coverage'
23
+ | 'purity'
24
+ | 'dependency-injection'
25
+ | 'interface-focus'
26
+ | 'observability'
27
+ | 'framework';
22
28
  }
23
29
 
24
30
  export interface TestabilityReport {