@aiready/testability 0.6.0 → 0.6.2

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.
@@ -0,0 +1,270 @@
1
+ {
2
+ "summary": {
3
+ "totalIssues": 0,
4
+ "criticalIssues": 0,
5
+ "majorIssues": 0,
6
+ "totalFiles": 0,
7
+ "toolsRun": ["testability-index"],
8
+ "executionTime": 41,
9
+ "config": {
10
+ "scan": {
11
+ "tools": ["testability-index"]
12
+ },
13
+ "tools": {
14
+ "testability-index": {}
15
+ }
16
+ },
17
+ "toolConfigs": {
18
+ "testability-index": {}
19
+ }
20
+ },
21
+ "testability-index": {
22
+ "results": [],
23
+ "summary": {
24
+ "score": 47,
25
+ "sourceFiles": 6,
26
+ "testFiles": 4,
27
+ "coverageRatio": 0.67,
28
+ "rating": "poor",
29
+ "aiChangeSafetyRating": "high-risk",
30
+ "dimensions": {
31
+ "testCoverageRatio": 67,
32
+ "purityScore": 50,
33
+ "dependencyInjectionScore": 0,
34
+ "interfaceFocusScore": 73,
35
+ "observabilityScore": 50
36
+ }
37
+ },
38
+ "metadata": {
39
+ "toolName": "testability-index",
40
+ "version": "0.2.5",
41
+ "timestamp": "2026-03-14T11:52:28.204Z",
42
+ "rawData": {
43
+ "sourceFiles": 6,
44
+ "testFiles": 4,
45
+ "pureFunctions": 1,
46
+ "totalFunctions": 2,
47
+ "injectionPatterns": 0,
48
+ "totalClasses": 0,
49
+ "bloatedInterfaces": 1,
50
+ "totalInterfaces": 3,
51
+ "externalStateMutations": 1,
52
+ "hasTestFramework": true
53
+ },
54
+ "config": {}
55
+ }
56
+ },
57
+ "testability": {
58
+ "results": [],
59
+ "summary": {
60
+ "score": 47,
61
+ "sourceFiles": 6,
62
+ "testFiles": 4,
63
+ "coverageRatio": 0.67,
64
+ "rating": "poor",
65
+ "aiChangeSafetyRating": "high-risk",
66
+ "dimensions": {
67
+ "testCoverageRatio": 67,
68
+ "purityScore": 50,
69
+ "dependencyInjectionScore": 0,
70
+ "interfaceFocusScore": 73,
71
+ "observabilityScore": 50
72
+ }
73
+ },
74
+ "metadata": {
75
+ "toolName": "testability-index",
76
+ "version": "0.2.5",
77
+ "timestamp": "2026-03-14T11:52:28.204Z",
78
+ "rawData": {
79
+ "sourceFiles": 6,
80
+ "testFiles": 4,
81
+ "pureFunctions": 1,
82
+ "totalFunctions": 2,
83
+ "injectionPatterns": 0,
84
+ "totalClasses": 0,
85
+ "bloatedInterfaces": 1,
86
+ "totalInterfaces": 3,
87
+ "externalStateMutations": 1,
88
+ "hasTestFramework": true
89
+ },
90
+ "config": {}
91
+ }
92
+ },
93
+ "tests": {
94
+ "results": [],
95
+ "summary": {
96
+ "score": 47,
97
+ "sourceFiles": 6,
98
+ "testFiles": 4,
99
+ "coverageRatio": 0.67,
100
+ "rating": "poor",
101
+ "aiChangeSafetyRating": "high-risk",
102
+ "dimensions": {
103
+ "testCoverageRatio": 67,
104
+ "purityScore": 50,
105
+ "dependencyInjectionScore": 0,
106
+ "interfaceFocusScore": 73,
107
+ "observabilityScore": 50
108
+ }
109
+ },
110
+ "metadata": {
111
+ "toolName": "testability-index",
112
+ "version": "0.2.5",
113
+ "timestamp": "2026-03-14T11:52:28.204Z",
114
+ "rawData": {
115
+ "sourceFiles": 6,
116
+ "testFiles": 4,
117
+ "pureFunctions": 1,
118
+ "totalFunctions": 2,
119
+ "injectionPatterns": 0,
120
+ "totalClasses": 0,
121
+ "bloatedInterfaces": 1,
122
+ "totalInterfaces": 3,
123
+ "externalStateMutations": 1,
124
+ "hasTestFramework": true
125
+ },
126
+ "config": {}
127
+ }
128
+ },
129
+ "verification": {
130
+ "results": [],
131
+ "summary": {
132
+ "score": 47,
133
+ "sourceFiles": 6,
134
+ "testFiles": 4,
135
+ "coverageRatio": 0.67,
136
+ "rating": "poor",
137
+ "aiChangeSafetyRating": "high-risk",
138
+ "dimensions": {
139
+ "testCoverageRatio": 67,
140
+ "purityScore": 50,
141
+ "dependencyInjectionScore": 0,
142
+ "interfaceFocusScore": 73,
143
+ "observabilityScore": 50
144
+ }
145
+ },
146
+ "metadata": {
147
+ "toolName": "testability-index",
148
+ "version": "0.2.5",
149
+ "timestamp": "2026-03-14T11:52:28.204Z",
150
+ "rawData": {
151
+ "sourceFiles": 6,
152
+ "testFiles": 4,
153
+ "pureFunctions": 1,
154
+ "totalFunctions": 2,
155
+ "injectionPatterns": 0,
156
+ "totalClasses": 0,
157
+ "bloatedInterfaces": 1,
158
+ "totalInterfaces": 3,
159
+ "externalStateMutations": 1,
160
+ "hasTestFramework": true
161
+ },
162
+ "config": {}
163
+ }
164
+ },
165
+ "testabilityIndex": {
166
+ "results": [],
167
+ "summary": {
168
+ "score": 47,
169
+ "sourceFiles": 6,
170
+ "testFiles": 4,
171
+ "coverageRatio": 0.67,
172
+ "rating": "poor",
173
+ "aiChangeSafetyRating": "high-risk",
174
+ "dimensions": {
175
+ "testCoverageRatio": 67,
176
+ "purityScore": 50,
177
+ "dependencyInjectionScore": 0,
178
+ "interfaceFocusScore": 73,
179
+ "observabilityScore": 50
180
+ }
181
+ },
182
+ "metadata": {
183
+ "toolName": "testability-index",
184
+ "version": "0.2.5",
185
+ "timestamp": "2026-03-14T11:52:28.204Z",
186
+ "rawData": {
187
+ "sourceFiles": 6,
188
+ "testFiles": 4,
189
+ "pureFunctions": 1,
190
+ "totalFunctions": 2,
191
+ "injectionPatterns": 0,
192
+ "totalClasses": 0,
193
+ "bloatedInterfaces": 1,
194
+ "totalInterfaces": 3,
195
+ "externalStateMutations": 1,
196
+ "hasTestFramework": true
197
+ },
198
+ "config": {}
199
+ }
200
+ },
201
+ "results": [],
202
+ "scoring": {
203
+ "overall": 47,
204
+ "rating": "Needs Work",
205
+ "timestamp": "2026-03-14T11:52:28.205Z",
206
+ "toolsUsed": ["testability-index"],
207
+ "breakdown": [
208
+ {
209
+ "toolName": "testability-index",
210
+ "score": 47,
211
+ "rawMetrics": {
212
+ "sourceFiles": 6,
213
+ "testFiles": 4,
214
+ "pureFunctions": 1,
215
+ "totalFunctions": 2,
216
+ "injectionPatterns": 0,
217
+ "totalClasses": 0,
218
+ "bloatedInterfaces": 1,
219
+ "totalInterfaces": 3,
220
+ "externalStateMutations": 1,
221
+ "hasTestFramework": true,
222
+ "rating": "poor",
223
+ "aiChangeSafetyRating": "high-risk",
224
+ "coverageRatio": 0.67
225
+ },
226
+ "factors": [
227
+ {
228
+ "name": "Test Coverage",
229
+ "impact": 17,
230
+ "description": "4 test files / 6 source files (67%)"
231
+ },
232
+ {
233
+ "name": "Function Purity",
234
+ "impact": 0,
235
+ "description": "1/2 functions are pure"
236
+ },
237
+ {
238
+ "name": "Dependency Injection",
239
+ "impact": -50,
240
+ "description": "0/0 classes use DI"
241
+ },
242
+ {
243
+ "name": "Interface Focus",
244
+ "impact": 23,
245
+ "description": "1 interfaces have >10 methods"
246
+ },
247
+ {
248
+ "name": "Observability",
249
+ "impact": 0,
250
+ "description": "1 functions mutate external state"
251
+ }
252
+ ],
253
+ "recommendations": []
254
+ }
255
+ ],
256
+ "calculation": {
257
+ "formula": "[(47 × 10)] / 10 = 47",
258
+ "weights": {
259
+ "testability-index": 10
260
+ },
261
+ "normalized": "[(47 × 10)] / 10 = 47"
262
+ }
263
+ },
264
+ "repository": {
265
+ "url": "https://github.com/caopengau/aiready.git",
266
+ "branch": "main",
267
+ "commit": "3be4936e1c8aa50d6324b014ca8fcf791c6dcfaf",
268
+ "author": "caopengau@gmail.com"
269
+ }
270
+ }
@@ -1,6 +1,6 @@
1
1
 
2
2
  
3
- > @aiready/testability@0.5.0 build /Users/pengcao/projects/aiready/packages/testability
3
+ > @aiready/testability@0.6.2 build /Users/pengcao/projects/aiready/packages/testability
4
4
  > tsup src/index.ts src/cli.ts --format cjs,esm --dts
5
5
 
6
6
  CLI Building entry: src/cli.ts, src/index.ts
@@ -10,15 +10,15 @@
10
10
  CJS Build start
11
11
  ESM Build start
12
12
  CJS dist/cli.js 15.61 KB
13
- CJS dist/index.js 10.73 KB
14
- CJS ⚡️ Build success in 73ms
13
+ CJS dist/index.js 10.61 KB
14
+ CJS ⚡️ Build success in 82ms
15
15
  ESM dist/cli.mjs 5.75 KB
16
- ESM dist/index.mjs 1.28 KB
17
16
  ESM dist/chunk-QOIBI5E7.mjs 8.31 KB
18
- ESM ⚡️ Build success in 77ms
17
+ ESM dist/index.mjs 1.17 KB
18
+ ESM ⚡️ Build success in 83ms
19
19
  DTS Build start
20
- DTS ⚡️ Build success in 1201ms
20
+ DTS ⚡️ Build success in 4018ms
21
21
  DTS dist/cli.d.ts 20.00 B
22
- DTS dist/index.d.ts 2.41 KB
22
+ DTS dist/index.d.ts 2.45 KB
23
23
  DTS dist/cli.d.mts 20.00 B
24
- DTS dist/index.d.mts 2.41 KB
24
+ DTS dist/index.d.mts 2.45 KB
@@ -1,19 +1,20 @@
1
1
 
2
2
  
3
- > @aiready/testability@0.5.0 test /Users/pengcao/projects/aiready/packages/testability
3
+ > @aiready/testability@0.6.1 test /Users/pengcao/projects/aiready/packages/testability
4
4
  > vitest run
5
5
 
6
6
  [?25l
7
7
   RUN  v4.0.18 /Users/pengcao/projects/aiready/packages/testability
8
8
 
9
- ✓ src/__tests__/scoring.test.ts (3 tests) 2ms
9
+ ✓ src/__tests__/types.test.ts (4 tests) 7ms
10
10
  ✓ src/__tests__/provider.test.ts (2 tests) 3ms
11
- ✓ src/__tests__/analyzer.test.ts (5 tests) 791ms
12
- ✓ detects test frameworks in multiple languages  766ms
11
+ ✓ src/__tests__/scoring.test.ts (3 tests) 2ms
12
+ ✓ src/__tests__/analyzer.test.ts (5 tests) 1716ms
13
+ ✓ detects test frameworks in multiple languages  1688ms
13
14
 
14
-  Test Files  3 passed (3)
15
-  Tests  10 passed (10)
16
-  Start at  16:14:23
17
-  Duration  1.47s (transform 581ms, setup 0ms, import 1.46s, tests 796ms, environment 0ms)
15
+  Test Files  4 passed (4)
16
+  Tests  14 passed (14)
17
+  Start at  02:00:17
18
+  Duration  4.74s (transform 1.12s, setup 0ms, import 6.16s, tests 1.73s, environment 0ms)
18
19
 
19
20
  [?25h
package/dist/index.d.mts CHANGED
@@ -1,9 +1,10 @@
1
- import { ToolProvider, Issue, IssueType, ToolScoringOutput } from '@aiready/core';
1
+ import * as _aiready_core from '@aiready/core';
2
+ import { Issue, IssueType, ToolScoringOutput } from '@aiready/core';
2
3
 
3
4
  /**
4
5
  * Testability Tool Provider
5
6
  */
6
- declare const TestabilityProvider: ToolProvider;
7
+ declare const TestabilityProvider: _aiready_core.ToolProvider;
7
8
 
8
9
  interface TestabilityOptions {
9
10
  /** Root directory to scan */
package/dist/index.d.ts CHANGED
@@ -1,9 +1,10 @@
1
- import { ToolProvider, Issue, IssueType, ToolScoringOutput } from '@aiready/core';
1
+ import * as _aiready_core from '@aiready/core';
2
+ import { Issue, IssueType, ToolScoringOutput } from '@aiready/core';
2
3
 
3
4
  /**
4
5
  * Testability Tool Provider
5
6
  */
6
- declare const TestabilityProvider: ToolProvider;
7
+ declare const TestabilityProvider: _aiready_core.ToolProvider;
7
8
 
8
9
  interface TestabilityOptions {
9
10
  /** Root directory to scan */
package/dist/index.js CHANGED
@@ -282,39 +282,38 @@ function calculateTestabilityScore(report) {
282
282
  }
283
283
 
284
284
  // src/provider.ts
285
- var TestabilityProvider = {
285
+ var TestabilityProvider = (0, import_core3.createProvider)({
286
286
  id: import_core3.ToolName.TestabilityIndex,
287
287
  alias: ["testability", "tests", "verification"],
288
- async analyze(options) {
289
- const report = await analyzeTestability(options);
290
- const results = report.issues.map((i) => ({
291
- fileName: i.location.file,
292
- issues: [i],
288
+ version: "0.2.5",
289
+ defaultWeight: 10,
290
+ async analyzeReport(options) {
291
+ return analyzeTestability(options);
292
+ },
293
+ getResults(report) {
294
+ return report.issues.map((issue) => ({
295
+ fileName: issue.location.file,
296
+ issues: [issue],
293
297
  metrics: {
294
298
  testabilityScore: report.summary.score
295
299
  }
296
300
  }));
297
- return import_core3.SpokeOutputSchema.parse({
298
- results,
299
- summary: report.summary,
300
- metadata: {
301
- toolName: import_core3.ToolName.TestabilityIndex,
302
- version: "0.2.5",
303
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
304
- rawData: report.rawData
305
- }
306
- });
307
301
  },
308
- score(output, options) {
302
+ getSummary(report) {
303
+ return report.summary;
304
+ },
305
+ getMetadata(report) {
306
+ return { rawData: report.rawData };
307
+ },
308
+ score(output) {
309
309
  const report = {
310
310
  summary: output.summary,
311
311
  rawData: output.metadata.rawData,
312
312
  recommendations: output.summary.recommendations || []
313
313
  };
314
314
  return calculateTestabilityScore(report);
315
- },
316
- defaultWeight: 10
317
- };
315
+ }
316
+ });
318
317
 
319
318
  // src/index.ts
320
319
  import_core4.ToolRegistry.register(TestabilityProvider);
package/dist/index.mjs CHANGED
@@ -8,42 +8,41 @@ import { ToolRegistry } from "@aiready/core";
8
8
 
9
9
  // src/provider.ts
10
10
  import {
11
- ToolName,
12
- SpokeOutputSchema
11
+ createProvider,
12
+ ToolName
13
13
  } from "@aiready/core";
14
- var TestabilityProvider = {
14
+ var TestabilityProvider = createProvider({
15
15
  id: ToolName.TestabilityIndex,
16
16
  alias: ["testability", "tests", "verification"],
17
- async analyze(options) {
18
- const report = await analyzeTestability(options);
19
- const results = report.issues.map((i) => ({
20
- fileName: i.location.file,
21
- issues: [i],
17
+ version: "0.2.5",
18
+ defaultWeight: 10,
19
+ async analyzeReport(options) {
20
+ return analyzeTestability(options);
21
+ },
22
+ getResults(report) {
23
+ return report.issues.map((issue) => ({
24
+ fileName: issue.location.file,
25
+ issues: [issue],
22
26
  metrics: {
23
27
  testabilityScore: report.summary.score
24
28
  }
25
29
  }));
26
- return SpokeOutputSchema.parse({
27
- results,
28
- summary: report.summary,
29
- metadata: {
30
- toolName: ToolName.TestabilityIndex,
31
- version: "0.2.5",
32
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
33
- rawData: report.rawData
34
- }
35
- });
36
30
  },
37
- score(output, options) {
31
+ getSummary(report) {
32
+ return report.summary;
33
+ },
34
+ getMetadata(report) {
35
+ return { rawData: report.rawData };
36
+ },
37
+ score(output) {
38
38
  const report = {
39
39
  summary: output.summary,
40
40
  rawData: output.metadata.rawData,
41
41
  recommendations: output.summary.recommendations || []
42
42
  };
43
43
  return calculateTestabilityScore(report);
44
- },
45
- defaultWeight: 10
46
- };
44
+ }
45
+ });
47
46
 
48
47
  // src/index.ts
49
48
  ToolRegistry.register(TestabilityProvider);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiready/testability",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
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.0"
43
+ "@aiready/core": "0.23.2"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@types/node": "^24.0.0",
@@ -18,7 +18,7 @@ describe('Testability Provider', () => {
18
18
  const output = await TestabilityProvider.analyze({ rootDir: '.' });
19
19
 
20
20
  expect(output.summary.score).toBe(90);
21
- expect(output.metadata.toolName).toBe('testability-index');
21
+ expect(output.metadata!.toolName).toBe('testability-index');
22
22
  });
23
23
 
24
24
  it('should score an output', () => {
@@ -0,0 +1,72 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import type { TestabilityOptions, TestabilityReport } from '../types';
3
+
4
+ describe('Testability Types', () => {
5
+ describe('TestabilityOptions', () => {
6
+ it('should allow required rootDir', () => {
7
+ const options: TestabilityOptions = {
8
+ rootDir: '/test/project',
9
+ };
10
+
11
+ expect(options.rootDir).toBe('/test/project');
12
+ });
13
+
14
+ it('should allow optional minCoverageRatio', () => {
15
+ const options: TestabilityOptions = {
16
+ rootDir: '/test/project',
17
+ minCoverageRatio: 0.5,
18
+ };
19
+
20
+ expect(options.minCoverageRatio).toBe(0.5);
21
+ });
22
+
23
+ it('should allow custom test patterns', () => {
24
+ const options: TestabilityOptions = {
25
+ rootDir: '/test/project',
26
+ testPatterns: ['**/*.test.ts', '**/*.spec.ts', '**/*.e2e.ts'],
27
+ };
28
+
29
+ expect(options.testPatterns).toHaveLength(3);
30
+ });
31
+ });
32
+
33
+ describe('TestabilityReport', () => {
34
+ it('should structure summary correctly', () => {
35
+ const report: TestabilityReport = {
36
+ summary: {
37
+ sourceFiles: 100,
38
+ testFiles: 30,
39
+ coverageRatio: 0.3,
40
+ score: 60,
41
+ rating: 'moderate',
42
+ aiChangeSafetyRating: 'moderate-risk',
43
+ dimensions: {
44
+ testCoverageRatio: 30,
45
+ purityScore: 70,
46
+ dependencyInjectionScore: 50,
47
+ interfaceFocusScore: 80,
48
+ observabilityScore: 60,
49
+ },
50
+ },
51
+ issues: [],
52
+ rawData: {
53
+ sourceFiles: 100,
54
+ testFiles: 30,
55
+ pureFunctions: 80,
56
+ totalFunctions: 100,
57
+ injectionPatterns: 5,
58
+ totalClasses: 20,
59
+ bloatedInterfaces: 10,
60
+ totalInterfaces: 50,
61
+ externalStateMutations: 15,
62
+ hasTestFramework: true,
63
+ },
64
+ recommendations: ['Add more tests to reach 30% coverage'],
65
+ };
66
+
67
+ expect(report.summary.sourceFiles).toBe(100);
68
+ expect(report.summary.coverageRatio).toBe(0.3);
69
+ expect(report.rawData.hasTestFramework).toBe(true);
70
+ });
71
+ });
72
+ });
package/src/analyzer.ts CHANGED
@@ -115,7 +115,9 @@ function detectTestFramework(rootDir: string): boolean {
115
115
  try {
116
116
  const content = readFileSync(p, 'utf-8');
117
117
  if (m.deps.some((d) => content.includes(d))) return true;
118
- } catch {}
118
+ } catch {
119
+ // Ignore file read errors
120
+ }
119
121
  }
120
122
  }
121
123
  return false;
package/src/provider.ts CHANGED
@@ -1,11 +1,8 @@
1
1
  import {
2
- ToolProvider,
2
+ AnalysisResult,
3
+ createProvider,
3
4
  ToolName,
4
- SpokeOutput,
5
5
  ScanOptions,
6
- ToolScoringOutput,
7
- AnalysisResult,
8
- SpokeOutputSchema,
9
6
  } from '@aiready/core';
10
7
  import { analyzeTestability } from './analyzer';
11
8
  import { calculateTestabilityScore } from './scoring';
@@ -14,42 +11,35 @@ import { TestabilityOptions, TestabilityReport } from './types';
14
11
  /**
15
12
  * Testability Tool Provider
16
13
  */
17
- export const TestabilityProvider: ToolProvider = {
14
+ export const TestabilityProvider = createProvider({
18
15
  id: ToolName.TestabilityIndex,
19
16
  alias: ['testability', 'tests', 'verification'],
20
-
21
- async analyze(options: ScanOptions): Promise<SpokeOutput> {
22
- const report = await analyzeTestability(options as TestabilityOptions);
23
-
24
- const results: AnalysisResult[] = report.issues.map((i) => ({
25
- fileName: i.location.file,
26
- issues: [i] as any[],
17
+ version: '0.2.5',
18
+ defaultWeight: 10,
19
+ async analyzeReport(options: ScanOptions) {
20
+ return analyzeTestability(options as TestabilityOptions);
21
+ },
22
+ getResults(report): AnalysisResult[] {
23
+ return report.issues.map((issue) => ({
24
+ fileName: issue.location.file,
25
+ issues: [issue] as any[],
27
26
  metrics: {
28
27
  testabilityScore: report.summary.score,
29
28
  },
30
29
  }));
31
-
32
- return SpokeOutputSchema.parse({
33
- results,
34
- summary: report.summary,
35
- metadata: {
36
- toolName: ToolName.TestabilityIndex,
37
- version: '0.2.5',
38
- timestamp: new Date().toISOString(),
39
- rawData: report.rawData,
40
- },
41
- });
42
30
  },
43
-
44
- score(output: SpokeOutput, options: ScanOptions): ToolScoringOutput {
31
+ getSummary(report) {
32
+ return report.summary;
33
+ },
34
+ getMetadata(report) {
35
+ return { rawData: report.rawData };
36
+ },
37
+ score(output) {
45
38
  const report = {
46
39
  summary: output.summary,
47
40
  rawData: (output.metadata as any).rawData,
48
41
  recommendations: (output.summary as any).recommendations || [],
49
42
  } as unknown as TestabilityReport;
50
-
51
43
  return calculateTestabilityScore(report);
52
44
  },
53
-
54
- defaultWeight: 10,
55
- };
45
+ });