@aiready/testability 0.7.6 → 0.7.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.
@@ -1,23 +1,24 @@
1
-
2
- > @aiready/testability@0.7.6 build /Users/pengcao/projects/aiready/packages/testability
3
- > tsup src/index.ts src/cli.ts --format cjs,esm --dts
4
-
5
- CLI Building entry: src/cli.ts, src/index.ts
6
- CLI Using tsconfig: tsconfig.json
7
- CLI tsup v8.5.1
8
- CLI Target: es2020
9
- CJS Build start
10
- ESM Build start
11
- CJS dist/index.js 8.62 KB
12
- CJS dist/cli.js 11.88 KB
13
- CJS ⚡️ Build success in 126ms
14
- ESM dist/cli.mjs 4.33 KB
15
- ESM dist/index.mjs 1.17 KB
16
- ESM dist/chunk-NC2LRFVJ.mjs 6.35 KB
17
- ESM ⚡️ Build success in 127ms
18
- DTS Build start
19
- DTS ⚡️ Build success in 10364ms
20
- DTS dist/cli.d.ts 20.00 B
21
- DTS dist/index.d.ts 2.42 KB
22
- DTS dist/cli.d.mts 20.00 B
23
- DTS dist/index.d.mts 2.42 KB
1
+
2
+ 
3
+ > @aiready/testability@0.7.8 build /Users/pengcao/projects/aiready/packages/testability
4
+ > tsup src/index.ts src/cli.ts --format cjs,esm --dts
5
+
6
+ CLI Building entry: src/cli.ts, src/index.ts
7
+ CLI Using tsconfig: tsconfig.json
8
+ CLI tsup v8.5.1
9
+ CLI Target: es2020
10
+ CJS Build start
11
+ ESM Build start
12
+ ESM dist/chunk-YSXDYH7O.mjs 8.48 KB
13
+ ESM dist/index.mjs 1.17 KB
14
+ ESM dist/cli.mjs 4.33 KB
15
+ ESM ⚡️ Build success in 23ms
16
+ CJS dist/cli.js 14.11 KB
17
+ CJS dist/index.js 10.85 KB
18
+ CJS ⚡️ Build success in 23ms
19
+ DTS Build start
20
+ DTS ⚡️ Build success in 2347ms
21
+ DTS dist/cli.d.ts 20.00 B
22
+ DTS dist/index.d.ts 2.42 KB
23
+ DTS dist/cli.d.mts 20.00 B
24
+ DTS dist/index.d.mts 2.42 KB
@@ -0,0 +1,7 @@
1
+
2
+ 
3
+ > @aiready/testability@0.7.8 format-check /Users/pengcao/projects/aiready/packages/testability
4
+ > prettier --check . --ignore-path ../../.prettierignore
5
+
6
+ Checking formatting...
7
+ package.jsonREADME.mdsrc/__tests__/analyzer.test.tssrc/__tests__/provider.test.tssrc/__tests__/scoring.test.tssrc/__tests__/types.test.tssrc/analyzer.tssrc/cli.tssrc/index.tssrc/provider.tssrc/scoring.tssrc/types.tstsconfig.jsonAll matched files use Prettier code style!
@@ -1,4 +1,5 @@
1
-
2
- > @aiready/testability@0.6.20 lint /Users/pengcao/projects/aiready/packages/testability
3
- > eslint src
4
-
1
+
2
+ 
3
+ > @aiready/testability@0.7.8 lint /Users/pengcao/projects/aiready/packages/testability
4
+ > eslint src
5
+
@@ -1,18 +1,20 @@
1
-
2
- > @aiready/testability@0.7.5 test /Users/pengcao/projects/aiready/packages/testability
3
- > vitest run
4
-
5
-
6
-  RUN  v4.0.18 /Users/pengcao/projects/aiready/packages/testability
7
-
8
- ✓ src/__tests__/types.test.ts (4 tests) 6ms
9
- ✓ src/__tests__/scoring.test.ts (6 tests) 2ms
10
- ✓ src/__tests__/provider.test.ts (2 tests) 22ms
11
- ✓ src/__tests__/analyzer.test.ts (5 tests) 3869ms
12
- ✓ detects test frameworks in multiple languages  3833ms
13
-
14
-  Test Files  4 passed (4)
15
-  Tests  17 passed (17)
16
-  Start at  20:27:24
17
-  Duration  8.32s (transform 2.13s, setup 0ms, import 10.42s, tests 3.90s, environment 0ms)
18
-
1
+
2
+ 
3
+ > @aiready/testability@0.7.7 test /Users/pengcao/projects/aiready/packages/testability
4
+ > vitest run
5
+
6
+ [?25l
7
+  RUN  v4.0.18 /Users/pengcao/projects/aiready/packages/testability
8
+
9
+ ✓ src/__tests__/types.test.ts (4 tests) 2ms
10
+ ✓ src/__tests__/provider.test.ts (2 tests) 4ms
11
+ ✓ src/__tests__/scoring.test.ts (6 tests) 3ms
12
+ ✓ src/__tests__/analyzer.test.ts (5 tests) 2017ms
13
+ ✓ detects test frameworks in multiple languages  1988ms
14
+
15
+  Test Files  4 passed (4)
16
+  Tests  17 passed (17)
17
+  Start at  00:08:36
18
+  Duration  3.74s (transform 1.07s, setup 0ms, import 4.57s, tests 2.03s, environment 0ms)
19
+
20
+ [?25h
@@ -0,0 +1,5 @@
1
+
2
+ 
3
+ > @aiready/testability@0.7.8 type-check /Users/pengcao/projects/aiready/packages/testability
4
+ > tsc --noEmit
5
+
@@ -0,0 +1,243 @@
1
+ // src/analyzer.ts
2
+ import {
3
+ scanFiles,
4
+ calculateTestabilityIndex,
5
+ Severity,
6
+ IssueType,
7
+ runBatchAnalysis,
8
+ getParser,
9
+ isTestFile,
10
+ isIgnorableSourceFile,
11
+ detectTestFramework
12
+ } from "@aiready/core";
13
+ import { readFileSync } from "fs";
14
+ async function analyzeFileTestability(filePath) {
15
+ const result = {
16
+ pureFunctions: 0,
17
+ totalFunctions: 0,
18
+ injectionPatterns: 0,
19
+ totalClasses: 0,
20
+ bloatedInterfaces: 0,
21
+ totalInterfaces: 0,
22
+ externalStateMutations: 0
23
+ };
24
+ const parser = await getParser(filePath);
25
+ if (!parser) return result;
26
+ let code;
27
+ try {
28
+ code = readFileSync(filePath, "utf-8");
29
+ } catch {
30
+ return result;
31
+ }
32
+ try {
33
+ await parser.initialize();
34
+ const parseResult = parser.parse(code, filePath);
35
+ for (const exp of parseResult.exports) {
36
+ if (exp.type === "function") {
37
+ result.totalFunctions++;
38
+ if (exp.isPure) result.pureFunctions++;
39
+ if (exp.hasSideEffects) result.externalStateMutations++;
40
+ }
41
+ if (exp.type === "class") {
42
+ result.totalClasses++;
43
+ if (exp.parameters && exp.parameters.length > 0) {
44
+ result.injectionPatterns++;
45
+ }
46
+ const total = (exp.methodCount || 0) + (exp.propertyCount || 0);
47
+ if (total > 10) {
48
+ result.bloatedInterfaces++;
49
+ }
50
+ }
51
+ if (exp.type === "interface") {
52
+ result.totalInterfaces++;
53
+ const total = (exp.methodCount || 0) + (exp.propertyCount || 0);
54
+ if (total > 10) {
55
+ result.bloatedInterfaces++;
56
+ }
57
+ }
58
+ }
59
+ } catch (error) {
60
+ console.warn(`Testability: Failed to parse ${filePath}: ${error}`);
61
+ }
62
+ return result;
63
+ }
64
+ async function analyzeTestability(options) {
65
+ const allFiles = await scanFiles({
66
+ ...options,
67
+ include: options.include || ["**/*.{ts,tsx,js,jsx,py,java,cs,go}"],
68
+ includeTests: true
69
+ });
70
+ const sourceFiles = allFiles.filter(
71
+ (f) => !isTestFile(f, options.testPatterns) && !isIgnorableSourceFile(f)
72
+ );
73
+ const testFiles = allFiles.filter((f) => isTestFile(f, options.testPatterns));
74
+ const aggregated = {
75
+ pureFunctions: 0,
76
+ totalFunctions: 0,
77
+ injectionPatterns: 0,
78
+ totalClasses: 0,
79
+ bloatedInterfaces: 0,
80
+ totalInterfaces: 0,
81
+ externalStateMutations: 0
82
+ };
83
+ const fileDetails = [];
84
+ await runBatchAnalysis(
85
+ sourceFiles,
86
+ "analyzing files",
87
+ "testability",
88
+ options.onProgress,
89
+ async (f) => ({
90
+ filePath: f,
91
+ analysis: await analyzeFileTestability(f)
92
+ }),
93
+ (result) => {
94
+ const a = result.analysis;
95
+ for (const key of Object.keys(aggregated)) {
96
+ aggregated[key] += a[key];
97
+ }
98
+ fileDetails.push({
99
+ filePath: result.filePath,
100
+ pureFunctions: a.pureFunctions,
101
+ totalFunctions: a.totalFunctions
102
+ });
103
+ }
104
+ );
105
+ const hasTestFramework = detectTestFramework(options.rootDir);
106
+ const indexResult = calculateTestabilityIndex({
107
+ testFiles: testFiles.length,
108
+ sourceFiles: sourceFiles.length,
109
+ pureFunctions: aggregated.pureFunctions,
110
+ totalFunctions: Math.max(1, aggregated.totalFunctions),
111
+ injectionPatterns: aggregated.injectionPatterns,
112
+ totalClasses: Math.max(1, aggregated.totalClasses),
113
+ bloatedInterfaces: aggregated.bloatedInterfaces,
114
+ totalInterfaces: Math.max(1, aggregated.totalInterfaces),
115
+ externalStateMutations: aggregated.externalStateMutations,
116
+ hasTestFramework,
117
+ fileDetails
118
+ });
119
+ const issues = [];
120
+ const minCoverage = options.minCoverageRatio ?? 0.3;
121
+ const actualRatio = sourceFiles.length > 0 ? testFiles.length / sourceFiles.length : 0;
122
+ if (!hasTestFramework) {
123
+ issues.push({
124
+ type: IssueType.LowTestability,
125
+ dimension: "framework",
126
+ severity: Severity.Critical,
127
+ message: "No major testing framework detected \u2014 AI changes cannot be safely verified.",
128
+ location: { file: options.rootDir, line: 0 },
129
+ suggestion: "Add a testing framework (e.g., Jest, Pytest, JUnit) to enable automated verification."
130
+ });
131
+ }
132
+ if (actualRatio < minCoverage) {
133
+ const needed = Math.ceil(sourceFiles.length * minCoverage) - testFiles.length;
134
+ issues.push({
135
+ type: IssueType.LowTestability,
136
+ dimension: "test-coverage",
137
+ severity: actualRatio === 0 ? Severity.Critical : Severity.Major,
138
+ message: `Test ratio is ${Math.round(actualRatio * 100)}% (${testFiles.length} test files for ${sourceFiles.length} source files). Need at least ${Math.round(minCoverage * 100)}%.`,
139
+ location: { file: options.rootDir, line: 0 },
140
+ suggestion: `Add ~${needed} test file(s) to reach the ${Math.round(minCoverage * 100)}% minimum for safe AI assistance.`
141
+ });
142
+ }
143
+ if (indexResult.dimensions.purityScore < 70) {
144
+ const worstPurityFiles = (indexResult.fileMetrics || []).filter((m) => !m.isEntryPoint && m.purityScore < 50).sort((a, b) => a.purityScore - b.purityScore).slice(0, 5);
145
+ if (worstPurityFiles.length > 0) {
146
+ worstPurityFiles.forEach((file) => {
147
+ issues.push({
148
+ type: IssueType.LowTestability,
149
+ dimension: "purity",
150
+ severity: Severity.Major,
151
+ message: `File has only ${file.purityScore}% pure functions \u2014 logic is hard for AI to verify safely.`,
152
+ location: { file: file.filePath, line: 1 },
153
+ suggestion: "Extract side-effectful logic into pure, testable functions."
154
+ });
155
+ });
156
+ } else {
157
+ issues.push({
158
+ type: IssueType.LowTestability,
159
+ dimension: "purity",
160
+ severity: Severity.Major,
161
+ message: `Only ${indexResult.dimensions.purityScore}% of project functions are pure \u2014 reduces AI verification safety.`,
162
+ location: { file: options.rootDir, line: 0 },
163
+ suggestion: "Promote pure function patterns to improve codebase testability."
164
+ });
165
+ }
166
+ }
167
+ if (indexResult.dimensions.dependencyInjectionScore < 60) {
168
+ issues.push({
169
+ type: IssueType.LowTestability,
170
+ dimension: "dependency-injection",
171
+ severity: Severity.Major,
172
+ message: `Only ${indexResult.dimensions.dependencyInjectionScore}% of classes use dependency injection \u2014 hard to mock for AI verification.`,
173
+ location: { file: options.rootDir, line: 0 },
174
+ suggestion: "Use constructor-based dependency injection to make components mockable."
175
+ });
176
+ }
177
+ if (aggregated.bloatedInterfaces > 0) {
178
+ issues.push({
179
+ type: IssueType.LowTestability,
180
+ dimension: "interface-focus",
181
+ severity: Severity.Minor,
182
+ message: `Found ${aggregated.bloatedInterfaces} bloated interfaces/classes \u2014 large interfaces are harder for AI to implement correctly.`,
183
+ location: { file: options.rootDir, line: 0 },
184
+ suggestion: "Split large interfaces into smaller, focused ones (Interface Segregation Principle)."
185
+ });
186
+ }
187
+ if (indexResult.dimensions.observabilityScore < 60) {
188
+ issues.push({
189
+ type: IssueType.LowTestability,
190
+ dimension: "observability",
191
+ severity: Severity.Major,
192
+ message: `High rate of external state mutations detected (${indexResult.dimensions.observabilityScore}/100 observability) \u2014 hard for AI to reason about state changes.`,
193
+ location: { file: options.rootDir, line: 0 },
194
+ suggestion: "Prefer immutable data patterns and return values instead of mutating external state."
195
+ });
196
+ }
197
+ return {
198
+ summary: {
199
+ sourceFiles: sourceFiles.length,
200
+ testFiles: testFiles.length,
201
+ coverageRatio: Math.round(actualRatio * 100) / 100,
202
+ score: indexResult.score,
203
+ rating: indexResult.rating,
204
+ aiChangeSafetyRating: indexResult.aiChangeSafetyRating,
205
+ dimensions: indexResult.dimensions
206
+ },
207
+ issues,
208
+ rawData: {
209
+ sourceFiles: sourceFiles.length,
210
+ testFiles: testFiles.length,
211
+ ...aggregated,
212
+ hasTestFramework
213
+ },
214
+ recommendations: indexResult.recommendations
215
+ };
216
+ }
217
+
218
+ // src/scoring.ts
219
+ import { ToolName, buildStandardToolScore } from "@aiready/core";
220
+ function calculateTestabilityScore(report) {
221
+ const { summary, rawData, recommendations } = report;
222
+ return buildStandardToolScore({
223
+ toolName: ToolName.TestabilityIndex,
224
+ score: summary.score,
225
+ rawData,
226
+ dimensions: summary.dimensions,
227
+ dimensionNames: {
228
+ testCoverageRatio: "Test Coverage",
229
+ purityScore: "Function Purity",
230
+ dependencyInjectionScore: "Dependency Injection",
231
+ interfaceFocusScore: "Interface Focus",
232
+ observabilityScore: "Observability"
233
+ },
234
+ recommendations,
235
+ recommendationImpact: summary.aiChangeSafetyRating === "blind-risk" ? 15 : 8,
236
+ rating: summary.aiChangeSafetyRating || summary.rating
237
+ });
238
+ }
239
+
240
+ export {
241
+ analyzeTestability,
242
+ calculateTestabilityScore
243
+ };
package/dist/cli.js CHANGED
@@ -158,14 +158,58 @@ async function analyzeTestability(options) {
158
158
  suggestion: `Add ~${needed} test file(s) to reach the ${Math.round(minCoverage * 100)}% minimum for safe AI assistance.`
159
159
  });
160
160
  }
161
- if (indexResult.dimensions.purityScore < 50) {
161
+ if (indexResult.dimensions.purityScore < 70) {
162
+ const worstPurityFiles = (indexResult.fileMetrics || []).filter((m) => !m.isEntryPoint && m.purityScore < 50).sort((a, b) => a.purityScore - b.purityScore).slice(0, 5);
163
+ if (worstPurityFiles.length > 0) {
164
+ worstPurityFiles.forEach((file) => {
165
+ issues.push({
166
+ type: import_core.IssueType.LowTestability,
167
+ dimension: "purity",
168
+ severity: import_core.Severity.Major,
169
+ message: `File has only ${file.purityScore}% pure functions \u2014 logic is hard for AI to verify safely.`,
170
+ location: { file: file.filePath, line: 1 },
171
+ suggestion: "Extract side-effectful logic into pure, testable functions."
172
+ });
173
+ });
174
+ } else {
175
+ issues.push({
176
+ type: import_core.IssueType.LowTestability,
177
+ dimension: "purity",
178
+ severity: import_core.Severity.Major,
179
+ message: `Only ${indexResult.dimensions.purityScore}% of project functions are pure \u2014 reduces AI verification safety.`,
180
+ location: { file: options.rootDir, line: 0 },
181
+ suggestion: "Promote pure function patterns to improve codebase testability."
182
+ });
183
+ }
184
+ }
185
+ if (indexResult.dimensions.dependencyInjectionScore < 60) {
186
+ issues.push({
187
+ type: import_core.IssueType.LowTestability,
188
+ dimension: "dependency-injection",
189
+ severity: import_core.Severity.Major,
190
+ message: `Only ${indexResult.dimensions.dependencyInjectionScore}% of classes use dependency injection \u2014 hard to mock for AI verification.`,
191
+ location: { file: options.rootDir, line: 0 },
192
+ suggestion: "Use constructor-based dependency injection to make components mockable."
193
+ });
194
+ }
195
+ if (aggregated.bloatedInterfaces > 0) {
196
+ issues.push({
197
+ type: import_core.IssueType.LowTestability,
198
+ dimension: "interface-focus",
199
+ severity: import_core.Severity.Minor,
200
+ message: `Found ${aggregated.bloatedInterfaces} bloated interfaces/classes \u2014 large interfaces are harder for AI to implement correctly.`,
201
+ location: { file: options.rootDir, line: 0 },
202
+ suggestion: "Split large interfaces into smaller, focused ones (Interface Segregation Principle)."
203
+ });
204
+ }
205
+ if (indexResult.dimensions.observabilityScore < 60) {
162
206
  issues.push({
163
207
  type: import_core.IssueType.LowTestability,
164
- dimension: "purity",
208
+ dimension: "observability",
165
209
  severity: import_core.Severity.Major,
166
- message: `Only ${indexResult.dimensions.purityScore}% of functions appear pure \u2014 side-effectful code is harder for AI to verify safely.`,
210
+ message: `High rate of external state mutations detected (${indexResult.dimensions.observabilityScore}/100 observability) \u2014 hard for AI to reason about state changes.`,
167
211
  location: { file: options.rootDir, line: 0 },
168
- suggestion: "Refactor complex side-effectful logic into pure functions where possible."
212
+ suggestion: "Prefer immutable data patterns and return values instead of mutating external state."
169
213
  });
170
214
  }
171
215
  return {
package/dist/cli.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  analyzeTestability,
4
4
  calculateTestabilityScore
5
- } from "./chunk-NC2LRFVJ.mjs";
5
+ } from "./chunk-YSXDYH7O.mjs";
6
6
 
7
7
  // src/cli.ts
8
8
  import { Command } from "commander";
package/dist/index.js CHANGED
@@ -162,14 +162,58 @@ async function analyzeTestability(options) {
162
162
  suggestion: `Add ~${needed} test file(s) to reach the ${Math.round(minCoverage * 100)}% minimum for safe AI assistance.`
163
163
  });
164
164
  }
165
- if (indexResult.dimensions.purityScore < 50) {
165
+ if (indexResult.dimensions.purityScore < 70) {
166
+ const worstPurityFiles = (indexResult.fileMetrics || []).filter((m) => !m.isEntryPoint && m.purityScore < 50).sort((a, b) => a.purityScore - b.purityScore).slice(0, 5);
167
+ if (worstPurityFiles.length > 0) {
168
+ worstPurityFiles.forEach((file) => {
169
+ issues.push({
170
+ type: import_core.IssueType.LowTestability,
171
+ dimension: "purity",
172
+ severity: import_core.Severity.Major,
173
+ message: `File has only ${file.purityScore}% pure functions \u2014 logic is hard for AI to verify safely.`,
174
+ location: { file: file.filePath, line: 1 },
175
+ suggestion: "Extract side-effectful logic into pure, testable functions."
176
+ });
177
+ });
178
+ } else {
179
+ issues.push({
180
+ type: import_core.IssueType.LowTestability,
181
+ dimension: "purity",
182
+ severity: import_core.Severity.Major,
183
+ message: `Only ${indexResult.dimensions.purityScore}% of project functions are pure \u2014 reduces AI verification safety.`,
184
+ location: { file: options.rootDir, line: 0 },
185
+ suggestion: "Promote pure function patterns to improve codebase testability."
186
+ });
187
+ }
188
+ }
189
+ if (indexResult.dimensions.dependencyInjectionScore < 60) {
190
+ issues.push({
191
+ type: import_core.IssueType.LowTestability,
192
+ dimension: "dependency-injection",
193
+ severity: import_core.Severity.Major,
194
+ message: `Only ${indexResult.dimensions.dependencyInjectionScore}% of classes use dependency injection \u2014 hard to mock for AI verification.`,
195
+ location: { file: options.rootDir, line: 0 },
196
+ suggestion: "Use constructor-based dependency injection to make components mockable."
197
+ });
198
+ }
199
+ if (aggregated.bloatedInterfaces > 0) {
200
+ issues.push({
201
+ type: import_core.IssueType.LowTestability,
202
+ dimension: "interface-focus",
203
+ severity: import_core.Severity.Minor,
204
+ message: `Found ${aggregated.bloatedInterfaces} bloated interfaces/classes \u2014 large interfaces are harder for AI to implement correctly.`,
205
+ location: { file: options.rootDir, line: 0 },
206
+ suggestion: "Split large interfaces into smaller, focused ones (Interface Segregation Principle)."
207
+ });
208
+ }
209
+ if (indexResult.dimensions.observabilityScore < 60) {
166
210
  issues.push({
167
211
  type: import_core.IssueType.LowTestability,
168
- dimension: "purity",
212
+ dimension: "observability",
169
213
  severity: import_core.Severity.Major,
170
- message: `Only ${indexResult.dimensions.purityScore}% of functions appear pure \u2014 side-effectful code is harder for AI to verify safely.`,
214
+ message: `High rate of external state mutations detected (${indexResult.dimensions.observabilityScore}/100 observability) \u2014 hard for AI to reason about state changes.`,
171
215
  location: { file: options.rootDir, line: 0 },
172
- suggestion: "Refactor complex side-effectful logic into pure functions where possible."
216
+ suggestion: "Prefer immutable data patterns and return values instead of mutating external state."
173
217
  });
174
218
  }
175
219
  return {
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  analyzeTestability,
3
3
  calculateTestabilityScore
4
- } from "./chunk-NC2LRFVJ.mjs";
4
+ } from "./chunk-YSXDYH7O.mjs";
5
5
 
6
6
  // src/index.ts
7
7
  import { ToolRegistry } from "@aiready/core";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiready/testability",
3
- "version": "0.7.6",
3
+ "version": "0.7.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.24.6"
43
+ "@aiready/core": "0.24.8"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@types/node": "^24.0.0",
@@ -57,6 +57,8 @@
57
57
  "test": "vitest run",
58
58
  "lint": "eslint src",
59
59
  "clean": "rm -rf dist",
60
- "release": "pnpm build && pnpm publish --no-git-checks"
60
+ "release": "pnpm build && pnpm publish --no-git-checks",
61
+ "type-check": "tsc --noEmit",
62
+ "format-check": "prettier --check . --ignore-path ../../.prettierignore"
61
63
  }
62
64
  }
package/src/analyzer.ts CHANGED
@@ -197,15 +197,70 @@ export async function analyzeTestability(
197
197
  });
198
198
  }
199
199
 
200
- if (indexResult.dimensions.purityScore < 50) {
200
+ if (indexResult.dimensions.purityScore < 70) {
201
+ const worstPurityFiles = (indexResult.fileMetrics || [])
202
+ .filter((m) => !m.isEntryPoint && m.purityScore < 50)
203
+ .sort((a, b) => a.purityScore - b.purityScore)
204
+ .slice(0, 5);
205
+
206
+ if (worstPurityFiles.length > 0) {
207
+ worstPurityFiles.forEach((file) => {
208
+ issues.push({
209
+ type: IssueType.LowTestability,
210
+ dimension: 'purity',
211
+ severity: Severity.Major,
212
+ message: `File has only ${file.purityScore}% pure functions — logic is hard for AI to verify safely.`,
213
+ location: { file: file.filePath, line: 1 },
214
+ suggestion:
215
+ 'Extract side-effectful logic into pure, testable functions.',
216
+ });
217
+ });
218
+ } else {
219
+ issues.push({
220
+ type: IssueType.LowTestability,
221
+ dimension: 'purity',
222
+ severity: Severity.Major,
223
+ message: `Only ${indexResult.dimensions.purityScore}% of project functions are pure — reduces AI verification safety.`,
224
+ location: { file: options.rootDir, line: 0 },
225
+ suggestion:
226
+ 'Promote pure function patterns to improve codebase testability.',
227
+ });
228
+ }
229
+ }
230
+
231
+ if (indexResult.dimensions.dependencyInjectionScore < 60) {
232
+ issues.push({
233
+ type: IssueType.LowTestability,
234
+ dimension: 'dependency-injection',
235
+ severity: Severity.Major,
236
+ message: `Only ${indexResult.dimensions.dependencyInjectionScore}% of classes use dependency injection — hard to mock for AI verification.`,
237
+ location: { file: options.rootDir, line: 0 },
238
+ suggestion:
239
+ 'Use constructor-based dependency injection to make components mockable.',
240
+ });
241
+ }
242
+
243
+ if (aggregated.bloatedInterfaces > 0) {
244
+ issues.push({
245
+ type: IssueType.LowTestability,
246
+ dimension: 'interface-focus',
247
+ severity: Severity.Minor,
248
+ message: `Found ${aggregated.bloatedInterfaces} bloated interfaces/classes — large interfaces are harder for AI to implement correctly.`,
249
+ location: { file: options.rootDir, line: 0 },
250
+ suggestion:
251
+ 'Split large interfaces into smaller, focused ones (Interface Segregation Principle).',
252
+ });
253
+ }
254
+
255
+ if (indexResult.dimensions.observabilityScore < 60) {
201
256
  issues.push({
202
257
  type: IssueType.LowTestability,
203
- dimension: 'purity',
258
+ dimension: 'observability',
204
259
  severity: Severity.Major,
205
- message: `Only ${indexResult.dimensions.purityScore}% of functions appear pure side-effectful code is harder for AI to verify safely.`,
260
+ message: `High rate of external state mutations detected (${indexResult.dimensions.observabilityScore}/100 observability)hard for AI to reason about state changes.`,
206
261
  location: { file: options.rootDir, line: 0 },
207
262
  suggestion:
208
- 'Refactor complex side-effectful logic into pure functions where possible.',
263
+ 'Prefer immutable data patterns and return values instead of mutating external state.',
209
264
  });
210
265
  }
211
266