@aiready/testability 0.6.18 โ 0.6.20
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/.turbo/turbo-build.log +24 -23
- package/.turbo/turbo-lint.log +5 -10
- package/.turbo/turbo-test.log +20 -18
- package/dist/chunk-QMDUZA7H.mjs +239 -0
- package/dist/chunk-RBPS3OGD.mjs +238 -0
- package/dist/chunk-YT5DTEQ4.mjs +238 -0
- package/dist/cli.js +57 -118
- package/dist/cli.mjs +46 -80
- package/dist/index.d.mts +2 -5
- package/dist/index.d.ts +2 -5
- package/dist/index.js +14 -44
- package/dist/index.mjs +1 -1
- package/package.json +2 -2
- package/src/__tests__/analyzer.test.ts +15 -26
- package/src/__tests__/scoring.test.ts +1 -1
- package/src/analyzer.ts +1 -1
- package/src/cli.ts +44 -89
- package/src/scoring.ts +16 -56
package/dist/index.js
CHANGED
|
@@ -44,7 +44,7 @@ async function analyzeFileTestability(filePath) {
|
|
|
44
44
|
totalInterfaces: 0,
|
|
45
45
|
externalStateMutations: 0
|
|
46
46
|
};
|
|
47
|
-
const parser = (0, import_core.getParser)(filePath);
|
|
47
|
+
const parser = await (0, import_core.getParser)(filePath);
|
|
48
48
|
if (!parser) return result;
|
|
49
49
|
let code;
|
|
50
50
|
try {
|
|
@@ -240,52 +240,22 @@ async function analyzeTestability(options) {
|
|
|
240
240
|
var import_core2 = require("@aiready/core");
|
|
241
241
|
function calculateTestabilityScore(report) {
|
|
242
242
|
const { summary, rawData, recommendations } = report;
|
|
243
|
-
|
|
244
|
-
{
|
|
245
|
-
name: "Test Coverage",
|
|
246
|
-
impact: Math.round(summary.dimensions.testCoverageRatio - 50),
|
|
247
|
-
description: `${rawData.testFiles} test files / ${rawData.sourceFiles} source files (${Math.round(summary.coverageRatio * 100)}%)`
|
|
248
|
-
},
|
|
249
|
-
{
|
|
250
|
-
name: "Function Purity",
|
|
251
|
-
impact: Math.round(summary.dimensions.purityScore - 50),
|
|
252
|
-
description: `${rawData.pureFunctions}/${rawData.totalFunctions} functions are pure`
|
|
253
|
-
},
|
|
254
|
-
{
|
|
255
|
-
name: "Dependency Injection",
|
|
256
|
-
impact: Math.round(summary.dimensions.dependencyInjectionScore - 50),
|
|
257
|
-
description: `${rawData.injectionPatterns}/${rawData.totalClasses} classes use DI`
|
|
258
|
-
},
|
|
259
|
-
{
|
|
260
|
-
name: "Interface Focus",
|
|
261
|
-
impact: Math.round(summary.dimensions.interfaceFocusScore - 50),
|
|
262
|
-
description: `${rawData.bloatedInterfaces} interfaces have >10 methods`
|
|
263
|
-
},
|
|
264
|
-
{
|
|
265
|
-
name: "Observability",
|
|
266
|
-
impact: Math.round(summary.dimensions.observabilityScore - 50),
|
|
267
|
-
description: `${rawData.externalStateMutations} functions mutate external state`
|
|
268
|
-
}
|
|
269
|
-
];
|
|
270
|
-
const recs = recommendations.map(
|
|
271
|
-
(action) => ({
|
|
272
|
-
action,
|
|
273
|
-
estimatedImpact: summary.aiChangeSafetyRating === "blind-risk" ? 15 : 8,
|
|
274
|
-
priority: summary.aiChangeSafetyRating === "blind-risk" || summary.aiChangeSafetyRating === "high-risk" ? "high" : "medium"
|
|
275
|
-
})
|
|
276
|
-
);
|
|
277
|
-
return {
|
|
243
|
+
return (0, import_core2.buildStandardToolScore)({
|
|
278
244
|
toolName: import_core2.ToolName.TestabilityIndex,
|
|
279
245
|
score: summary.score,
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
246
|
+
rawData,
|
|
247
|
+
dimensions: summary.dimensions,
|
|
248
|
+
dimensionNames: {
|
|
249
|
+
testCoverageRatio: "Test Coverage",
|
|
250
|
+
purityScore: "Function Purity",
|
|
251
|
+
dependencyInjectionScore: "Dependency Injection",
|
|
252
|
+
interfaceFocusScore: "Interface Focus",
|
|
253
|
+
observabilityScore: "Observability"
|
|
285
254
|
},
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
255
|
+
recommendations,
|
|
256
|
+
recommendationImpact: summary.aiChangeSafetyRating === "blind-risk" ? 15 : 8,
|
|
257
|
+
rating: summary.aiChangeSafetyRating || summary.rating
|
|
258
|
+
});
|
|
289
259
|
}
|
|
290
260
|
|
|
291
261
|
// src/provider.ts
|
package/dist/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiready/testability",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.20",
|
|
4
4
|
"description": "Measures how safely and verifiably AI-generated changes can be made to your codebase",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"chalk": "^5.3.0",
|
|
41
41
|
"commander": "^14.0.0",
|
|
42
42
|
"glob": "^13.0.0",
|
|
43
|
-
"@aiready/core": "0.23.
|
|
43
|
+
"@aiready/core": "0.23.21"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@types/node": "^24.0.0",
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
1
2
|
import { analyzeTestability } from '../analyzer';
|
|
2
3
|
import { join } from 'path';
|
|
3
|
-
import {
|
|
4
|
+
import { mkdirSync, writeFileSync, rmSync } from 'fs';
|
|
4
5
|
import { tmpdir } from 'os';
|
|
5
|
-
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
6
6
|
|
|
7
7
|
describe('Testability Analyzer', () => {
|
|
8
8
|
let tmpDir: string;
|
|
9
9
|
|
|
10
10
|
beforeAll(() => {
|
|
11
|
-
tmpDir = join(tmpdir(), `aiready-testability
|
|
11
|
+
tmpDir = join(tmpdir(), `aiready-testability-${Date.now()}`);
|
|
12
12
|
mkdirSync(tmpDir, { recursive: true });
|
|
13
13
|
});
|
|
14
14
|
|
|
@@ -52,55 +52,44 @@ describe('Testability Analyzer', () => {
|
|
|
52
52
|
const javaReport = await analyzeTestability({ rootDir: javaDir });
|
|
53
53
|
expect(javaReport.rawData.hasTestFramework).toBe(true);
|
|
54
54
|
|
|
55
|
-
// Test Go detection
|
|
55
|
+
// Test Go detection
|
|
56
56
|
const goReport = await analyzeTestability({ rootDir: goDir });
|
|
57
57
|
expect(goReport.rawData.hasTestFramework).toBe(true);
|
|
58
|
-
});
|
|
58
|
+
}, 15000);
|
|
59
59
|
|
|
60
60
|
it('flags missing test framework as critical', async () => {
|
|
61
61
|
const emptyDir = join(tmpDir, 'no-tests');
|
|
62
62
|
mkdirSync(emptyDir);
|
|
63
|
-
writeFileSync(join(emptyDir, '
|
|
63
|
+
writeFileSync(join(emptyDir, 'App.ts'), 'export const a = 1;');
|
|
64
64
|
|
|
65
65
|
const report = await analyzeTestability({ rootDir: emptyDir });
|
|
66
66
|
expect(report.rawData.hasTestFramework).toBe(false);
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
(i) => i.dimension === 'framework' && i.severity === 'critical'
|
|
70
|
-
)
|
|
71
|
-
).toBe(true);
|
|
67
|
+
// Rating is 'unverifiable' when no framework is found
|
|
68
|
+
expect(report.summary.rating).toBe('unverifiable');
|
|
72
69
|
});
|
|
73
70
|
|
|
74
71
|
it('detects injection patterns in classes', async () => {
|
|
75
72
|
createTestFile(
|
|
76
73
|
'src/di.ts',
|
|
77
|
-
|
|
78
|
-
export class Service {
|
|
79
|
-
constructor(public db: any) {}
|
|
80
|
-
}
|
|
81
|
-
`
|
|
74
|
+
'export class UserService { constructor(db: any) {} }'
|
|
82
75
|
);
|
|
83
76
|
const report = await analyzeTestability({ rootDir: tmpDir });
|
|
84
|
-
expect(report.rawData.injectionPatterns).
|
|
77
|
+
expect(report.rawData.injectionPatterns).toBeGreaterThan(0);
|
|
85
78
|
});
|
|
86
79
|
|
|
87
80
|
it('detects bloated interfaces', async () => {
|
|
88
81
|
createTestFile(
|
|
89
82
|
'src/bloated.ts',
|
|
90
|
-
|
|
91
|
-
export interface Massive {
|
|
92
|
-
a(): void; b(): void; c(): void; d(): void; e(): void; f(): void; g(): void;
|
|
93
|
-
h(): void; i(): void; j(): void; k(): void; l(): void;
|
|
94
|
-
}
|
|
95
|
-
`
|
|
83
|
+
'export interface Big { m1(); m2(); m3(); m4(); m5(); m6(); m7(); m8(); m9(); m10(); m11(); m12(); }'
|
|
96
84
|
);
|
|
97
85
|
const report = await analyzeTestability({ rootDir: tmpDir });
|
|
98
|
-
expect(report.rawData.bloatedInterfaces).
|
|
86
|
+
expect(report.rawData.bloatedInterfaces).toBeGreaterThan(0);
|
|
99
87
|
});
|
|
100
88
|
|
|
101
89
|
it('gracefully handles missing parser or read errors', async () => {
|
|
102
|
-
createTestFile('src/
|
|
90
|
+
createTestFile('src/unknown.xyz', 'some content');
|
|
103
91
|
const report = await analyzeTestability({ rootDir: tmpDir });
|
|
104
|
-
expect(report
|
|
92
|
+
expect(report).toBeDefined();
|
|
93
|
+
expect(report.summary.sourceFiles).toBeGreaterThan(0);
|
|
105
94
|
});
|
|
106
95
|
});
|
|
@@ -44,7 +44,7 @@ describe('Testability Scoring', () => {
|
|
|
44
44
|
expect(scoring.factors.length).toBe(5);
|
|
45
45
|
|
|
46
46
|
const coverageFactor = scoring.factors.find(
|
|
47
|
-
(f) => f.name === 'Test Coverage'
|
|
47
|
+
(f: any) => f.name === 'Test Coverage'
|
|
48
48
|
);
|
|
49
49
|
expect(coverageFactor?.impact).toBe(30); // 80 - 50
|
|
50
50
|
expect(coverageFactor?.description).toContain(
|
package/src/analyzer.ts
CHANGED
package/src/cli.ts
CHANGED
|
@@ -8,15 +8,13 @@ import chalk from 'chalk';
|
|
|
8
8
|
import { writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
9
9
|
import { dirname } from 'path';
|
|
10
10
|
import {
|
|
11
|
-
loadConfig,
|
|
12
|
-
mergeConfigWithDefaults,
|
|
13
11
|
resolveOutputPath,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
getSeverityColor,
|
|
12
|
+
displayStandardConsoleReport,
|
|
13
|
+
createStandardProgressCallback,
|
|
17
14
|
} from '@aiready/core';
|
|
18
15
|
|
|
19
16
|
const program = new Command();
|
|
17
|
+
const startTime = Date.now();
|
|
20
18
|
|
|
21
19
|
program
|
|
22
20
|
.name('aiready-testability')
|
|
@@ -62,21 +60,14 @@ EXAMPLES:
|
|
|
62
60
|
.option('--output-file <path>', 'Output file path (for json)')
|
|
63
61
|
.action(async (directory, options) => {
|
|
64
62
|
console.log(chalk.blue('๐งช Analyzing testability...\n'));
|
|
65
|
-
const startTime = Date.now();
|
|
66
|
-
|
|
67
|
-
const config = await loadConfig(directory);
|
|
68
|
-
const mergedConfig = mergeConfigWithDefaults(config, {
|
|
69
|
-
minCoverageRatio: 0.3,
|
|
70
|
-
});
|
|
71
63
|
|
|
72
64
|
const finalOptions: TestabilityOptions = {
|
|
73
65
|
rootDir: directory,
|
|
74
|
-
minCoverageRatio:
|
|
75
|
-
parseFloat(options.minCoverage ?? '0.3') ||
|
|
76
|
-
mergedConfig.minCoverageRatio,
|
|
66
|
+
minCoverageRatio: parseFloat(options.minCoverage ?? '0.3'),
|
|
77
67
|
testPatterns: options.testPatterns?.split(','),
|
|
78
68
|
include: options.include?.split(','),
|
|
79
69
|
exclude: options.exclude?.split(','),
|
|
70
|
+
onProgress: createStandardProgressCallback('testability'),
|
|
80
71
|
};
|
|
81
72
|
|
|
82
73
|
const report = await analyzeTestability(finalOptions);
|
|
@@ -95,82 +86,46 @@ EXAMPLES:
|
|
|
95
86
|
writeFileSync(outputPath, JSON.stringify(payload, null, 2));
|
|
96
87
|
console.log(chalk.green(`โ Report saved to ${outputPath}`));
|
|
97
88
|
} else {
|
|
98
|
-
|
|
89
|
+
displayStandardConsoleReport({
|
|
90
|
+
title: '๐งช Testability Analysis',
|
|
91
|
+
score: scoring.summary.score,
|
|
92
|
+
rating: scoring.summary.rating,
|
|
93
|
+
dimensions: [
|
|
94
|
+
{
|
|
95
|
+
name: 'Test Coverage',
|
|
96
|
+
value: scoring.summary.dimensions.testCoverageRatio,
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: 'Function Purity',
|
|
100
|
+
value: scoring.summary.dimensions.purityScore,
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: 'Dependency Injection',
|
|
104
|
+
value: scoring.summary.dimensions.dependencyInjectionScore,
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: 'Interface Focus',
|
|
108
|
+
value: scoring.summary.dimensions.interfaceFocusScore,
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: 'Observability',
|
|
112
|
+
value: scoring.summary.dimensions.observabilityScore,
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
stats: [
|
|
116
|
+
{ label: 'Source Files', value: report.rawData.sourceFiles },
|
|
117
|
+
{ label: 'Test Files', value: report.rawData.testFiles },
|
|
118
|
+
{
|
|
119
|
+
label: 'Coverage Ratio',
|
|
120
|
+
value: Math.round(scoring.summary.coverageRatio * 100) + '%',
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
issues: report.issues,
|
|
124
|
+
recommendations: report.recommendations,
|
|
125
|
+
elapsedTime: elapsed,
|
|
126
|
+
safetyRating: report.summary.aiChangeSafetyRating,
|
|
127
|
+
});
|
|
99
128
|
}
|
|
100
129
|
});
|
|
101
130
|
|
|
102
131
|
program.parse();
|
|
103
|
-
|
|
104
|
-
function displayConsoleReport(report: any, scoring: any, elapsed: string) {
|
|
105
|
-
const { summary, rawData, issues, recommendations } = report;
|
|
106
|
-
|
|
107
|
-
// The most important banner
|
|
108
|
-
const safetyRating = summary.aiChangeSafetyRating;
|
|
109
|
-
console.log(chalk.bold('\n๐งช Testability Analysis\n'));
|
|
110
|
-
|
|
111
|
-
if (safetyRating === 'blind-risk') {
|
|
112
|
-
console.log(
|
|
113
|
-
chalk.bgRed.white.bold(
|
|
114
|
-
' ๐ BLIND RISK โ NO TESTS DETECTED. AI-GENERATED CHANGES CANNOT BE VERIFIED. '
|
|
115
|
-
)
|
|
116
|
-
);
|
|
117
|
-
console.log();
|
|
118
|
-
} else if (safetyRating === 'high-risk') {
|
|
119
|
-
console.log(
|
|
120
|
-
chalk.red.bold(
|
|
121
|
-
` ๐ด HIGH RISK โ Insufficient test coverage. AI changes may introduce silent bugs.`
|
|
122
|
-
)
|
|
123
|
-
);
|
|
124
|
-
console.log();
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const safetyColor = getSeverityColor(safetyRating, chalk);
|
|
128
|
-
console.log(
|
|
129
|
-
`AI Change Safety: ${safetyColor(`${getSafetyIcon(safetyRating)} ${safetyRating.toUpperCase()}`)}`
|
|
130
|
-
);
|
|
131
|
-
console.log(
|
|
132
|
-
`Score: ${chalk.bold(summary.score + '/100')} (${summary.rating})`
|
|
133
|
-
);
|
|
134
|
-
console.log(
|
|
135
|
-
`Source Files: ${chalk.cyan(rawData.sourceFiles)} Test Files: ${chalk.cyan(rawData.testFiles)}`
|
|
136
|
-
);
|
|
137
|
-
console.log(
|
|
138
|
-
`Coverage Ratio: ${chalk.bold(Math.round(summary.coverageRatio * 100) + '%')}`
|
|
139
|
-
);
|
|
140
|
-
console.log(`Analysis Time: ${chalk.gray(elapsed + 's')}\n`);
|
|
141
|
-
|
|
142
|
-
console.log(chalk.bold('๐ Dimension Scores\n'));
|
|
143
|
-
const dims: [string, number][] = [
|
|
144
|
-
['Test Coverage', summary.dimensions.testCoverageRatio],
|
|
145
|
-
['Function Purity', summary.dimensions.purityScore],
|
|
146
|
-
['Dependency Injection', summary.dimensions.dependencyInjectionScore],
|
|
147
|
-
['Interface Focus', summary.dimensions.interfaceFocusScore],
|
|
148
|
-
['Observability', summary.dimensions.observabilityScore],
|
|
149
|
-
];
|
|
150
|
-
for (const [name, val] of dims) {
|
|
151
|
-
const color =
|
|
152
|
-
val >= 70 ? chalk.green : val >= 50 ? chalk.yellow : chalk.red;
|
|
153
|
-
console.log(` ${name.padEnd(22)} ${color(getScoreBar(val))} ${val}/100`);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (issues.length > 0) {
|
|
157
|
-
console.log(chalk.bold('\nโ ๏ธ Issues\n'));
|
|
158
|
-
for (const issue of issues) {
|
|
159
|
-
const sev = getSeverityColor(issue.severity, chalk);
|
|
160
|
-
console.log(`${sev(issue.severity.toUpperCase())} ${issue.message}`);
|
|
161
|
-
if (issue.suggestion)
|
|
162
|
-
console.log(
|
|
163
|
-
` ${chalk.dim('โ')} ${chalk.italic(issue.suggestion)}`
|
|
164
|
-
);
|
|
165
|
-
console.log();
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (recommendations.length > 0) {
|
|
170
|
-
console.log(chalk.bold('๐ก Recommendations\n'));
|
|
171
|
-
recommendations.forEach((rec: string, i: number) => {
|
|
172
|
-
console.log(`${i + 1}. ${rec}`);
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
console.log();
|
|
176
|
-
}
|
package/src/scoring.ts
CHANGED
|
@@ -1,67 +1,27 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ToolName, buildStandardToolScore } from '@aiready/core';
|
|
2
2
|
import type { TestabilityReport } from './types';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Convert testability report into a ToolScoringOutput for the unified score.
|
|
6
|
-
*
|
|
7
|
-
* @param report - The comprehensive testability report containing raw metrics and summary.
|
|
8
|
-
* @returns Standardized scoring output with impact factors and recommendations.
|
|
9
6
|
*/
|
|
10
|
-
export function calculateTestabilityScore(
|
|
11
|
-
report: TestabilityReport
|
|
12
|
-
): ToolScoringOutput {
|
|
7
|
+
export function calculateTestabilityScore(report: TestabilityReport): any {
|
|
13
8
|
const { summary, rawData, recommendations } = report;
|
|
14
9
|
|
|
15
|
-
|
|
16
|
-
{
|
|
17
|
-
name: 'Test Coverage',
|
|
18
|
-
impact: Math.round(summary.dimensions.testCoverageRatio - 50),
|
|
19
|
-
description: `${rawData.testFiles} test files / ${rawData.sourceFiles} source files (${Math.round(summary.coverageRatio * 100)}%)`,
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
name: 'Function Purity',
|
|
23
|
-
impact: Math.round(summary.dimensions.purityScore - 50),
|
|
24
|
-
description: `${rawData.pureFunctions}/${rawData.totalFunctions} functions are pure`,
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
name: 'Dependency Injection',
|
|
28
|
-
impact: Math.round(summary.dimensions.dependencyInjectionScore - 50),
|
|
29
|
-
description: `${rawData.injectionPatterns}/${rawData.totalClasses} classes use DI`,
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
name: 'Interface Focus',
|
|
33
|
-
impact: Math.round(summary.dimensions.interfaceFocusScore - 50),
|
|
34
|
-
description: `${rawData.bloatedInterfaces} interfaces have >10 methods`,
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
name: 'Observability',
|
|
38
|
-
impact: Math.round(summary.dimensions.observabilityScore - 50),
|
|
39
|
-
description: `${rawData.externalStateMutations} functions mutate external state`,
|
|
40
|
-
},
|
|
41
|
-
];
|
|
42
|
-
|
|
43
|
-
const recs: ToolScoringOutput['recommendations'] = recommendations.map(
|
|
44
|
-
(action) => ({
|
|
45
|
-
action,
|
|
46
|
-
estimatedImpact: summary.aiChangeSafetyRating === 'blind-risk' ? 15 : 8,
|
|
47
|
-
priority:
|
|
48
|
-
summary.aiChangeSafetyRating === 'blind-risk' ||
|
|
49
|
-
summary.aiChangeSafetyRating === 'high-risk'
|
|
50
|
-
? 'high'
|
|
51
|
-
: 'medium',
|
|
52
|
-
})
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
return {
|
|
10
|
+
return buildStandardToolScore({
|
|
56
11
|
toolName: ToolName.TestabilityIndex,
|
|
57
12
|
score: summary.score,
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
13
|
+
rawData,
|
|
14
|
+
dimensions: summary.dimensions,
|
|
15
|
+
dimensionNames: {
|
|
16
|
+
testCoverageRatio: 'Test Coverage',
|
|
17
|
+
purityScore: 'Function Purity',
|
|
18
|
+
dependencyInjectionScore: 'Dependency Injection',
|
|
19
|
+
interfaceFocusScore: 'Interface Focus',
|
|
20
|
+
observabilityScore: 'Observability',
|
|
63
21
|
},
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
22
|
+
recommendations,
|
|
23
|
+
recommendationImpact:
|
|
24
|
+
summary.aiChangeSafetyRating === 'blind-risk' ? 15 : 8,
|
|
25
|
+
rating: summary.aiChangeSafetyRating || summary.rating,
|
|
26
|
+
});
|
|
67
27
|
}
|