@aiready/testability 0.4.3 → 0.4.8
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 +12 -12
- package/.turbo/turbo-test.log +4 -4
- package/dist/chunk-LJLAJRZR.mjs +342 -0
- package/dist/cli.js +4 -2
- package/dist/cli.mjs +1 -1
- package/dist/index.js +4 -2
- package/dist/index.mjs +1 -1
- package/package.json +2 -2
- package/src/analyzer.ts +5 -2
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
-
> @aiready/testability@0.4.
|
|
3
|
+
> @aiready/testability@0.4.7 build /Users/pengcao/projects/aiready/packages/testability
|
|
4
4
|
> tsup src/index.ts src/cli.ts --format cjs,esm --dts
|
|
5
5
|
|
|
6
6
|
[34mCLI[39m Building entry: src/cli.ts, src/index.ts
|
|
@@ -9,16 +9,16 @@
|
|
|
9
9
|
[34mCLI[39m Target: es2020
|
|
10
10
|
[34mCJS[39m Build start
|
|
11
11
|
[34mESM[39m Build start
|
|
12
|
+
[32mCJS[39m [1mdist/index.js [22m[32m13.60 KB[39m
|
|
13
|
+
[32mCJS[39m [1mdist/cli.js [22m[32m18.49 KB[39m
|
|
14
|
+
[32mCJS[39m ⚡️ Build success in 217ms
|
|
12
15
|
[32mESM[39m [1mdist/cli.mjs [22m[32m5.75 KB[39m
|
|
13
16
|
[32mESM[39m [1mdist/index.mjs [22m[32m1.28 KB[39m
|
|
14
|
-
[32mESM[39m [1mdist/chunk-
|
|
15
|
-
[32mESM[39m ⚡️ Build success in
|
|
16
|
-
[
|
|
17
|
-
[
|
|
18
|
-
[
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
DTS dist/index.d.ts 2.78 KB
|
|
23
|
-
DTS dist/cli.d.mts 20.00 B
|
|
24
|
-
DTS dist/index.d.mts 2.78 KB
|
|
17
|
+
[32mESM[39m [1mdist/chunk-LJLAJRZR.mjs [22m[32m11.12 KB[39m
|
|
18
|
+
[32mESM[39m ⚡️ Build success in 222ms
|
|
19
|
+
[34mDTS[39m Build start
|
|
20
|
+
[32mDTS[39m ⚡️ Build success in 3936ms
|
|
21
|
+
[32mDTS[39m [1mdist/cli.d.ts [22m[32m20.00 B[39m
|
|
22
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m2.78 KB[39m
|
|
23
|
+
[32mDTS[39m [1mdist/cli.d.mts [22m[32m20.00 B[39m
|
|
24
|
+
[32mDTS[39m [1mdist/index.d.mts [22m[32m2.78 KB[39m
|
package/.turbo/turbo-test.log
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
-
> @aiready/testability@0.4.
|
|
3
|
+
> @aiready/testability@0.4.7 test /Users/pengcao/projects/aiready/packages/testability
|
|
4
4
|
> vitest run
|
|
5
5
|
|
|
6
6
|
[?25l
|
|
7
7
|
[1m[46m RUN [49m[22m [36mv4.0.18 [39m[90m/Users/pengcao/projects/aiready/packages/testability[39m
|
|
8
8
|
|
|
9
|
-
[32m✓[39m src/__tests__/analyzer.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m
|
|
9
|
+
[32m✓[39m src/__tests__/analyzer.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m 112[2mms[22m[39m
|
|
10
10
|
|
|
11
11
|
[2m Test Files [22m [1m[32m1 passed[39m[22m[90m (1)[39m
|
|
12
12
|
[2m Tests [22m [1m[32m3 passed[39m[22m[90m (3)[39m
|
|
13
|
-
[2m Start at [22m
|
|
14
|
-
[2m Duration [22m
|
|
13
|
+
[2m Start at [22m 13:15:43
|
|
14
|
+
[2m Duration [22m 1.61s[2m (transform 204ms, setup 0ms, import 1.14s, tests 112ms, environment 0ms)[22m
|
|
15
15
|
|
|
16
16
|
[?25h
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
// src/analyzer.ts
|
|
2
|
+
import {
|
|
3
|
+
scanFiles,
|
|
4
|
+
calculateTestabilityIndex,
|
|
5
|
+
Severity,
|
|
6
|
+
IssueType,
|
|
7
|
+
emitProgress
|
|
8
|
+
} from "@aiready/core";
|
|
9
|
+
import { readFileSync, existsSync } from "fs";
|
|
10
|
+
import { join } from "path";
|
|
11
|
+
import { parse } from "@typescript-eslint/typescript-estree";
|
|
12
|
+
function countMethodsInInterface(node) {
|
|
13
|
+
if (node.type === "TSInterfaceDeclaration") {
|
|
14
|
+
return node.body.body.filter(
|
|
15
|
+
(m) => m.type === "TSMethodSignature" || m.type === "TSPropertySignature"
|
|
16
|
+
).length;
|
|
17
|
+
}
|
|
18
|
+
if (node.type === "TSTypeAliasDeclaration" && node.typeAnnotation.type === "TSTypeLiteral") {
|
|
19
|
+
return node.typeAnnotation.members.length;
|
|
20
|
+
}
|
|
21
|
+
return 0;
|
|
22
|
+
}
|
|
23
|
+
function hasDependencyInjection(node) {
|
|
24
|
+
for (const member of node.body.body) {
|
|
25
|
+
if (member.type === "MethodDefinition" && member.key.type === "Identifier" && member.key.name === "constructor") {
|
|
26
|
+
const fn = member.value;
|
|
27
|
+
if (fn.params && fn.params.length > 0) {
|
|
28
|
+
const typedParams = fn.params.filter((p) => {
|
|
29
|
+
const param = p;
|
|
30
|
+
return param.typeAnnotation != null || param.parameter?.typeAnnotation != null;
|
|
31
|
+
});
|
|
32
|
+
if (typedParams.length > 0) return true;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
function isPureFunction(fn) {
|
|
39
|
+
let hasReturn = false;
|
|
40
|
+
let hasSideEffect = false;
|
|
41
|
+
function walk(node) {
|
|
42
|
+
if (node.type === "ReturnStatement" && node.argument) hasReturn = true;
|
|
43
|
+
if (node.type === "AssignmentExpression" && node.left.type === "MemberExpression")
|
|
44
|
+
hasSideEffect = true;
|
|
45
|
+
if (node.type === "CallExpression" && node.callee.type === "MemberExpression" && node.callee.object.type === "Identifier" && ["console", "process", "window", "document", "fs"].includes(
|
|
46
|
+
node.callee.object.name
|
|
47
|
+
))
|
|
48
|
+
hasSideEffect = true;
|
|
49
|
+
for (const key of Object.keys(node)) {
|
|
50
|
+
if (key === "parent") continue;
|
|
51
|
+
const child = node[key];
|
|
52
|
+
if (child && typeof child === "object") {
|
|
53
|
+
if (Array.isArray(child)) {
|
|
54
|
+
child.forEach((c) => c?.type && walk(c));
|
|
55
|
+
} else if (child.type) {
|
|
56
|
+
walk(child);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (fn.body?.type === "BlockStatement") {
|
|
62
|
+
fn.body.body.forEach((s) => walk(s));
|
|
63
|
+
} else if (fn.body) {
|
|
64
|
+
hasReturn = true;
|
|
65
|
+
}
|
|
66
|
+
return hasReturn && !hasSideEffect;
|
|
67
|
+
}
|
|
68
|
+
function hasExternalStateMutation(fn) {
|
|
69
|
+
let found = false;
|
|
70
|
+
function walk(node) {
|
|
71
|
+
if (found) return;
|
|
72
|
+
if (node.type === "AssignmentExpression" && node.left.type === "MemberExpression")
|
|
73
|
+
found = true;
|
|
74
|
+
for (const key of Object.keys(node)) {
|
|
75
|
+
if (key === "parent") continue;
|
|
76
|
+
const child = node[key];
|
|
77
|
+
if (child && typeof child === "object") {
|
|
78
|
+
if (Array.isArray(child)) child.forEach((c) => c?.type && walk(c));
|
|
79
|
+
else if (child.type) walk(child);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (fn.body?.type === "BlockStatement") fn.body.body.forEach((s) => walk(s));
|
|
84
|
+
return found;
|
|
85
|
+
}
|
|
86
|
+
function analyzeFileTestability(filePath) {
|
|
87
|
+
const result = {
|
|
88
|
+
pureFunctions: 0,
|
|
89
|
+
totalFunctions: 0,
|
|
90
|
+
injectionPatterns: 0,
|
|
91
|
+
totalClasses: 0,
|
|
92
|
+
bloatedInterfaces: 0,
|
|
93
|
+
totalInterfaces: 0,
|
|
94
|
+
externalStateMutations: 0
|
|
95
|
+
};
|
|
96
|
+
let code;
|
|
97
|
+
try {
|
|
98
|
+
code = readFileSync(filePath, "utf-8");
|
|
99
|
+
} catch {
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
let ast;
|
|
103
|
+
try {
|
|
104
|
+
ast = parse(code, {
|
|
105
|
+
jsx: filePath.endsWith(".tsx") || filePath.endsWith(".jsx"),
|
|
106
|
+
range: false,
|
|
107
|
+
loc: false
|
|
108
|
+
});
|
|
109
|
+
} catch {
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
function visit(node) {
|
|
113
|
+
if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") {
|
|
114
|
+
result.totalFunctions++;
|
|
115
|
+
if (isPureFunction(node)) result.pureFunctions++;
|
|
116
|
+
if (hasExternalStateMutation(node)) result.externalStateMutations++;
|
|
117
|
+
}
|
|
118
|
+
if (node.type === "ClassDeclaration" || node.type === "ClassExpression") {
|
|
119
|
+
result.totalClasses++;
|
|
120
|
+
if (hasDependencyInjection(node)) result.injectionPatterns++;
|
|
121
|
+
}
|
|
122
|
+
if (node.type === "TSInterfaceDeclaration" || node.type === "TSTypeAliasDeclaration") {
|
|
123
|
+
result.totalInterfaces++;
|
|
124
|
+
const methodCount = countMethodsInInterface(node);
|
|
125
|
+
if (methodCount > 10) result.bloatedInterfaces++;
|
|
126
|
+
}
|
|
127
|
+
for (const key of Object.keys(node)) {
|
|
128
|
+
if (key === "parent") continue;
|
|
129
|
+
const child = node[key];
|
|
130
|
+
if (child && typeof child === "object") {
|
|
131
|
+
if (Array.isArray(child)) child.forEach((c) => c?.type && visit(c));
|
|
132
|
+
else if (child.type) visit(child);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
ast.body.forEach(visit);
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
function detectTestFramework(rootDir) {
|
|
140
|
+
const pkgPath = join(rootDir, "package.json");
|
|
141
|
+
if (!existsSync(pkgPath)) return false;
|
|
142
|
+
try {
|
|
143
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
144
|
+
const allDeps = {
|
|
145
|
+
...pkg.dependencies ?? {},
|
|
146
|
+
...pkg.devDependencies ?? {}
|
|
147
|
+
};
|
|
148
|
+
const testFrameworks = [
|
|
149
|
+
"jest",
|
|
150
|
+
"vitest",
|
|
151
|
+
"mocha",
|
|
152
|
+
"jasmine",
|
|
153
|
+
"ava",
|
|
154
|
+
"tap",
|
|
155
|
+
"pytest",
|
|
156
|
+
"unittest"
|
|
157
|
+
];
|
|
158
|
+
return testFrameworks.some((fw) => allDeps[fw]);
|
|
159
|
+
} catch {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
var TEST_PATTERNS = [
|
|
164
|
+
/\.(test|spec)\.(ts|tsx|js|jsx)$/,
|
|
165
|
+
/__tests__\//,
|
|
166
|
+
/\/tests?\//,
|
|
167
|
+
/\/e2e\//,
|
|
168
|
+
/\/fixtures\//
|
|
169
|
+
];
|
|
170
|
+
function isTestFile(filePath, extra) {
|
|
171
|
+
if (TEST_PATTERNS.some((p) => p.test(filePath))) return true;
|
|
172
|
+
if (extra) return extra.some((p) => filePath.includes(p));
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
async function analyzeTestability(options) {
|
|
176
|
+
const allFiles = await scanFiles({
|
|
177
|
+
...options,
|
|
178
|
+
include: options.include || ["**/*.{ts,tsx,js,jsx}"],
|
|
179
|
+
includeTests: true
|
|
180
|
+
});
|
|
181
|
+
const sourceFiles = allFiles.filter(
|
|
182
|
+
(f) => !isTestFile(f, options.testPatterns)
|
|
183
|
+
);
|
|
184
|
+
const testFiles = allFiles.filter((f) => isTestFile(f, options.testPatterns));
|
|
185
|
+
const aggregated = {
|
|
186
|
+
pureFunctions: 0,
|
|
187
|
+
totalFunctions: 0,
|
|
188
|
+
injectionPatterns: 0,
|
|
189
|
+
totalClasses: 0,
|
|
190
|
+
bloatedInterfaces: 0,
|
|
191
|
+
totalInterfaces: 0,
|
|
192
|
+
externalStateMutations: 0
|
|
193
|
+
};
|
|
194
|
+
let processed = 0;
|
|
195
|
+
for (const f of sourceFiles) {
|
|
196
|
+
processed++;
|
|
197
|
+
emitProgress(
|
|
198
|
+
processed,
|
|
199
|
+
sourceFiles.length,
|
|
200
|
+
"testability",
|
|
201
|
+
"analyzing files",
|
|
202
|
+
options.onProgress
|
|
203
|
+
);
|
|
204
|
+
const a = analyzeFileTestability(f);
|
|
205
|
+
for (const key of Object.keys(aggregated)) {
|
|
206
|
+
aggregated[key] += a[key];
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const hasTestFramework = detectTestFramework(options.rootDir);
|
|
210
|
+
const indexResult = calculateTestabilityIndex({
|
|
211
|
+
testFiles: testFiles.length,
|
|
212
|
+
sourceFiles: sourceFiles.length,
|
|
213
|
+
pureFunctions: aggregated.pureFunctions,
|
|
214
|
+
totalFunctions: Math.max(1, aggregated.totalFunctions),
|
|
215
|
+
injectionPatterns: aggregated.injectionPatterns,
|
|
216
|
+
totalClasses: Math.max(1, aggregated.totalClasses),
|
|
217
|
+
bloatedInterfaces: aggregated.bloatedInterfaces,
|
|
218
|
+
totalInterfaces: Math.max(1, aggregated.totalInterfaces),
|
|
219
|
+
externalStateMutations: aggregated.externalStateMutations,
|
|
220
|
+
hasTestFramework
|
|
221
|
+
});
|
|
222
|
+
const issues = [];
|
|
223
|
+
const minCoverage = options.minCoverageRatio ?? 0.3;
|
|
224
|
+
const actualRatio = sourceFiles.length > 0 ? testFiles.length / sourceFiles.length : 0;
|
|
225
|
+
if (!hasTestFramework) {
|
|
226
|
+
issues.push({
|
|
227
|
+
type: IssueType.LowTestability,
|
|
228
|
+
dimension: "framework",
|
|
229
|
+
severity: Severity.Critical,
|
|
230
|
+
message: "No testing framework detected in package.json \u2014 AI changes cannot be verified at all.",
|
|
231
|
+
location: { file: options.rootDir, line: 0 },
|
|
232
|
+
suggestion: "Add Jest, Vitest, or another testing framework as a devDependency."
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
if (actualRatio < minCoverage) {
|
|
236
|
+
const needed = Math.ceil(sourceFiles.length * minCoverage) - testFiles.length;
|
|
237
|
+
issues.push({
|
|
238
|
+
type: IssueType.LowTestability,
|
|
239
|
+
dimension: "test-coverage",
|
|
240
|
+
severity: actualRatio === 0 ? Severity.Critical : Severity.Major,
|
|
241
|
+
message: `Test ratio is ${Math.round(actualRatio * 100)}% (${testFiles.length} test files for ${sourceFiles.length} source files). Need at least ${Math.round(minCoverage * 100)}%.`,
|
|
242
|
+
location: { file: options.rootDir, line: 0 },
|
|
243
|
+
suggestion: `Add ~${needed} test file(s) to reach the ${Math.round(minCoverage * 100)}% minimum for safe AI assistance.`
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
if (indexResult.dimensions.purityScore < 50) {
|
|
247
|
+
issues.push({
|
|
248
|
+
type: IssueType.LowTestability,
|
|
249
|
+
dimension: "purity",
|
|
250
|
+
severity: Severity.Major,
|
|
251
|
+
message: `Only ${indexResult.dimensions.purityScore}% of functions are pure \u2014 side-effectful functions require complex test setup.`,
|
|
252
|
+
location: { file: options.rootDir, line: 0 },
|
|
253
|
+
suggestion: "Extract pure transformation logic from I/O and mutation code."
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
if (indexResult.dimensions.observabilityScore < 50) {
|
|
257
|
+
issues.push({
|
|
258
|
+
type: IssueType.LowTestability,
|
|
259
|
+
dimension: "observability",
|
|
260
|
+
severity: Severity.Major,
|
|
261
|
+
message: `Many functions mutate external state directly \u2014 outputs are invisible to unit tests.`,
|
|
262
|
+
location: { file: options.rootDir, line: 0 },
|
|
263
|
+
suggestion: "Prefer returning values over mutating shared state."
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
summary: {
|
|
268
|
+
sourceFiles: sourceFiles.length,
|
|
269
|
+
testFiles: testFiles.length,
|
|
270
|
+
coverageRatio: Math.round(actualRatio * 100) / 100,
|
|
271
|
+
score: indexResult.score,
|
|
272
|
+
rating: indexResult.rating,
|
|
273
|
+
aiChangeSafetyRating: indexResult.aiChangeSafetyRating,
|
|
274
|
+
dimensions: indexResult.dimensions
|
|
275
|
+
},
|
|
276
|
+
issues,
|
|
277
|
+
rawData: {
|
|
278
|
+
sourceFiles: sourceFiles.length,
|
|
279
|
+
testFiles: testFiles.length,
|
|
280
|
+
...aggregated,
|
|
281
|
+
hasTestFramework
|
|
282
|
+
},
|
|
283
|
+
recommendations: indexResult.recommendations
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// src/scoring.ts
|
|
288
|
+
import { ToolName } from "@aiready/core";
|
|
289
|
+
function calculateTestabilityScore(report) {
|
|
290
|
+
const { summary, rawData, recommendations } = report;
|
|
291
|
+
const factors = [
|
|
292
|
+
{
|
|
293
|
+
name: "Test Coverage",
|
|
294
|
+
impact: Math.round(summary.dimensions.testCoverageRatio - 50),
|
|
295
|
+
description: `${rawData.testFiles} test files / ${rawData.sourceFiles} source files (${Math.round(summary.coverageRatio * 100)}%)`
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
name: "Function Purity",
|
|
299
|
+
impact: Math.round(summary.dimensions.purityScore - 50),
|
|
300
|
+
description: `${rawData.pureFunctions}/${rawData.totalFunctions} functions are pure`
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
name: "Dependency Injection",
|
|
304
|
+
impact: Math.round(summary.dimensions.dependencyInjectionScore - 50),
|
|
305
|
+
description: `${rawData.injectionPatterns}/${rawData.totalClasses} classes use DI`
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
name: "Interface Focus",
|
|
309
|
+
impact: Math.round(summary.dimensions.interfaceFocusScore - 50),
|
|
310
|
+
description: `${rawData.bloatedInterfaces} interfaces have >10 methods`
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
name: "Observability",
|
|
314
|
+
impact: Math.round(summary.dimensions.observabilityScore - 50),
|
|
315
|
+
description: `${rawData.externalStateMutations} functions mutate external state`
|
|
316
|
+
}
|
|
317
|
+
];
|
|
318
|
+
const recs = recommendations.map(
|
|
319
|
+
(action) => ({
|
|
320
|
+
action,
|
|
321
|
+
estimatedImpact: summary.aiChangeSafetyRating === "blind-risk" ? 15 : 8,
|
|
322
|
+
priority: summary.aiChangeSafetyRating === "blind-risk" || summary.aiChangeSafetyRating === "high-risk" ? "high" : "medium"
|
|
323
|
+
})
|
|
324
|
+
);
|
|
325
|
+
return {
|
|
326
|
+
toolName: ToolName.TestabilityIndex,
|
|
327
|
+
score: summary.score,
|
|
328
|
+
rawMetrics: {
|
|
329
|
+
...rawData,
|
|
330
|
+
rating: summary.rating,
|
|
331
|
+
aiChangeSafetyRating: summary.aiChangeSafetyRating,
|
|
332
|
+
coverageRatio: summary.coverageRatio
|
|
333
|
+
},
|
|
334
|
+
factors,
|
|
335
|
+
recommendations: recs
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export {
|
|
340
|
+
analyzeTestability,
|
|
341
|
+
calculateTestabilityScore
|
|
342
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -216,10 +216,12 @@ async function analyzeTestability(options) {
|
|
|
216
216
|
let processed = 0;
|
|
217
217
|
for (const f of sourceFiles) {
|
|
218
218
|
processed++;
|
|
219
|
-
|
|
219
|
+
(0, import_core.emitProgress)(
|
|
220
220
|
processed,
|
|
221
221
|
sourceFiles.length,
|
|
222
|
-
|
|
222
|
+
"testability",
|
|
223
|
+
"analyzing files",
|
|
224
|
+
options.onProgress
|
|
223
225
|
);
|
|
224
226
|
const a = analyzeFileTestability(f);
|
|
225
227
|
for (const key of Object.keys(aggregated)) {
|
package/dist/cli.mjs
CHANGED
package/dist/index.js
CHANGED
|
@@ -220,10 +220,12 @@ async function analyzeTestability(options) {
|
|
|
220
220
|
let processed = 0;
|
|
221
221
|
for (const f of sourceFiles) {
|
|
222
222
|
processed++;
|
|
223
|
-
|
|
223
|
+
(0, import_core.emitProgress)(
|
|
224
224
|
processed,
|
|
225
225
|
sourceFiles.length,
|
|
226
|
-
|
|
226
|
+
"testability",
|
|
227
|
+
"analyzing files",
|
|
228
|
+
options.onProgress
|
|
227
229
|
);
|
|
228
230
|
const a = analyzeFileTestability(f);
|
|
229
231
|
for (const key of Object.keys(aggregated)) {
|
package/dist/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiready/testability",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.8",
|
|
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.21.
|
|
43
|
+
"@aiready/core": "0.21.8"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@types/node": "^24.0.0",
|
package/src/analyzer.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
calculateTestabilityIndex,
|
|
16
16
|
Severity,
|
|
17
17
|
IssueType,
|
|
18
|
+
emitProgress,
|
|
18
19
|
} from '@aiready/core';
|
|
19
20
|
import { readFileSync, existsSync } from 'fs';
|
|
20
21
|
import { join } from 'path';
|
|
@@ -306,10 +307,12 @@ export async function analyzeTestability(
|
|
|
306
307
|
let processed = 0;
|
|
307
308
|
for (const f of sourceFiles) {
|
|
308
309
|
processed++;
|
|
309
|
-
|
|
310
|
+
emitProgress(
|
|
310
311
|
processed,
|
|
311
312
|
sourceFiles.length,
|
|
312
|
-
|
|
313
|
+
'testability',
|
|
314
|
+
'analyzing files',
|
|
315
|
+
options.onProgress
|
|
313
316
|
);
|
|
314
317
|
|
|
315
318
|
const a = analyzeFileTestability(f);
|