@aiready/testability 0.7.0 → 0.7.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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @aiready/testability@0.7.0 build /Users/pengcao/projects/aiready/packages/testability
2
+ > @aiready/testability@0.7.2 build /Users/pengcao/projects/aiready/packages/testability
3
3
  > tsup src/index.ts src/cli.ts --format cjs,esm --dts
4
4
 
5
5
  CLI Building entry: src/cli.ts, src/index.ts
@@ -8,15 +8,15 @@ CLI tsup v8.5.1
8
8
  CLI Target: es2020
9
9
  CJS Build start
10
10
  ESM Build start
11
- ESM dist/chunk-CISO2RDG.mjs 6.30 KB
12
- ESM dist/cli.mjs 4.33 KB
11
+ CJS dist/index.js 8.62 KB
12
+ CJS dist/cli.js 11.88 KB
13
+ CJS ⚡️ Build success in 24ms
13
14
  ESM dist/index.mjs 1.17 KB
14
- ESM ⚡️ Build success in 189ms
15
- CJS dist/cli.js 11.84 KB
16
- CJS dist/index.js 8.57 KB
17
- CJS ⚡️ Build success in 189ms
15
+ ESM dist/cli.mjs 4.33 KB
16
+ ESM dist/chunk-NC2LRFVJ.mjs 6.35 KB
17
+ ESM ⚡️ Build success in 25ms
18
18
  DTS Build start
19
- DTS ⚡️ Build success in 5102ms
19
+ DTS ⚡️ Build success in 9219ms
20
20
  DTS dist/cli.d.ts 20.00 B
21
21
  DTS dist/index.d.ts 2.42 KB
22
22
  DTS dist/cli.d.mts 20.00 B
@@ -1,18 +1,8 @@
1
1
 
2
- > @aiready/testability@0.6.23 test /Users/pengcao/projects/aiready/packages/testability
2
+ > @aiready/testability@0.7.1 test /Users/pengcao/projects/aiready/packages/testability
3
3
  > vitest run
4
4
 
5
5
 
6
6
   RUN  v4.0.18 /Users/pengcao/projects/aiready/packages/testability
7
7
 
8
- ✓ src/__tests__/types.test.ts (4 tests) 5ms
9
- ✓ src/__tests__/scoring.test.ts (6 tests) 4ms
10
- ✓ src/__tests__/provider.test.ts (2 tests) 4ms
11
- ✓ src/__tests__/analyzer.test.ts (5 tests) 1816ms
12
- ✓ detects test frameworks in multiple languages  1788ms
13
-
14
-  Test Files  4 passed (4)
15
-  Tests  17 passed (17)
16
-  Start at  15:17:54
17
-  Duration  3.76s (transform 942ms, setup 0ms, import 4.23s, tests 1.83s, environment 0ms)
18
-
8
+  ELIFECYCLE  Test failed. See above for more details.
package/README.md CHANGED
@@ -13,7 +13,6 @@ The "Verify" loop is the most expensive part of the AI agent workflow. Codebases
13
13
 
14
14
  - **Full Support:** TypeScript, JavaScript, Python, Java, Go, C#
15
15
  - **Capabilities:** Purity analysis, global state detection, DI pattern recognition.
16
- toxicology
17
16
 
18
17
  ## 🏛️ Architecture
19
18
 
@@ -22,11 +21,11 @@ The "Verify" loop is the most expensive part of the AI agent workflow. Codebases
22
21
 
23
22
 
24
23
  🎛️ @aiready/cli (orchestrator)
25
- │ │ │ │ │ │ │ │ │
26
- ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼
27
- [PAT] [CTX] [CON] [AMP] [DEP] [DOC] [SIG] [AGT] [TST]
28
- │ │ │ │ │ │ │ │ │
29
- └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
24
+ │ │ │ │ │ │ │ │ │
25
+ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼
26
+ [PAT] [CTX] [CON] [AMP] [DEP] [DOC] [SIG] [AGT] [TST] [CTR]
27
+ │ │ │ │ │ │ │ │ │
28
+ └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
30
29
 
31
30
 
32
31
  🏢 @aiready/core
@@ -36,7 +35,8 @@ Legend:
36
35
  CON = consistency AMP = change-amplification
37
36
  DEP = deps-health DOC = doc-drift
38
37
  SIG = ai-signal-clarity AGT = agent-grounding
39
- TST = testability ★ = YOU ARE HERE
38
+ TST = testability ★ CTR = contract-enforcement
39
+ ★ = YOU ARE HERE
40
40
  ```
41
41
 
42
42
  ## Features
@@ -0,0 +1,199 @@
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 < 50) {
144
+ issues.push({
145
+ type: IssueType.LowTestability,
146
+ dimension: "purity",
147
+ severity: Severity.Major,
148
+ message: `Only ${indexResult.dimensions.purityScore}% of functions appear pure \u2014 side-effectful code is harder for AI to verify safely.`,
149
+ location: { file: options.rootDir, line: 0 },
150
+ suggestion: "Refactor complex side-effectful logic into pure functions where possible."
151
+ });
152
+ }
153
+ return {
154
+ summary: {
155
+ sourceFiles: sourceFiles.length,
156
+ testFiles: testFiles.length,
157
+ coverageRatio: Math.round(actualRatio * 100) / 100,
158
+ score: indexResult.score,
159
+ rating: indexResult.rating,
160
+ aiChangeSafetyRating: indexResult.aiChangeSafetyRating,
161
+ dimensions: indexResult.dimensions
162
+ },
163
+ issues,
164
+ rawData: {
165
+ sourceFiles: sourceFiles.length,
166
+ testFiles: testFiles.length,
167
+ ...aggregated,
168
+ hasTestFramework
169
+ },
170
+ recommendations: indexResult.recommendations
171
+ };
172
+ }
173
+
174
+ // src/scoring.ts
175
+ import { ToolName, buildStandardToolScore } from "@aiready/core";
176
+ function calculateTestabilityScore(report) {
177
+ const { summary, rawData, recommendations } = report;
178
+ return buildStandardToolScore({
179
+ toolName: ToolName.TestabilityIndex,
180
+ score: summary.score,
181
+ rawData,
182
+ dimensions: summary.dimensions,
183
+ dimensionNames: {
184
+ testCoverageRatio: "Test Coverage",
185
+ purityScore: "Function Purity",
186
+ dependencyInjectionScore: "Dependency Injection",
187
+ interfaceFocusScore: "Interface Focus",
188
+ observabilityScore: "Observability"
189
+ },
190
+ recommendations,
191
+ recommendationImpact: summary.aiChangeSafetyRating === "blind-risk" ? 15 : 8,
192
+ rating: summary.aiChangeSafetyRating || summary.rating
193
+ });
194
+ }
195
+
196
+ export {
197
+ analyzeTestability,
198
+ calculateTestabilityScore
199
+ };
package/dist/cli.js CHANGED
@@ -86,7 +86,7 @@ async function analyzeTestability(options) {
86
86
  includeTests: true
87
87
  });
88
88
  const sourceFiles = allFiles.filter(
89
- (f) => !(0, import_core.isTestFile)(f, options.testPatterns)
89
+ (f) => !(0, import_core.isTestFile)(f, options.testPatterns) && !(0, import_core.isIgnorableSourceFile)(f)
90
90
  );
91
91
  const testFiles = allFiles.filter((f) => (0, import_core.isTestFile)(f, options.testPatterns));
92
92
  const aggregated = {
package/dist/cli.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  analyzeTestability,
4
4
  calculateTestabilityScore
5
- } from "./chunk-CISO2RDG.mjs";
5
+ } from "./chunk-NC2LRFVJ.mjs";
6
6
 
7
7
  // src/cli.ts
8
8
  import { Command } from "commander";
package/dist/index.js CHANGED
@@ -90,7 +90,7 @@ async function analyzeTestability(options) {
90
90
  includeTests: true
91
91
  });
92
92
  const sourceFiles = allFiles.filter(
93
- (f) => !(0, import_core.isTestFile)(f, options.testPatterns)
93
+ (f) => !(0, import_core.isTestFile)(f, options.testPatterns) && !(0, import_core.isIgnorableSourceFile)(f)
94
94
  );
95
95
  const testFiles = allFiles.filter((f) => (0, import_core.isTestFile)(f, options.testPatterns));
96
96
  const aggregated = {
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  analyzeTestability,
3
3
  calculateTestabilityScore
4
- } from "./chunk-CISO2RDG.mjs";
4
+ } from "./chunk-NC2LRFVJ.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.0",
3
+ "version": "0.7.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.24.0"
43
+ "@aiready/core": "0.24.2"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@types/node": "^24.0.0",
package/src/analyzer.ts CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  runBatchAnalysis,
7
7
  getParser,
8
8
  isTestFile,
9
+ isIgnorableSourceFile,
9
10
  detectTestFramework,
10
11
  } from '@aiready/core';
11
12
  import { readFileSync } from 'fs';
@@ -104,7 +105,7 @@ export async function analyzeTestability(
104
105
  });
105
106
 
106
107
  const sourceFiles = allFiles.filter(
107
- (f) => !isTestFile(f, options.testPatterns)
108
+ (f) => !isTestFile(f, options.testPatterns) && !isIgnorableSourceFile(f)
108
109
  );
109
110
  const testFiles = allFiles.filter((f) => isTestFile(f, options.testPatterns));
110
111